Put the media seed behind MEDIA_SEED_ENABLED so a partner filled by other means never starts a multi-week transfer
Tier 2 beside the per-orchestrator gates. Unset reads as on — the toggle postdates the seed.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +292,7 @@ function vv_pt_media_seed(): array {
|
||||
}
|
||||
|
||||
return array_filter([
|
||||
'enabled' => $enabled,
|
||||
'status' => $status,
|
||||
'start' => isset($j['start']) ? (int)$j['start'] : null,
|
||||
'end' => isset($j['end']) ? (int)$j['end'] : null,
|
||||
|
||||
@@ -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,28 +1174,40 @@ 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';
|
||||
let seedColor, seedLabel;
|
||||
if (ms.enabled === false && !seedRunning) {
|
||||
seedColor = '#555'; seedLabel = 'Seed off';
|
||||
} else if (seedRunning) { seedColor = '#ff9800'; seedLabel = 'Seeding'; }
|
||||
else if (ms.status === 'ok') { seedColor = '#4caf50'; seedLabel = 'Seed complete'; }
|
||||
else if (ms.status === 'stopped'){ seedColor = '#666'; seedLabel = 'Seed stopped'; }
|
||||
else if (ms.status) { seedColor = '#f44336'; seedLabel = 'Seed failed'; }
|
||||
else { seedColor = '#555'; seedLabel = 'Not seeded'; }
|
||||
|
||||
const seedOff = ms.enabled === false;
|
||||
const showLine = seedRunning || (ms.line && !seedOff);
|
||||
html += `<div style="margin-top:9px;padding-top:8px;border-top:1px solid #181818;">
|
||||
<div style="display:flex;align-items:center;gap:7px;flex-wrap:wrap;">
|
||||
<span style="font-size:9px;font-weight:600;color:${seedColor};">${seedLabel}</span>
|
||||
${ms.share ? `<span style="font-size:9px;color:#444;font-family:monospace;">${vvEscHtml(ms.share)}</span>` : ''}
|
||||
${seedRunning ? `<button class="vv-pt-action-btn warn" onclick="vvPtStopSeed(this)"
|
||||
style="font-size:9px;opacity:.55;margin-left:auto;">■ Stop</button>` : ''}
|
||||
${seedOff ? `<span style="font-size:9px;color:#333;">MEDIA_SEED_ENABLED=false</span>` : ''}
|
||||
${!seedOff && ms.share ? `<span style="font-size:9px;color:#444;font-family:monospace;">${vvEscHtml(ms.share)}</span>` : ''}
|
||||
${seedRunning
|
||||
? `<button class="vv-pt-action-btn warn" onclick="vvPtStopSeed(this)"
|
||||
style="font-size:9px;opacity:.55;margin-left:auto;">■ Stop</button>`
|
||||
: (seedOff ? '' : `<button class="vv-pt-action-btn info" onclick="vvPtStartSeed(this)"
|
||||
style="font-size:9px;opacity:.55;margin-left:auto;"
|
||||
title="Push every DAILY_SYNC_SHARES entry to ${remote.id}">▶ Seed</button>`)}
|
||||
</div>
|
||||
${ms.line ? `<div style="margin-top:3px;font-size:9px;color:#2e2e2e;font-family:monospace;
|
||||
${showLine ? `<div style="margin-top:3px;font-size:9px;color:#2e2e2e;font-family:monospace;
|
||||
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;"
|
||||
>${vvEscHtml(ms.line)}</div>` : ''}
|
||||
>${vvEscHtml(ms.line || '')}</div>` : ''}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Delete Keys panel ──────────────────────────────────────────────────
|
||||
if (isDeleting) {
|
||||
|
||||
@@ -1788,6 +1788,7 @@ Saved into `master.conf`, which does not need to be opened by hand.
|
||||
| `WEEKLY_RSYNC_ENABLED` | a switch | in this section | Tier 2 — weekly_sync_maintenance.sh rsync section |
|
||||
| `MONTHLY_RSYNC_ENABLED` | a switch | in this section | Tier 2 — monthly_maintenance.sh rsync section |
|
||||
| `FALLBACK_RSYNC_ENABLED` | a switch | Fallback tab | Tier 2 — fallback.sh writeback jobs on handback |
|
||||
| `MEDIA_SEED_ENABLED` | a switch | Partnership tab | Tier 2 — Rsync/media_seed.sh, the onboard first-fill of a new partner |
|
||||
|
||||
## Rsync Merge Auto-Promote
|
||||
|
||||
|
||||
@@ -111,6 +111,11 @@ HOST1's library is ~28 TB against a 12.5 MB/s `--bwlimit`, which is why onboard
|
||||
rather than waiting on it, and why the Partnership tab gives it a Stop button — `rsync.sh` runs
|
||||
`--inplace --partial`, so stopping costs the file in flight, not the share.
|
||||
|
||||
It answers to `MEDIA_SEED_ENABLED` in `master.conf` — a Tier 2 toggle beside the per-orchestrator
|
||||
ones, still under Tier 1 `RSYNC_ENABLED`. Off means onboard finishes without ever dispatching it,
|
||||
for a partner being filled from a moved disk or one that already holds the library. An unset
|
||||
`MEDIA_SEED_ENABLED` reads as on, so a conf that predates the toggle keeps its behaviour.
|
||||
|
||||
---
|
||||
|
||||
## ━━━ HOW THE SCRIPTS RELATE ━━━
|
||||
|
||||
+35
-9
@@ -41,12 +41,19 @@
|
||||
# Interrupting this script costs the current file, not the current share, and a
|
||||
# re-run picks up where it stopped. Stopping it is cheap; that is deliberate.
|
||||
#
|
||||
# Two Gates, And They Mean Different Things
|
||||
# MEDIA_SEED_ENABLED (master.conf, Tier 2) switches off this transfer and only
|
||||
# this transfer — for a partner being filled from a physically moved disk, or
|
||||
# one that already holds the library. Off is a decision, so it exits 0.
|
||||
# RSYNC_ENABLED (Tier 1) switches off every rsync on the host. Reaching this
|
||||
# script with Tier 1 closed is a misconfiguration, so it exits 1.
|
||||
#
|
||||
# Gate Read From Disk
|
||||
# RSYNC_ENABLED is read out of master.conf here rather than trusted from the
|
||||
# sourced environment. Onboard's Step 9c rewrites that file moments before
|
||||
# dispatching this script, and each rsync.sh below sources it fresh anyway.
|
||||
# With the gate closed rsync.sh moves nothing and still exits 0, so every share
|
||||
# would be counted as seeded — refuse once instead of reporting fourteen no-ops.
|
||||
# Both are read out of master.conf here rather than trusted from the sourced
|
||||
# environment. Onboard's Step 9c rewrites that file moments before dispatching
|
||||
# this script, and each rsync.sh below sources it fresh anyway. With Tier 1
|
||||
# closed rsync.sh moves nothing and still exits 0, so every share would be
|
||||
# counted as seeded — refuse once instead of reporting fourteen no-ops.
|
||||
#
|
||||
# One Failed Share Is Not A Failed Seed
|
||||
# A share whose backing disk is unmounted on the partner fails its own rsync and
|
||||
@@ -58,10 +65,16 @@
|
||||
# ==============================================================================================
|
||||
#
|
||||
# acquire_lock "skip" — a second seed cannot run beside the first
|
||||
# Gate check — refuses when RSYNC_ENABLED is not true
|
||||
# Gate checks — MEDIA_SEED_ENABLED then RSYNC_ENABLED, in that order
|
||||
# Empty list check — refuses when DAILY_SYNC_SHARES is empty
|
||||
# Per-share accounting — failures are listed by name, not summed away
|
||||
#
|
||||
# An absent MEDIA_SEED_ENABLED reads as on, never as off.
|
||||
# The toggle was added after this script shipped, so a master.conf that has not been
|
||||
# through a conf_upgrade does not have the key. Defaulting an unset toggle to off would
|
||||
# silently stop seeding on every node that has not upgraded — a change in behaviour
|
||||
# delivered by a missing line, which is the hardest kind to notice.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
@@ -94,11 +107,24 @@ echo "━━━ $ICON_SYNC Media Share Seed — $MY_ID ($LOCAL_SERVER_NAME) —
|
||||
echo ""
|
||||
|
||||
# See "Gate Read From Disk" above. The trailing comment is cut before the value is compared:
|
||||
# master.conf writes this as `RSYNC_ENABLED=true # Tier 1 — global gate, overrides everything
|
||||
# master.conf writes these as `RSYNC_ENABLED=true # Tier 1 — global gate, overrides everything
|
||||
# below`, so stopping at `cut -d= -f2` yields "true#Tier1—globalgate,…" and never matches.
|
||||
_gate=$(grep -m1 -E '^[[:space:]]*RSYNC_ENABLED=' "$SCRIPTS_ROOT/Configurations/master.conf" 2>/dev/null \
|
||||
| cut -d= -f2- | cut -d'#' -f1 | tr -d '"'"'" | tr -d '[:space:]')
|
||||
_read_gate() {
|
||||
grep -m1 -E "^[[:space:]]*$1=" "$SCRIPTS_ROOT/Configurations/master.conf" 2>/dev/null \
|
||||
| cut -d= -f2- | cut -d'#' -f1 | tr -d '"'"'" | tr -d '[:space:]'
|
||||
}
|
||||
|
||||
# Tier 2 first, because it is the more specific answer and the operator deserves to be told
|
||||
# which switch stopped this. An unset MEDIA_SEED_ENABLED is treated as on: it was added after
|
||||
# the seed already existed, so a conf that predates it must keep behaving the way it did.
|
||||
_seed_gate=$(_read_gate MEDIA_SEED_ENABLED)
|
||||
if [[ "$DRY_RUN" == false && -n "$_seed_gate" && "$_seed_gate" != "true" ]]; then
|
||||
warn "MEDIA_SEED_ENABLED is '$_seed_gate' — the media seed is switched off in master.conf"
|
||||
warn "Set it true there if the partner should be filled by rsync rather than by hand"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
_gate=$(_read_gate RSYNC_ENABLED)
|
||||
if [[ "$DRY_RUN" == false && "$_gate" != "true" ]]; then
|
||||
error "RSYNC_ENABLED is '${_gate:-unset}' — rsync.sh would move nothing"
|
||||
error "Arm it in master.conf, then re-run this script"
|
||||
|
||||
Reference in New Issue
Block a user