chore(scripts): organize scripts/ by job: release, secrets, checks, ci, dev, migration #156

Merged
jfraeys merged 1 commit from jfraeys/chore/scripts-layout into main 2026-10-14 16:09:57 -04:00
Owner

Stacked on #154 → #152. Until those merge, this PR's diff also shows their commits; merge them first. After they land this rebases cleanly (it only moves files and rewrites paths).

scripts/ was flat: 30 scripts mixing production release commands, vault tools, read-only checks, CI helpers, a dev wrapper and one-off migration wizards. Now:

Directory Contents Reached through
release/ plan-prod, approve-promotion, promote-prod, deploy-services ./infra plan / approve / deploy
secrets/ rotate-secret, rotate-ovh-s3-credentials, regenerate-ci-ephemeral-ssh-key, provision-headscale-api-key, provision-ci-state-key, vault-set, vault-pr, with-vault, sync-ci-secrets, secrets-expiry-report, init-secrets, gen-auth-secrets, gen-staging-secrets, age-*-keygen, init-vaultwarden, verify-vaultwarden-token ./infra rotate / secrets / keys
checks/ check-downloads, check-secret-hygiene, check-exposure, check-tool-versions ./infra check, CI, pre-commit
ci/ forgejo-upsert-issue, export-infra-metrics workflows, deploy
dev/ molecule-test by hand
migration/ cloudflare-migration-setup-wizard, export-cloudflare-zone, ovh-setup-wizard (MIGRATION-ONLY) by hand, then deleted
lib/, tests/ unchanged

./infra keeps the same commands: day-to-day use does not change.

References updated (~270 in 60 files): ./infra's dispatch, workflows and .githooks/pre-commit, roles (defaults, CLAUDE.md, alert text), the secrets manifest's rotation: fields, runbooks, README, tests (including path-joined Python tests) and the scripts' calls to each other. CHANGELOG.md and docs/incidents.md keep the paths of their time. Each moved script now finds the repo root two levels up and lib/ as ../lib (34 lines in 26 scripts). scripts/CLAUDE.md documents the layout and where a new script goes. .claude/settings.json has no script paths.

Checked: bash -n / py_compile on every script; a repo-wide scan finds no reference to a script path that does not exist (the 3 hits are other repos' or containers' scripts); check-downloads, check-secret-hygiene, 39 unit tests, test-promotion, -rotation-lock, -project-key-isolation, -bitwarden-save, -vault-pr, -tofu-unlock, -infra-cli, -approve-promotion, -container-scan, ansible-lint and yamllint pass; the moved entry points run from outside the repo (usage paths, the manifest-only expiry report); the commit's own pre-commit hook ran from scripts/checks/. test-alert-rules and test-alertmanager-config fail locally identically before and after (Docker/promtool on macOS); CI runs them. Staging (staging-validate.yml calls release/ scripts) will exercise the deploy path once merged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ge5tovcwdEUBtPwUktsZZZ

**Stacked on #154 → #152.** Until those merge, this PR's diff also shows their commits; merge them first. After they land this rebases cleanly (it only moves files and rewrites paths). `scripts/` was flat: 30 scripts mixing production release commands, vault tools, read-only checks, CI helpers, a dev wrapper and one-off migration wizards. Now: | Directory | Contents | Reached through | |---|---|---| | `release/` | plan-prod, approve-promotion, promote-prod, deploy-services | `./infra plan / approve / deploy` | | `secrets/` | rotate-secret, rotate-ovh-s3-credentials, regenerate-ci-ephemeral-ssh-key, provision-headscale-api-key, provision-ci-state-key, vault-set, vault-pr, with-vault, sync-ci-secrets, secrets-expiry-report, init-secrets, gen-auth-secrets, gen-staging-secrets, age-*-keygen, init-vaultwarden, verify-vaultwarden-token | `./infra rotate / secrets / keys` | | `checks/` | check-downloads, check-secret-hygiene, check-exposure, check-tool-versions | `./infra check`, CI, pre-commit | | `ci/` | forgejo-upsert-issue, export-infra-metrics | workflows, deploy | | `dev/` | molecule-test | by hand | | `migration/` | cloudflare-migration-setup-wizard, export-cloudflare-zone, ovh-setup-wizard (MIGRATION-ONLY) | by hand, then deleted | | `lib/`, `tests/` | unchanged | | `./infra` keeps the same commands: day-to-day use does not change. **References updated (~270 in 60 files):** `./infra`'s dispatch, workflows and `.githooks/pre-commit`, roles (defaults, CLAUDE.md, alert text), the secrets manifest's `rotation:` fields, runbooks, README, tests (including path-joined Python tests) and the scripts' calls to each other. `CHANGELOG.md` and `docs/incidents.md` keep the paths of their time. Each moved script now finds the repo root two levels up and `lib/` as `../lib` (34 lines in 26 scripts). `scripts/CLAUDE.md` documents the layout and where a new script goes. `.claude/settings.json` has no script paths. **Checked:** `bash -n` / `py_compile` on every script; a repo-wide scan finds no reference to a script path that does not exist (the 3 hits are other repos' or containers' scripts); check-downloads, check-secret-hygiene, 39 unit tests, test-promotion, -rotation-lock, -project-key-isolation, -bitwarden-save, -vault-pr, -tofu-unlock, -infra-cli, -approve-promotion, -container-scan, ansible-lint and yamllint pass; the moved entry points run from outside the repo (usage paths, the manifest-only expiry report); the commit's own pre-commit hook ran from `scripts/checks/`. `test-alert-rules` and `test-alertmanager-config` fail locally identically before and after (Docker/promtool on macOS); CI runs them. Staging (`staging-validate.yml` calls `release/` scripts) will exercise the deploy path once merged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Ge5tovcwdEUBtPwUktsZZZ
feat(infra): one front door: day-2 ops, every rotation, checks and keys through ./infra
All checks were successful
Lint / lint (pull_request) Successful in 1m50s
Secret scan / scan (pull_request) Successful in 14s
db6748d882
./infra now also forwards health/backup/user/firewall/vpn/node/service/demo to
./manage (its global options work anywhere on the line), and covers the
credentials outside rotate-secret.sh's service list (rotate ovh-s3, ci-ssh-key,
headscale-api-key, ci-state-key), the read-only checks (check exposure,
versions) and SOPS age key creation (keys operator|recovery [project]).

