This commit is contained in:
Raven Scott
2026-04-03 18:02:05 -04:00
parent ec236f9cc7
commit 24d221209c
20 changed files with 167 additions and 18 deletions
+11 -3
View File
@@ -8,7 +8,7 @@ import { topicKey, parseMbr } from 'bare-os-protocol'
import { SwarmDisk } from './lib/swarm-disk.js'
import { runKernelFromSource, runBinCommand } from './lib/kernel-runner.js'
import { createVfs } from './lib/vfs.js'
import { execShellLine } from './lib/shell.js'
import { execShellLine, syncBareOsExitStatusEnv } from './lib/shell.js'
import { packageRootDir, defaultBootCorestorePath } from './lib/paths.js'
import {
createBareReadlineQuestion,
@@ -36,6 +36,7 @@ import {
startBareInitd,
registerKernelShutdownHook
} from './lib/bare-initd.js'
import { BARE_OS_CTX_API_VERSION } from './lib/bare-os-ctx-api.js'
import './lib/bare-cron.js'
const _pkg = packageRootDir(import.meta.url)
@@ -221,6 +222,7 @@ async function executeKernel(disk, store, swarm, initSource) {
GID: '65534',
GROUP: 'guest',
BARE_OS_IDENTITY: 'guest',
BARE_OS_EXIT_STATUS: '0',
0: 'bare-os'
}
/** @type {{ getMounts: () => Map<string, { drive: import('hyperdrive').default, writable: boolean }> }} */
@@ -234,6 +236,8 @@ async function executeKernel(disk, store, swarm, initSource) {
let forceSessionEnd = false
/** @type {Record<string, unknown>} */
const ctx = {
/** Documented `ctx` contract version; bump in lib/bare-os-ctx-api.js when the surface changes. */
bareOsCtxApiVersion: BARE_OS_CTX_API_VERSION,
disk,
drive: disk.drive,
personalDrive: disk.personalDrive,
@@ -359,10 +363,14 @@ async function executeKernel(disk, store, swarm, initSource) {
if (exitMatch) {
const ec =
exitMatch[1] != null ? Number.parseInt(exitMatch[1], 10) : 0
ctx.requestBooterExit(Number.isFinite(ec) ? ec : 0)
const code = Number.isFinite(ec) ? ec : 0
ctx.exitCode = code
syncBareOsExitStatusEnv(ctx)
ctx.requestBooterExit(code)
return 'exit'
}
return await execShellLine(ctx, line)
const st = await execShellLine(ctx, line)
return st
}
ctx.readLine = async (prompt) => {
if (forceSessionEnd) return null
+15 -1
View File
@@ -84,6 +84,12 @@ export async function startBareInitd(ctx) {
const KERNEL_LOG_REL = '.kernel/kernel.log'
/** Max size before trimming older log bytes (best-effort). */
const KERNEL_LOG_MAX_BYTES = 512 * 1024
/** After trim, keep this many trailing bytes plus a notice line. */
const KERNEL_LOG_KEEP_BYTES = 256 * 1024
/**
* Append one UTF-8 line to ~/.kernel/kernel.log (personal drive). Best-effort; never throws.
* @param {Record<string, unknown>} ctx
@@ -106,7 +112,15 @@ async function appendKernelLog(ctx, kind, line) {
const prev = await vfs.readFile(logPath)
const ts = new Date().toISOString()
const chunk = ctx.b4a.from(`[${ts}] [${kind}] ${line}\n`)
const merged = prev ? ctx.b4a.concat([prev, chunk]) : chunk
let merged = prev ? ctx.b4a.concat([prev, chunk]) : chunk
if (merged.length > KERNEL_LOG_MAX_BYTES) {
const start = Math.max(0, merged.length - KERNEL_LOG_KEEP_BYTES)
const tail = merged.subarray(start)
const notice = ctx.b4a.from(
`[${ts}] [bare-os] kernel.log truncated (kept last ${KERNEL_LOG_KEEP_BYTES} bytes)\n`
)
merged = ctx.b4a.concat([notice, tail])
}
await vfs.writeFile(logPath, merged)
} catch {
/* ignore */
@@ -0,0 +1,5 @@
/**
* Semantic version of the booter `ctx` contract for custom kernels.
* Bump when adding/removing/renaming documented `ctx` fields or changing behavior.
*/
export const BARE_OS_CTX_API_VERSION = '1.0.0'
+30 -2
View File
@@ -30,6 +30,20 @@ function mergeChildExitCode(ctx, childCtx) {
}
}
/** Env key mirroring last command exit status (POSIX `$?` parity). */
export const BARE_OS_EXIT_STATUS_ENV = 'BARE_OS_EXIT_STATUS'
/**
* Mirror `ctx.exitCode` into `ctx.vfs.env` so kernels and `echo $?` see last status.
* @param {Record<string, unknown>} ctx
*/
export function syncBareOsExitStatusEnv(ctx) {
const env = ctx.vfs?.env
if (!env || typeof env !== 'object') return
const n = Number(ctx.exitCode)
env[BARE_OS_EXIT_STATUS_ENV] = String(Number.isFinite(n) ? n : 0)
}
/** Max alias indirections (prevents cycles). */
const MAX_ALIAS_DEPTH = 16
@@ -338,10 +352,19 @@ export function expandWord(s, env) {
break
}
const name = s.slice(j + 2, end)
out += env[name] ?? ''
if (name === '?') {
out += env[BARE_OS_EXIT_STATUS_ENV] ?? '0'
} else {
out += env[name] ?? ''
}
j = end + 1
continue
}
if (s[j + 1] === '?') {
out += env[BARE_OS_EXIT_STATUS_ENV] ?? '0'
j += 2
continue
}
if (/[0-9]/.test(s[j + 1] ?? '')) {
out += env[s[j + 1]] ?? ''
j += 2
@@ -853,6 +876,7 @@ export async function execShellLine(ctx, line) {
'shell: unsupported operator & (job control is not available)'
)
ctx.exitCode = 2
syncBareOsExitStatusEnv(ctx)
return 'ok'
}
@@ -860,7 +884,11 @@ export async function execShellLine(ctx, line) {
for (const listTok of lists) {
if (!listTok.length) continue
const r = await execAndOrList(ctx, listTok)
if (r === 'exit') return 'exit'
if (r === 'exit') {
syncBareOsExitStatusEnv(ctx)
return 'exit'
}
}
syncBareOsExitStatusEnv(ctx)
return 'ok'
}
+43 -1
View File
@@ -21,8 +21,11 @@ import {
loadBarerc,
BARERC_SKELETON,
splitTokensBySemicolon,
splitTokensByAndOr
splitTokensByAndOr,
BARE_OS_EXIT_STATUS_ENV,
syncBareOsExitStatusEnv
} from './lib/shell.js'
import { BARE_OS_CTX_API_VERSION } from './lib/bare-os-ctx-api.js'
import {
fuzzyMatch,
stripAnsi,
@@ -74,6 +77,7 @@ function testCtx(drive, personal, env) {
UID: '1000',
GID: '1000',
PWD: '/home/user',
BARE_OS_EXIT_STATUS: '0',
...env
}
const vfs = createVfs(drive, personal, shellEnv)
@@ -455,6 +459,44 @@ test('expandWord reads env', async (t) => {
t.is(expandWord('x${HOME}y', { HOME: '/h' }), 'x/hy')
})
test('expandWord $? and ${?} use BARE_OS_EXIT_STATUS', async (t) => {
const env = { [BARE_OS_EXIT_STATUS_ENV]: '7', HOME: '/h' }
t.is(expandWord('code=$?', env), 'code=7')
t.is(expandWord('c=${?}', env), 'c=7')
t.is(expandWord('missing=$?', {}), 'missing=0')
})
test('syncBareOsExitStatusEnv and execShellLine update env', async (t) => {
const dir = testCorestoreDir('exstat')
const store = new Corestore(dir)
const drive = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('pex'))
await drive.ready()
await personal.ready()
await drive.put(
'/bin/false',
b4a.from(`async function run(ctx) { ctx.exitCode = 1 }`)
)
await drive.put(
'/bin/true',
b4a.from(`async function run(ctx) { ctx.exitCode = 0 }`)
)
const ctx = testCtx(drive, personal)
ctx.exitCode = 0
syncBareOsExitStatusEnv(ctx)
t.is(ctx.vfs.env[BARE_OS_EXIT_STATUS_ENV], '0')
await execShellLine(ctx, 'false')
t.is(ctx.vfs.env[BARE_OS_EXIT_STATUS_ENV], '1')
await execShellLine(ctx, 'true')
t.is(ctx.vfs.env[BARE_OS_EXIT_STATUS_ENV], '0')
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('BARE_OS_CTX_API_VERSION is semver-shaped', async (t) => {
t.ok(/^\d+\.\d+\.\d+$/.test(BARE_OS_CTX_API_VERSION))
})
test('expandArgvAliases expands first word and keeps trailing argv', async (t) => {
t.alike(expandArgvAliases(['ll', 'z'], defaultShellAliases()), [
'ls',
@@ -0,0 +1,4 @@
# Optional boot-time shell lines (one command per non-comment line).
# Executed by /boot/init.js after /etc/os-release and this motd, before the main banner.
# Example (uncomment to use):
# export BARE_OS_SHOW_RC=1
+1
View File
@@ -0,0 +1 @@
Welcome to Bare OS — edit /etc/motd on the system image to customize this line.
File diff suppressed because one or more lines are too long