Add: DNS über Pi-Hole LXC. Härtung von CT-105 Wireguard.
This commit is contained in:
@@ -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
@@ -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).
|
||||
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
@@ -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
-3
@@ -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
|
||||
@@ -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)
|
||||
+27
-9
@@ -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:
|
||||
|
||||
+37
-15
@@ -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. |
|
||||
+19
-19
@@ -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.
|
||||
+37
-22
@@ -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
|
||||
+150
-15
@@ -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.
|
||||
Reference in New Issue
Block a user