Add hosts config for local network resolution
This commit is contained in:
parent
a7e3a6333b
commit
894a0455a7
14
README.md
14
README.md
|
|
@ -59,3 +59,17 @@ Config is stored in `~/.config/sandlot/config.json`.
|
|||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `memory` | `16G` | Container memory limit (e.g. 4G, 16G, 32G, 64G) |
|
||||
| `hosts` | `[]` | Hostnames to resolve on the host and inject into the container |
|
||||
|
||||
#### Local network hosts
|
||||
|
||||
The container can't resolve `.local` (mDNS) hostnames natively. To make local network hosts reachable from inside the container, add them to the `hosts` config:
|
||||
|
||||
```bash
|
||||
sandlot config hosts add claude.toes.local
|
||||
sandlot config hosts add myserver.local
|
||||
sandlot config hosts rm myserver.local
|
||||
sandlot config hosts # list configured hosts
|
||||
```
|
||||
|
||||
Hostnames are resolved on the Mac via mDNS and written to the container's `/etc/hosts` every time the VM starts.
|
||||
|
|
|
|||
|
|
@ -2,14 +2,19 @@ import { die } from "../fmt.ts"
|
|||
import * as config from "../config.ts"
|
||||
|
||||
const VALID_KEYS = Object.keys(config.DEFAULTS) as config.Key[]
|
||||
const ARRAY_KEYS = Object.entries(config.DEFAULTS).filter(([, v]) => Array.isArray(v)).map(([k]) => k)
|
||||
|
||||
export async function action(args: string[]) {
|
||||
if (args.length === 0) {
|
||||
const cfg = await config.load()
|
||||
for (const key of VALID_KEYS) {
|
||||
const val = cfg[key]
|
||||
const display = val ?? `${config.DEFAULTS[key]} (default)`
|
||||
console.log(`${key} = ${display}`)
|
||||
if (Array.isArray(config.DEFAULTS[key])) {
|
||||
const arr = (val as string[] | undefined) ?? []
|
||||
console.log(`${key} = ${arr.length ? arr.join(", ") : "(empty)"}`)
|
||||
} else {
|
||||
console.log(`${key} = ${val ?? `${config.DEFAULTS[key]} (default)`}`)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -17,6 +22,32 @@ export async function action(args: string[]) {
|
|||
const [key, ...rest] = args
|
||||
if (!VALID_KEYS.includes(key as config.Key)) die(`Unknown config key: ${key}\nAvailable keys: ${VALID_KEYS.join(", ")}`)
|
||||
|
||||
// Array keys: `config hosts add foo.local`, `config hosts rm foo.local`
|
||||
if (ARRAY_KEYS.includes(key)) {
|
||||
const current = ((await config.get(key as config.Key)) ?? []) as string[]
|
||||
if (rest.length === 0) {
|
||||
if (current.length === 0) console.log("(empty)")
|
||||
else current.forEach(v => console.log(v))
|
||||
return
|
||||
}
|
||||
const [op, ...values] = rest
|
||||
if (op === "add") {
|
||||
if (values.length === 0) die("Usage: sandlot config hosts add <hostname>")
|
||||
const updated = [...new Set([...current, ...values])]
|
||||
await config.set(key as config.Key, updated as any)
|
||||
updated.forEach(v => console.log(v))
|
||||
} else if (op === "rm" || op === "remove") {
|
||||
if (values.length === 0) die("Usage: sandlot config hosts rm <hostname>")
|
||||
const updated = current.filter(v => !values.includes(v))
|
||||
await config.set(key as config.Key, updated as any)
|
||||
if (updated.length === 0) console.log("(empty)")
|
||||
else updated.forEach(v => console.log(v))
|
||||
} else {
|
||||
die(`Unknown operation: ${op}\nUsage: sandlot config ${key} add|rm <value>`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (rest.length === 0) {
|
||||
const val = await config.get(key as config.Key)
|
||||
console.log(val ?? `${config.DEFAULTS[key as config.Key]} (default)`)
|
||||
|
|
|
|||
|
|
@ -5,14 +5,16 @@ import { join } from "path"
|
|||
const CONFIG_DIR = join(homedir(), ".config", "sandlot")
|
||||
const CONFIG_PATH = join(CONFIG_DIR, "config.json")
|
||||
|
||||
export const DEFAULTS = {
|
||||
export const DEFAULTS: Record<string, string | string[]> = {
|
||||
memory: "16G",
|
||||
} as const
|
||||
hosts: [],
|
||||
}
|
||||
|
||||
export type Key = keyof typeof DEFAULTS
|
||||
|
||||
export interface Config {
|
||||
memory?: string
|
||||
hosts?: string[]
|
||||
}
|
||||
|
||||
const MIN_MEMORY_MB = 512
|
||||
|
|
|
|||
23
src/vm.ts
23
src/vm.ts
|
|
@ -246,6 +246,22 @@ echo '${claudeJson}' > ~/.claude.json
|
|||
`}`.quiet()
|
||||
}
|
||||
|
||||
/** Resolve hostnames on the host (via mDNS) and add them to the container's /etc/hosts.
|
||||
* Reads the "hosts" array from config (e.g. ["claude.toes.local"]). */
|
||||
async function syncLocalHosts(): Promise<void> {
|
||||
const hostnames = (await getConfig("hosts")) as string[] | undefined
|
||||
if (!hostnames?.length) return
|
||||
const entries: string[] = []
|
||||
for (const name of hostnames) {
|
||||
const out = (await $`dscacheutil -q host -a name ${name}`.nothrow().quiet().text()).trim()
|
||||
const match = out.match(/ip_address:\s+(\S+)/)
|
||||
if (match) entries.push(`${match[1]} ${name}`)
|
||||
}
|
||||
if (!entries.length) return
|
||||
const block = entries.join("\\n")
|
||||
await $`container exec ${CONTAINER_NAME} bash -c ${`grep -v '# sandlot-hosts' /etc/hosts > /tmp/hosts.clean; echo -e '${block}' | sed 's/$/ # sandlot-hosts/' >> /tmp/hosts.clean; cp /tmp/hosts.clean /etc/hosts`}`.nothrow().quiet()
|
||||
}
|
||||
|
||||
// ── create() ────────────────────────────────────────────────────────
|
||||
|
||||
/** Create and provision the container from scratch. Fails if it already exists. */
|
||||
|
|
@ -273,6 +289,7 @@ export async function create(log?: (msg: string) => void): Promise<void> {
|
|||
|
||||
log?.("Configuring environment")
|
||||
await configureEnvironment(home, apiKey)
|
||||
await syncLocalHosts()
|
||||
}
|
||||
|
||||
/** Start a stopped container. */
|
||||
|
|
@ -282,6 +299,7 @@ export async function start(): Promise<void> {
|
|||
if (s === "running") return
|
||||
if (s === "missing") throw new Error("Container does not exist. Use 'sandlot vm create' first.")
|
||||
await run($`container start ${CONTAINER_NAME}`, "Container start")
|
||||
await syncLocalHosts()
|
||||
}
|
||||
|
||||
/** Ensure the sandlot container exists and is running. Creates and provisions on first use. */
|
||||
|
|
@ -294,7 +312,10 @@ export async function ensure(log?: (msg: string) => void): Promise<void> {
|
|||
else await $`container system start --enable-kernel-install`.nothrow().quiet()
|
||||
|
||||
const s = await status()
|
||||
if (s === "running") return
|
||||
if (s === "running") {
|
||||
await syncLocalHosts()
|
||||
return
|
||||
}
|
||||
|
||||
if (s === "stopped") {
|
||||
await start()
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user