/** * Hyperdrive Management System: registry on personal drive, extra drives + swarm, * Autopass for invite/pair. Use a static ESM import (not createRequire) so Pear can * trace and stage `autopass`; bare-module require() from pear:// URLs does not resolve node_modules. */ import Autopass from 'autopass' import b4a from 'b4a' import idEnc from 'hypercore-id-encoding' export const HDMS_REGISTRY_PATH = '/.bare/hdms/registry.json' /** @param {string} label */ export function assertValidHdmsLabel(label) { if (!label || typeof label !== 'string') throw new Error('Invalid label') if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}$/.test(label)) { throw new Error( 'Label must start with alphanumeric; allowed: . _ - (max 63 chars)' ) } } function randomNsSuffix() { return b4a.toString(b4a.alloc(8), 'hex') } /** * @typedef {{ id: string, label: string, mode: 'writable' | 'readonly', key?: string, ns?: string }} HdmsRegistryEntry * @typedef {{ version: 1, drives: HdmsRegistryEntry[] }} HdmsRegistryFile */ /** * @param {import('hyperdrive').default} personalDrive * @returns {Promise} */ export async function loadHdmsRegistry(personalDrive) { const buf = await personalDrive.get(HDMS_REGISTRY_PATH, { follow: true }) if (!buf || buf.length === 0) { return { version: 1, drives: [] } } try { const j = JSON.parse(b4a.toString(buf)) if (j && j.version === 1 && Array.isArray(j.drives)) return j } catch { /* fallthrough */ } return { version: 1, drives: [] } } /** * @param {import('hyperdrive').default} personalDrive * @param {HdmsRegistryFile} data */ export async function saveHdmsRegistry(personalDrive, data) { const json = JSON.stringify(data, null, 0) await personalDrive.put(HDMS_REGISTRY_PATH, b4a.from(json)) } export class HdmsController { constructor() { /** @type {import('corestore').default | null} */ this.store = null /** @type {import('hyperswarm').default | null} */ this.swarm = null /** @type {typeof import('hyperdrive').default | null} */ this.Hyperdrive = null /** @type {import('hyperdrive').default | null} */ this.personalDrive = null /** @type {import('./swarm-disk.js').SwarmDisk | null} */ this.disk = null /** @type {{ getMounts: () => Map } | null} */ this.vfsMountRef = null /** @type {string[] | null} */ this.bootstrap = null /** @type {HdmsRegistryFile | null} */ this.registry = null /** @type {Map} */ this.byLabel = new Map() /** @type {import('autopass') | null} */ this.autopass = null this.active = false } /** * @param {{ * store: import('corestore').default, * swarm: import('hyperswarm').default, * Hyperdrive: typeof import('hyperdrive').default, * personalDrive: import('hyperdrive').default, * disk: import('./swarm-disk.js').SwarmDisk, * vfsMountRef: { getMounts: () => Map }, * bootstrap?: string[] | null * }} opts */ async activate(opts) { if (this.active) await this.deactivate() this.store = opts.store this.swarm = opts.swarm this.Hyperdrive = opts.Hyperdrive this.personalDrive = opts.personalDrive this.disk = opts.disk this.vfsMountRef = opts.vfsMountRef const b = opts.bootstrap this.bootstrap = Array.isArray(b) && b.length ? b : parseBootstrapEnv() ?? null this.registry = await loadHdmsRegistry(this.personalDrive) this.byLabel.clear() this.disk.auxiliaryDrives = [] for (const entry of this.registry.drives) { try { await this._openEntry(entry) } catch (err) { console.warn('[hdms] skip drive ' + entry.label + ':', err?.message || err) } } this.vfsMountRef.getMounts = () => this.getMountMap() this.active = true } async deactivate() { if (this.autopass) { try { await this.autopass.close() } catch (_) {} this.autopass = null } for (const { drive, entry } of this.byLabel.values()) { try { if (this.swarm && drive.discoveryKey) { this.swarm.leave(drive.discoveryKey) } } catch (_) {} try { await drive.close() } catch (_) {} } this.byLabel.clear() if (this.disk) this.disk.auxiliaryDrives = [] if (this.vfsMountRef) { this.vfsMountRef.getMounts = () => new Map() } this.registry = null this.active = false } /** * @param {HdmsRegistryEntry} entry */ async _openEntry(entry) { const Hyperdrive = this.Hyperdrive const store = this.store const swarm = this.swarm if (!Hyperdrive || !store || !swarm) throw new Error('HDMS not configured') let drive if (entry.mode === 'writable' && entry.ns) { const ns = store.namespace(entry.ns, { writable: true }) drive = new Hyperdrive(ns) } else if (entry.mode === 'readonly' && entry.key) { const key = idEnc.decode(entry.key) drive = new Hyperdrive(store, key) } else { throw new Error('Bad registry entry') } await drive.ready() swarm.join(drive.discoveryKey) const done = drive.findingPeers() swarm.flush().then(done, done) if (this.disk) { this.disk.auxiliaryDrives.push(drive) } this.byLabel.set(entry.label, { drive, entry, writable: entry.mode === 'writable' }) for (const peer of this.disk?.peers || []) { try { drive.replicate(peer.mux.stream, { live: true, download: true }) } catch (_) {} } } getMountMap() { /** @type {Map} */ const m = new Map() for (const [label, x] of this.byLabel) { m.set(label, { drive: x.drive, writable: x.writable }) } return m } /** * @param {Record} ctx */ assertLoggedIn(ctx) { if (ctx.identity?.state !== 'unlocked') { throw new Error('Log in to use Hyperdrive management (hdms)') } if (!this.active) throw new Error('HDMS inactive') } /** * @param {Record} ctx */ async list(ctx) { this.assertLoggedIn(ctx) const reg = this.registry if (!reg || !reg.drives.length) { ctx.console.log('(no extra drives)') return } for (const d of reg.drives) { const k = d.key || '(local)' ctx.console.log(`${d.label}\t${d.mode}\t${k}`) } } /** * @param {Record} ctx * @param {string} label */ async create(ctx, label) { this.assertLoggedIn(ctx) assertValidHdmsLabel(label) if (this.byLabel.has(label)) throw new Error('Label already exists') const id = randomNsSuffix() const ns = `bare-os-hdms-w-${id}` const nsStore = this.store.namespace(ns, { writable: true }) const drive = new this.Hyperdrive(nsStore) await drive.ready() const keyStr = idEnc.encode(drive.key) /** @type {HdmsRegistryEntry} */ const entry = { id, label, mode: 'writable', ns, key: keyStr } this.registry.drives.push(entry) await saveHdmsRegistry(this.personalDrive, this.registry) await this._openEntry(entry) ctx.console.log(`Created ${label} key=${keyStr}`) } /** * @param {Record} ctx * @param {string} label * @param {string} keyZ32 */ async addReadonly(ctx, label, keyZ32) { this.assertLoggedIn(ctx) assertValidHdmsLabel(label) if (this.byLabel.has(label)) throw new Error('Label already exists') idEnc.decode(keyZ32) const id = randomNsSuffix() /** @type {HdmsRegistryEntry} */ const entry = { id, label, mode: 'readonly', key: keyZ32 } this.registry.drives.push(entry) await saveHdmsRegistry(this.personalDrive, this.registry) await this._openEntry(entry) ctx.console.log(`Added readonly ${label}`) } /** * @param {Record} ctx * @param {string} label */ async remove(ctx, label) { this.assertLoggedIn(ctx) const idx = this.registry.drives.findIndex((d) => d.label === label) if (idx < 0) throw new Error('Unknown label: ' + label) const entry = this.registry.drives[idx] const open = this.byLabel.get(label) if (open) { try { if (this.swarm && open.drive.discoveryKey) { this.swarm.leave(open.drive.discoveryKey) } } catch (_) {} try { await open.drive.close() } catch (_) {} if (this.disk?.auxiliaryDrives) { this.disk.auxiliaryDrives = this.disk.auxiliaryDrives.filter( (d) => d !== open.drive ) } this.byLabel.delete(label) } this.registry.drives.splice(idx, 1) await saveHdmsRegistry(this.personalDrive, this.registry) ctx.console.log('Removed ' + label) } /** * @param {Record} ctx * @param {string} label */ async show(ctx, label) { this.assertLoggedIn(ctx) const e = this.registry.drives.find((d) => d.label === label) if (!e) throw new Error('Unknown label: ' + label) ctx.console.log(JSON.stringify(e, null, 2)) } /** * @param {Record} ctx * @param {boolean} readOnly */ async invite(ctx, readOnly) { this.assertLoggedIn(ctx) await this._ensureAutopass() const inv = await this.autopass.createInvite({ readOnly }) ctx.console.log(inv) } /** * @param {Record} ctx * @param {string} inviteZ32 */ async pair(ctx, inviteZ32) { this.assertLoggedIn(ctx) if (!inviteZ32 || typeof inviteZ32 !== 'string') { throw new Error('Usage: hdms pair ') } const pairNs = this.store.namespace('bare-os-hdms-pair-' + randomNsSuffix(), { writable: true }) const pairer = Autopass.pair(pairNs, inviteZ32.trim(), { bootstrap: this.bootstrap }) const pass = await pairer.finished() await pass.ready() try { const wk = pass.writerKey ctx.console.log( 'Paired Autopass. writerKey=' + (wk ? b4a.toString(wk, 'hex').slice(0, 16) + '…' : '?') ) } finally { try { await pass.close() } catch (_) {} } } async _ensureAutopass() { if (this.autopass) return const ns = this.store.namespace('bare-os-hdms-autopass', { writable: true }) this.autopass = new Autopass(ns, { swarm: this.swarm, replicate: true, bootstrap: this.bootstrap }) await this.autopass.ready() attachAutopassReplicateOnce(this.swarm, () => this.autopass?.base) } } /** * @param {import('hyperswarm').default} swarm * @param {() => unknown} getBase */ function attachAutopassReplicateOnce(swarm, getBase) { const s = /** @type {{ _bareOsHdmsAutopassRepl?: boolean }} */ (swarm) if (s._bareOsHdmsAutopassRepl) return s._bareOsHdmsAutopassRepl = true swarm.on('connection', (socket) => { const base = getBase() if (base && typeof base.replicate === 'function') { try { base.replicate(socket, { live: true }) } catch (_) {} } }) } function parseBootstrapEnv() { const raw = globalThis.process?.env?.HYPERSWARM_BOOTSTRAP if (!raw || typeof raw !== 'string') return null const parts = raw .split(',') .map((x) => x.trim()) .filter(Boolean) return parts.length ? parts : null } /** * @param {HdmsController} hdms * @param {string[]} argv * @param {Record} ctx */ export async function runHdmsCli(hdms, argv, ctx) { try { const sub = argv[1] const rest = argv.slice(2) if (!sub || sub === 'help' || sub === '--help') { ctx.console.log( 'hdms list | create