import test from 'node:test'; import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import { spawnSync } from 'node:child_process'; const servicePath = new URL('../deploy/timmy-staging.service', import.meta.url); const envPath = new URL('../deploy/timmy-staging.env.example', import.meta.url); const caddyPath = new URL('../deploy/Caddyfile.staging.example', import.meta.url); const runbookPath = new URL('../docs/STAGING-RUNBOOK.md', import.meta.url); const literalSecret = /(password|token|secret|api[_-]?key)\s*[=:]\s*(?!\$\{|<|CHANGE_ME|false|$)["']?[A-Za-z0-9/+_.-]{8,}/i; test('systemd template runs a dedicated loopback-only agent-disabled service', async () => { const service = await readFile(servicePath, 'utf8'); for (const directive of [ 'User=timmy-staging', 'Group=timmy-staging', 'WorkingDirectory=/opt/timmy-staging/current', 'EnvironmentFile=/etc/timmy-staging.env', 'Environment=HOST=127.0.0.1', 'Environment=PORT=4174', 'Environment=TIMMY_BASE_PATH=/timmy-staging', 'Environment=TIMMY_AGENT_ENABLED=false', ]) assert.match(service, new RegExp(`^${directive.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm'), directive); assert.match(service, /^ExecStart=\/usr\/bin\/env TIMMY_AGENT_ENABLED=false TIMMY_VISION_ENABLED=false \/usr\/local\/lib\/timmy-staging\/node server\.mjs$/m); assert.doesNotMatch(service, /ExecStart=\/usr\/(?:local\/)?bin\/node|0\.0\.0\.0|TIMMY_AGENT_ENABLED=true/); assert.doesNotMatch(service, literalSecret); }); test('systemd command-level environment keeps agent and vision disabled after EnvironmentFile overrides', async () => { const service = await readFile(servicePath, 'utf8'); const environmentFileIndex = service.indexOf('EnvironmentFile='); const execLine = service.match(/^ExecStart=(.+)$/m)?.[1]; assert.ok(execLine, 'ExecStart must exist'); assert.ok(environmentFileIndex < service.indexOf(`ExecStart=${execLine}`), 'EnvironmentFile must be loaded before command overrides'); const argv = execLine.trim().split(/\s+/); assert.equal(argv.shift(), '/usr/bin/env'); const assignments = argv.filter(value => /^TIMMY_(?:AGENT|VISION)_ENABLED=/.test(value)); assert.deepEqual(assignments, ['TIMMY_AGENT_ENABLED=false', 'TIMMY_VISION_ENABLED=false']); const probe = spawnSync('/usr/bin/env', [ ...assignments, process.execPath, '-e', 'process.stdout.write(`${process.env.TIMMY_AGENT_ENABLED},${process.env.TIMMY_VISION_ENABLED}`)', ], { env: { ...process.env, TIMMY_AGENT_ENABLED: 'true', TIMMY_VISION_ENABLED: 'true' }, encoding: 'utf8' }); assert.equal(probe.status, 0, probe.stderr); assert.equal(probe.stdout, 'false,false'); }); test('systemd template applies filesystem privilege process and resource confinement', async () => { const service = await readFile(servicePath, 'utf8'); for (const directive of [ 'UMask=0077', 'NoNewPrivileges=true', 'PrivateTmp=true', 'PrivateDevices=true', 'ProtectSystem=strict', 'ProtectHome=true', 'ReadWritePaths=/var/lib/timmy-staging', 'RestrictSUIDSGID=true', 'LockPersonality=true', 'RestrictNamespaces=true', 'ProtectKernelTunables=true', 'ProtectKernelModules=true', 'ProtectKernelLogs=true', 'ProtectControlGroups=true', 'ProtectClock=true', 'ProtectHostname=true', 'CapabilityBoundingSet=', 'AmbientCapabilities=', 'RestrictRealtime=true', 'TasksMax=64', 'MemoryMax=512M', 'LimitNOFILE=1024', ]) assert.match(service, new RegExp(`^${directive.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm'), directive); assert.equal((service.match(/^ReadWritePaths=/gm) || []).length, 1); }); test('environment example is least privilege base-path staging configuration without credentials', async () => { const env = await readFile(envPath, 'utf8'); for (const setting of [ 'HOST=127.0.0.1', 'PORT=4174', 'TIMMY_BASE_PATH=/timmy-staging', 'TIMMY_STAGING_LABEL=true', 'TIMMY_AGENT_ENABLED=false', 'TIMMY_VISION_ENABLED=false', ]) assert.match(env, new RegExp(`^${setting.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm')); assert.match(env, /install -m 600/); assert.doesNotMatch(env, /TIMMY_AGENT_ACCESS_TOKEN|GITEA_TOKEN|OPENAI_API_KEY|COOKIE/); assert.doesNotMatch(env, literalSecret); }); test('Caddy snippet isolates git and authenticates staging before a catchall', async () => { const caddy = await readFile(caddyPath, 'utf8'); const git = caddy.indexOf('handle @git'); const staging = caddy.indexOf('handle_path /timmy-staging/*'); const catchall = caddy.indexOf('handle {'); assert.ok(git >= 0 && staging > git && catchall > staging, 'route order must be git, staging, catchall'); assert.match(caddy, /basic_auth/); assert.match(caddy, /\{\$TIMMY_STAGING_PASSWORD_HASH\}/); assert.match(caddy, /reverse_proxy 127\.0\.0\.1:4174/); assert.match(caddy, /max_size 8MB/); assert.match(caddy, /X-Content-Type-Options "nosniff"/); assert.match(caddy, /X-Frame-Options "DENY"/); assert.match(caddy, /Referrer-Policy "no-referrer"/); assert.match(caddy, /Content-Security-Policy/); assert.match(caddy, /rewrite \* \/timmy-staging\{uri\}/, 'handle_path strips once, then upstream base path is explicitly reconstructed'); assert.doesNotMatch(caddy, /\$2[aby]\$[A-Za-z0-9./]{20,}|password\s+[^<{\s]/i); }); test('runbook covers safe installation operation verification rollback backup and removal', async () => { const runbook = await readFile(runbookPath, 'utf8'); for (const heading of [ 'Prerequisites', 'DNS and URL', 'Install', 'Promote', 'Smoke test', 'Status and logs', 'Rollback', 'Backup', 'Remove staging', 'Template validation', ]) assert.match(runbook, new RegExp(`^## .*${heading}`, 'mi'), heading); assert.match(runbook, /sha256/i); assert.match(runbook, /install -D -o root -g root -m 755[^\n]+\/usr\/local\/lib\/timmy-staging\/node/); assert.match(runbook, /\/usr\/local\/lib\/timmy-staging\/node --version/); assert.match(runbook, /chmod 600|install -m 600/); assert.match(runbook, /systemctl restart timmy-staging\.service/); assert.match(runbook, /no prior release[^.]*stops[^.]*service/i); assert.match(runbook, /failed release[^.]*inert[^.]*evidence/i); assert.match(runbook, /journalctl -u timmy-staging\.service/); assert.match(runbook, /do not.*live|approval/i); assert.doesNotMatch(runbook, literalSecret); });