OverviewFilesCommitsIssuesDocsReleasesPeers

favela-web commits / a1007b0

0.3.58: unconfigured must mean inert — stop shipping the author's fleet as defaults
A productization audit of the install path found that a stranger installing favela-web inherited this fleet's identity as DEFAULTS, silently: - package.json defaults.env shipped FAVELA_CONTROL_KEY = e0cac0d0… — the LIVE wj62 hpp control key. A fresh install dialled this fleet's production node over the DHT for every write. - lib/cli.js and bin/favela-pair.js hardcoded the same key as a fallback, so `favela-web pair` on a box with no local socket did the same. lib/writer.js:56-62 already records this exact accident class: "a post landed in wj62 from a test run". - bin/favela-web.js defaulted FAVELA_ROSTER to did:plc:wj62… + did:plc:e2jev…, so a stranger indexed SOMEONE ELSE'S timeline and nothing errored to say so. All four now default to empty. Unconfigured means inert: no roster = index nothing, no transport = refuse every write with a legible reason. That is the rule writeTransportConfigured already argued for ("an instance that cannot name the hpp it writes to must not guess; there is no default") — the manifest and two CLIs simply violated it. Both fleet nodes were pinned explicitly BEFORE this change (FAVELA_ROSTER + FAVELA_HPP_SOCK in each host's apps.json), so behaviour is unchanged for them; tabertron additionally moves from the swarm key to its co-located UDS socket, which is strictly better anyway. README rewritten: it documented 4 of ~38 env vars and no install line. Now carries the pgh install command, the must-set-vs-optional split, the read/write gates as they actually are (default-deny write gate, repo-ownership requirement, opt-in membership, visibility flag) and read-only lens mode, which is a production posture that appeared nowhere. Verified: full node suite green; a node booted with NO config renders 200, indexes 0 DIDs and reports no write transport — inert rather than adopting someone else's fleet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
a1007b07f964139d18e72cb1cda570f61e91f04b
by plankatron <fleeky@gmail.com> · 2026-08-16T22:06:56.000Z

Changed files (5) +80 -17

