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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user