OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-proto-peer / bin / peer-migrate.js

57 lines · javascript ref:
// Migration daemon: adopt an EXISTING published DID + its signing key, import a repo CAR, and
// serve it self-custody over Hyperswarm (discoverable by DID). First run adopts + imports;
// later runs reload the vault. Used to move a custodial account (e.g. wj62/p2p.7rnx.net) onto
// a self-custody peer without changing the DID.
//
//   PEER_STORAGE=<dir> ADOPT_DID=did:plc:… ADOPT_DID_KEY=did:key:… ADOPT_DEVICE_PRIV_HEX=…
//   ADOPT_HANDLE=… ADOPT_PDS_URL=… ADOPT_CAR=<path-to-repo.car> node bin/peer-migrate.js

import Hyperswarm from 'hyperswarm'
import { existsSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
import { Peer } from '../lib/peer.js'
import { loadOrCreateSwarmKeyPair } from '../lib/swarm-keypair.js'
import { readCar, listRecords } from 'atproto-repo/lib/repo.js'

const storage = process.env.PEER_STORAGE
if (!storage) { console.error('PEER_STORAGE required'); process.exit(1) }

let peer
if (existsSync(join(storage, 'peer-identity.json'))) {
  peer = await Peer.load({ storage })
  console.log(JSON.stringify({ msg: 'reloaded adopted peer', did: peer.did }))
} else {
  peer = await Peer.adopt({
    storage,
    did: process.env.ADOPT_DID,
    handle: process.env.ADOPT_HANDLE,
    privateKey: process.env.ADOPT_DEVICE_PRIV_HEX,
    didKey: process.env.ADOPT_DID_KEY,
    pdsUrl: process.env.ADOPT_PDS_URL
  })
  if (process.env.ADOPT_CAR) {
    const car = await readCar(new Uint8Array(readFileSync(process.env.ADOPT_CAR)))
    const recs = await listRecords(car)
    const parsed = recs.map((r) => {
      if (r.collection && r.rkey != null) return { collection: r.collection, rkey: r.rkey, record: r.value }
      const [collection, ...rest] = String(r.key).split('/')
      return { collection, rkey: rest.join('/'), record: r.value }
    })
    await peer.importRecords(parsed)
    console.log(JSON.stringify({ msg: 'imported repo', count: parsed.length, collections: [...new Set(parsed.map((p) => p.collection))] }))
  }
}

const swarm = new Hyperswarm({ keyPair: loadOrCreateSwarmKeyPair(storage) })
const serving = peer.serve(swarm)
await serving.ready()
console.log(JSON.stringify({
  msg: 'adopted peer serving', did: peer.did, handle: peer.handle, didKey: peer.didKey,
  serverKey: serving.serverKey, discoveryTopic: serving.discoveryTopic,
  repoKey: peer.repoKeyHex, firehoseKey: peer.firehoseKeyHex, firehoseLen: peer.seq.length
}))

const shutdown = async () => { try { serving.close(); await swarm.destroy(); await peer.close() } catch {}; process.exit(0) }
process.on('SIGINT', shutdown)
process.on('SIGTERM', shutdown)