Skip to main content

⚙️ wartung-eos.sh

#!/bin/bash
# ------------------------------------------------------------------------
# Menü für Server Wartung & Pflege-Skript (EndeavourOS ARM, Raspberry Pi 4)
#
# Zweck:
#   Zentrales, interaktives Wartungs- und Kontrollskript für einen
#   headless betriebenen Raspberry Pi 4 im Heimnetz.
#   Das Skript bündelt typische Administrations- und Wartungsaufgaben
#   in einem übersichtlichen Menü.
#
# Aufruf:
#   bash wartung-eos.sh   (NICHT mit sudo ausführen)
#
# Eigenschaften:
#   - läuft bewusst als normaler Benutzer
#   - nutzt sudo nur gezielt für benötigte Aktionen
#   - keine automatischen Änderungen ohne Bestätigung
#   - geeignet für Dauerbetrieb und Remote-Administration
#
# Voraussetzungen:
#   - Arch Linux / EndeavourOS ARM
#   - pacman (Pflicht), yay (optional für AUR)
#   - ioBroker inkl. iob CLI
#   - Pi-hole 6 (pihole-FTL Service)
#   - Unbound DNS Resolver
#     * /etc/unbound/trusted-key.key
#     * /etc/unbound/root.hints
#   - optional: htop, fastfetch
#
# Dienste:
#   - ioBroker
#   - Pi-hole
#   - Unbound
#   - Syncthing (Status & Logs)
#
# Einsatzbereich:
#   - Heimserver
#   - Headless-Systeme
#   - Kein Portforwarding erforderlich
#
# ------------------------------------------------------------------------
# Version
SCRIPT_VERSION="2025-12-15"

# Farben (fallback ohne Farben, wenn tput fehlt oder TERM dumm ist)
if command -v tput >/dev/null 2>&1 && [ -n "$TERM" ] && [ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]; then
  gruen=$(tput setaf 2); rot=$(tput setaf 1); gelb=$(tput setaf 3); reset=$(tput sgr0)
else
  gruen=""; rot=""; gelb=""; reset=""
fi

# --- Sicherheitsprüfung: Script darf NICHT mit sudo oder als root laufen ---
if [ "$EUID" -eq 0 ] || [ -n "${SUDO_USER:-}" ]; then
  echo
  echo "${rot}Fehler:${reset} Dieses Skript darf nicht mit sudo oder als root ausgeführt werden!"
  echo "Bitte starte es normal als Benutzer mit sudo-Rechten, z.B.:"
  echo "  bash wartung-eos.sh"
  echo
  exit 1
fi

# Versionsbanner (vor sudo)
clear
echo
echo "─────────────────────────────────────────────────────────────────"
echo " EndeavourOS ARM Wartung – Pi 4 (ioBroker + Pi-hole 6 + Unbound)"
echo " Version: ${gruen}${SCRIPT_VERSION}${reset}"
echo "─────────────────────────────────────────────────────────────────"
echo

# Systemd-Pager deaktivieren (verhindert "q"-Abfragen bei journalctl)
export SYSTEMD_PAGER=cat
echo "Pager für systemd-Befehle deaktiviert (SYSTEMD_PAGER=cat)"
echo

# Unbound-Status
unbound_msgs=()

systemctl is-active --quiet unbound || unbound_msgs+=("INAKTIV")
[ -f /etc/unbound/trusted-key.key ] || unbound_msgs+=("FEHLT trusted-key.key")
[ -f /etc/unbound/root.hints ] || unbound_msgs+=("FEHLT root.hints")

