From 8d70bfec7a2ba5158cf45b5b162f5f0d8648962a Mon Sep 17 00:00:00 2001 From: bolbol Date: Mon, 24 Aug 2026 18:55:31 +0000 Subject: [PATCH] docs: resilience DSH-VPS (route directe OpenCode) + fix tailscale-vps-ssh-relay + lecon bifrost-proxy auth (24/08/2026) --- ...-bifrost-proxy-tailscale-relay-20260824.md | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 common/dsh-resilience-opencode-direct-bifrost-proxy-tailscale-relay-20260824.md diff --git a/common/dsh-resilience-opencode-direct-bifrost-proxy-tailscale-relay-20260824.md b/common/dsh-resilience-opencode-direct-bifrost-proxy-tailscale-relay-20260824.md new file mode 100644 index 0000000..33ed9f0 --- /dev/null +++ b/common/dsh-resilience-opencode-direct-bifrost-proxy-tailscale-relay-20260824.md @@ -0,0 +1,90 @@ +# Résilience DSH-VPS face aux coupures NAS + fix tailscale-vps-ssh-relay + leçon bifrost-proxy + +**Date** : 24/08/2026 +**Contexte** : coupure STEG (canicule) ayant coupé le NAS pendant plusieurs heures, DSH-VPS (VPS Contabo, indépendant électriquement) s'est retrouvé hors service car il dépendait entièrement de Bifrost (NAS) pour parler aux modèles. + +## 1. Route directe OpenCode pour DSH (indépendante du NAS) + +**Problème** : DSH ne parlait qu'à Bifrost (`http://100.86.197.88:3086/v1`), lui-même hébergé sur le NAS. Toute coupure NAS = DSH inopérant, même si le VPS tourne. + +**Solution** : ajout d'un second provider dans `settings.yaml` de DSH (volume `dsh_vps_home`, fichier `/home/dsh-agent/.dsh/settings.yaml`), en plus de `bifrost` (pas en remplacement) : + +```yaml +llm-pi-ai: + providers: + bifrost: + # ... inchangé, reste le défaut (agent-default-model) ... + opencode-direct: + displayName: OpenCode Zen (direct, sans NAS) + apiKeyEnv: OPENCODE_DIRECT_API_KEY + api: openai-completions + baseURL: https://opencode.ai/zen/go/v1 + compat: + thinkingFormat: deepseek + models: + - id: deepseek-v4-flash + contextWindow: 131072 + maxTokens: 16384 + - id: deepseek-v4-pro + contextWindow: 131072 + maxTokens: 16384 + - id: mimo-v2.5 + contextWindow: 1048576 + maxTokens: 131000 + - id: ox-alpha-free + contextWindow: 1048576 + maxTokens: 131072 +``` + +Clé (`OPENCODE_DIRECT_API_KEY`) = **même clé** que celle utilisée par Bifrost pour son provider `opencode` (`BIFROST_OPENCODE_KEY` dans `/mnt/docker/hermes-platform/.env` sur le NAS). Même compte, même forfait — juste un deuxième chemin d'accès. Ajoutée en dur dans `.credentials.yaml` du conteneur DSH et dans `docker-compose.yml` (`services.dsh-vps.environment`). + +**⚠️ Conséquence à documenter** : cette clé existe maintenant sur DEUX hôtes (NAS + VPS). En cas de rotation future, penser aux deux. + +**Décision produit (Nabil, 24/08/2026) — OpenCode plutôt qu'OpenRouter pour un modèle gratuit-temporaire** : Bifrost a aussi un provider `openrouter` avec accès large (`"models": ["*"]`). Pour un modèle comme `stealth/ox-alpha` (gratuit maintenant, potentiellement payant demain), la route OpenRouter a été **écartée délibérément** : c'est du pay-as-you-go sans plafond — si le modèle bascule en payant, la facturation continue silencieusement jusqu'à ce qu'on s'en aperçoive. La route OpenCode Zen Go tourne sur un forfait plafonné ($10/mois) : le pire cas est soit une consommation du forfait existant, soit un arrêt net du service — jamais une dérive de facturation. **Règle générale à retenir** : pour tout futur modèle gratuit-temporaire/stealth, préférer systématiquement une route à coût plafonné (forfait) à une route pay-as-you-go pure, même si cette dernière est théoriquement plus permissive. + +**Validation** : testé via `dsh --profile headless --patch ` (pas juste `curl` brut vers l'API OpenCode — un `curl` réussi ne prouve pas que le runtime DSH lui-même sait utiliser la config). 3/3 sur `mimo-v2.5` et `ox-alpha-free` après quelques échecs initiaux dus à la flakiness intrinsèque de l'endpoint OpenCode Zen "go" (gratuit/beta) — pas un bug de config. **Piège à éviter** : ne pas conclure à un bug de config sur un seul test raté sur cet endpoint ; retester plusieurs fois avant de creuser plus loin. + +## 2. Leçon bifrost-proxy — faux positif à ne pas reproduire + +**Ce qui a été (à tort) diagnostiqué comme une régression** : tout appel vers `http://100.86.197.88:3086/v1/...` (bifrost-proxy, exposition Tailscale de Bifrost pour le VPS) avec un header `x-bf-vk: ` renvoyait `401 virtual key is required`, alors que la même clé fonctionnait parfaitement en LAN direct (`172.17.0.1:3085` ou `192.168.100.33:3085`). + +**Cause réelle (pas une panne)** : `bifrost-proxy` est un nginx (openresty) qui **ne transmet pas** le header `x-bf-vk` reçu du client. Sa conf (`/usr/local/openresty/nginx/conf/nginx.conf` dans le conteneur `bifrost-proxy`) le reconstruit lui-même : +```nginx +map $http_authorization $bearer_vk { ~*^Bearer\s+(.+)$ $1; default ""; } +map $http_x_api_key $resolved_vk { "" $bearer_vk; default $http_x_api_key; } +... +proxy_set_header x-bf-vk $resolved_vk; +``` +Donc ce endpoint accepte l'auth via `Authorization: Bearer ` OU `X-Api-Key: ` — **jamais** `x-bf-vk` directement en entrée (ce header n'existe qu'en interne, entre `bifrost-proxy` et `bifrost:8080`, et sur le endpoint LAN direct qui lui l'accepte nativement). Tester avec `x-bf-vk` sur le endpoint Tailscale envoie une VK vide côté proxy → 401 qui ressemble à une vraie panne mais n'en est pas une. + +**Règle à retenir** : pour tester `bifrost-proxy` (port 3086, Tailscale) → `-H "Authorization: Bearer sk-bf-..."`. Pour tester Bifrost en LAN direct (port 3085) → `-H "x-bf-vk: sk-bf-..."`. Deux endpoints, deux conventions différentes — ne pas les confondre au prochain incident. + +## 3. Fix tailscale-vps-ssh-relay (NAS) + +**Symptôme** : conteneur `tailscale-vps-ssh-relay` refusait de démarrer après un reboot NAS : `cannot join network of a non running container: ` (`network_mode: container:`, cible = `tailscale-nyora-bridge`, pas encore démarrée — simple course au démarrage, résolu par un `docker start` une fois la cible up). + +**Vrai problème sous-jacent, découvert en creusant la fiabilité** : `tailscale-nyora-bridge` tourne en **Tailscale mode userspace** — pas de `/dev/net/tun`, pas d'interface `tailscale0`/`tun0` dans son netns (confirmé via `docker exec tailscale-nyora-bridge ip a`). Conséquence : toute connexion TCP directe du noyau vers une IP Tailscale (`100.64.0.0/10`) depuis ce netns **time-out systématiquement** — le trafic doit obligatoirement passer par le proxy SOCKS5 local que Tailscale expose en mode userspace, sur `127.0.0.1:1055`. + +L'ancien relais (`while true; do nc -lk -p 22223 -e nc ; sleep 1; done`, BusyBox nc) cumulait deux problèmes : tentative de connexion TCP directe (donc timeout garanti, indépendamment du reste) ET un `-lk` dont le support par BusyBox nc est douteux (races de bind/`TIME_WAIT` → trous d'écoute intermittents, `nc: bind: Address in use` en boucle). + +**Correctif pérenne déployé** : service dédié `/volume1/docker/tailscale-vps-ssh-relay/` : +```dockerfile +FROM alpine:latest +RUN apk add --no-cache socat +ENTRYPOINT ["socat", "TCP-LISTEN:22223,fork,reuseaddr", "SOCKS5-CONNECT:127.0.0.1::,socksport=1055"] +``` +```yaml +services: + tailscale-vps-ssh-relay: + build: . + network_mode: "container:tailscale-nyora-bridge" + restart: unless-stopped +``` + +**Validation** : 20/20 (bannière SSH via IP Tailscale) + 20/20 (bannière via IP interne Docker) + 5/5 sessions SSH authentifiées réelles côté Gemini ; 10/10 rebanniere confirmés indépendamment par Claude en méthode différente (Python socket direct plutôt que curl). Convergence forte, accepté. + +**Règle générale à retenir** : pour tout futur relais/proxy TCP sortant vers une IP Tailscale depuis un conteneur — vérifier d'abord son mode Tailscale (`docker exec ip a` : présence ou non de `/dev/net/tun` et d'une interface `tailscale0`/`tun0`). En mode userspace (le cas courant pour un conteneur sans `--cap-add=NET_ADMIN --device=/dev/net/tun`), la connexion sortante doit systématiquement passer par `SOCKS5-CONNECT:127.0.0.1::,socksport=1055` (typiquement via `socat`), jamais une connexion TCP directe — celle-ci time-out à coup sûr. + +## Fichiers de référence +- `brief-gemini-fix-bifrost-proxy-ox-alpha-dsh-20260824.md` (historique complet de la décision OpenCode vs OpenRouter, v1→v3) +- `brief-gemini-fix-ssh-relay-20260824.md`