diff --git a/app/Livewire/Ui/Security/Fail2BanCard.php b/app/Livewire/Ui/Security/Fail2BanCard.php index 274602e..f9ca979 100644 --- a/app/Livewire/Ui/Security/Fail2BanCard.php +++ b/app/Livewire/Ui/Security/Fail2BanCard.php @@ -6,116 +6,83 @@ use Livewire\Component; class Fail2BanCard extends Component { - public bool $available = true; // ob fail2ban vorhanden ist - public int $activeBans = 0; // Summe über alle Jails - /** @var array}> */ - public array $jails = []; // je Jail: Name, Anzahl, IPs (gekürzt) - /** @var array */ - public array $topIps = []; // Top IPs aus Log/Journal (Ban-Events) + public bool $available = true; + public bool $permDenied = false; // neu + public int $activeBans = 0; + public array $jails = []; + public array $topIps = []; - public function mount(): void - { - $this->load(); - } - - public function render() - { - return view('livewire.ui.security.fail2-ban-card'); - } - - public function refresh(): void - { - $this->load(true); - } + // ... protected function load(bool $force = false): void { - // 0) vorhanden? $bin = trim((string) @shell_exec('command -v fail2ban-client 2>/dev/null')) ?: ''; if ($bin === '') { - $this->available = false; + $this->available = false; + $this->permDenied = false; $this->activeBans = 0; - $this->jails = []; - $this->topIps = []; + $this->jails = []; + $this->topIps = []; return; } - // 1) Jails ermitteln - $status = (string) (@shell_exec("timeout 2 $bin status 2>/dev/null") ?? ''); + // ping → prüft zugleich Rechte (liefert Fehlertext, wenn Socket gesperrt) + [$ok, $raw] = $this->f2b('ping'); // ok==true wenn "pong" + + if (!$ok && stripos($raw, 'permission denied') !== false) { + $this->available = true; + $this->permDenied = true; + $this->activeBans = 0; + $this->jails = []; + $this->topIps = $this->collectTopIps(); + return; + } + + // Jails + [, $status] = $this->f2b('status'); $jailsLn = $this->firstMatch('/Jail list:\s*(.+)$/mi', $status); $jails = $jailsLn ? array_filter(array_map('trim', preg_split('/\s*,\s*/', $jailsLn))) : []; - $total = 0; - $rows = []; - + $total = 0; $rows = []; foreach ($jails as $j) { - $s = (string) (@shell_exec("timeout 2 $bin status ".escapeshellarg($j)." 2>/dev/null") ?? ''); + [, $s] = $this->f2b('status '.escapeshellarg($j)); $banned = (int) ($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0); - - // „Banned IP list:“ kann fehlen → leeres Array $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: ''; $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : []; - - $rows[] = [ - 'name' => $j, - 'banned' => $banned, - // nur die ersten 8 zur UI-Anzeige - 'ips' => array_slice($ips, 0, 8), - ]; + $rows[] = ['name'=>$j,'banned'=>$banned,'ips'=>array_slice($ips, 0, 8)]; $total += $banned; } $this->available = true; + $this->permDenied = false; $this->activeBans = $total; $this->jails = $rows; - - // 2) Top-IPs aus den letzten Ban-Events - $this->topIps = $this->collectTopIps(); + $this->topIps = $this->collectTopIps(); + } + + /** führt fail2ban-client aus; mit sudo-Fallback; gibt [ok, output] zurück */ + private function f2b(string $args): array + { + $sudo = '/usr/bin/sudo'; + $f2b = '/usr/bin/fail2ban-client'; + $cmd = "timeout 2 $sudo -n $f2b $args 2>&1"; + $out = (string) @shell_exec($cmd); + + $ok = stripos($out, 'Status') !== false + || stripos($out, 'Jail list') !== false + || stripos($out, 'pong') !== false; + + return [$ok, $out]; } - /** Extrahiert erste Regex-Gruppe oder null */ private function firstMatch(string $pattern, string $haystack): ?string { return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null; } - /** Zählt „Ban “ aus fail2ban.log (Fallback: journalctl) */ private function collectTopIps(): array { - $lines = ''; - if (is_readable('/var/log/fail2ban.log')) { - // letzte 500 Zeilen reichen völlig - $lines = (string) (@shell_exec('tail -n 500 /var/log/fail2ban.log 2>/dev/null') ?? ''); - } - - if ($lines === '') { - // Fallback: Journal (falls rsyslog die Datei nicht schreibt) - $lines = (string) (@shell_exec('timeout 2 journalctl -u fail2ban -n 500 --no-pager 2>/dev/null') ?? ''); - } - - if ($lines === '') { - return []; - } - - // Nur Ban-Events, IP extrahieren - $ips = []; - foreach (preg_split('/\R+/', $lines) as $ln) { - if (stripos($ln, 'Ban ') === false) continue; - if (preg_match('/\b(\d{1,3}(?:\.\d{1,3}){3})\b/', $ln, $m)) { - $ip = $m[1]; - $ips[$ip] = ($ips[$ip] ?? 0) + 1; - } - } - - if (!$ips) return []; - - arsort($ips); - $top = array_slice($ips, 0, 5, true); - - $out = []; - foreach ($top as $ip => $cnt) { - $out[] = ['ip' => $ip, 'count' => (int)$cnt]; - } - return $out; + // … (deine vorhandene Methode aus meiner letzten Antwort – passt) + // lässt du unverändert. } } diff --git a/app/Livewire/Ui/System/UpdateCard.php b/app/Livewire/Ui/System/UpdateCard.php index 7b5177b..c88e750 100644 --- a/app/Livewire/Ui/System/UpdateCard.php +++ b/app/Livewire/Ui/System/UpdateCard.php @@ -111,6 +111,7 @@ class UpdateCard extends Component $this->recompute(); if ($this->rc === 0 && !$this->postActionsDone) { + @shell_exec('nohup php /var/www/mailwolt/artisan optimize:clear >/dev/null 2>&1 &'); @shell_exec('nohup php /var/www/mailwolt/artisan health:collect >/dev/null 2>&1 &'); @shell_exec('nohup php /var/www/mailwolt/artisan settings:sync >/dev/null 2>&1 &'); @shell_exec('nohup php /var/www/mailwolt/artisan spamav:collect >/dev/null 2>&1 &'); @@ -119,8 +120,6 @@ class UpdateCard extends Component $ver = $this->displayCurrent ?? 'aktuelle Version'; $this->progressLine = 'Update abgeschlossen: ' . $ver; - - @shell_exec('nohup php /var/www/mailwolt/artisan optimize:clear >/dev/null 2>&1 &'); // Optional: NICHT sofort reloaden – Nutzer entscheidet // $this->dispatch('reload-page', delay: 6000); } elseif ($this->rc !== null && $this->rc !== 0 && !$this->postActionsDone) {