Umsetzungs-Log¶
Laufendes Protokoll der Roadmap-Umsetzung — was in welcher Session tatsächlich passiert ist, inklusive der Funde, die vorher niemand auf dem Zettel hatte. Neueste Einträge oben.
Ergänzt die Roadmap, die das Vorhaben beschreibt: hier steht der Verlauf. Das Log läuft bis zum Abschluss aller Epics (Zielmarke 31.12.2026).
Nachträglich rekonstruiert
Die Einträge vor dem 06.09.2026 sind aus der Session-Memory nachgetragen, das Log entstand erst danach. Sie sind knapper als die laufenden Einträge.
2026-09-13 — E0a: Alarmkette ausgewertet, ein Fehlalarm gefunden, 321 GB frei geworden¶
Issues: homelab-external#23 ausgewertet (bleibt offen) · homeserver#213 und
homelab-external#31 und aiserver#9 neu · homeserver#210 und homeserver#194 aktualisiert
PR: homeserver#214
Das 7-Tage-Fenster: bestanden, aber nicht ganz¶
Das Fenster aus #23 endete am 12.09. um 20:07. Ausgewertet wurde es aus drei Quellen: Prometheus-Alarmhistorie, ntfy-Nachrichtenzähler plus Traefik-Access-Log auf dem VPS, und die Journale von NUC und aiserver.
Kriterium 1 trägt. 15 der Meldungen liefen durch Traefik, und 15 von 15 wurden 1–3
Sekunden später mit poll=1&id=<genau diese Nachrichten-ID> abgeholt, jeweils Status 200. Dazu
poll=1&since=-Abrufe auf beiden Topics. Das am 05.09. vorgeschlagene Ersatzkriterium für
das unerreichbare subscribers >= 1 ist damit bewiesen.
Kriterium 4 nicht ganz. Im Fenster gingen 17 Meldungen raus, nicht 15 — zwei liefen
VPS-intern an Traefik vorbei und fielen erst durch den Nachrichtenzähler auf. Es waren
Kuma-Erholungsmeldungen der beiden Backup-Monitore, also die bereits behobene
Fehlalarm-Klasse aus homelab-external#20, in den ersten 6 Stunden des Fensters. Danach:
6 Tage 18 Stunden ohne einen einzigen Kuma-Zustandswechsel.
Die übrigen 15 sind belegt echt — darunter ein 45-Sekunden-Stromausfall am 09.09.
(upsmon 11:04:53 „on battery" → 11:05:38 „on line power", onbatt-shutdown-Timer korrekt
abgebrochen). Der NUC hielt durch, der aiserver fiel aus.
Der schönste Einzelbefund ist ein negativer: UpsOnBattery und AiserverBackupMetricMissing
blieben beide pending und wurden korrekt nie zugestellt. Die Kette stellt also nicht nur zu,
sie hält kurze Ereignisse auch zurück. Das ist der Gegenbeweis zu „meldet einfach alles".
Die Lehre über die Auswertung selbst¶
Prometheus läuft mit --storage.tsdb.retention.time=7d. Die ersten 16 Stunden des Fensters
waren am 13.09. schon gelöscht (ältestes Sample: 06.09. 12:05). Ein 7-Tage-Fenster ist einen
Tag zu spät aus Prometheus nicht mehr belegbar.
Gerettet hat die Auswertung der ntfy-Nachrichtenzähler auf dem VPS: lückenlos, und er zählt auch die Meldungen, die nicht durch Traefik laufen. Ohne ihn wären die zwei Kuma-Meldungen unentdeckt geblieben und das Fazit wäre falsch positiv ausgefallen.
Der Fehlalarm von heute¶
19 Stunden nach Fensterende war die UNAS 4 Sekunden nicht erreichbar (nfs: server
10.10.10.150 not responding 15:10:17 → OK 15:10:21). Ergebnis: drei Critical-Alarme für
/mnt/unas, /mnt/paperless und /mnt/monitoring, während alle Mounts durchgehend gesund und
rw gemountet waren.
Ursache: bei einem statfs-Timeout wirft node-exporter die Filesystem-Serien des Mounts
komplett weg. absent(...) mit for: 2m kann deshalb „NAS war kurz beschäftigt" nicht von
„Mount ist weg" unterscheiden — die Serien fehlten rund 3 Minuten. Solche Aussetzer gab es
10× in 8 Tagen.
Fix in homeserver#214: absent_over_time(...[10m]), womit das Fenster selbst die Entprellung
ist und die Regeln kein for mehr brauchen. Gegen den echten Vorfall verifiziert — der alte
Ausdruck feuert um 15:08, 15:09 und 15:10, der neue bleibt an jedem dieser Zeitpunkte leer.
Nebenbei fiel auf, dass /mnt/pictures gar keine Regel hatte, also der Fotobestand, der mit
E5 zum Primärsystem wird.
Wäre der Aussetzer einen Tag früher gekommen, hätte er das Fenster gerissen. Deshalb wird #213 vor dem Schließen von #23 ausgerollt.
Storage Box: 89 % → 57 %, ohne dass jemand etwas getan hat¶
2026-09-12T04:13:00 borgmatic: Remote: compaction freed about 346.03 GB repository space.
2026-09-12T05:05:40 borgmatic: compaction freed about 47.08 GB repository space.
Normal sind rund 0,6 GB pro Nacht. Es war keine Konfigurationsänderung (hetzner.yaml hat
mtime 23. Mai) und kein borg-Update (apt-Historie 09.–13.09.: nur python3.10, locales, libc6,
perl).
Der Umweg, den diese Session genommen hat, gehört zum Verlauf: Zuerst wurde vermutet, am 12.09. habe ein Massen-Prune stattgefunden, und daraus geschlossen, die 07.09.-Bewertung („Retention kürzen ist wirkungslos", gemessen am Exklusiv-Anteil des ältesten Einzelarchivs) sei widerlegt. Die Archivliste hat das kassiert:
| Gruppe | Archive |
|---|---|
| Monatlich | 01-04, 01-31, 02-28, 03-31, 04-30, 05-31, 06-30, 07-31, 08-31 |
| Wöchentlich | 08-16, 08-23, 08-30 |
| Täglich | 09-06 … 09-13 |
20 Archive, lehrbuchmäßiges Muster, und das älteste ist weiterhin nuc-2026-01-04 — am
07.09. waren es 21 Archive ab dem 04.01., heute 20 ab dem 04.01. Das Aufbewahrungsfenster hat
sich nicht verschoben, am 12.09. fiel nur das übliche Tagesarchiv heraus. Die
07.09.-Bewertung war richtig und bleibt stehen.
Tatsächlich waren die 346 GB physisch toter Speicher, den frühere Prunes längst logisch
freigegeben hatten und den borg compact nie abgetragen hat — borg schreibt ein Segment erst
um, wenn sein Totanteil über eine Schwelle steigt (Standard 10 %). Das ist ein anderer Befund:
die Storage Box war zu rund 38 % mit Müll gefüllt. Der Füllstand von 89 % war nie ein
Kapazitäts- und nie ein Wachstumsproblem, sondern Hauswirtschaft. Kein Aufstocken und keine
Retention-Entscheidung hätte daran etwas geändert.
Das erklärt weiterhin, warum das lokale Repo 43 % kleiner war: den Altbestand gab es lokal
nicht in dem Maß (346 GB gegen 47 GB). Heute hält /dev/md0 600 G für beide lokalen Repos
zusammen, die Storage Box 592 G für den NUC allein.
Offen bleibt, warum die nächtliche Compaction den Rückstand über Monate nicht abtrug. Die Zeiten passen zu einer lange aufgeschobenen Arbeit, die endlich durchlief (Prune 12 min statt 20–50 s, Compaction 1 h 43 min statt Sekunden). Unbelegte Hypothese: die Storage Box hängt über rclone-fuse, und eine dort abbrechende Compaction lässt Arbeit liegen, die erneut ansteht.
Woran man den Unterschied künftig erkennt: steigt der Füllstand wieder Richtung 89 %, während das Archivfenster gleich lang bleibt, ist es wieder Compaction-Rückstand. Wächst dagegen die Datenmenge bei konstantem Fenster, ist es echtes Wachstum.
Unverändert offen und von freiem Platz nicht berührt: die UNAS-Fotos sind in beiden Configs auskommentiert, also nirgends gesichert. Der Platz kauft Zeit, nicht Abdeckung.
#194: Bestandsaufnahme statt Vermutung¶
Der Stand vom 07.09. prüfte sieben namentlich bekannte Variablen. Diesmal ein flächiger Scan
über alle getrackten Dateien, und für jeden Treffer per SHA-256-Vergleich gegen .Config.Env
des laufenden Containers die Frage: lebt der Wert noch?
Fünf Secrets sind aktiv und stehen in getrackten Dateien — CF_DNS_API_TOKEN
(traefik/.env, zonenweite DNS-Kontrolle), SONARR__APIKEY und RADARR__APIKEY
(media-stack), TS3SERVER_DB_PASSWORD (ts3), PAPERLESS_SECRET_KEY
(paperless-ngx/paperless.env, ebenfalls getrackt).
Neu: zwei tote Stacks tragen Secrets in Git — proxy-manager (3 MySQL-Passwörter) und
pzserver (SRCDS_TOKEN). Für beide existiert auf dem NUC kein Container, auch kein
gestoppter. Aufräumarbeit für E0b; der Steam-Token muss bei Valve entwertet werden.
Entschärft: die Traefik-Dashboard-Credentials sind tatsächlich admin/admin — die Datei
sagt es selbst (# Generiert mit: htpasswd -nb admin admin). Aber von außen nicht erreichbar:
traefik.home.robinwerner.net löst öffentlich nicht auf, TCP 443 zur Heim-IP ist zu, und ein
Host-Header-Versuch vom VPS kommt nicht durch. Ein LAN-Risiko, kein Internet-Risiko — der Stand
nach #195 hält.
Panne des Tages¶
Bei dieser Bestandsaufnahme sind das TS3-DB-Passwort und zwei der vier
Monitoring-Tokens erneut in einen Chat-Verlauf geraten. Die Redigierung zielte auf
=-Syntax, die Compose-Datei nutzt YAML, und die borgmatic-Journalzeilen enthielten die vollen
Ping-URLs.
Für die Monitoring-Tokens ist es das zweite Mal nach dem 07.09. Die Lehre: solange diese Werte im Klartext in Dateien und Journalen stehen, tauchen sie bei jeder Diagnosearbeit wieder auf. Das ist kein Argument für vorsichtigeres Hinsehen, sondern für Rotation und Credential- Dateien.
Erster Rotationsschritt vorbereitet¶
Weil das TS3-DB-Passwort durch die Panne oben erneut exponiert war, wurde es vorgezogen:
PR homeserver#215 holt es aus der getrackten Compose-Datei nach ts3/.env, mit
${TS3_DB_PASSWORD:?…} — fehlt die Variable, bricht docker compose ab statt mit leerem
Passwort zu starten (in beide Richtungen geprüft).
Die eigentliche Rotation steht im PR als Runbook, mit zwei Fußangeln, die man sonst erst
merkt, wenn TS3 nicht mehr verbindet: MARIADB_PASSWORD wirkt nur bei der
Erstinitialisierung, die Datenbank existiert längst — das Passwort muss per SQL geändert
werden. Und MARIADB_RANDOM_ROOT_PASSWORD: yes heißt, das root-Passwort ist mit dem ersten
Recreate verloren, weshalb der Benutzer teamspeak sein Passwort selbst ändert.
TS3-Rotation durchgeführt — mit zwei MariaDB-Fallen¶
Die Rotation lief direkt im Anschluss und ist belegt: teamspeak neu erzeugt, Listener auf
9987/30033/10011, keine DB-Fehlerzeile, teamspeak_db unberührt (die Datenbank lief
durchgehend), und das alte Passwort aus der History wird mit ERROR 1045 abgelehnt.
Zwei Fallen unterwegs, beide vorher nicht bedacht:
ALTER USER reicht nicht für das eigene Konto. MariaDB verlangt dafür CREATE USER; der
Benutzer hat laut SHOW GRANTS nur USAGE ON *.* plus ALL PRIVILEGES ON teamspeak.*. Der
richtige Weg ist SET PASSWORD ohne FOR-Klausel — laut Doku „which any non-anonymous
user can perform".
Ein nackter String wird bei SET PASSWORD als fertiger Hash gespeichert, nicht gehasht.
SET PASSWORD = '<klartext>' hätte das Konto unbrauchbar gemacht — und weil das root-Passwort
durch MARIADB_RANDOM_ROOT_PASSWORD: yes mit dem ersten Recreate verloren ist, wäre das nicht
mehr zu reparieren gewesen. Richtig ist SET PASSWORD = PASSWORD('<klartext>'). Nützlicher
Nebeneffekt derselben Syntax: der alte Hash aus SHOW GRANTS lässt sich unverändert
zurückschreiben — ein Rollback, der das alte Klartextpasswort nicht braucht.
Beim Recreate wurde bewusst nur teamspeak angefasst: MARIADB_PASSWORD wirkt ohnehin nur bei
der Erstinitialisierung, und der Stack hat kein depends_on, weshalb ein Recreate beider
Dienste TS3 vor die Datenbank hätte starten können.
Rollout — und ein selbst gebauter Fehlschlag¶
homeserver#214 und #218 gingen zusammen raus. Der erste Reload scheiterte mit HTTP 500:
err="/etc/prometheus/rules/node-alerts.yml: open …: permission denied"
err="error loading rules, previous rule set restored"
Ursache war eine umask 077, die aus dem TS3-Schritt noch in derselben Root-Shell galt —
git pull schrieb die geänderten Dateien mit Modus 0600 neu, für den als nobody laufenden
Container unlesbar. Betroffen waren genau die zwei angefassten Dateien, die übrigen
Regeldateien blieben auf 0644. Das ist eine Nebenwirkung des Verzeichnis-Mounts aus #199: der
Container liest die Host-Dateien direkt, also entscheidet deren Modus mit. git überträgt außer
dem Executable-Bit keine Rechte, ein Pull schreibt also immer mit der aktuellen umask.
Der unangenehme Teil: prometheus.yml mit 0600 heißt, ein Neustart von Prometheus wäre
nicht mehr hochgekommen. Der laufende Prozess merkte nichts, weil er seine Config beim Start
gelesen hatte. Lehre für Runbooks: eine restriktive umask gehört in eine Subshell
((umask 077; … > .env)), nie in die Shell, in der danach noch git läuft.
Und wieder war es still: prometheus_config_last_reload_successful stand auf 0, eine Regel
darauf gibt es nicht. Ohne den manuellen Reload und den Blick ins Log hätte nichts gemeldet,
dass Prometheus mit veralteten Regeln läuft.
Nach chmod 644 war der Endstand verifiziert: 4 × absent_over_time, keine alte
absent()-Regel mehr, /mnt/pictures dabei, 0 Ziele down, Reload erfolgreich, keine aktiven
Alarme.
Nebenfund: Überwachung, die nicht überwacht — Fall 3 und 4¶
Beim Lesen der Prometheus-Logs fielen zwei dauerhaft ausgefallene Scrape-Ziele auf, im
gesamten Retention-Fenster nie up, obwohl beide Container laufen: qdrant:6333/metrics gibt
401 (Prometheus schickt keinen API-Key), open-webui:8080/metrics liefert
<!doctype html> (Metrik-Endpunkt nie aktiviert). Letzteres scheiterte alle 15 Sekunden
und machte den Log unlesbar.
Der eigentliche Fund ist aber: von 18 Scrape-Jobs hat genau einer eine Regel auf up
(AiserverDown). Robins Entscheid dazu war klar — alles KI-bezogene wandert auf den aiserver
(E4), also wurden beide Jobs in #218 entfernt statt repariert. Dabei kam heraus, dass die
Scrape-Target-Tabelle in docs-metriken.md nur 10 von 18 Jobs listete, und genau in dieser
Lücke lebten die toten Ziele; die Tabelle ist jetzt vollständig.
prometheus hängt nur wegen dieser zwei Jobs im Netz ollama_internal. Das ist jetzt toter
Ballast, braucht aber einen Container-Recreate — als Kommentar in der Config festgehalten und
E4 zugeordnet.
Damit stehen vier Fälle desselben Musters in homeserver#217:
| Regel | Was still nicht funktionierte |
|---|---|
ContainerRestarting |
lief auf container_restart_count, das cAdvisor nie exportiert (#197) |
NfsMountLost |
feuerte bei 4-Sekunden-Aussetzern statt bei verlorenen Mounts (#213) |
fehlende TargetDown |
zwei Scrape-Ziele dauerhaft down, keine Meldung |
| fehlende Reload-Regel | fehlgeschlagener Config-Reload, keine Meldung |
Für E3 ist das weniger eine Liste einzelner Regeln als ein Prinzip: jede neue Regel einmal
gegen /api/v1/query prüfen, ob die Metrik überhaupt Serien hat — und für jede Überwachung die
Gegenfrage stellen, woran man merken würde, dass sie selbst ausgefallen ist.
Stand E0a danach¶
homelab-external#23 ist geschlossen, E0a ist abgenommen. Kriterium 2 entfiel auf Robins
Entscheid („Lass es einfach mit dem Screenshot, wir haben es schon ausreichend bewiesen") — der
Logbeleg ist ohnehin stärker als ein Foto.
Offen bleiben homeserver#194 (Rotation; TS3 ✓, nächster Posten CF-Token), homeserver#81
(Mosquitto, braucht Wartungsfenster) und die provozierten Fehler nach homelab-external#31.
Der Cloudflare-Tunnel ist laut Robin im Zero-Trust-Dashboard gelöscht — damit ist das
TUNNEL_TOKEN aus der History entwertet und fällt endgültig von der Rotationsliste.
Neu angelegt: homeserver#216 (Backup-Konzept belastbar machen, inklusive der Frage, was
die UNAS Pro inzwischen selbst kann — Robin: „Das muss dann wirklich RockSolid sein und nicht
auf Vermutungen basieren") und homeserver#217 (TargetDown- und Reload-Regel).
2026-09-11 — E0a: #200 abgeschlossen, der Reboot war die Ursache¶
Issue: homeserver#200 geschlossen (beide Teile)
PR: homeserver#212
Die Auswertung, auf die vier Tage gewartet wurde¶
Am 07.09. stand die Frage offen, ob die Stale-Ereignisse wirklich weg sind oder ob 19 Stunden ohne Ereignis nur Zufall waren (p ≈ 7 %). Jetzt, nach 5 Tagen und 4 Stunden, ist es eindeutig:
| Nachweis | Wert |
|---|---|
RestartCount des Containers |
0 |
| „stale" im Container-Log | 0 Treffer |
min_over_time(nuc_ups_scrape_success[4d]) |
1 — 23.040 Messungen, kein Fehlschlag |
changes(nuc_ups_scrape_success[4d]) |
0 |
Bei der vorherigen Rate von 3,3 Ereignissen pro Tag wären rund 17 fällig gewesen; die Wahrscheinlichkeit für null liegt bei etwa 4·10⁻⁸.
Der tragende Beleg ist RestartCount: 0, nicht die Metrik. Der Collector tastet alle 30 s
ab und kann ein 20-Sekunden-Ereignis verpassen — der Restart-Zähler nicht, weil jedes Ereignis
über den Healthcheck einen Neustart ausgelöst hätte. Dass zwei unabhängige Quellen dasselbe
sagen, macht die Aussage belastbar.
Was offen bleibt, und warum das in Ordnung ist¶
Ob das Kernel-Update (5.15.0-179 → 191) oder der Speicherdruck (Swap 3,9 von 4 GiB nach 100
Tagen Uptime, danach 0) das Problem behoben hat, ist rückwirkend nicht trennbar. Weitere
Instrumentierung bringt nichts, solange das Problem nicht auftritt — debug_min = 1 hat in
fünf Tagen exakt null Ereignisse protokolliert, weil es keine gab.
Der eigentliche Schaden war ohnehin nie das einzelne Ereignis (20 s blinde Überwachung, danach Selbstheilung), sondern dass es vier Monate unbemerkt lief. Das ist behoben: Ein Rückfall wäre jetzt ein Alarm, keine Archäologie.
Randbeobachtung: die eigene Einschränkung live bestätigt¶
Beim Deploy von #212 war die USV-Überwachung für einige Sekunden unterbrochen — die Metrik hat
das nicht erfasst (min_over_time[15m] = 1, keine Flanke). Der 30-Sekunden-Takt fiel zufällig
daneben.
Genau diese Einschränkung steht seit dem 07.09. in nut/docs.md, hier unbeabsichtigt
verifiziert: Der Collector misst die Rate über Stunden, nicht das Einzelereignis. Ein
schöner Beleg dafür, dass es richtig war, den Beweis für Teil 1 auf RestartCount zu stützen
statt auf die Metrik.
Verifikation nach dem Deploy¶
Status: running RestartCount: 0 Health: healthy FailingStreak: 0
driver.debug: 0 [D1]-Zeilen: 0 (Log 236 → 14 Zeilen)
driver.flag.pollonly: enabled driver.parameter.pollfreq: 30
pollonly und pollfreq blieben erhalten, und der Container lief nicht in die Restart-Schleife
aus #206.
2026-09-07 — E0a: USV-Metriken gebaut, und die Stale-Ereignisse sind vorerst weg¶
Issue: homeserver#200 (Teil 2 erledigt, Teil 1 offen)
PRs: homeserver#209, homelab-documentation#43
Teil 1: das Log war leer, weil nichts mehr passiert ist¶
Die Session sollte mit dem auswerten beginnen, was debug_min = 1 seit dem 06.09. mitschreibt —
der libusb-Fehler, der erklärt, warum usbhid-ups 3–5×/Tag verstummt. Der steht nirgends.
Es gab schlicht kein Stale-Ereignis mehr, seit der Container beim Reboot neu erzeugt wurde:
19 h 17 min, RestartCount: 0, kein einziger Treffer auf „stale".
Zum Vergleich die Restart-Verteilung aus dem Docker-Journal des alten Boots:
| Zeitraum | Ereignisse/Tag |
|---|---|
| 01.–09.08. | 7–12 |
| 10.08.–05.09. | 1–5 (Ø 3,3) |
| seit 06.09. 20:11 | 0 |
Bei 3,3/Tag wären in 19 h rund 2,6 zu erwarten gewesen. Null ist auffällig, aber bei p ≈ 7 % noch kein Beweis — das braucht 2–3 Tage.
Ein Ausschluss steht jetzt breiter: „kein USB-Reset auf Kernel-Ebene" war über 7 Tage geprüft. Ich habe den kompletten vorherigen Boot durchgesehen — 29.05. bis 06.09., über drei Monate: null USB-Resets, Disconnects oder xhci-Fehler. Der Kernel hat die USV nie verloren; das Problem lag die ganze Zeit im Userspace.
Neue Hypothese: der Reboot selbst. Kernel 5.15.0-179 → 191, und der Swap war nach 100 Tagen Uptime mit 3,9 von 4 GiB voll. Rückwirkend nicht beweisbar — nur beobachtbar, ob es wegbleibt.
Teil 2: die Messgrundlage, die vorher fehlte¶
Statt weiter zu warten wurde Teil 2 gebaut, denn der liefert genau die Zeitreihe, mit der sich
Teil 1 überhaupt auswerten lässt. nut/scripts/ups-textfile-collector.sh ruft alle 30 s upsc
auf und schreibt 15 Metriken über den Textfile-Collector des node-exporter, dazu 7 Alarmregeln.
Die tragende Metrik ist nuc_ups_scrape_success: upsc scheitert mit Error: Data stale genau
dann, wenn der Treiber verstummt. Damit wird aus dem Problem eine Zeitreihe statt einer Zahl,
die jemand im Journal zusammenzählt:
Zwei Fehlerfälle sind absichtlich behandelt, beide aus früheren Pannen gelernt:
- Nicht-numerische Werte werden übersprungen. NUT liefert auf manchen Modellen
unknown; eine einzige unparsbare Zeile lässt den node-exporter die ganze Datei verwerfen — das hätte stillschweigend alle USV-Metriken mitgenommen. Genau die Sorte stiller Ausfall, die dieses Epic aufräumt. - Der Erfolgs-Zeitstempel wird bei einem Fehlschlag übernommen. Verschwände die Serie während
eines Ausfalls, wäre „wie lange schon stale" nicht messbar und
UpsMetricMissingwürde zusätzlich zum echten Alarm feuern.
Funde nebenbei¶
- Die Alerting-Doku war veraltet:
docs-alerting.mdlistete weiterhinContainerRestarting, obwohl die Regel am 06.09. in#197als tot entfernt wurde. Wer die Doku las, hielt eine Restart-Erkennung für vorhanden, die es seit vier Monaten nicht gab und seit gestern gar nicht mehr gibt. Korrigiert. - Drei Runbook-Kommandos wären falsch gewesen, hätte ich sie aus Annahme geschrieben:
Prometheus hat kein Port-Mapping (
curl localhost:9090läuft ins Leere), das Image hatwget, aber wedercurlnochkill(Reload also über--web.enable-lifecycle), undrule_filesist ein Glob — die neue Regeldatei wird allein durchgit pullgefunden. Alle Kommandos im Runbook sind gegen den laufenden NUC geprüft. - Klartext-Secrets in Container-Umgebungen (
nut-upsd,navidrome,gluetun). Geprüft und als Kommentar an#194gehängt: aktuell steht keines davon in einer getrackten Datei. In der Historie zwei echte Literale —API_PASSWORD(ein Tag) undWIREGUARD_PRIVATE_KEY(20.09.2024 → 27.09.2025, gut ein Jahr). Beide sind nachweislich rotiert, gemessen über einen Hash-Vergleich gegen die laufenden Container. Zusammen damit, dasshomeserverprivat ist: kein Handlungsbedarf, insbesondere kein History-Rewrite. - Nur zwei Stacks nutzen den Infisical-Agent (
ollama,speedtest-tracker). Alle anderen halten ihre Secrets in gitignorierten.env-Dateien — das ist der Umfang, den E2a vor sich hat.
Nachtrag am selben Abend: eingerichtet und verifiziert¶
Robin hat die Host-Einrichtung durchgeführt, alle drei PRs sind gemergt. Gegengeprüft:
| Prüfung | Ergebnis |
|---|---|
| Timer | enabled, active, OnUnitActiveUSec=30s greift |
| Letzter Lauf | Result=success |
ups.prom |
18 s alt, root:root 644 |
| node-exporter | Up, Flag aktiv — kein Restart durch den :ro-Mount |
| Metriken im Exporter | 15 von 15 |
node_textfile_scrape_error |
0 |
| Prometheus | Ladung 100 %, Restlaufzeit 2025 s, Last 29 % |
| Alarmregeln | 7/7 geladen, alle health: ok |
nut-upsd steht zu diesem Zeitpunkt bei 21 Stunden, RestartCount: 0, null Stale-Treffer.
Dabei gefunden: die Storage Box läuft voll¶
Issue: homeserver#210 (E1)
Beim Prüfen der aktiven Alarme feuerte einer, der nichts mit dieser Session zu tun hat:
DiskSpaceLow für /mnt/storagebox seit dem 05.09. — 913G von 1,0T belegt, 90 %.
Nichts ist kaputt: Der borgmatic-Lauf von 15:36 war erfolgreich, und die Journal-Treffer auf
„error/failed" sind ausnahmslos Deprecation-Warnungen (on_error is deprecated). Aber die Box
ist das Off-Box-DR-Ziel, und der Puffer schrumpft.
Aufteilung: nuc 913G, aiserver 13M. Erster Verdacht war die Retention (keep_monthly: 12,
keep_yearly: 2 halten gelöschte Daten bis zu zwei Jahre fest) — das erwies sich im nächsten
Abschnitt als falsch.
Der Punkt, der es dringend macht: In hetzner.yaml stehen die UNAS-Daten auskommentiert
(# - /mnt/unas/Fotos). Die Familienfotos sind also derzeit nicht off-box gesichert — und für
sie ist auf der Box auch kein Platz. Das kollidiert mit E5 (#188), wo Immich zum
Familien-Primärsystem wird.
Die Zahlen dazu lieferte Robin anschließend selbst — die Configs sind 0600 root, an
borgmatic rinfo kam nur er heran.
Der Backup-Status, den Robin daraufhin sehen wollte¶
Aus der Frage „warum ist es so groß und warum liegt lokal weniger?" wurde die inhaltsreichste Untersuchung des Tages. Drei Hypothesen wurden aufgestellt und zwei davon widerlegt — festgehalten, weil das Widerlegen hier mehr wert war als das Vermuten.
| lokal | Hetzner | |
|---|---|---|
| Original | 11,31 TB | 11,67 TB |
| Dedupliziert | 686,67 GB | 978,99 GB |
| Unique Chunks | 1.255.267 | 1.371.847 |
| Archive | 21 (ab 04.01.2026) | 21 (ab 04.01.2026) |
Widerlegt 1 — „das lokale Repo ist jünger": Beide haben 21 Archive mit praktisch identischen Zeitpunkten.
Widerlegt 2 — „das Hetzner-Repo wurde unkomprimiert befüllt": Genau umgekehrt. Das
Januar-Archiv spart 46,3 % durch Kompression, das September-Archiv nur 5,7 %. borg recreate
--recompress fällt als Option weg.
Stattdessen gefunden — die Dateizahl je Archiv ist um 94,5 % eingebrochen: von 5.595.710
(Januar) auf 305.662 (September), bei gestiegenem Volumen. Ursache ist die Umstellung von
expliziten Pfaden auf --patterns-from. Kurz sah das nach einem Backup aus, das seit Monaten
die Systemkonfiguration nicht mehr enthält.
Der Test darauf gab Entwarnung — alle sechs Quellverzeichnisse sind im Archiv:
Weggefallen sind rund 5,3 Mio Kleindateien, mit hoher Wahrscheinlichkeit der overlay2-Baum
unter /var/lib/docker — aus den Images jederzeit rekonstruierbar. Das erklärt zugleich den
Kompressionsabfall, und die Laufzeit sank von 14 h 15 min auf 4 min 9 s. Eine gute Optimierung,
keine Lücke.
Die teuerste Korrektur betraf die eigene Rechnung. Die Schätzung „24,5 GB je Archiv" stand
bereits in #210. borgmatic info je Archiv zeigt: Am ältesten Monatsarchiv hängen 3,44 GB,
am neuesten 885 MB. Die 979 GB stecken im gemeinsamen Grundbestand, nicht in den alten
Archiven. Damit ist Retention kürzen weitgehend wirkungslos — es bliebe nur Aufstocken oder
die Quellen zu reduzieren. Das Issue wurde korrigiert.
Offen bleibt, warum das lokale Repo bei nahezu gleichen Quellen 43 % kleiner ist.
Zweiter Fund: mkdocs prüft seine Links nicht¶
Issue: homelab-documentation#45 (E0c)
mkdocs.yml setzt weder strict: true noch validation:. Ein toter interner Link erzeugt nur
eine Warnung, der Build läuft grün durch und deployt. Der grüne Build von #43 beweist also
nicht, dass die vier neuen Querverweise auflösen — nur, dass gebaut wurde; geprüft wurden sie von
Hand. Dieselbe Fehlerklasse wie die toten Alarmregeln: ein Guardrail, der aussieht, als würde er
prüfen. Beim Sichten fiel nebenbei auf, dass Infisical Agent zweimal in der Nav steht.
Zweiter Teil: #195 abgeschlossen — und ein offener Port gefunden¶
Issue: homeserver#195 geschlossen · homeserver#211 neu (geparkt)
Bei #195 fehlte seit dem 06.09. nur ein Nachweis: ob der UDM-Port-Forward auf 443 noch aktiv ist. Ein LAN-Test ist wegen Hairpin-NAT wertlos — also lief er vom Hetzner-VPS aus, einem echten externen Aussichtspunkt.
Vorher die Heim-IP verifiziert, aus zwei unabhängigen Quellen: Der cloudflare-ddns-Container
hatte 217.250.186.222 drei Minuten zuvor bestätigt, ifconfig.me vom NUC lieferte dieselbe.
Ohne diesen Schritt hätte der Test womöglich den Anschluss eines fremden Telekom-Kunden getroffen.
| Ziel | Ergebnis |
|---|---|
1.1.1.1:443 (Kontrolle) |
OFFEN |
VPS selbst :443 (Kontrolle) |
OFFEN |
Heim-IP :443 |
gefiltert (DROP) |
Die Kontrollen belegen, dass die Methode trägt. Wäre der Forward wirksam, müsste Traefik den Handshake beantworten. #195 geschlossen.
Der Fund nebenbei: ServerQuery ist von außen erreichbar¶
Da der externe Aussichtspunkt ohnehin stand, wurden zwölf Ports mitgeprüft. Genau zwei waren
offen: 30033 (TS3-Filetransfer, gehört zum öffentlichen Betrieb) und 10011 — die
TeamSpeak-ServerQuery-Administrationsschnittstelle. Sie antwortet von außen nicht nur mit einem
Banner, sondern führt Kommandos aus (version → error id=0 msg=ok).
Die query_ip_allowlist mit 127.0.0.1 im Log sieht nach Zugangsschutz aus, ist aber keiner —
sie nimmt IPs vom Flood- und Ban-Schutz aus. Vor einem Login steht nur das Passwort.
IR-Check sauber: 126 Logzeilen seit dem 03.06.2026, kein einziger Login-Versuch, keine Bans. Exposition ja, Ausnutzung nein.
Robins Entscheidung: Der Port bleibt vorerst offen, ServerQuery wird gebraucht. In #211 dokumentiert und geparkt, damit der Befund bei einem künftigen Review nicht erneut als neue Entdeckung auftaucht. Als Mittelweg für später notiert: Zugriff über Tailscale statt über den offenen Port, thematisch bei E8.
Methodisch schlecht gelaufen: Die Bewertung dieses Fundes wechselte zweimal — erst Alarm,
dann Entwarnung, dann wieder Alarm. Ursache waren ein falscher Container-Name (ts3 statt
teamspeak) und ein fehlerhafter Banner-Test (head -c 200 blockiert, wenn die Antwort kürzer
ist), vor allem aber das Bewerten zwischen den Messungen statt danach. Der Endstand ist belegt,
der Weg dahin war unnötig unruhig.
Offen¶
In 2–3 Tagen Teil 1 auswerten. Ab jetzt geht das über die Metrik statt über das Journal:
Bleibt es bei 0 Ereignissen, war der Reboot die Ursache und debug_min kann raus. Kommt eines, steht der libusb-Fehler jetzt im Log.
2026-09-06 (4) — E0a: Reboots durchgeführt, plus eine selbstverursachte Panne¶
Issue: homeserver#196 erledigt
PR: homeserver#206 (Regression aus #204)
Zuerst: eine Regression aus dem eigenen PR¶
Der Verzeichnis-Mount für nut aus #204 trug :ro. Das Entrypoint des Images führt aber
ein chgrp auf /etc/nut/local aus — auf einem Read-only-Mount scheitert das, das Entrypoint
bricht mit Exit 1 ab, restart: unless-stopped startet neu. nut-upsd lief rund zwei
Stunden in einer Restart-Schleife, 198 Restarts, die USV-Überwachung war in der Zeit tot.
Der Einzeldatei-Mount davor hatte das Problem nicht: Docker legte /etc/nut/local als
beschreibbares Verzeichnis an, nur die Datei darin war read-only.
Der Beleg lag sogar schon vor: Bei der Analyse zu #197 war das Verzeichnis mit
drw-r----- root nut aufgelistet worden — die Gruppe nut ist genau das Ergebnis dieses
chgrp. Angesehen, nicht gelesen.
Die Lücke in der Prüfung: #204 hatte mit docker compose config verifiziert, dass die
Mounts auflösen — nie, ob ein Container mit ihnen startet. Das ist kein Ersatz. In der
mitgegebenen Verify-Liste standen Traefik und unbound, ausgerechnet nut aber nicht. Beim Fix
wurden beide Varianten lokal gegen das echte Image gestartet, bevor irgendetwas behauptet
wurde.
Koordinationspanne beim Hetzner¶
Der Hetzner wurde zweimal neu gestartet — einmal von Claude um 19:11, einmal von Robin um 19:16. Ursache: Die Aufgabe war Robin zugewiesen worden („das machst du"), er schickte daraufhin den SSH-Key, und Claude leitete daraus ab, sie selbst ausführen zu sollen, ohne das deutlich anzukündigen. In der Folge wurde Robins „Reboot ist durch" fälschlich auf den NUC bezogen und dessen fehlender Neustart als Fehlschlag gedeutet.
Ohne Schaden, aber die Lehre steht: Wer eine Aufgabe abgibt, führt sie nicht ungefragt selbst aus, nur weil die Mittel dazu bereitgestellt werden.
Die Reboots¶
| Hetzner | NUC | |
|---|---|---|
| Kernel | 6.8.0-124 → 6.8.0-139 |
5.15.0-179 → 5.15.0-191 |
| Uptime vorher | 75 Tage | 100 Tage |
| Ausfallzeit | ~30 s | ~90 s |
| Container | 12/12, Liste identisch | 65/65, Liste identisch |
Auf dem Hetzner lief anschließend ein dist-upgrade mit Docker 29.6.1 → 29.8.0 und
containerd 2.2.5 → 2.3.4. Bemerkenswert: der Austausch des Daemons hat die laufenden Container
nicht neu gestartet.
Das Drop-in hat messbar gewirkt¶
Der eigentliche Ertrag des Runbooks, im Bootlog des NUC nachweisbar:
mnt-unas / pictures / monitoring / paperless 20:11:31
mnt-storagebox 20:11:32
docker.service 20:11:40 ← 8 Sekunden später
Ohne RequiresMountsFor wären beide parallel gestartet. Diese acht Sekunden sind der
Unterschied zwischen „Container sieht seine Daten" und „Container arbeitet dauerhaft auf einem
leeren Verzeichnis". Gegengeprüft: Immich, Paperless, VictoriaLogs und VictoriaMetrics sehen
alle ihre Daten.
Swap war vorher zu 3,9 von 4,0 GiB belegt und steht jetzt bei 0.
2026-09-06 (3) — E0a: Einzeldatei-Mounts beseitigt, Reboot-Runbook¶
Issues: homeserver#199 geschlossen · #196 vorbereitet
PRs: homeserver#204, homelab-documentation#40
Einzeldatei-Mounts (#199)¶
Die letzten vier Stacks umgestellt — traefik, pihole, nut, influxdb. Jeder legt seine
Configs jetzt in ein configs/-Unterverzeichnis und mountet das Verzeichnis. Damit hat das
Repo keinen Einzeldatei-Bind-Mount mehr.
configs/ mit „s" ist kein Zufall. Der erste Versuch mit config/ scheiterte an
.gitignore, das ein globales **/config/ enthält. Bereits getrackte Dateien überleben das
zwar, aber jede später hinzugefügte Datei wäre stillschweigend nicht eingecheckt worden — eine
Falle derselben Bauart wie die, die hier beseitigt wird. monitoring-stack benutzte
configs/ ohnehin schon.
Vor jeder Umstellung geprüft, was das Image am Zielpfad mitbringt, weil ein Verzeichnis-Mount
das verdeckt: bei traefik, unbound und nut existiert das Verzeichnis im Image gar nicht,
bei telegraf liegen dort telegraf.conf und ein telegraf.d/ — letzteres wird deshalb als
leeres Verzeichnis mitgeführt.
Traefiks Dateien behalten ihre .yml-Namen, damit sämtliche Container-Pfade unverändert
bleiben und der File-Provider-Eintrag weiter stimmt. Am kritischsten Bauteil ändert sich so
nur der Mount und sonst nichts.
Fünfter toter Guardrail: der unbound-Healthcheck¶
Beim Prüfen fiel auf, dass unbound seit Containerstart als unhealthy markiert ist —
FailingStreak: 158070, also rund 55 Tage ununterbrochen. Der Check ruft drill-hc auf, ein
Binary, das im Image nicht existiert. DNS funktionierte die ganze Zeit einwandfrei; der
Check hat nie etwas ausgesagt, weder im Guten noch im Schlechten.
Das Image bringt auch keine Shell mit, CMD-SHELL ist dort also unmöglich. Von den
vorhandenen Binaries unterscheidet nur dig sauber: im Container verifiziert, 0 wenn unbound
antwortet, 9 wenn nichts da ist. delv wäre inhaltlich näher an der DNSSEC-Absicht gewesen,
liefert aber auch gegen einen toten Server 0 — also ebenso nutzlos. Der neue Check erkennt
einen toten Resolver, keinen SERVFAIL.
Reboot-Runbook (#196)¶
Als Runbook angelegt. Der Kern ist ein struktureller Fund:
docker.service hat keine Abhängigkeit zu den Mount-Units, es ist nur nach
network-online.target geordnet. Die fünf Netzwerk-Mounts sind Before=multi-user.target,
Docker aber auch — zueinander sind sie damit nicht geordnet.
Docker kann Container also starten, bevor NFS und Storage Box bereit sind. Bind-Mounts werden
beim Start aufgelöst; zeigt /mnt/unas in dem Moment noch auf das leere lokale Verzeichnis,
arbeitet der Container dauerhaft darauf weiter. Betroffen wären Immich, Paperless, der
Media-Stack und die 5-Jahres-Daten von VictoriaMetrics und VictoriaLogs. Das Runbook enthält
das systemd-Drop-in mit RequiresMountsFor als einmalige Vorbedingung.
Zweiter Punkt, der leicht übersehen wird: Pi-hole auf dem NUC ist der DNS-Server des ganzen LANs. Während des Reboots löst im Haus nichts auf — daher die Ansage an Lars und die Prüfung, ob auf der UDM ein Zweit-Resolver hinterlegt ist.
Alle Verify-Kommandos wurden gegen die laufende Maschine getestet; zwei Container-Namen und ein Datenpfad im Entwurf waren falsch und sind korrigiert. Die Reboots selbst stehen noch aus, sie brauchen ein Wartungsfenster.
Aus der Verifikation heraus: zwei Leichen entfernt¶
PRs: homeserver#205, bonus-tracker#42
Beim Gegenprüfen des Deployments fielen zwei tote Routen auf. Zwei eigene Prüffehler übrigens
auch: die Traefik-Metrikzahl fiel von 3262 auf 66, was nach Verlust aussah, aber Normalverhalten
ist — Metriken entstehen pro Service erst mit dem ersten Request (nach neun Testanfragen: 282).
Und der erste Router-Test lieferte sechsmal 000, weil -H "Host: …" keine SNI setzt und
Traefik den Handshake ohne SNI ablehnt. Mit --resolve antworteten alle sechs.
usv.home.robinwerner.net zeigte auf PowerPanel Business, Port 3052. Die Software ist vom
NUC vollständig verschwunden — kein Listener, keine Unit, keine Binaries, kein Paket; NUT hat
sie abgelöst. Der Router lieferte seitdem 502, und Traefik hielt ein Zertifikat für einen
Hostnamen aktuell, hinter dem nichts steht.
Nebenwirkung, die zu #200 gehört: Damit gibt es keine Weboberfläche für die USV mehr —
PowerPanel weg, NUT hat keine. Das macht den Vorschlag, die USV-Werte nach Prometheus zu
bringen, konkreter als zuvor.
bonus.local war doppelt tot: der Name löst nirgends auf, und beide Container hängen nur
im Netz internal, während Traefiks Docker-Provider auf proxy_network beschränkt ist. Nicht
tot war die Nebenwirkung — der websecure-Entrypoint forderte für jeden Router ein Zertifikat
an, und Let's Encrypt kann für .local grundsätzlich keines ausstellen. Traefik versuchte es
in Dauerschleife; gescheiterte Orders zählen auf das Rate-Limit des Kontos ein und können die
Ausstellung für Namen blockieren, auf die es ankommt. Die App selbst bleibt unberührt, sie
läuft über Host-Port 8888.
2026-09-06 (2) — E0a: Cloudflare-Tunnel stillgelegt¶
Issue: homeserver#195 — Schritte 1 und 2 erledigt, 3 und 4 offen
PR: homeserver#203 gemergt
Der Ingress ließ sich ohne Dashboard-Zugriff ermitteln: cloudflared protokolliert beim
Start die empfangene Config. Der Tunnel bediente ha.robinwerner.net (Home Assistant) und
vs.robinwerner.net (VS Code Server), dazu aktives WARP-Routing.
Genutzt wurde nichts davon. cloudflared_tunnel_total_requests stand über das gesamte
30-Tage-Fenster auf 0, bei sieben Wochen Container-Uptime ohne Restart — der Zähler wurde
also nie zurückgesetzt. Eine eingerichtete HA-Companion-App würde dauerhaft Traffic
erzeugen. Robin bestätigte, dass beide Hostnames entbehrlich sind und WARP nie eingerichtet
war.
Entfernt: der cloudflared-Stack, sein Prometheus-Scrape-Job und alle Referenzen in beiden
Repos inklusive LikeC4-Modell.
Sicherheitsrelevant¶
Das TUNNEL_TOKEN stand im Klartext in der getrackten docker-compose.yaml — nicht in
einer .env. Die Stack-docs.md führte das bereits als dringendes ToDo mit dem Vorschlag,
es zu rotieren. Die Stilllegung ist die bessere Antwort: den Tunnel zu löschen entwertet das
Token vollständig, statt ein Geheimnis zu rotieren, das gleich darauf wegfliegt. Damit
erledigt sich ein Posten aus #194. Die git-History behält den Token, deshalb muss der
Tunnel im Cloudflare-Dashboard gelöscht werden.
Nebenbefund zur externen Erreichbarkeit¶
Die Netzwerk-Doku behauptete, die externe Exposition liefe über den UDM-Port-Forward 443 auf
den Traefik des NUC. Das löst kein Router ein: alle 21 Traefik-Router lauten
*.home.robinwerner.net, von außen matcht kein Host-Header. cloudflare-ddns pflegt genau
zwei Records auf die Heim-IP, ddns.robinwerner.net und udm.robinwerner.net — keiner
davon ein Router-Name. Der Forward ist damit Angriffsfläche ohne Funktion.
Ob er überhaupt noch aktiv ist, ließ sich nicht klären: die UDM-Konfiguration ist von hier
nicht lesbar, und ein Test aus dem LAN ist wegen Hairpin-NAT wertlos. Bleibt als Schritt 3
in #195 offen.
Beim TS3-Stack fiel zusätzlich auf, dass 10011 (ServerQuery, Admin-Schnittstelle) neben
den für Freunde nötigen Ports 9987/udp und 30033 steht, und dass dort ein DB-Passwort im
Klartext in der getrackten Compose-Datei liegt — an #194 gemeldet.
2026-09-06 (1) — E0a: USV-Kette, Alarmregeln, Config-Mounts¶
Issues: homeserver#197 geschlossen · #199 zum Großteil erledigt · #200 neu
PRs: homeserver#201, #202 — beide gemergt und auf dem NUC deployt
NUT-Restart-Loop (#197)¶
Der nut-upsd-Container hatte 606 Restarts mit sauberem SIGTERM. Die Vermutung war
Fremdeinwirken — Watchtower-Reste, ein Cron, eine NUT-Doppelinstanz. Tatsächlich stammt der
Kill aus dem Image selbst: dessen Healthcheck führt bei „stale" ein
killall -TERM upsmon aus, und upsmon ist im Container PID 1. Der Kill beendet damit
den ganzen Container, restart: unless-stopped bringt ihn 0,3 s später mit frischer
USB-Verbindung zurück. Das ist die Selbstheilung des Images; der Restart-Zähler ist in
Wahrheit ein Zähler für Stale-Ereignisse (3–5/Tag, je rund 20 s blinde USV-Überwachung).
Einzeln ausgeschlossen: Watchtower (existiert nicht), Cron/systemd-Timer, NUT-Doppelinstanz
(nut-server ist not-found), OOM, USB-Reset auf Kernel-Ebene (7 Tage keine Meldung in
journalctl -k), USB-Autosuspend, wirkungslose ups.conf.
Warum der Treiber verstummt, war aus den vorhandenen Daten nicht bestimmbar —
usbhid-ups loggt beim Standard-Level nichts. Statt zu raten wurde debug_min = 1 gesetzt.
Drei tote Guardrails¶
Der eigentliche Fund der Session: alle drei sahen dauerhaft grün aus.
| Guardrail | Defekt |
|---|---|
ContainerRestarting |
lief auf container_restart_count — cAdvisor exportiert die Metrik nicht. Konnte nie feuern; deshalb blieben 606 Restarts vier Monate unbemerkt |
ContainerDown |
absent() über einen Regex-Selektor feuert nur, wenn alle Container gleichzeitig fehlen — ein einzelner toter Dienst blieb still |
prometheus.yml |
als Einzeldatei gemountet: der Container lief vier Wochen mit einer Config von vor dem Merge, der aiserver wurde nie gescrapt |
Regel 1 entfernt (kein Ersatz möglich — container_start_time_seconds bewegt sich bei
Docker-Restarts nicht), Regel 2 in Gleichheits-Matcher aufgeteilt und um nut-upsd
erweitert, beides live gegen Prometheus gegengeprüft.
Config-Mounts (#199)¶
prometheus, alertmanager und alloy mounten jetzt ihr Config-Verzeichnis statt der
Einzeldatei. Ein Einzeldatei-Bind-Mount hängt am Inode: git pull ersetzt die Datei, der
Container behält die alte, und nichts meldet einen Fehler. Der Reload-Pfad ist geklärt (alle
drei haben /-/reload), --force-recreate braucht es künftig nur bei Änderungen am Service
selbst.
Ein vollständiger Audit fand 8 Einzeldatei-Mounts. Drei behoben; fünf offen (traefik
2×, pihole, nut, influxdb) — die brauchen je ein config/-Unterverzeichnis, weil dort
die Config neben .env und Zustandsdateien liegt.
Nebenbei repariert¶
Die ONBATT- und LOWBATT-Meldungen der Shutdown-Kette trugen einen Gedankenstrich im
Titel. Der geht als Title:-HTTP-Header raus, HTTP-Header sind latin-1 — ntfy stellt
alles jenseits ASCII verstümmelt dar, ohne einen Fehler zu melden. Ausgerechnet die beiden
Meldungen, die bei Stromausfall zählen.
Offen daraus¶
- #200: Wurzel der Stale-Ereignisse. In 1–2 Tagen
docker logs nut-upsdum ein „Signal 15"-Ereignis auswerten; bleibt es stumm,debug_min = 2 - #200: echte USV-Metriken (Status, Ladung, Restlaufzeit, Staleness) — heute ist nur bekannt, ob der Container existiert. Vorschlag: Textfile-Collector wie auf dem aiserver
- #199: die fünf verbleibenden Einzeldatei-Mounts
2026-09-05 — E0a: aiserver-Pakete und externe Alarmkette¶
Erledigt: aiserver#5, #6 · homelab-external#20, #21, #27 · #22 geparkt
Neu entdeckt: homeserver#199
aiserver#5— borgmatic-Fehlalarme seit dem 14.08. Drei Ursachen: Textfile-Rechte, ein systemd-Drop-in ohne[Timer]-Kopf (wird zeilenweise still verworfen) und ein ntfy-Titel mit Geviertstrich, der einenUnicodeEncodeErrorwarf — die Anfrage ging wortlos nie raus. Doku-PR#7aiserver#6— Open WebUI: Signup war real längst zu;ENABLE_SIGNUPist eine ConfigVar, die DB schlägt die ENV. Ein Konto (Robin/admin),.envnachgezogen. Doku-PR#8. Damit ist der aiserver E0a-freihomelab-external#20— Uptime-Kuma-Backup-Monitore standen auf einem 60-s-Fenster statt auf täglichem Rhythmus; Monitor „Backup Local" hatte gar keine Benachrichtigunghomelab-external#21— Healthchecks-SMTP-Username im falschen Format; die alte Adresse gab535, Healthchecks konnte also nie mailenhomelab-external#23— Alarmkette positiv bewiesen: injizierter Critical-Alarm 20:07, iPhone holt die Nachricht 2 s später ab. Bleibt bis 12.09. offen (7-Tage-Fenster), dann Auswertung und E0a-Abnahmehomelab-external#22geparkt — Port-Mapping 3478 korrigiert (PR#30), STUN antwortet trotzdem nicht. Nächster Schritt:headscale log.level=tracebzw.tailscale netcheckhomelab-external#27— verwaister Deploy-Key gelöscht (1× am 30.01. benutzt, danach nie)
2026-08-26 — E0a: Forward-Auth für die externen Werkzeuge¶
Erledigt: homelab-external#19 · Neu entdeckt: #25, #26, #27
PRs #24 und #18 deployt: Forward-Auth inklusive lldap live, IR-Check negativ,
Admin-Passwort rotiert, Doku-PR #28.
Drei Funde nebenbei aufs Board: der Auto-Deploy-Cron existiert auf dem VPS gar nicht (Renovate-Merges erreichten den Server also nie), das Traefik-Access-Log war ungebremst auf 415 MB gewachsen, und ein verwaister Deploy-Key lag herum.
2026-08-23/24 — Planungssession und Roadmap v3¶
Bestandsaufnahme über 6 Repos und 3 Server in 12 Frage-Runden, kritischer Seitenreview durch zwei unabhängige Review-Agents plus Web-Faktencheck, danach Robins Gegenlesen-Runde. Ergebnis: Roadmap v3, freigegeben am 24.08. mit der Zielmarke 31.12.2026.
Board und Issues angelegt: Epics als homeserver#176–#193, granulare Pakete je Repo.
homelab-external wurde auf privat gestellt. Die Planungsdokumente kamen als PR #35 in
diese Doku-Drehscheibe, die damit die kanonische Quelle ist.
Details in Bestandsaufnahme, Zielbild & Roadmap und Seitenreview.