Files
homelab/docs/superpowers/plans/2026-10-18-homelab-konairius-main.md
T

17 KiB

homelab → konairius/homelab, master → main — Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Move the Gitea repo k.renner/homelab into the konairius org and rename its default branch master → main, with no lost Flux sync, pipeline, cron, secret or review.

Architecture: Two separate cut-overs, each with its own window, prep PR and rollback: Cut-over 1 moves the repo (path changes, branch stays master); Cut-over 2 renames the branch (path stays). Everything that can land beforehand lands beforehand; each cut-over's own PR is merged inside its window. The pattern is the one proven on vsftpd, notes, renovate-config, authentik-samba-bridge and homeassistant on 2026-10-18.

Tech Stack: Gitea 1.27 (API, transfer, repoRenameBranch), Woodpecker v3.19.0 (move API, crons, org secrets), Flux (GitRepository flux-system, ImageUpdateAutomation homelab), secret-rotator, OpenBao.

Spec: this conversation's decisions, recorded in the wiki raw notes of 2026-10-18 (konairius migration series). No separate spec doc.

Global Constraints

  • All repos live in konairius (exception: k.renner/turm1388 stays).
  • The default branch of every repo is main.
  • Gitea old paths redirect (301) for reads; Gitea API writes through an old path fail (wiki on k.renner/notes, 2026-10-18). Every writer must address the new path right after the transfer.
  • A transferred repo keeps Woodpecker's old org link until POST /api/repos/{id}/move?to=<owner>/<repo> (homelab: Woodpecker repo id 1). Org secrets follow the org link.
  • Gitea's RenameBranch renames, in one transaction: the branch, the default branch, a protection rule named exactly master, and the base/head of unmerged PRs (models/git/branch.go). It does not keep a master ref; anything fetching master fails afterwards.
  • Renaming the default branch needs repo admin (owner or site admin), not claude-bot.
  • Gitea's transfer removes collaborators who are members of the new org (their access then comes only from their konairius team) and keeps non-members (services/repository/transfer.go).
  • woodpecker repo repair copies the forge's default branch into Woodpecker (repairRepo → Repo.Update sets Branch).
  • Checked live 2026-10-18: Woodpecker repo id 1; cron ids alert-monitor 4, ci-claude-refresh 7, daily-security-scan 2, issue-fix 5, update-audit 3, weekly-update 1, wiki-maintenance 8; the k.renner org holds only claude_code_oauth_token and review_bot_token, every other pipeline secret is a repo secret of homelab.
  • Gitea API: PATCH /repos/{owner}/{repo}/branches/{branch} with {"name": "<new>"} (repoRenameBranch), present on our Gitea.
  • Each service reads only its own OpenBao path; no cross-service key sharing.
  • Never force-push; PRs for every repo change; never 2>/dev/null.

Review Focus

  1. Flux fetch across the transfer. Flux's GitRepository fetches k.renner/homelab.git until the URL change lands. Documented behaviour says this works: source-controller v1.9.5 uses go-git v5.19.1 (its go.mod), whose RedirectPolicy "mirror[s] Git's http.followRedirects config" with the zero value initial (pkg.go.dev); git documents initial as following the redirect on the first request and using "the redirected URL as the base for the follow-up requests" (git-config, http.followRedirects). Gitea's 301 stays on the same host and scheme, so fetch and the IUA push both reach konairius/homelab. Fallback only, should that fail: Task 3 has a declared break-glass (suspend the flux-system Kustomization so it cannot re-apply the old URL, patch the GitRepository URL to the value the PR sets, resume once the source is Ready on the merge commit), checked by flux get source git flux-system.
  2. Write access lost in the transfer. Any writer that is a konairius org member loses its collaborator grant and keeps only its team permission (flux pushes for image automation; claude-bot, issue-fix-bot, security-bot, review-bot, argus-bot, renovate-bot write PRs/reviews). Expect: Task 3 Step 0 snapshot of collaborators and the master protection, compared after Step 1 (memberships confirmed fine by the owner).
  3. Image automation push between merge and rename (Cut-over 2). The IUA pushes to master; after the Task 5 PR it expects main. Expect: IUA suspended for the whole window and resumed only after Flux reports main.
  4. Org secrets after the Woodpecker move. Homelab pipelines use the org secrets claude_code_oauth_token and review_bot_token; after the move they resolve in konairius. Expect: both exist in konairius before Cut-over 1 (Task 1 verification).
  5. Crons on a vanished branch. All 7 crons run on master. Expect: each switched to main in place (woodpecker repo cron update --branch main) inside the Cut-over 2 window, verified by cron ls.
  6. Rotations that verify against the old path. flux-git (GITEA_REQUIRE_PUSH=true, verify repo k.renner/homelab) would fail every quarter. Expect: verify repo switched in Task 2 and a manual rotator run in Task 3.

