docs: resilience DSH-VPS (route directe OpenCode) + fix tailscale-vps-ssh-relay + lecon bifrost-proxy auth (24/08/2026)
This commit is contained in:
@@ -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 <override agent-default-model>` (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: <clé>` 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 <clé>` OU `X-Api-Key: <clé>` — **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: <id>` (`network_mode: container:<id>`, 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 <ip-cible> <port-cible>; 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:<ip-cible>:<port-cible>,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 <ctn> 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:<cible>:<port>,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`
|
||||
Reference in New Issue
Block a user