diff --git a/README.md b/README.md index af69ae6..a820c51 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A modern web hosting control panel for WordPress and general PHP hosting. Built with Laravel 12, Filament v5, Livewire 4, and Tailwind CSS v4. -Version: 0.9-rc23 (release candidate) +Version: 0.9-rc24 (release candidate) This is a release candidate. Expect rapid iteration and breaking changes until 1.0. @@ -156,6 +156,7 @@ php artisan test --compact ## Initial Release +- 0.9-rc24: WAF installer improvements and ModSecurity setup fixes. - 0.9-rc: initial release candidate with core hosting, mail, DNS, SSL, backups, and migrations. ## License diff --git a/VERSION b/VERSION index 4b4c0b3..262cc3a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -VERSION=0.9-rc23 +VERSION=0.9-rc24 diff --git a/app/Console/Commands/Jabali/UpgradeCommand.php b/app/Console/Commands/Jabali/UpgradeCommand.php index d097ab7..b8ef3d0 100644 --- a/app/Console/Commands/Jabali/UpgradeCommand.php +++ b/app/Console/Commands/Jabali/UpgradeCommand.php @@ -48,11 +48,10 @@ class UpgradeCommand extends Command try { $this->configureGitSafeDirectory(); $this->ensureGitRepository(); - // Fetch from remote without merging - $this->executeCommandOrFail('git fetch origin main'); + $updateSource = $this->fetchUpdates(); // Check if there are updates - $behindCount = trim($this->executeCommandOrFail('git rev-list HEAD..origin/main --count')); + $behindCount = trim($this->executeCommandOrFail("git rev-list HEAD..{$updateSource['remoteRef']} --count")); if ($behindCount === '0') { $this->info('Jabali Panel is up to date!'); @@ -64,7 +63,7 @@ class UpgradeCommand extends Command // Show recent commits $this->line("\nRecent changes:"); - $commits = $this->executeCommandOrFail('git log HEAD..origin/main --oneline -10'); + $commits = $this->executeCommandOrFail("git log HEAD..{$updateSource['remoteRef']} --oneline -10"); if ($commits !== '') { $this->line($commits); } @@ -115,7 +114,7 @@ class UpgradeCommand extends Command // Step 2: Fetch updates $this->info('[2/9] Fetching updates from repository...'); try { - $this->executeCommandOrFail('git fetch origin main'); + $updateSource = $this->fetchUpdates(); } catch (Exception $e) { $this->error('Failed to fetch updates: '.$e->getMessage()); @@ -123,7 +122,7 @@ class UpgradeCommand extends Command } // Step 3: Check if updates available - $behindCount = trim($this->executeCommandOrFail('git rev-list HEAD..origin/main --count')); + $behindCount = trim($this->executeCommandOrFail("git rev-list HEAD..{$updateSource['remoteRef']} --count")); if ($behindCount === '0' && ! $this->option('force')) { $this->info('Already up to date!'); @@ -134,7 +133,7 @@ class UpgradeCommand extends Command $oldHead = trim($this->executeCommandOrFail('git rev-parse HEAD')); $this->info('[3/9] Pulling latest changes...'); try { - $pullResult = $this->executeCommand('git pull --ff-only origin main'); + $pullResult = $this->executeCommand("git pull --ff-only {$updateSource['pullRemote']} main"); if ($pullResult['exitCode'] !== 0) { throw new Exception($pullResult['output'] ?: 'Git pull failed.'); } @@ -249,6 +248,76 @@ class UpgradeCommand extends Command return 0; } + /** + * @return array{pullRemote: string, remoteRef: string} + */ + private function fetchUpdates(): array + { + $originUrl = trim($this->executeCommandOrFail('git remote get-url origin')); + $fetchAttempts = [ + ['remote' => 'origin', 'ref' => 'origin/main', 'type' => 'remote'], + ]; + + if ($this->hasRemote('gitea')) { + $fetchAttempts[] = ['remote' => 'gitea', 'ref' => 'gitea/main', 'type' => 'remote']; + } + + if ($this->isGithubSshUrl($originUrl)) { + $fetchAttempts[] = [ + 'remote' => $this->githubHttpsUrlFromSsh($originUrl), + 'ref' => 'jabali-upgrade/main', + 'type' => 'url', + ]; + } + + $lastError = null; + foreach ($fetchAttempts as $attempt) { + $result = $attempt['type'] === 'url' + ? $this->executeCommand("git fetch {$attempt['remote']} main:refs/remotes/{$attempt['ref']}") + : $this->executeCommand("git fetch {$attempt['remote']} main"); + + if (($result['exitCode'] ?? 1) === 0) { + return [ + 'pullRemote' => $attempt['remote'], + 'remoteRef' => $attempt['ref'], + ]; + } + + $lastError = $result['output'] ?? 'Unknown error'; + } + + throw new Exception($lastError ?: 'Unable to fetch updates from any configured remote.'); + } + + private function hasRemote(string $remote): bool + { + $result = $this->executeCommand("git remote get-url {$remote}"); + + return ($result['exitCode'] ?? 1) === 0; + } + + private function isGithubSshUrl(string $url): bool + { + return str_starts_with($url, 'git@github.com:') || str_starts_with($url, 'ssh://git@github.com/'); + } + + private function githubHttpsUrlFromSsh(string $url): string + { + if (str_starts_with($url, 'git@github.com:')) { + $path = substr($url, strlen('git@github.com:')); + + return 'https://github.com/'.$path; + } + + if (str_starts_with($url, 'ssh://git@github.com/')) { + $path = substr($url, strlen('ssh://git@github.com/')); + + return 'https://github.com/'.$path; + } + + return $url; + } + private function getCurrentVersion(): string { if (! File::exists($this->versionFile)) { diff --git a/bin/jabali-agent b/bin/jabali-agent index 425eebe..1a29918 100755 --- a/bin/jabali-agent +++ b/bin/jabali-agent @@ -2781,8 +2781,20 @@ function ensureJabaliNginxIncludeFiles(): void @mkdir(JABALI_NGINX_INCLUDES, 0755, true); } + $baseConfig = findWafBaseConfig(); + $shouldDisableWaf = $baseConfig === null; + if (!file_exists(JABALI_WAF_INCLUDE)) { - file_put_contents(JABALI_WAF_INCLUDE, "# Managed by Jabali\n"); + $content = "# Managed by Jabali\n"; + if ($shouldDisableWaf) { + $content .= "modsecurity off;\n"; + } + file_put_contents(JABALI_WAF_INCLUDE, $content); + } elseif ($shouldDisableWaf) { + $current = file_get_contents(JABALI_WAF_INCLUDE); + if ($current === false || strpos($current, 'modsecurity_rules_file') !== false || strpos($current, 'modsecurity on;') !== false) { + file_put_contents(JABALI_WAF_INCLUDE, "# Managed by Jabali\nmodsecurity off;\n"); + } } if (!file_exists(JABALI_GEO_INCLUDE)) { @@ -2847,7 +2859,7 @@ function findWafBaseConfig(): ?string ]; foreach ($paths as $path) { - if (file_exists($path)) { + if (file_exists($path) && isWafBaseConfigUsable($path)) { return $path; } } @@ -2855,6 +2867,28 @@ function findWafBaseConfig(): ?string return null; } +function isWafBaseConfigUsable(string $path): bool +{ + if (!is_readable($path)) { + return false; + } + + $content = file_get_contents($path); + if ($content === false) { + return false; + } + + if (preg_match_all('/^\s*Include\s+("?)([^"\s]+)\1/m', $content, $matches)) { + foreach ($matches[2] as $includePath) { + if ($includePath === '/etc/modsecurity/modsecurity.conf' && !file_exists($includePath)) { + return false; + } + } + } + + return true; +} + function wafApplySettings(array $params): array { $enabled = !empty($params['enabled']); @@ -2870,6 +2904,7 @@ function wafApplySettings(array $params): array if ($enabled) { $baseConfig = findWafBaseConfig(); if (!$baseConfig) { + file_put_contents(JABALI_WAF_INCLUDE, "# Managed by Jabali\nmodsecurity off;\n"); return ['success' => false, 'error' => 'ModSecurity base configuration not found']; } diff --git a/install.sh b/install.sh index 651c5cb..649068d 100755 --- a/install.sh +++ b/install.sh @@ -1083,7 +1083,10 @@ configure_nginx() { local jabali_includes="/etc/nginx/jabali/includes" mkdir -p "$jabali_includes" if [[ ! -f "$jabali_includes/waf.conf" ]]; then - echo "# Managed by Jabali" > "$jabali_includes/waf.conf" + cat > "$jabali_includes/waf.conf" <<'EOF' +# Managed by Jabali +modsecurity off; +EOF fi if [[ ! -f "$jabali_includes/geo.conf" ]]; then echo "# Managed by Jabali" > "$jabali_includes/geo.conf" @@ -2041,36 +2044,62 @@ configure_security() { if [[ "$INSTALL_SECURITY" == "true" ]]; then info "Installing ModSecurity (optional WAF)..." local module_pkg="" - if apt-cache show libnginx-mod-http-modsecurity2 &>/dev/null; then - module_pkg="libnginx-mod-http-modsecurity2" - elif apt-cache show libnginx-mod-http-modsecurity &>/dev/null; then + if apt-cache show libnginx-mod-http-modsecurity &>/dev/null; then module_pkg="libnginx-mod-http-modsecurity" + elif apt-cache show libnginx-mod-http-modsecurity2 &>/dev/null; then + module_pkg="libnginx-mod-http-modsecurity2" elif apt-cache show nginx-extras &>/dev/null; then module_pkg="nginx-extras" else warn "ModSecurity nginx module not available in apt repositories" fi + local modsec_lib="" + if apt-cache show libmodsecurity3t64 &>/dev/null; then + modsec_lib="libmodsecurity3t64" + elif apt-cache show libmodsecurity3 &>/dev/null; then + modsec_lib="libmodsecurity3" + fi + local crs_pkg="" if apt-cache show modsecurity-crs &>/dev/null; then crs_pkg="modsecurity-crs" fi if [[ -n "$module_pkg" ]]; then - DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "$module_pkg" $crs_pkg 2>/dev/null || warn "ModSecurity install failed" + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "$module_pkg" $modsec_lib $crs_pkg 2>/dev/null || warn "ModSecurity install failed" # Ensure ModSecurity base config - if [[ -f /etc/modsecurity/modsecurity.conf-recommended ]] && [[ ! -f /etc/modsecurity/modsecurity.conf ]]; then - cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf + if [[ ! -f /etc/modsecurity/modsecurity.conf ]]; then + if [[ -f /etc/nginx/modsecurity.conf ]]; then + cp /etc/nginx/modsecurity.conf /etc/modsecurity/modsecurity.conf + elif [[ -f /etc/modsecurity/modsecurity.conf-recommended ]]; then + cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf + elif [[ -f /usr/share/modsecurity-crs/modsecurity.conf-recommended ]]; then + cp /usr/share/modsecurity-crs/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf + else + cat > /etc/modsecurity/modsecurity.conf <<'EOF' +SecRuleEngine DetectionOnly +SecRequestBodyAccess On +SecResponseBodyAccess Off +SecAuditEngine RelevantOnly +SecAuditLog /var/log/nginx/modsec_audit.log +EOF + fi fi # Create main include file for nginx if missing mkdir -p /etc/nginx/modsec if [[ ! -f /etc/nginx/modsec/main.conf ]]; then - if [[ -f /usr/share/modsecurity-crs/crs-setup.conf ]]; then + if [[ -f /usr/share/modsecurity-crs/owasp-crs.load ]]; then cat > /etc/nginx/modsec/main.conf <<'EOF' Include /etc/modsecurity/modsecurity.conf -Include /usr/share/modsecurity-crs/crs-setup.conf +Include /usr/share/modsecurity-crs/owasp-crs.load +EOF + elif [[ -f /etc/modsecurity/crs/crs-setup.conf ]]; then + cat > /etc/nginx/modsec/main.conf <<'EOF' +Include /etc/modsecurity/modsecurity.conf +Include /etc/modsecurity/crs/crs-setup.conf Include /usr/share/modsecurity-crs/rules/*.conf EOF else