#!/bin/bash # ----------------------------------------------------------------------------------------------- # ----------------------- Common Helpers for Unraid Scripts ------------------------------------- # ----------------------------------------------------------------------------------------------- # ----------------------------- Output Helpers ----------------------------- info() { echo "[INFO] $*" } warn() { echo "[WARN] $*" } error() { echo "[ERROR] $*" } log() { [[ "$ENABLE_LOGGING" == true ]] && echo "[LOG] $*" } # ----------------------------- Argument Parsing ----------------------------- parse_args() { DRY_RUN=${DRY_RUN:-false} ENABLE_LOGGING=${ENABLE_LOGGING:-false} for ARG in "$@"; do 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" elif [[ "$VAR_VALUE" == "false" ]]; then ENABLE_LOGGING=false else warn "Invalid value for LOG: $VAR_VALUE (use true/false)" fi ;; *) if declare -p "$VAR_NAME" &>/dev/null; then printf -v "$VAR_NAME" '%s' "$VAR_VALUE" log "Overriding $VAR_NAME -> $VAR_VALUE" else warn "Unknown variable $VAR_NAME, ignoring." fi ;; esac else case "$ARG" in --dry-run|-n) DRY_RUN=true info "Dry-run mode enabled" ;; --log) ENABLE_LOGGING=true info "Verbose logging enabled" ;; --no-log) ENABLE_LOGGING=false ;; --status|--summary) SHOW_STATUS=true ;; --help|-h) echo "Usage: script [--dry-run|-n] [--log] [LOG=true] [VAR=value ...]" exit 0 ;; *) warn "Unknown argument $ARG" ;; esac fi done } # ----------------------------- Validation ----------------------------- require_var() { local var="$1" if [[ -z "${!var:-}" ]]; then error "Required variable $var is not set." exit 1 fi } validate_int() { local name="$1" local value="$2" if ! [[ "${value:-}" =~ ^[0-9]+$ ]]; then error "$name must be a non-negative 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 "Local hostname ($LOCAL_HOSTNAME) not recognized." 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 "No SSH key defined for $LOCAL_SERVER_NAME → $REMOTE_SERVER_NAME" exit 1 fi info "Local: $LOCAL_SERVER_NAME → Remote: $REMOTE_SERVER_NAME" log "SSH key: $SSH_KEY" } # ----------------------------- Remote IP ----------------------------- resolve_remote_ip() { log "Resolving Tailscale IP for $REMOTE_SERVER_NAME..." REMOTE_SERVER=$(tailscale ip -4 "$REMOTE_SERVER_NAME" 2>/dev/null) if [[ -z "$REMOTE_SERVER" ]]; then error "Could not resolve Tailscale IP for $REMOTE_SERVER_NAME" exit 1 fi info "Remote IP: $REMOTE_SERVER" } # ----------------------------- Connectivity ----------------------------- check_remote_online() { log "Pinging $REMOTE_SERVER..." ping -c 1 "$REMOTE_SERVER" &>/dev/null } # ----------------------------- Containers ----------------------------- declare -a RUNNING_CONTAINERS=() stop_containers() { if [[ ${#CRITICAL_CONTAINER_NAMES[@]} -eq 0 ]]; then log "No containers defined to stop" return fi info "Stopping containers on $REMOTE_SERVER_NAME..." RUNNING_CONTAINERS=() for container in "${CRITICAL_CONTAINER_NAMES[@]}"; do log "Checking container: $container" STATUS=$(ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" \ "docker inspect -f '{{.State.Running}}' $container 2>/dev/null" || echo "false") if [[ "$STATUS" == "true" ]]; then info "Stopping $container" RUNNING_CONTAINERS+=("$container") ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" "docker stop $container" else log "$container already stopped" fi done } start_containers() { if [[ ${#RUNNING_CONTAINERS[@]} -eq 0 ]]; then log "No containers to restart" return fi info "Starting containers on $REMOTE_SERVER_NAME..." for container in "${RUNNING_CONTAINERS[@]}"; do if [[ " ${DELAYED_CONTAINERS[*]} " == *" $container "* ]]; then info "Delaying $container (${CONTAINER_DELAY}s)" sleep "$CONTAINER_DELAY" fi info "Starting $container" ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" "docker start $container" done } # ----------------------------- Rsync Options ----------------------------- get_rsync_opts() { if [[ -n "${PROFILE_RSYNC_OPTS[$PROFILE_NAME]:-}" ]]; then read -r -a RSYNC_OPTS <<< "${PROFILE_RSYNC_OPTS[$PROFILE_NAME]}" log "Using profile rsync options: ${PROFILE_RSYNC_OPTS[$PROFILE_NAME]}" else RSYNC_OPTS=("${DEFAULT_RSYNC_OPTS[@]}") log "Using default rsync options: ${DEFAULT_RSYNC_OPTS[*]}" fi } # ----------------------------- Status Output ----------------------------- show_status() { echo "===== RSYNC STATUS =====" echo "Local: $LOCAL_SERVER_NAME" echo "Remote: $REMOTE_SERVER_NAME" echo "Remote IP: $REMOTE_SERVER" echo "SSH Key: $SSH_KEY" echo echo "Directory: $DIRECTORY" echo "Profile: $PROFILE_NAME" echo echo "Max Procs: $MAX_RSYNC_PROCS" echo "Bandwidth: $BW_LIMIT KB/s" echo "Retries: $RETRY_COUNT" echo "Sleep: $SLEEP" echo "Dry Run: $DRY_RUN" echo echo "Containers: ${CRITICAL_CONTAINER_NAMES[*]:-(none)}" echo "Delayed: ${DELAYED_CONTAINERS[*]:-(none)}" echo echo "Excludes: ${EXCLUDE_DIRS[*]:-(none)}" echo echo "Rsync Opts:" printf ' %s\n' "${RSYNC_OPTS[@]}" echo "=========================" }