// loser-club hpp — boot + serve the loser-club community DID's repo (fronted by the membrane over its DHT
// discovery topic) and write its app.bsky.feed.generator record. Reopens the StateBaseV3 minted by
// favela-node (key derivation is identical). Run persistently: bare scripts/serve-loser-club.mjs
import '../lib/bare-globals.js'
import process from 'bare-process'
import fs from 'bare-fs'
import b4a from 'b4a'
import { bootSwarmDevice } from '../lib/device-node.js'
import { deviceKeetKeyPair, attestDeviceChain, rootIdentityPublicKey } from 'atproto-room/lib/membership.js'
const HOME = process.env.HOME
const SECRETS = HOME + '/.config/loser-club/secrets.json'
// Store path — env-overridable so a clean relaunch can use a fresh path (the DID + genesis are fixed on
// PLC and repo content is re-authorable). Hard-kills of the bare worker corrupted earlier stores.
const REPO = process.env.LOSER_CLUB_REPO || (HOME + '/.config/loser-club/repo-live')
const MEMBRANE = 'https://pds.7rnx.net'
const s = JSON.parse(fs.readFileSync(SECRETS, 'utf8'))
const founderKp = await deviceKeetKeyPair(s.founderDeviceMnemonic)
const deviceProof = b4a.toString(b4a.from(await attestDeviceChain({ rootMnemonic: s.rootMnemonic, devicePublicKey: founderKp.publicKey })), 'hex')
const rootIdentityKey = b4a.toString(await rootIdentityPublicKey(s.rootMnemonic), 'hex')
const members = [{ key: b4a.toString(founderKp.publicKey, 'hex'), role: 1 }]
console.log('[loser-club] booting hpp for', s.did, '…')
const node = await bootSwarmDevice({
storage: REPO,
deviceMnemonic: s.founderDeviceMnemonic,
did: s.did,
signingKey: s.signingKey,
deviceProof,
rootIdentityKey,
handle: s.handle,
pdsUrl: MEMBRANE,
members,
swarm: true
})
// Write the feed generator record (the bsky-facing feed) if not already present, then sign.
let existing = null
try { existing = await node.device.repo.getRecord('app.bsky.feed.generator', 'loser-club') } catch {}
if (!existing) {
await node.device.putRecord('app.bsky.feed.generator', 'loser-club', {
$type: 'app.bsky.feed.generator',
did: s.did,
displayName: 'loser-club',
description: 'the loser-club — posts from p2p.7rnx.net + ashatron.7rnx.net',
createdAt: '2026-07-15T12:00:00.000Z'
})
const h = await node.device.sign({ force: true })
console.log('[loser-club] wrote app.bsky.feed.generator/loser-club (rev', h ? h.rev : '?', ')')
} else {
console.log('[loser-club] feed.generator record already present')
}
// Federation via HEARTBEAT (not restart) — the durable fix. A subscribed non-archival relay only indexes
// LIVE #commits (bridge POSTMORTEM), so emit a fresh marker commit now and every few minutes WHILE running.
// No restart-to-federate → no corestore lock contention. A non-bsky collection so it never pollutes the feed.
const FPING = HOME + '/.config/loser-club/fedping'
let n = 0
try { n = Number(fs.readFileSync(FPING, 'utf8')) || 0 } catch {}
async function heartbeat () {
try {
n++
try { fs.writeFileSync(FPING, String(n)) } catch {}
await node.device.putRecord('net.favela.federation', 'self', { $type: 'net.favela.federation', seq: n })
const h = await node.device.sign({ force: true })
console.log('[loser-club] federation heartbeat #' + n + ' (rev ' + (h ? h.rev : '?') + ')')
} catch (e) { console.error('[loser-club] heartbeat failed:', e.message) }
}
await heartbeat() // emit one now
const hb = setInterval(heartbeat, 4 * 60 * 1000) // and every 4 min while running
if (hb && typeof hb.unref === 'function') hb.unref()
console.log('[loser-club] serving', s.did, '— fronted + heartbeating for federation. (Ctrl-C to stop)')
const stop = async () => { try { await node.close() } catch {} process.exit(0) }
process.on('SIGINT', stop)
process.on('SIGTERM', stop)