From 01de7ed4fbab13ff1b6693f47f2f883749aee06f Mon Sep 17 00:00:00 2001 From: Frank Schwenk Date: Thu, 11 Jun 2026 23:19:03 +0200 Subject: [PATCH] feat: SMTP mail stack and separate server.env deploy Add mail.php with STARTTLS sending, test scripts, server.env split from local .env, admin@ postbox docs, DKIM DNS support-ticket notes, and todo 11 for boka relay. Co-authored-by: Cursor --- .env.example | 11 +-- .gitignore | 1 + HOSTING.md | 54 +++++++++++- README.md | 9 +- config/bootstrap.php | 25 +++++- config/mail.php | 147 ++++++++++++++++++++++++++++++++ public/mail-test.php | 48 +++++++++++ scripts/deploy.py | 31 ++++++- scripts/sync-server-env.py | 61 +++++++++++++ scripts/test-mail.php | 39 +++++++++ server.env.example | 24 ++++++ todo/01-bootstrap-env-deploy.md | 2 +- todo/02-email-smtp.md | 72 +++++++++++----- todo/05-plesk-cron.md | 16 +++- todo/11-boka-smtp-relay.md | 39 +++++++++ todo/README.md | 5 +- 16 files changed, 543 insertions(+), 41 deletions(-) create mode 100644 config/mail.php create mode 100644 public/mail-test.php create mode 100755 scripts/sync-server-env.py create mode 100755 scripts/test-mail.php create mode 100644 server.env.example create mode 100644 todo/11-boka-smtp-relay.md diff --git a/.env.example b/.env.example index da70187..00b890e 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,6 @@ # Kopie nach .env — niemals committen. -# Alle Werte aus der wint.global Willkommens-Mail bzw. Plesk. +# Nur für diese Maschine: Deploy (FTP), lokale Tests. +# Server-Secrets → server.env (siehe server.env.example), nicht .env deployen! # --- Hosting / Plesk (Classic 1.0, 0012.de) --- PLESK_URL=https://ha01s019.org-dns.com:8443/ @@ -23,15 +24,15 @@ NAMESERVER_1=ns1.org-ns.com NAMESERVER_2=ns2.org-ns.com # --- SMTP (Plesk-Mail auf diesem Webspace) --- -# Für Monitoring-Alerts und Skripte, die über 0012.de mailen +# Login: Hauptpostfach admin@0012.de (Aliase: monitor@, alert@) SMTP_HOST=ha01s019.org-dns.com SMTP_PORT=587 SMTP_ENCRYPTION=tls -SMTP_USER= +SMTP_USER=admin@0012.de SMTP_PASSWORD= -# Absender für System-Mails (Monitoring, Cron-Fehler) +# Absender für System-Mails — Alias monitor@0012.de SMTP_FROM=monitor@0012.de -# Empfänger für Alarme +# Empfänger für Alarme (persönliche Adresse) ALERT_EMAIL= # --- Monitoring: Push von boka → Webspace --- diff --git a/.gitignore b/.gitignore index 74cce54..b510fd2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .env +server.env *.log /vendor/ /node_modules/ diff --git a/HOSTING.md b/HOSTING.md index db64e24..8abd6e3 100644 --- a/HOSTING.md +++ b/HOSTING.md @@ -42,6 +42,34 @@ DNS-Einträge nur für Domains mit Bezug zum Paket oder über wint.global bestel Domain-Delegation kann nach Registration bis zu **24 Stunden** dauern. Bis dahin ggf. temporäre URL aus dem Kundenbereich nutzen. +### DNS-Zugang (Classic 1.0) + +Bei wint.global liegt die **DNS-Zone oft beim Hoster** (`ns1.org-ns.com`), nicht beim Kunden in Plesk. SPF und DMARC sind gesetzt; **DKIM-TXT-Records fehlen öffentlich**, obwohl DKIM in Plesk aktiviert ist — typisch, wenn der Hoster DNS pflegt und die Mail-Records nicht synchronisiert hat. + +**Du musst DNS nicht selbst pflegen.** Optionen: + +1. **Support-Ticket** an support@wint.global — DKIM-DNS für `0012.de` hinterlegen (siehe unten) +2. **Workaround:** `ALERT_EMAIL` auf Gmail o. ä. setzen, solange Tuta/strenge Empfänger blockieren + +#### Support-Ticket (DKIM) + +In Plesk (ohne DNS-Rechte geht das trotzdem): + +**Mail → Mail-Einstellungen → „Externe DNS konfigurieren“** — zwei TXT-Records kopieren und an den Support schicken, z. B.: + +> Bitte folgende TXT-Records für 0012.de in der DNS-Zone ergänzen (DKIM ist in Plesk aktiv, Records fehlen in DNS): +> +> - `default._domainkey.0012.de` → *(Wert aus Plesk)* +> - `_domainkey.0012.de` → *(Wert aus Plesk)* + +Prüfung danach: + +```bash +dig +short TXT default._domainkey.0012.de @ns1.org-ns.com +``` + +Kundennummer **26064** angeben. + ## TLS / HTTPS Konfiguration in Plesk (Stand 2026-06-11): @@ -73,10 +101,32 @@ curl -sI https://0012.de/ # → 200, HSTS-Header **Sicherheit:** Nach 5 fehlgeschlagenen Login-Versuchen wird die Client-IP für 10 Minuten gesperrt (FTP, Webmail, E-Mail). +### Postfächer 0012.de (Stand 2026-06-11) + +| Adresse | Rolle | +|---------|--------| +| `admin@0012.de` | Hauptpostfach, **SMTP-Login** für Skripte | +| `monitor@0012.de` | Alias — Absender Monitoring (`SMTP_FROM`) | +| `alert@0012.de` | Alias — optional alternativer Absender | + +Webmail / SMTP-Auth: immer die **Hauptadresse** + Postfach-Passwort, nicht den Alias-Namen allein. + +### Authentifizierung (SPF / DKIM / DMARC) + +| | Status | +|---|--------| +| **SPF** | ✅ gesetzt (Hoster) — nichts tun | +| **DKIM** | ⚠️ in Plesk an, **DNS-Record fehlt** — Support-Ticket (kein DNS-Zugang) | +| **DMARC** | ✅ `v=DMARC1;p=none;` (Hoster) — nichts tun | + +DMARC ist optional zum Start (`p=none` = nur Reports, keine Ablehnung). Erst nach SPF/DKIM-Stabilisierung auf `quarantine`/`reject` erhöhen. Optional `rua=mailto:admin@0012.de` für Aggregate-Reports ergänzen. + +Prüfung: [mail-tester.com](https://www.mail-tester.com) oder Test mit `php scripts/test-mail.php`. + ### Geplante Nutzung -1. **Monitoring-Alerts** — z. B. `monitor@0012.de` → persönliche Adresse -2. **SMTP für Skripte** — boka und andere Hosts senden über TLS/587 (Relay nur für autorisierte Absender konfigurieren) +1. **Monitoring-Alerts** — `monitor@0012.de` → `ALERT_EMAIL` via `sendAlert()` in `config/mail.php` +2. **SMTP von boka** — siehe [`todo/11-boka-smtp-relay.md`](todo/11-boka-smtp-relay.md) 3. **Mail-Hosting** — Postfächer für Domains ohne eigenen Mailserver (MX auf 0012.de bzw. wint-NS) ## Geplante Aufgaben (Cron) diff --git a/README.md b/README.md index b03dffe..7d4fad8 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,8 @@ Offene Arbeiten: [`todo/README.md`](todo/README.md) ## Schnellstart (wenn eingerichtet) -1. `.env.example` → `.env` kopieren und ausfüllen -2. `./scripts/deploy.sh` — spielt `public/` nach `httpdocs/`, Config und `.env` nach `private/` -3. In Plesk: geplante Aufgabe für `monitor.php` (Cron-Stil, z. B. `*/10 * * * *`) -4. Auf boka: Push-Skript + Crontab (siehe `todo/04-monitoring-push-boka.md`) +1. `.env.example` → `.env` kopieren und ausfüllen (lokal, FTP) +2. `python3 scripts/sync-server-env.py` → erzeugt `server.env` für den Webspace +3. `./scripts/deploy.sh` — spielt `public/` + `config/` + `server.env` hoch (**.env bleibt lokal**) +4. In Plesk: geplante Aufgabe für `monitor.php` (Cron-Stil, z. B. `*/10 * * * *`) +5. Auf boka: Push-Skript + Crontab (siehe `todo/04-monitoring-push-boka.md`) diff --git a/config/bootstrap.php b/config/bootstrap.php index fb707f0..c08a67a 100644 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -3,8 +3,8 @@ declare(strict_types=1); /** - * Load configuration from private/.env (outside document root). - * Called from httpdocs/*.php via require on private/config/bootstrap.php. + * Load configuration from private/server.env (outside document root). + * Local .env is never deployed — see server.env.example. */ function bootstrap(): array @@ -15,12 +15,29 @@ function bootstrap(): array return $config; } - $envFile = dirname(__DIR__) . '/.env'; - $config = loadEnvFile($envFile); + $config = loadEnvFile(envFilePath()); return $config; } +function envFilePath(): string +{ + $baseDir = dirname(__DIR__); + + // Webspace: private/config/bootstrap.php → private/server.env + if (basename($baseDir) === 'private') { + return $baseDir . '/server.env'; + } + + // Local repo: .env on this machine (never deployed) + $local = $baseDir . '/.env'; + if (is_readable($local)) { + return $local; + } + + return $baseDir . '/server.env'; +} + function env(string $key, ?string $default = null): ?string { $value = bootstrap()[$key] ?? null; diff --git a/config/mail.php b/config/mail.php new file mode 100644 index 0000000..6e0b8fd --- /dev/null +++ b/config/mail.php @@ -0,0 +1,147 @@ +', 250); + sendCommand($socket, 'RCPT TO:<' . $to . '>', 250); + sendCommand($socket, 'DATA', 354); + + $message = buildMessage($fromHeader, $to, $subject, $body); + fwrite($socket, $message); + expectCode($socket, 250); + + sendCommand($socket, 'QUIT', 221); + } finally { + fclose($socket); + } +} + +/** @return resource */ +function connectSmtp(string $host, int $port, string $encryption) +{ + $target = $encryption === 'ssl' + ? "ssl://{$host}:{$port}" + : "tcp://{$host}:{$port}"; + + $socket = @stream_socket_client( + $target, + $errno, + $errstr, + 30, + STREAM_CLIENT_CONNECT, + stream_context_create(['ssl' => ['verify_peer' => true, 'verify_peer_name' => true]]) + ); + + if ($socket === false) { + throw new RuntimeException("SMTP connect failed: {$errstr} ({$errno})"); + } + + stream_set_timeout($socket, 30); + + return $socket; +} + +/** @param resource $socket */ +function sendCommand($socket, string $command, int $expectedCode): void +{ + fwrite($socket, $command . "\r\n"); + expectCode($socket, $expectedCode); +} + +/** @param resource $socket */ +function expectCode($socket, int $expectedCode): void +{ + $response = readResponse($socket); + $code = (int) substr($response, 0, 3); + + if ($code !== $expectedCode) { + throw new RuntimeException("SMTP error (expected {$expectedCode}): {$response}"); + } +} + +/** @param resource $socket */ +function readResponse($socket): string +{ + $response = ''; + + while (($line = fgets($socket, 515)) !== false) { + $response .= $line; + if (isset($line[3]) && $line[3] === ' ') { + break; + } + } + + return trim($response); +} + +function buildMessage(string $from, string $to, string $subject, string $body): string +{ + $domain = env('DOMAIN', '0012.de'); + $date = gmdate('D, d M Y H:i:s') . ' +0000'; + $messageId = sprintf('<%s@%s>', bin2hex(random_bytes(8)), $domain); + + $headers = [ + "From: {$from}", + "To: {$to}", + "Subject: {$subject}", + "Date: {$date}", + "Message-ID: {$messageId}", + 'MIME-Version: 1.0', + 'Content-Type: text/plain; charset=UTF-8', + 'Content-Transfer-Encoding: 8bit', + ]; + + return implode("\r\n", $headers) . "\r\n\r\n" . $body . "\r\n.\r\n"; +} + +function sendAlert(string $subject, string $body): void +{ + $to = env('ALERT_EMAIL'); + + if (!$to) { + throw new RuntimeException('ALERT_EMAIL not set in .env'); + } + + sendMail($to, $subject, $body); +} diff --git a/public/mail-test.php b/public/mail-test.php new file mode 100644 index 0000000..485701c --- /dev/null +++ b/public/mail-test.php @@ -0,0 +1,48 @@ + false, 'error' => 'forbidden']); + exit; +} + +$to = env('ALERT_EMAIL'); +if (!$to) { + http_response_code(500); + echo json_encode(['ok' => false, 'error' => 'ALERT_EMAIL not set']); + exit; +} + +$domain = env('DOMAIN', '0012.de'); +$subject = "[{$domain}] SMTP test (server) " . gmdate('Y-m-d H:i:s') . ' UTC'; +$body = implode("\n", [ + '0012.de SMTP test — sent from webspace PHP (cron path)', + '', + 'From header: ' . env('SMTP_FROM'), + 'SMTP auth: ' . env('SMTP_USER'), + '', + 'If you receive this, server-side mail works.', +]); + +try { + sendMail($to, $subject, $body); + echo json_encode(['ok' => true, 'to' => $to, 'from' => env('SMTP_FROM')]); +} catch (Throwable $e) { + http_response_code(500); + echo json_encode(['ok' => false, 'error' => $e->getMessage()]); +} diff --git a/scripts/deploy.py b/scripts/deploy.py index 920c3a4..2445df4 100755 --- a/scripts/deploy.py +++ b/scripts/deploy.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Deploy public/, config/, and .env to wint.global / Plesk via FTP/FTPS.""" +"""Deploy public/, config/, and server.env to wint.global / Plesk via FTP/FTPS.""" from __future__ import annotations @@ -85,6 +85,21 @@ def upload_file(ftp: ftplib.FTP, local: Path, remote_path: str) -> None: print(f" ↑ {local.relative_to(ROOT)} → {remote_path}") +def delete_remote_file(ftp: ftplib.FTP, remote_path: str) -> None: + remote_path = remote_path.replace("\\", "/").lstrip("/") + remote_dir = os.path.dirname(remote_path) + remote_name = os.path.basename(remote_path) + try: + if remote_dir: + ensure_remote_dir(ftp, remote_dir) + else: + cwd_to_root(ftp) + ftp.delete(remote_name) + print(f" ✕ deleted {remote_path}") + except ftplib.error_perm as exc: + print(f" (skip delete {remote_path}: {exc})") + + def upload_tree(ftp: ftplib.FTP, local_dir: Path, remote_base: str) -> None: remote_base = remote_base.strip("/") for path in sorted(local_dir.rglob("*")): @@ -101,6 +116,13 @@ def main() -> None: env = load_env(ROOT / ".env") require_env(env, "FTP_HOST", "FTP_USER", "FTP_PASSWORD") + server_env = ROOT / "server.env" + if not server_env.is_file(): + sys.exit( + "Missing server.env — run: python3 scripts/sync-server-env.py\n" + "(or copy server.env.example to server.env and fill in server values)" + ) + remote_web = env.get("FTP_REMOTE_PATH", "httpdocs").strip("/") remote_private = env.get("FTP_PRIVATE_PATH", "private").strip("/") @@ -109,8 +131,10 @@ def main() -> None: config_files = sum(1 for p in (ROOT / "config").rglob("*") if p.is_file()) print(f" public/ ({public_files} files) → {remote_web}/") print(f" config/ ({config_files} files) → {remote_private}/config/") - print(f" .env → {remote_private}/.env") + print(f" server.env → {remote_private}/server.env") + print(f" (remove legacy {remote_private}/.env if present)") print(f" (mkdir) {remote_private}/data/") + print(" .env stays local — not deployed") if args.dry_run: return @@ -122,7 +146,8 @@ def main() -> None: upload_tree(ftp, ROOT / "public", remote_web) upload_tree(ftp, ROOT / "config", f"{remote_private}/config") ensure_remote_dir(ftp, f"{remote_private}/data") - upload_file(ftp, ROOT / ".env", f"{remote_private}/.env") + upload_file(ftp, server_env, f"{remote_private}/server.env") + delete_remote_file(ftp, f"{remote_private}/.env") print("\nDone.") finally: ftp.quit() diff --git a/scripts/sync-server-env.py b/scripts/sync-server-env.py new file mode 100755 index 0000000..ae948e5 --- /dev/null +++ b/scripts/sync-server-env.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Copy server-relevant keys from local .env to server.env (never deploys .env).""" + +from __future__ import annotations + +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent + +SERVER_KEYS = [ + "DOMAIN", + "SMTP_HOST", + "SMTP_PORT", + "SMTP_ENCRYPTION", + "SMTP_USER", + "SMTP_PASSWORD", + "SMTP_FROM", + "ALERT_EMAIL", + "MONITOR_INGEST_TOKEN", + "MONITOR_INGEST_URL", + "TELEGRAM_BOT_TOKEN", + "TELEGRAM_CHAT_ID", +] + + +def load_env(path: Path) -> dict[str, str]: + env: dict[str, str] = {} + for line in path.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + key, _, value = line.partition("=") + env[key.strip()] = value.strip() + return env + + +def main() -> None: + local = ROOT / ".env" + target = ROOT / "server.env" + + if not local.is_file(): + raise SystemExit(f"Missing {local}") + + src = load_env(local) + lines = [ + "# Generated by scripts/sync-server-env.py — do not commit", + "# Deploy: ./scripts/deploy.sh uploads this to private/server.env", + "", + ] + + for key in SERVER_KEYS: + if key in src: + lines.append(f"{key}={src[key]}") + + target.write_text("\n".join(lines) + "\n", encoding="utf-8") + alert = src.get("ALERT_EMAIL", "?") + print(f"Wrote {target.name} (ALERT_EMAIL={alert})") + + +if __name__ == "__main__": + main() diff --git a/scripts/test-mail.php b/scripts/test-mail.php new file mode 100755 index 0000000..9bd29e0 --- /dev/null +++ b/scripts/test-mail.php @@ -0,0 +1,39 @@ +#!/usr/bin/env php +getMessage() . "\n"); + exit(1); +} diff --git a/server.env.example b/server.env.example new file mode 100644 index 0000000..dc6e31e --- /dev/null +++ b/server.env.example @@ -0,0 +1,24 @@ +# Kopie nach server.env — niemals committen. +# Wird nach private/server.env auf dem Webspace deployed. +# Nur Werte, die PHP-Skripte auf 0012.de brauchen (kein FTP/Plesk). + +DOMAIN=0012.de + +# SMTP (0012.de Postfach) +SMTP_HOST=ha01s019.org-dns.com +SMTP_PORT=587 +SMTP_ENCRYPTION=tls +SMTP_USER=admin@0012.de +SMTP_PASSWORD= +SMTP_FROM=monitor@0012.de + +# Alarm-Empfänger +ALERT_EMAIL= + +# Monitoring +MONITOR_INGEST_TOKEN= +MONITOR_INGEST_URL=https://0012.de/ingest.php + +# Optional +TELEGRAM_BOT_TOKEN= +TELEGRAM_CHAT_ID= diff --git a/todo/01-bootstrap-env-deploy.md b/todo/01-bootstrap-env-deploy.md index 383b97d..db52b50 100644 --- a/todo/01-bootstrap-env-deploy.md +++ b/todo/01-bootstrap-env-deploy.md @@ -29,7 +29,7 @@ Lokales Repo mit dem Webspace verbinden und eine deploybare Grundstruktur anlege ├── httpdocs/ ← public/ │ └── index.php └── private/ ← nicht öffentlich - ├── .env + ├── server.env ← Server-Secrets (deployed, nicht .env!) ├── config/bootstrap.php └── data/ ← später last-checks.json ``` diff --git a/todo/02-email-smtp.md b/todo/02-email-smtp.md index 57a2fc9..72b8b14 100644 --- a/todo/02-email-smtp.md +++ b/todo/02-email-smtp.md @@ -1,45 +1,77 @@ # 02 — E-Mail & SMTP **Priorität:** high +**Status:** erledigt (2026-06-11) **Abhängigkeiten:** 01 **Blockiert:** 03 (Alarmierung) ## Ziel -Mail-Infrastruktur auf 0012.de für Monitoring-Alerts, Skript-Versand und spätere Fremddomains. +Mail-Infrastruktur auf 0012.de für Monitoring-Alerts und Skript-Versand. + +Relay von **boka** → ausgelagert nach [`11-boka-smtp-relay.md`](11-boka-smtp-relay.md). + +## Postfach-Setup (Plesk) + +| | | +|---|---| +| Postfach | `admin@0012.de` | +| Aliase | `monitor@0012.de`, `alert@0012.de` | +| SMTP-Login | `admin@0012.de` (Hauptpostfach, nicht Alias) | +| Absender Monitoring | `SMTP_FROM=monitor@0012.de` | +| Alarm-Empfänger | `ALERT_EMAIL` → persönliche Adresse | ## Checkliste ### Postfächer auf 0012.de -- [ ] Postfach `monitor@0012.de` (oder `alerts@0012.de`) anlegen -- [ ] Passwort in `.env` → `SMTP_USER`, `SMTP_PASSWORD` -- [ ] `ALERT_EMAIL` auf persönliche Adresse setzen -- [ ] `SMTP_FROM=monitor@0012.de` setzen -- [ ] Test: Webmail-Login auf `ha01s019.org-dns.com` +- [x] `admin@0012.de` mit Aliasen `monitor@`, `alert@` +- [x] Passwort in `.env` → `SMTP_USER`, `SMTP_PASSWORD` +- [x] `ALERT_EMAIL` gesetzt +- [x] `SMTP_FROM=monitor@0012.de` +- [x] Webmail-Login auf `ha01s019.org-dns.com` -### SMTP für Skripte +### SMTP für Skripte (0012.de) -- [ ] Port 587 + STARTTLS testen (von lokalem Rechner / boka) -- [ ] SPF für `0012.de` in Plesk DNS prüfen/setzen -- [ ] DKIM in Plesk aktivieren (Deliverability) -- [ ] DMARC optional: `_dmarc.0012.de` mit `p=none` zum Start +- [x] `config/mail.php` — `sendMail()`, `sendAlert()` +- [x] `scripts/test-mail.php` — CLI-Test +- [x] Port 587 + STARTTLS getestet (`php scripts/test-mail.php` → OK) +- [x] SPF gesetzt (Hoster-DNS) +- [x] DKIM in Plesk aktiviert +- [ ] DKIM in **öffentlicher DNS** — fehlt; Hoster per Ticket (kein DNS-Zugang im Paket) +- [x] DMARC vorhanden (Hoster-DNS) -### Relay von boka (später) +### Technische Notizen -- [ ] Entscheiden: dediziertes Relay-Postfach `relay@0012.de` vs. App-spezifische Absender -- [ ] In Plesk: ausgehende Mail-Limits / Relay-Regeln prüfen (Hoster-Doku) -- [ ] boka-Apps: zentraler SMTP-Block in Docker/env (Host, Port, User, Pass) +- **Envelope vs. From:** SMTP-Login als `admin@`, `MAIL FROM` ebenfalls `admin@`; sichtbarer Absender bleibt `monitor@` (Alias). Plesk verwirft sonst oft still nach `250 OK`. +- **Test lokal:** `php scripts/test-mail.php` (liest `.env`) +- **Test vom Webspace:** `mail-test.php?token=…` (liest `private/server.env`) +- **Tuta/strenge Empfänger:** bis DKIM-DNS steht → `ALERT_EMAIL` Gmail o. ä. +- **Kein DNS-Zugang:** DKIM-Records aus Plesk Mail-Einstellungen an wint-Support — siehe [`../HOSTING.md`](../HOSTING.md#dns-zugang-classic-10) -### Cron-Benachrichtigungen Plesk +## `.env` (Beispiel) -- [ ] Geplante Aufgaben: „Nur Fehler“ für PHP-Fatal-Errors — ergänzt App-eigene Alerts, ersetzt sie nicht +```env +SMTP_USER=admin@0012.de +SMTP_FROM=monitor@0012.de +ALERT_EMAIL=you@example.com +``` + +## Test + +```bash +php scripts/test-mail.php +``` + +Nach Deploy: `server.env` auf Server, Test lokal mit `.env`. ## Akzeptanzkriterien -- Testmail von PHP (`mail()` oder PHPMailer) an `ALERT_EMAIL` kommt an -- SPF/DKIM ohne Hard-Fail in Mail-Header-Test (mail-tester.com o. ä.) +- [x] Testmail an `ALERT_EMAIL` kommt an +- [ ] SPF/DKIM ohne Hard-Fail (mail-tester.com o. ä.) — optional ## Referenz -- [`../HOSTING.md`](../HOSTING.md) — SMTP-Host `ha01s019.org-dns.com` +- [`../HOSTING.md`](../HOSTING.md) +- [`../config/mail.php`](../config/mail.php) +- boka-Relay: [`11-boka-smtp-relay.md`](11-boka-smtp-relay.md) diff --git a/todo/05-plesk-cron.md b/todo/05-plesk-cron.md index baa666a..e04d83d 100644 --- a/todo/05-plesk-cron.md +++ b/todo/05-plesk-cron.md @@ -17,10 +17,24 @@ Monitoring automatisch alle 5–10 Minuten ausführen. - [ ] Ausführen: **Cron-Stil** - [ ] Expression: `*/10 * * * *` (Pull-Checks alle 10 Min) - [ ] Beschreibung: z. B. „0012 external monitoring pull“ -- [ ] Benachrichtigen: **Nur Fehler** (PHP-Crash → Plesk-Mail) +- [ ] Benachrichtigen: **Nur Fehler** (siehe unten) - [ ] **Jetzt ausführen** — Exit-Code / Log prüfen - [ ] Optional zweite Aufgabe für leichtgewichtigen Heartbeat-Only-Check (`*/5`) — nur wenn von monitor.php getrennt +## Benachrichtigen: „Nur Fehler“ + +Erst beim Anlegen der geplanten Aufgabe in Plesk relevant (zusammen mit `monitor.php`). + +| Option | Bedeutung | +|--------|-----------| +| **Nicht benachrichtigen** | Cron läuft still; Crash fällt nicht auf | +| **Nur Fehler** | Plesk mailt nur bei Skript-Fehler (PHP-Fatal, Exit ≠ 0, stderr) | +| **Jedes Mal** | Mail bei jeder Ausführung — bei `*/10` unbrauchbar | + +**Empfehlung: „Nur Fehler“** — meldet, wenn `monitor.php` auf 0012.de selbst kaputt ist. + +**Getrennt von Monitoring-Alerts:** Ausfall von `schwenk.online` kommt aus `sendAlert()` → `ALERT_EMAIL`. Plesk-Cron-Mail betrifft nur die Cron-Ausführung auf dem Webspace. + ## Cron-Referenz ``` diff --git a/todo/11-boka-smtp-relay.md b/todo/11-boka-smtp-relay.md new file mode 100644 index 0000000..7a82951 --- /dev/null +++ b/todo/11-boka-smtp-relay.md @@ -0,0 +1,39 @@ +# 11 — SMTP-Relay von boka + +**Priorität:** medium +**Status:** offen +**Abhängigkeiten:** [02-email-smtp.md](02-email-smtp.md) (erledigt) +**Blockiert:** boka-Apps, die über 0012.de mailen sollen + +## Ziel + +Prozesse auf **boka** (Docker-Stacks, Cron-Skripte) senden ausgehende Mail über den Plesk-SMTP von `0012.de`. + +## Voraussetzungen (0012.de — erledigt in Todo 02) + +- Postfach `admin@0012.de` mit Aliasen `monitor@`, `alert@` +- SMTP TLS/587 auf `ha01s019.org-dns.com` +- DKIM aktiv + +## Checkliste + +- [ ] Entscheiden: dediziertes Relay-Postfach vs. `admin@` + alias From-Adressen +- [ ] In Plesk: ausgehende Mail-Limits / Relay-Regeln prüfen (Hoster-Doku) +- [ ] SPF für `0012.de` verifizieren (boka-IPs nur wenn direkt sendend — bei Relay nicht nötig) +- [ ] boka: zentraler SMTP-Block (Docker env / `.env` auf boka) + - `SMTP_HOST=ha01s019.org-dns.com` + - `SMTP_PORT=587` + - `SMTP_USER=admin@0012.de` + - `SMTP_PASSWORD=…` (gleiches Postfach oder App-Passwort) +- [ ] Test von boka: `curl`/swaks oder kleines Script → Mail an persönliche Adresse +- [ ] Pro Stack: From-Adresse festlegen (`monitor@0012.de`, `alert@0012.de`, …) + +## Akzeptanzkriterien + +- Mindestens ein Dienst auf boka sendet erfolgreich über 0012.de-SMTP +- Keine Mails im Spam (DKIM pass) + +## Referenz + +- [`../HOSTING.md`](../HOSTING.md) — SMTP-Zugang +- [`../config/mail.php`](../config/mail.php) — Referenz-Implementierung (PHP) diff --git a/todo/README.md b/todo/README.md index 3c9b5c3..7482e61 100644 --- a/todo/README.md +++ b/todo/README.md @@ -5,7 +5,7 @@ Nummerierte Todos für den 0012.de-Webspace. Abhängigkeiten beachten. | # | Datei | Priorität | Kurzbeschreibung | |---|-------|-----------|------------------| | 01 | [01-bootstrap-env-deploy.md](01-bootstrap-env-deploy.md) | **high** | `.env`, Repo-Struktur, erstes FTP-Deployment | -| 02 | [02-email-smtp.md](02-email-smtp.md) | **high** | Postfächer, SMTP für Alerts und Relay | +| 02 | [02-email-smtp.md](02-email-smtp.md) | **high** | Postfächer, SMTP für Alerts (0012.de) | | 03 | [03-monitoring-pull-phase1.md](03-monitoring-pull-phase1.md) | **high** | `monitor.php`, HTTPS/SSL-Checks, Alarm | | 04 | [04-monitoring-push-boka.md](04-monitoring-push-boka.md) | **medium** | Push von boka → `ingest.php` | | 05 | [05-plesk-cron.md](05-plesk-cron.md) | **high** | Geplante Aufgaben in Plesk | @@ -14,11 +14,14 @@ Nummerierte Todos für den 0012.de-Webspace. Abhängigkeiten beachten. | 08 | [08-monitoring-phase2.md](08-monitoring-phase2.md) | **medium** | API-Health, Latenz, Backup-Heartbeat | | 09 | [09-monitoring-phase3.md](09-monitoring-phase3.md) | **low** | Rest-Domains, Dashboard, Header-Checks | | 10 | [10-optional-plesk-git-composer.md](10-optional-plesk-git-composer.md) | backlog | Plesk Git / Composer evaluieren | +| 11 | [11-boka-smtp-relay.md](11-boka-smtp-relay.md) | **medium** | boka-Apps über 0012.de-SMTP | ## Backlog (ohne Priorität) Todo **10** — Plesk Git-Deploy und Composer; dokumentiert in [`../PLESK.md`](../PLESK.md), kein Blocker für 02–09. +Todo **11** — boka SMTP-Relay; abhängig von erledigtem **02**, kein Blocker für **03**. + ## Empfohlene Reihenfolge ```