After a successful swarm boot, fill seedCapabilityInfo with stock kernelCapabilityWords when the pre-MBR capabilities RPC was skipped, failed, or lacked words, so peer system seed eligibility passes and localRAM can serve block 0. Add BARE_OS_PEER_SEED_SYNTHETIC_CAPABILITIES (opt-out) and BARE_OS_PEER_SEED_ADVERTISE_IMAGE_TIP_ID; document tip propagation in env appendix, users manual, and handbook. Extend peer seed tests.
440 lines
13 KiB
JavaScript
440 lines
13 KiB
JavaScript
import b4a from 'b4a'
|
||
import Hyperdrive from 'hyperdrive'
|
||
import { parseMbr } from 'bare-os-protocol'
|
||
import {
|
||
getKernelCapabilityWords,
|
||
readKernelCapabilityWord,
|
||
BARE_OS_KERNEL_FEATURE_BITS_DOC,
|
||
BARE_OS_KERNEL_CAPABILITY_WIRE_VERSION,
|
||
BARE_OS_KERNEL_CAPABILITY_WORDS_JSON_KEY,
|
||
BARE_OS_KERNEL_FEATURES_STOCK_WORD_PRIMARY
|
||
} from 'bare-os-protocol'
|
||
import {
|
||
KERNEL_CAPABILITY_SEED_STRICT_ROWS,
|
||
buildStockKernelCapabilityWords
|
||
} from './bare-os-capability-registry.js'
|
||
import { maybeSynthesizePeerSeedCapabilityInfo } from './bare-os-peer-system-seed.js'
|
||
|
||
/** Splash lines while block 0 / MBR is in flight (avoids a silent multi‑minute wait). */
|
||
const MBR_READ_HEARTBEAT_MS = 10_000
|
||
|
||
/**
|
||
* @param {import('./swarm-disk.js').SwarmDisk} disk
|
||
* @param {{ log: (s: string) => void }} splash
|
||
*/
|
||
async function readBlock0WithProgress(disk, splash) {
|
||
const iv = setInterval(() => {
|
||
const n = disk.peers?.size ?? 0
|
||
splash.log(
|
||
n > 0
|
||
? `MBR: still waiting for block 0 (${n} peer${n === 1 ? '' : 's'} connected)…`
|
||
: 'MBR: no Hyperswarm peers yet — run the system seeder or check network / HYPERSWARM_BOOTSTRAP'
|
||
)
|
||
}, MBR_READ_HEARTBEAT_MS)
|
||
try {
|
||
return await disk.read(0)
|
||
} finally {
|
||
clearInterval(iv)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {import('corestore').default} store
|
||
* @param {import('hyperswarm').default} swarm
|
||
* @param {typeof Hyperdrive} HyperdriveCtor
|
||
* @param {import('./swarm-disk.js').SwarmDisk} disk
|
||
* @param {ReturnType<import('./boot-splash.js').createBootSplash>} splash
|
||
* @param {string} keyHex
|
||
* @returns {Promise<Uint8Array>}
|
||
*/
|
||
export async function loadOsFromOfflineLkg(
|
||
store,
|
||
swarm,
|
||
HyperdriveCtor,
|
||
disk,
|
||
splash,
|
||
keyHex
|
||
) {
|
||
const hostEnv = globalThis.process?.env
|
||
splash.setPhase('Offline LKG: opening system Hyperdrive…')
|
||
const hex = String(keyHex).replace(/^0x/i, '').toLowerCase().trim()
|
||
let key
|
||
try {
|
||
key = b4a.from(hex, 'hex')
|
||
} catch {
|
||
throw new Error('BARE_OS_LKG_SYSTEM_KEY_HEX: invalid hex')
|
||
}
|
||
if (key.length !== 32) {
|
||
throw new Error(
|
||
'BARE_OS_LKG_SYSTEM_KEY_HEX: expected 32-byte Hyperdrive key (64 hex chars)'
|
||
)
|
||
}
|
||
disk.drive = new HyperdriveCtor(store, key)
|
||
await disk.drive.ready()
|
||
disk.mbrKeysHex = [hex]
|
||
swarm.join(disk.drive.discoveryKey)
|
||
try {
|
||
await swarm.flush()
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
disk.seedCapabilityInfo = {
|
||
role: 'offline-lkg',
|
||
doc: BARE_OS_KERNEL_FEATURE_BITS_DOC,
|
||
featureBitsDoc: BARE_OS_KERNEL_FEATURE_BITS_DOC,
|
||
kernelCapabilityWireVersion: BARE_OS_KERNEL_CAPABILITY_WIRE_VERSION,
|
||
[BARE_OS_KERNEL_CAPABILITY_WORDS_JSON_KEY]: buildStockKernelCapabilityWords(
|
||
BARE_OS_KERNEL_FEATURES_STOCK_WORD_PRIMARY
|
||
),
|
||
protocol: 'bare-os-v1',
|
||
note: 'offline boot; bare_os.capabilities RPC not used'
|
||
}
|
||
let initSource = null
|
||
splash.setPhase('Offline LKG: resolving /boot/init.js…')
|
||
for (let i = 0; i < 90; i++) {
|
||
initSource = await disk.drive.get('/boot/init.js')
|
||
if (initSource) break
|
||
await new Promise((r) => setTimeout(r, 200))
|
||
}
|
||
if (!initSource) {
|
||
throw new Error(
|
||
'Offline LKG: /boot/init.js missing in local Corestore — boot online once to replicate'
|
||
)
|
||
}
|
||
const lazyPd =
|
||
hostEnv?.BARE_OS_LAZY_PERSONAL_DRIVE === '1' ||
|
||
hostEnv?.BARE_OS_LAZY_PERSONAL_DRIVE === 'true'
|
||
if (!lazyPd) {
|
||
splash.setPhase('Mounting personal Hyperdrive…')
|
||
await disk.initPersonalDrive(store, swarm, HyperdriveCtor)
|
||
splash.log('Personal drive ready')
|
||
}
|
||
splash.setPhase('Starting shell…')
|
||
splash.prepareForKernel()
|
||
return initSource
|
||
}
|
||
|
||
/**
|
||
* Network + drive setup only (must finish within boot timeout). Does not run the
|
||
* interactive kernel — that can take arbitrarily long.
|
||
* @returns {Promise<Uint8Array>}
|
||
*/
|
||
export async function loadOsFromPeers(disk, store, swarm, splash) {
|
||
const hostEnv = globalThis.process?.env
|
||
const skipCap =
|
||
hostEnv?.BARE_OS_SEED_RPC_HANDSHAKE === '0' ||
|
||
hostEnv?.BARE_OS_SEED_RPC_HANDSHAKE === 'false'
|
||
if (!skipCap && disk.peers.size > 0) {
|
||
splash.setPhase('Seed capability handshake…')
|
||
try {
|
||
const cap = await disk.rpc('bare_os', 'capabilities', [], 4000)
|
||
disk.seedCapabilityInfo =
|
||
cap && typeof cap === 'object' ? cap : { raw: cap }
|
||
const strict =
|
||
hostEnv?.BARE_OS_SEED_CAP_STRICT === '1' ||
|
||
hostEnv?.BARE_OS_SEED_CAP_STRICT === 'true'
|
||
if (strict) {
|
||
const words = getKernelCapabilityWords(cap)
|
||
if (!words) {
|
||
throw new Error(
|
||
'BARE_OS_SEED_CAP_STRICT: seeder capabilities missing kernelCapabilityWords (wire v2)'
|
||
)
|
||
}
|
||
for (const [key, need] of KERNEL_CAPABILITY_SEED_STRICT_ROWS) {
|
||
const got = readKernelCapabilityWord(words, key) >>> 0
|
||
const n = need >>> 0
|
||
if ((got & n) !== n) {
|
||
throw new Error(
|
||
`BARE_OS_SEED_CAP_STRICT: seeder kernelCapabilityWords.${key} does not cover stock booter requirements`
|
||
)
|
||
}
|
||
}
|
||
}
|
||
try {
|
||
disk.seedReplicationStatus = await disk.rpc(
|
||
'bare_os',
|
||
'replication_status',
|
||
[],
|
||
3000
|
||
)
|
||
} catch {
|
||
disk.seedReplicationStatus = null
|
||
}
|
||
try {
|
||
disk.seedManifestHints = await disk.rpc(
|
||
'bare_os',
|
||
'manifest_hints',
|
||
[],
|
||
3000
|
||
)
|
||
} catch {
|
||
disk.seedManifestHints = null
|
||
}
|
||
try {
|
||
disk.seedPeerHealth = await disk.rpc('bare_os', 'peer_health', [], 3000)
|
||
} catch {
|
||
disk.seedPeerHealth = null
|
||
}
|
||
try {
|
||
disk.seedStagingSlot = await disk.rpc(
|
||
'bare_os',
|
||
'staging_slot',
|
||
[],
|
||
3000
|
||
)
|
||
} catch {
|
||
disk.seedStagingSlot = null
|
||
}
|
||
try {
|
||
disk.seedReplicationQueue = await disk.rpc(
|
||
'bare_os',
|
||
'replication_queue',
|
||
[],
|
||
3000
|
||
)
|
||
} catch {
|
||
disk.seedReplicationQueue = null
|
||
}
|
||
try {
|
||
disk.seedCapabilityAttestation = await disk.rpc(
|
||
'bare_os',
|
||
'capability_attestation',
|
||
[],
|
||
3000
|
||
)
|
||
} catch {
|
||
disk.seedCapabilityAttestation = null
|
||
}
|
||
try {
|
||
disk.seedMbrLayout = await disk.rpc('bare_os', 'mbr_layout', [], 3000)
|
||
} catch {
|
||
disk.seedMbrLayout = null
|
||
}
|
||
try {
|
||
disk.seedSnapshotHints = await disk.rpc(
|
||
'bare_os',
|
||
'snapshot_hints',
|
||
[],
|
||
3000
|
||
)
|
||
} catch {
|
||
disk.seedSnapshotHints = null
|
||
}
|
||
try {
|
||
disk.seedPeerFirewallStats = await disk.rpc(
|
||
'bare_os',
|
||
'peer_firewall_stats',
|
||
[],
|
||
3000
|
||
)
|
||
} catch {
|
||
disk.seedPeerFirewallStats = null
|
||
}
|
||
try {
|
||
disk.seedReplicationPlan = await disk.rpc(
|
||
'bare_os',
|
||
'replication_plan',
|
||
[],
|
||
2000
|
||
)
|
||
} catch {
|
||
disk.seedReplicationPlan = null
|
||
}
|
||
try {
|
||
disk.seedDhtBootstrapHint = await disk.rpc(
|
||
'bare_os',
|
||
'dht_bootstrap_hint',
|
||
[],
|
||
2000
|
||
)
|
||
} catch {
|
||
disk.seedDhtBootstrapHint = null
|
||
}
|
||
try {
|
||
disk.seedSnapshotChain = await disk.rpc(
|
||
'bare_os',
|
||
'snapshot_chain',
|
||
[],
|
||
2000
|
||
)
|
||
} catch {
|
||
disk.seedSnapshotChain = null
|
||
}
|
||
try {
|
||
disk.seedMirrorCompactionHint = await disk.rpc(
|
||
'bare_os',
|
||
'mirror_compaction_hint',
|
||
[],
|
||
2000
|
||
)
|
||
} catch {
|
||
disk.seedMirrorCompactionHint = null
|
||
}
|
||
try {
|
||
disk.seedUpdaterState = await disk.rpc(
|
||
'bare_os',
|
||
'updater_state',
|
||
[],
|
||
2000
|
||
)
|
||
} catch {
|
||
disk.seedUpdaterState = null
|
||
}
|
||
try {
|
||
disk.seedBlindPeerTopologyV2 = await disk.rpc(
|
||
'bare_os',
|
||
'blind_peer_topology_v2',
|
||
[],
|
||
2000
|
||
)
|
||
} catch {
|
||
disk.seedBlindPeerTopologyV2 = null
|
||
}
|
||
try {
|
||
disk.seedCompactPing = await disk.rpc(
|
||
'bare_os',
|
||
'compact_ping',
|
||
[],
|
||
1500
|
||
)
|
||
} catch {
|
||
disk.seedCompactPing = null
|
||
}
|
||
const wave7Rpc = [
|
||
['corestore_stats', 'seedCorestoreStats'],
|
||
['snapshot_manifest_slice', 'seedSnapshotManifestSlice'],
|
||
['mirror_drive_hint_v2', 'seedMirrorDriveHintV2'],
|
||
['hrpc_registry_summary', 'seedHrpcRegistrySummary'],
|
||
['protomux_capability_ad', 'seedProtomuxCapabilityAd'],
|
||
['dht_address_book', 'seedDhtAddressBook'],
|
||
['replication_throttle_hint', 'seedReplicationThrottleHint'],
|
||
['bundlebee_stage', 'seedBundlebeeStage'],
|
||
['http_dht_proxy_hint', 'seedHttpDhtProxyHint']
|
||
]
|
||
const wave8Rpc = [
|
||
['protomux_rpc_pool_hint', 'seedProtomuxRpcPoolHint'],
|
||
['hyperblob_store_hint', 'seedHyperblobStoreHint'],
|
||
['signing_request_queue_hint', 'seedSigningRequestQueueHint'],
|
||
['core_storage_layout_hint', 'seedCoreStorageLayoutHint'],
|
||
['mirror_drive_compaction_v3', 'seedMirrorDriveCompactionV3'],
|
||
['bundlebee_cli_stage', 'seedBundlebeeCliStage'],
|
||
['ready_guard_v2', 'seedReadyGuardV2'],
|
||
['blind_relay_circuit_hint', 'seedBlindRelayCircuitHint'],
|
||
['http_dht_proxy_routes', 'seedHttpDhtProxyRoutes']
|
||
]
|
||
const denyRpcRaw = String(
|
||
hostEnv?.BARE_OS_BOOT_POLICY_DENY_SEED_RPC_METHODS ?? ''
|
||
).trim()
|
||
const denyRpc = new Set(
|
||
denyRpcRaw
|
||
.split(/[\s,]+/)
|
||
.map((s) => s.trim())
|
||
.filter(Boolean)
|
||
)
|
||
for (const [method, key] of [...wave7Rpc, ...wave8Rpc]) {
|
||
if (denyRpc.has(method)) {
|
||
disk[key] = null
|
||
continue
|
||
}
|
||
try {
|
||
disk[key] = await disk.rpc('bare_os', method, [], 2000)
|
||
} catch {
|
||
disk[key] = null
|
||
}
|
||
}
|
||
} catch (e) {
|
||
disk.seedCapabilityInfo = { error: (e && e.message) || String(e) }
|
||
if (
|
||
hostEnv?.BARE_OS_SEED_CAP_FAIL === '1' ||
|
||
hostEnv?.BARE_OS_SEED_CAP_FAIL === 'true'
|
||
) {
|
||
throw e
|
||
}
|
||
}
|
||
}
|
||
|
||
splash.setPhase('Reading MBR from swarm…')
|
||
const mbrMaxAttempts = 6
|
||
const mbrRetryDelayMs = 400
|
||
let mbr = /** @type {Uint8Array | null} */ (null)
|
||
/** @type {Uint8Array[] | null} */
|
||
let mbrKeys = null
|
||
let lastMbrErr = ''
|
||
for (let attempt = 1; attempt <= mbrMaxAttempts; attempt++) {
|
||
try {
|
||
splash.log(
|
||
attempt === 1
|
||
? 'Loading block 0 (MBR)'
|
||
: `MBR read retry ${attempt - 1}/${mbrMaxAttempts - 1}…`
|
||
)
|
||
const block = await readBlock0WithProgress(disk, splash)
|
||
const { keys } = parseMbr(block)
|
||
if (!keys.length) throw new Error('MBR has no drive keys')
|
||
mbr = block
|
||
mbrKeys = keys
|
||
lastMbrErr = ''
|
||
break
|
||
} catch (e) {
|
||
lastMbrErr = (e && e.message) || String(e)
|
||
splash.log(`MBR read failed (${attempt}/${mbrMaxAttempts}): ${lastMbrErr}`)
|
||
if (attempt < mbrMaxAttempts) {
|
||
await new Promise((r) => setTimeout(r, mbrRetryDelayMs))
|
||
}
|
||
}
|
||
}
|
||
if (!mbr || !mbrKeys) {
|
||
throw new Error(
|
||
'MBR unavailable after ' + mbrMaxAttempts + ' attempts: ' + lastMbrErr
|
||
)
|
||
}
|
||
disk.mbrKeysHex = mbrKeys.map((k) => b4a.toString(k, 'hex'))
|
||
disk.bootMbr512 = new Uint8Array(mbr)
|
||
|
||
let initSource = null
|
||
for (const driveKey of mbrKeys) {
|
||
try {
|
||
const hex = b4a.toString(driveKey, 'hex')
|
||
splash.setPhase('Opening system Hyperdrive…')
|
||
splash.log(`Drive key ${hex.slice(0, 16)}…`)
|
||
disk.drive = new Hyperdrive(store, driveKey)
|
||
await disk.drive.ready()
|
||
|
||
splash.setPhase('Replicating with peers…')
|
||
for (const peer of disk.peers) {
|
||
disk.drive.replicate(peer.mux.stream, { live: true, download: true })
|
||
}
|
||
swarm.join(disk.drive.discoveryKey)
|
||
const done = disk.drive.findingPeers()
|
||
swarm.flush().then(done, done)
|
||
|
||
splash.setPhase('Downloading /boot/init.js…')
|
||
for (let i = 0; i < 30; i++) {
|
||
initSource = await disk.drive.get('/boot/init.js')
|
||
if (initSource) break
|
||
await new Promise((r) => setTimeout(r, 200))
|
||
}
|
||
|
||
if (initSource) break
|
||
splash.log('Kernel not on this key — trying next MBR entry')
|
||
} catch (err) {
|
||
splash.log(`Drive error: ${err?.message ?? err}`)
|
||
}
|
||
}
|
||
|
||
if (!initSource) throw new Error('Kernel not found after replication')
|
||
|
||
maybeSynthesizePeerSeedCapabilityInfo(disk, hostEnv)
|
||
|
||
const lazyPd =
|
||
hostEnv?.BARE_OS_LAZY_PERSONAL_DRIVE === '1' ||
|
||
hostEnv?.BARE_OS_LAZY_PERSONAL_DRIVE === 'true'
|
||
if (!lazyPd) {
|
||
splash.setPhase('Mounting personal Hyperdrive…')
|
||
await disk.initPersonalDrive(store, swarm, Hyperdrive)
|
||
splash.log('Personal drive ready')
|
||
}
|
||
splash.setPhase('Starting shell…')
|
||
splash.prepareForKernel()
|
||
return initSource
|
||
}
|