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-10-28 bis 11-02 — E1-Rest: Design, Plan, erste Umsetzung¶
PRs: homelab-documentation#85, #86 · homeserver#290, #292, #293 (gemergt), #291 (offen, vor Task 2 mergen) · neu: homeserver#283–#289 (T1–T7) · Log: E1-Umsetzungs-Log
- Denk-Session mit Abgleich am System, drei Exa-Deep-Research-Läufen (Werkzeuge, UNAS Pro, Storage Box) und einer Skizze im Browser. Design und Plan hat Robin freigegeben.
- Kern:
- Borg bleibt.
- Das USB-RAID fällt weg, die UNAS wird lokales Ziel mit Snapshots.
- Auf der Storage Box bekommt jeder Host einen Sub-Account, dazu tägliche Snapshots.
- Der VPS bekommt Hetzner-Backup und Borg.
- Morgenstatus per ntfy, drei Restore-Rezepte, die einmal geprobt werden.
- Störungs-Handbuch für Lars und Thies (nachsehen und neu starten, kein Wiederaufbau).
- Eine dritte Kopie wird erst bei der iCloud-Kündigung 2027 entschieden, BX21 kommt bei 80 % Füllstand.
- Umgesetzt: Task 0 (Issues, Ausgangslage) und Task 1 (Configs ins Git, Robin 02.11.). Den Code für den Dump-Alarm und den Morgenstatus habe ich vorgezogen.
- Funde:
- Die Box beantwortet
borg serveohneremote_pathmit borg 1.2. - Die Box hat 1 TiB, nicht 931 GiB (Korrektur an D1).
- Der UNAS-Export
thiesgehört zu Robins Laptop. - Die HA-Backups werden vom Exclude-Muster verschluckt.
- Panne: Eine parallele Session hat im selben Checkout den Branch gewechselt, ein Commit landete kurz falsch. Ohne Schaden behoben. Für parallele Sessions gilt ab jetzt: eigener Worktree.
- Offen: Morgen-Check 03.11., dann Task 2–3 (Robin), Task 4 mit der Bestandsaufnahme in UniFi Drive.
2026-10-27/28 — D1 entschieden: Ziel-Topologie, ADR-012 „Rolle von Kubernetes“¶
PR: homelab-documentation#83 (ersetzt #82) · Ergebnis: Design Ziel-Topologie · ADR-012 · Recherchen und Prüfbericht unter planung/2026-10-d1/recherche/ · neu: homeserver#278, homelab-external#34 · geschlossen: homeserver#277 · Kommentar: homeserver#181 (upsmon-Passwort rotieren)
- Ablauf: Bestandsaufnahme am Live-System (NUC, aiserver, VPS, VictoriaMetrics 30/90 Tage), Dienste-Inventar mit Nutzungsbelegen, vier Exa-Recherchen, sieben Entscheidungsrunden mit Robin. Danach prüfte Fable den Stand (drei Blocker, zwölf wichtige Funde), es folgte ein Doppelcheck mit Robin und eine abschließende Lese-Prüfung des Designs.
- Kernentscheidungen:
- Kubernetes kommt hybrid und gestuft, das Haus darf vom Cluster nicht abhängen.
- Der VPS bleibt in abgespeckter Form, Identity bleibt dort.
- HA und Immich laufen von außen über mTLS mit Offline-CA, der VPS wird die einzige Tür.
- Zweiter DNS auf dem Pi 4 vor E9.
- NUC-Neuinstallation mit 26.04 im Feb/März 2027.
- iCloud-Kündigung → 2027.
- Medien bekommen kein Offsite-Backup, die Archivordner auf der UNAS schon.
- Funde nebenbei:
- Das Standort-Tracking von HA funktioniert unterwegs nicht: Beide standen 8 Tage auf „home“.
- Docker auf dem NUC startet ohne UNAS oder Internet nicht (
RequiresMountsForinkl. rclone-Storagebox), dann fallen DNS und HA aus (#278). - Die USV ist eine OR650ERM1U mit 360 W, nicht die PR1500 aus der Doku. Der aiserver passt nicht mehr dazu.
- pz-server belegt 7–8,7 GiB bei 5 Spielstunden im Monat.
- TeamSpeak hatte seit 13.09. keine Verbindung.
- Der Voice-Stack auf dem NUC wurde nie genutzt.
- 17 untracked
.envliegen auf der System-NVMe. - Tailworm: headscale-DoS ohne Login ist offen, die Tailscale-Clients sind veraltet (#34).
- Flex-IO-Modul
L83414-012hat Stepping B3, damit ist #277 geschlossen. - Panne: Beim Auslesen der USV-Konfiguration wurde das upsmon-Passwort ungefiltert ins Session-Protokoll ausgegeben. Die Rotation steht als Punkt an #181 (Risiko gering, nur LAN).
- Korrigiert durch den Prüfbericht: USV-Modell. Storage-Box-Wachstum ~15–20 statt 35 GB/Monat. NUC-Auslöser auf PSI statt Swap.
- VPS-Preis geklärt: 3,99 € im Monat laut Rechnung (Robin).
- Offen: Umsetzung über die Epics (Übergabetabelle im Design). Rotation des upsmon-Passworts mit E2a. Hetzner-AGB zu Tor vor dem Umzug von snowflake.
2026-10-27 — Agenten-Fabrik abgegrenzt, Kubernetes-Cluster eingeordnet¶
PRs: homelab-documentation#81 (Nachtrag) · Kommentare: homeserver#216, #181, #182, #180 · neu: homeserver#277 (Cluster-Hardware-Check)
- Agenten-Fabrik: nicht Teil der Roadmap 2026 (Entscheid aus der Fabrik-Session). Nur Weichen in D1, E9 und E3, Start frühestens Januar 2027 auf einem Talos-Cluster, Pilot sharetab.
- Beifunde aus dem Infra-Abgleich an die Epics gehängt: aiserver-Borg-Key nicht append-only (E1), Infisical Free mit 5 Identitäten inkl. Menschen und ohne Versionierung sowie Robins gh-Token auf dem aiserver (E2a), 3.000 Actions-Minuten mit Sperre danach (E2b), NUC-Swap voll (D1).
- E1-Rest Teil 2 nimmt zusätzlich den gh-Token auf dem aiserver und
aiserver#9(Autostart nach Stromausfall) mit. -
Cluster: Die drei HP-G6-Minis sind schon da. Hardware-Check in KW44, Aufbau im Januar 2027 nach E9 (KW53 als Option, Entscheid am Checkpoint). Welche Rolle Kubernetes langfristig spielt, ist offen und wird in D1 per ADR mit Daten entschieden.
-
Hardware-Check erledigt (#277): 3 × ProDesk 600 G6 Mini, i5-10600T, 1 × 16 GB (bis 64 GB möglich), NVMe gesund, AMT aktiv, Switch vorhanden; die Geräte bleiben. Das Flex-IO-Modul 2,5 GbE fehlt noch. Node 2 ist im BIOS anders eingestellt (OS Recovery an, DMA-Schutz schwächer), beim Aufbau vereinheitlichen und AMT zurücksetzen.
- Reihenfolge: D1 kommt vor E1-Rest (Robin: viele Entscheidungen festzuhalten, beim Backup kommt es auf Tage nicht an).
Offen: D1 mit ADR „Rolle von Kubernetes“ (nächste Session), Flex-IO-Modul beschaffen, Horizont Q1 2027 am Checkpoint 22.11.
2026-10-24 (2) — Neuplanung: Abfolge v4¶
PRs: homelab-documentation (dieser PR) · Issues zu: homeserver#238 · Kommentar: homeserver#188 (iCloud-Export)
- Re-Schätzung nachgeholt: Seit 24.08. sind fünf Epics durch, offen sind zwölf, rund neun Wochen bleiben. Robin hält an der Zielmarke 31.12. fest und plant mehr Tempo ein; die Abfolge ist neu geordnet (Änderung 24.10.).
- Neue Abfolge: E1-Rest + D1 → E6 (direkt nach E1, Defekte zuerst) mit E5-Start → E2a–d im Urlaub (KW47–48) → E3-Kern → E8 → E9 (Umbau 19./20.12.) → E10 → iCloud kündigen. E5 läuft ab KW46 als Wartephase nebenher.
- Regel: Jedes Epic beginnt mit Bestandsaufnahme und kurzem Design, daraus werden die Issues geschnitten.
- Checkpoint So 22.11., danach gilt das neue Ventil: Headscale-OIDC → E2b-Sweep → E10 und iCloud-Kündigung (E9 bleibt, Robins Entscheid).
- iCloud-Export wird schon diese Woche angestoßen (Robin + Lars), Anleitung in #188. Advanced Data Protection muss dafür aus sein; Import später mit
immich-go upload from-icloud.
Offen: iCloud-Export beider Konten anstoßen (Robin, Lars); nächste Session E1-Rest Teil 1.
2026-10-24 — E6a abgenommen, alte Heizung abgebaut¶
PRs: homelab-documentation#78, Abnahme-Log (dieser PR) · Kommentare: homeserver#185 (Abnahme), #262 (Energie-Meldungen) · neu: homeserver#276
- Abnahme (#185): Robin hat nach zwei Wochen Heizbetrieb abgenommen. Seit 14.10. gab es zwei Störungen (Arbeitszimmer-Ventil, Notbetrieb Esszimmer, beide 16./17.10., beide gemeldet), seit dem Z2M-Update am 17.10. keinen einzigen Fehler mehr. Rückmeldungen umgesetzt: Bad und Arbeitszimmer Komfort 21,5 °C, Schlafzimmer 21 °C, Wohnzimmer abends bis zur Nachtruhe 20 °C. Bad-Ventil verliert 1,2 % Batterie pro Tag, Sendeschwelle dort auf 0,3 K.
- iPad nachts hell: Das stündliche Auffrischen hob die Hintergrundbeleuchtung im Bildschirmschoner an (nachts 50 %, tags 70 % statt 25 %). Behoben.
- Abbau (Task 14): Better Thermostat lief noch mit an den Wohnzimmer-Ventilen und ist jetzt weg. Ebenfalls entfernt: die 14 AHC- und Reset-Automationen, der Blueprint, 51 alte Helfer, das Dashboard „Heizung“, simple-thermostat, die HomeKit-Bridges „Button“ und „Climate“ (Heizung nicht mehr in Apple Home),
packages/heating.yaml,schedule.yamlund die veraltetehomekit.yamlim Config-Hauptverzeichnis. Vorher Backup, danach HA-Neustart ohne Fehlerverweise. Details im E6a-Umsetzungs-Log. - Abstellraum heizte nicht nach (21./22.10.): Sollwert kam an, Raum blieb kalt, Ventilalarm stand an, keine Meldung. Vier Ventile kalibriert, neuer Wächter „Raum heizt nicht nach“ (Raum > 1 K unter Soll und kein Anstieg). Die Abschlusstests entfallen (Robin).
- Nebenfunde: Die Energie-Meldungen gehen an einen nicht mehr vorhandenen Benachrichtigungsdienst (#262). Hue-Dimmer Schlafzimmer offline, seine Automationen nutzen alte Geräte-Auslöser (#276).
Offen: Abstellraum-Heizkörper prüfen (Robin), Betriebsdoku (Task 15), Lars später noch einmal fragen.
2026-10-17 — Zigbee/Z2M (E6a), mqdockerup entfernt, VictoriaMetrics/-Logs wieder an¶
PRs: homeserver#268, #269 · homelab-documentation#75, #76 · Issues zu: homeserver#266, #267 · neu: #270
- Zigbee (#267): Z2M 2.12.0 → 2.14.2, Netzkarte ausgewertet, fünf Geräte entfernt (drei namenlose, beide Wassersensoren), Altlasten im Broker gelöscht. Details im E6a-Umsetzungs-Log. Dabei auch der iPad-Akku-Schutz repariert, der nachts nie griff.
- mqdockerup entfernt (#266, #269): Über 11 000 Neustarts seit 09.10. Auslöser war nicht
speedtest-tracker-db(trotzdem neu erstellt, Postgres 18.4 ohne Tag → 18.6), sondernha-mcpmit Digest-Pin; v1.23.8 bricht daran den ganzen Lauf ab. Die 178 HA-Entitäten waren nirgends verwendet, Robin brauchte die Anzeige nicht. Container, Image, 1429 retained Topics, drei leere HA-Geräte und das Datenverzeichnis (archiviert) sind weg. - Nebenfund (#270): VictoriaMetrics und VictoriaLogs lagen seit 15.10. 15:52 still. Ursache: NFS
stale file handleauf/mnt/monitoring, Docker scheiterte beim Neustart am Mount und gab auf. Kein Alarm in zwei Tagen. Mount war wieder gesund, beide gestartet, Daten kommen an. Lücke im Langzeitspeicher: 15.10. 15:52 – 17.10. 19:34.
Offen: Infisical-Secrets von mqdockerup löschen und GitHub-PAT widerrufen (Robin); #270 (Ursache an der UNAS, Wiederanlauf, Alarm, gehört zu #217); E6a-Abnahme ab 24.10.
2026-09-29 und 10-10 — E7b: Paperless-Taxonomie, Duplikate, gemeinsame Dokumente¶
PRs: homeserver#254, #264 · homelab-documentation#64, #70 · Stand und To-dos im Epic homeserver#253
Review der ersten 46 Dokumente. Claude liest dabei nur Metadaten (Titel, Typ, Pfad, Korrespondent, Tags), nie den
OCR-Text; das ist Robins Freigabe für pikante Dokumente. 27 von 46 lagen unter „Schreiben". Sieben neue Typen
(Bescheinigung, Vertragsänderung, Abrechnung, Angebot/Kostenvoranschlag, Mahnung, Auskunft, Zugangsdaten),
Kündigung und Bescheid umbenannt und 24 Dokumente per Bulk-Edit umgehängt. Danach waren es noch 8 unter „Schreiben".
Steuer ist jetzt ein Tag je Steuerjahr (Steuer 2025), weil die Belege erst im Folgejahr kommen und ein
Datumsfilter das Steuerjahr nicht trifft. Die gespeicherte Ansicht ist entsprechend umgestellt.
Nebenbei geklärt:
- Duplikate: Paperless erkennt nur bytegleiche Dateien (
CONSUMER_DELETE_DUPLICATES=true) und doppelte ASNs. Robin lädt viele digitale Versionen schon gescannter Papiere hoch. Die Paare findet ein Review nach dem großen Scan-Stapel über die Metadaten. Offen ist, welche Version bleibt (Empfehlung: digital, ASN umziehen). - Gemeinsame Dokumente (Robin + Lars): Sie bekommen keinen Eigentümer und landen unter
gemeinsam/…. Bis Lars' Konto da ist, markiert der TagGemeinsam(vormalsFamilie) sie. Es gibt einen ASN-Kreis für den Haushalt. - Fund: Alle Typen, Tags, Pfade und Korrespondenten gehören
robin. Lars würde sie mit eigenem Konto nicht sehen, deshalb steht ihre Freigabe auf der To-do-Liste in#253.
Offen: Speicherpfad Homelab (Hetzner, Strauss), „Recht & Behörden", PIN-Briefe, SWK-Dublette. Nächste Runde nach Robins großem Scan-Stapel.
2026-10-10 — E6a: Heizbeginn, Wand-iPad, ha-mcp mit YAML und Screenshots¶
Erledigt: homeserver#261 (ha-mcp YAML-Bearbeitung und Dashboard-Screenshots), E6a Task 12 (Wand-iPad), Task 13 (Sommer-Ventilbewegung), Task 8/9 umgeschaltet · Neu: homeserver#262 (Energie-Dashboard), homeserver#263 (Shellys absichern)
Die Zentralheizung läuft: Alle 7 Räume regeln seit 23:19 über VTherm nach dem neuen Zeitplan, alle auf einmal statt gestaffelt (Robins Entscheidung, sonst hätte niemand die übrigen Räume geregelt). Die Abnahmephase läuft bis 24.10.
ha-mcp kann jetzt YAML bearbeiten (feste Regel: Diff im Chat, Bestätigung nur nach Robins Go) und Dashboards selbst fotografieren. Der Test der Ventilbewegung zeigte, dass die Aqara E1 beim Wechsel aus→heizen ihren gespeicherten Sollwert zurückholt; die Automation schickt jetzt erst den Modus, dann den Sollwert und prüft nach. Das Wand-iPad Esszimmer hängt an einer Shelly mit Akku-Schutz 20–80 % und folgt mit Display und einem neuen zentralen input_boolean.nachtmodus dem Heizungs-Zeitplan; das Esszimmer-Bewegungslicht geht nachts stark gedimmt an.
Nebenbei: Schlafzimmer mit eigenen Komfortzeiten, Küchenventilator am Heizplan, Dashboard-Nacharbeit per Subagent. Details im E6a-Umsetzungs-Log.
2026-10-08 bis 10-09 — E6a umgesetzt bis auf die Heizperiode¶
Issues: homeserver#185 (E6a, Task 0–7 und 13 erledigt, 8–11 vorbereitet), #237 (Sensor-Timeouts gesetzt),
#189 (Dashboard-Backlog ergänzt) · Feature-Request KipK/a-better-history-card#8
PRs: homeserver#260 (ha-mcp 7.12.1 → 8.6.0, gemergt und ausgerollt) · homelab-documentation#67
(Umsetzungs-Log E6a) · PR für ha-mcp YAML-Bearbeitung und Dashboard-Screenshots in Arbeit
Details je Task stehen im Umsetzungs-Log E6a. Kurzfassung:
- Steuerung steht, heizt aber noch nicht. Die Zentralheizung läuft noch nicht, und die Heizkörper müssen entlüftet werden. Alle 7 VTherm-Raumregler sind angelegt, ausgeschaltet, der zentrale Modus steht auf Frostschutz. Zum Heizbeginn: Modus auf Auto und die Räume umschalten.
- Getestet: Einspeisung mit Notbetrieb, Zustellprüfung mit Reparatur, Tagesprofil, Übersteuerung A/B/C, Neustart-Schutz, T1 (die E1 übernimmt Werte mit zwei Nachkommastellen) und T2 (über 5 h ohne Einspeisung kein Rückfall auf den internen Fühler). Die Tests, für die Robin vor Ort sein muss, sind fürs Ende gesammelt.
- Fable als Advisor hat in zwei Reviews mehrere echte Fehler im Plan gefunden. Ohne Fix wäre der Notbetrieb-Push alle 30 min gekommen, die 0,2-K-Schwelle hätte nie gegriffen (120–290 Funkbefehle pro TRV und Tag), und die Zustellprüfung hätte nicht bereite Räume abgeschaltet. Dazu kamen falsche Luftfeuchte-Gruppen (Esszimmer zeigte das Schlafzimmer).
- ha-mcp 8.6.0 richtet Integrationen selbst ein (
ha_set_integration). Damit hat Claude 6 VTherm-Instanzen ohne Oberfläche angelegt. - HomeKit: Die Climate-Bridge nahm alle
climate-Entitäten auf, also auch die neuen Raumregler doppelt neben den TRVs. Jetzt sind es nur die 7 Raumregler (packages/homekit.yaml, die erste Dateiänderung unter/configmit Robins Freigabe). - Dashboard v2
/smart-home-zentrale-v2: nach Fable-Recherche (Exa) von Opus gebaut, mit Raumfarben, Zeitplan-Ansicht und kombiniertem Verlauf. Robin: „gefällt mir echt gut“. - Funde nebenbei: Die Arbeitsmaschine läuft auf UTC, die T2-Auswertung kam deshalb 2,5 h zu spät. Die E1 hat
keinen eingebauten Verkalkungsschutz, deshalb gibt es eine Automation. In der YAML-Konfiguration hängt kein
card-mod-Ladeeintrag (Risiko für das Update auf 2026.9 entschärft). Im NUC-Klon liegt ein verwaister lokaler
Branch
origin/main(ignoriert).
Offen: Heizbeginn (zentraler Modus auf Auto, Räume umschalten), Test der Ventilbewegung tagsüber, Wach-Erkennung am Samstag, iPad (Task 12), Abschlusstests mit Robin, zwei Wochen Abnahme, Abbau (Task 14).
2026-09-29 bis 10-08 — Board aufgeräumt, Backup belastbarer, E6a Heizung geplant¶
Issues: homeserver#216 (E1, Runbook 1 + 2 durch), #210 und #240/#241 geschlossen, #227 not planned,
#190 (E7) geschlossen · #185 (E6a), #237, #238 · neu: #256 (HomeKit träge), #257 (card-mod bricht ab
HA 2026.8/9), #258 (HA aufräumen)
PRs: homelab-documentation#65 (Spec E6a, gemergt), #66 (Umsetzungsplan E6a) · Renovate-PR homeserver#246
(HA → 2026.9) mit Sperrkommentar
E1 Backup (#216)¶
- Ist-Aufnahme widerlegte drei Annahmen: Die Fotos sind gesichert (
/mnt/picturesaktiv in beiden Configs), das Hetzner-Repo läuft direkt per SSH (kein rclone-fuse), und es gab keinen Compaction-Rückstand. Die 346 GB aus #210 waren SABnzbd-Downloads im Tagesarchiv vom 05.09., nach sieben Tagen korrekt freigegeben. - Runbook 1 (02.10., Robin): Dump-Skript auf Repo-Stand,
teamspeak_dbneu erstellt (Dump scheiterte seit der Passwort-Rotation am 13.09., der Container hatte das alte Passwort in der Umgebung), SABnzbd-Downloads ausgeschlossen (per Probedatei belegt), alte Config-Kopien mit Klartext-Passphrasen gelöscht. - Runbook 2 (05.10., Robin): Beide Borg-Passphrasen des NUC rotiert. borgmatic liest sie jetzt aus
credentials/*.pass(root 0600), Infisical ist die Quelle der Wahrheit, Notfallkopie in Proton Pass. Kniff:borgmatic key change-passphrasewirft die Config-Passphrase bewusst heraus, die alte kam deshalb perBORG_PASSCOMMAND, die neue perBORG_NEW_PASSPHRASE. Vorher im Wegwerf-Container geprobt. - Erster echter btrfs-Scrub (01.10.): 641 GiB, keine Fehler.
E6a Heizung (#185)¶
- Ist-Aufnahme: Letzten Winter liefen die TRVs wochenlang auf leeren NiMH-Akkus (jetzt Alkaline). Der Küchen-TRV
ist instabil, der Tuya-Stecker
kaffeehinter der Kaffeemaschine der schwächste Router. In der Heizlogik fanden sich Konfigurationsfehler. - Zigbee-Session: Netzwerkkarte mit Parent je Gerät. Die Latenzmessung (get-Rundlauf Median 52 ms, HA-Anteil echter HomeKit-Befehle Median 59 ms) zeigte: Die Trägheit liegt vor HA, auf der Apple-Seite (#256).
- Überwachung gebaut (per ha-mcp): Label „Überwacht“ (32 Geräte), Batterie- und Offline-Alarm, Dashboard „Gerätezustand“. TRV-Availability-Timeout 150 min nach 24-h-Messung (Herzschlag ≤ 55 min).
- ha-mcp war nur im Projekt
~/repos/homeservereingetragen und ist jetzt auch für~/reposverfügbar.claude_design-MCP global. - Planung: Versatile Thermostat ersetzt AHC und Better Thermostat. Eigene Bausteine für E1-Einspeisung (Z2M
verwirft den Wert sonst still), Zustellprüfung (kein Fundament prüft den Sollwert) und Tagesprofil. Wand-iPad als
Smart-Home-Zentrale nach Claude-Design-Mockup. Spec und Plan liegen unter
planung/2026-10-e6a/. - Beim Planen gefunden: AHC 5.5.7 schaltet bei offenem Fenster nicht ab (#161) und überschreibt Komfortwerte,
wenn ein TRV zurückkommt (#171). card-mod bricht ab HA 2026.8/9 still, die Minimalist-Themes sind betroffen (#257).
Ein Jinja-
split(':')[1]aufunknownhätte das Tagesprofil beim HA-Neustart abstürzen lassen.
Nächster Schritt: E6a Task 0 (Backup, VTherm installieren) laut Umsetzungsplan.
2026-09-22 bis 27 — E7 Inbetriebnahme: Paperless läuft, die KI ordnet ein, das Modell ist gewählt¶
Issues: homeserver#190 (Epic E7) — Phasen 0 bis 11 des Runbooks durch, Phase 12 zur Hälfte
PRs: homeserver#247 (Ausgabesprache de-de) · #248 (Lastbericht) · #249, #250, #251 (Modell-Duell) ·
#252 (Modell paperless-llamacpp) · aiserver#18 (Embedding-Batch) · aiserver#29 (Rolle paperless-llamacpp) ·
homelab-documentation#62 (gemergt), #63 (offen, alle Runbook-Korrekturen dieser Woche)
Was Robin aufgebaut hat (nach Runbook, Claude prüfte nur Ausgaben)¶
Infisical-Werte, Systembenutzer paperless (977/988), Samba auf SMB2 ohne Gastzugang mit Include-Freigabe, alter
Stack ersetzt, Superuser, Seed (30 Objekte, zweiter Lauf 0), Workflows, Ansichten, Quick Set am HP 4302, Proton
Bridge angemeldet, Mail-Regel, Timer, Alarmregeln, Kuma, Nachtlauf-Nachweis. Rund 20 Dokumente von Hand
eingeordnet; danach ordnete die KI neue Scans direkt richtig ein und benannte sie.
Was erst im Betrieb sichtbar wurde¶
- Die v3-KI kennt die Taxonomie nicht. Sie sieht nur Kandidaten aus ähnlichen Dokumenten im Embedding-Index,
nie die Liste der Speicherpfade. Am ersten Tag lag alles in
_Unsortiert. Bootstrap von Hand ist Pflicht, danach greift es (Quelltext 3.1.3taxonomy_block.j2). - Englische Titel. Übersetzung nur bei gesetzter Ausgabesprache, die UI-Sprache griff nicht →
de-dein der Compose-Datei (#247). Kostet eine zweite Anfrage je Klassifikation. document_llmindex updatescheiterte an llama-server: Embeddings müssen in einen physischen Batch passen, Standard 512, Paperless schickt 1024 →--ubatch-size 2048(aiserver#18).- Mail: Port 1234 statt 1243 (Zahlendreher, per
MailAccount-Abfrage gefunden). Und die Regel-Aktion „Nichts" aus meinem Runbook existiert nicht; „Als gelesen" holt nur ungelesene Mails, „Markieren" kollidiert mit Robins Sternen → Verschieben nachLabels/Paperless-erledigt, bei Bridge ein Label-Tausch, die Mail bleibt im Posteingang. Alter 0 statt 30 Tage. - Drei KI-Aktionen = sechs Anfragen je Dokument, kein Cache im Workflow-Pfad. Mit brain rund drei Minuten je Dokument, weil brain vor jeder Werkzeug-Antwort denkt (13 KB Antwort, 90 % Denktext).
Das Modell-Duell (27.09., ai_duel.py, fünf handeingeordnete Dokumente)¶
| Modell | Korrespondent | Typ | Pfad | Median |
|---|---|---|---|---|
| brain (mit Denken) | 3/5 | 4/5 | 5/5 | 60 s |
| coder | 2/5 | 4/5 | 4/5 | 14 s |
| translator | 4/5 | 4/5 | 5/5 | 56 s |
| paperless (brain ohne Denken) | 3/5 | 4/5 | 4/5 | 9 s |
Erster Lauf war ungültig: /suggestions/ ist der klassische Klassifikator (#250). Der hatte aus den 20 Vorbildern
gelernt und traf Korrespondent und Typ 5/5 in null Sekunden. Entscheid: Rolle paperless-llamacpp in der
docs-Gruppe (aiserver#29), Paperless darauf umgestellt (#252). Ein 20er-Stapel kostet den aiserver jetzt rund 13
Minuten statt einer Stunde. Offen aus dem Duell: Alle Modelle nennen „SWK Bank", Robins Korrespondent heißt anders
(Dubletten-Risiko), und die Steuerbescheinigung trifft keinen Typ.
Nächste Schritte¶
Robin deployt #252 (up -d paperless_ngx) und startet auf eigenen Wunsch (28.09.) mit leerem Bestand neu
(Runbook „Neustart mit leerem Bestand": Papierkorb leeren wegen ASN). Die Abnahme 12.2 (zweiter 20er-Stapel) läuft
dann im Alltag ohne Begleitung; offene Punkte gesammelt im E7b-Epic. Doku-Seite, Issue-Kommentar und E7b-Issue
sind erledigt, #87 geschlossen (native KI statt Sidecar).
2026-09-20 — E7 Teil A/B: Stack und Host-Bausteine gebaut, zwei PRs warten auf Robin¶
Issues: homeserver#190 (Epic E7) — Repo-Arbeit erledigt, Deploy steht aus
PRs: homeserver#243 (Teil A, Stack) · homeserver#244 (Teil B, Host-Bausteine, Alarme, Seed) ·
homelab-documentation#59 (gemergt, Plan) · #60 (Plan- und Design-Korrekturen aus dem Abschlussreview)
Wie gebaut wurde¶
Subagent-getrieben nach dem freigegebenen Plan: je Branch ein Implementierer in einem eigenen Worktree, danach ein unabhängiger Task-Review, zum Schluss ein Abschlussreview über beide Branches auf dem stärksten Modell. Die Einzelreviews fanden nichts (die Implementierungen waren byte-identisch zum Plan). Das Abschlussreview fand vier Plan-Defekte, die die Implementierer wörtlich übernommen hatten:
- Die ASN kommt als Text. Paperless übergibt
asnin der Dateinamen-Vorlage als String, mein'%05d' | format(asn)hätte einenTypeErrorgeworfen. Paperless fängt den still, legt die Datei als0000042.pdfim Wurzelverzeichnis ab, und der Seed wäre am ersten Speicherpfad mit HTTP 400 gescheitert. Der Ordnerbaum, das zentrale Designziel, wäre nie entstanden, und die Abnahme hätte es nicht gesehen, weil sie nur die UI-Felder prüfte. Fix:asn | intan allen zehn Stellen, in der Sandbox verifiziert. Genau dieser Prüfpunkt stand in Spec 13, der Plan hatte ihn nicht ausgeführt. - Die Collector-Unit konnte den Consume-Ordner nicht lesen: leere
CapabilityBoundingSetplus0770-Baum, den root nicht traversieren darf. FixSupplementaryGroups=paperless. - Die Bridge-Anmeldung wäre am Single-Instance-Lock gescheitert, solange der Run-Container läuft. Runbook stoppt ihn jetzt vorher.
- Ein Samba-
includemitten in[global]hätte alle folgenden globalen Werte still in die Freigabe verschoben. Include als letzte Zeile,testparm | grep -i 'global parameter'als Gegenprobe.
Dazu drei Kleinigkeiten: PAPERLESS_TRUSTED_PROXIES ist ein Präfix (10.100.), kein CIDR; die Abnahme prüft jetzt
den Ordnerbaum auf der Platte; ein siebter Seed-Test deckt den 400-Pfad ab. Alles per Fix-Welle eingebaut, gezielt
nachgeprüft, Plan und Design in #60 nachgezogen.
Was daraus zu lernen ist¶
Ein Review über die Einzeltasks hinweg lohnt sich auch bei reiner Abschreibarbeit: Die Fehler lagen nicht in der Umsetzung, sondern im Plan, und nur ein Reviewer mit Zugriff auf den Paperless-Quelltext konnte sie sehen. Und: Abnahmekriterien müssen das Artefakt prüfen (die Datei auf der Platte), nicht nur die Anzeige.
Nächste Session¶
Robin reviewt und merged #243, #244 und #60. Danach Teil C (NUC-Cutover, Robins Hand mit Runbook), dann D bis F.
Die Worktrees homeserver-e7-stack und homeserver-e7-host bleiben bis zum Merge stehen.
2026-09-19 — E7-Planung: Paperless v3 ist entworfen, der Bauplan steht¶
Issues: homeserver#190 (Epic E7) — Design und Umsetzungsplan liegen vor, gebaut ist nichts (Robins
Vorgabe: nur planen) · #87 (Ollama-Sidecar) wird durch die native KI abgelöst · #242 (Dropbox) nach E7b
PRs: homelab-documentation#58 (gemergt, Design) ·
#59 (Umsetzungsplan)
Was entschieden wurde¶
- Native paperless-ngx-3.1-KI statt Sidecar. Vorschläge über
brain-llamacpp, Embeddings überembed-llamacpp(bleibt fest, ein Wechsel erzwingt den Index-Neuaufbau), beides peropenai-likegegen llama-swap auf dem aiserver. Auto-Tagging über die neue Workflow-Aktion „KI-Vorschläge anwenden". paperless-gpt nur für OCR und nur, wenn bei 20 Dokumenten mindestens zwei unbrauchbar erkannt werden. Modellwahl brain vs. granite per Mini-Turnier auf echten Dokumenten. - Ordner über Speicherpfade je Lebensbereich, oberste Ebene ist der Eigentümer (
robin/Versicherungen/HUK/2026/…),_Unsortiertals Auffang. Steuer ist ein Tag. Robin arbeitet ordnerbasiert, die Zuordnung passiert aber in Paperless, nie im Dateisystem. - ASN-Barcodes trennen den Stapel. Etikett auf Seite 1, ganzer Stapel in den Duplex-Einzug, Paperless macht daraus n Dokumente mit je ihrer Archivnummer. Doppelte ASN werden abgewiesen.
- Zulauf: HP 4302 (fdn/fdw) → neue Samba-Freigabe
[paperless]→consume/robin/, Farbe 300 dpi beidseitig./mnt/scannerbleibt unangetastet (überwiegend Lars' Dokumente). Mail über Proton Mail Bridge headless im Stack, Robin labelt „Paperless" von Hand oder per Proton-Filter, kein Ordnerzwang (Robin hat Unlimited). - Nutzerkreis: Zielbild getrennte Bestände für Robin und Lars; jetzt nur Robins Konto, Lars in E8 oder E7b.
- Speicherort bleibt (Dokumente UNAS, DB/Index/Consume SSD). Tika/Gotenberg bleiben (Mail als PDF).
- E7 enthält den nächtlichen
document_exporterund eine Consume-Stau-Überwachung. E7b: Dropbox-Spiegel, Restore-Probe (ehrlich: bis dahin Rückweg ungeprüft), Lars, SSO, Vision-OCR, Custom Fields.
Funde nebenbei¶
- Die Juni-Spec war überholt: paperless-ngx 3.1.3 (04.09.) kann KI-Vorschläge automatisch anwenden; im Juni ging
die Spec noch von „nur Vorschläge per Klick" aus. Und
docs/superpowers/ist imhomeserverper gitignore ausgeschlossen — die Specs dort waren nie versioniert. Die E7-Spec liegt deshalb hier im Doku-Repo. - Der aiserver war vorbereitet: llama-swap trägt seit den Juli-Turnieren eine
docs-Gruppe (german-ocr + granite-JSON) und den residenten Embedding-Sieger. - Authelia und lldap laufen seit elf Tagen gesund auf dem VPS, OIDC-Clients fehlen noch — SSO bleibt E8.
- Samba auf dem NUC:
server min protocol = NT1(SMB1) undmap to guest = Bad Password(falsches Passwort wird Gastzugang). Beides wird mit der neuen Freigabe korrigiert. - Proton-Bridge-Fakten: Labels erscheinen als IMAP-Ordner
Labels/<Name>, die Mail bleibt im Posteingang. Paperless' Aktion „Tag setzen" ist Gmail-only, aber Paperless merkt sich verarbeitete Mails per UID — Aktion „Nichts" reicht. Bridge braucht einen bezahlten Plan und synchronisiert den ganzen Postkasten lokal. - Bridge-Image:
ghcr.io/enucatl/protonmail-bridge:3.26.0— aus dem offiziellen Quelltext gebaut, Healthcheck, läuft als UID 1000, IMAP intern auf 1243, Init über Compose-Profil. Der bekanntereshenxn-Build hat weder Healthcheck noch Härtung. - UID 977 / GID 988 (Paperless im Container und auf dem UNAS-Export) haben auf dem NUC keinen Benutzer — der
Systembenutzer
paperlesskommt mit Teil C.
Nächste Session¶
Umsetzung Teil A des Plans (Repo: Stack-Neubau, PR-A), danach Teil B. Teil C ist Robins NUC-Cutover.
2026-09-18 — E0c: Der Steuerstand sichert sich selbst, und das Board sagt endlich die Wahrheit¶
Issues: homeserver#178 (Epic E0c) weitgehend abgearbeitet · homelab-documentation#45
geschlossen · #77, #79, #80 geschlossen (erledigt bzw. Duplikat) · #236, #237, #238
neu · 22 offene Issues nachträglich aufs Board
PRs: claude-code-setup#5 (gemergt) · homeserver#235 (offen) ·
homelab-documentation#54 (gemergt)
~/.claude ist versioniert — und sichert sich ab jetzt allein¶
Das Repo claude-code-setup trackte zehn Doku- und Skript-Dateien, letzter Push 07.07. Von der
Schaltzentrale selbst war nichts versioniert: 19 Skills, 5 Hooks, 2 Agents, 2 Rules,
CLAUDE.md, statusline.sh, settings.json und 189 Memory-Dateien in 21 Projekt-Slots lagen
ausschließlich auf dem Laptop, der in keinem Backup steht.
sync-claude-home.sh spiegelt jetzt eine Allowlist nach claude-home/ und committet das
Ergebnis auf main. Ausgelöst vom SessionEnd-Hook, serialisiert über flock, weil mehrere
Sessions gleichzeitig enden können. Gespiegelt werden Konfiguration und alle Memory-Slots
(Robins Entscheid: vollständig, das Repo ist privat); Credentials, Session-Transkripte,
history.jsonl, Plugins und Caches bleiben auf der Maschine.
Zwei Entscheidungen, die man sonst neu treffen müsste:
- Der Zielordner heißt
claude-home/, nicht.claude/. Claude Code lädt ein<repo>/.claude/settings.jsonals Projekt-Settings — die globalen Hooks würden beim Arbeiten im Setup-Repo ein zweites Mal greifen. history.jsonlbleibt draußen. Prompt-Historie, in der irgendwann ein Secret getippt sein kann; der Nutzen deckt das Risiko nicht.
Ein Secret-Gate prüft den Spiegel vor jedem Commit auf sieben Muster und bricht bei einem
Treffer ohne Commit ab — gegen einen Fake-ghp_-Token getestet, Exit 1. Und weil eine Sicherung,
die still ausfällt, keine ist (#217), meldet --check am SessionStart einen gescheiterten oder
über sieben Tage alten Lauf. Erster scharfer Lauf: 265 Dateien, 21 Memory-Slots, committet und
gepusht.
Was nur auf dem Laptop lag¶
homeserverstand auffeat/sharetab-backupmit einem ungepushten Commit (018d83b, verschlüsselter täglicher pg_dump der ShareTab-Datenbank) — jetzt gepusht, PR#235. Der danebenliegende Stash ist wertlos: GitNexus-Boilerplate mit bereits veralteten Symbol-Zahlen.novabrandshatte gar keinen Remote. Vorher auf IBAN, Steuer-ID und Ausweisdaten geprüft (nichts gefunden), dann als privates Repo angelegt und gepusht.homelab-planung-2026-08/war ein Nicht-Git-Ordner, dessen drei Dateien byte-identisch im Doku-Repo liegen. Gelöscht — zwei Quellen für dieselbe Roadmap sind der Grund, aus dem man später die falsche liest.
Das Board zeigte nicht, was offen ist¶
Das Ziel von E0c ist „ein Board, das offen anzeigt". Der Abgleich ergab: 22 von 47 offenen Issues standen nicht darauf — darunter ausgerechnet #216 (Backup-Konzept), #217 (Target-Down-Regel) und #225 (VPS-Ausfall legt die Alarmierung still), also die drei, die im Roadmap-Stand als offene Kernpunkte geführt werden.
Aufgeräumt: elf Alt-Issues aus Mai/Juni bekamen Epic-Präfixe (voice-stack #104–#108 → E6,
Traefik #118/#119 → E10, #74/#84 → E3, #83 → E2b, #87 → E7) und stehen jetzt auf dem Board.
Drei wurden geschlossen, jeweils gegen den Repo-Stand belegt statt aus der Erinnerung: #77
(kein cloudflared-Stack, kein TUNNEL_TOKEN mehr), #79 (Passwort liegt in ts3/.env und ist
rotiert), #80 (Duplikat zu #194). Draußen bleiben nur die drei Renovate-Dependency-Dashboards.
Seither: 61 Board-Items, 44 offene Issues, Deckung vollständig.
Das HA-Journal ist gesund — und hat trotzdem ein Problem¶
Der letzte Commit in ha-config war vom 3. Juli. Die Verifikation (Punkt aus #178) zeigt: Der
Cron-Job läuft (96 Läufe in 24 h), /opt/ha-config ist synchron mit origin, und alle vier
YAML-Dateien plus packages/ und dashboards/ sind SHA-256-identisch mit dem Live-Stand. Die
mtime von automations.yaml ist der 3. Juli, 15:41 — der Commit um 15:45 war korrekt der letzte.
Die Stille ist echt: Arbeit läuft über ha-mcp und die UI und landet in .storage, das bewusst
nicht gespiegelt wird.
Bleibt der Haken: ein totes Journal und ein stilles Journal sehen identisch aus. Es hat keinen
Herzschlag. Dafür ist #236 angelegt (Healthchecks-Ping am Ende jedes Laufs, nach dem
git push). Nebenbei: Der Skript-Header behauptet einen systemd-Timer, tatsächlich läuft es über
/etc/cron.d.
Doku: strict scharf, drei Leichen raus¶
homelab-documentation#45 erledigt. strict: true plus validation: — nachgewiesen mit einem
eingebauten toten Link: „Aborted with 1 warnings in strict mode!", Exit 1. nav.omitted_files
steht bewusst auf info, weil die beiden Templates und die sieben Planungsdokumente unter
superpowers/ nicht in die Navigation gehören.
Der im Issue vermutete doppelte Nav-Eintrag „Infisical Agent" war mehr als eine Dopplung: Die
beiden Seiten widersprachen sich. infisical-agent.md (29.06., von niemandem verlinkt,
ASCII-Umlaute) führte Paperless als angebunden — am Agent hängen aber nur ollama und
speedtest-tracker. Die brauchbaren Teile sind in infisical.md gewandert, die alte Seite ist weg.
Dabei fiel auf, dass die Nav weiter Portainer, Syncthing und Wallos als laufende Dienste
führte, zwei Tage nach ihrer Abschaltung in E0b. Ersetzt durch stillgelegt.md: je Dienst wann,
warum und wo die Daten liegen — dazu Nextcloud (NUC), proxy-manager und Cloudflare Tunnel, die
nie eine eigene Seite hatten. Der Nextcloud unter cloud.novabrands.org ist ein anderer Dienst
auf einem eigenständigen Server und bleibt.
Heizungs-Sensorik: 31 Geräte erfasst, eines davon seit 100 Tagen tot¶
Bestandsliste für E6a erhoben (#238): 8 Aqara TRV E1, 10 Fenster-/Türkontakte, 13 Temperatur-/Klimasensoren.
kueche_heizung ist seit rund 100 Tagen offline — belegt über zwei unabhängige Quellen:
lastSeen in der z2m-Datenbank und z2m's eigenes Availability-Topic
({"state":"offline"}). Vor der Heizsaison der erste Punkt.
Insgesamt sind sechs Geräte offline, das älteste seit 367 Tagen. Der ernsteste Fall ist
kueche_sensor_wasser_waschmaschine, seit 281 Tagen tot: Ein Wasserleck-Sensor, der nicht
sendet, meldet auch kein Leck.
Dass das so lange niemandem auffiel, hat einen Grund: Es gibt keine Überwachung auf
Zigbee-Geräte, die offline gehen — keine Alarmregel im monitoring-stack, keine HA-Automation
auf unavailable. Die Daten liegen längst vor (77 Geräte tragen einen Availability-Status im
MQTT), es fehlt nur der Empfänger. Dafür ist #237 angelegt.
Nachmittag: Der Backup-Umbau — und ein Fund, der alles andere überholt hat¶
Aus dem Vorschaltpaket (#240) wurden Punkt 1 bis 3 erledigt. Auf dem Weg dorthin fiel etwas auf, das schwerer wiegt als der ganze geplante Umbau.
Kein Archiv enthielt je eine Datenbank (#241)¶
/var/backups/db-dumps stand als Quelle in beiden borgmatic-Configs. Gleichzeitig steht in den
exclude_patterns ein '**/backups/*' — gedacht gegen „Backups von Backups". Das Muster trifft
den Dump-Pfad. Die Dumps wurden jede Nacht erzeugt und beim Archivieren wieder weggeworfen.
Nachgewiesen am echten Archiv: --find db-dumps lieferte in beiden Repositories null Treffer,
die Gegenprobe mit traefik dagegen Treffer. Ein Trockenlauf mit Positivkontrolle bestätigte,
dass das Muster greift.
Am härtesten traf es MariaDB (TeamSpeak, Uptime Kuma): dort sind auch die Live-Dateien per
ibdata*/ib_logfile* ausgeschlossen — es lag also nichts Wiederherstellbares im Backup.
Bei PostgreSQL und SQLite gab es Live-Dateien, aber ohne Konsistenzgarantie; bei SQLite fehlt
zusätzlich der WAL (*.db-wal ist ausgeschlossen).
Das Muster ist richtig und bleibt: es hält 13 GB Home-Assistant-Backups, 3,8 GB
Jellyfin-Backups und 1,9 GB /home/robin/backup draußen. Stattdessen ist das Dump-Ziel aus dem
Namensraum gezogen: /var/lib/db-dumps.
Und wieder war das Backup dabei grün. Es lief jede Nacht durch und meldete Erfolg an ntfy, Healthchecks und Uptime Kuma — dieselbe Klasse wie #217 und #236: Die Erfolgsmeldung sagte nichts über den Inhalt.
Hooks auf commands: (Teil von #240)¶
Die Syntax stammt aus dem Schema der installierten Version 2.1.5, nicht aus dem Gedächtnis:
before: action + when: [create], after: action + states: [finish], after: error.
config validate meldete vorher sechs Deprecation-Warnungen, jetzt null.
Vorab an einer Wegwerf-Config geprüft, dass die Hooks wirklich feuern (Marker-Dateien vor und nach dem Lauf) — die Syntax zu validieren hätte nur bewiesen, dass die Datei wohlgeformt ist.
Zwei Fallstricke: YAML bricht an : in unquoted Strings, und borgmatic interpoliert {…} —
geschweifte Klammern in Hook-Befehlen sind gefährlich, deshalb if/then/fi statt { … }. Die
Zeile docker ps --format 'table {{.Names}}…' wurde eigens getestet: sie überlebt.
Entfernt: der Dropbox-Sync aus dem after-Hook. Er ist keine Backup-Stufe — 3-2-1 steht ohne
ihn. Robins Anliegen (Dokumente in der Dropbox griffbereit) lebt unabhängig in #242 weiter. Dabei
aufgefallen: Das Skript macht rclone sync, nicht copy — der Zielordner ist ein Spiegel,
manuell dort abgelegte Dateien würde der nächste Lauf löschen.
Mount-Guard (#240, Punkt 3)¶
source_directories_must_exist ist in borgmatic 2.x bereits default true — reicht aber
nicht: Ein ausgehängter NFS-Mount hinterlässt ein existierendes, leeres Verzeichnis. Der Guard
prüft deshalb echte Mountpoints als ersten Befehl im before-Hook.
Test bestanden: mit einem erfundenen Mountpoint bricht borgmatic ab (Error running before action
hook, Exit 1, kein Archiv), mit den echten Mounts läuft er durch. Ein Abbruch löst über
after: error die ntfy-Meldung aus.
Nebenbefund: Die NFS-Mounts stehen nicht in der fstab, sondern als systemd-.mount-Units —
sauber verwaltet und reboot-fest.
Der Beweis¶
Ein echter borgmatic create um 14:05 (8 Minuten, davon 3:25 fürs Archiv, 316.188 Dateien,
1,99 GB dedupliziert). Im Archiv liegen alle 15 Datenbank-Dumps mit frischem Zeitstempel und
mnt/scanner mit allen neun Scans — Bachelorzeugnis, Arbeitszeugnis, Einstellungsunterlagen,
nach zwei Jahren erstmals gesichert.
Eine Prüfmethode, die falsche Nullen liefert¶
borgmatic list --archive latest --find /mnt/scanner gab 0 Treffer, obwohl die Dateien im
Archiv lagen: --find sucht nach Dateinamen, nicht nach Pfaden, und borg speichert Pfade
ohne führenden Schrägstrich. Richtig ist --path mnt/scanner. Beide Fehler zusammen ergeben
ein überzeugendes Nichts — beinahe hätte ich ein zweites Loch gemeldet, das keines war.
Offen¶
PR #71 (native DB-Hooks) und die Restore-Probe bleiben liegen, bewusst. Beim Sichten fiel auf,
dass der PR vom 23. Mai die E0b-Abschaltungen nicht kennt: Er führt noch wallos.db, und die
Pfade zeigen auf das alte Dump-Ziel. Die Prüfpunkte stehen als Kommentar am PR.
Entscheid am Sessionende: Paperless wird vorgezogen¶
Robin: die Dokumentenstapel zu Hause drücken, Paperless ist das Produkt mit dem größten praktischen Nutzen. Neue Reihenfolge: #240 (Backup-Vorschaltpaket) → E7 → E1-Rest → D1 → E2.
Die Bestandsaufnahme dazu ergab zwei Dinge, die die Entscheidung tragen:
- Paperless ist beim Backup besser dran als gedacht.
/mnt/paperlesssteht in beiden borgmatic-Configs, ein täglicherpostgres-paperless_db.sql.gzliegt in/var/backups/db-dumps(ebenfalls Backup-Quelle), undafter_backupruft einpaperless-dropbox-sync.sh— es gibt also bereits eine Zweitlage. Aber: die DB-Dumps entstehen imbefore_backup-Hook, und alle drei Hook-Typen sind deprecated. Fallen sie mit einem borgmatic-Update weg, verschwinden still die konsistenten Dumps und die Dropbox-Kopie — das Backup bliebe grün und sicherte nur noch laufende PostgreSQL-Dateien. PR #71 liegt dafür vor. - Der Zulauf existiert längst — und endet in einer Sackgasse. Nativer Samba auf dem NUC hat eine
Freigabe
[scanner]→/mnt/scanner. Darin liegen neun echte Scans seit Juni 2024 (Bachelorzeugnis, Arbeitszeugnis, Einstellungsunterlagen), von denen keiner je in Paperless ankam — das hat weiterhin genau ein Dokument. Das Verzeichnis liegt auf der Systemplatte und gehört zu keiner borgmatic-Quelle. Zwei Jahre unersetzliche Dokumente ohne Backup.
Für E7 genügt deshalb eine Teilmenge von E1, nicht das ganze Epic: Scanner-Ordner in die Quellen,
PR #71, Mount-Guard (/mnt/paperless ist NFS — Mount weg ⇒ leeres Archiv mit Erfolgsmeldung), und
eine Restore-Probe aus dem Hetzner-Archiv. Lars ins Tailnet, Break-Glass, StorageBox-Sub-Accounts und
Appliance-Backups bleiben in E1 und kommen nach Paperless.
Offen aus E0c¶
Nichts. Epic #178 ist geschlossen. ha-mcp ist auf Robins Entscheid nach #239 (E6) gewandert —
es gehört zum HA-Neustart, nicht zum Prozess-Fundament. Der GitNexus-Stash im homeserver-Checkout
ist verworfen.
Zwei Funde dieser Session hat Robin am Ende korrigiert: Das Küchen-TRV ist nicht defekt, sondern bewusst stromlos (Batterie entnommen wegen wiederholter Verbindungsabbrüche), und hinter dem Wasserleck-Sensor liegt keine Automation. Beide Issues (#237, #238) sind entsprechend richtiggestellt; was bleibt, ist das Thema Zigbee-Stabilität für E6a/E6.
2026-09-16 — E0b: 337 GB frei, und der größte Hebel war nicht der geplante¶
Issues: homeserver#177 (Epic E0b) weitgehend abgearbeitet · #82 (proxy-manager) erledigt ·
#227 neu (UNAS-Archive außerhalb des Backups) · homelab-external#26 geschlossen ·
aiserver#9 ergänzt
PRs: homeserver#226, #230 · homelab-external#32
Ergebnis: /mnt/ssd von 469 GB (54 %) auf 132 GB (16 %). Zielmarke des Epics war < 40 %.
Die Planungsannahme stimmte nicht¶
In der Roadmap stand der Docker-Prune mit 58 GB als größter Hebel für das SSD-Ziel. Das ist
falsch: Docker Root Dir ist /var/lib/docker, das liegt auf der Systemplatte, nicht auf
/mnt/ssd. Der Prune räumt eine Platte auf, die zu 19 % gefüllt war — für das Ziel bringt er
exakt null.
Der eigentliche Posten stand im Epic als „300 GB immich-import, liegt nicht unter
container-data, erst finden". Er lag unter /mnt/ssd/tmp/immich-import, seit dem
19. April unberührt, 15.640 Dateien.
Die Verifikation, die das Epic verlangt — und was sie zutage förderte¶
Immich speichert in asset.checksum einen SHA-1 (20 Byte, verifiziert). Damit ließ sich die
Bedingung „nur nach Import-Verifikation" wörtlich erfüllen: SHA-1 über alle 15.640 Dateien
(0 Lesefehler), Abgleich gegen alle 26.616 aktiven Immich-Assets.
| Dateien | Volumen | |
|---|---|---|
| Nachweislich in Immich | 15.468 | ~243 GB |
| Nicht in Immich | 172 | 56,88 GB |
Die 172 zerfielen in drei sehr verschiedene Gruppen:
- 89 Sony-XAVC-Videos (47,9 GB), alle aus
CatVideosPhotos/— Yoga- und Massage-Clips. Von 224 MP4 dort waren 135 importiert, 89 nicht; kein Größenmuster (65 MB bis 4,6 GB). Robins Einordnung: fremdes/altes Projektmaterial, nicht nach Immich importieren. - 25 DVD-Dateien (9,0 GB) aus
Robin Klein/1/und/3/— VOB/IFO/BUP. Immich kann VOB grundsätzlich nicht importieren. Ein pauschales Löschen des Ordners hätte diese Kindheitsaufnahmen ersatzlos vernichtet. - 5 Fotos (4 JPEG, 1 DNG) plus 53 Beiwerk-Dateien (NFO/THM/INI/XMP), die Immich nie importiert.
Keine der 94 echten Mediendateien war defekt — der Import war schlicht unvollständig.
Vorgehen deshalb: die 172 zuerst auf die UNAS gerettet, alle 172 Prüfsummen gegen die Originale verglichen (keine Abweichung), erst dann den Ordner gelöscht.
Nextcloud: der Ordner, der als leer aussah¶
Nextcloud war seit dem 15.09.2024 tot — kein Compose-Stack mehr, aber 37 GB auf der SSD (15 GB Dateien, 21 GB Versionsstände, 790 MB Papierkorb). Robins Entscheid: komplett auf die UNAS verschieben.
Beim Kopieren als robin zeigte du für nextcloud/db 4 KB — scheinbar ein leeres
Verzeichnis. Der Ordner gehört aber lxd:root mit drwx------, und du konnte ihn nicht
betreten; gezählt wurde nur der Verzeichniseintrag selbst. Als root sah es anders aus:
eine vollständige PostgreSQL-Datenbank, 1.971 Dateien, 136 MB. Ohne sie wären
Versionsstände, Papierkorb, Shares und Tags nicht mehr zuzuordnen gewesen.
Lehre: du und find als unprivilegierter Nutzer melden für unlesbare Verzeichnisse
plausible Kleinwerte statt eines Fehlers. Vor dem Löschen eines fremden Datenbestands muss
die Bestandsaufnahme mit denselben Rechten laufen wie das Löschen.
Ein Rückschritt, den ich selbst verursacht habe¶
Die Nextcloud-Daten lagen unter /mnt/ssd/container-data und waren damit im Backup. Beide
borgmatic-Configs sichern /mnt/ssd/container-data, /mnt/paperless und /mnt/pictures —
/mnt/unas steht in keiner von beiden. Durch das Verschieben sind 39 GB aus der
Backup-Abdeckung gefallen, bevor ich das geprüft hatte. Ich hatte den Punkt selbst als Option
angeboten und ihn dann der Optionswahl überlassen, statt ihn vor dem Löschen zu klären.
Robins Einordnung (16.09.): nicht kritisch, die Daten liegen teilweise auch in der Dropbox, es sind alte Leichen, UNAS mit RAID6 reicht. Bewusst vertagt nach #227 (hängt an #216).
Ein Fehlalarm von mir, innerhalb einer Minute widerlegt¶
Das btrfs-Scrub-Log endete auf Bytes scrubbed: 0.00B (0.00%) bei Error summary: no errors
found — nach dem Muster aus #217 („meldet grün, prüft nichts") schien der Fall klar. Der
Live-Status sagte das Gegenteil: 52 Minuten Laufzeit, 601,79 GiB geprüft, 195 MiB/s, keine
Fehler. Der Scrub arbeitet einwandfrei.
Der echte Fehler lag im Cron-Job:
scrub start ohne -B kehrt sofort zurück, der direkt folgende status liest also den
Zustand von Sekunde 1. Das Log zeigte deshalb immer 0 Bytes — strukturell wertlos, aber kein
Backup-Problem. Korrigiert auf scrub start -B mit vorangestelltem date. /mnt/backup-nas
ist übrigens korrekt: das ist /dev/md0 als Ganzes, beide Borg-Subvolumes liegen darin.
Dabei ein Fallstrick, der fast durchgerutscht wäre: in /etc/cron.d ist % ein Zeilenumbruch
und muss \% geschrieben werden — %% (Makefile-Gewohnheit) hätte den Befehl zerrissen.
Deshalb jetzt date ohne Formatstring.
Was sonst noch tot war¶
/etc/cron.daily/borgmaticlief täglich um 06:25 an und tat nichts:borgmaticliegt unter/root/.local/bin, das ist nicht im cron-PATH. Nachgewiesen mitenv -i PATH=/usr/bin:/bin sh /etc/cron.daily/borgmatic→borgmatic: not found. Ein Glücksfall — funktionsfähig hätte er ein zweites Vollbackup parallel zum Timer-Lauf um 02:04 gestartet. Entfernt;borgmatic.timerläuft unverändert.- Syncthing hatte in der gesamten Log-Historie null Geräteverbindungen; der
Paperless-
consume-Ordner ist seit Dezember 2025 unverändert leer. Der konfigurierte Peer „Macbook" hat sich nie verbunden. Paperless selbst enthält genau ein Dokument (zuletzt 11.01.2026). - Wallos hatte keinen einzigen Benachrichtigungskanal konfiguriert — alle
*_notifications-Tabellen undnotification_settingsleer. Ein Abo-Tracker ohne Erinnerung ist eine Liste, die man aufrufen muss, um an etwas erinnert zu werden; man ruft sie also genau dann auf, wenn man ohnehin daran denkt. Die 24 Abos (19 aktiv, 1.112,53 €) sind mitsamt Notizen als CSV und SQLite-Kopie nach/mnt/unas/wallos-export-2026-09-16/gesichert, dann abgeschaltet. - Portainer ungenutzt — ein ungenutzter Container mit vollem Docker-Socket-Zugriff ist reine Angriffsfläche.
- proxy-manager (#82): seit August 2025 kein Container, nur eine compose-Datei mit drei MySQL-Passwörtern im Klartext. Die bleiben in der History; da Stack und Datenbank weg sind, sind sie wertlos — außer die Werte wurden anderswo wiederverwendet (gehört zu #194).
ollama/ollama:latestauf dem aiserver (8 GB) von keinem Container genutzt, llama-swap hat es abgelöst. Entfernt: 18,02 GB → 10,09 GB Images.- 8 verwaiste Volumes (7,76 GB → 45 kB), darunter
van_dam_db_data, eine PostgreSQL-DB vom Oktober 2023.
Der Timer, der das Journal übertönte¶
Der USV-Textfile-Collector aus #200 läuft alle 30 s als oneshot-Unit — systemd schreibt dabei
drei Zeilen je Lauf. Gemessen: von 8668 systemd-Einträgen in 24 h stammten rund 8400 von
diesem einen Timer. LogLevelMax=warning (PR #230) beendet das, ohne Fehlschläge zu
verstecken. Geprüft wurde die Wirkung, nicht der Dateiinhalt: systemctl show -p
LogLevelMax → 4, Journalzeilen in 2 min → 0, ups.prom 10 s alt.
Traefik-Log auf dem VPS (external#26)¶
Das Access-Log war von 415 MB auf 493 MB gewachsen, ohne jede Rotation. Traefik schließt
und öffnet seine Logdateien laut offizieller Doku bei USR1 — deshalb postrotate statt
copytruncate, das die Zeilen zwischen Kopie und Kürzung verliert. Nach logrotate -f blieb
die neue Datei zunächst 0 Byte; erst ein erzwungener Request bewies, dass Traefik wirklich
hineinschreibt. Ohne diesen Test wäre offen geblieben, ob das Logging noch läuft.
Gzippt: 493 MB → 27,8 MB.
Posten, die sich in Luft auflösten¶
- unbound-Healthcheck war bereits in einer früheren Session repariert und ausführlich
begründet (
drill-hcfehlt im Image, keine Shell,digals einziges Binary mit brauchbaren Exit-Codes). Containerhealthy. Nur abgehakt. - samba-Log-Spam gibt es nicht mehr: 92 Journal-Einträge in 7 Tagen,
log.nmbd4,6 KB. - DOKUMENTATION.md existiert nicht mehr.
- Branch-Friedhof war keiner: die Remote-Branches waren beim Merge automatisch gelöscht
worden, nur die lokalen Tracking-Refs waren veraltet.
git fetch --prunebrachte 36 auf 16; fünf tatsächlich verbliebene Branches und vier lokale Leichen entfernt. - pzserver ist weiterhin keine Leiche (läuft mit Monitoring, #220/#222).
Nebenbefund außerhalb des Epics¶
Der Checkout von homeserver stand auf feat/sharetab-backup mit einem ungepushten
Commit (018d83b, verschlüsselter täglicher pg_dump der ShareTab-Datenbank). Unberührt
gelassen; die gesamte E0b-Arbeit lief in separaten Worktrees. Die Arbeit liegt nur lokal.
Offen aus E0b¶
container-data/ollama(60 GB) bleibt liegen — der NUC-KI-Stack wandert laut Entscheid vom 13.09. komplett auf den aiserver, das ist E4.- 56 GB getaggte, ungenutzte Docker-Images bewusst behalten (Robins Entscheid: nur Dangling-Images entfernt, 2,8 GB) — die Systemplatte ist zu 17 % gefüllt, es gibt keinen Druck.
- aiserver-Swap:
/swap.img(8 GB) scheitert seit dem 31.07. mitswapon: Invalid argument, die Unit ist dauerhaftfailed. Bei 124 GB RAM folgenlos. Vertagt nachaiserver#9/D1 (dort dokumentiert). - #227: die UNAS-Archive liegen außerhalb jeder borgmatic-Quelle.
2026-09-15 — E0a abgeschlossen; die neue Regel war selbst ein Fehlalarm¶
Issues: homeserver#176 (Epic E0a) geschlossen · #195 geschlossen · #225 neu ·
#194 → E2a und #81 → E6 verschoben
PRs: homeserver#221, #224
Die Zertifikatsregel hätte auf ein totes Zertifikat alarmiert¶
Am Freitag stand als offene Frage im Raum, ob lego den neuen kontoeigenen Cloudflare-Token
annimmt. Die Metrik sah beunruhigend aus: esphome fiel von 29,8 auf 28,0 Tage Restlaufzeit,
ohne dass ein Erneuerungsversuch im Log stand — obwohl Traefiks Doku eindeutig ist
(„initiates the renewal process 30 days before expiration").
Das tatsächlich ausgelieferte Zertifikat sagte etwas anderes:
Es war am Freitag längst erneuert worden. Traefik exportiert nach einer Erneuerung das
ersetzte Zertifikat weiter, eine CN trägt dann zwei Serien mit verschiedenen Seriennummern.
Verräterisch: count(traefik_tls_certs_not_after) = 27 bei count(count by (cn) (…)) = 26.
Damit hätte CertificateExpiringSoon aus #221 — keine zwei Stunden alt — in sieben Tagen auf
ein Zertifikat alarmiert, das keine Rolle mehr spielt. Genau die Fehlerklasse, gegen die die
Regel gebaut wurde, diesmal selbst produziert. #224 aggregiert jetzt max by (cn) und stellt
damit die Frage, auf die es ankommt: läuft jedes Zertifikat für diesen Namen aus?
Die Lehre ist unbequemer als die Regel: die Metrik ist Traefiks Selbstauskunft. Was wirklich
ausgeliefert wird, sagt nur ein openssl s_client. Zweimal an einem Tag hatte ich aus einem
Negativergebnis auf eine Ursache geschlossen, ohne zu prüfen, ob das Messmittel überhaupt passt.
Der Token kann schreiben — bewiesen statt vermutet¶
Die TXT-Probe lief vollständig durch: Zone-Lookup, _acme-probe-Record anlegen, löschen,
Gegenprobe null Treffer. Der cfat_-Token hat also volle DNS-Schreibrechte. Offen bleibt nur,
ob lego ihn annimmt — die Erneuerung vom 13.09. lief eine Stunde vor dem Tausch, also noch
mit dem alten Token. Nächster Kandidat ist dashboard mit 33 Tagen.
Uptime Kuma: die Lücke liegt woanders als gedacht¶
Auf die Frage, ob Kuma SSL-Checker für alle Domains bekommen soll, war die erste Messung
irreführend: ein Test gegen die nackte Tailnet-IP ergab HTTP 000, was nach „nicht erreichbar"
aussah. Traefik antwortet ohne passenden SNI aber gar nicht. Tatsächlich erreicht Kuma jede
interne Domain (esphome → HTTP 200, Auflösung auf 10.10.10.3 per DNS, keine hosts-Einträge),
und zwei Monitore haben die Zertifikatswarnung längst an.
Die echte Lücke: kein einziger Monitor auf eine externe Domain, und der VPS-Traefik ist in keinem Scrape-Job. Uptime und Zertifikate des VPS beobachtet also nichts.
#225: fällt der VPS, verstummt alles¶
Daraus wurde der Fund des Tages. ntfy, Uptime Kuma und Healthchecks laufen alle auf demselben
VPS, und der Alertmanager auf dem NUC hat genau einen Empfänger — auch für critical.
Fällt der VPS, gehen alle Alarme verloren, und kein Baustein kann es melden: Kuma kann seinen
eigenen Tod nicht berichten, Healthchecks ist der Dead-Man's-Switch und wäre selbst tot, und
auch NUTs Stromausfall-Meldungen gehen dorthin.
Die Denkfalle steht im Issue: ein Alarm über den VPS kann nicht durch den VPS zugestellt werden. Eine NUC-Sonde allein reicht nicht, es braucht einen zweiten, unabhängigen Zustellweg. Vier Optionen mit Für und Wider sind notiert; die Abnahme ist in jedem Fall ein simulierter Ausfall, nicht eine plausibel aussehende Konfiguration.
E0a geschlossen¶
homeserver#176 ist zu. Verschoben statt liegengelassen: #194 (restliche Secret-Rotation)
geht zu E2a, damit Rotation und Infisical-Umstellung jeden Stack nur einmal anfassen —
heute hängen nur ollama und speedtest-tracker am Agent. #81 (Mosquitto-Auth) geht zu
E6, weil der Umbau ein Wartungsfenster braucht, das beim HA-Aufräumen ohnehin eingeplant
ist. Bewusst geparkt bleiben #211 und external#22, entkoppelt läuft external#31.
Vorher war noch #195 fällig: der cloudflare-tunnel-Container lief weiter, und vier aktive
Verbindungen belegten, dass der Tunnel in Cloudflare noch existierte. Beides ist jetzt weg.
Ausgangsstand für E0b¶
Erhoben, damit die nächste Session nicht bei null anfängt. Richtwert des Epics ist SSD < 40 %:
| Posten | Größe |
|---|---|
/mnt/ssd belegt |
469G von 916G = 54 % (rund 130 GB bis zum Richtwert) |
| Docker reclaimable | 58,14 GB von 121,4 GB (147 Images, 67 aktiv) + 7,76 GB in 8 ungenutzten Volumes |
container-data/ollama |
60G — KI wandert auf den aiserver, also E4-nah |
container-data/nextcloud |
38G — steht namentlich im Epic |
Die drei Hebel allein übertreffen das Ziel. Zwei Korrekturen am Epic-Text: pzserver ist
keine Leiche mehr (in #220/#222 wiederbelebt, 6,9G, mit Monitoring), und die 300 GB
immich-import liegen nicht unter container-data — dort sind nur 7,3G immich, das Ziel muss
also erst gefunden werden.
Verwaiste Container wurden systematisch gesucht (je Container
com.docker.compose.project.working_dir gegen das Dateisystem prüfen): keine mehr. Der
Tunnel war der einzige.
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.
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).
Nachtrag: der Tunnel war nicht gelöscht¶
Beim Inventarisieren der Cloudflare-Credentials fiel auf, dass der cloudflare-tunnel-Container
noch lief — angelegt vor zwei Monaten, seit 6 Tagen up. PR homeserver#203 hatte
cloudflared/ aus dem Repo entfernt, aber nie docker compose down ausgeführt; das
Stack-Verzeichnis existierte auf dem NUC nicht mehr, also war auch kein compose down mehr
möglich. Durch restart: unless-stopped hatte die Waise jeden Reboot überlebt.
Der Metrik-Endpunkt (--metrics 0.0.0.0:60123, network_mode: host) lieferte den Beweis:
cloudflared_tunnel_ha_connections 4 ← vier aktive Verbindungen zu Cloudflares Edge
cloudflared_tunnel_total_requests 0 ← seit dem 06.09. kein einziger Request
Vier registrierte Verbindungen heißen: der Tunnel existierte in Cloudflare noch — ein
gelöschter Tunnel kann keine halten. homeserver#195 wurde deshalb wieder geöffnet; Container
entfernt und Tunnel gelöscht sind es jetzt beide.
Das korrigiert auch eine Aussage von vorher: aus „TCP 443 zur Heim-IP ist zu" war geschlossen
worden, die Dashboard-Credentials admin/admin seien ein reines LAN-Risiko. Ein aktiver Tunnel
ist ein Ingress-Weg ohne Port-Forward und hätte das ausgehebelt. Bei total_requests 0 hielt
das Ergebnis praktisch — es beruhte aber auf einer ungeprüften Annahme.
Nachtrag: CF_DNS_API_TOKEN rotiert — und zwei Lehren über Cloudflares Token-Modell¶
traefik/.env ist aus dem Git (PR homeserver#219), der Token rotiert, der alte gelöscht.
Nachweis über denselben Aufruf mit demselben Wert, vor und nach dem Löschen:
vorher: {"success":true} "This API Token is valid and active"
nachher: {"success":false,"errors":[{"code":1000,"message":"Invalid API Token"}]}
Unterwegs zwei Fehlschlüsse, die hier festgehalten gehören, weil sie sich wiederholen werden:
1. Ein 401 an /user/tokens/verify heißt nicht, dass der Token kaputt ist. Cloudflare steuert
auf kontoeigene Tokens zu (Secret cfat_ + 48 Zeichen, also 53); benutzer-eigene haben 40.
Der User-Endpunkt lehnt einen kontoeigenen mit 401 Invalid API Token ab, obwohl er einwandfrei
ist. Daraus war zunächst geschlossen worden, der neue Token sei ungültig — falsch. Belastbar
prüft man an den Endpunkten, die der Verbraucher benutzt: GET /zones?name=… und die
dns_records-Operationen liefern mit dem neuen Token HTTP 200, und genau die macht lego bei
einer DNS-01-Challenge.
2. Ein kontoeigener Token kann benutzer-eigene nicht verwalten —
9109 "Valid user-level authentication not found". Das Löschen eines Legacy-Benutzer-Tokens
braucht ein benutzer-eigenes Credential oder die Oberfläche. Gelöst wurde es über die
Oberfläche, per ID-Abgleich statt über „Last used" (das zeigt womöglich nur das Datum, und
dann stehen der alte Traefik- und der ddns-Token beide auf „heute"). Beide IDs vorher zu kennen
war der Punkt, an dem es ohne Raten ging.
Dritte Kleinigkeit: 32 Hex-Zeichen sind bei Cloudflare immer eine ID, nie ein Secret. Wird
eine ID als Bearer geschickt, kommt 6111 "Invalid format for Authorization header" — erkennbar
anders als ein Berechtigungsfehler.
Vor dem Löschen geprüft, damit nichts mitfällt: cloudflare-ddns nutzt einen anderen Token,
und der VPS-Traefik nutzt httpChallenge und brauchte nie einen. Offen bleibt nur, ob lego
einen cfat_-Token annimmt — legos Doku erwähnt kontoeigene Tokens nicht. Das klärt sich von
selbst: kleinste Restlaufzeit 29,8 Tage, Traefik erneuert bei 30 und prüft im Tagesrhythmus.
Nachtrag: die drei Regeln gegen stille Ausfälle (PR homeserver#221)¶
Neue Datei silent-failure-alerts.yml mit TargetDown (15m, job="aiserver" bewusst
ausgenommen, weil AiserverDown das schon als Critical abdeckt),
PrometheusConfigReloadFailed und CertificateExpiringSoon (21 Tage). Keine neue Datenquelle
nötig — alle drei Metriken lagen schon da und waren unbeobachtet, allein
traefik_tls_certs_not_after mit 27 Serien.
Verifiziert in beide Richtungen, und das ist der Inhalt von #217: keine der drei feuert bei
Ankunft, und jeder Selektor trifft nachweislich Serien (16 Ziele, 27 Zertifikate). Die
Gegenprobe mit absichtlich zu großer Schwelle (40 statt 21 Tage) beweist, dass der Ausdruck
auswertet. Eine Regel auf eine nicht existierende Metrik sieht dauerhaft grün aus — genau so
blieben die 606 nut-upsd-Restarts vier Monate unbemerkt.
Korrektur an der E0b-Liste¶
pzserver war als Leiche mit einem SRCDS_TOKEN in Git notiert. Robin belebt den Stack in
PR homeserver#220 auf Basis der veröffentlichten Images wieder — der Token gehört dort also
nicht entwertet, sondern gleich aus dem Git heraus in eine .env. proxy-manager bleibt eine
Leiche (kein Container, drei MySQL-Passwörter in Git).
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.