fix(backup): give binjovi-plans and hetzner-node-image an off-cluster copy #585

Merged
binjovi-bot merged 2 commits from fix/backup-plans-and-node-image into trunk 2026-09-10 16:55:59 +00:00
Owner

Two repositories had no off-cluster backup of any kind. Measured on 2026-09-10 with an authenticated Codeberg read — not inferred from manifests:

someara/binjovi-plans      -> HTTP 404
someara/hetzner-node-image -> HTTP 404

Of the 45 repositories on the instance, 39 have a push mirror. seanfarm and platform-seanfarm are covered through Codeberg as the Flux bootstrap source. These two had nothing: no ForgejoPushMirror, absent from repository-bootstrap.json, absent from ForgejoRepositoryBindings.

hetzner-node-image was created by forgejo-users/hetzner-image-repository.sh, a one-repo bootstrap that uses auto_init: true — so a cold make rebuild recreated it empty, with one initial commit and nothing to restore from.

What was at risk

  • binjovi-plans holds the project definitions and the canonical RustFS bucket, policy and identity declarations that rustfs-identities/{loki,zdnspromote,binjovi-rpg-build,binjovi-rpg-promote}.yaml all name as their source of truth. Code can be rebuilt from a running node; those declarations exist nowhere else.
  • hetzner-node-image holds the disk assembly recipe for the image that boots every node.

gitmirrors/README.md records sean/linux destroyed on 2026-05-31 with Codeberg as the only other copy.

Change

  • A ForgejoPushMirror for each, branchFilter: main,trunk. Both branches exist on both repositories (git ls-remote). A non-empty filter is what stops Forgejo building the bidirectional --mirror remote that rewrote local main in 2026-07; a filter naming an absent branch fails the whole push atomically, which broke 11/28 repositories on 2026-08-11.
  • Both added to repository-bootstrap.json with a source, so a cold rebuild imports content rather than creating an empty shell. Job completions 40 -> 42, and the test now derives that from the record count instead of repeating the literal.
  • hetzner-node-image added to management-grants-repositories.txt, which it was missing. binjovi-plans was already present.
  • New scripts/check-repository-backup.sh, run for both before make rebuild destroys anything. It is the general form of check-kernel-source-backup.sh minus the kernel-only size ceiling.

Neither file declares a ForgejoRepository. Both repositories already exist and are created elsewhere; a repository CR would only be one more object to remove when binjovi takes over the declaration.

Guard changes, with controls

tests/gitmirror-fleet.sh required exactly one ForgejoRepository per file. It now recognises mirror-only files through a short explicit list. Both controls confirmed red before this landed:

  • removing binjovi-plans from the list -> FAIL: has 0 ForgejoRepository docs, want 1
  • adding a ForgejoRepository to a mirror-only file -> FAIL: is mirror-only but has 1 ForgejoRepository docs

The new backup guard was proven both ways too: bytey (mirrored) passes; both target repositories fail naming the exact refs.

Passing now: gitmirror-fleet (36 repositories, 38 mirrors), binjovi-repository-bootstrap, binjovi-service-handoff, hetzner-image-repository.

MERGE ORDER — please read

The two Codeberg repositories must be created before this merges. A push mirror whose remote does not exist never delivers, and the new rebuild guards will refuse to destroy until the refs match. That refusal is correct — there genuinely is no backup — but it blocks make rebuild until the backup is real.

I could not create them: the local harness refused the authenticated write to codeberg.org. The command is in the PR discussion.

Not in scope here

service-legacy-access.yaml is untouched. Its forgejorepositories rule lists legacy CR names, and neither repository is in ForgejoRepositoryBindings yet, so neither has a service handle to authorize. That belongs with the binjovi-side work. Worth noting separately: that rule has 40 names against 41 bindings — hermes is missing, so its adoption fails with denied rather than legacy_controller_present.

https://claude.ai/code/session_01TdW5FSvRJW8CtGnpd29Xp8

