Compare commits

...

7 Commits

Author SHA1 Message Date
boban d50aedeafb Fix: nginx http2 Syntax für nginx 1.25+ (listen 443 ssl + http2 on)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 17:16:20 +02:00
boban bc2810eb8a Fix: certbot in sudoers + SSL-Seite zeigt Zertifikate
www-data braucht sudo-Recht auf certbot für SSL-Seite (certificates/renew)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:29:57 +02:00
boban 894f753b81 Fix: Wizard SSL-Flow end-to-end sauber gelöst
- 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>
2026-04-24 16:11:50 +02:00
boban 9d3cbd88b6 Fix: Race Condition SSL-Wizard + fastcgi_param HTTPS on
- 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>
2026-04-24 16:06:25 +02:00
boban 1547302297 Fix: Wizard leitet nach SSL-Setup automatisch auf HTTPS weiter
- 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>
2026-04-24 15:54:08 +02:00
boban 68a31e894d Fix: SESSION_DOMAIN=null entfernt aus .env.example
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>
2026-04-24 15:25:38 +02:00
boban 7833257126 Fix: footer_ok + Monit Nginx-Check bereinigt
- 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>
2026-04-24 15:15:34 +02:00
4 changed files with 45 additions and 17 deletions

View File

@ -33,7 +33,7 @@ SESSION_ENCRYPT=false
SESSION_PATH=/
# For cross-subdomain session sharing (e.g. webmail on mail.example.com):
# SESSION_DOMAIN=.example.com
SESSION_DOMAIN=null
SESSION_DOMAIN=
#BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local

View File

@ -67,8 +67,12 @@ class WizardDomains extends Command
$ssl ? 1 : 0,
));
$helperOk = $out !== null && !str_contains((string) $out, '[x]');
$outStr = (string) $out;
$outStr = (string) $out;
$helperOk = $out !== null
&& !str_contains($outStr, '[x]')
&& !str_contains($outStr, 'command not found')
&& !str_contains($outStr, 'No such file')
&& trim($outStr) !== '';
foreach (['ui', 'mail', 'webmail'] as $key) {
$status = file_get_contents(self::STATE_DIR . "/{$key}");
@ -82,12 +86,15 @@ class WizardDomains extends Command
}
}
file_put_contents(self::STATE_DIR . '/done', $helperOk ? '1' : '0');
Setting::set('ssl_configured', $helperOk ? '1' : '0');
if ($helperOk && $ssl) {
$this->updateEnv(base_path('.env'), 'SESSION_SECURE_COOKIE', 'true');
// Shell-Script schreibt done bereits vor dem nginx-Switch — nicht überschreiben
$alreadyDone = trim((string) @file_get_contents(self::STATE_DIR . '/done')) === '1';
if (!$alreadyDone) {
file_put_contents(self::STATE_DIR . '/done', $helperOk ? '1' : '0');
}
Setting::set('ssl_configured', ($helperOk || $alreadyDone) ? '1' : '0');
// SESSION_SECURE_COOKIE wird nicht automatisch gesetzt —
// nginx leitet HTTP→HTTPS weiter, Secure-Flag wird im Admin gesetzt
return self::SUCCESS;
}

View File

