- Go 99.3%
- HTML 0.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
The github-repos source polled every 30 minutes whatever the site listed, so a new project showed "github is silent" until its next round. Now a reload of the site that changes content/projects.yaml kicks the source: a poller's Kick wakes its own loop, so the fetch is not one more running beside it and an older answer cannot land last. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
| .forgejo/workflows | ||
| cmd/site-api | ||
| internal | ||
| .editorconfig | ||
| .gitattributes | ||
| .gitignore | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| README.md | ||
site-api
Backend of https://belomaxorka.dev, written in Go. It renders the pages from the site repository's templates with live data, serves htmx fragments for the units that update themselves, the guestbook, and the site's static files.
The primary repository is on my Forgejo: https://git.belomaxorka.dev/belomaxorka/site-api
Sources
| Source | What for | Refreshed |
|---|---|---|
| Koito | now playing, recent listens, the cover (the LCD colours follow it) | 20 s |
| Gatus | a lamp per check; outages and uptimes for the log and the monitor | 30 s / 5 min |
NUT (upsd) |
the UPS unit | 30 s |
| Proxmox API (read-only token) | the /lab rack: the node and the guests; finished backups for the log | 30 s / 5 min |
| Wakapi public stats | coding time for the log | 10 min |
| otoring | the webring neighbours' names | 30 min |
| GitHub (the profile's public calendar fragment and public events, no token) | the activity unit: a year of contributions, the streak, the last push | 30 min |
GitHub REST API (public, GITHUB_TOKEN optional) |
the projects rack: each repository of the site's content/projects.yaml, its description, licence, topics, stars, latest release and last commit |
30 min |
| Telegram bot | profile photos; guestbook moderation | 6 h |
Claude Code reporters (infra claude-tokens/) |
the token meter: tokens burned by day and model, priced with content/tokens.yaml |
pushed every 10 min |
Every source is polled in the background and keeps its last good answer, so a page never waits for another service and a service being down never breaks the page.
Page views go to a self-hosted Umami from the server (internal/umami), with the visitor's address
and agent in the payload: they count with JavaScript off, in the light version and over Tor, bots left out as by the
visit counter, and the page loads nothing from elsewhere. A slow Umami drops views rather than holding pages up.
The infrastructure's page (infra.*) goes to Umami too, under its own host, but not to the visit counter.
Routes
| Route | |
|---|---|
GET /, GET /{rack} |
a rack of the row from content/racks.yaml; other names are static files |
GET /{rack}?open={unit} |
the rack with that unit pulled out |
GET /fragments/{name}, /fragments/unit/{unit} |
units and pulled-out units for htmx |
GET /events |
the page's live stream (Server-Sent Events) |
POST /events/beam, /events/visit |
a visitor's flashlight; a rack switched to without a reload |
POST /guestbook, GET /gb/drawings/{id}, GET, POST /gb/admin |
the guestbook and its moderation |
GET /avatars/{id}.jpg |
a mirrored Telegram profile photo |
POST /api/tokens |
a machine's ledger of tokens for the meter, with TOKENS_TOKEN as a bearer token |
GET /i/{id}.jpg, .gif |
a picture of infra.belomaxorka.dev, cut square, without EXIF |
GET /sitemap.xml, GET /healthz |
the sitemap; JSON with the version and every source's state |
| anything else | a static file of the site (templates/, content/, i18n/, tools/ and dotfiles are not served) |
Other hosts: lite.* renders the same racks with the site's lite template for old browsers, infra.* renders
/ with its infra template.
The contract with the site
The site repository owns the markup, styles and scripts; this repository owns the data. Templates get a web.Page
(internal/web/page.go) and the functions in internal/web/funcs.go;
texts come from the site's i18n/<lang>.json.
Templates are reloaded after the site changes, without a restart. Before new templates replace the working ones,
site-api renders every page with them (with live data, with none, and with made-up data from every source); a page
that fails keeps the previous version running. site-api -check -site <dir> does the same once and exits with 1 on
the first error: each repository's CI runs it against the other's main.
Run locally
git clone https://git.belomaxorka.dev/belomaxorka/belomaxorka.dev ../belomaxorka.dev
go run ./cmd/site-api -site ../belomaxorka.dev -addr 127.0.0.1:8090
Configuration
Flags, or environment variables with the same meaning:
| Flag | Variable | Default |
|---|---|---|
-addr |
SITE_ADDR |
:8090 |
-site |
SITE_DIR |
site |
-state |
STATE_DIR |
state |
-public |
PUBLIC_URL |
https://belomaxorka.dev |
-tz |
SITE_TZ |
Asia/Barnaul |
-koito, -koito-public |
KOITO_URL, KOITO_PUBLIC_URL |
https://fm.belomaxorka.dev |
-gatus, -gatus-groups |
GATUS_URL, GATUS_GROUPS |
https://status.belomaxorka.dev, Website,Services,Infrastructure |
-gatus-failures, -gatus-successes |
GATUS_FAILURES, GATUS_SUCCESSES |
3, 2 (Gatus's failure- and success-threshold: the outages shown are the ones it alerts about) |
-wakapi, -wakapi-user |
WAKAPI_URL, WAKAPI_USER |
https://waka.belomaxorka.dev, belomaxorka (empty URL: off) |
-github-user |
GITHUB_USER |
belomaxorka (empty: off) |
-ring, -ring-slug |
RING_URL, RING_SLUG |
https://webring.otomir23.me, belomaxorka |
-nut, -nut-ups |
NUT_ADDR, NUT_UPS |
off, myups |
-umami, -umami-website |
UMAMI_URL, UMAMI_WEBSITE |
off (both needed: the base URL and the website id) |
-pve, -pve-ca, -pve-tls-name |
PVE_URL, PVE_CA, PVE_TLS_NAME |
off |
-tg-api, -tg-user |
TG_API, TG_USER_ID |
https://api.telegram.org, 669508248 |
-check |
render every page with the site's templates and exit |
Secrets come only from the environment: GB_ADMIN_TOKEN, TG_BOT_TOKEN, TG_PROXY, PVE_TOKEN, TOKENS_TOKEN, GITHUB_TOKEN (optional: without it GitHub allows 60 requests an hour, three per project every 30 minutes).
Deploy
A push to main runs CI (.forgejo/workflows/ci.yml): gofmt, vet, tests, -check against the site's main, then a
static linux binary is shipped to the server, which switches to it and rolls back if /healthz does not come up.