diff --git a/common.sh b/common.sh index 3fe28f6..b100f1e 100644 --- a/common.sh +++ b/common.sh @@ -3,84 +3,191 @@ # ----------------- UNRAID OPS COMMON LIBRARY (CORE FRAMEWORK v1) ------------------------------- # ----------------------------------------------------------------------------------------------- -# OUTPUT HELPERS -info() { echo "[INFO] $*"; } -warn() { echo "[WARN] $*"; } -error() { echo "[ERROR] $*"; exit 1; } -log() { [[ "${ENABLE_LOGGING:-false}" == true ]] && echo "[LOG] $*"; } - -# DEFAULT FLAGS +# ----------------------------- DEFAULT STATES ----------------------------- DRY_RUN=${DRY_RUN:-false} ENABLE_LOGGING=${ENABLE_LOGGING:-false} SHOW_STATUS=${SHOW_STATUS:-false} -# ARGUMENT PARSER +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) - [[ "$VAR_VALUE" == "true" ]] && ENABLE_LOGGING=true - [[ "$VAR_VALUE" == "false" ]] && ENABLE_LOGGING=false - log "Logging set to $ENABLE_LOGGING" + 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" + 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 [--dry-run|-n] [--log] [--status] [VAR=value]" + 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 ;; + *) - warn "Unknown argument: $ARG" + POSITIONAL_ARGS+=("$ARG") + log "Positional arg: $ARG" ;; esac fi + done } +# ---------------------------------------------------------------------------------------------- # VALIDATION +# ---------------------------------------------------------------------------------------------- + require_var() { local var="$1" - [[ -z "${!var:-}" ]] && error "Missing required variable: $var" + if [[ -z "${!var:-}" ]]; then + error "Missing required variable: $var" + exit 1 + fi } validate_int() { local name="$1" local value="$2" - [[ ! "${value:-}" =~ ^[0-9]+$ ]] && error "$name must be an integer" + if ! [[ "$value" =~ ^[0-9]+$ ]]; then + error "$name must be a valid integer" + exit 1 + fi } +# ---------------------------------------------------------------------------------------------- # HOST DETECTION +# ---------------------------------------------------------------------------------------------- + detect_hosts() { LOCAL_HOSTNAME="$(hostname)" @@ -93,103 +200,100 @@ detect_hosts() { REMOTE_SERVER_NAME="$HOST1" else error "Unknown host: $LOCAL_HOSTNAME" + exit 1 fi - declare -gA SSH_KEYS - + 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]:-}" + SSH_KEY="${SSH_KEYS[$KEY_ID]}" - [[ -z "$SSH_KEY" ]] && error "Missing SSH key for $KEY_ID" + if [[ -z "$SSH_KEY" ]]; then + error "Missing SSH key for $KEY_ID" + exit 1 + fi - log "Host mapping: $LOCAL_SERVER_NAME -> $REMOTE_SERVER_NAME" + info "Local: $LOCAL_SERVER_NAME โ†’ Remote: $REMOTE_SERVER_NAME" } -# REMOTE RESOLUTION resolve_remote_ip() { - log "Resolving remote IP..." - REMOTE_SERVER=$(tailscale ip -4 "$REMOTE_SERVER_NAME" 2>/dev/null) - [[ -z "$REMOTE_SERVER" ]] && error "Failed to resolve IP for $REMOTE_SERVER_NAME" + if [[ -z "$REMOTE_SERVER" ]]; then + error "Cannot resolve remote IP" + exit 1 + fi - log "Remote IP: $REMOTE_SERVER" + info "Remote IP: $REMOTE_SERVER" } check_remote_online() { ping -c 1 "$REMOTE_SERVER" &>/dev/null } -# SAFE SSH WRAPPER -ssh_exec() { - ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" "$@" -} +# ---------------------------------------------------------------------------------------------- +# CONTAINERS +# ---------------------------------------------------------------------------------------------- -# CONTAINER CONTROL -RUNNING_CONTAINERS=() +declare -a RUNNING_CONTAINERS=() stop_containers() { - [[ ${#CRITICAL_CONTAINER_NAMES[@]:-0} -eq 0 ]] && return - RUNNING_CONTAINERS=() - for container in "${CRITICAL_CONTAINER_NAMES[@]}"; do + for c in "${CRITICAL_CONTAINER_NAMES[@]}"; do - STATUS=$(ssh_exec "docker inspect -f '{{.State.Running}}' $container 2>/dev/null" || echo "false") + 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 $container" - RUNNING_CONTAINERS+=("$container") - ssh_exec "docker stop $container" || warn "Failed to stop $container" + info "Stopping $c" + RUNNING_CONTAINERS+=("$c") + ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" "docker stop $c" fi done } start_containers() { - [[ ${#RUNNING_CONTAINERS[@]} -eq 0 ]] && return + for c in "${RUNNING_CONTAINERS[@]}"; do - for container in "${RUNNING_CONTAINERS[@]}"; do + info "Starting $c" - if [[ " ${DELAYED_CONTAINERS[*]:-} " == *" $container "* ]]; then - info "Delaying $container ($CONTAINER_DELAY s)" - sleep "$CONTAINER_DELAY" - fi - - info "Starting $container" - ssh_exec "docker start $container" || warn "Failed to start $container" + ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" "docker start $c" done } -# RSYNC OPTS +# ---------------------------------------------------------------------------------------------- +# RSYNC +# ---------------------------------------------------------------------------------------------- + get_rsync_opts() { if [[ -n "${PROFILE_RSYNC_OPTS[$PROFILE_NAME]:-}" ]]; then - IFS=' ' read -r -a RSYNC_OPTS <<< "${PROFILE_RSYNC_OPTS[$PROFILE_NAME]}" - log "Using profile rsync opts" + read -r -a RSYNC_OPTS <<< "${PROFILE_RSYNC_OPTS[$PROFILE_NAME]}" else - RSYNC_OPTS=("${DEFAULT_RSYNC_OPTS[@]:-}") - log "Using default rsync opts" + RSYNC_OPTS=("${DEFAULT_RSYNC_OPTS[@]}") fi } -# STATUS OUTPUT +# ---------------------------------------------------------------------------------------------- +# STATUS +# ---------------------------------------------------------------------------------------------- + show_status() { + echo "===== OPS STATUS =====" + echo "Local: $LOCAL_SERVER_NAME" + echo "Remote: $REMOTE_SERVER_NAME" + echo "IP: $REMOTE_SERVER" echo - echo "==================================================" - echo " RSYNC OPS STATUS" - echo "--------------------------------------------------" - echo " Local: $LOCAL_SERVER_NAME" - echo " Remote: $REMOTE_SERVER_NAME" - echo " IP: ${REMOTE_SERVER:-unset}" - echo " Dry Run: $DRY_RUN" - echo " Logging: $ENABLE_LOGGING" - echo " Profile: ${PROFILE_NAME:-default}" - echo "==================================================" + echo "DryRun: $DRY_RUN" + echo "Log: $ENABLE_LOGGING" + echo + echo "Args: ${POSITIONAL_ARGS[*]:-(none)}" + echo "======================" } \ No newline at end of file diff --git a/unRAID Essentials/rsync_stop.sh b/unRAID Essentials/rsync_stop.sh index 39ccec4..aad88aa 100644 --- a/unRAID Essentials/rsync_stop.sh +++ b/unRAID Essentials/rsync_stop.sh @@ -9,85 +9,78 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../Master.conf" source "$SCRIPT_DIR/../common.sh" +# ----------------------------- INIT ----------------------------- parse_args "$@" -# STATUS MODE +require_var "HOST1" +require_var "HOST2" + +detect_hosts +resolve_remote_ip + +# ----------------------------- POSITIONAL MAPPING ----------------------------- +SOURCE="${POSITIONAL_ARGS[0]:-}" +DEST="${POSITIONAL_ARGS[1]:-}" + +# ----------------------------- STATUS MODE ----------------------------- if [[ "$SHOW_STATUS" == true ]]; then - echo - echo "==================================================" - echo " โ„น๏ธ RSYNC STOP STATUS" - echo "--------------------------------------------------" - echo " ๐Ÿ”„ Target: rsync processes" - echo " ๐Ÿงช Dry Run: $DRY_RUN" - echo "==================================================" + show_status exit 0 fi -# HEADER -echo -echo "============================================================" -echo " ๐Ÿš€ RSYNC STOP STARTING" -echo "------------------------------------------------------------" -echo " ๐Ÿ”„ Target: all rsync processes" -echo " ๐Ÿงช Dry Run: $DRY_RUN" -echo "============================================================" +# ----------------------------- HEADER ----------------------------- +ui_header "RSYNC OPERATION" -log "โ„น๏ธ Checking for running rsync processes" +ui_section "Context" +ui_kv "Source" "$SOURCE" +ui_kv "Destination" "$DEST" +ui_kv "Remote" "$REMOTE_SERVER_NAME" +ui_kv "IP" "$REMOTE_SERVER" +ui_kv "Dry Run" "$DRY_RUN" -# FUNCTIONS -check_rsync_running() { - pgrep -f rsync >/dev/null 2>&1 -} - -stop_rsync_processes() { - - if [[ "$DRY_RUN" == true ]]; then - warn "๐Ÿงช Would stop all rsync processes" - return - fi - - if check_rsync_running; then - info "๐ŸŸก Stopping rsync processes" - - # safer than blind pkill message-only: - pkill -f rsync - - sleep 1 - - if check_rsync_running; then - warn "๐ŸŸก Some rsync processes may still be running" - else - info "๐ŸŸข All rsync processes stopped successfully" - fi - else - info "๐ŸŸข No rsync processes currently running" - fi -} - -# EXECUTION -START_TIME=$(date +%s) - -stop_rsync_processes - -END_TIME=$(date +%s) -DURATION=$((END_TIME - START_TIME)) - -# SUMMARY -echo -echo "============================================================" - -if check_rsync_running; then - echo " ๐ŸŸก RSYNC STOP SUMMARY" - echo "------------------------------------------------------------" - echo " ๐Ÿ”„ Status: ๐ŸŸก SOME PROCESSES MAY REMAIN" - echo " โฑ Duration: ${DURATION}s" -else - echo " ๐ŸŸข RSYNC STOP SUMMARY" - echo "------------------------------------------------------------" - echo " ๐Ÿ”„ Status: ๐ŸŸข ALL STOPPED" - echo " โฑ Duration: ${DURATION}s" +# ----------------------------- VALIDATION ----------------------------- +if [[ -z "$SOURCE" ]]; then + ui_status error "Missing SOURCE (arg 1)" + exit 1 fi -echo "============================================================" +if [[ -z "$DEST" ]]; then + ui_status error "Missing DEST (arg 2)" + exit 1 +fi -info "โ„น๏ธ Rsync stop operation complete" \ No newline at end of file +ui_status ok "Validation passed" + +# ----------------------------- PRECHECK ----------------------------- +if ! check_remote_online; then + ui_status error "Remote offline" + exit 1 +fi + +# ----------------------------- EXECUTION ----------------------------- +stop_containers +get_rsync_opts + +RSYNC_CMD=( + rsync + "${RSYNC_OPTS[@]}" + "$SOURCE" + root@"$REMOTE_SERVER":"$DEST" +) + +if [[ "$DRY_RUN" == true ]]; then + RSYNC_CMD+=("--dry-run") +fi + +ui_section "Execution" +ui_status ok "Running rsync" + +log "CMD: ${RSYNC_CMD[*]}" + +"${RSYNC_CMD[@]}" + +# ----------------------------- RECOVERY ----------------------------- +start_containers + +# ----------------------------- END ----------------------------- +ui_footer "RSYNC COMPLETE" \ No newline at end of file