dahrk-node 0.1.17 → 0.1.19
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/main.js +188 -25
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -2740,12 +2740,30 @@ function tokenise(cmd) {
|
|
|
2740
2740
|
let quoted = false;
|
|
2741
2741
|
let started = false;
|
|
2742
2742
|
let quote = null;
|
|
2743
|
+
const pendingHeredocs = [];
|
|
2743
2744
|
const push = () => {
|
|
2744
2745
|
if (started) tokens.push({ value: cur, quoted });
|
|
2745
2746
|
cur = "";
|
|
2746
2747
|
quoted = false;
|
|
2747
2748
|
started = false;
|
|
2748
2749
|
};
|
|
2750
|
+
const consumeHeredocs = (s) => {
|
|
2751
|
+
let p = s;
|
|
2752
|
+
for (; ; ) {
|
|
2753
|
+
let lineEnd = p;
|
|
2754
|
+
while (lineEnd < cmd.length && cmd[lineEnd] !== "\n") lineEnd++;
|
|
2755
|
+
const line = cmd.slice(p, lineEnd);
|
|
2756
|
+
const { delim, dash } = pendingHeredocs[0];
|
|
2757
|
+
const terminator = dash ? line.replace(/^\t+/, "") : line;
|
|
2758
|
+
const atEnd = lineEnd >= cmd.length;
|
|
2759
|
+
if (terminator === delim) {
|
|
2760
|
+
pendingHeredocs.shift();
|
|
2761
|
+
if (pendingHeredocs.length === 0) return atEnd ? lineEnd : lineEnd + 1;
|
|
2762
|
+
}
|
|
2763
|
+
if (atEnd) return -1;
|
|
2764
|
+
p = lineEnd + 1;
|
|
2765
|
+
}
|
|
2766
|
+
};
|
|
2749
2767
|
for (let i = 0; i < cmd.length; i++) {
|
|
2750
2768
|
const c = cmd[i];
|
|
2751
2769
|
if (quote) {
|
|
@@ -2793,6 +2811,13 @@ function tokenise(cmd) {
|
|
|
2793
2811
|
i = j;
|
|
2794
2812
|
continue;
|
|
2795
2813
|
}
|
|
2814
|
+
if (c === "\n" && pendingHeredocs.length > 0) {
|
|
2815
|
+
push();
|
|
2816
|
+
const resume = consumeHeredocs(i + 1);
|
|
2817
|
+
if (resume < 0) return null;
|
|
2818
|
+
i = resume - 1;
|
|
2819
|
+
continue;
|
|
2820
|
+
}
|
|
2796
2821
|
if (/\s/.test(c)) {
|
|
2797
2822
|
push();
|
|
2798
2823
|
continue;
|
|
@@ -2805,6 +2830,38 @@ function tokenise(cmd) {
|
|
|
2805
2830
|
i += 2;
|
|
2806
2831
|
continue;
|
|
2807
2832
|
}
|
|
2833
|
+
if (two === "<<" && cmd[i + 2] !== "<") {
|
|
2834
|
+
push();
|
|
2835
|
+
let k = i + 2;
|
|
2836
|
+
const dash = cmd[k] === "-";
|
|
2837
|
+
if (dash) k++;
|
|
2838
|
+
while (k < cmd.length && (cmd[k] === " " || cmd[k] === " ")) k++;
|
|
2839
|
+
let delim = "";
|
|
2840
|
+
let dq = null;
|
|
2841
|
+
for (; k < cmd.length; k++) {
|
|
2842
|
+
const d = cmd[k];
|
|
2843
|
+
if (dq) {
|
|
2844
|
+
if (d === dq) dq = null;
|
|
2845
|
+
else delim += d;
|
|
2846
|
+
continue;
|
|
2847
|
+
}
|
|
2848
|
+
if (d === '"' || d === "'") {
|
|
2849
|
+
dq = d;
|
|
2850
|
+
continue;
|
|
2851
|
+
}
|
|
2852
|
+
if (d === "\\" && k + 1 < cmd.length) {
|
|
2853
|
+
delim += cmd[++k];
|
|
2854
|
+
continue;
|
|
2855
|
+
}
|
|
2856
|
+
if (/[\s;&|<>]/.test(d)) break;
|
|
2857
|
+
delim += d;
|
|
2858
|
+
}
|
|
2859
|
+
if (dq) return null;
|
|
2860
|
+
if (!delim) return null;
|
|
2861
|
+
pendingHeredocs.push({ delim, dash });
|
|
2862
|
+
i = k - 1;
|
|
2863
|
+
continue;
|
|
2864
|
+
}
|
|
2808
2865
|
if (["&&", "||", ">>", "2>", "&>"].includes(two)) {
|
|
2809
2866
|
push();
|
|
2810
2867
|
tokens.push({ value: two, quoted: false, operator: two });
|
|
@@ -2820,6 +2877,7 @@ function tokenise(cmd) {
|
|
|
2820
2877
|
started = true;
|
|
2821
2878
|
}
|
|
2822
2879
|
if (quote) return null;
|
|
2880
|
+
if (pendingHeredocs.length > 0) return null;
|
|
2823
2881
|
push();
|
|
2824
2882
|
return { tokens, subshells };
|
|
2825
2883
|
}
|
|
@@ -2949,7 +3007,7 @@ function scanCommand(cmd, roots, cwd = roots.cwd) {
|
|
|
2949
3007
|
}
|
|
2950
3008
|
if (out2.kind !== "ok") return out2;
|
|
2951
3009
|
}
|
|
2952
|
-
return { kind: "ok" };
|
|
3010
|
+
return { kind: "ok", cwd: cwdNow };
|
|
2953
3011
|
}
|
|
2954
3012
|
|
|
2955
3013
|
// ../../packages/edge/src/builtins.ts
|
|
@@ -3028,6 +3086,7 @@ function pathsIn(input) {
|
|
|
3028
3086
|
return out2;
|
|
3029
3087
|
}
|
|
3030
3088
|
function fsConfineRule(roots) {
|
|
3089
|
+
let sessionCwd = roots.cwd;
|
|
3031
3090
|
return {
|
|
3032
3091
|
name: "fs_confine",
|
|
3033
3092
|
evaluate(event) {
|
|
@@ -3038,8 +3097,12 @@ function fsConfineRule(roots) {
|
|
|
3038
3097
|
reason: `${need2 === "write" ? "write to" : "read of"} "${path}" is outside the run's worktree`
|
|
3039
3098
|
});
|
|
3040
3099
|
if (SHELL_TOOLS.has(event.tool)) {
|
|
3041
|
-
const result = scanCommand(commandOf(event.input), roots);
|
|
3100
|
+
const result = scanCommand(commandOf(event.input), roots, sessionCwd);
|
|
3042
3101
|
if (result.kind === "escape") return deny(result.path, result.need);
|
|
3102
|
+
if (result.kind === "ok") {
|
|
3103
|
+
if (result.cwd) sessionCwd = result.cwd;
|
|
3104
|
+
return null;
|
|
3105
|
+
}
|
|
3043
3106
|
if (result.kind === "unparseable") {
|
|
3044
3107
|
return {
|
|
3045
3108
|
verdict: "deny",
|
|
@@ -3884,6 +3947,9 @@ function createStageRunner(deps) {
|
|
|
3884
3947
|
|
|
3885
3948
|
// ../../packages/edge/src/ws-client.ts
|
|
3886
3949
|
var ENROLMENT_REJECTED_EXIT_CODE = 78;
|
|
3950
|
+
var RECONNECT_BASE_MS = 500;
|
|
3951
|
+
var RECONNECT_MAX_MS = 3e4;
|
|
3952
|
+
var PARK_POLL_MS = 6e4;
|
|
3887
3953
|
function classifyError(e) {
|
|
3888
3954
|
const msg = e instanceof Error ? e.message : String(e);
|
|
3889
3955
|
if (/\bgit\b|worktree|clone|fetch|branch|checkout|remote/i.test(msg)) return "git";
|
|
@@ -3919,7 +3985,11 @@ async function startEdgeNode(opts) {
|
|
|
3919
3985
|
let lastPongAt = 0;
|
|
3920
3986
|
let shuttingDown = false;
|
|
3921
3987
|
let connectCount = 0;
|
|
3988
|
+
let enrolToken = opts.enrolToken;
|
|
3989
|
+
let reconnectAttempts = 0;
|
|
3990
|
+
let parkTimer;
|
|
3922
3991
|
let onFatal;
|
|
3992
|
+
let onParked;
|
|
3923
3993
|
const send = (msg) => {
|
|
3924
3994
|
if (ws && ws.readyState === WebSocket.OPEN) ws.send(encode(msg));
|
|
3925
3995
|
};
|
|
@@ -4042,7 +4112,7 @@ async function startEdgeNode(opts) {
|
|
|
4042
4112
|
const sendHello = () => {
|
|
4043
4113
|
send({
|
|
4044
4114
|
type: "hello",
|
|
4045
|
-
enrolToken:
|
|
4115
|
+
enrolToken: enrolToken ?? "",
|
|
4046
4116
|
detectedRuntimes: currentRuntimes,
|
|
4047
4117
|
servesRepoIds: opts.servesRepoIds ?? [],
|
|
4048
4118
|
...opts.credentialModeExplicit && opts.credentialMode ? { credentialMode: opts.credentialMode } : {},
|
|
@@ -4102,6 +4172,7 @@ async function startEdgeNode(opts) {
|
|
|
4102
4172
|
const msg = decode(raw);
|
|
4103
4173
|
if (msg.type === "welcome") {
|
|
4104
4174
|
stageDeps.tenantId = msg.tenantId;
|
|
4175
|
+
reconnectAttempts = 0;
|
|
4105
4176
|
if (opts.retention === void 0 && msg.retention) stageDeps.retention = msg.retention;
|
|
4106
4177
|
if (opts.heartbeatMs === void 0 && msg.heartbeatMs > 0 && ws) {
|
|
4107
4178
|
startHeartbeat(ws, msg.heartbeatMs);
|
|
@@ -4286,19 +4357,59 @@ async function startEdgeNode(opts) {
|
|
|
4286
4357
|
if (heartbeat) clearInterval(heartbeat);
|
|
4287
4358
|
heartbeat = void 0;
|
|
4288
4359
|
if (isEnrolmentRejection(code)) {
|
|
4289
|
-
shuttingDown = true;
|
|
4290
4360
|
const detail = reason.toString() || "enrolment rejected";
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4361
|
+
const refresh = opts.refreshEnrolToken;
|
|
4362
|
+
log.error({ closeCode: code, detail, fatal: !refresh }, `EDGE_REJECTED:${code} ${detail}`);
|
|
4363
|
+
if (!refresh) {
|
|
4364
|
+
shuttingDown = true;
|
|
4365
|
+
process.exitCode = ENROLMENT_REJECTED_EXIT_CODE;
|
|
4366
|
+
onFatal?.(new Error(`hub rejected edge enrolment (${code}): ${detail}`));
|
|
4367
|
+
return;
|
|
4368
|
+
}
|
|
4369
|
+
park(refresh);
|
|
4294
4370
|
return;
|
|
4295
4371
|
}
|
|
4296
4372
|
if (!shuttingDown) {
|
|
4297
|
-
|
|
4298
|
-
|
|
4373
|
+
const delay = reconnectDelay();
|
|
4374
|
+
log.warn(
|
|
4375
|
+
{ closeCode: code, reason: reason.toString(), connectCount, delayMs: delay, attempt: reconnectAttempts },
|
|
4376
|
+
`EDGE_DISCONNECTED:${code} reconnecting in ${delay}ms`
|
|
4377
|
+
);
|
|
4378
|
+
reconnectTimer = setTimeout(connect, delay);
|
|
4299
4379
|
}
|
|
4300
4380
|
});
|
|
4301
4381
|
};
|
|
4382
|
+
const reconnectDelay = () => {
|
|
4383
|
+
const ceiling = Math.min(RECONNECT_BASE_MS * 2 ** reconnectAttempts, RECONNECT_MAX_MS);
|
|
4384
|
+
reconnectAttempts++;
|
|
4385
|
+
return Math.round(ceiling / 2 + Math.random() * (ceiling / 2));
|
|
4386
|
+
};
|
|
4387
|
+
const park = (refresh) => {
|
|
4388
|
+
const rejected = enrolToken;
|
|
4389
|
+
if (parkTimer) return;
|
|
4390
|
+
const pollMs = opts.parkPollMs ?? PARK_POLL_MS;
|
|
4391
|
+
log.error(
|
|
4392
|
+
{ pollMs },
|
|
4393
|
+
"EDGE_PARKED:enrolment rejected - the node is up but serving nothing. Re-enrol with `dahrk start --token <token>` (get one at https://app.dahrk.ai); it will reconnect on its own."
|
|
4394
|
+
);
|
|
4395
|
+
parkTimer = setInterval(() => {
|
|
4396
|
+
let fresh;
|
|
4397
|
+
try {
|
|
4398
|
+
fresh = refresh();
|
|
4399
|
+
} catch (e) {
|
|
4400
|
+
log.warn({ err: e }, `EDGE_PARK_REFRESH_FAILED ${e.message}`);
|
|
4401
|
+
return;
|
|
4402
|
+
}
|
|
4403
|
+
if (!fresh || fresh === rejected) return;
|
|
4404
|
+
if (parkTimer) clearInterval(parkTimer);
|
|
4405
|
+
parkTimer = void 0;
|
|
4406
|
+
enrolToken = fresh;
|
|
4407
|
+
reconnectAttempts = 0;
|
|
4408
|
+
log.info({}, "EDGE_UNPARKED:a new enrolment token was found, reconnecting");
|
|
4409
|
+
connect();
|
|
4410
|
+
}, pollMs);
|
|
4411
|
+
onParked?.();
|
|
4412
|
+
};
|
|
4302
4413
|
opts.signal?.addEventListener(
|
|
4303
4414
|
"abort",
|
|
4304
4415
|
() => {
|
|
@@ -4309,6 +4420,8 @@ async function startEdgeNode(opts) {
|
|
|
4309
4420
|
reconnectTimer = void 0;
|
|
4310
4421
|
if (reprobeTimer) clearInterval(reprobeTimer);
|
|
4311
4422
|
reprobeTimer = void 0;
|
|
4423
|
+
if (parkTimer) clearInterval(parkTimer);
|
|
4424
|
+
parkTimer = void 0;
|
|
4312
4425
|
ws?.close(1e3, "shutting down");
|
|
4313
4426
|
},
|
|
4314
4427
|
{ once: true }
|
|
@@ -4325,6 +4438,10 @@ async function startEdgeNode(opts) {
|
|
|
4325
4438
|
clearInterval(t);
|
|
4326
4439
|
reject(err);
|
|
4327
4440
|
};
|
|
4441
|
+
onParked = () => {
|
|
4442
|
+
clearInterval(t);
|
|
4443
|
+
resolve3();
|
|
4444
|
+
};
|
|
4328
4445
|
});
|
|
4329
4446
|
}
|
|
4330
4447
|
|
|
@@ -5737,6 +5854,7 @@ function persistEnrolment(env, enrolment) {
|
|
|
5737
5854
|
});
|
|
5738
5855
|
}
|
|
5739
5856
|
function resolveEnrolToken(env, opts = {}) {
|
|
5857
|
+
if (opts.supervised && !opts.ephemeral) return readPersistedToken(env) ?? env.DAHRK_ENROL_TOKEN;
|
|
5740
5858
|
if (env.DAHRK_ENROL_TOKEN) return env.DAHRK_ENROL_TOKEN;
|
|
5741
5859
|
if (opts.ephemeral) return void 0;
|
|
5742
5860
|
return readPersistedToken(env);
|
|
@@ -5756,7 +5874,6 @@ function serviceArgv(inputs) {
|
|
|
5756
5874
|
}
|
|
5757
5875
|
function serviceEnv(inputs) {
|
|
5758
5876
|
return {
|
|
5759
|
-
DAHRK_ENROL_TOKEN: inputs.token,
|
|
5760
5877
|
DAHRK_SUPERVISED: "1",
|
|
5761
5878
|
...inputs.hubUrl ? { DAHRK_HUB_URL: inputs.hubUrl } : {},
|
|
5762
5879
|
...inputs.name ? { DAHRK_NODE_NAME: inputs.name } : {},
|
|
@@ -5928,7 +6045,6 @@ async function runServiceInstall(inputs, deps = {}) {
|
|
|
5928
6045
|
manager,
|
|
5929
6046
|
nodeBin: d.nodeBin,
|
|
5930
6047
|
scriptPath: d.scriptPath,
|
|
5931
|
-
token: inputs.token,
|
|
5932
6048
|
...inputs.name ? { name: inputs.name } : {},
|
|
5933
6049
|
...inputs.hubUrl ? { hubUrl: inputs.hubUrl } : {},
|
|
5934
6050
|
...d.pathEnv ? { pathEnv: d.pathEnv } : {},
|
|
@@ -5996,21 +6112,19 @@ async function runNodeStart(inputs, deps = {}, opts = {}) {
|
|
|
5996
6112
|
manager,
|
|
5997
6113
|
nodeBin: d.nodeBin,
|
|
5998
6114
|
scriptPath: d.scriptPath,
|
|
5999
|
-
token: inputs.token ?? "-",
|
|
6000
6115
|
...inputs.name ? { name: inputs.name } : {},
|
|
6001
6116
|
...inputs.hubUrl ? { hubUrl: inputs.hubUrl } : {},
|
|
6002
6117
|
...d.pathEnv ? { pathEnv: d.pathEnv } : {},
|
|
6003
6118
|
homeDir: d.homeDir,
|
|
6004
6119
|
logDir: d.logDir
|
|
6005
6120
|
});
|
|
6006
|
-
const
|
|
6007
|
-
const current = canRender && unitExists && unitIsCurrent(plan, d.readFile(filePath));
|
|
6121
|
+
const current = unitExists && unitIsCurrent(plan, d.readFile(filePath));
|
|
6008
6122
|
if (!opts.alwaysLoad) {
|
|
6009
6123
|
const probe2 = unitExists ? d.capture(statusCommand(manager)) : { code: 1, stdout: "" };
|
|
6010
6124
|
const status = parseServiceStatus(manager, unitExists, probe2);
|
|
6011
6125
|
if (current && status.running) return { kind: "running", code: 0, already: true };
|
|
6012
6126
|
}
|
|
6013
|
-
if (
|
|
6127
|
+
if (!current) {
|
|
6014
6128
|
try {
|
|
6015
6129
|
d.mkdirp(d.logDir);
|
|
6016
6130
|
d.mkdirp(dirOf(plan.filePath));
|
|
@@ -6040,7 +6154,6 @@ async function runNodeRestart(inputs, deps = {}) {
|
|
|
6040
6154
|
manager,
|
|
6041
6155
|
nodeBin: d.nodeBin,
|
|
6042
6156
|
scriptPath: d.scriptPath,
|
|
6043
|
-
token: "-",
|
|
6044
6157
|
homeDir: d.homeDir,
|
|
6045
6158
|
logDir: d.logDir
|
|
6046
6159
|
});
|
|
@@ -6082,7 +6195,6 @@ async function runNodeStop(inputs = {}, deps = {}) {
|
|
|
6082
6195
|
manager,
|
|
6083
6196
|
nodeBin: d.nodeBin,
|
|
6084
6197
|
scriptPath: d.scriptPath,
|
|
6085
|
-
token: "-",
|
|
6086
6198
|
homeDir: d.homeDir,
|
|
6087
6199
|
logDir: d.logDir
|
|
6088
6200
|
});
|
|
@@ -6119,7 +6231,6 @@ async function runServiceUninstall(deps = {}) {
|
|
|
6119
6231
|
manager,
|
|
6120
6232
|
nodeBin: d.nodeBin,
|
|
6121
6233
|
scriptPath: d.scriptPath,
|
|
6122
|
-
token: "-",
|
|
6123
6234
|
homeDir: d.homeDir,
|
|
6124
6235
|
logDir: d.logDir
|
|
6125
6236
|
});
|
|
@@ -6183,9 +6294,9 @@ var defaultDeps3 = () => ({
|
|
|
6183
6294
|
// npm-global bins) that a supervisor's minimal PATH would otherwise hide.
|
|
6184
6295
|
pathEnv: process.env.PATH,
|
|
6185
6296
|
mkdirp: (dir) => void mkdirSync12(dir, { recursive: true }),
|
|
6186
|
-
// The unit
|
|
6187
|
-
//
|
|
6188
|
-
// too - re-installing over a unit an older client left at 0644 must tighten it
|
|
6297
|
+
// The unit holds no secret any more, but it stays owner-only: it names paths on this host, and there is
|
|
6298
|
+
// no reason for it to be world-readable. `writeFileSync`'s `mode` applies only when it CREATES the file,
|
|
6299
|
+
// so chmod explicitly too - re-installing over a unit an older client left at 0644 must tighten it.
|
|
6189
6300
|
writeFile: (path, content) => {
|
|
6190
6301
|
writeFileSync11(path, content, { mode: UNIT_FILE_MODE });
|
|
6191
6302
|
chmodSync3(path, UNIT_FILE_MODE);
|
|
@@ -6527,6 +6638,12 @@ function renderStatus(f) {
|
|
|
6527
6638
|
lines.push(kv("Work", jobLine(first, f.now)));
|
|
6528
6639
|
for (const j of rest) lines.push(kv("", jobLine(j, f.now)));
|
|
6529
6640
|
}
|
|
6641
|
+
if (isEnrolmentBlocked(f.connection)) {
|
|
6642
|
+
lines.push("");
|
|
6643
|
+
lines.push(verdict("fail", "The hub rejected this node's enrolment token; it is serving no Jobs."));
|
|
6644
|
+
lines.push("");
|
|
6645
|
+
lines.push(hint("Re-enrol with `dahrk start --token <token>`. Get one at https://app.dahrk.ai."));
|
|
6646
|
+
}
|
|
6530
6647
|
lines.push(...presenceHints(f).flatMap((h) => ["", h]));
|
|
6531
6648
|
lines.push("", dim(` State file: ${f.stateFile}`));
|
|
6532
6649
|
return lines;
|
|
@@ -6549,7 +6666,7 @@ function jobLine(j, now) {
|
|
|
6549
6666
|
return `${where} ${dim(humanDuration(now - j.startedAt))}`;
|
|
6550
6667
|
}
|
|
6551
6668
|
function isUnhealthy(f) {
|
|
6552
|
-
return f.presence.kind === "crashed";
|
|
6669
|
+
return f.presence.kind === "crashed" || isEnrolmentBlocked(f.connection);
|
|
6553
6670
|
}
|
|
6554
6671
|
function resolvePresence(service, lockedPid, desired) {
|
|
6555
6672
|
if (service?.running) return { kind: "running", ...service.pid ? { pid: service.pid } : {} };
|
|
@@ -6592,10 +6709,21 @@ async function gatherFacts(inputs, deps) {
|
|
|
6592
6709
|
}
|
|
6593
6710
|
var CONNECTION_MARKERS = [
|
|
6594
6711
|
{ prefix: "EDGE_WELCOMED", event: "welcomed", detail: false },
|
|
6712
|
+
// The two markers whose absence here was itself the bug. `EDGE_CONNECTED` is written when the SOCKET
|
|
6713
|
+
// opens, which is before the hub has looked at our token - so a node whose enrolment was rejected has
|
|
6714
|
+
// `EDGE_CONNECTED` as its last marker, and `status` used to read that and cheerfully report a connected
|
|
6715
|
+
// node while it served nothing. The rejection is the newer marker, and it is the true one.
|
|
6716
|
+
{ prefix: "EDGE_REJECTED", event: "rejected", detail: true },
|
|
6717
|
+
{ prefix: "EDGE_PARKED", event: "parked", detail: false },
|
|
6718
|
+
{ prefix: "EDGE_UNPARKED", event: "reconnecting", detail: false },
|
|
6595
6719
|
{ prefix: "EDGE_CONNECTED", event: "connected", detail: false },
|
|
6596
6720
|
{ prefix: "EDGE_DISCONNECTED", event: "disconnected", detail: true },
|
|
6597
6721
|
{ prefix: "EDGE_STALE", event: "went stale", detail: false }
|
|
6598
6722
|
];
|
|
6723
|
+
var BLOCKED_EVENTS = /* @__PURE__ */ new Set(["rejected", "parked"]);
|
|
6724
|
+
function isEnrolmentBlocked(connection) {
|
|
6725
|
+
return connection !== void 0 && BLOCKED_EVENTS.has(connection.event);
|
|
6726
|
+
}
|
|
6599
6727
|
function lastConnection(records) {
|
|
6600
6728
|
for (let i = records.length - 1; i >= 0; i--) {
|
|
6601
6729
|
const r = records[i];
|
|
@@ -6621,7 +6749,7 @@ async function runStatus(inputs, deps) {
|
|
|
6621
6749
|
}
|
|
6622
6750
|
|
|
6623
6751
|
// src/main.ts
|
|
6624
|
-
var CLIENT_VERSION = "0.1.
|
|
6752
|
+
var CLIENT_VERSION = "0.1.19";
|
|
6625
6753
|
var DEFAULT_HUB_URL = "wss://api.dahrk.ai";
|
|
6626
6754
|
var list = (v) => (v ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
6627
6755
|
var RUNTIMES2 = ["claude-code", "codex", "pi"];
|
|
@@ -6706,6 +6834,8 @@ function wantsForeground(env, flags) {
|
|
|
6706
6834
|
async function start(flags) {
|
|
6707
6835
|
const env = envWithFlags(process.env, flags);
|
|
6708
6836
|
if (wantsForeground(env, flags)) return startForeground(env, flags);
|
|
6837
|
+
const enrolled = await enrolToDisk(env);
|
|
6838
|
+
if (enrolled !== 0) return enrolled;
|
|
6709
6839
|
await offerUpdate(env);
|
|
6710
6840
|
const outcome = await runNodeStart(serviceInputs(env));
|
|
6711
6841
|
if (outcome.kind === "error") return outcome.code;
|
|
@@ -6725,6 +6855,28 @@ var serviceInputs = (env) => ({
|
|
|
6725
6855
|
...env.DAHRK_NODE_NAME ? { name: env.DAHRK_NODE_NAME } : {},
|
|
6726
6856
|
...env.DAHRK_HUB_URL ? { hubUrl: env.DAHRK_HUB_URL } : {}
|
|
6727
6857
|
});
|
|
6858
|
+
async function enrolToDisk(env) {
|
|
6859
|
+
const token = resolveEnrolToken(env);
|
|
6860
|
+
if (!token || token === readPersistedToken(env)) return 0;
|
|
6861
|
+
const hubUrl = env.DAHRK_HUB_URL ?? DEFAULT_HUB_URL;
|
|
6862
|
+
const probe2 = await probeHub({ hubUrl, enrolToken: token, clientVersion: CLIENT_VERSION });
|
|
6863
|
+
if (!probe2.ok && probe2.reason === "rejected") {
|
|
6864
|
+
out("");
|
|
6865
|
+
out(verdict("fail", `The hub rejected that enrolment token: ${probe2.detail}`));
|
|
6866
|
+
out("");
|
|
6867
|
+
out(hint("Get a fresh token at https://app.dahrk.ai, then run `dahrk start --token <token>` again."));
|
|
6868
|
+
return 2;
|
|
6869
|
+
}
|
|
6870
|
+
if (!probe2.ok) {
|
|
6871
|
+
out("");
|
|
6872
|
+
out(verdict("warn", `Could not reach ${hubUrl} to check the token (${probe2.detail}); using it anyway.`));
|
|
6873
|
+
}
|
|
6874
|
+
persistEnrolment(env, {
|
|
6875
|
+
token,
|
|
6876
|
+
...probe2.ok ? { name: probe2.name, tenantId: probe2.tenantId } : {}
|
|
6877
|
+
});
|
|
6878
|
+
return 0;
|
|
6879
|
+
}
|
|
6728
6880
|
async function restart(flags, force) {
|
|
6729
6881
|
const env = envWithFlags(process.env, flags);
|
|
6730
6882
|
out("");
|
|
@@ -6763,7 +6915,10 @@ async function startForeground(env, flags) {
|
|
|
6763
6915
|
clientVersion: CLIENT_VERSION,
|
|
6764
6916
|
...env.DAHRK_CRASH_EXIT === "1" ? { exitOnCrash: true } : {}
|
|
6765
6917
|
});
|
|
6766
|
-
const token = resolveEnrolToken(env, {
|
|
6918
|
+
const token = resolveEnrolToken(env, {
|
|
6919
|
+
ephemeral: flags.ephemeral,
|
|
6920
|
+
supervised: env.DAHRK_SUPERVISED === "1"
|
|
6921
|
+
});
|
|
6767
6922
|
if (token) env.DAHRK_ENROL_TOKEN = token;
|
|
6768
6923
|
const runtimes = await resolveRuntimes(env);
|
|
6769
6924
|
if (!flags.ephemeral) {
|
|
@@ -6802,7 +6957,15 @@ async function startForeground(env, flags) {
|
|
|
6802
6957
|
...flags.ephemeral ? {} : { jobLedger: fileJobLedger(jobLedgerFile(stateDir(env))) },
|
|
6803
6958
|
...persist ? {
|
|
6804
6959
|
onEnrolled: (welcome) => persistEnrolment(env, { token, name: welcome.name, tenantId: welcome.tenantId })
|
|
6805
|
-
} : {}
|
|
6960
|
+
} : {},
|
|
6961
|
+
// How a rejected node heals. Reads `node.json` DIRECTLY rather than going through
|
|
6962
|
+
// `resolveEnrolToken`, and that is the whole point: the disk is where re-enrolment writes, so it is
|
|
6963
|
+
// the only place a *newer* token can appear. (Deliberately narrower than boot-time resolution, which
|
|
6964
|
+
// still lets an explicit `DAHRK_ENROL_TOKEN` win - but a stale env var must never be able to shadow
|
|
6965
|
+
// the fresh token on disk forever, which is precisely how a node used to wedge.)
|
|
6966
|
+
//
|
|
6967
|
+
// Only for a persistent node: an ephemeral one has no disk to heal from and must still fail fast.
|
|
6968
|
+
...flags.ephemeral ? {} : { refreshEnrolToken: () => readPersistedToken(env) }
|
|
6806
6969
|
});
|
|
6807
6970
|
return 0;
|
|
6808
6971
|
}
|