c73d717f71
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>
60 lines
2.0 KiB
Python
60 lines
2.0 KiB
Python
"""SmolmachinesBottlePlan — concrete BottlePlan for the smolmachines
|
|
backend (PRD 0023).
|
|
|
|
Chunk 1 + 2a fields: slug, smolfile_path, bundle docker subnet /
|
|
gateway / pinned IP. VM lifecycle + provisioning fields (machine
|
|
name, `.smolmachine` artifact path, etc.) land in later chunks
|
|
as the launch flow grows."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
from ...log import info
|
|
from .. import BottlePlan
|
|
from ..print_util import print_multi
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SmolmachinesBottlePlan(BottlePlan):
|
|
"""Resolved fields the launch step needs to bring up the bottle.
|
|
|
|
Inherits `spec` and `stage_dir` from BottlePlan."""
|
|
|
|
slug: str
|
|
smolfile_path: Path
|
|
# Per-bottle docker subnet for the sidecar bundle container.
|
|
# The bundle runs at `bundle_ip` (always `.2`); the gateway is
|
|
# at `.1`. smolvm's TSI allowlist is set to `bundle_ip/32`.
|
|
bundle_subnet: str
|
|
bundle_gateway: str
|
|
bundle_ip: str
|
|
|
|
def print(self, *, remote_control: bool) -> None:
|
|
"""Compact y/N preflight. Same shape as the Docker
|
|
backend's so operators see one format across backends."""
|
|
del remote_control # not surfaced in the compact summary
|
|
spec = self.spec
|
|
manifest = spec.manifest
|
|
agent = manifest.agents[spec.agent_name]
|
|
bottle = manifest.bottle_for(spec.agent_name)
|
|
|
|
env_names = sorted(bottle.env.keys())
|
|
upstreams = [
|
|
f"{g.Name} → {g.Upstream}" for g in bottle.git
|
|
]
|
|
routes = [r.host for r in bottle.egress.routes]
|
|
|
|
print(file=sys.stderr)
|
|
info(f"agent : {spec.agent_name}")
|
|
print_multi("env ", env_names)
|
|
print_multi("skills ", list(agent.skills))
|
|
info(f"bottle : {agent.bottle}")
|
|
if upstreams:
|
|
print_multi(" git gate ", upstreams)
|
|
if routes:
|
|
print_multi(" egress ", routes)
|
|
print(file=sys.stderr)
|