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. 9 remaining items

  10. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off (task 8202)

  11. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/queuedHas a queued task in the Supabase agent queue
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 3, 2026
  12. developing-today commented on Nov 3, 2026

    @developing-today
    Contributor

    agent-note:v1
    state: triaged (coverage pass)
    Coverage pass 2026-11-03: reviewed in the fleet-wide per-issue pass (drift / overlap / already-handled / stale / blockers / ordering). Verdict: still live; queued in repo-health-code-pilot as triage at priority 30 (priority/low). If this is already handled at current main, the triage closes it with evidence; if it needs splitting, the triage files the split.
    Found via: coverage pass 2026-11-03

  13. removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 3, 2026
  14. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    state: handed_off
    task: task 8202 (triage, repo-health-code-pilot, lane code-l)
    worker: repo-worker-code
    issue: #3215

    Execution lane assigned: opencode. Handed off to the laptop claimant. Resume context (from the prior attempt's recorded findings): triage evidence is gathered — umbrella still live, 818 mirrors verified, one new defect found (github-backup-drain.timer monotonic anchor lost in the #3720 home-manager migration; drain path dead since 2026-11-01). Remaining steps: file the prepared drain-timer defect issue (~/hatch/state/queue-8202-drain-timer-issue.md), post the triage evidence comment here, complete task 8202 as triaged.

  15. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

    • Agent: queue worker (repo-worker-code), lane code-h
    • Session / ID: task 8202
    • Found via: queue claim
    • Task: Triage issue github-backup: estate mirror system (laptop mirrors + dedicated App + Worker webhooks + weekly sweep) #3215 at current main — handed off to the opencode execution lane; the laptop claimant launches the bound opencode session, which drives task 8202 to its terminal state. Claim labels were already in sync on pickup (queue/claimed, work/assigned, status/in-progress).
    • Expectation: effort l; done = triaged at current main with evidence, path scoped or closed with evidence.
    • Links: queue task 8202 (repo-health-code-pilot)
  16. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    Launched queue task 8202 for execution in the bound OpenCode session ses_e5e5cb53c62ffeGclCLkRvFj72.

  17. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    Triage at current main — 2026-11-03

    Still live; do not close. Verified issue state OPEN via GitHub and compared repository main (dcca6cd510866556a00dedb2c92b7d21cf817e2b). PR #3214 and follow-on #3280 are merged; the dedicated developing-today-backup App is installed on 16/16 targets and its ping/push→Worker→laptop-drain proof was recorded above. Laptop verification recorded 818 mirrors, 0 missing, and the 2026-11-02 reconcile report (one inventory error: dezren39 SAML). The current acceptance criteria are not complete.

    New live blocker: standing webhook drain is not running

    The laptop’s github-backup-drain.timer last triggered 2026-11-01 22:07:10 CST; at the 2026-11-03 verification it showed NextElapseUSecMonotonic=infinity, Trigger: n/a, and an empty drain-service journal. This coincides with the home-manager unit migration in #3720. The evidence and scoped remediation are filed as #5157: replace the monotonic-only OnBootSec + OnUnitActiveSec schedule with a calendar schedule (OnCalendar=*:0/5), reload/restart the user timer, verify a finite next elapse and a successful drain tick, and add a runbook/test assertion that timers retain a finite next elapse. Search found no existing issue for this distinct timer regression; it is not covered by the phases of #3282.

    Order: address #5157 before treating webhook freshness as continuously operational or closing this umbrella. No code-level dependency blocks that scoped fix; completion evidence requires the laptop unit update/reload and observed drain tick. Then finish the remaining umbrella acceptance: review the first weekly reconcile report and file/record any gaps; verify restore/DR procedures in RUNBOOK §§8–9 against live mirrors; and complete closing audit #3283, which is gated on the #3282 phases. The inventory error and the fact that the 818-mirror count alone is not restore/DR proof remain visible until those checks are done.

  18. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    Correction / precision on #5157: the live failure evidence remains NextElapseUSecMonotonic=infinity, no next realtime trigger, and no drain-service journal entry. The reported last service trigger (22:07) precedes timer reactivation (about 22:16) by only nine minutes, so an OnUnitActiveSec anchor would ordinarily be expected. I updated #5157 to treat the home-manager migration as a temporal association, not a proven cause, and to leave the exact systemd transition for confirmation during implementation. The calendar-trigger fix remains scoped because it supplies a future schedule independent of that anchor.

  19. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    Current-main recheck before triage completion: remote main advanced to 910213507772c55c6fee21085fcc651fb4acfe05 (merge PR #5159) while this pass was in progress. I fetched the timer file and confirmed it is unchanged: OnBootSec=3min, OnUnitActiveSec=5min, Persistent=true, no OnCalendar=. Thus #5157 remains live at the latest main; the earlier commit SHA in my first triage note was the main tip at the time of that check.

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