Expand Warden runtime model

This commit is contained in:
Clawdie 2026-03-08 11:00:52 +01:00
parent 4dec267545
commit 3375dd2c56
5 changed files with 120 additions and 14 deletions

View file

@ -185,5 +185,23 @@ The intended split is:
- `clawdie-cp`: thick, shared networking, persistent
- worker jails: thin, shared networking, ephemeral
- networked workers: thin, VNET, ephemeral
- browser automation desktop: Linux VM, image-backed, outside Bastille jail provisioning
That policy is defined in code so provisioning decisions are explicit before Bastille automation is added.
## Bastille Template Mapping
For Warden, Bastille templates map cleanly to only part of the future runtime matrix:
- `default/thick``controlPlane`
- `default/thin``worker`
- `default/thin` + `default/vnet``networkedWorker`
- `default/clone` → future optimization for fast worker spawning from a prepared base
The future `browserVm` profile is different:
- not a Bastille jail
- not a FreeBSD jail template problem
- should be implemented as a `bhyve` Linux VM with snapshot or image-based lifecycle
So no extra `bastille.conf` changes are needed now to prepare for the browser VM path.

View file

@ -121,3 +121,10 @@ Use:
- `bhyve` Linux VM for GUI/browser tasks
Do not force the browser/desktop use case into the plain jail runtime path.
In Warden profile terms, that future executor is:
- runtime: `linux-vm`
- role: `browser-vm`
- provisioning: `image`
- networking: `vm-bridged` by default

View file

@ -20,11 +20,12 @@ In practice:
Warden uses distinct jail profiles so the control-plane and worker layers do not collapse into one shape.
| Profile | Role | Provisioning | Networking | Persistent | Use |
| ------- | ---- | ------------ | ---------- | ---------- | --- |
| `controlPlane` | `control-plane` | `thick` | `shared` | yes | Main Clawdie control-plane jail |
| `worker` | `worker` | `thin` | `shared` | no | Default short-lived worker jail |
| `networkedWorker` | `networked-worker` | `thin` | `vnet` | no | Future worker needing its own network stack |
| Profile | Runtime | Role | Provisioning | Networking | Persistent | Use |
| ------- | ------- | ---- | ------------ | ---------- | ---------- | --- |
| `controlPlane` | `freebsd-jail` | `control-plane` | `thick` | `shared` | yes | Main Clawdie control-plane jail |
| `worker` | `freebsd-jail` | `worker` | `thin` | `shared` | no | Default short-lived worker jail |
| `networkedWorker` | `freebsd-jail` | `networked-worker` | `thin` | `vnet` | no | Future worker needing its own network stack |
| `browserVm` | `linux-vm` | `browser-vm` | `image` | `vm-bridged` | yes | Future Linux desktop/browser executor |
This policy is now encoded in `src/jail-config.ts`.
@ -57,6 +58,6 @@ Do not use `Warden` to rename:
The intended model is:
- **Warden jail runtime** for PI, coding, CLI work, and low-overhead task execution
- optional **Warden browser VM** later via `bhyve` for Linux desktop and browser automation workloads
- **Warden browser VM** later via `bhyve` for Linux desktop and browser automation workloads
That keeps the lightweight path lightweight while still leaving room for a heavier GUI executor later.
The browser VM is intentionally not modeled as a jail option. It is a separate runtime class.

View file

