| … | … | @@ -0,0 +1,50 @@ |
| 1 | // favela-node serve — the pgh-managed community full-node (favela-web/spec/community-ux.md reactor "serve"). |
| 2 | // Durably serves each configured community's CommunityRoom (the keyless roster favela-web reads for the feed |
| 3 | // skeleton) on ONE shared swarm — replacing the fragile nohup serve-loser-club-room.mjs. Config-driven: |
| 4 | // FAVELA_NODE_CONFIG (or ~/.config/favela-node/config.json) = { communities: [{ roomKey, storage, namespace? }] }. |
| 5 | // This is a pgh daemon (readiness = pidfile, written by the supervisor). Add a community = one config entry. |
| 6 | import './lib/bare-globals.js' |
| 7 | import process from 'bare-process' |
| 8 | import fs from 'bare-fs' |
| 9 | import Corestore from 'corestore' |
| 10 | import Hyperswarm from 'hyperswarm' |
| 11 | import { CommunityRoom } from 'favela-community' |
| 12 | |
| 13 | const HOME = process.env.HOME |
| 14 | const expand = (p) => (typeof p === 'string' ? p.replace(/^~(?=\/)/, HOME).replace(/\$\{HOME\}/g, HOME) : p) |
| 15 | const CONFIG = expand(process.env.FAVELA_NODE_CONFIG || (HOME + '/.config/favela-node/config.json')) |
| 16 | |
| 17 | if (!fs.existsSync(CONFIG)) { console.error('favela-node: no config at ' + CONFIG); process.exit(1) } |
| 18 | const cfg = JSON.parse(fs.readFileSync(CONFIG, 'utf8')) |
| 19 | const communities = Array.isArray(cfg.communities) ? cfg.communities : [] |
| 20 | if (!communities.length) { console.error('favela-node: no communities configured in ' + CONFIG); process.exit(1) } |
| 21 | |
| 22 | const swarm = new Hyperswarm() |
| 23 | const served = [] |
| 24 | for (const c of communities) { |
| 25 | try { |
| 26 | const store = new Corestore(expand(c.storage)) |
| 27 | const room = await CommunityRoom.open(store, c.roomKey, { namespace: c.namespace || 'community' }) |
| 28 | const members = await room.members() |
| 29 | swarm.join(room.discoveryKey, { server: true, client: true }) |
| 30 | served.push({ room, store }) |
| 31 | console.log(JSON.stringify({ msg: 'serving community room', roomKey: c.roomKey, did: await room.communityDid(), members: members.length, writable: room.writable })) |
| 32 | } catch (e) { |
| 33 | console.error(JSON.stringify({ msg: 'failed to serve community', roomKey: c.roomKey, error: e.message })) |
| 34 | } |
| 35 | } |
| 36 | // Replicate every served room's autobase over each connection (keet model: one swarm, many topics muxed). |
| 37 | swarm.on('connection', (conn) => { conn.on('error', () => {}); for (const { room } of served) room.base.replicate(conn) }) |
| 38 | await swarm.flush() |
| 39 | console.log(JSON.stringify({ msg: 'favela-node serving', communities: served.length })) |
| 40 | |
| 41 | let closing = false |
| 42 | const stop = async () => { |
| 43 | if (closing) return |
| 44 | closing = true |
| 45 | try { await swarm.destroy() } catch {} |
| 46 | for (const { room, store } of served) { try { await room.close() } catch {} try { await store.close() } catch {} } |
| 47 | process.exit(0) |
| 48 | } |
| 49 | process.on('SIGINT', stop) |
| 50 | process.on('SIGTERM', stop) |