ac5b37d1e2
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.mixfed.ru) and not matching (mixfed.ru), 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>
5.0 KiB
5.0 KiB
Changelog
All notable changes to this project are documented here. Format follows Keep a Changelog; versioning follows SemVer.
[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.
postqueuerelies on its setgid-postdropbit, whichno-new-privileges(set in the shipped compose file) disables, so the Queue screen always said "Could not read the mail queue". Thepaneluser is now a real member ofpostdrop. - 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=truefor 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(nounknown/unknown). - security: optionally honour
X-Forwarded-Forfor login/setup rate-limiting when the request's direct peer is in the newTRUSTED_PROXY_CIDRlist, giving real per-client limits behind a reverse proxy instead of one global bucket. Unset by default (unchangedRemoteAddr-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_mapsenforcing 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 tosend_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.gzof/data, consistent SQLite snapshot viaVACUUM 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.ioonvX.Y.Ztags. - 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.mixfed.ruwith a real Let's Encrypt certificate; end-to-end delivery confirmed (DKIM pass, SPF pass).