refactor: extract runtime-specific code into src/container-runtime.ts (#321)

Move all container-runtime-specific logic (binary name, mount args,
stop command, startup check, orphan cleanup) into a single file so
swapping runtimes only requires replacing this one file.

Neutralize "Apple Container" references in comments and docs that
would become incorrect after a runtime swap. References that list
both runtimes as options are left unchanged.

No behavior change — Apple Container remains the default runtime.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-02-20 13:13:55 +02:00
committed by GitHub
parent 8fd67916b3
commit c6e1bfecc6
11 changed files with 305 additions and 101 deletions

View File

@@ -1,4 +1,3 @@
import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';
@@ -17,6 +16,7 @@ import {
writeGroupsSnapshot,
writeTasksSnapshot,
} from './container-runner.js';
import { cleanupOrphans, ensureContainerRuntimeRunning } from './container-runtime.js';
import {
getAllChats,
getAllRegisteredGroups,
@@ -399,65 +399,8 @@ function recoverPendingMessages(): void {
}
function ensureContainerSystemRunning(): void {
try {
execSync('container system status', { stdio: 'pipe' });
logger.debug('Apple Container system already running');
} catch {
logger.info('Starting Apple Container system...');
try {
execSync('container system start', { stdio: 'pipe', timeout: 30000 });
logger.info('Apple Container system started');
} catch (err) {
logger.error({ err }, 'Failed to start Apple Container system');
console.error(
'\n╔════════════════════════════════════════════════════════════════╗',
);
console.error(
'║ FATAL: Apple Container system failed to start ║',
);
console.error(
'║ ║',
);
console.error(
'║ Agents cannot run without Apple Container. To fix: ║',
);
console.error(
'║ 1. Install from: https://github.com/apple/container/releases ║',
);
console.error(
'║ 2. Run: container system start ║',
);
console.error(
'║ 3. Restart NanoClaw ║',
);
console.error(
'╚════════════════════════════════════════════════════════════════╝\n',
);
throw new Error('Apple Container system is required but failed to start');
}
}
// Kill and clean up orphaned NanoClaw containers from previous runs
try {
const output = execSync('container ls --format json', {
stdio: ['pipe', 'pipe', 'pipe'],
encoding: 'utf-8',
});
const containers: { status: string; configuration: { id: string } }[] = JSON.parse(output || '[]');
const orphans = containers
.filter((c) => c.status === 'running' && c.configuration.id.startsWith('nanoclaw-'))
.map((c) => c.configuration.id);
for (const name of orphans) {
try {
execSync(`container stop ${name}`, { stdio: 'pipe' });
} catch { /* already stopped */ }
}
if (orphans.length > 0) {
logger.info({ count: orphans.length, names: orphans }, 'Stopped orphaned containers');
}
} catch (err) {
logger.warn({ err }, 'Failed to clean up orphaned containers');
}
ensureContainerRuntimeRunning();
cleanupOrphans();
}
async function main(): Promise<void> {