diff --git a/Partnership/partnership_manager.sh b/Partnership/partnership_manager.sh index 5c4938d..7c390c9 100644 --- a/Partnership/partnership_manager.sh +++ b/Partnership/partnership_manager.sh @@ -200,6 +200,8 @@ REASON="manual" UNBLOCK_HOST="" FILTERED_ARGS=() +LOCAL_ONLY=false # --onboard --local-only: skips remote steps, runs HOST1-local setup only + for arg in "$@"; do case "$arg" in --onboard) MODE="onboard" ;; @@ -208,6 +210,7 @@ for arg in "$@"; do --check) MODE="check" ;; --status) MODE="status" ;; --unblock) MODE="unblock" ;; + --local-only) LOCAL_ONLY=true ;; --confirm=*) TRANSFER_CONFIRM_INPUT="${arg#--confirm=}" ;; --remote-seen) REMOTE_SEEN=true ;; --remote-unseen) REMOTE_UNSEEN=true ;; @@ -1408,6 +1411,51 @@ if [[ "$MODE" == "onboard" ]]; then echo "" echo "━━━ $ICON_FALLBACK Onboard — $(date '+%Y-%m-%d %H:%M:%S') ━━━" + # ── LOCAL-ONLY PATH ────────────────────────────────────────────────────────── + # HOST1-local setup steps that don't need HOST2 present. Called from + # partnership_onboard.sh --phase1-only so HOST1 can complete its own side + # (FolderView3, setup.db flag) while waiting for HOST2 to install and onboard. + if [[ "$LOCAL_ONLY" == true ]]; then + log "Mode: local-only — skipping remote pre-flight and WebUI steps" + log "Owner: $OWNER_ID ($OWNER) · Mirror: $MIRROR_ID ($MIRROR)" + echo "" + + if [[ "${PARTNERSHIP_FOLDERVIEW3:-false}" == true ]]; then + echo "FolderView3 Integration" + PARTNER_FOLDER_NAME=$(derive_partner_folder_name "$MIRROR") + declare -a PARTNER_CONTAINERS=() + gather_partner_fallback_containers PARTNER_CONTAINERS + if [[ ${#PARTNER_CONTAINERS[@]} -gt 0 ]]; then + folderview3_create_partner_folder "$PARTNER_FOLDER_NAME" "${PARTNER_CONTAINERS[@]}" + else + log "No FALLBACK_${MY_ID}_COVERS_${MIRROR_ID}_TIER* containers — skipping folder" + fi + else + log "FolderView3 not configured — skipping" + fi + + local_state_file="/boot/config/varaverk_setup.db" + if [[ "$DRY_RUN" == false ]]; then + local flag_key="${MY_ID}_LOCAL_DONE" + if grep -q "^${flag_key}=" "$local_state_file" 2>/dev/null; then + sed -i "s|^${flag_key}=.*|${flag_key}=true|" "$local_state_file" + else + echo "${flag_key}=true" >> "$local_state_file" + fi + command -v php &>/dev/null && \ + php -r "require_once '/usr/local/emhttp/plugins/varaverk/include/config.php'; vv_push_setup_state();" 2>/dev/null || true + log "${MY_ID}_LOCAL_DONE=true written to setup.db ✅" + else + warn "DRY RUN — would write ${MY_ID}_LOCAL_DONE=true" + fi + + echo "" + echo "HOST1 local setup complete. Waiting for $MIRROR_ID ($MIRROR) to onboard." + exit 0 + fi + + # ── FULL ONBOARD PATH (requires HOST2) ──────────────────────────────────── + # Check already onboarded if [[ -f "$LOCAL_STATE_FILE" ]]; then CURRENT_STATE=$(read_state_file "$LOCAL_STATE_FILE" "state") diff --git a/Partnership/partnership_onboard.sh b/Partnership/partnership_onboard.sh index 7b11250..9d97a86 100755 --- a/Partnership/partnership_onboard.sh +++ b/Partnership/partnership_onboard.sh @@ -606,16 +606,23 @@ exit(\$failed > 0 ? 1 : 0); fi fi + # HOST1 local setup — runs immediately without needing HOST2 + echo "" + echo "━━━ Phase 1 — HOST1 Local Setup ━━━" + bash "$SCRIPT_DIR/partnership_manager.sh" --onboard --local-only "${EXTRA_FLAGS[@]}" || \ + warn "Local setup had issues — FolderView3 may need manual setup" + [[ "$DRY_RUN" == false ]] && write_onboard_phase "$MIRROR_ID" 1 END=$(date +%s) echo "" echo "━━━━━ $ICON_SUMMARY PHASE 1 COMPLETE ━━━━━" - echo " SSH keys: $( [[ "$STEP_SSH_OK" == true ]] && echo "ready ✅" || echo "skipped" )" - echo " Conf push: $( [[ "$CONF_PUSH_OK" == true ]] && echo "done ✅" || echo "⚠ manual needed" )" - echo " Duration: $(format_duration $(( END - START )))" + echo " SSH keys: $( [[ "$STEP_SSH_OK" == true ]] && echo "ready ✅" || echo "skipped" )" + echo " Conf push: $( [[ "$CONF_PUSH_OK" == true ]] && echo "done ✅" || echo "⚠ manual needed" )" + echo " HOST1 setup: done ✅" + echo " Duration: $(format_duration $(( END - START )))" echo "" - echo " HOST2 ($MIRROR) can now install the Varaverk plugin." + echo " HOST1 is fully set up. HOST2 ($MIRROR) can now install the Varaverk plugin." echo " The wizard will detect the pushed conf and take the correct path." echo " When HOST2 completes its onboard, it will automatically trigger Phase 2 here." echo "━━━━━━━━━━━━━━━━━━━━━━━" diff --git a/Plugin/unraid/include/partnership.php b/Plugin/unraid/include/partnership.php index 55a14fb..08cb71e 100644 --- a/Plugin/unraid/include/partnership.php +++ b/Plugin/unraid/include/partnership.php @@ -151,10 +151,13 @@ function vv_pt_nodes(): array { ? vv_pt_local_system() : ($ts['online'] && $ts['ip'] && $mySshKey ? vv_pt_remote_system($ts['ip'], $mySshKey) : []); - // Onboard phase from setup.db — 0=not started, 1=SSH+conf done, 2=fully onboarded - $nodeIdUpper = strtoupper($slot); - $onboardPhase = ($setupDb[$nodeIdUpper . '_PHASE2_DONE'] ?? '') === 'true' ? 2 - : (($setupDb[$nodeIdUpper . '_PHASE1_DONE'] ?? '') === 'true' ? 1 : 0); + // Onboard phase from setup.db — null=self, 0=not started, 1=SSH+conf done, 2=fully onboarded + $nodeIdUpper = strtoupper($slot); + $onboardPhase = $isMe ? null + : (($setupDb[$nodeIdUpper . '_PHASE2_DONE'] ?? '') === 'true' ? 2 + : (($setupDb[$nodeIdUpper . '_PHASE1_DONE'] ?? '') === 'true' ? 1 : 0)); + // For self: local setup complete flag (set by partnership_manager --onboard --local-only) + $localDone = $isMe && ($setupDb[$nodeIdUpper . '_LOCAL_DONE'] ?? '') === 'true'; $nodes[] = [ 'slot' => $slot, @@ -168,7 +171,8 @@ function vv_pt_nodes(): array { 'fallback' => $fbState, 'partnership' => $ptDb, 'system' => $system, - 'onboard_phase' => $isMe ? null : $onboardPhase, + 'onboard_phase' => $onboardPhase, + 'local_done' => $localDone, ]; } return $nodes; diff --git a/Plugin/unraid/pages/partnership.php b/Plugin/unraid/pages/partnership.php index 943559b..ab72d92 100644 --- a/Plugin/unraid/pages/partnership.php +++ b/Plugin/unraid/pages/partnership.php @@ -106,6 +106,16 @@ function vvPtOnboard(btn) { .finally(() => setTimeout(() => { btn.disabled = false; btn.textContent = '▶ Onboard (Mirror)'; }, 4000)); } +function vvPtLocalSetup(btn) { + if (!confirm('Complete HOST1 local setup?\n\nRuns FolderView3 integration and marks HOST1 as locally ready.\nDoes not require HOST2 to be online.')) return; + btn.disabled = true; + btn.textContent = '⟳ Running…'; + _vvPtRun('Partnership/partnership_manager.sh', '--onboard --local-only') + .then(d => { if (!d.ok) alert('Failed: ' + (d.error ?? 'Unknown error')); }) + .catch(e => alert('Error: ' + e)) + .finally(() => setTimeout(() => { btn.disabled = false; btn.textContent = '▶ Complete HOST1 Setup'; }, 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; @@ -227,12 +237,15 @@ function _nodeCard(node) { const [ptCol, ptLbl] = ptStates[ptState] || ['#444', ptState || '—']; const ptUpdated = pt.updated ? _relTime(parseInt(pt.updated)) : null; - // Onboard phase (null = self, 0 = not started, 1 = SSH done, 2 = fully onboarded) + // Onboard phase badge (remote nodes) or local-done badge (self) const phase = node.onboard_phase; - const phaseHtml = phase === null ? '' : - phase === 2 ? '' : - phase === 1 ? `
⏳ SSH ready · awaiting HOST2 onboard
` : - `
○ Not yet provisioned
`; + const phaseHtml = node.is_me + ? (node.local_done + ? `
✓ Local setup complete · waiting for partner
` + : '') + : (phase === 2 ? '' : + phase === 1 ? `
⏳ SSH ready · awaiting onboard
` : + `
○ Not yet provisioned
`); let body = ''; @@ -286,6 +299,24 @@ function _renderActions(nodes, cfg) { let html = ''; + // ── HOST1 local setup button (owner only, before local_done) ────────────── + const selfNode = nodes.find(n => n.is_me); + if (isOwner && selfNode && !selfNode.local_done && remotes.some(r => (r.onboard_phase ?? 0) >= 1)) { + html += `
+
${selfNode.id} — ${selfNode.hostname} (this server)
+
+ +
+
+ Configures HOST1's local side independently — FolderView3 folder, state flags. + Does not require HOST2 to be online. +
+
`; + } + // ── Per-partner onboard phase actions (owner only) ───────────────────────── if (isOwner && hasPartner) { for (const remote of remotes) {