firewallTool === 'firewalld' ? 'firewalld' : 'ufw', $os->managesPackages() ? PackageManager::for($os) : new PackageManager('none'), $os->managesPackages(), ); } public function isFirewalld(): bool { return $this->tool === 'firewalld'; } private function packageName(): string { return $this->tool === 'firewalld' ? 'firewalld' : 'ufw'; } /** Guarded enable: install if needed + missing, open ssh/80/443, then turn on. */ public function enableScript(int $sshPort): string { $ensure = $this->canInstall ? $this->pm->ensureInstalledScript($this->packageName()).' && ' : ''; if ($this->isFirewalld()) { $ports = "--add-port={$sshPort}/tcp --add-port=80/tcp --add-port=443/tcp"; // GUARD: open ssh/80/443 in the PERMANENT config BEFORE firewalld starts // filtering, so starting it can never drop the operator's SSH session (a // custom SSH port is not in the default zone). Handle both states: if the // daemon is already running, add to permanent + reload; if it is stopped, // write the permanent rules offline first, THEN start it. return $ensure .'if systemctl is-active --quiet firewalld; then ' ."firewall-cmd --permanent {$ports} && systemctl enable --now firewalld && firewall-cmd --reload; " .'else ' ."firewall-offline-cmd {$ports} && systemctl enable --now firewalld; " .'fi'; } return $ensure ."ufw allow {$sshPort}/tcp && ufw allow 80/tcp && ufw allow 443/tcp && ufw --force enable"; } /** Clean disable (never a panic/drop-all). */ public function disableScript(): string { return $this->isFirewalld() ? 'systemctl disable --now firewalld' : 'ufw disable'; } /** Shell CONDITION exiting 0 when the firewall binary is present. */ public function installedTest(): string { return $this->isFirewalld() ? 'command -v firewall-cmd >/dev/null 2>&1' : 'command -v ufw >/dev/null 2>&1'; } /** Shell CONDITION exiting 0 when the firewall is currently active. */ public function activeTest(): string { return $this->isFirewalld() ? 'systemctl is-active --quiet firewalld' : 'ufw status 2>/dev/null | grep -qi "Status: active"'; } }