clawdie-ai/setup/jails.ts
Operator & Codex f1dc7ea6df Drop stale jail and agent migration paths (Codex)
Remove completed controlplane agent-id migration, simplify jail-name resolution to current canonical names, and drop SUDO_UID ownership fallback from service setup.

---
Build: pass | Tests: pass — 2370 passed (704 files)
2026-05-10 21:30:17 +02:00

146 lines
3.8 KiB
TypeScript

/**
* setup/jails.ts — Provision the worker jail(s).
*
* Current main uses a persistent worker jail as a baseline sandbox. The agent
* runtime may still execute on the host depending on profile, but creating the
* worker jail keeps the FreeBSD jail layout consistent and ready.
*/
import { execSync, spawnSync } from 'child_process';
import { AGENT_INTERNAL_DOMAIN, SUBNET_BASE } from '../src/config.js';
import { logger } from '../src/logger.js';
import { loadPackageList, mountPkgCacheInJail } from './packages.js';
import { commandExists, getPlatform, isRoot } from './platform.js';
import { emitStatus } from './status.js';
import { maybeEnableTailscaleInJail } from './tailscale.js';
import {
bastille,
jailExists,
detectFreeBSDRelease,
} from './bastille-helpers.js';
const LOG = 'logs/setup.log';
export async function run(args: string[]): Promise<void> {
if (getPlatform() !== 'freebsd') {
emitStatus('SETUP_JAILS', {
STATUS: 'failed',
ERROR: 'unsupported_platform',
LOG,
});
process.exit(1);
}
if (!isRoot()) {
emitStatus('SETUP_JAILS', {
STATUS: 'failed',
ERROR: 'requires_root',
LOG,
});
throw new Error('setup_jails_requires_root');
}
if (!commandExists('bastille')) {
emitStatus('SETUP_JAILS', {
STATUS: 'failed',
ERROR: 'missing_bastille',
LOG,
});
throw new Error('missing_bastille');
}
const doCreate =
args.includes('--create') || args.includes('--create-worker');
if (!doCreate) {
emitStatus('SETUP_JAILS', {
STATUS: 'skipped',
REASON: 'missing_flag',
LOG,
});
logger.info('Jails step skipped — pass --create');
return;
}
const workerJail = 'worker';
const workerIp =
process.env.WORKER_JAIL_IP_START ||
process.env.WORKER_JAIL_IP ||
`${SUBNET_BASE}.101`;
const gateway = process.env.WARDEN_GATEWAY || `${SUBNET_BASE}.1`;
const bridge = process.env.WARDEN_BRIDGE || 'warden0';
const release = detectFreeBSDRelease();
try {
if (!jailExists(workerJail)) {
logger.info(
{ jail: workerJail, ip: workerIp, release },
'Creating worker jail',
);
const create = bastille(
'create',
// thin jail (no -T): worker jails do not need a copied base userland.
'-B',
'-g',
gateway,
workerJail,
release,
`${workerIp}/24`,
bridge,
);
if (!create.ok) {
throw new Error(`bastille create failed: ${create.output}`);
}
bastille(
'config',
workerJail,
'set',
'host.hostname',
`worker.${AGENT_INTERNAL_DOMAIN}`,
);
bastille('restart', workerJail);
} else {
logger.info(
{ jail: workerJail },
'Worker jail already exists, skipping creation',
);
}
mountPkgCacheInJail(workerJail);
const pkgs = loadPackageList('worker-jail.txt');
const pkg = bastille('pkg', workerJail, 'install', '-y', ...pkgs);
if (!pkg.ok) {
logger.warn(
{ output: pkg.output },
'Worker jail package install had warnings',
);
}
const chsh = bastille(
'cmd',
workerJail,
'chsh',
'-s',
'/usr/local/bin/bash',
'root',
);
if (!chsh.ok) {
logger.warn(
{ output: chsh.output },
'chsh to bash failed in worker jail',
);
}
const runBastille = (args: string[]) => bastille(...args);
maybeEnableTailscaleInJail(runBastille, workerJail, workerJail);
emitStatus('SETUP_JAILS', {
STATUS: 'success',
WORKER_JAIL_NAME: workerJail,
WORKER_JAIL_IP: workerIp,
LOG,
});
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
emitStatus('SETUP_JAILS', { STATUS: 'failed', ERROR: message, LOG });
throw err;
}
}