Make PARTNERSHIP_ENABLED the authoritative gate for all cross-server operations

Adds require_partnership() to common.sh — exits cleanly when PARTNERSHIP_ENABLED=false.
Removes FALLBACK_PARTNERSHIP_REQUIRED toggle — partnership is now always required,
not optional. Cross-server scripts (rsync, conf sync, fallback, arr sync, play state,
backup verify) all call require_partnership after detect_hosts.
This commit is contained in:
Gmer4Lfe
2026-06-19 18:27:41 -04:00
parent 7a12c3eee6
commit 0564580605
13 changed files with 77 additions and 80 deletions
+62 -73
View File
@@ -116,12 +116,11 @@
# Exits if the host cannot be identified — prevents running on an unknown machine.
#
# Partnership Gate
# When FALLBACK_PARTNERSHIP_REQUIRED=true, fallback is only permitted when the
# local partnership DB reports an active partnership. If partnership goes inactive,
# a grace timer starts. After FALLBACK_PARTNERSHIP_SUSPEND_AFTER minutes the loop
# suspends: no new FALLBACK is entered, and any live fallback containers are
# stopped cleanly. Resumes automatically when partnership becomes active again.
# Set FALLBACK_PARTNERSHIP_REQUIRED=false to run fallback without a partnership.
# Fallback is only permitted when the local partnership DB reports an active
# partnership. If partnership goes inactive, a grace timer starts. After
# FALLBACK_PARTNERSHIP_SUSPEND_AFTER minutes the loop suspends: no new FALLBACK
# is entered, and any live fallback containers are stopped cleanly. Resumes
# automatically when partnership becomes active again.
#
# Silent by Default
# State transitions: warn() — always visible. Healthy routine cycles: log()
@@ -174,11 +173,6 @@
# FALLBACK_ENABLED
# false = exit cleanly (e.g. remote server being rebuilt). (default: false)
#
# FALLBACK_PARTNERSHIP_REQUIRED
# true = fallback is gated on an active partnership — suspends when no active
# partner is found after FALLBACK_PARTNERSHIP_SUSPEND_AFTER minutes.
# false = fallback runs standalone regardless of partnership state. (default: true)
#
# FALLBACK_PARTNERSHIP_SUSPEND_AFTER
# Minutes without an active partnership before fallback suspends itself.
# Grace period: fallback continues normally during this window.
@@ -290,6 +284,7 @@ if ! command -v docker &>/dev/null; then
fi
detect_hosts
require_partnership
resolve_remote_ip
# Validate unRAID notify script — used throughout for state change notifications
@@ -301,7 +296,7 @@ DOCKER_TIMEOUT=15
SSH_TIMEOUT=10
CONTAINER_VERIFY_WAIT=5 # seconds after start before verifying container is up
log "$ICON_GEAR Config: check-interval=${FALLBACK_CHECK_INTERVAL}s handback-strikes=${FALLBACK_HANDBACK_STRIKES} partnership-required=${FALLBACK_PARTNERSHIP_REQUIRED:-true} rsync-enabled=${FALLBACK_RSYNC_ENABLED:-true}"
log "$ICON_GEAR Config: check-interval=${FALLBACK_CHECK_INTERVAL}s handback-strikes=${FALLBACK_HANDBACK_STRIKES} rsync-enabled=${FALLBACK_RSYNC_ENABLED:-true}"
log "$ICON_GEAR Timeouts: docker=${DOCKER_TIMEOUT}s ssh=${SSH_TIMEOUT}s verify-wait=${CONTAINER_VERIFY_WAIT}s"
# ==============================================================================================
@@ -627,19 +622,15 @@ if [[ "$SHOW_STATUS" == true ]]; then
echo "$ICON_GEAR Enabled: $FALLBACK_ENABLED"
echo "$ICON_GEAR Dry Run: $DRY_RUN"
if [[ "${FALLBACK_PARTNERSHIP_REQUIRED:-true}" == "true" ]]; then
SUSPENDED=$(state_get partnership_suspended)
PLOST=$(state_get partner_lost_at)
if [[ "$SUSPENDED" == "true" ]]; then
echo "$ICON_GEAR Partnership: SUSPENDED (no active partnership)"
elif [[ -n "$PLOST" && "$PLOST" != "0" ]]; then
PLOST_MIN=$(( ($(date +%s) - PLOST) / 60 ))
echo "$ICON_GEAR Partnership: GRACE (${PLOST_MIN}min / ${FALLBACK_PARTNERSHIP_SUSPEND_AFTER:-120}min until suspend)"
else
echo "$ICON_GEAR Partnership: ACTIVE"
fi
SUSPENDED=$(state_get partnership_suspended)
PLOST=$(state_get partner_lost_at)
if [[ "$SUSPENDED" == "true" ]]; then
echo "$ICON_GEAR Partnership: SUSPENDED (no active partnership)"
elif [[ -n "$PLOST" && "$PLOST" != "0" ]]; then
PLOST_MIN=$(( ($(date +%s) - PLOST) / 60 ))
echo "$ICON_GEAR Partnership: GRACE (${PLOST_MIN}min / ${FALLBACK_PARTNERSHIP_SUSPEND_AFTER:-120}min until suspend)"
else
echo "$ICON_GEAR Partnership: REQUIRED=false (standalone mode)"
echo "$ICON_GEAR Partnership: ACTIVE"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━"
@@ -862,57 +853,55 @@ while [[ "$FALLBACK_RUNNING" == true ]]; do
log "Remote: $REMOTE_UP | Internet: $INTERNET_UP | State: $CURRENT_STATE"
# ── Partnership gate ──────────────────────────────────────────────────────────────────────
# When FALLBACK_PARTNERSHIP_REQUIRED=true, a fallback without an active partner is
# meaningless — who are we covering for? The gate suspends the loop after a grace
# period and aborts any live fallback containers. Clears automatically when partnership
# is restored.
if [[ "${FALLBACK_PARTNERSHIP_REQUIRED:-true}" == "true" ]]; then
if check_partnership_active; then
# Partnership healthy — clear any accumulated grace state
if [[ "$(state_get partnership_suspended)" == "true" ]]; then
warn "Active partnership restored — re-enabling fallback monitoring"
state_set partnership_suspended "false"
state_set partner_lost_at "0"
notify "Fallback re-enabled on $(hostname) — active partnership restored" \
"Fallback" "normal"
elif [[ "$(state_get partner_lost_at)" != "0" ]]; then
log "Partnership recovered within grace window — resetting timer"
state_set partner_lost_at "0"
# ── Partnership Gate ──────────────────────────────────────────────────────────────────────
# A fallback without an active partner is meaningless — who are we covering for?
# The gate suspends the loop after a grace period and aborts any live fallback
# containers. Clears automatically when partnership becomes active again.
if check_partnership_active; then
# Partnership healthy — clear any accumulated grace state
if [[ "$(state_get partnership_suspended)" == "true" ]]; then
warn "Active partnership restored — re-enabling fallback monitoring"
state_set partnership_suspended "false"
state_set partner_lost_at "0"
notify "Fallback re-enabled on $(hostname) — active partnership restored" \
"Fallback" "normal"
elif [[ "$(state_get partner_lost_at)" != "0" ]]; then
log "Partnership recovered within grace window — resetting timer"
state_set partner_lost_at "0"
fi
else
PARTNER_LOST_AT=$(state_get partner_lost_at)
if [[ -z "$PARTNER_LOST_AT" || "$PARTNER_LOST_AT" == "0" ]]; then
warn "No active partnership detected — grace timer started (${FALLBACK_PARTNERSHIP_SUSPEND_AFTER:-120}min before suspend)"
state_set partner_lost_at "$NOW"
PARTNER_LOST_AT=$NOW
fi
PARTNER_LOST_MIN=$(( (NOW - PARTNER_LOST_AT) / 60 ))
SUSPEND_AFTER=${FALLBACK_PARTNERSHIP_SUSPEND_AFTER:-120}
if [[ "$(state_get partnership_suspended)" == "true" || "$PARTNER_LOST_MIN" -ge "$SUSPEND_AFTER" ]]; then
# Suspend — abort any live fallback containers, then skip this cycle entirely
if [[ "$(state_get partnership_suspended)" != "true" ]]; then
warn "No active partnership for ${PARTNER_LOST_MIN}min — suspending fallback"
state_set partnership_suspended "true"
notify "Fallback suspended on $(hostname) — no active partnership for ${PARTNER_LOST_MIN}min" \
"Fallback" "warning"
fi
if [[ "$CURRENT_STATE" == "FALLBACK" ]]; then
warn "Aborting in-progress fallback — partnership suspended"
_abort_fallback_containers
state_set state "NORMAL"
state_set fallback_start "0"
state_set handback_strikes "0"
notify "Fallback aborted on $(hostname) — no active partnership, containers stopped" \
"Fallback" "warning"
fi
log "Partnership suspended — skipping fallback cycle"
sleep "$FALLBACK_CHECK_INTERVAL" & wait $!
continue
else
PARTNER_LOST_AT=$(state_get partner_lost_at)
if [[ -z "$PARTNER_LOST_AT" || "$PARTNER_LOST_AT" == "0" ]]; then
warn "No active partnership detected — grace timer started (${FALLBACK_PARTNERSHIP_SUSPEND_AFTER:-120}min before suspend)"
state_set partner_lost_at "$NOW"
PARTNER_LOST_AT=$NOW
fi
PARTNER_LOST_MIN=$(( (NOW - PARTNER_LOST_AT) / 60 ))
SUSPEND_AFTER=${FALLBACK_PARTNERSHIP_SUSPEND_AFTER:-120}
if [[ "$(state_get partnership_suspended)" == "true" || "$PARTNER_LOST_MIN" -ge "$SUSPEND_AFTER" ]]; then
# Suspend — abort any live fallback containers, then skip this cycle entirely
if [[ "$(state_get partnership_suspended)" != "true" ]]; then
warn "No active partnership for ${PARTNER_LOST_MIN}min — suspending fallback"
state_set partnership_suspended "true"
notify "Fallback suspended on $(hostname) — no active partnership for ${PARTNER_LOST_MIN}min" \
"Fallback" "warning"
fi
if [[ "$CURRENT_STATE" == "FALLBACK" ]]; then
warn "Aborting in-progress fallback — partnership suspended"
_abort_fallback_containers
state_set state "NORMAL"
state_set fallback_start "0"
state_set handback_strikes "0"
notify "Fallback aborted on $(hostname) — no active partnership, containers stopped" \
"Fallback" "warning"
fi
log "Partnership suspended — skipping fallback cycle"
sleep "$FALLBACK_CHECK_INTERVAL" & wait $!
continue
else
log "No active partnership — ${PARTNER_LOST_MIN}min elapsed / ${SUSPEND_AFTER}min until suspend"
fi
log "No active partnership — ${PARTNER_LOST_MIN}min elapsed / ${SUSPEND_AFTER}min until suspend"
fi
fi