fix(docker): use export for remote env vars and increase build timeout

Remote compose commands placed env vars directly before a compound
if/then statement which is invalid bash syntax. Changed to export
statements chained with &&.

Added REMOTE_DOCKER_BUILD_TIMEOUT_MS (5min) for up and update
operations that involve npm install inside the container. Quick
queries (status, config, down) keep the default 30s timeout.

Closes #832
This commit is contained in:
Tam Nhu Tran
2026-03-28 16:22:17 -04:00
parent 23fd673fee
commit 60167d3f2b
+34 -15
View File
@@ -20,6 +20,7 @@ import type {
const LOCAL_DOCKER_SYNC_TIMEOUT_MS = 10_000; const LOCAL_DOCKER_SYNC_TIMEOUT_MS = 10_000;
const REMOTE_DOCKER_SYNC_TIMEOUT_MS = 30_000; const REMOTE_DOCKER_SYNC_TIMEOUT_MS = 30_000;
const REMOTE_DOCKER_BUILD_TIMEOUT_MS = 300_000;
function quotePosix(value: string): string { function quotePosix(value: string): string {
return `'${value.replace(/'/g, `'\"'\"'`)}'`; return `'${value.replace(/'/g, `'\"'\"'`)}'`;
@@ -194,11 +195,16 @@ export class DockerExecutor {
this.stageRemoteAssets(options.host); this.stageRemoteAssets(options.host);
} }
this.ensureSuccess( this.ensureSuccess(
this.runCompose(['up', '-d', '--build'], options, { this.runCompose(
CCS_NPM_VERSION: this.getInstalledCcsVersion(), ['up', '-d', '--build'],
CCS_DASHBOARD_PORT: String(options.port), options,
CCS_CLIPROXY_PORT: String(options.proxyPort), {
}), CCS_NPM_VERSION: this.getInstalledCcsVersion(),
CCS_DASHBOARD_PORT: String(options.port),
CCS_CLIPROXY_PORT: String(options.proxyPort),
},
REMOTE_DOCKER_BUILD_TIMEOUT_MS
),
'Docker stack startup', 'Docker stack startup',
options options
); );
@@ -225,7 +231,11 @@ export class DockerExecutor {
const script = const script =
'npm install -g @kaitranntt/ccs@latest --force && ccs cliproxy --latest && supervisorctl -c /etc/supervisord.conf restart ccs-dashboard cliproxy'; 'npm install -g @kaitranntt/ccs@latest --force && ccs cliproxy --latest && supervisorctl -c /etc/supervisord.conf restart ccs-dashboard cliproxy';
this.ensureSuccess( this.ensureSuccess(
this.runDocker(['exec', DOCKER_CONTAINER_NAME, 'sh', '-lc', script], options), this.runDocker(
['exec', DOCKER_CONTAINER_NAME, 'sh', '-lc', script],
options,
REMOTE_DOCKER_BUILD_TIMEOUT_MS
),
'Docker stack update', 'Docker stack update',
options options
); );
@@ -279,7 +289,8 @@ export class DockerExecutor {
private runCompose( private runCompose(
args: string[], args: string[],
options: DockerCommandTarget, options: DockerCommandTarget,
env: Record<string, string> = {} env: Record<string, string> = {},
timeoutMs?: number
): DockerCommandResult { ): DockerCommandResult {
if (!options.host) { if (!options.host) {
const prefix = this.resolveLocalComposePrefix(); const prefix = this.resolveLocalComposePrefix();
@@ -289,22 +300,30 @@ export class DockerExecutor {
cwd: path.dirname(this.assets.composeFile), cwd: path.dirname(this.assets.composeFile),
env: { ...process.env, ...env }, env: { ...process.env, ...env },
remote: false, remote: false,
timeoutMs,
}); });
} }
const envPrefix = Object.entries(env) const envExports = Object.entries(env)
.map(([key, value]) => `${key}=${quotePosix(value)}`) .map(([key, value]) => `export ${key}=${quotePosix(value)}`)
.join(' '); .join(' && ');
const composeArgs = ['-f', path.basename(this.assets.composeFile), ...args]; const composeArgs = ['-f', path.basename(this.assets.composeFile), ...args];
const remoteCommand = `cd ${DOCKER_REMOTE_DIR} && ${envPrefix ? `${envPrefix} ` : ''}${buildRemoteComposeCommand(composeArgs)}`; const remoteCommand = `cd ${DOCKER_REMOTE_DIR}${envExports ? ` && ${envExports}` : ''} && ${buildRemoteComposeCommand(composeArgs)}`;
return this.runSync('ssh', [options.host, remoteCommand], { remote: true }); return this.runSync('ssh', [options.host, remoteCommand], { remote: true, timeoutMs });
} }
private runDocker(args: string[], options: DockerCommandTarget): DockerCommandResult { private runDocker(
args: string[],
options: DockerCommandTarget,
timeoutMs?: number
): DockerCommandResult {
if (!options.host) { if (!options.host) {
return this.runSync('docker', args); return this.runSync('docker', args, { timeoutMs });
} }
return this.runSync('ssh', [options.host, buildRemoteDockerCommand(args)], { remote: true }); return this.runSync('ssh', [options.host, buildRemoteDockerCommand(args)], {
remote: true,
timeoutMs,
});
} }
private async runDockerStreaming(args: string[], options: DockerCommandTarget): Promise<void> { private async runDockerStreaming(args: string[], options: DockerCommandTarget): Promise<void> {