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:
@@ -242,7 +242,7 @@ function vv_pt_remote_system(string $ip, string $sshKey): array {
|
||||
//
|
||||
// Returns [] when there has never been a seed. A stale "running" record whose pid is gone is
|
||||
// reported as stopped rather than running: a record is not a process.
|
||||
function vv_pt_media_seed(): array {
|
||||
function vv_pt_media_seed(array $setupDb = [], string $nodeIdUpper = ''): array {
|
||||
$stat = '/var/log/varaverk/Rsync/media_seed.json';
|
||||
$log = '/var/log/varaverk/Rsync/media_seed.log';
|
||||
|
||||
@@ -257,10 +257,16 @@ function vv_pt_media_seed(): array {
|
||||
$toggle = vv_arr_scalar($raw, 'MEDIA_SEED_ENABLED');
|
||||
$enabled = ($toggle === '' || strtolower($toggle) === 'true');
|
||||
|
||||
if (!is_file($stat)) return ['enabled' => $enabled];
|
||||
// Phase 3 is complete only when every share landed, which media_seed.sh decides and records
|
||||
// at the end of a run that may have taken weeks. The job record cannot answer this: it goes
|
||||
// "ok" the moment the process exits, including the exits that seeded nothing.
|
||||
$phase3Done = $nodeIdUpper !== ''
|
||||
&& ($setupDb[$nodeIdUpper . '_PHASE3_DONE'] ?? '') === 'true';
|
||||
|
||||
if (!is_file($stat)) return ['enabled' => $enabled, 'phase3_done' => $phase3Done];
|
||||
|
||||
$j = json_decode((string)file_get_contents($stat), true);
|
||||
if (!is_array($j)) return ['enabled' => $enabled];
|
||||
if (!is_array($j)) return ['enabled' => $enabled, 'phase3_done' => $phase3Done];
|
||||
|
||||
$status = (string)($j['status'] ?? '');
|
||||
$pid = (int)($j['pid'] ?? 0);
|
||||
@@ -292,7 +298,8 @@ function vv_pt_media_seed(): array {
|
||||
}
|
||||
|
||||
return array_filter([
|
||||
'enabled' => $enabled,
|
||||
'enabled' => $enabled,
|
||||
'phase3_done' => $phase3Done,
|
||||
'status' => $status,
|
||||
'start' => isset($j['start']) ? (int)$j['start'] : null,
|
||||
'end' => isset($j['end']) ? (int)$j['end'] : null,
|
||||
@@ -433,7 +440,7 @@ function vv_pt_nodes(): array {
|
||||
'api_key_set' => $apiKeySet,
|
||||
'api_key_preview' => $apiKeyPreview,
|
||||
'metrics' => $metrics,
|
||||
'media_seed' => $isMe ? [] : vv_pt_media_seed(),
|
||||
'media_seed' => $isMe ? [] : vv_pt_media_seed($setupDb, $nodeIdUpper),
|
||||
];
|
||||
}
|
||||
return $nodes;
|
||||
|
||||
@@ -381,27 +381,23 @@ 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);
|
||||
}
|
||||
// Phase 3 — the media seed, as its own phase rather than the tail of an onboard. Routed through
|
||||
// partnership_onboard.sh --phase3-only rather than straight at media_seed.sh, because the phase
|
||||
// is more than the transfer: it refuses when Phase 2 is unfinished, and it sets the gate posture
|
||||
// the seed needs (Tier 1 open so rsync.sh will move anything, every Tier 2 gate closed so the
|
||||
// scheduled orchestrators are not fighting it for the link for the next several weeks).
|
||||
async function vvPtPhase3(btn, id) {
|
||||
const label = btn.textContent;
|
||||
if (!await vvConfirm(`Start Phase 3 — seed media to ${id}?\n\nEvery DAILY_SYNC_SHARES entry is pushed. A first seed of a full library runs for days, and the partnership is already live without it.\n\nTier 1 rsync is opened and every per-orchestrator gate is closed for the duration.`)) return;
|
||||
btn.disabled = true;
|
||||
btn.textContent = '⟳ Starting…';
|
||||
_vvPtRun('Partnership/partnership_onboard.sh', '--phase3-only')
|
||||
.then(d => { if (!d.ok) vvAlert('Phase 3 did not start: ' + (d.error ?? 'Unknown error')); })
|
||||
.catch(e => vvAlert('Error: ' + e))
|
||||
.finally(() => setTimeout(() => {
|
||||
btn.disabled = false; btn.textContent = label;
|
||||
if (typeof vvPtLoad === 'function') vvPtLoad();
|
||||
}, 3000));
|
||||
}
|
||||
|
||||
async function vvPtStopSeed(btn) {
|
||||
@@ -1178,35 +1174,41 @@ function _renderActions(nodes, cfg) {
|
||||
// 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.
|
||||
// Phase 3 — the media seed. Deliberately not rendered at all when MEDIA_SEED_ENABLED is
|
||||
// false: with seeding off the model is two phases, and a greyed-out third one would
|
||||
// invite the question of how to reach it. The toggle removes the phase, it does not
|
||||
// disable a button.
|
||||
//
|
||||
// The toggle is also read before the job record. media_seed.sh exits 0 when seeding is
|
||||
// off, so the record can read "ok" for a run that moved nothing.
|
||||
const ms = remote.media_seed || {};
|
||||
const seedRunning = ms.status === 'running';
|
||||
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'; }
|
||||
if (ms.enabled !== false) {
|
||||
const seedRunning = ms.status === 'running';
|
||||
const seedDone = ms.phase3_done === true;
|
||||
let seedColor, seedLabel;
|
||||
if (seedRunning) { seedColor = '#ff9800'; seedLabel = 'Phase 3 — seeding'; }
|
||||
else if (seedDone) { seedColor = '#4caf50'; seedLabel = 'Phase 3 complete'; }
|
||||
else if (ms.status === 'stopped') { seedColor = '#666'; seedLabel = 'Phase 3 stopped'; }
|
||||
else if (ms.status === 'ok') { seedColor = '#ff9800'; seedLabel = 'Phase 3 partial'; }
|
||||
else if (ms.status) { seedColor = '#f44336'; seedLabel = 'Phase 3 failed'; }
|
||||
else { seedColor = '#555'; seedLabel = 'Phase 3 not started'; }
|
||||
|
||||
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>
|
||||
${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>
|
||||
${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>` : ''}
|
||||
</div>`;
|
||||
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>`
|
||||
: `<button class="vv-pt-action-btn info" onclick="vvPtPhase3(this,'${remote.id}')"
|
||||
style="font-size:9px;opacity:.55;margin-left:auto;"
|
||||
title="Seed every DAILY_SYNC_SHARES entry to ${remote.id}">${seedDone ? '↻ Re-seed' : '▶ Phase 3'}</button>`}
|
||||
</div>
|
||||
${ms.line ? `<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>` : ''}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Delete Keys panel ──────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user