Two repositories had **no off-cluster backup of any kind**. Measured on 2026-09-10 with an authenticated Codeberg read — not inferred from manifests: ``` someara/binjovi-plans -> HTTP 404 someara/hetzner-node-image -> HTTP 404 ``` Of the 45 repositories on the instance, 39 have a push mirror. `seanfarm` and `platform-seanfarm` are covered through Codeberg as the Flux bootstrap source. These two had nothing: no `ForgejoPushMirror`, absent from `repository-bootstrap.json`, absent from `ForgejoRepositoryBindings`. `hetzner-node-image` was created by `forgejo-users/hetzner-image-repository.sh`, a one-repo bootstrap that uses `auto_init: true` — so a cold `make rebuild` recreated it **empty**, with one initial commit and nothing to restore from. ## What was at risk - **`binjovi-plans`** holds the project definitions and the canonical RustFS bucket, policy and identity declarations that `rustfs-identities/{loki,zdnspromote,binjovi-rpg-build,binjovi-rpg-promote}.yaml` all name as their source of truth. Code can be rebuilt from a running node; those declarations exist nowhere else. - **`hetzner-node-image`** holds the disk assembly recipe for the image that boots every node. `gitmirrors/README.md` records `sean/linux` destroyed on 2026-05-31 with Codeberg as the only other copy. ## Change - A `ForgejoPushMirror` for each, `branchFilter: main,trunk`. Both branches exist on both repositories (`git ls-remote`). A non-empty filter is what stops Forgejo building the bidirectional `--mirror` remote that rewrote local `main` in 2026-07; a filter naming an absent branch fails the whole push atomically, which broke 11/28 repositories on 2026-08-11. - Both added to `repository-bootstrap.json` with a source, so a cold rebuild **imports content** rather than creating an empty shell. Job `completions` 40 -> 42, and the test now derives that from the record count instead of repeating the literal. - `hetzner-node-image` added to `management-grants-repositories.txt`, which it was missing. `binjovi-plans` was already present. - New `scripts/check-repository-backup.sh`, run for both before `make rebuild` destroys anything. It is the general form of `check-kernel-source-backup.sh` minus the kernel-only size ceiling. **Neither file declares a `ForgejoRepository`.** Both repositories already exist and are created elsewhere; a repository CR would only be one more object to remove when binjovi takes over the declaration. ## Guard changes, with controls `tests/gitmirror-fleet.sh` required exactly one `ForgejoRepository` per file. It now recognises mirror-only files through a short explicit list. Both controls confirmed red before this landed: - removing `binjovi-plans` from the list -> `FAIL: has 0 ForgejoRepository docs, want 1` - adding a `ForgejoRepository` to a mirror-only file -> `FAIL: is mirror-only but has 1 ForgejoRepository docs` The new backup guard was proven both ways too: `bytey` (mirrored) passes; both target repositories fail naming the exact refs. Passing now: `gitmirror-fleet` (36 repositories, 38 mirrors), `binjovi-repository-bootstrap`, `binjovi-service-handoff`, `hetzner-image-repository`. ## MERGE ORDER — please read **The two Codeberg repositories must be created before this merges.** A push mirror whose remote does not exist never delivers, and the new rebuild guards will refuse to destroy until the refs match. That refusal is correct — there genuinely is no backup — but it blocks `make rebuild` until the backup is real. I could not create them: the local harness refused the authenticated write to codeberg.org. The command is in the PR discussion. ## Not in scope here `service-legacy-access.yaml` is untouched. Its `forgejorepositories` rule lists **legacy CR names**, and neither repository is in `ForgejoRepositoryBindings` yet, so neither has a service handle to authorize. That belongs with the binjovi-side work. Worth noting separately: that rule has 40 names against 41 bindings — **`hermes` is missing**, so its adoption fails with `denied` rather than `legacy_controller_present`. https://claude.ai/code/session_01TdW5FSvRJW8CtGnpd29Xp8
fix(backup): give binjovi-plans and hetzner-node-image an off-cluster copy
Some checks failed
binjovi/ci Binjovi failed the frozen plan
de2469cc08
Both repositories had NO off-cluster backup of any kind. Measured on 2026-09-10
with an authenticated read: `codeberg.org/someara/binjovi-plans` and
`codeberg.org/someara/hetzner-node-image` both answered 404. Of the 45
repositories on the instance, 39 have a push mirror and these two had nothing.

