architectural substrate

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:

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

happiness.gf.cx sandbox.gf.cx home.gf.cx / ebay burst-vm rig …future Messaging notify.py · notify_verdict() Reporting status JSON → status.gf.cx Durability the off-Cloudflare twin Pushover Telegram* ntfy* email* status.gf.cx data layer + registry rsync.net · restic Linode pCloud Bunny.net · cdn hosting FOUNDATION — the lynch-pin everything resolves through DNS twin · deSEC warm standby + Porkbun registrar — both held off Cloudflare if CF freezes, this is the one switch that makes everything above reachable again SURFACES — they compose, never re-roll SERVICES — stable interface, hidden mechanism PROVIDERS — pluggable & replaceable

* 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. And it rests on the crimson foundation for a reason — the durability twin's other three providers (Linode, rsync.net, pCloud) are only reachable if DNS still resolves. DNS is the worst place to be frozen, so a warm deSEC standby with the registrar held at Porkbun — both off Cloudflare — is the one switch that makes a suspension survivable. Everything above resolves through it; that is what makes it the lynch-pin, not just another box.

The amber, dashed Bunny.net box is a different state from the red durability providers: it's primary hosting, not a backup copy. noir (the 18+ surface) was pulled fully off the Cloudflare account onto its own domain and now lives on Bunny — the first surface hosted entirely off-platform, so a CF suspension can't reach it at all. It wears its own colour because it isn't a durability twin yet; once Bunny's S3 API leaves closed preview it can also mirror R2 like the others, and it'll take the crimson durability colour then.

The out-of-region distance between these durability providers — and the case for moving the offsite copy from Hong Kong to Zürich — is mapped, with measured round-trip latencies, at srvc.gf.cx/transit.

DNS is the worst place to be frozen — so the twin that keeps it resolving, registrar and all, off Cloudflare is the lynch-pin the rest of the stack hangs from.

The three services today

Kicked off by today's work — the R2 off-site 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.

messaging

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.

notify_verdict("R2 offsite restic", summary, "green", surface="…")
reporting

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.

data/<slug>-latest.json → status.gf.cx → notify()
durability

the off-Cloudflare twin

Everything Cloudflare holds — R2, DNS, the surfaces — duplicated where CF can't touch it. The cloud copy is restic on rsync.net: an encrypted twin of R2 on a wholly separate provider, with egress-free restore (it replaced the retired Wasabi mirror). Linode bursts compute, pCloud holds a second restic mirror so no single storage vendor can hold the corpus hostage, and a deSEC warm-standby — registrar at Porkbun, both off Cloudflare — keeps DNS resolving if CF goes dark. That DNS twin is the lynch-pin the rest hang from: nothing above is reachable unless DNS still resolves.

restic_rsyncnet · linode_status · gfcx_dns

On 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.

The emerging fourth — private access

The durability twin keeps the portfolio's data alive where Cloudflare can't reach it. Tailscale is its access sibling: it keeps the new homelab box — the warm-tier Immich photo server — privately reachable off-platform too. A WireGuard mesh (a tailnet) gives our phones and laptops an encrypted path to the box from anywhere, with zero inbound holes in the router and nothing exposed to the public internet. tailscale serve already terminates real HTTPS for Immich at immich.porcupine-skate.ts.net — the off-Cloudflare access twin to the off-Cloudflare durability twin, same survivability logic applied to reach instead of storage.

It's the same provider-pluggable shape: private mesh access is a capability a surface reaches for, not a router config it hand-rolls. Today it serves one box; as the homelab grows, Tailnet Lock (peers co-sign new devices, so even a breached coordination server can't graft one on) and a subnet router (reach gear that can't run a client) extend the same access service — written up on the vault software page.

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:

# 1 · emit the data-layer status (status.gf.cx renders + deploys it) write("~/Code/status.gf.cx/data/linode-cost-latest.json", payload) # 2 · hand the contextual sentence to the messaging gate from notify import notify_verdict notify_verdict("Linode burst-VM", "Spent $0.48 for 2.5h, 6 days ago — nothing running now.", "green", surface="linode-burst-cost") # Pushover today. Add Telegram? One class in notify.py — this line never changes.

Scheduling is just launchd pointing at the orchestrator — the off-site restic backup to rsync.net runs daily, 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.

Pay as you go — the cost posture

The model has a money side, and it's deliberate: minimise standing cost, keep exit cheap. Almost every provider is metered, not committed — no regional floors, no seats, no always-on rent — and the single flat line we do keep (durable backup) is chosen on purpose, not defaulted into. An idle surface costs essentially nothing; the bill tracks what actually got used, not a forecast of it. Capacity is something you reach for when a request arrives, then release.

The noir move is the cleanest worked example. It used to sit on Wasabi, which bills a 1 TB regional floor plus a 90-day minimum storage duration — fixed rent for a near-empty shelf. It now lives on Bunny.net, metered to about $1/mo for what it serves, on its own domain entirely off the Cloudflare account and access-locked to a single home IP — the right posture for an 18+ surface whose blast radius shouldn't touch the rest of the portfolio. Same content, a fraction of the cost, a smaller threat surface.

The same discipline runs through the stack: R2 and Workers ride free tiers and bill per-request past them; Linode bursts from a baked image and is torn down after the job, turning an ~$80/day always-on rig into pennies per run; the deSEC DNS standby is free. rsync.net is the one deliberate exception — a flat 1 TB plan at $90/yr ($7.50/mo), billed the same whether restic parks ~19 GB (today) or nine hundred — accepted on purpose, because durability is the thing you don't meter, and it stays egress-free so restore and exit still cost nothing. It's the upkeep argument in dollars — keep the floor near-zero everywhere it can be, pay a flat line only where it buys survival, so adding a surface or sitting idle never shows up as a variable cost. Tailscale fits the same floor: the private-access mesh runs on its free tier, so reaching the homelab privately from anywhere adds $0/mo.

Where to look

srvc.gf.cx · architectural substrate · authored 2026-06-13 · three services live: messaging (notify.py) · reporting (status.gf.cx) · durability (rsync.net/restic · Linode · pCloud · DNS: deSEC/Porkbun) · emerging fourth: private access (Tailscale — Serve live for the Immich homelab) · inherits assets.gf.cx/cards + media · three-layer baseline + release stamp · updated · kubrick.gf.cx listed as US video CDN · noir hosted off-platform (Bunny, metered) · pay-as-you-go cost posture
Built with6 tools · scaffolded · three-layer baseline ▸ pattern · built-with provenance footer