@vm0/runner 3.12.3 → 3.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +276 -115
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -429,6 +429,26 @@ function isProcessRunning(pid) {
|
|
|
429
429
|
return false;
|
|
430
430
|
}
|
|
431
431
|
}
|
|
432
|
+
async function gracefulKillProcess(pid, timeoutMs = 5e3) {
|
|
433
|
+
if (!isProcessRunning(pid)) return true;
|
|
434
|
+
try {
|
|
435
|
+
process.kill(pid, "SIGTERM");
|
|
436
|
+
} catch {
|
|
437
|
+
return !isProcessRunning(pid);
|
|
438
|
+
}
|
|
439
|
+
const startTime = Date.now();
|
|
440
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
441
|
+
if (!isProcessRunning(pid)) return true;
|
|
442
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
443
|
+
}
|
|
444
|
+
if (isProcessRunning(pid)) {
|
|
445
|
+
try {
|
|
446
|
+
process.kill(pid, "SIGKILL");
|
|
447
|
+
} catch {
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return !isProcessRunning(pid);
|
|
451
|
+
}
|
|
432
452
|
function killProcessTree(pid) {
|
|
433
453
|
try {
|
|
434
454
|
const childPidsStr = execSync(`pgrep -P ${pid} 2>/dev/null || true`, {
|
|
@@ -9935,7 +9955,7 @@ async function executeJob(context, config, options = {}) {
|
|
|
9935
9955
|
logger9.log(`Guest client ready`);
|
|
9936
9956
|
if (config.firecracker.snapshot) {
|
|
9937
9957
|
const timestamp = (Date.now() / 1e3).toFixed(3);
|
|
9938
|
-
await guest.exec(`date -s "@${timestamp}"`);
|
|
9958
|
+
await guest.exec(`sudo date -s "@${timestamp}"`);
|
|
9939
9959
|
}
|
|
9940
9960
|
if (context.storageManifest) {
|
|
9941
9961
|
await withSandboxTiming(
|
|
@@ -9994,7 +10014,7 @@ async function executeJob(context, config, options = {}) {
|
|
|
9994
10014
|
const duration = Math.round(durationMs / 1e3);
|
|
9995
10015
|
if (exitCode === 137 || exitCode === 9) {
|
|
9996
10016
|
const dmesgCheck = await guest.exec(
|
|
9997
|
-
`dmesg | tail -20 | grep -iE "killed|oom" 2>/dev/null`
|
|
10017
|
+
`sudo dmesg | tail -20 | grep -iE "killed|oom" 2>/dev/null`
|
|
9998
10018
|
);
|
|
9999
10019
|
if (dmesgCheck.stdout.toLowerCase().includes("oom") || dmesgCheck.stdout.toLowerCase().includes("killed")) {
|
|
10000
10020
|
logger9.log(`OOM detected: ${dmesgCheck.stdout}`);
|
|
@@ -10519,9 +10539,20 @@ import { Command as Command2 } from "commander";
|
|
|
10519
10539
|
import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
|
|
10520
10540
|
import { execSync as execSync3 } from "child_process";
|
|
10521
10541
|
|
|
10522
|
-
// src/lib/
|
|
10523
|
-
import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync4 } from "fs";
|
|
10542
|
+
// src/lib/process.ts
|
|
10543
|
+
import { readdirSync, readFileSync as readFileSync2, readlinkSync, existsSync as existsSync4 } from "fs";
|
|
10524
10544
|
import path8 from "path";
|
|
10545
|
+
function isOrphanProcess(pid) {
|
|
10546
|
+
try {
|
|
10547
|
+
const stat = readFileSync2(`/proc/${pid}/stat`, "utf-8");
|
|
10548
|
+
const lastParen = stat.lastIndexOf(")");
|
|
10549
|
+
if (lastParen === -1) return false;
|
|
10550
|
+
const fields = stat.slice(lastParen + 1).trim().split(/\s+/);
|
|
10551
|
+
return fields[1] === "1";
|
|
10552
|
+
} catch {
|
|
10553
|
+
return false;
|
|
10554
|
+
}
|
|
10555
|
+
}
|
|
10525
10556
|
function parseFirecrackerCmdline(cmdline) {
|
|
10526
10557
|
const args = cmdline.split("\0");
|
|
10527
10558
|
if (!args[0]?.includes("firecracker")) return null;
|
|
@@ -10556,6 +10587,27 @@ function parseMitmproxyCmdline(cmdline) {
|
|
|
10556
10587
|
}
|
|
10557
10588
|
return null;
|
|
10558
10589
|
}
|
|
10590
|
+
function parseRunnerCmdline(cmdline) {
|
|
10591
|
+
const args = cmdline.split("\0").filter((a) => a !== "");
|
|
10592
|
+
const startIdx = args.indexOf("start");
|
|
10593
|
+
const benchmarkIdx = args.indexOf("benchmark");
|
|
10594
|
+
let mode;
|
|
10595
|
+
let modeIdx;
|
|
10596
|
+
if (startIdx !== -1 && (benchmarkIdx === -1 || startIdx < benchmarkIdx)) {
|
|
10597
|
+
mode = "start";
|
|
10598
|
+
modeIdx = startIdx;
|
|
10599
|
+
} else if (benchmarkIdx !== -1) {
|
|
10600
|
+
mode = "benchmark";
|
|
10601
|
+
modeIdx = benchmarkIdx;
|
|
10602
|
+
} else {
|
|
10603
|
+
return null;
|
|
10604
|
+
}
|
|
10605
|
+
const configIdx = args.indexOf("--config", modeIdx + 1);
|
|
10606
|
+
if (configIdx === -1 || configIdx >= args.length - 1) return null;
|
|
10607
|
+
const configPath = args[configIdx + 1];
|
|
10608
|
+
if (!configPath?.match(/\.ya?ml$/)) return null;
|
|
10609
|
+
return { configPath, mode };
|
|
10610
|
+
}
|
|
10559
10611
|
function findFirecrackerProcesses() {
|
|
10560
10612
|
const processes = [];
|
|
10561
10613
|
const procDir = "/proc";
|
|
@@ -10574,7 +10626,12 @@ function findFirecrackerProcesses() {
|
|
|
10574
10626
|
const cmdline = readFileSync2(cmdlinePath, "utf-8");
|
|
10575
10627
|
const parsed = parseFirecrackerCmdline(cmdline);
|
|
10576
10628
|
if (parsed) {
|
|
10577
|
-
processes.push({
|
|
10629
|
+
processes.push({
|
|
10630
|
+
pid,
|
|
10631
|
+
vmId: parsed.vmId,
|
|
10632
|
+
baseDir: parsed.baseDir,
|
|
10633
|
+
isOrphan: isOrphanProcess(pid)
|
|
10634
|
+
});
|
|
10578
10635
|
}
|
|
10579
10636
|
} catch {
|
|
10580
10637
|
continue;
|
|
@@ -10587,27 +10644,39 @@ function findProcessByVmId(vmId) {
|
|
|
10587
10644
|
const vmIdStr = vmIdValue(vmId);
|
|
10588
10645
|
return processes.find((p) => vmIdValue(p.vmId) === vmIdStr) || null;
|
|
10589
10646
|
}
|
|
10590
|
-
|
|
10591
|
-
|
|
10647
|
+
function findMitmproxyProcesses() {
|
|
10648
|
+
const processes = [];
|
|
10649
|
+
const procDir = "/proc";
|
|
10650
|
+
let entries;
|
|
10592
10651
|
try {
|
|
10593
|
-
|
|
10652
|
+
entries = readdirSync(procDir);
|
|
10594
10653
|
} catch {
|
|
10595
|
-
return
|
|
10596
|
-
}
|
|
10597
|
-
const startTime = Date.now();
|
|
10598
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
10599
|
-
if (!isProcessRunning(pid)) return true;
|
|
10600
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
10654
|
+
return [];
|
|
10601
10655
|
}
|
|
10602
|
-
|
|
10656
|
+
for (const entry of entries) {
|
|
10657
|
+
if (!/^\d+$/.test(entry)) continue;
|
|
10658
|
+
const pid = parseInt(entry, 10);
|
|
10659
|
+
const cmdlinePath = path8.join(procDir, entry, "cmdline");
|
|
10660
|
+
if (!existsSync4(cmdlinePath)) continue;
|
|
10603
10661
|
try {
|
|
10604
|
-
|
|
10662
|
+
const cmdline = readFileSync2(cmdlinePath, "utf-8");
|
|
10663
|
+
const baseDir = parseMitmproxyCmdline(cmdline);
|
|
10664
|
+
if (baseDir) {
|
|
10665
|
+
processes.push({ pid, baseDir, isOrphan: isOrphanProcess(pid) });
|
|
10666
|
+
}
|
|
10605
10667
|
} catch {
|
|
10668
|
+
continue;
|
|
10606
10669
|
}
|
|
10607
10670
|
}
|
|
10608
|
-
return
|
|
10671
|
+
return processes;
|
|
10609
10672
|
}
|
|
10610
|
-
function
|
|
10673
|
+
function isNodeIndexJs(cmdline) {
|
|
10674
|
+
const args = cmdline.split("\0").filter((a) => a !== "");
|
|
10675
|
+
if (args.length < 2) return false;
|
|
10676
|
+
if (!args[0]?.includes("node")) return false;
|
|
10677
|
+
return args[1]?.endsWith("index.js") ?? false;
|
|
10678
|
+
}
|
|
10679
|
+
function findRunnerProcesses() {
|
|
10611
10680
|
const processes = [];
|
|
10612
10681
|
const procDir = "/proc";
|
|
10613
10682
|
let entries;
|
|
@@ -10623,9 +10692,27 @@ function findMitmproxyProcesses() {
|
|
|
10623
10692
|
if (!existsSync4(cmdlinePath)) continue;
|
|
10624
10693
|
try {
|
|
10625
10694
|
const cmdline = readFileSync2(cmdlinePath, "utf-8");
|
|
10626
|
-
const
|
|
10627
|
-
if (
|
|
10628
|
-
processes.push({
|
|
10695
|
+
const parsed = parseRunnerCmdline(cmdline);
|
|
10696
|
+
if (parsed) {
|
|
10697
|
+
processes.push({
|
|
10698
|
+
pid,
|
|
10699
|
+
configPath: parsed.configPath,
|
|
10700
|
+
mode: parsed.mode
|
|
10701
|
+
});
|
|
10702
|
+
continue;
|
|
10703
|
+
}
|
|
10704
|
+
if (isNodeIndexJs(cmdline)) {
|
|
10705
|
+
const cwdPath = path8.join(procDir, entry, "cwd");
|
|
10706
|
+
const cwd = readlinkSync(cwdPath);
|
|
10707
|
+
const configPath = path8.join(cwd, "runner.yaml");
|
|
10708
|
+
if (existsSync4(configPath)) {
|
|
10709
|
+
processes.push({
|
|
10710
|
+
pid,
|
|
10711
|
+
configPath,
|
|
10712
|
+
mode: "start"
|
|
10713
|
+
// Default to start mode (cannot determine from cmdline)
|
|
10714
|
+
});
|
|
10715
|
+
}
|
|
10629
10716
|
}
|
|
10630
10717
|
} catch {
|
|
10631
10718
|
continue;
|
|
@@ -10646,68 +10733,39 @@ var RunnerStatusSchema = z31.object({
|
|
|
10646
10733
|
});
|
|
10647
10734
|
|
|
10648
10735
|
// src/commands/doctor.ts
|
|
10649
|
-
function
|
|
10736
|
+
function getRunnerStatus(statusFilePath, warnings) {
|
|
10650
10737
|
if (!existsSync5(statusFilePath)) {
|
|
10651
|
-
console.log("Mode: unknown (no status.json)");
|
|
10652
10738
|
return null;
|
|
10653
10739
|
}
|
|
10654
10740
|
try {
|
|
10655
|
-
|
|
10741
|
+
return RunnerStatusSchema.parse(
|
|
10656
10742
|
JSON.parse(readFileSync3(statusFilePath, "utf-8"))
|
|
10657
10743
|
);
|
|
10658
|
-
console.log(`Mode: ${status.mode}`);
|
|
10659
|
-
if (status.started_at) {
|
|
10660
|
-
const started = new Date(status.started_at);
|
|
10661
|
-
const uptime = formatUptime(Date.now() - started.getTime());
|
|
10662
|
-
console.log(`Started: ${started.toLocaleString()} (uptime: ${uptime})`);
|
|
10663
|
-
}
|
|
10664
|
-
return status;
|
|
10665
10744
|
} catch {
|
|
10666
|
-
console.log("Mode: unknown (status.json unreadable)");
|
|
10667
10745
|
warnings.push({ message: "status.json exists but cannot be parsed" });
|
|
10668
10746
|
return null;
|
|
10669
10747
|
}
|
|
10670
10748
|
}
|
|
10671
10749
|
async function checkApiConnectivity(config, warnings) {
|
|
10672
|
-
console.log("API Connectivity:");
|
|
10673
10750
|
try {
|
|
10674
10751
|
await pollForJob(config.server, config.group);
|
|
10675
|
-
|
|
10676
|
-
console.log(" \u2713 Authentication: OK");
|
|
10752
|
+
return true;
|
|
10677
10753
|
} catch (error) {
|
|
10678
|
-
console.log(` \u2717 Cannot connect to ${config.server.url}`);
|
|
10679
|
-
console.log(
|
|
10680
|
-
` Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
10681
|
-
);
|
|
10682
10754
|
warnings.push({
|
|
10683
10755
|
message: `Cannot connect to API: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
10684
10756
|
});
|
|
10757
|
+
return false;
|
|
10685
10758
|
}
|
|
10686
10759
|
}
|
|
10687
|
-
function
|
|
10688
|
-
console.log("Network:");
|
|
10689
|
-
const mitmProcesses = findMitmproxyProcesses();
|
|
10690
|
-
const mitmProc = mitmProcesses.find((p) => p.baseDir === config.base_dir);
|
|
10691
|
-
if (mitmProc) {
|
|
10692
|
-
console.log(
|
|
10693
|
-
` \u2713 Proxy mitmproxy (PID ${mitmProc.pid}) on :${config.proxy.port}`
|
|
10694
|
-
);
|
|
10695
|
-
} else {
|
|
10696
|
-
console.log(` \u2717 Proxy mitmproxy not running`);
|
|
10697
|
-
warnings.push({ message: "Proxy mitmproxy is not running" });
|
|
10698
|
-
}
|
|
10699
|
-
console.log(
|
|
10700
|
-
` \u2139 Namespaces: each VM runs in isolated namespace with IP ${SNAPSHOT_NETWORK.guestIp}`
|
|
10701
|
-
);
|
|
10702
|
-
}
|
|
10703
|
-
function buildJobInfo(status, processes) {
|
|
10760
|
+
function buildJobInfo(status, processes, baseDir) {
|
|
10704
10761
|
const jobs = [];
|
|
10705
10762
|
const statusVmIds = /* @__PURE__ */ new Set();
|
|
10763
|
+
const runnerProcesses = processes.filter((p) => p.baseDir === baseDir);
|
|
10706
10764
|
if (status?.active_run_ids) {
|
|
10707
10765
|
for (const runId of status.active_run_ids) {
|
|
10708
10766
|
const vmId = createVmId(runId);
|
|
10709
10767
|
statusVmIds.add(vmId);
|
|
10710
|
-
const proc =
|
|
10768
|
+
const proc = runnerProcesses.find((p) => p.vmId === vmId);
|
|
10711
10769
|
jobs.push({
|
|
10712
10770
|
runId,
|
|
10713
10771
|
vmId,
|
|
@@ -10717,18 +10775,6 @@ function buildJobInfo(status, processes) {
|
|
|
10717
10775
|
}
|
|
10718
10776
|
return { jobs, statusVmIds };
|
|
10719
10777
|
}
|
|
10720
|
-
function displayRuns(jobs, maxConcurrent) {
|
|
10721
|
-
console.log(`Runs (${jobs.length} active, max ${maxConcurrent}):`);
|
|
10722
|
-
if (jobs.length === 0) {
|
|
10723
|
-
console.log(" No active runs");
|
|
10724
|
-
return;
|
|
10725
|
-
}
|
|
10726
|
-
console.log(" Run ID VM ID Status");
|
|
10727
|
-
for (const job of jobs) {
|
|
10728
|
-
const statusText = job.firecrackerPid ? `\u2713 Running (PID ${job.firecrackerPid})` : "\u26A0\uFE0F No process";
|
|
10729
|
-
console.log(` ${job.runId} ${job.vmId} ${statusText}`);
|
|
10730
|
-
}
|
|
10731
|
-
}
|
|
10732
10778
|
async function findOrphanNetworkNamespaces(warnings) {
|
|
10733
10779
|
let allNamespaces = [];
|
|
10734
10780
|
try {
|
|
@@ -10777,12 +10823,12 @@ async function findOrphanNetworkNamespaces(warnings) {
|
|
|
10777
10823
|
return [];
|
|
10778
10824
|
}
|
|
10779
10825
|
}
|
|
10780
|
-
|
|
10826
|
+
function detectRunnerOrphanResources(jobs, allProcesses, workspaces, statusVmIds, baseDir, warnings) {
|
|
10781
10827
|
const processes = allProcesses.filter((p) => p.baseDir === baseDir);
|
|
10782
10828
|
for (const job of jobs) {
|
|
10783
10829
|
if (!job.firecrackerPid) {
|
|
10784
10830
|
warnings.push({
|
|
10785
|
-
message: `Run ${job.vmId} in status.json but no Firecracker process
|
|
10831
|
+
message: `Run ${job.vmId} in status.json but no Firecracker process`
|
|
10786
10832
|
});
|
|
10787
10833
|
}
|
|
10788
10834
|
}
|
|
@@ -10794,31 +10840,15 @@ async function detectOrphanResources(jobs, allProcesses, workspaces, statusVmIds
|
|
|
10794
10840
|
});
|
|
10795
10841
|
}
|
|
10796
10842
|
}
|
|
10797
|
-
const orphanNetns = await findOrphanNetworkNamespaces(warnings);
|
|
10798
|
-
for (const ns of orphanNetns) {
|
|
10799
|
-
warnings.push({
|
|
10800
|
-
message: `Orphan network namespace: ${ns} (runner process not running)`
|
|
10801
|
-
});
|
|
10802
|
-
}
|
|
10803
10843
|
for (const ws of workspaces) {
|
|
10804
10844
|
const vmId = runnerPaths.extractVmId(ws);
|
|
10805
10845
|
if (!processVmIds.has(vmId) && !statusVmIds.has(vmId)) {
|
|
10806
10846
|
warnings.push({
|
|
10807
|
-
message: `Orphan workspace: ${ws}
|
|
10847
|
+
message: `Orphan workspace: ${ws}`
|
|
10808
10848
|
});
|
|
10809
10849
|
}
|
|
10810
10850
|
}
|
|
10811
10851
|
}
|
|
10812
|
-
function displayWarnings(warnings) {
|
|
10813
|
-
console.log("Warnings:");
|
|
10814
|
-
if (warnings.length === 0) {
|
|
10815
|
-
console.log(" None");
|
|
10816
|
-
} else {
|
|
10817
|
-
for (const w of warnings) {
|
|
10818
|
-
console.log(` - ${w.message}`);
|
|
10819
|
-
}
|
|
10820
|
-
}
|
|
10821
|
-
}
|
|
10822
10852
|
function formatUptime(ms) {
|
|
10823
10853
|
const seconds = Math.floor(ms / 1e3);
|
|
10824
10854
|
const minutes = Math.floor(seconds / 60);
|
|
@@ -10829,34 +10859,165 @@ function formatUptime(ms) {
|
|
|
10829
10859
|
if (minutes > 0) return `${minutes}m`;
|
|
10830
10860
|
return `${seconds}s`;
|
|
10831
10861
|
}
|
|
10832
|
-
|
|
10862
|
+
async function displayRunnerHealth(runner, index, allFirecrackerProcesses, allMitmproxyProcesses) {
|
|
10863
|
+
const warnings = [];
|
|
10864
|
+
const { config, pid, mode } = runner;
|
|
10865
|
+
const baseDir = config.base_dir;
|
|
10866
|
+
console.log(`[${index}] ${baseDir} (PID ${pid}) [${mode}]`);
|
|
10867
|
+
const statusFilePath = runnerPaths.statusFile(baseDir);
|
|
10868
|
+
const status = getRunnerStatus(statusFilePath, warnings);
|
|
10869
|
+
if (status) {
|
|
10870
|
+
let statusLine = ` Mode: ${status.mode}`;
|
|
10871
|
+
if (status.started_at) {
|
|
10872
|
+
const started = new Date(status.started_at);
|
|
10873
|
+
const uptime = formatUptime(Date.now() - started.getTime());
|
|
10874
|
+
statusLine += `, uptime: ${uptime}`;
|
|
10875
|
+
}
|
|
10876
|
+
console.log(statusLine);
|
|
10877
|
+
} else {
|
|
10878
|
+
console.log(" Mode: unknown (no status.json)");
|
|
10879
|
+
}
|
|
10880
|
+
const apiOk = await checkApiConnectivity(config, warnings);
|
|
10881
|
+
if (apiOk) {
|
|
10882
|
+
console.log(` API: \u2713 Connected to ${config.server.url}`);
|
|
10883
|
+
} else {
|
|
10884
|
+
console.log(` API: \u2717 Cannot connect to ${config.server.url}`);
|
|
10885
|
+
}
|
|
10886
|
+
const mitmProc = allMitmproxyProcesses.find((p) => p.baseDir === baseDir);
|
|
10887
|
+
if (mitmProc) {
|
|
10888
|
+
console.log(
|
|
10889
|
+
` Proxy: \u2713 mitmproxy (PID ${mitmProc.pid}) on :${config.proxy.port}`
|
|
10890
|
+
);
|
|
10891
|
+
} else if (mode === "start") {
|
|
10892
|
+
console.log(" Proxy: \u2717 not running");
|
|
10893
|
+
warnings.push({ message: "Proxy mitmproxy is not running" });
|
|
10894
|
+
} else {
|
|
10895
|
+
console.log(" Proxy: - (not running)");
|
|
10896
|
+
}
|
|
10897
|
+
const { jobs, statusVmIds } = buildJobInfo(
|
|
10898
|
+
status,
|
|
10899
|
+
allFirecrackerProcesses,
|
|
10900
|
+
baseDir
|
|
10901
|
+
);
|
|
10902
|
+
console.log(
|
|
10903
|
+
` Runs (${jobs.length} active, max ${config.sandbox.max_concurrent}):`
|
|
10904
|
+
);
|
|
10905
|
+
if (jobs.length === 0) {
|
|
10906
|
+
console.log(" No active runs");
|
|
10907
|
+
} else {
|
|
10908
|
+
for (const job of jobs) {
|
|
10909
|
+
const statusText = job.firecrackerPid ? `\u2713 Running (PID ${job.firecrackerPid})` : "\u26A0\uFE0F No process";
|
|
10910
|
+
console.log(` ${job.vmId} ${statusText}`);
|
|
10911
|
+
}
|
|
10912
|
+
}
|
|
10913
|
+
const workspacesDir = runnerPaths.workspacesDir(baseDir);
|
|
10914
|
+
const workspaces = existsSync5(workspacesDir) ? readdirSync2(workspacesDir).filter(runnerPaths.isVmWorkspace) : [];
|
|
10915
|
+
detectRunnerOrphanResources(
|
|
10916
|
+
jobs,
|
|
10917
|
+
allFirecrackerProcesses,
|
|
10918
|
+
workspaces,
|
|
10919
|
+
statusVmIds,
|
|
10920
|
+
baseDir,
|
|
10921
|
+
warnings
|
|
10922
|
+
);
|
|
10923
|
+
console.log(` Warnings:`);
|
|
10924
|
+
if (warnings.length === 0) {
|
|
10925
|
+
console.log(" None");
|
|
10926
|
+
} else {
|
|
10927
|
+
for (const w of warnings) {
|
|
10928
|
+
console.log(` - ${w.message}`);
|
|
10929
|
+
}
|
|
10930
|
+
}
|
|
10931
|
+
return warnings;
|
|
10932
|
+
}
|
|
10933
|
+
async function detectGlobalOrphans(discoveredRunners, allFirecrackerProcesses, allMitmproxyProcesses, globalWarnings) {
|
|
10934
|
+
const runnerBaseDirs = new Set(
|
|
10935
|
+
discoveredRunners.map((r) => r.config.base_dir)
|
|
10936
|
+
);
|
|
10937
|
+
for (const mitm of allMitmproxyProcesses) {
|
|
10938
|
+
if (mitm.isOrphan) {
|
|
10939
|
+
globalWarnings.push({
|
|
10940
|
+
message: `Orphan mitmproxy: PID ${mitm.pid} (PPID=1, parent process dead)`
|
|
10941
|
+
});
|
|
10942
|
+
} else if (!runnerBaseDirs.has(mitm.baseDir)) {
|
|
10943
|
+
globalWarnings.push({
|
|
10944
|
+
message: `Orphan mitmproxy: PID ${mitm.pid} (baseDir ${mitm.baseDir}, runner not running)`
|
|
10945
|
+
});
|
|
10946
|
+
}
|
|
10947
|
+
}
|
|
10948
|
+
for (const fc of allFirecrackerProcesses) {
|
|
10949
|
+
if (fc.isOrphan) {
|
|
10950
|
+
globalWarnings.push({
|
|
10951
|
+
message: `Orphan Firecracker: PID ${fc.pid} (vmId ${fc.vmId}, PPID=1, parent process dead)`
|
|
10952
|
+
});
|
|
10953
|
+
} else if (!runnerBaseDirs.has(fc.baseDir)) {
|
|
10954
|
+
globalWarnings.push({
|
|
10955
|
+
message: `Orphan Firecracker: PID ${fc.pid} (vmId ${fc.vmId}, baseDir ${fc.baseDir}, runner not running)`
|
|
10956
|
+
});
|
|
10957
|
+
}
|
|
10958
|
+
}
|
|
10959
|
+
const orphanNetns = await findOrphanNetworkNamespaces(globalWarnings);
|
|
10960
|
+
for (const ns of orphanNetns) {
|
|
10961
|
+
globalWarnings.push({
|
|
10962
|
+
message: `Orphan namespace: ${ns} (runner process not running)`
|
|
10963
|
+
});
|
|
10964
|
+
}
|
|
10965
|
+
}
|
|
10966
|
+
var doctorCommand = new Command2("doctor").description("Diagnose health of all runners on this host").action(async () => {
|
|
10833
10967
|
try {
|
|
10834
|
-
const
|
|
10835
|
-
|
|
10836
|
-
const
|
|
10837
|
-
const
|
|
10838
|
-
|
|
10839
|
-
const
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
|
|
10848
|
-
|
|
10968
|
+
const globalWarnings = [];
|
|
10969
|
+
let totalWarnings = 0;
|
|
10970
|
+
const allFirecrackerProcesses = findFirecrackerProcesses();
|
|
10971
|
+
const allMitmproxyProcesses = findMitmproxyProcesses();
|
|
10972
|
+
const runnerProcesses = findRunnerProcesses();
|
|
10973
|
+
const discoveredRunners = [];
|
|
10974
|
+
for (const rp of runnerProcesses) {
|
|
10975
|
+
try {
|
|
10976
|
+
const config = loadConfig(rp.configPath);
|
|
10977
|
+
discoveredRunners.push({
|
|
10978
|
+
pid: rp.pid,
|
|
10979
|
+
config,
|
|
10980
|
+
mode: rp.mode
|
|
10981
|
+
});
|
|
10982
|
+
} catch (err) {
|
|
10983
|
+
globalWarnings.push({
|
|
10984
|
+
message: `Failed to load config ${rp.configPath}: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
10985
|
+
});
|
|
10986
|
+
}
|
|
10987
|
+
}
|
|
10988
|
+
console.log(`Runners (${discoveredRunners.length} found):`);
|
|
10849
10989
|
console.log("");
|
|
10850
|
-
|
|
10851
|
-
|
|
10852
|
-
|
|
10853
|
-
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
10990
|
+
if (discoveredRunners.length === 0) {
|
|
10991
|
+
console.log(" No runner processes found");
|
|
10992
|
+
console.log("");
|
|
10993
|
+
} else {
|
|
10994
|
+
for (let i = 0; i < discoveredRunners.length; i++) {
|
|
10995
|
+
const warnings = await displayRunnerHealth(
|
|
10996
|
+
discoveredRunners[i],
|
|
10997
|
+
i + 1,
|
|
10998
|
+
allFirecrackerProcesses,
|
|
10999
|
+
allMitmproxyProcesses
|
|
11000
|
+
);
|
|
11001
|
+
totalWarnings += warnings.length;
|
|
11002
|
+
console.log("");
|
|
11003
|
+
}
|
|
11004
|
+
}
|
|
11005
|
+
await detectGlobalOrphans(
|
|
11006
|
+
discoveredRunners,
|
|
11007
|
+
allFirecrackerProcesses,
|
|
11008
|
+
allMitmproxyProcesses,
|
|
11009
|
+
globalWarnings
|
|
10857
11010
|
);
|
|
10858
|
-
|
|
10859
|
-
|
|
11011
|
+
console.log("Global:");
|
|
11012
|
+
if (globalWarnings.length === 0) {
|
|
11013
|
+
console.log(" No orphan resources");
|
|
11014
|
+
} else {
|
|
11015
|
+
for (const w of globalWarnings) {
|
|
11016
|
+
console.log(` ${w.message}`);
|
|
11017
|
+
}
|
|
11018
|
+
}
|
|
11019
|
+
totalWarnings += globalWarnings.length;
|
|
11020
|
+
process.exit(totalWarnings > 0 ? 1 : 0);
|
|
10860
11021
|
} catch (error) {
|
|
10861
11022
|
console.error(
|
|
10862
11023
|
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
@@ -10900,7 +11061,7 @@ var killCommand = new Command3("kill").description("Force terminate a run and cl
|
|
|
10900
11061
|
}
|
|
10901
11062
|
const results = [];
|
|
10902
11063
|
if (proc) {
|
|
10903
|
-
const killed = await
|
|
11064
|
+
const killed = await gracefulKillProcess(proc.pid);
|
|
10904
11065
|
results.push({
|
|
10905
11066
|
step: "Firecracker process",
|
|
10906
11067
|
success: killed,
|
|
@@ -11345,7 +11506,7 @@ var snapshotCommand = new Command5("snapshot").description("Generate a Firecrack
|
|
|
11345
11506
|
);
|
|
11346
11507
|
|
|
11347
11508
|
// src/index.ts
|
|
11348
|
-
var version = true ? "3.
|
|
11509
|
+
var version = true ? "3.14.0" : "0.1.0";
|
|
11349
11510
|
program.name("vm0-runner").version(version).description("Self-hosted runner for VM0 agents");
|
|
11350
11511
|
program.addCommand(startCommand);
|
|
11351
11512
|
program.addCommand(doctorCommand);
|