@ -76,10 +76,9 @@ footer_ok() {
echo -e ""
echo -e " ${CYAN}➜ Setup-Wizard jetzt öffnen:${NC}"
echo -e " ${CYAN}http://${ip}/setup${NC}"
echo -e " ${GREY}https://${ip}/setup${NC} (self-signed Zertifikat)"
echo -e ""
echo -e " Laravel Root: ${GREY}${app_dir}${NC}"
echo -e " Self-signed Cert: ${GREY}${cert_dir}/{cert.pem,key.pem}${NC}"
echo -e " Mail-TLS Cert: ${GREY}${cert_dir}/{cert.pem,key.pem}${NC} (Postfix/Dovecot)"
echo -e " Postfix/Dovecot: ${GREY}25, 465, 587, 110, 995, 143, 993${NC}"
echo -e "${GREEN}${BAR}${NC}"
echo
@ -726,8 +725,9 @@ if [ -n "${UI_HOST}" ] && [ "${UI_HAS_CERT}" = "1" ]; then
cat <<CONF
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name ${UI_HOST};
ssl_certificate /etc/letsencrypt/live/${UI_HOST}/fullchain.pem;
@ -740,6 +740,7 @@ server {
location / { try_files \$uri \$uri/ /index.php?\$query_string; }
location ~ \.php\$ {
include snippets/fastcgi-php.conf;
fastcgi_param HTTPS on;
fastcgi_pass unix:${PHP_FPM_SOCK};
}
location ^~ /livewire/ { try_files \$uri /index.php?\$query_string; }
@ -752,8 +753,9 @@ if [ -n "${WEBMAIL_HOST}" ] && [ "${WM_HAS_CERT}" = "1" ]; then
cat <<CONF
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name ${WEBMAIL_HOST};
ssl_certificate /etc/letsencrypt/live/${WEBMAIL_HOST}/fullchain.pem;
@ -766,6 +768,7 @@ server {
location / { try_files \$uri \$uri/ /index.php?\$query_string; }
location ~ \.php\$ {
include snippets/fastcgi-php.conf;
fastcgi_param HTTPS on;
fastcgi_pass unix:${PHP_FPM_SOCK};
}
location ^~ /livewire/ { try_files \$uri /index.php?\$query_string; }
@ -775,6 +778,22 @@ CONF
fi
) > "${NGINX_SITE}"
# State-Dateien VOR dem nginx-Switch schreiben:
# Browser-Poll (alle 2s) liest done=1 → Polling stoppt → "Zum Login" erscheint.
# Danach 6s sleep → nginx switchet auf HTTPS → User klickt Link → funktioniert.
STATE_DIR="/var/lib/mailwolt/wizard"
if [ -d "${STATE_DIR}" ]; then
for k in ui mail webmail; do
[ -f "${STATE_DIR}/${k}" ] && printf "done" > "${STATE_DIR}/${k}"
done
if [ "${UI_HAS_CERT}" = "1" ] || [ "${WM_HAS_CERT}" = "1" ]; then
printf "1" > "${STATE_DIR}/done"
else
printf "0" > "${STATE_DIR}/done"
fi
sleep 6
fi
nginx -t && systemctl reload nginx
HELPER
chmod 755 /usr/local/sbin/mailwolt-apply-domains
@ -786,6 +805,7 @@ install -m 755 "${APP_DIR}/update.sh" /usr/local/sbin/mailwolt-update
cat > /etc/sudoers.d/mailwolt-certbot <<'SUDOERS'
www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-apply-domains
www-data ALL=(root) NOPASSWD: /usr/local/sbin/mailwolt-update
www-data ALL=(root) NOPASSWD: /usr/bin/certbot
SUDOERS
chmod 440 /etc/sudoers.d/mailwolt-certbot
@ -895,7 +915,6 @@ check process nginx with pidfile /run/nginx.pid
start program = "/bin/systemctl start nginx"
stop program = "/bin/systemctl stop nginx"
if failed host 127.0.0.1 port 80 type tcp for 2 cycles then restart
if failed host 127.0.0.1 port 443 type tcpssl for 2 cycles then restart
if 5 restarts within 10 cycles then alert
check process fail2ban with pidfile /run/fail2ban/fail2ban.pid

View File

@ -292,10 +292,12 @@
</div>
@elseif($step === 5 && $setupDone)
<div style="display:flex;justify-content:flex-end;margin-top:20px">
<button wire:click="goToLogin" class="mbx-btn-primary" style="font-size:12.5px;width:fit-content">
{{-- Kein wire:click plain Link damit kein Livewire-POST nötig ist.
nginx leitet /login nach SSL-Switch automatisch auf HTTPS weiter. --}}
<a href="/login" class="mbx-btn-primary" style="font-size:12.5px;width:fit-content;text-decoration:none;display:inline-flex;align-items:center;gap:6px">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 6.5l2.5 2.5 5.5-5.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
{{ collect($domainStatus)->contains(fn($s) => in_array($s, ['error','nodns','noipv6'])) ? 'Trotzdem zum Login' : 'Zum Login' }}
</button>
</a>
</div>
@endif