diff --git a/Deployment/master.conf.template b/Deployment/master.conf.template index 7f1596e..3bac4be 100644 --- a/Deployment/master.conf.template +++ b/Deployment/master.conf.template @@ -640,6 +640,14 @@ # WEEKLY_RSYNC_ENABLED=true ← Emby + Critical-Data still sync (NVMe) # MONTHLY_RSYNC_ENABLED=true ← monthly_maintenance.sh rsync section # FALLBACK_RSYNC_ENABLED=true ← handback writeback still works +# MEDIA_SEED_ENABLED=false ← the onboard first-fill never starts +# +# MEDIA_SEED_ENABLED is the odd one out: it does not gate a schedule, it gates a single +# multi-week transfer. Rsync/media_seed.sh pushes every DAILY_SYNC_SHARES entry to a newly +# onboarded partner — on HOST1 that is ~28 TB against BW_LIMIT, so weeks. Turn it off when the +# partner is going to be filled some other way (a physically moved disk, an existing library), +# and onboard will finish without ever starting it. Turning it back on does not start anything +# by itself; dispatch it from the Partnership tab or run the script. # → Run individual: bash Rsync/rsync.sh /mnt/user/Movies # → When ready: INTERMEDIATE_RSYNC_ENABLED=true DAILY_RSYNC_ENABLED=true # NOTE ON THE DEFAULT: Tier 1 ships OFF. This template is what a brand-new node is seeded from @@ -655,6 +663,7 @@ WEEKLY_RSYNC_ENABLED=true # Tier 2 — weekly_sync_maintenance.sh rsync section MONTHLY_RSYNC_ENABLED=true # Tier 2 — monthly_maintenance.sh rsync section FALLBACK_RSYNC_ENABLED=true # Tier 2 — fallback.sh writeback jobs on handback + MEDIA_SEED_ENABLED=true # Tier 2 — Rsync/media_seed.sh, the onboard first-fill # ━━━ Download Webhook ━━━ # Immediate push to remote nodes on every Sonarr/Radarr/Lidarr Download event. diff --git a/Partnership/partnership_onboard.sh b/Partnership/partnership_onboard.sh index 36ee3a5..1baeefa 100755 --- a/Partnership/partnership_onboard.sh +++ b/Partnership/partnership_onboard.sh @@ -1089,8 +1089,20 @@ _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 9c 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 @@ -1158,8 +1170,9 @@ echo " Step 10 — Conf push: $( [[ "$ONBOARD_OK" == false ]] && echo "s 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 "not dispatched ❌"; }; } )" echo "" if [[ "$ONBOARD_OK" == true ]]; then diff --git a/Plugin/unraid/include/partnership.php b/Plugin/unraid/include/partnership.php index cc33d00..54a1768 100644 --- a/Plugin/unraid/include/partnership.php +++ b/Plugin/unraid/include/partnership.php @@ -245,10 +245,22 @@ function vv_pt_remote_system(string $ip, string $sshKey): array { function vv_pt_media_seed(): array { $stat = '/var/log/varaverk/Rsync/media_seed.json'; $log = '/var/log/varaverk/Rsync/media_seed.log'; - if (!is_file($stat)) return []; + + // MEDIA_SEED_ENABLED is reported alongside the record rather than instead of it, because + // the two answer different questions: the toggle says whether a seed may start, the record + // says what the last one did. media_seed.sh exits 0 when the toggle is off — correct, it is + // a decision and not a failure — so a record reading "ok" beside a disabled toggle would + // otherwise render as "Seed complete" over a partner that was never seeded. + // + // Unset reads as enabled, matching media_seed.sh: the toggle postdates the script. + $raw = vv_read_conf_raw('master.conf'); + $toggle = vv_arr_scalar($raw, 'MEDIA_SEED_ENABLED'); + $enabled = ($toggle === '' || strtolower($toggle) === 'true'); + + if (!is_file($stat)) return ['enabled' => $enabled]; $j = json_decode((string)file_get_contents($stat), true); - if (!is_array($j)) return []; + if (!is_array($j)) return ['enabled' => $enabled]; $status = (string)($j['status'] ?? ''); $pid = (int)($j['pid'] ?? 0); @@ -280,11 +292,12 @@ function vv_pt_media_seed(): array { } return array_filter([ - 'status' => $status, - 'start' => isset($j['start']) ? (int)$j['start'] : null, - 'end' => isset($j['end']) ? (int)$j['end'] : null, - 'share' => $share, - 'line' => mb_substr($line, 0, 160), + 'enabled' => $enabled, + 'status' => $status, + 'start' => isset($j['start']) ? (int)$j['start'] : null, + 'end' => isset($j['end']) ? (int)$j['end'] : null, + 'share' => $share, + 'line' => mb_substr($line, 0, 160), ], fn($v) => $v !== null && $v !== ''); } diff --git a/Plugin/unraid/pages/partnership.php b/Plugin/unraid/pages/partnership.php index 2ddf7ba..fb90586 100644 --- a/Plugin/unraid/pages/partnership.php +++ b/Plugin/unraid/pages/partnership.php @@ -381,6 +381,29 @@ function vvPtHostChanged(el) { // Stop the background media seed. Safe to press: rsync.sh runs --inplace --partial, so this // costs the file in flight and a later start resumes the share rather than restarting it. +// Start the background seed. Nothing here decides whether it may run — MEDIA_SEED_ENABLED is +// checked by the script, and the button is not rendered when the toggle is off. +async function vvPtStartSeed(btn) { + if (!await vvConfirm('Start the media seed?\n\nEvery DAILY_SYNC_SHARES entry is pushed to the partner. A first seed of a full library runs for days.')) return; + btn.disabled = true; btn.textContent = '⟳'; + try { + const r = await fetch('/plugins/varaverk/api/media_seed.php', { + method: 'POST', + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + body: new URLSearchParams({ + csrf_token: typeof csrf_token !== 'undefined' ? csrf_token : '', + action: 'start' + }) + }); + const d = await r.json(); + if (!d.ok) { vvAlert('Seed did not start: ' + (d.error ?? 'Unknown error')); btn.disabled = false; btn.textContent = '▶ Seed'; return; } + if (typeof vvPtLoad === 'function') vvPtLoad(); + } catch (e) { + btn.disabled = false; btn.textContent = '▶ Seed'; + vvAlert('Error: ' + e); + } +} + async function vvPtStopSeed(btn) { if (!await vvConfirm('Stop the media seed?\n\nThe transfer resumes from where it stopped when restarted.')) return; btn.disabled = true; btn.textContent = '⟳'; @@ -1151,27 +1174,39 @@ function _renderActions(nodes, cfg) { // Media seed strip. The seed is dispatched detached by onboard Step 13 and runs for // days, so without this the page would show a finished onboard and no sign that // terabytes are still moving underneath it. + // The toggle is read first and the record second. media_seed.sh exits 0 when + // MEDIA_SEED_ENABLED is false, so the record can read "ok" for a seed that never + // moved a byte — showing that as "Seed complete" would be exactly the kind of + // outcome-from-a-toggle claim this codebase keeps having to unpick. const ms = remote.media_seed || {}; - if (ms.status) { - const seedRunning = ms.status === 'running'; - const seedColor = seedRunning ? '#ff9800' - : ms.status === 'ok' ? '#4caf50' - : ms.status === 'stopped' ? '#666' : '#f44336'; - const seedLabel = seedRunning ? 'Seeding' - : ms.status === 'ok' ? 'Seed complete' - : ms.status === 'stopped' ? 'Seed stopped' : 'Seed failed'; - html += `