Files
bare-operating-system/packages/bare-os-seeder/kernel/init.js
T
2026-04-06 08:47:54 -04:00

4367 lines
136 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Keep algorithm in sync with `packages/bare-os-protocol/lib/bare-os-pear-multisig-shape.js`
* (`pearMultisigShapeOk`). Kernel bundle cannot import npm packages.
* @param {unknown} parsed
* @returns {boolean}
*/
function bareOsPearMultisigShapeOk(parsed) {
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return false
const signers = /** @type {{ signers?: unknown }} */ (parsed).signers
const quorum = /** @type {{ quorum?: unknown }} */ (parsed).quorum
if (!Array.isArray(signers) || typeof quorum !== 'number') return false
if (quorum < 1 || quorum > signers.length) return false
return true
}
/**
* Boot preamble: cooperative lock FSM constants + `invokeCtxBootHooks`.
* This file is the **only** source for these symbols; `kernel/init.js` is generated by
* `node scripts/bundle-kernel-init.mjs` (concat `lib/boot/*.js`, `lib/init/fragments/*.js`, then `lib/init/init-main.js`).
* CI: `scripts/verify-init-bundle-recipe.mjs` / `verify-kernel-seeder-parity.mjs`.
*/
/** Boot transaction journal row state (resume / audit / rollback FSM). */
const BARE_OS_BOOT_TXN_STATE = Object.freeze({
STAGE_STARTED: 'stage_started',
STAGE_COMMITTED: 'stage_committed',
STAGE_ROLLBACK: 'stage_rollback',
STAGE_SKIPPED: 'stage_skipped'
})
/**
* @param {Record<string, unknown>} ctx
* @param {Record<string, unknown>} ev
*/
async function invokeCtxBootHooks(ctx, ev) {
const label = String(
ev.phase !== undefined && ev.phase !== null
? ev.phase
: ev.step !== undefined && ev.step !== null
? ev.step
: ''
)
const merged = { ...ev, phase: label, step: label }
if (typeof ctx.bareOsInvokeBootStepHooks === 'function') {
await ctx.bareOsInvokeBootStepHooks(merged)
return
}
if (typeof ctx.bareOsInvokeBootPhaseHooks === 'function') {
await ctx.bareOsInvokeBootPhaseHooks(merged)
}
}
/**
* Concatenated into /boot/init.js before init-main.js by scripts/bundle-kernel-init.mjs.
* Boot policy merge: semver helpers, skip merge, kernel.d env guards, applyBootPolicyFile.
* @see kernel/lib/init/STRUCTURE.md
*/
/**
* Optional `/etc/bare-os/boot.policy.json` when `BARE_OS_BOOT_POLICY=1`:
* `{ "skipBootStages": ["rc.d"], "denyBootStages": ["onboot"], "minKernelCapabilitiesPrimary": 1, "requireSeedCaps": 1,
* "maxExecLineDepth": 32, "denyEnvKeys": ["FOO"], "requireProcNodes": ["/proc/bare_os/features"] }`.
* Boot policy v5 (optional): `requireKernelCapabilitiesHostTransportDelegates`, `requireInitJsSha256` (hex sha256 of `/boot/init.js` raw bytes).
* Boot policy v6 (optional): `requireKernelCapabilitiesReplicationOperatorSurface`, `requireBooterSemver`, `requireCtxApiMin`, `denyKernelExtensionIds`,
* `kernelExtensionHashPins`, `offlineLkgIntegrityStrict`.
* Boot policy v7 (optional): `requireKernelCapabilitiesPearCorestoreHrpc`, `requirePearRuntimeMin`, `requireProtocolPackageMin`,
* `denyCtxMethodPrefixes`, `maxKernelExtensionDepth`, `gitPartialClonePolicy`.
* Boot policy v8 (optional): `requireKernelCapabilitiesBareRuntimeProtoMux`, `requireBareRuntimeMin`, `denySeedRpcMethods`,
* `maxProtomuxChannelNameLength`.
* Boot policy v9 (optional): `requireKernelCapabilitiesBareModuleCryptoStaging`, `requirePearRuntimeRange` (`{ min?, max? }` semver),
* `denyBareModuleSpecifierPatterns`, `requireBareCryptoMin`, `denyKernelSyscalls`, `requirePearIpcMin`,
* `extensionSignerPinsV2`, `offlineLkgManifestMaxAgeSec`, `bootStagesRequireProcIndexMinSchema` (legacy `bootPhasesRequireProcIndexMinSchema`).
* Boot policy v10 (optional): `requireKernelCapabilitiesPearInspectLoggerTls`, `requireBareBootMin`, `denyBareRpcMethodPatterns`,
* `maxPearInspectDepth`, `requireBareLoggerMin`, `denyAutobaseDiscoveryChannels`, `requireBareTlsMin`,
* `extensionSignerPinsV3`, `offlineLkgRequirePearStamp`, `bootStagesRequireLifecycleMinSchema` (legacy `bootPhasesRequireLifecycleMinSchema`).
* Boot policy v11 (optional): `requireKernelCapabilitiesHypercorePackHrpcLifecycle`, `extensionSignerPinsV4`, `requireBarePackMin`,
* `requireBareAddonPolicyMin`, `maxHrpcAllowlistDepth`, `offlineLkgRequireHypercorePackHrpcLifecycle`.
* @param {Record<string, unknown>} ctx
* @returns {Promise<boolean>} false when strict policy fails (caller should abort boot)
*/
/**
* @param {string} s
* @returns {number[] | null}
*/
function parseSemverTriplet(s) {
const m = String(s)
.trim()
.match(/^(\d+)\.(\d+)\.(\d+)/)
if (!m) return null
return [Number(m[1]), Number(m[2]), Number(m[3])]
}
/**
* @param {string} have
* @param {string} need
*/
function semverGte(have, need) {
const a = parseSemverTriplet(have)
const b = parseSemverTriplet(need)
if (!a || !b) return false
for (let i = 0; i < 3; i++) {
if (a[i] > b[i]) return true
if (a[i] < b[i]) return false
}
return true
}
/**
* @param {string} have
* @param {string} need
*/
function semverLte(have, need) {
const a = parseSemverTriplet(have)
const b = parseSemverTriplet(need)
if (!a || !b) return false
for (let i = 0; i < 3; i++) {
if (a[i] < b[i]) return true
if (a[i] > b[i]) return false
}
return true
}
/**
* @param {unknown} m
* @param {string} k
* @returns {number | undefined}
*/
function ctxKernelCapWord(m, k) {
if (!m || typeof m !== 'object') return undefined
const v = /** @type {Record<string, unknown>} */ (m)[k]
return typeof v === 'number' ? v : undefined
}
/**
* Merge only boot-stage skip fields from a policy fragment (tiered fallbacks / rollback marker).
* Honors canonical `skipBootStages` / `denyBootStages` and legacy `skipPhases` / `denyBootPhases`.
* @param {Set<string>} merge
* @param {Record<string, unknown>} pol
*/
function mergeBootPolicySkipStagesIntoSet(merge, pol) {
if (!pol || typeof pol !== 'object') return
if (Array.isArray(pol.skipBootStages)) {
for (const p of pol.skipBootStages) merge.add(String(p).toLowerCase())
}
if (Array.isArray(pol.skipPhases)) {
for (const p of pol.skipPhases) merge.add(String(p).toLowerCase())
}
if (Array.isArray(pol.denyBootStages)) {
for (const p of pol.denyBootStages) merge.add(String(p).toLowerCase())
}
if (Array.isArray(pol.denyBootPhases)) {
for (const p of pol.denyBootPhases) merge.add(String(p).toLowerCase())
}
}
/**
* Optional dry-run — parse boot snippets but skip trusted execLine / extension scripts.
* @param {Record<string, unknown>} ctx
*/
function bootDryRun(ctx) {
const v = ctx.env && ctx.env.BARE_OS_BOOT_DRY_RUN
return v === '1' || v === 'true'
}
/**
* Named boot stage for journals / checkpoints.
* @param {string} label
*/
function bootStageForPhase(label) {
const l = String(label).toLowerCase()
if (l === 'boot.policy') return 'policy'
if (l === 'os-release' || l === 'motd') return 'preflight'
if (
l === 'rc' ||
l.startsWith('rc.') ||
l === 'rc.d' ||
l === 'rc.local'
)
return 'rc'
if (l === 'kernel.d' || l === 'kernel.ext.d') return 'extensions'
if (l === 'onboot' || l === 'selftest') return 'services'
if (l === 'banner') return 'shell'
return 'misc'
}
/**
* kernel.d: leading `# ConditionEnvironment=KEY=VAL` / `# AssertEnvironment=…` guards.
* @param {Record<string, unknown>} ctx
* @param {string} text
*/
function kernelSnippetEnvGuardsOk(ctx, text) {
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
for (const raw of String(text).split(/\r?\n/)) {
const line = raw.trim()
if (!line.startsWith('#')) break
const m = line.match(
/^#\s*ConditionEnvironment=(.+)$/i
) || line.match(/^#\s*AssertEnvironment=(.+)$/i)
if (!m) continue
const expr = String(m[1]).trim()
const eq = expr.indexOf('=')
if (eq < 1) return false
const key = expr.slice(0, eq).trim()
const need = expr.slice(eq + 1).trim()
const have =
env[key] != null && env[key] !== undefined ? String(env[key]) : ''
if (have !== need) return false
}
return true
}
async function applyBootPolicyFile(ctx) {
const en = ctx.env && ctx.env.BARE_OS_BOOT_POLICY
if (en !== '1' && en !== 'true') return true
const merge =
ctx.bareOsBootPolicySkipStages instanceof Set
? ctx.bareOsBootPolicySkipStages
: ctx.bareOsBootPolicySkipPhases
if (!(merge instanceof Set)) return true
const { drive, b4a } = ctx
const strictPol =
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === '1' ||
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === 'true'
const advWords = ctx.bareOsAdvertisedKernelCapabilityWords
try {
const rb = ctx.env?.BARE_OS_BOOT_ROLLBACK_APPLY
if ((rb === '1' || rb === 'true') && ctx.vfs && typeof ctx.vfs.readFile === 'function') {
try {
const mbuf = await ctx.vfs.readFile('/run/bare-os/boot-rollback.marker')
const mobj = JSON.parse(b4a.toString(mbuf))
mergeBootPolicySkipStagesIntoSet(merge, mobj)
} catch {
/* ignore */
}
}
const policyPathRaw = String(ctx.env?.BARE_OS_BOOT_POLICY_PATH || '').trim()
const policyPath =
policyPathRaw && policyPathRaw.startsWith('/etc/bare-os/')
? policyPathRaw
: '/etc/bare-os/boot.policy.json'
const buf = await drive.get(policyPath)
if (!buf) return true
const pol = JSON.parse(b4a.toString(buf))
if (!pol || typeof pol !== 'object') return true
const legacySkip =
(Array.isArray(pol.skipPhases) && pol.skipPhases.length > 0) ||
(Array.isArray(pol.denyBootPhases) && pol.denyBootPhases.length > 0)
const modernSkip =
(Array.isArray(pol.skipBootStages) && pol.skipBootStages.length > 0) ||
(Array.isArray(pol.denyBootStages) && pol.denyBootStages.length > 0)
if (legacySkip && !modernSkip) {
bootStructuredLog(
ctx,
'warn',
'deprecatedSkipPhases',
'[boot-policy] deprecated: skipPhases/denyBootPhases in boot.policy.json — prefer skipBootStages/denyBootStages'
)
}
mergeBootPolicySkipStagesIntoSet(merge, pol)
if (Array.isArray(pol.policyFallbackPaths)) {
for (const rel of pol.policyFallbackPaths) {
const p2 = String(rel).trim()
if (!p2.startsWith('/etc/bare-os/')) continue
try {
const fb = await drive.get(p2)
if (!fb) continue
const pol2 = JSON.parse(b4a.toString(fb))
mergeBootPolicySkipStagesIntoSet(merge, pol2)
} catch (e) {
bootStructuredLog(
ctx,
'error',
'policyFallbackPaths',
'[boot-policy] policyFallbackPaths ' +
p2 +
': ' +
((e && e.message) || String(e))
)
}
}
}
if (
typeof pol.requireBootBundleSha256Hex === 'string' &&
String(pol.requireBootBundleSha256Hex).trim()
) {
const need = String(pol.requireBootBundleSha256Hex).trim().toLowerCase()
const have = String(ctx.env?.BARE_OS_BOOT_BUNDLE_DIGEST_HEX || '')
.trim()
.toLowerCase()
if (!have || have !== need) {
bootStructuredLog(
ctx,
'error',
'requireBootBundleSha256Hex',
'[boot-policy] requireBootBundleSha256Hex not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (pol.initdAdmission && typeof pol.initdAdmission === 'object' && ctx.env) {
const a = /** @type {Record<string, unknown>} */ (pol.initdAdmission)
if (typeof a.maxParallel === 'number' && a.maxParallel >= 1) {
ctx.env.BARE_OS_INITD_MAX_PARALLEL = String(
Math.min(32, Math.floor(a.maxParallel))
)
}
if (typeof a.memoryHintMb === 'number' && a.memoryHintMb >= 1) {
ctx.env.BARE_OS_INITD_MEMORY_HINT_MB = String(
Math.min(1e6, Math.floor(a.memoryHintMb))
)
}
if (typeof a.wallBudgetMsDefault === 'number' && a.wallBudgetMsDefault >= 1) {
ctx.env.BARE_OS_INITD_WALL_BUDGET_MS_DEFAULT = String(
Math.min(3.6e6, Math.floor(a.wallBudgetMsDefault))
)
}
}
if (pol.p2pAdmission && typeof pol.p2pAdmission === 'object' && ctx.env) {
const p2p = /** @type {Record<string, unknown>} */ (pol.p2pAdmission)
const allow = typeof p2p.peerAllowlistHex === 'string' ? p2p.peerAllowlistHex.trim() : ''
if (allow && !String(ctx.env.BARE_OS_PEER_ALLOWLIST_HEX || '').trim()) {
ctx.env.BARE_OS_PEER_ALLOWLIST_HEX = allow
}
const boot =
typeof p2p.hyperswarmBootstrap === 'string' ? p2p.hyperswarmBootstrap.trim() : ''
if (boot && !String(ctx.env.HYPERSWARM_BOOTSTRAP || '').trim()) {
ctx.env.HYPERSWARM_BOOTSTRAP = boot
}
}
if (
typeof pol.minKernelCapabilitiesPrimary === 'number' &&
pol.minKernelCapabilitiesPrimary > 0
) {
const adv = ctxKernelCapWord(advWords, 'primary')
const need = pol.minKernelCapabilitiesPrimary >>> 0
if (typeof adv === 'number') {
const ok = ((adv >>> 0) & need) === need
if (!ok) {
bootStructuredLog(
ctx,
'error',
'minKernelCapabilitiesPrimary',
'[boot-policy] minKernelCapabilitiesPrimary not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
}
if (typeof pol.requireSeedCaps === 'number' && pol.requireSeedCaps > 0) {
const seed = ctxKernelCapWord(ctx.bareOsSeedKernelCapabilityWords, 'primary')
const need = pol.requireSeedCaps >>> 0
const ok = typeof seed === 'number' && ((seed >>> 0) & need) === need
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireSeedCaps',
'[boot-policy] requireSeedCaps not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireKernelCapabilitiesExtendedSeedingPlatform === 'number' &&
pol.requireKernelCapabilitiesExtendedSeedingPlatform > 0
) {
const adv2 = ctxKernelCapWord(advWords, 'extendedSeedingPlatform')
const need2 = pol.requireKernelCapabilitiesExtendedSeedingPlatform >>> 0
const ok = typeof adv2 === 'number' && ((adv2 >>> 0) & need2) === need2
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireKernelCapabilitiesExtendedSeedingPlatform',
'[boot-policy] requireKernelCapabilitiesExtendedSeedingPlatform not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireKernelCapabilitiesRlimitsDelegatesShell === 'number' &&
pol.requireKernelCapabilitiesRlimitsDelegatesShell > 0
) {
const adv3 = ctxKernelCapWord(advWords, 'rlimitsDelegatesShell')
const need3 = pol.requireKernelCapabilitiesRlimitsDelegatesShell >>> 0
const ok = typeof adv3 === 'number' && ((adv3 >>> 0) & need3) === need3
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireKernelCapabilitiesRlimitsDelegatesShell',
'[boot-policy] requireKernelCapabilitiesRlimitsDelegatesShell not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireKernelCapabilitiesOfflineNetExtensions === 'number' &&
pol.requireKernelCapabilitiesOfflineNetExtensions > 0
) {
const adv4 = ctxKernelCapWord(advWords, 'offlineNetExtensions')
const need4 = pol.requireKernelCapabilitiesOfflineNetExtensions >>> 0
const ok = typeof adv4 === 'number' && ((adv4 >>> 0) & need4) === need4
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireKernelCapabilitiesOfflineNetExtensions',
'[boot-policy] requireKernelCapabilitiesOfflineNetExtensions not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireKernelCapabilitiesHostTransportDelegates === 'number' &&
pol.requireKernelCapabilitiesHostTransportDelegates > 0
) {
const adv5 = ctxKernelCapWord(advWords, 'hostTransportDelegates')
const need5 = pol.requireKernelCapabilitiesHostTransportDelegates >>> 0
const ok = typeof adv5 === 'number' && ((adv5 >>> 0) & need5) === need5
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireKernelCapabilitiesHostTransportDelegates',
'[boot-policy] requireKernelCapabilitiesHostTransportDelegates not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireKernelCapabilitiesReplicationOperatorSurface === 'number' &&
pol.requireKernelCapabilitiesReplicationOperatorSurface > 0
) {
const adv6 = ctxKernelCapWord(advWords, 'replicationOperatorSurface')
const need6 = pol.requireKernelCapabilitiesReplicationOperatorSurface >>> 0
const ok = typeof adv6 === 'number' && ((adv6 >>> 0) & need6) === need6
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireKernelCapabilitiesReplicationOperatorSurface',
'[boot-policy] requireKernelCapabilitiesReplicationOperatorSurface not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireKernelCapabilitiesPearCorestoreHrpc === 'number' &&
pol.requireKernelCapabilitiesPearCorestoreHrpc > 0
) {
const adv7 = ctxKernelCapWord(advWords, 'pearCorestoreHrpc')
const need7 = pol.requireKernelCapabilitiesPearCorestoreHrpc >>> 0
const ok = typeof adv7 === 'number' && ((adv7 >>> 0) & need7) === need7
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireKernelCapabilitiesPearCorestoreHrpc',
'[boot-policy] requireKernelCapabilitiesPearCorestoreHrpc not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireKernelCapabilitiesBareRuntimeProtoMux === 'number' &&
pol.requireKernelCapabilitiesBareRuntimeProtoMux > 0
) {
const adv8 = ctxKernelCapWord(advWords, 'bareRuntimeProtoMux')
const need8 = pol.requireKernelCapabilitiesBareRuntimeProtoMux >>> 0
const ok = typeof adv8 === 'number' && ((adv8 >>> 0) & need8) === need8
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireKernelCapabilitiesBareRuntimeProtoMux',
'[boot-policy] requireKernelCapabilitiesBareRuntimeProtoMux not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireKernelCapabilitiesBareModuleCryptoStaging === 'number' &&
pol.requireKernelCapabilitiesBareModuleCryptoStaging > 0
) {
const adv9 = ctxKernelCapWord(advWords, 'bareModuleCryptoStaging')
const need9 = pol.requireKernelCapabilitiesBareModuleCryptoStaging >>> 0
const ok = typeof adv9 === 'number' && ((adv9 >>> 0) & need9) === need9
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireKernelCapabilitiesBareModuleCryptoStaging',
'[boot-policy] requireKernelCapabilitiesBareModuleCryptoStaging not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireKernelCapabilitiesPearInspectLoggerTls === 'number' &&
pol.requireKernelCapabilitiesPearInspectLoggerTls > 0
) {
const adv10 = ctxKernelCapWord(advWords, 'pearInspectLoggerTls')
const need10 = pol.requireKernelCapabilitiesPearInspectLoggerTls >>> 0
const ok =
typeof adv10 === 'number' && ((adv10 >>> 0) & need10) === need10
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireKernelCapabilitiesPearInspectLoggerTls',
'[boot-policy] requireKernelCapabilitiesPearInspectLoggerTls not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireKernelCapabilitiesHypercorePackHrpcLifecycle === 'number' &&
pol.requireKernelCapabilitiesHypercorePackHrpcLifecycle > 0
) {
const adv11 = ctxKernelCapWord(advWords, 'hypercorePackHrpcLifecycle')
const need11 = pol.requireKernelCapabilitiesHypercorePackHrpcLifecycle >>> 0
const ok =
typeof adv11 === 'number' && ((adv11 >>> 0) & need11) === need11
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireKernelCapabilitiesHypercorePackHrpcLifecycle',
'[boot-policy] requireKernelCapabilitiesHypercorePackHrpcLifecycle not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
pol.requirePearRuntimeRange &&
typeof pol.requirePearRuntimeRange === 'object'
) {
const r = pol.requirePearRuntimeRange
const minS = typeof r.min === 'string' ? r.min.trim() : ''
const maxS = typeof r.max === 'string' ? r.max.trim() : ''
const haveS =
typeof ctx.bareOsPearRuntimeVersion === 'string'
? ctx.bareOsPearRuntimeVersion.trim()
: ''
let ok = Boolean(haveS)
if (ok && minS) ok = semverGte(haveS, minS)
if (ok && maxS) ok = semverLte(haveS, maxS)
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requirePearRuntimeRange',
'[boot-policy] requirePearRuntimeRange not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireBareRuntimeMin === 'string' &&
String(pol.requireBareRuntimeMin).trim()
) {
const needS = String(pol.requireBareRuntimeMin).trim()
const haveS =
typeof ctx.bareOsBareRuntimeVersion === 'string'
? ctx.bareOsBareRuntimeVersion.trim()
: ''
const ok = haveS && semverGte(haveS, needS)
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireBareRuntimeMin',
'[boot-policy] requireBareRuntimeMin not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requirePearRuntimeMin === 'string' &&
String(pol.requirePearRuntimeMin).trim()
) {
const needS = String(pol.requirePearRuntimeMin).trim()
const haveS =
typeof ctx.bareOsPearRuntimeVersion === 'string'
? ctx.bareOsPearRuntimeVersion.trim()
: ''
const ok = haveS && semverGte(haveS, needS)
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requirePearRuntimeMin',
'[boot-policy] requirePearRuntimeMin not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireProtocolPackageMin === 'string' &&
String(pol.requireProtocolPackageMin).trim()
) {
const needS = String(pol.requireProtocolPackageMin).trim()
const haveS =
typeof ctx.bareOsProtocolPackageVersion === 'string'
? ctx.bareOsProtocolPackageVersion.trim()
: ''
const ok = haveS && semverGte(haveS, needS)
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireProtocolPackageMin',
'[boot-policy] requireProtocolPackageMin not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireBooterSemver === 'string' &&
String(pol.requireBooterSemver).trim()
) {
const needS = String(pol.requireBooterSemver).trim()
const haveS =
typeof ctx.bareOsBooterPackageVersion === 'string'
? ctx.bareOsBooterPackageVersion.trim()
: ''
const ok = haveS && semverGte(haveS, needS)
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireBooterSemver',
'[boot-policy] requireBooterSemver not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireCtxApiMin === 'string' &&
String(pol.requireCtxApiMin).trim()
) {
const needS = String(pol.requireCtxApiMin).trim()
const haveS =
typeof ctx.bareOsCtxApiVersion === 'string'
? ctx.bareOsCtxApiVersion.trim()
: String(ctx.bareOsCtxApiVersion || '').trim()
const ok = haveS && semverGte(haveS, needS)
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireCtxApiMin',
'[boot-policy] requireCtxApiMin not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (Array.isArray(pol.denyKernelExtensionIds) && ctx.env) {
const parts = pol.denyKernelExtensionIds
.map((x) => String(x).trim())
.filter(Boolean)
if (parts.length)
ctx.env.BARE_OS_BOOT_POLICY_DENY_KERNEL_EXT_IDS = parts.join(',')
}
if (
pol.kernelExtensionHashPins &&
typeof pol.kernelExtensionHashPins === 'object' &&
ctx.env
) {
try {
ctx.env.BARE_OS_BOOT_POLICY_EXTENSION_HASH_PINS_JSON = JSON.stringify(
pol.kernelExtensionHashPins
)
} catch {
/* ignore */
}
}
if (pol.offlineLkgIntegrityStrict === true && ctx.env) {
ctx.env.BARE_OS_OFFLINE_LKG_INTEGRITY_STRICT = '1'
}
if (
typeof pol.requireInitJsSha256 === 'string' &&
String(pol.requireInitJsSha256).trim()
) {
const need = String(pol.requireInitJsSha256).trim().toLowerCase()
const buf = await drive.get('/boot/init.js')
const hashFn = ctx.bareOsBootFileSha256Hex
if (typeof hashFn !== 'function') {
bootStructuredLog(
ctx,
'error',
'requireInitJsSha256MissingHasher',
'[boot-policy] requireInitJsSha256 needs ctx.bareOsBootFileSha256Hex'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
} else {
const hex = hashFn(buf || new Uint8Array())
if (hex !== need) {
bootStructuredLog(
ctx,
'error',
'requireInitJsSha256Mismatch',
'[boot-policy] requireInitJsSha256 mismatch for /boot/init.js'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
}
if (Array.isArray(pol.allowedPearIpcChannels) && ctx.env) {
const parts = pol.allowedPearIpcChannels
.map((c) => String(c).trim())
.filter(Boolean)
if (parts.length)
ctx.env.BARE_OS_BOOT_POLICY_PEAR_IPC_CHANNELS = parts.join(',')
}
if (Array.isArray(pol.denyVfsPrefixes) && ctx.env) {
const parts = pol.denyVfsPrefixes
.map((p) => String(p).trim())
.filter(Boolean)
if (parts.length) ctx.env.BARE_OS_BOOT_POLICY_DENY_VFS = parts.join(',')
}
if (Array.isArray(pol.denyExecLineBuiltins) && ctx.env) {
const parts = pol.denyExecLineBuiltins
.map((b) => String(b).trim())
.filter(Boolean)
if (parts.length) {
ctx.env.BARE_OS_BOOT_POLICY_DENY_EXEC_LINE_BUILTINS =
parts.join(',')
}
}
if (Array.isArray(pol.allowedCtxMethods) && ctx.env) {
const parts = pol.allowedCtxMethods
.map((m) => String(m).trim())
.filter(Boolean)
if (parts.length) {
ctx.env.BARE_OS_BOOT_POLICY_ALLOWED_CTX_METHODS = parts.join(',')
}
}
if (Array.isArray(pol.denyCtxMethodPrefixes) && ctx.env) {
const parts = pol.denyCtxMethodPrefixes
.map((m) => String(m).trim())
.filter(Boolean)
if (parts.length) {
ctx.env.BARE_OS_BOOT_POLICY_DENY_CTX_PREFIXES = parts.join(',')
}
}
if (Array.isArray(pol.denySeedRpcMethods) && ctx.env) {
const parts = pol.denySeedRpcMethods
.map((m) => String(m).trim())
.filter(Boolean)
const existing = String(
ctx.env.BARE_OS_BOOT_POLICY_DENY_SEED_RPC_METHODS || ''
)
.split(',')
.map((s) => s.trim())
.filter(Boolean)
const merged = [...new Set([...existing, ...parts])]
if (merged.length) {
ctx.env.BARE_OS_BOOT_POLICY_DENY_SEED_RPC_METHODS = merged.join(',')
}
}
if (
Array.isArray(pol.denyBareModuleSpecifierPatterns) &&
ctx.env
) {
try {
ctx.env.BARE_OS_BOOT_POLICY_DENY_BARE_MODULE_SPECS_JSON =
JSON.stringify(pol.denyBareModuleSpecifierPatterns)
} catch {
/* ignore */
}
}
if (
Array.isArray(pol.denyKernelSyscalls) &&
ctx.env
) {
const parts = pol.denyKernelSyscalls
.map((s) => String(s).trim())
.filter(Boolean)
if (parts.length) {
ctx.env.BARE_OS_BOOT_POLICY_DENY_KERNEL_SYSCALLS = parts.join(',')
}
}
if (
typeof pol.requireBareCryptoMin === 'string' &&
String(pol.requireBareCryptoMin).trim()
) {
const needS = String(pol.requireBareCryptoMin).trim()
const haveS =
ctx.env && String(ctx.env.BARE_OS_BARE_CRYPTO_VERSION || '').trim()
const ok = haveS && semverGte(haveS, needS)
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireBareCryptoMin',
'[boot-policy] requireBareCryptoMin not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requirePearIpcMin === 'string' &&
String(pol.requirePearIpcMin).trim()
) {
const needS = String(pol.requirePearIpcMin).trim()
const haveS =
ctx.env && String(ctx.env.BARE_OS_PEAR_IPC_PACKAGE_VERSION || '').trim()
const ok = haveS && semverGte(haveS, needS)
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requirePearIpcMin',
'[boot-policy] requirePearIpcMin not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
pol.extensionSignerPinsV2 &&
typeof pol.extensionSignerPinsV2 === 'object' &&
ctx.env
) {
try {
ctx.env.BARE_OS_BOOT_POLICY_EXTENSION_SIGNER_PINS_V2_JSON =
JSON.stringify(pol.extensionSignerPinsV2)
} catch {
/* ignore */
}
}
if (
pol.extensionSignerPinsV3 &&
typeof pol.extensionSignerPinsV3 === 'object' &&
ctx.env
) {
try {
ctx.env.BARE_OS_BOOT_POLICY_EXTENSION_SIGNER_PINS_V3_JSON =
JSON.stringify(pol.extensionSignerPinsV3)
} catch {
/* ignore */
}
}
if (
pol.extensionSignerPinsV4 &&
typeof pol.extensionSignerPinsV4 === 'object' &&
ctx.env
) {
try {
ctx.env.BARE_OS_BOOT_POLICY_EXTENSION_SIGNER_PINS_V4_JSON =
JSON.stringify(pol.extensionSignerPinsV4)
} catch {
/* ignore */
}
}
if (
pol.extensionSignerPinsV5 &&
typeof pol.extensionSignerPinsV5 === 'object' &&
ctx.env
) {
try {
ctx.env.BARE_OS_BOOT_POLICY_EXTENSION_SIGNER_PINS_V5_JSON =
JSON.stringify(pol.extensionSignerPinsV5)
} catch {
/* ignore */
}
}
if (
typeof pol.requireBareBootMin === 'string' &&
String(pol.requireBareBootMin).trim()
) {
const needS = String(pol.requireBareBootMin).trim()
const haveS =
ctx.env && String(ctx.env.BARE_OS_BARE_BOOT_VERSION || '').trim()
const ok = haveS && semverGte(haveS, needS)
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireBareBootMin',
'[boot-policy] requireBareBootMin not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
Array.isArray(pol.denyBareRpcMethodPatterns) &&
ctx.env
) {
try {
ctx.env.BARE_OS_BOOT_POLICY_DENY_BARE_RPC_PATTERNS_JSON =
JSON.stringify(pol.denyBareRpcMethodPatterns)
} catch {
/* ignore */
}
}
if (
typeof pol.maxPearInspectDepth === 'number' &&
pol.maxPearInspectDepth >= 0 &&
ctx.env
) {
ctx.env.BARE_OS_BOOT_POLICY_MAX_PEAR_INSPECT_DEPTH = String(
Math.min(64, Math.floor(pol.maxPearInspectDepth))
)
}
if (
typeof pol.maxHrpcAllowlistDepth === 'number' &&
pol.maxHrpcAllowlistDepth >= 0 &&
ctx.env
) {
ctx.env.BARE_OS_BOOT_POLICY_MAX_HRPC_ALLOWLIST_DEPTH = String(
Math.min(64, Math.floor(pol.maxHrpcAllowlistDepth))
)
}
if (
typeof pol.requireBareLoggerMin === 'string' &&
String(pol.requireBareLoggerMin).trim()
) {
const needS = String(pol.requireBareLoggerMin).trim()
const haveS =
ctx.env && String(ctx.env.BARE_OS_BARE_LOGGER_VERSION || '').trim()
const ok = haveS && semverGte(haveS, needS)
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireBareLoggerMin',
'[boot-policy] requireBareLoggerMin not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
Array.isArray(pol.denyAutobaseDiscoveryChannels) &&
ctx.env
) {
try {
ctx.env.BARE_OS_BOOT_POLICY_DENY_AUTOBASE_DISCOVERY_CHANNELS_JSON =
JSON.stringify(pol.denyAutobaseDiscoveryChannels)
} catch {
/* ignore */
}
}
if (
typeof pol.requireBareTlsMin === 'string' &&
String(pol.requireBareTlsMin).trim()
) {
const needS = String(pol.requireBareTlsMin).trim()
const haveS =
ctx.env && String(ctx.env.BARE_OS_BARE_TLS_VERSION || '').trim()
const ok = haveS && semverGte(haveS, needS)
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireBareTlsMin',
'[boot-policy] requireBareTlsMin not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireBarePackMin === 'string' &&
String(pol.requireBarePackMin).trim()
) {
const needS = String(pol.requireBarePackMin).trim()
const haveS =
ctx.env && String(ctx.env.BARE_OS_BARE_PACK_VERSION || '').trim()
const ok = haveS && semverGte(haveS, needS)
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireBarePackMin',
'[boot-policy] requireBarePackMin not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (
typeof pol.requireBareAddonPolicyMin === 'string' &&
String(pol.requireBareAddonPolicyMin).trim()
) {
const needS = String(pol.requireBareAddonPolicyMin).trim()
const haveS =
ctx.env &&
String(ctx.env.BARE_OS_BARE_ADDON_POLICY_VERSION || '').trim()
const ok = haveS && semverGte(haveS, needS)
if (!ok) {
bootStructuredLog(
ctx,
'error',
'requireBareAddonPolicyMin',
'[boot-policy] requireBareAddonPolicyMin not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
if (pol.offlineLkgRequirePearStamp === true && ctx.env) {
ctx.env.BARE_OS_OFFLINE_LKG_REQUIRE_PEAR_STAMP = '1'
}
if (pol.offlineLkgRequireHypercorePackHrpcLifecycle === true && ctx.env) {
ctx.env.BARE_OS_OFFLINE_LKG_REQUIRE_HYPERCORE_PACK_HRPC_LIFECYCLE = '1'
}
if (
typeof pol.offlineLkgManifestMaxAgeSec === 'number' &&
pol.offlineLkgManifestMaxAgeSec > 0 &&
ctx.env
) {
ctx.env.BARE_OS_OFFLINE_LKG_MANIFEST_MAX_AGE_SEC = String(
Math.min(1e9, Math.floor(pol.offlineLkgManifestMaxAgeSec))
)
}
if (
typeof pol.maxProtomuxChannelNameLength === 'number' &&
pol.maxProtomuxChannelNameLength >= 8 &&
ctx.env
) {
const n = Math.min(512, Math.floor(pol.maxProtomuxChannelNameLength))
ctx.env.BARE_OS_BOOT_POLICY_MAX_PROTO_MUX_CHANNEL_NAME_LENGTH = String(n)
}
if (
typeof pol.maxKernelExtensionDepth === 'number' &&
pol.maxKernelExtensionDepth >= 1 &&
ctx.env
) {
const d = Math.min(32, Math.floor(pol.maxKernelExtensionDepth))
ctx.env.BARE_OS_BOOT_POLICY_MAX_KERNEL_EXT_DEPTH = String(d)
}
if (
typeof pol.gitPartialClonePolicy === 'string' &&
String(pol.gitPartialClonePolicy).trim() &&
ctx.env
) {
ctx.env.BARE_OS_BOOT_POLICY_GIT_PARTIAL_CLONE = String(
pol.gitPartialClonePolicy
).trim()
}
if (
typeof pol.maxInitdRestartsPerUnit === 'number' &&
pol.maxInitdRestartsPerUnit >= 1 &&
ctx.env
) {
const cap = Math.min(32, Math.floor(pol.maxInitdRestartsPerUnit))
ctx.env.BARE_OS_INITD_RESTART_MAX_DEFAULT = String(cap)
}
if (
typeof pol.maxExecLineDepth === 'number' &&
pol.maxExecLineDepth > 0 &&
ctx.env
) {
const cap = Math.min(256, Math.floor(pol.maxExecLineDepth))
ctx.env.BARE_OS_EXEC_MAX_DEPTH = String(cap)
}
if (Array.isArray(pol.denyEnvKeys) && ctx.env) {
for (const k of pol.denyEnvKeys) {
const key = String(k).trim()
if (key && Object.prototype.hasOwnProperty.call(ctx.env, key)) {
delete ctx.env[key]
}
}
}
const needLifecycleSchema = Math.max(
typeof pol.bootStagesRequireLifecycleMinSchema === 'number'
? pol.bootStagesRequireLifecycleMinSchema
: 0,
typeof pol.bootPhasesRequireLifecycleMinSchema === 'number'
? pol.bootPhasesRequireLifecycleMinSchema
: 0
)
if (needLifecycleSchema > 0) {
const needSch = Math.floor(needLifecycleSchema)
const raw = ctx.env && String(ctx.env.BARE_OS_LIFECYCLE_SCHEMA_VERSION || '').trim()
const haveSch = Number.parseInt(raw, 10)
const ok = Number.isFinite(haveSch) && haveSch >= needSch
if (!ok) {
bootStructuredLog(
ctx,
'error',
'bootStagesRequireLifecycleMinSchema',
'[boot-policy] bootStagesRequireLifecycleMinSchema not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
const needProcIndexSchema = Math.max(
typeof pol.bootStagesRequireProcIndexMinSchema === 'number'
? pol.bootStagesRequireProcIndexMinSchema
: 0,
typeof pol.bootPhasesRequireProcIndexMinSchema === 'number'
? pol.bootPhasesRequireProcIndexMinSchema
: 0
)
if (needProcIndexSchema > 0) {
const vfs = ctx.vfs
const needSch = Math.floor(needProcIndexSchema)
if (vfs && typeof vfs.readFile === 'function') {
try {
const raw = await vfs.readFile('/proc/bare_os/index.json')
const txt =
raw && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(raw)
: ''
const j = txt ? JSON.parse(txt) : null
const sch =
j && typeof j === 'object' && typeof j.schema === 'number'
? j.schema
: 0
if (sch < needSch) throw new Error('proc_index schema too old')
} catch {
bootStructuredLog(
ctx,
'error',
'bootStagesRequireProcIndexMinSchema',
'[boot-policy] bootStagesRequireProcIndexMinSchema not satisfied'
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
}
if (Array.isArray(pol.requireProcNodes)) {
const vfs = ctx.vfs
if (vfs && typeof vfs.readFile === 'function') {
for (const rel of pol.requireProcNodes) {
const raw = String(rel).trim()
const p = raw.startsWith('/')
? raw
: '/proc/' + raw.replace(/^\/*/, '')
try {
const b = await vfs.readFile(p)
const empty =
!b ||
(ctx.b4a &&
typeof ctx.b4a.from === 'function' &&
ctx.b4a.from(b).length === 0)
if (empty) throw new Error('empty or missing')
} catch {
bootStructuredLog(
ctx,
'error',
'requireProcNodes',
'[boot-policy] requireProcNodes not satisfied: ' + p
)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
}
}
} catch (e) {
bootStructuredLog(
ctx,
'error',
'bootPolicyParse',
'[boot-policy] boot.policy.json: ' + ((e && e.message) || String(e))
)
}
return true
}
/**
* Concatenated into /boot/init.js before init-main.js by scripts/bundle-kernel-init.mjs.
* kernel.ext.d: topological sort, signer pins, multisig gate, runKernelExtDropins.
* Depends on: BARE_OS_BOOT_TXN_STATE (boot preamble), bareOsPearMultisigShapeOk (00-pear-multisig-shape),
* semverGte from 20-init-boot-policy.js; bootStructuredLog/bootDryRun from init-main.js (same AsyncFunction body; hoisted).
* @see kernel/lib/init/STRUCTURE.md
*/
/**
* @param {{ file: string, extId: string, scripts: string[], dependsOn: string[], signaturePointer?: string }[]} entries
* @returns {{ ok: true, ordered: typeof entries } | { ok: false, cycleExtIds: string[], cycleEdges: string[] }}
*/
function topologicalOrderKernelExtEntries(entries) {
const sorted = [...entries].sort((a, b) => a.file.localeCompare(b.file))
const idToFile = new Map()
for (const e of sorted) {
if (!idToFile.has(e.extId)) idToFile.set(e.extId, e.file)
}
/** @type {Map<string, string[]>} */
const adj = new Map()
/** @type {Map<string, number>} */
const indeg = new Map()
for (const e of sorted) indeg.set(e.file, 0)
for (const e of sorted) {
for (const dep of e.dependsOn) {
const from = idToFile.get(dep)
if (!from || from === e.file) continue
if (!adj.has(from)) adj.set(from, [])
adj.get(from).push(e.file)
indeg.set(e.file, (indeg.get(e.file) || 0) + 1)
}
}
/** @type {string[]} */
const q = sorted
.map((e) => e.file)
.filter((f) => (indeg.get(f) || 0) === 0)
.sort()
/** @type {string[]} */
const out = []
while (q.length) {
const f = q.shift()
out.push(f)
for (const to of adj.get(f) || []) {
indeg.set(to, (indeg.get(to) || 0) - 1)
if (indeg.get(to) === 0) {
q.push(to)
q.sort()
}
}
}
if (out.length !== sorted.length) {
const stuck = sorted.filter((e) => !out.includes(e.file))
const stuckIds = new Set(stuck.map((e) => e.extId))
/** @type {string[]} */
const cycleEdges = []
for (const e of stuck) {
for (const dep of e.dependsOn) {
if (stuckIds.has(dep))
cycleEdges.push(String(e.extId) + ' -> dependsOn:' + String(dep))
}
}
cycleEdges.sort()
return {
ok: false,
cycleExtIds: stuck.map((e) => e.extId),
cycleEdges: cycleEdges.slice(0, 48)
}
}
const byFile = new Map(sorted.map((e) => [e.file, e]))
return {
ok: true,
ordered: out.map((f) => byFile.get(f)).filter(Boolean)
}
}
/**
* @param {{ extId: string, dependsOn: string[] }[]} entries
* @returns {number}
*/
function kernelExtDependencyDepth(entries) {
const idToEntry = new Map(entries.map((e) => [e.extId, e]))
const memo = new Map()
/**
* @param {string} id
* @param {Set<string>} stack
*/
function depth(id, stack) {
if (memo.has(id)) return memo.get(id)
if (stack.has(id)) return 99
const e = idToEntry.get(id)
if (!e) return 0
stack.add(id)
let d = 0
for (const dep of e.dependsOn) {
d = Math.max(d, depth(dep, stack) + 1)
}
stack.delete(id)
memo.set(id, d)
return d
}
let max = 0
for (const e of entries) {
max = Math.max(max, depth(e.extId, new Set()))
}
return max
}
/**
* Merge extension signer pin maps from boot policy env mirrors (V2V5; later JSON wins per key).
* @param {Record<string, unknown> | null | undefined} env
* @returns {Record<string, string | string[]>}
*/
function mergeExtensionSignerPinsFromEnv(env) {
if (!env || typeof env !== 'object') return {}
const keys = [
'BARE_OS_BOOT_POLICY_EXTENSION_SIGNER_PINS_V2_JSON',
'BARE_OS_BOOT_POLICY_EXTENSION_SIGNER_PINS_V3_JSON',
'BARE_OS_BOOT_POLICY_EXTENSION_SIGNER_PINS_V4_JSON',
'BARE_OS_BOOT_POLICY_EXTENSION_SIGNER_PINS_V5_JSON'
]
/** @type {Record<string, string | string[]>} */
const out = {}
for (const k of keys) {
const raw = String(env[k] ?? '').trim()
if (!raw) continue
try {
const o = JSON.parse(raw)
if (o && typeof o === 'object' && !Array.isArray(o)) {
for (const [ik, iv] of Object.entries(o)) {
out[String(ik)] = /** @type {string | string[]} */ (iv)
}
}
} catch {
/* ignore malformed JSON */
}
}
return out
}
/**
* When boot.policy pins an extension id to Ed25519 key(s), verify detached signature over script bytes.
* @param {Record<string, unknown>} ctx
* @param {{ extId: string, file: string, signaturePointer?: string }} ent
* @param {string} imgPath
* @param {Record<string, string | string[]>} pins
* @param {boolean} strictPol
* @returns {Promise<boolean>} true if the script may run
*/
async function verifyKernelExtSignerPinsForScript(
ctx,
ent,
imgPath,
pins,
strictPol
) {
const id = String(ent.extId || '').trim()
const need = pins[id]
if (need == null) return true
/** @type {string[]} */
const pubHexList = Array.isArray(need)
? need.map((x) =>
String(x)
.trim()
.toLowerCase()
.replace(/^0x/, '')
)
: [String(need).trim().toLowerCase().replace(/^0x/, '')]
const validKeys = pubHexList.filter(
(h) => h.length === 64 && /^[0-9a-f]+$/.test(h)
)
if (!validKeys.length) {
bootStructuredLog(
ctx,
'error',
'kernelExt.signerPinBadKey',
`[kernel.ext.d] extensionSignerPins for "${id}" must be 64-char hex pubkey(s)`
)
return !strictPol
}
const sigPtr = ent.signaturePointer
? String(ent.signaturePointer).trim()
: ''
if (!sigPtr.startsWith('/')) {
bootStructuredLog(
ctx,
'error',
'kernelExt.signerPinNoSigPath',
`[kernel.ext.d] "${id}": signaturePointer (absolute path to signature) required when extensionSignerPins lists this id`
)
return !strictPol
}
const { drive, b4a } = ctx
if (!drive || typeof drive.get !== 'function' || !b4a) {
bootStructuredLog(
ctx,
'error',
'kernelExt.signerPinNoDrive',
'[kernel.ext.d] signer pin verify requires ctx.drive.get and ctx.b4a'
)
return !strictPol
}
let scriptBuf
let sigBuf
try {
scriptBuf = await drive.get(imgPath)
sigBuf = await drive.get(sigPtr)
} catch (e) {
bootStructuredLog(
ctx,
'error',
'kernelExt.signerPinRead',
`[kernel.ext.d] signer pin read: ${(e && e.message) || String(e)}`
)
return !strictPol
}
if (!scriptBuf || !scriptBuf.byteLength || !sigBuf || !sigBuf.byteLength) {
bootStructuredLog(
ctx,
'error',
'kernelExt.signerPinMissingBytes',
`[kernel.ext.d] "${id}": script or signature file missing/empty for pin verify`
)
return !strictPol
}
const verifyFn = ctx.bareOsVerifyBootManifestSignature
if (typeof verifyFn !== 'function') {
bootStructuredLog(
ctx,
'error',
'kernelExt.signerPinNoVerify',
'[kernel.ext.d] ctx.bareOsVerifyBootManifestSignature unavailable; cannot enforce extensionSignerPins'
)
return !strictPol
}
for (const pk of validKeys) {
try {
if (verifyFn.call(ctx, scriptBuf, sigBuf, pk) === true) return true
} catch {
/* try next pubkey */
}
}
bootStructuredLog(
ctx,
'error',
'kernelExt.signerPinVerifyFailed',
`[kernel.ext.d] "${id}": Ed25519 verify failed for pinned key(s)`
)
return !strictPol
}
/**
* Append one NDJSON line to `/run/bare-os/kernel-ext-audit.ndjson` (strict boot diagnostics).
* @param {Record<string, unknown>} ctx
* @param {Record<string, unknown>} row
*/
async function appendKernelExtAuditNdjson(ctx, row) {
if (
!ctx.vfs ||
typeof ctx.vfs.readFile !== 'function' ||
typeof ctx.vfs.writeFile !== 'function' ||
!ctx.b4a
) {
return
}
const line =
JSON.stringify({
schema: 1,
type: 'kernelExtAudit',
atMs: Date.now(),
sessionId: String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''),
...row
}) + '\n'
try {
let prev = ''
try {
prev = ctx.b4a.toString(
await ctx.vfs.readFile('/run/bare-os/kernel-ext-audit.ndjson')
)
} catch {
/* absent */
}
const maxBytes = 96 * 1024
let next = prev + line
if (next.length > maxBytes) next = next.slice(-maxBytes)
await ctx.vfs.writeFile(
'/run/bare-os/kernel-ext-audit.ndjson',
ctx.b4a.from(next)
)
} catch {
/* ignore */
}
}
/**
* Optional `/etc/bare-os/kernel.ext.d/*.json` with `{ "scripts": ["/lib/bare-os/extensions/foo.js"] }`.
* @param {Record<string, unknown>} ctx
* @param {{ incremental?: boolean, ranScripts?: string[] }} [opts]
* @returns {Promise<boolean>}
*/
async function runKernelExtDropins(ctx, opts = {}) {
const incremental = opts.incremental === true
const ranScripts = opts.ranScripts
const { drive } = ctx
const run = ctx.bareOsRunImageScript
if (typeof run !== 'function') return true
if (!ctx.bareOsLoadedKernelExtScripts) {
ctx.bareOsLoadedKernelExtScripts = new Set()
}
/** @type {Set<string>} */
const loadedSet = /** @type {Set<string>} */ (ctx.bareOsLoadedKernelExtScripts)
const denyRaw = String(
ctx.env?.BARE_OS_BOOT_POLICY_DENY_KERNEL_EXT_IDS || ''
).trim()
const deny = new Set(
denyRaw
.split(',')
.map((s) => s.trim())
.filter(Boolean)
)
const strictPol =
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === '1' ||
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === 'true'
const extSignerPins = mergeExtensionSignerPinsFromEnv(ctx.env)
const multisigExtGate =
ctx.env?.BARE_OS_EXTENSION_MULTISIG_VERIFY === '1' ||
ctx.env?.BARE_OS_EXTENSION_MULTISIG_VERIFY === 'true' ||
ctx.env?.BARE_OS_BOOT_POLICY_REQUIRE_PEAR_MULTISIG === '1' ||
ctx.env?.BARE_OS_BOOT_POLICY_REQUIRE_PEAR_MULTISIG === 'true'
if (multisigExtGate) {
const requireFile =
ctx.env?.BARE_OS_BOOT_POLICY_REQUIRE_PEAR_MULTISIG === '1' ||
ctx.env?.BARE_OS_BOOT_POLICY_REQUIRE_PEAR_MULTISIG === 'true'
try {
const mbuf = await drive.get('/etc/bare-os/pear.multisig.json')
if (!mbuf || mbuf.byteLength === 0) {
if (strictPol && requireFile) {
bootStructuredLog(
ctx,
'error',
'kernelExt.multisigMissing',
'[kernel.ext.d] strict boot requires /etc/bare-os/pear.multisig.json (BARE_OS_BOOT_POLICY_REQUIRE_PEAR_MULTISIG)'
)
if (typeof ctx.bareOsRequestBooterExit === 'function')
ctx.bareOsRequestBooterExit(1)
return false
}
} else {
const mj = JSON.parse(ctx.b4a.toString(mbuf))
if (!bareOsPearMultisigShapeOk(mj)) {
bootStructuredLog(
ctx,
'error',
'kernelExt.multisigInvalid',
'[kernel.ext.d] pear.multisig.json must be { signers: string[], quorum: number } with 1 ≤ quorum ≤ signers.length'
)
if (strictPol) {
if (typeof ctx.bareOsRequestBooterExit === 'function')
ctx.bareOsRequestBooterExit(1)
return false
}
}
}
} catch (e) {
bootStructuredLog(
ctx,
'warn',
'kernelExt.multisigRead',
'[kernel.ext.d] pear.multisig.json: ' + ((e && e.message) || String(e))
)
if (strictPol && requireFile) {
if (typeof ctx.bareOsRequestBooterExit === 'function')
ctx.bareOsRequestBooterExit(1)
return false
}
}
}
const maxDepthRaw = String(
ctx.env?.BARE_OS_BOOT_POLICY_MAX_KERNEL_EXT_DEPTH || ''
).trim()
const maxDepthCap =
maxDepthRaw && Number.parseInt(maxDepthRaw, 10) > 0
? Math.min(32, Number.parseInt(maxDepthRaw, 10))
: 0
/** @type {string[]} */
const names = []
try {
for await (const n of drive.readdir('/etc/bare-os/kernel.ext.d'))
names.push(n)
} catch {
return true
}
names.sort()
/** @type {{ file: string, extId: string, scripts: string[], dependsOn: string[], beforeIds: string[], conflictsWith: string[], signaturePointer?: string, provides: { name: string, version: string }[] }[]} */
const collected = []
/** @type {Map<string, string[]>} */
const extIdFiles = new Map()
for (const name of names) {
if (!name.endsWith('.json')) continue
const p = `/etc/bare-os/kernel.ext.d/${name}`
try {
const buf = await drive.get(p)
if (!buf) continue
const pol = JSON.parse(ctx.b4a.toString(buf))
if (!pol || typeof pol !== 'object') continue
const extId =
pol.id != null
? String(pol.id).trim()
: name.replace(/\.json$/i, '')
if (deny.has(extId)) {
bootStructuredLog(
ctx,
'error',
'kernelExt.denyId',
`[kernel.ext.d] denied by policy id: ${extId}`
)
await appendKernelExtAuditNdjson(ctx, {
event: 'deny_id',
extId,
dropin: name
})
continue
}
const scripts = pol.scripts
if (!Array.isArray(scripts)) continue
/** @type {string[]} */
let dependsOn = Array.isArray(pol.dependsOn)
? pol.dependsOn.map((d) => String(d).trim()).filter(Boolean).slice(0, 16)
: []
if (Array.isArray(pol.requires)) {
dependsOn = dependsOn.concat(
pol.requires.map((d) => String(d).trim()).filter(Boolean).slice(0, 16)
)
}
if (Array.isArray(pol.after)) {
dependsOn = dependsOn.concat(
pol.after.map((d) => String(d).trim()).filter(Boolean).slice(0, 16)
)
}
const beforeIds = Array.isArray(pol.before)
? pol.before.map((d) => String(d).trim()).filter(Boolean).slice(0, 16)
: []
const sig =
typeof pol.signaturePointer === 'string'
? pol.signaturePointer.trim().slice(0, 512)
: undefined
const minExtCtx =
typeof pol.minCtxApiVersion === 'string'
? pol.minCtxApiVersion.trim()
: ''
if (minExtCtx) {
const haveS =
typeof ctx.bareOsCtxApiVersion === 'string'
? ctx.bareOsCtxApiVersion.trim()
: String(ctx.bareOsCtxApiVersion || '').trim()
if (!haveS || !semverGte(haveS, minExtCtx)) {
bootStructuredLog(
ctx,
'error',
'kernelExt.minCtxApiVersion',
`[kernel.ext.d] ${name}: minCtxApiVersion ${minExtCtx} not satisfied (have ${haveS || 'none'})`
)
if (strictPol) return false
continue
}
}
const conflictsWith = Array.isArray(pol.conflictsWith)
? pol.conflictsWith.map((d) => String(d).trim()).filter(Boolean).slice(0, 16)
: []
/** @type {{ name: string, version: string }[]} */
const provides = Array.isArray(pol.provides)
? pol.provides
.map((x) => {
if (!x || typeof x !== 'object') return null
const nm = String(x.name || '').trim()
const ver = String(x.version ?? x.semver ?? '').trim()
if (!nm || !ver) return null
return { name: nm, version: ver }
})
.filter(Boolean)
.slice(0, 16)
: []
collected.push({
file: name,
extId,
scripts: scripts.map((s) => String(s).trim()).filter(Boolean),
dependsOn,
beforeIds,
conflictsWith,
provides,
signaturePointer: sig || undefined
})
if (!extIdFiles.has(extId)) extIdFiles.set(extId, [])
extIdFiles.get(extId).push(name)
} catch (e) {
bootStructuredLog(
ctx,
'error',
'kernelExt.dropinParse',
`kernel.ext.d/${name}: ` + ((e && e.message) || String(e))
)
}
}
/** @type {string[]} */
const duplicateExtIds = []
for (const [eid, files] of extIdFiles) {
if (files.length > 1) duplicateExtIds.push(eid)
}
duplicateExtIds.sort()
if (duplicateExtIds.length) {
bootStructuredLog(
ctx,
'error',
'kernelExt.duplicateId',
`[kernel.ext.d] duplicate extension id(s): ${duplicateExtIds.join(', ')} — each id must appear in at most one drop-in`
)
if (strictPol) {
await appendKernelExtAuditNdjson(ctx, {
event: 'duplicate_ext_id',
duplicateExtIds
})
return false
}
}
/** @param {string} ver */
function bareOsKernelExtSemverValid(ver) {
const s = String(ver || '').trim()
return /^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/.test(
s
)
}
/** @type {string[]} */
const providesInvalidSemver = []
for (const e of collected) {
for (const pr of e.provides || []) {
if (!bareOsKernelExtSemverValid(pr.version)) {
providesInvalidSemver.push(`${e.extId}:${pr.name}@${pr.version}`)
}
}
}
providesInvalidSemver.sort()
if (providesInvalidSemver.length) {
bootStructuredLog(
ctx,
'warn',
'kernelExt.providesInvalidSemver',
`[kernel.ext.d] provides[].version must look like semver (major.minor.patch): ${providesInvalidSemver.join('; ')}`
)
if (strictPol) {
await appendKernelExtAuditNdjson(ctx, {
event: 'provides_invalid_semver',
items: providesInvalidSemver
})
return false
}
}
/** @type {Map<string, { extId: string, version: string }>} */
const provideNameToOwner = new Map()
/** @type {string[]} */
const providesVersionConflicts = []
for (const e of collected) {
for (const pr of e.provides || []) {
const prev = provideNameToOwner.get(pr.name)
if (prev && prev.version !== pr.version) {
providesVersionConflicts.push(
`${pr.name}: ${prev.extId}@${prev.version} vs ${e.extId}@${pr.version}`
)
} else if (!prev) {
provideNameToOwner.set(pr.name, { extId: e.extId, version: pr.version })
}
}
}
providesVersionConflicts.sort()
if (providesVersionConflicts.length) {
bootStructuredLog(
ctx,
'error',
'kernelExt.providesConflict',
`[kernel.ext.d] conflicting provides version(s): ${providesVersionConflicts.join('; ')}`
)
if (strictPol) {
await appendKernelExtAuditNdjson(ctx, {
event: 'provides_version_conflict',
conflicts: providesVersionConflicts
})
return false
}
}
const idToCollected = new Map(collected.map((e) => [e.extId, e]))
for (const e of collected) {
for (const bid of e.beforeIds) {
const target = idToCollected.get(bid)
if (target && target.extId !== e.extId) {
const next = new Set(target.dependsOn)
next.add(e.extId)
target.dependsOn = [...next].slice(0, 24)
}
}
}
for (const e of collected) {
e.dependsOn = [...new Set(e.dependsOn)]
}
const idSet = new Set(collected.map((e) => e.extId))
for (const e of collected) {
for (const c of e.conflictsWith) {
if (idSet.has(c)) {
bootStructuredLog(
ctx,
'error',
'kernelExt.conflict',
`[kernel.ext.d] conflict: extension "${e.extId}" conflictsWith "${c}"`
)
if (strictPol) {
await appendKernelExtAuditNdjson(ctx, {
event: 'conflict',
extId: e.extId,
conflictsWith: c
})
return false
}
}
}
}
if (maxDepthCap > 0) {
const d = kernelExtDependencyDepth(collected)
if (d > maxDepthCap) {
bootStructuredLog(
ctx,
'error',
'kernelExt.maxDepth',
`[kernel.ext.d] maxKernelExtensionDepth exceeded (${d} > ${maxDepthCap})`
)
if (strictPol) return false
}
}
const topo = topologicalOrderKernelExtEntries(collected)
/** @type {typeof collected} */
let ordered
if (!topo.ok) {
const edgeHint =
topo.cycleEdges && topo.cycleEdges.length
? '; cycle edges (stuck->dep): ' + topo.cycleEdges.join('; ')
: ''
bootStructuredLog(
ctx,
'error',
'kernelExt.cycle',
'[kernel.ext.d] dependency cycle in extension drop-ins; ext ids: ' +
topo.cycleExtIds.join(', ') +
edgeHint
)
if (strictPol) {
await appendKernelExtAuditNdjson(ctx, {
event: 'dependency_cycle',
cycleExtIds: [...topo.cycleExtIds].sort(),
cycleEdges: topo.cycleEdges || []
})
return false
}
ordered = [...collected].sort((a, b) => a.file.localeCompare(b.file))
} else {
ordered = topo.ordered
}
const dry = bootDryRun(ctx)
const traceOn =
strictPol &&
(ctx.env?.BARE_OS_BOOT_EXT_RESOLUTION_TRACE === '1' ||
ctx.env?.BARE_OS_BOOT_EXT_RESOLUTION_TRACE === 'true')
const resolutionAlways =
ctx.env?.BARE_OS_KERNEL_EXT_RESOLUTION_JSON_ALWAYS === '1' ||
ctx.env?.BARE_OS_KERNEL_EXT_RESOLUTION_JSON_ALWAYS === 'true'
const extResolutionFailure = strictPol && !topo.ok
if (
ctx.vfs &&
typeof ctx.vfs.writeFile === 'function' &&
ctx.b4a &&
(extResolutionFailure || traceOn || resolutionAlways)
) {
try {
const cycleSorted = topo.ok
? []
: [...topo.cycleExtIds].sort((a, b) => a.localeCompare(b))
const provideSnapshot = {}
for (const [k, v] of provideNameToOwner) {
provideSnapshot[k] = v
}
const body =
JSON.stringify({
schema: 3,
atMs: Date.now(),
sessionId: String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''),
bootPolicyStrict: !!strictPol,
failure: extResolutionFailure
? {
kind: 'dependency_cycle',
cycleExtIds: cycleSorted,
cycleEdges: topo.cycleEdges || [],
provenance: 'kernel.ext.d topological sort (init-main)'
}
: null,
ordered: ordered.map((e) => ({
extId: e.extId,
file: e.file,
dependsOn: e.dependsOn,
provides: e.provides || []
})),
cycleExtIds: topo.ok ? [] : cycleSorted,
providesInvalidSemver,
providesVersionConflicts:
providesVersionConflicts.length > 0
? providesVersionConflicts
: undefined,
provideNameToOwner: provideSnapshot
}) + '\n'
await ctx.vfs.writeFile(
'/run/bare-os/kernel-ext-resolution.json',
ctx.b4a.from(body)
)
} catch {
/* ignore */
}
}
const extGraphOn =
ctx.env?.BARE_OS_KERNEL_EXT_GRAPH === '1' ||
ctx.env?.BARE_OS_KERNEL_EXT_GRAPH === 'true'
const extGraphDefer =
ctx.env?.BARE_OS_INIT_DEFER_KERNEL_EXT_GRAPH === '1' ||
ctx.env?.BARE_OS_INIT_DEFER_KERNEL_EXT_GRAPH === 'true'
if (
extGraphOn &&
!extGraphDefer &&
ctx.vfs &&
typeof ctx.vfs.writeFile === 'function' &&
ctx.b4a
) {
let bareModuleTraverse = 'unresolved'
try {
await import('bare-module-traverse')
bareModuleTraverse = 'import_ok'
} catch {
bareModuleTraverse = 'import_failed'
}
try {
const body =
JSON.stringify({
schema: 1,
atMs: Date.now(),
bareModuleTraverse,
note: 'Static drop-in DAG; deeper static edges require host tooling on extension sources.',
nodes: ordered.map((e) => ({
extId: e.extId,
file: e.file,
scripts: e.scripts,
dependsOn: e.dependsOn
}))
}) + '\n'
await ctx.vfs.writeFile(
'/run/bare-os/kernel-ext-graph.json',
ctx.b4a.from(body)
)
} catch {
/* ignore */
}
}
for (const ent of ordered) {
for (const sp of ent.scripts) {
const imgPath = String(sp).trim()
if (!imgPath.startsWith('/lib/bare-os/extensions/')) {
bootStructuredLog(
ctx,
'error',
'kernelExt.rejectedPath',
`[kernel.ext.d] rejected script path: ${imgPath}`
)
continue
}
if (incremental && loadedSet.has(imgPath)) {
continue
}
if (dry) {
bootStructuredLog(
ctx,
'error',
'bootDryRunKernelExt',
`[boot-dry-run] skip kernel.ext.d script: ${imgPath}`
)
continue
}
if (
!(await verifyKernelExtSignerPinsForScript(
ctx,
ent,
imgPath,
extSignerPins,
strictPol
))
) {
if (strictPol) {
if (typeof ctx.bareOsRequestBooterExit === 'function')
ctx.bareOsRequestBooterExit(1)
return false
}
continue
}
try {
const extT0 = Date.now()
await run(imgPath)
const loadMs = Date.now() - extT0
loadedSet.add(imgPath)
if (Array.isArray(ranScripts)) ranScripts.push(imgPath)
if (typeof ctx.bareOsRegisterKernelExtensionRecord === 'function') {
ctx.bareOsRegisterKernelExtensionRecord({
dropin: ent.file,
script: imgPath,
id: ent.extId,
dependsOn: ent.dependsOn,
signaturePointer: ent.signaturePointer,
loadMs
})
}
} catch (e) {
bootStructuredLog(
ctx,
'error',
'kernelExt.scriptThrown',
`[kernel.ext.d] ${ent.file}: ` + ((e && e.message) || String(e))
)
}
}
}
return true
}
/**
* Hyperdrive-resident kernel (staged as /boot/init.js).
* Loaded by the booter with an injected ctx object (trusted replication source).
*
* Boot order: /etc/os-release → /etc/motd → optional profile rc → /etc/bare-os/rc →
* /etc/bare-os/rc.d/* (sorted; digit-prefixed snippet names) → /etc/bare-os/rc.local →
* /etc/bare-os/kernel.d/* (same naming rules as rc.d) → session banner →
* optional onboot lines when BARE_OS_SKIP_REPL → interactive loop.
*
* Profile: first non-empty line of /etc/bare-os/profile, overridden by BARE_OS_BOOT_PROFILE.
* When set, runs /etc/bare-os/rc.profile.<name> if present (trusted execLine, before main rc).
*
* Non-interactive onboot: when ctx.bareOsSkipRepl, runs each non-empty, non-# line from
* BARE_OS_ONBOOT (newline-separated) or, if unset, every such line from /etc/bare-os/onboot
* in file order (trusted). Then readLine yields EOF.
*
* BARE_OS_BOOT_TRACE=json logs one JSON object per boot stage on stderr: **`step`** (preferred), **`stage`**, legacy **`phase`** mirror, **`ms`**, **`bootTraceSchemaVersion`** (2). Guest **`os-release`**, **`motd`**, and **`banner`** lines also emit **`type":"bootOutput"`** previews on stderr (stdout body still goes through **`ctx.console.log`** only).
* BARE_OS_BOOT_TRACE=ndjson logs **`bootTraceSchemaVersion`**: **2** lines with **`type":"boot"`**, **`step`**, **`stage`**, **`phase`** (mirror), **`ms`**, **`sessionId`**, **`ts`**, plus **`type":"bootOutput"`** for guest-visible text previews.
*
* BARE_OS_BOOT_MINIMAL=1 or true: skip profile rc, rc, rc.d, rc.local, kernel.d (recovery shell).
* BARE_OS_BOOT_SAFE_MODE=1 or true: after boot policy merge, skip rc.d, kernel.ext.d, onboot (lighter recovery).
* BARE_OS_BOOT_TRANSACTION_JOURNAL=1, true, or ndjson: append boot stage NDJSON lines to
* /run/bare-os/boot-transaction.ndjson when ctx.vfs supports readFile/writeFile.
* BARE_OS_BOOT_CHECKPOINT=1 or true: write /run/bare-os/boot-checkpoint.json after each completed boot stage (schema 2 adds bootStage; legacy `phase` key retained).
* BARE_OS_BOOT_DRY_RUN=1 or true: parse rc/kernel.ext.d but skip trusted execLine and extension script execution (CI).
* BARE_OS_BOOT_POLICY_PATH=/etc/bare-os/… optional primary boot.policy.json path (must stay under /etc/bare-os/).
* BARE_OS_BOOT_ROLLBACK_APPLY=1: merge skip stages from /run/bare-os/boot-rollback.marker JSON when vfs supports readFile.
* BARE_OS_BOOT_BUNDLE_DIGEST_HEX: compared to boot.policy.json requireBootBundleSha256Hex when set.
* BARE_OS_REQUIRE_CTX_API_MIN & BARE_OS_BOOT_ABI_STRICT: enforce ctx.bareOsCtxApiVersion semver vs minimum (strict exits boot).
* BARE_OS_BOOT_SKIP_STAGES (preferred) and BARE_OS_BOOT_SKIP (legacy): comma-separated boot stages to skip among:
* profile, rc, rc.d, rc.local, kernel.d, kernel.ext.d, onboot (os-release and motd always run).
*
* BARE_OS_KERNEL_SELFTEST=1 or true: after boot snippets, run a short trusted self-check via execLine.
* BARE_OS_SELFTEST_FORMAT=tap: TAP-style lines on stderr for CI parsers; **junit**: single XML `<testsuite>` line on stderr.
* BARE_OS_KERNEL_STARTUP_CLASS: `critical` | `system` | `interactive` | `deferred` (default **interactive**); exposed as **`ctx.bareOsKernelStartupClass`** for extensions/initd.
* BARE_OS_BOOT_BUDGET_MS_COLD: optional cold-boot wall-time warning threshold (ms) after **`bareOsPublishBootReady`**.
* BARE_OS_BOOT_BUDGET_MS_BARE_STDLIB: optional wall-time budget (ms) for booter **`ctx.bare`** drive merge + host resolve (**`BARE_OS_BOOT_BARE_STDLIB_RESOLUTION_MS`**, set by the stock booter); guest logs and **`boot-perf.json`** when exceeded.
* BARE_OS_BOOT_BUDGET_STRICT=1 with **BARE_OS_BOOT_POLICY_STRICT**: exit via **`bareOsRequestBooterExit(1)`** when either budget is exceeded (after **`boot-transaction.ndjson`** row **`bootBudgetViolation`** when journaling is on).
* BARE_OS_BOOT_CAPABILITY_CONTRACT_DEBUG=1: log capability-contract merge diagnostics (strict builds).
* BARE_OS_BOOT_EXT_RESOLUTION_TRACE=1 with **BARE_OS_BOOT_POLICY_STRICT**: write **`/run/bare-os/kernel-ext-resolution.json`** (extension load order / cycle ids).
* BARE_OS_BOOT_RC_RESOLUTION_TRACE=1 with **BARE_OS_BOOT_POLICY_STRICT**: write **`/run/bare-os/rc-d-resolution.json`** and **`/run/bare-os/kernel-d-resolution.json`** (lexicographic execution order after filters).
* BARE_OS_BOOT_ALLOWLIST=1 and /etc/bare-os/boot.allow: only first-word commands in that file (plus shell builtins) run from trusted rc/onboot snippets.
*
* BARE_OS_BOOT_MANIFEST_SIGN=1: verify Ed25519 signature in /etc/bare-os/boot.manifest.sig over the raw
* manifest bytes; public key from BARE_OS_BOOT_MANIFEST_PUBKEY_HEX (64 hex chars). Uses ctx.bareOsVerifyBootManifestSignature.
*
* BARE_OS_KERNEL_EXT_D_HOT_RELOAD=1: after boot, exposes **`ctx.bareOsReloadKernelExtDropinsSafe()`** which re-scans
* `/etc/bare-os/kernel.ext.d` and runs only extension scripts not yet recorded in **`ctx.bareOsLoadedKernelExtScripts`**
* (append-only; does not unload). Append-only audit: **`/run/bare-os/kernel-ext-reload.ndjson`** when **`ctx.vfs.writeFile`** exists.
*
* BARE_OS_VFS_HYPERBLOBS_DEDUP=1: operator hint surfaced in **`/proc/bare_os/features`** — content-defined chunking may be enabled
* in host mirror/hyperblob pipelines; the guest VFS does not turn on hyperblobs automatically.
*
* BARE_OS_BOOT_POLICY=1: merge `skipBootStages` / `denyBootStages` from boot.policy (legacy `skipPhases` / `denyBootPhases` still honored; see applyBootPolicyFile). Optional `policyFallbackPaths` for tiered skip merge; `initdAdmission` sets initd env caps; `extensionSignerPinsV5` multi-signer pins.
* Boot policy v2 (optional): `maxExecLineDepth`, `denyEnvKeys`, `requireProcNodes` (VFS paths under /proc).
* Optional `minKernelCapabilitiesPrimary` / `requireSeedCaps` (integers) when ctx exposes
* `bareOsAdvertisedKernelCapabilityWords` / `bareOsSeedKernelCapabilityWords` (wire v2: `primary` word).
* `BARE_OS_BOOT_POLICY_STRICT=1`: exit boot on policy violation (via requestBooterExit(1)).
*
* kernel.d: optional leading comment lines `# ConditionEnvironment=KEY=VAL` or `# AssertEnvironment=KEY=VAL` skip the snippet when env does not match.
* kernel.ext.d: JSON drop-ins under /etc/bare-os/kernel.ext.d/*.json with `{ "scripts": ["/lib/bare-os/extensions/…"] }` (trusted).
* Optional per-drop-in **`minCtxApiVersion`** (semver): skip or strict-fail when **`ctx.bareOsCtxApiVersion`** is lower.
* Optional **`conflictsWith`**: string array of other extension **`id`** values; strict boot fails if both are present.
* Optional ordering: `dependsOn`, `requires` (merged into dependsOn), `after` (ids loaded before this drop-in),
* `before` (ids that must load after this drop-in). Cycles fail strict boot or fall back to filename order.
*
* BARE_OS_BOOT_STRICT=1 or true: first execLine throw in trusted boot snippets calls
* requestBooterExit(1) and stops further boot stages.
*
* BARE_OS_RC_D_SKIP: comma-separated rc.d basenames to skip; a pattern ending with * skips
* names with that prefix (e.g. 10-* skips 10-foo).
*
* BARE_OS_RC_PROPOSAL_MULTISIG_STRICT=1: before **rc.d**, require **`/etc/bare-os/pear.multisig.json`**
* ( **`bareOsPearMultisigShapeOk`** ) and validate every **`/etc/bare-os/rc.proposals/enabled/*.json`**
* (**`schema`**: **1**, **`proposalId`**, **`targetRcSnippet`** under **`/etc/bare-os/rc.d/`**, **`signaturesFrom`**: distinct
* public keys each listed in **`pear.multisig.json`** **`signers`**, count ≥ **`quorum`**). Emits **`ctx.bareOsAuditLogAppend`**
* rows **`rc.proposal.multisig_ok`** / **`rc.proposal.multisig_fail`** when the hook exists.
*
* Use ctx.registerKernelShutdownHook(fn) for teardown before initd disposers.
*/
/**
* @param {Record<string, unknown>} ctx
*/
function wantBootTrace(ctx) {
const v = ctx.env && ctx.env.BARE_OS_BOOT_TRACE
return v === '1' || v === 'true' || v === 'json' || v === 'ndjson'
}
/**
* @param {Record<string, unknown>} ctx
*/
function isBootTraceJson(ctx) {
return ctx.env && ctx.env.BARE_OS_BOOT_TRACE === 'json'
}
/**
* @param {Record<string, unknown>} ctx
*/
function isBootTraceNdjson(ctx) {
return ctx.env && ctx.env.BARE_OS_BOOT_TRACE === 'ndjson'
}
/**
* Guest-visible boot text via **`ctx.console.log`** only (booter session / var-log mirror).
* When **`BARE_OS_BOOT_TRACE`** is **`json`** or **`ndjson`**, append one stderr JSON line per emission (**`type":"bootOutput"`**, preview-capped) for CI and hosts.
* @param {Record<string, unknown>} ctx
* @param {string} step
* @param {string} text
*/
function bootGuestOutLine(ctx, step, text) {
const line = String(text)
const out = ctx.console
if (out && typeof out.log === 'function') out.log(line)
else if (
globalThis.console &&
typeof globalThis.console.log === 'function'
) {
globalThis.console.log(line)
}
if (!wantBootTrace(ctx)) return
const prev =
isBootTraceNdjson(ctx) && line.length > 400
? line.slice(0, 400) + '\u2026'
: isBootTraceJson(ctx) && line.length > 200
? line.slice(0, 200) + '\u2026'
: line
const errFn =
out && typeof out.error === 'function'
? out.error.bind(out)
: globalThis.console && typeof globalThis.console.error === 'function'
? globalThis.console.error.bind(globalThis.console)
: null
if (!errFn) return
if (isBootTraceNdjson(ctx)) {
errFn(
JSON.stringify({
type: 'bootOutput',
bootTraceSchemaVersion: 2,
step,
stream: 'stdout',
sessionId: String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''),
ts: Date.now(),
textPreview: prev
})
)
} else if (isBootTraceJson(ctx)) {
errFn(
JSON.stringify({
bootTraceSchemaVersion: 2,
type: 'bootOutput',
step,
stream: 'stdout',
textPreview: prev
})
)
}
}
/**
* Prefer **`ctx.console`** for boot-policy and boot-stage messages; when **`BARE_OS_BOOT_TRACE`**
* is **`json`** or **`ndjson`**, also emit **`type":"bootLog"`** on stderr for structured parsers.
* @param {'error'|'warn'} severity
* @param {string} code Stable machine-readable code (policy field name, **`boot.allow`**, etc.).
* @param {string} humanLine Operator-visible line (keep **`[boot-policy]`** / **`[boot]`** prefixes).
*/
function bootStructuredLog(ctx, severity, code, humanLine) {
const out = ctx.console
const line = String(humanLine)
if (severity === 'warn') {
if (out && typeof out.warn === 'function') out.warn(line)
else if (globalThis.console && typeof globalThis.console.warn === 'function') {
globalThis.console.warn(line)
}
} else {
if (out && typeof out.error === 'function') out.error(line)
else if (globalThis.console && typeof globalThis.console.error === 'function') {
globalThis.console.error(line)
}
}
if (!wantBootTrace(ctx)) return
const errFn =
out && typeof out.error === 'function'
? out.error.bind(out)
: globalThis.console && typeof globalThis.console.error === 'function'
? globalThis.console.error.bind(globalThis.console)
: null
if (!errFn) return
const msg = line.length > 600 ? line.slice(0, 600) + '\u2026' : line
const payload = {
type: 'bootLog',
bootTraceSchemaVersion: 2,
severity,
code: String(code),
message: msg,
ts: Date.now(),
sessionId: String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || '')
}
if (isBootTraceNdjson(ctx)) {
errFn(JSON.stringify(payload))
} else if (isBootTraceJson(ctx)) {
errFn(JSON.stringify({ ...payload, step: 'bootLog' }))
}
}
/**
* @param {Record<string, unknown>} ctx
*/
function bootMinimal(ctx) {
const v = ctx.env && ctx.env.BARE_OS_BOOT_MINIMAL
return v === '1' || v === 'true'
}
/**
* Recovery-oriented boot: skip rc.d snippets, kernel extensions, and onboot after policy merge.
* @param {Record<string, unknown>} ctx
*/
function applyBootSafeMode(ctx) {
const v = ctx.env && ctx.env.BARE_OS_BOOT_SAFE_MODE
if (v !== '1' && v !== 'true') return
const pol =
ctx.bareOsBootPolicySkipStages instanceof Set
? ctx.bareOsBootPolicySkipStages
: ctx.bareOsBootPolicySkipPhases
if (!(pol instanceof Set)) return
pol.add('rc.d')
pol.add('kernel.ext.d')
pol.add('onboot')
}
/**
* @param {Record<string, unknown>} ctx
*/
function kernelExtHotReloadEnabled(ctx) {
const v = ctx.env?.BARE_OS_KERNEL_EXT_D_HOT_RELOAD
return v === '1' || v === 'true'
}
/**
* @param {Record<string, unknown>} ctx
* @param {Record<string, unknown>} row
*/
async function maybeAppendKernelExtReloadJournal(ctx, row) {
const vfs = ctx.vfs
const b4 = ctx.b4a
if (!vfs || typeof vfs.writeFile !== 'function' || !b4) return
const path = '/run/bare-os/kernel-ext-reload.ndjson'
const line =
JSON.stringify({
kernelExtReloadSchemaVersion: 1,
ts: Date.now(),
...row
}) + '\n'
try {
let prev = ''
try {
const buf = await vfs.readFile(path)
if (buf) prev = b4.toString(buf)
} catch {
/* new */
}
await vfs.writeFile(path, b4.from(prev + line))
} catch {
/* ignore */
}
}
async function maybeAppendBootTransactionJournal(ctx, row) {
const en = ctx.env && ctx.env.BARE_OS_BOOT_TRANSACTION_JOURNAL
if (en !== '1' && en !== 'true' && en !== 'ndjson') return
const vfs = ctx.vfs
const b4 = ctx.b4a
if (!vfs || typeof vfs.readFile !== 'function' || typeof vfs.writeFile !== 'function' || !b4) return
const path = '/run/bare-os/boot-transaction.ndjson'
const phaseStr = String(row && row.phase !== undefined ? row.phase : '')
const txnState =
row && row.transactionState != null
? String(row.transactionState)
: BARE_OS_BOOT_TXN_STATE.STAGE_COMMITTED
const line =
JSON.stringify({
bootTransactionSchemaVersion: 2,
type: 'boot_transaction',
ts: Date.now(),
sessionId: String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''),
...row,
transactionState: txnState,
bootStage:
row && typeof row.bootStage === 'string'
? row.bootStage
: bootStageForPhase(phaseStr)
}) + '\n'
try {
let prev = ''
try {
const buf = await vfs.readFile(path)
prev = b4.toString(buf)
} catch {
/* new */
}
await vfs.writeFile(path, b4.from(prev + line))
} catch {
/* ignore */
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} phase
* @param {number} ms
*/
async function maybeWriteBootCheckpoint(ctx, phase, ms) {
const v = ctx.env && ctx.env.BARE_OS_BOOT_CHECKPOINT
if (v !== '1' && v !== 'true') return
const vfs = ctx.vfs
const b4 = ctx.b4a
if (!vfs || typeof vfs.writeFile !== 'function' || !b4) return
try {
const body =
JSON.stringify({
schema: 2,
phase: String(phase),
stage: String(phase),
bootStage: bootStageForPhase(String(phase)),
phaseMs: typeof ms === 'number' ? ms : 0,
stageMs: typeof ms === 'number' ? ms : 0,
atMs: Date.now(),
sessionId: String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || '')
}) + '\n'
await vfs.writeFile('/run/bare-os/boot-checkpoint.json', b4.from(body))
} catch {
/* ignore */
}
}
/**
* Immutable boot snapshot under /run/bare-os/boot/snapshot.json for operators / doctor tooling.
* Schema **2** adds **`provenance`** (**`buildId`**, **`policyHashSketch`** from **`BARE_OS_BOOT_BUNDLE_DIGEST_HEX`**, **`sessionId`**).
* @param {Record<string, unknown>} ctx
* @param {Set<string>} skipSet
*/
async function maybeWriteBootSnapshotExport(ctx, skipSet) {
const vfs = ctx.vfs
const b4 = ctx.b4a
if (!vfs || typeof vfs.writeFile !== 'function' || !b4) return
const policyPathRaw = String(ctx.env?.BARE_OS_BOOT_POLICY_PATH || '').trim()
const policyPath =
policyPathRaw && policyPathRaw.startsWith('/etc/bare-os/')
? policyPathRaw
: '/etc/bare-os/boot.policy.json'
try {
const preview =
skipSet instanceof Set ? [...skipSet].map((s) => String(s).toLowerCase()).sort() : []
const sid = String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || '')
const digestHex = String(ctx.env?.BARE_OS_BOOT_BUNDLE_DIGEST_HEX || '').trim()
const policyHashSketch =
digestHex.length >= 16 ? digestHex.slice(0, 16).toLowerCase() : null
const buildIdRaw = String(
ctx.env?.BARE_OS_BUILD_ID || ctx.env?.BARE_OS_IMAGE_BUILD_ID || ''
).trim()
const buildId = buildIdRaw ? buildIdRaw.slice(0, 128) : null
const body =
JSON.stringify({
schema: 2,
exportedAtMs: Date.now(),
sessionId: sid,
bootPolicyPath: policyPath,
bootPolicyEnabled:
ctx.env?.BARE_OS_BOOT_POLICY === '1' ||
ctx.env?.BARE_OS_BOOT_POLICY === 'true',
skipBootStagesPreview: preview,
bootSafeMode:
ctx.env?.BARE_OS_BOOT_SAFE_MODE === '1' ||
ctx.env?.BARE_OS_BOOT_SAFE_MODE === 'true',
bootMinimal: bootMinimal(ctx),
provenance: {
schema: 1,
buildId,
policyHashSketch,
sessionId: sid
}
}) + '\n'
await vfs.writeFile('/run/bare-os/boot/snapshot.json', b4.from(body))
} catch {
/* ignore */
}
}
/**
* @param {Record<string, unknown>} ctx
* @returns {Set<string>}
*/
function parseBootSkip(ctx) {
const out = new Set()
const env = ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
for (const raw of [env.BARE_OS_BOOT_SKIP_STAGES, env.BARE_OS_BOOT_SKIP]) {
if (raw == null || !String(raw).trim()) continue
for (const p of String(raw)
.split(',')
.map((s) => s.trim().toLowerCase())
.filter(Boolean)) {
out.add(p)
}
}
return out
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} stage
*/
function shouldSkipBootStage(ctx, stage) {
const pol =
ctx.bareOsBootPolicySkipStages instanceof Set
? ctx.bareOsBootPolicySkipStages
: ctx.bareOsBootPolicySkipPhases
const pl = String(stage).toLowerCase()
if (pol instanceof Set && pol.has(pl)) return true
if (bootMinimal(ctx)) {
return (
stage === 'profile' ||
stage === 'rc' ||
stage === 'rc.d' ||
stage === 'rc.local' ||
stage === 'kernel.d' ||
stage === 'kernel.ext.d' ||
stage === 'onboot'
)
}
return parseBootSkip(ctx).has(pl)
}
function bootStrict(ctx) {
const v = ctx.env && ctx.env.BARE_OS_BOOT_STRICT
return v === '1' || v === 'true'
}
/**
* @param {Record<string, unknown>} ctx
* @returns {string[]}
*/
function parseRcDSkipPatterns(ctx) {
const raw = ctx.env && ctx.env.BARE_OS_RC_D_SKIP
if (raw == null || !String(raw).trim()) return []
return String(raw)
.split(',')
.map((s) => s.trim())
.filter(Boolean)
}
/**
* @param {string} name
* @param {string[]} patterns
*/
function shouldSkipRcDName(name, patterns) {
for (const p of patterns) {
if (p === name) return true
if (p.endsWith('*') && p.length > 1) {
const pre = p.slice(0, -1)
if (name.startsWith(pre)) return true
}
}
return false
}
/**
* @param {Record<string, unknown>} ctx
* @returns {Promise<Set<string> | null>} null when allowlist disabled
*/
async function loadBootAllowSet(ctx) {
const v = ctx.env && ctx.env.BARE_OS_BOOT_ALLOWLIST
if (v !== '1' && v !== 'true') return null
const { drive, b4a } = ctx
const out = new Set()
try {
const buf = await drive.get('/etc/bare-os/boot.allow')
if (!buf) return out
for (const line of b4a.toString(buf).split(/\r?\n/)) {
const t = line.trim()
if (!t || t.startsWith('#')) continue
out.add(t)
}
} catch {
/* ignore */
}
return out
}
/**
* @param {string} line
*/
function firstShellCommandWord(line) {
const s = line.trim()
const m = s.match(/^(\S+)/)
return m ? m[1] : ''
}
/**
* @param {string} line
* @param {Set<string>} allow
*/
function bootLineAllowed(line, allow) {
const w = firstShellCommandWord(line)
if (!w) return true
if (
w === 'export' ||
w === 'unset' ||
w === 'readonly' ||
w === ':' ||
w === 'umask' ||
w === 'cd'
) {
return true
}
if (allow.has(w)) return true
const base = w.includes('/') ? w.split('/').pop() : w
return !!(base && allow.has(base))
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} label
* @param {() => void | Promise<void>} fn
* @param {string[]} stageLog
*/
async function bootTimed(ctx, label, fn, stageLog) {
await invokeCtxBootHooks(ctx, {
phase: label,
stage: label,
when: 'before',
label
})
await maybeAppendBootTransactionJournal(ctx, {
phase: label,
stage: label,
ms: 0,
ok: true,
transactionState: BARE_OS_BOOT_TXN_STATE.STAGE_STARTED,
note: 'enter'
})
const t0 = Date.now()
try {
await fn()
} catch (err) {
const ms = Date.now() - t0
await maybeAppendBootTransactionJournal(ctx, {
phase: label,
stage: label,
ms,
ok: false,
transactionState: BARE_OS_BOOT_TXN_STATE.STAGE_ROLLBACK,
error: String(
err && /** @type {Error} */ (err).message
? /** @type {Error} */ (err).message
: err
)
})
throw err
}
const ms = Date.now() - t0
stageLog.push(label)
if (
ctx.env?.BARE_OS_BOOT_PERF_DETAIL === '1' ||
ctx.env?.BARE_OS_BOOT_PERF_DETAIL === 'true'
) {
if (!Array.isArray(ctx.bareOsBootStageTimings)) {
/** @type {{ label: string, wallMs: number, monotonicNs?: string }[]} */
ctx.bareOsBootStageTimings = []
}
let monotonicNs
let monotonicDeltaNs
try {
const bh = await import('bare-hrtime')
const bg = bh && (bh.bigint || bh.default?.bigint)
if (typeof bg === 'function') {
const now = bg.call(bh)
monotonicNs = String(now)
const prev = ctx.bareOsBootHrtimePrev
if (typeof prev === 'bigint')
monotonicDeltaNs = String(now - prev)
ctx.bareOsBootHrtimePrev = now
}
} catch {
/* optional native */
}
ctx.bareOsBootStageTimings.push({
label,
wallMs: ms,
...(monotonicNs ? { monotonicNs } : {}),
...(monotonicDeltaNs ? { monotonicDeltaNs } : {})
})
}
await maybeAppendBootTransactionJournal(ctx, {
phase: label,
stage: label,
ms,
ok: true,
transactionState: BARE_OS_BOOT_TXN_STATE.STAGE_COMMITTED
})
await maybeWriteBootCheckpoint(ctx, label, ms)
if (wantBootTrace(ctx)) {
if (isBootTraceNdjson(ctx)) {
const sid = (ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''
const rec = {
type: 'boot',
bootTraceSchemaVersion: 2,
step: label,
stage: label,
phase: label,
ms,
sessionId: sid,
ts: Date.now()
}
const w11 = ctx.env && String(ctx.env.BARE_OS_PROBE_ID_HYPERCORE_PACK_HRPC_LIFECYCLE || '').trim()
if (w11) rec.hypercorePackHrpcLifecycleProbeId = w11.slice(0, 128)
const bp = ctx.env && String(ctx.env.BARE_OS_BARE_PACK_VERSION || '').trim()
if (bp) rec.barePackVersionHint = bp.slice(0, 64)
const ap = ctx.env &&
String(ctx.env.BARE_OS_BARE_ADDON_POLICY_VERSION || '').trim()
if (ap) rec.bareAddonPolicyVersionHint = ap.slice(0, 64)
ctx.console.error(JSON.stringify(rec))
} else if (isBootTraceJson(ctx)) {
const base = {
bootTraceSchemaVersion: 2,
step: label,
stage: label,
phase: label,
ms
}
const w11 = ctx.env && String(ctx.env.BARE_OS_PROBE_ID_HYPERCORE_PACK_HRPC_LIFECYCLE || '').trim()
if (w11) base.hypercorePackHrpcLifecycleProbeId = w11.slice(0, 128)
ctx.console.error(JSON.stringify(base))
} else {
ctx.console.error(`[boot] ${label}: ${ms}ms`)
}
}
if (typeof ctx.bareOsEmitBootEvent === 'function') {
ctx.bareOsEmitBootEvent({
type: 'boot',
phase: label,
stage: label,
bootStage: bootStageForPhase(label),
ms,
ts: Date.now(),
sessionId: (ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''
})
}
if (typeof ctx.bareOsEmitKernelEvent === 'function') {
ctx.bareOsEmitKernelEvent({
type: 'kernel',
topic: 'boot.phase',
phase: label,
stage: label,
bootStage: bootStageForPhase(label),
ms,
ts: Date.now(),
sessionId: (ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''
})
}
await invokeCtxBootHooks(ctx, {
phase: label,
stage: label,
when: 'after',
label
})
}
/** @type {Record<string, unknown> | null | undefined} */
let bootManifestMemo
/**
* @param {Record<string, unknown>} ctx
* @returns {Promise<Record<string, unknown> | null>}
*/
async function loadBootManifest(ctx) {
const v = ctx.env && ctx.env.BARE_OS_BOOT_MANIFEST
if (v !== '1' && v !== 'true') return null
if (bootManifestMemo !== undefined) return bootManifestMemo
const { drive, b4a } = ctx
try {
const buf = await drive.get('/etc/bare-os/boot.manifest.json')
if (!buf) {
bootManifestMemo = null
return null
}
const signOn =
ctx.env &&
(ctx.env.BARE_OS_BOOT_MANIFEST_SIGN === '1' ||
ctx.env.BARE_OS_BOOT_MANIFEST_SIGN === 'true')
if (signOn) {
const sigBuf = await drive.get('/etc/bare-os/boot.manifest.sig')
const pub =
ctx.env && ctx.env.BARE_OS_BOOT_MANIFEST_PUBKEY_HEX
? String(ctx.env.BARE_OS_BOOT_MANIFEST_PUBKEY_HEX).trim()
: ''
const verifyFn = ctx.bareOsVerifyBootManifestSignature
if (typeof verifyFn !== 'function' || !pub) {
bootStructuredLog(
ctx,
'error',
'bootManifestSignMissingVerifier',
'[boot] signed manifest requires ctx.bareOsVerifyBootManifestSignature and BARE_OS_BOOT_MANIFEST_PUBKEY_HEX'
)
bootManifestMemo = null
return null
}
if (!verifyFn(buf, sigBuf, pub)) {
bootStructuredLog(
ctx,
'error',
'bootManifestSignFailed',
'[boot] boot.manifest.json Ed25519 signature verification failed'
)
bootManifestMemo = null
return null
}
}
bootManifestMemo = JSON.parse(b4a.toString(buf))
return bootManifestMemo
} catch (e) {
bootStructuredLog(
ctx,
'error',
'bootManifestParse',
'[boot] boot.manifest.json: ' + ((e && e.message) || String(e))
)
bootManifestMemo = null
return null
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} drivePath
* @param {string | Uint8Array} content
*/
async function bootManifestDigestOk(ctx, drivePath, content) {
const m = await loadBootManifest(ctx)
const sha = m && typeof m === 'object' ? m.sha256 : null
if (!sha || typeof sha !== 'object') return true
const exp = /** @type {Record<string, string>} */ (sha)[drivePath]
if (exp == null || exp === '') return true
if (typeof ctx.bareOsBootFileSha256Hex !== 'function') {
bootStructuredLog(
ctx,
'error',
'bootManifestMissingHasher',
'[boot] manifest present but bareOsBootFileSha256Hex missing'
)
return false
}
const buf =
typeof content === 'string' ? ctx.b4a.from(content, 'utf8') : content
const hex = ctx.bareOsBootFileSha256Hex(buf)
if (hex !== String(exp).trim().toLowerCase()) {
bootStructuredLog(
ctx,
'error',
'bootManifestSha256Mismatch',
'[boot] manifest sha256 mismatch: ' + drivePath
)
return false
}
return true
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} text
* @returns {Promise<boolean>} false if BARE_OS_BOOT_STRICT and a line threw
*/
async function runRcLines(ctx, text) {
const { execLine } = ctx
const strict = bootStrict(ctx)
const allow = await loadBootAllowSet(ctx)
const dry = bootDryRun(ctx)
for (const line of text.split(/\r?\n/)) {
const t = line.trim()
if (!t || t.startsWith('#')) continue
if (allow && !bootLineAllowed(t, allow)) {
bootStructuredLog(
ctx,
'error',
'bootAllowDenied',
'[boot] command not in boot.allow: ' + t.slice(0, 120)
)
if (strict) {
if (typeof ctx.requestBooterExit === 'function')
ctx.requestBooterExit(1)
return false
}
continue
}
if (dry) {
bootStructuredLog(
ctx,
'error',
'bootDryRunExecLine',
'[boot-dry-run] skip execLine: ' + t.slice(0, 120)
)
continue
}
try {
await execLine(t)
} catch (e) {
bootStructuredLog(
ctx,
'error',
'execLineThrown',
(e && e.message) || String(e)
)
if (strict) {
if (typeof ctx.requestBooterExit === 'function')
ctx.requestBooterExit(1)
return false
}
}
}
return true
}
/**
* @param {Record<string, unknown>} ctx
*/
async function printOsRelease(ctx) {
const { drive, b4a } = ctx
try {
const rel = await drive.get('/etc/os-release')
if (rel) bootGuestOutLine(ctx, 'os-release', b4a.toString(rel))
} catch (e) {
const c = ctx.console
if (c && typeof c.error === 'function')
c.error((e && e.message) || String(e))
}
}
/**
* @param {Record<string, unknown>} ctx
*/
async function printMotd(ctx) {
const { drive, b4a } = ctx
try {
const motd = await drive.get('/etc/motd')
if (motd) bootGuestOutLine(ctx, 'motd', b4a.toString(motd).trimEnd())
} catch (e) {
const c = ctx.console
if (c && typeof c.error === 'function')
c.error((e && e.message) || String(e))
}
}
/**
* Boot profile name: BARE_OS_BOOT_PROFILE wins over first line of /etc/bare-os/profile.
* @param {Record<string, unknown>} ctx
* @returns {Promise<string>}
*/
async function resolveBootProfileName(ctx) {
const fromEnv = ctx.env && ctx.env.BARE_OS_BOOT_PROFILE
if (fromEnv != null && String(fromEnv).trim()) return String(fromEnv).trim()
const { drive, b4a } = ctx
try {
const buf = await drive.get('/etc/bare-os/profile')
if (!buf) return ''
const line = b4a.toString(buf).split(/\r?\n/)[0] || ''
return line.trim()
} catch {
return ''
}
}
/**
* Optional trusted snippet /etc/bare-os/rc.profile.<name> (before /etc/bare-os/rc).
* @param {Record<string, unknown>} ctx
* @param {string} profileName
* @returns {Promise<boolean>}
*/
async function runProfileRc(ctx, profileName) {
if (!profileName) return true
const safe = profileName.replace(/[^a-zA-Z0-9._-]/g, '')
if (safe !== profileName) {
bootStructuredLog(
ctx,
'error',
'bootProfileInvalidChars',
'[boot] profile name contains unsupported characters; skipping rc.profile'
)
return true
}
return await runRcFileAt(
ctx,
`/etc/bare-os/rc.profile.${safe}`,
`rc.profile.${safe}`
)
}
/**
* When stdin is non-interactive, run trusted boot commands (automation).
* @param {Record<string, unknown>} ctx
* @returns {Promise<boolean>}
*/
async function runOnboot(ctx) {
if (!ctx.bareOsSkipRepl) return true
const { execLine, drive, b4a, env } = ctx
const strict = bootStrict(ctx)
const allow = await loadBootAllowSet(ctx)
/** @type {string[]} */
const lines = []
const fromEnv = env && env.BARE_OS_ONBOOT
if (fromEnv != null && String(fromEnv).trim()) {
for (const raw of String(fromEnv).split(/\r?\n/)) {
const t = raw.trim()
if (!t || t.startsWith('#')) continue
lines.push(t)
}
} else {
try {
const buf = await drive.get('/etc/bare-os/onboot')
if (buf) {
for (const raw of b4a.toString(buf).split(/\r?\n/)) {
const t = raw.trim()
if (!t || t.startsWith('#')) continue
lines.push(t)
}
}
} catch (e) {
bootStructuredLog(
ctx,
'error',
'onbootRead',
'onboot: ' + ((e && e.message) || String(e))
)
}
}
const dry = bootDryRun(ctx)
for (const line of lines) {
if (allow && !bootLineAllowed(line, allow)) {
bootStructuredLog(
ctx,
'error',
'onbootBootAllowDenied',
'[boot] onboot command not in boot.allow: ' + line.slice(0, 120)
)
if (strict) {
if (typeof ctx.requestBooterExit === 'function')
ctx.requestBooterExit(1)
return false
}
continue
}
if (dry) {
bootStructuredLog(
ctx,
'error',
'bootDryRunOnboot',
'[boot-dry-run] skip onboot: ' + line.slice(0, 120)
)
continue
}
try {
await execLine(line)
} catch (e) {
bootStructuredLog(
ctx,
'error',
'onbootExecLineThrown',
(e && e.message) || String(e)
)
if (strict) {
if (typeof ctx.requestBooterExit === 'function')
ctx.requestBooterExit(1)
return false
}
}
}
return true
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} drivePath absolute path on system drive
* @param {string} label for errors
* @returns {Promise<boolean>}
*/
async function runRcFileAt(ctx, drivePath, label) {
const { drive, b4a } = ctx
try {
const buf = await drive.get(drivePath)
if (!buf) return true
const text = b4a.toString(buf)
if (!(await bootManifestDigestOk(ctx, drivePath, text))) {
if (bootStrict(ctx)) {
if (typeof ctx.requestBooterExit === 'function')
ctx.requestBooterExit(1)
return false
}
return true
}
return await runRcLines(ctx, text)
} catch (e) {
bootStructuredLog(
ctx,
'error',
'rcFileRead',
`${label}: ` + ((e && e.message) || String(e))
)
return true
}
}
/**
* Only run rc.d files whose name starts with a digit (e.g. `10-local`).
* Skips README*, *.md, dotfiles, and *~ so documentation is never exec'd as shell.
* @param {string} name basename from readdir
*/
function isBareOsRcSnippetFile(name) {
if (!name || name.startsWith('.') || name.endsWith('~')) return false
if (/^README(\.|$)/i.test(name)) return false
if (/\.md$/i.test(name)) return false
return /^[0-9]/.test(name)
}
/**
* Optional snippets under /etc/bare-os/kernel.d/ — same rules as rc.d; runs after rc.local.
* @param {Record<string, unknown>} ctx
* @returns {Promise<boolean>}
*/
async function runBareOsKernelDir(ctx) {
const { drive, b4a } = ctx
try {
/** @type {string[]} */
const names = []
try {
for await (const n of drive.readdir('/etc/bare-os/kernel.d'))
names.push(n)
} catch {
return true
}
names.sort()
/** @type {string[]} */
const toRun = []
for (const name of names) {
if (!isBareOsRcSnippetFile(name)) continue
toRun.push(name)
}
const strictPol =
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === '1' ||
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === 'true'
const traceKd =
strictPol &&
(ctx.env?.BARE_OS_BOOT_RC_RESOLUTION_TRACE === '1' ||
ctx.env?.BARE_OS_BOOT_RC_RESOLUTION_TRACE === 'true')
if (traceKd && ctx.vfs && typeof ctx.vfs.writeFile === 'function' && ctx.b4a) {
try {
const body =
JSON.stringify({
schema: 1,
atMs: Date.now(),
sessionId: String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''),
kernelDOrdered: toRun
}) + '\n'
await ctx.vfs.writeFile(
'/run/bare-os/kernel-d-resolution.json',
ctx.b4a.from(body)
)
} catch {
/* ignore */
}
}
for (const name of toRun) {
const p = `/etc/bare-os/kernel.d/${name}`
try {
const buf = await drive.get(p)
if (!buf) continue
const txt = b4a.toString(buf)
if (!kernelSnippetEnvGuardsOk(ctx, txt)) {
bootStructuredLog(
ctx,
'error',
'kernel.d.envGuardSkip',
`[boot] kernel.d/${name}: ConditionEnvironment / AssertEnvironment not met; skip`
)
continue
}
const cont = await runRcLines(ctx, txt)
if (!cont) return false
} catch (e) {
bootStructuredLog(
ctx,
'error',
'kernel.d.exec',
`kernel.d/${name}: ` + ((e && e.message) || String(e))
)
}
}
} catch (e) {
bootStructuredLog(
ctx,
'error',
'kernel.d.outer',
(e && e.message) || String(e)
)
}
return true
}
/**
* @param {Record<string, unknown>} ctx
* @param {Record<string, unknown>} entry
*/
function bareOsAppendRcProposalAudit(ctx, entry) {
try {
const fn = ctx.bareOsAuditLogAppend
if (typeof fn === 'function') fn(entry)
} catch {
/* ignore */
}
}
/**
* When **`BARE_OS_RC_PROPOSAL_MULTISIG_STRICT`**, every enabled proposal must cite enough
* **`pear.multisig.json`** signers (distinct keys). Cryptographic signature verification is host/seeder responsibility.
* @param {Record<string, unknown>} ctx
* @returns {Promise<boolean>}
*/
async function verifyRcProposalsMultisigStrict(ctx) {
const strict = ctx.env?.BARE_OS_RC_PROPOSAL_MULTISIG_STRICT
if (strict !== '1' && strict !== 'true') return true
const { drive, b4a } = ctx
let pearBuf
try {
pearBuf = await drive.get('/etc/bare-os/pear.multisig.json')
} catch {
pearBuf = null
}
if (!pearBuf) {
bootStructuredLog(
ctx,
'error',
'rc.proposal.multisig.pear_missing',
'[rc.proposals] BARE_OS_RC_PROPOSAL_MULTISIG_STRICT requires /etc/bare-os/pear.multisig.json'
)
bareOsAppendRcProposalAudit(ctx, {
type: 'rc.proposal.multisig_fail',
reason: 'pear_multisig_missing',
ts: Date.now()
})
return false
}
/** @type {unknown} */
let pearParsed
try {
pearParsed = JSON.parse(b4a.toString(pearBuf))
} catch (e) {
bootStructuredLog(
ctx,
'error',
'rc.proposal.multisig.pear_parse',
'[rc.proposals] pear.multisig.json: ' + ((e && e.message) || String(e))
)
bareOsAppendRcProposalAudit(ctx, {
type: 'rc.proposal.multisig_fail',
reason: 'pear_multisig_parse',
ts: Date.now()
})
return false
}
if (!bareOsPearMultisigShapeOk(pearParsed)) {
bootStructuredLog(
ctx,
'error',
'rc.proposal.multisig.pear_shape',
'[rc.proposals] pear.multisig.json must be { signers: string[], quorum: number } with 1 ≤ quorum ≤ signers.length'
)
bareOsAppendRcProposalAudit(ctx, {
type: 'rc.proposal.multisig_fail',
reason: 'pear_multisig_shape',
ts: Date.now()
})
return false
}
const pear = /** @type {{ signers: string[]; quorum: number }} */ (pearParsed)
const signerSet = new Set(pear.signers.map((s) => String(s).toLowerCase()))
const quorum = pear.quorum
/** @type {string[]} */
const proposalNames = []
try {
for await (const n of drive.readdir('/etc/bare-os/rc.proposals/enabled')) {
if (String(n).endsWith('.json')) proposalNames.push(String(n))
}
} catch {
return true
}
proposalNames.sort()
if (!proposalNames.length) return true
for (const fn of proposalNames) {
const p = `/etc/bare-os/rc.proposals/enabled/${fn}`
/** @type {unknown} */
let raw
try {
const buf = await drive.get(p)
if (!buf) {
bootStructuredLog(ctx, 'error', 'rc.proposal.missing', `[rc.proposals] empty: ${fn}`)
bareOsAppendRcProposalAudit(ctx, {
type: 'rc.proposal.multisig_fail',
reason: 'proposal_missing',
proposalFile: fn,
ts: Date.now()
})
return false
}
raw = JSON.parse(b4a.toString(buf))
} catch (e) {
bootStructuredLog(
ctx,
'error',
'rc.proposal.parse',
`[rc.proposals] ${fn}: ` + ((e && e.message) || String(e))
)
bareOsAppendRcProposalAudit(ctx, {
type: 'rc.proposal.multisig_fail',
reason: 'proposal_parse',
proposalFile: fn,
ts: Date.now()
})
return false
}
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
bootStructuredLog(ctx, 'error', 'rc.proposal.shape', `[rc.proposals] ${fn}: expected object`)
return false
}
const o = /** @type {Record<string, unknown>} */ (raw)
if (o.schema !== 1) {
bootStructuredLog(
ctx,
'error',
'rc.proposal.schema',
`[rc.proposals] ${fn}: schema must be 1`
)
bareOsAppendRcProposalAudit(ctx, {
type: 'rc.proposal.multisig_fail',
reason: 'proposal_schema',
proposalFile: fn,
ts: Date.now()
})
return false
}
const proposalId = String(o.proposalId || '').trim()
if (!proposalId) {
bootStructuredLog(ctx, 'error', 'rc.proposal.id', `[rc.proposals] ${fn}: proposalId required`)
bareOsAppendRcProposalAudit(ctx, {
type: 'rc.proposal.multisig_fail',
reason: 'proposal_id',
proposalFile: fn,
ts: Date.now()
})
return false
}
const targetRcSnippet = String(o.targetRcSnippet || '').trim()
if (!targetRcSnippet.startsWith('/etc/bare-os/rc.d/')) {
bootStructuredLog(
ctx,
'error',
'rc.proposal.target',
`[rc.proposals] ${fn}: targetRcSnippet must start with /etc/bare-os/rc.d/`
)
bareOsAppendRcProposalAudit(ctx, {
type: 'rc.proposal.multisig_fail',
reason: 'proposal_target',
proposalFile: fn,
proposalId,
ts: Date.now()
})
return false
}
const sigFrom = o.signaturesFrom
if (!Array.isArray(sigFrom)) {
bootStructuredLog(
ctx,
'error',
'rc.proposal.signaturesFrom',
`[rc.proposals] ${fn}: signaturesFrom must be an array of signer public keys`
)
bareOsAppendRcProposalAudit(ctx, {
type: 'rc.proposal.multisig_fail',
reason: 'signatures_from_shape',
proposalFile: fn,
proposalId,
ts: Date.now()
})
return false
}
/** @type {Set<string>} */
const approved = new Set()
for (const s of sigFrom) {
const k = String(s || '').toLowerCase().trim()
if (!k) continue
if (!signerSet.has(k)) {
bootStructuredLog(
ctx,
'error',
'rc.proposal.signer_unknown',
`[rc.proposals] ${fn}: signer not in pear.multisig.json`
)
bareOsAppendRcProposalAudit(ctx, {
type: 'rc.proposal.multisig_fail',
reason: 'signer_not_in_policy',
proposalFile: fn,
proposalId,
ts: Date.now()
})
return false
}
approved.add(k)
}
if (approved.size < quorum) {
bootStructuredLog(
ctx,
'error',
'rc.proposal.quorum',
`[rc.proposals] ${fn}: need ≥ pear.multisig quorum (${quorum}) distinct approved signers, got ${approved.size}`
)
bareOsAppendRcProposalAudit(ctx, {
type: 'rc.proposal.multisig_fail',
reason: 'quorum_not_met',
proposalFile: fn,
proposalId,
ts: Date.now()
})
return false
}
bareOsAppendRcProposalAudit(ctx, {
type: 'rc.proposal.multisig_ok',
proposalFile: fn,
proposalId,
targetRcSnippet,
signerCount: approved.size,
quorum,
ts: Date.now()
})
}
return true
}
/**
* Optional snippets under /etc/bare-os/rc.d/ — executed in lexicographic order.
* @param {Record<string, unknown>} ctx
* @returns {Promise<boolean>}
*/
async function runBareOsRcDir(ctx) {
const { drive, b4a } = ctx
if (!(await verifyRcProposalsMultisigStrict(ctx))) return false
const skip = parseRcDSkipPatterns(ctx)
try {
/** @type {string[]} */
const names = []
try {
for await (const n of drive.readdir('/etc/bare-os/rc.d')) names.push(n)
} catch {
return true
}
names.sort()
/** @type {string[]} */
const toRun = []
for (const name of names) {
if (!isBareOsRcSnippetFile(name)) continue
if (shouldSkipRcDName(name, skip)) continue
toRun.push(name)
}
const strictPol =
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === '1' ||
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === 'true'
const traceRc =
strictPol &&
(ctx.env?.BARE_OS_BOOT_RC_RESOLUTION_TRACE === '1' ||
ctx.env?.BARE_OS_BOOT_RC_RESOLUTION_TRACE === 'true')
if (traceRc && ctx.vfs && typeof ctx.vfs.writeFile === 'function' && ctx.b4a) {
try {
const body =
JSON.stringify({
schema: 1,
atMs: Date.now(),
sessionId: String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''),
rcDOrdered: toRun
}) + '\n'
await ctx.vfs.writeFile(
'/run/bare-os/rc-d-resolution.json',
ctx.b4a.from(body)
)
} catch {
/* ignore */
}
}
for (const name of toRun) {
const p = `/etc/bare-os/rc.d/${name}`
try {
const buf = await drive.get(p)
if (!buf) continue
const cont = await runRcLines(ctx, b4a.toString(buf))
if (!cont) return false
} catch (e) {
bootStructuredLog(
ctx,
'error',
'rc.d.exec',
`rc.d/${name}: ` + ((e && e.message) || String(e))
)
}
}
} catch (e) {
bootStructuredLog(
ctx,
'error',
'rc.d.outer',
(e && e.message) || String(e)
)
}
return true
}
/**
* @param {Record<string, unknown>} ctx
*/
async function printSessionBanner(ctx) {
const { drive, b4a } = ctx
for (const p of ['/etc/bare-os/banner', '/etc/issue']) {
try {
const buf = await drive.get(p)
if (buf) {
bootGuestOutLine(ctx, 'banner', b4a.toString(buf).trimEnd())
return
}
} catch {
/* ignore */
}
}
const defaultBanner =
'Bare operating system — guest session (login [--new] <passphrase> to unlock)'
if (ctx.bareOsSkipRepl) {
bootGuestOutLine(
ctx,
'banner',
'Bare operating system — non-interactive session (BARE_OS_SKIP_REPL).'
)
return
}
bootGuestOutLine(ctx, 'banner', defaultBanner)
}
/**
* Trusted post-boot checks. **`BARE_OS_SELFTEST_FORMAT=tap`** and **`junit`** emit diagnostics on
* **`ctx.console.error`** (this function destructures **`ctx.console`** as **`console`**) so output
* follows the Test Anything Protocols stderr convention while staying on the booter session sink—
* not **`globalThis.console`**. Plain **`selftest:`** lines use the same sink.
*
* @param {Record<string, unknown>} ctx
* @returns {Promise<boolean>}
*/
async function runKernelSelftest(ctx) {
const v = ctx.env && ctx.env.BARE_OS_KERNEL_SELFTEST
if (v !== '1' && v !== 'true' && v !== 'upgrade') return true
const { execLine, console } = ctx
const strict = bootStrict(ctx)
const fmt = (ctx.env && ctx.env.BARE_OS_SELFTEST_FORMAT) || ''
const fmtLow = String(fmt).toLowerCase()
const tap = fmtLow === 'tap'
const junit = fmtLow === 'junit'
let tapN = 0
/** @type {{ ok: boolean, name: string, detail: string }[]} */
const junitCases = []
/** @param {string} s */
function escXml(s) {
return String(s || '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/"/g, '&quot;')
}
/** @param {string} name */
function tapLine(ok, name, detail) {
if (junit) {
junitCases.push({ ok, name, detail: detail || '' })
return
}
if (!tap) return
tapN++
if (ok) {
console.error(`ok ${tapN} ${name}`)
} else {
console.error(`not ok ${tapN} ${name}${detail ? ' — ' + detail : ''}`)
}
}
if (tap) {
console.error('TAP version 13')
}
/** @type {[string, string][]} */
const specs =
v === 'upgrade'
? [
['true', 'upgrade-smoke-true'],
[
'test -f /proc/bare_os/kernel_program.json && echo selftest_kernel_program',
'proc-kernel-program-upgrade'
],
[
'test -f /run/bare-os/boot.json && echo selftest_boot_json',
'run-boot-json-upgrade'
]
]
: [
[':', 'colon-builtin'],
['true', 'true-builtin'],
['false; echo selftest_false_ok', 'false-sequencing'],
['test -f /proc/version && echo selftest_proc', 'proc-version'],
[
'test -f /proc/bare_os_resources && echo selftest_resources',
'proc-bare_os_resources'
],
[
'test -f /proc/bare_os_features && echo selftest_features',
'proc-bare_os_features'
],
[
'test -f /proc/bare_os/index.json && echo selftest_bare_os_dir',
'proc-bare_os_dir'
],
['test -d /proc/self/fd && echo selftest_proc_fd', 'proc-self-fd'],
['test -d /sys/devices && echo selftest_sys_devices', 'sys-devices'],
['echo selftest_vfs | tee /dev/null', 'pipeline-tee-devnull'],
[
'test -f /lib/bare/manifest.json && echo selftest_bare_manifest',
'lib-bare-manifest'
],
[
'test -f /proc/bare_os/provenance && echo selftest_provenance',
'proc-bare_os_provenance'
],
[
'test -f /proc/bare_os/metrics_live.json && echo selftest_metrics_live',
'proc-metrics-live'
],
[
'test -f /proc/bare_os/net_summary.json && echo selftest_net_summary',
'proc-net-summary'
],
[
'test -f /proc/bare_os/host_os.json && echo selftest_host_os',
'proc-host-os'
],
[
'test -f /proc/bare_os/sync_window.json && echo selftest_sync_window',
'proc-sync-window'
],
[
'test -f /proc/bare_os/manifest_hints && echo selftest_manifest_hints',
'proc-manifest-hints'
],
['systemctl list-units 2>/dev/null || true', 'systemctl-list']
]
for (const [line, name] of specs) {
try {
await execLine(line)
tapLine(true, name, '')
} catch (e) {
const msg = (e && e.message) || String(e)
console.error('selftest: ' + msg)
tapLine(false, name, msg)
if (strict) {
if (typeof ctx.requestBooterExit === 'function')
ctx.requestBooterExit(1)
return false
}
}
}
try {
/** @type {string[]} */
const snames = []
for await (const n of ctx.drive.readdir('/etc/bare-os/selftest.d'))
snames.push(n)
snames.sort()
for (const name of snames) {
if (!isBareOsRcSnippetFile(name)) continue
const p = `/etc/bare-os/selftest.d/${name}`
const buf = await ctx.drive.get(p)
if (!buf) continue
const label = `selftest.d/${name}`
try {
await runRcLines(ctx, ctx.b4a.toString(buf))
tapLine(true, label, '')
} catch (e) {
const msg = (e && e.message) || String(e)
console.error('selftest: ' + msg)
tapLine(false, label, msg)
if (strict) {
if (typeof ctx.requestBooterExit === 'function')
ctx.requestBooterExit(1)
return false
}
}
}
} catch {
/* no selftest.d */
}
if (tap) {
console.error(`1..${tapN}`)
}
if (junit && junitCases.length) {
const failures = junitCases.filter((c) => !c.ok).length
const cases = junitCases
.map((c) => {
const body = c.ok
? ''
: `<failure message="selftest">${escXml(c.detail)}</failure>`
return `<testcase name="${escXml(c.name)}" classname="kernel.selftest">${body}</testcase>`
})
.join('')
console.error(
`<?xml version="1.0" encoding="UTF-8"?><testsuite name="bare-os-kernel-selftest" tests="${junitCases.length}" failures="${failures}">${cases}</testsuite>`
)
}
return true
}
/**
* @param {Record<string, unknown>} ctx
* @param {number} bootT0
* @param {{ kind: string, code: string, wallMs: number, limitMs: number }[]} violations
* @param {boolean} coldExceeded
* @param {boolean} stdlibExceeded
* @param {string[]} [stageLog]
*/
async function maybeWriteBootBudgetSummaryJson(
ctx,
bootT0,
violations,
coldExceeded,
stdlibExceeded,
stageLog
) {
const vfs = ctx.vfs
const b4 = ctx.b4a
if (!vfs || typeof vfs.writeFile !== 'function' || !b4) return
const wall = Date.now() - bootT0
const row =
JSON.stringify({
schema: 2,
atMs: Date.now(),
coldWallMs: wall,
coldExceeded,
bareStdlibExceeded: stdlibExceeded,
violationCodes: violations.map((v) => v.code).filter(Boolean),
violations,
bootStageCount:
Array.isArray(stageLog) && stageLog.length ? stageLog.length : null,
bootStageTail:
Array.isArray(stageLog) && stageLog.length
? stageLog.slice(-16)
: undefined,
procHint: '/proc/bare_os/boot_budget_summary.json',
note: 'Written every boot; operators mirror into proc via booter VFS provider. Schema 2 adds bootStageCount/bootStageTail from kernel stage log.'
}) + '\n'
try {
await vfs.writeFile('/run/bare-os/boot-budget-summary.json', b4.from(row))
} catch {
/* optional */
}
}
async function maybeWriteBootPerfJson(ctx, bootT0, stageLog) {
const vfs = ctx.vfs
const b4 = ctx.b4a
if (!vfs || typeof vfs.readFile !== 'function' || typeof vfs.writeFile !== 'function' || !b4)
return
const wall = Date.now() - bootT0
const budgetRaw = Number.parseInt(
String(ctx.env?.BARE_OS_BOOT_BUDGET_MS_COLD || ''),
10
)
const budget = Number.isFinite(budgetRaw) && budgetRaw > 0 ? budgetRaw : null
const stdlibWallRaw = Number.parseInt(
String(ctx.env?.BARE_OS_BOOT_BARE_STDLIB_RESOLUTION_MS || ''),
10
)
const stdlibWall =
Number.isFinite(stdlibWallRaw) && stdlibWallRaw >= 0 ? stdlibWallRaw : null
const stdlibBudgetRaw = Number.parseInt(
String(ctx.env?.BARE_OS_BOOT_BUDGET_MS_BARE_STDLIB || ''),
10
)
const stdlibBudget =
Number.isFinite(stdlibBudgetRaw) && stdlibBudgetRaw > 0
? stdlibBudgetRaw
: null
const stages =
Array.isArray(ctx.bareOsBootStageTimings) && ctx.bareOsBootStageTimings.length
? ctx.bareOsBootStageTimings
: undefined
let schema = stages ? 2 : 1
if (stdlibWall != null || stdlibBudget != null) schema = Math.max(schema, 3)
schema = Math.max(schema, 4)
schema = Math.max(schema, 5)
const bootBudgetTelemetry = {
schema: 1,
coldWallMs: wall,
coldBudgetMs: budget,
coldWithinBudget: budget == null ? null : wall <= budget,
stdlibWallMs: stdlibWall,
stdlibBudgetMs: stdlibBudget,
stdlibWithinBudget:
stdlibBudget == null || stdlibWall == null
? null
: stdlibWall <= stdlibBudget,
envCold: 'BARE_OS_BOOT_BUDGET_MS_COLD',
envStdlibWall: 'BARE_OS_BOOT_BARE_STDLIB_RESOLUTION_MS',
envStdlibBudget: 'BARE_OS_BOOT_BUDGET_MS_BARE_STDLIB',
note: 'Unified mirror of guest cold boot vs bare-stdlib resolution budgets; same values as top-level coldWallMs / bareStdlib* (booter sets BARE_OS_BOOT_BARE_STDLIB_RESOLUTION_MS).'
}
const row =
JSON.stringify({
schema,
bootGraphJsonPath: '/proc/bare_os/boot_graph.json',
stageLogSnapshot: [...stageLog],
coldWallMs: wall,
bootBudgetMsCold: budget,
withinBudget: budget == null ? null : wall <= budget,
bootBudgetWarning:
budget != null && wall > budget
? `cold boot ${wall}ms exceeded BARE_OS_BOOT_BUDGET_MS_COLD ${budget}ms`
: null,
bareStdlibResolutionWallMs: stdlibWall,
bareStdlibBudgetMs: stdlibBudget,
bareStdlibWithinBudget:
stdlibBudget == null || stdlibWall == null
? null
: stdlibWall <= stdlibBudget,
bareStdlibBudgetWarning:
stdlibBudget != null &&
stdlibWall != null &&
stdlibWall > stdlibBudget
? `bare stdlib resolution ${stdlibWall}ms exceeded BARE_OS_BOOT_BUDGET_MS_BARE_STDLIB ${stdlibBudget}ms`
: null,
bootBudgetTelemetry,
bootBudgetStrict:
ctx.env?.BARE_OS_BOOT_BUDGET_STRICT === '1' ||
ctx.env?.BARE_OS_BOOT_BUDGET_STRICT === 'true',
bootPolicyStrict:
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === '1' ||
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === 'true',
stageCount: stageLog.length,
stages,
completedAtMs: Date.now()
}) + '\n'
try {
await vfs.writeFile('/run/bare-os/boot-perf.json', b4.from(row))
} catch {
/* optional */
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string[]} stageLog
*/
function publishBootReady(ctx, stageLog) {
if (typeof ctx.bareOsPublishBootReady !== 'function') return
const sid = (ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''
const gpen = ctx.env && ctx.env.BARE_OS_BOOT_TRANSACTION_JOURNAL
const safe =
ctx.env?.BARE_OS_BOOT_SAFE_MODE === '1' ||
ctx.env?.BARE_OS_BOOT_SAFE_MODE === 'true'
const bootStages = stageLog.map((label) => ({
label: String(label),
bootStage: bootStageForPhase(String(label))
}))
ctx.bareOsPublishBootReady({
ready: true,
stages: [...stageLog],
phases: [...stageLog],
steps: [...stageLog],
bootSteps: [...stageLog],
sessionId: sid,
completedAtMs: Date.now(),
minimal: bootMinimal(ctx),
subsystems: {
kernel: {
ready: true,
stageCount: stageLog.length,
phaseCount: stageLog.length,
stepCount: stageLog.length,
bootStages,
bootPhases: bootStages,
bootSteps: bootStages,
programProc: {
schema: 2,
programVersion: 2,
safeMode: safe,
bootDryRun: bootDryRun(ctx),
transactionJournal: gpen === '1' || gpen === 'true' || gpen === 'ndjson',
checkpoint:
ctx.env?.BARE_OS_BOOT_CHECKPOINT === '1' ||
ctx.env?.BARE_OS_BOOT_CHECKPOINT === 'true',
bootTransactionFsm: {
schema: 1,
state: 'committed',
note: 'Guest kernel finished boot stage sequence; see boot-transaction.ndjson for per-stage rows.'
},
kernelExtHotReload: kernelExtHotReloadEnabled(ctx)
}
},
initd: { awaited: true }
}
})
}
/**
* Merge sketch for seed vs advertised capability words under strict policy (expand over time).
* @param {Record<string, unknown>} ctx
*/
function mergeBootCapabilityContract(ctx) {
const strict =
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === '1' ||
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === 'true'
const adv = ctx.bareOsAdvertisedKernelCapabilityWords
const seed = ctx.bareOsSeedKernelCapabilityWords
if (
strict &&
adv &&
seed &&
typeof adv === 'object' &&
typeof seed === 'object'
) {
const ap = /** @type {{ primary?: number }} */ (adv).primary
const sp = /** @type {{ primary?: number }} */ (seed).primary
if (typeof ap === 'number' && typeof sp === 'number' && ap !== sp) {
bootStructuredLog(
ctx,
'error',
'bootCapabilityPrimaryMismatch',
'[boot] strict: primary kernel capability word mismatch (advertised vs seed)'
)
if (typeof ctx.requestBooterExit === 'function') ctx.requestBooterExit(1)
return false
}
}
const dbg =
ctx.env?.BARE_OS_BOOT_CAPABILITY_CONTRACT_DEBUG === '1' ||
ctx.env?.BARE_OS_BOOT_CAPABILITY_CONTRACT_DEBUG === 'true'
if (dbg) {
bootStructuredLog(
ctx,
'error',
'bootCapabilityContractDebug',
'[boot] capability contract debug: advertised=' +
(adv ? 'yes' : 'no') +
' seed=' +
(seed ? 'yes' : 'no')
)
}
return true
}
/**
* Boot ABI gate: optional `BARE_OS_REQUIRE_CTX_API_MIN` vs `ctx.bareOsCtxApiVersion`.
* @param {Record<string, unknown>} ctx
*/
function enforceBootCtxApiMin(ctx) {
const need = String(ctx.env?.BARE_OS_REQUIRE_CTX_API_MIN || '').trim()
if (!need) return true
const have =
typeof ctx.bareOsCtxApiVersion === 'string'
? ctx.bareOsCtxApiVersion.trim()
: ''
if (!have || !semverGte(have, need)) {
bootStructuredLog(
ctx,
'error',
'bootCtxApiMin',
'[boot] BARE_OS_REQUIRE_CTX_API_MIN not satisfied (need ' +
need +
', have ' +
(have || '(none)') +
')'
)
if (
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === '1' ||
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === 'true' ||
ctx.env?.BARE_OS_BOOT_ABI_STRICT === '1' ||
ctx.env?.BARE_OS_BOOT_ABI_STRICT === 'true'
) {
if (typeof ctx.requestBooterExit === 'function') ctx.requestBooterExit(1)
return false
}
}
return true
}
async function start(ctx) {
const bootT0 = Date.now()
const { readLine, execLine } = ctx
/** @type {string[]} */
const stageLog = []
const bootPolicySkipSet = new Set()
ctx.bareOsBootPolicySkipStages = bootPolicySkipSet
ctx.bareOsBootPolicySkipPhases = bootPolicySkipSet
{
const raw = String(ctx.env?.BARE_OS_KERNEL_STARTUP_CLASS || 'interactive')
.trim()
.toLowerCase()
const allowed = new Set(['critical', 'system', 'interactive', 'deferred'])
ctx.bareOsKernelStartupClass = allowed.has(raw)
? raw
: 'interactive'
}
if (!(await applyBootPolicyFile(ctx))) return
if (!enforceBootCtxApiMin(ctx)) return
if (!mergeBootCapabilityContract(ctx)) return
await maybeAppendBootTransactionJournal(ctx, {
phase: 'boot.policy',
stage: 'boot.policy',
bootStage: 'policy',
ms: 0,
ok: true,
transactionState: BARE_OS_BOOT_TXN_STATE.STAGE_COMMITTED
})
await maybeWriteBootCheckpoint(ctx, 'boot.policy', 0)
applyBootSafeMode(ctx)
await maybeWriteBootSnapshotExport(ctx, bootPolicySkipSet)
await bootTimed(ctx, 'os-release', () => printOsRelease(ctx), stageLog)
await bootTimed(ctx, 'motd', () => printMotd(ctx), stageLog)
const profileName = await resolveBootProfileName(ctx)
/** @type {boolean} */
let bootOk = true
if (!shouldSkipBootStage(ctx, 'profile')) {
await bootTimed(
ctx,
'rc.profile',
async () => {
bootOk = await runProfileRc(ctx, profileName)
},
stageLog
)
} else {
stageLog.push('rc.profile(skipped)')
}
if (!bootOk) {
await maybeWriteBootPerfJson(ctx, bootT0, stageLog)
return
}
if (!shouldSkipBootStage(ctx, 'rc')) {
await bootTimed(
ctx,
'rc',
async () => {
bootOk = await runRcFileAt(ctx, '/etc/bare-os/rc', 'rc')
},
stageLog
)
} else {
stageLog.push('rc(skipped)')
}
if (!bootOk) {
await maybeWriteBootPerfJson(ctx, bootT0, stageLog)
return
}
if (!shouldSkipBootStage(ctx, 'rc.d')) {
await bootTimed(
ctx,
'rc.d',
async () => {
bootOk = await runBareOsRcDir(ctx)
},
stageLog
)
} else {
stageLog.push('rc.d(skipped)')
}
if (!bootOk) {
await maybeWriteBootPerfJson(ctx, bootT0, stageLog)
return
}
if (!shouldSkipBootStage(ctx, 'rc.local')) {
await bootTimed(
ctx,
'rc.local',
async () => {
bootOk = await runRcFileAt(ctx, '/etc/bare-os/rc.local', 'rc.local')
},
stageLog
)
} else {
stageLog.push('rc.local(skipped)')
}
if (!bootOk) {
await maybeWriteBootPerfJson(ctx, bootT0, stageLog)
return
}
if (!shouldSkipBootStage(ctx, 'kernel.d')) {
await bootTimed(
ctx,
'kernel.d',
async () => {
bootOk = await runBareOsKernelDir(ctx)
},
stageLog
)
} else {
stageLog.push('kernel.d(skipped)')
}
if (!bootOk) {
await maybeWriteBootPerfJson(ctx, bootT0, stageLog)
return
}
if (!shouldSkipBootStage(ctx, 'kernel.ext.d')) {
await bootTimed(
ctx,
'kernel.ext.d',
async () => {
bootOk = await runKernelExtDropins(ctx)
},
stageLog
)
} else {
stageLog.push('kernel.ext.d(skipped)')
}
if (!bootOk) {
await maybeWriteBootPerfJson(ctx, bootT0, stageLog)
return
}
await bootTimed(
ctx,
'banner',
async () => {
await printSessionBanner(ctx)
},
stageLog
)
if (!shouldSkipBootStage(ctx, 'onboot')) {
await bootTimed(
ctx,
'onboot',
async () => {
bootOk = await runOnboot(ctx)
},
stageLog
)
} else {
stageLog.push('onboot(skipped)')
}
if (!bootOk) {
await maybeWriteBootPerfJson(ctx, bootT0, stageLog)
return
}
await bootTimed(
ctx,
'selftest',
async () => {
bootOk = await runKernelSelftest(ctx)
},
stageLog
)
if (!bootOk) {
await maybeWriteBootPerfJson(ctx, bootT0, stageLog)
return
}
publishBootReady(ctx, stageLog)
await maybeWriteBootPerfJson(ctx, bootT0, stageLog)
if (kernelExtHotReloadEnabled(ctx)) {
ctx.bareOsReloadKernelExtDropinsSafe = async () => {
if (!kernelExtHotReloadEnabled(ctx)) {
return { ok: false, reason: 'env_disabled', atMs: Date.now() }
}
/** @type {string[]} */
const ran = []
const ok = await runKernelExtDropins(ctx, {
incremental: true,
ranScripts: ran
})
await maybeAppendKernelExtReloadJournal(ctx, {
ok,
incremental: true,
ranScripts: ran,
sessionId: String((ctx.env && ctx.env.BARE_OS_SESSION_ID) || '')
})
return { ok, ranScripts: ran, atMs: Date.now() }
}
}
{
/** @type {{ kind: string, code: string, wallMs: number, limitMs: number }[]} */
let bootBudgetViolationsForSummary = []
const budgetStrict =
ctx.env?.BARE_OS_BOOT_BUDGET_STRICT === '1' ||
ctx.env?.BARE_OS_BOOT_BUDGET_STRICT === 'true'
const polStrict =
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === '1' ||
ctx.env?.BARE_OS_BOOT_POLICY_STRICT === 'true'
let coldExceeded = false
let stdlibExceeded = false
const budget = Number.parseInt(
String(ctx.env?.BARE_OS_BOOT_BUDGET_MS_COLD || ''),
10
)
if (Number.isFinite(budget) && budget > 0) {
const wall = Date.now() - bootT0
if (wall > budget) {
coldExceeded = true
bootStructuredLog(
ctx,
'error',
'bootBudgetColdExceeded',
`[boot] cold wall ${wall}ms exceeds BARE_OS_BOOT_BUDGET_MS_COLD=${budget}ms`
)
if (ctx.env) {
ctx.env.BARE_OS_BOOT_BUDGET_COLD_EXCEEDED = '1'
ctx.env.BARE_OS_BOOT_BUDGET_COLD_WALL_MS = String(wall)
ctx.env.BARE_OS_BOOT_BUDGET_COLD_LIMIT_MS = String(budget)
}
}
}
const sb = Number.parseInt(
String(ctx.env?.BARE_OS_BOOT_BUDGET_MS_BARE_STDLIB || ''),
10
)
if (Number.isFinite(sb) && sb > 0) {
const sw = Number.parseInt(
String(ctx.env?.BARE_OS_BOOT_BARE_STDLIB_RESOLUTION_MS || ''),
10
)
if (Number.isFinite(sw) && sw > sb) {
stdlibExceeded = true
bootStructuredLog(
ctx,
'error',
'bootBudgetBareStdlibExceeded',
`[boot] bare stdlib resolution ${sw}ms exceeds BARE_OS_BOOT_BUDGET_MS_BARE_STDLIB=${sb}ms`
)
if (ctx.env) {
ctx.env.BARE_OS_BOOT_BUDGET_STDLIB_EXCEEDED = '1'
ctx.env.BARE_OS_BOOT_BUDGET_STDLIB_WALL_MS = String(sw)
ctx.env.BARE_OS_BOOT_BUDGET_STDLIB_LIMIT_MS = String(sb)
}
}
}
if (coldExceeded || stdlibExceeded) {
/** @type {{ kind: string, code: string, wallMs: number, limitMs: number }[]} */
const violations = []
if (coldExceeded && Number.isFinite(budget) && budget > 0) {
violations.push({
kind: 'cold',
code: 'BARE_OS_BOOT_BUDGET_COLD_EXCEEDED',
wallMs: Date.now() - bootT0,
limitMs: budget
})
}
if (stdlibExceeded && Number.isFinite(sb) && sb > 0) {
const sw = Number.parseInt(
String(ctx.env?.BARE_OS_BOOT_BARE_STDLIB_RESOLUTION_MS || ''),
10
)
violations.push({
kind: 'bare_stdlib',
code: 'BARE_OS_BOOT_BUDGET_BARE_STDLIB_EXCEEDED',
wallMs: Number.isFinite(sw) ? sw : 0,
limitMs: sb
})
}
await maybeAppendBootTransactionJournal(ctx, {
phase: 'boot.budget',
stage: 'boot.budget',
bootStage: 'budget',
ms: 0,
ok: false,
bootBudgetViolation: true,
bootBudgetSchemaVersion: 2,
coldBudgetExceeded: coldExceeded,
bareStdlibBudgetExceeded: stdlibExceeded,
bootBudgetViolations: violations,
transactionState: BARE_OS_BOOT_TXN_STATE.STAGE_COMMITTED
})
bootBudgetViolationsForSummary = violations
}
await maybeWriteBootBudgetSummaryJson(
ctx,
bootT0,
bootBudgetViolationsForSummary,
coldExceeded,
stdlibExceeded,
stageLog
)
if (budgetStrict && polStrict && (coldExceeded || stdlibExceeded)) {
bootStructuredLog(
ctx,
'error',
'bootBudgetStrictAbort',
'[boot] BARE_OS_BOOT_BUDGET_STRICT with BARE_OS_BOOT_POLICY_STRICT: exiting after budget violation'
)
if (typeof ctx.bareOsRequestBooterExit === 'function')
ctx.bareOsRequestBooterExit(1)
return
}
}
while (true) {
const line = await readLine('')
if (line == null) break
const t = line.trim()
if (t === '') continue
let status = 'ok'
try {
status = await execLine(t)
} catch (e) {
bootStructuredLog(
ctx,
'error',
'replExecLineThrown',
(e && e.message) || String(e)
)
}
if (status === 'exit') break
}
}