src/domain/assistant-templates.js
Rama main · réplica de lectura del repositorio original.
import {
getAssistantContainerNames,
getAssistantLabels,
validateAssistantInstallation,
validateAssistantSearxngSelection,
} from "./assistant.js";
import { validateDockerName } from "./validation.js";
export const ASSISTANT_COMPANION_IMAGES = Object.freeze({
api: "ghcr.io/n8n-io/n8n-sandbox-service-api:1.1.1@sha256:21672029fee08495e2398cff7fc370ff60ce0e7c461610732bf2f5265cb75704",
runner: "ghcr.io/n8n-io/n8n-sandbox-service-runner-dind:1.1.1@sha256:9de7a8aad7f0d2293716daff40206be60577a59a2c2dae641dd9a425c18bf6fd",
sandbox: "ghcr.io/n8n-io/n8n-sandbox-service-sandbox:1.1.0@sha256:16f62fb90a4ce61ef74925f62ea76bb11eb2a5598888b7c0651100c7944ed2d8",
searxng: "ghcr.io/searxng/searxng:2026.8.28-a30b2d474@sha256:addd2cf36efb4b9815a2820a522aef7cce4da0d1c0e4527f6675f5663332fc9b",
});
const SANDBOX_SECRET_FIELDS = Object.freeze([
"sandboxApiKey",
"runnerRegistrationToken",
"runnerApiKey",
]);
function createSecret(randomBytes) {
const value = randomBytes(32);
if (!Buffer.isBuffer(value) || value.length !== 32) {
throw new TypeError("The local secret generator is unavailable.");
}
return value.toString("base64url");
}
function getRequiredSecretFields(includeSearxng) {
validateAssistantSearxngSelection(includeSearxng);
return [...SANDBOX_SECRET_FIELDS, "searxngSecret"];
}
export function createAssistantSecrets({ randomBytes, includeSearxng }) {
if (typeof randomBytes !== "function") {
throw new TypeError("A cryptographic random-byte generator is required.");
}
const selectedSearxng = validateAssistantSearxngSelection(includeSearxng);
const secrets = {
sandboxApiKey: createSecret(randomBytes),
runnerRegistrationToken: createSecret(randomBytes),
runnerApiKey: createSecret(randomBytes),
searxngSecret: createSecret(randomBytes),
};
if (new Set(Object.values(secrets)).size !== getRequiredSecretFields(selectedSearxng).length) {
throw new TypeError("AI Assistant secrets must be independently generated.");
}
return secrets;
}
export function createAssistantEnv(secrets, { includeSearxng }) {
const selectedSearxng = validateAssistantSearxngSelection(includeSearxng);
const requiredSecretFields = getRequiredSecretFields(selectedSearxng);
if (
!secrets ||
requiredSecretFields.some(
(field) =>
typeof secrets[field] !== "string" ||
!/^[A-Za-z0-9_-]{43}$/u.test(secrets[field]),
)
) {
throw new TypeError("AI Assistant secrets are invalid.");
}
const searxngEnv = `SEARXNG_SECRET=${secrets.searxngSecret}
`;
return `SANDBOX_API_KEYS=${secrets.sandboxApiKey}
SANDBOX_API_RUNNER_REGISTRATION_TOKEN=${secrets.runnerRegistrationToken}
SANDBOX_API_RUNNER_API_KEY=${secrets.runnerApiKey}
${searxngEnv}`;
}
export function createSearxngSettings() {
return `use_default_settings: true
search:
formats:
- html
- json
`;
}
export function createAssistantComposeFile({ networkName, installation }) {
const safeNetworkName = validateDockerName(networkName);
const safeInstallation = validateAssistantInstallation(installation);
const containerNames = getAssistantContainerNames(safeInstallation);
const labels = getAssistantLabels(safeInstallation);
return `volumes:
sandbox-tls:
name: ${safeInstallation.projectName}-sandbox-tls
labels:
io.relmio.ai-assistant.managed: "${labels["io.relmio.ai-assistant.managed"]}"
io.relmio.ai-assistant.install-id: "${labels["io.relmio.ai-assistant.install-id"]}"
services:
relmio-sandbox-certs:
container_name: ${containerNames.certs}
image: ${ASSISTANT_COMPANION_IMAGES.api}
user: '0:0'
entrypoint: ['sh', '-c']
command:
- >
bootstrap-mtls.sh --out-dir /tls --api-san relmio-sandbox-api
--control-san-prefix relmio-sandbox-runner &&
chown -R sandbox-api:sandbox-api /tls/api
environment:
NUM_RUNNERS: '1'
volumes:
- sandbox-tls:/tls
networks:
- assistant-internal
labels:
io.relmio.ai-assistant.managed: "${labels["io.relmio.ai-assistant.managed"]}"
io.relmio.ai-assistant.install-id: "${labels["io.relmio.ai-assistant.install-id"]}"
relmio-sandbox-api:
container_name: ${containerNames.api}
image: ${ASSISTANT_COMPANION_IMAGES.api}
depends_on:
relmio-sandbox-certs:
condition: service_completed_successfully
environment:
SANDBOX_API_KEYS: \${SANDBOX_API_KEYS}
SANDBOX_API_RUNNER_REGISTRATION_TOKEN: \${SANDBOX_API_RUNNER_REGISTRATION_TOKEN}
SANDBOX_API_RUNNER_API_KEY: \${SANDBOX_API_RUNNER_API_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: relmio-sandbox-runner-1
volumes:
- sandbox-tls:/tls:ro
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/healthz"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
networks:
assistant-internal:
n8n-shared:
aliases:
- ${safeInstallation.sandboxAlias}
labels:
io.relmio.ai-assistant.managed: "${labels["io.relmio.ai-assistant.managed"]}"
io.relmio.ai-assistant.install-id: "${labels["io.relmio.ai-assistant.install-id"]}"
relmio-sandbox-runner-1:
container_name: ${containerNames.runner}
image: ${ASSISTANT_COMPANION_IMAGES.runner}
privileged: true
depends_on:
relmio-sandbox-api:
condition: service_healthy
environment:
SANDBOX_RUNNER_API_KEYS: \${SANDBOX_API_RUNNER_API_KEY}
SANDBOX_RUNNER_REGISTRATION_TOKEN: \${SANDBOX_API_RUNNER_REGISTRATION_TOKEN}
SANDBOX_RUNNER_API_GRPC_ADDR: relmio-sandbox-api:9090
SANDBOX_RUNNER_HTTP_BASE_URL: http://relmio-sandbox-runner-1:8080
SANDBOX_RUNNER_CONTROL_GRPC_LISTEN_ADDR: ':9091'
SANDBOX_RUNNER_CONTROL_GRPC_ADVERTISE_ADDR: relmio-sandbox-runner-1:9091
SANDBOX_RUNNER_ID: runner-1
SANDBOX_RUNNER_DOCKER_SANDBOX_IMAGE: ${ASSISTANT_COMPANION_IMAGES.sandbox}
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: relmio-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:
- sandbox-tls:/tls:ro
networks:
- assistant-internal
labels:
io.relmio.ai-assistant.managed: "${labels["io.relmio.ai-assistant.managed"]}"
io.relmio.ai-assistant.install-id: "${labels["io.relmio.ai-assistant.install-id"]}"
${safeInstallation.includeSearxng ? ` relmio-searxng:
container_name: ${containerNames.searxng}
image: ${ASSISTANT_COMPANION_IMAGES.searxng}
environment:
SEARXNG_SECRET: \${SEARXNG_SECRET}
volumes:
- ./searxng-settings.yml:/etc/searxng/settings.yml:ro
networks:
n8n-shared:
aliases:
- ${safeInstallation.searxngAlias}
labels:
io.relmio.ai-assistant.managed: "${labels["io.relmio.ai-assistant.managed"]}"
io.relmio.ai-assistant.install-id: "${labels["io.relmio.ai-assistant.install-id"]}"
` : ""}networks:
assistant-internal:
name: ${safeInstallation.projectName}-internal
labels:
io.relmio.ai-assistant.managed: "${labels["io.relmio.ai-assistant.managed"]}"
io.relmio.ai-assistant.install-id: "${labels["io.relmio.ai-assistant.install-id"]}"
n8n-shared:
external: true
name: ${safeNetworkName}
`;
}