fix: teardown-host refuses a terminal-less run instead of aborting mute #119
3 changed files with 39 additions and 0 deletions
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -154,6 +154,21 @@ which records not just what changed but what each drill run proved.
|
||||||
wrong default: it is the only thing to roll back *to*, at exactly the
|
wrong default: it is the only thing to roll back *to*, at exactly the
|
||||||
moment that matters. No behaviour change.
|
moment that matters. No behaviour change.
|
||||||
|
|
||||||
|
- **`teardown-host.sh` refuses a terminal-less run instead of aborting mute**
|
||||||
|
(#113) — the most destructive script in the tree was the one that would not
|
||||||
|
tell a non-interactive caller how to proceed. With no `--yes`/`BOX_YES` and
|
||||||
|
no TTY it fell straight into `read`, took the instant EOF and exited 1
|
||||||
|
saying only `aborted` — a refusal that names neither the cause nor the
|
||||||
|
override. It now checks `[ -t 0 ]` and refuses with
|
||||||
|
`--yes (or BOX_YES=1) means yes.`, exit **2** — "you invoked this wrong",
|
||||||
|
the same contract and the same code as `host/revoke-user.sh --purge` and
|
||||||
|
`install.sh`'s `confirm()`, versus 1 for "you were asked and you said no".
|
||||||
|
The gate sits *below* the `--yes`/`BOX_YES` arm, so consent given
|
||||||
|
non-interactively still runs headless — CI's uninstall drill and
|
||||||
|
`box uninstall --purge-host --force` forward `--yes` and are unaffected —
|
||||||
|
and *above* the first `incus` call, so the refusal costs no daemon and
|
||||||
|
`test/cli.sh` drives it for real rather than grepping for it.
|
||||||
|
|
||||||
## 0.8.0 — 2026-07-19
|
## 0.8.0 — 2026-07-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,18 @@ $purge && echo "Incus itself will also be uninstalled (--purge-incus)."
|
||||||
if [ "$yes" -eq 1 ]; then
|
if [ "$yes" -eq 1 ]; then
|
||||||
echo "(confirmed non-interactively: --yes/BOX_YES)"
|
echo "(confirmed non-interactively: --yes/BOX_YES)"
|
||||||
else
|
else
|
||||||
|
# No terminal to ask on, and no consent given: refuse and say how to proceed,
|
||||||
|
# rather than fall into 'read', hit instant EOF and abort with nothing but
|
||||||
|
# "aborted" (#113). This must stay BELOW the --yes/BOX_YES arm above — the
|
||||||
|
# order is the contract: consent given non-interactively still runs headless
|
||||||
|
# (CI's uninstall drill and 'box uninstall --purge-host --force' depend on
|
||||||
|
# it), consent NOT given without a terminal is a usage error, exit 2, the
|
||||||
|
# same shape as host/revoke-user.sh and install.sh. It also lands before the
|
||||||
|
# first 'incus' call below, so the refusal needs no daemon.
|
||||||
|
if [ ! -t 0 ]; then
|
||||||
|
echo "teardown-host: refusing to run without a terminal to confirm on. --yes (or BOX_YES=1) means yes." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
# EOF (Ctrl-D) refuses, out loud: unguarded, errexit would end the run on
|
# EOF (Ctrl-D) refuses, out loud: unguarded, errexit would end the run on
|
||||||
# this line and the 'aborted' below would never print (#111).
|
# this line and the 'aborted' below would never print (#111).
|
||||||
read -rp "Continue? [y/N] " a || { echo "aborted"; exit 1; }
|
read -rp "Continue? [y/N] " a || { echo "aborted"; exit 1; }
|
||||||
|
|
|
||||||
12
test/cli.sh
12
test/cli.sh
|
|
@ -7,6 +7,13 @@
|
||||||
# a fixture, or grep the load-bearing line so a deleted guard cannot ship green.
|
# a fixture, or grep the load-bearing line so a deleted guard cannot ship green.
|
||||||
# Deliberately no `set -e` — the harness asserts on failing commands.
|
# Deliberately no `set -e` — the harness asserts on failing commands.
|
||||||
set -u
|
set -u
|
||||||
|
# BOX_YES is this family's documented automation switch, so an operator's CI
|
||||||
|
# wrapper may well export it. Checks that drive a destructive script for real
|
||||||
|
# would then take the CONSENT arm instead of the refusal they are asserting —
|
||||||
|
# turning this suite into `box uninstall --purge-host` on the host it runs on.
|
||||||
|
# Individual call sites use `env -u BOX_YES`; this is the belt to that braces,
|
||||||
|
# so the header's "runnable anywhere" promise cannot be broken by one export.
|
||||||
|
unset BOX_YES
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
PASS=0 FAIL=0
|
PASS=0 FAIL=0
|
||||||
|
|
||||||
|
|
@ -1993,6 +2000,11 @@ check "teardown-host: honors --yes/BOX_YES (CI runs it unattended)" 0 "" \
|
||||||
grep -qF 'BOX_YES' "$ROOT/host/teardown-host.sh"
|
grep -qF 'BOX_YES' "$ROOT/host/teardown-host.sh"
|
||||||
check "teardown-host: points at box uninstall when done" 0 "" \
|
check "teardown-host: points at box uninstall when done" 0 "" \
|
||||||
grep -qF "box uninstall" "$ROOT/host/teardown-host.sh"
|
grep -qF "box uninstall" "$ROOT/host/teardown-host.sh"
|
||||||
|
# ...and the other side of that contract (#113): consent NOT given and no
|
||||||
|
# terminal to ask on is a usage error, not a mute 'aborted'. Driven for real —
|
||||||
|
# the gate sits above the first 'incus' call, so a daemon-free run reaches it.
|
||||||
|
check "teardown-host: refuses without a TTY and names the override (#113)" 2 \
|
||||||
|
"--yes (or BOX_YES=1) means yes" env -u BOX_YES bash "$ROOT/host/teardown-host.sh" </dev/null
|
||||||
|
|
||||||
# #102's race, in the one other file that sets pipefail. A daemon-free run
|
# #102's race, in the one other file that sets pipefail. A daemon-free run
|
||||||
# cannot exercise a UFW teardown, so the shape is pinned instead: no `ufw
|
# cannot exercise a UFW teardown, so the shape is pinned instead: no `ufw
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue