@vm0/runner 3.0.6 → 3.1.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 +66 -55
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -192,10 +192,11 @@ async function completeJob(apiUrl, context, exitCode, error) {
|
|
|
192
192
|
import path4 from "path";
|
|
193
193
|
|
|
194
194
|
// src/lib/firecracker/vm.ts
|
|
195
|
-
import {
|
|
195
|
+
import { exec as exec3, spawn } from "child_process";
|
|
196
196
|
import fs3 from "fs";
|
|
197
197
|
import path2 from "path";
|
|
198
198
|
import readline from "readline";
|
|
199
|
+
import { promisify as promisify3 } from "util";
|
|
199
200
|
|
|
200
201
|
// src/lib/firecracker/client.ts
|
|
201
202
|
import http from "http";
|
|
@@ -706,23 +707,24 @@ async function clearStaleIptablesRulesForIP(ip) {
|
|
|
706
707
|
} catch {
|
|
707
708
|
}
|
|
708
709
|
}
|
|
709
|
-
async function createTapDevice(vmId) {
|
|
710
|
+
async function createTapDevice(vmId, logger) {
|
|
711
|
+
const log = logger ?? console.log;
|
|
710
712
|
const tapDevice = `tap${vmId.substring(0, 8)}`;
|
|
711
713
|
const guestMac = generateMacAddress(vmId);
|
|
712
714
|
const guestIp = await allocateIP(vmId);
|
|
715
|
+
log(`[VM ${vmId}] IP allocated: ${guestIp}`);
|
|
713
716
|
await clearStaleIptablesRulesForIP(guestIp);
|
|
714
|
-
|
|
715
|
-
await setupBridge();
|
|
717
|
+
log(`[VM ${vmId}] Stale iptables cleared`);
|
|
716
718
|
if (await tapDeviceExists(tapDevice)) {
|
|
717
|
-
|
|
719
|
+
log(`[VM ${vmId}] TAP device ${tapDevice} already exists, deleting...`);
|
|
718
720
|
await deleteTapDevice(tapDevice);
|
|
719
721
|
}
|
|
720
722
|
await execCommand(`ip tuntap add ${tapDevice} mode tap`);
|
|
723
|
+
log(`[VM ${vmId}] TAP device created`);
|
|
721
724
|
await execCommand(`ip link set ${tapDevice} master ${BRIDGE_NAME2}`);
|
|
725
|
+
log(`[VM ${vmId}] TAP added to bridge`);
|
|
722
726
|
await execCommand(`ip link set ${tapDevice} up`);
|
|
723
|
-
|
|
724
|
-
`TAP ${tapDevice} created: guest MAC ${guestMac}, guest IP ${guestIp}`
|
|
725
|
-
);
|
|
727
|
+
log(`[VM ${vmId}] TAP created: ${tapDevice}, MAC ${guestMac}, IP ${guestIp}`);
|
|
726
728
|
return {
|
|
727
729
|
tapDevice,
|
|
728
730
|
guestMac,
|
|
@@ -959,6 +961,7 @@ async function cleanupOrphanedProxyRules(runnerName) {
|
|
|
959
961
|
}
|
|
960
962
|
|
|
961
963
|
// src/lib/firecracker/vm.ts
|
|
964
|
+
var execAsync3 = promisify3(exec3);
|
|
962
965
|
var FirecrackerVM = class {
|
|
963
966
|
config;
|
|
964
967
|
process = null;
|
|
@@ -978,6 +981,9 @@ var FirecrackerVM = class {
|
|
|
978
981
|
this.vmOverlayPath = path2.join(this.workDir, "overlay.ext4");
|
|
979
982
|
this.vsockPath = path2.join(this.workDir, "vsock.sock");
|
|
980
983
|
}
|
|
984
|
+
log(msg) {
|
|
985
|
+
(this.config.logger ?? console.log)(msg);
|
|
986
|
+
}
|
|
981
987
|
/**
|
|
982
988
|
* Get current VM state
|
|
983
989
|
*/
|
|
@@ -1021,15 +1027,21 @@ var FirecrackerVM = class {
|
|
|
1021
1027
|
if (fs3.existsSync(this.socketPath)) {
|
|
1022
1028
|
fs3.unlinkSync(this.socketPath);
|
|
1023
1029
|
}
|
|
1024
|
-
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1030
|
+
this.log(`[VM ${this.config.vmId}] Setting up overlay and network...`);
|
|
1031
|
+
const createOverlay = async () => {
|
|
1032
|
+
const overlaySize = 2 * 1024 * 1024 * 1024;
|
|
1033
|
+
const fd = fs3.openSync(this.vmOverlayPath, "w");
|
|
1034
|
+
fs3.ftruncateSync(fd, overlaySize);
|
|
1035
|
+
fs3.closeSync(fd);
|
|
1036
|
+
await execAsync3(`mkfs.ext4 -F -q "${this.vmOverlayPath}"`);
|
|
1037
|
+
this.log(`[VM ${this.config.vmId}] Overlay created`);
|
|
1038
|
+
};
|
|
1039
|
+
const [, networkConfig] = await Promise.all([
|
|
1040
|
+
createOverlay(),
|
|
1041
|
+
createTapDevice(this.config.vmId, this.log.bind(this))
|
|
1042
|
+
]);
|
|
1043
|
+
this.networkConfig = networkConfig;
|
|
1044
|
+
this.log(`[VM ${this.config.vmId}] Starting Firecracker...`);
|
|
1033
1045
|
this.process = spawn(
|
|
1034
1046
|
this.config.firecrackerBinary,
|
|
1035
1047
|
["--api-sock", this.socketPath],
|
|
@@ -1040,11 +1052,11 @@ var FirecrackerVM = class {
|
|
|
1040
1052
|
}
|
|
1041
1053
|
);
|
|
1042
1054
|
this.process.on("error", (err) => {
|
|
1043
|
-
|
|
1055
|
+
this.log(`[VM ${this.config.vmId}] Firecracker error: ${err}`);
|
|
1044
1056
|
this.state = "error";
|
|
1045
1057
|
});
|
|
1046
1058
|
this.process.on("exit", (code, signal) => {
|
|
1047
|
-
|
|
1059
|
+
this.log(
|
|
1048
1060
|
`[VM ${this.config.vmId}] Firecracker exited: code=${code}, signal=${signal}`
|
|
1049
1061
|
);
|
|
1050
1062
|
if (this.state !== "stopped") {
|
|
@@ -1057,7 +1069,7 @@ var FirecrackerVM = class {
|
|
|
1057
1069
|
});
|
|
1058
1070
|
stdoutRL.on("line", (line) => {
|
|
1059
1071
|
if (line.trim()) {
|
|
1060
|
-
|
|
1072
|
+
this.log(`[VM ${this.config.vmId}] ${line}`);
|
|
1061
1073
|
}
|
|
1062
1074
|
});
|
|
1063
1075
|
}
|
|
@@ -1067,19 +1079,19 @@ var FirecrackerVM = class {
|
|
|
1067
1079
|
});
|
|
1068
1080
|
stderrRL.on("line", (line) => {
|
|
1069
1081
|
if (line.trim()) {
|
|
1070
|
-
|
|
1082
|
+
this.log(`[VM ${this.config.vmId}] stderr: ${line}`);
|
|
1071
1083
|
}
|
|
1072
1084
|
});
|
|
1073
1085
|
}
|
|
1074
1086
|
this.client = new FirecrackerClient(this.socketPath);
|
|
1075
|
-
|
|
1087
|
+
this.log(`[VM ${this.config.vmId}] Waiting for API...`);
|
|
1076
1088
|
await this.client.waitUntilReady(1e4, 100);
|
|
1077
1089
|
this.state = "configuring";
|
|
1078
1090
|
await this.configure();
|
|
1079
|
-
|
|
1091
|
+
this.log(`[VM ${this.config.vmId}] Booting...`);
|
|
1080
1092
|
await this.client.start();
|
|
1081
1093
|
this.state = "running";
|
|
1082
|
-
|
|
1094
|
+
this.log(
|
|
1083
1095
|
`[VM ${this.config.vmId}] Running at ${this.networkConfig.guestIp}`
|
|
1084
1096
|
);
|
|
1085
1097
|
} catch (error) {
|
|
@@ -1095,7 +1107,7 @@ var FirecrackerVM = class {
|
|
|
1095
1107
|
if (!this.client || !this.networkConfig) {
|
|
1096
1108
|
throw new Error("VM not properly initialized");
|
|
1097
1109
|
}
|
|
1098
|
-
|
|
1110
|
+
this.log(
|
|
1099
1111
|
`[VM ${this.config.vmId}] Configuring: ${this.config.vcpus} vCPUs, ${this.config.memoryMb}MB RAM`
|
|
1100
1112
|
);
|
|
1101
1113
|
await this.client.setMachineConfig({
|
|
@@ -1104,28 +1116,26 @@ var FirecrackerVM = class {
|
|
|
1104
1116
|
});
|
|
1105
1117
|
const networkBootArgs = generateNetworkBootArgs(this.networkConfig);
|
|
1106
1118
|
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}`;
|
|
1107
|
-
|
|
1119
|
+
this.log(`[VM ${this.config.vmId}] Boot args: ${bootArgs}`);
|
|
1108
1120
|
await this.client.setBootSource({
|
|
1109
1121
|
kernel_image_path: this.config.kernelPath,
|
|
1110
1122
|
boot_args: bootArgs
|
|
1111
1123
|
});
|
|
1112
|
-
|
|
1113
|
-
`[VM ${this.config.vmId}] Base rootfs: ${this.config.rootfsPath}`
|
|
1114
|
-
);
|
|
1124
|
+
this.log(`[VM ${this.config.vmId}] Base rootfs: ${this.config.rootfsPath}`);
|
|
1115
1125
|
await this.client.setDrive({
|
|
1116
1126
|
drive_id: "rootfs",
|
|
1117
1127
|
path_on_host: this.config.rootfsPath,
|
|
1118
1128
|
is_root_device: true,
|
|
1119
1129
|
is_read_only: true
|
|
1120
1130
|
});
|
|
1121
|
-
|
|
1131
|
+
this.log(`[VM ${this.config.vmId}] Overlay: ${this.vmOverlayPath}`);
|
|
1122
1132
|
await this.client.setDrive({
|
|
1123
1133
|
drive_id: "overlay",
|
|
1124
1134
|
path_on_host: this.vmOverlayPath,
|
|
1125
1135
|
is_root_device: false,
|
|
1126
1136
|
is_read_only: false
|
|
1127
1137
|
});
|
|
1128
|
-
|
|
1138
|
+
this.log(
|
|
1129
1139
|
`[VM ${this.config.vmId}] Network: ${this.networkConfig.tapDevice}`
|
|
1130
1140
|
);
|
|
1131
1141
|
await this.client.setNetworkInterface({
|
|
@@ -1133,7 +1143,7 @@ var FirecrackerVM = class {
|
|
|
1133
1143
|
guest_mac: this.networkConfig.guestMac,
|
|
1134
1144
|
host_dev_name: this.networkConfig.tapDevice
|
|
1135
1145
|
});
|
|
1136
|
-
|
|
1146
|
+
this.log(`[VM ${this.config.vmId}] Vsock: ${this.vsockPath}`);
|
|
1137
1147
|
await this.client.setVsock({
|
|
1138
1148
|
vsock_id: "vsock0",
|
|
1139
1149
|
guest_cid: 3,
|
|
@@ -1145,17 +1155,16 @@ var FirecrackerVM = class {
|
|
|
1145
1155
|
*/
|
|
1146
1156
|
async stop() {
|
|
1147
1157
|
if (this.state !== "running") {
|
|
1148
|
-
|
|
1158
|
+
this.log(`[VM ${this.config.vmId}] Not running, state: ${this.state}`);
|
|
1149
1159
|
return;
|
|
1150
1160
|
}
|
|
1151
1161
|
this.state = "stopping";
|
|
1152
|
-
|
|
1162
|
+
this.log(`[VM ${this.config.vmId}] Stopping...`);
|
|
1153
1163
|
try {
|
|
1154
1164
|
if (this.client) {
|
|
1155
1165
|
await this.client.sendCtrlAltDel().catch((error) => {
|
|
1156
|
-
|
|
1157
|
-
`[VM ${this.config.vmId}] Graceful shutdown signal failed (VM may already be stopping)
|
|
1158
|
-
error instanceof Error ? error.message : error
|
|
1166
|
+
this.log(
|
|
1167
|
+
`[VM ${this.config.vmId}] Graceful shutdown signal failed (VM may already be stopping): ${error instanceof Error ? error.message : error}`
|
|
1159
1168
|
);
|
|
1160
1169
|
});
|
|
1161
1170
|
}
|
|
@@ -1167,7 +1176,7 @@ var FirecrackerVM = class {
|
|
|
1167
1176
|
* Force kill the VM
|
|
1168
1177
|
*/
|
|
1169
1178
|
async kill() {
|
|
1170
|
-
|
|
1179
|
+
this.log(`[VM ${this.config.vmId}] Force killing...`);
|
|
1171
1180
|
await this.cleanup();
|
|
1172
1181
|
}
|
|
1173
1182
|
/**
|
|
@@ -1192,7 +1201,7 @@ var FirecrackerVM = class {
|
|
|
1192
1201
|
}
|
|
1193
1202
|
this.client = null;
|
|
1194
1203
|
this.state = "stopped";
|
|
1195
|
-
|
|
1204
|
+
this.log(`[VM ${this.config.vmId}] Stopped`);
|
|
1196
1205
|
}
|
|
1197
1206
|
/**
|
|
1198
1207
|
* Wait for the VM process to exit
|
|
@@ -5626,11 +5635,6 @@ var appStringSchema = z6.string().superRefine((val, ctx) => {
|
|
|
5626
5635
|
});
|
|
5627
5636
|
var agentDefinitionSchema = z6.object({
|
|
5628
5637
|
description: z6.string().optional(),
|
|
5629
|
-
/**
|
|
5630
|
-
* @deprecated Use `apps` field instead for pre-installed tools.
|
|
5631
|
-
* This field will be removed in a future version.
|
|
5632
|
-
*/
|
|
5633
|
-
image: z6.string().optional(),
|
|
5634
5638
|
framework: z6.string().min(1, "Framework is required"),
|
|
5635
5639
|
/**
|
|
5636
5640
|
* Array of pre-installed apps/tools for the agent environment.
|
|
@@ -5640,8 +5644,6 @@ var agentDefinitionSchema = z6.object({
|
|
|
5640
5644
|
*/
|
|
5641
5645
|
apps: z6.array(appStringSchema).optional(),
|
|
5642
5646
|
volumes: z6.array(z6.string()).optional(),
|
|
5643
|
-
working_dir: z6.string().optional(),
|
|
5644
|
-
// Optional when provider supports auto-config
|
|
5645
5647
|
environment: z6.record(z6.string(), z6.string()).optional(),
|
|
5646
5648
|
/**
|
|
5647
5649
|
* Path to instructions file (e.g., AGENTS.md).
|
|
@@ -5668,7 +5670,17 @@ var agentDefinitionSchema = z6.object({
|
|
|
5668
5670
|
* Requires experimental_runner to be configured.
|
|
5669
5671
|
* When enabled, filters outbound traffic by domain/IP rules.
|
|
5670
5672
|
*/
|
|
5671
|
-
experimental_firewall: experimentalFirewallSchema.optional()
|
|
5673
|
+
experimental_firewall: experimentalFirewallSchema.optional(),
|
|
5674
|
+
/**
|
|
5675
|
+
* @deprecated Server-resolved field. User input is ignored.
|
|
5676
|
+
* @internal
|
|
5677
|
+
*/
|
|
5678
|
+
image: z6.string().optional(),
|
|
5679
|
+
/**
|
|
5680
|
+
* @deprecated Server-resolved field. User input is ignored.
|
|
5681
|
+
* @internal
|
|
5682
|
+
*/
|
|
5683
|
+
working_dir: z6.string().optional()
|
|
5672
5684
|
});
|
|
5673
5685
|
var agentComposeContentSchema = z6.object({
|
|
5674
5686
|
version: z6.string().min(1, "Version is required"),
|
|
@@ -6800,13 +6812,11 @@ var scopeResponseSchema = z14.object({
|
|
|
6800
6812
|
id: z14.string().uuid(),
|
|
6801
6813
|
slug: z14.string(),
|
|
6802
6814
|
type: scopeTypeSchema,
|
|
6803
|
-
displayName: z14.string().nullable(),
|
|
6804
6815
|
createdAt: z14.string(),
|
|
6805
6816
|
updatedAt: z14.string()
|
|
6806
6817
|
});
|
|
6807
6818
|
var createScopeRequestSchema = z14.object({
|
|
6808
|
-
slug: scopeSlugSchema
|
|
6809
|
-
displayName: z14.string().max(128).optional()
|
|
6819
|
+
slug: scopeSlugSchema
|
|
6810
6820
|
});
|
|
6811
6821
|
var updateScopeRequestSchema = z14.object({
|
|
6812
6822
|
slug: scopeSlugSchema,
|
|
@@ -9299,7 +9309,8 @@ async function executeJob(context, config, options = {}) {
|
|
|
9299
9309
|
kernelPath: config.firecracker.kernel,
|
|
9300
9310
|
rootfsPath: config.firecracker.rootfs,
|
|
9301
9311
|
firecrackerBinary: config.firecracker.binary,
|
|
9302
|
-
workDir: path4.join(workspacesDir, `vm0-${vmId}`)
|
|
9312
|
+
workDir: path4.join(workspacesDir, `vm0-${vmId}`),
|
|
9313
|
+
logger: log
|
|
9303
9314
|
};
|
|
9304
9315
|
log(`[Executor] Creating VM ${vmId}...`);
|
|
9305
9316
|
vm = new FirecrackerVM(vmConfig);
|
|
@@ -10253,14 +10264,14 @@ var Timer = class {
|
|
|
10253
10264
|
this.startTime = Date.now();
|
|
10254
10265
|
}
|
|
10255
10266
|
/**
|
|
10256
|
-
* Get elapsed time formatted as [MM:SS.
|
|
10267
|
+
* Get elapsed time formatted as [MM:SS.ss]
|
|
10257
10268
|
*/
|
|
10258
10269
|
elapsed() {
|
|
10259
10270
|
const ms = Date.now() - this.startTime;
|
|
10260
10271
|
const totalSeconds = ms / 1e3;
|
|
10261
10272
|
const minutes = Math.floor(totalSeconds / 60);
|
|
10262
|
-
const seconds = (totalSeconds % 60).toFixed(
|
|
10263
|
-
return `[${String(minutes).padStart(2, "0")}:${seconds.padStart(
|
|
10273
|
+
const seconds = (totalSeconds % 60).toFixed(2);
|
|
10274
|
+
return `[${String(minutes).padStart(2, "0")}:${seconds.padStart(5, "0")}]`;
|
|
10264
10275
|
}
|
|
10265
10276
|
/**
|
|
10266
10277
|
* Log message with timestamp
|
|
@@ -10334,7 +10345,7 @@ var benchmarkCommand = new Command4("benchmark").description(
|
|
|
10334
10345
|
});
|
|
10335
10346
|
|
|
10336
10347
|
// src/index.ts
|
|
10337
|
-
var version = true ? "3.0
|
|
10348
|
+
var version = true ? "3.1.0" : "0.1.0";
|
|
10338
10349
|
program.name("vm0-runner").version(version).description("Self-hosted runner for VM0 agents");
|
|
10339
10350
|
program.addCommand(startCommand);
|
|
10340
10351
|
program.addCommand(doctorCommand);
|