Backend for belomaxorka.dev: guestbook, live chat, drawings, Last.fm proxy (Go)
  • Go 99.3%
  • HTML 0.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Roman Kelesidis 2f6741905a
All checks were successful
ci / test (push) Successful in 48s
ci / deploy (push) Successful in 33s
A project added to the site is fetched from GitHub at once, not half an hour later
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]>
2026-10-30 00:17:19 +07:00
.forgejo/workflows No fastfetch, one infrastructure page; a short README 2026-10-13 00:22:34 +07:00
cmd/site-api The site's projects as GitHub describes them: .Projects for a projects rack 2026-10-30 00:04:38 +07:00
internal A project added to the site is fetched from GitHub at once, not half an hour later 2026-10-30 00:17:19 +07:00
.editorconfig First version: render the rack page with live data 2026-10-11 01:30:50 +07:00
.gitattributes First version: render the rack page with live data 2026-10-11 01:30:50 +07:00
.gitignore First version: render the rack page with live data 2026-10-11 01:30:50 +07:00
go.mod Go 1.27.3: compromised intermediate certificates are distrusted 2026-10-22 14:45:08 +07:00
go.sum Gatus's history in one poller: each check asked once, a few at a time 2026-10-12 18:09:15 +07:00
LICENSE Initial commit 2026-10-10 16:18:44 +07:00
README.md The site's projects as GitHub describes them: .Projects for a projects rack 2026-10-30 00:04:38 +07:00

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.

License

MIT