"""Slug / preflight / subnet helpers for the smolmachines backend (PRD 0023). Kept in its own module so the renderers can be unit-tested without importing the docker subprocess paths.""" from __future__ import annotations import hashlib import shutil from ...log import die def smolmachines_preflight() -> None: """Ensure `smolvm` is on PATH before the launch flow runs. Called from `_resolve_plan`; gives the operator a clear install pointer rather than a cryptic FileNotFoundError later. `gvproxy` is no longer required — see the PRD's design pivot section.""" if shutil.which("smolvm") is not None: return die( "BOT_BOTTLE_BACKEND=smolmachines requires `smolvm` on " "PATH. Install with: " "curl -sSL https://smolmachines.com/install.sh | sh. " "To use the legacy Docker backend instead, set " "BOT_BOTTLE_BACKEND=docker or pass --backend=docker." ) def smolmachines_bundle_subnet(slug: str) -> tuple[str, str, str]: """Derive a per-bottle docker subnet + gateway IP + bundle IP from the slug. Returns `(subnet_cidr, gateway_ip, bundle_ip)`. The third octet comes from SHA-256 of the slug mod 254 (skipping 17 to avoid the docker-default bridge), so parallel bottles get distinct /24s and `resume` reuses the same /24. The bundle container always lands at `.2`; gateway is `.1`; the smolvm Smolfile's `allow_cidrs` is `/32`.""" digest = hashlib.sha256(slug.encode("utf-8")).digest() octet = (digest[0] % 254) + 1 # Skip the docker-default bridge to dodge the most common # collision (operators with `docker0` at 172.17.x.x or a # 192.168.17.x VPN client). if octet == 17: octet = 18 subnet = f"192.168.{octet}.0/24" gateway = f"192.168.{octet}.1" bundle_ip = f"192.168.{octet}.2" return subnet, gateway, bundle_ip