a3a9ec065e
`./cli.py cleanup` previously called only the env-var-selected backend's `prepare_cleanup` / `cleanup` — so a leftover smolvm machine + bundle container + bundle network from a crashed smolmachines bottle would survive a default `docker`-mode cleanup indefinitely. Smolmachines now has a real `cleanup` module (alongside `enumerate.py` from issue #77) that walks: - smolvm machines named `claude-bottle-*` (via `smolvm machine ls --json`) - bundle containers `claude-bottle-sidecars-*` - bundle networks `claude-bottle-bundle-*` Cleanup runs stop+delete on the machines, force-rm on the containers, network rm on the networks. Each step is best-effort so a failed rm doesn't block the rest. `cli.py cleanup` walks every backend in `known_backend_names()` and runs each backend's `cleanup` after a single y/N prompt that shows a combined plan. State dirs (`~/.claude-bottle/state/<slug>/`) are shared layout with the docker backend, which still owns the orphan-state-dir bucket. It now consults `enumerate_active_bottles()` for the cross-backend live identity set so a running smolmachines bottle's state dir isn't reaped during a cleanup. Tests: smolmachines cleanup (prepare + cleanup ordering + failure handling); cross-backend orphan protection on the docker state-dir check; CLI cmd_cleanup walks both backends, short- circuits on all-empty, aborts on N. 617 unit tests pass. End-to-end verified on this host: $ smolvm machine ls --json | jq '.[].name' "claude-bottle-researcher-m3hxd" $ ./cli.py cleanup --- smolmachines backend --- smolvm machine: claude-bottle-researcher-m3hxd remove all of the above? [y/N] Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
56 lines
1.9 KiB
Python
56 lines
1.9 KiB
Python
"""SmolmachinesBottleCleanupPlan — concrete BottleCleanupPlan (issue #77).
|
|
|
|
Tracks the resources `SmolmachinesBottleBackend.cleanup` will
|
|
remove:
|
|
|
|
- machines: smolvm machines whose name starts with
|
|
`claude-bottle-` (running or stopped). Stopped +
|
|
deleted via `smolvm machine stop` + `machine delete -f`.
|
|
- bundles: docker containers `claude-bottle-sidecars-<slug>`
|
|
left over from a smolmachines bottle (the bundle's
|
|
port-forwards stay published on lo0 aliases until
|
|
the container is gone). Removed via `docker rm -f`.
|
|
- networks: docker networks `claude-bottle-bundle-<slug>`
|
|
attached to the bundles. Removed via
|
|
`docker network rm`.
|
|
|
|
Smolmachines state dirs live under the same `~/.claude-bottle/state/`
|
|
path the docker backend uses; the docker backend's
|
|
`prepare_cleanup` already enumerates orphan state dirs and is the
|
|
single source of truth for that bucket (consults
|
|
`enumerate_active_bottles()` so it doesn't reap a live
|
|
smolmachines bottle's dir)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from dataclasses import dataclass
|
|
|
|
from ...log import info
|
|
from .. import BottleCleanupPlan
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SmolmachinesBottleCleanupPlan(BottleCleanupPlan):
|
|
"""Resources SmolmachinesBottleBackend.cleanup will remove.
|
|
Produced by `prepare_cleanup`; sorted so the y/N output is
|
|
stable."""
|
|
|
|
machines: tuple[str, ...] = ()
|
|
bundles: tuple[str, ...] = ()
|
|
networks: tuple[str, ...] = ()
|
|
|
|
@property
|
|
def empty(self) -> bool:
|
|
return not self.machines and not self.bundles and not self.networks
|
|
|
|
def print(self) -> None:
|
|
print(file=sys.stderr)
|
|
for name in self.machines:
|
|
info(f"smolvm machine: {name}")
|
|
for name in self.bundles:
|
|
info(f"bundle container:{name}")
|
|
for name in self.networks:
|
|
info(f"bundle network: {name}")
|
|
print(file=sys.stderr)
|