@slock-ai/computer 0.0.13 → 0.0.15
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 +1492 -713
- package/dist/lib/index.d.ts +554 -0
- package/dist/lib/index.js +505 -0
- package/package.json +9 -2
package/dist/index.js
CHANGED
|
@@ -6917,13 +6917,13 @@ var require_infra = __commonJS({
|
|
|
6917
6917
|
}
|
|
6918
6918
|
function collectASequenceOfCodePointsFast(char, input, position) {
|
|
6919
6919
|
const idx = input.indexOf(char, position.position);
|
|
6920
|
-
const
|
|
6920
|
+
const start2 = position.position;
|
|
6921
6921
|
if (idx === -1) {
|
|
6922
6922
|
position.position = input.length;
|
|
6923
|
-
return input.slice(
|
|
6923
|
+
return input.slice(start2);
|
|
6924
6924
|
}
|
|
6925
6925
|
position.position = idx;
|
|
6926
|
-
return input.slice(
|
|
6926
|
+
return input.slice(start2, position.position);
|
|
6927
6927
|
}
|
|
6928
6928
|
var ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g;
|
|
6929
6929
|
function forgivingBase64(data) {
|
|
@@ -9193,11 +9193,11 @@ var require_formdata_parser = __commonJS({
|
|
|
9193
9193
|
}
|
|
9194
9194
|
}
|
|
9195
9195
|
function collectASequenceOfBytes(condition, input, position) {
|
|
9196
|
-
let
|
|
9197
|
-
while (
|
|
9198
|
-
++
|
|
9196
|
+
let start2 = position.position;
|
|
9197
|
+
while (start2 < input.length && condition(input[start2])) {
|
|
9198
|
+
++start2;
|
|
9199
9199
|
}
|
|
9200
|
-
return input.subarray(position.position, position.position =
|
|
9200
|
+
return input.subarray(position.position, position.position = start2);
|
|
9201
9201
|
}
|
|
9202
9202
|
function removeChars(buf, leading, trailing, predicate) {
|
|
9203
9203
|
let lead = 0;
|
|
@@ -9210,12 +9210,12 @@ var require_formdata_parser = __commonJS({
|
|
|
9210
9210
|
}
|
|
9211
9211
|
return lead === 0 && trail === buf.length - 1 ? buf : buf.subarray(lead, trail + 1);
|
|
9212
9212
|
}
|
|
9213
|
-
function bufferStartsWith(buffer,
|
|
9214
|
-
if (buffer.length <
|
|
9213
|
+
function bufferStartsWith(buffer, start2, position) {
|
|
9214
|
+
if (buffer.length < start2.length) {
|
|
9215
9215
|
return false;
|
|
9216
9216
|
}
|
|
9217
|
-
for (let i = 0; i <
|
|
9218
|
-
if (
|
|
9217
|
+
for (let i = 0; i < start2.length; i++) {
|
|
9218
|
+
if (start2[i] !== buffer[position.position + i]) {
|
|
9219
9219
|
return false;
|
|
9220
9220
|
}
|
|
9221
9221
|
}
|
|
@@ -9657,8 +9657,8 @@ var require_client_h1 = __commonJS({
|
|
|
9657
9657
|
*/
|
|
9658
9658
|
wasm_on_status: (p, at, len) => {
|
|
9659
9659
|
assert(currentParser.ptr === p);
|
|
9660
|
-
const
|
|
9661
|
-
return currentParser.onStatus(new FastBuffer(currentBufferRef.buffer,
|
|
9660
|
+
const start2 = at - currentBufferPtr + currentBufferRef.byteOffset;
|
|
9661
|
+
return currentParser.onStatus(new FastBuffer(currentBufferRef.buffer, start2, len));
|
|
9662
9662
|
},
|
|
9663
9663
|
/**
|
|
9664
9664
|
* @param {number} p
|
|
@@ -9676,8 +9676,8 @@ var require_client_h1 = __commonJS({
|
|
|
9676
9676
|
*/
|
|
9677
9677
|
wasm_on_header_field: (p, at, len) => {
|
|
9678
9678
|
assert(currentParser.ptr === p);
|
|
9679
|
-
const
|
|
9680
|
-
return currentParser.onHeaderField(new FastBuffer(currentBufferRef.buffer,
|
|
9679
|
+
const start2 = at - currentBufferPtr + currentBufferRef.byteOffset;
|
|
9680
|
+
return currentParser.onHeaderField(new FastBuffer(currentBufferRef.buffer, start2, len));
|
|
9681
9681
|
},
|
|
9682
9682
|
/**
|
|
9683
9683
|
* @param {number} p
|
|
@@ -9687,8 +9687,8 @@ var require_client_h1 = __commonJS({
|
|
|
9687
9687
|
*/
|
|
9688
9688
|
wasm_on_header_value: (p, at, len) => {
|
|
9689
9689
|
assert(currentParser.ptr === p);
|
|
9690
|
-
const
|
|
9691
|
-
return currentParser.onHeaderValue(new FastBuffer(currentBufferRef.buffer,
|
|
9690
|
+
const start2 = at - currentBufferPtr + currentBufferRef.byteOffset;
|
|
9691
|
+
return currentParser.onHeaderValue(new FastBuffer(currentBufferRef.buffer, start2, len));
|
|
9692
9692
|
},
|
|
9693
9693
|
/**
|
|
9694
9694
|
* @param {number} p
|
|
@@ -9709,8 +9709,8 @@ var require_client_h1 = __commonJS({
|
|
|
9709
9709
|
*/
|
|
9710
9710
|
wasm_on_body: (p, at, len) => {
|
|
9711
9711
|
assert(currentParser.ptr === p);
|
|
9712
|
-
const
|
|
9713
|
-
return currentParser.onBody(new FastBuffer(currentBufferRef.buffer,
|
|
9712
|
+
const start2 = at - currentBufferPtr + currentBufferRef.byteOffset;
|
|
9713
|
+
return currentParser.onBody(new FastBuffer(currentBufferRef.buffer, start2, len));
|
|
9714
9714
|
},
|
|
9715
9715
|
/**
|
|
9716
9716
|
* @param {number} p
|
|
@@ -13962,8 +13962,8 @@ var require_retry_handler = __commonJS({
|
|
|
13962
13962
|
data: { count: this.retryCount }
|
|
13963
13963
|
});
|
|
13964
13964
|
}
|
|
13965
|
-
const { start, size, end = size ? size - 1 : null } = contentRange;
|
|
13966
|
-
assert(this.start ===
|
|
13965
|
+
const { start: start2, size, end = size ? size - 1 : null } = contentRange;
|
|
13966
|
+
assert(this.start === start2, "content-range mismatch");
|
|
13967
13967
|
assert(this.end == null || this.end === end, "content-range mismatch");
|
|
13968
13968
|
return;
|
|
13969
13969
|
}
|
|
@@ -13980,13 +13980,13 @@ var require_retry_handler = __commonJS({
|
|
|
13980
13980
|
);
|
|
13981
13981
|
return;
|
|
13982
13982
|
}
|
|
13983
|
-
const { start, size, end = size ? size - 1 : null } = range;
|
|
13983
|
+
const { start: start2, size, end = size ? size - 1 : null } = range;
|
|
13984
13984
|
assert(
|
|
13985
|
-
|
|
13985
|
+
start2 != null && Number.isFinite(start2),
|
|
13986
13986
|
"content-range mismatch"
|
|
13987
13987
|
);
|
|
13988
13988
|
assert(end != null && Number.isFinite(end), "invalid content-length");
|
|
13989
|
-
this.start =
|
|
13989
|
+
this.start = start2;
|
|
13990
13990
|
this.end = end;
|
|
13991
13991
|
}
|
|
13992
13992
|
if (this.end == null) {
|
|
@@ -14473,9 +14473,9 @@ var require_readable = __commonJS({
|
|
|
14473
14473
|
}
|
|
14474
14474
|
const { _readableState: state } = consume2.stream;
|
|
14475
14475
|
if (state.bufferIndex) {
|
|
14476
|
-
const
|
|
14476
|
+
const start2 = state.bufferIndex;
|
|
14477
14477
|
const end = state.buffer.length;
|
|
14478
|
-
for (let n =
|
|
14478
|
+
for (let n = start2; n < end; n++) {
|
|
14479
14479
|
consumePush(consume2, state.buffer[n]);
|
|
14480
14480
|
}
|
|
14481
14481
|
} else {
|
|
@@ -14500,11 +14500,11 @@ var require_readable = __commonJS({
|
|
|
14500
14500
|
}
|
|
14501
14501
|
const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks, length);
|
|
14502
14502
|
const bufferLength = buffer.length;
|
|
14503
|
-
const
|
|
14503
|
+
const start2 = bufferLength > 2 && buffer[0] === 239 && buffer[1] === 187 && buffer[2] === 191 ? 3 : 0;
|
|
14504
14504
|
if (!encoding || encoding === "utf8" || encoding === "utf-8") {
|
|
14505
|
-
return buffer.utf8Slice(
|
|
14505
|
+
return buffer.utf8Slice(start2, bufferLength);
|
|
14506
14506
|
} else {
|
|
14507
|
-
return buffer.subarray(
|
|
14507
|
+
return buffer.subarray(start2, bufferLength).toString(encoding);
|
|
14508
14508
|
}
|
|
14509
14509
|
}
|
|
14510
14510
|
function chunksConcat(chunks, length) {
|
|
@@ -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
|
|
@@ -22862,7 +22862,7 @@ var require_fetch = __commonJS({
|
|
|
22862
22862
|
function handleFetchDone(response) {
|
|
22863
22863
|
finalizeAndReportTiming(response, "fetch");
|
|
22864
22864
|
}
|
|
22865
|
-
function
|
|
22865
|
+
function fetch5(input, init = void 0) {
|
|
22866
22866
|
webidl.argumentLengthCheck(arguments, 1, "globalThis.fetch");
|
|
22867
22867
|
let p = createDeferredPromise();
|
|
22868
22868
|
let requestObject;
|
|
@@ -23892,7 +23892,7 @@ var require_fetch = __commonJS({
|
|
|
23892
23892
|
}
|
|
23893
23893
|
}
|
|
23894
23894
|
module.exports = {
|
|
23895
|
-
fetch:
|
|
23895
|
+
fetch: fetch5,
|
|
23896
23896
|
Fetch,
|
|
23897
23897
|
fetching,
|
|
23898
23898
|
finalizeAndReportTiming
|
|
@@ -27902,7 +27902,7 @@ var require_undici = __commonJS({
|
|
|
27902
27902
|
err.stack = stack ? `${stack}
|
|
27903
27903
|
${captureLines}` : capture.stack;
|
|
27904
27904
|
}
|
|
27905
|
-
module.exports.fetch = function
|
|
27905
|
+
module.exports.fetch = function fetch5(init, options = void 0) {
|
|
27906
27906
|
return fetchImpl(init, options).catch((err) => {
|
|
27907
27907
|
if (currentFilename) {
|
|
27908
27908
|
appendFetchStackTrace(err, currentFilename);
|
|
@@ -28040,10 +28040,10 @@ var require_polyfills = __commonJS({
|
|
|
28040
28040
|
if (platform === "win32") {
|
|
28041
28041
|
fs.rename = typeof fs.rename !== "function" ? fs.rename : (function(fs$rename) {
|
|
28042
28042
|
function rename5(from, to, cb) {
|
|
28043
|
-
var
|
|
28043
|
+
var start2 = Date.now();
|
|
28044
28044
|
var backoff = 0;
|
|
28045
28045
|
fs$rename(from, to, function CB(er) {
|
|
28046
|
-
if (er && (er.code === "EACCES" || er.code === "EPERM" || er.code === "EBUSY") && Date.now() -
|
|
28046
|
+
if (er && (er.code === "EACCES" || er.code === "EPERM" || er.code === "EBUSY") && Date.now() - start2 < 6e4) {
|
|
28047
28047
|
setTimeout(function() {
|
|
28048
28048
|
fs.stat(to, function(stater, st) {
|
|
28049
28049
|
if (stater && stater.code === "ENOENT")
|
|
@@ -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);
|
|
@@ -29094,7 +29094,7 @@ var require_signal_exit = __commonJS({
|
|
|
29094
29094
|
emitter.count -= 1;
|
|
29095
29095
|
};
|
|
29096
29096
|
module.exports.unload = unload;
|
|
29097
|
-
|
|
29097
|
+
emit7 = function emit8(event, code, signal) {
|
|
29098
29098
|
if (emitter.emitted[event]) {
|
|
29099
29099
|
return;
|
|
29100
29100
|
}
|
|
@@ -29110,8 +29110,8 @@ var require_signal_exit = __commonJS({
|
|
|
29110
29110
|
var listeners = process2.listeners(sig);
|
|
29111
29111
|
if (listeners.length === emitter.count) {
|
|
29112
29112
|
unload();
|
|
29113
|
-
|
|
29114
|
-
|
|
29113
|
+
emit7("exit", null, sig);
|
|
29114
|
+
emit7("afterexit", null, sig);
|
|
29115
29115
|
if (isWin && sig === "SIGHUP") {
|
|
29116
29116
|
sig = "SIGINT";
|
|
29117
29117
|
}
|
|
@@ -29148,8 +29148,8 @@ var require_signal_exit = __commonJS({
|
|
|
29148
29148
|
}
|
|
29149
29149
|
process2.exitCode = code || /* istanbul ignore next */
|
|
29150
29150
|
0;
|
|
29151
|
-
|
|
29152
|
-
|
|
29151
|
+
emit7("exit", process2.exitCode, null);
|
|
29152
|
+
emit7("afterexit", process2.exitCode, null);
|
|
29153
29153
|
originalProcessReallyExit.call(process2, process2.exitCode);
|
|
29154
29154
|
};
|
|
29155
29155
|
originalProcessEmit = process2.emit;
|
|
@@ -29159,8 +29159,8 @@ var require_signal_exit = __commonJS({
|
|
|
29159
29159
|
process2.exitCode = arg;
|
|
29160
29160
|
}
|
|
29161
29161
|
var ret = originalProcessEmit.apply(this, arguments);
|
|
29162
|
-
|
|
29163
|
-
|
|
29162
|
+
emit7("exit", process2.exitCode, null);
|
|
29163
|
+
emit7("afterexit", process2.exitCode, null);
|
|
29164
29164
|
return ret;
|
|
29165
29165
|
} else {
|
|
29166
29166
|
return originalProcessEmit.apply(this, arguments);
|
|
@@ -29173,7 +29173,7 @@ var require_signal_exit = __commonJS({
|
|
|
29173
29173
|
var EE;
|
|
29174
29174
|
var emitter;
|
|
29175
29175
|
var unload;
|
|
29176
|
-
var
|
|
29176
|
+
var emit7;
|
|
29177
29177
|
var sigListeners;
|
|
29178
29178
|
var loaded;
|
|
29179
29179
|
var load;
|
|
@@ -29591,6 +29591,48 @@ var {
|
|
|
29591
29591
|
|
|
29592
29592
|
// src/login.ts
|
|
29593
29593
|
init_esm_shims();
|
|
29594
|
+
|
|
29595
|
+
// src/output.ts
|
|
29596
|
+
init_esm_shims();
|
|
29597
|
+
var CliExit = class extends Error {
|
|
29598
|
+
/**
|
|
29599
|
+
* v8.3.3 PR-2c — carry the closed-set / stderr token through the thrown
|
|
29600
|
+
* error so callers can pattern-match without parsing stderr. Optional
|
|
29601
|
+
* because some callsites throw `new CliExit(code)` directly without a
|
|
29602
|
+
* named token (e.g. the harness EX_CONFIG paths).
|
|
29603
|
+
*/
|
|
29604
|
+
constructor(exitCode, code) {
|
|
29605
|
+
super(`CliExit(${exitCode}${code ? ` ${code}` : ""})`);
|
|
29606
|
+
this.exitCode = exitCode;
|
|
29607
|
+
this.code = code;
|
|
29608
|
+
this.name = "CliExit";
|
|
29609
|
+
}
|
|
29610
|
+
};
|
|
29611
|
+
function info(line) {
|
|
29612
|
+
process.stdout.write(`${line}
|
|
29613
|
+
`);
|
|
29614
|
+
}
|
|
29615
|
+
function fail(code, message, exitCode = 1) {
|
|
29616
|
+
process.stderr.write(`${JSON.stringify({ ok: false, code, message })}
|
|
29617
|
+
`);
|
|
29618
|
+
throw new CliExit(exitCode, code);
|
|
29619
|
+
}
|
|
29620
|
+
|
|
29621
|
+
// src/services/errors.ts
|
|
29622
|
+
init_esm_shims();
|
|
29623
|
+
var ComputerServiceError = class extends Error {
|
|
29624
|
+
code;
|
|
29625
|
+
cause;
|
|
29626
|
+
constructor(code, message, cause) {
|
|
29627
|
+
super(message);
|
|
29628
|
+
this.name = "ComputerServiceError";
|
|
29629
|
+
this.code = code;
|
|
29630
|
+
if (cause !== void 0) this.cause = cause;
|
|
29631
|
+
}
|
|
29632
|
+
};
|
|
29633
|
+
|
|
29634
|
+
// src/services/login.ts
|
|
29635
|
+
init_esm_shims();
|
|
29594
29636
|
import { mkdir, writeFile } from "fs/promises";
|
|
29595
29637
|
import { dirname } from "path";
|
|
29596
29638
|
|
|
@@ -29849,6 +29891,9 @@ function serverManagedFlagPath(slockHome, serverId) {
|
|
|
29849
29891
|
function serverHealthPath(slockHome, serverId) {
|
|
29850
29892
|
return path2.join(serverDir(slockHome, serverId), "health.json");
|
|
29851
29893
|
}
|
|
29894
|
+
function serviceStatePath(slockHome) {
|
|
29895
|
+
return path2.join(computerDir(slockHome), "service.state.json");
|
|
29896
|
+
}
|
|
29852
29897
|
function supervisorPidPath(slockHome) {
|
|
29853
29898
|
return path2.join(computerDir(slockHome), "supervisor.pid");
|
|
29854
29899
|
}
|
|
@@ -29901,32 +29946,6 @@ function formatUpgradeLogTimestamp(date = /* @__PURE__ */ new Date()) {
|
|
|
29901
29946
|
return iso.replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
29902
29947
|
}
|
|
29903
29948
|
|
|
29904
|
-
// src/output.ts
|
|
29905
|
-
init_esm_shims();
|
|
29906
|
-
var CliExit = class extends Error {
|
|
29907
|
-
/**
|
|
29908
|
-
* v8.3.3 PR-2c — carry the closed-set / stderr token through the thrown
|
|
29909
|
-
* error so callers can pattern-match without parsing stderr. Optional
|
|
29910
|
-
* because some callsites throw `new CliExit(code)` directly without a
|
|
29911
|
-
* named token (e.g. the harness EX_CONFIG paths).
|
|
29912
|
-
*/
|
|
29913
|
-
constructor(exitCode, code) {
|
|
29914
|
-
super(`CliExit(${exitCode}${code ? ` ${code}` : ""})`);
|
|
29915
|
-
this.exitCode = exitCode;
|
|
29916
|
-
this.code = code;
|
|
29917
|
-
this.name = "CliExit";
|
|
29918
|
-
}
|
|
29919
|
-
};
|
|
29920
|
-
function info(line) {
|
|
29921
|
-
process.stdout.write(`${line}
|
|
29922
|
-
`);
|
|
29923
|
-
}
|
|
29924
|
-
function fail(code, message, exitCode = 1) {
|
|
29925
|
-
process.stderr.write(`${JSON.stringify({ ok: false, code, message })}
|
|
29926
|
-
`);
|
|
29927
|
-
throw new CliExit(exitCode, code);
|
|
29928
|
-
}
|
|
29929
|
-
|
|
29930
29949
|
// src/serverUrl.ts
|
|
29931
29950
|
init_esm_shims();
|
|
29932
29951
|
var DEFAULT_SLOCK_SERVER_URL = "https://api.slock.ai";
|
|
@@ -29938,62 +29957,146 @@ function resolveServerUrl(...candidates) {
|
|
|
29938
29957
|
return DEFAULT_SLOCK_SERVER_URL;
|
|
29939
29958
|
}
|
|
29940
29959
|
|
|
29941
|
-
// src/login.ts
|
|
29942
|
-
function sleep(ms) {
|
|
29943
|
-
return new Promise((
|
|
29960
|
+
// src/services/login.ts
|
|
29961
|
+
function sleep(ms, signal) {
|
|
29962
|
+
return new Promise((resolve, reject) => {
|
|
29963
|
+
if (signal?.aborted) {
|
|
29964
|
+
reject(abortError(signal));
|
|
29965
|
+
return;
|
|
29966
|
+
}
|
|
29967
|
+
const t = setTimeout(() => {
|
|
29968
|
+
signal?.removeEventListener("abort", onAbort);
|
|
29969
|
+
resolve();
|
|
29970
|
+
}, ms);
|
|
29971
|
+
const onAbort = () => {
|
|
29972
|
+
clearTimeout(t);
|
|
29973
|
+
reject(abortError(signal));
|
|
29974
|
+
};
|
|
29975
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
29976
|
+
});
|
|
29944
29977
|
}
|
|
29945
|
-
|
|
29946
|
-
const
|
|
29978
|
+
function abortError(signal) {
|
|
29979
|
+
const reason = signal.reason;
|
|
29980
|
+
if (reason instanceof Error) return reason;
|
|
29981
|
+
const err = new Error("aborted");
|
|
29982
|
+
err.name = "AbortError";
|
|
29983
|
+
return err;
|
|
29984
|
+
}
|
|
29985
|
+
function emit(opts, event) {
|
|
29986
|
+
const cb = opts?.onEvent;
|
|
29987
|
+
if (!cb) return;
|
|
29988
|
+
try {
|
|
29989
|
+
cb(event);
|
|
29990
|
+
} catch {
|
|
29991
|
+
}
|
|
29992
|
+
}
|
|
29993
|
+
async function login(input, options = {}) {
|
|
29994
|
+
const baseUrl = resolveServerUrl(input.serverUrl, process.env.SLOCK_SERVER_URL);
|
|
29995
|
+
options.signal?.throwIfAborted?.();
|
|
29947
29996
|
const client = new DeviceAuthClient(baseUrl);
|
|
29948
29997
|
let grant;
|
|
29949
29998
|
try {
|
|
29950
29999
|
grant = await client.authorize("slock-computer");
|
|
29951
30000
|
} catch (err) {
|
|
29952
30001
|
const reason = err instanceof Error ? err.message : String(err);
|
|
29953
|
-
|
|
30002
|
+
throw new ComputerServiceError(
|
|
29954
30003
|
"DEVICE_AUTHORIZE_FAILED",
|
|
29955
|
-
`Could not start device login at ${baseUrl}: ${reason}. Check that the server URL is correct and reachable
|
|
30004
|
+
`Could not start device login at ${baseUrl}: ${reason}. Check that the server URL is correct and reachable.`,
|
|
30005
|
+
err
|
|
29956
30006
|
);
|
|
29957
30007
|
}
|
|
29958
30008
|
const verificationUri = grant.verificationUriComplete || grant.verificationUri;
|
|
29959
30009
|
const verifyUrl = new URL(verificationUri, baseUrl).toString();
|
|
29960
|
-
|
|
29961
|
-
|
|
29962
|
-
|
|
30010
|
+
const expiresAt = new Date(Date.now() + grant.expiresIn * 1e3).toISOString();
|
|
30011
|
+
emit(options, {
|
|
30012
|
+
type: "device-code",
|
|
30013
|
+
verifyUrl,
|
|
30014
|
+
userCode: grant.userCode,
|
|
30015
|
+
expiresAt,
|
|
30016
|
+
expiresInSeconds: grant.expiresIn
|
|
30017
|
+
});
|
|
29963
30018
|
const intervalMs = Math.max(1, grant.interval) * 1e3;
|
|
29964
30019
|
const deadline = Date.now() + grant.expiresIn * 1e3;
|
|
29965
30020
|
while (Date.now() < deadline) {
|
|
29966
|
-
|
|
30021
|
+
options.signal?.throwIfAborted?.();
|
|
30022
|
+
await sleep(intervalMs, options.signal);
|
|
30023
|
+
emit(options, { type: "polling" });
|
|
29967
30024
|
const r = await client.token(grant.deviceCode);
|
|
29968
30025
|
if (r.status === "pending") continue;
|
|
29969
|
-
if (r.status === "denied")
|
|
29970
|
-
|
|
29971
|
-
|
|
30026
|
+
if (r.status === "denied") {
|
|
30027
|
+
throw new ComputerServiceError("LOGIN_DENIED", "Login was denied in the approval page.");
|
|
30028
|
+
}
|
|
30029
|
+
if (r.status === "expired") {
|
|
30030
|
+
throw new ComputerServiceError(
|
|
30031
|
+
"LOGIN_EXPIRED",
|
|
30032
|
+
"Login request expired before approval. Re-run `slock-computer login`."
|
|
30033
|
+
);
|
|
30034
|
+
}
|
|
30035
|
+
if (r.status === "error") {
|
|
30036
|
+
throw new ComputerServiceError(
|
|
30037
|
+
"LOGIN_FAILED",
|
|
30038
|
+
`Login failed (${r.code}). Re-run \`slock-computer login\`.`
|
|
30039
|
+
);
|
|
30040
|
+
}
|
|
29972
30041
|
const slockHome = resolveSlockHome();
|
|
29973
30042
|
const file = userSessionPath(slockHome);
|
|
29974
30043
|
await mkdir(dirname(file), { recursive: true });
|
|
29975
30044
|
await writeFile(
|
|
29976
30045
|
file,
|
|
29977
30046
|
JSON.stringify(
|
|
29978
|
-
{
|
|
30047
|
+
{
|
|
30048
|
+
kind: "user-session",
|
|
30049
|
+
userId: r.userId,
|
|
30050
|
+
accessToken: r.accessToken,
|
|
30051
|
+
refreshToken: r.refreshToken,
|
|
30052
|
+
serverUrl: baseUrl,
|
|
30053
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
30054
|
+
},
|
|
29979
30055
|
null,
|
|
29980
30056
|
2
|
|
29981
30057
|
),
|
|
29982
30058
|
{ mode: 384 }
|
|
29983
30059
|
);
|
|
29984
|
-
|
|
29985
|
-
|
|
29986
|
-
|
|
30060
|
+
emit(options, { type: "approved", userId: r.userId, sessionPath: file });
|
|
30061
|
+
return { userId: r.userId, sessionPath: file, serverUrl: baseUrl };
|
|
30062
|
+
}
|
|
30063
|
+
throw new ComputerServiceError(
|
|
30064
|
+
"LOGIN_EXPIRED",
|
|
30065
|
+
"Login request expired before approval. Re-run `slock-computer login`."
|
|
30066
|
+
);
|
|
30067
|
+
}
|
|
30068
|
+
|
|
30069
|
+
// src/login.ts
|
|
30070
|
+
async function runLogin(opts) {
|
|
30071
|
+
try {
|
|
30072
|
+
await login(
|
|
30073
|
+
{ serverUrl: opts.serverUrl },
|
|
30074
|
+
{
|
|
30075
|
+
onEvent: (event) => {
|
|
30076
|
+
if (event.type === "device-code") {
|
|
30077
|
+
info(`To finish login, open: ${event.verifyUrl}`);
|
|
30078
|
+
info(`and enter the code: ${event.userCode}`);
|
|
30079
|
+
info(`Waiting for approval (expires in ${event.expiresInSeconds}s)\u2026`);
|
|
30080
|
+
} else if (event.type === "approved") {
|
|
30081
|
+
info(`Logged in. User session written to ${event.sessionPath}`);
|
|
30082
|
+
if (!opts.orchestrated) {
|
|
30083
|
+
info(`Next: run \`slock-computer attach <serverSlug>\` to attach this machine.`);
|
|
30084
|
+
}
|
|
30085
|
+
}
|
|
30086
|
+
}
|
|
30087
|
+
}
|
|
30088
|
+
);
|
|
30089
|
+
} catch (err) {
|
|
30090
|
+
if (err instanceof CliExit) throw err;
|
|
30091
|
+
if (err instanceof ComputerServiceError) {
|
|
30092
|
+
fail(err.code, err.message);
|
|
29987
30093
|
}
|
|
29988
|
-
|
|
30094
|
+
throw err;
|
|
29989
30095
|
}
|
|
29990
|
-
fail("LOGIN_EXPIRED", "Login request expired before approval. Re-run `slock-computer login`.");
|
|
29991
30096
|
}
|
|
29992
30097
|
|
|
29993
30098
|
// src/attach.ts
|
|
29994
30099
|
init_esm_shims();
|
|
29995
|
-
import { chmod as chmod2, mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
29996
|
-
import { dirname as dirname3 } from "path";
|
|
29997
30100
|
|
|
29998
30101
|
// src/serverState.ts
|
|
29999
30102
|
init_esm_shims();
|
|
@@ -30102,77 +30205,95 @@ async function listManagedServerIds(slockHome) {
|
|
|
30102
30205
|
return out;
|
|
30103
30206
|
}
|
|
30104
30207
|
|
|
30105
|
-
// src/attach.ts
|
|
30106
|
-
|
|
30208
|
+
// src/services/attach.ts
|
|
30209
|
+
init_esm_shims();
|
|
30210
|
+
import { chmod as chmod2, mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
30211
|
+
import { dirname as dirname3 } from "path";
|
|
30212
|
+
function emit2(opts, event) {
|
|
30213
|
+
const cb = opts?.onEvent;
|
|
30214
|
+
if (!cb) return;
|
|
30215
|
+
try {
|
|
30216
|
+
cb(event);
|
|
30217
|
+
} catch {
|
|
30218
|
+
}
|
|
30219
|
+
}
|
|
30220
|
+
async function attach(input, options = {}) {
|
|
30221
|
+
options.signal?.throwIfAborted?.();
|
|
30107
30222
|
const slockHome = resolveSlockHome();
|
|
30108
30223
|
const sessionFile = userSessionPath(slockHome);
|
|
30109
30224
|
let session;
|
|
30110
30225
|
try {
|
|
30111
30226
|
session = JSON.parse(await readFile2(sessionFile, "utf8"));
|
|
30112
|
-
} catch {
|
|
30113
|
-
|
|
30227
|
+
} catch (err) {
|
|
30228
|
+
throw new ComputerServiceError(
|
|
30114
30229
|
"NO_USER_SESSION",
|
|
30115
|
-
`No user session at ${sessionFile}. Run \`slock-computer login\` first
|
|
30230
|
+
`No user session at ${sessionFile}. Run \`slock-computer login\` first.`,
|
|
30231
|
+
err
|
|
30116
30232
|
);
|
|
30117
30233
|
}
|
|
30118
30234
|
if (session.kind !== "user-session" || typeof session.accessToken !== "string" || !session.accessToken) {
|
|
30119
|
-
|
|
30235
|
+
throw new ComputerServiceError(
|
|
30120
30236
|
"INVALID_USER_SESSION",
|
|
30121
30237
|
`User session at ${sessionFile} is invalid. Re-run \`slock-computer login\`.`
|
|
30122
30238
|
);
|
|
30123
30239
|
}
|
|
30124
|
-
const baseUrl = resolveServerUrl(
|
|
30125
|
-
const
|
|
30126
|
-
const slugForServer = normalizeServerSlug(opts.serverSlug);
|
|
30240
|
+
const baseUrl = resolveServerUrl(input.serverUrl, session.serverUrl, process.env.SLOCK_SERVER_URL);
|
|
30241
|
+
const slugForServer = normalizeServerSlug(input.serverSlug);
|
|
30127
30242
|
if (!slugForServer) {
|
|
30128
|
-
|
|
30243
|
+
throw new ComputerServiceError("ATTACH_NOT_AUTHORIZED", "Server slug must not be empty.");
|
|
30129
30244
|
}
|
|
30130
|
-
const computerName =
|
|
30131
|
-
|
|
30245
|
+
const computerName = input.name?.trim() || deriveDefaultComputerName();
|
|
30246
|
+
options.signal?.throwIfAborted?.();
|
|
30247
|
+
emit2(options, { type: "attaching", serverSlug: slugForServer });
|
|
30248
|
+
const client = new ComputerAttachClient(baseUrl, session.accessToken);
|
|
30132
30249
|
const attached = await client.attach(slugForServer, computerName);
|
|
30133
30250
|
if (attached.status === "disabled") {
|
|
30134
|
-
|
|
30251
|
+
throw new ComputerServiceError(
|
|
30135
30252
|
"ATTACH_DISABLED",
|
|
30136
30253
|
"Computer attach is not enabled on this server (ask an admin to unset SLOCK_DEVICE_LOGIN_ENABLED or set it back to a non-false value; this surface is on by default since PR-G \u2014 upgrade the server if you are on an older build)."
|
|
30137
30254
|
);
|
|
30138
30255
|
}
|
|
30139
30256
|
if (attached.status === "not_authorized") {
|
|
30140
|
-
|
|
30257
|
+
throw new ComputerServiceError(
|
|
30141
30258
|
"ATTACH_NOT_AUTHORIZED",
|
|
30142
30259
|
"Not authorized to attach to that server. Check the server slug and that you're a member."
|
|
30143
30260
|
);
|
|
30144
30261
|
}
|
|
30145
30262
|
if (attached.status === "server_not_found") {
|
|
30146
|
-
|
|
30263
|
+
throw new ComputerServiceError(
|
|
30147
30264
|
"ATTACH_SERVER_NOT_FOUND",
|
|
30148
30265
|
`Server ${formatServerSlugDisplay(slugForServer)} was not found on ${baseUrl}. Check the slug spelling and --server-url, then retry.`
|
|
30149
30266
|
);
|
|
30150
30267
|
}
|
|
30151
30268
|
if (attached.status === "error") {
|
|
30152
30269
|
if (attached.code === "session_invalid") {
|
|
30153
|
-
|
|
30270
|
+
throw new ComputerServiceError(
|
|
30154
30271
|
"USER_SESSION_EXPIRED",
|
|
30155
30272
|
"Your user session is no longer valid. Re-run `slock-computer login`."
|
|
30156
30273
|
);
|
|
30157
30274
|
}
|
|
30158
30275
|
if (attached.code === "request_failed") {
|
|
30159
|
-
|
|
30276
|
+
throw new ComputerServiceError(
|
|
30160
30277
|
"ATTACH_REQUEST_FAILED",
|
|
30161
30278
|
`Could not reach ${baseUrl} while attaching to ${formatServerSlugDisplay(slugForServer)}. Check --server-url / network connectivity, then retry.`
|
|
30162
30279
|
);
|
|
30163
30280
|
}
|
|
30164
30281
|
if (attached.code === "COMPUTER_NAME_COLLISION") {
|
|
30165
|
-
|
|
30282
|
+
throw new ComputerServiceError(
|
|
30166
30283
|
"COMPUTER_NAME_COLLISION",
|
|
30167
30284
|
`A Computer named ${JSON.stringify(computerName)} already exists on that server. Re-run with --name <name>.`
|
|
30168
30285
|
);
|
|
30169
30286
|
}
|
|
30170
|
-
|
|
30287
|
+
throw new ComputerServiceError(
|
|
30288
|
+
"ATTACH_FAILED",
|
|
30289
|
+
`Attach failed (${attached.code}). Re-run after checking --server-url / server version.`
|
|
30290
|
+
);
|
|
30171
30291
|
}
|
|
30172
|
-
|
|
30292
|
+
emit2(options, { type: "preflight", resumed: attached.resumed });
|
|
30293
|
+
options.signal?.throwIfAborted?.();
|
|
30173
30294
|
const pre = await client.preflight(attached.apiKey);
|
|
30174
30295
|
if (!pre.ok) {
|
|
30175
|
-
|
|
30296
|
+
throw new ComputerServiceError(
|
|
30176
30297
|
"PREFLIGHT_FAILED",
|
|
30177
30298
|
`Server preflight failed (${pre.code}). The server's Computer surface is not aligned; nothing was written locally. Upgrade the server or retry.`
|
|
30178
30299
|
);
|
|
@@ -30197,9 +30318,57 @@ async function runAttach(opts) {
|
|
|
30197
30318
|
{ mode: 384 }
|
|
30198
30319
|
);
|
|
30199
30320
|
await chmod2(file, 384);
|
|
30200
|
-
|
|
30201
|
-
|
|
30202
|
-
|
|
30321
|
+
const apiKeyRedactedPrefix = attached.apiKey.slice(0, 8);
|
|
30322
|
+
emit2(options, {
|
|
30323
|
+
type: "attached",
|
|
30324
|
+
serverId: attached.serverId,
|
|
30325
|
+
serverMachineId: attached.serverMachineId,
|
|
30326
|
+
serverSlug: attached.serverSlug,
|
|
30327
|
+
attachmentPath: file,
|
|
30328
|
+
resumed: attached.resumed,
|
|
30329
|
+
apiKeyRedactedPrefix
|
|
30330
|
+
});
|
|
30331
|
+
return {
|
|
30332
|
+
serverId: attached.serverId,
|
|
30333
|
+
serverMachineId: attached.serverMachineId,
|
|
30334
|
+
serverSlug: attached.serverSlug,
|
|
30335
|
+
serverUrl: baseUrl,
|
|
30336
|
+
attachmentPath: file,
|
|
30337
|
+
resumed: attached.resumed,
|
|
30338
|
+
apiKeyRedactedPrefix
|
|
30339
|
+
};
|
|
30340
|
+
}
|
|
30341
|
+
|
|
30342
|
+
// src/attach.ts
|
|
30343
|
+
async function runAttach(opts) {
|
|
30344
|
+
try {
|
|
30345
|
+
await attach(
|
|
30346
|
+
{
|
|
30347
|
+
serverSlug: opts.serverSlug,
|
|
30348
|
+
serverUrl: opts.serverUrl,
|
|
30349
|
+
name: opts.name
|
|
30350
|
+
},
|
|
30351
|
+
{
|
|
30352
|
+
onEvent: (event) => {
|
|
30353
|
+
if (event.type === "attaching") {
|
|
30354
|
+
info(`Attaching this machine to server ${formatServerSlugDisplay(event.serverSlug)}\u2026`);
|
|
30355
|
+
} else if (event.type === "preflight") {
|
|
30356
|
+
info(event.resumed ? "Resumed existing attachment; running preflight\u2026" : "Attachment issued; running preflight\u2026");
|
|
30357
|
+
} else if (event.type === "attached") {
|
|
30358
|
+
info(`Attached. Computer state written to ${event.attachmentPath}`);
|
|
30359
|
+
info(` server: ${formatServerSlugDisplay(event.serverSlug)}`);
|
|
30360
|
+
info(` serverMachine: ${event.serverMachineId}`);
|
|
30361
|
+
}
|
|
30362
|
+
}
|
|
30363
|
+
}
|
|
30364
|
+
);
|
|
30365
|
+
} catch (err) {
|
|
30366
|
+
if (err instanceof CliExit) throw err;
|
|
30367
|
+
if (err instanceof ComputerServiceError) {
|
|
30368
|
+
fail(err.code, err.message);
|
|
30369
|
+
}
|
|
30370
|
+
throw err;
|
|
30371
|
+
}
|
|
30203
30372
|
if (opts.orchestrated) {
|
|
30204
30373
|
return;
|
|
30205
30374
|
}
|
|
@@ -30210,85 +30379,92 @@ async function runAttach(opts) {
|
|
|
30210
30379
|
}
|
|
30211
30380
|
}
|
|
30212
30381
|
|
|
30213
|
-
// src/
|
|
30382
|
+
// src/setup.ts
|
|
30214
30383
|
init_esm_shims();
|
|
30215
|
-
|
|
30216
|
-
import {
|
|
30217
|
-
import { dirname as
|
|
30218
|
-
|
|
30219
|
-
|
|
30220
|
-
|
|
30221
|
-
|
|
30222
|
-
|
|
30223
|
-
|
|
30224
|
-
|
|
30225
|
-
|
|
30384
|
+
var import_undici3 = __toESM(require_undici(), 1);
|
|
30385
|
+
import { chmod as chmod4, mkdir as mkdir9, readFile as readFile8, rename as rename3, rm as rm2, writeFile as writeFile8 } from "fs/promises";
|
|
30386
|
+
import { dirname as dirname9 } from "path";
|
|
30387
|
+
|
|
30388
|
+
// src/lib/migration.ts
|
|
30389
|
+
init_esm_shims();
|
|
30390
|
+
import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
|
|
30391
|
+
import { join } from "path";
|
|
30392
|
+
var MACHINE_DIR_PREFIX = "machine-";
|
|
30393
|
+
async function readOwnerEvidence(installRoot, machineDirName) {
|
|
30394
|
+
const ownerFile = join(
|
|
30395
|
+
installRoot,
|
|
30396
|
+
"machines",
|
|
30397
|
+
machineDirName,
|
|
30398
|
+
"daemon.lock",
|
|
30399
|
+
"owner.json"
|
|
30400
|
+
);
|
|
30401
|
+
let raw;
|
|
30402
|
+
try {
|
|
30403
|
+
raw = await readFile3(ownerFile, "utf8");
|
|
30404
|
+
} catch {
|
|
30405
|
+
return {};
|
|
30226
30406
|
}
|
|
30227
|
-
|
|
30228
|
-
|
|
30229
|
-
|
|
30230
|
-
|
|
30231
|
-
|
|
30232
|
-
return contents.trim();
|
|
30233
|
-
}
|
|
30234
|
-
});
|
|
30407
|
+
let parsed;
|
|
30408
|
+
try {
|
|
30409
|
+
parsed = JSON.parse(raw);
|
|
30410
|
+
} catch {
|
|
30411
|
+
return {};
|
|
30235
30412
|
}
|
|
30236
|
-
|
|
30237
|
-
|
|
30238
|
-
|
|
30239
|
-
|
|
30240
|
-
|
|
30413
|
+
const machineName = typeof parsed.hostname === "string" && parsed.hostname.length > 0 ? parsed.hostname : void 0;
|
|
30414
|
+
const serverUrl = typeof parsed.serverUrl === "string" && parsed.serverUrl.length > 0 ? parsed.serverUrl : void 0;
|
|
30415
|
+
return { machineName, serverUrl };
|
|
30416
|
+
}
|
|
30417
|
+
async function detectLegacyMigration(installRoot, loggedInUserId) {
|
|
30418
|
+
void loggedInUserId;
|
|
30419
|
+
const machinesDir = join(installRoot, "machines");
|
|
30420
|
+
let entries;
|
|
30421
|
+
try {
|
|
30422
|
+
entries = await readdir2(machinesDir);
|
|
30423
|
+
} catch {
|
|
30424
|
+
return { kind: "no-match" };
|
|
30241
30425
|
}
|
|
30242
|
-
const
|
|
30243
|
-
|
|
30244
|
-
|
|
30245
|
-
|
|
30246
|
-
|
|
30426
|
+
const candidates = [];
|
|
30427
|
+
for (const name of entries) {
|
|
30428
|
+
if (!name.startsWith(MACHINE_DIR_PREFIX)) continue;
|
|
30429
|
+
const evidence = await readOwnerEvidence(installRoot, name);
|
|
30430
|
+
candidates.push({
|
|
30431
|
+
machineId: name,
|
|
30432
|
+
machineName: evidence.machineName,
|
|
30433
|
+
serverUrl: evidence.serverUrl
|
|
30247
30434
|
});
|
|
30248
30435
|
}
|
|
30249
|
-
if (
|
|
30250
|
-
|
|
30251
|
-
|
|
30252
|
-
|
|
30253
|
-
|
|
30254
|
-
|
|
30255
|
-
|
|
30256
|
-
|
|
30257
|
-
|
|
30258
|
-
"LEGACY_KEY_MULTIPLE_SOURCES",
|
|
30259
|
-
`Multiple legacy api key sources provided (${modes}). Choose exactly one.`
|
|
30260
|
-
);
|
|
30261
|
-
}
|
|
30262
|
-
const chosen = sources[0];
|
|
30263
|
-
const rawKey = await chosen.load();
|
|
30264
|
-
if (chosen.mode === "legacy_key_env") {
|
|
30265
|
-
delete env.SLOCK_LEGACY_API_KEY;
|
|
30266
|
-
}
|
|
30267
|
-
if (!rawKey.startsWith("sk_machine_") && !rawKey.startsWith("sk_daemon_")) {
|
|
30268
|
-
fail(
|
|
30269
|
-
"LEGACY_KEY_INVALID",
|
|
30270
|
-
"Provided key does not look like a legacy machine key (expected `sk_machine_*` or `sk_daemon_*`)."
|
|
30271
|
-
);
|
|
30436
|
+
if (candidates.length === 0) return { kind: "no-match" };
|
|
30437
|
+
if (candidates.length === 1) {
|
|
30438
|
+
const only = candidates[0];
|
|
30439
|
+
return {
|
|
30440
|
+
kind: "match",
|
|
30441
|
+
machineId: only.machineId,
|
|
30442
|
+
machineName: only.machineName,
|
|
30443
|
+
serverUrl: only.serverUrl
|
|
30444
|
+
};
|
|
30272
30445
|
}
|
|
30273
|
-
return {
|
|
30274
|
-
rawKey,
|
|
30275
|
-
mode: chosen.mode,
|
|
30276
|
-
redactedPrefix: rawKey.slice(0, 8)
|
|
30277
|
-
};
|
|
30446
|
+
return { kind: "ambiguous", candidates };
|
|
30278
30447
|
}
|
|
30279
|
-
|
|
30280
|
-
|
|
30281
|
-
|
|
30282
|
-
|
|
30283
|
-
|
|
30284
|
-
|
|
30285
|
-
|
|
30448
|
+
|
|
30449
|
+
// src/services/adoptLegacy.ts
|
|
30450
|
+
init_esm_shims();
|
|
30451
|
+
import { chmod as chmod3, mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4, appendFile, stat } from "fs/promises";
|
|
30452
|
+
import { createHash as createHash2 } from "crypto";
|
|
30453
|
+
import { dirname as dirname4, join as join2 } from "path";
|
|
30454
|
+
import { setTimeout as delay } from "timers/promises";
|
|
30455
|
+
function emit3(opts, event) {
|
|
30456
|
+
const cb = opts?.onEvent;
|
|
30457
|
+
if (!cb) return;
|
|
30458
|
+
try {
|
|
30459
|
+
cb(event);
|
|
30460
|
+
} catch {
|
|
30461
|
+
}
|
|
30286
30462
|
}
|
|
30287
30463
|
var LEGACY_STOP_WAIT_MS = 1e4;
|
|
30288
30464
|
var LEGACY_STOP_POLL_MS = 200;
|
|
30289
30465
|
function legacyLockOwnerPath(slockHome, rawKey) {
|
|
30290
30466
|
const fingerprint = createHash2("sha256").update(rawKey).digest("hex").slice(0, 16);
|
|
30291
|
-
return
|
|
30467
|
+
return join2(slockHome, "machines", `machine-${fingerprint}`, "daemon.lock", "owner.json");
|
|
30292
30468
|
}
|
|
30293
30469
|
function isProcessAlive(pid) {
|
|
30294
30470
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -30303,7 +30479,7 @@ function isProcessAlive(pid) {
|
|
|
30303
30479
|
async function stopLegacyDaemonByOwnerFile(ownerFile) {
|
|
30304
30480
|
let raw;
|
|
30305
30481
|
try {
|
|
30306
|
-
raw = await
|
|
30482
|
+
raw = await readFile4(ownerFile, "utf8");
|
|
30307
30483
|
} catch {
|
|
30308
30484
|
return { attempted: false, outcome: "absent" };
|
|
30309
30485
|
}
|
|
@@ -30337,7 +30513,7 @@ async function stopLegacyDaemonByOwnerFile(ownerFile) {
|
|
|
30337
30513
|
async function readLegacyOwnerEvidence(ownerFile) {
|
|
30338
30514
|
let raw;
|
|
30339
30515
|
try {
|
|
30340
|
-
raw = await
|
|
30516
|
+
raw = await readFile4(ownerFile, "utf8");
|
|
30341
30517
|
} catch {
|
|
30342
30518
|
return { ownerFile, reason: "owner_file_absent" };
|
|
30343
30519
|
}
|
|
@@ -30371,162 +30547,164 @@ function formatLegacyOwnerEvidence(slockHome, evidence) {
|
|
|
30371
30547
|
if (evidence.reason) fields.push(`ownerStatus=${evidence.reason}`);
|
|
30372
30548
|
return fields.join(" ");
|
|
30373
30549
|
}
|
|
30374
|
-
async function
|
|
30550
|
+
async function adoptLegacy(input, options = {}) {
|
|
30551
|
+
options.signal?.throwIfAborted?.();
|
|
30375
30552
|
const slockHome = resolveSlockHome();
|
|
30376
30553
|
const sessionFile = userSessionPath(slockHome);
|
|
30377
30554
|
let session;
|
|
30378
30555
|
try {
|
|
30379
|
-
session = JSON.parse(await
|
|
30380
|
-
} catch {
|
|
30381
|
-
|
|
30556
|
+
session = JSON.parse(await readFile4(sessionFile, "utf8"));
|
|
30557
|
+
} catch (err) {
|
|
30558
|
+
throw new ComputerServiceError(
|
|
30382
30559
|
"NO_USER_SESSION",
|
|
30383
|
-
`No user session at ${sessionFile}. Run \`slock-computer login\` first
|
|
30560
|
+
`No user session at ${sessionFile}. Run \`slock-computer login\` first.`,
|
|
30561
|
+
err
|
|
30384
30562
|
);
|
|
30385
30563
|
}
|
|
30386
30564
|
if (session.kind !== "user-session" || typeof session.accessToken !== "string" || !session.accessToken) {
|
|
30387
|
-
|
|
30565
|
+
throw new ComputerServiceError(
|
|
30388
30566
|
"INVALID_USER_SESSION",
|
|
30389
30567
|
`User session at ${sessionFile} is invalid. Re-run \`slock-computer login\`.`
|
|
30390
30568
|
);
|
|
30391
30569
|
}
|
|
30392
|
-
const baseUrl = resolveServerUrl(
|
|
30393
|
-
const slugForServer = normalizeServerSlug(
|
|
30570
|
+
const baseUrl = resolveServerUrl(input.serverUrl, session.serverUrl, process.env.SLOCK_SERVER_URL);
|
|
30571
|
+
const slugForServer = normalizeServerSlug(input.serverSlug);
|
|
30394
30572
|
if (!slugForServer) {
|
|
30395
|
-
|
|
30573
|
+
throw new ComputerServiceError("ADOPT_NOT_AUTHORIZED", "Server slug must not be empty.");
|
|
30396
30574
|
}
|
|
30397
|
-
|
|
30398
|
-
|
|
30399
|
-
`Adopting legacy daemon for ${formatServerSlugDisplay(slugForServer)} via ${legacy.mode}\u2026`
|
|
30400
|
-
);
|
|
30575
|
+
options.signal?.throwIfAborted?.();
|
|
30576
|
+
emit3(options, { type: "adopting", serverSlug: slugForServer, mode: input.mode });
|
|
30401
30577
|
const client = new ComputerAttachClient(baseUrl, session.accessToken);
|
|
30402
30578
|
const adoptStartedAt = /* @__PURE__ */ new Date();
|
|
30403
|
-
const legacyOwnerFile = legacyLockOwnerPath(slockHome,
|
|
30579
|
+
const legacyOwnerFile = legacyLockOwnerPath(slockHome, input.rawKey);
|
|
30404
30580
|
const ownerEvidence = await readLegacyOwnerEvidence(legacyOwnerFile);
|
|
30405
|
-
const result = await client.adoptLegacy(
|
|
30406
|
-
|
|
30581
|
+
const result = await client.adoptLegacy(input.rawKey, input.name);
|
|
30582
|
+
input.rawKey = void 0;
|
|
30407
30583
|
if (result.status === "disabled") {
|
|
30408
30584
|
await appendAdoptionLog(slockHome, {
|
|
30409
|
-
mode:
|
|
30410
|
-
redactedPrefix:
|
|
30585
|
+
mode: input.mode,
|
|
30586
|
+
redactedPrefix: input.redactedPrefix,
|
|
30411
30587
|
startedAt: adoptStartedAt,
|
|
30412
30588
|
outcome: "failed",
|
|
30413
30589
|
failureReason: "computer_adopt_disabled"
|
|
30414
30590
|
});
|
|
30415
|
-
|
|
30591
|
+
throw new ComputerServiceError(
|
|
30416
30592
|
"ADOPT_DISABLED",
|
|
30417
30593
|
"Computer legacy adoption is not enabled on this server. Upgrade the server or set SLOCK_DEVICE_LOGIN_ENABLED."
|
|
30418
30594
|
);
|
|
30419
30595
|
}
|
|
30420
30596
|
if (result.status === "legacy_key_invalid") {
|
|
30421
30597
|
await appendAdoptionLog(slockHome, {
|
|
30422
|
-
mode:
|
|
30423
|
-
redactedPrefix:
|
|
30598
|
+
mode: input.mode,
|
|
30599
|
+
redactedPrefix: input.redactedPrefix,
|
|
30424
30600
|
startedAt: adoptStartedAt,
|
|
30425
30601
|
outcome: "failed",
|
|
30426
30602
|
failureReason: "legacy_key_invalid"
|
|
30427
30603
|
});
|
|
30428
|
-
|
|
30604
|
+
throw new ComputerServiceError(
|
|
30429
30605
|
"LEGACY_KEY_INVALID",
|
|
30430
30606
|
`Server rejected the legacy api key (unknown / wrong server / malformed). Local legacy owner evidence for this key: ${formatLegacyOwnerEvidence(slockHome, ownerEvidence)}. Verify the key came from this SLOCK_HOME and server, or use a fresh isolated SLOCK_HOME for a clean Computer setup.`
|
|
30431
30607
|
);
|
|
30432
30608
|
}
|
|
30433
30609
|
if (result.status === "legacy_machine_key_migrated") {
|
|
30434
30610
|
await appendAdoptionLog(slockHome, {
|
|
30435
|
-
mode:
|
|
30436
|
-
redactedPrefix:
|
|
30611
|
+
mode: input.mode,
|
|
30612
|
+
redactedPrefix: input.redactedPrefix,
|
|
30437
30613
|
startedAt: adoptStartedAt,
|
|
30438
30614
|
outcome: "failed",
|
|
30439
30615
|
failureReason: "legacy_machine_key_migrated"
|
|
30440
30616
|
});
|
|
30441
|
-
|
|
30617
|
+
throw new ComputerServiceError(
|
|
30442
30618
|
"LEGACY_MACHINE_KEY_MIGRATED",
|
|
30443
30619
|
`This machine has already been adopted; the legacy key is no longer accepted. Local legacy owner evidence for this key: ${formatLegacyOwnerEvidence(slockHome, ownerEvidence)}. Use \`slock-computer attach\` to add another Computer attachment, or use a fresh isolated SLOCK_HOME for a clean setup.`
|
|
30444
30620
|
);
|
|
30445
30621
|
}
|
|
30446
30622
|
if (result.status === "auth_required") {
|
|
30447
30623
|
await appendAdoptionLog(slockHome, {
|
|
30448
|
-
mode:
|
|
30449
|
-
redactedPrefix:
|
|
30624
|
+
mode: input.mode,
|
|
30625
|
+
redactedPrefix: input.redactedPrefix,
|
|
30450
30626
|
startedAt: adoptStartedAt,
|
|
30451
30627
|
outcome: "failed",
|
|
30452
30628
|
failureReason: "auth_required"
|
|
30453
30629
|
});
|
|
30454
|
-
|
|
30630
|
+
throw new ComputerServiceError(
|
|
30455
30631
|
"ADOPT_AUTH_REQUIRED",
|
|
30456
30632
|
`Your Computer user session was rejected by the server before legacy adoption. Local legacy owner evidence for this key: ${formatLegacyOwnerEvidence(slockHome, ownerEvidence)}. Re-run \`slock-computer login\` for this server (use the same \`--server-url\` if not on production), then retry the adopt command. No local Computer state was written.`
|
|
30457
30633
|
);
|
|
30458
30634
|
}
|
|
30459
30635
|
if (result.status === "not_authorized") {
|
|
30460
30636
|
await appendAdoptionLog(slockHome, {
|
|
30461
|
-
mode:
|
|
30462
|
-
redactedPrefix:
|
|
30637
|
+
mode: input.mode,
|
|
30638
|
+
redactedPrefix: input.redactedPrefix,
|
|
30463
30639
|
startedAt: adoptStartedAt,
|
|
30464
30640
|
outcome: "failed",
|
|
30465
30641
|
failureReason: "not_authorized"
|
|
30466
30642
|
});
|
|
30467
|
-
|
|
30643
|
+
throw new ComputerServiceError(
|
|
30468
30644
|
"ADOPT_NOT_AUTHORIZED",
|
|
30469
30645
|
"Not authorized to adopt this machine on this server. Check that you are a current member."
|
|
30470
30646
|
);
|
|
30471
30647
|
}
|
|
30472
30648
|
if (result.status === "unexpected_response") {
|
|
30473
30649
|
await appendAdoptionLog(slockHome, {
|
|
30474
|
-
mode:
|
|
30475
|
-
redactedPrefix:
|
|
30650
|
+
mode: input.mode,
|
|
30651
|
+
redactedPrefix: input.redactedPrefix,
|
|
30476
30652
|
startedAt: adoptStartedAt,
|
|
30477
30653
|
outcome: "failed",
|
|
30478
30654
|
failureReason: result.code ? `unexpected_response_${result.code}` : "unexpected_response_missing_code"
|
|
30479
30655
|
});
|
|
30480
30656
|
const responseDetail = result.code ? `status ${result.httpStatus}, code ${result.code}` : `status ${result.httpStatus}, missing error code`;
|
|
30481
30657
|
const authHint = result.httpStatus === 401 ? " If your server may be on an older release that does not emit `code: auth_required`, re-run `slock-computer login` first to refresh your user session, then retry. If the issue persists, report it as server contract drift." : "";
|
|
30482
|
-
|
|
30658
|
+
throw new ComputerServiceError(
|
|
30483
30659
|
"ADOPT_UNEXPECTED_RESPONSE",
|
|
30484
30660
|
`Server returned an unexpected legacy adoption response (${responseDetail}).${authHint} Local legacy owner evidence for this key: ${formatLegacyOwnerEvidence(slockHome, ownerEvidence)}. Please report this with the command, server URL, and SLOCK_HOME; no local Computer state was written.`
|
|
30485
30661
|
);
|
|
30486
30662
|
}
|
|
30487
30663
|
if (result.status === "error") {
|
|
30488
30664
|
await appendAdoptionLog(slockHome, {
|
|
30489
|
-
mode:
|
|
30490
|
-
redactedPrefix:
|
|
30665
|
+
mode: input.mode,
|
|
30666
|
+
redactedPrefix: input.redactedPrefix,
|
|
30491
30667
|
startedAt: adoptStartedAt,
|
|
30492
30668
|
outcome: "failed",
|
|
30493
30669
|
failureReason: result.code
|
|
30494
30670
|
});
|
|
30495
|
-
|
|
30671
|
+
throw new ComputerServiceError(
|
|
30496
30672
|
"ADOPT_FAILED",
|
|
30497
30673
|
`Adoption failed at server exchange (${result.code}). Local legacy owner evidence for this key: ${formatLegacyOwnerEvidence(slockHome, ownerEvidence)}. Confirm the user session is valid, the legacy key belongs to this server/SLOCK_HOME, and the server URL is correct. No local Computer state was written.`
|
|
30498
30674
|
);
|
|
30499
30675
|
}
|
|
30500
|
-
|
|
30676
|
+
emit3(options, { type: "preflight", resumed: result.resumed });
|
|
30677
|
+
options.signal?.throwIfAborted?.();
|
|
30501
30678
|
const pre = await client.preflight(result.apiKey);
|
|
30502
30679
|
if (!pre.ok) {
|
|
30503
30680
|
await appendAdoptionLog(slockHome, {
|
|
30504
|
-
mode:
|
|
30505
|
-
redactedPrefix:
|
|
30681
|
+
mode: input.mode,
|
|
30682
|
+
redactedPrefix: input.redactedPrefix,
|
|
30506
30683
|
startedAt: adoptStartedAt,
|
|
30507
30684
|
outcome: "failed",
|
|
30508
30685
|
failureReason: `preflight_${pre.code}`
|
|
30509
30686
|
});
|
|
30510
|
-
|
|
30687
|
+
throw new ComputerServiceError(
|
|
30511
30688
|
"PREFLIGHT_FAILED",
|
|
30512
30689
|
`Server preflight failed (${pre.code}); local state not written. Upgrade the server or retry.`
|
|
30513
30690
|
);
|
|
30514
30691
|
}
|
|
30515
|
-
|
|
30516
|
-
|
|
30692
|
+
options.signal?.throwIfAborted?.();
|
|
30693
|
+
const stop2 = await stopLegacyDaemonByOwnerFile(legacyOwnerFile);
|
|
30694
|
+
if (stop2.outcome === "timed_out" || stop2.outcome === "denied" || stop2.outcome === "error") {
|
|
30517
30695
|
await appendAdoptionLog(slockHome, {
|
|
30518
|
-
mode:
|
|
30519
|
-
redactedPrefix:
|
|
30696
|
+
mode: input.mode,
|
|
30697
|
+
redactedPrefix: input.redactedPrefix,
|
|
30520
30698
|
startedAt: adoptStartedAt,
|
|
30521
30699
|
outcome: "failed",
|
|
30522
|
-
failureReason: `legacy_stop_${
|
|
30700
|
+
failureReason: `legacy_stop_${stop2.outcome}`,
|
|
30523
30701
|
computerId: result.computerId,
|
|
30524
30702
|
machineId: result.machineId,
|
|
30525
30703
|
serverId: result.serverId,
|
|
30526
|
-
legacyStop:
|
|
30704
|
+
legacyStop: stop2
|
|
30527
30705
|
});
|
|
30528
|
-
const detail =
|
|
30529
|
-
|
|
30706
|
+
const detail = stop2.outcome === "timed_out" ? `pid ${stop2.pid} did not exit within ${LEGACY_STOP_WAIT_MS}ms` : stop2.outcome === "denied" ? `pid ${stop2.pid} cannot be stopped (permission denied)` : `stop attempt error (${stop2.reason ?? "unknown"})`;
|
|
30707
|
+
throw new ComputerServiceError(
|
|
30530
30708
|
"LEGACY_DAEMON_STOP_FAILED",
|
|
30531
30709
|
`Adoption succeeded server-side but the legacy daemon could not be stopped: ${detail}. Stop it manually and re-run \`slock-computer adopt-legacy\`, or run \`slock-computer attach\` after confirming the legacy process is gone. No local Computer state was written.`
|
|
30532
30710
|
);
|
|
@@ -30554,33 +30732,38 @@ async function runAdoptLegacy(inputs) {
|
|
|
30554
30732
|
);
|
|
30555
30733
|
await chmod3(file, 384);
|
|
30556
30734
|
await appendAdoptionLog(slockHome, {
|
|
30557
|
-
mode:
|
|
30558
|
-
redactedPrefix:
|
|
30735
|
+
mode: input.mode,
|
|
30736
|
+
redactedPrefix: input.redactedPrefix,
|
|
30559
30737
|
startedAt: adoptStartedAt,
|
|
30560
30738
|
outcome: "succeeded",
|
|
30561
30739
|
computerId: result.computerId,
|
|
30562
30740
|
machineId: result.machineId,
|
|
30563
30741
|
serverId: result.serverId,
|
|
30564
|
-
legacyStop:
|
|
30742
|
+
legacyStop: stop2
|
|
30565
30743
|
});
|
|
30566
|
-
|
|
30567
|
-
|
|
30568
|
-
|
|
30569
|
-
|
|
30570
|
-
|
|
30571
|
-
|
|
30572
|
-
|
|
30573
|
-
|
|
30574
|
-
|
|
30575
|
-
|
|
30576
|
-
|
|
30577
|
-
|
|
30578
|
-
|
|
30579
|
-
|
|
30580
|
-
|
|
30581
|
-
|
|
30582
|
-
|
|
30583
|
-
|
|
30744
|
+
const apiKeyRedactedPrefix = result.apiKey.slice(0, 8);
|
|
30745
|
+
emit3(options, {
|
|
30746
|
+
type: "adopted",
|
|
30747
|
+
serverId: result.serverId,
|
|
30748
|
+
serverMachineId: result.computerId,
|
|
30749
|
+
legacyMachineId: result.machineId,
|
|
30750
|
+
serverSlug: slugForServer,
|
|
30751
|
+
attachmentPath: file,
|
|
30752
|
+
resumed: result.resumed,
|
|
30753
|
+
apiKeyRedactedPrefix,
|
|
30754
|
+
legacyStop: stop2
|
|
30755
|
+
});
|
|
30756
|
+
return {
|
|
30757
|
+
serverId: result.serverId,
|
|
30758
|
+
serverMachineId: result.computerId,
|
|
30759
|
+
legacyMachineId: result.machineId,
|
|
30760
|
+
serverSlug: slugForServer,
|
|
30761
|
+
serverUrl: baseUrl,
|
|
30762
|
+
attachmentPath: file,
|
|
30763
|
+
resumed: result.resumed,
|
|
30764
|
+
apiKeyRedactedPrefix,
|
|
30765
|
+
legacyStop: stop2
|
|
30766
|
+
};
|
|
30584
30767
|
}
|
|
30585
30768
|
async function appendAdoptionLog(slockHome, line) {
|
|
30586
30769
|
try {
|
|
@@ -30616,24 +30799,53 @@ async function appendAdoptionLog(slockHome, line) {
|
|
|
30616
30799
|
}
|
|
30617
30800
|
}
|
|
30618
30801
|
|
|
30619
|
-
// src/setup.ts
|
|
30620
|
-
init_esm_shims();
|
|
30621
|
-
var import_undici2 = __toESM(require_undici(), 1);
|
|
30622
|
-
import { chmod as chmod4, mkdir as mkdir8, readdir as readdir3, readFile as readFile6, rename as rename3, rm as rm2, writeFile as writeFile7 } from "fs/promises";
|
|
30623
|
-
import { dirname as dirname8, join as join3 } from "path";
|
|
30624
|
-
|
|
30625
30802
|
// src/supervisor.ts
|
|
30626
30803
|
init_esm_shims();
|
|
30627
30804
|
import { spawn as spawn2 } from "child_process";
|
|
30628
|
-
import { mkdir as
|
|
30629
|
-
import { dirname as
|
|
30805
|
+
import { mkdir as mkdir8, readFile as readFile7, writeFile as writeFile7, open, rename as rename2 } from "fs/promises";
|
|
30806
|
+
import { dirname as dirname8, join as joinPath } from "path";
|
|
30630
30807
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
30631
30808
|
|
|
30632
30809
|
// src/cleanup.ts
|
|
30633
30810
|
init_esm_shims();
|
|
30634
|
-
import { readdir as
|
|
30811
|
+
import { readdir as readdir3, stat as stat2, unlink as unlink3, rm, rmdir, rename, mkdir as mkdir6 } from "fs/promises";
|
|
30635
30812
|
import { spawn } from "child_process";
|
|
30636
|
-
import { join as
|
|
30813
|
+
import { join as join3 } from "path";
|
|
30814
|
+
|
|
30815
|
+
// src/internal/process-primitives.ts
|
|
30816
|
+
init_esm_shims();
|
|
30817
|
+
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5, unlink as unlink2 } from "fs/promises";
|
|
30818
|
+
import { dirname as dirname5 } from "path";
|
|
30819
|
+
async function readPidfileAt(pidfilePath) {
|
|
30820
|
+
try {
|
|
30821
|
+
const raw = (await readFile5(pidfilePath, "utf8")).trim();
|
|
30822
|
+
const pid = Number.parseInt(raw, 10);
|
|
30823
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
30824
|
+
} catch {
|
|
30825
|
+
return null;
|
|
30826
|
+
}
|
|
30827
|
+
}
|
|
30828
|
+
function isProcessAlive2(pid) {
|
|
30829
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
30830
|
+
try {
|
|
30831
|
+
process.kill(pid, 0);
|
|
30832
|
+
return true;
|
|
30833
|
+
} catch (err) {
|
|
30834
|
+
return err.code === "EPERM";
|
|
30835
|
+
}
|
|
30836
|
+
}
|
|
30837
|
+
async function writePidfileAt(pidfilePath, pid) {
|
|
30838
|
+
await mkdir5(dirname5(pidfilePath), { recursive: true });
|
|
30839
|
+
await writeFile5(pidfilePath, String(pid), { mode: 384 });
|
|
30840
|
+
}
|
|
30841
|
+
async function clearPidfileAt(pidfilePath) {
|
|
30842
|
+
try {
|
|
30843
|
+
await unlink2(pidfilePath);
|
|
30844
|
+
} catch {
|
|
30845
|
+
}
|
|
30846
|
+
}
|
|
30847
|
+
|
|
30848
|
+
// src/cleanup.ts
|
|
30637
30849
|
function emptyCleanupReport() {
|
|
30638
30850
|
return {
|
|
30639
30851
|
stalePidfiles: [],
|
|
@@ -30649,7 +30861,7 @@ async function cleanupStalePidfile(pidfilePath) {
|
|
|
30649
30861
|
if (pid === null) return false;
|
|
30650
30862
|
if (isProcessAlive2(pid)) return false;
|
|
30651
30863
|
try {
|
|
30652
|
-
await
|
|
30864
|
+
await unlink3(pidfilePath);
|
|
30653
30865
|
return true;
|
|
30654
30866
|
} catch {
|
|
30655
30867
|
return false;
|
|
@@ -30728,24 +30940,24 @@ async function cleanupOrphanProcesses(slockHome, psSpawn) {
|
|
|
30728
30940
|
return signaled;
|
|
30729
30941
|
}
|
|
30730
30942
|
function quarantineDir(slockHome) {
|
|
30731
|
-
return
|
|
30943
|
+
return join3(computerDir(slockHome), ".quarantine");
|
|
30732
30944
|
}
|
|
30733
30945
|
async function quarantineServerSubtree(slockHome, serverId) {
|
|
30734
|
-
const src =
|
|
30946
|
+
const src = join3(serversDir(slockHome), serverId);
|
|
30735
30947
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
30736
|
-
const dest =
|
|
30737
|
-
await
|
|
30948
|
+
const dest = join3(quarantineDir(slockHome), `${stamp}-${serverId}`);
|
|
30949
|
+
await mkdir6(dirname6(dest), { recursive: true });
|
|
30738
30950
|
await rename(src, dest);
|
|
30739
30951
|
return dest;
|
|
30740
30952
|
}
|
|
30741
|
-
function
|
|
30953
|
+
function dirname6(p) {
|
|
30742
30954
|
const idx = p.lastIndexOf("/");
|
|
30743
30955
|
return idx > 0 ? p.slice(0, idx) : "/";
|
|
30744
30956
|
}
|
|
30745
30957
|
async function cleanupPowerLossPartialState(slockHome) {
|
|
30746
30958
|
let entries;
|
|
30747
30959
|
try {
|
|
30748
|
-
entries = await
|
|
30960
|
+
entries = await readdir3(serversDir(slockHome));
|
|
30749
30961
|
} catch {
|
|
30750
30962
|
return [];
|
|
30751
30963
|
}
|
|
@@ -30772,11 +30984,11 @@ var TMP_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
|
30772
30984
|
async function cleanupTmpFiles(slockHome) {
|
|
30773
30985
|
const removed = [];
|
|
30774
30986
|
const cdir = computerDir(slockHome);
|
|
30775
|
-
const stagingDir =
|
|
30987
|
+
const stagingDir = join3(cdir, "upgrade-staging");
|
|
30776
30988
|
try {
|
|
30777
|
-
const versions = await
|
|
30989
|
+
const versions = await readdir3(stagingDir);
|
|
30778
30990
|
for (const v of versions) {
|
|
30779
|
-
const vdir =
|
|
30991
|
+
const vdir = join3(stagingDir, v);
|
|
30780
30992
|
try {
|
|
30781
30993
|
const s = await stat2(vdir);
|
|
30782
30994
|
if (Date.now() - s.mtimeMs > TMP_MAX_AGE_MS) {
|
|
@@ -30787,17 +30999,17 @@ async function cleanupTmpFiles(slockHome) {
|
|
|
30787
30999
|
}
|
|
30788
31000
|
}
|
|
30789
31001
|
try {
|
|
30790
|
-
const remaining = await
|
|
31002
|
+
const remaining = await readdir3(stagingDir);
|
|
30791
31003
|
if (remaining.length === 0) await rmdir(stagingDir);
|
|
30792
31004
|
} catch {
|
|
30793
31005
|
}
|
|
30794
31006
|
} catch {
|
|
30795
31007
|
}
|
|
30796
|
-
const snap =
|
|
31008
|
+
const snap = join3(cdir, "upgrade-snapshot.json");
|
|
30797
31009
|
try {
|
|
30798
31010
|
const s = await stat2(snap);
|
|
30799
31011
|
if (Date.now() - s.mtimeMs > TMP_MAX_AGE_MS) {
|
|
30800
|
-
await
|
|
31012
|
+
await unlink3(snap);
|
|
30801
31013
|
removed.push(snap);
|
|
30802
31014
|
}
|
|
30803
31015
|
} catch {
|
|
@@ -30805,7 +31017,7 @@ async function cleanupTmpFiles(slockHome) {
|
|
|
30805
31017
|
return removed;
|
|
30806
31018
|
}
|
|
30807
31019
|
async function cleanupStaleLock(slockHome) {
|
|
30808
|
-
const lockDir =
|
|
31020
|
+
const lockDir = join3(computerDir(slockHome), ".lock");
|
|
30809
31021
|
try {
|
|
30810
31022
|
const s = await stat2(lockDir);
|
|
30811
31023
|
if (Date.now() - s.mtimeMs > 60 * 1e3) {
|
|
@@ -30817,7 +31029,7 @@ async function cleanupStaleLock(slockHome) {
|
|
|
30817
31029
|
return [];
|
|
30818
31030
|
}
|
|
30819
31031
|
async function forceReleaseLock(slockHome) {
|
|
30820
|
-
const lockDir =
|
|
31032
|
+
const lockDir = join3(computerDir(slockHome), ".lock");
|
|
30821
31033
|
try {
|
|
30822
31034
|
await stat2(lockDir);
|
|
30823
31035
|
await rm(lockDir, { recursive: true, force: true });
|
|
@@ -30841,14 +31053,14 @@ async function runFullCleanup(slockHome, options = {}) {
|
|
|
30841
31053
|
|
|
30842
31054
|
// src/health.ts
|
|
30843
31055
|
init_esm_shims();
|
|
30844
|
-
import { readFile as
|
|
30845
|
-
import { dirname as
|
|
31056
|
+
import { readFile as readFile6, writeFile as writeFile6, unlink as unlink4, mkdir as mkdir7, appendFile as appendFile2 } from "fs/promises";
|
|
31057
|
+
import { dirname as dirname7 } from "path";
|
|
30846
31058
|
var CRASH_WINDOW_MS = 6e4;
|
|
30847
31059
|
var DEGRADED_THRESHOLD = 3;
|
|
30848
31060
|
async function readHealthFile(slockHome, serverId) {
|
|
30849
31061
|
if (!isValidServerId(serverId)) return { crashes: [] };
|
|
30850
31062
|
try {
|
|
30851
|
-
const raw = await
|
|
31063
|
+
const raw = await readFile6(serverHealthPath(slockHome, serverId), "utf8");
|
|
30852
31064
|
const parsed = JSON.parse(raw);
|
|
30853
31065
|
if (parsed && typeof parsed === "object" && Array.isArray(parsed.crashes)) {
|
|
30854
31066
|
return parsed;
|
|
@@ -30859,8 +31071,8 @@ async function readHealthFile(slockHome, serverId) {
|
|
|
30859
31071
|
}
|
|
30860
31072
|
async function writeHealthFile(slockHome, serverId, file) {
|
|
30861
31073
|
const path3 = serverHealthPath(slockHome, serverId);
|
|
30862
|
-
await
|
|
30863
|
-
await
|
|
31074
|
+
await mkdir7(dirname7(path3), { recursive: true });
|
|
31075
|
+
await writeFile6(path3, JSON.stringify(file), { mode: 384 });
|
|
30864
31076
|
}
|
|
30865
31077
|
async function recordCrash(slockHome, serverId, exitCode, signal, nowMs = Date.now()) {
|
|
30866
31078
|
if (!isValidServerId(serverId)) return;
|
|
@@ -30909,47 +31121,401 @@ async function markFatalConfig(slockHome, serverId, exitCode, signal, nowMs = Da
|
|
|
30909
31121
|
async function resetHealth(slockHome, serverId) {
|
|
30910
31122
|
if (!isValidServerId(serverId)) return;
|
|
30911
31123
|
try {
|
|
30912
|
-
await
|
|
31124
|
+
await unlink4(serverHealthPath(slockHome, serverId));
|
|
30913
31125
|
} catch {
|
|
30914
31126
|
}
|
|
30915
31127
|
}
|
|
30916
|
-
|
|
30917
|
-
|
|
30918
|
-
|
|
30919
|
-
|
|
30920
|
-
|
|
30921
|
-
|
|
30922
|
-
|
|
31128
|
+
async function resetRunnerHealth(slockHome, serverId, nowMs = Date.now()) {
|
|
31129
|
+
if (!isValidServerId(serverId)) {
|
|
31130
|
+
return { status: "not-found" };
|
|
31131
|
+
}
|
|
31132
|
+
const wasDegraded = await isDegraded(slockHome, serverId, nowMs);
|
|
31133
|
+
const recent = await readCrashHistory(slockHome, serverId, nowMs);
|
|
31134
|
+
const previousState = wasDegraded ? "degraded" : "running";
|
|
31135
|
+
await resetHealth(slockHome, serverId);
|
|
31136
|
+
await emitRunnerStateTransition(
|
|
31137
|
+
slockHome,
|
|
31138
|
+
serverId,
|
|
31139
|
+
previousState,
|
|
31140
|
+
"running",
|
|
31141
|
+
"reset-runner"
|
|
31142
|
+
);
|
|
31143
|
+
return {
|
|
31144
|
+
status: "ok",
|
|
31145
|
+
previousState,
|
|
31146
|
+
clearedCrashCount: recent.length
|
|
31147
|
+
};
|
|
31148
|
+
}
|
|
31149
|
+
async function emitRunnerStateTransition(slockHome, serverId, fromState, toState, trigger) {
|
|
31150
|
+
const entry = {
|
|
31151
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31152
|
+
kind: "runner-state-changed",
|
|
31153
|
+
serverId,
|
|
31154
|
+
fromState,
|
|
31155
|
+
toState,
|
|
31156
|
+
trigger
|
|
31157
|
+
};
|
|
31158
|
+
try {
|
|
31159
|
+
const path3 = supervisorLogPath(slockHome);
|
|
31160
|
+
await mkdir7(dirname7(path3), { recursive: true });
|
|
31161
|
+
await appendFile2(path3, JSON.stringify(entry) + "\n");
|
|
30923
31162
|
} catch {
|
|
30924
|
-
return null;
|
|
30925
31163
|
}
|
|
30926
31164
|
}
|
|
30927
|
-
|
|
30928
|
-
|
|
31165
|
+
|
|
31166
|
+
// src/services/start.ts
|
|
31167
|
+
init_esm_shims();
|
|
31168
|
+
var START_ENSURE_TIMEOUT_MS = 15e3;
|
|
31169
|
+
var START_ENSURE_POLL_INTERVAL_MS = 100;
|
|
31170
|
+
function emit4(opts, event) {
|
|
31171
|
+
const cb = opts?.onEvent;
|
|
31172
|
+
if (!cb) return;
|
|
30929
31173
|
try {
|
|
30930
|
-
|
|
30931
|
-
|
|
31174
|
+
cb(event);
|
|
31175
|
+
} catch {
|
|
31176
|
+
}
|
|
31177
|
+
}
|
|
31178
|
+
async function waitForManagedDaemonPids(slockHome, serverIds, opts) {
|
|
31179
|
+
const readPidfile = opts.readPidfile ?? readPidfileAt;
|
|
31180
|
+
const isAlive = opts.isProcessAlive ?? isProcessAlive2;
|
|
31181
|
+
const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
31182
|
+
const timeoutMs = opts.ensureTimeoutMs ?? START_ENSURE_TIMEOUT_MS;
|
|
31183
|
+
const pollIntervalMs = opts.ensurePollIntervalMs ?? START_ENSURE_POLL_INTERVAL_MS;
|
|
31184
|
+
const deadline = Date.now() + timeoutMs;
|
|
31185
|
+
const ready = /* @__PURE__ */ new Map();
|
|
31186
|
+
while (ready.size < serverIds.length) {
|
|
31187
|
+
for (const serverId of serverIds) {
|
|
31188
|
+
if (ready.has(serverId)) continue;
|
|
31189
|
+
const pid = await readPidfile(serverDaemonPidPath(slockHome, serverId));
|
|
31190
|
+
if (pid && isAlive(pid)) ready.set(serverId, pid);
|
|
31191
|
+
}
|
|
31192
|
+
if (ready.size === serverIds.length) return ready;
|
|
31193
|
+
const remaining = deadline - Date.now();
|
|
31194
|
+
if (remaining <= 0) return ready;
|
|
31195
|
+
await sleep2(Math.min(pollIntervalMs, remaining));
|
|
31196
|
+
}
|
|
31197
|
+
return ready;
|
|
31198
|
+
}
|
|
31199
|
+
function buildTimeoutMessage(slockHome, serverIds, ready, input) {
|
|
31200
|
+
const missing = serverIds.filter((id) => !ready.has(id));
|
|
31201
|
+
const target = input.serverId && missing.length === 1 ? `${input.serverLabel ?? input.serverId}` : `${missing.length} daemon(s): ${missing.join(", ")}`;
|
|
31202
|
+
return `Timed out waiting for ${target} to start. Run \`slock-computer status\` and inspect ${supervisorLogPath(slockHome)} plus per-server daemon logs under ~/.slock/computer/servers/<serverId>/daemon.log.`;
|
|
31203
|
+
}
|
|
31204
|
+
async function start(input, options = {}) {
|
|
31205
|
+
options.signal?.throwIfAborted?.();
|
|
31206
|
+
const slockHome = resolveSlockHome();
|
|
31207
|
+
const attached = await listAttachedServerIds(slockHome);
|
|
31208
|
+
if (attached.length === 0) {
|
|
31209
|
+
throw new ComputerServiceError(
|
|
31210
|
+
"NO_ATTACHMENT",
|
|
31211
|
+
"No server attachments yet. Run `slock-computer attach <serverId>` first."
|
|
31212
|
+
);
|
|
31213
|
+
}
|
|
31214
|
+
if (input.serverId && !attached.includes(input.serverId)) {
|
|
31215
|
+
throw new ComputerServiceError(
|
|
31216
|
+
"NOT_ATTACHED",
|
|
31217
|
+
`Not attached to server ${input.serverId}. Run \`slock-computer attach ${input.serverId}\` first or omit the argument.`
|
|
31218
|
+
);
|
|
31219
|
+
}
|
|
31220
|
+
const managedTargets = input.serverId ? [input.serverId] : attached;
|
|
31221
|
+
emit4(options, {
|
|
31222
|
+
type: "starting",
|
|
31223
|
+
managedTargets,
|
|
31224
|
+
attachedCount: attached.length,
|
|
31225
|
+
foreground: !!input.foreground
|
|
31226
|
+
});
|
|
31227
|
+
for (const id of managedTargets) {
|
|
31228
|
+
await setServerManaged(slockHome, id);
|
|
31229
|
+
}
|
|
31230
|
+
const existing = await readPidfileAt(supervisorPidPath(slockHome));
|
|
31231
|
+
if (existing && isProcessAlive2(existing)) {
|
|
31232
|
+
emit4(options, {
|
|
31233
|
+
type: "already_running",
|
|
31234
|
+
supervisorPid: existing,
|
|
31235
|
+
managedTargets,
|
|
31236
|
+
attachedCount: attached.length
|
|
31237
|
+
});
|
|
31238
|
+
const ready2 = await waitForManagedDaemonPids(slockHome, managedTargets, options);
|
|
31239
|
+
if (ready2.size !== managedTargets.length) {
|
|
31240
|
+
throw new ComputerServiceError(
|
|
31241
|
+
"START_DAEMON_TIMEOUT",
|
|
31242
|
+
buildTimeoutMessage(slockHome, managedTargets, ready2, input)
|
|
31243
|
+
);
|
|
31244
|
+
}
|
|
31245
|
+
emit4(options, { type: "ready", ready: ready2, managedTargets });
|
|
31246
|
+
return {
|
|
31247
|
+
status: "already_running",
|
|
31248
|
+
managedTargets,
|
|
31249
|
+
attachedCount: attached.length,
|
|
31250
|
+
ready: ready2,
|
|
31251
|
+
supervisorPid: existing,
|
|
31252
|
+
supervisorLogPath: supervisorLogPath(slockHome)
|
|
31253
|
+
};
|
|
31254
|
+
}
|
|
31255
|
+
if (input.foreground) {
|
|
31256
|
+
emit4(options, {
|
|
31257
|
+
type: "running",
|
|
31258
|
+
managedTargets,
|
|
31259
|
+
attachedCount: attached.length
|
|
31260
|
+
});
|
|
31261
|
+
const supervise = options.runSupervise ?? runSupervise;
|
|
31262
|
+
const previousParentLockMarker = process.env[PARENT_LOCK_HELD_ENV_VAR];
|
|
31263
|
+
process.env[PARENT_LOCK_HELD_ENV_VAR] = "1";
|
|
31264
|
+
try {
|
|
31265
|
+
await supervise();
|
|
31266
|
+
} finally {
|
|
31267
|
+
if (previousParentLockMarker === void 0) delete process.env[PARENT_LOCK_HELD_ENV_VAR];
|
|
31268
|
+
else process.env[PARENT_LOCK_HELD_ENV_VAR] = previousParentLockMarker;
|
|
31269
|
+
}
|
|
31270
|
+
return {
|
|
31271
|
+
status: "running",
|
|
31272
|
+
managedTargets,
|
|
31273
|
+
attachedCount: attached.length,
|
|
31274
|
+
ready: /* @__PURE__ */ new Map(),
|
|
31275
|
+
supervisorPid: null,
|
|
31276
|
+
supervisorLogPath: supervisorLogPath(slockHome)
|
|
31277
|
+
};
|
|
31278
|
+
}
|
|
31279
|
+
options.signal?.throwIfAborted?.();
|
|
31280
|
+
let pid;
|
|
31281
|
+
try {
|
|
31282
|
+
pid = await (options.spawnDetachedSupervisor ?? spawnDetachedSupervisor)(slockHome);
|
|
30932
31283
|
} catch (err) {
|
|
30933
|
-
|
|
31284
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
31285
|
+
throw new ComputerServiceError("SUPERVISOR_SPAWN_FAILED", msg, err);
|
|
30934
31286
|
}
|
|
31287
|
+
emit4(options, {
|
|
31288
|
+
type: "spawned",
|
|
31289
|
+
supervisorPid: pid,
|
|
31290
|
+
managedTargets,
|
|
31291
|
+
attachedCount: attached.length
|
|
31292
|
+
});
|
|
31293
|
+
if (options.signal?.aborted) {
|
|
31294
|
+
const ready2 = await pollReadyOnce(slockHome, managedTargets, options);
|
|
31295
|
+
emit4(options, { type: "aborted", supervisorPid: pid, managedTargets, ready: ready2 });
|
|
31296
|
+
return {
|
|
31297
|
+
status: "aborted",
|
|
31298
|
+
managedTargets,
|
|
31299
|
+
attachedCount: attached.length,
|
|
31300
|
+
ready: ready2,
|
|
31301
|
+
supervisorPid: pid,
|
|
31302
|
+
supervisorLogPath: supervisorLogPath(slockHome)
|
|
31303
|
+
};
|
|
31304
|
+
}
|
|
31305
|
+
const ready = await waitForManagedDaemonPids(slockHome, managedTargets, options);
|
|
31306
|
+
if (ready.size !== managedTargets.length) {
|
|
31307
|
+
throw new ComputerServiceError(
|
|
31308
|
+
"START_DAEMON_TIMEOUT",
|
|
31309
|
+
buildTimeoutMessage(slockHome, managedTargets, ready, input)
|
|
31310
|
+
);
|
|
31311
|
+
}
|
|
31312
|
+
emit4(options, { type: "ready", ready, managedTargets });
|
|
31313
|
+
return {
|
|
31314
|
+
status: "spawned",
|
|
31315
|
+
managedTargets,
|
|
31316
|
+
attachedCount: attached.length,
|
|
31317
|
+
ready,
|
|
31318
|
+
supervisorPid: pid,
|
|
31319
|
+
supervisorLogPath: supervisorLogPath(slockHome)
|
|
31320
|
+
};
|
|
30935
31321
|
}
|
|
30936
|
-
async function
|
|
30937
|
-
|
|
30938
|
-
|
|
31322
|
+
async function pollReadyOnce(slockHome, serverIds, opts) {
|
|
31323
|
+
const readPidfile = opts.readPidfile ?? readPidfileAt;
|
|
31324
|
+
const isAlive = opts.isProcessAlive ?? isProcessAlive2;
|
|
31325
|
+
const ready = /* @__PURE__ */ new Map();
|
|
31326
|
+
for (const serverId of serverIds) {
|
|
31327
|
+
const pid = await readPidfile(serverDaemonPidPath(slockHome, serverId));
|
|
31328
|
+
if (pid && isAlive(pid)) ready.set(serverId, pid);
|
|
31329
|
+
}
|
|
31330
|
+
return ready;
|
|
30939
31331
|
}
|
|
30940
|
-
|
|
31332
|
+
|
|
31333
|
+
// src/services/stop.ts
|
|
31334
|
+
init_esm_shims();
|
|
31335
|
+
var STOP_POLL_INTERVAL_MS = 200;
|
|
31336
|
+
var STOP_TIMEOUT_MS = 5e3;
|
|
31337
|
+
function emit5(opts, event) {
|
|
31338
|
+
const cb = opts?.onEvent;
|
|
31339
|
+
if (!cb) return;
|
|
30941
31340
|
try {
|
|
30942
|
-
|
|
31341
|
+
cb(event);
|
|
30943
31342
|
} catch {
|
|
30944
31343
|
}
|
|
30945
31344
|
}
|
|
31345
|
+
async function stop(input = {}, options = {}) {
|
|
31346
|
+
void input;
|
|
31347
|
+
options.signal?.throwIfAborted?.();
|
|
31348
|
+
const slockHome = resolveSlockHome();
|
|
31349
|
+
const readPidfile = options.readPidfile ?? readPidfileAt;
|
|
31350
|
+
const isAlive = options.isProcessAlive ?? isProcessAlive2;
|
|
31351
|
+
const killer = options.killSupervisor ?? ((pid2) => {
|
|
31352
|
+
process.kill(pid2, "SIGTERM");
|
|
31353
|
+
});
|
|
31354
|
+
const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
31355
|
+
const pollIntervalMs = options.pollIntervalMs ?? STOP_POLL_INTERVAL_MS;
|
|
31356
|
+
const timeoutMs = options.timeoutMs ?? STOP_TIMEOUT_MS;
|
|
31357
|
+
const pidfilePath = supervisorPidPath(slockHome);
|
|
31358
|
+
const pid = await readPidfile(pidfilePath);
|
|
31359
|
+
emit5(options, { type: "stopping", pid });
|
|
31360
|
+
if (pid === null) {
|
|
31361
|
+
emit5(options, { type: "not_running" });
|
|
31362
|
+
return { status: "not_running", pid: null, pidfilePath };
|
|
31363
|
+
}
|
|
31364
|
+
if (!isAlive(pid)) {
|
|
31365
|
+
await clearPidfileAt(pidfilePath);
|
|
31366
|
+
emit5(options, { type: "stale_pidfile_cleared", pid });
|
|
31367
|
+
return { status: "stale_pidfile_cleared", pid, pidfilePath };
|
|
31368
|
+
}
|
|
31369
|
+
options.signal?.throwIfAborted?.();
|
|
31370
|
+
try {
|
|
31371
|
+
killer(pid);
|
|
31372
|
+
} catch (err) {
|
|
31373
|
+
const cause = err instanceof Error ? err : new Error(String(err));
|
|
31374
|
+
throw new ComputerServiceError(
|
|
31375
|
+
"STOP_SIGNAL_FAILED",
|
|
31376
|
+
`Failed to send SIGTERM to supervisor (pid ${pid}): ${cause.message}. Check process permissions or run: kill ${pid}`,
|
|
31377
|
+
err
|
|
31378
|
+
);
|
|
31379
|
+
}
|
|
31380
|
+
emit5(options, { type: "signaled", pid });
|
|
31381
|
+
const deadline = Date.now() + timeoutMs;
|
|
31382
|
+
while (Date.now() < deadline) {
|
|
31383
|
+
options.signal?.throwIfAborted?.();
|
|
31384
|
+
if (!isAlive(pid)) {
|
|
31385
|
+
await clearPidfileAt(pidfilePath);
|
|
31386
|
+
emit5(options, { type: "stopped", pid });
|
|
31387
|
+
return { status: "stopped", pid, pidfilePath };
|
|
31388
|
+
}
|
|
31389
|
+
const remaining = deadline - Date.now();
|
|
31390
|
+
if (remaining <= 0) break;
|
|
31391
|
+
await sleep2(Math.min(pollIntervalMs, remaining));
|
|
31392
|
+
}
|
|
31393
|
+
throw new ComputerServiceError(
|
|
31394
|
+
"STOP_TIMEOUT",
|
|
31395
|
+
`Supervisor (pid ${pid}) did not exit within ${timeoutMs}ms after SIGTERM. Force-kill with: kill -9 ${pid}`
|
|
31396
|
+
);
|
|
31397
|
+
}
|
|
31398
|
+
|
|
31399
|
+
// src/services/detach.ts
|
|
31400
|
+
init_esm_shims();
|
|
31401
|
+
var import_undici2 = __toESM(require_undici(), 1);
|
|
31402
|
+
var REVOKE_TIMEOUT_MS = 3e3;
|
|
31403
|
+
function emit6(opts, event) {
|
|
31404
|
+
const cb = opts?.onEvent;
|
|
31405
|
+
if (!cb) return;
|
|
31406
|
+
try {
|
|
31407
|
+
cb(event);
|
|
31408
|
+
} catch {
|
|
31409
|
+
}
|
|
31410
|
+
}
|
|
31411
|
+
async function bestEffortServerRevoke(fetchImpl, serverUrl, apiKey, serverId, timeoutMs) {
|
|
31412
|
+
const url = `${serverUrl.replace(/\/$/, "")}/internal/computer/attachment/revoke`;
|
|
31413
|
+
const controller = new AbortController();
|
|
31414
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
31415
|
+
try {
|
|
31416
|
+
const res = await fetchImpl(url, {
|
|
31417
|
+
method: "POST",
|
|
31418
|
+
headers: {
|
|
31419
|
+
"content-type": "application/json",
|
|
31420
|
+
authorization: `Bearer ${apiKey}`
|
|
31421
|
+
},
|
|
31422
|
+
body: JSON.stringify({ reason: "computer_detach" }),
|
|
31423
|
+
signal: controller.signal
|
|
31424
|
+
});
|
|
31425
|
+
if (!res.ok) {
|
|
31426
|
+
return {
|
|
31427
|
+
kind: "http_error",
|
|
31428
|
+
status: res.status,
|
|
31429
|
+
message: `server-side revoke for server ${serverId} returned HTTP ${res.status}; local detach proceeds, but the credential may remain valid until it expires.`
|
|
31430
|
+
};
|
|
31431
|
+
}
|
|
31432
|
+
return { kind: "success" };
|
|
31433
|
+
} catch (err) {
|
|
31434
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
31435
|
+
return {
|
|
31436
|
+
kind: "network_error",
|
|
31437
|
+
message: `server-side revoke for server ${serverId} failed: ${m}. Local detach proceeds, but the credential may remain valid until it expires.`
|
|
31438
|
+
};
|
|
31439
|
+
} finally {
|
|
31440
|
+
clearTimeout(timeout);
|
|
31441
|
+
}
|
|
31442
|
+
}
|
|
31443
|
+
async function detach(input, options = {}) {
|
|
31444
|
+
options.signal?.throwIfAborted?.();
|
|
31445
|
+
const serverId = assertValidServerId(input.serverId);
|
|
31446
|
+
const serverLabel = input.serverLabel ?? serverId;
|
|
31447
|
+
const slockHome = resolveSlockHome();
|
|
31448
|
+
const attachment = await readServerAttachment(slockHome, serverId);
|
|
31449
|
+
if (!attachment) {
|
|
31450
|
+
throw new ComputerServiceError(
|
|
31451
|
+
"NOT_ATTACHED",
|
|
31452
|
+
`Not attached to server ${serverLabel} (nothing to detach).`
|
|
31453
|
+
);
|
|
31454
|
+
}
|
|
31455
|
+
emit6(options, { type: "detaching", serverId, serverLabel });
|
|
31456
|
+
options.signal?.throwIfAborted?.();
|
|
31457
|
+
const fetchImpl = options.fetch ?? import_undici2.fetch;
|
|
31458
|
+
const timeoutMs = options.revokeTimeoutMs ?? REVOKE_TIMEOUT_MS;
|
|
31459
|
+
const outcome = await bestEffortServerRevoke(
|
|
31460
|
+
fetchImpl,
|
|
31461
|
+
attachment.serverUrl,
|
|
31462
|
+
attachment.apiKey,
|
|
31463
|
+
serverId,
|
|
31464
|
+
timeoutMs
|
|
31465
|
+
);
|
|
31466
|
+
let revokeOutcome;
|
|
31467
|
+
let revokeHttpStatus;
|
|
31468
|
+
if (outcome.kind === "success") {
|
|
31469
|
+
revokeOutcome = "success";
|
|
31470
|
+
emit6(options, { type: "revoke_succeeded", serverId, serverLabel });
|
|
31471
|
+
} else if (outcome.kind === "http_error") {
|
|
31472
|
+
revokeOutcome = "http_error";
|
|
31473
|
+
revokeHttpStatus = outcome.status;
|
|
31474
|
+
emit6(options, {
|
|
31475
|
+
type: "revoke_failed",
|
|
31476
|
+
serverId,
|
|
31477
|
+
serverLabel,
|
|
31478
|
+
reason: "http_error",
|
|
31479
|
+
httpStatus: outcome.status,
|
|
31480
|
+
message: outcome.message
|
|
31481
|
+
});
|
|
31482
|
+
} else {
|
|
31483
|
+
revokeOutcome = "network_error";
|
|
31484
|
+
emit6(options, {
|
|
31485
|
+
type: "revoke_failed",
|
|
31486
|
+
serverId,
|
|
31487
|
+
serverLabel,
|
|
31488
|
+
reason: "network_error",
|
|
31489
|
+
message: outcome.message
|
|
31490
|
+
});
|
|
31491
|
+
}
|
|
31492
|
+
options.signal?.throwIfAborted?.();
|
|
31493
|
+
await clearServerManaged(slockHome, serverId);
|
|
31494
|
+
const subtree = [
|
|
31495
|
+
serverAttachmentPath(slockHome, serverId),
|
|
31496
|
+
serverDaemonPidPath(slockHome, serverId),
|
|
31497
|
+
serverDaemonLogPath(slockHome, serverId)
|
|
31498
|
+
];
|
|
31499
|
+
for (const p of subtree) await clearPidfileAt(p);
|
|
31500
|
+
emit6(options, { type: "subtree_cleared", serverId, serverLabel });
|
|
31501
|
+
emit6(options, { type: "detached", serverId, serverLabel });
|
|
31502
|
+
return {
|
|
31503
|
+
status: "detached",
|
|
31504
|
+
serverId,
|
|
31505
|
+
serverLabel,
|
|
31506
|
+
revokeOutcome,
|
|
31507
|
+
revokeHttpStatus
|
|
31508
|
+
};
|
|
31509
|
+
}
|
|
31510
|
+
|
|
31511
|
+
// src/supervisor.ts
|
|
30946
31512
|
function buildResidentSpawn(mode, serverId, selfEntry = process.argv[1] ?? "", execArgv = process.execArgv) {
|
|
30947
31513
|
const tail = serverId ? [mode, serverId] : [mode];
|
|
30948
31514
|
return { command: process.execPath, args: [...execArgv, selfEntry, ...tail] };
|
|
30949
31515
|
}
|
|
30950
31516
|
var PARENT_LOCK_HELD_ENV_VAR = "SLOCK_COMPUTER_PARENT_MUTATION_LOCK_HELD";
|
|
30951
31517
|
async function spawnDetachedSupervisor(slockHome) {
|
|
30952
|
-
await
|
|
31518
|
+
await mkdir8(computerDir(slockHome), { recursive: true });
|
|
30953
31519
|
const supLogFd = await open(supervisorLogPath(slockHome), "a");
|
|
30954
31520
|
const { command, args } = buildResidentSpawn("__supervise", null);
|
|
30955
31521
|
const child = spawn2(command, args, {
|
|
@@ -31034,29 +31600,6 @@ async function runResident(serverId, deps = {}) {
|
|
|
31034
31600
|
}
|
|
31035
31601
|
var RECONCILE_INTERVAL_MS = 5e3;
|
|
31036
31602
|
var CHILD_RESTART_BACKOFF_MS = 2e3;
|
|
31037
|
-
var START_ENSURE_TIMEOUT_MS = 15e3;
|
|
31038
|
-
var START_ENSURE_POLL_INTERVAL_MS = 100;
|
|
31039
|
-
async function waitForManagedDaemonPids(slockHome, serverIds, deps) {
|
|
31040
|
-
const readPidfile = deps.readPidfile ?? readPidfileAt;
|
|
31041
|
-
const isAlive = deps.isProcessAlive ?? isProcessAlive2;
|
|
31042
|
-
const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
31043
|
-
const timeoutMs = deps.ensureTimeoutMs ?? START_ENSURE_TIMEOUT_MS;
|
|
31044
|
-
const pollIntervalMs = deps.ensurePollIntervalMs ?? START_ENSURE_POLL_INTERVAL_MS;
|
|
31045
|
-
const deadline = Date.now() + timeoutMs;
|
|
31046
|
-
const ready = /* @__PURE__ */ new Map();
|
|
31047
|
-
while (ready.size < serverIds.length) {
|
|
31048
|
-
for (const serverId of serverIds) {
|
|
31049
|
-
if (ready.has(serverId)) continue;
|
|
31050
|
-
const pid = await readPidfile(serverDaemonPidPath(slockHome, serverId));
|
|
31051
|
-
if (pid && isAlive(pid)) ready.set(serverId, pid);
|
|
31052
|
-
}
|
|
31053
|
-
if (ready.size === serverIds.length) return ready;
|
|
31054
|
-
const remaining = deadline - Date.now();
|
|
31055
|
-
if (remaining <= 0) return ready;
|
|
31056
|
-
await sleep2(Math.min(pollIntervalMs, remaining));
|
|
31057
|
-
}
|
|
31058
|
-
return ready;
|
|
31059
|
-
}
|
|
31060
31603
|
function formatReadySummary(ready, serverIds, opts) {
|
|
31061
31604
|
if (opts.serverId && serverIds.length === 1) {
|
|
31062
31605
|
const pid = ready.get(opts.serverId);
|
|
@@ -31064,14 +31607,6 @@ function formatReadySummary(ready, serverIds, opts) {
|
|
|
31064
31607
|
}
|
|
31065
31608
|
return `Daemons for ${serverIds.length} managed server(s) are running.`;
|
|
31066
31609
|
}
|
|
31067
|
-
function failStartEnsureTimeout(slockHome, serverIds, ready, opts) {
|
|
31068
|
-
const missing = serverIds.filter((id) => !ready.has(id));
|
|
31069
|
-
const target = opts.serverId && missing.length === 1 ? `${opts.serverLabel ?? opts.serverId}` : `${missing.length} daemon(s): ${missing.join(", ")}`;
|
|
31070
|
-
fail(
|
|
31071
|
-
"START_DAEMON_TIMEOUT",
|
|
31072
|
-
`Timed out waiting for ${target} to start. Run \`slock-computer status\` and inspect ${supervisorLogPath(slockHome)} plus per-server daemon logs under ~/.slock/computer/servers/<serverId>/daemon.log.`
|
|
31073
|
-
);
|
|
31074
|
-
}
|
|
31075
31610
|
async function runSupervisorStartupRecovery(slockHome) {
|
|
31076
31611
|
const parentHoldsLock = process.env[PARENT_LOCK_HELD_ENV_VAR] === "1";
|
|
31077
31612
|
try {
|
|
@@ -31105,10 +31640,10 @@ async function runSupervisorStartupRecovery(slockHome) {
|
|
|
31105
31640
|
}
|
|
31106
31641
|
async function resolveSupervisorIdentity() {
|
|
31107
31642
|
const here = fileURLToPath2(import.meta.url);
|
|
31108
|
-
const installRoot =
|
|
31643
|
+
const installRoot = dirname8(dirname8(here));
|
|
31109
31644
|
let version = null;
|
|
31110
31645
|
try {
|
|
31111
|
-
const raw = await
|
|
31646
|
+
const raw = await readFile7(joinPath(installRoot, "package.json"), "utf8");
|
|
31112
31647
|
const parsed = JSON.parse(raw);
|
|
31113
31648
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
31114
31649
|
version = parsed.version;
|
|
@@ -31128,7 +31663,7 @@ async function writeSupervisorVersionEvidence(slockHome) {
|
|
|
31128
31663
|
};
|
|
31129
31664
|
const dest = supervisorVersionPath(slockHome);
|
|
31130
31665
|
const tmp = `${dest}.tmp`;
|
|
31131
|
-
await
|
|
31666
|
+
await writeFile7(tmp, JSON.stringify(payload) + "\n", { mode: 384 });
|
|
31132
31667
|
await rename2(tmp, dest);
|
|
31133
31668
|
} catch (err) {
|
|
31134
31669
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -31140,7 +31675,7 @@ async function writeSupervisorVersionEvidence(slockHome) {
|
|
|
31140
31675
|
}
|
|
31141
31676
|
async function readSupervisorVersionEvidence(slockHome) {
|
|
31142
31677
|
try {
|
|
31143
|
-
const raw = await
|
|
31678
|
+
const raw = await readFile7(supervisorVersionPath(slockHome), "utf8");
|
|
31144
31679
|
const parsed = JSON.parse(raw);
|
|
31145
31680
|
if (typeof parsed.installRoot !== "string" || typeof parsed.pid !== "number" || typeof parsed.writtenAt !== "string" || parsed.version !== null && typeof parsed.version !== "string") {
|
|
31146
31681
|
return null;
|
|
@@ -31157,7 +31692,7 @@ async function readSupervisorVersionEvidence(slockHome) {
|
|
|
31157
31692
|
}
|
|
31158
31693
|
async function runSupervise() {
|
|
31159
31694
|
const slockHome = resolveSlockHome();
|
|
31160
|
-
await
|
|
31695
|
+
await mkdir8(computerDir(slockHome), { recursive: true });
|
|
31161
31696
|
await runSupervisorStartupRecovery(slockHome);
|
|
31162
31697
|
await writePidfileAt(supervisorPidPath(slockHome), process.pid);
|
|
31163
31698
|
await writeSupervisorVersionEvidence(slockHome);
|
|
@@ -31165,7 +31700,7 @@ async function runSupervise() {
|
|
|
31165
31700
|
const { [PARENT_LOCK_HELD_ENV_VAR]: _parentLockMarker, ...childEnv } = process.env;
|
|
31166
31701
|
const spawnChild = async (serverId) => {
|
|
31167
31702
|
const logPath = serverDaemonLogPath(slockHome, serverId);
|
|
31168
|
-
await
|
|
31703
|
+
await mkdir8(dirname8(logPath), { recursive: true });
|
|
31169
31704
|
const logFd = await open(logPath, "a");
|
|
31170
31705
|
const { command, args } = buildResidentSpawn("__run", serverId);
|
|
31171
31706
|
const child = spawn2(command, args, {
|
|
@@ -31254,157 +31789,127 @@ async function runSupervise() {
|
|
|
31254
31789
|
});
|
|
31255
31790
|
}
|
|
31256
31791
|
async function runStart(opts = {}, deps = {}) {
|
|
31257
|
-
|
|
31258
|
-
const attached = await listAttachedServerIds(slockHome);
|
|
31259
|
-
if (attached.length === 0) {
|
|
31260
|
-
fail(
|
|
31261
|
-
"NO_ATTACHMENT",
|
|
31262
|
-
"No server attachments yet. Run `slock-computer attach <serverId>` first."
|
|
31263
|
-
);
|
|
31264
|
-
}
|
|
31265
|
-
if (opts.serverId && !attached.includes(opts.serverId)) {
|
|
31266
|
-
fail(
|
|
31267
|
-
"NOT_ATTACHED",
|
|
31268
|
-
`Not attached to server ${opts.serverId}. Run \`slock-computer attach ${opts.serverId}\` first or omit the argument.`
|
|
31269
|
-
);
|
|
31270
|
-
}
|
|
31271
|
-
const managedTargets = opts.serverId ? [opts.serverId] : attached;
|
|
31272
|
-
for (const id of managedTargets) {
|
|
31273
|
-
await setServerManaged(slockHome, id);
|
|
31274
|
-
}
|
|
31275
|
-
const existing = await readPidfileAt(supervisorPidPath(slockHome));
|
|
31276
|
-
if (existing && isProcessAlive2(existing)) {
|
|
31277
|
-
info(`Supervisor already running (pid ${existing}).`);
|
|
31278
|
-
const ready2 = await waitForManagedDaemonPids(slockHome, managedTargets, deps);
|
|
31279
|
-
if (ready2.size !== managedTargets.length) {
|
|
31280
|
-
failStartEnsureTimeout(slockHome, managedTargets, ready2, opts);
|
|
31281
|
-
}
|
|
31282
|
-
info(formatReadySummary(ready2, managedTargets, opts));
|
|
31283
|
-
return;
|
|
31284
|
-
}
|
|
31285
|
-
if (opts.foreground) {
|
|
31286
|
-
info(
|
|
31287
|
-
`Running supervisor in the foreground (managing ${managedTargets.length} of ${attached.length} attached server(s)). Ctrl-C to stop.`
|
|
31288
|
-
);
|
|
31289
|
-
await runSupervise();
|
|
31290
|
-
return;
|
|
31291
|
-
}
|
|
31292
|
-
let pid;
|
|
31792
|
+
let spawnedBackground = null;
|
|
31293
31793
|
try {
|
|
31294
|
-
|
|
31295
|
-
|
|
31296
|
-
|
|
31297
|
-
|
|
31298
|
-
|
|
31299
|
-
|
|
31300
|
-
|
|
31301
|
-
|
|
31302
|
-
|
|
31303
|
-
|
|
31304
|
-
|
|
31305
|
-
|
|
31306
|
-
|
|
31307
|
-
|
|
31308
|
-
|
|
31309
|
-
|
|
31310
|
-
}
|
|
31311
|
-
|
|
31312
|
-
|
|
31313
|
-
|
|
31314
|
-
|
|
31315
|
-
|
|
31316
|
-
|
|
31317
|
-
|
|
31318
|
-
|
|
31319
|
-
|
|
31320
|
-
|
|
31321
|
-
|
|
31322
|
-
|
|
31323
|
-
|
|
31324
|
-
|
|
31325
|
-
|
|
31326
|
-
}
|
|
31327
|
-
if (!isAlive(pid)) {
|
|
31328
|
-
await clearPidfileAt(pidfilePath);
|
|
31329
|
-
info(`Supervisor not running (cleared stale pidfile for pid ${pid}).`);
|
|
31330
|
-
return;
|
|
31331
|
-
}
|
|
31332
|
-
try {
|
|
31333
|
-
killer(pid);
|
|
31334
|
-
} catch (err) {
|
|
31335
|
-
fail(
|
|
31336
|
-
"STOP_SIGNAL_FAILED",
|
|
31337
|
-
`Failed to send SIGTERM to supervisor (pid ${pid}): ${err instanceof Error ? err.message : String(err)}. Check process permissions or run: kill ${pid}`
|
|
31794
|
+
await start(
|
|
31795
|
+
{
|
|
31796
|
+
foreground: opts.foreground,
|
|
31797
|
+
serverId: opts.serverId ?? null,
|
|
31798
|
+
serverLabel: opts.serverLabel ?? null
|
|
31799
|
+
},
|
|
31800
|
+
{
|
|
31801
|
+
spawnDetachedSupervisor: deps.spawnDetachedSupervisor,
|
|
31802
|
+
readPidfile: deps.readPidfile,
|
|
31803
|
+
isProcessAlive: deps.isProcessAlive,
|
|
31804
|
+
sleep: deps.sleep,
|
|
31805
|
+
ensureTimeoutMs: deps.ensureTimeoutMs,
|
|
31806
|
+
ensurePollIntervalMs: deps.ensurePollIntervalMs,
|
|
31807
|
+
onEvent: (event) => {
|
|
31808
|
+
if (event.type === "already_running") {
|
|
31809
|
+
info(`Supervisor already running (pid ${event.supervisorPid}).`);
|
|
31810
|
+
} else if (event.type === "running") {
|
|
31811
|
+
info(
|
|
31812
|
+
`Running supervisor in the foreground (managing ${event.managedTargets.length} of ${event.attachedCount} attached server(s)). Ctrl-C to stop.`
|
|
31813
|
+
);
|
|
31814
|
+
} else if (event.type === "spawned") {
|
|
31815
|
+
info(`Supervisor started (pid ${event.supervisorPid}); keeps running after this terminal closes.`);
|
|
31816
|
+
spawnedBackground = {
|
|
31817
|
+
managedCount: event.managedTargets.length,
|
|
31818
|
+
attachedCount: event.attachedCount,
|
|
31819
|
+
logPath: supervisorLogPath(resolveSlockHome())
|
|
31820
|
+
};
|
|
31821
|
+
} else if (event.type === "ready") {
|
|
31822
|
+
info(formatReadySummary(event.ready, event.managedTargets, opts));
|
|
31823
|
+
}
|
|
31824
|
+
}
|
|
31825
|
+
}
|
|
31338
31826
|
);
|
|
31339
|
-
}
|
|
31340
|
-
|
|
31341
|
-
|
|
31342
|
-
|
|
31343
|
-
await clearPidfileAt(pidfilePath);
|
|
31344
|
-
info(`Stopped supervisor (pid ${pid}).`);
|
|
31345
|
-
return;
|
|
31827
|
+
} catch (err) {
|
|
31828
|
+
if (err instanceof CliExit) throw err;
|
|
31829
|
+
if (err instanceof ComputerServiceError) {
|
|
31830
|
+
fail(err.code, err.message);
|
|
31346
31831
|
}
|
|
31347
|
-
|
|
31348
|
-
}
|
|
31349
|
-
fail(
|
|
31350
|
-
"STOP_TIMEOUT",
|
|
31351
|
-
`Supervisor (pid ${pid}) did not exit within ${timeoutMs}ms after SIGTERM. Force-kill with: kill -9 ${pid}`
|
|
31352
|
-
);
|
|
31353
|
-
}
|
|
31354
|
-
async function runDetach(serverId, serverLabel = serverId) {
|
|
31355
|
-
assertValidServerId(serverId);
|
|
31356
|
-
const slockHome = resolveSlockHome();
|
|
31357
|
-
const a = await readServerAttachment(slockHome, serverId);
|
|
31358
|
-
if (!a) {
|
|
31359
|
-
fail("NOT_ATTACHED", `Not attached to server ${serverLabel} (nothing to detach).`);
|
|
31832
|
+
throw err;
|
|
31360
31833
|
}
|
|
31361
|
-
|
|
31362
|
-
|
|
31363
|
-
|
|
31364
|
-
|
|
31365
|
-
|
|
31366
|
-
|
|
31367
|
-
|
|
31368
|
-
|
|
31369
|
-
|
|
31370
|
-
|
|
31371
|
-
|
|
31372
|
-
|
|
31373
|
-
|
|
31374
|
-
|
|
31375
|
-
|
|
31376
|
-
|
|
31377
|
-
|
|
31378
|
-
|
|
31379
|
-
|
|
31380
|
-
|
|
31381
|
-
|
|
31382
|
-
|
|
31383
|
-
|
|
31384
|
-
|
|
31385
|
-
|
|
31386
|
-
|
|
31387
|
-
|
|
31388
|
-
|
|
31389
|
-
|
|
31390
|
-
|
|
31391
|
-
|
|
31834
|
+
if (spawnedBackground) {
|
|
31835
|
+
const sb = spawnedBackground;
|
|
31836
|
+
info(
|
|
31837
|
+
`Managing ${sb.managedCount} of ${sb.attachedCount} attached server(s). Logs: ${sb.logPath}`
|
|
31838
|
+
);
|
|
31839
|
+
info(`Per-server daemon logs: ~/.slock/computer/servers/<serverId>/daemon.log`);
|
|
31840
|
+
info(`Check state with \`slock-computer status\`.`);
|
|
31841
|
+
}
|
|
31842
|
+
}
|
|
31843
|
+
async function runStop(deps = {}) {
|
|
31844
|
+
try {
|
|
31845
|
+
await stop(
|
|
31846
|
+
{},
|
|
31847
|
+
{
|
|
31848
|
+
readPidfile: deps.readPidfile,
|
|
31849
|
+
isProcessAlive: deps.isProcessAlive,
|
|
31850
|
+
killSupervisor: deps.killSupervisor,
|
|
31851
|
+
sleep: deps.sleep,
|
|
31852
|
+
pollIntervalMs: deps.pollIntervalMs,
|
|
31853
|
+
timeoutMs: deps.timeoutMs,
|
|
31854
|
+
onEvent: (event) => {
|
|
31855
|
+
if (event.type === "not_running") {
|
|
31856
|
+
info("Supervisor not running.");
|
|
31857
|
+
} else if (event.type === "stale_pidfile_cleared") {
|
|
31858
|
+
info(`Supervisor not running (cleared stale pidfile for pid ${event.pid}).`);
|
|
31859
|
+
} else if (event.type === "stopped") {
|
|
31860
|
+
info(`Stopped supervisor (pid ${event.pid}).`);
|
|
31861
|
+
}
|
|
31862
|
+
}
|
|
31863
|
+
}
|
|
31864
|
+
);
|
|
31392
31865
|
} catch (err) {
|
|
31393
|
-
|
|
31394
|
-
|
|
31395
|
-
|
|
31396
|
-
|
|
31866
|
+
if (err instanceof CliExit) throw err;
|
|
31867
|
+
if (err instanceof ComputerServiceError) {
|
|
31868
|
+
fail(err.code, err.message);
|
|
31869
|
+
}
|
|
31870
|
+
throw err;
|
|
31871
|
+
}
|
|
31872
|
+
}
|
|
31873
|
+
async function runDetach(serverId, serverLabel = serverId) {
|
|
31874
|
+
try {
|
|
31875
|
+
await detach(
|
|
31876
|
+
{ serverId, serverLabel },
|
|
31877
|
+
{
|
|
31878
|
+
onEvent: (event) => {
|
|
31879
|
+
if (event.type === "revoke_failed") {
|
|
31880
|
+
process.stderr.write(`Warning: ${event.message}
|
|
31881
|
+
`);
|
|
31882
|
+
} else if (event.type === "detached") {
|
|
31883
|
+
info(`Detached from server ${event.serverLabel}.`);
|
|
31884
|
+
info(
|
|
31885
|
+
`The supervisor (if running) will stop that server's daemon on its next reconcile tick.`
|
|
31886
|
+
);
|
|
31887
|
+
}
|
|
31888
|
+
}
|
|
31889
|
+
}
|
|
31397
31890
|
);
|
|
31398
|
-
}
|
|
31399
|
-
|
|
31891
|
+
} catch (err) {
|
|
31892
|
+
if (err instanceof CliExit) throw err;
|
|
31893
|
+
if (err instanceof ComputerServiceError) {
|
|
31894
|
+
fail(err.code, err.message);
|
|
31895
|
+
}
|
|
31896
|
+
throw err;
|
|
31400
31897
|
}
|
|
31401
31898
|
}
|
|
31402
31899
|
|
|
31403
31900
|
// src/setup.ts
|
|
31404
31901
|
var USER_SESSION_EXPIRY_LEEWAY_MS = 3e4;
|
|
31902
|
+
async function readUserSessionUserId(slockHome) {
|
|
31903
|
+
try {
|
|
31904
|
+
const parsed = JSON.parse(await readFile8(userSessionPath(slockHome), "utf8"));
|
|
31905
|
+
return typeof parsed.userId === "string" ? parsed.userId : "";
|
|
31906
|
+
} catch {
|
|
31907
|
+
return "";
|
|
31908
|
+
}
|
|
31909
|
+
}
|
|
31405
31910
|
async function hasValidUserSession(slockHome) {
|
|
31406
31911
|
try {
|
|
31407
|
-
const parsed = JSON.parse(await
|
|
31912
|
+
const parsed = JSON.parse(await readFile8(userSessionPath(slockHome), "utf8"));
|
|
31408
31913
|
return parsed.kind === "user-session" && typeof parsed.accessToken === "string" && parsed.accessToken.length > 0 && !isJwtExpired(parsed.accessToken);
|
|
31409
31914
|
} catch {
|
|
31410
31915
|
return false;
|
|
@@ -31424,7 +31929,7 @@ async function refreshUserSession(slockHome, serverUrl) {
|
|
|
31424
31929
|
const file = userSessionPath(slockHome);
|
|
31425
31930
|
let session;
|
|
31426
31931
|
try {
|
|
31427
|
-
session = JSON.parse(await
|
|
31932
|
+
session = JSON.parse(await readFile8(file, "utf8"));
|
|
31428
31933
|
} catch {
|
|
31429
31934
|
return false;
|
|
31430
31935
|
}
|
|
@@ -31434,7 +31939,7 @@ async function refreshUserSession(slockHome, serverUrl) {
|
|
|
31434
31939
|
const baseUrl = resolveServerUrl(serverUrl, session.serverUrl, process.env.SLOCK_SERVER_URL);
|
|
31435
31940
|
const tmpFile = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
31436
31941
|
try {
|
|
31437
|
-
const res = await (0,
|
|
31942
|
+
const res = await (0, import_undici3.fetch)(new URL("/api/auth/refresh", baseUrl).toString(), {
|
|
31438
31943
|
method: "POST",
|
|
31439
31944
|
headers: { "Content-Type": "application/json" },
|
|
31440
31945
|
body: JSON.stringify({ refreshToken: session.refreshToken })
|
|
@@ -31443,8 +31948,8 @@ async function refreshUserSession(slockHome, serverUrl) {
|
|
|
31443
31948
|
if (res.status !== 200 || typeof body?.accessToken !== "string" || typeof body.refreshToken !== "string") {
|
|
31444
31949
|
return false;
|
|
31445
31950
|
}
|
|
31446
|
-
await
|
|
31447
|
-
await
|
|
31951
|
+
await mkdir9(dirname9(file), { recursive: true });
|
|
31952
|
+
await writeFile8(
|
|
31448
31953
|
tmpFile,
|
|
31449
31954
|
JSON.stringify(
|
|
31450
31955
|
{
|
|
@@ -31469,49 +31974,74 @@ async function refreshUserSession(slockHome, serverUrl) {
|
|
|
31469
31974
|
return false;
|
|
31470
31975
|
}
|
|
31471
31976
|
}
|
|
31472
|
-
async function
|
|
31473
|
-
|
|
31474
|
-
|
|
31475
|
-
|
|
31476
|
-
|
|
31477
|
-
return false;
|
|
31478
|
-
}
|
|
31479
|
-
for (const name of machineDirs) {
|
|
31480
|
-
if (!name.startsWith("machine-")) continue;
|
|
31481
|
-
try {
|
|
31482
|
-
const raw = await readFile6(join3(slockHome, "machines", name, "daemon.lock", "owner.json"), "utf8");
|
|
31483
|
-
const owner = JSON.parse(raw);
|
|
31484
|
-
if (typeof owner.pid === "number" && isProcessAlive2(owner.pid)) return true;
|
|
31485
|
-
} catch {
|
|
31486
|
-
}
|
|
31487
|
-
}
|
|
31488
|
-
return false;
|
|
31977
|
+
async function defaultConfirmMigrate(prompt) {
|
|
31978
|
+
process.stdout.write(prompt);
|
|
31979
|
+
const line = await readLine(false);
|
|
31980
|
+
const norm = line.trim().toLowerCase();
|
|
31981
|
+
return norm === "y" || norm === "yes";
|
|
31489
31982
|
}
|
|
31490
|
-
function
|
|
31491
|
-
|
|
31983
|
+
async function defaultReadMigrateSecret(prompt) {
|
|
31984
|
+
process.stdout.write(prompt);
|
|
31985
|
+
const line = await readLine(true);
|
|
31986
|
+
process.stdout.write("\n");
|
|
31987
|
+
return line.trim();
|
|
31492
31988
|
}
|
|
31493
|
-
function
|
|
31494
|
-
|
|
31989
|
+
async function readLine(masked) {
|
|
31990
|
+
const stdin = process.stdin;
|
|
31991
|
+
const wasRaw = stdin.isTTY === true && stdin.isRaw === true;
|
|
31992
|
+
const enterRaw = masked && stdin.isTTY === true && typeof stdin.setRawMode === "function";
|
|
31993
|
+
if (enterRaw) stdin.setRawMode(true);
|
|
31994
|
+
stdin.resume();
|
|
31995
|
+
return await new Promise((resolve) => {
|
|
31996
|
+
let buf = "";
|
|
31997
|
+
const cleanup = () => {
|
|
31998
|
+
stdin.off("data", onData);
|
|
31999
|
+
stdin.off("end", onEnd);
|
|
32000
|
+
stdin.pause();
|
|
32001
|
+
if (enterRaw) stdin.setRawMode(wasRaw);
|
|
32002
|
+
};
|
|
32003
|
+
const onData = (chunk) => {
|
|
32004
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
32005
|
+
for (const ch of text) {
|
|
32006
|
+
if (ch === "\n" || ch === "\r") {
|
|
32007
|
+
cleanup();
|
|
32008
|
+
resolve(buf);
|
|
32009
|
+
return;
|
|
32010
|
+
}
|
|
32011
|
+
if (ch === "") {
|
|
32012
|
+
cleanup();
|
|
32013
|
+
resolve("");
|
|
32014
|
+
return;
|
|
32015
|
+
}
|
|
32016
|
+
if (ch === "\x7F" || ch === "\b") {
|
|
32017
|
+
buf = buf.slice(0, -1);
|
|
32018
|
+
continue;
|
|
32019
|
+
}
|
|
32020
|
+
buf += ch;
|
|
32021
|
+
}
|
|
32022
|
+
};
|
|
32023
|
+
const onEnd = () => {
|
|
32024
|
+
cleanup();
|
|
32025
|
+
resolve(buf);
|
|
32026
|
+
};
|
|
32027
|
+
stdin.on("data", onData);
|
|
32028
|
+
stdin.on("end", onEnd);
|
|
32029
|
+
});
|
|
31495
32030
|
}
|
|
31496
32031
|
async function runSetup(opts, deps = {}) {
|
|
31497
32032
|
const slockHome = resolveSlockHome();
|
|
31498
32033
|
const isTty = deps.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
31499
|
-
const
|
|
31500
|
-
const
|
|
31501
|
-
const
|
|
31502
|
-
const start = deps.runStart ?? runStart;
|
|
32034
|
+
const login2 = deps.runLogin ?? runLogin;
|
|
32035
|
+
const attach2 = deps.runAttach ?? runAttach;
|
|
32036
|
+
const start2 = deps.runStart ?? runStart;
|
|
31503
32037
|
const refreshSession = deps.refreshUserSession ?? refreshUserSession;
|
|
31504
|
-
const
|
|
32038
|
+
const detectMigration = deps.detectLegacyMigration ?? detectLegacyMigration;
|
|
32039
|
+
const confirmMigrate = deps.confirmMigrate ?? defaultConfirmMigrate;
|
|
32040
|
+
const readMigrateSecret = deps.readMigrateSecret ?? defaultReadMigrateSecret;
|
|
31505
32041
|
if (!isTty && !opts.yes) {
|
|
31506
32042
|
fail(
|
|
31507
32043
|
"NON_INTERACTIVE_SETUP_REQUIRES_FLAGS",
|
|
31508
|
-
"Non-interactive setup requires --yes after you have confirmed the login/attach/
|
|
31509
|
-
);
|
|
31510
|
-
}
|
|
31511
|
-
if (!opts.adoptLegacy && hasExplicitLegacyKeyInput(opts)) {
|
|
31512
|
-
fail(
|
|
31513
|
-
"LEGACY_KEY_OUTSIDE_ADOPT",
|
|
31514
|
-
"`--legacy-api-key*` options are only accepted with `slock-computer setup --adopt-legacy` or `slock-computer adopt-legacy`."
|
|
32044
|
+
"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."
|
|
31515
32045
|
);
|
|
31516
32046
|
}
|
|
31517
32047
|
const label = formatServerSlugDisplay(opts.serverSlug);
|
|
@@ -31527,7 +32057,7 @@ async function runSetup(opts, deps = {}) {
|
|
|
31527
32057
|
);
|
|
31528
32058
|
}
|
|
31529
32059
|
info("User session: missing or expired; starting login.");
|
|
31530
|
-
await
|
|
32060
|
+
await login2({ serverUrl: opts.serverUrl, orchestrated: true });
|
|
31531
32061
|
}
|
|
31532
32062
|
} else {
|
|
31533
32063
|
info("User session: already logged in.");
|
|
@@ -31536,35 +32066,83 @@ async function runSetup(opts, deps = {}) {
|
|
|
31536
32066
|
if (attachment) {
|
|
31537
32067
|
info(`Attachment: already attached to ${label}.`);
|
|
31538
32068
|
} else {
|
|
31539
|
-
|
|
31540
|
-
|
|
31541
|
-
|
|
31542
|
-
|
|
31543
|
-
|
|
31544
|
-
"
|
|
31545
|
-
|
|
31546
|
-
|
|
31547
|
-
|
|
31548
|
-
|
|
31549
|
-
|
|
31550
|
-
|
|
31551
|
-
|
|
31552
|
-
|
|
31553
|
-
|
|
31554
|
-
|
|
31555
|
-
|
|
31556
|
-
|
|
31557
|
-
|
|
31558
|
-
|
|
31559
|
-
|
|
31560
|
-
|
|
31561
|
-
|
|
31562
|
-
|
|
31563
|
-
|
|
31564
|
-
|
|
31565
|
-
|
|
32069
|
+
let migrated = false;
|
|
32070
|
+
if (isTty) {
|
|
32071
|
+
const detection = await detectMigration(slockHome, await readUserSessionUserId(slockHome));
|
|
32072
|
+
if (detection.kind === "match") {
|
|
32073
|
+
const where = detection.serverUrl ? ` attached to ${detection.serverUrl}` : "";
|
|
32074
|
+
const who = detection.machineName ? ` "${detection.machineName}"` : "";
|
|
32075
|
+
const accepted = await confirmMigrate(
|
|
32076
|
+
`Migration: detected legacy daemon machine${who}${where}. Migrate it under Computer instead of creating a fresh attachment? [y/N] `
|
|
32077
|
+
);
|
|
32078
|
+
if (accepted) {
|
|
32079
|
+
const rawKey = await readMigrateSecret(
|
|
32080
|
+
"Paste legacy api key (sk_machine_* or sk_daemon_*); input is hidden: "
|
|
32081
|
+
);
|
|
32082
|
+
if (rawKey.length === 0) {
|
|
32083
|
+
info("Migration: no key provided; falling back to fresh attach.");
|
|
32084
|
+
} else if (!rawKey.startsWith("sk_machine_") && !rawKey.startsWith("sk_daemon_")) {
|
|
32085
|
+
fail(
|
|
32086
|
+
"LEGACY_KEY_INVALID",
|
|
32087
|
+
"Provided key does not look like a legacy machine key (expected `sk_machine_*` or `sk_daemon_*`)."
|
|
32088
|
+
);
|
|
32089
|
+
} else {
|
|
32090
|
+
try {
|
|
32091
|
+
await adoptLegacy(
|
|
32092
|
+
{
|
|
32093
|
+
serverSlug: opts.serverSlug,
|
|
32094
|
+
serverUrl: opts.serverUrl,
|
|
32095
|
+
name: opts.name,
|
|
32096
|
+
rawKey,
|
|
32097
|
+
mode: "legacy_key_stdin",
|
|
32098
|
+
redactedPrefix: rawKey.slice(0, 8)
|
|
32099
|
+
},
|
|
32100
|
+
{
|
|
32101
|
+
onEvent: (event) => {
|
|
32102
|
+
if (event.type === "adopting") {
|
|
32103
|
+
info(
|
|
32104
|
+
`Adopting legacy daemon for ${formatServerSlugDisplay(event.serverSlug)} via ${event.mode}\u2026`
|
|
32105
|
+
);
|
|
32106
|
+
} else if (event.type === "preflight") {
|
|
32107
|
+
info(
|
|
32108
|
+
event.resumed ? "Adopted (resumed prior attachment); running preflight\u2026" : "Adopted; running preflight\u2026"
|
|
32109
|
+
);
|
|
32110
|
+
} else if (event.type === "adopted") {
|
|
32111
|
+
info(`Adopted. Computer state written to ${event.attachmentPath}`);
|
|
32112
|
+
info(` server: ${formatServerSlugDisplay(event.serverSlug)}`);
|
|
32113
|
+
info(` serverMachine: ${event.serverMachineId}`);
|
|
32114
|
+
info(` legacyMachine: ${event.legacyMachineId}`);
|
|
32115
|
+
switch (event.legacyStop.outcome) {
|
|
32116
|
+
case "absent":
|
|
32117
|
+
info(" legacy daemon: not detected on this Computer (no local lock file)");
|
|
32118
|
+
break;
|
|
32119
|
+
case "already_dead":
|
|
32120
|
+
info(` legacy daemon: already stopped (pid ${event.legacyStop.pid} not running)`);
|
|
32121
|
+
break;
|
|
32122
|
+
case "stopped":
|
|
32123
|
+
info(` legacy daemon: stopped (pid ${event.legacyStop.pid}, SIGTERM)`);
|
|
32124
|
+
break;
|
|
32125
|
+
}
|
|
32126
|
+
}
|
|
32127
|
+
}
|
|
32128
|
+
}
|
|
32129
|
+
);
|
|
32130
|
+
migrated = true;
|
|
32131
|
+
} catch (err) {
|
|
32132
|
+
if (err instanceof CliExit) throw err;
|
|
32133
|
+
if (err instanceof ComputerServiceError) {
|
|
32134
|
+
fail(err.code, err.message);
|
|
32135
|
+
}
|
|
32136
|
+
throw err;
|
|
32137
|
+
}
|
|
32138
|
+
}
|
|
32139
|
+
} else {
|
|
32140
|
+
info("Migration: declined; falling back to fresh attach.");
|
|
32141
|
+
}
|
|
31566
32142
|
}
|
|
31567
|
-
|
|
32143
|
+
}
|
|
32144
|
+
if (!migrated) {
|
|
32145
|
+
await attach2({
|
|
31568
32146
|
serverSlug: opts.serverSlug,
|
|
31569
32147
|
serverUrl: opts.serverUrl,
|
|
31570
32148
|
name: opts.name,
|
|
@@ -31576,7 +32154,7 @@ async function runSetup(opts, deps = {}) {
|
|
|
31576
32154
|
if (!attachment) {
|
|
31577
32155
|
fail(
|
|
31578
32156
|
"SETUP_ATTACHMENT_MISSING",
|
|
31579
|
-
`Setup did not produce a local attachment for ${label}. Re-run \`slock-computer attach ${label}
|
|
32157
|
+
`Setup did not produce a local attachment for ${label}. Re-run \`slock-computer attach ${label}\`.`
|
|
31580
32158
|
);
|
|
31581
32159
|
}
|
|
31582
32160
|
}
|
|
@@ -31584,7 +32162,7 @@ async function runSetup(opts, deps = {}) {
|
|
|
31584
32162
|
info(`Start: skipped (--no-start). Run \`slock-computer start ${label}\` when ready.`);
|
|
31585
32163
|
return;
|
|
31586
32164
|
}
|
|
31587
|
-
await
|
|
32165
|
+
await start2({
|
|
31588
32166
|
serverId: attachment.serverId,
|
|
31589
32167
|
serverLabel: opts.serverSlug,
|
|
31590
32168
|
foreground: opts.foreground
|
|
@@ -31593,10 +32171,10 @@ async function runSetup(opts, deps = {}) {
|
|
|
31593
32171
|
|
|
31594
32172
|
// src/status.ts
|
|
31595
32173
|
init_esm_shims();
|
|
31596
|
-
import { readFile as
|
|
32174
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
31597
32175
|
async function readUserSession(path3) {
|
|
31598
32176
|
try {
|
|
31599
|
-
const parsed = JSON.parse(await
|
|
32177
|
+
const parsed = JSON.parse(await readFile9(path3, "utf8"));
|
|
31600
32178
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
31601
32179
|
return { state: "present", session: parsed, error: null };
|
|
31602
32180
|
}
|
|
@@ -31624,32 +32202,31 @@ async function deriveHealth(slockHome, serverId, daemon) {
|
|
|
31624
32202
|
if (await isDegraded(slockHome, serverId)) return "degraded";
|
|
31625
32203
|
return "ok";
|
|
31626
32204
|
}
|
|
31627
|
-
async function buildStatusReport() {
|
|
31628
|
-
const
|
|
31629
|
-
const sessionRead = await readUserSession(userSessionPath(slockHome));
|
|
32205
|
+
async function buildStatusReport(installRoot) {
|
|
32206
|
+
const sessionRead = await readUserSession(userSessionPath(installRoot));
|
|
31630
32207
|
const session = sessionRead.session;
|
|
31631
|
-
const attachments = await listServerAttachments(
|
|
32208
|
+
const attachments = await listServerAttachments(installRoot);
|
|
31632
32209
|
const supervisor = {
|
|
31633
|
-
...await pidStatus(supervisorPidPath(
|
|
31634
|
-
logPath: supervisorLogPath(
|
|
32210
|
+
...await pidStatus(supervisorPidPath(installRoot)),
|
|
32211
|
+
logPath: supervisorLogPath(installRoot)
|
|
31635
32212
|
};
|
|
31636
32213
|
const servers = [];
|
|
31637
32214
|
for (const a of attachments) {
|
|
31638
|
-
const daemon = await pidStatus(serverDaemonPidPath(
|
|
32215
|
+
const daemon = await pidStatus(serverDaemonPidPath(installRoot, a.serverId));
|
|
31639
32216
|
servers.push({
|
|
31640
32217
|
serverId: a.serverId,
|
|
31641
32218
|
serverSlug: a.serverSlug ?? null,
|
|
31642
32219
|
serverMachineId: a.serverMachineId,
|
|
31643
32220
|
serverUrl: a.serverUrl,
|
|
31644
32221
|
attachedAt: a.attachedAt ?? null,
|
|
31645
|
-
daemonLogPath: serverDaemonLogPath(
|
|
32222
|
+
daemonLogPath: serverDaemonLogPath(installRoot, a.serverId),
|
|
31646
32223
|
daemon,
|
|
31647
|
-
health: await deriveHealth(
|
|
32224
|
+
health: await deriveHealth(installRoot, a.serverId, daemon)
|
|
31648
32225
|
});
|
|
31649
32226
|
}
|
|
31650
32227
|
const loggedIn = session?.kind === "user-session" && typeof session.accessToken === "string" && session.accessToken.length > 0;
|
|
31651
32228
|
return {
|
|
31652
|
-
slockHome,
|
|
32229
|
+
slockHome: installRoot,
|
|
31653
32230
|
loggedIn,
|
|
31654
32231
|
userId: session ? str(session.userId) : null,
|
|
31655
32232
|
loginServerUrl: session ? str(session.serverUrl) : null,
|
|
@@ -31662,7 +32239,7 @@ function pad(s, n) {
|
|
|
31662
32239
|
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
31663
32240
|
}
|
|
31664
32241
|
async function runStatus(opts) {
|
|
31665
|
-
const report = await buildStatusReport();
|
|
32242
|
+
const report = await buildStatusReport(resolveSlockHome());
|
|
31666
32243
|
if (opts.json) {
|
|
31667
32244
|
info(JSON.stringify(report, null, 2));
|
|
31668
32245
|
return;
|
|
@@ -31767,42 +32344,92 @@ async function resolveTargetAttachment(opts) {
|
|
|
31767
32344
|
return a;
|
|
31768
32345
|
}
|
|
31769
32346
|
|
|
32347
|
+
// src/lib/readers.ts
|
|
32348
|
+
init_esm_shims();
|
|
32349
|
+
|
|
32350
|
+
// src/lib/types.ts
|
|
32351
|
+
init_esm_shims();
|
|
32352
|
+
var StateReaderError = class extends Error {
|
|
32353
|
+
code;
|
|
32354
|
+
constructor(code, message) {
|
|
32355
|
+
super(message);
|
|
32356
|
+
this.name = "StateReaderError";
|
|
32357
|
+
this.code = code;
|
|
32358
|
+
}
|
|
32359
|
+
};
|
|
32360
|
+
|
|
32361
|
+
// src/lib/readers.ts
|
|
32362
|
+
async function listRunners(installRoot, opts = {}) {
|
|
32363
|
+
const all = await listServerAttachments(installRoot);
|
|
32364
|
+
let subset = all;
|
|
32365
|
+
if (opts.serverId !== void 0) {
|
|
32366
|
+
const found = all.find((a) => a.serverId === opts.serverId);
|
|
32367
|
+
if (!found) {
|
|
32368
|
+
throw new StateReaderError(
|
|
32369
|
+
"NOT_ATTACHED",
|
|
32370
|
+
`Server ${opts.serverId} is not attached to this Computer.`
|
|
32371
|
+
);
|
|
32372
|
+
}
|
|
32373
|
+
subset = [found];
|
|
32374
|
+
}
|
|
32375
|
+
const servers = [];
|
|
32376
|
+
for (const a of subset) {
|
|
32377
|
+
const client = new RunnersClient(a.serverUrl, a.apiKey);
|
|
32378
|
+
const result = await client.list();
|
|
32379
|
+
const idCols = { serverId: a.serverId, serverSlug: a.serverSlug ?? null };
|
|
32380
|
+
if (result.status === "success") {
|
|
32381
|
+
servers.push({
|
|
32382
|
+
...idCols,
|
|
32383
|
+
status: "ok",
|
|
32384
|
+
whitelist: result.whitelist,
|
|
32385
|
+
runners: result.runners
|
|
32386
|
+
});
|
|
32387
|
+
} else if (result.status === "unauthorized") {
|
|
32388
|
+
servers.push({ ...idCols, status: "unauthorized" });
|
|
32389
|
+
} else {
|
|
32390
|
+
servers.push({ ...idCols, status: "error", code: result.code });
|
|
32391
|
+
}
|
|
32392
|
+
}
|
|
32393
|
+
return { servers };
|
|
32394
|
+
}
|
|
32395
|
+
|
|
31770
32396
|
// src/runners.ts
|
|
31771
32397
|
async function runRunnersList(opts) {
|
|
31772
|
-
const
|
|
31773
|
-
const
|
|
31774
|
-
const
|
|
31775
|
-
const
|
|
31776
|
-
|
|
32398
|
+
const serverId = await resolveTargetServerId({ server: opts.server });
|
|
32399
|
+
const installRoot = resolveSlockHome();
|
|
32400
|
+
const { servers } = await listRunners(installRoot, { serverId });
|
|
32401
|
+
const block = servers[0];
|
|
32402
|
+
const label = block.serverSlug ? formatServerSlugDisplay(block.serverSlug) : block.serverId;
|
|
32403
|
+
if (block.status === "unauthorized") {
|
|
31777
32404
|
fail(
|
|
31778
32405
|
"RUNNERS_UNAUTHORIZED",
|
|
31779
32406
|
`The Computer credential for ${label} was rejected. Re-run \`slock-computer attach ${label}\`.`
|
|
31780
32407
|
);
|
|
31781
32408
|
}
|
|
31782
|
-
if (
|
|
32409
|
+
if (block.status === "error") {
|
|
31783
32410
|
fail(
|
|
31784
32411
|
"RUNNERS_LIST_FAILED",
|
|
31785
|
-
`Could not list runners on ${label} (${
|
|
32412
|
+
`Could not list runners on ${label} (${block.code}). Check --server-url / server version.`
|
|
31786
32413
|
);
|
|
31787
32414
|
}
|
|
31788
32415
|
if (opts.json) {
|
|
31789
32416
|
info(
|
|
31790
32417
|
JSON.stringify(
|
|
31791
|
-
{ server: label, serverId:
|
|
32418
|
+
{ server: label, serverId: block.serverId, whitelist: block.whitelist, runners: block.runners },
|
|
31792
32419
|
null,
|
|
31793
32420
|
2
|
|
31794
32421
|
)
|
|
31795
32422
|
);
|
|
31796
32423
|
return;
|
|
31797
32424
|
}
|
|
31798
|
-
if (
|
|
32425
|
+
if (block.runners.length === 0) {
|
|
31799
32426
|
info(`No runners on server ${label}.`);
|
|
31800
32427
|
return;
|
|
31801
32428
|
}
|
|
31802
32429
|
info("");
|
|
31803
32430
|
info(`Server ${label}:`);
|
|
31804
32431
|
info(" AGENT STATUS RUNTIME MODEL NAME");
|
|
31805
|
-
for (const r of
|
|
32432
|
+
for (const r of block.runners) {
|
|
31806
32433
|
info(
|
|
31807
32434
|
` ${r.agentId.padEnd(38)}${(r.status ?? "").padEnd(11)}${(r.runtime ?? "").padEnd(10)}${(r.model ?? "").padEnd(15)}${r.name ?? ""}`
|
|
31808
32435
|
);
|
|
@@ -31851,7 +32478,8 @@ function redactSecrets(line) {
|
|
|
31851
32478
|
return out;
|
|
31852
32479
|
}
|
|
31853
32480
|
async function runDoctorChecks() {
|
|
31854
|
-
const
|
|
32481
|
+
const slockHome = resolveSlockHome();
|
|
32482
|
+
const report = await buildStatusReport(slockHome);
|
|
31855
32483
|
const checks = [];
|
|
31856
32484
|
checks.push({ name: "SLOCK_HOME", ok: true, detail: report.slockHome });
|
|
31857
32485
|
checks.push(
|
|
@@ -31864,7 +32492,6 @@ async function runDoctorChecks() {
|
|
|
31864
32492
|
detail: "stopped (run `slock-computer start` when you want background)"
|
|
31865
32493
|
}
|
|
31866
32494
|
);
|
|
31867
|
-
const slockHome = resolveSlockHome();
|
|
31868
32495
|
const attachments = await listServerAttachments(slockHome);
|
|
31869
32496
|
if (attachments.length === 0) {
|
|
31870
32497
|
checks.push({
|
|
@@ -31976,14 +32603,14 @@ async function runDoctor(opts) {
|
|
|
31976
32603
|
|
|
31977
32604
|
// src/logs.ts
|
|
31978
32605
|
init_esm_shims();
|
|
31979
|
-
import { readFile as
|
|
32606
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
31980
32607
|
var DEFAULT_LINES = 200;
|
|
31981
32608
|
async function runLogs(opts) {
|
|
31982
32609
|
const home = resolveSlockHome();
|
|
31983
32610
|
const file = opts.supervisor ? supervisorLogPath(home) : serverDaemonLogPath(home, await resolveTargetServerId({ server: opts.server }));
|
|
31984
32611
|
let content;
|
|
31985
32612
|
try {
|
|
31986
|
-
content = await
|
|
32613
|
+
content = await readFile10(file, "utf8");
|
|
31987
32614
|
} catch {
|
|
31988
32615
|
fail(
|
|
31989
32616
|
"NO_DAEMON_LOG",
|
|
@@ -31997,16 +32624,166 @@ async function runLogs(opts) {
|
|
|
31997
32624
|
for (const line of tail) info(redactSecrets(line));
|
|
31998
32625
|
}
|
|
31999
32626
|
|
|
32627
|
+
// src/reset.ts
|
|
32628
|
+
init_esm_shims();
|
|
32629
|
+
|
|
32630
|
+
// src/serviceState.ts
|
|
32631
|
+
init_esm_shims();
|
|
32632
|
+
import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile9, appendFile as appendFile3 } from "fs/promises";
|
|
32633
|
+
import { dirname as dirname10 } from "path";
|
|
32634
|
+
|
|
32635
|
+
// src/lib/state.ts
|
|
32636
|
+
init_esm_shims();
|
|
32637
|
+
var SERVICE_STATE_VALUES = [
|
|
32638
|
+
"starting",
|
|
32639
|
+
"running",
|
|
32640
|
+
"degraded",
|
|
32641
|
+
"stopping",
|
|
32642
|
+
"stopped"
|
|
32643
|
+
];
|
|
32644
|
+
function isServiceState(value) {
|
|
32645
|
+
return typeof value === "string" && SERVICE_STATE_VALUES.includes(value);
|
|
32646
|
+
}
|
|
32647
|
+
|
|
32648
|
+
// src/serviceState.ts
|
|
32649
|
+
var DEFAULT_STATE = {
|
|
32650
|
+
state: "running",
|
|
32651
|
+
crashHistory: []
|
|
32652
|
+
};
|
|
32653
|
+
async function readServiceState(slockHome) {
|
|
32654
|
+
try {
|
|
32655
|
+
const raw = await readFile11(serviceStatePath(slockHome), "utf8");
|
|
32656
|
+
const parsed = JSON.parse(raw);
|
|
32657
|
+
if (!parsed || typeof parsed !== "object") return { ...DEFAULT_STATE };
|
|
32658
|
+
const obj = parsed;
|
|
32659
|
+
const state = isServiceState(obj.state) ? obj.state : "running";
|
|
32660
|
+
const crashHistory = Array.isArray(obj.crashHistory) ? obj.crashHistory.filter(isCrashEntry) : [];
|
|
32661
|
+
return { state, crashHistory };
|
|
32662
|
+
} catch {
|
|
32663
|
+
return { ...DEFAULT_STATE };
|
|
32664
|
+
}
|
|
32665
|
+
}
|
|
32666
|
+
async function writeServiceState(slockHome, file) {
|
|
32667
|
+
const path3 = serviceStatePath(slockHome);
|
|
32668
|
+
await mkdir10(dirname10(path3), { recursive: true });
|
|
32669
|
+
await writeFile9(path3, JSON.stringify(file), { mode: 384 });
|
|
32670
|
+
}
|
|
32671
|
+
function isCrashEntry(value) {
|
|
32672
|
+
if (!value || typeof value !== "object") return false;
|
|
32673
|
+
const obj = value;
|
|
32674
|
+
return typeof obj.at === "string";
|
|
32675
|
+
}
|
|
32676
|
+
async function emitServiceStateTransition(slockHome, fromState, toState, trigger) {
|
|
32677
|
+
const entry = {
|
|
32678
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
32679
|
+
kind: "service-state-changed",
|
|
32680
|
+
fromState,
|
|
32681
|
+
toState,
|
|
32682
|
+
trigger
|
|
32683
|
+
};
|
|
32684
|
+
try {
|
|
32685
|
+
const path3 = supervisorLogPath(slockHome);
|
|
32686
|
+
await mkdir10(dirname10(path3), { recursive: true });
|
|
32687
|
+
await appendFile3(path3, JSON.stringify(entry) + "\n");
|
|
32688
|
+
} catch {
|
|
32689
|
+
}
|
|
32690
|
+
}
|
|
32691
|
+
async function clearServiceCrashHistory(slockHome) {
|
|
32692
|
+
const current = await readServiceState(slockHome);
|
|
32693
|
+
const previousState = current.state;
|
|
32694
|
+
const clearedCrashCount = current.crashHistory.length;
|
|
32695
|
+
const next = { state: "running", crashHistory: [] };
|
|
32696
|
+
await writeServiceState(slockHome, next);
|
|
32697
|
+
await emitServiceStateTransition(
|
|
32698
|
+
slockHome,
|
|
32699
|
+
previousState,
|
|
32700
|
+
"running",
|
|
32701
|
+
"reset-service"
|
|
32702
|
+
);
|
|
32703
|
+
return { previousState, clearedCrashCount };
|
|
32704
|
+
}
|
|
32705
|
+
|
|
32706
|
+
// src/reset.ts
|
|
32707
|
+
async function resetService(installRoot) {
|
|
32708
|
+
const { previousState, clearedCrashCount } = await clearServiceCrashHistory(installRoot);
|
|
32709
|
+
return {
|
|
32710
|
+
status: "ok",
|
|
32711
|
+
previousState,
|
|
32712
|
+
clearedCrashCount
|
|
32713
|
+
};
|
|
32714
|
+
}
|
|
32715
|
+
async function resetRunner(installRoot, serverId) {
|
|
32716
|
+
const attachment = await readServerAttachment(installRoot, serverId);
|
|
32717
|
+
if (!attachment) {
|
|
32718
|
+
return { status: "not-found", serverId };
|
|
32719
|
+
}
|
|
32720
|
+
const outcome = await resetRunnerHealth(installRoot, serverId);
|
|
32721
|
+
if (outcome.status === "not-found") {
|
|
32722
|
+
return { status: "not-found", serverId };
|
|
32723
|
+
}
|
|
32724
|
+
return {
|
|
32725
|
+
status: "ok",
|
|
32726
|
+
serverId,
|
|
32727
|
+
previousState: outcome.previousState ?? "running",
|
|
32728
|
+
clearedCrashCount: outcome.clearedCrashCount ?? 0
|
|
32729
|
+
};
|
|
32730
|
+
}
|
|
32731
|
+
async function runReset(opts) {
|
|
32732
|
+
const wantsService = opts.service === true;
|
|
32733
|
+
const wantsRunner = opts.runner === true;
|
|
32734
|
+
if (wantsService && wantsRunner) {
|
|
32735
|
+
fail(
|
|
32736
|
+
"RESET_SCOPE_AMBIGUOUS",
|
|
32737
|
+
"`--service` and `--runner` are mutually exclusive. Pass exactly one."
|
|
32738
|
+
);
|
|
32739
|
+
}
|
|
32740
|
+
if (!wantsService && !wantsRunner) {
|
|
32741
|
+
fail(
|
|
32742
|
+
"RESET_SCOPE_MISSING",
|
|
32743
|
+
"Pass `--service` to clear the service-level crash history, or `--runner` to clear a per-runner crash history."
|
|
32744
|
+
);
|
|
32745
|
+
}
|
|
32746
|
+
const slockHome = resolveSlockHome();
|
|
32747
|
+
if (wantsService) {
|
|
32748
|
+
const result2 = await resetService(slockHome);
|
|
32749
|
+
if (opts.json) {
|
|
32750
|
+
info(JSON.stringify(result2));
|
|
32751
|
+
return;
|
|
32752
|
+
}
|
|
32753
|
+
info(
|
|
32754
|
+
`Reset service crash history (previous state: ${result2.previousState}; cleared ${result2.clearedCrashCount} entr${result2.clearedCrashCount === 1 ? "y" : "ies"}).`
|
|
32755
|
+
);
|
|
32756
|
+
info("Service state transitioned to `running`. Runners were not touched.");
|
|
32757
|
+
return;
|
|
32758
|
+
}
|
|
32759
|
+
const serverId = await resolveTargetServerId({ server: opts.server });
|
|
32760
|
+
const result = await resetRunner(slockHome, serverId);
|
|
32761
|
+
if (result.status === "not-found") {
|
|
32762
|
+
fail(
|
|
32763
|
+
"RESET_RUNNER_NOT_FOUND",
|
|
32764
|
+
`Runner for server ${serverId} was not found.`
|
|
32765
|
+
);
|
|
32766
|
+
}
|
|
32767
|
+
if (opts.json) {
|
|
32768
|
+
info(JSON.stringify(result));
|
|
32769
|
+
return;
|
|
32770
|
+
}
|
|
32771
|
+
info(
|
|
32772
|
+
`Reset runner crash history for server ${result.serverId} (previous state: ${result.previousState}; cleared ${result.clearedCrashCount} entr${result.clearedCrashCount === 1 ? "y" : "ies"}).`
|
|
32773
|
+
);
|
|
32774
|
+
info("Runner state transitioned to `running`. Process was not respawned.");
|
|
32775
|
+
}
|
|
32776
|
+
|
|
32000
32777
|
// src/concurrency.ts
|
|
32001
32778
|
init_esm_shims();
|
|
32002
32779
|
var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
|
|
32003
|
-
import { mkdir as
|
|
32780
|
+
import { mkdir as mkdir11 } from "fs/promises";
|
|
32004
32781
|
import { join as join4 } from "path";
|
|
32005
32782
|
var STALE_LOCK_THRESHOLD_MS = 6e4;
|
|
32006
32783
|
async function withMutationLock(fn) {
|
|
32007
32784
|
const slockHome = resolveSlockHome();
|
|
32008
32785
|
const lockTarget = computerDir(slockHome);
|
|
32009
|
-
await
|
|
32786
|
+
await mkdir11(lockTarget, { recursive: true });
|
|
32010
32787
|
const lockfilePath = join4(lockTarget, ".lock");
|
|
32011
32788
|
let release = null;
|
|
32012
32789
|
try {
|
|
@@ -32045,8 +32822,8 @@ async function withMutationLock(fn) {
|
|
|
32045
32822
|
|
|
32046
32823
|
// src/channel.ts
|
|
32047
32824
|
init_esm_shims();
|
|
32048
|
-
import { readFile as
|
|
32049
|
-
import { dirname as
|
|
32825
|
+
import { readFile as readFile12, writeFile as writeFile10, mkdir as mkdir12 } from "fs/promises";
|
|
32826
|
+
import { dirname as dirname11 } from "path";
|
|
32050
32827
|
var DEFAULT_CHANNEL = "latest";
|
|
32051
32828
|
var SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
32052
32829
|
function parseChannel(raw) {
|
|
@@ -32061,7 +32838,7 @@ function parseChannel(raw) {
|
|
|
32061
32838
|
}
|
|
32062
32839
|
async function readChannel(slockHome) {
|
|
32063
32840
|
try {
|
|
32064
|
-
const raw = await
|
|
32841
|
+
const raw = await readFile12(channelPath(slockHome), "utf8");
|
|
32065
32842
|
const parsed = parseChannel(raw);
|
|
32066
32843
|
if (parsed !== null) return parsed;
|
|
32067
32844
|
} catch {
|
|
@@ -32070,8 +32847,8 @@ async function readChannel(slockHome) {
|
|
|
32070
32847
|
}
|
|
32071
32848
|
async function writeChannel(slockHome, channel2) {
|
|
32072
32849
|
const p = channelPath(slockHome);
|
|
32073
|
-
await
|
|
32074
|
-
await
|
|
32850
|
+
await mkdir12(dirname11(p), { recursive: true });
|
|
32851
|
+
await writeFile10(p, `${channel2}
|
|
32075
32852
|
`, { mode: 384 });
|
|
32076
32853
|
}
|
|
32077
32854
|
async function runChannelShow(slockHome) {
|
|
@@ -32094,20 +32871,20 @@ async function runChannelSet(slockHome, raw) {
|
|
|
32094
32871
|
|
|
32095
32872
|
// src/upgradeCli.ts
|
|
32096
32873
|
init_esm_shims();
|
|
32097
|
-
import { readFile as
|
|
32874
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
32098
32875
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
32099
|
-
import { dirname as
|
|
32876
|
+
import { dirname as dirname12, join as join7 } from "path";
|
|
32100
32877
|
|
|
32101
32878
|
// src/upgrade.ts
|
|
32102
32879
|
init_esm_shims();
|
|
32103
32880
|
import { spawn as spawn4 } from "child_process";
|
|
32104
|
-
import { mkdir as
|
|
32881
|
+
import { mkdir as mkdir13, readFile as readFile14, writeFile as writeFile11, rm as rm3, rename as rename4 } from "fs/promises";
|
|
32105
32882
|
import { join as join6 } from "path";
|
|
32106
32883
|
import { createHash as createHash3 } from "crypto";
|
|
32107
32884
|
|
|
32108
32885
|
// src/preflightDepDrift.ts
|
|
32109
32886
|
init_esm_shims();
|
|
32110
|
-
import { readFile as
|
|
32887
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
32111
32888
|
import { spawn as spawn3 } from "child_process";
|
|
32112
32889
|
import { createRequire } from "module";
|
|
32113
32890
|
import { join as join5 } from "path";
|
|
@@ -32116,12 +32893,10 @@ var DAEMON_PACKAGE_NAME = "@slock-ai/daemon";
|
|
|
32116
32893
|
async function preflightDepDriftCheck(currentBinaryDir, stagedTarballPath, deps = {}) {
|
|
32117
32894
|
const readTarball = deps.readTarballPackageJson ?? defaultReadTarballPackageJson;
|
|
32118
32895
|
const readCurrent = deps.readCurrentPackageJson ?? defaultReadCurrentPackageJson;
|
|
32119
|
-
const readInstalled = deps.readInstalledDaemonVersion ?? defaultReadInstalledDaemonVersion;
|
|
32120
32896
|
const satisfies = deps.semverSatisfies ?? defaultSemverSatisfies;
|
|
32121
32897
|
const allowUnsafe = deps.allowUnsafeSpec === true;
|
|
32122
32898
|
let targetPkg;
|
|
32123
32899
|
let currentPkg;
|
|
32124
|
-
let installedVersion;
|
|
32125
32900
|
try {
|
|
32126
32901
|
targetPkg = await readTarball(stagedTarballPath);
|
|
32127
32902
|
} catch (e) {
|
|
@@ -32140,15 +32915,6 @@ async function preflightDepDriftCheck(currentBinaryDir, stagedTarballPath, deps
|
|
|
32140
32915
|
detail: `cannot read current binary package.json: ${errMsg(e)}`
|
|
32141
32916
|
};
|
|
32142
32917
|
}
|
|
32143
|
-
try {
|
|
32144
|
-
installedVersion = await readInstalled(currentBinaryDir);
|
|
32145
|
-
} catch (e) {
|
|
32146
|
-
return {
|
|
32147
|
-
ok: false,
|
|
32148
|
-
reason: "read_failed",
|
|
32149
|
-
detail: `cannot read installed ${DAEMON_PACKAGE_NAME} version: ${errMsg(e)}`
|
|
32150
|
-
};
|
|
32151
|
-
}
|
|
32152
32918
|
const targetSpec = targetPkg.dependencies?.[DAEMON_PACKAGE_NAME];
|
|
32153
32919
|
const currentSpec = currentPkg.dependencies?.[DAEMON_PACKAGE_NAME];
|
|
32154
32920
|
if (typeof targetSpec !== "string" || targetSpec.length === 0) {
|
|
@@ -32159,60 +32925,64 @@ async function preflightDepDriftCheck(currentBinaryDir, stagedTarballPath, deps
|
|
|
32159
32925
|
currentSpec
|
|
32160
32926
|
};
|
|
32161
32927
|
}
|
|
32162
|
-
|
|
32928
|
+
const unsafeTarget = isUnsafeSpec(targetSpec) || isUnparseableRange(targetSpec);
|
|
32929
|
+
if (!allowUnsafe && unsafeTarget) {
|
|
32163
32930
|
return {
|
|
32164
32931
|
ok: false,
|
|
32165
|
-
reason: "
|
|
32166
|
-
detail:
|
|
32932
|
+
reason: "unsafe_spec",
|
|
32933
|
+
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.`,
|
|
32934
|
+
currentSpec,
|
|
32167
32935
|
targetSpec
|
|
32168
32936
|
};
|
|
32169
32937
|
}
|
|
32170
|
-
|
|
32938
|
+
void satisfies;
|
|
32939
|
+
return { ok: true, currentSpec, targetSpec };
|
|
32940
|
+
}
|
|
32941
|
+
async function verifyHydratedDaemonDependency(extractedPackageDir, targetSpec, deps = {}) {
|
|
32942
|
+
const readInstalled = deps.readInstalledDaemonVersion ?? defaultReadInstalledDaemonVersion;
|
|
32943
|
+
const satisfies = deps.semverSatisfies ?? defaultSemverSatisfies;
|
|
32944
|
+
const allowUnsafe = deps.allowUnsafeSpec === true;
|
|
32945
|
+
const unsafeTarget = isUnsafeSpec(targetSpec) || isUnparseableRange(targetSpec);
|
|
32946
|
+
if (!allowUnsafe && unsafeTarget) {
|
|
32171
32947
|
return {
|
|
32172
32948
|
ok: false,
|
|
32173
|
-
reason: "
|
|
32174
|
-
detail: `${DAEMON_PACKAGE_NAME} is not
|
|
32175
|
-
currentSpec,
|
|
32949
|
+
reason: "unsafe_spec",
|
|
32950
|
+
detail: `${DAEMON_PACKAGE_NAME} target spec is not a safe semver range (target="${targetSpec}").`,
|
|
32176
32951
|
targetSpec
|
|
32177
32952
|
};
|
|
32178
32953
|
}
|
|
32179
|
-
|
|
32180
|
-
|
|
32181
|
-
|
|
32182
|
-
|
|
32954
|
+
let installedVersion;
|
|
32955
|
+
try {
|
|
32956
|
+
installedVersion = await readInstalled(extractedPackageDir);
|
|
32957
|
+
} catch (e) {
|
|
32183
32958
|
return {
|
|
32184
32959
|
ok: false,
|
|
32185
|
-
reason: "
|
|
32186
|
-
detail:
|
|
32187
|
-
|
|
32188
|
-
targetSpec,
|
|
32189
|
-
installedVersion
|
|
32960
|
+
reason: "read_failed",
|
|
32961
|
+
detail: `cannot read hydrated ${DAEMON_PACKAGE_NAME} version: ${errMsg(e)}`,
|
|
32962
|
+
targetSpec
|
|
32190
32963
|
};
|
|
32191
32964
|
}
|
|
32192
|
-
if (
|
|
32965
|
+
if (installedVersion === null || installedVersion.length === 0) {
|
|
32193
32966
|
return {
|
|
32194
32967
|
ok: false,
|
|
32195
|
-
reason: "
|
|
32196
|
-
detail: `${DAEMON_PACKAGE_NAME}
|
|
32197
|
-
|
|
32198
|
-
targetSpec,
|
|
32199
|
-
installedVersion
|
|
32968
|
+
reason: "read_failed",
|
|
32969
|
+
detail: `${DAEMON_PACKAGE_NAME} is not installed in the hydrated staged package`,
|
|
32970
|
+
targetSpec
|
|
32200
32971
|
};
|
|
32201
32972
|
}
|
|
32202
|
-
if (allowUnsafe &&
|
|
32203
|
-
return { ok: true,
|
|
32973
|
+
if (allowUnsafe && unsafeTarget) {
|
|
32974
|
+
return { ok: true, targetSpec, installedVersion };
|
|
32204
32975
|
}
|
|
32205
32976
|
if (!satisfies(installedVersion, targetSpec)) {
|
|
32206
32977
|
return {
|
|
32207
32978
|
ok: false,
|
|
32208
32979
|
reason: "installed_unsatisfied",
|
|
32209
|
-
detail: `
|
|
32210
|
-
currentSpec,
|
|
32980
|
+
detail: `hydrated ${DAEMON_PACKAGE_NAME}@${installedVersion} does not satisfy target spec "${targetSpec}".`,
|
|
32211
32981
|
targetSpec,
|
|
32212
32982
|
installedVersion
|
|
32213
32983
|
};
|
|
32214
32984
|
}
|
|
32215
|
-
return { ok: true,
|
|
32985
|
+
return { ok: true, targetSpec, installedVersion };
|
|
32216
32986
|
}
|
|
32217
32987
|
function isUnparseableRange(spec) {
|
|
32218
32988
|
const s = spec.trim();
|
|
@@ -32278,7 +33048,7 @@ async function defaultReadTarballPackageJson(tarballPath) {
|
|
|
32278
33048
|
return JSON.parse(raw);
|
|
32279
33049
|
}
|
|
32280
33050
|
async function defaultReadCurrentPackageJson(currentBinaryDir) {
|
|
32281
|
-
const raw = await
|
|
33051
|
+
const raw = await readFile13(join5(currentBinaryDir, "package.json"), "utf8");
|
|
32282
33052
|
return JSON.parse(raw);
|
|
32283
33053
|
}
|
|
32284
33054
|
async function defaultReadInstalledDaemonVersion(currentBinaryDir) {
|
|
@@ -32295,7 +33065,7 @@ async function defaultReadInstalledDaemonVersion(currentBinaryDir) {
|
|
|
32295
33065
|
for (const base of searchPaths) {
|
|
32296
33066
|
const candidate = join5(base, ...subPath, "package.json");
|
|
32297
33067
|
try {
|
|
32298
|
-
const raw = await
|
|
33068
|
+
const raw = await readFile13(candidate, "utf8");
|
|
32299
33069
|
const parsed = JSON.parse(raw);
|
|
32300
33070
|
if (parsed.name !== DAEMON_PACKAGE_NAME) continue;
|
|
32301
33071
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
@@ -32375,9 +33145,9 @@ function satisfiesTilde(ver, base) {
|
|
|
32375
33145
|
// src/upgrade.ts
|
|
32376
33146
|
async function stagePhase(slockHome, version, deps = {}) {
|
|
32377
33147
|
const npmPack = deps.npmPack ?? defaultNpmPack;
|
|
32378
|
-
const fsReadFile = deps.fsReadFile ??
|
|
33148
|
+
const fsReadFile = deps.fsReadFile ?? readFile14;
|
|
32379
33149
|
const stagedPath = upgradeStagingDir(slockHome, version);
|
|
32380
|
-
await
|
|
33150
|
+
await mkdir13(stagedPath, { recursive: true });
|
|
32381
33151
|
const packageRef = `@slock-ai/computer@${version}`;
|
|
32382
33152
|
const result = await npmPack(stagedPath, packageRef);
|
|
32383
33153
|
if (result.exitCode !== 0) {
|
|
@@ -32485,8 +33255,8 @@ async function cleanupStaged(slockHome, version) {
|
|
|
32485
33255
|
async function snapshotPhase(slockHome, snap) {
|
|
32486
33256
|
const path3 = upgradeSnapshotPath(slockHome);
|
|
32487
33257
|
const tmp = `${path3}.tmp`;
|
|
32488
|
-
await
|
|
32489
|
-
await
|
|
33258
|
+
await mkdir13(computerDir(slockHome), { recursive: true });
|
|
33259
|
+
await writeFile11(tmp, JSON.stringify(snap, null, 2), { mode: 384 });
|
|
32490
33260
|
await rename4(tmp, path3);
|
|
32491
33261
|
}
|
|
32492
33262
|
async function clearUpgradeSnapshot(slockHome) {
|
|
@@ -32498,7 +33268,7 @@ async function clearUpgradeSnapshot(slockHome) {
|
|
|
32498
33268
|
}
|
|
32499
33269
|
async function extractTarball(tarballPath, destDir, deps = {}) {
|
|
32500
33270
|
const tarSpawn = deps.tarSpawn ?? defaultTarSpawn;
|
|
32501
|
-
await
|
|
33271
|
+
await mkdir13(destDir, { recursive: true });
|
|
32502
33272
|
const result = await tarSpawn(tarballPath, destDir);
|
|
32503
33273
|
if (result.exitCode !== 0) {
|
|
32504
33274
|
const err = new Error(
|
|
@@ -32603,7 +33373,7 @@ async function rollbackSwap(currentBinaryDir, deps = {}) {
|
|
|
32603
33373
|
}
|
|
32604
33374
|
async function rollbackRestart(slockHome, currentBinaryDir, deps = {}) {
|
|
32605
33375
|
const readSupervisorPid = deps.readSupervisorPid ?? (() => defaultReadSupervisorPid(slockHome));
|
|
32606
|
-
const
|
|
33376
|
+
const isProcessAlive4 = deps.isProcessAlive ?? defaultIsProcessAlive;
|
|
32607
33377
|
const killSupervisor = deps.killSupervisor ?? defaultKillSupervisor;
|
|
32608
33378
|
const forceKillSupervisor = deps.forceKillSupervisor ?? defaultForceKillSupervisor;
|
|
32609
33379
|
const waitForExit = deps.waitForExit ?? defaultWaitForExit;
|
|
@@ -32614,7 +33384,7 @@ async function rollbackRestart(slockHome, currentBinaryDir, deps = {}) {
|
|
|
32614
33384
|
let supervisorStopped = false;
|
|
32615
33385
|
try {
|
|
32616
33386
|
const pid = await readSupervisorPid();
|
|
32617
|
-
if (pid === null || !
|
|
33387
|
+
if (pid === null || !isProcessAlive4(pid)) {
|
|
32618
33388
|
supervisorStopped = true;
|
|
32619
33389
|
} else {
|
|
32620
33390
|
await killSupervisor(pid);
|
|
@@ -32683,9 +33453,9 @@ async function rollbackRestart(slockHome, currentBinaryDir, deps = {}) {
|
|
|
32683
33453
|
reason: e instanceof Error ? e.message : String(e)
|
|
32684
33454
|
};
|
|
32685
33455
|
}
|
|
32686
|
-
const
|
|
33456
|
+
const start2 = Date.now();
|
|
32687
33457
|
let healthy = false;
|
|
32688
|
-
while (Date.now() -
|
|
33458
|
+
while (Date.now() - start2 < healthTimeoutMs) {
|
|
32689
33459
|
if (await healthCheck()) {
|
|
32690
33460
|
healthy = true;
|
|
32691
33461
|
break;
|
|
@@ -32746,10 +33516,10 @@ async function restartPhase(slockHome, deps = {}) {
|
|
|
32746
33516
|
return { ok: false, reason: "spawn_failed" };
|
|
32747
33517
|
}
|
|
32748
33518
|
}
|
|
32749
|
-
const
|
|
32750
|
-
while (Date.now() -
|
|
33519
|
+
const start2 = Date.now();
|
|
33520
|
+
while (Date.now() - start2 < healthTimeoutMs) {
|
|
32751
33521
|
if (await healthCheck()) {
|
|
32752
|
-
return { ok: true, healthAfterMs: Date.now() -
|
|
33522
|
+
return { ok: true, healthAfterMs: Date.now() - start2 };
|
|
32753
33523
|
}
|
|
32754
33524
|
await new Promise((r) => setTimeout(r, healthPollIntervalMs));
|
|
32755
33525
|
}
|
|
@@ -32757,7 +33527,7 @@ async function restartPhase(slockHome, deps = {}) {
|
|
|
32757
33527
|
}
|
|
32758
33528
|
async function defaultReadSupervisorPid(slockHome) {
|
|
32759
33529
|
try {
|
|
32760
|
-
const raw = (await
|
|
33530
|
+
const raw = (await readFile14(supervisorPidPath(slockHome), "utf8")).trim();
|
|
32761
33531
|
const pid = Number.parseInt(raw, 10);
|
|
32762
33532
|
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
32763
33533
|
} catch {
|
|
@@ -32784,8 +33554,8 @@ async function defaultIsDaemonBusy(_slockHome) {
|
|
|
32784
33554
|
return false;
|
|
32785
33555
|
}
|
|
32786
33556
|
async function defaultWaitForExit(pid, timeoutMs) {
|
|
32787
|
-
const
|
|
32788
|
-
while (Date.now() -
|
|
33557
|
+
const start2 = Date.now();
|
|
33558
|
+
while (Date.now() - start2 < timeoutMs) {
|
|
32789
33559
|
try {
|
|
32790
33560
|
process.kill(pid, 0);
|
|
32791
33561
|
} catch (err) {
|
|
@@ -32876,6 +33646,18 @@ async function runUpgrade(slockHome, opts) {
|
|
|
32876
33646
|
preflight
|
|
32877
33647
|
};
|
|
32878
33648
|
}
|
|
33649
|
+
const targetDaemonSpec = preflight.targetSpec;
|
|
33650
|
+
if (typeof targetDaemonSpec !== "string" || targetDaemonSpec.length === 0) {
|
|
33651
|
+
await cleanupStaged(slockHome, opts.targetVersion);
|
|
33652
|
+
return {
|
|
33653
|
+
ok: false,
|
|
33654
|
+
phase: "preflight",
|
|
33655
|
+
reason: `${preflight.detail ?? preflight.reason ?? "dep_drift"}: missing target ${DAEMON_PACKAGE_NAME} spec`,
|
|
33656
|
+
staged,
|
|
33657
|
+
verify,
|
|
33658
|
+
preflight
|
|
33659
|
+
};
|
|
33660
|
+
}
|
|
32879
33661
|
const snap = {
|
|
32880
33662
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
32881
33663
|
fromVersion: opts.fromVersion,
|
|
@@ -32926,6 +33708,24 @@ async function runUpgrade(slockHome, opts) {
|
|
|
32926
33708
|
verify
|
|
32927
33709
|
};
|
|
32928
33710
|
}
|
|
33711
|
+
const hydratedDaemon = await verifyHydratedDaemonDependency(
|
|
33712
|
+
extractedPackageDir,
|
|
33713
|
+
targetDaemonSpec,
|
|
33714
|
+
deps.preflight ?? {}
|
|
33715
|
+
);
|
|
33716
|
+
if (!hydratedDaemon.ok) {
|
|
33717
|
+
await cleanupStaged(slockHome, opts.targetVersion);
|
|
33718
|
+
await clearUpgradeSnapshot(slockHome);
|
|
33719
|
+
return {
|
|
33720
|
+
ok: false,
|
|
33721
|
+
phase: "hydrate",
|
|
33722
|
+
reason: `staged dependency tree verification failed: ${hydratedDaemon.detail ?? hydratedDaemon.reason ?? "hydrated dependency verification failed"}`,
|
|
33723
|
+
staged,
|
|
33724
|
+
verify,
|
|
33725
|
+
preflight,
|
|
33726
|
+
hydratedDaemon
|
|
33727
|
+
};
|
|
33728
|
+
}
|
|
32929
33729
|
let swap;
|
|
32930
33730
|
try {
|
|
32931
33731
|
swap = await swapPhase(opts.currentBinaryDir, extractedPackageDir, {
|
|
@@ -33014,25 +33814,25 @@ async function runUpgrade(slockHome, opts) {
|
|
|
33014
33814
|
};
|
|
33015
33815
|
}
|
|
33016
33816
|
await cleanupSuccessPhase(slockHome, opts.targetVersion, swap.prevBinaryDir);
|
|
33017
|
-
return { ok: true, phase: "cleanup", staged, verify, swap, restart, rolling };
|
|
33817
|
+
return { ok: true, phase: "cleanup", staged, verify, preflight, hydratedDaemon, swap, restart, rolling };
|
|
33018
33818
|
}
|
|
33019
33819
|
async function rollingDaemonHealthCheck(slockHome, deps = {}) {
|
|
33020
33820
|
const list = deps.listManagedServerIds ?? listManagedServerIds;
|
|
33021
33821
|
const readDaemonPid = deps.readDaemonPid ?? defaultReadDaemonPid;
|
|
33022
|
-
const
|
|
33822
|
+
const isProcessAlive4 = deps.isProcessAlive ?? defaultIsProcessAlive;
|
|
33023
33823
|
const perDaemonTimeoutMs = deps.perDaemonTimeoutMs ?? 3e4;
|
|
33024
33824
|
const pollIntervalMs = deps.pollIntervalMs ?? 500;
|
|
33025
33825
|
const managed = await list(slockHome);
|
|
33026
33826
|
const daemons = [];
|
|
33027
33827
|
for (const serverId of managed) {
|
|
33028
|
-
const
|
|
33828
|
+
const start2 = Date.now();
|
|
33029
33829
|
let lastReason = "timeout";
|
|
33030
33830
|
let healthy = false;
|
|
33031
|
-
while (Date.now() -
|
|
33831
|
+
while (Date.now() - start2 < perDaemonTimeoutMs) {
|
|
33032
33832
|
const pid = await readDaemonPid(slockHome, serverId);
|
|
33033
33833
|
if (pid === null) {
|
|
33034
33834
|
lastReason = "pidfile_missing";
|
|
33035
|
-
} else if (!
|
|
33835
|
+
} else if (!isProcessAlive4(pid)) {
|
|
33036
33836
|
lastReason = "process_dead";
|
|
33037
33837
|
} else {
|
|
33038
33838
|
healthy = true;
|
|
@@ -33040,7 +33840,7 @@ async function rollingDaemonHealthCheck(slockHome, deps = {}) {
|
|
|
33040
33840
|
}
|
|
33041
33841
|
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
33042
33842
|
}
|
|
33043
|
-
const entry = healthy ? { serverId, ok: true, healthAfterMs: Date.now() -
|
|
33843
|
+
const entry = healthy ? { serverId, ok: true, healthAfterMs: Date.now() - start2 } : { serverId, ok: false, reason: lastReason };
|
|
33044
33844
|
daemons.push(entry);
|
|
33045
33845
|
if (!healthy) {
|
|
33046
33846
|
return { ok: false, daemons };
|
|
@@ -33050,7 +33850,7 @@ async function rollingDaemonHealthCheck(slockHome, deps = {}) {
|
|
|
33050
33850
|
}
|
|
33051
33851
|
async function defaultReadDaemonPid(slockHome, serverId) {
|
|
33052
33852
|
try {
|
|
33053
|
-
const raw = (await
|
|
33853
|
+
const raw = (await readFile14(serverDaemonPidPath(slockHome, serverId), "utf8")).trim();
|
|
33054
33854
|
const pid = Number.parseInt(raw, 10);
|
|
33055
33855
|
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
33056
33856
|
} catch {
|
|
@@ -33078,7 +33878,7 @@ async function locateStagedTarball(stagedPath) {
|
|
|
33078
33878
|
|
|
33079
33879
|
// src/upgradeLog.ts
|
|
33080
33880
|
init_esm_shims();
|
|
33081
|
-
import { chmod as chmod5, mkdir as
|
|
33881
|
+
import { chmod as chmod5, mkdir as mkdir14, open as open2 } from "fs/promises";
|
|
33082
33882
|
var FILE_MODE = 384;
|
|
33083
33883
|
var UPGRADE_ERROR_CODES = [
|
|
33084
33884
|
"UPGRADE_DEPS_CHANGED",
|
|
@@ -33123,7 +33923,7 @@ function assertUpgradeLogEntry(entry) {
|
|
|
33123
33923
|
}
|
|
33124
33924
|
async function appendUpgradeLogEntry(slockHome, entry) {
|
|
33125
33925
|
assertUpgradeLogEntry(entry);
|
|
33126
|
-
await
|
|
33926
|
+
await mkdir14(computerDir(slockHome), { recursive: true });
|
|
33127
33927
|
const path3 = upgradeLogPath(slockHome);
|
|
33128
33928
|
const at = entry.at ?? formatUpgradeLogTimestamp();
|
|
33129
33929
|
const fullEntry = { ...entry, at };
|
|
@@ -33146,7 +33946,7 @@ function isEphemeralNpxContext(binaryDir) {
|
|
|
33146
33946
|
async function readBundledDaemonVersion(binaryDir) {
|
|
33147
33947
|
try {
|
|
33148
33948
|
const pkgPath = join7(binaryDir, "package.json");
|
|
33149
|
-
const raw = await
|
|
33949
|
+
const raw = await readFile15(pkgPath, "utf8");
|
|
33150
33950
|
const parsed = JSON.parse(raw);
|
|
33151
33951
|
const pinned = parsed.dependencies?.["@slock-ai/daemon"];
|
|
33152
33952
|
if (typeof pinned !== "string" || pinned.length === 0) return null;
|
|
@@ -33340,6 +34140,9 @@ function mapFailurePhaseToCode(outcome) {
|
|
|
33340
34140
|
case "extract":
|
|
33341
34141
|
return "UPGRADE_INTEGRITY_FAILED";
|
|
33342
34142
|
case "hydrate":
|
|
34143
|
+
if (outcome.hydratedDaemon?.ok === false) {
|
|
34144
|
+
return "UPGRADE_INTEGRITY_FAILED";
|
|
34145
|
+
}
|
|
33343
34146
|
return "UPGRADE_NETWORK_FAILED";
|
|
33344
34147
|
case "swap":
|
|
33345
34148
|
return "UPGRADE_SWAP_FAILED";
|
|
@@ -33374,12 +34177,12 @@ async function defaultFetchDistTags() {
|
|
|
33374
34177
|
}
|
|
33375
34178
|
function defaultCurrentBinaryDir() {
|
|
33376
34179
|
const here = fileURLToPath3(import.meta.url);
|
|
33377
|
-
return
|
|
34180
|
+
return dirname12(dirname12(here));
|
|
33378
34181
|
}
|
|
33379
34182
|
async function defaultCurrentVersion() {
|
|
33380
34183
|
const pkgPath = join7(defaultCurrentBinaryDir(), "package.json");
|
|
33381
34184
|
try {
|
|
33382
|
-
const raw = await
|
|
34185
|
+
const raw = await readFile15(pkgPath, "utf8");
|
|
33383
34186
|
const parsed = JSON.parse(raw);
|
|
33384
34187
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
33385
34188
|
return parsed.version;
|
|
@@ -33391,7 +34194,7 @@ async function defaultCurrentVersion() {
|
|
|
33391
34194
|
|
|
33392
34195
|
// src/upgradeTestHarness.ts
|
|
33393
34196
|
init_esm_shims();
|
|
33394
|
-
import { mkdir as
|
|
34197
|
+
import { mkdir as mkdir15, readdir as readdir4, stat as stat3, writeFile as writeFile12 } from "fs/promises";
|
|
33395
34198
|
import { join as join8 } from "path";
|
|
33396
34199
|
import { createHash as createHash4 } from "crypto";
|
|
33397
34200
|
var PHASES = /* @__PURE__ */ new Set([
|
|
@@ -33450,7 +34253,7 @@ function buildSimulatedDeps(slockHome, opts) {
|
|
|
33450
34253
|
return { tarballPath: "", exitCode: 1, stderr: "simulated stage failure" };
|
|
33451
34254
|
}
|
|
33452
34255
|
const filename = `slock-ai-computer-${targetVersion}.tgz`;
|
|
33453
|
-
await
|
|
34256
|
+
await writeFile12(join8(cwd, filename), tarballBytes);
|
|
33454
34257
|
return { tarballPath: join8(cwd, filename), exitCode: 0, stderr: "" };
|
|
33455
34258
|
},
|
|
33456
34259
|
fetchAdvertisedHash: async () => {
|
|
@@ -33462,8 +34265,8 @@ function buildSimulatedDeps(slockHome, opts) {
|
|
|
33462
34265
|
if (opts.simulateFail === "extract") {
|
|
33463
34266
|
return { exitCode: 1, stderr: "simulated extract failure" };
|
|
33464
34267
|
}
|
|
33465
|
-
await
|
|
33466
|
-
await
|
|
34268
|
+
await mkdir15(join8(destDir, "package"), { recursive: true });
|
|
34269
|
+
await writeFile12(join8(destDir, "package", "marker.txt"), `NEW@${targetVersion}`);
|
|
33467
34270
|
return { exitCode: 0, stderr: "" };
|
|
33468
34271
|
},
|
|
33469
34272
|
npmInstall: async () => ({ exitCode: 0, stderr: "" }),
|
|
@@ -33524,7 +34327,7 @@ function buildSimulatedDeps(slockHome, opts) {
|
|
|
33524
34327
|
}
|
|
33525
34328
|
async function arrangeSnapshotFailure(slockHome) {
|
|
33526
34329
|
const snapshotPath = join8(slockHome, "computer", "upgrade-snapshot.json");
|
|
33527
|
-
await
|
|
34330
|
+
await mkdir15(snapshotPath, { recursive: true });
|
|
33528
34331
|
}
|
|
33529
34332
|
async function pathInfo(path3) {
|
|
33530
34333
|
try {
|
|
@@ -33579,12 +34382,12 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
|
|
|
33579
34382
|
process.exitCode = 1;
|
|
33580
34383
|
return;
|
|
33581
34384
|
}
|
|
33582
|
-
await
|
|
34385
|
+
await mkdir15(slockHome, { recursive: true });
|
|
33583
34386
|
if (opts.simulateFail === "snapshot") {
|
|
33584
34387
|
await arrangeSnapshotFailure(slockHome);
|
|
33585
34388
|
}
|
|
33586
34389
|
const { opts: upgradeOpts } = buildSimulatedDeps(slockHome, opts);
|
|
33587
|
-
await
|
|
34390
|
+
await mkdir15(upgradeOpts.currentBinaryDir, { recursive: true });
|
|
33588
34391
|
let outcome;
|
|
33589
34392
|
try {
|
|
33590
34393
|
outcome = await runUpgrade(slockHome, upgradeOpts);
|
|
@@ -33622,9 +34425,9 @@ async function runUpgradeTestHarness(slockHome, opts, writer = (s) => process.st
|
|
|
33622
34425
|
|
|
33623
34426
|
// src/upgradeInstallSmoke.ts
|
|
33624
34427
|
init_esm_shims();
|
|
33625
|
-
import { copyFile, mkdir as
|
|
34428
|
+
import { copyFile, mkdir as mkdir16, readFile as readFile16 } from "fs/promises";
|
|
33626
34429
|
import { createHash as createHash5 } from "crypto";
|
|
33627
|
-
import { dirname as
|
|
34430
|
+
import { dirname as dirname13, isAbsolute, join as join9, resolve as pathResolve } from "path";
|
|
33628
34431
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
33629
34432
|
async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
|
|
33630
34433
|
if (typeof opts.packageTarball !== "string" || opts.packageTarball.trim().length === 0) {
|
|
@@ -33633,7 +34436,7 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
|
|
|
33633
34436
|
const tarballPath = isAbsolute(opts.packageTarball) ? opts.packageTarball : pathResolve(opts.packageTarball);
|
|
33634
34437
|
let tarballBytes;
|
|
33635
34438
|
try {
|
|
33636
|
-
tarballBytes = await
|
|
34439
|
+
tarballBytes = await readFile16(tarballPath);
|
|
33637
34440
|
} catch (e) {
|
|
33638
34441
|
const msg = e instanceof Error ? e.message : String(e);
|
|
33639
34442
|
throw new Error(`__upgrade-install-smoke: cannot read --package-tarball ${tarballPath}: ${msg}`);
|
|
@@ -33645,7 +34448,7 @@ async function runUpgradeInstallSmoke(slockHome, opts, deps = {}) {
|
|
|
33645
34448
|
const spawnFreshSupervisor = deps.spawnFreshSupervisor ?? (async (h) => {
|
|
33646
34449
|
await spawnDetachedSupervisor(h);
|
|
33647
34450
|
});
|
|
33648
|
-
await
|
|
34451
|
+
await mkdir16(slockHome, { recursive: true });
|
|
33649
34452
|
const outcome = await runUpgrade(slockHome, {
|
|
33650
34453
|
targetVersion: opts.targetVersion,
|
|
33651
34454
|
fromVersion,
|
|
@@ -33703,12 +34506,12 @@ async function runUpgradeInstallSmokeCli(slockHome, opts, writer = (s) => proces
|
|
|
33703
34506
|
}
|
|
33704
34507
|
function defaultCurrentBinaryDirLocal() {
|
|
33705
34508
|
const here = fileURLToPath4(import.meta.url);
|
|
33706
|
-
return
|
|
34509
|
+
return dirname13(dirname13(here));
|
|
33707
34510
|
}
|
|
33708
34511
|
async function defaultCurrentVersionLocal() {
|
|
33709
34512
|
const pkgPath = join9(defaultCurrentBinaryDirLocal(), "package.json");
|
|
33710
34513
|
try {
|
|
33711
|
-
const raw = await
|
|
34514
|
+
const raw = await readFile16(pkgPath, "utf8");
|
|
33712
34515
|
const parsed = JSON.parse(raw);
|
|
33713
34516
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
33714
34517
|
return parsed.version;
|
|
@@ -33758,17 +34561,13 @@ program2.command("attach").argument("<serverSlug>", "target Slock server slug (c
|
|
|
33758
34561
|
() => runAttach({ serverSlug, serverUrl: opts.serverUrl, name: opts.name, run: opts.run, foreground: opts.foreground })
|
|
33759
34562
|
);
|
|
33760
34563
|
}));
|
|
33761
|
-
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
|
|
34564
|
+
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 supervisor").option("--foreground", "run the supervisor in this terminal instead of the background").option("-y, --yes", "allow non-interactive setup after confirming the planned actions").action(
|
|
33762
34565
|
withCliExit(async (serverSlug, opts) => {
|
|
33763
34566
|
await withMutationLock(
|
|
33764
34567
|
() => runSetup({
|
|
33765
34568
|
serverSlug,
|
|
33766
34569
|
serverUrl: opts.serverUrl,
|
|
33767
34570
|
name: opts.name,
|
|
33768
|
-
adoptLegacy: opts.adoptLegacy,
|
|
33769
|
-
legacyApiKey: opts.legacyApiKey,
|
|
33770
|
-
legacyApiKeyFile: opts.legacyApiKeyFile,
|
|
33771
|
-
legacyApiKeyStdin: opts.legacyApiKeyStdin,
|
|
33772
34571
|
start: opts.start,
|
|
33773
34572
|
foreground: opts.foreground,
|
|
33774
34573
|
yes: opts.yes
|
|
@@ -33776,22 +34575,6 @@ program2.command("setup").argument("<serverSlug>", "target Slock server slug (ca
|
|
|
33776
34575
|
);
|
|
33777
34576
|
})
|
|
33778
34577
|
);
|
|
33779
|
-
program2.command("adopt-legacy").argument("<serverSlug>", "target Slock server slug (the legacy machine's server)").description(
|
|
33780
|
-
"One-time migration: trade a legacy sk_machine_* / sk_daemon_* key for a fresh Computer attachment (sk_computer_*)."
|
|
33781
|
-
).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(
|
|
33782
|
-
withCliExit(async (serverSlug, opts) => {
|
|
33783
|
-
await withMutationLock(
|
|
33784
|
-
() => runAdoptLegacy({
|
|
33785
|
-
serverSlug,
|
|
33786
|
-
serverUrl: opts.serverUrl,
|
|
33787
|
-
name: opts.name,
|
|
33788
|
-
legacyApiKey: opts.legacyApiKey,
|
|
33789
|
-
legacyApiKeyFile: opts.legacyApiKeyFile,
|
|
33790
|
-
legacyApiKeyStdin: opts.legacyApiKeyStdin
|
|
33791
|
-
})
|
|
33792
|
-
);
|
|
33793
|
-
})
|
|
33794
|
-
);
|
|
33795
34578
|
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) => {
|
|
33796
34579
|
await withMutationLock(async () => runDetach(await resolveTargetServerId({ server: serverSlug }), serverSlug));
|
|
33797
34580
|
}));
|
|
@@ -33825,6 +34608,18 @@ program2.command("doctor").argument("[serverSlug]", "optional: scope detail (rec
|
|
|
33825
34608
|
}
|
|
33826
34609
|
)
|
|
33827
34610
|
);
|
|
34611
|
+
program2.command("reset").description("Clear a degraded state and resume the supervisor'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(
|
|
34612
|
+
withCliExit(async (opts) => {
|
|
34613
|
+
await withMutationLock(
|
|
34614
|
+
() => runReset({
|
|
34615
|
+
service: opts.service,
|
|
34616
|
+
runner: opts.runner,
|
|
34617
|
+
server: opts.server,
|
|
34618
|
+
json: opts.json
|
|
34619
|
+
})
|
|
34620
|
+
);
|
|
34621
|
+
})
|
|
34622
|
+
);
|
|
33828
34623
|
program2.command("logs").description("Tail one server's daemon log (or the supervisor 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("--supervisor", "tail the global supervisor log instead of a per-server daemon log").action(withCliExit(async (opts) => {
|
|
33829
34624
|
await runLogs({ lines: opts.lines, server: opts.server ?? null, supervisor: !!opts.supervisor });
|
|
33830
34625
|
}));
|
|
@@ -33850,9 +34645,8 @@ program2.command("upgrade").description(
|
|
|
33850
34645
|
[
|
|
33851
34646
|
"Auto-upgrade this Computer to the latest version in its channel (or --target-version <semver>).",
|
|
33852
34647
|
"",
|
|
33853
|
-
"
|
|
33854
|
-
"
|
|
33855
|
-
"UPGRADE_DEPS_CHANGED and require manual `npm install -g @slock-ai/computer@<version>`."
|
|
34648
|
+
"Stages the target package, hydrates production dependencies, verifies the",
|
|
34649
|
+
"hydrated @slock-ai/daemon version, then swaps the Computer package root."
|
|
33856
34650
|
].join("\n")
|
|
33857
34651
|
).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(
|
|
33858
34652
|
withCliExit(
|
|
@@ -33945,21 +34739,6 @@ program2.command("__upgrade-install-smoke", { hidden: true }).requiredOption("--
|
|
|
33945
34739
|
);
|
|
33946
34740
|
})
|
|
33947
34741
|
);
|
|
33948
|
-
{
|
|
33949
|
-
const argv = process.argv.slice(2);
|
|
33950
|
-
const isAdopt = argv[0] === "adopt-legacy";
|
|
33951
|
-
const isSetup = argv[0] === "setup";
|
|
33952
|
-
const strayLegacyFlag = argv.find(
|
|
33953
|
-
(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"
|
|
33954
|
-
);
|
|
33955
|
-
if (strayLegacyFlag && !isAdopt && !isSetup) {
|
|
33956
|
-
process.stderr.write(
|
|
33957
|
-
`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.
|
|
33958
|
-
`
|
|
33959
|
-
);
|
|
33960
|
-
process.exit(2);
|
|
33961
|
-
}
|
|
33962
|
-
}
|
|
33963
34742
|
program2.parseAsync(process.argv).catch((err) => {
|
|
33964
34743
|
process.stderr.write(`slock-computer: ${err instanceof Error ? err.message : String(err)}
|
|
33965
34744
|
`);
|