7b4549a35d
Phase 13. Two new packages and one new screen. internal/health owns the shared status vocabulary (ok/warn/error/unknown) and the local checks: supervisord's process table, TLS certificate expiry and the two milter sockets. Each check reports a problem as a status rather than an error, so one broken component costs a line and not the page. internal/dnscheck does the read-only lookups: forward-confirmed reverse DNS for SELFPOST_HOSTNAME, and per-domain DKIM (compared against the key this server actually signs with), SPF and DMARC. Every check is bounded by a timeout and cached, and the resolver sits behind an interface so the tests drive every branch without touching the network. The SPF check is deliberately shallow: it looks for a mechanism literally covering the server's address and does not follow include:/redirect=, so a record that authorises us through an include is reported as "cannot tell" rather than as a failure. /status renders both, with the local checks in an HTMX-polled fragment and the DNS lookups behind a Re-check button, and becomes the panel's landing page: / now redirects there and the domain list lives at /domains. The Reload button moves onto /status, where it reads as what it is — a drift-recovery for the daemons — with text explaining what it regenerates. A template test fails on any remaining href="/" so a stale link cannot silently land on the wrong screen. Also fixes a defect this made visible: the panel could never read the mail queue in the documented deployment. postqueue relies on its setgid-postdrop bit, which the compose file's no-new-privileges disables, so the Queue screen always said "Could not read the mail queue" — including in the released 1.0.0 image. The panel user is now a real member of postdrop, which needs no setgid transition. Verified in a container on the dev server against real DNS: PTR matching (selfpost.example.com) and not matching (example.com), DKIM absent and mismatched, SPF absent and via include:, DMARC p=quarantine/p=reject/absent, and a resolver timeout degrading to "unknown" without hanging the page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
83 lines
5.0 KiB
Markdown
83 lines
5.0 KiB
Markdown
# Changelog
|
|
|
|
All notable changes to this project are documented here.
|
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows [SemVer](https://semver.org/).
|
|
|
|
## [Unreleased]
|
|
|
|
- panel: new **Status** page — supervised processes, mail queue, TLS
|
|
certificate expiry, milter sockets and the server's own hostname/reverse-DNS
|
|
(FCrDNS) check — and it is now the panel's landing page. The local checks
|
|
refresh by polling; the DNS lookup is cached with a *Re-check* button.
|
|
- panel: the domain page shows a **DNS status** card: the published DKIM record
|
|
compared against the key this server actually signs with, plus SPF and DMARC.
|
|
The SPF check is deliberately shallow — it looks for a mechanism literally
|
|
covering this server's address and does not follow `include:`/`redirect=`, so
|
|
a record that authorises the server through an include is reported as "cannot
|
|
tell", not as a failure.
|
|
- panel: the domain list moved from `/` to `/domains`; `/` redirects to the
|
|
status page. The **Reload** button moved from the domain list to the status
|
|
page and now explains what it regenerates and when to use it.
|
|
- fix: the panel could never read the mail queue in the documented deployment.
|
|
`postqueue` relies on its setgid-`postdrop` bit, which `no-new-privileges`
|
|
(set in the shipped compose file) disables, so the *Queue* screen always said
|
|
"Could not read the mail queue". The `panel` user is now a real member of
|
|
`postdrop`.
|
|
- panel: navigation bar is now rendered once from the shared layout, so every
|
|
authenticated page has it — including the domain page and the delete
|
|
confirmation, which had no navigation links at all — and the current page is
|
|
highlighted instead of silently missing from the list.
|
|
- panel: new *Account* page to change the administrator's username and/or
|
|
password (the current password is required, throttled on the same limiter as
|
|
the login form). Changing the password invalidates all other sessions.
|
|
- panel: *Backup & migration* moved off the domain list onto its own *Backup*
|
|
page, with the full backup and the domain import as two separate cards.
|
|
- panel: the domain page now shows the *Sending server settings* (server,
|
|
port and encryption) needed to configure a mail client; port 587 is listed
|
|
only when `SUBMISSION_ENABLE=true` for this deployment.
|
|
- panel: *Copy* buttons on the DKIM record, on a newly issued application
|
|
login/password and on the sending server name.
|
|
- panel: the *Addresses* field is hidden while an application's address mode is
|
|
*Any address of the domain*, where the server ignores it.
|
|
- ci: disable provenance attestation on release image push, so the ghcr.io
|
|
manifest list shows only `linux/amd64`/`linux/arm64` (no `unknown/unknown`).
|
|
- security: optionally honour `X-Forwarded-For` for login/setup rate-limiting
|
|
when the request's direct peer is in the new `TRUSTED_PROXY_CIDR` list,
|
|
giving real per-client limits behind a reverse proxy instead of one global
|
|
bucket. Unset by default (unchanged `RemoteAddr`-only behaviour).
|
|
|
|
## [0.1.0] - 2026-07-15
|
|
|
|
Initial feature-complete implementation of the v1.0 specification (phases 0-11
|
|
of `docs/implementation-plan.md`).
|
|
|
|
### Added
|
|
|
|
- Panel (Go, single static binary) with SQLite persistence, one-time
|
|
crypto-random setup link, bcrypt admin auth, session cookies.
|
|
- Domain management with per-domain DKIM (RSA-2048, generated in pure Go) and
|
|
OpenDKIM KeyTable/SigningTable regeneration + privilege-safe reload.
|
|
- Application (sender identity) management: SASL credentials via `sasldb2`,
|
|
`smtpd_sender_login_maps` enforcing sender/domain ownership, no open relay.
|
|
- Full Postfix relay config generated from env at container start: SMTPS 465,
|
|
optional STARTTLS submission 587, SASL auth, TLS for outbound delivery,
|
|
anvil-based rate limiting (level 1).
|
|
- Journal milter (pure Go, `go-milter`) recording every send to `send_log`;
|
|
fail-open by design so a milter fault never blocks mail.
|
|
- Monitoring UI: send log, Postfix queue, and mail.log tail, all
|
|
HTMX-polling, HTML-escaped.
|
|
- Per-domain/per-application sending rate limit (level 2), enforced in the
|
|
journal milter at `MAIL FROM`, fail-open on the limiter's own errors.
|
|
- Full backup/restore (`tar.gz` of `/data`, consistent SQLite snapshot via
|
|
`VACUUM INTO`) with a version guard that refuses to start on a
|
|
manifest/binary version mismatch. Per-domain export/import for moving a
|
|
single domain between hosts without re-issuing DNS records.
|
|
- Deployment: Docker image + compose, reverse-proxy fragments for Apache
|
|
(default), nginx, Caddy, and Traefik; CI workflow publishing tagged,
|
|
multi-arch images to `ghcr.io` on `vX.Y.Z` tags.
|
|
- Security pass against spec 7.6 (exec safety, config-write sanitization,
|
|
server-side validation, rate limiting, session/cookie hardening, output
|
|
escaping, non-root panel) — full compliance, no code changes required.
|
|
- Live production deployment on `selfpost.example.com` with a real Let's
|
|
Encrypt certificate; end-to-end delivery confirmed (DKIM pass, SPF pass).
|