Updates
This commit is contained in:
@@ -39,6 +39,7 @@ import {
|
||||
getBareServiceRuntime
|
||||
} from './lib/bare-initd.js'
|
||||
import { BARE_OS_CTX_API_VERSION } from './lib/bare-os-ctx-api.js'
|
||||
import { buildBareOsRuntimeCaps } from './lib/bare-os-runtime-caps.js'
|
||||
import './lib/bare-cron.js'
|
||||
|
||||
const _pkg = packageRootDir(import.meta.url)
|
||||
@@ -228,6 +229,19 @@ async function executeKernel(disk, store, swarm, initSource) {
|
||||
BARE_OS_CTX_API_VERSION,
|
||||
0: 'bare-os'
|
||||
}
|
||||
const hostEnv = globalThis.process?.env
|
||||
if (hostEnv && typeof hostEnv === 'object') {
|
||||
for (const k of [
|
||||
'BARE_OS_PIPELINE_MAX_STAGES',
|
||||
'BARE_OS_PIPELINE_MAX_BYTES',
|
||||
'BARE_OS_PIPELINE_MAX_LINES',
|
||||
'BARE_OS_BOOT_PROFILE',
|
||||
'BARE_OS_ONBOOT'
|
||||
]) {
|
||||
const v = hostEnv[k]
|
||||
if (v != null && v !== '') shellEnv[k] = v
|
||||
}
|
||||
}
|
||||
/** @type {{ getMounts: () => Map<string, { drive: import('hyperdrive').default, writable: boolean }> }} */
|
||||
const vfsMountRef = { getMounts: () => new Map() }
|
||||
const bootStartedMs = Date.now()
|
||||
@@ -262,6 +276,8 @@ async function executeKernel(disk, store, swarm, initSource) {
|
||||
const ctx = {
|
||||
/** Documented `ctx` contract version; bump in lib/bare-os-ctx-api.js when the surface changes. */
|
||||
bareOsCtxApiVersion: BARE_OS_CTX_API_VERSION,
|
||||
/** Frozen snapshot: pipeline limits, pseudo-`/` paths, feature flags (see developer guide). */
|
||||
bareOsRuntimeCaps: buildBareOsRuntimeCaps(shellEnv),
|
||||
/** Milliseconds since Unix epoch when this session started VFS construction (for `/proc/uptime`). */
|
||||
bareOsBootStartedMs: bootStartedMs,
|
||||
/** True when `BARE_OS_SKIP_REPL=1` — stdin is non-interactive; `readLine` yields EOF immediately after boot. */
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
* 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.1.0'
|
||||
export const BARE_OS_CTX_API_VERSION = '1.2.0'
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Stable introspection for custom kernels and scripts (`ctx.bareOsRuntimeCaps`).
|
||||
* Keep in sync with `vfs.js` pseudo paths and `shell.js` pipeline limits.
|
||||
*/
|
||||
|
||||
import { BARE_OS_CTX_API_VERSION } from './bare-os-ctx-api.js'
|
||||
import {
|
||||
DEFAULT_PIPELINE_MAX_CAPTURE_BYTES,
|
||||
DEFAULT_PIPELINE_MAX_CAPTURE_LINES,
|
||||
DEFAULT_PIPELINE_MAX_STAGES,
|
||||
getBareOsPipelineLimits
|
||||
} from './shell.js'
|
||||
|
||||
/**
|
||||
* Documented pseudo filesystem paths (read-mostly; some accept discard writes).
|
||||
* @type {readonly string[]}
|
||||
*/
|
||||
export const BARE_OS_PSEUDO_FS_PATHS = Object.freeze([
|
||||
'/dev',
|
||||
'/dev/null',
|
||||
'/dev/zero',
|
||||
'/dev/urandom',
|
||||
'/proc',
|
||||
'/proc/bare_os_version',
|
||||
'/proc/cpuinfo',
|
||||
'/proc/loadavg',
|
||||
'/proc/meminfo',
|
||||
'/proc/self',
|
||||
'/proc/self/cmdline',
|
||||
'/proc/self/environ',
|
||||
'/proc/self/exe',
|
||||
'/proc/uptime',
|
||||
'/proc/version',
|
||||
'/run',
|
||||
'/run/bare-os',
|
||||
'/run/bare-os/units',
|
||||
'/sys',
|
||||
'/sys/fs',
|
||||
'/sys/fs/bare_os',
|
||||
'/sys/fs/bare_os/version'
|
||||
])
|
||||
|
||||
/**
|
||||
* @param {Record<string, string>} shellEnv Session environment (same object as `ctx.env` / `vfs.env`).
|
||||
*/
|
||||
export function buildBareOsRuntimeCaps(shellEnv) {
|
||||
const pl = getBareOsPipelineLimits(shellEnv)
|
||||
return Object.freeze({
|
||||
ctxApiVersion: BARE_OS_CTX_API_VERSION,
|
||||
pipeline: Object.freeze({
|
||||
maxStages: pl.maxStages,
|
||||
maxBytes: pl.maxBytes,
|
||||
maxLines: pl.maxLines,
|
||||
defaults: Object.freeze({
|
||||
maxStages: DEFAULT_PIPELINE_MAX_STAGES,
|
||||
maxBytes: DEFAULT_PIPELINE_MAX_CAPTURE_BYTES,
|
||||
maxLines: DEFAULT_PIPELINE_MAX_CAPTURE_LINES
|
||||
}),
|
||||
envKeys: Object.freeze([
|
||||
'BARE_OS_PIPELINE_MAX_STAGES',
|
||||
'BARE_OS_PIPELINE_MAX_BYTES',
|
||||
'BARE_OS_PIPELINE_MAX_LINES'
|
||||
])
|
||||
}),
|
||||
pseudoFsPaths: BARE_OS_PSEUDO_FS_PATHS,
|
||||
features: Object.freeze({
|
||||
simulatedPipelines: true,
|
||||
bareInitd: true,
|
||||
vfsTwoDrive: true,
|
||||
identityVault: true
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -53,9 +53,10 @@ export const DEFAULT_PIPELINE_MAX_CAPTURE_BYTES = 2 * 1024 * 1024
|
||||
export const DEFAULT_PIPELINE_MAX_CAPTURE_LINES = 50000
|
||||
|
||||
/**
|
||||
* Resolved simulated pipeline limits for the current `vfs.env` / session env.
|
||||
* @param {Record<string, string> | null | undefined} env
|
||||
*/
|
||||
function pipelineLimitsFromEnv(env) {
|
||||
export function getBareOsPipelineLimits(env) {
|
||||
const o = env && typeof env === 'object' ? env : {}
|
||||
const parse = (key, def) => {
|
||||
const v = o[key]
|
||||
@@ -650,7 +651,7 @@ function segmentHasCommand(seg) {
|
||||
async function execParsedPipeline(ctx, pipeline) {
|
||||
const vfs = ctx.vfs
|
||||
const env = vfs.env
|
||||
const lim = pipelineLimitsFromEnv(env)
|
||||
const lim = getBareOsPipelineLimits(env)
|
||||
if (pipeline.length > lim.maxStages) {
|
||||
ctx.console.error(
|
||||
`shell: pipeline exceeds BARE_OS_PIPELINE_MAX_STAGES (${lim.maxStages})`
|
||||
|
||||
@@ -23,9 +23,12 @@ import {
|
||||
splitTokensBySemicolon,
|
||||
splitTokensByAndOr,
|
||||
BARE_OS_EXIT_STATUS_ENV,
|
||||
syncBareOsExitStatusEnv
|
||||
syncBareOsExitStatusEnv,
|
||||
getBareOsPipelineLimits,
|
||||
DEFAULT_PIPELINE_MAX_STAGES
|
||||
} from './lib/shell.js'
|
||||
import { BARE_OS_CTX_API_VERSION } from './lib/bare-os-ctx-api.js'
|
||||
import { buildBareOsRuntimeCaps } from './lib/bare-os-runtime-caps.js'
|
||||
import {
|
||||
fuzzyMatch,
|
||||
stripAnsi,
|
||||
@@ -753,6 +756,31 @@ test('BARE_OS_CTX_API_VERSION is semver-shaped', async (t) => {
|
||||
t.ok(/^\d+\.\d+\.\d+$/.test(BARE_OS_CTX_API_VERSION))
|
||||
})
|
||||
|
||||
test('getBareOsPipelineLimits reads BARE_OS_PIPELINE_* from env', async (t) => {
|
||||
const def = getBareOsPipelineLimits({})
|
||||
t.is(def.maxStages, DEFAULT_PIPELINE_MAX_STAGES)
|
||||
const custom = getBareOsPipelineLimits({
|
||||
BARE_OS_PIPELINE_MAX_STAGES: '4',
|
||||
BARE_OS_PIPELINE_MAX_BYTES: '100',
|
||||
BARE_OS_PIPELINE_MAX_LINES: '20'
|
||||
})
|
||||
t.is(custom.maxStages, 4)
|
||||
t.is(custom.maxBytes, 100)
|
||||
t.is(custom.maxLines, 20)
|
||||
})
|
||||
|
||||
test('buildBareOsRuntimeCaps matches ctx API version and pipeline env', async (t) => {
|
||||
const caps = buildBareOsRuntimeCaps({
|
||||
BARE_OS_PIPELINE_MAX_STAGES: '8',
|
||||
BARE_OS_CTX_API_VERSION
|
||||
})
|
||||
t.is(caps.ctxApiVersion, BARE_OS_CTX_API_VERSION)
|
||||
t.is(caps.pipeline.maxStages, 8)
|
||||
t.ok(Array.isArray(caps.pseudoFsPaths))
|
||||
t.ok(caps.pseudoFsPaths.includes('/proc/version'))
|
||||
t.is(caps.features.simulatedPipelines, true)
|
||||
})
|
||||
|
||||
test('expandArgvAliases expands first word and keeps trailing argv', async (t) => {
|
||||
t.alike(expandArgvAliases(['ll', 'z'], defaultShellAliases()), [
|
||||
'ls',
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# kernel — system image sources
|
||||
|
||||
Files in this directory are **read from disk by the seeder** (or copied into `packages/bare-os-seeder/kernel/` for Pear) and written into the **system Hyperdrive** with **no temporary directory** on the host.
|
||||
|
||||
## Staging map (seeder)
|
||||
|
||||
| Source | Drive path |
|
||||
| ----------------- | --------------------- |
|
||||
| `init.js` | `/boot/init.js` |
|
||||
| `bin/<name>` | `/bin/<name>` |
|
||||
| `etc/...` | `/etc/...` |
|
||||
| `share/man/...` | `/share/man/...` |
|
||||
| Any other file | `/<relative path>` |
|
||||
|
||||
## Contents
|
||||
|
||||
- **`init.js`** — Kernel entry: must define `async function start(ctx)`. Boot order: **`/etc/os-release`** → **`/etc/motd`** → optional **`/etc/bare-os/rc.profile.<profile>`** (profile from **`BARE_OS_BOOT_PROFILE`** or first line of **`/etc/bare-os/profile`**) → **`/etc/bare-os/rc`** → **`/etc/bare-os/rc.d/*`** (sorted; only names starting with a digit, plus skips dotfiles, `*~`, `README*`, `*.md`) → banner → when **`BARE_OS_SKIP_REPL`**, optional one line from **`BARE_OS_ONBOOT`** or **`/etc/bare-os/onboot`** → **`readLine` / `execLine`** loop (boot snippet errors are logged, not fatal). Custom kernels may call **`ctx.registerKernelShutdownHook(fn)`** before initd disposers; use **`ctx.bareOsRuntimeCaps`** for limits and pseudo path lists ([`developer-guide/02-the-context-object.md`](../developer-guide/02-the-context-object.md)).
|
||||
- **`bin/`** — **Tier-1 utilities** built by [bare-os-coreutils](../packages/bare-os-coreutils/README.md). Each file is **`runtime.js`** + optional **`lib/*-engine.js`** (**`sed`**, **`awk`**) or **`lib/man-render.js`** (**`man`**) + **`async function run(ctx, argv)`** (no ESM **`import`** in **`src/`**).
|
||||
- **`share/man/man.json`** — Merged manual database for **`/bin/man`** (built by **`bare-os-coreutils`**; see [handbook ch.10](../handbook/10-manpages-and-online-help.md)).
|
||||
- **`etc/os-release`** — Static OS metadata (`NAME`, `VERSION`, …).
|
||||
- **`etc/motd`** — Optional message printed after **`os-release`** (distributors can customize).
|
||||
- **`etc/bare-os/banner`** or **`/etc/issue`** — If present on the system drive, the default kernel prints one of these instead of the built-in session hint (unless **`BARE_OS_SKIP_REPL`** shortens the banner). Set **`BARE_OS_BOOT_TRACE=1`** in the session environment to log boot phase timings on stderr.
|
||||
- **`etc/bare-os/rc`** — Optional boot snippet: one **`execLine`** per non-comment line (trusted).
|
||||
- **`etc/bare-os/rc.d/`** — Optional extra snippets (basename must start with a digit), same line rules, run after **`rc`** in filename order. Human-oriented notes live in **`.README`** (a dotfile so legacy **`init.js`** never executes it).
|
||||
|
||||
## Editing workflow
|
||||
|
||||
1. Change sources under `kernel/` or `packages/bare-os-coreutils/src/`.
|
||||
2. Run `npm run build -w bare-os-coreutils` to refresh `kernel/bin/*`.
|
||||
3. Run seeder again to re-stage the drive (or use a fresh Corestore for a clean image).
|
||||
|
||||
Pear bundles use the **vendored** tree under `packages/bare-os-seeder/kernel/`; keep it in sync by running the same build before `pear stage`. **`npm test`** runs **`scripts/verify-kernel-seeder-parity.mjs`** (after **`bare-os-coreutils`** build) so the two trees match byte-for-byte.
|
||||
|
||||
## See also
|
||||
|
||||
- [Handbook — Kernel and userspace](../handbook/06-kernel-and-binaries.md)
|
||||
- [Handbook — POSIX utilities, shell, VFS](../handbook/09-posix-utilities-shell-and-vfs.md)
|
||||
- [DOCUMENTATION.md](../DOCUMENTATION.md) §9
|
||||
@@ -2,8 +2,16 @@
|
||||
* 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 → /etc/bare-os/rc → /etc/bare-os/rc.d/*
|
||||
* (sorted; digit-prefixed snippet names) → session banner → interactive loop.
|
||||
* Boot order: /etc/os-release → /etc/motd → optional profile rc → /etc/bare-os/rc →
|
||||
* /etc/bare-os/rc.d/* (sorted; digit-prefixed snippet names) → session banner →
|
||||
* optional oneshot onboot 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 one line from BARE_OS_ONBOOT env
|
||||
* or first non-empty, non-# line from /etc/bare-os/onboot (trusted), then readLine yields EOF.
|
||||
*
|
||||
* Use ctx.registerKernelShutdownHook(fn) for teardown before initd disposers.
|
||||
*/
|
||||
|
||||
@@ -71,6 +79,80 @@ async function printMotd(ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
async function runProfileRc(ctx, profileName) {
|
||||
if (!profileName) return
|
||||
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
|
||||
}
|
||||
await runRcFileAt(
|
||||
ctx,
|
||||
`/etc/bare-os/rc.profile.${safe}`,
|
||||
`rc.profile.${safe}`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* When stdin is non-interactive, run a single trusted boot command (automation).
|
||||
* @param {Record<string, unknown>} ctx
|
||||
*/
|
||||
async function runOnbootOnce(ctx) {
|
||||
if (!ctx.bareOsSkipRepl) return
|
||||
const { execLine, console, drive, b4a, env } = ctx
|
||||
let line = ''
|
||||
const fromEnv = env && env.BARE_OS_ONBOOT
|
||||
if (fromEnv != null && String(fromEnv).trim()) {
|
||||
line = String(fromEnv).trim()
|
||||
} 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
|
||||
line = t
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('onboot: ' + ((e && e.message) || String(e)))
|
||||
}
|
||||
}
|
||||
if (!line) return
|
||||
try {
|
||||
await execLine(line)
|
||||
} catch (e) {
|
||||
console.error((e && e.message) || String(e))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string} drivePath absolute path on system drive
|
||||
@@ -161,9 +243,12 @@ async function start(ctx) {
|
||||
const { readLine, execLine, console } = ctx
|
||||
await bootTimed(ctx, 'os-release', () => printOsRelease(ctx))
|
||||
await bootTimed(ctx, 'motd', () => printMotd(ctx))
|
||||
const profileName = await resolveBootProfileName(ctx)
|
||||
await bootTimed(ctx, 'rc.profile', () => runProfileRc(ctx, profileName))
|
||||
await bootTimed(ctx, 'rc', () => runRcFileAt(ctx, '/etc/bare-os/rc', 'rc'))
|
||||
await bootTimed(ctx, 'rc.d', () => runBareOsRcDir(ctx))
|
||||
await printSessionBanner(ctx)
|
||||
await bootTimed(ctx, 'onboot', () => runOnbootOnce(ctx))
|
||||
while (true) {
|
||||
const line = await readLine('')
|
||||
if (line == null) break
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user