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:
master3395
2026-02-04 23:19:22 +01:00
parent 7cab70dbaf
commit 2388d68f9f
13 changed files with 692 additions and 82 deletions

View File

@@ -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:"

View File

@@ -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]")

View File

@@ -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):

View File

@@ -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():

View 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 instances 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).

View 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
```

View 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>`.

View File

@@ -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>`.

View 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 stables 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.

View File

@@ -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>

View File

@@ -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'),

View File

@@ -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']

View File

@@ -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}