fix: stream cloud-init output instead of blind-polling on DigitalOcean (#1734)

Replace 60×5s blind poll loop ("Cloud-init in progress N/60") with
real-time streaming of /var/log/cloud-init-output.log via tail -f
over SSH. Users now see every apt-get, curl, and error as it happens.

Background checker exits as soon as .cloud-init-complete marker
appears. 5min timeout. Brief 30s fallback poll if streaming fails.

Co-authored-by: lab <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
A 2026-02-22 11:58:29 -08:00 committed by GitHub
parent ac5e8495b1
commit b50b27141c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 38 additions and 13 deletions

View file

@ -1,6 +1,6 @@
{
"name": "@openrouter/spawn",
"version": "0.6.8",
"version": "0.6.9",
"type": "module",
"bin": {
"spawn": "cli.js"

View file

@ -760,29 +760,54 @@ export async function waitForCloudInit(
await sleep(5000);
}
logStep("Waiting for cloud-init to complete...");
for (let attempt = 1; attempt <= 60; attempt++) {
// Stream cloud-init output so the user sees progress in real time
logStep("Streaming cloud-init output (timeout: 5min)...");
const remoteScript = [
'tail -f /var/log/cloud-init-output.log 2>/dev/null &',
'TAIL_PID=$!',
'for i in $(seq 1 150); do',
' if [ -f /root/.cloud-init-complete ]; then',
' kill $TAIL_PID 2>/dev/null; wait $TAIL_PID 2>/dev/null',
' echo ""; echo "--- cloud-init complete ---"; exit 0',
' fi',
' sleep 2',
'done',
'kill $TAIL_PID 2>/dev/null; wait $TAIL_PID 2>/dev/null',
'echo ""; echo "--- cloud-init timed out ---"; exit 1',
].join("; ");
try {
const proc = Bun.spawn(
["ssh", ...SSH_OPTS, `root@${serverIp}`, remoteScript],
{ stdio: ["ignore", "inherit", "inherit"] },
);
const exitCode = await proc.exited;
if (exitCode === 0) {
logInfo("Cloud-init complete");
return;
}
logWarn("Cloud-init did not complete within 5 minutes");
} catch {
logWarn("Could not stream cloud-init log, falling back to polling...");
}
// Brief fallback poll if streaming failed (e.g. log file not yet created)
for (let attempt = 1; attempt <= 6; attempt++) {
try {
const proc = Bun.spawn(
["ssh", ...SSH_OPTS, `root@${serverIp}`, "test -f /root/.cloud-init-complete && echo done"],
{ stdio: ["ignore", "pipe", "pipe"] },
);
const stdout = await new Response(proc.stdout).text();
const exitCode = await proc.exited;
if (exitCode === 0 && stdout.includes("done")) {
if ((await proc.exited) === 0 && stdout.includes("done")) {
logInfo("Cloud-init complete");
return;
}
} catch {
// ignore
}
if (attempt >= 60) {
logWarn("Cloud-init marker not found, continuing anyway...");
return;
}
logStep(`Cloud-init in progress (${attempt}/60)`);
} catch { /* ignore */ }
logStep(`Cloud-init in progress (${attempt}/6)`);
await sleep(5000);
}
logWarn("Cloud-init marker not found, continuing anyway...");
}
export async function runServer(