Fix: whiptail-TUI reagiert nicht auf Tastatureingaben bei curl | bash

sudo, aus einer nicht-interaktiven gepipten Shell heraus aufgerufen, gibt die
Terminal-Vordergrundgruppe nicht zuverlässig an den Kindprozess weiter.
setup.sh lief bisher komplett (inkl. aller whiptail-Dialoge) innerhalb von
sudo, wodurch das Menü zwar sichtbar war, aber keine Tasten mehr ankamen.

setup.sh fragt jetzt alle interaktiven Eingaben (Stage-Auswahl, Zielbenutzer,
Ollama-Pfad) zuerst als normaler Nutzer ab und eskaliert erst danach per
exec sudo, wobei die bereits getroffenen Entscheidungen als Flags/Env-Variablen
durchgereicht werden. install.sh eskaliert dadurch nicht mehr selbst.
Zusätzlich wird die fehlerhafte "-c /dev/tty"-Interaktivitätsprüfung
(prüfte nur Existenz der Gerätedatei, nicht ob ein Terminal verbunden ist)
durch has_tty() ersetzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
This commit is contained in:
2026-09-06 20:31:54 +02:00
co-authored by Claude Sonnet 5
parent 116a33a876
commit 5a8dd73d32
4 changed files with 68 additions and 37 deletions
+7 -5
View File
@@ -23,29 +23,31 @@ curl -fsSL https://gitea.creative-dragonslayer.de/Scripts/Setup/raw/branch/main/
### 2. Manuelles Klonen & Starten
Alternativ kann das Repository manuell geklont und ausgeführt werden:
```bash
git clone https://gitea.creative-dragonslayer.de/Scripts/Setup.git && cd Setup && sudo ./setup.sh
git clone https://gitea.creative-dragonslayer.de/Scripts/Setup.git && cd Setup && ./setup.sh
```
Oder direkt vollautomatisch alle Stages (`00` bis `10`) ausführen:
```bash
git clone https://gitea.creative-dragonslayer.de/Scripts/Setup.git && cd Setup && sudo ./setup.sh --all
git clone https://gitea.creative-dragonslayer.de/Scripts/Setup.git && cd Setup && ./setup.sh --all
```
> `setup.sh` fragt zuerst alle nötigen Eingaben (Modulauswahl, Zielbenutzer, ...) direkt am Terminal ab und eskaliert erst danach selbstständig per `sudo`. Ein manuelles `sudo` davor ist nicht nötig, funktioniert aber ebenfalls.
### 3. Interaktiver TUI-Modus
Starte das grafische Terminal-Menü (`whiptail`) zur flexiblen Auswahl einzelner Module:
```bash
sudo ./setup.sh
./setup.sh
```
### 4. Vollautomatischer CLI-Modus
Führe alle Phasen (`00` bis `10`) ohne Interaktion aus:
```bash
sudo ./setup.sh --all
./setup.sh --all
```
### 5. Gezielte Modulauswahl
Installiere nur bestimmte Phasen (z. B. nur Paketlisten und Schriften):
```bash
sudo ./setup.sh --stages 04,05
./setup.sh --stages 04,05
```
### 6. Simulationslauf (Dry-Run)
+6 -19
View File
@@ -110,25 +110,12 @@ main() {
log_success "Repository cloned successfully. Starting setup orchestrator..."
# Check if dry-run flag is requested
local is_dry_run=0
for arg in "$@"; do
if [[ "$arg" == "-d" || "$arg" == "--dry-run" ]]; then
is_dry_run=1
break
fi
done
# Prepare command to execute setup.sh
local -a setup_cmd=()
if [[ "$(id -u)" -ne 0 ]] && [[ "$is_dry_run" -eq 0 ]]; then
if ! command -v sudo >/dev/null 2>&1; then
log_error "sudo is required to run setup.sh. Please install sudo or run as root."
exit 1
fi
setup_cmd+=(sudo)
fi
setup_cmd+=(./setup.sh "$@")
# setup.sh itself elevates to root via sudo once it needs it - after all
# interactive TUI/prompts have already run as the current user. Escalating
# here instead would wrap the whiptail dialogs in sudo, which (when sudo is
# invoked from this non-interactive piped shell) leaves them unable to
# receive keystrokes. See setup.sh for details.
local -a setup_cmd=(./setup.sh "$@")
# Reconnect stdin/stdout to /dev/tty before executing setup.sh if piped
# (e.g. curl ... | bash), otherwise the interactive TUI receives no keystrokes
+4 -4
View File
@@ -158,14 +158,14 @@ prompt_target_user() {
local chosen=""
local is_interactive=0
if [[ -t 0 || -c /dev/tty ]] && [[ "${DEBIAN_FRONTEND:-}" != "noninteractive" && "${CI:-}" != "1" && "${AUTO_CONFIRM:-}" != "1" ]]; then
if { [[ -t 0 ]] || has_tty; } && [[ "${DEBIAN_FRONTEND:-}" != "noninteractive" && "${CI:-}" != "1" && "${AUTO_CONFIRM:-}" != "1" ]]; then
is_interactive=1
fi
if [[ "$is_interactive" -eq 1 ]]; then
local tty_in="/dev/tty"
local tty_out="/dev/tty"
if [[ ! -c /dev/tty ]]; then
if ! has_tty; then
tty_in="/dev/stdin"
tty_out="/dev/stderr"
fi
@@ -243,14 +243,14 @@ prompt_ollama_models_path() {
local default_path="${OLLAMA_MODELS_DEFAULT:-/mnt/Data/Software/ollama/models}"
local chosen=""
local is_interactive=0
if [[ -t 0 || -c /dev/tty ]] && [[ "${DEBIAN_FRONTEND:-}" != "noninteractive" && "${CI:-}" != "1" && "${AUTO_CONFIRM:-}" != "1" ]]; then
if { [[ -t 0 ]] || has_tty; } && [[ "${DEBIAN_FRONTEND:-}" != "noninteractive" && "${CI:-}" != "1" && "${AUTO_CONFIRM:-}" != "1" ]]; then
is_interactive=1
fi
if [[ "$is_interactive" -eq 1 ]]; then
local tty_in="/dev/tty"
local tty_out="/dev/tty"
if [[ ! -c /dev/tty ]]; then
if ! has_tty; then
tty_in="/dev/stdin"
tty_out="/dev/stderr"
fi
+51 -9
View File
@@ -37,10 +37,14 @@ ensure_term
# Show help text
show_help() {
cat <<EOF
Usage: sudo ./setup.sh [OPTIONS]
Usage: ./setup.sh [OPTIONS]
Debian Unstable Setup & Desktop Customization Framework
Note: setup.sh elevates itself via sudo once needed, after any interactive
prompts have been answered. Run it as your normal desktop user - no need to
prefix it with 'sudo' yourself (though doing so still works).
Options:
-a, --all Run all setup stages sequentially (00 through 10)
-s, --stages <list> Comma-separated list of stages to run (e.g. --stages 01,02,04)
@@ -62,10 +66,10 @@ Available Stages:
10: Hyprland Desktop (LinuxBeginnings Auto-Installer)
Examples:
sudo ./setup.sh # Interactive TUI mode
sudo ./setup.sh --all # Full automated install
sudo ./setup.sh --stages 04,05 # Install only packages and fonts
sudo ./setup.sh --stages 01 --dry-run # Dry-run stage 01
./setup.sh # Interactive TUI mode
./setup.sh --all # Full automated install
./setup.sh --stages 04,05 # Install only packages and fonts
./setup.sh --stages 01 --dry-run # Dry-run stage 01
EOF
}
@@ -121,10 +125,9 @@ if [[ -n "$CLI_USER" ]]; then
export TARGET_USER="$CLI_USER"
fi
# Ensure root privileges unless dry-run
if [[ "$FLAG_DRY_RUN" -eq 0 ]]; then
require_root
fi
# NOTE: Root privileges are intentionally NOT required here. All interactive
# prompts (whiptail TUI, target user, Ollama path) must run BEFORE escalating
# to sudo - see the privilege escalation block below for why.
# Determine stages to run
SELECTED_STAGES=()
@@ -181,6 +184,45 @@ if [[ -z "${TARGET_USER:-}" ]]; then
export TARGET_USER
fi
# Pre-resolve the Ollama models path here (while still unprivileged and
# directly attached to the terminal) if stage 06 will run, so stage
# 06-services.sh never has to prompt again after privilege escalation.
if [[ "$FLAG_DRY_RUN" -eq 0 ]] && [[ -z "${OLLAMA_MODELS:-}" ]]; then
for st in "${SELECTED_STAGES[@]}"; do
if [[ "$st" == "06" ]]; then
OLLAMA_MODELS="$(prompt_ollama_models_path)"
export OLLAMA_MODELS
break
fi
done
fi
# Escalate to root via sudo now that every interactive prompt (whiptail TUI,
# target user, Ollama path) has already been answered while directly attached
# to the terminal. sudo run from a non-interactive shell (e.g. 'curl | bash')
# does not reliably hand the controlling terminal's foreground process group
# to the command it execs, which leaves whiptail/read unable to receive any
# keystrokes. Running all interactive dialogs before this point, and passing
# the already-resolved choices through as flags/env vars, avoids ever needing
# terminal input again after the sudo boundary.
if [[ "$FLAG_DRY_RUN" -eq 0 ]] && [[ "$(id -u)" -ne 0 ]]; then
if ! command_exists sudo; then
log_error "sudo is required to run setup.sh. Please install sudo or run as root."
exit 1
fi
log_info "Elevating privileges via sudo to continue installation..."
joined_stages="$(IFS=,; echo "${SELECTED_STAGES[*]}")"
sudo_env_args=("TARGET_USER=$TARGET_USER")
if [[ -n "${OLLAMA_MODELS:-}" ]]; then
sudo_env_args+=("OLLAMA_MODELS=$OLLAMA_MODELS")
fi
exec sudo "${sudo_env_args[@]}" bash "$SCRIPT_DIR/setup.sh" --stages "$joined_stages" --user "$TARGET_USER"
fi
if [[ "$FLAG_DRY_RUN" -eq 0 ]]; then
require_root
fi
# Map stage identifier to script file
get_stage_script() {
local stage_id="$1"