diff --git a/src/commands/config.ts b/src/commands/config.ts index b084c7d..3c8565d 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -1,8 +1,7 @@ import { die } from "../fmt.ts" import * as config from "../config.ts" -const VALID_KEYS = ["memory"] as const -type Key = (typeof VALID_KEYS)[number] +const VALID_KEYS = Object.keys(config.DEFAULTS) as config.Key[] export async function action(args: string[]) { if (args.length === 0) { @@ -16,25 +15,18 @@ export async function action(args: string[]) { } const [key, ...rest] = args - if (!VALID_KEYS.includes(key as Key)) die(`Unknown config key: ${key}\nAvailable keys: ${VALID_KEYS.join(", ")}`) + if (!VALID_KEYS.includes(key as config.Key)) die(`Unknown config key: ${key}\nAvailable keys: ${VALID_KEYS.join(", ")}`) if (rest.length === 0) { - const val = await config.get(key as Key) - console.log(val ?? `${config.DEFAULTS[key as Key]} (default)`) + const val = await config.get(key as config.Key) + console.log(val ?? `${config.DEFAULTS[key as config.Key]} (default)`) return } if (rest.length > 1) die(`Too many arguments. Usage: sandlot config ${key} `) - switch (key) { - case "memory": { - let normalized: string - try { normalized = config.validateMemory(rest[0]) } catch { die("Must be a number followed by G or M (e.g. 16G)") } - await config.set("memory", normalized!) - console.log(`memory = ${normalized!}`) - break - } - default: - die(`Unhandled config key: ${key}`) - } + let normalized: string + try { normalized = config.validateMemory(rest[0]) } catch { return die("Must be a number followed by G or M, minimum 512M (e.g. 16G)") } + await config.set("memory", normalized) + console.log(`memory = ${normalized}`) } diff --git a/src/config.ts b/src/config.ts index 6c75bef..d1ce0fa 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,19 +1,28 @@ import { mkdir } from "fs/promises" import { homedir } from "os" -import { dirname, join } from "path" +import { join } from "path" -const CONFIG_PATH = join(homedir(), ".config", "sandlot", "config.json") +const CONFIG_DIR = join(homedir(), ".config", "sandlot") +const CONFIG_PATH = join(CONFIG_DIR, "config.json") export const DEFAULTS = { memory: "16G", } as const +export type Key = keyof typeof DEFAULTS + export interface Config { memory?: string } +const MIN_MEMORY_MB = 512 + export function validateMemory(v: string): string { if (!/^[1-9]\d*[GMgm]$/.test(v)) throw new Error(`Invalid memory value: ${v} (must be a number followed by G or M, e.g. 16G)`) + const num = parseInt(v) + const unit = v.slice(-1).toUpperCase() + const mb = unit === "G" ? num * 1024 : num + if (mb < MIN_MEMORY_MB) throw new Error(`Memory too low: ${v} (minimum ${MIN_MEMORY_MB}M)`) return v.toUpperCase() } @@ -30,7 +39,7 @@ export async function load(): Promise { } export async function save(config: Config): Promise { - await mkdir(dirname(CONFIG_PATH), { recursive: true }) + await mkdir(CONFIG_DIR, { recursive: true }) await Bun.write(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n") } diff --git a/src/vm.ts b/src/vm.ts index 3302469..9f33af1 100644 --- a/src/vm.ts +++ b/src/vm.ts @@ -67,9 +67,8 @@ function hostMounts(home: string): { dev: boolean; code: boolean } { /** Pull the image and start the container in detached mode. */ async function createContainer(home: string): Promise { const mounts = hostMounts(home) - const raw = (await getConfig("memory")) ?? DEFAULTS.memory let memory: string - try { memory = validateMemory(raw) } catch { memory = DEFAULTS.memory } + try { memory = validateMemory((await getConfig("memory")) ?? DEFAULTS.memory) } catch { memory = DEFAULTS.memory } const args = ["container", "run", "-d", "--name", CONTAINER_NAME, "-m", memory] if (mounts.dev) args.push("--mount", `type=bind,source=${home}/dev,target=/host/dev,readonly`) if (mounts.code) args.push("--mount", `type=bind,source=${home}/code,target=/host/code,readonly`)