Skip to content

github-backup: estate mirror system (laptop mirrors + dedicated App + Worker webhooks + weekly sweep) #3215

Open

Description

@dezren39

System of record: the docs in this repo — infra/github-backup/ (README + docs/RUNBOOK.md). This issue summarizes and tracks rollout; the repo teaches.

Related: #3205 (per-repo hygiene census / standard task).

What it is

Bare-mirror backup of Drew's GitHub estate on the laptop (~/backups/github/<owner>/<repo>.git), decided 2026-10-31:

  • Scope: Tier C — all non-forks + forks <500 MB across 17 owners + force-includes (818 mirrors); nixpkgs family stored once via git alternates pool; 7 passive giants excluded by ruling (upstream is their backup); gaps recorded (dezren39/edsebooks HTTP 451, vertexinc SAML).
  • Freshness: dedicated GitHub App hatch-repo-backup (metadata:read + contents:read; push + repository events; its own rate-limit pool) → Cloudflare Worker receiver (HMAC-verified, KV queue) → laptop drainer every 5 min (15-min per-repo coalescing).
  • Floors: daily fetch of all mirrors; weekly reconciliation sweep (new repos auto-mirrored per config tier rules; new giant forks land as pending-Drew-review).
  • Secrets: Bitwarden by name only (github-app.backup-*), never in repo/tree/chat.

Rollout status

Restore/DR procedures: RUNBOOK §8–§9.

Acceptance criteria

  • Every rollout checklist item above is complete: PR github-backup: estate mirror system (scripts, units, Worker, runbook) #3214 merged, units installed on the laptop from ~/git/code, the initial clone fleet (818 mirrors) verified against MANIFEST/FAMILIES, the Worker deployed, the hatch-repo-backup App created and installed, and an end-to-end webhook proof (ping + one real push fetch) recorded.
  • The first weekly reconcile report has been reviewed and any gaps it names are filed or recorded here.
  • Restore/DR procedures (RUNBOOK §8–§9) have been verified against the live mirrors.

