Introduces infra/jails.yaml as single source of truth for jail definitions,
src/jail-schema.ts with Zod validation, src/jail-registry.ts for runtime,
setup/bastille-helpers.ts as shared module replacing 7 copy-pasted
bastille()/jailExists()/detectFreeBSDRelease() wrappers.
Refactors setup/{db,cms,git,forgejo,jails,llama-cpp,ollama,skills-memory}.ts
to import from bastille-helpers. Archives infra/ansible/ to .archive/.
Net reduction: ~300 lines of duplicated code. All IPs now derive
from jails.yaml with env var overrides preserved.
Build: pass | Tests: not run (Linux)
155 lines
4.1 KiB
TypeScript
155 lines
4.1 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_NAME, 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 safeAgentName = AGENT_NAME.replace(/[-_]/g, '');
|
|
const preferredJailName = `${safeAgentName}worker`;
|
|
const legacyHyphenName = `${AGENT_NAME}-worker`;
|
|
const legacyPlainName = 'worker';
|
|
let workerJail = preferredJailName;
|
|
if (jailExists(legacyHyphenName) && !jailExists(preferredJailName)) {
|
|
workerJail = legacyHyphenName;
|
|
} else if (jailExists(legacyPlainName) && !jailExists(preferredJailName)) {
|
|
workerJail = legacyPlainName;
|
|
}
|
|
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',
|
|
'-T',
|
|
'-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_NAME}.home.arpa`,
|
|
);
|
|
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;
|
|
}
|
|
}
|