Skip to content

Drive init from the E2E and preflight scripts with flags instead of piped prompt answers (#822) - #823

Merged
AcoPiper merged 1 commit into
mainfrom
AcoPiper/issue-822
Aug 13, 2026
Merged

Drive init from the E2E and preflight scripts with flags instead of piped prompt answers (#822)#823
AcoPiper merged 1 commit into
mainfrom
AcoPiper/issue-822

Conversation

@AcoPiper

@AcoPiper AcoPiper commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Every bootroot init invocation under scripts/ was driven non-interactively by piping a fixed answer sequence into it. How many prompts a run actually reaches is state-dependent — an overwrite confirmation for each of secrets/password.txt, secrets/config/ca.json and state.json that exists on disk, plus a db-provision confirmation — so those sequences lined up only because a cleanup step earlier in the same script had left the right files absent. Since init fails on EOF rather than answering a prompt itself, that invisible coupling breaks the run the moment a cleanup step, a flag or the prompt set changes.

Each prompt that has a non-interactive flag is now supplied with that flag (--overwrite-password, --overwrite-ca-json, --overwrite-state, --confirm-db-provision, --no-eab, and --save-unseal-keys / --no-save-unseal-keys), and the invocations left with nothing to answer read from /dev/null. An overwrite flag whose file is absent is a silent no-op, so the same command works on a clean workspace and on a rerun.

Two piped answers remain, both in scripts/preflight/ci/test-core.sh, each commented with the prompt it answers. Only the declined branch of the save-unseal-keys prompt echoes the keys in cleartext, and that echo is exactly what the zero-config assertion reads; --no-save-unseal-keys suppresses it, and additionally requires a --summary-json neither smoke run writes.

The three scripts/impl/ invocations that wrote BOOTROOT_LANG=en on the left of the pipe bound it to printf, leaving the bootroot run whose English output the script parses to take the caller's ambient locale. With the pipe gone, the prefix binds to the run it was written for.

The bootroot init excerpts in docs/en/e2e-ci.md and docs/ko/e2e-ci.md were updated to match the scripts they document; the remote excerpt had also drifted, still showing --eab-kid / --eab-hmac for a script that passes --no-eab.

No Rust changed: init's EOF behaviour, the prompts it asks, and the flags it accepts are all untouched.

Closes #822

Test plan

  • scripts/preflight/ci/test-core.sh completes, including its cleartext unseal-key assertion (grep -q "unseal key" zero-config-init.log)
  • scripts/preflight/extra/cli-scenarios.sh completes
  • scripts/preflight/ci/e2e-matrix.sh completes, covering the local, remote and reinit-recovery lifecycle scripts
  • scripts/preflight/ci/e2e-extended.sh completes, covering the extended suite that runs scripts/impl/run-ca-key-rotation-recovery.sh
  • The Docker E2E matrix jobs (test-docker-e2e-matrix) pass in CI on this PR
  • run-extended from e2e-extended.yml passes
  • ./scripts/check-docs.sh passes (verified locally)
  • Each script still runs standalone as well as through scripts/preflight/run-all.sh
  • No init invocation under scripts/ depends on the on-disk state of password.txt, ca.json or state.json for its answer sequence to be long enough
  • Every remaining piped answer names the prompt it answers in a comment

Every script that ran `bootroot init` non-interactively piped a fixed
answer sequence into it, and how many prompts the run reached depended
on what was on disk: an overwrite confirmation for each of
password.txt, ca.json and state.json that happened to exist, plus a
db-provision confirmation. The sequences lined up only because a
cleanup step earlier in the same script had removed the right files.
That coupling was invisible at the call site, and since init fails on
EOF rather than answering a prompt itself, changing a cleanup step, a
flag or the prompt set aborts the run.

Each prompt that has a non-interactive flag is now supplied with that
flag, and the runs that need no answer at all read from /dev/null. An
overwrite flag whose file is absent is a silent no-op, so the same
invocation works on a clean workspace and on a rerun.

Two answers remain, both in test-core.sh, and both are commented with
the prompt they answer: only the declined branch of the
save-unseal-keys prompt echoes the keys in cleartext, which the
zero-config assertion reads, and `--no-save-unseal-keys` suppresses
that echo as well as requiring a `--summary-json` neither smoke run
writes.

The three impl scripts that wrote `BOOTROOT_LANG=en` on the left of
the pipe bound it to `printf`, leaving the run whose English output
they parse to take the caller's locale. With the pipe gone the prefix
binds to the run it was written for.

Closes #822
@AcoPiper

Copy link
Copy Markdown
Contributor Author

Test plan executed. Everything under scripts/ that this PR touches was run, plus the extended suite on a Linux runner.

Local (macOS, Docker Desktop)

Ran Result
scripts/preflight/ci/test-core.sh pass — PASS: unseal keys displayed in cleartext after declining save
scripts/preflight/extra/cli-scenarios.sh pass
run-local-lifecycle.sh, run-remote-lifecycle.sh, run-rotation-recovery.sh, run-reinit-recovery.sh, run-stepca-san.sh, run-openbao-tls-no-delta.sh, run-two-instance-isolation.sh pass
scripts/preflight/ci/check.sh, validate-deploy-compose.sh, validate-compose-instance-names.sh, deploy-no-build-smoke.sh, agent-scenarios.sh happy pass
./scripts/check-docs.sh pass

Both init invocations in test-core.sh reach Save unseal keys to file for automatic unseal? [y/N]:, consume the piped n and print the keys in cleartext, so the assertion passes through the declined branch deliberately rather than through EOF.

The overwrite flags were also exercised against a dirty workspace: the first test-core.sh run started with secrets/password.txt, secrets/config/ca.json and state.json all present from an earlier run, and init overwrote each without a prompt.

CI

  • Docker E2E matrix jobs on this PR: all green.
  • e2e-extended.yml only triggers on schedule/dispatch, so it had never run against this branch. Dispatched it on AcoPiper/issue-822: run 31657572857Run Extended Docker E2E succeeded, which covers run-ca-key-rotation-recovery.sh.

Not verifiable on this host (all pre-existing environment constraints, none of them touched by this PR):

  • The hosts-mode lifecycle steps and run-openbao-tls-reown.sh need non-interactive sudo; covered by the green CI jobs.
  • run-reinit-recovery.sh, run-stepca-san.sh and run-openbao-tls-no-delta.sh default their bind host to the docker0 gateway 172.17.0.1, which exists on Linux only, and the first also probes interfaces with ip. All three pass locally once OPENBAO_BIND_HOST / STEPCA_BIND_HOST names a real interface — which is exactly what those scripts' own error messages tell you to do.
  • In the local extended run, runner-timer and runner-cron fail because this machine's hostname (Acos-MacBook-Pro.local) is not a DNS label, so bootroot-remote rejects --profile-hostname. ca-key-recovery, scale-contention, failure-recovery and infra-lifecycle passed locally, and the whole suite passed on the Linux runner above.

For that reason scripts/preflight/run-all.sh was not run as a single end-to-end invocation — it cannot complete on this host — so that box is left unchecked. Every script it invokes was run individually instead, with the results above.

One observation outside this issue's scope: .github/workflows/ci.yml carries its own inline copies of the init smoke invocations (Zero-config Init, CLI Init (Smoke)), and those still pipe answers and still write BOOTROOT_LANG=en printf ... | with the assignment bound to printf. They pass today because a fresh runner reaches fewer prompts, which is the same latent coupling this issue removed from scripts/. Worth a follow-up.

@AcoPiper

Copy link
Copy Markdown
Contributor Author

[Reviewer Round 1]

I found no blocking issue. The changed init call sites consistently use the dedicated overwrite, database-provision, EAB, and unseal-key flags, so a leftover password.txt, ca.json, or state.json can no longer consume an answer intended for a later prompt. The /dev/null redirections also make an unexpected future prompt fail visibly.

scripts/preflight/ci/test-core.sh:28 and :61 correctly retain one commented n for the declined save-unseal-keys path; this preserves the cleartext-key assertion without attempting to use --no-save-unseal-keys, which would suppress that output. The lifecycle calls use --save-unseal-keys with their existing summary output, and cli-scenarios.sh uses the safe --no-save-unseal-keys plus its existing summary JSON.

The English and Korean E2E excerpts now reflect the corresponding non-interactive invocations, including the previously stale remote EAB setup. PR linkage and test-plan structure are also in place.

@AcoPiper

Copy link
Copy Markdown
Contributor Author

[Review Verdict Round 1: APPROVED]

@AcoPiper
AcoPiper merged commit c8c94d3 into main Aug 13, 2026
21 checks passed
@AcoPiper
AcoPiper deleted the AcoPiper/issue-822 branch August 13, 2026 02:32
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.

Drive init from the E2E and preflight scripts with flags instead of piped prompt answers

1 participant