mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-02 07:34:45 +08:00
297 lines
9.2 KiB
Bash
Executable File
297 lines
9.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# Clean up stale Docker images, volumes, and build cache that are not in use
|
|
# by the currently deployed OpenShell cluster.
|
|
#
|
|
# Preserves:
|
|
# - Current openshell/* :dev images (and 127.0.0.1:5000 registry mirrors)
|
|
# - Images actively used by running containers
|
|
# - Common base/infrastructure images (registry, k3s, alpine, rust, python, etc.)
|
|
# - Volumes attached to running containers
|
|
#
|
|
# Usage:
|
|
# ./scripts/docker-cleanup.sh [options]
|
|
#
|
|
# Options:
|
|
# --dry-run Show what would be removed without deleting anything
|
|
# --force Skip the confirmation prompt
|
|
# --skip-cache Skip build cache pruning (cache speeds up subsequent builds)
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "${SCRIPT_DIR}/../tasks/scripts/container-engine.sh"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Options
|
|
# ---------------------------------------------------------------------------
|
|
DRY_RUN=false
|
|
SKIP_CACHE=false
|
|
FORCE=false
|
|
|
|
usage() {
|
|
sed -n '/^# Usage:/,/^$/p' "$0" | sed 's/^# \?//'
|
|
exit 0
|
|
}
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--dry-run) DRY_RUN=true ;;
|
|
--skip-cache) SKIP_CACHE=true ;;
|
|
--force) FORCE=true ;;
|
|
-h|--help) usage ;;
|
|
*) echo "Unknown option: $arg"; usage ;;
|
|
esac
|
|
done
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
BOLD='\033[1m'
|
|
DIM='\033[2m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
RED='\033[0;31m'
|
|
RESET='\033[0m'
|
|
|
|
info() { echo -e "${BOLD}==> $*${RESET}"; }
|
|
note() { echo -e " ${DIM}$*${RESET}"; }
|
|
ok() { echo -e " ${GREEN}$*${RESET}"; }
|
|
warn() { echo -e " ${YELLOW}$*${RESET}"; }
|
|
err() { echo -e " ${RED}$*${RESET}"; }
|
|
dry() { echo -e " ${YELLOW}[dry-run]${RESET} $*"; }
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Preflight
|
|
# ---------------------------------------------------------------------------
|
|
# container-engine.sh already validates that an engine is available.
|
|
|
|
if ! ce info &>/dev/null; then
|
|
echo "Error: Container engine (${CONTAINER_ENGINE}) is not running" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Image repository prefixes that belong to the CURRENT cluster deployment.
|
|
# Any tagged image whose repository starts with one of these is kept.
|
|
# ---------------------------------------------------------------------------
|
|
CURRENT_IMAGE_PREFIXES=(
|
|
"openshell/"
|
|
"127.0.0.1:5000/openshell/"
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Infrastructure / base images to always keep (matched by repository prefix).
|
|
# These are pulled from remote registries and are expensive to re-download.
|
|
# ---------------------------------------------------------------------------
|
|
KEEP_IMAGE_PREFIXES=(
|
|
"registry"
|
|
"rancher/k3s"
|
|
"ghcr.io/k3d-io/"
|
|
"kindest/"
|
|
"alpine"
|
|
"python"
|
|
"rust"
|
|
"ghcr.io/astral-sh/uv"
|
|
"redis"
|
|
"pgvector/"
|
|
"minio/"
|
|
)
|
|
|
|
# Returns 0 if the image repository should be kept, 1 otherwise.
|
|
should_keep_image() {
|
|
local repo="$1"
|
|
|
|
# Keep current OpenShell images
|
|
for prefix in "${CURRENT_IMAGE_PREFIXES[@]}"; do
|
|
if [[ "$repo" == "$prefix"* ]]; then
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
# Keep infrastructure / base images
|
|
for prefix in "${KEEP_IMAGE_PREFIXES[@]}"; do
|
|
if [[ "$repo" == "$prefix"* ]]; then
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Snapshot disk usage before cleanup
|
|
# ---------------------------------------------------------------------------
|
|
info "Current disk usage (${CONTAINER_ENGINE})"
|
|
ce system df
|
|
echo
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Confirmation
|
|
# ---------------------------------------------------------------------------
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
warn "Running in dry-run mode -- nothing will be deleted"
|
|
echo
|
|
elif [[ "$FORCE" != true ]]; then
|
|
echo -e "${BOLD}This will remove stale images, unused volumes, and build cache.${RESET}"
|
|
echo "The currently deployed OpenShell images and running containers are preserved."
|
|
echo
|
|
read -r -p "Continue? [y/N] " confirm
|
|
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
|
echo "Aborted."
|
|
exit 0
|
|
fi
|
|
echo
|
|
fi
|
|
|
|
# Track totals for the summary
|
|
TOTAL_IMAGES_REMOVED=0
|
|
TOTAL_VOLUMES_REMOVED=0
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Step 1: Remove dangling (untagged) images
|
|
# ---------------------------------------------------------------------------
|
|
info "Removing dangling images..."
|
|
|
|
dangling_count=$(ce images --filter "dangling=true" -q | wc -l | tr -d ' ')
|
|
if [[ "$dangling_count" -gt 0 ]]; then
|
|
note "Found $dangling_count dangling image(s)"
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
dry "Would remove $dangling_count dangling image(s)"
|
|
else
|
|
ce image prune -f | tail -1
|
|
TOTAL_IMAGES_REMOVED=$((TOTAL_IMAGES_REMOVED + dangling_count))
|
|
fi
|
|
else
|
|
ok "No dangling images found"
|
|
fi
|
|
echo
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Step 2: Remove stale tagged images
|
|
# ---------------------------------------------------------------------------
|
|
info "Removing stale tagged images..."
|
|
|
|
# Collect image IDs that are directly used by running containers so we never
|
|
# touch them regardless of tag matching.
|
|
container_ids=($(ce ps -q 2>/dev/null))
|
|
if [[ ${#container_ids[@]} -gt 0 ]]; then
|
|
running_image_ids=$(ce inspect --format '{{.Image}}' "${container_ids[@]}" 2>/dev/null | sort -u)
|
|
else
|
|
running_image_ids=""
|
|
fi
|
|
|
|
stale_images=()
|
|
while IFS=$'\t' read -r repo tag id; do
|
|
# Skip dangling images (already handled above)
|
|
[[ "$repo" == "<none>" ]] && continue
|
|
|
|
# Never remove images actively used by a container
|
|
if echo "$running_image_ids" | grep -q "$id" 2>/dev/null; then
|
|
continue
|
|
fi
|
|
|
|
if ! should_keep_image "$repo"; then
|
|
stale_images+=("${repo}:${tag}")
|
|
fi
|
|
done < <(ce images --format '{{.Repository}}\t{{.Tag}}\t{{.ID}}')
|
|
|
|
if [[ ${#stale_images[@]} -gt 0 ]]; then
|
|
for img in "${stale_images[@]}"; do
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
dry "Would remove $img"
|
|
else
|
|
note "Removing $img"
|
|
ce rmi "$img" >/dev/null 2>&1 || warn "Could not remove $img (may share layers)"
|
|
TOTAL_IMAGES_REMOVED=$((TOTAL_IMAGES_REMOVED + 1))
|
|
fi
|
|
done
|
|
else
|
|
ok "No stale tagged images found"
|
|
fi
|
|
echo
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Step 3: Remove unused volumes
|
|
# ---------------------------------------------------------------------------
|
|
info "Removing unused volumes..."
|
|
|
|
# Identify volumes in use by running containers
|
|
container_ids=($(ce ps -q 2>/dev/null))
|
|
if [[ ${#container_ids[@]} -gt 0 ]]; then
|
|
in_use_volumes=$(ce inspect --format '{{range .Mounts}}{{.Name}} {{end}}' "${container_ids[@]}" 2>/dev/null \
|
|
| tr ' ' '\n' | sort -u | grep -v '^$' || true)
|
|
else
|
|
in_use_volumes=""
|
|
fi
|
|
|
|
unused_volumes=()
|
|
while read -r vol; do
|
|
[[ -z "$vol" ]] && continue
|
|
if ! echo "$in_use_volumes" | grep -qx "$vol" 2>/dev/null; then
|
|
unused_volumes+=("$vol")
|
|
fi
|
|
done < <(ce volume ls -q)
|
|
|
|
if [[ ${#unused_volumes[@]} -gt 0 ]]; then
|
|
note "Found ${#unused_volumes[@]} unused volume(s)"
|
|
for vol in "${unused_volumes[@]}"; do
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
dry "Would remove volume $vol"
|
|
else
|
|
ce volume rm "$vol" >/dev/null 2>&1 || warn "Could not remove volume $vol"
|
|
TOTAL_VOLUMES_REMOVED=$((TOTAL_VOLUMES_REMOVED + 1))
|
|
fi
|
|
done
|
|
else
|
|
ok "No unused volumes found"
|
|
fi
|
|
echo
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Step 4: Prune build cache
|
|
# ---------------------------------------------------------------------------
|
|
if [[ "$SKIP_CACHE" == true ]]; then
|
|
info "Skipping build cache prune (--skip-cache)"
|
|
else
|
|
info "Pruning build cache..."
|
|
cache_size=$(ce system df --format '{{.Size}}' 2>/dev/null | tail -1)
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
dry "Would prune build cache (current size: ${cache_size:-unknown})"
|
|
else
|
|
ce_builder_prune -af 2>&1 | tail -1
|
|
fi
|
|
fi
|
|
echo
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Step 5: Clean up any newly-dangling images left after tagged image removal
|
|
# ---------------------------------------------------------------------------
|
|
remaining_dangling=$(ce images --filter "dangling=true" -q | wc -l | tr -d ' ')
|
|
if [[ "$remaining_dangling" -gt 0 ]]; then
|
|
info "Cleaning up $remaining_dangling residual dangling image(s)..."
|
|
if [[ "$DRY_RUN" != true ]]; then
|
|
ce image prune -f >/dev/null 2>&1
|
|
TOTAL_IMAGES_REMOVED=$((TOTAL_IMAGES_REMOVED + remaining_dangling))
|
|
fi
|
|
echo
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Summary
|
|
# ---------------------------------------------------------------------------
|
|
info "Cleanup complete"
|
|
echo
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
warn "Dry run -- no changes were made. Re-run without --dry-run to apply."
|
|
else
|
|
ce system df
|
|
fi
|
|
echo
|
|
|
|
info "Remaining images:"
|
|
ce images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
|