Kernel Updates

This commit is contained in:
Raven Scott
2026-04-03 23:21:30 -04:00
parent 184b7fe3b1
commit d8e580d1af
44 changed files with 1869 additions and 476 deletions
+5 -2
View File
@@ -53,8 +53,11 @@ The following are set on `ctx` before the kernel starts (unless noted as overwri
| **`bareOsAwaitInitdUnits(names, timeoutMs)`** | Resolves when all listed initd units are **`active`** (polls **`getBareServiceRuntime`**); returns **`false`** on timeout. | | **`bareOsAwaitInitdUnits(names, timeoutMs)`** | Resolves when all listed initd units are **`active`** (polls **`getBareServiceRuntime`**); returns **`false`** on timeout. |
| **`bareOsGetResourceStatus()`** | Returns a plain object snapshot (pipeline limits, exec depth, IPC **`stats()`**, session counters, swarm peer count)—mirrors **`/proc/bare_os_resources`**. | | **`bareOsGetResourceStatus()`** | Returns a plain object snapshot (pipeline limits, exec depth, IPC **`stats()`**, session counters, swarm peer count)—mirrors **`/proc/bare_os_resources`**. |
| **`bareOsRegisterVirtualFile(name, reader)`** | Registers **`/run/bare-os/virtual/<name>`** content; **`reader`** may return string or **`Uint8Array`** (sync or async). Gated by runtime cap **`virtualRegisterFiles`**. | | **`bareOsRegisterVirtualFile(name, reader)`** | Registers **`/run/bare-os/virtual/<name>`** content; **`reader`** may return string or **`Uint8Array`** (sync or async). Gated by runtime cap **`virtualRegisterFiles`**. |
| **`bareOsSandboxRunScript()`** | **Throws** today—reserved for future worker/isolate execution; see [Chapter 9](09-security-and-trust.md). | | **`bareOsSandboxRunScript(source, argv?, opts?)`** | Runs script source with a **restricted `ctx`** (personal-drive writes only; identity/virtual registration disabled). Respects **`raceWithAbortAndTimeout`** opts. Disable with **`BARE_OS_SANDBOX_SCRIPT=0`**. See [Chapter 9](09-security-and-trust.md). |
| **`bareOsRequestPearReload()`** | Returns **`{ requested, hint, env }`** for Pear OTA integration (host must apply); not a host IPC call by itself. | | **`bareOsBootFileSha256Hex(buf)`** | **`sha256` hex for boot manifest checks** (`BARE_OS_BOOT_MANIFEST` + `/etc/bare-os/boot.manifest.json` on the stock kernel). |
| **`bareOsRegisterBootPhaseHook(phase, fn)`** / **`bareOsInvokeBootPhaseHooks(ev)`** | Hooks around stock **`kernel/init.js`** phases; **`ev`** includes **`phase`**, **`when`** (`before` / `after`), **`label`**. **`phase`** may be `*` or `before:rc` style. |
| **`bareOsInvalidateVirtualFile(name)`** / **`bareOsUpdateVirtualFileMeta(name, patch)`** | Virtual files under **`/run/bare-os/virtual/`**; **`bareOsRegisterVirtualFile`** accepts optional **`{ etag }`** third argument or **`{ read }`** object. |
| **`bareOsRequestPearReload(opts?)`** | **`async`** — returns **`{ requested, hint, env }`**; with **`{ persistRequest: true }`** writes **`~/.bare-os/pear-reload.request`** and may **`process.emit('bare-os:pear-reload', …)`** on Node. |
| **`bareOsHostStats`** _(optional)_ | When the **`bare-os`** npm module loads on the host, a **frozen** snapshot: **`hostname`**, **`loadavg`**, **`cpus`**, **`networkInterfaces`**. | | **`bareOsHostStats`** _(optional)_ | When the **`bare-os`** npm module loads on the host, a **frozen** snapshot: **`hostname`**, **`loadavg`**, **`cpus`**, **`networkInterfaces`**. |
| **`httpFetch`** _(optional)_ | When Node/global **`fetch`** exists, the booter sets a **`fetch`** compatible function with optional **HTTP allow/deny** policy (**`BARE_OS_HTTP_ALLOWLIST`**, **`BARE_OS_HTTP_DENYLIST`**) and audit hooks when **`BARE_OS_AUDIT`** is on. | | **`httpFetch`** _(optional)_ | When Node/global **`fetch`** exists, the booter sets a **`fetch`** compatible function with optional **HTTP allow/deny** policy (**`BARE_OS_HTTP_ALLOWLIST`**, **`BARE_OS_HTTP_DENYLIST`**) and audit hooks when **`BARE_OS_AUDIT`** is on. |
+1 -1
View File
@@ -18,7 +18,7 @@ The **system** Hyperdrive is the **OS image**. You normally obtain it by **repli
**IPC JSON-RPC:** when **`BARE_OS_IPC_RPC_TOKEN`** is set, **`pushJson`** payloads must include matching **`bareOsIpcToken`** or the push throws. Line size is capped (**`BARE_OS_IPC_JSON_MAX_BYTES`**, default 256KiB). **IPC JSON-RPC:** when **`BARE_OS_IPC_RPC_TOKEN`** is set, **`pushJson`** payloads must include matching **`bareOsIpcToken`** or the push throws. Line size is capped (**`BARE_OS_IPC_JSON_MAX_BYTES`**, default 256KiB).
**Future isolation:** **`ctx.bareOsSandboxRunScript()`** is reserved and **throws** today; real worker or Pear-isolate execution would narrow trust for untrusted scripts—see the handbook blueprints chapter for the architectural split. **Sandboxed scripts:** **`ctx.bareOsSandboxRunScript(source, argv?, opts?)`** runs in-image JS with a **restricted `ctx`**: writes are limited to the personal namespace (same routing rules as `isPersonalRoute`), and identity / vault / virtual-file registration hooks are disabled. Disable entirely with **`BARE_OS_SANDBOX_SCRIPT=0`**. This is still **not** a hardware isolate—treat it as a trust reducer, not a security boundary.
--- ---
+1
View File
@@ -93,6 +93,7 @@ function bareOsEmitRaw(ctx, chunk) {
* next, exit, break, continue, ++/--, arrays, strings, regex ~ !~, builtins (length, substr, index, * next, exit, break, continue, ++/--, arrays, strings, regex ~ !~, builtins (length, substr, index,
* split, sprintf, sub, gsub, match, int, tolower, toupper, rand, srand), getline from stdin/files, * split, sprintf, sub, gsub, match, int, tolower, toupper, rand, srand), getline from stdin/files,
* -F FS, NF, NR, FNR, $0..$n, OFS, ORS, RS, ARGC, ARGV, FILENAME, ENVIRON (read-only mirror). * -F FS, NF, NR, FNR, $0..$n, OFS, ORS, RS, ARGC, ARGV, FILENAME, ENVIRON (read-only mirror).
* GNU-style empty fields: consecutive FS delimiters still advance $1..$NF where the grammar allows.
*/ */
function bareAwkError(msg) { function bareAwkError(msg) {
+3
View File
@@ -98,6 +98,9 @@ async function run(ctx, argv) {
git remote add origin 'git+pear://<key-or-link>' git remote add origin 'git+pear://<key-or-link>'
git push origin main git push origin main
Host env (aligns with gip-transport / git-remote-git+pear):
GIT_PEAR_DEBUG, HYPERSWARM_DHT, PEAR_CHANNEL — forwarded by Pear tooling when set.
Pear app channel metadata for images: set BARE_OS_PEAR_CHANNEL / BARE_OS_PEAR_RELEASE on the host. Pear app channel metadata for images: set BARE_OS_PEAR_CHANNEL / BARE_OS_PEAR_RELEASE on the host.
`) `)
ctx.exitCode = 0 ctx.exitCode = 0
+1 -1
View File
@@ -87,7 +87,7 @@ function bareOsEmitRaw(ctx, chunk) {
return false return false
} }
var BARE_OS_HELP_BIN_SPACED = "arch awk base32 base64 basename basenc cat chgrp chmod chown cksum clear comm cp crontab curl cut date df dir dircolors dirname du echo edit env exit expand expr factor false find fmt fold getconf git git-pear grep groups hdms head help hostid hostname id install join journalctl jq ln login logname logout ls man md5sum mkdir mkfifo mktemp mv nano nl nproc numfmt od paste pathchk pr printenv printf pwd readlink realpath rev rm rmdir savevault sed seq sha1sum sha256sum sha512sum shuf sleep sort split stat sum sync systemctl tac tail tee test theme time touch tr true truncate tsort tty uname unexpand uniq unlink uptime users vdir wc wget which who whoami xargs yes" var BARE_OS_HELP_BIN_SPACED = "arch awk base32 base64 basename basenc cat chgrp chmod chown cksum clear comm cp crontab curl cut date df dir dircolors dirname du echo edit env exit expand expr factor false find fmt fold getconf git git-pear grep groups hdms head help hostid hostname id install join journalctl jq ln login logname logout ls man md5sum mkdir mkfifo mktemp mv nano nl nproc numfmt od oidc-publish paste pathchk pr printenv printf pwd readlink realpath rev rm rmdir savevault sed seq sha1sum sha256sum sha512sum shuf sleep sort split stat sum sync systemctl tac tail tee test theme time touch tr true truncate tsort tty uname unexpand uniq unlink uptime users vdir wc wget which who whoami xargs yes"
async function run(ctx, argv) { async function run(ctx, argv) {
ctx.console.log( ctx.console.log(
'Bare OS — default user: guest | shell builtins: alias, barerc, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: ' + 'Bare OS — default user: guest | shell builtins: alias, barerc, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: ' +
+150
View File
@@ -0,0 +1,150 @@
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* Thin helper for OIDC-style token exchange workflows (Holepunch oidc-publishing patterns).
* Uses ctx.httpFetch when present and host HTTP policy allows the issuer URL.
*/
async function run(ctx, argv) {
if (argv[1] === 'help' || argv[1] === '-h' || argv[1] === '--help') {
ctx.console.log(`oidc-publish — optional OIDC token helper
Reads OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET (or argv) and POSTs
to \${issuer}/oauth/token with grant_type=client_credentials when ctx.httpFetch exists.
Example:
export OIDC_ISSUER=https://issuer.example
oidc-publish
Requires delegated httpFetch and allowlisted host (BARE_OS_HTTP_ALLOWLIST).
`)
ctx.exitCode = 0
return
}
const env = ctx.vfs?.env || {}
const issuer = String(env.OIDC_ISSUER || argv[2] || '').replace(/\/+$/, '')
const cid = String(env.OIDC_CLIENT_ID || argv[3] || '')
const csec = String(env.OIDC_CLIENT_SECRET || argv[4] || '')
if (!issuer || !cid || !csec) {
ctx.console.error(
'oidc-publish: set OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET'
)
ctx.exitCode = 2
return
}
if (typeof ctx.httpFetch !== 'function') {
ctx.console.error('oidc-publish: ctx.httpFetch not available')
ctx.exitCode = 1
return
}
const url = `${issuer}/oauth/token`
const body =
'grant_type=client_credentials&client_id=' +
encodeURIComponent(cid) +
'&client_secret=' +
encodeURIComponent(csec)
try {
const res = await ctx.httpFetch(url, {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body
})
const text = await res.text()
if (!res.ok) {
ctx.console.error('oidc-publish: HTTP ' + res.status + ' ' + text.slice(0, 200))
ctx.exitCode = 1
return
}
ctx.console.log(text.slice(0, 4000))
ctx.exitCode = 0
} catch (e) {
ctx.console.error('oidc-publish: ' + ((e && e.message) || String(e)))
ctx.exitCode = 1
}
}
@@ -0,0 +1,5 @@
{
"sha256": {
"/etc/bare-os/rc": "replace-with-sha256-hex-of-file-contents"
}
}
+111 -2
View File
@@ -196,6 +196,13 @@ function bootLineAllowed(line, allow) {
* @param {string[]} phaseLog * @param {string[]} phaseLog
*/ */
async function bootTimed(ctx, label, fn, phaseLog) { async function bootTimed(ctx, label, fn, phaseLog) {
if (typeof ctx.bareOsInvokeBootPhaseHooks === 'function') {
await ctx.bareOsInvokeBootPhaseHooks({
phase: label,
when: 'before',
label
})
}
const t0 = Date.now() const t0 = Date.now()
await fn() await fn()
const ms = Date.now() - t0 const ms = Date.now() - t0
@@ -226,6 +233,67 @@ async function bootTimed(ctx, label, fn, phaseLog) {
sessionId: (ctx.env && ctx.env.BARE_OS_SESSION_ID) || '' sessionId: (ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''
}) })
} }
if (typeof ctx.bareOsInvokeBootPhaseHooks === 'function') {
await ctx.bareOsInvokeBootPhaseHooks({
phase: 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
}
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
} }
/** /**
@@ -400,7 +468,15 @@ async function runRcFileAt(ctx, drivePath, label) {
try { try {
const buf = await drive.get(drivePath) const buf = await drive.get(drivePath)
if (!buf) return true if (!buf) return true
return await runRcLines(ctx, b4a.toString(buf)) 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) { } catch (e) {
console.error(`${label}: ` + ((e && e.message) || String(e))) console.error(`${label}: ` + ((e && e.message) || String(e)))
return true return true
@@ -578,6 +654,35 @@ async function runKernelSelftest(ctx) {
} }
} }
} }
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) { if (tap) {
console.error(`1..${tapN}`) console.error(`1..${tapN}`)
} }
@@ -596,7 +701,11 @@ function publishBootReady(ctx, phaseLog) {
phases: [...phaseLog], phases: [...phaseLog],
sessionId: sid, sessionId: sid,
completedAtMs: Date.now(), completedAtMs: Date.now(),
minimal: bootMinimal(ctx) minimal: bootMinimal(ctx),
subsystems: {
kernel: { ready: true, phaseCount: phaseLog.length },
initd: { awaited: true }
}
}) })
} }
+3
View File
@@ -0,0 +1,3 @@
{
"pins": {}
}
+204 -204
View File
@@ -1,6 +1,12 @@
{ {
"version": 1, "version": 1,
"bundles": [ "bundles": [
{
"path": "/lib/bare/bundles/b4a.js",
"keys": [
"b4a"
]
},
{ {
"path": "/lib/bare/bundles/safetyCatch.js", "path": "/lib/bare/bundles/safetyCatch.js",
"keys": [ "keys": [
@@ -13,12 +19,6 @@
"hypercoreIdEncoding" "hypercoreIdEncoding"
] ]
}, },
{
"path": "/lib/bare/bundles/b4a.js",
"keys": [
"b4a"
]
},
{ {
"path": "/lib/bare/bundles/compactEncoding.js", "path": "/lib/bare/bundles/compactEncoding.js",
"keys": [ "keys": [
@@ -31,30 +31,30 @@
"bareUrl" "bareUrl"
] ]
}, },
{
"path": "/lib/bare/bundles/protomux.js",
"keys": [
"protomux"
]
},
{ {
"path": "/lib/bare/bundles/bareEncoding.js", "path": "/lib/bare/bundles/bareEncoding.js",
"keys": [ "keys": [
"bareEncoding" "bareEncoding"
] ]
}, },
{
"path": "/lib/bare/bundles/barePath.js",
"keys": [
"barePath"
]
},
{ {
"path": "/lib/bare/bundles/bareEvents.js", "path": "/lib/bare/bundles/bareEvents.js",
"keys": [ "keys": [
"bareEvents" "bareEvents"
] ]
}, },
{
"path": "/lib/bare/bundles/protomux.js",
"keys": [
"protomux"
]
},
{
"path": "/lib/bare/bundles/barePath.js",
"keys": [
"barePath"
]
},
{ {
"path": "/lib/bare/bundles/bareAbort.js", "path": "/lib/bare/bundles/bareAbort.js",
"keys": [ "keys": [
@@ -73,6 +73,12 @@
"bareAnsiEscapes" "bareAnsiEscapes"
] ]
}, },
{
"path": "/lib/bare/bundles/bareAddonResolve.js",
"keys": [
"bareAddonResolve"
]
},
{ {
"path": "/lib/bare/bundles/bareReadline.js", "path": "/lib/bare/bundles/bareReadline.js",
"keys": [ "keys": [
@@ -85,24 +91,6 @@
"bareCrypto" "bareCrypto"
] ]
}, },
{
"path": "/lib/bare/bundles/bareAddonResolve.js",
"keys": [
"bareAddonResolve"
]
},
{
"path": "/lib/bare/bundles/bareAsyncHooks.js",
"keys": [
"bareAsyncHooks"
]
},
{
"path": "/lib/bare/bundles/bareAtomics.js",
"keys": [
"bareAtomics"
]
},
{ {
"path": "/lib/bare/bundles/bareAppKit.js", "path": "/lib/bare/bundles/bareAppKit.js",
"keys": [ "keys": [
@@ -116,9 +104,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareAssert.js", "path": "/lib/bare/bundles/bareAsyncHooks.js",
"keys": [ "keys": [
"bareAssert" "bareAsyncHooks"
] ]
}, },
{ {
@@ -127,6 +115,18 @@
"fetch" "fetch"
] ]
}, },
{
"path": "/lib/bare/bundles/bareAtomics.js",
"keys": [
"bareAtomics"
]
},
{
"path": "/lib/bare/bundles/bareAssert.js",
"keys": [
"bareAssert"
]
},
{ {
"path": "/lib/bare/bundles/bareBmp.js", "path": "/lib/bare/bundles/bareBmp.js",
"keys": [ "keys": [
@@ -139,12 +139,6 @@
"bareBundleCompile" "bareBundleCompile"
] ]
}, },
{
"path": "/lib/bare/bundles/bareBuffer.js",
"keys": [
"bareBuffer"
]
},
{ {
"path": "/lib/bare/bundles/bareBundle.js", "path": "/lib/bare/bundles/bareBundle.js",
"keys": [ "keys": [
@@ -152,9 +146,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareBundleEvaluate.js", "path": "/lib/bare/bundles/bareBuffer.js",
"keys": [ "keys": [
"bareBundleEvaluate" "bareBuffer"
] ]
}, },
{ {
@@ -163,24 +157,36 @@
"bareBluetoothApple" "bareBluetoothApple"
] ]
}, },
{
"path": "/lib/bare/bundles/bareBundleEvaluate.js",
"keys": [
"bareBundleEvaluate"
]
},
{ {
"path": "/lib/bare/bundles/bareBoot.js", "path": "/lib/bare/bundles/bareBoot.js",
"keys": [ "keys": [
"bareBoot" "bareBoot"
] ]
}, },
{
"path": "/lib/bare/bundles/bareDaemon.js",
"keys": [
"bareDaemon"
]
},
{ {
"path": "/lib/bare/bundles/bareConsole.js", "path": "/lib/bare/bundles/bareConsole.js",
"keys": [ "keys": [
"bareConsole" "bareConsole"
] ]
}, },
{
"path": "/lib/bare/bundles/bareDebugLog.js",
"keys": [
"bareDebugLog"
]
},
{
"path": "/lib/bare/bundles/bareDaemon.js",
"keys": [
"bareDaemon"
]
},
{ {
"path": "/lib/bare/bundles/bareBundleId.js", "path": "/lib/bare/bundles/bareBundleId.js",
"keys": [ "keys": [
@@ -193,12 +199,6 @@
"bareChannel" "bareChannel"
] ]
}, },
{
"path": "/lib/bare/bundles/bareDebugLog.js",
"keys": [
"bareDebugLog"
]
},
{ {
"path": "/lib/bare/bundles/bareDelta.js", "path": "/lib/bare/bundles/bareDelta.js",
"keys": [ "keys": [
@@ -223,6 +223,12 @@
"bareEnv" "bareEnv"
] ]
}, },
{
"path": "/lib/bare/bundles/bareExif.js",
"keys": [
"bareExif"
]
},
{ {
"path": "/lib/bare/bundles/bareCov.js", "path": "/lib/bare/bundles/bareCov.js",
"keys": [ "keys": [
@@ -235,12 +241,6 @@
"bareDgram" "bareDgram"
] ]
}, },
{
"path": "/lib/bare/bundles/bareExif.js",
"keys": [
"bareExif"
]
},
{ {
"path": "/lib/bare/bundles/bareFfmpeg.js", "path": "/lib/bare/bundles/bareFfmpeg.js",
"keys": [ "keys": [
@@ -253,12 +253,6 @@
"bareFfmpegEncodings" "bareFfmpegEncodings"
] ]
}, },
{
"path": "/lib/bare/bundles/bareFormData.js",
"keys": [
"bareFormData"
]
},
{ {
"path": "/lib/bare/bundles/bareFormat.js", "path": "/lib/bare/bundles/bareFormat.js",
"keys": [ "keys": [
@@ -266,9 +260,15 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareGif.js", "path": "/lib/bare/bundles/bareFormData.js",
"keys": [ "keys": [
"bareGif" "bareFormData"
]
},
{
"path": "/lib/bare/bundles/bareHeif.js",
"keys": [
"bareHeif"
] ]
}, },
{ {
@@ -278,15 +278,15 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareGtk.js", "path": "/lib/bare/bundles/bareGif.js",
"keys": [ "keys": [
"bareGtk" "bareGif"
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareHeif.js", "path": "/lib/bare/bundles/bareGtk.js",
"keys": [ "keys": [
"bareHeif" "bareGtk"
] ]
}, },
{ {
@@ -307,12 +307,6 @@
"bareHttpParser" "bareHttpParser"
] ]
}, },
{
"path": "/lib/bare/bundles/bareImageResample.js",
"keys": [
"bareImageResample"
]
},
{ {
"path": "/lib/bare/bundles/bareIco.js", "path": "/lib/bare/bundles/bareIco.js",
"keys": [ "keys": [
@@ -320,9 +314,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareHttp1.js", "path": "/lib/bare/bundles/bareImageResample.js",
"keys": [ "keys": [
"bareHttp1" "bareImageResample"
] ]
}, },
{ {
@@ -331,6 +325,12 @@
"bareInspect" "bareInspect"
] ]
}, },
{
"path": "/lib/bare/bundles/bareHttp1.js",
"keys": [
"bareHttp1"
]
},
{ {
"path": "/lib/bare/bundles/bareHttps.js", "path": "/lib/bare/bundles/bareHttps.js",
"keys": [ "keys": [
@@ -361,6 +361,12 @@
"bareLief" "bareLief"
] ]
}, },
{
"path": "/lib/bare/bundles/bareLogger.js",
"keys": [
"bareLogger"
]
},
{ {
"path": "/lib/bare/bundles/bareLink.js", "path": "/lib/bare/bundles/bareLink.js",
"keys": [ "keys": [
@@ -373,18 +379,6 @@
"bareInspector" "bareInspector"
] ]
}, },
{
"path": "/lib/bare/bundles/bareLogger.js",
"keys": [
"bareLogger"
]
},
{
"path": "/lib/bare/bundles/bareModuleLexer.js",
"keys": [
"bareModuleLexer"
]
},
{ {
"path": "/lib/bare/bundles/bareMake.js", "path": "/lib/bare/bundles/bareMake.js",
"keys": [ "keys": [
@@ -398,15 +392,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareModule.js", "path": "/lib/bare/bundles/bareModuleLexer.js",
"keys": [ "keys": [
"bareModule" "bareModuleLexer"
]
},
{
"path": "/lib/bare/bundles/bareNdk.js",
"keys": [
"bareNdk"
] ]
}, },
{ {
@@ -416,9 +404,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareNative.js", "path": "/lib/bare/bundles/bareModule.js",
"keys": [ "keys": [
"bareNative" "bareModule"
] ]
}, },
{ {
@@ -428,39 +416,15 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareOpen.js", "path": "/lib/bare/bundles/bareNdk.js",
"keys": [ "keys": [
"bareOpen" "bareNdk"
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareNet.js", "path": "/lib/bare/bundles/bareNative.js",
"keys": [ "keys": [
"bareNet" "bareNative"
]
},
{
"path": "/lib/bare/bundles/bareMedia.js",
"keys": [
"bareMedia"
]
},
{
"path": "/lib/bare/bundles/bareOs.js",
"keys": [
"bareOs"
]
},
{
"path": "/lib/bare/bundles/barePack.js",
"keys": [
"barePack"
]
},
{
"path": "/lib/bare/bundles/bareNodeRuntime.js",
"keys": [
"bareNodeRuntime"
] ]
}, },
{ {
@@ -470,9 +434,27 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/barePng.js", "path": "/lib/bare/bundles/bareOs.js",
"keys": [ "keys": [
"barePng" "bareOs"
]
},
{
"path": "/lib/bare/bundles/bareOpen.js",
"keys": [
"bareOpen"
]
},
{
"path": "/lib/bare/bundles/bareMedia.js",
"keys": [
"bareMedia"
]
},
{
"path": "/lib/bare/bundles/bareNet.js",
"keys": [
"bareNet"
] ]
}, },
{ {
@@ -481,12 +463,30 @@
"barePerformance" "barePerformance"
] ]
}, },
{
"path": "/lib/bare/bundles/barePng.js",
"keys": [
"barePng"
]
},
{ {
"path": "/lib/bare/bundles/barePackDrive.js", "path": "/lib/bare/bundles/barePackDrive.js",
"keys": [ "keys": [
"barePackDrive" "barePackDrive"
] ]
}, },
{
"path": "/lib/bare/bundles/barePack.js",
"keys": [
"barePack"
]
},
{
"path": "/lib/bare/bundles/barePipe.js",
"keys": [
"barePipe"
]
},
{ {
"path": "/lib/bare/bundles/barePunycode.js", "path": "/lib/bare/bundles/barePunycode.js",
"keys": [ "keys": [
@@ -505,18 +505,18 @@
"barePrebuild" "barePrebuild"
] ]
}, },
{
"path": "/lib/bare/bundles/barePipe.js",
"keys": [
"barePipe"
]
},
{ {
"path": "/lib/bare/bundles/bareQueueMicrotask.js", "path": "/lib/bare/bundles/bareQueueMicrotask.js",
"keys": [ "keys": [
"bareQueueMicrotask" "bareQueueMicrotask"
] ]
}, },
{
"path": "/lib/bare/bundles/bareNodeRuntime.js",
"keys": [
"bareNodeRuntime"
]
},
{ {
"path": "/lib/bare/bundles/bareRealm.js", "path": "/lib/bare/bundles/bareRealm.js",
"keys": [ "keys": [
@@ -541,12 +541,6 @@
"bareRuntime" "bareRuntime"
] ]
}, },
{
"path": "/lib/bare/bundles/bareSdl.js",
"keys": [
"bareSdl"
]
},
{ {
"path": "/lib/bare/bundles/bareRepl.js", "path": "/lib/bare/bundles/bareRepl.js",
"keys": [ "keys": [
@@ -559,6 +553,12 @@
"bareRpc" "bareRpc"
] ]
}, },
{
"path": "/lib/bare/bundles/bareSdl.js",
"keys": [
"bareSdl"
]
},
{ {
"path": "/lib/bare/bundles/bareSemver.js", "path": "/lib/bare/bundles/bareSemver.js",
"keys": [ "keys": [
@@ -571,12 +571,6 @@
"bareRun" "bareRun"
] ]
}, },
{
"path": "/lib/bare/bundles/bareSidecar.js",
"keys": [
"bareSidecar"
]
},
{ {
"path": "/lib/bare/bundles/bareSignals.js", "path": "/lib/bare/bundles/bareSignals.js",
"keys": [ "keys": [
@@ -584,9 +578,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareStorage.js", "path": "/lib/bare/bundles/bareSidecar.js",
"keys": [ "keys": [
"bareStorage" "bareSidecar"
] ]
}, },
{ {
@@ -596,9 +590,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareStringDecoder.js", "path": "/lib/bare/bundles/bareStorage.js",
"keys": [ "keys": [
"bareStringDecoder" "bareStorage"
] ]
}, },
{ {
@@ -607,6 +601,12 @@
"bareStdio" "bareStdio"
] ]
}, },
{
"path": "/lib/bare/bundles/bareStringDecoder.js",
"keys": [
"bareStringDecoder"
]
},
{ {
"path": "/lib/bare/bundles/bareSvg.js", "path": "/lib/bare/bundles/bareSvg.js",
"keys": [ "keys": [
@@ -625,6 +625,12 @@
"bareStructuredClone" "bareStructuredClone"
] ]
}, },
{
"path": "/lib/bare/bundles/bareTiff.js",
"keys": [
"bareTiff"
]
},
{ {
"path": "/lib/bare/bundles/bareTap.js", "path": "/lib/bare/bundles/bareTap.js",
"keys": [ "keys": [
@@ -638,15 +644,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareTiff.js", "path": "/lib/bare/bundles/bareThread.js",
"keys": [ "keys": [
"bareTiff" "bareThread"
]
},
{
"path": "/lib/bare/bundles/bareTimers.js",
"keys": [
"bareTimers"
] ]
}, },
{ {
@@ -655,18 +655,30 @@
"bareTcp" "bareTcp"
] ]
}, },
{
"path": "/lib/bare/bundles/bareThread.js",
"keys": [
"bareThread"
]
},
{ {
"path": "/lib/bare/bundles/bareTpl.js", "path": "/lib/bare/bundles/bareTpl.js",
"keys": [ "keys": [
"bareTpl" "bareTpl"
] ]
}, },
{
"path": "/lib/bare/bundles/bareTimers.js",
"keys": [
"bareTimers"
]
},
{
"path": "/lib/bare/bundles/bareTls.js",
"keys": [
"bareTls"
]
},
{
"path": "/lib/bare/bundles/bareTty.js",
"keys": [
"bareTty"
]
},
{ {
"path": "/lib/bare/bundles/bareType.js", "path": "/lib/bare/bundles/bareType.js",
"keys": [ "keys": [
@@ -679,30 +691,24 @@
"bareUiKit" "bareUiKit"
] ]
}, },
{
"path": "/lib/bare/bundles/bareTls.js",
"keys": [
"bareTls"
]
},
{ {
"path": "/lib/bare/bundles/bareUnpack.js", "path": "/lib/bare/bundles/bareUnpack.js",
"keys": [ "keys": [
"bareUnpack" "bareUnpack"
] ]
}, },
{
"path": "/lib/bare/bundles/bareTty.js",
"keys": [
"bareTty"
]
},
{ {
"path": "/lib/bare/bundles/bareV8.js", "path": "/lib/bare/bundles/bareV8.js",
"keys": [ "keys": [
"bareV8" "bareV8"
] ]
}, },
{
"path": "/lib/bare/bundles/bareWalkHandles.js",
"keys": [
"bareWalkHandles"
]
},
{ {
"path": "/lib/bare/bundles/bareVm.js", "path": "/lib/bare/bundles/bareVm.js",
"keys": [ "keys": [
@@ -715,12 +721,6 @@
"bareUnionBundle" "bareUnionBundle"
] ]
}, },
{
"path": "/lib/bare/bundles/bareWalkHandles.js",
"keys": [
"bareWalkHandles"
]
},
{ {
"path": "/lib/bare/bundles/bareWebKit.js", "path": "/lib/bare/bundles/bareWebKit.js",
"keys": [ "keys": [
@@ -739,18 +739,18 @@
"bareWebp" "bareWebp"
] ]
}, },
{
"path": "/lib/bare/bundles/bareUtils.js",
"keys": [
"bareUtils"
]
},
{ {
"path": "/lib/bare/bundles/bareWhich.js", "path": "/lib/bare/bundles/bareWhich.js",
"keys": [ "keys": [
"bareWhich" "bareWhich"
] ]
}, },
{
"path": "/lib/bare/bundles/bareUtils.js",
"keys": [
"bareUtils"
]
},
{ {
"path": "/lib/bare/bundles/bareV8ToIstanbul.js", "path": "/lib/bare/bundles/bareV8ToIstanbul.js",
"keys": [ "keys": [
@@ -769,12 +769,6 @@
"bareXdiff" "bareXdiff"
] ]
}, },
{
"path": "/lib/bare/bundles/bareZlib.js",
"keys": [
"bareZlib"
]
},
{ {
"path": "/lib/bare/bundles/bareWs.js", "path": "/lib/bare/bundles/bareWs.js",
"keys": [ "keys": [
@@ -782,9 +776,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareZmq.js", "path": "/lib/bare/bundles/bareZlib.js",
"keys": [ "keys": [
"bareZmq" "bareZlib"
] ]
}, },
{ {
@@ -792,6 +786,12 @@
"keys": [ "keys": [
"bareWorker" "bareWorker"
] ]
},
{
"path": "/lib/bare/bundles/bareZmq.js",
"keys": [
"bareZmq"
]
} }
], ],
"bundleStats": { "bundleStats": {
File diff suppressed because one or more lines are too long
+1
View File
@@ -4,6 +4,7 @@
| Version | Booter (workspace) | Notes | | Version | Booter (workspace) | Notes |
| ------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1.8.0 | 0.1.0 | **`ctx.bareOsSandboxRunScript`**, **`bareOsBootFileSha256Hex`**, boot phase hooks (**`bareOsRegisterBootPhaseHook`** / **`bareOsInvokeBootPhaseHooks`**), virtual file meta (**`bareOsInvalidateVirtualFile`**, **`bareOsUpdateVirtualFileMeta`**), **`bareOsIpc.createDuplexBridge`**, **`/proc/bare_os_swarm`**, VFS union read (**`BARE_OS_VFS_UNION_PREFIXES`**), **`BARE_OS_URANDOM_CRYPTO=0`**, async **`bareOsRequestPearReload`**, **`bareOsPublishBootReady.subsystems`**, **`verifyBareModuleLockfile`**. Kernel: **`BARE_OS_BOOT_MANIFEST`**, **`/etc/bare-os/selftest.d/`**, stock init hooks. |
| 1.7.0 | 0.1.0 | **`ctx.bare`**: frozen map of Holepunch-style npm modules for in-image scripts (manifest-driven host `import()` + optional trusted **`/lib/bare/bundles/*.js`** merge). Caps **`bareCtxModules`**, **`bareDriveBundles`**. Env **`BARE_OS_BARE_MODULES`**, **`BARE_OS_BARE_DRIVE_BUNDLES`**. Workspace **`bare-os-bare-libs`** builds seeded bundles. | | 1.7.0 | 0.1.0 | **`ctx.bare`**: frozen map of Holepunch-style npm modules for in-image scripts (manifest-driven host `import()` + optional trusted **`/lib/bare/bundles/*.js`** merge). Caps **`bareCtxModules`**, **`bareDriveBundles`**. Env **`BARE_OS_BARE_MODULES`**, **`BARE_OS_BARE_DRIVE_BUNDLES`**. Workspace **`bare-os-bare-libs`** builds seeded bundles. |
| 1.6.0 | 0.1.0 | Abort/timeout on `execLine`, `readLine`, `runBinCommand`, VFS `readFile`/`writeFile`; IPC fan-out + JSON-RPC token/line limits; HTTP allow/deny + audit; `/proc/bare_os_resources`, `/proc/bare_os_features`; `/run/bare-os/virtual/*`; booter boot phases in `boot.json` (`booterPhases`); optional `ctx.bareOsHostStats`, `ctx.httpFetch` policy wrapper; initd `ReadinessPath` / `ReadinessTimeoutSec`; Pear/sandbox stubs. | | 1.6.0 | 0.1.0 | Abort/timeout on `execLine`, `readLine`, `runBinCommand`, VFS `readFile`/`writeFile`; IPC fan-out + JSON-RPC token/line limits; HTTP allow/deny + audit; `/proc/bare_os_resources`, `/proc/bare_os_features`; `/run/bare-os/virtual/*`; booter boot phases in `boot.json` (`booterPhases`); optional `ctx.bareOsHostStats`, `ctx.httpFetch` policy wrapper; initd `ReadinessPath` / `ReadinessTimeoutSec`; Pear/sandbox stubs. |
| 1.5.0 | (prior) | Previous documented contract. | | 1.5.0 | (prior) | Previous documented contract. |
+199 -24
View File
@@ -9,10 +9,16 @@ import {
topicKey, topicKey,
parseMbr, parseMbr,
BARE_OS_KERNEL_FEATURES_STOCK_V1, BARE_OS_KERNEL_FEATURES_STOCK_V1,
BARE_OS_KERNEL_FEATURE_BITS_DOC BARE_OS_KERNEL_FEATURE_BITS_DOC,
BARE_OS_FEATURE_CRYPTO_URANDOM
} from 'bare-os-protocol' } from 'bare-os-protocol'
import { SwarmDisk } from './lib/swarm-disk.js' import { SwarmDisk } from './lib/swarm-disk.js'
import { runKernelFromSource, runBinCommand } from './lib/kernel-runner.js' import {
runKernelFromSource,
runBinCommand,
runUserScriptFromSource
} from './lib/kernel-runner.js'
import { createBareOsSandboxContext } from './lib/bare-os-sandbox.js'
import { createVfs } from './lib/vfs.js' import { createVfs } from './lib/vfs.js'
import { createBareOsIpc } from './lib/bare-os-ipc.js' import { createBareOsIpc } from './lib/bare-os-ipc.js'
import { import {
@@ -57,7 +63,8 @@ import {
bareOsBareModulesEnabled, bareOsBareModulesEnabled,
buildBareCtxObjectFromHost, buildBareCtxObjectFromHost,
maybeMergeBareFromDrive, maybeMergeBareFromDrive,
bareOsBareHostImportsEnabled bareOsBareHostImportsEnabled,
verifyBareModuleLockfile
} from './lib/bare-os-ctx-bare.js' } from './lib/bare-os-ctx-bare.js'
import { raceWithAbortAndTimeout } from './lib/bare-os-abort.js' import { raceWithAbortAndTimeout } from './lib/bare-os-abort.js'
import { import {
@@ -69,11 +76,25 @@ import {
bareOsListThemeNames bareOsListThemeNames
} from './lib/bare-os-theme-presets.js' } from './lib/bare-os-theme-presets.js'
import './lib/bare-cron.js' import './lib/bare-cron.js'
import { createHash } from 'node:crypto'
const { randomUUID, randomBytes } = bareCrypto const { randomUUID, randomBytes } = bareCrypto
const _pkg = packageRootDir(import.meta.url) const _pkg = packageRootDir(import.meta.url)
/**
* @param {Record<string, string>} env
* @returns {string[]}
*/
function parseUnionReadPrefixes(env) {
const raw = env && env.BARE_OS_VFS_UNION_PREFIXES
if (raw == null || !String(raw).trim()) return []
return String(raw)
.split(/[,:]+/)
.map((s) => s.trim())
.filter((s) => s.startsWith('/'))
}
/** /**
* On Pear/Bare, `process.exit` is often missing or ineffective; use `Bare.exit` * On Pear/Bare, `process.exit` is often missing or ineffective; use `Bare.exit`
* (see holepunch `pear-prerelease`, `pear-terminal`, `bare-process`). * (see holepunch `pear-prerelease`, `pear-terminal`, `bare-process`).
@@ -332,7 +353,7 @@ async function executeKernel(disk, store, swarm, initSource) {
const hdmsLifecycleSubs = [] const hdmsLifecycleSubs = []
/** @type {Set<(ev: Record<string, unknown>) => void | Promise<void>>} */ /** @type {Set<(ev: Record<string, unknown>) => void | Promise<void>>} */
const bootEventSubs = new Set() const bootEventSubs = new Set()
/** @type {{ ready: boolean, phases: string[], booterPhases: string[], sessionId: string, completedAtMs: number | null, minimal: boolean, imageDigest: string, pearChannel: string, pearRelease: string }} */ /** @type {{ ready: boolean, phases: string[], booterPhases: string[], sessionId: string, completedAtMs: number | null, minimal: boolean, imageDigest: string, pearChannel: string, pearRelease: string, subsystems?: Record<string, unknown>, bareModuleLockWarnings?: string[] }} */
const bootReadyStateRef = { const bootReadyStateRef = {
ready: false, ready: false,
phases: [], phases: [],
@@ -343,7 +364,9 @@ async function executeKernel(disk, store, swarm, initSource) {
minimal: false, minimal: false,
imageDigest: '', imageDigest: '',
pearChannel: '', pearChannel: '',
pearRelease: '' pearRelease: '',
subsystems: {},
bareModuleLockWarnings: []
} }
bootReadyStateRef.imageDigest = String( bootReadyStateRef.imageDigest = String(
shellEnv.BARE_OS_IMAGE_DIGEST || '' shellEnv.BARE_OS_IMAGE_DIGEST || ''
@@ -379,8 +402,13 @@ async function executeKernel(disk, store, swarm, initSource) {
shellEnv.BARE_OS_IPC_FANOUT !== '0' && shellEnv.BARE_OS_IPC_FANOUT !== '0' &&
shellEnv.BARE_OS_IPC_FANOUT !== 'false' shellEnv.BARE_OS_IPC_FANOUT !== 'false'
const ipcRpcTok = String(shellEnv.BARE_OS_IPC_RPC_TOKEN || '').trim() || null const ipcRpcTok = String(shellEnv.BARE_OS_IPC_RPC_TOKEN || '').trim() || null
/** @type {Map<string, () => string | Uint8Array | Promise<string | Uint8Array>>} */ /** @type {Map<string, { read: () => string | Uint8Array | Promise<string | Uint8Array>, etag: string, version: number }>} */
const virtualReaders = new Map() const virtualReaderEntries = new Map()
/** @type {{ phase: string, fn: (ev: Record<string, unknown>) => void | Promise<void> }[]} */
const bootPhaseHooks = []
const urandomCryptoOn =
shellEnv.BARE_OS_URANDOM_CRYPTO !== '0' &&
shellEnv.BARE_OS_URANDOM_CRYPTO !== 'false'
const bareOsIpc = createBareOsIpc({ const bareOsIpc = createBareOsIpc({
maxFifoBytes: ipcMaxResolved, maxFifoBytes: ipcMaxResolved,
@@ -447,9 +475,13 @@ async function executeKernel(disk, store, swarm, initSource) {
(d != null && String(d).trim() ? String(d).trim() : 'unknown') + '\n' (d != null && String(d).trim() ? String(d).trim() : 'unknown') + '\n'
) )
}, },
secureRandomBytes(n) { secureRandomBytes:
return new Uint8Array(randomBytes(Math.min(65536, Math.max(1, n | 0)))) urandomCryptoOn
}, ? (n) =>
new Uint8Array(
randomBytes(Math.min(65536, Math.max(1, n | 0)))
)
: undefined,
bareOsIpc, bareOsIpc,
procNetDevText() { procNetDevText() {
const n = disk.peers?.size ?? 0 const n = disk.peers?.size ?? 0
@@ -489,12 +521,25 @@ async function executeKernel(disk, store, swarm, initSource) {
})}\n` })}\n`
}, },
procBareOsFeaturesText() { procBareOsFeaturesText() {
let bits = BARE_OS_KERNEL_FEATURES_STOCK_V1
if (!urandomCryptoOn) bits &= ~BARE_OS_FEATURE_CRYPTO_URANDOM
return `${JSON.stringify({ return `${JSON.stringify({
doc: BARE_OS_KERNEL_FEATURE_BITS_DOC, doc: BARE_OS_KERNEL_FEATURE_BITS_DOC,
bits: BARE_OS_KERNEL_FEATURES_STOCK_V1 bits,
urandomCrypto: urandomCryptoOn
})}\n` })}\n`
}, },
getVirtualReaders: () => virtualReaders, procBareOsSwarmText() {
const tk = topicKey()
const peers = disk.peers?.size ?? 0
return `${JSON.stringify({
topicHex: b4a.toString(tk, 'hex'),
peerCount: peers,
protocol: 'bare-os-v1'
})}\n`
},
getVirtualReaders: () => virtualReaderEntries,
unionReadPrefixes: parseUnionReadPrefixes(shellEnv),
sysClassNetLoText() { sysClassNetLoText() {
const n = disk.peers?.size ?? 0 const n = disk.peers?.size ?? 0
return `operstate ${n > 0 ? 'unknown' : 'down'}\ncarrier ${n > 0 ? 1 : 0}\n` return `operstate ${n > 0 ? 'unknown' : 'down'}\ncarrier ${n > 0 ? 1 : 0}\n`
@@ -502,6 +547,10 @@ async function executeKernel(disk, store, swarm, initSource) {
}) })
emitBooterBootPhase('vfs') emitBooterBootPhase('vfs')
Object.assign(bootReadyStateRef.subsystems, {
vfs: { ready: true, atMs: Date.now() },
swarm: { peersAtSession: disk.peers?.size ?? 0 }
})
/** @type {Record<string, unknown>} */ /** @type {Record<string, unknown>} */
const bareLibrary = {} const bareLibrary = {}
@@ -510,6 +559,8 @@ async function executeKernel(disk, store, swarm, initSource) {
if (bareOsBareHostImportsEnabled(shellEnv)) { if (bareOsBareHostImportsEnabled(shellEnv)) {
await buildBareCtxObjectFromHost(shellEnv, bareLibrary) await buildBareCtxObjectFromHost(shellEnv, bareLibrary)
} }
const lockWarn = await verifyBareModuleLockfile(vfs, bareLibrary)
if (lockWarn.length) bootReadyStateRef.bareModuleLockWarnings = lockWarn
} }
const hdmsController = new HdmsController() const hdmsController = new HdmsController()
@@ -671,8 +722,14 @@ async function executeKernel(disk, store, swarm, initSource) {
* @param {Partial<{ ready: boolean, phases: string[], sessionId: string, completedAtMs: number, minimal: boolean }>} patch * @param {Partial<{ ready: boolean, phases: string[], sessionId: string, completedAtMs: number, minimal: boolean }>} patch
*/ */
bareOsPublishBootReady(patch) { bareOsPublishBootReady(patch) {
if (patch && typeof patch === 'object') if (!patch || typeof patch !== 'object') return
Object.assign(bootReadyStateRef, patch) if (patch.subsystems && typeof patch.subsystems === 'object') {
Object.assign(bootReadyStateRef.subsystems, patch.subsystems)
}
for (const key of Object.keys(patch)) {
if (key === 'subsystems') continue
bootReadyStateRef[key] = patch[key]
}
}, },
/** Mutable ref mirrored in `/proc/bare_os_session_stats` (execLineCount, pipelineBytesTotal). */ /** Mutable ref mirrored in `/proc/bare_os_session_stats` (execLineCount, pipelineBytesTotal). */
bareOsSessionStats: sessionStatsRef, bareOsSessionStats: sessionStatsRef,
@@ -735,7 +792,12 @@ async function executeKernel(disk, store, swarm, initSource) {
* @param {string} name * @param {string} name
* @param {() => string | Uint8Array | Promise<string | Uint8Array>} reader * @param {() => string | Uint8Array | Promise<string | Uint8Array>} reader
*/ */
bareOsRegisterVirtualFile(name, reader) { /**
* @param {string} name
* @param {(() => string | Uint8Array | Promise<string | Uint8Array>) | { read: typeof reader }} reader
* @param {{ etag?: string }} [opts]
*/
bareOsRegisterVirtualFile(name, reader, opts = {}) {
const caps = this.bareOsRuntimeCaps const caps = this.bareOsRuntimeCaps
const ok = const ok =
caps && caps &&
@@ -748,30 +810,135 @@ async function executeKernel(disk, store, swarm, initSource) {
if (!name || !/^[a-zA-Z0-9._-]+$/.test(String(name))) { if (!name || !/^[a-zA-Z0-9._-]+$/.test(String(name))) {
throw new Error('bareOsRegisterVirtualFile: invalid name') throw new Error('bareOsRegisterVirtualFile: invalid name')
} }
if (typeof reader !== 'function') { const readFn =
typeof reader === 'function'
? reader
: reader &&
typeof reader === 'object' &&
typeof reader.read === 'function'
? reader.read
: null
if (typeof readFn !== 'function') {
throw new Error('bareOsRegisterVirtualFile: reader must be a function') throw new Error('bareOsRegisterVirtualFile: reader must be a function')
} }
virtualReaders.set(String(name), reader) virtualReaderEntries.set(String(name), {
read: readFn,
etag: String(opts.etag != null ? opts.etag : '1'),
version: 1
})
}, },
/** Placeholder for future Bare worker / Pear isolate execution (see developer guide). */ bareOsInvalidateVirtualFile(name) {
async bareOsSandboxRunScript() { virtualReaderEntries.delete(String(name))
throw new Error( },
'bareOsSandboxRunScript is not implemented; see developer-guide/09-security-and-trust.md' /**
* @param {string} name
* @param {{ etag?: string, version?: number }} patch
*/
bareOsUpdateVirtualFileMeta(name, patch) {
const e = virtualReaderEntries.get(String(name))
if (!e) throw new Error('bareOsUpdateVirtualFileMeta: unknown virtual file')
if (patch.etag != null) e.etag = String(patch.etag)
if (patch.version != null && Number.isFinite(Number(patch.version))) {
e.version = Number(patch.version)
}
},
/**
* @param {string} phase e.g. `os-release`, `before:rc`, `after:rc`, or `*`
* @param {(ev: { phase: string, when?: string, label?: string }) => void | Promise<void>} fn
* @returns {() => void} unsubscribe
*/
bareOsRegisterBootPhaseHook(phase, fn) {
if (typeof fn !== 'function' || !phase) return () => {}
const entry = { phase: String(phase), fn }
bootPhaseHooks.push(entry)
return () => {
const i = bootPhaseHooks.indexOf(entry)
if (i >= 0) bootPhaseHooks.splice(i, 1)
}
},
/** @param {{ phase: string, when?: string, label?: string }} ev */
async bareOsInvokeBootPhaseHooks(ev) {
if (!ev || typeof ev !== 'object') return
for (const { phase, fn } of bootPhaseHooks) {
const p = String(ev.phase || '')
const w = ev.when ? `${ev.when}:${p}` : p
if (phase === '*' || phase === w || phase === p) {
try {
await fn(ev)
} catch {
/* ignore */
}
}
}
},
/**
* @param {string} source
* @param {string[]} [argv]
* @param {{ signal?: AbortSignal, timeoutMs?: number }} [runOpts]
*/
async bareOsSandboxRunScript(source, argv = [], runOpts) {
const caps = this.bareOsRuntimeCaps
if (
!caps?.features ||
/** @type {{ sandboxScript?: boolean }} */ (caps.features)
.sandboxScript === false
) {
throw new Error('bareOsSandboxRunScript: feature disabled')
}
if (shellEnv.BARE_OS_SANDBOX_SCRIPT === '0') {
throw new Error('bareOsSandboxRunScript: disabled by BARE_OS_SANDBOX_SCRIPT=0')
}
const sb = createBareOsSandboxContext(this)
const { raceWithAbortAndTimeout } = await import('./lib/bare-os-abort.js')
return raceWithAbortAndTimeout(
runUserScriptFromSource(sb, String(source), argv, 'sandbox'),
runOpts,
'bareOsSandboxRunScript'
) )
}, },
/** /**
* Hint object for Pear OTA / channel reload (host must implement). * Hint object for Pear OTA / channel reload (host must implement).
* @returns {{ requested: boolean, hint: string, env: string[] }} * @returns {{ requested: boolean, hint: string, env: string[] }}
*/ */
bareOsRequestPearReload() { async bareOsRequestPearReload(opts = {}) {
const env = [] const env = []
const ch = shellEnv.BARE_OS_PEAR_CHANNEL || shellEnv.PEAR_CHANNEL const ch = shellEnv.BARE_OS_PEAR_CHANNEL || shellEnv.PEAR_CHANNEL
if (ch) env.push(`PEAR_CHANNEL=${ch}`) if (ch) env.push(`PEAR_CHANNEL=${ch}`)
if (shellEnv.BARE_OS_PEAR_RELEASE) if (shellEnv.BARE_OS_PEAR_RELEASE)
env.push(`BARE_OS_PEAR_RELEASE=${shellEnv.BARE_OS_PEAR_RELEASE}`) env.push(`BARE_OS_PEAR_RELEASE=${shellEnv.BARE_OS_PEAR_RELEASE}`)
let requested = false
if (opts && opts.persistRequest) {
try {
const line = JSON.stringify({
ts: Date.now(),
channel: String(ch || ''),
release: String(shellEnv.BARE_OS_PEAR_RELEASE || '')
})
await vfs.mkdir('~/.bare-os', { recursive: true })
await vfs.writeFile(
'~/.bare-os/pear-reload.request',
b4a.from(line + '\n', 'utf8')
)
requested = true
} catch {
/* ignore */
}
}
if (typeof globalThis.process?.emit === 'function') {
try {
globalThis.process.emit('bare-os:pear-reload', {
channel: ch,
release: shellEnv.BARE_OS_PEAR_RELEASE
})
} catch {
/* ignore */
}
}
return { return {
requested: false, requested,
hint: 'Host Pear runtime (pear-runtime-updater) must apply reload; kernel only exposes env hints.', hint: requested
? 'Reload marker written to ~/.bare-os/pear-reload.request; host Pear / pear-runtime-updater should consume it.'
: 'Pass { persistRequest: true } to write ~/.bare-os/pear-reload.request; host may listen for process "bare-os:pear-reload".',
env env
} }
}, },
@@ -782,6 +949,14 @@ async function executeKernel(disk, store, swarm, initSource) {
/** Preset names for `/bin/theme` and docs. */ /** Preset names for `/bin/theme` and docs. */
bareOsListThemes() { bareOsListThemes() {
return bareOsListThemeNames() return bareOsListThemeNames()
},
/**
* SHA-256 hex digest for boot manifest checks (stock kernel).
* @param {Uint8Array | ArrayBuffer} buf
*/
bareOsBootFileSha256Hex(buf) {
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
return createHash('sha256').update(u8).digest('hex')
} }
} }
+79 -11
View File
@@ -11,10 +11,14 @@ export const BARE_INITD_DISABLED_FILE = '~/.config/bare-os/initd/disabled.txt'
/** /**
* Optional `name.unit` files under this directory. Supports `[Unit]` keys: * Optional `name.unit` files under this directory. Supports `[Unit]` keys:
* After=, Requires=, Wants=, TimeoutStartSec=, TimeoutStopSec=, Restart=, RestartSec=, ExecStartPost=, SocketActivationIpc=, * After=, Requires=, Wants=, TimeoutStartSec=, TimeoutStopSec=, Restart=, RestartSec=, ExecStartPost=, SocketActivationIpc=,
* ReadinessPath= (VFS path until exists), ReadinessTimeoutSec=. * ReadinessPath= (VFS path until exists), ReadinessTimeoutSec=,
* ExecHealthCmd=, HealthIntervalSec=, HealthFailureThreshold=, BareMaxExecDepth=.
*/ */
export const BARE_INITD_UNITS_DIR = '~/.config/bare-os/units' export const BARE_INITD_UNITS_DIR = '~/.config/bare-os/units'
/** `systemctl --user` style overrides (merged on top of {@link BARE_INITD_UNITS_DIR}). */
export const BARE_INITD_SYSTEMCTL_USER_UNITS_DIR = '~/.config/bare-init/units'
/** Timer drop-ins: `[Timer]` with OnCalendar= (five cron fields) and ExecLine=. */ /** Timer drop-ins: `[Timer]` with OnCalendar= (five cron fields) and ExecLine=. */
export const BARE_INITD_TIMERS_DIR = '~/.config/bare-os/timers' export const BARE_INITD_TIMERS_DIR = '~/.config/bare-os/timers'
@@ -35,7 +39,11 @@ export const BARE_INITD_DEFAULT_AFTER = Object.freeze({
* execStartPost: string | null, * execStartPost: string | null,
* socketActivationIpc: string | null, * socketActivationIpc: string | null,
* readinessPath: string | null, * readinessPath: string | null,
* readinessTimeoutSec: number | null * readinessTimeoutSec: number | null,
* execHealthCmd: string | null,
* healthIntervalSec: number | null,
* healthFailureThreshold: number | null,
* bareMaxExecDepth: number | null
* }} BareInitdUnitDropIn * }} BareInitdUnitDropIn
*/ */
@@ -52,7 +60,11 @@ export function emptyUnitDropIn() {
execStartPost: null, execStartPost: null,
socketActivationIpc: null, socketActivationIpc: null,
readinessPath: null, readinessPath: null,
readinessTimeoutSec: null readinessTimeoutSec: null,
execHealthCmd: null,
healthIntervalSec: null,
healthFailureThreshold: null,
bareMaxExecDepth: null
} }
} }
@@ -110,6 +122,17 @@ export function parseUnitDropInText(text) {
} else if (key === 'readinesstimeoutsec') { } else if (key === 'readinesstimeoutsec') {
const n = Number.parseInt(val, 10) const n = Number.parseInt(val, 10)
if (Number.isFinite(n) && n > 0) out.readinessTimeoutSec = n if (Number.isFinite(n) && n > 0) out.readinessTimeoutSec = n
} else if (key === 'exechealthcmd') {
if (val.trim()) out.execHealthCmd = val.trim()
} else if (key === 'healthintervalsec') {
const n = Number.parseInt(val, 10)
if (Number.isFinite(n) && n > 0) out.healthIntervalSec = n
} else if (key === 'healthfailurethreshold') {
const n = Number.parseInt(val, 10)
if (Number.isFinite(n) && n > 0) out.healthFailureThreshold = n
} else if (key === 'baremaxexecdepth') {
const n = Number.parseInt(val, 10)
if (Number.isFinite(n) && n > 0) out.bareMaxExecDepth = n
} }
} }
out.after = [...new Set(out.after)] out.after = [...new Set(out.after)]
@@ -118,6 +141,50 @@ export function parseUnitDropInText(text) {
return out return out
} }
/**
* @param {BareInitdUnitDropIn} base
* @param {BareInitdUnitDropIn} user
* @returns {BareInitdUnitDropIn}
*/
export function mergeUnitDropIns(base, user) {
const pickArr = (u, b) =>
u.length ? [...new Set(u)] : [...new Set(b)]
return {
after: pickArr(user.after, base.after),
requires: pickArr(user.requires, base.requires),
wants: pickArr(user.wants, base.wants),
timeoutStartSec: user.timeoutStartSec ?? base.timeoutStartSec,
timeoutStopSec: user.timeoutStopSec ?? base.timeoutStopSec,
restart: user.restart ?? base.restart,
restartSec: user.restartSec ?? base.restartSec,
execStartPost: user.execStartPost ?? base.execStartPost,
socketActivationIpc: user.socketActivationIpc ?? base.socketActivationIpc,
readinessPath: user.readinessPath ?? base.readinessPath,
readinessTimeoutSec:
user.readinessTimeoutSec ?? base.readinessTimeoutSec,
execHealthCmd: user.execHealthCmd ?? base.execHealthCmd,
healthIntervalSec: user.healthIntervalSec ?? base.healthIntervalSec,
healthFailureThreshold:
user.healthFailureThreshold ?? base.healthFailureThreshold,
bareMaxExecDepth: user.bareMaxExecDepth ?? base.bareMaxExecDepth
}
}
/**
* @param {{ readFile: (p: string) => Promise<Uint8Array | null> }} vfs
* @param {string} logicalPath
* @returns {Promise<BareInitdUnitDropIn>}
*/
export async function readUnitDropInFromPath(vfs, logicalPath) {
try {
const buf = await vfs.readFile(logicalPath)
if (!buf) return emptyUnitDropIn()
return parseUnitDropInText(b4a.toString(buf, 'utf8'))
} catch {
return emptyUnitDropIn()
}
}
/** /**
* @param {{ readFile: (p: string) => Promise<Uint8Array | null> }} vfs * @param {{ readFile: (p: string) => Promise<Uint8Array | null> }} vfs
* @returns {Promise<Set<string>>} * @returns {Promise<Set<string>>}
@@ -168,14 +235,15 @@ export async function writeInitdDisabledSet(vfs, disabled) {
*/ */
export async function readUnitDropIn(vfs, unitName) { export async function readUnitDropIn(vfs, unitName) {
if (!/^[a-zA-Z0-9._-]+$/.test(unitName)) return emptyUnitDropIn() if (!/^[a-zA-Z0-9._-]+$/.test(unitName)) return emptyUnitDropIn()
const path = `${BARE_INITD_UNITS_DIR}/${unitName}.unit` const base = await readUnitDropInFromPath(
try { vfs,
const buf = await vfs.readFile(path) `${BARE_INITD_UNITS_DIR}/${unitName}.unit`
if (!buf) return emptyUnitDropIn() )
return parseUnitDropInText(b4a.toString(buf, 'utf8')) const usr = await readUnitDropInFromPath(
} catch { vfs,
return emptyUnitDropIn() `${BARE_INITD_SYSTEMCTL_USER_UNITS_DIR}/${unitName}.unit`
} )
return mergeUnitDropIns(base, usr)
} }
/** /**
+62
View File
@@ -36,6 +36,9 @@ const registry = []
/** @type {Map<string, BareServiceRuntime>} */ /** @type {Map<string, BareServiceRuntime>} */
const runtime = new Map() const runtime = new Map()
/** @type {Map<string, ReturnType<typeof setInterval>>} */
const unitHealthTimers = new Map()
/** @type {(() => void)[]} */ /** @type {(() => void)[]} */
const disposers = [] const disposers = []
@@ -82,6 +85,8 @@ export async function runKernelShutdownHooks() {
} }
export function stopBareInitd() { export function stopBareInitd() {
for (const t of unitHealthTimers.values()) clearInterval(t)
unitHealthTimers.clear()
for (const fn of disposers) { for (const fn of disposers) {
try { try {
fn() fn()
@@ -238,6 +243,56 @@ async function withTimeoutSec(promise, sec, label) {
* @param {string} logicalPath * @param {string} logicalPath
* @param {number} timeoutSec * @param {number} timeoutSec
*/ */
/**
* @param {Record<string, unknown>} ctx
* @param {string} name
* @param {import('./bare-initd-user.js').BareInitdUnitDropIn} dropIn
*/
function scheduleUnitHealth(ctx, name, dropIn) {
if (!dropIn.execHealthCmd || !dropIn.healthIntervalSec) return
if (typeof ctx.execLine !== 'function') return
const intervalMs = Math.max(5, dropIn.healthIntervalSec) * 1000
const threshold = Math.max(1, dropIn.healthFailureThreshold ?? 3)
let fails = 0
const t = setInterval(() => {
void (async () => {
const rt = runtime.get(name)
if (!rt || rt.phase !== 'active') return
const env = ctx.vfs?.env
const saved = env ? env.BARE_OS_EXEC_MAX_DEPTH : undefined
if (dropIn.bareMaxExecDepth != null && env) {
env.BARE_OS_EXEC_MAX_DEPTH = String(dropIn.bareMaxExecDepth)
}
try {
await ctx.execLine(dropIn.execHealthCmd.trim())
if ((Number(ctx.exitCode) || 0) === 0) fails = 0
else fails++
} catch {
fails++
} finally {
if (env && saved !== undefined) env.BARE_OS_EXEC_MAX_DEPTH = saved
}
if (fails >= threshold) {
clearInterval(t)
unitHealthTimers.delete(name)
const msg = 'health check failed'
runtime.set(name, {
phase: 'failed',
startedAtMs: Date.now(),
error: msg
})
try {
ctx.console?.error?.(`[bare-initd] ${name}: ${msg}`)
} catch {
/* ignore */
}
void appendVarLog(ctx, INITD_LOG, name, msg)
}
})()
}, intervalMs)
unitHealthTimers.set(name, t)
}
async function waitForReadinessPath(ctx, logicalPath, timeoutSec) { async function waitForReadinessPath(ctx, logicalPath, timeoutSec) {
const vfs = ctx.vfs const vfs = ctx.vfs
if (!vfs || typeof vfs.exists !== 'function') return if (!vfs || typeof vfs.exists !== 'function') return
@@ -257,6 +312,11 @@ async function waitForReadinessPath(ctx, logicalPath, timeoutSec) {
export async function stopBareService(ctx, name) { export async function stopBareService(ctx, name) {
const s = findBareServiceDefinition(name) const s = findBareServiceDefinition(name)
if (!s) throw new Error(`Unknown unit: ${name}`) if (!s) throw new Error(`Unknown unit: ${name}`)
const ht = unitHealthTimers.get(name)
if (ht) {
clearInterval(ht)
unitHealthTimers.delete(name)
}
if (typeof s.stop !== 'function') { if (typeof s.stop !== 'function') {
throw new Error(`Unit ${name} does not support stop (no stop handler)`) throw new Error(`Unit ${name} does not support stop (no stop handler)`)
} }
@@ -387,6 +447,7 @@ export async function startBareInitd(ctx) {
void appendVarLog(ctx, INITD_LOG, s.name, 'ExecStartPost: ' + msg) void appendVarLog(ctx, INITD_LOG, s.name, 'ExecStartPost: ' + msg)
} }
} }
scheduleUnitHealth(ctx, s.name, dropIn)
} catch (e) { } catch (e) {
const msg = e?.message || String(e) const msg = e?.message || String(e)
runtime.set(s.name, { phase: 'failed', startedAtMs: t0, error: msg }) runtime.set(s.name, { phase: 'failed', startedAtMs: t0, error: msg })
@@ -435,6 +496,7 @@ export async function startBareInitd(ctx) {
void appendVarLog(ctx, INITD_LOG, s.name, 'ExecStartPost: ' + msg) void appendVarLog(ctx, INITD_LOG, s.name, 'ExecStartPost: ' + msg)
} }
} }
scheduleUnitHealth(ctx, s.name, dropIn)
break break
} catch (e) { } catch (e) {
const msg = e?.message || String(e) const msg = e?.message || String(e)
@@ -2,4 +2,4 @@
* Semantic version of the booter `ctx` contract for custom kernels. * Semantic version of the booter `ctx` contract for custom kernels.
* Bump when adding/removing/renaming documented `ctx` fields or changing behavior. * Bump when adding/removing/renaming documented `ctx` fields or changing behavior.
*/ */
export const BARE_OS_CTX_API_VERSION = '1.7.1' export const BARE_OS_CTX_API_VERSION = '1.8.0'
@@ -212,6 +212,43 @@ export async function buildBareCtxObjectFromHost(shellEnv, target) {
* @param {{ readFile: (p: string, opts?: unknown) => Promise<Uint8Array | null> }} vfs * @param {{ readFile: (p: string, opts?: unknown) => Promise<Uint8Array | null> }} vfs
* @param {Record<string, unknown>} target * @param {Record<string, unknown>} target
*/ */
/**
* Optional `/lib/bare/bare-module-lock.json` on the system image: `{ "pins": { "ctxKey": "semver" } }`.
* Emits warnings for pins whose `ctxKey` is still missing after drive merge + host import.
* @param {{ readFile: (p: string) => Promise<Uint8Array | null> }} vfs
* @param {Record<string, unknown>} target
* @returns {Promise<string[]>}
*/
export async function verifyBareModuleLockfile(vfs, target) {
/** @type {string[]} */
const warnings = []
if (!vfs || typeof vfs.readFile !== 'function') return warnings
let buf
try {
buf = await vfs.readFile('/lib/bare/bare-module-lock.json')
} catch {
return warnings
}
if (!buf || !buf.byteLength) return warnings
let j
try {
j = JSON.parse(new TextDecoder().decode(buf))
} catch {
warnings.push('bare-module-lock.json: invalid JSON')
return warnings
}
const pins = j && typeof j === 'object' ? j.pins : null
if (!pins || typeof pins !== 'object') return warnings
for (const [k, ver] of Object.entries(pins)) {
if (target[k] === undefined) {
warnings.push(
`bare-module-lock: ctx.bare.${k} missing (pinned ${String(ver)})`
)
}
}
return warnings
}
export async function maybeMergeBareFromDrive(shellEnv, vfs, target) { export async function maybeMergeBareFromDrive(shellEnv, vfs, target) {
if (!bareOsBareModulesEnabled(shellEnv)) return if (!bareOsBareModulesEnabled(shellEnv)) return
if (!bareOsBareDriveBundlesEnabled(shellEnv)) return if (!bareOsBareDriveBundlesEnabled(shellEnv)) return
+34 -4
View File
@@ -46,6 +46,16 @@ export interface BareOsIpc {
fanoutTopicCount: number fanoutTopicCount: number
fanoutSubscribersTotal: number fanoutSubscribersTotal: number
} }
createDuplexBridge(baseName: string): {
left: {
push: (buf: Uint8Array | ArrayBuffer) => void
take: () => Promise<Uint8Array>
}
right: {
push: (buf: Uint8Array | ArrayBuffer) => void
take: () => Promise<Uint8Array>
}
}
} }
export interface BareOsHostStats { export interface BareOsHostStats {
@@ -89,14 +99,34 @@ export interface BareOsKernelContext {
bareOsGetResourceStatus(): BareOsResourceStatus bareOsGetResourceStatus(): BareOsResourceStatus
bareOsRegisterVirtualFile( bareOsRegisterVirtualFile(
name: string, name: string,
reader: () => string | Uint8Array | Promise<string | Uint8Array> reader:
| (() => string | Uint8Array | Promise<string | Uint8Array>)
| { read: () => string | Uint8Array | Promise<string | Uint8Array> },
opts?: { etag?: string }
): void ): void
bareOsSandboxRunScript(): Promise<never> bareOsInvalidateVirtualFile(name: string): void
bareOsRequestPearReload(): { bareOsUpdateVirtualFileMeta(
name: string,
patch: { etag?: string; version?: number }
): void
bareOsRegisterBootPhaseHook(
phase: string,
fn: (ev: Record<string, unknown>) => void | Promise<void>
): () => void
bareOsInvokeBootPhaseHooks(ev: Record<string, unknown>): Promise<void>
bareOsSandboxRunScript(
source: string,
argv?: string[],
opts?: BareOsAbortOpts
): Promise<void>
bareOsBootFileSha256Hex(buf: Uint8Array | ArrayBuffer): string
bareOsRequestPearReload(opts?: {
persistRequest?: boolean
}): Promise<{
requested: boolean requested: boolean
hint: string hint: string
env: string[] env: string[]
} }>
bareOsHostStats?: Readonly<BareOsHostStats> bareOsHostStats?: Readonly<BareOsHostStats>
/** Host-resolved (and optional drive-bundled) Holepunch-style modules; absent when `BARE_OS_BARE_MODULES=0`. */ /** Host-resolved (and optional drive-bundled) Holepunch-style modules; absent when `BARE_OS_BARE_MODULES=0`. */
bare?: Readonly<Record<string, unknown>> bare?: Readonly<Record<string, unknown>>
@@ -275,6 +275,26 @@ export function createBareOsIpc(opts = {}) {
fanoutTopicCount: fanouts.size, fanoutTopicCount: fanouts.size,
fanoutSubscribersTotal fanoutSubscribersTotal
} }
},
/**
* Bounded duplex: side A's `push` delivers to side B's `take`, and vice versa.
* @param {string} baseName
* @returns {{ left: { push: (buf: Uint8Array | ArrayBuffer) => void, take: () => Promise<Uint8Array> }, right: { push: (buf: Uint8Array | ArrayBuffer) => void, take: () => Promise<Uint8Array> } }}
*/
createDuplexBridge(baseName) {
assertSafeIpcName(baseName)
const ab = new FifoChannel()
const ba = new FifoChannel()
const left = {
push: (buf) => ba.push(buf, maxFifoBytes),
take: () => ab.take()
}
const right = {
push: (buf) => ab.push(buf, maxFifoBytes),
take: () => ba.take()
}
return { left, right }
} }
} }
} }
@@ -29,6 +29,7 @@ export const BARE_OS_PSEUDO_FS_PATHS = Object.freeze([
'/proc/bare_os_features', '/proc/bare_os_features',
'/proc/bare_os_quotas', '/proc/bare_os_quotas',
'/proc/bare_os_resources', '/proc/bare_os_resources',
'/proc/bare_os_swarm',
'/proc/bare_os_session_stats', '/proc/bare_os_session_stats',
'/proc/bare_os_version', '/proc/bare_os_version',
'/proc/cpuinfo', '/proc/cpuinfo',
@@ -150,7 +151,21 @@ export function buildBareOsRuntimeCaps(shellEnv) {
bareOsBareHostImportsEnabled(shellEnv), bareOsBareHostImportsEnabled(shellEnv),
bareDriveBundles: bareDriveBundles:
bareOsBareModulesEnabled(shellEnv) && bareOsBareModulesEnabled(shellEnv) &&
bareOsBareDriveBundlesEnabled(shellEnv) bareOsBareDriveBundlesEnabled(shellEnv),
sandboxScript:
shellEnv.BARE_OS_SANDBOX_SCRIPT !== '0' &&
shellEnv.BARE_OS_SANDBOX_SCRIPT !== 'false',
bootPhaseHooks: true,
ipcDuplexBridge: true,
vfsUnionRead:
shellEnv.BARE_OS_VFS_UNION_PREFIXES != null &&
String(shellEnv.BARE_OS_VFS_UNION_PREFIXES).trim() !== '',
procSwarmSnapshot: true,
bootManifestVerify:
shellEnv.BARE_OS_BOOT_MANIFEST === '1' ||
shellEnv.BARE_OS_BOOT_MANIFEST === 'true',
bareModuleLockfile: true,
oidcPublishHook: true
}) })
}) })
} }
@@ -0,0 +1,87 @@
/**
* Reduced `ctx` for `bareOsSandboxRunScript`: personal-drive writes only, no identity hooks.
*/
import { isPersonalRoute } from './vfs-posix-meta.js'
/**
* @param {Record<string, unknown>} ctx
* @returns {boolean}
*/
function sandboxWritesOk(ctx, logicalPath) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.resolveLogical !== 'function') return false
if (typeof vfs.route !== 'function') return false
const abs = vfs.resolveLogical(logicalPath)
const r = vfs.route(abs)
const pd = ctx.personalDrive
if (!pd) return false
return isPersonalRoute(pd, r)
}
/**
* @param {Record<string, unknown>} ctx
*/
export function createBareOsSandboxContext(ctx) {
const vfs = ctx.vfs
if (!vfs) throw new Error('bareOsSandboxRunScript: missing vfs')
/**
* @param {string} name
* @param {(...args: unknown[]) => unknown} fn
* @param {number} pathArgIndex
*/
const wrapWrite = (name, fn, pathArgIndex = 0) => {
if (typeof fn !== 'function') return fn
return async function (...args) {
const path = args[pathArgIndex]
if (typeof path === 'string' && !sandboxWritesOk(ctx, path)) {
throw new Error(`sandbox: ${name}: write denied outside personal namespace`)
}
return fn.apply(vfs, args)
}
}
const sandboxVfs = Object.assign(Object.create(Object.getPrototypeOf(vfs)), vfs, {
writeFile: wrapWrite('writeFile', vfs.writeFile, 0),
mkdir: wrapWrite('mkdir', vfs.mkdir, 0),
unlink: wrapWrite('unlink', vfs.unlink, 0),
rmdir: wrapWrite('rmdir', vfs.rmdir, 0),
chmod: wrapWrite('chmod', vfs.chmod, 0),
chown: wrapWrite('chown', vfs.chown, 0),
symlink: wrapWrite('symlink', vfs.symlink, 1)
})
/** @type {Record<string, unknown>} */
const o = Object.assign({}, ctx, {
vfs: sandboxVfs,
bareOsSandboxed: true,
async applyUnlock() {
throw new Error('sandbox: identity unlock disabled')
},
async applyRegister() {
throw new Error('sandbox: identity register disabled')
},
async applyLogin() {
throw new Error('sandbox: identity login disabled')
},
async applyLogout() {
throw new Error('sandbox: identity logout disabled')
},
async saveVault() {
throw new Error('sandbox: saveVault disabled')
},
registerKernelShutdownHook() {},
bareOsRegisterVirtualFile() {
throw new Error('sandbox: bareOsRegisterVirtualFile disabled')
},
bareOsInvalidateVirtualFile() {
throw new Error('sandbox: bareOsInvalidateVirtualFile disabled')
},
bareOsUpdateVirtualFileMeta() {
throw new Error('sandbox: bareOsUpdateVirtualFileMeta disabled')
}
})
return o
}
+11 -4
View File
@@ -96,7 +96,14 @@ export async function runKernelFromSource(source, ctx) {
* @param {string[]} argv * @param {string[]} argv
* @param {string} [_label] reserved for diagnostics * @param {string} [_label] reserved for diagnostics
*/ */
async function runScriptFromSource(ctx, src, argv, _label = argv[0]) { /**
* Evaluate user script source (same contract as `/bin` utilities).
* @param {Record<string, unknown>} ctx
* @param {string} src
* @param {string[]} argv
* @param {string} [_label]
*/
export async function runUserScriptFromSource(ctx, src, argv, _label = argv[0]) {
try { try {
const body = stripShebang(src) const body = stripShebang(src)
const fn = new AsyncFunction( const fn = new AsyncFunction(
@@ -181,7 +188,7 @@ async function runBinCommandInner(ctx, argv) {
return return
} }
const source = b4a.toString(buf) const source = b4a.toString(buf)
return runScriptFromSource(ctx, source, argv, cmd) return runUserScriptFromSource(ctx, source, argv, cmd)
} }
// `script.js` in $PWD before PATH (same VFS routing as `./script.js`). // `script.js` in $PWD before PATH (same VFS routing as `./script.js`).
@@ -191,7 +198,7 @@ async function runBinCommandInner(ctx, argv) {
const buf = await drive.get(path, { follow: true }) const buf = await drive.get(path, { follow: true })
if (buf) { if (buf) {
const source = b4a.toString(buf) const source = b4a.toString(buf)
return runScriptFromSource(ctx, source, argv, cmd) return runUserScriptFromSource(ctx, source, argv, cmd)
} }
} }
@@ -201,7 +208,7 @@ async function runBinCommandInner(ctx, argv) {
const buf = await systemDrive.get(p, { follow: true }) const buf = await systemDrive.get(p, { follow: true })
if (buf) { if (buf) {
const source = b4a.toString(buf) const source = b4a.toString(buf)
return runScriptFromSource(ctx, source, argv, p) return runUserScriptFromSource(ctx, source, argv, p)
} }
} }
+143 -8
View File
@@ -1173,6 +1173,8 @@ function splitTopLevelStatements(tokens) {
if (t.type === 'word') { if (t.type === 'word') {
if (t.value === 'if') depth++ if (t.value === 'if') depth++
else if (t.value === 'fi') depth = Math.max(0, depth - 1) else if (t.value === 'fi') depth = Math.max(0, depth - 1)
else if (t.value === 'while' || t.value === 'for') depth++
else if (t.value === 'done') depth = Math.max(0, depth - 1)
} }
if (t.type === 'op' && t.value === ';' && depth === 0) { if (t.type === 'op' && t.value === ';' && depth === 0) {
if (cur.length) out.push(cur) if (cur.length) out.push(cur)
@@ -1200,6 +1202,8 @@ function splitTopLevelByAmpersand(tokens) {
if (t.type === 'word') { if (t.type === 'word') {
if (t.value === 'if') depth++ if (t.value === 'if') depth++
else if (t.value === 'fi') depth = Math.max(0, depth - 1) else if (t.value === 'fi') depth = Math.max(0, depth - 1)
else if (t.value === 'while' || t.value === 'for') depth++
else if (t.value === 'done') depth = Math.max(0, depth - 1)
} }
if (t.type === 'op' && t.value === '&' && depth === 0) { if (t.type === 'op' && t.value === '&' && depth === 0) {
out.push(cur) out.push(cur)
@@ -1231,10 +1235,7 @@ function scheduleBackgroundShell(ctx, toks) {
const statements = splitTopLevelStatements(toks) const statements = splitTopLevelStatements(toks)
for (const stmt of statements) { for (const stmt of statements) {
if (!stmt.length) continue if (!stmt.length) continue
const r = const r = await dispatchShellStatement(childCtx, stmt)
stmt[0]?.type === 'word' && stmt[0].value === 'if'
? await execIfConstruct(childCtx, stmt)
: await execAndOrList(childCtx, stmt)
if (r === 'exit') return r if (r === 'exit') return r
} }
syncBareOsExitStatusEnv(childCtx) syncBareOsExitStatusEnv(childCtx)
@@ -1325,6 +1326,143 @@ async function execSemicolonLists(ctx, toks) {
return 'ok' return 'ok'
} }
/**
* @param {Token[]} tokens
* @returns {number}
*/
function findWhileDoSplit(tokens) {
for (let j = 1; j < tokens.length - 2; j++) {
const t = tokens[j]
if (t.type === 'op' && t.value === ';') {
const n = tokens[j + 1]
if (n && n.type === 'word' && n.value === 'do') return j
}
}
return -1
}
/**
* @param {Record<string, unknown>} ctx
* @param {Token[]} tokens
* @returns {Promise<'exit' | 'ok'>}
*/
async function execWhileConstruct(ctx, tokens) {
const split = findWhileDoSplit(tokens)
if (split < 0) {
ctx.console.error('shell: while: expected "; do …; done"')
ctx.exitCode = 2
return 'ok'
}
const last = tokens[tokens.length - 1]
if (last.type !== 'word' || last.value !== 'done') {
ctx.console.error('shell: while: missing done')
ctx.exitCode = 2
return 'ok'
}
const condToks = tokens.slice(1, split)
const bodyToks = tokens.slice(split + 2, tokens.length - 1)
const maxIter = Number.parseInt(
ctx.vfs?.env?.BARE_OS_SHELL_LOOP_MAX || '10000',
10
)
const cap = Number.isFinite(maxIter) && maxIter > 0 ? maxIter : 10000
for (let i = 0; i < cap; i++) {
const r0 = await execSemicolonLists(ctx, condToks)
if (r0 === 'exit') return 'exit'
if ((Number(ctx.exitCode) || 0) !== 0) break
const r1 = await execSemicolonLists(ctx, bodyToks)
if (r1 === 'exit') return 'exit'
}
return 'ok'
}
/**
* @param {Record<string, unknown>} ctx
* @param {Token[]} tokens
* @returns {Promise<'exit' | 'ok'>}
*/
async function execForConstruct(ctx, tokens) {
const last = tokens[tokens.length - 1]
if (last.type !== 'word' || last.value !== 'done') {
ctx.console.error('shell: for: missing done')
ctx.exitCode = 2
return 'ok'
}
if (tokens.length < 7 || tokens[1].type !== 'word') {
ctx.console.error('shell: for: invalid syntax')
ctx.exitCode = 2
return 'ok'
}
if (tokens[2].type !== 'word' || tokens[2].value !== 'in') {
ctx.console.error('shell: for: expected `in`')
ctx.exitCode = 2
return 'ok'
}
/** @type {Token[]} */
const inToks = []
let semi = -1
for (let j = 3; j < tokens.length; j++) {
const t = tokens[j]
if (t.type === 'op' && t.value === ';') {
semi = j
break
}
inToks.push(t)
}
if (semi < 0) {
ctx.console.error('shell: for: expected `;` before do')
ctx.exitCode = 2
return 'ok'
}
if (
tokens[semi + 1]?.type !== 'word' ||
tokens[semi + 1].value !== 'do'
) {
ctx.console.error('shell: for: expected `do` after `;`')
ctx.exitCode = 2
return 'ok'
}
const varName = tokens[1].value
const bodyToks = tokens.slice(semi + 2, tokens.length - 1)
const env = ctx.vfs.env
const words = inToks
.filter((t) => t.type === 'word')
.map((t) => expandWord(/** @type {{ type: 'word', value: string }} */ (t).value, env))
const maxIter = Number.parseInt(
ctx.vfs?.env?.BARE_OS_SHELL_LOOP_MAX || '10000',
10
)
const cap = Number.isFinite(maxIter) && maxIter > 0 ? maxIter : 10000
let total = 0
for (const w of words) {
env[varName] = w
if (++total > cap) {
ctx.console.error('shell: for: exceeded BARE_OS_SHELL_LOOP_MAX')
ctx.exitCode = 1
return 'ok'
}
const r = await execSemicolonLists(ctx, bodyToks)
if (r === 'exit') return 'exit'
}
return 'ok'
}
/**
* @param {Record<string, unknown>} ctx
* @param {Token[]} stmt
* @returns {Promise<'exit' | 'ok'>}
*/
async function dispatchShellStatement(ctx, stmt) {
const head = stmt[0]
if (head?.type === 'word' && head.value === 'if')
return execIfConstruct(ctx, stmt)
if (head?.type === 'word' && head.value === 'while')
return execWhileConstruct(ctx, stmt)
if (head?.type === 'word' && head.value === 'for')
return execForConstruct(ctx, stmt)
return execAndOrList(ctx, stmt)
}
async function execIfConstruct(ctx, tokens) { async function execIfConstruct(ctx, tokens) {
const thenIdx = findThenIndex(tokens, 1) const thenIdx = findThenIndex(tokens, 1)
if (thenIdx < 0) { if (thenIdx < 0) {
@@ -1483,10 +1621,7 @@ export async function execShellLine(ctx, line) {
scheduleBackgroundShell(ctx, part) scheduleBackgroundShell(ctx, part)
continue continue
} }
const r = const r = await dispatchShellStatement(ctx, part)
part[0]?.type === 'word' && part[0].value === 'if'
? await execIfConstruct(ctx, part)
: await execAndOrList(ctx, part)
if (r === 'exit') { if (r === 'exit') {
syncBareOsExitStatusEnv(ctx) syncBareOsExitStatusEnv(ctx)
return 'exit' return 'exit'
@@ -71,6 +71,7 @@ function printHelp(ctx, prog) {
Persistent preset: ~/.config/bare-os/initd/disabled.txt (personal drive). Persistent preset: ~/.config/bare-os/initd/disabled.txt (personal drive).
Optional ~/.config/bare-os/units/<name>.unit with [Unit] After=other-unit. Optional ~/.config/bare-os/units/<name>.unit with [Unit] After=other-unit.
User overrides: ~/.config/bare-init/units/<name>.unit (merged; systemctl --user style).
journalctl: journalctl -u UNIT [--lines N] (alias for logs)` journalctl: journalctl -u UNIT [--lines N] (alias for logs)`
) )
} }
+69 -2
View File
@@ -47,7 +47,9 @@ const DIR_MARKER = '.bareos_empty'
* procBareOsQuotasText?: () => string, * procBareOsQuotasText?: () => string,
* procBareOsResourcesText?: () => string, * procBareOsResourcesText?: () => string,
* procBareOsFeaturesText?: () => string, * procBareOsFeaturesText?: () => string,
* getVirtualReaders?: () => Map<string, () => string | Uint8Array | Promise<string | Uint8Array>>, * procBareOsSwarmText?: () => string,
* getVirtualReaders?: () => Map<string, unknown>,
* unionReadPrefixes?: readonly string[],
* sysClassNetLoText?: () => string * sysClassNetLoText?: () => string
* }} [vfsOptions] * }} [vfsOptions]
*/ */
@@ -112,10 +114,19 @@ export function createVfs(
typeof vfsOptions.procBareOsFeaturesText === 'function' typeof vfsOptions.procBareOsFeaturesText === 'function'
? vfsOptions.procBareOsFeaturesText ? vfsOptions.procBareOsFeaturesText
: null : null
const procBareOsSwarmText =
typeof vfsOptions.procBareOsSwarmText === 'function'
? vfsOptions.procBareOsSwarmText
: null
const getVirtualReaders = const getVirtualReaders =
typeof vfsOptions.getVirtualReaders === 'function' typeof vfsOptions.getVirtualReaders === 'function'
? vfsOptions.getVirtualReaders ? vfsOptions.getVirtualReaders
: null : null
const unionReadPrefixes = Array.isArray(vfsOptions.unionReadPrefixes)
? vfsOptions.unionReadPrefixes.filter(
(s) => typeof s === 'string' && s.startsWith('/')
)
: []
const sysClassNetLoText = const sysClassNetLoText =
typeof vfsOptions.sysClassNetLoText === 'function' typeof vfsOptions.sysClassNetLoText === 'function'
? vfsOptions.sysClassNetLoText ? vfsOptions.sysClassNetLoText
@@ -306,6 +317,10 @@ export function createVfs(
const t = procBareOsFeaturesText ? procBareOsFeaturesText() : '{}\n' const t = procBareOsFeaturesText ? procBareOsFeaturesText() : '{}\n'
return utf8Encode(t) return utf8Encode(t)
} }
if (f === 'bare_os_swarm') {
const t = procBareOsSwarmText ? procBareOsSwarmText() : '{}\n'
return utf8Encode(t)
}
if (f === 'net_dev') { if (f === 'net_dev') {
const t = procNetDevText const t = procNetDevText
? procNetDevText() ? procNetDevText()
@@ -466,6 +481,14 @@ export function createVfs(
file: 'bare_os_features' file: 'bare_os_features'
} }
} }
if (sub === 'bare_os_swarm') {
return {
virtualPseudo: true,
kind: 'proc',
node: 'file',
file: 'bare_os_swarm'
}
}
if (sub === 'net' || sub === 'net/') { if (sub === 'net' || sub === 'net/') {
return { virtualPseudo: true, kind: 'proc', node: 'dir', dir: 'net' } return { virtualPseudo: true, kind: 'proc', node: 'dir', dir: 'net' }
} }
@@ -798,6 +821,17 @@ export function createVfs(
const p = unixPathResolve(homeRoot, rel) const p = unixPathResolve(homeRoot, rel)
return { drive: personalDrive, path: p } return { drive: personalDrive, path: p }
} }
const unionNorm = absPath.replace(/\/+$/, '') || '/'
if (
unionNorm === '/.bare-os/union' ||
absPath.startsWith('/.bare-os/union/')
) {
const p =
unionNorm === '/.bare-os/union' ? '/.bare-os/union' : unionNorm
return { drive: personalDrive, path: p }
}
return { drive: systemDrive, path: absPath } return { drive: systemDrive, path: absPath }
} }
@@ -1167,6 +1201,7 @@ export function createVfs(
'bare_os_quotas', 'bare_os_quotas',
'bare_os_resources', 'bare_os_resources',
'bare_os_session_stats', 'bare_os_session_stats',
'bare_os_swarm',
'bare_os_version', 'bare_os_version',
'cpuinfo', 'cpuinfo',
'diskstats', 'diskstats',
@@ -1455,10 +1490,20 @@ export function createVfs(
) { ) {
await assertTraverseTo(abs, 'read') await assertTraverseTo(abs, 'read')
const m = getVirtualReaders() const m = getVirtualReaders()
const fn = const ent =
r.virtualName && m && typeof m.get === 'function' r.virtualName && m && typeof m.get === 'function'
? m.get(r.virtualName) ? m.get(r.virtualName)
: null : null
const fn =
typeof ent === 'function'
? ent
: ent &&
typeof ent === 'object' &&
ent !== null &&
typeof /** @type {{ read?: unknown }} */ (ent).read ===
'function'
? /** @type {{ read: () => unknown }} */ (ent).read
: null
if (typeof fn !== 'function') return null if (typeof fn !== 'function') return null
const out = await Promise.resolve(fn()) const out = await Promise.resolve(fn())
if (typeof out === 'string') return utf8Encode(out) if (typeof out === 'string') return utf8Encode(out)
@@ -1479,6 +1524,28 @@ export function createVfs(
const { drive, path: p } = r const { drive, path: p } = r
if (isHyperdriveRootPath(p)) return null if (isHyperdriveRootPath(p)) return null
await assertTraverseTo(abs, 'read') await assertTraverseTo(abs, 'read')
if (unionReadPrefixes.length && drive === systemDrive) {
for (const pre of unionReadPrefixes) {
if (abs === pre || abs.startsWith(pre + '/')) {
const ol =
'/.bare-os/union' + (abs === '/' ? '' : abs)
const ur = route(ol)
if (
!ur.virtualPseudo &&
ur.drive === personalDrive &&
!isHyperdriveRootPath(ur.path)
) {
try {
const ubuf = await ur.drive.get(ur.path, { follow: true })
if (ubuf) return ubuf
} catch {
/* fall through to system */
}
}
break
}
}
}
return drive.get(p, { follow: true }) return drive.get(p, { follow: true })
})(), })(),
abortOpts, abortOpts,
+2
View File
@@ -844,6 +844,7 @@ test('vfs /proc /sys read-only pseudo files; write rejected', async (t) => {
bootProfileText: () => 'mini\n', bootProfileText: () => 'mini\n',
sessionText: () => 'test-session-id\n', sessionText: () => 'test-session-id\n',
initdRunText: () => 'demo-unit\tactive\t1\tdemo\n', initdRunText: () => 'demo-unit\tactive\t1\tdemo\n',
procBareOsSwarmText: () => '{}\n',
bareOsIpc bareOsIpc
} }
) )
@@ -858,6 +859,7 @@ test('vfs /proc /sys read-only pseudo files; write rejected', async (t) => {
'bare_os_quotas', 'bare_os_quotas',
'bare_os_resources', 'bare_os_resources',
'bare_os_session_stats', 'bare_os_session_stats',
'bare_os_swarm',
'bare_os_version', 'bare_os_version',
'cpuinfo', 'cpuinfo',
'diskstats', 'diskstats',
@@ -4,6 +4,7 @@
* next, exit, break, continue, ++/--, arrays, strings, regex ~ !~, builtins (length, substr, index, * next, exit, break, continue, ++/--, arrays, strings, regex ~ !~, builtins (length, substr, index,
* split, sprintf, sub, gsub, match, int, tolower, toupper, rand, srand), getline from stdin/files, * split, sprintf, sub, gsub, match, int, tolower, toupper, rand, srand), getline from stdin/files,
* -F FS, NF, NR, FNR, $0..$n, OFS, ORS, RS, ARGC, ARGV, FILENAME, ENVIRON (read-only mirror). * -F FS, NF, NR, FNR, $0..$n, OFS, ORS, RS, ARGC, ARGV, FILENAME, ENVIRON (read-only mirror).
* GNU-style empty fields: consecutive FS delimiters still advance $1..$NF where the grammar allows.
*/ */
function bareAwkError(msg) { function bareAwkError(msg) {
@@ -65,6 +65,7 @@ export const COREUTILS_COMMANDS = [
'nproc', 'nproc',
'numfmt', 'numfmt',
'od', 'od',
'oidc-publish',
'paste', 'paste',
'pathchk', 'pathchk',
'pr', 'pr',
@@ -0,0 +1,12 @@
{
"name": "oidc-publish",
"section": 1,
"title": "OIDC client-credentials helper",
"synopsis": ["oidc-publish help", "oidc-publish"],
"description": "POSTs client_credentials to ${OIDC_ISSUER}/oauth/token when ctx.httpFetch exists and HTTP policy allows. See packages/bare-os-coreutils/src/oidc-publish.js.",
"options": [],
"keywords": ["oidc", "pear", "http", "bare-os"],
"examples": [
{ "caption": "help", "code": "oidc-publish help" }
]
}
@@ -9,6 +9,9 @@ async function run(ctx, argv) {
git remote add origin 'git+pear://<key-or-link>' git remote add origin 'git+pear://<key-or-link>'
git push origin main git push origin main
Host env (aligns with gip-transport / git-remote-git+pear):
GIT_PEAR_DEBUG, HYPERSWARM_DHT, PEAR_CHANNEL forwarded by Pear tooling when set.
Pear app channel metadata for images: set BARE_OS_PEAR_CHANNEL / BARE_OS_PEAR_RELEASE on the host. Pear app channel metadata for images: set BARE_OS_PEAR_CHANNEL / BARE_OS_PEAR_RELEASE on the host.
`) `)
ctx.exitCode = 0 ctx.exitCode = 0
@@ -0,0 +1,61 @@
/**
* Thin helper for OIDC-style token exchange workflows (Holepunch oidc-publishing patterns).
* Uses ctx.httpFetch when present and host HTTP policy allows the issuer URL.
*/
async function run(ctx, argv) {
if (argv[1] === 'help' || argv[1] === '-h' || argv[1] === '--help') {
ctx.console.log(`oidc-publish — optional OIDC token helper
Reads OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET (or argv) and POSTs
to \${issuer}/oauth/token with grant_type=client_credentials when ctx.httpFetch exists.
Example:
export OIDC_ISSUER=https://issuer.example
oidc-publish
Requires delegated httpFetch and allowlisted host (BARE_OS_HTTP_ALLOWLIST).
`)
ctx.exitCode = 0
return
}
const env = ctx.vfs?.env || {}
const issuer = String(env.OIDC_ISSUER || argv[2] || '').replace(/\/+$/, '')
const cid = String(env.OIDC_CLIENT_ID || argv[3] || '')
const csec = String(env.OIDC_CLIENT_SECRET || argv[4] || '')
if (!issuer || !cid || !csec) {
ctx.console.error(
'oidc-publish: set OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET'
)
ctx.exitCode = 2
return
}
if (typeof ctx.httpFetch !== 'function') {
ctx.console.error('oidc-publish: ctx.httpFetch not available')
ctx.exitCode = 1
return
}
const url = `${issuer}/oauth/token`
const body =
'grant_type=client_credentials&client_id=' +
encodeURIComponent(cid) +
'&client_secret=' +
encodeURIComponent(csec)
try {
const res = await ctx.httpFetch(url, {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body
})
const text = await res.text()
if (!res.ok) {
ctx.console.error('oidc-publish: HTTP ' + res.status + ' ' + text.slice(0, 200))
ctx.exitCode = 1
return
}
ctx.console.log(text.slice(0, 4000))
ctx.exitCode = 0
} catch (e) {
ctx.console.error('oidc-publish: ' + ((e && e.message) || String(e)))
ctx.exitCode = 1
}
}
+6
View File
@@ -16,5 +16,11 @@ export {
BARE_OS_FEATURE_HTTP_POLICY, BARE_OS_FEATURE_HTTP_POLICY,
BARE_OS_FEATURE_VIRTUAL_FILES, BARE_OS_FEATURE_VIRTUAL_FILES,
BARE_OS_FEATURE_HOST_STATS, BARE_OS_FEATURE_HOST_STATS,
BARE_OS_FEATURE_CRYPTO_URANDOM,
BARE_OS_FEATURE_SANDBOX_SCRIPT,
BARE_OS_FEATURE_BOOT_PHASE_HOOKS,
BARE_OS_FEATURE_IPC_DUPLEX,
BARE_OS_FEATURE_VFS_UNION,
BARE_OS_FEATURE_PROC_SWARM,
BARE_OS_KERNEL_FEATURES_STOCK_V1 BARE_OS_KERNEL_FEATURES_STOCK_V1
} from './lib/kernel-feature-bits.js' } from './lib/kernel-feature-bits.js'
@@ -21,10 +21,34 @@ export const BARE_OS_FEATURE_VIRTUAL_FILES = 1 << 3
/** Bit 4: Host introspection bridge (`ctx.bareOsHostStats`) when available. */ /** Bit 4: Host introspection bridge (`ctx.bareOsHostStats`) when available. */
export const BARE_OS_FEATURE_HOST_STATS = 1 << 4 export const BARE_OS_FEATURE_HOST_STATS = 1 << 4
/** Default bitmask implied by current stock booter (1.6.x). */ /** Bit 5: `/dev/urandom` backed by `bare-crypto` (disable with `BARE_OS_URANDOM_CRYPTO=0`). */
export const BARE_OS_FEATURE_CRYPTO_URANDOM = 1 << 5
/** Bit 6: `ctx.bareOsSandboxRunScript` (restricted `ctx`). */
export const BARE_OS_FEATURE_SANDBOX_SCRIPT = 1 << 6
/** Bit 7: Boot phase hooks (`ctx.bareOsRegisterBootPhaseHook`). */
export const BARE_OS_FEATURE_BOOT_PHASE_HOOKS = 1 << 7
/** Bit 8: Duplex IPC bridge (`bareOsIpc.createDuplexBridge`). */
export const BARE_OS_FEATURE_IPC_DUPLEX = 1 << 8
/** Bit 9: VFS union read overlay (`BARE_OS_VFS_UNION_PREFIXES`). */
export const BARE_OS_FEATURE_VFS_UNION = 1 << 9
/** Bit 10: Swarm snapshot proc (`/proc/bare_os_swarm`). */
export const BARE_OS_FEATURE_PROC_SWARM = 1 << 10
/** Default bitmask implied by current stock booter. */
export const BARE_OS_KERNEL_FEATURES_STOCK_V1 = export const BARE_OS_KERNEL_FEATURES_STOCK_V1 =
BARE_OS_FEATURE_IPC_FANOUT | BARE_OS_FEATURE_IPC_FANOUT |
BARE_OS_FEATURE_ABORT_TIMEOUT | BARE_OS_FEATURE_ABORT_TIMEOUT |
BARE_OS_FEATURE_HTTP_POLICY | BARE_OS_FEATURE_HTTP_POLICY |
BARE_OS_FEATURE_VIRTUAL_FILES | BARE_OS_FEATURE_VIRTUAL_FILES |
BARE_OS_FEATURE_HOST_STATS BARE_OS_FEATURE_HOST_STATS |
BARE_OS_FEATURE_CRYPTO_URANDOM |
BARE_OS_FEATURE_SANDBOX_SCRIPT |
BARE_OS_FEATURE_BOOT_PHASE_HOOKS |
BARE_OS_FEATURE_IPC_DUPLEX |
BARE_OS_FEATURE_VFS_UNION |
BARE_OS_FEATURE_PROC_SWARM
+2
View File
@@ -17,6 +17,7 @@ import {
defaultKernelRoot, defaultKernelRoot,
defaultSeedCorestorePath defaultSeedCorestorePath
} from './lib/paths.js' } from './lib/paths.js'
import { logPearMultisigKernelHint } from './lib/pear-multisig-hint.js'
const _pkg = packageRootDir(import.meta.url) const _pkg = packageRootDir(import.meta.url)
@@ -110,6 +111,7 @@ async function main() {
console.log('Drive ready:', drive.id) console.log('Drive ready:', drive.id)
await stageKernelTree(drive, kernelRoot) await stageKernelTree(drive, kernelRoot)
await logPearMultisigKernelHint(kernelRoot)
const localRAM = new Map() const localRAM = new Map()
const mbr = buildMbr(drive.key) const mbr = buildMbr(drive.key)
+1
View File
@@ -93,6 +93,7 @@ function bareOsEmitRaw(ctx, chunk) {
* next, exit, break, continue, ++/--, arrays, strings, regex ~ !~, builtins (length, substr, index, * next, exit, break, continue, ++/--, arrays, strings, regex ~ !~, builtins (length, substr, index,
* split, sprintf, sub, gsub, match, int, tolower, toupper, rand, srand), getline from stdin/files, * split, sprintf, sub, gsub, match, int, tolower, toupper, rand, srand), getline from stdin/files,
* -F FS, NF, NR, FNR, $0..$n, OFS, ORS, RS, ARGC, ARGV, FILENAME, ENVIRON (read-only mirror). * -F FS, NF, NR, FNR, $0..$n, OFS, ORS, RS, ARGC, ARGV, FILENAME, ENVIRON (read-only mirror).
* GNU-style empty fields: consecutive FS delimiters still advance $1..$NF where the grammar allows.
*/ */
function bareAwkError(msg) { function bareAwkError(msg) {
@@ -98,6 +98,9 @@ async function run(ctx, argv) {
git remote add origin 'git+pear://<key-or-link>' git remote add origin 'git+pear://<key-or-link>'
git push origin main git push origin main
Host env (aligns with gip-transport / git-remote-git+pear):
GIT_PEAR_DEBUG, HYPERSWARM_DHT, PEAR_CHANNEL — forwarded by Pear tooling when set.
Pear app channel metadata for images: set BARE_OS_PEAR_CHANNEL / BARE_OS_PEAR_RELEASE on the host. Pear app channel metadata for images: set BARE_OS_PEAR_CHANNEL / BARE_OS_PEAR_RELEASE on the host.
`) `)
ctx.exitCode = 0 ctx.exitCode = 0
+1 -1
View File
@@ -87,7 +87,7 @@ function bareOsEmitRaw(ctx, chunk) {
return false return false
} }
var BARE_OS_HELP_BIN_SPACED = "arch awk base32 base64 basename basenc cat chgrp chmod chown cksum clear comm cp crontab curl cut date df dir dircolors dirname du echo edit env exit expand expr factor false find fmt fold getconf git git-pear grep groups hdms head help hostid hostname id install join journalctl jq ln login logname logout ls man md5sum mkdir mkfifo mktemp mv nano nl nproc numfmt od paste pathchk pr printenv printf pwd readlink realpath rev rm rmdir savevault sed seq sha1sum sha256sum sha512sum shuf sleep sort split stat sum sync systemctl tac tail tee test theme time touch tr true truncate tsort tty uname unexpand uniq unlink uptime users vdir wc wget which who whoami xargs yes" var BARE_OS_HELP_BIN_SPACED = "arch awk base32 base64 basename basenc cat chgrp chmod chown cksum clear comm cp crontab curl cut date df dir dircolors dirname du echo edit env exit expand expr factor false find fmt fold getconf git git-pear grep groups hdms head help hostid hostname id install join journalctl jq ln login logname logout ls man md5sum mkdir mkfifo mktemp mv nano nl nproc numfmt od oidc-publish paste pathchk pr printenv printf pwd readlink realpath rev rm rmdir savevault sed seq sha1sum sha256sum sha512sum shuf sleep sort split stat sum sync systemctl tac tail tee test theme time touch tr true truncate tsort tty uname unexpand uniq unlink uptime users vdir wc wget which who whoami xargs yes"
async function run(ctx, argv) { async function run(ctx, argv) {
ctx.console.log( ctx.console.log(
'Bare OS — default user: guest | shell builtins: alias, barerc, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: ' + 'Bare OS — default user: guest | shell builtins: alias, barerc, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: ' +
@@ -0,0 +1,150 @@
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/**
* Thin helper for OIDC-style token exchange workflows (Holepunch oidc-publishing patterns).
* Uses ctx.httpFetch when present and host HTTP policy allows the issuer URL.
*/
async function run(ctx, argv) {
if (argv[1] === 'help' || argv[1] === '-h' || argv[1] === '--help') {
ctx.console.log(`oidc-publish — optional OIDC token helper
Reads OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET (or argv) and POSTs
to \${issuer}/oauth/token with grant_type=client_credentials when ctx.httpFetch exists.
Example:
export OIDC_ISSUER=https://issuer.example
oidc-publish
Requires delegated httpFetch and allowlisted host (BARE_OS_HTTP_ALLOWLIST).
`)
ctx.exitCode = 0
return
}
const env = ctx.vfs?.env || {}
const issuer = String(env.OIDC_ISSUER || argv[2] || '').replace(/\/+$/, '')
const cid = String(env.OIDC_CLIENT_ID || argv[3] || '')
const csec = String(env.OIDC_CLIENT_SECRET || argv[4] || '')
if (!issuer || !cid || !csec) {
ctx.console.error(
'oidc-publish: set OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET'
)
ctx.exitCode = 2
return
}
if (typeof ctx.httpFetch !== 'function') {
ctx.console.error('oidc-publish: ctx.httpFetch not available')
ctx.exitCode = 1
return
}
const url = `${issuer}/oauth/token`
const body =
'grant_type=client_credentials&client_id=' +
encodeURIComponent(cid) +
'&client_secret=' +
encodeURIComponent(csec)
try {
const res = await ctx.httpFetch(url, {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body
})
const text = await res.text()
if (!res.ok) {
ctx.console.error('oidc-publish: HTTP ' + res.status + ' ' + text.slice(0, 200))
ctx.exitCode = 1
return
}
ctx.console.log(text.slice(0, 4000))
ctx.exitCode = 0
} catch (e) {
ctx.console.error('oidc-publish: ' + ((e && e.message) || String(e)))
ctx.exitCode = 1
}
}
@@ -0,0 +1,5 @@
{
"sha256": {
"/etc/bare-os/rc": "replace-with-sha256-hex-of-file-contents"
}
}
+111 -2
View File
@@ -196,6 +196,13 @@ function bootLineAllowed(line, allow) {
* @param {string[]} phaseLog * @param {string[]} phaseLog
*/ */
async function bootTimed(ctx, label, fn, phaseLog) { async function bootTimed(ctx, label, fn, phaseLog) {
if (typeof ctx.bareOsInvokeBootPhaseHooks === 'function') {
await ctx.bareOsInvokeBootPhaseHooks({
phase: label,
when: 'before',
label
})
}
const t0 = Date.now() const t0 = Date.now()
await fn() await fn()
const ms = Date.now() - t0 const ms = Date.now() - t0
@@ -226,6 +233,67 @@ async function bootTimed(ctx, label, fn, phaseLog) {
sessionId: (ctx.env && ctx.env.BARE_OS_SESSION_ID) || '' sessionId: (ctx.env && ctx.env.BARE_OS_SESSION_ID) || ''
}) })
} }
if (typeof ctx.bareOsInvokeBootPhaseHooks === 'function') {
await ctx.bareOsInvokeBootPhaseHooks({
phase: 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
}
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
} }
/** /**
@@ -400,7 +468,15 @@ async function runRcFileAt(ctx, drivePath, label) {
try { try {
const buf = await drive.get(drivePath) const buf = await drive.get(drivePath)
if (!buf) return true if (!buf) return true
return await runRcLines(ctx, b4a.toString(buf)) 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) { } catch (e) {
console.error(`${label}: ` + ((e && e.message) || String(e))) console.error(`${label}: ` + ((e && e.message) || String(e)))
return true return true
@@ -578,6 +654,35 @@ async function runKernelSelftest(ctx) {
} }
} }
} }
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) { if (tap) {
console.error(`1..${tapN}`) console.error(`1..${tapN}`)
} }
@@ -596,7 +701,11 @@ function publishBootReady(ctx, phaseLog) {
phases: [...phaseLog], phases: [...phaseLog],
sessionId: sid, sessionId: sid,
completedAtMs: Date.now(), completedAtMs: Date.now(),
minimal: bootMinimal(ctx) minimal: bootMinimal(ctx),
subsystems: {
kernel: { ready: true, phaseCount: phaseLog.length },
initd: { awaited: true }
}
}) })
} }
@@ -0,0 +1,3 @@
{
"pins": {}
}
@@ -1,6 +1,12 @@
{ {
"version": 1, "version": 1,
"bundles": [ "bundles": [
{
"path": "/lib/bare/bundles/b4a.js",
"keys": [
"b4a"
]
},
{ {
"path": "/lib/bare/bundles/safetyCatch.js", "path": "/lib/bare/bundles/safetyCatch.js",
"keys": [ "keys": [
@@ -13,12 +19,6 @@
"hypercoreIdEncoding" "hypercoreIdEncoding"
] ]
}, },
{
"path": "/lib/bare/bundles/b4a.js",
"keys": [
"b4a"
]
},
{ {
"path": "/lib/bare/bundles/compactEncoding.js", "path": "/lib/bare/bundles/compactEncoding.js",
"keys": [ "keys": [
@@ -31,30 +31,30 @@
"bareUrl" "bareUrl"
] ]
}, },
{
"path": "/lib/bare/bundles/protomux.js",
"keys": [
"protomux"
]
},
{ {
"path": "/lib/bare/bundles/bareEncoding.js", "path": "/lib/bare/bundles/bareEncoding.js",
"keys": [ "keys": [
"bareEncoding" "bareEncoding"
] ]
}, },
{
"path": "/lib/bare/bundles/barePath.js",
"keys": [
"barePath"
]
},
{ {
"path": "/lib/bare/bundles/bareEvents.js", "path": "/lib/bare/bundles/bareEvents.js",
"keys": [ "keys": [
"bareEvents" "bareEvents"
] ]
}, },
{
"path": "/lib/bare/bundles/protomux.js",
"keys": [
"protomux"
]
},
{
"path": "/lib/bare/bundles/barePath.js",
"keys": [
"barePath"
]
},
{ {
"path": "/lib/bare/bundles/bareAbort.js", "path": "/lib/bare/bundles/bareAbort.js",
"keys": [ "keys": [
@@ -73,6 +73,12 @@
"bareAnsiEscapes" "bareAnsiEscapes"
] ]
}, },
{
"path": "/lib/bare/bundles/bareAddonResolve.js",
"keys": [
"bareAddonResolve"
]
},
{ {
"path": "/lib/bare/bundles/bareReadline.js", "path": "/lib/bare/bundles/bareReadline.js",
"keys": [ "keys": [
@@ -85,24 +91,6 @@
"bareCrypto" "bareCrypto"
] ]
}, },
{
"path": "/lib/bare/bundles/bareAddonResolve.js",
"keys": [
"bareAddonResolve"
]
},
{
"path": "/lib/bare/bundles/bareAsyncHooks.js",
"keys": [
"bareAsyncHooks"
]
},
{
"path": "/lib/bare/bundles/bareAtomics.js",
"keys": [
"bareAtomics"
]
},
{ {
"path": "/lib/bare/bundles/bareAppKit.js", "path": "/lib/bare/bundles/bareAppKit.js",
"keys": [ "keys": [
@@ -116,9 +104,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareAssert.js", "path": "/lib/bare/bundles/bareAsyncHooks.js",
"keys": [ "keys": [
"bareAssert" "bareAsyncHooks"
] ]
}, },
{ {
@@ -127,6 +115,18 @@
"fetch" "fetch"
] ]
}, },
{
"path": "/lib/bare/bundles/bareAtomics.js",
"keys": [
"bareAtomics"
]
},
{
"path": "/lib/bare/bundles/bareAssert.js",
"keys": [
"bareAssert"
]
},
{ {
"path": "/lib/bare/bundles/bareBmp.js", "path": "/lib/bare/bundles/bareBmp.js",
"keys": [ "keys": [
@@ -139,12 +139,6 @@
"bareBundleCompile" "bareBundleCompile"
] ]
}, },
{
"path": "/lib/bare/bundles/bareBuffer.js",
"keys": [
"bareBuffer"
]
},
{ {
"path": "/lib/bare/bundles/bareBundle.js", "path": "/lib/bare/bundles/bareBundle.js",
"keys": [ "keys": [
@@ -152,9 +146,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareBundleEvaluate.js", "path": "/lib/bare/bundles/bareBuffer.js",
"keys": [ "keys": [
"bareBundleEvaluate" "bareBuffer"
] ]
}, },
{ {
@@ -163,24 +157,36 @@
"bareBluetoothApple" "bareBluetoothApple"
] ]
}, },
{
"path": "/lib/bare/bundles/bareBundleEvaluate.js",
"keys": [
"bareBundleEvaluate"
]
},
{ {
"path": "/lib/bare/bundles/bareBoot.js", "path": "/lib/bare/bundles/bareBoot.js",
"keys": [ "keys": [
"bareBoot" "bareBoot"
] ]
}, },
{
"path": "/lib/bare/bundles/bareDaemon.js",
"keys": [
"bareDaemon"
]
},
{ {
"path": "/lib/bare/bundles/bareConsole.js", "path": "/lib/bare/bundles/bareConsole.js",
"keys": [ "keys": [
"bareConsole" "bareConsole"
] ]
}, },
{
"path": "/lib/bare/bundles/bareDebugLog.js",
"keys": [
"bareDebugLog"
]
},
{
"path": "/lib/bare/bundles/bareDaemon.js",
"keys": [
"bareDaemon"
]
},
{ {
"path": "/lib/bare/bundles/bareBundleId.js", "path": "/lib/bare/bundles/bareBundleId.js",
"keys": [ "keys": [
@@ -193,12 +199,6 @@
"bareChannel" "bareChannel"
] ]
}, },
{
"path": "/lib/bare/bundles/bareDebugLog.js",
"keys": [
"bareDebugLog"
]
},
{ {
"path": "/lib/bare/bundles/bareDelta.js", "path": "/lib/bare/bundles/bareDelta.js",
"keys": [ "keys": [
@@ -223,6 +223,12 @@
"bareEnv" "bareEnv"
] ]
}, },
{
"path": "/lib/bare/bundles/bareExif.js",
"keys": [
"bareExif"
]
},
{ {
"path": "/lib/bare/bundles/bareCov.js", "path": "/lib/bare/bundles/bareCov.js",
"keys": [ "keys": [
@@ -235,12 +241,6 @@
"bareDgram" "bareDgram"
] ]
}, },
{
"path": "/lib/bare/bundles/bareExif.js",
"keys": [
"bareExif"
]
},
{ {
"path": "/lib/bare/bundles/bareFfmpeg.js", "path": "/lib/bare/bundles/bareFfmpeg.js",
"keys": [ "keys": [
@@ -253,12 +253,6 @@
"bareFfmpegEncodings" "bareFfmpegEncodings"
] ]
}, },
{
"path": "/lib/bare/bundles/bareFormData.js",
"keys": [
"bareFormData"
]
},
{ {
"path": "/lib/bare/bundles/bareFormat.js", "path": "/lib/bare/bundles/bareFormat.js",
"keys": [ "keys": [
@@ -266,9 +260,15 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareGif.js", "path": "/lib/bare/bundles/bareFormData.js",
"keys": [ "keys": [
"bareGif" "bareFormData"
]
},
{
"path": "/lib/bare/bundles/bareHeif.js",
"keys": [
"bareHeif"
] ]
}, },
{ {
@@ -278,15 +278,15 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareGtk.js", "path": "/lib/bare/bundles/bareGif.js",
"keys": [ "keys": [
"bareGtk" "bareGif"
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareHeif.js", "path": "/lib/bare/bundles/bareGtk.js",
"keys": [ "keys": [
"bareHeif" "bareGtk"
] ]
}, },
{ {
@@ -307,12 +307,6 @@
"bareHttpParser" "bareHttpParser"
] ]
}, },
{
"path": "/lib/bare/bundles/bareImageResample.js",
"keys": [
"bareImageResample"
]
},
{ {
"path": "/lib/bare/bundles/bareIco.js", "path": "/lib/bare/bundles/bareIco.js",
"keys": [ "keys": [
@@ -320,9 +314,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareHttp1.js", "path": "/lib/bare/bundles/bareImageResample.js",
"keys": [ "keys": [
"bareHttp1" "bareImageResample"
] ]
}, },
{ {
@@ -331,6 +325,12 @@
"bareInspect" "bareInspect"
] ]
}, },
{
"path": "/lib/bare/bundles/bareHttp1.js",
"keys": [
"bareHttp1"
]
},
{ {
"path": "/lib/bare/bundles/bareHttps.js", "path": "/lib/bare/bundles/bareHttps.js",
"keys": [ "keys": [
@@ -361,6 +361,12 @@
"bareLief" "bareLief"
] ]
}, },
{
"path": "/lib/bare/bundles/bareLogger.js",
"keys": [
"bareLogger"
]
},
{ {
"path": "/lib/bare/bundles/bareLink.js", "path": "/lib/bare/bundles/bareLink.js",
"keys": [ "keys": [
@@ -373,18 +379,6 @@
"bareInspector" "bareInspector"
] ]
}, },
{
"path": "/lib/bare/bundles/bareLogger.js",
"keys": [
"bareLogger"
]
},
{
"path": "/lib/bare/bundles/bareModuleLexer.js",
"keys": [
"bareModuleLexer"
]
},
{ {
"path": "/lib/bare/bundles/bareMake.js", "path": "/lib/bare/bundles/bareMake.js",
"keys": [ "keys": [
@@ -398,15 +392,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareModule.js", "path": "/lib/bare/bundles/bareModuleLexer.js",
"keys": [ "keys": [
"bareModule" "bareModuleLexer"
]
},
{
"path": "/lib/bare/bundles/bareNdk.js",
"keys": [
"bareNdk"
] ]
}, },
{ {
@@ -416,9 +404,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareNative.js", "path": "/lib/bare/bundles/bareModule.js",
"keys": [ "keys": [
"bareNative" "bareModule"
] ]
}, },
{ {
@@ -428,39 +416,15 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareOpen.js", "path": "/lib/bare/bundles/bareNdk.js",
"keys": [ "keys": [
"bareOpen" "bareNdk"
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareNet.js", "path": "/lib/bare/bundles/bareNative.js",
"keys": [ "keys": [
"bareNet" "bareNative"
]
},
{
"path": "/lib/bare/bundles/bareMedia.js",
"keys": [
"bareMedia"
]
},
{
"path": "/lib/bare/bundles/bareOs.js",
"keys": [
"bareOs"
]
},
{
"path": "/lib/bare/bundles/barePack.js",
"keys": [
"barePack"
]
},
{
"path": "/lib/bare/bundles/bareNodeRuntime.js",
"keys": [
"bareNodeRuntime"
] ]
}, },
{ {
@@ -470,9 +434,27 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/barePng.js", "path": "/lib/bare/bundles/bareOs.js",
"keys": [ "keys": [
"barePng" "bareOs"
]
},
{
"path": "/lib/bare/bundles/bareOpen.js",
"keys": [
"bareOpen"
]
},
{
"path": "/lib/bare/bundles/bareMedia.js",
"keys": [
"bareMedia"
]
},
{
"path": "/lib/bare/bundles/bareNet.js",
"keys": [
"bareNet"
] ]
}, },
{ {
@@ -481,12 +463,30 @@
"barePerformance" "barePerformance"
] ]
}, },
{
"path": "/lib/bare/bundles/barePng.js",
"keys": [
"barePng"
]
},
{ {
"path": "/lib/bare/bundles/barePackDrive.js", "path": "/lib/bare/bundles/barePackDrive.js",
"keys": [ "keys": [
"barePackDrive" "barePackDrive"
] ]
}, },
{
"path": "/lib/bare/bundles/barePack.js",
"keys": [
"barePack"
]
},
{
"path": "/lib/bare/bundles/barePipe.js",
"keys": [
"barePipe"
]
},
{ {
"path": "/lib/bare/bundles/barePunycode.js", "path": "/lib/bare/bundles/barePunycode.js",
"keys": [ "keys": [
@@ -505,18 +505,18 @@
"barePrebuild" "barePrebuild"
] ]
}, },
{
"path": "/lib/bare/bundles/barePipe.js",
"keys": [
"barePipe"
]
},
{ {
"path": "/lib/bare/bundles/bareQueueMicrotask.js", "path": "/lib/bare/bundles/bareQueueMicrotask.js",
"keys": [ "keys": [
"bareQueueMicrotask" "bareQueueMicrotask"
] ]
}, },
{
"path": "/lib/bare/bundles/bareNodeRuntime.js",
"keys": [
"bareNodeRuntime"
]
},
{ {
"path": "/lib/bare/bundles/bareRealm.js", "path": "/lib/bare/bundles/bareRealm.js",
"keys": [ "keys": [
@@ -541,12 +541,6 @@
"bareRuntime" "bareRuntime"
] ]
}, },
{
"path": "/lib/bare/bundles/bareSdl.js",
"keys": [
"bareSdl"
]
},
{ {
"path": "/lib/bare/bundles/bareRepl.js", "path": "/lib/bare/bundles/bareRepl.js",
"keys": [ "keys": [
@@ -559,6 +553,12 @@
"bareRpc" "bareRpc"
] ]
}, },
{
"path": "/lib/bare/bundles/bareSdl.js",
"keys": [
"bareSdl"
]
},
{ {
"path": "/lib/bare/bundles/bareSemver.js", "path": "/lib/bare/bundles/bareSemver.js",
"keys": [ "keys": [
@@ -571,12 +571,6 @@
"bareRun" "bareRun"
] ]
}, },
{
"path": "/lib/bare/bundles/bareSidecar.js",
"keys": [
"bareSidecar"
]
},
{ {
"path": "/lib/bare/bundles/bareSignals.js", "path": "/lib/bare/bundles/bareSignals.js",
"keys": [ "keys": [
@@ -584,9 +578,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareStorage.js", "path": "/lib/bare/bundles/bareSidecar.js",
"keys": [ "keys": [
"bareStorage" "bareSidecar"
] ]
}, },
{ {
@@ -596,9 +590,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareStringDecoder.js", "path": "/lib/bare/bundles/bareStorage.js",
"keys": [ "keys": [
"bareStringDecoder" "bareStorage"
] ]
}, },
{ {
@@ -607,6 +601,12 @@
"bareStdio" "bareStdio"
] ]
}, },
{
"path": "/lib/bare/bundles/bareStringDecoder.js",
"keys": [
"bareStringDecoder"
]
},
{ {
"path": "/lib/bare/bundles/bareSvg.js", "path": "/lib/bare/bundles/bareSvg.js",
"keys": [ "keys": [
@@ -625,6 +625,12 @@
"bareStructuredClone" "bareStructuredClone"
] ]
}, },
{
"path": "/lib/bare/bundles/bareTiff.js",
"keys": [
"bareTiff"
]
},
{ {
"path": "/lib/bare/bundles/bareTap.js", "path": "/lib/bare/bundles/bareTap.js",
"keys": [ "keys": [
@@ -638,15 +644,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareTiff.js", "path": "/lib/bare/bundles/bareThread.js",
"keys": [ "keys": [
"bareTiff" "bareThread"
]
},
{
"path": "/lib/bare/bundles/bareTimers.js",
"keys": [
"bareTimers"
] ]
}, },
{ {
@@ -655,18 +655,30 @@
"bareTcp" "bareTcp"
] ]
}, },
{
"path": "/lib/bare/bundles/bareThread.js",
"keys": [
"bareThread"
]
},
{ {
"path": "/lib/bare/bundles/bareTpl.js", "path": "/lib/bare/bundles/bareTpl.js",
"keys": [ "keys": [
"bareTpl" "bareTpl"
] ]
}, },
{
"path": "/lib/bare/bundles/bareTimers.js",
"keys": [
"bareTimers"
]
},
{
"path": "/lib/bare/bundles/bareTls.js",
"keys": [
"bareTls"
]
},
{
"path": "/lib/bare/bundles/bareTty.js",
"keys": [
"bareTty"
]
},
{ {
"path": "/lib/bare/bundles/bareType.js", "path": "/lib/bare/bundles/bareType.js",
"keys": [ "keys": [
@@ -679,30 +691,24 @@
"bareUiKit" "bareUiKit"
] ]
}, },
{
"path": "/lib/bare/bundles/bareTls.js",
"keys": [
"bareTls"
]
},
{ {
"path": "/lib/bare/bundles/bareUnpack.js", "path": "/lib/bare/bundles/bareUnpack.js",
"keys": [ "keys": [
"bareUnpack" "bareUnpack"
] ]
}, },
{
"path": "/lib/bare/bundles/bareTty.js",
"keys": [
"bareTty"
]
},
{ {
"path": "/lib/bare/bundles/bareV8.js", "path": "/lib/bare/bundles/bareV8.js",
"keys": [ "keys": [
"bareV8" "bareV8"
] ]
}, },
{
"path": "/lib/bare/bundles/bareWalkHandles.js",
"keys": [
"bareWalkHandles"
]
},
{ {
"path": "/lib/bare/bundles/bareVm.js", "path": "/lib/bare/bundles/bareVm.js",
"keys": [ "keys": [
@@ -715,12 +721,6 @@
"bareUnionBundle" "bareUnionBundle"
] ]
}, },
{
"path": "/lib/bare/bundles/bareWalkHandles.js",
"keys": [
"bareWalkHandles"
]
},
{ {
"path": "/lib/bare/bundles/bareWebKit.js", "path": "/lib/bare/bundles/bareWebKit.js",
"keys": [ "keys": [
@@ -739,18 +739,18 @@
"bareWebp" "bareWebp"
] ]
}, },
{
"path": "/lib/bare/bundles/bareUtils.js",
"keys": [
"bareUtils"
]
},
{ {
"path": "/lib/bare/bundles/bareWhich.js", "path": "/lib/bare/bundles/bareWhich.js",
"keys": [ "keys": [
"bareWhich" "bareWhich"
] ]
}, },
{
"path": "/lib/bare/bundles/bareUtils.js",
"keys": [
"bareUtils"
]
},
{ {
"path": "/lib/bare/bundles/bareV8ToIstanbul.js", "path": "/lib/bare/bundles/bareV8ToIstanbul.js",
"keys": [ "keys": [
@@ -769,12 +769,6 @@
"bareXdiff" "bareXdiff"
] ]
}, },
{
"path": "/lib/bare/bundles/bareZlib.js",
"keys": [
"bareZlib"
]
},
{ {
"path": "/lib/bare/bundles/bareWs.js", "path": "/lib/bare/bundles/bareWs.js",
"keys": [ "keys": [
@@ -782,9 +776,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareZmq.js", "path": "/lib/bare/bundles/bareZlib.js",
"keys": [ "keys": [
"bareZmq" "bareZlib"
] ]
}, },
{ {
@@ -792,6 +786,12 @@
"keys": [ "keys": [
"bareWorker" "bareWorker"
] ]
},
{
"path": "/lib/bare/bundles/bareZmq.js",
"keys": [
"bareZmq"
]
} }
], ],
"bundleStats": { "bundleStats": {
File diff suppressed because one or more lines are too long
@@ -0,0 +1,31 @@
import { readFile } from 'fs/promises'
import path from 'path'
/**
* When `pear.multisig.json` exists next to the kernel tree, validate shape and log status.
* Mirrors Holepunch `pear-multisig-link` style metadata (operational hint, not cryptography).
* @param {string} kernelRoot
*/
export async function logPearMultisigKernelHint(kernelRoot) {
const f = path.join(kernelRoot, 'pear.multisig.json')
try {
const raw = await readFile(f, 'utf8')
const j = JSON.parse(raw)
const signers = j.signers
const quorum = j.quorum
if (!Array.isArray(signers) || typeof quorum !== 'number') {
console.warn('[seeder] pear.multisig.json: expected { signers: [], quorum: number }')
return
}
if (quorum < 1 || quorum > signers.length) {
console.warn('[seeder] pear.multisig.json: quorum out of range')
return
}
console.log(
`[seeder] pear.multisig.json OK (${signers.length} signers, quorum ${quorum})`
)
} catch (e) {
if (/** @type {NodeJS.ErrnoException} */ (e).code === 'ENOENT') return
console.warn('[seeder] pear.multisig.json:', e?.message || e)
}
}