fix(install): prompt via /dev/tty so 'curl | bash' is interactive

- prompts (location, OpenCode activation, web UI service, collision strategy) now read from the controlling terminal, not stdin
- have_tty()/tty_read() helpers; all read sites routed through tty_read
- previously 'curl | bash' had no TTY on stdin, so every prompt was silently skipped
- README: note the one-liner is interactive (and falls back to safe defaults in CI)
This commit is contained in:
Claus Lohmar 2026-10-13 07:36:11 +00:00
parent b2939d525a
commit 169c900805
2 changed files with 37 additions and 17 deletions

View file

@ -144,6 +144,8 @@ The installer is **all-in-one**: if OpenCode isn't already installed, it bootstr
curl -fsSL https://git.lohmar.co.uk/cclohmar/NewOpenAgents/raw/branch/main/install.sh | bash
```
Even though the script arrives over a pipe, it is **interactive**: it reads your answers from the terminal (`/dev/tty`). In a non-terminal context (CI), it runs with safe defaults instead of prompting.
### Or clone, then install (recommended for updates)
```bash

View file

@ -71,6 +71,24 @@ error() { printf "${RED}✗${NC} %s\n" "$1" >&2; }
step() { printf "\n${MAGENTA}${BOLD}▶${NC} %s\n\n" "$1"; }
die() { error "$1"; exit 1; }
# Prompting — works even under `curl … | bash`, where stdin is the script pipe.
# We prompt on the controlling terminal instead of relying on stdin.
have_tty() {
[ -t 1 ] || return 1
[ -t 0 ] && return 0
[ -r /dev/tty ]
}
tty_read() {
local __var="$1" __val=""
if [ -t 0 ] || [ ! -r /dev/tty ]; then
read -r __val || true
else
read -r __val </dev/tty || true
fi
printf -v "$__var" '%s' "$__val"
}
print_header() {
printf "${CYAN}${BOLD}"
cat <<'BANNER'
@ -230,9 +248,9 @@ activate_opencode() {
info "Add it to your shell profile: export PATH=\"${OPENCODE_BIN_DIR}:\$PATH\""
fi
if [ -t 0 ] && [ -t 1 ]; then
if have_tty; then
printf "${BOLD}Make 'opencode' available now (symlink into /usr/local/bin)?${NC} [y/N]: "
ans=""; read -r ans || true
ans=""; tty_read ans
case "$ans" in
[Yy]*)
local sudo=""
@ -271,10 +289,10 @@ ensure_opencode() {
return 0
fi
if [ "$OPENCODE_MODE" = "auto" ] && ! have_opencode && [ -t 0 ] && [ -t 1 ]; then
if [ "$OPENCODE_MODE" = "auto" ] && ! have_opencode && have_tty; then
printf "${BOLD}OpenCode is not installed. Install it now?${NC} [Y/n]: "
ans=""
read -r ans || true
tty_read ans
case "$ans" in
[Nn]*) info "Skipping OpenCode."; return 0 ;;
esac
@ -328,7 +346,7 @@ setup_service() {
local interactive=0
if [ "$SERVICE_MODE" = "auto" ]; then
if [ -t 0 ] && [ -t 1 ]; then
if have_tty; then
interactive=1
else
return 0 # non-interactive: only set up the service when explicitly asked
@ -337,7 +355,7 @@ setup_service() {
if [ "$interactive" = 1 ]; then
printf "\n${BOLD}Set up the OpenCode web UI as a systemd service?${NC} [y/N]: "
ans=""; read -r ans || true
ans=""; tty_read ans
case "$ans" in [Yy]*) ;; *) info "Skipping the web UI service."; return 0 ;; esac
fi
@ -357,10 +375,10 @@ setup_service() {
g="$(id -gn)"
if [ "$interactive" = 1 ]; then
printf " Bind address [%s]: " "$host"; v=""; read -r v || true; [ -n "$v" ] && host="$v"
printf " Port [%s]: " "$port"; v=""; read -r v || true; [ -n "$v" ] && port="$v"
printf " Working directory [%s]: " "$dir"; v=""; read -r v || true; [ -n "$v" ] && dir="$v"
printf " Start now and enable on boot? [Y/n]: "; v=""; read -r v || true
printf " Bind address [%s]: " "$host"; v=""; tty_read v; [ -n "$v" ] && host="$v"
printf " Port [%s]: " "$port"; v=""; tty_read v; [ -n "$v" ] && port="$v"
printf " Working directory [%s]: " "$dir"; v=""; tty_read v; [ -n "$v" ] && dir="$v"
printf " Start now and enable on boot? [Y/n]: "; v=""; tty_read v
case "$v" in [Nn]*) enable=0 ;; esac
fi
@ -395,7 +413,7 @@ UNIT
cat "$unit"
echo "------------------------------------------------------------"
printf "Install to /etc/systemd/system/%s? [y/N]: " "$SERVICE_UNIT_NAME"
ans=""; read -r ans || true
ans=""; tty_read ans
case "$ans" in
[Yy]*) ;;
*) rm -f "$unit"; info "Skipping the web UI service."; return 0 ;;
@ -482,11 +500,11 @@ choose_location_interactive() {
echo -e " 4) Cancel"
echo
printf "Enter your choice [1-4]: "
read -r choice
tty_read choice
case "$choice" in
1) INSTALL_DIR="$(pwd)/.opencode" ;;
2) INSTALL_DIR="$gp" ;;
3) printf "Enter installation path: "; read -r custom
3) printf "Enter installation path: "; tty_read custom
[ -n "$custom" ] || die "No path entered"
case "$custom" in "~"*) custom="${HOME}${custom#\~}" ;; esac
INSTALL_DIR="$custom" ;;
@ -504,7 +522,7 @@ resolve_install_dir() {
global) INSTALL_DIR="$(global_path)" ;;
custom) die "--install-dir requires a path" ;;
*)
if [ -t 0 ] && [ -t 1 ]; then
if have_tty; then
choose_location_interactive
else
INSTALL_DIR="$(pwd)/.opencode"
@ -530,10 +548,10 @@ do_choose_strategy() {
echo -e " ${RED}4)${NC} Cancel"
echo
printf "Enter your choice [1-4]: "
read -r choice
tty_read choice
case "$choice" in
1) STRATEGY="skip" ;;
2) printf "Type 'yes' to confirm overwrite: "; read -r c
2) printf "Type 'yes' to confirm overwrite: "; tty_read c
[ "$c" = "yes" ] || { info "Cancelled."; exit 0; }
STRATEGY="overwrite" ;;
3) STRATEGY="backup" ;;
@ -585,7 +603,7 @@ perform_install() {
elif [ "$ASSUME_YES" = 1 ]; then
STRATEGY="skip"
info "Non-interactive: existing items will be skipped"
elif [ -t 0 ] && [ -t 1 ]; then
elif have_tty; then
do_choose_strategy
else
STRATEGY="skip"