./manage stays a separate, self-contained script: roles/manage_cli installs it on
the hosts as infra-manage, where git, tea and the vault are not available. Its
`secret` command now points at ./infra rotate. ./setup stays the engine; staging,
DR and bootstrap still call it directly.

daily-commands.md documents the commands and how the three entry points fit;
test-infra-cli.sh gains 13 dispatch cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ge5tovcwdEUBtPwUktsZZZ
feat(infra): ./infra approve merges the promotion PR after the gate's own checks
All checks were successful
Lint / lint (pull_request) Successful in 2m26s
Secret scan / scan (pull_request) Successful in 14s
6c2dd8a722
scripts/approve-promotion.sh (./infra approve [PR]) checks, before anything is
merged, what the deploy gate will check later: the PR is main -> prod, staging
validated a commit, the PR's head is that commit or differs from it only in files
staging does not exercise (the gate's PROMOTION_STAGED_RE, reused), CI on the head
is green, and scripts/plan-prod.sh does not error. You then type the PR number
("<N> with changes" when the plan found changes); it merges with tea and confirms
on the API that the merge took. Replaces merging in the Forgejo UI, where merges
silently did not take (#129, #133, #134). Needs a terminal; reads no secret.

test-approve-promotion.sh: 15 cases with a fake tea, a fake plan and a throwaway
git repo for the ancestry rule; added to the lint workflow. Runbooks and README
point step 4 at ./infra approve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ge5tovcwdEUBtPwUktsZZZ
chore(scripts): organize scripts/ by job: release, secrets, checks, ci, dev, migration
All checks were successful
Lint / lint (pull_request) Successful in 1m55s
Secret scan / scan (pull_request) Successful in 13s
f1ca12aaaf
scripts/ was flat: production release commands, vault tools, read-only checks, CI
helpers, a dev wrapper and one-off migration wizards side by side (30 scripts).
They now live in release/, secrets/, checks/, ci/, dev/ and migration/; lib/ and
tests/ are unchanged. ./infra keeps the same commands, so daily use does not change.

Every reference is updated: ./infra's dispatch, the workflows and the pre-commit
hook, roles' defaults/docs/alert text, the secrets manifest's rotation fields, the
runbooks, README, tests and the scripts' calls to each other (CHANGELOG.md and
docs/incidents.md keep the paths of their time). Each moved script now finds the
repo root two levels up and lib/ as ../lib. scripts/CLAUDE.md documents the layout.

Checked: bash -n / py_compile on every script; check-downloads, check-secret-hygiene,
39 unit tests, test-promotion, -rotation-lock, -project-key-isolation, -bitwarden-save,
-vault-pr, -tofu-unlock, -infra-cli, -approve-promotion, -container-scan, ansible-lint
and yamllint pass; the moved entry points run from outside the repo (usage paths and
the manifest-only expiry report). test-alert-rules and test-alertmanager-config fail
locally the same way before and after (Docker/promtool on macOS); CI runs them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ge5tovcwdEUBtPwUktsZZZ
jfraeys force-pushed jfraeys/chore/scripts-layout from f1ca12aaaf
All checks were successful
Lint / lint (pull_request) Successful in 1m55s
Secret scan / scan (pull_request) Successful in 13s
to 0d2667f8eb
All checks were successful
Lint / lint (pull_request) Successful in 2m42s
Secret scan / scan (pull_request) Successful in 37s
2026-10-14 14:40:40 -04:00
Compare
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
jfraeys/infra!156
No description provided.