@slock-ai/computer 0.0.16 → 0.0.18
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/dist/index.js +514 -229
- package/dist/lib/index.d.ts +172 -72
- package/dist/lib/index.js +357 -125
- package/package.json +1 -1
package/dist/lib/index.js
CHANGED
|
@@ -31,73 +31,253 @@ var StateReaderError = class extends Error {
|
|
|
31
31
|
this.code = code;
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
|
-
var MIGRATION_DETECTION_KINDS = ["
|
|
34
|
+
var MIGRATION_DETECTION_KINDS = ["candidates", "server-unavailable"];
|
|
35
35
|
function isMigrationDetectionKind(value) {
|
|
36
36
|
return typeof value === "string" && MIGRATION_DETECTION_KINDS.includes(value);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// src/lib/migration.ts
|
|
40
|
-
import { readdir, readFile } from "fs/promises";
|
|
41
|
-
import { join } from "path";
|
|
40
|
+
import { readdir, readFile, stat } from "fs/promises";
|
|
41
|
+
import { join, resolve } from "path";
|
|
42
42
|
var MACHINE_DIR_PREFIX = "machine-";
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
var FINGERPRINT_HEX_RE = /^[0-9a-f]{16}$/;
|
|
44
|
+
async function readLocalOwners(installRoot) {
|
|
45
|
+
const machinesDir = join(installRoot, "machines");
|
|
46
|
+
let entries;
|
|
47
|
+
try {
|
|
48
|
+
entries = await readdir(machinesDir);
|
|
49
|
+
} catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
const owners = [];
|
|
53
|
+
for (const name of entries) {
|
|
54
|
+
if (!name.startsWith(MACHINE_DIR_PREFIX)) continue;
|
|
55
|
+
const ownerPath = join(machinesDir, name, "daemon.lock", "owner.json");
|
|
56
|
+
let raw;
|
|
57
|
+
try {
|
|
58
|
+
raw = await readFile(ownerPath, "utf8");
|
|
59
|
+
} catch {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
let parsed;
|
|
63
|
+
try {
|
|
64
|
+
parsed = JSON.parse(raw);
|
|
65
|
+
} catch {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const fp = parsed.apiKeyFingerprint;
|
|
69
|
+
if (typeof fp !== "string" || !FINGERPRINT_HEX_RE.test(fp)) continue;
|
|
70
|
+
owners.push({ apiKeyFingerprint: fp, localPath: ownerPath });
|
|
71
|
+
}
|
|
72
|
+
return owners;
|
|
73
|
+
}
|
|
74
|
+
function indexLocalByFingerprint(owners) {
|
|
75
|
+
const map = /* @__PURE__ */ new Map();
|
|
76
|
+
for (const owner of owners) {
|
|
77
|
+
if (!map.has(owner.apiKeyFingerprint)) {
|
|
78
|
+
map.set(owner.apiKeyFingerprint, owner);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return map;
|
|
82
|
+
}
|
|
83
|
+
function intersect(localByFp, roster) {
|
|
84
|
+
const out = [];
|
|
85
|
+
for (const entry of roster) {
|
|
86
|
+
const local = localByFp.get(entry.apiKeyFingerprint);
|
|
87
|
+
if (!local) continue;
|
|
88
|
+
out.push({
|
|
89
|
+
apiKeyFingerprint: entry.apiKeyFingerprint,
|
|
90
|
+
daemonId: entry.daemonId,
|
|
91
|
+
localPath: local.localPath,
|
|
92
|
+
machineName: entry.machineName,
|
|
93
|
+
...entry.hostname ? { hostname: entry.hostname } : {},
|
|
94
|
+
...entry.lastSeenAt ? { lastSeenAt: entry.lastSeenAt } : {}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
out.sort((a, b) => {
|
|
98
|
+
const aSeen = a.lastSeenAt ?? "";
|
|
99
|
+
const bSeen = b.lastSeenAt ?? "";
|
|
100
|
+
if (aSeen !== bSeen) {
|
|
101
|
+
if (aSeen === "") return 1;
|
|
102
|
+
if (bSeen === "") return -1;
|
|
103
|
+
return aSeen < bSeen ? 1 : -1;
|
|
104
|
+
}
|
|
105
|
+
return a.apiKeyFingerprint < b.apiKeyFingerprint ? -1 : a.apiKeyFingerprint > b.apiKeyFingerprint ? 1 : 0;
|
|
106
|
+
});
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
async function detectLegacyMigration(installRoot, serverSlug, client) {
|
|
110
|
+
const localOwners = await readLocalOwners(installRoot);
|
|
111
|
+
if (localOwners.length === 0) {
|
|
112
|
+
return { kind: "candidates", candidates: [] };
|
|
113
|
+
}
|
|
114
|
+
const result = await client.list(serverSlug);
|
|
115
|
+
if (result.status !== "success") {
|
|
116
|
+
return { kind: "server-unavailable" };
|
|
117
|
+
}
|
|
118
|
+
const localByFp = indexLocalByFingerprint(localOwners);
|
|
119
|
+
return { kind: "candidates", candidates: intersect(localByFp, result.entries) };
|
|
120
|
+
}
|
|
121
|
+
async function validateManualMigratePath(inputPath, roster) {
|
|
122
|
+
const absInput = resolve(inputPath);
|
|
123
|
+
let stats;
|
|
124
|
+
try {
|
|
125
|
+
stats = await stat(absInput);
|
|
126
|
+
} catch {
|
|
127
|
+
return { ok: false, code: "MIGRATE_FROM_NOT_FOUND" };
|
|
128
|
+
}
|
|
129
|
+
const ownerPath = stats.isDirectory() ? join(absInput, "daemon.lock", "owner.json") : absInput;
|
|
130
|
+
const candidateOwnerPath = await firstReadableOwner([
|
|
131
|
+
ownerPath,
|
|
132
|
+
join(absInput, "owner.json"),
|
|
133
|
+
absInput
|
|
134
|
+
]);
|
|
135
|
+
if (!candidateOwnerPath) {
|
|
136
|
+
return { ok: false, code: "MIGRATE_FROM_INVALID" };
|
|
137
|
+
}
|
|
51
138
|
let raw;
|
|
52
139
|
try {
|
|
53
|
-
raw = await readFile(
|
|
140
|
+
raw = await readFile(candidateOwnerPath, "utf8");
|
|
54
141
|
} catch {
|
|
55
|
-
return {};
|
|
142
|
+
return { ok: false, code: "MIGRATE_FROM_INVALID" };
|
|
56
143
|
}
|
|
57
144
|
let parsed;
|
|
58
145
|
try {
|
|
59
146
|
parsed = JSON.parse(raw);
|
|
60
147
|
} catch {
|
|
61
|
-
return {};
|
|
148
|
+
return { ok: false, code: "MIGRATE_FROM_INVALID" };
|
|
149
|
+
}
|
|
150
|
+
const fp = parsed.apiKeyFingerprint;
|
|
151
|
+
if (typeof fp !== "string" || !FINGERPRINT_HEX_RE.test(fp)) {
|
|
152
|
+
return { ok: false, code: "MIGRATE_FROM_INVALID" };
|
|
62
153
|
}
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
154
|
+
const match = roster.find((r) => r.apiKeyFingerprint === fp);
|
|
155
|
+
if (!match) {
|
|
156
|
+
return { ok: false, code: "MIGRATE_FROM_NOT_OWNED" };
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
ok: true,
|
|
160
|
+
candidate: {
|
|
161
|
+
apiKeyFingerprint: match.apiKeyFingerprint,
|
|
162
|
+
daemonId: match.daemonId,
|
|
163
|
+
localPath: candidateOwnerPath,
|
|
164
|
+
machineName: match.machineName,
|
|
165
|
+
...match.hostname ? { hostname: match.hostname } : {},
|
|
166
|
+
...match.lastSeenAt ? { lastSeenAt: match.lastSeenAt } : {}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
66
169
|
}
|
|
67
|
-
async function
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
170
|
+
async function firstReadableOwner(paths) {
|
|
171
|
+
const seen = /* @__PURE__ */ new Set();
|
|
172
|
+
for (const p of paths) {
|
|
173
|
+
if (seen.has(p)) continue;
|
|
174
|
+
seen.add(p);
|
|
175
|
+
try {
|
|
176
|
+
const st = await stat(p);
|
|
177
|
+
if (st.isFile()) return p;
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
75
180
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/apiClient.ts
|
|
185
|
+
import { fetch } from "undici";
|
|
186
|
+
var LegacyMachinesClient = class {
|
|
187
|
+
constructor(baseUrl, accessToken) {
|
|
188
|
+
this.baseUrl = baseUrl;
|
|
189
|
+
this.accessToken = accessToken;
|
|
190
|
+
}
|
|
191
|
+
url(p) {
|
|
192
|
+
return new URL(p, this.baseUrl).toString();
|
|
193
|
+
}
|
|
194
|
+
async list(serverSlug) {
|
|
195
|
+
let res;
|
|
196
|
+
try {
|
|
197
|
+
res = await fetch(
|
|
198
|
+
this.url(`/api/computer/legacy-machines?serverSlug=${encodeURIComponent(serverSlug)}`),
|
|
199
|
+
{
|
|
200
|
+
method: "GET",
|
|
201
|
+
headers: { Authorization: `Bearer ${this.accessToken}` }
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
} catch {
|
|
205
|
+
return { status: "error", code: "request_failed" };
|
|
206
|
+
}
|
|
207
|
+
const body = await res.json().catch(() => null);
|
|
208
|
+
if (res.status === 200 && body && Array.isArray(body.entries)) {
|
|
209
|
+
const entries = body.entries.map((raw) => {
|
|
210
|
+
if (!raw || typeof raw !== "object") return null;
|
|
211
|
+
const e = raw;
|
|
212
|
+
if (typeof e.daemonId !== "string" || typeof e.apiKeyFingerprint !== "string" || typeof e.machineName !== "string") {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
daemonId: e.daemonId,
|
|
217
|
+
apiKeyFingerprint: e.apiKeyFingerprint,
|
|
218
|
+
machineName: e.machineName,
|
|
219
|
+
hostname: typeof e.hostname === "string" ? e.hostname : null,
|
|
220
|
+
lastSeenAt: typeof e.lastSeenAt === "string" ? e.lastSeenAt : null
|
|
221
|
+
};
|
|
222
|
+
}).filter((entry) => entry !== null);
|
|
223
|
+
return { status: "success", entries };
|
|
224
|
+
}
|
|
225
|
+
if (res.status === 401) return { status: "auth_required" };
|
|
226
|
+
if (res.status === 403) return { status: "not_authorized" };
|
|
227
|
+
if (res.status === 404) return { status: "disabled" };
|
|
228
|
+
const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
|
|
229
|
+
return { status: "error", code };
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
var RunnersClient = class {
|
|
233
|
+
constructor(baseUrl, computerApiKey) {
|
|
234
|
+
this.baseUrl = baseUrl;
|
|
235
|
+
this.computerApiKey = computerApiKey;
|
|
236
|
+
}
|
|
237
|
+
url(p) {
|
|
238
|
+
return new URL(p, this.baseUrl).toString();
|
|
239
|
+
}
|
|
240
|
+
async list() {
|
|
241
|
+
const res = await fetch(this.url("/internal/computer/runners"), {
|
|
242
|
+
method: "GET",
|
|
243
|
+
headers: { Authorization: `Bearer ${this.computerApiKey}` }
|
|
84
244
|
});
|
|
245
|
+
const body = await res.json().catch(() => null);
|
|
246
|
+
if (res.status === 200 && body && Array.isArray(body.runners)) {
|
|
247
|
+
return {
|
|
248
|
+
status: "success",
|
|
249
|
+
whitelist: Array.isArray(body.whitelist) ? body.whitelist : [],
|
|
250
|
+
runners: body.runners
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
|
|
254
|
+
const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
|
|
255
|
+
return { status: "error", code };
|
|
85
256
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
};
|
|
257
|
+
async stop(agentId) {
|
|
258
|
+
const res = await fetch(this.url(`/internal/computer/runners/${encodeURIComponent(agentId)}/stop`), {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: { Authorization: `Bearer ${this.computerApiKey}`, "Content-Type": "application/json" },
|
|
261
|
+
body: "{}"
|
|
262
|
+
});
|
|
263
|
+
if (res.status === 200) return { status: "success" };
|
|
264
|
+
if (res.status === 404) return { status: "not_found" };
|
|
265
|
+
if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
|
|
266
|
+
const body = await res.json().catch(() => null);
|
|
267
|
+
const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
|
|
268
|
+
return { status: "error", code };
|
|
95
269
|
}
|
|
96
|
-
|
|
97
|
-
}
|
|
270
|
+
};
|
|
98
271
|
|
|
99
|
-
// src/
|
|
100
|
-
import { readFile as
|
|
272
|
+
// src/setup.ts
|
|
273
|
+
import { chmod as chmod4, mkdir as mkdir9, readFile as readFile8, rename as rename3, rm as rm2, writeFile as writeFile8 } from "fs/promises";
|
|
274
|
+
import { dirname as dirname8 } from "path";
|
|
275
|
+
import { fetch as fetch3 } from "undici";
|
|
276
|
+
|
|
277
|
+
// src/serverState.ts
|
|
278
|
+
import { readFile as readFile2, readdir as readdir2, writeFile, mkdir, unlink, access, chmod } from "fs/promises";
|
|
279
|
+
import { dirname } from "path";
|
|
280
|
+
import { constants as fsConstants } from "fs";
|
|
101
281
|
|
|
102
282
|
// src/paths.ts
|
|
103
283
|
import { createHash } from "crypto";
|
|
@@ -128,6 +308,9 @@ function serverDir(slockHome, serverId) {
|
|
|
128
308
|
function serverAttachmentPath(slockHome, serverId) {
|
|
129
309
|
return path.join(serverDir(slockHome, serverId), "runner.state.json");
|
|
130
310
|
}
|
|
311
|
+
function legacyServerAttachmentPath(slockHome, serverId) {
|
|
312
|
+
return path.join(serverDir(slockHome, serverId), "attachment.json");
|
|
313
|
+
}
|
|
131
314
|
function serverRunnerPidPath(slockHome, serverId) {
|
|
132
315
|
return path.join(serverDir(slockHome, serverId), "server-runner.pid");
|
|
133
316
|
}
|
|
@@ -161,9 +344,6 @@ function serviceLogPath(slockHome) {
|
|
|
161
344
|
}
|
|
162
345
|
|
|
163
346
|
// src/serverState.ts
|
|
164
|
-
import { readFile as readFile2, readdir as readdir2, writeFile, mkdir, unlink, access, chmod } from "fs/promises";
|
|
165
|
-
import { dirname } from "path";
|
|
166
|
-
import { constants as fsConstants } from "fs";
|
|
167
347
|
function parseAttachment(raw) {
|
|
168
348
|
try {
|
|
169
349
|
const a = JSON.parse(raw);
|
|
@@ -175,21 +355,33 @@ function parseAttachment(raw) {
|
|
|
175
355
|
serverMachineId: a.serverMachineId,
|
|
176
356
|
apiKey: a.apiKey,
|
|
177
357
|
serverUrl: a.serverUrl,
|
|
178
|
-
attachedAt: typeof a.attachedAt === "string" ? a.attachedAt : void 0
|
|
358
|
+
attachedAt: typeof a.attachedAt === "string" ? a.attachedAt : void 0,
|
|
359
|
+
adoptedFromLegacy: a.adoptedFromLegacy === true ? true : void 0,
|
|
360
|
+
legacyMachineId: typeof a.legacyMachineId === "string" ? a.legacyMachineId : void 0
|
|
179
361
|
};
|
|
180
362
|
}
|
|
181
363
|
} catch {
|
|
182
364
|
}
|
|
183
365
|
return null;
|
|
184
366
|
}
|
|
185
|
-
async function
|
|
186
|
-
if (!isValidServerId(serverId)) return null;
|
|
367
|
+
async function readAttachmentAt(path2) {
|
|
187
368
|
try {
|
|
188
|
-
return parseAttachment(await readFile2(
|
|
369
|
+
return parseAttachment(await readFile2(path2, "utf8"));
|
|
189
370
|
} catch {
|
|
190
371
|
return null;
|
|
191
372
|
}
|
|
192
373
|
}
|
|
374
|
+
async function readServerAttachment(slockHome, serverId) {
|
|
375
|
+
if (!isValidServerId(serverId)) return null;
|
|
376
|
+
const current = await readAttachmentAt(serverAttachmentPath(slockHome, serverId));
|
|
377
|
+
const legacy = await readAttachmentAt(legacyServerAttachmentPath(slockHome, serverId));
|
|
378
|
+
if (!current) return legacy;
|
|
379
|
+
if (!legacy) return current;
|
|
380
|
+
if (legacy.adoptedFromLegacy === true && current.adoptedFromLegacy !== true && legacy.serverMachineId !== current.serverMachineId) {
|
|
381
|
+
return legacy;
|
|
382
|
+
}
|
|
383
|
+
return current;
|
|
384
|
+
}
|
|
193
385
|
async function listAttachedServerIds(slockHome) {
|
|
194
386
|
let entries;
|
|
195
387
|
try {
|
|
@@ -214,12 +406,37 @@ async function listServerAttachments(slockHome) {
|
|
|
214
406
|
return out;
|
|
215
407
|
}
|
|
216
408
|
|
|
217
|
-
// src/
|
|
218
|
-
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2
|
|
409
|
+
// src/services/attach.ts
|
|
410
|
+
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
219
411
|
import { dirname as dirname2 } from "path";
|
|
412
|
+
|
|
413
|
+
// src/services/login.ts
|
|
414
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
415
|
+
import { dirname as dirname3 } from "path";
|
|
416
|
+
|
|
417
|
+
// src/services/adoptLegacy.ts
|
|
418
|
+
import { chmod as chmod3, mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4, appendFile, stat as stat2 } from "fs/promises";
|
|
419
|
+
import { createHash as createHash2 } from "crypto";
|
|
420
|
+
import { dirname as dirname4, join as join2 } from "path";
|
|
421
|
+
import { setTimeout as delay } from "timers/promises";
|
|
422
|
+
|
|
423
|
+
// src/service.ts
|
|
424
|
+
import { spawn as spawn2 } from "child_process";
|
|
425
|
+
import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile7, open, rename as rename2 } from "fs/promises";
|
|
426
|
+
import { dirname as dirname7, join as joinPath } from "path";
|
|
427
|
+
import { fileURLToPath } from "url";
|
|
428
|
+
|
|
429
|
+
// src/cleanup.ts
|
|
430
|
+
import { readdir as readdir3, stat as stat3, unlink as unlink3, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
|
|
431
|
+
import { spawn } from "child_process";
|
|
432
|
+
import { join as join3 } from "path";
|
|
433
|
+
|
|
434
|
+
// src/internal/process-primitives.ts
|
|
435
|
+
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5, unlink as unlink2 } from "fs/promises";
|
|
436
|
+
import { dirname as dirname5 } from "path";
|
|
220
437
|
async function readPidfileAt(pidfilePath) {
|
|
221
438
|
try {
|
|
222
|
-
const raw = (await
|
|
439
|
+
const raw = (await readFile5(pidfilePath, "utf8")).trim();
|
|
223
440
|
const pid = Number.parseInt(raw, 10);
|
|
224
441
|
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
225
442
|
} catch {
|
|
@@ -236,6 +453,37 @@ function isProcessAlive(pid) {
|
|
|
236
453
|
}
|
|
237
454
|
}
|
|
238
455
|
|
|
456
|
+
// src/cleanup.ts
|
|
457
|
+
var TMP_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
458
|
+
|
|
459
|
+
// src/health.ts
|
|
460
|
+
import { readFile as readFile6, writeFile as writeFile6, unlink as unlink4, mkdir as mkdir7, appendFile as appendFile2 } from "fs/promises";
|
|
461
|
+
import { dirname as dirname6 } from "path";
|
|
462
|
+
var CRASH_WINDOW_MS = 6e4;
|
|
463
|
+
var DEGRADED_THRESHOLD = 3;
|
|
464
|
+
async function readHealthFile(slockHome, serverId) {
|
|
465
|
+
if (!isValidServerId(serverId)) return { crashes: [] };
|
|
466
|
+
try {
|
|
467
|
+
const raw = await readFile6(serverHealthPath(slockHome, serverId), "utf8");
|
|
468
|
+
const parsed = JSON.parse(raw);
|
|
469
|
+
if (parsed && typeof parsed === "object" && Array.isArray(parsed.crashes)) {
|
|
470
|
+
return parsed;
|
|
471
|
+
}
|
|
472
|
+
} catch {
|
|
473
|
+
}
|
|
474
|
+
return { crashes: [] };
|
|
475
|
+
}
|
|
476
|
+
async function isDegraded(slockHome, serverId, nowMs = Date.now()) {
|
|
477
|
+
const file = await readHealthFile(slockHome, serverId);
|
|
478
|
+
if (file.fatalConfig) return true;
|
|
479
|
+
const cutoffMs = nowMs - CRASH_WINDOW_MS;
|
|
480
|
+
const recent = file.crashes.filter((c) => {
|
|
481
|
+
const t = new Date(c.at).getTime();
|
|
482
|
+
return Number.isFinite(t) && t >= cutoffMs;
|
|
483
|
+
});
|
|
484
|
+
return recent.length >= DEGRADED_THRESHOLD;
|
|
485
|
+
}
|
|
486
|
+
|
|
239
487
|
// src/internal/service-pid-fallback.ts
|
|
240
488
|
async function findLiveServicePidReadOnly(slockHome, deps = {}) {
|
|
241
489
|
const readPidfile = deps.readPidfile ?? readPidfileAt;
|
|
@@ -271,38 +519,60 @@ async function walkFallback(slockHome, readPidfile, isAlive, clearStale) {
|
|
|
271
519
|
};
|
|
272
520
|
}
|
|
273
521
|
|
|
274
|
-
// src/
|
|
275
|
-
import {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
var
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
522
|
+
// src/services/detach.ts
|
|
523
|
+
import { fetch as fetch2 } from "undici";
|
|
524
|
+
|
|
525
|
+
// src/setup.ts
|
|
526
|
+
var MIGRATION_FRESH_TRIGGERS = [
|
|
527
|
+
"empty-intersection",
|
|
528
|
+
"explicit-zero",
|
|
529
|
+
"eof",
|
|
530
|
+
"non-tty",
|
|
531
|
+
"server-unavailable"
|
|
532
|
+
];
|
|
533
|
+
async function pickMigrationCandidateFromInput(candidates, read, write) {
|
|
534
|
+
write(
|
|
535
|
+
"Migration: detected legacy daemon(s) on this Computer that match the target server.\n"
|
|
536
|
+
);
|
|
537
|
+
candidates.forEach((c, i) => {
|
|
538
|
+
const host = c.hostname ? ` (${c.hostname})` : "";
|
|
539
|
+
const seen = c.lastSeenAt ? ` last seen ${c.lastSeenAt}` : "";
|
|
540
|
+
write(` ${i + 1}. ${c.machineName}${host}${seen}
|
|
541
|
+
`);
|
|
542
|
+
write(` local: ${c.localPath}
|
|
543
|
+
`);
|
|
544
|
+
});
|
|
545
|
+
write(" 0. Fresh attach (skip migration)\n");
|
|
546
|
+
write(" m. Migrate from a different on-disk path\n");
|
|
547
|
+
while (true) {
|
|
548
|
+
write("Choose [1]: ");
|
|
549
|
+
const { line, eof } = await read();
|
|
550
|
+
if (eof) return { kind: "fresh" };
|
|
551
|
+
const trimmed = line.trim();
|
|
552
|
+
const norm = trimmed.toLowerCase();
|
|
553
|
+
if (norm === "m") {
|
|
554
|
+
write("Path to legacy machine dir or owner.json: ");
|
|
555
|
+
const { line: pathLine } = await read();
|
|
556
|
+
return { kind: "manual", path: pathLine.trim() };
|
|
286
557
|
}
|
|
287
|
-
|
|
558
|
+
if (norm.length === 0) return { kind: "candidate", index: 0 };
|
|
559
|
+
if (norm === "0") return { kind: "fresh" };
|
|
560
|
+
const n = Number.parseInt(norm, 10);
|
|
561
|
+
if (Number.isFinite(n) && String(n) === norm && n >= 1 && n <= candidates.length) {
|
|
562
|
+
return { kind: "candidate", index: n - 1 };
|
|
563
|
+
}
|
|
564
|
+
write(
|
|
565
|
+
`Invalid selection "${trimmed}". Enter 1..${candidates.length}, 0, or m.
|
|
566
|
+
`
|
|
567
|
+
);
|
|
288
568
|
}
|
|
289
|
-
return { crashes: [] };
|
|
290
|
-
}
|
|
291
|
-
async function isDegraded(slockHome, serverId, nowMs = Date.now()) {
|
|
292
|
-
const file = await readHealthFile(slockHome, serverId);
|
|
293
|
-
if (file.fatalConfig) return true;
|
|
294
|
-
const cutoffMs = nowMs - CRASH_WINDOW_MS;
|
|
295
|
-
const recent = file.crashes.filter((c) => {
|
|
296
|
-
const t = new Date(c.at).getTime();
|
|
297
|
-
return Number.isFinite(t) && t >= cutoffMs;
|
|
298
|
-
});
|
|
299
|
-
return recent.length >= DEGRADED_THRESHOLD;
|
|
300
569
|
}
|
|
301
570
|
|
|
302
571
|
// src/status.ts
|
|
572
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
303
573
|
async function readUserSession(path2) {
|
|
304
574
|
try {
|
|
305
|
-
const parsed = JSON.parse(await
|
|
575
|
+
const parsed = JSON.parse(await readFile9(path2, "utf8"));
|
|
306
576
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
307
577
|
return { state: "present", session: parsed, error: null };
|
|
308
578
|
}
|
|
@@ -368,48 +638,6 @@ async function buildStatusReport(installRoot) {
|
|
|
368
638
|
};
|
|
369
639
|
}
|
|
370
640
|
|
|
371
|
-
// src/apiClient.ts
|
|
372
|
-
import { fetch } from "undici";
|
|
373
|
-
var RunnersClient = class {
|
|
374
|
-
constructor(baseUrl, computerApiKey) {
|
|
375
|
-
this.baseUrl = baseUrl;
|
|
376
|
-
this.computerApiKey = computerApiKey;
|
|
377
|
-
}
|
|
378
|
-
url(p) {
|
|
379
|
-
return new URL(p, this.baseUrl).toString();
|
|
380
|
-
}
|
|
381
|
-
async list() {
|
|
382
|
-
const res = await fetch(this.url("/internal/computer/runners"), {
|
|
383
|
-
method: "GET",
|
|
384
|
-
headers: { Authorization: `Bearer ${this.computerApiKey}` }
|
|
385
|
-
});
|
|
386
|
-
const body = await res.json().catch(() => null);
|
|
387
|
-
if (res.status === 200 && body && Array.isArray(body.runners)) {
|
|
388
|
-
return {
|
|
389
|
-
status: "success",
|
|
390
|
-
whitelist: Array.isArray(body.whitelist) ? body.whitelist : [],
|
|
391
|
-
runners: body.runners
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
|
|
395
|
-
const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
|
|
396
|
-
return { status: "error", code };
|
|
397
|
-
}
|
|
398
|
-
async stop(agentId) {
|
|
399
|
-
const res = await fetch(this.url(`/internal/computer/runners/${encodeURIComponent(agentId)}/stop`), {
|
|
400
|
-
method: "POST",
|
|
401
|
-
headers: { Authorization: `Bearer ${this.computerApiKey}`, "Content-Type": "application/json" },
|
|
402
|
-
body: "{}"
|
|
403
|
-
});
|
|
404
|
-
if (res.status === 200) return { status: "success" };
|
|
405
|
-
if (res.status === 404) return { status: "not_found" };
|
|
406
|
-
if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
|
|
407
|
-
const body = await res.json().catch(() => null);
|
|
408
|
-
const code = body && typeof body.code === "string" ? body.code : `http_${res.status}`;
|
|
409
|
-
return { status: "error", code };
|
|
410
|
-
}
|
|
411
|
-
};
|
|
412
|
-
|
|
413
641
|
// src/lib/readers.ts
|
|
414
642
|
async function readServiceStatus(installRoot) {
|
|
415
643
|
return buildStatusReport(installRoot);
|
|
@@ -497,7 +725,7 @@ function isServiceState(value) {
|
|
|
497
725
|
}
|
|
498
726
|
|
|
499
727
|
// src/upgradeLog.ts
|
|
500
|
-
import { chmod as
|
|
728
|
+
import { chmod as chmod5, mkdir as mkdir10, open as open2 } from "fs/promises";
|
|
501
729
|
var UPGRADE_ERROR_CODES = [
|
|
502
730
|
"UPGRADE_DEPS_CHANGED",
|
|
503
731
|
"UPGRADE_NETWORK_FAILED",
|
|
@@ -541,7 +769,9 @@ function assertUpgradeLogEntry(entry) {
|
|
|
541
769
|
}
|
|
542
770
|
export {
|
|
543
771
|
IPC_ERROR_CODES,
|
|
772
|
+
LegacyMachinesClient,
|
|
544
773
|
MIGRATION_DETECTION_KINDS,
|
|
774
|
+
MIGRATION_FRESH_TRIGGERS,
|
|
545
775
|
RUNNER_STATE_VALUES,
|
|
546
776
|
SERVICE_STATE_VALUES,
|
|
547
777
|
STATE_READER_ERROR_CODES,
|
|
@@ -555,6 +785,8 @@ export {
|
|
|
555
785
|
isRunnerState,
|
|
556
786
|
isServiceState,
|
|
557
787
|
listRunners,
|
|
788
|
+
pickMigrationCandidateFromInput,
|
|
558
789
|
readRunnerStatus,
|
|
559
|
-
readServiceStatus
|
|
790
|
+
readServiceStatus,
|
|
791
|
+
validateManualMigratePath
|
|
560
792
|
};
|
package/package.json
CHANGED