import { spawnSync } from "node:child_process"; import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { expect, it } from "vitest"; const root = process.cwd(); const hasCompose = spawnSync("docker", ["compose", "version"], { encoding: "utf8", timeout: 10_000, }).status === 0; it.skipIf(!hasCompose)( "loads the loopback profile from .env for bare Compose commands without changing mounts or host networking", () => { const directory = mkdtempSync(path.join(tmpdir(), "cms-proxy-config-")); try { mkdirSync(path.join(directory, "deployment/proxy"), { recursive: true }); copyFileSync( path.join(root, "docker-compose.yml"), path.join(directory, "docker-compose.yml"), ); copyFileSync( path.join(root, "deployment/proxy/compose.loopback.yml"), path.join(directory, "deployment/proxy/compose.loopback.yml"), ); writeFileSync( path.join(directory, ".env"), [ "COMPOSE_FILE=docker-compose.yml:deployment/proxy/compose.loopback.yml", "COMPOSE_PATH_SEPARATOR=:", "CMS_RELEASE=reviewed-release", "HOSTNAME=0.0.0.0", "PORT=9999", "HOTEL_NAME=Proxy fixture", "DATABASE_URL=mysql://fixture:fixture@127.0.0.1/fixture", ].join("\n"), ); const environment = { ...process.env }; for (const key of Object.keys(environment)) if ( key.startsWith("COMPOSE_") || ["CMS_RELEASE", "HOSTNAME", "PORT"].includes(key) ) delete environment[key]; const result = spawnSync( "docker", ["compose", "config", "--format", "json"], { cwd: directory, env: environment, encoding: "utf8", timeout: 15_000 }, ); expect(result.status, result.stderr).toBe(0); const config = JSON.parse(result.stdout); const cms = config.services.cms; expect(cms.environment.HOSTNAME).toBe("127.0.0.1"); expect(cms.environment.PORT).toBe("3002"); expect(cms.network_mode).toBe("host"); expect(cms.ports).toBeUndefined(); expect(cms.image).toBe("epicnext-cms:reviewed-release"); expect(cms.healthcheck.test).toEqual([ "CMD", "node", "-e", "fetch('http://127.0.0.1:3002/api/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))", ]); expect(cms.volumes.map((volume) => volume.target).sort()).toEqual([ "/app/public/nitro-assets", "/app/public/swf", "/app/storage", "/var/www/Gamedata", ]); expect(cms.environment.DATABASE_URL).toBe( "mysql://fixture:fixture@127.0.0.1/fixture", ); } finally { rmSync(directory, { recursive: true, force: true }); } }, 30_000, ); it("documents the local CrowdSec switches in .env.example", () => { const examples = readFileSync(path.join(root, ".env.example"), "utf8"); for (const key of [ "CROWDSEC_LOCAL_ENABLED", "CROWDSEC_LAPI_URL", "CROWDSEC_LAPI_PORT", "CROWDSEC_LAPI_API_KEY", "CROWDSEC_NGINX_LOG_DIR", ]) { expect(examples).toContain(key); } }); it.skipIf(!hasCompose)( "renders the standalone CrowdSec stack with a loopback-only LAPI", () => { const directory = mkdtempSync(path.join(tmpdir(), "cms-crowdsec-")); try { mkdirSync(path.join(directory, "deployment/crowdsec/acquis.d"), { recursive: true, }); copyFileSync( path.join(root, "deployment/crowdsec/compose.crowdsec.yml"), path.join(directory, "deployment/crowdsec/compose.crowdsec.yml"), ); copyFileSync( path.join(root, "deployment/crowdsec/acquis.d/nginx.yaml"), path.join(directory, "deployment/crowdsec/acquis.d/nginx.yaml"), ); writeFileSync( path.join(directory, ".env"), [ "CROWDSEC_LAPI_API_KEY=fixture-key", "CROWDSEC_LAPI_PORT=18080", "CROWDSEC_LAPI_URL=http://127.0.0.1:18080", "CROWDSEC_NGINX_LOG_DIR=/var/log/nginx", ].join("\n"), ); const environment = { ...process.env }; for (const key of Object.keys(environment)) if ( key.startsWith("COMPOSE_") || key.startsWith("CROWDSEC_") || key.startsWith("TZ") ) delete environment[key]; const result = spawnSync( "docker", [ "compose", "--project-name", "crowdsec-fixture", "--env-file", ".env", "-f", "deployment/crowdsec/compose.crowdsec.yml", "--profile", "security", "config", "--format", "json", ], { cwd: directory, env: environment, encoding: "utf8", timeout: 15_000 }, ); expect(result.status, result.stderr).toBe(0); const config = JSON.parse(result.stdout); const service = config.services.crowdsec; expect(service).toBeDefined(); expect(service.image).toContain("crowdsecurity/crowdsec:"); expect(service.environment.BOUNCER_KEY_cms).toBe("fixture-key"); expect(service.environment.DISABLE_ONLINE_API).toBe("true"); expect( service.ports.some( (published) => published.host_ip === "127.0.0.1" && published.published === "18080" && published.target === 8080, ), ).toBe(true); const targets = service.volumes.map((volume) => volume.target); expect(targets).toContain("/var/log/nginx"); expect(targets).toContain("/etc/crowdsec/acquis.d"); expect(service.healthcheck.test.join(" ")).toContain("wget"); } finally { rmSync(directory, { recursive: true, force: true }); } }, 30_000, );