'pending', 'webmail' => 'pending', 'mail' => 'pending']; private const SSL_STATE_DIR = '/var/lib/mailwolt/wizard'; public function mount(): void { $this->uiDomain = (string) Setting::get('ui_domain', ''); $this->webmailDomain = (string) Setting::get('webmail_domain', ''); $this->mailDomain = (string) Setting::get('mail_domain', ''); $this->certs = $this->loadCertificates(); $this->restoreSslProvisioningState(); } public function refresh(): void { $this->certs = $this->loadCertificates(); $this->dispatch('toast', type: 'done', badge: 'SSL', title: 'Aktualisiert', text: 'Zertifikatsliste wurde neu geladen.', duration: 3000); } public function startSslProvisioning(): void { if (! ($this->uiDomain && $this->webmailDomain && $this->mailDomain)) { $this->dispatch('toast', type: 'warn', badge: 'SSL', title: 'Domains fehlen', text: 'Bitte erst alle Domains unter Einstellungen speichern.', duration: 6000); return; } @mkdir(self::SSL_STATE_DIR, 0755, true); @unlink(self::SSL_STATE_DIR . '/done'); foreach (['ui', 'mail', 'webmail'] as $k) { file_put_contents(self::SSL_STATE_DIR . "/{$k}", 'pending'); } $this->sslProgress = ['ui' => 'pending', 'webmail' => 'pending', 'mail' => 'pending']; $this->sslDone = false; $this->sslProvisioning = true; $artisan = base_path('artisan'); $cmd = sprintf( 'nohup php %s clubird:wizard-domains --ui=%s --mail=%s --webmail=%s --ssl=1 > /dev/null 2>&1 &', escapeshellarg($artisan), escapeshellarg($this->uiDomain), escapeshellarg($this->mailDomain), escapeshellarg($this->webmailDomain), ); @shell_exec($cmd); } public function pollSsl(): void { if (! $this->sslProvisioning || $this->sslDone) return; foreach (['ui', 'mail', 'webmail'] as $key) { $file = self::SSL_STATE_DIR . "/{$key}"; $this->sslProgress[$key] = is_readable($file) ? trim((string) @file_get_contents($file)) : 'pending'; } $done = @file_get_contents(self::SSL_STATE_DIR . '/done'); if ($done !== false) { $this->sslDone = true; $this->sslProvisioning = false; $this->certs = $this->loadCertificates(); } } public function renew(string $name): void { $safe = preg_replace('/[^a-z0-9._-]/i', '', $name); if ($safe === '') return; $out = (string) @shell_exec( "sudo -n /usr/bin/certbot renew --cert-name {$safe} --force-renewal 2>&1" ); $this->certs = $this->loadCertificates(); if (str_contains($out, 'Successfully renewed') || str_contains($out, 'success')) { $this->dispatch('toast', type: 'done', badge: 'SSL', title: 'Zertifikat erneuert', text: "Zertifikat {$safe} wurde erfolgreich erneuert.", duration: 5000); } else { $this->dispatch('toast', type: 'error', badge: 'SSL', title: 'Fehler', text: nl2br(htmlspecialchars(substr($out, 0, 300))), duration: 0); } } private function restoreSslProvisioningState(): void { $doneFile = self::SSL_STATE_DIR . '/done'; if (! file_exists($doneFile)) return; $this->sslDone = true; $this->sslProvisioning = false; foreach (['ui', 'mail', 'webmail'] as $key) { $file = self::SSL_STATE_DIR . "/{$key}"; if (is_readable($file)) { $this->sslProgress[$key] = trim((string) @file_get_contents($file)); } } } private function loadCertificates(): array { $out = (string) @shell_exec('sudo -n /usr/bin/certbot certificates 2>&1'); if (empty(trim($out))) return [['_error' => 'unavailable']]; if (str_contains($out, 'No certificates found')) return []; $certs = []; $blocks = preg_split('/\n(?=\s*Certificate Name:)/m', $out); foreach ($blocks as $block) { if (!preg_match('/Certificate Name:\s*(.+)/i', $block, $nameM)) continue; preg_match('/Domains:\s*(.+)/i', $block, $domainsM); preg_match('/Expiry Date:\s*(.+)/i', $block, $expiryM); preg_match('/Certificate Path:\s*(.+)/i', $block, $certM); $expiryRaw = trim($expiryM[1] ?? ''); $daysLeft = null; $expired = false; $expiryDate = null; // Datum extrahieren (Format: "2026-07-24 10:30:00+00:00 (VALID: 88 days)") if (preg_match('/(\d{4}-\d{2}-\d{2})/', $expiryRaw, $dateM)) { $ts = strtotime($dateM[1]); $expiryDate = $ts ? date('d.m.Y', $ts) : null; } if (preg_match('/VALID: (\d+) days/i', $expiryRaw, $dM)) { $daysLeft = (int) $dM[1]; } elseif (preg_match('/INVALID/i', $expiryRaw)) { $expired = true; $daysLeft = 0; } $domainsRaw = trim($domainsM[1] ?? ''); $domains = $domainsRaw !== '' ? array_values(array_filter(explode(' ', $domainsRaw))) : []; $certs[] = [ 'name' => trim($nameM[1]), 'domains' => $domains, 'expiry_date' => $expiryDate, 'days_left' => $daysLeft, 'expired' => $expired, 'cert_path' => trim($certM[1] ?? ''), ]; } usort($certs, fn($a, $b) => ($a['days_left'] ?? 999) <=> ($b['days_left'] ?? 999)); return $certs; } public function render() { return view('livewire.ui.security.ssl-certificates-table'); } }