#!/usr/bin/env bash
# ============================================================
# Docker backup — volumes + compose + container config + images
#
# Docker volumes hold persistent data that lives in /var/lib/docker
# (NOT in the cPanel home), so rsync of /home misses them entirely.
# This captures everything needed to rebuild the stacks elsewhere:
#   volumes/     every named volume, tar.gz'd (the DATA — critical)
#   compose/     every docker-compose file + its sibling .env
#   inspect/     docker inspect of all containers (env, ports, mounts)
#   images/      locally-BUILT images (no public source) via docker save
#   MANIFEST.txt ps -a / images / volume ls listings
#
# Public images (postgres, redis, supabase, n8n, …) are NOT saved — they
# re-pull from their registry. Use --all-images to save those too.
#
#   sudo ops/docker-backup.sh                 # volumes + compose + custom images
#   sudo ops/docker-backup.sh --no-images     # skip images entirely (smallest)
#   sudo ops/docker-backup.sh --all-images    # also save public images (large)
# Output: backups/docker-<TS>/  (chmod 700 — volumes/compose hold secrets)
# ============================================================
set -uo pipefail

REPO_ROOT="/home/ashraffarid2010/halavoice.store"
TS="$(date -u +%Y%m%d-%H%M%S)"
DEST="$REPO_ROOT/backups/docker-$TS"
IMG_MODE="custom"
while [ $# -gt 0 ]; do
  case "$1" in
    --no-images) IMG_MODE="none" ;;
    --all-images) IMG_MODE="all" ;;
    *) echo "unknown arg: $1" >&2; exit 2 ;;
  esac; shift
done

log(){ echo -e "\033[1;36m[docker-backup]\033[0m $*"; }
ok(){ echo -e "\033[1;32m[ ok ]\033[0m $*"; }
warn(){ echo -e "\033[1;33m[warn]\033[0m $*" >&2; }
[ "$(id -u)" = 0 ] || { echo "run as root (sudo)"; exit 1; }
command -v docker >/dev/null || { echo "docker not found"; exit 1; }
mkdir -p "$DEST"/{volumes,compose,inspect,images}; chmod -R 700 "$DEST"

# ---- volumes (the critical data) ----
log "Backing up $(docker volume ls -q | wc -l) volumes…"
docker volume ls -q 2>/dev/null | while read -r v; do
  [ -n "$v" ] || continue
  if docker run --rm -v "$v":/from:ro -v "$DEST/volumes":/to alpine \
       tar czf "/to/$v.tar.gz" -C /from . 2>/dev/null; then
    echo "    $v ($(du -h "$DEST/volumes/$v.tar.gz" 2>/dev/null | cut -f1))"
  else warn "  failed: $v"; fi
done
ok "volumes: $(ls "$DEST/volumes" 2>/dev/null | wc -l) archived ($(du -sh "$DEST/volumes" 2>/dev/null | cut -f1))"

# ---- compose files + sibling .env ----
log "Collecting docker-compose files…"
find /home /root /opt -maxdepth 4 \( -name 'docker-compose.y*ml' -o -name 'compose.y*ml' \) 2>/dev/null \
  | grep -v node_modules | while read -r cf; do
    d="$(dirname "$cf")"; safe="$(echo "$d" | tr '/' '_')"
    mkdir -p "$DEST/compose/$safe"
    cp -a "$cf" "$DEST/compose/$safe/" 2>/dev/null
    [ -f "$d/.env" ] && cp -a "$d/.env" "$DEST/compose/$safe/.env" 2>/dev/null
  done
ok "compose: $(find "$DEST/compose" -name 'docker-compose*' -o -name 'compose*' 2>/dev/null | wc -l) stacks"

# ---- container config (env/ports/mounts/networks) ----
log "Dumping container config (docker inspect)…"
docker inspect $(docker ps -aq) > "$DEST/inspect/containers.json" 2>/dev/null || true
docker network ls --format '{{.Name}}' 2>/dev/null | while read -r n; do
  docker network inspect "$n" >> "$DEST/inspect/networks.json" 2>/dev/null || true
done
ok "inspect saved"

# ---- images ----
if [ "$IMG_MODE" != "none" ]; then
  log "Saving images (mode: $IMG_MODE)…"
  PUBLIC_RE='^(postgres|redis|mariadb|mysql|mongo|node|python|nginx|alpine|busybox|ubuntu|debian|traefik|n8nio/|chatwoot/chatwoot|pgvector/|svix/|hashicorp/)|^public\.ecr\.aws/|^ghcr\.io/|^docker\.io/library/|^registry\.'
  docker images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -v '<none>' | sort -u | while read -r img; do
    if [ "$IMG_MODE" = "custom" ] && echo "$img" | grep -qE "$PUBLIC_RE"; then
      continue  # public/registry image — re-pullable, skip
    fi
    safe="$(echo "$img" | tr '/:' '__')"
    if docker save "$img" 2>/dev/null | gzip -1 > "$DEST/images/$safe.tar.gz"; then
      echo "    $img ($(du -h "$DEST/images/$safe.tar.gz" | cut -f1))"
    else warn "  save failed: $img"; rm -f "$DEST/images/$safe.tar.gz"; fi
  done
  ok "images: $(ls "$DEST/images" 2>/dev/null | wc -l) saved ($(du -sh "$DEST/images" 2>/dev/null | cut -f1))"
fi

# ---- manifest ----
{
  echo "Docker backup — $(date -u +%FT%TZ) — host $(hostname)"
  echo; echo "== CONTAINERS =="; docker ps -a --format '{{.Names}}\t{{.Image}}\t{{.Status}}' 2>/dev/null
  echo; echo "== VOLUMES =="; docker volume ls 2>/dev/null
  echo; echo "== IMAGES =="; docker images --format '{{.Repository}}:{{.Tag}}\t{{.Size}}' 2>/dev/null
} > "$DEST/MANIFEST.txt"
( cd "$DEST" && find . -type f -name '*.gz' -exec sha256sum {} \; > SHA256SUMS.txt 2>/dev/null || true )

chmod -R 700 "$DEST"; chown -R ashraffarid2010:ashraffarid2010 "$DEST" 2>/dev/null || true
ok "Docker backup ready: $DEST  ($(du -sh "$DEST" | cut -f1))"
echo "Pull it to your machine with WinSCP/rsync (port 21098), same as the other backups."
