import { validateLocalDockerHost } from "../infrastructure/local-process.js"; import { validateSiwcAuthBinding, validateSiwcRegistrationId, validateSiwcRuntimeId } from "./safety.js"; import { validateDockerName } from "./validation.js"; import { validateInstallId, validateSha256Verifier } from "./local-endpoints.js"; export const LOCAL_N8N_SIDECAR_TARGET = "n8n-openai-oauth"; export const LOCAL_N8N_SIDECAR_HOSTNAME = "n8n-openai-oauth"; export const LOCAL_N8N_SIDECAR_PORT = 10_531; export const LOCAL_N8N_SIDECAR_ENDPOINT = "http://n8n-openai-oauth:10531/v1"; const DOCKER_OBJECT_ID_PATTERN = /^[a-f0-9]{64}$/u; const AUTH_GENERATION_MAX_BYTES = 128; export function validateDockerObjectId(value, label = "Docker object") { if (typeof value !== "string" || !DOCKER_OBJECT_ID_PATTERN.test(value)) { throw new TypeError(`${label} identity is invalid.`); } return value; } export function createLocalN8nSidecarPlan({ dockerHost, n8nContainerId, n8nContainerName, dockerNetworkId, networkName, authBinding, }) { return { kind: "n8n-sidecar", target: LOCAL_N8N_SIDECAR_TARGET, label: "Self-hosted n8n bridge", endpoint: LOCAL_N8N_SIDECAR_ENDPOINT, baseUrl: LOCAL_N8N_SIDECAR_ENDPOINT, protocol: "openai-v1", upstreamAuth: "chatgpt-oauth", dockerHost: validateLocalDockerHost(dockerHost), n8nContainerId: validateDockerObjectId( n8nContainerId, "n8n container", ), n8nContainerName: validateDockerName(n8nContainerName), dockerNetworkId: validateDockerObjectId( dockerNetworkId, "Docker network", ), networkName: validateDockerName(networkName), authBinding: validateSiwcAuthBinding(authBinding), managedPath: "~/.relmio/local/n8n-openai-oauth", hostPublication: "none", unofficial: false, }; } export function normalizeLocalN8nSidecarPlan(value) { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new TypeError("The local n8n sidecar plan is invalid."); } const normalized = createLocalN8nSidecarPlan({ dockerHost: value.dockerHost, n8nContainerId: value.n8nContainerId, n8nContainerName: value.n8nContainerName, dockerNetworkId: value.dockerNetworkId, networkName: value.networkName, authBinding: value.authBinding, }); for (const [name, expected] of Object.entries(normalized)) { if (name === "authBinding") { for (const [field, bound] of Object.entries(expected)) { if (value.authBinding?.[field] !== bound) { throw new TypeError("The local n8n sidecar plan is invalid."); } } } else if (value[name] !== expected) { throw new TypeError("The local n8n sidecar plan is invalid."); } } return normalized; } export function createLocalN8nSidecarDockerfile({ installId }) { const safeInstallId = validateInstallId(installId); return `FROM node:24-bookworm-slim ARG RELMIO_INSTALL_ID=${safeInstallId} LABEL io.relmio.managed="true" \\ io.relmio.target="${LOCAL_N8N_SIDECAR_TARGET}" \\ io.relmio.install="${safeInstallId}" WORKDIR /app COPY --chown=node:node package.json package-lock.json ./ RUN npm ci --omit=dev --ignore-scripts && npm cache clean --force COPY --chown=node:node services/ ./services/ COPY --chown=node:node gateway/ ./gateway/ COPY --chown=node:node infrastructure/ ./infrastructure/ USER node ENTRYPOINT ["node", "/app/gateway/openai-oauth-sidecar.mjs"] `; } export function createLocalN8nSidecarDockerignore() { return "**\n!Dockerfile\n!package.json\n!package-lock.json\n!services/\n!services/siwc-session.mjs\n!services/siwc-handoff.mjs\n!services/local-integration-lifecycle-lock.js\n!services/codex-images.mjs\n!services/model-discovery.mjs\n!gateway/\n!gateway/openai-oauth-sidecar.mjs\n!infrastructure/\n!infrastructure/local-process.js\n!infrastructure/process-identity.js\n"; } export function createLocalN8nSidecarComposeFile({ installId, networkName, registrationId, tokenSha256, }) { const safeInstallId = validateInstallId(installId); const safeNetworkName = validateDockerName(networkName); const safeRegistrationId = validateSiwcRegistrationId(registrationId); const safeRuntimeId = validateSiwcRuntimeId(safeInstallId); const safeVerifier = validateSha256Verifier(tokenSha256); const image = `relmio-n8n-openai-oauth-${safeInstallId}:local`; return `services: openai-oauth: image: ${image} build: context: . dockerfile: Dockerfile args: RELMIO_INSTALL_ID: "${safeInstallId}" labels: io.relmio.managed: "true" io.relmio.target: "${LOCAL_N8N_SIDECAR_TARGET}" io.relmio.install: "${safeInstallId}" restart: unless-stopped init: true environment: N8N_OPENAI_OAUTH_HOME: /home/node/.relmio-siwc RELMIO_REGISTRATION_ID: "${safeRegistrationId}" RELMIO_RUNTIME_ID: "${safeRuntimeId}" RELMIO_GATEWAY_TOKEN_SHA256: "${safeVerifier}" volumes: - siwc-store:/home/node/.relmio-siwc expose: - "10531" networks: n8n-shared: aliases: - ${LOCAL_N8N_SIDECAR_HOSTNAME} security_opt: - no-new-privileges:true cap_drop: - ALL read_only: true tmpfs: - /tmp:size=16m,mode=1777 - /home/node/.local:uid=1000,gid=1000,mode=0700 pids_limit: 128 mem_limit: 512m cpus: 1.0 healthcheck: test: - CMD - node - -e - 'fetch("http://127.0.0.1:10531/health").then((response) => process.exit(response.ok ? 0 : 1)).catch(() => process.exit(1))' interval: 10s timeout: 5s retries: 6 start_period: 20s labels: io.relmio.managed: "true" io.relmio.target: "${LOCAL_N8N_SIDECAR_TARGET}" io.relmio.install: "${safeInstallId}" credential-seed: image: ${image} pull_policy: never profiles: - relmio-credential-seed restart: "no" network_mode: none user: "0:0" entrypoint: - /bin/sh - -c command: - | set -eu owner="$$(stat -c '%u:%g:%a' /run/relmio-auth)" if [ "$$owner" = "0:0:755" ] || [ "$$owner" = "0:0:700" ]; then chmod 0700 /run/relmio-auth chown 1000:1000 /run/relmio-auth elif [ "$$owner" != "1000:1000:700" ]; then exit 1 fi [ "$$(stat -c '%u:%g:%a' /run/relmio-auth)" = "1000:1000:700" ] volumes: - siwc-store:/run/relmio-auth security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - CHOWN read_only: true pids_limit: 32 mem_limit: 64m cpus: 0.25 logging: driver: "none" labels: io.relmio.managed: "true" io.relmio.target: "${LOCAL_N8N_SIDECAR_TARGET}" io.relmio.install: "${safeInstallId}" networks: n8n-shared: external: true name: ${safeNetworkName} volumes: siwc-store: labels: io.relmio.managed: "true" io.relmio.target: "${LOCAL_N8N_SIDECAR_TARGET}" io.relmio.install: "${safeInstallId}" `; }