{config, ...}: let sandboxApiPort = config.m3ta.ports.get "n8n-sandbox-api"; tlsDir = "/var/lib/n8n-sandbox/tls"; apiImage = "ghcr.io/n8n-io/n8n-sandbox-service-api:1.2.0"; runnerImage = "ghcr.io/n8n-io/n8n-sandbox-service-runner-dind:1.2.0"; sandboxImage = "ghcr.io/n8n-io/n8n-sandbox-service-sandbox:latest"; in { virtualisation.oci-containers.containers = { sandbox-api = { image = apiImage; environmentFiles = [config.age.secrets.n8n-sandbox-env.path]; environment = { 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 = ["${tlsDir}:/tls:ro"]; ports = ["127.0.0.1:${toString sandboxApiPort}:8080"]; extraOptions = ["--network=n8n-sandbox"]; }; sandbox-runner-1 = { image = runnerImage; environmentFiles = [config.age.secrets.n8n-sandbox-env.path]; environment = { SANDBOX_RUNNER_API_GRPC_ADDR = "sandbox-api:9090"; 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 = sandboxImage; 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 = ["${tlsDir}:/tls:ro"]; dependsOn = ["sandbox-api"]; extraOptions = ["--network=n8n-sandbox" "--privileged"]; }; }; systemd.services.n8n-sandbox-certs = { description = "n8n sandbox: mTLS certificate bootstrap"; wantedBy = ["multi-user.target"]; after = ["network-online.target"]; wants = ["network-online.target"]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; ConditionPathExists = "!${tlsDir}/ca.crt"; }; path = [config.virtualisation.podman.package]; preStart = '' mkdir -p ${tlsDir} ''; script = '' podman run --rm \ --name n8n-sandbox-certs \ --user 0:0 \ -e NUM_RUNNERS=1 \ -v ${tlsDir}:/tls \ --entrypoint sh \ ${apiImage} \ -c 'bootstrap-mtls.sh --out-dir /tls --api-san sandbox-api --control-san-prefix sandbox-runner --world-readable && chown -R sandbox-api:sandbox-api /tls/api && chmod -R a+rX /tls' ''; }; systemd.services."podman-sandbox-api" = { after = ["n8n-sandbox-certs.service"]; requires = ["n8n-sandbox-certs.service"]; preStart = '' ${config.virtualisation.podman.package}/bin/podman rm -f sandbox-api 2>/dev/null || true ''; }; systemd.services."podman-sandbox-runner-1" = { after = ["n8n-sandbox-certs.service"]; requires = ["n8n-sandbox-certs.service"]; preStart = '' ${config.virtualisation.podman.package}/bin/podman rm -f sandbox-runner-1 2>/dev/null || true ''; }; }