Farfield Systems
A set of small, single-binary Go services. Each app is an HTML admin UI for writing and moderating content, plus a JSON API the website reads behind a read token. These apps provide the backends for various self hosted projects.
Stack
The standard library plus exactly one dependency:
modernc.org/sqlite, a pure-Go SQLite driver. No
cgo, so every build is a static binary. HTTP is
net/http, templates are
html/template, assets are embedded with
embed, logging is log/slog. No web
framework, no ORM, no router library.
Core Ideas
- Content-addressed. Every record carries a CID — a sha-256 hash of its content — for verification, change detection, and ETag caching.
- Self-migrating. A database migrates its own schema when it is opened; deploying new code is the migration step.
- One aesthetic. The admin UIs share one warm-instrument look — paper ground, white panels, ink, one signal red — from a single shared stylesheet.
Services
Conventions
-
Reads need a read token. The JSON read
APIs require a bearer token — send the app's read key as
Authorization: Bearer <token>orX-API-Key. The write key is also accepted. Responses still sendAccess-Control-Allow-Origin: *. -
Some routes stay public. Things a
browser or scanner loads directly and cannot attach a
header to: blob bytes
(
/blobs/{cid}), QR scan and redirect (/qr/{id},/r/{id}), the daily artifacts, and every/status. -
"View source" is public. A single
published record by slug —
feed /api/posts/{slug}andcontent /api/entries/{slug}— loads without a token so the site's "view source" links open in a browser, but it is rate-limited per client IP (keyed callers are exempt). Drafts stay a404to anyone without the write key; the enumerating lists stay token-gated. - Writes need the write key — same headers, a separate higher-privilege token.
-
Drafts. The read token returns
published content only. The write key additionally
previews drafts on
content(?status=allor?status=draft, or fetch a draft by slug). - Every record has a stable key (its slug) and a CID (sha-256 hash). The key never changes; the CID changes when content changes.
-
Single-record endpoints send the CID as a strong
ETag. SendIf-None-Matchfor conditional GET. - Timestamps are RFC3339 UTC strings.