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>