#!/usr/bin/env node import { existsSync, readFileSync } from "node:fs"; const requiredFiles = [ "unraid/install.sh", "unraid/projectbrain.xml", "unraid/projectbrain-icon.png", "unraid/README.md", "docker-compose.yml", "Dockerfile.unraid", "docker/nginx.single.conf", "scripts/deploy-unraid.sh", "scripts/deploy-unraid-single.sh", "scripts/unraid-single-entrypoint.sh", "docker/unraid/README.md", "docker/unraid/projectbrain.env.example", "docker/unraid/projectbrain-container.env.example", "docker/unraid/projectbrain-compose-template.xml", "docker/unraid/projectbrain-container-template.xml", "docker/unraid/projectbrain-icon.png", "docs/DOCKER_UNRAID_SETUP.md", ]; const failures = []; const read = (file) => readFileSync(file, "utf8"); for (const file of requiredFiles) { if (!existsSync(file)) failures.push(`Missing required file: ${file}`); } const compose = existsSync("docker-compose.yml") ? read("docker-compose.yml") : ""; const env = existsSync("docker/unraid/projectbrain.env.example") ? read("docker/unraid/projectbrain.env.example") : ""; const containerEnv = existsSync("docker/unraid/projectbrain-container.env.example") ? read("docker/unraid/projectbrain-container.env.example") : ""; const xml = existsSync("docker/unraid/projectbrain-compose-template.xml") ? read("docker/unraid/projectbrain-compose-template.xml") : ""; const containerXml = existsSync("docker/unraid/projectbrain-container-template.xml") ? read("docker/unraid/projectbrain-container-template.xml") : ""; const icon = existsSync("docker/unraid/projectbrain-icon.png") ? "png-icon" : ""; const readme = existsSync("docker/unraid/README.md") ? read("docker/unraid/README.md") : ""; const docs = existsSync("docs/DOCKER_UNRAID_SETUP.md") ? read("docs/DOCKER_UNRAID_SETUP.md") : ""; const deploy = existsSync("scripts/deploy-unraid.sh") ? read("scripts/deploy-unraid.sh") : ""; const singleDockerfile = existsSync("Dockerfile.unraid") ? read("Dockerfile.unraid") : ""; const singleNginx = existsSync("docker/nginx.single.conf") ? read("docker/nginx.single.conf") : ""; const singleEntry = existsSync("scripts/unraid-single-entrypoint.sh") ? read("scripts/unraid-single-entrypoint.sh") : ""; const singleDeploy = existsSync("scripts/deploy-unraid-single.sh") ? read("scripts/deploy-unraid-single.sh") : ""; const unraidInstall = existsSync("unraid/install.sh") ? read("unraid/install.sh") : ""; const unraidTemplate = existsSync("unraid/projectbrain.xml") ? read("unraid/projectbrain.xml") : ""; const unraidIcon = existsSync("unraid/projectbrain-icon.png") ? "png-icon" : ""; const unraidReadme = existsSync("unraid/README.md") ? read("unraid/README.md") : ""; const mustContain = [ [compose, "${PROJECTBRAIN_WEB_PORT:-1209}:80", "docker-compose.yml must keep the frontend host port configurable."], [compose, "${POSTGRES_PASSWORD:?", "Production compose must require a PostgreSQL password."], [compose, "${PROJECTBRAIN_OWNER_PASSWORD:?", "Production compose must require the owner password."], [compose, "${PROJECTBRAIN_AUTH_SECRET:?", "Production compose must require the authentication secret."], [unraidInstall, "templates-user", "Root Unraid installer must write a DockerMan template."], [unraidInstall, "dockerMan/images", "Root Unraid installer must write a DockerMan icon."], [unraidInstall, "git clone", "Root Unraid installer must support installing from Gitea."], [unraidInstall, "deploy-unraid-single.sh", "Root Unraid installer must deploy the single-container app."], [unraidTemplate, "projectbrain", "Root Unraid template name must match the running container name so DockerMan can show Edit."], [unraidTemplate, "projectbrain-unraid:latest", "Root Unraid template must reference the built ProjectBrain image."], [unraidTemplate, "Target=\"80\"", "Root Unraid template must expose container port 80."], [unraidTemplate, "projectbrain-icon.png", "Root Unraid template must reference the PNG icon."], [unraidTemplate, "--label net.unraid.docker.managed=dockerman", "Root Unraid template must create DockerMan-managed containers."], [unraidTemplate, "Target=\"PROJECTBRAIN_OWNER_PASSWORD\" Default=\"\"", "Root Unraid template must require an explicit owner password."], [unraidTemplate, "Target=\"PROJECTBRAIN_AUTH_SECRET\" Default=\"\"", "Root Unraid template must require an explicit authentication secret."], [unraidTemplate, "Target=\"POSTGRES_PASSWORD\" Default=\"\"", "Root Unraid template must not ship an embedded PostgreSQL password."], [unraidIcon, "png-icon", "Root Unraid PNG icon must exist."], [unraidReadme, "Docker > Add Container > Template: ProjectBrain", "Root Unraid README must explain where the template appears."], [env, "PROJECTBRAIN_WEB_PORT=1209", "Unraid env template must expose PROJECTBRAIN_WEB_PORT."], [env, "VITE_PROJECTBRAIN_CLIENT_MODE=real", "Unraid env template must default the frontend to real mode."], [env, "PROJECTBRAIN_DATA_SOURCE=database", "Unraid env template must default backend data source to database."], [containerEnv, "PROJECTBRAIN_IMAGE=projectbrain-unraid:latest", "Single-container env template must expose the local image name."], [containerEnv, "PROJECTBRAIN_DOCKER_NETWORK=auto", "Single-container env template must default Docker network detection to auto."], [containerEnv, "PROJECTBRAIN_STOP_COMPOSE_APP_SERVICES=true", "Single-container env template must stop old compose app containers by default."], [containerEnv, "PROJECTBRAIN_USE_EXTERNAL_DATABASE=false", "Single-container env template must default to embedded Postgres."], [containerEnv, "PROJECTBRAIN_DEPLOY_NO_CACHE=false", "Single-container env template must default to cached deploys."], [containerEnv, "PROJECTBRAIN_MIN_DOCKER_FREE_MB=2048", "Single-container env template must expose Docker storage threshold."], [containerEnv, "PROJECTBRAIN_REQUIRE_FRESH_BACKUP=false", "Single-container env template must expose backup freshness gate."], [containerEnv, "POSTGRES_PASSWORD=", "Single-container env template must expose the embedded Postgres secret input."], [xml, "ProjectBrain", "Unraid XML template must name ProjectBrain."], [xml, "PROJECTBRAIN_WEB_PORT", "Unraid XML template must expose the web port variable."], [xml, "http://[IP]:[PORT:1209]", "Unraid XML template must define the WebUI URL."], [xml, "projectbrain-icon.png", "Unraid XML template must reference the PNG icon asset."], [xml, "POSTGRES_PASSWORD", "Unraid XML template must expose Postgres password."], [containerXml, "projectbrain", "Single-container XML template name must match the running container name."], [containerXml, "projectbrain-unraid:latest", "Single-container XML template must point at the local ProjectBrain image."], [containerXml, "--label net.unraid.docker.managed=dockerman", "Single-container XML template must create DockerMan-managed containers."], [containerXml, "Target=\"80\"", "Single-container XML template must expose container port 80."], [containerXml, "Target=\"/var/lib/projectbrain\"", "Single-container XML template must map persistent appdata."], [containerXml, "Target=\"PROJECTBRAIN_OWNER_PASSWORD\" Default=\"\"", "Single-container XML template must require an explicit owner password."], [containerXml, "Target=\"PROJECTBRAIN_AUTH_SECRET\" Default=\"\"", "Single-container XML template must require an explicit authentication secret."], [containerXml, "Target=\"POSTGRES_PASSWORD\" Default=\"\"", "Single-container XML template must not ship an embedded PostgreSQL password."], [containerXml, "projectbrain-icon.png", "Single-container XML template must reference the PNG icon asset."], [icon, "png-icon", "Unraid PNG icon must exist."], [singleDockerfile, "nginx.single.conf", "Single-container Dockerfile must use the single-container nginx config."], [singleDockerfile, "unraid-single-entrypoint.sh", "Single-container Dockerfile must use the single-container entrypoint."], [singleDockerfile, "HEALTHCHECK", "Single-container Dockerfile must define a healthcheck."], [singleNginx, "127.0.0.1:4000", "Single-container nginx config must proxy API calls to the internal backend."], [singleEntry, "DATABASE_URL is required", "Single-container entrypoint must fail clearly without DATABASE_URL."], [singleEntry, "POSTGRES_PASSWORD is required for embedded PostgreSQL", "Single-container entrypoint must fail closed without an embedded PostgreSQL password."], [singleEntry, "PROJECTBRAIN_USE_EXTERNAL_DATABASE", "Single-container entrypoint must default to embedded Postgres unless external DB is requested."], [singleEntry, "initdb", "Single-container entrypoint must initialize embedded Postgres."], [singleEntry, "nginx -g", "Single-container entrypoint must start nginx."], [readme, "Change The External Port", "Unraid README must explain port changes."], [readme, "Single Container", "Unraid README must document the single-container template."], [readme, "scripts/deploy-unraid.sh", "Unraid README must reference the deploy helper."], [readme, "scripts/deploy-unraid-single.sh", "Unraid README must reference the single-container deploy helper."], [readme, "docker compose up -d --build", "Unraid README must include restart command."], [docs, "projectbrain.env.example", "Docker/Unraid docs must reference the Unraid env template."], [docs, "projectbrain-container-template.xml", "Docker/Unraid docs must reference the single-container XML template."], [docs, "scripts/deploy-unraid.sh", "Docker/Unraid docs must reference the deploy helper."], [docs, "scripts/deploy-unraid-single.sh", "Docker/Unraid docs must reference the single-container deploy helper."], [deploy, "git pull --ff-only", "Unraid deploy helper must pull the latest repository state."], [deploy, "projectbrain.env.example", "Unraid deploy helper must apply the env template."], [deploy, "docker compose build --no-cache", "Unraid deploy helper must rebuild the stack."], [deploy, "api/system/readiness", "Unraid deploy helper must check readiness."], [singleDeploy, "Dockerfile.unraid", "Single-container deploy helper must build Dockerfile.unraid."], [singleDeploy, "PROJECTBRAIN_DEPLOY_NO_CACHE", "Single-container deploy helper must make no-cache configurable."], [singleDeploy, "docker build -f Dockerfile.unraid", "Single-container deploy helper must allow cached builds by default."], [singleDeploy, "Docker storage preflight", "Single-container deploy helper must show Docker storage preflight."], [singleDeploy, "docker system df", "Single-container deploy helper must print Docker storage usage."], [singleDeploy, "PROJECTBRAIN_MIN_DOCKER_FREE_MB", "Single-container deploy helper must expose Docker free-space threshold."], [singleDeploy, "backup freshness preflight", "Single-container deploy helper must check backup freshness."], [singleDeploy, "PROJECTBRAIN_REQUIRE_FRESH_BACKUP", "Single-container deploy helper must expose strict backup gate."], [singleDeploy, "git rev-parse --short HEAD", "Single-container deploy helper must report the current git commit."], [singleDeploy, "started ${CONTAINER_NAME}", "Single-container deploy helper must report container replacement id."], [singleDeploy, "projectbrain-postgres", "Single-container deploy helper must auto-detect the Postgres container network."], [singleDeploy, "projectbrain-frontend projectbrain-api", "Single-container deploy helper must remove old compose app containers for live cutover."], [singleDeploy, "projectbrain-postgres", "Single-container deploy helper must remove the old standalone ProjectBrain Postgres container."], [singleDeploy, "projectbrain-icon.png", "Single-container deploy helper must set the Unraid PNG icon label."], [singleDeploy, "docker run -d", "Single-container deploy helper must run one app container."], [singleDeploy, "projectbrain-container.env.example", "Single-container deploy helper must apply the container env template."], [singleDeploy, "api/system/readiness", "Single-container deploy helper must check readiness."], ]; for (const [haystack, needle, message] of mustContain) { if (!haystack.includes(needle)) failures.push(message); } for (const secretName of ["POSTGRES_PASSWORD", "PROJECTBRAIN_OWNER_PASSWORD", "PROJECTBRAIN_AUTH_SECRET"]) { if (new RegExp(`^${secretName}=.+$`, "m").test(containerEnv)) failures.push(`Single-container env template must not ship a default ${secretName}.`); } for (const [templateName, template] of [["root", unraidTemplate], ["single-container", containerXml]]) { if (/Target="POSTGRES_PASSWORD"\s+Default="projectbrain"/i.test(template)) failures.push(`${templateName} Unraid template must not ship the historical PostgreSQL password.`); if (/Target="PROJECTBRAIN_OWNER_PASSWORD"\s+Default="[^"]+"/i.test(template)) failures.push(`${templateName} Unraid template must not ship an owner password.`); if (/Target="PROJECTBRAIN_AUTH_SECRET"\s+Default="[^"]+"/i.test(template)) failures.push(`${templateName} Unraid template must not ship an authentication secret.`); } if (failures.length) { console.error("ProjectBrain Unraid template readiness: FAIL"); for (const failure of failures) console.error(`- ${failure}`); process.exit(1); } console.log("ProjectBrain Unraid template readiness: PASS");