Updates
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,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'
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user