From f092fba4ceae51b8c9e20d7e1c48bee6304092fd Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 16 May 2026 12:56:23 +0000 Subject: [PATCH] fix: upload long deploy scripts before remote launch --- scripts/src/deploy.ts | 47 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/scripts/src/deploy.ts b/scripts/src/deploy.ts index 9887506a..7a6af7a1 100644 --- a/scripts/src/deploy.ts +++ b/scripts/src/deploy.ts @@ -55,6 +55,13 @@ interface BackgroundPoll { raw: unknown; } +interface RemoteScriptUpload { + ok: boolean; + path: string; + error: string; + raw: unknown[]; +} + interface ServiceRuntimeState { serviceId: string; ok: boolean; @@ -964,6 +971,37 @@ async function runTargetCommand(config: UniDeskConfig, service: UniDeskMicroserv return await dispatchSsh(config, service.providerId, command, cwd, waitMs, remoteTimeoutMs); } +function splitFixed(value: string, size: number): string[] { + const chunks: string[] = []; + for (let index = 0; index < value.length; index += size) chunks.push(value.slice(index, index + size)); + return chunks; +} + +async function uploadRemoteShellScript( + config: UniDeskConfig, + service: UniDeskMicroserviceConfig, + script: string, + cwd: string, + name: string, +): Promise { + const remotePath = `/tmp/unidesk-deploy-${safeId(service.id)}-${safeId(name)}-${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 8)}.sh`; + const b64Path = `${remotePath}.b64`; + const raw: unknown[] = []; + const init = await runTargetCommand(config, service, `umask 077; rm -f ${shellQuote(remotePath)} ${shellQuote(b64Path)}; : > ${shellQuote(b64Path)}`, cwd, shortDispatchWaitMs, shortRemoteTimeoutMs); + raw.push(init.raw); + if (!init.ok) return { ok: false, path: remotePath, error: init.stderr || init.stdout || "failed to initialize remote script upload", raw }; + const encoded = Buffer.from(script, "utf8").toString("base64"); + for (const chunk of splitFixed(encoded, 2400)) { + const append = await runTargetCommand(config, service, `printf %s ${shellQuote(chunk)} >> ${shellQuote(b64Path)}`, cwd, shortDispatchWaitMs, shortRemoteTimeoutMs); + raw.push(append.raw); + if (!append.ok) return { ok: false, path: remotePath, error: append.stderr || append.stdout || "failed to append remote script chunk", raw }; + } + const finalize = await runTargetCommand(config, service, `base64 -d ${shellQuote(b64Path)} > ${shellQuote(remotePath)}; chmod 700 ${shellQuote(remotePath)}; rm -f ${shellQuote(b64Path)}; wc -c ${shellQuote(remotePath)}`, cwd, shortDispatchWaitMs, shortRemoteTimeoutMs); + raw.push(finalize.raw); + if (!finalize.ok) return { ok: false, path: remotePath, error: finalize.stderr || finalize.stdout || "failed to finalize remote script upload", raw }; + return { ok: true, path: remotePath, error: "", raw }; +} + async function launchRemoteBackground( config: UniDeskConfig, service: UniDeskMicroserviceConfig, @@ -1029,7 +1067,14 @@ async function step( const runId = `${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 8)}`; const logFile = `/tmp/unidesk-deploy-${safeId(service.id)}-${name}-${runId}.log`; const sentinelFile = `/tmp/unidesk-deploy-${safeId(service.id)}-${name}-${runId}.done`; - const launch = await launchRemoteBackground(config, service, command, cwd ?? "/home/ubuntu", logFile, sentinelFile); + let launchCommand = command; + if (launchCommand.length > 1800) { + const upload = await uploadRemoteShellScript(config, service, command, cwd ?? "/home/ubuntu", name); + if (!upload.ok) return { step: name, ok: false, detail: upload.error, startedAt, finishedAt: nowIso(), raw: upload.raw }; + launchCommand = `bash ${shellQuote(upload.path)}`; + progressLine(name, "remote script uploaded", { serviceId: service.id, path: upload.path, bytes: command.length }); + } + const launch = await launchRemoteBackground(config, service, launchCommand, cwd ?? "/home/ubuntu", logFile, sentinelFile); if (!launch.ok) return { step: name, ok: false, detail: launch.error, startedAt, finishedAt: nowIso(), raw: launch.raw }; progressLine(name, "remote background started", { serviceId: service.id, pid: launch.pid, logFile }); const deadline = Date.now() + timeoutMs;