changemaker.lite/scripts/mirror-images.sh

213 lines
7.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# =============================================================================
# Changemaker Lite V2 — Mirror ALL Images to Gitea Registry
#
# Pulls every Docker image used in docker-compose.prod.yml and re-pushes it
# to the Gitea container registry. After mirroring, release installs can
# pull the entire platform from a single registry — no Docker Hub, GHCR,
# LSCR, GCR, or other external registries needed.
#
# Usage:
# ./scripts/mirror-images.sh [OPTIONS]
#
# Options:
# --core-only Mirror only the essential infrastructure images (postgres,
# redis, alpine) — skip platform and communication services
# --dry-run Print commands without executing
# --registry Override registry (default: gitea.bnkops.com/admin)
# --help Show this help
#
# Prerequisites:
# docker login gitea.bnkops.com
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
# --- Defaults ---
REGISTRY="${GITEA_REGISTRY:-gitea.bnkops.com/admin}"
CORE_ONLY=false
DRY_RUN=false
# --- Colors ---
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m'
BLUE='\033[0;34m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m'
else
RED='' GREEN='' YELLOW='' BLUE='' CYAN='' BOLD='' NC=''
fi
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
run() { if [[ "$DRY_RUN" == "true" ]]; then echo -e "${CYAN}[DRY-RUN]${NC} $*"; else eval "$@"; fi; }
# --- Arg parser ---
while [[ $# -gt 0 ]]; do
case "$1" in
--core-only) CORE_ONLY=true; shift ;;
--dry-run) DRY_RUN=true; shift ;;
--registry) REGISTRY="$2"; shift 2 ;;
--help|-h)
sed -n '2,30p' "$0" | grep '^#' | sed 's/^# \?//'
exit 0 ;;
# Legacy flag — now default behavior
--all) shift ;;
*) error "Unknown option: $1"; exit 1 ;;
esac
done
# =============================================================================
# IMAGE MAP — source → destination name in Gitea registry
#
# Naming conventions:
# - Simple names kept as-is: postgres, redis, alpine, mongo, mysql, nats
# - Namespaced → short name: nocodb/nocodb → nocodb, listmonk/listmonk → listmonk
# - Conflicts resolved explicitly:
# gotify/server → gotify (not "server")
# vaultwarden/server → vaultwarden (not "server")
# jitsi/web → jitsi-web (not "web")
# lscr.io/.../nginx → ls-nginx (not "nginx", avoids changemaker-nginx conflict)
# alpine/curl → alpine-curl (not "curl")
# - Untagged images get :latest appended
# =============================================================================
# --- Core infrastructure (always mirrored) ---
declare -A CORE_IMAGES=(
# PostgreSQL
["postgres:16-alpine"]="postgres:16-alpine"
["postgres:17-alpine"]="postgres:17-alpine"
# Redis
["redis:7-alpine"]="redis:7-alpine"
# Alpine (init containers)
["alpine:3"]="alpine:3"
["alpine/curl:8.11.1"]="alpine-curl:8.11.1"
)
# --- Platform services ---
declare -A PLATFORM_IMAGES=(
# NocoDB
["nocodb/nocodb:0.301.3"]="nocodb:0.301.3"
# Listmonk
["listmonk/listmonk:v6.0.0"]="listmonk:v6.0.0"
# MailHog
["mailhog/mailhog:v1.0.1"]="mailhog:v1.0.1"
# MkDocs
["squidfunk/mkdocs-material:latest"]="mkdocs-material:latest"
# MkDocs static site server (LinuxServer nginx — NOT our custom nginx)
["lscr.io/linuxserver/nginx:1.28.2"]="ls-nginx:1.28.2"
# n8n workflow automation
["docker.n8n.io/n8nio/n8n:latest"]="n8n:latest"
# Homepage dashboard
["ghcr.io/gethomepage/homepage:v0.7.2"]="homepage:v0.7.2"
# Gitea + MySQL
["gitea/gitea:1.23.7"]="gitea:1.23.7"
["mysql:8"]="mysql:8"
# Mini QR
["ghcr.io/lyqht/mini-qr:v0.26.0"]="mini-qr:v0.26.0"
# Excalidraw
["excalidraw/excalidraw:latest"]="excalidraw:latest"
# Vaultwarden
["vaultwarden/server:1.35.4"]="vaultwarden:1.35.4"
# Docker socket proxy
["tecnativa/docker-socket-proxy:v0.4.2"]="docker-socket-proxy:v0.4.2"
# Pangolin tunnel
["fosrl/newt:latest"]="newt:latest"
# Plain nginx (CCP instances: reverse proxy + mkdocs-site-server)
["nginx:alpine"]="nginx:alpine"
# Code Server (LinuxServer)
["lscr.io/linuxserver/code-server:latest"]="code-server:latest"
)
# --- Communication services (Rocket.Chat, Gancio, Jitsi) ---
declare -A COMMS_IMAGES=(
# Rocket.Chat + dependencies
["rocketchat/rocket.chat:7.9.7"]="rocketchat:7.9.7"
["mongo:6.0"]="mongo:6.0"
["nats:2.11-alpine"]="nats:2.11-alpine"
# Gancio events
["cisti/gancio:1.28.2"]="gancio:1.28.2"
# Jitsi Meet (4 containers)
["jitsi/web:stable-9823"]="jitsi-web:stable-9823"
["jitsi/prosody:stable-9823"]="jitsi-prosody:stable-9823"
["jitsi/jicofo:stable-9823"]="jitsi-jicofo:stable-9823"
["jitsi/jvb:stable-9823"]="jitsi-jvb:stable-9823"
)
# --- Monitoring stack ---
declare -A MONITORING_IMAGES=(
["prom/prometheus:v3.10.0"]="prometheus:v3.10.0"
["grafana/grafana:12.3.0"]="grafana:12.3.0"
["prom/alertmanager:v0.31.1"]="alertmanager:v0.31.1"
["oliver006/redis_exporter:v1.81.0"]="redis_exporter:v1.81.0"
["gcr.io/cadvisor/cadvisor:v0.55.1"]="cadvisor:v0.55.1"
["prom/node-exporter:v1.10.2"]="node-exporter:v1.10.2"
["gotify/server:v2.9.0"]="gotify:v2.9.0"
)
# =============================================================================
mirror_image() {
local source="$1"
local dest_name="$2"
local dest="${REGISTRY}/${dest_name}"
info "Mirroring ${source}${dest}..."
run docker pull "${source}"
run docker tag "${source}" "${dest}"
run docker push "${dest}"
success "Mirrored ${dest_name}"
}
mirror_map() {
local -n map=$1
local label="$2"
echo ""
echo -e "${BOLD}── ${label} (${#map[@]} images) ──${NC}"
for source in "${!map[@]}"; do
if ! mirror_image "$source" "${map[$source]}"; then
FAILED+=("$source")
warn "Failed to mirror: $source (continuing...)"
else
((MIRRORED++)) || true
fi
done
}
echo -e "${BOLD}Changemaker Lite — Mirror ALL Images to Gitea${NC}"
echo " Registry: $REGISTRY"
echo " Core only: $CORE_ONLY"
echo ""
FAILED=()
MIRRORED=0
TOTAL=0
# Always mirror core
for _ in "${!CORE_IMAGES[@]}"; do ((TOTAL++)) || true; done
mirror_map CORE_IMAGES "Core Infrastructure"
if [[ "$CORE_ONLY" != "true" ]]; then
for _ in "${!PLATFORM_IMAGES[@]}"; do ((TOTAL++)) || true; done
for _ in "${!COMMS_IMAGES[@]}"; do ((TOTAL++)) || true; done
for _ in "${!MONITORING_IMAGES[@]}"; do ((TOTAL++)) || true; done
mirror_map PLATFORM_IMAGES "Platform Services"
mirror_map COMMS_IMAGES "Communication Services"
mirror_map MONITORING_IMAGES "Monitoring Stack"
else
warn "Skipping platform, communication, and monitoring images (--core-only)"
fi
echo ""
echo -e "${BOLD}── Summary ──${NC}"
if [[ ${#FAILED[@]} -eq 0 ]]; then
success "All ${MIRRORED} images mirrored to ${REGISTRY}"
else
warn "Mirrored ${MIRRORED}/${TOTAL} images"
error "Failed: ${FAILED[*]}"
exit 1
fi