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)
213 lines
6.2 KiB
TypeScript
213 lines
6.2 KiB
TypeScript
import { SERVICE_NAME } from '../src/platform-identity.js';
|
|
import { execFileSync } from 'child_process';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
import {
|
|
ZFS_PREFIX,
|
|
} from '../src/config.js';
|
|
import type { FirstBootInstallMode } from './first-boot.js';
|
|
import { resolvePlatformRootDataset } from './install-identity.js';
|
|
import { commandExists } from './platform.js';
|
|
import { loadSystemEnv } from './system-env.js';
|
|
import { getZfsMetadata } from './zfs-metadata.js';
|
|
|
|
export interface ExistingInstallSignals {
|
|
envFile: boolean;
|
|
groupsDir: boolean;
|
|
serviceFile: boolean;
|
|
zfsDataset: boolean;
|
|
zfsMetadata: boolean;
|
|
runtimeUser: boolean;
|
|
}
|
|
|
|
export interface ExistingInstallDetection {
|
|
signals: ExistingInstallSignals;
|
|
strongSignals: string[];
|
|
softSignals: string[];
|
|
existing: boolean;
|
|
rootDataset: string | null;
|
|
installUuid: string | null;
|
|
}
|
|
|
|
export interface ResolvedInstallMode {
|
|
requested: FirstBootInstallMode;
|
|
effective: 'fresh' | 'upgrade' | 'rescue';
|
|
detection: ExistingInstallDetection;
|
|
}
|
|
|
|
interface DetectorDeps {
|
|
existsSync: (filePath: string) => boolean;
|
|
readdirSync: (filePath: string) => string[];
|
|
execFileSync: typeof execFileSync;
|
|
commandExists: (name: string) => boolean;
|
|
}
|
|
|
|
const DEFAULT_DEPS: DetectorDeps = {
|
|
existsSync: fs.existsSync,
|
|
readdirSync: (filePath) => fs.readdirSync(filePath),
|
|
execFileSync,
|
|
commandExists,
|
|
};
|
|
|
|
function hasProjectEnv(projectRoot: string, deps: DetectorDeps): boolean {
|
|
const envFile = path.join(projectRoot, '.env');
|
|
if (!deps.existsSync(envFile)) return false;
|
|
try {
|
|
const content = fs.readFileSync(envFile, 'utf-8');
|
|
return content
|
|
.split('\n')
|
|
.map((line) => line.trim())
|
|
.some((line) => line && !line.startsWith('#'));
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function hasNonEmptyGroupsDir(projectRoot: string, deps: DetectorDeps): boolean {
|
|
const groupsDir = path.join(projectRoot, 'groups');
|
|
if (!deps.existsSync(groupsDir)) return false;
|
|
try {
|
|
return deps.readdirSync(groupsDir).some((entry) => !entry.startsWith('.'));
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function hasInstalledService(deps: DetectorDeps): boolean {
|
|
return deps.existsSync(`/usr/local/etc/rc.d/${SERVICE_NAME}`);
|
|
}
|
|
|
|
function hasRuntimeUser(deps: DetectorDeps): boolean {
|
|
try {
|
|
deps.execFileSync('id', ['-u', SERVICE_NAME], {
|
|
stdio: ['ignore', 'ignore', 'ignore'],
|
|
});
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function hasZfsDataset(projectRoot: string, deps: DetectorDeps): boolean {
|
|
if (!deps.commandExists('zfs')) return false;
|
|
try {
|
|
const systemEnv = loadSystemEnv(projectRoot, deps);
|
|
const zfsPrefix = systemEnv.zfsPrefix || ZFS_PREFIX;
|
|
const exactDataset =
|
|
systemEnv.zfsPool && zfsPrefix
|
|
? `${systemEnv.zfsPool}/${zfsPrefix}`
|
|
: null;
|
|
const output = deps.execFileSync('zfs', ['list', '-H', '-o', 'name'], {
|
|
encoding: 'utf-8',
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
});
|
|
const datasets = output
|
|
.split('\n')
|
|
.map((line) => line.trim())
|
|
.filter(Boolean);
|
|
return datasets.some(
|
|
(name) =>
|
|
(exactDataset
|
|
? name === exactDataset ||
|
|
name.startsWith(`${exactDataset}/`) ||
|
|
name.endsWith(`${exactDataset}@`)
|
|
: false) ||
|
|
name.endsWith(`/${zfsPrefix}`) ||
|
|
name === zfsPrefix ||
|
|
name.includes(`/${zfsPrefix}/`) ||
|
|
name.endsWith(`/${zfsPrefix}/pgdata`),
|
|
);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function getZfsInstallUuid(
|
|
projectRoot: string,
|
|
deps: DetectorDeps,
|
|
): { rootDataset: string | null; installUuid: string | null } {
|
|
if (!deps.commandExists('zfs')) {
|
|
return { rootDataset: null, installUuid: null };
|
|
}
|
|
try {
|
|
const root = resolvePlatformRootDataset(projectRoot, deps);
|
|
const metadata = getZfsMetadata(root.dataset, ['install-uuid'], deps);
|
|
return {
|
|
rootDataset: root.dataset,
|
|
installUuid: metadata['install-uuid'],
|
|
};
|
|
} catch {
|
|
return { rootDataset: null, installUuid: null };
|
|
}
|
|
}
|
|
|
|
export function detectExistingInstall(
|
|
projectRoot: string,
|
|
deps: Partial<DetectorDeps> = {},
|
|
): ExistingInstallDetection {
|
|
const resolvedDeps = { ...DEFAULT_DEPS, ...deps };
|
|
const signals: ExistingInstallSignals = {
|
|
envFile: hasProjectEnv(projectRoot, resolvedDeps),
|
|
groupsDir: hasNonEmptyGroupsDir(projectRoot, resolvedDeps),
|
|
serviceFile: hasInstalledService(resolvedDeps),
|
|
zfsDataset: hasZfsDataset(projectRoot, resolvedDeps),
|
|
zfsMetadata: false,
|
|
runtimeUser: hasRuntimeUser(resolvedDeps),
|
|
};
|
|
const zfsIdentity = getZfsInstallUuid(projectRoot, resolvedDeps);
|
|
signals.zfsMetadata = Boolean(zfsIdentity.installUuid);
|
|
|
|
const strongSignals: string[] = [];
|
|
const softSignals: string[] = [];
|
|
|
|
if (signals.serviceFile) strongSignals.push('service');
|
|
if (signals.zfsDataset) strongSignals.push('zfs');
|
|
if (signals.zfsMetadata) strongSignals.push('zfs-metadata');
|
|
if (signals.runtimeUser) strongSignals.push('runtime-user');
|
|
if (signals.envFile) softSignals.push('env');
|
|
if (signals.groupsDir) softSignals.push('groups');
|
|
|
|
const existing = strongSignals.length > 0 || softSignals.length >= 2;
|
|
|
|
return {
|
|
signals,
|
|
strongSignals,
|
|
softSignals,
|
|
existing,
|
|
rootDataset: zfsIdentity.rootDataset,
|
|
installUuid: zfsIdentity.installUuid,
|
|
};
|
|
}
|
|
|
|
export function resolveInstallMode(
|
|
requested: FirstBootInstallMode,
|
|
detection: ExistingInstallDetection,
|
|
): ResolvedInstallMode {
|
|
if (requested === 'auto') {
|
|
return {
|
|
requested,
|
|
effective: detection.existing ? 'upgrade' : 'fresh',
|
|
detection,
|
|
};
|
|
}
|
|
|
|
if (requested === 'fresh' && detection.existing) {
|
|
const found = [...detection.strongSignals, ...detection.softSignals].join(', ');
|
|
throw new Error(
|
|
`INSTALL_MODE=fresh refused: existing install detected (${found || 'signals present'}). Use INSTALL_MODE=upgrade or INSTALL_MODE=rescue, or remove the existing installation first.`,
|
|
);
|
|
}
|
|
|
|
if ((requested === 'upgrade' || requested === 'rescue') && !detection.existing) {
|
|
throw new Error(
|
|
`INSTALL_MODE=${requested} refused: no existing install detected. Use INSTALL_MODE=auto or INSTALL_MODE=fresh for a new machine.`,
|
|
);
|
|
}
|
|
|
|
return {
|
|
requested,
|
|
effective: requested,
|
|
detection,
|
|
};
|
|
}
|