Commit Graph
10 Commits
Author SHA1 Message Date
mixandClaude Opus 5 23d52bab7c fix: port-25 milter chain, DMARC report validation, auto rate-limit window
test / test (push) Has been cancelled
Review pass over the 1.4.0-1.9.1 work.

Port 25 no longer inherits main.cf's submission milter chain: smtpd_milters
is always overridden on smtp/inet, to the antispam milter or to nothing.
Without that, OpenDKIM (strict, tempfail) gated relayed mail during a signing
outage, and the journal-milter filed inbound mail into the outbound send log
under the sender's domain -- so Deliveries showed other people's mail and a
forged From: consumed that domain's level-2 budget. architecture.md already
described the intended behaviour; the code now matches.

DMARC reports are checked before they are stored: the policy domain must be a
configured sending domain, and must match the +tag of a per-domain hosted
address. The rua address form is predictable, so anyone able to reach it could
file reports under any domain and evict genuine ones through the retention cap.
Zip attachments are now decompressed as well as gzip.

The auto level-2 ceiling is scaled to the level-1 window before the multiplier
is applied; any window other than 3600s shifted the allowance by window/3600.

Migration 0010 indexes send_log(app_login, created_at) -- both the per-MAIL
FROM rate-limit count and the per-application statistics were scanning the
created_at range.

e2e now regenerates the Postfix config in-container for all four port-25 flag
combinations and requires postfix check to pass; the 1.9.1 crash-loop shipped
in 1.4.0 and survived three releases because only the flags-off path was
exercised.

Migration 0009 stays as shipped -- its conversion branch only fires for a
database carrying application-scope allowed_ips from before 1.9.0. The schema
reference notes that the old list was permissive and the new column is
restrictive; the guide documents the allow-list as a sending control on a
fail-open path, not an authentication boundary.

Also: gofmt across cmd/ and internal/, unused duplicate validators dropped from
internal/web/validate, export no longer swallows store errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 14:17:39 +03:00
mixandCursor 27aeadc71d release: 1.7.0
test / test (push) Has been cancelled
Receive DMARC aggregate reports on port 25 and show parsed summaries in the panel. Close Unreleased; pin compose and docs to 1.7.0.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 22:53:36 +03:00
mixandCursor 0d98d92642 Add optional inbound relay (backup-MX) behind INBOUND_RELAY_ENABLE.
test / test (push) Has been cancelled
Port 25 accepts only configured domains and listed recipients, then forwards to an upstream; the outbound path is unchanged when the flag is off.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-17 23:17:30 +03:00
mixandCursor 705239568b Show this Postfix's retry policy on Mail queue and delivery history.
test / test (push) Has been cancelled
Numbers come from a one-shot postconf -h at panel start so a manual override is visible after restart, without inventing an attempt count.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-17 22:12:03 +03:00
mixandCursor e70ba9046e fix(panel): resync mail-path maps once after restore
When CheckRestore accepts a backup manifest, the panel re-derives OpenDKIM tables and the Postfix sender map from SQLite on that first boot and reloads both daemons, so archive/database drift is healed before mail flows.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-14 14:58:08 +03:00
1f548dfc7a fix(logtail): keep mail.log in /data and reconcile stuck rows (v1.x closure phase 2)
Move the delivery log from the ephemeral /var/log to /data/log/mail.log so
the lines that resolve a queued send-log row survive a container recreate.
postlogd writes it as postfix, the panel reads it through the selfpost group
(dir 2750, file 0640, normalised every start); backups exclude log/.

Close the residual gap with a queue sweep: rows queued for over two minutes
whose id postqueue -p no longer lists are marked bounced. The sweep waits
until the tailer has read the log to its end and does nothing when the queue
cannot be listed, so a message in flight is never touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-08 11:35:27 +03:00
mixandClaude Opus 5 c0d9aa7518 chore/docs: move to GitHub as the single home; drop archived-spec references
Codeberg is being retired as the project's public site, so every reference now
points at GitHub. That includes the Go module path (codeberg.org/mix/selfpost →
github.com/mixeme/selfpost): leaving an import path on a host that is going
away would break `go get` and `go install`, so this is not only a docs change.
Touches go.mod, test/e2e/go.mod, all imports, Makefile MODULE, the -ldflags
version stamp in build/Dockerfile and docs/development.md, the licence headers
in the SVG/HTML assets, and README (no more primary/mirror pair).

Comments no longer cite the archived specification. "spec 7.6.1", "spec 5.1"
and friends pointed into docs/archive/specification-v1.0.md, which is marked as
not a source of truth; each is now a reference to the live document that owns
the subject — architecture.md (with section), product.md, security.md or the
README. The review only asked for the 7.x refs (code-review.md § 4), but 4/5/6/
8/9 had the same defect, so they went too. Comments only, no behaviour change.

