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:
Thore Cimbal
2026-08-11 12:00:00 +00:00
co-authored by Claude Fable 5
parent 70e81e2ff1
commit 92b448fe30
37 changed files with 424 additions and 120 deletions
+305
View File
@@ -0,0 +1,305 @@
---
type: wiki-page
area: admin
related: []
---
# CFGMON
Monitoring-Stack, Gitea und der Reverse Proxy für alles Öffentliche.
| | |
|---|---|
| **Hostname** | `CFGMON` |
| **OS** | Ubuntu 24.04.4 LTS |
| **IPv4** | `188.245.193.243` |
| **IPv6** | `2a01:4f8:c17:93eb::1` |
| **Privat** | `10.0.0.3` (`enp7s0`, Hetzner-Netz — dort liegt auch k3s auf `10.0.0.2`) |
| **DNS** | `rohana.axion1337.de` → Gitea, `selendis.axion1337.de` → Grafana |
| **Stand** | 2026-07-30 |
## Dienste
| Container | Image | Compose-Projekt | Definition |
|---|---|---|---|
| prometheus | `prom/prometheus:v3.3.1` | `monitoring` | `sorb/threadnet-operating`, `monitoring/` |
| loki | `grafana/loki:3.7.1` | `monitoring` | dito |
| grafana | `grafana/grafana:12.0.0` | `monitoring` | dito |
| alloy | `grafana/alloy:v1.16.0` | `monitoring` | dito |
| node-exporter | `prom/node-exporter:v1.9.1` | `monitoring` | dito |
| traefik | `traefik:v3.7.9` | `thread-net-git` | `sorb/thread-net-git`, seit 2026-07-30 in `main` (siehe [CFGMON-02](#cfgmon-02--traefik-gitea-cadvisor-und-runner-unter-iac-gebracht--erledigt-2026-07-30)) |
| gitea | `gitea/gitea:1.27.0` | `thread-net-git` | dito, gepinnt (war `:latest`) |
| cadvisor | `gcr.io/cadvisor/cadvisor:v0.49.1` | `thread-net-git` | dito, gepinnt (war `:latest`) |
| runner | `gitea/act_runner:0.6.1` | `thread-net-git` | dito, Container `gitea-runner`, siehe CFGMON-02 |
| portainer_agent | `portainer/agent:2.27.5` | — | standalone, kein Compose |
Prometheus-Jobs: `operating_prometheus`, `operating_node-exporter`,
`operating_cadvisor`, `operating_traefik` (alle lokal), `k3s_host_node`
(`10.0.0.2:9100`), `pterodactyl_host_node` und `gameserver_cadvisor`
(beide `157.90.155.206`, siehe [game](game.md)).
## Offene Punkte → git.lab-Issues
Seit dem Framework-Umbau (2026-08-01) leben offene Punkte als Issues im
[management-Projekt](https://git.lab/axion1337.chat/management/-/issues); die IDs bleiben in den Issue-Titeln erhalten.
Dieses File hält nur noch Bestand und Historie.
- [CFGMON-01 — Zertifikatserneuerung braucht offene Ports (zeitkritisch ab 2026-09-28)](https://git.lab/axion1337.chat/management/-/issues/7)
- [CFGMON-03 — Prometheus-Remote-Write/Loki öffentlich ohne Auth (Weg A, nachgelagerte Prüfung)](https://git.lab/axion1337.chat/management/-/issues/8)
- [CFGMON-04 — Grafana-Admin-Credentials aus `.env` gelten nicht für die API](https://git.lab/axion1337.chat/management/-/issues/9)
- [CFGMON-09 — Gitea-Backups off-host (⚠️ Backup-Cron deaktiviert)](https://git.lab/axion1337.chat/management/-/issues/10)
## CFGMON-11 — Gitea-CI-Rückbau nach GitLab-Umzug
**Status:** erledigt (2026-07-31 spätabends) — bis auf einen kosmetischen Handgriff:
auf CFGMON `cd /opt/thread-net-git && git checkout main && git pull` (Checkout parkt
noch auf dem inhaltsgleichen, inzwischen gelöschten Fix-Branch).
**Dazu neu (2026-08-01 ~05:00):** Auch `/opt/threadnet-operating` braucht einmal
`git fetch && git reset --hard origin/main` — der State-Persistenz-Commit wurde
dort direkt nach Gitea gepusht (dfe04c4a), vom Mirror überschrieben, vom Mac aus
per Patch gerettet und kanonisch als `6ffab68` neu aufgelegt (inhaltsgleich,
anderer Hash; Autorschaft erhalten).
**Erledigt (2026-08-01, autonom):**
- Actions-Toggles deaktiviert: `ThreadNet-Web`, `threadnet-call`, `axion1337.chat-gitops`
- `ThreadNet-Web`: alle `.github/workflows/`-Dateien entfernt (Commit `a876758`)
- gitops: Verifikations-Job nach GitLab portiert + `.gitea/workflows/` entfernt
(Commit `5e46a24`, Pipeline grün, Mirror→Gitea verifiziert; `milestone-release.yml`
war toter Code, siehe #33). Flux unberührt.
- `thread-net-git`: Runner-Service/Config/`.env.example` per Commit `d904734` entfernt
(auf git.lab; Mirror trägt nach Gitea) — **noch nicht deployt**, siehe unten.
- Registry-Entscheidung npm final (Evidenz: `@sorb/threadnet-call-embedded` ist
pnpm-Dependency von `apps/web`, Lockfile pinnt Tarball-URL auf rohana): **bleibt Gitea**.
**Verbleibende manuelle Schritte (User):**
1. ~~`thread-net-git`-Stand deployen~~ **erledigt (2026-07-31 spätabends, via
CFGMON-Session)**: Runner-Container/Netz/`runner-data/`/`.env`-Zeile entfernt,
`builder-1` aus der Gitea-Admin-UI gelöscht, Actions-Registrierungstoken rotiert.
Stolperstein dabei: Rückbau-Commit `d904734` hinterließ ein verwaistes
`networks:`-Fragment (YAML invalide) — Fix nahm den kanonischen Weg
Mac→git.lab→Mirror (`15c8f2d`), Hergang in thread-net-git#1 (geschlossen).
2. ~~Token-Rotation a~~ **geklärt (2026-07-31 abends)**: der versehentlich in die VM
getippte Token (`a89bfb…`) war der Gitea-**Actions-Runner-Registrierungstoken**
(taucht nicht unter Access Tokens auf) — kein Access-Token-Risiko; wird mit
Schritt 1 obsolet. Nach dem Runner-Löschen in der Admin-UI den
Registrierungstoken dort einmal neu generieren (ein Klick), dann ist auch der
exponierte Wert wertlos.
3. ~~Token-Rotation b~~ **erledigt (2026-07-31 abends)**: Generalschlüssel
(`Projectplaning and implemantation axion`, war npm-Publish UND Issue-Verwaltung,
Klartext in `.npmrc` + Scratchpad + macOS-Schlüsselbund) gelöscht und durch drei
least-privilege-Tokens ersetzt: `gitlab-ci-npm` (write:package, als maskierte
GitLab-Gruppen-Variable `GITEA_NPM_TOKEN`), `claude-issues` (write:issue,
`~/.config/gitea-rohana/token` auf dem Mac), `claude-push` (write:repository,
`~/.config/gitea-rohana/push-token`). Erster CI-Publish `0.19.2-threadnet.6`
verifiziert → threadnet-call#1 geschlossen. Alle Klartext-Reste entfernt
(.npmrc, Skript, Schlüsselbund-Eintrag). Zusätzlich aufgeräumt: ungenutzte
Alt-Tokens `claude-push-20260730`, `claude-monitoring-rework-20260730` gelöscht.
Entscheidung vom 2026-07-30: Build-CI/CD zieht ins Homelab-GitLab
(`git.lab/axion1337.chat`, Gruppe mit importierten Projekten angelegt; die Domain ist
**nur im Homelab auflösbar** — von CFGMON/MATRIX aus nicht erreichbar, was für den
Build-Anwendungsfall in Ordnung ist: Prod läuft bei Lab-Ausfall weiter, nur neue Builds
pausieren). Der am 2026-07-30 auf Gitea-Seite aufgebaute CI-Unterbau wird damit teilweise
überflüssig. **Erst zurückbauen, wenn die GitLab-CI nachweislich läuft.**
### Sicher rückbaubar
- **Actions-Toggle** `has_actions` bei `ThreadNet-Web` (am 2026-07-30 per API aktiviert)
wieder deaktivieren, ebenso bei `threadnet-call` (stoppt die fehlschlagende
Workflow-Kaskade dort).
- **`.github/workflows/` in `ThreadNet-Web`** (der kuratierte 6-Dateien-Satz) — wird durch
`.gitlab-ci.yml` ersetzt. Die Erkenntnisse aus den Läufen vom 2026-07-30 mitnehmen:
kein `layered.sh` (würde den js-sdk-Pin mit Upstream-develop überschreiben, braucht
außerdem jq), stattdessen `pnpm install --frozen-lockfile`; webpack-Build braucht
real ~4 GB Heap; `contains(needs.*.result, ...)`-Gates sind act-spezifisch kaputt.
- **Geerbte Upstream-Workflows in `threadnet-call`** (build/publish/test/translations/
zizmor/pr-deploy) — gleiches Schicksal.
- **Runner-Identität**: wenn der Runner-Service entfällt (siehe unten), `builder-1` in
der Gitea-Admin-UI deregistrieren und `runner-data/.runner` auf dem Host entfernen.
- **Token: npm-Token in `threadnet-call`s untracked `embedded/web/.npmrc`** (Klartext im
Arbeitsverzeichnis, Fund vom 2026-07-30): revoken/rotieren. Der GitLab-CI-Publish
bekommt einen eigenen, frisch erzeugten Token als GitLab-CI-Variable — der alte
verschwindet von der Platte.
### Entscheidungsabhängig
- **Runner-Service in `thread-net-git` ganz entfernen?** Hängt daran, ob das gitops-Repo
seinen leichten `deploy-on-push.yml` (YAML-Validierung/Notification, läuft sauber)
behält — dann bleibt ein Minimal-Runner nötig. Bei Komplett-Entfernung als
Revert-Commit in `thread-net-git`: Compose-Service `runner`, `runner/config.yaml`,
`.env.example` (RUNNER_TOKEN), Cache-Port-Bindung 8088, `runner-data/`.
- **Registry-Ziel für `@sorb/threadnet-call-embedded`**: bleibt die Gitea-npm-Registry
(dann braucht GitLab-CI einen Push-Token dorthin — Neuanlage) oder wandert in die
GitLab-Package-Registry (dann läuft die Gitea-Package-Seite leer).
- **Container-Images bleiben in der rohana-Registry** (Flux/k8s pullt von dort — spricht
stark für Beibehalt). Gegenstück zur Token-Bilanz: GitLab-CI braucht dann einen
**neuen** Deploy-/Push-Token für die rohana-Registry (Neuanlage, kein Rückbau).
### Explizit nicht rückbaubar
Gitea selbst, gitops-Repo als Flux-Source, Issues/Wiki/dieses Repo, der
API-Token für Issue-Verwaltung, das Gitea-Backup-Script (CFGMON-09).
*(Stand der Analyse 2026-07-31. Issues und dieses Repo sind seitdem doch
umgezogen — [ADR-0002](../../adr/0002-issues-und-management-ins-lab.md) —,
das Repo dabei von `Backlogs` zu `management` umgewidmet
[ADR-0005](../../adr/0005-pm-framework-kanban.md). „Nicht rückbaubar" galt für
den damaligen Rückbau der Gitea-CI, nicht auf Dauer.)*
Betroffene Issues (werden bei der GitLab-Migrations-Planung umformuliert):
`ThreadNet-Web#2` (Gitea-Zählung, Tracker stillgelegt — verbindlich: Migrations-Fußtext im GitLab-Issue),
`threadnet-call#1` (Gitea-Zählung, Tracker stillgelegt).
**Nächster Schritt:** die drei manuellen Schritte oben, dann → erledigt.
## CFGMON-13 — Absender-Design für Release-/CVE-Meldungen: eigener Bot?
**Status:** entschieden (2026-08-01, sorb) — **gleicher Bot (`@alerts`), eigener Raum**
`!YRJvcEbVXtRlUIkNld:axion1337.chat`. Umsetzungsplan inkl. CVE-Metriken/Grafana/
Alertmanager-Routing: [gitops#45 auf git.lab](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/45) (ehemals Gitea-gitops#47, Tracker stillgelegt).
release-watch ist bereits auf den Raum vorbereitet (Env `MATRIX_RELEASE_ROOM_ID`,
Fallback Alerts-Raum). ⬜ Rest: `@alerts` in den Raum **einladen** (Join wurde als
restricted abgelehnt — sorb), dann Deploy.
Zwei neue Meldequellen entstehen gerade neben dem klassischen Alerting:
1. **release-watch** (gitops#22, deploybereit): Upstream-Releases/Security-Releases
→ aktuell als Notiz über den `@alerts`-Bot in den Alerts-Raum
2. **Trivy-CVE-Scans** (gitops#31, läuft wöchentlich in der Lab-CI): Funde landen
bisher NUR als Job-Artifact/-Log — keine aktive Benachrichtigung
**Frage:** Sollen diese "Informations-Meldungen" (Releases, CVE-Reports) einen
**eigenen Bot** bekommen (z. B. `@releases:axion1337.chat`, ggf. eigener Raum),
damit `@alerts` ausschließlich für echte Betriebsalarme steht und separat
scharf/stumm schaltbar bleibt? Oder bewusst alles über `@alerts` bündeln?
Bei Entscheidung "eigener Bot": Anlage per mas-cli wie gehabt, release-watch-Env
umziehen, Trivy-Anbindung (CI-Job → Matrix-Notiz bei Funden) gleich mit auf den
neuen Absender bauen.
## CFGMON-12 — Gitea-Projektmetadaten nach GitLab umziehen/integrieren
**Status:** abgelöst durch [gitops#46 auf git.lab](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/46) (ehemals Gitea-gitops#48, Tracker stillgelegt) (2026-08-01, sorb: HOHE Priorität — vollständige Issue-Migration + zentrale Gruppen-Roadmap; Plan-Skizze und die offene Erreichbarkeits-Entscheidung git.lab-only vs. extern stehen dort)
**Umgesetzt am 2026-08-01/02**: Die Migration ist durch — 62 Issues liegen auf
git.lab, die Gitea-Issues sind geschlossen und tragen einen Migrations-Fußtext.
Alles darunter ist der **Stand vor dem Umzug** und bleibt als Historie stehen;
die Aufzählung „Noch auf Gitea" gilt nicht mehr. Die zunächst verbliebene Ausnahme
für Deploy-Übergabe-Issues ist am 2026-08-02 mit LABNET-03 ebenfalls zurückgebaut.
---
Beim CI/CD-Umzug (2026-07-31) ist nur der **Code** nach git.lab gewandert; alle
Projektmetadaten liegen weiterhin auf Gitea/rohana. Verifiziert per API am
2026-07-31: alle 20 git.lab-Projekte melden `open_issues=0`.
Noch auf Gitea:
- **Issues** inkl. Kommentare/Labels: ThreadNet-Web (#2, #5, …), threadnet-call (#1),
gitops (#24, #25, #32, …)
- **Meilensteine** (u. a. die Release-Meilensteine im gitops-Repo)
- **Wiki** (gitops-Wiki mit `00-TASKS.md`-Log — bisher bewusst direkt-Gitea)
- **Releases/Packages** (npm-Registry bleibt laut Lockfile-Entscheidung in
[CFGMON-11](#cfgmon-11--gitea-ci-rückbau-nach-gitlab-umzug) auf rohana — bei
diesem Punkt prüfen, ob das so bleibt)
Vor Umsetzung zu klären:
1. **GitLab-Gitea-Importer vs. API-Skript** — der Importer verliert Autorenschaft
(alles läuft unter dem Import-User) und Issue-Nummern können sich verschieben
→ Commit-/Kommentar-Referenzen prüfen.
2. **Erreichbarkeit**: rohana ist von überall erreichbar, git.lab nur im Homelab —
das gilt dann für alle Issues und muss bewusst entschieden werden. Gleiches
Argument betrifft dieses Repo selbst (damals noch `Backlogs`, bewusst
direkt-Gitea).
3. **Flux-relevante Teile**: ob die gitops-Wiki-Konventionen mitgehen.
Nach dem Umzug gehört ein Hinweis in die Topologie-Abschnitte (gitops
README/CLAUDE.md, Fork-Docs) — dort steht aktuell „Issues/Wiki/Releases bleiben
auf Gitea" als geltende Regel.
---
## Erledigt
### CFGMON-10 — threadnet-call-CI schlägt am Artifact-Schritt fehl · verworfen 2026-07-30
Ausgelöst durch einen Push nach `threadnet-call` am 2026-07-30: der Runner (`builder-1`)
verarbeitete mehrere geerbte Upstream-Workflows, die meisten scheiterten am
Artifact-Upload/-Download-Schritt. Ursprünglich unverifizierte Hypothese: das
Job-Container-Limit (2,2 GiB / 1,5 CPU) ist zu knapp.
**Hypothese inzwischen im Kern bestätigt** — beim parallelen ThreadNet-Web-CI-Versuch
starb der webpack-Build bei 92 % mit `FATAL ERROR: ... JavaScript heap out of memory`
(Job 3442, 2026-07-30): diese Build-Klasse braucht real ~4 GB, der Host (3,7 GiB gesamt,
ohne Swap, trägt daneben Gitea/Traefik/Monitoring) kann das strukturell nicht liefern.
**Verworfen statt gefixt**: Limit-Anhebung/Swap wird bewusst nicht weiterverfolgt —
Build-CI zieht ins Homelab-GitLab um (siehe
[CFGMON-11](#cfgmon-11--gitea-ci-rückbau-nach-gitlab-umzug)), CFGMON bleibt bei leichten
Jobs. Issue-Seite: `threadnet-call#1` (Gitea-Zählung, Tracker stillgelegt).
### CFGMON-02 — Traefik, Gitea, cAdvisor und Runner unter IaC gebracht · erledigt 2026-07-30
Liefen ursprünglich im Compose-Projekt `thread-net-git` aus `/data/compose/8`, einem von
Portainer verwalteten Stack ohne Repo dazu. Jetzt in `sorb/thread-net-git`: `:latest`-Tags
gepinnt (Gitea `1.27.0`, cAdvisor `v0.49.1`), Projektname `thread-net-git` beibehalten
(Volume-Kontinuität), README mit Betriebsregeln ("nie wieder über Portainer anfassen",
Volume-Namen, Downgrade-Verbot für Gitea), nächtliches Backup-Script. Zusätzlich neu: ein
`runner`-Service (`gitea/act_runner:0.6.1`, Container `gitea-runner`, Labels
`ubuntu-latest`/`linux-build`/`win-wine` — die letzten beiden gezielt für Electron-Builds)
— ursprünglich unter [CFGMON-08](#cfgmon-08) als offene Frage gelistet, siehe dort.
Entstanden auf Branch `rework/stack`, zunächst nicht gemergt (produktiv aber schon aktiv).
**2026-07-30 nach `main` gemergt** (`origin/main` == `origin/rework/stack` auf `02b3224`,
verifiziert) — damit spiegelt die Standardansicht des Repos jetzt den Live-Stand.
Verifiziert am 2026-07-30 über die Compose-Labels der laufenden Container
(`working_dir: /opt/thread-net-git`) und `docker compose ls`. `gitea-data` ist als
external Volume deklariert — ein Deploy mit falschem Projektnamen schlägt laut fehl,
statt leise ein leeres Volume anzulegen.
Zum Bootstrapping-Problem (Definition von Gitea liegt in Gitea): mitigiert,
weil das Deploy-Verzeichnis selbst der Checkout ist — fällt Gitea aus, liegt
die Definition weiterhin lokal auf dem Host. Gegen Verlust des ganzen Hosts
hilft nur die Off-Host-Kopie, siehe
[CFGMON-09](#cfgmon-09--gitea-backups-off-host-in-die-storage-box-eigenes-borg-repo).
### CFGMON-05 — Monitoring-Stack unter IaC bringen · erledigt 2026-07-30
Der Stack lief aus `/opt/monitoring` ohne Versionierung und mit `:latest`-Tags. Jetzt
in `sorb/threadnet-operating` unter `monitoring/`, Images gepinnt,
Grafana-Datasources und 9 Dashboards provisioniert. Projektname `monitoring`
beibehalten, dadurch blieben die Volumes erhalten.
### CFGMON-06 — Grafana-Certresolver zeigte ins Leere · erledigt 2026-07-30
Das Label sagte `certresolver=le`, Traefik kennt den Resolver aber als
`letsencrypt`. Traefik protokollierte `Router uses a nonexistent certificate
resolver` und lieferte für `selendis.axion1337.de` sein Default-Self-Signed-Cert
aus. Aus dem Altbestand in `/opt/monitoring` unverändert übernommen und dort
mindestens seit dem 2026-07-27 vorhanden.
Behoben in `threadnet-operating`, Commit `a400f8a`. Cert von Let's Encrypt (YR2)
ausgestellt, gültig bis 2026-10-28 — die Nachfolge davon ist
[CFGMON-01](#cfgmon-01--zertifikatserneuerung-braucht-offene-ports-ipv4-und-ipv6).
### CFGMON-07 — Alloy verlor seine Positions-Datei bei jedem Deploy · erledigt 2026-07-30
`--storage.path=/var/lib/alloy/data` war gesetzt, aber ohne Volume: die
Positions-Datei lag im Container-Layer. Nach jedem Recreate las Alloy alle
Docker-Logdateien von vorn, worauf Loki alles älter als 7 Tage mit HTTP 400 abwies
(`timestamp too old`, `reject_old_samples`). Betroffen waren nur Alt-Logzeilen bis
zurück zu 2025, keine aktuellen Daten.
Behoben durch ein `alloy_data`-Volume, Commit `edac97e`. Verifiziert: Positions
überleben `--force-recreate`, zweiter Recreate erzeugt 0 Fehler.
### CFGMON-08 — Kein Gitea-Actions-Runner registriert, Standort noch offen · erledigt 2026-07-30
**Korrektur einer falschen Prämisse**: der Eintrag ging davon aus, dass gar kein Runner
existiert und wo einer laufen sollte, noch offen sei. Beides falsch — ein Runner
(`builder-1`) läuft bereits, auf CFGMON, als Teil von `thread-net-git`s `rework/stack`-
Branch, mit gezielt für Electron-Builds eingerichteten Labels. Details siehe
[CFGMON-02](#cfgmon-02--traefik-gitea-cadvisor-und-runner-unter-iac-gebracht--erledigt-2026-07-30) — hier
nicht dupliziert. `gitops#33` (Gitea-Zählung, Tracker stillgelegt)
(dieselbe falsche Prämisse) entsprechend korrigiert/geschlossen.
+91
View File
@@ -0,0 +1,91 @@
---
type: wiki-page
area: admin
related: []
---
# game
Pterodactyl- / Gameserver-Host.
| | |
|---|---|
| **IPv4** | `157.90.155.206` |
| **IPv6** | kein AAAA-Record |
| **DNS** | `game.axion1337.de` |
| **Privat** | `10.0.0.4` (im vSwitch seit 2026-08-02) |
| **Stand** | 2026-08-02 |
> **Teil-inventarisiert (2026-08-02).** Die Compose-Definitionen sind bekannt, der
> Host selbst wurde weiterhin nicht betreten — OS-Stand, Plattenbelegung und
> Docker-Version fehlen. Beim ersten direkten Zugriff nachtragen.
## Was darauf läuft
Zwei über **Coolify** deployte Stacks, abgebildet in
[axion1337.chat/game-operating](https://git.lab/axion1337.chat/game-operating)
(angelegt 2026-08-02).
⚠️ Das Repo ist ein **Abbild, keine Quelle**: Verwaltet wird weiter in Coolify,
Änderungen müssen dort *und* im Repo passieren. Der Umstieg auf git-basiertes
Deployment ist gewollt, aber bewusst **zurückgestellt, bis das Matrix-Projekt
abgeschlossen ist** (sorb, 2026-08-02). Bis dahin fehlen dem Abbild noch die
mitgemounteten Konfigdateien (`entrypoint.sh`, wings-Config, die drei
Monitoring-Konfigs) und die Volume-Deklarationen — **aus dem Repo allein ließe
sich der Host derzeit nicht wiederherstellen.**
**Pterodactyl** (Gameserver-Verwaltung, in Benutzung durch Bekannte des Betreibers
— Ausfälle und Datenverlust sind hier real spürbar):
| Dienst | Image |
|---|---|
| `pterodactyl` (Panel) | `ghcr.io/pterodactyl/panel:v1.12.0` |
| `wings` (Daemon, fährt die Gameserver als Docker-Container) | `ghcr.io/pterodactyl/wings:v1.12.0` |
| `mariadb` | `mariadb:11.8` |
| `redis` | `redis:alpine` |
**Eigener Monitoring-Stack** (grafana-oss, prometheus v3.0.0 mit 15 d Retention,
loki 3.1.1, promtail 3.1.1, node-exporter v1.8.1, cadvisor v0.49.2). Wird
perspektivisch von CFGMON abgelöst — siehe unten.
⚠️ **Kein einziger `ports:`-Block in beiden Stacks.** Alles hängt an Coolifys
Docker-Netz und ist nur containerintern erreichbar. Genau das war die Ursache von
GAME-01: Auf 9100/8080 des Hosts lauscht nichts, CFGMONs Scrape-Ziele auf der
öffentlichen IP konnten nie funktionieren.
## Erreichbarkeit von außen (gemessen)
Vor dem Host liegt eine **quellbewusste Firewall** — dieselben Ports verhalten sich
je nach Quelle unterschiedlich:
| Port | von CFGMON (`188.245.193.243`, 2026-08-01) | vom Hausanschluss (`178.25.213.70`, 2026-08-02) |
|---|---|---|
| 80 / 443 | offen | offen (HTTP 404 bzw. 503) |
| **22** | **Timeout** | **offen** |
| 8080 / 9100 (Exporter) | Timeout | Timeout |
| ICMP | 100 % Verlust | 100 % Verlust |
Daraus folgt zweierlei: Es gibt bereits eine **SSH-Freigabe für den Hausanschluss**,
und CFGMON ist **nicht pauschal gesperrt** (sonst wären auch 80/443 von dort tot) —
es ist eine portbezogene Regel mit Quellliste. Die Exporter-Ports 8080/9100 sind
dagegen **für niemanden** freigegeben, auch nicht für den Hausanschluss.
Es fehlte also keine Ausnahme für CFGMON. Seit 2026-08-02 liegt der Host im
vSwitch (`10.0.0.4`); die Monitoring-Anbindung läuft künftig **per Push über das
private Netz** — Alloy sammelt lokal ein und schiebt nach `10.0.0.3`, wodurch der
Host **keinen einzigen eingehenden Port** braucht. Dasselbe Muster wie beim
k3s-Cluster. Details: [GAME-01](https://git.lab/axion1337.chat/management/-/issues/2).
Die Umstellung erfolgt **additiv**: Der lokale Monitoring-Stack läuft weiter, bis
auf CFGMON über Wochen belastbar Daten liegen. Auf diesem Host wird nichts
abgeräumt, solange nicht klar ist, dass nichts fehlt.
## Offene Punkte → git.lab-Issues
Seit dem Framework-Umbau (2026-08-01) leben offene Punkte als Issues im
[management-Projekt](https://git.lab/axion1337.chat/management/-/issues); die IDs bleiben in den Issue-Titeln erhalten.
Dieses File hält nur noch Bestand und Historie.
- [GAME-01 — Host von CFGMON aus nicht erreichbar, 2 Targets down (⚠️ Silences bis 2026-08-04)](https://git.lab/axion1337.chat/management/-/issues/2)
- [GAME-02 — `www.game.axion1337.de` ist überflüssig](https://git.lab/axion1337.chat/management/-/issues/3)
+217
View File
@@ -0,0 +1,217 @@
---
type: wiki-page
area: admin
related: []
---
# matrix
Matrix-Homeserver (Element Server Suite / Synapse) + K3s-Single-Node-Cluster, GitOps-verwaltet.
| | |
|---|---|
| **Hostname** | `MATRIX` |
| **IPv4** | `49.13.132.245` |
| **IPv6** | kein AAAA-Record |
| **Privat** | `10.0.0.2` (`enp7s0`, dasselbe Hetzner-Netz wie CFGMON `10.0.0.3`) |
| **OS** | Debian 13 (trixie) |
| **DNS** | `matrix.axion1337.de` **und** `matrix.axion1337.chat` zeigen auf dieselbe IP — ebenso `axion1337.chat` (Apex) und `account.axion1337.chat` (MAS). `axion1337.de` ist die ältere/Registrar-Domain (IONOS-Mail läuft dort), `axion1337.chat` die eigentliche Matrix-Service-Domain. |
| **Stand** | 2026-07-30 |
**Inventarisiert** (direkter SSH-Zugriff, `~/.ssh/config`-Alias `axion1337`, Port 2248):
K3s + FluxCD + Element Server Suite (Synapse, MAS, Element Web, MatrixRTC), Authentik (OIDC),
Traefik, Cert-Manager, coturn, Draupnir, ClamAV, NetworkPolicies (Default-Deny). IaC-Repo:
[`sorb/axion1337.chat-gitops`](https://rohana.axion1337.de/sorb/axion1337.chat-gitops) - dieser
Host **ist** das Deployment-Ziel dieses Repos, nicht nur verwandt. Client-Forks:
`sorb/ThreadNet-Web` (Element Web), `sorb/threadnet-call` (Element Call/LiveKit-Widget).
`sorb/element-web` und `sorb/ThreadNet-Stack` sind **veraltete/abgelöste** Vorgänger-Repos
(letzte Aktivität 2026-05-11 bzw. 2025-10-24) - nicht mehr das, was hier läuft.
`ufw`: aktiv, Default Deny Incoming / Allow Outgoing, explizite Allow-Regeln für
2248/tcp (SSH), 80/443, TURN/RTC-Ports. `unattended-upgrades` aktiv (Debian-Security +
Debian-Origin), siehe [MATRIX-04](#matrix-04--host-level-pre-update-benachrichtigung-erledigt).
## Offene Punkte → git.lab-Issues
Seit dem Framework-Umbau (2026-08-01) leben offene Punkte als Issues im
[management-Projekt](https://git.lab/axion1337.chat/management/-/issues); die IDs bleiben in den Issue-Titeln erhalten.
Dieses File hält nur noch Bestand und Historie.
- [MATRIX-03 — `www.matrix.axion1337.de` ist überflüssig](https://git.lab/axion1337.chat/management/-/issues/1)
## MATRIX-05 — node-exporter-DaemonSet in CrashLoopBackOff, Cluster-Scrape seit 2026-08-01 tot
**Status:** erledigt (2026-08-01 ~04:10, vom Mac aus mit kubectl/SSH)
**Auflösung:** Beide Teile hatten dieselbe Wurzel — ein **doppelter node-exporter**.
Teil 1 bestätigt per Pod-Log: `listen tcp 0.0.0.0:9100: bind: address already in use`;
den Port hält der systemd-Dienst `prometheus-node-exporter` (der etablierte, den CFGMON
via `10.0.0.2:9100` scrapt). Teil 2 erklärt: der Cluster-Service "funktionierte" nur in
den Ready-Momenten des Crashloop-Pods, dessen hostNetwork-Endpoint (= Host-IP) zufällig
auf den gesunden systemd-Exporter zeigte; seit der Endpoint dauerhaft NotReady war,
setzte kube-proxy `no endpoints → REJECT`. ufw-Regeln seit April unverändert — kein
Firewall-Drift; extern war 9100 nie freigegeben (und soll es nicht sein).
**Fix (gitops `228807f`, Weg A aus gitops#45):** HelmRelease + Alloy-Scrape entfernt,
Flux hat gepruned — DaemonSet/Service/Pod sind weg, Host-Metriken kommen unverändert
vom systemd-Exporter. Volle Diagnose:
`gitops#45` (Gitea-Zählung, Tracker stillgelegt — verbindlich: Migrations-Fußtext im GitLab-Issue).
<details><summary>Ursprünglicher Befund (CFGMON-Session, vor der Host-Prüfung)</summary>
Von CFGMON aus diagnostiziert, per kube-state-metrics und den remote-geschriebenen Serien.
**Auf dem Host selbst wurde nichts geprüft** — CFGMON hat kein `kubectl`.
### Teil 1 — der Pod crasht seit Monaten (chronisch)
| Messwert | Stand 2026-08-01 |
|---|---|
| Pod `prometheus-node-exporter-4wwn7`, Restarts | **4880**, davon 1970 in 7 Tagen |
| Vorgängerpod `…-fv7hj`, Zählerstand vor 30 Tagen | 13918 |
| aktuelle Rate | ~12/h — entspricht dem CrashLoopBackOff-Deckel von 5 min |
| `last_terminated_reason` | `Error` (Exit ≠ 0, **kein** OOMKill) |
| `waiting_reason` / `ready` | `CrashLoopBackOff` / `0` |
Das besteht mindestens seit 30 Tagen, also **lange vor** dem Monitoring-Rework und vor dem
Alerting-Rollout. Neu ist nur die Sichtbarkeit: die Regel `KubePodRestartLoop` kam am
2026-08-01 dazu. Wie bei Synapse hat das Alerting einen stillen Altfehler aufgedeckt.
**Vermutete Ursache, nicht verifiziert:** Der Pod läuft mit `hostNetwork: true` und will
Port 9100 auf dem Host binden. Dort hört bereits ein **eigenständiger node-exporter**
derselbe, den CFGMON als Job `k3s_host_node` direkt auf `10.0.0.2:9100` scrapt und der
2706 Metriken sauber ausliefert. Zwei Exporter, ein Port; der Pod bekäme
`address already in use` und beendete sich sofort, was zu `reason: Error` ohne OOM passt.
Zum Bestätigen auf dem Host:
```
ss -lntp | grep :9100
kubectl -n monitoring logs prometheus-node-exporter-4wwn7 --previous | tail -20
```
### Teil 2 — der Cluster-Scrape ist am 2026-08-01 01:19 UTC ausgefallen (akut)
| Messwert | Stand |
|---|---|
| `up`-Mittel 24 h für `prometheus-node-exporter.monitoring.svc.cluster.local:9100` | 98,3 % |
| Zustandswechsel in 24 h | **1** — einmal runter, nicht zurück |
| Host-Uptime | 77,6 Tage → **kein Reboot** |
Ein Pod, der 12× pro Stunde stirbt, kann kein Target sein, das zu 98 % up ist. Geantwortet
hat also nie der Pod, sondern der eigenständige Exporter über die Host-IP. Der Pod hat
`hostNetwork`, sein Pod-IP ist die öffentliche `49.13.132.245`, dorthin zeigt der
Service-Endpoint — und dort kommt seit 01:19 nichts mehr.
Von CFGMON aus gemessen:
| Pfad | Ergebnis |
|---|---|
| `10.0.0.2:9100` (privat) | offen, 2706 Metriken |
| `49.13.132.245:9100` (öffentlich) | **keine Antwort** |
| `49.13.132.245:80` / `:443` | offen — Host lebt |
Ohne Reboot heißt das: in der Nacht wurde die Erreichbarkeit auf 9100 eingeengt. Zwei
Möglichkeiten, von hier aus nicht unterscheidbar:
1. Der Exporter bindet jetzt `10.0.0.2:9100` statt `0.0.0.0:9100`.
2. Eine `ufw`-Regel wurde geändert. Die Allow-Liste oben in dieser Datei führt 9100
ohnehin nicht auf — die private Zustellung muss also über eine Interface- oder
Subnetz-Regel laufen, an der sich etwas geändert haben kann.
Zeitlich fällt das exakt in das Fenster der Synapse-Port-Korrektur derselben Nacht. Wer
dort die Exposition aufgeräumt hat, hat den Cluster-Scrape-Pfad mitgenommen. Prüfen mit
`ss -lntp | grep :9100` und `ufw status numbered`.
### Fix — es ist ein node-exporter zu viel
**Weg B (empfohlen):** Den DaemonSet-Exporter abschalten (`nodeExporter.enabled: false` in
den kube-prometheus-stack-Values) und das Cluster-Alloy statt auf den Service-Namen direkt
auf `10.0.0.2:9100` zeigen lassen. Beendet den Crashloop und erhält die enge Bindung ans
private Netz, die in der Nacht vom 2026-08-01 gesetzt wurde.
**Weg A:** Den eigenständigen Exporter stilllegen und dem DaemonSet den Port überlassen.
Ebenfalls sauber, aber er bindet dann wieder `0.0.0.0` — also auch die öffentliche IP,
abgesichert nur noch durch `ufw` und die Cloud-Firewall. Das nähme die Einschränkung
zurück, die gerade erst gesetzt wurde.
### Nebenbefund — Job-Label kollidiert zwischen zwei Hosts
Das Label `prometheus.scrape.node_exporter` existiert zweimal, weil CFGMONs Alloy und das
Cluster-Alloy ihre Scrape-Komponente gleich benennen:
```
up=1 instance=node-exporter:9100 -> CFGMON (Kernel 6.8.0-136-generic)
up=0 instance=prometheus-node-exporter.monitoring.svc...:9100 -> MATRIX
```
Die Serien kollidieren nicht, `instance` trennt sie. Aber jede Abfrage, die nur nach `job`
filtert, mischt zwei Maschinen — und in Alarmtexten steht dann ein Job-Name, der nicht sagt,
welcher Host gemeint ist. Ein External Label auf der Remote-Write-Seite dieses Clusters
(`cluster="matrix"`) würde das sauber trennen.
---
</details>
## Erledigt
### MATRIX-01 — Klären, ob der Server Mail als `@matrix.axion1337.de` verschickt · erledigt 2026-07-30
Für `matrix.axion1337.de` existiert der komplette IONOS-Mail-Satz: `MX mx00/mx01`,
`TXT "v=spf1 include:_spf-eu.ionos.com ~all"`, `CNAME s1-ionos._domainkey` und
`CNAME autodiscover`. Bei `selendis` ist dasselbe Muster reine Altlast; hier war die Frage
offen, weil Matrix-Homeserver typischerweise Mail für Registrierung/Passwort-Reset
verschicken.
**Antwort, verifiziert per Config** (nicht nur vermutet) — direkt im IaC-Repo
`sorb/axion1337.chat-gitops`, dem tatsächlich hier deployten Stand geprüft:
- `apps/production/custom-configs/synapse-values.yaml` — kein `email:`/`smtp_host`/
`notif_from`-Block.
- `apps/production/custom-configs/mas-secret.yaml` (SOPS-entschlüsselt geprüft) — kein
`email`/`smtp`/`mailer`-Eintrag.
- `apps/production/element-server-suite.yaml` (HelmRelease values) — dito, nichts.
Weder Synapse noch MAS versenden aktuell irgendeine Mail. Registrierung/Passwort-Reset
laufen ausschließlich über Authentik (OIDC, `auth.axion1337.chat`) und Einladungslinks.
Der komplette IONOS-Mail-Satz auf `matrix.axion1337.de` ist damit **funktional unnötig**
dieselbe Härtung wie bei `selendis` anwenden (Null-MX, `v=spf1 -all`, `_dmarc p=reject`),
`autodiscover.matrix` kann ebenfalls weg. Damit ist auch
[ZONE-02](../architecture/zone-axion1337.md) an dieser Stelle entblockt.
**Separat davon** (andere Domain-Ebene, kein Widerspruch): auf diesem Host läuft seit
2026-07-30 ein eigener Mailversand für Host-Wartungsbenachrichtigungen
(`wartung@axion1337.de`, **Apex**-Postfach, nicht die `matrix.`-Subdomain) — siehe
MATRIX-04 unten. Nutzt die ohnehin am Apex laufende echte IONOS-Mail-Infrastruktur,
betrifft die `matrix.`-Subdomain-Records oben also nicht.
### MATRIX-02 — Pusht per Remote-Write auf einen offenen Prometheus · erledigt 2026-07-30
**Korrektur einer falschen Annahme im ursprünglichen Eintrag**: der Text ging von zwei
getrennten Absendern aus - "CFGMON (`10.0.0.3`) und der k3s-Host (`10.0.0.2`)" - als wären
das zwei verschiedene Maschinen. Es ist **dieselbe Maschine**: dieser Host (`matrix`) hat
selbst die private IP `10.0.0.2` (verifiziert per `ip -4 addr show` auf dem Host).
Verifiziert in `apps/monitoring/alloy-config.yaml` (diesem Cluster): Der Remote-Write-Push
geht bereits an `http://10.0.0.3:9090/api/v1/write` und Loki an `http://10.0.0.3:3100/...` -
**private IP, nicht die öffentliche** `188.245.193.243:9090`. Von dieser Seite aus ist hier
nichts mehr zu tun. Ob Prometheus/Loki auf CFGMON zusätzlich öffentlich erreichbar sind
(unabhängig davon, ob dieser Host den privaten Weg nutzt), ist
[CFGMON-03](cfgmon.md#cfgmon-03--prometheus-remote-write-und-loki-sind-öffentlich-ohne-auth)
- ein reines CFGMON-Thema, nicht mehr blockiert durch etwas auf diesem Host.
### MATRIX-04 — Host-Level Pre-Update-Benachrichtigung · erledigt 2026-07-30
Neuer, eigenständiger Mechanismus auf diesem Host, außerhalb von Flux/GitOps (Details:
`docs/deployment-guides/07-host-maintenance-notifications.md` im gitops-Repo,
`gitops#24` (Gitea-Zählung, Tracker stillgelegt)):
`unattended-upgrades` war bereits aktiv, neu ergänzt ist ein systemd-Timer
(`maintenance-notify.timer`, fest 05:00 Uhr, vor dem 06:00-07:00-Update-Fenster), der bei
anstehenden Paket-Updates per Mail **und** Matrix (Thread-Reply im `wartung`-Raum)
benachrichtigt.
Mail-Versand läuft über `msmtp`, Absender `wartung@axion1337.de` (IONOS SMTP,
`smtp.ionos.de:587`, STARTTLS - **Port 465 war ausgehend blockiert**, vermutlich
Cloud-Provider-Firewall-Regel, 587 ging durch). Relevant für die Mail-Policy-Diskussion
oben: dieses Postfach nutzt die reale, bereits am Apex laufende IONOS-Mail-Infrastruktur,
keine neue Subdomain, kein neuer Handlungsbedarf für die Zone.
+152
View File
@@ -0,0 +1,152 @@
---
type: wiki-page
area: admin
related: []
---
# Overmind
Homelab-Host: GitLab (Dokploy-verwaltet) + CI-Runner. **Nur im Lab erreichbar**
`git.lab` löst außerhalb des Homelabs nicht auf; Produktion (Flux auf CFGMON/MATRIX) hängt
nicht von diesem Host ab, nur neue Builds pausieren bei Ausfall.
| | |
|---|---|
| **Hostname** | `Overmind` |
| **DNS (Lab)** | `git.lab``10.58.73.17` (TLS via Dokploy-Proxy, Zertifikate von der aXionLabs-CA: step-ca, 24h-Leaf, Intermediate bis 2035) |
| **CPU/RAM** | 14 Kerne, 30 Gi (Stand 2026-07-31: ~11 Gi verfügbar) |
| **Disk** | 444 G NVMe (~278 G frei, Stand 2026-07-31) |
| **KVM** | `/dev/kvm` vorhanden — Basis für die Windows-Build-VM |
| **Stand** | 2026-07-31 |
## Dienste
| Dienst | Definition | Anmerkungen |
|---|---|---|
| GitLab CE 18.7.1 + Postgres 16 + Redis 7 | Dokploy-Stack `management-gitlabce` | `external_url https://git.lab`, SSH 2224; TLS terminiert der Dokploy-Proxy (GitLab-nginx lauscht nur :80) |
| gitlab-runner `lab-builder-1` (v18.7.0) | gleicher Stack, Service `gitlab-runner` | Docker-Executor + Socket, `concurrent = 1`. **Stolpersteine, live gefunden**: (1) Docker-interner DNS löst `git.lab` auf den GitLab-Container auf, wo 443 zu ist → `extra_hosts: git.lab:10.58.73.17` nötig; (2) Lab-CA muss nach `/etc/gitlab-runner/certs/git.lab.crt` (Config-Volume, übersteht Redeploys) |
| gitlab-runner `lab-windows-1` | Windows-Gast in der Build-VM | Tags `[windows]`, `run_untagged=false`, Shell-Executor PowerShell — siehe Runbook |
| Windows-Build-VM | Dokploy-Stack `windows-runner` (live seit 2026-07-31) | Image `registry.git.lab/axion1337.chat/vendor/windows:stable` (Eigenbau aus reviewtem Pin `7645a2b`, Vendor-Repo `git.lab/axion1337.chat/vendor/windows`), **on-demand** (`restart: "no"`, Start/Stop über CI-Jobs), 8G/6 Kerne/96G. Runbook: `docs/axion-runner.md` im Vendor-Repo. Gast-Uhr geht falsch (Traces stempeln ~+7h) — kosmetisch |
## Repo-Topologie (Kontext)
git.lab ist seit 2026-07-31 **kanonisch** für die gespiegelten Repos der Gruppe
`axion1337.chat` — Stand 2026-08-09 **sieben**: die sechs Produkt-Repos (ThreadNet-Web,
threadnet-call, thread-net-git, threadnet-operating, axion1337.chat-gitops, seit heute auch
`game-operating`) **und `management`, also dieses Repo**. Push-Mirrors nach rohana/Gitea,
direkte Gitea-Pushes tabu.
⚠️ `gameserver` (achtes Projekt der Gruppe) hat **keinen** Mirror — offen in
[management#32](https://git.lab/axion1337.chat/management/-/issues/32), dort liegt auf Gitea
ein gleichnamiges Repo mit anderem Stand.
Gitea bleibt: Flux-Source (via Mirror beliefert), Registry, Packages.
**Issues nicht mehr** — die sind am 2026-08-01/02 nach git.lab gewandert
([ADR-0002](../../adr/0002-issues-und-management-ins-lab.md)). Die letzte Ausnahme,
die Deploy-Übergabe-Issues auf dem Gitea-Tracker `sorb/management`, ist am 2026-08-02
mit LABNET-03 zurückgebaut: beide umgezogen (#25, #26), der Tracker ist leer.
**Ohne Ausnahme: Issues leben auf git.lab.**
*(Bis 2026-08-01 stand hier „Backlogs (dieses Repo, ungespiegelt)" — das Repo heißt
seit der Umwidmung zum Management-Repo `management` und wird seither gespiegelt,
[ADR-0005](../../adr/0005-pm-framework-kanban.md).)*
## OVERMIND-01 — GitLab-Container-Registry aktivieren, Images nach Konsument sortieren
**Status:** erledigt (2026-08-01)
**Abschluss-Verifikation**: `desktop_image` baut und pusht per `CI_JOB_TOKEN` nach
`registry.git.lab/axion1337.chat/threadnet-web/desktop-build:bullseye` (Job 386 grün,
Tag per API bestätigt); `desktop_linux` nutzt dieses Image als Job-Container und lief
damit grün durch (Job 398 - beweist auch den anonymen Pull des public Projekts durch
den Runner-Daemon). Die rohana-`REGISTRY_*`-Variablen bleiben nur noch für den
App-Image-Push (`docker_web`) in Gebrauch - genau die Ziel-Sortierung nach Konsument.
Die eingebaute GitLab-Registry ist aus (kein `registry_external_url` im Omnibus-Config).
Folge: lab-interne Build-Images (`windows-vm`, `element-desktop-build`) machen den Umweg
Lab-CI → rohana (Prod, Internet) → zurück ins Lab — koppelt Lab-Infrastruktur unnötig an
die Verfügbarkeit des Prod-Hosts.
**Ziel-Sortierung nach Konsument:**
- **rohana (Gitea) behält**: `sorb/threadnet-web` (App-Image — Flux/Prod pullt es),
npm-Packages
- **Lab-Registry (`registry.git.lab`) bekommt**: `windows-vm`,
`element-desktop-build` — beides konsumiert nur das Lab selbst
**Umsetzung** (TLS terminiert wie bei `git.lab` der Dokploy-Proxy):
1. Omnibus-Config: `registry_external_url 'https://registry.git.lab'`,
`registry_nginx['listen_port'] = 5050`, `registry_nginx['listen_https'] = false`;
Port 5050 in der Compose exposen
2. Lab-DNS: `registry.git.lab``10.58.73.17`
3. Dokploy: Domain `registry.git.lab` → GitLab-Service Port 5050 (Zertifikat von der
aXionLabs-CA wie gehabt)
4. Docker-Daemon-Trust auf Overmind: CA-Kette nach
`/etc/docker/certs.d/registry.git.lab/ca.crt` (Datei liegt schon als
`/tmp/git.lab.crt` vom Runner-Setup — kopieren reicht; kein Daemon-Restart nötig)
5. CI-Umstellung: `vendor/windows` pusht nach `registry.git.lab` (Bonus: GitLabs
eingebaute `$CI_REGISTRY`/`$CI_JOB_TOKEN`-Auth statt Gruppen-Secrets),
`desktop_image`/`desktop_linux` in ThreadNet-Web folgen; Registry-Speicher liegt im
`gitlab_data`-Volume (278 G frei)
**Fortschritt 2026-07-31**: Punkte 14 umgesetzt (Registry live auf
`registry.git.lab`, 401/Bearer-Auth korrekt, CA-Trust auf dem Host); `vendor/windows`
pusht per `CI_JOB_TOKEN` in die Lab-Registry — verifiziert, Tags `5bc25447` + `stable`
vorhanden, Runbook referenziert `stable`.
**Nächster Schritt:** `element-desktop-build` von rohana in die Lab-Registry umziehen
(ThreadNet-Web-CI: `desktop_image`-Push-Ziel + `desktop_linux`-Image-Referenz) — bewusst
zurückgestellt, bis kein Auto-Job das alte Image parallel referenziert (Reihenfolge:
erst neues Image bauen, dann Referenz umstellen).
## OVERMIND-02 — Host-Ausfall 2026-07-31 ~19:15 lokal (NIC-Hang, Fix aktiv)
**Status:** Fix aktiv — die Beobachtung läuft als [Issue #4](https://git.lab/axion1337.chat/management/-/issues/4)
(Framework-Umbau 2026-08-01); dieser Abschnitt ist Bestand/Historie.
**Ursache (Journal des Vor-Boots, via Claude-Session auf Overmind):** `e1000e`
`Detected Hardware Unit Hang` auf `eno1` in Endlosschleife — die Intel-NIC hing
(bekanntes e1000e-Problem in Kombination mit EEE/Energiesparen), der Host lief weiter,
war aber netzwerktot. Kein OOM, kein Bezug zur Windows-VM/CI. Passt zu allen Symptomen:
Ping tot, aber der Windows-Runner erreichte GitLab host-intern noch (Job 413 wurde
aufgegriffen) und scheiterte erst an der DNS-Auflösung übers tote Interface.
**Fix (2026-07-31, Overmind-Session):** `ethtool --set-eee eno1 eee off` live gesetzt
+ persistente udev-Regel `/etc/udev/rules.d/71-disable-eee-eno1.rules` (greift bei
jedem Boot). Temporäre sudoers-Freigabe danach wieder entfernt.
**Offen:**
- ~~NIC-/BIOS-Firmware-Update 2.4.0.0 → 2.5.2.0~~ **erledigt** (Wartungsfenster
2026-08-01, durch sorb)
- Falls der Hang trotz EEE-off + neuer Firmware wiederkehrt: gezielter ASPM-Fix
statt globalem Kernel-Parameter
**Zeitleiste (lokal, UTC+2):**
- 18:58 — Windows-VM nach händischem Container-Stop+Start zurück, Runner online
- 19:00 — desktop_windows Job 399 (Versuch 5): läuft bis `build:native`, scheitert an
fehlendem rustc (kein Host-Problem)
- 19:0519:12 — Provision-Job 409 grün (Rust 1.97.1 maschinenweit, Strawberry Perl,
Python 3.14, NASM-PATH), Dienst-Neustart via Scheduled Task funktionierte
- 19:13/19:14 — letzte saubere Runner-Herzschläge
- **zwischen 19:14 und 19:21 — Host fällt aus**: Ping 100 % Verlust, git.lab-API tot
- 19:21 — Job 413 (Versuch 6) wird noch aufgegriffen, Git-Fetch scheitert nach 8 s mit
`Could not resolve host: git.lab` → Host-Netz/DNS zu dem Zeitpunkt bereits kaputt;
danach Funkstille
- ~20:15 — Host pingt wieder (Reboot durch sorb), 20:21 GitLab-API zurück,
lab-builder-1 online; windows-runner-Container down (`restart: "no"` — korrekt)
**Wichtig:** Der Windows-Build war NICHT die Ursache — Job 413 hat nie Quellcode
geholt, die VM lief nur idle (frisch provisioniert; denkbare Gast-Hintergrundlast:
Windows Update/Defender nach den choco-Installs). Die 8-G-Zuteilung der VM plus
GitLab-Stack blieb aber auch nach dem Puma-Fix ein enges Budget auf 30 Gi.
**Konsequenz für die Build-Kette:** RAM-Budget war nicht das Problem — VM bleibt bei
8G. Nach dem NIC-Fix lief die Kette durch: **desktop_windows Job 438 grün**
(2026-07-31 ~21:50 lokal, `Element Setup 1.12.17.exe`, 141 MB, unsigniert) —
ThreadNet-Web#5 geschlossen, Folgethemen (Signing/Branding) in ThreadNet-Web#6.
Auf dem Weg dahin zusätzlich gefixt: GitHub-CDN-Abrisse bei app-builder-Downloads
(resumefähiges Prefetch-Skript im ThreadNet-Web-Repo, Jobs 415/416/424/431).
---
Weitere CI-Betriebsthemen laufen über die Projekt-Issues (ThreadNet-Web#5
Windows-Strecke, threadnet-call#1 npm-Ziel) und CFGMON-11 (Gitea-CI-Rückbau).
+100
View File
@@ -0,0 +1,100 @@
---
type: wiki-page
area: admin
related: []
---
# Refinement und Retro — die Termine des Frameworks
Kanban braucht wenige, aber verlässliche Termine, sonst verkommt das Board zur
Ablage. Festgelegt in [ADR-0005](../../adr/0005-pm-framework-kanban.md); hier
steht, wie sie ablaufen.
## Termine (festgelegt im Struktur-Workshop, 2026-08-06)
| Termin | Wann |
|---|---|
| **Refinement** | **sonntagabends**, wöchentlich, 3045 min |
| **Retro light** | im **ersten Refinement des Monats**, +2030 min |
Sonntag, weil die GitLab-Backups dort ohnehin laufen (00:00) — die Woche hat an
dieser Stelle eine Kante, und die Abendblöcke, in denen real gearbeitet wird,
liegen meist am Wochenende.
Die Retro bekommt **keinen eigenen Termin**: Ein monatlicher Extra-Termin im
Solo-Betrieb ist ein Termin, der ausfällt. Sie hängt sich an das erste Refinement
des Monats an — dann ist die Vorbereitung (die AARs des Monats) ohnehin offen.
## Refinement (≈ wöchentlich, 3045 min)
Der eine Termin, der das System am Leben hält. Immer dieselbe Reihenfolge:
1. **Board von rechts nach links lesen** — zuerst `status:doing`: Läuft es noch,
oder ist es in Wahrheit blockiert? Dann `status:wartet`: Wartet es noch auf das,
was im Issue steht? Erst zuletzt `status:next`.
2. **WIP-Limit prüfen** — höchstens zwei Issues in `doing`. Ist es voll, wird nichts
Neues gezogen; stattdessen wird gefragt, was das Laufende blockiert.
3. **Nachziehen** — freie Plätze aus `next` füllen, `next` aus dem Backlog auffüllen.
Auswahlkriterium ist nicht Priorität allein, sondern **was still kaputtgeht**
(Fristen, abgeschaltete Schutzmechanismen) vor **was nervt** vor **was Spaß macht**.
4. **Entscheidungsvorlagen** — offene Fragen, die eine Entscheidung von sorb brauchen,
werden als Optionen mit Empfehlung vorgelegt, nicht als offene Fragen geparkt.
Dauerhafte Ausnahmen von Regeln werden hier zu ADRs.
5. **Datumspflicht prüfen** — jedes zeitkritische Issue trägt ein Datum, kein „bald".
## Retro light (≈ monatlich, 2030 min)
Drei Fragen, mehr nicht:
- Welche **Verfahren** haben diesen Monat getragen, welche haben gestört?
- Welche **ADRs** sind durch die Realität überholt (→ neues ADR, altes auf
„abgelöst durch")?
- **Fasert etwas aus?** Gibt es wieder Arbeit, die nur in Chatverläufen lebt?
Grundlage sind die AARs des Monats — sie sind die Retro-Vorbereitung, nicht ihr
Ersatz.
Ergebnisse werden unter [`docs/sources/protokolle/`](../../sources/protokolle/retro-2026-08-09.md) abgelegt, eine Datei je Termin. Die
erste: [2026-08-09](../../sources/protokolle/retro-2026-08-09.md).
## AAR (anlassbezogen)
Nach jedem Deploy mit Übergabe und nach jedem Incident, Vorlage in
[docs/aar/template.md](../../aar/template.md). Ein AAR ist keine Chronik, sondern ein
Wissensspeicher: Was war das Ergebnis, welche Befunde, was hat die Eingrenzung
ermöglicht, welche Lehren, was bleibt offen. **Offene Punkte aus einem AAR werden
im selben Zug zu Issues** — sonst versacken sie in der Prosa (real passiert am
2026-08-01, nachgezogen als #14#16).
## Board-Pflege, wenn sorb länger nicht dazukommt
Festgelegt 2026-08-06. Eine Session darf das Board **abbilden**, aber nichts
**zusagen**:
| erlaubt | nicht erlaubt |
|---|---|
| `status:wartet` setzen (mit benanntem Grund) | nach `status:doing` ziehen |
| Erledigtes schließen, mit Begründung im Issue | `status:next` vergeben |
| Fristen ins `due_date`-Feld nachtragen | Prioritäten umsortieren |
| Befunde als neues Issue anlegen | Milestones neu zuordnen |
Die Trennlinie ist nicht Vorsicht, sondern Bedeutung: `doing` und `next` sind die
**Zusage-Spalten** — sie sagen, was als Nächstes wirklich passiert. Das entscheidet
sorb. Alles links davon bildet nur ab, was ohnehin schon der Fall ist.
**Jede Änderung wird im Issue begründet**, nicht still vorgenommen. Ein Board, dem
man nicht ansieht, wer warum etwas verschoben hat, ist beim nächsten Refinement
wertlos.
## Zusammenspiel mit den Sessions
Mehrere Claude-Sessions arbeiten parallel (Mac-Session, Host-Sessions auf CFGMON
und Overmind). Für sie gilt:
- Die **kanonischen Arbeitskonventionen** stehen in [`CLAUDE.md`](../../../AGENTS.md) und
sind über den Gitea-Mirror von überall lesbar.
- Arbeit zwischen Sessions läuft über das
[Deploy-Übergabe-Verfahren](../deployment/deploy-uebergabe.md) — Auftrag, Meldung, Protokoll
im Issue, nicht im Chat.
- Was eine Session lernt, gehört ins Repo (AAR/ADR/Doku), nicht nur in ihr
Gedächtnis — Sessions gehen verloren, Repos nicht.
+77
View File
@@ -0,0 +1,77 @@
---
type: wiki-page
area: admin
related: []
---
# Stillstandsprüfung
Sucht Dinge, die **leise aufgehört haben zu funktionieren**. Beschlossen in der
[Retro 2026-08-09](../../sources/protokolle/retro-2026-08-09.md).
## Warum es sie gibt
In neun Augusttagen sind sechs Fehler aufgefallen, die alle dasselbe Merkmal
hatten: Sie sahen erfolgreich aus, ohne es zu sein — eine grüne Pipeline, die nie
ein Artefakt hochlud; ein veröffentlichtes npm-Paket ohne Inhalt; ein Blueprint,
der bei jedem Lauf verworfen wurde, während Flux grün meldete.
**Keiner davon wurde durch eine Überwachung gefunden.** Vier durch Zufall beim
Suchen nach etwas anderem. Genau diese Lücke schließt das Skript.
## Was geprüft wird
Jede Prüfung bildet einen **real passierten** Fall ab. Nichts steht hier auf
Vorrat.
| Prüfung | Der Fall dahinter |
|---|---|
| Repo ohne aktiven Push-Mirror | `game-operating` wurde angelegt und nie gespiegelt — auf Gitea existierte es nicht |
| Mirror-Drift | MIRROR-01 (management#28): fällt der Mirror aus, liefert Flux still den letzten Stand weiter |
| Pipeline mit null Jobs | ThreadNet-Web 203/204, threadnet-call 187 — rot, ohne dass etwas kaputt war |
| Erfolgreicher Job ohne Artefakt | `build_embedded` lief seit jeher grün und lud **nichts** hoch |
| npm-Paket zu klein | `0.19.2-threadnet.6`: 12,5 KB statt 12,8 MB, ohne `dist/` |
| Authentik-Blueprint ≠ successful | `matrix-recovery-flow` wurde tagelang bei jedem Lauf verworfen |
Die Projektliste wird **zur Laufzeit aus der Gruppe gelesen**, nicht im Code
gepflegt — eine Liste im Quelltext wäre genau die Stelle, an der ein neues Repo
jahrelang durchrutscht. (Beim ersten Lauf kamen so zwei Projekte zum Vorschein,
die niemand auf dem Schirm hatte.)
## Wie sie läuft
Geplanter CI-Job im management-Repo, zusätzlich von Hand über *Run pipeline*
auslösbar. Befunde färben die Pipeline **rot** — das ist bei uns die Alarmanlage,
nicht ein zusätzlicher Meldeweg (siehe `gitops/CLAUDE.md` zur TURN-Rotation).
Lokal:
```bash
export GITLAB_TOKEN=$(cat ~/.config/gitlab-lab/token)
export GITEA_TOKEN=$(cat ~/.config/gitea-rohana/push-token) # fuer private Spiegel
export LAB_CA=.../ci/lab-ca-chain.crt
python3 scripts/stillstandspruefung.py
```
## Zwei Regeln für diese Prüfung
**Ein „kann nicht geprüft werden" ist ein Befund, kein Übersprungen.** Real
aufgefallen am 2026-08-09: `game-operating` wurde auf Gitea privat gestellt, und
die Prüfung übersprang den Mirror-Abgleich klaglos. Ein Repo, das gespiegelt wird,
dessen Gegenseite aber unlesbar ist, ist **ungeprüft** — und das darf nicht wie
„in Ordnung" aussehen.
**Ein Befund wird zum Issue, nicht weggeklickt.** Sonst wird die Prüfung zu dem,
was sie sucht: etwas, das läuft, ohne dass jemand hinsieht.
⚠️ **Fehlt ein Zugang, bricht sie ab — sie überspringt sich nicht still.** Eine
Prüfung, die sich bei fehlendem Token selbst deaktiviert, ist wertlos: Sie meldet
dann jahrelang nichts, und niemand merkt den Unterschied zu „alles in Ordnung".
Ausnahme sind die klar benannten optionalen Teile (Authentik), die ihr Fehlen im
Ergebnis ausweisen.
## Erweitern
Neue Prüfungen kommen dazu, **wenn wieder etwas still ausgefallen ist** — mit einem
Docstring, der den konkreten Fall nennt. Prüfungen auf Verdacht erzeugen Rauschen
und kosten die Glaubwürdigkeit, die diese hier braucht.
+122
View File
@@ -0,0 +1,122 @@
---
type: wiki-page
area: admin
related: []
---
# Textbausteine für Sessions
Kurze, kopierbare Blöcke, die man einer Claude-/Agenten-Session voranstellt.
Die Konventionen stehen kanonisch in [`CLAUDE.md`](../../../AGENTS.md) — aber eine
Session liest sie nur, wenn sie dazu aufgefordert wird. Diese Bausteine sind die
Aufforderung.
## Zwei Regeln für diese Datei
**Die Bausteine verweisen auf die Regeln, sie wiederholen sie nicht.** Stünden die
Regeln hier ausgeschrieben, gäbe es eine zweite Fassung, die driftet — real
passiert am 2026-08-02, als `gitops/CLAUDE.md` „keine Gitea-Ausnahme mehr" behauptete,
während die `management/CLAUDE.md` zwei nannte.
**Höchstens acht Zeilen je Baustein.** Der Test ist banal: Wer zum Kopieren scrollen
muss, benutzt es nicht. Was länger wäre, gehört in die CLAUDE.md — nicht hierher.
---
## 1 · Session-Start (Mac, mit Lab-Zugang)
```
Lies zuerst CLAUDE.md im management-Repo auf git.lab und halte dich daran.
Kanonisch ist git.lab; nie direkt nach Gitea pushen.
Alles Offene wird zum Issue, nicht zur Chat-Notiz — auch Nebenbefunde.
Bevor du ein Issue schließt oder darüber urteilst: vollständig lesen, inklusive
Kommentare.
Bevor du aus einer Vorlage/Spezifikation ableitest: die Quelle öffnen, nicht raten.
Verifiziert und vermutet klar trennen; fremde Messungen als fremde kennzeichnen.
```
> Die letzten drei Zeilen stehen hier, weil genau das dreimal an einem Tag
> schiefging: erfundene Theme-Paletten statt gelesener Skill-Quelle; ein Sweep nach
> dem Pfad `sorb/Backlogs` statt nach dem Namen `Backlogs`; und ein Issue, von dem
> 750 von 1237 Zeichen gelesen wurden — samt übersehenem Korrekturkommentar, der
> seit 16 Stunden darunterstand.
## 2 · Host-Session (CFGMON, MATRIX — ohne Lab-Zugang)
```
Du arbeitest auf einem Hetzner-Host ohne direkte Lab-Route.
Konventionen: CLAUDE.md im management-Repo — von hier lesbar über den Gitea-Mirror
rohana.axion1337.de/sorb/management. Dort NUR lesen, niemals hinpushen.
Für git.lab (Issues, Pushes) muss sorb erst den Site-to-Site-Tunnel einschalten.
git.lab-API: PRIVATE-TOKEN-Header — .netrc gilt nur für clone/push (sonst 401,
bei privaten Projekten irreführend 404, sieht aus wie "Projekt gibt es nicht").
Ping auf 10.58.73.17 schlägt IMMER fehl (nur 443 + DNS offen), das ist kein
Tunnelproblem — prüfen mit: curl https://git.lab/users/sign_in
```
> Soll in dieser Session etwas ausgerollt werden, kommt **Baustein 3** dazu — der
> Deploy-Weg samt AAR-Pflicht steht dort, nicht hier, damit dieser Block kurz bleibt.
## 3 · Deploy-Übergabe
```
Öffne auf git.lab ein Issue aus der Vorlage "Deploy-Übergabe"
(Feld "Description template") und fülle ALLE Felder — Verfahren und Begründung
je Feld: verfahren/deploy-uebergabe.md.
Pflicht: Stand (Repo/Branch/Commit) · Testtiefe (ehrlich, "ungetestet" ist gültig)
· Mengengerüst (geschätzt oder gemessen, dazuschreiben welches) · vollständiges
Deploy-Kommando inkl. Reload/Recreate · Verifikation DORT WO DER DIENST LIEST
· Außenwirkung und Not-Aus · Rollback · bewusst offen Gelassenes.
Wo nichts zutrifft: "-" eintragen, nicht das Feld löschen.
```
## 4 · Abschluss einer Session
> **Dieser Baustein ist zugleich unsere Definition of Done für Änderungen ohne
> Deploy** (festgelegt 2026-08-06). Für Deployments gilt weiterhin das
> [Übergabe-Verfahren](../deployment/deploy-uebergabe.md) — das ist die längere DoD.
>
> Bewusst kein eigenes DoD-Dokument: Es wäre die dritte Fassung derselben Regeln
> und damit die dritte, die driften kann.
```
Vor dem Ende prüfen und benennen:
- Alle Commits über git.lab gepusht, kein Rest im Arbeitsverzeichnis, Mirror grün.
- Jeder offene Punkt und Nebenbefund ist ein Issue — nichts bleibt nur im Chat.
- Zeitkritisches trägt ein Datum im due_date-Feld, nicht nur im Fließtext.
- Genau ein status:*-Label je angefasstem Issue; status:wartet nur mit Grund.
- Gedächtnis aktualisiert: nur was kein Repo festhält.
- Wiederaufsetzpunkt in einem Satz: Was ist als Nächstes dran, und wer ist dran?
```
## 5 · Entscheidungsvorlage
```
Leg mir das als Entscheidung vor, nicht als offene Frage:
24 Optionen, je eine Zeile Konsequenz, und deine Empfehlung zuerst mit Begründung.
Sag dazu, was du gemessen und was du angenommen hast.
Wenn die Entscheidung eine dauerhafte Ausnahme von einer Regel schafft, ist sie
ADR-pflichtig (decisions/, siehe CLAUDE.md) — dann leg die ADR gleich mit vor.
```
---
## Wann welcher
| Situation | Baustein |
|---|---|
| Neue Session auf dem Mac | 1 |
| Session auf CFGMON/MATRIX/game | 2 |
| Etwas gebautes soll ausgerollt werden | 3 |
| Session neigt sich dem Ende | 4 |
| Eine Frage braucht sorbs Entscheidung | 5 |
Bausteine 1 und 2 schließen sich aus; 35 kommen anlassbezogen dazu.
## Pflege
Ein Baustein wird ergänzt, wenn **derselbe Fehler zweimal** passiert ist — nicht
vorsorglich. Sonst wachsen sie, bis sie niemand mehr kopiert, und dann wirken sie
gar nicht mehr. Wächst einer über acht Zeilen, gehört der Inhalt in die CLAUDE.md
und hier bleibt der Verweis.
+220
View File
@@ -0,0 +1,220 @@
---
type: wiki-page
area: architecture
related: []
---
# Branding: Marke, Farben, Bildsprache
Übergreifend, weil dieselben Farben in mehreren Oberflächen eingestellt werden
(Chat-Clients, Wikis, künftige Dienste) und sich das nicht pro Host oder Linie
trennen lässt: **Bestand** (welche Farbe wo eingetragen ist) und **Historie**
(was wann warum geändert wurde).
Hier im `management`-Repo, weil es als einziges der beteiligten Repos
**gespiegelt** ist und jede Werkzeugentscheidung überlebt: Wird das
BookStack-Experiment nach [ADR-0007](../../adr/0007-wiki-oberflaeche-docusaurus-vs-bookstack.md)
abgeräumt oder ein Client ersetzt, bleiben Marke und Paletten bestehen.
## Bildmarke
`threadnet-mark.png` (Bildmarke) und `threadnet-logo-wortmarke.png` (mit
Schriftzug), erstellt von sorb. Sie liegen im Wiki-Repo unter `static/img/` und
sind die Quelle für alle abgeleiteten Formate — Favicon, App-Icons für macOS,
Windows und Linux, Web-Icons.
⚠️ Beim Ableiten den **transparenten Rand wegschneiden** und mit `resize()`
skalieren, nicht mit `thumbnail()` — Letzteres verkleinert nur und lässt das Motiv
in großen Icons als Briefmarke zurück (real passiert, siehe
[AAR 2026-08-02](../../aar/2026-08-02-wiki-und-desktop-clients.md)).
⚠️ **Und danach mittig setzen — das ist ein zweiter, eigener Schritt.** Beim ersten
Anlauf wurde nur beschnitten und skaliert: Das Motiv füllte 81 % der Breite, saß
aber mit 3 % Rand oben und 40 % unten an der Oberkante. In runden und quadratischen
Icon-Slots fällt das sofort auf. Korrigiert am 2026-08-06 auf 21 % oben wie unten.
**Das Rezept, das jetzt gilt:** Bounding-Box des Motivs ermitteln, darauf
beschneiden, auf ~81 % der Kantenlänge skalieren, auf einer quadratischen,
transparenten Fläche **mittig einsetzen**. Alle Größen aus *derselben* Quelldatei
ableiten — dann können sie nicht auseinanderlaufen.
### Wo überall Icons liegen
Elf Artefakte, alle aus einer Quelle (Stand 2026-08-06, `v0.4.0`):
| Ort | Was |
|---|---|
| `apps/web/res/vector-icons/` | 1024, 512, 180, 152, 144, 120, 24 px |
| `apps/desktop/build/icon.png` | App-/Installer-Icon |
| `apps/desktop/build/icon.ico` | Windows, 7 Größen von 16 bis 256 |
| `apps/desktop/build/icon.icns` | macOS, via `iconutil` aus einem `.iconset` |
| `apps/desktop/build/icon.icon/Assets/element.png` | Layer des macOS-Icon-Composers |
Prüfen lässt sich die Gleichheit über die Prüfsumme von `vector-icons/1024.png`
gegen `build/icon.png` — weichen sie ab, ist eine Seite nachgezogen worden und die
andere nicht.
## Paletten
### aXion1337 Dark — das Stammschema
Gruvbox Dark. Grundtöne `#282828` / `#1d2021`, Text `#ebdbb2`, Akzent `#bd93f9`,
Sekundär `#fe8019`. Die acht Username-Farben sind der Gruvbox-Satz.
**`aXion1337 Light` ist das exakte helle Gegenstück** (Gruvbox Light) — gleiche
Rollenverteilung, gespiegelte Helligkeitsachse. Wer eines ändert, zieht das andere
mit.
### Terrakotta-Beige — sorbs BookStack-Schema
Am 2026-08-02 in der BookStack-Oberfläche eingestellt und von dort extrahiert
(die Werte stehen als CSS-Variablen im ausgelieferten HTML). Fünf der sieben sind
der Coolors-Satz `#264653 · #2A9D8F · #E9C46A · #F4A261 · #E76F51`:
| Rolle in BookStack | Wert | Ton |
|---|---|---|
| Primäre Farbe | `#264653` | Charcoal |
| Standard-Linkfarbe | `#5f757b` | gedämpftes Blaugrau |
| Regalfarbe | `#e76e51` | Burnt Sienna |
| Buchfarbe | `#e9c46a` | Saffron |
| Kapitelfarbe | `#f3a261` | Sandy Brown |
| Seitenfarbe | `#77bb41` | Grün |
| Seitenentwurfsfarbe | `#e32400` | Rot |
Bemerkenswert: **dieses von Hand eingestellte Schema ist bis auf zwei Ziffern die
offizielle Sunset-Boulevard-Palette** (siehe nächster Abschnitt) — `#e76e51` statt
`#e76f51`, `#f3a261` statt `#f4a261`. Beide Wege sind unabhängig voneinander auf
demselben Coolors-Satz gelandet. Die Irritation an der ersten Theme-Fassung war
also berechtigt und ließ sich an der Originalquelle belegen.
### theme-factory — die zehn benannten Themes
Quelle ist Anthropics [theme-factory-Skill](https://github.com/anthropics/skills/tree/main/skills/theme-factory):
je Theme vier Farben plus ein Schriftpaar. Sie sind seit 2026-08-02 **wörtlich
übernommen** (gitops `b10b607`), nachdem eine erste Fassung die Namen frei
interpretiert hatte.
| Theme | Hintergrund | hell/dunkel | Akzent · Sekundär · Highlight |
|---|---|---|---|
| Ocean Depths | `#f1faee` | hell | `#2d8b8b` · `#a8dadc` · `#457b9d` |
| Sunset Boulevard | `#264653` | dunkel | `#e76f51` · `#f4a261` · `#e9c46a` |
| Forest Canopy | `#faf9f6` | hell | `#2d4a2b` · `#7d8471` · `#a4ac86` |
| Modern Minimalist | `#ffffff` | hell | `#36454f` · `#708090` · `#d3d3d3` |
| Golden Hour | `#4a403a` | dunkel | `#f4a900` · `#c1666b` · `#d4b896` |
| Arctic Frost | `#fafafa` | hell | `#4a6fa5` · `#d4e4f7` · `#c0c0c0` |
| Desert Rose | `#5d2e46` | dunkel | `#d4a5a5` · `#b87d6d` · `#e8d5c4` |
| Tech Innovation | `#ffffff` | hell | `#0066ff` · `#00ffff` · `#1e1e1e` |
| Botanical Garden | `#f5f3ed` | hell | `#4a7c59` · `#f9a620` · `#b7472a` |
| Midnight Galaxy | `#e6e6fa` | hell | `#2b1e3e` · `#4a4e8f` · `#a490c2` |
⚠️ **Ob ein Theme hell oder dunkel gemeint ist, steht nicht verlässlich in den
Beschreibungen** — „Warm Sand · backgrounds" findet sich bei einem Theme, dessen
Showcase-Seite dunkel ist. Maßgeblich ist `theme-showcase.pdf` im Skill: sieben der
zehn sind hell, nur Sunset Boulevard, Golden Hour und Desert Rose dunkel.
„Midnight" Galaxy ist trotz des Namens ein helles Theme mit dunkelviolettem Akzent.
Wer die Werte prüfen will, rendert die PDF-Seiten und misst die Hintergrundfarbe,
statt der Prosa zu glauben.
Die übrigen Rollen (Flächenabstufungen, Username-Farben) sind aus diesen vier
Farben gemischt — so bleibt jedes Theme in sich stimmig, ohne dass Werte
dazuerfunden werden.
## Namensgebung: ThreadNet oder aXion1337.Chat?
**Beides, auf verschiedenen Ebenen — und das ist Absicht, kein Versehen.**
| Ebene | Name | Wo gesetzt |
|---|---|---|
| Betriebssystem, Startmenü, Installer, PWA | **ThreadNet** | `productName` in `apps/desktop/axion1337/build.json`, `name` in `apps/web/res/manifest.json` |
| in der Anwendung | **aXion1337.Chat** | `brand` in `element-values.yaml` (Prod) und `apps/desktop/axion1337/config.json` |
| eingebettetes Call-Widget | **aXion1337.Chat** | `VITE_PRODUCT_NAME` in `.env.production` (threadnet-call) |
| Anmeldeseite (Authentik) | **ThreadNet** | `branding_title` im Brand-Blueprint (gitops, `apps/authentik/authentik-blueprints.yaml`) |
Das Call-Widget folgt der Zeile darüber: Es läuft *in* der Anwendung, also heißt es
dort auch so. Die Anmeldeseite dagegen kommt **vor** der Anwendung — dort meldet man
sich am Werkzeug an, nicht in der Gemeinschaft. Deshalb ThreadNet.
Die Leitplanke dahinter steht in [`vision/threadnet.md`](../vision/threadnet.md):
*ThreadNet ist das Tool, axion1337.chat die Community.* Das Programm heißt
ThreadNet — auch wenn es jemand für eine andere Instanz nutzt; die Gemeinschaft
darin heißt aXion1337.Chat.
⚠️ **Nicht „geradeziehen".** Wer nur eine der beiden Stellen sieht, hält es für
eine Inkonsistenz. Ist es nicht.
**Attribution:** „ThreadNet — powered by Element" steht seit `v0.4.0` in
*Einstellungen → Hilfe & Info*, direkt unter der Client-Version, verlinkt auf
element.io. Element Web steht unter der AGPL; ein Fork unter eigenem Namen ist
erlaubt, die Nennung ist die faire Form davon. Bewusst **nicht** im Kopiertext der
Versionsangabe — der landet in Fehlerberichten, dort ist die Fork-Herkunft nur
Rauschen.
## Wo was eingestellt ist
| Oberfläche | Ort | Anmerkung |
|---|---|---|
| Element/ThreadNet-Web | `apps/production/custom-configs/element-values.yaml` (gitops), `setting_defaults.custom_themes` | 17 Themes; Änderungen chirurgisch, **nie die YAML neu serialisieren** |
| Web-Icons + PWA | `apps/web/res/vector-icons/`, `apps/web/res/manifest.json` (ThreadNet-Web) | `theme_color` = `#ed4f4c`, die Markenfarbe — nicht Elements `#76CFA6` |
| Desktop-Icons | `apps/desktop/build/` (ThreadNet-Web) | `.png`, `.ico`, `.icns`, Layer-Asset — alle aus derselben Quelle |
| ThreadNet Desktop | `apps/desktop/axion1337/config.json` (ThreadNet-Web) | eigene Kopie derselben Themes — beim Ändern beide mitziehen |
| BookStack | *Settings → Customization*, getrennt für hell und dunkel | liegt in der Datenbank, **nicht im Repo** — schriftlich hier und in `theme/sorbs-palette.md` |
| BookStack (Feinschliff) | `theme/*.css` im Wiki-BookStack-Repo | nur Flächen, Text, Ränder — die sieben Farben oben gehören in die Oberfläche |
| Docusaurus-Wiki | `src/css/custom.css` (homelab/wiki) | bislang nur Akzentfarbe |
| Titelbild Login | `apps/web/res/themes/element/img/backgrounds/alpenglow.jpg` (ThreadNet-Web), gesetzt in `SdkConfig.ts` | siehe unten — Bilddatei kommt nur über einen Build in den Container |
| Anmeldeseite Authentik | Brand-Blueprint in `apps/authentik/authentik-blueprints.yaml` (gitops) | Favicon und Hintergrund werden **von axion1337.chat referenziert**, nicht hochgeladen. **Logo ist noch Authentiks eigenes** → gitops#55 |
### Titelbild
Seit 2026-08-06 zeigt die Login-Seite ein Alpenglühen über einer Bergkette statt
Elements See: **John Towner** ([@heytowner](https://unsplash.com/@heytowner)),
[Unsplash](https://unsplash.com/photos/JgOeRuGD_Y4), *Unsplash License*.
Die Lizenz erlaubt kommerzielle Nutzung und Bearbeitung ohne Genehmigung und
**verlangt keine Namensnennung**. Genannt wird er trotzdem — in *Einstellungen →
Hilfe & Info* unter „Danksagungen". Verboten wäre nur der Weiterverkauf
unbearbeiteter Bilder und das Nachbauen eines konkurrierenden Bilddienstes; beides
trifft uns nicht.
⚠️ **Die Danksagung ist bewusst nicht übersetzt.** Elements Schlüssel
`credits|default_cover_photo` liegt in 32 Sprachdateien, 31 davon nennen deren
Fotografen namentlich. Nur `en`/`de` anzupassen hätte in 29 Sprachen eine **falsche
Attribution** stehen lassen — und diese 29 pflegen wir nicht, sie kommen aus Elements
Übersetzungsdienst. Deshalb steht die Zeile als fester Text im TSX, wie schon die
„powered by Element"-Attribution.
⚠️ **Authentik hängt an dieser Datei.** Der Flow-Hintergrund dort zeigt auf
`https://axion1337.chat/themes/element/img/backgrounds/alpenglow.jpg`. Wer das Bild im
Client umbenennt oder entfernt, macht die Anmeldeseite grau — und merkt es nicht,
weil im Client alles stimmt.
### Warum in Authentik kein PNG-Logo funktioniert
Der erste Versuch setzte `branding_logo` auf `vector-icons/512.png`. Ergebnis: das
Logo rendert in der Anmeldemaske in **Naturgröße**. Authentiks Default ist ein SVG,
das sich seiner Box anpasst — ein PNG tut das nicht, und der Slot begrenzt die Höhe
nicht.
Zurückgesetzt am 2026-08-06 auf Authentiks eigenes Logo. Ein Ersatz braucht eine
**Wortmarke im Querformat, am besten als SVG**; alles Vorhandene ist quadratisch,
auch `threadnet-logo-wortmarke.png` (Bildmarke *über* Schriftzug). Offen in
gitops#55.
⚠️ **Nicht über Authentiks Oberfläche einstellen.** Solange `branding_logo` im
Blueprint steht, gewinnt der Blueprint: eine Auswahl in der UI hält bis zur nächsten
Reconciliation und ist dann wieder weg.
⚠️ **Das BookStack-Schema lebt nur in der Datenbank.** Bei einem Volume-Verlust
ist es weg — deshalb steht es oben in dieser Tabelle. Wiederherstellen heißt:
sieben Felder in der Oberfläche neu eintragen.
Es steht bewusst an **zwei** Stellen schriftlich, und die Rollen sind verschieden:
`theme/sorbs-palette.md` im BookStack-Repo ist die betriebsnahe Kopie mit den
DB-Schlüsseln, liegt aber in der Gruppe `homelab` — die hat **keine Mirrors** und
ist von außerhalb des Labs nicht lesbar. Diese Datei hier ist die gespiegelte
und damit maßgebliche Fassung. Wer die Farben ändert, zieht beide mit; im
Zweifel gilt, was in der laufenden Instanz eingestellt ist.
## Offen
Ob die Terrakotta-Richtung das Stammschema ablösen oder eine Alternative neben
Gruvbox bleiben soll, ist nicht entschieden — das gehört in die Rebranding-Runde
(→ [`vision/threadnet.md`](../vision/threadnet.md), ThreadNet-Web#10).
+133
View File
@@ -0,0 +1,133 @@
---
type: wiki-page
area: architecture
related: []
---
# Lab-Netzwerk (Heimnetz: Fritzbox + UDM Pro)
Themen rund um das Homelab-Netz selbst — Router-Kaskade, VLANs, VPN-Zugänge.
Hosts im Lab: Overmind (git.lab, [hosts/overmind.md](../admin/overmind.md)),
der Mac. Kaskade: **Fritzbox (WAN) → UDM Pro**, kein Doppel-NAT, statische
Route in der Fritzbox für das Lab-VLAN.
> ✅ **Das VPN-Thema ist abgeschlossen und validiert** (sorb, 2026-08-02).
> Beide Zugänge laufen und sind abgenommen, ADR-0004 steht auf *akzeptiert*
> (Testreihe 17 in [#12](https://git.lab/axion1337.chat/management/-/issues/12)).
> Es gibt dazu **keine offenen Issues mehr** — auch die Restpunkte #11
> (MacBook-Profil) und #16 (LABNET-04, Feinschliff an den UniFi-Regeln) sind
> geschlossen. Alles Folgende ist **Bestand und Historie**, keine offene Arbeit.
**Zwei WireGuard-Zugänge (Stand 2026-08-01, beide gelöst/abgenommen):**
| Zugang | Server | Port | Tunnelnetz | Zweck |
|---|---|---|---|---|
| Roadwarrior „Thore" | UDM | 51840 | 10.58.74.0/24 | Handy/MacBook ins Lab (LABNET-01) |
| Site-to-Site „Matrix" | UDM | 51841 | 10.58.75.0/24 | Hetzner-Netz 10.0.0.0/24 ↔ Lab (LABNET-02, [ADR-0004](../../adr/0004-site-to-site-vpn-hetzner-lab.md)) |
### Verhältnis zu `homelab/docs`
Die Tabelle oben steht **absichtlich doppelt**. Maßgeblich für die
Soll-Konfiguration ist
[homelab/docs → MorninglightMountain](https://git.lab/homelab/docs/-/blob/main/netz/morninglightmountain.md)
(dort auch Portfreigaben, Firewall-Zonen, Diagnose-Merksätze) — dieses Repo
führt die **Historie**: was wann warum geändert wurde und mit welchem Issue.
Der Grund für die Doppelung ist der Mirror-Geltungsbereich aus der
[CLAUDE.md](../../../AGENTS.md): Die Gruppe `homelab` hat bewusst **keine Mirrors** und
ist von außerhalb des Labs nicht lesbar. Wer ohne Tunnel nachsehen muss, welcher
Tunnel überhaupt auf welchem Port liegt, findet es nur hier. Deshalb hält dieses
Dokument einen Kurzüberblick vor — Ports, Tunnelnetze, Zweck — und nichts
darüber hinaus. **Bei Widerspruch gilt `homelab/docs`.**
---
## LABNET-01 — WireGuard-Roadwarrior ins Lab kaputt (seit einigen Monaten)
**Status:** GELÖST 2026-08-01 ~15:20 — `git.lab` lädt vom Handy über 5G/VPN. ✅
Damit ist die Cutover-Voraussetzung für gitops#48 erfüllt.
**Drei gestapelte Ursachen (jede verdeckte die nächste):**
1. **Privater Endpunkt** in jeder UDM-generierten Client-Config (UDM kennt hinter
der Fritzbox ihre öffentliche IP nicht) → Fix: Endpunkt `178.25.213.70`;
dauerhaft gelöst über UniFi-Option **„Alternate Address for Clients"**.
2. **FritzOS reserviert UDP 51820 für seinen eigenen WireGuard-Stack** — die
Portfreigabe 51820→UDM lief ins Leere (erklärt die „invalid response"-Stürme
im Juli: zwei WG-Stacks auf einem Port) → Fix: UDM-WG auf **51840**.
3. **Docker-Routen-Kollision auf Overmind**: Dokploys Bridge-Netze belegen zehn
/20-Blöcke in 192.168.0.0/16; `192.168.0.0/20` verschluckte das VPN-Subnetz
192.168.5.0/24 → Antworten an VPN-Clients endeten in der Bridge (SYN kam an,
SYN-ACK verschwand — exakt der Chrome-Connection-Timeout, während Ping/DNS/
fremde Hosts funktionierten) → Fix: **VPN-Subnetz auf 10.58.74.0/24** (Docker
fasst 10.x nie an).
**Restarbeiten:** MacBook-WG-Profil → [Issue #11](https://git.lab/axion1337.chat/management/-/issues/11). ⚠️ Latente Wiederholungsgefahr
notiert: Overminds Docker-Pool deckt auch `192.168.176.0/20` ab = kollidiert mit
dem Fritzbox-Netz 192.168.178.x — aktuell folgenlos, aber bei künftigen Subnetz-
Entscheidungen 192.168.x auf Overmind grundsätzlich meiden (oder Docker
default-address-pools begrenzen).
**Bestätigte Ursachenkette (Diagnose-Session 2026-08-01):**
1. Handy-Profil „@home" hatte seit 07.07. die **private** UDM-WAN-IP
(192.168.178.20) als Endpunkt — die UDM kennt hinter der Fritzbox ihre
öffentliche IP nicht und schreibt sie in jede generierte Config
(⚠️ gilt für ALLE künftig exportierten Profile: Endpunkt manuell auf
178.25.213.70 ändern!).
2. Nach Endpunkt-Fix weiter tot: **FritzOS reserviert UDP 51820 für seinen
EIGENEN WireGuard-Stack** — die alte Portfreigabe 51820→UDM lief ins Leere
(erklärt auch die „invalid response"-Stürme im Juli: zwei WG-Stacks auf
einem Port). Fix: UDM-WG auf **51840** umgezogen + Freigabe angepasst.
3. Server + Schlüssel waren nie das Problem (WLAN-Handshake bewies beides).
Danach nur noch der ursprüngliche Blocker-Vermerk:
blockierte gitops#48 (Erreichbarkeits-Entscheidung „WireGuard statt exponieren")
sorb hatte einen funktionierenden VPN-Zugang fürs Handy ins Lab; seit einigen
Monaten „funktioniert das nicht mehr sauber" (Symptome noch zu präzisieren:
Handshake? Routing nur teilweise? DNS?). Setup-Rahmen laut sorb (2026-08-01):
UDM Pro hinter Fritzbox, ohne doppeltes NAT, statische Route in der Fritzbox
für das VLAN.
**Diagnose-Plan von VOR der Lösung** — ⚠️ abgearbeitet und überholt, steht hier
nur als Beleg, wie die Ursachen eingekreist wurden. Nichts davon ist zu tun:
1. Symptom präzisieren: Handshake kommt zustande? (`wg show` auf der UDM /
Client-Log) — trennt Portweiterleitungs- von Routing-Problemen
2. Fritzbox: Portfreigabe UDP (WireGuard-Port) → UDM noch vorhanden/korrekt?
(FritzOS-Updates werfen gern Freigaben/Exposed-Host-Einstellungen um)
3. ~~DS-Lite~~ **ausgeschlossen** (sorb 2026-08-01: Dualstack + feste IPs) —
damit auch kein DynDNS-Drift möglich. Verdacht konzentriert sich auf:
FritzOS-Update warf die UDP-Portfreigabe um, UniFi-OS-Update veränderte
WG-Server/Firewall, oder die statische Route griff nach Änderung nicht mehr.
4. (entfällt — feste IP)
5. UDM-Seite: WireGuard-Server-Config/Firewall-Regeln nach UniFi-OS-Updates
prüfen; statische Route Fritzbox → VLAN gegenchecken
6. Erst wenn 15 sauber: Client-Profil fürs Handy neu ausstellen, git.lab-DNS
(Lab-Resolver) in die AllowedIPs/DNS-Konfig aufnehmen — Erfolgsbeweis =
Issue-Board vom Handy über VPN erreichbar
**Verwandt:** gitops#48 (Cutover erst nach Lösung), perspektivisch ersetzt ein
funktionierender Roadwarrior auch Ad-hoc-Wünsche wie „GitLab exponieren".
## Zugehörige Issues — alle geschlossen
Seit dem Framework-Umbau (2026-08-01) leben offene Punkte als Issues im
[management-Projekt](https://git.lab/axion1337.chat/management/-/issues); die IDs bleiben in den Issue-Titeln erhalten.
Dieses File hält nur noch Bestand und Historie.
Zum Netz/VPN ist **nichts mehr offen** (Stand 2026-08-02):
| Issue | Thema | Stand |
|---|---|---|
| [#11](https://git.lab/axion1337.chat/management/-/issues/11) | LABNET-01-Rest — MacBook-WireGuard-Profil | geschlossen |
| [#12](https://git.lab/axion1337.chat/management/-/issues/12) | LABNET-02 — Site-to-Site-VPN (Design: [ADR-0004](../../adr/0004-site-to-site-vpn-hetzner-lab.md)) | geschlossen, Testreihe 17 protokolliert |
| [#16](https://git.lab/axion1337.chat/management/-/issues/16) | LABNET-04 — Feinschliff UniFi-Regeln | geschlossen |
Zwei Punkte tragen zwar LABNET im Text, gehören aber **nicht** zum VPN-Thema und
bleiben offen: [#13](https://git.lab/axion1337.chat/management/-/issues/13)
(LABNET-03, Rückbau der Gitea-Ausnahme für Übergabe-Issues — durch den Tunnel
erst möglich geworden, aber eine Repo-Frage) und
[#15](https://git.lab/axion1337.chat/management/-/issues/15) (CFGMON-15,
Widerruf der Einmal-Tokens aus der LABNET-02-Nacht — Credential-Hygiene, und der
Widerruf kann still einen Push-Mirror brechen, solange dessen hinterlegtes Token
unbekannt ist).
@@ -0,0 +1,64 @@
---
type: wiki-page
area: architecture
related:
- "docs/adr/0001-gitlab-kanonisch-push-mirror.md"
- "docs/adr/0002-issues-und-management-ins-lab.md"
- "docs/adr/0004-site-to-site-vpn-hetzner-lab.md"
- "docs/adr/0006-wikis-konsolidieren-docusaurus.md"
---
# Mirror-Topologie: Das Lab ist die Quelle der Wahrheit
Begründungsprosa übernommen aus der alten `CLAUDE.md` (Abschnitt
„Projektrealitäten", Wortlaut in der git-Historie vor der
neckbeard-Migration); die verbindlichen Regeln stehen in `AGENTS.md` §6
und den verlinkten ADRs. Erhalten per Feldtest-Befund F-013: Diese
Begründung samt Gegenargument und Rettungspfad ist der Wert — sie
erklärt, warum die Topologie so aussieht und so bleiben soll.
- Kanonische Repos liegen auf `git.lab/axion1337.chat/*` (nur im
Lab/VPN auflösbar). Gitea/rohana wird per **Push-Mirror** beliefert
und bleibt Flux-Source, Container-/npm-Registry und Release-Download
([ADR-0001](../../adr/0001-gitlab-kanonisch-push-mirror.md)).
- **Warum überhaupt zwei Orte — und warum das kein Altbestand ist:**
Auf git.lab liegen die *Baupläne*, auf Gitea eine Kopie, die der
Cluster **ohne verfügbares Lab** erreicht. Der Hetzner-Cluster muss
sich bauen und neu ausrollen lassen, wenn das Homelab aus ist, im
Umbau steckt oder niemand zu Hause ist — er darf deshalb nicht von
einem Host abhängen, der nur im Lab antwortet.
⚠️ **Die Flux-Quelle nicht „geradeziehen"** auf git.lab: Das sähe
aufgeräumter aus und würde die Verfügbarkeit der Produktion an das
Lab koppeln — genau das, was die Trennung verhindert.
- **Gespiegelt wird nur die Gruppe `axion1337.chat`** (die Produkt-Repos
und `management`). Die Gruppe **`homelab`** (`docs`, `wiki`,
`wiki-bookstack`) hat bewusst **keine Mirrors**: Sie beschreibt und
konfiguriert ausschließlich Lab-Infrastruktur, und seit dem
Site-to-Site-VPN ([ADR-0004](../../adr/0004-site-to-site-vpn-hetzner-lab.md))
erreichen auch Host-Sessions git.lab direkt — Tunnel einschalten
genügt. Betriebslehren, die von außen lesbar sein müssen, gehören
deshalb in die **AARs** unter `docs/aar/` (dieses Repo ist
gespiegelt), nicht nur in die READMEs der Lab-Repos.
- Landet doch ein Commit auf Gitea (z. B. aus einer Host-Session ohne
Lab-Route): **Kanonisierungs-Verfahren** in
[deploy-uebergabe](../deployment/deploy-uebergabe.md) — `.patch` von
Gitea ziehen, `git am` (erhält Autorschaft), Push über git.lab.
- ⚠️ gitops-Issue-Nummern haben sich beim Gitea-Umzug verschoben (Gitea
zählte PRs mit; z. B. Gitea#48 → GitLab#46) — alte „gitops#N"-Verweise
meinen die Gitea-Nummer; verbindlich ist der Migrations-Fußtext im
Issue ([ADR-0002](../../adr/0002-issues-und-management-ins-lab.md)).
- **Ausnahme** (bewusst entschieden, nur noch eine): der
TURN-Rotations-CronJob schreibt weiter nach Gitea, weil er im Cluster
läuft und git.lab nicht erreicht. **Die Rotation nicht von Hand
nachziehen und den PR nie auf Gitea mergen** — das erledigt seit
2026-08-02 der geplante CI-Job `canonize_rotation` im gitops-Repo
täglich von git.lab aus. Scheitert er, bleibt die Pipeline rot; diese
rote Pipeline **ist** der Alarm, einen zusätzlichen Termin gibt es
bewusst nicht.
- **Dokumentation** ([ADR-0006](../../adr/0006-wikis-konsolidieren-docusaurus.md)):
Das gitops-Wiki liegt seit 2026-08-02 auf git.lab (*Wiki*-Reiter im
Projekt); ⚠️ der `wiki`-**Branch** im gitops-Repo ist ein überholter
Mai-Abzug von `docs/` und nicht die gepflegte Fassung. Alle Quellen
zusammen erscheinen unter **axionwiki.lab**
([`homelab/wiki`](https://git.lab/homelab/wiki), Docusaurus) —
Inhalte werden beim Bau geholt, **Änderungen gehören ins Quell-Repo**.
+148
View File
@@ -0,0 +1,148 @@
---
type: wiki-page
area: architecture
related: []
---
# DNS-Zone `axion1337.de` und Mail-Policy
Übergreifend, weil die Zone alle Hosts abdeckt und die Mail-Policy sich nicht pro
Host trennen lässt.
| | |
|---|---|
| **Registrar / DNS** | IONOS (`ns1098.ui-dns.biz`, `ns1022.ui-dns.com`, `ns1070.ui-dns.org`, `ns1059.ui-dns.de`) |
| **Apex** | `217.160.0.140` / `2001:8d8:100f:f000::2e9` — IONOS-Hosting, nicht eigene Infrastruktur |
| **Mail** | IONOS (`mx00.ionos.de`, `mx01.ionos.de`) |
| **Stand** | 2026-08-06 (Mail-Records gemessen) |
**Ist-Stand der Mail-Härtung** (gemessen 2026-08-06 über DoH, um den Lab-Resolver
zu umgehen):
| Name | `www` | MX | SPF | `_dmarc` | Bewertung |
|---|---|---|---|---|---|
| `rohana` | löst auf ❌ | gelöscht | **gelöscht** ⚠️ | fehlt | ⚠️ schwächer als vorher |
| `selendis` | weg ✅ | IONOS ❌ | `~all` ❌ | fehlt | unangetastet |
| `matrix` | löst auf ❌ | IONOS ❌ | `~all` ❌ | fehlt | offen |
| `game` | löst auf ❌ | — | — | fehlt | teilweise |
| **Apex** | legitim ✅ | IONOS (genutzt) | `~all` | **`p=none`** ⚠️ | siehe ZONE-02 |
Kein einziger Name trägt bisher `_dmarc`, alle erben damit `p=none` vom Apex.
> **Die Aufstellung ist nicht garantiert vollständig.** Zonentransfer ist verweigert,
> DNS erlaubt kein Enumerieren. Die Records unten stammen aus gezielten Abfragen und
> der IONOS-Oberfläche. Für eine vollständige Prüfung entweder die ungefilterte
> IONOS-Liste durchgehen oder die Certificate-Transparency-Logs abfragen (zeigt alle
> Namen, für die je ein Cert ausgestellt wurde).
## Bekannter Bestand
| Name | A | AAAA | Ziel |
|---|---|---|---|
| `axion1337.de` | `217.160.0.140` | `2001:8d8:100f:f000::2e9` | IONOS-Hosting |
| `www` | `217.160.0.140` | dito | IONOS-Hosting — hier ist `www` **legitim** |
| `rohana` | `188.245.193.243` | `2a01:4f8:c17:93eb::1` | CFGMON, Gitea |
| `selendis` | `188.245.193.243` | `2a01:4f8:c17:93eb::1` | CFGMON, Grafana |
| `game` | `157.90.155.206` | — | Pterodactyl |
| `matrix` | `49.13.132.245` | — | Matrix-Homeserver |
| `ftp` | `217.160.233.227` | `2001:8d8:1000:30f5:…` | IONOS-Default |
| `www.rohana`, `www.selendis`, `www.game`, `www.matrix` | wie ohne `www` | teils | überflüssig, siehe ZONE-01 |
Mail-Records existieren auf `selendis` und `matrix` (MX ×2, SPF, DKIM-CNAMEs,
`autodiscover`), auf `rohana` und `game` nicht.
## Was die Mail-Records eigentlich tun
Damit die Rezepte in [ZONE-01](https://git.lab/axion1337.chat/management/-/issues/5)
nicht als Zahlensalat dastehen: Vier Mechanismen, die zusammenspielen. **Keiner
schützt allein.**
### Das Grundproblem
Der Absender einer Mail (`From:`) ist frei wählbar — SMTP prüft ihn nicht. Jeder
kann `rechnung@rohana.axion1337.de` in den Umschlag schreiben. Die folgenden
Records sind die Möglichkeit, dem widersprechen, **bevor** jemand darauf hereinfällt.
### SPF — „diese Server dürfen für mich senden"
TXT-Record am Namen selbst. Listet die berechtigten Absender-IPs.
Entscheidend ist das **Ende** des Eintrags:
| Endung | Bedeutung | Wirkung beim Empfänger |
|---|---|---|
| `~all` | Softfail | „war nicht auf der Liste" → wird meist **trotzdem zugestellt**, evtl. markiert |
| `-all` | Hardfail | „war nicht auf der Liste" → **ablehnen** |
`v=spf1 -all` ohne jeden Server davor heißt: *Für diesen Namen sendet niemand.*
Genau die richtige Aussage für `rohana`, `selendis`, `matrix` — die verschicken keine
Mail (für `matrix` verifiziert in MATRIX-01: weder Synapse noch MAS senden).
⚠️ **Es darf nur EIN SPF-Record je Name existieren.** Ein zweiter erzeugt
`PermError`, und dann prüfen viele Empfänger **gar nicht mehr** — die Härtung
schlägt ins Gegenteil um. Deshalb: bestehenden TXT **editieren**, nie einen zweiten
anlegen.
### DKIM — „diese Mail wurde unterwegs nicht verändert"
Signatur mit einem privaten Schlüssel, öffentlicher Teil als CNAME/TXT im DNS
(`s1-ionos._domainkey…`). Beweist Unversehrtheit und Herkunft.
Für Namen, die nicht senden, sind die DKIM-Einträge schlicht **Ballast** — sie
signieren nichts. Sie können weg.
### DMARC — „und das tust du, wenn SPF oder DKIM nicht passen"
TXT unter `_dmarc.<name>`. SPF und DKIM stellen nur **fest**; DMARC sagt, welche
**Konsequenz** das hat:
| Policy | Wirkung |
|---|---|
| `p=none` | nur beobachten — **kein Schutz**, Mail wird zugestellt |
| `p=quarantine` | in den Spam-Ordner |
| `p=reject` | ablehnen |
⚠️ **DMARC wird vererbt.** Fehlt `_dmarc.rohana`, gilt die Policy des
organisatorischen Namens `axion1337.de`. Die steht heute auf **`p=none`**
([ZONE-02](https://git.lab/axion1337.chat/management/-/issues/6)) — **damit erben
alle Subdomains „kein Schutz"**, egal wie sauber ihr SPF ist.
Das ist der wichtigste Hebel der ganzen Zone: **Der Apex wirkt auf alle Namen
gleichzeitig.** Mit `sp=` lässt sich für Subdomains sogar eine strengere Policy
setzen als für den Apex selbst.
### Null-MX — „hier nimmt niemand Mail an" (RFC 7505)
Ein MX-Record mit dem Ziel `.` (Punkt) und Priorität 0.
Der Grund, warum **Löschen nicht reicht**: Findet ein Absender **keinen** MX,
weicht er per RFC 5321 auf den **A/AAAA-Record** aus und versucht, direkt an den
Webserver zuzustellen. Der Name sieht dann nach „nimmt vielleicht Mail an" aus.
Null-MX sagt stattdessen ausdrücklich *nein* — Absender brechen sofort ab.
### Warum die Kombination
| Record | Beantwortet die Frage |
|---|---|
| **Null-MX** | Nimmt dieser Name Mail **an**? → nein |
| **SPF `-all`** | Darf jemand für diesen Namen **senden**? → niemand |
| **DMARC `p=reject`** | Was tun, wenn doch jemand behauptet, es zu dürfen? → ablehnen |
Erst zusammen ergeben sie eine Aussage, auf die sich ein Empfänger verlassen kann.
Einzeln bleibt jeweils eine Lücke — und **ersatzloses Löschen** hinterlässt die
größte: keine Aussage ist schwächer als eine schlechte Aussage.
**Real eingetreten:** Bei `rohana` sind MX und SPF gelöscht, die Ersatz-Records
fehlen (gemessen 2026-08-06). Vorher gab es wenigstens ein Softfail-SPF, jetzt gar
keine Aussage mehr — dazu die geerbte `p=none` vom Apex. Der Halbfertig-Zustand ist
schwächer als der Ausgangszustand.
## Offene Punkte → git.lab-Issues
Seit dem Framework-Umbau (2026-08-01) leben offene Punkte als Issues im
[management-Projekt](https://git.lab/axion1337.chat/management/-/issues); die IDs bleiben in den Issue-Titeln erhalten.
Dieses File hält nur noch Bestand und Historie.
- [ZONE-01 — IONOS-Default-Records bereinigen (Rezepte im Issue; rohana/selendis in Arbeit)](https://git.lab/axion1337.chat/management/-/issues/5)
- [ZONE-02 — Apex-DMARC ist `p=none` und schützt nichts](https://git.lab/axion1337.chat/management/-/issues/6)
+100
View File
@@ -0,0 +1,100 @@
---
type: wiki-page
area: deployment
related: []
---
# Verfahren: Deploy-Übergabe
Für die Konstellation „einer baut, ein anderer rollt aus". Zweck ist nicht mehr
Prozess, sondern **weniger Rückfragen und weniger stille Fehlschläge**.
Eingeführt am 2026-08-01 nach dem Deploy der CVE-Pipeline (`gitops#47`), siehe
[aar/2026-08-01-cve-pipeline-gitops47.md](../../aar/2026-08-01-cve-pipeline-gitops47.md).
## Ablauf
1. Wer baut, öffnet **auf git.lab** ein Issue aus der Vorlage **Deploy-Übergabe**
(`.gitlab/issue_templates/Deploy-Übergabe.md`, im Feld *Description template*).
2. Wer ausrollt, arbeitet die Prüfliste unten ab und deployt.
3. Wer ausrollt, hängt den **AAR** als Kommentar an dasselbe Issue
(Vorlage: [docs/aar/template.md](../../aar/template.md)). Bei Befunden ab MEDIUM
zusätzlich als Datei unter `docs/aar/`.
## Die vier Punkte, die den Unterschied machen
### 1. Mengengerüst vor dem Deploy
Bei allem, was etwas erzeugt — Nachrichten, Alarme, Zeitreihen, Requests —
gehört die erwartete Anzahl beim **ersten** Lauf in die Übergabe. Geschätzt ist
in Ordnung, gemessen ist besser; welches von beidem, muss dabeistehen.
Der Grund: „ein Alarm pro Fund" ist eine völlig unauffällige Zeile im Code und
harmlos bei 5 Funden. Bei 126 ist es ein Ausfall. Der Unterschied steht nirgends
im Diff — er ergibt sich erst aus den Daten, gegen die das Ding läuft.
Eine Stichprobe reicht: drei repräsentative Elemente von 29 messen und
hochrechnen kostet Minuten und liefert die Größenordnung.
### 2. Verifikation dort, wo der Dienst liest
Ein grüner Linter belegt, dass eine Datei **gültig** ist — nicht, dass sie
**geladen** wurde. Diese beiden Aussagen sind bei Bind-Mounts, Caches und
Reload-Semantiken regelmäßig verschieden.
Also im Container prüfen, in der laufenden API, im tatsächlich geladenen
Regelwerk. Ein Blick auf die Platte beweist nichts über den Prozess.
### 3. Deploy-Kommando vollständig übergeben
Inklusive Reload-, Restart- und Recreate-Schritten. Ein `docker compose up -d`,
das `Running` meldet und dabei nichts aktiviert, ist der häufigste stille
Fehlschlag: kein Fehler, kein Log, falscher Zustand.
Konkret auf dem Monitoring-Stack (CFGMON): Einzeldatei-Mounts hängen am Inode,
`git pull` benennt beim Schreiben um und erzeugt damit einen neuen — der
Container zeigt danach weiter auf die alte Datei. Es braucht
`--force-recreate`. Details: `gitops#52`.
### 4. Zustellwege stumm schalten statt Deploy verschieben
Datensammlung und Außenwirkung lassen sich fast immer getrennt scharf schalten.
Wenn der Zustellweg das Risiko ist, wird **er** abgeklemmt — nicht der ganze
Deploy verschoben.
So läuft die Datensammlung ab sofort, das Dashboard steht, echte Zahlen
ersetzen die Schätzung, und die Entscheidung über die Zustellung fällt auf
Basis von Messwerten statt Vermutungen. Wichtig: die Stummschaltung gehört
committet und dokumentiert, sonst ist sie in zwei Wochen ein Rätsel.
## Prüfliste für den Ausrollenden
<!-- pruefe-prosa:ok (Checkliste des Verfahrens, keine offene Aufgabe) -->
- [ ] Diff gelesen, nicht nur die Beschreibung
- [ ] Mengengerüst plausibel? Bei Zweifel an einer Stichprobe selbst messen
- [ ] Configs mit den jeweiligen Werkzeugen validiert (`promtool`, `amtool`,
`compose config`, `py_compile` …)
- [ ] Außenwirkung identifiziert — was verlässt beim ersten Lauf das System?
- [ ] Nach dem Deploy **im Container** verifiziert, dass die neue Config aktiv ist
- [ ] Geprüft, ob vorher gesunde Dinge noch gesund sind (keine stille Regression)
- [ ] AAR geschrieben, Folge-Issues angelegt, Stummschaltungen dokumentiert
## Abgrenzung
Das Verfahren gilt für Übergaben zwischen Personen. Wer baut **und** ausrollt,
braucht kein Issue — der AAR lohnt trotzdem, sobald es Befunde ab MEDIUM gab.
## Kanonisierung nach CFGMON-Deploys (Topologie-Pflichtschritt)
CFGMON erreicht git.lab nicht — Commits aus Deploy-Sessions landen zwangsläufig
direkt auf dem Gitea-Mirror und werden vom nächsten Mirror-Lauf **kommentarlos
überschrieben** (zweimal passiert: dfe04c4→6ffab68 am 01.08. nachts,
2b715ca→0bd77e2 am 01.08. nachmittags). Deshalb gehört zu jeder Übergabe:
1. **CFGMON-Seite** vermerkt den Commit-Hash im Übergabe-Issue (Feld „Stand").
2. **Mac-Seite** kanonisiert zeitnah: Patch per
`https://rohana.axion1337.de/sorb/<repo>/commit/<sha>.patch` ziehen
(verwaiste Objekte bleiben eine Weile abrufbar), `git am`, Push nach git.lab.
Der Hash ändert sich dabei — **Autorschaft und Inhalt bleiben erhalten**.
3. **CFGMON** vor dem nächsten Pull: `git fetch && git reset --hard origin/main`
(inhaltsgleich, nur neuer Hash).
+41
View File
@@ -0,0 +1,41 @@
---
type: wiki-page
area: index
related: []
---
# Wiki-Index
Areas sind Ordner; leere Ordner werden nie angelegt — Struktur entsteht
mit Inhalt. Projektfassung des neckbeard-Index (Original:
`docs/sources/upstream/neckbeard-v0.1.1/`), erweitert um die Area
`vision` (Alt-Wert „eine Datei je Linie", ADR-0005).
## Areas
| Area | Enthält | Stand |
|---|---|---|
| `admin/` | Betrieb: [cfgmon](admin/cfgmon.md) · [game](admin/game.md) · [matrix](admin/matrix.md) · [overmind](admin/overmind.md) · [Refinement & Retro](admin/refinement.md) · [Stillstandsprüfung](admin/stillstandspruefung.md) · [Textbausteine](admin/textbloecke.md) | belegt |
| `deployment/` | [Deploy-Übergabe](deployment/deploy-uebergabe.md) (Definition of Done, Kanonisierungs-Verfahren) | belegt |
| `architecture/` | [Mirror-Topologie](architecture/mirror-topologie.md) · [Lab-Netz](architecture/lab-netzwerk.md) · [DNS-Zone](architecture/zone-axion1337.md) · [Branding](architecture/branding.md) | belegt |
| `vision/` | Eine Datei je Linie: [axion1337.chat](vision/axion1337-chat.md) · [Homelab](vision/homelab.md) · [ThreadNet](vision/threadnet.md) | belegt |
| `user-guide/` | Für Nicht-Owner | entfällt — Gate 0: Publikum ist Owner + Sessions |
| `requirements/` | Eigenständige Anforderungssicht | nur bei echtem Bedarf |
| `faq/`, `stolpersteine/` | Nur aus AARs und geschlossenen Issues geerntet — nie auf Vorrat | leer, entsteht im Refinement |
## Seitenregeln
- Jede Seite trägt Frontmatter per `schema.yaml` (`type: wiki-page`,
`area`, `related`, ggf. `sources`).
- `sources` zitiert, worauf die Seite fußt — Dateien unter
`docs/sources/` (unveränderlich, agentenschreibgeschützt) oder
externe URLs.
- Nur Standard-Markdown-Links, Diagramme als Mermaid (ADR-0003
upstream); keine Wikilinks.
- **Aufgaben gehören nicht ins Wiki:** offene Arbeitspunkte sind Issues
(`docs/issues/`), Wiki-Seiten verweisen höchstens darauf
(Lehre F-004; Prüfung: `scripts/pruefe_prosa.py`).
- Widersprüche werden aufgelöst oder ausdrücklich als Konflikt markiert
— nie stillschweigend nebeneinander stehen gelassen.
- Kein „Stand:"-Etikett in Seitenkörpern — `git log` beantwortet das
(Lehre F-010).
+44
View File
@@ -0,0 +1,44 @@
---
type: wiki-page
area: vision
related: []
---
# Vision: aXion1337.chat — die Community
> **Getragene Fassung** — geschärft im Struktur-Workshop am 2026-08-06 (#17).
> Leitplanke von sorb:
> **„axion1337.chat ist die Community, ThreadNet ist das Tool."**
## Kern
Eine selbst betriebene, souveräne Kommunikationsplattform für die eigene
Community: Matrix-Homeserver mit eigenem Client, eigener Identität (Authentik),
eigenen Regeln — unabhängig von Discord & Co.
## Was dazugehört (Stand heute)
- Matrix/ESS-Stack mit Voice/Video (Element Call, 1440p-Defaults)
- Gäste sollen unkompliziert, aber kontrolliert reinkommen
(Invite-Workflow gitops-Issue, Freischaltung im Matrix-Raum, `@concierge`)
- Community-Funktionen über Chat hinaus: Raidplaner mit Fotoalbum (HumHub-Kandidat)
- Sicherheit als Feature: ClamAV beidseitig, CVE-Transparenz im Security-Raum
## Zielgruppe und Größe
**Kontrolliert wachsend** (entschieden 2026-08-06). Offen für Neue, aber **jeder
Eintritt wird freigegeben** — genau das, was der Invite-Workflow mit `@concierge`
und befristeten Gast-Accounts baut. Wachstum ist erwünscht, aber gedeckelt durch
das, was eine Person moderieren kann; der Ein-Node-Stack setzt denselben Rahmen.
Verworfen: **geschlossener Kreis** (dann wäre der Invite-Workflow überdimensioniert)
und **offene Registrierung** (verlangt Moderationsteam und Kapazität, die es nicht
gibt).
Was daraus folgt: Der Invite-Workflow ist kein Nice-to-have, sondern das Mittel,
mit dem diese Entscheidung durchgesetzt wird. Freie Registrierung bleibt aus.
## Verhältnis zum ThreadNet-Branding
Nicht mehr offen: Das Rebranding wird in **M4 zu Ende gebracht**, nicht separat
terminiert — siehe [`threadnet.md`](threadnet.md).
+41
View File
@@ -0,0 +1,41 @@
---
type: wiki-page
area: vision
related: []
---
# Vision: Homelab — die Plattform
> **Getragene Fassung** — geschärft im Struktur-Workshop am 2026-08-06 (#17).
## Kern
Das Lab (MorninglightMountain, Overmind, das Netz dazwischen) ist die
**Quelle der Wahrheit** und die Werkbank: kanonische Repos, Build-CI,
Verwaltung — alles, was nicht öffentlich erreichbar sein muss, lebt hier
(ADR-0001/0002).
## Prinzipien
- Prod hängt nie vom Lab ab: Hetzner-Hosts laufen bei Lab-Ausfall weiter,
nur Neues pausiert.
- Verbindungen ins Lab sind schaltbar und minimal (Roadwarrior für Menschen,
Site-to-Site für Server — ADR-0004), nie dauerhaft exponiert.
- Bestand wird dokumentiert, wo er lebt: [homelab/docs](https://git.lab/homelab/docs)
für Netz/Geräte, Betriebs-Repos je Host.
## Ausbaustufen
**Konsolidieren vor Ausbauen** (entschieden 2026-08-06). Kein neuer Dienst und
kein weiterer Host, solange die Absicherung des Bestehenden nicht steht.
Der Maßstab ist nicht „läuft es", sondern „überlebt es den Verlust der Maschine,
auf der es läuft". Ein Lab, das Quelle der Wahrheit ist, hat diese Frage zuerst zu
beantworten — jeder zusätzliche Dienst vergrößert sonst die Angriffsfläche
schneller als die Kontrolle.
**Nachtrag sorb (2026-08-06):** Die Git-Daten werden bereits in einen S3-Bucket
auf sein NAS gesichert. Das entschärft den ursprünglichen Befund aus
[#10](https://git.lab/axion1337.chat/management/-/issues/10) — offen bleibt
jedoch, was diese Sicherung **nicht** umfasst (Registry-Blobs, npm-Pakete,
Gitea-Datenbank). Siehe dort.
+52
View File
@@ -0,0 +1,52 @@
---
type: wiki-page
area: vision
related: []
---
# Vision: ThreadNet — das Tool
> **Getragene Fassung** — geschärft im Struktur-Workshop am 2026-08-06 (#17).
> Leitplanke: ThreadNet ist die Produkt-/Tool-Linie, axion1337.chat der Betrieb.
## Kern
Die Software-Artefakte, die die Community tragen — als eigenständige,
wiederverwendbare Produkte gedacht: ThreadNet-Web (Element-Web-Fork mit
Discord-artiger Raumliste), threadnet-call (Call-Fork), thread-net-git,
threadnet-operating.
## Prinzipien
- **Reproduzierbar für Dritte:** Deployment-Arbeit so bauen, dass eine andere
Community den Stack forken kann (stehendes Ziel von sorb, 2026-07-30) —
Instanzwerte getrennt von generischer Struktur.
- Fork-Pflege mit kleinem Delta: Upstream-Merges müssen billig bleiben,
chirurgische Patches statt Umbauten.
- CI beweist Releases: Tag → Pipeline → Artefakt, keine Handbuilds.
## Veröffentlichungsgrad
**Die Forks werden öffentlich** (entschieden 2026-08-06) — aber erst nach einem
**History-Audit**, und in dieser Reihenfolge: zuerst gitops und Doku, die Clients
später.
Der Grund für die Entscheidung steckt im Prinzip oben: „reproduzierbar für Dritte"
zahlt sich nur öffentlich aus. Bliebe alles privat, würde dauerhaft für einen Zweck
gebaut, den es nicht gibt — dann hätte das Prinzip gestrichen gehört.
⚠️ **Der Audit ist Bedingung, nicht Formsache.** In der Historie des gitops-Repos
steht Commit `51ea513` — *„remove plaintext TURN shared secret, rotate leaked
value"*. Der Wert ist rotiert und damit wertlos, aber er steht weiterhin in der
Historie, und er ist vermutlich nicht der einzige Fund. Vor dem Umschalten auf
public: Historie aller zu veröffentlichenden Repos auf Klartext-Geheimnisse prüfen
und entscheiden, ob bereinigt (History-Rewrite) oder bewusst akzeptiert wird.
## Rebranding
**Wird in M4 zu Ende gebracht** (entschieden 2026-08-06), ohne eigenen Termin.
Begründung: Halbfertig ist der schlechteste Zustand — der Desktop-Client heißt
seit `6b0261d` ThreadNet und trägt die eigene Marke, der Web-Client zeigt weiter
Element. Der Rest gehört zusammen mit Signing und Installer-Branding in
„Produktreife ThreadNet" (ThreadNet-Web#6, #7, #10).