- mailwolt-apply-domains: MAIL_HOST wird in ACME-Challenge-Block aufgenommen,
certbot wird auch für die Mail-Domain ausgeführt, Postfix + Dovecot erhalten
danach automatisch das neue Zertifikat
- SslCertificatesTable: certbot-Ausgabe korrekt geparst (Einrückung mit Leerzeichen)
- settings-form: "kein Zertifikat nötig" entfernt (Mail-Domain braucht Zertifikat)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
exec > >(tee -a LOG_FILE) leitet stdout an tee weiter:
- CLI: Ausgabe weiterhin im Terminal + in Log-Datei
- UI (nohup >/dev/null): stdout geht nach /dev/null aber tee
schreibt trotzdem in die Log-Datei
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem: state-Datei vom letzten Update zeigte noch 'done'. pollUpdate()
las das direkt nach runUpdate() als "Update fertig" — bevor der neue
Prozess überhaupt startete. displayCurrent war dann noch die alte Version.
Fix:
- runUpdate() setzt state='starting' und löscht rc VOR dem Shell-Aufruf
- runUpdate() setzt rc=null, lowState='starting', postActionsDone=false zurück
- refreshLowLevelState(): Kommentar klärt dass 'starting' als running gilt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- loadSslStatus(): clearstatcache() + renewal-Conf als primärer Existenzcheck
(renewal/ ist immer 755, zuverlässiger als is_dir auf live/ das 750 sein kann)
+ /usr/bin/openssl (voller Pfad für sudoers-Matching)
- restoreSslProvisioningState(): stellt letzten Provisioning-Zustand aus
State-Dateien wieder her, so dass Status nach Page-Reload sichtbar bleibt
- Button zeigt nach Abschluss "Erneut einrichten" statt disabled zu bleiben
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scripts, Sudoers-Regeln und sbin-Binaries werden jetzt immer
aktualisiert — auch wenn kein Update verfügbar ist. Damit entfällt
das manuelle Nachtragen von Sudoers-Regeln nach neuen Releases.
ensure_system() wird aufgerufen:
- Vor jedem "bereits aktuell"-Exit (tags + branch mode)
- Am Ende jedes erfolgreichen Updates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem: /etc/letsencrypt/archive/ ist chmod 700 (root only).
file_exists() auf Symlinks in live/ schlägt fehl weil Symlink-Ziel
in archive/ nicht lesbar ist. is_dir() auf live/domain/ funktioniert
da das Verzeichnis selbst 755 ist.
- SettingsForm: file_exists() → is_dir() für Existenzcheck
- SettingsForm: openssl-Aufruf via sudo -n (archive/ ist root-only)
- installer.sh + update.sh: sudoers-Regel für openssl auf LE-Cert-Pfade
(www-data darf nur exakt diesen openssl-Aufruf, keine anderen Pfade)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Auf Produktivservern gibt es fast immer kleine Änderungen (App-generierte
Dateien, Setup-Änderungen). Statt Abbruch: tracked Dateien via git reset
--hard zurücksetzen, untracked Nicht-Systemdateien via git clean entfernen.
.env, storage/ und node_modules/ werden dabei bewusst ausgespart.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Problem: Henne-Ei-Situation — alte Server haben alte CheckUpdates.php die
git fetch als www-data aufruft (schlägt wegen fehlender Credentials fehl).
Neue mailwolt-fetch-tags Helper sind noch nicht deployed.
Lösung:
- scripts/update.sh: --check-only Flag — ruft nur git ls-remote als App-User
auf, schreibt version_remote, beendet sich sofort (kein Update)
- update.sh: schreibt version_remote auch nach normalem Tag-Fetch (damit es
nach manuellen Updates aktuell bleibt)
- CheckUpdates.php: sichere Fallback-Kette:
1. mailwolt-update --check-only (nur wenn Skript das Flag kennt — kein
versehentliches Triggern auf alten Servern)
2. mailwolt-fetch-tags (ab v1.1.268)
3. lokale git tags (letzter Fallback)
+ version_remote wird nur verwendet wenn < 2h alt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Neuer Abschnitt "SSL-Zertifikate" in den Einstellungen:
· Zeigt pro Domain (UI, Webmail, Mailserver) ob LE-Cert vorhanden ist,
Ablaufdatum und Status (OK / fehlt / läuft ab / abgelaufen)
· Button "Let's Encrypt Zertifikate einrichten" startet Provisioning
im Hintergrund (nohup, non-blocking)
· Live-Fortschrittsanzeige per wire:poll.2s mit Status-Icons pro Domain
(pending → running → done/error/skip)
- saveDomains() ruft apply-domains jetzt ohne certbot auf (--ssl-auto 0) —
Domains speichern und SSL einrichten sind damit getrennte Aktionen
- loadSslStatus() liest Certbot-Zertifikat-Info direkt aus openssl
- spin-Keyframe für Spinner-Animation in app.css
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Neu: scripts/mailwolt-fetch-tags ruft git ls-remote als App-User auf (hat Credentials),
schreibt neuesten Tag nach /var/lib/mailwolt/version_remote
- CheckUpdates.php nutzt jetzt sudo mailwolt-fetch-tags statt direktem git fetch als www-data
(www-data hat keine Git-Credentials für private Repos)
- SettingsForm.php zeigt tatsächliche Fehlerzeilen ([!], error, failed) im Toast statt
generischer "fehlgeschlagen"-Meldung
- installer.sh + update.sh installieren mailwolt-fetch-tags nach /usr/local/sbin/
- update.sh trägt mailwolt-fetch-tags automatisch in sudoers nach (Upgrade-Pfad)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- certbot_safe(): dig ist auf Ubuntu nicht immer installiert (kein dnsutils).
Mit set -euo pipefail crashte die Funktion lautlos → certbot nie ausgeführt.
Fallback auf getent ahostsv6 wenn dig fehlt; Zuweisung mit || has_aaaa=""
damit set -e nicht greift falls beide Befehle scheitern.
- echo "mailwolt-apply-domains fertig" am Ende: SettingsForm.php prüft ob
"fertig" im Output steht um ssl_configured=1 zu setzen — ohne diesen Echo
wurde SSL nie als konfiguriert markiert, selbst wenn Zertifikate vorhanden.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Alle Scripts zentral unter scripts/:
- scripts/update.sh (war update.sh)
- scripts/mailwolt-apply-domains
installer.sh und update.sh selbst auf neuen Pfad angepasst.
Update laufender Server (einmalig):
sudo install -m 755 /var/www/mailwolt/scripts/update.sh /usr/local/sbin/mailwolt-update
sudo install -m 755 /var/www/mailwolt/scripts/mailwolt-apply-domains /usr/local/sbin/mailwolt-apply-domains
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Beide Scripts werden jetzt bei jedem erfolgreichen Update eingespielt,
unabhängig von CHANGED_FILES — löst den Bootstrap-Problem wo die alte
mailwolt-update Version die neue Logik nicht kannte.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bei Änderungen unter scripts/ wird mailwolt-apply-domains automatisch
nach /usr/local/sbin/ kopiert — kein manueller Eingriff mehr nötig.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Script aus installer.sh-Heredoc in scripts/mailwolt-apply-domains ausgelagert.
installer.sh kopiert es jetzt via install -m 755 statt Heredoc.
Vorteile:
- git pull + sudo cp reicht um das Script auf laufenden Servern zu aktualisieren
- Keine doppelte Pflege mehr (Heredoc vs. Datei)
- Änderungen direkt im Script-File sichtbar (git diff)
Update laufender Server:
sudo install -m 755 /var/www/mailwolt/scripts/mailwolt-apply-domains \
/usr/local/sbin/mailwolt-apply-domains
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
installer.sh / mailwolt-apply-domains:
- State-Dateien jetzt korrekt pro Domain: "done" nur wenn LE-Cert existiert,
sonst "error" — verhindert grüne Checkmarks bei fehlgeschlagenem certbot
- mail-Domain: "skip" statt fälschlich "done" (certbot läuft nicht für MX im Wizard)
- sleep 6 nur wenn Cert ausgestellt wurde (nginx-HTTPS-Switch nötig)
WizardDomains.php:
- Bei frühem DNS-Abbruch: verbleibende "running"-Domains auf "error" setzen
statt ewig als "Wird registriert…" hängen zu bleiben
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
UI_HAS_CERT/WM_HAS_CERT wurden im Subshell ( ... ) > NGINX_SITE definiert.
Nach dem Subshell waren sie im Outer Scope ungebunden — mit set -euo pipefail
führte das zu "unbound variable" Crash bei der State-Datei-Zuweisung.
Variablen vor den Subshell in den Outer Scope verschoben.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- mailwolt-apply-domains schreibt jetzt pro Domain running/done/error/nodns
in die State-Dateien während certbot läuft (statt alles auf einmal am Ende)
- get_cert_dir() erstellt fullchain.pem/privkey.pem Symlinks auf cert.pem/key.pem
wenn kein LE-Zertifikat vorhanden — verhindert nginx-t-Fehler und kaputten Redirect
- WizardDomains.php: ssl_configured wird jetzt anhand /etc/letsencrypt/live/ geprüft
statt per Shell-Output (der wegen exec>>LOG immer leer war)
- Shell-Script schreibt done-Datei selbst; PHP nur noch als Absturz-Fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pollSetup() macht keinen auto-redirect mehr (port 443 wäre noch nicht offen)
- "Zum Login" ist jetzt ein plain <a href="/login"> ohne Livewire-POST
→ nginx leitet /login nach SSL-Switch automatisch auf HTTPS weiter
- mailwolt-apply-domains schreibt done=1/0 (je nach Cert-Status) VOR nginx-Switch
+ sleep 6s damit Polling noch 3x done lesen kann bevor port 443 öffnet
- done=1 nur wenn mindestens ein LE-Cert erfolgreich ausgestellt wurde
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- mailwolt-apply-domains schreibt State-Dateien (done=1) BEVOR nginx auf HTTPS
switcht, dann sleep 6s → Browser kann noch über HTTP redirecten
- WizardDomains.php überschreibt done nicht wenn Shell-Script es bereits gesetzt hat
- fastcgi_param HTTPS on in HTTPS-Blocks ergänzt (ohne dies liefert Laravel 404
weil Request-Schema falsch erkannt wird)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SESSION_SECURE_COOKIE wird nicht mehr automatisch gesetzt (verursachte 419 während HTTP-Poll)
- pollSetup() leitet Browser sofort auf https://domain/setup weiter sobald SSL fertig
- verhindert dass Livewire-Polling über HTTP läuft während nginx schon auf HTTPS umgestellt hat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
String "null" wird von Laravel nicht als PHP null interpretiert —
Cookie bekommt Domain=null, Browser lehnt ihn ab → 419 auf allen Livewire-Requests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- footer_ok: HTTPS/self-signed Zeile entfernt (nginx hat anfangs kein HTTPS mehr)
- Mail-TLS Cert Label ergänzt damit klar ist wofür das Zertifikat ist
- Monit: Port-443-Check für nginx entfernt (kein HTTPS initial → Monit würde nginx in Loop neustarten)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Nginx initial: nur HTTP-Block (kein 443/self-signed)
- mailwolt-apply-domains: kein self-signed Fallback; ohne LE-Cert
bleibt nginx HTTP-only, mit LE-Cert wird auf HTTPS umgestellt
- Monit: MariaDB per matching statt pidfile (mysqld.pid oft nicht da)
- ACME-Challenge Location bereits im initialen HTTP-Block vorhanden
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
wire:poll Div war bedingt gerendert – beim Entfernen aus dem DOM
versuchte Livewire noch es zu patchen → outerHTML Fehler.
Div bleibt jetzt immer im DOM, pollSetup() kehrt früh zurück wenn fertig.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Verhindert Abbruch des Update-Scripts durch Dirty-Check wenn
Backups im storage-Verzeichnis vorhanden sind.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
HTTPS→HTTP Redirect war ein Workaround für SESSION_SECURE_COOKIE,
der jetzt durch den Installer-Fix (false initial) nicht mehr nötig ist.
fastcgi_param HTTPS on gehört nur in den 443-Block, nicht in den 80-Block.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Installer setzt SESSION_SECURE_COOKIE=false initial – damit Setup-Wizard
über http://ip erreichbar ist. WizardDomains setzt es auf true nach
erfolgreichem SSL. mount()-Redirect ohne exit (sauberer Return).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Livewire setzt beim Einblenden display:inline statt inline-flex.
Innerer Wrapper erzwingt inline-flex damit SVG und Text nie stacken.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
wire:loading-Span war vor Livewire-Init sichtbar, wodurch Anmelden
und Spinner gleichzeitig angezeigt wurden.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eigenes HTML-Layout ohne Sidebar, mw-* CSS-Klassen, gleiches Logo
und Karten-Design wie der Setup-Wizard. Icons als Inline-SVG da
app.js (Phosphor) auf der Login-Seite nicht geladen wird.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
route('login') nutzt die gecachte Config (alte IP/URL). Stattdessen
direkt auf https://{ui_domain}/login umleiten, da APP_URL erst nach
dem Prozess-Neustart greift.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Vor SSL-Zertifikaten schlägt Livewires AJAX über HTTPS fehl.
mount() leitet automatisch auf http:// um damit der Wizard funktioniert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ls-remote fragt den Remote direkt — funktioniert mit shallow clone ohne
History-Download. Kein --unshallow, kein Fehler mehr.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shallow clones (--depth=1) kennen keine Tag-Historie. --unshallow
konvertiert zuerst zum vollständigen Clone, danach sind alle Tags erreichbar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Spinner (⠋⠙⠹…) läuft während quietly() auf Abschluss wartet
- stop_spin() in ok/warn/err integriert, EXIT-Trap sichert Cleanup
- Smoke-Test: kein eval, separate Funktionen pro Protokoll (smtp/tls/imap/pop3)
- Service-Namen neben Port, Zusammenfassung X/7 Dienste erreichbar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Nur Hauptschritte mit Zeitschätzung sichtbar (~Pakete 2-5 Min, etc.)
- Alle verbose Ausgaben (apt/composer/npm/git) gehen in /var/log/mailwolt-install.log
- Bei Fehler: letzte 20 Log-Zeilen werden angezeigt + Log-Pfad
- quietly()/try_quiet() Helper für stille Ausführung
- Smoke-Test zeigt nur OK/⚠ pro Port ohne verbose openssl-Output
- Node/npm wird nach Git-Clone installiert (package.json bereits vorhanden)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- installer.sh: mailwolt-apply-domains mit 3-Phasen certbot (HTTP → LE → SSL),
IPv6-Check vor certbot, Zertifikat-Ablauf-Check (10 Tage), Version-Datei schreiben
- WizardDomains: noipv6-Status aus Helper-Output erkennen
- Wizard: retryDomains()-Methode für Wiederholung ohne neuen Wizard-Durchlauf
- Step 5 Blade: Hints pro Fehlerstatus, Retry-Button, "Trotzdem zum Login"
- UpdatePage: Version aus Datei, Fallback auf git describe (kein "dev" mehr)
- UpdatePage: refreshLowLevelState behandelt fehlende State-Datei als idle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>