@vm0/runner 3.11.0 → 3.11.2

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.
Files changed (2) hide show
  1. package/index.js +192 -157
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -33,6 +33,8 @@ var runtimePaths = {
33
33
  };
34
34
  var VM_WORKSPACE_PREFIX = "vm0-";
35
35
  var runnerPaths = {
36
+ /** Base directory used for snapshot generation (baseDir + /snapshot-gen) */
37
+ snapshotBaseDir: (baseDir) => path.join(baseDir, "snapshot-gen"),
36
38
  /** Overlay pool directory for pre-warmed VM overlays */
37
39
  overlayPool: (baseDir) => path.join(baseDir, "overlay-pool"),
38
40
  /** Workspaces directory for VM work directories */
@@ -41,16 +43,24 @@ var runnerPaths = {
41
43
  vmWorkDir: (baseDir, vmId) => path.join(baseDir, "workspaces", `${VM_WORKSPACE_PREFIX}${vmId}`),
42
44
  /** Runner status file */
43
45
  statusFile: (baseDir) => path.join(baseDir, "status.json"),
46
+ /** Snapshot generation work directory */
47
+ snapshotWorkDir: (baseDir) => path.join(baseDir, "workspaces", ".snapshot-work"),
44
48
  /** Check if a directory name is a VM workspace */
45
49
  isVmWorkspace: (dirname) => dirname.startsWith(VM_WORKSPACE_PREFIX),
46
50
  /** Extract vmId from workspace directory name */
47
51
  extractVmId: (dirname) => createVmId(dirname.replace(VM_WORKSPACE_PREFIX, ""))
48
52
  };
49
53
  var vmPaths = {
50
- /** Firecracker config file (used with --config-file --no-api) */
54
+ /** Firecracker config file (used with --config-file) */
51
55
  config: (workDir) => path.join(workDir, "config.json"),
52
- /** Vsock UDS for host-guest communication */
53
- vsock: (workDir) => path.join(workDir, "vsock.sock")
56
+ /** Vsock directory for host-guest communication */
57
+ vsockDir: (workDir) => path.join(workDir, "vsock"),
58
+ /** Vsock UDS path for host-guest communication */
59
+ vsock: (workDir) => path.join(workDir, "vsock", "vsock.sock"),
60
+ /** Firecracker API socket (used with --api-sock) */
61
+ apiSock: (workDir) => path.join(workDir, "api.sock"),
62
+ /** Overlay filesystem for VM writes */
63
+ overlay: (workDir) => path.join(workDir, "overlay.ext4")
54
64
  };
55
65
  var tempPaths = {
56
66
  /** Default proxy CA directory */
@@ -325,15 +335,16 @@ async function subscribeToJobs(server, group, onJob, onConnectionChange) {
325
335
  };
326
336
  }
327
337
 
338
+ // src/lib/executor.ts
339
+ import fs9 from "fs";
340
+
328
341
  // src/lib/firecracker/vm.ts
329
342
  import { spawn } from "child_process";
330
343
  import fs4 from "fs";
331
344
  import readline from "readline";
332
345
 
333
346
  // src/lib/firecracker/netns-pool.ts
334
- import { exec } from "child_process";
335
347
  import path2 from "path";
336
- import { promisify } from "util";
337
348
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
338
349
  import { z as z3 } from "zod";
339
350
 
@@ -377,9 +388,25 @@ async function withFileLock(path7, fn, options) {
377
388
  }
378
389
  }
379
390
 
380
- // src/lib/firecracker/netns-pool.ts
391
+ // src/lib/utils/exec.ts
392
+ import { exec } from "child_process";
393
+ import { promisify } from "util";
381
394
  var execAsync = promisify(exec);