Also closes the remaining review items: architecture.md gained a Code layers
section with the layer diagram (A2), and TestParseDelivery gained the exotic
mail.log cases (§ 3).

Fixes a bug that last test found: the delivery-line pattern matched status=
greedily, taking the *last* occurrence on the line. Postfix appends the remote
server's reply verbatim, so a rejection whose reply quoted "status=sent" was
filed as a delivered message in the send log. It now takes the first status=
after the recipient, which is the real field.

R7 (CONTRIBUTING.md) moved to roadmap 2.x — one developer, no external PR flow,
so the file would have no audience yet. R1 (compose image tag) and the git tag
stay in roadmap § v1.x as the release-commit steps.

gofmt/go vet clean on both modules; go test ./... green except the three known
Windows-only failures (file perms, backslash paths, renaming an open file).
Not exercised on the dev server — no Docker locally.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 22:14:13 +03:00
mixandClaude Sonnet 5 670982fb3e docs/chore: Phase 1 doc/code hygiene (code-review.md § Phase 1)
Removes ~30 stale "Phase N" / historical-staging comment references from
code and shell scripts now that v1.0 is done; fixes a stale dashboard
comment claiming applications/send-log were unimplemented; adds a CSRF ADR
to security.md documenting the Origin-check-over-tokens decision; resolves
docs/logo in roadmap.md (directory doesn't exist, criterion already met);
adds a gofmt -l check to CI so unformatted Go fails the build.

The known-limitations write-up for the log-tailer offset gap (the other
Phase 1 item) was already present in architecture.md § Log tailer, so no
change was needed there.

gofmt/go vet/go test clean on both Go modules (main + test/e2e), verified
on the dev server.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 16:13:36 +03:00
mixandClaude Sonnet 5 cb25923a7b Phase 7: monitoring UI — send log, queue, mail.log tail
Three HTMX-polled monitoring screens (spec 7.2.11-13): send log with
server-side domain/application filters and pagination, Postfix queue
(postqueue -p), and a mail.log tail. Fragment endpoints return HTML
snippets, not JSON (spec 7.1); all output is auto-escaped via
html/template (spec 7.6.7).

Adds store.QuerySendLog/CountSendLog/ListApplicationLogins,
postfix.Queue(), and logtail.TailLines (a point-in-time reverse read,
independent of the background follow loop). Verified on the dev server:
gofmt/vet/test green, docker build green, container e2e (filters,
60-row pagination, <script> escaping, real postqueue/mail.log output,
existing Reload button unaffected).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 23:22:08 +03:00
mixandClaude Opus 4.8 c6eeb30258 Phase 4: applications + SASL (sasldb2) + sender_login_maps
Adds application accounts bound to domains: a SASL login/password in
sasldb2, a per-application address mode (wildcard @domain or an explicit
list), and matching smtpd_sender_login_maps bindings — with create,
list, edit-mode, delete and password regeneration (spec 4.1, 5.1,
7.2.5-9). Generated passwords are shown exactly once and never stored in
plaintext (7.6.1).

- internal/store/applications.go: transactional CRUD; globally unique
  login; ListBindings (address->login) as the map source; logins-by-
  domain for pre-cascade SASL cleanup.
- internal/app: saslpasswd2 wrapper (password via stdin, login as a
  whitelisted argv element, no shell — 7.6.3); strong base64url password;
  address validation that enforces domain ownership before any config
  write (7.6.2); service orchestrating store + sasldb2 + map with full
  rollback on partial failure.
- internal/postfix: sender_login_maps regenerated as a pure function of
  the registry (many-to-one logins merged per address), atomic write,
  injection backstop (7.6.4).
- Postfix reload, corrected: `postfix start-fg` forks a separate master,
  so signalling the supervised process never reaches it. Reload now runs
  the canonical `postfix reload` via a one-shot supervisord program the
  unprivileged panel triggers over the group control socket. Verified in
  mail.log.
- domain.Service.Delete purges the domain's SASL accounts, then cascades,
  then rebuilds the sender map and reloads; manual reload now covers both
  OpenDKIM and Postfix.
- web: application management in the domain page, one-time credential
  shown inline; postfix joins the selfpost group and entrypoint normalises
  /data/sasl and /data/postfix (setgid, group-readable) with self-heal.

Verified on the dev server: gofmt/vet/test green, image builds, and a
container e2e covers the full application lifecycle, domain-delete
cascade, restart persistence, and a real postfix reload.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:18:10 +03:00