mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 02:11:28 +00:00
fix(docker): delegate restart/install to supervisorctl in Docker deployments
The dashboard restart button and install service used local-mode process management (stopProxy + ensureCliproxyService) which conflicts with supervisord in Docker deployments. Killing the CLIProxy binary caused the bootstrap wrapper to exit, supervisord to attempt a restart, but ensureCliproxyService already spawned an orphaned process on :8317 resulting in EADDRINUSE and supervisord entering FATAL state. Detect supervisord via /var/run/supervisor.sock and delegate to supervisorctl restart, matching the pattern already used by ccs docker update. Closes #964
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Supervisord lifecycle helpers for Docker deployments (`ccs docker up`).
|
||||
*
|
||||
* In Docker, supervisord owns the CLIProxy process lifecycle. Direct
|
||||
* stop+start via session-tracker / service-manager creates orphaned
|
||||
* processes and causes supervisord to enter FATAL state (EADDRINUSE).
|
||||
* All restart operations must delegate to `supervisorctl` instead.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { execSync } from 'child_process';
|
||||
import { CLIPROXY_DEFAULT_PORT } from '../cliproxy/config/port-manager';
|
||||
|
||||
const SUPERVISOR_SOCK = '/var/run/supervisor.sock';
|
||||
const SUPERVISOR_CONF = '/etc/supervisord.conf';
|
||||
|
||||
/** True when running inside a supervisord-managed container. */
|
||||
export function isRunningUnderSupervisord(): boolean {
|
||||
return fs.existsSync(SUPERVISOR_SOCK);
|
||||
}
|
||||
|
||||
/** Restart the cliproxy program via supervisorctl. Returns port on success. */
|
||||
export function restartCliproxyViaSupervisord(): {
|
||||
success: boolean;
|
||||
port?: number;
|
||||
error?: string;
|
||||
} {
|
||||
try {
|
||||
execSync(`supervisorctl -c ${SUPERVISOR_CONF} restart cliproxy`, { timeout: 15_000 });
|
||||
return { success: true, port: CLIPROXY_DEFAULT_PORT };
|
||||
} catch (err) {
|
||||
const detail = err instanceof Error ? err.message : String(err);
|
||||
console.error(`[cliproxy] supervisorctl restart failed: ${detail}`);
|
||||
return { success: false, error: 'supervisorctl restart failed' };
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,10 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
isRunningUnderSupervisord,
|
||||
restartCliproxyViaSupervisord,
|
||||
} from '../../docker/supervisord-lifecycle';
|
||||
import {
|
||||
fetchCliproxyStats,
|
||||
fetchCliproxyModels,
|
||||
@@ -1014,7 +1018,14 @@ router.post('/install', async (req: Request, res: Response): Promise<void> => {
|
||||
*/
|
||||
router.post('/restart', async (_req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
// Stop proxy first
|
||||
if (isRunningUnderSupervisord()) {
|
||||
// Docker mode: delegate to supervisord which owns the process lifecycle
|
||||
const result = restartCliproxyViaSupervisord();
|
||||
res.json(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// Local mode: direct process management
|
||||
await stopProxy();
|
||||
|
||||
// Small delay to ensure port is released
|
||||
|
||||
@@ -3,6 +3,10 @@ import { ensureCliproxyService, type ServiceStartResult } from '../../cliproxy/s
|
||||
import { getProxyStatus as getProxyProcessStatus } from '../../cliproxy/session-tracker';
|
||||
import { isCliproxyRunning } from '../../cliproxy/stats-fetcher';
|
||||
import type { CLIProxyBackend } from '../../cliproxy/types';
|
||||
import {
|
||||
isRunningUnderSupervisord,
|
||||
restartCliproxyViaSupervisord,
|
||||
} from '../../docker/supervisord-lifecycle';
|
||||
|
||||
interface ProxyStatusLike {
|
||||
running: boolean;
|
||||
@@ -63,6 +67,20 @@ export async function installDashboardCliproxyVersion(
|
||||
};
|
||||
}
|
||||
|
||||
// In Docker, supervisord owns process lifecycle — delegate restart to it
|
||||
if (isRunningUnderSupervisord()) {
|
||||
const result = restartCliproxyViaSupervisord();
|
||||
return {
|
||||
success: result.success,
|
||||
restarted: result.success,
|
||||
port: result.port,
|
||||
error: result.error,
|
||||
message: result.success
|
||||
? `Successfully installed ${backendLabel} v${version} and restarted it on port ${result.port}`
|
||||
: `Installed ${backendLabel} v${version}, but restart failed`,
|
||||
};
|
||||
}
|
||||
|
||||
const startResult = await deps.ensureCliproxyService();
|
||||
if (!startResult.started && !startResult.alreadyRunning) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user