diff --git a/CHANGELOG.md b/CHANGELOG.md index e6d85764..55b65fe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,16 @@ All notable changes to the Lager platform are documented here. For detailed rele server's rotation replay detection). Found by the box-lifecycle CI's first supervised runs. +- **`lager install` and `lager uninstall` no longer remove containers and + images they do not own.** The deploy script stopped and force-removed + EVERY container on the box and pruned every unused image; uninstall's + image cleanup did the same prune. On a box that also runs third-party + containers (a management agent, a user's own services) this destroyed + infrastructure the tooling cannot restore. Cleanup is now scoped to the + containers lager creates (`lager`, `pigpio`, legacy `controller`), the + lager image, and dangling layers. Found by the box-lifecycle CI's first + supervised reinstall, which took down the box's gateway agent. + ## [0.32.3] - 2026-07-22 ### Fixed diff --git a/cli/commands/utility/uninstall.py b/cli/commands/utility/uninstall.py index f01600e6..058e2fbe 100644 --- a/cli/commands/utility/uninstall.py +++ b/cli/commands/utility/uninstall.py @@ -603,7 +603,7 @@ def query_ssh(cmd): click.echo(" - Docker containers (lager, pigpio)") click.echo(" - Docker network (lagernet)") if not keep_docker_images: - click.echo(" - Docker images (ALL unused images on the box, not only lager's)") + click.echo(" - Docker images (the lager image and dangling layers only)") click.echo(" - ~/box directory") if not keep_config: click.echo(" - /etc/lager directory (saved nets)") @@ -707,10 +707,15 @@ def run_priv_session(steps): run_ssh("docker network rm lagernet 2>/dev/null", "Removing lagernet network", allow_fail=True) click.echo() - # Remove Docker images (unless --keep-docker-images) + # Remove Docker images (unless --keep-docker-images). Scoped to the + # lager image plus dangling layers: 'prune -af' would also delete + # images belonging to any OTHER stopped container on the box (a + # management agent, a user's own services) — infrastructure this + # command cannot restore. click.secho("[Step 2/5] Cleaning Docker...", fg='cyan') if not keep_docker_images: - run_ssh("docker image prune -af 2>/dev/null", "Removing Docker images", allow_fail=True) + run_ssh("docker rmi -f lager 2>/dev/null", "Removing lager image", allow_fail=True) + run_ssh("docker image prune -f 2>/dev/null", "Removing dangling images", allow_fail=True) run_ssh("docker builder prune -af 2>/dev/null", "Clearing Docker build cache", allow_fail=True) else: click.echo(" Skipping Docker image removal (--keep-docker-images)") diff --git a/cli/deployment/scripts/setup_and_deploy_box.sh b/cli/deployment/scripts/setup_and_deploy_box.sh index c84f1ccf..af0a1fa4 100755 --- a/cli/deployment/scripts/setup_and_deploy_box.sh +++ b/cli/deployment/scripts/setup_and_deploy_box.sh @@ -1339,33 +1339,37 @@ print_info "Ensuring Docker service is enabled (for auto-start on boot)..." ssh $SSH_OPTS "${BOX_USER}@${BOX_IP}" "sudo systemctl enable docker >/dev/null 2>&1 || true" print_success "Docker service enabled" -print_info "Stopping and removing all existing containers..." -# Update restart policy first to prevent auto-restart, then force remove all containers +print_info "Stopping and removing lager containers..." +# Scoped to the containers this deployment owns (lager, pigpio, and the +# legacy controller). A box may run third-party containers alongside lager +# — a management agent, a user's own services — and removing containers we +# did not create takes down infrastructure this script cannot restore. +# Update restart policy first to prevent auto-restart, then force remove. ssh $SSH_OPTS "${BOX_USER}@${BOX_IP}" " - # Disable auto-restart on all containers first - docker update --restart=no \$(docker ps -aq) 2>/dev/null || true - # Stop all running containers - docker stop \$(docker ps -q) 2>/dev/null || true - # Remove all containers (running and stopped) - docker rm -f \$(docker ps -aq) 2>/dev/null || true - # Prune any leftover containers - docker container prune -f 2>/dev/null || true + for c in lager pigpio controller; do + docker update --restart=no \$c 2>/dev/null || true + docker stop \$c 2>/dev/null || true + docker rm -f \$c 2>/dev/null || true + done # Wait a moment for Docker to clean up sleep 2 " 2>/dev/null || true -print_success "All containers cleaned up" +print_success "Lager containers cleaned up" -print_info "Cleaning up Docker images and build cache to free disk space..." +print_info "Cleaning up Docker build cache and dangling images..." echo "" +# Dangling-only (no -a): 'prune -af' would also delete images belonging to +# third-party containers stopped at this moment, which cannot be re-pulled +# by this script. ssh $SSH_OPTS "${BOX_USER}@${BOX_IP}" " - echo 'Removing unused Docker images...' - docker image prune -af 2>/dev/null || true + echo 'Removing dangling Docker images...' + docker image prune -f 2>/dev/null || true echo 'Removing Docker build cache...' docker builder prune -af 2>/dev/null || true echo 'Cleanup complete' " 2>/dev/null || true echo "" -print_success "Docker images and build cache cleaned up" +print_success "Docker build cache and dangling images cleaned up" print_info "Checking available disk space..." ssh $SSH_OPTS "${BOX_USER}@${BOX_IP}" "df -h / | tail -n 1 | awk '{print \"Available: \" \$4 \" (\" \$5 \" used)\"}'" 2>/dev/null || true