2026-04-27 08:02:27 +02:00
|
|
|
import fs from 'fs';
|
|
|
|
|
import path from 'path';
|
|
|
|
|
|
|
|
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
|
|
|
|
2026-05-07 11:16:40 +02:00
|
|
|
import { detectExistingInstall, resolveInstallMode } from './install-mode.js';
|
refactor(identity): remove PLATFORM_ID/SERVICE_NAME/RUNTIME_USER env vars
Step 5 of system-namespace cutover: complete the env-var removal that
step 4 set up. All consumers now import SERVICE_NAME from
src/platform-identity.ts directly; the deprecated PLATFORM_*
re-exports in src/config.ts are gone.
src/config.ts:
- PLATFORM_ID, PLATFORM_SERVICE_NAME, PLATFORM_RUNTIME_USER exports
removed.
- PLATFORM_RUNTIME_HOME stays (derived from SERVICE_NAME, used by
~10 consumers for path construction).
- Env-var allowlist drops PLATFORM_ID / PLATFORM_SERVICE_NAME /
PLATFORM_RUNTIME_USER / PLATFORM_RUNTIME_HOME entries.
- CONTROLPLANE_AIDER_TMUX_SESSION uses SERVICE_NAME directly.
setup/onboarding.ts:
- writeIdentity() simplified to write only ASSISTANT_NAME (display).
PLATFORM_ID / PLATFORM_SERVICE_NAME / PLATFORM_RUNTIME_USER are no
longer written to .env. Fresh installs have no PLATFORM_* keys.
- Status emission switched from PLATFORM_ID to SERVICE_NAME.
setup/env-audit.ts:
- Audit lists SERVICE_NAME instead of PLATFORM_ID; the env-file
PLATFORM_ID read is gone.
24 source files (src/*.ts, setup/*.ts, scripts/dashboard.ts):
- Bare PLATFORM_ID / PLATFORM_SERVICE_NAME / PLATFORM_RUNTIME_USER
references replaced with SERVICE_NAME.
- Imports rewired: SERVICE_NAME comes from
../{src/}platform-identity.js, not from config.js.
- Imports deduped where the sed sweep produced collisions.
Shell scripts (scripts/bhyve-evidence.sh, glass.sh, inspect-system.sh):
- Hardcoded SERVICE_NAME='clawdie' and SERVICE_USER='clawdie'.
No more grep-the-.env fallbacks; the constants are the source.
Tests (middle path):
- Mechanical fixes (import path, renamed assertion text):
src/hostd/privileged-commands.test.ts, src/startup-report.test.ts,
setup/env-audit.test.ts, setup/install-mode.test.ts.
- Skipped with `// system-namespace:` markers (pinned removed
env-driven override behavior; Codex rewrites once the bootstrap-
config service-user override path lands):
setup/verify.test.ts > 'uses the platform service name for PID candidates'
setup/service.test.ts > 'resolves a platform runtime separately from the tenant'
Test files still containing PLATFORM_* strings in vi.mock contents,
ENV_KEYS arrays, or comments are left untouched — they are test
artifacts that don't affect runtime; mock contents resolve to
'clawdie' which still equals SERVICE_NAME.
tsc clean. 2095 tests pass, 4 skipped, 0 fail.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---
Build: pass | Tests: pass — Tests 2095 passed | 4 skipped (2099)
2026-05-02 14:49:19 +02:00
|
|
|
import { SERVICE_NAME } from '../src/platform-identity.js';
|
2026-04-27 08:02:27 +02:00
|
|
|
|
|
|
|
|
function makeProjectRoot(): string {
|
|
|
|
|
const base = path.join(process.cwd(), 'tmp', 'tests');
|
|
|
|
|
fs.mkdirSync(base, { recursive: true });
|
|
|
|
|
return fs.mkdtempSync(path.join(base, 'clawdie-install-mode-'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function removeDir(dirPath: string): void {
|
|
|
|
|
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('detectExistingInstall', () => {
|
|
|
|
|
const roots: string[] = [];
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
while (roots.length > 0) {
|
|
|
|
|
removeDir(roots.pop()!);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('treats two soft signals as an existing install', () => {
|
|
|
|
|
const root = makeProjectRoot();
|
|
|
|
|
roots.push(root);
|
|
|
|
|
fs.writeFileSync(path.join(root, '.env'), 'OPENROUTER_API_KEY=test\n');
|
|
|
|
|
fs.mkdirSync(path.join(root, 'groups'), { recursive: true });
|
|
|
|
|
fs.writeFileSync(path.join(root, 'groups', 'ops.json'), '{}');
|
|
|
|
|
|
|
|
|
|
const detection = detectExistingInstall(root, {
|
2026-05-03 20:58:27 +02:00
|
|
|
existsSync: (target) =>
|
|
|
|
|
fs.existsSync(target) &&
|
|
|
|
|
target !== `/usr/local/etc/rc.d/${SERVICE_NAME}`,
|
2026-04-27 08:02:27 +02:00
|
|
|
commandExists: () => false,
|
|
|
|
|
execFileSync: (() => {
|
|
|
|
|
throw new Error('not called');
|
|
|
|
|
}) as typeof import('child_process').execFileSync,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(detection.existing).toBe(true);
|
|
|
|
|
expect(detection.softSignals).toEqual(['env', 'groups']);
|
|
|
|
|
expect(detection.strongSignals).toEqual([]);
|
2026-04-27 10:58:26 +02:00
|
|
|
expect(detection.installUuid).toBeNull();
|
2026-04-27 08:02:27 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('treats a strong signal as an existing install', () => {
|
|
|
|
|
const root = makeProjectRoot();
|
|
|
|
|
roots.push(root);
|
|
|
|
|
|
|
|
|
|
const detection = detectExistingInstall(root, {
|
|
|
|
|
existsSync: (target) =>
|
2026-05-07 11:16:40 +02:00
|
|
|
target === `/usr/local/etc/rc.d/${SERVICE_NAME}` ||
|
|
|
|
|
fs.existsSync(target),
|
2026-04-27 08:02:27 +02:00
|
|
|
commandExists: () => false,
|
|
|
|
|
execFileSync: (() => {
|
|
|
|
|
throw new Error('not called');
|
|
|
|
|
}) as typeof import('child_process').execFileSync,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(detection.existing).toBe(true);
|
|
|
|
|
expect(detection.strongSignals).toContain('service');
|
2026-04-27 10:58:26 +02:00
|
|
|
expect(detection.installUuid).toBeNull();
|
2026-04-27 08:02:27 +02:00
|
|
|
});
|
|
|
|
|
|
2026-04-27 10:06:44 +02:00
|
|
|
it('prefers system.env zfs pool/prefix when matching datasets', () => {
|
|
|
|
|
const root = makeProjectRoot();
|
|
|
|
|
roots.push(root);
|
|
|
|
|
fs.writeFileSync(
|
|
|
|
|
path.join(root, 'system.env'),
|
2026-05-07 11:16:40 +02:00
|
|
|
['ZFS_POOL=tank', 'ZFS_PREFIX=alpha-runtime', ''].join('\n'),
|
2026-04-27 10:06:44 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const detection = detectExistingInstall(root, {
|
2026-05-03 20:58:27 +02:00
|
|
|
existsSync: (target) =>
|
|
|
|
|
fs.existsSync(target) &&
|
|
|
|
|
target !== `/usr/local/etc/rc.d/${SERVICE_NAME}`,
|
2026-04-27 10:06:44 +02:00
|
|
|
commandExists: (name) => name === 'zfs',
|
|
|
|
|
execFileSync: ((cmd: string) => {
|
2026-05-07 11:16:40 +02:00
|
|
|
if (cmd === 'zfs')
|
|
|
|
|
return 'tank/alpha-runtime\ntank/alpha-runtime/pgdata\n';
|
2026-04-27 10:06:44 +02:00
|
|
|
throw new Error('unexpected command');
|
|
|
|
|
}) as typeof import('child_process').execFileSync,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(detection.existing).toBe(true);
|
|
|
|
|
expect(detection.strongSignals).toContain('zfs');
|
2026-05-07 11:16:40 +02:00
|
|
|
expect(detection.rootDataset).toBe('tank/alpha-runtime');
|
2026-04-27 10:58:26 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('treats persisted zfs install metadata as a strong signal', () => {
|
|
|
|
|
const root = makeProjectRoot();
|
|
|
|
|
roots.push(root);
|
|
|
|
|
fs.writeFileSync(
|
|
|
|
|
path.join(root, 'system.env'),
|
2026-05-07 11:16:40 +02:00
|
|
|
['ZFS_POOL=tank', 'ZFS_PREFIX=alpha-runtime', ''].join('\n'),
|
2026-04-27 10:58:26 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const detection = detectExistingInstall(root, {
|
2026-05-03 20:58:27 +02:00
|
|
|
existsSync: (target) =>
|
|
|
|
|
fs.existsSync(target) &&
|
|
|
|
|
target !== `/usr/local/etc/rc.d/${SERVICE_NAME}`,
|
2026-04-27 10:58:26 +02:00
|
|
|
commandExists: (name) => name === 'zfs',
|
|
|
|
|
execFileSync: ((cmd: string, args: string[]) => {
|
|
|
|
|
if (cmd !== 'zfs') throw new Error('unexpected command');
|
|
|
|
|
if (args[0] === 'list') return '';
|
|
|
|
|
if (args[0] === 'get') return 'abc123\n';
|
|
|
|
|
throw new Error('unexpected zfs args');
|
|
|
|
|
}) as typeof import('child_process').execFileSync,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(detection.existing).toBe(true);
|
|
|
|
|
expect(detection.strongSignals).toContain('zfs-metadata');
|
|
|
|
|
expect(detection.installUuid).toBe('abc123');
|
2026-04-27 10:06:44 +02:00
|
|
|
});
|
|
|
|
|
|
2026-04-27 08:02:27 +02:00
|
|
|
it('returns fresh when no signals are present', () => {
|
|
|
|
|
const root = makeProjectRoot();
|
|
|
|
|
roots.push(root);
|
|
|
|
|
|
|
|
|
|
const detection = detectExistingInstall(root, {
|
2026-05-03 20:58:27 +02:00
|
|
|
existsSync: (target) =>
|
|
|
|
|
fs.existsSync(target) &&
|
|
|
|
|
target !== `/usr/local/etc/rc.d/${SERVICE_NAME}`,
|
2026-04-27 08:02:27 +02:00
|
|
|
commandExists: () => false,
|
|
|
|
|
execFileSync: (() => {
|
|
|
|
|
throw new Error('not called');
|
|
|
|
|
}) as typeof import('child_process').execFileSync,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(detection.existing).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('resolveInstallMode', () => {
|
|
|
|
|
const freshDetection = {
|
|
|
|
|
signals: {
|
|
|
|
|
envFile: false,
|
|
|
|
|
groupsDir: false,
|
|
|
|
|
serviceFile: false,
|
|
|
|
|
zfsDataset: false,
|
2026-04-27 10:58:26 +02:00
|
|
|
zfsMetadata: false,
|
2026-04-27 08:02:27 +02:00
|
|
|
runtimeUser: false,
|
|
|
|
|
},
|
|
|
|
|
strongSignals: [],
|
|
|
|
|
softSignals: [],
|
|
|
|
|
existing: false,
|
2026-04-27 10:58:26 +02:00
|
|
|
rootDataset: null,
|
|
|
|
|
installUuid: null,
|
2026-04-27 08:02:27 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const existingDetection = {
|
|
|
|
|
signals: {
|
|
|
|
|
envFile: true,
|
|
|
|
|
groupsDir: true,
|
|
|
|
|
serviceFile: false,
|
|
|
|
|
zfsDataset: true,
|
2026-04-27 10:58:26 +02:00
|
|
|
zfsMetadata: false,
|
2026-04-27 08:02:27 +02:00
|
|
|
runtimeUser: false,
|
|
|
|
|
},
|
|
|
|
|
strongSignals: ['zfs'],
|
|
|
|
|
softSignals: ['env', 'groups'],
|
|
|
|
|
existing: true,
|
2026-04-27 10:58:26 +02:00
|
|
|
rootDataset: 'zroot/clawdie-runtime',
|
|
|
|
|
installUuid: null,
|
2026-04-27 08:02:27 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
it('resolves auto to fresh when nothing exists', () => {
|
|
|
|
|
expect(resolveInstallMode('auto', freshDetection).effective).toBe('fresh');
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-27 09:07:18 +02:00
|
|
|
it('resolves auto to upgrade when an install exists', () => {
|
2026-05-07 11:16:40 +02:00
|
|
|
expect(resolveInstallMode('auto', existingDetection).effective).toBe(
|
|
|
|
|
'upgrade',
|
|
|
|
|
);
|
2026-04-27 08:02:27 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects fresh against an existing install', () => {
|
2026-05-07 11:16:40 +02:00
|
|
|
expect(() => resolveInstallMode('fresh', existingDetection)).toThrow(
|
|
|
|
|
/INSTALL_MODE=fresh/,
|
|
|
|
|
);
|
2026-04-27 08:02:27 +02:00
|
|
|
});
|
|
|
|
|
|
2026-04-27 09:07:18 +02:00
|
|
|
it('rejects upgrade when no install exists', () => {
|
2026-05-07 11:16:40 +02:00
|
|
|
expect(() => resolveInstallMode('upgrade', freshDetection)).toThrow(
|
|
|
|
|
/INSTALL_MODE=upgrade/,
|
|
|
|
|
);
|
2026-04-27 09:07:18 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects rescue when no install exists', () => {
|
2026-05-07 11:16:40 +02:00
|
|
|
expect(() => resolveInstallMode('rescue', freshDetection)).toThrow(
|
|
|
|
|
/INSTALL_MODE=rescue/,
|
|
|
|
|
);
|
2026-04-27 08:02:27 +02:00
|
|
|
});
|
|
|
|
|
});
|