// favela-node serve — the pgh-managed community full-node (favela-web/spec/community-ux.md reactor "serve").
// Durably serves each configured community's CommunityRoom (the keyless roster favela-web reads for the feed
// skeleton) on ONE shared swarm — replacing the fragile nohup serve-loser-club-room.mjs. Config-driven:
// FAVELA_NODE_CONFIG (or ~/.config/favela-node/config.json) = { communities: [{ roomKey, storage, namespace? }] }.
// This is a pgh daemon (readiness = pidfile, written by the supervisor). Add a community = one config entry.
import './lib/bare-globals.js'
import process from 'bare-process'
import fs from 'bare-fs'
import Corestore from 'corestore'
import Hyperswarm from 'hyperswarm'
import { CommunityRoom } from 'favela-community'
const HOME = process.env.HOME
const expand = (p) => (typeof p === 'string' ? p.replace(/^~(?=\/)/, HOME).replace(/\$\{HOME\}/g, HOME) : p)
const CONFIG = expand(process.env.FAVELA_NODE_CONFIG || (HOME + '/.config/favela-node/config.json'))
if (!fs.existsSync(CONFIG)) { console.error('favela-node: no config at ' + CONFIG); process.exit(1) }
const cfg = JSON.parse(fs.readFileSync(CONFIG, 'utf8'))
const communities = Array.isArray(cfg.communities) ? cfg.communities : []
if (!communities.length) { console.error('favela-node: no communities configured in ' + CONFIG); process.exit(1) }
const swarm = new Hyperswarm()
const served = []
for (const c of communities) {
try {
const store = new Corestore(expand(c.storage))
const room = await CommunityRoom.open(store, c.roomKey, { namespace: c.namespace || 'community' })
const members = await room.members()
swarm.join(room.discoveryKey, { server: true, client: true })
served.push({ room, store })
console.log(JSON.stringify({ msg: 'serving community room', roomKey: c.roomKey, did: await room.communityDid(), members: members.length, writable: room.writable }))
} catch (e) {
console.error(JSON.stringify({ msg: 'failed to serve community', roomKey: c.roomKey, error: e.message }))
}
}
// Replicate every served room's autobase over each connection (keet model: one swarm, many topics muxed).
swarm.on('connection', (conn) => { conn.on('error', () => {}); for (const { room } of served) room.base.replicate(conn) })
await swarm.flush()
console.log(JSON.stringify({ msg: 'favela-node serving', communities: served.length }))
let closing = false
const stop = async () => {
if (closing) return
closing = true
try { await swarm.destroy() } catch {}
for (const { room, store } of served) { try { await room.close() } catch {} try { await store.close() } catch {} }
process.exit(0)
}
process.on('SIGINT', stop)
process.on('SIGTERM', stop)