The deploy repository for the Owner's headless homelab box (the AWOW AK41 always-on core): a zero-touch, always-on Docker stack — split-horizon DNS (Technitium), reverse proxy + TLS (Caddy), the NagLight life-tracker behind Google sign-in (oauth2-proxy), Actual Budget, LAN-only observability, and the bash backup service — plus an opt-in tier-2 catalog (photos, media, music, podcasts, home automation, passwords, …) behind compose profiles, off by default — with an unattended Ubuntu autoinstall image and full LAN remote management. This repo holds configuration only (compose, Caddy, autoinstall, provisioning); the application code lives in its own repos (NagLight, Finance-Auditor, …).
Public-facing repo (Q10.6): only
*.exampletemplates are tracked. No real secret, password, hash, email, or LAN detail is ever committed. Copy each*.exampleto its real name and fill it in locally.
Everything lives under stack/ — start with
stack/README.md:
| Service | Role | Auth |
|---|---|---|
| Technitium | split-horizon LAN DNS + recursion + blocklists | admin login |
| Caddy | reverse proxy + automatic TLS | — |
| oauth2-proxy | Google sign-in for the tracker | Google OAuth |
| tracker (NagLight) | the multi-user life tracker | via oauth2-proxy |
| Actual Budget | finances | basic_auth |
| Uptime-Kuma · Dozzle · (ntfy) | LAN-only observability | LAN-only |
| Finance-Auditor | daily finance audit pipeline (triggers Actual's bank sync, posts de-identified status to the tracker) — profile-gated until its own G-Final | bridge-internal only |
| tier-2 opt-in catalog | Immich/PhotoPrism · Jellyfin · Navidrome · Audiobookshelf · Vaultwarden · Home Assistant/Mosquitto · Syncthing · FreshRSS/Mealie/Homepage · diun — compose profiles, off by default (stack/README.md §9) | per-service |
Remote management of the headless box (SSH, Cockpit, the reimage ladder) is in REMOTE_MANAGEMENT.md.
There is no single "run" command — this repo produces a deploy image, not an
app. To bring the stack up on a Docker host, follow
stack/README.md: resolve the tracker image with
scripts/ensure-local-images.sh (SR-006 chain: present → sibling ../NagLight
build → declared TRACKER_PUBLIC_IMAGE) → docker compose up -d. To see the
whole thing connect up virtually with zero real secrets, run the V1 sim
(sim/README.md) — every external interface has a fictional,
committed stand-in (Dex for Google, internal CA for ACME, Samba fixtures for
Mini-serv). The root run.{cmd,sh,command} launchers are inert (this is not a
launchable product).
python scripts/check.py # config-coverage + doc + registry gates (G1)
python scripts/validate_config.py # env/Caddy/file coverage + YAML parse
Three progressively-more-real gates precede flashing the real AWOW:
| Gate | What | Status |
|---|---|---|
V1 — sim/ |
full stack on WSL2/Docker vs. a mock OIDC provider (Dex stands in for Google); split-horizon DNS, multi-user isolation, and the bash backup service + restore drill all exercised for real — plus mock-shim call-contract legs for drive power (run-drivepower-sim.sh) and docker-volume sources (run-volume-sim.sh, SR-013) |
GREEN — all checks pass |
V2 — launcher (Personal repo, MINI_PC_Setup/) |
the Mini-serv rebuild launcher validated in Windows Sandbox (real task-import/reg/share rungs) | GREEN |
V3 — vmtest/ |
the REAL autoinstall booted in a local Hyper-V VM — every container baked into the ISO payload (Q10.9 B+), zero registry pulls at first boot | scripted + smoke-tested; the boot itself is the Owner's step (needs elevation + the Hyper-V feature) |
Dev-box container runtime (WSL2 + docker-ce) is installed and verified — see docs/status.md for the full ledger, including exactly which images are pinned and their digests.
vmtest/'s scripts (build-seed.sh, etc.) are VM-test-only — they
substitute throwaway SIM secrets and an ephemeral SSH key on purpose, so
never flash vmtest/.out/*.iso onto real hardware. To build the actual
deploy image:
- Fill in a real
stack/.envfrom.env.example(every knob is documented there and in stack/README.md §2 — mind the QUOTING RULE in its header: values with spaces must be double-quoted) and put your real SSH public key intostack/autoinstall/user-data(§"Build the USB"). Two opt-in decisions live here too:COMPOSE_PROFILES(which tier-2 services come up at first boot, stack/README §9) and the commentedvolume:lines in the backup config (stack/backup/backup.env.example) so the box's own state — the finances — joins the backup (SR-013). - Filling
.envtoday is still a manual step. The automated secret-handoff design inPersonal\SECRET_HANDOFF.md(a sibling repo; extends the Owner's existing DPAPI credential pattern) was ratified 2026-07-25, but the tooling is not written yet — so hand-fill for now. Once built it materializes.envfrom Personal's credential store, and pathing values come fromPersonal\deploy\storage-map.md. - Run
vmtest/export-images.sh(it reads the real pinned tags in.env.example, not sim values) so the real USB also carries every core container image baked in (Q10.9 B+) — see stack/README.md §3. Tier-2 opt-in images are excluded from the bake by default; passEXTRA_PROFILES="…"to include an enabled set (SR-012). - After first boot, the one-time in-app authentications (not
.envvalues): the Google OAuth client above is env-side, but Actual's SimpleFIN bank sync is configured inside Actual's own UI and stored in theactual_datavolume (stack/README §2 manual steps). What survives a container update vs a reimage — credentials never live in containers; they live in volumes +.env, both recoverable — is spelled out in REMOTE_MANAGEMENT.md "State & credentials".
This repo follows a gated, requirement-traced process. The working brief is AGENTS.md; the method is docs/process.md. Start with the code map in docs/architecture.md and the current state in docs/status.md.