booter updates

This commit is contained in:
Raven Scott
2026-04-26 07:05:24 -04:00
parent 47da0b8531
commit d95c135dd9
7 changed files with 313 additions and 37 deletions
+69 -2
View File
@@ -155,6 +155,7 @@ import { buildPearIpcRegistryJson } from './lib/bare-os-pear-ipc-registry.js'
import {
appendVarLog,
AUDIT_LOG,
BOOT_LOG,
LOGGER_JSON_LOG,
mirrorBareOsTelemetryNdjson
} from './lib/bare-os-var-log.js'
@@ -4472,14 +4473,44 @@ async function executeKernel(disk, store, swarm, initSource) {
* @param {string} passphrase
*/
async applyUnlock(passphrase) {
await unlockIdentity(this, passphrase)
try {
await unlockIdentity(this, passphrase)
void appendVarLog(this, BOOT_LOG, 'boot', JSON.stringify({
event: 'login_success',
mode: 'unlock',
atMs: Date.now()
}))
} catch (e) {
void appendVarLog(this, BOOT_LOG, 'boot', JSON.stringify({
event: 'login_failure',
mode: 'unlock',
atMs: Date.now(),
error: e && e.message ? e.message : String(e)
}))
throw e
}
},
/**
* Create account and unlock session.
* @param {string} passphrase
*/
async applyRegister(passphrase) {
await registerIdentity(this, passphrase)
try {
await registerIdentity(this, passphrase)
void appendVarLog(this, BOOT_LOG, 'boot', JSON.stringify({
event: 'login_success',
mode: 'register',
atMs: Date.now()
}))
} catch (e) {
void appendVarLog(this, BOOT_LOG, 'boot', JSON.stringify({
event: 'login_failure',
mode: 'register',
atMs: Date.now(),
error: e && e.message ? e.message : String(e)
}))
throw e
}
},
/**
* @param {{ publicKey: Uint8Array, secretKey: Uint8Array }} keys
@@ -10055,9 +10086,23 @@ async function executeKernel(disk, store, swarm, initSource) {
ctx.writeScreen = session.writeScreen
ctx.console = session.console
void appendVarLog(ctx, BOOT_LOG, 'boot', JSON.stringify({
event: 'boot_start',
atMs: Date.now(),
bootStartedMs
}))
emitBooterBootPhase('repl')
void appendVarLog(ctx, BOOT_LOG, 'boot', JSON.stringify({
event: 'kernel_ready',
atMs: Date.now()
}))
await maybeStartBareHolesailKernelFromBooter(ctx)
void appendVarLog(ctx, BOOT_LOG, 'boot', JSON.stringify({
event: 'initd_start',
atMs: Date.now()
}))
await startBareInitd(ctx)
{
@@ -10065,6 +10110,24 @@ async function executeKernel(disk, store, swarm, initSource) {
.filter((s) => getBareServiceRuntime(s.name)?.phase === 'active')
.map((s) => s.name)
Object.assign(bootReadyStateRef.subsystems, { initdActiveUnits: active })
void appendVarLog(ctx, BOOT_LOG, 'boot', JSON.stringify({
event: 'unit_active',
atMs: Date.now(),
units: active
}))
if (
active.some((n) =>
n === 'bare-openssh' || n === 'bare-holesail' || n === 'bare-os-www'
)
) {
void appendVarLog(ctx, BOOT_LOG, 'boot', JSON.stringify({
event: 'network_ready',
atMs: Date.now(),
units: active.filter((n) =>
n === 'bare-openssh' || n === 'bare-holesail' || n === 'bare-os-www'
)
}))
}
}
try {
if (vfs && typeof vfs.writeFile === 'function') {
@@ -10084,6 +10147,10 @@ async function executeKernel(disk, store, swarm, initSource) {
}
emitBooterBootPhase('initd')
void appendVarLog(ctx, BOOT_LOG, 'boot', JSON.stringify({
event: 'login_prompt_ready',
atMs: Date.now()
}))
const peerSeedElig = computePeerSystemSeedEligibility({
disk,
+33 -1
View File
@@ -61,6 +61,8 @@ const unitHealthTimers = new Map()
/** @type {(() => void)[]} */
const disposers = []
/** @type {Set<AbortController>} */
const socketLoopAbortControllers = new Set()
/** @type {(() => void | Promise<void>)[]} */
const kernelShutdownHooks = []
@@ -220,6 +222,14 @@ export async function bareInitdShutdownActiveUnitsReverse(ctx) {
}
export function stopBareInitd() {
for (const ac of socketLoopAbortControllers) {
try {
ac.abort()
} catch {
/* ignore */
}
}
socketLoopAbortControllers.clear()
for (const t of unitHealthTimers.values()) clearInterval(t)
unitHealthTimers.clear()
for (const fn of disposers) {
@@ -949,9 +959,26 @@ export async function startBareInitd(ctx) {
for (;;) {
appendBareInitdJournal(s.name, { event: 'socket_wait' })
runtime.set(s.name, { phase: 'inactive', startedAtMs: Date.now() })
const outerWaitAc = new AbortController()
socketLoopAbortControllers.add(outerWaitAc)
try {
await ctx.bareOsIpc.take(ipcName)
await ctx.bareOsIpc.take(ipcName, { signal: outerWaitAc.signal })
} catch (e) {
socketLoopAbortControllers.delete(outerWaitAc)
if (
e &&
typeof e === 'object' &&
/** @type {Error} */ (e).name === 'AbortError'
) {
runtime.set(s.name, {
phase: 'inactive',
startedAtMs: Date.now()
})
appendBareInitdJournal(s.name, {
event: 'socket_loop_stopped'
})
return
}
const msg = e?.message || String(e)
runtime.set(s.name, {
phase: 'failed',
@@ -970,6 +997,8 @@ export async function startBareInitd(ctx) {
})
await runOnFailureHookForUnit(ctx, s, di)
return
} finally {
socketLoopAbortControllers.delete(outerWaitAc)
}
const t1 = Date.now()
try {
@@ -1013,6 +1042,7 @@ export async function startBareInitd(ctx) {
if (!idleStopOk) return
while (runtime.get(s.name)?.phase === 'active') {
const ac = new AbortController()
socketLoopAbortControllers.add(ac)
const takeP = ctx.bareOsIpc.take(ipcName, { signal: ac.signal })
const idleMs = idleSec * 1000
const race = await Promise.race([
@@ -1021,6 +1051,7 @@ export async function startBareInitd(ctx) {
])
if (race === 'idle') {
ac.abort()
socketLoopAbortControllers.delete(ac)
try {
await takeP
} catch {
@@ -1044,6 +1075,7 @@ export async function startBareInitd(ctx) {
})
break
}
socketLoopAbortControllers.delete(ac)
}
} catch (e) {
const msg = e?.message || String(e)
+29 -6
View File
@@ -139,7 +139,7 @@ function assertSafeIpcName(name) {
const DEFAULT_JSON_RPC_MAX = 256 * 1024
/**
* @param {{ maxFifoBytes?: number, maxChannels?: number, perChannelMaxBytes?: Map<string, number>, ipcRpcToken?: string | null, enableFanout?: boolean, maxJsonRpcLineBytes?: number, posixMqDefaultMaxmsg?: number, posixMqDefaultMaxBytes?: number, posixMqMaxmsgCeiling?: number }} [opts]
* @param {{ maxFifoBytes?: number, maxChannels?: number, perChannelMaxBytes?: Map<string, number>, ipcRpcToken?: string | null, enableFanout?: boolean, maxJsonRpcLineBytes?: number, maxWaitersPerChannel?: number, posixMqDefaultMaxmsg?: number, posixMqDefaultMaxBytes?: number, posixMqMaxmsgCeiling?: number }} [opts]
*/
export function createBareOsIpc(opts = {}) {
const maxFifoBytes =
@@ -155,12 +155,18 @@ export function createBareOsIpc(opts = {}) {
const perChannelMax =
opts.perChannelMaxBytes instanceof Map ? opts.perChannelMaxBytes : null
/** @type {{ fifoCreates: number, fifoCreateDeniedQuota: number, fifoPushDenied: number, fifoTakeAborted: number, fanoutDrop: number, mqFull: number }} */
const maxWaitersPerChannel =
typeof opts.maxWaitersPerChannel === 'number' && opts.maxWaitersPerChannel > 0
? Math.min(65536, Math.floor(opts.maxWaitersPerChannel))
: 1024
/** @type {{ fifoCreates: number, fifoCreateDeniedQuota: number, fifoPushDenied: number, fifoTakeAborted: number, fifoTakeDeniedWaiters: number, fanoutDrop: number, mqFull: number }} */
const telemetry = {
fifoCreates: 0,
fifoCreateDeniedQuota: 0,
fifoPushDenied: 0,
fifoTakeAborted: 0,
fifoTakeDeniedWaiters: 0,
fanoutDrop: 0,
mqFull: 0
}
@@ -329,6 +335,14 @@ export function createBareOsIpc(opts = {}) {
take(name, takeOpts) {
const ch = channels.get(name)
if (!ch) return Promise.reject(new Error('no such fifo: ' + name))
if (ch.waiters.length >= maxWaitersPerChannel) {
telemetry.fifoTakeDeniedWaiters++
return Promise.reject(
new Error(
'bare-os ipc: waiter backlog exceeds maxWaitersPerChannel'
)
)
}
return ch.take(takeOpts?.signal).catch((e) => {
if (e && typeof e === 'object' && /** @type {Error} */ (e).name === 'AbortError') {
telemetry.fifoTakeAborted++
@@ -505,10 +519,15 @@ export function createBareOsIpc(opts = {}) {
throw new Error('bare-os ipc mq: queue byte budget exceeded')
}
const seq = q.nextSeq++
q.msgs.push({ prio: p, seq, data: u8 })
q.msgs.sort(
(a, b) => b.prio - a.prio || a.seq - b.seq
)
const row = { prio: p, seq, data: u8 }
let idx = q.msgs.length
while (idx > 0) {
const prev = q.msgs[idx - 1]
if (prev.prio > row.prio) break
if (prev.prio === row.prio && prev.seq < row.seq) break
idx--
}
q.msgs.splice(idx, 0, row)
q.curBytes += u8.byteLength
return { ok: true }
},
@@ -559,6 +578,10 @@ export function createBareOsIpc(opts = {}) {
channelCount: channels.size,
maxChannels,
telemetry: { ...telemetry },
waiterLimits: {
schema: 1,
maxWaitersPerChannel
},
queuedBytesTotal,
fanoutTopicCount: fanouts.size,
fanoutSubscribersTotal,
@@ -7,6 +7,10 @@ const INITD_SYNTH_PID_BASE = 5100
/** @type {Map<string, number>} */
const initdSyntheticPidByName = new Map()
let nextInitdSyntheticPid = INITD_SYNTH_PID_BASE
/** @type {Map<number, { createdAtMs: number, lastStateChangeAtMs: number, lastState: string, completedAtMs?: number }>} */
const shellJobLifecycleById = new Map()
/** @type {Map<string, { createdAtMs: number, lastStateChangeAtMs: number, lastState: string, completedAtMs?: number }>} */
const initdLifecycleByName = new Map()
/**
* @param {string} unitName
@@ -21,6 +25,59 @@ function syntheticInitdPidFor(unitName) {
return pid
}
/**
* @param {number} id
* @param {string} state
* @param {number} now
*/
function observeShellJobLifecycle(id, state, now) {
const cur = shellJobLifecycleById.get(id)
if (!cur) {
const row = { createdAtMs: now, lastStateChangeAtMs: now, lastState: state }
if (state === 'zombie') row.completedAtMs = now
shellJobLifecycleById.set(id, row)
return row
}
if (cur.lastState !== state) {
cur.lastState = state
cur.lastStateChangeAtMs = now
if (state === 'zombie' && !Number.isFinite(cur.completedAtMs)) cur.completedAtMs = now
}
return cur
}
/**
* @param {string} unit
* @param {string} state
* @param {number} startedAtMs
* @param {number} now
*/
function observeInitdLifecycle(unit, state, startedAtMs, now) {
const cur = initdLifecycleByName.get(unit)
if (!cur) {
const row = {
createdAtMs: Number.isFinite(startedAtMs) ? startedAtMs : now,
lastStateChangeAtMs: now,
lastState: state
}
if (state === 'failed' || state === 'dead' || state === 'inactive') row.completedAtMs = now
initdLifecycleByName.set(unit, row)
return row
}
if (cur.lastState !== state) {
cur.lastState = state
cur.lastStateChangeAtMs = now
if (
(state === 'failed' || state === 'dead' || state === 'inactive') &&
!Number.isFinite(cur.completedAtMs)
) {
cur.completedAtMs = now
}
if (state === 'active' || state === 'starting') cur.completedAtMs = undefined
}
return cur
}
/**
* @param {Record<string, string | undefined> | null | undefined} env
*/
@@ -238,6 +295,7 @@ export function bareOsProcessTableSnapshot(opts = {}) {
const pid = 4100 + j.id
const pgid = typeof j.pgid === 'number' ? j.pgid : pid
const session = typeof j.sid === 'number' ? j.sid : 1
const life = observeShellJobLifecycle(j.id, j.stopped ? 'stopped' : 'running', now)
return {
pid,
ppid: 3,
@@ -245,7 +303,10 @@ export function bareOsProcessTableSnapshot(opts = {}) {
sid: session,
name: 'bare-os-shell-job',
state: j.stopped ? 'stopped' : 'running',
startedAtMs: now,
startedAtMs: life.createdAtMs,
createdAtMs: life.createdAtMs,
lastStateChangeAtMs: life.lastStateChangeAtMs,
completedAtMs: life.completedAtMs,
jobId: j.id,
role: 'shell_job',
label: typeof j.label === 'string' ? j.label.slice(0, 256) : undefined,
@@ -259,6 +320,7 @@ export function bareOsProcessTableSnapshot(opts = {}) {
const pid = 4200 + jid
const pgid = typeof j.pgid === 'number' ? j.pgid : pid
const session = typeof j.sid === 'number' ? j.sid : 1
const life = observeShellJobLifecycle(jid, 'zombie', now)
return {
pid,
ppid: 3,
@@ -266,7 +328,10 @@ export function bareOsProcessTableSnapshot(opts = {}) {
sid: session,
name: 'bare-os-shell-job',
state: 'zombie',
startedAtMs: now,
startedAtMs: life.createdAtMs,
createdAtMs: life.createdAtMs,
lastStateChangeAtMs: life.lastStateChangeAtMs,
completedAtMs: life.completedAtMs,
jobId: jid,
role: 'shell_job',
label: typeof j.label === 'string' ? j.label.slice(0, 256) : undefined,
@@ -316,6 +381,12 @@ export function bareOsProcessTableSnapshot(opts = {}) {
/repl|swarm|net|peer|dht|hdms|hyperswarm/.test(nmLower)
? 'net-related-initd-failure'
: undefined
const life = observeInitdLifecycle(
u.name,
String(u.phase || ''),
typeof u.startedAtMs === 'number' ? u.startedAtMs : now,
now
)
initdLogicalProcs.push({
pid,
ppid: 2,
@@ -335,6 +406,9 @@ export function bareOsProcessTableSnapshot(opts = {}) {
? 'sleeping'
: 'sleeping',
startedAtMs: typeof u.startedAtMs === 'number' ? u.startedAtMs : now,
createdAtMs: life.createdAtMs,
lastStateChangeAtMs: life.lastStateChangeAtMs,
completedAtMs: life.completedAtMs,
role: 'initd_unit',
initdUnit: u.name.slice(0, 128),
initdPhase: String(u.phase || '').slice(0, 32),
@@ -383,8 +457,8 @@ export function bareOsProcessTableSnapshot(opts = {}) {
return {
schema: 9,
schemaVersion: 9,
note: 'Synthetic rows; Bare OS guests do not expose host OS processes. v9 adds per-row nice (-20..19) via ctx.bareOsRenice, accountingSource on accounting fields, and companions process_maps.json / process_threads.json (logical models). v8 optional accounting + initd replicationHint retained.',
schemaVersion: 10,
note: 'Synthetic rows; Bare OS guests do not expose host OS processes. v10 adds lifecycle timestamps (createdAtMs/lastStateChangeAtMs/completedAtMs) for shell jobs and initd units. v9 adds per-row nice (-20..19) via ctx.bareOsRenice, accountingSource on accounting fields, and companions process_maps.json / process_threads.json (logical models). v8 optional accounting + initd replicationHint retained.',
signalModel: {
schema: 1,
note: 'Virtual deliveries via ctx.bareOsSendSignal (not full sigaction/sigprocmask).',
@@ -64,7 +64,8 @@ function readSwarmPolicyEnv(env) {
* lastLatencyMs: number | null,
* reconnectBudget: number,
* earliestRetryAtMs: number,
* retryTier: 'none' | 'short' | 'medium' | 'long' | 'xlong'
* retryTier: 'none' | 'short' | 'medium' | 'long' | 'xlong',
* failByClass: Record<string, number>
* }} PeerState
*/
@@ -103,7 +104,8 @@ export class BareOsSwarmPeerPolicyEngine {
lastLatencyMs: null,
reconnectBudget: 32,
earliestRetryAtMs: 0,
retryTier: 'none'
retryTier: 'none',
failByClass: {}
}
this._peers.set(k, st)
}
@@ -131,7 +133,7 @@ export class BareOsSwarmPeerPolicyEngine {
/**
* @param {string} peerKey
* @param {boolean} ok
* @param {{ latencyMs?: number }} [probeMeta]
* @param {{ latencyMs?: number, failClass?: string }} [probeMeta]
*/
noteProbe(peerKey, ok, probeMeta = {}) {
const k = this._key(peerKey)
@@ -161,6 +163,10 @@ export class BareOsSwarmPeerPolicyEngine {
} else {
st.fail++
st.consecutiveFail++
const failClass = String(probeMeta.failClass || 'unknown')
.trim()
.slice(0, 32) || 'unknown'
st.failByClass[failClass] = (st.failByClass[failClass] || 0) + 1
const retry = this._retryDelayFor(st)
st.retryTier = retry.tier
st.earliestRetryAtMs = now + retry.delayMs
@@ -202,6 +208,7 @@ export class BareOsSwarmPeerPolicyEngine {
const st = this._peers.get(k)
const now = Date.now()
if (!st) return true
if (this._globalReconnectBudget <= 0) return false
if (st.banUntilMs > now) return false
if (st.earliestRetryAtMs > now) return false
if (st.reconnectBudget <= 0) return false
@@ -259,12 +266,15 @@ export class BareOsSwarmPeerPolicyEngine {
if (!total) return 0
const successRate = st.ok / total
const failPenalty = Math.min(0.5, st.fail * 0.05)
const protocolPenalty = Math.min(0.25, (st.failByClass.protocol || 0) * 0.03)
const policyPenalty = Math.min(0.2, (st.failByClass.policy || 0) * 0.02)
const timeoutPenalty = Math.min(0.15, (st.failByClass.timeout || 0) * 0.01)
const now = Date.now()
const banned = st.banUntilMs > now ? 0.35 : 0
const lat = st.ewmaLatencyMs
const latPenalty =
lat > 0 ? Math.min(0.25, Math.log10(1 + lat / 50) * 0.08) : 0
return successRate - failPenalty - banned - latPenalty
return successRate - failPenalty - protocolPenalty - policyPenalty - timeoutPenalty - banned - latPenalty
}
snapshot() {
+31 -18
View File
@@ -55,6 +55,7 @@ export const CRON_LOG = `${BARE_OS_VAR_LOG_DIR}/cron.log`
export const INITD_LOG = `${BARE_OS_VAR_LOG_DIR}/initd.log`
export const AUDIT_LOG = `${BARE_OS_VAR_LOG_DIR}/audit.log`
export const BOOT_LOG = `${BARE_OS_VAR_LOG_DIR}/boot.log`
/** Structured JSON lines from `/bin/logger` (syslog-style metadata). */
export const LOGGER_JSON_LOG = `${BARE_OS_VAR_LOG_DIR}/logger.jsonl`
@@ -69,6 +70,7 @@ const README_TEXT = `Bare OS session logs (mirrored on your personal drive under
www.log — bare-os-www static HTTP for ~/.www
initd.log — bare-initd service start failures
audit.log — optional execLine audit when BARE_OS_AUDIT=1
boot.log — boot-to-login timeline milestones (best-effort)
`
/**
@@ -101,6 +103,29 @@ const LOG_MAX_BYTES = 512 * 1024
/** After trim, keep this many trailing bytes plus a notice line. */
const LOG_KEEP_BYTES = 256 * 1024
const TELEMETRY_LOG_CAP_BYTES = 2 * 1024 * 1024
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
* @param {Uint8Array} chunk
* @param {number} capBytes
*/
async function appendLineWithOptionalCap(ctx, path, chunk, capBytes) {
const vfs = ctx.vfs
if (!vfs) return
if (typeof vfs.appendFile === 'function') {
await vfs.appendFile(path, chunk)
return
}
if (typeof vfs.readFile !== 'function' || typeof vfs.writeFile !== 'function') return
const prev = await vfs.readFile(path)
let merged = prev ? ctx.b4a.concat([prev, chunk]) : chunk
if (merged.length > capBytes) {
merged = merged.subarray(merged.length - capBytes)
}
await vfs.writeFile(path, merged)
}
/**
* Ensure `/var/log/bare-os` exists and a short README is present. Best-effort; never throws.
@@ -153,7 +178,6 @@ export async function appendVarLog(ctx, logicalFilePath, kind, line) {
)
return
}
const prev = await vfs.readFile(logicalFilePath)
const ts = new Date().toISOString()
const chunk = ctx.b4a.from(`[${ts}] [${kind}] ${line}\n`)
const fastAppend =
@@ -161,7 +185,7 @@ export async function appendVarLog(ctx, logicalFilePath, kind, line) {
ctx.env?.BARE_OS_VAR_LOG_FAST_APPEND === 'true') &&
typeof vfs.appendFile === 'function'
if (fastAppend) {
await vfs.appendFile(logicalFilePath, chunk)
await appendLineWithOptionalCap(ctx, logicalFilePath, chunk, LOG_MAX_BYTES)
await mirrorBareOsTelemetryNdjson(ctx, {
type: 'varLog',
path: logicalFilePath,
@@ -175,6 +199,7 @@ export async function appendVarLog(ctx, logicalFilePath, kind, line) {
})
return
}
const prev = await vfs.readFile(logicalFilePath)
let merged = prev ? ctx.b4a.concat([prev, chunk]) : chunk
if (merged.length > LOG_MAX_BYTES) {
const start = Math.max(0, merged.length - LOG_KEEP_BYTES)
@@ -222,7 +247,7 @@ export async function mirrorBareOsTelemetryNdjson(ctx, rec) {
const dest = String(raw).trim()
if (!dest.startsWith('/') && !dest.startsWith('~/')) return
const vfs = ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function') return
if (!vfs) return
const lineage = String(env.BARE_OS_TELEMETRY_SESSION_LINEAGE_ID || '').trim()
const bootAttemptId = String(env.BARE_OS_BOOT_ATTEMPT_ID || '').trim()
const bareModuleCryptoStagingProbeId = String(env.BARE_OS_PROBE_ID_BARE_MODULE_CRYPTO_STAGING || '').trim()
@@ -261,13 +286,7 @@ export async function mirrorBareOsTelemetryNdjson(ctx, rec) {
...safeRec
}) + '\n'
const chunk = ctx.b4a.from(line)
const prev = await vfs.readFile(dest)
let merged = prev ? ctx.b4a.concat([prev, chunk]) : chunk
const cap = 2 * 1024 * 1024
if (merged.length > cap) {
merged = merged.subarray(merged.length - cap)
}
await vfs.writeFile(dest, merged)
await appendLineWithOptionalCap(ctx, dest, chunk, TELEMETRY_LOG_CAP_BYTES)
} catch {
/* ignore */
}
@@ -289,7 +308,7 @@ async function mirrorBareOsOtelJsonl(ctx, eventName, attrs) {
const dest = String(raw).trim()
if (!dest.startsWith('/') && !dest.startsWith('~/')) return
const vfs = ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function') return
if (!vfs) return
const safeAttrs = bareOsTelemetrySanitizeRec(
/** @type {Record<string, unknown>} */ (attrs)
)
@@ -334,13 +353,7 @@ async function mirrorBareOsOtelJsonl(ctx, eventName, attrs) {
]
}) + '\n'
const chunk = ctx.b4a.from(line)
const prev = await vfs.readFile(dest)
let merged = prev ? ctx.b4a.concat([prev, chunk]) : chunk
const cap = 2 * 1024 * 1024
if (merged.length > cap) {
merged = merged.subarray(merged.length - cap)
}
await vfs.writeFile(dest, merged)
await appendLineWithOptionalCap(ctx, dest, chunk, TELEMETRY_LOG_CAP_BYTES)
} catch {
/* ignore */
}
+59 -2
View File
@@ -126,7 +126,8 @@ import {
import {
appendVarLog,
ensureBareOsVarLogTree,
KERNEL_CONSOLE_LOG
KERNEL_CONSOLE_LOG,
BOOT_LOG
} from './lib/bare-os-var-log.js'
import {
bareOsKernelMetricsReset,
@@ -7018,6 +7019,20 @@ test('bare-os-ipc pushJson requires token when configured', async (t) => {
t.is(j.x, 1)
})
test('bare-os-ipc enforces waiter cap and reports telemetry', async (t) => {
const ipc = createBareOsIpc({ maxWaitersPerChannel: 1 })
ipc.create('cap')
const p1 = ipc.take('cap')
await Promise.resolve()
await t.exception(async () => {
await ipc.take('cap')
}, /maxWaitersPerChannel/)
const st = ipc.stats()
t.ok(st && st.telemetry && st.telemetry.fifoTakeDeniedWaiters >= 1)
ipc.push('cap', b4a.from('x'))
t.is(b4a.toString(await p1), 'x')
})
test('bareOsHttpUrlAllowed allowlist and denylist', async (t) => {
t.ok(bareOsHttpUrlAllowed('https://a.example/x', { allow: [], deny: [] }).ok)
t.ok(
@@ -7558,7 +7573,7 @@ test('process table snapshot includes processGroups metadata', async (t) => {
t.ok(s.processGroups)
t.is(s.processGroups.schema, 1)
t.ok(String(s.processGroups.killpgAnalog).includes('killpg'))
t.is(s.schemaVersion, 9)
t.is(s.schemaVersion, 10)
t.ok(s.jobControlSemantics && s.jobControlSemantics.pipefail === true)
const booter = s.processes.find((p) => p && p.pid === 2)
t.ok(booter && booter.parentName === 'bare-os-kernel')
@@ -7570,6 +7585,48 @@ test('process table snapshot includes processGroups metadata', async (t) => {
t.ok(s.exitStatusModel && s.exitStatusModel.schema === 1)
})
test('process table lifecycle timestamps remain stable across snapshots', async (t) => {
const running = bareOsProcessTableSnapshot({
shellJobs: [{ id: 7, done: false, stopped: false, label: 'demo' }]
})
const row1 = running.processes.find((p) => p && p.jobId === 7)
t.ok(row1 && Number.isFinite(row1.createdAtMs))
const stopped = bareOsProcessTableSnapshot({
shellJobs: [{ id: 7, done: false, stopped: true, label: 'demo' }]
})
const row2 = stopped.processes.find((p) => p && p.jobId === 7)
t.is(row2.createdAtMs, row1.createdAtMs)
t.ok(row2.lastStateChangeAtMs >= row1.createdAtMs)
const done = bareOsProcessTableSnapshot({
shellJobs: [{ id: 7, done: true, stopped: false, label: 'demo' }]
})
const row3 = done.processes.find((p) => p && p.jobId === 7)
t.is(row3.createdAtMs, row1.createdAtMs)
t.ok(Number.isFinite(row3.completedAtMs))
})
test('boot log writes timeline rows into personal backing', async (t) => {
const dir = testCorestoreDir('bootlog')
const store = new Corestore(dir)
const drive = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('pbootlog'))
await drive.ready()
await personal.ready()
const ctx = testCtx(drive, personal)
await ensureBareOsVarLogTree(ctx)
await appendVarLog(
ctx,
BOOT_LOG,
'boot',
JSON.stringify({ event: 'login_prompt_ready', atMs: Date.now() })
)
const rel = personalHomeBacking('/home/user', 'var/log/bare-os/boot.log')
const raw = b4a.toString(await personal.readFile(rel), 'utf8')
t.ok(raw.includes('login_prompt_ready'))
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('bare_acl other:: and mask interact like POSIX ACL classes', async (t) => {
const env = { UID: '1000', GID: '1000' }
const p1 = bareOsParseBareAclText('other::---\n')