diff --git a/apps/config.json b/apps/config.json new file mode 100644 index 0000000..862594a --- /dev/null +++ b/apps/config.json @@ -0,0 +1,50 @@ +{ + "name": "n8n Sandbox Service", + "id": "n8n-sandbox", + "available": true, + "exposable": false, + "no_gui": true, + "dynamic_config": true, + "port": 8080, + "tipi_version": 1, + "min_tipi_version": "4.7.0", + "version": "1.3.0", + "author": "n8n", + "source": "https://github.com/n8n-io/n8n-sandbox-service", + "website": "https://docs.n8n.io/deploy/host-n8n/configure-n8n/set-up-ai-assistant", + "categories": ["automation", "development"], + "short_desc": "Sandbox d'execution de code pour l'AI Assistant de n8n (api + runner Docker-in-Docker).", + "description": "Stack n8n Sandbox Service (sandbox-certs, sandbox-api, sandbox-runner-1) en mTLS. Aucun port publie : n8n joint sandbox-api:8080 via tipi_main_network. Le runner tourne en privileged (Docker-in-Docker).", + "supported_architectures": ["amd64", "arm64"], + "form_fields": [ + { + "type": "random", + "encoding": "hex", + "min": 48, + "label": "Cle API sandbox (SANDBOX_API_KEYS)", + "hint": "A recopier dans l'app n8n : N8N_SANDBOX_SERVICE_API_KEY. Plusieurs cles possibles, separees par des virgules.", + "required": true, + "env_variable": "SANDBOX_API_KEYS" + }, + { + "type": "random", + "encoding": "hex", + "min": 48, + "label": "Jeton d'enregistrement du runner (SANDBOX_REGISTRATION_TOKEN)", + "hint": "Secret interne api <-> runner. Rien a recopier ailleurs.", + "required": true, + "env_variable": "SANDBOX_REGISTRATION_TOKEN" + }, + { + "type": "random", + "encoding": "hex", + "min": 48, + "label": "Cle API interne du runner (SANDBOX_RUNNER_KEY)", + "hint": "Secret interne api <-> runner. Rien a recopier ailleurs.", + "required": true, + "env_variable": "SANDBOX_RUNNER_KEY" + } + ], + "created_at": 1788345796000, + "updated_at": 1788345796000 +} diff --git a/apps/description.md b/apps/description.md new file mode 100644 index 0000000..022b1cb --- /dev/null +++ b/apps/description.md @@ -0,0 +1,49 @@ +# n8n Sandbox Service + +Sandbox auto-hébergé pour l'**AI Assistant** de n8n (setup « Self-host the sandbox manually » de la doc n8n). L'app déploie les trois services de la stack officielle : + +| Service | Rôle | +|---|---| +| `sandbox-certs` | Job one-shot : génère la CA privée et les certificats mTLS, puis s'arrête. | +| `sandbox-api` | Point d'entrée HTTP (`:8080`) que n8n appelle pour exécuter du code. | +| `sandbox-runner-1` | Docker-in-Docker **privileged** : crée et exécute les conteneurs sandbox. | + +## Après l'installation + +Dans l'app **n8n** (paramètres ou `app.env`), ajouter : + +``` +N8N_ENABLED_MODULES=instance-ai +N8N_INSTANCE_AI_SANDBOX_ENABLED=true +N8N_INSTANCE_AI_SANDBOX_PROVIDER=n8n-sandbox +N8N_INSTANCE_AI_SANDBOX_IMAGE=n8nio/n8n-sandbox-service-sandbox:1.3.0 +N8N_SANDBOX_SERVICE_URL=http://sandbox-api:8080 +N8N_SANDBOX_SERVICE_API_KEY= +``` + +Puis redémarrer n8n et vérifier depuis son conteneur : + +``` +wget -qO- http://sandbox-api:8080/healthz # {"status":"ok"} +``` + +## Données persistantes + +Tout est sous `app-data//n8n-sandbox/data/` : + +- `tls/` : certificats mTLS (contient la clé de la CA, à traiter comme un secret) +- `api/` : base SQLite de l'API +- `runner-state/` : base SQLite du runner +- `runner-docker/` : `/var/lib/docker` du DinD (cache de l'image sandbox, peut peser plusieurs Go ; vidable sans perte) + +## Sécurité + +- Aucun port n'est publié sur l'hôte. `sandbox-api:8080` est joignable par les autres apps Runtipi via `tipi_main_network`, protégé uniquement par la clé API. +- Le runner est `privileged` : équivalent root sur l'hôte. Ne jamais l'exposer. +- n8n recommande cette stack pour le développement/test et Daytona pour la production. + +## Notes + +- Les noms `sandbox-api` et `sandbox-runner-1` sont les SAN des certificats : ne pas les renommer. +- Les certificats ne se renouvellent pas seuls. Pour les régénérer, supprimer `data/tls/` et redémarrer l'app. +- L'image sandbox est téléchargée par le runner au premier usage. diff --git a/apps/docker-compose.yml b/apps/docker-compose.yml new file mode 100644 index 0000000..9992c1c --- /dev/null +++ b/apps/docker-compose.yml @@ -0,0 +1,159 @@ +# n8n Sandbox Service — app custom Runtipi (format docker-compose.yml + x-runtipi) +# +# Images : n8nio/n8n-sandbox-service-{api,runner-dind,sandbox}:1.3.0 +# (verifier au besoin : docker manifest inspect n8nio/n8n-sandbox-service-sandbox:1.3.0) +# +# Les 3 secrets ne sont PAS dans ce fichier : ce sont des form_fields du +# config.json (SANDBOX_API_KEYS, SANDBOX_REGISTRATION_TOKEN, SANDBOX_RUNNER_KEY), +# saisis/generes dans la GUI Runtipi a l'installation, modifiables ensuite +# dans les parametres de l'app, et injectes ici par ${...}. +# +# Cote app n8n (autre app Runtipi, doc "Set up AI Assistant", setup 2) : +# N8N_ENABLED_MODULES=instance-ai +# N8N_INSTANCE_AI_SANDBOX_ENABLED=true +# N8N_INSTANCE_AI_SANDBOX_PROVIDER=n8n-sandbox +# N8N_INSTANCE_AI_SANDBOX_IMAGE=n8nio/n8n-sandbox-service-sandbox:1.3.0 +# N8N_SANDBOX_SERVICE_URL=http://sandbox-api:8080 +# N8N_SANDBOX_SERVICE_API_KEY= +# Test depuis le conteneur n8n : wget -qO- http://sandbox-api:8080/healthz +# +# Les noms de services sandbox-api et sandbox-runner-1 sont fixes : ce sont +# les SAN des certificats. Si un nom ou NUM_RUNNERS change, supprimer +# ${APP_DATA_DIR}/data/tls (bootstrap-mtls.sh ne regenere rien tant que +# les fichiers existent). +# +# Donnees persistantes (toutes sous ${APP_DATA_DIR}/data, donc couvertes par +# les sauvegardes Runtipi — attention, runner-docker peut peser plusieurs Go) : +# tls/ certificats mTLS (CA + feuilles) +# api/ SQLite de l'API (etat des sandboxes) +# runner-state/ SQLite du runner +# runner-docker/ /var/lib/docker du DinD (cache de l'image sandbox) + +services: + sandbox-certs: + image: n8nio/n8n-sandbox-service-api:1.3.0 + # Obligatoire sous Runtipi : sans cette ligne le builder injecte + # restart: unless-stopped et ce job one-shot boucle en redemarrage. + restart: "no" + user: "0:0" + entrypoint: ["sh", "-c"] + # Genere les certs (idempotent) puis fixe les droits pour l'utilisateur + # sandbox-api de l'image : /tls/api et le repertoire SQLite de l'API + # (en bind mount, Docker cree le dossier en root sinon). + command: + - > + bootstrap-mtls.sh --out-dir /tls --api-san sandbox-api + --control-san-prefix sandbox-runner && + chown -R sandbox-api:sandbox-api /tls/api /var/lib/n8n-sandbox-api + environment: + NUM_RUNNERS: "1" + volumes: + - ${APP_DATA_DIR}/data/tls:/tls + - ${APP_DATA_DIR}/data/api:/var/lib/n8n-sandbox-api + + sandbox-api: + image: n8nio/n8n-sandbox-service-api:1.3.0 + restart: unless-stopped + + depends_on: + sandbox-certs: + condition: service_completed_successfully + + environment: + SANDBOX_API_KEYS: "${SANDBOX_API_KEYS}" + SANDBOX_API_RUNNER_REGISTRATION_TOKEN: "${SANDBOX_REGISTRATION_TOKEN}" + SANDBOX_API_RUNNER_API_KEY: "${SANDBOX_RUNNER_KEY}" + + SANDBOX_API_GRPC_TLS_CERT_FILE: /tls/api/grpc-server.crt + SANDBOX_API_GRPC_TLS_KEY_FILE: /tls/api/grpc-server.key + SANDBOX_API_GRPC_TLS_CLIENT_CA_FILE: /tls/api/ca.crt + + SANDBOX_API_RUNNER_CONTROL_GRPC_TLS_CA_FILE: /tls/api/ca.crt + SANDBOX_API_RUNNER_CONTROL_GRPC_TLS_CERT_FILE: /tls/api/control-grpc-api-client.crt + SANDBOX_API_RUNNER_CONTROL_GRPC_TLS_KEY_FILE: /tls/api/control-grpc-api-client.key + SANDBOX_API_RUNNER_CONTROL_GRPC_TLS_SERVER_NAME: sandbox-runner-1 + + volumes: + - ${APP_DATA_DIR}/data/tls:/tls:ro + # SANDBOX_API_DATA_DIR par defaut (SQLite). + - ${APP_DATA_DIR}/data/api:/var/lib/n8n-sandbox-api + + healthcheck: + # Forme chaine (et non tableau) : c'est la forme documentee par Runtipi + # et la seule acceptee par le formulaire de creation. + test: "wget -qO- http://localhost:8080/healthz" + interval: 5s + timeout: 3s + retries: 5 + start_period: 10s + + # is_main est OBLIGATOIRE : l'UI Runtipi refuse un compose sans + # exactement un service principal ("There must be exactly one main service"). + # Sans internal_port et sans cocher "open port" a l'installation, + # is_main ne publie aucun port et ne pose aucun label Traefik. + # Le service principal rejoint automatiquement tipi_main_network, ce qui + # rend sandbox-api:8080 joignable par les autres apps Runtipi (dont n8n), + # protege uniquement par SANDBOX_API_KEYS. Ne jamais publier 8080/9090. + x-runtipi: + is_main: true + add_to_main_network: true + + sandbox-runner-1: + image: n8nio/n8n-sandbox-service-runner-dind:1.3.0 + restart: unless-stopped + # Mode "dev/macOS" de l'upstream (qui attend sysbox-runc en production ; + # n8n recommande Daytona pour la prod). Equivalent root sur l'hote : + # ce service reste hors de tipi_main_network et ne publie aucun port. + privileged: true + + depends_on: + sandbox-api: + condition: service_healthy + + environment: + SANDBOX_RUNNER_API_KEYS: "${SANDBOX_RUNNER_KEY}" + SANDBOX_RUNNER_REGISTRATION_TOKEN: "${SANDBOX_REGISTRATION_TOKEN}" + + SANDBOX_RUNNER_API_GRPC_ADDR: sandbox-api:9090 + # http:// et non https:// : le mTLS ne couvre que le gRPC + # (enregistrement + SandboxControl). Le trafic proxy exec/files de + # l'API vers le runner reste en HTTP clair authentifie par X-Api-Key + # (docs/configuration.md du depot et compose officiel n8n). + SANDBOX_RUNNER_HTTP_BASE_URL: http://sandbox-runner-1:8080 + + SANDBOX_RUNNER_CONTROL_GRPC_LISTEN_ADDR: ":9091" + SANDBOX_RUNNER_CONTROL_GRPC_ADVERTISE_ADDR: sandbox-runner-1:9091 + + SANDBOX_RUNNER_ID: runner-1 + + SANDBOX_RUNNER_DOCKER_SANDBOX_IMAGE: n8nio/n8n-sandbox-service-sandbox:1.3.0 + + SANDBOX_RUNNER_REGISTRATION_GRPC_CA_FILE: /tls/runner/ca.crt + SANDBOX_RUNNER_REGISTRATION_GRPC_CERT_FILE: /tls/runner/grpc-client.crt + SANDBOX_RUNNER_REGISTRATION_GRPC_KEY_FILE: /tls/runner/grpc-client.key + SANDBOX_RUNNER_REGISTRATION_GRPC_SERVER_NAME: sandbox-api + + SANDBOX_RUNNER_CONTROL_GRPC_TLS_CERT_FILE: /tls/runner/control-grpc-server.crt + SANDBOX_RUNNER_CONTROL_GRPC_TLS_KEY_FILE: /tls/runner/control-grpc-server.key + SANDBOX_RUNNER_CONTROL_GRPC_TLS_CLIENT_CA_FILE: /tls/runner/ca.crt + + volumes: + - ${APP_DATA_DIR}/data/tls:/tls:ro + # Etat SQLite du runner (SANDBOX_RUNNER_DATA_DIR par defaut). + - ${APP_DATA_DIR}/data/runner-state:/var/sandboxes + # Docker interne du DinD : sans ce montage, l'image sandbox est + # re-telechargee a chaque recreation de l'app. Necessite un app-data + # sur ext4/xfs/btrfs (overlay2 ne fonctionne pas sur NFS). + - ${APP_DATA_DIR}/data/runner-docker:/var/lib/docker + + healthcheck: + # /readyz passe au vert une fois le runner enregistre aupres de l'API + # (meme check que le compose du depot upstream). + test: "wget -qO- http://localhost:8080/readyz" + interval: 5s + timeout: 5s + retries: 24 + start_period: 20s + +x-runtipi: + schema_version: 2 diff --git a/apps/logo.jpg b/apps/logo.jpg new file mode 100644 index 0000000..925d0d1 Binary files /dev/null and b/apps/logo.jpg differ