diff --git a/Master.conf b/Master.conf index 7a14a6a..fcbcf3a 100644 --- a/Master.conf +++ b/Master.conf @@ -2,98 +2,160 @@ #----------------------------------------------------------------------------------------------- #----------------------------- Master Variables for unRAID scripts ----------------------------- #----------------------------------------------------------------------------------------------- +#----------------- User Variables, Please adjust here in Master.conf as needed ----------------- +#----------------------------------------------------------------------------------------------- +# This is the master.conf file, it contains all the variables used by +# all scripts. It is sourced by each script at runtime, so any changes here will +# affect all scripts immediately. This is where you should adjust any settings or variables +# that you want to apply globally across all your scripts +# +#This allows for one centralized location for all your configurations, making it easier to manage +# and maintain your scripts. You can adjust the variables below as needed. +# +#----------------------------------------------------------------------------------------------- +#---------------End Of User Variables, Please adjust here in Master.conf as needed ------------- +#----------------------------------------------------------------------------------------------- #-------------------- Host Configuration -------------------- # Hostnames of both servers HOST1="unRAID-Gmer4Lfe" HOST2="unRAID-Jayred365" -# SSH Key Mapping (data only) -declare -A SSH_KEYS -SSH_KEYS["unRAID-Gmer4Lfe|unRAID-Jayred365"]="/root/.ssh/Gmer4Lfe-rsync-key" -SSH_KEYS["unRAID-Jayred365|unRAID-Gmer4Lfe"]="/root/.ssh/Jayred365-rsync-key" +# Detect local hostname +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" + + +# Remote Server Ip Resolution +# Resolve Tailscale IP dynamically +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 + + +# SSH Key Mapping +SSH_KEYS["$HOST1|$HOST2"]="/root/.ssh/Gmer4Lfe-rsync-key" +SSH_KEYS["$HOST2|$HOST1"]="/root/.ssh/Jayred365-rsync-key" + +KEY_ID="$LOCAL_SERVER_NAME|$REMOTE_SERVER_NAME" +if [[ -n "${SSH_KEYS[$KEY_ID]}" ]]; then + SSH_KEY="${SSH_KEYS[$KEY_ID]}" +else + echo "Error: No SSH key defined for $LOCAL_SERVER_NAME → $REMOTE_SERVER_NAME" + exit 1 +fi +echo "Using SSH key: $SSH_KEY" #----------------------------------------------------------------------------------------------- #----------------- User Variables -------------------------------------------------------------- #----------------------------------------------------------------------------------------------- -#-------------------- Rsync Defaults ----------------------- +#--------------------Rsync Script Defaults----------------------- +# Network speed limit (KB/s) BW_LIMIT=12500 +# Retry logic RETRY_COUNT=3 +# Sleep between retries (seconds) SLEEP=300 +# Max number of concurrently running rsync processes MAX_RSYNC_PROCS=1 +# Container start/stop/restart before and after rsync CRITICAL_CONTAINER_NAMES=("") +# Containers that need delayed before starting DELAYED_CONTAINERS=("") +# Delay in seconds between starting containers, useful for things like Authelia CONTAINER_DELAY=5 +# Not include logs during transfer EXCLUDE_DIRS=("") -#-------------------- Essential Scripts ------------------ +#-------------------- unRAID essentail scripts ------------------ +# unRAID reboot script user warning time (seconds) REBOOT_SLEEP=300 +# unRAID mover stop script timeout (seconds) MOVER_STOP_TIMEOUT=300 +# docker_syslog_filter.sh filter file path FILTER_FILE="/etc/rsyslog.d/ignore-docker-veth.conf" +# php_fpm_max_children.sh php-fpm config file path PHP_CONF="/etc/php-fpm.d/www.conf" +# php_fpm_max_children.sh max children value PHP_MAX_CHILDREN=250 #---------------------------------------------------------------------------------------------- #----------------------------------- Profile System ------------------------------------------- #---------------------------------------------------------------------------------------------- - +# Profiles are mainly used for rsync operations, but can be used for any variable that needs +# to be different based on the source or destination. +# +# Profiles are inferred from the directory basename (lowercased) +# Max rsync processes per profile declare -A PROFILE_MAX_PROCS PROFILE_MAX_PROCS["arrs_stack"]=2 PROFILE_MAX_PROCS["critical-data"]=2 PROFILE_MAX_PROCS["gmer4lfe"]=1 PROFILE_MAX_PROCS["important-data"]=2 PROFILE_MAX_PROCS["emby"]=1 - +# Bandwidth limits per profile (KB/s, 0 = unlimited) declare -A PROFILE_BW_LIMIT PROFILE_BW_LIMIT["arrs_stack"]=5000 PROFILE_BW_LIMIT["critical-data"]=9500 PROFILE_BW_LIMIT["gmer4lfe"]=8000 PROFILE_BW_LIMIT["important-data"]=9500 PROFILE_BW_LIMIT["emby"]=8000 - +# Retry logic per profile declare -A PROFILE_RETRY_COUNT PROFILE_RETRY_COUNT["arrs_stack"]=3 PROFILE_RETRY_COUNT["critical-data"]=3 PROFILE_RETRY_COUNT["gmer4lfe"]=3 PROFILE_RETRY_COUNT["important-data"]=3 PROFILE_RETRY_COUNT["emby"]=3 - +# Sleep between retries per profile (seconds) declare -A PROFILE_SLEEP PROFILE_SLEEP["arrs_stack"]=300 PROFILE_SLEEP["critical-data"]=300 PROFILE_SLEEP["gmer4lfe"]=300 PROFILE_SLEEP["important-data"]=300 PROFILE_SLEEP["emby"]=300 - +# Containers to stop/start per profile (space-separated strings) declare -A PROFILE_CRITICAL_CONTAINER_NAMES PROFILE_CRITICAL_CONTAINER_NAMES["arrs_stack"]="Sonarr Lidarr Readarr Radarr Prowlarr Bazarr Pinchflat" PROFILE_CRITICAL_CONTAINER_NAMES["critical-data"]="Mariadb-Authelia Redis-Authelia Lldap-Gmer4Lfe NginxProxyManager Authelia" PROFILE_CRITICAL_CONTAINER_NAMES["gmer4lfe"]="Organizrv2-Gmer4Lfe UptimeKuma-Gmer4Lfe VaultWarden-Gmer4Lfe" PROFILE_CRITICAL_CONTAINER_NAMES["important-data"]="Postgres-NextCloud NextCloud" PROFILE_CRITICAL_CONTAINER_NAMES["emby"]="" - +# Delayed containers per profile (space-separated strings) declare -A PROFILE_DELAYED_CONTAINERS PROFILE_DELAYED_CONTAINERS["arrs_stack"]="" PROFILE_DELAYED_CONTAINERS["critical-data"]="Authelia" PROFILE_DELAYED_CONTAINERS["gmer4lfe"]="" PROFILE_DELAYED_CONTAINERS["important-data"]="NextCloud" PROFILE_DELAYED_CONTAINERS["emby"]="" - +# Delay in seconds before starting delayed containers per profile (seconds) declare -A PROFILE_CONTAINER_DELAY PROFILE_CONTAINER_DELAY["arrs_stack"]=5 PROFILE_CONTAINER_DELAY["critical-data"]=10 PROFILE_CONTAINER_DELAY["gmer4lfe"]=5 PROFILE_CONTAINER_DELAY["important-data"]=10 PROFILE_CONTAINER_DELAY["emby"]=5 - +# Not include logs or other file types during transfer per profile declare -A PROFILE_EXCLUDE_DIRS PROFILE_EXCLUDE_DIRS["arrs_stack"]="logs *.tmp" PROFILE_EXCLUDE_DIRS["critical-data"]="logs *.tmp" PROFILE_EXCLUDE_DIRS["gmer4lfe"]="logs *.tmp" PROFILE_EXCLUDE_DIRS["important-data"]="logs *.tmp" PROFILE_EXCLUDE_DIRS["emby"]="logs *.tmp" - #----------------------------------------------------------------------------------------------- -#----------------------------- End of Master.conf ---------------------------------------------- +#---------------End Of User Variables, Please adjust here in Master.conf as needed ------------- #----------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Rsync/rsync.sh b/Rsync/rsync.sh index fa96f7e..5594735 100644 --- a/Rsync/rsync.sh +++ b/Rsync/rsync.sh @@ -25,29 +25,25 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../common.sh" -# Load config +# Load central config load_master_conf -# 🔥 Handle directory FIRST +# Input Arguments DIRECTORY="$1" if [[ -z "$DIRECTORY" ]]; then - echo "Usage: $0 [--dry-run] [VAR=value ...]" + echo "Usage: $0 [--dry-run|-n] [VAR=value ...]" exit 1 fi +shift # remove DIRECTORY -shift 1 - -# Parse args AFTER shifting +# Parse CLI args (flags + VAR=value) parse_args "$@" -# Detect hosts AFTER config + args -detect_hosts - # Determine profile PROFILE_NAME=$(basename "$DIRECTORY" | tr '[:upper:]' '[:lower:]') echo "Detected profile: $PROFILE_NAME" -# Apply profile defaults +# Apply profile defaults or fallback to Master.conf 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]}) @@ -57,16 +53,27 @@ RETRY_COUNT=${PROFILE_RETRY_COUNT[$PROFILE_NAME]:-$RETRY_COUNT} SLEEP=${PROFILE_SLEEP[$PROFILE_NAME]:-$SLEEP} EXCLUDE_DIRS=(${PROFILE_EXCLUDE_DIRS[$PROFILE_NAME]:-${EXCLUDE_DIRS[@]}}) +# Validate numeric variables +validate_int MAX_RSYNC_PROCS "$MAX_RSYNC_PROCS" +validate_int BW_LIMIT "$BW_LIMIT" +validate_int CONTAINER_DELAY "$CONTAINER_DELAY" +validate_int RETRY_COUNT "$RETRY_COUNT" +validate_int SLEEP "$SLEEP" + echo "Settings: MAX_RSYNC_PROCS=$MAX_RSYNC_PROCS, BW_LIMIT=$BW_LIMIT, CONTAINERS=${CRITICAL_CONTAINER_NAMES[*]}, EXCLUDES=${EXCLUDE_DIRS[*]}" -#============================================================ -# Helper Functions -#============================================================ +# 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 + +# Helper functions 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 + if [ "$RSYNC_COUNT" -ge "$MAX_RSYNC_PROCS" ]; then echo "[$LOCAL_SERVER_NAME] Waiting... ($RSYNC_COUNT active rsync processes)" sleep "$SLEEP" else @@ -81,39 +88,42 @@ 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 + if [ "$STATUS" == "true" ]; then echo "Stopping $container" RUNNING_CONTAINERS+=("$container") - [[ "$DRY_RUN" = true ]] || ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" "docker stop $container" + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Would stop container $container" + else + ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" "docker stop $container" + fi 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" + [ "$DRY_RUN" = false ] && sleep "$CONTAINER_DELAY" + fi + if [ "$DRY_RUN" = true ]; then + echo "[DRY-RUN] Would start container $container" + else + ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" "docker start $container" fi - - [[ "$DRY_RUN" = true ]] || ssh -i "$SSH_KEY" root@"$REMOTE_SERVER" "docker start $container" done } -#============================================================ -# Main Logic -#============================================================ - +# Main sync logic main() { echo "[$LOCAL_SERVER_NAME] Syncing $DIRECTORY → $REMOTE_SERVER:$DIRECTORY" @@ -138,15 +148,15 @@ main() { --no-whole-file ) + # Apply multiple exclude patterns for pattern in "${EXCLUDE_DIRS[@]}"; do RSYNC_OPTS+=(--exclude="$pattern") done - [[ "$DRY_RUN" = true ]] && RSYNC_OPTS+=(--dry-run) + [ "$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" \ "$DIRECTORY" "root@${REMOTE_SERVER}:$DIRECTORY"; then @@ -154,8 +164,7 @@ main() { break else echo "Rsync attempt $attempt failed." - - if [[ "$attempt" -lt "$RETRY_COUNT" ]]; then + if [ "$attempt" -lt "$RETRY_COUNT" ]; then echo "Retrying in $SLEEP seconds..." sleep "$SLEEP" else @@ -170,4 +179,5 @@ main() { echo "[$LOCAL_SERVER_NAME] Sync complete." } +# Execute main \ No newline at end of file diff --git a/common.sh b/common.sh index 86a3361..6eac5d8 100644 --- a/common.sh +++ b/common.sh @@ -1,7 +1,20 @@ #!/bin/bash #----------------------------------------------------------------------------------------------- -#--------------------- Common Shared Functions ------------------------------------------------- +# --------------------- Common Shared Functions for Unraid Scripts ----------------------------- +#----------------------------------------------------------------------------------------------- +#----------------- User Variables, Please adjust in Master.conf as needed ---------------------- +#----------------------------------------------------------------------------------------------- +# +# Nothing to adjust here, all user variables should be in Master.conf +# +# This is just a script that has all common functions and logic that are shared +# between the different scripts. +# +# This way we can avoid code duplication and have a single source +# of truth for all common functions and logic. +#----------------------------------------------------------------------------------------------- +#---------------End Of User Variables, Please adjust in Master.conf as needed ------------------ #----------------------------------------------------------------------------------------------- # Load Master.conf @@ -19,7 +32,7 @@ load_master_conf() { fi } -# Parse CLI arguments +# Parse CLI args parse_args() { DRY_RUN=${DRY_RUN:-false} @@ -57,59 +70,11 @@ validate_int() { fi } -# Require variable +# Require variable to be set require_var() { local var="$1" if [[ -z "${!var:-}" ]]; then echo "Error: Required variable $var is not set." exit 1 fi -} - -# Detect hosts + SSH key + Tailscale IP -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 - echo "Error: Local hostname ($LOCAL_HOSTNAME) not recognized." - exit 1 - fi - - echo "Local server: $LOCAL_SERVER_NAME" - echo "Remote server: $REMOTE_SERVER_NAME" - - # Resolve Tailscale IP - 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 - - # Validate SSH_KEYS - if [[ "$(declare -p SSH_KEYS 2>/dev/null)" != declare\ -A* ]]; then - echo "Error: SSH_KEYS is not a valid associative array." - exit 1 - fi - - KEY_ID="$LOCAL_SERVER_NAME|$REMOTE_SERVER_NAME" - - # Debug (safe to keep) - echo "DEBUG: KEY_ID=$KEY_ID" - echo "DEBUG: Available keys: ${!SSH_KEYS[@]}" - - if [[ -n "${SSH_KEYS[$KEY_ID]}" ]]; then - SSH_KEY="${SSH_KEYS[$KEY_ID]}" - else - echo "Error: No SSH key defined for $LOCAL_SERVER_NAME → $REMOTE_SERVER_NAME" - exit 1 - fi - - echo "Using SSH key: $SSH_KEY" } \ No newline at end of file