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/domainis 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/templaterenders the pages with templ, and embeds the files served as they are, instatic/: the stylesheet, PGP key and photographs.make templregenerates the_templ.gofiles, andmake testfails if they're stale or unformatted.internal/infrastructure/xhttp/httpsiteserves 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/evantannloads 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.TestSandboxRealruns 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
FuzzHandlerchecks. - 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. Seeansible/playbooks/evantann.yamlin paravoce-infra, andscripts/deploy_evantann.shthere 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=Handlerfuzzes only the targets matching.make build- a static binary inbuild/.
