#!/bin/bash # ----------------------------------------------------------------------------------------------- # ----------------- UNRAID OPS COMMON LIBRARY (CORE FRAMEWORK v1) ------------------------------- # ----------------------------------------------------------------------------------------------- # ----------------------------- DEFAULT STATES ----------------------------- DRY_RUN=${DRY_RUN:-false} ENABLE_LOGGING=${ENABLE_LOGGING:-false} SHOW_STATUS=${SHOW_STATUS:-false} POSITIONAL_ARGS=() # ---------------------------------------------------------------------------------------------- # UI ICON SYSTEM # ---------------------------------------------------------------------------------------------- ICON_INFO="🟢" ICON_WARN="🟡" ICON_ERROR="🔴" ICON_LOG="⚪" ICON_OK="✅" ICON_FAIL="❌" ICON_RUN="🚀" ICON_ARROW="➜" # ---------------------------------------------------------------------------------------------- # OUTPUT HELPERS # ---------------------------------------------------------------------------------------------- info() { echo -e "$ICON_INFO [INFO] $*"; } warn() { echo -e "$ICON_WARN [WARN] $*"; } error() { echo -e "$ICON_ERROR [ERROR] $*"; } ok() { echo -e "$ICON_OK [OK] $*"; } log() { [[ "$ENABLE_LOGGING" == true ]] && echo -e "$ICON_LOG [LOG] $*" } # ---------------------------------------------------------------------------------------------- # UI LAYER # ---------------------------------------------------------------------------------------------- ui_header() { echo echo "==================================================" echo " $ICON_RUN $*" echo "==================================================" } ui_section() { echo echo "--------------------------------------------------" echo " $ICON_ARROW $*" echo "--------------------------------------------------" } ui_kv() { printf " %-18s %s\n" "$1:" "$2" } ui_status() { case "$1" in ok) echo "$ICON_OK [OK] $2" ;; warn) echo "$ICON_WARN [WARN] $2" ;; error) echo "$ICON_ERROR [ERROR] $2" ;; *) echo "$ICON_INFO [INFO] $2" ;; esac } ui_footer() { echo echo "==================================================" echo " $ICON_OK $*" echo "==================================================" } # ---------------------------------------------------------------------------------------------- # ARG PARSER (POSIX + POSITIONAL + FLAGS) # ---------------------------------------------------------------------------------------------- parse_args() { POSITIONAL_ARGS=() for ARG in "$@"; do # KEY=VALUE if [[ "$ARG" == *=* ]]; then VAR_NAME="${ARG%%=*}" VAR_VALUE="${ARG#*=}" case "$VAR_NAME" in LOG) if [[ "$VAR_VALUE" == "true" ]]; then ENABLE_LOGGING=true info "Verbose logging enabled" else ENABLE_LOGGING=false info "Verbose logging disabled" fi ;; *) if declare -p "$VAR_NAME" &>/dev/null; then printf -v "$VAR_NAME" '%s' "$VAR_VALUE" log "Override: $VAR_NAME=$VAR_VALUE" else warn "Unknown variable: $VAR_NAME" fi ;; esac # FLAGS + POSITIONAL else case "$ARG" in --dry-run|-n) DRY_RUN=true info "Dry-run enabled" ;; --log) ENABLE_LOGGING=true info "Logging enabled" ;; --no-log) ENABLE_LOGGING=false ;; --status|--summary) SHOW_STATUS=true ;; --help|-h) echo "Usage: script [options] [positional args]" echo echo "Options:" echo " --dry-run|-n" echo " --log" echo " --no-log" echo " --status" echo echo "Positional args:" echo " Free-form arguments stored in POSITIONAL_ARGS[]" exit 0 ;; *) POSITIONAL_ARGS+=("$ARG") log "Positional arg: $ARG" ;; esac fi done } # ---------------------------------------------------------------------------------------------- # VALIDATION # ---------------------------------------------------------------------------------------------- require_var() { local var="$1" if [[ -z "${!var:-}" ]]; then error "Missing required variable: $var" exit 1 fi } validate_int() { local name="$1" local value="$2" if ! [[ "$value" =~ ^[0-9]+$ ]]; then error "$name must be a valid integer" exit 1 fi } # ---------------------------------------------------------------------------------------------- # HOST DETECTION # ---------------------------------------------------------------------------------------------- detect_hosts() { LOCAL_HOSTNAME="$(hostname)" if [[ "$LOCAL_HOSTNAME" == "$HOST1" ]]; then LOCAL_SERVER_NAME="$HOST1" REMOTE_SERVER_NAME="$HOST2" elif [[ "$LOCAL_HOSTNAME" == "$HOST2" ]]; then LOCAL_SERVER_NAME="$HOST2" REMOTE_SERVER_NAME="$HOST1" else error "Unknown host: $LOCAL_HOSTNAME" exit 1 fi declare -A SSH_KEYS SSH_KEYS["$HOST1|$HOST2"]="$HOST1_SSH_KEY" SSH_KEYS["$HOST2|$HOST1"]="$HOST2_SSH_KEY" KEY_ID="$LOCAL_SERVER_NAME|$REMOTE_SERVER_NAME" SSH_KEY="${SSH_KEYS[$KEY_ID]}" if [[ -z "$SSH_KEY" ]]; then error "Missing SSH key for $KEY_ID" exit 1 fi info "Local: $LOCAL_SERVER_NAME → Remote: $REMOTE_SERVER_NAME" } resolve_remote_ip() { REMOTE_SERVER=$(tailscale ip -4 "$REMOTE_SERVER_NAME" 2>/dev/null) if [[ -z "$REMOTE_SERVER" ]]; then error "Cannot resolve remote IP" exit 1 fi info "Remote IP: $REMOTE_SERVER" } check_remote_online() { ping -c 1 "$REMOTE_SERVER" &>/dev/null } # ---------------------------------------------------------------------------------------------- # CONTAINERS # ---------------------------------------------------------------------------------------------- declare -a RUNNING_CONTAINERS=() stop_containers() { RUNNING_CONTAINERS=() for c in "${CRITICAL_CONTAINER_NAMES[@]}"; do STATUS=$(ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" \ "docker inspect -f '{{.State.Running}}' $c 2>/dev/null" || echo "false") if [[ "$STATUS" == "true" ]]; then info "Stopping $c" RUNNING_CONTAINERS+=("$c") ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" "docker stop $c" fi done } start_containers() { for c in "${RUNNING_CONTAINERS[@]}"; do info "Starting $c" ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" "docker start $c" done } # ---------------------------------------------------------------------------------------------- # RSYNC # ---------------------------------------------------------------------------------------------- get_rsync_opts() { if [[ -n "${PROFILE_RSYNC_OPTS[$PROFILE_NAME]:-}" ]]; then read -r -a RSYNC_OPTS <<< "${PROFILE_RSYNC_OPTS[$PROFILE_NAME]}" else RSYNC_OPTS=("${DEFAULT_RSYNC_OPTS[@]}") fi } # ---------------------------------------------------------------------------------------------- # STATUS # ---------------------------------------------------------------------------------------------- show_status() { echo "===== OPS STATUS =====" echo "Local: $LOCAL_SERVER_NAME" echo "Remote: $REMOTE_SERVER_NAME" echo "IP: $REMOTE_SERVER" echo echo "DryRun: $DRY_RUN" echo "Log: $ENABLE_LOGGING" echo echo "Args: ${POSITIONAL_ARGS[*]:-(none)}" echo "======================" }