Skip to content

Commit abbc898

Browse files
Quickbeardclaude
andcommitted
feat(sandbox): mount extra virtiofs tags from mounts.json in the guest
codev-init now reads /codev-config/mounts.json (written by the vz provider every start) and mounts each listed tag, non-fatally per entry so one bad share cannot brick the boot. This is the one-time image change: all future mount-list changes are host-side manifest writes. Compatibility is safe in both directions — an old image ignores the extra shares (they sit unmounted and inert), and a new image without a manifest skips the block. Existing macOS sandboxes pick the new init up via Reset, which re-provisions from the current image. e2e-vz.ts gains a mounts probe: the guest reads a marker from /mnt/extra and writes back into it, exercising both directions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 027741a commit abbc898

2 files changed

Lines changed: 55 additions & 2 deletions

File tree

‎packages/codev-sandbox/image/guest/codev-init‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ mount -t tmpfs tmpfs /tmp
1616
mount -t virtiofs workspace /workspace
1717
mount -t virtiofs bundle /opt/codev
1818
mount -t virtiofs config /codev-config
19+
# Extra host folders arrive as additional virtiofs tags listed in mounts.json,
20+
# which the host rewrites on every start. Tags and guest paths are sanitized
21+
# host-side (no spaces or quotes); a failed mount is logged and skipped so one
22+
# bad share cannot brick the boot.
23+
if [ -f /codev-config/mounts.json ]; then
24+
node -e '
25+
for (const m of require("/codev-config/mounts.json").mounts ?? []) console.log(m.tag, m.path)
26+
' | while read -r tag path; do
27+
mkdir -p "$path" && mount -t virtiofs "$tag" "$path" || echo "codev-init: mount $tag at $path failed" >&2
28+
done
29+
fi
1930
ip link set lo up
2031

2132
[ -f /etc/codev-guest.env ] && . /etc/codev-guest.env

‎packages/codev-sandbox/script/e2e-vz.ts‎

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ const work = await mkdtemp(join(tmpdir(), "codev-vz-e2e-"))
2424
const projectDir = argValue("--project") ?? join(work, "project")
2525
await mkdir(projectDir, { recursive: true })
2626
await writeFile(join(projectDir, "hello.txt"), "hello from the host project\n")
27+
// A second host folder mounted on top of the project dir, to prove the
28+
// mounts.json path: the guest must see it at /mnt/extra.
29+
const extraDir = join(work, "extra")
30+
await mkdir(extraDir, { recursive: true })
31+
await writeFile(join(extraDir, "marker.txt"), "extra-mount-ok\n")
2732

2833
const payload = Bun.file(image)
2934
const hash = createHash("sha256")
@@ -55,7 +60,13 @@ const provider = createVzProvider({
5560
})
5661

5762
console.log("availability:", await provider.availability())
58-
const config: SandboxConfig = { id: "sandbox:e2e", name: "e2e", backend: "vz", projectDir }
63+
const config: SandboxConfig = {
64+
id: "sandbox:e2e",
65+
name: "e2e",
66+
backend: "vz",
67+
projectDir,
68+
mounts: [{ path: extraDir }],
69+
}
5970

6071
console.time("create")
6172
await provider.create(config)
@@ -106,6 +117,37 @@ const brokerOk = await (async () => {
106117
})()
107118
console.log(`broker: ${brokerOk ? "OK" : "FAILED"}`)
108119

120+
// Extra mount: the guest reads the marker from /mnt/extra (mounted by
121+
// codev-init from mounts.json) and writes proof both into the workspace and
122+
// back into the extra mount itself, exercising read and write.
123+
console.log(`guest mounts: ${JSON.stringify(sandbox.mounts)}`)
124+
await fetch(`${sandbox.url}/api/pty`, {
125+
method: "POST",
126+
headers: { authorization: auth, "content-type": "application/json" },
127+
body: JSON.stringify({
128+
command: "/bin/sh",
129+
args: [
130+
"-c",
131+
"cat /mnt/extra/marker.txt > /workspace/mount-probe.txt 2>&1; echo written-from-guest > /mnt/extra/guest-write.txt 2>/dev/null",
132+
],
133+
cwd: "/workspace",
134+
}),
135+
}).catch(() => null)
136+
const mountOk = await (async () => {
137+
for (let attempt = 0; attempt < 30; attempt++) {
138+
const read = await Bun.file(join(projectDir, "mount-probe.txt"))
139+
.text()
140+
.catch(() => "")
141+
const written = await Bun.file(join(extraDir, "guest-write.txt"))
142+
.text()
143+
.catch(() => "")
144+
if (read.trim() === "extra-mount-ok" && written.trim() === "written-from-guest") return true
145+
await new Promise((resolve) => setTimeout(resolve, 500))
146+
}
147+
return false
148+
})()
149+
console.log(`extra mount: ${mountOk ? "OK" : "FAILED"}`)
150+
109151
if (hold) {
110152
console.log("holding the VM up; Ctrl-C to stop")
111153
await new Promise(() => {})
@@ -117,7 +159,7 @@ await Promise.race([exited, new Promise((resolve) => setTimeout(resolve, 10_000)
117159
await provider.destroy(config)
118160
server.stop(true)
119161
await rm(work, { recursive: true, force: true })
120-
if (!healthy || !pathsOk || !brokerOk) process.exit(1)
162+
if (!healthy || !pathsOk || !brokerOk || !mountOk) process.exit(1)
121163
console.log("e2e OK")
122164
process.exit(0)
123165

0 commit comments

Comments
 (0)