mirror of
https://github.com/samjage/lab.git
synced 2026-06-06 02:40:43 +00:00
159 lines
5.5 KiB
Bash
159 lines
5.5 KiB
Bash
#!/usr/bin/env bash
|
|
# =============================================================
|
|
# do-update.sh
|
|
# Interactive picker to update containers flagged by Watchtower.
|
|
# Pulls new image, recreates container via its compose file,
|
|
# and prunes old images.
|
|
#
|
|
# Usage:
|
|
# bash /opt/stacks/scripts/do-update.sh
|
|
# =============================================================
|
|
|
|
set -euo pipefail
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
GRAY='\033[0;90m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m'
|
|
|
|
# ── Check Watchtower is running ───────────────────────────────
|
|
if ! docker inspect watchtower &>/dev/null; then
|
|
echo -e "${RED}✗ Watchtower is not running.${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
# ── Get outdated images from Watchtower logs ──────────────────
|
|
OUTDATED_IMAGES=()
|
|
while IFS= read -r line; do
|
|
img=$(echo "$line" | grep -oP '(?<=Found new )\S+(?= image)')
|
|
[ -n "$img" ] && OUTDATED_IMAGES+=("$img")
|
|
done < <(docker logs watchtower --since 36h 2>&1 | grep "Found new")
|
|
|
|
if [ ${#OUTDATED_IMAGES[@]} -eq 0 ]; then
|
|
echo -e "${GREEN}✓ No updates available per last Watchtower scan.${NC}"
|
|
exit 0
|
|
fi
|
|
|
|
# ── Build list of containers to update ───────────────────────
|
|
declare -a CONTAINER_NAMES
|
|
declare -a CONTAINER_IMAGES
|
|
declare -a CONTAINER_COMPOSE_DIRS
|
|
declare -a CONTAINER_SERVICES
|
|
|
|
for image in "${OUTDATED_IMAGES[@]}"; do
|
|
while IFS='|' read -r cname cimage; do
|
|
COMPOSE_DIR=$(docker inspect "$cname" \
|
|
--format '{{index .Config.Labels "com.docker.compose.project.working_dir"}}' 2>/dev/null || echo "")
|
|
SERVICE=$(docker inspect "$cname" \
|
|
--format '{{index .Config.Labels "com.docker.compose.service"}}' 2>/dev/null || echo "")
|
|
CONTAINER_NAMES+=("$cname")
|
|
CONTAINER_IMAGES+=("$cimage")
|
|
CONTAINER_COMPOSE_DIRS+=("$COMPOSE_DIR")
|
|
CONTAINER_SERVICES+=("$SERVICE")
|
|
done < <(docker ps -a --format '{{.Names}}|{{.Image}}' | awk -F'|' -v img="$image" '$2==img')
|
|
done
|
|
|
|
if [ ${#CONTAINER_NAMES[@]} -eq 0 ]; then
|
|
echo -e "${YELLOW}⚠ Outdated images found but no matching running containers.${NC}"
|
|
exit 0
|
|
fi
|
|
|
|
# ── Show menu ─────────────────────────────────────────────────
|
|
echo ""
|
|
echo -e "${BOLD}Available updates:${NC}"
|
|
echo ""
|
|
for i in "${!CONTAINER_NAMES[@]}"; do
|
|
idx=$((i + 1))
|
|
name="${CONTAINER_NAMES[$i]}"
|
|
image="${CONTAINER_IMAGES[$i]}"
|
|
svc="${CONTAINER_SERVICES[$i]}"
|
|
dir="${CONTAINER_COMPOSE_DIRS[$i]}"
|
|
printf " ${CYAN}%2d)${NC} %-20s ${GRAY}%-40s${NC}\n" "$idx" "$name" "$image"
|
|
if [ -n "$dir" ]; then
|
|
printf " ${GRAY}compose: %s service: %s${NC}\n" "$dir" "$svc"
|
|
fi
|
|
done
|
|
echo ""
|
|
echo -e " ${GRAY}a)${NC} update all"
|
|
echo -e " ${GRAY}q)${NC} quit"
|
|
echo ""
|
|
read -rp " Enter numbers to update (e.g. 1 3 5): " SELECTION
|
|
|
|
if [ "$SELECTION" = "q" ] || [ -z "$SELECTION" ]; then
|
|
echo -e "\n ${GRAY}Cancelled.${NC}"
|
|
exit 0
|
|
fi
|
|
|
|
# ── Build list of selected indices ────────────────────────────
|
|
SELECTED=()
|
|
if [ "$SELECTION" = "a" ]; then
|
|
for i in "${!CONTAINER_NAMES[@]}"; do SELECTED+=("$i"); done
|
|
else
|
|
for num in $SELECTION; do
|
|
idx=$((num - 1))
|
|
if [ "$idx" -ge 0 ] && [ "$idx" -lt "${#CONTAINER_NAMES[@]}" ]; then
|
|
SELECTED+=("$idx")
|
|
else
|
|
echo -e " ${YELLOW}⚠ Skipping invalid selection: ${num}${NC}"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [ ${#SELECTED[@]} -eq 0 ]; then
|
|
echo -e " ${YELLOW}No valid selections.${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
# ── Confirm ───────────────────────────────────────────────────
|
|
echo ""
|
|
echo -e "${BOLD}Will update:${NC}"
|
|
for i in "${SELECTED[@]}"; do
|
|
echo -e " ${GREEN}→${NC} ${CONTAINER_NAMES[$i]} ${GRAY}(${CONTAINER_IMAGES[$i]})${NC}"
|
|
done
|
|
echo ""
|
|
read -rp " Proceed? [y/N] " CONFIRM
|
|
[[ "$CONFIRM" =~ ^[Yy]$ ]] || { echo -e " ${GRAY}Aborted.${NC}"; exit 0; }
|
|
|
|
# ── Update selected containers ────────────────────────────────
|
|
PRUNED=0
|
|
for i in "${SELECTED[@]}"; do
|
|
name="${CONTAINER_NAMES[$i]}"
|
|
image="${CONTAINER_IMAGES[$i]}"
|
|
dir="${CONTAINER_COMPOSE_DIRS[$i]}"
|
|
svc="${CONTAINER_SERVICES[$i]}"
|
|
|
|
echo ""
|
|
echo -e "${CYAN}── Updating ${name} ──────────────────────────────────────${NC}"
|
|
|
|
echo -e " ${GRAY}pulling ${image}...${NC}"
|
|
docker pull "$image"
|
|
|
|
if [ -n "$dir" ] && [ -d "$dir" ]; then
|
|
echo -e " ${GRAY}recreating via compose...${NC}"
|
|
docker compose -f "$dir/docker-compose.yml" up -d --force-recreate "$svc"
|
|
else
|
|
echo -e " ${YELLOW}⚠ No compose dir found — recreating container manually${NC}"
|
|
docker stop "$name" 2>/dev/null || true
|
|
docker rm "$name" 2>/dev/null || true
|
|
echo -e " ${YELLOW} Container removed. Restart manually with your compose file.${NC}"
|
|
fi
|
|
|
|
echo -e " ${GREEN}✓ ${name} updated${NC}"
|
|
PRUNED=1
|
|
done
|
|
|
|
# ── Prune old images ──────────────────────────────────────────
|
|
if [ "$PRUNED" -eq 1 ]; then
|
|
echo ""
|
|
echo -e "${GRAY}Pruning dangling images...${NC}"
|
|
docker image prune -f
|
|
echo -e "${GREEN}✓ Done.${NC}"
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${GREEN}${BOLD}All selected containers updated.${NC}"
|
|
echo ""
|