Files
nuntius/docs/DEVELOPMENT.md
petrbalvin 3a38f00dc0
Test / test (push) Successful in 2m1s
Release / gates (push) Successful in 1m57s
Release / build (amd64, freebsd) (push) Successful in 1m26s
Release / build (amd64, linux) (push) Successful in 1m30s
Release / build (arm64, freebsd) (push) Successful in 1m28s
Release / build (arm64, linux) (push) Successful in 1m49s
Release / build (loong64, linux) (push) Successful in 1m30s
Release / build (riscv64, linux) (push) Successful in 1m29s
Release / release (push) Successful in 41s
feat: contact form backend for linux and freebsd servers
Assisted-by: GLM 5.3 Flash
2026-09-29 00:32:56 +02:00

4.3 KiB

Development

How to work on nuntius.

Prerequisites

  • Go 1.27.1, the version recorded in go.mod, the newest stable release.
  • just for the recipes.
  • gcc for just gates: the race detector needs cgo.

Nothing else. There is no Node, no Python, and no third-party Go module beyond the first-party interpres TOML parser.

Setup

git clone https://sourcedock.dev/petrbalvin/nuntius.git
cd nuntius
just build

Configuration lives in TOML, read from /etc/nuntius/config.toml by default; the NUNTIUS_CONFIG environment variable points the server at any file you like, which keeps development free of root-owned paths:

export NUNTIUS_CONFIG=./config.toml
export NUNTIUS_SMTP_PASSWORD=admin    # the placeholder the template references
just run                              # first start writes the starter config

The starter forms point at example.com addresses, so submissions return 500 send_failed until config.toml carries real SMTP settings; strict environment expansion means every ${VAR} the config references must be set before startup.

Recipes

Every recipe in the project's file, taken from the file itself:

Recipe What it does
just default prints the recipe list
just build CGO_ENABLED=0 go build -trimpath -buildvcs=true -ldflags "-s -w" into bin/nuntius
just test the suite with no test cache, then the 80 % coverage floor over ./internal/...
just race the same suite under the race detector
just unit fast scoped run for iterating: cached, no race, no coverage
just fuzz time-boxed fuzz of one target in one package
just bench benchmarks
just fmt gofmt -w .
just fmt-check zero gofmt diff; prints nothing when everything is formatted
just vet go vet ./... and go fix -diff ./...
just gates build, fmt-check, vet, test, race: the definition of done, once per task
just clean removes bin/ and coverage.out
just install builds, then copies the binary into ~/.local/bin (override with BINDIR)
just uninstall removes the installed binary
just run go run -buildvcs=true ./cmd/server
just dev the same as run: nuntius carries no watch or reload tool
just coverage-html HTML coverage report from the gate's profile; an extension, not a gate

The test, race, unit and fuzz recipes run under a cgroup memory fence, so a runaway test dies at the ceiling instead of eating the machine.

Running a single test

go test -run TestName ./internal/handler/

Add -v for the sub-test names, and -race when the change touches concurrency. -count=1 defeats the test cache when a result looks stale; just unit keeps the cache on purpose, because a scoped iterating run wants to be instant.

Coverage

just test
go tool cover -func=coverage.out

The total: line is the number that matters, and it stays at 80 percent or more. Coverage is measured over the logic packages only; cmd/server is thin glue around them. For the HTML map:

just coverage-html

Benchmarks

just bench

Benchmark on an idle machine, and compare only runs made in one process against each other: runs in separate processes, or on a loaded machine, differ by more than the effects being measured.

Fuzzing

Two fuzz targets exist: FuzzValidate in internal/contactform and FuzzLoadConfig in internal/config. They are exploration, never a gate; time-box one explicitly:

just fuzz FuzzValidate ./internal/contactform 30s

Debugging the build

go build -gcflags='-m' ./...   # inlining decisions
go build -gcflags='-S' ./...   # what the compiler generated

Continuous integration

Workflows live in .gitea/workflows/ and run on the project's own runners. They are written by hand rather than through just, but they enforce the same set of gates, so a green just gates locally is the fastest way to a green pipeline. The per-workflow table is in CONTRIBUTING.md.

Releases

Releases are cut by merging development into main and tagging vX.Y.Z. The tag drives the release workflow, which builds the assets and publishes the notes it extracted from CHANGELOG.md.