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:
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Container Runner for NanoClaw
|
||||
* Spawns agent execution in Apple Container and handles IPC
|
||||
* Spawns agent execution in containers and handles IPC
|
||||
*/
|
||||
import { ChildProcess, exec, spawn } from 'child_process';
|
||||
import fs from 'fs';
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from './config.js';
|
||||
import { readEnvFile } from './env.js';
|
||||
import { logger } from './logger.js';
|
||||
import { CONTAINER_RUNTIME_BIN, readonlyMountArgs, stopContainer } from './container-runtime.js';
|
||||
import { validateAdditionalMounts } from './mount-security.js';
|
||||
import { RegisteredGroup } from './types.js';
|
||||
|
||||
@@ -88,7 +89,7 @@ function buildVolumeMounts(
|
||||
});
|
||||
|
||||
// Global memory directory (read-only for non-main)
|
||||
// Apple Container only supports directory mounts, not file mounts
|
||||
// Only directory mounts are supported, not file mounts
|
||||
const globalDir = path.join(GROUPS_DIR, 'global');
|
||||
if (fs.existsSync(globalDir)) {
|
||||
mounts.push({
|
||||
@@ -160,7 +161,7 @@ function buildVolumeMounts(
|
||||
});
|
||||
|
||||
// Mount agent-runner source from host — recompiled on container startup.
|
||||
// Bypasses Apple Container's sticky build cache for code changes.
|
||||
// Bypasses sticky build cache for code changes.
|
||||
const agentRunnerSrc = path.join(projectRoot, 'container', 'agent-runner', 'src');
|
||||
mounts.push({
|
||||
hostPath: agentRunnerSrc,
|
||||
@@ -202,13 +203,9 @@ function buildContainerArgs(mounts: VolumeMount[], containerName: string): strin
|
||||
args.push('-e', 'HOME=/home/node');
|
||||
}
|
||||
|
||||
// Apple Container: --mount for readonly, -v for read-write
|
||||
for (const mount of mounts) {
|
||||
if (mount.readonly) {
|
||||
args.push(
|
||||
'--mount',
|
||||
`type=bind,source=${mount.hostPath},target=${mount.containerPath},readonly`,
|
||||
);
|
||||
args.push(...readonlyMountArgs(mount.hostPath, mount.containerPath));
|
||||
} else {
|
||||
args.push('-v', `${mount.hostPath}:${mount.containerPath}`);
|
||||
}
|
||||
@@ -262,7 +259,7 @@ export async function runContainerAgent(
|
||||
fs.mkdirSync(logsDir, { recursive: true });
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const container = spawn('container', containerArgs, {
|
||||
const container = spawn(CONTAINER_RUNTIME_BIN, containerArgs, {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
@@ -369,7 +366,7 @@ export async function runContainerAgent(
|
||||
const killOnTimeout = () => {
|
||||
timedOut = true;
|
||||
logger.error({ group: group.name, containerName }, 'Container timeout, stopping gracefully');
|
||||
exec(`container stop ${containerName}`, { timeout: 15000 }, (err) => {
|
||||
exec(stopContainer(containerName), { timeout: 15000 }, (err) => {
|
||||
if (err) {
|
||||
logger.warn({ group: group.name, containerName, err }, 'Graceful stop failed, force killing');
|
||||
container.kill('SIGKILL');
|
||||
|
||||
Reference in New Issue
Block a user