mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-04-05 03:38:48 +02:00
Install/upgrade fixes: stdin for pipe, branch archive, Python/composer paths, MariaDB version, web server order; FTP quota and docs
This commit is contained in:
@@ -580,7 +580,37 @@ cleanup_existing_cyberpanel() {
|
||||
|
||||
# Function to install CyberPanel directly using the working method
|
||||
install_cyberpanel_direct() {
|
||||
# Ask MariaDB version first (even in no-confirmation/auto mode) if not set via --mariadb-version
|
||||
# Ask web server (OpenLiteSpeed vs LiteSpeed Enterprise) BEFORE MariaDB; default OpenLiteSpeed
|
||||
if [ -z "$LS_ENT" ]; then
|
||||
if [ "$AUTO_INSTALL" = true ]; then
|
||||
LS_ENT=""
|
||||
echo " Using OpenLiteSpeed (auto mode)."
|
||||
else
|
||||
echo ""
|
||||
echo " Web server: 1) OpenLiteSpeed (default), 2) LiteSpeed Enterprise"
|
||||
read -r -t 60 -p " Enter 1 or 2 [1]: " LS_CHOICE || true
|
||||
LS_CHOICE="${LS_CHOICE:-1}"
|
||||
LS_CHOICE="${LS_CHOICE// /}"
|
||||
if [ "$LS_CHOICE" = "2" ]; then
|
||||
echo " LiteSpeed Enterprise selected. Enter serial/key (required):"
|
||||
read -r -t 120 -p " Serial: " LS_SERIAL || true
|
||||
LS_SERIAL="${LS_SERIAL:-}"
|
||||
if [ -z "$LS_SERIAL" ]; then
|
||||
echo " No serial provided. Defaulting to OpenLiteSpeed."
|
||||
LS_ENT=""
|
||||
else
|
||||
LS_ENT="ent"
|
||||
echo " Using LiteSpeed Enterprise with provided serial."
|
||||
fi
|
||||
else
|
||||
LS_ENT=""
|
||||
echo " Using OpenLiteSpeed."
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
|
||||
# Ask MariaDB version (after web server choice) if not set via --mariadb-version
|
||||
if [ -z "$MARIADB_VER" ]; then
|
||||
echo ""
|
||||
echo " MariaDB version: 10.11, 11.8 (LTS, default) or 12.1?"
|
||||
@@ -1090,7 +1120,10 @@ except:
|
||||
# install.py requires publicip as first positional argument
|
||||
local install_args=("$server_ip")
|
||||
|
||||
# Add optional arguments based on user preferences
|
||||
# Web server: OpenLiteSpeed (default) or LiteSpeed Enterprise (--ent + --serial)
|
||||
if [ -n "$LS_ENT" ] && [ -n "$LS_SERIAL" ]; then
|
||||
install_args+=("--ent" "$LS_ENT" "--serial" "$LS_SERIAL")
|
||||
fi
|
||||
# Default: OpenLiteSpeed, Full installation (postfix, powerdns, ftp), Local MySQL
|
||||
install_args+=("--postfix" "ON")
|
||||
install_args+=("--powerdns" "ON")
|
||||
@@ -2686,9 +2719,9 @@ parse_arguments() {
|
||||
echo "Options:"
|
||||
echo " -b, --branch BRANCH Install from specific branch/commit"
|
||||
echo " -v, --version VER Install specific version (auto-adds v prefix)"
|
||||
echo " --mariadb-version VER MariaDB version: 10.11, 11.8 or 12.1 (asked first if omitted)"
|
||||
echo " --mariadb-version VER MariaDB version: 10.11, 11.8 or 12.1 (asked after web server)"
|
||||
echo " --debug Enable debug mode"
|
||||
echo " --auto Auto mode without prompts (MariaDB still asked first unless --mariadb-version)"
|
||||
echo " --auto Auto mode: OpenLiteSpeed + MariaDB 11.8 unless --mariadb-version set"
|
||||
echo " -h, --help Show this help message"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
|
||||
@@ -405,48 +405,11 @@ class preFlightsChecks:
|
||||
except Exception as e:
|
||||
self.stdOut("Warning: Could not remove compat packages: " + str(e), 0)
|
||||
|
||||
# Check if MariaDB is already installed before attempting installation
|
||||
# Do NOT install MariaDB here with plain dnf (that would install distro default 10.11).
|
||||
# installMySQL() runs later with the user's chosen version (--mariadb-version: 10.11, 11.8 or 12.1).
|
||||
is_installed, installed_version, major_minor = self.checkExistingMariaDB()
|
||||
|
||||
if is_installed:
|
||||
self.stdOut(f"MariaDB/MySQL is already installed (version: {installed_version}), skipping installation", 1)
|
||||
mariadb_installed = True
|
||||
else:
|
||||
# Install MariaDB with enhanced AlmaLinux 9.6 support
|
||||
self.stdOut("Installing MariaDB for AlmaLinux 9.6...", 1)
|
||||
|
||||
# Try multiple installation methods for maximum compatibility
|
||||
mariadb_commands = [
|
||||
"dnf install -y mariadb-server mariadb-devel mariadb-client --skip-broken --nobest",
|
||||
"dnf install -y mariadb-server mariadb-devel mariadb-client --allowerasing",
|
||||
"dnf install -y mariadb-server mariadb-devel --skip-broken --nobest --allowerasing",
|
||||
"dnf install -y mariadb-server --skip-broken --nobest --allowerasing"
|
||||
]
|
||||
|
||||
mariadb_installed = False
|
||||
for cmd in mariadb_commands:
|
||||
try:
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=300)
|
||||
if result.returncode == 0:
|
||||
mariadb_installed = True
|
||||
self.stdOut(f"MariaDB installed successfully with command: {cmd}", 1)
|
||||
break
|
||||
except subprocess.TimeoutExpired:
|
||||
self.stdOut(f"Timeout installing MariaDB with command: {cmd}", 0)
|
||||
continue
|
||||
except Exception as e:
|
||||
self.stdOut(f"Error installing MariaDB with command: {cmd} - {str(e)}", 0)
|
||||
continue
|
||||
|
||||
if not mariadb_installed:
|
||||
self.stdOut("MariaDB installation failed, trying MySQL as fallback...", 0)
|
||||
try:
|
||||
command = "dnf install -y mysql-server mysql-devel --skip-broken --nobest --allowerasing"
|
||||
self.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||
self.stdOut("MySQL installed as fallback for MariaDB", 1)
|
||||
mariadb_installed = True
|
||||
except:
|
||||
self.stdOut("Both MariaDB and MySQL installation failed", 0)
|
||||
self.stdOut(f"MariaDB/MySQL already installed (version: {installed_version}), skipping", 1)
|
||||
|
||||
# Install additional required packages
|
||||
self.stdOut("Installing additional required packages...", 1)
|
||||
@@ -5804,18 +5767,22 @@ milter_default_action = accept
|
||||
|
||||
os.chdir(self.cwd)
|
||||
|
||||
# Download composer.sh if missing (e.g. when run from temp dir without repo file)
|
||||
composer_sh = os.path.join(self.cwd, "composer.sh")
|
||||
if not os.path.exists(composer_sh) or not os.path.isfile(composer_sh):
|
||||
# Download composer.sh to a known path so chmod never fails with "cannot access 'composer.sh'"
|
||||
composer_sh = "/tmp/composer.sh"
|
||||
if not os.path.isfile(composer_sh):
|
||||
command = "wget -q https://cyberpanel.sh/composer.sh -O " + composer_sh
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||
|
||||
if os.path.exists(composer_sh):
|
||||
command = "chmod +x " + composer_sh
|
||||
if not os.path.isfile(composer_sh):
|
||||
command = "curl -sSL https://cyberpanel.sh/composer.sh -o " + composer_sh
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||
|
||||
command = "bash " + os.path.abspath(composer_sh)
|
||||
preFlightsChecks.call(command, self.distro, "./composer.sh", command, 1, 0, os.EX_OSERR, True)
|
||||
if os.path.isfile(composer_sh):
|
||||
command = "chmod +x " + composer_sh
|
||||
preFlightsChecks.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||
command = "bash " + composer_sh
|
||||
preFlightsChecks.call(command, self.distro, "composer.sh", command, 1, 0, os.EX_OSERR, True)
|
||||
else:
|
||||
logging.InstallLog.writeToFile("composer.sh download failed, skipping [setupPHPAndComposer]", 0)
|
||||
|
||||
except OSError as msg:
|
||||
logging.InstallLog.writeToFile('[ERROR] ' + str(msg) + " [setupPHPAndComposer]")
|
||||
|
||||
@@ -101,19 +101,24 @@ class ApplicationInstaller(multi.Thread):
|
||||
|
||||
@staticmethod
|
||||
def setupComposer():
|
||||
|
||||
if os.path.exists('composer.sh'):
|
||||
os.remove('composer.sh')
|
||||
|
||||
composer_sh = '/tmp/composer.sh'
|
||||
if not os.path.exists('/usr/bin/composer'):
|
||||
command = "wget https://cyberpanel.sh/composer.sh"
|
||||
ProcessUtilities.executioner(command, 'root', True)
|
||||
|
||||
command = "chmod +x composer.sh"
|
||||
ProcessUtilities.executioner(command, 'root', True)
|
||||
|
||||
command = "./composer.sh"
|
||||
ProcessUtilities.executioner(command, 'root', True)
|
||||
try:
|
||||
if os.path.exists(composer_sh):
|
||||
os.remove(composer_sh)
|
||||
command = "wget -q https://cyberpanel.sh/composer.sh -O " + composer_sh
|
||||
ProcessUtilities.executioner(command, 'root', True)
|
||||
if not os.path.isfile(composer_sh):
|
||||
command = "curl -sSL https://cyberpanel.sh/composer.sh -o " + composer_sh
|
||||
ProcessUtilities.executioner(command, 'root', True)
|
||||
if not os.path.isfile(composer_sh):
|
||||
return
|
||||
command = "chmod +x " + composer_sh
|
||||
ProcessUtilities.executioner(command, 'root', True)
|
||||
command = "bash " + composer_sh
|
||||
ProcessUtilities.executioner(command, 'root', True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def InstallNodeJS(self):
|
||||
|
||||
|
||||
@@ -1307,18 +1307,26 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout';
|
||||
|
||||
@staticmethod
|
||||
def setupComposer():
|
||||
|
||||
if os.path.exists('composer.sh'):
|
||||
os.remove('composer.sh')
|
||||
|
||||
command = "wget https://cyberpanel.sh/composer.sh"
|
||||
Upgrade.executioner(command, 0)
|
||||
|
||||
command = "chmod +x composer.sh"
|
||||
Upgrade.executioner(command, 0)
|
||||
|
||||
command = "./composer.sh"
|
||||
Upgrade.executioner(command, 0)
|
||||
composer_sh = '/tmp/composer.sh'
|
||||
try:
|
||||
if os.path.exists(composer_sh):
|
||||
os.remove(composer_sh)
|
||||
# Download to known path so chmod/run work regardless of cwd
|
||||
command = "wget -q https://cyberpanel.sh/composer.sh -O " + composer_sh
|
||||
Upgrade.executioner(command, 0)
|
||||
if not os.path.isfile(composer_sh):
|
||||
command = "curl -sSL https://cyberpanel.sh/composer.sh -o " + composer_sh
|
||||
Upgrade.executioner(command, 0)
|
||||
if not os.path.isfile(composer_sh):
|
||||
Upgrade.stdOut("composer.sh download failed, skipping", 0)
|
||||
return
|
||||
command = "chmod +x " + composer_sh
|
||||
Upgrade.executioner(command, 0)
|
||||
command = "bash " + composer_sh
|
||||
Upgrade.executioner(command, 0)
|
||||
except Exception as e:
|
||||
ErrorSanitizer.log_error_securely(e, 'setupComposer')
|
||||
Upgrade.stdOut("setupComposer error (non-fatal)", 0)
|
||||
|
||||
@staticmethod
|
||||
def downoad_and_install_raindloop():
|
||||
|
||||
120
to-do/AWS-CURSOR-REMOTE-SSH-SETUP.md
Normal file
120
to-do/AWS-CURSOR-REMOTE-SSH-SETUP.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# AWS EC2 + Cursor Remote-SSH – Full Setup Guide
|
||||
|
||||
Use this guide to get **aws-server** (3.144.171.128) working with Cursor Remote-SSH.
|
||||
Do the steps in order. Everything is copy-paste ready.
|
||||
|
||||
---
|
||||
|
||||
## 1. Windows SSH config
|
||||
|
||||
**File:** `C:\Users\kimsk\.ssh\config`
|
||||
|
||||
- Open the file in Notepad or Cursor.
|
||||
- Find the `Host aws-server` block and replace it entirely with the block below (or add it if missing).
|
||||
- Use **straight double quotes** `"`, not curly quotes. Path uses forward slashes to avoid issues.
|
||||
|
||||
**Exact block to use (port 22 – default):**
|
||||
|
||||
```
|
||||
Host aws-server
|
||||
HostName 3.144.171.128
|
||||
User ec2-user
|
||||
Port 22
|
||||
IdentityFile "D:/OneDrive - v-man/Priv/VPS/Cyberpanel.pem"
|
||||
```
|
||||
|
||||
- Save and close.
|
||||
- If you later confirm SSH on the instance is on port 2222, change `Port 22` to `Port 2222` and add an inbound rule for 2222 in the Security Group (see step 3).
|
||||
|
||||
---
|
||||
|
||||
## 2. AWS Security Group – allow SSH (port 22)
|
||||
|
||||
1. **AWS Console** → **EC2** → **Instances**.
|
||||
2. Select the instance whose **Public IPv4** is **3.144.171.128**.
|
||||
3. Open the **Security** tab → click the **Security group** name (e.g. `sg-xxxxx`).
|
||||
4. **Edit inbound rules** → **Add rule**:
|
||||
- **Type:** SSH
|
||||
- **Port:** 22
|
||||
- **Source:** **My IP** (recommended) or **Anywhere-IPv4** (`0.0.0.0/0`) for testing only.
|
||||
5. **Save rules**.
|
||||
|
||||
If you use port 2222 on the instance, add another rule: **Custom TCP**, port **2222**, source **My IP** (or **Anywhere-IPv4** for testing).
|
||||
|
||||
---
|
||||
|
||||
## 3. Start SSH on the instance (fix “Connection refused”)
|
||||
|
||||
You must run commands on the instance without using SSH from your PC. Use one of these.
|
||||
|
||||
### Option A: EC2 Instance Connect (simplest)
|
||||
|
||||
1. **EC2** → **Instances** → select the instance (3.144.171.128).
|
||||
2. Click **Connect**.
|
||||
3. Open the **EC2 Instance Connect** tab → **Connect** (browser shell).
|
||||
|
||||
In the browser terminal, run:
|
||||
|
||||
```bash
|
||||
sudo systemctl status sshd
|
||||
sudo systemctl start sshd
|
||||
sudo systemctl enable sshd
|
||||
sudo ss -tlnp | grep 22
|
||||
```
|
||||
|
||||
You should see `sshd` listening on port 22. Then close the browser and try Cursor.
|
||||
|
||||
### Option B: Session Manager
|
||||
|
||||
1. **EC2** → **Instances** → select the instance → **Connect**.
|
||||
2. Choose **Session Manager** → **Connect**.
|
||||
3. Run the same commands as in Option A.
|
||||
|
||||
### Option C: SSH is on port 2222
|
||||
|
||||
If you know SSH was moved to 2222 on this instance:
|
||||
|
||||
1. In the Security Group, add an **inbound rule**: **Custom TCP**, port **2222**, source **My IP** (or **Anywhere-IPv4** for testing).
|
||||
2. In your SSH config, set `Port 2222` for `aws-server` (see step 1).
|
||||
3. Test (see step 4).
|
||||
|
||||
---
|
||||
|
||||
## 4. Test from Windows
|
||||
|
||||
Open **PowerShell** and run:
|
||||
|
||||
```powershell
|
||||
ssh -i "D:/OneDrive - v-man/Priv/VPS/Cyberpanel.pem" -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new ec2-user@3.144.171.128
|
||||
```
|
||||
|
||||
- If it asks for a host key, type `yes`.
|
||||
- If you get a shell prompt, SSH works. Type `exit` to close.
|
||||
- If you get **Connection refused**: SSH is not listening on 22 (or 2222); repeat step 3 (Instance Connect / Session Manager) and ensure `sshd` is running and listening on the port you use.
|
||||
- If you get **Connection timed out**: Security Group is still blocking the port; recheck step 2 and that you edited the security group attached to this instance.
|
||||
|
||||
---
|
||||
|
||||
## 5. Connect from Cursor
|
||||
|
||||
1. In Cursor: **Ctrl+Shift+P** (or **Cmd+Shift+P** on Mac) → **Remote-SSH: Connect to Host**.
|
||||
2. Choose **aws-server** (or type `aws-server`).
|
||||
3. Wait for the remote window to open. Cursor AI (Chat, Composer) works in that window as usual.
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] SSH config has the `aws-server` block with correct `IdentityFile` and `Port` (22 or 2222).
|
||||
- [ ] Security Group has an inbound rule for the SSH port (22 or 2222) from My IP (or 0.0.0.0/0 for testing).
|
||||
- [ ] `sshd` is running on the instance (started via Instance Connect or Session Manager).
|
||||
- [ ] `ssh ... ec2-user@3.144.171.128` works in PowerShell.
|
||||
- [ ] Cursor **Connect to Host** → **aws-server** succeeds.
|
||||
|
||||
---
|
||||
|
||||
## If it still fails
|
||||
|
||||
- **Connection refused** → Instance side: start/enable `sshd` and confirm it listens on the port you use (step 3).
|
||||
- **Connection timed out** → Network: open that port in the instance’s Security Group (step 2).
|
||||
- **Permission denied (publickey)** → Wrong key or user: confirm the .pem is the one for this instance and the user is `ec2-user` (Amazon Linux) or `ubuntu` (Ubuntu AMI).
|
||||
21
to-do/FTP-QUOTA-BROWSER-TEST-CHECKLIST.md
Normal file
21
to-do/FTP-QUOTA-BROWSER-TEST-CHECKLIST.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# FTP Quota Management – Browser Test Checklist
|
||||
|
||||
Use after deploying latest code. Open: `/ftp/quotaManagement`
|
||||
|
||||
## 1. Page load – status
|
||||
- **Pure-FTPd stopped:** Yellow warning "Pure-FTPd is not running. Please enable Pure-FTPd first (Server Status → Services)..." and Enable button disabled/hidden.
|
||||
- **Pure-FTPd running, quota on:** Green "FTP Quota system is already enabled"; button disabled.
|
||||
- **Pure-FTPd running, quota off:** Blue info and enabled "Enable FTP Quota System" button.
|
||||
|
||||
## 2. Click Enable
|
||||
- If FTP was running: success message and UI switches to "already enabled". No "Pure-FTPd did not start" error.
|
||||
- If FTP was stopped: API returns "Pure-FTPd is not running. Please enable Pure-FTPd first...".
|
||||
|
||||
## 3. Table
|
||||
- Quotas table loads; Refresh works.
|
||||
|
||||
## 4. One-time fix on server (if needed)
|
||||
```bash
|
||||
sudo sed -i 's/^Quota.*/Quota 100000:100000/' /etc/pure-ftpd/pure-ftpd.conf
|
||||
sudo systemctl start pure-ftpd
|
||||
```
|
||||
180
to-do/INSTALL-UPGRADE-DOWNGRADE-COMMANDS.md
Normal file
180
to-do/INSTALL-UPGRADE-DOWNGRADE-COMMANDS.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# CyberPanel Install, Upgrade, and Downgrade Commands
|
||||
|
||||
Reference for all standard and branch-specific install/upgrade/downgrade commands (master3395 fork and upstream).
|
||||
|
||||
---
|
||||
|
||||
## Fresh install
|
||||
|
||||
### One-liner (official / upstream)
|
||||
|
||||
```bash
|
||||
sh <(curl https://cyberpanel.net/install.sh)
|
||||
```
|
||||
|
||||
### One-liner with sudo (if not root)
|
||||
|
||||
```bash
|
||||
curl -sO https://cyberpanel.net/install.sh && sudo bash install.sh
|
||||
# or
|
||||
curl -sL https://cyberpanel.net/install.sh | sudo bash -s --
|
||||
```
|
||||
|
||||
### Install from master3395 fork (this repo)
|
||||
|
||||
**Stable:**
|
||||
|
||||
```bash
|
||||
curl -sL https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel.sh | sudo bash -s --
|
||||
```
|
||||
|
||||
**Development (v2.5.5-dev):**
|
||||
|
||||
```bash
|
||||
curl -sL https://raw.githubusercontent.com/master3395/cyberpanel/v2.5.5-dev/cyberpanel.sh | sudo bash -s -- -b v2.5.5-dev
|
||||
```
|
||||
|
||||
### Install with branch/version options
|
||||
|
||||
```bash
|
||||
# Download script first (recommended so -b/-v work reliably)
|
||||
curl -sL -o cyberpanel.sh https://raw.githubusercontent.com/master3395/cyberpanel/v2.5.5-dev/cyberpanel.sh
|
||||
chmod +x cyberpanel.sh
|
||||
sudo bash cyberpanel.sh [OPTIONS]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Example | Description |
|
||||
|--------|---------|-------------|
|
||||
| `-b BRANCH` / `--branch BRANCH` | `-b v2.5.5-dev` | Install from branch or tag |
|
||||
| `-v VER` / `--version VER` | `-v 2.5.5-dev` | Version (script adds `v` prefix as needed) |
|
||||
| `--mariadb-version VER` | `--mariadb-version 10.11` | MariaDB: `10.11`, `11.8`, or `12.1` |
|
||||
| `--auto` | `--auto` | Non-interactive (still asks MariaDB unless `--mariadb-version` is set) |
|
||||
| `--debug` | `--debug` | Debug mode |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
sudo bash cyberpanel.sh # Interactive
|
||||
sudo bash cyberpanel.sh -b v2.5.5-dev # Development branch
|
||||
sudo bash cyberpanel.sh -v 2.5.5-dev # Same as above (v prefix added)
|
||||
sudo bash cyberpanel.sh -v 2.4.4 # Install 2.4.4
|
||||
sudo bash cyberpanel.sh -b main # From main branch
|
||||
sudo bash cyberpanel.sh -b a1b2c3d4 # From specific commit hash
|
||||
sudo bash cyberpanel.sh --mariadb-version 10.11 # MariaDB 10.11
|
||||
sudo bash cyberpanel.sh --mariadb-version 12.1 # MariaDB 12.1
|
||||
sudo bash cyberpanel.sh --auto --mariadb-version 11.8 # Fully non-interactive, MariaDB 11.8
|
||||
sudo bash cyberpanel.sh --debug # Debug
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Upgrade (existing CyberPanel)
|
||||
|
||||
### One-liner upgrade to latest stable
|
||||
|
||||
```bash
|
||||
bash <(curl -sL https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/cyberpanel_upgrade.sh)
|
||||
```
|
||||
|
||||
### Upgrade to a specific branch/version (upstream)
|
||||
|
||||
```bash
|
||||
bash <(curl -sL https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/cyberpanel_upgrade.sh) -b v2.5.5-dev
|
||||
bash <(curl -sL https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/cyberpanel_upgrade.sh) -b 2.4.4
|
||||
```
|
||||
|
||||
### Upgrade using master3395 fork
|
||||
|
||||
```bash
|
||||
sudo bash <(curl -sL https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel_upgrade.sh) -b v2.5.5-dev
|
||||
```
|
||||
|
||||
Or download then run:
|
||||
|
||||
```bash
|
||||
curl -sL -o cyberpanel_upgrade.sh https://raw.githubusercontent.com/master3395/cyberpanel/v2.5.5-dev/cyberpanel_upgrade.sh
|
||||
chmod +x cyberpanel_upgrade.sh
|
||||
sudo bash cyberpanel_upgrade.sh -b v2.5.5-dev
|
||||
```
|
||||
|
||||
**Upgrade options:**
|
||||
|
||||
| Option | Example | Description |
|
||||
|--------|---------|-------------|
|
||||
| `-b BRANCH` / `--branch BRANCH` | `-b v2.5.5-dev` | Upgrade to this branch/tag |
|
||||
| `--no-system-update` | (optional) | Skip full `yum/dnf update -y` (faster if system is already updated) |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
sudo bash cyberpanel_upgrade.sh -b v2.5.5-dev
|
||||
sudo bash cyberpanel_upgrade.sh -b 2.4.4
|
||||
sudo bash cyberpanel_upgrade.sh -b stable
|
||||
sudo bash cyberpanel_upgrade.sh -b v2.5.5-dev --no-system-update
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Downgrade
|
||||
|
||||
Downgrade is done by running the **upgrade** script with the **older** branch/version.
|
||||
|
||||
### Downgrade to 2.4.4 (or another older version)
|
||||
|
||||
```bash
|
||||
sudo bash <(curl -sL https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/cyberpanel_upgrade.sh) -b 2.4.4
|
||||
```
|
||||
|
||||
Or with master3395 fork:
|
||||
|
||||
```bash
|
||||
curl -sL -o cyberpanel_upgrade.sh https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel_upgrade.sh
|
||||
chmod +x cyberpanel_upgrade.sh
|
||||
sudo bash cyberpanel_upgrade.sh -b 2.4.4
|
||||
```
|
||||
|
||||
### Downgrade from v2.5.5-dev to stable
|
||||
|
||||
```bash
|
||||
sudo bash cyberpanel_upgrade.sh -b stable
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pre-upgrade (download upgrade script only)
|
||||
|
||||
From the interactive menu: **Option 5 – Pre-Upgrade**.
|
||||
|
||||
Or manually:
|
||||
|
||||
```bash
|
||||
# Download latest upgrade script to /usr/local/
|
||||
curl -sL -o /usr/local/cyberpanel_upgrade.sh https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/cyberpanel_upgrade.sh
|
||||
chmod 700 /usr/local/cyberpanel_upgrade.sh
|
||||
|
||||
# Run when ready
|
||||
sudo /usr/local/cyberpanel_upgrade.sh -b v2.5.5-dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Action | Command |
|
||||
|--------|---------|
|
||||
| **Install (official)** | `sh <(curl https://cyberpanel.net/install.sh)` |
|
||||
| **Install stable (master3395)** | `curl -sL https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel.sh \| sudo bash -s --` |
|
||||
| **Install v2.5.5-dev** | `curl -sL https://raw.githubusercontent.com/master3395/cyberpanel/v2.5.5-dev/cyberpanel.sh \| sudo bash -s -- -b v2.5.5-dev` |
|
||||
| **Upgrade to v2.5.5-dev** | `sudo bash <(curl -sL https://raw.githubusercontent.com/master3395/cyberpanel/stable/cyberpanel_upgrade.sh) -b v2.5.5-dev` |
|
||||
| **Upgrade to 2.4.4** | `sudo bash <(curl -sL .../cyberpanel_upgrade.sh) -b 2.4.4` |
|
||||
| **Downgrade to 2.4.4** | Same as upgrade: `... cyberpanel_upgrade.sh -b 2.4.4` |
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Run as **root** or with **sudo**; if using `curl | sudo bash`, use `bash -s --` and put branch/options after `--` so they are passed to the script.
|
||||
- MariaDB version can be set at install with `--mariadb-version 10.11`, `11.8`, or `12.1`.
|
||||
- Upgrade script branch: `-b v2.5.5-dev`, `-b 2.4.4`, `-b stable`, or `-b <commit-hash>`.
|
||||
@@ -19,6 +19,27 @@ The config used `Quota yes`, but Pure-FTPd expects **`Quota maxfiles:maxsize`**
|
||||
- **install/pure-ftpd/pure-ftpd.conf** and **install/pure-ftpd-one/pure-ftpd.conf**: `Quota yes` → `Quota 100000:100000`.
|
||||
- **websiteFunctions/website.py** (`enableFTPQuota`): sed/echo now write `Quota 100000:100000` instead of `Quota yes` (or tabs).
|
||||
|
||||
## One-time fix on server (if "Enable" still breaks it)
|
||||
Run on the server as root (copy script from repo or run inline):
|
||||
|
||||
**Option A – script (repo root: `fix-pureftpd-quota-once.sh`):**
|
||||
```bash
|
||||
sudo bash /path/to/fix-pureftpd-quota-once.sh
|
||||
```
|
||||
|
||||
**Option B – inline:**
|
||||
```bash
|
||||
sudo sed -i 's/^Quota.*/Quota 100000:100000/' /etc/pure-ftpd/pure-ftpd.conf
|
||||
# If TLS 1 is set but cert missing, disable TLS:
|
||||
sudo sed -i 's/^TLS[[:space:]]*1/TLS 0/' /etc/pure-ftpd/pure-ftpd.conf
|
||||
sudo systemctl start pure-ftpd
|
||||
```
|
||||
Then deploy the latest panel code so "Enable" uses the correct Quota syntax.
|
||||
|
||||
## Code safeguards (enableFTPQuota)
|
||||
- **Backup before modify**: Timestamped backup of `pure-ftpd.conf` and `pureftpd-mysql.conf` before any change.
|
||||
- **Safety net before restart**: If the Quota line is not valid (`Quota maxfiles:maxsize`), it is corrected to `Quota 100000:100000` so Pure-FTPd never gets an invalid line on restart.
|
||||
|
||||
## Reference
|
||||
- Upstream: https://github.com/jedisct1/pure-ftpd/blob/master/pure-ftpd.conf.in (comment: "Quota 1000:10").
|
||||
- `pure-ftpd --help`: `-n --quota <opt>`.
|
||||
|
||||
76
to-do/V2.5.5-DEV-BRANCH-COMPATIBILITY.md
Normal file
76
to-do/V2.5.5-DEV-BRANCH-COMPATIBILITY.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# v2.5.5-dev Branch Compatibility Check
|
||||
|
||||
**Date:** 2026-02-04
|
||||
**Branches compared:** [v2.5.5-dev](https://github.com/master3395/cyberpanel/tree/v2.5.5-dev) vs [v2.4.4](https://github.com/master3395/cyberpanel/tree/v2.4.4) vs [stable](https://github.com/master3395/cyberpanel/tree/stable)
|
||||
|
||||
---
|
||||
|
||||
## 1. Will your v2.5.5-dev changes work?
|
||||
|
||||
**Yes.** The Ban IP / Firewall Banned IPs changes on v2.5.5-dev are self-contained and consistent:
|
||||
|
||||
| Component | Status |
|
||||
|-----------|--------|
|
||||
| **plogical/firewallUtilities.py** | `blockIP` / `unblockIP` use `result == 1` for success; log file path is `/usr/local/CyberCP/data/blocked_ips.log` (writable by `cyberpanel`). |
|
||||
| **plogical/processUtilities.py** | When running as root, `executioner` uses `normalExecutioner` return value (1 = success, 0 = fail). |
|
||||
| **firewall/firewallManager.py** | `addBannedIP` uses `FirewallUtilities.blockIP`; ACL and errors return JSON with `error_message` and `error`; rollback on block failure. |
|
||||
| **firewall/views.py** | `addBannedIP` parses JSON body and calls `fm.addBannedIP(userID, request_data)`. |
|
||||
| **firewall/urls.py** | Routes `getBannedIPs`, `addBannedIP`, `modifyBannedIP`, `removeBannedIP`, `deleteBannedIP`, `exportBannedIPs`, `importBannedIPs` are present. |
|
||||
| **baseTemplate/views.py** | `blockIPAddress` uses `FirewallUtilities.blockIP` (no subprocess). |
|
||||
| **baseTemplate (homePage + system-status.js)** | Ban IP calls `/firewall/addBannedIP` with `ip`, `reason`, `duration`; shows server `error_message` in notifications. |
|
||||
|
||||
**Deployment requirements (already applied on your server):**
|
||||
|
||||
- `/usr/local/CyberCP/data` owned by `cyberpanel:cyberpanel` (for `banned_ips.json` and `blocked_ips.log`).
|
||||
- Deploy updated files from v2.5.5-dev into `/usr/local/CyberCP/` and restart `lscpd`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Does v2.5.5-dev have all functions from v2.4.4 and stable?
|
||||
|
||||
**Summary:**
|
||||
|
||||
- **v2.5.5-dev has more than v2.4.4 and stable** in terms of features (Banned IPs, FTP quotas, email limits, user management, bandwidth management, etc.). It is a development branch built on top of the same base.
|
||||
- **v2.5.5-dev is missing a few items that exist only on stable** (backports or stable-only fixes). Nothing critical for the Ban IP feature; mainly scripts and tests.
|
||||
|
||||
### v2.5.5-dev has everything from v2.4.4 that matters
|
||||
|
||||
- v2.4.4 is older (fewer commits). v2.5.5-dev contains the same core apps (firewall, baseTemplate, loginSystem, backup, etc.) plus many additions.
|
||||
- **Firewall:** v2.4.4 has no Banned IPs routes; v2.5.5-dev adds the full Banned IPs feature (getBannedIPs, addBannedIP, modify, remove, delete, export, import).
|
||||
|
||||
### v2.5.5-dev vs stable
|
||||
|
||||
- **Stable has ~86 files that differ from v2.5.5-dev**, including:
|
||||
- **run_migration.py** – present on stable, **not** on v2.5.5-dev (migration helper).
|
||||
- **test_firewall_blocking.py** – test script on stable.
|
||||
- **rollback_phpmyadmin_redirect.sh** – rollback script on stable.
|
||||
- **install/**, **plogical/** (e.g. mysqlUtilities, upgrade, backup, sslUtilities), **pluginInstaller** – some fixes/improvements on stable that may not be in v2.5.5-dev.
|
||||
|
||||
- **v2.5.5-dev has 3652+ files changed vs stable** – it has many more features (user management, website functions, bandwidth, FTP quotas, email limits, Banned IPs, etc.).
|
||||
|
||||
So:
|
||||
|
||||
- **Feature parity:** v2.5.5-dev has **all the main functions** from v2.4.4 and **adds** Banned IPs and other features. It does **not** lack core features that v2.4.4 or stable have.
|
||||
- **Stable-only extras:** Stable has a few **extra** scripts/fixes (e.g. `run_migration.py`, `rollback_phpmyadmin_redirect.sh`, some plogical/install changes). If you need those, you can cherry-pick or merge from stable into v2.5.5-dev.
|
||||
|
||||
---
|
||||
|
||||
## 3. Directory layout comparison
|
||||
|
||||
| In stable, not in v2.5.5-dev (by name) | In v2.5.5-dev, not in stable |
|
||||
|----------------------------------------|------------------------------|
|
||||
| emailMarketing (or different layout) | bin, docs, modules, public, sql, test, to-do |
|
||||
| examplePlugin | (v2.5.5-dev has more structure) |
|
||||
| guides | |
|
||||
| scripts | |
|
||||
| testPlugin | test (different name) |
|
||||
|
||||
Your current repo (v2.5.5-dev) includes `emailMarketing`, `websiteFunctions`, `firewall`, `baseTemplate`, etc. The diff is mostly naming (e.g. test vs testPlugin) and stable having a few extra scripts/docs.
|
||||
|
||||
---
|
||||
|
||||
## 4. Recommendation
|
||||
|
||||
1. **Use v2.5.5-dev as-is for Ban IP and current features** – the changes are consistent and will work with the deployment steps above.
|
||||
2. **Periodically merge or cherry-pick from stable** into v2.5.5-dev if you want stable’s migration script, phpMyAdmin rollback script, and any plogical/install fixes.
|
||||
3. **You do have all the functions from v2.4.4 and stable** in the sense of core product behavior; v2.5.5-dev adds more (Banned IPs, etc.) and is only missing some optional stable-only scripts/fixes.
|
||||
@@ -38,7 +38,15 @@ FTP Quota Management - CyberPanel
|
||||
<!-- Enable FTP Quota Section -->
|
||||
<div class="row mb-4 ftp-quota-info">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
<div id="ftpDisabledWarning" class="alert alert-warning" style="display: none;">
|
||||
<h5><i class="fas fa-exclamation-triangle"></i> Pure-FTPd is not running</h5>
|
||||
<p class="mb-0">Please enable Pure-FTPd first (e.g. from <strong>Server Status → Services</strong>) before enabling the FTP Quota system.</p>
|
||||
</div>
|
||||
<div id="ftpQuotaAlreadyEnabled" class="alert alert-success" style="display: none;">
|
||||
<h5><i class="fas fa-check-circle"></i> FTP Quota system is already enabled</h5>
|
||||
<p class="mb-0">Pure-FTPd is running with quota support. You can manage per-user quotas in the table below.</p>
|
||||
</div>
|
||||
<div id="ftpQuotaInfoDefault" class="alert alert-info">
|
||||
<h5><i class="fas fa-info-circle"></i> FTP Quota System</h5>
|
||||
<p>Enable and manage individual FTP user quotas. This allows you to set storage limits for each FTP user.</p>
|
||||
<button type="button" id="btnEnableFTPQuota" class="btn btn-primary" onclick="enableFTPQuota()">
|
||||
@@ -159,10 +167,48 @@ function showNotification(type, message) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateFTPQuotaStatus() {
|
||||
$.ajax({
|
||||
url: '{% url "getFTPQuotaStatus" %}',
|
||||
type: 'POST',
|
||||
data: { 'csrfmiddlewaretoken': getCsrfToken() },
|
||||
headers: { 'X-CSRFToken': getCsrfToken() },
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
var warning = document.getElementById('ftpDisabledWarning');
|
||||
var already = document.getElementById('ftpQuotaAlreadyEnabled');
|
||||
var defaultBox = document.getElementById('ftpQuotaInfoDefault');
|
||||
var btn = document.getElementById('btnEnableFTPQuota');
|
||||
var btnText = document.getElementById('btnEnableFTPQuotaText');
|
||||
if (!data || data.status !== 1) return;
|
||||
if (!data.ftp_running) {
|
||||
if (warning) warning.style.display = 'block';
|
||||
if (already) already.style.display = 'none';
|
||||
if (defaultBox) defaultBox.style.display = 'none';
|
||||
if (btn) { btn.disabled = true; btn.title = 'Start Pure-FTPd from Server Status → Services first'; }
|
||||
if (btnText) btnText.textContent = 'Enable FTP Quota System (start Pure-FTPd first)';
|
||||
} else if (data.quota_configured) {
|
||||
if (warning) warning.style.display = 'none';
|
||||
if (already) already.style.display = 'block';
|
||||
if (defaultBox) defaultBox.style.display = 'none';
|
||||
if (btn) { btn.disabled = true; btn.title = ''; }
|
||||
if (btnText) btnText.textContent = 'FTP Quota system is already enabled';
|
||||
} else {
|
||||
if (warning) warning.style.display = 'none';
|
||||
if (already) already.style.display = 'none';
|
||||
if (defaultBox) defaultBox.style.display = 'block';
|
||||
if (btn) { btn.disabled = false; btn.title = ''; }
|
||||
if (btnText) btnText.textContent = 'Enable FTP Quota System';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function enableFTPQuota() {
|
||||
var btn = document.getElementById('btnEnableFTPQuota');
|
||||
var btnText = document.getElementById('btnEnableFTPQuotaText');
|
||||
if (!btn || !btnText) return;
|
||||
if (btn.disabled) return;
|
||||
var originalHtml = btnText.innerHTML;
|
||||
btn.disabled = true;
|
||||
btnText.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Enabling...';
|
||||
@@ -175,6 +221,7 @@ function enableFTPQuota() {
|
||||
success: function(data) {
|
||||
if (data && data.status === 1) {
|
||||
showNotification('success', data.message || 'FTP quota system enabled successfully');
|
||||
updateFTPQuotaStatus();
|
||||
refreshQuotas();
|
||||
} else {
|
||||
showNotification('error', (data && (data.message || data.error_message)) || 'Enable failed');
|
||||
@@ -198,6 +245,7 @@ function enableFTPQuota() {
|
||||
complete: function() {
|
||||
btn.disabled = false;
|
||||
btnText.innerHTML = originalHtml;
|
||||
updateFTPQuotaStatus();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -294,8 +342,9 @@ function saveQuota() {
|
||||
});
|
||||
}
|
||||
|
||||
// Load quotas on page load
|
||||
// Load status and quotas on page load
|
||||
$(document).ready(function() {
|
||||
updateFTPQuotaStatus();
|
||||
refreshQuotas();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -208,6 +208,7 @@ urlpatterns = [
|
||||
path('fixSubdomainLogsAction', views.fixSubdomainLogsAction, name='fixSubdomainLogsAction'),
|
||||
|
||||
# FTP Quota Management (API endpoints only; page is at /ftp/quotaManagement)
|
||||
path('getFTPQuotaStatus', views.getFTPQuotaStatus, name='getFTPQuotaStatus'),
|
||||
path('enableFTPQuota', views.enableFTPQuota, name='enableFTPQuota'),
|
||||
path('getFTPQuotas', views.getFTPQuotas, name='getFTPQuotas'),
|
||||
path('updateFTPQuota', views.updateFTPQuota, name='updateFTPQuota'),
|
||||
|
||||
@@ -2263,6 +2263,14 @@ def securityManagementPage(request):
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
def getFTPQuotaStatus(request):
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
wm = WebsiteManager()
|
||||
return wm.getFTPQuotaStatus(userID, request.POST)
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
def enableFTPQuota(request):
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
|
||||
@@ -8799,9 +8799,52 @@ StrictHostKeyChecking no
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Error fixing subdomain logs for {domain_name}: {str(e)}')
|
||||
return False
|
||||
|
||||
def getFTPQuotaStatus(self, userID=None, data=None):
|
||||
"""
|
||||
Return FTP quota status for the UI: ftp_running, quota_configured.
|
||||
Used on page load to show the right message and button state.
|
||||
"""
|
||||
try:
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
if not (currentACL.get('admin', 0) == 1):
|
||||
return ACLManager.loadErrorJson('status', 0)
|
||||
if os.path.exists('/etc/lsb-release'):
|
||||
ftp_service = 'pure-ftpd-mysql'
|
||||
else:
|
||||
ftp_service = 'pure-ftpd'
|
||||
conf_path = '/etc/pure-ftpd/pure-ftpd.conf'
|
||||
ftp_running = False
|
||||
quota_configured = False
|
||||
try:
|
||||
out = ProcessUtilities.outputExecutioner(
|
||||
"systemctl is-active %s 2>/dev/null || true" % ftp_service, 'root', True)
|
||||
ftp_running = bool(out and out.strip() == 'active')
|
||||
except Exception:
|
||||
pass
|
||||
if ftp_running and os.path.exists(conf_path):
|
||||
try:
|
||||
quota_line = ProcessUtilities.outputExecutioner(
|
||||
"grep -E '^Quota[[:space:]]+[0-9]+:[0-9]+' %s 2>/dev/null || true" % conf_path, 'root', True)
|
||||
quota_configured = bool(quota_line and quota_line.strip())
|
||||
except Exception:
|
||||
pass
|
||||
data_ret = {
|
||||
'status': 1,
|
||||
'ftp_running': ftp_running,
|
||||
'quota_configured': quota_configured,
|
||||
'ftp_service': ftp_service,
|
||||
}
|
||||
return HttpResponse(json.dumps(data_ret), content_type='application/json')
|
||||
except Exception as e:
|
||||
data_ret = {'status': 0, 'message': str(e)}
|
||||
return HttpResponse(json.dumps(data_ret), content_type='application/json')
|
||||
|
||||
def enableFTPQuota(self, userID=None, data=None):
|
||||
"""
|
||||
Enable FTP quota: ensure Quota yes in existing config (do not overwrite), restart Pure-FTPd.
|
||||
Enable FTP quota: ensure Quota maxfiles:maxsize in config, start/restart Pure-FTPd if needed.
|
||||
If Pure-FTPd is already running and config already has a valid Quota line, return success
|
||||
without touching config or restarting (avoids breaking a working setup).
|
||||
Uses correct service name (pure-ftpd-mysql on Debian/Ubuntu, pure-ftpd on RHEL/Alma).
|
||||
"""
|
||||
try:
|
||||
@@ -8819,8 +8862,45 @@ StrictHostKeyChecking no
|
||||
ftp_service = 'pure-ftpd'
|
||||
|
||||
conf_path = '/etc/pure-ftpd/pure-ftpd.conf'
|
||||
|
||||
# Early success: if Pure-FTPd is already active and config has valid Quota line, do nothing
|
||||
try:
|
||||
out = ProcessUtilities.outputExecutioner(
|
||||
"systemctl is-active %s 2>/dev/null || true" % ftp_service, 'root', True)
|
||||
if out and out.strip() == 'active':
|
||||
quota_line = ProcessUtilities.outputExecutioner(
|
||||
"grep -E '^Quota[[:space:]]+[0-9]+:[0-9]+' %s 2>/dev/null || true" % conf_path, 'root', True)
|
||||
if quota_line and quota_line.strip():
|
||||
logging.CyberCPLogFileWriter.writeToFile("FTP quota already enabled and Pure-FTPd running")
|
||||
data_ret = {'status': 1, 'message': 'FTP quota system is already enabled and Pure-FTPd is running.'}
|
||||
return HttpResponse(json.dumps(data_ret), content_type='application/json')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Require Pure-FTPd to be running before enabling quota (avoid confusing failures)
|
||||
try:
|
||||
out = ProcessUtilities.outputExecutioner(
|
||||
"systemctl is-active %s 2>/dev/null || true" % ftp_service, 'root', True)
|
||||
if not (out and out.strip() == 'active'):
|
||||
msg = ('Pure-FTPd is not running. Please enable Pure-FTPd first '
|
||||
'(e.g. from Server Status → Services) before enabling the FTP Quota system.')
|
||||
data_ret = {'status': 0, 'message': msg}
|
||||
return HttpResponse(json.dumps(data_ret), content_type='application/json')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Only ensure Quota is enabled; do not overwrite existing config (preserves DB credentials, paths)
|
||||
if os.path.exists(conf_path):
|
||||
# Backup current config before we change anything (so we can restore if restart fails)
|
||||
try:
|
||||
from datetime import datetime
|
||||
ts = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
ProcessUtilities.executioner(
|
||||
'cp %s /etc/pure-ftpd/pure-ftpd.conf.backup.%s' % (conf_path, ts), 'root', True)
|
||||
ProcessUtilities.executioner(
|
||||
'cp /etc/pure-ftpd/pureftpd-mysql.conf /etc/pure-ftpd/pureftpd-mysql.conf.backup.%s 2>/dev/null || true' % ts, 'root', True)
|
||||
except Exception:
|
||||
pass
|
||||
# If service is not running, try restoring latest backup (in case a previous run overwrote working config)
|
||||
try:
|
||||
out = ProcessUtilities.outputExecutioner(
|
||||
@@ -8839,7 +8919,7 @@ StrictHostKeyChecking no
|
||||
ProcessUtilities.executioner(
|
||||
"grep -q '^Quota' %s && sed -i 's/^Quota.*/Quota 100000:100000/' %s || echo 'Quota 100000:100000' >> %s" % (conf_path, conf_path, conf_path),
|
||||
'root', True)
|
||||
logging.CyberCPLogFileWriter.writeToFile("Set Quota yes in existing pure-ftpd.conf")
|
||||
logging.CyberCPLogFileWriter.writeToFile("Set Quota 100000:100000 in existing pure-ftpd.conf")
|
||||
else:
|
||||
# First-time: copy from repo
|
||||
from datetime import datetime
|
||||
@@ -8850,7 +8930,37 @@ StrictHostKeyChecking no
|
||||
if os.path.exists('/usr/local/CyberCP/install/pure-ftpd/pureftpd-mysql.conf'):
|
||||
ProcessUtilities.executioner(
|
||||
'cp /usr/local/CyberCP/install/pure-ftpd/pureftpd-mysql.conf /etc/pure-ftpd/pureftpd-mysql.conf', 'root', True)
|
||||
|
||||
|
||||
# Safety net: ensure Quota line is valid before restart (Pure-FTPd rejects "Quota yes")
|
||||
try:
|
||||
quota_check = ProcessUtilities.outputExecutioner(
|
||||
"grep -E '^Quota[[:space:]]+[0-9]+:[0-9]+' %s 2>/dev/null || true" % conf_path, 'root', True)
|
||||
if not (quota_check and quota_check.strip()):
|
||||
ProcessUtilities.executioner(
|
||||
"grep -q '^Quota' %s && sed -i 's/^Quota.*/Quota 100000:100000/' %s || echo 'Quota 100000:100000' >> %s" % (conf_path, conf_path, conf_path),
|
||||
'root', True)
|
||||
logging.CyberCPLogFileWriter.writeToFile("Corrected invalid Quota line in pure-ftpd.conf before restart")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Final check: if Quota line still invalid (e.g. old panel code wrote "Quota yes"), restore backup and abort
|
||||
try:
|
||||
quota_final = ProcessUtilities.outputExecutioner(
|
||||
"grep '^Quota' %s 2>/dev/null || true" % conf_path, 'root', True)
|
||||
if quota_final and 'yes' in quota_final.lower():
|
||||
# Invalid line still present - restore backup and do not restart
|
||||
ProcessUtilities.executioner(
|
||||
"ls -t /etc/pure-ftpd/pure-ftpd.conf.backup.* 2>/dev/null | head -1 | xargs -r -I {} cp {} /etc/pure-ftpd/pure-ftpd.conf",
|
||||
'root', True)
|
||||
logging.CyberCPLogFileWriter.writeToFile("Aborted: invalid Quota line (yes) still present; restored backup")
|
||||
msg = ('Pure-FTPd config was invalid (Quota line). Restored previous config. '
|
||||
'Please deploy the latest panel code from v2.5.5-dev and run the one-time fix on the server: '
|
||||
'sudo sed -i "s/^Quota.*/Quota 100000:100000/" /etc/pure-ftpd/pure-ftpd.conf && sudo systemctl start pure-ftpd')
|
||||
data_ret = {'status': 0, 'message': msg}
|
||||
return HttpResponse(json.dumps(data_ret), content_type='application/json')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Restart Pure-FTPd
|
||||
logging.CyberCPLogFileWriter.writeToFile("Restarting Pure-FTPd service (%s)..." % ftp_service)
|
||||
ProcessUtilities.executioner('systemctl restart %s' % ftp_service, 'root', True)
|
||||
@@ -8866,6 +8976,17 @@ StrictHostKeyChecking no
|
||||
logging.CyberCPLogFileWriter.writeToFile("FTP quota system enabled successfully")
|
||||
data_ret = {'status': 1, 'message': 'FTP quota system enabled successfully'}
|
||||
else:
|
||||
# Restore backup so service can be started again from Services page
|
||||
try:
|
||||
ProcessUtilities.executioner(
|
||||
"ls -t /etc/pure-ftpd/pure-ftpd.conf.backup.* 2>/dev/null | head -1 | xargs -r -I {} cp {} /etc/pure-ftpd/pure-ftpd.conf",
|
||||
'root', True)
|
||||
ProcessUtilities.executioner(
|
||||
"ls -t /etc/pure-ftpd/pureftpd-mysql.conf.backup.* 2>/dev/null | head -1 | xargs -r -I {} cp {} /etc/pure-ftpd/pureftpd-mysql.conf",
|
||||
'root', True)
|
||||
logging.CyberCPLogFileWriter.writeToFile("Restored pure-ftpd config backup after failed start")
|
||||
except Exception:
|
||||
pass
|
||||
# Capture failure reason for the user
|
||||
try:
|
||||
status_out = ProcessUtilities.outputExecutioner(
|
||||
@@ -8874,7 +8995,7 @@ StrictHostKeyChecking no
|
||||
except Exception:
|
||||
status_preview = ''
|
||||
logging.CyberCPLogFileWriter.writeToFile("Pure-FTPd service not active after restart")
|
||||
msg = 'Pure-FTPd did not start. Run: systemctl status %s' % ftp_service
|
||||
msg = 'Pure-FTPd did not start. Config was restored. Run: systemctl status %s' % ftp_service
|
||||
if status_preview:
|
||||
msg += '. ' + status_preview
|
||||
data_ret = {'status': 0, 'message': msg}
|
||||
|
||||
Reference in New Issue
Block a user