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>
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.