#!/usr/bin/env bash # ============================================================================== # Unified Deploy Wrapper # ============================================================================== # Auto-discovers deploy-{docker,local}-*.sh scripts in the same directory and # dispatches to them with a shorter CLI interface. # # Usage: # deploy.sh # List available services # deploy.sh mattermost # Deploy via docker (default) # deploy.sh -l paperclip # Deploy via local (bare-metal) # deploy.sh --local hermes --domain h.an2.io # Local deploy with flags # deploy.sh -r mattermost # --remove # deploy.sh -r -p mattermost # --remove --purge # deploy.sh -r -p -y mattermost # --remove --purge --yes # deploy.sh web --domain w.an2.io --dir acme # Pass-through flags # ============================================================================== set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # ── Colours ────────────────────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m' BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m' # ── Discover available deploy scripts ──────────────────────────────────────── # Builds two associative arrays: # DOCKER_SCRIPTS[service] = script_path # LOCAL_SCRIPTS[service] = script_path # And a sorted list of unique service names. declare -A DOCKER_SCRIPTS=() declare -A LOCAL_SCRIPTS=() declare -a ALL_SERVICES=() discover_scripts() { local script basename method service for script in "${SCRIPT_DIR}"/deploy-docker-*.sh "${SCRIPT_DIR}"/deploy-local-*.sh; do [[ -f "$script" ]] || continue basename="$(basename "$script" .sh)" # deploy-docker-hermes method="${basename#deploy-}" # docker-hermes method="${method%%-*}" # docker or local service="${basename#deploy-${method}-}" # hermes case "$method" in docker) DOCKER_SCRIPTS["$service"]="$script" ;; local) LOCAL_SCRIPTS["$service"]="$script" ;; esac done # Build sorted unique service list local -A seen=() for s in "${!DOCKER_SCRIPTS[@]}" "${!LOCAL_SCRIPTS[@]}"; do seen["$s"]=1 done IFS=$'\n' read -r -d '' -a ALL_SERVICES < <(printf '%s\n' "${!seen[@]}" | sort && printf '\0') || true } # ── Usage / list ───────────────────────────────────────────────────────────── print_usage() { echo "" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo -e "${CYAN} Deploy — Unified Wrapper${NC}" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo "" echo -e " ${BOLD}Usage:${NC} deploy.sh [flags] " echo "" echo -e " ${BOLD}Flags:${NC}" echo -e " ${GREEN}--local, -l${NC} Use bare-metal (local) deploy instead of Docker" echo -e " ${GREEN}--remove, -r${NC} Remove the service (keep data)" echo -e " ${GREEN}--purge, -p${NC} Delete all data (requires --remove)" echo -e " ${GREEN}--yes, -y${NC} Skip confirmations" echo -e " ${GREEN}--domain ${NC} Set the service domain" echo -e " ${GREEN}--dir ${NC} Instance directory (for web)" echo -e " ${GREEN}--cold${NC} Cold backup mode (passed through)" echo "" echo -e " ${BOLD}Available services:${NC}" echo "" # Table header printf " ${DIM}%-16s %-8s %-8s${NC}\n" "SERVICE" "DOCKER" "LOCAL" printf " ${DIM}%-16s %-8s %-8s${NC}\n" "───────────────" "────────" "────────" for service in "${ALL_SERVICES[@]}"; do local d_mark l_mark if [[ -n "${DOCKER_SCRIPTS[$service]+x}" ]]; then d_mark="${GREEN} ✓${NC}" else d_mark="${DIM} —${NC}" fi if [[ -n "${LOCAL_SCRIPTS[$service]+x}" ]]; then l_mark="${GREEN} ✓${NC}" else l_mark="${DIM} —${NC}" fi printf " %-16s %b %b\n" "$service" "$d_mark" "$l_mark" done echo "" echo -e " ${BOLD}Examples:${NC}" echo -e " ${DIM}deploy.sh mattermost${NC} # Docker deploy" echo -e " ${DIM}deploy.sh -l paperclip --domain clip.an2.io${NC} # Local deploy" echo -e " ${DIM}deploy.sh -r -p gitea${NC} # Remove + purge" echo -e " ${DIM}deploy.sh web --domain w.an2.io --dir acme${NC} # Multi-instance" echo "" } # ── Main ───────────────────────────────────────────────────────────────────── discover_scripts # Parse wrapper flags — separate them from pass-through flags USE_LOCAL=0 SERVICE="" PASSTHROUGH=() while [[ $# -gt 0 ]]; do case "$1" in --local|-l) USE_LOCAL=1 shift ;; --remove|-r) PASSTHROUGH+=("--remove") shift ;; --purge|-p) PASSTHROUGH+=("--purge") shift ;; --yes|-y) PASSTHROUGH+=("--yes") shift ;; --domain) PASSTHROUGH+=("--domain" "$2") shift 2 ;; --dir) PASSTHROUGH+=("--dir" "$2") shift 2 ;; --cold) PASSTHROUGH+=("--cold") shift ;; -*) # Unknown flag — pass through PASSTHROUGH+=("$1") shift ;; *) if [[ -z "$SERVICE" ]]; then SERVICE="$1" else # Extra positional — pass through PASSTHROUGH+=("$1") fi shift ;; esac done # No service specified — show usage if [[ -z "$SERVICE" ]]; then print_usage exit 0 fi # Resolve script TARGET_SCRIPT="" if (( USE_LOCAL )); then if [[ -n "${LOCAL_SCRIPTS[$SERVICE]+x}" ]]; then TARGET_SCRIPT="${LOCAL_SCRIPTS[$SERVICE]}" else echo -e "${RED}[FAIL]${NC} No local deploy script for '${SERVICE}'." if [[ -n "${DOCKER_SCRIPTS[$SERVICE]+x}" ]]; then echo -e " A Docker variant exists — run without ${GREEN}-l${NC}." fi exit 1 fi else if [[ -n "${DOCKER_SCRIPTS[$SERVICE]+x}" ]]; then TARGET_SCRIPT="${DOCKER_SCRIPTS[$SERVICE]}" elif [[ -n "${LOCAL_SCRIPTS[$SERVICE]+x}" ]]; then echo -e "${YELLOW}[WARN]${NC} No Docker variant for '${SERVICE}' — using local." TARGET_SCRIPT="${LOCAL_SCRIPTS[$SERVICE]}" else echo -e "${RED}[FAIL]${NC} Unknown service '${SERVICE}'." echo "" echo -e " Available: ${ALL_SERVICES[*]}" exit 1 fi fi # Dispatch exec bash "$TARGET_SCRIPT" "${PASSTHROUGH[@]}"