From d88a8a6cba21f2d194510c71b338ef131ea22419 Mon Sep 17 00:00:00 2001 From: Mikhail Yenuchenko Date: Wed, 19 Aug 2026 14:57:03 +0300 Subject: [PATCH] docs: add preflight installation-check page plan and roadmap entry Captures the design for a /preflight page that verifies instance-level infrastructure (rDNS, TLS, ports, HELO banner, proxy headers, DKIM socket, test email). Status: candidate. Co-authored-by: Cursor --- CHANGELOG.md | 4 +++ docs/plans/preflight.md | 71 +++++++++++++++++++++++++++++++++++++++++ docs/roadmap.md | 25 +++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 docs/plans/preflight.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fa8e1d..78ffb87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ rate limit is correct for a non-default level-1 window. Schema migration ### Added +- Roadmap item and plan for `/preflight` installation check page + ([docs/plans/preflight.md](docs/plans/preflight.md)) — instance-level + infrastructure verification (rDNS, TLS, ports, HELO banner, proxy headers, + DKIM socket, test email). Status: candidate. - SQLite migration `0010_send_log_app_login_index.sql` — `idx_send_log_app_login_created_at`. The journal-milter counts an application's messages on every `MAIL FROM`, and the domain page computes diff --git a/docs/plans/preflight.md b/docs/plans/preflight.md new file mode 100644 index 0000000..26f9230 --- /dev/null +++ b/docs/plans/preflight.md @@ -0,0 +1,71 @@ +# Plan: preflight (installation check page) + +**Status:** candidate +**Date:** 2026-08-19 +**Version:** `1.x` MINOR; `candidate` until explicitly agreed. + +--- + +## Goal + +A dedicated `/preflight` page (global-admin only) that runs infrastructure-level +checks and helps the administrator confirm the instance is fully configured and +ready to deliver mail. Each failed check shows an actionable recommendation +(what to do or where to look). + +## Scope + +**In:** + +- rDNS / PTR verification (reuse `dnscheck.Checker.checkServer`) +- TLS certificate validity + hostname match (CN/SAN contains `SELFPOST_HOSTNAME`) +- Port connectivity: 25, 465, and 587 (if `SUBMISSION_ENABLE`) — dial own + public IP from inside the container, expect a 220 banner +- HELO/EHLO banner — connect to localhost:25, verify Postfix announces + `SELFPOST_HOSTNAME` (not `localhost` or a container ID) +- Trusted proxy headers: + - If `TRUSTED_PROXY_CIDR` set: verify peer IP is in range and + `X-Forwarded-For` is present + - If `PANEL_COOKIE_SECURE=true`: verify `X-Forwarded-Proto: https` + - If `TRUSTED_PROXY_CIDR` empty: informational warning (panel exposed + directly) +- DKIM signing — OpenDKIM milter socket reachable (reuse `health.CheckSocket`) +- Send test email — admin enters recipient address, system sends via + localhost:25, shows queued/error result + +**Out:** + +- Per-domain DNS checks (SPF, DKIM TXT, DMARC) — those live on domain detail + pages. +- Delivery tracking of test email (only confirms local MTA accepted). + +## Done when + +An administrator can open `/preflight`, see a traffic-light summary of all +infrastructure prerequisites, read specific fix instructions for any failures, +and send a test email to verify end-to-end flow. + +## Risks + +- Port-connectivity check from inside the container may not reflect external + reachability if NAT hairpin is unsupported (document limitation). +- Test email requires at least one configured domain (or falls back to + `postmaster@SELFPOST_HOSTNAME`). + +## Implementation checklist + +Target version cut: TBD (MINOR). One commit per step; code only after roadmap +status is **agreed**. See [development.md](../development.md) § Plan checklists. + +- [ ] Port-connectivity helper (`internal/health/port.go`) — dial own IP, read + SMTP banner — **Sonnet** +- [ ] HELO banner check (connect localhost:25, parse 220 line) — **Sonnet** +- [ ] Hostname-in-certificate check (extend or wrap `health.CheckCertificate`) + — **Sonnet** +- [ ] Handler `handlers_preflight.go` (assemble all checks, actionable + recommendations) — **Sonnet** +- [ ] Test-email handler (`POST /preflight/test-email`, `net/smtp` to + localhost:25) — **Sonnet** +- [ ] Template `preflight.html` (traffic-light cards + test-email form) — + **Sonnet** +- [ ] Routes in `web.go`, nav link — **Sonnet** diff --git a/docs/roadmap.md b/docs/roadmap.md index 5fc1cd5..954fb90 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -28,6 +28,7 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md). | ID | Topic | Status | Progress | Plan | |---|---|---|---|---| +| preflight | Installation check page (`/preflight`) | candidate | — | [plans/preflight.md](plans/preflight.md) | | contributing | `CONTRIBUTING.md` | candidate | — | — | | inbound-antispam-panel | Inbound antispam journal + allow/deny lists | agreed | 1/12 | [plans/inbound-antispam-panel.md](plans/inbound-antispam-panel.md) | | inbound-quarantine | Inbound spam quarantine (hold / review / release) | candidate | — | [plans/inbound-quarantine.md](plans/inbound-quarantine.md) | @@ -137,6 +138,30 @@ yet. --- +## preflight + +**Goal:** a `/preflight` page (global-admin only) that verifies instance-level +infrastructure: rDNS/PTR, TLS certificate + hostname match, port 25/465/587 +reachability, HELO banner correctness, reverse-proxy headers, DKIM milter +socket, and a test-email sender. Each check shows an actionable fix +recommendation on failure. + +**Boundary:** instance infrastructure only; per-domain DNS checks remain on +domain detail pages. Does not track test-email delivery beyond local MTA +acceptance. + +**Done when:** the page loads, all checks run with traffic-light results and +recommendations, and the test-email form successfully submits a message through +Postfix. + +**Dependencies / risks:** port-connectivity check may not reflect external +reachability behind NAT without hairpin; test email needs at least one domain or +falls back to `postmaster@SELFPOST_HOSTNAME`. + +**Version:** `1.x` MINOR; `candidate`. + +--- + ## schema-squash **Goal:** when 2.x is cut, stop shipping the 1.x migration chain in the binary