No description
  • Python 68.6%
  • Shell 31.4%
Find a file
jimmy b4718f1198
All checks were successful
check / lint (push) Successful in 21s
spike: fire the local check run
2026-08-27 01:43:53 +00:00
.github/workflows chore(ci): shellcheck becomes a hard gate (fix the 5 standing warnings) (#44) 2026-07-27 16:40:31 -05:00
assets The mark: swoosh rooster v3 — assets, README, scaffold favicon 2026-07-08 08:16:30 -05:00
bin roost stats runs the hatchery stacks collector (#69) 2026-08-23 16:59:12 -05:00
docs docs: the creation of roost 2026-08-25 09:59:11 -05:00
tests roost config hands writes to hatchery (#68) 2026-08-23 16:53:37 -05:00
.gitignore stats: run the loc collector (#37) 2026-07-23 09:32:02 -05:00
.spike-trigger spike: fire the local check run 2026-08-26 23:20:30 +00:00
.spike-trigger-2 spike: fire the local check run 2026-08-26 23:25:27 +00:00
.spike-trigger-3 spike: fire the local check run 2026-08-27 01:21:51 +00:00
.spike-trigger-4 spike: fire the local check run 2026-08-27 01:43:53 +00:00
CLAUDE.md fix(status): scheduled refresh pulls before regenerating — hand ledes survive other writers (#47) 2026-07-29 21:01:34 -05:00
README.md roost config hands writes to hatchery (#68) 2026-08-23 16:53:37 -05:00
roostrc.example The roostrc example carries the serve token key (#70) 2026-08-23 17:03:33 -05:00
TODO.md docs: merge fleet power WIP into playbook; clear done TODOs 2026-07-13 14:53:56 -05:00

Roost

Roost — a swoosh rooster on its perch

A self-hosted app platform on one small box. One ARM SBC (or any Debian/Ubuntu machine), Dokku, a Cloudflare tunnel, and a toolbelt of scripts — serving any number of apps at <name>.yourdomain, deployed by git push, behind CGNAT with no public IP and no port forwarding.

Internet ─▶ Cloudflare ─▶ tunnel (dials OUT) ─▶ nginx :80 ─▶ Dokku app containers

New app, one command, ~40 seconds to a live URL:

bin/new-app.sh myapp --static      # or --node, --swift (Hummingbird 2), --board (statusgen)

The roost command

Everything is driven by one dispatcher, bin/roost (add bin/ to your PATH). roost help prints this list; roost doctor diagnoses the setup when anything misbehaves.

Command What
roost new <name> [--static|--node|--swift|--board] nothing → live app in ~40 s
roost route <subdomain> publish a tunnel route via the Cloudflare API
roost status ["message"] collect + validate + deploy the status site (no message: narrative auto-composed from merged PRs)
roost stats run the configured board-stat collectors
roost fleet refresh the fleet board json
roost kick fire the status runner's hourly deploy now
roost rollout [--kick] ff-only pull roost + statusgen on every writer machine after a merge
roost apps / ps [app] / logs <app> [-n N] / restart <app> / config <app> day-2 Dokku reads over ssh. Config writes moved to hatchery config set, which keeps the declaration true; --force keeps the old direct write for emergencies
roost prune [project] [--yes] [--deep] [--caches] reclaim build artifacts (dry-run by default)
roost backup pull pi data to ~/Backups/roost
roost doctor diagnose ssh, token, zone, and tooling
roost ui full-screen terminal: console, monitor, config, docs tabs

Configuration lives in ~/.roostrc (roostrc.example); secrets live in separate chmod-600 dotfiles (~/.cf_api_token, ~/.roost_node_key, ~/.roost_ci_key), never in the rc file or the repo.

What's here

Path What
docs/getting-started.md Prerequisites → first deploy: hardware, accounts, installs, tunnel
docs/tutorial.md Guided tour: deploy an app, board it, operate it — and which of the three repos to touch
docs/playbook.md The operating manual: storage, crons, secrets, accounts, status boards, disk reclaim, and every gotcha learned the hard way
homeauto Smart plugs, bulbs and Home Assistant: wiring, credential locations, rebuild-from-nothing plan (separate repo)
docs/status-events.md Design sketch (future): push-based CI → central ingest → boards + history
bin/roost The dispatcher — every command above
bin/new-app.sh Nothing → live app: Dokku app + domain + scaffold + deploy + route + verify
bin/publish-route.sh Publish a subdomain through the Cloudflare tunnel via API — no dashboard
bin/status.sh The roost status orchestrator: self-update, collect, validate, deploy
bin/fleet-board.py / bin/fleet-alert.py Fleet snapshot board + state-transition desktop/ntfy alerts
bin/node-report.sh Per-node telemetry (load/mem/disk/watts/battery/runner) → pulse /api/nodes, macOS + Linux; launchd/systemd installer alongside
bin/ci-live-report.sh Live CI-run poller (runs on the CI Mac) → the ci-live app; launchd installer alongside
bin/gen-narrative.py Composes the board narrative from merged PRs when roost status gets no message
bin/roost-prune.py / bin/backup-roost.sh Disk reclaim (dry-run default) / nightly storage-mount backups
bin/roost-ui.py roost ui — full-screen terminal in four tabs: console (prompt + streaming commands), monitor (live fleet via pulse), config, docs pager (stdlib only)

Each script carries its own usage/config header — the headers are the authoritative per-tool reference. Tests: python3 -m unittest discover -s tests.

Rendered docs: docs.jimmyhoughjr.net

The reference roost

What this pattern runs in production, on one 8-core / 16 GB Orange Pi:

  • watts — electric cost calculator (EIA rates cron, seasonal modeling)
  • vault — Apple/Google sign-in + per-app user storage (Swift/Hummingbird 2)
  • head2head — measured implementation shootouts (Node vs Swift, bout 1)
  • statusstatusgen boards with git-generated history
  • docs — this repo's docs plus living usage reports
  • a blog, and a hello created by new-app.sh as its living test

Companion projects

  • statusgen — data-driven status boards (and the bare-metal SETUP.md for the locally-managed-tunnel variant)

Built by Jimmy Hough Jr & Claude. Donations appreciated: $jimmyhoughjr