Files
bare-operating-system/packages/bare-os-booter/lib/hdms-manager.js
T
Raven Scott 342b6040fe
CI / test (push) Successful in 9m48s
Update
2026-04-03 02:49:18 -04:00

468 lines
12 KiB
JavaScript

/**
* 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<HdmsRegistryFile>}
*/
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<string, { drive: import('hyperdrive').default, writable: boolean }> } | null} */
this.vfsMountRef = null
/** @type {string[] | null} */
this.bootstrap = null
/** @type {HdmsRegistryFile | null} */
this.registry = null
/** @type {Map<string, { drive: import('hyperdrive').default, entry: HdmsRegistryEntry, writable: boolean }>} */
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<string, { drive: import('hyperdrive').default, writable: boolean }> },
* 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<string, { drive: import('hyperdrive').default, writable: boolean }>} */
const m = new Map()
for (const [label, x] of this.byLabel) {
m.set(label, { drive: x.drive, writable: x.writable })
}
return m
}
/**
* @param {Record<string, unknown>} 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<string, unknown>} 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<string, unknown>} 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<string, unknown>} 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<string, unknown>} 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<string, unknown>} 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<string, unknown>} 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<string, unknown>} ctx
* @param {string} inviteZ32
*/
async pair(ctx, inviteZ32) {
this.assertLoggedIn(ctx)
if (!inviteZ32 || typeof inviteZ32 !== 'string') {
throw new Error('Usage: hdms pair <invite>')
}
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<string, unknown>} 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 <label> | add <label> <key> | remove <label> | show <label> | invite [--read-only] | pair <invite>'
)
return
}
if (sub === 'list') {
await hdms.list(ctx)
return
}
if (sub === 'create') {
await hdms.create(ctx, rest[0])
return
}
if (sub === 'add') {
await hdms.addReadonly(ctx, rest[0], rest[1])
return
}
if (sub === 'remove') {
await hdms.remove(ctx, rest[0])
return
}
if (sub === 'show') {
await hdms.show(ctx, rest[0])
return
}
if (sub === 'invite') {
const ro = rest.includes('--read-only')
await hdms.invite(ctx, ro)
return
}
if (sub === 'pair') {
await hdms.pair(ctx, rest[0])
return
}
ctx.console.error('hdms: unknown subcommand (try hdms help)')
} catch (e) {
ctx.console.error('hdms: ' + (e?.message || e))
}
}