@runuai/host 0.9.74 → 0.9.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/machine-provider-aws.ts +54 -5
- package/package.json +1 -1
|
@@ -103,7 +103,12 @@ export function awsUserData(authorizedPublicKey: string): string {
|
|
|
103
103
|
const script = [
|
|
104
104
|
"#!/bin/sh",
|
|
105
105
|
"set -e",
|
|
106
|
-
|
|
106
|
+
// uid 1000 is often TAKEN on stock cloud images (Debian's `admin`) —
|
|
107
|
+
// insisting on it aborted the whole script under set -e and left no key
|
|
108
|
+
// installed for anyone (live 2026-08-27, first EC2 smoke). Prefer 1000,
|
|
109
|
+
// fall back to auto: on a machine the task owns the whole filesystem,
|
|
110
|
+
// so the exact uid is convention, not correctness.
|
|
111
|
+
"id node >/dev/null 2>&1 || useradd -m -u 1000 -s /bin/bash node 2>/dev/null || useradd -m -s /bin/bash node",
|
|
107
112
|
"for account in node root; do",
|
|
108
113
|
' home=$(getent passwd "$account" | cut -d: -f6)',
|
|
109
114
|
' mkdir -p "$home/.ssh"',
|
|
@@ -114,7 +119,12 @@ export function awsUserData(authorizedPublicKey: string): string {
|
|
|
114
119
|
"done",
|
|
115
120
|
"",
|
|
116
121
|
].join("\n");
|
|
117
|
-
|
|
122
|
+
// RAW script, not base64: AWS CLI v2 base64-encodes --user-data ITSELF.
|
|
123
|
+
// Pre-encoding double-encoded it, cloud-init saw garbage and silently ran
|
|
124
|
+
// nothing — no key installed for anyone (live 2026-08-27, second EC2
|
|
125
|
+
// smoke failure; the uid-1000 collision fixed just before was real too,
|
|
126
|
+
// but masked by this).
|
|
127
|
+
return script;
|
|
118
128
|
}
|
|
119
129
|
|
|
120
130
|
type LiveInstance = {
|
|
@@ -144,6 +154,7 @@ function parseReservations(stdout: string): LiveInstance[] | null {
|
|
|
144
154
|
InstanceId?: unknown;
|
|
145
155
|
State?: { Name?: unknown };
|
|
146
156
|
PrivateIpAddress?: unknown;
|
|
157
|
+
PublicIpAddress?: unknown;
|
|
147
158
|
Tags?: Array<{ Key?: unknown; Value?: unknown }>;
|
|
148
159
|
};
|
|
149
160
|
if (typeof record.InstanceId !== "string") return null;
|
|
@@ -162,10 +173,16 @@ function parseReservations(stdout: string): LiveInstance[] | null {
|
|
|
162
173
|
instances.push({
|
|
163
174
|
instanceId: record.InstanceId,
|
|
164
175
|
state,
|
|
176
|
+
// Prefer the public address when the subnet assigns one: an
|
|
177
|
+
// orchestrator OUTSIDE the VPC (the local smoke, a dev box) can only
|
|
178
|
+
// dial that, while the production in-VPC orchestrator sees no public
|
|
179
|
+
// IP on its machines and keeps dialing private, unchanged.
|
|
165
180
|
privateIp:
|
|
166
|
-
typeof record.
|
|
167
|
-
? record.
|
|
168
|
-
:
|
|
181
|
+
typeof record.PublicIpAddress === "string"
|
|
182
|
+
? record.PublicIpAddress
|
|
183
|
+
: typeof record.PrivateIpAddress === "string"
|
|
184
|
+
? record.PrivateIpAddress
|
|
185
|
+
: null,
|
|
169
186
|
taskLabel,
|
|
170
187
|
});
|
|
171
188
|
}
|
|
@@ -325,6 +342,38 @@ export function createAwsMachineProvider(
|
|
|
325
342
|
}`,
|
|
326
343
|
);
|
|
327
344
|
}
|
|
345
|
+
// EC2 sits in `stopping` for tens of seconds, and StartInstances
|
|
346
|
+
// refuses it with IncorrectInstanceState — an immediate stop→resume
|
|
347
|
+
// (a user pressing Resume right after Stop; the live 2026-08-27
|
|
348
|
+
// smoke) failed exactly there because our state map folds `stopping`
|
|
349
|
+
// into "stopped". Stop resolves only once the instance can actually
|
|
350
|
+
// be started again, mirroring terminate's poll-to-absent.
|
|
351
|
+
const deadline = Date.now() + 180_000;
|
|
352
|
+
for (;;) {
|
|
353
|
+
const raw = await runner([
|
|
354
|
+
"ec2",
|
|
355
|
+
"describe-instances",
|
|
356
|
+
"--instance-ids",
|
|
357
|
+
instance.instanceId,
|
|
358
|
+
"--query",
|
|
359
|
+
"Reservations[].Instances[].State.Name",
|
|
360
|
+
"--output",
|
|
361
|
+
"text",
|
|
362
|
+
]);
|
|
363
|
+
const state = raw.status === 0 ? raw.stdout.trim() : "";
|
|
364
|
+
if (state === "stopped") return;
|
|
365
|
+
if (state === "terminated" || state === "shutting-down") {
|
|
366
|
+
throw new Error(
|
|
367
|
+
`machine ${machineId} terminated while stopping (${state})`,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
if (Date.now() >= deadline) {
|
|
371
|
+
throw new Error(
|
|
372
|
+
`machine ${machineId} did not reach stopped within the stop deadline (${state || "unknown"})`,
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 5_000));
|
|
376
|
+
}
|
|
328
377
|
},
|
|
329
378
|
|
|
330
379
|
async start(machineId: string): Promise<MachineInfo> {
|