OverviewFilesCommitsIssuesDocsReleasesPeers

pgh / lib / bootstrap.js

81 lines · javascript ref:
/**
 * bootstrap — constants and helpers for pgh's initial-install flow.
 *
 * P.10 Phase 0.5i. The bootstrap is the bridge between "user has a
 * pgh binary" and "pgh has an identity + a tree store + seeded
 * content." It runs once; afterwards pgh is self-hosting.
 *
 * Bootstrap topic:
 *   Hardcoded discovery key = blake2b-256("pgh-bootstrap-v1"). Multiple
 *   seed nodes (tabertron, stortron, any other machine we run) join
 *   this topic and serve the current pgh-core tree + common dep trees.
 *   Clients joining the same topic find any online seed via the DHT —
 *   no single serverKey single-point-of-failure.
 *
 * The `pgh init` flow:
 *   1. Create ~/.config/pgh/ + ~/.local/share/store/ if missing
 *   2. Load or create a shared device identity via hyperident (~/.hyperident/)
 *   3. If --bootstrap <url> provided: connect to that peer
 *      else:                           join the hardcoded topic
 *   4. Pull pgh-core tree into store/dependencies/ (when full network
 *      fetcher exists — currently stubbed; 0.5i ships the local-only path)
 *   5. Install pgh-core into store/apps/pgh
 */

import crypto from 'hypercore-crypto'

export const BOOTSTRAP_VERSION = 'pgh-bootstrap-v1'
export const BOOTSTRAP_TOPIC = crypto.hash(Buffer.from(BOOTSTRAP_VERSION))

/**
 * NameRegistry autobases a fresh sidecar replicates read-only by default.
 *
 * Name resolution is SUBSCRIPTION-based, not global-singleton: `resolve-name`
 * searches the sidecar's own registry first, then each subscribed replica
 * (pgh-sidecar `_resolveNameAcross`). Anyone can run their own namespace
 * registry and publish its key; this list is only the default one shipped
 * with pgh, so `pgh app install <name>` resolves out of the box instead of
 * failing against a fresh peer's own empty registry.
 *
 * Read-only — subscribing does NOT let this peer claim names here, and it
 * does not stop them claiming names in their own registry.
 *
 * Overridden by an explicit `nameRegistries` in ~/.config/pgh/subscriptions.json
 * or PGH_NAME_REGISTRIES; those win outright rather than merging, so a peer
 * that wants a different namespace set never has this one forced on it.
 */
export const DEFAULT_NAME_REGISTRIES = [
  '3c702c3a6767fa507523caed7b992bc9b0bc38638bd4ca0d3a7f79152e68e9d1'
]

/**
 * Resolve a user-supplied bootstrap spec into { kind, value }.
 *
 * Accepted forms:
 *   pgh://<publisher>/...            — identity-based (future)
 *   punch://<serverKey>/...          — raw serverKey (identity-less)
 *   topic:<hex>                      — custom discovery topic
 *   undefined                        — default hardcoded topic
 */
export function parseBootstrap (spec) {
  if (!spec) {
    return { kind: 'topic', value: BOOTSTRAP_TOPIC, label: BOOTSTRAP_VERSION }
  }
  if (spec.startsWith('punch://')) {
    const rest = spec.slice('punch://'.length)
    const serverKey = rest.split('/')[0]
    return { kind: 'punch', value: serverKey, label: 'punch://' + serverKey.slice(0, 12) + '...' }
  }
  if (spec.startsWith('pgh://')) {
    const rest = spec.slice('pgh://'.length)
    const publisher = rest.split('/')[0]
    return { kind: 'pgh', value: publisher, label: 'pgh://' + publisher }
  }
  if (spec.startsWith('topic:')) {
    const hex = spec.slice('topic:'.length)
    if (hex.length !== 64) throw new Error('bootstrap topic must be 64-char hex')
    return { kind: 'topic', value: Buffer.from(hex, 'hex'), label: 'topic:' + hex.slice(0, 12) + '...' }
  }
  throw new Error('bootstrap spec must be pgh://, punch://, topic:<hex>, or empty (uses default)')
}