A bounce could be recorded as a successful delivery. The log-tailer's Fixed: - A bounce could be recorded as a successful delivery. The log-tailer's - Log-tailer resumes where it stopped instead of jumping to end-of-file on - Level-2 rate limit no longer overshoots under concurrency: messages that Changed: - The project has a single public home: `github.com/mixeme/selfpost`. Codeberg - Code comments no longer cite the archived specification. References like - `docs/code-review.md` is gone. Its plan is finished — phases 0 (bar the - `docs/architecture.md` gained a *Code layers* section: a diagram of - Phase 1 doc/code hygiene (`docs/code-review.md`): removed ~30 stale … (1 more in CHANGELOG) Security: - Pre-release security review (plan § D, model Fable, 2026-08-06): full pass - `saslpasswd2` argv: the application login is now passed after a `--` Added: - Optional password encryption for the two secret-bearing downloads (plan - Domain import accepts an encrypted export: the envelope is detected by its - `selfpost-backup` writes encrypted archives and reads them back: - `TestParseDelivery` covers the exotic mail.log shapes the review asked for - docs: README *Encrypting a backup or export*; `docs/security.md` § … (14 more in CHANGELOG) Removed: - docs: `security.md` — accepted risk «restore old backup revives session rows» Changed: - docs: `implementation-plan.md` trimmed to the sole open v1.x gate — pre-release - docs: `architecture.md` — sessions (SQLite, idle renew, password change), - docs: `development.md` — expanded e2e stack and `release.yml` matrix workflow. - docs: `security.md` — accepted risk for send-log rows stuck at `queued` after - docs: `roadmap.md` — optional send-log / `mail.log` follow-ups under v1.x tail. … (10 more in CHANGELOG) Full detail: CHANGELOG.md (0.5.0).
5.2 KiB
SelfPost — development
What this file is. How to build, test, and ship changes. Current sprint
state lives in progress.md — read that first after /clear.
Product boundaries: product.md. As-built layout: architecture.md.
Repository layout
cmd/panel— panel HTTP server + milter + log-tailercmd/selfpost-backup— CLI backup (docker exec … selfpost-backup)internal/— domain logic, store, web handlers, health checksbuild/— Dockerfile, supervisord, Postfix/OpenDKIM wiring, entrypointdeploy/—docker-compose.yml, proxy examples,.env.exampletest/e2e/— separate Go module; container integration tests
Local Go workflow
Requires Go 1.26+ and CGO_ENABLED=0 (pure Go SQLite).
make vet # go vet ./...
make test # go test ./...
make build # bin/panel, bin/selfpost-backup (VERSION=dev by default)
make build VERSION=1.0.0
Or directly:
go vet ./...
go test ./...
go build -trimpath -ldflags "-X github.com/mixeme/selfpost/internal/buildinfo.Version=dev" -o bin/panel ./cmd/panel
Env documentation regression: go test ./cmd/panel -run TestLoadConfig —
new loadConfig keys must appear in README env lists
(cmd/panel/envdoc_test.go).
End-to-end tests
Hermetic container suite (implemented as test/e2e/, separate Go module):
make e2e
# equivalent: cd test/e2e && go test -v -timeout 20m ./...
Stack: shipped deploy/docker-compose.yml +
test/e2e/compose.override.yml — same
cap_drop/cap_add/no-new-privileges as production. Override uses high
ports (20465/20587/20080), test hostname, self-signed TLS,
PANEL_COOKIE_SECURE=false, isolated compose project. Mail is hermetic: CoreDNS
fake zone + Postfix smtp-sink as sink-MX; DKIM TXT is scraped from the panel
and published into the zone so the test verifies the record the operator would
use.
Coverage (summary): full bootstrap → SMTP AUTH → delivery → DKIM verify →
send-log queued → sent; negatives (no AUTH, relay, sender/login mismatch, L1/L2
limits, milter fail-open, bad SELFPOST_HOSTNAME, session survives
docker restart). Polling with timeouts only — no fixed sleep.
Requires Docker + Compose v2 on the test host. Not included in go test ./...
of the main module.
CI (release.yml): tag vX.Y.Z triggers
prepare (version from tag) → native matrix [ubuntu-latest, ubuntu-24.04-arm]
— build --load, e2e, push per-arch tag → merge publishes unified
ghcr.io/...:X.Y.Z via docker buildx imagetools create. Failed e2e blocks the
image. Ordinary pushes still run only vet/test in test.yml.
Dev server (full container)
When unit tests are not enough — Postfix, OpenDKIM, supervisord, real SMTP:
Typical setup: edit locally → sync tree to dev host → build/test there.
Documented dev host: selfpost.example.com (Debian 12). Sync example from
progress.md:
tar -czf - --exclude=.git . | ssh root@selfpost.example.com \
'rm -rf /root/selfpost-src && mkdir -p /root/selfpost-src && tar -xzf - -C /root/selfpost-src'
On the server (Go in /usr/local/go/bin if not in PATH):
cd /root/selfpost-src
/usr/local/go/bin/go vet ./...
/usr/local/go/bin/go test ./...
docker build -f build/Dockerfile -t selfpost:dev --build-arg VERSION=dev .
Manual smoke on production-like host: panel at https://selfpost.example.com,
real LE cert, live deliverability — not replaceable by e2e alone (no outbound
25 on CI runners, no real PTR/reputation).
Commit and changelog protocol
From progress.md:
- Meaningful step → entry under
[Unreleased]in CHANGELOG.md (Keep a Changelog format). - Commit on
mainunless asked for a branch. Do not commit unless the user asks. - Before
/clearat end of a phase: updateprogress.md, verify acceptance criteria, CHANGELOG, final commit.
Documentation changes that add/rename env vars, panel routes, or observable mail behaviour ship in the same commit as the code change.
Release tagging and image push — only on explicit request.
Agent rules (formerly spec §12)
- No git commits without explicit instruction in the prompt.
- After Go changes:
go build,go vet; fix all issues. Rungo testwhen tests exist. - Before calling a container task done: image builds and container starts.
- Iterate: minimal skeleton first, then features.
- Security requirements in security.md — implement with the feature, not deferred.
- Do not implement out-of-scope items (product.md) or change fixed assumptions without agreement.
- For large tasks: propose a plan before coding unless the user already approved one.
- Check licence compatibility of new Go dependencies (permissive or GPL-family for AGPL-3.0 project).
Model routing (from progress): security/infra → Opus; UI/docs → Sonnet; trivial mechanics → Haiku. Pre-release security review (not authorship) → Fable.