| … | … | @@ -36,7 +36,7 @@ import { resolveVerifyKey } from '../lib/plc.js' |
| 36 | 36 | import net from 'bare-net' |
| 37 | 37 | import { PlcDirectory } from 'atproto-identity/lib/plc-directory.js' |
| 38 | 38 | import { createHandleResolver } from '../lib/handles.js' |
| 39 | | import { createTcpResolveTxt, parseResolvConf } from '../lib/dns-txt.js' |
| 39 | import { createTcpResolveTxt, chooseNameservers } from '../lib/dns-txt.js' |
| 40 | 40 | import { createFollowerLens } from '../lib/followers.js' |
| 41 | 41 | import { createApFollowers } from '../lib/ap-followers.js' |
| 42 | 42 | import { createConstellationClient } from '../lib/constellation.js' |
| … | … | @@ -295,15 +295,25 @@ async function getRecordStore (did) { |
| 295 | 295 | // Same resolver as before: whatever /etc/resolv.conf points at, which is what c-ares defaulted to. No |
| 296 | 296 | // third-party DoH, because handle verification is the last place to hand a stranger the list of every |
| 297 | 297 | // identity we look up. |
| 298 | | const nameservers = (() => { |
| 299 | | try { |
| 300 | | return parseResolvConf(fs.readFileSync('/etc/resolv.conf', 'utf8')) |
| 301 | | } catch { |
| 302 | | return [] |
| 303 | | } |
| 304 | | })() |
| 298 | // |
| 299 | // FAVELA_NAMESERVERS is for the one environment that HAS a resolver and no file naming it: Android, |
| 300 | // where netd owns resolution, there is no /etc/resolv.conf, and `net.dns1` has been empty since Android |
| 301 | // 8 (confirmed on a handset 2026-08-24). The embedder passes the OS's own servers for the active network |
| 302 | // — favela-android reads them from ConnectivityManager — so this is still the network's resolver, it |
| 303 | // just arrives by a different route, and the objection above to third-party resolvers is untouched. |
| 304 | // |
| 305 | // Without it the phone logs "verification is OFF" and every custom-domain handle renders |
| 306 | // handle.invalid, which by the measurement above is most of the interesting identities on the network. |
| 307 | // An unusable env value falls through to the file rather than shadowing it: exporting the variable |
| 308 | // wrong must not silently turn verification off on a server. |
| 309 | const { servers: nameservers, source: nsSource } = chooseNameservers({ |
| 310 | envValue: process.env.FAVELA_NAMESERVERS, |
| 311 | resolvConf: (() => { try { return fs.readFileSync('/etc/resolv.conf', 'utf8') } catch { return null } })() |
| 312 | }) |
| 305 | 313 | if (nameservers.length === 0) { |
| 306 | | console.log('[favela] no nameserver in /etc/resolv.conf — DNS TXT handle verification is OFF') |
| 314 | console.log('[favela] no nameserver (FAVELA_NAMESERVERS unset, none in /etc/resolv.conf) — DNS TXT handle verification is OFF') |
| 315 | } else { |
| 316 | console.log('[favela] DNS TXT handle verification via ' + nameservers.length + ' nameserver(s) from ' + nsSource) |
| 307 | 317 | } |
| 308 | 318 | const handleResolver = createHandleResolver({ |
| 309 | 319 | plc: new PlcDirectory({ fetchImpl: bareFetch }), |
| … | … | @@ -1372,7 +1382,15 @@ const server = http.createServer(async (req, res) => { |
| 1372 | 1382 | // (pair / revoke) are progressive enhancement, matching how the avatar section already works. |
| 1373 | 1383 | let devices = [] |
| 1374 | 1384 | if (viewer) { try { devices = await writer.listDevices() } catch { devices = [] } } |
| 1375 | | return html(res, 200, settingsPage({ viewer, profile, did, devices, theme: res._theme })) |
| 1385 | // App updates, on a handset only. FAVELA_SHELL_STATE is set by favela-android's worklet and by |
| 1386 | // nothing else, so a server never renders the section — `appUpdateSection(null)` is ''. Read per |
| 1387 | // request rather than at boot: a check performed while this page is open must be visible on the |
| 1388 | // next render, and the file is four fields. |
| 1389 | let shell = null |
| 1390 | if (process.env.FAVELA_SHELL_STATE) { |
| 1391 | try { shell = JSON.parse(fs.readFileSync(process.env.FAVELA_SHELL_STATE, 'utf8')) } catch { shell = null } |
| 1392 | } |
| 1393 | return html(res, 200, settingsPage({ viewer, profile, did, devices, theme: res._theme, shell })) |
| 1376 | 1394 | } |
| 1377 | 1395 | if (path === '/login') { |
| 1378 | 1396 | // Sign-in UI: the client script (/static/login.js) generates the browser's secp256k1 sub-key, |