@ -26,6 +26,7 @@ describe('Jail Config', () => {
});
it('should default control-plane profile to thick shared persistent', () => {
expect(DEFAULT_JAIL_CONFIG.runtime).toBe('freebsd-jail');
expect(DEFAULT_JAIL_CONFIG.role).toBe('control-plane');
expect(DEFAULT_JAIL_CONFIG.provisioning).toBe('thick');
expect(DEFAULT_JAIL_CONFIG.networking).toBe('shared');
@ -68,6 +69,7 @@ describe('Jail Config', () => {
it('should include Warden profile comments', () => {
const config = generateJailConfig();
expect(config).toContain('# Warden runtime: freebsd-jail');
expect(config).toContain('# Warden role: control-plane');
expect(config).toContain('# Provisioning: thick');
expect(config).toContain('# Networking: shared');
@ -212,27 +214,47 @@ describe('Jail Config', () => {
expect(errors).toContain('Invalid jail networking mode');
});
it('should fail for invalid runtime kind', () => {
const errors = validateJailConfig({
...DEFAULT_JAIL_CONFIG,
runtime: 'invalid' as JailConfig['runtime'],
});
expect(errors).toContain('Invalid Warden runtime kind');
});
});
describe('WARDEN_JAIL_PROFILES', () => {
it('should define control-plane as thick and shared', () => {
expect(WARDEN_JAIL_PROFILES.controlPlane.runtime).toBe('freebsd-jail');
expect(WARDEN_JAIL_PROFILES.controlPlane.provisioning).toBe('thick');
expect(WARDEN_JAIL_PROFILES.controlPlane.networking).toBe('shared');
expect(WARDEN_JAIL_PROFILES.controlPlane.persistent).toBe(true);
});
it('should define worker as thin and shared', () => {
expect(WARDEN_JAIL_PROFILES.worker.runtime).toBe('freebsd-jail');
expect(WARDEN_JAIL_PROFILES.worker.provisioning).toBe('thin');
expect(WARDEN_JAIL_PROFILES.worker.networking).toBe('shared');
expect(WARDEN_JAIL_PROFILES.worker.persistent).toBe(false);
});
it('should define networked worker as thin and vnet', () => {
expect(WARDEN_JAIL_PROFILES.networkedWorker.runtime).toBe('freebsd-jail');
expect(WARDEN_JAIL_PROFILES.networkedWorker.provisioning).toBe('thin');
expect(WARDEN_JAIL_PROFILES.networkedWorker.networking).toBe('vnet');
expect(WARDEN_JAIL_PROFILES.networkedWorker.persistent).toBe(false);
});
it('should define browser VM as image-backed linux vm', () => {
expect(WARDEN_JAIL_PROFILES.browserVm.runtime).toBe('linux-vm');
expect(WARDEN_JAIL_PROFILES.browserVm.role).toBe('browser-vm');
expect(WARDEN_JAIL_PROFILES.browserVm.provisioning).toBe('image');
expect(WARDEN_JAIL_PROFILES.browserVm.networking).toBe('vm-bridged');
expect(WARDEN_JAIL_PROFILES.browserVm.persistent).toBe(true);
});
it('should build worker config from profile', () => {
const config = getDefaultJailConfigForProfile('worker');
@ -250,5 +272,16 @@ describe('Jail Config', () => {
expect(config.networking).toBe('vnet');
expect(config.persistent).toBe(false);
});
it('should build browser VM config from profile', () => {
const config = getDefaultJailConfigForProfile('browserVm');
expect(config.name).toBe('warden-browser-vm');
expect(config.runtime).toBe('linux-vm');
expect(config.provisioning).toBe('image');
expect(config.networking).toBe('vm-bridged');
expect(config.persistent).toBe(true);
expect(config.path).toBe('/vm/warden-browser-vm');
});
});
});

View file

@ -3,6 +3,7 @@ export interface JailConfig {
hostname: string;
ip: string;
path: string;
runtime: WardenRuntimeKind;
role: JailRole;
provisioning: JailProvisioning;
networking: JailNetworking;
@ -12,11 +13,17 @@ export interface JailConfig {
resources: ResourceLimits;
}
export type JailRole = 'control-plane' | 'worker' | 'networked-worker';
export type JailProvisioning = 'thick' | 'thin';
export type JailNetworking = 'shared' | 'vnet';
export type WardenRuntimeKind = 'freebsd-jail' | 'linux-vm';
export type JailRole =
| 'control-plane'
| 'worker'
| 'networked-worker'
| 'browser-vm';
export type JailProvisioning = 'thick' | 'thin' | 'clone' | 'image';
export type JailNetworking = 'shared' | 'vnet' | 'vm-bridged' | 'vm-nat';
export interface JailProfile {
runtime: WardenRuntimeKind;
role: JailRole;
provisioning: JailProvisioning;
networking: JailNetworking;
@ -25,10 +32,11 @@ export interface JailProfile {
}
export const WARDEN_JAIL_PROFILES: Record<
'controlPlane' | 'worker' | 'networkedWorker',
'controlPlane' | 'worker' | 'networkedWorker' | 'browserVm',
JailProfile
> = {
controlPlane: {
runtime: 'freebsd-jail',
role: 'control-plane',
provisioning: 'thick',
networking: 'shared',
@ -36,6 +44,7 @@ export const WARDEN_JAIL_PROFILES: Record<
baseRelease: '15.0-RELEASE',
},
worker: {
runtime: 'freebsd-jail',
role: 'worker',
provisioning: 'thin',
networking: 'shared',
@ -43,12 +52,21 @@ export const WARDEN_JAIL_PROFILES: Record<
baseRelease: '15.0-RELEASE',
},
networkedWorker: {
runtime: 'freebsd-jail',
role: 'networked-worker',
provisioning: 'thin',
networking: 'vnet',
persistent: false,
baseRelease: '15.0-RELEASE',
},
browserVm: {
runtime: 'linux-vm',
role: 'browser-vm',
provisioning: 'image',
networking: 'vm-bridged',
persistent: true,
baseRelease: 'linux-desktop-image',
},
};
export interface MountPoint {
@ -69,6 +87,7 @@ export const DEFAULT_JAIL_CONFIG: JailConfig = {
hostname: 'clawdie-cp.clawdie.si',
ip: '192.168.1.100',
path: '/jails/clawdie-cp',
runtime: WARDEN_JAIL_PROFILES.controlPlane.runtime,
role: WARDEN_JAIL_PROFILES.controlPlane.role,
provisioning: WARDEN_JAIL_PROFILES.controlPlane.provisioning,
networking: WARDEN_JAIL_PROFILES.controlPlane.networking,
@ -110,6 +129,7 @@ export function generateJailConfig(config: Partial<JailConfig> = {}): string {
const lines: string[] = [
`${finalConfig.name} {`,
` # Warden runtime: ${finalConfig.runtime}`,
` # Warden role: ${finalConfig.role}`,
` # Provisioning: ${finalConfig.provisioning}`,
` # Networking: ${finalConfig.networking}`,
@ -183,18 +203,26 @@ export function validateJailConfig(config: JailConfig): string[] {
errors.push('Jail path must be absolute');
}
if (!['control-plane', 'worker', 'networked-worker'].includes(config.role)) {
if (
!['control-plane', 'worker', 'networked-worker', 'browser-vm'].includes(
config.role,
)
) {
errors.push('Invalid jail role');
}
if (!['thick', 'thin'].includes(config.provisioning)) {
if (!['thick', 'thin', 'clone', 'image'].includes(config.provisioning)) {
errors.push('Invalid jail provisioning mode');
}
if (!['shared', 'vnet'].includes(config.networking)) {
if (!['shared', 'vnet', 'vm-bridged', 'vm-nat'].includes(config.networking)) {
errors.push('Invalid jail networking mode');
}
if (!['freebsd-jail', 'linux-vm'].includes(config.runtime)) {
errors.push('Invalid Warden runtime kind');
}
config.mountpoints.forEach((mp, index) => {
if (!mp.source || !mp.destination) {
errors.push(`Mountpoint ${index} missing source or destination`);
@ -217,12 +245,31 @@ export function getDefaultJailConfigForProfile(
return { ...DEFAULT_JAIL_CONFIG };
}
if (profileName === 'browserVm') {
return {
...DEFAULT_JAIL_CONFIG,
name: 'warden-browser-vm',
hostname: 'warden-browser-vm.clawdie.si',
path: '/vm/warden-browser-vm',
runtime: profile.runtime,
role: profile.role,
provisioning: profile.provisioning,
networking: profile.networking,
persistent: profile.persistent,
environment: {
...DEFAULT_JAIL_CONFIG.environment,
JAIL_NAME: 'warden-browser-vm',
},
};
}
const suffix = profileName === 'worker' ? 'worker' : 'networked-worker';
return {
...DEFAULT_JAIL_CONFIG,
name: `warden-${suffix}`,
hostname: `warden-${suffix}.clawdie.si`,
path: `/jails/warden-${suffix}`,
runtime: profile.runtime,
role: profile.role,
provisioning: profile.provisioning,
networking: profile.networking,