Files
bare-operating-system/packages/bare-os-seeder/kernel/init.js
T
Raven Scott 8f2e3cceb0 Move editable kernel bulk from kernel/init-main.js to kernel/lib/init/
(staged as /lib/init/init-main.js); point bundle-kernel-init and verify
scripts at the new path.

Wire curl, wget, openssl, ssh-keygen, and tar through coreutils and
booter host delegates with booter-side CLI helpers; refresh related
bins, bare manifest, shell completion, and man DB (kernel + seeder).

Add booter support modules for ACL evaluation, audit chain, secret
handles, peer admission, replication priority, process table, swarm
lifecycle, boot-graph proc, metrics, monotonic time, protomux alias
registry, and swarm peer policy; extend extension resolver, VFS,
swarm connection managers, IPC, identity-account, and initd.

Harden bare-os-bare-libs build on esbuild failure; add verify scripts
for extension manifest schema and runtime incomplete markers; extend
ctx API typings, gen-ctx-client-stub, and verify-ctx-dts.

Update boot hook fragment, bundled init.js, handbook and reference
docs (incl. kernel security and VFS path classes).
2026-04-04 17:51:47 -04:00

2792 lines
89 KiB
JavaScript

/** 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)
}
}
/**
* 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).
* BARE_OS_BOOT_TRACE=ndjson logs **`bootTraceSchemaVersion`**: **2** lines with **`type":"boot"`**, **`step`**, **`stage`**, **`phase`** (mirror), **`ms`**, **`sessionId`**, **`ts`**.
*
* 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_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_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 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).
*
* 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'
}
/**
* @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')
}
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)
}
/**
* 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, console } = 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) {
console.warn(
'[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) {
console.error(
'[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) {
console.error('[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 (
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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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') {
console.error(
'[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error('[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) {
console.error(
'[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 {
console.error(
'[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 {
console.error('[boot-policy] requireProcNodes not satisfied: ' + p)
if (strictPol) {
if (typeof ctx.requestBooterExit === 'function') {
ctx.requestBooterExit(1)
}
return false
}
}
}
}
}
} catch (e) {
console.error(
'[boot-policy] boot.policy.json: ' + ((e && e.message) || String(e))
)
}
return true
}
/**
* @param {Record<string, unknown>} ctx
*/
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)
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, console } = 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) {
console.error(
'[boot] signed manifest requires ctx.bareOsVerifyBootManifestSignature and BARE_OS_BOOT_MANIFEST_PUBKEY_HEX'
)
bootManifestMemo = null
return null
}
if (!verifyFn(buf, sigBuf, pub)) {
console.error(
'[boot] boot.manifest.json Ed25519 signature verification failed'
)
bootManifestMemo = null
return null
}
}
bootManifestMemo = JSON.parse(b4a.toString(buf))
return bootManifestMemo
} catch (e) {
console.error(
'[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') {
ctx.console.error(
'[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()) {
ctx.console.error('[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, console } = 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)) {
console.error('[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) {
console.error('[boot-dry-run] skip execLine: ' + t.slice(0, 120))
continue
}
try {
await execLine(t)
} catch (e) {
console.error((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, console } = ctx
try {
const rel = await drive.get('/etc/os-release')
if (rel) console.log(b4a.toString(rel))
} catch (e) {
console.error((e && e.message) || String(e))
}
}
/**
* @param {Record<string, unknown>} ctx
*/
async function printMotd(ctx) {
const { drive, b4a, console } = ctx
try {
const motd = await drive.get('/etc/motd')
if (motd) console.log(b4a.toString(motd).trimEnd())
} catch (e) {
console.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) {
ctx.console.error(
'[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, console, 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) {
console.error('onboot: ' + ((e && e.message) || String(e)))
}
}
const dry = bootDryRun(ctx)
for (const line of lines) {
if (allow && !bootLineAllowed(line, allow)) {
console.error(
'[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) {
console.error('[boot-dry-run] skip onboot: ' + line.slice(0, 120))
continue
}
try {
await execLine(line)
} catch (e) {
console.error((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, console } = 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) {
console.error(`${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, console } = 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)) {
console.error(`[boot] kernel.d/${name}: ConditionEnvironment / AssertEnvironment not met; skip`)
continue
}
const cont = await runRcLines(ctx, txt)
if (!cont) return false
} catch (e) {
console.error(`kernel.d/${name}: ` + ((e && e.message) || String(e)))
}
}
} catch (e) {
console.error((e && e.message) || String(e))
}
return true
}
/**
* @param {{ file: string, extId: string, scripts: string[], dependsOn: string[], signaturePointer?: string }[]} entries
* @returns {{ ok: true, ordered: typeof entries } | { ok: false, cycleExtIds: 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))
return { ok: false, cycleExtIds: stuck.map((e) => e.extId) }
}
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
}
/**
* Optional `/etc/bare-os/kernel.ext.d/*.json` with `{ "scripts": ["/lib/bare-os/extensions/foo.js"] }`.
* @param {Record<string, unknown>} ctx
* @returns {Promise<boolean>}
*/
async function runKernelExtDropins(ctx) {
const { drive, console } = ctx
const run = ctx.bareOsRunImageScript
if (typeof run !== 'function') return true
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 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[], signaturePointer?: string }[]} */
const collected = []
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)) {
console.error(`[kernel.ext.d] denied by policy id: ${extId}`)
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
collected.push({
file: name,
extId,
scripts: scripts.map((s) => String(s).trim()).filter(Boolean),
dependsOn,
beforeIds,
signaturePointer: sig || undefined
})
} catch (e) {
console.error(`kernel.ext.d/${name}: ` + ((e && e.message) || String(e)))
}
}
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)]
}
if (maxDepthCap > 0) {
const d = kernelExtDependencyDepth(collected)
if (d > maxDepthCap) {
console.error(
`[kernel.ext.d] maxKernelExtensionDepth exceeded (${d} > ${maxDepthCap})`
)
if (strictPol) return false
}
}
const topo = topologicalOrderKernelExtEntries(collected)
/** @type {typeof collected} */
let ordered
if (!topo.ok) {
console.error(
'[kernel.ext.d] dependency cycle in extension drop-ins; ext ids: ' +
topo.cycleExtIds.join(', ')
)
if (strictPol) 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')
if (traceOn && 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) || ''),
ordered: ordered.map((e) => ({
extId: e.extId,
file: e.file,
dependsOn: e.dependsOn
})),
cycleExtIds: topo.ok ? [] : topo.cycleExtIds
}) + '\n'
await ctx.vfs.writeFile(
'/run/bare-os/kernel-ext-resolution.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/')) {
console.error(`[kernel.ext.d] rejected script path: ${imgPath}`)
continue
}
if (dry) {
console.error(`[boot-dry-run] skip kernel.ext.d script: ${imgPath}`)
continue
}
try {
await run(imgPath)
if (typeof ctx.bareOsRegisterKernelExtensionRecord === 'function') {
ctx.bareOsRegisterKernelExtensionRecord({
dropin: ent.file,
script: imgPath,
id: ent.extId,
dependsOn: ent.dependsOn,
signaturePointer: ent.signaturePointer
})
}
} catch (e) {
console.error(
`[kernel.ext.d] ${ent.file}: ` + ((e && e.message) || String(e))
)
}
}
}
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, console } = ctx
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) {
console.error(`rc.d/${name}: ` + ((e && e.message) || String(e)))
}
}
} catch (e) {
console.error((e && e.message) || String(e))
}
return true
}
/**
* @param {Record<string, unknown>} ctx
*/
async function printSessionBanner(ctx) {
const { drive, b4a, console } = ctx
for (const p of ['/etc/bare-os/banner', '/etc/issue']) {
try {
const buf = await drive.get(p)
if (buf) {
console.log(b4a.toString(buf).trimEnd())
return
}
} catch {
/* ignore */
}
}
const defaultBanner =
'Bare operating system — guest session (login [--new] <passphrase> to unlock) | shell: cd, export, if/fi, && || ;, |, &, jobs/fg, <<<, exit | services: systemctl list-units, journalctl -u UNIT | try: help, ls /bin, crontab -l'
if (ctx.bareOsSkipRepl) {
console.log(
'Bare operating system — non-interactive session (BARE_OS_SKIP_REPL).'
)
return
}
console.log(defaultBanner)
}
/**
* @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 {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.'
}
}
},
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) {
ctx.console.error(
'[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 && ctx.console) {
ctx.console.error(
'[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)) {
ctx.console.error(
'[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, console } = 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) 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) 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) 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) 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) 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) 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) return
await bootTimed(
ctx,
'selftest',
async () => {
bootOk = await runKernelSelftest(ctx)
},
stageLog
)
if (!bootOk) return
publishBootReady(ctx, stageLog)
{
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) {
console.error(
`[boot] cold wall ${wall}ms exceeds BARE_OS_BOOT_BUDGET_MS_COLD=${budget}ms`
)
}
}
}
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) {
console.error((e && e.message) || String(e))
}
if (status === 'exit') break
}
}