382
- var logger = createLogger("NetnsPool");
395
+ async function execCommand(cmd, sudo = true) {
396
+ const fullCmd = sudo ? `sudo ${cmd}` : cmd;
397
+ try {
398
+ const { stdout } = await execAsync(fullCmd);
399
+ return stdout.trim();
400
+ } catch (error) {
401
+ const execError = error;
402
+ throw new Error(
403
+ `Command failed: ${fullCmd}
404
+ ${execError.stderr || execError.message}`
405
+ );
406
+ }
407
+ }
408
+
409
+ // src/lib/firecracker/netns.ts
383
410
  var SNAPSHOT_NETWORK = {
384
411
  /** TAP device name inside namespace (must match Firecracker config) */
385
412
  tapName: "vm0-tap",
@@ -394,14 +421,28 @@ var SNAPSHOT_NETWORK = {
394
421
  /** CIDR prefix length (for ip commands) */
395
422
  prefixLen: 29
396
423
  };
397
- var VETH_NS = "veth0";
398
- var NS_PREFIX = "vm0-ns-";
399
- var VETH_PREFIX = "vm0-ve-";
400
- var VETH_IP_PREFIX = "10.200";
401
424
  function generateSnapshotNetworkBootArgs() {
402
425
  const { guestIp, gatewayIp, netmask } = SNAPSHOT_NETWORK;
403
426
  return `ip=${guestIp}::${gatewayIp}:${netmask}:vm0-guest:eth0:off`;
404
427
  }
428
+ async function createNetnsWithTap(nsName, tap) {
429
+ await execCommand(`ip netns add ${nsName}`);
430
+ await execCommand(
431
+ `ip netns exec ${nsName} ip tuntap add ${tap.tapName} mode tap`
432
+ );
433
+ await execCommand(
434
+ `ip netns exec ${nsName} ip addr add ${tap.gatewayIpWithPrefix} dev ${tap.tapName}`
435
+ );
436
+ await execCommand(`ip netns exec ${nsName} ip link set ${tap.tapName} up`);
437
+ await execCommand(`ip netns exec ${nsName} ip link set lo up`);
438
+ }
439
+
440
+ // src/lib/firecracker/netns-pool.ts
441
+ var logger = createLogger("NetnsPool");
442
+ var VETH_NS = "veth0";
443
+ var NS_PREFIX = "vm0-ns-";
444
+ var VETH_PREFIX = "vm0-ve-";
445
+ var VETH_IP_PREFIX = "10.200";
405
446
  var MAX_RUNNERS = 64;
406
447
  var MAX_NAMESPACES_PER_RUNNER = 256;
407
448
  var NamespaceEntrySchema = z3.object({
@@ -459,19 +500,6 @@ var RegistryFile = class {
459
500
  });
460
501
  }
461
502
  };
462
- async function execCommand(cmd, sudo = true) {
463
- const fullCmd = sudo ? `sudo ${cmd}` : cmd;
464
- try {
465
- const { stdout } = await execAsync(fullCmd);
466
- return stdout.trim();
467
- } catch (error) {
468
- const execError = error;
469
- throw new Error(
470
- `Command failed: ${fullCmd}
471
- ${execError.stderr || execError.message}`
472
- );
473
- }
474
- }
475
503
  async function getDefaultInterface() {
476
504
  const result = await execCommand("ip route get 8.8.8.8", false);
477
505
  const match = result.match(/dev\s+(\S+)/);
@@ -694,16 +722,10 @@ var NetnsPool = class _NetnsPool {
694
722
  });
695
723
  logger.log(`Creating namespace ${name}...`);
