/** * Swarm-disk peer RPC bridge: local Hyperdrive path search and whitelisted OS RPC. * Wired onto {@link import('./swarm-disk.js').SwarmDisk} as `disk.os` after initd bring-up. */ import b4a from 'b4a' import { BARE_OS_KERNEL_CAPABILITY_WIRE_VERSION, BARE_OS_KERNEL_CAPABILITY_WORDS_JSON_KEY, BARE_OS_KERNEL_FEATURE_BITS_DOC, BARE_OS_PROTOCOL_PACKAGE_VERSION, BARE_OS_PROTOMUX_CHANNEL_SCHEMA_VERSION, PROTOCOL_NAME, getKernelCapabilityWords } from 'bare-os-protocol' import { listBareServices } from './bare-initd.js' import { BareOsKernelError } from './bare-os-errors.js' import { bareOsCorestoreSnapshotOperatorHint } from './corestore-host-lifecycle.js' const MAX_SEARCH_RESULTS = 256 /** Allowed `action` values for `bare_os.replication_operator_intent` (P2P operator hints only; no central control). */ const REPLICATION_OPERATOR_INTENT_ACTIONS = new Set([ 'pause_hint', 'resume_hint', 'audit_only', 'peer_preference_sketch' ]) const MAX_INTENT_REASON_LEN = 512 const MAX_INTENT_LABEL_LEN = 128 /** * @param {import('hyperdrive').default | null | undefined} drive * @param {string} q normalized lowercase query * @param {string[]} matches mutates * @param {number} cap */ async function collectSearchMatches(drive, q, matches, cap) { if (!drive || typeof drive.list !== 'function') return try { for await (const ent of drive.list('/', { recursive: true })) { const key = String(ent.key || '') if (!key) continue const norm = key.startsWith('/') ? key : `/${key}` if (norm.toLowerCase().includes(q)) { matches.push(norm) if (matches.length >= cap) break } } } catch { /* drive list failure — skip this drive */ } } /** @type {WeakMap} */ const pathManifestCache = new WeakMap() /** * @param {import('hyperdrive').default | null | undefined} drive * @param {string} manifestRel absolute path on drive * @param {number} ttlMs * @returns {Promise} */ async function loadPathManifestPaths(drive, manifestRel, ttlMs) { if (!drive || typeof drive.get !== 'function' || !manifestRel) return [] const now = Date.now() const cached = pathManifestCache.get(drive) if (cached && now - cached.atMs < ttlMs) return cached.paths /** @type {string[]} */ let paths = [] try { const buf = await drive.get(manifestRel) if (!buf || !(buf.byteLength > 0)) { pathManifestCache.set(drive, { paths: [], atMs: now }) return [] } const text = b4a.toString(buf) const j = JSON.parse(text) const arr = Array.isArray(j) ? j : j && typeof j === 'object' && Array.isArray(j.paths) ? j.paths : [] paths = arr .map((p) => String(p || '').trim()) .filter((p) => p.startsWith('/')) } catch { paths = [] } pathManifestCache.set(drive, { paths, atMs: now }) return paths } /** * @param {string[]} manifestPaths * @param {string} q normalized lowercase query * @param {string[]} matches * @param {number} cap * @returns {number} number of manifest matches appended */ function collectManifestMatches(manifestPaths, q, matches, cap) { let n = 0 for (const p of manifestPaths) { if (matches.length >= cap) break if (p.toLowerCase().includes(q)) { matches.push(p) n++ } } return n } /** * @param {number} ms * @returns {Promise} */ function sleepMs(ms) { return new Promise((r) => setTimeout(r, ms)) } /** * @typedef {{ * drive: import('hyperdrive').default | null | undefined * auxiliaryDrives?: import('hyperdrive').default[] | null | undefined * bareOsIpc: { list?: () => string[] } | null | undefined * ctxApiVersion: string * systemRevision: Readonly<{ currentId?: string; pendingId?: string; slot?: string }> | null | undefined * bootStartedMs: number * seedReplicationStatus?: Record | null * seedMirrorDriveHintV2?: Record | null * seedHttpDhtProxyHint?: Record | null * seedSnapshotHints?: Record | null * seedSnapshotManifestSlice?: Record | null * seedSnapshotChain?: Record | null * swarmPeerCount?: number | null * swarmConnectionBudget?: Record | null * protomuxOperatorSketch?: Record | null * auditBatch?: (entries: unknown[]) => unknown * booterPackageVersion?: string | null * corestore?: unknown * seedCapabilityInfo?: Record | null * peerSystemSeedMirror?: boolean * peerSeedSnapshots?: Record | null * }} BareOsDiskOsBridgeOpts */ /** * @param {BareOsDiskOsBridgeOpts} opts * @returns {Record | null} */ function buildPeerCapabilitiesBody(opts) { const cap = opts.seedCapabilityInfo if (!cap || typeof cap !== 'object') return null const words = getKernelCapabilityWords(cap) if (!words) return null const c = /** @type {Record} */ (cap) const chat = c.chatChannel const tip = c.imageTipId return { doc: typeof c.doc === 'string' ? c.doc : BARE_OS_KERNEL_FEATURE_BITS_DOC, featureBitsDoc: typeof c.featureBitsDoc === 'string' ? c.featureBitsDoc : typeof c.doc === 'string' ? c.doc : BARE_OS_KERNEL_FEATURE_BITS_DOC, kernelCapabilityWireVersion: typeof c.kernelCapabilityWireVersion === 'number' ? c.kernelCapabilityWireVersion : BARE_OS_KERNEL_CAPABILITY_WIRE_VERSION, [BARE_OS_KERNEL_CAPABILITY_WORDS_JSON_KEY]: words, protocolPackageVersion: typeof c.protocolPackageVersion === 'string' ? c.protocolPackageVersion : BARE_OS_PROTOCOL_PACKAGE_VERSION, booterPackageVersion: opts.booterPackageVersion || undefined, protocol: typeof c.protocol === 'string' ? c.protocol : PROTOCOL_NAME, role: 'peer-system-seeder', ...(tip != null && String(tip).trim() !== '' ? { imageTipId: String(tip).trim() } : {}), ...(chat && typeof chat === 'object' ? { chatChannel: chat } : {}), note: 'Mirrored from publisher capabilities at this node boot; trust matches swarm trust.' } } /** * @param {BareOsDiskOsBridgeOpts} opts */ export function createBareOsDiskOsBridge(opts) { /** Last `searchLocal` metrics for `disk_os_hints` (schema 3). */ const pathSearchLocalMetrics = { schema: 1, lastAtMs: 0, lastQueryLen: 0, manifestRel: /** @type {string | null} */ (null), manifestOnly: false, manifestEntryCount: 0, manifestMatchCount: 0, primaryScanMatchCount: 0, auxiliaryScanMatchCount: 0 } return { /** * @param {string} query * @returns {Promise} */ async searchLocal(query) { const q = String(query || '').trim().toLowerCase() if (!q) return [] const env = globalThis.process?.env const throttleMs = Math.max( 0, Math.min(500, Number(env?.BARE_OS_DISK_OS_SEARCH_THROTTLE_MS) || 0) ) const manifestDefault = '/etc/bare-os/path-manifest.json' const manifestRelRaw = String( env?.BARE_OS_DISK_OS_PATH_MANIFEST != null ? env.BARE_OS_DISK_OS_PATH_MANIFEST : manifestDefault ).trim() const manifestRel = manifestRelRaw === '' ? '' : manifestRelRaw.startsWith('/') ? manifestRelRaw : `/${manifestRelRaw}` const manifestOnly = String(env?.BARE_OS_DISK_OS_SEARCH_MANIFEST_ONLY || '') === '1' || String(env?.BARE_OS_DISK_OS_SEARCH_MANIFEST_ONLY || '') === 'true' const cacheMs = Math.max( 0, Math.min(3600000, Number(env?.BARE_OS_DISK_OS_MANIFEST_CACHE_MS) || 30000) ) /** @type {import('hyperdrive').default[]} */ const drives = [] if (opts.drive && typeof opts.drive.list === 'function') { drives.push(opts.drive) } if (Array.isArray(opts.auxiliaryDrives)) { for (const d of opts.auxiliaryDrives) { if (d && typeof d.list === 'function') drives.push(d) } } /** @type {string[]} */ const raw = [] let manifestMatchCount = 0 let primaryScanCount = 0 let auxScanCount = 0 let manifestEntryCount = 0 const primary = drives[0] if (primary && manifestRel) { const mpaths = await loadPathManifestPaths( primary, manifestRel, cacheMs ) manifestEntryCount = mpaths.length manifestMatchCount = collectManifestMatches( mpaths, q, raw, MAX_SEARCH_RESULTS ) } if (primary && raw.length < MAX_SEARCH_RESULTS && !manifestOnly) { const before = raw.length await collectSearchMatches( primary, q, raw, MAX_SEARCH_RESULTS - raw.length ) primaryScanCount += raw.length - before } for (let i = 1; i < drives.length && raw.length < MAX_SEARCH_RESULTS; i++) { if (throttleMs > 0) await sleepMs(throttleMs) const before = raw.length await collectSearchMatches( drives[i], q, raw, MAX_SEARCH_RESULTS - raw.length ) auxScanCount += raw.length - before } pathSearchLocalMetrics.lastAtMs = Date.now() pathSearchLocalMetrics.lastQueryLen = q.length pathSearchLocalMetrics.manifestRel = manifestRel || null pathSearchLocalMetrics.manifestOnly = manifestOnly pathSearchLocalMetrics.manifestEntryCount = manifestEntryCount pathSearchLocalMetrics.manifestMatchCount = manifestMatchCount pathSearchLocalMetrics.primaryScanMatchCount = primaryScanCount pathSearchLocalMetrics.auxiliaryScanMatchCount = auxScanCount return [...new Set(raw)] }, /** * @param {string} module * @param {string} method * @param {string[]} args * @returns {Promise} */ async execRpc(module, method, args) { const mod = String(module || '').trim() const meth = String(method || '').trim() const a = Array.isArray(args) ? args.map((x) => String(x)) : [] if (opts.peerSystemSeedMirror && mod === 'bare_os') { if (meth === 'capabilities') { const capB = buildPeerCapabilitiesBody(opts) if (capB) return JSON.stringify(capB) } const snaps = opts.peerSeedSnapshots if ( snaps && typeof snaps === 'object' && Object.prototype.hasOwnProperty.call(snaps, meth) ) { const payload = snaps[meth] if (payload != null && typeof payload === 'object') { return JSON.stringify(payload) } return JSON.stringify({ schema: 1, role: 'peer-system-seeder', ok: false, reason: 'not_captured_at_boot', method: meth }) } } if (mod === 'bare_os' && meth === 'ping') return 'pong' if (mod === 'bare_os' && meth === 'ctx_api_version') { return String(opts.ctxApiVersion || '') } if (mod === 'bare_os' && meth === 'uptime_ms') { const t = Date.now() - (Number(opts.bootStartedMs) || 0) return String(Math.max(0, t)) } if (mod === 'bare_os' && meth === 'system_revision') { const rev = opts.systemRevision || {} return JSON.stringify({ currentId: String(rev.currentId || ''), pendingId: String(rev.pendingId || ''), slot: String(rev.slot || '') }) } if (mod === 'bare_os' && meth === 'ipc_list') { try { const ipc = opts.bareOsIpc const list = typeof ipc?.list === 'function' ? ipc.list() : [] return JSON.stringify(list) } catch (e) { return JSON.stringify({ error: (e && /** @type {Error} */ (e).message) || String(e) }) } } if (mod === 'bare_os' && meth === 'service_names') { try { const names = listBareServices().map((s) => s.name) return JSON.stringify(names) } catch { return '[]' } } if (mod === 'bare_os' && meth === 'echo') { return JSON.stringify({ args: a }) } if (mod === 'bare_os' && meth === 'disk_os_hints') { const aux = Array.isArray(opts.auxiliaryDrives) ? opts.auxiliaryDrives.filter((d) => d && typeof d.list === 'function') .length : 0 const booterVer = String(opts.booterPackageVersion || '').trim() return JSON.stringify({ schema: 3, ok: true, auxiliaryDriveCount: aux, protocolPackageVersion: BARE_OS_PROTOCOL_PACKAGE_VERSION, protomuxChannelSchemaVersion: BARE_OS_PROTOMUX_CHANNEL_SCHEMA_VERSION, booterPackageVersion: booterVer || null, mirrorDriveHintV2: opts.seedMirrorDriveHintV2 && typeof opts.seedMirrorDriveHintV2 === 'object' ? opts.seedMirrorDriveHintV2 : null, httpDhtProxyHint: opts.seedHttpDhtProxyHint && typeof opts.seedHttpDhtProxyHint === 'object' ? opts.seedHttpDhtProxyHint : null, pathSearchLocal: { ...pathSearchLocalMetrics }, note: 'Schema 3: adds pathSearchLocal metrics (path-manifest–accelerated disk.os searchLocal). Schema 2 fields retained. Advisory hints only.' }) } if (mod === 'bare_os' && meth === 'replication_snapshot') { const drive = opts.drive if (!drive) { return JSON.stringify({ schema: 2, ok: false, reason: 'no_system_drive' }) } const env = globalThis.process?.env const parseEnvJson = (key) => { const raw = String(env?.[key] ?? '').trim() if (!raw) return null try { const o = JSON.parse(raw) return o && typeof o === 'object' ? o : null } catch { return { schema: 1, error: 'invalid_json', key } } } try { const id = /** @type {{ id?: Uint8Array }} */ (drive).id const dk = /** @type {{ discoveryKey?: Uint8Array }} */ (drive) .discoveryKey return JSON.stringify({ schema: 2, ok: true, writable: !!drive.writable, idPrefix: id && id.byteLength ? b4a.toString(id, 'hex').slice(0, 16) : null, discoveryKeyPrefix: dk && dk.byteLength ? b4a.toString(dk, 'hex').slice(0, 16) : null, seedSnapshotHints: opts.seedSnapshotHints && typeof opts.seedSnapshotHints === 'object' ? opts.seedSnapshotHints : null, seedSnapshotManifestSlice: opts.seedSnapshotManifestSlice && typeof opts.seedSnapshotManifestSlice === 'object' ? opts.seedSnapshotManifestSlice : null, seedSnapshotChain: opts.seedSnapshotChain && typeof opts.seedSnapshotChain === 'object' ? opts.seedSnapshotChain : null, corestoreSnapshotEnv: parseEnvJson( 'BARE_OS_CORESTORE_SNAPSHOT_JSON' ), note: 'Schema 2: adds optional seed snapshot hints/manifest slice/chain (non-secret) plus BARE_OS_CORESTORE_SNAPSHOT_JSON merge for corestore-snapshot–style operator signals. Hex prefixes unchanged.' }) } catch (e) { return JSON.stringify({ schema: 2, ok: false, error: (e && /** @type {Error} */ (e).message) || String(e) }) } } if (mod === 'bare_os' && meth === 'replication_operator_sketch') { const sr = opts.seedReplicationStatus const env = globalThis.process?.env const parseEnvJson = (key) => { const raw = String(env?.[key] ?? '').trim() if (!raw) return null try { const o = JSON.parse(raw) return o && typeof o === 'object' ? o : null } catch { return { schema: 1, error: 'invalid_json', key } } } const sp = Number(opts.swarmPeerCount) const blindV3 = parseEnvJson('BARE_OS_BLIND_PEER_TOPOLOGY_V3_JSON') const hblobs = parseEnvJson('BARE_OS_HYPERBLOBS_STATS_JSON') const corestoreSketch = parseEnvJson('BARE_OS_CORESTORE_STATS_JSON') const peerPrioritySketch = parseEnvJson( 'BARE_OS_REPLICATION_PEER_PRIORITY_JSON' ) const hyperdhtAddressSketch = parseEnvJson( 'BARE_OS_HYPERDHT_ADDRESS_JSON' ) return JSON.stringify({ schema: 8, ok: true, seedReplicationStatus: sr && typeof sr === 'object' ? sr : null, swarmPeerCount: Number.isFinite(sp) ? sp : null, swarmConnectionBudget: opts.swarmConnectionBudget && typeof opts.swarmConnectionBudget === 'object' ? opts.swarmConnectionBudget : null, protomuxOperatorSketch: opts.protomuxOperatorSketch && typeof opts.protomuxOperatorSketch === 'object' ? opts.protomuxOperatorSketch : null, pkgIndexSurface: { schema: 2, diskOsRpc: 'bare_os.pkg_index_get', hrpcRoute: 'bare_os.pkg_index_get', defaultManifestPath: '/etc/bare-os/pkg-index.json', envManifestPath: 'BARE_OS_PKG_INDEX_PATH', listingKeyCap: 512, note: 'Schema 2: listingKeyCap + manifest schema echo. Hyperbee-backed indexes replicate separately; drive JSON is a static manifest for operators.' }, hyperdhtAddressSketch, corestoreSnapshotUxHint: bareOsCorestoreSnapshotOperatorHint( opts.corestore ?? null ), corestoreSnapshotPackageRef: { schema: 1, npmPackage: 'corestore-snapshot', upstreamOrg: 'holepunchto', role: 'Host-side optional workflow; pair BARE_OS_CORESTORE_SNAPSHOT_JSON with BARE_OS_CORESTORE_STATS_JSON — corestoreSnapshotUxHint compares non-secret counts for operator rollback UX.' }, corestoreSnapshotPaused: String(env?.BARE_OS_CORESTORE_SNAPSHOT_PAUSED || '') === '1' || String(env?.BARE_OS_CORESTORE_SNAPSHOT_PAUSED || '') === 'true', replicationPausedEnv: String(env?.BARE_OS_REPLICATION_PAUSED || '') === '1' || String(env?.BARE_OS_REPLICATION_PAUSED || '') === 'true', blindRelayStats: parseEnvJson('BARE_OS_BLIND_RELAY_STATS_JSON'), replicationBackpressure: parseEnvJson( 'BARE_OS_REPLICATION_BACKPRESSURE_JSON' ), blindTopologySketchV3: blindV3, hyperblobsDedupSketch: hblobs, corestoreOperatorSketch: corestoreSketch, peerPrioritySketch, note: 'Schema 8: hyperdhtAddressSketch from BARE_OS_HYPERDHT_ADDRESS_JSON (non-secret); pkgIndexSurface schema 2. Schema 7: corestoreSnapshotUxHint advisory counts. Schema 6: pkgIndexSurface. Schema 5: corestoreOperatorSketch from BARE_OS_CORESTORE_STATS_JSON.' }) } if (mod === 'bare_os' && meth === 'replication_operator_intent') { const env = globalThis.process?.env const on = String(env?.BARE_OS_DISK_OS_OPERATOR_INTENT_RPC || '') === '1' || String(env?.BARE_OS_DISK_OS_OPERATOR_INTENT_RPC || '') === 'true' if (!on) { return JSON.stringify({ schema: 2, ok: false, code: 'EPERM', note: 'replication_operator_intent is cap-gated: set BARE_OS_DISK_OS_OPERATOR_INTENT_RPC=1 on the host.' }) } /** @type {Record} */ let payload = {} if (a[0]) { try { const p = JSON.parse(a[0]) if (p && typeof p === 'object' && !Array.isArray(p)) { payload = /** @type {Record} */ (p) } else { return JSON.stringify({ schema: 2, ok: false, code: 'EINVAL', note: 'First arg must be a JSON object string (non-array).' }) } } catch { return JSON.stringify({ schema: 2, ok: false, code: 'EINVAL', note: 'First arg must be a JSON object string.' }) } } const act = String(payload.action ?? '').trim() if (!act || !REPLICATION_OPERATOR_INTENT_ACTIONS.has(act)) { return JSON.stringify({ schema: 2, ok: false, code: 'EINVAL', note: `Unknown or missing action. Allowed: ${[...REPLICATION_OPERATOR_INTENT_ACTIONS].sort().join(', ')}.`, allowedActions: [...REPLICATION_OPERATOR_INTENT_ACTIONS].sort() }) } const reason = String(payload.reason ?? '').trim() if (reason.length > MAX_INTENT_REASON_LEN) { return JSON.stringify({ schema: 2, ok: false, code: 'EINVAL', note: `reason exceeds ${MAX_INTENT_REASON_LEN} chars` }) } const label = String(payload.label ?? '').trim() if (label.length > MAX_INTENT_LABEL_LEN) { return JSON.stringify({ schema: 2, ok: false, code: 'EINVAL', note: `label exceeds ${MAX_INTENT_LABEL_LEN} chars` }) } const ttlRaw = payload.ttlSec let ttlSec = null if (ttlRaw != null && ttlRaw !== '') { const n = Math.floor(Number(ttlRaw)) if (!Number.isFinite(n) || n < 0 || n > 86400 * 7) { return JSON.stringify({ schema: 2, ok: false, code: 'EINVAL', note: 'ttlSec must be between 0 and 604800 (7 days).' }) } ttlSec = n } /** @type {Record | null} */ let backpressureSummary = null const bpRaw = String(env?.BARE_OS_REPLICATION_BACKPRESSURE_JSON ?? '').trim() if (bpRaw) { try { const bp = JSON.parse(bpRaw) if (bp && typeof bp === 'object') { backpressureSummary = { schema: typeof bp.schema === 'number' ? bp.schema : 1, keys: Object.keys(bp).slice(0, 24) } } } catch { backpressureSummary = { parseError: true } } } const normalizedIntent = { action: act, ...(reason ? { reason } : {}), ...(label ? { label } : {}), ...(ttlSec != null ? { ttlSec } : {}) } if (typeof opts.auditBatch === 'function') { try { opts.auditBatch([ { kind: 'disk_os.replication_operator_intent', atMs: Date.now(), intent: normalizedIntent } ]) } catch { /* ignore */ } } return JSON.stringify({ schema: 2, ok: true, audited: true, normalizedIntent, replicationBackpressureSummary: backpressureSummary, note: 'Schema 2: validates action/reason/label/ttlSec; appends audit via ctx.bareOsAuditLogAppendBatch when wired. Hints only — host applies policy.' }) } if (mod === 'bare_os' && meth === 'path_exists') { const drive = opts.drive const raw = String(a[0] || '').trim() if (!raw.startsWith('/')) { return JSON.stringify({ schema: 1, ok: false, code: 'EINVAL', note: 'path must be absolute' }) } if (!drive || typeof drive.get !== 'function') { return JSON.stringify({ schema: 1, ok: false, reason: 'no_system_drive' }) } try { const node = await drive.get(raw) const exists = node != null return JSON.stringify({ schema: 1, ok: true, path: raw, exists }) } catch (e) { return JSON.stringify({ schema: 1, ok: false, error: (e && /** @type {Error} */ (e).message) || String(e) }) } } if (mod === 'bare_os' && meth === 'boot_init_exists') { const drive = opts.drive if (!drive || typeof drive.get !== 'function') { return JSON.stringify({ schema: 1, ok: false, reason: 'no_system_drive' }) } try { const p = '/boot/init.js' const node = await drive.get(p) return JSON.stringify({ schema: 1, ok: true, path: p, exists: node != null }) } catch (e) { return JSON.stringify({ schema: 1, ok: false, error: (e && /** @type {Error} */ (e).message) || String(e) }) } } if (mod === 'bare_os' && meth === 'pkg_index_get') { const drive = opts.drive const env = globalThis.process?.env const relRaw = String( env?.BARE_OS_PKG_INDEX_PATH != null ? env.BARE_OS_PKG_INDEX_PATH : '/etc/bare-os/pkg-index.json' ).trim() const rel = relRaw === '' ? '/etc/bare-os/pkg-index.json' : relRaw.startsWith('/') ? relRaw : `/${relRaw}` const want = String(a[0] || '').trim() if (!drive || typeof drive.get !== 'function') { return JSON.stringify({ schema: 1, ok: false, reason: 'no_system_drive' }) } try { const node = await drive.get(rel) if (!node || !node.byteLength) { return JSON.stringify({ schema: 1, ok: false, path: rel, reason: 'missing_or_empty' }) } const j = JSON.parse(b4a.toString(node)) const pkgs = j && typeof j === 'object' && j.packages && typeof j.packages === 'object' ? /** @type {Record} */ (j.packages) : {} const manifestSchema = j && typeof j === 'object' && typeof j.schema === 'number' ? j.schema : 1 if (!want) { return JSON.stringify({ schema: 2, ok: true, path: rel, manifestSchema, keys: Object.keys(pkgs).slice(0, 512) }) } const ent = Object.prototype.hasOwnProperty.call(pkgs, want) ? pkgs[want] : null return JSON.stringify({ schema: 2, ok: ent != null, path: rel, manifestSchema, key: want, entry: ent }) } catch (e) { return JSON.stringify({ schema: 1, ok: false, error: (e && /** @type {Error} */ (e).message) || String(e) }) } } throw new BareOsKernelError( 'BARE_OS_EXEC_RPC_UNKNOWN', `execRpc: unknown module/method: ${mod}.${meth}` ) } } }