OverviewFilesCommitsIssuesDocsReleasesPeers

favela-node commits / 9cebe76

favela-node 0.1.6: pgh-managed community serving daemon (serve.js)
Replaces the fragile nohup serve-loser-club-room.mjs with a durable pgh daemon: serve.js opens + serves each configured community's CommunityRoom on one shared swarm (keet model), config-driven via FAVELA_NODE_CONFIG. package.json gains the pgh manifest (type daemon, runtime bare, autostart, restart-on-failure, pidfile readiness). Adopted + running as the `favela-node` pgh app, serving loser-club's room — the feed's roster now survives reboots + hard-kills (no more fd-lock corruption risk). Add a community = one config entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9cebe769e6cfa46f276d5e21b129d4db955bc447
by plankatron <fleeky@gmail.com> · 2026-07-16T14:00:27.000Z

Changed files (2) +78 -1

package.json +28 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "favela-node",
3 "version": "0.1.5",
3 "version": "0.1.6",
44 "description": "favela-node \u2014 the full-node that composes a community's CO-OWNED atproto DID (hyper-proto-peer / atproto-room: StateBase admins + Lease + Signer/LabelRoom = the multiswarm-PDS pattern) with the keyless CommunityRoom roster (favela-community) and, later, the favela-web lens-export gateway. See favela-web/spec/lens-export.md.",
55 "type": "module",
66 "main": "index.js",
……@@ -21,5 +21,32 @@
2121 },
2222 "devDependencies": {
2323 "@hyperswarm/testnet": "^3.1.4"
24 },
25 "pgh": {
26 "bin": "favela-node",
27 "entrypoint": "serve.js",
28 "type": "daemon",
29 "runtime": "bare",
30 "autostart": true,
31 "depends": [
32 "pgh-sidecar"
33 ],
34 "readiness": {
35 "type": "pidfile",
36 "timeoutMs": 30000
37 },
38 "service": {
39 "requiresNetwork": true
40 },
41 "systemd": {
42 "user": true,
43 "enable": true,
44 "restart": "on-failure"
45 },
46 "defaults": {
47 "env": {
48 "FAVELA_NODE_CONFIG": "${HOME}/.config/favela-node/config.json"
49 }
50 }
2451 }
2552}
serve.js +50 -0
……@@ -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.
6import './lib/bare-globals.js'
7import process from 'bare-process'
8import fs from 'bare-fs'
9import Corestore from 'corestore'
10import Hyperswarm from 'hyperswarm'
11import { CommunityRoom } from 'favela-community'
12
13const HOME = process.env.HOME
14const expand = (p) => (typeof p === 'string' ? p.replace(/^~(?=\/)/, HOME).replace(/\$\{HOME\}/g, HOME) : p)
15const CONFIG = expand(process.env.FAVELA_NODE_CONFIG || (HOME + '/.config/favela-node/config.json'))
16
17if (!fs.existsSync(CONFIG)) { console.error('favela-node: no config at ' + CONFIG); process.exit(1) }
18const cfg = JSON.parse(fs.readFileSync(CONFIG, 'utf8'))
19const communities = Array.isArray(cfg.communities) ? cfg.communities : []
20if (!communities.length) { console.error('favela-node: no communities configured in ' + CONFIG); process.exit(1) }
21
22const swarm = new Hyperswarm()
23const served = []
24for (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).
37swarm.on('connection', (conn) => { conn.on('error', () => {}); for (const { room } of served) room.base.replicate(conn) })
38await swarm.flush()
39console.log(JSON.stringify({ msg: 'favela-node serving', communities: served.length }))
40
41let closing = false
42const 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}
49process.on('SIGINT', stop)
50process.on('SIGTERM', stop)