From 537c6139d455bdaf10a5cc7d2effecea1b825db2 Mon Sep 17 00:00:00 2001 From: Operator & Codex Date: Sun, 10 May 2026 18:14:57 +0200 Subject: [PATCH] Make hostd jail destroy non-interactive (Sam & Claude) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Feed bastille destroy confirmation through hostd so scripts can switch from sudo pipelines to hostd-call.sh safely. --- Build: pass Tests: pass — 90 passed (1 file) --- Build: pass | Tests: pass — 2372 passed (704 files) --- src/hostd/privileged-commands.test.ts | 10 ++++++++++ src/hostd/privileged-commands.ts | 5 +++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/hostd/privileged-commands.test.ts b/src/hostd/privileged-commands.test.ts index 5bf6250..7e70ea0 100644 --- a/src/hostd/privileged-commands.test.ts +++ b/src/hostd/privileged-commands.test.ts @@ -53,6 +53,7 @@ describe('handleOp — dispatch', () => { 'bastille-stop', 'bastille-restart', 'bastille-list', + 'bastille-destroy', 'bastille-pkg-install', 'bastille-mount-pkg-cache', 'zfs-snapshot', @@ -292,6 +293,15 @@ describe('handleOp — spawnSync args (Bastille)', () => { ); }); + it('bastille-destroy confirms the interactive prompt non-interactively', () => { + handleOp('bastille-destroy', { jail: 'clawdie-old' }); + expect(mockSpawnSync).toHaveBeenCalledWith( + 'bastille', + ['destroy', 'clawdie-old'], + expect.objectContaining({ input: 'y\n' }), + ); + }); + it('bastille-pkg-install: splits packages into args', () => { handleOp('bastille-pkg-install', { jail: 'clawdie-worker', diff --git a/src/hostd/privileged-commands.ts b/src/hostd/privileged-commands.ts index 7951812..ac19f5c 100644 --- a/src/hostd/privileged-commands.ts +++ b/src/hostd/privileged-commands.ts @@ -70,12 +70,13 @@ export interface OpResult { function exec( cmd: string, args: string[], - opts: { timeoutMs?: number } = {}, + opts: { timeoutMs?: number; input?: string } = {}, ): OpResult { const result = spawnSync(cmd, args, { encoding: 'utf-8', env: process.env, ...(opts.timeoutMs ? { timeout: opts.timeoutMs } : {}), + ...(opts.input !== undefined ? { input: opts.input } : {}), }); const output = [result.stdout || '', result.stderr || ''] .filter(Boolean) @@ -248,7 +249,7 @@ const OPS: Record = { }, 'bastille-destroy': { schema: z.object({ jail: jailName }), - handler: (p) => exec('bastille', ['destroy', String(p.jail)]), + handler: (p) => exec('bastille', ['destroy', String(p.jail)], { input: 'y\n' }), }, // ── ZFS ──────────────────────────────────────────────────────────────────