831 lines
27 KiB
JavaScript
831 lines
27 KiB
JavaScript
/**
|
||
* 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<object, { paths: string[], atMs: number }>} */
|
||
const pathManifestCache = new WeakMap()
|
||
|
||
/**
|
||
* @param {import('hyperdrive').default | null | undefined} drive
|
||
* @param {string} manifestRel absolute path on drive
|
||
* @param {number} ttlMs
|
||
* @returns {Promise<string[]>}
|
||
*/
|
||
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<void>}
|
||
*/
|
||
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<string, unknown> | null
|
||
* seedMirrorDriveHintV2?: Record<string, unknown> | null
|
||
* seedHttpDhtProxyHint?: Record<string, unknown> | null
|
||
* seedSnapshotHints?: Record<string, unknown> | null
|
||
* seedSnapshotManifestSlice?: Record<string, unknown> | null
|
||
* seedSnapshotChain?: Record<string, unknown> | null
|
||
* swarmPeerCount?: number | null
|
||
* swarmConnectionBudget?: Record<string, unknown> | null
|
||
* protomuxOperatorSketch?: Record<string, unknown> | null
|
||
* auditBatch?: (entries: unknown[]) => unknown
|
||
* booterPackageVersion?: string | null
|
||
* corestore?: unknown
|
||
* seedCapabilityInfo?: Record<string, unknown> | null
|
||
* peerSystemSeedMirror?: boolean
|
||
* peerSeedSnapshots?: Record<string, unknown | null> | null
|
||
* }} BareOsDiskOsBridgeOpts
|
||
*/
|
||
|
||
/**
|
||
* @param {BareOsDiskOsBridgeOpts} opts
|
||
* @returns {Record<string, unknown> | 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<string, unknown>} */ (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<string[]>}
|
||
*/
|
||
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<string>}
|
||
*/
|
||
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<string, unknown>} */
|
||
let payload = {}
|
||
if (a[0]) {
|
||
try {
|
||
const p = JSON.parse(a[0])
|
||
if (p && typeof p === 'object' && !Array.isArray(p)) {
|
||
payload = /** @type {Record<string, unknown>} */ (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<string, unknown> | 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<string, unknown>} */ (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}`
|
||
)
|
||
}
|
||
}
|
||
}
|