clawdie-ai/setup/install-mode.ts
Operator & Claude Code 75009dcb7f 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

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,
};
}