README.md +65 -9
……@@ -21,29 +21,85 @@ JSON APIs — the "P2P-less window" into the P2P world.
2121 - a **labeler** (`lib/labeler-export.js` + `lib/label-firehose.js`) — `com.atproto.label` `queryLabels`
2222 (pull) + `subscribeLabels` (WS firehose).
2323
24## Run
24## Install
2525
2626```sh
27bare bin/favela-web.js # FAVELA_PORT=4041 by default
28npm test # node suite; npm run test:ws for the bare-ws firehose test
27pgh app install pgh://1dd36bdfa9cb7f7d84adbd07f017e7200f7f90a3a546cd0761fac2a1f8a13e86/favela-web --name favela-web
2928```
3029
31Deployed as a pgh app (`pgh.type: daemon`, `runtime: bare`); the [atproto-bridge](../atproto-bridge)
32membrane proxies the lens-export XRPC to it (`FAVELA_WEB_UPSTREAM`).
30Then set the two things that have **no default** (see Config), and start it:
31
32```sh
33pgh app start favela-web # http://localhost:4041
34```
35
36⚠ **Unconfigured means inert, on purpose.** With no `FAVELA_ROSTER` this node indexes nothing, and with
37no write transport it refuses every write. Earlier versions defaulted both to the author's own fleet,
38so a fresh install silently indexed someone else's timeline and aimed its writes at their production
39node. Empty is the correct unconfigured state; the errors tell you what to set.
3340
3441## Config (env)
3542
43Overrides go in `~/.config/pgh/apps.json` under `favela-web.env` (there is no CLI for this yet).
44
45**Must be set — no safe default exists:**
46
3647| Var | Meaning |
3748|---|---|
38| `FAVELA_PORT` | HTTP port (default 4041) |
39| `FAVELA_COMMUNITY_ROOMS` | `id=roomKeyHex,…` — community rooms to serve (roster source) |
40| `FAVELA_COMMUNITY_NAMES` | `name,…` — communities resolved to roomKeys over the DHT |
41| `FAVELA_LABELER_SEED` | enable favela's own atproto labeler (interim; community-DID labeler supersedes) |
49| `FAVELA_ROSTER` | `did:plc:…,…` — whose repos this node reads. Empty = index nothing. |
50| `FAVELA_HPP_SOCK` | Path to a **co-located** hpp control socket. The write transport. |
51| `FAVELA_CONTROL_KEY` | Alternative to the above: a **remote** hpp's control key, reached over the swarm. Set exactly one; setting neither makes the node read-only. |
52
53**Usually right, override per host:**
54
55| Var | Default | Meaning |
56|---|---|---|
57| `FAVELA_PORT` | `4041` | HTTP port |
58| `FAVELA_HOST` | `0.0.0.0` (manifest) / `127.0.0.1` (code) | ⚠ a hand-run `bare bin/favela-web.js` binds loopback |
59| `ATPROTO_OVERLAY_ROOT` | hpp's `data/atproto-repo` | shared record byte store; wrong path silently downgrades every DID to firehose backfill |
60| `FAVELA_STORE_ROOT` | `~/.config/favela-web/data` | appview corestore — keep it OUTSIDE the install tree or `pgh app update` wipes it |
61| `FAVELA_EXTERNAL_URL` | — | public URL published in did:web / feed records |
62| `FAVELA_COMMUNITY_ROOMS` | — | `id=roomKeyHex,…` community rooms to serve (roster source) |
63| `FAVELA_COMMUNITY_NAMES` | — | `name,…` communities resolved to roomKeys over the DHT |
64| `FAVELA_REQUIRE_MEMBERSHIP` | off | `1` = writers must also be on a community roster |
65| `FAVELA_LABELER_SEED` | off | enable favela's own atproto labeler (interim) |
66
67`bin/favela-web.js` reads ~38 env vars in total; the rest are tuning (`FAVELA_SPACEDUST`,
68`FAVELA_RETENTION_BUDGET`, `FAVELA_AP_BRIDGE`, …) and are safe to ignore.
69
70## Who may read and write
71
72Authentication is not authorisation. A session proves only that the browser holds a sub-key the DID
73delegated — any atproto account can prove that about itself.
74
75- **Reads** are public by default. A community closes its site with `set-meta visibility members`; the
76 sign-in path, static assets and `/xrpc/*` stay reachable regardless (`lib/gate-policy.js`).
77 ⚠ `/xrpc/*` must stay open: bsky's AppView reads the lens anonymously.
78- **Writes** pass one default-deny gate (`lib/check.js`). A new POST route is treated as a repo write
79 until it is deliberately classified otherwise.
80 - repo writes require **owning the repo** — `viewer.did` must equal the DID this node's hpp signs for;
81 - self-scoped routes (devices, bookmarks, mutes) require only a live, non-revoked session;
82 - membership is an additional opt-in gate (`FAVELA_REQUIRE_MEMBERSHIP=1`).
83
84**Read-only lens mode** — configure no write transport and the node serves feeds and refuses every write
85with `this instance is read-only`. This is a first-class deployment posture: it is how a publicly
86reachable instance can front the lens without exposing an authoring path.
87
88## Run (development)
89
90```sh
91bare bin/favela-web.js # FAVELA_PORT=4041 by default
92npm test # node suite; npm run test:ws for the bare-ws firehose test
93```
94
95Deployed as a pgh app (`pgh.type: daemon`, `runtime: bare`); the [atproto-bridge](../atproto-bridge)
96membrane proxies the lens-export XRPC to it (`FAVELA_WEB_UPSTREAM`).
4297
4398## Read
4499
45100- `DESIGN.md` — the full design (per-peer view, POSSE-inverted, node topology, §3.6 "favela is its own lens").
46101- `spec/lens-export.md` — communities as bsky feeds + labelers, the co-owned-DID pipeline.
102- `spec/mobile-node.md` — the phone as a full node (measured APK/runtime costs, Android floors).
47103
48104See also: [favela-community](../favela-community) (the keyless community rooms) and
49105[favela-node](../favela-node) (the co-owned-DID full-node).
bin/favela-pair.js +4 -1
……@@ -11,7 +11,10 @@ import process from 'bare-process'
1111import { Writer } from '../lib/writer.js'
1212import { createPairInvite } from '../lib/pairing.js'
1313
14const CONTROL = process.env.FAVELA_CONTROL_KEY || 'e0cac0d06e67da9c79674a5ca195809300b04635d1a46254f4fd21edd1ce491d'
14// ⛔ NO DEFAULT. This fell back to the author's LIVE wj62 hpp control key, so running this on a box
15// with no local socket dialled production over the DHT — the accident lib/writer.js:56-62 already
16// records ('a post landed in wj62 from a test run'). Unset now means unreachable, which is correct.
17const CONTROL = (process.env.FAVELA_CONTROL_KEY || '').trim() || null
1518// DEDICATED pairing writer identity — MUST differ from favela-web's poster seed
1619// (lib/writer.js WRITE_SEED). Both derive a Hyperswarm keypair from the seed; if they
1720// matched, favela-web's persistent poster and this CLI would claim the SAME swarm identity
bin/favela-web.js +5 -3
……@@ -143,9 +143,11 @@ const lensReader = LENSES_ON ? createLensReader({ doFetch: bareFetch }) : null
143143console.log(JSON.stringify({ msg: 'lens import', enabled: LENSES_ON, lenses: LENSES.map((l) => l.name) }))
144144
145145const LIST = (process.env.FAVELA_LIST || '').trim()
146const ROSTER = (process.env.FAVELA_ROSTER ||
147 'did:plc:wj62ebbusln6gxo5azo6ogzo,did:plc:e2jevhuurrhlm36ufk7fklpg'
148).split(',').map((s) => s.trim()).filter(Boolean)
146// ⛔ NO FLEET DEFAULT. This used to fall back to the author's own DIDs, so a stranger who installed
147// favela-web silently indexed SOMEONE ELSE'S timeline and nothing errored to say so. Unconfigured
148// means EMPTY — the node indexes nothing until told whose repos it serves. Same rule the write
149// transport already follows (lib/writer.js: an instance that cannot name its hpp must not guess).
150const ROSTER = (process.env.FAVELA_ROSTER || '').split(',').map((s) => s.trim()).filter(Boolean)
149151// Optional verifyKey overrides (FAVELA_VERIFYKEYS="did=hex,did=hex") for DIDs not resolvable via the
150152// PLC directory — local/offline/test peers, or a roster owner whose DID isn't PLC-published. Members
151153// still resolve via PLC unless overridden.
lib/cli.js +4 -1
……@@ -31,7 +31,10 @@ import { roomKey16 } from './blob-serve.js'
3131import { repairStrongRefs } from './repair-strongrefs.js'
3232import { checkConformance } from './conformance.js'
3333
34const CONTROL = process.env.FAVELA_CONTROL_KEY || 'e0cac0d06e67da9c79674a5ca195809300b04635d1a46254f4fd21edd1ce491d'
34// ⛔ NO DEFAULT. This fell back to the author's LIVE wj62 hpp control key, so running this on a box
35// with no local socket dialled production over the DHT — the accident lib/writer.js:56-62 already
36// records ('a post landed in wj62 from a test run'). Unset now means unreachable, which is correct.
37const CONTROL = (process.env.FAVELA_CONTROL_KEY || '').trim() || null
3538const CLI_SEED = process.env.FAVELA_PAIR_SEED || 'favela-pair-tabertron-2026-07-16' // dedicated, allowlisted (277037…)
3639const EXTERNAL = (process.env.FAVELA_EXTERNAL_URL || 'http://localhost:4041').replace(/\/$/, '')
3740
package.json +2 -3
……@@ -1,6 +1,6 @@
11{
22 "name": "favela-web",
3 "version": "0.3.57",
3 "version": "0.3.58",
44 "description": "favela-web \u2014 the web/SSR surface of Favela, the native P2P-local, community-owned social client + appview over the self-custody atproto stack (hyper-proto-peer). One of the Favela surfaces (alongside favela-cli and favela-native). bsky is an optional POSSE export, not the home. See DESIGN.md.",
55 "type": "module",
66 "main": "bin/favela-web.js",
……@@ -32,8 +32,7 @@
3232 "FAVELA_STORE_ROOT": "${HOME}/.config/favela-web/data",
3333 "FAVELA_SPACEDUST": "1",
3434 "FAVELA_RETENTION_BUDGET": "268435456",
35 "FAVELA_AP_BRIDGE": "https://ap.7rnx.net",
36 "FAVELA_CONTROL_KEY": "e0cac0d06e67da9c79674a5ca195809300b04635d1a46254f4fd21edd1ce491d"
35 "FAVELA_AP_BRIDGE": "https://ap.7rnx.net"
3736 }
3837 },
3938 "readiness": {