reloadVersionsAndStatus(); $this->recompute(); $this->progressLine = ''; // $this->progressLine = $this->tailUpdateLog(); if ($this->running) { $this->state = 'running'; } $this->recomputeUi(); } public function render() { return view('livewire.ui.system.update-card'); } /* ================== Aktionen ================== */ public function runUpdate(): void { // evtl. alte Einträge aufräumen Cache::forget('mailwolt.update_available'); Cache::put($this->cacheStartedAtKey, time(), now()->addHour()); // Wrapper starten (setzt /var/lib/mailwolt/update/{state,rc} und schreibt Versionen) @shell_exec('nohup sudo -n /usr/local/sbin/mailwolt-update >/dev/null 2>&1 &'); // Sofort ins Running gehen $this->latest = null; $this->displayLatest = null; $this->hasUpdate = false; $this->state = 'running'; $this->running = true; $this->errorLine = null; $this->progressLine = 'Update gestartet …'; $this->recomputeUi(); } public function pollUpdate(): void { // 1) aktuellen Wrapper-Status einlesen $this->refreshLowLevelState(); if ($this->rc !== null) { $this->running = false; } // 2) Failsafe $started = (int)Cache::get($this->cacheStartedAtKey, 0); if ($this->running && $started > 0 && (time() - $started) > $this->failsafeSeconds) { $this->running = false; $this->rc ??= 0; // wenn unklar, als erfolgreich werten } // 3) Abschluss? if (!$this->running) { Cache::forget($this->cacheStartedAtKey); // Nachlauf: Versionen & Vergleich neu aufbauen $this->reloadVersionsAndStatus(); $this->recompute(); if ($this->rc === 0 && !$this->postActionsDone) { // Dienste neu starten (asynchron) // @shell_exec('nohup php /var/www/mailwolt/artisan mailwolt:restart-services >/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 &'); $this->postActionsDone = true; $ver = $this->displayCurrent ?? 'aktuelle Version'; $this->progressLine = 'Update abgeschlossen: ' . $ver; // $this->dispatch('reload-page', delay: 5000); $this->dispatch('toast', type: 'success', title: 'Update erfolgreich', text: "MailWolt wurde auf {$ver} aktualisiert.", badge: 'System', duration: 4000 ); } elseif ($this->rc !== null && $this->rc !== 0 && !$this->postActionsDone) { // Fehlerfall $this->postActionsDone = true; $this->errorLine = "Update fehlgeschlagen (rc={$this->rc})."; $this->dispatch('toast', type: 'error', title: 'Update fehlgeschlagen', text: $this->progressLine ?: 'Bitte Logs prüfen: /var/log/mailwolt-update.log', badge: 'System', duration: 0 ); } $this->state = 'idle'; } // UI bei jedem Poll neu ableiten $this->recomputeUi(); } /* ================== Helpers ================== */ protected function reloadVersionsAndStatus(): void { $this->current = $this->readCurrentVersion(); // Update-Checker schreibt: // - updates:latest (normiert) // - updates:latest_raw (original) $latNorm = Cache::get('updates:latest'); $latRaw = Cache::get('updates:latest_raw'); // Legacy-Fallback if (!$latNorm && ($legacy = Cache::get('mailwolt.update_available'))) { $latNorm = $this->normalizeVersion($legacy); $latRaw = $legacy; } $this->latest = $latNorm ?: null; $this->displayLatest = $latRaw ?: ($latNorm ? 'v' . $latNorm : null); $this->refreshLowLevelState(); } protected function recompute(): void { $curNorm = $this->normalizeVersion($this->current); $latNorm = $this->normalizeVersion($this->latest); $this->hasUpdate = ($curNorm && $latNorm) ? version_compare($latNorm, $curNorm, '>') : false; $this->displayCurrent = $curNorm ? 'v' . $curNorm : null; if (!$this->displayLatest && $latNorm) { $this->displayLatest = 'v' . $latNorm; } } protected function recomputeUi(): void { if ($this->state === 'running') { $this->badgeText = 'Update läuft'; $this->badgeIcon = 'ph-arrows-clockwise animate-spin'; $this->badgeClass = 'text-sky-200 bg-sky-500/10 border-sky-400/30'; $this->showButton = true; $this->buttonDisabled = true; $this->buttonLabel = 'Update läuft …'; return; } if ($this->rc !== null && $this->rc !== 0) { $this->badgeText = 'Fehlgeschlagen'; $this->badgeIcon = 'ph-warning-circle'; $this->badgeClass = 'text-rose-200 bg-rose-500/10 border-rose-400/30'; $this->showButton = true; $this->buttonDisabled = false; $this->buttonLabel = 'Erneut versuchen'; return; } if ($this->hasUpdate) { $this->badgeText = 'Update verfügbar'; $this->badgeIcon = 'ph-arrow-fat-line-up'; $this->badgeClass = 'text-yellow-200 bg-yellow-500/10 border-yellow-400/30'; $this->showButton = true; $this->buttonDisabled = false; $this->buttonLabel = 'Jetzt aktualisieren'; return; } // Aktuell $this->badgeText = 'Aktuell'; $this->badgeIcon = 'ph-check-circle'; $this->badgeClass = 'text-emerald-200 bg-emerald-500/10 border-emerald-400/30'; $this->showButton = false; $this->buttonDisabled = true; $this->buttonLabel = ''; } protected function finishUiIfNoUpdate(): void { if (!$this->hasUpdate) { $this->state = 'idle'; Cache::forget('mailwolt.update_available'); // Legacy-Key } } protected function refreshLowLevelState(): void { $state = @trim(@file_get_contents('/var/lib/mailwolt/update/state') ?: ''); $rcRaw = @trim(@file_get_contents('/var/lib/mailwolt/update/rc') ?: ''); $this->rc = is_numeric($rcRaw) ? (int)$rcRaw : null; $this->running = ($this->rc === null) && ($state !== 'done'); $this->progressLine = $this->tailUpdateLog(); // $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; // // $this->progressLine = $this->tailUpdateLog(); } protected function readCurrentVersion(): ?string { // 1) normierte Version vom Wrapper $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) build.info $build = @file_get_contents(self::BUILD_INFO); if ($build) { foreach (preg_split('/\R+/', $build) as $line) { if (str_starts_with($line, 'version=')) { $v = $this->normalizeVersion(trim(substr($line, 8))); if ($v) return $v; } } } // 4) Fallback auf config(app.version) $v = $this->normalizeVersion(config('app.version') ?: ''); return $v ?: null; } protected function normalizeVersion(?string $v): ?string { if ($v === null) return null; $v = trim($v); if ($v === '') return null; $v = ltrim($v, "vV \t\n\r\0\x0B"); // führendes v weg $v = preg_replace('/-.*$/', '', $v); // Build-/dirty-Suffix kappen return $v !== '' ? $v : null; } /** Einzeilige letzte Log-Zeile hübsch aufbereitet */ protected function tailUpdateLog(): ?string { $p = self::UPDATE_LOG; if (!is_readable($p)) return null; $lines = @file($p, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); if (!$lines || !count($lines)) return null; $last = trim(end($lines)); // Kosmetik $last = preg_replace('/^\[\w\]\s*/', '', $last); // "[i] " etc. $last = preg_replace('/^=+ .*? =+\s*$/', 'Update beendet', $last); // Banner zusammenfassen $last = preg_replace('/^\d{4}-\d{2}-\d{2}T[^ ]+\s*::\s*/', '', $last); // "2025-.. :: " entfernen return Str::limit($last, 120); } } //namespace App\Livewire\Ui\System; // //use Illuminate\Support\Facades\Artisan; //use Illuminate\Support\Facades\Cache; //use Livewire\Component; // //class UpdateCard extends Component //{ // public ?string $current = null; // public ?string $latest = null; // public ?string $displayCurrent = null; // public ?string $displayLatest = null; // // public bool $hasUpdate = false; // public string $state = 'idle'; // public ?string $message = null; // public ?bool $messagePositive = null; // // public bool $running = false; // public ?int $rc = null; // // // NEU: UI-Properties (nur fürs Blade ausgeben) // public string $badgeText = ''; // public string $badgeClass = ''; // public string $badgeIcon = ''; // public bool $showButton = false; // public bool $buttonDisabled = false; // public string $buttonLabel = ''; // public ?string $progressLine = null; // Einzeiler unter der Version // public ?string $errorLine = null; // optionaler Fehlertext // // public bool $postActionsDone = false; // // protected string $cacheStartedAtKey = 'mw.update.started_at'; // 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 // // public function mount(): void // { // $this->reloadVersionsAndStatus(); // $this->recompute(); // // // falls ein Update bereits läuft (State-Datei existiert), gleich “running” zeigen // if ($this->running) { // $this->state = 'running'; // } // } // // protected function reloadVersionsAndStatus(): void // { // $this->current = $this->readCurrentVersion(); // // // 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 // { // $curNorm = $this->normalizeVersion($this->current); // $latNorm = $this->normalizeVersion($this->latest); // // $this->hasUpdate = ($curNorm && $latNorm) // ? version_compare($latNorm, $curNorm, '>') // : false; // // // 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 finishUiIfNoUpdate(): void // { // if (!$this->hasUpdate) { // $this->state = 'idle'; // $cur = $this->displayCurrent ?? '–'; // $this->message = "Du bist auf dem neuesten Stand ({$cur})"; // $this->messagePositive = true; // // // Nur den alten Legacy-Key aufräumen // Cache::forget('mailwolt.update_available'); // } // } // // protected function readCurrentVersion(): ?string // { // // 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 = $this->normalizeVersion(trim(substr($line, 8))); // if ($v) return $v; // } // } // } // // // 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); // 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'; // } // } // //}