Merge pull request #2351 from qwibitai/feat/container-config-to-db
feat(db): move container config from filesystem to DB
This commit is contained in:
@@ -19,7 +19,9 @@ import {
|
||||
ONECLI_URL,
|
||||
TIMEZONE,
|
||||
} from './config.js';
|
||||
import { readContainerConfig, writeContainerConfig } from './container-config.js';
|
||||
import { materializeContainerJson } from './container-config.js';
|
||||
import { getContainerConfig } from './db/container-configs.js';
|
||||
import { updateContainerConfigScalars, updateContainerConfigJson } from './db/container-configs.js';
|
||||
import { CONTAINER_RUNTIME_BIN, hostGatewayArgs, readonlyMountArgs, stopContainer } from './container-runtime.js';
|
||||
import { composeGroupClaudeMd } from './claude-md-compose.js';
|
||||
import { getAgentGroup } from './db/agent-groups.js';
|
||||
@@ -119,13 +121,10 @@ async function spawnContainer(session: Session): Promise<void> {
|
||||
}
|
||||
writeSessionRouting(agentGroup.id, session.id);
|
||||
|
||||
// Read container config once — threaded through provider resolution,
|
||||
// buildMounts, and buildContainerArgs so we don't re-read the file.
|
||||
const containerConfig = readContainerConfig(agentGroup.folder);
|
||||
|
||||
// Ensure container.json has the agent group identity fields the runner needs.
|
||||
// Written at spawn time so the runner can read them from the RO mount.
|
||||
ensureRuntimeFields(containerConfig, agentGroup);
|
||||
// Materialize container.json from DB — writes fresh file and returns
|
||||
// the config object, threaded through provider resolution, buildMounts,
|
||||
// and buildContainerArgs so we don't re-read.
|
||||
const containerConfig = materializeContainerJson(agentGroup.id);
|
||||
|
||||
// Resolve the effective provider + any host-side contribution it declares
|
||||
// (extra mounts, env passthrough). Computed once and threaded through both
|
||||
@@ -191,10 +190,14 @@ async function spawnContainer(session: Session): Promise<void> {
|
||||
}
|
||||
|
||||
/** Kill a container for a session. */
|
||||
export function killContainer(sessionId: string, reason: string): void {
|
||||
export function killContainer(sessionId: string, reason: string, onExit?: () => void): void {
|
||||
const entry = activeContainers.get(sessionId);
|
||||
if (!entry) return;
|
||||
|
||||
if (onExit) {
|
||||
entry.process.once('close', onExit);
|
||||
}
|
||||
|
||||
log.info('Killing container', { sessionId, reason, containerName: entry.containerName });
|
||||
try {
|
||||
stopContainer(entry.containerName);
|
||||
@@ -204,22 +207,19 @@ export function killContainer(sessionId: string, reason: string): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the provider name for a session using the precedence documented in
|
||||
* the provider-install skills:
|
||||
* Resolve the provider name for a session:
|
||||
*
|
||||
* sessions.agent_provider
|
||||
* → agent_groups.agent_provider
|
||||
* → container.json `provider`
|
||||
* → container_configs.provider
|
||||
* → 'claude'
|
||||
*
|
||||
* Pure so the precedence can be unit-tested without a DB or filesystem.
|
||||
*/
|
||||
export function resolveProviderName(
|
||||
sessionProvider: string | null | undefined,
|
||||
agentGroupProvider: string | null | undefined,
|
||||
containerConfigProvider: string | null | undefined,
|
||||
): string {
|
||||
return (sessionProvider || agentGroupProvider || containerConfigProvider || 'claude').toLowerCase();
|
||||
return (sessionProvider || containerConfigProvider || 'claude').toLowerCase();
|
||||
}
|
||||
|
||||
function resolveProviderContribution(
|
||||
@@ -227,7 +227,7 @@ function resolveProviderContribution(
|
||||
agentGroup: AgentGroup,
|
||||
containerConfig: import('./container-config.js').ContainerConfig,
|
||||
): { provider: string; contribution: ProviderContainerContribution } {
|
||||
const provider = resolveProviderName(session.agent_provider, agentGroup.agent_provider, containerConfig.provider);
|
||||
const provider = resolveProviderName(session.agent_provider, containerConfig.provider);
|
||||
const fn = getProviderContainerConfig(provider);
|
||||
const contribution = fn
|
||||
? fn({
|
||||
@@ -396,34 +396,6 @@ function syncSkillSymlinks(claudeDir: string, containerConfig: import('./contain
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure container.json has the runtime identity fields the runner needs.
|
||||
* Written at spawn time so they're always current even if the DB values
|
||||
* change (e.g. group rename). Only writes if values differ to avoid
|
||||
* unnecessary file churn.
|
||||
*/
|
||||
function ensureRuntimeFields(
|
||||
containerConfig: import('./container-config.js').ContainerConfig,
|
||||
agentGroup: AgentGroup,
|
||||
): void {
|
||||
let dirty = false;
|
||||
if (containerConfig.agentGroupId !== agentGroup.id) {
|
||||
containerConfig.agentGroupId = agentGroup.id;
|
||||
dirty = true;
|
||||
}
|
||||
if (containerConfig.groupName !== agentGroup.name) {
|
||||
containerConfig.groupName = agentGroup.name;
|
||||
dirty = true;
|
||||
}
|
||||
if (containerConfig.assistantName !== agentGroup.name) {
|
||||
containerConfig.assistantName = agentGroup.name;
|
||||
dirty = true;
|
||||
}
|
||||
if (dirty) {
|
||||
writeContainerConfig(agentGroup.folder, containerConfig);
|
||||
}
|
||||
}
|
||||
|
||||
async function buildContainerArgs(
|
||||
mounts: VolumeMount[],
|
||||
containerName: string,
|
||||
@@ -497,10 +469,10 @@ export async function buildAgentGroupImage(agentGroupId: string): Promise<void>
|
||||
const agentGroup = getAgentGroup(agentGroupId);
|
||||
if (!agentGroup) throw new Error('Agent group not found');
|
||||
|
||||
const containerConfig = readContainerConfig(agentGroup.folder);
|
||||
const aptPackages = containerConfig.packages.apt;
|
||||
const npmPackages = containerConfig.packages.npm;
|
||||
|
||||
const configRow = getContainerConfig(agentGroup.id);
|
||||
if (!configRow) throw new Error('Container config not found');
|
||||
const aptPackages = JSON.parse(configRow.packages_apt) as string[];
|
||||
const npmPackages = JSON.parse(configRow.packages_npm) as string[];
|
||||
if (aptPackages.length === 0 && npmPackages.length === 0) {
|
||||
throw new Error('No packages to install. Use install_packages first.');
|
||||
}
|
||||
@@ -536,9 +508,8 @@ export async function buildAgentGroupImage(agentGroupId: string): Promise<void>
|
||||
fs.unlinkSync(tmpDockerfile);
|
||||
}
|
||||
|
||||
// Store the image tag in groups/<folder>/container.json
|
||||
containerConfig.imageTag = imageTag;
|
||||
writeContainerConfig(agentGroup.folder, containerConfig);
|
||||
// Store the image tag in the DB
|
||||
updateContainerConfigScalars(agentGroup.id, { image_tag: imageTag });
|
||||
|
||||
log.info('Per-agent-group image built', { agentGroupId, imageTag });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user