@hasna/machines 0.0.10 → 0.0.11
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/agent/index.js +6 -20
- package/dist/cli/index.js +583 -31
- package/dist/commands/heal-daemon.d.ts +36 -0
- package/dist/commands/heal-daemon.d.ts.map +1 -0
- package/dist/commands/heal.d.ts +122 -0
- package/dist/commands/heal.d.ts.map +1 -0
- package/dist/index.js +11 -29
- package/dist/mcp/index.js +5 -9
- package/package.json +1 -1
package/dist/agent/index.js
CHANGED
|
@@ -5,29 +5,15 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
function __accessProp(key) {
|
|
9
|
-
return this[key];
|
|
10
|
-
}
|
|
11
|
-
var __toESMCache_node;
|
|
12
|
-
var __toESMCache_esm;
|
|
13
8
|
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
-
var canCache = mod != null && typeof mod === "object";
|
|
15
|
-
if (canCache) {
|
|
16
|
-
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
-
var cached = cache.get(mod);
|
|
18
|
-
if (cached)
|
|
19
|
-
return cached;
|
|
20
|
-
}
|
|
21
9
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
10
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
11
|
for (let key of __getOwnPropNames(mod))
|
|
24
12
|
if (!__hasOwnProp.call(to, key))
|
|
25
13
|
__defProp(to, key, {
|
|
26
|
-
get:
|
|
14
|
+
get: () => mod[key],
|
|
27
15
|
enumerable: true
|
|
28
16
|
});
|
|
29
|
-
if (canCache)
|
|
30
|
-
cache.set(mod, to);
|
|
31
17
|
return to;
|
|
32
18
|
};
|
|
33
19
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
@@ -2130,15 +2116,15 @@ var __getProtoOf2 = Object.getPrototypeOf;
|
|
|
2130
2116
|
var __defProp2 = Object.defineProperty;
|
|
2131
2117
|
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
2132
2118
|
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
2133
|
-
function
|
|
2119
|
+
function __accessProp(key) {
|
|
2134
2120
|
return this[key];
|
|
2135
2121
|
}
|
|
2136
|
-
var
|
|
2137
|
-
var
|
|
2122
|
+
var __toESMCache_node;
|
|
2123
|
+
var __toESMCache_esm;
|
|
2138
2124
|
var __toESM2 = (mod, isNodeMode, target) => {
|
|
2139
2125
|
var canCache = mod != null && typeof mod === "object";
|
|
2140
2126
|
if (canCache) {
|
|
2141
|
-
var cache = isNodeMode ?
|
|
2127
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
2142
2128
|
var cached = cache.get(mod);
|
|
2143
2129
|
if (cached)
|
|
2144
2130
|
return cached;
|
|
@@ -2148,7 +2134,7 @@ var __toESM2 = (mod, isNodeMode, target) => {
|
|
|
2148
2134
|
for (let key of __getOwnPropNames2(mod))
|
|
2149
2135
|
if (!__hasOwnProp2.call(to, key))
|
|
2150
2136
|
__defProp2(to, key, {
|
|
2151
|
-
get:
|
|
2137
|
+
get: __accessProp.bind(mod, key),
|
|
2152
2138
|
enumerable: true
|
|
2153
2139
|
});
|
|
2154
2140
|
if (canCache)
|
package/dist/cli/index.js
CHANGED
|
@@ -5,43 +5,25 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
function __accessProp(key) {
|
|
9
|
-
return this[key];
|
|
10
|
-
}
|
|
11
|
-
var __toESMCache_node;
|
|
12
|
-
var __toESMCache_esm;
|
|
13
8
|
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
-
var canCache = mod != null && typeof mod === "object";
|
|
15
|
-
if (canCache) {
|
|
16
|
-
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
-
var cached = cache.get(mod);
|
|
18
|
-
if (cached)
|
|
19
|
-
return cached;
|
|
20
|
-
}
|
|
21
9
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
10
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
11
|
for (let key of __getOwnPropNames(mod))
|
|
24
12
|
if (!__hasOwnProp.call(to, key))
|
|
25
13
|
__defProp(to, key, {
|
|
26
|
-
get:
|
|
14
|
+
get: () => mod[key],
|
|
27
15
|
enumerable: true
|
|
28
16
|
});
|
|
29
|
-
if (canCache)
|
|
30
|
-
cache.set(mod, to);
|
|
31
17
|
return to;
|
|
32
18
|
};
|
|
33
19
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
|
-
var __returnValue = (v) => v;
|
|
35
|
-
function __exportSetter(name, newValue) {
|
|
36
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
37
|
-
}
|
|
38
20
|
var __export = (target, all) => {
|
|
39
21
|
for (var name in all)
|
|
40
22
|
__defProp(target, name, {
|
|
41
23
|
get: all[name],
|
|
42
24
|
enumerable: true,
|
|
43
25
|
configurable: true,
|
|
44
|
-
set:
|
|
26
|
+
set: (newValue) => all[name] = () => newValue
|
|
45
27
|
});
|
|
46
28
|
};
|
|
47
29
|
var __require = import.meta.require;
|
|
@@ -6780,15 +6762,15 @@ var __getProtoOf2 = Object.getPrototypeOf;
|
|
|
6780
6762
|
var __defProp2 = Object.defineProperty;
|
|
6781
6763
|
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
6782
6764
|
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
6783
|
-
function
|
|
6765
|
+
function __accessProp(key) {
|
|
6784
6766
|
return this[key];
|
|
6785
6767
|
}
|
|
6786
|
-
var
|
|
6787
|
-
var
|
|
6768
|
+
var __toESMCache_node;
|
|
6769
|
+
var __toESMCache_esm;
|
|
6788
6770
|
var __toESM2 = (mod, isNodeMode, target) => {
|
|
6789
6771
|
var canCache = mod != null && typeof mod === "object";
|
|
6790
6772
|
if (canCache) {
|
|
6791
|
-
var cache = isNodeMode ?
|
|
6773
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
6792
6774
|
var cached = cache.get(mod);
|
|
6793
6775
|
if (cached)
|
|
6794
6776
|
return cached;
|
|
@@ -6798,7 +6780,7 @@ var __toESM2 = (mod, isNodeMode, target) => {
|
|
|
6798
6780
|
for (let key of __getOwnPropNames2(mod))
|
|
6799
6781
|
if (!__hasOwnProp2.call(to, key))
|
|
6800
6782
|
__defProp2(to, key, {
|
|
6801
|
-
get:
|
|
6783
|
+
get: __accessProp.bind(mod, key),
|
|
6802
6784
|
enumerable: true
|
|
6803
6785
|
});
|
|
6804
6786
|
if (canCache)
|
|
@@ -6806,9 +6788,9 @@ var __toESM2 = (mod, isNodeMode, target) => {
|
|
|
6806
6788
|
return to;
|
|
6807
6789
|
};
|
|
6808
6790
|
var __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
6809
|
-
var
|
|
6810
|
-
function
|
|
6811
|
-
this[name] =
|
|
6791
|
+
var __returnValue = (v) => v;
|
|
6792
|
+
function __exportSetter(name, newValue) {
|
|
6793
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6812
6794
|
}
|
|
6813
6795
|
var __export2 = (target, all) => {
|
|
6814
6796
|
for (var name in all)
|
|
@@ -6816,7 +6798,7 @@ var __export2 = (target, all) => {
|
|
|
6816
6798
|
get: all[name],
|
|
6817
6799
|
enumerable: true,
|
|
6818
6800
|
configurable: true,
|
|
6819
|
-
set:
|
|
6801
|
+
set: __exportSetter.bind(all, name)
|
|
6820
6802
|
});
|
|
6821
6803
|
};
|
|
6822
6804
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -18368,6 +18350,482 @@ async function discoverPeers() {
|
|
|
18368
18350
|
return peers;
|
|
18369
18351
|
}
|
|
18370
18352
|
|
|
18353
|
+
// src/commands/heal.ts
|
|
18354
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
18355
|
+
import { join as join11 } from "path";
|
|
18356
|
+
var DEFAULT_THRESHOLDS = {
|
|
18357
|
+
reconnect: 3,
|
|
18358
|
+
nmRestart: 7,
|
|
18359
|
+
fallback: 12,
|
|
18360
|
+
reboot: 15
|
|
18361
|
+
};
|
|
18362
|
+
var DEFAULT_HEAL_CONFIG = {
|
|
18363
|
+
version: 1,
|
|
18364
|
+
enabled: true,
|
|
18365
|
+
wifiInterface: "",
|
|
18366
|
+
preferredSsid: "",
|
|
18367
|
+
fallbackSsid: "",
|
|
18368
|
+
internetUrl: "https://1.1.1.1",
|
|
18369
|
+
tailscaleAnchors: [],
|
|
18370
|
+
quorumRequired: 2,
|
|
18371
|
+
intervalSec: 60,
|
|
18372
|
+
thresholds: { ...DEFAULT_THRESHOLDS },
|
|
18373
|
+
rebootMinIntervalSec: 1800,
|
|
18374
|
+
nmRestartMinIntervalSec: 1800,
|
|
18375
|
+
reconnectMinIntervalSec: 120,
|
|
18376
|
+
healthyWindowSec: 300,
|
|
18377
|
+
maxFailedBootRecoveries: 2,
|
|
18378
|
+
bootBackoffSec: 21600,
|
|
18379
|
+
fallbackWindowSec: 600,
|
|
18380
|
+
gpuJobGuard: true,
|
|
18381
|
+
allowReboot: true
|
|
18382
|
+
};
|
|
18383
|
+
function defaultHealState() {
|
|
18384
|
+
return {
|
|
18385
|
+
failCount: 0,
|
|
18386
|
+
bootId: "",
|
|
18387
|
+
bootHealthySince: null,
|
|
18388
|
+
lastRebootAttempt: 0,
|
|
18389
|
+
lastNmRestart: 0,
|
|
18390
|
+
lastReconnect: 0,
|
|
18391
|
+
lastFallback: 0,
|
|
18392
|
+
degradedUntil: 0,
|
|
18393
|
+
pendingRebootRecovery: false,
|
|
18394
|
+
failedBootRecoveries: 0,
|
|
18395
|
+
rebootSuppressUntil: 0
|
|
18396
|
+
};
|
|
18397
|
+
}
|
|
18398
|
+
function getHealConfigPath() {
|
|
18399
|
+
return process.env["HASNA_MACHINES_HEAL_CONFIG_PATH"] || join11(getDataDir(), "heal-config.json");
|
|
18400
|
+
}
|
|
18401
|
+
function getHealStatePath() {
|
|
18402
|
+
return process.env["HASNA_MACHINES_HEAL_STATE_PATH"] || join11(getDataDir(), "heal-state.json");
|
|
18403
|
+
}
|
|
18404
|
+
function readHealConfig(path) {
|
|
18405
|
+
const p = path || getHealConfigPath();
|
|
18406
|
+
if (!existsSync9(p))
|
|
18407
|
+
return { ...DEFAULT_HEAL_CONFIG, thresholds: { ...DEFAULT_THRESHOLDS } };
|
|
18408
|
+
const parsed = JSON.parse(readFileSync10(p, "utf8"));
|
|
18409
|
+
return {
|
|
18410
|
+
...DEFAULT_HEAL_CONFIG,
|
|
18411
|
+
...parsed,
|
|
18412
|
+
thresholds: { ...DEFAULT_THRESHOLDS, ...parsed.thresholds || {} },
|
|
18413
|
+
tailscaleAnchors: parsed.tailscaleAnchors ?? []
|
|
18414
|
+
};
|
|
18415
|
+
}
|
|
18416
|
+
function writeHealConfig(config, path) {
|
|
18417
|
+
const p = path || getHealConfigPath();
|
|
18418
|
+
ensureParentDir(p);
|
|
18419
|
+
writeFileSync7(p, `${JSON.stringify(config, null, 2)}
|
|
18420
|
+
`, "utf8");
|
|
18421
|
+
}
|
|
18422
|
+
function readHealState(path) {
|
|
18423
|
+
const p = path || getHealStatePath();
|
|
18424
|
+
if (!existsSync9(p))
|
|
18425
|
+
return defaultHealState();
|
|
18426
|
+
try {
|
|
18427
|
+
return { ...defaultHealState(), ...JSON.parse(readFileSync10(p, "utf8")) };
|
|
18428
|
+
} catch {
|
|
18429
|
+
return defaultHealState();
|
|
18430
|
+
}
|
|
18431
|
+
}
|
|
18432
|
+
function writeHealState(state, path) {
|
|
18433
|
+
const p = path || getHealStatePath();
|
|
18434
|
+
ensureParentDir(p);
|
|
18435
|
+
writeFileSync7(p, `${JSON.stringify(state, null, 2)}
|
|
18436
|
+
`, "utf8");
|
|
18437
|
+
}
|
|
18438
|
+
function evaluateHealth(probe, config, state) {
|
|
18439
|
+
const reasons = [];
|
|
18440
|
+
const inDegraded = state.degradedUntil > 0;
|
|
18441
|
+
const acceptableSsid = probe.associatedSsid === config.preferredSsid || config.fallbackSsid !== "" && inDegraded && probe.associatedSsid === config.fallbackSsid;
|
|
18442
|
+
if (!acceptableSsid)
|
|
18443
|
+
reasons.push(`wrong-ssid:${probe.associatedSsid ?? "none"}`);
|
|
18444
|
+
if (!probe.gatewayReachable)
|
|
18445
|
+
reasons.push("gateway-unreachable");
|
|
18446
|
+
let remoteScore = 0;
|
|
18447
|
+
for (const [anchor, ok] of Object.entries(probe.anchorsReachable)) {
|
|
18448
|
+
if (ok)
|
|
18449
|
+
remoteScore += 1;
|
|
18450
|
+
else
|
|
18451
|
+
reasons.push(`anchor-down:${anchor}`);
|
|
18452
|
+
}
|
|
18453
|
+
if (probe.internetReachable)
|
|
18454
|
+
remoteScore += 1;
|
|
18455
|
+
else
|
|
18456
|
+
reasons.push("internet-down");
|
|
18457
|
+
const localOk = acceptableSsid && probe.gatewayReachable;
|
|
18458
|
+
const quorumOk = remoteScore >= config.quorumRequired;
|
|
18459
|
+
if (!quorumOk)
|
|
18460
|
+
reasons.push(`quorum:${remoteScore}/${config.quorumRequired}`);
|
|
18461
|
+
return { healthy: localOk && quorumOk, remoteScore, reasons };
|
|
18462
|
+
}
|
|
18463
|
+
function decideAction(input) {
|
|
18464
|
+
const { healthy, now, gpuBusy, config, currentBootId } = input;
|
|
18465
|
+
const s = { ...input.state };
|
|
18466
|
+
const t = config.thresholds;
|
|
18467
|
+
if (s.bootId !== currentBootId) {
|
|
18468
|
+
s.bootId = currentBootId;
|
|
18469
|
+
s.bootHealthySince = null;
|
|
18470
|
+
s.failCount = 0;
|
|
18471
|
+
}
|
|
18472
|
+
if (healthy) {
|
|
18473
|
+
s.failCount = 0;
|
|
18474
|
+
if (s.bootHealthySince === null)
|
|
18475
|
+
s.bootHealthySince = now;
|
|
18476
|
+
if (now - s.bootHealthySince >= config.healthyWindowSec) {
|
|
18477
|
+
s.failedBootRecoveries = 0;
|
|
18478
|
+
s.rebootSuppressUntil = 0;
|
|
18479
|
+
s.pendingRebootRecovery = false;
|
|
18480
|
+
}
|
|
18481
|
+
if (s.degradedUntil > 0 && now >= s.degradedUntil) {
|
|
18482
|
+
s.degradedUntil = 0;
|
|
18483
|
+
return { action: "restore_preferred", state: s };
|
|
18484
|
+
}
|
|
18485
|
+
return { action: "none", state: s };
|
|
18486
|
+
}
|
|
18487
|
+
s.failCount += 1;
|
|
18488
|
+
s.bootHealthySince = null;
|
|
18489
|
+
let tier = "none";
|
|
18490
|
+
if (s.failCount >= t.reboot)
|
|
18491
|
+
tier = "reboot";
|
|
18492
|
+
else if (s.failCount >= t.fallback && config.fallbackSsid !== "")
|
|
18493
|
+
tier = "fallback";
|
|
18494
|
+
else if (s.failCount >= t.nmRestart)
|
|
18495
|
+
tier = "nmRestart";
|
|
18496
|
+
else if (s.failCount >= t.reconnect)
|
|
18497
|
+
tier = "reconnect";
|
|
18498
|
+
const tryReconnect = (reason) => {
|
|
18499
|
+
if (now - s.lastReconnect >= config.reconnectMinIntervalSec) {
|
|
18500
|
+
s.lastReconnect = now;
|
|
18501
|
+
return { action: "reconnect_wifi", suppressedReason: reason, state: s };
|
|
18502
|
+
}
|
|
18503
|
+
return { action: "none", suppressedReason: reason, state: s };
|
|
18504
|
+
};
|
|
18505
|
+
switch (tier) {
|
|
18506
|
+
case "reconnect":
|
|
18507
|
+
return tryReconnect();
|
|
18508
|
+
case "nmRestart":
|
|
18509
|
+
if (now - s.lastNmRestart >= config.nmRestartMinIntervalSec) {
|
|
18510
|
+
s.lastNmRestart = now;
|
|
18511
|
+
return { action: "restart_nm", state: s };
|
|
18512
|
+
}
|
|
18513
|
+
return tryReconnect();
|
|
18514
|
+
case "fallback":
|
|
18515
|
+
if (now - s.lastFallback >= config.fallbackWindowSec) {
|
|
18516
|
+
s.lastFallback = now;
|
|
18517
|
+
s.degradedUntil = now + config.fallbackWindowSec;
|
|
18518
|
+
return { action: "fallback_ssid", state: s };
|
|
18519
|
+
}
|
|
18520
|
+
return tryReconnect();
|
|
18521
|
+
case "reboot": {
|
|
18522
|
+
let reason = null;
|
|
18523
|
+
if (!config.allowReboot)
|
|
18524
|
+
reason = "disabled";
|
|
18525
|
+
else if (now < s.rebootSuppressUntil)
|
|
18526
|
+
reason = "loop";
|
|
18527
|
+
else if (config.gpuJobGuard && gpuBusy)
|
|
18528
|
+
reason = "gpu";
|
|
18529
|
+
else if (now - s.lastRebootAttempt < config.rebootMinIntervalSec)
|
|
18530
|
+
reason = "rate";
|
|
18531
|
+
if (reason)
|
|
18532
|
+
return tryReconnect(reason);
|
|
18533
|
+
if (s.pendingRebootRecovery) {
|
|
18534
|
+
s.failedBootRecoveries += 1;
|
|
18535
|
+
if (s.failedBootRecoveries >= config.maxFailedBootRecoveries) {
|
|
18536
|
+
s.rebootSuppressUntil = now + config.bootBackoffSec;
|
|
18537
|
+
return tryReconnect("loop");
|
|
18538
|
+
}
|
|
18539
|
+
}
|
|
18540
|
+
s.lastRebootAttempt = now;
|
|
18541
|
+
s.pendingRebootRecovery = true;
|
|
18542
|
+
return { action: "reboot", state: s };
|
|
18543
|
+
}
|
|
18544
|
+
default:
|
|
18545
|
+
return { action: "none", state: s };
|
|
18546
|
+
}
|
|
18547
|
+
}
|
|
18548
|
+
function sh(cmd, timeoutMs = 8000) {
|
|
18549
|
+
const r = Bun.spawnSync(["bash", "-lc", cmd], { stdout: "pipe", stderr: "pipe", env: process.env, timeout: timeoutMs });
|
|
18550
|
+
return { ok: r.exitCode === 0, out: r.stdout.toString("utf8").trim() };
|
|
18551
|
+
}
|
|
18552
|
+
function getCurrentBootId() {
|
|
18553
|
+
try {
|
|
18554
|
+
return readFileSync10("/proc/sys/kernel/random/boot_id", "utf8").trim();
|
|
18555
|
+
} catch {
|
|
18556
|
+
return "";
|
|
18557
|
+
}
|
|
18558
|
+
}
|
|
18559
|
+
function detectWifiInterface() {
|
|
18560
|
+
const r = sh(`nmcli -t -f DEVICE,TYPE device status 2>/dev/null | awk -F: '$2=="wifi"{print $1; exit}'`);
|
|
18561
|
+
return r.ok ? r.out : "";
|
|
18562
|
+
}
|
|
18563
|
+
function detectGateway() {
|
|
18564
|
+
const r = sh(`ip route 2>/dev/null | awk '/^default/{print $3; exit}'`);
|
|
18565
|
+
return r.ok ? r.out : "";
|
|
18566
|
+
}
|
|
18567
|
+
function getAssociatedSsid() {
|
|
18568
|
+
const r = sh(`iwgetid -r 2>/dev/null || nmcli -t -f active,ssid dev wifi 2>/dev/null | awk -F: '/^yes/{print $2; exit}'`);
|
|
18569
|
+
return r.ok && r.out ? r.out : null;
|
|
18570
|
+
}
|
|
18571
|
+
function pingHost(host) {
|
|
18572
|
+
if (!host)
|
|
18573
|
+
return false;
|
|
18574
|
+
return sh(`ping -c1 -W2 ${host} >/dev/null 2>&1 && echo ok`, 5000).out === "ok";
|
|
18575
|
+
}
|
|
18576
|
+
function internetReachable(url) {
|
|
18577
|
+
return sh(`curl -sf -m5 -o /dev/null ${url} && echo ok`, 8000).out === "ok";
|
|
18578
|
+
}
|
|
18579
|
+
function tailscalePing(host) {
|
|
18580
|
+
return sh(`timeout 8 tailscale ping --until-direct=false ${host} 2>/dev/null | grep -q pong && echo ok`, 1e4).out === "ok";
|
|
18581
|
+
}
|
|
18582
|
+
function gpuBusy() {
|
|
18583
|
+
const r = sh(`command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi --query-compute-apps=pid --format=csv,noheader 2>/dev/null | grep -q . && echo busy`, 6000);
|
|
18584
|
+
return r.out === "busy";
|
|
18585
|
+
}
|
|
18586
|
+
function discoverAnchors() {
|
|
18587
|
+
const r = sh(`tailscale status --json 2>/dev/null`);
|
|
18588
|
+
if (!r.ok)
|
|
18589
|
+
return [];
|
|
18590
|
+
try {
|
|
18591
|
+
const status = JSON.parse(r.out);
|
|
18592
|
+
const anchors = [];
|
|
18593
|
+
for (const peer of Object.values(status.Peer || {})) {
|
|
18594
|
+
const name = peer.HostName || (peer.DNSName || "").split(".")[0];
|
|
18595
|
+
if (name)
|
|
18596
|
+
anchors.push(name);
|
|
18597
|
+
}
|
|
18598
|
+
return anchors;
|
|
18599
|
+
} catch {
|
|
18600
|
+
return [];
|
|
18601
|
+
}
|
|
18602
|
+
}
|
|
18603
|
+
function probeHealth(config) {
|
|
18604
|
+
const gw = config.wifiInterface ? detectGateway() : detectGateway();
|
|
18605
|
+
const anchors = config.tailscaleAnchors.length > 0 ? config.tailscaleAnchors : discoverAnchors().slice(0, 3);
|
|
18606
|
+
const anchorsReachable = {};
|
|
18607
|
+
for (const a of anchors)
|
|
18608
|
+
anchorsReachable[a] = tailscalePing(a);
|
|
18609
|
+
return {
|
|
18610
|
+
associatedSsid: getAssociatedSsid(),
|
|
18611
|
+
gatewayReachable: pingHost(gw),
|
|
18612
|
+
anchorsReachable,
|
|
18613
|
+
internetReachable: internetReachable(config.internetUrl)
|
|
18614
|
+
};
|
|
18615
|
+
}
|
|
18616
|
+
function executeAction(action, config) {
|
|
18617
|
+
const iface = config.wifiInterface || detectWifiInterface();
|
|
18618
|
+
switch (action) {
|
|
18619
|
+
case "reconnect_wifi":
|
|
18620
|
+
sh(`nmcli connection up "${config.preferredSsid}" 2>&1; tailscale up 2>&1 || true`, 30000);
|
|
18621
|
+
return `reconnected wifi to ${config.preferredSsid}`;
|
|
18622
|
+
case "restart_nm":
|
|
18623
|
+
sh(`systemctl restart NetworkManager 2>&1; sleep 5; nmcli connection up "${config.preferredSsid}" 2>&1; tailscale up 2>&1 || true`, 40000);
|
|
18624
|
+
return "restarted NetworkManager";
|
|
18625
|
+
case "fallback_ssid":
|
|
18626
|
+
sh(`nmcli connection modify "${config.fallbackSsid}" connection.autoconnect yes 2>&1; nmcli connection up "${config.fallbackSsid}" 2>&1; tailscale up 2>&1 || true`, 30000);
|
|
18627
|
+
return `switched to degraded fallback ${config.fallbackSsid}`;
|
|
18628
|
+
case "restore_preferred":
|
|
18629
|
+
sh(`nmcli connection modify "${config.fallbackSsid}" connection.autoconnect no 2>&1; nmcli connection up "${config.preferredSsid}" 2>&1; tailscale up 2>&1 || true`, 30000);
|
|
18630
|
+
return `restored preferred ${config.preferredSsid}`;
|
|
18631
|
+
case "reboot":
|
|
18632
|
+
sh(`systemctl reboot 2>&1 || reboot 2>&1`, 1e4);
|
|
18633
|
+
return "reboot issued";
|
|
18634
|
+
default:
|
|
18635
|
+
return "no action";
|
|
18636
|
+
}
|
|
18637
|
+
}
|
|
18638
|
+
|
|
18639
|
+
// src/commands/heal-daemon.ts
|
|
18640
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
|
|
18641
|
+
import { join as join12 } from "path";
|
|
18642
|
+
var DAEMON_PID_PATH2 = join12(getDataDir(), "heal-daemon.pid");
|
|
18643
|
+
var SERVICE_PATH = "/etc/systemd/system/machines-heal.service";
|
|
18644
|
+
var SYSTEM_CONF = "/etc/systemd/system.conf";
|
|
18645
|
+
function log(msg) {
|
|
18646
|
+
console.log(`${new Date().toISOString()} [machines-heal] ${msg}`);
|
|
18647
|
+
}
|
|
18648
|
+
function runHealOnce(config, opts = {}) {
|
|
18649
|
+
const state = readHealState();
|
|
18650
|
+
const probe = probeHealth(config);
|
|
18651
|
+
const health = evaluateHealth(probe, config, state);
|
|
18652
|
+
const busy = config.gpuJobGuard ? gpuBusy() : false;
|
|
18653
|
+
const decision = decideAction({
|
|
18654
|
+
state,
|
|
18655
|
+
healthy: health.healthy,
|
|
18656
|
+
now: Math.floor(Date.now() / 1000),
|
|
18657
|
+
gpuBusy: busy,
|
|
18658
|
+
config,
|
|
18659
|
+
currentBootId: getCurrentBootId()
|
|
18660
|
+
});
|
|
18661
|
+
let executed = "skipped (dry-run)";
|
|
18662
|
+
if (!opts.dryRun) {
|
|
18663
|
+
writeHealState(decision.state);
|
|
18664
|
+
if (decision.action !== "none")
|
|
18665
|
+
executed = executeAction(decision.action, config);
|
|
18666
|
+
else
|
|
18667
|
+
executed = "no action";
|
|
18668
|
+
}
|
|
18669
|
+
const result = {
|
|
18670
|
+
healthy: health.healthy,
|
|
18671
|
+
action: decision.action,
|
|
18672
|
+
suppressedReason: decision.suppressedReason,
|
|
18673
|
+
reasons: health.reasons,
|
|
18674
|
+
remoteScore: health.remoteScore,
|
|
18675
|
+
failCount: decision.state.failCount,
|
|
18676
|
+
executed
|
|
18677
|
+
};
|
|
18678
|
+
const sup = decision.suppressedReason ? ` suppressed=${decision.suppressedReason}` : "";
|
|
18679
|
+
log(health.healthy ? `healthy (quorum ${health.remoteScore}) action=${decision.action} ${executed}` : `UNHEALTHY [${health.reasons.join(",")}] fails=${decision.state.failCount} action=${decision.action}${sup} -> ${executed}`);
|
|
18680
|
+
return result;
|
|
18681
|
+
}
|
|
18682
|
+
function writePid2(pid) {
|
|
18683
|
+
writeFileSync8(DAEMON_PID_PATH2, `${pid}
|
|
18684
|
+
`);
|
|
18685
|
+
}
|
|
18686
|
+
function readPid2() {
|
|
18687
|
+
try {
|
|
18688
|
+
const pid = Number.parseInt(readFileSync11(DAEMON_PID_PATH2, "utf8").trim());
|
|
18689
|
+
return Number.isFinite(pid) ? pid : null;
|
|
18690
|
+
} catch {
|
|
18691
|
+
return null;
|
|
18692
|
+
}
|
|
18693
|
+
}
|
|
18694
|
+
function isProcessRunning2(pid) {
|
|
18695
|
+
try {
|
|
18696
|
+
process.kill(pid, 0);
|
|
18697
|
+
return true;
|
|
18698
|
+
} catch {
|
|
18699
|
+
return false;
|
|
18700
|
+
}
|
|
18701
|
+
}
|
|
18702
|
+
function stopHealDaemon() {
|
|
18703
|
+
const pid = readPid2();
|
|
18704
|
+
if (pid && isProcessRunning2(pid)) {
|
|
18705
|
+
process.kill(pid, "SIGTERM");
|
|
18706
|
+
return { stopped: true, pid };
|
|
18707
|
+
}
|
|
18708
|
+
return { stopped: false, pid };
|
|
18709
|
+
}
|
|
18710
|
+
function startHealDaemon() {
|
|
18711
|
+
const config = readHealConfig();
|
|
18712
|
+
if (!config.preferredSsid) {
|
|
18713
|
+
log("refusing to start: preferredSsid is not configured (run `machines heal config --set ...`)");
|
|
18714
|
+
process.exit(1);
|
|
18715
|
+
}
|
|
18716
|
+
writePid2(process.pid);
|
|
18717
|
+
log(`daemon started (pid ${process.pid}) interval=${config.intervalSec}s preferred=${config.preferredSsid}`);
|
|
18718
|
+
const tick = () => {
|
|
18719
|
+
try {
|
|
18720
|
+
runHealOnce(config);
|
|
18721
|
+
} catch (err) {
|
|
18722
|
+
log(`tick error: ${err.message}`);
|
|
18723
|
+
}
|
|
18724
|
+
};
|
|
18725
|
+
tick();
|
|
18726
|
+
setInterval(tick, Math.max(10, config.intervalSec) * 1000);
|
|
18727
|
+
}
|
|
18728
|
+
function sh2(cmd, timeoutMs = 15000) {
|
|
18729
|
+
const r = Bun.spawnSync(["bash", "-lc", cmd], { stdout: "pipe", stderr: "pipe", env: process.env, timeout: timeoutMs });
|
|
18730
|
+
return { ok: r.exitCode === 0, out: `${r.stdout.toString("utf8")}${r.stderr.toString("utf8")}`.trim() };
|
|
18731
|
+
}
|
|
18732
|
+
function applyDeterminism(config) {
|
|
18733
|
+
const iface = config.wifiInterface || detectWifiInterface();
|
|
18734
|
+
const log2 = [];
|
|
18735
|
+
if (!config.preferredSsid)
|
|
18736
|
+
return ["no preferredSsid configured; skipping determinism"];
|
|
18737
|
+
sh2(`nmcli connection modify "${config.preferredSsid}" connection.autoconnect yes connection.autoconnect-priority 10 802-11-wireless.powersave 2`);
|
|
18738
|
+
log2.push(`pinned ${config.preferredSsid} (autoconnect, priority 10, powersave off)`);
|
|
18739
|
+
const profiles = sh2(`nmcli -t -f NAME,TYPE connection show 2>/dev/null | awk -F: '$2 ~ /wireless/{print $1}'`).out.split(`
|
|
18740
|
+
`).filter(Boolean);
|
|
18741
|
+
for (const p of profiles) {
|
|
18742
|
+
if (p === config.preferredSsid)
|
|
18743
|
+
continue;
|
|
18744
|
+
if (p === config.fallbackSsid) {
|
|
18745
|
+
sh2(`nmcli connection modify "${p}" connection.autoconnect no`);
|
|
18746
|
+
log2.push(`disabled autoconnect on fallback ${p}`);
|
|
18747
|
+
continue;
|
|
18748
|
+
}
|
|
18749
|
+
sh2(`nmcli connection modify "${p}" connection.autoconnect no`);
|
|
18750
|
+
log2.push(`disabled autoconnect on ${p}`);
|
|
18751
|
+
}
|
|
18752
|
+
if (iface) {
|
|
18753
|
+
sh2(`iw dev ${iface} set power_save off 2>/dev/null || true`);
|
|
18754
|
+
log2.push(`power_save off on ${iface}`);
|
|
18755
|
+
}
|
|
18756
|
+
return log2;
|
|
18757
|
+
}
|
|
18758
|
+
function enableHardwareWatchdog() {
|
|
18759
|
+
const log2 = [];
|
|
18760
|
+
if (!existsSync10(SYSTEM_CONF))
|
|
18761
|
+
return ["/etc/systemd/system.conf not found; skipping hardware watchdog"];
|
|
18762
|
+
let conf = readFileSync11(SYSTEM_CONF, "utf8");
|
|
18763
|
+
const set = (key, value) => {
|
|
18764
|
+
const re = new RegExp(`^#?\\s*${key}=.*$`, "m");
|
|
18765
|
+
if (re.test(conf))
|
|
18766
|
+
conf = conf.replace(re, `${key}=${value}`);
|
|
18767
|
+
else
|
|
18768
|
+
conf += `
|
|
18769
|
+
${key}=${value}
|
|
18770
|
+
`;
|
|
18771
|
+
};
|
|
18772
|
+
set("RuntimeWatchdogSec", "20s");
|
|
18773
|
+
set("RebootWatchdogSec", "2min");
|
|
18774
|
+
writeFileSync8(SYSTEM_CONF, conf);
|
|
18775
|
+
sh2("systemctl daemon-reexec");
|
|
18776
|
+
log2.push("hardware watchdog: RuntimeWatchdogSec=20s RebootWatchdogSec=2min");
|
|
18777
|
+
return log2;
|
|
18778
|
+
}
|
|
18779
|
+
function binPath() {
|
|
18780
|
+
const r = sh2("command -v machines");
|
|
18781
|
+
return r.ok && r.out ? r.out.split(`
|
|
18782
|
+
`)[0].trim() : "machines";
|
|
18783
|
+
}
|
|
18784
|
+
function installHealService() {
|
|
18785
|
+
const log2 = [];
|
|
18786
|
+
const exec = binPath();
|
|
18787
|
+
const unit = `[Unit]
|
|
18788
|
+
Description=Hasna machines self-healing network watchdog
|
|
18789
|
+
After=network.target NetworkManager.service tailscaled.service
|
|
18790
|
+
Wants=network.target
|
|
18791
|
+
|
|
18792
|
+
[Service]
|
|
18793
|
+
Type=simple
|
|
18794
|
+
ExecStart=${exec} heal daemon
|
|
18795
|
+
Restart=always
|
|
18796
|
+
RestartSec=10
|
|
18797
|
+
# Persisted state/config live in root's data dir.
|
|
18798
|
+
Environment=HOME=/root
|
|
18799
|
+
|
|
18800
|
+
[Install]
|
|
18801
|
+
WantedBy=multi-user.target
|
|
18802
|
+
`;
|
|
18803
|
+
writeFileSync8(SERVICE_PATH, unit);
|
|
18804
|
+
sh2("systemctl daemon-reload");
|
|
18805
|
+
sh2("systemctl enable --now machines-heal.service");
|
|
18806
|
+
log2.push(`installed + enabled ${SERVICE_PATH} (ExecStart=${exec} heal daemon)`);
|
|
18807
|
+
return log2;
|
|
18808
|
+
}
|
|
18809
|
+
function uninstallHealService() {
|
|
18810
|
+
const log2 = [];
|
|
18811
|
+
sh2("systemctl disable --now machines-heal.service 2>/dev/null || true");
|
|
18812
|
+
if (existsSync10(SERVICE_PATH)) {
|
|
18813
|
+
sh2(`rm -f ${SERVICE_PATH}`);
|
|
18814
|
+
sh2("systemctl daemon-reload");
|
|
18815
|
+
log2.push(`removed ${SERVICE_PATH}`);
|
|
18816
|
+
} else {
|
|
18817
|
+
log2.push("service not installed");
|
|
18818
|
+
}
|
|
18819
|
+
return log2;
|
|
18820
|
+
}
|
|
18821
|
+
function healServiceStatus() {
|
|
18822
|
+
return {
|
|
18823
|
+
installed: existsSync10(SERVICE_PATH),
|
|
18824
|
+
active: sh2("systemctl is-active machines-heal.service").out === "active",
|
|
18825
|
+
enabled: sh2("systemctl is-enabled machines-heal.service 2>/dev/null").out === "enabled"
|
|
18826
|
+
};
|
|
18827
|
+
}
|
|
18828
|
+
|
|
18371
18829
|
// src/cli-utils.ts
|
|
18372
18830
|
function parseIntegerOption(value, label, constraints = {}) {
|
|
18373
18831
|
const parsed = Number.parseInt(value, 10);
|
|
@@ -18399,7 +18857,7 @@ ${items.map((item) => `- ${item}`).join(`
|
|
|
18399
18857
|
|
|
18400
18858
|
// src/cli/index.ts
|
|
18401
18859
|
import { rmSync as rmSync2 } from "fs";
|
|
18402
|
-
import { readFileSync as
|
|
18860
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
18403
18861
|
var program2 = new Command;
|
|
18404
18862
|
function printJsonOrText(data, text, json = false) {
|
|
18405
18863
|
if (json || program2.opts().quiet) {
|
|
@@ -18546,7 +19004,7 @@ manifestCommand.command("add").description("Add or replace a machine in the flee
|
|
|
18546
19004
|
console.error("error: --from-stdin requires piped input");
|
|
18547
19005
|
process.exit(1);
|
|
18548
19006
|
}
|
|
18549
|
-
const input =
|
|
19007
|
+
const input = readFileSync12(0, "utf8");
|
|
18550
19008
|
const machine2 = JSON.parse(input);
|
|
18551
19009
|
console.log(JSON.stringify(manifestAdd(machine2), null, 2));
|
|
18552
19010
|
return;
|
|
@@ -18785,4 +19243,98 @@ program2.command("serve").description("Serve a local fleet dashboard and JSON AP
|
|
|
18785
19243
|
const server = startDashboardServer({ host: info.host, port: info.port });
|
|
18786
19244
|
console.log(source_default.green(`machines dashboard listening on http://${server.hostname}:${server.port}`));
|
|
18787
19245
|
});
|
|
19246
|
+
var healCommand = program2.command("heal").description("Self-healing network watchdog: keeps a Wi-Fi node reachable (SSID pinning + peer-reachability + gated reboot)");
|
|
19247
|
+
function requireRoot() {
|
|
19248
|
+
const uid = process.getuid ? process.getuid() : 1;
|
|
19249
|
+
if (uid !== 0) {
|
|
19250
|
+
console.error(source_default.red("error: this command must run as root (try: sudo machines heal install)"));
|
|
19251
|
+
return false;
|
|
19252
|
+
}
|
|
19253
|
+
return true;
|
|
19254
|
+
}
|
|
19255
|
+
healCommand.command("config").description(`View or update self-healing config (e.g. --set '{"preferredSsid":"X81ND","fallbackSsid":"DIGI-s2N5"}')`).option("--set <json>", "Merge a JSON object into the config").option("-j, --json", "Print JSON output", false).action((options) => {
|
|
19256
|
+
if (options.set) {
|
|
19257
|
+
const current = readHealConfig();
|
|
19258
|
+
const partial = JSON.parse(options.set);
|
|
19259
|
+
writeHealConfig({
|
|
19260
|
+
...current,
|
|
19261
|
+
...partial,
|
|
19262
|
+
thresholds: { ...current.thresholds, ...partial.thresholds || {} }
|
|
19263
|
+
});
|
|
19264
|
+
}
|
|
19265
|
+
const config = readHealConfig();
|
|
19266
|
+
printJsonOrText(config, renderKeyValueTable([
|
|
19267
|
+
["enabled", String(config.enabled)],
|
|
19268
|
+
["preferredSsid", config.preferredSsid || source_default.yellow("(unset)")],
|
|
19269
|
+
["fallbackSsid", config.fallbackSsid || "(none)"],
|
|
19270
|
+
["anchors", config.tailscaleAnchors.length ? config.tailscaleAnchors.join(", ") : "(auto-discover)"],
|
|
19271
|
+
["quorumRequired", String(config.quorumRequired)],
|
|
19272
|
+
["intervalSec", String(config.intervalSec)],
|
|
19273
|
+
["thresholds", `reconnect=${config.thresholds.reconnect} nm=${config.thresholds.nmRestart} fallback=${config.thresholds.fallback} reboot=${config.thresholds.reboot}`],
|
|
19274
|
+
["allowReboot", String(config.allowReboot)],
|
|
19275
|
+
["gpuJobGuard", String(config.gpuJobGuard)]
|
|
19276
|
+
]), options.json);
|
|
19277
|
+
});
|
|
19278
|
+
healCommand.command("check").description("Run one health + decision tick read-only (no side effects)").option("-j, --json", "Print JSON output", false).action((options) => {
|
|
19279
|
+
const result = runHealOnce(readHealConfig(), { dryRun: true });
|
|
19280
|
+
printJsonOrText(result, renderList("heal check", [
|
|
19281
|
+
`health: ${result.healthy ? source_default.green("HEALTHY") : source_default.red("UNHEALTHY")} (remote quorum ${result.remoteScore})`,
|
|
19282
|
+
`reasons: ${result.reasons.length ? result.reasons.join(", ") : "none"}`,
|
|
19283
|
+
`would do: ${result.action}${result.suppressedReason ? ` (reboot suppressed: ${result.suppressedReason})` : ""}`,
|
|
19284
|
+
`consecutive fails: ${result.failCount}`
|
|
19285
|
+
]), options.json);
|
|
19286
|
+
});
|
|
19287
|
+
healCommand.command("status").description("Show watchdog service status and last persisted state").option("-j, --json", "Print JSON output", false).action((options) => {
|
|
19288
|
+
const svc = healServiceStatus();
|
|
19289
|
+
const state = readHealState();
|
|
19290
|
+
const config = readHealConfig();
|
|
19291
|
+
printJsonOrText({ service: svc, state, config }, renderKeyValueTable([
|
|
19292
|
+
["service installed", svc.installed ? source_default.green("yes") : "no"],
|
|
19293
|
+
["service active", svc.active ? source_default.green("yes") : source_default.yellow("no")],
|
|
19294
|
+
["service enabled", svc.enabled ? "yes" : "no"],
|
|
19295
|
+
["preferredSsid", config.preferredSsid || source_default.yellow("(unset)")],
|
|
19296
|
+
["consecutive fails", String(state.failCount)],
|
|
19297
|
+
["pending reboot recovery", String(state.pendingRebootRecovery)],
|
|
19298
|
+
["failed boot recoveries", String(state.failedBootRecoveries)]
|
|
19299
|
+
]), options.json);
|
|
19300
|
+
});
|
|
19301
|
+
healCommand.command("daemon").description("Run the watchdog loop in the foreground (used by systemd)").action(() => {
|
|
19302
|
+
startHealDaemon();
|
|
19303
|
+
});
|
|
19304
|
+
healCommand.command("stop").description("Stop a foreground daemon started via `heal daemon`").action(() => {
|
|
19305
|
+
const r = stopHealDaemon();
|
|
19306
|
+
console.log(r.stopped ? `stopped heal daemon (pid ${r.pid})` : "heal daemon not running");
|
|
19307
|
+
});
|
|
19308
|
+
healCommand.command("determinism").description("Pin the preferred SSID, disable other autoconnects, turn off Wi-Fi power save").action(() => {
|
|
19309
|
+
const log2 = applyDeterminism(readHealConfig());
|
|
19310
|
+
console.log(renderList("determinism", log2));
|
|
19311
|
+
});
|
|
19312
|
+
healCommand.command("install").description("Install the watchdog: determinism + hardware watchdog + systemd service (requires root)").option("--no-determinism", "Skip SSID pinning / power-save changes").option("--no-watchdog", "Skip enabling the systemd hardware watchdog").option("--no-service", "Skip installing the systemd service").action((options) => {
|
|
19313
|
+
if (!requireRoot()) {
|
|
19314
|
+
process.exitCode = 1;
|
|
19315
|
+
return;
|
|
19316
|
+
}
|
|
19317
|
+
const config = readHealConfig();
|
|
19318
|
+
if (!config.preferredSsid) {
|
|
19319
|
+
console.error(source_default.red(`error: set preferredSsid first: machines heal config --set '{"preferredSsid":"X81ND"}'`));
|
|
19320
|
+
process.exitCode = 1;
|
|
19321
|
+
return;
|
|
19322
|
+
}
|
|
19323
|
+
const out = [];
|
|
19324
|
+
if (options.determinism !== false)
|
|
19325
|
+
out.push(...applyDeterminism(config));
|
|
19326
|
+
if (options.watchdog !== false)
|
|
19327
|
+
out.push(...enableHardwareWatchdog());
|
|
19328
|
+
if (options.service !== false)
|
|
19329
|
+
out.push(...installHealService());
|
|
19330
|
+
console.log(renderList("install", out));
|
|
19331
|
+
console.log(source_default.green("self-healing watchdog installed"));
|
|
19332
|
+
});
|
|
19333
|
+
healCommand.command("uninstall").description("Remove the systemd watchdog service (requires root)").action(() => {
|
|
19334
|
+
if (!requireRoot()) {
|
|
19335
|
+
process.exitCode = 1;
|
|
19336
|
+
return;
|
|
19337
|
+
}
|
|
19338
|
+
console.log(renderList("uninstall", uninstallHealService()));
|
|
19339
|
+
});
|
|
18788
19340
|
await program2.parseAsync(process.argv);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type HealConfig } from "./heal.js";
|
|
2
|
+
export interface HealTickResult {
|
|
3
|
+
healthy: boolean;
|
|
4
|
+
action: string;
|
|
5
|
+
suppressedReason?: string;
|
|
6
|
+
reasons: string[];
|
|
7
|
+
remoteScore: number;
|
|
8
|
+
failCount: number;
|
|
9
|
+
executed: string;
|
|
10
|
+
}
|
|
11
|
+
/** Run a single health/decision tick. With dryRun=true, never executes side effects. */
|
|
12
|
+
export declare function runHealOnce(config: HealConfig, opts?: {
|
|
13
|
+
dryRun?: boolean;
|
|
14
|
+
}): HealTickResult;
|
|
15
|
+
export declare function stopHealDaemon(): {
|
|
16
|
+
stopped: boolean;
|
|
17
|
+
pid: number | null;
|
|
18
|
+
};
|
|
19
|
+
export declare function startHealDaemon(): void;
|
|
20
|
+
/**
|
|
21
|
+
* SSID determinism: pin the preferred profile (autoconnect + high priority, power
|
|
22
|
+
* save off) and disable autoconnect on every other Wi-Fi profile so the node
|
|
23
|
+
* cannot silently roam onto an isolated network.
|
|
24
|
+
*/
|
|
25
|
+
export declare function applyDeterminism(config: HealConfig): string[];
|
|
26
|
+
/** Enable the systemd hardware watchdog for true freezes (idempotent). */
|
|
27
|
+
export declare function enableHardwareWatchdog(): string[];
|
|
28
|
+
/** Install + enable the systemd service that runs the daemon as root. */
|
|
29
|
+
export declare function installHealService(): string[];
|
|
30
|
+
export declare function uninstallHealService(): string[];
|
|
31
|
+
export declare function healServiceStatus(): {
|
|
32
|
+
installed: boolean;
|
|
33
|
+
active: boolean;
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=heal-daemon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heal-daemon.d.ts","sourceRoot":"","sources":["../../src/commands/heal-daemon.ts"],"names":[],"mappings":"AAUA,OAAO,EAWL,KAAK,UAAU,EAChB,MAAM,WAAW,CAAC;AAMnB,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAMD,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,cAAc,CAsC/F;AAwBD,wBAAgB,cAAc,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAOzE;AAED,wBAAgB,eAAe,IAAI,IAAI,CAiBtC;AAOD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,EAAE,CAyB7D;AAED,0EAA0E;AAC1E,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAejD;AAQD,yEAAyE;AACzE,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAwB7C;AAED,wBAAgB,oBAAoB,IAAI,MAAM,EAAE,CAW/C;AAED,wBAAgB,iBAAiB,IAAI;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAM7F"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export interface HealThresholds {
|
|
2
|
+
/** consecutive failed checks before reconnecting Wi-Fi */
|
|
3
|
+
reconnect: number;
|
|
4
|
+
/** before restarting NetworkManager */
|
|
5
|
+
nmRestart: number;
|
|
6
|
+
/** before trying the degraded fallback SSID */
|
|
7
|
+
fallback: number;
|
|
8
|
+
/** before rebooting (last resort) */
|
|
9
|
+
reboot: number;
|
|
10
|
+
}
|
|
11
|
+
export interface HealConfig {
|
|
12
|
+
version: number;
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
/** Wi-Fi interface (empty = auto-detect) */
|
|
15
|
+
wifiInterface: string;
|
|
16
|
+
/** the SSID this node must stay on */
|
|
17
|
+
preferredSsid: string;
|
|
18
|
+
/** one-shot degraded fallback SSID, restored to preferred after fallbackWindowSec */
|
|
19
|
+
fallbackSsid: string;
|
|
20
|
+
/** HTTPS URL used as the internet anchor */
|
|
21
|
+
internetUrl: string;
|
|
22
|
+
/** Tailscale hostnames used as peer anchors (empty = auto-discover online peers) */
|
|
23
|
+
tailscaleAnchors: string[];
|
|
24
|
+
/** how many of {anchors..., internet} must be reachable to count as healthy */
|
|
25
|
+
quorumRequired: number;
|
|
26
|
+
/** seconds between checks (daemon loop / timer) */
|
|
27
|
+
intervalSec: number;
|
|
28
|
+
thresholds: HealThresholds;
|
|
29
|
+
/** min seconds between reboots */
|
|
30
|
+
rebootMinIntervalSec: number;
|
|
31
|
+
/** min seconds between NetworkManager restarts */
|
|
32
|
+
nmRestartMinIntervalSec: number;
|
|
33
|
+
/** min seconds between Wi-Fi reconnect attempts */
|
|
34
|
+
reconnectMinIntervalSec: number;
|
|
35
|
+
/** continuous healthy seconds after boot before a watchdog reboot is allowed again */
|
|
36
|
+
healthyWindowSec: number;
|
|
37
|
+
/** after this many reboots that never reached a healthy window, stop rebooting */
|
|
38
|
+
maxFailedBootRecoveries: number;
|
|
39
|
+
/** how long to suppress reboots once a reboot loop is detected */
|
|
40
|
+
bootBackoffSec: number;
|
|
41
|
+
/** how long to stay on the fallback SSID before restoring preferred */
|
|
42
|
+
fallbackWindowSec: number;
|
|
43
|
+
/** skip reboot while a GPU compute job is running (alert instead) */
|
|
44
|
+
gpuJobGuard: boolean;
|
|
45
|
+
/** master switch for the reboot tier */
|
|
46
|
+
allowReboot: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface HealState {
|
|
49
|
+
failCount: number;
|
|
50
|
+
bootId: string;
|
|
51
|
+
bootHealthySince: number | null;
|
|
52
|
+
lastRebootAttempt: number;
|
|
53
|
+
lastNmRestart: number;
|
|
54
|
+
lastReconnect: number;
|
|
55
|
+
lastFallback: number;
|
|
56
|
+
degradedUntil: number;
|
|
57
|
+
pendingRebootRecovery: boolean;
|
|
58
|
+
failedBootRecoveries: number;
|
|
59
|
+
rebootSuppressUntil: number;
|
|
60
|
+
}
|
|
61
|
+
export interface HealthProbe {
|
|
62
|
+
associatedSsid: string | null;
|
|
63
|
+
gatewayReachable: boolean;
|
|
64
|
+
/** anchor hostname -> reachable via tailscale ping */
|
|
65
|
+
anchorsReachable: Record<string, boolean>;
|
|
66
|
+
internetReachable: boolean;
|
|
67
|
+
}
|
|
68
|
+
export interface HealthResult {
|
|
69
|
+
healthy: boolean;
|
|
70
|
+
remoteScore: number;
|
|
71
|
+
reasons: string[];
|
|
72
|
+
}
|
|
73
|
+
export type HealAction = "none" | "reconnect_wifi" | "restart_nm" | "fallback_ssid" | "restore_preferred" | "reboot";
|
|
74
|
+
export type SuppressedReason = "disabled" | "gpu" | "rate" | "loop";
|
|
75
|
+
export interface HealDecision {
|
|
76
|
+
action: HealAction;
|
|
77
|
+
/** set when a reboot was wanted but withheld */
|
|
78
|
+
suppressedReason?: SuppressedReason;
|
|
79
|
+
state: HealState;
|
|
80
|
+
}
|
|
81
|
+
export declare const DEFAULT_THRESHOLDS: HealThresholds;
|
|
82
|
+
export declare const DEFAULT_HEAL_CONFIG: HealConfig;
|
|
83
|
+
export declare function defaultHealState(): HealState;
|
|
84
|
+
export declare function getHealConfigPath(): string;
|
|
85
|
+
export declare function getHealStatePath(): string;
|
|
86
|
+
export declare function readHealConfig(path?: string): HealConfig;
|
|
87
|
+
export declare function writeHealConfig(config: HealConfig, path?: string): void;
|
|
88
|
+
export declare function readHealState(path?: string): HealState;
|
|
89
|
+
export declare function writeHealState(state: HealState, path?: string): void;
|
|
90
|
+
/**
|
|
91
|
+
* Pure health evaluation. Healthy requires the local invariants (associated to an
|
|
92
|
+
* acceptable SSID + gateway reachable) AND a remote quorum of reachable anchors,
|
|
93
|
+
* so a node that is locally fine but isolated from its peers is correctly unhealthy.
|
|
94
|
+
*/
|
|
95
|
+
export declare function evaluateHealth(probe: HealthProbe, config: HealConfig, state: HealState): HealthResult;
|
|
96
|
+
/**
|
|
97
|
+
* Pure escalation state machine. Given the current persisted state, whether this
|
|
98
|
+
* tick is healthy, the clock, GPU activity, and config, decide the single action
|
|
99
|
+
* to take and return the updated state. No side effects.
|
|
100
|
+
*/
|
|
101
|
+
export declare function decideAction(input: {
|
|
102
|
+
state: HealState;
|
|
103
|
+
healthy: boolean;
|
|
104
|
+
now: number;
|
|
105
|
+
gpuBusy: boolean;
|
|
106
|
+
config: HealConfig;
|
|
107
|
+
currentBootId: string;
|
|
108
|
+
}): HealDecision;
|
|
109
|
+
export declare function getCurrentBootId(): string;
|
|
110
|
+
export declare function detectWifiInterface(): string;
|
|
111
|
+
export declare function detectGateway(): string;
|
|
112
|
+
export declare function getAssociatedSsid(): string | null;
|
|
113
|
+
export declare function pingHost(host: string): boolean;
|
|
114
|
+
export declare function internetReachable(url: string): boolean;
|
|
115
|
+
export declare function tailscalePing(host: string): boolean;
|
|
116
|
+
export declare function gpuBusy(): boolean;
|
|
117
|
+
/** Auto-discover online tailscale peers (excluding self) as anchors. */
|
|
118
|
+
export declare function discoverAnchors(): string[];
|
|
119
|
+
export declare function probeHealth(config: HealConfig): HealthProbe;
|
|
120
|
+
/** Apply the action's side effects. Returns a human-readable description. */
|
|
121
|
+
export declare function executeAction(action: HealAction, config: HealConfig): string;
|
|
122
|
+
//# sourceMappingURL=heal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heal.d.ts","sourceRoot":"","sources":["../../src/commands/heal.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -4,43 +4,25 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
function __accessProp(key) {
|
|
8
|
-
return this[key];
|
|
9
|
-
}
|
|
10
|
-
var __toESMCache_node;
|
|
11
|
-
var __toESMCache_esm;
|
|
12
7
|
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
-
var canCache = mod != null && typeof mod === "object";
|
|
14
|
-
if (canCache) {
|
|
15
|
-
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
-
var cached = cache.get(mod);
|
|
17
|
-
if (cached)
|
|
18
|
-
return cached;
|
|
19
|
-
}
|
|
20
8
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
9
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
10
|
for (let key of __getOwnPropNames(mod))
|
|
23
11
|
if (!__hasOwnProp.call(to, key))
|
|
24
12
|
__defProp(to, key, {
|
|
25
|
-
get:
|
|
13
|
+
get: () => mod[key],
|
|
26
14
|
enumerable: true
|
|
27
15
|
});
|
|
28
|
-
if (canCache)
|
|
29
|
-
cache.set(mod, to);
|
|
30
16
|
return to;
|
|
31
17
|
};
|
|
32
18
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
-
var __returnValue = (v) => v;
|
|
34
|
-
function __exportSetter(name, newValue) {
|
|
35
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
-
}
|
|
37
19
|
var __export = (target, all) => {
|
|
38
20
|
for (var name in all)
|
|
39
21
|
__defProp(target, name, {
|
|
40
22
|
get: all[name],
|
|
41
23
|
enumerable: true,
|
|
42
24
|
configurable: true,
|
|
43
|
-
set:
|
|
25
|
+
set: (newValue) => all[name] = () => newValue
|
|
44
26
|
});
|
|
45
27
|
};
|
|
46
28
|
|
|
@@ -6583,15 +6565,15 @@ var __getProtoOf2 = Object.getPrototypeOf;
|
|
|
6583
6565
|
var __defProp2 = Object.defineProperty;
|
|
6584
6566
|
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
6585
6567
|
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
6586
|
-
function
|
|
6568
|
+
function __accessProp(key) {
|
|
6587
6569
|
return this[key];
|
|
6588
6570
|
}
|
|
6589
|
-
var
|
|
6590
|
-
var
|
|
6571
|
+
var __toESMCache_node;
|
|
6572
|
+
var __toESMCache_esm;
|
|
6591
6573
|
var __toESM2 = (mod, isNodeMode, target) => {
|
|
6592
6574
|
var canCache = mod != null && typeof mod === "object";
|
|
6593
6575
|
if (canCache) {
|
|
6594
|
-
var cache = isNodeMode ?
|
|
6576
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
6595
6577
|
var cached = cache.get(mod);
|
|
6596
6578
|
if (cached)
|
|
6597
6579
|
return cached;
|
|
@@ -6601,7 +6583,7 @@ var __toESM2 = (mod, isNodeMode, target) => {
|
|
|
6601
6583
|
for (let key of __getOwnPropNames2(mod))
|
|
6602
6584
|
if (!__hasOwnProp2.call(to, key))
|
|
6603
6585
|
__defProp2(to, key, {
|
|
6604
|
-
get:
|
|
6586
|
+
get: __accessProp.bind(mod, key),
|
|
6605
6587
|
enumerable: true
|
|
6606
6588
|
});
|
|
6607
6589
|
if (canCache)
|
|
@@ -6609,9 +6591,9 @@ var __toESM2 = (mod, isNodeMode, target) => {
|
|
|
6609
6591
|
return to;
|
|
6610
6592
|
};
|
|
6611
6593
|
var __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
6612
|
-
var
|
|
6613
|
-
function
|
|
6614
|
-
this[name] =
|
|
6594
|
+
var __returnValue = (v) => v;
|
|
6595
|
+
function __exportSetter(name, newValue) {
|
|
6596
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6615
6597
|
}
|
|
6616
6598
|
var __export2 = (target, all) => {
|
|
6617
6599
|
for (var name in all)
|
|
@@ -6619,7 +6601,7 @@ var __export2 = (target, all) => {
|
|
|
6619
6601
|
get: all[name],
|
|
6620
6602
|
enumerable: true,
|
|
6621
6603
|
configurable: true,
|
|
6622
|
-
set:
|
|
6604
|
+
set: __exportSetter.bind(all, name)
|
|
6623
6605
|
});
|
|
6624
6606
|
};
|
|
6625
6607
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
package/dist/mcp/index.js
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
|
-
var __returnValue = (v) => v;
|
|
5
|
-
function __exportSetter(name, newValue) {
|
|
6
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
7
|
-
}
|
|
8
4
|
var __export = (target, all) => {
|
|
9
5
|
for (var name in all)
|
|
10
6
|
__defProp(target, name, {
|
|
11
7
|
get: all[name],
|
|
12
8
|
enumerable: true,
|
|
13
9
|
configurable: true,
|
|
14
|
-
set:
|
|
10
|
+
set: (newValue) => all[name] = () => newValue
|
|
15
11
|
});
|
|
16
12
|
};
|
|
17
13
|
|
|
@@ -4274,9 +4270,9 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
4274
4270
|
return to;
|
|
4275
4271
|
};
|
|
4276
4272
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
4277
|
-
var
|
|
4278
|
-
function
|
|
4279
|
-
this[name] =
|
|
4273
|
+
var __returnValue = (v) => v;
|
|
4274
|
+
function __exportSetter(name, newValue) {
|
|
4275
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
4280
4276
|
}
|
|
4281
4277
|
var __export2 = (target, all) => {
|
|
4282
4278
|
for (var name in all)
|
|
@@ -4284,7 +4280,7 @@ var __export2 = (target, all) => {
|
|
|
4284
4280
|
get: all[name],
|
|
4285
4281
|
enumerable: true,
|
|
4286
4282
|
configurable: true,
|
|
4287
|
-
set:
|
|
4283
|
+
set: __exportSetter.bind(all, name)
|
|
4288
4284
|
});
|
|
4289
4285
|
};
|
|
4290
4286
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|