vmsan 0.1.0 → 0.1.1

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.
@@ -3,7 +3,7 @@ import { l as handleCommandError } from "./errors.mjs";
3
3
  import { r as getOutputMode, t as createCommandLogger } from "./logger.mjs";
4
4
  import { basename, join } from "node:path";
5
5
  import { execSync } from "node:child_process";
6
- import { accessSync, constants, existsSync, readdirSync, statfsSync } from "node:fs";
6
+ import { accessSync, constants, existsSync, readFileSync, readdirSync, statfsSync } from "node:fs";
7
7
  import { consola } from "consola";
8
8
  import { defineCommand } from "citty";
9
9
  function checkKvm() {
@@ -82,6 +82,67 @@ function checkDefaultInterface() {
82
82
  };
83
83
  }
84
84
  }
85
+ function checkTunDevice() {
86
+ try {
87
+ accessSync("/dev/net/tun", constants.R_OK | constants.W_OK);
88
+ return {
89
+ category: "System",
90
+ name: "TUN device",
91
+ status: "pass",
92
+ detail: "/dev/net/tun"
93
+ };
94
+ } catch {
95
+ return {
96
+ category: "System",
97
+ name: "TUN device",
98
+ status: "fail",
99
+ detail: "/dev/net/tun not accessible",
100
+ fix: "Load the tun kernel module: sudo modprobe tun"
101
+ };
102
+ }
103
+ }
104
+ function checkJailerFilesystem(jailerBaseDir) {
105
+ try {
106
+ const mounts = readFileSync("/proc/mounts", "utf-8");
107
+ let bestMatch = "";
108
+ let bestOptions = "";
109
+ for (const line of mounts.split("\n")) {
110
+ const parts = line.split(" ");
111
+ if (parts.length < 4) continue;
112
+ const mountpoint = parts[1];
113
+ if ((jailerBaseDir === mountpoint || jailerBaseDir.startsWith(mountpoint.endsWith("/") ? mountpoint : `${mountpoint}/`)) && mountpoint.length > bestMatch.length) {
114
+ bestMatch = mountpoint;
115
+ bestOptions = parts[3];
116
+ }
117
+ }
118
+ if (!bestMatch) return {
119
+ category: "System",
120
+ name: "Jailer filesystem",
121
+ status: "pass",
122
+ detail: "Check skipped"
123
+ };
124
+ if (bestOptions.split(",").includes("nodev")) return {
125
+ category: "System",
126
+ name: "Jailer filesystem",
127
+ status: "fail",
128
+ detail: `${bestMatch} mounted with nodev`,
129
+ fix: `The jailer needs device nodes to work. Remount without nodev: sudo mount -o remount,dev ${bestMatch}`
130
+ };
131
+ return {
132
+ category: "System",
133
+ name: "Jailer filesystem",
134
+ status: "pass",
135
+ detail: bestMatch
136
+ };
137
+ } catch {
138
+ return {
139
+ category: "System",
140
+ name: "Jailer filesystem",
141
+ status: "pass",
142
+ detail: "Check skipped"
143
+ };
144
+ }
145
+ }
85
146
  function checkFirecracker(binDir) {
86
147
  const fcPath = join(binDir, "firecracker");
87
148
  if (!existsSync(fcPath)) return {
@@ -193,8 +254,10 @@ function runDoctorChecks(paths) {
193
254
  const p = paths ?? vmsanPaths();
194
255
  return [
195
256
  checkKvm(),
257
+ checkTunDevice(),
196
258
  checkDiskSpace(p.baseDir),
197
259
  checkDefaultInterface(),
260
+ checkJailerFilesystem(p.jailerBaseDir),
198
261
  checkFirecracker(p.binDir),
199
262
  checkJailer(p.binDir),
200
263
  checkAgent(p.agentBin),
@@ -154,12 +154,28 @@ var FirecrackerApiError = class extends VmsanError {
154
154
  };
155
155
  }
156
156
  };
157
- const firecrackerApiError = (method, path, httpStatus, body) => new FirecrackerApiError("ERR_FIRECRACKER_API", {
158
- method,
159
- path,
160
- httpStatus,
161
- message: `${method} ${path} failed (${httpStatus}): ${body}`
162
- });
157
+ const firecrackerApiError = (method, path, httpStatus, body) => {
158
+ const opts = {
159
+ method,
160
+ path,
161
+ httpStatus,
162
+ message: `${method} ${path} failed (${httpStatus}): ${body}`
163
+ };
164
+ if (body.includes("/dev/net/tun") && body.includes("Permission denied")) {
165
+ opts.why = "Firecracker cannot open /dev/net/tun inside the jailer chroot. This usually means the filesystem where the vmsan data directory resides is mounted with the 'nodev' option.";
166
+ opts.fix = [
167
+ "Run 'vmsan doctor' to check for nodev and other issues.",
168
+ "",
169
+ "If the filesystem is mounted with 'nodev', remount without it:",
170
+ " sudo mount -o remount,dev <mountpoint>",
171
+ "",
172
+ "Or move vmsan to a different filesystem:",
173
+ " export VMSAN_DIR=/var/lib/vmsan",
174
+ " curl -fsSL https://vmsan.dev/install | bash"
175
+ ].join("\n");
176
+ }
177
+ return new FirecrackerApiError("ERR_FIRECRACKER_API", opts);
178
+ };
163
179
  var NetworkError = class extends VmsanError {
164
180
  constructor(code, options) {
165
181
  super(code, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vmsan",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Firecracker microVM sandbox toolkit",
5
5
  "homepage": "https://github.com/angelorc/vmsan",
6
6
  "bugs": "https://github.com/angelorc/vmsan/issues",