Fix dead/incorrect vars and consolidate duplicated logic into common.sh

Codebase-wide audit pass: fixed real bugs (SSH hangs missing BatchMode,
local-outside-function no-ops, variable name collisions, a truncated
ratio calc, wrong state-dir path, DARK vs NO_INTERNET drift, and more),
then pulled logic that was duplicated across multiple scripts — arr
cleanup safety gates, docker restart ordering, container maintenance
stop/restart, watchdog state-file helpers, partnership role resolution,
cert expiry checks, remote node discovery, and TMDB discovery scoring —
into common.sh so each now has a single implementation.
This commit is contained in:
Gmer4Lfe
2026-07-03 23:52:33 -04:00
parent ef3980cf07
commit 6623d1e776
46 changed files with 921 additions and 1398 deletions
+31 -66
View File
@@ -249,22 +249,7 @@ fi
detect_hosts
# ── Derive owner and mirror from PARTNERSHIP_OWNER_HOST ───────────────────────────────────────
OWNER_ID="${PARTNERSHIP_OWNER_HOST:-HOST1}" # e.g. "HOST1"
MIRROR_ID=$( [[ "$OWNER_ID" == "HOST1" ]] && echo "HOST2" || echo "HOST1" )
OWNER="${!OWNER_ID}" # hostname string
MIRROR="${!MIRROR_ID}"
# SSH_KEY (set by detect_hosts) is this server's own private key.
# The remote accepts it because this server's PUBLIC key was installed there via ssh_setup.sh.
# With sparse checkout, each server only has its own host{N}.conf — the other server's
# key path is never available here. Use SSH_KEY for all outbound SSH regardless of mode.
MIRROR_SSH_KEY="$SSH_KEY"
OWNER_SSH_KEY="$SSH_KEY"
AM_OWNER=false
AM_MIRROR=false
[[ "$MY_ID" == "$OWNER_ID" ]] && AM_OWNER=true
[[ "$MY_ID" == "$MIRROR_ID" ]] && AM_MIRROR=true
partnership_resolve_roles
# State files
LOCAL_STATE_FILE="${STATE_DIR}/partnership_${LOCAL_SERVER_NAME}.db"
@@ -355,7 +340,7 @@ push_state_to_remote() {
warn "DRY RUN — would push state file to remote"
return 0
fi
timeout "$SSH_TIMEOUT" scp -i "$ssh_key" -o ConnectTimeout="$SSH_TIMEOUT" \
timeout "$SSH_TIMEOUT" scp -i "$ssh_key" -o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes \
"$local_file" "root@${remote_ip}:${local_file}" 2>/dev/null && \
echo "State file pushed to remote ✅" || \
warn "Could not push state file to remote — will propagate on next sync"
@@ -364,7 +349,7 @@ push_state_to_remote() {
read_remote_state() {
local remote_ip="$1" ssh_key="$2" remote_file="$3"
timeout "$SSH_TIMEOUT" ssh -i "$ssh_key" \
-o ConnectTimeout="$SSH_TIMEOUT" root@"$remote_ip" \
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$remote_ip" \
"cat '$remote_file' 2>/dev/null" 2>/dev/null
}
@@ -524,34 +509,9 @@ gather_partner_fallback_containers() {
done
}
# Read a scalar var from the mirror's own config via SSH.
# Sources load_config.sh + detect_hosts() on the remote so HOST* aliasing works.
read_remote_conf_var() {
local mirror_ip="$1" var_name="$2"
timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$mirror_ip" \
"source '$SCRIPT_DIR/../load_config.sh' 2>/dev/null
detect_hosts 2>/dev/null
printf '%s' \"\${${var_name}:-}\"" 2>/dev/null
}
# read_remote_conf_var() / read_remote_conf_array() — provided by common.sh
# Read an array var from the mirror's own config via SSH — one element per line.
read_remote_conf_array() {
local mirror_ip="$1" var_name="$2"
timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$mirror_ip" \
"source '$SCRIPT_DIR/../load_config.sh' 2>/dev/null
detect_hosts 2>/dev/null
printf '%s\n' \"\${${var_name}[@]:-}\"" 2>/dev/null
}
# Returns just the short name portion: "unRAID-Gmer4Lfe" → "Gmer4Lfe"
derive_short_name() {
local hostname="$1"
local short="${hostname,,}"
[[ "$short" == unraid-* ]] && short="${short:7}"
echo "${short^}"
}
# derive_short_name() — provided by common.sh
# Start this server's own parked containers after partnership ends.
start_own_stack() {
@@ -644,7 +604,7 @@ cleanup_owner_containers_on_mirror() {
local container_list
container_list=$(timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
-o ConnectTimeout="$SSH_TIMEOUT" root@"$mirror_ip" \
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$mirror_ip" \
"docker ps -a --format '{{.Names}}' 2>/dev/null | grep -i -- '-${owner_short}$'" 2>/dev/null)
if [[ -z "$container_list" ]]; then
@@ -658,12 +618,12 @@ cleanup_owner_containers_on_mirror() {
# Collect appdata paths before removal
local appdata_paths
appdata_paths=$(timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
-o ConnectTimeout="$SSH_TIMEOUT" root@"$mirror_ip" \
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$mirror_ip" \
"docker inspect --format '{{range .HostConfig.Binds}}{{println .}}{{end}}' '$container' 2>/dev/null \
| awk -F: '{print \$1}' | grep '^/mnt/.*/appdata'" 2>/dev/null)
timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
-o ConnectTimeout="$SSH_TIMEOUT" root@"$mirror_ip" \
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$mirror_ip" \
"docker stop '$container' >/dev/null 2>&1
docker rm '$container' >/dev/null 2>&1 && echo removed" 2>/dev/null | \
grep -q removed && \
@@ -674,7 +634,7 @@ cleanup_owner_containers_on_mirror() {
while IFS= read -r path; do
[[ -z "$path" ]] && continue
timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
-o ConnectTimeout="$SSH_TIMEOUT" root@"$mirror_ip" \
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$mirror_ip" \
"rm -rf '$path' && echo removed" 2>/dev/null | grep -q removed && \
echo " Appdata removed on $MIRROR: $path" || \
warn " Failed to remove appdata on $MIRROR: $path"
@@ -707,7 +667,7 @@ start_mirror_own_stack() {
continue
fi
timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
-o ConnectTimeout="$SSH_TIMEOUT" root@"$mirror_ip" \
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$mirror_ip" \
"docker start '$container' >/dev/null 2>&1 && echo started" 2>/dev/null | \
grep -q started && \
echo "$container started on $MIRROR" || \
@@ -881,7 +841,7 @@ check_both_healthy() {
[[ -z "$mirror_ip" ]] && { error "Cannot resolve $MIRROR Tailscale IP"; return 1; }
timeout "$SSH_TIMEOUT" ssh -i "$MIRROR_SSH_KEY" \
-o ConnectTimeout="$SSH_TIMEOUT" root@"$mirror_ip" \
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$mirror_ip" \
"mountpoint -q '$REMOTE_STORAGE_PATH' && timeout 10 docker ps" >/dev/null 2>&1 || {
error "Mirror $MIRROR not healthy"
return 1
@@ -913,7 +873,8 @@ do_final_sync() {
warn "Final sync complete — mirror has current state ✅"
}
# Safe master.conf modification with error handling
# Safe master.conf modification with error handling — appends the key if not already present,
# since sed -i returns 0 whether or not it matched anything.
update_master_conf() {
local key="$1" value="$2"
local conf="$CONF_DIR/master.conf"
@@ -921,12 +882,22 @@ update_master_conf() {
error "master.conf not found at $conf"
return 1
fi
if sed -i "s|^[[:space:]]*${key}=.*| ${key}=${value}|" "$conf" 2>/dev/null; then
echo "master.conf updated: ${key}=${value}"
return 0
if grep -q "^[[:space:]]*${key}=" "$conf" 2>/dev/null; then
if sed -i "s|^[[:space:]]*${key}=.*| ${key}=${value}|" "$conf" 2>/dev/null; then
echo "master.conf updated: ${key}=${value}"
return 0
else
error "Failed to update master.conf: ${key}=${value}"
return 1
fi
else
error "Failed to update master.conf: ${key}=${value}"
return 1
if echo " ${key}=${value}" >> "$conf" 2>/dev/null; then
echo "master.conf updated: ${key}=${value} (appended)"
return 0
else
error "Failed to append to master.conf: ${key}=${value}"
return 1
fi
fi
}
@@ -1162,13 +1133,7 @@ if [[ "$MODE" == "onboard" ]]; then
echo ""
echo "Enabling partnership in master.conf..."
if [[ "$DRY_RUN" == false ]]; then
_master_conf="$CONF_DIR/master.conf"
if grep -q "^[[:space:]]*PARTNERSHIP_ENABLED=" "$_master_conf" 2>/dev/null; then
sed -i "s|^[[:space:]]*PARTNERSHIP_ENABLED=.*|PARTNERSHIP_ENABLED=true|" "$_master_conf"
else
echo "PARTNERSHIP_ENABLED=true" >> "$_master_conf"
fi
echo "PARTNERSHIP_ENABLED=true in master.conf ✅"
update_master_conf "PARTNERSHIP_ENABLED" "true"
platform_push_conf | while IFS= read -r line; do log "$line"; done
else
warn "DRY RUN — would set PARTNERSHIP_ENABLED=true in master.conf and push"
@@ -1435,9 +1400,9 @@ if false; then
# Update remote master.conf
timeout "$SSH_TIMEOUT" ssh -i "$NEW_MIRROR_SSH_KEY" \
-o ConnectTimeout="$SSH_TIMEOUT" root@"$NEW_MIRROR_IP" \
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$NEW_MIRROR_IP" \
"sed -i 's|^[[:space:]]*PARTNERSHIP_OWNER_HOST=.*| PARTNERSHIP_OWNER_HOST=\"$NEW_OWNER_ID\"|' \
'$SCRIPT_DIR/../master.conf'" 2>/dev/null && \
'$SCRIPT_DIR/../Configurations/master.conf'" 2>/dev/null && \
echo "Remote master.conf updated ✅" || \
error "Failed to update remote master.conf — update manually"
else
+1 -11
View File
@@ -108,17 +108,7 @@ fi
detect_hosts
OWNER_ID="${PARTNERSHIP_OWNER_HOST:-HOST1}"
MIRROR_ID=$( [[ "$OWNER_ID" == "HOST1" ]] && echo "HOST2" || echo "HOST1" )
OWNER="${!OWNER_ID}"
MIRROR="${!MIRROR_ID}"
MIRROR_SSH_KEY="$SSH_KEY"
OWNER_SSH_KEY="$SSH_KEY"
AM_OWNER=false
AM_MIRROR=false
[[ "$MY_ID" == "$OWNER_ID" ]] && AM_OWNER=true
[[ "$MY_ID" == "$MIRROR_ID" ]] && AM_MIRROR=true
partnership_resolve_roles
LOCAL_STATE_FILE="${STATE_DIR}/partnership_${LOCAL_SERVER_NAME}.db"
REMOTE_STATE_FILE="${STATE_DIR}/partnership_${REMOTE_SERVER_NAME}.db"
+8 -37
View File
@@ -223,20 +223,7 @@ fi
detect_hosts
OWNER_ID="${PARTNERSHIP_OWNER_HOST:-HOST1}"
MIRROR_ID=$( [[ "$OWNER_ID" == "HOST1" ]] && echo "HOST2" || echo "HOST1" )
OWNER="${!OWNER_ID}"
MIRROR="${!MIRROR_ID}"
# SSH_KEY (set by detect_hosts) is this server's own private key.
# The remote accepts it because this server's PUBLIC key was installed there via ssh_setup.sh.
# HOST{N}_SSH_KEY lives in host{N}.conf — with sparse checkout, the other server's
# conf is never present here. Always use SSH_KEY (local private key) for outbound SSH.
MIRROR_SSH_KEY="$SSH_KEY"
AM_OWNER=false
AM_MIRROR=false
[[ "$MY_ID" == "$OWNER_ID" ]] && AM_OWNER=true
[[ "$MY_ID" == "$MIRROR_ID" ]] && AM_MIRROR=true
partnership_resolve_roles
EXTRA_FLAGS=()
[[ "$DRY_RUN" == true ]] && EXTRA_FLAGS+=("--dry-run")
@@ -250,11 +237,7 @@ write_onboard_phase() {
local key="${target_id}_PHASE${phase}_DONE"
local state_file="$(platform_setup_db_path)"
[[ "$DRY_RUN" == true ]] && { warn "DRY RUN — would write ${key}=true"; return 0; }
if grep -q "^${key}=" "$state_file" 2>/dev/null; then
sed -i "s|^${key}=.*|${key}=true|" "$state_file"
else
echo "${key}=true" >> "$state_file"
fi
set_state_var "$state_file" "$key" "true"
platform_push_setup_state
}
@@ -282,13 +265,7 @@ stop_mirror_stack() {
local config_var="$1" label="$2"
local -a to_stop=()
mapfile -t to_stop < <(
timeout "$SSH_TIMEOUT" ssh -i "$MIRROR_SSH_KEY" \
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$MIRROR_IP" \
"source '$SCRIPTS_ROOT/load_config.sh' 2>/dev/null
detect_hosts 2>/dev/null
printf '%s\n' \"\${${config_var}[@]:-}\"" 2>/dev/null | grep -v '^$'
)
mapfile -t to_stop < <(read_remote_conf_array "$MIRROR_IP" "$config_var" | grep -v '^$')
if [[ ${#to_stop[@]} -eq 0 ]]; then
log "No $label containers to stop on $MIRROR — skipping"
@@ -302,7 +279,7 @@ stop_mirror_stack() {
continue
fi
timeout "$SSH_TIMEOUT" ssh -i "$MIRROR_SSH_KEY" \
-o ConnectTimeout="$SSH_TIMEOUT" root@"$MIRROR_IP" \
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$MIRROR_IP" \
"docker stop '$container' 2>/dev/null
docker rm '$container' 2>/dev/null && echo removed" 2>/dev/null | \
grep -q removed && \
@@ -338,11 +315,7 @@ if [[ "$AM_MIRROR" == true ]]; then
if [[ -n "$OWNER_IP" ]]; then
# Read OWNER's SCRIPTS_DIR via platform probe command — don't assume same path as mirror
_probe_cmd=$(platform_scripts_dir_probe_cmd)
OWNER_SCRIPTS_DIR=$(timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$OWNER_IP" \
"$_probe_cmd" 2>/dev/null | tr -d '[:space:]')
OWNER_SCRIPTS_DIR="${OWNER_SCRIPTS_DIR:-$SCRIPTS_DIR}"
OWNER_SCRIPTS_DIR=$(resolve_remote_scripts_dir "$OWNER_IP")
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would SSH to $OWNER ($OWNER_IP) and trigger Phase 2"
@@ -444,11 +417,9 @@ elif [[ "$PHASE1_ONLY" == true ]]; then
echo " Then click 'Push Conf' in the Partnership tab."
# Write key-ready flag so UI can show the manual-install state
[[ "$DRY_RUN" == false ]] && {
local kflag="${MIRROR_ID}_KEY_READY"
local _setup_f="$(platform_setup_db_path)"
grep -q "^${kflag}=" "$_setup_f" 2>/dev/null \
&& sed -i "s|^${kflag}=.*|${kflag}=true|" "$_setup_f" \
|| echo "${kflag}=true" >> "$_setup_f"
kflag="${MIRROR_ID}_KEY_READY"
_setup_f="$(platform_setup_db_path)"
set_state_var "$_setup_f" "$kflag" "true"
}
fi
STEP_SSH_OK=false
+3 -16
View File
@@ -128,17 +128,7 @@ fi
detect_hosts
OWNER_ID="${PARTNERSHIP_OWNER_HOST:-HOST1}"
MIRROR_ID=$( [[ "$OWNER_ID" == "HOST1" ]] && echo "HOST2" || echo "HOST1" )
OWNER="${!OWNER_ID}"
MIRROR="${!MIRROR_ID}"
MIRROR_SSH_KEY="$SSH_KEY"
OWNER_SSH_KEY="$SSH_KEY"
AM_OWNER=false
AM_MIRROR=false
[[ "$MY_ID" == "$OWNER_ID" ]] && AM_OWNER=true
[[ "$MY_ID" == "$MIRROR_ID" ]] && AM_MIRROR=true
partnership_resolve_roles
LOCAL_STATE_FILE="${STATE_DIR}/partnership_${LOCAL_SERVER_NAME}.db"
REMOTE_STATE_FILE="${STATE_DIR}/partnership_${REMOTE_SERVER_NAME}.db"
@@ -286,14 +276,11 @@ if [[ "$DRY_RUN" == false ]]; then
# Push updated master.conf to new owner so both servers agree immediately.
# master.conf is shared — host-specific credentials live in host*.conf.
_probe_cmd=$(platform_scripts_dir_probe_cmd)
_REMOTE_SD=$(ssh -i "$SSH_KEY" -o ConnectTimeout=5 -o StrictHostKeyChecking=no \
"root@${MIRROR_IP}" "$_probe_cmd" \
2>/dev/null | tr -d '[:space:]')
_REMOTE_SD="${_REMOTE_SD:-$SCRIPTS_DIR}"
_REMOTE_SD=$(resolve_remote_scripts_dir "$MIRROR_IP" "$SSH_KEY" "no")
scp -i "$SSH_KEY" \
-o ConnectTimeout="$SSH_TIMEOUT" \
-o StrictHostKeyChecking=no \
-o BatchMode=yes \
"$SCRIPTS_ROOT/Configurations/master.conf" \
"root@${MIRROR_IP}:${_REMOTE_SD}/Configurations/master.conf" 2>/dev/null && \
echo "master.conf pushed to $NEW_OWNER" || \
+1 -1
View File
@@ -52,7 +52,7 @@ MIRROR_IP=$(resolve_tailscale_ip "$MIRROR")
[[ -z "$MIRROR_IP" ]] && { error "Cannot resolve $MIRROR Tailscale IP — is Tailscale running?"; exit 1; }
_ssh() { ssh -i "$SSH_KEY" -o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=no root@"$MIRROR_IP" "$@"; }
_scp() { scp -i "$SSH_KEY" -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$@"; }
_scp() { scp -i "$SSH_KEY" -o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=no "$@"; }
# ── Detect remote appdata pool ────────────────────────────────────────────────────────────────
+4 -3
View File
@@ -74,9 +74,10 @@ acquire_lock
detect_hosts
# ── Derive key name from hostname ─────────────────────────────────────────────────────────────
# Strip unraid- prefix (case-insensitive) if present → gmer4lfe_rsync_automation
SHORT_NAME="${LOCAL_SERVER_NAME,,}"
[[ "${SHORT_NAME}" == unraid-* ]] && SHORT_NAME="${SHORT_NAME:7}"
# derive_short_name() title-cases the result (for display elsewhere) — lowercase it here,
# same as before, since this feeds a filename → gmer4lfe_rsync_automation
SHORT_NAME="$(derive_short_name "$LOCAL_SERVER_NAME")"
SHORT_NAME="${SHORT_NAME,,}"
SSH_KEY_NAME="${SHORT_NAME}_rsync_automation"
SSH_KEY_PATH="/root/.ssh/${SSH_KEY_NAME}"
SSH_PUB_PATH="${SSH_KEY_PATH}.pub"