From 5e83ecc2ef6cd6208253f703d1c5c1f6366bf56b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 16 Apr 2026 22:00:13 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20[nomad-step-2]=20S2-fix-F=20=E2=80=94=20?= =?UTF-8?q?wire=20tools/vault-seed-.sh=20into=20bin/disinto=20--with?= =?UTF-8?q?=20=20(#928)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `tools/vault-seed-forgejo.sh` existed and worked, but `bin/disinto init --backend=nomad --with forgejo` never invoked it, so a fresh LXC with an empty Vault hit `Template Missing: vault.read(kv/data/disinto/shared/ forgejo)` and the forgejo alloc timed out inside deploy.sh's 240s healthy_deadline — operator had to run the seeder + `nomad alloc restart` by hand to recover. In `_disinto_init_nomad`, after `vault-import.sh` (or its skip branch) and before `deploy.sh`, iterate `--with ` and auto-invoke `tools/vault-seed-.sh` when the file exists + is executable. Services without a seeder are silently skipped — Step 3+ services (woodpecker, chat, etc.) can ship their own seeder without touching `bin/disinto`. VAULT_ADDR is passed explicitly because cluster-up.sh writes the profile.d export during this same init run (current shell hasn't sourced it yet) and `vault-seed-forgejo.sh` — unlike its sibling vault-* scripts — requires the caller to set VAULT_ADDR instead of defaulting it via `_hvault_default_env`. Mirror the loop in the --dry-run plan so the operator-visible plan matches the real run. Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/disinto | 59 ++++++++++++++++++++++++++++++++++- tests/disinto-init-nomad.bats | 22 +++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/bin/disinto b/bin/disinto index f9bfe04..0a78db6 100755 --- a/bin/disinto +++ b/bin/disinto @@ -783,9 +783,29 @@ _disinto_init_nomad() { fi if [ -n "$with_services" ]; then + # Vault seed plan (S2.6, #928): one line per service whose + # tools/vault-seed-.sh ships. Services without a seeder are + # silently skipped — the real-run loop below mirrors this, + # making `--with woodpecker` in Step 3 auto-invoke + # tools/vault-seed-woodpecker.sh once that file lands without + # any further change to bin/disinto. + local seed_hdr_printed=false + local IFS=',' + for svc in $with_services; do + svc=$(echo "$svc" | xargs) # trim whitespace + local seed_script="${FACTORY_ROOT}/tools/vault-seed-${svc}.sh" + if [ -x "$seed_script" ]; then + if [ "$seed_hdr_printed" = false ]; then + echo "── Vault seed dry-run ─────────────────────────────────" + seed_hdr_printed=true + fi + echo "[seed] [dry-run] ${seed_script} --dry-run" + fi + done + [ "$seed_hdr_printed" = true ] && echo "" + echo "── Deploy services dry-run ────────────────────────────" echo "[deploy] services to deploy: ${with_services}" - local IFS=',' for svc in $with_services; do svc=$(echo "$svc" | xargs) # trim whitespace # Validate known services first @@ -893,6 +913,43 @@ _disinto_init_nomad() { echo "[import] no --import-env/--import-sops — skipping; set them or seed kv/disinto/* manually before deploying secret-dependent services" fi + # Seed Vault for services that ship their own seeder (S2.6, #928). + # Convention: tools/vault-seed-.sh — auto-invoked when --with + # is requested. Runs AFTER vault-import so that real imported values + # win over generated seeds when both are present; each seeder is + # idempotent on a per-key basis (see vault-seed-forgejo.sh's + # "missing → generate, present → unchanged" contract), so re-running + # init does not rotate existing keys. Services without a seeder are + # silently skipped — keeps this loop forward-compatible with Step 3+ + # services that may ship their own seeder without touching bin/disinto. + # + # VAULT_ADDR is passed explicitly because cluster-up.sh writes the + # profile.d export *during* this same init run, so the current shell + # hasn't sourced it yet; sibling vault-* scripts (engines/policies/ + # auth/import) default VAULT_ADDR internally via _hvault_default_env, + # but vault-seed-forgejo.sh requires the caller to set it. + if [ -n "$with_services" ]; then + local vault_addr="${VAULT_ADDR:-http://127.0.0.1:8200}" + local IFS=',' + for svc in $with_services; do + svc=$(echo "$svc" | xargs) # trim whitespace + local seed_script="${FACTORY_ROOT}/tools/vault-seed-${svc}.sh" + if [ -x "$seed_script" ]; then + echo "" + echo "── Seeding Vault for ${svc} ───────────────────────────" + if [ "$(id -u)" -eq 0 ]; then + VAULT_ADDR="$vault_addr" "$seed_script" || exit $? + else + if ! command -v sudo >/dev/null 2>&1; then + echo "Error: vault-seed-${svc}.sh must run as root and sudo is not installed" >&2 + exit 1 + fi + sudo -n "VAULT_ADDR=$vault_addr" -- "$seed_script" || exit $? + fi + fi + done + fi + # Deploy services if requested if [ -n "$with_services" ]; then echo "" diff --git a/tests/disinto-init-nomad.bats b/tests/disinto-init-nomad.bats index f38805e..8467ebb 100644 --- a/tests/disinto-init-nomad.bats +++ b/tests/disinto-init-nomad.bats @@ -155,6 +155,28 @@ setup_file() { [[ "$output" == *"[deploy] dry-run complete"* ]] } +# S2.6 / #928 — every --with that ships tools/vault-seed-.sh +# must auto-invoke the seeder before deploy.sh runs. forgejo is the +# only service with a seeder today, so the dry-run plan must include +# its seed line when --with forgejo is set. The seed block must also +# appear BEFORE the deploy block (seeded secrets must exist before +# nomad reads the template stanza) — pinned here by scanning output +# order. Services without a seeder (e.g. unknown hypothetical future +# ones) are silently skipped by the loop convention. +@test "disinto init --backend=nomad --with forgejo --dry-run prints seed plan before deploy plan" { + run "$DISINTO_BIN" init placeholder/repo --backend=nomad --with forgejo --dry-run + [ "$status" -eq 0 ] + [[ "$output" == *"Vault seed dry-run"* ]] + [[ "$output" == *"tools/vault-seed-forgejo.sh --dry-run"* ]] + # Order: seed header must appear before deploy header. + local seed_line deploy_line + seed_line=$(echo "$output" | grep -n "Vault seed dry-run" | head -1 | cut -d: -f1) + deploy_line=$(echo "$output" | grep -n "Deploy services dry-run" | head -1 | cut -d: -f1) + [ -n "$seed_line" ] + [ -n "$deploy_line" ] + [ "$seed_line" -lt "$deploy_line" ] +} + @test "disinto init --backend=nomad --with forgejo,forgejo --dry-run handles comma-separated services" { run "$DISINTO_BIN" init placeholder/repo --backend=nomad --with forgejo,forgejo --dry-run [ "$status" -eq 0 ]