fix(build): exclude test files from tsc production build
*.test.ts files have zod v3/v4 type incompatibilities that break `npm run build`. Exclude them from tsconfig so the service step can build successfully during install. Also includes: bastille exec→cmd rename, packages rw cache mount, agent-jails pg18-client + pi install, jail-schema subnet_base env override. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- Build: pass | Tests: pass — Tests 1530 passed (1530)
This commit is contained in:
parent
8d36d3af4b
commit
8ec9db68b9
7 changed files with 97 additions and 19 deletions
|
|
@ -69,11 +69,29 @@ const DOMAIN_KEY_ENV_MAP: Record<string, string[]> = {
|
|||
};
|
||||
|
||||
const EXTRA_PACKAGES: Record<string, string[]> = {
|
||||
'db-admin': ['postgresql17-client'],
|
||||
'db-admin': ['postgresql18-client'],
|
||||
'git-admin': [],
|
||||
coordinator: [],
|
||||
};
|
||||
|
||||
function ensurePiInstalled(jailName: string): void {
|
||||
const exists = bastille('cmd', jailName, '/bin/sh', '-lc', 'command -v pi');
|
||||
if (exists.ok) return;
|
||||
|
||||
logger.info({ jailName }, 'Installing pi inside agent jail');
|
||||
const install = bastille(
|
||||
'cmd',
|
||||
jailName,
|
||||
'npm',
|
||||
'install',
|
||||
'-g',
|
||||
'@mariozechner/pi-coding-agent',
|
||||
);
|
||||
if (!install.ok) {
|
||||
throw new Error(`pi install failed in ${jailName}: ${install.output}`);
|
||||
}
|
||||
}
|
||||
|
||||
function buildJailEnv(
|
||||
specialist: string,
|
||||
allEnv: Record<string, string | undefined>,
|
||||
|
|
@ -211,6 +229,8 @@ export async function run(args: string[]): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
ensurePiInstalled(jailName);
|
||||
|
||||
const chsh = bastille(
|
||||
'cmd',
|
||||
jailName,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export function loadPackageList(name: PackageListName): string[] {
|
|||
}
|
||||
|
||||
/**
|
||||
* Nullfs-mount the shared host pkg cache read-only into a jail.
|
||||
* Nullfs-mount the shared host pkg cache into a jail.
|
||||
* Idempotent — checks the jail fstab before adding the mount.
|
||||
* Silently skips if the cache dataset or bastille fstab don't exist yet.
|
||||
* Call this after jail creation and before any bastille pkg install.
|
||||
|
|
@ -40,15 +40,39 @@ export function loadPackageList(name: PackageListName): string[] {
|
|||
export function mountPkgCacheInJail(jailName: string): void {
|
||||
const fstabPath = `/usr/local/bastille/jails/${jailName}/fstab`;
|
||||
if (!fs.existsSync('/var/cache/pkg')) return;
|
||||
if (
|
||||
fs.existsSync(fstabPath) &&
|
||||
fs.readFileSync(fstabPath, 'utf-8').includes('/var/cache/pkg')
|
||||
) {
|
||||
return;
|
||||
|
||||
const desiredLine = `/var/cache/pkg /usr/local/bastille/jails/${jailName}/root/var/cache/pkg nullfs rw 0 0`;
|
||||
if (fs.existsSync(fstabPath)) {
|
||||
const existing = fs.readFileSync(fstabPath, 'utf-8');
|
||||
if (existing.includes(desiredLine)) return;
|
||||
|
||||
if (existing.includes('/var/cache/pkg') && existing.includes(' nullfs ro ')) {
|
||||
const updated = existing.replace(
|
||||
/^\/var\/cache\/pkg\s+.*?\/var\/cache\/pkg\s+nullfs\s+ro\s+0\s+0\s*$/gmu,
|
||||
desiredLine,
|
||||
);
|
||||
fs.writeFileSync(fstabPath, updated);
|
||||
try {
|
||||
execSync(`bastille umount -a ${jailName} /var/cache/pkg`, { stdio: 'ignore' });
|
||||
} catch {
|
||||
// ignore — may not be mounted yet
|
||||
}
|
||||
try {
|
||||
execSync(`bastille mount ${jailName} /var/cache/pkg /var/cache/pkg nullfs rw 0 0`, {
|
||||
stdio: 'ignore',
|
||||
});
|
||||
} catch {
|
||||
// mount failed — jail pkg installs will fall back to network downloads
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (existing.includes('/var/cache/pkg')) return;
|
||||
}
|
||||
|
||||
try {
|
||||
execSync(
|
||||
`bastille mount ${jailName} /var/cache/pkg /var/cache/pkg nullfs ro 0 0`,
|
||||
`bastille mount ${jailName} /var/cache/pkg /var/cache/pkg nullfs rw 0 0`,
|
||||
{
|
||||
stdio: 'ignore',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -75,12 +75,12 @@ describe('jailExecSync', () => {
|
|||
mockSpawnSync.mockReset();
|
||||
});
|
||||
|
||||
it('calls bastille exec with correct jail and command', () => {
|
||||
it('calls bastille cmd with correct jail and command', () => {
|
||||
mockSpawnSync.mockReturnValue({ status: 0, stdout: 'ok', stderr: '', pid: 1, signal: null, output: [] } as any);
|
||||
jailExecSync('my-jail', 'bastille', ['list']);
|
||||
expect(mockSpawnSync).toHaveBeenCalledWith(
|
||||
'bastille',
|
||||
['exec', 'my-jail', 'bastille', 'list'],
|
||||
['cmd', 'my-jail', 'bastille', 'list'],
|
||||
expect.objectContaining({ encoding: 'utf-8' }),
|
||||
);
|
||||
});
|
||||
|
|
@ -152,12 +152,12 @@ describe('jailExec', () => {
|
|||
expect(result.output).toBeNull();
|
||||
});
|
||||
|
||||
it('uses bastille exec as the command', async () => {
|
||||
it('uses bastille cmd as the command', async () => {
|
||||
mockSpawn.mockReturnValue(makeMockProc({ stdout: 'ok', exitCode: 0 }) as any);
|
||||
await jailExec({ jailName: 'my-jail', command: 'pi', prompt: 'hello' });
|
||||
expect(mockSpawn).toHaveBeenCalledWith(
|
||||
'bastille',
|
||||
expect.arrayContaining(['exec', 'my-jail']),
|
||||
expect.arrayContaining(['cmd', 'my-jail']),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* Instead of spawning on the host, this runner:
|
||||
* 1. Writes prompt + system context to a temp file inside the jail
|
||||
* 2. Runs `bastille exec <jail> pi ...` (or aider)
|
||||
* 2. Runs `bastille cmd <jail> pi ...` (or aider)
|
||||
* 3. Captures stdout/stderr and returns the result
|
||||
*
|
||||
* Session files are stored inside the jail (persisted via nullfs mounts).
|
||||
|
|
@ -98,14 +98,14 @@ function cleanupJailTemp(jailName: string, ...jailRelPaths: string[]): void {
|
|||
}
|
||||
}
|
||||
|
||||
// ── Bastille exec ──────────────────────────────────────────────────────────
|
||||
// ── Bastille cmd ───────────────────────────────────────────────────────────
|
||||
|
||||
export function jailExecSync(
|
||||
jailName: string,
|
||||
command: string,
|
||||
args: string[] = [],
|
||||
): { ok: boolean; output: string } {
|
||||
const result = spawnSync('bastille', ['exec', jailName, command, ...args], {
|
||||
const result = spawnSync('bastille', ['cmd', jailName, command, ...args], {
|
||||
encoding: 'utf-8',
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
|
@ -134,7 +134,7 @@ export async function jailExec(opts: JailExecOptions): Promise<JailExecResult> {
|
|||
const fullCommand = command === 'pi' ? 'pi' : 'aider';
|
||||
|
||||
const bastilleArgs = [
|
||||
'exec',
|
||||
'cmd',
|
||||
jailName,
|
||||
'/bin/sh',
|
||||
'-c',
|
||||
|
|
@ -182,7 +182,7 @@ export async function jailExec(opts: JailExecOptions): Promise<JailExecResult> {
|
|||
output,
|
||||
error:
|
||||
code !== 0
|
||||
? stderr.trim() || `bastille exec exited with code ${code}`
|
||||
? stderr.trim() || `bastille cmd exited with code ${code}`
|
||||
: null,
|
||||
exitCode: code,
|
||||
tokensUsed: Math.max(1, Math.ceil((output?.length || 0) / 4)),
|
||||
|
|
|
|||
|
|
@ -194,6 +194,22 @@ describe('resolveJailIp', () => {
|
|||
expect(resolveJailIp(r, 'web')).toBe('10.0.1.7');
|
||||
});
|
||||
|
||||
it('prefers WARDEN_SUBNET_BASE/AGENT_SUBNET_BASE when set', () => {
|
||||
const priorWardenSubnetBase = process.env.WARDEN_SUBNET_BASE;
|
||||
const priorAgentSubnetBase = process.env.AGENT_SUBNET_BASE;
|
||||
process.env.WARDEN_SUBNET_BASE = '10.9.9';
|
||||
delete process.env.AGENT_SUBNET_BASE;
|
||||
try {
|
||||
const r = loadJailRegistry(fixtureYaml);
|
||||
expect(resolveJailIp(r, 'db')).toBe('10.9.9.3');
|
||||
} finally {
|
||||
if (priorWardenSubnetBase) process.env.WARDEN_SUBNET_BASE = priorWardenSubnetBase;
|
||||
else delete process.env.WARDEN_SUBNET_BASE;
|
||||
if (priorAgentSubnetBase) process.env.AGENT_SUBNET_BASE = priorAgentSubnetBase;
|
||||
else delete process.env.AGENT_SUBNET_BASE;
|
||||
}
|
||||
});
|
||||
|
||||
it('throws for unknown jail role', () => {
|
||||
const r = loadJailRegistry(fixtureYaml);
|
||||
expect(() => resolveJailIp(r, 'no_such_jail')).toThrow('Unknown jail role');
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { z } from 'zod';
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { readEnvFile } from './env.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
|
@ -145,6 +146,19 @@ export function loadJailRegistry(registryPath?: string): JailRegistry {
|
|||
const registry = JailRegistrySchema.parse(parsed);
|
||||
|
||||
if (!registryPath) {
|
||||
const env = readEnvFile([
|
||||
'AGENT_SUBNET_BASE',
|
||||
'WARDEN_SUBNET_BASE',
|
||||
'WARDEN_GATEWAY',
|
||||
'WARDEN_BRIDGE',
|
||||
'WARDEN_RELEASE',
|
||||
]);
|
||||
registry.subnet_base =
|
||||
env.WARDEN_SUBNET_BASE || env.AGENT_SUBNET_BASE || registry.subnet_base;
|
||||
registry.gateway = env.WARDEN_GATEWAY || registry.gateway;
|
||||
registry.bridge = env.WARDEN_BRIDGE || registry.bridge;
|
||||
registry.release = env.WARDEN_RELEASE || registry.release;
|
||||
|
||||
cachedRegistry = registry;
|
||||
}
|
||||
return registry;
|
||||
|
|
@ -153,7 +167,11 @@ export function loadJailRegistry(registryPath?: string): JailRegistry {
|
|||
export function resolveJailIp(registry: JailRegistry, role: string): string {
|
||||
const jail = registry.jails[role];
|
||||
if (!jail) throw new Error(`Unknown jail role: ${role}`);
|
||||
return `${registry.subnet_base}.${jail.ip_suffix}`;
|
||||
const subnetBase =
|
||||
process.env.WARDEN_SUBNET_BASE ||
|
||||
process.env.AGENT_SUBNET_BASE ||
|
||||
registry.subnet_base;
|
||||
return `${subnetBase}.${jail.ip_suffix}`;
|
||||
}
|
||||
|
||||
export function resolveAgentJailName(
|
||||
|
|
|
|||
|
|
@ -16,5 +16,5 @@
|
|||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue