From eeebbd06eb431abc3e04ed83b6917468b374bdb0 Mon Sep 17 00:00:00 2001 From: Mikei386 <44135113+Mikei386@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:56:12 +0200 Subject: [PATCH] Fix UI networking across gateway recreation --- ATHENA.md | 12 ++++++---- compose.yaml | 9 ++------ dev/test_recovery.py | 22 ++++++++++++------- docs/ARCHITECTURE.md | 11 ++++++---- install.sh | 1 - manage.sh | 14 ------------ .../docker/wireguard-gateway/entrypoint.sh | 2 ++ smoke-test.sh | 7 ++++++ 8 files changed, 40 insertions(+), 38 deletions(-) diff --git a/ATHENA.md b/ATHENA.md index 5da812b..224db31 100644 --- a/ATHENA.md +++ b/ATHENA.md @@ -31,10 +31,14 @@ werden keine zweiten Instanzen dieser Dienste angelegt. | `/etc/mike-ai` | lokale Konfiguration und Secrets, niemals Git | Portainer läuft als separater, optionaler Verwaltungscontainer -`mike-ai-portainer`, teilt den Netzwerk-Namespace des WireGuard-Gateways und -ist unter `https://192.168.1.212:9443` erreichbar. Seine Einstellungen liegen -im Docker-Volume `portainer_data`, das vom Athena-Backup mitgesichert wird. -Portainer beobachtet beziehungsweise +`mike-ai-portainer` im internen Frontend-Netz und ist ausschließlich über den +namensbasierten Proxy des WireGuard-Gateways unter +`https://192.168.1.212:9443` erreichbar. Das Dashboard verwendet denselben +stabilen Aufbau auf Port 8099. Beide teilen ausdrücklich nicht den +Netzwerk-Namespace des Gateway-Containers: Ein Recreate des Gateways kann sie +dadurch nicht mehr in einem veralteten Namespace zurücklassen. Portainers +Einstellungen liegen im Docker-Volume `portainer_data`, das vom Athena-Backup +mitgesichert wird. Portainer beobachtet beziehungsweise verwaltet Docker, ist aber keine Abhängigkeit des Inferenz-Stacks. ## Standardbefehle diff --git a/compose.yaml b/compose.yaml index 08535be..30cb741 100644 --- a/compose.yaml +++ b/compose.yaml @@ -50,11 +50,6 @@ services: - /tmp:size=16m,mode=1777 volumes: - "${WIREGUARD_CONFIG_FILE:-/etc/mike-ai/wireguard/fritz-athena.conf}:/run/secrets/fritz-athena.conf:ro" - # Namespace-sharing services cannot publish ports themselves. The owner - # must keep these bindings so a gateway recreation cannot hide their UIs. - ports: - - "8099:8099" - - "9443:9443" networks: frontend: ipv4_address: 172.30.10.254 @@ -843,7 +838,7 @@ services: image: mike-ai/llama-dashboard:local container_name: mike-ai-llama-dashboard restart: unless-stopped - network_mode: "service:wireguard-gateway" + networks: [frontend] gpus: all read_only: true tmpfs: @@ -884,7 +879,7 @@ services: image: ${PORTAINER_IMAGE:-portainer/portainer-ce@sha256:511f3f06c96fe3b993ebeaafde311c1959cae73a7ef825dba6397d51b450dffa} container_name: mike-ai-portainer restart: unless-stopped - network_mode: "service:wireguard-gateway" + networks: [frontend] command: [--no-setup-token] volumes: - /var/run/docker.sock:/var/run/docker.sock diff --git a/dev/test_recovery.py b/dev/test_recovery.py index 9a529f7..2916185 100644 --- a/dev/test_recovery.py +++ b/dev/test_recovery.py @@ -82,17 +82,23 @@ class RecoveryScriptTests(unittest.TestCase): for profile in ("fast", "medium", "large", "ultra", "uncensored"): self.assertIn(f"llama-{profile}", installer) - def test_gateway_consumers_are_stopped_before_gateway_recreation(self) -> None: + def test_gateway_consumers_do_not_require_rebinding(self) -> None: manager = (ROOT / "manage.sh").read_text(encoding="utf-8") - stop = 'stop llama-dashboard portainer' - deploy = 'up -d --build' - self.assertIn(stop, manager) - self.assertLess(manager.index(stop), manager.index(deploy)) + installer = (ROOT / "install.sh").read_text(encoding="utf-8") + self.assertNotIn('stop llama-dashboard portainer', manager) + self.assertNotIn('stop llama-dashboard portainer', installer) + self.assertNotIn('force-recreate llama-dashboard portainer', manager) - def test_gateway_owns_shared_ui_ports_and_portainer_backup(self) -> None: + def test_gateway_proxies_stable_ui_services_and_portainer_backup(self) -> None: compose = (ROOT / "compose.yaml").read_text(encoding="utf-8") - self.assertIn('"8099:8099"', compose) - self.assertIn('"9443:9443"', compose) + gateway = (ROOT / "platform/docker/wireguard-gateway/entrypoint.sh").read_text( + encoding="utf-8" + ) + self.assertNotIn('network_mode: "service:wireguard-gateway"', compose) + self.assertNotIn('"8099:8099"', compose) + self.assertNotIn('"9443:9443"', compose) + self.assertIn('start_proxy 8099 llama-dashboard:8099', gateway) + self.assertIn('start_proxy 9443 portainer:9443', gateway) self.assertIn('portainer-data:/backup/volumes/portainer-data:ro', compose) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index cbf0ff7..0eb4ae6 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -19,10 +19,10 @@ flowchart LR H --> X[Nginx-Proxy-Manager-MCP] U --> M[Media-Tools
ffmpeg / ffprobe / yt-dlp] - W[WireGuard-Gateway
Athena] --- R - W --- B[Athena Dashboard :8099] - W --- PRT[Portainer :9443
optionale Docker-Ansicht] - W --- O[Athena Operator] + W[WireGuard-Gateway
Athena] -->|DNS-Proxy| R + W -->|DNS-Proxy :8099| B[Athena Dashboard
internes Frontend-Netz] + W -->|DNS-Proxy :9443| PRT[Portainer
internes Frontend-Netz] + W -->|DNS-Proxy| O[Athena Operator] K[Backup alle 5 Stunden] --> DATA[/data und /etc/mike-ai] ``` @@ -34,6 +34,9 @@ flowchart LR - **MUA** verwaltet Unraid. **Athena Operator** bleibt auf den Athena-Host begrenzt. - Der Router ist die einzige Modelladresse, die Hermes kennen muss. +- Dashboard und Portainer besitzen eigene Netzwerk-Namespaces. Das + WireGuard-Gateway löst ihre stabilen Compose-Dienstnamen bei jeder + Verbindung neu auf; seine konkrete Container-ID ist damit irrelevant. ## Dynamische Qwen-Profile diff --git a/install.sh b/install.sh index 08da825..8ee955f 100755 --- a/install.sh +++ b/install.sh @@ -517,7 +517,6 @@ build_and_start() { llama-fast llama-medium llama-beta1 llama-large llama-ultra llama-uncensored docker compose --env-file "$SECRETS_DIR/stack.env" --profile image create image-worker docker volume inspect portainer_data >/dev/null 2>&1 || docker volume create portainer_data >/dev/null - docker compose --env-file "$SECRETS_DIR/stack.env" stop llama-dashboard portainer docker compose --env-file "$SECRETS_DIR/stack.env" up -d --build \ wireguard-gateway qwen3-tts piper tts-gateway profile-controller router llama-dashboard portainer backup if [[ ${WIREGUARD_MODE:-container} == container ]]; then diff --git a/manage.sh b/manage.sh index 1a368aa..f404812 100755 --- a/manage.sh +++ b/manage.sh @@ -51,24 +51,10 @@ case "$command" in shift [[ $# -eq 0 ]] || { echo "core akzeptiert keine weiteren Services" >&2; exit 2; } run "$ROOT_DIR/platform/mcp/install-tools.sh" - # Release the old gateway namespace (and its fixed network addresses) - # before replacing its owner. Otherwise Docker can strand the host with - # the old namespace still held by these two consumers. - run "${compose[@]}" stop llama-dashboard portainer run "${compose[@]}" up -d --build \ wireguard-gateway piper xtts tts-gateway profile-controller router llama-dashboard portainer backup else - rebind_gateway=false - for service in "$@"; do - [[ $service == wireguard-gateway ]] && rebind_gateway=true - done - if [[ $rebind_gateway == true ]]; then - run "${compose[@]}" stop llama-dashboard portainer - fi run "${compose[@]}" up -d --build --no-deps "$@" - if [[ $rebind_gateway == true ]]; then - run "${compose[@]}" up -d --no-deps --force-recreate llama-dashboard portainer - fi fi ;; purge-legacy) diff --git a/platform/docker/wireguard-gateway/entrypoint.sh b/platform/docker/wireguard-gateway/entrypoint.sh index 0dabbb5..dfc5fec 100644 --- a/platform/docker/wireguard-gateway/entrypoint.sh +++ b/platform/docker/wireguard-gateway/entrypoint.sh @@ -77,6 +77,8 @@ start_proxy 22 172.30.10.1:22 start_proxy 8081 router:8081 start_proxy 8085 tts-gateway:8085 start_proxy 8091 piper:8085 +start_proxy 8099 llama-dashboard:8099 start_proxy 8202 mcp-athena-operator:8000 +start_proxy 9443 portainer:9443 wait $(printf '%s\n' "$proxy_pids" | awk '{print $2}') diff --git a/smoke-test.sh b/smoke-test.sh index 49ce016..452b09d 100755 --- a/smoke-test.sh +++ b/smoke-test.sh @@ -37,6 +37,13 @@ for name in \ done pass "Athena-Kerndienste sind gesund" +for name in mike-ai-llama-dashboard mike-ai-portainer; do + network_mode=$(docker inspect -f '{{.HostConfig.NetworkMode}}' "$name" 2>/dev/null || true) + [[ $network_mode != container:* ]] || \ + fail "$name teilt noch einen fluechtigen Container-Netzwerk-Namespace" +done +pass "Dashboard und Portainer besitzen stabile Netzwerk-Namespaces" + active_llama=$(docker ps --format '{{.Names}}' | \ grep -Ec '^mike-ai-llama-(fast|medium|beta1|large|ultra|uncensored)$' || true) [[ $active_llama -eq 1 ]] || fail "$active_llama aktive llama-Profile (erwartet: 1)"