Add persistent Rust and Go tooling to container
This commit is contained in:
parent
c4c0703bee
commit
3c6cd05ef7
44
src/vm.ts
44
src/vm.ts
|
|
@ -9,7 +9,13 @@ const DEBUG = !!process.env.DEBUG
|
||||||
const CONTAINER_NAME = "sandlot"
|
const CONTAINER_NAME = "sandlot"
|
||||||
const USER = "ubuntu"
|
const USER = "ubuntu"
|
||||||
const CLAUDE_BIN = `/home/${USER}/.local/bin/claude`
|
const CLAUDE_BIN = `/home/${USER}/.local/bin/claude`
|
||||||
const CONTAINER_PATH = `/home/${USER}/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`
|
const CONTAINER_PATH = `/sandlot/.cargo/bin:/sandlot/.go/bin:/sandlot/.gopath/bin:/home/${USER}/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`
|
||||||
|
const CONTAINER_ENV = {
|
||||||
|
RUSTUP_HOME: "/sandlot/.rustup",
|
||||||
|
CARGO_HOME: "/sandlot/.cargo",
|
||||||
|
GOROOT: "/sandlot/.go",
|
||||||
|
GOPATH: "/sandlot/.gopath",
|
||||||
|
}
|
||||||
|
|
||||||
/** Translate a host path to its corresponding container path. */
|
/** Translate a host path to its corresponding container path. */
|
||||||
export function containerPath(hostPath: string): string {
|
export function containerPath(hostPath: string): string {
|
||||||
|
|
@ -142,6 +148,30 @@ async function installTooling(cached: boolean, log?: (msg: string) => void): Pro
|
||||||
|
|
||||||
// Cache binaries for next time
|
// Cache binaries for next time
|
||||||
await $`container exec --user ${USER} ${CONTAINER_NAME} bash -c ${"cp ~/.local/bin/bun ~/.local/bin/claude ~/.local/bin/neofetch /sandlot/.cache/ && cp /tmp/nvim.tar.gz /sandlot/.cache/nvim.tar.gz"}`.nothrow().quiet()
|
await $`container exec --user ${USER} ${CONTAINER_NAME} bash -c ${"cp ~/.local/bin/bun ~/.local/bin/claude ~/.local/bin/neofetch /sandlot/.cache/ && cp /tmp/nvim.tar.gz /sandlot/.cache/nvim.tar.gz"}`.nothrow().quiet()
|
||||||
|
|
||||||
|
await installPersistentTooling(log)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Install Rust and Go to /sandlot/ so they persist across container recreates. */
|
||||||
|
async function installPersistentTooling(log?: (msg: string) => void): Promise<void> {
|
||||||
|
// Rust — skip if already installed on the persistent mount
|
||||||
|
const hasRust = await $`container exec --user ${USER} ${CONTAINER_NAME} test -x /sandlot/.cargo/bin/rustc`.nothrow().quiet()
|
||||||
|
if (hasRust.exitCode !== 0) {
|
||||||
|
log?.("Installing Rust")
|
||||||
|
await run(
|
||||||
|
$`container exec --user ${USER} ${CONTAINER_NAME} env ${`RUSTUP_HOME=${CONTAINER_ENV.RUSTUP_HOME}`} ${`CARGO_HOME=${CONTAINER_ENV.CARGO_HOME}`} bash -c ${"curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y"}`,
|
||||||
|
"Rust installation")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go — skip if already installed on the persistent mount
|
||||||
|
const hasGo = await $`container exec --user ${USER} ${CONTAINER_NAME} test -x /sandlot/.go/bin/go`.nothrow().quiet()
|
||||||
|
if (hasGo.exitCode !== 0) {
|
||||||
|
log?.("Installing Go")
|
||||||
|
await run(
|
||||||
|
$`container exec --user ${USER} ${CONTAINER_NAME} bash -c ${"mkdir -p /sandlot/.go && curl -fsSL https://go.dev/dl/go1.24.1.linux-arm64.tar.gz | tar xz -C /sandlot/.go --strip-components=1"}`,
|
||||||
|
"Go installation")
|
||||||
|
await $`container exec --user ${USER} ${CONTAINER_NAME} bash -c ${"mkdir -p /sandlot/.gopath"}`.nothrow().quiet()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Write a script to a temp file, copy it into ~/.local/bin/ in the container, then clean up. */
|
/** Write a script to a temp file, copy it into ~/.local/bin/ in the container, then clean up. */
|
||||||
|
|
@ -273,6 +303,7 @@ export async function claude(workdir: string, opts?: { prompt?: string; print?:
|
||||||
systemPromptLines.push(
|
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.",
|
||||||
|
"Rust (cargo/rustc) is installed at /sandlot/.cargo/. Go is installed at /sandlot/.go/.",
|
||||||
)
|
)
|
||||||
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.")
|
||||||
|
|
@ -280,7 +311,8 @@ export async function claude(workdir: string, opts?: { prompt?: string; print?:
|
||||||
const systemPrompt = systemPromptLines.join("\n")
|
const systemPrompt = systemPromptLines.join("\n")
|
||||||
|
|
||||||
const term = process.env.TERM || "xterm-256color"
|
const term = process.env.TERM || "xterm-256color"
|
||||||
const args = ["container", "exec", "-it", "--user", USER, "--workdir", cwd, CONTAINER_NAME, "env", `TERM=${term}`, `PATH=${CONTAINER_PATH}`, CLAUDE_BIN, "--dangerously-skip-permissions", "--model", "claude-opus-4-6", "--append-system-prompt", systemPrompt]
|
const envArgs = [`TERM=${term}`, `PATH=${CONTAINER_PATH}`, ...Object.entries(CONTAINER_ENV).map(([k, v]) => `${k}=${v}`)]
|
||||||
|
const args = ["container", "exec", "-it", "--user", USER, "--workdir", cwd, CONTAINER_NAME, "env", ...envArgs, CLAUDE_BIN, "--dangerously-skip-permissions", "--model", "claude-opus-4-6", "--append-system-prompt", systemPrompt]
|
||||||
if (opts?.continue) args.push("--continue")
|
if (opts?.continue) args.push("--continue")
|
||||||
if (opts?.print) args.push("-p", opts.print)
|
if (opts?.print) args.push("-p", opts.print)
|
||||||
else if (opts?.prompt) args.push(opts.prompt)
|
else if (opts?.prompt) args.push(opts.prompt)
|
||||||
|
|
@ -309,7 +341,8 @@ export async function claude(workdir: string, opts?: { prompt?: string; print?:
|
||||||
export async function shell(workdir?: string): Promise<void> {
|
export async function shell(workdir?: string): Promise<void> {
|
||||||
const args = ["container", "exec", "-it", "--user", USER]
|
const args = ["container", "exec", "-it", "--user", USER]
|
||||||
if (workdir) args.push("--workdir", containerPath(workdir))
|
if (workdir) args.push("--workdir", containerPath(workdir))
|
||||||
args.push(CONTAINER_NAME, "env", "TERM=xterm-256color", `PATH=${CONTAINER_PATH}`, "fish", "--login")
|
const envArgs = ["TERM=xterm-256color", `PATH=${CONTAINER_PATH}`, ...Object.entries(CONTAINER_ENV).map(([k, v]) => `${k}=${v}`)]
|
||||||
|
args.push(CONTAINER_NAME, "env", ...envArgs, "fish", "--login")
|
||||||
const proc = Bun.spawn(args, { stdin: "inherit", stdout: "inherit", stderr: "inherit" })
|
const proc = Bun.spawn(args, { stdin: "inherit", stdout: "inherit", stderr: "inherit" })
|
||||||
await proc.exited
|
await proc.exited
|
||||||
}
|
}
|
||||||
|
|
@ -317,7 +350,7 @@ export async function shell(workdir?: string): Promise<void> {
|
||||||
/** Run neofetch in the container. */
|
/** Run neofetch in the container. */
|
||||||
export async function neofetch(): Promise<void> {
|
export async function neofetch(): Promise<void> {
|
||||||
const proc = Bun.spawn(
|
const proc = Bun.spawn(
|
||||||
["container", "exec", "--user", USER, CONTAINER_NAME, "env", `PATH=${CONTAINER_PATH}`, "neofetch"],
|
["container", "exec", "--user", USER, CONTAINER_NAME, "env", `PATH=${CONTAINER_PATH}`, ...Object.entries(CONTAINER_ENV).map(([k, v]) => `${k}=${v}`), "neofetch"],
|
||||||
{ stdin: "inherit", stdout: "inherit", stderr: "inherit" },
|
{ stdin: "inherit", stdout: "inherit", stderr: "inherit" },
|
||||||
)
|
)
|
||||||
await proc.exited
|
await proc.exited
|
||||||
|
|
@ -325,7 +358,8 @@ export async function neofetch(): Promise<void> {
|
||||||
|
|
||||||
/** Run a bash command in the container at the given workdir, capturing output. */
|
/** Run a bash command in the container at the given workdir, capturing output. */
|
||||||
export async function exec(workdir: string, command: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
export async function exec(workdir: string, command: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
||||||
const result = await $`container exec --user ${USER} --workdir ${containerPath(workdir)} ${CONTAINER_NAME} bash -c ${"export PATH=$HOME/.local/bin:$PATH; " + command}`.nothrow().quiet()
|
const envExports = Object.entries(CONTAINER_ENV).map(([k, v]) => `export ${k}=${v}`).join("; ")
|
||||||
|
const result = await $`container exec --user ${USER} --workdir ${containerPath(workdir)} ${CONTAINER_NAME} bash -c ${`export PATH=$HOME/.local/bin:$PATH; ${envExports}; ` + command}`.nothrow().quiet()
|
||||||
return {
|
return {
|
||||||
exitCode: result.exitCode,
|
exitCode: result.exitCode,
|
||||||
stdout: result.stdout.toString().trim(),
|
stdout: result.stdout.toString().trim(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user