696
724
  try {
697
- await execCommand(`ip netns add ${name}`);
698
- await execCommand(
699
- `ip netns exec ${name} ip tuntap add ${SNAPSHOT_NETWORK.tapName} mode tap`
700
- );
701
- await execCommand(
702
- `ip netns exec ${name} ip addr add ${SNAPSHOT_NETWORK.gatewayIp}/${SNAPSHOT_NETWORK.prefixLen} dev ${SNAPSHOT_NETWORK.tapName}`
703
- );
704
- await execCommand(
705
- `ip netns exec ${name} ip link set ${SNAPSHOT_NETWORK.tapName} up`
706
- );
725
+ await createNetnsWithTap(name, {
726
+ tapName: SNAPSHOT_NETWORK.tapName,
727
+ gatewayIpWithPrefix: `${SNAPSHOT_NETWORK.gatewayIp}/${SNAPSHOT_NETWORK.prefixLen}`
728
+ });
707
729
  await execCommand(
708
730
  `ip link add ${vethHost} type veth peer name ${VETH_NS} netns ${name}`
709
731
  );
@@ -711,7 +733,6 @@ var NetnsPool = class _NetnsPool {
711
733
  `ip netns exec ${name} ip addr add ${vethNsIp}/30 dev ${VETH_NS}`
712
734
  );
713
735
  await execCommand(`ip netns exec ${name} ip link set ${VETH_NS} up`);
714
- await execCommand(`ip netns exec ${name} ip link set lo up`);
715
736
  await execCommand(`ip addr add ${vethHostIp}/30 dev ${vethHost}`);
716
737
  await execCommand(`ip link set ${vethHost} up`);
717
738
  await execCommand(
@@ -878,7 +899,7 @@ import { promisify as promisify2 } from "util";
878
899
  var execAsync2 = promisify2(exec2);
879
900
  var logger2 = createLogger("OverlayPool");
880
901
  var OVERLAY_SIZE = 2 * 1024 * 1024 * 1024;
881
- async function defaultCreateFile(filePath) {
902
+ async function createOverlayFile(filePath) {
882
903
  const fd = fs2.openSync(filePath, "w");
883
904
  fs2.ftruncateSync(fd, OVERLAY_SIZE);
884
905
  fs2.closeSync(fd);
@@ -894,7 +915,7 @@ var OverlayPool = class {
894
915
  size: config.size,
895
916
  replenishThreshold: config.replenishThreshold,
896
917
  poolDir: config.poolDir,
897
- createFile: config.createFile ?? defaultCreateFile
918
+ createFile: config.createFile ?? createOverlayFile
898
919
  };
899
920
  }
900
921
  /**
@@ -903,19 +924,6 @@ var OverlayPool = class {
903
924
  generateFileName() {
904
925
  return `overlay-${randomUUID()}.ext4`;
905
926
  }
906
- /**
907
- * Ensure the pool directory exists
908
- */
909
- async ensurePoolDir() {
910
- const parentDir = path3.dirname(this.config.poolDir);
911
- if (!fs2.existsSync(parentDir)) {
912
- await execAsync2(`sudo mkdir -p ${parentDir}`);
913
- await execAsync2(`sudo chmod 777 ${parentDir}`);
914
- }
915
- if (!fs2.existsSync(this.config.poolDir)) {
916
- fs2.mkdirSync(this.config.poolDir, { recursive: true });
917
- }
918
- }
919
927
  /**
920
928
  * Scan pool directory for overlay files
921
929
  */
@@ -969,7 +977,7 @@ var OverlayPool = class {
969
977
  logger2.log(
970
978
  `Initializing overlay pool (size=${this.config.size}, threshold=${this.config.replenishThreshold})...`
971
979
  );
972
- await this.ensurePoolDir();
980
+ fs2.mkdirSync(this.config.poolDir, { recursive: true });
973
981
  const existing = this.scanPoolDir();
974
982
  if (existing.length > 0) {
975
983
  logger2.log(`Cleaning up ${existing.length} stale overlay(s)`);
@@ -1081,6 +1089,36 @@ var FirecrackerClient = class {
1081
1089
  constructor(socketPath) {
1082
1090
  this.socketPath = socketPath;
1083
1091
  }
1092
+ /**
1093
+ * Configure machine settings (vCPUs, memory)
1094
+ */
1095
+ async configureMachine(config) {
1096
+ await this.put("/machine-config", config);
1097
+ }
1098
+ /**
1099
+ * Configure boot source (kernel, boot args)
1100
+ */
1101
+ async configureBootSource(config) {
1102
+ await this.put("/boot-source", config);
1103
+ }
1104
+ /**
1105
+ * Configure network interface
1106
+ */
1107
+ async configureNetworkInterface(config) {
1108
+ await this.put(`/network-interfaces/${config.iface_id}`, config);
1109
+ }
1110
+ /**
1111
+ * Configure vsock device
1112
+ */
1113
+ async configureVsock(config) {
1114
+ await this.put("/vsock", config);
1115
+ }
1116
+ /**
1117
+ * Start the VM instance
1118
+ */
1119
+ async startInstance() {
1120
+ await this.put("/actions", { action_type: "InstanceStart" });
1121
+ }
1084
1122
  /**
1085
1123
  * Pause the VM
1086
1124
  *
@@ -1177,17 +1215,27 @@ var FirecrackerClient = class {
1177
1215
  */
1178
1216
  request(method, path7, body, timeoutMs = 3e4) {
1179
1217
  return new Promise((resolve, reject) => {
1218
+ const bodyStr = body !== void 0 ? JSON.stringify(body) : void 0;
1219
+ const headers = {
1220
+ Accept: "application/json",
1221
+ // Disable keep-alive to prevent pipelining issues
1222
+ Connection: "close"
1223
+ };
1224
+ if (bodyStr !== void 0) {
1225
+ headers["Content-Type"] = "application/json";
1226
+ headers["Content-Length"] = Buffer.byteLength(bodyStr);
1227
+ }
1180
1228
  const options = {
1181
1229
  socketPath: this.socketPath,
1182
- path: `http://localhost${path7}`,
1230
+ path: path7,
1183
1231
  method,
1184
- headers: {
1185
- "Content-Type": "application/json",
1186
- Accept: "application/json"
1187
- },
1188
- timeout: timeoutMs
1232
+ headers,
1233
+ timeout: timeoutMs,
1234
+ // Disable agent to ensure fresh connection for each request
1235
+ // Firecracker's single-threaded API can have issues with pipelined requests
1236
+ agent: false
1189
1237
  };
1190
- logger3.log(`${method} ${path7}${body ? " " + JSON.stringify(body) : ""}`);
1238
+ logger3.log(`${method} ${path7}${bodyStr ? " " + bodyStr : ""}`);
1191
1239
  const req = http.request(options, (res) => {
1192
1240
  let data = "";
1193
1241
  res.on("data", (chunk) => {
@@ -1217,8 +1265,8 @@ var FirecrackerClient = class {
1217
1265
  req.on("error", (err) => {
1218
1266
  reject(err);
1219
1267
  });
1220
- if (body) {
1221
- req.write(JSON.stringify(body));
1268
+ if (bodyStr !== void 0) {
1269
+ req.write(bodyStr);
1222
1270
  }
1223
1271
  req.end();
1224
1272
  });
@@ -1231,6 +1279,58 @@ var FirecrackerClient = class {
1231
1279
  }
1232
1280
  };
1233
1281
 
1282
+ // src/lib/firecracker/config.ts
1283
+ function buildBootArgs() {
1284
+ const networkBootArgs = generateSnapshotNetworkBootArgs();
1285
+ return `console=ttyS0 reboot=k panic=1 pci=off nomodules random.trust_cpu=on quiet loglevel=0 nokaslr audit=0 numa=off mitigations=off noresume init=/sbin/vm-init ${networkBootArgs}`;
1286
+ }
1287
+ function buildFirecrackerConfig(params) {
1288
+ const bootArgs = buildBootArgs();
1289
+ return {
1290
+ "boot-source": {
1291
+ kernel_image_path: params.kernelPath,
1292
+ boot_args: bootArgs
1293
+ },
1294
+ drives: [
1295
+ // Base drive (squashfs, read-only, shared across VMs)
1296
+ // Mounted as /dev/vda inside the VM
1297
+ {
1298
+ drive_id: "rootfs",
1299
+ path_on_host: params.rootfsPath,
1300
+ is_root_device: true,
1301
+ is_read_only: true
1302
+ },
1303
+ // Overlay drive (ext4, read-write, per-VM)
1304
+ // Mounted as /dev/vdb inside the VM
1305
+ // The vm-init script combines these using overlayfs
1306
+ {
1307
+ drive_id: "overlay",
1308
+ path_on_host: params.overlayPath,
1309
+ is_root_device: false,
1310
+ is_read_only: false
1311
+ }
1312
+ ],
1313
+ "machine-config": {
1314
+ vcpu_count: params.vcpus,
1315
+ mem_size_mib: params.memoryMb
1316
+ },
1317
+ "network-interfaces": [
1318
+ {
1319
+ // Network interface uses fixed config from SNAPSHOT_NETWORK
1320
+ // TAP device is inside the namespace, created by netns-pool
1321
+ iface_id: "eth0",
1322
+ guest_mac: SNAPSHOT_NETWORK.guestMac,
1323
+ host_dev_name: SNAPSHOT_NETWORK.tapName
1324
+ }
1325
+ ],
1326
+ // Guest CID 3 is the standard guest identifier (CID 0=hypervisor, 1=local, 2=host)
1327
+ vsock: {
1328
+ guest_cid: 3,
1329
+ uds_path: params.vsockPath
1330
+ }
1331
+ };
1332
+ }
1333
+
1234
1334
  // src/lib/firecracker/vm.ts
1235
1335
  var logger4 = createLogger("VM");
1236
1336
  var FirecrackerVM = class {
@@ -1295,7 +1395,6 @@ var FirecrackerVM = class {
1295
1395
  throw new Error(`Cannot start VM in state: ${this.state}`);
1296
1396
  }
1297
1397
  try {
1298
- fs4.mkdirSync(this.workDir, { recursive: true });
1299
1398
  logger4.log(`[VM ${this.config.vmId}] Acquiring resources...`);
1300
1399
  const results = await Promise.allSettled([
1301
1400
  acquireOverlay(),
@@ -1471,74 +1570,23 @@ var FirecrackerVM = class {
1471
1570
  }
1472
1571
  /**
1473
1572
  * Build Firecracker configuration object
1474
- *
1475
- * Creates the JSON configuration for Firecracker's --config-file option.
1476
- * Boot args:
1477
- * - console=ttyS0: serial console output
1478
- * - reboot=k: use keyboard controller for reboot
1479
- * - panic=1: reboot after 1 second on kernel panic
1480
- * - pci=off: disable PCI bus (not needed in microVM)
1481
- * - nomodules: skip module loading (not needed in microVM)
1482
- * - random.trust_cpu=on: trust CPU RNG, skip entropy wait
1483
- * - quiet loglevel=0: minimize kernel log output
1484
- * - nokaslr: disable kernel address space randomization
1485
- * - audit=0: disable kernel auditing
1486
- * - numa=off: disable NUMA (single node)
1487
- * - mitigations=off: disable CPU vulnerability mitigations
1488
- * - noresume: skip hibernation resume check
1489
- * - init=/sbin/vm-init: use vm-init (Rust binary) for filesystem setup and vsock-agent
1490
- * - ip=...: network configuration (fixed IPs from SNAPSHOT_NETWORK)
1491
1573
  */
1492
1574
  buildConfig() {
1493
1575
  if (!this.netns || !this.vmOverlayPath) {
1494
1576
  throw new Error("VM not properly initialized");
1495
1577
  }
1496
- const networkBootArgs = generateSnapshotNetworkBootArgs();
1497
- const bootArgs = `console=ttyS0 reboot=k panic=1 pci=off nomodules random.trust_cpu=on quiet loglevel=0 nokaslr audit=0 numa=off mitigations=off noresume init=/sbin/vm-init ${networkBootArgs}`;
1498
- logger4.log(`[VM ${this.config.vmId}] Boot args: ${bootArgs}`);
1499
- return {
1500
- "boot-source": {
1501
- kernel_image_path: this.config.kernelPath,
1502
- boot_args: bootArgs
1503
- },
1504
- drives: [
1505
- // Base drive (squashfs, read-only, shared across VMs)
1506
- // Mounted as /dev/vda inside the VM
1507
- {
1508
- drive_id: "rootfs",
1509
- path_on_host: this.config.rootfsPath,
1510
- is_root_device: true,
1511
- is_read_only: true
1512
- },
1513
- // Overlay drive (ext4, read-write, per-VM)
1514
- // Mounted as /dev/vdb inside the VM
1515
- // The vm-init script combines these using overlayfs
1516
- {
1517
- drive_id: "overlay",
1518
- path_on_host: this.vmOverlayPath,
1519
- is_root_device: false,
1520
- is_read_only: false
1521
- }
1522
- ],
1523
- "machine-config": {
1524
- vcpu_count: this.config.vcpus,
1525
- mem_size_mib: this.config.memoryMb
1526
- },
1527
- "network-interfaces": [
1528
- {
1529
- // Network interface uses fixed config from SNAPSHOT_NETWORK
1530
- // TAP device is inside the namespace, created by netns-pool
1531
- iface_id: "eth0",
1532
- guest_mac: SNAPSHOT_NETWORK.guestMac,
1533
- host_dev_name: SNAPSHOT_NETWORK.tapName
1534
- }
1535
- ],
1536
- // Guest CID 3 is the standard guest identifier (CID 0=hypervisor, 1=local, 2=host)
1537
- vsock: {
1538
- guest_cid: 3,
1539
- uds_path: this.vsockPath
1540
- }
1541
- };
1578
+ const config = buildFirecrackerConfig({
1579
+ kernelPath: this.config.kernelPath,
1580
+ rootfsPath: this.config.rootfsPath,
1581
+ overlayPath: this.vmOverlayPath,
1582
+ vsockPath: this.vsockPath,
1583
+ vcpus: this.config.vcpus,
1584
+ memoryMb: this.config.memoryMb
1585
+ });
1586
+ logger4.log(
1587
+ `[VM ${this.config.vmId}] Boot args: ${config["boot-source"].boot_args}`
1588
+ );
1589
+ return config;
1542
1590
  }
1543
1591
  /**
1544
1592
  * Stop the VM
@@ -7666,6 +7714,7 @@ var modelProviderTypeSchema = z18.enum([
7666
7714
  "openrouter-api-key",
7667
7715
  "moonshot-api-key",
7668
7716
  "minimax-api-key",
7717
+ "deepseek-api-key",
7669
7718
  "aws-bedrock"
7670
7719
  ]);
7671
7720
  var modelProviderFrameworkSchema = z18.enum(["claude-code", "codex"]);
@@ -8931,7 +8980,7 @@ var FEATURE_SWITCHES = {
8931
8980
  },
8932
8981
  ["platformAgents" /* PlatformAgents */]: {
8933
8982
  maintainer: "ethan@vm0.ai",
8934
- enabled: false
8983
+ enabled: true
8935
8984
  },
8936
8985
  ["platformSecrets" /* PlatformSecrets */]: {
8937
8986
  maintainer: "ethan@vm0.ai",
@@ -10039,6 +10088,13 @@ async function executeJob(context, config, options = {}) {
10039
10088
  firecrackerBinary: config.firecracker.binary,
10040
10089
  workDir: runnerPaths.vmWorkDir(config.base_dir, vmId)
10041
10090
  };
10091
+ fs9.mkdirSync(vmConfig.workDir, { recursive: true });
10092
+ fs9.mkdirSync(vmPaths.vsockDir(vmConfig.workDir), { recursive: true });
10093
+ const vsockPath = vmPaths.vsock(vmConfig.workDir);
10094
+ vsockClient = new VsockClient(vsockPath);
10095
+ const guest = vsockClient;
10096
+ logger9.log(`Starting vsock listener: ${vsockPath}`);
10097
+ const guestConnectionPromise = guest.waitForGuestConnection(3e4);
10042
10098
  logger9.log(`Creating VM ${vmId}...`);
10043
10099
  vm = new FirecrackerVM(vmConfig);
10044
10100
  await withSandboxTiming("vm_create", () => vm.start());
@@ -10050,10 +10106,6 @@ async function executeJob(context, config, options = {}) {
10050
10106
  logger9.log(
10051
10107
  `VM ${vmId} started, guest IP: ${guestIp}, veth NS IP: ${vethNsIp}`
10052
10108
  );
10053
- const vsockPath = vm.getVsockPath();
10054
- vsockClient = new VsockClient(vsockPath);
10055
- const guest = vsockClient;
10056
- logger9.log(`Using vsock for guest communication: ${vsockPath}`);
10057
10109
  const envJson = JSON.stringify(
10058
10110
  buildEnvironmentVariables(context, config.server.url)
10059
10111
  );
@@ -10071,10 +10123,7 @@ async function executeJob(context, config, options = {}) {
10071
10123
  });
10072
10124
  }
10073
10125
  logger9.log(`Waiting for guest connection...`);
10074
- await withSandboxTiming(
10075
- "guest_wait",
10076
- () => guest.waitForGuestConnection(3e4)
10077
- );
10126
+ await withSandboxTiming("guest_wait", () => guestConnectionPromise);
10078
10127
  logger9.log(`Guest client ready`);
10079
10128
  if (context.storageManifest) {
10080
10129
  await withSandboxTiming(
@@ -10277,24 +10326,11 @@ async function isPortInUse(port) {
10277
10326
  }
10278
10327
 
10279
10328
  // src/lib/runner/runner-lock.ts
10280
- import { exec as exec4 } from "child_process";
10281
- import fs9 from "fs";
10329
+ import fs10 from "fs";
10282
10330
  import path5 from "path";
10283
- import { promisify as promisify4 } from "util";
10284
- var execAsync4 = promisify4(exec4);
10285
10331
  var logger11 = createLogger("RunnerLock");
10286
10332
  var DEFAULT_PID_FILE = runtimePaths.runnerPid;
10287
10333
  var currentPidFile = null;
10288
- async function ensureRunDir(dirPath, skipSudo) {
10289
- if (!fs9.existsSync(dirPath)) {
10290
- if (skipSudo) {
10291
- fs9.mkdirSync(dirPath, { recursive: true });
10292
- } else {
10293
- await execAsync4(`sudo mkdir -p ${dirPath}`);
10294
- await execAsync4(`sudo chmod 777 ${dirPath}`);
10295
- }
10296
- }
10297
- }
10298
10334
  function isProcessRunning(pid) {
10299
10335
  try {
10300
10336
  process.kill(pid, 0);
@@ -10306,13 +10342,12 @@ function isProcessRunning(pid) {
10306
10342
  return false;
10307
10343
  }
10308
10344
  }
10309
- async function acquireRunnerLock(options = {}) {
10345
+ function acquireRunnerLock(options = {}) {
10310
10346
  const pidFile = options.pidFile ?? DEFAULT_PID_FILE;
10311
- const skipSudo = options.skipSudo ?? false;
10312
10347
  const runDir = path5.dirname(pidFile);
10313
- await ensureRunDir(runDir, skipSudo);
10314
- if (fs9.existsSync(pidFile)) {
10315
- const pidStr = fs9.readFileSync(pidFile, "utf-8").trim();
10348
+ fs10.mkdirSync(runDir, { recursive: true });
10349
+ if (fs10.existsSync(pidFile)) {
10350
+ const pidStr = fs10.readFileSync(pidFile, "utf-8").trim();
10316
10351
  const pid = parseInt(pidStr, 10);
10317
10352
  if (!isNaN(pid) && isProcessRunning(pid)) {
10318
10353
  logger11.error(`Error: Another runner is already running (PID ${pid})`);
@@ -10324,16 +10359,16 @@ async function acquireRunnerLock(options = {}) {
10324
10359
  } else {
10325
10360
  logger11.log(`Cleaning up stale PID file (PID ${pid} not running)`);
10326
10361
  }
10327
- fs9.unlinkSync(pidFile);
10362
+ fs10.unlinkSync(pidFile);
10328
10363
  }
10329
- fs9.writeFileSync(pidFile, process.pid.toString());
10364
+ fs10.writeFileSync(pidFile, process.pid.toString());
10330
10365
  currentPidFile = pidFile;
10331
10366
  logger11.log(`Runner lock acquired (PID ${process.pid})`);
10332
10367
  }
10333
10368
  function releaseRunnerLock() {
10334
10369
  const pidFile = currentPidFile ?? DEFAULT_PID_FILE;
10335
- if (fs9.existsSync(pidFile)) {
10336
- fs9.unlinkSync(pidFile);
10370
+ if (fs10.existsSync(pidFile)) {
10371
+ fs10.unlinkSync(pidFile);
10337
10372
  logger11.log("Runner lock released");
10338
10373
  }
10339
10374
  currentPidFile = null;
@@ -11205,7 +11240,7 @@ var benchmarkCommand = new Command4("benchmark").description(
11205
11240
  });
11206
11241
 
11207
11242
  // src/index.ts
11208
- var version = true ? "3.11.0" : "0.1.0";
11243
+ var version = true ? "3.11.2" : "0.1.0";
11209
11244
  program.name("vm0-runner").version(version).description("Self-hosted runner for VM0 agents");
11210
11245
  program.addCommand(startCommand);
11211
11246
  program.addCommand(doctorCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vm0/runner",
3
- "version": "3.11.0",
3
+ "version": "3.11.2",
4
4
  "description": "Self-hosted runner for VM0 agents",
5
5
  "repository": {
6
6
  "type": "git",