eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1# πŸ€– The one way scripts talk to the Bitwarden CLI. Sourced (not run) by
2# release-server.sh, doctor.sh, ops-dev.sh β€” POSIX sh.
3#
4# Two rules, learned the hard way:
5#
6# 1. **bw never prompts β€” we do.** bw's inquirer prompt writes to stdout, so
7# inside `$(...)` it sits invisible and the script "hangs" until a stray
8# Enter redraws it (the doctor lesson, then the release-server lesson).
9# Instead, OUR prompt (stty -echo + a plain `read -r`, POSIX β€” dash has
10# no `read -s`) reads /dev/tty with the label on stderr β€”
11# always visible, capture-proof β€” and the password reaches bw through
12# --passwordenv: never argv (ps would show it), never disk, unset the
13# moment the unlock returns. bw is a non-interactive backend, full stop.
14# (The one exception is `bw login` β€” 2FA is genuinely interactive and
15# rare; scripts detect "unauthenticated" and hand the human that ONE
16# command to run themselves.)
17#
18# 2. **Every bw call gets a watchdog.** bw's API calls carry no timeout and
19# can stall for good on a bad connection or stale session (a promote sat
20# 14 silent minutes in `bw get` 2026-07-21; another hung indefinitely in
21# `bw unlock` 2026-07-24). perl's alarm is the never-a-hang promise β€”
22# the timer survives exec, and macOS ships perl.
23
24# Rule 1, enforced globally: with BW_NOINTERACTION set, a locked-vault
25# `bw get`/`bw list` fails immediately instead of prompting β€” without it, bw's
26# invisible in-pipe prompt eats a stray Enter before falling through to OUR
27# unlock prompt (the "I still had to press Enter" papercut, 2026-07-25).
28# Exported at source time so every bw call in the sourcing script is covered.
29BW_NOINTERACTION=true
30export BW_NOINTERACTION
31
32# bw_run <seconds> <bw-args...> β€” run one bw command under a watchdog.
33# Exit status is bw's, or SIGALRM's (142) on a stall. stdin is pinned to
34# /dev/null β€” none of our calls (get/list/sync/status) legitimately read it,
35# so nothing bw does can ever wait on the operator's keyboard.
36bw_run() {
37 _bw_t="$1"; shift
38 perl -e "alarm $_bw_t; exec @ARGV" bw "$@" </dev/null
39}
40
41# bw_unlock_session β€” unlock the vault with OUR prompt and print the session
42# token on stdout (nothing else touches stdout). Callers:
43#
44# BW_SESSION="$(bw_unlock_session)" && export BW_SESSION || <degrade>
45#
46# Fails (rather than blocking) without a tty: headless contexts export
47# BW_SESSION themselves or use the explicit env-key overrides.
48bw_unlock_session() {
49 if [ ! -e /dev/tty ]; then
50 echo "bw: no tty to prompt on β€” export BW_SESSION (bw unlock --raw) first" >&2
51 return 1
52 fi
53 printf "Bitwarden master password (typing is hidden): " >&2
54 # `read -s` is a bashism β€” Debian's /bin/sh (dash) rejects it with
55 # "Illegal option -s" (bit an operator mid-publish, 2026-08-06). Hide the
56 # typing the POSIX way: drop tty echo around a plain `read -r`. Restore on
57 # every exit from the read, including ^C at the prompt (restore, then
58 # re-raise INT so the script still dies) β€” a password prompt that leaves
59 # the terminal echo-less is its own papercut. The `|| true` tails keep
60 # `set -e` sourcing scripts alive when there's no stty to save (odd ttys).
61 _bw_stty="$(stty -g </dev/tty 2>/dev/null || true)"
62 _bw_restore_tty() { [ -n "$_bw_stty" ] && stty "$_bw_stty" </dev/tty 2>/dev/null || true; }
63 trap '_bw_restore_tty; trap - INT; kill -INT $$' INT
64 [ -n "$_bw_stty" ] && stty -echo </dev/tty 2>/dev/null || true
65 IFS= read -r LCHAT_BW_PASSWORD </dev/tty || {
66 _bw_restore_tty
67 trap - INT
68 echo >&2
69 unset LCHAT_BW_PASSWORD
70 return 1
71 }
72 _bw_restore_tty
73 trap - INT
74 echo >&2
75 # 90s: the KDF is seconds, but bw's first server round-trip gets headroom.
76 _bw_sess="$(LCHAT_BW_PASSWORD="$LCHAT_BW_PASSWORD" perl -e 'alarm 90; exec @ARGV' \
77 bw unlock --raw --passwordenv LCHAT_BW_PASSWORD </dev/null)" || _bw_sess=""
78 unset LCHAT_BW_PASSWORD
79 if [ -z "$_bw_sess" ]; then
80 echo "bw: unlock failed or stalled β€” run 'bw sync' and retry, or export BW_SESSION yourself" >&2
81 return 1
82 fi
83 printf '%s\n' "$_bw_sess"
84}
85