Neither had a ForgejoPushMirror, neither was in `repository-bootstrap.json`, and
neither is in binjovi's `ForgejoRepositoryBindings`. `hetzner-node-image` was
created by a one-repo bootstrap script that uses `auto_init: true`, so a cold
`make rebuild` recreated it EMPTY, with one initial commit and nothing to
restore from.

WHAT WAS AT RISK. `binjovi-plans` holds the project definitions and the
canonical RustFS bucket, policy and identity declarations that
`rustfs-identities/*.yaml` name as their source of truth. Code can be rebuilt
from a running node; those declarations exist nowhere else.
`hetzner-node-image` holds the disk assembly recipe for the image that boots
every node. `gitmirrors/README.md` records that `sean/linux` was destroyed on
2026-05-31 and codeberg was the only other copy.

This change:

- adds a ForgejoPushMirror for each, with `branchFilter: main,trunk`. Both
  branches exist on both repositories, checked with `git ls-remote`. A non-empty
  filter is what stops Forgejo building the bidirectional `--mirror` remote that
  rewrote local `main` in 2026-07, and a filter that names an absent branch
  fails the WHOLE push atomically, which broke 11 of 28 repositories on
  2026-08-11.
- adds both to `repository-bootstrap.json` with a source, so a cold rebuild
  imports content instead of creating an empty shell, and moves the Job's
  `completions` to 42. The test now derives that number instead of repeating it.
- adds `hetzner-node-image` to `management-grants-repositories.txt`, which it
  was missing. `binjovi-plans` was already there.
- adds `scripts/check-repository-backup.sh` and runs it for both repositories
  before `make rebuild` destroys anything. It is the general form of
  `check-kernel-source-backup.sh` without the kernel-only size ceiling.

Neither file declares a ForgejoRepository. Both repositories already exist and
are created elsewhere; a repository CR would only be one more object to remove
when binjovi takes over the declaration. `tests/gitmirror-fleet.sh` learns about
mirror-only files through a short explicit list, so a file that silently loses
its ForgejoRepository still fails. Both controls were confirmed red.

MERGE ORDER MATTERS. Create the two codeberg repositories BEFORE this merges. A
push mirror whose remote does not exist never delivers, and the new rebuild
guards refuse to destroy until the refs match, which is correct but blocks
`make rebuild` until the backup is real.

Claude-Session: https://claude.ai/code/session_01TdW5FSvRJW8CtGnpd29Xp8
sean force-pushed fix/backup-plans-and-node-image from de2469cc08
Some checks failed
binjovi/ci Binjovi failed the frozen plan
to 766f41323f
Some checks failed
binjovi/ci Binjovi failed the frozen plan
2026-09-10 16:34:51 +00:00
Compare
sean force-pushed fix/backup-plans-and-node-image from 766f41323f
Some checks failed
binjovi/ci Binjovi failed the frozen plan
to 3b9ee46ced
Some checks failed
binjovi/ci Binjovi failed the frozen plan
2026-09-10 16:47:19 +00:00
Compare
test(grants): derive the grant count and tie it to the seed
All checks were successful
binjovi/ci Binjovi completed the frozen plan
5a1783d5c7
The guard hard-coded 42 repositories in three places: the list length and two
scenario call counts. Adding hetzner-node-image, which was missing its
management grant, broke all three.

Derive the call counts from the list, and assert the seed is a SUBSET of the
grants. A repository the cold rebuild creates without a management grant cannot
be adopted afterwards: the settings snapshot fails closed unless the token is a
repository admin, so the two lists drifting apart is a real defect and not a
bookkeeping detail.

Claude-Session: https://claude.ai/code/session_01TdW5FSvRJW8CtGnpd29Xp8
binjovi-bot deleted branch fix/backup-plans-and-node-image 2026-09-10 16:55:59 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
sean/seanfarm!585
No description provided.