Termin aus Mail, Rollen & Rechte, Abwesenheiten, Glas-Darstellung und Pi-Betrieb

- Termin aus Mail: Mailtext oder .eml per Drag & Drop/Strg+V auf Terminplan
  oder Aufgaben, KI schlägt Aufgaben vor, Prüfung im Aufgaben-Panel; neues
  Notizfeld je Aufgabe (plan_todos.notiz)
- Terminplan: Meilensteine ohne Aufgabenbezug je in eigener Zeile,
  "+ Neu"-Menü klappt neben schmaler Seitenleiste nach rechts, Leistungsphasen
- Rollen & Rechte, Abwesenheiten-Seite, Benachrichtigungen, Rundgang
- Glas-Darstellung, Logo als Favicon
- Pi-Betrieb: PDF-Arbeit im Kindprozess, Wächter, Einrichtungs- und
  Prüfskript inkl. WLAN-Wächter

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
erikandClaude Opus 5.5 committed 2026-10-29 14:16:52 +01:00
1 parent f4fb3c550b
commit b7e0bd0d92
56 files changed
+12265 -2974

No files matched your search

+9 -5
View File
@@ -7,10 +7,10 @@ WORKDIR /app
# package.json und package-lock.json zuerst kopieren, damit npm-Installation gecacht wird
COPY package.json package-lock.json ./
RUN npm ci --production
RUN npm config set fetch-retries 6 && npm config set fetch-retry-maxtimeout 180000 && npm config set fetch-timeout 600000 && npm ci --omit=dev
# Nur die Laufzeit-Dateien kopieren
COPY server.js ./
COPY server.js pdf-arbeit.js ./
COPY public ./public
# Sessions-Ordner fuer die SQLite-Datenbank sicherstellen
@@ -20,7 +20,11 @@ USER node
EXPOSE 3003
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD node -e "require('http').get('http://localhost:3003/health', r => r.statusCode === 200 ? process.exit(0) : process.exit(1)).on('error', () => process.exit(1))"
# wget (busybox) statt eines eigenen Node-Prozesses je Prüfung: auf dem
# Pi Zero kostete jeder Healthcheck sonst ~40 MB und eine Sekunde CPU.
HEALTHCHECK --interval=60s --timeout=10s --start-period=60s --retries=3 \
CMD wget -qO- http://127.0.0.1:3003/health >/dev/null || exit 1
CMD ["node", "server.js"]
# --max-old-space-size: Node räumt früher auf, statt bis an die
# Container-Grenze zu wachsen (Pi Zero 2, 512 MB). Siehe docker-compose.yml.
CMD ["node", "--max-old-space-size=160", "server.js"]
+236
View File
@@ -0,0 +1,236 @@
#!/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"
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# pi-pruefen.sh — zeigt, ob der Pi und die Projektübersicht stabil laufen.
# sudo bash Docker/pi-pruefen.sh (ohne sudo fehlen Docker und Strom)
echo "== Speicher =="
free -m
echo
echo "== Auslagerung (zram) =="
swapon --show 2>/dev/null || echo "keine"
echo
echo "== Watchdog =="
systemctl show -p RuntimeWatchdogUSec 2>/dev/null
[ -e /dev/watchdog ] && echo "/dev/watchdog vorhanden" || echo "/dev/watchdog FEHLT"
echo
echo "== Speichergrenzen für Container =="
grep -qw memory /sys/fs/cgroup/cgroup.controllers 2>/dev/null && echo "verfügbar" || echo "NICHT verfügbar (cmdline.txt, siehe pi-einrichten.sh)"
echo
echo "== Container =="
docker ps -a --filter name=projektuebersicht --format 'Status: {{.Status}}'
docker inspect projektuebersicht --format 'Neustarts: {{.RestartCount}} Grenze: {{.HostConfig.Memory}} Byte zuletzt gestartet: {{.State.StartedAt}} OOM: {{.State.OOMKilled}}' 2>/dev/null
docker stats --no-stream --format 'Verbrauch: {{.MemUsage}} ({{.MemPerc}}) CPU: {{.CPUPerc}}' projektuebersicht 2>/dev/null
echo
echo "== App =="
curl -s --max-time 5 http://localhost:3003/health || wget -qO- http://localhost:3003/health || echo "antwortet NICHT"
echo
echo
echo "== Neustarts der App (letzte 10 Meldungen) =="
docker logs projektuebersicht 2>&1 | grep -E "Wächter|Unerwarteter Fehler|server running" | tail -10
echo
echo "== Speichermangel im System (earlyoom / Kernel) =="
journalctl -u earlyoom --since "-7 days" --no-pager 2>/dev/null | grep -i "sending\|killing" | tail -5
journalctl -k --since "-7 days" --no-pager 2>/dev/null | grep -i "out of memory\|oom-kill" | tail -5
echo
echo "== Stromversorgung =="
if command -v vcgencmd >/dev/null; then vcgencmd get_throttled
elif [ -r /sys/devices/platform/soc/soc:firmware/get_throttled ]; then echo "throttled=$(cat /sys/devices/platform/soc/soc:firmware/get_throttled) (0 = gut)"
else echo "nicht auslesbar"; fi
echo
echo "== WLAN =="
iw dev wlan0 get power_save 2>/dev/null || echo "nicht auslesbar"
systemctl is-active wlan-waechter.timer 2>/dev/null | sed 's/^/Wächter: /'
journalctl -t wlan-waechter --since "-7 days" --no-pager 2>/dev/null | tail -5
echo
echo "== Letzte Systemstarts =="
last -x reboot 2>/dev/null | head -5