Skip to main content

Learning

PIXEL is a self-hosted family media stack — one Hetzner bare-metal box, Tailscale as the perimeter (no app-layer auth), Caddy as the only edge. It onboarded cleanly as PETROVA governed child #31, and the scaffold→deployment-readiness pass surfaced three durable lessons. 1. Dual-tracking is the submodule contract, not a chore. PIXEL is both a standalone repo (github.com/devarno-cloud/pixel) and a parent gitlink in devarno-cloud. Sequencing matters: land the child’s full tree (incl. the MR-10 verify-round artifact) before the parent captures the gitlink, or you pay a double ref-bump. The parent commit is topology-only — .gitmodules + a decision record + the gitlink — and fast-forwards into main. 2. The build is the test. Only chat/ (Axum/Tokio) is custom code; everything else is a pinned upstream image. With no Rust toolchain in the authoring environment, docker compose build is the real compile-check — CI runs cargo build/test + a compose validate, but the local build is the gate before deploy. 3. Know which “latent migrate gap” bugs you’ve already avoided. Unlike airlock (which needed a dedicated migrate runner bolted on later), PIXEL’s chat service runs sqlx::migrate!("./migrations") on startup in main.rs — migrations are self-applying. One less deploy-day surprise. Deployment shape (no CD yet): CI is build/test only; first deploy is manual SSH — provision Ubuntu 24.04, install Docker + Tailscale + UFW (default-deny, 80/443 on tailscale0 only), mount /mnt/media, fill .env, docker compose up -d. Two functional frontends go live: the apex RADIO ARNO landing page (static, plays the Icecast radio-arno.mp3 mount) and the chat client (/ws WebSocket → Postgres → ntfy push). Two documented non-blockers to expect on first boot: radio/index.html hardcodes the domain (find-and-replace required), and Collabora’s internal URL only settles after its first boot. Both are noted in the README so deploy-day doesn’t read them as failures. The single push seam (notify_ntfy()) is deliberately isolated — swap it for Web Push/FCM later without touching the rest. Same discipline as the rest of the stack: one box, one edge, one perimeter.