diff --git a/community-fixes/README.md b/community-fixes/README.md new file mode 100644 index 0000000..42432a6 --- /dev/null +++ b/community-fixes/README.md @@ -0,0 +1,39 @@ +# Community Fixes + +CloudPanel CE tracks issues in this repository, but the application source is distributed via the `cloudpanel` Debian package and is not published here. These scripts patch installed CloudPanel servers for well-defined bugs reported in GitHub issues. + +## Available Fixes + +| Issue | Script | Description | +|-------|--------|-------------| +| [#761](https://github.com/cloudpanel-io/cloudpanel-ce/issues/761) | [fix-761-site-php-settings-type.sh](./fix-761-site-php-settings-type.sh) | Fixes PHP 8+ TypeError in `SitePhpSettingsType::getPhpVersionChoices()` | +| [#758](https://github.com/cloudpanel-io/cloudpanel-ce/issues/758) | [fix-758-site-delete-crontab.sh](./fix-758-site-delete-crontab.sh) | Removes orphan user crontabs during `site:delete` | +| [#758](https://github.com/cloudpanel-io/cloudpanel-ce/issues/758) | [cleanup-orphan-crontabs.sh](./cleanup-orphan-crontabs.sh) | One-time cleanup for existing orphan crontab files | + +## Usage + +Run as root on a CloudPanel server: + +```bash +curl -fsSL https://raw.githubusercontent.com/cloudpanel-io/cloudpanel-ce/master/community-fixes/fix-761-site-php-settings-type.sh | sudo bash +``` + +Or clone this repository and run the script directly: + +```bash +sudo bash community-fixes/fix-761-site-php-settings-type.sh +``` + +Each script creates a timestamped backup before modifying files. + +## Reverting + +Backups are written next to the patched file with a `.bak.YYYYMMDDHHMMSS` suffix. To revert: + +```bash +cp /path/to/file.bak.TIMESTAMP /path/to/file +``` + +## Contributing + +If you have a fix for another open issue, add a script here and reference the issue number in the filename and script header. diff --git a/community-fixes/cleanup-orphan-crontabs.sh b/community-fixes/cleanup-orphan-crontabs.sh new file mode 100644 index 0000000..612a367 --- /dev/null +++ b/community-fixes/cleanup-orphan-crontabs.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Utility for https://github.com/cloudpanel-io/cloudpanel-ce/issues/758 +# Removes crontab spool files for users that no longer exist on the system. +set -euo pipefail + +CRONTAB_SPOOL="/var/spool/cron/crontabs" + +if [[ ! -d "$CRONTAB_SPOOL" ]]; then + echo "No crontab spool directory at $CRONTAB_SPOOL" + exit 0 +fi + +removed=0 +kept=0 + +for crontab_file in "$CRONTAB_SPOOL"/*; do + [[ -e "$crontab_file" ]] || continue + + username="$(basename "$crontab_file")" + + if id "$username" &>/dev/null; then + kept=$((kept + 1)) + continue + fi + + echo "Removing orphan crontab for deleted user: $username" + rm -f "$crontab_file" + removed=$((removed + 1)) +done + +echo "Done. Removed: $removed, kept: $kept" diff --git a/community-fixes/fix-758-site-delete-crontab.sh b/community-fixes/fix-758-site-delete-crontab.sh new file mode 100644 index 0000000..7c33a0a --- /dev/null +++ b/community-fixes/fix-758-site-delete-crontab.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# Fix for https://github.com/cloudpanel-io/cloudpanel-ce/issues/758 +# site:delete removes /etc/cron.d/ but leaves /var/spool/cron/crontabs/, +# causing cron "Authentication failure" spam after user deletion. +set -euo pipefail + +APP_SRC="/home/clp/htdocs/app/files/src" +CRONTAB_SPOOL="/var/spool/cron/crontabs" + +if [[ ! -d "$APP_SRC" ]]; then + echo "ERROR: $APP_SRC not found. Is CloudPanel installed?" >&2 + exit 1 +fi + +mapfile -t CANDIDATES < <(grep -rl 'userdel' "$APP_SRC" 2>/dev/null || true) + +if [[ ${#CANDIDATES[@]} -eq 0 ]]; then + echo "ERROR: No files containing 'userdel' found under $APP_SRC" >&2 + exit 1 +fi + +TARGET_FILE="" +for candidate in "${CANDIDATES[@]}"; do + if grep -qE 'site:delete|SiteDelete|DeleteSite|domainName' "$candidate" 2>/dev/null; then + TARGET_FILE="$candidate" + break + fi +done + +if [[ -z "$TARGET_FILE" ]]; then + for candidate in "${CANDIDATES[@]}"; do + if grep -qE "userdel.*-rf|'userdel',\s*'-rf'" "$candidate" 2>/dev/null; then + TARGET_FILE="$candidate" + break + fi + done +fi + +if [[ -z "$TARGET_FILE" ]]; then + echo "ERROR: Could not locate site delete command file." >&2 + exit 1 +fi + +if grep -qE 'crontab.*-r|/var/spool/cron/crontabs' "$TARGET_FILE"; then + echo "Already patched: $TARGET_FILE handles crontab cleanup" + exit 0 +fi + +BACKUP="${TARGET_FILE}.bak.$(date +%Y%m%d%H%M%S)" +cp "$TARGET_FILE" "$BACKUP" +echo "Backup created: $BACKUP" +echo "Patching: $TARGET_FILE" + +php <run(); + } else { + exec(sprintf('crontab -r -u %s 2>/dev/null', escapeshellarg({$userVar}))); + } + @unlink(sprintf('/var/spool/cron/crontabs/%s', {$userVar})); + +PATCH; + +\$patched = false; +\$linePatterns = [ + "/^(\\s*)(\\\$process\\s*=\\s*new\\s+Process\\s*\\(\\s*\\[\\s*['\"]userdel['\"])/m", + "/^(\\s*)(.*userdel.*-rf.*)/m", +]; + +foreach (\$linePatterns as \$pattern) { + \$result = preg_replace(\$pattern, \$injection . "\$1\$2", \$content, 1, \$count); + if (\$count > 0 && \$result !== null) { + \$content = \$result; + \$patched = true; + break; + } +} + +if (!\$patched) { + fwrite(STDERR, "ERROR: Could not inject crontab cleanup before userdel in \$file\n"); + exit(1); +} + +if (\$content === \$original) { + fwrite(STDERR, "ERROR: No changes applied to \$file\n"); + exit(1); +} + +if (file_put_contents(\$file, \$content) === false) { + fwrite(STDERR, "ERROR: Failed to write \$file\n"); + exit(1); +} + +echo "SUCCESS: Patched site delete flow for issue #758\n"; +echo "Detected site user variable: {\$userVar}\n"; +PHP + +echo "" +echo "Cleaning up existing orphan crontabs..." +bash "$(dirname "$0")/cleanup-orphan-crontabs.sh" diff --git a/community-fixes/fix-761-site-php-settings-type.sh b/community-fixes/fix-761-site-php-settings-type.sh new file mode 100644 index 0000000..a2fbb82 --- /dev/null +++ b/community-fixes/fix-761-site-php-settings-type.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Fix for https://github.com/cloudpanel-io/cloudpanel-ce/issues/761 +# SitePhpSettingsType::getPhpVersionChoices() uses `$phpVersion + 0` which +# throws TypeError on PHP 8.0+ when $phpVersion is a non-numeric string. +set -euo pipefail + +TARGET_FILE="/home/clp/htdocs/app/files/src/Form/SitePhpSettingsType.php" + +if [[ ! -f "$TARGET_FILE" ]]; then + echo "ERROR: $TARGET_FILE not found. Is CloudPanel installed?" >&2 + exit 1 +fi + +BACKUP="${TARGET_FILE}.bak.$(date +%Y%m%d%H%M%S)" +cp "$TARGET_FILE" "$BACKUP" +echo "Backup created: $BACKUP" + +php <<'PHP' + 'if (preg_match(\'/^\d+\.\d+$/\', $phpVersion))', + 'if (is_float($phpVersion + 0))' => 'if (preg_match(\'/^\d+\.\d+$/\', $phpVersion))', + // Obfuscated variants may use different spacing + 'is_float($phpVersion + 0)' => 'preg_match(\'/^\d+\.\d+$/\', $phpVersion)', + 'is_float((float) $phpVersion)' => 'preg_match(\'/^\d+\.\d+$/\', $phpVersion)', +]; + +foreach ($replacements as $search => $replace) { + $content = str_replace($search, $replace, $content); +} + +// Fallback: regex for any variable name used in getPhpVersionChoices-style checks +$content = preg_replace( + '/is_float\s*\(\s*(\$\w+)\s*\+\s*0\s*\)/', + 'preg_match(\'/^\\d+\\.\\d+$/\', $1)', + $content +) ?? $content; + +if ($content === $original) { + fwrite(STDERR, "ERROR: No matching pattern found in $file.\n"); + fwrite(STDERR, "The file may already be patched or use a different code structure.\n"); + exit(1); +} + +if (file_put_contents($file, $content) === false) { + fwrite(STDERR, "ERROR: Failed to write $file\n"); + exit(1); +} + +echo "SUCCESS: Patched SitePhpSettingsType.php for issue #761\n"; +echo "PHP version directory detection now uses preg_match('/^\\d+\\.\\d+$/', \$phpVersion)\n"; +PHP diff --git a/community-fixes/test-761-fix-logic.php b/community-fixes/test-761-fix-logic.php new file mode 100644 index 0000000..fb2b43d --- /dev/null +++ b/community-fixes/test-761-fix-logic.php @@ -0,0 +1,38 @@ + 0) { + echo "$failed test(s) failed." . PHP_EOL; + exit(1); +} + +echo 'All tests passed.' . PHP_EOL;