Make seeding Phase 3 — its own operator-triggered phase, not the tail of an onboard

Onboard now ends at Phase 2: connected, running, and saying so. Phase 3 sets the seeding gate posture and dispatches the seed. With MEDIA_SEED_ENABLED false there is no Phase 3 and the model is two phases.
This commit is contained in:
Gmer4Lfe
2026-08-17 10:23:03 -04:00
parent 427a31cdff
commit 7c957d96be
4 changed files with 207 additions and 139 deletions
+127 -85
View File
@@ -42,9 +42,10 @@
# would discover are actually deployed there
# Step 12: Container grouping — file our containers on the mirror under "<OwnerShort>-Fallback",
# icon resolved here and passed over: the mirror has no Emby key
# Step 13: Media seed — dispatch Rsync/media_seed.sh detached, after the phase flag
# is written. It is a multi-week transfer and must never be
# the thing standing between an onboard and its own summary
#
# Phase 3 (media seed) is NOT part of the above. Onboard ends at Phase 2 — connected, running,
# and saying so. Seeding is a separate operator-triggered phase; see --phase3-only. With
# MEDIA_SEED_ENABLED=false there is no Phase 3 and the model is two phases.
#
# ==============================================================================================
# DESIGN PRINCIPLES
@@ -159,14 +160,15 @@
# Partnership/partnership_onboard.sh --skip-webhook-setup
# Skip webhook registration in arrs (Step 9b)
#
# Partnership/partnership_onboard.sh --phase3-only
# OWNER: media seed only. Refuses unless Phase 2 is done and MEDIA_SEED_ENABLED is true.
# Sets the seeding gate posture (Tier 1 open, every Tier 2 gate closed) and dispatches
# Rsync/media_seed.sh detached. Returns in seconds; the seed runs for weeks.
#
# Partnership/partnership_onboard.sh --skip-appdata-provision
# Skip the pre-container auth appdata rsync (Step 1e)
# The mirror's auth containers then start against whatever is already there
#
# Partnership/partnership_onboard.sh --skip-media-seed
# Skip dispatching the background media seed (Step 13)
# Use when mirror already has files or you want to seed manually
#
# Partnership/partnership_onboard.sh --skip-webhook-listener
# Skip starting webhook listener on mirror (Step 9e)
# Listener will start automatically on next array restart
@@ -203,11 +205,11 @@ SKIP_ARR_STACK=false
SKIP_SERVICES_STACK=false
SKIP_ARR_SYNC=false
SKIP_WEBHOOK_SETUP=false
SKIP_MEDIA_SEED=false
SKIP_WEBHOOK_LISTENER=false
PHASE1_ONLY=false # OWNER: SSH + conf push only (HOST2 not yet installed)
# MIRROR: SSH key install only, no owner notification
PHASE2_ONLY=false # OWNER: containers/arr/onboard only (triggered by HOST2 after it onboards)
PHASE3_ONLY=false # OWNER: media seed only — a separate decision, see the PHASE 3 block
SKIP_ARM=false # leave the sync gates as they are — see Step 1d
SKIP_APPDATA_PROVISION=false # skip the pre-container auth appdata sync — see Step 1e
FILTERED_ARGS=()
@@ -221,11 +223,11 @@ for arg in "$@"; do
--skip-services-stack) SKIP_SERVICES_STACK=true ;;
--skip-arr-sync) SKIP_ARR_SYNC=true ;;
--skip-webhook-setup) SKIP_WEBHOOK_SETUP=true ;;
--skip-media-seed) SKIP_MEDIA_SEED=true ;;
--skip-appdata-provision) SKIP_APPDATA_PROVISION=true ;;
--skip-webhook-listener) SKIP_WEBHOOK_LISTENER=true ;;
--phase1-only) PHASE1_ONLY=true ;;
--phase2-only) PHASE2_ONLY=true; SKIP_SSH=true ;;
--phase3-only) PHASE3_ONLY=true; SKIP_SSH=true ;;
--no-arm) SKIP_ARM=true ;;
*) FILTERED_ARGS+=("$arg") ;;
esac
@@ -474,8 +476,124 @@ MIRROR_IP=$(resolve_tailscale_ip "$MIRROR")
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)"
[[ "$PHASE3_ONLY" == true ]] && log "Mode: Phase 3 only (media seed)"
echo ""
# ==============================================================================================
# ── PHASE 3: MEDIA SEED ───────────────────────────────────────────────────────────────────────
#
# The partnership is finished at the end of Phase 2 — connected, running, and saying so. Phase 3
# is not part of it. It is the separate decision to fill the partner's disks by rsync, and it is
# operator-triggered because a first seed of a full library is weeks of transfer and tens of
# terabytes of somebody else's free space.
#
# The seed used to be a step inside Phase 2. Inline it held the onboard open for the entire
# transfer, which meant the phase-2 flag, the conf push, discovery and container grouping all
# waited on it, and both hosts described an unfinished onboard over a partnership that was
# already live. Detaching it fixed the blocking but left the seed starting on its own, which is
# still the wrong default: the machine decided to move 28 TB because an onboard succeeded.
#
# With MEDIA_SEED_ENABLED=false there is no Phase 3 at all and the model is two phases. That is
# the point of the toggle — not a seed that runs and does nothing, an absent phase.
# ==============================================================================================
if [[ "$PHASE3_ONLY" == true ]]; then
echo ""
echo "━━━━━ $ICON_SYNC PHASE 3 — MEDIA SEED — $MY_ID$MIRROR ━━━━━"
echo ""
_master_conf="$SCRIPTS_ROOT/Configurations/master.conf"
_seed_gate=$(grep -m1 -E '^[[:space:]]*MEDIA_SEED_ENABLED=' "$_master_conf" 2>/dev/null \
| cut -d= -f2- | cut -d'#' -f1 | tr -d '"'"'" | tr -d '[:space:]')
if [[ -n "$_seed_gate" && "$_seed_gate" != "true" ]]; then
error "MEDIA_SEED_ENABLED is '$_seed_gate' — there is no Phase 3 on this host"
error " Onboarding is two phases while seeding is off. Arm the toggle in master.conf"
error " if you want the partner's library filled by rsync."
exit 1
fi
_setup_db="$(platform_setup_db_path)"
if ! grep -q "^${MIRROR_ID}_PHASE2_DONE=true" "$_setup_db" 2>/dev/null; then
error "$MIRROR has not completed Phase 2 — seeding a partner that is not onboarded"
error " would push media at a host with no containers to serve it. Finish Phase 2 first."
exit 1
fi
unset _setup_db
# Seeding posture, set before the transfer rather than after it. Tier 1 has to be open or
# rsync.sh refuses every share; every Tier 2 gate is closed so the scheduled orchestrators
# are not competing for the same link and the same disks for the next several weeks. This is
# also the posture the seed leaves behind — global on, per-orchestrator off — so finishing
# Phase 3 does not quietly hand the schedule back.
echo "━━━ Gate posture ━━━"
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would set RSYNC_ENABLED true and every Tier 2 rsync gate false"
else
cp -a "$_master_conf" "${_master_conf}.bak-phase3-$(date +%Y%m%d-%H%M%S)"
set_conf_bool RSYNC_ENABLED "true" "$_master_conf" \
|| { error "Could not open Tier 1 — the seed would move nothing"; exit 1; }
for _gate in CRITICAL_RSYNC_ENABLED INTERMEDIATE_RSYNC_ENABLED DAILY_RSYNC_ENABLED \
WEEKLY_RSYNC_ENABLED MONTHLY_RSYNC_ENABLED FALLBACK_RSYNC_ENABLED; do
set_conf_bool "$_gate" "false" "$_master_conf" \
|| warn "Could not close $_gate — a scheduled sync may run beside the seed"
done
unset _gate
echo " Tier 1 open, every Tier 2 gate closed ✅"
fi
unset _master_conf _seed_gate
echo ""
echo "━━━ Dispatch ━━━"
_seed_script="$SCRIPTS_ROOT/Rsync/media_seed.sh"
_runner="$SCRIPTS_ROOT/Plugin/$PLATFORM/run_job.sh"
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would dispatch Rsync/media_seed.sh"
exit 0
fi
if [[ ! -f "$_seed_script" || ! -f "$_runner" ]]; then
error "Seed runner not found at $_seed_script"
exit 1
fi
# setsid, not nohup + &: the seed must outlive this script and must not share its process
# group, or stopping Phase 3 takes the transfer down with it.
_seed_flags=()
[[ "$ENABLE_LOGGING" == true ]] && _seed_flags+=(--log)
setsid bash "$_runner" "Rsync/media_seed.sh" "$_seed_script" "${_seed_flags[@]}" \
>/dev/null 2>&1 < /dev/null &
disown 2>/dev/null || true
# Report the record, not the launch. run_job.sh writes its stat file before running the
# script, so a fresh "running" record is the difference between dispatched and attempted.
_seed_stat="/var/log/varaverk/Rsync/media_seed.json"
_dispatched=false
for _i in $(seq 1 10); do
if [[ -f "$_seed_stat" ]] \
&& grep -q '"status":"running"' "$_seed_stat" 2>/dev/null \
&& (( $(date +%s) - $(stat -c %Y "$_seed_stat") < 60 )); then
_dispatched=true
break
fi
sleep 1
done
echo ""
echo "━━━━━ $ICON_SUMMARY PHASE 3 SUMMARY ━━━━━"
if [[ "$_dispatched" == true ]]; then
echo " Seed: running as job Rsync/media_seed.sh ✅"
echo " Shares: ${#DAILY_SYNC_SHARES[@]}"
echo " Gates: Tier 1 open · every Tier 2 closed"
echo ""
echo " A first full seed runs for days. Watch it on the Partnership tab; the"
echo " partnership itself has been live since Phase 2 and does not depend on this."
echo "━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
error " Seed did not start — check /var/log/varaverk/Rsync/media_seed.log"
echo "━━━━━━━━━━━━━━━━━━━━━━━"
exit 1
fi
STEP_SSH_OK=false
STEP_NETWORK_OK=false
LOCAL_SETUP_OK=true # partnership_manager --local-only; the summary claimed done ✅ regardless
@@ -1122,78 +1240,6 @@ fi
# ── Write Phase 2 completion state ────────────────────────────────────────────────────────────
[[ "$ONBOARD_OK" == true && "$DRY_RUN" == false ]] && write_onboard_phase "$MIRROR_ID" 2
# ── Step 13: Dispatch the media seed ──────────────────────────────────────────────────────────
# Last, detached, and after the phase flag is already on disk — see the header of
# Rsync/media_seed.sh for why. Briefly: on HOST1 this is thirteen shares and ~28 TB behind a
# 12.5 MB/s bwlimit, so inline it held this script open for weeks. Everything below it waited,
# including write_onboard_phase above, so the owner's card stayed on "Waiting for HOST2 to
# install Varaverk" and the mirror's checklist on "1 required item left: Partnership" for the
# entire transfer, describing a partnership that was already established. The onboard job
# record also stayed "running", which run_job.sh's already-running guard reads as a reason to
# refuse every later onboard.
#
# setsid, not nohup + &: the seed must survive this script exiting and must not share a process
# group with it, or stopping the onboard takes the transfer down with it.
echo ""
echo "━━━ $ICON_SYNC Step 13 — Media Share Seed (dispatch) ━━━"
MEDIA_SEED_DISPATCHED=false
_seed_job="Rsync/media_seed.sh"
_seed_script="$SCRIPTS_ROOT/Rsync/media_seed.sh"
_runner="$SCRIPTS_ROOT/Plugin/$PLATFORM/run_job.sh"
# Read from disk, not from the sourced value: Step 1d rewrote master.conf a few steps ago.
# Unset reads as on — the toggle postdates the seed, so a conf that has not been through a
# conf_upgrade must keep the behaviour it had. media_seed.sh checks this again itself; the
# check here exists so the summary can say "disabled" instead of dispatching a job whose only
# act is to exit.
_seed_gate=$(grep -m1 -E '^[[:space:]]*MEDIA_SEED_ENABLED=' "$SCRIPTS_ROOT/Configurations/master.conf" 2>/dev/null \
| cut -d= -f2- | cut -d'#' -f1 | tr -d '"'"'" | tr -d '[:space:]')
if [[ "$SKIP_MEDIA_SEED" == true ]]; then
warn "Skipping (--skip-media-seed)"
elif [[ -n "$_seed_gate" && "$_seed_gate" != "true" ]]; then
warn "MEDIA_SEED_ENABLED is '$_seed_gate' — not dispatching the seed"
warn "$MIRROR will need its library filled another way, or arm the toggle and start it"
warn "from the Partnership tab"
elif [[ "$ONBOARD_OK" == false ]]; then
warn "Skipping — onboard did not complete"
elif [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would dispatch $_seed_job"
elif [[ ! -f "$_seed_script" || ! -f "$_runner" ]]; then
warn "Seed runner not found — run manually: bash $_seed_script"
else
_seed_flags=()
[[ "$ENABLE_LOGGING" == true ]] && _seed_flags+=(--log)
setsid bash "$_runner" "$_seed_job" "$_seed_script" "${_seed_flags[@]}" \
>/dev/null 2>&1 < /dev/null &
disown 2>/dev/null || true
# Confirm the runner actually took the job rather than reporting the launch. run_job.sh
# writes its stat file before exec'ing the script, so a fresh "running" record with a live
# pid is the difference between dispatched and merely attempted.
_seed_stat="/var/log/varaverk/Rsync/media_seed.json"
for _i in 1 2 3 4 5 6 7 8 9 10; do
if [[ -f "$_seed_stat" ]] \
&& grep -q '"status":"running"' "$_seed_stat" 2>/dev/null \
&& (( $(date +%s) - $(stat -c %Y "$_seed_stat") < 60 )); then
MEDIA_SEED_DISPATCHED=true
break
fi
sleep 1
done
unset _seed_stat _i _seed_flags
if [[ "$MEDIA_SEED_DISPATCHED" == true ]]; then
echo "Seed running as job $_seed_job"
echo " ${#DAILY_SYNC_SHARES[@]} share(s) — watch it on the Scheduler tab, not here"
echo " A first full seed runs for days. The partnership is live now; this only fills disks."
else
warn "Seed did not start — run manually: bash $_seed_script"
fi
fi
unset _seed_job _seed_script _runner
# ── Summary ───────────────────────────────────────────────────────────────────────────────────
END=$(date +%s)
echo ""
@@ -1223,10 +1269,6 @@ echo " Step 9e — Webhook listener: $(_skip "$SKIP_WEBHOOK_LISTENER" "$WEBHOOK
echo " Step 10 — Conf push: $( [[ "$ONBOARD_OK" == false ]] && echo "skipped" || echo "$(_ok "$MASTER_PUSH_OK")" )"
echo " Step 11 — Discovery: $( [[ "$POPULATE_OK" == skipped ]] && echo "skipped (unreachable)" || _ok "$POPULATE_OK" )"
echo " Step 12 — Grouping: $( [[ "$FOLDER_OK" == skipped ]] && echo "skipped" || _ok "$FOLDER_OK" )"
echo " Step 13 — Media seed: $( [[ "$SKIP_MEDIA_SEED" == true ]] && echo "skipped" \
|| { [[ -n "${_seed_gate:-}" && "${_seed_gate:-}" != "true" ]] && echo "off (MEDIA_SEED_ENABLED=$_seed_gate)" \
|| { [[ "$MEDIA_SEED_DISPATCHED" == true ]] && echo "dispatched — runs in background ✅" \
|| echo "not dispatched ❌"; }; } )"
echo ""
if [[ "$ONBOARD_OK" == true ]]; then