From dfc24e37c05ba3c5a8f616f7a20d5bf739703fd4 Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Sat, 30 May 2026 18:33:56 -0400 Subject: [PATCH] Add Phase 1 cancel: remove SSH keys from both sides + reset state onboard_cancel.sh: - SSHes HOST2 while key still valid: removes HOST1 pubkey from authorized_keys and clears HOST2_PHASE* from setup.db on the remote side - Deletes local private + public key pair - Clears HOST2_PHASE* from local setup.db - Graceful if HOST2 unreachable (local side still cleaned up) partnership.php: - Cancel button (warn style, slightly dimmed) at phase 0 and phase 1 - vvPtCancel() top-level function, calls onboard_cancel.sh via run.php --- Partnership/onboard_cancel.sh | 141 ++++++++++++++++++++++++++++ Plugin/unraid/pages/partnership.php | 19 ++++ 2 files changed, 160 insertions(+) create mode 100755 Partnership/onboard_cancel.sh diff --git a/Partnership/onboard_cancel.sh b/Partnership/onboard_cancel.sh new file mode 100755 index 0000000..e8d3d7d --- /dev/null +++ b/Partnership/onboard_cancel.sh @@ -0,0 +1,141 @@ +#!/bin/bash +# ============================================================================================== +# ============================= Onboard Cancel ================================================= +# ============================================================================================== +# +# PURPOSE +# ───────────────────────────────────────────────────────────────────────────── +# Undoes Phase 1 of the onboard process: +# 1. Remove HOST1's public key from HOST2's authorized_keys (while key still works) +# 2. Remove phase flags from HOST2's varaverk_setup.db +# 3. Delete local SSH key pair +# 4. Clear phase flags from local varaverk_setup.db +# +# Run on the OWNER. If HOST2 is unreachable the local side is still cleaned up. +# +# ============================================================================================== +# RUNTIME MODES +# ============================================================================================== +# +# Partnership/onboard_cancel.sh +# Cancel Phase 1 — remove keys and reset state +# +# Partnership/onboard_cancel.sh --dry-run +# Preview all steps without making changes +# +# ============================================================================================== + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SCRIPTS_ROOT="$SCRIPT_DIR/.." +SSH_TIMEOUT=15 + +source "$SCRIPTS_ROOT/load_config.sh" +parse_args "$@" + +[[ "$EUID" -ne 0 ]] && { error "Must be run as root"; exit 1; } + +acquire_lock + +detect_hosts + +OWNER_ID="${PARTNERSHIP_OWNER_HOST:-HOST1}" +MIRROR_ID=$( [[ "$OWNER_ID" == "HOST1" ]] && echo "HOST2" || echo "HOST1" ) +MIRROR="${!MIRROR_ID}" +SSH_KEY_PUB="${SSH_KEY}.pub" +STATE_FILE="/boot/config/varaverk_setup.db" + +START=$(date +%s) + +echo "" +echo "━━━ Cancel Phase 1 — $MY_ID → $MIRROR_ID ($MIRROR) — $(date '+%Y-%m-%d %H:%M:%S') ━━━" +echo "" +[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made" +echo "" + +REMOTE_CLEANED=false +LOCAL_KEY_GONE=false +STATE_CLEARED=false + +# ── Step 1: Remove HOST1's public key from HOST2 ────────────────────────────── +echo "━━━ Step 1 — Remove Public Key from $MIRROR_ID ━━━" + +if [[ ! -f "$SSH_KEY_PUB" ]]; then + log "No local public key at $SSH_KEY_PUB — nothing to remove from $MIRROR_ID" + REMOTE_CLEANED=true +else + # Extract the key blob (middle field of pub key) — used as a unique identifier. + # Use | as sed delimiter to avoid clashing with base64 / characters in the blob. + KEY_BLOB=$(awk '{print $2}' "$SSH_KEY_PUB") + MIRROR_IP=$(resolve_tailscale_ip "$MIRROR" 2>/dev/null || true) + + if [[ -z "$MIRROR_IP" ]]; then + warn "Cannot resolve $MIRROR Tailscale IP — skipping remote cleanup" + warn "Remove HOST1's public key from $MIRROR:/root/.ssh/authorized_keys manually" + elif [[ "$DRY_RUN" == true ]]; then + warn "DRY RUN — would remove key blob from $MIRROR:/root/.ssh/authorized_keys" + warn "DRY RUN — would clear ${MIRROR_ID}_PHASE* from $MIRROR:/boot/config/varaverk_setup.db" + REMOTE_CLEANED=true + else + timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \ + -o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$MIRROR_IP" \ + "sed -i \"|${KEY_BLOB}|d\" /root/.ssh/authorized_keys 2>/dev/null + sed -i \"/^${MIRROR_ID}_PHASE/d\" /boot/config/varaverk_setup.db 2>/dev/null + echo ok" 2>/dev/null | grep -q ok && { + log "HOST1 key removed from $MIRROR authorized_keys ✅" + log "Phase flags cleared on $MIRROR ✅" + REMOTE_CLEANED=true + } || warn "Could not SSH to $MIRROR — remove HOST1 key and phase flags there manually" + fi +fi + +# ── Step 2: Delete local SSH key pair ───────────────────────────────────────── +echo "" +echo "━━━ Step 2 — Delete Local SSH Key ━━━" + +if [[ ! -f "$SSH_KEY" && ! -f "$SSH_KEY_PUB" ]]; then + log "Local key already gone" + LOCAL_KEY_GONE=true +elif [[ "$DRY_RUN" == true ]]; then + warn "DRY RUN — would delete: $SSH_KEY" + warn "DRY RUN — would delete: $SSH_KEY_PUB" + LOCAL_KEY_GONE=true +else + rm -f "$SSH_KEY" "$SSH_KEY_PUB" && { + log "Local key pair deleted ✅" + LOCAL_KEY_GONE=true + } || warn "Failed to delete $SSH_KEY — check permissions" +fi + +# ── Step 3: Clear phase flags from local setup.db ───────────────────────────── +echo "" +echo "━━━ Step 3 — Clear Phase State ━━━" + +if [[ ! -f "$STATE_FILE" ]]; then + log "No varaverk_setup.db — nothing to clear" + STATE_CLEARED=true +elif [[ "$DRY_RUN" == true ]]; then + warn "DRY RUN — would remove ${MIRROR_ID}_PHASE* from $STATE_FILE" + STATE_CLEARED=true +else + sed -i "/^${MIRROR_ID}_PHASE/d" "$STATE_FILE" && { + log "Phase flags cleared from local setup.db ✅" + STATE_CLEARED=true + } +fi + +# ── Summary ─────────────────────────────────────────────────────────────────── +END=$(date +%s) +echo "" +echo "━━━━━ CANCEL SUMMARY ━━━━━" +echo " $MIRROR_ID key removed: $( [[ "$REMOTE_CLEANED" == true ]] && echo "✅" || echo "⚠ manual cleanup needed" )" +echo " Local key deleted: $( [[ "$LOCAL_KEY_GONE" == true ]] && echo "✅" || echo "⚠ still present" )" +echo " Phase state cleared: $( [[ "$STATE_CLEARED" == true ]] && echo "✅" || echo "⚠" )" +echo " Duration: $(format_duration $(( END - START )))" +echo "" +if [[ "$REMOTE_CLEANED" == false ]]; then + echo " Manual cleanup on $MIRROR:" + echo " sed -i '/$(awk "{print \$3}" "$SSH_KEY_PUB" 2>/dev/null || echo "HOST1_key_comment")//d' /root/.ssh/authorized_keys" +fi +echo " Run Phase 1 again to restart the onboard process." +echo "━━━━━━━━━━━━━━━━━━━━━━━" +exit 0 diff --git a/Plugin/unraid/pages/partnership.php b/Plugin/unraid/pages/partnership.php index 177463a..302168a 100644 --- a/Plugin/unraid/pages/partnership.php +++ b/Plugin/unraid/pages/partnership.php @@ -96,6 +96,16 @@ function vvPtOnboard(btn) { .finally(() => setTimeout(() => { btn.disabled = false; btn.textContent = '▶ Onboard (Mirror)'; }, 4000)); } +function vvPtCancel(btn, hostId) { + if (!confirm(`Cancel Phase 1 for ${hostId}?\n\nThis will:\n• Remove HOST1's SSH key from ${hostId}'s authorized_keys\n• Delete the local key pair\n• Reset phase state on both hosts\n\nContinue?`)) return; + btn.disabled = true; + btn.textContent = '⟳ Cancelling…'; + _vvPtRun('Partnership/onboard_cancel.sh') + .then(d => { if (!d.ok) alert('Failed: ' + (d.error ?? 'Unknown error')); }) + .catch(e => alert('Error: ' + e)) + .finally(() => setTimeout(() => { btn.disabled = false; btn.textContent = '✕ Cancel'; }, 4000)); +} + function vvPtOffboard(btn) { if (btn.style.opacity === '0.35' || btn.style.cursor === 'default') return; if (!confirm('Run partnership_offboard.sh?\n\nThis will end the partnership, reconfigure WebUIs, and revoke SSH access.\n\nContinue?')) return; @@ -285,6 +295,11 @@ function _renderActions(nodes, cfg) { style="opacity:.5;"> ▶ Full Onboard `; + html += ``; } else if (phase === 1) { // Phase 1 done — waiting for HOST2 to onboard and trigger Phase 2 html += `⏳ Waiting for ${remote.id} to onboard…`; @@ -292,6 +307,10 @@ function _renderActions(nodes, cfg) { title="Manually trigger Phase 2 — deploy containers, arr stack, and establish partnership on ${remote.hostname}. Normally triggered automatically when ${remote.id} completes its onboard."> ▶ Run Phase 2 Manually `; + html += ``; } else { // Phase 2 done — fully onboarded html += `✅ Partnership established`;