Add: DNS über Pi-Hole LXC. Härtung von CT-105 Wireguard.

This commit is contained in:
2026-10-05 17:45:38 +02:00
parent 0758813e63
commit b40f726d22
26 changed files with 1014 additions and 158 deletions
+7 -7
View File
@@ -3,7 +3,7 @@
**Dokumenten-ID:** GNAI-005-SEC
**Titel:** Homelab / LLM-Infrastruktur – Sicherheits-, Authentifizierungs- und Zugriffskonzept
**Status:** Verbindliches Zielmodell; technische Durchsetzung teilweise offen
**Vorgänger / Referenzen:** [Architektur](../ai/GNAI-004-INFRA_Architecture.md), [LXC-SSH](proxmox-lxc/GNAI-011-SEC_LXC-SSH-Architecture.md), [Gateway](proxmox-lxc/ct-105/GNAI-007-SEC_VPN.md), [GUI-SOP](proxmox-lxc/ct-105/GNAI-007-SEC_VPN_Client-Management.md), [Reverse Proxy](proxmox-lxc/ct-103/GNAI-006-SEC_Reverse-Proxy.md)
**Vorgänger / Referenzen:** [Architektur](llm-server/ai/GNAI-004-INFRA_Architecture.md), [LXC-SSH](proxmox-server/GNAI-011-SEC_LXC-SSH-Architecture.md), [Gateway](proxmox-server/lxc/ct-105-wireguard/GNAI-007-SEC_VPN.md), [GUI-SOP](proxmox-server/lxc/ct-105-wireguard/GNAI-007-SEC_VPN_Client-Management.md), [Reverse Proxy](proxmox-server/lxc/ct-103-reverse-proxy/GNAI-006-SEC_Reverse-Proxy.md)
---
@@ -43,7 +43,7 @@ Familienmitglieder (Betreiber und Bruder) dürfen das gesamte Heimnetz `10.0.0.0
### 2.2 Server Admin
Server Admins dürfen Proxmox `10.0.0.2`, aktuelle Service-LXCs und KI-Server `10.0.0.90` erreichen. NPM und wg-easy zählen zu den Service-LXCs. Allgemeine Heimnetz-PCs/Clients gehören nicht zu dieser Freigabe. Serverseitig eine Liste bestätigter Hosts pflegen; der statische Adressbereich bis `.99` ist keine pauschale Zugriffsfreigabe.
Server Admins dürfen Proxmox `10.0.0.2`, aktuelle Service-LXCs und KI-Server `10.0.0.90` erreichen. Pi-hole, NPM und wg-easy zählen zu den Service-LXCs. Allgemeine Heimnetz-PCs/Clients gehören nicht zu dieser Freigabe. Serverseitig eine Liste bestätigter Hosts pflegen; der statische Adressbereich bis `.99` ist keine pauschale Zugriffsfreigabe. Für internes Split-DNS verwenden Server-Admin-Profile Pi-hole `10.0.0.14`.
### 2.3 LLM Admin
@@ -94,7 +94,7 @@ Ein normaler LLM User erhält **keinen WireGuard-Peer**, sofern für einen konkr
| OpenWebUI / weitere Webanwendungen | nach Anwendungsrechten | nach Anwendungsrechten | nach Anwendungsrechten | Soll: HTTPS und geeignete Anmeldung |
| llama.cpp / Datenbanken | nur nach Backend-/Dienstpolicy | nur nach Backend-/Dienstpolicy | nur KI-Server-bezogen nach Bedarf | nicht direkt öffentlich |
Das ist die Sollmatrix. Per-Client Firewall ist aktuell deaktiviert; Keycloak-Admin-Exposure ist durch einen externen Betreiber-Test bestätigt. Keine Sollfreigabe wird damit als bereits sicher implementiert behauptet.
Das ist die Sollmatrix. Die wg-easy-Per-Client-Firewall bleibt deaktiviert; eine native nftables-Rollenpolicy ist aktiv, aber noch nicht vollständig bereinigt oder abgenommen. Keycloak-Admin-Exposure ist durch einen externen Betreiber-Test bestätigt. Keine Sollfreigabe wird damit als bereits sicher implementiert behauptet.
---
@@ -170,9 +170,9 @@ zusätzliche per-Workstation- oder per-Peer-SSH-Allowlist.
## 5. Netzwerk-Autorisierung
Familie erhält ausdrücklich Heimnetz-Vollzugriff. Server Admins und LLM Admins erhalten nur die beschlossenen Zielhosts. Die **serverseitigen Firewall Allowed IPs** in wg-easy sollen diese Rechte durchsetzen, während clientseitige Allowed IPs nur Routing festlegen. Ein Benutzer darf die Serverbeschränkung nicht durch Änderungen an seinem lokalen Profil umgehen können. Für Gateway-eigene Dienste wird die INPUT-Chain separat geprüft und anhand zugeordneter VPN-Quellen begrenzt.
Familie erhält ausdrücklich Heimnetz-Vollzugriff. Server Admins und LLM Admins erhalten nur die beschlossenen Zielhosts. Native nftables-Sets auf CT 105 setzen diese Rechte anhand von Peer-IP und Zielhost durch, während clientseitige Allowed IPs nur Routing festlegen. Ein Benutzer darf die Serverbeschränkung nicht durch Änderungen an seinem lokalen Profil umgehen können. Für Gateway-eigene Dienste wird die INPUT-Chain separat anhand zugeordneter VPN-Quellen begrenzt.
Aktuell fehlt die Per-Client-Durchsetzung. Negative Tests müssen mit absichtlich erweitertem Client-Routing und aktivem Zielservice stattfinden. Normale LLM-Anwendungsnutzer erhalten keinen Managementpeer. Geräteprofile werden durch Administratoren erstellt und geschützt verteilt. Die GUI ist für Familie und Server Admins vorgesehen.
Die native Rollenpolicy wurde am 05.10.2026 zunächst für den alten und neuen Server-Admin-Laptop-Peer geladen. Die parallelen wg-easy-`iptables-legacy`-Hooks und Laufzeitregeln wurden entfernt und blieben nach einem kontrollierten wg-easy-Neustart fort. Positive und negative Abnahme stehen noch aus. Negative Tests müssen mit absichtlich erweitertem Client-Routing und aktivem Zielservice stattfinden. Normale LLM-Anwendungsnutzer erhalten keinen Managementpeer. Geräteprofile werden durch Administratoren erstellt und geschützt verteilt. Die GUI ist für Familie und Server Admins vorgesehen.
MASQUERADE wird mangels Rückroute verwendet; es verbirgt gegenüber LAN-Diensten die ursprüngliche Peer-IP. Zielkontrolle muss daher am VPN-Gateway vor NAT erfolgen. Lokale LAN-Quellen lassen sich nicht allein durch die VPN-Rolle gruppengenau zuordnen; hierfür sind zusätzliche Regeln/Identitäten nötig.
@@ -222,7 +222,7 @@ Nicht vorgesehen ist ein pauschales Blocking aller HTTP-Fehlercodes.
Insbesondere soll ein allgemeiner `404` nicht automatisch als Angriff gewertet werden.
Die vorhandenen NPM-Jails sind in [GNAI-006](proxmox-lxc/ct-103/GNAI-006-SEC_Reverse-Proxy.md) dokumentiert. Ihre Funktion nach dem Image-Pin sowie Neustart und Wiederanlauf müssen noch abgenommen werden.
Die vorhandenen NPM-Jails sind in [GNAI-006](proxmox-server/lxc/ct-103-reverse-proxy/GNAI-006-SEC_Reverse-Proxy.md) dokumentiert. Ihre Funktion nach dem Image-Pin sowie Neustart und Wiederanlauf müssen noch abgenommen werden.
---
@@ -311,4 +311,4 @@ Das Sicherheitsmodell basiert damit nicht auf einem einzelnen „Security-Progra
## 11. Umsetzungsnachweise
Dieses Dokument definiert die Zugriffsziele. Der [Dokumentationsindex](../GNAI-000-DOC_Index.md) bündelt den aktuellen Projektstand. Konkrete Gateway-Regeln und Rollentests stehen in [GNAI-007](proxmox-lxc/ct-105/GNAI-007-SEC_VPN.md), der Keycloak-Adminpfad und Fail2Ban in [GNAI-006](proxmox-lxc/ct-103/GNAI-006-SEC_Reverse-Proxy.md). Offene Zielrechte gelten bis zu ihrer technischen Abnahme nicht als durchgesetzt.
Dieses Dokument definiert die Zugriffsziele. Der [Dokumentationsindex](../GNAI-000-DOC_Index.md) bündelt den aktuellen Projektstand. Konkrete Gateway-Regeln und Rollentests stehen in [GNAI-007](proxmox-server/lxc/ct-105-wireguard/GNAI-007-SEC_VPN.md), der Keycloak-Adminpfad und Fail2Ban in [GNAI-006](proxmox-server/lxc/ct-103-reverse-proxy/GNAI-006-SEC_Reverse-Proxy.md). Offene Zielrechte gelten bis zu ihrer technischen Abnahme nicht als durchgesetzt.
+8 -12
View File
@@ -1,20 +1,16 @@
# Plattform- und Hostdokumentation
# Plattform- und Gerätedokumentation
Dieser Bereich trennt den physischen KI-Host von den auf Proxmox betriebenen
LXCs. Übergreifende Rollen und Zugriffsziele bleiben auf dieser Ebene;
host- beziehungsweise LXC-spezifische Betriebsdokumente liegen in eigenen
Unterverzeichnissen. Die SSH-Konfiguration des Proxmox-Hypervisors selbst ist
nicht Teil der LXC-Dokumentation.
Dieser Bereich bildet die beiden physischen Geräte der Infrastruktur ab: den
Proxmox-Server mit seinen LXCs und den separaten LLM-Server mit AI-Serving und
CPU-Encoding. Übergreifende Rollen und Zugriffsziele bleiben auf dieser Ebene.
Die SSH-Konfiguration des Proxmox-Hypervisors selbst ist weiterhin nicht Teil
der LXC-Dokumentation.
| Dokument | Inhalt |
| --- | --- |
| [Ghostnet.AI-Host](ghostnet-ai/GNAI-002-INFRA_BaseSystem.md) | GNAI-002 Hardware und Debian-Basisinstallation des KI-Servers `10.0.0.90` |
| [Ghostnet.AI-Zugänge](ghostnet-ai/GNAI-003-INFRA_Users.md) | GNAI-003 Hostbenutzer, Netzwerk, Resolver und SSH des KI-Servers |
| [LLM-Server](llm-server/README.md) | physischer Server `10.0.0.90`, Hostbasis, AI-Serving und Encoding |
| [Proxmox-Server](proxmox-server/README.md) | Proxmox-Kontext und LXC-Dienste einschließlich DNS, Reverse Proxy und WireGuard |
| [GNAI-005](GNAI-005-SEC_Access-Control.md) | Rollen- und Zugriffsziel für die Infrastruktur |
| [Proxmox-LXCs](proxmox-lxc/README.md) | gemeinsamer Einstieg für CTs auf Proxmox `10.0.0.2` |
| [GNAI-011](proxmox-lxc/GNAI-011-SEC_LXC-SSH-Architecture.md) | gemeinsame SSH-Sicherheitsarchitektur der LXCs |
| [CT 103 / GNAI-006](proxmox-lxc/ct-103/GNAI-006-SEC_Reverse-Proxy.md) | Reverse Proxy, Keycloak-Adminpfad und Fail2Ban |
| [CT 105 / GNAI-007](proxmox-lxc/ct-105/GNAI-007-SEC_VPN.md) | WireGuard-Gateway und Rollen-Firewall |
Der übergreifende Projektstand steht im
[zentralen Dokumentationsindex](../GNAI-000-DOC_Index.md).
+17
View File
@@ -0,0 +1,17 @@
# LLM-Server `ghostnet-ki`
Dieser Gerätebereich dokumentiert den physischen Server `10.0.0.90`. Er
gruppiert die gemeinsame Hostbasis, den eigentlichen LLM-Stack und den davon
fachlich getrennten CPU-Encoding-Workload. Alle drei Bereiche teilen Hardware,
Betriebssystem und Ansible-Control-Projekt.
| Bereich | Inhalt |
| --- | --- |
| [Host](host/GNAI-002-INFRA_BaseSystem.md) | Hardware, Debian-Basis und GPU-Ausgangslage |
| [Zugänge](host/GNAI-003-INFRA_Users.md) | Benutzer, Netzwerk, Resolver und SSH |
| [AI](ai/README.md) | Architektur, Ansible-Deployment, Modellbewertung und `llama.cpp`-Serving |
| [Encoding](encoding/GNENC-000-DOC_Index.md) | CPU-Encoding-Architektur und offener Implementierungsstand |
Übergreifende Rollen stehen im
[Zugriffskonzept](../GNAI-005-SEC_Access-Control.md), der Gesamtstand im
[zentralen Dokumentationsindex](../../GNAI-000-DOC_Index.md).
@@ -0,0 +1,370 @@
# Technical Architecture Specification
**Dokumenten-ID:** GNAI-004-INFRA
**Titel:** Homelab / LLM-Infrastruktur – Zielarchitektur
**Status:** Zielarchitektur; bestätigter Teilstand und offene Umsetzung separat ausgewiesen
**Vorgänger / Referenzen:** [Basisinstallation](../host/GNAI-002-INFRA_BaseSystem.md), [Benutzer/SSH](../host/GNAI-003-INFRA_Users.md), [Zugriffskonzept](../../GNAI-005-SEC_Access-Control.md), [Reverse Proxy](../../proxmox-server/lxc/ct-103-reverse-proxy/GNAI-006-SEC_Reverse-Proxy.md), [WireGuard](../../proxmox-server/lxc/ct-105-wireguard/GNAI-007-SEC_VPN.md)
---
## Änderungsverlauf
| Datum | Version | Autor | Beschreibung |
| --- | --- | --- | --- |
| 2026-08-28 | 1.0 | spektr | Erstellung der Zielarchitektur |
| 2026-09-15 | 1.1 | spektr| Adressplan, Rollenmodell, OIDC-Ist und offene Sicherheitsbefunde ergänzt. |
| 2026-09-16 | 1.2 | spektr | Bestätigten Fail2Ban-Pin und noch offene Funktionsabnahme ergänzt. |
| 2026-09-18 | 1.3 | spektr | Tatsächlichen KI-Server-Hostnamen übernommen; frischen Basisstand und späteren Ansible-Sprint vom Zielbild getrennt. |
| 2026-09-21 | 1.4 | spektr / Codex | belegten Plattformstand aktualisiert und LiteLLM als zu bestätigenden Kandidaten einer allgemeinen API-Policy-Schicht eingeordnet |
---
## 1. Zweck und Geltungsbereich
Dieses Dokument beschreibt die festgelegte Zielarchitektur für die sichere Bereitstellung des LLM-Servers und der öffentlich erreichbaren Webservices innerhalb der Ghostnet-Infrastruktur.
Es handelt sich ausdrücklich um eine **technische Spezifikation und kein Installationshandbuch**. Konkrete Implementierungsanweisungen, Konfigurationsdateien und Abnahmetests werden in nachgelagerten SOPs dokumentiert.
Die Architektur baut auf dem in **GNAI-002-INFRA** beschriebenen Debian-basierten LLM-Server und der in **GNAI-003-INFRA** beschriebenen SSH-/Benutzer-Härtung auf.
Der KI-Server begann am 18.09.2026 als frisches Basissystem. Inzwischen sind
Deployment-Zugang, Host-Baseline, Podman, getrennte Workload-Konten und der
rootless ROCm-Gerätenachweis mit Ansible umgesetzt. Dauerhaftes
`llama.cpp`-Serving und weitere Anwendungen sind noch nicht installiert.
LiteLLM, OpenWebUI und GPT Researcher sind **Zielbild-Kandidaten**, aber ohne
Bestätigung in der priorisierten Roadmap keine Zusage für die nächste
Ausbaustufe.
---
## 2. Architekturprinzipien
Die Infrastruktur folgt diesen Grundsätzen:
1. **Minimale Internet-Exposure:** Aus dem Internet werden ausschließlich die tatsächlich benötigten öffentlichen Dienste freigegeben.
2. **Dedizierter Reverse Proxy:** Sämtliche öffentlichen HTTP(S)-Dienste werden über einen zentralen Nginx Proxy Manager (NPM) veröffentlicht.
3. **Managementnetz getrennt von Applikationszugriff:** Privilegierter Netzwerkzugriff erfolgt über WireGuard.
4. **Anwendungsidentität über Keycloak:** Webanwendungen verwenden OIDC zur zentralen Authentifizierung.
5. **LLM-API als dedizierte Applikationsschnittstelle:** Direkter Zugriff auf
`llama.cpp` aus dem Internet ist nicht vorgesehen. Eine öffentliche
Schnittstelle benötigt eine getrennte Policy-Schicht; LiteLLM ist dafür der
bisherige Kandidat, aber noch nicht als erste Ausbaustufe bestätigt.
6. **Defense in Depth:** Reverse Proxy, Fail2Ban, Firewall-Regeln, MFA, Patchmanagement, Monitoring und Backups ergänzen sich.
7. **Least Privilege:** Server Admins und LLM Admins erhalten passende Serverrechte; Familie ist ausdrücklich für das gesamte Heimnetz freigegeben. Netzwerkrechte werden pro Peer serverseitig durchgesetzt.
8. **Isolation:** Öffentlich exponierte Infrastruktur und einzelne Backend-Dienste bleiben logisch bzw. durch eigene LXCs getrennt.
---
## 3. Netzwerk- und Hoststruktur
### 3.1 Bestehende Proxmox-Struktur
Der Proxmox-Server bildet die Virtualisierungs- und Isolationsschicht.
| Komponente | Adresse | Rolle |
| --- | --- | --- |
| Proxmox VE | `10.0.0.2` | Virtualisierungs-/Managementhost |
| DNS LXC | `10.0.0.14` | Pi-hole, lokaler Resolver und Split-DNS |
| Reverse Proxy LXC | `10.0.0.10` | einziger öffentlicher HTTP(S)-Entry-Point |
| WireGuard LXC | `10.0.0.15` | VPN-/Managementzugang |
| LLM Server | `10.0.0.90`, Hostname `ghostnet-ki` | KI-Inferenz und LLM-Dienste |
Die Service-LXCs (z. B. Nextcloud, Gitea, Vaultwarden, Keycloak etc.) verbleiben jeweils in eigenen LXCs und werden nicht in den Reverse-Proxy-LXC zusammengelegt.
### Adressplanung und aktuelles Serviceinventar
Heimnetz `10.0.0.0/24`, Fritzbox/Gateway `.1`, lokaler DNS `.14`. Statische
Server-/LXC-Adressen sind bis `.99` vorgesehen; DHCP vergibt `.100–.200` an
Clients und verteilt Pi-hole als IPv4-DNS. Die `.90` ist dem physischen
KI-Server vorbehalten. Das ist eine Belegungsordnung innerhalb desselben
Netzes, keine VLAN-/Firewall-Isolation.
| Host / Service | Quellenstand | Status der Angabe |
| --- | --- | --- |
| Proxmox | `10.0.0.2` | Betreiber bestätigt |
| Keycloak | `10.0.0.4:8080` | Betreiber bestätigt NPM-Backend |
| Pi-hole | `10.0.0.14:53` | am 01.10.2026 in CT 100 abgenommen |
| NPM / wg-easy | `10.0.0.10` / `10.0.0.15` | Betreiber bestätigt |
| Jellyfin | `10.0.0.11:8096` | GNAI-006-Inventar, vor Freigabe abgleichen |
| Nextcloud | `10.0.0.12:8080` | GNAI-006-Inventar, vor Freigabe abgleichen |
| Gitea | `10.0.0.17:3000` | GNAI-006-Inventar, vor Freigabe abgleichen |
| Japanese Web App | `10.0.0.23:80` | GNAI-006-Inventar, vor Freigabe abgleichen |
| Vaultwarden | `10.0.0.47:8080` | GNAI-006-Inventar; OIDC-Nutzung vom Betreiber bestätigt |
| KI-Server | `10.0.0.90` | Betreiber bestätigt |
Neue LXCs im Inventar ergänzen. Der ursprüngliche Bereich `.4–.40` war geschätzt und ist keine gültige Grenze. Lokales Gästenetz, mögliche separate Servernetze, Routing und VLAN-Unterstützung gehören in einen späteren Netzwerk-Sprint; es gibt hierfür noch keine beschlossenen Netzadressen. Automatische Broadcast-/Multicast-Geräteerkennung über WireGuard wird nicht vorausgesetzt.
### 3.2 Perimeter
Die Fritzbox 7530 bildet den Internet-Perimeter.
Aus dem Internet sollen grundsätzlich nur die für den öffentlichen Betrieb erforderlichen Weiterleitungen existieren:
- TCP 80 → Reverse Proxy LXC `10.0.0.10`
- TCP 443 → Reverse Proxy LXC `10.0.0.10`
- UDP 51820 → WireGuard LXC `10.0.0.15`
Managementdienste wie Proxmox Web UI oder SSH werden **nicht direkt aus dem Internet veröffentlicht**.
IPv4- und IPv6-Exposure sind gleichwertig zu betrachten und müssen bei der Implementierung separat verifiziert werden.
---
## 4. Reverse-Proxy-LXC
Der Reverse-Proxy-LXC (`10.0.0.10`) ist eine dedizierte Infrastrukturzone für öffentlich exponierte HTTP(S)-Komponenten.
### 4.1 Containerisierung
Innerhalb des LXCs wird Podman verwendet.
Vorgesehene Container:
- **Nginx Proxy Manager**
- **Fail2Ban**
NPM und Fail2Ban befinden sich absichtlich im selben LXC. Dadurch kann Fail2Ban die Logdaten von NPM auswerten und – unter minimal erforderlichen Capabilities – die Netzwerk-/Firewallebene des eigenen LXCs absichern, ohne Zugriff auf die Firewall eines anderen LXCs zu benötigen.
### 4.2 Verantwortlichkeiten
**NPM**
- TLS-Terminierung
- Reverse Proxy
- Routing zu internen Services
- HTTP(S)-Security-Header und weitere Proxy-Policies
- optionales Rate Limiting, soweit sinnvoll
**Fail2Ban**
- Erkennung wiederholter bzw. eindeutiger Missbrauchsmuster
- temporäre Sperrung auffälliger Quelladressen
- Auswertung gezielter NPM-/Applikationslogs
Fail2Ban ist eine **zusätzliche Abuse-Protection-Schicht**, kein Ersatz für Patchmanagement, sichere Authentifizierung oder Netzwerksegmentierung.
---
## 5. Öffentliche Webservices
Alle öffentlich vorgesehenen Webservices werden hinter NPM veröffentlicht.
Beispiele:
### Services auf separaten Proxmox-LXCs
- Nextcloud
- Gitea
- Vaultwarden
- Keycloak
### Mögliche Services auf dem LLM-Server
- OpenWebUI
- GPT Researcher
- LiteLLM API
Diese Liste beschreibt Architektur-Kandidaten, keine gemeinsam freigegebene
Implementierungsreihenfolge. Die aktive Reihenfolge steht in GNAI-001.
Direkter Internetzugriff auf die internen Backend-Ports der Services ist nicht vorgesehen.
---
## 6. LLM-Server-Architektur
Der LLM-Server basiert auf der in **GNAI-002-INFRA** dokumentierten Debian-Basis mit AMD-Multi-GPU-Konfiguration und der in **GNAI-003-INFRA** vorgesehenen SSH-Key-basierten Absicherung.
### 6.1 LLM-Inferenz
`llama.cpp` soll die eigentliche lokale Modell-Inferenz bereitstellen.
Grundsatz:
> `llama.cpp` wird nicht direkt aus dem Internet exponiert.
Der Zugriff erfolgt intern über die dafür vorgesehene API-Schicht.
### 6.2 API- und Policy-Schicht
Für eine externe API ist eine standardisierte API- und Policy-Schicht vor
`llama.cpp` vorgesehen. LiteLLM ist der bisherige Kandidat. Seine Einführung
wird vor der Veröffentlichung gegen die tatsächlich benötigten individuellen
Keys, Quoten, Routing- und Beobachtbarkeitsfunktionen geprüft.
Verantwortlichkeiten:
- öffentliche API-Schnittstelle
- API-Key-Authentifizierung
- Routing zu laufenden Modellen
- ggf. Modell-/Benutzer-Policies
- Rate-/Quota-Steuerung
- Trennung der öffentlichen API von der eigentlichen Inferenz-Engine
Zielpfad:
Im Zielbild verbindet sich ein externer Client per HTTPS mit NPM. NPM leitet
an die beschlossene Policy-Schicht weiter; diese nutzt `llama.cpp` über das
interne Container-/Hostnetz. Die Inferenz läuft auf den GPUs. Dieser Pfad ist
noch nicht umgesetzt.
---
## 7. WireGuard-Managementnetz
WireGuard ist die dedizierte Zugangsschicht für privilegierte Netzwerkzugriffe.
### 7.1 Implementierung
- eigener WireGuard-LXC auf Proxmox
- `wg-easy` als Podman-Container
- WireGuard-Peer-Authentifizierung ausschließlich über kryptografische Schlüssel
- kein OIDC für die eigentliche WireGuard-Peer-Authentifizierung
### 7.2 Rollen und VPN-Betrieb
| Rolle | Zielrechte |
| --- | --- |
| Familie (Brüder) | gesamtes Heimnetz einschließlich PCs/Clients; Serververwaltung nach Dienstanmeldung |
| Server Admin | Proxmox, Service-LXCs und KI-Server; NPM und wg-easy eingeschlossen |
| LLM Admin | ausschließlich KI-Server `10.0.0.90` |
Profile werden pro Gerät vom Administrator erstellt und verteilt. Benutzer benötigen für den Profilbezug keine GUI-Anmeldung. Serverseitige **Firewall Allowed IPs** in wg-easy sollen die Zielrechte durchsetzen; clientseitige **Allowed IPs** steuern Split-Tunneling. Per-Client Firewall ist aktuell deaktiviert, Rollenisolierung nicht abgenommen. Gateway-GUI/SSH benötigen zusätzlich passende INPUT-Regeln. Die alte Full-Admin-Bezeichnung wird nicht automatisch einer der neuen Rollen gleichgesetzt.
Das Gateway nutzt MASQUERADE für VPN→LAN; die Fritzbox besitzt keine Rückroute zum VPN-Netz. LAN-Dienste sehen dadurch die Quelladresse des Gateways. Die serverseitige Peer-Zielkontrolle muss vor NAT greifen. Installation, Hooks und Abnahme stehen in GNAI-007.
---
## 8. Keycloak / OIDC
Keycloak ist die zentrale Identitäts- und Authentifizierungsplattform für die Webanwendungen.
Vorgesehenes Modell:
Der Browser erreicht den Webdienst über HTTPS und NPM. Der Webdienst bindet Keycloak für den OIDC-Anmeldeprozess ein; Anwendung und Identitätsquelle behalten getrennte Aufgaben.
Webservices sollen OIDC nutzen, soweit die jeweilige Anwendung daran angebunden wird. Laut Betreiber ist derzeit nur Vaultwarden produktiv an Keycloak/OIDC angebunden.
Die Keycloak-Administrationsoberfläche und administrative Managementschnittstellen sollen nicht öffentlich zugänglich sein und werden über das Managementnetz bereitgestellt.
Öffentlich erforderlich bleiben ausschließlich die für OIDC tatsächlich benötigten Endpunkte des jeweiligen Realms.
---
## 9. SSH
SSH auf dem LLM-Server wird ausschließlich schlüsselbasiert betrieben.
Aus **GNAI-003-INFRA** übernimmt die Zielkonfiguration insbesondere:
- `PermitRootLogin no`
- `PasswordAuthentication no`
- `KbdInteractiveAuthentication no`
- `PubkeyAuthentication yes`
Der LLM-Server-SSH-Port wird nicht aus dem Internet veröffentlicht.
Zulässige Herkunft:
- lokales vertrauenswürdiges Netzwerk
- WireGuard-Managementnetz
---
## 10. Sicherheitszonen und Vertrauensmodell
Die Infrastruktur unterscheidet mindestens drei Vertrauensklassen:
### Public Application Access
Zugang über:
HTTPS über NPM.
Authentifizierung erfolgt je nach Anwendung über:
- OIDC / Keycloak
- API-Key bei der LLM-API
### Management Access
Zugang über:
WireGuard zum freigegebenen Zielsystem.
Zusätzliche Dienstauthentifizierung bleibt erhalten, z. B. SSH Public Keys oder Proxmox-Authentifizierung.
### Backend Access
Nicht öffentlich.
Beispiele:
- `llama.cpp`
- interne Datenbankports
- interne Container-/Serviceports
- sonstige administrative Backend-Schnittstellen
---
## 11. Geplante Defense-in-Depth-Schichten
Die Zielarchitektur sieht folgende komplementäre Schutzschichten vor:
1. Fritzbox als Perimeter
2. keine unnötigen Portweiterleitungen
3. Proxmox-/LXC-Netzwerk- und Firewallregeln
4. dedizierter Reverse Proxy
5. Fail2Ban für erkannte Missbrauchsmuster
6. TLS/HTTPS
7. zentrale Authentifizierung mit Keycloak/OIDC
8. MFA/WebAuthn für privilegierte Benutzer, wo unterstützt
9. API-Keys bzw. OAuth-/Anwendungscredentials für Maschinenzugriffe
10. SSH ausschließlich über Schlüssel
11. regelmäßige Updates / Patchmanagement
12. Backup- und Restore-Strategie
13. externes Monitoring und regelmäßige Prüfung der öffentlichen Exposure
---
## 12. Nicht Teil dieser Spezifikation
Folgende konkrete Werte und Verfahren werden in den Fachdokumenten geführt. Einige sind bereits festgelegt, andere dort ausdrücklich als offen markiert:
- konkrete WireGuard-Subnetze und Peer-Adressen
- konkrete Firewall-Regelsätze
- genaue `wg-easy`-Version und Containerparameter
- genaue NPM-Container-/Storage-Konfiguration
- konkrete Fail2Ban-Jails und Filter
- konkrete Modell-, Credential- und Quota-Policies der späteren API-Schicht
- konkrete Keycloak-Realm-/Client-Struktur
- konkrete DNS-/Domainnamen
- konkrete Backup- und Monitoring-Implementierung
---
## 13. Architektur-Abnahmekriterien
Die Architektur gilt als korrekt umgesetzt, wenn mindestens folgende Eigenschaften nachweisbar sind:
- Nur vorgesehene öffentliche Ports sind aus dem Internet erreichbar.
- Proxmox ist nicht direkt aus dem Internet erreichbar.
- SSH des LLM-Servers ist nicht direkt aus dem Internet erreichbar.
- `llama.cpp` ist nicht direkt aus dem Internet erreichbar.
- Öffentliche Webservices terminieren an NPM.
- Fail2Ban befindet sich zusammen mit NPM im Reverse-Proxy-LXC.
- Familie-, Server-Admin- und LLM-Admin-Peers besitzen die beschlossenen, serverseitig durchgesetzten Zugriffsprofile.
- Webanwendungen verwenden Keycloak/OIDC, sofern dies für den jeweiligen
Nutzerpfad beschlossen und von der Anwendung belastbar unterstützt wird;
Maschinen-APIs behalten eigene Credentials und Policies.
- Die LLM-API wird über eine ausdrücklich beschlossene Policy-Schicht
bereitgestellt und über individuelle Credentials geschützt; bei Auswahl von
LiteLLM werden dessen Key-, Quota- und Routingfunktionen gesondert abgenommen.
- Administrative Weboberflächen von Keycloak sind nicht öffentlich.
- Service-LXCs bleiben voneinander isoliert und erlauben nur erforderliche Kommunikationsbeziehungen.
---
## 14. Umsetzungsstand
Die Architektur-Abnahmekriterien in Abschnitt 13 sind Sollkriterien und keine bestätigte Abnahme. Der aktuelle Projektstand und die priorisierten Befunde stehen im [Dokumentationsindex](../../../GNAI-000-DOC_Index.md); technische Nachweise und offene Prüfungen werden in GNAI-006 und GNAI-007 geführt. Backendisolation und lokale Netzwerksegmentierung sind ebenfalls noch nachzuweisen.
@@ -0,0 +1,852 @@
# Arbeitsentwurf
**Dokumenten-ID:** GNAI-008-PLAN
**Titel:** Ansible-Deployment für den KI-Server – Planungsstand
**Stand:** 2026-09-21
**Status:** Nicht finaler Arbeitsentwurf zur Persistierung des aktuellen Planungsstands
**Referenzen:** [Projektstand](../../../GNAI-000-DOC_Index.md), [Zielarchitektur](GNAI-004-INFRA_Architecture.md), [Modellbewertung](GNAI-009-PLAN_Llama-Model-Evaluation.md), [llama.cpp-Sollkonfiguration](GNAI-010-TECH_Llama-Serving.md), [Zugriffskonzept](../../GNAI-005-SEC_Access-Control.md)
> **Abgrenzung:** Dieses Dokument hält die Ergebnisse der am 19.09.2026
> begonnenen und bis zum 21.09.2026 fortgeschriebenen Planungssession fest. Es ist
> weder eine freigegebene technische Spezifikation noch
> eine Installationsanleitung oder ein Nachweis über bereits umgesetzte
> Komponenten. Entscheidungen, die noch geprüft oder bestätigt werden müssen,
> sind ausdrücklich als vorläufig oder offen gekennzeichnet.
---
## Änderungsverlauf
| Datum | Version | Autor | Beschreibung |
| --- | --- | --- | --- |
| 2026-09-19 | 0.1 | spektr / Codex | Planungsstand zu Ansible, Gitea Actions, Runner, Freigaben und künftiger Dokumentation aus der Session festgehalten |
| 2026-09-19 | 0.2 | spektr / Codex | vorläufige Ein-Repository-Strategie sowie logisch getrennte Production- und Testing-Umgebungen auf dem KI-Server ergänzt |
| 2026-09-19 | 0.3 | spektr / Codex | exklusiven zeitlich gestaffelten Betrieb von Production und Testing ohne physische Ressourcenaufteilung als Planungsentscheidung übernommen |
| 2026-09-19 | 0.4 | spektr / Codex | erstes Ansible-Control-Projekt und erfolgreich ausgeführten lesenden Live-Preflight als belegten Implementierungsstand ergänzt |
| 2026-09-19 | 0.5 | spektr / Codex | technischen Schlüssel sowie Bootstrap- und Prüfplaybook für `ansible-deploy` vorbereitet; Ausführung bleibt interaktiv offen |
| 2026-09-19 | 0.6 | spektr / Codex | `ansible-deploy` auf dem KI-Server eingerichtet und SSH-/sudo-Zugriff unabhängig erfolgreich verifiziert |
| 2026-09-19 | 0.7 | spektr / Codex | minimale gemeinsame Host-Baseline mit Schutzprüfungen und getrennten Verzeichniswurzeln implementiert, angewendet und auf Idempotenz geprüft |
| 2026-09-20 | 0.8 | spektr / Codex | gemeinsame Podman-/Quadlet-Runtime implementiert, installiert und ohne Images oder Container verifiziert |
| 2026-09-20 | 0.9 | spektr / Codex | getrennte rootless Konten für AI Production, AI Testing und CPU-Encoding eingerichtet; Parallelitäts- und Repository-Grenzen ergänzt |
| 2026-09-20 | 0.10 | spektr / Codex | ROCm als Backend für zwei RX 6900 XT, initialen `llama.cpp`-Modellstand und Layer-Split mit gleicher GPU-Gewichtung festgehalten |
| 2026-09-20 | 0.11 | spektr / Codex | AMD-Gerätezugriff für beide rootless KI-Konten implementiert, angewendet und idempotent geprüft |
| 2026-09-20 | 0.12 | spektr / Codex | gepinnten rootless ROCm-Probelauf für Testing implementiert; zwei `gfx1030`-Agents und zwei `llama.cpp`-ROCm-Geräte nachgewiesen |
| 2026-09-21 | 0.13 | spektr / Codex | Swift-Qwen3.8 Q6_K mit 128k und `xhigh` als Testing-Kandidat übernommen; Modellbewertung und technische Sollkonfiguration ausgelagert |
| 2026-09-21 | 0.14 | spektr / Codex | Unsloth UD-Q6_K als zweites installiertes 128k-Profil und A/B-Kandidat festgelegt |
| 2026-09-21 | 0.15 | spektr / Codex | je Modell ein festes, gegenseitig ausgeschlossenes Quadlet vorgesehen und dynamischen llama.cpp-Router zurückgestellt |
| 2026-09-21 | 0.16 | spektr / Codex | überholten Phasen- und Dokumentenbacklog konsolidiert, Umsetzungsreihenfolge an GNAI-001 delegiert und privilegierten Runner nach Recovery verschoben |
---
## 1. Zweck
Der KI-Server `ghostnet-ki` soll reproduzierbar mit Ansible aufgebaut und
aktualisiert werden. Mehrere berechtigte Personen sollen Änderungen gemeinsam
über Gitea entwickeln, prüfen, freigeben und nachvollziehbar ausrollen können.
Gesucht wird ein Verfahren, das:
- den vollständigen Sollzustand des Servers versioniert,
- wiederholbare und möglichst idempotente Deployments ermöglicht,
- Änderungen vor der Anwendung prüft,
- einen nachvollziehbaren Freigabeweg für mehrere Mitwirkende bietet,
- Geheimnisse und privilegierte Zugänge vom Quelltext trennt,
- Fehler und Rollbacks betrieblich beherrschbar macht und
- für einen einzelnen KI-Server ohne unnötig große Managementplattform
betrieben werden kann.
Die Planung umfasst sowohl die spätere technische Implementierung als auch die
Dokumentation für Administratoren, Entwickler und Deployment-Berechtigte.
---
## 2. Ausgangslage
Nach dem dokumentierten Stand besitzt der KI-Server die gemeinsame
Ansible-/Podman-/ROCm-Grundlage unter `10.0.0.90`. Dauerhaftes `llama.cpp`-
Serving und die Zielbild-Kandidaten LiteLLM, OpenWebUI und weitere Anwendungen
sind noch nicht installiert. Gitea ist im bestehenden
Serviceinventar unter `10.0.0.17:3000` geführt; Version, Actions-Konfiguration
und Registry-Funktionen müssen vor der Implementierung live geprüft werden.
Der bestehende Account `ghost-agent` ist für lesende Prüfungen vorgesehen und
besitzt nach dem letzten Abgleich keine `sudo`-Berechtigung. Er soll nach dem
aktuellen Plan nicht zum privilegierten Deployment-Account erweitert werden.
Das Repository enthält inzwischen ein minimales Ansible-Control-Projekt mit
Testing-Inventar, einem ausschließlich lesenden Preflight sowie Bootstrap- und
Prüfplaybooks für `ansible-deploy`. Der privilegierte Deployment-Account ist
eingerichtet und verifiziert. Schreibende Rollen verwalten die gemeinsamen
Konfigurations- und Datenwurzeln sowie Podman und Quadlet. Anwendungsrollen und
ein Production-Inventar existieren noch nicht. Der Preflight selbst nahm keine
Änderungen vor; die bislang angewendeten Rollen bleiben auf die nachfolgend
dokumentierten Zugangs-, Verzeichnis- und Runtime-Zustände begrenzt.
Ein fachlich vom LLM-Stack getrennter CPU-Encoding-Workload wird auf demselben
Host vorgesehen. Sein Ansible-Sollzustand bleibt wegen des gemeinsamen Hosts im
Infrastructure-Repository; Architektur und spätere Nutzer-SOPs werden unter
[`docs/platform/llm-server/encoding/`](../encoding/GNENC-000-DOC_Index.md) getrennt geführt.
---
## 3. Vorläufiges Zielbild
Ansible bleibt die einzige technische Quelle für den gewünschten Zustand des
KI-Servers. Gitea verwaltet Quelltext, Reviews, Freigaben und Jobprotokolle.
Getrennte Runner- beziehungsweise Berechtigungsstufen führen CI-Prüfungen,
Testing-Deployments und Production-Deployments aus. Die genaue Anzahl der
Runner wird nach Prüfung der Gitea-Berechtigungsgrenzen festgelegt.
```mermaid
flowchart LR
DEV[Feature-Branch] --> PR[Pull Request]
PR --> CI[statische CI ohne Serverzugang]
CI --> TESTOK[freigegebener Testing-Lauf]
TESTOK --> TESTRUNNER[Testing-Berechtigung]
TESTRUNNER --> TEST[Testing auf ghostnet-ki]
TEST --> REVIEW[Review und Merge nach main]
REVIEW --> RELEASE[bewusste Production-Freigabe]
RELEASE --> PRODRUNNER[Production-Berechtigung]
PRODRUNNER --> PROD[Production auf ghostnet-ki]
PROD --> VERIFY[Health- und Funktionstests]
UPSTREAM[versionierte Upstream-Artefakte] --> UPDATE[Versions-Pin im Infrastruktur-Repository]
UPDATE --> PR
```
Runner sollen keine eingehenden Verbindungen aus dem Internet benötigen. Sie
holen Jobs bei Gitea ab und benötigen ausgehend nur die für Gitea, Paket- oder
Imagequellen und den jeweiligen SSH-Zugriff zum KI-Server erforderlichen
Verbindungen. CI-Jobs erhalten keinen SSH-Zugriff zum KI-Server.
---
## 4. Ergebnisse der bisherigen Planung
Die folgende Tabelle unterscheidet bewusst zwischen einer gewählten
Planungsrichtung und noch nicht bestätigten Implementierungsdetails.
| Thema | Aktueller Planungsstand | Verbindlichkeit / Nachweis |
| --- | --- | --- |
| Konfigurationsmanagement | Ansible beschreibt Basissystem, Laufzeitumgebung, Dienste und Konfiguration | Control-Projekt, Preflight, `host_baseline`, `podman_runtime`, `workload_accounts`, `amd_gpu_access` und der ROCm-Container-Probelauf vorhanden und angewendet; Anwendungsrollen fehlen noch |
| Quell- und Freigabeverfahren | Änderungen über Gitea-Branches und Pull Requests; kein regulärer Direkt-Push auf `main` | vorgesehen; Branch-Schutz noch nicht geprüft oder eingerichtet |
| CI | Pull Requests erhalten Lint-, YAML- und Ansible-Syntaxprüfungen ohne produktive Zugangsdaten | vorgesehen; Workflow noch nicht vorhanden |
| Produktionsausführung | Gitea Actions startet Ansible über eine dedizierte Production-Berechtigungsstufe | bevorzugte Lösung; Gitea-Version, Runner-Trennung und Actions-Funktion noch zu prüfen |
| Runner-Platzierung | eigener kleiner LXC, getrennt von Gitea und KI-Server | bevorzugt; Host, Adresse und Ressourcen noch offen |
| Job-Isolation | containerisierte Jobs; für den Gitea Runner ist nach aktueller Herstellerdokumentation Docker der unterstützte Standard | bevorzugt; lokale Freigabe und LXC-Eignung noch offen |
| Parallelität | Produktionsrunner führt höchstens einen Job gleichzeitig aus | vorgesehen; Runner-Konfiguration noch nicht vorhanden |
| Deployment-Freigabe | nach erfolgreichem Review bewusste Freigabe über geschützten `deploy-*`-Tag | bevorzugte Startvariante; Namensschema und berechtigte Teams noch offen |
| Zielzugang | eigener technischer SSH-Account `ansible-deploy` mit separatem, quellnetzbeschränktem Schlüssel und validiertem `NOPASSWD: ALL` | auf dem KI-Server eingerichtet und unabhängig verifiziert; spätere Runner-Übernahme und Rotation offen |
| Geheimnisse | verschlüsselte Ansible-Variablen plus getrennt gespeichertes Vault-Passwort und Runner-Zugangsdaten | Richtung angenommen; konkreter Secret-Workflow noch offen |
| Artefaktversionen | Pakete, Collections und Container-Images werden auf konkrete Versionen, nach Möglichkeit Digests, festgelegt | vorgesehen |
| Repository-Zuschnitt | `infrastructure` bleibt alleinige Quelle für alle Ansible-Zustände des Hosts; KI- und Encoding-Dokumentation werden getrennt geführt; weitere Quellcode-Repositories erst bei eigenständig entwickelten Anwendungen | als aktuelle Planungsentscheidung angenommen |
| Anwendungsupdates | Upstream-Artefakte werden im Infrastruktur-Repository auf eine geprüfte Version festgelegt; eigene Build-Repositories sind vorerst nicht Bestandteil des Projekts | vorgesehen; Registry- und Buildverfahren bei späterem Eigenentwicklungsbedarf neu bewerten |
| Automatische Updates | kein unbeaufsichtigtes Produktionsupdate über `latest` oder Watchtower | vorläufige Sicherheitsentscheidung |
| Laufzeitumgebungen | Production und Testing werden als getrennte Stacks auf demselben physischen KI-Server betrieben | als aktuelle Planungsentscheidung angenommen |
| Production | stabiler, extern nutzbarer Stand mit bewusst freigegebenen Versionen | Zielbild; Dienste noch nicht eingerichtet |
| Testing | interner, kurzlebiger oder zurücksetzbarer Stand für kontrollierte Tests vor dem Merge nach `main`; nutzt während eines Wartungsfensters beide GPUs exklusiv gegenüber Production | Zielbild; Dienste, Zugriffsweg und Umschaltverfahren noch nicht eingerichtet |
| Ressourcenbetrieb | AI Production und AI Testing laufen nicht gleichzeitig; der getrennte CPU-Encoding-Workload darf nachts parallel zur jeweils aktiven KI-Umgebung laufen | als aktuelle Planungsentscheidung angenommen; Grenzwerte und Lasttest offen |
| Workload-Konten | `gnai-production`, `gnai-testing` und `gn-encoding` verwenden getrennte rootless Podman-Stores und systemd-User-Manager | eingerichtet und mit `changed=0` verifiziert |
| Betriebsnachweis | erfolgreicher Lauf dokumentiert Git-Commit, Zielhost, Zeitpunkt, Prüfungen und Ergebnis | vorgesehen |
| Rollback | Konfigurationsänderung über Revert und erneute Freigabe; persistente Daten zusätzlich über getestete Backups wiederherstellen | Grundprinzip; konkrete SOP noch zu erstellen |
Eine spätere Umstellung auf ein automatisches Deployment nach jedem Merge in
`main` bleibt möglich. Zu Beginn soll die Freigabe jedoch bewusst und
nachvollziehbar erfolgen, während die technische Ausführung vollständig
automatisiert wird.
---
## 5. Verworfene oder zurückgestellte Varianten
### 5.1 Ausschließliche Ausführung vom Admin-PC
Lokale Ansible-Ausführung bleibt für Entwicklung, Diagnose und Notfälle
sinnvoll. Sie soll nicht der reguläre Produktionsweg werden, weil Ausführung,
Werkzeugversionen und Protokolle sonst von einem einzelnen Arbeitsplatz
abhängen.
### 5.2 `ansible-pull` auf dem KI-Server
Der Zielserver könnte das Repository periodisch selbst abrufen und lokal
anwenden. Für den einzelnen zentral erreichbaren Server bietet das derzeit
weniger klare Freigaben und eine ungünstigere Trennung zwischen Zielsystem und
Deployment-Steuerung. Die Variante bleibt zurückgestellt.
### 5.3 AWX
AWX bietet eine eigene Oberfläche, Rollen, Zeitpläne und Inventarverwaltung.
Der zusätzliche Plattformbetrieb erscheint für den aktuellen Umfang zu groß.
AWX kann erneut bewertet werden, wenn mehrere Zielsysteme, feinere delegierte
Rechte oder umfangreiche Jobpläne verwaltet werden müssen.
---
## 6. Repository-Verantwortung
Vorerst wird ausschließlich das bestehende Repository `infrastructure`
verwendet. Es soll den vollständigen Sollzustand des KI-Servers, beide
Laufzeitumgebungen und die zugehörigen Workflows besitzen. Damit verändern
nicht mehrere unabhängige Deployment-Repositories denselben Host.
Vorgesehene grobe Struktur:
```text
ansible/
├── ansible.cfg
├── collections/
│ └── requirements.yml
├── inventories/
│ ├── production/
│ └── testing/
├── playbooks/
│ ├── host.yml
│ ├── environment.yml
│ └── verify.yml
└── roles/
├── host_baseline/
├── environment_stack/
└── weitere_fachrollen/
.gitea/workflows/
├── validate.yml
├── deploy-testing.yml
└── deploy-production.yml
```
Diese Struktur ist ein Ausgangspunkt und noch keine Festlegung aller Rollen.
`host.yml` und `environment.yml` sollen gemeinsam den unterstützten
Gesamtzustand herstellen. Ansible-Tags können Diagnose und gezielte Wartung
unterstützen, dürfen aber nicht zu ungetesteten alternativen Installationswegen
führen.
Unveränderte Drittanbieterkomponenten wie `llama.cpp`, LiteLLM oder OpenWebUI
erhalten derzeit kein eigenes Repository. Ihre freigegebenen Versionen oder
Image-Digests werden im Infrastruktur-Repository geführt. Ein weiteres
Repository wird erst angelegt, wenn tatsächlich eigener, unabhängig
versionierter Quellcode für eine Anwendung, einen MCP Router oder MCP Server
entwickelt wird. Diese spätere Erweiterung ändert nichts daran, dass das
Infrastruktur-Repository allein festlegt, welche Artefaktversion in welcher
Umgebung läuft.
---
## 7. Production und Testing auf einem Host
### 7.1 Geltungsbereich der Trennung
Production und Testing laufen zunächst auf demselben physischen KI-Server.
Damit können Anwendungscontainer, Konfigurationen und Daten sauber logisch
getrennt werden. Die Hardware kann wegen des hohen Leistungsbedarfs nicht
dauerhaft zwischen beiden KI-Umgebungen aufgeteilt werden. Stattdessen erhält
die jeweils aktive KI-Umgebung beide GPUs exklusiv gegenüber der anderen
KI-Umgebung. Ein fachlich getrennter CPU-Encoding-Workload darf außerhalb der
Stoßzeiten parallel laufen. Es entstehen dennoch keine getrennten
Fehlerdomänen:
- Kernel, AMD-Treiber beziehungsweise ROCm, Podman und systemd werden gemeinsam
verwendet;
- ein Hostausfall betrifft beide Umgebungen;
- ein fehlerhafter Testing-Stand kann den gemeinsamen Host beeinträchtigen und
dadurch den Wiederanlauf von Production verzögern;
- Kernel-, Treiber- und Host-Runtime-Updates können nicht realitätsnah in
Testing geprüft werden, ohne den gemeinsamen Host zu verändern.
Testing ist deshalb für neue Anwendungsstände, Konfigurationen, MCP-Komponenten
und Integrationen vorgesehen. Für risikoarme Vorabtests von Kernel-, Treiber-
oder Podman-Upgrades wäre später ein zweiter Host oder eine ausreichend
vergleichbare separate Maschine erforderlich.
Die von externen Nutzern erwartete Stabilität von Production gilt außerhalb
angekündigter Test- und Wartungsfenster. Solange nur dieser eine ausgelastete
Host verfügbar ist, verursachen realistische Tests mit voller Hardware eine
geplante Unterbrechung von Production.
### 7.2 Isolationsziele
| Bereich | Production | Testing |
| --- | --- | --- |
| Erreichbarkeit | für freigegebene Dienste über NPM; Backend- und Managementports bleiben intern | nur aus freigegebenem LAN/VPN; keine öffentliche NPM-Route |
| Container- und Unit-Namen | eigener eindeutiger Präfix | eigener eindeutiger Präfix |
| Podman-Netz | eigenes internes Netz | eigenes internes Netz ohne Verbindung zum Production-Netz |
| Konfiguration | eigener Verzeichnisbaum | eigener Verzeichnisbaum |
| Persistente Daten | eigene Volumes mit Backup- und Restore-Verfahren | eigene zurücksetzbare Volumes; keine Production-Datenkopie ohne Bereinigung und Freigabe |
| Secrets und API-Keys | ausschließlich Production-Credentials | eigene eingeschränkte Test-Credentials |
| Ports und Hostnamen | stabile freigegebene Endpunkte | getrennte interne Ports und noch festzulegende Testhostnamen |
| Ressourcen | erhält im Normalbetrieb beide GPUs und das festzulegende KI-Ressourcenbudget | erhält im Testfenster beide GPUs und das festzulegende KI-Ressourcenbudget |
| GPU | beide GPUs, solange Production aktiv ist | beide GPUs, nachdem Production vollständig beendet wurde |
| Ausführungszustand | standardmäßig aktiv | standardmäßig gestoppt |
| Verfügbarkeit | stabiler Betriebsstand außerhalb angekündigter Wartungsfenster | best effort; wird nach jedem Testlauf wieder vollständig gestoppt |
Eine gleichzeitige Inferenzlast beider KI-Umgebungen ist nicht vorgesehen. Vor dem
Start von Testing muss nachweisbar sein, dass Production beendet wurde und
keine zugehörigen Prozesse mehr CPU-, RAM- oder GPU-Ressourcen halten. Vor dem
Wiederanlauf von Production muss Testing entsprechend vollständig beendet und
sein temporärer Zustand bereinigt sein. Speicherplatz- und Prozessgrenzen
bleiben auch bei exklusiver GPU-Nutzung sinnvoll, damit ein Test nicht den
gemeinsamen Datenträger füllt oder den Host dauerhaft beeinträchtigt.
Der CPU-basierte Encoding-Workload ist nicht Teil dieser KI-Umschaltung. Er darf
nachts parallel zu Production oder Testing laufen, erhält keinen GPU-Zugriff
und muss über CPU-, Speicher- und I/O-Grenzen die Reaktionsfähigkeit der
KI-Dienste schützen. Die Betreiberfestlegung „CPU-basiert“ und die geplante
Parallelität sind noch durch einen repräsentativen gemeinsamen Lasttest zu
bestätigen. Zeitfenster und Grenzwerte werden ausschließlich im getrennten
[Encoding-Plan](../encoding/GNENC-001-PLAN_Architecture.md) spezifiziert.
Große schreibgeschützte Modelldateien können später aus einem gemeinsamen,
nur lesbar eingebundenen Modellspeicher bereitgestellt werden. Datenbanken,
Uploads, Caches mit Schreibzugriff, Benutzerkonten und Anwendungsschlüssel
bleiben vollständig getrennt. Ob ein gemeinsamer Modellspeicher technisch und
betrieblich sinnvoll ist, wird erst mit dem Modell- und Backupkonzept
entschieden.
### 7.3 Exklusives Umschaltverfahren
Ein Testing-Deployment ist zugleich ein geplantes Wartungsfenster für
Production. Der vorläufige Ablauf lautet:
1. konkreten PR-Commit und Testumfang festlegen;
2. Testing-Lauf durch eine berechtigte Person freigeben und Wartungsfenster
dokumentieren;
3. letzten funktionsfähigen Production-Stand und erforderlichen Backupstatus
feststellen;
4. neue externe Anfragen nach Möglichkeit kontrolliert abweisen und Production
geordnet beenden;
5. prüfen, dass alle Production-Units, -Container und GPU-Prozesse beendet
sind;
6. einen exklusiven Lock für den KI-Umgebungswechsel setzen;
7. Testing für den festgelegten Commit ausrollen und mit der vollständigen
Hardware starten;
8. Health Checks und fachliche Tests ausführen und Ergebnisse dem Pull Request
zuordnen;
9. Testing unabhängig vom Testergebnis vollständig stoppen;
10. prüfen, dass alle Testing-Units, -Container und GPU-Prozesse beendet sind;
11. Production unverändert auf dem zuletzt freigegebenen Stand starten;
12. Production-Health- und Inferenztests ausführen und erst danach das
Wartungsfenster beenden.
Der Wiederanlauf von Production darf nicht ausschließlich von einem letzten
Schritt im Gitea-Job abhängen. Runner-Abbruch, Netzwerkverlust oder ein
fehlgeschlagener Test müssen durch einen hostseitigen Recovery-Mechanismus und
eine dokumentierte manuelle SOP beherrscht werden. Die konkrete Umsetzung kann
beispielsweise einen systemd-gesteuerten Umschaltdienst, einen exklusiven Lock,
Timeouts und einen definierten Rückfall auf Production kombinieren; sie wird
erst in der technischen Spezifikation festgelegt.
Der Testing-Deployment-Account soll Production nicht frei verwalten können. Er
darf lediglich eine eng definierte hostseitige Umschaltoperation starten und
deren Status abfragen. Der Umschaltdienst besitzt die notwendigen Rechte zum
geordneten Stoppen und Wiederanlaufen der beiden Stacks, stellt dem Testing-Job
aber weder Production-Secrets noch allgemeine Production-Verwaltungsrechte zur
Verfügung.
Production und Testing dürfen niemals gleichzeitig als aktiv markiert sein.
Jeder Start prüft den Gegenstatus und bricht bei einem widersprüchlichen oder
unklaren Zustand ab. Der Encoding-Workload bleibt von diesem KI-Lock unabhängig;
ein allgemeines Hostwartungsfenster kann weiterhin alle Workloads stoppen.
### 7.4 Vorgesehene Ansible-Abbildung
Gemeinsame Hostaufgaben und umgebungsspezifische Anwendungsaufgaben werden
getrennt:
```text
ansible/
├── playbooks/
│ ├── host.yml
│ ├── ai.yml
│ ├── video-encoding.yml
│ └── site.yml
└── roles/
├── host_baseline/
├── podman_runtime/
├── workload_accounts/
├── ai_stack/
├── ai_environment_switch/
└── video_encoding/
```
`host.yml` verwaltet gemeinsame Pakete, Treiber, Benutzer und
Runtime-Komponenten einmalig. `ai.yml` verwaltet ausschließlich den LLM-Stack
und genau eine explizit gewählte KI-Umgebung. `video-encoding.yml` verwaltet nur
Encoding-Pfade, Images, Quadlets, Timer und Prüfungen. `site.yml` kann den
unterstützten Gesamtzustand in definierter Reihenfolge herstellen. Hoständerungen
dürfen nicht beiläufig durch einen Workload-Lauf erfolgen.
Für den ersten umgesetzten Baseline-Stand gelten getrennte Konfigurationsbäume
unter `/etc/ghostnet-ai/{production,testing}` und getrennte persistente
Datenbäume unter `/var/lib/ghostnet-ai/{production,testing}`. Der Pfad
`/var/lib/ghostnet-ai/shared` ist für später ausdrücklich freigegebene
gemeinsame Daten reserviert. Konkrete Unterverzeichnisse, Eigentümer sowie
Unit- und Container-Namen werden mit den jeweiligen Fachrollen festgelegt. Die
Konten `gnai-production` und `gnai-testing` besitzen inzwischen ihre
Datenwurzeln und getrennten rootless Podman-Stores; Konfiguration bleibt
rootverwaltet und für die jeweilige Gruppe lesbar. Der Encoding-Workload
verwendet entsprechend `/etc/ghostnet-encoding` und
`/var/lib/ghostnet-encoding`.
### 7.5 ROCm- und `llama.cpp`-Ausgangskonfiguration
Der Betreiber hat zwei Sapphire Nitro+ Radeon RX 6900 XT mit jeweils 16 GiB
VRAM als Zielhardware bestätigt. Für diesen homogenen AMD-Aufbau wird ROCm als
Backend verwendet. Vulkan bleibt eine mögliche spätere Alternative, falls der
Server um GPUs mit unterschiedlichen Backend-Anforderungen erweitert wird.
Als Ausgangspunkt dient eine bereits auf einer RX 7900 XTX verwendete
Podman-Compose-Konfiguration. Die Serverumsetzung wird daraus mit folgenden
Abweichungen als rootless Quadlet erzeugt:
- das Image `ghcr.io/ggml-org/llama.cpp:server-rocm` ist für den ersten
Testing-Stand als Build `b11058`, Revision
`f072b103714dfa1eee531f80b24512faf38e3dd2` und amd64-Manifest
`sha256:b429b8be10d3d0353a09c98f4ab840fe516f779858a28f9fd4e5bdf10bc63cb4`
festgelegt;
- `/dev/kfd` und die tatsächlich benötigten `/dev/dri`-Geräte werden explizit
eingebunden; `GroupAdd=keep-groups` erhält die nötigen ergänzenden
Geräte-Gruppen im rootless Container;
- das gemeinsame Modellverzeichnis wird nur lesbar unter `/models`
eingebunden;
- `llama-server` erhält keinen Host-Port und ist ausschließlich für die später
beschlossene API-Policy-Schicht im umgebungseigenen Podman-Netz erreichbar;
- systemd übernimmt Start, Neustart, Abhängigkeiten und Health-Überwachung;
- Modell- und Laufzeitwerte werden als Ansible-Variablen in die
Quadlet-Kommandozeile gerendert. Zusätzliche Container-Environment-Variablen
werden nur verwendet, wenn das Backend sie technisch benötigt;
- `HSA_OVERRIDE_GFX_VERSION=11.0.0` aus der RX-7900-XTX-Konfiguration wird
nicht übernommen. Die RX 6900 XT verwendet nativ `gfx1030`, das im
vorgesehenen ROCm-Image enthalten sein muss. Ein Override wäre nur nach
einem fehlgeschlagenen Geräte-/Backendtest zulässig und müsste für RDNA2
gesondert begründet werden;
- `label=disable` wird nur übernommen, falls eine tatsächlich aktive
SELinux-Konfiguration dies erfordert. Für den dokumentierten Debian-Host
wird es nicht vorsorglich gesetzt.
Die ausführliche Kandidatenbewertung steht in
[GNAI-009-PLAN](GNAI-009-PLAN_Llama-Model-Evaluation.md), die technische
Sollkonfiguration einschließlich revisionsgebundener Downloadquelle und
SHA-256 in [GNAI-010-TECH](GNAI-010-TECH_Llama-Serving.md). Der bevorzugte
erste Testing-Kandidat ist:
| Parameter | Ausgangswert |
| --- | --- |
| Modelldatei | `Swift-Qwen3.8-27B-Q6_K.gguf` |
| API-Alias | `swift-qwen3.8-27b-q6k` |
| Kontext | 131.072 Token; Inbetriebnahme zunächst mit 65.536 |
| parallele Slots | 1 |
| maximale Ausgabe | zunächst 32.768 Token; gesonderter 65.536-Token-Test |
| GPU-Layer | alle |
| Flash Attention | aktiviert |
| K-/V-KV-Cache | jeweils `q8_0` |
| Reasoning Effort | `xhigh`, ohne kurzen Reasoning-Cap |
| MTP | zunächst deaktiviert; spätere A/B-Messung mit `n-max=2` |
| Vision-Projektor | zunächst nicht geladen |
Samplingwerte und Chat-Template-Parameter gehören zum Modellprofil und nicht
zu allgemeinen Rollendefaults. Dadurch können andere Modelle später eigene
geprüfte Profile verwenden, ohne die gemeinsame `llama_cpp`-Rolle zu ändern.
Zusätzlich wird `Qwen3.8-27B-UD-Q6_K.gguf` aus dem revisionsgebundenen
Unsloth-Repository installiert. Es läuft ebenfalls mit 131.072 Token Kontext.
Ein kontrollierter A/B-Test verwendet für beide Kandidaten `xhigh`; ein
zweiter Vergleich stellt Swift `xhigh` dem vorgesehenen Unsloth-`medium`-
Praxisprofil gegenüber. Beide Dateien liegen auf dem Host, aber immer nur ein
Modell wird in den VRAM geladen. Jedes Modell erhält ein eigenes festes
Testing-Quadlet; der Wechsel stoppt die aktive Unit vollständig, bevor die
andere startet. Der im gepinnten Build vorhandene dynamische `llama.cpp`-
Router-Modus bleibt für die erste Ausbaustufe bewusst zurückgestellt.
Für die beiden gleich großen GPUs wird zunächst
`--split-mode layer --tensor-split 1,1` festgelegt. Der Layer-Split minimiert
GPU-übergreifende Kommunikation und passt damit zur dokumentierten
PCIe-Topologie, bei der die zweite Karte über einen langsameren, am PCH
angebundenen Slot läuft. `row` wird wegen seiner Ablösung im aktuellen
`llama.cpp` nicht als neue Betriebsvariante vorgesehen. Der experimentelle
`tensor`-Split bleibt ein späterer Benchmarkkandidat; er benötigt deutlich
mehr Kommunikation zwischen den Karten und ist mit dem geplanten
quantisierten V-KV-Cache derzeit nicht kompatibel.
Der rootless Container-Probelauf hat inzwischen zwei `gfx1030`-Agents sowie
`ROCm0` und `ROCm1` mit jeweils rund 16 GiB VRAM bestätigt. Vor dem dauerhaften
Start muss ein begrenzter Modelltest noch die Device-Reihenfolge und die
tatsächliche 1:1-Verteilung in den `llama.cpp`-Startlogs bestätigen. Zusätzlich
werden Prompt-Verarbeitung, Token-Generierung, VRAM-Nutzung und Stabilität bei
131.072 Token Kontext gemessen. Der 1:1-Wert ist erst nach diesem Test ein
abgenommener Betriebsparameter.
---
## 8. Vorgesehener Workflow
### 8.1 Änderung und Prüfung
1. Repository klonen und dokumentierte lokale Ansible-Umgebung einrichten.
2. Feature-Branch anlegen.
3. Rollen, Variablen, Templates und zugehörige Dokumentation ändern.
4. lokale Lint-, Syntax- und nach Möglichkeit Check-Mode-Prüfungen ausführen.
5. Pull Request erstellen.
6. automatisierte CI-Prüfungen ohne produktive SSH- oder Vault-Zugangsdaten
ausführen.
7. erste menschliche Prüfung und einen Testing-Lauf für den konkreten Commit
freigeben;
8. Testing-Ergebnis im Pull Request dokumentieren;
9. fachliches Abschlussreview und Freigabe durch eine zweite berechtigte
Person und
10. nach erfolgreichem Statuscheck in den geschützten `main`-Branch mergen.
### 8.2 Testing-Deployment vor dem Merge
Ein Pull Request führt zunächst nur statische CI-Prüfungen in einer isolierten
Jobumgebung ohne SSH-, Vault- oder Serverzugang aus. Nach einer ersten
menschlichen Prüfung kann eine berechtigte Person einen kontrollierten
Testing-Lauf für den konkreten Commit auslösen.
Der Testing-Lauf:
1. verwendet ausschließlich den Testing-Deployment-Account und
Testing-Secrets;
2. erhält erst nach einem freigegebenen Wartungsfenster Zugriff auf den
Umschaltvorgang;
3. fordert über die fest definierte hostseitige Umschaltoperation den
kontrollierten Production-Stopp an und prüft den vollständigen Stillstand;
4. darf im aktiven Testabschnitt nur Testing-Pfade, -Units, -Container und
-Netze verwalten;
5. zeichnet den getesteten Commit unverwechselbar auf;
6. führt technische Health Checks und die für die Änderung notwendigen
Funktionstests aus;
7. veröffentlicht keine Testdienste im Internet;
8. kann die Testing-Daten vor oder nach dem Lauf auf einen bekannten Stand
zurücksetzen und
9. muss Testing stoppen sowie den letzten freigegebenen Production-Stand
wieder starten und prüfen, auch wenn der eigentliche Test fehlschlägt.
Beliebiger Pull-Request-Code darf nicht automatisch mit privilegierten
Serverzugängen ausgeführt werden. Runner-Labels allein gelten nicht als
ausreichende Berechtigungsgrenze. Vor der Implementierung ist zu prüfen, wie
Gitea in der eingesetzten Version Secrets, interne Branches, Forks und manuell
freigegebene Jobs behandelt. Production-Credentials werden keinem
Pull-Request-Workflow bereitgestellt.
### 8.3 Production-Deployment
1. berechtigte Person erzeugt einen geschützten Deployment-Tag auf einem
geprüften Commit;
2. der repository-spezifische Runner übernimmt den Job mit einer Parallelität
von eins;
3. der Job prüft erneut Syntax und Abhängigkeiten;
4. optionaler Check-Modus zeigt erwartete Änderungen, soweit die verwendeten
Module ihn zuverlässig unterstützen;
5. `environment.yml` wendet den Production-Sollzustand auf `ghostnet-ki` an;
6. `verify.yml` prüft Dienste, interne Endpunkte, Containerzustand und später
einen kleinen Inferenztest;
7. der Lauf hält Commit, Imageversionen oder Digests sowie Prüfergebnis fest;
8. erst nach allen Prüfungen gilt das Deployment als erfolgreich.
Ein Production-Deployment verwendet ausschließlich einen Commit aus dem
geschützten `main`-Branch und eine eigene bewusste Freigabe. Ein erfolgreicher
Testing-Lauf ist Voraussetzung für fachlich relevante Änderungen, ersetzt aber
weder Review noch Production-Backup und Freigabe.
Check Mode ist nur eine zusätzliche Kontrolle. Module und Shell-Kommandos, die
keine vollständige Simulation unterstützen, müssen durch gezielte Tests und
idempotente Implementierung abgesichert werden.
---
## 9. Zugangsdaten und Sicherheitsgrenzen
Der Produktionsrunner verarbeitet privilegierte Zugangsdaten und muss daher
wie ein Administrationssystem behandelt werden. Vorgesehen sind:
- repository-spezifische Runner-Registrierung und eindeutiges Runner-Label,
- keine Ausführung fremder oder öffentlicher Repositories,
- ein technischer Zielaccount getrennt von persönlichen Konten und
`ghost-agent`,
- getrennte technische Berechtigungen und Schlüssel für Production und
Testing, soweit dies mit der gewählten Podman-/systemd-Verwaltung belastbar
durchsetzbar ist,
- ein eigener SSH-Schlüssel ausschließlich für das Deployment,
- geprüfter und fest hinterlegter SSH-Host-Key,
- keine Passwort- oder Root-Anmeldung per SSH,
- möglichst eng begrenzter Netzwerkzugang des Runner-LXC,
- keine Secrets in Repository, Jobausgaben oder diff-fähigen Templates,
- `no_log` und deaktivierte Diffs für geheimnishaltige Tasks,
- getrennte Aufbewahrung des Vault-Passworts im Runner und im administrativen
Passwortmanager sowie
- dokumentierte Rotation und Sperrung von Runner-, SSH- und Vault-Zugangsdaten.
Der Testing-Zugang darf keinen Zugriff auf Production-Secrets oder
Production-Daten erhalten. Die Produktionsberechtigung darf nicht allein durch
die Auswahl eines frei benennbaren Runner-Labels erreichbar sein. Das genaue
Verfahren wird nach Prüfung der eingesetzten Gitea-Version festgelegt. Die
notwendige zeitliche Umschaltung wird über eine eng definierte hostseitige
Operation vermittelt und ist keine allgemeine Production-Berechtigung des
Testing-Accounts.
Der Deployment-Account besitzt derzeit bewusst `NOPASSWD: ALL`, weil
Paketverwaltung, systemd, Treiber und rootful Containerverwaltung weitreichende
Rechte erfordern. Eine formal kleine Befehlsliste wäre nur sinnvoll, wenn sie
den tatsächlichen Ansible-Betrieb nicht durch unsichere Umgehungen oder fragile
Sonderfälle ersetzt. Schlüssel, Account und berechtigte Workflows sind deshalb
als root-äquivalente Sicherheitsgrenze zu behandeln. Die spätere Trennung von
Production- und Testing-Berechtigungen bleibt davon unabhängig zu spezifizieren.
---
## 10. Zu erstellende technische Dokumentation und SOPs
Die frühere Liste aus neun Einzeldokumenten war für den aktuellen Umfang zu
feingranular und hätte Rollen-READMEs, Betriebsabläufe und Sicherheitsregeln
mehrfach beschrieben. Dokumentation wird deshalb entlang realer Zielgruppen
gebündelt und erst gemeinsam mit der zugehörigen Implementierung erstellt:
| Artefakt | Inhalt |
| --- | --- |
| Deployment-Architektur | Runner- und Secret-Grenzen, Inventar, Freigaben, Production-/Testing-Isolation und Abnahmekriterien |
| Contributor- und Deployment-SOP | lokales Environment, Änderung, Review, statische CI, bewusste Freigabe und Deploymentnachweis |
| Betriebs- und Recovery-SOP | Testing-Fenster, Environment-Wechsel, Rollback, Backup/Restore und Störungsbehandlung |
| Rollen-READMEs | konkrete Variablen, Voraussetzungen, Aufrufe, erwartete Ergebnisse und technische Fehlerfälle |
Ein eigenes Dokument entsteht nur bei eigenständiger Zielgruppe oder
Vertrauensgrenze. Eine Zielbeschreibung allein gilt nicht als Nachweis einer
erfolgreichen Einrichtung.
---
## 11. Umsetzungsreihenfolge
Der frühere Phasenplan vermischte bereits erledigte Grundlagen mit offener
Arbeit und stellte die Gitea-Integration pauschal vor den ersten
Anwendungsnachweis. Er wird durch den statusbasierten
[High-Level-Implementierungsplan](../../../GNAI-001-PLAN_Project-Roadmap.md) ersetzt.
Für diesen Ansible-Teilplan gelten daraus drei Leitplanken:
1. Der erste interne `llama.cpp`-Vertical-Slice wird lokal kontrolliert und
ohne öffentliche Route ausgerollt.
2. Statische CI erhält keine SSH- oder Deployment-Secrets.
3. Ein privilegierter Runner folgt erst nach stabilem Rollen-, Rollback- und
Recovery-Verfahren.
---
## 12. Verbleibende Deployment-Entscheidungen
Produkt- und Prioritätsentscheidungen stehen zentral in GNAI-001. Für die
spätere Delivery-Kette bleiben hier nur folgende technische Fragen:
1. Welche Gitea-/Actions-/Runner-Version und welche Schutzfunktionen sind live
verfügbar?
2. Wo läuft der repository-spezifische Runner, und welche Job-Isolation wird
nachweisbar unterstützt?
3. Welche Personen dürfen reviewen, mergen und Testing beziehungsweise
Production freigeben?
4. Welcher Secret-Store, welche Schlüsselrotation und welche getrennten
Testing-/Production-Rechte werden verwendet?
5. Welcher unveränderliche Commit und welcher manuelle Freigabeschritt lösen
Production aus?
6. Welcher hostseitige Lock-, Timeout- und Recovery-Mechanismus stellt
Production nach einem Abbruch wieder her?
Diese Fragen werden vor Einführung eines privilegierten Runners entschieden,
nicht als Voraussetzung für den internen Testing-Vertical-Slice.
---
## 13. Referenzstand der Planung
Für die weitere Ausarbeitung wurden am 19.09.2026 insbesondere folgende
Herstellerdokumentationen herangezogen:
- [Gitea Actions – Überblick](https://docs.gitea.com/usage/actions/)
- [Gitea Runner](https://docs.gitea.com/runner/)
- [Gitea Actions – Secrets](https://docs.gitea.com/usage/actions/secrets/)
- [Gitea Actions – Job-Token-Berechtigungen](https://docs.gitea.com/usage/actions/token-permissions/)
- [Gitea – geschützte Tags](https://docs.gitea.com/usage/protected-tags/)
- [Ansible Playbooks](https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_intro.html)
- [Ansible Check- und Diff-Modus](https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_checkmode.html)
- [Ansible Vault](https://docs.ansible.com/projects/ansible/latest/vault_guide/vault.html)
- [Debian 13 – Paket `podman`](https://packages.debian.org/trixie/podman)
- [Podman – Quadlet und systemd](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html)
- [llama.cpp – Multi-GPU](https://github.com/ggml-org/llama.cpp/blob/master/docs/multi-gpu.md)
- [llama.cpp – ROCm-Container](https://github.com/ggml-org/llama.cpp/blob/master/.devops/rocm.Dockerfile)
Vor der Implementierung sind versionsspezifische Aussagen gegen die dann
tatsächlich eingesetzten Gitea-, Runner- und Ansible-Versionen zu prüfen.
---
## 14. Belegter Ansible-Implementierungsstand
Am 19.09.2026 wurde `ansible-core` 2.21.4 in einer lokalen, von Git
ausgeschlossenen virtuellen Umgebung installiert. Das neue Control-Projekt
enthält:
- `ansible.cfg` mit festem Testing-Inventar, Host-Key-Prüfung und lokalem
Ansible-Arbeitsverzeichnis;
- ein Testing-Inventar für `ghostnet-ki` unter `10.0.0.90` mit dem
unprivilegierten Konto `ghost-agent`;
- ein Preflight-Playbook ohne `become` und ohne konfigurationsändernde Tasks;
- eine kurze Anleitung für Control-Node-Setup, SSH-Agent, Syntaxprüfung und
Live-Lauf.
Inventardarstellung und Syntaxprüfung waren erfolgreich. Der Live-Lauf
bestätigte:
| Merkmal | Ergebnis |
| --- | --- |
| Hostname | `ghostnet-ki` |
| Betriebssystem | Debian 13.6 |
| Standard-IPv4 | `10.0.0.90` |
| CPU | 14 vCPUs |
| RAM | 64.123 MiB |
| Remote-Python | 3.13.5 |
| GPUs | zwei Navi-21-PCI-Geräte `1002:73bf` |
| Play Recap | `ok=5 changed=0 unreachable=0 failed=0` |
Dieser Lauf ist ein Zugriffs- und Identitätsnachweis. Er bestätigt weder ROCm
noch Inferenzfunktion, privilegierte Deploymentrechte, Containerbetrieb oder
die geplante Umschaltung zwischen Production und Testing.
Für den folgenden Bootstrap wurden ein eigener Ed25519-Schlüssel, ein
Account-/sudoers-Playbook und ein getrenntes Prüfplaybook erstellt. Der
öffentliche Schlüssel ist mit Fingerprint
`SHA256:IKEInbIo8VVI1USg5+a3ypGK8H8FweJiQu+v7KvhQDU` versioniert; der private
Schlüssel liegt mit Modus `0600` außerhalb des Repositories. Syntaxprüfung und
Bootstrap-Inventar waren erfolgreich.
Der Bootstrap wurde anschließend über das persönliche Administratorkonto
ausgeführt. Benutzer, Home-/SSH-Verzeichnisse, eingeschränkter Public Key und
die mit `visudo` geprüfte sudoers-Datei wurden erfolgreich angelegt. Die letzte
Assertion des ersten Laufs schlug ausschließlich fehl, weil die nicht vollständig
quotierte Bedingung mit `NOPASSWD: ALL` als YAML-Mapping statt als String
interpretiert wurde. Die Bedingung wurde korrigiert. Das getrennte Prüfplaybook
bestätigte danach die Anmeldung als `ansible-deploy`, normale Modulausführung
und nicht-interaktive Eskalation auf UID 0 mit
`ok=5 changed=0 unreachable=0 failed=0`. Ein vollständiger zweiter
Bootstrap-Lauf zur praktischen Idempotenzprüfung wurde noch nicht ausgeführt.
Als erste gemeinsame Hostrolle wurde anschließend `host_baseline` mit eigenem
Host-Inventar und `playbooks/host.yml` implementiert. Vor Änderungen prüft sie
Hostname, Debian-Hauptversion, Standard-IPv4, die Erkennung des
Root-Dateisystems und mindestens 100 GiB freien Speicher. Sie verwaltet derzeit
ausschließlich folgende root-eigenen Verzeichniswurzeln:
```text
/etc/ghostnet-ai/production
/etc/ghostnet-ai/testing
/var/lib/ghostnet-ai/production
/var/lib/ghostnet-ai/testing
/var/lib/ghostnet-ai/shared
```
Inventar- und Syntaxprüfung waren erfolgreich. Der Check-Modus zeigte nur die
sieben erwarteten Verzeichnisse einschließlich ihrer Eltern als Änderungen und
meldete 1.677,7 GiB freien Platz auf `/`. Der erste reale Lauf endete mit
`ok=6 changed=1 unreachable=0 failed=0`. Der unmittelbar folgende identische
Lauf endete mit `ok=6 changed=0 unreachable=0 failed=0` und belegt die
Idempotenz des aktuellen Rollenumfangs. Es wurden keine Pakete installiert und
keine Dienste, Container oder GPU-Komponenten verändert.
Am 20.09.2026 wurde `podman_runtime` in das gemeinsame Host-Playbook
aufgenommen. Die Rolle installiert `podman`, `aardvark-dns`, `passt`,
`slirp4netns` und `uidmap` ohne empfohlene Build- und Checkpoint-Pakete. Sie
verlangt Debian 13, systemd und cgroup v2, verwaltet
`/etc/containers/systemd`, prüft mindestens Podman-Hauptversion 5 sowie den
Overlay-Storage und führt den Quadlet-Generator ohne Workload im Dry-Run aus.
Der API-Socket und der automatische Image-Update-Timer bleiben explizit
deaktiviert und gestoppt.
Der Check-Modus sagte 38 neue Pakete und das Quadlet-Verzeichnis voraus. Im
ersten realen Lauf wurden die Pakete installiert; die anschließende
Generatorprüfung schlug fehl, weil der Generator in Debian als Symlink
bereitgestellt wird und die `stat`-Prüfung das Ziel zunächst nicht auflöste.
Nach der Korrektur auf `follow: true` bestanden sämtliche Laufzeitprüfungen.
Der finale Gesamtlauf endete mit
`ok=20 changed=0 unreachable=0 failed=0`. Verifiziert wurden Podman 5.4.2,
cgroup v2, der Storage-Treiber `overlay` und der ausführbare Quadlet-Generator.
Eine getrennte Prüfung bestätigte außerdem leere Image- und Containerlisten
sowie `podman.socket` und `podman-auto-update.timer` jeweils als `disabled` und
`inactive`.
Die Rolle `workload_accounts` wurde anschließend in `host.yml` aufgenommen. Sie
verwaltet feste IDs, gesperrte Passwörter, `/usr/sbin/nologin`, getrennte
SubUID-/SubGID-Bereiche, Daten- und Konfigurationsrechte, rootverwaltete
Quadlet-Verzeichnisse und Linger für `gnai-production` (UID/GID 980),
`gnai-testing` (981) und `gn-encoding` (982). Die Datenwurzeln gehören dem
jeweiligen Konto; Konfiguration gehört root und ist für die jeweilige Gruppe
lesbar.
Beim ersten realen Lauf scheiterte nur die Abschlussprüfung, weil die
sudoers-Regel von `ansible-deploy` den direkten Ansible-Wechsel zu den
Servicekonten nicht erlaubt. Die Prüfung wurde auf einen rootseitigen Aufruf
über `runuser` umgestellt, ohne die sudoers-Regel zu erweitern. Ein weiterer
Lauf deckte eine konkurrierende Eigentümerverwaltung durch `host_baseline` auf;
die umgebungsspezifischen Pfade wurden daraufhin vollständig an
`workload_accounts` übertragen. Der korrigierte Gesamtlauf endete mit
`ok=36 changed=0 unreachable=0 failed=0` und bestätigte drei aktive
systemd-User-Manager sowie drei getrennte rootless Podman Graph Roots. Es
wurden keine Images oder Workload-Container angelegt.
Anschließend wurde `amd_gpu_access` in das gemeinsame Host-Playbook
aufgenommen. Die Rolle verlangt genau zwei Geräte `1002:73bf` mit `amdgpu`,
ordnet über sysfs ausschließlich deren Render-Nodes zu, prüft `/dev/kfd` und
leitet die minimal erforderlichen Gerätegruppen aus dem Istzustand ab. Auf dem
Host sind dies `/dev/kfd`, `/dev/dri/renderD128` und
`/dev/dri/renderD129`, jeweils mit Gruppe `render`.
Der Check-Modus prognostizierte nur die ergänzende Gruppenmitgliedschaft für
`gnai-production` und `gnai-testing`. Der reale Lauf bestätigte danach für
beide Konten Lese- und Schreibzugriff auf alle drei Geräte. Ein erster
Wiederholungslauf deckte auf, dass `workload_accounts` die Gruppe zunächst
entfernte und `amd_gpu_access` sie wieder hinzufügte. `render` wurde deshalb in
den expliziten Sollzustand der beiden KI-Konten übernommen; das Encoding-Konto
bleibt ohne GPU-Gruppe. Der korrigierte Gesamtlauf endete mit
`ok=55 changed=0 unreachable=0 failed=0`. Weder ein ROCm-Image noch ein
Container oder eine Inferenz wurden dabei ausgeführt.
Danach wurde `verify-rocm-container.yml` mit der eigenständigen Rolle
`rocm_container_probe` ergänzt. Das Playbook wiederholt zunächst den
Host-Gerätenachweis und arbeitet anschließend ausschließlich im rootless
Podman-Store von `gnai-testing`. Es verwendet das offizielle
`llama.cpp`-ROCm-Image als unveränderliches amd64-Manifest
`sha256:b429b8be10d3d0353a09c98f4ab840fe516f779858a28f9fd4e5bdf10bc63cb4`
und prüft dessen OCI-Labels auf Build `b11058` und Revision
`f072b103714dfa1eee531f80b24512faf38e3dd2`.
Die ephemeren Probecontainer erhielten nur `/dev/kfd`, die beiden zuvor
ermittelten Render-Nodes und `keep-groups`; ein
`HSA_OVERRIDE_GFX_VERSION` wurde nicht gesetzt. `rocminfo` meldete genau zwei
GPU-Agents mit `gfx1030`. `llama-server --list-devices` meldete getrennt
`ROCm0` und `ROCm1`, beide als AMD Radeon RX 6900 XT mit jeweils rund 16 GiB
VRAM. Der Abschlusslauf endete mit
`ok=33 changed=0 unreachable=0 failed=0`. Die Probecontainer wurden durch
`--rm` entfernt; das gepinnte Image bleibt für den folgenden Testing-Aufbau im
Store. Modellladen, Layer-Verteilung und Inferenz wurden noch nicht geprüft.
---
## 15. Abschlussgrenze dieses Entwurfs
Mit Version 0.15 sind der aktuelle Planungsstand, der ausdrücklich begrenzte
Ansible-Preflight, der verifizierte Deployment-Account und die angewendete
minimale Host-Baseline, die Podman-/Quadlet-Grundruntime und die getrennten
Workload-Konten, deren begrenzter AMD-Gerätezugriff sowie der rootless
ROCm-/`llama.cpp`-Gerätenachweis für Testing persistiert.
Insbesondere wurden noch nicht:
- ein Gitea Runner angelegt,
- Gitea Actions oder Schutzregeln konfiguriert,
- eine dauerhafte `llama.cpp`-Anwendungsrolle, ein Production-Inventar oder ein
Vault erstellt,
- eine Production- oder Testing-Umgebung eingerichtet oder voneinander
abgenommen,
- ein dauerhafter GPU-Container, ein Modell oder eine Anwendung auf dem
KI-Server eingerichtet,
- Backups oder Rollbacks praktisch getestet oder
- die geplante Deploymentkette technisch abgenommen.
Der nächste KI-spezifische Schritt ist die `llama.cpp`-Rolle für Testing. Die
bereichsübergreifende Priorität und die Abhängigkeit von den offenen
Sicherheitsbefunden legt GNAI-001 fest. LiteLLM ist nicht länger automatisch
der Folgeschritt, sondern wird vor einer Veröffentlichung anhand der benötigten
API-Policies entschieden.
@@ -0,0 +1,345 @@
# Arbeitsentwurf
**Dokumenten-ID:** GNAI-009-PLAN
**Titel:** Modell- und Laufzeitbewertung für `llama.cpp`
**Stand:** 2026-09-21
**Status:** Nicht finaler Evaluierungsplan; keine Production-Freigabe
**Referenzen:** [Ansible-Deployment](GNAI-008-PLAN_Ansible-Deployment.md), [technische Sollkonfiguration](GNAI-010-TECH_Llama-Serving.md), [Zielarchitektur](GNAI-004-INFRA_Architecture.md)
> Dieses Dokument persistiert die Modellrecherche und legt fest, wie daraus
> eine belastbare Entscheidung für den KI-Server entsteht. Veröffentlichte
> Benchmarks, Community-Berichte und lokale Abnahmetests werden getrennt
> bewertet. Ein hier genannter Kandidat ist erst nach erfolgreicher lokaler
> Abnahme für Production freigegeben.
---
## Änderungsverlauf
| Datum | Version | Autor | Beschreibung |
| --- | --- | --- | --- |
| 2026-09-21 | 0.4 | spektr / Codex | Metrikübergabe an die später gewählte API-Policy-Schicht statt vorab an LiteLLM gebunden |
| 2026-09-21 | 0.3 | spektr / Codex | feste, gegenseitig ausgeschlossene Quadlet-Unit je Modell als Umschaltverfahren festgelegt |
| 2026-09-21 | 0.2 | spektr / Codex | Unsloth Qwen3.8 UD-Q6_K als verbindlichen zweiten 128k-Kandidaten festgelegt und kontrollierten A/B-Test ergänzt |
| 2026-09-21 | 0.1 | spektr / Codex | Swift-Qwen3.8, Q6_K, Reasoning, Kontext, MTP und lokales Vergleichsverfahren als ersten Evaluierungsstand aufgenommen |
---
## 1. Ziel
Gesucht wird ein Modellprofil für Coding-, Tool- und längere agentische
Aufgaben auf zwei Radeon RX 6900 XT mit zusammen 32 GiB VRAM. Das Profil soll:
- komplexe Aufgaben mit ausreichend langem Reasoning bearbeiten können;
- unnötige Reasoning-Schleifen reduzieren;
- Tool Calls und strukturierte Ausgaben zuverlässig erzeugen;
- mindestens 128 KiToken Kontext als Zielkonfiguration unterstützen;
- vollständig über beide GPUs mit ROCm betrieben werden;
- durch unveränderliche Modell- und Containerartefakte reproduzierbar sein;
- Swift und Unsloth gleichzeitig im Modellbestand vorhalten, aber für Tests
jeweils nur ein Modell in den VRAM laden;
- ohne Modellwechsel zwischen Testing und Production übertragbar sein; und
- später gegen neue Swift-, Base- oder Dynamic-Quantisierungen austauschbar
bleiben.
Die Entscheidung wird anhand realer Aufgaben getroffen. Herstellerbenchmarks
dienen als Kandidatenauswahl, ersetzen aber nicht die lokale Abnahme.
## 2. Bewerteter Quellenstand
### 2.1 Qwen3.8-27B
Die [Qwen-Modellkarte](https://huggingface.co/Qwen/Qwen3.8-27B) nennt für das
27B-Modell 64 Blöcke, davon 16 Full-Attention-Blöcke, einen nativen Kontext von
262.144 Token und ein trainiertes Multi-Token-Prediction-Head. Thinking ist
standardmäßig aktiv. Qwen empfiehlt für Thinking `temperature=1.0`,
`top_p=0.95`, `top_k=20`, `min_p=0.0`, `presence_penalty=0.0` und
`repetition_penalty=1.0`.
Qwen unterstützt `xhigh`, `medium` und `low` als Reasoning-Stufen. Die
Modellkarte warnt ausdrücklich, dass ein niedrigeres Reasoning-Niveau bei
mehrstufigen Agentenaufgaben zu Fehlversuchen und Wiederholungen führen kann
und dadurch nicht zwingend Zeit oder Token spart. Ein harter kurzer
Reasoning-Cap wird deshalb nicht als Standardprofil vorgesehen.
### 2.2 Swift-Qwen3.8-27B
Der Herausgeber beschreibt
[Swift-Qwen3.8-27B](https://huggingface.co/ukisai/Swift-Qwen3.8-27b) als
reasoning-effiziente Ableitung des Qwen-Basismodells. Nach eigener Darstellung
wurden mit Overthinking korrelierte Reasoning-Muster im Fine-Tuning
benachteiligt und verlorene Qualität durch weitere Trainingsschritte
kompensiert.
Die veröffentlichten BF16-Vergleiche wurden mit vLLM, 262.144 Token Kontext,
`xhigh`, fünf Seeds und den Qwen-Samplingwerten durchgeführt. Für GPQA Diamond
werden nahezu gleiche Scores bei 41 Prozent weniger mittleren beziehungsweise
58,3 Prozent weniger medianen Thinking-Tokens ausgewiesen. Terminal-Bench 2.1
liegt geringfügig unter Base, LiveCodeBench v6 darüber. AIME 2026 und HMMT
zeigen hingegen einen messbaren Rückgang.
Diese Ergebnisse stammen vom Modellhersteller. Sie sind ein guter Grund für
einen lokalen Test, aber noch kein unabhängiger Nachweis für unsere Aufgaben,
für GGUF Q6_K, für llama.cpp oder für ROCm auf Navi 21. Insbesondere darf der
LiveCodeBench-Unterschied nicht ohne Kontrolle der Output-Limits als allgemeine
Qualitätssteigerung interpretiert werden.
### 2.3 GGUF-Quantisierungen
Das vom Swift-Herausgeber gepflegte
[GGUF-Repository](https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF)
enthält unter anderem folgende Ein-Datei-Quantisierungen:
| Quantisierung | veröffentlichte Größe | vorgesehene Rolle |
| --- | ---: | --- |
| Q4_K_M | 18,0 GB | speichersparender Vergleich und möglicher Fast-/Vision-Fallback |
| Q5_K_M | 20,2 GB | möglicher Kompromiss für MTP oder zusätzliche Reserve |
| Q6_K | 22,9 GB | bevorzugter Qualitätskandidat für Text-, Coding- und Tool-Aufgaben |
| Q6_K_L | 25,2 GB | derzeit zu geringe Reserve für 128k und Laufzeitpuffer |
| Q8_0 | 29,1 GB | für 32 GiB Gesamt-VRAM mit langem Kontext ungeeignet |
Der Herausgeber empfiehlt Q6_K oder höher für lange Agentenläufe und strikte
Tool-Formate. Die GGUFs wurden laut Modellkarte mit llama.cpp `b10896` erzeugt;
recurrent-sensitive Tensoren und das MTP-Head verwenden gezielt höhere
Präzision. Die veröffentlichten quantisierten Aufgabenbenchmarks betreffen
jedoch W4A16/AWQ und nicht direkt die Q6_K-GGUF. Die Eignung von Q6_K bleibt
daher Gegenstand unseres Tests.
### 2.4 Unsloth Qwen3.8-27B UD-Q6_K
Das zweite Testartefakt stammt aus dem
[Unsloth-GGUF-Repository](https://huggingface.co/unsloth/Qwen3.8-27B-GGUF).
Ausgewählt ist `Qwen3.8-27B-UD-Q6_K.gguf` mit einer veröffentlichten Größe von
22 GB. Das Repository steht laut Modellkarte unter Apache-2.0. Unsloth hat die
Quantisierung mit einer Importance Matrix und dem eigenen Dynamic-Verfahren
erzeugt.
Damit unterscheidet sich der Kandidat nicht nur durch das ursprüngliche
Qwen-Verhalten von Swift, sondern auch durch das Quantisierungslayout. Der
Vergleich misst deshalb die beiden konkret einsetzbaren Artefakte als Ganzes;
er isoliert nicht wissenschaftlich den Effekt des Swift-Fine-Tunings. Diese
Einschränkung wird bei der Auswertung ausdrücklich festgehalten.
### 2.5 MTP
Das MTP-Head ist in den Swift-GGUFs enthalten. Die Modellkarte nennt für
llama.cpp `--spec-type draft-mtp --spec-draft-n-max 3`. Die aktuelle
llama.cpp-Dokumentation führt MTP als unterstützte spekulative Methode.
MTP wird trotzdem nicht in das erste Abnahmeprofil aufgenommen. Es benötigt
zusätzlichen Draft-Kontext und zusätzliche Laufzeitpuffer. Der verlinkte
Community-Nachweis zeigt, dass MTP deutliche Gewinne liefern kann, verwendet
aber Strix Halo, Vulkan, ein anderes Qwen-Modell und zeitweise einen Fork. Er
ist kein Nachweis für zwei RX 6900 XT mit dem gepinnten ROCm-Image.
MTP wird erst nach einer stabilen Baseline in derselben Sitzung und mit
identischen Aufgaben ein- und ausgeschaltet. Begonnen wird mit
`--spec-draft-n-max 2`; `3` bleibt eine zweite Messstufe.
### 2.6 Swift 1.5 und Community-Quants
Ankündigungen zu Swift 1.5 und kombinierten Swift-/Unsloth-Dynamic-Quants
werden als Beobachtung festgehalten. Am 21.09.2026 wurde daraus noch kein
freigegebenes Projektartefakt ausgewählt. Weder die Ansible-Rolle noch das
Production-Profil warten auf eine angekündigte Version.
Ein neuer Checkpoint wird als neues, unveränderliches Profil mit eigener
Quelle, Revision, Prüfsumme und Lizenzprüfung aufgenommen. Er ersetzt niemals
automatisch ein bereits abgenommenes Profil.
## 3. Vorläufige Kandidaten
### 3.1 Bevorzugter Kandidat
**Swift-Qwen3.8-27B Q6_K mit `xhigh`** wird als erster Testing-Kandidat
festgelegt. Gründe sind die höhere Gewichtsqualität gegenüber Q4/Q5, die
Herstellerempfehlung für lange Agentenläufe und die Zielsetzung von Swift,
Reasoning-Effizienz ohne kurzen harten Token-Cap zu erreichen.
Das erste Profil verwendet:
| Parameter | Wert |
| --- | --- |
| Quantisierung | Q6_K |
| Kontextziel | 131.072 Token |
| K-/V-KV-Cache | `q8_0` / `q8_0` |
| Reasoning | aktiv, `xhigh`, ohne Reasoning-Budget |
| parallele Slots | 1 |
| Flash Attention | aktiv |
| GPU-Offload | alle Layer |
| GPU-Aufteilung | `layer`, `1,1` |
| MTP | zunächst deaktiviert |
| Vision-Projektor | zunächst nicht geladen |
| maximale Ausgabe | zunächst 32.768 Token; gesonderter 65.536-Token-Test |
128 KiToken sind das Zielprofil. Die Inbetriebnahme prüft dasselbe Modell
zuerst mit 64 KiToken, damit Fehler bei Modellformat, ROCm oder GPU-Aufteilung
von reinem Speicherdruck unterschieden werden können.
### 3.2 Verbindlicher Vergleichskandidat
**Unsloth Qwen3.8-27B UD-Q6_K** wird ebenfalls installiert und mit 131.072
Token Kontext abgenommen. Das Artefakt ist verbindlich festgelegt:
| Feld | Wert |
| --- | --- |
| Repository | `unsloth/Qwen3.8-27B-GGUF` |
| Revision | `4ca720788d1e01f1bff70c033e0d0028fd02e502` |
| Datei | `Qwen3.8-27B-UD-Q6_K.gguf` |
| veröffentlichte Größe | 22 GB |
| SHA-256 | `c9c206812fbe4ac7b76a729e25928b63f2ae89d37f69da7a71c20aec763cd436` |
| API-Alias | `unsloth-qwen3.8-27b-ud-q6k` |
Beide Dateien liegen gleichzeitig auf dem Host und jedes Modell besitzt eine
eigene feste Quadlet-Unit. Für einen Messlauf ist immer nur die ausgewählte
Unit aktiv; die Modelle werden nicht gleichzeitig in den VRAM geladen. Der
Wechsel stoppt den bisherigen Container vollständig, bevor der andere startet.
Der erste kontrollierte A/B-Vergleich verwendet bei beiden Modellen `xhigh`,
131.072 Token Kontext, Q8-K/V, einen Slot, dieselben Samplingwerte und kein
MTP. Danach folgt der praxisnahe Profilvergleich Swift `xhigh` gegen Unsloth
`medium`. Dadurch bleiben die Effekte der Reasoning-Einstellung sichtbar,
statt sie unbemerkt dem Modellartefakt zuzurechnen.
### 3.3 Ressourcen-Fallback
**Swift Q5_K_M mit `xhigh`** ist der erste Fallback, falls Q6_K mit 128k,
Laufzeitpuffern oder später MTP nicht stabil in 32 GiB VRAM betrieben werden
kann. Ein Wechsel auf Q5 erfolgt nur aufgrund gemessener Speicher- oder
Leistungsergebnisse, nicht vorsorglich.
## 4. Testmatrix
| Stufe | Modellprofil | Kontext | MTP | Zweck |
| --- | --- | ---: | --- | --- |
| A0 | Swift Q6_K, text-only | 65.536 | aus | Modellformat, vollständiger GPU-Offload, API und Grundstabilität |
| A1 | Swift Q6_K, text-only | 131.072 | aus | bevorzugtes Zielprofil und VRAM-Abnahme |
| A2 | Swift Q6_K mit mmproj | 131.072 | aus | optionaler Vision-Betrieb und zusätzlicher VRAM-Bedarf |
| A3 | Swift Q6_K, text-only | 131.072 | `n-max=2` | MTP-A/B gegen A1 |
| A4 | Swift Q6_K, text-only | 131.072 | `n-max=3` | nur nach erfolgreichem A3-Test |
| B1 | Swift Q5_K_M, text-only | 131.072 | aus / 2 | Ressourcen- und Geschwindigkeitsvergleich |
| U0 | Unsloth UD-Q6_K, text-only, `xhigh` | 65.536 | aus | Modellformat, GPU-Offload, API und Grundstabilität |
| U1 | Unsloth UD-Q6_K, text-only, `xhigh` | 131.072 | aus | kontrolliertes Gegenstück zu A1 |
| AB1 | Swift Q6_K gegen Unsloth UD-Q6_K, beide `xhigh` | 131.072 | aus | A/B mit identischen Laufzeitparametern |
| AB2 | Swift Q6_K `xhigh` gegen Unsloth UD-Q6_K `medium` | 131.072 | aus | Vergleich der vorgesehenen Praxisprofile |
Vision und MTP sind eigenständige Varianten. Sie werden nicht gleichzeitig
erstmals aktiviert, weil andernfalls Speicher- und Performanceeffekte nicht
zugeordnet werden können.
## 5. Aufgaben und Messwerte
Die Vergleichssuite enthält 10 bis 20 repräsentative Aufgaben aus dem späteren
Betrieb:
1. kleine, eindeutig prüfbare Codeänderung;
2. repositoryweite Änderung mit mehreren Dateien;
3. Fehlersuche anhand von Logs und Tests;
4. Tool-Aufruf mit korrektem JSON-Schema;
5. mehrstufige Aufgabe mit fehlgeschlagenem ersten Ansatz;
6. lange Kontextaufgabe mit relevanten Informationen an verschiedenen Stellen;
7. Refactoring mit unverändertem Verhalten;
8. Dokumentations- und Konfigurationsänderung;
9. mathematisch-logische Kontrollaufgabe; und
10. Negativtest, bei dem das Modell fehlende Informationen erkennen soll.
Pro Aufgabe und Profil werden soweit praktikabel mindestens drei Läufe
durchgeführt. Für Leistungsmessungen werden zusätzlich deterministische
Prompts mit festem Seed verwendet. Reale Agentenaufgaben behalten die von Qwen
empfohlenen Samplingwerte.
Für AB1 wird die Reihenfolge pro Aufgabe zwischen Swift und Unsloth gewechselt.
Nach jedem Profilwechsel wird ein definierter Warm-up-Request ausgeführt; seine
Zeit fließt nicht in den fachlichen Vergleich ein. Modellladezeit und Warm-up
werden separat erfasst. Container-Digest, Prompt, Systemnachricht, Toolschema,
Seed, Kontext, Sampling und Ausgabegrenze bleiben innerhalb einer Serie
identisch. Die automatischen Prüfungen und die manuelle Bewertungsrubrik werden
vor dem ersten gewerteten Lauf festgelegt.
Jede abgeschlossene Serie erhält im Repository einen datierten
Evaluierungsdatensatz mit Profilmanifest, Aufgabenrevision, Rohmesswerten und
Kurzbericht. Modell- oder Containerdateien sowie sensible Promptinhalte werden
nicht in Git gespeichert. Der Bericht muss erkennen lassen, welche Läufe wegen
technischer Fehler wiederholt oder aus der Wertung genommen wurden.
Zu erfassen sind:
- fachlicher Erfolg und bestandene Tests;
- Zahl der Tool-Aufrufe, Fehlversuche und Wiederholungen;
- Prompt-, Reasoning-, Antwort- und Gesamttoken;
- Time to First Token, Prompt Processing und Generationstempo;
- gesamte Laufzeit bis zum erfolgreichen Abschluss;
- Spitzen- und stabiler VRAM-Verbrauch je GPU;
- CPU- und RAM-Verbrauch;
- GPU-Aufteilung und etwaige CPU-Offloads;
- Abbruch, OOM, fehlerhafte Tool-Formate oder unvollständige Antworten.
## 6. Vorläufige Abnahmekriterien
Ein Kandidat wird zum bevorzugten Production-Profil, wenn:
- das 128k-Profil ohne OOM startet und einen einstündigen gemischten Test
ohne Containerneustart oder GPU-Fehler besteht;
- alle vorgesehenen Modell-Layer auf die GPUs ausgelagert werden;
- beide GPUs genutzt werden und keine Karte ihren verfügbaren VRAM erschöpft;
- API-Antwort, Reasoning-Trennung und Tool Calls dem erwarteten Schema
entsprechen;
- die Aufgabensuite höchstens einen Erfolg weniger als der
jeweils andere Vergleichskandidat erzielt;
- keine systematische Regression bei Mathematik- oder Tool-Aufgaben sichtbar
wird; und
- der Median der generierten Gesamttoken oder der Zeit bis zum erfolgreichen
Abschluss gegenüber dem anderen Profil praktisch relevant sinkt.
Als Zielwert für die Effizienz gilt zunächst eine Reduktion von mindestens
20 Prozent. Die vom Hersteller berichteten 30 bis 50 Prozent sind eine
Hypothese, kein lokales Abnahmekriterium.
MTP wird nur übernommen, wenn es in einer A/B-Messung derselben Sitzung den
Median der Generationsleistung oder End-to-End-Laufzeit um mindestens
10 Prozent verbessert, ohne Stabilität, Tool-Format oder Abschlussquote zu
verschlechtern. Einzelne schnelle Läufe reichen nicht als Nachweis.
## 7. Freigabe und Rollback
Jedes Modellprofil erhält einen stabilen Namen, eine unveränderliche Quelle,
eine SHA-256-Prüfsumme und einen dokumentierten Lizenzstand. Testing und
Production referenzieren dieselbe geprüfte Datei aus dem gemeinsamen
schreibgeschützten Modellbestand.
Ein Upgrade erzeugt ein neues Profil. Das bisherige Modell bleibt bis zum
abgeschlossenen Production-Test erhalten. Der Rollback ändert nur den
versionierten Profilverweis und startet die betroffene Umgebung neu. Ein
unbeaufsichtigter Download von `main`, `latest` oder einer beweglichen
Quantisierungsbezeichnung ist ausgeschlossen.
Swift und Unsloth werden als getrennte Profile installiert. Das Ergebnis kann
ein bevorzugtes Standardprofil und ein weiterhin verfügbares alternatives
Profil sein; beide dürfen nur über den kontrollierten Umgebungswechsel
aktiviert werden.
## 8. Offene Punkte
- Lizenz und tatsächlichen Verwendungsrahmen des Swift-Modells vor Production
bestätigen;
- maximal zulässige Antwortdauer und Ausgabegröße für externe Nutzer
festlegen;
- entscheiden, ob Vision Bestandteil des ersten Production-Profils wird;
- Metrikerfassung für Reasoning- und Antworttoken mit der später gewählten
API-Policy-Schicht abstimmen;
- Aufgabenbestand und erwartete Ergebnisse der lokalen Suite versionieren; und
- nach Veröffentlichung bewerten, ob Swift 1.5 oder eine geprüfte
Swift-Dynamic-Quantisierung als neuer Kandidat aufgenommen wird.
## 9. Quellen
- [Qwen3.8-27B – Modellkarte](https://huggingface.co/Qwen/Qwen3.8-27B)
- [Swift-Qwen3.8-27B – Modellkarte und Benchmarks](https://huggingface.co/ukisai/Swift-Qwen3.8-27b)
- [Swift-Qwen3.8-27B-GGUF – Quantisierungen, KV-Angaben und MTP](https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF)
- [Unsloth Qwen3.8-27B-GGUF – Modellkarte und Dateien](https://huggingface.co/unsloth/Qwen3.8-27B-GGUF)
- [Unsloth UD-Q6_K – Datei und Prüfsumme](https://huggingface.co/unsloth/Qwen3.8-27B-GGUF/blob/4ca720788d1e01f1bff70c033e0d0028fd02e502/Qwen3.8-27B-UD-Q6_K.gguf)
- [llama.cpp Server – aktuelle Parameterreferenz](https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md)
- [llama.cpp MTP-/ROCm-Communitybericht](https://github.com/ggml-org/llama.cpp/discussions/27950)
Community-Berichte dienen ausschließlich der Hypothesenbildung. Freigaben
beruhen auf reproduzierbaren Messungen des gepinnten eigenen Stacks.
@@ -0,0 +1,563 @@
# Technischer Arbeitsentwurf
**Dokumenten-ID:** GNAI-010-TECH
**Titel:** `llama.cpp`-Serving mit ROCm und rootless Podman
**Stand:** 2026-09-21
**Status:** Technische Sollkonfiguration für Testing; noch nicht implementiert
**Referenzen:** [Modellbewertung](GNAI-009-PLAN_Llama-Model-Evaluation.md), [Ansible-Deployment](GNAI-008-PLAN_Ansible-Deployment.md), [Zielarchitektur](GNAI-004-INFRA_Architecture.md), [Ansible-Anleitung](../../../../ansible/README.md)
> Dieses Dokument beschreibt den vorgesehenen technischen Sollzustand. Es ist
> keine Betriebs-SOP und kein Nachweis, dass Modell, Quadlet oder Inferenz
> bereits eingerichtet wurden. Abweichungen aus der Implementierung werden vor
> einer Production-Freigabe hier nachgeführt.
---
## Änderungsverlauf
| Datum | Version | Autor | Beschreibung |
| --- | --- | --- | --- |
| 2026-09-21 | 0.4 | spektr / Codex | Serving von der noch zu entscheidenden externen API-Policy-Schicht entkoppelt |
| 2026-09-21 | 0.3 | spektr / Codex | je Modell ein festes Quadlet vorgesehen; Swift und Unsloth durch kontrollierten Stop/Start gegenseitig ausgeschlossen; Router-Modus zurückgestellt |
| 2026-09-21 | 0.2 | spektr / Codex | Unsloth UD-Q6_K als zweites installiertes Modellprofil und kontrollierten Profilwechsel für A/B-Tests ergänzt |
| 2026-09-21 | 0.1 | spektr / Codex | erstes technisches Profil für Swift Q6_K, 128k, Q8-KV, rootless Quadlet und gestufte MTP-Abnahme erstellt |
---
## 1. Geltungsbereich
Die Spezifikation umfasst zwei feste `llama-server`-Quadlets in der
Testing-Umgebung, von denen immer genau eines aktiv ist. Sie legt fest:
- welche Container- und Modellartefakte verwendet werden;
- wie Modell und Konfiguration gespeichert werden;
- welche Geräte und Netzwerke der Container erhält;
- welche `llama-server`-Parameter als Ausgangsprofil gelten;
- wie VRAM-Annahmen behandelt werden; und
- welche technischen Prüfungen vor der Übernahme nach Production bestehen
müssen.
API-Policy-Schicht, öffentliche Veröffentlichung, Nutzer-SOPs und die zeitliche
Umschaltung zwischen Production und Testing erhalten eigene Rollen und
Dokumente. Ob dafür LiteLLM eingesetzt wird, entscheidet GNAI-001 erst vor der
Veröffentlichung. `llama-server` wird nicht direkt für LAN, VPN oder Internet
veröffentlicht.
## 2. Bereits belegte Voraussetzungen
Am 20.09.2026 wurden auf `ghostnet-ki` folgende Grundlagen nachgewiesen:
- Debian 13.6 und Podman 5.4.2 mit cgroup v2 und `crun`;
- rootless Konto `gnai-testing` mit UID/GID 981 und persistentem
systemd-User-Manager;
- `/dev/kfd`, `/dev/dri/renderD128` und `/dev/dri/renderD129`;
- zwei RX 6900 XT als getrennte ROCm-Geräte und `gfx1030`-Agents; und
- erfolgreicher Start des offiziellen `llama.cpp`-ROCm-Images ohne
`HSA_OVERRIDE_GFX_VERSION`.
Modellladen, 128k-Kontext, Multi-GPU-Layer-Verteilung und Inferenz wurden noch
nicht nachgewiesen.
## 3. Unveränderliche Artefakte
### 3.1 Container
| Feld | Wert |
| --- | --- |
| Upstream | `ghcr.io/ggml-org/llama.cpp:server-rocm` |
| verwendete Referenz | `ghcr.io/ggml-org/llama.cpp@sha256:b429b8be10d3d0353a09c98f4ab840fe516f779858a28f9fd4e5bdf10bc63cb4` |
| Architektur | amd64 |
| Build | `b11058` |
| Revision | `f072b103714dfa1eee531f80b24512faf38e3dd2` |
| Status | ROCm-Geräteerkennung unter `gnai-testing` bestanden |
Der Dienst verwendet ausschließlich den Digest. Ein bewegliches Tag darf in
Testing zur Recherche aufgelöst, aber nicht in ein freigegebenes Profil
übernommen werden.
### 3.2 Swift-Modellprofil
| Feld | Wert |
| --- | --- |
| Repository | `ukisai/Swift-Qwen3.8-27B-GGUF` |
| Repository-Revision | `eb0e3a7dc70643c2ab920f5133750d02c20849ff` |
| Datei | `Swift-Qwen3.8-27B-Q6_K.gguf` |
| veröffentlichte Größe | 22,9 GB |
| SHA-256 | `7f4de8abd5446c08b0f975a1b38e43d02639f4ae9ecdd2ddfd5c5b0f612bda59` |
| Download | `https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/resolve/eb0e3a7dc70643c2ab920f5133750d02c20849ff/Swift-Qwen3.8-27B-Q6_K.gguf` |
| API-Alias | `swift-qwen3.8-27b-q6k` |
Die Prüfsumme stammt aus der vom Herausgeber veröffentlichten
[`SHA256SUMS`](https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/blob/main/SHA256SUMS)-Datei.
Ansible muss die SHA-256-Prüfsumme während des Downloads prüfen. Eine bereits
vorhandene Datei mit abweichender Prüfsumme führt zum Abbruch; sie wird nicht
stillschweigend überschrieben oder verwendet.
### 3.3 Unsloth-Vergleichsprofil
| Feld | Wert |
| --- | --- |
| Repository | `unsloth/Qwen3.8-27B-GGUF` |
| Repository-Revision | `4ca720788d1e01f1bff70c033e0d0028fd02e502` |
| Datei | `Qwen3.8-27B-UD-Q6_K.gguf` |
| veröffentlichte Größe | 22 GB |
| SHA-256 | `c9c206812fbe4ac7b76a729e25928b63f2ae89d37f69da7a71c20aec763cd436` |
| Download | `https://huggingface.co/unsloth/Qwen3.8-27B-GGUF/resolve/4ca720788d1e01f1bff70c033e0d0028fd02e502/Qwen3.8-27B-UD-Q6_K.gguf` |
| API-Alias | `unsloth-qwen3.8-27b-ud-q6k` |
Die SHA-256-Prüfsumme ist in den Xet-Dateimetadaten des Herausgebers
veröffentlicht. Für Download, vorhandene Dateien und Fehlerfälle gelten
dieselben Regeln wie beim Swift-Artefakt. Beide Modelldateien werden
bereitgestellt, bevor die A/B-Serie beginnt.
### 3.4 Optionaler Vision-Projektor
Der erste Text-/Coding-Test lädt keinen Vision-Projektor. Dadurch bleibt rund
ein weiteres GiB für KV-Cache und Laufzeitpuffer verfügbar und die
Grundabnahme erhält weniger Variablen.
Für eine spätere Vision-Stufe ist vorgesehen:
| Feld | Wert |
| --- | --- |
| Datei | `mmproj-Swift-Qwen3.8-27B-F16.gguf` |
| veröffentlichte Größe | 928 MB |
| SHA-256 | `daa1116c9422fa390cc8688495da0e91781f92841dfc3b31a378ff252571745a` |
| Download | `https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/resolve/eb0e3a7dc70643c2ab920f5133750d02c20849ff/mmproj-Swift-Qwen3.8-27B-F16.gguf` |
Der Projektor wird erst heruntergeladen und mit `--mmproj` eingebunden, wenn
die Vision-Teststufe ausdrücklich aktiviert wird.
### 3.5 Lizenz und Herkunft
Das Qwen-Basismodell und das Unsloth-GGUF-Repository stehen laut ihren
Modellkarten unter Apache-2.0. Die Swift-Änderungen werden laut
Modellkarte unter der Swift Open License 1.0 angeboten. Die Modellkarte nennt
eine kostenlose persönliche, Forschungs-, Bildungs-, Evaluierungs- und
kommerzielle Nutzung bis zu einer dort definierten Umsatzgrenze; oberhalb
davon gelten zusätzliche Lizenzbedingungen.
Vor einer externen oder kommerziellen Production-Nutzung wird der dann
aktuelle Lizenztext gegen den konkreten Betreiber- und Nutzungsrahmen geprüft.
Repository-Revision, Modellkarte, Prüfsummen und Lizenzhinweis werden als
Herkunftsnachweis zum Profil dokumentiert.
## 4. Verzeichnis- und Rechtekonzept
Modelle sind große, unveränderliche und nicht geheime Artefakte. Production
und Testing sollen dieselbe geprüfte Datei lesen, ohne sie in getrennten
Podman-Stores zu duplizieren.
Vorgesehene Hostpfade:
```text
/var/lib/ghostnet-ai/shared/models/
├── swift-qwen3.8-27b/
│ ├── Swift-Qwen3.8-27B-Q6_K.gguf
│ ├── mmproj-Swift-Qwen3.8-27B-F16.gguf # erst in der Vision-Stufe
│ └── SOURCE.yml
└── unsloth-qwen3.8-27b/
├── Qwen3.8-27B-UD-Q6_K.gguf
└── SOURCE.yml
```
| Objekt | Eigentümer | Modus | Begründung |
| --- | --- | --- | --- |
| Modellverzeichnisse | `root:root` | `0755` | durch Deployment verwaltet, für beide Umgebungen lesbar |
| GGUF-Dateien | `root:root` | `0644` | unveränderlicher, nicht geheimer gemeinsamer Bestand |
| `SOURCE.yml` | `root:root` | `0644` | nachvollziehbarer Herkunftsnachweis |
Der Container bindet nur das zum aktiven Profil gehörende Modellverzeichnis
als `/models:ro` ein. Er erhält keinen Schreibzugriff auf den gemeinsamen Bestand.
Downloads erfolgen atomar in ein temporäres Ziel und werden erst nach
erfolgreicher Prüfsumme an den endgültigen Pfad verschoben.
Vor dem Download beider Modelle prüft Ansible mindestens 70 GiB freien Platz
auf dem Zieldateisystem. Das deckt rund 45 GB endgültigen Modellbestand, die
größte temporäre Downloaddatei und einen kleinen Betriebspuffer ab. Bereits
korrekt vorhandene Artefakte werden bei der Berechnung nicht erneut als
Downloadbedarf angesetzt.
## 5. Rootless Quadlet
### 5.1 Ablage und Identität
Ansible rendert für jedes Modell ein eigenes, rootverwaltetes Testing-Quadlet:
```text
/etc/containers/systemd/users/981/
├── gnai-testing-llama-swift.container
└── gnai-testing-llama-unsloth.container
```
Sie werden vom persistenten systemd-User-Manager von `gnai-testing`
ausgeführt. Die Units heißen `gnai-testing-llama-swift.service` und
`gnai-testing-llama-unsloth.service`. Beide verwenden dasselbe gepinnte Image,
aber einen eigenen Containernamen, Modellpfad, API-Alias und Parametersatz.
Podman speichert das identische Image nur einmal im rootless Image-Store.
### 5.2 Containeroptionen
Der `[Container]`-Abschnitt muss mindestens folgende Eigenschaften abbilden:
| Quadlet-Eigenschaft | Sollwert |
| --- | --- |
| `Image=` | gepinnter Digest aus Abschnitt 3.1 |
| `Pull=` | `never`; Images werden vor dem Dienststart kontrolliert importiert |
| `AddDevice=` | `/dev/kfd`, `/dev/dri/renderD128`, `/dev/dri/renderD129` |
| `GroupAdd=` | `keep-groups` |
| `Volume=` | Modellverzeichnis nach `/models:ro` |
| `Network=` | dediziertes Testing-Netz für `llama.cpp` und eine spätere API-Policy-Schicht |
| `NetworkAlias=` | `llama-cpp`; wegen gegenseitigem Ausschluss immer nur einmal aktiv |
| `PublishPort=` | nicht gesetzt |
| `ReadOnly=` | nach erfolgreichem Starttest `true` |
| `SecurityLabelDisable=` | nicht gesetzt, solange SELinux auf dem Host nicht aktiv ist |
Podman dokumentiert `AddDevice=` und `GroupAdd=keep-groups` direkt als
Quadlet-Optionen. Freie `PodmanArgs=` werden für diese Funktionen nicht
benötigt. Die Render-Nodes werden weiterhin aus der Hardwareerkennung der
Ansible-Rolle übernommen und nicht unabhängig als zweite Hardwarequelle
konfiguriert.
### 5.3 systemd-Verhalten und gegenseitiger Ausschluss
Vorgesehene Servicewerte:
```ini
[Service]
Restart=on-failure
RestartSec=10
TimeoutStartSec=900
```
Der erhöhte Start-Timeout berücksichtigt Modellinitialisierung und einen
möglichen kontrollierten Erstimport. Das Image und das Modell sollen dennoch
vor dem Dienststart bereitstehen. Ein normaler administrativer Stop darf nicht
durch eine `always`-Policy sofort rückgängig gemacht werden.
Das Testing-Quadlet erhält keinen `[Install]`-Abschnitt und wird nicht für
`default.target` aktiviert. Es startet ausschließlich für ein bewusst
geöffnetes Testfenster. Die spätere Production-Unit kann nach bestandener
Umschalt- und Neustartabnahme gezielt aktiviert werden.
Die Units erhalten folgende negative Abhängigkeit und eine eindeutige
Reihenfolge:
```ini
# Swift-Unit
[Unit]
Conflicts=gnai-testing-llama-unsloth.service
Before=gnai-testing-llama-unsloth.service
# Unsloth-Unit
[Unit]
Conflicts=gnai-testing-llama-swift.service
```
`Conflicts=` sorgt dafür, dass der Start einer Unit die andere stoppt. Das
einmalig gesetzte `Before=` ergänzt die nötige Ordnungsabhängigkeit; systemd
führt dadurch den Stop vollständig vor dem neuen Start aus. Der reguläre
Profilwechsel bleibt zusätzlich eine explizite, sequenzielle Operation:
1. aktuell aktive Modell-Unit stoppen;
2. auf vollständiges Ende des Containers warten;
3. prüfen, dass kein `llama-server` mehr die GPUs belegt;
4. gewünschte Modell-Unit starten; und
5. Alias, Health, Modellrevision und GPU-Belegung prüfen.
Das Ansible-Playbook und die spätere Betriebs-SOP kapseln diese Reihenfolge.
Ein direkter Start der jeweils anderen Unit löst denselben geordneten Wechsel
aus. Der gleichzeitige Start beider Units in einer Transaktion gilt als Fehler
und ist kein unterstützter Betriebsweg.
### 5.4 Entscheidung gegen den Router-Modus in der ersten Ausbaustufe
Der gepinnte `llama.cpp`-Build unterstützt bereits einen Router-Modus mit
`--models-preset`, `--models-max`, automatischem Laden sowie den Endpunkten
`/models/load` und `/models/unload`. Technisch wäre damit ein einzelner
Router-Container für beide Modelle möglich.
Für die erste Abnahme bleibt jedes Quadlet im einfacheren Single-Model-Modus.
Damit sind Startparameter, Logs, Health, VRAM-Verbrauch und Fehlerzustand einem
Modell eindeutig zugeordnet. Außerdem benötigt der interne Dienst keine
dynamische Modellverwaltungs-API. Nach stabiler Abnahme kann der Router-Modus
separat bewertet werden; er ist keine Voraussetzung für eine externe
API-Policy-Schicht.
## 6. `llama-server`-Ausgangsprofil
Das erste 128k-Zielprofil entspricht konzeptionell folgender Kommandozeile:
```text
--host 0.0.0.0
--port 8080
--model /models/Swift-Qwen3.8-27B-Q6_K.gguf
--alias swift-qwen3.8-27b-q6k
--parallel 1
--ctx-size 131072
--n-predict 32768
--n-gpu-layers all
--split-mode layer
--tensor-split 1,1
--flash-attn on
--cache-type-k q8_0
--cache-type-v q8_0
--jinja
--reasoning on
--reasoning-effort xhigh
--reasoning-budget -1
--temp 1.0
--top-p 0.95
--top-k 20
--min-p 0.0
--presence-penalty 0.0
--repeat-penalty 1.0
--no-webui
--metrics
```
Die Parameter werden als strukturierte Ansible-Variablen verwaltet und von
einem Template in `Exec=` gerendert. Eine freie, manuell gepflegte
Environment-Datei ist nicht die Konfigurationsquelle.
Vor der Implementierung wird `llama-server --help` im exakt gepinnten Image
gegen alle Optionen geprüft. Falls dieser Build `all` für
`--n-gpu-layers` nicht akzeptiert, wird der bereits bewährte numerische Wert
`999` verwendet und die Abweichung dokumentiert.
Das Unsloth-Profil verwendet dieselbe Kommandozeile mit diesen gezielten
Abweichungen:
| Parameter | kontrollierter A/B-Test | Praxisvergleich |
| --- | --- | --- |
| `--model` | `/models/Qwen3.8-27B-UD-Q6_K.gguf` | identisch |
| `--alias` | `unsloth-qwen3.8-27b-ud-q6k` | identisch |
| `--reasoning-effort` | `xhigh` | `medium` |
Alle nicht aufgeführten Parameter bleiben gegenüber dem Swift-Profil
unverändert.
### 6.1 Reasoning
`xhigh` ist der Ausgangswert für Swift. `--reasoning-budget -1` vermeidet einen
kurzen serverseitigen Thinking-Cap. `--n-predict 32768` begrenzt dagegen die
gesamte Ausgabe eines einzelnen Requests und schützt den zunächst manuell
betriebenen Testing-Dienst vor unbegrenzten Läufen.
Die Evaluierung enthält zusätzlich einen kontrollierten 65.536-Token-Lauf.
Vor Production wird entschieden, ob die allgemeine Obergrenze erhöht oder
später durch die API-Policy-Schicht pro Nutzer und Modell gesteuert wird. Ein Default von
2.048 oder 4.096 Token ist für dieses Profil nicht vorgesehen.
Die API muss Reasoning getrennt von der finalen Antwort liefern. Zunächst wird
das automatische `--reasoning-format` des gepinnten Builds geprüft. Ein fester
Wert wird erst gesetzt, wenn die Kombination aus `llama.cpp`, der gewählten
API-Policy-Schicht und den
verwendeten Clients mit einem reproduzierbaren Request abgenommen wurde.
Für Unsloth werden zwei Reasoning-Varianten gerendert. `xhigh` dient dem
kontrollierten A/B-Test mit Swift. `medium` bildet das geplante Praxisprofil
ab. Alle übrigen Laufzeitparameter bleiben innerhalb der jeweiligen
Vergleichsserie gleich.
### 6.2 Sampling
Die Werte entsprechen der Qwen-Modellkarte für Thinking. Sie bleiben Teil des
Modellprofils. Ein späteres Instruct-/Non-Thinking-Profil erhält eigene Werte
und überschreibt diese nicht stillschweigend.
### 6.3 Multi-GPU
`layer` und `1,1` sind die Ausgangskonfiguration für die zwei gleich großen
Karten. Der zweite PCIe-Slot ist langsamer angebunden, weshalb experimentelle
parallele Split-Verfahren nicht vor der stabilen Layer-Baseline aktiviert
werden.
Die Startlogs müssen für beide Karten den belegten VRAM und die tatsächliche
Layer-Verteilung ausweisen. `1,1` ist eine Gewichtung, keine Garantie für exakt
gleiche Speicherbelegung. Ein abweichender Split wird ausschließlich anhand
gemessener freier Reserven und Durchsatzwerte eingeführt.
### 6.4 Vision
Der Haupttest verwendet `--model` mit einer lokalen Datei und übergibt kein
`--mmproj`. Erst die Vision-Stufe bindet den Projektor ein. Dadurch werden
Text-/Tool-Qualität, Modellformat und 128k-Speicherbedarf unabhängig vom
Vision-Encoder geprüft.
### 6.5 MTP
MTP bleibt im Ausgangsprofil aus. Die erste Variante ergänzt:
```text
--spec-type draft-mtp
--spec-draft-n-max 2
```
Erst nach bestandener A/B-Messung folgt optional `n-max=3`. Draft-Kontext,
VRAM-Zuwachs, Akzeptanzrate und tatsächlicher End-to-End-Gewinn werden
protokolliert. MTP wird nicht zusammen mit Vision erstmals aktiviert.
## 7. VRAM-Modell
Die Hardware stellt nominell 2 × 16 GiB VRAM bereit. Die Summe allein genügt
nicht als Fit-Nachweis, weil llama.cpp Gewichte, KV-Cache, Rechenpuffer und
gegebenenfalls den Vision- oder Draft-Kontext nicht zwingend gleichmäßig auf
beide Karten verteilt.
### 7.1 Gewichte
Die veröffentlichte Swift-Q6_K-Dateigröße von 22,9 GB entspricht grob
21,3 GiB. Unsloth UD-Q6_K ist mit veröffentlichten 22 GB ähnlich groß. Die
tatsächliche GPU-Belegung kann durch Metadaten, Ausrichtung, mmap und
Backend-Puffer abweichen und wird aus den Startlogs übernommen.
### 7.2 KV-Cache
Die Swift-Modellkarte berechnet für FP16-K/V 64 KiB pro Token, weil nur 16 der
64 Blöcke Full Attention verwenden:
| Kontext | FP16-K/V laut Modellkarte | Q8-K/V, rechnerische Näherung |
| ---: | ---: | ---: |
| 65.536 | 4 GiB | etwa 2 GiB |
| 131.072 | 8 GiB | etwa 4 GiB |
| 196.608 | 12 GiB | etwa 6 GiB |
| 262.144 | 16 GiB | etwa 8 GiB |
Die Q8-Spalte ist eine Halbierungsnäherung und kein gemessener Wert. Block- und
Backend-Overhead sowie die Verteilung je GPU werden beim Modellstart erfasst.
### 7.3 Grobe Zielrechnung
Für das textbasierte 128k-Profil ergibt sich vor der Messung:
```text
Q6_K-Gewichte etwa 21,3 GiB
Q8-K/V bei 128 KiToken etwa 4,0 GiB
ROCm-/Graph-/Compute-Puffer noch zu messen
──────────────────────────────────────────
Zwischensumme vor Puffern etwa 25,3 GiB
```
Mit Vision kommen rund 0,9 GB Dateigröße sowie zusätzliche Laufzeitpuffer
hinzu. MTP kann ebenfalls weiteren Draft-Kontext und Puffer belegen. Deshalb
werden beide Funktionen getrennt zugeschaltet und Q5_K_M bleibt als
Ressourcen-Fallback vorgesehen.
## 8. Netzwerk und API-Grenze
`llama-server` hört im Container auf TCP 8080. Es gibt kein `PublishPort=` und
keine Host-Portweiterleitung. Eine später beschlossene API-Policy-Schicht wird
in dasselbe umgebungsspezifische rootless Podman-Netz aufgenommen und erreicht
den Dienst über `http://llama-cpp:8080`.
Direkte Funktionsprüfungen erfolgen aus dem Container-Netz oder über einen
kurzlebigen administrativen Prüfcontainer. Externe Nutzer sprechen später nur
die freigegebene API-Policy-Schicht beziehungsweise die dafür vorgesehene
veröffentlichte Anwendung an.
Der integrierte Webserver wird mit `--no-webui` betrieben. Metriken und Health
bleiben ausschließlich im internen Container-Netz. Vor der Health-Quadlet-
Konfiguration wird geprüft, welches HTTP-Werkzeug das gepinnte Image enthält;
die Abnahme darf nicht von einem im Image fehlenden `curl` abhängen.
## 9. Ansible-Rollenentwurf
Die spätere Rolle `llama_cpp` soll folgende Zuständigkeiten besitzen:
1. Variablen und Artefaktmanifest vollständig validieren.
2. Modellverzeichnisse mit festgelegten Rechten anlegen.
3. beide Modelle über revisionsgebundene URLs und SHA-256 atomar
bereitstellen.
4. Gepinntes Containerimage im rootless Store der Zielumgebung sicherstellen.
5. Render-Nodes aus `amd_gpu_access` übernehmen.
6. umgebungsspezifisches Podman-Netz als Quadlet rendern.
7. beide `llama.cpp`-Quadlets rendern und Generator-Dry-Run ausführen.
8. systemd-User-Manager neu laden und den ausgewählten Testing-Dienst bewusst
starten.
9. Startlog, API-Health, Modellalias und GPU-Verteilung prüfen.
10. einen kleinen Inferenztest ausführen und Ergebnis protokollieren.
Die Rollenvariablen enthalten eine Map unveränderlicher Modellprofile und
einen einzelnen Selektor wie `llama_cpp_active_profile`. Die Rolle rendert
beide Quadlets, startet aber nur die ausgewählte Unit. Ein Profilwechsel
verändert keine Unit-Datei, sondern stoppt die aktive Unit vollständig und
startet anschließend die bereits gerenderte Ziel-Unit.
Modell-Download und Dienstaktivierung erhalten getrennte Tags. Dadurch kann
ein großes Artefakt vorbereitet werden, ohne den aktiven Stack umzuschalten.
Check-Modus lädt kein Modell und startet keinen Dienst.
Production und Testing verwenden dieselbe Rollenlogik, aber getrennte
Variablen, Quadlets, Netzwerke, Podman-Stores und systemd-User-Manager. Die
Rolle darf nie beide GPU-Umgebungen gleichzeitig starten.
## 10. Technische Abnahme
### 10.1 Vor dem Start
- Modellrevision, Dateiname, Größe und SHA-256 stimmen mit dem Manifest
überein.
- Das Containerimage stimmt mit Digest und OCI-Labels überein.
- Beide Render-Nodes und `/dev/kfd` sind für `gnai-testing` zugreifbar.
- Production ist vollständig beendet und hält keine GPU-Prozesse.
- Der Quadlet-Generator meldet keine Fehler.
- Die generierte Unit enthält keinen veröffentlichten Host-Port.
### 10.2 Start mit 64k
- Container und Unit werden `active` und bleiben stabil.
- Modellformat und eingebettetes Chat-Template werden erkannt.
- alle Layer werden auf ROCm ausgelagert;
- beide GPUs erscheinen in den Startlogs;
- `/health` und `/v1/models` antworten erfolgreich;
- der API-Alias entspricht dem jeweils ausgewählten Profil;
- ein kurzer Thinking-Request liefert Reasoning und finale Antwort; und
- ein definierter Tool-Call ist syntaktisch gültig.
### 10.3 Start mit 128k
- kein OOM oder unbeabsichtigter CPU-Offload;
- freier VRAM je GPU und tatsächlicher KV-Verbrauch werden protokolliert;
- Prompt-Prefill wird stufenweise mit 8k, 32k, 64k und 128k geprüft;
- mindestens ein langer Request endet ohne Kontext- oder Parserfehler; und
- ein einstündiger gemischter Test erzeugt keinen Neustart, GPU-Reset oder
dauerhaft hängenden Slot.
Diese Abnahme wird zuerst für Swift und danach für Unsloth wiederholt. Erst
wenn beide Profile die technische Abnahme bestanden haben, beginnt die
fachliche A/B-Serie aus GNAI-009.
### 10.4 Wiederanlauf und Idempotenz
- ein zweiter identischer Ansible-Lauf meldet `changed=0`;
- ein kontrollierter Containerneustart erhält Profil und Modell;
- ein geplanter Hostneustart startet nur die freigegebene Umgebung;
- ein absichtlich gestoppter Testing-Dienst wird nicht ungewollt neu gestartet;
- ein Startversuch der zweiten Modell-Unit kann niemals beide Modelle
gleichzeitig im VRAM belassen;
- `podman ps`, systemd-Status und Health stimmen überein; und
- der Rückwechsel auf das vorherige Profil funktioniert ohne erneuten
Modelldownload.
## 11. Noch nicht festgelegte Werte
- genaue Health-Implementierung im gepinnten Containerimage;
- CPU-Thread-, Batch- und UBatch-Tuning für den i5-13600K;
- verbindliche maximale Ausgabe für Production und einzelne Nutzer;
- Aufnahme des Vision-Projektors in das erste Production-Profil;
- gemessener optimaler Split bei unterschiedlicher PCIe-Anbindung;
- Aktivierung und Parameter von MTP;
- Aufbewahrungs- und Löschregeln für abgelöste Modellartefakte.
## 12. Quellen
- [Qwen3.8-27B – Architektur, Reasoning und Sampling](https://huggingface.co/Qwen/Qwen3.8-27B)
- [Swift-Qwen3.8-27B – Training und Benchmarks](https://huggingface.co/ukisai/Swift-Qwen3.8-27b)
- [Swift-Qwen3.8-27B-GGUF – Dateien, Quantisierung und MTP](https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF)
- [Swift-GGUF-Prüfsummen](https://huggingface.co/ukisai/Swift-Qwen3.8-27B-GGUF/blob/main/SHA256SUMS)
- [Unsloth Qwen3.8-27B-GGUF – Modellkarte](https://huggingface.co/unsloth/Qwen3.8-27B-GGUF)
- [Unsloth UD-Q6_K – Datei und SHA-256](https://huggingface.co/unsloth/Qwen3.8-27B-GGUF/blob/4ca720788d1e01f1bff70c033e0d0028fd02e502/Qwen3.8-27B-UD-Q6_K.gguf)
- [llama.cpp Server-Parameter und Router-Modus des gepinnten Builds](https://github.com/ggml-org/llama.cpp/blob/f072b103714dfa1eee531f80b24512faf38e3dd2/tools/server/README.md)
- [Podman Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html)
- [systemd-Unit-Abhängigkeiten](https://manpages.debian.org/trixie/systemd/systemd.unit.5.en.html)
+19
View File
@@ -0,0 +1,19 @@
# KI-Dokumentation
Dieser Bereich enthält die fachliche Zielarchitektur und Deployment-Planung für
den KI-Stack auf dem LLM-Server. Gemeinsame Hostgrundlagen liegen im
[Geräteindex](../README.md), übergreifende Netzwerk- und Sicherheitsgrundlagen
in der [Plattformdokumentation](../../README.md). Der CPU-basierte
Video-Encoding-Workload besitzt einen
[eigenen Dokumentationsbereich](../encoding/GNENC-000-DOC_Index.md).
| Dokument | Inhalt |
| --- | --- |
| [GNAI-001](../../../GNAI-001-PLAN_Project-Roadmap.md) | priorisierter High-Level-Implementierungsplan mit Entscheidungstoren und Abnahmekriterien |
| [GNAI-004](GNAI-004-INFRA_Architecture.md) | Zielarchitektur, Komponenten und Datenwege des KI-Stacks |
| [GNAI-008](GNAI-008-PLAN_Ansible-Deployment.md) | nicht finaler Arbeitsentwurf für Ansible- und Gitea-Deployment |
| [GNAI-009](GNAI-009-PLAN_Llama-Model-Evaluation.md) | nicht finaler A/B-Plan für Swift, Unsloth, Reasoning-, Kontext- und MTP-Abnahme |
| [GNAI-010](GNAI-010-TECH_Llama-Serving.md) | technische Sollkonfiguration für zwei geprüfte Modellprofile auf rootless `llama.cpp` mit ROCm |
Der übergreifende Projektstand steht im
[zentralen Dokumentationsindex](../../../GNAI-000-DOC_Index.md).
@@ -0,0 +1,32 @@
# Video-Encoding – Dokumentationsindex und Projektstand
**Dokumenten-ID:** GNENC-000-DOC
**Stand:** 2026-10-05
**Status:** Nicht finaler Arbeitsstand; Encoding-Workload noch nicht installiert
## Dokumente
| Dokument | Zweck / Zielgruppe |
| --- | --- |
| [GNENC-001-PLAN_Architecture.md](GNENC-001-PLAN_Architecture.md) | vorläufige Architektur, Repository-Grenzen, Ressourcenmodell und offene Entscheidungen; Infrastrukturadministratoren |
## Bestätigter Stand
- Das gemeinsame Infrastructure-Repository bleibt die einzige Quelle für den
Ansible-Sollzustand des physischen Servers.
- Encoding-Dokumentation verwendet innerhalb des LLM-Server-Gerätebereichs
`docs/platform/llm-server/encoding/` und die Dokumentenkennung `GNENC`.
- Encoding ist nach Betreiberfestlegung CPU-basiert und darf parallel zur
jeweils aktiven KI-Umgebung laufen.
- Encoding soll ausschließlich außerhalb der Stoßzeiten, zunächst nachts,
automatisch ausgeführt werden.
- Das gesperrte Servicekonto `gn-encoding` mit UID/GID 982 und eigenem
rootless Podman-Store ist eingerichtet und verifiziert.
- Es sind noch kein Encoding-Image, kein Container, keine Quadlet-Unit und kein
Timer eingerichtet.
## Nächste Festlegungen
Vor einer Encoding-Implementierung müssen insbesondere Encoder und
Container-Image, Ein- und Ausgabeverfahren, genaue Zeitfenster, CPU-/I/O-Grenzen,
Abbruchverhalten am Ende des Zeitfensters und Aufbewahrung festgelegt werden.
@@ -0,0 +1,142 @@
# Arbeitsentwurf
**Dokumenten-ID:** GNENC-001-PLAN
**Titel:** CPU-basiertes Video-Encoding auf dem KI-Server
**Stand:** 2026-10-05
**Status:** Nicht finaler Arbeitsentwurf zur Persistierung der Planungsentscheidung
## 1. Zweck und Abgrenzung
Der physische Server `ghostnet-ki` soll neben den KI-Workloads einen getrennten
Workload zum Encoding von Videodateien ausführen. Der Encoding-Workload gehört
nicht zum LLM-Stack. Seine fachliche Architektur, Bedienung und späteren SOPs
werden deshalb in diesem eigenen Dokumentationsbereich geführt.
Ansible, Podman, Betriebssystem und hostweite Ressourcenregeln bleiben
gemeinsame Plattformbestandteile. Sie werden weiterhin ausschließlich im
Infrastructure-Repository verwaltet, damit nicht mehrere Repositories
unabhängig konkurrierende Sollzustände auf denselben Host anwenden.
## 2. Bestätigte Entscheidungen
| Thema | Entscheidung | Nachweisgrenze |
| --- | --- | --- |
| Repository | Host- und Deployment-Ansible bleibt im bestehenden Infrastructure-Repository | angenommen |
| Dokumentation | Encoding liegt unter `docs/platform/llm-server/encoding/` und verwendet die Kennung `GNENC` | umgesetzt |
| Ausführungsidentität | eigenes gesperrtes Konto `gn-encoding`, UID/GID 982 | eingerichtet und mit rootless Podman geprüft |
| Containertrennung | eigener rootless Podman-Store und eigener rootverwalteter Quadlet-Pfad | eingerichtet; noch keine Workload definiert |
| Rechenressource | Encoding ist nach Betreiberfestlegung rein CPU-basiert und erhält keinen GPU-Zugriff | Planungsentscheidung; reale Last noch nicht gemessen |
| Parallelität | darf parallel zu AI Production oder AI Testing laufen | Planungsentscheidung |
| Zeitsteuerung | automatische Ausführung ausschließlich außerhalb der Stoßzeiten, zunächst nachts | Richtung festgelegt; konkrete Uhrzeiten fehlen |
| Anwendungen | konkrete Encoder, Images, Profile und Queue sind noch nicht gewählt | offen |
## 3. Zielbild
```mermaid
flowchart LR
HOST[ghostnet-ki]
PROD[gnai-production\nrootless Podman]
TEST[gnai-testing\nrootless Podman]
ENC[gn-encoding\nrootless Podman]
GPU[beide GPUs]
CPU[CPU / I-O Budget]
TIMER[nächtliche systemd-Zeitsteuerung]
HOST --> PROD
HOST --> TEST
HOST --> ENC
PROD --> GPU
TEST --> GPU
ENC --> CPU
TIMER --> ENC
```
AI Production und AI Testing bleiben untereinander exklusiv. Der
Encoding-Workload ist von dieser KI-Umschaltung unabhängig und darf parallel
zur jeweils aktiven KI-Umgebung laufen. Hostwartung kann weiterhin einen Stopp
aller Workloads erfordern.
## 4. Ressourcen- und Zeitsteuerung
Die nächtliche Einplanung allein schützt Production nicht vollständig: Ein
Encoding-Lauf kann länger dauern als sein Startfenster, und Inferenz benötigt
trotz GPU-Schwerpunkt CPU für Tokenisierung, Netzwerk und Begleitdienste. Die
spätere Encoding-Unit soll deshalb mindestens verwalten:
- ein eindeutig definiertes Zeitfenster mit Zeitzone;
- Verhalten bei verpasstem Start, Neustart und überlangem Lauf;
- maximale CPU-Nutzung oder CPU-Gewichtung;
- niedrigere Scheduling-Priorität gegenüber interaktiven Diensten;
- I/O-Gewichtung und freien Speicher;
- geordneten Abbruch beziehungsweise Abschluss am Ende des erlaubten Fensters;
- Begrenzung auf genau einen gleichzeitigen Encoding-Job.
Konkrete Grenzwerte werden erst nach einem repräsentativen Encoding-Test unter
gleichzeitiger Inferenzlast festgelegt. `gn-encoding` erhält keine Mitgliedschaft
in GPU-Gruppen und keine `/dev/kfd`- oder `/dev/dri`-Weitergabe.
## 5. Repository-Grenzen
Vorgesehene Ansible-Struktur:
```text
ansible/
├── playbooks/
│ ├── host.yml
│ ├── ai.yml
│ ├── video-encoding.yml
│ └── site.yml
└── roles/
├── host_baseline/
├── podman_runtime/
├── workload_accounts/
├── ai_stack/
├── ai_environment_switch/
└── video_encoding/
```
`host.yml` verwaltet ausschließlich gemeinsame Plattformzustände.
`ai.yml` darf nur KI-Zustände verändern. `video-encoding.yml` verwaltet nur das
Konto `gn-encoding`, seine Pfade, Images, Quadlets, Timer und Health Checks. Ein
späteres `site.yml` kann die unterstützte Gesamtkonfiguration in definierter
Reihenfolge anwenden.
Ein separates Quellcode-Repository wird erst benötigt, wenn eigene
Encoding-Software, eine Oberfläche oder ein Queue-Dienst entwickelt wird. Ein
solches Repository erzeugt ein versioniertes Artefakt oder Image; ausschließlich
das Infrastructure-Repository legt fest, welche Version auf dem Server läuft.
## 6. Aktueller Implementierungsstand
Die gemeinsame Rolle `workload_accounts` hat am 20.09.2026 eingerichtet:
```text
Konto: gn-encoding (UID/GID 982)
Konfiguration: /etc/ghostnet-encoding
Daten: /var/lib/ghostnet-encoding
Eingang: /var/lib/ghostnet-encoding/input
Ausgang: /var/lib/ghostnet-encoding/output
Arbeitsdaten: /var/lib/ghostnet-encoding/work
Quadlets: /etc/containers/systemd/users/982
Podman-Store: /var/lib/ghostnet-encoding/.local/share/containers/storage
```
Passwort und interaktive Shell sind gesperrt. Der systemd-User-Manager läuft
dank Linger unabhängig von einer Anmeldung. Der Podman-Store wurde als
rootless und getrennt von beiden KI-Stores geprüft. Es wurden noch keine Images
oder Container angelegt.
## 7. Offene Entscheidungen
1. Welcher Encoder und welches fest versionierte Container-Image werden genutzt?
2. Welche Codecs, Qualitätsprofile und Hardwarekompatibilitäten sind erforderlich?
3. Woher kommen Eingabedateien, und wie werden fertige Dateien abgeholt?
4. Welches konkrete nächtliche Zeitfenster und welche Zeitzone gelten?
5. Darf ein laufender Job das Zeitfenster überschreiten oder wird er gestoppt?
6. Welche CPU-, Speicher- und I/O-Grenzen schützen die KI-Dienste?
7. Wie werden Warteschlange, Wiederholung, Teilresultate und Fehler behandelt?
8. Welche Aufbewahrungs-, Backup- und Löschregeln gelten für Video- und
Arbeitsdateien?
9. Welche Statusanzeige oder Benachrichtigung benötigen Nutzer?
Diese Werte werden nicht vorab in Playbooks angenommen.
-11
View File
@@ -1,11 +0,0 @@
# Proxmox-LXCs
Dieser Bereich dokumentiert ausschließlich die auf Ghostnet Proxmox
`10.0.0.2` betriebenen LXCs. Er beschreibt nicht die SSH-Konfiguration des
Hypervisors selbst.
| Dokument / Bereich | Inhalt |
| --- | --- |
| [GNAI-011](GNAI-011-SEC_LXC-SSH-Architecture.md) | gemeinsame SSH-Sicherheitsarchitektur und Rolloutverfahren der LXCs |
| [CT 103](ct-103/GNAI-006-SEC_Reverse-Proxy.md) | Reverse Proxy, Keycloak-Adminpfad und Fail2Ban |
| [CT 105](ct-105/GNAI-007-SEC_VPN.md) | WireGuard-Gateway, Rollen-Firewall und Clientverfahren |
@@ -4,7 +4,7 @@
**Titel:** Gemeinsame SSH-Sicherheitsarchitektur der LXCs auf Ghostnet Proxmox
**Status:** Legacy-Root-Bestand und Zielbild definiert; CT-103-SSH-Rollout einschließlich Neustart abgenommen
**Geltungsbereich:** LXCs auf Proxmox `10.0.0.2`; nicht der Proxmox-Host selbst
**Referenzen:** [KI-Host-SSH](../ghostnet-ai/GNAI-003-INFRA_Users.md), [Zugriffskonzept](../GNAI-005-SEC_Access-Control.md), [CT 103](ct-103/GNAI-006-SEC_Reverse-Proxy.md), [CT 105](ct-105/GNAI-007-SEC_VPN.md)
**Referenzen:** [KI-Host-SSH](../llm-server/host/GNAI-003-INFRA_Users.md), [Zugriffskonzept](../GNAI-005-SEC_Access-Control.md), [CT 103](lxc/ct-103-reverse-proxy/GNAI-006-SEC_Reverse-Proxy.md), [CT 105](lxc/ct-105-wireguard/GNAI-007-SEC_VPN.md)
## Änderungsverlauf
@@ -39,7 +39,7 @@ Verfahren.
| CT 103 `10.0.0.10` | Reverse Proxy, NPM und Fail2Ban; öffentliches HTTP(S)-Ingress | `ghost` als persönlicher Administrator und `ghost-agent` als unprivilegiertes Lesekonto; Root- und Passwort-SSH deaktiviert; Proxmox-Konsole bleibt Recovery-Weg |
| CT 105 `10.0.0.15` | WireGuard-Gateway und wg-easy | verifizierte Ausnahme: `admin` UID 1000, Key-Login und sudo/`NOPASSWD`; vollständige Baseline noch abnehmen |
| weitere Legacy-LXCs | unterschiedliche Plattformdienste | laut Betreiber nur `root`; erst nach Inventaraufnahme einzeln migrieren, keine ungeprüfte Massenänderung |
| KI-Host `10.0.0.90` | physischer KI-Server | eigene Dokumentation unter `platform/ghostnet-ai/` |
| KI-Host `10.0.0.90` | physischer KI-Server | eigene Dokumentation unter `platform/llm-server/` |
| Proxmox `10.0.0.2` | Hypervisor und Recovery-Konsole | ausdrücklich außerhalb dieses Dokuments |
Öffentliche Anwendungsports eines LXC begründen keine öffentliche
@@ -132,7 +132,8 @@ LoginGraceTime 30
Abweichungen benötigen einen dokumentierten Anwendungsfall. Insbesondere
Port-, Agent- oder X11-Forwarding wird nicht nur aus Bequemlichkeit aktiviert.
Änderungen werden mit `sshd -t` geprüft. Vor der Aktivierung ist zu prüfen, ob
`ssh.socket` aktiv ist. Auf CT 103 schlug `systemctl reload ssh.service` fehl,
`ssh.socket` aktiv ist. Auf CT 103 und CT 105 schlug
`systemctl reload ssh.service` fehl,
weil der per Socket-Aktivierung gestartete Daemon beim SIGHUP Port 22 erneut
binden wollte. `ssh.socket` startete den Dienst beim nächsten Zugriff wieder
korrekt. Dort künftig nach erfolgreichem Syntaxcheck und bei offener
+12
View File
@@ -0,0 +1,12 @@
# Proxmox-Server und LXCs
Dieser Gerätebereich dokumentiert den Proxmox-Kontext unter `10.0.0.2` und die
darauf betriebenen LXCs. Er beschreibt derzeit nicht die SSH-Konfiguration des
Hypervisors selbst.
| Dokument / Bereich | Inhalt |
| --- | --- |
| [GNAI-011](GNAI-011-SEC_LXC-SSH-Architecture.md) | gemeinsame SSH-Sicherheitsarchitektur und Rolloutverfahren der LXCs |
| [GNAI-012 / CT 100](lxc/ct-100-dns/GNAI-012-PLAN_Pi-hole-DNS-Handoff.md) | `10.0.0.14`: Pi-hole-Quadlet, FRITZ!Box-DNS, Split-DNS, Abnahme und Rollback |
| [GNAI-006 / CT 103](lxc/ct-103-reverse-proxy/GNAI-006-SEC_Reverse-Proxy.md) | `10.0.0.10`: Reverse Proxy, Keycloak-Adminpfad und Fail2Ban |
| [GNAI-007 / CT 105](lxc/ct-105-wireguard/GNAI-007-SEC_VPN.md) | `10.0.0.15`: WireGuard-Gateway, Rollen-Firewall und Clientverfahren |
@@ -0,0 +1,439 @@
# Pi-hole und internes Split-DNS – Übergabeauftrag
**Dokumenten-ID:** GNAI-012-PLAN
**Titel:** Pi-hole-LXC, FRITZ!Box-DNS und Split-DNS für interne Dienste
**Status:** Implementiert und im LAN produktiv; klar ausgewiesene Folgepunkte für WireGuard, mobile DNS-Umgehung und NPM/Keycloak
**Stand:** 01.10.2026
**Referenzen:** [Projekt-Roadmap](../../../../GNAI-001-PLAN_Project-Roadmap.md), [Zugriffskonzept](../../../GNAI-005-SEC_Access-Control.md), [LXC-SSH-Architektur](../../GNAI-011-SEC_LXC-SSH-Architecture.md), [Reverse Proxy](../ct-103-reverse-proxy/GNAI-006-SEC_Reverse-Proxy.md), [WireGuard](../ct-105-wireguard/GNAI-007-SEC_VPN.md)
## 1. Auftrag und Umsetzungsergebnis
> GNAI-012 wurde am 01.10.2026 als abgegrenztes Infrastrukturvorhaben
> umgesetzt. Grundlage war der folgende Auftrag: Lies zuerst
> `AGENTS.md`, `.codex/LOCAL_MEMORY.md`, den zentralen Dokumentationsindex und
> alle oben referenzierten Fachdokumente. Prüfe den Live-Zustand, bevor du
> Änderungen vornimmst. Richte einen dedizierten, unprivilegierten
> Pi-hole-LXC ein, härte ihn nach GNAI-011, teste DNS und Blocking zunächst
> ohne Netz-Cutover und verteile ihn erst nach bestandener Abnahme über die
> FRITZ!Box an das LAN. Implementiere den lokalen DNS-Eintrag
> `auth-admin.ghostnet.selfhost.eu -> 10.0.0.10`. Verändere in diesem Auftrag
> weder NPM/CT 103 noch Keycloak/`10.0.0.4` und sperre den öffentlichen
> `/admin`-Pfad noch nicht. Dokumentiere Versionen, Konfiguration, Tests,
> Backup und Rollback ohne Passwörter, Schlüssel oder vollständige
> WireGuard-Profile. Beende den Auftrag mit dem Übergabebericht aus Abschnitt
> 10, damit die ursprüngliche Sitzung die Keycloak-/NPM-Arbeit fortsetzen kann.
Die Umsetzung wich nach Betreiberentscheidung in einem wesentlichen Punkt vom
ursprünglichen Soll ab: Pi-hole läuft nicht nativ im LXC, sondern als
digest-gepinnter rootful Podman-Container unter einer Quadlet-Unit. Das folgt
dem Muster der anderen modernen Ghostnet-LXCs. Der dafür notwendige
unprivilegierte LXC besitzt `nesting=1,keyctl=1`. Diese Abweichung wurde durch
Host-Netzwerkbetrieb, minimale Containerrechte, lokalen Paketfilter und
Reboot-/Backup-Tests abgenommen.
## 2. Anlass und bestätigter Ausgangszustand
Die laufende Sicherheitsarbeit soll den öffentlich erreichbaren
Keycloak-Adminpfad von den öffentlichen OIDC-Endpunkten trennen. Dafür ist ein
interner Name vorgesehen:
```text
auth-admin.ghostnet.selfhost.eu -> 10.0.0.10
```
`10.0.0.10` ist CT 103 mit Nginx Proxy Manager. Keycloak selbst läuft hinter
NPM auf `10.0.0.4:8080`. Der lokale DNS-Eintrag soll später einen getrennten
internen NPM-Host ermöglichen; er zeigt deshalb **nicht** direkt auf
`10.0.0.4`.
Bestätigter Netzstand:
| Komponente | Adresse / Stand |
| --- | --- |
| FRITZ!Box, Gateway und bisheriger DNS | `10.0.0.1` |
| Proxmox | `10.0.0.2` |
| Keycloak-Backend | `10.0.0.4:8080` |
| CT 103 / NPM | `10.0.0.10` |
| CT 105 / WireGuard | `10.0.0.15`, VPN-Netz `10.8.0.0/24`, LAN-seitig MASQUERADE |
| KI-Server | `10.0.0.90` |
| Heimnetz | `10.0.0.0/24` |
| geplanter statischer Bereich | `.2` bis `.99` |
| bisheriger DHCP-Clientbereich | `.100` bis `.200` |
| CT 100 / Pi-hole | `10.0.0.14`, Hostname `dns`; seit 01.10.2026 produktiver lokaler DNS |
Am 24.09.2026 wurde bestätigt:
- `auth.ghostnet.selfhost.eu` löst im LAN öffentlich auf und erreicht NPM über
Hairpin-NAT; NPM sah dabei die öffentliche WAN-Adresse als Clientquelle.
- Ein gezielter interner Aufruf desselben Hostnamens gegen `10.0.0.10` war mit
gültigem TLS erfolgreich und erschien bei NPM mit der echten LAN-Adresse.
- Der direkte Keycloak-Aufruf `10.0.0.4:8080/admin` leitet auf den öffentlichen
Auth-Host zurück. DNS allein ersetzt daher nicht die spätere
Keycloak-`hostname-admin`-/NPM-Konfiguration.
- CT 103 besitzt einen am selben Tag technisch geprüften Stop-Mode-vzdump und
ist nach Neustart vollständig wieder angelaufen. Details stehen in GNAI-006.
## 3. Verbindlicher Umfang
### 3.1 In diesem Auftrag
- freien CT-Identifier und die freie statische Adresse bestätigen;
- dedizierten, unprivilegierten Debian-LXC für Pi-hole erstellen;
- LXC-Basis, Updates, lokalen Paketfilter und SSH gemäß GNAI-011 härten;
- Pi-hole als digest-gepinntes offizielles Image über rootful Podman und
Quadlet betreiben und die konkreten Versionen festhalten;
- FRITZ!Box als DHCP-Server beibehalten; Pi-hole-DHCP nicht aktivieren;
- geeigneten Upstream-DNS ohne Resolver-Schleife konfigurieren;
- Blocking mit einer kleinen, nachvollziehbaren Ausgangsliste testen;
- lokalen DNS-Eintrag
`auth-admin.ghostnet.selfhost.eu -> 10.0.0.10` einrichten;
- DNS über UDP und TCP, Weboberfläche, Updates und Neustart abnehmen;
- IPv4- und IPv6-DNS-Pfade prüfen und DNS-Bypass dokumentieren;
- Pi-hole erst nach Vorabtests als lokalen DNS-Server über die FRITZ!Box
verteilen;
- mindestens einen LAN-Client nach neuer DHCP-Lease regressionsprüfen;
- WireGuard-Erreichbarkeit des DNS-Dienstes prüfen oder als exakt begründeten
Folgepunkt ausweisen;
- konsistenten Proxmox-Rückfallstand und Rückstellverfahren dokumentieren.
### 3.2 Ausdrücklich nicht in diesem Auftrag
- keine Änderung an NPM, CT 103 oder dessen Proxy-Host 7;
- keine Änderung an Keycloak, `hostname` oder `hostname-admin`;
- den öffentlichen Pfad `auth.ghostnet.selfhost.eu/admin` noch nicht sperren;
- keine VPN-Rollenarchitektur oder Per-Client-Firewall neu entwerfen;
- Pi-hole nicht als DHCP-Server aktivieren;
- keine erzwungene netzweite Sperre externer DNS-/DoH-Ziele;
- keine großen Community-Blocklisten ohne begründete Auswahl einführen;
- keine geheimen Werte, Passwörter, privaten Schlüssel oder vollständigen
VPN-Profile in Git, Laufprotokolle oder dieses Dokument schreiben.
## 4. Zielarchitektur und Entscheidungen
```text
LAN-/WireGuard-Client
|
| DNS TCP/UDP 53
v
Pi-hole-LXC CT 100 / 10.0.0.14
|-- lokaler Record: auth-admin.ghostnet.selfhost.eu -> 10.0.0.10
|-- Blocklisten / Cache
`-- Upstream-DNS für alle übrigen Namen
FRITZ!Box 10.0.0.1
`-- DHCP bleibt aktiv und verteilt Pi-hole als lokalen DNS
```
Verbindliche Entscheidungen:
1. Es wird **ein** Pi-hole-LXC verwendet; kein zusätzlicher reiner DNS-LXC.
2. Pi-hole wird als rootful Podman-Quadlet in einem dedizierten,
unprivilegierten LXC betrieben, nicht auf CT 103, CT 105 oder dem KI-Host.
Das Image ist über Version und Digest festgeschrieben; Podman-Socket und
automatisches Image-Update bleiben deaktiviert.
3. `.local` wird nicht als Unicast-DNS-Zone verwendet, weil diese Endung für
mDNS reserviert ist. Der projektspezifische Split-DNS-Eintrag nutzt die
bereits verwendete öffentliche Domain.
4. Die FRITZ!Box bleibt DHCP-Server. Pi-hole wird über die Einstellung
**Lokaler DNS-Server** an Clients verteilt, nicht ungeprüft als WAN-Upstream
der FRITZ!Box eingetragen.
5. Pi-hole darf nicht die FRITZ!Box als Upstream verwenden, wenn umgekehrt die
FRITZ!Box Pi-hole als WAN-Upstream nutzt; diese Schleife ist zu verhindern.
6. Ein öffentlicher Ersatzresolver darf Clients nicht als scheinbar sichere
zweite DHCP-DNS-Adresse angeboten werden, weil Clients ihn unabhängig
nutzen und damit Blocking sowie Split-DNS umgehen können.
7. Das erste Ziel ist ein kontrollierter Einzelresolver. Das verbleibende
Single-Point-of-Failure-Risiko wird dokumentiert; ein zweiter Pi-hole ist
eine spätere Verfügbarkeitsentscheidung.
8. DNS-Erreichbarkeit ist keine Anwendungsautorisierung. NPM und Keycloak
erzwingen den internen Adminzugang erst im anschließenden Hauptvorhaben.
## 5. Mindestressourcen und Sicherheitsbaseline
Realisierter Stand nach Live-Inventur:
| Parameter | Vorschlag |
| --- | --- |
| Hostname | `dns` |
| IPv4 | `10.0.0.14/24`, Gateway `10.0.0.1` |
| LXC-ID | `100` |
| Betriebssystem | Debian 13.7 aus `debian-13-standard_13.1-2_amd64.tar.zst` |
| Privilegierung | unprivilegierter LXC |
| CPU / RAM / Swap | 1 vCPU / 1024 MiB / 512 MiB |
| RootFS / Storage | 8 GiB auf `local-lvm` |
| Start | `onboot=1`, sinnvolle Startreihenfolge nach Netzwerk |
| Features | `nesting=1,keyctl=1` für den gewählten Podman-Betrieb |
Lokaler Paketfilter:
- INPUT standardmäßig `drop`;
- established/related, Loopback und benötigtes ICMP zulassen;
- TCP/UDP 53 nur aus `10.0.0.0/24`; damit ist auch der von CT 105 maskierte
WireGuard-Verkehr mit Quelle `10.0.0.15` umfasst;
- Pi-hole-Weboberfläche nur aus LAN/WireGuard;
- SSH gemäß Betreiberentscheidung aus LAN/WireGuard, nicht öffentlich;
- keine Portweiterleitung an der FRITZ!Box für DNS oder Pi-hole-Webzugang;
- OUTPUT zunächst zulassen und benötigte Upstream-/Updatepfade dokumentieren.
SSH-Konten nach GNAI-011:
- persönliches Konto `ghost` mit eigenem passphrasegeschütztem Ed25519-Key und
passwortgeschütztem sudo;
- `ghost-agent` mit eigenem Schlüssel, gesperrtem Passwort, ohne Zusatzgruppen
und ohne sudo;
- Root-, Passwort- und Keyboard-Interactive-SSH deaktivieren;
- individuelle Schlüssel pro LXC, keine Wiederverwendung von CT 103, CT 105
oder KI-Host;
- Hostschlüssel vor dem ersten vertrauenswürdigen Login über die
Proxmox-Konsole bestätigen;
- Proxmox-Konsole bis nach Positiv- und Negativtests offen halten;
- Neustart und erneute Anmeldung beider vorgesehenen Konten abnehmen.
## 6. Umsetzungsphasen
### Phase A – Live-Inventur und Freigabe
Mindestens erfassen:
```bash
pct list
qm list
pveam list local
ip -4 neigh show dev vmbr0
```
Zusätzlich wurde vor der Erstellung geprüft, dass `10.0.0.14` nicht anderweitig
verwendet oder reserviert war. Ein fehlender Ping allein hätte keine freie
Adresse bewiesen. CT-ID, Adresse, Template, Storage und Ressourcen wurden mit
dem Betreiber festgelegt.
Vorhandene Proxmox-Firewall-, Backup- und Startreihenfolgen erfassen. Keine
Massenänderung an anderen LXCs vornehmen.
### Phase B – LXC und Betriebssystem
1. Unprivilegierten LXC mit statischer Adresse und `onboot=1` erstellen.
2. Hostname, Adresse, Gateway, Resolver und Zeitsynchronisation prüfen.
3. Basispakete aktualisieren und automatisches Sicherheitsupdateverfahren
festlegen.
4. nftables-Baseline anwenden und persistent testen.
5. Persönlichen und lesenden SSH-Zugang nach Abschnitt 5 einrichten.
6. Root-/Passwort-SSH erst nach erfolgreicher zweiter Sitzung abschalten.
7. Ausgangszustand und alle Abweichungen dokumentieren.
### Phase C – Pi-hole ohne Netz-Cutover
1. Aktuelle Pi-hole-Container-Version ausschließlich aus offiziellen Quellen
bestätigen.
2. Offizielles Image über Version und Multi-Arch-Digest pinnen und als
rootful Quadlet unter `/etc/containers/systemd/pihole.container` betreiben.
Die persistente Konfiguration liegt unter `/opt/pihole/etc-pihole`; das
Webpasswort wird ausschließlich als Podman-Secret eingebunden.
3. Statische LXC-Adresse als Pi-hole-Adresse konfigurieren.
4. Pi-hole-DHCP ausgeschaltet lassen.
5. Upstream-Resolver bewusst festlegen. Wenn die FRITZ!Box als Upstream dient,
darf deren WAN-DNS nicht gleichzeitig zurück auf Pi-hole zeigen.
6. Zunächst nur die offizielle Standard-/Ausgangsliste verwenden. Zusätzliche
Listen benötigen einen Zweck, eine Quelle und einen False-Positive-Test.
7. Webpasswort interaktiv setzen; niemals in Dokumentation oder Befehlsausgabe
übernehmen.
8. Logging und Aufbewahrung so konfigurieren, dass Diagnose möglich bleibt,
ohne unnötig lange personenbezogene DNS-Verläufe zu speichern. Entscheidung
dokumentieren.
### Phase D – Split-DNS
Den lokalen A-Record über die Pi-hole-Oberfläche oder die zur installierten
Version passende offizielle Konfigurationsschnittstelle einrichten:
```text
auth-admin.ghostnet.selfhost.eu A 10.0.0.10
```
Vor dem FRITZ!Box-Cutover direkt gegen Pi-hole testen:
```bash
dig @10.0.0.14 auth-admin.ghostnet.selfhost.eu A +short
dig @10.0.0.14 auth-admin.ghostnet.selfhost.eu A +tcp +short
dig @10.0.0.14 auth.ghostnet.selfhost.eu A +short
dig @10.0.0.14 example.com A +short
```
Erwartung:
- der Adminname liefert ausschließlich `10.0.0.10`;
- der öffentliche Auth-Name bleibt normale Upstream-Auflösung und wird nicht
versehentlich überschrieben;
- externe Namen funktionieren über UDP und TCP;
- NXDOMAIN, Cache und Blockantworten verhalten sich nachvollziehbar.
### Phase E – Dienst- und Sicherheitstests vor Cutover
- `pihole-FTL` aktiv und beim Boot aktiviert;
- TCP und UDP 53 lauschen nur wie vorgesehen;
- Weboberfläche aus LAN erreichbar und aus öffentlichen Netzen nicht
veröffentlicht;
- eine nachweislich in der aktiven Liste enthaltene Testdomain wird blockiert;
- bewusst erlaubte Projektdomains, OIDC, Vaultwarden, Nextcloud, Gitea und
Updatequellen lösen weiterhin auf;
- lokale Recordänderung über Neustart hinweg persistent;
- DNS-Anfragen aus einem nicht erlaubten Quellnetz werden verworfen;
- LXC-Neustart, DNS-Wiederkehr und SSH-Wiederanmeldung erfolgreich;
- keine fehlgeschlagenen systemd-Units und keine unkontrolliert wachsenden Logs.
### Phase F – FRITZ!Box-Cutover
Erst nach bestandenen Phasen A bis E:
1. bisherigen FRITZ!Box-DNS-Zustand und Rückstellwert dokumentieren;
2. unter
`Heimnetz > Netzwerk > Netzwerkeinstellungen > IP-Adressen >
IPv4-Konfiguration > Heimnetz` Pi-hole als **Lokalen DNS-Server** eintragen;
3. nicht gleichzeitig unkoordiniert die WAN-DNS-Einstellung unter
`Internet > Zugangsdaten > DNS-Server` ändern;
4. DHCP-Lease zunächst nur auf einem Testclient erneuern;
5. am Client prüfen, dass ausschließlich der beabsichtigte Resolver genutzt
wird und `auth-admin...` auf `10.0.0.10` zeigt;
6. normales Browsing, Softwareupdates und die bekannten Ghostnet-Dienste
regressionsprüfen;
7. erst danach weitere Clients ihre Lease erneuern lassen.
### Phase G – IPv6 und WireGuard
IPv6 darf nicht stillschweigend an Pi-hole vorbeiführen. Vor Abschluss:
- tatsächliche IPv6-Nutzung und von der FRITZ!Box angekündigte DNSv6-Server auf
mindestens Linux und einem weiteren relevanten Client erfassen;
- entweder eine stabile Pi-hole-ULA nach der offiziellen FRITZ!Box-Anleitung
verteilen und testen oder IPv6-DNS-Blocking ausdrücklich als offenen Befund
dokumentieren;
- keine link-lokale IPv6-Adresse als segmentübergreifenden WireGuard-DNS
verwenden;
- für WireGuard prüfen, ob `10.0.0.14` in Client-Routen und Gateway-Regeln
erreichbar ist;
- Änderungen an produktiven WireGuard-Profilen nur mit gesicherter
Rückfallkopie und gemäß GNAI-007 vornehmen;
- wenn die Profilumstellung bewusst vertagt wird, muss die Übergabe exakt
nennen, welche Rollen/Profile noch keinen Pi-hole-DNS verwenden.
## 7. Abnahmekriterien
- [x] CT-ID, Adresse und Template sind live bestätigt und dokumentiert.
- [x] Pi-hole läuft in einem dedizierten unprivilegierten LXC mit statischer IP.
- [x] LXC-Paketfilter erlaubt DNS, Web und SSH nur aus LAN/WireGuard.
- [x] Persönlicher und lesender SSH-Zugang entsprechen GNAI-011.
- [x] Root-/Passwort-SSH sind deaktiviert und Negativtests bestanden.
- [x] Pi-hole-Version, Installationsquelle, Upstream und Blocklisten sind erfasst.
- [x] Pi-hole-DHCP ist deaktiviert; FRITZ!Box bleibt DHCP-Server.
- [x] UDP- und TCP-DNS funktionieren direkt gegen Pi-hole.
- [x] `auth-admin.ghostnet.selfhost.eu` liefert intern `10.0.0.10`.
- [x] `auth.ghostnet.selfhost.eu` bleibt unverändert öffentlich auflösbar.
- [x] Blocking und eine kontrollierte Freigabe wurden positiv getestet.
- [x] LXC-Neustart sowie DNS-, Web- und SSH-Wiederanlauf sind bestanden.
- [x] FRITZ!Box verteilt Pi-hole zunächst erfolgreich an einen Testclient.
- [x] DHCP-Erneuerung und tatsächliche Resolverauswahl sind belegt.
- [x] IPv6-DNS ist wirksam über Pi-hole geführt oder als offener Bypass klar
dokumentiert; ein stiller Mischzustand gilt nicht als bestanden.
- [x] WireGuard-DNS-Erreichbarkeit ist bestanden oder mit betroffenen Profilen
als Folgearbeit ausgewiesen.
- [ ] Öffentliche Ghostnet-Dienste und OIDC funktionieren unverändert.
- [ ] Rückstellung auf den vorherigen FRITZ!Box-DNS wurde beschrieben und
mindestens auf einem Testclient nachvollzogen.
- [x] Ein aktuelles Proxmox-Backup des fertigen DNS-LXC ist technisch geprüft.
- [x] Keine Secrets oder privaten Schlüssel wurden dokumentiert oder committed.
## 8. Rollback
Bei DNS-Ausfall oder Fehlfilterung:
1. In der FRITZ!Box den **Lokalen DNS-Server** von `10.0.0.14` auf den
wirksamen Ausgangswert `10.0.0.1` zurückstellen.
2. Testclient vom Netz trennen/verbinden oder DHCP-Lease erneuern.
3. Resolverauswahl und normale öffentliche Auflösung bestätigen.
4. Pi-hole aus dem Clientpfad nehmen, aber für Diagnose eingeschaltet lassen,
sofern kein Sicherheitsproblem vorliegt.
5. Fehlgeschlagene Änderung anhand Pi-hole-Query-Log, FTL-Log, nftables und
Upstreamtests untersuchen.
6. Nur falls Konfiguration nicht sicher korrigierbar ist, den geprüften
Proxmox-Rückfallstand verwenden. Restore nie über den laufenden LXC hinweg
improvisieren; Ziel-ID, Storage und Netzwerk vorher kontrollieren.
Die Rückstellung darf nicht davon abhängen, dass Pi-hole selbst noch DNS
liefert. FRITZ!Box-Adresse, PVE-Konsole und IP-basierter Zugang müssen deshalb
vorher dokumentiert und erreichbar bleiben.
## 9. Dokumentationspflichten des Folgeagenten
Der Folgeagent aktualisiert mindestens:
- dieses Dokument mit tatsächlicher CT-ID, Adresse, Versionen, Datum und
Abnahmeergebnissen;
- den [zentralen Index](../../../../GNAI-000-DOC_Index.md);
- den [Proxmox-LXC-Index](../../README.md);
- GNAI-007, falls WireGuard-DNS oder Profile verändert wurden;
- `.codex/LOCAL_MEMORY.md` nur mit nicht geheimen, datierten Betriebsfakten.
Neue Detaildokumente nur anlegen, wenn sie einen dauerhaften Betriebszweck
haben. Keine langen Konsolenmitschnitte duplizieren; Ergebnisse und relevante
Fehlerursachen zusammenfassen.
## 10. Verbindlicher Übergabebericht zurück an die Hauptsitzung
Nach Abschluss einen kompakten Bericht mit diesen Punkten liefern:
| Punkt | erforderliche Angabe |
| --- | --- |
| LXC | CT-ID, Hostname, IPv4/IPv6, Template, Ressourcen, Storage |
| Pi-hole | Version, Installationsart, Upstream, aktive Listen, DHCP-Status |
| Sicherheit | Konten, SSH-Abnahme, Firewallports, Updateverfahren |
| Split-DNS | Nachweis für `auth-admin... -> 10.0.0.10` über UDP und TCP |
| FRITZ!Box | geänderte Felder, Ausgangswerte, Cutover-Zeitpunkt |
| Clients | getestete Betriebssysteme, DHCP-/DNS-Auswahl, IPv6-Ergebnis |
| WireGuard | getestete Rollen/Profile oder präzise verbleibende Arbeit |
| Regression | öffentliche Ghostnet-Dienste und OIDC |
| Backup | Archivpfad, Größe, Integritätstest, Prüfsumme; keine Secrets |
| Rollback | praktisch geprüfter Client-Rückweg und verbleibende Risiken |
### 10.1 Umsetzungs- und Übergabebericht vom 01.10.2026
| Punkt | Ist-Stand und Nachweis |
| --- | --- |
| LXC | CT `100`, Hostname `dns`, unprivilegierter Debian-13-LXC, `10.0.0.14/24`, Gateway `10.0.0.1`, kein konfiguriertes IPv6; 1 vCPU, 1024 MiB RAM, 512 MiB Swap, 8 GiB `local-lvm`; Template `debian-13-standard_13.1-2_amd64.tar.zst` |
| Pi-hole | Offizielles Image `ghcr.io/pi-hole/pihole:2026.09.0` mit Multi-Arch-Digest `sha256:5b9c8cf51de7d6d3f2240dbe72baf5f06e1fd39cb4d77a99fab4fa13e23bd1be`; Core `v6.4.3`, Web `v6.6`, FTL `v6.7.1`; rootful Podman `5.4.2`, Host-Netzwerk, Quadlet-Autostart; Upstream `9.9.9.9` und `149.112.112.112`; 74.761 Gravity-Domains; DHCP und NTP deaktiviert; Abfrageaufbewahrung 30 Tage |
| Persistenz und Secret | `/opt/pihole/etc-pihole` persistent und durch `/opt/pihole` Modus `0700` geschützt; Webpasswort als Podman-Secret `pihole_web_password`, nicht in Unit oder Dokumentation; Weblogin nach Restart bestätigt |
| Sicherheit | `ghost` mit eigenem passphrasegeschütztem Ed25519-Schlüssel und passwortgeschütztem sudo; `ghost-agent` ohne sudo und ohne Zusatzgruppen; Root-, Passwort- und Keyboard-Interactive-SSH sowie Forwarding deaktiviert; nftables INPUT-Drop erlaubt aus `10.0.0.0/24` TCP 22/53/80/443 und UDP 53; DHCP-/NTP-Ports lauschen nicht; `unattended-upgrades` aktiv; Podman-Socket und Auto-Update-Timer deaktiviert |
| Split-DNS | `auth-admin.ghostnet.selfhost.eu` liefert über UDP und TCP ausschließlich `10.0.0.10`; öffentliche `auth.ghostnet.selfhost.eu`-Auflösung blieb erhalten. HTTPS gegen den neuen Adminnamen scheitert erwartungsgemäß noch, weil NPM/Keycloak in diesem Auftrag nicht verändert wurden. |
| Blocking | `techques.com` aus der aktiven Gravity-Liste lieferte A `0.0.0.0`, AAAA `::` und über TCP EDE 15 `Blocked (gravity)`. Ein temporärer exakter Allowlist-Eintrag lieferte die reale IPv4; nach Entfernung und Client-Cache-Flush griff Blocking erneut. |
| Diensttests | Quadlet-Unit nach Container-Restart, vollständigem LXC-Reboot und Stop-Mode-Backup automatisch `active`; DNS, Split-DNS, Blocking, HTTP/HTTPS, SSH und `systemctl --failed` anschließend erneut geprüft. Pi-hole lauscht auf TCP/UDP 53 und TCP 80/443; die von `podman ps` angezeigten Ports 67/123 sind nur Image-Metadaten und keine Listener. |
| FRITZ!Box | Effektiver Ausgangsresolver `10.0.0.1`; danach ausschließlich IPv4 **Lokaler DNS-Server** `10.0.0.14` verteilt. WAN-DNS, DHCP-Server und IPv6-Einstellungen blieben unverändert; kein öffentlicher Zweitresolver eingetragen. |
| LAN-Clients | Linux-Laptop bezog nach Lease-Erneuerung ausschließlich `10.0.0.14`; kein DNSv6-Server und keine globale IPv6-Route; normale Auflösung, Split-DNS und Blocking bestanden. Ein Android-Handy zeigte ebenfalls `10.0.0.14`; bei gleichzeitig aktiven DuckDuckGo-Schutz-/VPN-Mechanismen wurden lokale Namen in Firefox und Chrome jedoch nicht aufgelöst. Der Betreiber akzeptierte diesen clientseitigen Bypass; `10.0.0.14` war auf dem Handy direkt erreichbar. Direkt gegen Pi-hole liefert `pi.hole` A `10.0.0.14`, aber auch eine nicht segmentübergreifend nutzbare link-lokale AAAA-Adresse; dieser IPv6-Befund bleibt offen. |
| WireGuard | Keine produktiven Profile oder CT-105-Regeln verändert. CT 105 maskiert VPN-Verkehr im LAN als `10.0.0.15`, was die DNS-LXC-Firewall grundsätzlich umfasst; ein tatsächlicher DNS-Test pro Peer/Rolle und die Umstellung vorhandener Profile auf `10.0.0.14` bleiben bis zur Rollenklärung aus GNAI-007 offen. |
| Regression | Öffentliche DNS-Auflösung und `auth.ghostnet.selfhost.eu` blieben erreichbar; der Auth-Endpunkt lieferte weiterhin seinen erwarteten Redirect. Eine vollständige erneute Regression aller in Phase E genannten Ghostnet-Anwendungen/OIDC-Flows steht noch aus. |
| Backup | `/var/lib/vz/dump/vzdump-lxc-100-2026_10_01-18_49_00.tar.zst`; Stop-Mode, 18 Sekunden; 1.360.465.920 unkomprimierte Bytes, 492.997.570 Byte Archivgröße; `zstd --test` bestanden; SHA-256 `8dca820d72df0a38060ce85370bc3fc7441a408c7c73534821aa5137d6f21b66`; Dienstwiederanlauf bestanden |
| Rollback | FRITZ!Box **Lokaler DNS-Server** auf `10.0.0.1` zurückstellen und Client-Lease erneuern. Das Verfahren ist beschrieben, wurde nach erfolgreichem Cutover aber nicht praktisch zurück- und wieder vorgespult. Das Backup liegt nur auf demselben Proxmox-Host; unabhängige Kopie und praktischer Restore-Test fehlen. |
Offene Abschlussarbeiten sind damit bewusst begrenzt auf die vollständige
Anwendungs-/OIDC-Regression, einen praktisch durchgespielten FRITZ!Box-Rollback,
die Entscheidung zum mobilen DuckDuckGo-DNS-Bypass, eine saubere IPv6-Strategie
und den rollenbezogenen WireGuard-DNS-Test. Sie ändern nichts am bestandenen
IPv4-LAN-Betrieb des Pi-hole.
**Rückkehrpunkt für die Hauptsitzung:** Pi-hole und Split-DNS sind stabil;
`auth-admin.ghostnet.selfhost.eu` löst im LAN auf `10.0.0.10`. WireGuard bleibt
bis zum profilorientierten Test ein ausgewiesener Folgepunkt. Die Hauptsitzung
kann nun den internen NPM-Proxy-Host und Keycloak `hostname-admin` einrichten,
den internen Login testen und danach `auth.ghostnet.selfhost.eu/admin` für
öffentliche Quellen sperren.
## 11. Primärquellen
- [Pi-hole – Voraussetzungen](https://docs.pi-hole.net/main/prerequisites/)
- [Pi-hole – Installation](https://docs.pi-hole.net/main/basic-install/)
- [Pi-hole – FRITZ!Box](https://docs.pi-hole.net/routers/fritzbox-de/)
- [Pi-hole FTL – Konfiguration und lokale Host Records](https://docs.pi-hole.net/ftldns/configfile/)
- [Pi-hole – Container-Konfiguration](https://docs.pi-hole.net/docker/configuration/)
- [Podman – Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html)
- [DuckDuckGo – App Tracking Protection und lokale VPN-Schnittstelle](https://duckduckgo.com/duckduckgo-help-pages/p-app-tracking-protection/is-app-tracking-protection-a-vpn)
- [RFC 6762 – `.local` und Multicast DNS](https://www.rfc-editor.org/rfc/rfc6762)
- [RFC 8375 – `home.arpa` für Heimnetze](https://www.rfc-editor.org/rfc/rfc8375)
- [Keycloak – Hostname-Konfiguration](https://www.keycloak.org/server/hostname)
@@ -4,7 +4,7 @@
**Titel:** Reverse Proxy und Fail2Ban – Konfiguration, Betrieb und Sicherheitsabnahme
**Status:** NPM 2.15.1 und Fail2Ban aktiv; SSH-Rollout und lokale Dateirechte gehärtet; Keycloak-Admin-Exposure offen
**Vorgänger / Referenzen:** [Architektur](../../../ai/GNAI-004-INFRA_Architecture.md), [LXC-SSH](../GNAI-011-SEC_LXC-SSH-Architecture.md), [Zugriffskonzept](../../GNAI-005-SEC_Access-Control.md), [WireGuard](../ct-105/GNAI-007-SEC_VPN.md)
**Vorgänger / Referenzen:** [Architektur](../../../llm-server/ai/GNAI-004-INFRA_Architecture.md), [LXC-SSH](../../GNAI-011-SEC_LXC-SSH-Architecture.md), [Zugriffskonzept](../../../GNAI-005-SEC_Access-Control.md), [WireGuard](../ct-105-wireguard/GNAI-007-SEC_VPN.md)
---
@@ -30,6 +30,7 @@
| 2026-09-24 | 2.3 | Codex | Aktive nftables-INPUT-DROP-Policy, LAN-Begrenzung von SSH und Boot-Aktivierung bestätigt; temporäre Public-Key-Datei entfernt. |
| 2026-09-24 | 2.4 | Codex | LAN- und WireGuard-Erreichbarkeit als bewusste einfache SSH-Netzbaseline bestätigt; engere Workstation-Allowlist verworfen. |
| 2026-09-24 | 2.5 | Codex | Kontrollierten Neustart und Wiederanlauf von SSH, nftables, NPM, Fail2Ban und öffentlichen Proxy-Pfaden erfolgreich abgenommen. |
| 2026-09-24 | 2.6 | Codex | Konsistentes Stop-Mode-vzdump erstellt, Archivintegrität und Prüfsumme geprüft sowie Dienstwiederanlauf regressionsgetestet. |
---
@@ -77,7 +78,7 @@ ED25519-Hostschlüssel und die global angebotenen Methoden
diesem Repository noch in automatisierten Laufprotokollen verarbeitet.
CT 103 ist der erste Rollout der gemeinsamen
[LXC-SSH-Architektur](../GNAI-011-SEC_LXC-SSH-Architecture.md). `ghost-agent`
[LXC-SSH-Architektur](../../GNAI-011-SEC_LXC-SSH-Architecture.md). `ghost-agent`
ist mit eigenem Schlüssel, ohne Passwort, Zusatzgruppen oder sudo eingerichtet.
Das Drop-in `99-security.conf` deaktiviert Root-, Passwort- und
Keyboard-Interactive-Login, verlangt Public Key, erlaubt nur `ghost-agent` und
@@ -406,11 +407,27 @@ podman exec fail2ban fail2ban-client set npm-scanner unbanip "$BAN_IP"
## 10. Backup & Disaster Recovery
Im bisherigen Quellenstand ist ein Proxmox-vzdump-Backup von CT 103 dokumentiert:
Historischer Quellenstand:
* **Backup-Datei:** `/var/lib/vz/dump/vzdump-lxc-103-2026_08_31-19_11_10.tar.zst`
Vor strukturellen Änderungen an nftables, Proxy-Konfiguration oder Quadlets ein aktuelles konsistentes Proxmox-Backup erstellen. Ein Snapshot kann ergänzen, ersetzt aber kein unabhängiges Backup. Der genannte Archivpfad ist historischer Quellenstand, kein Nachweis eines aktuellen oder getesteten Backups.
Aktueller Rückfallstand vom 24.09.2026:
| Merkmal | Nachweis |
| --- | --- |
| Archiv | `/var/lib/vz/dump/vzdump-lxc-103-2026_09_24-17_40_06.tar.zst` |
| Verfahren | `vzdump`, Modus `stop`, Zstandard-Kompression; vollständiges 8-GiB-RootFS, keine zusätzlichen Mountpoints |
| Lauf | erfolgreich in 50 Sekunden; 3.859.916.800 unkomprimierte Bytes, 1.228.224.791 Byte Archivgröße |
| Integrität | `zstd --test` erfolgreich |
| SHA-256 | `0d11a0d7dc75a61e402011264a6dca6932551d60e6e89e907db2ac1d0854d43c` |
| Wiederanlauf | CT `running`; neuer Bootzeitpunkt `17:40:56`; SSH, nftables, NPM und Fail2Ban aktiv; keine fehlgeschlagenen Units; öffentliche Proxy-Ziele erfolgreich geprüft |
Dieses Archiv ist ein geprüfter lokaler Rückfallstand für die anstehende
Proxyänderung. Es liegt auf demselben Proxmox-Host; eine hostunabhängige Kopie
und ein praktischer Restore-Test sind damit nicht nachgewiesen. Vor späteren
strukturellen Änderungen an nftables, Proxy-Konfiguration oder Quadlets erneut
einen aktuellen konsistenten Rückfallstand erstellen. Ein Snapshot kann
ergänzen, ersetzt aber kein unabhängiges Backup.
---
@@ -451,11 +468,12 @@ Der erneute externe Abgleich am 22.09.2026 ergab:
Die Sperre von `/admin` kann unabhängig vom Anwendungsrealm erfolgen. Eine
zusätzliche Sperre von `/realms/master/` bleibt bis zur Identifikation des
tatsächlichen Vaultwarden-Issuers zurückgestellt. Vor der Änderung fehlt noch
ein aktuelles Backup. Der bei der ersten Prüfung fehlende administrative Weg
zu CT 103 wurde später durch die getrennten Konten `ghost` und `ghost-agent`
hergestellt; Root besitzt weiterhin weder Passwort noch installierten
SSH-Schlüssel.
tatsächlichen Vaultwarden-Issuers zurückgestellt. Der aktuelle lokale
Rückfallstand für CT 103 liegt inzwischen vor. Vor der Pfadsperre muss noch ein
funktionierender interner Keycloak-Administrationsweg bestätigt werden. Der bei
der ersten Prüfung fehlende administrative Weg zu CT 103 wurde durch die
getrennten Konten `ghost` und `ghost-agent` hergestellt; Root besitzt weiterhin
weder Passwort noch installierten SSH-Schlüssel.
**TLS-Widerspruch:** Externer HTTPS-Login funktionierte, obwohl für diesen Proxy Host SSL als ausgeschaltet angegeben ist. Effektiven Listener, Zertifikat und vHost-Zuordnung prüfen, bevor eine vollständige HTTPS-Bereitstellung behauptet wird. Auf CT 103 etwa:
@@ -1,9 +1,9 @@
# CT 105 – WireGuard-Gateway
**Dokumenten-ID:** GNAI-007-SEC
**Status:** Live-Stand am 22.09.2026 verifiziert; Schlüsseldateien gehärtet; Rollen-Firewall und Abnahme offen
**Status:** Native nftables-Rollenpolicy aktiv; Legacy-Hooks entfernt; Fremdnetz- und Rollenabnahme offen
**Referenzen:** [Zielarchitektur](../../../ai/GNAI-004-INFRA_Architecture.md), [LXC-SSH](../GNAI-011-SEC_LXC-SSH-Architecture.md), [Zugriffskonzept](../../GNAI-005-SEC_Access-Control.md), [GUI-SOP](GNAI-007-SEC_VPN_Client-Management.md), [Implementation-Log](GNAI-007-SEC_VPN_Implementation-Log.md)
**Referenzen:** [Zielarchitektur](../../../llm-server/ai/GNAI-004-INFRA_Architecture.md), [LXC-SSH](../../GNAI-011-SEC_LXC-SSH-Architecture.md), [Zugriffskonzept](../../../GNAI-005-SEC_Access-Control.md), [GUI-SOP](GNAI-007-SEC_VPN_Client-Management.md), [Implementation-Log](GNAI-007-SEC_VPN_Implementation-Log.md)
## Änderungsverlauf
@@ -14,6 +14,7 @@
| 2026-09-15 | 2.0 | spektr | Rollen Familie/Server Admin/LLM Admin; bestätigtes LXC und Hooks; NAT-Abhängigkeit; v15-Bereinigung; getrennte SOPs und belastbarer Abnahmestatus |
| 2026-09-18 | 2.1 | spektr | Lesenden Gateway-Abgleich, iptables-legacy-Befund und DNS-Abgleich ergänzt; Betriebshinweise entsprechend korrigiert. |
| 2026-09-22 | 2.2 | Codex | Gateway, Container, Regelwerke, Peers, Listener und SSH-Baseline erneut live geprüft; Schlüsseldateien auf Modus 0600 gehärtet. |
| 2026-10-05 | 2.3 | Codex | Server-Admin-Rolle auf native nftables-Sets und natives NAT umgestellt; parallele Legacy-Hook-Bereinigung und externe Abnahme bleiben offen. |
## 1. Zweck und dokumentierter Stand
@@ -21,7 +22,7 @@ Das Gateway ermöglicht administrativen und familiären Fernzugriff auf das Heim
Das WireGuard-Gateway läuft bereits. Die nachfolgende Installation beschreibt seinen reproduzierbaren Aufbau; neue Sicherheitsmaßnahmen sind als **Soll** gekennzeichnet. Grundlage sind die Quellen, die Tests vom 12.09.2026, die vom Betreiber bereitgestellte Konfiguration und die lesenden Gateway-Abgleiche vom 18. und 22.09.2026. Am 22.09.2026 wurden ausschließlich die Dateirechte von `wg-easy.db` und dem historischen `wg0.json` auf `0600` reduziert; Client- und Rollentests wurden nicht durchgeführt.
**Wesentliche offene Umsetzung:** Per-Client Firewall ist deaktiviert. Die bisherige breite Forward-Regel erzwingt keine Rollenisolierung. HTTPS für die wg-easy-GUI ist noch nicht eingerichtet. Der dokumentierte Endpoint `vpn.ghostnet.selfhost.eu` stimmte am 18.09.2026 mit der ausgehenden IPv4 des Gateways überein; die DynDNS-Aktualisierung nach einem Adresswechsel ist noch nicht nachgewiesen. Der aktuelle Live-Abgleich steht im Implementation-Log.
**Wesentliche offene Umsetzung:** Die experimentelle wg-easy-Per-Client-Firewall bleibt deaktiviert. Seit 05.10.2026 begrenzen native nftables-Sets den Server-Admin-Laptop auf inventarisierte Zielhosts und erlauben Gateway-SSH/-GUI für seine VPN-IPs. Die alten wg-easy-`iptables-legacy`-Hooks und Laufzeitregeln sind entfernt; ein kontrollierter wg-easy-Neustart erzeugte sie nicht erneut. Positive und negative Rollen- sowie Fremdnetztests stehen noch aus. HTTPS für die wg-easy-GUI ist noch nicht eingerichtet. Der aktuelle Live-Abgleich steht im Implementation-Log.
## 2. Architektur und Adressplan
@@ -46,7 +47,7 @@ flowchart TD
F -->|"TCP 80/443"| N["NPM · 10.0.0.10"]
F -->|"UDP 51820"| V["wg-easy · 10.0.0.15"]
N --> A["Öffentliche Anwendungen"]
V --> R["Peer-Firewall · Soll; NAT · Ist"]
V --> R["native nftables-Rollenpolicy und NAT"]
R --> S["Proxmox, LXCs und KI-Server"]
R -->|"nur Familie"| C["Heimnetz-PCs und Clients"]
```
@@ -63,9 +64,9 @@ Die Adressgruppierung im selben `/24` bildet keine Netzwerksegmentierung. Server
Diese Vorgaben erlauben Netzwerkzugriff auf die genannten Hosts. Anwendungs-, SSH- und Betriebssystemrechte werden separat vergeben. Ein statischer Adressbereich ist kein ausreichender Nachweis, dass jede Adresse darin ein Server ist: Erweiterungen benötigen einen Inventareintrag und die passende Peer-Freigabe.
**Soll:** In wg-easy die Per-Client Firewall aktivieren und für jeden Peer **Firewall Allowed IPs** ausdrücklich setzen. **Allowed IPs** im exportierten Profil steuert weiter das Split-Tunnel-Routing. **Server Allowed IPs** bezeichnet die Netze, die der Server zum Peer routet; für ein einzelnes Endgerät bleibt das dessen eigene VPN-IP `/32`. Diese drei Felder dürfen nicht miteinander verwechselt werden. [Client-Felder](https://wg-easy.github.io/wg-easy/latest/guides/clients/)
**Umsetzung:** wg-easy verwaltet Schlüssel, Tunneladressen und Exportprofile. **Allowed IPs** im exportierten Profil steuert das Split-Tunnel-Routing. **Server Allowed IPs** bezeichnet zusätzliche Netze hinter dem Client; bei einem einzelnen Endgerät bleibt dieses Feld leer. Die verbindliche Zielkontrolle erfolgt unabhängig davon in nativen nftables-Sets anhand der zugewiesenen Peer-IP. [Client-Felder](https://wg-easy.github.io/wg-easy/latest/guides/clients/)
Die Funktion ist laut offizieller Dokumentation experimentell. Abnahme muss absichtliche Erweiterungen der clientseitigen AllowedIPs auf gesperrte Ziele einschließen. Insbesondere muss der Zugriff auf **lokale Gateway-Dienste** geprüft werden, da dieser die INPUT- statt FORWARD-Chain durchläuft. [Per-Client Firewall](https://wg-easy.github.io/wg-easy/latest/guides/admin/)
Die wg-easy-Per-Client-Firewall wird nicht verwendet, weil sie laut offizieller Dokumentation experimentell ist und `iptables`/`ip6tables` benötigt. CT 105 verwendet stattdessen ausschließlich native nftables-Policy als Sollzustand. Abnahme muss absichtliche Erweiterungen der clientseitigen AllowedIPs auf gesperrte Ziele einschließen. Gateway-Dienste werden in INPUT, weitergeleitete Serverziele in FORWARD behandelt. [Per-Client Firewall](https://wg-easy.github.io/wg-easy/latest/guides/admin/)
## 3. Installation im Proxmox-LXC
@@ -115,7 +116,7 @@ sudo install -m 600 /dev/null /opt/wg-easy/environment
Der letzte Befehl ist ausschließlich für eine **Neuinstallation**: ein vorhandenes Environment-File nicht überschreiben. Dokumentiert ist Podman 5.4.2. Die durch Debian bereitgestellte Version mit `podman --version` festhalten und Quadlet-Unterstützung prüfen.
Den Benutzer `admin` nur anlegen, wenn er noch nicht existiert. SSH-Key hinterlegen und Rechte testen. Für die SSH-Härtung gilt die gemeinsame [LXC-SSH-Architektur](../GNAI-011-SEC_LXC-SSH-Architecture.md); KI-Host-Konten und Netzwerte werden nicht übernommen. Im bestehenden WireGuard-LXC ist `admin ALL=(ALL) NOPASSWD: ALL` eingerichtet; das ist der bestätigte Ist-Stand und keine allgemeine Vorgabe für andere LXCs. Änderungen an SSH immer mit einer offenen zweiten Sitzung oder Proxmox-Konsole absichern.
Den Benutzer `admin` nur anlegen, wenn er noch nicht existiert. SSH-Key hinterlegen und Rechte testen. Für die SSH-Härtung gilt die gemeinsame [LXC-SSH-Architektur](../../GNAI-011-SEC_LXC-SSH-Architecture.md); KI-Host-Konten und Netzwerte werden nicht übernommen. Im bestehenden WireGuard-LXC ist `admin ALL=(ALL) NOPASSWD: ALL` eingerichtet; das ist der bestätigte Ist-Stand und keine allgemeine Vorgabe für andere LXCs. Änderungen an SSH immer mit einer offenen zweiten Sitzung oder Proxmox-Konsole absichern.
### 3.3 IP-Forwarding
@@ -176,7 +177,7 @@ table inet filter {
}
```
**Diese Baseline ist keine fertige Rollen-Firewall.** Die breite FORWARD-Freigabe muss gemeinsam mit Per-Client Firewall getestet werden. Die INPUT-Regel erlaubt LAN-Quellen, aber noch keine neuen Verbindungen von VPN-Quellen `10.8.0.x` auf TCP 22/51821. Für Familie und Server Admins muss am Gateway eine ausdrückliche INPUT-Freigabe anhand ihrer zugewiesenen VPN-IP oder einer gepflegten Peer-IP-Menge hinzukommen. LLM Admins dürfen diese Ports auch nach einer lokalen Routingänderung nicht erreichen. Die Rollenzuordnung vorhandener Peers ist noch offen; daher wird keine angeblich fertige IP-Regel erfunden.
Diese historische Baseline wurde am 05.10.2026 ersetzt. Die aktuelle Datei verwendet die Tabellen `inet ghostnet_filter` und `ip ghostnet_wg_nat`, ein Set `vpn_server_admins` für zugewiesene Peer-IPs sowie `server_admin_targets` für inventarisierte Ziele. INPUT erlaubt den Server-Admin-IPs TCP 22/51821; FORWARD erlaubt ihnen neue Verbindungen nur zu Zielhosts im Set. Rückverkehr läuft über Connection Tracking, natives MASQUERADE über `eth0`.
`flush ruleset` löscht dynamisch angelegte nftables-/iptables-nft-Regeln. Der Live-Abgleich am 18.09.2026 zeigte jedoch wg-easy-Hooks in **iptables-legacy**; diese werden durch einen nftables-Flush nicht automatisch mit gelöscht. Beide Regelwerke und ihre gemeinsame Wirkung müssen nach Reload oder Neustart geprüft werden. Bei einer Neuinstallation zuerst die Baseline laden, danach wg-easy starten. Im laufenden Betrieb vor einem Reload Regelzustand sichern und Änderung über die Proxmox-Konsole ausführen. Dauerhafte Regeln und dynamische Regeln brauchen eine getestete Start-/Reload-Reihenfolge; diese Abnahme steht noch aus.
@@ -240,8 +241,8 @@ Nach Quadlet-Änderungen `daemon-reload` und `restart` ausführen. Autostart von
4. Als Host `vpn.ghostnet.selfhost.eu` und als Port `51820` eintragen; vor Ausgabe neuer Profile DNS-Auflösung und aktuelle WAN-IP erneut vergleichen.
5. Interface-Adressraum `10.8.0.0/24`, Gateway `10.8.0.1` und IPv4-Betrieb prüfen. In Admin Panel → Interface das LAN-Ausgangsgerät `eth0` prüfen.
6. Neue Client-Defaults so wählen, dass neue Peers keine ungeprüften Vollzugriffsrechte erhalten. Für jede Freigabe gilt die separate GUI-SOP; ein Default ist keine Rollenzuordnung.
7. DNS rollenabhängig setzen: Familie kann die Fritzbox `10.0.0.1` über das Heimnetz erreichen. Server Admins und LLM Admins verwenden zunächst System-DNS, sofern keine gesonderte DNS-Freigabe beschlossen wird. Historischer Laptop-DNS `10.0.0.1` ist kein pauschaler neuer Default.
8. Per-Client Firewall für die neue Rollenpolitik aktivieren und abnehmen. Vor Aktivierung vorhandene Peers erfassen, Änderungen über LAN durchführen und unzugeordnete Peers während der Umstellung gesperrt halten.
7. DNS rollenabhängig setzen: Familie und Server Admin verwenden Pi-hole `10.0.0.14`, damit der produktive lokale Resolver und Split-DNS auch im fremden Netz funktionieren. Pi-hole muss dafür zugleich in Allowed IPs und im nativen nftables-Zielset enthalten sein. LLM Admin verwendet zunächst System-DNS, solange keine interne Namensauflösung benötigt und freigegeben wird. Historischer Laptop-DNS `10.0.0.1` ist kein neuer Default.
8. Die wg-easy-Per-Client-Firewall deaktiviert lassen. Neue Peer-IP und Rolle in den nativen nftables-Sets pflegen, Konfiguration mit `nft --check --file` prüfen und erst danach atomar laden und abnehmen.
[Offizieller Setup-Ablauf](https://wg-easy.github.io/wg-easy/latest/guides/setup/)
@@ -295,6 +296,27 @@ MASQUERADE lässt LAN-Dienste VPN-Verkehr unter der Quelladresse `10.0.0.15` seh
Vor Änderungen ein konsistentes Proxmox-Backup erstellen. Ein Snapshot allein ist kein unabhängiges Backup. Für ein Dateibackup wg-easy in einem Wartungsfenster stoppen, **das gesamte Datenverzeichnis** einschließlich etwaiger SQLite-WAL-Dateien sowie Quadlet, Environment, Firewall und sysctl sichern; anschließend Dienst wieder starten. Backups enthalten PrivateKeys und PresharedKeys und sind entsprechend zu schützen. Wiederherstellung einschließlich Rollenregeln, NAT, externem Handshake und gesperrten Zielen testen. Restore kann inzwischen widerrufene Peers wieder aktivieren; Widerrufsliste abgleichen.
### 6.1 Kontrolliertes Patchverfahren für CT 105
Paketupdates werden mindestens monatlich sowie vor sicherheitsrelevanten
Änderungen kontrolliert geprüft und manuell installiert. Die aktiven
`apt-daily`-Timer ersetzen dieses Verfahren nicht; ein
`unattended-upgrade`-Programm und eine wirksame
`APT::Periodic::Unattended-Upgrade`-Konfiguration sind nicht vorhanden.
1. Wartungsfenster ankündigen und bei grundlegenden Paket-, SSH-, Firewall-
oder Container-Runtime-Änderungen ein aktuelles Stop-Backup sicherstellen.
2. Paketlisten mit `sudo apt update` aktualisieren, angebotene Pakete
inventarisieren und die Transaktion mit `sudo apt-get -s upgrade`
simulieren. Entfernte oder zurückgehaltene Pakete vorab bewerten.
3. Reguläre Aktualisierungen mit `sudo apt upgrade` installieren.
Release-Upgrades und `full-upgrade` sind eigene Wartungsvorgänge.
4. `/var/run/reboot-required`, den Paketmanager-Exitstatus und relevante
Journale prüfen. Falls erforderlich, CT 105 kontrolliert neu starten.
5. Danach SSH, nftables, wg-easy, `wg0`, Peerzahl, SQLite-Integrität,
Legacy-Regelfreiheit und GUI erneut prüfen. Fehler führen zum Abbruch und
gegebenenfalls zur Wiederherstellung aus dem letzten konsistenten Backup.
## 7. Abnahme und Evidenz
| Prüfung | Ergebnis / Status |
@@ -303,12 +325,12 @@ Vor Änderungen ein konsistentes Proxmox-Backup erstellen. Ein Snapshot allein i
| Laptop → `10.8.0.1` und Ping `10.0.0.2` | bestanden am 12.09.2026 |
| Laptop-Internet bleibt direkt | IPv4-Test bestanden am 12.09.2026 |
| Zugriff auf KI-Server | offen; im letzten Test ausgeschaltet |
| Proxmox-GUI / SSH-Diensttests | offen; Ping ersetzt keinen Diensttest |
| Proxmox-GUI / SSH-Diensttests | bestanden am 05.10.2026 |
| Familie → Heimnetz-PC | offen, einschließlich Host-Firewall und Authentifizierung |
| Server Admin → Server erlaubt, DHCP-Client gesperrt | offen, Per-Client Firewall noch aus |
| Server Admin → Server erlaubt, nicht freigegebenes aktives Ziel gesperrt | bestanden am 05.10.2026 im Fremdnetz; Proxmox parallel erlaubt, FRITZ!Box `.1` nach absichtlicher Routenerweiterung gesperrt, nftables-Zähler geprüft |
| LLM Admin → KI-Server erlaubt, Proxmox/NPM/Gateway-GUI gesperrt | offen, auch bei erweiterten lokalen AllowedIPs prüfen |
| WAN → TCP 22/51821 und IPv6-Exposure | offen |
| Reboot / Firewall-Reload / Restore | offen |
| Reboot / Firewall-Reload / Restore | LXC-Neustart und persistente Firewall am 05.10.2026 bestanden; tatsächlicher Restore offen |
Tests von einem fremden Netz ausführen, nicht nur aus dem Heimnetz. Für Isolation zuerst sicherstellen, dass das gesperrte Ziel von einem erlaubten Peer tatsächlich erreichbar ist. Handshake und VPN-Routen prüfen, dann bewusst clientseitiges Routing zu einem gesperrten Ziel ergänzen. Timeout allein ist ohne aktiven Zielservice und nachvollziehbare Firewallwirkung kein ausreichender Isolationsnachweis. Ergebnisse mit Datum, Quellnetz, Peer, Ziel, Port und Regelzählern dokumentieren; Secrets auslassen.
@@ -317,7 +339,7 @@ Tests von einem fremden Netz ausführen, nicht nur aus dem Heimnetz. Für Isolat
| Punkt | erforderliche Klärung / Umsetzung |
| --- | --- |
| VPN-DynDNS | Am 18.09.2026 löste `vpn.ghostnet.selfhost.eu` zur ausgehenden Gateway-IPv4 `93.208.161.126` auf; `vpn.ghostnet.dyndns.eu` zeigte auf `83.169.40.234`. Profile nutzen den selfhost-Namen. Automatische Aktualisierung nach WAN-IP-Wechsel und tatsächliche Fritzbox-DynDNS-Einstellung noch prüfen. |
| Vorhandene Peers | `Laptop-FullAdmin` (`10.8.0.2`) und `Android-LLMAdmin` (`10.8.0.3`) sind historisch. Laptop der neuen Familie-/Server-Admin-Rolle ausdrücklich zuordnen; Android bleibt außerhalb der neuen Client-SOPs. |
| Vorhandene Peers | `ghost-laptop` (`10.8.0.3`) ist der einzige Peer und wird als Server Admin durch native nftables-Sets eingeschränkt. Die Altpeers `Android-LLMAdmin` und `Laptop-FullAdmin` wurden am 05.10.2026 vollständig gelöscht. |
| Gateway-Management | INPUT-Regeln für zugeordnete Familie-/Server-Admin-Peers ergänzen; lokale Zugangsgruppen festlegen. |
| Per-Client Firewall | aktivieren, erzeugte Regeln samt Hooks prüfen, negative Tests und Wiederanlauf abnehmen. |
| Rollen-Firewall | Native nftables-Policy aktiv; Legacy-Hooks entfernt, negative Tests und Wiederanlauf bestanden. |
| GUI-HTTPS | Hostname, Zertifikat und Namensauflösung entscheiden und implementieren. |
@@ -2,8 +2,8 @@
**Dokumenten-ID:** GNAI-007-SEC-SOP-01
**Titel:** Clients in der wg-easy-GUI einrichten und verwalten
**Status:** Betriebsverfahren dokumentiert; Rollen-Firewall noch nicht umgesetzt oder abgenommen
**Referenzen:** [Gateway-Architektur](GNAI-007-SEC_VPN.md), [Rollenmodell](../../GNAI-005-SEC_Access-Control.md), [Windows](GNAI-007-SEC_VPN_Clients_Windows11.md), [Linux](GNAI-007-SEC_VPN_Clients_Linux.md)
**Status:** Native nftables-Rollenpolicy teilweise umgesetzt; Legacy-Hook-Bereinigung und Abnahme offen
**Referenzen:** [Gateway-Architektur](GNAI-007-SEC_VPN.md), [Rollenmodell](../../../GNAI-005-SEC_Access-Control.md), [Windows](GNAI-007-SEC_VPN_Clients_Windows11.md), [Linux](GNAI-007-SEC_VPN_Clients_Linux.md)
## Änderungsverlauf
@@ -18,44 +18,44 @@ Administratoren erstellen, ändern, verteilen und widerrufen WireGuard-Profile.
Benötigt werden ein Administratorlogin, Zugriff auf `http://10.0.0.15:51821` aus einem erlaubten Netz und ein aktuelles Serverinventar. Der dokumentierte Endpoint ist `vpn.ghostnet.selfhost.eu`; vor Profilausgabe seine DNS-Auflösung mit der aktuellen WAN-IP vergleichen. HTTPS ist geplant, noch nicht eingerichtet. Ein eigener Peer pro Gerät ermöglicht gezielten Widerruf.
Vor Rollenvergabe müssen Per-Client Firewall und Gateway-INPUT-Regeln nach der Architektur eingerichtet und geprüft werden. Der derzeitige Serverzustand bietet noch keine nachgewiesene Rollenisolierung. Diese SOP dokumentiert die Umstellung und den anschließenden Regelbetrieb; sie erklärt bestehende Peers nicht rückwirkend für sicher.
Vor Rollenvergabe müssen native nftables-Zielsets und Gateway-INPUT-Regeln nach der Architektur eingerichtet und geprüft werden. Die Policy wurde am 05.10.2026 geladen, ist aber bis zur Entfernung paralleler Legacy-Hooks und zu den Rollen-Negativtests noch nicht abgenommen. Diese SOP erklärt bestehende Peers nicht rückwirkend für sicher.
## 2. Rollenwerte festlegen
| Rolle | Allowed IPs im Profil | Firewall Allowed IPs auf dem Server | DNS |
| Rolle | Allowed IPs im Profil | native nftables-Zielmenge auf CT 105 | DNS |
| --- | --- | --- | --- |
| Familie | `10.0.0.0/24` | `10.0.0.0/24` | `10.0.0.1` |
| Server Admin | freigegebene Server jeweils `/32` | dieselben Serveradressen ausdrücklich eintragen | zunächst leer / System-DNS |
| Familie | `10.0.0.0/24` | `10.0.0.0/24` | `10.0.0.14` |
| Server Admin | freigegebene Server jeweils `/32` | dieselben Serveradressen ausdrücklich eintragen | `10.0.0.14` |
| LLM Admin | `10.0.0.90/32` | `10.0.0.90/32` | leer / System-DNS |
Für Server Admins enthält das Quelleninventar derzeit Proxmox `.2` sowie `.4`, `.10`, `.11`, `.12`, `.15`, `.17`, `.23`, `.47` und KI-Server `.90`. Vor Vergabe in Proxmox mit der tatsächlichen Belegung abgleichen. Beispiel nach bestätigtem Inventarabgleich:
Für Server Admins enthält das Quelleninventar derzeit Proxmox `.2` sowie `.4`, `.10`, `.11`, `.12`, Pi-hole `.14`, `.15`, `.17`, `.23`, `.47` und KI-Server `.90`. Vor Vergabe in Proxmox mit der tatsächlichen Belegung abgleichen. Beispiel nach bestätigtem Inventarabgleich:
```text
10.0.0.2/32, 10.0.0.4/32, 10.0.0.10/32, 10.0.0.11/32, 10.0.0.12/32, 10.0.0.15/32, 10.0.0.17/32, 10.0.0.23/32, 10.0.0.47/32, 10.0.0.90/32
10.0.0.2/32, 10.0.0.4/32, 10.0.0.10/32, 10.0.0.11/32, 10.0.0.12/32, 10.0.0.14/32, 10.0.0.15/32, 10.0.0.17/32, 10.0.0.23/32, 10.0.0.47/32, 10.0.0.90/32
```
Das ist eine Inventarliste, keine behauptete vollständige LXC-Erfassung. Neue Server einzeln ergänzen. Der geplante statische Bereich bis `.99` wird nicht automatisch freigegeben. Die Fritzbox `.1` ist keine implizite Server-Admin-Freigabe. DNS über die Fritzbox für diese Rolle benötigt eine separate Entscheidung und Freigabe, gegebenenfalls gezielt TCP/UDP 53.
Das ist eine Inventarliste, keine behauptete vollständige LXC-Erfassung. Neue Server einzeln ergänzen. Der geplante statische Bereich bis `.99` wird nicht automatisch freigegeben. Die Fritzbox `.1` ist keine implizite Server-Admin-Freigabe. Pi-hole `.14` ist bewusst enthalten, damit interne Namen auch im fremden Netz aufgelöst werden.
Zugriff auf `10.8.0.1` oder andere VPN-Peers ist in diesen Basislisten nicht enthalten. Bei Bedarf gesondert beschließen und sowohl Routing als auch Firewall ergänzen. Familie hat mit `10.0.0.0/24` Zugriff auf Heimnetz-Clients, nicht automatisch auf sämtliche anderen VPN-Peers.
## 3. Einmalige Umstellung auf Per-Client Firewall
## 3. Einmalige Umstellung auf native nftables-Rollenpolicy
1. Vor Änderung konsistentes Backup und aktuelle Regeln sichern; Arbeiten über LAN/Proxmox-Konsole durchführen.
2. Bestehende Peers, ihre VPN-IP, Gerät und Rolle erfassen. Der alte Name „FullAdmin“ unterscheidet Familie und Server Admin nicht. Unzugeordnete Peers deaktivieren.
3. In **Admin Panel → Interface** **Per-Client Firewall** einschalten und speichern. Sichtbarkeit und Funktion im installierten v15.4.0 prüfen. Falls das Feld fehlt oder eine Fehlermeldung erscheint, nicht auf clientseitiges Routing als Ersatz ausweichen.
4. Die zugeordneten Peers nacheinander bearbeiten und ausdrückliche **Firewall Allowed IPs** setzen. Die Fallback-Wirkung leerer Felder nicht als Rollenpolitik verwenden.
5. Gateway-INPUT-Zugriff für Familie und Server Admin anhand der bestätigten VPN-IP-Zuordnung ergänzen. wg-easy-Port 51821 und SSH-Port 22 für LLM Admin prüfen und sperren; GUI-Einstellungen allein beweisen diese INPUT-Beschränkung nicht.
6. Serverregeln und Hooks prüfen. Positive und negative Tests aus Abschnitt 7 ausführen. Erst danach Peers für die neue Rollenpolitik freigeben.
2. Bestehende Peers, ihre VPN-IP, Gerät und Rolle erfassen. Unzugeordnete Peers deaktivieren.
3. Die wg-easy-Per-Client-Firewall deaktiviert lassen. Sie benötigt iptables; CT 105 verwendet native nftables-Regeln als alleinigen Sollpfad.
4. Peer-IP in das passende Rollen-Set und erlaubte Zielhosts in das zugehörige Ziel-Set aufnehmen. Kein pauschales Server-Subnetz als Ersatz für das Inventar verwenden.
5. Gateway-INPUT-Zugriff für Familie und Server Admin anhand der bestätigten VPN-IP-Zuordnung ergänzen. wg-easy-Port 51821 und SSH-Port 22 für LLM Admin sperren.
6. Konfiguration mit `nft --check --file` prüfen, gesichert laden und Sets, INPUT, FORWARD, NAT sowie parallel vorhandene Legacy-Regeln kontrollieren. Positive und negative Tests aus Abschnitt 7 ausführen.
Die offizielle Dokumentation kennzeichnet Per-Client Firewall als experimentell; sie benötigt eine funktionierende iptables-Umgebung. [Admin Panel](https://wg-easy.github.io/wg-easy/latest/guides/admin/)
Die offizielle Dokumentation kennzeichnet die nicht verwendete wg-easy-Per-Client-Firewall als experimentell und iptables-basiert. [Admin Panel](https://wg-easy.github.io/wg-easy/latest/guides/admin/)
## 4. Neuen Geräteclient anlegen und Profil verteilen
1. In wg-easy anmelden und über die Funktion zum Hinzufügen eines Clients einen neuen Eintrag erstellen. Die konkrete Schaltflächenbeschriftung kann je nach Sprache abweichen.
2. Einen eindeutigen Gerätenamen setzen und Besitzer, Rolle, Datum und Zweck im Peer-Verzeichnis erfassen. Kurze technische Profilnamen verwenden, beispielsweise `gnai-laptop`; Linux-Interface-Namen sind auf 15 Zeichen begrenzt.
2. Einen eindeutigen Gerätenamen setzen und Besitzer, Rolle, Datum und Zweck im Peer-Verzeichnis erfassen. Kurze technische Profilnamen verwenden, beispielsweise `ghost-laptop`; Linux-Interface-Namen sind auf 15 Zeichen begrenzt.
3. Den Eintrag bis zur abgeschlossenen Konfiguration deaktiviert halten. Falls die GUI das Anlegen nur aktiviert ermöglicht, direkt deaktivieren und noch kein Profil verteilen.
4. Zugewiesene IPv4-Adresse im VPN-Netz prüfen: Server `.1`, Endgeräte eindeutige freie Adressen. Adressen bestehender Peers nicht versehentlich wiederverwenden.
5. Client-Details öffnen. **Allowed IPs**, **Firewall Allowed IPs** und **DNS** gemäß Rolle setzen. **Server Allowed IPs** bei einem einfachen Endgerät auf dessen eigene VPN-IP `/32` belassen.
5. Client-Details öffnen. **Allowed IPs** und **DNS** gemäß Rolle setzen. **Firewall Allowed IPs** wird nicht verwendet. **Server Allowed IPs** beschreibt zusätzliche Netze, die hinter dem Client liegen und zum Client geroutet werden. Bei einem einfachen Endgerät ohne nachgelagertes Netz bleibt dieses Feld leer; wg-easy erzeugt die Route zur zugewiesenen Client-VPN-IP selbst.
6. Endpoint kontrollieren: `vpn.ghostnet.selfhost.eu`, UDP-Port `51820`, DNS-Abgleich mit aktueller WAN-IP. MTU zunächst beim geprüften Default belassen. Keepalive ist im Altbestand `0`; bei Bedarf für Geräte hinter NAT gezielt `25` Sekunden konfigurieren und testen.
7. Client-Hooks leer lassen, damit das Profil auch in Windows und NetworkManager importierbar bleibt. Server-Hooks für NAT nicht mit diesen Client-Hooks verwechseln.
8. Änderungen speichern. Über die Download-Funktion das aktualisierte `.conf` exportieren. Für die vorgesehenen Windows-/Linux-Verfahren wird die Datei verwendet.
@@ -96,4 +96,4 @@ Im Status prüfen: letzter Handshake und steigende Transferwerte unter erzeugtem
## 8. Nachweis und Abschluss
Pro Peer Besitzer, Gerät, neue Rolle, VPN-IP, Freigaben, Aktivierungsstatus, Ausgabedatum und Abnahme erfassen. PrivateKeys, PresharedKeys, GUI-Passwörter und QR-Codes nicht aufnehmen. Neue Rollen sind erst nach erfolgreicher serverseitiger Durchsetzung freigegeben. Die vorhandenen Altpeers und ihre neuen Rollen sind noch mit dem Betreiber abzugleichen.
Pro Peer Besitzer, Gerät, neue Rolle, VPN-IP, Freigaben, Aktivierungsstatus, Ausgabedatum und Abnahme erfassen. PrivateKeys, PresharedKeys, GUI-Passwörter und QR-Codes nicht aufnehmen. Neue Rollen sind erst nach erfolgreicher serverseitiger Durchsetzung freigegeben. Am 05.10.2026 wurden die Altpeers `Laptop-FullAdmin` und `Android-LLMAdmin` gelöscht; `ghost-laptop` `.3` ist der einzige aktive Peer und als Server Admin durch native nftables-Sets abgenommen.
@@ -11,10 +11,11 @@
| --- | --- | --- | --- |
| 2026-09-09 | 1.0 | spektr | Erste Clientanleitung für Linux mit KDE Plasma erstellt. |
| 2026-09-15 | 2.0 | spektr | Debian, Arch/CachyOS, Fedora und Kinoite getrennt beschrieben; NetworkManager und wg-quick, Geheimnisschutz, Fehlersuche und belegten Teiltest überarbeitet. |
| 2026-10-05 | 2.1 | spektr | Sicheren lokalen Ablagepfad, kurze Profilnamen und das beim Import beobachtete Aktivierungsverhalten von NetworkManager ergänzt. |
## 1. Voraussetzungen und Begriffe
Du benötigst ein eigenes `.conf`-Profil vom Administrator, sudo-Berechtigung für die Einrichtung und Internetzugang. Der Administrator nennt deine Rolle und ein erreichbares Testziel. Du benötigst keine wg-easy-Anmeldung.
Du benötigst ein eigenes `.conf`-Profil vom Administrator und Internetzugang. Je nach NetworkManager-/PolicyKit-Konfiguration kann die Einrichtung ohne sudo erfolgen; die wg-quick-Methode benötigt sudo. Der Administrator nennt deine Rolle und ein erreichbares Testziel. Du benötigst keine wg-easy-Anmeldung.
Das Profil enthält geheime Schlüssel: **PrivateKey und PresharedKey** dürfen nicht geteilt werden. PublicKeys sind öffentlich, die vollständige Datei ist dennoch geheim. Jeder Rechner verwendet einen eigenen Peer. Serverdienste verlangen weiterhin ihre eigene Anmeldung.
@@ -22,14 +23,18 @@ Bei Split-Tunneling gehen nur freigegebene Ziele durch WireGuard, normale Intern
## 2. Software bereitstellen
| Distribution | Installation der WireGuard-Werkzeuge |
Für Methode A genügt ein NetworkManager mit nativer WireGuard-Unterstützung;
`wireguard-tools` muss dafür nicht zusätzlich installiert sein. Die folgenden
Pakete sind für Methode B mit `wg`, `wg-quick` und dessen CLI-Diagnose nötig:
| Distribution | optionale WireGuard-Werkzeuge für Methode B |
| --- | --- |
| Debian | `sudo apt update`, anschließend `sudo apt install wireguard-tools` |
| Arch / CachyOS | `sudo pacman -S wireguard-tools` |
| Fedora, klassisch | `sudo dnf install wireguard-tools` |
| Fedora Kinoite | falls Werkzeuge fehlen: `sudo rpm-ostree install wireguard-tools`, anschließend in die neue Deployment-Version neu starten |
Die offiziellen Distributionsbefehle stehen auf der [WireGuard-Installationsseite](https://www.wireguard.com/install/). Aktuelle Standardkernel dieser Distributionen unterstützen WireGuard; bei angepassten Kerneln muss der Administrator dies prüfen. DKMS nicht pauschal installieren.
Die offiziellen Distributionsbefehle stehen auf der [WireGuard-Installationsseite](https://www.wireguard.com/install/). Aktuelle Standardkernel dieser Distributionen unterstützen WireGuard; bei angepassten Kerneln muss der Administrator dies prüfen. DKMS nicht pauschal installieren. Auf dem geprüften CachyOS-Arbeitsplatz funktionierte der native NetworkManager-Import ohne installiertes `wireguard-tools`.
Für NetworkManager muss dessen Dienst vorhanden und aktiv sein; auf KDE wird die Netzwerkoberfläche durch `plasma-nm` bereitgestellt, nicht durch ein automatisch von wireguard-tools erzeugtes Tray-Icon. Desktop-Installationen bringen das häufig bereits mit:
@@ -42,26 +47,36 @@ Wenn NetworkManager fehlt, klärt der Administrator zunächst, welcher Dienst de
## 3. Profilname und Aufbewahrung
Für diese Anleitung heißt die lokale Datei `gnai-laptop.conf`. Vom Administrator anders benannte Dateien gegebenenfalls im Dateimanager umbenennen. Der tatsächliche Name muss bei folgenden Befehlen passen. Linux-wg-quick verwendet den Dateinamen ohne `.conf` als Interface-Namen, **ohne `wg-`-Präfix**, maximal 15 Zeichen. [wg-quick-Implementierung](https://git.zx2c4.com/wireguard-tools/tree/src/wg-quick/linux.bash)
Für diese Anleitung heißt die lokale Datei `ghost-laptop.conf`. Vom Administrator anders benannte Dateien gegebenenfalls im Dateimanager umbenennen. Der tatsächliche Name muss bei folgenden Befehlen passen. Linux-wg-quick verwendet den Dateinamen ohne `.conf` als Interface-Namen, **ohne `wg-`-Präfix**, maximal 15 Zeichen. Buchstaben, Ziffern sowie unter anderem Bindestrich und Unterstrich sind erlaubt; `ghost-laptop` hat 12 Zeichen und ist gültig. [wg-quick-Implementierung](https://git.zx2c4.com/wireguard-tools/tree/src/wg-quick/linux.bash)
```bash
chmod 600 "$HOME/Downloads/gnai-laptop.conf"
install -d -m 700 "$HOME/.config/ghostnet/wireguard"
mv "$HOME/Downloads/ghost-laptop.conf" "$HOME/.config/ghostnet/wireguard/ghost-laptop.conf"
chmod 600 "$HOME/.config/ghostnet/wireguard/ghost-laptop.conf"
```
Der private Ablagepfad liegt außerhalb des Projekt-Repositories. Keine
Ignore-Regel im Repository als Ersatz für eine sichere Ablage verwenden.
## 4. Methode A: NetworkManager und Desktop
Für Desktop-Nutzer eignet sich die bestehende NetworkManager-Verwaltung. Unter KDE **Systemeinstellungen → Verbindungen** öffnen und die Funktion zum Importieren einer Verbindung verwenden, sofern deine Version WireGuard-Dateiimport anbietet. Wenn die Oberfläche diese Funktion nicht anbietet, ist der folgende Import eindeutig; danach erscheint die Verbindung in der Desktop-Netzwerkverwaltung:
```bash
sudo nmcli connection import type wireguard file "$HOME/Downloads/gnai-laptop.conf"
nmcli connection import type wireguard file "$HOME/.config/ghostnet/wireguard/ghost-laptop.conf"
nmcli connection show
```
Den beim Import gemeldeten Verbindungsnamen verwenden. Bei abweichendem Namen in folgenden Befehlen `gnai-laptop` ersetzen:
Der Import kann die Verbindung unmittelbar aktivieren. Im Heimnetz mit demselben
Adressbereich deshalb direkt wieder trennen. Wenn sie noch nicht aktiv war, ist
die entsprechende Fehlermeldung beim ersten Befehl unkritisch. Den beim Import
gemeldeten Verbindungsnamen verwenden; bei abweichendem Namen in folgenden
Befehlen `ghost-laptop` ersetzen:
```bash
sudo nmcli connection modify gnai-laptop connection.autoconnect no
sudo nmcli connection up gnai-laptop
nmcli connection down ghost-laptop
nmcli connection modify ghost-laptop connection.autoconnect no connection.permissions "user:$USER"
nmcli connection up ghost-laptop
nmcli connection show --active
ip -br addr
```
@@ -69,24 +84,24 @@ ip -br addr
Zum Trennen:
```bash
sudo nmcli connection down gnai-laptop
nmcli connection down ghost-laptop
```
Alternativ über die Desktop-Netzwerkverwaltung verbinden/trennen. Verbindung erst nach Absprache automatisch starten lassen; Autoconnect-Verhalten wird beim Import nicht vorausgesetzt. NetworkManager speichert das Profil in seiner eigenen Verwaltung; die Downloaddatei ist danach nur eine zusätzliche Geheimniskopie. [NetworkManager-CLI](https://networkmanager.dev/docs/api/latest/nmcli.html)
Alternativ über die Desktop-Netzwerkverwaltung verbinden/trennen. Verbindung erst nach Absprache automatisch starten lassen; Autoconnect-Verhalten wird beim Import nicht vorausgesetzt. NetworkManager speichert das Profil zusätzlich in seiner eigenen geschützten Verwaltung. Die Datei unter `.config` bleibt ebenfalls ein Geheimnis und dient als kontrollierte lokale Ausgangskopie. Falls PolicyKit den Import verweigert, die Befehle gezielt mit `sudo` wiederholen. [NetworkManager-CLI](https://networkmanager.dev/docs/api/latest/nmcli.html)
## 5. Methode B: wg-quick, Terminal und systemd
Diese Methode verwendet die Datei `/etc/wireguard/gnai-laptop.conf`. Nur verwenden, wenn Methode A für dieses Profil nicht aktiv ist:
Diese Methode verwendet die Datei `/etc/wireguard/ghost-laptop.conf`. Nur verwenden, wenn Methode A für dieses Profil nicht aktiv ist:
```bash
sudo install -d -m 700 /etc/wireguard
sudo install -m 600 "$HOME/Downloads/gnai-laptop.conf" /etc/wireguard/gnai-laptop.conf
sudo wg-quick up gnai-laptop
sudo wg show gnai-laptop
ip -br addr show gnai-laptop
sudo install -m 600 "$HOME/.config/ghostnet/wireguard/ghost-laptop.conf" /etc/wireguard/ghost-laptop.conf
sudo wg-quick up ghost-laptop
sudo wg show ghost-laptop
ip -br addr show ghost-laptop
```
Erwartet: Interface `gnai-laptop`, eigene Adresse `10.8.0.x`, ein Handshake nach Verkehr zu einem freigegebenen Ziel. Nicht pauschal `wg0` abfragen; das ist der Interface-Name auf dem Server.
Erwartet: Interface `ghost-laptop`, eigene Adresse `10.8.0.x`, ein Handshake nach Verkehr zu einem freigegebenen Ziel. Nicht pauschal `wg0` abfragen; das ist der Interface-Name auf dem Server.
**DNS-Abhängigkeit:** Wenn das Profil `DNS = ...` enthält, ruft Linux-wg-quick `resolvconf` auf. Vorher prüfen:
@@ -99,26 +114,26 @@ Bei fehlendem Befehl nicht die DNS-Zeile ungeprüft entfernen. Der Administrator
Zum Trennen:
```bash
sudo wg-quick down gnai-laptop
sudo wg-quick down ghost-laptop
```
Optionaler Autostart, erst nach erfolgreicher Prüfung und Absprache:
```bash
sudo systemctl enable wg-quick@gnai-laptop.service
sudo systemctl enable wg-quick@ghost-laptop.service
```
Dies richtet den Bootstart ein. Für systemd-Verwaltung nach einem manuellen `wg-quick down`:
```bash
sudo systemctl start wg-quick@gnai-laptop.service
sudo systemctl status wg-quick@gnai-laptop.service
sudo systemctl start wg-quick@ghost-laptop.service
sudo systemctl status wg-quick@ghost-laptop.service
```
Autostart entfernen und systemd-verwaltete Verbindung stoppen:
```bash
sudo systemctl disable --now wg-quick@gnai-laptop.service
sudo systemctl disable --now wg-quick@ghost-laptop.service
```
## 6. Verbindung prüfen
@@ -13,6 +13,7 @@
| 2026-09-16 | 2.1 | spektr | bestätigten Fail2Ban-Pin und wieder laufenden Container als Querverweis aufgenommen; weitergehende Abnahme nicht unterstellt |
| 2026-09-18 | 2.2 | spektr | Lesenden SSH-Abgleich von CT 105 und Grenzen des Nachweises dokumentiert. |
| 2026-09-22 | 2.3 | Codex | aktuellen Gateway-Zustand live inventarisiert und Berechtigungen der Schlüsseldateien gehärtet |
| 2026-10-05 | 2.4 | Codex | Peerrotation, native nftables-Rollenpolicy, Fremdnetztests sowie Stop-Backup und Wiederanlauf dokumentiert |
## 1. Evidenz und zeitliche Einordnung
@@ -49,15 +50,17 @@ Der aktuelle Quadlet-Pin `15.4.0` ist durch den Betreiber bestätigt. v15 verwen
| Altpeer | VPN-IP | v15-Client-ID laut Quellen | Einordnung |
| --- | --- | --- | --- |
| Laptop-FullAdmin | `10.8.0.2` | 1 | neue Rolle Familie oder Server Admin noch ausdrücklich zuordnen |
| Android-LLMAdmin | `10.8.0.3` | 2 | Altbestand; Android außerhalb des neuen SOP-Umfangs, keine Praxisabnahme |
| Laptop-FullAdmin | `10.8.0.2` | 1 | am 05.10.2026 als Server Admin festgelegt; technische Rollenregeln noch offen |
| Android-LLMAdmin | `10.8.0.3` | 2 | reiner Testpeer; am 05.10.2026 zum Widerruf freigegeben |
Profile wurden laut Log manuell aus der SQLite-Datenbank generiert und gegen
die Serverkonfiguration geprüft. Das ist ein historischer Vorgang, kein
künftiges SOP-Verfahren. Neue Profile werden über die GUI exportiert. Der Pfad
`docs/platform/GNAI-007-profiles/` wird inzwischen vollständig durch die
Repository-`.gitignore` ausgeschlossen; Profile bleiben unabhängig von
Git-Ausschlüssen Geheimnisse.
künftiges SOP-Verfahren. Neue Profile werden über die GUI exportiert. Die
vorhandenen Profile wurden am 05.10.2026 aus dem Repository-Baum nach
`~/.config/ghostnet/wireguard/` verschoben. Das Verzeichnis ist lokal `0700`,
die Profildateien sind `0600`. Im Repository gibt es bewusst keine Ignore-Regel
für einen Profilordner: Eine versehentliche erneute Ablage soll bei
`git status` sichtbar werden. Profile bleiben unabhängig davon Geheimnisse.
Der Laptop wurde von außerhalb des Heimnetzes über EasyCable mit damaliger Außen-IPv4 `80.187.120.60` getestet. Diese und die damalige Heimnetz-WAN-IP sind zeitgebundene Messwerte, keine DNS-Sollwerte.
@@ -110,7 +113,7 @@ Die Fritzbox-Portweiterleitungen sind TCP 80/443→NPM `.10` und UDP 51820→Wir
| F7 | wg-Tool im LXC nicht gefunden | Serverstatus mit `sudo podman exec wg-easy wg show wg0` prüfen |
| F8 | sysctl nicht gefunden | procps/PATH prüfen; Forwarding unter `/proc/sys/net/ipv4/ip_forward` lesbar |
| F9 | wireguard-dkms in CachyOS nicht verfügbar | im getesteten Kernel WireGuard vorhanden; wireguard-tools genügte |
| F10 | zu langer Profil-/Interface-Name | `Laptop-FullAdmin` hat 16 Zeichen; auf `laptop` umbenannt, ohne Präfix |
| F10 | zu langer Profil-/Interface-Name | `Laptop-FullAdmin` hat 16 Zeichen; aktueller Peer und lokale Datei heißen `ghost-laptop` beziehungsweise `ghost-laptop.conf` |
| F11 | öffentliche IP-Abfrage lieferte IPv6 | `curl -4` für einen gezielten IPv4-Vergleich |
## 7. Lesender Live-Abgleich am 18.09.2026
@@ -147,19 +150,151 @@ Repository protokolliert.
| Backup | Nur das historische wg-easy-Backup vom 09.09.2026 wurde gefunden. Ein aktuelles konsistentes Proxmox-/Dateibackup vor Firewall- oder Peer-Änderungen ist nicht belegt. |
| Dateirechte | `wg0.conf` war bereits `0600`. `wg-easy.db` und das historische `wg0.json` wurden von `0644` beziehungsweise `0640` auf `0600` reduziert. Danach waren Dienst und WireGuard-Interface weiterhin funktionsfähig. |
## 9. Offene Umsetzung und Abnahme
## 9. Lokale Profilablage und NetworkManager am 05.10.2026
Die beiden vorhandenen Clientprofile wurden aus dem Repository-Baum nach
`~/.config/ghostnet/wireguard/` verschoben und in `gnai-laptop.conf` sowie
`gnai-android.conf` umbenannt. Beide Basisnamen liegen unter der Linux-Grenze
von 15 Zeichen. Bindestrich und Unterstrich wären zulässig; die Länge von
`Laptop-FullAdmin` war das eigentliche Namensproblem. Übergeordnetes Verzeichnis
und Profilverzeichnis sind `0700`, beide Dateien `0600` und gehören `ghost`.
Auf dem CachyOS-Arbeitsplatz ist NetworkManager 1.58.1 aktiv. Das Laptopprofil
wurde nativ als `gnai-laptop` importiert, auf `connection.autoconnect=no` und
`connection.permissions=user:ghost` gesetzt und nach der beim Import erfolgten
Sofortaktivierung wieder getrennt. Danach existierten weder das Interface noch
eine VPN-Route; das lokale `10.0.0.0/24` blieb direkt über Ethernet erreichbar.
`wireguard-tools` ist nicht installiert und für die nachgewiesene native
NetworkManager-Einrichtung nicht erforderlich. Es wird nur bei Bedarf an
`wg show` oder der alternativen wg-quick-Methode ergänzt. Der Betreiber ordnete
den Laptop am selben Tag der Rolle Server Admin zu und gab den reinen Testpeer
`Android-LLMAdmin` zum Widerruf frei. Ein erneuter Test aus einem fremden Netz,
die technische Rollendurchsetzung und deren Abnahme stehen aus.
Vor dem Widerruf wurde wg-easy kurz gestoppt und ein konsistentes
Anwendungsbackup unter
`/var/backups/ghostnet/wg-easy/ct105-wg-easy-2026-10-05-pre-android-revoke.tar.gz`
erstellt. Es enthält Datenverzeichnis, Environment, Quadlet, nftables- und
sysctl-Konfiguration, gehört `root:root` und ist `0600`. gzip-Test und
SQLite-`integrity_check` waren erfolgreich; SHA-256:
`623fc71c17a43d1359a05c949e2ae42ae5bf21bc11c69b2417386ed0c4f0a37f`.
wg-easy und `wg0` liefen anschließend mit zwei Peers wieder. Das lokale
`gnai-android.conf` wurde endgültig entfernt. Die lokale API lehnte
unangemeldete Session- und Clientzugriffe erwartungsgemäß mit HTTP 401 ab. Der
Betreiber löschte `Android-LLMAdmin` anschließend über eine authentifizierte
GUI-Sitzung. Die Verifikation zeigte genau einen Datenbank- und Laufzeit-Peer
(`Laptop-FullAdmin`), `database_integrity=ok` und einen aktiven wg-easy-Dienst.
Der neue Peer `gnai-ghost-laptop` wurde anschließend mit `10.8.0.3` angelegt;
die sichtbare IPv6-Adresse ist bei wirksamem `DISABLE_IPV6=true` reserviert,
aber `wg0` besitzt nur IPv4. Der globale Client-DNS ist nun Pi-hole
`10.0.0.14`. Der Peername ist als Linux-Interface-Name zu lang, daher bleibt der
lokale Dateiname `ghost-laptop.conf`.
Die Server-Admin-Durchsetzung wurde auf native nftables-Tabellen umgestellt.
`vpn_server_admins` enthält während der Migration `.2` und `.3`;
`server_admin_targets` enthält die einzeln dokumentierten Server einschließlich
Pi-hole `.14`. INPUT erlaubt diesen Peer-IPs TCP 22/51821, FORWARD nur neue
Verbindungen zu den inventarisierten Zielen, und `ip ghostnet_wg_nat` übernimmt
MASQUERADE für `10.8.0.0/24` über `eth0`. Die Konfiguration bestand
`nft --check --file`, wurde mit zweiminütigem Rückfalltimer geladen und über
eine neue SSH-Verbindung, aktive Dienste, `wg0`, Sets, NAT und erhaltene
Netavark-Tabelle geprüft; danach wurde der Timer entfernt. Die vorherige Datei
liegt rootgeschützt unter
`/var/backups/ghostnet/firewall/nftables.conf.pre-native-roles-2026-10-05`.
Beim ersten Laden waren die alten wg-easy-`iptables-legacy`-Hooks und
Laufzeitregeln noch vorhanden; ihre anschließende Bereinigung ist im folgenden
Absatz dokumentiert.
Der Betreiber benannte den neuen Peer anschließend in `ghost-laptop` um und
setzte dessen Allowed IPs auf die elf Server-`/32`. DNS `10.0.0.14` wird aus dem
globalen Default geerbt; `Server Allowed IPs` ist leer. Alle globalen Hooks
wurden geleert. Nach Sicherung des vollständigen Legacy-Regelsatzes unter
`/var/backups/ghostnet/firewall/iptables-legacy-2026-10-05-pre-removal.rules`
wurden exakt die frühere UDP-INPUT-, beide wg0-FORWARD- und die
MASQUERADE-Regel entfernt. Vor und nach einem kontrollierten
wg-easy-Dienstneustart betrug die Zahl der Legacy-Regeln null. Beide Peers,
`wg0`, native nftables-Sets/NAT, Netavark und die GUI blieben aktiv.
Das exportierte neue Profil wurde ohne Ausgabe seiner Schlüssel geprüft:
`10.8.0.3/32`, Pi-hole-DNS, elf Serverziele und der dokumentierte Endpoint waren
korrekt; IPv6 war nicht enthalten. Es liegt als `ghost-laptop.conf` mit `0600`
unter `~/.config/ghostnet/wireguard/` und ist in NetworkManager als manuelle,
inaktive, auf `ghost` beschränkte Verbindung importiert. Der alte lokale
NetworkManager-Eintrag und das alte Profil wurden entfernt. Der serverseitige
Altpeer `.2` blieb bis zur erfolgreichen Fremdnetzabnahme als Rückfall aktiv.
Die Fremdnetzabnahme erfolgte über einen Smartphone-Hotspot im Netz
`10.62.63.0/24`; Ethernet war getrennt. `ghost-laptop` erhielt ausschließlich
die elf vorgesehenen `/32`-Routen. Pi-hole beantwortete interne und externe
DNS-Anfragen, `auth-admin.ghostnet.selfhost.eu` lieferte `10.0.0.10`, und
Proxmox, Keycloak, NPM, Pi-hole, wg-easy sowie Gateway-SSH waren erreichbar.
Die öffentliche IPv4 blieb mit und ohne Tunnel gleich. Serverseitig wurden ein
aktueller Handshake und steigende Transferwerte bestätigt.
Für den Negativtest wurde nur in einer geschützten temporären Profilkopie
`10.0.0.1/32` ergänzt. Bei gleichzeitig erfolgreichem Proxmox-Aufruf waren
Ping und HTTP zur aktiven FRITZ!Box gesperrt; der Gateway-eigene HTTP-Aufruf auf
`.1` lieferte dagegen 200. Nach Ergänzung persistenter nftables-Zähler zeigte
die Wiederholung vier neue Accepts zur Serverzielmenge, drei Drops und vier
NAT-Bindungen. Testverbindung und temporäre Geheimnisdatei wurden entfernt; das
Produktivprofil blieb unverändert.
Nach bestandener Abnahme löschte der Betreiber `Laptop-FullAdmin` über die
authentifizierte wg-easy-GUI. Datenbank und `wg0` enthielten danach genau
`ghost-laptop` `.3`; SQLite-Integritätsprüfung und Dienste waren fehlerfrei.
Die persistente nftables-Datei wurde im Prüfmodus validiert und anschließend so
geladen, dass `vpn_server_admins` nur noch `10.8.0.3` enthält. Split-DNS,
Proxmox und wg-easy blieben erreichbar, die Legacy-Regelzahl blieb null und das
Produktiv-VPN wurde kontrolliert getrennt. Damit ist die Schlüsselrotation des
Laptopzugangs abgeschlossen.
Anschließend erstellte der Betreiber ein vollständiges Proxmox-Backup von
CT 105 im Modus `Stop`. Der Neustart des LXC und von wg-easy um 17:19 Uhr ist
damit belegt. Nach dem Start waren SSH, nftables und wg-easy aktiv; es gab
keine fehlgeschlagene Unit. Die persistente nftables-Datei bestand die
Syntaxprüfung, `vpn_server_admins` enthielt ausschließlich `10.8.0.3`, alle elf
Serverziele und das native MASQUERADE waren vorhanden und `iptables-legacy`
enthielt nur leere Standardketten. Der Container lief mit genau einem
WireGuard-Peer, SQLite meldete `integrity_check=ok` und die lokale GUI
antwortete mit HTTP 302. Ein tatsächlicher Restore des Backups bleibt eine
gesonderte Recovery-Abnahme.
Für die SSH-Härtung wurde die bestehende `99-security.conf` rootgeschützt
gesichert und um `DisableForwarding yes` sowie explizite Verbote für TCP-,
Agent-, StreamLocal-, X11- und Tunnel-Forwarding ergänzt. `sshd -t` bestand,
ein Remote-Forwarding-Versuch wurde abgelehnt und ein neuer Schlüssel-Login
funktionierte. Der erste `reload` traf auf das bekannte
Socket-Aktivierungsproblem: sshd konnte beim SIGHUP den von `ssh.socket`
gehaltenen Port nicht erneut binden; die nächste Verbindung startete den
Dienst korrekt. Künftig wird nach Syntaxprüfung `restart` statt `reload`
verwendet.
Der Symlink
`/etc/systemd/system/timers.target.wants/wtmpdb-rotate.timer` zeigte auf
eine nicht vorhandene Paket-Unit und wurde entfernt. Das Patchverfahren bleibt
bewusst kontrolliert manuell: `apt-daily`-Timer sind aktiv, aber
`unattended-upgrade` und eine wirksame automatische Installation fehlen.
Simulation, Wartungsfenster, gegebenenfalls Stop-Backup und anschließender
Health-Check sind in der Betriebsdokumentation festgelegt. Nach dem
kontrollierten LXC-Neustart um 17:38 Uhr waren keine Units fehlgeschlagen,
sämtliche Forwarding-Arten effektiv aus, Kerndienste und native Firewall aktiv,
Legacy-Regeln leer, genau ein Peer vorhanden, SQLite intakt und die GUI
erreichbar.
## 10. Offene Umsetzung und Abnahme
| ID | Arbeit | Status |
| --- | --- | --- |
| O1 | Fritzbox-Portforward | bestätigt; Laptop-WAN-Handshake als Teilnachweis vorhanden |
| O2 | DynDNS-Aktualisierung nach WAN-IP-Wechsel und Fritzbox-Einstellung prüfen | aktueller selfhost-Endpoint am 18.09.2026 durch DNS-/WAN-Abgleich bestätigt; dyndns.eu zeigt auf andere Adresse |
| O3 | Altpeers neuen Rollen zuordnen | offen |
| O4 | Per-Client Firewall und Gateway-INPUT-Rollenrechte umsetzen | offen |
| O5 | Rollenisolierung bei geänderten lokalen AllowedIPs testen | offen |
| O6 | KI-Server, Proxmox-GUI und SSH testen | offen |
| O3 | Altpeers neuen Rollen zuordnen | abgeschlossen: Android-Testpeer und alter Laptoppeer gelöscht; `ghost-laptop` `.3` ist einziger Server-Admin-Peer |
| O4 | Native nftables-Ziel- und Gateway-INPUT-Rechte umsetzen | abgeschlossen: Policy und NAT aktiv, Legacy-Hooks bereinigt, Dienst- und LXC-Wiederanlauf sowie Rollentests bestanden |
| O5 | Rollenisolierung bei geänderten lokalen AllowedIPs testen | bestanden am 05.10.2026: erlaubtes Ziel parallel erreichbar, aktive `.1` gesperrt, nftables-Zähler geprüft |
| O6 | KI-Server, Proxmox-GUI und SSH testen | Proxmox-GUI und Gateway-SSH bestanden; KI-Server bewusst ausgeschaltet und noch offen |
| O7 | WAN-/IPv6-Managementport-Isolation | offen |
| O8 | Reboot, Firewall-Reload und Restore | offen |
| O8 | Reboot, Firewall-Reload und Restore | Stop-Backup mit vollständigem LXC-Wiederanlauf und persistenter Firewall bestanden; Restore noch offen |
| O9 | wg-easy-GUI-HTTPS und lokales Gruppenmodell | geplant; Werte noch festzulegen |
| O10 | SSH-Forwarding, X11 und automatisches Updateverfahren entscheiden | offen; keine Änderung ohne bestätigten Betriebsbedarf und Wartungsmodell |
| O10 | SSH-Forwarding, X11 und automatisches Updateverfahren entscheiden | abgeschlossen: alle SSH-Forwarding-Arten aus; kontrolliertes manuelles Patchverfahren mit Simulation, Wartungsfenster und Health-Check festgelegt |
Andere Sicherheitsbefunde werden im [Dokumentationsindex](../../../GNAI-000-DOC_Index.md) geführt. Dieses Log hält nur WireGuard-Umsetzung und zugehörige Tests fest.
Andere Sicherheitsbefunde werden im [Dokumentationsindex](../../../../GNAI-000-DOC_Index.md) geführt. Dieses Log hält nur WireGuard-Umsetzung und zugehörige Tests fest.