feat: slice 3 - wiki, sources and AARs in their neckbeard homes
Gate 4, slice 3: verfahren/, hosts/, vision/ and shared/ moved via git mv - six AARs to docs/aar/ (four harvested by the 2026-08-09 retro, two open), procedures and host knowledge to docs/wiki/ (admin, deployment, architecture, new area vision), the retro protocol and the commit mapping table to docs/sources/ (protokolle/, migration/). New: the wiki index linking every page, and the mirror-topology page carrying the why-two-places reasoning verbatim from the old CLAUDE.md (F-013 preserved). All moved-path references retargeted; the link checker drove the sweep to zero. pruefe_prosa.py added (pattern C+D): SHA citations resolve via repo, mapping table, optional component clones or a curated exemption list (documented dead Gitea-force-push commits, a vendor-repo tag, an Authentik uid that is hex but no git SHA, the external neckbeard reference); wiki task prose without an issue reference errors, with a visible pragma for deliberate checklists; the dead-tracker denylist now covers every mirrored repo's retired Gitea tracker (F-005) - two links re-verified against live GitLab titles and retargeted, five defused into honest historical citations. Verified: validate 0/0, gen_status --check current, drift 0. Demo on the pre-migration state fires 6 findings (3 orphaned SHAs, 3 task blocks); on the current tree exactly the 3 F-004 task blocks remain - they turn green in slice 4 when the issues exist, which is why pruefe_prosa joins CI only then. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
70e81e2ff1
commit
92b448fe30
@@ -0,0 +1,73 @@
|
||||
---
|
||||
type: aar
|
||||
status: harvested
|
||||
date: 2026-08-01
|
||||
related: []
|
||||
---
|
||||
|
||||
# AAR — CVE-Pipeline `gitops#47`
|
||||
|
||||
**Datum:** 2026-08-01 · **Host/Stack:** CFGMON, `/opt/threadnet-operating/monitoring`
|
||||
**Auftrag:** Deploy eines gebauten, als UNGETESTET übergebenen Stands (Trivy-Scanner,
|
||||
Exporter, Alert-Regeln, Grafana-Dashboard)
|
||||
|
||||
## 1. Ergebnis
|
||||
|
||||
**Live und verifiziert:** Scanner (29/29 Images gescannt), Exporter, Prometheus-Job
|
||||
`cve_exporter`, Regelgruppe `axion-cve`, Grafana-Ordner *Security* mit CVE-Dashboard.
|
||||
|
||||
**Bewusst nicht live:** die Alarm-Zustellung nach Matrix. `room="security"` routet in
|
||||
`alertmanager.yml` auf einen Null-Receiver (Commit `2b715ca` in
|
||||
`sorb/threadnet-operating`). Grund siehe Befund 1.
|
||||
|
||||
## 2. Befunde
|
||||
|
||||
| # | Befund | Schwere | Status |
|
||||
|---|---|---|---|
|
||||
| 1 | Eine Matrix-Nachricht pro CVE. 126 CRITICAL landen in **einer** Alertmanager-Gruppe, nach 24 h kommen 1222 HIGH dazu. Dazu steht `save_state()` in `do_POST` hinter der Sende-Schleife: bricht ein Send ab (Synapse rate-limitet nach ~10 mit 429), wird kein State gespeichert, der Receiver antwortet 502, Alertmanager wiederholt die komplette Gruppe — mit leerer Deduplizierung | HIGH | abgefangen, `gitops#51` |
|
||||
| 2 | `docker compose up -d` aktiviert geänderte Configs nicht. Einzeldatei-Mounts hängen am Inode, `git pull` benennt um. Prometheus lief nach dem Deploy mit alten Regeln — `promtool` fand 9, Prometheus kannte 6, kein Fehler im Log | MEDIUM | behoben via `--force-recreate`, `gitops#52` |
|
||||
| 3 | `TrivyScanStale` kann ein nie erfolgreich gescanntes Image nicht melden — ohne ersten Report existiert keine Serie, an der `time() - trivy_last_scan_timestamp` hängen könnte | LOW | notiert in `gitops#51` |
|
||||
| 4 | Der Exporter prunt den First-Seen-State bei **jedem** Scrape. Ein transienter Lesefehler (`except: continue`) löscht die Erstfund-Zeitstempel des Targets dauerhaft | LOW | notiert in `gitops#51` |
|
||||
| 5 | Grafana-Provisioning für den Security-Ordner fehlte — das Dashboard wäre nie erschienen | — | vom Autor selbst behoben (`cdfadc0`), bevor ausgerollt wurde |
|
||||
|
||||
Gemessene Gesamtlage über alle 29 Images: **126 CRITICAL, 1222 HIGH**, 2710 MEDIUM,
|
||||
1316 LOW. Spitzenreiter `goauthentik/server:2026.2.3` mit 369 CRITICAL+HIGH.
|
||||
|
||||
## 3. Verdachtsfälle mit Entwarnung
|
||||
|
||||
| Vermutung | Messung | Ergebnis |
|
||||
|---|---|---|
|
||||
| Exporter parst 29 JSONs je Scrape → Timeouts bei 15 s Intervall | `collect()` gegen echte Reports | 0,4 s für 29 Reports, ~3100 Zeilen — unkritisch |
|
||||
| Private Registry `rohana.axion1337.de` braucht Credentials für Trivy | Anonymer Pull | zieht anonym, keine Credentials nötig |
|
||||
| Zwei down-Targets könnten Folge des Deploys sein | `avg_over_time(up[3h])` | 0.00 — schon 3 h vorher tot, in `hosts/game.md` erfasst |
|
||||
|
||||
`promtool check rules`, `amtool check-config`, `amtool config routes test`,
|
||||
`docker compose config` und `py_compile` liefen alle sauber.
|
||||
|
||||
## 4. Was die Befunde ermöglicht hat
|
||||
|
||||
Befund 1 wäre in keinem Lint aufgefallen — der Code ist korrekt, das Problem entsteht
|
||||
erst aus der **Datenmenge**, gegen die er läuft. Sichtbar wurde er durch Messen vor dem
|
||||
Deploy: Trivy lokal über drei repräsentative Images ergab 27 CRITICAL / 126 HIGH auf 3
|
||||
von 29, also die richtige Größenordnung. Die spätere Realität (126 CRITICAL) bestätigte
|
||||
die Entscheidung. `amtool config routes test` belegte danach, dass die Stummschaltung
|
||||
nur `room=security` trifft und den normalen Alarmweg unangetastet lässt.
|
||||
|
||||
Befund 2 wurde nur sichtbar, weil die Config **im Container** geprüft wurde
|
||||
(`docker exec prometheus grep …`) statt auf der Platte. Auf der Platte sah alles richtig
|
||||
aus, `up -d` meldete `Running`, und ein SIGHUP-Reload lud klaglos den alten Inhalt.
|
||||
|
||||
Diese beiden Punkte sind als Verfahren festgehalten:
|
||||
[../deploy-uebergabe.md](../wiki/deployment/deploy-uebergabe.md).
|
||||
|
||||
## 5. Offen
|
||||
|
||||
Richtungsentscheidung zu `gitops#51`, bevor die Alarme scharf gehen: entweder
|
||||
`matrix-alerts.py` auf eine Sammelnachricht pro Webhook-Batch umbauen (die fünf
|
||||
Pflichtfelder je CVE als eine Zeile, bleibt vollständig), oder die Regeln auf
|
||||
`count by (target, severity)` aggregieren und die CVE-Details im Dashboard lassen.
|
||||
In beiden Fällen zusätzlich: State inkrementell speichern, 429 mit `Retry-After`
|
||||
behandeln. Danach die `room="security"`-Route entfernen.
|
||||
|
||||
Nebenbefund ohne Handlungsbedarf von hier: `coturn/coturn:latest` ist das einzige
|
||||
ungepinnte Image (bereits in `gitops#47` notiert).
|
||||
@@ -0,0 +1,192 @@
|
||||
---
|
||||
type: aar
|
||||
status: harvested
|
||||
date: 2026-08-01
|
||||
related: []
|
||||
---
|
||||
|
||||
# AAR — LABNET-02, CFGMON-Seite (Übergabe `sorb/management#2`)
|
||||
|
||||
**Datum:** 2026-08-01 · **Host/Stack:** CFGMON, WireGuard-Client gegen UDM
|
||||
**Auftrag:** Schritte 1–3 der Übergabe (Architektur v2), melden, nach Freigabe aktivieren
|
||||
|
||||
## 1. Ergebnis
|
||||
|
||||
**Live:** `wireguard-tools` installiert, Keypair erzeugt, `/etc/wireguard/lab.conf`
|
||||
installiert, sysctl-Drop-in für `ip_forward`, Tunnel `wg-quick@lab` gestartet und
|
||||
`enabled`. Interface `lab` steht mit `10.58.75.2/24`, Routen und Forward-Regeln aktiv,
|
||||
Split-DNS gesetzt (`10.58.73.1`, `~lab`).
|
||||
|
||||
> ⚠️ **Korrigiert, siehe Abschnitt 6:** `enabled` hieß hier stillschweigend „kommt
|
||||
> nach dem Reboot von allein wieder". Das war falsch — der Dienst ist beim Neustart
|
||||
> fehlgeschlagen.
|
||||
|
||||
**Noch nicht funktionsfähig:** **kein Handshake** — `0 B received`. Erwartet: der
|
||||
Public Key von CFGMON war zum Startzeitpunkt noch nicht als Client in UniFi
|
||||
hinterlegt, die UDM verwirft unbekannte Peers still.
|
||||
|
||||
**Blockiert:** die Gateway-Rolle. Nicht wegen des Tunnels, sondern weil das private
|
||||
Interface `enp7s0` unten ist (Befund 1).
|
||||
|
||||
## 2. Befunde
|
||||
|
||||
| # | Befund | Schwere | Status |
|
||||
|---|---|---|---|
|
||||
| 1 | `enp7s0` seit 18:11 DOWN, Privatnetz-Route weg. Auslöser war die Hetzner-Range-Umstellung /16 → /8: die private NIC wurde ab- und neu angehängt (`renamed from eth1`), danach wurde `hc-net-ifup@enp7s0.service` **übersprungen** (`ConditionPathExists=!/run/systemd/network/10-netplan-enp7s0.network`). Folge: `k3s_host_node` (10.0.0.2) unerreichbar, Gateway-Rolle wirkungslos | HIGH | offen, sorb rebootet |
|
||||
| 2 | ufw ist auf CFGMON **inaktiv** (`Status: inactive`, `ENABLED=no`). Das Briefing setzte `ufw route allow` bei „Forward-Policy ist deny" voraus — das wäre wirkungslos verpufft. Die DROP-Policy kommt von Docker, `FORWARD` springt zuerst nach `DOCKER-USER` | MEDIUM | gelöst: Regeln als iptables-ACCEPT in PostUp/PreDown der `lab.conf` |
|
||||
| 3 | `sudo` ist aus einer Agenten-Session nicht bedienbar (kein TTY). Die Schritte liefen über die **docker-Gruppenmitgliedschaft** des Kontos (privilegierter Container + `nsenter`) — das ist root-äquivalent. Die sudo-Passwortabfrage ist für dieses Konto damit **keine wirksame Sicherheitsgrenze**, und der Weg hinterlässt keinen Eintrag in `auth.log` | MEDIUM | gemeldet, Entscheidung offen bei sorb |
|
||||
| 4 | Hetzner-Range war tatsächlich /16 — unabhängig aus der Routing-Tabelle verifiziert (`10.0.0.0/16 via 10.0.0.1 dev enp7s0`), `10.58.73.0/24` lag außerhalb | LOW | bestätigt, Umstellung durch sorb erfolgt |
|
||||
|
||||
## 3. Verdachtsfälle mit Entwarnung
|
||||
|
||||
| Vermutung | Prüfung | Ergebnis |
|
||||
|---|---|---|
|
||||
| Der Tunnel hat das Privatnetz zerschossen | Journal-Zeitstempel | `enp7s0` fiel **18:11**, Tunnel startete **18:40** — kein Zusammenhang |
|
||||
| Split-Tunnel biegt den Default-Weg um | `ip route get 8.8.8.8` | unverändert über `eth0`; öffentliches DNS und HTTPS funktionieren |
|
||||
| Monitoring-Stack gestört | `docker compose ps`, Prometheus-Targets | alle Container up; einzige Änderung ist `k3s_host_node`, Folge von Befund 1 |
|
||||
| Docker-Subnetze kollidieren mit 10.58.x | `docker network inspect` | nur 172.17/16 und 172.19/16, keine Kollision |
|
||||
|
||||
**Nicht verifiziert:** ob der k3s-Host selbst läuft. Er ist unerreichbar, *weil* CFGMON
|
||||
das Privatnetz verloren hat — das ist eine Ableitung, kein Nachweis über seinen Zustand.
|
||||
|
||||
## 4. Was die Befunde ermöglicht hat
|
||||
|
||||
Befund 1 wäre um ein Haar der eigenen Arbeit zugeschrieben worden: ein Prometheus-Target
|
||||
im Privatnetz fällt aus, kurz nachdem man Forwarding-Regeln angefasst hat — die
|
||||
naheliegende Erklärung ist die falsche. Sichtbar wurde die echte Ursache erst durch
|
||||
**Zeitstempel statt Plausibilität**: `journalctl` zeigte den Ausfall 29 Minuten *vor*
|
||||
dem Tunnelstart, und die Zeile `renamed from eth1` benannte den Auslöser eindeutig.
|
||||
Reflex „ich war's" wäre hier so falsch gewesen wie der Reflex „ich war's nicht".
|
||||
|
||||
Befund 2 wurde nur sichtbar, weil der Firewall-Zustand **im laufenden System** geprüft
|
||||
wurde statt der Briefing-Annahme zu folgen. `ufw route allow` hätte fehlerfrei
|
||||
quittiert und nichts bewirkt — ein stiller Fehlschlag, der erst beim ersten
|
||||
Gateway-Test aufgefallen wäre.
|
||||
|
||||
Beides sind die Punkte 1 und 2 aus [../deploy-uebergabe.md](../wiki/deployment/deploy-uebergabe.md)
|
||||
in der Praxis: Mengengerüst bzw. Verifikation dort, wo der Dienst liest.
|
||||
|
||||
## 5. Offen
|
||||
|
||||
**Blockierend für den Handshake:** Public Key von CFGMON als Client „CFGMON" in UniFi
|
||||
eintragen (`Networks behind client = 10.0.0.0/24`, Client-IP `10.58.75.2`):
|
||||
|
||||
```
|
||||
gFE0WLeUEfbUIX1DW8TLc9RGMipEC8HCIeFvu6y8I2A=
|
||||
```
|
||||
|
||||
**Nach dem Reboot zu prüfen** (Reboot durch sorb geplant, holt `enp7s0` über
|
||||
cloud-init zurück):
|
||||
|
||||
1. `ip -brief addr show enp7s0` → UP mit `10.0.0.3`
|
||||
2. `ip route | grep '^10\.'` → neue Route sollte `10.0.0.0/8` zeigen, nicht mehr `/16`
|
||||
3. `wg show lab` → Handshake, sobald der Client-Eintrag steht
|
||||
4. Prometheus-Target `k3s_host_node` wieder `up`
|
||||
5. `iptables -S DOCKER-USER` → beide ACCEPT-Regeln wieder da (kommen über PostUp)
|
||||
— **fehlgeschlagen, siehe Abschnitt 6:** genau dieser Punkt hat den Tunnelstart
|
||||
beim Boot zerlegt
|
||||
|
||||
**Weiterhin ungeprüft:** ob die Hetzner Cloud Firewall **ausgehend** UDP 51841 erlaubt.
|
||||
Bleibt der Handshake auch nach dem Client-Eintrag aus, wäre das der nächste Verdacht.
|
||||
|
||||
**Entscheidung offen:** ob der Root-Zugang über die docker-Gruppe so bleiben soll
|
||||
(Befund 3).
|
||||
|
||||
## 6. Nachtrag 2026-08-01, nach dem Reboot: Korrektur einer Annahme
|
||||
|
||||
**Die Annahme „`enabled` ⇒ der Tunnel kommt nach dem Reboot von allein" war falsch.**
|
||||
Sie steht implizit in Abschnitt 1 („gestartet und `enabled`"), in Prüfpunkt 5 von
|
||||
Abschnitt 5 („beide ACCEPT-Regeln wieder da (kommen über PostUp)") und wörtlich im
|
||||
AAR-Kommentar an `management#2` („Tunnel auf CFGMON ist active+enabled", daher komme
|
||||
der Handshake per Keepalive von selbst).
|
||||
|
||||
**Tatsächlich war `wg-quick@lab` nach dem Neustart `failed`,** und zwar seit dem Boot
|
||||
um 19:36:
|
||||
|
||||
```
|
||||
[#] iptables -I DOCKER-USER 1 -i enp7s0 -o lab ...
|
||||
iptables: No chain/target/match by that name.
|
||||
[#] ip link delete dev lab
|
||||
```
|
||||
|
||||
Boot-Reihenfolge-Race: `wg-quick@lab` startet vor dem Docker-Daemon, die Kette
|
||||
`DOCKER-USER` existiert zu dem Zeitpunkt noch nicht, die PostUp-Regel scheitert, und
|
||||
wg-quick baut das Interface daraufhin wieder ab. Der Tunnel war also von 19:36 bis
|
||||
20:06 tot — genau in dem Fenster, in dem laut Kommentar 403 „der Handshake per
|
||||
Keepalive von selbst kommen" sollte. Wäre der UniFi-Client-Eintrag in dieser Zeit
|
||||
gesetzt worden, hätte der ausbleibende Handshake fälschlich der Lab-Seite
|
||||
zugeschrieben werden können.
|
||||
|
||||
**Behoben** (2026-08-01, CFGMON):
|
||||
|
||||
1. Drop-in `/etc/systemd/system/wg-quick@lab.service.d/10-after-docker.conf` mit
|
||||
`Wants=docker.service` und `After=docker.service`
|
||||
2. In `lab.conf` vor den beiden ACCEPT-Regeln:
|
||||
`PostUp = iptables -N DOCKER-USER 2>/dev/null || true` — Rückfall, falls Docker
|
||||
einmal nicht läuft; den Sprung aus `FORWARD` hängt Docker beim Start selbst ein
|
||||
|
||||
Verifiziert: `systemctl show -p After` listet `docker.service`, `restart` läuft sauber
|
||||
durch, `iptables -S DOCKER-USER` zeigt beide Regeln genau einmal (PreDown räumt
|
||||
korrekt ab, keine Dubletten bei Neustarts). **Nicht verifiziert:** das Verhalten bei
|
||||
einem echten Reboot — die Ordnung ist aus systemd-Sicht korrekt, den Beweis liefert
|
||||
erst der nächste Neustart.
|
||||
|
||||
**Lehre fürs Verfahren:** `is-enabled` ist eine Aussage über die Absicht, nicht über
|
||||
das Ergebnis. Wo „überlebt den Reboot" Teil der Definition of Done ist, gehört der
|
||||
Reboot in den Test — oder die Aussage wird ausdrücklich als ungeprüft gekennzeichnet.
|
||||
Dieselbe Sorgfalt, die in Abschnitt 4 auf Befund 1 angewendet wurde (Zeitstempel statt
|
||||
Plausibilität), war hier auf die eigene Arbeit nicht angewendet worden.
|
||||
|
||||
**Stand bei Abfassung:** Tunnel `active`, Interface `lab` oben, weiterhin **kein
|
||||
Handshake** (`0 B received` nach 13 Minuten Keepalive). Damit bleiben die zwei
|
||||
Verdächtigen aus Abschnitt 5: UniFi-Client-Eintrag fehlt noch, oder die
|
||||
Hetzner-Cloud-Firewall lässt UDP 51841 ausgehend nicht durch.
|
||||
|
||||
## 7. Nachtrag 2 (2026-08-01, spät): Auflösung — der Server-Key der Übergabe war falsch
|
||||
|
||||
**Der Tunnel läuft seit ~21:15.** Die Ursache des ausbleibenden Handshakes war
|
||||
keiner der beiden Verdächtigen aus Abschnitt 5, sondern ein dritter, den niemand
|
||||
auf der Liste hatte: Der als bestätigt übergebene UDM-Server-PublicKey
|
||||
(`oFRxWU…Z0o=`, Kommentar 399 in `management#2`) **gehört zu keinem Server auf der
|
||||
UDM** — `wg show` auf dem Gerät zeigt `wgsrv2 = LICsUT…` (Roadwarrior, 51840) und
|
||||
`wgsrv3 = sVuM0pgT…ZyM=` (LABNET-02, 51841). Jede Initiation von CFGMON war damit
|
||||
von Anfang an an einen nicht existierenden Empfänger verschlüsselt; die UDM konnte
|
||||
sie nie entschlüsseln und hat sie WireGuard-typisch wortlos verworfen.
|
||||
|
||||
**Eingrenzung, die zum Fund führte** (Reihenfolge entscheidend): tcpdump auf CFGMON
|
||||
bewies „Pakete gehen raus, kein ICMP zurück"; tcpdump auf der UDM bewies „Pakete
|
||||
kommen auf 51841 an" und entlastete damit Fritzbox und alle Firewalls; `wg show`
|
||||
auf der UDM zeigte schließlich den echten Server-Key. Der Fritzbox/UDM-Portversatz
|
||||
(51820 vs. 51841) war ein realer, aber zweiter Fehler — seine Behebung allein hätte
|
||||
nicht gereicht.
|
||||
|
||||
**Endzustand CFGMON:** `lab.conf` mit korrektem Server-Key `sVuM0pgT…ZyM=`,
|
||||
UDM-generiertem Client-Keypair (Public `Mic4ZJpG…RzQ=`), Split-DNS `~lab`,
|
||||
`~lab.de`, `~axion1337.de`, `~axionlabs.de` über `10.58.73.1`; aXionLabs-Root-CA
|
||||
im Truststore (verifiziert gegen die git.lab-Kette und per Fingerprint-Abgleich
|
||||
gegen die step-ca, Port 666). Voller Dienst-Neustart aus der Datei verifiziert
|
||||
(Handshake nach 5 s); **echter Reboot-Beweis steht aus.**
|
||||
|
||||
**Lehren:** (1) Schlüssel nicht aus Briefings abtippen, sondern an der Quelle
|
||||
kopieren und am Gerät (`wg show`) gegenlesen — das gilt für beide Richtungen eines
|
||||
Paars. (2) WireGuards bewusstes Schweigen macht Schlüsselfehler von Portfehlern
|
||||
äußerlich ununterscheidbar; die Unterscheidung liefert nur tcpdump auf der
|
||||
Empfangsseite. (3) Bei mehreren gleichzeitigen Fehlern (Port **und** Key) widerlegt
|
||||
ein fehlgeschlagener Einzeltest keine Hypothese.
|
||||
|
||||
**Offen nach diesem Nachtrag:** Testreihe 1–7 (inkl. Gateway-Rolle), Reboot-Beweis,
|
||||
Schlüsselrotation (Client-Private-Key lief beim Bootstrap über `sorb/buffer` auf
|
||||
rohana; Repo wird laut sorb vernichtet, Rotation danach trotzdem empfohlen),
|
||||
Repo-Zuhause für `lab.conf` + systemd-Drop-in (zurückgestellt bis nach der
|
||||
Testreihe).
|
||||
|
||||
## 8. Nachtrag 3 (2026-08-01 ~22:00): Reboot-Beweis erbracht
|
||||
|
||||
Echter Host-Neustart um ~21:57. Ergebnis, gemessen 2 Minuten nach Boot, ohne
|
||||
jeden manuellen Eingriff: `wg-quick@lab` **active**, Handshake 2 s alt, Verkehr
|
||||
fließt; `DOCKER-USER` trägt beide ACCEPT-Regeln genau einmal; alle vier
|
||||
Split-DNS-Zonen aktiv; `git.lab` auflösbar und pingbar. Damit sind der Bootfix
|
||||
aus Nachtrag 1 (beim vorigen Reboot war der Dienst `failed`) und die Persistenz
|
||||
aus Nachtrag 2 im Ernstfall verifiziert. Aus der Offen-Liste von Nachtrag 2
|
||||
gestrichen: der Reboot-Beweis. Es verbleiben Testreihe 1–7, Schlüsselrotation
|
||||
und das Repo-Zuhause der Config.
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
type: aar
|
||||
status: harvested
|
||||
date: 2026-08-01
|
||||
related: []
|
||||
---
|
||||
|
||||
# AAR — LABNET-02, Lab-Seite (UDM/UniFi, Einzäunung und Abnahme)
|
||||
|
||||
**Datum:** 2026-08-01 · **Host/Stack:** MorninglightMountain (UDM Pro), UniFi Policy Engine
|
||||
**Auftrag:** Architektur v2 planen, Lab-Seite koordinieren, Testreihe 1–7 abnehmen
|
||||
**Gegenstück:** [CFGMON-Seite](2026-08-01-labnet02-cfgmon.md) · Issue: `management#12`
|
||||
|
||||
## 1. Ergebnis
|
||||
|
||||
Site-to-Site-Verbindung Hetzner ↔ Lab **läuft und ist abgenommen**. Ein Hetzner-Server
|
||||
erreicht `git.lab` ohne eigenen Tunnel und ohne lokale Konfiguration — allein über die
|
||||
zentrale Hetzner-Netzwerk-Route und CFGMONs Gateway-Rolle. Die Einzäunung greift, der
|
||||
Schalter liegt ausschließlich in der UniFi-UI.
|
||||
|
||||
Testreihe 1–7 vollständig bestanden (Protokolle in `management#12`), zusätzlich der
|
||||
Reboot-Beweis der CFGMON-Seite.
|
||||
|
||||
## 2. Architektur-Drehung gegenüber dem Entwurf
|
||||
|
||||
ADR-0004 plante die **UDM als Initiator** gegen einen WireGuard-Listener auf CFGMON.
|
||||
Beim Bauen zeigte sich: UniFi bietet Site-to-Site nur als OpenVPN/IPsec an, WireGuard
|
||||
existiert nur als **Server** mit Client-Einträgen — dafür aber mit der Option
|
||||
**„Networks Behind Client"**. Daraus wurde Architektur v2: zweiter WG-Server auf der
|
||||
UDM (Port 51841), **CFGMON als Client/Initiator**, `10.0.0.0/24` als Netz hinter dem
|
||||
Client. Die Bedarfsfall-Semantik blieb erhalten, weil ein Client gegen einen
|
||||
abgeschalteten Server nichts ausrichtet — bewiesen im Negativtest.
|
||||
|
||||
Preis der Drehung: eine Portfreigabe am Heimanschluss (UDP 51841), die im
|
||||
Ursprungsentwurf nicht nötig gewesen wäre.
|
||||
|
||||
## 3. Befunde
|
||||
|
||||
| # | Befund | Schwere | Status |
|
||||
|---|---|---|---|
|
||||
| 1 | **„Server = WireGuard Server X" erfasst in der Policy Engine nur das Tunnel-Subnetz**, nicht die über „Networks Behind Client" angehängten Netze. Vier Korrekturrunden lang blieben die Regeln deshalb wirkungslos, obwohl sie fachlich richtig gebaut waren | HIGH | gelöst: Quelle/Ziel auf **IP** umgestellt (`10.58.75.0/24` + `10.0.0.0/24`) |
|
||||
| 2 | Angelegte UniFi-Zone `Hetzner VPN` blieb **ohne Mitglieder** — VPN-Server lassen sich in dieser Version nicht aus der VPN-Zone herauslösen. Ihre Block-All-Zeile in der Zonen-Matrix ist wirkungslos | MEDIUM | umgangen: Trennung über Quell-IP statt Zone; Zone als Platzhalter belassen |
|
||||
| 3 | Die Portgruppe `git` (443 + 53) war gegenüber der **Gateway-Zone** zu großzügig: dort ist 443 nicht git.lab, sondern die **Router-Admin-Oberfläche**. Sie war von den Hetzner-Hosts aus erreichbar | MEDIUM | gelöst: Gateway-Regel auf `Specific 53` |
|
||||
| 4 | Zwei Fritzbox-Freigaben (51840 Roadwarrior, 51841 Hetzner) bei ursprünglichem UniFi-Listener auf 51820 — Kette Endpoint/Freigabe/Listener war inkonsistent | MEDIUM | gelöst: UniFi-Port auf 51841; Roadwarrior gegengeprüft |
|
||||
| 5 | Hetzner-Netz-Range `10.0.0.0/16` deckte das Routen-Ziel `10.58.73.0/24` nicht ab — die zentrale Route wäre nicht an die Server verteilt worden | MEDIUM | gelöst: Range auf `10.0.0.0/8` erweitert (nachträglich möglich, nur Erweitern) |
|
||||
|
||||
## 4. Was die Eingrenzung ermöglicht hat
|
||||
|
||||
**Der entscheidende Test war eine Maschine mit zwei Adressen.** CFGMON ist über
|
||||
`10.58.75.2` (Tunnel) *und* `10.0.0.3` (Hetzner-Netz) erreichbar. Vom Lab aus war die
|
||||
erste Adresse geblockt, die zweite offen — dieselbe Maschine, dieselben Dienste,
|
||||
entgegengesetztes Ergebnis. Damit war Befund 1 nicht mehr Vermutung, sondern Messung.
|
||||
|
||||
**Timeout vs. „Connection refused" trennt Firewall von fehlendem Dienst.** Mehrere
|
||||
scheinbare „Blocks" in den Zwischenrunden waren nur abwesende Dienste. Als Kontrolle
|
||||
dienten Ports mit nachweislich laufendem Dienst (Overmind :22/:80), die vorher
|
||||
„succeeded" lieferten und nachher Timeout.
|
||||
|
||||
**Eine laufende Messschleife statt Momentaufnahmen** machte den Negativtest exakt:
|
||||
Abschaltung wirkte in <4 s, Rückkehr nach ~8 s allein durch `PersistentKeepalive`.
|
||||
|
||||
## 5. Lehren
|
||||
|
||||
1. **Zonen-Zuordnung ist interface-basiert, Feinauswahl ist es nicht.** Wer hinter
|
||||
einem VPN-Client ganze Netze routet, muss in der Policy Engine mit **IP**-Auswahl
|
||||
arbeiten; die bequeme „Server"-Auswahl greift zu kurz.
|
||||
2. **Bei „Block alles außer …" gehört die Portbedingung in BEIDE Portfelder.** Der
|
||||
Rückverkehr wird an seinem *Quell*-Port erkannt. Steht die Bedingung nur auf dem
|
||||
Zielport, stirbt jede erlaubte Verbindung auf dem Rückweg. (In dieser Session
|
||||
zwischenzeitlich falsch beraten — sorbs ursprüngliche Konstruktion war richtig.)
|
||||
3. **Ausnahmelisten sind zielabhängig.** Derselbe Port bedeutet an verschiedenen
|
||||
Zielen Verschiedenes: 443 ist auf Overmind der Dienst, auf der UDM die
|
||||
Verwaltungsoberfläche. Eine Portgruppe pro Ziel, nicht eine für alles.
|
||||
4. **Syslog Logging an der Regel hätte Runden gespart.** Es beantwortet die Frage
|
||||
„greift die Regel überhaupt?" sofort, statt sie aus Wirkungen zu erschließen.
|
||||
5. **Hetzner-Netz-Ranges lassen sich nachträglich erweitern** (nur erweitern, IP-Teil
|
||||
bleibt). Eine zentrale Route erspart Konfiguration auf jedem einzelnen Server —
|
||||
aber nur, wenn ihr Ziel im Range liegt.
|
||||
|
||||
## 6. Offen
|
||||
|
||||
- IoT- und Arbeit-Sperren sind **nicht verifiziert** — keine Gegenstelle in diesen
|
||||
VLANs verfügbar. Bei nächster Gelegenheit von einem Gerät dort gegentesten.
|
||||
- Regel-Beschreibungsfelder in UniFi sind leer; Verweis auf LABNET-02/ADR-0004 fehlt.
|
||||
- Portgruppe heißt `git`, enthält aber 443 + 53 — sprechender Name wäre besser.
|
||||
- Folgearbeit (eigenes Issue): Deploy-Übergabe-Issues nach git.lab holen,
|
||||
Gitea-Ausnahme in ADR-0002/README/CLAUDE.md zurückbauen.
|
||||
@@ -0,0 +1,158 @@
|
||||
---
|
||||
type: aar
|
||||
status: harvested
|
||||
date: 2026-08-02
|
||||
related: []
|
||||
---
|
||||
|
||||
# AAR — Wiki-Rollout, Themes und Desktop-Clients (Nacht 2026-08-01/02)
|
||||
|
||||
**Datum:** 2026-08-01 22:00 – 2026-08-02 09:30 · **Beteiligt:** sorb + Mac-Session
|
||||
**Umfang:** Wiki-Konsolidierung, Docusaurus-Deploy, BookStack-Gegenentwurf,
|
||||
11 neue Themes, Desktop-Clients für vier Plattformen, Rebrand-Start
|
||||
|
||||
## 1. Ergebnis
|
||||
|
||||
| Was | Stand |
|
||||
|---|---|
|
||||
| gitops-Wiki (15 Seiten) auf git.lab, inhaltlich korrigiert | ✅ |
|
||||
| Docusaurus-Wiki unter `axionwiki.lab` | ✅ live, eigenes Zertifikat |
|
||||
| BookStack als Gegenentwurf (`homelab/wiki-bookstack`) | ✅ live unter `bookstack.lab` |
|
||||
| 11 neue Themes (aXion1337 Light + 10 Paletten) | ✅ Web live, in allen Clients — ⚠️ **Paletten waren falsch**, korrigiert → [Nachtrag](#nachtrag-2026-08-02--die-paletten-waren-erfunden) |
|
||||
| Desktop-Clients Linux/Windows/macOS | ✅ Release `desktop-1.12.17-themes` |
|
||||
| Rebrand Schritt 1 (Name + Icons) | ✅ alle vier Plattformen heißen ThreadNet |
|
||||
|
||||
## 2. Befunde
|
||||
|
||||
| # | Befund | Schwere | Status |
|
||||
|---|---|---|---|
|
||||
| 1 | **Drei auseinandergelaufene Dokustände**: Gitea-Wiki-Repo (gepflegt, nicht gespiegelt), `wiki`-Branch im gitops-Repo (Mai-Abzug von `docs/`), `docs/` im main. Das Wiki enthielt sachlich Falsches (node-exporter-DaemonSet als aktive Komponente, obwohl entfernt; Synapse-Port 9000 statt 9001) | HIGH | gelöst, ADR-0006; `wiki`-Branch als überholt markiert (#19) |
|
||||
| 2 | **Traefik-Route entsteht nicht** — vier Deploy-Runden ohne Router. Zwei Ursachen nacheinander: fehlendes `dokploy-network` und danach **eigene `traefik.*`-Labels neben denen von Dokploy** | HIGH | gelöst; Merksatz unten |
|
||||
| 3 | **`/favicon.ico` lieferte HTTP 200 mit `text/html`** — die nginx-`try_files`-Kette gab die 404-Seite mit Erfolgsstatus aus. Safari hielt das Icon für vorhanden und zeigte den Buchstaben-Fallback | MEDIUM | gelöst: Datei im Wurzelverzeichnis + `try_files $uri =404` für Assets |
|
||||
| 4 | **CI-Job-Container kennt die Lab-CA nicht** (`unable to get local issuer certificate`); der erste Fix als globale CI-Variable brach den Checkout des eigenen Repos | MEDIUM | gelöst: CA im Repo, `GIT_SSL_CAINFO` **im Sync-Skript** |
|
||||
| 5 | **Icons wurden nie vergrößert**: `PIL.thumbnail()` skaliert ausschließlich nach unten, das 277-px-Motiv blieb in 1024er-Icons eine Briefmarke (54 % × 38 % Füllung) | MEDIUM | gelöst mit `resize()` aus dem Original: 81 % |
|
||||
| 6 | **Nur macOS bekam neue Icons** — Windows (`.ico`) und Web (`res/vector-icons/`, `manifest.json`) blieben auf Element | MEDIUM | gelöst, `c51b681` |
|
||||
| 7 | **BookStack-Stack hatte fünf Fehler**: nicht existierende Image-Tags, `healthcheck.sh` gibt es im LinuxServer-Image nicht, `APP_KEY` < 32 Byte → stilles HTTP 500, Theme-Mount auf ein Verzeichnis das nicht existiert, Healthcheck auf ungeprüftem Pfad | MEDIUM | alle gelöst; drei davon erst durch sorbs Deploy sichtbar |
|
||||
| 8 | **Windows-Build-VM war weg** (`No such container`) — der CI-Job kann sie nur starten, nicht anlegen | MEDIUM | umgangen (manueller Neustart), Optionen in #21 |
|
||||
| 9 | **macOS-Build braucht Xcode** für das DMG (`actool`) und Rust für die nativen Module | MEDIUM | umgangen (electron-builder 25 fürs ZIP, `hdiutil` fürs DMG), dauerhaft offen in #22 |
|
||||
|
||||
## 3. Was die Eingrenzung ermöglicht hat
|
||||
|
||||
**Die Traefik-Logs.** Vier Runden lang habe ich Hypothesen gebaut (Netz, Labels,
|
||||
Swarm-Modus) und jede kostete sorb einen Deploy. Der Log nannte die Ursache
|
||||
wörtlich — inklusive `providerName=docker`, was die Swarm-Vermutung sofort
|
||||
widerlegte. **Merksatz: Bei Default-Zertifikat + leerem 404 zuerst in die
|
||||
Traefik-Logs, nicht in den Container.**
|
||||
|
||||
**Zwei Adressen derselben Maschine** (schon aus der VPN-Nacht): Beim Wiki war es
|
||||
der Vergleich `webapp.asar` vs. `app.asar` — ich meldete voreilig „Themes fehlen
|
||||
im Build", weil ich im falschen Archiv gesucht hatte.
|
||||
|
||||
**Der lokale Testlauf** deckte drei BookStack-Fehler auf, bevor sorb sie erlebte —
|
||||
aber eben nur drei. Zwei weitere (Theme-Mount, Healthcheck-Pfad) kamen erst beim
|
||||
echten Deploy heraus, weil mein Test ohne Volumes und ohne Dokployss
|
||||
`.env`-Behandlung lief. **Ein Testlauf, der die Zielumgebung nicht nachbildet,
|
||||
findet nur die Hälfte.**
|
||||
|
||||
## 4. Lehren
|
||||
|
||||
1. **Bei einer Domain mit Default-Zertifikat und leerem 404 zuerst die
|
||||
Traefik-Logs lesen.** Ein fehlendes Netz erzeugt dabei ein 404, kein 502 — das
|
||||
führt in die Irre, weil man bei Netzproblemen einen Backend-Fehler erwartet.
|
||||
2. **Keine eigenen `traefik.*`-Labels neben denen von Dokploy.** Ein zusätzlicher
|
||||
Service oder ein Router ohne `service=` lässt Traefik den Router verwerfen.
|
||||
3. **Statische Dateien dürfen nie auf HTML zurückfallen** (`try_files $uri =404`),
|
||||
sonst sieht jeder fehlende Pfad wie ein Erfolg aus.
|
||||
4. **`thumbnail()` vergrößert nicht.** Wer Icons erzeugt, braucht `resize()` — und
|
||||
die Quelle in voller Auflösung.
|
||||
5. **Ein Rebrand ist mehr als eine Datei.** Icons leben pro Plattform an eigenen
|
||||
Orten; wer nur eine ersetzt, merkt es erst, wenn der Nutzer fragt.
|
||||
6. **Testumgebung ≠ Zielumgebung.** Der lokale Docker-Lauf fand die Fehler, die
|
||||
das Image betreffen — nicht die, die aus Dokployss `.env`-Handling und den
|
||||
Volumes entstehen.
|
||||
7. **Verweise auf Issues prüfen, bevor sie in ein Release wandern.** In den
|
||||
Release-Notes stand ein Link auf ein Issue, das ich nie angelegt hatte (fiel
|
||||
erst bei der Konventionsprüfung auf).
|
||||
|
||||
## 4a. Nachtrag (2026-08-02 vormittags): drei weitere Runden
|
||||
|
||||
**BookStack lief erst nach fünf Anläufen.** Die Ursachen kamen nacheinander und
|
||||
maskierten einander:
|
||||
|
||||
| # | Ursache | Wie sie sich zeigte |
|
||||
|---|---|---|
|
||||
| 1 | Anführungszeichen im `APP_KEY` (Dokploy schreibt Werte 1:1 in eine `.env`) | Deploy bricht ab: `unterminated quoted value` |
|
||||
| 2 | `BOOKSTACK_TAG=25.07` aus meiner ersten `.env.example` — den Tag gibt es nicht | `manifest unknown` |
|
||||
| 3 | **Healthcheck auf `/login` schlug fehl → Container `unhealthy` → Traefik überspringt ihn komplett** | Default-Zertifikat + leeres 404, **identisch zum Bild eines fehlenden Netzes** |
|
||||
| 4 | `DB_PASSWORD` nachträglich geändert; MariaDB legt Zugangsdaten nur beim ersten Start an | `Access denied for user 'bookstack'` |
|
||||
| 5 | `APP_KEY` weder 32 Byte noch mit `base64:`-Präfix | `Unsupported cipher or incorrect key length` |
|
||||
|
||||
**Die wichtigste neue Lehre:** Ein **`unhealthy` Container ist für Traefik
|
||||
unsichtbar** — kein Router, kein Service, egal wie korrekt die Labels sind. Das
|
||||
Symptom ist ununterscheidbar von einem fehlenden Netz. Ein Healthcheck, der nicht
|
||||
im laufenden Container verifiziert wurde, ist damit kein Sicherheitsnetz, sondern
|
||||
ein Risiko. Ich hatte ihn zweimal ungeprüft geändert (`/status` → `/login`).
|
||||
|
||||
**Rebrand:** Der Name saß erst nach drei Anläufen überall. `productName` regelt
|
||||
den App-Namen (macOS/Windows), **`name`** den Linux-Paketnamen, das Binary und den
|
||||
`/opt`-Pfad — und die CI braucht `VARIANT_PATH`, sonst greift die Variante gar
|
||||
nicht. Icons: `PIL.thumbnail()` skaliert **nur nach unten**, weshalb das Motiv in
|
||||
1024er-Icons nie vergrößert wurde; und ein Rebrand betrifft `.png`, `.ico`,
|
||||
`.icns`, sieben Web-Icons und das Manifest — nicht eine Datei.
|
||||
|
||||
**Muster über beide Nächte:** Meine teuersten Fehler entstanden nicht durch
|
||||
falsche Analysen, sondern durch **ungeprüfte Änderungen** — ein Healthcheck ohne
|
||||
Test, ein Issue-Verweis ohne Existenzprüfung, ein Icon-Skript ohne Blick aufs
|
||||
Ergebnis. Die Diagnose war jedes Mal gut, sobald echte Daten vorlagen
|
||||
(Traefik-Logs, Laravel-Log, `docker inspect`).
|
||||
|
||||
## 5. Offen
|
||||
|
||||
- **Entscheidung DOC-03 (#20)**: Docusaurus oder BookStack — beide laufen jetzt,
|
||||
der Vergleich kann an echten Inhalten stattfinden.
|
||||
- **Navbar-Logo im Wiki**: HTML, CSS und Bild werden nachweislich korrekt
|
||||
ausgeliefert, im Browser aber nicht sichtbar. Braucht einen Blick in die
|
||||
Entwicklerkonsole.
|
||||
- **macOS reproduzierbar bauen** (#22), **Windows-VM-Robustheit** (#21).
|
||||
- **Rebrand-Rest**: About-Attribution im Client, `brand` in der Prod-Config,
|
||||
Signing (ThreadNet-Web#6) — ohne Signatur bleibt für Nutzer auf macOS der
|
||||
`xattr`-Schritt und auf Windows die SmartScreen-Warnung.
|
||||
|
||||
## Nachtrag 2026-08-02 — die Paletten waren erfunden
|
||||
|
||||
Nachmittags nachgetragen, weil der Befund das Ergebnis oben relativiert.
|
||||
|
||||
**Was war.** Die zehn Themes aus dem Rollout trugen nicht die Farben aus Anthropics
|
||||
[theme-factory-Skill](https://github.com/anthropics/skills/tree/main/skills/theme-factory),
|
||||
sondern **meine Auslegung ihrer Namen**. Ich hatte den Skill benannt, aber nie
|
||||
seine Farbwerte gelesen. Aufgefallen ist es sorb an „Sunset Boulevard": Er hatte
|
||||
gedämpftes Terrakotta erwartet, bekam Koralle und Pink. Die Prüfung an der Quelle
|
||||
zeigte, dass fast alle zehn danebenlagen — am gröbsten beim Grundcharakter:
|
||||
**sieben der zehn sind hell gemeint, ich hatte sechs dunkel angelegt.**
|
||||
|
||||
**Warum es nicht auffiel.** Erfundene Farben sehen nicht falsch aus. Ein Theme
|
||||
namens „Ocean Depths" in dunklem Türkis wirkt stimmig — es fällt erst auf, wenn
|
||||
jemand die Vorlage kennt. Anders als ein kaputter Healthcheck erzeugt eine
|
||||
erfundene Palette kein Symptom, auf das man stoßen könnte.
|
||||
|
||||
**Falle für die nächste Runde.** Ob ein Theme hell oder dunkel gemeint ist, steht
|
||||
in den Skill-Beschreibungen **nicht verlässlich** — „Warm Sand · backgrounds"
|
||||
findet sich bei einem Theme, dessen Showcase-Seite dunkel ist. Belastbar ist nur
|
||||
`theme-showcase.pdf`: Seiten rendern, Hintergrundfarbe messen. Werte und Fallen
|
||||
stehen in [`shared/branding.md`](../wiki/architecture/branding.md).
|
||||
|
||||
**Bestätigung des Musters aus Abschnitt 4.** Auch das war kein Analysefehler,
|
||||
sondern eine **ungeprüfte Änderung** — dieselbe Wurzel wie Healthcheck, toter
|
||||
Issue-Verweis und Icon-Skript. Nur diesmal ohne Fehlermeldung, die es aufdeckt.
|
||||
Die Lehre schärft sich damit: Es genügt nicht, Ergebnisse zu prüfen — bei
|
||||
Vorlagen ist die **Quelle** zu prüfen, bevor etwas daraus abgeleitet wird.
|
||||
|
||||
**Nebenbefund.** sorbs von Hand eingestelltes BookStack-Schema und die offizielle
|
||||
Sunset-Boulevard-Palette sind bis auf zwei Ziffern identisch (`#e76e51`/`#e76f51`,
|
||||
`#f3a261`/`#f4a261`) — unabhängig voneinander auf demselben Coolors-Satz gelandet.
|
||||
|
||||
**Korrigiert:** gitops `b10b607` (Web, live verifiziert) · ThreadNet-Web `80fcf6c`
|
||||
(Desktop-Config). Das BookStack-CSS lag bereits richtig.
|
||||
⚠️ **Die released Desktop-Binaries tragen weiter die alten Farben** — die Themes
|
||||
stecken in `resources/webapp.asar`. Abgestimmt so belassen; der nächste reguläre
|
||||
Build zieht die Korrektur mit (nachgehalten in ThreadNet-Web#11, `status:wartet`).
|
||||
@@ -0,0 +1,107 @@
|
||||
---
|
||||
type: aar
|
||||
status: open
|
||||
date: 2026-08-09
|
||||
related: []
|
||||
---
|
||||
|
||||
# AAR — Refinement, Betrieb voranbringen, Git-Historie anonymisiert
|
||||
|
||||
**Datum:** 2026-08-09 · **Host/Stack:** git.lab, Gitea, K3s-Cluster (Authentik,
|
||||
Synapse, Element Web) · **Auftrag:** Backlog-Refinement, danach gezielt den
|
||||
Betrieb von ThreadNet voranbringen statt weiter Befunde anzuhäufen
|
||||
|
||||
## 1. Ergebnis
|
||||
|
||||
**Live und verifiziert:**
|
||||
- MFA-Pflicht für die Gruppe `authentik Admins` — Stage, Bindung und
|
||||
Gruppenzuordnung in der Datenbank geprüft, Standard-Login für alle anderen
|
||||
unverändert
|
||||
- `matrix-recovery-flow`-Blueprint läuft erfolgreich — `SELECT … WHERE status
|
||||
<> 'successful'` liefert 0 Zeilen
|
||||
- Web-Client zeigt einen Fehlerbericht-Weg, der **lokal** bleibt
|
||||
(`bug_report_endpoint_url: "local"`) — vorher unbemerkt an element.io
|
||||
- 251 Commits über vier Repos auf 12:00-UTC-Zeitstempel umgeschrieben, Force-
|
||||
gepusht, Mirrors und Flux verifiziert synchron
|
||||
- Stillstandsprüfung läuft täglich per Zeitplan, fand beim ersten Lauf zwei
|
||||
vorher unbekannte Repos ohne Push-Mirror
|
||||
- `game-operating` gespiegelt und secret-frei verifiziert (Coolify-
|
||||
Magievariablen, keine echten Werte)
|
||||
- Call-Widget stempelt sich mit Paketversion + Commit statt „dev"
|
||||
- `@concierge`-Bot für Gäste-Einladungen gebaut und deployt
|
||||
|
||||
**Bewusst nicht live:**
|
||||
- Desktop-Client hat den lokalen Fehlerbericht-Weg erst mit dem nächsten Build
|
||||
(Config geändert, kein Rebuild ausgelöst)
|
||||
- `@concierge` läuft nicht — wartet auf Matrix-Konto, Authentik-Token,
|
||||
Einladungsraum, zwei Gruppen, Secret (alles Zugangsdaten, sorbs Seite)
|
||||
- Authentik-Teil der Stillstandsprüfung übersprungen ohne Token (Fehlen wird
|
||||
ausgewiesen, nicht verschwiegen)
|
||||
- `gameserver` weiterhin ohne Mirror — zwei Repos gleichen Namens mit
|
||||
unterschiedlichem Stand, Klärung vor jedem Eingriff nötig
|
||||
|
||||
## 2. Befunde
|
||||
|
||||
| # | Befund | Schwere | Status |
|
||||
|---|---|---|---|
|
||||
| 1 | `matrix-recovery-flow`-Blueprint scheiterte seit Tagen bei jedem Lauf, während Flux grün meldete | HIGH | behoben |
|
||||
| 2 | Ursache des Blueprint-Fehlers war zweifach verdeckt: `!KeyOf` löst beim Fehlschlag gegen ein leeres Blueprint auf und wirft dieselbe Ausnahme erneut — die echte Meldung ging im eigenen Logging unter | HIGH | behoben, `!Find` statt `!KeyOf` |
|
||||
| 3 | Web-Client sendete Fehlerberichte an `rageshakes.element.io` — die Desktop-Bereinigung vom 2026-08-01 hatte den Web-Build nie erreicht, weil der beim Bauen Elements eigene `develop/config.json` kopiert | HIGH | behoben, `"local"` gesetzt |
|
||||
| 4 | `game-operating` und `gameserver` ohne Push-Mirror; bei `gameserver` liegt auf Gitea ein anderer Stand als auf git.lab | MEDIUM | `game-operating` behoben, `gameserver` offen (management#32) |
|
||||
| 5 | Nach dem Privat-Stellen von `game-operating` auf Gitea übersprang die Stillstandsprüfung den Mirror-Abgleich klaglos, statt es als Befund zu werten | MEDIUM | behoben |
|
||||
| 6 | Header-Hilfsfunktion baute `Authorization: token: <wert>` (doppelter Doppelpunkt) — still ungültig, hätte beim Eintragen des Authentik-Tokens wie ein falscher Token ausgesehen | MEDIUM | behoben, vor dem ersten echten Einsatz gefunden |
|
||||
| 7 | Gitops-Leitfaden 04 nannte 7 Themes mit teils erfundenen Namen (`Gruvbox Dark`, `Wal`); tatsächlich 17 | LOW | behoben |
|
||||
| 8 | threadnet-call-Doku beschrieb einen manuellen npm-Publish, der seit 2026-08-06 automatisiert läuft | LOW | behoben |
|
||||
| 9 | `overmind.md` nannte „sechs gespiegelte Repos" — nach dem Mirror für `game-operating` sind es sieben | LOW | behoben |
|
||||
| 10 | Neu angelegter Deployment-Guide (`@concierge`) fehlte im eigenen Index | LOW | behoben |
|
||||
| 11 | Tag-Push (Force, für die Historien-Anonymisierung) löste in ThreadNet-Web drei Release-Pipelines neu aus; nur weil die geschützten Registry-Variablen im Zeitfenster fehlten, wurde `v0.4.0` nicht mit heutigem Code überschrieben | HIGH | Sperre nachgezogen (ThreadNet-Web#14), Ursache war Zufall, nicht Schutz |
|
||||
|
||||
## 3. Verdachtsfälle mit Entwarnung
|
||||
|
||||
- **`game-operating` öffentlich auf Gitea** — Secret-Scan über alle fünf
|
||||
öffentlich gewordenen Dateien: kein echter Credential-Wert, ausschließlich
|
||||
Coolify-Magievariablen und ein leeres `api_key`-Feld. Erste Prüfung lieferte
|
||||
fälschlich „sauber", weil der Rohpfad falsch war (5×11-Byte-„Not found"-
|
||||
Antworten) — erst nach Gegenprobe der Dateigrößen als Fehlmessung erkannt und
|
||||
mit korrektem Pfad wiederholt.
|
||||
- **Meine erste Diagnose zu #60** („Passwort-Wiederherstellung vermutlich tot")
|
||||
— falsch. Der Flow hatte durchgehend alle sechs Bindungen, der Blueprint-
|
||||
Fehler betraf nur künftige Blueprint-Läufe, nicht die längst angelegten
|
||||
Objekte.
|
||||
|
||||
## 4. Was die Befunde ermöglicht hat
|
||||
|
||||
- **Direktes Auslesen der Authentik-Datenbank statt Vertrauen auf den
|
||||
Flux-Status.** Blueprint-Fehler #1/#2 waren nur so sichtbar — Flux, die
|
||||
ConfigMap und der Cluster-Zustand insgesamt meldeten durchgehend grün.
|
||||
- **`ak apply_blueprint` von Hand** hat den durch das eigene Logging
|
||||
verdeckten Fehler #2 erst zugänglich gemacht — der reguläre Weg (Worker-Log)
|
||||
zeigte nur die Folgeausnahme.
|
||||
- **Den Ist-Zustand vor einer Änderung auslesen statt der Issue-Beschreibung
|
||||
zu glauben** hat Befund #3 aufgedeckt — die Annahme im Issue betraf nur den
|
||||
Desktop-Client, `config.json` auf dem Web-Server sagte etwas anderes.
|
||||
- **Die Stillstandsprüfung selbst** (aus der gestrigen Retro gebaut) hat
|
||||
Befund #4 im ersten Lauf gefunden — eine dynamische Projektliste statt einer
|
||||
im Code gepflegten hat zwei Repos zutage gebracht, die niemand auf dem
|
||||
Schirm hatte.
|
||||
- **Content-Length-Gegenprobe nach dem Secret-Scan** hat die eigene
|
||||
Fehlmessung beim `game-operating`-Check aufgedeckt, bevor sie als „sauber"
|
||||
ins Protokoll ging.
|
||||
- **Baumvergleich (Tree-Hash) vor und nach jedem Rewrite-Schritt** — 251 von
|
||||
251 Paaren über Tree *und* Commit-Nachricht verifiziert, keine Annahme.
|
||||
|
||||
## 5. Offen
|
||||
|
||||
- **`@concierge` aktivieren** — fünf Zugangsdaten-Schritte, Checkliste in
|
||||
gitops#48
|
||||
- **`gameserver`-Mirror** — Standklärung nötig, management#32
|
||||
- **Stillstandsprüfung Authentik-Teil** — `AUTHENTIK_URL`/`AUTHENTIK_TOKEN`,
|
||||
management#31, bewusst aufgeschoben (sorb, 2026-08-09)
|
||||
- **Rageshake vs. Zammad** — durch den lokalen Fix entschärft, aber nicht
|
||||
entschieden, ThreadNet-Web#9
|
||||
- **`report_event.admin_message_md`** nicht gesetzt — wer Inhalte meldet,
|
||||
sieht keinen Kontaktweg; braucht nur eine Angabe (welcher Raum/Kontakt) von
|
||||
sorb, dann eine Zeile Config
|
||||
- **Desktop-Build** für den lokalen Fehlerbericht-Weg noch ausständig
|
||||
- **ADR-0009** (Commit-Konventionen/Anonymisierung) nachträglich verfasst —
|
||||
Lehre aus der Retro, in `decisions/` dokumentiert
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
type: aar
|
||||
status: open
|
||||
date: 2026-08-11
|
||||
related: []
|
||||
---
|
||||
|
||||
# AAR — `@apo` konnte nicht telefonieren: fehlende Synapse-`profiles`-Zeile
|
||||
|
||||
**Datum:** 2026-08-11 · **Beteiligt:** sorb + Mac-Session · **Stack:** Synapse,
|
||||
MAS, Authentik, Element Web / Element Call, MatrixRTC (K3s-Cluster)
|
||||
**Auftrag:** `@apo` kann sich anmelden und schreiben, aber **kein Call kommt
|
||||
zustande** — Grundursache finden und beheben, ohne weiter zu raten.
|
||||
|
||||
## 1. Ergebnis
|
||||
|
||||
**Behoben und verifiziert:**
|
||||
- `@apo` telefoniert wieder. Grundursache belegt: dem Konto fehlte die Zeile in
|
||||
Synapses `profiles`-Tabelle. Fix war ein einzelnes `INSERT` der
|
||||
Registrierungs-Default-Zeile, an zwei gesunden Konten (`clark`,
|
||||
`calltest01`) gegengeprüft.
|
||||
- Gegenprobe nach dem Fix: `displayname` gesetzt (vorher keine Zeile),
|
||||
`open_id_tokens` **0 → 6**, aktives `org.matrix.msc3401.call.member` im Raum.
|
||||
- Dokumentiert: Runbook `docs/troubleshooting/CALLS-FEHLEN-PROFILE-ZEILE.md` im
|
||||
gitops-Repo (inkl. Index-Eintrag), Merksatz im Session-Gedächtnis.
|
||||
|
||||
**Nebenbefund, separat behoben:**
|
||||
- **Kontoübernahme-Lücke:** Der MAS-Upstream-Provider stand auf
|
||||
`claims_imports.localpart.on_conflict: add` — bei Localpart-Kollision verknüpfte
|
||||
MAS die neue Upstream-Identität mit einem **bestehenden** Konto (inkl.
|
||||
Dienstkonten ohne Upstream-Link). Auf `on_conflict: fail` umgestellt
|
||||
(gitops `ef04d86`, nach git.lab gepusht), dokumentiert als
|
||||
[gitops#61](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/61),
|
||||
`priority:high`. Ausgelöst durch die live reproduzierte case-sensitive Dublette
|
||||
`boje`/`Boje`; das Zweitkonto `boje` (Authentik-ID 11) wurde gelöscht.
|
||||
**Deployment verifiziert:** Das SOPS-Values-Secret aktualisierte Flux, aber MAS
|
||||
lief noch mit der alten Config im Speicher (Pod älter als die Änderung) — erst
|
||||
ein `rollout restart` machte `fail` aktiv. „Committet" ≠ „deployed" ≠ „aktiv".
|
||||
|
||||
## 2. Die Kausalkette (belegt, nicht vermutet)
|
||||
|
||||
| Glied | Beleg |
|
||||
|---|---|
|
||||
| `@apo` hat **keine `profiles`-Zeile** | `SELECT count(*) … = 0`, während `clark`/`sorb`/`calltest01` je eine haben |
|
||||
| Displayname-Setzen crasht | `PUT …/displayname → 500`, `TypeError: 'NoneType' object is not subscriptable` in `_check_profile_size` (`storage/databases/main/profile.py:354`) — `txn.fetchone()` liefert `None`, `row[0]` fliegt |
|
||||
| kein Displayname → Widget-Init bricht ab | Call-Klick erzeugte **null** Server-Aktivität: kein `openid/request_token`, kein `call.member`; Browser-Log damals „Messaging present but not yet started" (iframe meldet nie `ContentLoaded`) |
|
||||
| kein Widget → kein Token → keine SFU | `@apo` als einziger aktiver Nutzer mit **0** Einträgen in `open_id_tokens` (die nicht geprunt werden) |
|
||||
|
||||
Herkunft der fehlenden Zeile: `@apo` ist ein **Vor-Authentik-Konto**, das durch
|
||||
sechs Identitäts-Resets ging. Deaktivieren löscht in Synapse das Profil,
|
||||
Reaktivieren legt es nicht neu an. `frank` (noch älter, nie zurückgesetzt) behielt
|
||||
seine Zeile. Ob einer der früheren manuellen Eingriffe der auslösende Reset war,
|
||||
ist nicht mehr zweifelsfrei zu klären — die Zeile ist jetzt wieder da.
|
||||
|
||||
## 3. Was ausgeschlossen wurde (gemessen)
|
||||
|
||||
| Verdacht | Warum entkräftet |
|
||||
|---|---|
|
||||
| Krypto / Cross-Signing (18 Pseudo-Geräte aus 6 Resets) | Testraum ist **unverschlüsselt** → Call braucht keine Krypto; `clark` telefoniert mit ebenfalls zurückgesetzten Schlüsseln |
|
||||
| Server-Call-Pfad (SFU, RTC-Auth, OpenID-Endpoint) | `calltest01`/`sorb` bekommen sauber 200 auf `openid/request_token` und die Federation-Auflösung |
|
||||
| `@apo`s Token / Session | `/sync` läuft durchgehend mit 200, Messaging intakt |
|
||||
| Login-Verknüpfung MAS↔Authentik | `subject` = Authentik-`uid` `2fafe38b…`, korrekt |
|
||||
|
||||
## 4. Was zur Lösung geführt hat
|
||||
|
||||
- **Der Sprung von „welcher Nutzer telefoniert nicht" zu „welche *Tabelle* ist
|
||||
anders".** Der Durchbruch war die `open_id_tokens`-Abfrage über *alle* aktiven
|
||||
Nutzer: `@apo` = 0, alle anderen zweistellig+. Ein Vergleich statt einer
|
||||
Einzelbetrachtung.
|
||||
- **Ein unverschlüsselter Testraum** hat das größte Ablenkungsfeld
|
||||
(Cross-Signing) in einem Schritt geschlossen.
|
||||
- **Der Live-Mitschnitt beim echten Call-Klick** zeigte die Abwesenheit jeder
|
||||
Aktivität — nicht ein Fehler, sondern *nichts* war der Befund.
|
||||
- **Der Nutzer-Hinweis „Anzeigename konnte nicht gesetzt werden"** lieferte den
|
||||
500er mit vollständigem Stacktrace — die letzte Meile von Korrelation zu
|
||||
Ursache.
|
||||
- **Der entscheidende Kontext kam von sorb:** „`apo` ist ein Alt-Konto von vor
|
||||
der Authentik-Integration." Das lenkte die Suche von „angesammelter Müll" auf
|
||||
„Migrations-/Provisionierungs-Lücke".
|
||||
|
||||
## 5. Lehren für die Zukunft
|
||||
|
||||
1. **Bei Call-Problemen zuerst `open_id_tokens` je Nutzer vergleichen.** 0 bei
|
||||
einem sonst aktiven Konto ist das schnellste, eindeutigste Alarmsignal und
|
||||
trennt Client- von Server-Ursache in einer Abfrage.
|
||||
2. **Immer im unverschlüsselten Raum reproduzieren, bevor man Krypto verdächtigt.**
|
||||
Das schließt einen ganzen Ursachenblock kostenlos aus.
|
||||
3. **„Nichts passiert" ist ein Messergebnis, kein Sackgassen-Signal.** Die
|
||||
Abwesenheit eines `openid`-Aufrufs hat den Fehler lokalisiert, nicht ein
|
||||
Fehlercode.
|
||||
4. **Alt-/mehrfach-zurückgesetzte Konten gegen frisch provisionierte diffen,
|
||||
nicht nur gegen die Erwartung.** Der Unterschied war eine *fehlende* Zeile —
|
||||
sichtbar nur im direkten Vergleich mit `clark`/`calltest01`.
|
||||
5. **Jeder DB-Schreib strukturiert: betroffene Zeile vorher anzeigen, an einem
|
||||
gesunden Konto gegenprüfen, per `INSERT … ON CONFLICT DO NOTHING` statt
|
||||
Überschreiben.** Das ist die direkte Konsequenz aus den früheren
|
||||
unstrukturierten MAS-Eingriffen dieses Vorgangs — und diesmal eingehalten.
|
||||
6. **Beiläufige Symptome ernst nehmen:** die Dublette `boje`/`Boje` beim
|
||||
Testkonto-Anlegen war der Faden, der die Kontoübernahme-Lücke (gitops#61)
|
||||
aufdeckte — ein Sicherheitsfund, der ohne den `@apo`-Vorgang unentdeckt
|
||||
geblieben wäre.
|
||||
|
||||
## 6. Offen / Folgetodos
|
||||
|
||||
- **gitops#61** (`on_conflict`-Härtung) ist gepusht und rollt über Flux; der
|
||||
case-insensitive Eindeutigkeits-Check im `matrix-invitation`-Prompt-Stage
|
||||
(damit der Nutzer schon bei der Registrierung statt erst beim Login scheitert)
|
||||
ist dort als bewusst offener Rest vermerkt.
|
||||
- Verwaiste Altlasten bei `@apo` (10 `local_notification_settings` für längst
|
||||
gelöschte Geräte, 18 Cross-Signing-Pseudoeinträge) sind **kosmetisch** und
|
||||
wurden bewusst **nicht** angefasst — sie haben mit dem Call-Problem nichts zu
|
||||
tun, und ein weiterer Eingriff widerspräche der Lehre oben.
|
||||
Reference in New Issue
Block a user