deploy: POSIX password prompt — dash has no 'read -s' (#1433)
The Bitwarden unlock prompt used 'read -rs', a bashism: on a Debian operator box /bin/sh is dash, which dies with "read: Illegal option -s" mid-publish. Hide typing with stty -echo around a plain read -r instead, restoring the terminal on every exit including ^C at the prompt.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 file changed,
+18
−2
deploy/bw-lib.sh+18 −2
diff --git a/deploy/bw-lib.sh b/deploy/bw-lib.sh| index 50bd9e4..bc606fe 100644 |
| --- a/deploy/bw-lib.sh |
| +++ b/deploy/bw-lib.sh |
| @@ -6,7 +6,8 @@ |
| # 1. **bw never prompts — we do.** bw's inquirer prompt writes to stdout, so |
| # inside `$(...)` it sits invisible and the script "hangs" until a stray |
| # Enter redraws it (the doctor lesson, then the release-server lesson). |
| -# Instead, OUR `read -rs` prompts on /dev/tty with the label on stderr — |
| +# Instead, OUR prompt (stty -echo + a plain `read -r`, POSIX — dash has |
| +# no `read -s`) reads /dev/tty with the label on stderr — |
| # always visible, capture-proof — and the password reaches bw through |
| # --passwordenv: never argv (ps would show it), never disk, unset the |
| # moment the unlock returns. bw is a non-interactive backend, full stop. |
| @@ -50,11 +51,26 @@ bw_unlock_session() { |
| return 1 |
| fi |
| printf "Bitwarden master password (typing is hidden): " >&2 |
| - IFS= read -rs LCHAT_BW_PASSWORD </dev/tty || { |
| + # `read -s` is a bashism — Debian's /bin/sh (dash) rejects it with |
| + # "Illegal option -s" (bit an operator mid-publish, 2026-08-06). Hide the |
| + # typing the POSIX way: drop tty echo around a plain `read -r`. Restore on |
| + # every exit from the read, including ^C at the prompt (restore, then |
| + # re-raise INT so the script still dies) — a password prompt that leaves |
| + # the terminal echo-less is its own papercut. The `|| true` tails keep |
| + # `set -e` sourcing scripts alive when there's no stty to save (odd ttys). |
| + _bw_stty="$(stty -g </dev/tty 2>/dev/null || true)" |
| + _bw_restore_tty() { [ -n "$_bw_stty" ] && stty "$_bw_stty" </dev/tty 2>/dev/null || true; } |
| + trap '_bw_restore_tty; trap - INT; kill -INT $$' INT |
| + [ -n "$_bw_stty" ] && stty -echo </dev/tty 2>/dev/null || true |
| + IFS= read -r LCHAT_BW_PASSWORD </dev/tty || { |
| + _bw_restore_tty |
| + trap - INT |
| echo >&2 |
| unset LCHAT_BW_PASSWORD |
| return 1 |
| } |
| + _bw_restore_tty |
| + trap - INT |
| echo >&2 |
| # 90s: the KDF is seconds, but bw's first server round-trip gets headroom. |
| _bw_sess="$(LCHAT_BW_PASSWORD="$LCHAT_BW_PASSWORD" perl -e 'alarm 90; exec @ARGV' \ |