feat(backend): add teardown() to the contract; backend teardown
Add an abstract teardown() classmethod to BottleBackend — the inverse of setup(), surfaced as `./cli.py backend teardown [--backend=NAME]` (uninstall). Symmetric with setup: it prints the privileged commands / declarative config change to remove the host prerequisites. - firecracker: NixOS-aware — disable the flake module (or drop the import) and rebuild, or `firecracker-netpool.sh down` imperatively. - docker / macos-container: nothing to undo (no privileged host state); print a short note. Not called by the launch path or the test suite. Extends test_cli_backend for the new dispatch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
This commit is contained in:
@@ -560,6 +560,17 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
|
||||
the backend is ready to launch and non-zero when something is
|
||||
missing. Invoked by `./cli.py backend status [--backend=…]`."""
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def teardown(cls) -> int:
|
||||
"""Undo `setup()` — the inverse operation, surfaced as
|
||||
`./cli.py backend teardown [--backend=…]` (uninstall). Symmetric
|
||||
with setup: where setup is advisory (prints the privileged
|
||||
commands / declarative config to apply), teardown prints the
|
||||
commands / config change to remove the host prerequisites. A
|
||||
backend with no host setup prints a short note and returns 0.
|
||||
Not called by the launch path or the test suite."""
|
||||
|
||||
|
||||
# Import concrete backend classes AFTER the base types are defined, so
|
||||
# each backend module can pull BottleSpec / BottlePlan / BottleBackend
|
||||
|
||||
@@ -64,6 +64,11 @@ class DockerBottleBackend(BottleBackend["DockerBottlePlan", "DockerBottleCleanup
|
||||
from . import setup as _setup
|
||||
return _setup.status()
|
||||
|
||||
@classmethod
|
||||
def teardown(cls) -> int:
|
||||
from . import setup as _setup
|
||||
return _setup.teardown()
|
||||
|
||||
def _preflight(self) -> None:
|
||||
_resolve_plan.preflight()
|
||||
|
||||
|
||||
@@ -61,6 +61,15 @@ def setup() -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def teardown() -> int:
|
||||
sys.stderr.write(
|
||||
"Docker backend: nothing to undo — it provisions no privileged host "
|
||||
"state (networks and the sidecar bundle are per-launch and are "
|
||||
"removed by `./cli.py cleanup`). Docker itself is left installed.\n"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def status() -> int:
|
||||
ok = True
|
||||
if _docker_on_path():
|
||||
|
||||
@@ -56,6 +56,11 @@ class FirecrackerBottleBackend(
|
||||
from . import setup as _setup
|
||||
return _setup.status()
|
||||
|
||||
@classmethod
|
||||
def teardown(cls) -> int:
|
||||
from . import setup as _setup
|
||||
return _setup.teardown()
|
||||
|
||||
def _preflight(self) -> None:
|
||||
_resolve_plan.preflight()
|
||||
|
||||
|
||||
@@ -81,6 +81,25 @@ def setup() -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def teardown() -> int:
|
||||
slots = netpool.all_slots()
|
||||
sys.stderr.write(
|
||||
f"Undo the Firecracker network pool ({len(slots)} slots, base "
|
||||
f"{netpool.ip_base()}) — a privileged, one-time operation.\n\n"
|
||||
)
|
||||
if _is_nixos():
|
||||
sys.stderr.write(
|
||||
"On NixOS: set `services.bot-bottle-firecracker.enable = false;` "
|
||||
"(or drop the module import) and `nixos-rebuild switch`. The TAP "
|
||||
"netdevs and nft table are removed declaratively.\n\n"
|
||||
"To tear down imperatively before a rebuild (does not persist):\n\n"
|
||||
)
|
||||
else:
|
||||
sys.stderr.write("Run the teardown as root:\n\n")
|
||||
sys.stdout.write("sudo ./scripts/firecracker-netpool.sh down\n")
|
||||
return 0
|
||||
|
||||
|
||||
def status() -> int:
|
||||
ok = True
|
||||
if netpool.nft_table_present():
|
||||
|
||||
@@ -46,6 +46,11 @@ class MacosContainerBottleBackend(
|
||||
from . import setup as _setup
|
||||
return _setup.status()
|
||||
|
||||
@classmethod
|
||||
def teardown(cls) -> int:
|
||||
from . import setup as _setup
|
||||
return _setup.teardown()
|
||||
|
||||
def _preflight(self) -> None:
|
||||
_resolve_plan.preflight()
|
||||
|
||||
|
||||
@@ -46,6 +46,16 @@ def setup() -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def teardown() -> int:
|
||||
sys.stderr.write(
|
||||
"macos-container backend: nothing to undo — it provisions no "
|
||||
"privileged host state. The Apple Container CLI and its system "
|
||||
"service are left as-is (stop the service yourself with "
|
||||
"`container system stop` if you want).\n"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def status() -> int:
|
||||
ok = True
|
||||
if _container.is_macos():
|
||||
|
||||
@@ -42,7 +42,7 @@ COMMANDS = {
|
||||
def usage() -> None:
|
||||
sys.stderr.write(f"usage: {PROG} <command> [args...]\n\n")
|
||||
sys.stderr.write("Commands:\n")
|
||||
sys.stderr.write(" backend set up or check a backend's host prerequisites (setup|status)\n")
|
||||
sys.stderr.write(" backend set up / check / undo a backend's host prerequisites (setup|status|teardown)\n")
|
||||
sys.stderr.write(" cleanup stop and remove all active bot-bottle containers\n")
|
||||
sys.stderr.write(" commit snapshot a running bottle's container state to a Docker image\n")
|
||||
sys.stderr.write(" edit open an agent in vim for editing\n")
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
"""`backend` CLI command — generic host setup/status across backends.
|
||||
|
||||
`./cli.py backend setup [--backend=NAME]` provisions (or points at how
|
||||
`./cli.py backend setup [--backend=NAME]` provisions (or points at how
|
||||
to provision) the chosen backend's one-time host prerequisites.
|
||||
`./cli.py backend status [--backend=NAME]` reports readiness.
|
||||
`./cli.py backend status [--backend=NAME]` reports readiness.
|
||||
`./cli.py backend teardown [--backend=NAME]` undoes setup (uninstall).
|
||||
|
||||
Both dispatch to the backend's `setup()` / `status()` classmethods, so
|
||||
there are no backend-specific commands — swapping backends is just a
|
||||
different `--backend` (or `$BOT_BOTTLE_BACKEND`, or the host default).
|
||||
All dispatch to the backend's `setup()` / `status()` / `teardown()`
|
||||
classmethods, so there are no backend-specific commands — swapping
|
||||
backends is just a different `--backend` (or `$BOT_BOTTLE_BACKEND`, or
|
||||
the host default).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -24,8 +26,9 @@ def cmd_backend(args: list[str]) -> int:
|
||||
)
|
||||
parser.add_argument(
|
||||
"action",
|
||||
choices=("setup", "status"),
|
||||
help="setup: provision/print host prerequisites; status: report readiness",
|
||||
choices=("setup", "status", "teardown"),
|
||||
help="setup: provision/print host prerequisites; status: report "
|
||||
"readiness; teardown: undo setup (uninstall)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--backend",
|
||||
@@ -38,4 +41,6 @@ def cmd_backend(args: list[str]) -> int:
|
||||
backend = get_bottle_backend(ns.backend)
|
||||
if ns.action == "setup":
|
||||
return backend.setup()
|
||||
if ns.action == "teardown":
|
||||
return backend.teardown()
|
||||
return backend.status()
|
||||
|
||||
Reference in New Issue
Block a user