@slock-ai/computer 0.0.14 → 0.0.16-alpha.0
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 +809 -533
- package/dist/lib/index.d.ts +340 -36
- package/dist/lib/index.js +407 -9
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -16617,8 +16617,8 @@ var require_snapshot_recorder = __commonJS({
|
|
|
16617
16617
|
"../../node_modules/.pnpm/undici@7.24.8/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
|
|
16618
16618
|
"use strict";
|
|
16619
16619
|
init_esm_shims();
|
|
16620
|
-
var { writeFile:
|
|
16621
|
-
var { dirname:
|
|
16620
|
+
var { writeFile: writeFile13, readFile: readFile17, mkdir: mkdir17 } = __require("fs/promises");
|
|
16621
|
+
var { dirname: dirname14, resolve } = __require("path");
|
|
16622
16622
|
var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
|
|
16623
16623
|
var { InvalidArgumentError: InvalidArgumentError2, UndiciError } = require_errors();
|
|
16624
16624
|
var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
|
|
@@ -16819,7 +16819,7 @@ var require_snapshot_recorder = __commonJS({
|
|
|
16819
16819
|
throw new InvalidArgumentError2("Snapshot path is required");
|
|
16820
16820
|
}
|
|
16821
16821
|
try {
|
|
16822
|
-
const data = await
|
|
16822
|
+
const data = await readFile17(resolve(path3), "utf8");
|
|
16823
16823
|
const parsed = JSON.parse(data);
|
|
16824
16824
|
if (Array.isArray(parsed)) {
|
|
16825
16825
|
this.#snapshots.clear();
|
|
@@ -16849,12 +16849,12 @@ var require_snapshot_recorder = __commonJS({
|
|
|
16849
16849
|
throw new InvalidArgumentError2("Snapshot path is required");
|
|
16850
16850
|
}
|
|
16851
16851
|
const resolvedPath = resolve(path3);
|
|
16852
|
-
await
|
|
16852
|
+
await mkdir17(dirname14(resolvedPath), { recursive: true });
|
|
16853
16853
|
const data = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
|
|
16854
16854
|
hash,
|
|
16855
16855
|
snapshot
|
|
16856
16856
|
}));
|
|
16857
|
-
await
|
|
16857
|
+
await writeFile13(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
|
|
16858
16858
|
}
|
|
16859
16859
|
/**
|
|
16860
16860
|
* Clears all recorded snapshots
|
|
@@ -28469,8 +28469,8 @@ var require_graceful_fs = __commonJS({
|
|
|
28469
28469
|
fs2.createReadStream = createReadStream;
|
|
28470
28470
|
fs2.createWriteStream = createWriteStream;
|
|
28471
28471
|
var fs$readFile = fs2.readFile;
|
|
28472
|
-
fs2.readFile =
|
|
28473
|
-
function
|
|
28472
|
+
fs2.readFile = readFile17;
|
|
28473
|
+
function readFile17(path3, options, cb) {
|
|
28474
28474
|
if (typeof options === "function")
|
|
28475
28475
|
cb = options, options = null;
|
|
28476
28476
|
return go$readFile(path3, options, cb);
|
|
@@ -28486,8 +28486,8 @@ var require_graceful_fs = __commonJS({
|
|
|
28486
28486
|
}
|
|
28487
28487
|
}
|
|
28488
28488
|
var fs$writeFile = fs2.writeFile;
|
|
28489
|
-
fs2.writeFile =
|
|
28490
|
-
function
|
|
28489
|
+
fs2.writeFile = writeFile13;
|
|
28490
|
+
function writeFile13(path3, data, options, cb) {
|
|
28491
28491
|
if (typeof options === "function")
|
|
28492
28492
|
cb = options, options = null;
|
|
28493
28493
|
return go$writeFile(path3, data, options, cb);
|
|
@@ -28504,8 +28504,8 @@ var require_graceful_fs = __commonJS({
|
|
|
28504
28504
|
}
|
|
28505
28505
|
var fs$appendFile = fs2.appendFile;
|
|
28506
28506
|
if (fs$appendFile)
|
|
28507
|
-
fs2.appendFile =
|
|
28508
|
-
function
|
|
28507
|
+
fs2.appendFile = appendFile4;
|
|
28508
|
+
function appendFile4(path3, data, options, cb) {
|
|
28509
28509
|
if (typeof options === "function")
|
|
28510
28510
|
cb = options, options = null;
|
|
28511
28511
|
return go$appendFile(path3, data, options, cb);
|
|
@@ -29737,7 +29737,7 @@ var ComputerAttachClient = class {
|
|
|
29737
29737
|
* over the machine. On success the server marks the machine row migrated
|
|
29738
29738
|
* and mints a fresh `sk_computer_*`. The raw legacy key MUST NOT be
|
|
29739
29739
|
* persisted by the caller — only the freshly-minted sk_computer_* lands
|
|
29740
|
-
* in
|
|
29740
|
+
* in runner.state.json.
|
|
29741
29741
|
*/
|
|
29742
29742
|
async adoptLegacy(legacyApiKey, name) {
|
|
29743
29743
|
const res = await (0, import_undici.fetch)(this.url("/api/computer/adopt-legacy"), {
|
|
@@ -29877,7 +29877,7 @@ function serverDir(slockHome, serverId) {
|
|
|
29877
29877
|
return path2.join(serversDir(slockHome), assertValidServerId(serverId));
|
|
29878
29878
|
}
|
|
29879
29879
|
function serverAttachmentPath(slockHome, serverId) {
|
|
29880
|
-
return path2.join(serverDir(slockHome, serverId), "
|
|
29880
|
+
return path2.join(serverDir(slockHome, serverId), "runner.state.json");
|
|
29881
29881
|
}
|
|
29882
29882
|
function serverDaemonPidPath(slockHome, serverId) {
|
|
29883
29883
|
return path2.join(serverDir(slockHome, serverId), "daemon.pid");
|
|
@@ -29891,14 +29891,20 @@ function serverManagedFlagPath(slockHome, serverId) {
|
|
|
29891
29891
|
function serverHealthPath(slockHome, serverId) {
|
|
29892
29892
|
return path2.join(serverDir(slockHome, serverId), "health.json");
|
|
29893
29893
|
}
|
|
29894
|
-
function
|
|
29894
|
+
function serviceStatePath(slockHome) {
|
|
29895
|
+
return path2.join(computerDir(slockHome), "service.state.json");
|
|
29896
|
+
}
|
|
29897
|
+
function servicePidPath(slockHome) {
|
|
29898
|
+
return path2.join(computerDir(slockHome), "service.pid");
|
|
29899
|
+
}
|
|
29900
|
+
function legacySupervisorPidPath(slockHome) {
|
|
29895
29901
|
return path2.join(computerDir(slockHome), "supervisor.pid");
|
|
29896
29902
|
}
|
|
29897
|
-
function
|
|
29898
|
-
return path2.join(computerDir(slockHome), "
|
|
29903
|
+
function serviceLogPath(slockHome) {
|
|
29904
|
+
return path2.join(computerDir(slockHome), "service.log");
|
|
29899
29905
|
}
|
|
29900
|
-
function
|
|
29901
|
-
return path2.join(computerDir(slockHome), "
|
|
29906
|
+
function serviceVersionPath(slockHome) {
|
|
29907
|
+
return path2.join(computerDir(slockHome), "service-version.json");
|
|
29902
29908
|
}
|
|
29903
29909
|
var HOSTNAME_SUFFIXES = [
|
|
29904
29910
|
".fritz.box",
|
|
@@ -30376,15 +30382,78 @@ async function runAttach(opts) {
|
|
|
30376
30382
|
}
|
|
30377
30383
|
}
|
|
30378
30384
|
|
|
30379
|
-
// src/
|
|
30385
|
+
// src/setup.ts
|
|
30380
30386
|
init_esm_shims();
|
|
30381
|
-
|
|
30387
|
+
var import_undici3 = __toESM(require_undici(), 1);
|
|
30388
|
+
import { chmod as chmod4, mkdir as mkdir9, readFile as readFile8, rename as rename3, rm as rm2, writeFile as writeFile8 } from "fs/promises";
|
|
30389
|
+
import { dirname as dirname9 } from "path";
|
|
30390
|
+
|
|
30391
|
+
// src/lib/migration.ts
|
|
30392
|
+
init_esm_shims();
|
|
30393
|
+
import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
|
|
30394
|
+
import { join } from "path";
|
|
30395
|
+
var MACHINE_DIR_PREFIX = "machine-";
|
|
30396
|
+
async function readOwnerEvidence(installRoot, machineDirName) {
|
|
30397
|
+
const ownerFile = join(
|
|
30398
|
+
installRoot,
|
|
30399
|
+
"machines",
|
|
30400
|
+
machineDirName,
|
|
30401
|
+
"daemon.lock",
|
|
30402
|
+
"owner.json"
|
|
30403
|
+
);
|
|
30404
|
+
let raw;
|
|
30405
|
+
try {
|
|
30406
|
+
raw = await readFile3(ownerFile, "utf8");
|
|
30407
|
+
} catch {
|
|
30408
|
+
return {};
|
|
30409
|
+
}
|
|
30410
|
+
let parsed;
|
|
30411
|
+
try {
|
|
30412
|
+
parsed = JSON.parse(raw);
|
|
30413
|
+
} catch {
|
|
30414
|
+
return {};
|
|
30415
|
+
}
|
|
30416
|
+
const machineName = typeof parsed.hostname === "string" && parsed.hostname.length > 0 ? parsed.hostname : void 0;
|
|
30417
|
+
const serverUrl = typeof parsed.serverUrl === "string" && parsed.serverUrl.length > 0 ? parsed.serverUrl : void 0;
|
|
30418
|
+
return { machineName, serverUrl };
|
|
30419
|
+
}
|
|
30420
|
+
async function detectLegacyMigration(installRoot, loggedInUserId) {
|
|
30421
|
+
void loggedInUserId;
|
|
30422
|
+
const machinesDir = join(installRoot, "machines");
|
|
30423
|
+
let entries;
|
|
30424
|
+
try {
|
|
30425
|
+
entries = await readdir2(machinesDir);
|
|
30426
|
+
} catch {
|
|
30427
|
+
return { kind: "no-match" };
|
|
30428
|
+
}
|
|
30429
|
+
const candidates = [];
|
|
30430
|
+
for (const name of entries) {
|
|
30431
|
+
if (!name.startsWith(MACHINE_DIR_PREFIX)) continue;
|
|
30432
|
+
const evidence = await readOwnerEvidence(installRoot, name);
|
|
30433
|
+
candidates.push({
|
|
30434
|
+
machineId: name,
|
|
30435
|
+
machineName: evidence.machineName,
|
|
30436
|
+
serverUrl: evidence.serverUrl
|
|
30437
|
+
});
|
|
30438
|
+
}
|
|
30439
|
+
if (candidates.length === 0) return { kind: "no-match" };
|
|
30440
|
+
if (candidates.length === 1) {
|
|
30441
|
+
const only = candidates[0];
|
|
30442
|
+
return {
|
|
30443
|
+
kind: "match",
|
|
30444
|
+
machineId: only.machineId,
|
|
30445
|
+
machineName: only.machineName,
|
|
30446
|
+
serverUrl: only.serverUrl
|
|
30447
|
+
};
|
|
30448
|
+
}
|
|
30449
|
+
return { kind: "ambiguous", candidates };
|
|
30450
|
+
}
|
|
30382
30451
|
|
|
30383
30452
|
// src/services/adoptLegacy.ts
|
|
30384
30453
|
init_esm_shims();
|
|
30385
|
-
import { chmod as chmod3, mkdir as mkdir4, readFile as
|
|
30454
|
+
import { chmod as chmod3, mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4, appendFile, stat } from "fs/promises";
|
|
30386
30455
|
import { createHash as createHash2 } from "crypto";
|
|
30387
|
-
import { dirname as dirname4, join } from "path";
|
|
30456
|
+
import { dirname as dirname4, join as join2 } from "path";
|
|
30388
30457
|
import { setTimeout as delay } from "timers/promises";
|
|
30389
30458
|
function emit3(opts, event) {
|
|
30390
30459
|
const cb = opts?.onEvent;
|
|
@@ -30398,7 +30467,7 @@ var LEGACY_STOP_WAIT_MS = 1e4;
|
|
|
30398
30467
|
var LEGACY_STOP_POLL_MS = 200;
|
|
30399
30468
|
function legacyLockOwnerPath(slockHome, rawKey) {
|
|
30400
30469
|
const fingerprint = createHash2("sha256").update(rawKey).digest("hex").slice(0, 16);
|
|
30401
|
-
return
|
|
30470
|
+
return join2(slockHome, "machines", `machine-${fingerprint}`, "daemon.lock", "owner.json");
|
|
30402
30471
|
}
|
|
30403
30472
|
function isProcessAlive(pid) {
|
|
30404
30473
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -30413,7 +30482,7 @@ function isProcessAlive(pid) {
|
|
|
30413
30482
|
async function stopLegacyDaemonByOwnerFile(ownerFile) {
|
|
30414
30483
|
let raw;
|
|
30415
30484
|
try {
|
|
30416
|
-
raw = await
|
|
30485
|
+
raw = await readFile4(ownerFile, "utf8");
|
|
30417
30486
|
} catch {
|
|
30418
30487
|
return { attempted: false, outcome: "absent" };
|
|
30419
30488
|
}
|
|
@@ -30447,7 +30516,7 @@ async function stopLegacyDaemonByOwnerFile(ownerFile) {
|
|
|
30447
30516
|
async function readLegacyOwnerEvidence(ownerFile) {
|
|
30448
30517
|
let raw;
|
|
30449
30518
|
try {
|
|
30450
|
-
raw = await
|
|
30519
|
+
raw = await readFile4(ownerFile, "utf8");
|
|
30451
30520
|
} catch {
|
|
30452
30521
|
return { ownerFile, reason: "owner_file_absent" };
|
|
30453
30522
|
}
|
|
@@ -30487,7 +30556,7 @@ async function adoptLegacy(input, options = {}) {
|
|
|
30487
30556
|
const sessionFile = userSessionPath(slockHome);
|
|
30488
30557
|
let session;
|
|
30489
30558
|
try {
|
|
30490
|
-
session = JSON.parse(await
|
|
30559
|
+
session = JSON.parse(await readFile4(sessionFile, "utf8"));
|
|
30491
30560
|
} catch (err) {
|
|
30492
30561
|
throw new ComputerServiceError(
|
|
30493
30562
|
"NO_USER_SESSION",
|
|
@@ -30733,134 +30802,18 @@ async function appendAdoptionLog(slockHome, line) {
|
|
|
30733
30802
|
}
|
|
30734
30803
|
}
|
|
30735
30804
|
|
|
30736
|
-
// src/
|
|
30737
|
-
|
|
30738
|
-
|
|
30739
|
-
|
|
30740
|
-
|
|
30741
|
-
|
|
30742
|
-
load: async () => inputs.legacyApiKey.trim()
|
|
30743
|
-
});
|
|
30744
|
-
}
|
|
30745
|
-
if (typeof inputs.legacyApiKeyFile === "string" && inputs.legacyApiKeyFile.length > 0) {
|
|
30746
|
-
sources.push({
|
|
30747
|
-
mode: "legacy_key_file",
|
|
30748
|
-
load: async () => {
|
|
30749
|
-
const contents = await readFile4(inputs.legacyApiKeyFile, "utf8");
|
|
30750
|
-
return contents.trim();
|
|
30751
|
-
}
|
|
30752
|
-
});
|
|
30753
|
-
}
|
|
30754
|
-
if (inputs.legacyApiKeyStdin) {
|
|
30755
|
-
sources.push({
|
|
30756
|
-
mode: "legacy_key_stdin",
|
|
30757
|
-
load: async () => readAllStdin()
|
|
30758
|
-
});
|
|
30759
|
-
}
|
|
30760
|
-
const envKey = env.SLOCK_LEGACY_API_KEY;
|
|
30761
|
-
if (typeof envKey === "string" && envKey.trim().length > 0) {
|
|
30762
|
-
sources.push({
|
|
30763
|
-
mode: "legacy_key_env",
|
|
30764
|
-
load: async () => envKey.trim()
|
|
30765
|
-
});
|
|
30766
|
-
}
|
|
30767
|
-
if (sources.length === 0) {
|
|
30768
|
-
fail(
|
|
30769
|
-
"LEGACY_KEY_REQUIRED",
|
|
30770
|
-
"No legacy api key provided. Pass exactly one of: --legacy-api-key <key>, --legacy-api-key-file <path>, --legacy-api-key-stdin, or SLOCK_LEGACY_API_KEY."
|
|
30771
|
-
);
|
|
30772
|
-
}
|
|
30773
|
-
if (sources.length > 1) {
|
|
30774
|
-
const modes = sources.map((s) => s.mode).join(", ");
|
|
30775
|
-
fail(
|
|
30776
|
-
"LEGACY_KEY_MULTIPLE_SOURCES",
|
|
30777
|
-
`Multiple legacy api key sources provided (${modes}). Choose exactly one.`
|
|
30778
|
-
);
|
|
30779
|
-
}
|
|
30780
|
-
const chosen = sources[0];
|
|
30781
|
-
const rawKey = await chosen.load();
|
|
30782
|
-
if (chosen.mode === "legacy_key_env") {
|
|
30783
|
-
delete env.SLOCK_LEGACY_API_KEY;
|
|
30784
|
-
}
|
|
30785
|
-
if (!rawKey.startsWith("sk_machine_") && !rawKey.startsWith("sk_daemon_")) {
|
|
30786
|
-
fail(
|
|
30787
|
-
"LEGACY_KEY_INVALID",
|
|
30788
|
-
"Provided key does not look like a legacy machine key (expected `sk_machine_*` or `sk_daemon_*`)."
|
|
30789
|
-
);
|
|
30790
|
-
}
|
|
30791
|
-
return {
|
|
30792
|
-
rawKey,
|
|
30793
|
-
mode: chosen.mode,
|
|
30794
|
-
redactedPrefix: rawKey.slice(0, 8)
|
|
30795
|
-
};
|
|
30796
|
-
}
|
|
30797
|
-
async function readAllStdin() {
|
|
30798
|
-
return new Promise((resolve, reject) => {
|
|
30799
|
-
const chunks = [];
|
|
30800
|
-
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
30801
|
-
process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8").trim()));
|
|
30802
|
-
process.stdin.on("error", reject);
|
|
30803
|
-
});
|
|
30804
|
-
}
|
|
30805
|
-
async function runAdoptLegacy(inputs) {
|
|
30806
|
-
const legacy = await resolveLegacyKey(inputs, process.env);
|
|
30807
|
-
try {
|
|
30808
|
-
await adoptLegacy(
|
|
30809
|
-
{
|
|
30810
|
-
serverSlug: inputs.serverSlug,
|
|
30811
|
-
serverUrl: inputs.serverUrl,
|
|
30812
|
-
name: inputs.name,
|
|
30813
|
-
rawKey: legacy.rawKey,
|
|
30814
|
-
mode: legacy.mode,
|
|
30815
|
-
redactedPrefix: legacy.redactedPrefix
|
|
30816
|
-
},
|
|
30817
|
-
{
|
|
30818
|
-
onEvent: (event) => {
|
|
30819
|
-
if (event.type === "adopting") {
|
|
30820
|
-
info(
|
|
30821
|
-
`Adopting legacy daemon for ${formatServerSlugDisplay(event.serverSlug)} via ${event.mode}\u2026`
|
|
30822
|
-
);
|
|
30823
|
-
} else if (event.type === "preflight") {
|
|
30824
|
-
info(event.resumed ? "Adopted (resumed prior attachment); running preflight\u2026" : "Adopted; running preflight\u2026");
|
|
30825
|
-
} else if (event.type === "adopted") {
|
|
30826
|
-
info(`Adopted. Computer state written to ${event.attachmentPath}`);
|
|
30827
|
-
info(` server: ${formatServerSlugDisplay(event.serverSlug)}`);
|
|
30828
|
-
info(` serverMachine: ${event.serverMachineId}`);
|
|
30829
|
-
info(` legacyMachine: ${event.legacyMachineId}`);
|
|
30830
|
-
switch (event.legacyStop.outcome) {
|
|
30831
|
-
case "absent":
|
|
30832
|
-
info(" legacy daemon: not detected on this Computer (no local lock file)");
|
|
30833
|
-
break;
|
|
30834
|
-
case "already_dead":
|
|
30835
|
-
info(` legacy daemon: already stopped (pid ${event.legacyStop.pid} not running)`);
|
|
30836
|
-
break;
|
|
30837
|
-
case "stopped":
|
|
30838
|
-
info(` legacy daemon: stopped (pid ${event.legacyStop.pid}, SIGTERM)`);
|
|
30839
|
-
break;
|
|
30840
|
-
}
|
|
30841
|
-
}
|
|
30842
|
-
}
|
|
30843
|
-
}
|
|
30844
|
-
);
|
|
30845
|
-
} catch (err) {
|
|
30846
|
-
if (err instanceof CliExit) throw err;
|
|
30847
|
-
if (err instanceof ComputerServiceError) {
|
|
30848
|
-
fail(err.code, err.message);
|
|
30849
|
-
}
|
|
30850
|
-
throw err;
|
|
30851
|
-
} finally {
|
|
30852
|
-
legacy.rawKey = void 0;
|
|
30853
|
-
}
|
|
30854
|
-
if (!inputs.orchestrated) {
|
|
30855
|
-
info(`Next: run \`slock-computer start\` to bring this server online under the Computer supervisor.`);
|
|
30856
|
-
}
|
|
30857
|
-
}
|
|
30805
|
+
// src/service.ts
|
|
30806
|
+
init_esm_shims();
|
|
30807
|
+
import { spawn as spawn2 } from "child_process";
|
|
30808
|
+
import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile7, open, rename as rename2 } from "fs/promises";
|
|
30809
|
+
import { dirname as dirname8, join as joinPath } from "path";
|
|
30810
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
30858
30811
|
|
|
30859
|
-
// src/
|
|
30812
|
+
// src/cleanup.ts
|
|
30860
30813
|
init_esm_shims();
|
|
30861
|
-
|
|
30862
|
-
import {
|
|
30863
|
-
import {
|
|
30814
|
+
import { readdir as readdir3, stat as stat2, unlink as unlink3, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
|
|
30815
|
+
import { spawn } from "child_process";
|
|
30816
|
+
import { join as join3 } from "path";
|
|
30864
30817
|
|
|
30865
30818
|
// src/internal/process-primitives.ts
|
|
30866
30819
|
init_esm_shims();
|
|
@@ -30895,18 +30848,7 @@ async function clearPidfileAt(pidfilePath) {
|
|
|
30895
30848
|
}
|
|
30896
30849
|
}
|
|
30897
30850
|
|
|
30898
|
-
// src/supervisor.ts
|
|
30899
|
-
init_esm_shims();
|
|
30900
|
-
import { spawn as spawn2 } from "child_process";
|
|
30901
|
-
import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile7, open, rename as rename2 } from "fs/promises";
|
|
30902
|
-
import { dirname as dirname8, join as joinPath } from "path";
|
|
30903
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
30904
|
-
|
|
30905
30851
|
// src/cleanup.ts
|
|
30906
|
-
init_esm_shims();
|
|
30907
|
-
import { readdir as readdir2, stat as stat2, unlink as unlink3, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
|
|
30908
|
-
import { spawn } from "child_process";
|
|
30909
|
-
import { join as join2 } from "path";
|
|
30910
30852
|
function emptyCleanupReport() {
|
|
30911
30853
|
return {
|
|
30912
30854
|
stalePidfiles: [],
|
|
@@ -30930,8 +30872,8 @@ async function cleanupStalePidfile(pidfilePath) {
|
|
|
30930
30872
|
}
|
|
30931
30873
|
async function cleanupAllStalePidfiles(slockHome) {
|
|
30932
30874
|
const cleaned = [];
|
|
30933
|
-
if (await cleanupStalePidfile(
|
|
30934
|
-
cleaned.push(
|
|
30875
|
+
if (await cleanupStalePidfile(servicePidPath(slockHome))) {
|
|
30876
|
+
cleaned.push(servicePidPath(slockHome));
|
|
30935
30877
|
}
|
|
30936
30878
|
const attached = await listAttachedServerIds(slockHome);
|
|
30937
30879
|
for (const sid of attached) {
|
|
@@ -30980,7 +30922,7 @@ async function cleanupOrphanProcesses(slockHome, psSpawn) {
|
|
|
30980
30922
|
const managed = new Set(await listManagedServerIds(slockHome));
|
|
30981
30923
|
const signaled = [];
|
|
30982
30924
|
const knownPids = /* @__PURE__ */ new Set();
|
|
30983
|
-
const supPid = await readPidfileAt(
|
|
30925
|
+
const supPid = await readPidfileAt(servicePidPath(slockHome));
|
|
30984
30926
|
if (supPid !== null) knownPids.add(supPid);
|
|
30985
30927
|
for (const sid of managed) {
|
|
30986
30928
|
const pid = await readPidfileAt(serverDaemonPidPath(slockHome, sid));
|
|
@@ -31001,12 +30943,12 @@ async function cleanupOrphanProcesses(slockHome, psSpawn) {
|
|
|
31001
30943
|
return signaled;
|
|
31002
30944
|
}
|
|
31003
30945
|
function quarantineDir(slockHome) {
|
|
31004
|
-
return
|
|
30946
|
+
return join3(computerDir(slockHome), ".quarantine");
|
|
31005
30947
|
}
|
|
31006
30948
|
async function quarantineServerSubtree(slockHome, serverId) {
|
|
31007
|
-
const src =
|
|
30949
|
+
const src = join3(serversDir(slockHome), serverId);
|
|
31008
30950
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
31009
|
-
const dest =
|
|
30951
|
+
const dest = join3(quarantineDir(slockHome), `${stamp}-${serverId}`);
|
|
31010
30952
|
await mkdir6(dirname6(dest), { recursive: true });
|
|
31011
30953
|
await rename(src, dest);
|
|
31012
30954
|
return dest;
|
|
@@ -31018,7 +30960,7 @@ function dirname6(p) {
|
|
|
31018
30960
|
async function cleanupPowerLossPartialState(slockHome) {
|
|
31019
30961
|
let entries;
|
|
31020
30962
|
try {
|
|
31021
|
-
entries = await
|
|
30963
|
+
entries = await readdir3(serversDir(slockHome));
|
|
31022
30964
|
} catch {
|
|
31023
30965
|
return [];
|
|
31024
30966
|
}
|
|
@@ -31045,11 +30987,11 @@ var TMP_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
|
31045
30987
|
async function cleanupTmpFiles(slockHome) {
|
|
31046
30988
|
const removed = [];
|
|
31047
30989
|
const cdir = computerDir(slockHome);
|
|
31048
|
-
const stagingDir =
|
|
30990
|
+
const stagingDir = join3(cdir, "upgrade-staging");
|
|
31049
30991
|
try {
|
|
31050
|
-
const versions = await
|
|
30992
|
+
const versions = await readdir3(stagingDir);
|
|
31051
30993
|
for (const v of versions) {
|
|
31052
|
-
const vdir =
|
|
30994
|
+
const vdir = join3(stagingDir, v);
|
|
31053
30995
|
try {
|
|
31054
30996
|
const s = await stat2(vdir);
|
|
31055
30997
|
if (Date.now() - s.mtimeMs > TMP_MAX_AGE_MS) {
|
|
@@ -31060,13 +31002,13 @@ async function cleanupTmpFiles(slockHome) {
|
|
|
31060
31002
|
}
|
|
31061
31003
|
}
|
|
31062
31004
|
try {
|
|
31063
|
-
const remaining = await
|
|
31005
|
+
const remaining = await readdir3(stagingDir);
|
|
31064
31006
|
if (remaining.length === 0) await rmdir(stagingDir);
|
|
31065
31007
|
} catch {
|
|
31066
31008
|
}
|
|
31067
31009
|
} catch {
|
|
31068
31010
|
}
|
|
31069
|
-
const snap =
|
|
31011
|
+
const snap = join3(cdir, "upgrade-snapshot.json");
|
|
31070
31012
|
try {
|
|
31071
31013
|
const s = await stat2(snap);
|
|
31072
31014
|
if (Date.now() - s.mtimeMs > TMP_MAX_AGE_MS) {
|
|
@@ -31078,7 +31020,7 @@ async function cleanupTmpFiles(slockHome) {
|
|
|
31078
31020
|
return removed;
|
|
31079
31021
|
}
|
|
31080
31022
|
async function cleanupStaleLock(slockHome) {
|
|
31081
|
-
const lockDir =
|
|
31023
|
+
const lockDir = join3(computerDir(slockHome), ".lock");
|
|
31082
31024
|
try {
|
|
31083
31025
|
const s = await stat2(lockDir);
|
|
31084
31026
|
if (Date.now() - s.mtimeMs > 60 * 1e3) {
|
|
@@ -31090,7 +31032,7 @@ async function cleanupStaleLock(slockHome) {
|
|
|
31090
31032
|
return [];
|
|
31091
31033
|
}
|
|
31092
31034
|
async function forceReleaseLock(slockHome) {
|
|
31093
|
-
const lockDir =
|
|
31035
|
+
const lockDir = join3(computerDir(slockHome), ".lock");
|
|
31094
31036
|
try {
|
|
31095
31037
|
await stat2(lockDir);
|
|
31096
31038
|
await rm(lockDir, { recursive: true, force: true });
|
|
@@ -31114,7 +31056,7 @@ async function runFullCleanup(slockHome, options = {}) {
|
|
|
31114
31056
|
|
|
31115
31057
|
// src/health.ts
|
|
31116
31058
|
init_esm_shims();
|
|
31117
|
-
import { readFile as readFile6, writeFile as writeFile6, unlink as unlink4, mkdir as mkdir7 } from "fs/promises";
|
|
31059
|
+
import { readFile as readFile6, writeFile as writeFile6, unlink as unlink4, mkdir as mkdir7, appendFile as appendFile2 } from "fs/promises";
|
|
31118
31060
|
import { dirname as dirname7 } from "path";
|
|
31119
31061
|
var CRASH_WINDOW_MS = 6e4;
|
|
31120
31062
|
var DEGRADED_THRESHOLD = 3;
|
|
@@ -31186,6 +31128,43 @@ async function resetHealth(slockHome, serverId) {
|
|
|
31186
31128
|
} catch {
|
|
31187
31129
|
}
|
|
31188
31130
|
}
|
|
31131
|
+
async function resetRunnerHealth(slockHome, serverId, nowMs = Date.now()) {
|
|
31132
|
+
if (!isValidServerId(serverId)) {
|
|
31133
|
+
return { status: "not-found" };
|
|
31134
|
+
}
|
|
31135
|
+
const wasDegraded = await isDegraded(slockHome, serverId, nowMs);
|
|
31136
|
+
const recent = await readCrashHistory(slockHome, serverId, nowMs);
|
|
31137
|
+
const previousState = wasDegraded ? "degraded" : "running";
|
|
31138
|
+
await resetHealth(slockHome, serverId);
|
|
31139
|
+
await emitRunnerStateTransition(
|
|
31140
|
+
slockHome,
|
|
31141
|
+
serverId,
|
|
31142
|
+
previousState,
|
|
31143
|
+
"running",
|
|
31144
|
+
"reset-runner"
|
|
31145
|
+
);
|
|
31146
|
+
return {
|
|
31147
|
+
status: "ok",
|
|
31148
|
+
previousState,
|
|
31149
|
+
clearedCrashCount: recent.length
|
|
31150
|
+
};
|
|
31151
|
+
}
|
|
31152
|
+
async function emitRunnerStateTransition(slockHome, serverId, fromState, toState, trigger) {
|
|
31153
|
+
const entry = {
|
|
31154
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31155
|
+
kind: "runner-state-changed",
|
|
31156
|
+
serverId,
|
|
31157
|
+
fromState,
|
|
31158
|
+
toState,
|
|
31159
|
+
trigger
|
|
31160
|
+
};
|
|
31161
|
+
try {
|
|
31162
|
+
const path3 = serviceLogPath(slockHome);
|
|
31163
|
+
await mkdir7(dirname7(path3), { recursive: true });
|
|
31164
|
+
await appendFile2(path3, JSON.stringify(entry) + "\n");
|
|
31165
|
+
} catch {
|
|
31166
|
+
}
|
|
31167
|
+
}
|
|
31189
31168
|
|
|
31190
31169
|
// src/services/start.ts
|
|
31191
31170
|
init_esm_shims();
|
|
@@ -31223,7 +31202,7 @@ async function waitForManagedDaemonPids(slockHome, serverIds, opts) {
|
|
|
31223
31202
|
function buildTimeoutMessage(slockHome, serverIds, ready, input) {
|
|
31224
31203
|
const missing = serverIds.filter((id) => !ready.has(id));
|
|
31225
31204
|
const target = input.serverId && missing.length === 1 ? `${input.serverLabel ?? input.serverId}` : `${missing.length} daemon(s): ${missing.join(", ")}`;
|
|
31226
|
-
return `Timed out waiting for ${target} to start. Run \`slock-computer status\` and inspect ${
|
|
31205
|
+
return `Timed out waiting for ${target} to start. Run \`slock-computer status\` and inspect ${serviceLogPath(slockHome)} plus per-server daemon logs under ~/.slock/computer/servers/<serverId>/daemon.log.`;
|
|
31227
31206
|
}
|
|
31228
31207
|
async function start(input, options = {}) {
|
|
31229
31208
|
options.signal?.throwIfAborted?.();
|
|
@@ -31251,11 +31230,11 @@ async function start(input, options = {}) {
|
|
|
31251
31230
|
for (const id of managedTargets) {
|
|
31252
31231
|
await setServerManaged(slockHome, id);
|
|
31253
31232
|
}
|
|
31254
|
-
const existing = await readPidfileAt(
|
|
31233
|
+
const existing = await readPidfileAt(servicePidPath(slockHome));
|
|
31255
31234
|
if (existing && isProcessAlive2(existing)) {
|
|
31256
31235
|
emit4(options, {
|
|
31257
31236
|
type: "already_running",
|
|
31258
|
-
|
|
31237
|
+
servicePid: existing,
|
|
31259
31238
|
managedTargets,
|
|
31260
31239
|
attachedCount: attached.length
|
|
31261
31240
|
});
|
|
@@ -31272,8 +31251,8 @@ async function start(input, options = {}) {
|
|
|
31272
31251
|
managedTargets,
|
|
31273
31252
|
attachedCount: attached.length,
|
|
31274
31253
|
ready: ready2,
|
|
31275
|
-
|
|
31276
|
-
|
|
31254
|
+
servicePid: existing,
|
|
31255
|
+
serviceLogPath: serviceLogPath(slockHome)
|
|
31277
31256
|
};
|
|
31278
31257
|
}
|
|
31279
31258
|
if (input.foreground) {
|
|
@@ -31282,41 +31261,48 @@ async function start(input, options = {}) {
|
|
|
31282
31261
|
managedTargets,
|
|
31283
31262
|
attachedCount: attached.length
|
|
31284
31263
|
});
|
|
31285
|
-
const
|
|
31286
|
-
|
|
31264
|
+
const service = options.runService ?? runService;
|
|
31265
|
+
const previousParentLockMarker = process.env[PARENT_LOCK_HELD_ENV_VAR];
|
|
31266
|
+
process.env[PARENT_LOCK_HELD_ENV_VAR] = "1";
|
|
31267
|
+
try {
|
|
31268
|
+
await service();
|
|
31269
|
+
} finally {
|
|
31270
|
+
if (previousParentLockMarker === void 0) delete process.env[PARENT_LOCK_HELD_ENV_VAR];
|
|
31271
|
+
else process.env[PARENT_LOCK_HELD_ENV_VAR] = previousParentLockMarker;
|
|
31272
|
+
}
|
|
31287
31273
|
return {
|
|
31288
31274
|
status: "running",
|
|
31289
31275
|
managedTargets,
|
|
31290
31276
|
attachedCount: attached.length,
|
|
31291
31277
|
ready: /* @__PURE__ */ new Map(),
|
|
31292
|
-
|
|
31293
|
-
|
|
31278
|
+
servicePid: null,
|
|
31279
|
+
serviceLogPath: serviceLogPath(slockHome)
|
|
31294
31280
|
};
|
|
31295
31281
|
}
|
|
31296
31282
|
options.signal?.throwIfAborted?.();
|
|
31297
31283
|
let pid;
|
|
31298
31284
|
try {
|
|
31299
|
-
pid = await (options.
|
|
31285
|
+
pid = await (options.spawnDetachedService ?? spawnDetachedService)(slockHome);
|
|
31300
31286
|
} catch (err) {
|
|
31301
31287
|
const msg = err instanceof Error ? err.message : String(err);
|
|
31302
31288
|
throw new ComputerServiceError("SUPERVISOR_SPAWN_FAILED", msg, err);
|
|
31303
31289
|
}
|
|
31304
31290
|
emit4(options, {
|
|
31305
31291
|
type: "spawned",
|
|
31306
|
-
|
|
31292
|
+
servicePid: pid,
|
|
31307
31293
|
managedTargets,
|
|
31308
31294
|
attachedCount: attached.length
|
|
31309
31295
|
});
|
|
31310
31296
|
if (options.signal?.aborted) {
|
|
31311
31297
|
const ready2 = await pollReadyOnce(slockHome, managedTargets, options);
|
|
31312
|
-
emit4(options, { type: "aborted",
|
|
31298
|
+
emit4(options, { type: "aborted", servicePid: pid, managedTargets, ready: ready2 });
|
|
31313
31299
|
return {
|
|
31314
31300
|
status: "aborted",
|
|
31315
31301
|
managedTargets,
|
|
31316
31302
|
attachedCount: attached.length,
|
|
31317
31303
|
ready: ready2,
|
|
31318
|
-
|
|
31319
|
-
|
|
31304
|
+
servicePid: pid,
|
|
31305
|
+
serviceLogPath: serviceLogPath(slockHome)
|
|
31320
31306
|
};
|
|
31321
31307
|
}
|
|
31322
31308
|
const ready = await waitForManagedDaemonPids(slockHome, managedTargets, options);
|
|
@@ -31332,8 +31318,8 @@ async function start(input, options = {}) {
|
|
|
31332
31318
|
managedTargets,
|
|
31333
31319
|
attachedCount: attached.length,
|
|
31334
31320
|
ready,
|
|
31335
|
-
|
|
31336
|
-
|
|
31321
|
+
servicePid: pid,
|
|
31322
|
+
serviceLogPath: serviceLogPath(slockHome)
|
|
31337
31323
|
};
|
|
31338
31324
|
}
|
|
31339
31325
|
async function pollReadyOnce(slockHome, serverIds, opts) {
|
|
@@ -31365,14 +31351,22 @@ async function stop(input = {}, options = {}) {
|
|
|
31365
31351
|
const slockHome = resolveSlockHome();
|
|
31366
31352
|
const readPidfile = options.readPidfile ?? readPidfileAt;
|
|
31367
31353
|
const isAlive = options.isProcessAlive ?? isProcessAlive2;
|
|
31368
|
-
const killer = options.
|
|
31354
|
+
const killer = options.killService ?? ((pid2) => {
|
|
31369
31355
|
process.kill(pid2, "SIGTERM");
|
|
31370
31356
|
});
|
|
31371
31357
|
const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
31372
31358
|
const pollIntervalMs = options.pollIntervalMs ?? STOP_POLL_INTERVAL_MS;
|
|
31373
31359
|
const timeoutMs = options.timeoutMs ?? STOP_TIMEOUT_MS;
|
|
31374
|
-
|
|
31375
|
-
|
|
31360
|
+
let pidfilePath = servicePidPath(slockHome);
|
|
31361
|
+
let pid = await readPidfile(pidfilePath);
|
|
31362
|
+
if (pid === null) {
|
|
31363
|
+
const legacyPidfilePath = legacySupervisorPidPath(slockHome);
|
|
31364
|
+
const legacyPid = await readPidfile(legacyPidfilePath);
|
|
31365
|
+
if (legacyPid !== null) {
|
|
31366
|
+
pidfilePath = legacyPidfilePath;
|
|
31367
|
+
pid = legacyPid;
|
|
31368
|
+
}
|
|
31369
|
+
}
|
|
31376
31370
|
emit5(options, { type: "stopping", pid });
|
|
31377
31371
|
if (pid === null) {
|
|
31378
31372
|
emit5(options, { type: "not_running" });
|
|
@@ -31390,7 +31384,7 @@ async function stop(input = {}, options = {}) {
|
|
|
31390
31384
|
const cause = err instanceof Error ? err : new Error(String(err));
|
|
31391
31385
|
throw new ComputerServiceError(
|
|
31392
31386
|
"STOP_SIGNAL_FAILED",
|
|
31393
|
-
`Failed to send SIGTERM to
|
|
31387
|
+
`Failed to send SIGTERM to service (pid ${pid}): ${cause.message}. Check process permissions or run: kill ${pid}`,
|
|
31394
31388
|
err
|
|
31395
31389
|
);
|
|
31396
31390
|
}
|
|
@@ -31409,7 +31403,7 @@ async function stop(input = {}, options = {}) {
|
|
|
31409
31403
|
}
|
|
31410
31404
|
throw new ComputerServiceError(
|
|
31411
31405
|
"STOP_TIMEOUT",
|
|
31412
|
-
`
|
|
31406
|
+
`Service (pid ${pid}) did not exit within ${timeoutMs}ms after SIGTERM. Force-kill with: kill -9 ${pid}`
|
|
31413
31407
|
);
|
|
31414
31408
|
}
|
|
31415
31409
|
|
|
@@ -31525,16 +31519,16 @@ async function detach(input, options = {}) {
|
|
|
31525
31519
|
};
|
|
31526
31520
|
}
|
|
31527
31521
|
|
|
31528
|
-
// src/
|
|
31522
|
+
// src/service.ts
|
|
31529
31523
|
function buildResidentSpawn(mode, serverId, selfEntry = process.argv[1] ?? "", execArgv = process.execArgv) {
|
|
31530
31524
|
const tail = serverId ? [mode, serverId] : [mode];
|
|
31531
31525
|
return { command: process.execPath, args: [...execArgv, selfEntry, ...tail] };
|
|
31532
31526
|
}
|
|
31533
31527
|
var PARENT_LOCK_HELD_ENV_VAR = "SLOCK_COMPUTER_PARENT_MUTATION_LOCK_HELD";
|
|
31534
|
-
async function
|
|
31528
|
+
async function spawnDetachedService(slockHome) {
|
|
31535
31529
|
await mkdir8(computerDir(slockHome), { recursive: true });
|
|
31536
|
-
const supLogFd = await open(
|
|
31537
|
-
const { command, args } = buildResidentSpawn("
|
|
31530
|
+
const supLogFd = await open(serviceLogPath(slockHome), "a");
|
|
31531
|
+
const { command, args } = buildResidentSpawn("__service", null);
|
|
31538
31532
|
const child = spawn2(command, args, {
|
|
31539
31533
|
detached: true,
|
|
31540
31534
|
stdio: ["ignore", supLogFd.fd, supLogFd.fd],
|
|
@@ -31551,9 +31545,9 @@ async function spawnDetachedSupervisor(slockHome) {
|
|
|
31551
31545
|
child.unref();
|
|
31552
31546
|
await supLogFd.close();
|
|
31553
31547
|
if (!pid) {
|
|
31554
|
-
throw new Error("SUPERVISOR_SPAWN_FAILED: could not spawn the
|
|
31548
|
+
throw new Error("SUPERVISOR_SPAWN_FAILED: could not spawn the service process");
|
|
31555
31549
|
}
|
|
31556
|
-
await writePidfileAt(
|
|
31550
|
+
await writePidfileAt(servicePidPath(slockHome), pid);
|
|
31557
31551
|
return pid;
|
|
31558
31552
|
}
|
|
31559
31553
|
var EX_CONFIG_EXIT_CODE = 78;
|
|
@@ -31579,7 +31573,7 @@ var defaultCoreFactory = async (creds) => {
|
|
|
31579
31573
|
}
|
|
31580
31574
|
return new coreMod.DaemonCore({ serverUrl: creds.serverUrl, apiKey: creds.apiKey, localTrace: true });
|
|
31581
31575
|
};
|
|
31582
|
-
function
|
|
31576
|
+
function classifyRunnerExit(code, signal) {
|
|
31583
31577
|
if (code === EX_CONFIG_EXIT_CODE) return "config-error";
|
|
31584
31578
|
if (signal === "SIGTERM" || signal === "SIGINT") return "graceful";
|
|
31585
31579
|
if (code === 0) return "graceful";
|
|
@@ -31624,18 +31618,18 @@ function formatReadySummary(ready, serverIds, opts) {
|
|
|
31624
31618
|
}
|
|
31625
31619
|
return `Daemons for ${serverIds.length} managed server(s) are running.`;
|
|
31626
31620
|
}
|
|
31627
|
-
async function
|
|
31621
|
+
async function runServiceStartupRecovery(slockHome) {
|
|
31628
31622
|
const parentHoldsLock = process.env[PARENT_LOCK_HELD_ENV_VAR] === "1";
|
|
31629
31623
|
try {
|
|
31630
31624
|
if (parentHoldsLock) {
|
|
31631
31625
|
process.stderr.write(
|
|
31632
|
-
"
|
|
31626
|
+
"Service startup: parent CLI holds mutation lock \u2014 skipping lock cleanup.\n"
|
|
31633
31627
|
);
|
|
31634
31628
|
} else {
|
|
31635
31629
|
const releasedLocks = await forceReleaseLock(slockHome);
|
|
31636
31630
|
if (releasedLocks.length > 0) {
|
|
31637
31631
|
process.stderr.write(
|
|
31638
|
-
`
|
|
31632
|
+
`Service startup: force-released ${releasedLocks.length} stale lock(s).
|
|
31639
31633
|
`
|
|
31640
31634
|
);
|
|
31641
31635
|
}
|
|
@@ -31643,19 +31637,19 @@ async function runSupervisorStartupRecovery(slockHome) {
|
|
|
31643
31637
|
const report = await runFullCleanup(slockHome, { skipLockCleanup: parentHoldsLock });
|
|
31644
31638
|
if (report.anyAction) {
|
|
31645
31639
|
process.stderr.write(
|
|
31646
|
-
`
|
|
31640
|
+
`Service startup recovery: cleaned ${report.stalePidfiles.length} pidfile(s), ${report.powerLossRecovered.length} quarantined, ${report.tmpFilesCleared.length} tmp file(s), ${report.staleLocks.length} stale lock(s).
|
|
31647
31641
|
`
|
|
31648
31642
|
);
|
|
31649
31643
|
}
|
|
31650
31644
|
} catch (err) {
|
|
31651
31645
|
const msg = err instanceof Error ? err.message : String(err);
|
|
31652
31646
|
process.stderr.write(
|
|
31653
|
-
`
|
|
31647
|
+
`Service startup recovery pass failed: ${msg}. Continuing; cleanup will retry on next service restart.
|
|
31654
31648
|
`
|
|
31655
31649
|
);
|
|
31656
31650
|
}
|
|
31657
31651
|
}
|
|
31658
|
-
async function
|
|
31652
|
+
async function resolveServiceIdentity() {
|
|
31659
31653
|
const here = fileURLToPath2(import.meta.url);
|
|
31660
31654
|
const installRoot = dirname8(dirname8(here));
|
|
31661
31655
|
let version = null;
|
|
@@ -31669,30 +31663,30 @@ async function resolveSupervisorIdentity() {
|
|
|
31669
31663
|
}
|
|
31670
31664
|
return { installRoot, version };
|
|
31671
31665
|
}
|
|
31672
|
-
async function
|
|
31666
|
+
async function writeServiceVersionEvidence(slockHome) {
|
|
31673
31667
|
try {
|
|
31674
|
-
const ident = await
|
|
31668
|
+
const ident = await resolveServiceIdentity();
|
|
31675
31669
|
const payload = {
|
|
31676
31670
|
version: ident.version,
|
|
31677
31671
|
installRoot: ident.installRoot,
|
|
31678
31672
|
pid: process.pid,
|
|
31679
31673
|
writtenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
31680
31674
|
};
|
|
31681
|
-
const dest =
|
|
31675
|
+
const dest = serviceVersionPath(slockHome);
|
|
31682
31676
|
const tmp = `${dest}.tmp`;
|
|
31683
31677
|
await writeFile7(tmp, JSON.stringify(payload) + "\n", { mode: 384 });
|
|
31684
31678
|
await rename2(tmp, dest);
|
|
31685
31679
|
} catch (err) {
|
|
31686
31680
|
const msg = err instanceof Error ? err.message : String(err);
|
|
31687
31681
|
process.stderr.write(
|
|
31688
|
-
`
|
|
31682
|
+
`Service: failed to write version evidence: ${msg}. Continuing.
|
|
31689
31683
|
`
|
|
31690
31684
|
);
|
|
31691
31685
|
}
|
|
31692
31686
|
}
|
|
31693
|
-
async function
|
|
31687
|
+
async function readServiceVersionEvidence(slockHome) {
|
|
31694
31688
|
try {
|
|
31695
|
-
const raw = await readFile7(
|
|
31689
|
+
const raw = await readFile7(serviceVersionPath(slockHome), "utf8");
|
|
31696
31690
|
const parsed = JSON.parse(raw);
|
|
31697
31691
|
if (typeof parsed.installRoot !== "string" || typeof parsed.pid !== "number" || typeof parsed.writtenAt !== "string" || parsed.version !== null && typeof parsed.version !== "string") {
|
|
31698
31692
|
return null;
|
|
@@ -31707,12 +31701,12 @@ async function readSupervisorVersionEvidence(slockHome) {
|
|
|
31707
31701
|
return null;
|
|
31708
31702
|
}
|
|
31709
31703
|
}
|
|
31710
|
-
async function
|
|
31704
|
+
async function runService() {
|
|
31711
31705
|
const slockHome = resolveSlockHome();
|
|
31712
31706
|
await mkdir8(computerDir(slockHome), { recursive: true });
|
|
31713
|
-
await
|
|
31714
|
-
await writePidfileAt(
|
|
31715
|
-
await
|
|
31707
|
+
await runServiceStartupRecovery(slockHome);
|
|
31708
|
+
await writePidfileAt(servicePidPath(slockHome), process.pid);
|
|
31709
|
+
await writeServiceVersionEvidence(slockHome);
|
|
31716
31710
|
const children = /* @__PURE__ */ new Map();
|
|
31717
31711
|
const { [PARENT_LOCK_HELD_ENV_VAR]: _parentLockMarker, ...childEnv } = process.env;
|
|
31718
31712
|
const spawnChild = async (serverId) => {
|
|
@@ -31735,14 +31729,14 @@ async function runSupervise() {
|
|
|
31735
31729
|
children.delete(serverId);
|
|
31736
31730
|
await clearPidfileAt(serverDaemonPidPath(slockHome, serverId));
|
|
31737
31731
|
if (handle.stopping) return;
|
|
31738
|
-
const classification =
|
|
31732
|
+
const classification = classifyRunnerExit(code, signal);
|
|
31739
31733
|
if (classification === "config-error") {
|
|
31740
31734
|
try {
|
|
31741
31735
|
await markFatalConfig(slockHome, serverId, code, signal);
|
|
31742
31736
|
} catch {
|
|
31743
31737
|
}
|
|
31744
31738
|
process.stderr.write(
|
|
31745
|
-
`
|
|
31739
|
+
`Service: server ${serverId} child exited with EX_CONFIG (${EX_CONFIG_EXIT_CODE}); marked degraded, NOT auto-restarting. See ${serverDaemonLogPath(slockHome, serverId)} for the actionable error.
|
|
31746
31740
|
`
|
|
31747
31741
|
);
|
|
31748
31742
|
return;
|
|
@@ -31760,7 +31754,7 @@ async function runSupervise() {
|
|
|
31760
31754
|
}
|
|
31761
31755
|
if (await isDegraded(slockHome, serverId)) {
|
|
31762
31756
|
process.stderr.write(
|
|
31763
|
-
`
|
|
31757
|
+
`Service: server ${serverId} marked degraded (>=3 crashes in 60s); skipping auto-restart. Run \`slock-computer doctor ${serverId} --reset-health\` after fixing the underlying issue.
|
|
31764
31758
|
`
|
|
31765
31759
|
);
|
|
31766
31760
|
return;
|
|
@@ -31793,7 +31787,7 @@ async function runSupervise() {
|
|
|
31793
31787
|
if (shuttingDown) return;
|
|
31794
31788
|
shuttingDown = true;
|
|
31795
31789
|
for (const handle of children.values()) killChild(handle);
|
|
31796
|
-
void clearPidfileAt(
|
|
31790
|
+
void clearPidfileAt(servicePidPath(slockHome)).then(() => process.exit(0));
|
|
31797
31791
|
};
|
|
31798
31792
|
process.on("SIGTERM", shutdown);
|
|
31799
31793
|
process.on("SIGINT", shutdown);
|
|
@@ -31815,7 +31809,7 @@ async function runStart(opts = {}, deps = {}) {
|
|
|
31815
31809
|
serverLabel: opts.serverLabel ?? null
|
|
31816
31810
|
},
|
|
31817
31811
|
{
|
|
31818
|
-
|
|
31812
|
+
spawnDetachedService: deps.spawnDetachedService,
|
|
31819
31813
|
readPidfile: deps.readPidfile,
|
|
31820
31814
|
isProcessAlive: deps.isProcessAlive,
|
|
31821
31815
|
sleep: deps.sleep,
|
|
@@ -31823,17 +31817,17 @@ async function runStart(opts = {}, deps = {}) {
|
|
|
31823
31817
|
ensurePollIntervalMs: deps.ensurePollIntervalMs,
|
|
31824
31818
|
onEvent: (event) => {
|
|
31825
31819
|
if (event.type === "already_running") {
|
|
31826
|
-
info(`
|
|
31820
|
+
info(`Service already running (pid ${event.servicePid}).`);
|
|
31827
31821
|
} else if (event.type === "running") {
|
|
31828
31822
|
info(
|
|
31829
|
-
`Running
|
|
31823
|
+
`Running service in the foreground (managing ${event.managedTargets.length} of ${event.attachedCount} attached server(s)). Ctrl-C to stop.`
|
|
31830
31824
|
);
|
|
31831
31825
|
} else if (event.type === "spawned") {
|
|
31832
|
-
info(`
|
|
31826
|
+
info(`Service started (pid ${event.servicePid}); keeps running after this terminal closes.`);
|
|
31833
31827
|
spawnedBackground = {
|
|
31834
31828
|
managedCount: event.managedTargets.length,
|
|
31835
31829
|
attachedCount: event.attachedCount,
|
|
31836
|
-
logPath:
|
|
31830
|
+
logPath: serviceLogPath(resolveSlockHome())
|
|
31837
31831
|
};
|
|
31838
31832
|
} else if (event.type === "ready") {
|
|
31839
31833
|
info(formatReadySummary(event.ready, event.managedTargets, opts));
|
|
@@ -31864,17 +31858,17 @@ async function runStop(deps = {}) {
|
|
|
31864
31858
|
{
|
|
31865
31859
|
readPidfile: deps.readPidfile,
|
|
31866
31860
|
isProcessAlive: deps.isProcessAlive,
|
|
31867
|
-
|
|
31861
|
+
killService: deps.killService,
|
|
31868
31862
|
sleep: deps.sleep,
|
|
31869
31863
|
pollIntervalMs: deps.pollIntervalMs,
|
|
31870
31864
|
timeoutMs: deps.timeoutMs,
|
|
31871
31865
|
onEvent: (event) => {
|
|
31872
31866
|
if (event.type === "not_running") {
|
|
31873
|
-
info("
|
|
31867
|
+
info("Service not running.");
|
|
31874
31868
|
} else if (event.type === "stale_pidfile_cleared") {
|
|
31875
|
-
info(`
|
|
31869
|
+
info(`Service not running (cleared stale pidfile for pid ${event.pid}).`);
|
|
31876
31870
|
} else if (event.type === "stopped") {
|
|
31877
|
-
info(`Stopped
|
|
31871
|
+
info(`Stopped service (pid ${event.pid}).`);
|
|
31878
31872
|
}
|
|
31879
31873
|
}
|
|
31880
31874
|
}
|
|
@@ -31899,7 +31893,7 @@ async function runDetach(serverId, serverLabel = serverId) {
|
|
|
31899
31893
|
} else if (event.type === "detached") {
|
|
31900
31894
|
info(`Detached from server ${event.serverLabel}.`);
|
|
31901
31895
|
info(
|
|
31902
|
-
`The
|
|
31896
|
+
`The service (if running) will stop that server's daemon on its next reconcile tick.`
|
|
31903
31897
|
);
|
|
31904
31898
|
}
|
|
31905
31899
|
}
|
|
@@ -31916,6 +31910,14 @@ async function runDetach(serverId, serverLabel = serverId) {
|
|
|
31916
31910
|
|
|
31917
31911
|
// src/setup.ts
|
|
31918
31912
|
var USER_SESSION_EXPIRY_LEEWAY_MS = 3e4;
|
|
31913
|
+
async function readUserSessionUserId(slockHome) {
|
|
31914
|
+
try {
|
|
31915
|
+
const parsed = JSON.parse(await readFile8(userSessionPath(slockHome), "utf8"));
|
|
31916
|
+
return typeof parsed.userId === "string" ? parsed.userId : "";
|
|
31917
|
+
} catch {
|
|
31918
|
+
return "";
|
|
31919
|
+
}
|
|
31920
|
+
}
|
|
31919
31921
|
async function hasValidUserSession(slockHome) {
|
|
31920
31922
|
try {
|
|
31921
31923
|
const parsed = JSON.parse(await readFile8(userSessionPath(slockHome), "utf8"));
|
|
@@ -31983,49 +31985,74 @@ async function refreshUserSession(slockHome, serverUrl) {
|
|
|
31983
31985
|
return false;
|
|
31984
31986
|
}
|
|
31985
31987
|
}
|
|
31986
|
-
async function
|
|
31987
|
-
|
|
31988
|
-
|
|
31989
|
-
|
|
31990
|
-
|
|
31991
|
-
return false;
|
|
31992
|
-
}
|
|
31993
|
-
for (const name of machineDirs) {
|
|
31994
|
-
if (!name.startsWith("machine-")) continue;
|
|
31995
|
-
try {
|
|
31996
|
-
const raw = await readFile8(join3(slockHome, "machines", name, "daemon.lock", "owner.json"), "utf8");
|
|
31997
|
-
const owner = JSON.parse(raw);
|
|
31998
|
-
if (typeof owner.pid === "number" && isProcessAlive2(owner.pid)) return true;
|
|
31999
|
-
} catch {
|
|
32000
|
-
}
|
|
32001
|
-
}
|
|
32002
|
-
return false;
|
|
31988
|
+
async function defaultConfirmMigrate(prompt) {
|
|
31989
|
+
process.stdout.write(prompt);
|
|
31990
|
+
const line = await readLine(false);
|
|
31991
|
+
const norm = line.trim().toLowerCase();
|
|
31992
|
+
return norm === "y" || norm === "yes";
|
|
32003
31993
|
}
|
|
32004
|
-
function
|
|
32005
|
-
|
|
31994
|
+
async function defaultReadMigrateSecret(prompt) {
|
|
31995
|
+
process.stdout.write(prompt);
|
|
31996
|
+
const line = await readLine(true);
|
|
31997
|
+
process.stdout.write("\n");
|
|
31998
|
+
return line.trim();
|
|
32006
31999
|
}
|
|
32007
|
-
function
|
|
32008
|
-
|
|
32000
|
+
async function readLine(masked) {
|
|
32001
|
+
const stdin = process.stdin;
|
|
32002
|
+
const wasRaw = stdin.isTTY === true && stdin.isRaw === true;
|
|
32003
|
+
const enterRaw = masked && stdin.isTTY === true && typeof stdin.setRawMode === "function";
|
|
32004
|
+
if (enterRaw) stdin.setRawMode(true);
|
|
32005
|
+
stdin.resume();
|
|
32006
|
+
return await new Promise((resolve) => {
|
|
32007
|
+
let buf = "";
|
|
32008
|
+
const cleanup = () => {
|
|
32009
|
+
stdin.off("data", onData);
|
|
32010
|
+
stdin.off("end", onEnd);
|
|
32011
|
+
stdin.pause();
|
|
32012
|
+
if (enterRaw) stdin.setRawMode(wasRaw);
|
|
32013
|
+
};
|
|
32014
|
+
const onData = (chunk) => {
|
|
32015
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
32016
|
+
for (const ch of text) {
|
|
32017
|
+
if (ch === "\n" || ch === "\r") {
|
|
32018
|
+
cleanup();
|
|
32019
|
+
resolve(buf);
|
|
32020
|
+
return;
|
|
32021
|
+
}
|
|
32022
|
+
if (ch === "") {
|
|
32023
|
+
cleanup();
|
|
32024
|
+
resolve("");
|
|
32025
|
+
return;
|
|
32026
|
+
}
|
|
32027
|
+
if (ch === "\x7F" || ch === "\b") {
|
|
32028
|
+
buf = buf.slice(0, -1);
|
|
32029
|
+
continue;
|
|
32030
|
+
}
|
|
32031
|
+
buf += ch;
|
|
32032
|
+
}
|
|
32033
|
+
};
|
|
32034
|
+
const onEnd = () => {
|
|
32035
|
+
cleanup();
|
|
32036
|
+
resolve(buf);
|
|
32037
|
+
};
|
|
32038
|
+
stdin.on("data", onData);
|
|
32039
|
+
stdin.on("end", onEnd);
|
|
32040
|
+
});
|
|
32009
32041
|
}
|
|
32010
32042
|
async function runSetup(opts, deps = {}) {
|
|
32011
32043
|
const slockHome = resolveSlockHome();
|
|
32012
32044
|
const isTty = deps.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
32013
32045
|
const login2 = deps.runLogin ?? runLogin;
|
|
32014
32046
|
const attach2 = deps.runAttach ?? runAttach;
|
|
32015
|
-
const adopt = deps.runAdoptLegacy ?? runAdoptLegacy;
|
|
32016
32047
|
const start2 = deps.runStart ?? runStart;
|
|
32017
32048
|
const refreshSession = deps.refreshUserSession ?? refreshUserSession;
|
|
32018
|
-
const
|
|
32049
|
+
const detectMigration = deps.detectLegacyMigration ?? detectLegacyMigration;
|
|
32050
|
+
const confirmMigrate = deps.confirmMigrate ?? defaultConfirmMigrate;
|
|
32051
|
+
const readMigrateSecret = deps.readMigrateSecret ?? defaultReadMigrateSecret;
|
|
32019
32052
|
if (!isTty && !opts.yes) {
|
|
32020
32053
|
fail(
|
|
32021
32054
|
"NON_INTERACTIVE_SETUP_REQUIRES_FLAGS",
|
|
32022
|
-
"Non-interactive setup requires --yes after you have confirmed the login/attach/
|
|
32023
|
-
);
|
|
32024
|
-
}
|
|
32025
|
-
if (!opts.adoptLegacy && hasExplicitLegacyKeyInput(opts)) {
|
|
32026
|
-
fail(
|
|
32027
|
-
"LEGACY_KEY_OUTSIDE_ADOPT",
|
|
32028
|
-
"`--legacy-api-key*` options are only accepted with `slock-computer setup --adopt-legacy` or `slock-computer adopt-legacy`."
|
|
32055
|
+
"Non-interactive setup requires --yes after you have confirmed the login/attach/start actions. Run `slock-computer login`, `slock-computer attach`, and `slock-computer start` separately for fully explicit automation."
|
|
32029
32056
|
);
|
|
32030
32057
|
}
|
|
32031
32058
|
const label = formatServerSlugDisplay(opts.serverSlug);
|
|
@@ -32050,34 +32077,82 @@ async function runSetup(opts, deps = {}) {
|
|
|
32050
32077
|
if (attachment) {
|
|
32051
32078
|
info(`Attachment: already attached to ${label}.`);
|
|
32052
32079
|
} else {
|
|
32053
|
-
|
|
32054
|
-
|
|
32055
|
-
|
|
32056
|
-
|
|
32057
|
-
|
|
32058
|
-
"
|
|
32059
|
-
|
|
32060
|
-
|
|
32061
|
-
|
|
32062
|
-
|
|
32063
|
-
|
|
32064
|
-
|
|
32065
|
-
|
|
32066
|
-
|
|
32067
|
-
|
|
32068
|
-
|
|
32069
|
-
|
|
32070
|
-
|
|
32071
|
-
|
|
32072
|
-
|
|
32073
|
-
|
|
32074
|
-
|
|
32075
|
-
|
|
32076
|
-
|
|
32077
|
-
|
|
32078
|
-
|
|
32079
|
-
|
|
32080
|
+
let migrated = false;
|
|
32081
|
+
if (isTty) {
|
|
32082
|
+
const detection = await detectMigration(slockHome, await readUserSessionUserId(slockHome));
|
|
32083
|
+
if (detection.kind === "match") {
|
|
32084
|
+
const where = detection.serverUrl ? ` attached to ${detection.serverUrl}` : "";
|
|
32085
|
+
const who = detection.machineName ? ` "${detection.machineName}"` : "";
|
|
32086
|
+
const accepted = await confirmMigrate(
|
|
32087
|
+
`Migration: detected legacy daemon machine${who}${where}. Migrate it under Computer instead of creating a fresh attachment? [y/N] `
|
|
32088
|
+
);
|
|
32089
|
+
if (accepted) {
|
|
32090
|
+
const rawKey = await readMigrateSecret(
|
|
32091
|
+
"Paste legacy api key (sk_machine_* or sk_daemon_*); input is hidden: "
|
|
32092
|
+
);
|
|
32093
|
+
if (rawKey.length === 0) {
|
|
32094
|
+
info("Migration: no key provided; falling back to fresh attach.");
|
|
32095
|
+
} else if (!rawKey.startsWith("sk_machine_") && !rawKey.startsWith("sk_daemon_")) {
|
|
32096
|
+
fail(
|
|
32097
|
+
"LEGACY_KEY_INVALID",
|
|
32098
|
+
"Provided key does not look like a legacy machine key (expected `sk_machine_*` or `sk_daemon_*`)."
|
|
32099
|
+
);
|
|
32100
|
+
} else {
|
|
32101
|
+
try {
|
|
32102
|
+
await adoptLegacy(
|
|
32103
|
+
{
|
|
32104
|
+
serverSlug: opts.serverSlug,
|
|
32105
|
+
serverUrl: opts.serverUrl,
|
|
32106
|
+
name: opts.name,
|
|
32107
|
+
rawKey,
|
|
32108
|
+
mode: "legacy_key_stdin",
|
|
32109
|
+
redactedPrefix: rawKey.slice(0, 8)
|
|
32110
|
+
},
|
|
32111
|
+
{
|
|
32112
|
+
onEvent: (event) => {
|
|
32113
|
+
if (event.type === "adopting") {
|
|
32114
|
+
info(
|
|
32115
|
+
`Adopting legacy daemon for ${formatServerSlugDisplay(event.serverSlug)} via ${event.mode}\u2026`
|
|
32116
|
+
);
|
|
32117
|
+
} else if (event.type === "preflight") {
|
|
32118
|
+
info(
|
|
32119
|
+
event.resumed ? "Adopted (resumed prior attachment); running preflight\u2026" : "Adopted; running preflight\u2026"
|
|
32120
|
+
);
|
|
32121
|
+
} else if (event.type === "adopted") {
|
|
32122
|
+
info(`Adopted. Computer state written to ${event.attachmentPath}`);
|
|
32123
|
+
info(` server: ${formatServerSlugDisplay(event.serverSlug)}`);
|
|
32124
|
+
info(` serverMachine: ${event.serverMachineId}`);
|
|
32125
|
+
info(` legacyMachine: ${event.legacyMachineId}`);
|
|
32126
|
+
switch (event.legacyStop.outcome) {
|
|
32127
|
+
case "absent":
|
|
32128
|
+
info(" legacy daemon: not detected on this Computer (no local lock file)");
|
|
32129
|
+
break;
|
|
32130
|
+
case "already_dead":
|
|
32131
|
+
info(` legacy daemon: already stopped (pid ${event.legacyStop.pid} not running)`);
|
|
32132
|
+
break;
|
|
32133
|
+
case "stopped":
|
|
32134
|
+
info(` legacy daemon: stopped (pid ${event.legacyStop.pid}, SIGTERM)`);
|
|
32135
|
+
break;
|
|
32136
|
+
}
|
|
32137
|
+
}
|
|
32138
|
+
}
|
|
32139
|
+
}
|
|
32140
|
+
);
|
|
32141
|
+
migrated = true;
|
|
32142
|
+
} catch (err) {
|
|
32143
|
+
if (err instanceof CliExit) throw err;
|
|
32144
|
+
if (err instanceof ComputerServiceError) {
|
|
32145
|
+
fail(err.code, err.message);
|
|
32146
|
+
}
|
|
32147
|
+
throw err;
|
|
32148
|
+
}
|
|
32149
|
+
}
|
|
32150
|
+
} else {
|
|
32151
|
+
info("Migration: declined; falling back to fresh attach.");
|
|
32152
|
+
}
|
|
32080
32153
|
}
|
|
32154
|
+
}
|
|
32155
|
+
if (!migrated) {
|
|
32081
32156
|
await attach2({
|
|
32082
32157
|
serverSlug: opts.serverSlug,
|
|
32083
32158
|
serverUrl: opts.serverUrl,
|
|
@@ -32090,7 +32165,7 @@ async function runSetup(opts, deps = {}) {
|
|
|
32090
32165
|
if (!attachment) {
|
|
32091
32166
|
fail(
|
|
32092
32167
|
"SETUP_ATTACHMENT_MISSING",
|
|
32093
|
-
`Setup did not produce a local attachment for ${label}. Re-run \`slock-computer attach ${label}
|
|
32168
|
+
`Setup did not produce a local attachment for ${label}. Re-run \`slock-computer attach ${label}\`.`
|
|
32094
32169
|
);
|
|
32095
32170
|
}
|
|
32096
32171
|
}
|
|
@@ -32138,37 +32213,36 @@ async function deriveHealth(slockHome, serverId, daemon) {
|
|
|
32138
32213
|
if (await isDegraded(slockHome, serverId)) return "degraded";
|
|
32139
32214
|
return "ok";
|
|
32140
32215
|
}
|
|
32141
|
-
async function buildStatusReport() {
|
|
32142
|
-
const
|
|
32143
|
-
const sessionRead = await readUserSession(userSessionPath(slockHome));
|
|
32216
|
+
async function buildStatusReport(installRoot) {
|
|
32217
|
+
const sessionRead = await readUserSession(userSessionPath(installRoot));
|
|
32144
32218
|
const session = sessionRead.session;
|
|
32145
|
-
const attachments = await listServerAttachments(
|
|
32146
|
-
const
|
|
32147
|
-
...await pidStatus(
|
|
32148
|
-
logPath:
|
|
32219
|
+
const attachments = await listServerAttachments(installRoot);
|
|
32220
|
+
const service = {
|
|
32221
|
+
...await pidStatus(servicePidPath(installRoot)),
|
|
32222
|
+
logPath: serviceLogPath(installRoot)
|
|
32149
32223
|
};
|
|
32150
32224
|
const servers = [];
|
|
32151
32225
|
for (const a of attachments) {
|
|
32152
|
-
const daemon = await pidStatus(serverDaemonPidPath(
|
|
32226
|
+
const daemon = await pidStatus(serverDaemonPidPath(installRoot, a.serverId));
|
|
32153
32227
|
servers.push({
|
|
32154
32228
|
serverId: a.serverId,
|
|
32155
32229
|
serverSlug: a.serverSlug ?? null,
|
|
32156
32230
|
serverMachineId: a.serverMachineId,
|
|
32157
32231
|
serverUrl: a.serverUrl,
|
|
32158
32232
|
attachedAt: a.attachedAt ?? null,
|
|
32159
|
-
daemonLogPath: serverDaemonLogPath(
|
|
32233
|
+
daemonLogPath: serverDaemonLogPath(installRoot, a.serverId),
|
|
32160
32234
|
daemon,
|
|
32161
|
-
health: await deriveHealth(
|
|
32235
|
+
health: await deriveHealth(installRoot, a.serverId, daemon)
|
|
32162
32236
|
});
|
|
32163
32237
|
}
|
|
32164
32238
|
const loggedIn = session?.kind === "user-session" && typeof session.accessToken === "string" && session.accessToken.length > 0;
|
|
32165
32239
|
return {
|
|
32166
|
-
slockHome,
|
|
32240
|
+
slockHome: installRoot,
|
|
32167
32241
|
loggedIn,
|
|
32168
32242
|
userId: session ? str(session.userId) : null,
|
|
32169
32243
|
loginServerUrl: session ? str(session.serverUrl) : null,
|
|
32170
32244
|
userSessionError: sessionRead.state === "invalid" ? sessionRead.error : null,
|
|
32171
|
-
|
|
32245
|
+
service,
|
|
32172
32246
|
servers
|
|
32173
32247
|
};
|
|
32174
32248
|
}
|
|
@@ -32176,7 +32250,7 @@ function pad(s, n) {
|
|
|
32176
32250
|
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
32177
32251
|
}
|
|
32178
32252
|
async function runStatus(opts) {
|
|
32179
|
-
const report = await buildStatusReport();
|
|
32253
|
+
const report = await buildStatusReport(resolveSlockHome());
|
|
32180
32254
|
if (opts.json) {
|
|
32181
32255
|
info(JSON.stringify(report, null, 2));
|
|
32182
32256
|
return;
|
|
@@ -32189,9 +32263,9 @@ async function runStatus(opts) {
|
|
|
32189
32263
|
);
|
|
32190
32264
|
if (report.loginServerUrl) info(`Login server: ${report.loginServerUrl}`);
|
|
32191
32265
|
info(
|
|
32192
|
-
`
|
|
32266
|
+
`Service: ${report.service.running ? `running (pid ${report.service.pid})` : "stopped \u2014 run `slock-computer start`"}`
|
|
32193
32267
|
);
|
|
32194
|
-
info(`
|
|
32268
|
+
info(`Service log: ${report.service.logPath}`);
|
|
32195
32269
|
info("");
|
|
32196
32270
|
if (report.servers.length === 0) {
|
|
32197
32271
|
info("Attachments: none \u2014 run `slock-computer attach /<serverSlug>` (e.g. `/myserver`).");
|
|
@@ -32281,42 +32355,92 @@ async function resolveTargetAttachment(opts) {
|
|
|
32281
32355
|
return a;
|
|
32282
32356
|
}
|
|
32283
32357
|
|
|
32358
|
+
// src/lib/readers.ts
|
|
32359
|
+
init_esm_shims();
|
|
32360
|
+
|
|
32361
|
+
// src/lib/types.ts
|
|
32362
|
+
init_esm_shims();
|
|
32363
|
+
var StateReaderError = class extends Error {
|
|
32364
|
+
code;
|
|
32365
|
+
constructor(code, message) {
|
|
32366
|
+
super(message);
|
|
32367
|
+
this.name = "StateReaderError";
|
|
32368
|
+
this.code = code;
|
|
32369
|
+
}
|
|
32370
|
+
};
|
|
32371
|
+
|
|
32372
|
+
// src/lib/readers.ts
|
|
32373
|
+
async function listRunners(installRoot, opts = {}) {
|
|
32374
|
+
const all = await listServerAttachments(installRoot);
|
|
32375
|
+
let subset = all;
|
|
32376
|
+
if (opts.serverId !== void 0) {
|
|
32377
|
+
const found = all.find((a) => a.serverId === opts.serverId);
|
|
32378
|
+
if (!found) {
|
|
32379
|
+
throw new StateReaderError(
|
|
32380
|
+
"NOT_ATTACHED",
|
|
32381
|
+
`Server ${opts.serverId} is not attached to this Computer.`
|
|
32382
|
+
);
|
|
32383
|
+
}
|
|
32384
|
+
subset = [found];
|
|
32385
|
+
}
|
|
32386
|
+
const servers = [];
|
|
32387
|
+
for (const a of subset) {
|
|
32388
|
+
const client = new RunnersClient(a.serverUrl, a.apiKey);
|
|
32389
|
+
const result = await client.list();
|
|
32390
|
+
const idCols = { serverId: a.serverId, serverSlug: a.serverSlug ?? null };
|
|
32391
|
+
if (result.status === "success") {
|
|
32392
|
+
servers.push({
|
|
32393
|
+
...idCols,
|
|
32394
|
+
status: "ok",
|
|
32395
|
+
whitelist: result.whitelist,
|
|
32396
|
+
runners: result.runners
|
|
32397
|
+
});
|
|
32398
|
+
} else if (result.status === "unauthorized") {
|
|
32399
|
+
servers.push({ ...idCols, status: "unauthorized" });
|
|
32400
|
+
} else {
|
|
32401
|
+
servers.push({ ...idCols, status: "error", code: result.code });
|
|
32402
|
+
}
|
|
32403
|
+
}
|
|
32404
|
+
return { servers };
|
|
32405
|
+
}
|
|
32406
|
+
|
|
32284
32407
|
// src/runners.ts
|
|
32285
32408
|
async function runRunnersList(opts) {
|
|
32286
|
-
const
|
|
32287
|
-
const
|
|
32288
|
-
const
|
|
32289
|
-
const
|
|
32290
|
-
|
|
32409
|
+
const serverId = await resolveTargetServerId({ server: opts.server });
|
|
32410
|
+
const installRoot = resolveSlockHome();
|
|
32411
|
+
const { servers } = await listRunners(installRoot, { serverId });
|
|
32412
|
+
const block = servers[0];
|
|
32413
|
+
const label = block.serverSlug ? formatServerSlugDisplay(block.serverSlug) : block.serverId;
|
|
32414
|
+
if (block.status === "unauthorized") {
|
|
32291
32415
|
fail(
|
|
32292
32416
|
"RUNNERS_UNAUTHORIZED",
|
|
32293
32417
|
`The Computer credential for ${label} was rejected. Re-run \`slock-computer attach ${label}\`.`
|
|
32294
32418
|
);
|
|
32295
32419
|
}
|
|
32296
|
-
if (
|
|
32420
|
+
if (block.status === "error") {
|
|
32297
32421
|
fail(
|
|
32298
32422
|
"RUNNERS_LIST_FAILED",
|
|
32299
|
-
`Could not list runners on ${label} (${
|
|
32423
|
+
`Could not list runners on ${label} (${block.code}). Check --server-url / server version.`
|
|
32300
32424
|
);
|
|
32301
32425
|
}
|
|
32302
32426
|
if (opts.json) {
|
|
32303
32427
|
info(
|
|
32304
32428
|
JSON.stringify(
|
|
32305
|
-
{ server: label, serverId:
|
|
32429
|
+
{ server: label, serverId: block.serverId, whitelist: block.whitelist, runners: block.runners },
|
|
32306
32430
|
null,
|
|
32307
32431
|
2
|
|
32308
32432
|
)
|
|
32309
32433
|
);
|
|
32310
32434
|
return;
|
|
32311
32435
|
}
|
|
32312
|
-
if (
|
|
32436
|
+
if (block.runners.length === 0) {
|
|
32313
32437
|
info(`No runners on server ${label}.`);
|
|
32314
32438
|
return;
|
|
32315
32439
|
}
|
|
32316
32440
|
info("");
|
|
32317
32441
|
info(`Server ${label}:`);
|
|
32318
32442
|
info(" AGENT STATUS RUNTIME MODEL NAME");
|
|
32319
|
-
for (const r of
|
|
32443
|
+
for (const r of block.runners) {
|
|
32320
32444
|
info(
|
|
32321
32445
|
` ${r.agentId.padEnd(38)}${(r.status ?? "").padEnd(11)}${(r.runtime ?? "").padEnd(10)}${(r.model ?? "").padEnd(15)}${r.name ?? ""}`
|
|
32322
32446
|
);
|
|
@@ -32365,20 +32489,20 @@ function redactSecrets(line) {
|
|
|
32365
32489
|
return out;
|
|
32366
32490
|
}
|
|
32367
32491
|
async function runDoctorChecks() {
|
|
32368
|
-
const
|
|
32492
|
+
const slockHome = resolveSlockHome();
|
|
32493
|
+
const report = await buildStatusReport(slockHome);
|
|
32369
32494
|
const checks = [];
|
|
32370
32495
|
checks.push({ name: "SLOCK_HOME", ok: true, detail: report.slockHome });
|
|
32371
32496
|
checks.push(
|
|
32372
32497
|
report.userSessionError ? { name: "user session", ok: false, detail: "invalid user session file \u2014 re-run `slock-computer login`" } : report.loggedIn ? { name: "user session", ok: true, detail: `logged in (user ${report.userId ?? "?"})` } : { name: "user session", ok: false, detail: "not logged in \u2014 run `slock-computer login`" }
|
|
32373
32498
|
);
|
|
32374
32499
|
checks.push(
|
|
32375
|
-
report.
|
|
32376
|
-
name: "
|
|
32500
|
+
report.service.running ? { name: "service", ok: true, detail: `running (pid ${report.service.pid})` } : {
|
|
32501
|
+
name: "service",
|
|
32377
32502
|
ok: true,
|
|
32378
32503
|
detail: "stopped (run `slock-computer start` when you want background)"
|
|
32379
32504
|
}
|
|
32380
32505
|
);
|
|
32381
|
-
const slockHome = resolveSlockHome();
|
|
32382
32506
|
const attachments = await listServerAttachments(slockHome);
|
|
32383
32507
|
if (attachments.length === 0) {
|
|
32384
32508
|
checks.push({
|
|
@@ -32420,7 +32544,7 @@ async function runDoctor(opts) {
|
|
|
32420
32544
|
if (opts.resetHealth && opts.serverId) {
|
|
32421
32545
|
await resetHealth(slockHome, opts.serverId);
|
|
32422
32546
|
info(`Reset health state for server ${opts.serverLabel ?? opts.serverId}.`);
|
|
32423
|
-
info(`
|
|
32547
|
+
info(`Service will resume auto-restart on next daemon exit.`);
|
|
32424
32548
|
}
|
|
32425
32549
|
const checks = await runDoctorChecks();
|
|
32426
32550
|
const allOk = checks.every((c) => c.ok);
|
|
@@ -32494,14 +32618,14 @@ import { readFile as readFile10 } from "fs/promises";
|
|
|
32494
32618
|
var DEFAULT_LINES = 200;
|
|
32495
32619
|
async function runLogs(opts) {
|
|
32496
32620
|
const home = resolveSlockHome();
|
|
32497
|
-
const file = opts.
|
|
32621
|
+
const file = opts.service ? serviceLogPath(home) : serverDaemonLogPath(home, await resolveTargetServerId({ server: opts.server }));
|
|
32498
32622
|
let content;
|
|
32499
32623
|
try {
|
|
32500
32624
|
content = await readFile10(file, "utf8");
|
|
32501
32625
|
} catch {
|
|
32502
32626
|
fail(
|
|
32503
32627
|
"NO_DAEMON_LOG",
|
|
32504
|
-
opts.
|
|
32628
|
+
opts.service ? `No service log at ${file}. Start the service first (\`slock-computer start\`).` : `No daemon log at ${file}. Start its daemon first (\`slock-computer start\`).`
|
|
32505
32629
|
);
|
|
32506
32630
|
}
|
|
32507
32631
|
const n = Number.isInteger(opts.lines) && opts.lines > 0 ? opts.lines : DEFAULT_LINES;
|
|
@@ -32511,16 +32635,166 @@ async function runLogs(opts) {
|
|
|
32511
32635
|
for (const line of tail) info(redactSecrets(line));
|
|
32512
32636
|
}
|
|
32513
32637
|
|
|
32638
|
+
// src/reset.ts
|
|
32639
|
+
init_esm_shims();
|
|
32640
|
+
|
|
32641
|
+
// src/serviceState.ts
|
|
32642
|
+
init_esm_shims();
|
|
32643
|
+
import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile9, appendFile as appendFile3 } from "fs/promises";
|
|
32644
|
+
import { dirname as dirname10 } from "path";
|
|
32645
|
+
|
|
32646
|
+
// src/lib/state.ts
|
|
32647
|
+
init_esm_shims();
|
|
32648
|
+
var SERVICE_STATE_VALUES = [
|
|
32649
|
+
"starting",
|
|
32650
|
+
"running",
|
|
32651
|
+
"degraded",
|
|
32652
|
+
"stopping",
|
|
32653
|
+
"stopped"
|
|
32654
|
+
];
|
|
32655
|
+
function isServiceState(value) {
|
|
32656
|
+
return typeof value === "string" && SERVICE_STATE_VALUES.includes(value);
|
|
32657
|
+
}
|
|
32658
|
+
|
|
32659
|
+
// src/serviceState.ts
|
|
32660
|
+
var DEFAULT_STATE = {
|
|
32661
|
+
state: "running",
|
|
32662
|
+
crashHistory: []
|
|
32663
|
+
};
|
|
32664
|
+
async function readServiceState(slockHome) {
|
|
32665
|
+
try {
|
|
32666
|
+
const raw = await readFile11(serviceStatePath(slockHome), "utf8");
|
|
32667
|
+
const parsed = JSON.parse(raw);
|
|
32668
|
+
if (!parsed || typeof parsed !== "object") return { ...DEFAULT_STATE };
|
|
32669
|
+
const obj = parsed;
|
|
32670
|
+
const state = isServiceState(obj.state) ? obj.state : "running";
|
|
32671
|
+
const crashHistory = Array.isArray(obj.crashHistory) ? obj.crashHistory.filter(isCrashEntry) : [];
|
|
32672
|
+
return { state, crashHistory };
|
|
32673
|
+
} catch {
|
|
32674
|
+
return { ...DEFAULT_STATE };
|
|
32675
|
+
}
|
|
32676
|
+
}
|
|
32677
|
+
async function writeServiceState(slockHome, file) {
|
|
32678
|
+
const path3 = serviceStatePath(slockHome);
|
|
32679
|
+
await mkdir10(dirname10(path3), { recursive: true });
|
|
32680
|
+
await writeFile9(path3, JSON.stringify(file), { mode: 384 });
|
|
32681
|
+
}
|
|
32682
|
+
function isCrashEntry(value) {
|
|
32683
|
+
if (!value || typeof value !== "object") return false;
|
|
32684
|
+
const obj = value;
|
|
32685
|
+
return typeof obj.at === "string";
|
|
32686
|
+
}
|
|
32687
|
+
async function emitServiceStateTransition(slockHome, fromState, toState, trigger) {
|
|
32688
|
+
const entry = {
|
|
32689
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
32690
|
+
kind: "service-state-changed",
|
|
32691
|
+
fromState,
|
|
32692
|
+
toState,
|
|
32693
|
+
trigger
|
|
32694
|
+
};
|
|
32695
|
+
try {
|
|
32696
|
+
const path3 = serviceLogPath(slockHome);
|
|
32697
|
+
await mkdir10(dirname10(path3), { recursive: true });
|
|
32698
|
+
await appendFile3(path3, JSON.stringify(entry) + "\n");
|
|
32699
|
+
} catch {
|
|
32700
|
+
}
|
|
32701
|
+
}
|
|
32702
|
+
async function clearServiceCrashHistory(slockHome) {
|
|
32703
|
+
const current = await readServiceState(slockHome);
|
|
32704
|
+
const previousState = current.state;
|
|
32705
|
+
const clearedCrashCount = current.crashHistory.length;
|
|
32706
|
+
const next = { state: "running", crashHistory: [] };
|
|
32707
|
+
await writeServiceState(slockHome, next);
|
|
32708
|
+
await emitServiceStateTransition(
|
|
32709
|
+
slockHome,
|
|
32710
|
+
previousState,
|
|
32711
|
+
"running",
|
|
32712
|
+
"reset-service"
|
|
32713
|
+
);
|
|
32714
|
+
return { previousState, clearedCrashCount };
|
|
32715
|
+
}
|
|
32716
|
+
|
|
32717
|
+
// src/reset.ts
|
|
32718
|
+
async function resetService(installRoot) {
|
|
32719
|
+
const { previousState, clearedCrashCount } = await clearServiceCrashHistory(installRoot);
|
|
32720
|
+
return {
|
|
32721
|
+
status: "ok",
|
|
32722
|
+
previousState,
|
|
32723
|
+
clearedCrashCount
|
|
32724
|
+
};
|
|
32725
|
+
}
|
|
32726
|
+
async function resetRunner(installRoot, serverId) {
|
|
32727
|
+
const attachment = await readServerAttachment(installRoot, serverId);
|
|
32728
|
+
if (!attachment) {
|
|
32729
|
+
return { status: "not-found", serverId };
|
|
32730
|
+
}
|
|
32731
|
+
const outcome = await resetRunnerHealth(installRoot, serverId);
|
|
32732
|
+
if (outcome.status === "not-found") {
|
|
32733
|
+
return { status: "not-found", serverId };
|
|
32734
|
+
}
|
|
32735
|
+
return {
|
|
32736
|
+
status: "ok",
|
|
32737
|
+
serverId,
|
|
32738
|
+
previousState: outcome.previousState ?? "running",
|
|
32739
|
+
clearedCrashCount: outcome.clearedCrashCount ?? 0
|
|
32740
|
+
};
|
|
32741
|
+
}
|
|
32742
|
+
async function runReset(opts) {
|
|
32743
|
+
const wantsService = opts.service === true;
|
|
32744
|
+
const wantsRunner = opts.runner === true;
|
|
32745
|
+
if (wantsService && wantsRunner) {
|
|
32746
|
+
fail(
|
|
32747
|
+
"RESET_SCOPE_AMBIGUOUS",
|
|
32748
|
+
"`--service` and `--runner` are mutually exclusive. Pass exactly one."
|
|
32749
|
+
);
|
|
32750
|
+
}
|
|
32751
|
+
if (!wantsService && !wantsRunner) {
|
|
32752
|
+
fail(
|
|
32753
|
+
"RESET_SCOPE_MISSING",
|
|
32754
|
+
"Pass `--service` to clear the service-level crash history, or `--runner` to clear a per-runner crash history."
|
|
32755
|
+
);
|
|
32756
|
+
}
|
|
32757
|
+
const slockHome = resolveSlockHome();
|
|
32758
|
+
if (wantsService) {
|
|
32759
|
+
const result2 = await resetService(slockHome);
|
|
32760
|
+
if (opts.json) {
|
|
32761
|
+
info(JSON.stringify(result2));
|
|
32762
|
+
return;
|
|
32763
|
+
}
|
|
32764
|
+
info(
|
|
32765
|
+
`Reset service crash history (previous state: ${result2.previousState}; cleared ${result2.clearedCrashCount} entr${result2.clearedCrashCount === 1 ? "y" : "ies"}).`
|
|
32766
|
+
);
|
|
32767
|
+
info("Service state transitioned to `running`. Runners were not touched.");
|
|
32768
|
+
return;
|
|
32769
|
+
}
|
|
32770
|
+
const serverId = await resolveTargetServerId({ server: opts.server });
|
|
32771
|
+
const result = await resetRunner(slockHome, serverId);
|
|
32772
|
+
if (result.status === "not-found") {
|
|
32773
|
+
fail(
|
|
32774
|
+
"RESET_RUNNER_NOT_FOUND",
|
|
32775
|
+
`Runner for server ${serverId} was not found.`
|
|
32776
|
+
);
|
|
32777
|
+
}
|
|
32778
|
+
if (opts.json) {
|
|
32779
|
+
info(JSON.stringify(result));
|
|
32780
|
+
return;
|
|
32781
|
+
}
|
|
32782
|
+
info(
|
|
32783
|
+
`Reset runner crash history for server ${result.serverId} (previous state: ${result.previousState}; cleared ${result.clearedCrashCount} entr${result.clearedCrashCount === 1 ? "y" : "ies"}).`
|
|
32784
|
+
);
|
|
32785
|
+
info("Runner state transitioned to `running`. Process was not respawned.");
|
|
32786
|
+
}
|
|
32787
|
+
|
|
32514
32788
|
// src/concurrency.ts
|
|
32515
32789
|
init_esm_shims();
|
|
32516
32790
|
var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
|
|
32517
|
-
import { mkdir as
|
|
32791
|
+
import { mkdir as mkdir11 } from "fs/promises";
|
|
32518
32792
|
import { join as join4 } from "path";
|
|
32519
32793
|
var STALE_LOCK_THRESHOLD_MS = 6e4;
|
|
32520
32794
|
async function withMutationLock(fn) {
|
|
32521
32795
|
const slockHome = resolveSlockHome();
|
|
32522
32796
|
const lockTarget = computerDir(slockHome);
|
|
32523
|
-
await
|
|
32797
|
+
await mkdir11(lockTarget, { recursive: true });
|
|
32524
32798
|
const lockfilePath = join4(lockTarget, ".lock");
|
|
32525
32799
|
let release = null;
|
|
32526
32800
|
try {
|
|
@@ -32559,8 +32833,8 @@ async function withMutationLock(fn) {
|
|
|
32559
32833
|
|
|
32560
32834
|
// src/channel.ts
|
|
32561
32835
|
init_esm_shims();
|
|
32562
|
-
import { readFile as
|
|
32563
|
-
import { dirname as
|
|
32836
|
+
import { readFile as readFile12, writeFile as writeFile10, mkdir as mkdir12 } from "fs/promises";
|
|
32837
|
+
import { dirname as dirname11 } from "path";
|
|
32564
32838
|
var DEFAULT_CHANNEL = "latest";
|
|
32565
32839
|
var SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
32566
32840
|
function parseChannel(raw) {
|
|
@@ -32575,7 +32849,7 @@ function parseChannel(raw) {
|
|
|
32575
32849
|
}
|
|
32576
32850
|
async function readChannel(slockHome) {
|
|
32577
32851
|
try {
|
|
32578
|
-
const raw = await
|
|
32852
|
+
const raw = await readFile12(channelPath(slockHome), "utf8");
|
|
32579
32853
|
const parsed = parseChannel(raw);
|
|
32580
32854
|
if (parsed !== null) return parsed;
|
|
32581
32855
|
} catch {
|
|
@@ -32584,8 +32858,8 @@ async function readChannel(slockHome) {
|
|
|
32584
32858
|
}
|
|
32585
32859
|
async function writeChannel(slockHome, channel2) {
|
|
32586
32860
|
const p = channelPath(slockHome);
|
|
32587
|
-
await
|
|
32588
|
-
await
|
|
32861
|
+
await mkdir12(dirname11(p), { recursive: true });
|
|
32862
|
+
await writeFile10(p, `${channel2}
|
|
32589
32863
|
`, { mode: 384 });
|
|
32590
32864
|
}
|
|
32591
32865
|
async function runChannelShow(slockHome) {
|
|
@@ -32603,25 +32877,25 @@ async function runChannelSet(slockHome, raw) {
|
|
|
32603
32877
|
}
|
|
32604
32878
|
await writeChannel(slockHome, parsed);
|
|
32605
32879
|
info(`Channel set to ${parsed}.`);
|
|
32606
|
-
info(`Note:
|
|
32880
|
+
info(`Note: service reads channel at startup; restart \`slock-computer start\` to apply.`);
|
|
32607
32881
|
}
|
|
32608
32882
|
|
|
32609
32883
|
// src/upgradeCli.ts
|
|
32610
32884
|
init_esm_shims();
|
|
32611
|
-
import { readFile as
|
|
32885
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
32612
32886
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
32613
|
-
import { dirname as
|
|
32887
|
+
import { dirname as dirname12, join as join7 } from "path";
|
|
32614
32888
|
|
|
32615
32889
|
// src/upgrade.ts
|
|
32616
32890
|
init_esm_shims();
|
|
32617
32891
|
import { spawn as spawn4 } from "child_process";
|
|
32618
|
-
import { mkdir as
|
|
32892
|
+
import { mkdir as mkdir13, readFile as readFile14, writeFile as writeFile11, rm as rm3, rename as rename4 } from "fs/promises";
|
|
32619
32893
|
import { join as join6 } from "path";
|
|
32620
32894
|
import { createHash as createHash3 } from "crypto";
|
|
32621
32895
|
|
|
32622
32896
|
// src/preflightDepDrift.ts
|
|
32623
32897
|
init_esm_shims();
|
|
32624
|
-
import { readFile as
|
|
32898
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
32625
32899
|
import { spawn as spawn3 } from "child_process";
|
|
32626
32900
|
import { createRequire } from "module";
|
|
32627
32901
|
import { join as join5 } from "path";
|
|
@@ -32630,12 +32904,10 @@ var DAEMON_PACKAGE_NAME = "@slock-ai/daemon";
|
|
|
32630
32904
|
async function preflightDepDriftCheck(currentBinaryDir, stagedTarballPath, deps = {}) {
|
|
32631
32905
|
const readTarball = deps.readTarballPackageJson ?? defaultReadTarballPackageJson;
|
|
32632
32906
|
const readCurrent = deps.readCurrentPackageJson ?? defaultReadCurrentPackageJson;
|
|
32633
|
-
const readInstalled = deps.readInstalledDaemonVersion ?? defaultReadInstalledDaemonVersion;
|
|
32634
32907
|
const satisfies = deps.semverSatisfies ?? defaultSemverSatisfies;
|
|
32635
32908
|
const allowUnsafe = deps.allowUnsafeSpec === true;
|
|
32636
32909
|
let targetPkg;
|
|
32637
32910
|
let currentPkg;
|
|
32638
|
-
let installedVersion;
|
|
32639
32911
|
try {
|
|
32640
32912
|
targetPkg = await readTarball(stagedTarballPath);
|
|
32641
32913
|
} catch (e) {
|
|
@@ -32654,15 +32926,6 @@ async function preflightDepDriftCheck(currentBinaryDir, stagedTarballPath, deps
|
|
|
32654
32926
|
detail: `cannot read current binary package.json: ${errMsg(e)}`
|
|
32655
32927
|
};
|
|
32656
32928
|
}
|
|
32657
|
-
try {
|
|
32658
|
-
installedVersion = await readInstalled(currentBinaryDir);
|
|
32659
|
-
} catch (e) {
|
|
32660
|
-
return {
|
|
32661
|
-
ok: false,
|
|
32662
|
-
reason: "read_failed",
|
|
32663
|
-
detail: `cannot read installed ${DAEMON_PACKAGE_NAME} version: ${errMsg(e)}`
|
|
32664
|
-
};
|
|
32665
|
-
}
|
|
32666
32929
|
const targetSpec = targetPkg.dependencies?.[DAEMON_PACKAGE_NAME];
|
|
32667
32930
|
const currentSpec = currentPkg.dependencies?.[DAEMON_PACKAGE_NAME];
|
|
32668
32931
|
if (typeof targetSpec !== "string" || targetSpec.length === 0) {
|
|
@@ -32673,60 +32936,64 @@ async function preflightDepDriftCheck(currentBinaryDir, stagedTarballPath, deps
|
|
|
32673
32936
|
currentSpec
|
|
32674
32937
|
};
|
|
32675
32938
|
}
|
|
32676
|
-
|
|
32939
|
+
const unsafeTarget = isUnsafeSpec(targetSpec) || isUnparseableRange(targetSpec);
|
|
32940
|
+
if (!allowUnsafe && unsafeTarget) {
|
|
32677
32941
|
return {
|
|
32678
32942
|
ok: false,
|
|
32679
|
-
reason: "
|
|
32680
|
-
detail:
|
|
32943
|
+
reason: "unsafe_spec",
|
|
32944
|
+
detail: `${DAEMON_PACKAGE_NAME} target spec is not a safe semver range (target="${targetSpec}"). Auto-upgrade only supports target semver ranges that can be hydrated and verified before swap.`,
|
|
32945
|
+
currentSpec,
|
|
32681
32946
|
targetSpec
|
|
32682
32947
|
};
|
|
32683
32948
|
}
|
|
32684
|
-
|
|
32949
|
+
void satisfies;
|
|
32950
|
+
return { ok: true, currentSpec, targetSpec };
|
|
32951
|
+
}
|
|
32952
|
+
async function verifyHydratedDaemonDependency(extractedPackageDir, targetSpec, deps = {}) {
|
|
32953
|
+
const readInstalled = deps.readInstalledDaemonVersion ?? defaultReadInstalledDaemonVersion;
|
|
32954
|
+
const satisfies = deps.semverSatisfies ?? defaultSemverSatisfies;
|
|
32955
|
+
const allowUnsafe = deps.allowUnsafeSpec === true;
|
|
32956
|
+
const unsafeTarget = isUnsafeSpec(targetSpec) || isUnparseableRange(targetSpec);
|
|
32957
|
+
if (!allowUnsafe && unsafeTarget) {
|
|
32685
32958
|
return {
|
|
32686
32959
|
ok: false,
|
|
32687
|
-
reason: "
|
|
32688
|
-
detail: `${DAEMON_PACKAGE_NAME} is not
|
|
32689
|
-
currentSpec,
|
|
32960
|
+
reason: "unsafe_spec",
|
|
32961
|
+
detail: `${DAEMON_PACKAGE_NAME} target spec is not a safe semver range (target="${targetSpec}").`,
|
|
32690
32962
|
targetSpec
|
|
32691
32963
|
};
|
|
32692
32964
|
}
|
|
32693
|
-
|
|
32694
|
-
|
|
32695
|
-
|
|
32696
|
-
|
|
32965
|
+
let installedVersion;
|
|
32966
|
+
try {
|
|
32967
|
+
installedVersion = await readInstalled(extractedPackageDir);
|
|
32968
|
+
} catch (e) {
|
|
32697
32969
|
return {
|
|
32698
32970
|
ok: false,
|
|
32699
|
-
reason: "
|
|
32700
|
-
detail:
|
|
32701
|
-
|
|
32702
|
-
targetSpec,
|
|
32703
|
-
installedVersion
|
|
32971
|
+
reason: "read_failed",
|
|
32972
|
+
detail: `cannot read hydrated ${DAEMON_PACKAGE_NAME} version: ${errMsg(e)}`,
|
|
32973
|
+
targetSpec
|
|
32704
32974
|
};
|
|
32705
32975
|
}
|
|
32706
|
-
if (
|
|
32976
|
+
if (installedVersion === null || installedVersion.length === 0) {
|
|
32707
32977
|
return {
|
|
32708
32978
|
ok: false,
|
|
32709
|
-
reason: "
|
|
32710
|
-
detail: `${DAEMON_PACKAGE_NAME}
|
|
32711
|
-
|
|
32712
|
-
targetSpec,
|
|
32713
|
-
installedVersion
|
|
32979
|
+
reason: "read_failed",
|
|
32980
|
+
detail: `${DAEMON_PACKAGE_NAME} is not installed in the hydrated staged package`,
|
|
32981
|
+
targetSpec
|
|
32714
32982
|
};
|
|
32715
32983
|
}
|
|
32716
|
-
if (allowUnsafe &&
|
|
32717
|
-
return { ok: true,
|
|
32984
|
+
if (allowUnsafe && unsafeTarget) {
|
|
32985
|
+
return { ok: true, targetSpec, installedVersion };
|
|
32718
32986
|
}
|
|
32719
32987
|
if (!satisfies(installedVersion, targetSpec)) {
|
|
32720
32988
|
return {
|
|
32721
32989
|
ok: false,
|
|
32722
32990
|
reason: "installed_unsatisfied",
|
|
32723
|
-
detail: `
|
|
32724
|
-
currentSpec,
|
|
32991
|
+
detail: `hydrated ${DAEMON_PACKAGE_NAME}@${installedVersion} does not satisfy target spec "${targetSpec}".`,
|
|
32725
32992
|
targetSpec,
|
|
32726
32993
|
installedVersion
|
|
32727
32994
|
};
|
|
32728
32995
|
}
|
|
32729
|
-
return { ok: true,
|
|
32996
|
+
return { ok: true, targetSpec, installedVersion };
|
|
32730
32997
|
}
|
|
32731
32998
|
function isUnparseableRange(spec) {
|
|
32732
32999
|
const s = spec.trim();
|
|
@@ -32792,7 +33059,7 @@ async function defaultReadTarballPackageJson(tarballPath) {
|
|
|
32792
33059
|
return JSON.parse(raw);
|
|
32793
33060
|
}
|
|
32794
33061
|
async function defaultReadCurrentPackageJson(currentBinaryDir) {
|
|
32795
|
-
const raw = await
|
|
33062
|
+
const raw = await readFile13(join5(currentBinaryDir, "package.json"), "utf8");
|
|
32796
33063
|
return JSON.parse(raw);
|
|
32797
33064
|
}
|
|
32798
33065
|
async function defaultReadInstalledDaemonVersion(currentBinaryDir) {
|
|
@@ -32809,7 +33076,7 @@ async function defaultReadInstalledDaemonVersion(currentBinaryDir) {
|
|
|
32809
33076
|
for (const base of searchPaths) {
|
|
32810
33077
|
const candidate = join5(base, ...subPath, "package.json");
|
|
32811
33078
|
try {
|
|
32812
|
-
const raw = await
|
|
33079
|
+
const raw = await readFile13(candidate, "utf8");
|
|
32813
33080
|
const parsed = JSON.parse(raw);
|
|
32814
33081
|
if (parsed.name !== DAEMON_PACKAGE_NAME) continue;
|
|
32815
33082
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
@@ -32889,9 +33156,9 @@ function satisfiesTilde(ver, base) {
|
|
|
32889
33156
|
// src/upgrade.ts
|
|
32890
33157
|
async function stagePhase(slockHome, version, deps = {}) {
|
|
32891
33158
|
const npmPack = deps.npmPack ?? defaultNpmPack;
|
|
32892
|
-
const fsReadFile = deps.fsReadFile ??
|
|
33159
|
+
const fsReadFile = deps.fsReadFile ?? readFile14;
|
|
32893
33160
|
const stagedPath = upgradeStagingDir(slockHome, version);
|
|
32894
|
-
await
|
|
33161
|
+
await mkdir13(stagedPath, { recursive: true });
|
|
32895
33162
|
const packageRef = `@slock-ai/computer@${version}`;
|
|
32896
33163
|
const result = await npmPack(stagedPath, packageRef);
|
|
32897
33164
|
if (result.exitCode !== 0) {
|
|
@@ -32999,8 +33266,8 @@ async function cleanupStaged(slockHome, version) {
|
|
|
32999
33266
|
async function snapshotPhase(slockHome, snap) {
|
|
33000
33267
|
const path3 = upgradeSnapshotPath(slockHome);
|
|
33001
33268
|
const tmp = `${path3}.tmp`;
|
|
33002
|
-
await
|
|
33003
|
-
await
|
|
33269
|
+
await mkdir13(computerDir(slockHome), { recursive: true });
|
|
33270
|
+
await writeFile11(tmp, JSON.stringify(snap, null, 2), { mode: 384 });
|
|
33004
33271
|
await rename4(tmp, path3);
|
|
33005
33272
|
}
|
|
33006
33273
|
async function clearUpgradeSnapshot(slockHome) {
|
|
@@ -33012,7 +33279,7 @@ async function clearUpgradeSnapshot(slockHome) {
|
|
|
33012
33279
|
}
|
|
33013
33280
|
async function extractTarball(tarballPath, destDir, deps = {}) {
|
|
33014
33281
|
const tarSpawn = deps.tarSpawn ?? defaultTarSpawn;
|
|
33015
|
-
await
|
|
33282
|
+
await mkdir13(destDir, { recursive: true });
|
|
33016
33283
|
const result = await tarSpawn(tarballPath, destDir);
|
|
33017
33284
|
if (result.exitCode !== 0) {
|
|
33018
33285
|
const err = new Error(
|
|
@@ -33116,49 +33383,49 @@ async function rollbackSwap(currentBinaryDir, deps = {}) {
|
|
|
33116
33383
|
}
|
|
33117
33384
|
}
|
|
33118
33385
|
async function rollbackRestart(slockHome, currentBinaryDir, deps = {}) {
|
|
33119
|
-
const
|
|
33386
|
+
const readServicePid = deps.readServicePid ?? (() => defaultReadServicePid(slockHome));
|
|
33120
33387
|
const isProcessAlive4 = deps.isProcessAlive ?? defaultIsProcessAlive;
|
|
33121
|
-
const
|
|
33122
|
-
const
|
|
33388
|
+
const killService = deps.killService ?? defaultKillService;
|
|
33389
|
+
const forceKillService = deps.forceKillService ?? defaultForceKillService;
|
|
33123
33390
|
const waitForExit = deps.waitForExit ?? defaultWaitForExit;
|
|
33124
|
-
const
|
|
33391
|
+
const spawnFreshService = deps.spawnFreshService;
|
|
33125
33392
|
const healthCheck = deps.healthCheck ?? (() => defaultHealthCheck(slockHome));
|
|
33126
33393
|
const healthTimeoutMs = deps.healthTimeoutMs ?? 3e4;
|
|
33127
33394
|
const healthPollIntervalMs = deps.healthPollIntervalMs ?? 500;
|
|
33128
|
-
let
|
|
33395
|
+
let serviceStopped = false;
|
|
33129
33396
|
try {
|
|
33130
|
-
const pid = await
|
|
33397
|
+
const pid = await readServicePid();
|
|
33131
33398
|
if (pid === null || !isProcessAlive4(pid)) {
|
|
33132
|
-
|
|
33399
|
+
serviceStopped = true;
|
|
33133
33400
|
} else {
|
|
33134
|
-
await
|
|
33401
|
+
await killService(pid);
|
|
33135
33402
|
const exited = await waitForExit(pid, 1e4);
|
|
33136
33403
|
if (!exited) {
|
|
33137
|
-
await
|
|
33404
|
+
await forceKillService(pid);
|
|
33138
33405
|
const escalatedExit = await waitForExit(pid, 5e3);
|
|
33139
|
-
|
|
33406
|
+
serviceStopped = escalatedExit;
|
|
33140
33407
|
} else {
|
|
33141
|
-
|
|
33408
|
+
serviceStopped = true;
|
|
33142
33409
|
}
|
|
33143
33410
|
}
|
|
33144
33411
|
} catch (e) {
|
|
33145
33412
|
return {
|
|
33146
33413
|
binaryRestored: false,
|
|
33147
|
-
|
|
33148
|
-
|
|
33149
|
-
|
|
33414
|
+
serviceStopped: false,
|
|
33415
|
+
serviceRespawned: false,
|
|
33416
|
+
serviceHealthy: false,
|
|
33150
33417
|
failedStep: "stop",
|
|
33151
33418
|
reason: e instanceof Error ? e.message : String(e)
|
|
33152
33419
|
};
|
|
33153
33420
|
}
|
|
33154
|
-
if (!
|
|
33421
|
+
if (!serviceStopped) {
|
|
33155
33422
|
return {
|
|
33156
33423
|
binaryRestored: false,
|
|
33157
|
-
|
|
33158
|
-
|
|
33159
|
-
|
|
33424
|
+
serviceStopped: false,
|
|
33425
|
+
serviceRespawned: false,
|
|
33426
|
+
serviceHealthy: false,
|
|
33160
33427
|
failedStep: "stop",
|
|
33161
|
-
reason: "new-version
|
|
33428
|
+
reason: "new-version service did not exit (graceful + SIGKILL both timed out)"
|
|
33162
33429
|
};
|
|
33163
33430
|
}
|
|
33164
33431
|
const swapRb = await rollbackSwap(currentBinaryDir, {
|
|
@@ -33168,31 +33435,31 @@ async function rollbackRestart(slockHome, currentBinaryDir, deps = {}) {
|
|
|
33168
33435
|
if (!swapRb.rolledBack) {
|
|
33169
33436
|
return {
|
|
33170
33437
|
binaryRestored: false,
|
|
33171
|
-
|
|
33172
|
-
|
|
33173
|
-
|
|
33438
|
+
serviceStopped: true,
|
|
33439
|
+
serviceRespawned: false,
|
|
33440
|
+
serviceHealthy: false,
|
|
33174
33441
|
failedStep: "binary",
|
|
33175
33442
|
reason: "could not restore .prev \u2192 currentBinaryDir"
|
|
33176
33443
|
};
|
|
33177
33444
|
}
|
|
33178
|
-
if (!
|
|
33445
|
+
if (!spawnFreshService) {
|
|
33179
33446
|
return {
|
|
33180
33447
|
binaryRestored: true,
|
|
33181
|
-
|
|
33182
|
-
|
|
33183
|
-
|
|
33448
|
+
serviceStopped: true,
|
|
33449
|
+
serviceRespawned: false,
|
|
33450
|
+
serviceHealthy: false,
|
|
33184
33451
|
failedStep: "respawn",
|
|
33185
|
-
reason: "no
|
|
33452
|
+
reason: "no spawnFreshService callback wired; old service not respawned"
|
|
33186
33453
|
};
|
|
33187
33454
|
}
|
|
33188
33455
|
try {
|
|
33189
|
-
await
|
|
33456
|
+
await spawnFreshService();
|
|
33190
33457
|
} catch (e) {
|
|
33191
33458
|
return {
|
|
33192
33459
|
binaryRestored: true,
|
|
33193
|
-
|
|
33194
|
-
|
|
33195
|
-
|
|
33460
|
+
serviceStopped: true,
|
|
33461
|
+
serviceRespawned: false,
|
|
33462
|
+
serviceHealthy: false,
|
|
33196
33463
|
failedStep: "respawn",
|
|
33197
33464
|
reason: e instanceof Error ? e.message : String(e)
|
|
33198
33465
|
};
|
|
@@ -33209,53 +33476,53 @@ async function rollbackRestart(slockHome, currentBinaryDir, deps = {}) {
|
|
|
33209
33476
|
if (!healthy) {
|
|
33210
33477
|
return {
|
|
33211
33478
|
binaryRestored: true,
|
|
33212
|
-
|
|
33213
|
-
|
|
33214
|
-
|
|
33479
|
+
serviceStopped: true,
|
|
33480
|
+
serviceRespawned: true,
|
|
33481
|
+
serviceHealthy: false,
|
|
33215
33482
|
failedStep: "health",
|
|
33216
|
-
reason: "old
|
|
33483
|
+
reason: "old service failed health check after rollback respawn"
|
|
33217
33484
|
};
|
|
33218
33485
|
}
|
|
33219
33486
|
return {
|
|
33220
33487
|
binaryRestored: true,
|
|
33221
|
-
|
|
33222
|
-
|
|
33223
|
-
|
|
33488
|
+
serviceStopped: true,
|
|
33489
|
+
serviceRespawned: true,
|
|
33490
|
+
serviceHealthy: true
|
|
33224
33491
|
};
|
|
33225
33492
|
}
|
|
33226
33493
|
async function restartPhase(slockHome, deps = {}) {
|
|
33227
|
-
const
|
|
33228
|
-
const
|
|
33229
|
-
const
|
|
33494
|
+
const readServicePid = deps.readServicePid ?? (() => defaultReadServicePid(slockHome));
|
|
33495
|
+
const killService = deps.killService ?? defaultKillService;
|
|
33496
|
+
const forceKillService = deps.forceKillService ?? defaultForceKillService;
|
|
33230
33497
|
const waitForExit = deps.waitForExit ?? defaultWaitForExit;
|
|
33231
|
-
const
|
|
33498
|
+
const spawnFreshService = deps.spawnFreshService;
|
|
33232
33499
|
const healthCheck = deps.healthCheck ?? (() => defaultHealthCheck(slockHome));
|
|
33233
33500
|
const healthTimeoutMs = deps.healthTimeoutMs ?? 3e4;
|
|
33234
33501
|
const healthPollIntervalMs = deps.healthPollIntervalMs ?? 500;
|
|
33235
33502
|
const forceKill = deps.forceKill === true;
|
|
33236
|
-
const oldPid = await
|
|
33503
|
+
const oldPid = await readServicePid();
|
|
33237
33504
|
if (oldPid !== null) {
|
|
33238
33505
|
try {
|
|
33239
33506
|
if (forceKill) {
|
|
33240
|
-
await
|
|
33507
|
+
await forceKillService(oldPid);
|
|
33241
33508
|
} else {
|
|
33242
|
-
await
|
|
33509
|
+
await killService(oldPid);
|
|
33243
33510
|
const exited = await waitForExit(oldPid, 1e4);
|
|
33244
33511
|
if (!exited) {
|
|
33245
|
-
await
|
|
33512
|
+
await forceKillService(oldPid);
|
|
33246
33513
|
const escalatedExit = await waitForExit(oldPid, 5e3);
|
|
33247
33514
|
if (!escalatedExit) {
|
|
33248
|
-
return { ok: false, reason: "
|
|
33515
|
+
return { ok: false, reason: "service_kill_failed" };
|
|
33249
33516
|
}
|
|
33250
33517
|
}
|
|
33251
33518
|
}
|
|
33252
33519
|
} catch {
|
|
33253
|
-
return { ok: false, reason: "
|
|
33520
|
+
return { ok: false, reason: "service_kill_failed" };
|
|
33254
33521
|
}
|
|
33255
33522
|
}
|
|
33256
|
-
if (
|
|
33523
|
+
if (spawnFreshService) {
|
|
33257
33524
|
try {
|
|
33258
|
-
await
|
|
33525
|
+
await spawnFreshService();
|
|
33259
33526
|
} catch {
|
|
33260
33527
|
return { ok: false, reason: "spawn_failed" };
|
|
33261
33528
|
}
|
|
@@ -33269,16 +33536,16 @@ async function restartPhase(slockHome, deps = {}) {
|
|
|
33269
33536
|
}
|
|
33270
33537
|
return { ok: false, reason: "health_check_timeout" };
|
|
33271
33538
|
}
|
|
33272
|
-
async function
|
|
33539
|
+
async function defaultReadServicePid(slockHome) {
|
|
33273
33540
|
try {
|
|
33274
|
-
const raw = (await
|
|
33541
|
+
const raw = (await readFile14(servicePidPath(slockHome), "utf8")).trim();
|
|
33275
33542
|
const pid = Number.parseInt(raw, 10);
|
|
33276
33543
|
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
33277
33544
|
} catch {
|
|
33278
33545
|
return null;
|
|
33279
33546
|
}
|
|
33280
33547
|
}
|
|
33281
|
-
async function
|
|
33548
|
+
async function defaultKillService(pid) {
|
|
33282
33549
|
try {
|
|
33283
33550
|
process.kill(pid, "SIGTERM");
|
|
33284
33551
|
} catch (err) {
|
|
@@ -33286,7 +33553,7 @@ async function defaultKillSupervisor(pid) {
|
|
|
33286
33553
|
throw err;
|
|
33287
33554
|
}
|
|
33288
33555
|
}
|
|
33289
|
-
async function
|
|
33556
|
+
async function defaultForceKillService(pid) {
|
|
33290
33557
|
try {
|
|
33291
33558
|
process.kill(pid, "SIGKILL");
|
|
33292
33559
|
} catch (err) {
|
|
@@ -33310,7 +33577,7 @@ async function defaultWaitForExit(pid, timeoutMs) {
|
|
|
33310
33577
|
return false;
|
|
33311
33578
|
}
|
|
33312
33579
|
async function defaultHealthCheck(slockHome) {
|
|
33313
|
-
const pid = await
|
|
33580
|
+
const pid = await defaultReadServicePid(slockHome);
|
|
33314
33581
|
if (pid === null) return false;
|
|
33315
33582
|
try {
|
|
33316
33583
|
process.kill(pid, 0);
|
|
@@ -33390,6 +33657,18 @@ async function runUpgrade(slockHome, opts) {
|
|
|
33390
33657
|
preflight
|
|
33391
33658
|
};
|
|
33392
33659
|
}
|
|
33660
|
+
const targetDaemonSpec = preflight.targetSpec;
|
|
33661
|
+
if (typeof targetDaemonSpec !== "string" || targetDaemonSpec.length === 0) {
|
|
33662
|
+
await cleanupStaged(slockHome, opts.targetVersion);
|
|
33663
|
+
return {
|
|
33664
|
+
ok: false,
|
|
33665
|
+
phase: "preflight",
|
|
33666
|
+
reason: `${preflight.detail ?? preflight.reason ?? "dep_drift"}: missing target ${DAEMON_PACKAGE_NAME} spec`,
|
|
33667
|
+
staged,
|
|
33668
|
+
verify,
|
|
33669
|
+
preflight
|
|
33670
|
+
};
|
|
33671
|
+
}
|
|
33393
33672
|
const snap = {
|
|
33394
33673
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
33395
33674
|
fromVersion: opts.fromVersion,
|
|
@@ -33440,6 +33719,24 @@ async function runUpgrade(slockHome, opts) {
|
|
|
33440
33719
|
verify
|
|
33441
33720
|
};
|
|
33442
33721
|
}
|
|
33722
|
+
const hydratedDaemon = await verifyHydratedDaemonDependency(
|
|
33723
|
+
extractedPackageDir,
|
|
33724
|
+
targetDaemonSpec,
|
|
33725
|
+
deps.preflight ?? {}
|
|
33726
|
+
);
|
|
33727
|
+
if (!hydratedDaemon.ok) {
|
|
33728
|
+
await cleanupStaged(slockHome, opts.targetVersion);
|
|
33729
|
+
await clearUpgradeSnapshot(slockHome);
|
|
33730
|
+
return {
|
|
33731
|
+
ok: false,
|
|
33732
|
+
phase: "hydrate",
|
|
33733
|
+
reason: `staged dependency tree verification failed: ${hydratedDaemon.detail ?? hydratedDaemon.reason ?? "hydrated dependency verification failed"}`,
|
|
33734
|
+
staged,
|
|
33735
|
+
verify,
|
|
33736
|
+
preflight,
|
|
33737
|
+
hydratedDaemon
|
|
33738
|
+
};
|
|
33739
|
+
}
|
|
33443
33740
|
let swap;
|
|
33444
33741
|
try {
|
|
33445
33742
|
swap = await swapPhase(opts.currentBinaryDir, extractedPackageDir, {
|
|
@@ -33456,11 +33753,11 @@ async function runUpgrade(slockHome, opts) {
|
|
|
33456
33753
|
};
|
|
33457
33754
|
}
|
|
33458
33755
|
const restart = await restartPhase(slockHome, {
|
|
33459
|
-
|
|
33460
|
-
|
|
33461
|
-
|
|
33756
|
+
readServicePid: deps.readServicePid,
|
|
33757
|
+
killService: deps.killService,
|
|
33758
|
+
forceKillService: deps.forceKillService,
|
|
33462
33759
|
waitForExit: deps.waitForExit,
|
|
33463
|
-
|
|
33760
|
+
spawnFreshService: deps.spawnFreshService,
|
|
33464
33761
|
healthCheck: deps.healthCheck,
|
|
33465
33762
|
healthTimeoutMs: deps.healthTimeoutMs,
|
|
33466
33763
|
healthPollIntervalMs: deps.healthPollIntervalMs,
|
|
@@ -33468,12 +33765,12 @@ async function runUpgrade(slockHome, opts) {
|
|
|
33468
33765
|
});
|
|
33469
33766
|
if (!restart.ok) {
|
|
33470
33767
|
const rb = await rollbackRestart(slockHome, opts.currentBinaryDir, {
|
|
33471
|
-
|
|
33768
|
+
readServicePid: deps.readServicePid,
|
|
33472
33769
|
isProcessAlive: deps.isProcessAlive,
|
|
33473
|
-
|
|
33474
|
-
|
|
33770
|
+
killService: deps.killService,
|
|
33771
|
+
forceKillService: deps.forceKillService,
|
|
33475
33772
|
waitForExit: deps.waitForExit,
|
|
33476
|
-
|
|
33773
|
+
spawnFreshService: deps.spawnFreshService,
|
|
33477
33774
|
healthCheck: deps.healthCheck,
|
|
33478
33775
|
healthTimeoutMs: deps.healthTimeoutMs,
|
|
33479
33776
|
healthPollIntervalMs: deps.healthPollIntervalMs,
|
|
@@ -33488,7 +33785,7 @@ async function runUpgrade(slockHome, opts) {
|
|
|
33488
33785
|
verify,
|
|
33489
33786
|
swap,
|
|
33490
33787
|
restart,
|
|
33491
|
-
rolledBack: rb.binaryRestored && rb.
|
|
33788
|
+
rolledBack: rb.binaryRestored && rb.serviceHealthy,
|
|
33492
33789
|
rollback: rb
|
|
33493
33790
|
};
|
|
33494
33791
|
}
|
|
@@ -33501,12 +33798,12 @@ async function runUpgrade(slockHome, opts) {
|
|
|
33501
33798
|
});
|
|
33502
33799
|
if (!rolling.ok) {
|
|
33503
33800
|
const rb = await rollbackRestart(slockHome, opts.currentBinaryDir, {
|
|
33504
|
-
|
|
33801
|
+
readServicePid: deps.readServicePid,
|
|
33505
33802
|
isProcessAlive: deps.isProcessAlive,
|
|
33506
|
-
|
|
33507
|
-
|
|
33803
|
+
killService: deps.killService,
|
|
33804
|
+
forceKillService: deps.forceKillService,
|
|
33508
33805
|
waitForExit: deps.waitForExit,
|
|
33509
|
-
|
|
33806
|
+
spawnFreshService: deps.spawnFreshService,
|
|
33510
33807
|
healthCheck: deps.healthCheck,
|
|
33511
33808
|
healthTimeoutMs: deps.healthTimeoutMs,
|
|
33512
33809
|
healthPollIntervalMs: deps.healthPollIntervalMs,
|
|
@@ -33523,12 +33820,12 @@ async function runUpgrade(slockHome, opts) {
|
|
|
33523
33820
|
swap,
|
|
33524
33821
|
restart,
|
|
33525
33822
|
rolling,
|
|
33526
|
-
rolledBack: rb.binaryRestored && rb.
|
|
33823
|
+
rolledBack: rb.binaryRestored && rb.serviceHealthy,
|
|
33527
33824
|
rollback: rb
|
|
33528
33825
|
};
|
|
33529
33826
|
}
|
|
33530
33827
|
await cleanupSuccessPhase(slockHome, opts.targetVersion, swap.prevBinaryDir);
|
|
33531
|
-
return { ok: true, phase: "cleanup", staged, verify, swap, restart, rolling };
|
|
33828
|
+
return { ok: true, phase: "cleanup", staged, verify, preflight, hydratedDaemon, swap, restart, rolling };
|
|
33532
33829
|
}
|
|
33533
33830
|
async function rollingDaemonHealthCheck(slockHome, deps = {}) {
|
|
33534
33831
|
const list = deps.listManagedServerIds ?? listManagedServerIds;
|
|
@@ -33564,7 +33861,7 @@ async function rollingDaemonHealthCheck(slockHome, deps = {}) {
|
|
|
33564
33861
|
}
|
|
33565
33862
|
async function defaultReadDaemonPid(slockHome, serverId) {
|
|
33566
33863
|
try {
|
|
33567
|
-
const raw = (await
|
|
33864
|
+
const raw = (await readFile14(serverDaemonPidPath(slockHome, serverId), "utf8")).trim();
|
|
33568
33865
|
const pid = Number.parseInt(raw, 10);
|
|
33569
33866
|
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
33570
33867
|
} catch {
|
|
@@ -33592,7 +33889,7 @@ async function locateStagedTarball(stagedPath) {
|
|
|
33592
33889
|
|
|
33593
33890
|
// src/upgradeLog.ts
|
|
33594
33891
|
init_esm_shims();
|
|
33595
|
-
import { chmod as chmod5, mkdir as
|
|
33892
|
+
import { chmod as chmod5, mkdir as mkdir14, open as open2 } from "fs/promises";
|
|
33596
33893
|
var FILE_MODE = 384;
|
|
33597
33894
|
var UPGRADE_ERROR_CODES = [
|
|
33598
33895
|
"UPGRADE_DEPS_CHANGED",
|
|
@@ -33637,7 +33934,7 @@ function assertUpgradeLogEntry(entry) {
|
|
|
33637
33934
|
}
|
|
33638
33935
|
async function appendUpgradeLogEntry(slockHome, entry) {
|
|
33639
33936
|
assertUpgradeLogEntry(entry);
|
|
33640
|
-
await
|
|
33937
|
+
await mkdir14(computerDir(slockHome), { recursive: true });
|
|
33641
33938
|
const path3 = upgradeLogPath(slockHome);
|
|
33642
33939
|
const at = entry.at ?? formatUpgradeLogTimestamp();
|
|
33643
33940
|
const fullEntry = { ...entry, at };
|
|
@@ -33660,7 +33957,7 @@ function isEphemeralNpxContext(binaryDir) {
|
|
|
33660
33957
|
async function readBundledDaemonVersion(binaryDir) {
|
|
33661
33958
|
try {
|
|
33662
33959
|
const pkgPath = join7(binaryDir, "package.json");
|
|
33663
|
-
const raw = await
|
|
33960
|
+
const raw = await readFile15(pkgPath, "utf8");
|
|
33664
33961
|
const parsed = JSON.parse(raw);
|
|
33665
33962
|
const pinned = parsed.dependencies?.["@slock-ai/daemon"];
|
|
33666
33963
|
if (typeof pinned !== "string" || pinned.length === 0) return null;
|
|
@@ -33670,8 +33967,8 @@ async function readBundledDaemonVersion(binaryDir) {
|
|
|
33670
33967
|
return null;
|
|
33671
33968
|
}
|
|
33672
33969
|
}
|
|
33673
|
-
async function
|
|
33674
|
-
await
|
|
33970
|
+
async function defaultSpawnFreshService(slockHome) {
|
|
33971
|
+
await spawnDetachedService(slockHome);
|
|
33675
33972
|
}
|
|
33676
33973
|
async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
33677
33974
|
let channel2;
|
|
@@ -33771,7 +34068,7 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
|
33771
34068
|
info(`Drain mode: ${drainMode}${drainMode === "force" ? " (in-flight turns will be dropped)" : ""}.`);
|
|
33772
34069
|
}
|
|
33773
34070
|
const runUpgradeFn = deps.runUpgradeFn ?? runUpgrade;
|
|
33774
|
-
const
|
|
34071
|
+
const spawnFreshService = deps.spawnFreshService ?? defaultSpawnFreshService;
|
|
33775
34072
|
const outcome = await runUpgradeFn(slockHome, {
|
|
33776
34073
|
targetVersion,
|
|
33777
34074
|
fromVersion,
|
|
@@ -33779,12 +34076,12 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
|
33779
34076
|
currentBinaryDir,
|
|
33780
34077
|
drainMode,
|
|
33781
34078
|
deps: {
|
|
33782
|
-
// Wire the production
|
|
34079
|
+
// Wire the production service-spawn into BOTH phase 5 (happy)
|
|
33783
34080
|
// and the operational-rollback respawn path inside `runUpgrade`.
|
|
33784
34081
|
// Without this, phase 5 falls through to a no-spawn health-poll
|
|
33785
34082
|
// and rollback later trips `failedStep="respawn"`. See Dayu
|
|
33786
34083
|
// blocker (#wg-slock-computer:b43b36fb msg=911eb84e).
|
|
33787
|
-
|
|
34084
|
+
spawnFreshService: () => spawnFreshService(slockHome)
|
|
33788
34085
|
}
|
|
33789
34086
|
});
|
|
33790
34087
|
const readBundledFn = deps.readBundledDaemonVersion ?? readBundledDaemonVersion;
|
|
@@ -33814,7 +34111,7 @@ async function runUpgradeCli(slockHome, opts, deps = {}) {
|
|
|
33814
34111
|
}
|
|
33815
34112
|
if (outcome.phase === "cleanup") {
|
|
33816
34113
|
info(
|
|
33817
|
-
`Upgrade ${fromVersion} \u2192 ${targetVersion} succeeded; cleanup phase reported a non-fatal issue: ${outcome.reason ?? "unknown"}. Active layout +
|
|
34114
|
+
`Upgrade ${fromVersion} \u2192 ${targetVersion} succeeded; cleanup phase reported a non-fatal issue: ${outcome.reason ?? "unknown"}. Active layout + service are healthy.`
|
|
33818
34115
|
);
|
|
33819
34116
|
await appendUpgradeLogEntry(slockHome, {
|
|
33820
34117
|
fromBundle: bundle(fromVersion),
|
|
@@ -33854,6 +34151,9 @@ function mapFailurePhaseToCode(outcome) {
|
|
|
33854
34151
|
case "extract":
|
|
33855
34152
|
return "UPGRADE_INTEGRITY_FAILED";
|
|
33856
34153
|
case "hydrate":
|
|
34154
|
+
if (outcome.hydratedDaemon?.ok === false) {
|
|
34155
|
+
return "UPGRADE_INTEGRITY_FAILED";
|
|
34156
|
+
}
|
|
33857
34157
|
return "UPGRADE_NETWORK_FAILED";
|
|
33858
34158
|
case "swap":
|
|
33859
34159
|
return "UPGRADE_SWAP_FAILED";
|
|
@@ -33888,12 +34188,12 @@ async function defaultFetchDistTags() {
|
|
|
33888
34188
|
}
|
|
33889
34189
|
function defaultCurrentBinaryDir() {
|
|
33890
34190
|
const here = fileURLToPath3(import.meta.url);
|
|
33891
|
-
return
|
|
34191
|
+
return dirname12(dirname12(here));
|
|
33892
34192
|
}
|
|
33893
34193
|
async function defaultCurrentVersion() {
|
|
33894
34194
|
const pkgPath = join7(defaultCurrentBinaryDir(), "package.json");
|
|
33895
34195
|
try {
|
|
33896
|
-
const raw = await
|
|
34196
|
+
const raw = await readFile15(pkgPath, "utf8");
|
|
33897
34197
|
const parsed = JSON.parse(raw);
|
|
33898
34198
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
33899
34199
|
return parsed.version;
|
|
@@ -33905,7 +34205,7 @@ async function defaultCurrentVersion() {
|
|
|
33905
34205
|
|
|
33906
34206
|
// src/upgradeTestHarness.ts
|
|
33907
34207
|
init_esm_shims();
|
|
33908
|
-
import { mkdir as
|
|
34208
|
+
import { mkdir as mkdir15, readdir as readdir4, stat as stat3, writeFile as writeFile12 } from "fs/promises";
|
|
33909
34209
|
import { join as join8 } from "path";
|
|
33910
34210
|
import { createHash as createHash4 } from "crypto";
|
|
33911
34211
|
var PHASES = /* @__PURE__ */ new Set([
|
|
@@ -33964,7 +34264,7 @@ function buildSimulatedDeps(slockHome, opts) {
|
|
|
33964
34264
|
return { tarballPath: "", exitCode: 1, stderr: "simulated stage failure" };
|
|
33965
34265
|
}
|
|
33966
34266
|
const filename = `slock-ai-computer-${targetVersion}.tgz`;
|
|
33967
|
-
await
|
|
34267
|
+
await writeFile12(join8(cwd, filename), tarballBytes);
|
|
33968
34268
|
return { tarballPath: join8(cwd, filename), exitCode: 0, stderr: "" };
|
|
33969
34269
|
},
|
|
33970
34270
|
fetchAdvertisedHash: async () => {
|
|
@@ -33976,16 +34276,16 @@ function buildSimulatedDeps(slockHome, opts) {
|
|
|
33976
34276
|
if (opts.simulateFail === "extract") {
|
|
33977
34277
|
return { exitCode: 1, stderr: "simulated extract failure" };
|
|
33978
34278
|
}
|
|
33979
|
-
await
|
|
33980
|
-
await
|
|
34279
|
+
await mkdir15(join8(destDir, "package"), { recursive: true });
|
|
34280
|
+
await writeFile12(join8(destDir, "package", "marker.txt"), `NEW@${targetVersion}`);
|
|
33981
34281
|
return { exitCode: 0, stderr: "" };
|
|
33982
34282
|
},
|
|
33983
34283
|
npmInstall: async () => ({ exitCode: 0, stderr: "" }),
|
|
33984
34284
|
fsRename: opts.simulateFail === "swap" ? async () => {
|
|
33985
34285
|
throw new Error("simulated swap failure: fsRename");
|
|
33986
34286
|
} : void 0,
|
|
33987
|
-
|
|
33988
|
-
|
|
34287
|
+
readServicePid: async () => null,
|
|
34288
|
+
spawnFreshService: async () => {
|
|
33989
34289
|
await maybeSleep();
|
|
33990
34290
|
spawnCalls += 1;
|
|
33991
34291
|
if (opts.simulateFail === "restart" && spawnCalls === 1) {
|
|
@@ -34038,7 +34338,7 @@ function buildSimulatedDeps(slockHome, opts) {
|
|
|
34038
34338
|
}
|
|
34039
34339
|
async function arrangeSnapshotFailure(slockHome) {
|
|
34040
34340
|
const snapshotPath = join8(slockHome, "computer", "upgrade-snapshot.json");
|
|
34041
|
-
await
|
|
34341
|
+
await mkdir15(snapshotPath, { recursive: true });
|
|
34042
34342
|
}
|
|
34043
34343
|
async function pathInfo(path3) {
|
|
34044
34344
|
try {
|
|
@@ -34077,7 +34377,7 @@ async function captureStateSnapshot(slockHome, version, currentBinaryDir) {
|
|
|
34077
34377
|
upgradeSnapshot: await pathInfo(upgradeSnapshotPath(slockHome)),
|
|
34078
34378
|
stagingDir: stagingExists,
|
|
34079
34379
|
stagingEntries,
|
|
34080
|
-
|
|
34380
|
+
servicePid: await pathInfo(servicePidPath(slockHome)),
|
|
34081
34381
|
channelFile: await pathInfo(join8(computerDir(slockHome), "channel")),
|
|
34082
34382
|
computerDir: await pathInfo(computerDir(slockHome)),
|
|
34083
34383
|
servers,
|
|
@@ -34093,12 +34393,12 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
|
|
|
34093
34393
|
process.exitCode = 1;
|
|
34094
34394
|
return;
|
|
34095
34395
|
}
|
|
34096
|
-
await
|
|
34396
|
+
await mkdir15(slockHome, { recursive: true });
|
|
34097
34397
|
if (opts.simulateFail === "snapshot") {
|
|
34098
34398
|
await arrangeSnapshotFailure(slockHome);
|
|
34099
34399
|
}
|
|
34100
34400
|
const { opts: upgradeOpts } = buildSimulatedDeps(slockHome, opts);
|
|
34101
|
-
await
|
|
34401
|
+
await mkdir15(upgradeOpts.currentBinaryDir, { recursive: true });
|
|
34102
34402
|
let outcome;
|
|
34103
34403
|
try {
|
|
34104
34404
|
outcome = await runUpgrade(slockHome, upgradeOpts);
|
|
@@ -34136,9 +34436,9 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
|
|
|
34136
34436
|
|
|
34137
34437
|
// src/upgradeInstallSmoke.ts
|
|
34138
34438
|
init_esm_shims();
|
|
34139
|
-
import { copyFile, mkdir as
|
|
34439
|
+
import { copyFile, mkdir as mkdir16, readFile as readFile16 } from "fs/promises";
|
|
34140
34440
|
import { createHash as createHash5 } from "crypto";
|
|
34141
|
-
import { dirname as
|
|
34441
|
+
import { dirname as dirname13, isAbsolute, join as join9, resolve as pathResolve } from "path";
|
|
34142
34442
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
34143
34443
|
async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
|
|
34144
34444
|
if (typeof opts.packageTarball !== "string" || opts.packageTarball.trim().length === 0) {
|
|
@@ -34147,7 +34447,7 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
|
|
|
34147
34447
|
const tarballPath = isAbsolute(opts.packageTarball) ? opts.packageTarball : pathResolve(opts.packageTarball);
|
|
34148
34448
|
let tarballBytes;
|
|
34149
34449
|
try {
|
|
34150
|
-
tarballBytes = await
|
|
34450
|
+
tarballBytes = await readFile16(tarballPath);
|
|
34151
34451
|
} catch (e) {
|
|
34152
34452
|
const msg = e instanceof Error ? e.message : String(e);
|
|
34153
34453
|
throw new Error(`__upgrade-install-smoke: cannot read --package-tarball ${tarballPath}: ${msg}`);
|
|
@@ -34156,10 +34456,10 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
|
|
|
34156
34456
|
const currentBinaryDir = opts.currentBinaryDir ?? (deps.currentBinaryDir ?? defaultCurrentBinaryDirLocal)();
|
|
34157
34457
|
const fromVersion = opts.fromVersion ?? await (deps.currentVersion ?? defaultCurrentVersionLocal)();
|
|
34158
34458
|
const channel2 = opts.channel ?? "latest";
|
|
34159
|
-
const
|
|
34160
|
-
await
|
|
34459
|
+
const spawnFreshService = deps.spawnFreshService ?? (async (h) => {
|
|
34460
|
+
await spawnDetachedService(h);
|
|
34161
34461
|
});
|
|
34162
|
-
await
|
|
34462
|
+
await mkdir16(slockHome, { recursive: true });
|
|
34163
34463
|
const outcome = await runUpgrade(slockHome, {
|
|
34164
34464
|
targetVersion: opts.targetVersion,
|
|
34165
34465
|
fromVersion,
|
|
@@ -34185,7 +34485,7 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
|
|
|
34185
34485
|
// dependencies from the registry; this smoke isolates the swap /
|
|
34186
34486
|
// restart mechanics and keeps dependency hydration out of scope.
|
|
34187
34487
|
npmInstall: async () => ({ exitCode: 0, stderr: "" }),
|
|
34188
|
-
|
|
34488
|
+
spawnFreshService: () => spawnFreshService(slockHome),
|
|
34189
34489
|
// PR-E 18/n: the workspace-built tarball still carries
|
|
34190
34490
|
// `workspace:*` (or `file:` after pack-rewrite in some workflows)
|
|
34191
34491
|
// for `@slock-ai/daemon`, which production preflight rejects as
|
|
@@ -34196,7 +34496,7 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
|
|
|
34196
34496
|
preflight: { allowUnsafeSpec: true }
|
|
34197
34497
|
}
|
|
34198
34498
|
});
|
|
34199
|
-
const activeVersion = await
|
|
34499
|
+
const activeVersion = await readServiceVersionEvidence(slockHome);
|
|
34200
34500
|
return { outcome, activeVersion };
|
|
34201
34501
|
}
|
|
34202
34502
|
async function runUpgradeInstallSmokeCli(slockHome, opts, writer = (s) => process.stdout.write(s)) {
|
|
@@ -34217,12 +34517,12 @@ async function runUpgradeInstallSmokeCli(slockHome, opts, writer = (s) => proces
|
|
|
34217
34517
|
}
|
|
34218
34518
|
function defaultCurrentBinaryDirLocal() {
|
|
34219
34519
|
const here = fileURLToPath4(import.meta.url);
|
|
34220
|
-
return
|
|
34520
|
+
return dirname13(dirname13(here));
|
|
34221
34521
|
}
|
|
34222
34522
|
async function defaultCurrentVersionLocal() {
|
|
34223
34523
|
const pkgPath = join9(defaultCurrentBinaryDirLocal(), "package.json");
|
|
34224
34524
|
try {
|
|
34225
|
-
const raw = await
|
|
34525
|
+
const raw = await readFile16(pkgPath, "utf8");
|
|
34226
34526
|
const parsed = JSON.parse(raw);
|
|
34227
34527
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
34228
34528
|
return parsed.version;
|
|
@@ -34267,22 +34567,18 @@ program2.name("slock-computer").description("Slock Computer \u2014 local-machine
|
|
|
34267
34567
|
program2.command("login").description("Log in via device-code (one user identity per Computer / SLOCK_HOME).").option("--server-url <url>", `Slock API base URL; defaults to SLOCK_SERVER_URL or ${DEFAULT_SLOCK_SERVER_URL}`).action(withCliExit(async (opts) => {
|
|
34268
34568
|
await runLogin({ serverUrl: opts.serverUrl });
|
|
34269
34569
|
}));
|
|
34270
|
-
program2.command("attach").argument("<serverSlug>", "target Slock server slug (canonical form `/myserver`; bare `myserver` accepted)").description("Attach this Computer to one Slock server (add-not-replace; multi-server OK).").option("--server-url <url>", `Slock API base URL; defaults to the saved user session, SLOCK_SERVER_URL, or ${DEFAULT_SLOCK_SERVER_URL}`).option("--name <name>", "Computer display name; defaults to a sanitized hostname").option("--no-run", "only authorize + write local state; do not start the
|
|
34570
|
+
program2.command("attach").argument("<serverSlug>", "target Slock server slug (canonical form `/myserver`; bare `myserver` accepted)").description("Attach this Computer to one Slock server (add-not-replace; multi-server OK).").option("--server-url <url>", `Slock API base URL; defaults to the saved user session, SLOCK_SERVER_URL, or ${DEFAULT_SLOCK_SERVER_URL}`).option("--name <name>", "Computer display name; defaults to a sanitized hostname").option("--no-run", "only authorize + write local state; do not start the service").option("--foreground", "run the service in this terminal instead of the background").action(withCliExit(async (serverSlug, opts) => {
|
|
34271
34571
|
await withMutationLock(
|
|
34272
34572
|
() => runAttach({ serverSlug, serverUrl: opts.serverUrl, name: opts.name, run: opts.run, foreground: opts.foreground })
|
|
34273
34573
|
);
|
|
34274
34574
|
}));
|
|
34275
|
-
program2.command("setup").argument("<serverSlug>", "target Slock server slug (canonical form `/myserver`; bare `myserver` accepted)").description("Set up this Computer for one server: login if needed, attach
|
|
34575
|
+
program2.command("setup").argument("<serverSlug>", "target Slock server slug (canonical form `/myserver`; bare `myserver` accepted)").description("Set up this Computer for one server: login if needed, attach (or \xA7X.1 migrate-prompt) if needed, then start.").option("--server-url <url>", `Slock API base URL; defaults to the saved user session, SLOCK_SERVER_URL, or ${DEFAULT_SLOCK_SERVER_URL}`).option("--name <name>", "Computer display name for a new attachment; defaults to a sanitized hostname").option("--no-start", "stop after login + attach; do not start the service").option("--foreground", "run the service in this terminal instead of the background").option("-y, --yes", "allow non-interactive setup after confirming the planned actions").action(
|
|
34276
34576
|
withCliExit(async (serverSlug, opts) => {
|
|
34277
34577
|
await withMutationLock(
|
|
34278
34578
|
() => runSetup({
|
|
34279
34579
|
serverSlug,
|
|
34280
34580
|
serverUrl: opts.serverUrl,
|
|
34281
34581
|
name: opts.name,
|
|
34282
|
-
adoptLegacy: opts.adoptLegacy,
|
|
34283
|
-
legacyApiKey: opts.legacyApiKey,
|
|
34284
|
-
legacyApiKeyFile: opts.legacyApiKeyFile,
|
|
34285
|
-
legacyApiKeyStdin: opts.legacyApiKeyStdin,
|
|
34286
34582
|
start: opts.start,
|
|
34287
34583
|
foreground: opts.foreground,
|
|
34288
34584
|
yes: opts.yes
|
|
@@ -34290,29 +34586,13 @@ program2.command("setup").argument("<serverSlug>", "target Slock server slug (ca
|
|
|
34290
34586
|
);
|
|
34291
34587
|
})
|
|
34292
34588
|
);
|
|
34293
|
-
program2.command("adopt-legacy").argument("<serverSlug>", "target Slock server slug (the legacy machine's server)").description(
|
|
34294
|
-
"One-time migration: trade a legacy sk_machine_* / sk_daemon_* key for a fresh Computer attachment (sk_computer_*)."
|
|
34295
|
-
).option("--server-url <url>", `Slock API base URL; defaults to the saved user session, SLOCK_SERVER_URL, or ${DEFAULT_SLOCK_SERVER_URL}`).option("--name <name>", "name to record on the new Computer attachment (default: existing machine name)").option("--legacy-api-key <key>", "raw legacy key on argv (insecure on shared shells; prefer --legacy-api-key-file or stdin)").option("--legacy-api-key-file <path>", "path to a 0600 file containing the legacy key (one line)").option("--legacy-api-key-stdin", "read the legacy key from stdin").action(
|
|
34296
|
-
withCliExit(async (serverSlug, opts) => {
|
|
34297
|
-
await withMutationLock(
|
|
34298
|
-
() => runAdoptLegacy({
|
|
34299
|
-
serverSlug,
|
|
34300
|
-
serverUrl: opts.serverUrl,
|
|
34301
|
-
name: opts.name,
|
|
34302
|
-
legacyApiKey: opts.legacyApiKey,
|
|
34303
|
-
legacyApiKeyFile: opts.legacyApiKeyFile,
|
|
34304
|
-
legacyApiKeyStdin: opts.legacyApiKeyStdin
|
|
34305
|
-
})
|
|
34306
|
-
);
|
|
34307
|
-
})
|
|
34308
|
-
);
|
|
34309
34589
|
program2.command("detach").argument("<serverSlug>", "server slug to detach from this Computer (canonical form `/myserver`)").description("Remove ONE server's local attachment; never touches user-session or other servers.").action(withCliExit(async (serverSlug) => {
|
|
34310
34590
|
await withMutationLock(async () => runDetach(await resolveTargetServerId({ server: serverSlug }), serverSlug));
|
|
34311
34591
|
}));
|
|
34312
|
-
program2.command("status").description("Show this Computer's aggregate state (login +
|
|
34592
|
+
program2.command("status").description("Show this Computer's aggregate state (login + service + per-server daemons).").option("--json", "emit the machine-readable report").action(withCliExit(async (opts) => {
|
|
34313
34593
|
await runStatus({ json: opts.json });
|
|
34314
34594
|
}));
|
|
34315
|
-
program2.command("start").argument("[serverSlug]", "optional: verify this server is attached and ensure its daemon is reconciled (default: ensure all attached)").description("Start/ensure the Computer
|
|
34595
|
+
program2.command("start").argument("[serverSlug]", "optional: verify this server is attached and ensure its daemon is reconciled (default: ensure all attached)").description("Start/ensure the Computer service (manages all per-server daemons).").option("--foreground", "stay in this terminal instead of detaching").action(withCliExit(async (serverSlug, opts) => {
|
|
34316
34596
|
await withMutationLock(
|
|
34317
34597
|
async () => runStart({
|
|
34318
34598
|
foreground: opts.foreground,
|
|
@@ -34321,10 +34601,10 @@ program2.command("start").argument("[serverSlug]", "optional: verify this server
|
|
|
34321
34601
|
})
|
|
34322
34602
|
);
|
|
34323
34603
|
}));
|
|
34324
|
-
program2.command("stop").description("Stop the Computer
|
|
34604
|
+
program2.command("stop").description("Stop the Computer service (and all managed per-server daemons).").action(withCliExit(async () => {
|
|
34325
34605
|
await withMutationLock(() => runStop());
|
|
34326
34606
|
}));
|
|
34327
|
-
program2.command("doctor").argument("[serverSlug]", "optional: scope detail (recent crashes) to one server").description("Diagnose login + per-server attachments + per-server preflight (no secrets).").option("--json", "emit the machine-readable report").option("--cleanup", "after diagnosis, run the local residue cleanup pass").option("--fix", "alias for --cleanup (same behavior)").option("--reset-health", "clear <serverSlug>'s crash history so
|
|
34607
|
+
program2.command("doctor").argument("[serverSlug]", "optional: scope detail (recent crashes) to one server").description("Diagnose login + per-server attachments + per-server preflight (no secrets).").option("--json", "emit the machine-readable report").option("--cleanup", "after diagnosis, run the local residue cleanup pass").option("--fix", "alias for --cleanup (same behavior)").option("--reset-health", "clear <serverSlug>'s crash history so service resumes auto-restart").action(
|
|
34328
34608
|
withCliExit(
|
|
34329
34609
|
async (serverSlug, opts) => {
|
|
34330
34610
|
const serverId = serverSlug ? await resolveTargetServerId({ server: serverSlug }) : void 0;
|
|
@@ -34339,8 +34619,20 @@ program2.command("doctor").argument("[serverSlug]", "optional: scope detail (rec
|
|
|
34339
34619
|
}
|
|
34340
34620
|
)
|
|
34341
34621
|
);
|
|
34342
|
-
program2.command("
|
|
34343
|
-
|
|
34622
|
+
program2.command("reset").description("Clear a degraded state and resume the service's auto-restart loop.").option("--service", "clear the service-level crash history (cascade record)").option("--runner", "clear a runner's crash history (selected by `--server`)").option("--server <slug>", "with `--runner`, select target server slug (required when \u22652 attached)").option("--json", "emit the machine-readable result").action(
|
|
34623
|
+
withCliExit(async (opts) => {
|
|
34624
|
+
await withMutationLock(
|
|
34625
|
+
() => runReset({
|
|
34626
|
+
service: opts.service,
|
|
34627
|
+
runner: opts.runner,
|
|
34628
|
+
server: opts.server,
|
|
34629
|
+
json: opts.json
|
|
34630
|
+
})
|
|
34631
|
+
);
|
|
34632
|
+
})
|
|
34633
|
+
);
|
|
34634
|
+
program2.command("logs").description("Tail one server's daemon log (or the service log); secrets redacted.").option("--lines <n>", "trailing lines to show (default 200)", (v) => Number.parseInt(v, 10)).option("--server <slug>", "select target server slug (required when \u22652 attached)").option("--service", "tail the global service log instead of a per-server daemon log").action(withCliExit(async (opts) => {
|
|
34635
|
+
await runLogs({ lines: opts.lines, server: opts.server ?? null, service: !!opts.service });
|
|
34344
34636
|
}));
|
|
34345
34637
|
var runners = program2.command("runners").description("Computer runner control plane (per-server scoped; \xA712 whitelist server-side).");
|
|
34346
34638
|
runners.command("list").description("List runners on one attached server.").option("--json", "emit the machine-readable list").option("--server <slug>", "select target server slug (required when \u22652 attached)").action(withCliExit(async (opts) => {
|
|
@@ -34364,9 +34656,8 @@ program2.command("upgrade").description(
|
|
|
34364
34656
|
[
|
|
34365
34657
|
"Auto-upgrade this Computer to the latest version in its channel (or --target-version <semver>).",
|
|
34366
34658
|
"",
|
|
34367
|
-
"
|
|
34368
|
-
"
|
|
34369
|
-
"UPGRADE_DEPS_CHANGED and require manual `npm install -g @slock-ai/computer@<version>`."
|
|
34659
|
+
"Stages the target package, hydrates production dependencies, verifies the",
|
|
34660
|
+
"hydrated @slock-ai/daemon version, then swaps the Computer package root."
|
|
34370
34661
|
].join("\n")
|
|
34371
34662
|
).option("--dry-run", "stage + verify only; do not swap or restart").option("--channel <name>", "override channel for this invocation (latest | alpha | pinned:<semver>)").option("--target-version <semver>", "override target version explicitly (bypasses channel resolution)").option("--drain <mode>", "\xA72.7 drain mode: drain (default) | defer (abort if daemon busy) | force (SIGKILL, drop in-flight)").option("--force", "alias for --drain force (stuck-daemon recovery; drops in-flight turns)").action(
|
|
34372
34663
|
withCliExit(
|
|
@@ -34421,8 +34712,8 @@ program2.command("upgrade").description(
|
|
|
34421
34712
|
}
|
|
34422
34713
|
)
|
|
34423
34714
|
);
|
|
34424
|
-
program2.command("
|
|
34425
|
-
await
|
|
34715
|
+
program2.command("__service", { hidden: true }).action(withCliExit(async () => {
|
|
34716
|
+
await runService();
|
|
34426
34717
|
}));
|
|
34427
34718
|
program2.command("__run", { hidden: true }).argument("<serverId>", "server id this daemon child is bound to").action(withCliExit(async (serverId) => {
|
|
34428
34719
|
await runResident(serverId);
|
|
@@ -34459,21 +34750,6 @@ program2.command("__upgrade-install-smoke", { hidden: true }).requiredOption("--
|
|
|
34459
34750
|
);
|
|
34460
34751
|
})
|
|
34461
34752
|
);
|
|
34462
|
-
{
|
|
34463
|
-
const argv = process.argv.slice(2);
|
|
34464
|
-
const isAdopt = argv[0] === "adopt-legacy";
|
|
34465
|
-
const isSetup = argv[0] === "setup";
|
|
34466
|
-
const strayLegacyFlag = argv.find(
|
|
34467
|
-
(a) => a === "--legacy-api-key" || a.startsWith("--legacy-api-key=") || a === "--legacy-api-key-file" || a.startsWith("--legacy-api-key-file=") || a === "--legacy-api-key-stdin"
|
|
34468
|
-
);
|
|
34469
|
-
if (strayLegacyFlag && !isAdopt && !isSetup) {
|
|
34470
|
-
process.stderr.write(
|
|
34471
|
-
`slock-computer: LEGACY_KEY_OUTSIDE_ADOPT: ${strayLegacyFlag} is only accepted by \`slock-computer adopt-legacy\` or \`slock-computer setup --adopt-legacy\`. Remove it or run an adopt flow.
|
|
34472
|
-
`
|
|
34473
|
-
);
|
|
34474
|
-
process.exit(2);
|
|
34475
|
-
}
|
|
34476
|
-
}
|
|
34477
34753
|
program2.parseAsync(process.argv).catch((err) => {
|
|
34478
34754
|
process.stderr.write(`slock-computer: ${err instanceof Error ? err.message : String(err)}
|
|
34479
34755
|
`);
|