feat(issues): adopt the component trackers — one backlog, one numbering (ADR-0019)

ADR-0012 made docs/issues/ canonical for the management scope only and left
gitops, ThreadNet-Web and threadnet-call on GitLab "until the component adopts".
That split produced exactly what it invited: two numbering worlds where
management#20 and gitops#20 are different issues, drift nobody had to answer for
(gitops#61 carried no milestone since 2026-08-11), and component backlogs that
host sessions without lab access cannot read at all.

The 46 open component issues are now files 0056-0101. The file id is the
group-wide identifier; provenance lives in the frontmatter (new field `projekt`
plus gitlab_iid) and in the filename, so "gitops#61" still finds 0091. Bodies are
copied verbatim; comments and history stay on GitLab, as with the 2026-08-11
management import.

Both scripts learned the second dimension: spiegel_issues.py routes each file to
its origin project, reopens issues that are open in the repo but closed on the
board, and writes the new iid back after creating one; gruppenpruefung.py checks
drift across all four trackers instead of management alone. What the mirror
cannot decide stays a finding, not a silent state.

Two things needed a hand, both recorded in the files: gitops#61 had no milestone
(M1 - it is a live account-takeover path) and carried two area labels where the
schema holds one. The Gitea migration footers in the imported bodies point at
decommissioned trackers; their links are removed, the provenance sentence stays.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Thore Cimbal
2026-08-18 12:00:00 +00:00
co-authored by Claude Opus 5
parent 5ec4aa702e
commit a1def8666e
52 changed files with 1789 additions and 44 deletions
+50 -3
View File
@@ -2,9 +2,9 @@
<!-- Generated by scripts/gen_status.py — do not edit. --> <!-- Generated by scripts/gen_status.py — do not edit. -->
## Issues (21 open, 28 closed) ## Issues (67 open, 28 closed)
Verteilung: M1 5 · M2 13 · M4 2 · M5 1 Verteilung: M1 16 · M2 20 · M3 4 · M4 12 · M5 15
| Issue | Status | Meilenstein | Priorität | Title | | Issue | Status | Meilenstein | Priorität | Title |
|---|---|---|---|---| |---|---|---|---|---|
@@ -29,12 +29,58 @@ Verteilung: M1 5 · M2 13 · M4 2 · M5 1
| [0051](docs/issues/0051-cve-remediation-pass.md) | open | M5 | high | CVE-Remediation-Pass: Schwachstellen-Report abarbeiten | | [0051](docs/issues/0051-cve-remediation-pass.md) | open | M5 | high | CVE-Remediation-Pass: Schwachstellen-Report abarbeiten |
| [0053](docs/issues/0053-historien-durchgang-nicht-kanonische-commits.md) | open | M2 | low | Historien-Durchgang: acht nicht-kanonische Commits mitziehen | | [0053](docs/issues/0053-historien-durchgang-nicht-kanonische-commits.md) | open | M2 | low | Historien-Durchgang: acht nicht-kanonische Commits mitziehen |
| [0055](docs/issues/0055-npm-scope-aufloesung-threadnet-web.md) | open | M2 | medium | Issue-0055: `@sorb`-Scope in ThreadNet-Web ist nirgends auf rohana festgelegt | | [0055](docs/issues/0055-npm-scope-aufloesung-threadnet-web.md) | open | M2 | medium | Issue-0055: `@sorb`-Scope in ThreadNet-Web ist nirgends auf rohana festgelegt |
| [0056](docs/issues/0056-gitops-9-external-postgresql-migration-cloudnativepg-o.md) | open | M1 | high | External PostgreSQL Migration: CloudNativePG or Hetzner |
| [0057](docs/issues/0057-gitops-11-element-call-vp9-codec-retry.md) | open | M4 | medium | Element Call: VP9 codec retry |
| [0058](docs/issues/0058-gitops-14-web-application-firewall-waf.md) | open | M5 | medium | Web Application Firewall (WAF) |
| [0059](docs/issues/0059-gitops-16-pod-security-admission-restricted.md) | open | M5 | medium | Pod Security Admission (Restricted) |
| [0060](docs/issues/0060-gitops-17-federation-allowlist-or-closed-federation-dec.md) | open | M1 | medium | Federation allowlist or closed federation decision |
| [0061](docs/issues/0061-gitops-20-external-secrets-operator-vs-current-sops-set.md) | open | M2 | low | External-Secrets Operator vs. current SOPS setup |
| [0062](docs/issues/0062-gitops-21-renovate-dependabot-for-chart-and-image-updat.md) | open | M5 | medium | Renovate/Dependabot for chart and image updates |
| [0063](docs/issues/0063-gitops-22-security-advisory-monitoring-ess-element.md) | open | M5 | medium | Security advisory monitoring (ESS/Element) |
| [0064](docs/issues/0064-gitops-23-disable-automountserviceaccounttoken-where-no.md) | open | M5 | medium | Disable automountServiceAccountToken where not needed |
| [0065](docs/issues/0065-gitops-25-k3s-api-security-hardening.md) | open | M5 | high | K3s API security hardening |
| [0066](docs/issues/0066-gitops-26-auditd-for-file-integrity-syscall-audit.md) | open | M5 | medium | auditd for file integrity & syscall audit |
| [0067](docs/issues/0067-gitops-27-kernel-hardening-sysctl.md) | open | M5 | medium | Kernel hardening (sysctl) |
| [0068](docs/issues/0068-gitops-28-lynis-security-baseline.md) | open | M5 | medium | Lynis security baseline |
| [0069](docs/issues/0069-gitops-29-crowdsec-integration.md) | open | M5 | medium | CrowdSec integration |
| [0070](docs/issues/0070-gitops-30-falco-runtime-monitoring.md) | open | M5 | medium | Falco runtime monitoring |
| [0071](docs/issues/0071-gitops-31-trivy-image-scanning-for-cves.md) | open | M5 | low | Trivy image scanning for CVEs |
| [0072](docs/issues/0072-gitops-34-dsgvo-datenschutz-compliance-konkretisieren.md) | open | M1 | low | DSGVO/Datenschutz-Compliance konkretisieren |
| [0073](docs/issues/0073-gitops-35-architektur-monorepo-umbau-mit-generalisierte.md) | open | M2 | medium | Architektur: Monorepo-Umbau mit generalisiertem Config-Overlay |
| [0074](docs/issues/0074-gitops-39-cleanup-checkliste-laufend.md) | open | M2 | low | Cleanup-Checkliste (laufend) |
| [0075](docs/issues/0075-gitops-40-neue-issues-erscheinen-nicht-automatisch-im-g.md) | open | M2 | low | Neue Issues erscheinen nicht automatisch im Gitea-Kanban/Projects-Board |
| [0076](docs/issues/0076-gitops-41-registry-git-traffic-zum-gitea-host-ueber-pri.md) | open | M2 | low | Registry-/Git-Traffic zum Gitea-Host ueber privates Hetzner-Netzwerk statt oeffentlichem Internet routen |
| [0077](docs/issues/0077-gitops-42-grafana-dashboard-fuer-clamav-scan-ergebnisse.md) | open | M1 | low | Grafana-Dashboard für ClamAV-Scan-Ergebnisse (Issue #19) |
| [0078](docs/issues/0078-gitops-45-cve-meldeweg-v2-metriken-grafana-dashboard-al.md) | open | M1 | medium | CVE-Meldeweg v2: Metriken, Grafana-Dashboard, Alerts in eigenen Matrix-Raum |
| [0079](docs/issues/0079-gitops-46-issue-migration-nach-gitlab-zentrale-projekt.md) | open | M2 | high | Issue-Migration nach GitLab + zentrale Projekt-Roadmap (Harmonisierung) |
| [0080](docs/issues/0080-gitops-47-raidplaner-mit-sozialer-komponente-verfuegbar.md) | open | M3 | medium | Raidplaner mit sozialer Komponente (Verfügbarkeiten, Aufgaben, Roadmap, Fotoalbum) |
| [0081](docs/issues/0081-gitops-48-gaeste-invite-workflow-per-bot-3-tage-account.md) | open | M3 | medium | Gäste-Invite-Workflow per Bot (3-Tage-Accounts, Admin-Freischaltung, begrenzte Reaktivierung) |
| [0082](docs/issues/0082-gitops-49-cve-alarme-eine-matrix-nachricht-pro-cve-flut.md) | open | M1 | high | CVE-Alarme: eine Matrix-Nachricht pro CVE flutet den Security-Raum -- Zustellung derzeit stumm |
| [0083](docs/issues/0083-gitops-50-monitoring-deploy-geaenderte-configs-greifen.md) | open | M1 | medium | Monitoring-Deploy: geaenderte Configs greifen nicht ohne --force-recreate (Inode-Falle bei Einzeldatei-Mounts) |
| [0084](docs/issues/0084-gitops-51-ci-canonize-token-fuer-die-automatische-turn.md) | next | M1 | medium | CI: CANONIZE_TOKEN für die automatische TURN-Rotation hinterlegen |
| [0085](docs/issues/0085-gitops-52-docs-traegt-zwei-altbestaende-abgeschlossener.md) | open | M2 | low | docs/ trägt zwei Altbestände abgeschlossener Umzüge: TASKS.md und oldwiki/ |
| [0086](docs/issues/0086-gitops-53-k8s-ressourcen-heissen-noch-element-web-docs.md) | open | M4 | low | k8s-Ressourcen heißen noch element-web-docs (Rest des ThreadNet-Rebrands) |
| [0087](docs/issues/0087-gitops-55-logo-fuer-die-authentik-anmeldemaske-entwerfe.md) | waiting | M4 | low | Logo für die Authentik-Anmeldemaske entwerfen (Querformat/SVG) |
| [0088](docs/issues/0088-gitops-56-networkpolicy-ausgehender-verkehr-ist-unbesch.md) | open | M1 | medium | NetworkPolicy: ausgehender Verkehr ist unbeschränkt (13 Ingress-Regeln, 1 Egress) |
| [0089](docs/issues/0089-gitops-58-eigene-images-sind-unsigniert-beim-deploy-pru.md) | open | M5 | low | Eigene Images sind unsigniert — beim Deploy prüft nichts die Herkunft |
| [0090](docs/issues/0090-gitops-59-kein-kubernetes-audit-log-zugriffe-an-der-api.md) | open | M5 | low | Kein Kubernetes-Audit-Log — Zugriffe an der API werden nicht protokolliert |
| [0091](docs/issues/0091-gitops-61-enrollment-kollidierender-localpart-uebernimm.md) | open | M1 | high | Enrollment: kollidierender Localpart übernimmt bestehendes Konto (on_conflict: add) |
| [0092](docs/issues/0092-threadnet-web-1-default-client-einstellungen-theme-features-f.md) | waiting | M3 | low | Default-Client-Einstellungen (Theme, Features) für Neuinstallationen provisionieren |
| [0093](docs/issues/0093-threadnet-web-3-settings-normalisieren-video-audio-einstellun.md) | open | M4 | medium | Settings normalisieren: Video/Audio-Einstellungen nur im Call-Widget, nicht im Haupt-Client |
| [0094](docs/issues/0094-threadnet-web-4-direkter-link-zur-2fa-passkey-einrichtung-in.md) | open | M3 | medium | Direkter Link zur 2FA/Passkey-Einrichtung in den Account-Settings |
| [0095](docs/issues/0095-threadnet-web-6-windows-desktop-code-signing-installer-brandi.md) | open | M4 | low | Windows-Desktop: Code-Signing (+ Installer-Branding) |
| [0096](docs/issues/0096-threadnet-web-7-rebranding-element-axion1337-web-desktop-gesa.md) | waiting | M4 | medium | Rebranding: Element → aXion1337 (Web + Desktop, Gesamtklammer) |
| [0097](docs/issues/0097-threadnet-web-9-feedback-bugreport-weg-eigener-rageshake-oder.md) | open | M4 | low | Feedback-/Bugreport-Weg: eigener Rageshake oder Alternative (Zammad nachhalten) |
| [0098](docs/issues/0098-threadnet-web-11-themes-rollout-2026-08-02-web-live-linux-wind.md) | open | M4 | low | Themes-Rollout 2026-08-02: Web live, Linux + Windows gebaut, macOS lokal |
| [0099](docs/issues/0099-threadnet-web-12-upstream-sicherheitsfixes-lassen-sich-nicht-m.md) | open | M1 | medium | Upstream-Sicherheitsfixes lassen sich nicht mergen — kein gemeinsamer Vorfahre |
| [0100](docs/issues/0100-threadnet-web-13-asset-pfade-tragen-weiterhin-element-themes-e.md) | open | M4 | low | Asset-Pfade tragen weiterhin "element" (themes/element/…) |
| [0101](docs/issues/0101-threadnet-call-4-kaputtes-paket-0-19-2-threadnet-6-in-der-regi.md) | open | M4 | low | Kaputtes Paket 0.19.2-threadnet.6 in der Registry — Herkunft ungeklärt |
## Active design docs (0) ## Active design docs (0)
_none active_ _none active_
## ADRs (18) ## ADRs (19)
| ADR | Status | Title | | ADR | Status | Title |
|---|---|---| |---|---|---|
@@ -56,6 +102,7 @@ _none active_
| [0016](docs/adr/0016-notfallhandbuch-nicht-spiegeln.md) | accepted | 0016 — Das Notfallhandbuch bleibt lab-intern und wird nicht gespiegelt | | [0016](docs/adr/0016-notfallhandbuch-nicht-spiegeln.md) | accepted | 0016 — Das Notfallhandbuch bleibt lab-intern und wird nicht gespiegelt |
| [0017](docs/adr/0017-split-dns-cfgmon-vier-zonen.md) | accepted | 0017 — Split-DNS auf CFGMON: vier Zonen statt einer, je Zone begründet | | [0017](docs/adr/0017-split-dns-cfgmon-vier-zonen.md) | accepted | 0017 — Split-DNS auf CFGMON: vier Zonen statt einer, je Zone begründet |
| [0018](docs/adr/0018-ki-geraeuschunterdrueckung-clientseitig-opt-in.md) | accepted | 0018 — KI-Geräuschunterdrückung in threadnet-call: client-seitig, opt-in, selbst ausgeliefert | | [0018](docs/adr/0018-ki-geraeuschunterdrueckung-clientseitig-opt-in.md) | accepted | 0018 — KI-Geräuschunterdrückung in threadnet-call: client-seitig, opt-in, selbst ausgeliefert |
| [0019](docs/adr/0019-komponenten-issues-adoptiert.md) | accepted | ADR-0019: Komponenten-Issues in docs/issues/ adoptiert — eine Nummernwelt für die Gruppe |
## Open AARs (3) ## Open AARs (3)
@@ -0,0 +1,70 @@
---
type: adr
id: "0019"
status: accepted
date: 2026-08-18
supersedes: null
superseded_by: null
related:
- "docs/adr/0012-issues-im-repo-gitlab-als-spiegel.md"
- "docs/adr/0013-gruppenregeln-kanonisch-mit-pruefung.md"
---
# ADR-0019: Komponenten-Issues in docs/issues/ adoptiert — eine Nummernwelt für die Gruppe
## Kontext
ADR-0012 machte `docs/issues/` kanonisch, **beschränkt auf den
Management-Scope**, und ließ die Komponenten-Tracker (gitops,
ThreadNet-Web, threadnet-call) ausdrücklich auf GitLab als Wahrheit —
„bis die jeweilige Komponente selbst adoptiert". Der Zustand seither:
zwei Issue-Welten mit getrennten Nummernkreisen (management#20
gitops#20), Drift zwischen Board und Repo an mehreren Stellen
(Beispiel: gitops#61 monatelang ohne Meilenstein, geschlossene
Board-Issues zu offenen Dateien), und Host-Sessions ohne Lab-Zugang
sehen die Komponenten-Backlogs gar nicht. sorb hat am 2026-08-18 die
Adoption angeordnet: ein Backlog, eindeutige Bezeichner, Drift beenden.
## Entscheidung
**Die offenen Issues der Komponenten-Tracker werden in `docs/issues/`
adoptiert; es gibt ab jetzt genau eine kanonische Nummernwelt.**
- Jedes adoptierte Issue bekommt die nächste freie Datei-Nummer
(0056 ff.); die Datei-ID ist der eindeutige Bezeichner der Gruppe.
Die Herkunft steht im Frontmatter (`projekt` + `gitlab_iid`) und im
Dateinamen (`0091-gitops-61-…`); ADR-0012s Gleichung „GitLab-iid =
Datei-id" gilt nur noch für den Management-Altbestand 00010032 —
über mehrere Projekte ist sie nicht kollisionsfrei zu halten.
- Übernommen werden Titel, Beschreibung (wortgleich), Status
(Board-Labels `status:*`), Meilenstein, Priorität, Fälligkeit und
eine `area`; Kommentare und Verlauf bleiben auf GitLab (wie beim
Management-Import). Geschlossene GitLab-Issues bleiben Historie und
werden nicht importiert.
- Die GitLab-Projekt-Tracker werden zur **bespiegelten Ansicht** wie
zuvor schon das management-Projekt: `spiegel_issues.py` routet
jede Datei über ihr `projekt`-Feld ins Herkunftsprojekt, kann
geschlossene Issues zu offenen Dateien wieder öffnen, und
`gruppenpruefung.py` prüft die Drift über alle adoptierten Projekte.
Board-, Meilenstein- und Label-Ansichten arbeiten unverändert weiter
— der Grund, aus dem ADR-0012 Option C gewählt hat, bleibt erhalten.
- Neue Issues entstehen ab jetzt **immer** als Datei; das `projekt`-Feld
bestimmt, in welchem Tracker der Spiegel sie anlegt (fehlt es:
management). Ein GitLab-seitig neu angelegtes Issue ohne Datei ist
ein Befund der Gruppenprüfung („zweites Backlog"), kein stiller
Zustand.
- Werkzeug: `verfahren/issue-adoption/adoptiere.py` (deterministisch,
idempotent, Dry-Run-Default) hat die 46 offenen Komponenten-Issues
als 00560101 übernommen.
## Konsequenzen
- Ein Backlog, ein Nummernkreis, eine Drift-Prüfung; `STATUS.md` zeigt
erstmals die ganze Gruppe. Host-Sessions lesen alle Backlogs über den
Gitea-Mirror dieses Repos.
- Die Schwelle „Komponente adoptiert selbst" aus ADR-0012 ist damit für
gitops, ThreadNet-Web und threadnet-call genommen; thread-net-git,
threadnet-operating und notfallhandbuch hatten keine offenen Issues
und adoptieren bei Bedarf durch Aufnahme in die `projekt`-Enum.
- Querbezüge in Prosa nennen künftig die Datei-ID; alte Bezeichner wie
„gitops#61" bleiben über Frontmatter und Dateinamen auffindbar.
@@ -0,0 +1,21 @@
---
type: issue
id: "0056"
status: open
created: 2026-05-14
milestone: M1
priority: high
area: database
projekt: gitops
gitlab_iid: "9"
related: []
---
# External PostgreSQL Migration: CloudNativePG or Hetzner
> Adoptiert aus [gitops#9](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/9) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Migrate from ESS embedded Postgres to external database. Setup HA + Replication. Test all services. Est. Time: 1-2 days
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#9` — dort erstellt am 2026-05-14 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#9 -->
@@ -0,0 +1,21 @@
---
type: issue
id: "0057"
status: open
created: 2026-07-28
milestone: M4
priority: medium
area: element
projekt: gitops
gitlab_iid: "11"
related: []
---
# Element Call: VP9 codec retry
> Adoptiert aus [gitops#11](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/11) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Retry `video_codec: vp9` for better compression efficiency than the current H.264. First attempt (2026-07-28) broke calls entirely (no audio/video transmitted). Likely cause: LiveKit uses SVC for vp9/av1 instead of classic simulcast, but the threadnet-call fork's `buildPublishOptions()` (`src/livekit/options.ts`) always builds simulcast-shaped `videoSimulcastLayers` regardless of codec. Needs a code fix (branch SVC vs simulcast config by codec) before retrying, plus a real browser-console repro if it fails again. H.264 is live and working well in the meantime (7/8 tracks native, 1 clean VP8 fallback).
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#11` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#11 -->
@@ -0,0 +1,21 @@
---
type: issue
id: "0058"
status: open
created: 2026-07-28
milestone: M5
priority: medium
area: security
projekt: gitops
gitlab_iid: "14"
related: []
---
# Web Application Firewall (WAF)
> Adoptiert aus [gitops#14](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/14) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Application-layer (L7) request filtering in front of Traefik - inspects actual HTTP content for attack patterns (SQLi, XSS, known exploit signatures), separate from and not covered by the Hetzner Cloud Firewall (which is network-layer L3/L4 IP/port filtering only). Was counted in the original security task total but never had its own written-up task. Consider overlap with CrowdSec (separate issue) which can provide some WAF-like bouncer behavior via Traefik integration - evaluate whether a dedicated WAF (e.g. Coraza/ModSecurity-compatible) is still needed on top of that.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#14` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#14 -->
@@ -0,0 +1,21 @@
---
type: issue
id: "0059"
status: open
created: 2026-07-28
milestone: M5
priority: medium
area: security
projekt: gitops
gitlab_iid: "16"
related: []
---
# Pod Security Admission (Restricted)
> Adoptiert aus [gitops#16](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/16) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Apply Restricted Pod Security Admission to the `matrix` and `authentik` namespaces: enforce non-root, no privileged containers, read-only root filesystem. Test carefully for chart breakage before enforcing.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#16` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#16 -->
@@ -0,0 +1,21 @@
---
type: issue
id: "0060"
status: open
created: 2026-07-28
milestone: M1
priority: medium
area: security
projekt: gitops
gitlab_iid: "17"
related: []
---
# Federation allowlist or closed federation decision
> Adoptiert aus [gitops#17](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/17) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Decide: open federation with all public Matrix servers (current default, larger attack surface) vs. an explicit `federation_domain_whitelist`, vs. fully closed federation (`allow_public_rooms_without_join_rules: false`). Config lives in `apps/production/custom-configs/synapse-values.yaml`.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#17` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#17 -->
@@ -0,0 +1,21 @@
---
type: issue
id: "0061"
status: open
created: 2026-07-28
milestone: M2
priority: low
area: security
projekt: gitops
gitlab_iid: "20"
related: []
---
# External-Secrets Operator vs. current SOPS setup
> Adoptiert aus [gitops#20](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/20) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Current SOPS+age encryption is working fine. Consider whether External-Secrets Operator (cloud-native secret sourcing, e.g. from a proper secrets manager) is worth the migration effort, or whether to just keep/improve the current SOPS rotation strategy.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#20` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#20 -->
@@ -0,0 +1,21 @@
---
type: issue
id: "0062"
status: open
created: 2026-07-28
milestone: M5
priority: medium
area: infrastructure
projekt: gitops
gitlab_iid: "21"
related: []
---
# Renovate/Dependabot for chart and image updates
> Adoptiert aus [gitops#21](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/21) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Automate Helm chart version bumps and container image tag updates, with security patch monitoring, instead of manual version tracking.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#21` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#21 -->
@@ -0,0 +1,21 @@
---
type: issue
id: "0063"
status: open
created: 2026-07-28
milestone: M5
priority: medium
area: security
projekt: gitops
gitlab_iid: "22"
related: []
---
# Security advisory monitoring (ESS/Element)
> Adoptiert aus [gitops#22](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/22) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Subscribe to element-hq security mailing list / advisories and Matrix community security channels, set up alerts for new CVEs/patches affecting the deployed components.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#22` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#22 -->
@@ -0,0 +1,21 @@
---
type: issue
id: "0064"
status: open
created: 2026-07-28
milestone: M5
priority: medium
area: security
projekt: gitops
gitlab_iid: "23"
related: []
---
# Disable automountServiceAccountToken where not needed
> Adoptiert aus [gitops#23](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/23) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Audit all Deployments/StatefulSets in `matrix` and `authentik` namespaces, add `automountServiceAccountToken: false` wherever the pod doesn't actually need Kubernetes API access (Synapse, ElementWeb, MAS, Postgres, Authentik, etc). Test for no breakage.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#23` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#23 -->
@@ -0,0 +1,21 @@
---
type: issue
id: "0065"
status: open
created: 2026-07-28
milestone: M5
priority: high
area: infrastructure
projekt: gitops
gitlab_iid: "25"
related: []
---
# K3s API security hardening
> Adoptiert aus [gitops#25](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/25) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
K3s API currently listens on :6443 on all interfaces (default). Options: firewall-restrict :6443 to localhost only, bind K3s to a WireGuard/internal IP via `--bind-address`/`--advertise-address`, or require a bastion/jumphost for kubectl access. The API is a high-value target.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#25` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#25 -->
@@ -0,0 +1,21 @@
---
type: issue
id: "0066"
status: open
created: 2026-07-28
milestone: M5
priority: medium
area: security
projekt: gitops
gitlab_iid: "26"
related: []
---
# auditd for file integrity & syscall audit
> Adoptiert aus [gitops#26](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/26) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Monitor `/etc`, `~/.kube`, `/var/lib/rancher/k3s` for sensitive file changes via auditd rules, output to syslog/centralized logging. Low overhead, good forensics/compliance signal.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#26` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#26 -->
@@ -0,0 +1,21 @@
---
type: issue
id: "0067"
status: open
created: 2026-07-28
milestone: M5
priority: medium
area: infrastructure
projekt: gitops
gitlab_iid: "27"
related: []
---
# Kernel hardening (sysctl)
> Adoptiert aus [gitops#27](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/27) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Apply Lynis-recommended sysctl hardening: `kernel.kptr_restrict=2`, `kernel.dmesg_restrict=1`, `net.ipv4.tcp_syncookies=1`, `net.ipv4.conf.all.rp_filter=1`, disable ICMP redirects, etc. Persist via `/etc/sysctl.d/99-hardening.conf`.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#27` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#27 -->
@@ -0,0 +1,21 @@
---
type: issue
id: "0068"
status: open
created: 2026-07-28
milestone: M5
priority: medium
area: security
projekt: gitops
gitlab_iid: "28"
related: []
---
# Lynis security baseline
> Adoptiert aus [gitops#28](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/28) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Run `lynis audit system` on the host, review and implement high-priority recommendations, aim for a score >80. Re-run quarterly as a baseline check.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#28` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#28 -->
@@ -0,0 +1,21 @@
---
type: issue
id: "0069"
status: open
created: 2026-07-28
milestone: M5
priority: medium
area: security
projekt: gitops
gitlab_iid: "29"
related: []
---
# CrowdSec integration
> Adoptiert aus [gitops#29](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/29) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Install CrowdSec agent on the host, feed auth.log/syslog for collaborative attack detection, auto-block malicious IPs via the local firewall or Hetzner Firewall API. Also relevant to the WAF discussion (CrowdSec has Traefik bouncer integration).
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#29` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#29 -->
@@ -0,0 +1,21 @@
---
type: issue
id: "0070"
status: open
created: 2026-07-28
milestone: M5
priority: medium
area: security
projekt: gitops
gitlab_iid: "30"
related: []
---
# Falco runtime monitoring
> Adoptiert aus [gitops#30](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/30) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Deploy Falco as a DaemonSet in K3s to monitor for suspicious runtime behavior (shell spawning in containers, privilege escalation, anomalous syscalls), output to Loki/syslog with alerting.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#30` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#30 -->
@@ -0,0 +1,21 @@
---
type: issue
id: "0071"
status: open
created: 2026-07-28
milestone: M5
priority: low
area: infrastructure
projekt: gitops
gitlab_iid: "31"
related: []
---
# Trivy image scanning for CVEs
> Adoptiert aus [gitops#31](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/31) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Scan container images referenced in Flux HelmReleases for known CVEs, block deployment if a critical CVE is found. CI/CD hook in the git workflow (though note: no Gitea Actions runner is currently active in this repo, per the milestone-release.yml findings from an earlier session - would need that resolved first, or run scanning out-of-band).
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#31` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#31 -->
@@ -0,0 +1,20 @@
---
type: issue
id: "0072"
status: open
created: 2026-07-28
milestone: M1
priority: low
projekt: gitops
gitlab_iid: "34"
related: []
---
# DSGVO/Datenschutz-Compliance konkretisieren
> Adoptiert aus [gitops#34](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/34) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Bisher nur als vages "M7: Enterprise-Ready - Future"-Ziel in der alten Milestone-Tabelle vermerkt, nie konkretisiert. Relevant, sobald echte Nutzer (nicht nur Testaccounts) und offene Federation im Spiel sind - fremde Server/Nutzer sehen dann ggf. Daten mit. Prio 0 laut User - erst angehen, wenn die anderen Punkte durch sind.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#34` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#34 -->
@@ -0,0 +1,23 @@
---
type: issue
id: "0073"
status: open
created: 2026-07-28
milestone: M2
priority: medium
area: infrastructure
projekt: gitops
gitlab_iid: "35"
related: []
---
# Architektur: Monorepo-Umbau mit generalisiertem Config-Overlay
> Adoptiert aus [gitops#35](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/35) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Aktuell drei getrennte Repos (`axion1337.chat-gitops`, `ThreadNet-Web`, `threadnet-call`). Idee: alles in ein Monorepo überführen, mit einem generalisierten Setup und einer Art Config-Overlay (z.B. Kustomize-Overlays oder Helm-Values-Layering pro Deployment-Ziel), damit der gesamte Stack reproduzierbar auch an anderer Stelle/für eine andere Domain deploybar wird - nicht fest auf axion1337.chat verdrahtet.
**Wichtig**: Das ist ein größeres Architektur-Vorhaben und braucht erst eine gründliche, eigene Planungssession, bevor irgendwas umgesetzt wird. Nicht nebenbei anfassen.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#35` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#35 -->
@@ -0,0 +1,25 @@
---
type: issue
id: "0074"
status: open
created: 2026-07-28
milestone: M2
priority: low
projekt: gitops
gitlab_iid: "39"
related: []
---
# Cleanup-Checkliste (laufend)
> Adoptiert aus [gitops#39](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/39) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Laufende Sammel-Liste kleiner Aufraeumarbeiten, kein einzelnes Projekt - hier landen zukuenftig weitere kleine Punkte.
- [ ] Verwaistes `@bojeledoggo:axion1337.chat` (leeres Matrix-Konto ohne OIDC-Link) loeschen/deaktivieren
- [ ] `Boje`s fehlende E-Mail in Authentik ergaenzen (oder bewusst so lassen?)
- [ ] Test-Invitations/-Raeume aus der Session 2026-07-27/28 aufraeumen (`test-fix-2026-07-27*`, Call-Test-Raeume von akadmin/frank/clark/lucky)
- [ ] `clark`/`lucky` Testaccounts loeschen, sobald der Stack final abgenommen ist
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#39` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#39 -->
@@ -0,0 +1,30 @@
---
type: issue
id: "0075"
status: open
created: 2026-07-28
milestone: M2
priority: low
area: infrastructure
projekt: gitops
gitlab_iid: "40"
related: []
---
# Neue Issues erscheinen nicht automatisch im Gitea-Kanban/Projects-Board
> Adoptiert aus [gitops#40](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/40) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Gitea bietet aktuell keine REST-API fuer Projects/Kanban-Boards (bestaetigt mit 4 verschiedenen Tokens, darunter ein Full-Admin-Token - durchgehend 404). Das ist keine Berechtigungsfrage auf unserer Seite, sondern eine tatsaechlich fehlende Upstream-Funktion (siehe Gitea GitHub Issue #36824, offenes Feature-Request, Stand 2026-07-28 noch nicht implementiert).
Konkrete Auswirkung: Neu erstellte Issues (#11-#39, per Batch-Script aus dem alten TASKS.md-Backlog migriert) landen nicht automatisch im bestehenden Kanban/Projects-Board der Roadmap - muessen manuell per Drag&Drop/UI hinzugefuegt werden.
Optionen fuer die Zukunft:
- Manuell nachpflegen (aktueller Stand)
- Auf ein Gitea-Update warten, falls die Projects-API implementiert wird
- Alternatives Board-Tool evaluieren, falls das dauerhaft zu nervig wird
Niedrige Prioritaet, da rein organisatorisch - kein technisches Risiko.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#40` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#40 -->
@@ -0,0 +1,29 @@
---
type: issue
id: "0076"
status: open
created: 2026-07-28
milestone: M2
priority: low
area: infrastructure
projekt: gitops
gitlab_iid: "41"
related: []
---
# Registry-/Git-Traffic zum Gitea-Host ueber privates Hetzner-Netzwerk statt oeffentlichem Internet routen
> Adoptiert aus [gitops#41](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/41) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Waehrend der Backup-Implementierung (#6/#15) stellte sich heraus, dass eine Firewall-Fehlkonfiguration den K3s-Node komplett von `rohana.axion1337.de` (Gitea, Container-Registry) abschnitt - Image-Pulls schlugen mit Timeout fehl. Ursache gefunden: beide Server haengen im selben privaten Hetzner-Netzwerk (Node 10.0.0.2, Gitea-Host 10.0.0.3, <2ms Latenz), aber der Traffic lief bisher ausschliesslich ueber die oeffentliche IP/Internet.
Als Sofortmassnahme wurde ein statischer Eintrag in `/etc/hosts` auf dem K3s-Node ergaenzt (`10.0.0.3 rohana.axion1337.de`), der Image-Pulls unabhaengig vom Zustand der oeffentlichen Firewall macht. Das ist aber unmanaged Node-Konfiguration (kein GitOps, ueberlebt einen Node-Neuaufbau nicht).
Sauberer, dauerhafter Fix waere eine cluster-weite Loesung, z.B.:
- CoreDNS-Rewrite/Hosts-Plugin im Corefile, damit alle Pods (nicht nur der Node selbst) `rohana.axion1337.de` intern aufloesen
- Pruefen, ob auch Flux GitRepository-Sync davon profitieren kann/sollte
Vorteil: Traffic bleibt intern, unabhaengig von oeffentlicher Firewall/Internet-Erreichbarkeit, kein Punkt mehr, an dem eine Firewall-Anpassung versehentlich Image-Pulls oder Flux-Sync brechen kann.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#41` — dort erstellt am 2026-07-28 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#41 -->
@@ -0,0 +1,32 @@
---
type: issue
id: "0077"
status: open
created: 2026-07-29
milestone: M1
priority: low
area: infrastructure
projekt: gitops
gitlab_iid: "42"
related: []
---
# Grafana-Dashboard für ClamAV-Scan-Ergebnisse (Issue #19)
> Adoptiert aus [gitops#42](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/42) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Wunsch aus Issue #19-Tests: sichtbar machen, wie oft ClamAV tatsächlich etwas blockiert
(bisher nur in Synapse-Logs sichtbar: `clamav_spam_checker - WARNING - ClamAV rejected an
upload: <signature>`).
Da Alloy bereits alle Pod-Logs nach Loki schickt (`10.0.0.3:3100`, siehe
`docs/deployment-guides/03-monitoring-integration.md`), braucht es dafür keine neue
Instrumentierung - nur ein neues Grafana-Dashboard/Panel mit einer LogQL-Query auf
`{app="synapse-main"} |= "ClamAV rejected"` (Anzahl über Zeit, evtl. Tabelle mit erkannten
Signaturen). Optional zusätzlich: ein Panel für Scanner-Ausfälle (`ClamAV scan failed` -
fail-open-Fälle, die sonst unbemerkt blieben).
Kein Server-seitiger Code nötig, rein Grafana/Loki-Dashboard-Arbeit.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#43` — dort erstellt am 2026-07-29 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#43 -->
@@ -0,0 +1,33 @@
---
type: issue
id: "0078"
status: open
created: 2026-08-01
milestone: M1
priority: medium
projekt: gitops
gitlab_iid: "45"
related: []
---
# CVE-Meldeweg v2: Metriken, Grafana-Dashboard, Alerts in eigenen Matrix-Raum
> Adoptiert aus [gitops#45](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/45) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Entscheidung sorb (2026-08-01): Die reine Artifact-Ablage der Trivy-Funde (#31) ist **ungenügend**. Zielbild:
1. **Eigener Matrix-Raum für CVE-/Release-Meldungen**: `!YRJvcEbVXtRlUIkNld:axion1337.chat` (angelegt), Absender bleibt der bestehende `@alerts`-Bot — kein zweiter Bot (beantwortet CFGMON-13). ⬜ **Bot einladen** (Raum ist restricted, Join wurde abgelehnt) — sorb.
2. **CVEs als Metriken****Grafana-Dashboard** + **Prometheus-Alertregeln** → Alertmanager → Matrix. Damit laufen CVE-Warnungen über denselben Alarmweg wie alles andere (Historie, Silences, Dashboards inklusive).
**Architektur-Vorschlag (zur Diskussion):**
- **Scan-Ort wandert von der Lab-CI nach CFGMON**: Trivy als Compose-Service/Cron im monitoring-Stack (`--format json` → kleiner Stdlib-Konverter → Prometheus-Textfile/Pushgateway-los via Remote-Write auf localhost:9090). Begründung: Metriken, Prometheus und Grafana wohnen dort; die Lab-CI bleibt fürs schnelle „Report als Artifact" beim Release-Build. Alternativ: Lab-CI pusht Metriken — scheitert aber an CFGMON-03 (9090 wird gerade zugezogen) und koppelt Prod-Monitoring an Lab-Verfügbarkeit.
- **Metrik-Schema**: `trivy_image_vulnerabilities{image,severity}` (Gauge) + `trivy_scan_timestamp{image}`; Alertregel z. B. `trivy_image_vulnerabilities{severity="CRITICAL"} > 0` → severity=critical, `HIGH > 0` → warning mit `for: 24h` (Rauschdämpfung).
- **Raum-Routing**: matrix-alerts-Receiver bekommt Label-basiertes Routing (`room`-Label im Alert → Ziel-Raum, Default = Alerts-Raum); Alertmanager-Route setzt `room: cve` für Trivy-Alerts. Kleiner, sauberer Eingriff im bestehenden Stdlib-Receiver.
- **release-watch** (Advisory-Notizen, #22) zieht in denselben CVE-Raum um — Env dafür ist vorbereitet (`MATRIX_RELEASE_ROOM_ID`, Fallback Alerts-Raum).
**Abgrenzung SBOM** (Frage sorb, dokumentiert auch im Script): `release-watch` lebt von einer **handgepflegten Repo-Liste** — de facto ein Mini-SBOM auf Repo-Granularität, ohne Versions-/Dependency-Wissen. **Trivy dagegen erzeugt sein SBOM selbst aus den Images** (OS-Pakete + Sprach-Dependencies) — dort ist nichts zu pflegen. Beide ergänzen sich: Trivy = „was IST verwundbar in dem, was wir ausliefern", release-watch = „Upstream hat etwas veröffentlicht, das uns betreffen könnte".
Verweise: #31 (Scan existiert), #22 (release-watch), CFGMON-13 im Backlog (Absender-Design — durch Punkt 1 entschieden).
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#47` — dort erstellt am 2026-08-01 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#47 -->
@@ -0,0 +1,32 @@
---
type: issue
id: "0079"
status: open
created: 2026-08-01
milestone: M2
priority: high
projekt: gitops
gitlab_iid: "46"
related: []
---
# Issue-Migration nach GitLab + zentrale Projekt-Roadmap (Harmonisierung)
> Adoptiert aus [gitops#46](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/46) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
**Auftrag sorb (2026-08-01, HOHE PRIORITÄT):** Es wird zunehmend unübersichtlich — Issues liegen in vier Gitea-Repos, Host-Backlogs im Backlogs-Repo, Code seit der Migration auf git.lab. Ziel: **eine zentrale Sicht in GitLab.**
## Umfang
1. **Issue-Migration (zwingend):** alle bestehenden Gitea-Issues (offen UND geschlossen — die Abschlussdokumentation ist wertvoll) nach GitLab überführen: gitops (47+), ThreadNet-Web (8), threadnet-call (2), thread-net-git (1).
2. **Harmonisierung/Roadmap:** zentrale Projekt-Roadmap auf Gruppen-Ebene (`axion1337.chat`), die Issues + Host-Backlogs sinnvoll zusammenführt — GitLab-Bordmittel: Gruppen-**Epics/Roadmap-Ansicht**, Milestones, Labels-Taxonomie (prio/*, area/*, host/*), Gruppen-Boards.
## Plan-Skizze (zur Abstimmung vor Umsetzung)
- **Werkzeug:** GitLabs eingebauter Gitea-Importer übernimmt Issues+Kommentare (Autorenschaft läuft auf den Import-User — bekannter, akzeptierbarer Verlust). Da die Projekte in GitLab schon existieren, ist ggf. stattdessen ein API-Skript nötig (Issues in BESTEHENDE Projekte importieren kann der Importer nicht) — Skript-Weg: Gitea-API lesen → GitLab-API schreiben, `[Gitea #N]`-Präfix im Titel oder Migrations-Fußzeile pro Issue für die Nummern-Zuordnung (Commit-Messages referenzieren alte Nummern!).
- **Host-Backlogs:** `Backlogs`-Repo-Einträge (CFGMON-xx, MATRIX-xx, …) als GitLab-Issues in einem neuen Projekt `infrastruktur` (oder Labels `host::cfgmon` etc.) — die Markdown-Historie bleibt als Archiv erhalten.
- **Folgeänderungen (nicht vergessen):** Repo-Topologie-Doku (CLAUDE.md/README „Issues bleiben Gitea" wird obsolet), TURN-Rotations-CronJob-PR-Hinweis, alle `rohana…/issues`-Links in Doku, meine Sessions-Werkzeuge (claude-issues-Token → GitLab-Token). ⚠️ **Erreichbarkeits-Trade-off bewusst machen:** git.lab ist nur im Lab auflösbar — Issues wären unterwegs nicht mehr erreichbar. Optionen: (a) akzeptieren, (b) GitLab extern erreichbar machen (eigenes Projekt!), (c) Hybrid vermeiden — genau der räumt ja nicht auf. **Entscheidung sorb nötig, bevor migriert wird.**
- **Reihenfolge:** Labels/Epics-Gerüst zuerst, dann Testmigration EIN Repo (thread-net-git, 1 Issue), Review, dann Rest.
Verwandt: Backlogs CFGMON-12 (wird hiervon abgelöst/erweitert).
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#48` — dort erstellt am 2026-08-01 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#48 -->
@@ -0,0 +1,34 @@
---
type: issue
id: "0080"
status: open
created: 2026-08-01
milestone: M3
priority: medium
projekt: gitops
gitlab_iid: "47"
related: []
---
# Raidplaner mit sozialer Komponente (Verfügbarkeiten, Aufgaben, Roadmap, Fotoalbum)
> Adoptiert aus [gitops#47](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/47) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
**Wunsch sorb (2026-08-01):** gemeinsames Planungs-Tool mit sozialer Komponente — Verfügbarkeiten („wer hat wann Zeit"), Aufgaben-Zuweisung, gemeinsame Roadmap-Visualisierung, **integriertes Fotoalbum** (Bilder anlassbezogen an Aktivitäten/Board-/Roadmap-Einträge geknüpft, z. B. Bauphasen-Screenshots, Boss-Kämpfe).
**Rechercheergebnis (Kurzfassung):** Die Gaming-„Raidplaner"-Szene (Raid-Helper, Raid-Planner) ist durchweg **Discord-gebunden, nicht self-hosted** — passt nicht zum Matrix-Stack. Realistische Self-Hosted-Kandidaten:
| Kandidat | Verfügbarkeit | Aufgaben | Roadmap | Fotoalbum integriert | Einschätzung |
|---|---|---|---|---|---|
| **HumHub** | Kalender-Modul + Umfragen | Tasks-Modul | Kanban-artig | ✅ Gallery-Modul, an Spaces/Posts geknüpft | **Bester Fit für „sozial + Fotos"** — Community-Plattform mit Modulen; Achtung: manche Module Pro |
| **Nextcloud** (Deck+Calendar+Polls+Memories) | Polls/Kalender | Deck-Boards | Deck + Kalender | Memories/Photos, aber nur locker verknüpfbar | Mächtig, aber „zusammengesteckt" statt integriert |
| **Agorakit** | Termine + Umfragen | rudimentär | — | Datei-/Bildablage je Gruppe | Leichtgewichtiger Geheimtipp, kleineres Ökosystem |
| **Vikunja / Focalboard** | — | ✅ stark | ✅ | ❌ | reine Task-Tools, Sozialteil fehlt |
| **Mobilizon** | Events ✅ | ❌ | ❌ | ❌ | nur Event-Seite |
**Empfehlung zur Evaluation:** HumHub zuerst (deckt als Einziges alle vier Anforderungen in EINEM Tool), Nextcloud als Plan B falls HumHubs Modul-Lizenzmodell stört. Deployment-Ort-Frage (CFGMON? eigener Host?) und Matrix-SSO via Authentik (beide können OIDC!) gehören in die Evaluation.
Quellen: raid-helper.dev, raid-planner.com, alternativeto.net/software/open-event.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#49` — dort erstellt am 2026-08-01 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#49 -->
@@ -0,0 +1,37 @@
---
type: issue
id: "0081"
status: open
created: 2026-08-01
milestone: M3
priority: medium
projekt: gitops
gitlab_iid: "48"
related: []
---
# Gäste-Invite-Workflow per Bot (3-Tage-Accounts, Admin-Freischaltung, begrenzte Reaktivierung)
> Adoptiert aus [gitops#48](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/48) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
**Wunsch sorb (2026-08-01):** definierter Prozess für sichere, temporäre Gast-Einladungen:
1. **Einladung:** festgelegter Nutzerkreis schreibt den Bot an → Invite-Link wird generiert
2. **Initiales Limit:** Gast-Account läuft nach **3 Tagen** automatisch ab
3. **Permanente Freischaltung:** nur durch aktive Admin-Prüfung; sonst bleibt der Account deaktiviert
4. **Fallback:** ohne greifbaren Admin kann der einladende Kreis den Account über den Bot **max. 2× um je 1 Tag** reaktivieren, danach zwingend Admin
## Architektur-Realitätscheck (Stack-Gegebenheiten)
- Registrierung läuft in diesem Stack **ausschließlich über Authentik** (MAS-OIDC; die `matrix-invitation`-Flow-Infrastruktur mit Invitation-Stage existiert bereits aus Issue #7!). Der natürliche Invite-Link ist also ein **Authentik-Invitation-Token** (single-use, mit Ablauf) — kein Synapse-Registration-Token.
- **Draupnir** ist ein Moderations-Bot ohne Invite-/Lifecycle-Funktion — er kann Policy-seitig flankieren (Gast-Raumrechte), aber die Link-Generierung + Ablauf-/Reaktivierungslogik braucht einen **kleinen eigenen Bot** (Machart wie @alerts/maintenance-notify: Stdlib, Matrix-API + Authentik-API + MAS/Authentik-Deaktivierung). Empfehlung: eigener `@concierge`-Bot statt Draupnir-Verbiegung; Draupnir-Integration als Stufe 2 (z. B. Gast-Label → eingeschränkte Räume).
- **Ablauf/Deaktivierung:** Authentik-User-Attribut `expires_at` + periodischer Bot-Check (deaktiviert via Authentik-API → MAS-Sessions enden); Reaktivierungszähler als User-Attribut (max 2), Admin-Freischaltung = Attribut entfernen + Gruppe `members`.
- **Berechtigter Nutzerkreis:** Matrix-Raum als ACL (wer im `#einladungen`-Raum ist, darf den Bot nutzen) — einfach und sichtbar.
## Offene Designfragen (sorb)
- Wer ist der „festgelegte Nutzerkreis" initial? Eigener Raum ok?
- Soll die Admin-Prüfung im Matrix-Raum bestätigt werden (Reaktion/Kommando) oder in der Authentik-UI?
- Namens-/Branding-Wunsch für den Bot?
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#50` — dort erstellt am 2026-08-01 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#50 -->
@@ -0,0 +1,55 @@
---
type: issue
id: "0082"
status: open
created: 2026-08-01
milestone: M1
priority: high
projekt: gitops
gitlab_iid: "49"
related: []
---
# CVE-Alarme: eine Matrix-Nachricht pro CVE flutet den Security-Raum -- Zustellung derzeit stumm
> Adoptiert aus [gitops#49](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/49) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Aus dem Deploy von #47 (2026-08-01). Die Pipeline sammelt Daten, **die Alarm-Zustellung ist aber abgeklemmt**: in `monitoring/alertmanager/alertmanager.yml` routet `room="security"` auf einen Null-Receiver (Commit `2b715ca`).
## Warum
`TrivyCriticalVuln` und `TrivyHighVuln` erzeugen eine Alarm-Instanz **pro CVE pro Image**. Gemessen am ersten Scan-Durchlauf, bei 14 von 29 Images:
| | Anzahl |
|---|---|
| CRITICAL (feuert sofort, kein `for:`) | 59 |
| HIGH (`for: 24h`) | 445 |
Hochgerechnet auf alle 29 Images grob 120 CRITICAL / 900 HIGH.
`group_by: [alertname, instance]` legt alle in *eine* Gruppe -> ein Webhook-POST mit ~120 Alarmen. `matrix-alerts.py` schickt daraus **eine Matrix-Nachricht pro Alarm**, sequenziell.
## Was es zur Schleife macht
`save_state()` steht in `do_POST` **hinter** der Sende-Schleife. Sobald ein Send fehlschlaegt -- Synapse rate-limitet `rc_message` per Default nach ~10 Nachrichten mit 429 -- fliegt die Exception, der State wird **nicht** gespeichert, der Receiver antwortet 502. Alertmanager wiederholt daraufhin die komplette Gruppe, und die Fingerprint-Deduplizierung (`if fp in state: continue`), die genau das verhindern soll, ist beim Retry noch leer. Das wiederholt sich, statt einmalig durchzulaufen.
## Zum Scharfschalten noetig
1. **Zustellung buendeln.** Entweder `matrix-alerts.py` auf eine Sammelnachricht pro Webhook-Batch umbauen (die fuenf Pflichtfelder je CVE als eine Zeile -- bleibt vollstaendig), oder die Regeln auf `count by (target, severity)` aggregieren und die CVE-Details im Dashboard lassen.
2. **State inkrementell speichern**, nach jedem erfolgreichen Send, plus 429-Behandlung mit `Retry-After`.
Danach die `room="security"`-Route aus `alertmanager.yml` entfernen.
## Kleinere Punkte aus demselben Review
- `TrivyScanStale` kann ein Image, das **nie** erfolgreich gescannt wurde, nicht melden: ohne ersten Report gibt es keine Serie, an der `time() - trivy_last_scan_timestamp` haengen koennte. Ein dauerhaft fehlschlagendes Image bleibt still; `TargetDown` deckt nur den toten Exporter ab.
- Der Exporter prunt den First-Seen-State bei **jedem** Scrape. Ein transienter Lesefehler (`except: continue`) loescht die Erstfund-Zeitstempel des betroffenen Targets dauerhaft.
- `coturn/coturn:latest` ist als einziges Image ungepinnt (schon in #47 notiert).
## Nicht betroffen
Scanner, Exporter, Scrape-Job und Dashboard laufen und sind verifiziert -- Exporter-Last 0,4 s pro Scrape fuer 29 Reports, unkritisch bei 15 s Intervall. Details im `monitoring/README.md`.
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#51` — dort erstellt am 2026-08-01 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#51 -->
@@ -0,0 +1,61 @@
---
type: issue
id: "0083"
status: open
created: 2026-08-01
milestone: M1
priority: medium
projekt: gitops
gitlab_iid: "50"
related: []
---
# Monitoring-Deploy: geaenderte Configs greifen nicht ohne --force-recreate (Inode-Falle bei Einzeldatei-Mounts)
> Adoptiert aus [gitops#50](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/50) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Beim Deploy von #47 aufgefallen, betrifft aber **jede** Config-Aenderung am Monitoring-Stack.
## Symptom
`cd /opt/threadnet-operating && git pull && cd monitoring && docker compose up -d` aktiviert geaenderte Config-Dateien **nicht**. Nach dem Deploy von #47 liefen Scanner und Exporter, aber Prometheus hatte weder den neuen Scrape-Job `cve_exporter` noch die `axion-cve`-Regelgruppe geladen -- `promtool` fand 9 Regeln in der Datei, Prometheus kannte 6.
## Ursache
`prometheus.yml`, `alerts.yml` und `alertmanager.yml` sind als **einzelne Dateien** gemountet. Docker haengt so einen Bind-Mount am Inode auf. `git pull` schreibt eine neue Datei und benennt sie um -- neuer Inode. Der Container zeigt weiter auf die alte Datei.
Zwei Effekte, die es schwer sichtbar machen:
- `docker compose up -d` startet die Container nicht neu, weil die Service-Definition unveraendert ist. Es meldet `Running` und sieht erfolgreich aus.
- Ein `SIGHUP`-Reload laedt brav neu -- nur eben den **alten** Inhalt. Kein Fehler im Log.
Auf der Platte steht also die neue Config, im Container die alte, und nichts meldet einen Fehler.
## Nachweis
```
$ grep -c axion-cve monitoring/prometheus/alerts.yml # 1
$ docker exec prometheus grep -c axion-cve /etc/prometheus/alerts.yml # 0
```
## Abhilfe
Nach jedem `git pull`, der eine dieser Dateien anfasst:
```bash
docker compose up -d --force-recreate prometheus alertmanager
```
Verifikation muss **im Container** stattfinden, ein Blick auf die Platte beweist nichts.
## Nicht betroffen
Verzeichnis-Mounts (`grafana/provisioning/`, `grafana/dashboards/`) loesen ueber den Pfad auf und ziehen Aenderungen mit. Grafana liest **Provider-Definitionen** aber nur beim Start -- ein neuer Dashboard-Ordner braucht `docker compose restart grafana`. Dashboard-JSONs innerhalb eines bestehenden Providers werden laufend nachgezogen.
## Moegliche Dauerloesung
Statt Einzeldateien die Verzeichnisse mounten (`./prometheus:/etc/prometheus:ro`), dann verschwindet die Inode-Falle. Dokumentiert ist der Fallstrick vorerst in `monitoring/README.md` (Commit `2b715ca`).
---
*Migriert aus Gitea `sorb/axion1337.chat-gitops#52` — dort erstellt am 2026-08-01 von sorb.*
<!-- gitea-migration: sorb/axion1337.chat-gitops#52 -->
@@ -0,0 +1,45 @@
---
type: issue
id: "0084"
status: next
created: 2026-08-02
milestone: M1
priority: medium
due: 2026-09-01
projekt: gitops
gitlab_iid: "51"
related: []
---
# CI: CANONIZE_TOKEN für die automatische TURN-Rotation hinterlegen
> Adoptiert aus [gitops#51](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/51) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Der Job `canonize_rotation` in `.gitlab-ci.yml` übernimmt die monatliche TURN-Rotation automatisch (kein Handgriff mehr, kein Kalendereintrag). Der Schedule läuft, zwei Probeläufe sind durch — **es fehlt nur noch das Push-Token.**
## Was zu tun ist (2 Minuten, braucht deine Rechte)
1. *Settings → Access Tokens* in diesem Projekt: Token anlegen
- Name z. B. `canonize-rotation`
- Rolle **Maintainer** (nötig, weil `main` protected ist)
- Scope **`write_repository`** — mehr nicht
- Ablauf: setzen und im Kalender vormerken, sonst steht der Job irgendwann still
2. *Settings → CI/CD → Variables*: Variable **`CANONIZE_TOKEN`** mit dem Wert, **masked** und **protected**
Danach nichts weiter — der nächste Lauf nimmt sie von selbst.
## Warum das nötig ist
Der Rotations-CronJob läuft im Cluster und erreicht git.lab nicht; er pusht seinen Branch nach Gitea. Von dort muss die Rotation über git.lab zurück, sonst überschreibt sie der nächste Mirror-Push und Flux spielt still das **alte** Shared Secret wieder ein — ein Fehler, der kein Symptom erzeugt.
## Stand
| | |
|---|---|
| Job + Doku | ✅ `02c60cb`, CLAUDE.md in beiden Repos |
| Schedule (täglich 17:05) | ✅ angelegt |
| Probelauf | ✅ Pipeline 161 grün: „Keine offene Rotation" |
| `CANONIZE_TOKEN` | ⬜ **dieses Issue** |
Bis dahin ist nichts kaputt: Ohne offene Rotation läuft der Job grün durch. Erst wenn am 01.09. wirklich rotiert wird und das Token fehlt, bricht er ab — laut und sichtbar, statt still das Falsche zu tun.
Nebenbei aufgefallen: Der Branch `turn-secret-rotation-20260728-192656` liegt auf git.lab und Gitea, steckt aber längst in `main` — eine Karteileiche vom Juli. Kann weg, ist aber harmlos.
@@ -0,0 +1,14 @@
---
type: issue
id: "0085"
status: open
created: 2026-08-02
milestone: M2
priority: low
projekt: gitops
gitlab_iid: "52"
related: []
---
# docs/ trägt zwei Altbestände abgeschlossener Umzüge: TASKS.md und oldwiki/
> Adoptiert aus [gitops#52](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/52) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
@@ -0,0 +1,14 @@
---
type: issue
id: "0086"
status: open
created: 2026-08-02
milestone: M4
priority: low
projekt: gitops
gitlab_iid: "53"
related: []
---
# k8s-Ressourcen heißen noch element-web-docs (Rest des ThreadNet-Rebrands)
> Adoptiert aus [gitops#53](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/53) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
@@ -0,0 +1,34 @@
---
type: issue
id: "0087"
status: waiting
created: 2026-08-06
milestone: M4
priority: low
wartegrund: braucht eine Querformat-Wortmarke als SVG — Design-Arbeit, kein Deployment-Schritt
projekt: gitops
gitlab_iid: "55"
related: []
---
# Logo für die Authentik-Anmeldemaske entwerfen (Querformat/SVG)
> Adoptiert aus [gitops#55](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/55) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Die Anmeldemaske zeigt derzeit wieder **Authentiks eigenes Logo** (`480e281`). Unser Versuch mit `vector-icons/512.png` war unbrauchbar: Authentiks Default ist ein SVG, das sich seiner Box anpasst — ein PNG nimmt dort seine Naturgröße und rendert entsprechend riesig.
## Was gebraucht wird
Eine **Wortmarke im Querformat**, wie sie der Slot vorsieht. Vorhanden ist nur Quadratisches:
- `vector-icons/*.png` im Client — reine Bildmarken, 24 bis 1024 px
- `threadnet-logo-wortmarke.png` — Bildmarke **über** Schriftzug, also gestapelt und ebenfalls quadratisch (512×512). Liegt außerdem im `wiki`-Repo in der Gruppe `homelab`, die **keine Mirrors** hat: von Hetzner aus nicht erreichbar. Sie müsste erst mit dem Client ausgeliefert werden.
Ein SVG wäre das Richtige — dann passt es sich wie Authentiks eigenes an und die Größenfrage stellt sich nicht mehr.
## Wo es eingetragen wird
`branding_logo` im Brand-Blueprint, `apps/authentik/authentik-blueprints.yaml`.
⚠️ **Nicht über die Authentik-Oberfläche.** Solange die Zeile im Blueprint steht, gewinnt sie: eine Auswahl in der UI ist bis zur nächsten Reconciliation sichtbar und danach wieder weg. Steht im Kommentar an der Stelle.
Gehört inhaltlich zu management#29 (UI harmonisieren).
@@ -0,0 +1,35 @@
---
type: issue
id: "0088"
status: open
created: 2026-08-06
milestone: M1
priority: medium
projekt: gitops
gitlab_iid: "56"
related: []
---
# NetworkPolicy: ausgehender Verkehr ist unbeschränkt (13 Ingress-Regeln, 1 Egress)
> Adoptiert aus [gitops#56](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/56) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
In `apps/production/networkpolicy.yaml` stehen **13 Ingress-Regeln und genau ein Egress-Vorkommen**. Eingehend ist der Cluster dicht, ausgehend ist er offen: Jeder Pod darf ins Internet, in andere Namespaces und an den Metadaten-Dienst.
## Warum das zählt
Die Ingress-Regeln verhindern, dass jemand *hineinkommt*. Sie verhindern nicht, dass etwas, das schon drin ist, **hinausredet** — Daten abfließen lässt, Schadcode nachlädt oder sich zu einem Command-and-Control-Server verbindet. Genau das ist der Schritt, der aus einer kompromittierten Abhängigkeit einen Vorfall macht.
Besonders relevant hier, weil wir **fremden Code ausführen**: Synapse-Module, ClamAV, Draupnir, dazu eine große npm-Abhängigkeitskette im Client-Build.
## Was zu tun ist
1. Ist-Zustand aufnehmen: welche Pods brauchen wirklich ausgehende Verbindungen und wohin (Föderation, Let's Encrypt, Container-Registries, IONOS-API, Matrix-Push-Gateways).
2. `policyTypes: [Egress]` mit Default-Deny je Namespace, dann gezielt freigeben.
3. ⚠️ **DNS zuerst freigeben** (`kube-dns`/CoreDNS, UDP+TCP 53) — sonst steht alles, und der Fehler sieht aus wie ein Anwendungsproblem, nicht wie eine Firewall.
4. Föderation ist der schwierige Teil: Synapse muss zu beliebigen Matrix-Servern hinaus. Das lässt sich nicht auf eine Liste eingrenzen, solange die Föderation offen ist (gitops#17) — eher über einen Egress-Proxy oder bewusst offen lassen und dokumentieren.
## Rollback
NetworkPolicies sind additiv und einzeln löschbar. Der Not-Aus ist das Entfernen der Default-Deny-Regel.
*Gefunden am 2026-08-06 bei einer Bestandsaufnahme der Sicherheitslage.*
@@ -0,0 +1,35 @@
---
type: issue
id: "0089"
status: open
created: 2026-08-06
milestone: M5
priority: low
projekt: gitops
gitlab_iid: "58"
related: []
---
# Eigene Images sind unsigniert — beim Deploy prüft nichts die Herkunft
> Adoptiert aus [gitops#58](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/58) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Wir bauen eigene Images (`threadnet-web`, `clamav-http-scanner`, `desktop-build`) und schieben sie in die Gitea-Registry auf rohana. **Beim Deploy prüft nichts, ob das Image wirklich aus unserer CI stammt.** Kein cosign, keine Provenance, keine Admission-Prüfung. Der Tag ist die einzige Zusicherung — und ein Tag lässt sich überschreiben.
## Warum das hier konkreter ist als in vielen Setups
Der Weg ist lang und hat mehrere Stellen, an denen etwas eingeschleust werden könnte: git.lab baut → Push in die Gitea-Registry auf rohana → Flux zieht von dort → K3s startet es. Wer Schreibzugriff auf die Registry hat, kann einen Tag umbiegen, und im Cluster fällt es nicht auf.
Verwandt: gitops#16 (Pod Security Admission) — beides braucht am Ende einen Admission-Controller, das lässt sich in einem Zug denken.
## Was zu tun ist
1. In der CI nach dem Push mit **cosign** signieren (keyless über den GitLab-OIDC-Token, oder Schlüsselpaar als CI-Variable).
2. Im Cluster verifizieren — über einen Policy-Controller (Kyverno oder die Sigstore-Policy-Controller-Variante). Das ist der Teil mit der Arbeit.
3. ⚠️ **Reihenfolge beachten:** erst signieren und eine Weile mitlaufen lassen, dann erzwingen. Wer Verifikation aktiviert, bevor alle Images signiert sind, legt den Cluster still.
4. Fremd-Images (ESS-Chart, Authentik, Traefik) können nicht mit unserem Schlüssel signiert sein — die Richtlinie muss also nach Registry/Repository unterscheiden.
## Ehrliche Einordnung
`priority:low`. Das Risiko ist real, aber der Aufwand ist ein Policy-Controller plus Signaturkette, und der Angreifer bräuchte bereits Schreibzugriff auf unsere Registry. Andere Lücken aus derselben Bestandsaufnahme (Egress, Admin-MFA, Restore) kosten weniger und schützen mehr.
*Gefunden am 2026-08-06 bei einer Bestandsaufnahme der Sicherheitslage.*
@@ -0,0 +1,34 @@
---
type: issue
id: "0090"
status: open
created: 2026-08-06
milestone: M5
priority: low
projekt: gitops
gitlab_iid: "59"
related: []
---
# Kein Kubernetes-Audit-Log — Zugriffe an der API werden nicht protokolliert
> Adoptiert aus [gitops#59](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/59) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
`auditd` (gitops#26) protokolliert Dateizugriffe und Syscalls **auf dem Host**. Was jemand über die **Kubernetes-API** getan hat — Secret gelesen, Deployment geändert, in einen Pod exec-t — steht dort nicht. Dafür gibt es das Audit-Log des API-Servers, und das ist bei uns nicht konfiguriert.
## Warum das gerade hier fehlt
Zwei Dinge treffen zusammen:
- **ADR-0008** hält fest, dass Agenten-Sessions auf CFGMON root-äquivalent über die docker-Gruppe laufen und dabei **keinen Eintrag in `auth.log`** hinterlassen. Die ADR nennt als Ersatz ausdrücklich Git-Historie, Issues und AARs.
- Im Cluster gibt es dieselbe Lücke eine Ebene höher: Wer `kubectl` benutzt, hinterlässt nichts.
Solange alles über Flux und Git läuft, ist die Git-Historie tatsächlich das Protokoll. Der Punkt ist der Zugriff **daneben** — und genau der ist heute unsichtbar.
## Was zu tun ist
1. Audit-Policy schreiben (K3s: `--kube-apiserver-arg=audit-policy-file=…` plus `audit-log-path`). ⚠️ Das ist eine **Host-Änderung am K3s-Dienst**, kein Flux-Objekt — gehört zu `host-config/`.
2. Auf `Metadata` als Grundstufe beginnen und Secret-Zugriffe auf `RequestResponse` heben. Alles auf `RequestResponse` erzeugt riesige Logs **und schreibt Secret-Inhalte im Klartext ins Protokoll** — genau das nicht tun.
3. Über Alloy nach Loki einsammeln, damit es nicht nur auf der Platte liegt.
4. Sinnvoll zusammen mit gitops#25 (K3s API Hardening) — dieselbe Datei, derselbe Neustart.
*Gefunden am 2026-08-06 bei einer Bestandsaufnahme der Sicherheitslage.*
@@ -0,0 +1,78 @@
---
type: issue
id: "0091"
status: open
created: 2026-08-11
milestone: M1
priority: high
area: security
projekt: gitops
gitlab_iid: "61"
related:
- "docs/adr/0011-enrollment-localpart-kollision-verweigern.md"
---
# Enrollment: kollidierender Localpart übernimmt bestehendes Konto (on_conflict: add)
> Adoptiert aus [gitops#61](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/61) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
> Bei der Adoption ergänzt: Meilenstein M1 (fehlte auf GitLab); von den zwei
> area-Labels trägt das Schema eines — `security` bleibt, `authentik` entfällt.
## Befund
Der MAS-Upstream-Provider für Authentik verknüpfte eine neu registrierte
Upstream-Identität mit einem **bestehenden** lokalen Konto, sobald der
abgeleitete Localpart kollidiert:
```yaml
claims_imports:
localpart:
action: force
template: "{{ user.preferred_username }}"
on_conflict: add # <-- verknüpft statt abzubrechen
```
Zusammen mit Authentiks Benutzernamen-Eindeutigkeit, die **nur innerhalb von
Authentik** und **case-sensitive** greift, ergibt sich ein Übernahmeweg:
1. Ein Inhaber eines Einladungstokens registriert in Authentik einen Namen, der
als Matrix-Konto bereits existiert (oder eine Groß-/Kleinschreibungsvariante
davon — `boje` neben `Boje` ging live durch).
2. Authentiks Eindeutigkeit meldet keine Kollision, weil das Ziel-Matrix-Konto
für Authentik unsichtbar ist bzw. sich in der Schreibweise unterscheidet.
3. Beim ersten Login verknüpft MAS die neue Identität mit dem bestehenden Konto
— inklusive Räumen und Historie.
**Besonders exponiert:** Konten ganz ohne Upstream-Link, weil dort nichts
vorher da sein muss. Betroffen wären u. a. die Dienstkonten `draupnir`,
`alerts`, `maintenance-notify` sowie `frank`, `crank`, `shank`, `stank`,
`bojeledoggo`, `scanner-test`.
> Der Übernahmeweg wurde **nicht** aktiv ausprobiert (das wäre eine echte
> Kontoübernahme gewesen). Die Aussage stützt sich auf MAS' dokumentierte
> Semantik für `on_conflict` und auf die live reproduzierte case-sensitive
> Dublette `boje`/`Boje`.
## Wie es aufgefallen ist
Beim Anlegen eines Testkontos über den Einladungsflow entstand versehentlich
ein zweites Konto `boje` neben dem bestehenden `Boje` — kein Fehler, keine
Warnung. Die Nachprüfung der MAS-Claims-Konfiguration legte `on_conflict: add`
als Ursache offen.
## Fix (erledigt)
`on_conflict: fail` in `apps/production/custom-configs/mas-secret.yaml` — ein
kollidierender Localpart bricht die Provisionierung ab, statt zu verknüpfen.
Bestehende Verknüpfungen bleiben unberührt.
Commit `ef04d86`, gepusht nach git.lab. Rollt über Flux aus.
## Rest, bewusst offen
- **Authentiks case-sensitive Eindeutigkeit** verhindert `boje` neben `Boje`
weiterhin nicht. Der MAS-Fix fängt die Übernahme ab (Login schlägt fehl statt
zu verknüpfen), aber der Nutzer bekommt erst beim Login eine Fehlermeldung,
nicht schon bei der Registrierung. Eine Eindeutigkeitsprüfung (case-insensitive)
im Prompt-Stage des `matrix-invitation`-Flows wäre die saubere Ergänzung.
- **Verwaiste Zweitkonten** aus dieser Lücke (`apo2`, das gelöschte `boje`)
sind Altlasten, keine offene Verwundbarkeit.
@@ -0,0 +1,49 @@
---
type: issue
id: "0092"
status: waiting
created: 2026-07-29
milestone: M3
priority: low
wartegrund: wartet auf Entscheidung, welche Defaults verbindlich gelten sollen
projekt: threadnet-web
gitlab_iid: "1"
related: []
---
# Default-Client-Einstellungen (Theme, Features) für Neuinstallationen provisionieren
> Adoptiert aus [threadnet-web#1](https://git.lab/axion1337.chat/ThreadNet-Web/-/issues/1) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Neue Nutzer (egal ob Web-Client, installierte PWA oder Electron-Desktop) sollten mit
den für axion1337.chat gedachten Standardeinstellungen starten - nicht mit den generischen
Element-Defaults. Konkret betrifft das mindestens:
- **Theme**: `default_theme` ist im Web-Deployment bereits gesetzt (`aXion1337 Dark`, via
`element-values.yaml`) - muss für Electron-Builds ebenso gelten, ist dort aber nicht
eigenständig provisioniert.
- **Custom Features** (z.B. die Discord-Style-Raumliste): falls das ein Feature ist, das alle
Nutzer standardmäßig sehen sollen, muss geprüft werden, ob es aktuell hinter einem
Labs-Flag/einer manuellen Einstellung versteckt ist, die neue Nutzer nicht automatisch
bekommen.
- **Sonstige sinnvolle Defaults** (Benachrichtigungen, Video-Qualität, etc.) - einmal
durchgehen, was aktuell nur "zufällig" beim Entwickeln richtig eingestellt ist, statt
bewusst als Default für alle Nutzer konfiguriert.
**Konkreter Fund, der das Problem sichtbar gemacht hat** (beim manuellen Electron-Build,
siehe Release `desktop-v1.12.17-clientscan`): es gibt in `apps/desktop` keinen eigenen
Config-Variant-Ordner für unsere Instanz (wie `element.io/release`/`element.io/nightly`) -
das produktiv genutzte `config.json` musste manuell aus dem laufenden Pod kopiert werden, um
den generischen `config.sample.json`-Platzhalter zu ersetzen. Ohne eigenen Variant-Ordner
wiederholt sich das bei jedem zukünftigen Desktop-Build.
**Vorschlag**:
1. Eigenen Config-Variant-Ordner anlegen (z.B. `apps/desktop/element.io/axion1337/`) mit
`config.json` (Server-URL, Theme, gewünschte Default-Features) + `build.json` (Branding).
2. Web-seitige `element-values.yaml`-Config und Desktop-Variant synchron halten, damit beide
Clients dieselben Defaults haben.
3. Bewusste Entscheidung pro Feature/Setting treffen: soll es Default sein oder optional
bleiben - und das dokumentieren, statt es implizit zu lassen.
---
*Migriert aus Gitea `sorb/ThreadNet-Web#1` — dort erstellt am 2026-07-29 von sorb.*
<!-- gitea-migration: sorb/ThreadNet-Web#1 -->
@@ -0,0 +1,51 @@
---
type: issue
id: "0093"
status: open
created: 2026-07-29
milestone: M4
priority: medium
projekt: threadnet-web
gitlab_iid: "3"
related: []
---
# Settings normalisieren: Video/Audio-Einstellungen nur im Call-Widget, nicht im Haupt-Client
> Adoptiert aus [threadnet-web#3](https://git.lab/axion1337.chat/ThreadNet-Web/-/issues/3) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Beim Durchgehen der Einstellungs-Oberflächen aufgefallen: Element Web (der
Haupt-Client) und Element Call (das eingebettete Widget) haben **getrennte, sich teils
überschneidende Settings-UIs** für Audio/Video, ohne erkennbare Synchronisation.
**Konkreter Ist-Zustand**:
- Element Web hat einen eigenen "Voice & Video"-Tab
(`apps/web/src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx`) mit:
Audio-Eingabe-/Ausgabegerät, Video-Eingabegerät, sowie Echo-Cancellation/Noise-Suppression/
Auto-Gain-Toggles.
- Element Call (unser Fork, `threadnet-call`) hat im "Video"-Tab (heute erweitert,
Issue #8/#11-Kontext) eigene `AudioProcessingSettings` (dieselben drei Audio-Toggles!) UND
die `MediaQualitySettings` (Auflösung/Framerate/Bitrate/Codec für Kamera + Screen-Share).
- **Überschneidung**: die Echo/Noise/Gain-Toggles existieren scheinbar an beiden Stellen als
getrennte Settings-Objekte (unterschiedliche `SettingLevel`/Storage-Mechanismen je nach
Codebasis) - unklar, ob sie synchron bleiben oder sich widersprechen können.
- **Lücke**: die Video-Qualitätseinstellungen (Auflösung/Framerate/Bitrate/Codec) gibt es
**nur** im Call-Widget. Ein Nutzer, der seine Präferenzen vorab einstellen will (ohne
gerade in einem Call zu sein), findet dafür nichts im Haupt-Client - müsste dafür erst
einen Call starten/das Widget öffnen.
**Mehrwert einer Normalisierung**: konsistente, einmal auffindbare Einstellungen statt
zwei getrennter Orte mit potenziell widersprüchlichem Zustand. Nutzer erwarten
Video-Qualitätseinstellungen vermutlich eher im Haupt-Client als im Widget.
**Zu klären/umzusetzen** (spannt vermutlich beide Repos, `ThreadNet-Web` + `threadnet-call`):
1. Bestandsaufnahme: teilen sich die Audio-Toggles bereits denselben Storage-Mechanismus
(z.B. beide über Matrix-Account-Data) oder sind das wirklich zwei unabhängige Zustände?
2. Video-Qualitätseinstellungen: entweder in Element Webs eigenen "Voice & Video"-Tab
duplizieren (mit Sync zum Widget) oder zumindest einen Link/Verweis vom Haupt-Client zum
Call-Widget-Setting ergänzen, damit sie auffindbar sind, ohne aktiv in einem Call zu sein.
3. Grundsatzentscheidung: sollen beide UIs langfristig dieselben Setting-Objekte teilen
(echte Normalisierung), oder reicht ein Verweis/Link als pragmatischere Zwischenlösung?
---
*Migriert aus Gitea `sorb/ThreadNet-Web#3` — dort erstellt am 2026-07-29 von sorb.*
<!-- gitea-migration: sorb/ThreadNet-Web#3 -->
@@ -0,0 +1,47 @@
---
type: issue
id: "0094"
status: open
created: 2026-07-29
milestone: M3
priority: medium
projekt: threadnet-web
gitlab_iid: "4"
related: []
---
# Direkter Link zur 2FA/Passkey-Einrichtung in den Account-Settings
> Adoptiert aus [threadnet-web#4](https://git.lab/axion1337.chat/ThreadNet-Web/-/issues/4) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Übernommen aus gitops-Repo Issue #13 (dort geschlossen, siehe Verweis) - nach Testen
der Alternativen jetzt hier richtig verortet.
**Ausgangsproblem**: 2FA/Passkey-Selbsteinrichtung (`default-authenticator-totp-setup`/
`-webauthn-setup`, Authentik-Flow-Stages) ist aktuell nur über
`axion1337.chat/docs/setup/security.html` auffindbar, nicht direkt in den Account-Settings,
wo Nutzer sie erwarten würden.
**Geprüfte und verworfene Alternative**: MAS's OIDC-`account_management_uri`-Mechanismus
(den Element Web bereits über `externalAccountManagementUrl` in
`apps/web/src/components/views/settings/tabs/user/AccountUserSettingsTab.tsx` nutzt, aktuell
für den generischen "Konto verwalten"-Link zu MAS) unterstützt **keine** 2FA/TOTP/Passkey-
Deep-Link-Action. Live geprüft via
`https://account.axion1337.chat/.well-known/openid-configuration`
`account_management_actions_supported`: nur `profile`, `devices_list`, `device_view`,
`device_delete`, `cross_signing_reset`, `sessions_list`, `session_view`, `session_end`.
Ergibt architektonisch Sinn - MAS delegiert 2FA komplett an Authentik als Upstream-SSO und
kennt selbst kein "TOTP einrichten"-Konzept.
Ein MAS-Template-Fork (Askama/Tera, `templates.path`) wurde ebenfalls verworfen (mehr
Aufwand/Wartungsrisiko als eine Client-Änderung, siehe ursprüngliche Diskussion in
gitops#13).
**Vorschlag**: einfacher, direkter Link (kein OIDC-Mechanismus nötig) - ein zusätzlicher
Button/Link neben dem bestehenden "Konto verwalten"-Link in `AccountUserSettingsTab.tsx`
(oder alternativ `SecurityUserSettingsTab.tsx`), der direkt auf Authentiks eigene
Setup-Seite(n) zeigt (`default-authenticator-totp-setup`/`-webauthn-setup`). Statische URL,
über Config konfigurierbar, kein Forken von MAS-Templates nötig.
---
*Migriert aus Gitea `sorb/ThreadNet-Web#4` — dort erstellt am 2026-07-29 von sorb.*
<!-- gitea-migration: sorb/ThreadNet-Web#4 -->
@@ -0,0 +1,28 @@
---
type: issue
id: "0095"
status: open
created: 2026-07-31
milestone: M4
priority: low
projekt: threadnet-web
gitlab_iid: "6"
related: []
---
# Windows-Desktop: Code-Signing (+ Installer-Branding)
> Adoptiert aus [threadnet-web#6](https://git.lab/axion1337.chat/ThreadNet-Web/-/issues/6) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Der Windows-Build (Issue #5, jetzt geschlossen) liefert eine **unsignierte** NSIS-Exe — funktional, aber SmartScreen warnt beim Install. Optionen (aus #5 übernommen, Stand 2026-07-31):
1. **Azure Trusted Signing** (~10 $/Monat) — günstigster seriöser Weg, EV-äquivalente SmartScreen-Reputation, braucht Azure-Konto + Identitätsprüfung; electron-builder unterstützt es nativ (`azureSignOptions`).
2. **Certum Open-Source-/OV-Zertifikat** (Smartcard/Token, einmalig ~70130 €) — klassisch, Token-Handling in CI ist aber fummelig (physischer USB-Token an der Build-VM).
3. **SSL.com eSigner** (Cloud-Signing, teurer, API-freundlich).
Start unsigniert ist für den Community-Kreis okay; Signing lohnt, sobald der Client breiter verteilt wird.
Außerdem als Packaging-Feinschliff hier mit erledigen: **Installer-Branding** — aktuell Upstream-„Element Setup" (Default-`VARIANT_PATH` `element.io/release/build.json`); eine aXion-Variante (`apps/desktop/axion1337/`) mit eigenem appId/productName wäre der saubere Abschluss.
---
*Migriert aus Gitea `sorb/ThreadNet-Web#6` — dort erstellt am 2026-07-31 von sorb.*
<!-- gitea-migration: sorb/ThreadNet-Web#6 -->
@@ -0,0 +1,32 @@
---
type: issue
id: "0096"
status: waiting
created: 2026-07-31
milestone: M4
priority: medium
wartegrund: Gesamtklammer — appId-Wechsel (neues Profilverzeichnis) muss bewusst entschieden werden
projekt: threadnet-web
gitlab_iid: "7"
related: []
---
# Rebranding: Element → aXion1337 (Web + Desktop, Gesamtklammer)
> Adoptiert aus [threadnet-web#7](https://git.lab/axion1337.chat/ThreadNet-Web/-/issues/7) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Sammel-TODO (gewünscht von sorb, 2026-07-31): die Client-Komponenten tragen an etlichen Stellen noch Upstream-Element-Branding. Ziel: konsistentes aXion1337-Branding über Web + Desktop.
**Ist-Stand:**
- Web: `config.json` setzt bereits `brand: aXion1337.Chat` + eigenes Theme — aber Upstream-Assets (Favicons, Logos, Wortmarke im Onboarding/Auth-Flow) sind noch Element.
- Desktop: Installer heißt „Element Setup" (Default-`VARIANT_PATH` `element.io/release/build.json`), `appId: im.riot.app`, `productName: Element`, Element-Icons, Protokoll-Handler `io.element.desktop`/`element`.
**Arbeitspakete:**
1. Desktop-Variante `apps/desktop/axion1337/build.json` (eigenes appId/productName/Icons) + `VARIANT_PATH` in den CI-Jobs `desktop_linux`/`desktop_windows` setzen. ⚠️ appId-Wechsel = neues Install-/Profilverzeichnis für Bestandsnutzer (bewusst entscheiden, Migrationsnotiz).
2. Web-Assets: Favicon/Logo/Wortmarke gegen aXion-Varianten tauschen (Upstream-Merge-freundlich: eigene Dateien + Config-Referenzen statt Upstream-Dateien überschreiben, wo möglich).
3. Prüfen, welche Stellen Upstream regelmäßig anfasst (Merge-Reibung minimieren) — Ergebnis hier dokumentieren.
Teilaspekt Signing/Installer-Branding Windows: siehe #6 (bleibt dort für den Windows-Teil, dieses Issue ist die Gesamtklammer).
---
*Migriert aus Gitea `sorb/ThreadNet-Web#7` — dort erstellt am 2026-07-31 von sorb.*
<!-- gitea-migration: sorb/ThreadNet-Web#7 -->
@@ -0,0 +1,26 @@
---
type: issue
id: "0097"
status: open
created: 2026-08-01
milestone: M4
priority: low
projekt: threadnet-web
gitlab_iid: "9"
related: []
---
# Feedback-/Bugreport-Weg: eigener Rageshake oder Alternative (Zammad nachhalten)
> Adoptiert aus [threadnet-web#9](https://git.lab/axion1337.chat/ThreadNet-Web/-/issues/9) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Entscheidung sorb (2026-08-01): Der element.io-Rageshake-Endpoint ist aus der Desktop-Config **entfernt** (Commit `697e167`) — greift ab dem nächsten Desktop-Build. Bug-Melden-Knopf entfällt damit vorerst; Feedback läuft direkt über Matrix.
**Dieses Issue hält den künftigen eigenen Weg nach:**
- **Option 1: eigener Rageshake-Server** (element-hq/rageshake, klein, genau fürs Client-Log-Sammeln gebaut) — Client-Konfig zeigt dann auf uns.
- **Option 2 (sorb ins Spiel gebracht): [Zammad](https://zammad.org)** — vollwertiges Open-Source-Helpdesk/Ticketing. Deutlich mehr als Bug-Reports (Support-Postfach, Wissensbasis, könnte perspektivisch auch den Gäste-/Community-Support tragen), dafür ein ausgewachsener Dienst (Ruby/ES/Redis) mit Pflegeaufwand. Kein direkter Rageshake-Ersatz (nimmt keine automatischen Client-Log-Pakete an), eher eine strategische Ergänzung — ggf. beides: Rageshake für Logs, Zammad für menschliches Feedback.
Bewertung/Entscheidung bei Gelegenheit; bis dahin bewusst ohne Bug-Reporting.
---
*Migriert aus Gitea `sorb/ThreadNet-Web#9` — dort erstellt am 2026-08-01 von sorb.*
<!-- gitea-migration: sorb/ThreadNet-Web#9 -->
@@ -0,0 +1,38 @@
---
type: issue
id: "0098"
status: open
created: 2026-08-02
milestone: M4
priority: low
projekt: threadnet-web
gitlab_iid: "11"
related: []
---
# Themes-Rollout 2026-08-02: Web live, Linux + Windows gebaut, macOS lokal
> Adoptiert aus [threadnet-web#11](https://git.lab/axion1337.chat/ThreadNet-Web/-/issues/11) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Protokoll zum Ausrollen der elf neuen Themes (aXion1337 Light + zehn Paletten, gitops `a2438e1`).
## Web ✅ live
Flux reconciled auf `a2438e1`; **`https://axion1337.chat/config.json` liefert 17 Themes** — verifiziert, nicht abgeleitet. Keine Pod-Rotation nötig, kein Checksum-Problem (der Fallstrick vom 2026-08-01 trat nicht auf, weil sich nur ConfigMap-Inhalt änderte, den Element beim Laden liest).
## Desktop-Config `2a2bb37`
Die Desktop-Apps laden ihre **eigene** `config.json` (`apps/desktop/axion1337/config.json`) — ohne diesen Commit hätten sie die neuen Themes nicht. Chirurgisch eingefügt: 0 entfernte / 427 neue Zeilen, JSON validiert.
## Builds (Pipeline 144)
| Job | Ergebnis |
|---|---|
| `web` | ✅ 75 s |
| `desktop_linux` | ✅ 428 s — `.deb` + `.tar.gz`, 287 MB Artefakt |
| `desktop_windows` | ✅ 461 s — 135 MB Artefakt |
| `stop_windows_vm` | ausgelöst (8 GB wieder frei) |
**Verifikation der Linux-Artefakte:** Die Themes stecken in `resources/webapp.asar`, **nicht** in `app.asar` — dort hatte ich zuerst gesucht und fälschlich „fehlt" gemeldet. `webapp.asar` enthält die eingebettete `config.json` mit allen 17 Themes.
⚠️ `start_windows_vm` scheiterte zunächst (`No such container: windows-runner`), sorb hat den Container manuell neu gestartet — Ursache und Optionen als [management#21](https://git.lab/axion1337.chat/management/-/issues/21) festgehalten.
## macOS
Wird lokal auf sorbs Mac gebaut (arm64, unsigniert) — kein macOS-Runner im Lab. Ergebnis folgt als Kommentar.
@@ -0,0 +1,37 @@
---
type: issue
id: "0099"
status: open
created: 2026-08-06
milestone: M1
priority: medium
projekt: threadnet-web
gitlab_iid: "12"
related: []
---
# Upstream-Sicherheitsfixes lassen sich nicht mergen — kein gemeinsamer Vorfahre
> Adoptiert aus [threadnet-web#12](https://git.lab/axion1337.chat/ThreadNet-Web/-/issues/12) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Am 2026-08-06 gemessen (`docs/axion1337-fork.md`, Abschnitt 4): **Dieses Repo hat keine Upstream-Historie.** Element Web 1.12.17 kam am 2026-05-10 als kompletter Baum herein — in `3da3635`, im selben Commit wie das erste eigene Feature.
Damit gibt es **keinen gemeinsamen Vorfahren mit `element-hq/element-web`**. `git merge upstream/develop` ist nicht möglich; erzwungen kollidiert praktisch jede Datei.
## Warum das ein Sicherheitsthema ist, kein Build-Thema
Element veröffentlicht Sicherheitsfixes als neue Version. Bei einem normalen Fork zieht man sie mit einem Merge. Bei uns bedeutet dasselbe: neuen Upstream-Stand beschaffen und **unsere 12 Patches von Hand neu auftragen** — davon sieben in der Medien-Pipeline, wo Element gerade auf MVVM umbaut.
Das ergibt eine unangenehme Kette mit gitops#22 (Advisory-Monitoring): Wir würden von einer Lücke erfahren und wären trotzdem langsam. Die Zeitspanne zwischen „bekannt" und „gepatcht" ist das, was zählt — und sie ist hier strukturell zu lang.
⚠️ Verschärfend: Verschiebt Element beim MVVM-Umbau eine der `viewmodels/`-Dateien, entsteht **kein Konflikt** — unsere Zeilen sind schlicht weg, und Git meldet nichts.
## Was zu tun ist
1. **Zuerst messen, nicht bauen:** Auf welchem Stand ist Upstream inzwischen, und sind seit 1.12.17 Sicherheitsfixes für Element Web erschienen? Das beantwortet, ob das dringend ist oder Vorsorge.
2. `element-hq/element-web` als zweiten Remote aufnehmen und den Tag von 1.12.17 holen. Damit lässt sich ein Update wenigstens **als Diff** betrachten, statt blind zu kopieren.
3. Prüfen, ob sich ein gemeinsamer Vorfahre nachträglich herstellen lässt — ein Graft/Replace des Import-Commits auf den passenden Upstream-Tag. Wenn das trägt, sind künftige Updates wieder ein Merge.
4. Falls nicht: einen Ablauf schreiben, wie unsere 12 Patches auf einen neuen Stand aufgetragen werden — mit dem Funktionstest für ClamAV als Abnahme (verschlüsselte Datei senden, abgelehnte empfangen).
Zusammenhang: gitops#22 (Advisory-Monitoring) ist die Erkennung, dieses Issue die Reaktionsfähigkeit. Das eine nützt wenig ohne das andere.
*Gefunden am 2026-08-06 beim Vermessen der Merge-Reibung (Arbeitspaket 3 aus #7).*
@@ -0,0 +1,57 @@
---
type: issue
id: "0100"
status: open
created: 2026-08-07
milestone: M4
priority: low
projekt: threadnet-web
gitlab_iid: "13"
related: []
---
# Asset-Pfade tragen weiterhin "element" (themes/element/…)
> Adoptiert aus [threadnet-web#13](https://git.lab/axion1337.chat/ThreadNet-Web/-/issues/13) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
Aufgefallen beim Titelbild-Tausch (sorb, 2026-08-07): Das neue Hintergrundbild liegt unter
```
themes/element/img/backgrounds/alpenglow.jpg
```
Im Browser sieht man also weiterhin `element` im Pfad, obwohl der Client ThreadNet heißt.
## Wie weit das reicht
Betroffene Verzeichnisse im Fork:
- `apps/web/res/themes/element/` — das Theme-Verzeichnis
- `apps/web/res/img/element-icons/`
- `apps/desktop/element.io/` — Upstreams Varianten-Verzeichnis (unsere liegt daneben in `axion1337/`)
Verweise darauf im Quelltext: **8 Stellen**, unter anderem
| Datei | wofür |
|---|---|
| `SdkConfig.ts` | `auth_header_logo_url`, `welcome_background_url` |
| `AuthHeaderLogo.tsx`, `HomePage.tsx` | Fallback-Logo |
| `ErrorView.tsx` | drei Store-Badges |
| `HelpUserSettingsTab.tsx` | Link in der Danksagung |
⚠️ **Und außerhalb des Repos:** Authentiks Brand zeigt auf
`https://axion1337.chat/themes/element/img/backgrounds/alpenglow.jpg`
(`apps/authentik/authentik-blueprints.yaml`). Eine Umbenennung macht die
**Anmeldeseite grau**, wenn sie nicht in derselben Runde mitgezogen wird — und zwar in dieser Reihenfolge: erst Client bauen und ausrollen, dann Authentik.
## Warum das mehr kostet, als es aussieht
1. **`element` ist nicht nur ein Ordnername, sondern der Theme-Bezeichner.** Element Web löst Themes über diesen Pfad auf. Ein Umbenennen ohne vollständiges Nachziehen bricht die Theme-Auflösung — und das fällt erst im Browser auf, nicht im Build.
2. **Merge-Reibung.** `res/themes/element/` ist Upstreams Verzeichnis. Ein umbenanntes Verzeichnis kollidiert bei **jedem** Upstream-Update, das Theme-Assets anfasst — und wir haben ohnehin keinen gemeinsamen Vorfahren (ThreadNet-Web#12). Das verlagert Aufwand in jede künftige Aktualisierung.
## Ehrliche Einordnung
`priority:low`. Sichtbar ist es nur in URLs — Rechtsklick auf das Bild, Entwicklerwerkzeuge, geteilte Links. Kein Nutzer stolpert im normalen Betrieb darüber. Es ist Politur, und sie hat laufende Kosten.
**Naheliegender Zuschnitt, falls angegangen:** ein *zusätzliches* Verzeichnis `themes/threadnet/` für unsere eigenen Assets anlegen und nur die dorthin zeigen, die wir selbst mitbringen (Titelbild, Logos). Upstreams `themes/element/` bleibt unangetastet — dann entsteht keine Merge-Reibung, und der sichtbare Pfad stimmt für alles, was wirklich von uns ist.
Gehört inhaltlich zu ThreadNet-Web#7 (Rebranding-Gesamtklammer).
@@ -0,0 +1,44 @@
---
type: issue
id: "0101"
status: open
created: 2026-08-06
milestone: M4
priority: low
projekt: threadnet-call
gitlab_iid: "4"
related: []
---
# Kaputtes Paket 0.19.2-threadnet.6 in der Registry — Herkunft ungeklärt
> Adoptiert aus [threadnet-call#4](https://git.lab/axion1337.chat/threadnet-call/-/issues/4) (2026-08-18, ADR-0019). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei.
`0.19.2-threadnet.6` liegt mit **12.553 Bytes statt 12,8 MB** in der Gitea-npm-Registry: `package.json`, `README.md` und die beiden Lizenzdateien — **kein `dist/`**. Wer die Version installiert, bekommt ein leeres Paket.
Zusätzlich zeigt `dist-tags.latest` auf genau diese kaputte Version.
## Woher der Upload kam, ist offen
Beide `publish_npm`-Läufe vom 2026-08-06 sind **fehlgeschlagen**:
| Job | Pipeline | Fehler |
|---|---|---|
| 697 | 195 | `You must specify a tag using --tag when publishing a prerelease version` — Abbruch **vor** dem Upload |
| 699 | 196 | `409 Conflict — package version already exists` — die Version war da also schon |
In beiden Pipelines lief `build_embedded` erfolgreich, das Artefakt war da (Job 699 meldete selbst „package size: 12.8 MB"). Zwischen 16:12:27 und 16:13:45 hat niemand sonst publiziert, den ich in der GitLab-API sehe. Die Gitea-Packages-API antwortet mit meinem Token nicht, sonst stünde dort `created_at` und der Uploader.
**Ich habe keine belastbare Erklärung** — deshalb steht hier keine.
## Was bereits getan ist
- Veröffentlicht wurde stattdessen `0.19.2-threadnet.7` (geprüft: 12,8 MB, 151 Dateien unter `dist/`, Produktname enthalten).
- `publish_npm` bricht jetzt ab, wenn `embedded/web/dist` weniger als 50 Dateien hat (`8a38224`). Damit kann diese Fehlerklasse keine Versionsnummer mehr verbrennen.
- `--tag threadnet` gesetzt, weil npm Prereleases ohne dist-tag ablehnt (`760c6af`).
## Offen
1. **`.6` aus der Registry löschen** und prüfen, ob `dist-tags.latest` danach sinnvoll steht — braucht sorb (Gitea-Rechte).
2. Falls die Ursache doch noch auffindbar ist: Gitea-Log zum Zeitpunkt 16:1216:14 UTC ansehen.
Nicht dringend: `.5` (bis heute produktiv) und `.7` sind beide intakt, die kaputte `.6` wird von niemandem referenziert.
+4
View File
@@ -10,6 +10,9 @@
# next/waiting erweitert; due/host/area/wartegrund/gitlab_iid; # next/waiting erweitert; due/host/area/wartegrund/gitlab_iid;
# Regel waiting_requires_reason; globale Regel wip_limit (max. 2 # Regel waiting_requires_reason; globale Regel wip_limit (max. 2
# in-progress) in validate.py. # in-progress) in validate.py.
# * issue.projekt (ADR-0019): Herkunfts-Projekt adoptierter
# Komponenten-Issues; zusammen mit gitlab_iid die Spiegel-Adresse.
# Fehlt das Feld, ist management gemeint.
# * component: neuer Typ unter docs/components/ (Dateiname = Slug). # * component: neuer Typ unter docs/components/ (Dateiname = Slug).
# * wiki-page: Area-Enum um "vision" erweitert. # * wiki-page: Area-Enum um "vision" erweitert.
@@ -97,6 +100,7 @@ types:
host: { enum: [cfgmon, overmind, matrix, game], nullable: true } host: { enum: [cfgmon, overmind, matrix, game], nullable: true }
area: { enum: [security, infrastructure, database, element], nullable: true } area: { enum: [security, infrastructure, database, element], nullable: true }
wartegrund: { kind: str, nullable: true } wartegrund: { kind: str, nullable: true }
projekt: { enum: [gitops, threadnet-web, threadnet-call], nullable: true }
gitlab_iid: { pattern: "^\\d+$", nullable: true } gitlab_iid: { pattern: "^\\d+$", nullable: true }
related: { kind: links } related: { kind: links }
rules: rules:
+24 -12
View File
@@ -14,8 +14,10 @@ zu überspringen, und jede Prüfung bildet einen real passierten Fall ab
3. Meilenstein- und Prioritätspflicht über ALLE offenen 3. Meilenstein- und Prioritätspflicht über ALLE offenen
Gruppen-Issues. (realer Fall: gitops#61, entstanden 2026-08-11, Gruppen-Issues. (realer Fall: gitops#61, entstanden 2026-08-11,
ohne Meilenstein) ohne Meilenstein)
4. Issue-Drift management ↔ docs/issues/ — Titel, Zustand, 4. Issue-Drift GitLab ↔ docs/issues/ — Titel, Zustand, Meilenstein,
Meilenstein, Priorität, Status-Label. (F-001/F-017-Klasse) Priorität, Status-Label; seit ADR-0019 über management UND die
adoptierten Komponenten-Tracker (Feld `projekt`).
(F-001/F-017-Klasse)
5. Git-Hygiene seit 2026-08-07 — Autor- und Committer-Zeit 12:00:00 5. Git-Hygiene seit 2026-08-07 — Autor- und Committer-Zeit 12:00:00
UTC, kanonische Identität; ausgenommen sind maschinelle Absender UTC, kanonische Identität; ausgenommen sind maschinelle Absender
(MASCHINEN, per Adresse — ADR-0009). (F-002/F-003) (MASCHINEN, per Adresse — ADR-0009). (F-002/F-003)
@@ -188,29 +190,39 @@ def main() -> int:
if len(prios) != 1: if len(prios) != 1:
befunde.append(f"{ref}: {len(prios)} priority-Labels statt 1") befunde.append(f"{ref}: {len(prios)} priority-Labels statt 1")
# 4) Issue-Drift management ↔ docs/issues/ # 4) Issue-Drift GitLab ↔ docs/issues/ (management + adoptierte
gl = {i["iid"]: i for i in # Komponenten-Tracker, ADR-0019)
api(f"projects/{urllib.parse.quote(GRUPPE + '/management', safe='')}" spiegel_projekte = {
f"/issues?state=opened", token)} "management": "axion1337.chat/management",
"gitops": "axion1337.chat/axion1337.chat-gitops",
"threadnet-web": "axion1337.chat/ThreadNet-Web",
"threadnet-call": "axion1337.chat/threadnet-call",
}
gl = {}
for kurz, pfad in spiegel_projekte.items():
for i in api(f"projects/{urllib.parse.quote(pfad, safe='')}"
f"/issues?state=opened", token):
gl[(kurz, i["iid"])] = i
dateien = {} dateien = {}
for f in sorted((root / "docs/issues").glob("[0-9]*.md")): for f in sorted((root / "docs/issues").glob("[0-9]*.md")):
meta = frontmatter(f) meta = frontmatter(f)
proj = meta.get("projekt") or "management"
if meta.get("gitlab_iid"): if meta.get("gitlab_iid"):
dateien[int(meta["gitlab_iid"])] = (f, meta) dateien[(proj, int(meta["gitlab_iid"]))] = (f, meta)
elif meta.get("status") not in ("done", "rejected"): elif meta.get("status") not in ("done", "rejected"):
hinweise.append(f"{f.name}: noch nicht gespiegelt " hinweise.append(f"{f.name}: noch nicht gespiegelt "
f"(gitlab_iid fehlt — erwartet bis zum " f"(gitlab_iid fehlt — erwartet bis zum "
f"ersten Spiegel-Lauf)") f"ersten Spiegel-Lauf)")
for iid in sorted(set(gl) - set(dateien)): for proj, iid in sorted(set(gl) - set(dateien)):
befunde.append(f"management#{iid} ist offen auf GitLab, hat aber " befunde.append(f"{proj}#{iid} ist offen auf GitLab, hat aber "
f"keine kanonische Datei (zweites Backlog!)") f"keine kanonische Datei (zweites Backlog!)")
for iid, (f, meta) in sorted(dateien.items()): for (proj, iid), (f, meta) in sorted(dateien.items()):
if iid not in gl: if (proj, iid) not in gl:
if meta.get("status") not in ("done", "rejected"): if meta.get("status") not in ("done", "rejected"):
befunde.append(f"{f.name}: offen im Repo, aber auf GitLab " befunde.append(f"{f.name}: offen im Repo, aber auf GitLab "
f"geschlossen/fehlend — nachziehen") f"geschlossen/fehlend — nachziehen")
continue continue
i = gl[iid] i = gl[(proj, iid)]
if titel(f) != i["title"].strip(): if titel(f) != i["title"].strip():
befunde.append(f"{f.name}: Titel weicht von GitLab ab") befunde.append(f"{f.name}: Titel weicht von GitLab ab")
ms = (i.get("milestone") or {}).get("title", "") ms = (i.get("milestone") or {}).get("title", "")
+57 -29
View File
@@ -1,12 +1,15 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""spiegel_issues.py — Repo → GitLab, ein deterministischer Spiegel. """spiegel_issues.py — Repo → GitLab, ein deterministischer Spiegel.
docs/issues/ ist kanonisch (ADR-0012); dieses Skript bespielt die docs/issues/ ist kanonisch (ADR-0012, seit ADR-0019 auch für die
adoptierten Komponenten-Tracker); dieses Skript bespielt die
GitLab-Ansicht, damit Board, Meilensteine und Labels weiterarbeiten. GitLab-Ansicht, damit Board, Meilensteine und Labels weiterarbeiten.
Gespiegelt werden **nur** Titel, Zustand, Meilenstein, Priorität, Das Zielprojekt bestimmt das Frontmatter-Feld `projekt` (fehlt es:
Fälligkeit und Status-Label — nie Beschreibungen (der Migrations- management). Gespiegelt werden **nur** Titel, Zustand, Meilenstein,
Fußtext und die Kommentare auf GitLab bleiben unangetastet), und nie Priorität, Fälligkeit und Status-Label — nie Beschreibungen (der
in Gegenrichtung. Migrations-Fußtext und die Kommentare auf GitLab bleiben
unangetastet), und nie in Gegenrichtung. Eine offene Datei, deren
GitLab-Issue geschlossen wurde, wird wieder geöffnet.
Default ist **Dry-Run**: druckt jeden geplanten API-Aufruf und ändert Default ist **Dry-Run**: druckt jeden geplanten API-Aufruf und ändert
nichts. `--ausfuehren` schreibt wirklich — das stößt nur sorb an. nichts. `--ausfuehren` schreibt wirklich — das stößt nur sorb an.
@@ -27,8 +30,13 @@ import urllib.request
from pathlib import Path from pathlib import Path
API = "https://git.lab/api/v4" API = "https://git.lab/api/v4"
PROJEKT = urllib.parse.quote("axion1337.chat/management", safe="")
GRUPPE = "axion1337.chat" GRUPPE = "axion1337.chat"
PROJEKTE = { # Frontmatter `projekt` -> GitLab-Projektpfad (ADR-0019)
"management": "axion1337.chat/management",
"gitops": "axion1337.chat/axion1337.chat-gitops",
"threadnet-web": "axion1337.chat/ThreadNet-Web",
"threadnet-call": "axion1337.chat/threadnet-call",
}
STATUS_LABEL = {"open": None, "next": "status:next", STATUS_LABEL = {"open": None, "next": "status:next",
"in-progress": "status:doing", "waiting": "status:wartet"} "in-progress": "status:doing", "waiting": "status:wartet"}
@@ -84,13 +92,17 @@ def main() -> int:
meilensteine = {re.match(r"^(M\d)\b", m["title"]).group(1): m["id"] meilensteine = {re.match(r"^(M\d)\b", m["title"]).group(1): m["id"]
for m in alle(f"groups/{GRUPPE}/milestones", token) for m in alle(f"groups/{GRUPPE}/milestones", token)
if re.match(r"^M\d\b", m["title"])} if re.match(r"^M\d\b", m["title"])}
gitlab = {i["iid"]: i for i in pfade = {kurz: urllib.parse.quote(pfad, safe="")
alle(f"projects/{PROJEKT}/issues?state=opened", token)} for kurz, pfad in PROJEKTE.items()}
gitlab = {kurz: {i["iid"]: i for i in
alle(f"projects/{pfade[kurz]}/issues?state=all", token)}
for kurz in PROJEKTE}
plan: list[tuple[str, str, dict]] = [] plan: list[tuple[str, str, dict, str]] = []
gespiegelt: set[int] = set() gespiegelt: set[tuple[str, int]] = set()
for f in sorted((root / "docs/issues").glob("[0-9]*.md")): for f in sorted((root / "docs/issues").glob("[0-9]*.md")):
meta, titel = frontmatter_und_titel(f) meta, titel = frontmatter_und_titel(f)
proj = meta.get("projekt") or "management"
offen = meta.get("status") not in ("done", "rejected") offen = meta.get("status") not in ("done", "rejected")
labels = [f"priority:{meta['priority']}"] labels = [f"priority:{meta['priority']}"]
if STATUS_LABEL.get(meta.get("status")): if STATUS_LABEL.get(meta.get("status")):
@@ -100,24 +112,27 @@ def main() -> int:
labels.append(f"{feld}:{meta[feld]}") labels.append(f"{feld}:{meta[feld]}")
soll = {"title": titel, "labels": sorted(labels), soll = {"title": titel, "labels": sorted(labels),
"milestone_id": meilensteine.get(meta.get("milestone")), "milestone_id": meilensteine.get(meta.get("milestone")),
"due_date": meta.get("due") or None, "due_date": meta.get("due") or None}
"state_event": None if offen else "close"}
iid = int(meta["gitlab_iid"]) if meta.get("gitlab_iid") else None iid = int(meta["gitlab_iid"]) if meta.get("gitlab_iid") else None
if iid is None: if iid is None:
if offen: if offen:
plan.append(("ANLEGEN", f.name, plan.append(("ANLEGEN", f.name,
{k: v for k, v in soll.items() {k: v for k, v in soll.items() if v is not None},
if k != "state_event" and v is not None})) proj))
continue continue
gespiegelt.add(iid) gespiegelt.add((proj, iid))
ist = gitlab.get(iid) ist = gitlab[proj].get(iid)
if ist is None: if ist is None:
if offen: if offen:
plan.append(("MELDEN", f.name, plan.append(("MELDEN", f.name,
{"grund": f"#{iid} auf GitLab nicht offen, " {"grund": f"{proj}#{iid} auf GitLab nicht "
f"Datei aber {meta.get('status')}"})) f"gefunden, Datei aber {meta.get('status')}"},
proj))
continue continue
ist_offen = ist["state"] == "opened"
if not offen and not ist_offen:
continue # beidseitig zu — Historie nicht anfassen
delta = {} delta = {}
if ist["title"].strip() != titel: if ist["title"].strip() != titel:
delta["title"] = titel delta["title"] = titel
@@ -130,26 +145,39 @@ def main() -> int:
delta["milestone_id"] = soll["milestone_id"] delta["milestone_id"] = soll["milestone_id"]
if (ist.get("due_date") or None) != soll["due_date"]: if (ist.get("due_date") or None) != soll["due_date"]:
delta["due_date"] = soll["due_date"] delta["due_date"] = soll["due_date"]
if not offen: if offen and not ist_offen:
delta["state_event"] = "close" delta["state_event"] = "reopen"
elif not offen and ist_offen:
delta = {"state_event": "close"}
if delta: if delta:
plan.append(("ÄNDERN", f"{f.name} (#{iid})", delta)) plan.append(("ÄNDERN", f"{f.name} (#{iid})", delta, proj))
for iid in sorted(set(gitlab) - gespiegelt): for kurz in PROJEKTE:
plan.append(("MELDEN", f"management#{iid}", for iid in sorted(iid for iid, i in gitlab[kurz].items()
{"grund": "offen auf GitLab ohne kanonische Datei — " if i["state"] == "opened"
"wird NICHT automatisch geschlossen"})) and (kurz, iid) not in gespiegelt):
plan.append(("MELDEN", f"{kurz}#{iid}",
{"grund": "offen auf GitLab ohne kanonische Datei — "
"wird NICHT automatisch geschlossen"}, kurz))
modus = "AUSFÜHREN" if scharf else "DRY-RUN" modus = "AUSFÜHREN" if scharf else "DRY-RUN"
for aktion, wer, daten in plan: for aktion, wer, daten, proj in plan:
print(f"[{modus}] {aktion} {wer}: {json.dumps(daten, ensure_ascii=False)}") print(f"[{modus}] {aktion} {wer}: {json.dumps(daten, ensure_ascii=False)}")
if scharf and aktion == "ANLEGEN": if scharf and aktion == "ANLEGEN":
api(f"projects/{PROJEKT}/issues", token, "POST", daten) neu = api(f"projects/{pfade[proj]}/issues", token, "POST", daten)
# Spiegel-Adresse zurückschreiben, sonst legt der nächste
# Lauf ein Duplikat an.
datei = root / "docs/issues" / wer
text = datei.read_text(encoding="utf-8")
datei.write_text(text.replace(
"\n---\n", f'\ngitlab_iid: "{neu["iid"]}"\n---\n', 1),
encoding="utf-8")
print(f" -> {proj}#{neu['iid']} angelegt, gitlab_iid in {wer}")
elif scharf and aktion == "ÄNDERN": elif scharf and aktion == "ÄNDERN":
iid = int(wer.rsplit("#", 1)[1].rstrip(")")) iid = int(wer.rsplit("#", 1)[1].rstrip(")"))
api(f"projects/{PROJEKT}/issues/{iid}", token, "PUT", daten) api(f"projects/{pfade[proj]}/issues/{iid}", token, "PUT", daten)
print(f"spiegel_issues [{modus}]: {len(plan)} Aktionen " print(f"spiegel_issues [{modus}]: {len(plan)} Aktionen "
f"({sum(1 for a, _, _ in plan if a == 'MELDEN')} Meldungen)") f"({sum(1 for a, *_ in plan if a == 'MELDEN')} Meldungen)")
return 0 return 0
+139
View File
@@ -0,0 +1,139 @@
#!/usr/bin/env python3
"""adoptiere.py — Komponenten-Issues in docs/issues/ übernehmen (ADR-0019).
Liest die offenen Issues der adoptierten GitLab-Projekte und legt für
jedes eine kanonische Datei an: fortlaufende Nummer hinter der höchsten
vorhandenen, Herkunft als `projekt` + `gitlab_iid` im Frontmatter, die
GitLab-Beschreibung wortgleich als Rumpf. Kommentare und Verlauf bleiben
auf GitLab (wie beim Management-Import 2026-08-11, ADR-0012).
Deterministisch und idempotent: existiert bereits eine Datei mit
demselben (projekt, gitlab_iid), wird das Issue übersprungen.
Default ist Dry-Run; --execute schreibt die Dateien. Stdlib-only.
Usage: python3 verfahren/issue-adoption/adoptiere.py [repo-root] [--execute]
(Token: ~/.config/gitlab-lab/token oder $GITLAB_TOKEN)
"""
from __future__ import annotations
import json
import os
import re
import sys
import urllib.parse
import urllib.request
from pathlib import Path
API = "https://git.lab/api/v4"
# Reihenfolge = Nummernvergabe: erst gitops, dann die Clients.
PROJEKTE = [
("gitops", "axion1337.chat/axion1337.chat-gitops"),
("threadnet-web", "axion1337.chat/ThreadNet-Web"),
("threadnet-call", "axion1337.chat/threadnet-call"),
]
STATUS_AUS_LABEL = {"status:next": "next", "status:doing": "in-progress",
"status:wartet": "waiting"}
AREAS = ("security", "infrastructure", "database", "element")
def token_lesen() -> str:
if os.environ.get("GITLAB_TOKEN"):
return os.environ["GITLAB_TOKEN"]
return (Path.home() / ".config/gitlab-lab/token").read_text(
encoding="utf-8").strip()
def alle(pfad: str, token: str):
daten, seite = [], 1
while True:
trenner = "&" if "?" in pfad else "?"
req = urllib.request.Request(
f"{API}/{pfad}{trenner}per_page=100&page={seite}",
headers={"PRIVATE-TOKEN": token})
with urllib.request.urlopen(req) as antwort:
batch = json.load(antwort)
daten += batch
if len(batch) < 100:
return daten
seite += 1
def slug(text: str, maxlen: int = 45) -> str:
text = text.lower()
for a, b in (("ä", "ae"), ("ö", "oe"), ("ü", "ue"), ("ß", "ss")):
text = text.replace(a, b)
text = re.sub(r"[^a-z0-9]+", "-", text).strip("-")
return text[:maxlen].rstrip("-")
def vorhandene(root: Path) -> tuple[int, set[tuple[str, str]]]:
hoechste, belegt = 0, set()
for f in (root / "docs/issues").glob("[0-9]*.md"):
meta = {}
for z in f.read_text(encoding="utf-8").splitlines()[1:]:
if z == "---":
break
m = re.match(r"^(\w+):\s*(.*)$", z)
if m:
meta[m.group(1)] = m.group(2).strip().strip('"')
hoechste = max(hoechste, int(meta.get("id", 0)))
if meta.get("gitlab_iid"):
belegt.add((meta.get("projekt") or "management",
meta["gitlab_iid"]))
return hoechste, belegt
def main() -> int:
argv = [a for a in sys.argv[1:] if a != "--execute"]
scharf = "--execute" in sys.argv
root = Path(argv[0]) if argv else Path.cwd()
token = token_lesen()
nr, belegt = vorhandene(root)
modus = "EXECUTE" if scharf else "DRY-RUN"
for kurz, pfad in PROJEKTE:
issues = alle(f"projects/{urllib.parse.quote(pfad, safe='')}"
f"/issues?state=opened", token)
for i in sorted(issues, key=lambda x: x["iid"]):
if (kurz, str(i["iid"])) in belegt:
print(f"[{modus}] {kurz}#{i['iid']} bereits adoptiert — übersprungen")
continue
nr += 1
ms = (i.get("milestone") or {}).get("title", "")
ms = ms[:2] if re.match(r"^M[1-5]\b", ms) else ""
prio = next((l.split(":")[1] for l in i["labels"]
if l.startswith("priority:")), "")
status = next((STATUS_AUS_LABEL[l] for l in i["labels"]
if l in STATUS_AUS_LABEL), "open")
area = next((a for a in AREAS if f"area:{a}" in i["labels"]), None)
name = f"{nr:04d}-{kurz}-{i['iid']}-{slug(i['title'])}.md"
zeilen = ["---", "type: issue", f'id: "{nr:04d}"',
f"status: {status}", f"created: {i['created_at'][:10]}",
f"milestone: {ms or 'FEHLT'}", f"priority: {prio}"]
if i.get("due_date"):
zeilen.append(f"due: {i['due_date']}")
if area:
zeilen.append(f"area: {area}")
if status == "waiting":
zeilen.append("wartegrund: WARTEGRUND-NACHTRAGEN")
zeilen += [f"projekt: {kurz}", f'gitlab_iid: "{i["iid"]}"',
"related: []", "---", f"# {i['title'].strip()}", "",
f"> Adoptiert aus [{kurz}#{i['iid']}]"
f"(https://git.lab/{pfad}/-/issues/{i['iid']}) "
f"(2026-08-18, ADR-0019). Kommentare und Verlauf "
f"bleiben dort; kanonisch ist ab jetzt diese Datei.",
""]
rumpf = (i.get("description") or "").strip()
if rumpf:
zeilen += [rumpf, ""]
print(f"[{modus}] {kurz}#{i['iid']} -> {name}"
+ (" (MEILENSTEIN FEHLT)" if not ms else "")
+ (" (wartegrund nachtragen)" if status == "waiting" else ""))
if scharf:
(root / "docs/issues" / name).write_text(
"\n".join(zeilen), encoding="utf-8")
return 0
if __name__ == "__main__":
sys.exit(main())