domainhawk

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 and go build.
  • Pushes to main and explicit v*.*.* tag pushes build linux/amd64 and linux/arm64 images and push them to git.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> and build-arm64-<short> helper tags. Final tags are multi-architecture manifests.
  • Main builds are tagged main, latest and sha-<short>.
  • Tag builds are tagged with the release tag, latest and sha-<short>.
  • Semantic-release runs on main using .releaserc.yml; it creates the vX.Y.Z tag 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 with latest=true, which publishes the versioned image and moves latest. A manual Run workflow with only the tag input backfills or rebuilds an older release image without moving latest.
  • A cleanup job uses the Forgejo API to delete sha-*, build-amd64-* and build-arm64-* image versions older than 14 days. It runs after each main build and daily at 03:00 UTC. Moving main/latest tags stay current, while semantic-release v* 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
Email 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.