Docs
Development
Tests, build tags, project layout and the stack.
Build, test and code layout for contributors.
Tests
make test # unit tests
make test-integration # real PocketBase app: migrations, HTTP routes, OIDC flow,
# API tokens/API, email notifications, cache and limits
make test-e2e # Chromium: OIDC login, add/check/remove, HTMX updates,
# token creation/API use, notification email
make test-all
Integration and e2e suites use build tags so the default go test ./... stays
fast:
//go:build integration//go:build e2e
The end-to-end suite needs a Chromium/Chrome binary on PATH. It builds the
binary, starts it on a random port with fake OIDC, RDAP, WHOIS and SMTP servers,
then drives the real UI with chromedp. No external network access is required.
If Chromium is missing the tests skip, unless DH_E2E_REQUIRED=1 is set, in
which case they fail.
Logs
The default make run and the built binary emit structured JSON to stdout:
application startup, HTTP requests (including PocketBase /api/ routes), and
PocketBase's persisted app logs mirrored from _logs. Set DH_LOG_FORMAT=text
for logfmt, or DH_LOG_LEVEL=debug for more detail.
Use make run-dev to run with PocketBase's --dev console instead. That mode
keeps PocketBase's human-readable logs and SQL output, and skips the JSON
mirror of PocketBase's _logs entries.
Project layout
main.go app wiring
migrations/ PocketBase schema migrations
docs/ embedded Markdown documentation
internal/config/ env configuration and validation
internal/domain/ domain normalization/validation
internal/availability/ RDAP/WHOIS checkers + per-domain cache
internal/watchlist/ persistence, checks, scheduler logic, notifications
internal/apitoken/ API token issue, verify, list and deactivate
internal/account/ registration, OTP codes, password/email settings
internal/support/ support tickets and admin notifications
internal/notify/ availability, expiry and drop email rendering
internal/auth/ OIDC flow, sessions, password login, PB OAuth2 config
internal/server/ routes, HTMX handlers, templates, CSS
internal/testutil/ fake OIDC, RDAP, WHOIS and SMTP servers for tests
e2e/ browser end-to-end tests
scripts/ CI maintenance scripts (package pruning)
.forgejo/workflows/ Forgejo CI and release workflow
.releaserc.yml semantic-release configuration
CI and releases
The Forgejo workflow is .forgejo/workflows/build.yaml:
- Every push and pull request runs
go vet, unit tests, integration tests andgo build. - Pushes to
mainand explicitv*.*.*tag pushes buildlinux/amd64andlinux/arm64images and push them togit.chaops.de/rwxd/domainhawk. The workflow cross-compiles the Go binary, then builds through the existing rootless Podman runner engine using Docker CLI, so no QEMU emulation is required. - Per-architecture images use
build-amd64-<short>andbuild-arm64-<short>helper tags. Final tags are multi-architecture manifests. - Main builds are tagged
main,latestandsha-<short>. - Tag builds are tagged with the release tag,
latestandsha-<short>. - Semantic-release runs on
mainusing.releaserc.yml; it creates thevX.Y.Ztag and release notes. Because the tag is created with the workflow token, it does not trigger a second push workflow. The release job therefore dispatches the build workflow for the new tag withlatest=true, which publishes the versioned image and moveslatest. A manual Run workflow with only thetaginput backfills or rebuilds an older release image without movinglatest. - A cleanup job uses the Forgejo API to delete
sha-*,build-amd64-*andbuild-arm64-*image versions older than 14 days. It runs after each main build and daily at 03:00 UTC. Movingmain/latesttags stay current, while semantic-releasev*tags are kept indefinitely.
The automatic FORGEJO_TOKEN handles semantic-release (it has repository write
access). A repository secret named REGISTRY_TOKEN must contain a token with
write:package and write:repository so the workflow can push and prune
container images in git.chaops.de/rwxd/domainhawk. The image is built with the
release tag or main-<sha> as the application version.
Stack
| Layer | Choice |
|---|---|
| Language | Go 1.27 |
| Backend | PocketBase embedded (v0.40.x) |
| UI | html/template + HTMX 2 + plain CSS |
| OIDC | coreos/go-oidc/v3 for discovery, JWKS and ID-token verification; golang.org/x/oauth2 for the authorization-code flow and PKCE |
| PocketBase's SMTP mailer | |
| Availability | RDAP over HTTPS with IANA bootstrap; WHOIS fallback |
Using go-oidc + x/oauth2 is deliberate: go-oidc verifies issuer, audience,
expiry, signature and nonce against the provider's JWKS; x/oauth2 performs the
PKCE code exchange. There is no hand-rolled JWT validation. PocketBase remains
the user store and session token issuer.