diff --git a/Master.conf b/Master.conf index 2b3eb91..d319317 100644 --- a/Master.conf +++ b/Master.conf @@ -65,80 +65,4 @@ declare -A PROFILE_CRITICAL_CONTAINER_NAMES=( ) declare -A PROFILE_DELAYED_CONTAINERS=([arrs_stack]="" [critical-data]="Authelia" [gmer4lfe]="" [important-data]="NextCloud" [emby]="") declare -A PROFILE_CONTAINER_DELAY=([arrs_stack]=5 [critical-data]=10 [gmer4lfe]=5 [important-data]=10 [emby]=5) -declare -A PROFILE_EXCLUDE_DIRS=([arrs_stack]="logs *.tmp" [critical-data]="logs *.tmp" [gmer4lfe]="logs *.tmp" [important-data]="logs *.tmp" [emby]="logs *.tmp") - -# ----------------------------- Common Helpers ----------------------------- -parse_args() { - DRY_RUN=${DRY_RUN:-false} - for ARG in "$@"; do - if [[ "$ARG" == *=* ]]; then - local VAR_NAME="${ARG%%=*}" - local VAR_VALUE="${ARG#*=}" - if declare -p "$VAR_NAME" &>/dev/null; then - printf -v "$VAR_NAME" '%s' "$VAR_VALUE" - echo "Overriding $VAR_NAME -> $VAR_VALUE" - else - echo "Warning: Unknown variable $VAR_NAME, ignoring." - fi - else - case "$ARG" in - --dry-run|-n) DRY_RUN=true ;; - --help|-h) echo "Usage: script [--dry-run|-n] [VAR=value ...]"; exit 0 ;; - *) echo "Warning: Unknown argument $ARG" ;; - esac - fi - done -} - -require_var() { - local var="$1" - if [[ -z "${!var:-}" ]]; then - echo "Error: Required variable $var is not set." - exit 1 - fi -} - -validate_int() { - local name="$1" - local value="$2" - if ! [[ "${value:-}" =~ ^[0-9]+$ ]]; then - echo "Error: $name must be a non-negative integer." - exit 1 - fi -} - -# ----------------------------- Host Detection & SSH ----------------------------- -# Detect local/remote host -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 - echo "Error: Local hostname ($LOCAL_HOSTNAME) not recognized." - exit 1 -fi -echo "Local server: $LOCAL_SERVER_NAME" -echo "Remote server: $REMOTE_SERVER_NAME" - -# SSH Key Mapping -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 - echo "Error: No SSH key defined for $LOCAL_SERVER_NAME → $REMOTE_SERVER_NAME" - exit 1 -fi -echo "Using SSH key: $SSH_KEY" - -# Remote IP via Tailscale -REMOTE_SERVER=$(tailscale ip -4 "$REMOTE_SERVER_NAME" 2>/dev/null) -if [[ -z "$REMOTE_SERVER" ]]; then - echo "Error: Could not resolve Tailscale IP for $REMOTE_SERVER_NAME" - exit 1 -fi -echo "Remote IP: $REMOTE_SERVER" +declare -A PROFILE_EXCLUDE_DIRS=([arrs_stack]="logs *.tmp" [critical-data]="logs *.tmp" [gmer4lfe]="logs *.tmp" [important-data]="logs *.tmp" [emby]="logs *.tmp") \ No newline at end of file diff --git a/Rsync/rsync.sh b/Rsync/rsync.sh index 3ed2f16..56cc10c 100644 --- a/Rsync/rsync.sh +++ b/Rsync/rsync.sh @@ -1,39 +1,26 @@ #!/bin/bash set -e -# --------------------------------------------------------------------------------- -# -------------------------- Rsync Script Using Master.conf ------------------------ -# --------------------------------------------------------------------------------- - -# Detect script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# Load Master.conf -MASTER_CONF="$SCRIPT_DIR/../Master.conf" -if [ -f "$MASTER_CONF" ]; then - source "$MASTER_CONF" - echo "Loaded Master.conf from $MASTER_CONF" -else - echo "Error: Master.conf not found at $MASTER_CONF" - exit 1 -fi +# Load config & helpers +source "$SCRIPT_DIR/../Master.conf" +source "$SCRIPT_DIR/../common.sh" -# ----------------------------- Parse Input Arguments ----------------------------- +# Input directory DIRECTORY="$1" if [[ -z "$DIRECTORY" ]]; then echo "Usage: $0 [--dry-run] [VAR=value ...]" exit 1 fi + shift 1 +parse_args "$@" -# Handle optional flags & variable overrides -parse_args "$DIRECTORY" "$@" - -# -------------------------- Determine Profile & Settings ------------------------- +# Profile detection PROFILE_NAME=$(basename "$DIRECTORY" | tr '[:upper:]' '[:lower:]') echo "Detected profile: $PROFILE_NAME" -# Profile-specific settings with fallback to global defaults MAX_RSYNC_PROCS=${PROFILE_MAX_PROCS[$PROFILE_NAME]:-$MAX_RSYNC_PROCS} BW_LIMIT=${PROFILE_BW_LIMIT[$PROFILE_NAME]:-$BW_LIMIT} CRITICAL_CONTAINER_NAMES=(${PROFILE_CRITICAL_CONTAINER_NAMES[$PROFILE_NAME]}) @@ -45,101 +32,27 @@ EXCLUDE_DIRS=(${PROFILE_EXCLUDE_DIRS[$PROFILE_NAME]:-${EXCLUDE_DIRS[@]}}) echo "Settings: MAX_RSYNC_PROCS=$MAX_RSYNC_PROCS, BW_LIMIT=$BW_LIMIT, CONTAINERS=${CRITICAL_CONTAINER_NAMES[*]}, EXCLUDES=${EXCLUDE_DIRS[*]}" -# --------------------------- Dependency Check ------------------------------------ -for cmd in rsync ssh ping timeout docker tailscale; do - command -v "$cmd" >/dev/null 2>&1 || { - echo "Error: Required command '$cmd' is not installed." - exit 1 - } -done - -trap 'echo "Script interrupted. Exiting."; exit 130' INT TERM - -# --------------------------------------------------------------------------------- -# ------------------------- RUNTIME LOGIC (DO NOT MODIFY) ------------------------- -# --------------------------------------------------------------------------------- - -wait_for_rsync() { - for attempt in $(seq 1 "$RETRY_COUNT"); do - RSYNC_COUNT=$(pgrep -fc "rsync.*root@${REMOTE_SERVER}" || true) - if [ "$RSYNC_COUNT" -ge "$MAX_RSYNC_PROCS" ]; then - echo "[$LOCAL_SERVER_NAME] Waiting... ($RSYNC_COUNT active rsync processes)" - sleep "$SLEEP" - else - return 0 - fi - done - echo "Too many rsync processes, exiting." - exit 1 -} - -check_server_online() { - timeout 5 ping -c 1 "$REMOTE_SERVER" &>/dev/null -} - -declare -a RUNNING_CONTAINERS=() - -stop_containers() { - echo "[$LOCAL_SERVER_NAME] Checking containers on ${REMOTE_SERVER_NAME}..." - RUNNING_CONTAINERS=() - for container in "${CRITICAL_CONTAINER_NAMES[@]}"; do - STATUS=$(ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" \ - "docker inspect -f '{{.State.Running}}' $container 2>/dev/null" || echo "false") - if [ "$STATUS" == "true" ]; then - echo "Stopping $container" - RUNNING_CONTAINERS+=("$container") - ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" "docker stop $container" - fi - done -} - -start_containers() { - echo "[$LOCAL_SERVER_NAME] Restarting containers on ${REMOTE_SERVER_NAME}..." - for container in "${RUNNING_CONTAINERS[@]}"; do - if [[ " ${DELAYED_CONTAINERS[*]} " == *" $container "* ]]; then - echo "Delaying start of $container by ${CONTAINER_DELAY}s..." - sleep "$CONTAINER_DELAY" - fi - ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" "docker start $container" - done -} - +# Main sync logic main() { echo "[$LOCAL_SERVER_NAME] Syncing $DIRECTORY → $REMOTE_SERVER:$DIRECTORY" for i in $(seq 1 "$RETRY_COUNT"); do - if check_server_online; then - break - fi + if check_server_online; then break; fi echo "Remote $REMOTE_SERVER_NAME down, retrying in $SLEEP seconds..." sleep "$SLEEP" done - check_server_online || { echo "Remote $REMOTE_SERVER_NAME unreachable. Exiting."; exit 1; } wait_for_rsync stop_containers - RSYNC_OPTS=( - -av - --info=progress2 - --human-readable - --bwlimit="$BW_LIMIT" - --delete - --inplace - --no-whole-file - ) - - for ex in "${EXCLUDE_DIRS[@]}"; do - RSYNC_OPTS+=(--exclude="$ex") - done - + RSYNC_OPTS=(-av --info=progress2 --human-readable --bwlimit="$BW_LIMIT" --delete --inplace --no-whole-file) + for ex in "${EXCLUDE_DIRS[@]}"; do RSYNC_OPTS+=(--exclude="$ex"); done [ "$DRY_RUN" = true ] && RSYNC_OPTS+=("--dry-run") for attempt in $(seq 1 "$RETRY_COUNT"); do echo "Starting rsync attempt $attempt/$RETRY_COUNT..." - if rsync "${RSYNC_OPTS[@]}" \ - -e "ssh -i \"$SSH_KEY\" -T -o Compression=no -o IPQoS=throughput" \ + if rsync "${RSYNC_OPTS[@]}" -e "ssh -i \"$SSH_KEY\" -T -o Compression=no -o IPQoS=throughput" \ "$DIRECTORY" "root@${REMOTE_SERVER}:$DIRECTORY"; then echo "Rsync completed successfully." break @@ -160,5 +73,4 @@ main() { echo "[$LOCAL_SERVER_NAME] Sync complete." } -# Execute main -main +main \ No newline at end of file diff --git a/common.sh b/common.sh index 51c690b..20d6bcd 100644 --- a/common.sh +++ b/common.sh @@ -1,41 +1,11 @@ #!/bin/bash -# ----------------------------- Master Variables for unRAID scripts ----------------------------- +#----------------------------------------------------------------------------------------------- +#----------------------------- Common helpers for unRAID scripts ------------------------------- +# Requires Master.conf to be sourced first +#----------------------------------------------------------------------------------------------- -# Hosts -HOST1="unRAID-Gmer4Lfe" -HOST2="unRAID-Jayred365" -HOST1_SSH_KEY="/root/.ssh/Gmer4Lfe-rsync-key" -HOST2_SSH_KEY="/root/.ssh/Jayred365-rsync-key" - -# Rsync Defaults -BW_LIMIT=12500 -RETRY_COUNT=3 -SLEEP=300 -MAX_RSYNC_PROCS=1 -CRITICAL_CONTAINER_NAMES=("") -DELAYED_CONTAINERS=("") -CONTAINER_DELAY=5 -EXCLUDE_DIRS=("") - -# Profiles -declare -A PROFILE_MAX_PROCS=([arrs_stack]=2 [critical-data]=2 [gmer4lfe]=1 [important-data]=2 [emby]=1) -declare -A PROFILE_BW_LIMIT=([arrs_stack]=5000 [critical-data]=9500 [gmer4lfe]=8000 [important-data]=9500 [emby]=8000) -declare -A PROFILE_RETRY_COUNT=([arrs_stack]=3 [critical-data]=3 [gmer4lfe]=3 [important-data]=3 [emby]=3) -declare -A PROFILE_SLEEP=([arrs_stack]=300 [critical-data]=300 [gmer4lfe]=300 [important-data]=300 [emby]=300) -declare -A PROFILE_CRITICAL_CONTAINER_NAMES=( - [arrs_stack]="Sonarr Lidarr Readarr Radarr Prowlarr Bazarr Pinchflat" - [critical-data]="Mariadb-Authelia Redis-Authelia Lldap-Gmer4Lfe NginxProxyManager Authelia" - [gmer4lfe]="Organizrv2-Gmer4Lfe UptimeKuma-Gmer4Lfe VaultWarden-Gmer4Lfe" - [important-data]="Postgres-NextCloud NextCloud" - [emby]="" -) -declare -A PROFILE_DELAYED_CONTAINERS=([arrs_stack]="" [critical-data]="Authelia" [gmer4lfe]="" [important-data]="NextCloud" [emby]="") -declare -A PROFILE_CONTAINER_DELAY=([arrs_stack]=5 [critical-data]=10 [gmer4lfe]=5 [important-data]=10 [emby]=5) -declare -A PROFILE_EXCLUDE_DIRS=([arrs_stack]="logs *.tmp" [critical-data]="logs *.tmp" [gmer4lfe]="logs *.tmp" [important-data]="logs *.tmp" [emby]="logs *.tmp") - -# ----------------------------- Common Helpers ----------------------------- +# ----------------------------- Helpers ----------------------------- parse_args() { - DRY_RUN=${DRY_RUN:-false} for ARG in "$@"; do if [[ "$ARG" == *=* ]]; then local VAR_NAME="${ARG%%=*}" @@ -74,7 +44,6 @@ validate_int() { } # ----------------------------- Host Detection & SSH ----------------------------- -# Detect local/remote host LOCAL_HOSTNAME="$(hostname)" if [[ "$LOCAL_HOSTNAME" == "$HOST1" ]]; then LOCAL_SERVER_NAME="$HOST1" @@ -86,10 +55,8 @@ else echo "Error: Local hostname ($LOCAL_HOSTNAME) not recognized." exit 1 fi -echo "Local server: $LOCAL_SERVER_NAME" -echo "Remote server: $REMOTE_SERVER_NAME" -# SSH Key Mapping +# SSH key mapping declare -A SSH_KEYS SSH_KEYS["$HOST1|$HOST2"]="$HOST1_SSH_KEY" SSH_KEYS["$HOST2|$HOST1"]="$HOST2_SSH_KEY" @@ -107,4 +74,50 @@ if [[ -z "$REMOTE_SERVER" ]]; then echo "Error: Could not resolve Tailscale IP for $REMOTE_SERVER_NAME" exit 1 fi -echo "Remote IP: $REMOTE_SERVER" \ No newline at end of file +echo "Remote IP: $REMOTE_SERVER" + +# ----------------------------- Container Helpers ----------------------------- +declare -a RUNNING_CONTAINERS=() + +stop_containers() { + echo "[$LOCAL_SERVER_NAME] Checking containers on ${REMOTE_SERVER_NAME}..." + RUNNING_CONTAINERS=() + for container in "${CRITICAL_CONTAINER_NAMES[@]}"; do + STATUS=$(ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" \ + "docker inspect -f '{{.State.Running}}' $container 2>/dev/null" || echo "false") + if [ "$STATUS" == "true" ]; then + echo "Stopping $container" + RUNNING_CONTAINERS+=("$container") + ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" "docker stop $container" + fi + done +} + +start_containers() { + echo "[$LOCAL_SERVER_NAME] Restarting containers on ${REMOTE_SERVER_NAME}..." + for container in "${RUNNING_CONTAINERS[@]}"; do + if [[ " ${DELAYED_CONTAINERS[*]} " == *" $container "* ]]; then + echo "Delaying start of $container by ${CONTAINER_DELAY}s..." + sleep "$CONTAINER_DELAY" + fi + ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" "docker start $container" + done +} + +wait_for_rsync() { + for attempt in $(seq 1 "$RETRY_COUNT"); do + RSYNC_COUNT=$(pgrep -fc "rsync.*root@${REMOTE_SERVER}" || true) + if [ "$RSYNC_COUNT" -ge "$MAX_RSYNC_PROCS" ]; then + echo "[$LOCAL_SERVER_NAME] Waiting... ($RSYNC_COUNT active rsync processes)" + sleep "$SLEEP" + else + return 0 + fi + done + echo "Too many rsync processes, exiting." + exit 1 +} + +check_server_online() { + timeout 5 ping -c 1 "$REMOTE_SERVER" &>/dev/null +} \ No newline at end of file