Two-phase onboard + partner card enhancements
partnership_onboard.sh: - --phase1-only (OWNER): SSH key exchange + conf push only; safe to run before HOST2 has Varaverk installed; writes HOST2_PHASE1_DONE to setup.db - --phase2-only (OWNER): skips SSH, runs Steps 2-9 (containers, arr stack, onboard, arr sync, conf push); writes HOST2_PHASE2_DONE to setup.db - Mirror path: after SSH setup, SSHes OWNER and fires --phase2-only in background (nohup); reads OWNER's SCRIPTS_DIR from varaverk.cfg first; falls back to manual instruction if SSH fails partnership.php (include): vv_pt_nodes() reads setup.db and exposes onboard_phase (0/1/2) per remote node partnership.php (page): phase-aware Actions section per partner host; node cards show phase badge (not provisioned / awaiting onboard / done); Phase 1 and Phase 2 buttons call run.php with extra_args monitor.php: partner card logo (top-right), CPU thread count, RAM total GB, unRAID version row in remote stats grid
This commit is contained in:
@@ -124,6 +124,15 @@
|
||||
# Partnership/partnership_onboard.sh --skip-arr-sync
|
||||
# Skip arr library bootstrap (Step 8)
|
||||
#
|
||||
# Partnership/partnership_onboard.sh --phase1-only
|
||||
# OWNER only: SSH key exchange + conf push. Safe to run before HOST2 has Varaverk.
|
||||
# Writes HOST2_PHASE1_DONE=true to varaverk_setup.db.
|
||||
#
|
||||
# Partnership/partnership_onboard.sh --phase2-only
|
||||
# OWNER only: container deploy + arr + onboard (skips SSH). Triggered automatically
|
||||
# by HOST2 after it completes its Mirror-path onboard. Can also be run manually.
|
||||
# Writes HOST2_PHASE2_DONE=true to varaverk_setup.db.
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -138,6 +147,8 @@ SKIP_SSH=false
|
||||
SKIP_AUTH_STACK=false
|
||||
SKIP_ARR_STACK=false
|
||||
SKIP_ARR_SYNC=false
|
||||
PHASE1_ONLY=false # OWNER: SSH + conf push only (HOST2 not yet installed)
|
||||
PHASE2_ONLY=false # OWNER: containers/arr/onboard only (triggered by HOST2 after it onboards)
|
||||
FILTERED_ARGS=()
|
||||
|
||||
for arg in "$@"; do
|
||||
@@ -146,6 +157,8 @@ for arg in "$@"; do
|
||||
--skip-auth-stack) SKIP_AUTH_STACK=true ;;
|
||||
--skip-arr-stack) SKIP_ARR_STACK=true ;;
|
||||
--skip-arr-sync) SKIP_ARR_SYNC=true ;;
|
||||
--phase1-only) PHASE1_ONLY=true ;;
|
||||
--phase2-only) PHASE2_ONLY=true; SKIP_SSH=true ;;
|
||||
*) FILTERED_ARGS+=("$arg") ;;
|
||||
esac
|
||||
done
|
||||
@@ -187,6 +200,21 @@ EXTRA_FLAGS=()
|
||||
|
||||
START=$(date +%s)
|
||||
|
||||
# ── Helper: write phase completion flag to setup.db + push to remotes ─────────────────────────
|
||||
write_onboard_phase() {
|
||||
local target_id="$1" phase="$2"
|
||||
local key="${target_id}_PHASE${phase}_DONE"
|
||||
local state_file="/boot/config/varaverk_setup.db"
|
||||
[[ "$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
|
||||
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
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "━━━ $ICON_FALLBACK Partnership Onboard — $MY_ID ($LOCAL_SERVER_NAME) — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
|
||||
echo ""
|
||||
@@ -443,10 +471,9 @@ deploy_xml_stack() {
|
||||
# ── MIRROR PATH ───────────────────────────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
if [[ "$AM_MIRROR" == true ]]; then
|
||||
echo "━━━ Step 1/1 — SSH Key Setup (Mirror) ━━━"
|
||||
echo "━━━ Step 1/2 — SSH Key Setup (Mirror) ━━━"
|
||||
echo ""
|
||||
echo " Mirror only needs SSH keys ready."
|
||||
echo " Owner ($OWNER) completes the rest remotely."
|
||||
echo " Mirror sets up SSH keys, then notifies Owner to run Phase 2."
|
||||
echo ""
|
||||
|
||||
if [[ "$SKIP_SSH" == true ]]; then
|
||||
@@ -458,10 +485,46 @@ if [[ "$AM_MIRROR" == true ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━ Step 2/2 — Notify Owner to Run Phase 2 ━━━"
|
||||
echo ""
|
||||
|
||||
OWNER_IP=$(resolve_tailscale_ip "$OWNER" 2>/dev/null || true)
|
||||
PHASE2_TRIGGERED=false
|
||||
|
||||
if [[ -n "$OWNER_IP" ]]; then
|
||||
# Read OWNER's SCRIPTS_DIR from their varaverk.cfg — don't assume same path as mirror
|
||||
OWNER_SCRIPTS_DIR=$(timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
|
||||
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$OWNER_IP" \
|
||||
'grep SCRIPTS_DIR /boot/config/plugins/varaverk/varaverk.cfg 2>/dev/null | cut -d= -f2 | tr -d "\"'"'"'" 2>/dev/null' 2>/dev/null | tr -d '[:space:]')
|
||||
OWNER_SCRIPTS_DIR="${OWNER_SCRIPTS_DIR:-/mnt/user/appdata/Varaverk}"
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would SSH to $OWNER ($OWNER_IP) and trigger Phase 2"
|
||||
PHASE2_TRIGGERED=true
|
||||
elif timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
|
||||
-o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes root@"$OWNER_IP" \
|
||||
"nohup bash '${OWNER_SCRIPTS_DIR}/Partnership/partnership_onboard.sh' --phase2-only > /tmp/vv_phase2_onboard.log 2>&1 & echo triggered" \
|
||||
2>/dev/null | grep -q triggered; then
|
||||
log "Phase 2 triggered on $OWNER ✅"
|
||||
log "Watch progress on $OWNER: tail -f /tmp/vv_phase2_onboard.log"
|
||||
PHASE2_TRIGGERED=true
|
||||
else
|
||||
warn "Could not auto-trigger Phase 2 on $OWNER"
|
||||
fi
|
||||
else
|
||||
warn "Cannot resolve $OWNER Tailscale IP"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY MIRROR SETUP COMPLETE ━━━━━"
|
||||
echo " SSH key: ready"
|
||||
echo " Next: Run Partnership/partnership_onboard.sh on $OWNER to complete setup"
|
||||
echo " SSH key: ready"
|
||||
echo " Phase 2 on $OWNER: $( [[ "$PHASE2_TRIGGERED" == true ]] && echo "triggered ✅" || echo "needs manual trigger ⚠" )"
|
||||
if [[ "$PHASE2_TRIGGERED" == false ]]; then
|
||||
echo ""
|
||||
echo " Run manually on $OWNER:"
|
||||
echo " bash Partnership/partnership_onboard.sh --phase2-only"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
exit 0
|
||||
fi
|
||||
@@ -473,6 +536,8 @@ fi
|
||||
MIRROR_IP=$(resolve_tailscale_ip "$MIRROR")
|
||||
[[ -z "$MIRROR_IP" ]] && { error "Cannot resolve $MIRROR Tailscale IP — is Tailscale running?"; exit 1; }
|
||||
log "Mirror: $MIRROR ($MIRROR_IP)"
|
||||
[[ "$PHASE1_ONLY" == true ]] && log "Mode: Phase 1 only (SSH + conf push)"
|
||||
[[ "$PHASE2_ONLY" == true ]] && log "Mode: Phase 2 only (containers + arr + onboard)"
|
||||
echo ""
|
||||
|
||||
STEP_SSH_OK=false
|
||||
@@ -490,10 +555,11 @@ ARR_SYNC_OK=false
|
||||
MASTER_PUSH_OK=false
|
||||
|
||||
# ── Step 1: SSH ───────────────────────────────────────────────────────────────────────────────
|
||||
echo "━━━ Step 1/8 — SSH Key Setup ━━━"
|
||||
# Skipped when --phase2-only (SSH was already done in Phase 1).
|
||||
echo "━━━ Step 1 — SSH Key Setup ━━━"
|
||||
|
||||
if [[ "$SKIP_SSH" == true ]]; then
|
||||
warn "Skipping (--skip-ssh)"
|
||||
warn "Skipping (--skip-ssh / --phase2-only)"
|
||||
STEP_SSH_OK=true
|
||||
elif bash "$SCRIPT_DIR/ssh_setup.sh" "${EXTRA_FLAGS[@]}"; then
|
||||
log "SSH keys ready ✅"
|
||||
@@ -504,9 +570,61 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Phase 1 exit point ────────────────────────────────────────────────────────────────────────
|
||||
# --phase1-only: SSH + conf push is all HOST1 needs to do before HOST2 installs Varaverk.
|
||||
# HOST2's wizard will detect the pushed master.conf + state file and take the correct path.
|
||||
if [[ "$PHASE1_ONLY" == true ]]; then
|
||||
echo ""
|
||||
echo "━━━ Phase 1 — Conf Push ━━━"
|
||||
|
||||
CONF_PUSH_OK=false
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would push master.conf + state file to $MIRROR"
|
||||
CONF_PUSH_OK=true
|
||||
elif ! command -v php &>/dev/null; then
|
||||
warn "php not available — push master.conf manually via Scheduler → master.conf → Save Conf"
|
||||
else
|
||||
push_output=$(php -r "
|
||||
require_once '/usr/local/emhttp/plugins/varaverk/include/config.php';
|
||||
\$results = vv_push_master_conf();
|
||||
vv_push_setup_state();
|
||||
if (empty(\$results)) { echo 'no remote hosts'; exit(0); }
|
||||
\$failed = 0;
|
||||
foreach (\$results as \$r) {
|
||||
echo \$r['host'] . ': ' . (\$r['ok'] ? 'pushed' : 'FAILED — ' . \$r['error']) . PHP_EOL;
|
||||
if (!\$r['ok']) \$failed++;
|
||||
}
|
||||
exit(\$failed > 0 ? 1 : 0);
|
||||
" 2>/dev/null)
|
||||
push_rc=$?
|
||||
echo "$push_output"
|
||||
if [[ $push_rc -eq 0 ]]; then
|
||||
log "Conf push complete ✅"
|
||||
CONF_PUSH_OK=true
|
||||
else
|
||||
warn "Conf push had failures — retry via Scheduler → master.conf → Save Conf"
|
||||
fi
|
||||
fi
|
||||
|
||||
[[ "$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 ""
|
||||
echo " 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 "━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Step 2: Plugins ───────────────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "━━━ Step 2/8 — Plugin Install on Mirror ━━━"
|
||||
echo "━━━ Step 2 — Plugin Install on Mirror ━━━"
|
||||
|
||||
if [[ "${PARTNERSHIP_FOLDERVIEW3:-false}" == true ]] && [[ -n "${PARTNERSHIP_FOLDERVIEW3_URL:-}" ]]; then
|
||||
FV3_PRESENT=$(timeout "$SSH_TIMEOUT" ssh -i "$MIRROR_SSH_KEY" \
|
||||
@@ -533,7 +651,7 @@ fi
|
||||
|
||||
# ── Step 3: Stop mirror's existing auth stack ─────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "━━━ Step 3/8 — Stop Mirror Auth Stack ━━━"
|
||||
echo "━━━ Step 3 — Stop Mirror Auth Stack ━━━"
|
||||
|
||||
if [[ "$SKIP_AUTH_STACK" == true ]]; then
|
||||
warn "Skipping (--skip-auth-stack)"
|
||||
@@ -543,7 +661,7 @@ fi
|
||||
|
||||
# ── Step 4: Deploy auth stack on mirror ───────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "━━━ Step 4/8 — Deploy Auth Stack on Mirror ━━━"
|
||||
echo "━━━ Step 4 — Deploy Auth Stack on Mirror ━━━"
|
||||
|
||||
if [[ "$SKIP_AUTH_STACK" == true ]]; then
|
||||
warn "Skipping (--skip-auth-stack)"
|
||||
@@ -561,7 +679,7 @@ fi
|
||||
|
||||
# ── Step 5: Stop mirror's existing arr stack ──────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "━━━ Step 5/8 — Stop Mirror Arr Stack ━━━"
|
||||
echo "━━━ Step 5 — Stop Mirror Arr Stack ━━━"
|
||||
|
||||
if [[ "$SKIP_ARR_STACK" == true ]]; then
|
||||
warn "Skipping (--skip-arr-stack)"
|
||||
@@ -574,7 +692,7 @@ fi
|
||||
|
||||
# ── Step 6: Deploy arr stack on mirror ───────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "━━━ Step 6/8 — Deploy Arr Stack on Mirror ━━━"
|
||||
echo "━━━ Step 6 — Deploy Arr Stack on Mirror ━━━"
|
||||
|
||||
if [[ "$SKIP_ARR_STACK" == true ]]; then
|
||||
warn "Skipping (--skip-arr-stack)"
|
||||
@@ -588,7 +706,7 @@ fi
|
||||
|
||||
# ── Step 7: Partnership onboard ───────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "━━━ Step 7/8 — Partnership Onboard ━━━"
|
||||
echo "━━━ Step 7 — Partnership Onboard ━━━"
|
||||
|
||||
if bash "$SCRIPTS_ROOT/Partnership/partnership_manager.sh" --onboard "${EXTRA_FLAGS[@]}"; then
|
||||
echo "Partnership onboard complete ✅"
|
||||
@@ -600,7 +718,7 @@ fi
|
||||
|
||||
# ── Step 8: Arr library bootstrap ─────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "━━━ Step 8/8 — Arr Library Bootstrap ━━━"
|
||||
echo "━━━ Step 8 — Arr Library Bootstrap ━━━"
|
||||
|
||||
if [[ "$ONBOARD_OK" == false ]]; then
|
||||
warn "Skipping — onboard did not complete"
|
||||
@@ -620,7 +738,7 @@ fi
|
||||
# SSH is now established and all partners have the plugin installed.
|
||||
# Push the authoritative master.conf so every listed host is in sync immediately.
|
||||
echo ""
|
||||
echo "━━━ $ICON_GEAR Step 9/9 — master.conf Push ━━━"
|
||||
echo "━━━ $ICON_GEAR Step 9 — master.conf Push ━━━"
|
||||
|
||||
if [[ "$ONBOARD_OK" == false ]]; then
|
||||
warn "Skipping — onboard did not complete"
|
||||
@@ -652,12 +770,16 @@ exit(\$failed > 0 ? 1 : 0);
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Write Phase 2 completion state ────────────────────────────────────────────────────────────
|
||||
[[ "$ONBOARD_OK" == true && "$DRY_RUN" == false ]] && write_onboard_phase "$MIRROR_ID" 2
|
||||
|
||||
# ── Summary ───────────────────────────────────────────────────────────────────────────────────
|
||||
END=$(date +%s)
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY ONBOARD SUMMARY ━━━━━"
|
||||
echo " Owner: $MY_ID ($LOCAL_SERVER_NAME)"
|
||||
echo " Mirror: $MIRROR ($MIRROR_IP)"
|
||||
[[ "$PHASE2_ONLY" == true ]] && echo " Mode: Phase 2 (triggered by HOST2 notification)"
|
||||
echo " Duration: $(format_duration $(( END - START )))"
|
||||
echo ""
|
||||
_ok() { [[ "$1" == true ]] && echo "✅" || echo "❌"; }
|
||||
|
||||
@@ -108,6 +108,7 @@ function vv_pt_nodes(): array {
|
||||
$vars = vv_conf_vars();
|
||||
$tsPeers = vv_pt_ts_peers();
|
||||
$ownerSlot = strtolower($vars['PARTNERSHIP_OWNER_HOST'] ?? '');
|
||||
$setupDb = vv_setup_state_read();
|
||||
|
||||
// SSH key for this host
|
||||
$myId = strtoupper($currentHost);
|
||||
@@ -150,18 +151,24 @@ 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);
|
||||
|
||||
$nodes[] = [
|
||||
'slot' => $slot,
|
||||
'id' => strtoupper($slot),
|
||||
'hostname' => $hostname,
|
||||
'is_me' => $isMe,
|
||||
'is_owner' => $isOwner,
|
||||
'ts_online' => $ts['online'],
|
||||
'ts_active' => $ts['active'],
|
||||
'ts_ip' => $ts['ip'],
|
||||
'fallback' => $fbState,
|
||||
'partnership' => $ptDb,
|
||||
'system' => $system,
|
||||
'slot' => $slot,
|
||||
'id' => $nodeIdUpper,
|
||||
'hostname' => $hostname,
|
||||
'is_me' => $isMe,
|
||||
'is_owner' => $isOwner,
|
||||
'ts_online' => $ts['online'],
|
||||
'ts_active' => $ts['active'],
|
||||
'ts_ip' => $ts['ip'],
|
||||
'fallback' => $fbState,
|
||||
'partnership' => $ptDb,
|
||||
'system' => $system,
|
||||
'onboard_phase' => $isMe ? null : $onboardPhase,
|
||||
];
|
||||
}
|
||||
return $nodes;
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
<span class="vv-ico"><svg width="14" height="10" viewBox="0 0 14 10" fill="none" stroke="#aaa" stroke-width="1.1" stroke-linecap="round"><rect x="0.5" y="1.5" width="4" height="7" rx="0.8"/><rect x="9.5" y="1.5" width="4" height="7" rx="0.8"/><line x1="4.5" y1="5" x2="9.5" y2="5"/><circle cx="7" cy="5" r="1" fill="#888" stroke="none"/></svg></span>
|
||||
Partner
|
||||
</span>
|
||||
<img src="/plugins/varaverk/icons/varaverk.png" style="width:16px;height:16px;object-fit:contain;opacity:0.2;flex-shrink:0;" title="Varaverk">
|
||||
</h3>
|
||||
<div id="vv-partner-body">Loading...</div>
|
||||
</div>
|
||||
@@ -639,8 +640,8 @@ function vvPollMonitor() {
|
||||
const ramAvail = rs.mem_used_pct > 0;
|
||||
const cpuHue = cpuAvail ? Math.round(120 * (1 - rs.cpu_load / 100)) : 0;
|
||||
const memHue = ramAvail ? Math.round(120 * (1 - rs.mem_used_pct / 100)) : 0;
|
||||
const cpuStr = cpuAvail ? `<span style="color:hsl(${cpuHue},70%,45%);font-weight:600;">${rs.cpu_load}%</span>` : `<span style="color:#333;">—</span>`;
|
||||
const ramStr = ramAvail ? `<span style="color:hsl(${memHue},70%,45%);font-weight:600;">${rs.mem_used_pct}%</span>` : `<span style="color:#333;">—</span>`;
|
||||
const cpuStr = cpuAvail ? `<span style="color:hsl(${cpuHue},70%,45%);font-weight:600;">${rs.cpu_load}%${rs.cpu_threads ? `<span style="color:#333;font-weight:400;"> · ${rs.cpu_threads}t</span>` : ''}</span>` : `<span style="color:#333;">—</span>`;
|
||||
const ramStr = ramAvail ? `<span style="color:hsl(${memHue},70%,45%);font-weight:600;">${rs.mem_used_pct}%${rs.mem_total_gb ? `<span style="color:#333;font-weight:400;"> · ${rs.mem_total_gb}G</span>` : ''}</span>` : `<span style="color:#333;">—</span>`;
|
||||
const arrColor = rs.array_state === 'Started' || rs.array_state === 'STARTED' ? '#4caf50' : '#f44336';
|
||||
const uptimeStr = rs.uptime && rs.uptime !== '—' ? rs.uptime : '—';
|
||||
|
||||
@@ -654,6 +655,7 @@ function vvPollMonitor() {
|
||||
<span style="color:#555;">Script sync ops are gated until versions match</span>
|
||||
</div>` : '';
|
||||
|
||||
const verRow = remoteVer ? `<span style="color:#444;">unRAID</span><span style="color:#3a3a3a;grid-column:span 3;">${remoteVer}</span>` : '';
|
||||
statsHtml = `<div style="display:grid;grid-template-columns:auto 1fr auto 1fr;gap:2px 8px;font-size:10px;margin-top:5px;margin-bottom:2px;">
|
||||
<span style="color:#444;">CPU</span>${cpuStr}
|
||||
<span style="color:#444;">RAM</span>${ramStr}
|
||||
@@ -661,6 +663,7 @@ function vvPollMonitor() {
|
||||
<span style="color:${arrColor};font-weight:600;">${rs.array_state}</span>
|
||||
<span style="color:#444;">Uptime</span>
|
||||
<span style="color:#555;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${uptimeStr}</span>
|
||||
${verRow}
|
||||
</div>${verWarn}`;
|
||||
} else if (rs && rs.no_api_key) {
|
||||
statsHtml = `<div style="font-size:10px;color:#444;margin-top:4px;">API key not configured — complete Onboard to enable</div>`;
|
||||
|
||||
@@ -155,8 +155,17 @@ 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)
|
||||
const phase = node.onboard_phase;
|
||||
const phaseHtml = phase === null ? '' :
|
||||
phase === 2 ? '' :
|
||||
phase === 1 ? `<div style="font-size:10px;color:#ff9800;margin-bottom:6px;padding:2px 6px;background:#1a1200;border:1px solid #3a2800;border-radius:3px;">⏳ SSH ready · awaiting HOST2 onboard</div>` :
|
||||
`<div style="font-size:10px;color:#555;margin-bottom:6px;padding:2px 6px;background:#111;border:1px solid #222;border-radius:3px;">○ Not yet provisioned</div>`;
|
||||
|
||||
let body = '';
|
||||
|
||||
body += phaseHtml;
|
||||
|
||||
// Network
|
||||
body += `<div style="display:flex;align-items:center;gap:6px;margin-bottom:8px;">
|
||||
<span class="vv-pt-dot" style="background:${dotCol}"></span>
|
||||
@@ -199,27 +208,82 @@ function _nodeCard(node) {
|
||||
// ── Actions ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function _renderActions(nodes, cfg) {
|
||||
// Onboard: only show when partnership is disabled (not yet set up)
|
||||
// Offboard: only when enabled
|
||||
// Transfer: only when enabled + we are owner
|
||||
const isOwner = nodes.some(n => n.is_me && n.is_owner);
|
||||
const remotes = nodes.filter(n => !n.is_me);
|
||||
const remoteOpts = remotes.map(n =>
|
||||
`<option value="${n.slot}">${n.id} — ${n.hostname}</option>`).join('');
|
||||
|
||||
const isOwner = nodes.some(n => n.is_me && n.is_owner);
|
||||
const remotes = nodes.filter(n => !n.is_me);
|
||||
const hasPartner = remotes.length > 0 && remotes.some(n => n.hostname);
|
||||
|
||||
let html = '<div class="vv-pt-actions">';
|
||||
let html = '';
|
||||
|
||||
// Onboard — disabled until a partner hostname is configured in master.conf
|
||||
html += `<button class="vv-pt-action-btn run" onclick="vvPtOnboard(this)"
|
||||
${!hasPartner ? 'disabled title="Add partner hostname to master.conf first (Scheduler → master.conf)"' :
|
||||
'title="Run partnership_onboard.sh — auto-detects role (run on mirror first, then owner)"'}
|
||||
${!hasPartner ? 'style="opacity:.35;cursor:default;"' : ''}>
|
||||
▶ Onboard
|
||||
</button>`;
|
||||
// ── Per-partner onboard phase actions (owner only) ─────────────────────────
|
||||
if (isOwner && hasPartner) {
|
||||
for (const remote of remotes) {
|
||||
const phase = remote.onboard_phase ?? 0;
|
||||
html += `<div style="margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #1e1e1e;">`;
|
||||
html += `<div style="font-size:11px;color:#555;margin-bottom:6px;">${remote.id} — ${remote.hostname}</div>`;
|
||||
html += '<div class="vv-pt-actions">';
|
||||
|
||||
if (phase === 0) {
|
||||
// Not yet provisioned — offer Phase 1 (safe before HOST2 has Varaverk)
|
||||
html += `<button class="vv-pt-action-btn run" onclick="vvPtPhase1(this, '${remote.id}')"
|
||||
title="SSH key exchange + push master.conf to ${remote.hostname}. Safe to run before ${remote.id} has Varaverk installed.">
|
||||
▶ Phase 1: SSH + Conf Push
|
||||
</button>`;
|
||||
html += `<button class="vv-pt-action-btn info" onclick="vvPtOnboard(this)"
|
||||
title="Run full onboard (Phase 1 + 2 together) — requires ${remote.id} to already have Varaverk installed"
|
||||
style="opacity:.5;">
|
||||
▶ Full Onboard
|
||||
</button>`;
|
||||
} else if (phase === 1) {
|
||||
// Phase 1 done — waiting for HOST2 to onboard and trigger Phase 2
|
||||
html += `<span style="font-size:11px;color:#ff9800;align-self:center;">⏳ Waiting for ${remote.id} to onboard…</span>`;
|
||||
html += `<button class="vv-pt-action-btn info" onclick="vvPtPhase2(this, '${remote.id}')"
|
||||
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
|
||||
</button>`;
|
||||
} else {
|
||||
// Phase 2 done — fully onboarded
|
||||
html += `<span style="font-size:11px;color:#4caf50;align-self:center;">✅ Partnership established</span>`;
|
||||
html += `<button class="vv-pt-action-btn info" onclick="vvPtPhase2(this, '${remote.id}')"
|
||||
title="Re-run Phase 2 — redeploy containers and re-establish partnership on ${remote.hostname}"
|
||||
style="opacity:.5;">
|
||||
↻ Re-run Phase 2
|
||||
</button>`;
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
if (phase === 0) {
|
||||
html += `<div style="font-size:10px;color:#444;margin-top:6px;">
|
||||
Phase 1 pushes SSH keys and master.conf to ${remote.hostname} before it has Varaverk installed.
|
||||
${remote.id} can then install the plugin — the wizard will detect the conf and complete setup automatically.
|
||||
</div>`;
|
||||
} else if (phase === 1) {
|
||||
html += `<div style="font-size:10px;color:#444;margin-top:6px;">
|
||||
When ${remote.id} completes its onboard (Mirror path), it will SSH here and trigger Phase 2 automatically.
|
||||
Use "Run Phase 2 Manually" if that notification didn't arrive.
|
||||
</div>`;
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
}
|
||||
} else if (!hasPartner) {
|
||||
html += `<div style="font-size:11px;color:#444;margin-bottom:12px;">
|
||||
No partner hostname configured. Edit master.conf (Scheduler tab) and set HOST2.
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// ── Bottom row: Offboard + mirror Onboard ──────────────────────────────────
|
||||
html += '<div class="vv-pt-actions">';
|
||||
|
||||
if (!isOwner) {
|
||||
// Mirror: just runs its own path (SSH key + notifies owner)
|
||||
html += `<button class="vv-pt-action-btn run" onclick="vvPtOnboard(this)"
|
||||
${!hasPartner ? 'disabled style="opacity:.35;cursor:default;" title="Add partner hostname to master.conf first"' :
|
||||
'title="Run Mirror onboard: SSH key setup + notify owner to run Phase 2"'}>
|
||||
▶ Onboard (Mirror)
|
||||
</button>`;
|
||||
}
|
||||
|
||||
// Offboard
|
||||
html += `<button class="vv-pt-action-btn warn" onclick="vvPtOffboard(this)"
|
||||
${!cfg.enabled ? 'title="No active partnership to offboard" style="opacity:.35;cursor:default;"' : ''}>
|
||||
▶ Offboard
|
||||
@@ -227,18 +291,6 @@ function _renderActions(nodes, cfg) {
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// Contextual note under actions
|
||||
if (!hasPartner) {
|
||||
html += `<div style="font-size:11px;color:#444;margin-top:8px;">
|
||||
No partner hostname configured. Edit master.conf (Scheduler tab) and set HOST2.
|
||||
</div>`;
|
||||
} else if (!cfg.enabled) {
|
||||
html += `<div style="font-size:11px;color:#555;margin-top:8px;">
|
||||
Onboard will generate your SSH key, exchange it with the partner, and establish the partnership.
|
||||
Run on the mirror (HOST2) first, then on the owner (HOST1).
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Transfer — show manual command, too destructive to one-click
|
||||
if (cfg.enabled && isOwner) {
|
||||
const confirmStr = 'i-understand-this-transfers-ownership';
|
||||
@@ -290,12 +342,38 @@ function _render(data) {
|
||||
|
||||
// ── Actions ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function vvPtPhase1(btn, hostId) {
|
||||
if (!confirm(`Phase 1: SSH key exchange + conf push to ${hostId}?\n\nSafe to run before ${hostId} has Varaverk installed.`)) return;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '⟳ Starting…';
|
||||
fetch('/plugins/varaverk/api/run.php', {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams({id: 'Partnership/partnership_onboard.sh', manual: '1', extra_args: '--phase1-only'})
|
||||
}).then(r => r.json()).then(d => {
|
||||
if (!d.ok) { alert('Failed: ' + (d.error ?? 'Unknown error')); }
|
||||
setTimeout(() => { btn.disabled = false; btn.textContent = '▶ Phase 1: SSH + Conf Push'; }, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
function vvPtPhase2(btn, hostId) {
|
||||
if (!confirm(`Phase 2: Deploy containers + arr stack + establish partnership on ${hostId}?\n\nThis requires ${hostId} to have Varaverk installed and SSH keys set up.`)) return;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '⟳ Starting…';
|
||||
fetch('/plugins/varaverk/api/run.php', {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams({id: 'Partnership/partnership_onboard.sh', manual: '1', extra_args: '--phase2-only'})
|
||||
}).then(r => r.json()).then(d => {
|
||||
if (!d.ok) { alert('Failed: ' + (d.error ?? 'Unknown error')); }
|
||||
setTimeout(() => { btn.disabled = false; btn.textContent = '▶ Run Phase 2 Manually'; }, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
function vvPtOnboard(btn) {
|
||||
if (!confirm('Run partnership_onboard.sh?\n\nRun on the MIRROR first, then on the OWNER.')) return;
|
||||
if (!confirm('Run full partnership_onboard.sh?\n\nRun on the MIRROR first, then on the OWNER.\n\nUse Phase 1 + Phase 2 buttons for step-by-step control.')) return;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '⟳ Starting…';
|
||||
vvRunById('Partnership/partnership_onboard.sh');
|
||||
setTimeout(() => { btn.disabled = false; btn.textContent = '▶ Onboard'; }, 4000);
|
||||
setTimeout(() => { btn.disabled = false; btn.textContent = '▶ Onboard (Mirror)'; }, 4000);
|
||||
}
|
||||
|
||||
function vvPtOffboard(btn) {
|
||||
|
||||
Reference in New Issue
Block a user