Support optional ~/Code mount alongside ~/dev
This commit is contained in:
parent
be277b7623
commit
3d41e3de29
40
src/vm.ts
40
src/vm.ts
|
|
@ -18,6 +18,9 @@ export function containerPath(hostPath: string): string {
|
||||||
if (hostPath.startsWith(`${home}/dev`)) {
|
if (hostPath.startsWith(`${home}/dev`)) {
|
||||||
return "/host" + hostPath.slice(`${home}/dev`.length)
|
return "/host" + hostPath.slice(`${home}/dev`.length)
|
||||||
}
|
}
|
||||||
|
if (hostPath.startsWith(`${home}/Code`)) {
|
||||||
|
return "/host-code" + hostPath.slice(`${home}/Code`.length)
|
||||||
|
}
|
||||||
return hostPath
|
return hostPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,11 +44,28 @@ async function run(cmd: ReturnType<typeof $>, step: string): Promise<void> {
|
||||||
|
|
||||||
// ── create() helpers (internal) ──────────────────────────────────────
|
// ── create() helpers (internal) ──────────────────────────────────────
|
||||||
|
|
||||||
|
/** Check which host source directories exist. */
|
||||||
|
async function hostMounts(home: string): Promise<{ dev: boolean; code: boolean }> {
|
||||||
|
const [dev, code] = await Promise.all([
|
||||||
|
Bun.file(`${home}/dev`).exists().catch(() => false),
|
||||||
|
Bun.file(`${home}/Code`).exists().catch(() => false),
|
||||||
|
])
|
||||||
|
return { dev, code }
|
||||||
|
}
|
||||||
|
|
||||||
/** Pull the image and start the container in detached mode. */
|
/** Pull the image and start the container in detached mode. */
|
||||||
async function createContainer(home: string): Promise<void> {
|
async function createContainer(home: string): Promise<void> {
|
||||||
await run(
|
const mounts = await hostMounts(home)
|
||||||
$`container run -d --name ${CONTAINER_NAME} -m 4G --mount type=bind,source=${home}/dev,target=/host,readonly -v ${home}/.sandlot:/sandlot ubuntu:24.04 sleep infinity`,
|
const args = ["container", "run", "-d", "--name", CONTAINER_NAME, "-m", "4G"]
|
||||||
"Container creation")
|
if (mounts.dev) args.push("--mount", `type=bind,source=${home}/dev,target=/host,readonly`)
|
||||||
|
if (mounts.code) args.push("--mount", `type=bind,source=${home}/Code,target=/host-code,readonly`)
|
||||||
|
args.push("-v", `${home}/.sandlot:/sandlot`, "ubuntu:24.04", "sleep", "infinity")
|
||||||
|
const result = await $`${args}`.nothrow().quiet()
|
||||||
|
if (result.exitCode !== 0) {
|
||||||
|
const stderr = result.stderr.toString().trim()
|
||||||
|
const stdout = result.stdout.toString().trim()
|
||||||
|
throw new Error(`Container creation failed (exit ${result.exitCode}):\n${stderr || stdout || "(no output)"}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Install base system packages (as root). */
|
/** Install base system packages (as root). */
|
||||||
|
|
@ -60,8 +80,12 @@ async function installPackages(cached: boolean): Promise<void> {
|
||||||
|
|
||||||
/** Create symlinks so git worktree absolute paths from the host resolve inside the container. */
|
/** Create symlinks so git worktree absolute paths from the host resolve inside the container. */
|
||||||
async function createHostSymlinks(home: string): Promise<void> {
|
async function createHostSymlinks(home: string): Promise<void> {
|
||||||
|
const mounts = await hostMounts(home)
|
||||||
|
const cmds = [`mkdir -p '${home}'`, `ln -s /sandlot '${home}/.sandlot'`]
|
||||||
|
if (mounts.dev) cmds.push(`ln -s /host '${home}/dev'`)
|
||||||
|
if (mounts.code) cmds.push(`ln -s /host-code '${home}/Code'`)
|
||||||
await run(
|
await run(
|
||||||
$`container exec ${CONTAINER_NAME} bash -c ${`mkdir -p '${home}' && ln -s /host '${home}/dev' && ln -s /sandlot '${home}/.sandlot'`}`,
|
$`container exec ${CONTAINER_NAME} bash -c ${cmds.join(" && ")}`,
|
||||||
"Symlink creation")
|
"Symlink creation")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,13 +264,17 @@ export async function status(): Promise<"running" | "stopped" | "missing"> {
|
||||||
/** Launch claude in the container at the given workdir. */
|
/** Launch claude in the container at the given workdir. */
|
||||||
export async function claude(workdir: string, opts?: { prompt?: string; print?: string; continue?: boolean }): Promise<{ exitCode: number; output?: string }> {
|
export async function claude(workdir: string, opts?: { prompt?: string; print?: string; continue?: boolean }): Promise<{ exitCode: number; output?: string }> {
|
||||||
const cwd = containerPath(workdir)
|
const cwd = containerPath(workdir)
|
||||||
|
const mounts = await hostMounts(homedir())
|
||||||
const systemPromptLines = [
|
const systemPromptLines = [
|
||||||
"You are running inside a sandlot container (Apple Container, ubuntu:24.04).",
|
"You are running inside a sandlot container (Apple Container, ubuntu:24.04).",
|
||||||
`Your working directory is ${cwd}, a git worktree managed by sandlot.`,
|
`Your working directory is ${cwd}, a git worktree managed by sandlot.`,
|
||||||
"The host's ~/dev is mounted read-only at /host.",
|
]
|
||||||
|
if (mounts.dev) systemPromptLines.push("The host's ~/dev is mounted read-only at /host.")
|
||||||
|
if (mounts.code) systemPromptLines.push("The host's ~/Code is mounted read-only at /host-code.")
|
||||||
|
systemPromptLines.push(
|
||||||
"The host's ~/.sandlot is mounted at /sandlot.",
|
"The host's ~/.sandlot is mounted at /sandlot.",
|
||||||
"Bun is installed at ~/.local/bin/bun. Use bun instead of node/npm.",
|
"Bun is installed at ~/.local/bin/bun. Use bun instead of node/npm.",
|
||||||
]
|
)
|
||||||
if (opts?.print) {
|
if (opts?.print) {
|
||||||
systemPromptLines.push("IMPORTANT: Do not use plan mode. Do not call the EnterPlanMode tool. Proceed directly with the task.")
|
systemPromptLines.push("IMPORTANT: Do not use plan mode. Do not call the EnterPlanMode tool. Proceed directly with the task.")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user