From 5c63c2f354ee3922f94160690748441a9cc7aa39 Mon Sep 17 00:00:00 2001 From: codex Date: Sat, 4 Jul 2026 13:21:40 +0200 Subject: [PATCH] Add Forgejo tier-3 remote_url and sweep playbook Document State Hub PATCH procedure, sweep implications, railiance01 host_paths, state-hub image specifics, and rollback. Include batch patch helper; applied tier-2.5 remote_url updates in hub DB. --- ...rgejo-repo-migration-pilot-glas-harness.md | 3 +- ...forgejo-tier3-remote-url-sweep-playbook.md | 238 ++++++++++++++++++ tools/patch-forgejo-remote-urls.sh | 49 ++++ 3 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 docs/forgejo-tier3-remote-url-sweep-playbook.md create mode 100755 tools/patch-forgejo-remote-urls.sh diff --git a/docs/forgejo-repo-migration-pilot-glas-harness.md b/docs/forgejo-repo-migration-pilot-glas-harness.md index 3799641..5d2bfc2 100644 --- a/docs/forgejo-repo-migration-pilot-glas-harness.md +++ b/docs/forgejo-repo-migration-pilot-glas-harness.md @@ -149,7 +149,8 @@ Before `state-hub`, the ladder still needs: - [x] Operator/user SSH identity on Forgejo (`tegwick` + workstation key) - [x] Reusable workflow templates in `railiance-enablement` (incl. multi-repo / `hub-core` context template) -- [ ] State Hub `remote_url` + sweep checkout path update playbook +- [x] State Hub `remote_url` + sweep checkout path update playbook — + `docs/forgejo-tier3-remote-url-sweep-playbook.md` (execute per repo on promotion) - [ ] Gitea read-only mirror or push-disable policy for repos after cutover - [ ] Scheduled Forgejo backups (disaster-control track; restore drill passed) diff --git a/docs/forgejo-tier3-remote-url-sweep-playbook.md b/docs/forgejo-tier3-remote-url-sweep-playbook.md new file mode 100644 index 0000000..d5ca526 --- /dev/null +++ b/docs/forgejo-tier3-remote-url-sweep-playbook.md @@ -0,0 +1,238 @@ +# Forgejo Tier 3 — State Hub `remote_url` and Sweep Playbook + +Date: 2026-07-04 +Workplans: `RAIL-HO-WP-0005-T10`, `RAIL-HO-WP-0005-T11`, `CUST-WP-0054-T04`, `CUST-WP-0054-T05` +`no_secret_material_recorded: true` + +## Purpose + +Operational playbook for promoting a **registered** State Hub repo from Gitea +(`gitea-remote`) to Forgejo (`forgejo-remote`) without breaking the consistency +sweep, DoI checks, or agent attribution. + +Applies to: + +- **Tier 2.5** (done on Forgejo, hub `remote_url` may still say Gitea) — run + §4 after git promotion. +- **Tier 3** (`state-hub`, `issue-core`, production wave) — full §3–§8 with + operator approval. + +Does **not** perform cutover by itself. Gitea remains canonical until tier gates +and explicit approval per `RAIL-HO-WP-0005` safety contract. + +## Preconditions (gates) + +| Gate | Tier 2.5 | Tier 3 (`state-hub`) | +| --- | --- | --- | +| Forgejo repo exists + history mirrored | required | required | +| Workstation `origin=forgejo-remote`, `gitea` legacy remote | required | required | +| Operator SSH on Forgejo (`tegwick` or team keys) | required | required | +| CI smoke green on Forgejo (`.forgejo/workflows/ci-smoke.yaml`) | required | required | +| Image workflow if applicable | optional | required for `state-hub` | +| Restore drill passed | recommended | required | +| Scheduled backups automated | optional | required (disaster-control) | +| Operator approval recorded | not required | **required** | +| `CUST-WP-0011-T07` freeze/restore for hub primary | n/a | required before hub production | + +## Canonical remote conventions + +State Hub stores the **SSH remote name form** used on the operator workstation, +not the HTTPS URL. This matches existing registrations +(`gitea-remote:coulomb/.git`). + +| Role | Remote name | Example | +| --- | --- | --- | +| Canonical (post-promotion) | `origin` → `forgejo-remote` | `forgejo-remote:coulomb/state-hub.git` | +| Rollback mirror | `gitea` → `gitea-remote` | `gitea-remote:coulomb/state-hub.git` | +| Hub `remote_url` field | same as `origin` | `forgejo-remote:coulomb/state-hub.git` | + +HTTPS canonical URL for humans and archive CI: +`https://forgejo.coulomb.social/coulomb/.git` + +SSH config block (workstation `~/.ssh/config`): + +```ssh +Host forgejo-remote + HostName 92.205.62.239 + Port 30022 + User git + IdentityFile ~/.ssh/id_gitea + StrictHostKeyChecking accept-new +``` + +## Per-repo promotion procedure + +### 1. Git forge (workstation) + +Use `railiance-enablement/tools/promote-repo-to-forgejo.sh` or manually: + +```bash +cd ~/REPO_SLUG +# Create empty repo on Forgejo if needed (org coulomb) +git push forgejo-remote:coulomb/REPO_SLUG.git main # first mirror +git remote rename origin gitea +git remote add origin forgejo-remote:coulomb/REPO_SLUG.git +git push -u origin main +# Optional: keep Gitea mirror current +git push gitea main +``` + +Add `.forgejo/workflows/ci-smoke.yaml` from +`railiance-enablement/workflows/ci-smoke.yaml`. For images, use +`container-build-push.yaml` or `container-build-push-multirepo.yaml` (state-hub + +hub-core). + +### 2. Verify git + SSH + +```bash +git remote -v +# origin forgejo-remote:coulomb/REPO_SLUG.git (fetch/push) +# gitea gitea-remote:coulomb/REPO_SLUG.git (fetch/push) + +ssh forgejo-remote # expect: Hi there, tegwick! +git ls-remote origin # reachable +``` + +### 3. Patch State Hub registration + +```bash +API=http://127.0.0.1:8000 +SLUG=REPO_SLUG +NEW_URL="forgejo-remote:coulomb/${SLUG}.git" + +curl -s -X PATCH "${API}/repos/${SLUG}" \ + -H "Content-Type: application/json" \ + -d "{\"remote_url\": \"${NEW_URL}\"}" | python3 -m json.tool + +# Confirm +curl -s "${API}/repos/${SLUG}" | python3 -c \ + "import json,sys; r=json.load(sys.stdin); print(r['slug'], r['remote_url'])" +``` + +Batch helper for tier-2.5 stack (skips slugs not registered in State Hub): + +```bash +~/the-custodian/tools/patch-forgejo-remote-urls.sh --tier-25 +``` + +Applied 2026-07-04 for registered tier-2.5 repos + `key-cape`. `glas-harness` is +not in State Hub registration — patch when/if registered. + +### 4. Consistency sweep / fix-consistency + +The sweep (`POST /consistency/sweep/remote-all` or `statehub fix-consistency`) +resolves repos by `host_paths[hostname]` or `local_path`, then `git pull +--ff-only` from the checkout's **tracking branch** (now Forgejo `origin`). + +After `remote_url` patch: + +```bash +cd ~/REPO_SLUG +git fetch origin +statehub fix-consistency --repo REPO_SLUG +# or from state-hub checkout: +cd ~/state-hub && make fix-consistency REPO=REPO_SLUG +``` + +**Attribution:** `consistency_check.py` matches repos by git fingerprint and +`remote_url` string. Stale `gitea-remote` in the hub causes mis-attribution when +multiple checkouts exist — patch hub **before** relying on sweep writebacks. + +### 5. DoI verification + +```bash +curl -s "http://127.0.0.1:8000/repos/REPO_SLUG/doi" | python3 -m json.tool +# C4 Remote URL set → pass +# C4 reachability → git ls-remote origin succeeds +``` + +## Railiance01 sweep checkout paths (`CUST-WP-0054-T05`) + +Today sweeps write back from **workstation** `host_paths` (`bnt-lap001`). Wave 2 +moves primary checkouts to **railiance01** so triage survives workstation loss. + +When railiance01 clone tree exists (e.g. `/home/tegwick/` or agreed path): + +```bash +API=http://127.0.0.1:8000 +SLUG=REPO_SLUG +HOST=railiance01 # must match socket.gethostname() on that machine +PATH_ON_HOST=/home/tegwick/REPO_SLUG + +curl -s -X POST "${API}/repos/${SLUG}/paths" \ + -H "Content-Type: application/json" \ + -d "{\"host\": \"${HOST}\", \"path\": \"${PATH_ON_HOST}\"}" | python3 -m json.tool +``` + +Clone on railiance01 **from Forgejo**: + +```bash +git clone forgejo-remote:coulomb/REPO_SLUG.git /home/tegwick/REPO_SLUG +# or HTTPS with deploy key / token +``` + +Sweep on railiance01 then uses `host_paths[railiance01]`; workstation path +remains in map for dev but is no longer the production writeback source. + +## Tier 3 — `state-hub` specifics + +Before promoting `state-hub`: + +1. **Multi-repo image workflow** — copy + `railiance-enablement/workflows/container-build-push-multirepo.yaml` to + `state-hub/.forgejo/workflows/image.yaml`; set `IMAGE_NAME=coulomb/state-hub`, + `EXTRA_REPOS="coulomb/hub-core@hub_core_src"`. +2. **Prove image pull** on railiance01 (`crictl pull` or deployment dry-run). +3. **Hub primary cutover** — follow `CUST-WP-0011-T07` (freeze → restore → + rewire); independent of forge remote but same maintenance window may apply. +4. **ArgoCD / GitOps** — repoint repository URLs to Forgejo (Wave 5 in + `docs/coulombcore-drain-placement-plan.md`). +5. **Record approval** — `POST /decisions/` or workplan note with operator id. + +`state-hub` promotion order: + +``` +mirror git → Forgejo remotes → CI image workflow green → PATCH remote_url → +fix-consistency → (later) railiance01 host_paths → CUST-WP-0011-T07 cutover +``` + +## Gitea read-only policy (post-cutover per repo) + +After Forgejo is canonical **for that repo** and hub `remote_url` is patched: + +1. Do **not** delete the Gitea repo (safety contract). +2. Org/repo setting: disable push for non-admin users, or maintenance flag. +3. Workstation: stop pushing to `gitea` except intentional rollback mirror. +4. Document rollback: `git push gitea main` from last known-good Forgejo SHA. + +Org-wide Gitea read-only is an operator action (T11); per-repo is enough for +staged ladder. + +## Rollback + +| Step | Action | +| --- | --- | +| Git canonical | `git remote rename origin forgejo; git remote rename gitea origin` | +| Hub | `PATCH /repos/{slug}` → `gitea-remote:coulomb/.git` | +| Sweep | `statehub fix-consistency` on reverted checkout | +| Gitea | Re-enable push if read-only was set | + +## Verification checklist + +- [ ] `GET /repos/{slug}` → `remote_url` is `forgejo-remote:coulomb/.git` +- [ ] Workstation `git remote -v` → `origin` is Forgejo +- [ ] `ssh forgejo-remote` → operator user (not only `forgejo_admin`) +- [ ] `statehub fix-consistency --repo ` → PASS +- [ ] Forgejo Actions `ci-smoke` → success (tier 1+) +- [ ] Gitea copy exists and matches SHA (optional `git push gitea` mirror) +- [ ] Tier 3 only: image workflow + pull smoke + operator approval on record + +## References + +- `docs/forgejo-repo-migration-pilot-glas-harness.md` (tiers 1–2.5) +- `railiance-enablement/docs/forgejo-actions-workflow-templates.md` +- `railiance-enablement/tools/promote-repo-to-forgejo.sh` +- `state-hub/docs/consistency-sweep-runbook.md` +- `state-hub/policies/repo-doi.md` (C4 remote URL) +- `docs/coulombcore-drain-placement-plan.md` (Wave 1–2) +- `disaster-control/history/2026-07-04-forgejo-backup-strategy-assessment.md` \ No newline at end of file diff --git a/tools/patch-forgejo-remote-urls.sh b/tools/patch-forgejo-remote-urls.sh new file mode 100755 index 0000000..51aff9b --- /dev/null +++ b/tools/patch-forgejo-remote-urls.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Patch State Hub managed_repos.remote_url from gitea-remote to forgejo-remote. +# Usage: patch-forgejo-remote-urls.sh slug [slug ...] +# patch-forgejo-remote-urls.sh --tier-25 +set -euo pipefail + +API_BASE="${API_BASE:-http://127.0.0.1:8000}" +ORG="${FORGEJO_ORG:-coulomb}" + +TIER_25=( + railiance-enablement + railiance-infra + railiance-apps + railiance-platform + railiance-cluster + glas-harness + key-cape +) + +if [[ "${1:-}" == "--tier-25" ]]; then + set -- "${TIER_25[@]}" +fi + +if [[ $# -lt 1 ]]; then + echo "usage: $0 slug [slug ...] | --tier-25" >&2 + exit 1 +fi + +for slug in "$@"; do + new_url="forgejo-remote:${ORG}/${slug}.git" + http=$(curl -sS -o /tmp/repo-get.json -w '%{http_code}' "${API_BASE}/repos/${slug}") + if [[ "${http}" == "404" ]]; then + echo "SKIP ${slug} (not registered in State Hub)" + continue + fi + if [[ "${http}" != "200" ]]; then + echo "FAIL ${slug} GET http=${http}" >&2 + exit 1 + fi + current=$(python3 -c "import json; print(json.load(open('/tmp/repo-get.json')).get('remote_url') or '')") + if [[ "${current}" == "${new_url}" ]]; then + echo "OK ${slug} (unchanged)" + continue + fi + curl -fsS -X PATCH "${API_BASE}/repos/${slug}" \ + -H "Content-Type: application/json" \ + -d "{\"remote_url\": \"${new_url}\"}" >/dev/null + echo "PATCH ${slug}: ${current:-} -> ${new_url}" +done \ No newline at end of file