aboutsummaryrefslogtreecommitdiff

evantann.com

The repository of my personal website, www.evantann.com, written in Go. It serves the home page, the photography portfolio, and the kakeibo page, and the Go vanity import paths (evantann.com/<repo>) of the public repositories at git.evantann.com.

The code is licensed under the AGPL. The photos are copyrighted by me.

Running Locally

Install Nix with the flakes experimental feature enabled, then enter the dev shell (nix develop, or automatically with direnv) and run:

make deps   # fetch the Go tools
make up     # serve the site on http://localhost:3000/

The daemon sandboxes itself with Landlock, as on the server, which needs Linux 6.12 or later. On another kernel, or another OS, run it with EVANTANN_SANDBOX=off make up.

Configuration

The daemon takes no arguments. It reads these environment variables, each optional:

Variable Default Meaning
EVANTANN_HOST_PORT 127.0.0.1:3000 The listener, which must be on loopback: the daemon answers the reverse proxy alone.
EVANTANN_PUBLIC_URL https://www.evantann.com Where the site is on the internet, which the canonical links, sitemap and robots.txt name. https, or http on loopback for development. When its host starts with www., the apex host's requests are redirected to it, but for the go command's.
EVANTANN_SANDBOX on off turns the Landlock sandbox off, for development alone.

Architecture

The site follows Easel's clean architecture, scaled to a site with no state. Dependencies point inward: cmd wires infrastructure, which serves what presentation renders of domain.

  • internal/domain is what the site is made of: the repositories served as Go import paths (repo.go), and the photographs, with the ones the home page features (photo.go). It imports nothing outside the standard library.
  • internal/presentation/template renders the pages with templ, and embeds the files served as they are, in static/: the stylesheet, PGP key and photographs. make templ regenerates the _templ.go files, and make test fails if they're stale or unformatted.
  • internal/infrastructure/xhttp/httpsite serves it all: every page is rendered once, as the daemon starts, and every file read into memory, so a request only picks bytes from memory.
  • cmd/evantann loads the configuration, binds the listener, sandboxes the process, and serves.

There is no application layer: the site has no use cases beyond serving what's in the binary.

Adding a Go module

A public repository with a Go module needs its name added to Repos in internal/domain/repo.go, which serves its evantann.com/<name> go-import tag. See paravoce-infra's README for making the repository.

Adding a photograph

Put the photograph in internal/presentation/template/static/photography/display/ as <name>.jpg, and its thumbnail in .../thumbs/ as <name>_thumb.jpg. The name may hold letters, digits, - and _. The daemon refuses to start if any photograph lacks its thumbnail, or isn't a JPEG. The home page features the photographs featured in internal/domain/photo.go names.

URLs

Each page has one URL, which its canonical link names, every link uses, and the sitemap lists: /, /photography.html and /kakeibo.html. The other paths a page was reached by, such as /index.html or /photography/, redirect to it permanently, by path alone so the browser stays on HTTPS. robots.txt allows everything and names the sitemap. The tests follow every link on every page, and every URL in the sitemap, and fail on any that redirects or isn't found.

Security

The site is static, so the daemon is built to be able to do nothing but answer the reverse proxy:

  • Every page and file is built into the binary, so once the listener is bound, the daemon restricts itself with Landlock to nothing at all: it can't open any file, its own binary included, bind or connect to a TCP port, run a program, or signal a process outside itself. It refuses to start on a kernel without Landlock ABI 6 (Linux 6.12), unless EVANTANN_SANDBOX=off. TestSandboxReal runs the daemon under the real sandbox and checks each of these.
  • Every response carries a Content-Security-Policy allowing only the site's own stylesheet and images, no script, frames, forms or base URL, and nosniff, no-referrer, COOP, CORP, and HSTS.
  • Only GET and HEAD are answered; any other method is refused, 405. Every redirect goes to a fixed path on this site, to a repository's page at git.evantann.com, or, from the apex host, to the same path on the public URL, whatever host or path the request names, which FuzzHandler checks.
  • On the server, it runs as _evantann, a system user with no home or shell, in a systemd sandbox that leaves it no capabilities, a read-only system, no /home, no other service's files, nothing to execute but its own root-owned binary, only its own port to bind, and only loopback to talk to; nftables rejects any connection it opens. See ansible/playbooks/evantann.yaml in paravoce-infra, and scripts/deploy_evantann.sh there for the deploy.

Development

Run make with -j: test and lint are made of independent targets.

  • make check - vet and the short tests: the quick loop.
  • make test - the linters and the full suite, then sentinellint, which fails when no test returns one of the sentinel errors, deadlint, which fails on functions no main package reaches, and govulncheck.
  • make test-race - the tests under the race detector; run it before merging.
  • make lint - golangci-lint, timelint, typelint, leaklint and ifacelint, as the other projects run them.
  • make fuzz - fuzz every target until each goes quiet, with fuzzomatic. make fuzz FUZZ=Handler fuzzes only the targets matching.
  • make build - a static binary in build/.