Skip to content

Run site.yml from CI, one box at a time, with lockout checks - #6

Merged
TakGN merged 3 commits into
mainfrom
site-from-ci
Oct 13, 2026
Merged

TakGN merged 3 commits into
mainfrom
site-from-ci

Conversation

@TakGN

@TakGN TakGN commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

site.yml now runs from CI the same way platform.yml does. It needs no new secrets. routing.yml, bootstrap.yml, content.yml and k3s-upgrade.yml deliberately stay manual for now.

What happens on merge

Once the checks pass, a dry run (--check --diff) of the merged commit writes a per-box, per-task table to the run summary. The real run then waits for a required reviewer to approve it in production. If a merge touches both playbooks, site.yml goes first and platform.yml's dry run waits until it has been applied. A failed or rejected site.yml stops platform.yml. If a newer merge supersedes the pending site.yml apply, platform.yml still goes ahead, so its change is not lost. For manual runs, workflow_dispatch now takes playbook (site/platform) and run (none/check/apply), plus limit.

Safeguards against lockouts

  • One box at a time. production_rollout now governs site.yml as well: one box per batch, and a box that fails ends the run with the remaining boxes untouched. I verified this in check mode by forcing a failure on sing-1; lith-1 and chic-1 never entered the play.
  • After each box, whatever --tags ran, and in dry runs too:
    • a fresh SSH login (no multiplexed socket) as ipni with sudo -n
    • every key in admin_pubkey_file still in authorized_keys (a negative test with a missing key fails as expected)
    • sshd -t on the config sshd would load at its next start
  • CI never builds a box. CI sets site_require_hardened, so a box that refuses the admin key fails the run instead of falling back to root. I tested this with a bogus admin user.
  • CI does not fetch cluster-admin kubeconfigs (k3s_write_local_kubeconfig: false). The apt cache refresh no longer reports changed. Together these keep the dry-run table limited to real changes.

Current dry run (all three boxes, CI's vars, from this branch)

Box Task Changes
sing-1 common : Apply sysctl tuning 1
sing-1 hardening : Install the admin SSH public keys 1
lith-1 common : Apply sysctl tuning 1
lith-1 hardening : Install the admin SSH public keys 1
chic-1 common : Apply sysctl tuning 1
chic-1 hardening : Install the admin SSH public keys 1

Nothing changes in sshd, ufw or k3s, and there are no restarts. This merge will offer exactly this apply.

Other changes

  • scripts/ci-platform.sh becomes scripts/ci-playbook.sh <site|platform> <check|apply> [limit].
  • Fixes site.yml from a macOS controller: the local probe play cached the controller's Homebrew Python as each box's interpreter.
  • README ("Running playbooks from CI"), AGENTS.md and the production_rollout comment are updated.

Known trade-off

site-main and platform-main are separate concurrency groups, so a site.yml apply and a platform.yml apply from different merges can overlap. A single shared group would let one playbook's newer merge cancel the other playbook's pending apply. This is documented in the README.

Lint: yamllint, ansible-lint (production profile, 0 failures), syntax-check of all six playbooks, shellcheck and actionlint all pass locally.

🤖 Generated with Claude Code

TakGN and others added 3 commits October 13, 2026 19:04
site.yml now follows the same flow as platform.yml: the checks, a dry run
of the merged commit written to the run summary, then the real run once a
required reviewer approves it in `production`. When a merge touches both,
site.yml goes first and platform.yml's dry run waits for it.

Safeguards, since site.yml touches sshd, ufw, the admin keys and k3s:
- production_rollout now governs site.yml too: one box per batch, and a
  failed box ends the run with the rest untouched.
- After each box, whatever --tags ran: a fresh SSH login as the admin user
  with sudo, every key in admin_pubkey_file still in authorized_keys, and
  `sshd -t`.
- CI sets site_require_hardened: a box that refuses the admin key fails
  the run rather than falling back to root, so CI never builds a box.
- CI does not fetch kubeconfigs (k3s_write_local_kubeconfig), and the apt
  cache refresh no longer reports a change, so dry runs list real changes.

scripts/ci-platform.sh becomes scripts/ci-playbook.sh <playbook> <mode>,
and workflow_dispatch takes `playbook` and `run` instead of `platform`.

Also fixes site.yml from a macOS controller: the local probe play cached
the controller's Python as each box's interpreter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TakGN
TakGN merged commit eceeabe into main Oct 13, 2026
9 checks passed
@TakGN
TakGN deleted the site-from-ci branch October 13, 2026 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant