# Development How to work on nuntius. ## Prerequisites - Go 1.27.1, the version recorded in `go.mod`, the newest stable release. - [just](https://github.com/casey/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`](https://sourcedock.dev/petrbalvin/interpres) TOML parser. ## Setup ```sh 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: ```sh 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 ```sh 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 ```sh 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: ```sh just coverage-html ``` ## Benchmarks ```sh 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: ```sh just fuzz FuzzValidate ./internal/contactform 30s ``` ## Debugging the build ```sh 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](../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`.