feat: refresh shared chat and server workflows
This commit is contained in:
@@ -117,6 +117,8 @@ type BuildInspectionResult = {
|
||||
updateSummary: string | null;
|
||||
};
|
||||
|
||||
type WorkServerSlot = 'blue' | 'green';
|
||||
|
||||
const RUNNER_HEARTBEAT_FRESHNESS_MS = 30_000;
|
||||
const DEFERRED_RESTART_DELAY_MS = 2_000;
|
||||
const DEFERRED_RESTART_CONFIRM_TIMEOUT_MS = 4_500;
|
||||
@@ -484,6 +486,41 @@ export function resolveDockerSocketPath(source: NodeJS.ProcessEnv | Record<strin
|
||||
return '/var/run/docker.sock';
|
||||
}
|
||||
|
||||
function getWorkServerActiveSlotFileCandidates() {
|
||||
const mainProjectRoot = normalizePath(resolveMainProjectRoot());
|
||||
const projectRoot = normalizePath(env.SERVER_COMMAND_PROJECT_ROOT);
|
||||
|
||||
return [
|
||||
env.SERVER_COMMAND_WORK_SERVER_ACTIVE_SLOT_FILE?.trim() || null,
|
||||
path.join(mainProjectRoot, 'etc', 'servers', 'work-server', '.docker', 'runtime', 'active-slot'),
|
||||
path.join(projectRoot, 'etc', 'servers', 'work-server', '.docker', 'runtime', 'active-slot'),
|
||||
path.join(projectRoot, '.docker', 'runtime', 'active-slot'),
|
||||
].filter((value, index, array): value is string => Boolean(value) && array.indexOf(value) === index);
|
||||
}
|
||||
|
||||
async function readWorkServerActiveSlot(): Promise<WorkServerSlot> {
|
||||
for (const candidate of getWorkServerActiveSlotFileCandidates()) {
|
||||
try {
|
||||
const value = (await readFile(candidate, 'utf8')).trim();
|
||||
if (value === 'blue' || value === 'green') {
|
||||
return value;
|
||||
}
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 'blue';
|
||||
}
|
||||
|
||||
function resolveWorkServerContainerName(slot: WorkServerSlot) {
|
||||
return slot === 'green' ? 'work-server-green' : 'work-server-blue';
|
||||
}
|
||||
|
||||
function appendComposeDetails(detailParts: Array<string | null | undefined>) {
|
||||
return trimPreview(detailParts.filter(Boolean).join(' '));
|
||||
}
|
||||
|
||||
function shouldRetryWithDockerSocket(error: unknown) {
|
||||
const failure = error instanceof Error ? (error as ExecFileFailure) : null;
|
||||
const detail = [failure?.stderr, failure?.stdout, failure?.message].filter(Boolean).join('\n');
|
||||
@@ -1137,11 +1174,16 @@ async function inspectComposeStatus(definition: ServerDefinition) {
|
||||
}
|
||||
}
|
||||
|
||||
async function inspectContainerRuntime(definition: ServerDefinition): Promise<RuntimeInspectionResult> {
|
||||
async function inspectContainerRuntime(
|
||||
definition: ServerDefinition,
|
||||
containerNameOverride?: string,
|
||||
): Promise<RuntimeInspectionResult> {
|
||||
const containerName = containerNameOverride ?? definition.containerName;
|
||||
|
||||
try {
|
||||
const { stdout } = await execFileAsync(
|
||||
'docker',
|
||||
['inspect', '-f', '{{.State.StartedAt}}\t{{.State.Status}}\t{{.Name}}', definition.containerName],
|
||||
['inspect', '-f', '{{.State.StartedAt}}\t{{.State.Status}}\t{{.Name}}', containerName],
|
||||
{
|
||||
cwd: definition.commandWorkingDirectory,
|
||||
timeout: 8000,
|
||||
@@ -1158,7 +1200,7 @@ async function inspectContainerRuntime(definition: ServerDefinition): Promise<Ru
|
||||
} catch (error) {
|
||||
if (shouldRetryWithDockerSocket(error)) {
|
||||
try {
|
||||
const inspected = await inspectContainerViaSocket(definition.containerName);
|
||||
const inspected = await inspectContainerViaSocket(containerName);
|
||||
return {
|
||||
startedAt: normalizeDateTimeValue(inspected.State?.StartedAt ?? null),
|
||||
composeStatus: inspected.State?.Status?.trim() || null,
|
||||
@@ -1298,10 +1340,27 @@ async function inspectRuntime(definition: ServerDefinition): Promise<RuntimeInsp
|
||||
}
|
||||
|
||||
if (definition.key === 'work-server') {
|
||||
const primarySlot = await readWorkServerActiveSlot();
|
||||
const candidateSlots: WorkServerSlot[] = primarySlot === 'green' ? ['green', 'blue'] : ['blue', 'green'];
|
||||
|
||||
for (const slot of candidateSlots) {
|
||||
const runtimeInfo = await inspectContainerRuntime(definition, resolveWorkServerContainerName(slot));
|
||||
|
||||
if (runtimeInfo.startedAt) {
|
||||
return {
|
||||
...runtimeInfo,
|
||||
composeDetails: appendComposeDetails([`slot:${slot}`, runtimeInfo.composeDetails]),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const runtimeInfo = await inspectContainerRuntime(definition);
|
||||
|
||||
if (runtimeInfo.startedAt) {
|
||||
return runtimeInfo;
|
||||
return {
|
||||
...runtimeInfo,
|
||||
composeDetails: appendComposeDetails(['slot:proxy', runtimeInfo.composeDetails]),
|
||||
};
|
||||
}
|
||||
|
||||
return inspectCurrentProcessRuntime();
|
||||
|
||||
Reference in New Issue
Block a user