v1.0.23
This commit is contained in:
1 parent
e9a9f0ca25
commit
0bb10cabb9
42 files changed
+7147
-239
No files matched your search
+70
-29
@@ -58,6 +58,8 @@ export interface StackOperationResult {
|
||||
success: boolean;
|
||||
output?: string;
|
||||
error?: string;
|
||||
/** The docker compose command that was executed (for debugging/testing) */
|
||||
command?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,6 +101,8 @@ export interface DeployStackOptions {
|
||||
envId?: number | null;
|
||||
sourceDir?: string; // Directory to copy all files from (for git stacks)
|
||||
forceRecreate?: boolean;
|
||||
build?: boolean; // Build images before starting (--build)
|
||||
pullPolicy?: string; // Pull policy: 'always' | 'missing' | 'never'
|
||||
composePath?: string; // Custom compose file path (for adopted/imported stacks)
|
||||
envPath?: string; // Custom env file path (for adopted/imported stacks)
|
||||
composeFileName?: string; // Compose filename to use (e.g., "docker-compose.yaml") for git stacks
|
||||
@@ -788,6 +792,8 @@ interface ComposeCommandOptions {
|
||||
stackName: string;
|
||||
envId?: number | null;
|
||||
forceRecreate?: boolean;
|
||||
build?: boolean; // Build images before starting (--build)
|
||||
pullPolicy?: string; // Pull policy: 'always' | 'missing' | 'never'
|
||||
removeVolumes?: boolean;
|
||||
stackFiles?: Record<string, string>; // All files to send to Hawser
|
||||
/** Working directory for compose execution (for imported stacks) */
|
||||
@@ -848,7 +854,9 @@ async function executeLocalCompose(
|
||||
customComposePath?: string,
|
||||
customEnvPath?: string,
|
||||
useOverrideFile?: boolean,
|
||||
serviceName?: string
|
||||
serviceName?: string,
|
||||
build?: boolean,
|
||||
pullPolicy?: string
|
||||
): Promise<StackOperationResult> {
|
||||
const logPrefix = `[Stack:${stackName}]`;
|
||||
|
||||
@@ -1040,6 +1048,8 @@ async function executeLocalCompose(
|
||||
case 'up':
|
||||
args.push('up', '-d', '--remove-orphans');
|
||||
if (forceRecreate) args.push('--force-recreate');
|
||||
if (build) args.push('--build');
|
||||
if (pullPolicy) args.push('--pull', pullPolicy);
|
||||
// If targeting a specific service, only update that service
|
||||
if (serviceName) {
|
||||
args.push(serviceName);
|
||||
@@ -1067,11 +1077,13 @@ async function executeLocalCompose(
|
||||
break;
|
||||
}
|
||||
|
||||
const commandStr = args.join(' ');
|
||||
|
||||
console.log(`${logPrefix} ----------------------------------------`);
|
||||
console.log(`${logPrefix} EXECUTE LOCAL COMPOSE`);
|
||||
console.log(`${logPrefix} ----------------------------------------`);
|
||||
console.log(`${logPrefix} Operation:`, operation);
|
||||
console.log(`${logPrefix} Command:`, args.join(' '));
|
||||
console.log(`${logPrefix} Command:`, commandStr);
|
||||
console.log(`${logPrefix} Working directory:`, stackDir);
|
||||
console.log(`${logPrefix} Compose file:`, composeFile);
|
||||
console.log(`${logPrefix} DOCKER_HOST:`, dockerHost || '(local socket)');
|
||||
@@ -1141,20 +1153,23 @@ async function executeLocalCompose(
|
||||
return {
|
||||
success: false,
|
||||
output: stdout,
|
||||
error: `docker compose ${operation} timed out after ${COMPOSE_TIMEOUT_MS / 1000} seconds`
|
||||
error: `docker compose ${operation} timed out after ${COMPOSE_TIMEOUT_MS / 1000} seconds`,
|
||||
command: commandStr
|
||||
};
|
||||
}
|
||||
|
||||
if (code === 0) {
|
||||
return {
|
||||
success: true,
|
||||
output: stdout || stderr || `Stack "${stackName}" ${operation} completed successfully`
|
||||
output: stdout || stderr || `Stack "${stackName}" ${operation} completed successfully`,
|
||||
command: commandStr
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
output: stdout,
|
||||
error: stderr || `docker compose ${operation} exited with code ${code}`
|
||||
error: stderr || `docker compose ${operation} exited with code ${code}`,
|
||||
command: commandStr
|
||||
};
|
||||
}
|
||||
} finally {
|
||||
@@ -1165,7 +1180,8 @@ async function executeLocalCompose(
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: `Failed to run docker compose ${operation}: ${err.message}`
|
||||
error: `Failed to run docker compose ${operation}: ${err.message}`,
|
||||
command: commandStr
|
||||
};
|
||||
} finally {
|
||||
// Cleanup temp override file from host path translation
|
||||
@@ -1207,7 +1223,9 @@ async function executeComposeViaHawser(
|
||||
removeVolumes?: boolean,
|
||||
stackFiles?: Record<string, string>,
|
||||
serviceName?: string,
|
||||
composeFileName?: string
|
||||
composeFileName?: string,
|
||||
build?: boolean,
|
||||
pullPolicy?: string
|
||||
): Promise<StackOperationResult> {
|
||||
const logPrefix = `[Stack:${stackName}]`;
|
||||
// Import dockerFetch dynamically to avoid circular dependency
|
||||
@@ -1280,6 +1298,8 @@ async function executeComposeViaHawser(
|
||||
files, // Files including .env (secrets NOT in .env file)
|
||||
forceRecreate: forceRecreate || false,
|
||||
removeVolumes: removeVolumes || false,
|
||||
build: build || false,
|
||||
pullPolicy: pullPolicy || '',
|
||||
registries, // Registry credentials for docker login
|
||||
serviceName // Target specific service only (with --no-deps)
|
||||
});
|
||||
@@ -1347,7 +1367,7 @@ async function executeComposeCommand(
|
||||
envVars?: Record<string, string>,
|
||||
secretVars?: Record<string, string>
|
||||
): Promise<StackOperationResult> {
|
||||
const { stackName, envId, forceRecreate, removeVolumes, stackFiles, workingDir, composePath, envPath, useOverrideFile, serviceName, composeFileName } = options;
|
||||
const { stackName, envId, forceRecreate, build, pullPolicy, removeVolumes, stackFiles, workingDir, composePath, envPath, useOverrideFile, serviceName, composeFileName } = options;
|
||||
|
||||
// Get environment configuration
|
||||
const env = envId ? await getEnvironment(envId) : null;
|
||||
@@ -1369,7 +1389,9 @@ async function executeComposeCommand(
|
||||
composePath,
|
||||
envPath,
|
||||
useOverrideFile,
|
||||
serviceName
|
||||
serviceName,
|
||||
build,
|
||||
pullPolicy
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1431,7 +1453,9 @@ async function executeComposeCommand(
|
||||
removeVolumes,
|
||||
hawserStackFiles,
|
||||
serviceName,
|
||||
composeFileName
|
||||
composeFileName,
|
||||
build,
|
||||
pullPolicy
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1462,7 +1486,9 @@ async function executeComposeCommand(
|
||||
composePath,
|
||||
envPath,
|
||||
useOverrideFile,
|
||||
serviceName
|
||||
serviceName,
|
||||
build,
|
||||
pullPolicy
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1483,7 +1509,9 @@ async function executeComposeCommand(
|
||||
composePath,
|
||||
envPath,
|
||||
useOverrideFile,
|
||||
serviceName
|
||||
serviceName,
|
||||
build,
|
||||
pullPolicy
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1747,7 +1775,7 @@ export interface RequireComposeResult {
|
||||
* - envPath: Path to the .env file (Docker Compose reads non-secrets from it)
|
||||
* - needsFileLocation: true if stack needs user to specify file paths
|
||||
*/
|
||||
async function requireComposeFile(
|
||||
export async function requireComposeFile(
|
||||
stackName: string,
|
||||
envId?: number | null,
|
||||
composeConfigPath?: string
|
||||
@@ -1877,12 +1905,19 @@ export async function stopStack(
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart a stack using docker compose restart
|
||||
* Falls back to individual container restart for stacks without compose files
|
||||
* Restart a stack using docker compose restart or stop+up (recreate mode).
|
||||
*
|
||||
* mode='restart' (default): Uses 'docker compose restart' — fast, in-place restart
|
||||
* that preserves container IDs but won't fix stale network_mode references.
|
||||
* mode='recreate': Uses 'docker compose stop' then 'docker compose up -d' —
|
||||
* recreates containers, fixing network_mode: service:<container> dependencies.
|
||||
*
|
||||
* Falls back to individual container restart for stacks without compose files.
|
||||
*/
|
||||
export async function restartStack(
|
||||
stackName: string,
|
||||
envId?: number | null
|
||||
envId?: number | null,
|
||||
mode: 'restart' | 'recreate' = 'restart'
|
||||
): Promise<StackOperationResult> {
|
||||
const result = await requireComposeFile(stackName, envId);
|
||||
|
||||
@@ -1891,13 +1926,17 @@ export async function restartStack(
|
||||
return withContainerFallback(stackName, envId, 'restart');
|
||||
}
|
||||
|
||||
const composeResult = await executeComposeCommand(
|
||||
'restart',
|
||||
{ stackName, envId, workingDir: result.stackDir, composePath: result.composePath, envPath: result.envPath },
|
||||
result.content!,
|
||||
result.nonSecretVars,
|
||||
result.secretVars
|
||||
);
|
||||
const opts: ComposeCommandOptions = { stackName, envId, workingDir: result.stackDir, composePath: result.composePath, envPath: result.envPath };
|
||||
|
||||
let composeResult: StackOperationResult;
|
||||
|
||||
if (mode === 'recreate') {
|
||||
// Stop first, then bring up with --force-recreate to ensure new container IDs
|
||||
await executeComposeCommand('stop', opts, result.content!, result.nonSecretVars, result.secretVars);
|
||||
composeResult = await executeComposeCommand('up', { ...opts, forceRecreate: true }, result.content!, result.nonSecretVars, result.secretVars);
|
||||
} else {
|
||||
composeResult = await executeComposeCommand('restart', opts, result.content!, result.nonSecretVars, result.secretVars);
|
||||
}
|
||||
|
||||
// Restart any dynamically-spawned child containers not in the compose file
|
||||
await cleanupOrphanStackContainers(stackName, envId, 'restart');
|
||||
@@ -2133,7 +2172,7 @@ export async function removeStack(
|
||||
* Uses stack locking to prevent concurrent deployments.
|
||||
*/
|
||||
export async function deployStack(options: DeployStackOptions): Promise<StackOperationResult> {
|
||||
const { name, compose, envId, sourceDir, forceRecreate, composePath, envPath, composeFileName, envFileName } = options;
|
||||
const { name, compose, envId, sourceDir, forceRecreate, build, pullPolicy, composePath, envPath, composeFileName, envFileName } = options;
|
||||
const logPrefix = `[Stack:${name}]`;
|
||||
|
||||
console.log(`${logPrefix} ========================================`);
|
||||
@@ -2206,12 +2245,12 @@ export async function deployStack(options: DeployStackOptions): Promise<StackOpe
|
||||
console.log(`${logPrefix} Read ${Object.keys(stackFiles).length} files from source directory`);
|
||||
console.log(`${logPrefix} Files:`, Object.keys(stackFiles).join(', '));
|
||||
|
||||
// Copy source to stack directory
|
||||
// Copy git source files to stack directory (overlay, not replace).
|
||||
// Do NOT rmSync first — relative volume mounts (e.g., ./data) live here
|
||||
// and would be destroyed, causing data loss (#831).
|
||||
console.log(`${logPrefix} Copying source directory to stack directory...`);
|
||||
if (existsSync(workingDir)) {
|
||||
rmSync(workingDir, { recursive: true, force: true });
|
||||
}
|
||||
cpSync(sourceDir, workingDir, { recursive: true });
|
||||
mkdirSync(workingDir, { recursive: true });
|
||||
cpSync(sourceDir, workingDir, { recursive: true, force: true });
|
||||
console.log(`${logPrefix} Copied ${sourceDir} -> ${workingDir}`);
|
||||
} else {
|
||||
// Internal stack: check if a custom path exists in DB (adopted/imported stacks)
|
||||
@@ -2275,6 +2314,8 @@ export async function deployStack(options: DeployStackOptions): Promise<StackOpe
|
||||
stackName: name,
|
||||
envId,
|
||||
forceRecreate,
|
||||
build,
|
||||
pullPolicy,
|
||||
stackFiles,
|
||||
workingDir,
|
||||
composePath: actualComposePath,
|
||||
|
||||
Reference in new issue
Block a user