if [ ${#unbound_msgs[@]} -eq 0 ]; then
  echo "Unbound-Status: OK"
else
  echo "Unbound-Status: ${unbound_msgs[*]}"
fi
echo

# --- sudo-Credential-Cache aufbauen ---
if sudo -n true 2>/dev/null; then
  echo "➤ sudo erfordert kein Passwort – überspringe Abfrage."
else
  echo "➤ Dieses Skript benötigt teilweise sudo-Rechte. Bitte Passwort eingeben, falls abgefragt:"
  sudo -K 2>/dev/null
  sudo -v || { echo "${rot}sudo konnte nicht ausgeführt werden.${reset}"; exit 1; }
fi

# Verzeichnis dieses Skripts
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# REAL_USER ermitteln (robust, auch bei sudo)
REAL_USER=${SUDO_USER:-$(logname 2>/dev/null || whoami)}

# Log-Datei im echten Home-Verzeichnis
HOME_DIR="$(getent passwd "$REAL_USER" | cut -d: -f6)"
: "${HOME_DIR:="${HOME:-/home/$REAL_USER}"}"
logfile="$HOME_DIR/wartung.log"

# Logging
log() {
  echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$logfile"
}

# Log-Rotation ab ~100KB
rotate_log() {
  if [ -f "$logfile" ]; then
    filesize=$(stat -c%s "$logfile" 2>/dev/null || echo 0)
    if [ "$filesize" -ge 102400 ]; then
      timestamp=$(date '+%Y%m%d-%H%M%S')
      mv "$logfile" "$(dirname "$logfile")/wartung-$timestamp.log"
      : > "$logfile"
      log "Logfile >100KB, rotiert nach wartung-$timestamp.log"
    fi
  fi
}
rotate_log

press_enter() { echo; read -p "Drücke Enter zum Fortfahren..." _; }

have() { command -v "$1" >/dev/null 2>&1; }

while true; do
  clear
  echo "${gelb}====================================${reset}"
  echo " EndeavourOS ARM Wartung – Pi 4"
  echo " Version: ${gruen}${SCRIPT_VERSION}${reset}"
  echo "${gelb}====================================${reset}"
  echo "1) Systeminformationen"
  echo "2) Speicher- & Datenträgerstatus & SSD Trim"
  echo "3) Systemüberwachung in Echtzeit (htop)"
  echo "4) Journal & Logdateien"
  echo "5) Pacman & Paketverwaltung"
  echo "6) ioBroker – Wartung & Kontrolle"
  echo "7) Pi-hole mit Unbound – Wartung & Kontrolle"
  echo "8) Syncthing – Status & Kontrolle"
  echo "9) System-Neustart"
  echo "0) Beenden"
  echo "${gelb}====================================${reset}"
  read -p "Bitte wähle eine Option: " auswahl

  case "$auswahl" in
    1)
      clear
      echo "${gelb}=========================================${reset}"
      echo "${gruen}1) Systeminformationen${reset}"
      echo "${gelb}=========================================${reset}"
      log "Systeminformationen anzeigen"
      echo
      echo ">> uname -a"
      uname -a
      echo
      echo ">> hostnamectl"
      hostnamectl
      echo
      echo ">> uptime -p"
      uptime -p
      echo
      if have fastfetch; then
        echo ">> fastfetch (Systemübersicht)"
        fastfetch
      else
        echo "${gelb}Hinweis:${reset} fastfetch ist nicht installiert."
      fi
      press_enter
      ;;

    2)
      clear
      echo "${gelb}=============================================${reset}"
      echo "${gruen}2) Speicher- & Datenträgerstatus & SSD Trim${reset}"
      echo "${gelb}=============================================${reset}"
      log "Speicher- & Datenträgerstatus & SSD Trim"
      echo
      echo ">> df -h"
      df -h
      echo
      echo ">> sudo fstrim -av"
      sudo fstrim -av
      press_enter
      ;;

    3)
      clear
      echo "${gelb}=========================================${reset}"
      echo "${gruen}3) Systemüberwachung in Echtzeit – htop${reset}"
      echo "${gelb}=========================================${reset}"
      log "Systemüberwachung in Echtzeit – htop"
      echo
      if have htop; then
        echo ">> Starte htop – beenden mit q"
        sleep 2
        htop
      else
        echo "${gelb}Hinweis:${reset} htop ist nicht installiert."
        press_enter
      fi
      ;;

    4)
      clear
      echo "${gelb}=========================================${reset}"
      echo "${gruen}4) Journal & Logdateien${reset}"
      echo "${gelb}=========================================${reset}"
      log "Journal & Logdateien"
      echo

      read -p "Wie viele Zeilen sollen angezeigt werden? [Default: 20] " lines
      lines=${lines:-20}
      # nur Zahlen erlauben, sonst wieder 20
      if ! [[ "$lines" =~ ^[0-9]+$ ]]; then lines=20; fi

      echo
      echo ">> journalctl -p 3 -xb | tail -${lines}        # Fehler des letzten Systemstarts"
      journalctl -p 3 -xb | tail -${lines}
      echo
      echo ">> journalctl -u iobroker -n ${lines}          # Letzte ioBroker-Logzeilen"
      journalctl -u iobroker -n ${lines}
      echo
      echo ">> dmesg | tail -${lines}                      # Letzte Kernelmeldungen"
      if dmesg >/dev/null 2>&1; then
        dmesg | tail -${lines}
      else
        sudo dmesg | tail -${lines}
      fi
      echo
      if [ -f /var/log/pacman.log ]; then
        echo ">> tail -${lines} /var/log/pacman.log          # Letzte Paketinstallationen & Updates"
        tail -${lines} /var/log/pacman.log
      else
        echo "${gelb}Hinweis:${reset} /var/log/pacman.log nicht gefunden."
      fi
      press_enter
      ;;

    5)
      while true; do
        clear
        echo "${gelb}=========================================${reset}"
        echo "${gruen}5) Pacman & Paketverwaltung${reset}"
        echo "${gelb}=========================================${reset}"
        log "Pacman & Paketverwaltung"
        echo
        echo "a) sudo pacman -Syu                  # System aktualisieren"
        echo "b) sudo pacman -Syyuu                # Komplett-Refresh inkl. Downgrades"
        echo "c) sudo pacman -Qdtq                 # Verwaiste Pakete auflisten"
        echo "d) sudo pacman -Rsn \$(pacman -Qdtq)  # Verwaiste Pakete entfernen"
        echo "e) sudo pacman -Scc                  # Cache leeren"
        echo "f) yay -Syu                          # AUR aktualisieren"
        echo "g) yay -Sc                           # AUR Cache leeren"
        echo "h) yay -v -Scc                       # AUR Cache vollständig leeren"
        echo "z) Zurück zum Hauptmenü"
        echo
        read -p "Unterauswahl: " sub

        case "$sub" in
          a) log "pacman -Syu"; sudo pacman -Syu; press_enter;;
          b) log "pacman -Syyuu"; sudo pacman -Syyuu; press_enter;;
          c) log "pacman -Qdtq"; sudo pacman -Qdtq || true; press_enter;;
          d)
            log "verwaiste Pakete entfernen"
            orphans=$(pacman -Qdtq 2>/dev/null || true)
            if [ -n "${orphans:-}" ]; then
              echo "Folgende Pakete werden entfernt:"
              echo "$orphans"
              read -p "Bestätigen? [j/N] " ans
              if [[ "$ans" =~ ^[Jj]$ ]]; then
                sudo pacman -Rsn $orphans
              else
                echo "Abgebrochen."
              fi
            else
              echo "Keine verwaisten Pakete gefunden."
            fi
            press_enter;;
          e) log "pacman -Scc"; sudo pacman -Scc; press_enter;;
          f) if have yay; then log "yay -Syu"; yay -Syu; else echo "yay nicht gefunden."; fi; press_enter;;
          g) if have yay; then log "yay -Sc"; yay -Sc; else echo "yay nicht gefunden."; fi; press_enter;;
          h) if have yay; then log "yay -v -Scc"; yay -v -Scc; else echo "yay nicht gefunden."; fi; press_enter;;
          z|Z) break;;
          *) echo "${rot}Ungültige Auswahl.${reset}"; press_enter;;
        esac
      done
      ;;

    6)
      while true; do
        clear
        echo "${gelb}=========================================${reset}"
        echo "${gruen}6) ioBroker – Wartung & Kontrolle${reset}"
        echo "${gelb}=========================================${reset}"
        log "ioBroker – Wartung & Kontrolle"
        echo
        if ! have iob; then
          echo "${rot}iob (ioBroker CLI) nicht gefunden.${reset}"
          press_enter
          break
        fi

        echo "a) systemctl status iobroker.service   # Dienststatus anzeigen"
        echo "b) iob diag                            # Diagnosebericht erstellen"
        echo "c) iob stop && iob fix                 # Rechte & Dienste reparieren"
        echo "d) iob stop && iob update && iob upgrade self && iob upgrade   # ioBroker aktualisieren"
        echo "e) iob start                           # ioBroker starten"
        echo "z) Zurück zum Hauptmenü"
        echo
        read -p 'Unterauswahl: ' sub

        case "$sub" in
          a)
            log "systemctl status iobroker.service"
            echo ">> systemctl status iobroker.service --no-pager"
            systemctl status iobroker.service --no-pager
            press_enter;;
          b)
            log "iob diag"
            echo ">> iob diag"
            iob diag
            press_enter;;
          c)
            log "iob stop && iob fix"
            iob stop && iob fix
            press_enter;;
          d)
            log "ioBroker Update"
            iob stop && iob update && iob upgrade self && iob upgrade
            press_enter;;
          e)
            log "iob start"
            iob start
            press_enter;;
          z|Z)
            break;;
          *)
            echo "${rot}Ungültige Auswahl.${reset}"
            press_enter;;
        esac
      done
      ;;

    7)
      while true; do
        clear
        echo "${gelb}=========================================${reset}"
        echo "${gruen}7) Pi-hole mit Unbound – Wartung & Kontrolle${reset}"
        echo "${gelb}=========================================${reset}"
        log "Pi-hole mit Unbound – Wartung & Kontrolle"
        echo
        echo "a) systemctl status unbound            # Status des DNS-Resolvers anzeigen"
        echo "b) sudo pihole -g                      # Blockierlisten aktualisieren"
        echo "c) sudo pihole -f                      # Statistikdaten löschen"
        echo "d) sudo systemctl restart pihole-FTL   # DNS-Dienst neu starten"
        echo "e) cat /etc/unbound/trusted-key.key    # DNSSEC Trusted Keys anzeigen"
        echo "f) cat /etc/unbound/root.hints         # Root Hints (13 Root-Server) anzeigen"
        echo "z) Zurück zum Hauptmenü"
        echo
        read -p "Unterauswahl: " sub

        case "$sub" in
          a)
            log "systemctl status unbound"
            echo ">> systemctl status unbound --no-pager"
            systemctl status unbound --no-pager
            press_enter;;
          b)
            if have pihole; then
              log "pihole -g"
              sudo pihole -g
            else
              echo "pihole nicht gefunden."
            fi
            press_enter;;
          c)
            if have pihole; then
              log "pihole -f"
              sudo pihole -f
            else
              echo "pihole nicht gefunden."
            fi
            press_enter;;
          d)
            log "systemctl restart pihole-FTL"
            sudo systemctl restart pihole-FTL && echo "pihole-FTL neu gestartet."
            press_enter;;
          e)
            log "trusted-key.key anzeigen"
            sudo cat /etc/unbound/trusted-key.key || echo "Datei nicht gefunden."
            press_enter;;
          f)
            log "root.hints anzeigen"
            sudo cat /etc/unbound/root.hints || echo "Datei nicht gefunden."
            press_enter;;
          z|Z)
            break;;
          *)
            echo "${rot}Ungültige Auswahl.${reset}"
            press_enter;;
        esac
      done
      ;;

    8)
      clear
      echo "${gelb}=========================================${reset}"
      echo "${gruen}8) Syncthing – Status & Kontrolle${reset}"
      echo "${gelb}=========================================${reset}"
      log "Syncthing – Status & Kontrolle"
      echo

      echo ">> systemctl status syncthing@alarm.service --no-pager"
      systemctl status syncthing@alarm.service --no-pager
      echo

      echo ">> Letzte Logmeldungen (journalctl)"
      journalctl -u syncthing@alarm.service -n 10 --no-pager
      echo

      echo "GUI (LAN): http://$(ip -4 route get 1.1.1.1 | awk '{print $7; exit}'):8384"
      press_enter
      ;;

    9)
      clear
      echo "${gelb}=========================================${reset}"
      echo "${gruen}9) System-Neustart${reset}"
      echo "${gelb}=========================================${reset}"
      log "System-Neustart"
      echo
      read -p "Jetzt neu starten? [j/N] " ans
      if [[ "$ans" =~ ^[Jj]$ ]]; then
        log "sudo reboot"
        sudo reboot
      else
        echo "Abgebrochen."
        press_enter
      fi
      ;;

    0)
      log "Menü beendet."
      echo "${gruen}Beende das Menü. Auf Wiedersehen!${reset}"
      exit 0
      ;;

    *)
      echo "${rot}Ungültige Auswahl! Bitte wähle eine gültige Option.${reset}"
      press_enter
      ;;
  esac
done