diff --git a/cli/test/installer/installer.bats b/cli/test/installer/installer.bats index 0e1c0477..4197747a 100644 --- a/cli/test/installer/installer.bats +++ b/cli/test/installer/installer.bats @@ -464,3 +464,57 @@ FAKEID # SANDCAT_HOME dir removed since it was empty after cli/ deletion [ ! -d "$sandcat_home" ] } + +# Runs a shell command under a pseudo-terminal and types $1 on it. +# Stdin is held open briefly because macOS `script` forwards EOF to the +# terminal at once, and it could arrive before the typed answer. +_run_with_tty() { + local answer=$1 cmd=$2 + if [ "$(uname)" = "Darwin" ]; then + (printf '%s\n' "$answer"; sleep 2) | script -q /dev/null sh -c "$cmd" + else + (printf '%s\n' "$answer"; sleep 2) | script -q -e -c "$cmd" /dev/null + fi +} + +_install_fixture() { + SANDCAT_HOME="$1" \ + SANDCAT_BIN_DIR="$2" \ + SANDCAT_NON_INTERACTIVE=true \ + PATH="$FAKE_BIN:$PATH" bash "$INSTALL_SH" +} + +@test "install.sh piped into a shell reads the overwrite answer from the terminal" { + _stub_curl_with_fixture master + + local sandcat_home="$BATS_TEST_TMPDIR/home/.local/share/sandcat" + local sandcat_bin="$BATS_TEST_TMPDIR/home/.local/bin" + _install_fixture "$sandcat_home" "$sandcat_bin" + touch "$sandcat_home/cli/stale" + + SANDCAT_HOME="$sandcat_home" \ + SANDCAT_BIN_DIR="$sandcat_bin" \ + PATH="$FAKE_BIN:$PATH" \ + run _run_with_tty y "cat '$INSTALL_SH' | sh" + assert_success + assert_output --partial "Overwrite?" + assert_output --partial "Installed sandcat" + [ ! -e "$sandcat_home/cli/stale" ] +} + +@test "install.sh piped into a shell keeps the existing install on an empty answer" { + _stub_curl_with_fixture master + + local sandcat_home="$BATS_TEST_TMPDIR/home/.local/share/sandcat" + local sandcat_bin="$BATS_TEST_TMPDIR/home/.local/bin" + _install_fixture "$sandcat_home" "$sandcat_bin" + touch "$sandcat_home/cli/stale" + + SANDCAT_HOME="$sandcat_home" \ + SANDCAT_BIN_DIR="$sandcat_bin" \ + PATH="$FAKE_BIN:$PATH" \ + run _run_with_tty "" "cat '$INSTALL_SH' | sh" + assert_success + assert_output --partial "Aborted" + [ -e "$sandcat_home/cli/stale" ] +} diff --git a/docs/installation.md b/docs/installation.md index a6283972..d71374f5 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -24,9 +24,11 @@ curl -fsSL https://raw.githubusercontent.com/VirtusLab/sandcat/master/install.sh Ensure `~/.local/bin` is on your `PATH` (the installer prints a hint if it isn't), then jump to [Initialize the sandbox](getting-started/quickstart.md#2-initialize-the-sandbox-for-your-project). -**Upgrade:** re-run the same command. The installer atomically swaps the -existing install; `~/.config/sandcat/` (user settings) is never touched. -Combine with `SANDCAT_REF` to jump to a different branch/tag/commit: +**Upgrade:** re-run the same command and answer `y` when asked to overwrite +the existing install (set `SANDCAT_NON_INTERACTIVE=true` to skip the +question). The installer atomically swaps the existing install; +`~/.config/sandcat/` (user settings) is never touched. Combine with +`SANDCAT_REF` to jump to a different branch/tag/commit: ```bash curl -fsSL https://raw.githubusercontent.com/VirtusLab/sandcat/master/install.sh | SANDCAT_REF=v1.0.0 sh diff --git a/install.sh b/install.sh index 6693386c..8d4b90aa 100644 --- a/install.sh +++ b/install.sh @@ -10,7 +10,9 @@ # SANDCAT_REF branch/tag/commit to install (default: master) # SANDCAT_NON_INTERACTIVE skip prompts when set to "true" (default: false) -set -euo pipefail +# No pipefail: dash (sh on Debian/Ubuntu) lacks it, and no pipeline here +# relies on it. +set -eu # --- Logging helpers --------------------------------------------------------- @@ -36,8 +38,9 @@ cleanup() { fi } +# With set -e, a failing command exits with its status, and the EXIT trap +# then runs cleanup. (An ERR trap would not be portable to dash.) trap 'cleanup' EXIT -trap 'cleanup; exit 1' ERR # --- Argument parsing -------------------------------------------------------- @@ -214,7 +217,14 @@ prompt_yes_no() { fi printf '%s [y/N]: ' "$msg" >&2 local reply - IFS= read -r reply || reply="" + # When piped (curl ... | sh), stdin is the script itself, so ask the + # terminal directly. Without one (CI without SANDCAT_NON_INTERACTIVE), + # the answer stays empty and defaults to "no". + if [ -t 0 ]; then + IFS= read -r reply || reply="" + else + { IFS= read -r reply < /dev/tty; } 2>/dev/null || reply="" + fi case "$reply" in y|Y|yes|YES) return 0 ;; *) return 1 ;;