added stacks for backup

This commit is contained in:
samjage
2026-04-06 10:13:21 -04:00
parent 12892ec9db
commit c948d5cbde
22 changed files with 998 additions and 0 deletions
+158
View File
@@ -0,0 +1,158 @@
#!/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 ""
+39
View File
@@ -0,0 +1,39 @@
# image-sources.conf
# Maps Docker image names to their GitHub repos for changelog lookup.
# Format: ["image/name"]="github-owner/repo"
# Add entries here as you add new services.
declare -A IMAGE_REPOS=(
# Matrix stack
["jevolk/tuwunel"]="jevolk/tuwunel"
["livekit/livekit-server"]="livekit/livekit"
["ghcr.io/element-hq/lk-jwt-service"]="element-hq/lk-jwt-service"
["coturn/coturn"]="coturn/coturn"
# Code Server
["lscr.io/linuxserver/code-server"]="linuxserver/docker-code-server"
# Caddy
["caddy"]="caddyserver/caddy"
# Gitea
["gitea/gitea"]="go-gitea/gitea"
# Homebridge
["homebridge/homebridge"]="homebridge/homebridge"
# Mealie
["ghcr.io/mealie-recipes/mealie"]="mealie-recipes/mealie"
# Pi-hole
["pihole/pihole"]="pi-hole/pi-hole"
# Scrypted
["koush/scrypted"]="koush/scrypted"
# Wyze Bridge
["mrlt8/wyze-bridge"]="mrlt8/docker-wyze-bridge"
# Watchtower
["containrrr/watchtower"]="containrrr/watchtower"
)
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env bash
# =============================================================
# update-check.sh
# Reads Watchtower's recent logs to find outdated images,
# maps them to containers, and fetches GitHub release notes.
#
# Usage:
# bash /opt/stacks/scripts/update-check.sh
# =============================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/image-sources.conf"
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'
SEP="${GRAY}$(printf '─%.0s' $(seq 1 60))${NC}"
# ── Check Watchtower is running ───────────────────────────────
if ! docker inspect watchtower &>/dev/null; then
echo -e "${RED}✗ Watchtower is not running.${NC}"
echo -e " Start it: cd /opt/stacks/watchtower && docker compose up -d"
exit 1
fi
# ── Parse Watchtower logs for outdated images (last 36h) ─────
echo -e "${BOLD}Checking Watchtower logs for updates...${NC}"
echo ""
OUTDATED_IMAGES=()
while IFS= read -r line; do
# Extract image name from: "Found new some/image:tag image (sha256:...)"
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}✓ All images are up to date${NC} ${GRAY}(per last Watchtower scan)${NC}"
echo ""
LAST=$(docker logs watchtower --since 72h 2>&1 | grep "Session done" | tail -1 | grep -oP '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}')
[ -n "$LAST" ] && echo -e "${GRAY} Last scan: ${LAST}${NC}"
exit 0
fi
echo -e "${YELLOW}${BOLD}${#OUTDATED_IMAGES[@]} image update(s) available:${NC}"
echo ""
# ── For each outdated image: find container + fetch notes ─────
gh_release_notes() {
local repo=$1
local result
result=$(curl -sf "https://api.github.com/repos/${repo}/releases/latest" 2>/dev/null) || true
if [ -n "$result" ]; then
TAG=$(echo "$result" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tag_name','?'))" 2>/dev/null)
BODY=$(echo "$result" | python3 -c "
import sys,json,re
d=json.load(sys.stdin)
body=d.get('body','No release notes available.')
# Strip markdown links/badges, collapse whitespace
body=re.sub(r'!\[.*?\]\(.*?\)','',body)
body=re.sub(r'\[([^\]]+)\]\([^\)]+\)',r'\1',body)
body=re.sub(r'<!--.*?-->','',body,flags=re.DOTALL)
lines=[l.strip() for l in body.split('\n') if l.strip()]
print('\n'.join(lines[:6]))
" 2>/dev/null || echo "No release notes available.")
echo "$TAG|$BODY"
fi
}
for image in "${OUTDATED_IMAGES[@]}"; do
# Find container(s) using this image
CONTAINERS=$(docker ps -a --format '{{.Names}}|{{.Image}}' | awk -F'|' -v img="$image" '$2==img {print $1}' | tr '\n' ' ')
[ -z "$CONTAINERS" ] && CONTAINERS="${GRAY}(no running containers)${NC}"
echo -e "$SEP"
echo -e " ${CYAN}${BOLD}${image}${NC}"
echo -e " ${GRAY}containers:${NC} ${CONTAINERS}"
# Strip tag for image name lookup
base_image=$(echo "$image" | sed 's/:.*//')
if [ -n "${IMAGE_REPOS[$base_image]+_}" ]; then
REPO="${IMAGE_REPOS[$base_image]}"
RELEASE=$(gh_release_notes "$REPO")
if [ -n "$RELEASE" ]; then
TAG=$(echo "$RELEASE" | cut -d'|' -f1)
NOTES=$(echo "$RELEASE" | cut -d'|' -f2-)
echo -e " ${GRAY}latest tag:${NC} ${GREEN}${TAG}${NC}"
echo -e " ${GRAY}notes:${NC}"
while IFS= read -r line; do
echo -e " ${GRAY}${line}${NC}"
done <<< "$NOTES"
echo -e " ${GRAY}full notes:${NC} https://github.com/${REPO}/releases/latest"
else
echo -e " ${GRAY}→ https://hub.docker.com/r/${base_image}/tags${NC}"
fi
else
echo -e " ${GRAY}no GitHub mapping — add to scripts/image-sources.conf${NC}"
echo -e " ${GRAY}→ https://hub.docker.com/r/${base_image}/tags${NC}"
fi
echo ""
done
echo -e "$SEP"
echo ""
echo -e " Ready to update? Run: ${CYAN}bash /opt/stacks/scripts/do-update.sh${NC}"
echo ""