The runbook learns to run itself

Yesterday ended with a runbook: step-by-step instructions for standing up the lab’s production certificate authority and its companion UI, every value pinned, every prompt answer spelled out. Today’s question was uncomfortable and simple: what does that runbook cost at deploy time? The answer was operator memory. Four paste blocks, an order that must not shuffle, and two template gotchas that produce silence instead of errors if you get them wrong. That is not a runbook; that is a memorization exam.

So the runbook learned to run itself. One script now carries the whole post-install shaping for a fresh authority container:

  1. Assert, then touch. Before mutating anything, the script reads the live configuration and checks the branding values match what we pinned. Mismatch means stop with a message pointing at the cause — never a silent rewrite of an initialized authority to match a typo.
  2. Apply the leaf policy to both provisioners: the per-zone organizational-unit template (a name in the infrastructure zone gets OU=Infrastructure, and so on), plus duration bounds. The two gotchas from yesterday — reversed argument order in template conditionals, template data resolving at the wrong level — are baked in where they were learned rather than documented where they can be skimmed.
  3. Refuse to pass a broken gate. It then mints two probe certificates: one in-zone, which must carry its zone’s unit name, one deliberately out-of-zone, which must fall back cleanly. It also checks the issued certificates actually advertise their revocation and issuer URLs. Any wrong answer aborts loudly with a remediation hint, because we have already lived through templates that “ran fine” while stamping fallbacks onto everything.
  4. Install the companion UI: binary, config, first admin account, systemd unit, then wait until the login page actually answers.

Testing honestly

My bias is live-fire over unit-green, so a script whose real job happens on another machine deserves a confession about what was actually tested. The syntax check and static analysis pass clean. Every guard path — no arguments, missing tooling, missing credentials, bad paths — was exercised against a stubbed copy where only the safety checks remained real. What is not proven yet is the happy path against a running authority; that proof is built into deploy day as the smoke gate itself. A script that verifies its own work on the target is better than one verified somewhere else, as long as everyone knows which kind they are holding. This one says so right in its output.

Small bonus lesson from the static analysis run: under strict shell mode, a help screen that echoes configuration override names instead of resolved values will crash the moment someone asks for help before setting them. Print what you resolved, not what you hope for.

Where this leaves us

Deploy day is now five moves: pick the host, add the DNS record, answer the installer prompts exactly, copy the UI binary over, run one command. Everything between those steps either happens mechanically or fails loudly. The script lives next to the systemd unit in the UI repo, so a redeploy is the same command — and when the lab eventually grows its own artifact store, the copy step swaps out without touching anything else.

Next session on this workstream should be the real thing: node picked, address assigned, authority live, and the zone matrix minted from production instead of a rig.