From 40d96f65415375134c934abc860e2d150c30caef Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 15 Jul 2026 20:51:16 +0100 Subject: [PATCH] Publish hosted VoidBot health to Idunn --- scripts/publish-idunn-rudp-health.cjs | 19 +++++++++++----- scripts/serve-voidbot-swarm-cultmesh.cjs | 28 ++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/scripts/publish-idunn-rudp-health.cjs b/scripts/publish-idunn-rudp-health.cjs index 3e479c1..da8a312 100644 --- a/scripts/publish-idunn-rudp-health.cjs +++ b/scripts/publish-idunn-rudp-health.cjs @@ -3,7 +3,10 @@ const dgram = require("dgram"); const path = require("path"); -const cultLibPackages = path.resolve(__dirname, "..", "..", "CultLib", "packages"); +const cultLibRoot = process.env.VOIDBOT_CULTLIB_ROOT + ? path.resolve(process.env.VOIDBOT_CULTLIB_ROOT) + : path.resolve(__dirname, "..", "..", "CultLib"); +const cultLibPackages = path.join(cultLibRoot, "packages"); process.env.NODE_PATH = [cultLibPackages, process.env.NODE_PATH || ""].filter(Boolean).join(path.delimiter); require("module").Module._initPaths(); @@ -14,7 +17,7 @@ const { encodeRudpPacket, } = require("cultnet-ts"); -const { encode } = require(path.resolve(__dirname, "..", "..", "CultLib", "node_modules", "@msgpack", "msgpack")); +const { encode } = require(path.join(cultLibRoot, "node_modules", "@msgpack", "msgpack")); const CULTNET_RUDP_PROTOCOL_ID = "cultnet.transport.rudp.v0"; const IDUNN_HEALTH_RUDP_CONNECTION_ID = 0x1d0d0001; @@ -283,7 +286,11 @@ async function closeSocket(socket) { }); } -main().catch((error) => { - console.error(error?.stack || String(error)); - process.exit(1); -}); +module.exports = { publishIdunnRudpHealth, parseEndpoint }; + +if (require.main === module) { + main().catch((error) => { + console.error(error?.stack || String(error)); + process.exit(1); + }); +} diff --git a/scripts/serve-voidbot-swarm-cultmesh.cjs b/scripts/serve-voidbot-swarm-cultmesh.cjs index 0a5665a..e8fa19f 100644 --- a/scripts/serve-voidbot-swarm-cultmesh.cjs +++ b/scripts/serve-voidbot-swarm-cultmesh.cjs @@ -10,6 +10,7 @@ const { readSwarmOperatorInputs, watchSwarmOperatorInputs, } = require("./voidbot-swarm-operator-view.cjs"); +const { parseEndpoint: parseIdunnEndpoint, publishIdunnRudpHealth } = require("./publish-idunn-rudp-health.cjs"); const repoRoot = path.resolve(__dirname, ".."); const defaultStorePath = path.join(repoRoot, ".voidbot", "status", "cultmesh", "voidbot-swarm-state.cc"); @@ -32,6 +33,8 @@ const operatorInputPaths = { const bind = parseBind(args.bind || process.env.VOIDBOT_SWARM_CULTMESH_BIND || defaultBind); const odinCultMeshUri = args.odinCultMeshUri || args["odin-cultmesh-uri"] || process.env.VOIDBOT_ODIN_CULTMESH_URI || process.env.ODIN_CULTMESH_URI || defaultOdinCultMeshUri; const odinRudpEndpoint = args.odinRudpEndpoint || args["odin-rudp-endpoint"] || process.env.VOIDBOT_ODIN_RUDP || process.env.CULTMESH_URI_ODIN_RUDP || defaultOdinRudpEndpoint; +const idunnHealthEndpoint = String(process.env.VOIDBOT_IDUNN_RUDP_HEALTH || "").trim(); +const idunnHealthIntervalMs = 30_000; const { CultMesh, CultMeshMemoryProviderReceiptStore, @@ -56,6 +59,8 @@ let announceTimer = null; let liveProviderSession = null; let stopOperatorWatch = null; let operatorPublishChain = Promise.resolve(); +let idunnHealthTimer = null; +let idunnHealthPublishChain = Promise.resolve(); main().catch((error) => { console.error(error instanceof Error ? error.stack || error.message : String(error)); @@ -97,10 +102,18 @@ async function main() { }); }, 30_000); announceTimer.unref?.(); + if (idunnHealthEndpoint) { + await queueIdunnHealthPublication(); + idunnHealthTimer = setInterval(queueIdunnHealthPublication, idunnHealthIntervalMs); + idunnHealthTimer.unref?.(); + } else { + console.log("VoidBot Idunn health publication is disabled because VOIDBOT_IDUNN_RUDP_HEALTH is not configured."); + } const shutdown = () => { try { if (announceTimer) clearInterval(announceTimer); + if (idunnHealthTimer) clearInterval(idunnHealthTimer); server?.close?.(); stopOperatorWatch?.(); void liveProviderSession?.stop?.(); @@ -112,6 +125,21 @@ async function main() { process.once("SIGTERM", shutdown); } +function queueIdunnHealthPublication() { + idunnHealthPublishChain = idunnHealthPublishChain + .then(() => publishIdunnRudpHealth({ + endpoint: parseIdunnEndpoint(idunnHealthEndpoint), + daemonId: process.env.VOIDBOT_IDUNN_DAEMON || "voidbot", + healthContract: process.env.VOIDBOT_IDUNN_HEALTH_CONTRACT || "voidbot.cultnet-rudp-stack-health", + }, { + state: "healthy", + detail: "VoidBot swarm CultMesh publisher is serving retained runtime state.", + observedAt: new Date().toISOString(), + })) + .catch((error) => console.error(`VoidBot Idunn health publication failed: ${error instanceof Error ? error.message : String(error)}`)); + return idunnHealthPublishChain; +} + async function publishCurrentControlState() { if (!fs.existsSync(controlStorePath)) return; const controlNode = await CultMesh.createNode(controlStorePath, { documents: [documents.swarmControl] });