From c1f385108659078ec1e435d24d357498edbea316 Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Sun, 6 Sep 2026 21:54:47 +0200 Subject: [PATCH] Feature: Bei fehlgeschlagenem Befehl Wiederholen/Ignorieren/Abbrechen abfragen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der ERR-Trap fragt den Benutzer nun interaktiv, wie mit einem fehlgeschlagenen Befehl verfahren werden soll, statt die Installation sofort abzubrechen. Zusätzlich aktiviert setup_err_trap jetzt errtrace/functrace, damit Fehler auch innerhalb von lib/*.sh-Funktionen abgefangen werden statt das Skript stillschweigend zu beenden. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA --- AGENTS.md | 3 ++ lib/utils.sh | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 247c630..cc773e6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -91,6 +91,7 @@ Dieses Repository ist ein modulares, automatisiertes Setup- und Konfigurations-F source "$SCRIPT_DIR/../lib/utils.sh" setup_err_trap ``` +- `setup_err_trap` aktiviert `errtrace`/`functrace`, damit Fehler auch innerhalb von `lib/*.sh`-Funktionen abgefangen werden, und registriert `error_handler` als ERR-Trap. Schlägt ein Befehl fehl, wird der Benutzer interaktiv gefragt, ob der Befehl **wiederholt**, **ignoriert** oder die **Installation abgebrochen** werden soll (`ask_retry_ignore_abort`). In nicht-interaktiven Läufen (`CI=1`, `DEBIAN_FRONTEND=noninteractive`, `AUTO_CONFIRM=1`, keine TTY) wird immer automatisch abgebrochen, wie zuvor. - Verwende für temporäre Verzeichnisse immer `mktemp -d` und stelle sicher, dass am Skriptende oder bei Fehlern aufgeräumt wird: ```bash TMP_DIR="$(mktemp -d)" @@ -123,6 +124,8 @@ Dieses Repository ist ein modulares, automatisiertes Setup- und Konfigurations-F - `log_error "Nachricht"`: Rote Fehlermeldung (`[ERROR]`). - `log_step "Titel"`: Hervorgehobener Abschnitts-Header. - `log_substep "Titel"`: Eingerückter Unterabschnitt. +- `setup_err_trap`: Aktiviert `errtrace`/`functrace` und registriert `error_handler` als ERR-Trap für das gesamte Skript (inkl. `lib/*.sh`-Funktionen). +- `error_handler` / `ask_retry_ignore_abort` / `resume_after_error`: Zentrale Fehlerbehandlung. Bei einem fehlgeschlagenen Befehl wird interaktiv nach Wiederholen/Ignorieren/Abbrechen gefragt; nicht-interaktiv wird immer abgebrochen. - `require_root`: Bricht ab, wenn das Skript nicht mit Root-Rechten ausgeführt wird. - `command_exists "tool"`: Prüft, ob ein Befehl im Pfad verfügbar ist. - `prompt_target_user`: Erkennt `$SUDO_USER` oder fragt interaktiv nach dem Zielbenutzer. diff --git a/lib/utils.sh b/lib/utils.sh index c78032f..21f6c29 100755 --- a/lib/utils.sh +++ b/lib/utils.sh @@ -58,15 +58,101 @@ log_substep() { # Error trap handler setup_err_trap() { + # Required so the ERR trap also fires for failures inside sourced + # lib/*.sh functions (errtrace) and nested function calls (functrace), + # not just for top-level commands in the stage script itself. + set -o errtrace + set -o functrace trap 'error_handler $? $LINENO "$BASH_COMMAND"' ERR } +# Re-arms 'set -e' for the *next* command only, via a self-removing DEBUG +# trap. Bash normally terminates the script right after an ERR trap when +# errexit is set, no matter what the trap does - the only way to make the +# script actually resume past the failed line is to have errexit disabled +# at the moment the trap returns. Must be the very last statement executed +# on the "resume" path (no trailing 'return'/'break'/...): any further +# command run inside error_handler itself would consume the DEBUG trap's +# one-shot reset before control ever gets back to the failed line. +resume_after_error() { + set +e + trap 'set -e; trap - DEBUG' DEBUG +} + +# Ask the user what to do about a failed command. +# Echoes one of: r (retry), i (ignore), a (abort). Defaults to "a" when +# not running interactively (piped install, CI, DEBIAN_FRONTEND=noninteractive, +# AUTO_CONFIRM=1) or when the prompt can't be read, so unattended installs +# keep failing hard exactly like before this feature existed. +ask_retry_ignore_abort() { + local is_interactive=0 + if { [[ -t 0 ]] || has_tty; } && [[ "${DEBIAN_FRONTEND:-}" != "noninteractive" && "${CI:-}" != "1" && "${AUTO_CONFIRM:-}" != "1" ]]; then + is_interactive=1 + fi + + if [[ "$is_interactive" -ne 1 ]]; then + echo "a" + return 0 + fi + + local tty_in="/dev/tty" + local tty_out="/dev/tty" + if ! has_tty; then + tty_in="/dev/stdin" + tty_out="/dev/stderr" + fi + + local prompt answer="" + prompt="$(printf "%b[W]iederholen%b / %b[i]gnorieren%b / %b[a]bbrechen%b (Standard: a): " \ + "$CLR_BOLD" "$CLR_RESET" "$CLR_BOLD" "$CLR_RESET" "$CLR_BOLD" "$CLR_RESET")" + read -r -p "$prompt" answer < "$tty_in" > "$tty_out" 2>&1 || answer="a" + answer="$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<< "$answer")" + + case "$answer" in + [Ww]*) echo "r" ;; + [Ii]*) echo "i" ;; + *) echo "a" ;; + esac +} + error_handler() { local exit_code="$1" local line_no="$2" local bash_command="$3" + log_error "Command failed with exit code $exit_code at line $line_no: '$bash_command'" - exit "$exit_code" + + local choice + choice="$(ask_retry_ignore_abort)" + + case "$choice" in + r) + # Bare shell keywords (return/exit/break/continue) can't be + # meaningfully re-run via eval - they'd just unwind the current + # function instead of redoing any actual work. Treat these like + # "ignore" instead of silently aborting on a nonsensical retry. + if [[ "$bash_command" =~ ^(return|exit|break|continue)([[:space:]]|$) ]]; then + log_warn "'$bash_command' kann nicht wiederholt werden - Fehler wird ignoriert." + resume_after_error + else + log_info "Wiederhole: $bash_command" + if eval "$bash_command"; then + log_success "Wiederholung erfolgreich, Installation wird fortgesetzt." + resume_after_error + else + error_handler "$?" "$line_no" "$bash_command" + fi + fi + ;; + i) + log_warn "Fehler wird ignoriert, Installation wird fortgesetzt: $bash_command" + resume_after_error + ;; + *) + log_error "Installation abgebrochen." + exit "$exit_code" + ;; + esac } # Root permission check