# ia.enso.4nkweb.com — Nginx sur le proxy (192.168.1.100) Reverse TLS vers l’hôte LAN **`192.168.1.164`** (Ollama + AnythingLLM ; IP substituée au déploiement via `__IA_ENSO_BACKEND_IP__` / `IA_ENSO_BACKEND_IP`). ## URLs publiques complètes (HTTPS) | Service | URL | |---------|-----| | **AnythingLLM** (interface) | `https://ia.enso.4nkweb.com/anythingllm/` | | **Ollama** API native (ex. liste des modèles) | `https://ia.enso.4nkweb.com/ollama/api/tags` | | **Ollama** API compatible OpenAI (Cursor, etc.) | base URL `https://ia.enso.4nkweb.com/ollama/v1` — ex. `https://ia.enso.4nkweb.com/ollama/v1/models` | **Bearer nginx :** tout `/ollama/` exige `Authorization: Bearer ` (fichier `map` sur le proxy). La valeur n’est **pas** transmise à Ollama (`Authorization` effacé en amont). AnythingLLM sous `/anythingllm/` : auth **applicative** uniquement. | Chemin (relatif) | Backend | Port LAN | Protection | |------------------|---------|----------|------------| | `/ollama/` | Ollama | `11434` | **Bearer** nginx | | `/anythingllm/` | AnythingLLM | `3001` | Login AnythingLLM | **Contexte Cursor :** une URL en IP privée (ex. `http://192.168.1.164:11434`) peut être refusée par Cursor (`ssrf_blocked`). Un **nom public** HTTPS vers le proxy évite ce blocage si le DNS résolu depuis Internet n’est pas une IP RFC1918. **Fichiers dans le dépôt :** `sites/ia.enso.4nkweb.com.conf`, `http-maps/*.example`, `deploy-ia-enso-to-proxy.sh`. Détails d’architecture : [docs/features/ia-enso-nginx-proxy-ollama-anythingllm.md](../../docs/features/ia-enso-nginx-proxy-ollama-anythingllm.md). --- ## Déploiement recommandé : script SSH Depuis la racine du dépôt **`smart_ide`**, sur une machine avec accès SSH au bastion puis au proxy : ```bash export IA_ENSO_OLLAMA_BEARER_TOKEN='secret-ascii-sans-guillemets-ni-backslash' # accès LAN direct au proxy (.100), sans bastion : # export DEPLOY_SSH_PROXY_HOST= ./deploy/nginx/deploy-ia-enso-to-proxy.sh ``` Si `IA_ENSO_OLLAMA_BEARER_TOKEN` est absent, le script génère un token hex (affichage unique) à conserver pour Cursor. ### Prérequis sur le proxy - `http { include /etc/nginx/conf.d/*.conf; ... }` dans `/etc/nginx/nginx.conf` (sinon le script échoue avec un message explicite). - **Certificats** Let’s Encrypt pour `ia.enso.4nkweb.com` aux chemins du fichier site (`/etc/letsencrypt/live/ia.enso.4nkweb.com/fullchain.pem` et `privkey.pem`). Sans eux, le bloc `listen 443` fait échouer `nginx -t` : voir **Bootstrap TLS** ci-dessous. - **`sudo` non interactif** pour `nginx` et `systemctl reload nginx`. ### Bootstrap TLS (première fois, `nginx -t` impossible) 1. DNS : `ia.enso.4nkweb.com` doit résoudre vers l’entrée publique qui atteint ce proxy (HTTP port 80). 2. Sur le proxy : ```bash sudo install -d -m 0755 /var/www/certbot # Remplacer temporairement le vhost par HTTP seul (fichier dans le dépôt : sites/ia.enso.4nkweb.com.http-only.conf) sudo cp /chemin/smart_ide/deploy/nginx/sites/ia.enso.4nkweb.com.http-only.conf /etc/nginx/sites-available/ia.enso.4nkweb.com.conf sudo nginx -t && sudo systemctl reload nginx sudo certbot certonly --webroot -w /var/www/certbot -d ia.enso.4nkweb.com --non-interactive --agree-tos --register-unsafely-without-email ``` 3. Déployer la config complète : `./deploy/nginx/deploy-ia-enso-to-proxy.sh` (rétablit HTTPS + upstreams). ### Fichiers installés par le script | Chemin sur le proxy | Rôle | |---------------------|------| | `/etc/nginx/conf.d/ia-enso-http-maps.conf` | `map_hash_bucket_size`, `map` Bearer `$ia_enso_ollama_authorized`, et souvent `map` WebSocket | | `/etc/nginx/sites-available/ia.enso.4nkweb.com.conf` | `server` HTTP→HTTPS + HTTPS | | Lien `sites-enabled/ia.enso.4nkweb.com.conf` | Activation du vhost | Si `nginx -t` échoue à cause d’un **doublon** `map $http_upgrade $connection_upgrade`, le script retente avec **Bearer seul** (sans dupliquer le `map` WebSocket). ### Variables d’environnement du script | Variable | Défaut | Rôle | |----------|--------|------| | `IA_ENSO_OLLAMA_BEARER_TOKEN` | généré (`openssl rand -hex 32`) | Secret pour `Authorization: Bearer …` | | `IA_ENSO_SSH_KEY` | `~/.ssh/id_ed25519` | Clé privée SSH | | `IA_ENSO_PROXY_USER` | `ncantu` | Utilisateur SSH sur le proxy | | `IA_ENSO_PROXY_HOST` | `192.168.1.100` | Cible SSH (IP ou hostname LAN) | | `DEPLOY_SSH_PROXY_HOST` | `4nk.myftp.biz` | Bastion ProxyJump ; vide = SSH direct | | `DEPLOY_SSH_PROXY_USER` | idem proxy | Utilisateur sur le bastion | | `IA_ENSO_BACKEND_IP` | `192.168.1.164` | Hôte Ollama + AnythingLLM (IPv4) | Bibliothèque utilisée : `ia_dev/deploy/_lib/ssh.sh` (`BatchMode=yes`). --- ## Déploiement manuel (sans script) ### 1. DNS et TLS Le DNS doit résoudre `ia.enso.4nkweb.com` vers l’entrée publique qui atteint ce proxy. ```bash sudo certbot certonly --webroot -w /var/www/certbot -d ia.enso.4nkweb.com ``` Adapter dans `sites/ia.enso.4nkweb.com.conf` les directives `ssl_certificate` / `ssl_certificate_key` si le répertoire `live/` diffère. ### 2. Maps HTTP (Bearer + WebSocket) Le script déploie `ia-enso-http-maps.conf` avec `map_hash_bucket_size 256`, le `map` Bearer et le `map` WebSocket (ou Bearer seul si doublon WebSocket ailleurs). Installation manuelle : combiner `http-maps/ia-enso-ollama-bearer.map.conf.example` et `websocket-connection.map.conf.example` dans `http { }` si besoin. ### 3. Fichier `server` Le fichier dans le dépôt contient le marqueur `__IA_ENSO_BACKEND_IP__`. Remplacer par l’IPv4 du backend (ex. `192.168.1.164`) avant copie, ou utiliser : ```bash sed "s/__IA_ENSO_BACKEND_IP__/192.168.1.164/g" deploy/nginx/sites/ia.enso.4nkweb.com.conf | sudo tee /etc/nginx/sites-available/ia.enso.4nkweb.com.conf >/dev/null ``` Sans `sed` : éditer le fichier sur le proxy pour remplacer `__IA_ENSO_BACKEND_IP__` par l’IPv4 réelle, puis : ```bash sudo ln -sf /etc/nginx/sites-available/ia.enso.4nkweb.com.conf /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx ``` --- ## Vérifications ### API Ollama via le proxy ```bash curl -sS -o /dev/null -w "%{http_code}\n" \ -H "Authorization: Bearer " \ https://ia.enso.4nkweb.com/ollama/v1/models ``` Attendu : **200** avec le bon secret ; **401** sans `Authorization` ou secret incorrect. ### AnythingLLM Navigateur : `https://ia.enso.4nkweb.com/anythingllm/` (redirection vers `/anythingllm/`). Connexion avec les identifiants **AnythingLLM**. Si les assets statiques échouent, vérifier la doc upstream (sous-chemin, en-têtes `X-Forwarded-*`). ### Cursor - URL de base OpenAI : `https://ia.enso.4nkweb.com/ollama/v1` - Clé API : **identique** au secret du `map` nginx (sans préfixe `Bearer ` dans le champ). **`streamFromAgentBackend` (comportement observé)** : dans l’application Cursor (bundle Electron), le flux **Agent / chat** appelle une couche interne qui ouvre un stream vers **les serveurs Cursor** (`getAgentStreamResponse`, etc.), pas un `fetch` direct depuis ton poste vers ton URL OpenAI override. Cursor peut donc valider une **« User API key »** ou des droits **avant** ou **en parallèle** de l’usage de l’override. Si **`curl`** avec le Bearer vers `/ollama/v1/models` renvoie **200** mais Cursor affiche **`ERROR_BAD_USER_API_KEY`**, l’échec reste **côté client / infra Cursor** : [forum](https://forum.cursor.com/t/unauthorized-user-api-key-with-custom-openai-api-key-url/132572). Le code minifié du produit n’est pas dans ce dépôt ; seuls les noms de fonctions dans la stack trace décrivent ce chemin d’exécution. --- ## Pare-feu backend Sur **`192.168.1.164`**, n’autoriser **11434** et **3001** TCP que depuis **192.168.1.100** (proxy) si un pare-feu hôte est actif. --- ## Rotation du secret Bearer 1. Mettre à jour `"Bearer …"` dans `/etc/nginx/conf.d/ia-enso-http-maps.conf` (ou redéployer avec `IA_ENSO_OLLAMA_BEARER_TOKEN`). 2. `sudo nginx -t && sudo systemctl reload nginx`. 3. Mettre à jour la clé API dans Cursor (et tout autre client). --- ## Dépannage | Symptôme | Piste | |----------|--------| | `nginx -t` erreur sur `connection_upgrade` | Doublon de `map $http_upgrade $connection_upgrade` : retirer l’un des blocs ou laisser le déploiement « Bearer seul » du script. | | `could not build map_hash` / `map_hash_bucket_size` | Secret Bearer long : le fichier généré par le script inclut `map_hash_bucket_size 256;`. | | `401` sur `/ollama/` | Secret différent entre client et `map` ; en-tête `Authorization` absent ou mal formé. | | `502` / timeout | Ollama ou AnythingLLM arrêtés sur le backend ; pare-feu ; mauvaise IP dans `upstream` (vérifier `grep server /etc/nginx/sites-available/ia.enso.4nkweb.com.conf` sur le proxy ; redéployer avec `IA_ENSO_BACKEND_IP=192.168.1.164`). | | Erreur SSL / `cannot load certificate` | Certificat absent : exécuter certbot sur le proxy pour `ia.enso.4nkweb.com`, ou adapter les chemins `ssl_certificate` dans le fichier site. | | Cursor `ssrf_blocked` | L’hôte utilisé résout encore vers une IP privée côté infrastructure Cursor ; vérifier DNS public / NAT. |