- install.sh: Add Docker daemon check, 10GB disk space pre-flight, error handling on pull/up, post-startup health polling with crash detection, cleanup trap on failure - docker-compose: Fix nginx/listmonk depends_on to service_healthy, add x-logging anchor (10m/3 files) to all ~39 services - config.sh: Preserve existing secrets on re-run (reconfigure mode), add automated daily backup timer (systemd, 02:00, 30-day retention) - mirror-images.sh: Fix gotify source tag (2.9.0 not v2.9.0) - build-release.sh: Ensure mkdocs/docs and mkdocs/overrides dirs exist - .env.example: Add COMPOSE_PROFILES variable Bunker Admin
213 lines
7.1 KiB
Bash
Executable File
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:2.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
|