Feature: Bei fehlgeschlagenem Befehl Wiederholen/Ignorieren/Abbrechen abfragen

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017WMUd2fGsPCtTCVQkSS5TA
This commit is contained in:
2026-09-06 21:54:47 +02:00
co-authored by Claude Sonnet 5
parent 0bf2e81063
commit c1f3851086
2 changed files with 90 additions and 1 deletions
+3
View File
@@ -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.
+87 -1
View File
@@ -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