['label' => 'Nginx', 'service' => 'nginx'], 'postfix' => ['label' => 'Postfix', 'service' => 'postfix'], 'dovecot' => ['label' => 'Dovecot', 'service' => 'dovecot'], 'rspamd' => ['label' => 'Rspamd', 'service' => 'rspamd'], 'fail2ban' => ['label' => 'Fail2ban', 'service' => 'fail2ban'], 'certbot' => ['label' => 'Certbot', 'service' => null, 'binary' => 'certbot'], ]; /* ========================================================= */ public function mount(): void { $this->refreshLowLevelState(); $this->readLogLines(); if ($this->running) { $this->state = 'running'; } $this->checkComponentStatus(); $this->recalcProgress(); } public function render() { return view('livewire.ui.system.installer-page'); } /* ================== Aktionen ================== */ public function openConfirmModal(string $component = 'all'): void { $this->component = $component; $this->dispatch('openModal', component: 'ui.system.modal.installer-confirm-modal', arguments: ['component' => $component] ); } public function runInstaller(string $component = 'all'): void { if ($this->running || $this->state === 'running') { $this->dispatch('toast', type: 'warn', badge: 'Installer', title: 'Läuft bereits', text: 'Ein Installer-Prozess ist bereits aktiv.', duration: 3000); return; } $this->component = $component; $this->state = 'running'; $this->running = true; $this->rc = null; $this->postActionsDone = false; $this->logLines = ['Installer gestartet …']; $this->progressPct = 5; $safeComponent = preg_replace('/[^a-z0-9_-]/i', '', $component); @shell_exec("nohup sudo -n /usr/local/sbin/mailwolt-install {$safeComponent} >/dev/null 2>&1 &"); } public function pollStatus(): void { $this->refreshLowLevelState(); $this->readLogLines(); $this->recalcProgress(); if ($this->rc !== null) { $this->running = false; } if ($this->lowState === 'done') { usleep(300_000); $this->readLogLines(); $this->progressPct = 100; if ($this->rc === 0 && !$this->postActionsDone) { @shell_exec('nohup php /var/www/mailwolt/artisan health:collect >/dev/null 2>&1 &'); $this->postActionsDone = true; $this->dispatch('toast', type: 'done', badge: 'Installer', title: 'Installation abgeschlossen', text: 'Die Komponente wurde erfolgreich installiert/konfiguriert.', duration: 6000); } elseif ($this->rc !== null && $this->rc !== 0 && !$this->postActionsDone) { $this->postActionsDone = true; $this->dispatch('toast', type: 'error', badge: 'Installer', title: 'Installation fehlgeschlagen', text: "Rückgabecode: {$this->rc}. Bitte Log prüfen.", duration: 0); } $this->state = 'idle'; $this->checkComponentStatus(); } } public function checkComponentStatus(): void { $statuses = []; foreach (self::COMPONENTS as $key => $info) { $installed = false; $active = false; // Check if binary exists $binary = $info['binary'] ?? $key; $which = @trim(@shell_exec("which {$binary} 2>/dev/null") ?: ''); $installed = $which !== ''; // Check service active state if ($installed && isset($info['service']) && $info['service']) { $svcState = @trim(@shell_exec("systemctl is-active {$info['service']} 2>/dev/null") ?: ''); $active = ($svcState === 'active'); } elseif ($installed && $key === 'certbot') { $active = true; // certbot is a one-shot tool, if installed it's "OK" } $statuses[$key] = [ 'label' => $info['label'], 'installed' => $installed, 'active' => $active, ]; } $this->componentStatus = $statuses; } public function clearLog(): void { @file_put_contents(self::INSTALL_LOG, ''); $this->logLines = []; $this->dispatch('toast', type: 'done', badge: 'Installer', title: 'Log geleert', text: '', duration: 2500); } /* ================== Helpers ================== */ protected function refreshLowLevelState(): void { $state = @trim(@file_get_contents(self::STATE_DIR . '/state') ?: ''); $rcRaw = @trim(@file_get_contents(self::STATE_DIR . '/rc') ?: ''); $this->lowState = $state !== '' ? $state : null; $this->running = ($this->lowState !== 'done'); $this->rc = ($this->lowState === 'done' && is_numeric($rcRaw)) ? (int) $rcRaw : null; } protected function readLogLines(): void { $p = self::INSTALL_LOG; if (!is_readable($p)) { $this->logLines = []; return; } $lines = @file($p, FILE_IGNORE_NEW_LINES) ?: []; $this->logLines = array_slice($lines, -100); } protected function recalcProgress(): void { if ($this->state !== 'running' && $this->lowState !== 'running') { if ($this->lowState === 'done') { $this->progressPct = 100; } return; } $text = implode("\n", $this->logLines); $pct = 5; foreach ([ 'Installation gestartet' => 10, 'Nginx' => 20, 'Postfix' => 35, 'Dovecot' => 50, 'Rspamd' => 65, 'Fail2ban' => 78, 'SSL' => 88, 'Installation beendet' => 100, ] as $needle => $val) { if (stripos($text, $needle) !== false) { $pct = max($pct, $val); } } if ($this->lowState === 'done') { $pct = 100; } $this->progressPct = $pct; } }