fix 9 bugs in partnership offboard flow

partnership_manager.sh:
- fix auto-offboard threshold: × 96 → × 48 (was triggering at 2× configured days)
- fix do_final_sync(): use CRITICAL_SYNC_SHARES if configured; fall back to hardcoded paths
- replace inline --check offboard blocks with background partnership_offboard.sh calls:
  • owner path was missing remote container cleanup (steps 8+9) and blocklist
  • mirror path was missing blocklist, SSH revocation, CRITICAL_RSYNC_ENABLED=false
  • local grace_seconds outside a function (bash error) eliminated with the block removal
  • 6hr Tailscale grace sleep was blocking the cron for the full grace window

partnership_offboard.sh:
- reorder owner path: state write moved from step 5 to step 10 (last) so re-running
  after a mid-offboard crash restarts from scratch instead of exiting as INACTIVE;
  state is now written before the Tailscale grace sleep so --check stops re-triggering
- add FolderView3 remote folder cleanup in step 7 (mirror's fallback folder persisted)
- track step 1+2 outcome with STEP_STOP_OK / STEP_SYNC_OK; summary now reflects actual
  result instead of hardcoding  regardless of dry-run or failure
- update header step list to match new step order
This commit is contained in:
Gmer4Lfe
2026-05-23 09:24:34 -04:00
parent 14a74939e6
commit fda853eed8
2 changed files with 121 additions and 118 deletions
+87 -61
View File
@@ -18,12 +18,13 @@
# Step 2: Final sync — mirror leaves with current Critical-Data state
# Step 3: Reconfigure WebUIs — mirror's auth WebUIs → localhost
# Step 4: Disable sync — CRITICAL_RSYNC_ENABLED=false in master.conf
# Step 5: Write state — INACTIVE locally + pushed to mirror, mirror blocklisted
# Step 6: Local cleanup — remove fallback coverage containers + appdata
# Step 7: Restart own stack — bring up owner's own parked containers
# Step 8: Remote cleanup — remove auth/arr stack + fallback containers from mirror
# Step 9: Restart mirror — bring up mirror's own parked containers
# Step 10: Revocation — Emby admin, SSH keys, Tailscale device
# Step 5: Local cleanup — remove fallback coverage containers + appdata
# Step 6: Restart own stack — bring up owner's own parked containers
# Step 7: Remote cleanup — remove auth/arr stack + fallback containers from mirror
# Step 8: Restart mirror — bring up mirror's own parked containers
# Step 9: Revocation — Emby admin, SSH keys
# Step 10: Write state — INACTIVE locally + pushed to mirror, mirror blocklisted
# Tailscale — grace window then device removal (after state written)
#
# MIRROR PATH (8 steps)
# Step 1: Stop rsync — halt any running sync
@@ -499,6 +500,8 @@ if [[ "$DRY_RUN" == false ]]; then
fi
WEBUI_FAILURES=0
STEP_STOP_OK=true
STEP_SYNC_OK=true
SSH_REVOKE_REMOTE_OK=false
SSH_REVOKE_LOCAL_OK=false
@@ -507,7 +510,7 @@ echo ""
echo "━━━ $ICON_STOP Step 1/10 — Stop Rsync ━━━"
if [[ "$DRY_RUN" == false ]]; then
bash "$SCRIPTS_ROOT/Rsync/rsync_stop.sh" --rsync-only 2>/dev/null || true
bash "$SCRIPTS_ROOT/Rsync/rsync_stop.sh" --rsync-only 2>/dev/null || STEP_STOP_OK=false
log "Rsync stopped ✅"
else
warn "DRY RUN — would stop rsync"
@@ -517,7 +520,7 @@ fi
echo ""
echo "━━━ $ICON_SYNC Step 2/10 — Final Sync ━━━"
do_final_sync
do_final_sync || STEP_SYNC_OK=false
# ── Step 3: Reconfigure mirror WebUIs → localhost ─────────────────────────────────────────────
echo ""
@@ -552,9 +555,72 @@ else
warn "DRY RUN — would set CRITICAL_RSYNC_ENABLED=false"
fi
# ── Step 5: Write state, push to mirror, blocklist ────────────────────────────────────────────
# ── Step 5: Local container cleanup ───────────────────────────────────────────────────────────
echo ""
echo "━━━ $ICON_GEAR Step 5/10 — Write State ━━━"
echo "━━━ $ICON_CONTAINERS Step 5/10 — Local Container Cleanup ━━━"
PARTNER_FOLDER_NAME=$(derive_partner_folder_name "$MIRROR")
cleanup_partner_containers "$PARTNER_FOLDER_NAME"
# ── Step 6: Restart own stack ─────────────────────────────────────────────────────────────────
start_own_stack
# ── Step 7: Remote container cleanup ──────────────────────────────────────────────────────────
echo ""
echo "━━━ $ICON_CONTAINERS Step 7/10 — Remote Container Cleanup ━━━"
if [[ "$MIRROR_REACHABLE" == true ]]; then
# Remove auth/arr stack containers deployed during onboard (by config array)
cleanup_deployed_stack_on_remote "$MIRROR_IP" "$MIRROR_SSH_KEY"
# Remove fallback coverage containers (by *-owner_short naming pattern)
cleanup_owner_containers_on_mirror "$MIRROR_IP"
# Remove mirror's FolderView3 fallback folder (owner's containers were hosted there)
if [[ "${PARTNERSHIP_FOLDERVIEW3:-false}" == true ]]; then
OWNER_FOLDER_ON_MIRROR=$(derive_partner_folder_name "$OWNER")
log "Removing FolderView3 folder '$OWNER_FOLDER_ON_MIRROR' from $MIRROR..."
if [[ "$DRY_RUN" == false ]]; then
timeout "$SSH_TIMEOUT" ssh -i "$MIRROR_SSH_KEY" \
-o ConnectTimeout="$SSH_TIMEOUT" root@"$MIRROR_IP" \
"fv3='/boot/config/plugins/folder.view3/docker.json'
[[ -f \"\$fv3\" ]] && command -v jq >/dev/null 2>&1 && \
jq --arg n '$OWNER_FOLDER_ON_MIRROR' \
'with_entries(select(.value.name != \$n))' \
\"\$fv3\" > \"\${fv3}.tmp\" && \
mv \"\${fv3}.tmp\" \"\$fv3\" && echo removed" 2>/dev/null | \
grep -q removed && \
log "FolderView3 '$OWNER_FOLDER_ON_MIRROR' removed from $MIRROR" || \
warn "FolderView3 folder not found on $MIRROR or jq unavailable — skipping"
else
warn "DRY RUN — would remove FolderView3 folder '$OWNER_FOLDER_ON_MIRROR' from $MIRROR"
fi
fi
else
warn "$MIRROR unreachable — remote container cleanup skipped"
warn "Run 'partnership_offboard.sh' on $MIRROR to clean up manually"
fi
# ── Step 8: Restart mirror's own stack ────────────────────────────────────────────────────────
echo ""
echo "━━━ $ICON_START Step 8/10 — Restart Mirror Stack ━━━"
[[ "$MIRROR_REACHABLE" == true ]] && start_mirror_own_stack "$MIRROR_IP"
# ── Step 9: Revocation (Emby + SSH) ──────────────────────────────────────────────────────────
echo ""
echo "━━━ $ICON_SHIELD Step 9/10 — Revocation ━━━"
# Emby admin — before SSH key revocation while Emby still reachable
[[ "$MIRROR_REACHABLE" == true ]] && revoke_emby_admin "$MIRROR_IP"
# SSH key revocation — mutual, both directions; must run while Tailscale still active
do_ssh_key_revocation "${MIRROR_IP:-}"
# ── Step 10: Write state, push to mirror, blocklist ───────────────────────────────────────────
# State is written after container cleanup and SSH revocation so that:
# • Re-running after a crash between steps 59 restarts from scratch (no early-exit on INACTIVE)
# • --check sees INACTIVE during the Tailscale grace sleep and does not re-trigger offboard
echo ""
echo "━━━ $ICON_GEAR Step 10/10 — Write State ━━━"
NOW=$(date '+%Y-%m-%d %H:%M:%S')
@@ -569,47 +635,7 @@ else
warn "DRY RUN — would write INACTIVE state, blocklist $MIRROR, push to remote"
fi
# ── Step 6: Local container cleanup ───────────────────────────────────────────────────────────
echo ""
echo "━━━ $ICON_CONTAINERS Step 6/10 — Local Container Cleanup ━━━"
PARTNER_FOLDER_NAME=$(derive_partner_folder_name "$MIRROR")
cleanup_partner_containers "$PARTNER_FOLDER_NAME"
# ── Step 7: Restart own stack ─────────────────────────────────────────────────────────────────
start_own_stack
# ── Step 8: Remote container cleanup ──────────────────────────────────────────────────────────
echo ""
echo "━━━ $ICON_CONTAINERS Step 8/10 — Remote Container Cleanup ━━━"
if [[ "$MIRROR_REACHABLE" == true ]]; then
# Remove auth/arr stack containers deployed during onboard (by config array)
cleanup_deployed_stack_on_remote "$MIRROR_IP" "$MIRROR_SSH_KEY"
# Remove fallback coverage containers (by *-owner_short naming pattern)
cleanup_owner_containers_on_mirror "$MIRROR_IP"
else
warn "$MIRROR unreachable — remote container cleanup skipped"
warn "Run 'partnership_manager.sh --offboard' on $MIRROR to clean up manually"
fi
# ── Step 9: Restart mirror's own stack ────────────────────────────────────────────────────────
echo ""
echo "━━━ $ICON_START Step 9/10 — Restart Mirror Stack ━━━"
[[ "$MIRROR_REACHABLE" == true ]] && start_mirror_own_stack "$MIRROR_IP"
# ── Step 10: Revocation ───────────────────────────────────────────────────────────────────────
echo ""
echo "━━━ $ICON_SHIELD Step 10/10 — Revocation ━━━"
# Emby admin — before SSH key revocation while Emby still reachable
[[ "$MIRROR_REACHABLE" == true ]] && revoke_emby_admin "$MIRROR_IP"
# SSH key revocation — mutual, both directions; must run while Tailscale still active
do_ssh_key_revocation "${MIRROR_IP:-}"
# Tailscale removal — after SSH revocation, guard with grace window
# Tailscale removal — after state written so --check does not re-trigger offboard during grace sleep
if [[ "${PARTNERSHIP_REMOVE_TAILSCALE:-true}" == true ]]; then
echo ""
echo "━━━ $ICON_NET Tailscale Separation ━━━"
@@ -658,20 +684,20 @@ _revoke_status() {
fi
}
echo " Step 1 — Stop rsync: "
echo " Step 2 — Final sync: "
echo " Step 1 — Stop rsync: $(_ok "$STEP_STOP_OK")"
echo " Step 2 — Final sync: $(_ok "$STEP_SYNC_OK")"
echo " Step 3 — WebUI failures: $WEBUI_FAILURES"
echo " Step 4 — Disable sync: ✅"
echo " Step 5 — State: INACTIVE ✅"
echo " Step 6 — Local cleanup: ✅"
echo " Step 7 — Own stack: started"
echo " Step 8 — Remote cleanup: $( [[ "$MIRROR_REACHABLE" == true ]] && echo "" || echo "skipped (unreachable)" )"
echo " Step 9 — Mirror stack: $( [[ "$MIRROR_REACHABLE" == true ]] && echo "started" || echo "skipped (unreachable)" )"
echo " Step 10 — Keys revoked: $(_revoke_status)"
echo " Step 4 — Disable sync: ✅"
echo " Step 5 — Local cleanup: ✅"
echo " Step 6 — Own stack: started"
echo " Step 7 — Remote cleanup: $( [[ "$MIRROR_REACHABLE" == true ]] && echo "✅" || echo "skipped (unreachable)" )"
echo " Step 8 — Mirror stack: $( [[ "$MIRROR_REACHABLE" == true ]] && echo "started" || echo "skipped (unreachable)" )"
echo " Step 9 — Keys revoked: $(_revoke_status)"
echo " Step 10 — State: INACTIVE ✅"
echo ""
echo " Blocklist: $MIRROR blocked — re-onboard to permit access again ✅"
[[ "${PARTNERSHIP_FOLDERVIEW3:-false}" == true ]] && \
echo " FolderView3: ${PARTNER_FOLDER_NAME:-} cleaned ✅"
echo " FolderView3: ${PARTNER_FOLDER_NAME:-} (local) + mirror remote cleaned ✅"
[[ "${PARTNERSHIP_REMOVE_TAILSCALE:-true}" == true ]] && \
echo " Tailscale: $MIRROR removed ✅"
echo ""