Activity

  1. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    Rollout update (2026-10-31 16:05 CDT):

  2. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Status update (2026-10-31, verification close-out + Drew's naming/deploy directives):

    Clone fleet: complete and verified — 818/818 mirrors on the laptop (29 GB tree), MANIFEST regenerated (818 mirrored, 0 missing), fsck sample 5/5 clean, working checkout cloned from an alternates member, fleet HEADs consistent (nix exact; hatch-personal/code are ancestor-stale by 35/44 live commits, closed by the daily fetch). Two retry passes with the system's own clone_all.py cleared all 10 pass-1 failures (all network mid-transfer drops). Checklist boxes for PR merge, units installed, and clone fleet are therefore now true.

    Naming (Drew, 2026-10-31): the public name is developing-today-backup. The GitHub App was never created under any name (API 404 for both slugs; no app-credential secrets in Bitwarden), so it will simply be created under the new name — the slug is available. The Worker WAS live as hatch-repo-backup-webhook; PR #3280 renamed it repo-side and redeployed it as developing-today-backup (live, version tagged git-307a2501ca65), the laptop drainer was repointed via Bitwarden github-app.backup-worker-url and verified on its next tick, and the old Worker was deleted (old URL 404s).

    Deploy method (Drew, 2026-10-31): before #3280 the Worker deployed by bare wrangler commands from the runbook. It now follows the repo deployment standard's shape: just deploy-developing-today-backup → nix run .#developing-today-backup-deploy → secrets from Bitwarden/SOPS → version upload (git-SHA tag) → separate deployment at 100%, with github-backup-units / github-backup-fetch / github-backup-reconcile recipes alongside. Tool gap recorded in #3280: the deploy script drives wrangler's versions commands, not the standard's cf CLI, because this Worker is wrangler.toml-shaped and dependency-free while cf's flow requires the TypeScript cf/config format.

    Still open: the App creation click sequence (RUNBOOK §3, now for developing-today-backup), App installs, the first genuine GitHub webhook delivery proof, and defects #3219 / #3229 (verified still present in deployed + main code).

  3. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Design-architect pass 29 (2026-10-31) — routing note: the eight defect filings against this system from its first day (#3219, #3229, #3233, #3238, #3240, #3250, #3257, #3270) are unified under design #3282, "a mirror is what it can prove — one validity, outcome and alarm contract for the github-backup estate mirror" (design doc: files/designs/code-github-backup-mirror-contract.md in the keep-the-four-repos-healthy goal workspace). The eight are adopted as #3282's phases (no parallels); a closing audit (#3283, gated) will post completion evidence here. One rollout interaction to expect: when Phase 1 (#3238's resolvability predicate) lands, the first reconcile/fetch afterwards will recount any bare-skeleton mirrors as missing and re-clone them — while the initial 818-mirror clone is still in flight that recount is the correction working, not a regression. This umbrella's rollout checkboxes are unaffected.

  4. added
    kind/infrastructureInfrastructure / host-portability work
    source/humanFiled directly by a human
    effort/lLarge: multi-day or open-ended; expect staged check-ins
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Nov 1, 2026
  5. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Intake review

    Still live: rollout is mid-flight — PR #3214 is the system PR named in the checklist, and merged PR #3280 (github-backup rename/deploy standard) is a follow-on, not the rollout completing. No merged PR closes this tracker.

    Related: #3205 (per-repo hygiene census / standard task); PR #3214 (system code + config + units + Worker + runbook); PR #3280 (follow-on rename/deploy).

    Notes / risks: the remaining checklist is mostly laptop-side state (units installed, 818-mirror fleet, Worker deployed, App installed, webhook proof) — verifiable only from the laptop and the Cloudflare dashboard, not from this repo. The App creation click and org installs are the human-gated steps.

    Suggested approach: work the checklist in order; record the end-to-end webhook proof and the first weekly reconcile review as comments here.

    Labels applied: kind/infrastructure, source/human, status/needs-triage, effort/l, meta/enriched (priority/low already present). Acceptance criteria appended to the body, drawn from the rollout checklist.

  6. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    App-creation attempt via RustDesk (2026-10-31 evening): blocked before any GitHub submission — the App does not exist yet.

    What happened: the sandbox-side RustDesk client (1.4.5, nix build) cannot render its Flutter UI in this sandbox. Every GL configuration fails at GTK GLArea init — verbatim: Failed to initialize GLArea: No available configurations for the given pixel format (X11) / No GL implementation is available (Wayland). Verified with an independent system-libGL probe: the sandbox's Xvfb (with GLX enabled) advertises GLX 1.4 with 80 fbconfigs and the Ubuntu Mesa client works against it — but every nix-linked GL client (the RustDesk closure, nix glxinfo) fails identically on all servers tried (system Xvfb, TigerVNC Xvnc, nixpkgs Xvfb ±iglx — the last two don't advertise GLX at all). Root cause: the nix Mesa/GLVND stack cannot create GL contexts in this sandbox (the RustDesk closure ships libglvnd with no usable vendor path here); system Mesa can't be mixed into nix-libc processes (GLIBC_PRIVATE symbol clash). A headless wlroots/cage + EGL attempt hit the same wall. No RustDesk session to the laptop was ever established, so no browser step ran and nothing was submitted to GitHub.

    Prepared and in place for a retry (e.g. from a client with a working GL stack, or Drew at the laptop):

    • Laptop /tmp/appflow/: form.html (manifest form generated from the repo manifest — name developing-today-backup, contents+metadata read-only, push+repository, webhook https://developing-today-backup.developing-today.workers.dev/github, redirect localhost:8765/callback) and capture.py (saves the /callback path to /tmp/appflow/code_path.txt). Helper servers were started, verified (both ports 200), and shut down after the attempt — restart per RUNBOOK §3 when retrying.
    • Remaining once a working client exists: submit form → convert code (short-lived) → webhook-secret + just deploy-developing-today-backup → install on dezren39 + 15 admin orgs → install-report → push/delivery/drain webhook proof.
  7. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    GitHub App created + installed + webhook proof — 2026-10-31 (CDP session on laptop)

    App: developing-today-backup, App ID 5562183, owner @dezren39. Created via the manifest flow (manifest verified byte-exact: metadata+contents read-only, events push+repository, webhook https://developing-today-backup.developing-today.workers.dev/github). Sudo cleared via GitHub Mobile approval (match number relayed live).

    Credentials — recovered from a convert crash (bug filed by this comment)

    app_setup.py convert exchanged the code fine but crashed storing the private key: bws_set passes the value as a bare CLI argument and the Bitwarden CLI's parser rejects values beginning with - (the PEM header) — error: unexpected argument '-----BEGIN RSA PRIVATE KEY-----'. app-id / client-id / client-secret were stored before the crash; the conversion PEM was unrecoverable (GitHub shows it once).
    Recovery, completed same session:

    • Generated a new key pair in App settings → Credentials; fingerprint SHA256:j5HZDaG+2pWAuVbNdTwuNE4M8WpazhQqXcRZCNvD8lc=.
    • Stored it in Bitwarden as github-app.backup-private-key using a -- argument separator (probe secret round-tripped byte-exact first); stored value sha256 matches the downloaded file (43f2eb26…); download shredded.
    • Deleted the orphaned conversion key (SHA256:8qYjT+…); exactly one key pair remains.
    • Fix owed in app_setup.py: make bws_set insert -- before the value (or use bws secret edit/create value-safe form) so convert can't crash on PEM storage again.
    • JWT check: PyJWT isn't installed on the laptop, so install-report was run as an equivalent stdlib+openssl RS256 mint → GET /app/installations returned 200.

    Webhook secret

    app_setup.py webhook-secret run (fresh value in Bitwarden), the same value set on the App (General → Webhook → Change secret; save confirmed), and just deploy-developing-today-backup redeployed the Worker (version acbb41a4, tag git-efc4e182d400) re-setting Worker secrets from Bitwarden. /health ok.

    Visibility change — decision flagged

    The manifest's public: false makes the App installable only on the owner account (GitHub routes org install attempts back to the personal installation; the Install App chooser listed only dezren39). To complete the planned org installs the App was made public (Advanced → Make public). Consequence to note: any GitHub account can now install it on their own repos; such deliveries are signed with the App's webhook secret, so the Worker would accept and queue foreign repos. Recommend the drainer enforce the owner allowlist (the reconcile plan is already owner-scoped). Flip back to private is one click in Advanced if ever desired — but org installs require it to stay public.

    Installations — 16/16, all "All repositories" (verified via App JWT)

    dezren39 177385379 · 3d0d 177388121 · Cloistered 177388175 · chip-rom 177388265 · chiplox 177388292 · Clustered-Computing 177388628 · IDEXX-UTDesign 177388652 · SpongeScribe 177388683 · developing-today 177388707 · developing-today-fork 177388738 · developing-today-forks 177388761 · dezren3 177388791 · dezren39-dev 177388812 · drewry-pope 177388833 · sleep-atomic 177388857 · twitter-base 177388879.
    Sweep-only (no admin): NixOS, vertexinc — covered by reconcile polling as designed.

    Webhook proof (timestamps CDT, 2026-10-31)

    • Creation ping delivery 21:57:23; installation.created for dezren39 22:04:14.
    • Push delivery 2bd5d85e-d5a4-11f1-82c5-8d7b8483596f (dezren39/openclaw-personal) 22:28:25 → Response 202 from the Worker (signature verified with the synced secret).
    • Drainer (5-min timer): 22:07 run acked 4 events, fetched dezren39/openclaw-personal; 22:27 run acked 21, fetched developing-today/code + developing-today/openchamber + dezren39/openclaw-personal, 0 failed (mirror HEAD for openclaw-personal advanced to its 22:27:28 commit).
    • Coalescing observed as designed at 22:32 (11 events held for code + openclaw-personal while both are actively pushing; nothing failed or dropped).
    • No dezren39/hatch-personal push occurred during the observation window (GitHub HEAD static), so no hatch-personal-specific trace yet; its installation is in place, the identical path is proven on three sibling repos, and the daily fetch timer covers it regardless.

    Laptop helper servers (form :8766 / capture :8765) shut down. Session notes: the laptop network flapped repeatedly tonight (ERR_NETWORK_CHANGED) — several page reads and one install confirmation were lost to it, all recovered by retry; drewry-pope's first install attempt actually completed despite the error page (installation 177388833).

  8. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Update (2026-10-31 23:12 CDT): the drainer owner allowlist flagged as owed at App creation is done — #3326, PR #3328 (merged 72e347609), deployed to the laptop, and proven live with a hand-injected foreign webhook (DRAIN-DROP logged, no clone attempted, drain Result=success). The public-App foreign-install exposure is closed at the drainer; details on #3326.

  9. 33 remaining items

  10. developing-today commented on Nov 8, 2026

    @developing-today
    Contributor

    state: handed_off
    queue: repo-health-code-pilot
    task: 12101 (triage)
    by: lane code-k (worker repo-worker-code)
    at: 2026-11-08

    Picked up and revalidated fresh: issue #3215 is OPEN, no hard-stop labels (held, user/owned, do-not-merge, do-not-auto-edit absent). Execution-lane assignment returned opencode — handed off to the laptop opencode claimant, which launches the bound session that drives task 12101 to its terminal state. This lane does not work it in parallel.

  11. dezren39 commented on Nov 8, 2026

    @dezren39
    MemberAuthor

    Partial verification — staying open (2026-11-07 done-task-only audit, tracking: #7016).

    Queue task(s) [8202] were marked done, and some work did land:

    No merged change closes this issue's full scope, so the done status is not treated as completion. Remainder re-queued as triage task 11667.

  12. developing-today commented on Nov 8, 2026

    @developing-today
    Contributor

    🤖 Agent note · handed off

  13. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 8, 2026
  14. developing-today commented on Nov 8, 2026

    @developing-today
    Contributor

    🤖 Agent note · handed off

  15. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

effort/lLarge: multi-day or open-ended; expect staged check-inskind/infrastructureInfrastructure / host-portability workmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/lowNice to have; do when convenientqueue/claimedQueue worker lifecycle statesource/humanFiled directly by a humanstatus/in-progressQueue worker lifecycle statetask/triageQueue packet kind: verify, reproduce, and route this itemwork/assignedQueue worker lifecycle state

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions