Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.de.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ Aufgenommen auf einem Raspberry Pi mit Pi-hole Core 6.1.4, Web 6.2.1, FTL 6.2.3
#13 👥 FTL Client 25 active clients ✔ OK
```

> **Hinweis zur Ausgabe (v5.3.2-Refresh):** Die Live-Zeilen pro Schritt nutzen jetzt ein
> vereinheitlichtes, zeitgestempeltes Format (`[HH:MM:SS] OK|WARN|ERR …`) statt
> `✔ Erfolg`/`⚠ Warnung`. Das oben gezeigte Dashboard und die Zusammenfassung bleiben
> unverändert. `SIGINT`/`SIGTERM` (Strg-C) erzeugen die Zusammenfassung und räumen das
> Temp-Verzeichnis nun genau einmal auf. Alle Flags, JSON-Ausgabe und Steps sind identisch.

Der reale Lauf bestätigt außerdem:

- Backups werden vor Pi-hole-Wartung erstellt (z. B. `/etc/pihole/backup_20251025_100315`, `/etc/pihole/backup_20251025_100337`)
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ Captured on a Raspberry Pi with Pi-hole Core 6.1.4, Web 6.2.1, FTL 6.2.3 — thi
#13 👥 FTL Client 25 active clients ✔ OK
```

> **Output note (v5.3.2 refresh):** per-step live lines now use a unified, timestamped
> format (`[HH:MM:SS] OK|WARN|ERR …`) instead of `✔ Erfolg`/`⚠ Warnung`. The end-of-run
> dashboard and summary shown above are unchanged. `SIGINT`/`SIGTERM` (Ctrl-C) now render the
> summary and clean up the temp dir exactly once. All flags, JSON output and steps are identical.

The same production run confirms:

- Backups are created before Pi-hole maintenance kicks in (e.g. `/etc/pihole/backup_20251025_100315`, `/etc/pihole/backup_20251025_100337`)
Expand Down
156 changes: 101 additions & 55 deletions pihole_maintenance_pro.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,66 @@ IFS=$'\n\t'
# Voller PATH für cron/Nicht-Login-Shells (früh setzen)
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

# --------------------------- UI helpers (self-contained) --------------------
# Inline-Portierung der zentralen UI-Helfer (im Schwester-Repo als
# scripts/lib/ui.sh ausgelagert). Bewusst self-contained: dieses Skript wird
# als eigenständiger Einzel-Download verteilt und via cron ausgeführt – es darf
# keine externe Bibliothek voraussetzen.
ui_init() {
UI_COLOR=true
if [[ ! -t 1 ]]; then UI_COLOR=false; fi
if [[ -n "${NO_COLOR:-}" ]]; then UI_COLOR=false; fi

if [[ "$UI_COLOR" == true ]]; then
UI_RED=$'\033[0;31m'
UI_GREEN=$'\033[0;32m'
UI_YELLOW=$'\033[1;33m'
UI_BLUE=$'\033[0;34m'
UI_MAGENTA=$'\033[0;35m'
UI_CYAN=$'\033[0;36m'
UI_BOLD=$'\033[1m'
UI_RESET=$'\033[0m'
else
UI_RED='' UI_GREEN='' UI_YELLOW='' UI_BLUE='' UI_MAGENTA='' UI_CYAN='' UI_BOLD='' UI_RESET=''
fi
}

_ui_ts() { date +"%H:%M:%S"; }

_ui_format_line() {
local label="$1" color="$2" msg="$3"
if [[ "${UI_COLOR:-false}" == true && -n "$color" ]]; then
printf '[%s] %s%s%s %s' "$(_ui_ts)" "$color" "$label" "$UI_RESET" "$msg"
else
printf '[%s] %s %s' "$(_ui_ts)" "$label" "$msg"
fi
}

# Vereinheitlichte log_*-Ausgabe (aus ui.sh übernommen). Kein zusätzliches
# Datei-Logging nötig: exec-tee unten schreibt bereits alles ins $LOGFILE.
log_ok() { printf '%s\n' "$(_ui_format_line "OK" "$UI_GREEN" "$*")"; }
log_warn() { printf '%s\n' "$(_ui_format_line "WARN" "$UI_YELLOW" "$*")" >&2; }
log_err() { printf '%s\n' "$(_ui_format_line "ERR" "$UI_RED" "$*")" >&2; }

ui_init

# --------------------------- Colors & symbols -------------------------------
if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
else
RED=""
GREEN=""
YELLOW=""
BLUE=""
MAGENTA=""
CYAN=""
BOLD=""
NC=""
fi
CHECK="${GREEN}✔${NC}"
WARN="${YELLOW}⚠${NC}"
FAIL="${RED}✖${NC}"
RED="${UI_RED:-}"
GREEN="${UI_GREEN:-}"
YELLOW="${UI_YELLOW:-}"
BLUE="${UI_BLUE:-}"
MAGENTA="${UI_MAGENTA:-}"
CYAN="${UI_CYAN:-}"
BOLD="${UI_BOLD:-}"
NC="${UI_RESET:-}"

# --------------------------- Root check ------------------------------------
# Für sicheren lokalen Selftest (RUN_SELFTEST=1) ohne Root erlauben
if [[ ${EUID} -ne 0 ]]; then
if [[ "${RUN_SELFTEST:-0}" == "1" ]]; then
echo -e "${YELLOW}Hinweis:${NC} RUN_SELFTEST=1 erkannt – Root-Check übersprungen (APT/Upgrade/Gravity sollten via --no-* Flags deaktiviert sein)."
printf '%sHinweis:%s RUN_SELFTEST=1 erkannt – Root-Check übersprungen (APT/Upgrade/Gravity sollten via --no-* Flags deaktiviert sein).\n' "$YELLOW" "$NC"
else
echo -e "${RED}[ERROR]${NC} Bitte mit sudo oder als root ausführen." >&2
printf '%s[ERROR]%s Bitte mit sudo oder als root ausführen.\n' "$RED" "$NC" >&2
exit 1
fi
fi
Expand Down Expand Up @@ -130,7 +159,7 @@ if [[ -z "$PIHOLE_BIN" ]]; then
echo "Warnung: pihole CLI nicht im CI vorhanden. Test wird übersprungen."
exit 0
else
echo -e "${RED}[ERROR]${NC} 'pihole' CLI nicht gefunden. PATH=$PATH" >&2
printf '%s[ERROR]%s '\''pihole'\'' CLI nicht gefunden. PATH=%s\n' "$RED" "$NC" "$PATH" >&2
echo "Auf Pi-hole-Host ausführen oder CLI installieren." >&2
exit 127
fi
Expand All @@ -147,10 +176,11 @@ if [[ -d "$LOGDIR" && -w "$LOGDIR" ]]; then
LOGFILE="$LOGDIR/pihole_maintenance_pro_$(date +%Y-%m-%d_%H-%M-%S).log"
else
LOGFILE="$TMPDIR/pihole_maintenance_pro_$(date +%Y-%m-%d_%H-%M-%S).log"
echo -e "${YELLOW}Hinweis:${NC} /var/log nicht beschreibbar, Log nach $TMPDIR."
printf '%sHinweis:%s /var/log nicht beschreibbar, Log nach %s.\n' "$YELLOW" "$NC" "$TMPDIR"
fi

trap 'rm -rf "$TMPDIR" 2>/dev/null || true' EXIT
cleanup_tmpdir() { rm -rf "$TMPDIR" 2> /dev/null || true; }
trap cleanup_tmpdir EXIT INT TERM
exec > >(tee -a "$LOGFILE") 2>&1

# shellcheck disable=SC2034
Expand All @@ -161,24 +191,25 @@ strip_ansi() { sed -r $'s/\x1B\[[0-9;]*[a-zA-Z]//g' | tr -d '\r'; }

echo_hdr() {
if [[ -t 1 ]]; then clear; fi
echo -e "${MAGENTA}╔════════════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${MAGENTA}║${NC} 🛰️ ${BOLD}PI-HOLE MAINTENANCE PRO MAX${NC}${MAGENTA} - TimInTech (${CYAN}v5.3.2${MAGENTA}) ║${NC}"
echo -e "${MAGENTA}╠════════════════════════════════════════════════════════════════════════╣${NC}"
printf '%s╔════════════════════════════════════════════════════════════════════════╗%s\n' "$MAGENTA" "$NC"
printf '%s║%s 🛰️ %sPI-HOLE MAINTENANCE PRO MAX%s%s - TimInTech (%sv5.3.2%s) ║%s\n' \
"$MAGENTA" "$NC" "$BOLD" "$NC" "$MAGENTA" "$CYAN" "$MAGENTA" "$NC"
printf '%s╠════════════════════════════════════════════════════════════════════════╣%s\n' "$MAGENTA" "$NC"
if "$PIHOLE_BIN" -v > /dev/null 2>&1; then
PH_VER="$("$PIHOLE_BIN" -v 2> /dev/null || true)"
echo -e "${MAGENTA}║${NC} Version: ${CYAN}${PH_VER:-unbekannt}${NC}"
printf '%s║%s Version: %s%s%s\n' "$MAGENTA" "$NC" "$CYAN" "${PH_VER:-unbekannt}" "$NC"
else
echo -e "${MAGENTA}║${NC} ${YELLOW}Pi-hole CLI nicht gefunden${NC}"
printf '%s║%s %sPi-hole CLI nicht gefunden%s\n' "$MAGENTA" "$NC" "$YELLOW" "$NC"
fi
echo -e "${MAGENTA}╚════════════════════════════════════════════════════════════════════════╝${NC}"
printf '%s╚════════════════════════════════════════════════════════════════════════╝%s\n' "$MAGENTA" "$NC"
}

run_step() {
local n="$1" icon="$2" title="$3" cmd="$4" critical="${5:-false}" display_only="${6:-false}"
local step_log="$TMPDIR/step_${n}.log"
# shellcheck disable=SC2034 # consumed later when printing per-step log paths
STEP_LOGFILE["$n"]="$step_log"
echo -e "\n${BLUE}╔═[Step ${n}]${NC}\n${BLUE}║ ${icon} ${title}${NC}\n${BLUE}╚═>${NC} "
printf '\n%s╔═[Step %s]%s\n%s║ %s %s%s\n%s╚═>%s ' "$BLUE" "$n" "$NC" "$BLUE" "$icon" "$title" "$NC" "$BLUE" "$NC"

local out="/dev/null"
if [[ -t 1 ]]; then
Expand All @@ -187,14 +218,14 @@ run_step() {

if [[ "$display_only" == "true" ]]; then
if bash -lc "$cmd" 2>&1 | tee -a "$out" | strip_ansi > "$step_log"; then
echo -e "${CHECK} Erfolg"
log_ok "Step $n: $title"
STATUS["$n"]="${GREEN}✔ OK${NC}"
[[ -f "$step_log" ]] && extract_step_data "$n" "$(cat "$step_log")"
else
echo -e "${WARN} Warnung"
log_warn "Step $n: $title"
STATUS["$n"]="${YELLOW}⚠ WARN${NC}"
[[ -s "$step_log" ]] && tail -n 20 "$step_log"
[[ "$critical" == "true" ]] && echo -e "${RED}[ERROR] Kritischer Fehler – Abbruch${NC}" && exit 1
[[ "$critical" == "true" ]] && log_err "Kritischer Fehler in Step $n – Abbruch" && exit 1
fi
return 0
fi
Expand All @@ -214,15 +245,17 @@ run_step() {
printf '\r' > "$out" 2> /dev/null || true
) &
if wait "$pid"; then
echo -e "\n${CHECK} Erfolg"
printf '\n'
log_ok "Step $n: $title"
STATUS["$n"]="${GREEN}✔ OK${NC}"
[[ -f "$step_log" ]] && extract_step_data "$n" "$(cat "$step_log")"
else
local ec=$?
echo -e "\n${FAIL} Fehler (Code: $ec)"
printf '\n'
log_err "Step $n: $title (Code: $ec)"
STATUS["$n"]="${RED}✖ FAIL${NC}"
[[ -f "$step_log" ]] && tail -n 50 "$step_log"
[[ "$critical" == "true" ]] && echo -e "${RED}[ERROR] Kritischer Fehler in Step ${n}${NC}" && exit $ec
[[ "$critical" == "true" ]] && log_err "Kritischer Fehler in Step $n: $title" && exit $ec
fi
}

Expand All @@ -248,12 +281,12 @@ extract_step_data() {
summary() {
collect_system_info
echo
echo -e "${CYAN}╔═══════════════ PERFORMANCE DASHBOARD ═══════════════╗${NC}"
printf '%s╔═══════════════ PERFORMANCE DASHBOARD ═══════════════╗%s\n' "$CYAN" "$NC"
printf "${CYAN}║${NC} 🚀 Load: %-8s 💾 RAM: %s%% 🌡️ Temp: %s°C 🗄️ Disk: %s%% ${CYAN}║${NC}\n" \
"${PERFORMANCE_DATA[load]:-N/A}" "${PERFORMANCE_DATA[memory]:-N/A}" "${PERFORMANCE_DATA[temp]:-N/A}" "${PERFORMANCE_DATA[disk]:-N/A}"
echo -e "${CYAN}╚═══════════════════════════════════════════════════════╝${NC}"
printf '%s╚═══════════════════════════════════════════════════════╝%s\n' "$CYAN" "$NC"
echo
echo -e "${MAGENTA}════════ INTELLIGENTE ZUSAMMENFASSUNG ════════${NC}"
printf '%s════════ INTELLIGENTE ZUSAMMENFASSUNG ════════%s\n' "$MAGENTA" "$NC"
for k in $(printf '%s\n' "${!STATUS[@]}" | sort -n); do
local step_info=""
case "$k" in
Expand All @@ -277,8 +310,8 @@ summary() {
done
echo
show_recommendations
echo -e "Log: ${CYAN}$LOGFILE${NC}"
echo -e "Step logs: ${CYAN}$TMPDIR${NC} (werden beim Exit gelöscht)"
printf 'Log: %s%s%s\n' "$CYAN" "$LOGFILE" "$NC"
printf 'Step logs: %s%s%s (werden beim Exit gelöscht)\n' "$CYAN" "$TMPDIR" "$NC"
}

get_step_description() {
Expand Down Expand Up @@ -334,11 +367,11 @@ show_recommendations() {
recommendations+=("🚨 Restart Pi-hole FTL: sudo systemctl restart pihole-FTL")
}
((${#warnings[@]})) && {
echo -e "\n${YELLOW}════════ WARNINGS ════════${NC}"
printf '\n%s════════ WARNINGS ════════%s\n' "$YELLOW" "$NC"
printf '%s\n' "${warnings[@]}"
}
((${#recommendations[@]})) && {
echo -e "\n${BLUE}════════ RECOMMENDATIONS ════════${NC}"
printf '\n%s════════ RECOMMENDATIONS ════════%s\n' "$BLUE" "$NC"
printf '%s\n' "${recommendations[@]}"
}
}
Expand Down Expand Up @@ -428,20 +461,33 @@ for c in /etc/pihole/pihole-FTL.db /run/pihole-FTL.db /var/lib/pihole/pihole-FTL
done
# (gravity DB path is queried lazily when needed)

# Re-Entry-Guard: verhindert doppelte Summary/Cleanup, falls on_exit sowohl über
# ein Signal als auch über den finalen EXIT ausgelöst würde.
ON_EXIT_DONE=0
# shellcheck disable=SC2317 # trap callback is invoked by bash
on_exit() {
local rc="$1"
[[ "$ON_EXIT_DONE" == "1" ]] && return
ON_EXIT_DONE=1
echo ""
if [[ "$JSON_OUTPUT" == "1" ]]; then
output_json 2> /dev/null || true
else
summary 2> /dev/null || true
fi
rm -rf "$TMPDIR" 2> /dev/null || true
[[ $rc -ne 0 ]] && echo -e "${RED}Script ended with exit code $rc${NC}"
cleanup_tmpdir
[[ $rc -ne 0 ]] && printf '%sScript ended with exit code %s%s\n' "$RED" "$rc" "$NC"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve successful exit status in EXIT trap

On any clean run where rc is 0, this final [[ $rc -ne 0 ]] && ... expression returns 1 from on_exit; since the previous explicit exit "$rc" was removed and the script runs with set -e, Bash can make the overall script exit with status 1 even though every maintenance step succeeded. That will cause cron/systemd/CI wrappers to report successful maintenance runs as failures; restore an explicit exit "$rc" or otherwise make the trap return success for rc=0.

Useful? React with 👍 / 👎.

# Exit-Status explizit erhalten: ohne dieses exit würde bei rc=0 die vorherige
# (falsche) [[ ]]-Bedingung als Rückgabewert der EXIT-Trap durchschlagen und
# erfolgreiche Läufe fälschlich als Fehler (Code 1) melden. Re-Entry-Guard
# oben verhindert dabei doppelte Summary/Cleanup.
exit "$rc"
}
# on_exit läuft genau einmal über EXIT; INT/TERM leiten sauber dorthin um und
# sorgen so auch bei Ctrl-C für Summary + tmp-Cleanup.
trap 'on_exit $?' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

# --------------------------- Run -------------------------------------------
echo_hdr
Expand All @@ -462,11 +508,11 @@ if ((DO_APT == 1)); then
run_step 02 "🧹" "APT: autoremove & autoclean" "apt -y autoremove && apt -y autoclean"
if dpkg --print-architecture | grep -q '^armhf$'; then
if apt list --upgradable 2> /dev/null | grep -q '^linux-image-rpi-v8'; then
echo -e "${YELLOW}Hinweis:${NC} 'linux-image-rpi-v8' ist 64-bit (ARMv8). Auf Pi 3B (ARMv7) ignorierbar."
printf '%sHinweis:%s '\''linux-image-rpi-v8'\'' ist 64-bit (ARMv8). Auf Pi 3B (ARMv7) ignorierbar.\n' "$YELLOW" "$NC"
fi
fi
else
echo -e "${YELLOW}APT-Schritte übersprungen (--no-apt).${NC}"
printf '%sAPT-Schritte übersprungen (--no-apt).%s\n' "$YELLOW" "$NC"
fi

# 02 – Security Checks (optional display)
Expand All @@ -486,7 +532,7 @@ command -v clamscan > /dev/null 2>&1 && run_step 29 "🦠" "Security: clamav" "c
PIHOLE_LOG="/var/log/pihole.log"
if [[ -f "$PIHOLE_LOG" ]]; then
LOGSIZE=$(stat -c %s "$PIHOLE_LOG" 2> /dev/null || echo 0)
((LOGSIZE > 1073741824)) && echo -e "${YELLOW}WARNUNG: pihole.log > 1GB! Empfehlung: logrotate aktivieren.${NC}"
((LOGSIZE > 1073741824)) && printf '%sWARNUNG: pihole.log > 1GB! Empfehlung: logrotate aktivieren.%s\n' "$YELLOW" "$NC"
fi

# Backup-Integration (optional)
Expand Down Expand Up @@ -517,15 +563,15 @@ if ((DO_UPGRADE == 1)); then
backup_pihole
run_step 04 "🆙" "Pi-hole self-update" "\"$PIHOLE_BIN\" -up"
else
echo -e "${YELLOW}Pi-hole Upgrade übersprungen (--no-upgrade).${NC}"
printf '%sPi-hole Upgrade übersprungen (--no-upgrade).%s\n' "$YELLOW" "$NC"
fi

# 05 – Gravity
if ((DO_GRAVITY == 1)); then
backup_pihole
run_step 05 "📋" "Update Gravity / Blocklists" "\"$PIHOLE_BIN\" -g"
else
echo -e "${YELLOW}Gravity-Update übersprungen (--no-gravity).${NC}"
printf '%sGravity-Update übersprungen (--no-gravity).%s\n' "$YELLOW" "$NC"
fi

# 06 – optionaler FTL-Restart (v6: nur bei Bedarf)
Expand Down Expand Up @@ -557,7 +603,7 @@ if command -v sqlite3 > /dev/null 2>&1 && [[ -f "$FTL_DB" ]]; then
run_step 12 "📈" "Top 5 Domains (FTL)" $'sqlite3 -readonly "$FTL_DB" "SELECT domain, COUNT(1) c FROM queries GROUP BY domain ORDER BY c DESC LIMIT 5;" || true' false true
run_step 13 "👥" "Top 5 Clients (FTL)" $'sqlite3 -readonly "$FTL_DB" "SELECT client, COUNT(1) c FROM queries GROUP BY client ORDER BY c DESC LIMIT 5;" || true' false true
else
echo -e "${YELLOW}sqlite3 oder FTL DB nicht gefunden – Überspringe Top-Listen.${NC}"
printf '%ssqlite3 oder FTL DB nicht gefunden – Überspringe Top-Listen.%s\n' "$YELLOW" "$NC"
fi

# 14 – Abschluss (Summary/JSON kommt aus EXIT-Trap)
Expand Down
Loading