Merge branch 'cache-packages'
This commit is contained in:
commit
47a6107f76
|
|
@ -71,4 +71,12 @@ export function register(program: Command) {
|
|||
await vm.destroy()
|
||||
console.log("✔ VM destroyed")
|
||||
})
|
||||
|
||||
vmCmd
|
||||
.command("uncache")
|
||||
.description("Clear the package cache (next create will re-download)")
|
||||
.action(async () => {
|
||||
const had = await vm.clearCache()
|
||||
console.log(had ? "✔ Package cache cleared" : "No cache to clear")
|
||||
})
|
||||
}
|
||||
|
|
|
|||
70
src/vm.ts
70
src/vm.ts
|
|
@ -6,6 +6,7 @@ import { getApiKey } from "./env.ts"
|
|||
const CONTAINER_NAME = "sandlot"
|
||||
const USER = "ubuntu"
|
||||
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`
|
||||
|
||||
/** Translate a host path to its corresponding container path. */
|
||||
export function containerPath(hostPath: string): string {
|
||||
|
|
@ -47,9 +48,12 @@ async function createContainer(home: string): Promise<void> {
|
|||
}
|
||||
|
||||
/** Install base system packages (as root). */
|
||||
async function installPackages(): Promise<void> {
|
||||
async function installPackages(cached: boolean): Promise<void> {
|
||||
const packages = cached
|
||||
? "curl git fish"
|
||||
: "curl git fish unzip"
|
||||
await run(
|
||||
$`container exec ${CONTAINER_NAME} bash -c ${"apt update && apt install -y curl git neofetch fish unzip"}`,
|
||||
$`container exec ${CONTAINER_NAME} bash -c ${`apt update && apt install -y ${packages}`}`,
|
||||
"Package installation")
|
||||
}
|
||||
|
||||
|
|
@ -60,8 +64,34 @@ async function createHostSymlinks(home: string): Promise<void> {
|
|||
"Symlink creation")
|
||||
}
|
||||
|
||||
/** Install Bun and Claude Code (as ubuntu user). */
|
||||
async function installTooling(log?: (msg: string) => void): Promise<void> {
|
||||
const CACHE_DIR = join(homedir(), '.sandlot', '.cache')
|
||||
|
||||
/** Check whether the package cache is populated. */
|
||||
async function hasCachedTooling(): Promise<boolean> {
|
||||
const files = ['bun', 'claude', 'neofetch', 'nvim.tar.gz']
|
||||
const checks = await Promise.all(files.map(f => Bun.file(join(CACHE_DIR, f)).exists()))
|
||||
return checks.every(Boolean)
|
||||
}
|
||||
|
||||
/** Install Bun, Claude Code, neofetch, and Neovim using cached binaries when available. */
|
||||
async function installTooling(cached: boolean, log?: (msg: string) => void): Promise<void> {
|
||||
// Ensure cache directory exists on the host (mounted at /sandlot/.cache in the container)
|
||||
await $`mkdir -p ${CACHE_DIR}`.quiet()
|
||||
|
||||
if (cached) {
|
||||
log?.("Installing packages (cached)")
|
||||
await run(
|
||||
$`container exec --user ${USER} ${CONTAINER_NAME} bash -c ${"mkdir -p ~/.local/bin"}`,
|
||||
"Create bin directory")
|
||||
await run(
|
||||
$`container exec --user ${USER} ${CONTAINER_NAME} bash -c ${"cp /sandlot/.cache/bun /sandlot/.cache/claude /sandlot/.cache/neofetch ~/.local/bin/ && chmod +x ~/.local/bin/bun ~/.local/bin/claude ~/.local/bin/neofetch && ln -sf bun ~/.local/bin/bunx"}`,
|
||||
"Install cached binaries")
|
||||
await run(
|
||||
$`container exec --user ${USER} ${CONTAINER_NAME} bash -c ${"tar xzf /sandlot/.cache/nvim.tar.gz -C ~/.local --strip-components=1"}`,
|
||||
"Install cached Neovim")
|
||||
return
|
||||
}
|
||||
|
||||
log?.("Installing Bun")
|
||||
await run(
|
||||
$`container exec --user ${USER} ${CONTAINER_NAME} env BUN_INSTALL=/home/${USER}/.local bash -c ${"curl -fsSL https://bun.sh/install | bash"}`,
|
||||
|
|
@ -71,6 +101,19 @@ async function installTooling(log?: (msg: string) => void): Promise<void> {
|
|||
await run(
|
||||
$`container exec --user ${USER} ${CONTAINER_NAME} bash -c ${"curl -fsSL https://claude.ai/install.sh | bash"}`,
|
||||
"Claude Code installation")
|
||||
|
||||
log?.("Installing neofetch")
|
||||
await run(
|
||||
$`container exec --user ${USER} ${CONTAINER_NAME} bash -c ${"curl -fsSL https://raw.githubusercontent.com/dylanaraps/neofetch/master/neofetch -o ~/.local/bin/neofetch && chmod +x ~/.local/bin/neofetch"}`,
|
||||
"neofetch installation")
|
||||
|
||||
log?.("Installing Neovim")
|
||||
await run(
|
||||
$`container exec --user ${USER} ${CONTAINER_NAME} bash -c ${"curl -fsSL https://github.com/neovim/neovim/releases/latest/download/nvim-linux-arm64.tar.gz -o /tmp/nvim.tar.gz && tar xzf /tmp/nvim.tar.gz -C ~/.local --strip-components=1"}`,
|
||||
"Neovim installation")
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
/** Configure git identity, API key helper, activity hook, and Claude settings. */
|
||||
|
|
@ -136,14 +179,16 @@ export async function create(log?: (msg: string) => void): Promise<void> {
|
|||
|
||||
const home = homedir()
|
||||
|
||||
const cached = await hasCachedTooling()
|
||||
|
||||
log?.("Pulling image & creating container")
|
||||
await createContainer(home)
|
||||
|
||||
log?.("Installing packages")
|
||||
await installPackages()
|
||||
await installPackages(cached)
|
||||
await createHostSymlinks(home)
|
||||
|
||||
await installTooling(log)
|
||||
await installTooling(cached, log)
|
||||
|
||||
log?.("Configuring environment")
|
||||
const apiKey = await getApiKey()
|
||||
|
|
@ -207,7 +252,7 @@ export async function claude(workdir: string, opts?: { prompt?: string; print?:
|
|||
const systemPrompt = systemPromptLines.join("\n")
|
||||
|
||||
const term = process.env.TERM || "xterm-256color"
|
||||
const args = ["container", "exec", "-it", "--user", USER, "--workdir", cwd, CONTAINER_NAME, "env", `TERM=${term}`, `PATH=/home/${USER}/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`, CLAUDE_BIN, "--dangerously-skip-permissions", "--model", "claude-opus-4-6", "--append-system-prompt", systemPrompt]
|
||||
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]
|
||||
if (opts?.continue) args.push("--continue")
|
||||
if (opts?.print) args.push("-p", opts.print)
|
||||
else if (opts?.prompt) args.push(opts.prompt)
|
||||
|
|
@ -227,7 +272,7 @@ export async function claude(workdir: string, opts?: { prompt?: string; print?:
|
|||
export async function shell(workdir?: string): Promise<void> {
|
||||
const args = ["container", "exec", "-it", "--user", USER]
|
||||
if (workdir) args.push("--workdir", containerPath(workdir))
|
||||
args.push(CONTAINER_NAME, "env", "TERM=xterm-256color", `PATH=/home/${USER}/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`, "fish", "--login")
|
||||
args.push(CONTAINER_NAME, "env", "TERM=xterm-256color", `PATH=${CONTAINER_PATH}`, "fish", "--login")
|
||||
const proc = Bun.spawn(args, { stdin: "inherit", stdout: "inherit", stderr: "inherit" })
|
||||
await proc.exited
|
||||
}
|
||||
|
|
@ -235,7 +280,7 @@ export async function shell(workdir?: string): Promise<void> {
|
|||
/** Run neofetch in the container. */
|
||||
export async function info(): Promise<void> {
|
||||
const proc = Bun.spawn(
|
||||
["container", "exec", "--user", USER, CONTAINER_NAME, "neofetch"],
|
||||
["container", "exec", "--user", USER, CONTAINER_NAME, "env", `PATH=${CONTAINER_PATH}`, "neofetch"],
|
||||
{ stdin: "inherit", stdout: "inherit", stderr: "inherit" },
|
||||
)
|
||||
await proc.exited
|
||||
|
|
@ -292,3 +337,10 @@ export async function destroy(): Promise<void> {
|
|||
await stop()
|
||||
await $`container delete ${CONTAINER_NAME}`.nothrow().quiet()
|
||||
}
|
||||
|
||||
/** Clear the package cache so the next create re-downloads everything. */
|
||||
export async function clearCache(): Promise<boolean> {
|
||||
const exists = await Bun.file(join(CACHE_DIR, 'bun')).exists()
|
||||
await $`rm -rf ${CACHE_DIR}`.nothrow().quiet()
|
||||
return exists
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user