fix: prevent Docker dashboard from orphaning CLIProxy

This commit is contained in:
Tam Nhu Tran
2026-05-22 12:04:28 -04:00
parent 989f1973ec
commit acf9fd690a
2 changed files with 78 additions and 19 deletions
@@ -0,0 +1,48 @@
import { describe, expect, it } from 'bun:test';
import { handleConfigCommand } from '../config-command';
describe('handleConfigCommand Docker supervisord lifecycle', () => {
it('does not spawn CLIProxy directly when supervisord owns the runtime', async () => {
let ensureCalled = false;
const logs: string[] = [];
const originalLog = console.log;
console.log = (message?: unknown) => {
logs.push(String(message ?? ''));
};
try {
await handleConfigCommand([], {
getPort: async () => 3000,
openBrowser: async () => ({}),
startServer: async () =>
({
server: { address: () => ({ address: '0.0.0.0' }) },
wss: {},
cleanup: () => {},
}) as never,
setupGracefulShutdown: () => {},
ensureCliproxyService: async () => {
ensureCalled = true;
return { started: false, alreadyRunning: false, port: 8317 };
},
isRunningUnderSupervisord: () => true,
getDashboardAuthConfig: () => ({ enabled: true }),
initUI: async () => {},
header: (text: string) => text,
ok: (text: string) => text,
info: (text: string) => text,
warn: (text: string) => text,
fail: (text: string) => text,
resolveNamedCommand: () => undefined,
configSubcommandRoutes: [],
});
} finally {
console.log = originalLog;
}
expect(ensureCalled).toBe(false);
expect(logs).toContain('CLIProxy is managed by supervisord on port 8317');
});
});
+30 -19
View File
@@ -12,6 +12,7 @@ import { startServer } from '../web-server';
import { setupGracefulShutdown } from '../web-server/shutdown';
import { ensureCliproxyService } from '../cliproxy/service-manager';
import { resolveLifecyclePort } from '../cliproxy/config/port-manager';
import { isRunningUnderSupervisord } from '../docker/supervisord-lifecycle';
import { initUI, header, ok, info, warn, fail } from '../utils/ui';
import { resolveNamedCommand, type NamedCommandRoute } from './named-command-router';
import {
@@ -63,6 +64,7 @@ interface ConfigCommandDependencies {
startServer: typeof startServer;
setupGracefulShutdown: typeof setupGracefulShutdown;
ensureCliproxyService: typeof ensureCliproxyService;
isRunningUnderSupervisord?: typeof isRunningUnderSupervisord;
getDashboardAuthConfig: typeof getDashboardAuthConfig;
initUI: typeof initUI;
header: typeof header;
@@ -80,6 +82,7 @@ const defaultConfigCommandDependencies: ConfigCommandDependencies = {
startServer,
setupGracefulShutdown,
ensureCliproxyService,
isRunningUnderSupervisord,
getDashboardAuthConfig,
initUI,
header,
@@ -135,29 +138,37 @@ export async function handleConfigCommand(
console.log(deps.header('CCS Config Dashboard'));
console.log('');
// Ensure CLIProxy service is running for dashboard features
console.log(deps.info('Starting CLIProxy service...'));
const cliproxyResult = await deps.ensureCliproxyService(resolveLifecyclePort(), verbose);
logger.info('cliproxy.ensure_result', 'Config command checked CLIProxy availability', {
started: cliproxyResult.started,
alreadyRunning: cliproxyResult.alreadyRunning,
configRegenerated: cliproxyResult.configRegenerated,
port: cliproxyResult.port || null,
error: cliproxyResult.error || null,
});
const lifecyclePort = resolveLifecyclePort();
if (deps.isRunningUnderSupervisord?.() ?? false) {
logger.info('cliproxy.supervisord_managed', 'Skipping direct CLIProxy startup in Docker', {
port: lifecyclePort,
});
console.log(deps.info(`CLIProxy is managed by supervisord on port ${lifecyclePort}`));
} else {
// Ensure CLIProxy service is running for dashboard features
console.log(deps.info('Starting CLIProxy service...'));
const cliproxyResult = await deps.ensureCliproxyService(lifecyclePort, verbose);
logger.info('cliproxy.ensure_result', 'Config command checked CLIProxy availability', {
started: cliproxyResult.started,
alreadyRunning: cliproxyResult.alreadyRunning,
configRegenerated: cliproxyResult.configRegenerated,
port: cliproxyResult.port || null,
error: cliproxyResult.error || null,
});
if (cliproxyResult.started) {
if (cliproxyResult.alreadyRunning) {
console.log(deps.ok(`CLIProxy already running on port ${cliproxyResult.port}`));
if (cliproxyResult.configRegenerated) {
console.log(deps.warn('Config updated - restart CLIProxy to apply changes'));
if (cliproxyResult.started) {
if (cliproxyResult.alreadyRunning) {
console.log(deps.ok(`CLIProxy already running on port ${cliproxyResult.port}`));
if (cliproxyResult.configRegenerated) {
console.log(deps.warn('Config updated - restart CLIProxy to apply changes'));
}
} else {
console.log(deps.ok(`CLIProxy started on port ${cliproxyResult.port}`));
}
} else {
console.log(deps.ok(`CLIProxy started on port ${cliproxyResult.port}`));
console.log(deps.warn(`CLIProxy not available: ${cliproxyResult.error}`));
console.log(deps.info('Dashboard will work but Control Panel/Stats may be limited'));
}
} else {
console.log(deps.warn(`CLIProxy not available: ${cliproxyResult.error}`));
console.log(deps.info('Dashboard will work but Control Panel/Stats may be limited'));
}
console.log('');