diff --git a/deploy/nginx/README-ia-enso.md b/deploy/nginx/README-ia-enso.md index 07e6960..a24425b 100644 --- a/deploy/nginx/README-ia-enso.md +++ b/deploy/nginx/README-ia-enso.md @@ -1,57 +1,93 @@ -# ia.enso.4nkweb.com — Nginx on the proxy (192.168.1.100) +# ia.enso.4nkweb.com — Nginx sur le proxy (192.168.1.100) -## Automated deploy (from a machine with SSH access) +Reverse TLS vers l’hôte LAN **`192.168.1.164`** : -From the `smart_ide` repository root: +| Chemin public | Backend | Port | Protection | +|---------------|---------|------|------------| +| `/ollama/` | Ollama API | `11434` | **Bearer** vérifié par nginx ; en-tête `Authorization` **retiré** avant Ollama | +| `/anythingllm/` | AnythingLLM | `3001` | Auth **application** AnythingLLM (pas le Bearer Ollama) | -```bash -export IA_ENSO_OLLAMA_BEARER_TOKEN='your-long-secret' # optional: omit to auto-generate (printed once) -export DEPLOY_SSH_PROXY_HOST='4nk.myftp.biz' # optional: empty for direct LAN SSH to proxy -./deploy/nginx/deploy-ia-enso-to-proxy.sh -``` +**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. -Uses `ia_dev/deploy/_lib/ssh.sh` (BatchMode, ProxyJump). Requires passwordless `sudo` for `nginx` on the proxy. +**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). --- -Reverse proxy to `192.168.1.164`: +## Déploiement recommandé : script SSH -- `https://ia.enso.4nkweb.com/ollama/` → Ollama `11434` (Bearer gate, then `Authorization` cleared upstream). -- `https://ia.enso.4nkweb.com/anythingllm/` → AnythingLLM `3001`. +Depuis la racine du dépôt **`smart_ide`**, sur une machine avec accès SSH au bastion puis au proxy : -## 1. DNS and TLS +```bash +export IA_ENSO_OLLAMA_BEARER_TOKEN='secret-long-ascii-sans-guillemets-ni-backslash' +# optionnel si accès LAN direct au proxy, sans bastion : +# export DEPLOY_SSH_PROXY_HOST= +./deploy/nginx/deploy-ia-enso-to-proxy.sh +``` -DNS must resolve `ia.enso.4nkweb.com` to the public entry that reaches this proxy. Issue a certificate, for example: +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 (voir section TLS). +- **`sudo` non interactif** pour `nginx` et `systemctl reload nginx`. + +### Fichiers installés par le script + +| Chemin sur le proxy | Rôle | +|---------------------|------| +| `/etc/nginx/conf.d/ia-enso-http-maps.conf` | `map` Bearer (`$ia_enso_ollama_authorized`) et, si besoin, `map` WebSocket (`$connection_upgrade`) | +| `/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` déjà présent ailleurs, le script retente avec **Bearer seul** dans `ia-enso-http-maps.conf`. + +### Variables d’environnement du script + +| Variable | Défaut | Rôle | +|----------|--------|------| +| `IA_ENSO_OLLAMA_BEARER_TOKEN` | généré | 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 | + +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 ``` -Adjust `ssl_certificate` paths in `sites/ia.enso.4nkweb.com.conf` if the live directory name differs. +Adapter dans `sites/ia.enso.4nkweb.com.conf` les directives `ssl_certificate` / `ssl_certificate_key` si le répertoire `live/` diffère. -## 2. HTTP-level maps (required) +### 2. Maps HTTP (`$ia_enso_ollama_authorized`, WebSocket) -Copy the examples on the proxy and include them **inside** `http { }` **before** `server` blocks that use the variables: +**Option A — un seul fichier sous `conf.d` (équivalent au script)** +Créer `/etc/nginx/conf.d/ia-enso-http-maps.conf` en reprenant le contenu généré par le script ou en combinant : -From a checkout of this repository on the admin machine (paths relative to `deploy/nginx/http-maps/`): +- `http-maps/websocket-connection.map.conf.example` (uniquement si `$connection_upgrade` n’existe pas déjà dans l’instance), +- et un `map $http_authorization $ia_enso_ollama_authorized { ... "Bearer " 1; }`. -```bash -sudo mkdir -p /etc/nginx/http-maps -sudo cp deploy/nginx/http-maps/ia-enso-ollama-bearer.map.conf.example /etc/nginx/http-maps/ia-enso-ollama-bearer.map.conf -sudo cp deploy/nginx/http-maps/websocket-connection.map.conf.example /etc/nginx/http-maps/websocket-connection.map.conf -sudo nano /etc/nginx/http-maps/ia-enso-ollama-bearer.map.conf # set the Bearer secret (single line value) -``` - -In `/etc/nginx/nginx.conf` (or a file already included from `http { }`). Include the websocket map **only if** `$connection_upgrade` is not already defined elsewhere (duplicate `map` names will fail `nginx -t`): +**Option B — fichiers séparés sous `/etc/nginx/http-maps/`** +Copier les `.example` sans suffixe, éditer le secret Bearer, puis dans `http { }` : ```nginx include /etc/nginx/http-maps/websocket-connection.map.conf; include /etc/nginx/http-maps/ia-enso-ollama-bearer.map.conf; ``` -Do not commit the non-example `ia-enso-ollama-bearer.map.conf` with a real secret. +Ne pas commiter un fichier contenant le secret réel. -## 3. Site file +### 3. Fichier `server` ```bash sudo cp deploy/nginx/sites/ia.enso.4nkweb.com.conf /etc/nginx/sites-available/ia.enso.4nkweb.com.conf @@ -59,22 +95,52 @@ sudo ln -sf /etc/nginx/sites-available/ia.enso.4nkweb.com.conf /etc/nginx/sites- sudo nginx -t && sudo systemctl reload nginx ``` -## 4. Checks +--- + +## Vérifications + +### API Ollama via le proxy ```bash -curl -sS -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer CHANGE_ME_TO_LONG_RANDOM_SECRET" \ +curl -sS -o /dev/null -w "%{http_code}\n" \ + -H "Authorization: Bearer " \ https://ia.enso.4nkweb.com/ollama/v1/models ``` -Expect `200`. Without the header or with a wrong token, expect `401`. +Attendu : **200** avec le bon secret ; **401** sans en-tête ou secret incorrect. -AnythingLLM: open `https://ia.enso.4nkweb.com/anythingllm/` and use the **application** login. If static assets fail to load, verify upstream base-path settings for AnythingLLM or adjust proxy headers per upstream docs. +### AnythingLLM -## 5. Cursor (OpenAI-compatible) +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-*`). -- Override base URL: `https://ia.enso.4nkweb.com/ollama/v1` -- API key: **exactly** the same string as in the map after `Bearer ` (Cursor sends `Authorization: Bearer `; nginx compares the full `Authorization` value to `Bearer `). +### Cursor -## 6. Backend firewall +- URL de base OpenAI : `https://ia.enso.4nkweb.com/ollama/v1` +- Clé API : **identique** au secret Bearer (sans préfixe `Bearer ` dans le champ ; Cursor envoie `Authorization: Bearer `). -Allow from the proxy host only: TCP `11434` and `3001` on `192.168.1.164` if a host firewall is enabled. +--- + +## 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 la ligne `"Bearer …"` dans `/etc/nginx/conf.d/ia-enso-http-maps.conf` (ou le fichier `map` manuel équivalent). +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 n’installer que le `map` Bearer. | +| `401` sur `/ollama/` | Secret différent entre client et `map` ; en-tête `Authorization` absent ou mal formé (`Bearer ` + secret exact). | +| `502` / timeout | Ollama ou AnythingLLM arrêtés sur `.164` ; pare-feu ; mauvais IP/upstream dans le fichier site. | +| Erreur SSL | Certificat absent ou chemins `ssl_certificate` incorrects pour `ia.enso.4nkweb.com`. | +| Cursor `ssrf_blocked` | L’hôte utilisé résout encore vers une IP privée côté infrastructure Cursor ; vérifier DNS public / NAT. | diff --git a/docs/README.md b/docs/README.md index 2a9b173..74f9ee5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,8 +6,13 @@ Operational, architectural, and UX-design notes for the local-AI IDE initiative |----------|---------| | [../README.md](../README.md) | Project overview (French): vision, Lapce, AnythingLLM per project | | [deployment-target.md](./deployment-target.md) | First target: Linux client + SSH remote server (AI stack + repos) | +| [ia_dev-submodule.md](./ia_dev-submodule.md) | Git submodule `ia_dev` (clone, update, SSH URL) | +| [lecoffre_ng-checkout.md](./lecoffre_ng-checkout.md) | Plain clone `lecoffre_ng` next to `smart_ide` (`/home/ncantu/code`) | +| [split-lecoffre-repos.md](./split-lecoffre-repos.md) | Split monorepo into five Gitea repos (`setup/split-lecoffre-ng-to-five-repos.sh`) | | [infrastructure.md](./infrastructure.md) | Host inventory (LAN), SSH key workflow, host scripts | | [services.md](./services.md) | Ollama, AnythingLLM (Docker), Desktop installer, Ollama ↔ Docker | +| [../deploy/nginx/README-ia-enso.md](../deploy/nginx/README-ia-enso.md) | Proxy HTTPS `ia.enso.4nkweb.com` → Ollama / AnythingLLM (Bearer, script SSH, dépannage) | +| [features/ia-enso-nginx-proxy-ollama-anythingllm.md](./features/ia-enso-nginx-proxy-ollama-anythingllm.md) | Fiche évolution : objectifs, impacts, modalités du reverse proxy ia.enso | | [anythingllm-workspaces.md](./anythingllm-workspaces.md) | One AnythingLLM workspace per project; sync pipeline | | [ux-navigation-model.md](./ux-navigation-model.md) | Beyond file explorer: intentions, graph, palette, risks, expert mode | | [system-architecture.md](./system-architecture.md) | Layers, modules, agent gateway, OpenShell, events, Lapce | diff --git a/docs/features/ia-enso-nginx-proxy-ollama-anythingllm.md b/docs/features/ia-enso-nginx-proxy-ollama-anythingllm.md index 40cb745..1c6785a 100644 --- a/docs/features/ia-enso-nginx-proxy-ollama-anythingllm.md +++ b/docs/features/ia-enso-nginx-proxy-ollama-anythingllm.md @@ -8,33 +8,36 @@ Expose Ollama and AnythingLLM on the public proxy hostname with HTTPS, path pref ## Impacts -- **Proxy (nginx):** new `server_name`, TLS, locations, HTTP `map` for Bearer validation; optional new includes under `/etc/nginx/http-maps/`. +- **Proxy (nginx):** new `server_name`, TLS, locations, HTTP `map` for Bearer validation; maps deployed under `/etc/nginx/conf.d/` when using the provided script. - **Backend (192.168.1.164):** must accept connections from the proxy on `11434` and `3001`; Ollama must not rely on the client `Authorization` header (nginx clears it after validation). -- **Clients:** Cursor uses `https://ia.enso.4nkweb.com/ollama/v1` and the shared secret as API key; avoids private-IP SSRF blocks in Cursor when the hostname resolves publicly. +- **Clients:** Cursor uses `https://ia.enso.4nkweb.com/ollama/v1` and the shared secret as API key; avoids private-IP SSRF blocks in Cursor when the hostname resolves publicly from the client infrastructure. -## Modifications (repository) +## Repository layout -- `deploy/nginx/http-maps/ia-enso-ollama-bearer.map.conf.example` — `map` for `$ia_enso_ollama_authorized`. -- `deploy/nginx/http-maps/websocket-connection.map.conf.example` — `map` for `$connection_upgrade` (AnythingLLM WebSocket). -- `deploy/nginx/sites/ia.enso.4nkweb.com.conf` — `server` blocks and upstreams. -- `deploy/nginx/deploy-ia-enso-to-proxy.sh` — push maps + site over SSH, `nginx -t`, reload (Bearer-only retry if websocket `map` already exists). -- `deploy/nginx/README-ia-enso.md` — installation and verification on the proxy. +| Path | Purpose | +|------|---------| +| `deploy/nginx/sites/ia.enso.4nkweb.com.conf` | `server` blocks, upstreams to `192.168.1.164` | +| `deploy/nginx/http-maps/ia-enso-ollama-bearer.map.conf.example` | Example Bearer `map` (manual install) | +| `deploy/nginx/http-maps/websocket-connection.map.conf.example` | Example WebSocket `map` (manual install) | +| `deploy/nginx/deploy-ia-enso-to-proxy.sh` | SSH deploy: maps + site, `nginx -t`, reload; Bearer-only retry if websocket `map` already exists | +| `deploy/nginx/README-ia-enso.md` | **Operator reference:** automated + manual steps, env vars, checks, troubleshooting | ## Deployment modalities -1. DNS for `ia.enso.4nkweb.com` points to the proxy entry used for HTTPS. -2. Obtain TLS certificates (e.g. certbot) for that name. -3. Install map files under `/etc/nginx/http-maps/`, set the Bearer secret, include maps inside `http { }`. -4. Install the site file under `sites-available` / `sites-enabled`, `nginx -t`, reload nginx. -5. Restrict backend ports at the firewall to the proxy source where applicable. +**Preferred:** run `./deploy/nginx/deploy-ia-enso-to-proxy.sh` from `smart_ide` on a host with SSH access (see `README-ia-enso.md` for prerequisites and environment variables). + +**Manual:** DNS → TLS (certbot) → install `map` directives inside `http { }` (via `conf.d` or `http-maps` includes) → install site under `sites-available` / `sites-enabled` → `nginx -t` → reload. Details: `deploy/nginx/README-ia-enso.md`. + +Restrict backend ports on `192.168.1.164` to the proxy source where a host firewall is used. ## Analysis modalities - `curl` to `/ollama/v1/models` with and without `Authorization: Bearer ` (expect 200 / 401). - Browser access to `/anythingllm/` and application login. -- Cursor connectivity after configuration change (no `ssrf_blocked` if hostname resolves to a public IP from Cursor’s perspective). +- Cursor connectivity after configuration (no `ssrf_blocked` if the hostname does not resolve to a blocked private IP from Cursor’s perspective). ## Security notes -- The Bearer secret is equivalent to an API key; rotate by updating the map file and client configs together. +- The Bearer secret is equivalent to an API key; rotate by updating the `map` file and client configs together. - AnythingLLM remains protected by **its own** application authentication; the `/anythingllm` location does not add the Ollama Bearer gate. +- A public URL for `/ollama` exposes the inference endpoint to anyone who knows the secret; combine with network controls if required. diff --git a/docs/infrastructure.md b/docs/infrastructure.md index aa3cb34..09f49ad 100644 --- a/docs/infrastructure.md +++ b/docs/infrastructure.md @@ -25,6 +25,12 @@ Private segment **192.168.1.0/24** (DHCP with MAC reservations). The table match Internet access to backends uses **SSH ProxyJump** via `ncantu@4nk.myftp.biz` (see `JUMP` in `add-ssh-key.sh`). On the same LAN, direct `ssh ncantu@192.168.1.x` is valid. +## Reverse proxy `ia.enso.4nkweb.com` (Ollama / AnythingLLM) + +Hostname TLS sur le **proxy** `192.168.1.100` : préfixes `/ollama` et `/anythingllm` vers l’hôte LAN `192.168.1.164` (ports `11434` et `3001`). Gate Ollama par **Bearer** au nginx ; AnythingLLM reste derrière son auth applicative. + +Documentation opérationnelle : [deploy/nginx/README-ia-enso.md](../deploy/nginx/README-ia-enso.md). Fiche évolution : [features/ia-enso-nginx-proxy-ollama-anythingllm.md](./features/ia-enso-nginx-proxy-ollama-anythingllm.md). + ## Scripts (infrastructure / access) ### `add-ssh-key.sh` diff --git a/docs/services.md b/docs/services.md index 5bac4af..b314328 100644 --- a/docs/services.md +++ b/docs/services.md @@ -99,4 +99,9 @@ The last command must succeed after `OLLAMA_HOST=0.0.0.0:11434` and `host.docker ## Public reverse proxy (ia.enso.4nkweb.com) -When Ollama runs on a LAN host (e.g. `192.168.1.164`) and must be reached via the **proxy** with HTTPS and a **Bearer** gate (for clients such as Cursor that block private IPs), use the nginx snippets in `deploy/nginx/` and the steps in `deploy/nginx/README-ia-enso.md`. Cursor base URL: `https://ia.enso.4nkweb.com/ollama/v1`; API key must match the configured Bearer secret. Feature note: [ia-enso-nginx-proxy-ollama-anythingllm.md](./features/ia-enso-nginx-proxy-ollama-anythingllm.md). +When Ollama runs on a LAN host (e.g. `192.168.1.164`) and must be reached via the **proxy** with HTTPS and a **Bearer** gate (for clients such as Cursor that block private IPs), use `deploy/nginx/` and **[deploy/nginx/README-ia-enso.md](../deploy/nginx/README-ia-enso.md)** (script `deploy-ia-enso-to-proxy.sh`, checks, troubleshooting). + +- Cursor base URL: `https://ia.enso.4nkweb.com/ollama/v1` +- Cursor API key: same value as the Bearer secret configured on the proxy + +Feature note: [ia-enso-nginx-proxy-ollama-anythingllm.md](./features/ia-enso-nginx-proxy-ollama-anythingllm.md).