Re-enable nginx Bearer auth on ia.enso /ollama

**Motivations:**
- Restore gate on /ollama/; document Cursor streamFromAgentBackend note.

**Root causes:**
- N/A.

**Correctifs:**
- location /ollama/ if map + clear Authorization upstream; deploy script emits Bearer + websocket maps with retry bearer_only.

**Evolutions:**
- README Cursor subsection on streamFromAgentBackend (observed behavior); feature/services/infrastructure aligned.

**Pages affectées:**
- deploy/nginx/sites/ia.enso.4nkweb.com.conf
- deploy/nginx/deploy-ia-enso-to-proxy.sh
- deploy/nginx/README-ia-enso.md
- deploy/nginx/http-maps/ia-enso-ollama-bearer.map.conf.example
- docs/features/ia-enso-nginx-proxy-ollama-anythingllm.md
- docs/services.md
- docs/infrastructure.md
This commit is contained in:
Nicolas Cantu 2026-03-23 07:49:06 +01:00
parent c13ce79696
commit c4215044f0
7 changed files with 99 additions and 66 deletions

View File

@ -10,11 +10,11 @@ Reverse TLS vers lhôte LAN **`192.168.1.164`** (Ollama + AnythingLLM ; IP su
| **Ollama** API native (ex. liste des modèles) | `https://ia.enso.4nkweb.com/ollama/api/tags` | | **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` | | **Ollama** API compatible OpenAI (Cursor, etc.) | base URL `https://ia.enso.4nkweb.com/ollama/v1` — ex. `https://ia.enso.4nkweb.com/ollama/v1/models` |
**Sécurité :** `/ollama/` na **pas** de garde Bearer nginx par défaut : toute personne qui peut joindre lURL peut utiliser lAPI Ollama. Restreindre par **pare-feu** (IP du proxy uniquement vers `.164`) ou réintroduire un `map` Bearer (voir `http-maps/ia-enso-ollama-bearer.map.conf.example`). AnythingLLM sous `/anythingllm/` reste derrière son **login applicatif**. **Bearer nginx :** tout `/ollama/` exige `Authorization: Bearer <secret>` (fichier `map` sur le proxy). La valeur nest **pas** transmise à Ollama (`Authorization` effacé en amont). AnythingLLM sous `/anythingllm/` : auth **applicative** uniquement.
| Chemin (relatif) | Backend | Port LAN | Protection | | Chemin (relatif) | Backend | Port LAN | Protection |
|------------------|---------|----------|------------| |------------------|---------|----------|------------|
| `/ollama/` | Ollama | `11434` | Aucune auth nginx (Ollama sans clé par défaut) | | `/ollama/` | Ollama | `11434` | **Bearer** nginx |
| `/anythingllm/` | AnythingLLM | `3001` | Login AnythingLLM | | `/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 nest pas une IP RFC1918. **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 nest pas une IP RFC1918.
@ -28,11 +28,14 @@ Reverse TLS vers lhôte LAN **`192.168.1.164`** (Ollama + AnythingLLM ; IP su
Depuis la racine du dépôt **`smart_ide`**, sur une machine avec accès SSH au bastion puis au proxy : Depuis la racine du dépôt **`smart_ide`**, sur une machine avec accès SSH au bastion puis au proxy :
```bash ```bash
# accès LAN direct au proxy (.100), sans bastion (variable vide = pas de ProxyJump) : 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= # export DEPLOY_SSH_PROXY_HOST=
./deploy/nginx/deploy-ia-enso-to-proxy.sh ./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 ### 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). - `http { include /etc/nginx/conf.d/*.conf; ... }` dans `/etc/nginx/nginx.conf` (sinon le script échoue avec un message explicite).
@ -58,16 +61,17 @@ sudo certbot certonly --webroot -w /var/www/certbot -d ia.enso.4nkweb.com --non-
| Chemin sur le proxy | Rôle | | Chemin sur le proxy | Rôle |
|---------------------|------| |---------------------|------|
| `/etc/nginx/conf.d/ia-enso-http-maps.conf` | `map` WebSocket `$connection_upgrade` (ou fichier stub si doublon ailleurs) | | `/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 | | `/etc/nginx/sites-available/ia.enso.4nkweb.com.conf` | `server` HTTP→HTTPS + HTTPS |
| Lien `sites-enabled/ia.enso.4nkweb.com.conf` | Activation du vhost | | Lien `sites-enabled/ia.enso.4nkweb.com.conf` | Activation du vhost |
Si `nginx -t` échoue à cause dun **doublon** `map $http_upgrade $connection_upgrade`, le script retente avec un **stub** commenté à la place du `map`. Si `nginx -t` échoue à cause dun **doublon** `map $http_upgrade $connection_upgrade`, le script retente avec **Bearer seul** (sans dupliquer le `map` WebSocket).
### Variables denvironnement du script ### Variables denvironnement du script
| Variable | Défaut | Rôle | | 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_SSH_KEY` | `~/.ssh/id_ed25519` | Clé privée SSH |
| `IA_ENSO_PROXY_USER` | `ncantu` | Utilisateur SSH sur le proxy | | `IA_ENSO_PROXY_USER` | `ncantu` | Utilisateur SSH sur le proxy |
| `IA_ENSO_PROXY_HOST` | `192.168.1.100` | Cible SSH (IP ou hostname LAN) | | `IA_ENSO_PROXY_HOST` | `192.168.1.100` | Cible SSH (IP ou hostname LAN) |
@ -91,11 +95,9 @@ 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. 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 (WebSocket ; Bearer optionnel) ### 2. Maps HTTP (Bearer + WebSocket)
**WebSocket (AnythingLLM)** : si `$connection_upgrade` nexiste pas déjà dans linstance, inclure `http-maps/websocket-connection.map.conf.example` dans `http { }` ou utiliser le fichier `ia-enso-http-maps.conf` déployé par le script. 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.
**Bearer sur `/ollama/` (optionnel)** : pour réactiver une garde nginx, ajouter le `map` de `http-maps/ia-enso-ollama-bearer.map.conf.example` et dans `location /ollama/` un `if ($ia_enso_ollama_authorized = 0) { return 401; }` (+ `map_hash_bucket_size 256` si secret long). Ne pas commiter le secret.
### 3. Fichier `server` ### 3. Fichier `server`
@ -119,11 +121,12 @@ sudo nginx -t && sudo systemctl reload nginx
### API Ollama via le proxy ### API Ollama via le proxy
```bash ```bash
curl -sS -o /dev/null -w "%{http_code}\n" https://ia.enso.4nkweb.com/ollama/v1/models curl -sS -o /dev/null -w "%{http_code}\n" \
curl -sS -o /dev/null -w "%{http_code}\n" https://ia.enso.4nkweb.com/ollama/api/tags -H "Authorization: Bearer <secret>" \
https://ia.enso.4nkweb.com/ollama/v1/models
``` ```
Attendu : **200** sans en-tête `Authorization` (config par défaut sans Bearer nginx). Attendu : **200** avec le bon secret ; **401** sans `Authorization` ou secret incorrect.
### AnythingLLM ### AnythingLLM
@ -133,9 +136,9 @@ Si les assets statiques échouent, vérifier la doc upstream (sous-chemin, en-t
### Cursor ### Cursor
- URL de base OpenAI : `https://ia.enso.4nkweb.com/ollama/v1` - URL de base OpenAI : `https://ia.enso.4nkweb.com/ollama/v1`
- Clé API : laisser vide ou valeur factice si Cursor lexige (plus de Bearer nginx sur `/ollama/` par défaut). - Clé API : **identique** au secret du `map` nginx (sans préfixe `Bearer ` dans le champ).
Si **`curl`** vers `/ollama/v1/models` renvoie **200** mais Cursor affiche **`ERROR_BAD_USER_API_KEY`**, léchec vient **du client Cursor** (validation / infra Cursor), pas du proxy : [forum](https://forum.cursor.com/t/unauthorized-user-api-key-with-custom-openai-api-key-url/132572). **`streamFromAgentBackend` (comportement observé)** : dans lapplication 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 lusage de loverride. 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 nest pas dans ce dépôt ; seuls les noms de fonctions dans la stack trace décrivent ce chemin dexécution.
--- ---
@ -145,13 +148,21 @@ Sur **`192.168.1.164`**, nautoriser **11434** et **3001** TCP que depuis **19
--- ---
## 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 ## Dépannage
| Symptôme | Piste | | Symptôme | Piste |
|----------|--------| |----------|--------|
| `nginx -t` erreur sur `connection_upgrade` | Doublon de `map $http_upgrade $connection_upgrade` : retirer lun des blocs ou laisser le stub du script. | | `nginx -t` erreur sur `connection_upgrade` | Doublon de `map $http_upgrade $connection_upgrade` : retirer lun des blocs ou laisser le déploiement « Bearer seul » du script. |
| `could not build map_hash` / `map_hash_bucket_size` | Uniquement si tu réactives un `map` Bearer avec un secret très long ; ajouter `map_hash_bucket_size 256;` dans `http { }`. | | `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/` | Bearer nginx réactivé manuellement : aligner client et `map`, ou désactiver la garde. | | `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`). | | `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. | | 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` | Lhôte utilisé résout encore vers une IP privée côté infrastructure Cursor ; vérifier DNS public / NAT. | | Cursor `ssrf_blocked` | Lhôte utilisé résout encore vers une IP privée côté infrastructure Cursor ; vérifier DNS public / NAT. |

View File

@ -4,6 +4,7 @@
# Requires passwordless sudo for nginx on the proxy host. # Requires passwordless sudo for nginx on the proxy host.
# #
# Environment: # Environment:
# IA_ENSO_OLLAMA_BEARER_TOKEN Bearer secret for /ollama (if unset, openssl rand -hex 32).
# IA_ENSO_SSH_KEY SSH private key (default: ~/.ssh/id_ed25519). # IA_ENSO_SSH_KEY SSH private key (default: ~/.ssh/id_ed25519).
# IA_ENSO_PROXY_USER SSH user on proxy (default: ncantu). # IA_ENSO_PROXY_USER SSH user on proxy (default: ncantu).
# IA_ENSO_PROXY_HOST Proxy IP or hostname (default: 192.168.1.100). # IA_ENSO_PROXY_HOST Proxy IP or hostname (default: 192.168.1.100).
@ -37,26 +38,47 @@ elif [[ -z "$DEPLOY_SSH_PROXY_HOST" ]]; then
fi fi
export DEPLOY_SSH_PROXY_USER export DEPLOY_SSH_PROXY_USER
TOKEN="${IA_ENSO_OLLAMA_BEARER_TOKEN:-}"
if [[ -z "$TOKEN" ]]; then
TOKEN="$(openssl rand -hex 32)"
echo "IA_ENSO_OLLAMA_BEARER_TOKEN was unset; generated token (store for Cursor API key):"
echo "$TOKEN"
echo "---"
fi
if [[ "$TOKEN" == *'"'* ]] || [[ "$TOKEN" == *'\'* ]]; then
echo "Token must not contain double quotes or backslashes." >&2
exit 1
fi
if [[ ! "$IA_ENSO_BACKEND_IP" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then if [[ ! "$IA_ENSO_BACKEND_IP" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "IA_ENSO_BACKEND_IP must be an IPv4 address (got: ${IA_ENSO_BACKEND_IP})" >&2 echo "IA_ENSO_BACKEND_IP must be an IPv4 address (got: ${IA_ENSO_BACKEND_IP})" >&2
exit 1 exit 1
fi fi
# mode: full = websocket + bearer; bearer_only = bearer + map_hash (duplicate websocket elsewhere)
write_maps_file() { write_maps_file() {
local path="$1" local path="$1"
local with_websocket="$2" local mode="$2"
if [[ "$with_websocket" == "1" ]]; then {
cat <<'MAPEOF' >"$path" cat <<'HASHOF'
map_hash_bucket_size 256;
HASHOF
if [[ "$mode" == "full" ]]; then
cat <<'MAPEOF'
map $http_upgrade $connection_upgrade { map $http_upgrade $connection_upgrade {
default upgrade; default upgrade;
'' close; '' close;
} }
MAPEOF MAPEOF
else fi
cat <<'STUB' >"$path" cat <<MAPEOF
# ia-enso: $connection_upgrade is defined in another conf.d file; no duplicate map here. map \$http_authorization \$ia_enso_ollama_authorized {
STUB default 0;
fi "Bearer ${TOKEN}" 1;
}
MAPEOF
} >"$path"
} }
TMP_DIR="$(mktemp -d)" TMP_DIR="$(mktemp -d)"
@ -66,8 +88,8 @@ cleanup() {
trap cleanup EXIT trap cleanup EXIT
try_install() { try_install() {
local with_ws="$1" local mode="$1"
write_maps_file "${TMP_DIR}/ia-enso-http-maps.conf" "$with_ws" write_maps_file "${TMP_DIR}/ia-enso-http-maps.conf" "$mode"
sed "s/__IA_ENSO_BACKEND_IP__/${IA_ENSO_BACKEND_IP}/g" "${SCRIPT_DIR}/sites/ia.enso.4nkweb.com.conf" >"${TMP_DIR}/ia.enso.4nkweb.com.conf" sed "s/__IA_ENSO_BACKEND_IP__/${IA_ENSO_BACKEND_IP}/g" "${SCRIPT_DIR}/sites/ia.enso.4nkweb.com.conf" >"${TMP_DIR}/ia.enso.4nkweb.com.conf"
scp_copy "$IA_ENSO_SSH_KEY" "${TMP_DIR}/ia-enso-http-maps.conf" "$IA_ENSO_PROXY_USER" "$IA_ENSO_PROXY_HOST" "/tmp/ia-enso-http-maps.conf" scp_copy "$IA_ENSO_SSH_KEY" "${TMP_DIR}/ia-enso-http-maps.conf" "$IA_ENSO_PROXY_USER" "$IA_ENSO_PROXY_HOST" "/tmp/ia-enso-http-maps.conf"
scp_copy "$IA_ENSO_SSH_KEY" "${TMP_DIR}/ia.enso.4nkweb.com.conf" "$IA_ENSO_PROXY_USER" "$IA_ENSO_PROXY_HOST" "/tmp/ia.enso.4nkweb.com.conf" scp_copy "$IA_ENSO_SSH_KEY" "${TMP_DIR}/ia.enso.4nkweb.com.conf" "$IA_ENSO_PROXY_USER" "$IA_ENSO_PROXY_HOST" "/tmp/ia.enso.4nkweb.com.conf"
@ -91,16 +113,13 @@ REMOTE
echo "Deploying ia.enso upstreams to ${IA_ENSO_BACKEND_IP} (Ollama :11434, AnythingLLM :3001)." echo "Deploying ia.enso upstreams to ${IA_ENSO_BACKEND_IP} (Ollama :11434, AnythingLLM :3001)."
if ! try_install 1; then if ! try_install full; then
echo "Retrying with stub maps file (websocket map likely already defined on proxy)..." echo "Retrying with Bearer map only (websocket map likely already defined on proxy)..."
if ! try_install 0; then if ! try_install bearer_only; then
echo "Deploy failed (SSH, sudo, nginx -t, or missing include /etc/nginx/conf.d/*.conf)." >&2 echo "Deploy failed (SSH, sudo, nginx -t, or missing include /etc/nginx/conf.d/*.conf)." >&2
echo "Re-run from a host with SSH access to the proxy (LAN direct: DEPLOY_SSH_PROXY_HOST=)." >&2 echo "Re-run from a host with SSH access (LAN: DEPLOY_SSH_PROXY_HOST=); set IA_ENSO_OLLAMA_BEARER_TOKEN to reuse secret." >&2
exit 1 exit 1
fi fi
fi fi
echo "Done. Public URLs (no nginx Bearer on /ollama/):" echo "Done. Bearer required on /ollama/. Cursor base: https://ia.enso.4nkweb.com/ollama/v1 — API key = token above (if generated) or IA_ENSO_OLLAMA_BEARER_TOKEN."
echo " AnythingLLM: https://ia.enso.4nkweb.com/anythingllm/"
echo " Ollama native: https://ia.enso.4nkweb.com/ollama/api/tags"
echo " OpenAI-compat: https://ia.enso.4nkweb.com/ollama/v1"

View File

@ -1,8 +1,9 @@
# OPTIONAL: Bearer gate on /ollama/ (default repo site has no nginx auth on Ollama). # Bearer gate for /ollama/ (matches default site: if ($ia_enso_ollama_authorized = 0) { return 401; }).
# Install inside `http { ... }` before server blocks that use $ia_enso_ollama_authorized, and add to # Install inside `http { ... }` before server blocks that use $ia_enso_ollama_authorized:
# location /ollama/ { if ($ia_enso_ollama_authorized = 0) { return 401; } ... } # include /etc/nginx/http-maps/ia-enso-ollama-bearer.map.conf;
# #
# Copy without the .example suffix, set secret (ASCII, no double quotes in value). # Copy without the .example suffix; set secret (ASCII, no double quotes in value).
# Cursor: OpenAI base .../ollama/v1 and API key = same secret (no "Bearer " in field).
map_hash_bucket_size 256; map_hash_bucket_size 256;

View File

@ -4,12 +4,12 @@
# AnythingLLM UI: https://ia.enso.4nkweb.com/anythingllm/ # AnythingLLM UI: https://ia.enso.4nkweb.com/anythingllm/
# Ollama OpenAI API: https://ia.enso.4nkweb.com/ollama/v1/ (e.g. .../v1/models, .../v1/chat/completions) # Ollama OpenAI API: https://ia.enso.4nkweb.com/ollama/v1/ (e.g. .../v1/models, .../v1/chat/completions)
# Ollama native API: https://ia.enso.4nkweb.com/ollama/api/tags (and other /api/* paths) # Ollama native API: https://ia.enso.4nkweb.com/ollama/api/tags (and other /api/* paths)
# /ollama/* has NO nginx Bearer gate (public inference if DNS is reachable); restrict at firewall or re-add map. # /ollama/* requires Authorization: Bearer <secret> at nginx (map in conf.d); secret not forwarded to Ollama.
# Cursor base URL: https://ia.enso.4nkweb.com/ollama/v1 # Cursor base URL: https://ia.enso.4nkweb.com/ollama/v1 — API key field = same secret (no "Bearer " prefix).
# #
# Prerequisites on the proxy host: # Prerequisites on the proxy host:
# - TLS certificate for ia.enso.4nkweb.com (e.g. certbot). # - TLS certificate for ia.enso.4nkweb.com (e.g. certbot).
# - Optional: include http-maps/websocket-connection.map.conf.example in http { } if not using deploy script maps file. # - HTTP map $ia_enso_ollama_authorized (see deploy script / http-maps/ia-enso-ollama-bearer.map.conf.example).
# #
# Upstream backend: replaced at deploy time (default 192.168.1.164). Manual install: replace __IA_ENSO_BACKEND_IP__. # Upstream backend: replaced at deploy time (default 192.168.1.164). Manual install: replace __IA_ENSO_BACKEND_IP__.
@ -54,8 +54,12 @@ server {
client_max_body_size 100M; client_max_body_size 100M;
# Ollama: no nginx auth (Ollama itself does not enforce API keys by default). # Ollama: nginx Bearer gate (map $ia_enso_ollama_authorized); Authorization cleared upstream.
location /ollama/ { location /ollama/ {
if ($ia_enso_ollama_authorized = 0) {
return 401;
}
proxy_pass http://ia_enso_ollama/; proxy_pass http://ia_enso_ollama/;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host $host; proxy_set_header Host $host;
@ -64,6 +68,8 @@ server {
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection ""; proxy_set_header Connection "";
proxy_set_header Authorization "";
proxy_buffering off; proxy_buffering off;
proxy_read_timeout 3600s; proxy_read_timeout 3600s;
proxy_send_timeout 3600s; proxy_send_timeout 3600s;

View File

@ -4,46 +4,41 @@
## Objective ## Objective
Expose Ollama and AnythingLLM on the public proxy hostname with HTTPS, path prefixes `/ollama` and `/anythingllm`. **Default:** no nginx Bearer on `/ollama/` (optional `map` in `http-maps/ia-enso-ollama-bearer.map.conf.example` to re-enable). Expose Ollama and AnythingLLM on the public proxy hostname with HTTPS, path prefixes `/ollama` and `/anythingllm`, and **gate `/ollama/`** with a **Bearer token** at nginx (compatible with OpenAI clients that send `Authorization: Bearer <key>`). The secret is **not** forwarded to Ollama.
## Public URLs (HTTPS) ## Public URLs (HTTPS)
- AnythingLLM UI: `https://ia.enso.4nkweb.com/anythingllm/` - AnythingLLM UI: `https://ia.enso.4nkweb.com/anythingllm/`
- Ollama native API (example): `https://ia.enso.4nkweb.com/ollama/api/tags` - Ollama native API (example): `https://ia.enso.4nkweb.com/ollama/api/tags` — Bearer required at nginx
- OpenAI-compatible base (Cursor): `https://ia.enso.4nkweb.com/ollama/v1` - OpenAI-compatible base (Cursor): `https://ia.enso.4nkweb.com/ollama/v1`
## Impacts ## Impacts
- **Proxy (nginx):** `server_name`, TLS, locations; `conf.d/ia-enso-http-maps.conf` holds WebSocket `map` when deployed by script (or stub if duplicate elsewhere). - **Proxy (nginx):** `server_name`, TLS, locations; `conf.d/ia-enso-http-maps.conf` with `map_hash_bucket_size`, Bearer `map`, and WebSocket `map` (or Bearer-only if WebSocket map exists elsewhere).
- **Backend (192.168.1.164):** must accept connections from the proxy on `11434` and `3001`. - **Backend (192.168.1.164):** must accept connections from the proxy on `11434` and `3001`.
- **Clients:** Cursor can use `https://ia.enso.4nkweb.com/ollama/v1` without a matching nginx secret if Bearer is disabled; hostname may avoid private-IP SSRF blocks when DNS resolves publicly. - **Clients:** send `Authorization: Bearer <secret>` for `/ollama/*`; Cursor API key field = same secret as in the nginx `map`.
## Repository layout ## Repository layout
| Path | Purpose | | Path | Purpose |
|------|---------| |------|---------|
| `deploy/nginx/sites/ia.enso.4nkweb.com.conf` | `server` blocks ; upstreams use `__IA_ENSO_BACKEND_IP__` (default `192.168.1.164` substituted by `deploy-ia-enso-to-proxy.sh` or manual `sed`) | | `deploy/nginx/sites/ia.enso.4nkweb.com.conf` | `server` blocks ; upstreams use `__IA_ENSO_BACKEND_IP__` |
| `deploy/nginx/http-maps/ia-enso-ollama-bearer.map.conf.example` | **Optional** Bearer `map` + `location /ollama/` `if` to re-enable auth | | `deploy/nginx/http-maps/ia-enso-ollama-bearer.map.conf.example` | Bearer `map` reference for manual installs |
| `deploy/nginx/http-maps/websocket-connection.map.conf.example` | Example WebSocket `map` (manual install) | | `deploy/nginx/http-maps/websocket-connection.map.conf.example` | WebSocket `map` reference |
| `deploy/nginx/deploy-ia-enso-to-proxy.sh` | SSH deploy: maps + site, `nginx -t`, reload; stub retry if websocket `map` already exists | | `deploy/nginx/deploy-ia-enso-to-proxy.sh` | SSH deploy; retry Bearer-only if duplicate WebSocket `map` |
| `deploy/nginx/sites/ia.enso.4nkweb.com.http-only.conf` | Temporary HTTP-only vhost for first Lets Encrypt `webroot` issuance when `live/ia.enso…` is missing | | `deploy/nginx/sites/ia.enso.4nkweb.com.http-only.conf` | TLS bootstrap HTTP-only vhost |
| `deploy/nginx/README-ia-enso.md` | **Operator reference:** automated + manual steps, env vars, checks, troubleshooting, TLS bootstrap | | `deploy/nginx/README-ia-enso.md` | Operator reference (includes note on Cursor `streamFromAgentBackend`) |
## Deployment modalities ## Deployment modalities
**Preferred:** run `./deploy/nginx/deploy-ia-enso-to-proxy.sh` from `smart_ide` on a host with SSH access (see `README-ia-enso.md`). Run `./deploy/nginx/deploy-ia-enso-to-proxy.sh` with optional `IA_ENSO_OLLAMA_BEARER_TOKEN`. See `README-ia-enso.md`.
**Manual:** DNS → TLS (certbot) → WebSocket `map` if needed → install site → `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 ## Analysis modalities
- `curl` to `/ollama/v1/models` and `/ollama/api/tags` without `Authorization` (expect **200** when Bearer is off). - `curl` to `/ollama/v1/models` with and without Bearer (200 / 401).
- Browser access to `/anythingllm/` and application login. - Browser: `/anythingllm/`.
- Cursor connectivity; `ERROR_BAD_USER_API_KEY` may still be a Cursor client issue (see README forum link).
## Security notes ## Security notes
- **Default `/ollama/` is unauthenticated at nginx:** anyone who can reach the URL can call Ollama unless restricted by firewall or Ollama-level controls. Re-add Bearer using the example `map` if needed. - Bearer secret is equivalent to an API key; rotate in `ia-enso-http-maps.conf` and client configs together.
- AnythingLLM remains protected by **its own** application authentication. - AnythingLLM uses its own application login on `/anythingllm/`.

View File

@ -27,7 +27,7 @@ Internet access to backends uses **SSH ProxyJump** via `ncantu@4nk.myftp.biz` (s
## Reverse proxy `ia.enso.4nkweb.com` (Ollama / AnythingLLM) ## Reverse proxy `ia.enso.4nkweb.com` (Ollama / AnythingLLM)
Hostname TLS sur le **proxy** `192.168.1.100` : préfixes `/ollama` et `/anythingllm` vers lhôte LAN `192.168.1.164` (ports `11434` et `3001`, voir `deploy/nginx/sites/ia.enso.4nkweb.com.conf`). **`/ollama/`** sans garde Bearer nginx par défaut (option documentée dans `deploy/nginx/http-maps/`) ; AnythingLLM reste derrière son auth applicative. Hostname TLS sur le **proxy** `192.168.1.100` : préfixes `/ollama` et `/anythingllm` vers lhôte LAN `192.168.1.164` (ports `11434` et `3001`, voir `deploy/nginx/sites/ia.enso.4nkweb.com.conf`). **`/ollama/`** protégé par **Bearer** nginx (`map` dans `conf.d`) ; 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). 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).

View File

@ -99,12 +99,13 @@ The last command must succeed after `OLLAMA_HOST=0.0.0.0:11434` and `host.docker
## Public reverse proxy (ia.enso.4nkweb.com) ## Public reverse proxy (ia.enso.4nkweb.com)
When Ollama runs on a LAN host (e.g. `192.168.1.164` via `IA_ENSO_BACKEND_IP` / `deploy/nginx/sites/ia.enso.4nkweb.com.conf`) and must be reached via the **proxy** with HTTPS, 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). **Default:** no nginx Bearer on `/ollama/` (optional `http-maps/ia-enso-ollama-bearer.map.conf.example`). When Ollama runs on a LAN host (e.g. `192.168.1.164` via `IA_ENSO_BACKEND_IP` / `deploy/nginx/sites/ia.enso.4nkweb.com.conf`) and must be reached via the **proxy** with HTTPS and a **Bearer** gate on `/ollama/`, 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).
**Full URLs** **Full URLs**
- AnythingLLM UI: `https://ia.enso.4nkweb.com/anythingllm/` - AnythingLLM UI: `https://ia.enso.4nkweb.com/anythingllm/`
- Ollama native API example: `https://ia.enso.4nkweb.com/ollama/api/tags` - Ollama native API example: `https://ia.enso.4nkweb.com/ollama/api/tags` (header `Authorization: Bearer <secret>`)
- Cursor / OpenAI-compatible base URL: `https://ia.enso.4nkweb.com/ollama/v1` - Cursor / OpenAI-compatible base URL: `https://ia.enso.4nkweb.com/ollama/v1`
- Cursor API key: same value as the Bearer secret in nginx `map`
Feature note: [ia-enso-nginx-proxy-ollama-anythingllm.md](./features/ia-enso-nginx-proxy-ollama-anythingllm.md). Feature note: [ia-enso-nginx-proxy-ollama-anythingllm.md](./features/ia-enso-nginx-proxy-ollama-anythingllm.md).