Files
selfpost/docs/development.md
T
mix 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

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-tailer
  • cmd/selfpost-backup — CLI backup (docker exec … selfpost-backup)
  • internal/ — domain logic, store, web handlers, health checks
  • build/ — Dockerfile, supervisord, Postfix/OpenDKIM wiring, entrypoint
  • deploy/docker-compose.yml, proxy examples, .env.example
  • test/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:

  1. Meaningful step → entry under [Unreleased] in CHANGELOG.md (Keep a Changelog format).
  2. Commit on main unless asked for a branch. Do not commit unless the user asks.
  3. Before /clear at end of a phase: update progress.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)

  1. No git commits without explicit instruction in the prompt.
  2. After Go changes: go build, go vet; fix all issues. Run go test when tests exist.
  3. Before calling a container task done: image builds and container starts.
  4. Iterate: minimal skeleton first, then features.
  5. Security requirements in security.md — implement with the feature, not deferred.
  6. Do not implement out-of-scope items (product.md) or change fixed assumptions without agreement.
  7. For large tasks: propose a plan before coding unless the user already approved one.
  8. 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.