Task 1: Org secrets in konairius before the move

Files:

  • Modify: kubernetes/infrastructure/openbao/rotation/claude-oauth.yaml (add a second container sync-woodpecker-konairius, same env with WOODPECKER_ORG=konairius)
  • Modify: kubernetes/infrastructure/openbao/rotation/review-bot.yaml (same: a second sync container for konairius, WOODPECKER_SECRET_NAME=review_bot_token, events cron,manual,pull_request)

Interfaces: Produces the konairius org secrets claude_code_oauth_token and review_bot_token that Task 3 relies on. The k.renner containers stay until Task 4.

  • Step 1: Add the containers. Both CronJobs reuse their existing OpenBao role (it only reads OpenBao and the woodpecker-bot bootstrap; no policy change).
  • Step 2: kubectl kustomize kubernetes/infrastructure/openbao/rotation renders.
  • Step 3: PR, merge, flux reconcile kustomization infrastructure.
  • Step 4: Run both once: kubectl create job -n openbao --from=cronjob/claude-oauth-secret-sync claude-oauth-konairius and --from=cronjob/review-bot-secret-rotate review-bot-konairius. Expected logs: updated|created woodpecker org secret konairius/<name>. Note: the review-bot run also executes its init containers rotate-mint and rotate-mint-mcp, i.e. mints and reaps both review-bot PATs off schedule. Keeping the two newest makes one extra run safe; afterwards check kubectl get externalsecret -n review-bot review-bot-secrets is SecretSynced before anything else rotates.
  • Step 5: woodpecker org secret ls --organization konairius lists claude_code_oauth_token, review_bot_token, registry_bot_token; woodpecker org secret ls --organization k.renner still lists only claude_code_oauth_token and review_bot_token (nothing else to carry over).

Task 2: PR "follow homelab to konairius/homelab" (prepared, merged in Task 3)

