mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
One boolean answered five unrelated questions: whether the pull-through cache stack runs, whether image pulls take the GHCR mirror, whether the docker data root may be wiped, plus runtime naming and inventory resolution. The workspace is the environment where those answers diverge — it simulates a developer machine on a real runner — so getting one of them meant misreporting where the suite runs and silently paying for the other four.
is_ci() and runs_on_github() stay facts about the machine. cache_stack_enabled, image_mirror_enabled and docker_root_ephemeral become decisions that default off those facts and can be declared explicitly, so an environment states what it wants. Every default reproduces the previous condition exactly, including the distinction the old mirror and wipe gates drew between a real runner and act.
73ae40585 got the cache stack by flipping the identity flag and lost the docker-root wipe with it; the workspace now declares all three capabilities instead. Pairs with 4c068d1cf, which makes the resulting stray bind sources harmless rather than fatal.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
77 lines
2.2 KiB
Python
77 lines
2.2 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING
|
|
|
|
from . import PROJECT_ROOT
|
|
from .env import resolve_distro
|
|
|
|
if TYPE_CHECKING:
|
|
import argparse
|
|
|
|
|
|
def _resolve_docker_root() -> Path:
|
|
raw = (os.environ.get("INFINITO_DOCKER_VOLUME") or "").strip().rstrip("/")
|
|
if not raw:
|
|
raise RuntimeError("INFINITO_DOCKER_VOLUME must be set")
|
|
return Path(raw)
|
|
|
|
|
|
def _wipe_docker_root(docker_root: Path) -> None:
|
|
if not docker_root.exists():
|
|
print(f">>> Docker root does not exist, nothing to clean: {docker_root}")
|
|
return
|
|
print(f">>> CI cleanup: wiping Docker root: {docker_root}")
|
|
subprocess.run(["sudo", "rm", "-rf", str(docker_root)], check=True)
|
|
subprocess.run(["sudo", "mkdir", "-p", str(docker_root)], check=True)
|
|
subprocess.run(
|
|
["sudo", "chown", f"{os.getuid()}:{os.getgid()}", str(docker_root)],
|
|
check=True,
|
|
)
|
|
leftovers = sorted(entry.name for entry in docker_root.iterdir())
|
|
if leftovers:
|
|
raise RuntimeError(
|
|
f"Docker root survived the wipe: {docker_root} still holds {leftovers}"
|
|
)
|
|
|
|
|
|
def _should_wipe_docker_root() -> bool:
|
|
from .profile import Profile
|
|
|
|
if not Profile().docker_root_ephemeral():
|
|
return False
|
|
return os.environ.get("INFINITO_PRESERVE_DOCKER_CACHE") != "true"
|
|
|
|
|
|
def _cleanup_docker_root() -> None:
|
|
if not _should_wipe_docker_root():
|
|
docker_vol = os.environ.get("INFINITO_DOCKER_VOLUME") or "(unset)"
|
|
print(f">>> Skipping Docker root wipe: {docker_vol}")
|
|
return
|
|
docker_root = _resolve_docker_root()
|
|
_wipe_docker_root(docker_root)
|
|
|
|
|
|
def down_stack(*, repo_root: Path, distro: str) -> None:
|
|
from .compose import Compose
|
|
|
|
print(">>> Stopping compose stack and removing volumes")
|
|
try:
|
|
Compose(repo_root=repo_root, distro=distro).run(
|
|
["down", "--remove-orphans", "-v"]
|
|
)
|
|
finally:
|
|
_cleanup_docker_root()
|
|
|
|
|
|
def add_parser(sub: argparse._SubParsersAction) -> None:
|
|
p = sub.add_parser("down", help="Stop compose stack and remove volumes.")
|
|
p.set_defaults(_handler=handler)
|
|
|
|
|
|
def handler(args: argparse.Namespace) -> int:
|
|
down_stack(repo_root=PROJECT_ROOT, distro=resolve_distro())
|
|
return 0
|