The services model.
The portfolio is being re-grounded on reusable services — single, provider-pluggable capabilities that surfaces compose instead of re-rolling. One messaging gate, one reporting pattern, one off-Cloudflare twin — wired once, called everywhere, swappable underneath.
This page is the substrate map: what a service is here, how a surface wires into one, the three that exist today, and why the model lowers upkeep rather than adding it.
What a “service” means here
A service is one capability behind a stable interface, with the how hidden from every caller. Three properties define it, and a thing isn't a service until it has all three:
- Composable — a surface calls it in one line and never owns the mechanism. Adding a consumer is free.
- Swappable — the provider behind it (which push transport, which storage region, which compute) changes by editing a registry line, not by touching a single caller.
- Observable — its health and output land in one place (status.gf.cx), so you can see whether it's working without reading its code.
Bolt-on per-surface = friction, drift, and surfaces that never get the treatment. Service-backed = the floor rises every time, and the upkeep is one fix instead of N.
The substrate, in one diagram
* providers marked with an asterisk are interface-ready, not yet implemented — each is “a class + a registry line” away, and no surface changes when one lands. The Durability twin is highlighted because it exists to survive the one risk the rest of the stack shares: a single Cloudflare account. The DNS twin carries that same crimson for a reason — DNS is the worst place to be frozen, and a warm deSEC standby with the registrar held off Cloudflare (Porkbun) is what makes the failover actually fireable in a suspension.
The three services today
Kicked off by today's work — the R2→Wasabi mirror and the Linode cost monitor needed a push channel and a status home, and rather than hand-roll both, we extracted the services they revealed.
notify.py
One provider-agnostic gate. Pushover ships; Telegram / ntfy / Slack / email are each a Provider class + one registry line. Severity vocabulary (ok→emerg) maps onto each transport's own priority, so callers never think in Pushover ints.
status.gf.cx pattern
Every job emits a normalised status JSON to the data layer, registers in registry.json, and the hub renders + deploys it. Then the contextual one-liner goes through the messaging gate. Two layers, cleanly split — the value is the sentence, not the numbers.
the off-Cloudflare twin
Everything Cloudflare holds — R2 buckets, DNS, the surfaces — duplicated somewhere CF can't touch. Wasabi mirrors R2 (egress-free restore), Linode bursts compute off-platform, rsync.net holds the parachute, OneDrive relays it, and a deSEC warm-standby — registrar at Porkbun, both off Cloudflare — keeps DNS resolving if CF goes dark. One blast-radius answer, five providers.
r2_wasabi_mirror · linode_status · restic_rsyncnet · gfcx_dnsOn naming: “backup-as-a-service” undersells it — a backup is a file you hope never to open. This is a live duplicate of the Cloudflare-resident portfolio: storage, DNS standby, and burst compute, each off-platform, each restorable cheaply. The service's job is to make the single-CF-account risk survivable.
How a surface wires in
A surface that needs to report does not hand-roll a curl. It writes its status JSON + calls the gate. That's the whole contract:
Scheduling is just launchd pointing at the orchestrator — the R2→Wasabi mirror runs twice daily (10:00 & 18:00 ET), the Linode monitor once (09:00 ET). Both publish a card and push a sentence. Swap the transport, change the cadence, add a fourth provider — no surface notices.
Why the model pays — the ROI
The argument for services is operational, not aesthetic. Managing capabilities gets cheaper as the portfolio grows, where bolt-ons get more expensive:
Upkeep is one fix, not N
The Pushover curl block lived in 4 places and was drifting. Now it's one service. A fix to retry logic or rate-limit handling lands once and every surface inherits it.
Providers swap for free
Replace Pushover with Telegram, or add it alongside: one class + one registry line. Zero caller edits, because the interface is the contract — not the transport.
Advances plug in behind the interface
A better push channel, a cheaper storage region, a faster burst image — each lands behind the same call. The surface that wrote one line in 2026 gets the upgrade without being touched.
Observability is built in
Every service reports to status.gf.cx in the same shape. One place to answer “is it working?” across every surface — no per-surface dashboard to maintain.
Risk is centralised, then answered
The whole portfolio shares one Cloudflare account. The Durability twin makes that survivable for all 14 zones at once — a per-surface backup never could.
New surfaces start finished
Scaffold a subdomain and it inherits messaging, reporting, and the resilience floor by default. “Under-finished” shrinks every time the floor rises — the compounding-kb kernel, applied to ops.
Where to look
- status.gf.cx — the reporting service, live: every job's status card + the contextual sentences
- home.gf.cx — the operating system these services run underneath
- assets.gf.cx — the UI primitives layer (cards, media) — services' visual sibling
- sandbox.gf.cx — where new service-backed surfaces are dogfooded first
notify.py) · reporting (status.gf.cx) · durability (Wasabi · Linode · rsync.net · OneDrive · DNS: deSEC/Porkbun) ·
inherits assets.gf.cx/cards + media · three-layer baseline + release stamp ·
updated