9c333bc13081a0d5c48bc5788d7d7bb44e56ef7d
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9c333bc130 |
feat(smolmachines): smolvm subprocess wrapper (PRD 0023 chunk 2b)
claude_bottle/backend/smolmachines/smolvm.py — one thin Python
function per smolvm CLI subcommand the launch flow needs:
- pack_create(image, output) → smolvm pack create
- machine_create(name, from_path,
smolfile) → smolvm machine create
- machine_start(name) → smolvm machine start
- machine_stop(name) → smolvm machine stop
- machine_delete(name) → smolvm machine delete -f
- machine_exec(name, argv, env,
workdir, timeout) → smolvm machine exec
- machine_cp(src, dst) → smolvm machine cp
- is_available() → shutil.which check
The wrapper hides the CLI's inconsistent name-flag style
(positional NAME on create/delete, --name on start/stop/exec/
status) behind a uniform `name=` kwarg.
Two return shapes:
- SmolvmRunResult (returncode + stdout + stderr) from
machine_exec, because callers care about the in-VM
command's exit code.
- Raises SmolvmError on non-zero for all other commands;
failure to create/start/stop a VM is fatal to the launch
flow, not branched on.
Tests:
- 15 unit cases mocking subprocess.run, covering argv shape
per subcommand (the --name vs positional inconsistency
locked down), SmolvmError on non-zero for non-exec paths,
SmolvmRunResult passthrough on exec, empty-path cp no-op.
- 2 integration cases against the real smolvm binary
(gated on Darwin + smolvm on PATH + not GITEA_ACTIONS):
smolvm --help responds, machine ls --json parses as a
list (the contract chunk 4's list_active will consume).
531 unit tests passing. Real-smolvm smoke green locally.
Bundle bringup + launch wiring + the localhost-reach /
egress-port-bypass probes land in chunks 2c + 2d.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
c73d717f71 |
feat(smolmachines): rewrite Smolfile to smolvm 0.8.0 schema + drop gvproxy (PRD 0023 chunk 2a)
First sub-PR of chunk 2: rewrite the renderer chunk 1 shipped to match smolvm 0.8.0's actual Smolfile shape, delete the dead gvproxy renderer + its tests, simplify the prepare flow now that there's no gvproxy socket + no loopback-port allocation. Smolfile renderer: - Old shape (under the abandoned gvproxy design): name = ..., command = [...], [[net]] attachment = "unixgram", socket = "...". - New shape (smolvm 0.8.0): env = [...] (sorted K=V pairs), [network] allow_cidrs = ["<bundle-ip>/32"]. Nothing else. image / entrypoint / cmd come from the .smolmachine artifact built in chunk 2b; cpus / memory left at smolvm defaults. - Tests assert no leakage of TSI's --outbound-localhost-only or the old gvproxy/unixgram keys. util.py: - smolmachines_gvproxy_subnet → smolmachines_bundle_subnet, returning (subnet, gateway, bundle_ip). bundle_ip is always at .2 (gateway .1); subnet is /24, third octet derived from the slug hash, skipping the docker-default 17 to avoid the common 192.168.17.x collision. - allocate_loopback_port: deleted. The bundle gets a pinned docker IP now; the agent dials that IP directly through TSI. - smolmachines_preflight: dropped the gvproxy check; only smolvm is required. prepare.py: - Drops the gvproxy.yaml render + the loopback port allocation + the gvproxy_socket field on the plan. - Derives subnet / gateway / bundle_ip from the slug and populates the new SmolmachinesBottlePlan fields. - Agent env now uses IP-literal URLs (http://<bundle-ip>:8888 etc) since the guest will have no DNS resolver inside TSI's allowlist. bottle_plan.py: - Old fields: gvproxy_config_path, gvproxy_socket, gvproxy_subnet, gvproxy_gateway, host_port_map. - New fields: bundle_subnet, bundle_gateway, bundle_ip, smolfile_path. (smolmachine artifact path lands in chunk 2b.) Net: -410 lines. Full unit suite: 516 passing. The VM lifecycle + bundle bringup + launch wiring + smoke tests land in chunk 2b. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
2aca9e609a |
refactor(backend): extract shared print_multi for plan preflights
Addresses PR #62 review comments on claude_bottle/backend/smolmachines/bottle_plan.py: - Lift the multi-value label printer (was a nested helper inside DockerBottlePlan.print) into a new module claude_bottle/backend/print_util.py:print_multi. Both backends use it for env / skills / git / egress lines. - Strip the three smolmachines-preflight lines the review flagged: the gvproxy subnet line, the smolfile path line, and the gvproxy-config path line. Internal detail — operators see the agent / env / skills / bottle / git / egress that already matter on the docker side, and nothing else. - Add `git → upstream` to the smolmachines git output to match what's useful at preflight time (the docker version shows upstream_host:port; this is similar shape). Leaves the slug=spec.identity-or-mint pattern alone pending a reply on PR comment #432 — the docker backend uses the same pattern to preserve identity across `resume`, so dropping it would silently break the resume path once smolmachines launch lands. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
20f411b22e |
feat(smolmachines): backend skeleton + Smolfile/gvproxy renderers (PRD 0023 chunk 1)
Ships the smolmachines backend's prepare side: subpackage layout,
`_BACKENDS` registration under "smolmachines", preflight check
for `smolvm` + `gvproxy` on PATH, and the two config-file
renderers (Smolfile TOML + gvproxy YAML). Launch raises
NotImplementedError until chunk 2.
New module layout (mirrors backend/docker/):
claude_bottle/backend/smolmachines/
__init__.py re-exports SmolmachinesBottleBackend
backend.py SmolmachinesBottleBackend façade
bottle.py SmolmachinesBottle stub (NotImpl until ch2)
bottle_plan.py SmolmachinesBottlePlan + .print()
bottle_cleanup_plan.py SmolmachinesBottleCleanupPlan stub
prepare.py resolve_plan: writes both config files
smolfile.py TOML renderer (stdlib, no tomli_w dep)
gvproxy_config.py YAML renderer (same shape as pipelock_yaml)
util.py preflight + per-slug subnet + loopback port
The renderers are pure functions. `resolve_plan` runs the
preflight, allocates one host-side loopback port per active
sidecar (pipelock always; git-gate / supervise conditional),
derives a per-slug gvproxy subnet (hash-mod-254, skipping the
docker-default 17), and writes:
- <stage>/gvproxy.yaml: subnet + DNS rule resolving only
`proxy.internal` + port_forwards (one per active sidecar).
- <stage>/smolfile.toml: guest command/env + virtio-net device
backed by gvproxy's unixgram socket. No TSI flags — see
PRD 0023 "Why gvproxy, not TSI".
The agent's HTTPS_PROXY etc. point at `proxy.internal:<gateway-
port>` so the guest dials through gvproxy. gvproxy resolves only
`proxy.internal` → the gateway IP, and forwards exactly the
listed ports to the host-side sidecar bundle (PRD 0024); every
other destination — host LAN, host loopback, public internet
directly — is unreachable by construction.
29 new unit tests covering renderer correctness, subnet
derivation stability + collision-avoidance, loopback port
allocation, and preflight error paths. Full unit suite: 532
passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|