clawdie-ai/setup/packages.ts

142 lines
4.2 KiB
TypeScript
Raw Normal View History

import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { readEnvFile } from '../src/env.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const PACKAGE_DIR = path.resolve(__dirname, '../infra/packages');
export type PackageListName =
| 'host-baseline.txt'
| 'worker-jail.txt'
| 'agent-worker-jail.txt'
| 'git-jail.txt'
| 'forgejo-jail.txt'
| 'cms-jail.txt'
| 'db-jail.txt'
| 'browser-jail.txt'
| 'ollama-jail.txt'
| 'llama-cpp-jail.txt'
| 'management-jail.txt'
| 'cnc-jail.txt';
export function loadPackageList(name: PackageListName): string[] {
const file = path.join(PACKAGE_DIR, name);
const content = fs.readFileSync(file, 'utf-8');
return content
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0 && !line.startsWith('#'));
}
/**
* 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.
*/
export function mountPkgCacheInJail(jailName: string): void {
const fstabPath = `/usr/local/bastille/jails/${jailName}/fstab`;
if (!fs.existsSync('/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 rw 0 0`,
{
stdio: 'ignore',
},
);
} catch {
// mount failed — jail pkg installs will fall back to network downloads
}
}
/**
* Remove the pkg-cache nullfs mount from a jail and clean any matching fstab
* entry. Safe to call even if the mount was never added.
*/
export function unmountPkgCacheInJail(jailName: string): void {
const fstabPath = `/usr/local/bastille/jails/${jailName}/fstab`;
try {
execSync(`bastille umount ${jailName} /var/cache/pkg`, { stdio: 'ignore' });
} catch {
// ignore — may already be unmounted or absent
}
if (!fs.existsSync(fstabPath)) return;
const existing = fs.readFileSync(fstabPath, 'utf-8');
const updated = existing.replace(
/^\/var\/cache\/pkg\s+.*?\/var\/cache\/pkg\s+nullfs\s+(?:ro|rw)\s+0\s+0\s*$/gmu,
'',
);
if (updated !== existing) {
fs.writeFileSync(fstabPath, updated.replace(/\n{3,}/g, '\n\n'));
}
}
/**
* Load and deduplicate all package lists (host + all jail roles).
* This is the authoritative set for prefetching to the shared pkg cache.
*/
export function loadAllPackageLists(): string[] {
const envValues = readEnvFile(['FEATURE_TAILSCALE']);
const tailscaleEnabled = /^(YES|yes|true|TRUE|1)$/u.test(
process.env.FEATURE_TAILSCALE || envValues.FEATURE_TAILSCALE || '',
);
const all = (
[
'host-baseline.txt',
'worker-jail.txt',
'agent-worker-jail.txt',
'git-jail.txt',
'forgejo-jail.txt',
'cms-jail.txt',
'db-jail.txt',
'browser-jail.txt',
'ollama-jail.txt',
'llama-cpp-jail.txt',
'management-jail.txt',
'cnc-jail.txt',
] as PackageListName[]
).flatMap(loadPackageList);
if (tailscaleEnabled) {
all.push('tailscale');
}
return [...new Set(all)];
}