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:
1 parent
f4fb3c550b
commit
b7e0bd0d92
56 files changed
+12265
-2974
No files matched your search
+9
-5
@@ -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"]
|
||||
Executable
+236
@@ -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"
|
||||
Executable
+44
@@ -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
|
||||
Reference in new issue
Block a user