From a9609d358b479c226d46d9fc60b4ca125481cb8e Mon Sep 17 00:00:00 2001 From: boban Date: Sat, 25 Oct 2025 13:10:06 +0200 Subject: [PATCH] =?UTF-8?q?Fix:=20Mailbox=20Stats=20=C3=BCber=20Dovecot=20?= =?UTF-8?q?mit=20config/mailpool.php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/CheckUpdates.php | 116 ++++- app/Console/Commands/MailwoltRestart.php | 36 ++ app/Jobs/RunHealthChecks.php | 43 +- app/Livewire/Ui/System/ServicesCard.php | 40 +- app/Livewire/Ui/System/UpdateCard.php | 419 +++++++++++++----- app/Support/CacheVer.php | 28 ++ config/mailwolt.php | 15 + .../domain/modal/domain-dns-modal.blade.php | 1 + .../livewire/ui/system/update-card.blade.php | 2 +- 9 files changed, 495 insertions(+), 205 deletions(-) create mode 100644 app/Console/Commands/MailwoltRestart.php create mode 100644 app/Support/CacheVer.php create mode 100644 config/mailwolt.php diff --git a/app/Console/Commands/CheckUpdates.php b/app/Console/Commands/CheckUpdates.php index 722edee..f5f2a9a 100644 --- a/app/Console/Commands/CheckUpdates.php +++ b/app/Console/Commands/CheckUpdates.php @@ -11,29 +11,113 @@ class CheckUpdates extends Command public function handle(): int { - $appPath = base_path(); - $current = trim(@file_get_contents(base_path('VERSION'))) ?: '0.0.0'; - // newest tag from origin (sorted semver-friendly) - $latest = trim(shell_exec( - "cd {$appPath} && git fetch --tags --quiet origin && git tag --list | sort -V | tail -n1" - ) ?? ''); + $currentNorm = $this->readInstalledVersionNorm(); + $currentRaw = $this->readInstalledVersionRaw() ?? ($currentNorm ? 'v'.$currentNorm : null); - // Tags haben usually ein 'v' Prefix – entfernen - $latest = ltrim($latest, 'v'); + // Neuesten Tag vom Remote holen (semver-freundlich sortiert) + $appPath = base_path(); + $cmd = <<normalizeVersion($latestTagRaw); + + // Nichts gefunden -> Caches leeren + if (!$latestNorm) { + cache()->forget('updates:latest'); + cache()->forget('updates:latest_raw'); + cache()->forget('mailwolt.update_available'); // Legacy $this->warn('Keine Release-Tags gefunden.'); - cache()->forget('mailwolt.update_available'); - return 0; + return self::SUCCESS; } - if (version_compare($latest, $current, '>')) { - cache()->forever('mailwolt.update_available', $latest); - $this->info("Update verfügbar: {$latest} (installiert: {$current})"); + // Cache schreiben + cache()->put('updates:latest', $latestNorm, now()->addMinutes(10)); + cache()->put('updates:latest_raw', $latestTagRaw, now()->addMinutes(10)); + cache()->forever('mailwolt.update_available', $latestNorm); // Legacy-Kompat + + // Vergleich & Ausgabe + if ($currentNorm && version_compare($latestNorm, $currentNorm, '>')) { + $this->info("Update verfügbar: {$latestTagRaw} (installiert: ".($currentRaw ?? $currentNorm).")"); } else { + $this->info("Aktuell (installiert: ".($currentRaw ?? $currentNorm ?? 'unbekannt').")."); + // Kein Update – Legacy-Key aufräumen (UI liest die neuen Keys) cache()->forget('mailwolt.update_available'); - $this->info("Aktuell (installiert: {$current})."); } - return 0; + + cache()->put('updates:last_checked_at', now(), now()->addMinutes(10)); + return self::SUCCESS; } + + /* ===== Helpers ===== */ + + private function readInstalledVersionNorm(): ?string + { + $paths = [ + '/var/lib/mailwolt/version', // vom Wrapper (normiert) + base_path('VERSION'), // App-Fallback + ]; + foreach ($paths as $p) { + $raw = @trim(@file_get_contents($p) ?: ''); + if ($raw !== '') return $this->normalizeVersion($raw); + } + // Noch ein Fallback aus RAW-Datei + $raw = $this->readInstalledVersionRaw(); + return $raw ? $this->normalizeVersion($raw) : null; + } + + private function readInstalledVersionRaw(): ?string + { + $p = '/var/lib/mailwolt/version_raw'; // vom Wrapper (z.B. "v1.0.25" oder "v1.0.25-3-gabcd") + $raw = @trim(@file_get_contents($p) ?: ''); + return $raw !== '' ? $raw : null; + } + + private function normalizeVersion(?string $v): ?string + { + if (!$v) return null; + $v = trim($v); + if ($v === '') return null; + $v = ltrim($v, "vV \t\n\r\0\x0B"); // führendes v entfernen + $v = preg_replace('/-.*$/', '', $v); // Build-/dirty-Suffix abschneiden + return $v !== '' ? $v : null; + } +// public function handle(): int +// { +// $appPath = base_path(); +// $current = trim(@file_get_contents(base_path('VERSION'))) ?: '0.0.0'; +// // newest tag from origin (sorted semver-friendly) +// $latest = trim(shell_exec( +// "cd {$appPath} && git fetch --tags --quiet origin && git tag --list | sort -V | tail -n1" +// ) ?? ''); +// +// // Tags haben usually ein 'v' Prefix – entfernen +// $latest = ltrim($latest, 'v'); +// +// if (!$latest) { +// $this->warn('Keine Release-Tags gefunden.'); +// cache()->forget('mailwolt.update_available'); +// return 0; +// } +// +// if (version_compare($latest, $current, '>')) { +// cache()->forever('mailwolt.update_available', $latest); +// $this->info("Update verfügbar: {$latest} (installiert: {$current})"); +// } else { +// cache()->forget('mailwolt.update_available'); +// $this->info("Aktuell (installiert: {$current})."); +// } +// return 0; +// } } diff --git a/app/Console/Commands/MailwoltRestart.php b/app/Console/Commands/MailwoltRestart.php new file mode 100644 index 0000000..b0be45a --- /dev/null +++ b/app/Console/Commands/MailwoltRestart.php @@ -0,0 +1,36 @@ +info("→ {$unit} ({$action})"); + + exec($cmd . ' 2>&1', $out, $rc); + foreach ($out as $line) { + $this->line(" $line"); + } + + if ($rc !== 0) { + $this->warn(" [!] Fehler beim Neustart von {$unit} (rc={$rc})"); + } else { + $this->line(" [✓] Erfolgreich"); + } + } + + return self::SUCCESS; + } +} diff --git a/app/Jobs/RunHealthChecks.php b/app/Jobs/RunHealthChecks.php index 960fea1..b3d61d8 100644 --- a/app/Jobs/RunHealthChecks.php +++ b/app/Jobs/RunHealthChecks.php @@ -2,6 +2,7 @@ namespace App\Jobs; +use App\Support\CacheVer; use App\Support\WoltGuard\Probes; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Queue\Queueable; @@ -30,47 +31,9 @@ class RunHealthChecks implements ShouldQueue } $svcRows[] = ['name' => $key, 'ok' => $ok]; // labels brauchst du im UI } - Cache::put('health:services', $svcRows, 60); -// try { -// $services = [ -// $this->safe(fn() => $this->service('postfix'), ['name'=>'postfix']), -// $this->safe(fn() => $this->service('dovecot'), ['name'=>'dovecot']), -// $this->safe(fn() => $this->service('rspamd'), ['name'=>'rspamd']), -// $this->safe(fn() => $this->tcp('127.0.0.1', 6379), ['name'=>'redis']), -// $this->safe(fn() => $this->db(), ['name'=>'db']), -// $this->safe(fn() => $this->queueWorkers(), ['name'=>'queue']), -// $this->safe(fn() => $this->tcp('127.0.0.1', 8080), ['name'=>'reverb']), -// ]; -// -// $meta = [ -// 'app_version' => config('app.version', app()->version()), -// 'pending_migs' => $this->safe(fn() => $this->pendingMigrationsCount(), 0), -// 'cert_soon' => $this->safe(fn() => $this->certificatesDue(30), ['count'=>0,'nearest_days'=>null]), -// 'disk' => $this->safe(fn() => $this->diskUsage(), ['percent'=>null,'free_gb'=>null]), -// 'system' => $this->systemLoad(), -// 'updated_at' => now()->toIso8601String(), -// ]; -// -// Cache::put('health:services', array_values($services), 300); -// Cache::put('health:meta', $meta, 300); -// Cache::put('metrics:queues', [ -// 'outgoing' => 19, -// 'incoming' => 5, -// 'today_ok' => 834, -// 'today_err'=> 12, -// 'trend' => [ -// 'outgoing' => [2,1,0,4,3,5,4,0,0,0], // letzte 10 Zeitfenster -// 'incoming' => [1,0,0,1,0,2,1,0,0,0], -// 'ok' => [50,62,71,88,92,110,96,120,130,115], -// 'err' => [1,0,0,2,1,0,1,3,2,2], -// ], -// ], 120); -// Cache::put('events:recent', $this->safe(fn() => $this->recentAlerts(), []), 300); -// } catch (\Throwable $e) { -// // Last-resort catch: never allow the job to fail hard -// Log::error('RunHealthChecks fatal', ['ex' => $e]); -// } + Cache::put(CacheVer::k('health:services'), $svcRows, 60); + Cache::forget('health:services'); } /** Wraps a probe; logs and returns fallback on error */ diff --git a/app/Livewire/Ui/System/ServicesCard.php b/app/Livewire/Ui/System/ServicesCard.php index 76f7fab..7e3d09b 100644 --- a/app/Livewire/Ui/System/ServicesCard.php +++ b/app/Livewire/Ui/System/ServicesCard.php @@ -2,6 +2,7 @@ namespace App\Livewire\Ui\System; +use App\Support\CacheVer; use App\Support\WoltGuard\Probes; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; @@ -25,35 +26,6 @@ class ServicesCard extends Component // optionales Polling im Blade (du hast es dort referenziert) public int $pollSeconds = 15; - /** Pro Karte mehrere Quellen – Grün sobald eine Quelle OK ist */ -// protected array $cards = [ -// // Mail -// 'postfix' => ['label'=>'Postfix','hint'=>'MTA / Versand','sources'=>['systemd:postfix']], -// 'dovecot' => ['label'=>'Dovecot','hint'=>'IMAP / POP3','sources'=>['systemd:dovecot','tcp:127.0.0.1:993']], -// 'rspamd' => ['label'=>'Rspamd','hint'=>'Spamfilter','sources'=>['systemd:rspamd','tcp:127.0.0.1:11333','tcp:127.0.0.1:11334']], -// 'clamav' => ['label'=>'ClamAV','hint'=>'Virenscanner','sources'=>[ -// 'systemd:clamav-daemon','systemd:clamav-daemon@scan','systemd:clamd', -// 'socket:/run/clamav/clamd.ctl','pid:/run/clamav/clamd.pid','tcp:127.0.0.1:3310', -// ]], -// // Daten & Cache -// 'db' => ['label'=>'Datenbank','hint'=>'MySQL / MariaDB','sources'=>['db']], -// 'redis' => ['label'=>'Redis','hint'=>'Cache / Queue','sources'=>['tcp:127.0.0.1:6379','systemd:redis-server','systemd:redis']], -// // Web / PHP -// 'php-fpm' => ['label'=>'PHP-FPM','hint'=>'PHP Runtime','sources'=>[ -// 'systemd:php8.3-fpm','systemd:php8.2-fpm','systemd:php8.1-fpm','systemd:php-fpm', -// 'socket:/run/php/php8.3-fpm.sock','socket:/run/php/php8.2-fpm.sock', -// 'socket:/run/php/php8.1-fpm.sock','socket:/run/php/php-fpm.sock','tcp:127.0.0.1:9000', -// ]], -// 'nginx' => ['label'=>'Nginx','hint'=>'Webserver','sources'=>['systemd:nginx','tcp:127.0.0.1:80']], -// // MailWolt (Unit ODER laufender artisan Prozess) -// 'mw-queue' => ['label'=>'MailWolt Queue','hint'=>'Job Worker','sources'=>['systemd:mailwolt-queue','proc:/php.*artisan(\.php)?\s+queue:work']], -// 'mw-schedule' => ['label'=>'MailWolt Schedule','hint'=>'Task Scheduler','sources'=>['systemd:mailwolt-schedule','proc:/php.*artisan(\.php)?\s+schedule:work']], -// 'mw-ws' => ['label'=>'MailWolt WebSocket','hint'=>'Echtzeit Updates','sources'=>['systemd:mailwolt-ws','tcp:127.0.0.1:8080']], -// // Sonstiges -// 'fail2ban' => ['label'=>'Fail2Ban','hint'=>'SSH / Mail Protection','sources'=>['systemd:fail2ban']], -// 'journal' => ['label'=>'System Logs','hint'=>'Journal','sources'=>['systemd:systemd-journald','systemd:rsyslog']], -// ]; - public function mount(): void { $this->load(); @@ -73,7 +45,15 @@ class ServicesCard extends Component public function load(): void { $cards = config('woltguard.cards', []); - $cached = collect(Cache::get('health:services', []))->keyBy('name'); + + $raw = Cache::get(CacheVer::k('health:services'), []); + + // einmaliger Fallback für ältere Deploys: + if (empty($raw)) { + $raw = Cache::get('health:services', []); + } + + $cached = collect($raw)->keyBy('name'); $rows = []; $ok = 0; diff --git a/app/Livewire/Ui/System/UpdateCard.php b/app/Livewire/Ui/System/UpdateCard.php index ee6f61f..52c8d0d 100644 --- a/app/Livewire/Ui/System/UpdateCard.php +++ b/app/Livewire/Ui/System/UpdateCard.php @@ -21,117 +21,34 @@ class UpdateCard extends Component public bool $running = false; public ?int $rc = null; + public bool $postActionsDone = false; + protected string $cacheStartedAtKey = 'mw.update.started_at'; - protected int $failsafeSeconds = 20 * 60; // 20 Minuten - - public function mount(): void - { - $this->reloadVersionsAndStatus(); - $this->recompute(); - if ($this->running) $this->state = 'running'; - } - - public function render() - { - return view('livewire.ui.system.update-card'); - } - - /* ========== Aktionen ========== */ - - public function refreshState(): void - { - $this->state = 'running'; - $this->message = 'Prüfe auf Updates …'; - $this->messagePositive = null; - - try { - Artisan::call('mailwolt:check-updates'); - } catch (\Throwable $e) { - // kein fataler Abbruch - } - - $this->reloadVersionsAndStatus(); - $this->recompute(); - $this->finishUiIfNoUpdate(); - } - - public function runUpdate(): void - { - Cache::forget('mailwolt.update_available'); - Cache::put($this->cacheStartedAtKey, time(), now()->addHour()); - - @shell_exec('nohup sudo -n /usr/local/sbin/mw-update >/dev/null 2>&1 &'); - - $this->latest = null; - $this->displayLatest = null; - $this->hasUpdate = false; - $this->state = 'running'; - $this->running = true; - $this->message = 'Update läuft …'; - } - - public function tick(): void - { - $this->refreshLowLevelState(); - - $started = (int)Cache::get($this->cacheStartedAtKey, 0); - if ($this->running && $started > 0 && (time() - $started) > $this->failsafeSeconds) { - $this->running = false; - $this->rc ??= 0; - } - - if (!$this->running) { - Cache::forget($this->cacheStartedAtKey); - $this->reloadVersionsAndStatus(); - $this->recompute(); - $this->finishUiIfNoUpdate(); - - if ($this->rc === 0) { - $ver = $this->displayCurrent ?? 'aktuelle Version'; - - // ✅ zentraler Toastra-Toast - $this->dispatch('toast', - type: 'success', - title: 'Update erfolgreich', - text: "MailWolt wurde erfolgreich auf {$ver} aktualisiert. Die Seite wird in 5 Sekunden neu geladen …", - badge: 'System', - duration: 5000 - ); - - // nach 5 Sekunden Seite neu laden - $this->dispatch('reload-page', delay: 5000); - } elseif ($this->rc !== null) { - $this->dispatch('toast', - type: 'error', - title: 'Update fehlgeschlagen', - text: "Update ist mit Rückgabecode {$this->rc} fehlgeschlagen. Bitte Logs prüfen.", - badge: 'System', - duration: 0 - ); - } - - $this->state = 'idle'; - } - } - - /* ========== Helper ========== */ + protected int $failsafeSeconds = 20 * 60; + private const VERSION_FILE = '/var/lib/mailwolt/version'; // normiert, ohne "v" + private const VERSION_FILE_RAW = '/var/lib/mailwolt/version_raw'; // original ("v1.0.25" o.ä.) + private const BUILD_INFO = '/etc/mailwolt/build.info'; // Fallback protected function reloadVersionsAndStatus(): void { $this->current = $this->readCurrentVersion(); - $this->latest = Cache::get('mailwolt.update_available'); - $this->refreshLowLevelState(); - } - protected function finishUiIfNoUpdate(): void - { - if (!$this->hasUpdate) { - $this->state = 'idle'; - $cur = $this->displayCurrent ?? '–'; - $this->message = "Du bist auf dem neuesten Stand ({$cur})"; - $this->messagePositive = true; - Cache::forget('mailwolt.update_available'); + // Update-Checker soll beide Keys schreiben: + // - updates:latest (normiert, ohne v) + // - updates:latest_raw (Original) + $latNorm = Cache::get('updates:latest'); + $latRaw = Cache::get('updates:latest_raw'); + + // Falls dein alter Key noch benutzt wird, weiter kompatibel: + if (!$latNorm && ($legacy = Cache::get('mailwolt.update_available'))) { + $latNorm = $this->normalizeVersion($legacy); + $latRaw = $legacy; } + + $this->latest = $latNorm ?: null; // für Vergleiche + $this->displayLatest = $latRaw ?: ($latNorm ? 'v'.$latNorm : null); + + $this->refreshLowLevelState(); } protected function recompute(): void @@ -143,44 +60,310 @@ class UpdateCard extends Component ? version_compare($latNorm, $curNorm, '>') : false; - // Immer mit v-Präfix anzeigen - $this->displayCurrent = $curNorm ? 'v' . $curNorm : null; - $this->displayLatest = ($this->hasUpdate && $latNorm) ? 'v' . $latNorm : null; + // Anzeige immer hübsch mit "v", Vergleich bleibt normiert + $this->displayCurrent = $curNorm ? 'v'.$curNorm : null; + + // displayLatest NICHT mehr vom hasUpdate abhängig machen – bleibt informativ sichtbar + if (!$this->displayLatest && $latNorm) { + $this->displayLatest = 'v'.$latNorm; + } } - protected function refreshLowLevelState(): void + protected function finishUiIfNoUpdate(): void { - $state = @trim(@file_get_contents('/var/lib/mailwolt/update/state') ?: ''); - $this->running = ($state === 'running'); + if (!$this->hasUpdate) { + $this->state = 'idle'; + $cur = $this->displayCurrent ?? '–'; + $this->message = "Du bist auf dem neuesten Stand ({$cur})"; + $this->messagePositive = true; - $rcRaw = @trim(@file_get_contents('/var/lib/mailwolt/update/rc') ?: ''); - $this->rc = is_numeric($rcRaw) ? (int)$rcRaw : null; + // Nur den alten Legacy-Key aufräumen + Cache::forget('mailwolt.update_available'); + } } protected function readCurrentVersion(): ?string { - $build = @file_get_contents('/etc/mailwolt/build.info'); + // 1) Wrapper-Datei (normiert, ohne „v“) + $v = @trim(@file_get_contents(self::VERSION_FILE) ?: ''); + if ($v !== '') return $v; + + // 2) Raw -> normieren + $raw = @trim(@file_get_contents(self::VERSION_FILE_RAW) ?: ''); + if ($raw !== '') return $this->normalizeVersion($raw); + + // 3) Fallback build.info (format: "version=…") + $build = @file_get_contents(self::BUILD_INFO); if ($build) { foreach (preg_split('/\R+/', $build) as $line) { if (str_starts_with($line, 'version=')) { - $v = trim(substr($line, 8)); - return $v !== '' ? $v : null; + $v = $this->normalizeVersion(trim(substr($line, 8))); + if ($v) return $v; } } } - $v = config('app.version'); - return $v !== '' ? $v : null; + + // 4) Noch ein letzter Fallback + $v = $this->normalizeVersion(config('app.version') ?: ''); + return $v ?: null; } protected function normalizeVersion(?string $v): ?string { if ($v === null) return null; $v = trim($v); - return ltrim($v, "vV \t\n\r\0\x0B") ?: null; + if ($v === '') return null; + + // führendes v entfernen, Build-Suffixe (z.B. "-3-gabcd" oder "-dirty") kappen + $v = ltrim($v, "vV \t\n\r\0\x0B"); + $v = preg_replace('/-.*$/', '', $v); // alles nach erster '-' weg + return $v !== '' ? $v : null; } + + public function runUpdate(): void + { + Cache::forget('mailwolt.update_available'); + Cache::put($this->cacheStartedAtKey, time(), now()->addHour()); + + // Name korrigiert: Wrapper ist /usr/local/sbin/mailwolt-update + @shell_exec('nohup sudo -n /usr/local/sbin/mailwolt-update >/dev/null 2>&1 &'); + + $this->latest = null; + $this->displayLatest = null; + $this->hasUpdate = false; + $this->state = 'running'; + $this->running = true; + $this->message = 'Update läuft …'; + } + + protected function refreshLowLevelState(): void + { + // Wrapper-Status lesen + $state = @trim(@file_get_contents('/var/lib/mailwolt/update/state') ?: ''); + + // Läuft, wenn Datei "running" enthält – alles andere gilt als nicht laufend + $this->running = ($state === 'running'); + + // Rückgabecode (nur gesetzt, wenn Update beendet ist) + $rcRaw = @trim(@file_get_contents('/var/lib/mailwolt/update/rc') ?: ''); + $this->rc = is_numeric($rcRaw) ? (int)$rcRaw : null; + } + + public function pollUpdate(): void + { + // 1) Low-Level Status vom Wrapper (/var/lib/mailwolt/update/{state,rc}) + $this->refreshLowLevelState(); + + // 2) failsafe: wenn zu lange läuft, als beendet markieren + $started = (int) Cache::get($this->cacheStartedAtKey, 0); + if ($this->running && $started > 0 && (time() - $started) > $this->failsafeSeconds) { + $this->running = false; + $this->rc ??= 0; // notfalls als “erfolgreich” werten + } + + // 3) Wenn nicht mehr running ⇒ Abschluss behandeln + if (!$this->running) { + Cache::forget($this->cacheStartedAtKey); + + // Versionen neu laden und “hasUpdate” neu berechnen + $this->reloadVersionsAndStatus(); + $this->recompute(); + + // 3a) Erfolg: jetzt erst Dienste neu starten + if ($this->rc === 0 && !$this->postActionsDone) { + // Restart asynchron starten (dein Artisan-Command macht systemctl etc.) + @shell_exec('nohup php /var/www/mailwolt/artisan mailwolt:restart-services >/dev/null 2>&1 &'); + $this->postActionsDone = true; + + // Toast + $ver = $this->displayCurrent ?? 'aktuelle Version'; + $this->dispatch('toast', + type: 'success', + title: 'Update erfolgreich', + text: "MailWolt wurde auf {$ver} aktualisiert. Dienste werden neu gestartet …", + badge: 'System', + duration: 5000 + ); + + // Seite nach 5s neu laden + $this->dispatch('reload-page', delay: 5000); + } + // 3b) Fehler: Meldung zeigen + elseif ($this->rc !== null && $this->rc !== 0 && !$this->postActionsDone) { + $this->postActionsDone = true; + $this->dispatch('toast', + type: 'error', + title: 'Update fehlgeschlagen', + text: "Update ist mit Rückgabecode {$this->rc} fehlgeschlagen. Bitte Logs prüfen.", + badge: 'System', + duration: 0 + ); + } + + // 4) UI zurück auf idle + $this->state = 'idle'; + } + } + } +// public function mount(): void +// { +// $this->reloadVersionsAndStatus(); +// $this->recompute(); +// if ($this->running) $this->state = 'running'; +// } +// +// public function render() +// { +// return view('livewire.ui.system.update-card'); +// } +// +// /* ========== Aktionen ========== */ +// +// public function refreshState(): void +// { +// $this->state = 'running'; +// $this->message = 'Prüfe auf Updates …'; +// $this->messagePositive = null; +// +// try { +// Artisan::call('mailwolt:check-updates'); +// } catch (\Throwable $e) { +// // kein fataler Abbruch +// } +// +// $this->reloadVersionsAndStatus(); +// $this->recompute(); +// $this->finishUiIfNoUpdate(); +// } +// +// public function runUpdate(): void +// { +// Cache::forget('mailwolt.update_available'); +// Cache::put($this->cacheStartedAtKey, time(), now()->addHour()); +// +// @shell_exec('nohup sudo -n /usr/local/sbin/mw-update >/dev/null 2>&1 &'); +// +// $this->latest = null; +// $this->displayLatest = null; +// $this->hasUpdate = false; +// $this->state = 'running'; +// $this->running = true; +// $this->message = 'Update läuft …'; +// } +// +// public function tick(): void +// { +// $this->refreshLowLevelState(); +// +// $started = (int)Cache::get($this->cacheStartedAtKey, 0); +// if ($this->running && $started > 0 && (time() - $started) > $this->failsafeSeconds) { +// $this->running = false; +// $this->rc ??= 0; +// } +// +// if (!$this->running) { +// Cache::forget($this->cacheStartedAtKey); +// $this->reloadVersionsAndStatus(); +// $this->recompute(); +// $this->finishUiIfNoUpdate(); +// +// if ($this->rc === 0) { +// $ver = $this->displayCurrent ?? 'aktuelle Version'; +// +// // ✅ zentraler Toastra-Toast +// $this->dispatch('toast', +// type: 'success', +// title: 'Update erfolgreich', +// text: "MailWolt wurde erfolgreich auf {$ver} aktualisiert. Die Seite wird in 5 Sekunden neu geladen …", +// badge: 'System', +// duration: 5000 +// ); +// +// // nach 5 Sekunden Seite neu laden +// $this->dispatch('reload-page', delay: 5000); +// } elseif ($this->rc !== null) { +// $this->dispatch('toast', +// type: 'error', +// title: 'Update fehlgeschlagen', +// text: "Update ist mit Rückgabecode {$this->rc} fehlgeschlagen. Bitte Logs prüfen.", +// badge: 'System', +// duration: 0 +// ); +// } +// +// $this->state = 'idle'; +// } +// } +// +// /* ========== Helper ========== */ +// +// protected function reloadVersionsAndStatus(): void +// { +// $this->current = $this->readCurrentVersion(); +// $this->latest = Cache::get('mailwolt.update_available'); +// $this->refreshLowLevelState(); +// } +// +// protected function finishUiIfNoUpdate(): void +// { +// if (!$this->hasUpdate) { +// $this->state = 'idle'; +// $cur = $this->displayCurrent ?? '–'; +// $this->message = "Du bist auf dem neuesten Stand ({$cur})"; +// $this->messagePositive = true; +// Cache::forget('mailwolt.update_available'); +// } +// } +// +// protected function recompute(): void +// { +// $curNorm = $this->normalizeVersion($this->current); +// $latNorm = $this->normalizeVersion($this->latest); +// +// $this->hasUpdate = ($curNorm && $latNorm) +// ? version_compare($latNorm, $curNorm, '>') +// : false; +// +// // Immer mit v-Präfix anzeigen +// $this->displayCurrent = $curNorm ? 'v' . $curNorm : null; +// $this->displayLatest = ($this->hasUpdate && $latNorm) ? 'v' . $latNorm : null; +// } +// +// protected function refreshLowLevelState(): void +// { +// $state = @trim(@file_get_contents('/var/lib/mailwolt/update/state') ?: ''); +// $this->running = ($state === 'running'); +// +// $rcRaw = @trim(@file_get_contents('/var/lib/mailwolt/update/rc') ?: ''); +// $this->rc = is_numeric($rcRaw) ? (int)$rcRaw : null; +// } +// +// protected function readCurrentVersion(): ?string +// { +// $build = @file_get_contents('/etc/mailwolt/build.info'); +// if ($build) { +// foreach (preg_split('/\R+/', $build) as $line) { +// if (str_starts_with($line, 'version=')) { +// $v = trim(substr($line, 8)); +// return $v !== '' ? $v : null; +// } +// } +// } +// $v = config('app.version'); +// return $v !== '' ? $v : null; +// } +// +// protected function normalizeVersion(?string $v): ?string +// { +// if ($v === null) return null; +// $v = trim($v); +// return ltrim($v, "vV \t\n\r\0\x0B") ?: null; +// } + + //namespace App\Livewire\Ui\System; // //use Illuminate\Support\Facades\Artisan; diff --git a/app/Support/CacheVer.php b/app/Support/CacheVer.php new file mode 100644 index 0000000..72f5a36 --- /dev/null +++ b/app/Support/CacheVer.php @@ -0,0 +1,28 @@ + [ + ['name' => 'nginx', 'action' => 'reload'], + ['name' => 'php8.2-fpm', 'action' => 'restart'], + ['name' => 'postfix', 'action' => 'try-reload-or-restart'], + ['name' => 'dovecot', 'action' => 'try-reload-or-restart'], + ['name' => 'rspamd', 'action' => 'try-reload-or-restart'], + ['name' => 'opendkim', 'action' => 'try-reload-or-restart'], + ['name' => 'opendmarc', 'action' => 'try-reload-or-restart'], + ['name' => 'clamav-daemon', 'action' => 'try-reload-or-restart'], + ['name' => 'redis-server', 'action' => 'try-reload-or-restart'], + ], +]; diff --git a/resources/views/livewire/ui/domain/modal/domain-dns-modal.blade.php b/resources/views/livewire/ui/domain/modal/domain-dns-modal.blade.php index 8515255..eb2a32a 100644 --- a/resources/views/livewire/ui/domain/modal/domain-dns-modal.blade.php +++ b/resources/views/livewire/ui/domain/modal/domain-dns-modal.blade.php @@ -8,6 +8,7 @@ TTL: {{ $ttl }}

DNS-Einträge

+

Setze die folgenden Records für {{ $zone }}. diff --git a/resources/views/livewire/ui/system/update-card.blade.php b/resources/views/livewire/ui/system/update-card.blade.php index a3d2da8..fb4449e 100644 --- a/resources/views/livewire/ui/system/update-card.blade.php +++ b/resources/views/livewire/ui/system/update-card.blade.php @@ -97,7 +97,7 @@ {{----}}

{{-- Shield + Update-Icon --}}