igneum/tools/workers/merge-workers.mjs

25 lines
3.8 KiB
JavaScript

// Build-1-native workers-page merge (8 Oct 2026, the founder's rule: published from build-1, never the Mac, no secret, no key).
// Reads every box's own workers.json over its public https host (build.igneum.network for build-1, build-N.igneum.network for the
// rest), takes each .box into a boxes[] array, merges the Mac-dropped sources (mac.json, pcs.json in /srv/workers/sources,
// written by the Mac's push.mjs over ssh), and writes /srv/workers/workers.json, which build.igneum.network serves. Build-1's
// own box comes from a sources-free local collect into /tmp so it is never circular. Node 22, no dependencies.
import { readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
const OUT = '/srv/workers/workers.json';
const HOSTS = [['igneum-build-1','https://build.igneum.network'],['igneum-build-2','https://build-2.igneum.network'],['igneum-build-3','https://build-3.igneum.network'],['igneum-build-4','https://build-4.igneum.network'],['igneum-build-5','https://build-5.igneum.network'],['igneum-build-6','https://build-6.igneum.network'],['igneum-build-7','https://build-7.igneum.network'],['igneum-build-8','https://build-8.igneum.network'],['igneum-build-9','https://build-9.igneum.network']];
const now = Date.now(), iso = t => new Date(t).toISOString();
async function fetchBox(name, base) {
if (name === 'igneum-build-1') { // our own box, sources-free local collect (never read our own merged output)
try { mkdirSync('/tmp/wself', { recursive: true }); execFileSync('/usr/local/bin/node', ['/srv/workers/bin/collect.mjs'], { env: { ...process.env, IGNEUM_WORKERS_DIR: '/tmp/wself' }, timeout: 20000 }); const d = JSON.parse(readFileSync('/tmp/wself/workers.json', 'utf8')); return { ...(d.box || {}), name, source: { ok: true, at: iso(now) } }; } catch (e) { return { name, down: true, reason: String(e.message || e).slice(0, 80) }; }
}
try { const r = await fetch(`${base}/workers.json?t=${now}`, { signal: AbortSignal.timeout(8000) }); if (!r.ok) throw new Error('http ' + r.status); const d = await r.json(); const b = d.box || {}; const age = d.collected_at ? (now - Date.parse(d.collected_at)) / 1000 : null; return { ...b, name, source: { ok: age === null || age < 120, at: d.collected_at || null, age_s: age } }; }
catch (e) { const reason = String(e.message || e); return { name, down: true, reason: (name === 'igneum-build-3' ? 'down since 16:40 BST (Hetzner switch fault hel1-dc12-sw_99)' : reason).slice(0, 90) }; }
}
function source(name) { try { const d = JSON.parse(readFileSync(`/srv/workers/sources/${name}.json`, 'utf8')); return d; } catch { return null; } }
const boxes = await Promise.all(HOSTS.map(([n, b]) => fetchBox(n, b)));
const mac = source('mac'), pcs = source('pcs');
// the Mac mini (igneum-mini, the Mac build box, M6): no inbound route from the boxes, so its own launchd collector pushes mini.json to build-1 (8 Oct 2026). A row older than 120 s reads down.
const miniSrc = source('mini'); let mini = null; if (miniSrc) { const d = miniSrc.data ? miniSrc.data : miniSrc; const age = d.collected_at ? (now - Date.parse(d.collected_at)) / 1000 : null; mini = { ...(d.box || d), name: 'igneum-mini', label: 'the Mac build box, M6', source: { ok: age === null || age < 120, at: d.collected_at || null, age_s: age } }; }
const doc = { v: 3, generated_at: iso(now), generated_by: 'merge-workers.mjs on igneum-build-1', boxes, box: boxes[0], mini, mac: mac && mac.data ? mac.data : mac, pcs: pcs && pcs.data ? pcs.data : pcs, sources: { merge: { ok: true, at: iso(now) }, mac: mac ? { ok: true } : { ok: false }, pcs: pcs ? { ok: true } : { ok: false } } };
const tmp = OUT + '.tmp'; writeFileSync(tmp, JSON.stringify(doc)); renameSync(tmp, OUT);
process.stdout.write(`merged ${boxes.length} boxes (${boxes.filter(b => !b.down).length} up), mac ${!!doc.mac}, pcs ${!!doc.pcs} at ${iso(now)}\n`);