release: 1.9.2: Review pass: port-25 milters, DMARC validation, rate-limit fix

Review pass over `1.4.0`…`1.9.1`: port 25 no longer shares the submission milter chain, DMARC reports are validated before they are stored, and the auto rate limit is correct for a non-default level-1 window. Schema migration `0010` (index only). Upgrading is a tag bump.
Added:
  - SQLite migration `0010_send_log_app_login_index.sql` —
  - DMARC aggregate reports are accepted as **zip** as well as gzip or plain XML
  - e2e: `checkPort25ConfigCombinations` regenerates the Postfix configuration
Changed:
  - README hero line and a **Positioning** block in `docs/product.md` record the
Fixed:
  - The setup account's password is hashed at bcrypt cost `12` like every other
  - Login/setup rate limiter: protecting blocked buckets from eviction is now
  - `postqueue -p` output is bounded to 4 MiB (the timeout alone left the size of
  - [schema-migrations.md](docs/schema-migrations.md): the schema section was
  - postqueue reader is now time-bounded: `postqueue -p` runs under a 5-second
  … (11 more in CHANGELOG)
Changed:
  - [roadmap.md](docs/roadmap.md) now carries everything the 2026-08-19 code
  - Extracted `internal/configsafe` — the empty-value and forbidden-character
  - Build, deploy and migration comments no longer cite the archived
  - Dropped the `parseDomainRateLimitForm` / `parseAppRateLimitForm` pass-through
  - Extracted shared helpers: `internal/atomicfile` (atomic temp-write+rename) and
  … (7 more in CHANGELOG)
Removed:
  - Unused legacy asset `docs/assets/icon.png` (superseded by the four brand SVGs
  - Plan documents for already-shipped features: `domain-stats-auto-ratelimit`,

Full detail: CHANGELOG.md (1.9.2).
This commit is contained in:
mix
2026-09-22 15:24:53 +03:00
parent 967d3d95de
commit 7ef7f75246
179 changed files with 11558 additions and 1738 deletions
@@ -0,0 +1,52 @@
<!-- title: Preflight | section: server | page: preflight -->
<div class="sp-head">
<div class="sp-postmark sp-warn"><span>PREFLIGHT</span><b>WARN</b><span>1 OF 7 CHECKS</span></div>
<div class="sp-grow">
<p class="sp-kicker">Server</p>
<h1 class="title is-3">Preflight</h1>
<p class="sp-lead">Is this installation ready to deliver mail? Run it after the first start, after moving the server, or when receivers start refusing you. Health watches what runs; Preflight checks what the outside world sees.</p>
</div>
<form method="post" action="#"><button type="submit" class="button"><i class="ti ti-refresh"></i>Run again</button></form>
</div>
<div class="box">
<div class="sp-box-head"><span class="sp-no">01</span><h2>Checks</h2><div class="sp-end">Ran 14:02 UTC · 3.1 s</div></div>
<div class="table-container"><table class="table is-fullwidth">
<thead><tr><th>Check</th><th>Result</th><th>Found</th><th>What to do</th></tr></thead>
<tbody>
<tr><td class="sp-nowrap"><strong>Reverse DNS</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-mono sp-small">203.0.113.25 ⇄ mail.example.org</td><td class="sp-muted">—</td></tr>
<tr><td class="sp-nowrap"><strong>TLS certificate</strong></td><td><span class="tag is-warning is-light">warn</span></td><td class="sp-small">Valid for <span class="sp-mono">mail.example.org</span>, expires in 12 days</td><td>Renewal has not run. Check that port 80 reaches the ACME client and look for <code>acme</code> in the <a href="system-log.html">system log</a>.</td></tr>
<tr><td class="sp-nowrap"><strong>Port 25</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-mono sp-small">220 mail.example.org ESMTP</td><td class="sp-muted">—</td></tr>
<tr><td class="sp-nowrap"><strong>Ports 465, 587</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-mono sp-small">TLS handshake, 220 banner on both</td><td class="sp-muted">—</td></tr>
<tr><td class="sp-nowrap"><strong>HELO name</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-mono sp-small">mail.example.org</td><td class="sp-muted">—</td></tr>
<tr><td class="sp-nowrap"><strong>Proxy headers</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-small">Peer <span class="sp-mono">172.18.0.2</span> is trusted; <span class="sp-mono">X-Forwarded-Proto: https</span></td><td class="sp-muted">—</td></tr>
<tr><td class="sp-nowrap"><strong>DKIM signing</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-mono sp-small">inet:127.0.0.1:8891 answers</td><td class="sp-muted">—</td></tr>
</tbody>
</table></div>
<div class="sp-box-foot"><span>Ports are dialled from inside the container at the public address; a firewall that treats inside and outside differently can make this pass while the world is still blocked. SPF, DKIM and DMARC are per domain — each <a href=\"out-domains.html\">outbound domain</a> shows its own.</span></div>
</div>
<div class="box">
<div class="sp-box-head"><span class="sp-no">02</span><h2>Send a test e-mail</h2><div class="sp-end">Confirms the relay accepted it, not that it arrived</div></div>
<div class="sp-box-body">
<div class="columns">
<div class="column is-6">
<form class="sp-form" method="post" action="#">
<div class="field"><label class="label" for="rcpt">Recipient</label>
<div class="field has-addons">
<div class="control is-expanded has-icons-left"><input class="input" id="rcpt" name="recipient" type="email" placeholder="you@elsewhere.example" required><span class="icon is-left"><i class="ti ti-mail"></i></span></div>
<div class="control"><button type="submit" class="button is-primary"><i class="ti ti-send"></i>Send</button></div>
</div>
<p class="help">Sent from <span class="sp-mono">postmaster@mail.example.org</span> through the local Postfix.</p></div>
</form>
</div>
<div class="column is-6">
<dl class="sp-facts">
<div><dt>Last test</dt><dd>14:03 UTC to <span class="sp-mono">mix@elsewhere.example</span></dd></div>
<div><dt>Result</dt><dd><span class="tag is-success is-light">queued</span> <span class="sp-mono sp-small">4XcC1d8Pq2z1</span></dd></div>
<div><dt>Then</dt><dd><a href="out-message.html">Follow it in the outbound log</a></dd></div>
</dl>
</div>
</div>
</div>
</div>