From 3375dd2c5677e5445c8b0cb4153d04464d5367aa Mon Sep 17 00:00:00 2001 From: Clawdie Date: Sun, 8 Mar 2026 11:00:52 +0100 Subject: [PATCH] Expand Warden runtime model --- docs/BASTILLE.md | 18 ++++++++++++ docs/JAIL-NETWORKING.md | 7 +++++ docs/WARDEN.md | 15 +++++----- src/jail-config.test.ts | 33 ++++++++++++++++++++++ src/jail-config.ts | 61 ++++++++++++++++++++++++++++++++++++----- 5 files changed, 120 insertions(+), 14 deletions(-) diff --git a/docs/BASTILLE.md b/docs/BASTILLE.md index 72fb8c1..8f683ef 100644 --- a/docs/BASTILLE.md +++ b/docs/BASTILLE.md @@ -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. diff --git a/docs/JAIL-NETWORKING.md b/docs/JAIL-NETWORKING.md index 50752c5..d968734 100644 --- a/docs/JAIL-NETWORKING.md +++ b/docs/JAIL-NETWORKING.md @@ -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 diff --git a/docs/WARDEN.md b/docs/WARDEN.md index 137ee96..bd70155 100644 --- a/docs/WARDEN.md +++ b/docs/WARDEN.md @@ -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. diff --git a/src/jail-config.test.ts b/src/jail-config.test.ts index a479269..2b33a2f 100644 --- a/src/jail-config.test.ts +++ b/src/jail-config.test.ts @@ -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'); + }); }); }); diff --git a/src/jail-config.ts b/src/jail-config.ts index 1aabe9a..38473e1 100644 --- a/src/jail-config.ts +++ b/src/jail-config.ts @@ -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 = {}): 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,