#!/usr/bin/env bash # pi-einrichten.sh — macht Ubuntu Server auf dem Raspberry Pi Zero 2 (512 MB) # stabil für die Projektübersicht. Mehrfach ausführbar (prüft jeden Schritt). # # sudo bash Docker/pi-einrichten.sh # Standard # sudo bash Docker/pi-einrichten.sh --snapd-aus # zusätzlich snapd abschalten # # Was es tut (Einzelheiten in CLAUDE.md, "Betrieb auf dem Pi"): # 1. Hardware-Watchdog: hängt das System, startet der Pi selbst neu # 2. zram: komprimierter Auslagerungsspeicher im RAM (statt SD-Karte) # 3. Speichergrenzen für Container erlauben (cgroup memory) # 4. gpu_mem=16 — kein Bildschirm, mehr RAM für Linux # 5. Unnötige Dienste aus (multipathd, ModemManager, udisks2 …) # 6. earlyoom: greift ein, BEVOR das System im Speichermangel festfriert # 7. Systemprotokoll auf 50 MB begrenzen # 8. Nächtlicher Neustart des Containers (3:30 Uhr) # 9. Prüfung der Stromversorgung (Unterspannung) # 10. WLAN: Energiesparen aus, Wächter verbindet neu und startet den Pi # neu, wenn das Netz 10 Minuten lang weg bleibt set -u if [ "$(id -u)" -ne 0 ]; then echo "Bitte mit sudo ausführen."; exit 1; fi SNAPD_AUS=0 [ "${1:-}" = "--snapd-aus" ] && SNAPD_AUS=1 NEUSTART_NOETIG=0 FIRMWARE=/boot/firmware [ -d "$FIRMWARE" ] || FIRMWARE=/boot ok() { echo " ✓ $*"; } info() { echo " • $*"; } warn() { echo " ! $*"; } echo "== Projektübersicht: Pi einrichten ==" echo "System: $(. /etc/os-release; echo "$PRETTY_NAME"), $(uname -m), $(free -m | awk '/Mem:/{print $2}') MB RAM" # 1. Hardware-Watchdog ------------------------------------------------------ echo "[1] Hardware-Watchdog" mkdir -p /etc/systemd/system.conf.d cat > /etc/systemd/system.conf.d/10-watchdog.conf <<'EOF' # Hängt das System, löst der Watchdog des Pi nach 14 s einen Neustart aus # (der bcm2835-Watchdog kann höchstens ~15 s). [Manager] RuntimeWatchdogSec=14s RebootWatchdogSec=2min EOF if [ -e /dev/watchdog ]; then ok "Watchdog aktiv nach Neustart (/dev/watchdog vorhanden)" else warn "/dev/watchdog fehlt — Kernelmodul bcm2835_wdt wird beim Start geladen"; echo bcm2835_wdt > /etc/modules-load.d/watchdog.conf; NEUSTART_NOETIG=1; fi systemctl daemon-reexec # 2. zram ------------------------------------------------------------------- echo "[2] zram (komprimierter Auslagerungsspeicher)" apt-get update -qq if apt-cache show systemd-zram-generator >/dev/null 2>&1; then apt-get install -y -qq systemd-zram-generator >/dev/null cat > /etc/systemd/zram-generator.conf <<'EOF' [zram0] zram-size = ram / 2 compression-algorithm = zstd swap-priority = 100 EOF systemctl daemon-reload systemctl restart systemd-zram-setup@zram0.service 2>/dev/null || true ok "systemd-zram-generator eingerichtet (halber RAM, zstd)" else apt-get install -y -qq zram-tools >/dev/null printf 'ALGO=zstd\nPERCENT=50\nPRIORITY=100\n' > /etc/default/zramswap systemctl restart zramswap 2>/dev/null || true ok "zram-tools eingerichtet (halber RAM, zstd)" fi cat > /etc/sysctl.d/90-zram.conf <<'EOF' # zram ist schnell: früh auslagern, Seiten einzeln holen vm.swappiness=100 vm.page-cluster=0 vm.vfs_cache_pressure=200 EOF sysctl -q --system swapon --show | sed 's/^/ /' # 3. Speichergrenzen für Container ------------------------------------------- echo "[3] Speichergrenzen für Container (cgroup memory)" if grep -qw memory /sys/fs/cgroup/cgroup.controllers 2>/dev/null; then ok "bereits verfügbar" else CMDLINE="$FIRMWARE/cmdline.txt" if [ -f "$CMDLINE" ] && ! grep -q "cgroup_enable=memory" "$CMDLINE"; then cp "$CMDLINE" "$CMDLINE.vor-projektuebersicht" sed -i '1 s/$/ cgroup_enable=memory cgroup_memory=1/' "$CMDLINE" ok "in $CMDLINE ergänzt (Sicherung: cmdline.txt.vor-projektuebersicht)" NEUSTART_NOETIG=1 else warn "nicht verfügbar — $CMDLINE prüfen" fi fi # 4. GPU-Speicher ----------------------------------------------------------- echo "[4] GPU-Speicher" CONFIG="$FIRMWARE/config.txt" if [ -f "$CONFIG" ]; then if grep -q "^gpu_mem=" "$CONFIG"; then sed -i 's/^gpu_mem=.*/gpu_mem=16/' "$CONFIG" else printf '\n# Projektübersicht: kein Bildschirm, mehr RAM für Linux\ngpu_mem=16\n' >> "$CONFIG"; fi ok "gpu_mem=16" NEUSTART_NOETIG=1 else warn "$CONFIG nicht gefunden"; fi # 5. Unnötige Dienste ------------------------------------------------------- echo "[5] Unnötige Dienste abschalten" for d in multipathd multipathd.socket ModemManager udisks2 packagekit bluetooth avahi-daemon avahi-daemon.socket; do if systemctl list-unit-files "$d.service" "$d" 2>/dev/null | grep -q "$d"; then systemctl disable --now "$d" >/dev/null 2>&1 && ok "$d aus" fi done if [ "$SNAPD_AUS" = 1 ]; then if command -v snap >/dev/null && snap list docker >/dev/null 2>&1; then warn "Docker ist als Snap installiert — snapd bleibt an" else systemctl disable --now snapd.service snapd.socket snapd.seeded.service >/dev/null 2>&1 && ok "snapd aus" fi else info "snapd bleibt an (spart ~40 MB mit --snapd-aus)" fi # 6. earlyoom --------------------------------------------------------------- echo "[6] earlyoom" apt-get install -y -qq earlyoom >/dev/null cat > /etc/default/earlyoom <<'EOF' # Bei unter 5 % freiem RAM UND unter 10 % freiem Swap den größten Prozess # beenden — nie SSH, Docker oder systemd. Der App-Container startet dann neu. # Ohne innere Anführungszeichen: systemd reicht sie sonst wörtlich weiter. EARLYOOM_ARGS="-m 5 -s 10 -r 3600 --avoid ^(sshd|dockerd|containerd|systemd|systemd-journal)$ --prefer ^node$" EOF systemctl enable --now earlyoom >/dev/null 2>&1 systemctl restart earlyoom ok "aktiv" # 7. Systemprotokoll -------------------------------------------------------- echo "[7] Systemprotokoll begrenzen" mkdir -p /etc/systemd/journald.conf.d printf '[Journal]\nSystemMaxUse=50M\n' > /etc/systemd/journald.conf.d/10-groesse.conf systemctl restart systemd-journald ok "höchstens 50 MB" # 8. Nächtlicher Neustart --------------------------------------------------- echo "[8] Nächtlicher Neustart des Containers" cat > /etc/cron.d/projektuebersicht <<'EOF' # Projektübersicht: frischer Speicher jede Nacht (gegen schleichende Lecks) 30 3 * * * root docker restart projektuebersicht >/dev/null 2>&1 EOF systemctl enable docker >/dev/null 2>&1 ok "täglich 3:30 Uhr; Docker startet beim Booten" # 9. Stromversorgung -------------------------------------------------------- echo "[9] Stromversorgung" T="" [ -r /sys/devices/platform/soc/soc:firmware/get_throttled ] && T=$(cat /sys/devices/platform/soc/soc:firmware/get_throttled) command -v vcgencmd >/dev/null && T=$(vcgencmd get_throttled | cut -d= -f2) if [ -z "$T" ]; then info "nicht auslesbar (Paket libraspberrypi-bin installieren)" elif [ "$((T))" -eq 0 ]; then ok "keine Unterspannung seit dem Start ($T)" else warn "Unterspannung/Drosselung erkannt ($T) — besseres Netzteil (5 V / 2,5 A) verwenden!"; fi # 10. WLAN ------------------------------------------------------------------ # Befund 29.10.: Der Pi "stürzte" nicht ab, sondern verlor das WLAN # (wlan0: Lost carrier) und kam danach nie wieder in das Netz # (ASSOC-REJECT in Schleife) — erreichbar war er erst nach Stecker ziehen. echo "[10] WLAN stabil halten" apt-get install -y -qq iw >/dev/null 2>&1 cat > /etc/systemd/system/wlan-energiesparen-aus.service <<'EOF' [Unit] Description=WLAN-Energiesparen aus (Pi Zero 2 verliert sonst die Verbindung) After=sys-subsystem-net-devices-wlan0.device Wants=sys-subsystem-net-devices-wlan0.device [Service] Type=oneshot ExecStart=/usr/sbin/iw dev wlan0 set power_save off RemainAfterExit=yes [Install] WantedBy=multi-user.target EOF cat > /usr/local/sbin/wlan-waechter <<'EOF' #!/bin/sh # Prüft jede Minute, ob das Gateway erreichbar ist. Ohne Netz: alle 2 # Minuten WLAN neu verbinden, nach 10 Minuten den Pi neu starten. ZAEHLER=/run/wlan-waechter.zaehler GW=$(ip route | awk '/^default/{print $3; exit}') if [ -n "$GW" ] && ping -c 2 -W 3 "$GW" >/dev/null 2>&1; then rm -f "$ZAEHLER"; exit 0 fi N=$(( $(cat "$ZAEHLER" 2>/dev/null || echo 0) + 1 )) echo "$N" > "$ZAEHLER" logger -t wlan-waechter "kein Netz seit $N Minute(n)" if [ "$N" -ge 10 ]; then logger -t wlan-waechter "Neustart des Pi"; systemctl reboot elif [ $((N % 2)) -eq 0 ]; then logger -t wlan-waechter "WLAN neu verbinden" wpa_cli -i wlan0 reassociate >/dev/null 2>&1 if [ "$N" -ge 4 ]; then ip link set wlan0 down; sleep 2; ip link set wlan0 up networkctl reconfigure wlan0 >/dev/null 2>&1 fi fi EOF chmod 755 /usr/local/sbin/wlan-waechter cat > /etc/systemd/system/wlan-waechter.service <<'EOF' [Unit] Description=WLAN-Wächter der Projektübersicht [Service] Type=oneshot ExecStart=/usr/local/sbin/wlan-waechter EOF cat > /etc/systemd/system/wlan-waechter.timer <<'EOF' [Unit] Description=WLAN-Wächter jede Minute [Timer] OnBootSec=3min OnUnitActiveSec=1min [Install] WantedBy=timers.target EOF systemctl daemon-reload systemctl enable --now wlan-energiesparen-aus.service >/dev/null 2>&1 systemctl enable --now wlan-waechter.timer >/dev/null 2>&1 if iw dev wlan0 get power_save 2>/dev/null | grep -q off; then ok "Energiesparen aus"; else warn "Energiesparen ließ sich nicht abschalten"; fi ok "Wächter aktiv (neu verbinden ab 2 min, Neustart nach 10 min ohne Netz)" echo if [ "$NEUSTART_NOETIG" = 1 ]; then echo "Fertig. Bitte den Pi jetzt neu starten: sudo reboot" else echo "Fertig." fi echo "Danach prüfen: bash Docker/pi-pruefen.sh"