// rebuild-did — NOTARY REVIVAL. Export a hosted DID's authoritative repo CAR from the RUNNING daemon so
// it can be REBUILT fresh as the genesis creator/notary (forceCreator + adoptCar) in isolated storage.
//
// Why a rebuild (not an in-place fix): after the poisoned creator abandons the deterministic genesis via a
// resync (forceNonCreator), the base has NO live indexer — indexedLength freezes, new writes never
// finalize. A healthy co-owner revives the notary by RE-AUTHORING the genesis from the authoritative
// snapshot. COEXISTENCE RULE (proven in test/rebuild-2node-transition.test.js): the re-authored genesis
// has the SAME deterministic key but DIFFERENT content than the old one, so if the OLD base is still on
// the swarm they COLLIDE (conflicts, finalization blocked). Therefore the rebuild MUST run with the old
// base fully OFF the swarm and in FRESH storage — this tool EXPORTS the snapshot + prints that sequence;
// it deliberately does NOT mutate the live base in place.
import fs from 'bare-fs'
import { join } from 'bare-path'
import crypto from 'hypercore-crypto'
import b4a from 'b4a'
import { Client } from 'hyper-svc'
import { loadConfig } from './config.js'
const NS = (s) => b4a.from('atproto-base/v1/' + s)
const did16For = (did) => b4a.toString(crypto.hash([NS('room-key'), b4a.from(did)]), 'hex').slice(0, 16)
// args = the CLI args AFTER the subcommand, e.g. ['did:plc:...', '--storage', '/path']. Returns exit code.
export async function rebuildDidCommand (args = []) {
const did = args.find((a) => a.startsWith('did:'))
const sIdx = args.indexOf('--storage')
const cfg = loadConfig()
const storage = sIdx >= 0 ? args[sIdx + 1] : cfg.storage
if (!did) {
console.error('usage: hyper-proto-peer rebuild-did <did> [--storage <path>]')
console.error(' Exports the DID\'s authoritative CAR (via the running daemon) for a fresh notary-revival rebuild.')
console.error(' hosted DIDs:', JSON.stringify((cfg.dids || []).map((d) => d.did)))
return 1
}
// Resolve the daemon's LOCAL control socket for this DID (primary DID = flat; else per-DID sub-path).
const entry = (cfg.dids || []).find((d) => d.did === did)
const primary = !!(entry && entry.primary)
const sock = primary ? join(storage, 'control.sock') : join(storage, 'dids', did16For(did), 'control.sock')
if (!fs.existsSync(sock)) {
console.error('rebuild-did: control socket not found at ' + sock)
console.error(' Is the daemon running and hosting ' + did + '? (rebuild-did reads the LIVE authoritative state.)')
return 1
}
// Pull the authoritative CAR over the local UDS (owner-gated by the filesystem).
const client = await Client.connectUds(sock)
let car
try { car = await client.call('get-repo-car', {}) } catch (e) { console.error('rebuild-did: get-repo-car failed: ' + e.message); try { await client.close() } catch {} ; return 1 } finally { try { await client.close() } catch {} }
if (!car || !car.car) { console.error('rebuild-did: daemon returned no CAR for ' + did); return 1 }
const outDir = join(storage, 'rebuild')
fs.mkdirSync(outDir, { recursive: true })
const outPath = join(outDir, did16For(did) + '.car')
fs.writeFileSync(outPath, b4a.from(car.car, 'hex'))
console.log(JSON.stringify({ msg: 'rebuild-did: exported authoritative CAR', did, rev: car.rev, records: car.recordCount, car: outPath }))
console.log('')
console.log('NOTARY REVIVAL — the OLD base MUST be off the swarm before the rebuilt one is served:')
console.log(' 1. RETIRE the old base: on EVERY node currently hosting ' + did + ', remove it from that')
console.log(' daemon\'s config `dids` and restart. Old cores stay on disk (nothing deleted).')
console.log(' 2. RE-ONBOARD this node as the notary in FRESH storage (a daemon that no longer holds the')
console.log(' old cores). Add a dids entry for ' + did + ' with:')
console.log(' "forceCreator": true,')
console.log(' "adoptCar": "' + outPath + '"')
console.log(' 3. START it — it becomes the live notary; new posts FINALIZE. Bridge re-fronts + relay re-crawls.')
console.log(' (Add the retired node back later as a FRESH device for HA — do not rejoin its old base.)')
return 0
}