Files (all FUNCTIONAL k.renner/homelab references, 2026-10-18 inventory):

  • kubernetes/clusters/homelab/flux-system/gotk-sync.yaml:14 — GitRepository url → https://gitea.renner.uno/konairius/homelab.git

  • kubernetes/infrastructure/openbao/rotation/flux-git.yaml:81 — GITEA_VERIFY_REPO → konairius/homelab (and the comment at :84)

  • kubernetes/apps/review-bot/deployment.yaml:38 — REPOS entry → konairius/homelab

  • .woodpecker/alert-monitor.yml:133,241, ci-claude-refresh.yml:177,179,200, issue-fix.yml:85,166,190,199, pr-review.yml:46, security-scan.yml:150,160, update-audit.yml:114 — API, clone and push URLs

  • Header comments with --repo k.renner/homelab / pipeline deploy k.renner/homelab in the same files and e2e.yml:10

  • review-bot/go.mod:1 → module gitea.renner.uno/konairius/homelab/review-bot (no Go file imports it) and the test fixtures in review-bot/*_test.go (consistency only)

  • ci/images/heating-can/Containerfile:66 — OCI source label

  • Step 1: Apply the replacements; leave docs/, PROPOSAL* and MIGRATION* history as is.

  • Step 2: git grep -n 'k.renner/homelab' -- ':!docs' returns only historical files (PROPOSAL*, MIGRATION*).

  • Step 3: go test -C review-bot ./... passes; kubectl kustomize kubernetes/clusters/homelab and the touched app directories render.

  • Step 4: Open the PR titled "(merge inside Cut-over 1 only)"; do not merge.

Task 3: Cut-over 1 — transfer (window)

Preconditions: Task 1 verified; Task 2 PR green; no deploy pipeline running (woodpecker pipeline ls k.renner/homelab --limit 5); record flux get source git flux-system revision.

  • Step 0 (claude-bot): snapshot tea api --login claude-bot /repos/k.renner/homelab/collaborators (2026-10-19: security-bot, claude-bot, alert-monitor-bot, issue-fix-bot, update-audit-bot, review-bot, flux, renovate-bot, hermes-bot) and /repos/k.renner/homelab/branches/master (protected: true, required_approvals: 1). konairius memberships confirmed fine by the owner; whitelists need admin and are not snapshotted.
  • Step 1 (owner): tea api --login renner.uno -X POST /repos/k.renner/homelab/transfer -f new_owner=konairius. Expect curl -sI https://gitea.renner.uno/k.renner/homelab → 301.
  • Step 2 (claude-bot): Woodpecker move: POST $WOODPECKER_SERVER/api/repos/1/move?to=konairius/homelab → 204; woodpecker repo ls shows konairius/homelab.
  • Step 3 (owner): merge the Task 2 PR.
  • Step 1a (claude-bot): repeat the Step 0 reads against konairius/homelab and compare; a missing writer is re-added by the owner before continuing.
  • Step 4: flux reconcile source git flux-system. If it fails to fetch: break-glass — flux suspend kustomization flux-system (else it re-applies the last good artifact with the old URL), then kubectl -n flux-system patch gitrepository flux-system --type merge -p '{"spec":{"url":"https://gitea.renner.uno/konairius/homelab.git"}}' (the value the PR sets), wait for the source to be Ready on the Task 2 merge commit, then flux resume kustomization flux-system. Expect: revision = Task 2 merge commit, Ready.
  • Step 5: flux reconcile image update homelab after the next ImagePolicy change, or check the last IUA push reaches konairius/homelab (commit by Flux Image Automation on master).
  • Step 6: Run flux-git rotator once (kubectl create job -n openbao --from=cronjob/flux-git-secret-rotate flux-git-move); log shows pull+push verified against konairius/homelab.
  • Step 6a: Expected side effects of the Task 2 merge, not failures: build-ci-images rebuilds the review-bot image (go.mod, tests) and heating-can (label), and image automation rolls out the new review-bot tag; ci-claude-refresh runs (its own file is in its path filter) and may open a ci-claude bump PR.
  • Step 7: Verify writers: a PR on konairius/homelab gets a review-bot review; woodpecker pipeline ls konairius/homelab shows the merge's push pipeline green; an agent cron (alert-monitor, every 15 min) succeeds; security-scan/issue-fix open PRs against the new path at their next run.
  • Step 8: Check the GitHub push mirror (konairius/Homelab on GitHub, restore-test source) still syncs (Gitea repo Settings → Mirror, last sync after the transfer).

Rollback: owner transfers back to k.renner, Woodpecker move back to k.renner/homelab, revert the Task 2 PR.

Task 4: After Cut-over 1

  • Remove the k.renner sync containers from Task 1 (nothing in the k.renner Woodpecker org uses them any more; turm1388 uses repo secrets only). PR, reconcile.
  • Workstation: homelab clone remote → https://gitea.renner.uno/konairius/homelab.git; ~/source/CLAUDE.md:13 row → konairius/homelab.
  • Wiki deposit of the observed Cut-over 1 results.

Task 5: PR "default branch main" (prepared, merged in Task 6)

Files (all FUNCTIONAL master references):

  • .woodpecker/build-ci-images.yml:39, ci-claude-refresh.yml:71, deploy-claude-workstation.yml:4, deploy-edge-mqtt-bridge.yml:4, deploy-infrastructure.yml:7, deploy-netbird-client.yml:4, deploy-netbird-proxy.yml:4, deploy-netbird-server.yml:4, security-scan.yml:72 — branch: main

  • .woodpecker/ci-claude-refresh.yml:211, issue-fix.yml:202, security-scan.yml:155 — PR base: "main"

  • kubernetes/clusters/homelab/flux-system/gotk-sync.yaml:11 — ref.branch: main

  • kubernetes/infrastructure/image-automation/update-automation.yaml:26,36 — checkout and push branch main

  • kubernetes/restore-test/flux-root.yaml:16, kubernetes/vmtest-job.yaml:90 — main

  • kubernetes/bootstrap.sh:107 default main, :1478-1479 guard on main, :102 example

  • Not the prompts: review-bot/prompt.md is baked into the review-bot image, which build-ci-images only rebuilds on a push to the branch its when: names. In this PR that filter flips to main while the PR merges into master, so the prompt edit would not ship. It moves to Task 6 Step 9.

  • Step 1: Apply; git grep -n -w master -- ':!docs' ':!ansible' returns only historical/runbook text (handled in Task 7), the two prompts, and unrelated words (VRRP, Open3E, Multus "master").

  • Step 2: bash -n kubernetes/bootstrap.sh; kubectl kustomize kubernetes/clusters/homelab renders.

  • Step 3: Open the PR titled "(merge inside Cut-over 2 only)"; do not merge.

Task 6: Cut-over 2 — rename master → main (window)

Preconditions: Cut-over 1 done; Task 5 PR green; no deploy pipeline running.

  • Step 1: flux suspend image update homelab.
  • Step 2 (owner): merge the Task 5 PR into master. Flux now points at main, which does not exist yet; the cluster keeps its last applied state.
  • Step 3 (owner, immediately): tea api --login renner.uno -X PATCH /repos/konairius/homelab/branches/master -f name=main. Expect: default branch main, protection rule now named main (GET /repos/konairius/homelab/branches/main → protected: true, required_approvals: 1), open PRs based on main.
  • Step 4: the live GitRepository still has ref.branch: master and cannot fetch the commit that changes it (couldn't find remote ref "refs/heads/master"). flux suspend kustomization flux-system, kubectl -n flux-system patch gitrepository flux-system --type merge -p '{"spec":{"ref":{"branch":"main"}}}', flux resume kustomization flux-system → Ready on main; flux get kustomizations all Ready.
  • Step 5: Crons: for each of alert-monitor(4), ci-claude-refresh(7), daily-security-scan(2), issue-fix(5), update-audit(3), weekly-update(1), uptime-kuma-token-renew(10): PATCH $WOODPECKER_SERVER/api/repos/1/cron/<id> with {"branch":"main"} (woodpecker-cli 3.18.0 repo cron update returns 404 against server 3.19.0). Delete wiki-maintenance(8): no pipeline filters on it (git grep wiki-maintenance -- .woodpecker is empty), it starts nothing. cron ls shows seven crons, all on main.
  • Step 6: woodpecker repo repair konairius/homelab; woodpecker repo show shows branch main.
  • Step 7: flux resume image update homelab; next IUA commit lands on main.
  • Step 8: Verify: a small PR into main gets reviewed and its merge triggers the push pipelines; the deploy pipelines' when: branch: main fire.
  • Step 9: PR into main updating review-bot/prompt.md:16 and ci/agents/pr-review/mistral-prompt.md:16 (all repos use main). Its merge is a push to main touching review-bot/**, so build-ci-images rebuilds the review-bot image; image automation rolls it out; review-bot's log shows the Mistral agents re-synced (new prompt hash). This PR doubles as the Step 8 test PR.
  • Step 10 (owner, GitHub): the push mirror konairius/Homelab on GitHub now carries main. GitHub refuses to delete a repo's default branch, so first set its default branch to main (repo Settings → Branches), then delete master there. kubernetes/restore-test/flux-root.yaml already points at main (Task 5).

Rollback: PATCH .../branches/main -f name=master, revert the Task 5 PR, crons back to master (re-add wiki-maintenance only if wanted), resume IUA.

Task 7: Doc and memory sweep

  • One PR replacing remaining k.renner/homelab and master in runbooks and READMEs: kubernetes/RUNBOOK-cold-rebuild.md (incl. flux bootstrap --branch=master at :282), RUNBOOK-home-assistant-image-rollback.md, kubernetes/infrastructure/image-automation/README.md, README.md, pipeline comments, ci/agents/pr-review/run.sh. Leave dated plans/specs and MIGRATION* history unchanged.
  • Update the two memory notes that assume master (feedback_image_bumps_via_flux_automation.md, vican verify-repo-freshness-before-review.md).
  • Local clones: git branch -m master main && git fetch origin && git branch -u origin/main main (owner's workstation).
  • Wiki deposit of Cut-over 2 results.