This commit is contained in:
Raven Scott
2026-04-03 23:42:47 -04:00
parent d8e580d1af
commit 2ef56ac314
43 changed files with 1941 additions and 553 deletions
+11 -2
View File
@@ -30,6 +30,8 @@ pear run pear://0.4955.1ycyj65xxgn6c5cirhtnm8aokkrpe9dzibriszyfkmfggc3opjzy
pear run pear://sodbfkpk16rwowq5b9eigdgi57xh9ofh98mj6sg5rhhnykt1haho pear run pear://sodbfkpk16rwowq5b9eigdgi57xh9ofh98mj6sg5rhhnykt1haho
``` ```
The booter entrypoint avoids **static** **`node:…`** imports (Bare cannot resolve them). Hashing uses **`bare-crypto`** (`createHash`); **`fs`** / **`path`** / **`url`** go through **`package.json` `imports`** maps to **`bare-fs`**, **`bare-path`**, **`bare-url`**. Dynamic **`import('node:readline')`** / **`import('node:console')`** stay in try/catch fallbacks only. Signed boot manifest verification uses **`#bare-os-boot-manifest-sig`**: under the **`bare`** condition Pear loads **`bare-os-boot-manifest-sig.bare.js`** plus a small **`.cjs`** bridge (no **`node:module`**); Node / **`brittle-node`** resolves **`default`** to **`bare-os-boot-manifest-sig.node.js`** with lazy **`require`** + **`createRequire`**.
## Re-staging after code changes ## Re-staging after code changes
From the package directory: From the package directory:
@@ -48,15 +50,20 @@ pear release bare-os-booter
## Booter host env (Pear / shell) ## Booter host env (Pear / shell)
The booter copies many **`BARE_OS_*`** keys into the session (see [developer-guide/02-the-context-object.md](developer-guide/02-the-context-object.md)). Notable additions for Pear hosts wiring **OTA** or **HTTP**: The booter copies many **`BARE_OS_*`** keys into the session (see [developer-guide/02-the-context-object.md](developer-guide/02-the-context-object.md) and [docs/reference/environment-and-posix-appendix.md](docs/reference/environment-and-posix-appendix.md)). Notable additions for Pear hosts wiring **OTA** or **HTTP**:
- **`BARE_OS_PEAR_CHANNEL`**, **`BARE_OS_PEAR_RELEASE`**, **`PEAR_CHANNEL`** — mirrored into **`/run/bare-os/boot.json`**; use with **`ctx.bareOsRequestPearReload()`** hints and **`pear-runtime-updater`** on the host. - **`BARE_OS_PEAR_CHANNEL`**, **`BARE_OS_PEAR_RELEASE`**, **`PEAR_CHANNEL`** — mirrored into **`/run/bare-os/boot.json`**; pair with **`ctx.bareOsRequestPearReload(opts?)`** (async **`{ requested, hint, env }`**). With **`{ persistRequest: true }`**, the booter writes **`~/.bare-os/pear-reload.request`** and may emit **`process.emit('bare-os:pear-reload', …)`** on Node so a **`pear-runtime-updater`**-style host can swap the runtime without blocking the guest.
- **`BARE_OS_HTTP_ALLOWLIST`**, **`BARE_OS_HTTP_DENYLIST`** — restrict delegated **`curl`/`wget`** when global **`fetch`** exists. - **`BARE_OS_HTTP_ALLOWLIST`**, **`BARE_OS_HTTP_DENYLIST`** — restrict delegated **`curl`/`wget`** when global **`fetch`** exists.
- **`BARE_OS_TLS_PIN_SHA256`** — forwarded as **`init.bareOsCurlTls.pinnedSha256`** for host **`httpFetch`** implementations that implement pinning. - **`BARE_OS_TLS_PIN_SHA256`** — forwarded as **`init.bareOsCurlTls.pinnedSha256`** for host **`httpFetch`** implementations that implement pinning.
- **`BARE_OS_BARE_MODULES`**, **`BARE_OS_BARE_DRIVE_BUNDLES`** — control **`ctx.bare`** (host dynamic imports + optional trusted bundles from **`/lib/bare/`** on the system drive). See **`man 7 bare-os-ctx-bare`** (after seeding) and [developer-guide/12-bare-modules-and-pear-ecosystem.md](developer-guide/12-bare-modules-and-pear-ecosystem.md). - **`BARE_OS_BARE_MODULES`**, **`BARE_OS_BARE_DRIVE_BUNDLES`** — control **`ctx.bare`** (host dynamic imports + optional trusted bundles from **`/lib/bare/`** on the system drive). See **`man 7 bare-os-ctx-bare`** (after seeding) and [developer-guide/12-bare-modules-and-pear-ecosystem.md](developer-guide/12-bare-modules-and-pear-ecosystem.md).
- **`ctx.bareOsRequestMirror({ key?, label? })`**, **`ctx.bareOsExportPersonalSnapshot({ label? })`**, **`ctx.bareOsPearIpcEmit(channel, payload)`** — policy-gated host bridges (return hints / boolean success); on Node the booter emits **`bare-os:mirror-request`**, **`bare-os:export-personal-snapshot`**, and **`bare-os:pear-ipc`** for the embedding app to attach real mirror keys, run **corestore-snapshot**-style export, or forward **pear-ipc**-shaped payloads to the host UI.
Re-seed **`/lib/bare`** after changing the manifest or bundle list: **`npm run build -w bare-os-bare-libs`** (also runs from the seeder when started via `node` / `file:` URL). Re-seed **`/lib/bare`** after changing the manifest or bundle list: **`npm run build -w bare-os-bare-libs`** (also runs from the seeder when started via `node` / `file:` URL).
### pear-ipc alignment (embedded Pear)
Guest code should treat **`ctx.bareOsPearIpcEmit`** as a **thin forwarder**: the host decides how messages map to **[pear-ipc](https://github.com/holepunchto/pear-ipc)** (or your channel bridge). Use a small, versioned **`payload`** shape per **`channel`** (e.g. **`{ v: 1, type: '…', … }`**) and document both sides in your app; Bare OS does not ship a second IPC stack inside the image for this—duplex **`bareOsIpc`** remains the in-session FIFO/RPC surface (see **`duplexJsonRoundTrip`** in [developer-guide/02-the-context-object.md](developer-guide/02-the-context-object.md)).
## Pear / `import.meta.url` (fixed in tree) ## Pear / `import.meta.url` (fixed in tree)
Under `pear run`, `import.meta.url` is `pear://…`. Node-style `fileURLToPath(import.meta.url)` goes through `bare-url` and throws `INVALID_URL_SCHEME`. This repo follows the same idea as [pear-rti](https://github.com/holepunchto/pear-rti) (`holepunchto_repos/pear-rti/index.js`): use `global.Pear.constructor.RTI.mount`, then `Pear.config.swapDir`, then `process.cwd()`. Under `pear run`, `import.meta.url` is `pear://…`. Node-style `fileURLToPath(import.meta.url)` goes through `bare-url` and throws `INVALID_URL_SCHEME`. This repo follows the same idea as [pear-rti](https://github.com/holepunchto/pear-rti) (`holepunchto_repos/pear-rti/index.js`): use `global.Pear.constructor.RTI.mount`, then `Pear.config.swapDir`, then `process.cwd()`.
@@ -72,6 +79,8 @@ Kernel for Pear is vendored at `packages/bare-os-seeder/kernel/` (sync from repo
Hoisted deps live at the repo root. For `pear stage` to include runtime modules, use a symlink `packages/bare-os-seeder/node_modules``../../node_modules` and a **real directory copy** of `packages/bare-os-protocol` at `node_modules/bare-os-protocol` (workspace symlinks outside the package are not bundled correctly). Re-stage after dependency or path helper changes. Hoisted deps live at the repo root. For `pear stage` to include runtime modules, use a symlink `packages/bare-os-seeder/node_modules``../../node_modules` and a **real directory copy** of `packages/bare-os-protocol` at `node_modules/bare-os-protocol` (workspace symlinks outside the package are not bundled correctly). Re-stage after dependency or path helper changes.
**Bare/Pear runtime:** seeder sources must not import **`node:…`** specifiers (e.g. **`node:child_process`** is unresolved in Bare). Use **`bare-subprocess`** for sync spawn (see [`packages/bare-os-seeder/lib/pear-multisig-hint.js`](packages/bare-os-seeder/lib/pear-multisig-hint.js) and **`BARE_OS_HYPER_MULTISIG_VERIFY`**) and prefer **`import … from 'url'`** over **`node:url`** so `package.json` **`imports`** maps resolve to **`bare-url`**.
## `pear run` vs `pear release` ## `pear run` vs `pear release`
After `pear stage`, run `pear release <channel>` so `pear run pear://<key>` resolves to the new length. After `pear stage`, run `pear release <channel>` so `pear run pear://<key>` resolves to the new length.
+8 -3
View File
@@ -23,7 +23,7 @@ The following are set on `ctx` before the kernel starts (unless noted as overwri
| Field | Role | | Field | Role |
| -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`bareOsCtxApiVersion`** | String semver for the documented **`ctx`** contract (e.g. **`1.7.1`**). Bump in [`bare-os-ctx-api.js`](../packages/bare-os-booter/lib/bare-os-ctx-api.js) when you make breaking changes to stable fields. | | **`bareOsCtxApiVersion`** | String semver for the documented **`ctx`** contract (e.g. **`1.9.0`**). Bump in [`bare-os-ctx-api.js`](../packages/bare-os-booter/lib/bare-os-ctx-api.js) when you make breaking changes to stable fields. |
| **`bareOsRuntimeCaps`** | **Frozen** snapshot from [`bare-os-runtime-caps.js`](../packages/bare-os-booter/lib/bare-os-runtime-caps.js): **`ctxApiVersion`**, simulated **pipeline** limits, **`pseudoFsPaths`**, and **`features`** (including **`jobControl`**, **`shellHereString`**, **`bootReadyPseudoFs`**, **`vfsChown`**, **`auditLog`**, **`initdRequiresWants`**, **`seederRpcExtended`**, **`bareCtxModules`**, **`bareDriveBundles`**, …). | | **`bareOsRuntimeCaps`** | **Frozen** snapshot from [`bare-os-runtime-caps.js`](../packages/bare-os-booter/lib/bare-os-runtime-caps.js): **`ctxApiVersion`**, simulated **pipeline** limits, **`pseudoFsPaths`**, and **`features`** (including **`jobControl`**, **`shellHereString`**, **`bootReadyPseudoFs`**, **`vfsChown`**, **`auditLog`**, **`initdRequiresWants`**, **`seederRpcExtended`**, **`bareCtxModules`**, **`bareDriveBundles`**, …). |
| **`bareOsPublishBootReady(patch)`** | Kernel-only: merge **`patch`** into the session boot-ready state exposed as **`/run/bare-os/boot.json`** and **`/run/bare-os/ready`**. The booter pre-seeds **`imageDigest`**, **`pearChannel`**, **`pearRelease`** from host env when set (see **`BARE_OS_IMAGE_DIGEST`**, **`BARE_OS_PEAR_CHANNEL`**, **`BARE_OS_PEAR_RELEASE`**, **`PEAR_CHANNEL`**). | | **`bareOsPublishBootReady(patch)`** | Kernel-only: merge **`patch`** into the session boot-ready state exposed as **`/run/bare-os/boot.json`** and **`/run/bare-os/ready`**. The booter pre-seeds **`imageDigest`**, **`pearChannel`**, **`pearRelease`** from host env when set (see **`BARE_OS_IMAGE_DIGEST`**, **`BARE_OS_PEAR_CHANNEL`**, **`BARE_OS_PEAR_RELEASE`**, **`PEAR_CHANNEL`**). |
| **`bareOsSessionStats`** | Mutable **`{ execLineCount, pipelineBytesTotal }`** mirrored in **`/proc/bare_os_session_stats`**. | | **`bareOsSessionStats`** | Mutable **`{ execLineCount, pipelineBytesTotal }`** mirrored in **`/proc/bare_os_session_stats`**. |
@@ -58,12 +58,17 @@ The following are set on `ctx` before the kernel starts (unless noted as overwri
| **`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. | | **`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. | | **`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. | | **`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. |
| **`bareOsVerifyBootManifestSignature(manifestBytes, signatureBytes, publicKeyHex?)`** | Ed25519 verify helper used when **`BARE_OS_BOOT_MANIFEST_SIGN=1`**; public key from arg or **`BARE_OS_BOOT_MANIFEST_PUBKEY_HEX`**. |
| **`bareOsRequestMirror(opts?)`** / **`bareOsExportPersonalSnapshot(opts?)`** | **`async`** host bridges returning **`{ ok, hint }`**; on Node emit **`bare-os:mirror-request`** / **`bare-os:export-personal-snapshot`**. |
| **`bareOsPearIpcEmit(channel, payload)`** | **`boolean`** — forwards structured **`payload`** to the host when registered (**`bare-os:pear-ipc`** on Node). Align channel names with your **[pear-ipc](https://github.com/holepunchto/pear-ipc)** consumer. |
| **`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. |
Kernel boot composition lives on the **system image** (`/boot/init.js`, `/etc/bare-os/rc`, `/etc/bare-os/rc.d/`, optional **`/etc/bare-os/rc.local`**, optional **`/etc/bare-os/kernel.d/`** (same digit-prefix rules as **`rc.d`**), optional **`/etc/bare-os/profile`** / **`rc.profile.*`**, **`/etc/bare-os/onboot`**), not on `ctx`—extend the image or hooks like **`registerKernelShutdownHook`** rather than adding boot fields to the context object. The booter seeds **`ctx.env`** from the host for **`BARE_OS_PIPELINE_*`**, **`BARE_OS_BOOT_PROFILE`**, **`BARE_OS_ONBOOT`**, **`BARE_OS_BOOT_STRICT`**, **`BARE_OS_RC_D_SKIP`**, **`BARE_OS_BOOT_MINIMAL`**, **`BARE_OS_BOOT_SKIP`**, **`BARE_OS_BOOT_TRACE`**, **`BARE_OS_KERNEL_SELFTEST`**, **`BARE_OS_SELFTEST_FORMAT`**, **`BARE_OS_AUDIT`**, **`BARE_OS_AUDIT_JSON`**, **`BARE_OS_AUDIT_REDACT`**, **`BARE_OS_BOOT_ALLOWLIST`**, **`BARE_OS_EXEC_MAX_DEPTH`**, **`BARE_OS_IPC_MAX_BYTES`**, **`BARE_OS_IPC_RPC_TOKEN`**, **`BARE_OS_IPC_FANOUT`** (set to **`0`** to disable fan-out), **`BARE_OS_IPC_JSON_MAX_BYTES`**, **`BARE_OS_HTTP_ALLOWLIST`**, **`BARE_OS_HTTP_DENYLIST`**, **`BARE_OS_TLS_PIN_SHA256`** (comma-separated SHA-256 hints for delegated TLS; host **`httpFetch`** may honor **`init.bareOsCurlTls.pinnedSha256`**), **`BARE_OS_VFS_WATCH`**, **`BARE_OS_IMAGE_DIGEST`**, **`BARE_OS_PEAR_CHANNEL`**, **`BARE_OS_PEAR_RELEASE`**, **`BARE_OS_BARE_MODULES`**, **`BARE_OS_BARE_DRIVE_BUNDLES`**, **`BARE_OS_FIND_EXEC_MAX`**, **`BARE_OS_YES_MAX_LINES`**, **`BARE_OS_SHUF_MAX_LINES`**, **`BARE_OS_SPLIT_MAX_FILES`**, **`BARE_OS_NPROC`**, **`TERM`**, **`COLORTERM`**, and **`PEAR_CHANNEL`** when set; always sets **`BARE_OS_BOOT_PROFILE_RESOLVED`** and **`BARE_OS_SESSION_ID`**. When **`ctx.httpFetch`** handles **`curl`**, check optional **`init.bareOsCurlTls`** (`insecure`, **`caPem`**, **`pinnedSha256`**) for **`--cacert`** / **`-k`** semantics. Kernel boot composition lives on the **system image** (`/boot/init.js`, `/etc/bare-os/rc`, `/etc/bare-os/rc.d/`, optional **`/etc/bare-os/rc.local`**, optional **`/etc/bare-os/kernel.d/`** (same digit-prefix rules as **`rc.d`**), optional **`/etc/bare-os/profile`** / **`rc.profile.*`**, **`/etc/bare-os/onboot`**), not on `ctx`—extend the image or hooks like **`registerKernelShutdownHook`** rather than adding boot fields to the context object. The booter seeds **`ctx.env`** from the host for **`BARE_OS_PIPELINE_*`**, boot profile / audit / IPC / HTTP policy keys, **`BARE_OS_VFS_WATCH`**, **`BARE_OS_VFS_UNION_PREFIXES`**, **`BARE_OS_VFS_BIN_CACHE`**, **`BARE_OS_IMAGE_DIGEST`**, Pear channel fields, **`BARE_OS_BOOT_MANIFEST`**, **`BARE_OS_BOOT_MANIFEST_SIGN`**, **`BARE_OS_BOOT_MANIFEST_PUBKEY_HEX`**, **`BARE_OS_SANDBOX_SCRIPT`**, **`BARE_OS_SANDBOX_WORKER`**, **`BARE_OS_INITD_MAX_PARALLEL`**, **`BARE_OS_URANDOM_CRYPTO`**, **`BARE_OS_BLIND_BOOTSTRAP_URL`**, **`BARE_OS_BLIND_BOOTSTRAP_JSON`**, **`BARE_OS_MIRROR_READ_KEY`**, **`BARE_OS_FIND_EXEC_MAX`**, **`BARE_OS_YES_MAX_LINES`**, **`BARE_OS_SHUF_MAX_LINES`**, **`BARE_OS_SPLIT_MAX_FILES`**, **`BARE_OS_NPROC`**, **`TERM`**, **`COLORTERM`**, **`PEAR_CHANNEL`**, and the rest of the passthrough table in [environment appendix §14](../docs/reference/environment-and-posix-appendix.md#14-environment-variables-complete-list); always sets **`BARE_OS_BOOT_PROFILE_RESOLVED`** and **`BARE_OS_SESSION_ID`**. When **`ctx.httpFetch`** handles **`curl`**, check optional **`init.bareOsCurlTls`** (`insecure`, **`caPem`**, **`pinnedSha256`**) for **`--cacert`** / **`-k`** semantics.
After [`createVfs`](../packages/bare-os-booter/lib/vfs.js), **`ctx.vfs.watch(logicalPath)`** returns a Hyperdrive watcher when **`BARE_OS_VFS_WATCH`** is not disabled. **`ctx.bareOsIpc`** exposes FIFO **`push`/`take`**, optional JSON-RPC **`pushJson`/`takeJson`** (max line size, optional RPC token), **`fanoutPublish`/`fanoutSubscribe`**, and **`stats`** (see [`bare-os-ipc.js`](../packages/bare-os-booter/lib/bare-os-ipc.js)). After [`createVfs`](../packages/bare-os-booter/lib/vfs.js), **`ctx.vfs.watch(logicalPath)`** returns a Hyperdrive watcher when **`BARE_OS_VFS_WATCH`** is not disabled. **`ctx.bareOsIpc`** exposes FIFO **`push`/`take`**, optional JSON-RPC **`pushJson`/`takeJson`** (max line size, optional RPC token), **`fanoutPublish`/`fanoutSubscribe`**, **`createDuplexBridge`**, **`duplexJsonRoundTrip`** (one JSON request / one JSON reply over a duplex side — useful for unit-to-unit or guesthelper protocols without pulling **`bare-rpc`** into **`/bin`**), and **`stats`** (see [`bare-os-ipc.js`](../packages/bare-os-booter/lib/bare-os-ipc.js)).
**Initd / long-running services:** prefer **`duplexJsonRoundTrip`** or **`pushJson`/`takeJson`** for structured messages with byte limits already enforced by IPC options. A dedicated **`bare-rpc`** dependency is optional on the host or in **`ctx.bare`** if you need richer framing; the stock image documents the FIFO-level building blocks only.
After [`createKernelReplSession`](../packages/bare-os-booter/lib/repl-session.js) returns: After [`createKernelReplSession`](../packages/bare-os-booter/lib/repl-session.js) returns:
+3 -1
View File
@@ -12,13 +12,15 @@ The **system** Hyperdrive is the **OS image**. You normally obtain it by **repli
**Boot snippet tightening:** with **`BARE_OS_BOOT_ALLOWLIST=1`**, the stock kernel only runs lines from trusted **`rc`**, **`rc.d`**, **`rc.local`**, **`kernel.d`**, and **`onboot`** whose first shell token appears in **`/etc/bare-os/boot.allow`** (plus builtins such as **`export`**, **`cd`**, **`:`**). Pair with **`BARE_OS_BOOT_STRICT=1`** to exit the session on the first disallowed or failing line. **Boot snippet tightening:** with **`BARE_OS_BOOT_ALLOWLIST=1`**, the stock kernel only runs lines from trusted **`rc`**, **`rc.d`**, **`rc.local`**, **`kernel.d`**, and **`onboot`** whose first shell token appears in **`/etc/bare-os/boot.allow`** (plus builtins such as **`export`**, **`cd`**, **`:`**). Pair with **`BARE_OS_BOOT_STRICT=1`** to exit the session on the first disallowed or failing line.
**Boot manifest integrity:** **`BARE_OS_BOOT_MANIFEST=1`** makes the stock kernel compare **`/etc/bare-os/boot.manifest.json`** to an expected digest (**`ctx.bareOsBootFileSha256Hex`**). **`BARE_OS_BOOT_MANIFEST_SIGN=1`** adds **Ed25519** verification of the **raw manifest bytes** against **`/etc/bare-os/boot.manifest.sig`**, using **`ctx.bareOsVerifyBootManifestSignature`** and **`BARE_OS_BOOT_MANIFEST_PUBKEY_HEX`** (64 hex chars). Rotation is operational: ship a new public key in host env and a matching signature file on the system image. This does **not** replace image trust—you still choose peers and seed sources carefully.
**Audit:** **`BARE_OS_AUDIT=1`** logs **`execLine`** activity to **`/var/log/bare-os/audit.log`**. **`BARE_OS_AUDIT_JSON=1`** uses JSON lines; **`BARE_OS_AUDIT_REDACT=1`** (or **`true`**) masks common secret-like **`VAR=value`** patterns; **`BARE_OS_AUDIT_REDACT=TOKEN,PASSWORD`** masks named keys. **Audit:** **`BARE_OS_AUDIT=1`** logs **`execLine`** activity to **`/var/log/bare-os/audit.log`**. **`BARE_OS_AUDIT_JSON=1`** uses JSON lines; **`BARE_OS_AUDIT_REDACT=1`** (or **`true`**) masks common secret-like **`VAR=value`** patterns; **`BARE_OS_AUDIT_REDACT=TOKEN,PASSWORD`** masks named keys.
**Delegated HTTP:** when the booter sets **`ctx.httpFetch`** from the host **`fetch`**, **`BARE_OS_HTTP_ALLOWLIST`** and **`BARE_OS_HTTP_DENYLIST`** restrict **`http`/`https`** URLs for **`curl`/`wget`** (host-pattern globs). Failed checks throw before the request; with audit on, allow/deny outcomes can be logged. **Delegated HTTP:** when the booter sets **`ctx.httpFetch`** from the host **`fetch`**, **`BARE_OS_HTTP_ALLOWLIST`** and **`BARE_OS_HTTP_DENYLIST`** restrict **`http`/`https`** URLs for **`curl`/`wget`** (host-pattern globs). Failed checks throw before the request; with audit on, allow/deny outcomes can be logged.
**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).
**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. **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`**. **`BARE_OS_SANDBOX_WORKER=1`** records intent for a future worker-style isolate; the default path still runs **inline** in the session. This is still **not** a hardware isolate—treat it as a trust reducer, not a security boundary.
--- ---
@@ -18,7 +18,27 @@ Former **DOCUMENTATION.md** §14, §14a. [Reference index →](README.md)
| `MANWIDTH` | `/bin/man` | Wrap width for manual text (default `72`; minimum `40`) | | `MANWIDTH` | `/bin/man` | Wrap width for manual text (default `72`; minimum `40`) |
| `NO_COLOR` | `/bin/man` | If set, disable ANSI bold for section headings on a TTY | | `NO_COLOR` | `/bin/man` | If set, disable ANSI bold for section headings on a TTY |
**Host → session passthrough** (booter copies into **`shellEnv`** when the host sets a non-empty value): `BARE_OS_PIPELINE_MAX_STAGES`, `BARE_OS_PIPELINE_MAX_BYTES`, `BARE_OS_PIPELINE_MAX_LINES`, `BARE_OS_BOOT_PROFILE`, `BARE_OS_ONBOOT`, `BARE_OS_BOOT_STRICT`, `BARE_OS_RC_D_SKIP`, `BARE_OS_BOOT_MINIMAL`, `BARE_OS_BOOT_SKIP`, `BARE_OS_BOOT_TRACE`, `BARE_OS_KERNEL_SELFTEST`, `BARE_OS_SELFTEST_FORMAT`, `BARE_OS_AUDIT`, `BARE_OS_AUDIT_JSON`, `BARE_OS_AUDIT_REDACT`, `BARE_OS_IMAGE_DIGEST`, `BARE_OS_EXEC_MAX_DEPTH`, `BARE_OS_IPC_MAX_BYTES`, `BARE_OS_VFS_WATCH`, `BARE_OS_BOOT_ALLOWLIST`, `BARE_OS_PEAR_CHANNEL`, `BARE_OS_PEAR_RELEASE`, `PEAR_CHANNEL`, **`BARE_OS_FIND_EXEC_MAX`** (cap for **`find -exec`/`-ok`**), **`BARE_OS_YES_MAX_LINES`**, **`BARE_OS_SHUF_MAX_LINES`**, **`BARE_OS_SPLIT_MAX_FILES`**, **`BARE_OS_NPROC`** (override for **`/bin/nproc`**), **`TERM`**, **`COLORTERM`** (terminal capability hints for colorized tools). **Host → session passthrough** (booter copies into **`shellEnv`** when the host sets a non-empty value): `BARE_OS_PIPELINE_MAX_STAGES`, `BARE_OS_PIPELINE_MAX_BYTES`, `BARE_OS_PIPELINE_MAX_LINES`, `BARE_OS_BOOT_PROFILE`, `BARE_OS_ONBOOT`, `BARE_OS_BOOT_STRICT`, `BARE_OS_RC_D_SKIP`, `BARE_OS_BOOT_MINIMAL`, `BARE_OS_BOOT_SKIP`, `BARE_OS_BOOT_TRACE`, `BARE_OS_KERNEL_SELFTEST`, `BARE_OS_SELFTEST_FORMAT`, `BARE_OS_AUDIT`, `BARE_OS_AUDIT_JSON`, `BARE_OS_AUDIT_REDACT`, `BARE_OS_IMAGE_DIGEST`, `BARE_OS_EXEC_MAX_DEPTH`, `BARE_OS_IPC_MAX_BYTES`, `BARE_OS_IPC_RPC_TOKEN`, `BARE_OS_IPC_FANOUT`, `BARE_OS_IPC_JSON_MAX_BYTES`, `BARE_OS_VFS_WATCH`, `BARE_OS_VFS_UNION_PREFIXES`, `BARE_OS_VFS_BIN_CACHE`, `BARE_OS_BOOT_ALLOWLIST`, `BARE_OS_BOOT_MANIFEST`, `BARE_OS_BOOT_MANIFEST_SIGN`, `BARE_OS_BOOT_MANIFEST_PUBKEY_HEX`, `BARE_OS_SANDBOX_SCRIPT`, `BARE_OS_SANDBOX_WORKER`, `BARE_OS_INITD_MAX_PARALLEL`, `BARE_OS_URANDOM_CRYPTO`, `BARE_OS_HTTP_ALLOWLIST`, `BARE_OS_HTTP_DENYLIST`, `BARE_OS_TLS_PIN_SHA256`, `BARE_OS_BARE_MODULES`, `BARE_OS_BARE_DRIVE_BUNDLES`, `BARE_OS_PEAR_CHANNEL`, `BARE_OS_PEAR_RELEASE`, `PEAR_CHANNEL`, `BARE_OS_FIND_EXEC_MAX`, `BARE_OS_YES_MAX_LINES`, `BARE_OS_SHUF_MAX_LINES`, `BARE_OS_SPLIT_MAX_FILES`, `BARE_OS_NPROC`, `BARE_OS_BLIND_BOOTSTRAP_URL`, `BARE_OS_BLIND_BOOTSTRAP_JSON`, `BARE_OS_MIRROR_READ_KEY`, **`TERM`**, **`COLORTERM`**.
| Variable | Used by | Meaning |
| -------- | ------- | ------- |
| `BARE_OS_VFS_UNION_PREFIXES` | Booter / VFS | Comma-separated logical path prefixes where **`readFile`** may union system + overlay sources. |
| `BARE_OS_VFS_BIN_CACHE` | VFS | When **`1`** / **`true`**, LRU read cache for **`/bin`**; invalidated on **`vfs.watch`** when enabled. |
| `BARE_OS_BOOT_MANIFEST` | Stock kernel | Enable digest check of **`/etc/bare-os/boot.manifest.json`**. |
| `BARE_OS_BOOT_MANIFEST_SIGN` | Stock kernel + booter | When **`1`**, require valid Ed25519 signature file **`/etc/bare-os/boot.manifest.sig`** (see **`BARE_OS_BOOT_MANIFEST_PUBKEY_HEX`**). |
| `BARE_OS_BOOT_MANIFEST_PUBKEY_HEX` | Booter / kernel | 64 hex chars — public key for signed boot manifest verification. |
| `BARE_OS_SANDBOX_SCRIPT` | `ctx.bareOsSandboxRunScript` | Set **`0`** / **`false`** to disable sandbox script helper. |
| `BARE_OS_SANDBOX_WORKER` | Booter | When **`1`**, logs intent to use worker-style isolation when a host entrypoint exists (inline fallback today). |
| `BARE_OS_INITD_MAX_PARALLEL` | bare-initd | Max concurrent unit **starts** per DAG level (integer ≥ **1**; default **1**). |
| `BARE_OS_URANDOM_CRYPTO` | VFS **`/dev/urandom`** | Set **`0`** to use non-crypto PRNG for synthetic urandom (default prefers crypto where available). |
| `BARE_OS_BLIND_BOOTSTRAP_URL` | `/proc/bare_os_bootstrap` | Non-empty URL string echoed into the proc JSON for host/operator wiring (guest does not fetch it). |
| `BARE_OS_BLIND_BOOTSTRAP_JSON` | `/proc/bare_os_bootstrap` | Inline JSON string parsed and exposed under **`blindBootstrap`** (invalid JSON yields an error object in the proc file). |
| `BARE_OS_MIRROR_READ_KEY` | Replication proc / mirror hint | Optional mirror drive key string included in replication snapshot for operators. |
| `BARE_OS_SHELL_LOOP_MAX` | Shell | Max iterations for **`while`** / **`for`** (default **10000**). |
| `BARE_OS_SHELL_CASE_MAX_BRANCHES` | Shell | Max **`case`** branches (default **32**). |
| `BARE_OS_GREP_FILTER_MAX` | **`grep -r`** | Max **`--include` / `--exclude` / `--exclude-dir`** glob patterns (default **32**). |
| `BARE_OS_SED_NULL_MAX_RECORDS` | **`sed -z`** | Max NUL-separated records per run (default **100000**). |
| `BARE_OS_HYPER_MULTISIG_VERIFY` | Seeder | When set, runs **`hyper-multisig verify`** via **[`bare-subprocess`](https://github.com/holepunchto/bare-subprocess)** under Pear/Bare (dependency of **`bare-os-seeder`**), or **`child_process`** on Node. Requires **`hyper-multisig`** on **`PATH`**. |
**Session env (set by booter, not user configuration):** `USER`, `LOGNAME`, `HOME`, `PWD`, `UID`, `GID`, `GROUP`, `BARE_OS_IDENTITY` (`guest` or `unlocked`), `BARE_OS_CTX_API_VERSION`, `BARE_OS_SESSION_ID`, `BARE_OS_BOOT_PROFILE_RESOLVED`, and when unlocked `BARE_OS_PUBLIC_KEY` (hex Ed25519 public key). **Session env (set by booter, not user configuration):** `USER`, `LOGNAME`, `HOME`, `PWD`, `UID`, `GID`, `GROUP`, `BARE_OS_IDENTITY` (`guest` or `unlocked`), `BARE_OS_CTX_API_VERSION`, `BARE_OS_SESSION_ID`, `BARE_OS_BOOT_PROFILE_RESOLVED`, and when unlocked `BARE_OS_PUBLIC_KEY` (hex Ed25519 public key).
@@ -40,9 +60,9 @@ Former **DOCUMENTATION.md** §14, §14a. [Reference index →](README.md)
| Area | Status | | Area | Status |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **VFS** | Two-drive unified paths; **`$HOME`** maps to the personal Hyperdrive; writable mounts under **`/mnt`** when HDMS allows. **`mkdir`/`rmdir`**, **`chmod`** (octal + symbolic subset), **`symlink`/`readlink`**, **`stat`/`lstat`**, **`rm`** recursive, **`watch()`** on Hyperdrive paths (optional host **`BARE_OS_VFS_WATCH=0`** to disable). Synthetic **`/proc`**, **`/sys`**, **`/run`**, **`/dev`** for introspection (quotas JSON, boot JSON, initd snapshot, etc.). Empty dirs use **`.bareos_empty`** (same idea as `git-fs-adapter`). | | **VFS** | Two-drive unified paths; **`$HOME`** maps to the personal Hyperdrive; writable mounts under **`/mnt`** when HDMS allows. **`mkdir`/`rmdir`**, **`chmod`** (octal + symbolic subset), **`symlink`/`readlink`**, **`stat`/`lstat`**, **`rm`** recursive, **`watch()`** on Hyperdrive paths (optional host **`BARE_OS_VFS_WATCH=0`** to disable). Synthetic **`/proc`**, **`/sys`**, **`/run`**, **`/dev`** for introspection (quotas JSON, boot JSON, initd snapshot, etc.). Empty dirs use **`.bareos_empty`** (same idea as `git-fs-adapter`). |
| **Shell** | Pipelines (pipe between commands), list separator **`;`**, short-circuit logical-**AND** / logical-**OR** between commands, redirects **`>`** / **`>>`** / **`<`**, quoting, **`$VAR`** / **`${VAR}`**, **`$?`** / **`${?}`** (from **`BARE_OS_EXIT_STATUS`**), builtins: **`alias`**, **`unalias`**, **`cd`**, **`export`**, **`unset`**, **`readonly`**, **`umask`**, **`:`**, **`command`**, **`type`**, **`login`**, **`logout`**, **`exit`**. Branching uses **`ctx.exitCode`**. Bounded pipeline capture; optional boot-snippet allowlist (**`BARE_OS_BOOT_ALLOWLIST`** + **`/etc/bare-os/boot.allow`**). No full POSIX **`sh`** grammar. | | **Shell** | Pipelines, **`;`**, **`&&`** / **`||`**, redirects, quoting, **`$VAR`**, **`$?`**, builtins including bounded **`if`**, **`while`**, **`for`**, **`case`** (caps **`BARE_OS_SHELL_LOOP_MAX`**, **`BARE_OS_SHELL_CASE_MAX_BRANCHES`**). Branching uses **`ctx.exitCode`**. Bounded pipeline capture (**`BARE_OS_PIPELINE_*`**, **`ctx.bareOsRuntimeCaps`**); optional boot-snippet allowlist (**`BARE_OS_BOOT_ALLOWLIST`** + **`/etc/bare-os/boot.allow`**). No subshells or full POSIX **`sh`**. |
| **Ownership** | Display and permission checks use **`UID`/`GID`** and mode bits; **`chown`/`chgrp`** update **`metadata.bareOs`** on the **personal** writable tree (not a multi-user host kernel). | | **Ownership** | Display and permission checks use **`UID`/`GID`** and mode bits; **`chown`/`chgrp`** update **`metadata.bareOs`** on the **personal** writable tree (not a multi-user host kernel). |
| **Utilities** | Tier-1 JS **`/bin`** (**~111** commands; see [§12.10](package-bare-os-coreutils-and-ci.md#1210-package-bare-os-coreutils)): text tools include **`paste`**, **`split`**, **`tac`**, **`rev`**, **`expand`**, **`unexpand`**, **`fold`**, **`fmt`**, **`comm`**, **`join`**, **`pr`**, **`yes`** (line-capped via **`BARE_OS_YES_MAX_LINES`** / **`getconf`**), **`shuf`** (capped via **`BARE_OS_SHUF_MAX_LINES`**), **`tsort`**, **`factor`**, **`expr`** (integer-focused subset), **`numfmt`** (**`--to=iec`** / **`--to=si`**). Checksums: **`md5sum`** (bundled MD5), **`sha1sum`**, **`sha256sum`**, **`sha512sum`** (Web Crypto where available), **`sum`**, **`base32`**, **`basenc`** (**`--base16`**). Files: **`truncate`**, **`unlink`**, **`install`**, **`df`** (synthetic Hyperdrive row; **`-h`** human sizes), **`sync`** (no-op). Session stubs: **`arch`**, **`groups`**, **`hostid`**, **`nproc`**, **`uptime`**, **`users`**, **`who`**. Plus earlier parity: **`man`**, **`sed`**, **`awk`**, **`cp`** (**`-u`/`-v`/`-p`**), **`mv`**, **`find`** (**`-regex`**, **`-exec`/`-ok`**, **`BARE_OS_FIND_EXEC_MAX`**), **`mktemp`**, **`git-pear`**, **`cksum`**, **`getconf`** (includes pipeline / cap names + **`-a`**), **`xargs`**, **`dircolors`**, **`theme`**, **`ls`**, **`uniq`**, **`realpath`**, **`base64`**, **`rm`** **`-d`**, **`stat`** **`%F`**. **`dir`** / **`vdir`** call **`ls`**. Large **`sed`/`awk`** are not byte-identical to GNU on all inputs. **`mkfifo`** → **`/run/bare-os/ipc/`**. Online help: **`/share/man/man.json`** and **`man`**. | | **Utilities** | Tier-1 JS **`/bin`** (**~111** commands; see [§12.10](package-bare-os-coreutils-and-ci.md#1210-package-bare-os-coreutils)): text tools include **`paste`**, **`split`**, **`tac`**, **`rev`**, **`expand`**, **`unexpand`**, **`fold`**, **`fmt`**, **`comm`**, **`join`**, **`pr`**, **`yes`** (line-capped via **`BARE_OS_YES_MAX_LINES`** / **`getconf`**), **`shuf`** (capped via **`BARE_OS_SHUF_MAX_LINES`**), **`tsort`**, **`factor`**, **`expr`** (integer-focused subset), **`numfmt`** (**`--to=iec`** / **`--to=si`**). Checksums: **`md5sum`** (bundled MD5), **`sha1sum`**, **`sha256sum`**, **`sha512sum`** (Web Crypto where available), **`sum`**, **`base32`**, **`basenc`** (**`--base16`**). Files: **`truncate`**, **`unlink`**, **`install`**, **`df`** (synthetic Hyperdrive row; **`-h`** human sizes), **`sync`** (no-op). Session stubs: **`arch`**, **`groups`**, **`hostid`**, **`nproc`**, **`uptime`**, **`users`**, **`who`**. Plus earlier parity: **`man`**, **`sed`** (including **`-z`**, **`BARE_OS_SED_NULL_MAX_RECORDS`**), **`awk`**, **`grep`** (**`-r`** with **`--include`/`--exclude`/`--exclude-dir`**, **`BARE_OS_GREP_FILTER_MAX`**), **`cp`** (**`-u`/`-v`/`-p`**), **`mv`**, **`find`** (**`-regex`**, **`-exec`/`-ok`**, **`BARE_OS_FIND_EXEC_MAX`**), **`mktemp`**, **`git-pear`**, **`cksum`**, **`getconf`** (includes pipeline / cap names + **`-a`**), **`xargs`**, **`dircolors`**, **`theme`**, **`ls`**, **`uniq`**, **`realpath`**, **`base64`**, **`rm`** **`-d`**, **`stat`** **`%F`**. **`dir`** / **`vdir`** call **`ls`**. Large **`sed`/`awk`** are not byte-identical to GNU on all inputs. **`mkfifo`** → **`/run/bare-os/ipc/`**. Online help: **`/share/man/man.json`** and **`man`**. |
**Handbook:** [handbook/09-posix-utilities-shell-and-vfs.md](../../handbook/09-posix-utilities-shell-and-vfs.md) — narrative catalog, engine notes, and Issue 7 alignment. **Manual pages:** [handbook/10-manpages-and-online-help.md](../../handbook/10-manpages-and-online-help.md). **Handbook:** [handbook/09-posix-utilities-shell-and-vfs.md](../../handbook/09-posix-utilities-shell-and-vfs.md) — narrative catalog, engine notes, and Issue 7 alignment. **Manual pages:** [handbook/10-manpages-and-online-help.md](../../handbook/10-manpages-and-online-help.md).
+8 -5
View File
@@ -16,8 +16,8 @@ Non-TTY mode skips splash noise; automation uses **`BARE_OS_SKIP_REPL=1`**.
After the **system** and **personal** drives exist: After the **system** and **personal** drives exist:
1. Build **`shellEnv`** (guest defaults: `HOME`, `PATH`, `USER`, …, **`BARE_OS_CTX_API_VERSION`**), copying host keys when set — including **`BARE_OS_PIPELINE_*`**, boot and audit toggles (**`BARE_OS_BOOT_PROFILE`**, **`BARE_OS_ONBOOT`**, **`BARE_OS_BOOT_STRICT`**, **`BARE_OS_RC_D_SKIP`**, **`BARE_OS_BOOT_MINIMAL`**, **`BARE_OS_BOOT_SKIP`**, **`BARE_OS_BOOT_TRACE`**, **`BARE_OS_KERNEL_SELFTEST`**, **`BARE_OS_SELFTEST_FORMAT`**, **`BARE_OS_AUDIT`**, **`BARE_OS_AUDIT_JSON`**, **`BARE_OS_AUDIT_REDACT`**, **`BARE_OS_BOOT_ALLOWLIST`**, **`BARE_OS_EXEC_MAX_DEPTH`**, **`BARE_OS_IPC_MAX_BYTES`**, **`BARE_OS_IPC_RPC_TOKEN`**, **`BARE_OS_IPC_FANOUT`**, **`BARE_OS_IPC_JSON_MAX_BYTES`**, **`BARE_OS_HTTP_ALLOWLIST`**, **`BARE_OS_HTTP_DENYLIST`**, **`BARE_OS_TLS_PIN_SHA256`**, **`BARE_OS_VFS_WATCH`**, **`BARE_OS_IMAGE_DIGEST`**, **`BARE_OS_PEAR_CHANNEL`**, **`BARE_OS_PEAR_RELEASE`**, **`BARE_OS_BARE_MODULES`**, **`BARE_OS_BARE_DRIVE_BUNDLES`**, **`PEAR_CHANNEL`**) — see [Chapter 7](07-operations-and-development.md) and the [context object developer guide](../developer-guide/02-the-context-object.md). Set **`BARE_OS_BOOT_PROFILE_RESOLVED`** from the host override or the first line of **`/etc/bare-os/profile`** on the system drive; assign **`BARE_OS_SESSION_ID`** for **`/run/bare-os/session`**. Seed **`/run/bare-os/boot.json`** fields **`imageDigest`**, **`pearChannel`**, **`pearRelease`**, and accumulate **`booterPhases`** (**`vfs`**, **`ctx`**, **`repl`**, **`initd`**, **`kernel_invoke`**) for observability alongside kernel **`phases`**. 1. Build **`shellEnv`** (guest defaults: `HOME`, `PATH`, `USER`, …, **`BARE_OS_CTX_API_VERSION`**), copying host keys when set — including **`BARE_OS_PIPELINE_*`**, boot and audit toggles (**`BARE_OS_BOOT_PROFILE`**, **`BARE_OS_ONBOOT`**, **`BARE_OS_BOOT_STRICT`**, **`BARE_OS_RC_D_SKIP`**, **`BARE_OS_BOOT_MINIMAL`**, **`BARE_OS_BOOT_SKIP`**, **`BARE_OS_BOOT_TRACE`**, **`BARE_OS_KERNEL_SELFTEST`**, **`BARE_OS_SELFTEST_FORMAT`**, **`BARE_OS_AUDIT`**, **`BARE_OS_AUDIT_JSON`**, **`BARE_OS_AUDIT_REDACT`**, **`BARE_OS_BOOT_ALLOWLIST`**, **`BARE_OS_EXEC_MAX_DEPTH`**, **`BARE_OS_IPC_*`**, **`BARE_OS_HTTP_ALLOWLIST`**, **`BARE_OS_HTTP_DENYLIST`**, **`BARE_OS_TLS_PIN_SHA256`**, **`BARE_OS_VFS_WATCH`**, **`BARE_OS_VFS_UNION_PREFIXES`**, **`BARE_OS_VFS_BIN_CACHE`**, **`BARE_OS_IMAGE_DIGEST`**, **`BARE_OS_PEAR_CHANNEL`**, **`BARE_OS_PEAR_RELEASE`**, **`BARE_OS_BARE_MODULES`**, **`BARE_OS_BARE_DRIVE_BUNDLES`**, **`BARE_OS_BOOT_MANIFEST`**, **`BARE_OS_BOOT_MANIFEST_SIGN`**, **`BARE_OS_BOOT_MANIFEST_PUBKEY_HEX`**, **`BARE_OS_SANDBOX_SCRIPT`**, **`BARE_OS_SANDBOX_WORKER`**, **`BARE_OS_INITD_MAX_PARALLEL`**, **`BARE_OS_URANDOM_CRYPTO`**, **`BARE_OS_BLIND_BOOTSTRAP_URL`**, **`BARE_OS_BLIND_BOOTSTRAP_JSON`**, **`BARE_OS_MIRROR_READ_KEY`**, **`PEAR_CHANNEL`**) — see [Chapter 7](07-operations-and-development.md), the [environment appendix](../docs/reference/environment-and-posix-appendix.md#14-environment-variables-complete-list), and the [context object developer guide](../developer-guide/02-the-context-object.md). Set **`BARE_OS_BOOT_PROFILE_RESOLVED`** from the host override or the first line of **`/etc/bare-os/profile`** on the system drive; assign **`BARE_OS_SESSION_ID`** for **`/run/bare-os/session`**. Seed **`/run/bare-os/boot.json`** fields **`imageDigest`**, **`pearChannel`**, **`pearRelease`**, and accumulate **`booterPhases`** (**`vfs`**, **`ctx`**, **`repl`**, **`initd`**, **`kernel_invoke`**) for observability alongside kernel **`phases`**.
2. **`createBareOsIpc({ maxFifoBytes, ipcRpcToken?, enableFanout?, maxJsonRpcLineBytes? })`** — in-memory FIFOs under **`/run/bare-os/ipc/<name>`**; **`pushJson`/`takeJson`** with optional shared-secret field **`bareOsIpcToken`** when **`BARE_OS_IPC_RPC_TOKEN`** is set; **fan-out** topics **`fanoutPublish`/`fanoutSubscribe`** (disable with **`BARE_OS_IPC_FANOUT=0`**); **`stats`** includes fan-out subscriber counts. 2. **`createBareOsIpc({ maxFifoBytes, ipcRpcToken?, enableFanout?, maxJsonRpcLineBytes? })`** — in-memory FIFOs under **`/run/bare-os/ipc/<name>`**; **`pushJson`/`takeJson`** with optional shared-secret field **`bareOsIpcToken`** when **`BARE_OS_IPC_RPC_TOKEN`** is set; **fan-out** topics **`fanoutPublish`/`fanoutSubscribe`** (disable with **`BARE_OS_IPC_FANOUT=0`**); **`createDuplexBridge(baseName)`** pairs two **`push`/`take`** sides for bidirectional byte streams; **`duplexJsonRoundTrip(side, request)`** sends one JSON object and awaits one JSON reply (bounded); **`stats`** includes fan-out subscriber counts.
3. **`createVfs(drive, personalDrive, shellEnv, mntRef, vfsOptions?)`** — the two-drive router; optional **`vfsOptions`** supply **`procSnapshot`** (**`/proc/version`**, **`cmdline`**), dynamic text for **`/proc/net/dev`**, **`/proc/diskstats`**, **`/proc/bare_os_quotas`**, **`/proc/bare_os_resources`**, **`/proc/bare_os_features`**, **`/sys/class/net/lo`**, **`bootProfileText`**, **`sessionText`**, **`initdRunText`**, **`bootReadyJsonText`**, **`getVirtualReaders`** for **`/run/bare-os/virtual/<name>`**, etc. 3. **`createVfs(drive, personalDrive, shellEnv, mntRef, vfsOptions?)`** — the two-drive router; optional **`vfsOptions`** supply **`procSnapshot`** (**`/proc/version`**, **`cmdline`**), dynamic text for **`/proc/net/dev`**, **`/proc/diskstats`**, **`/proc/bare_os_quotas`**, **`/proc/bare_os_resources`**, **`/proc/bare_os_features`**, **`/sys/class/net/lo`**, **`bootProfileText`**, **`sessionText`**, **`initdRunText`**, **`bootReadyJsonText`**, **`getVirtualReaders`** for **`/run/bare-os/virtual/<name>`**, etc.
4. After VFS: **`buildBareCtxObjectFromHost`** + **`maybeMergeBareFromDrive`** fill a **`bareLibrary`** map (host imports first; drive bundles only add **missing** keys). **`/lib/bare/manifest.json`** and **`/lib/bare/bundles/*.js`** are **trusted** like **`/bin`**. See [developer guide ch.12](../developer-guide/12-bare-modules-and-pear-ecosystem.md). 4. After VFS: **`buildBareCtxObjectFromHost`** + **`maybeMergeBareFromDrive`** fill a **`bareLibrary`** map (host imports first; drive bundles only add **missing** keys). **`/lib/bare/manifest.json`** and **`/lib/bare/bundles/*.js`** are **trusted** like **`/bin`**. See [developer guide ch.12](../developer-guide/12-bare-modules-and-pear-ecosystem.md).
5. Construct **`ctx`**: disks, `vfs`, **`bareOsIpc`**, `env`, `b4a`, optional **`bare: Object.freeze(bareLibrary)`** when **`BARE_OS_BARE_MODULES`** is enabled, `topic`, **`bareOsRuntimeCaps`** (pipeline limits, **`quotas`**, pseudo path list, feature flags such as **`vfsWatch`**, **`ipcRpcJson`**, **`ipcFanout`**, **`initdSocketActivation`**, **`bareCtxModules`**, **`bareDriveBundles`**), optional **`httpFetch`** (policy-wrapped **`fetch`** when the host provides global **`fetch`**), optional **`bareOsHostStats`** from the **`bare-os`** module, **`bareOsGetResourceStatus`**, **`bareOsRegisterVirtualFile`**, Pear/sandbox stubs, identity hooks, **`bareOsSubscribeBootEvent`** / **`bareOsEmitBootEvent`**, **`bareOsSubscribeHdmsLifecycle`**, **`bareOsAwaitInitdUnits`**, **`runHdms`**, **`requestBooterExit`**, **`bareOsPublishBootReady`**, … 5. Construct **`ctx`**: disks, `vfs`, **`bareOsIpc`**, `env`, `b4a`, optional **`bare: Object.freeze(bareLibrary)`** when **`BARE_OS_BARE_MODULES`** is enabled, `topic`, **`bareOsRuntimeCaps`** (pipeline limits, **`quotas`**, pseudo path list, feature flags such as **`vfsWatch`**, **`ipcRpcJson`**, **`ipcFanout`**, **`initdSocketActivation`**, **`bareCtxModules`**, **`bareDriveBundles`**), optional **`httpFetch`** (policy-wrapped **`fetch`** when the host provides global **`fetch`**), optional **`bareOsHostStats`** from the **`bare-os`** module, **`bareOsGetResourceStatus`**, **`bareOsRegisterVirtualFile`**, Pear/sandbox stubs, identity hooks, **`bareOsSubscribeBootEvent`** / **`bareOsEmitBootEvent`**, **`bareOsSubscribeHdmsLifecycle`**, **`bareOsAwaitInitdUnits`**, **`runHdms`**, **`requestBooterExit`**, **`bareOsPublishBootReady`**, …
@@ -45,10 +45,11 @@ Virtual listings include **`/home`** (session-specific), **`/mnt`** when HDMS mo
These paths are **synthetic** (not stored on either Hyperdrive). They exist for inspection and scripting ergonomics, **not** Linux ABI compatibility. These paths are **synthetic** (not stored on either Hyperdrive). They exist for inspection and scripting ergonomics, **not** Linux ABI compatibility.
- **`/proc`**: **`version`**, **`bare_os_version`**, **`uptime`**, **`meminfo`**, **`cpuinfo`**, **`loadavg`**, **`mounts`**, **`diskstats`** (stub text), **`bare_os_quotas`** (JSON: pipeline limits, exec-depth cap, IPC cap, session stats, FIFO **`stats`**), **`bare_os_resources`** (unified operator snapshot), **`bare_os_features`** (protocol/kernel feature bitmask doc), **`net/dev`** (P2P-oriented stub), **`self/`** with **`environ`**, **`cmdline`**, **`exe`**, plus **`bare_os_session_stats`**. **`environ`** omits keys whose names look secret-bearing (e.g. **`PASSWORD`**, **`TOKEN`**, **`VAULT`**) and only includes a small public set plus **`BARE_OS_*`** (including **`BARE_OS_SESSION_ID`**). - **`/proc`**: **`version`**, **`bare_os_version`**, **`uptime`**, **`meminfo`**, **`cpuinfo`**, **`loadavg`**, **`mounts`**, **`diskstats`** (stub text), **`bare_os_quotas`** (JSON: pipeline limits, exec-depth cap, IPC cap, session stats, FIFO **`stats`**), **`bare_os_resources`**, **`bare_os_features`**, **`bare_os_swarm`** (bounded P2P / session snapshot when the host provides it), **`bare_os_replication`** (JSON hints: version, peers, last error when known), **`bare_os_capabilities`** (human-readable dump of **`ctx.bareOsRuntimeCaps`**; **`bare_os_capabilities.json`** or **`Accept: application/json`** for machine output), **`bare_os_bootstrap`** (optional blind-peer style hints: **`BARE_OS_BLIND_BOOTSTRAP_URL`** or parsed **`BARE_OS_BLIND_BOOTSTRAP_JSON`** surfaced as JSON for operators — no automatic network fetch from the guest), **`net/dev`** (P2P-oriented stub), **`self/`** with **`environ`**, **`cmdline`**, **`exe`**, plus **`bare_os_session_stats`**. **`environ`** omits keys whose names look secret-bearing (e.g. **`PASSWORD`**, **`TOKEN`**, **`VAULT`**) and only includes a small public set plus **`BARE_OS_*`** (including **`BARE_OS_SESSION_ID`**).
- **`/sys`**: **`fs/bare_os/version`**, **`fs/bare_os/build_id`**, **`class/net/lo`** (stub **`operstate`** / **`carrier`**). - **`/sys`**: **`fs/bare_os/version`**, **`fs/bare_os/build_id`**, **`class/net/lo`** (stub **`operstate`** / **`carrier`**).
- **`/sys/fs/bare_os/version`**: same text as **`/proc/version`**. - **`/sys/fs/bare_os/version`**: same text as **`/proc/version`**.
- **`/run/bare-os/units`**: tab-separated snapshot of **bare-initd** registered units (phase, start time, description). - **`/run/bare-os/units`**: tab-separated snapshot of **bare-initd** registered units (phase, start time, description).
- **`/run/bare-os/unit-journal/`**: append-only **NDJSON** per unit (**`<name>.ndjson`**) for start/stop/health/restart events (size-capped); **`journalctl -u`** also tails this file when present.
- **`/run/bare-os/boot_profile`**: one line (plus newline): resolved boot profile name (**`BARE_OS_BOOT_PROFILE`** from the host, else first line of **`/etc/bare-os/profile`**, else empty). Listed in **`ctx.bareOsRuntimeCaps.pseudoFsPaths`**. - **`/run/bare-os/boot_profile`**: one line (plus newline): resolved boot profile name (**`BARE_OS_BOOT_PROFILE`** from the host, else first line of **`/etc/bare-os/profile`**, else empty). Listed in **`ctx.bareOsRuntimeCaps.pseudoFsPaths`**.
- **`/run/bare-os/session`**: session UUID (from **`BARE_OS_SESSION_ID`**) plus newline. - **`/run/bare-os/session`**: session UUID (from **`BARE_OS_SESSION_ID`**) plus newline.
- **`/run/bare-os/virtual/`**: optional kernel-registered synthetic files via **`ctx.bareOsRegisterVirtualFile`**. - **`/run/bare-os/virtual/`**: optional kernel-registered synthetic files via **`ctx.bareOsRegisterVirtualFile`**.
@@ -58,6 +59,8 @@ These paths are **synthetic** (not stored on either Hyperdrive). They exist for
**`vfs.watch(logicalPath)`** (Hyperdrive-backed paths only — not pseudo roots): returns **`{ watcher, destroy, logicalAbs, driveFolder }`**; use Hyperdrives async iterator on **`watcher`**. Disabled when host sets **`BARE_OS_VFS_WATCH=0`**. See **`ctx.bareOsRuntimeCaps.features.vfsWatch`**. **`vfs.watch(logicalPath)`** (Hyperdrive-backed paths only — not pseudo roots): returns **`{ watcher, destroy, logicalAbs, driveFolder }`**; use Hyperdrives async iterator on **`watcher`**. Disabled when host sets **`BARE_OS_VFS_WATCH=0`**. See **`ctx.bareOsRuntimeCaps.features.vfsWatch`**.
**Union read:** when **`BARE_OS_VFS_UNION_PREFIXES`** lists comma-separated logical prefixes, **`readFile`** / **`readlink`** on those paths may merge the **system** image with overlays (see **`vfs.js`** and **`ctx.bareOsRuntimeCaps.features.vfsUnionRead`**). **`BARE_OS_VFS_BIN_CACHE=1`** enables a small LRU read cache for **`/bin`** entries, invalidated when **`vfs.watch`** notifications fire on watched prefixes.
**Implementation note:** pseudo-file content is UTF-8 encoded with **`b4a`**, not **`TextEncoder`**, because some Bare/Pear runtimes omit the Web Encoding globals (`TextEncoder` / `TextDecoder`). The same applies elsewhere in the booter and in-image utilities that must run on Bare. **Implementation note:** pseudo-file content is UTF-8 encoded with **`b4a`**, not **`TextEncoder`**, because some Bare/Pear runtimes omit the Web Encoding globals (`TextEncoder` / `TextDecoder`). The same applies elsewhere in the booter and in-image utilities that must run on Bare.
### **`/dev`** and **`/run`** (minimal subset) ### **`/dev`** and **`/run`** (minimal subset)
@@ -88,7 +91,7 @@ Work is sequenced for **POSIX/script ergonomics first**, then networking and lon
**`execShellLine`** (`lib/shell.js`): **`execShellLine`** (`lib/shell.js`):
- Tokenizes words, quotes, escapes, **`$VAR`**, pipelines **`|`**, redirections **`>` / `>>` / `<`**. - Tokenizes words, quotes, escapes, **`$VAR`**, pipelines **`|`**, redirections **`>` / `>>` / `<`**.
- Builtins: **`alias`**, **`unalias`**, **`cd`**, **`export`**, **`unset`**, **`readonly`**, **`umask`**, **`:`**, **`command`**, **`type`**, **`login`**, **`logout`**, **`exit`** — plus external commands via **`runBinCommand`**. **`readonly`** blocks **`export`** and assignment writes to the same name; **`command -v` / `-V`** and **`type`** use **`resolveBinInPath`** for **`PATH`** lookup. - Builtins: **`alias`**, **`unalias`**, **`cd`**, **`export`**, **`unset`**, **`readonly`**, **`umask`**, **`:`**, **`command`**, **`type`**, **`login`**, **`logout`**, **`exit`**, bounded **`if`/`while`/`for`/`case`** — plus external commands via **`runBinCommand`**. **`readonly`** blocks **`export`** and assignment writes to the same name; **`command -v` / `-V`** and **`type`** use **`resolveBinInPath`** for **`PATH`** lookup.
- First-word **aliases** (defaults like **`ll``ls -la`**, **`nano``edit`**) expand after **`$VAR`** substitution; **`alias`** / **`unalias`** match the restricted **`~/.barerc`** syntax (not full POSIX **`sh`**). - First-word **aliases** (defaults like **`ll``ls -la`**, **`nano``edit`**) expand after **`$VAR`** substitution; **`alias`** / **`unalias`** match the restricted **`~/.barerc`** syntax (not full POSIX **`sh`**).
- Pipes capture **`console.log`** into the next stage or a string sink. - Pipes capture **`console.log`** into the next stage or a string sink.
@@ -118,7 +121,7 @@ There is no arbitrary command execution, **`source`**, or control flow — it is
**`bare-initd.js`**: **`bare-initd.js`**:
- **`registerBareService({ name, start, stop?, description?, logPath? })`** — optional **`stop`** enables **`systemctl stop`** / **`restart`** for that unit; **`logPath`** is a logical VFS path for **`systemctl status`** / **`logs`** - **`registerBareService({ name, start, stop?, description?, logPath? })`** — optional **`stop`** enables **`systemctl stop`** / **`restart`** for that unit; **`logPath`** is a logical VFS path for **`systemctl status`** / **`logs`**
- **`startBareInitd(ctx)`** — ensures **`/var/log/bare-os`** (see below), then ordered start (skips units listed in **`~/.config/bare-os/initd/disabled.txt`** on the personal drive; optional **`~/.config/bare-os/units/<name>.unit`** with **`[Unit]`** keys **`After=`**, **`Requires=`**, **`Wants=`**, **`TimeoutStartSec=`**, **`TimeoutStopSec=`**, **`Restart=`**, **`RestartSec=`**, **`ExecStartPost=`**, **`SocketActivationIpc=`** — the last defers **`start()`** until the first read on that logical FIFO name — plus **`ReadinessPath=`** (VFS path that must exist before the unit is marked **`active`**) and **`ReadinessTimeoutSec=`** (poll budget, default 30s)). Default **`bare-cron`** runs after **`kernel-logger`**. Per-service `try/catch`, `[bare-initd] name: err` on failure; failures append to **`/var/log/bare-os/initd.log`**; runtime state **`active`** / **`failed`** / **`inactive`**. **`waitForBareInitdUnits(names, timeoutMs)`** polls until listed units are **`active`** (exposed on **`ctx`** as **`bareOsAwaitInitdUnits`**). - **`startBareInitd(ctx)`** — ensures **`/var/log/bare-os`** (see below), then **DAG-ordered** start with optional parallelism (**`BARE_OS_INITD_MAX_PARALLEL`**, default **1**). Skips units listed in **`~/.config/bare-os/initd/disabled.txt`**. Drop-ins **`~/.config/bare-os/units/<name>.unit`** support **`[Unit]`** keys **`After=`**, **`Before=`**, **`Requires=`**, **`Wants=`**, **`TimeoutStartSec=`**, **`TimeoutStopSec=`**, **`Restart=`**, **`RestartSec=`**, **`OnFailure=`**, **`FailureAction=`**, **`ExecStartPost=`**, **`SocketActivationIpc=`** (defers **`start()`** until the first read on that FIFO), **`ReadinessPath=`**, **`ReadinessTimeoutSec=`**. Cycles in **`After`/`Before`** are detected and logged; affected units fail start. Default **`bare-cron`** runs after **`kernel-logger`**. Per-service `try/catch`, `[bare-initd] name: err` on failure; failures append to **`/var/log/bare-os/initd.log`** and the structured unit journal; runtime state **`active`** / **`failed`** / **`inactive`**. **`waitForBareInitdUnits(names, timeoutMs)`** polls until listed units are **`active`** (exposed as **`ctx.bareOsAwaitInitdUnits`**).
- **`listBareServices()`**, **`getBareServiceRuntime(name)`**, **`findBareServiceDefinition(name)`**, **`startBareService` / `stopBareService` / `restartBareService`** — introspection and lifecycle (used by the CLI below) - **`listBareServices()`**, **`getBareServiceRuntime(name)`**, **`findBareServiceDefinition(name)`**, **`startBareService` / `stopBareService` / `restartBareService`** — introspection and lifecycle (used by the CLI below)
- **`registerKernelShutdownHook(fn)`** + **`runKernelShutdownHooks()`** — async-friendly teardown before disposers (REPL **`session.cleanup`** awaits hooks, then **`stopBareInitd()`**) - **`registerKernelShutdownHook(fn)`** + **`runKernelShutdownHooks()`** — async-friendly teardown before disposers (REPL **`session.cleanup`** awaits hooks, then **`stopBareInitd()`**)
- **`registerBareInitdDisposer(fn)`** + **`stopBareInitd()`** — for intervals and synchronous teardown - **`registerBareInitdDisposer(fn)`** + **`stopBareInitd()`** — for intervals and synchronous teardown
+4
View File
@@ -39,6 +39,10 @@ The **prompt** (`[user@host:path] > `) is applied by the booters readline lay
| **`BARE_OS_BOOT_ALLOWLIST=1`** | Only run boot snippet lines whose first shell token is listed in **`/etc/bare-os/boot.allow`** (plus safe builtins); distributors can start from **`etc/bare-os/boot.allow.example`** on the system image. Pair with **`BARE_OS_BOOT_STRICT`** to exit on the first disallowed or failing line. | | **`BARE_OS_BOOT_ALLOWLIST=1`** | Only run boot snippet lines whose first shell token is listed in **`/etc/bare-os/boot.allow`** (plus safe builtins); distributors can start from **`etc/bare-os/boot.allow.example`** on the system image. Pair with **`BARE_OS_BOOT_STRICT`** to exit on the first disallowed or failing line. |
| **`BARE_OS_EXEC_MAX_DEPTH`** | Max nested **`execLine`** depth (host → session; default **64**). | | **`BARE_OS_EXEC_MAX_DEPTH`** | Max nested **`execLine`** depth (host → session; default **64**). |
| **`ctx.bareOsPublishBootReady(...)`** | Kernel calls this when boot is complete; populates **`/run/bare-os/ready`** (**`1`** / **`0`**) and **`/run/bare-os/boot.json`** (**`imageDigest`**, **`pearChannel`**, **`pearRelease`** are pre-seeded from host env when set — see [environment variables reference](../docs/reference/environment-and-posix-appendix.md#14-environment-variables-complete-list)). | | **`ctx.bareOsPublishBootReady(...)`** | Kernel calls this when boot is complete; populates **`/run/bare-os/ready`** (**`1`** / **`0`**) and **`/run/bare-os/boot.json`** (**`imageDigest`**, **`pearChannel`**, **`pearRelease`** are pre-seeded from host env when set — see [environment variables reference](../docs/reference/environment-and-posix-appendix.md#14-environment-variables-complete-list)). |
| **`BARE_OS_BOOT_MANIFEST`** | When **`1`** / **`true`**, the stock kernel verifies **`/etc/bare-os/boot.manifest.json`** against an expected SHA-256 (see kernel **`init.js`** and **`ctx.bareOsBootFileSha256Hex`**). Example layout: [`kernel/etc/bare-os/boot.manifest.example.json`](../kernel/etc/bare-os/boot.manifest.example.json). |
| **`BARE_OS_BOOT_MANIFEST_SIGN`**, **`BARE_OS_BOOT_MANIFEST_PUBKEY_HEX`** | Optional **Ed25519** over the **raw manifest bytes**: expects **`/etc/bare-os/boot.manifest.sig`** and **`ctx.bareOsVerifyBootManifestSignature`** (host/booter). Mismatch fails boot with a clear stderr line. |
| **`ctx.bareOsRegisterBootPhaseHook` / `bareOsInvokeBootPhaseHooks`** | Kernel extensions can observe **`before:` / `after:`** stock phases (e.g. **`rc`**, **`repl`**) without replacing **`init.js`**. See [developer guide §2](../developer-guide/02-the-context-object.md). |
| **`/etc/bare-os/selftest.d/`** | Optional digit-prefixed **`*.sh`** snippets run after the main boot path when **`BARE_OS_KERNEL_SELFTEST`** is enabled (same **`execLine`** rules as other boot snippets). Pair with **`BARE_OS_SELFTEST_FORMAT=tap`** for CI-friendly stderr. |
### Custom `init.js` contract ### Custom `init.js` contract
+5 -5
View File
@@ -10,7 +10,7 @@ Bare OS targets a **usable subset** of [POSIX.1-2017](https://pubs.opengroup.org
| Expectation (full POSIX) | Bare OS reality | | Expectation (full POSIX) | Bare OS reality |
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`sh`** grammar (`for`, subshells, …) | Line-at-a-time shell: builtins + **`/bin`**; lists with **`;`** (outside **`if`/`fi`**), **`&&`**, **`\|`** pipelines; bounded **`if`** **`then`** **[** **`else`** **]** **`fi`** (condition = last exit status like **`&&`**)—no **`for`**, groups, or full **`sh`** grammar. | | **`sh`** grammar (full POSIX) | Line-at-a-time shell: builtins + **`/bin`**; lists with **`;`** (outside compound statements), **`&&`**, **`\|`** pipelines; bounded **`if`** **`fi`**; bounded **`while`** / **`for`** (iteration cap **`BARE_OS_SHELL_LOOP_MAX`**, default **10000**); bounded **`case`** … **`esac`** (**`BARE_OS_SHELL_CASE_MAX_BRANCHES`**, default **32**). No subshells, **`$(…)`**, or full **`sh`** grammar. |
| **Processes, `fork`, pipes as OS primitives** | Pipelines are simulated by capturing **`console.log`** (and merged **`console.error`** when **`2>&1`** is used) into the next command. | | **Processes, `fork`, pipes as OS primitives** | Pipelines are simulated by capturing **`console.log`** (and merged **`console.error`** when **`2>&1`** is used) into the next command. |
| **`chown` / `chgrp` / real UIDs across users** | Single-session identity; metadata carries **uid/gid** for display and checks. | | **`chown` / `chgrp` / real UIDs across users** | Single-session identity; metadata carries **uid/gid** for display and checks. |
| **FIFOs, `mknod`, real devices** | No kernel FIFOs or **`mknod`**; **`mkfifo`** creates **in-memory** channels under **`/run/bare-os/ipc/<name>`** (see **`bare-os-ipc.js`**). | | **FIFOs, `mknod`, real devices** | No kernel FIFOs or **`mknod`**; **`mkfifo`** creates **in-memory** channels under **`/run/bare-os/ipc/<name>`** (see **`bare-os-ipc.js`**). |
@@ -37,7 +37,7 @@ Hyperdrive does not always behave like a POSIX directory tree. Empty directories
### 2.3 Pseudo **`/proc`**, **`/sys`**, **`/run`**, **`/dev`**, and session **`/tmp`** ### 2.3 Pseudo **`/proc`**, **`/sys`**, **`/run`**, **`/dev`**, and session **`/tmp`**
- **`/proc`**, **`/sys`**, **`/run`**, **`/dev`** — read-only synthetic trees except **`/dev/null`** and **`/dev/zero`** accept writes that are discarded (see [Chapter 4](04-the-booter-runtime.md)). Other pseudo **`writeFile`** / **`unlink`** / **`chmod`** paths fail as documented there. Extra Linux-shaped stubs include **`/proc/cpuinfo`**, **`/proc/loadavg`**, **`/proc/net/dev`**, **`/proc/diskstats`**, **`/proc/bare_os_quotas`** (JSON quotas + session stats), **`/proc/bare_os_resources`** (unified resource snapshot), **`/proc/bare_os_features`** (documented capability bitmask), **`/proc/bare_os_session_stats`**, **`/proc/self/exe`**, **`/sys/class/net/lo`**, **`/sys/fs/bare_os/build_id`**, **`/run/bare-os/virtual/*`** (optional kernel-registered readers), and **`/dev/urandom`** (each **`readFile`** of urandom returns a **bounded** buffer; **not** suitable for cryptography). - **`/proc`**, **`/sys`**, **`/run`**, **`/dev`** — read-only synthetic trees except **`/dev/null`** and **`/dev/zero`** accept writes that are discarded (see [Chapter 4](04-the-booter-runtime.md)). Other pseudo **`writeFile`** / **`unlink`** / **`chmod`** paths fail as documented there. Extra Linux-shaped stubs include **`/proc/cpuinfo`**, **`/proc/loadavg`**, **`/proc/net/dev`**, **`/proc/diskstats`**, **`/proc/bare_os_quotas`**, **`/proc/bare_os_resources`**, **`/proc/bare_os_features`**, **`/proc/bare_os_session_stats`**, **`/proc/bare_os_swarm`**, **`/proc/bare_os_replication`**, **`/proc/bare_os_capabilities`** (and **`/proc/bare_os_capabilities.json`**), **`/proc/bare_os_bootstrap`**, **`/proc/self/exe`**, **`/sys/class/net/lo`**, **`/sys/fs/bare_os/build_id`**, **`/run/bare-os/virtual/*`**, **`/run/bare-os/unit-journal/*.ndjson`**, and **`/dev/urandom`** (each **`readFile`** of urandom returns a **bounded** buffer; **not** suitable for cryptography unless **`BARE_OS_URANDOM_CRYPTO`** is left at default).
- **`/tmp`** — writable on the **personal** drive under **`/.bare-os/tmp/<HOME-basename>/…`**, isolated like **`$HOME`** and **`/var/log`**. - **`/tmp`** — writable on the **personal** drive under **`/.bare-os/tmp/<HOME-basename>/…`**, isolated like **`$HOME`** and **`/var/log`**.
### 2.4 `chmod` (octal and symbolic) ### 2.4 `chmod` (octal and symbolic)
@@ -58,7 +58,7 @@ Hyperdrive does not always behave like a POSIX directory tree. Empty directories
## 3. Shell lists, pipelines, and builtins (`packages/bare-os-booter/lib/shell.js`) ## 3. Shell lists, pipelines, and builtins (`packages/bare-os-booter/lib/shell.js`)
**Top-level syntax:** the line is split on **`;`** into separate lists (semicolons **inside** an **`if`** … **`fi`** block do not end the outer statement). **`if`** **`then`** **[** **`else`** **]** **`fi`** uses the same **`&&`** / **`||`** / pipeline rules inside the condition and each branch; the condition is true when the last evaluated command in that **`&&`/`||`** list exits **0**. Each list is an **AND-OR** chain: **pipelines** separated by **`&&`** or **`||`**, evaluated left-to-right with POSIX-style short-circuiting (**`ctx.exitCode`** — treat missing as **0**). Within a pipeline, **`|`** connects stages as before (simulated stdin between utilities). **Top-level syntax:** the line is split on **`;`** into separate lists (semicolons **inside** compound blocks do not end the outer statement). **`if`** **`then`** **[** **`else`** **]** **`fi`** uses the same **`&&`** / **`||`** / pipeline rules inside the condition and each branch; the condition is true when the last evaluated command in that **`&&`/`||`** list exits **0**. **`while`** **`list;`** **`do`** **`list;`** **`done`** and **`for`** **`name`** **`in`** **words** **`;`** **`do`** **`list;`** **`done`** repeat the body while respecting **`BARE_OS_SHELL_LOOP_MAX`**. **`case`** **`word`** **`in`** **`pattern)`** **`list`** **`;;`** … **`esac`** matches the first glob pattern (token-safe); branch count is capped by **`BARE_OS_SHELL_CASE_MAX_BRANCHES`**. Each list is an **AND-OR** chain: **pipelines** separated by **`&&`** or **`||`**, evaluated left-to-right with POSIX-style short-circuiting (**`ctx.exitCode`** — treat missing as **0**). Within a pipeline, **`|`** connects stages as before (simulated stdin between utilities).
**Background / jobs (subset):** a trailing **`&`** on a top-level list runs that list asynchronously (microtask). **`jobs`** lists recent jobs; **`fg`** awaits the selected (or latest) job; **`bg`** is a stub (no stopped-job resume). This is **not** POSIX job control (no real processes or signals). **Background / jobs (subset):** a trailing **`&`** on a top-level list runs that list asynchronously (microtask). **`jobs`** lists recent jobs; **`fg`** awaits the selected (or latest) job; **`bg`** is a stub (no stopped-job resume). This is **not** POSIX job control (no real processes or signals).
@@ -136,7 +136,7 @@ Sources: **`packages/bare-os-coreutils/src/<name>.js`**. **Authoritative sorted
| **`tee`** | **`-a`** append; duplicates stdin to files and stdout. | | **`tee`** | **`-a`** append; duplicates stdin to files and stdout. |
| **`sed`** | Large subset — see §6. | | **`sed`** | Large subset — see §6. |
| **`awk`** | Substantial interpreter — see §7. | | **`awk`** | Substantial interpreter — see §7. |
| **`grep`** | **`-F`**, **`-i`**, **`-v`**, **`-w`**, **`-x`**, **`-n`**, **`-c`**, **`-l`**, **`-o`**, **`-m`**, **`-A`/`-B`/`-C`**, \*\*`--color=never | always | auto`**, **`-e`**, **`-f`**, etc.; JS **`RegExp`\*\* (not PCRE / full GNU). | | **`grep`** | **`-F`**, **`-i`**, **`-v`**, **`-w`**, **`-x`**, **`-n`**, **`-c`**, **`-l`**, **`-o`**, **`-m`**, **`-r`/`-R`** with **`--include`**, **`--exclude`**, **`--exclude-dir`** (glob count capped by **`BARE_OS_GREP_FILTER_MAX`**, default **32**), **`-A`/`-B`/`-C`**, **`--color=never` / `always` / `auto`**, **`-e`**, **`-f`**, etc.; JS **`RegExp`** (not PCRE / full GNU). |
| **`base64`** | **`-d`/`--decode`**, **`-w`** line wrap; decode uses **`bareOsEmitRaw`** when **`console.log`** would corrupt binary. | | **`base64`** | **`-d`/`--decode`**, **`-w`** line wrap; decode uses **`bareOsEmitRaw`** when **`console.log`** would corrupt binary. |
| **`base32`** | RFC 4648 encode/decode; decode path same raw-output contract as **`base64`**. | | **`base32`** | RFC 4648 encode/decode; decode path same raw-output contract as **`base64`**. |
| **`basenc`** | **`--base16`** (hex) encode/decode only; other alphabets not implemented. | | **`basenc`** | **`--base16`** (hex) encode/decode only; other alphabets not implemented. |
@@ -215,7 +215,7 @@ All other commands from **`build.mjs`** not listed here follow the summaries in
**Engine:** **`packages/bare-os-coreutils/lib/sed-engine.js`** (prepended before **`src/sed.js`** at build time). **Engine:** **`packages/bare-os-coreutils/lib/sed-engine.js`** (prepended before **`src/sed.js`** at build time).
**CLI:** **`sed`** supports **`-n`**, **`-E`/`-r`**, multiple **`-e`**, **`-f`**, and operands as files or stdin. **CLI:** **`sed`** supports **`-n`**, **`-E`/`-r`**, **`-z`** (NUL-separated “lines”; max records **`BARE_OS_SED_NULL_MAX_RECORDS`**, default **100000**), multiple **`-e`**, **`-f`**, and operands as files or stdin.
**Broadly supported:** line addresses (**`#`**, **`$`**, **`/re/`**, ranges, **`first~step`**), **`s///`** with common flags (**`g`**, **`p`**, digit), **`y///`**, **`d`/`D`/`p`/`P`/`n`/`N`**, hold space (**`h`/`H`/`g`/`G`/`x`**), **`b`/`t`/`:label`**, **`q`**, **`r`/`w`**, **`=`**, **`l`**, **`a`/`i`/`c`** (backslash forms). **`r`** reads paths via a preload scan + **`vfs.readFile`**; **`w`** appends via **`vfs.writeFile`**. **Broadly supported:** line addresses (**`#`**, **`$`**, **`/re/`**, ranges, **`first~step`**), **`s///`** with common flags (**`g`**, **`p`**, digit), **`y///`**, **`d`/`D`/`p`/`P`/`n`/`N`**, hold space (**`h`/`H`/`g`/`G`/`x`**), **`b`/`t`/`:label`**, **`q`**, **`r`/`w`**, **`=`**, **`l`**, **`a`/`i`/`c`** (backslash forms). **`r`** reads paths via a preload scan + **`vfs.readFile`**; **`w`** appends via **`vfs.writeFile`**.
+82 -6
View File
@@ -115,6 +115,12 @@ async function run(ctx, argv) {
/** @type {'never' | 'always' | 'auto'} */ /** @type {'never' | 'always' | 'auto'} */
let colorMode = 'never' let colorMode = 'never'
let recursive = false let recursive = false
/** @type {string[]} */
const includeGlobs = []
/** @type {string[]} */
const excludeGlobs = []
/** @type {string[]} */
const excludeDirGlobs = []
const args = argv.slice(1) const args = argv.slice(1)
let i = 0 let i = 0
@@ -182,6 +188,36 @@ async function run(ctx, argv) {
i++ i++
continue continue
} }
if (a === '--include' && args[i + 1]) {
includeGlobs.push(args[++i])
i++
continue
}
if (a.startsWith('--include=')) {
includeGlobs.push(a.slice('--include='.length))
i++
continue
}
if (a === '--exclude' && args[i + 1]) {
excludeGlobs.push(args[++i])
i++
continue
}
if (a.startsWith('--exclude=')) {
excludeGlobs.push(a.slice('--exclude='.length))
i++
continue
}
if (a === '--exclude-dir' && args[i + 1]) {
excludeDirGlobs.push(args[++i])
i++
continue
}
if (a.startsWith('--exclude-dir=')) {
excludeDirGlobs.push(a.slice('--exclude-dir='.length))
i++
continue
}
ctx.console.error('grep: unknown option ' + a) ctx.console.error('grep: unknown option ' + a)
ctx.exitCode = 2 ctx.exitCode = 2
return return
@@ -393,7 +429,15 @@ async function run(ctx, argv) {
continue continue
} }
if (st.type === 'directory') { if (st.type === 'directory') {
await grepWalkFiles(ctx, p, acc, suppressErrors) await grepWalkFiles(ctx, p, acc, suppressErrors, {
includeGlobs,
excludeGlobs,
excludeDirGlobs,
filterCap: Number.parseInt(
String(ctx.vfs?.env?.BARE_OS_GREP_FILTER_MAX || '32'),
10
) || 32
})
} else { } else {
acc.push(p) acc.push(p)
} }
@@ -589,8 +633,31 @@ const GREP_RECURSE_MAX_DEPTH = 64
* @param {string[]} acc * @param {string[]} acc
* @param {boolean} suppressErrors * @param {boolean} suppressErrors
*/ */
async function grepWalkFiles(ctx, dir, acc, suppressErrors) { /**
* @param {string} name
* @param {string} pat
*/
function grepSimpleGlobMatch(name, pat) {
if (!pat || pat === '*') return true
if (pat.includes('/')) return name === pat
if (pat.startsWith('*') && pat.length > 1 && pat.endsWith('*')) {
const mid = pat.slice(1, -1)
return mid !== '' && name.includes(mid)
}
if (pat.startsWith('*')) return name.endsWith(pat.slice(1))
if (pat.endsWith('*')) return name.startsWith(pat.slice(0, -1))
return name === pat
}
/**
* @param {{ includeGlobs?: string[], excludeGlobs?: string[], excludeDirGlobs?: string[], filterCap?: number }} [opts]
*/
async function grepWalkFiles(ctx, dir, acc, suppressErrors, opts = {}) {
const vfs = ctx.vfs const vfs = ctx.vfs
const cap = opts.filterCap && opts.filterCap > 0 ? opts.filterCap : 32
const inc = (opts.includeGlobs || []).slice(0, cap)
const exc = (opts.excludeGlobs || []).slice(0, cap)
const excd = (opts.excludeDirGlobs || []).slice(0, cap)
/** @param {string} d @param {number} depth */ /** @param {string} d @param {number} depth */
const walk = async (d, depth) => { const walk = async (d, depth) => {
if (depth > GREP_RECURSE_MAX_DEPTH) return if (depth > GREP_RECURSE_MAX_DEPTH) return
@@ -612,12 +679,21 @@ async function grepWalkFiles(ctx, dir, acc, suppressErrors) {
continue continue
} }
if (!st) continue if (!st) continue
if (st.type === 'directory') await walk(sub, depth + 1) if (st.type === 'directory') {
else if (st.type === 'file') acc.push(sub) if (excd.some((p) => grepSimpleGlobMatch(n, p))) continue
else if (st.type === 'symlink') { await walk(sub, depth + 1)
} else if (st.type === 'file') {
if (inc.length && !inc.some((p) => grepSimpleGlobMatch(n, p))) continue
if (exc.some((p) => grepSimpleGlobMatch(n, p))) continue
acc.push(sub)
} else if (st.type === 'symlink') {
try { try {
const ft = await vfs.stat(sub) const ft = await vfs.stat(sub)
if (ft && ft.type === 'file') acc.push(sub) if (ft && ft.type === 'file') {
if (inc.length && !inc.some((p) => grepSimpleGlobMatch(n, p))) continue
if (exc.some((p) => grepSimpleGlobMatch(n, p))) continue
acc.push(sub)
}
} catch { } catch {
/* skip */ /* skip */
} }
+38 -18
View File
@@ -603,12 +603,13 @@ function bareSedMatchAddr(
/** /**
* @param {string[]} lines * @param {string[]} lines
* @param {string[]} scripts * @param {string[]} scripts
* @param {{ silent?: boolean, extended?: boolean, readFile?: (p: string) => string | null, writeFile?: (p: string, chunk: string) => void, lastLineHint?: number }} opts * @param {{ silent?: boolean, extended?: boolean, nullData?: boolean, readFile?: (p: string) => string | null, writeFile?: (p: string, chunk: string) => void, lastLineHint?: number }} opts
* @returns {string} * @returns {string}
*/ */
function bareSedRun(lines, scripts, opts) { function bareSedRun(lines, scripts, opts) {
const silent = !!opts.silent const silent = !!opts.silent
const extended = !!opts.extended const extended = !!opts.extended
const eol = opts.nullData ? '\0' : '\n'
const readF = opts.readFile || (() => null) const readF = opts.readFile || (() => null)
const writeF = opts.writeFile || (() => {}) const writeF = opts.writeFile || (() => {})
const fullScript = scripts.join('\n') const fullScript = scripts.join('\n')
@@ -697,7 +698,7 @@ function bareSedRun(lines, scripts, opts) {
} }
if (count) { if (count) {
ps = res + str.slice(pos) ps = res + str.slice(pos)
if (fl.p) emit(ps + '\n') if (fl.p) emit(ps + eol)
} }
break break
} }
@@ -726,15 +727,15 @@ function bareSedRun(lines, scripts, opts) {
break break
} }
case 'print': case 'print':
emit(ps + '\n') emit(ps + eol)
break break
case 'printFirst': { case 'printFirst': {
const nl = ps.indexOf('\n') const nl = ps.indexOf('\n')
emit((nl === -1 ? ps : ps.slice(0, nl)) + '\n') emit((nl === -1 ? ps : ps.slice(0, nl)) + eol)
break break
} }
case 'nextLine': case 'nextLine':
if (autoPrint && !silent) emit(ps + '\n') if (autoPrint && !silent) emit(ps + eol)
lineIdx++ lineIdx++
nextRead = true nextRead = true
ci = cmds.length ci = cmds.length
@@ -763,25 +764,25 @@ function bareSedRun(lines, scripts, opts) {
break break
} }
case 'quit': case 'quit':
if (autoPrint && !silent) emit(ps + '\n') if (autoPrint && !silent) emit(ps + eol)
quit = /** @type {number} */ (cmd.quitCode) || 0 quit = /** @type {number} */ (cmd.quitCode) || 0
break break
case 'list': case 'list':
emit(bareSedListLine(ps) + '\n') emit(bareSedListLine(ps) + eol)
break break
case 'lineNum': case 'lineNum':
emit(String(lineNo) + '\n') emit(String(lineNo) + eol)
break break
case 'readFile': { case 'readFile': {
const text = readF(/** @type {string} */ (cmd.path)) const text = readF(/** @type {string} */ (cmd.path))
if (text) emit(text.endsWith('\n') ? text : text + '\n') if (text) emit(text.endsWith(eol) ? text : text + eol)
break break
} }
case 'writeFile': case 'writeFile':
writeF(/** @type {string} */ (cmd.path), ps + '\n') writeF(/** @type {string} */ (cmd.path), ps + '\n')
break break
case 'append': case 'append':
emit(/** @type {string} */ (cmd.text) + '\n') emit(/** @type {string} */ (cmd.text) + eol)
break break
case 'insert': case 'insert':
/* handled as emit before line — approximated by prepending to output before autoPrint */ /* handled as emit before line — approximated by prepending to output before autoPrint */
@@ -789,7 +790,7 @@ function bareSedRun(lines, scripts, opts) {
break break
case 'change': case 'change':
autoPrint = false autoPrint = false
emit(/** @type {string} */ (cmd.text) + '\n') emit(/** @type {string} */ (cmd.text) + eol)
delLine = true delLine = true
break break
case 'b': { case 'b': {
@@ -815,7 +816,7 @@ function bareSedRun(lines, scripts, opts) {
if (quit) break if (quit) break
if (nextRead) continue if (nextRead) continue
if (!delLine && autoPrint) emit(ps + '\n') if (!delLine && autoPrint) emit(ps + eol)
lineIdx++ lineIdx++
} }
@@ -825,6 +826,7 @@ function bareSedRun(lines, scripts, opts) {
async function run(ctx, argv) { async function run(ctx, argv) {
let silent = false let silent = false
let extended = false let extended = false
let nullData = false
/** @type {string[]} */ /** @type {string[]} */
const scripts = [] const scripts = []
/** @type {string[]} */ /** @type {string[]} */
@@ -835,6 +837,10 @@ async function run(ctx, argv) {
silent = true silent = true
continue continue
} }
if (a === '-z' || a === '--null-data') {
nullData = true
continue
}
if (a === '-E' || a === '-r') { if (a === '-E' || a === '-r') {
extended = true extended = true
continue continue
@@ -913,6 +919,12 @@ async function run(ctx, argv) {
readCache[rp] = buf ? ctx.b4a.toString(buf) : '' readCache[rp] = buf ? ctx.b4a.toString(buf) : ''
} }
const maxNull =
Number.parseInt(
String(ctx.vfs?.env?.BARE_OS_SED_NULL_MAX_RECORDS || '100000'),
10
) || 100000
/** @type {string[]} */ /** @type {string[]} */
const lines = [] const lines = []
async function pushFile(path) { async function pushFile(path) {
@@ -923,16 +935,22 @@ async function run(ctx, argv) {
return false return false
} }
const t = ctx.b4a.toString(buf) const t = ctx.b4a.toString(buf)
const ls = t.split(/\r?\n/) if (nullData) {
if (ls.length && ls[ls.length - 1] === '') ls.pop() const rec = t.split('\0')
lines.push(...ls) const room = maxNull - lines.length
lines.push(...rec.slice(0, Math.max(0, room)))
} else {
const ls = t.split(/\r?\n/)
if (ls.length && ls[ls.length - 1] === '') ls.pop()
lines.push(...ls)
}
return true return true
} }
if (!files.length) { if (!files.length) {
const s = bareStdin(ctx) const s = bareStdin(ctx)
const ls = s.split(/\r?\n/) const ls = nullData ? s.split('\0').slice(0, maxNull) : s.split(/\r?\n/)
if (ls.length && ls[ls.length - 1] === '') ls.pop() if (!nullData && ls.length && ls[ls.length - 1] === '') ls.pop()
lines.push(...ls) lines.push(...ls)
} else { } else {
for (const f of files) { for (const f of files) {
@@ -945,6 +963,7 @@ async function run(ctx, argv) {
const out = bareSedRun(lines, scripts, { const out = bareSedRun(lines, scripts, {
silent, silent,
extended, extended,
nullData,
readFile: (p) => readCache[p] ?? null, readFile: (p) => readCache[p] ?? null,
writeFile: (p, chunk) => { writeFile: (p, chunk) => {
wAccum[p] = (wAccum[p] || '') + chunk wAccum[p] = (wAccum[p] || '') + chunk
@@ -965,6 +984,7 @@ async function run(ctx, argv) {
} }
} }
const t = out.replace(/\n$/, '') const trail = nullData ? /\0$/ : /\n$/
const t = out.replace(trail, '')
ctx.console.log(t) ctx.console.log(t)
} }
+27
View File
@@ -25,6 +25,9 @@
* BARE_OS_SELFTEST_FORMAT=tap: TAP-style lines on stderr for CI parsers. * BARE_OS_SELFTEST_FORMAT=tap: TAP-style lines on stderr for CI parsers.
* BARE_OS_BOOT_ALLOWLIST=1 and /etc/bare-os/boot.allow: only first-word commands in that file (plus shell builtins) run from trusted rc/onboot snippets. * BARE_OS_BOOT_ALLOWLIST=1 and /etc/bare-os/boot.allow: only first-word commands in that file (plus shell builtins) run from trusted rc/onboot snippets.
* *
* BARE_OS_BOOT_MANIFEST_SIGN=1: verify Ed25519 signature in /etc/bare-os/boot.manifest.sig over the raw
* manifest bytes; public key from BARE_OS_BOOT_MANIFEST_PUBKEY_HEX (64 hex chars). Uses ctx.bareOsVerifyBootManifestSignature.
*
* BARE_OS_BOOT_STRICT=1 or true: first execLine throw in trusted boot snippets calls * BARE_OS_BOOT_STRICT=1 or true: first execLine throw in trusted boot snippets calls
* requestBooterExit(1) and stops further boot phases. * requestBooterExit(1) and stops further boot phases.
* *
@@ -260,6 +263,30 @@ async function loadBootManifest(ctx) {
bootManifestMemo = null bootManifestMemo = null
return null return null
} }
const signOn =
ctx.env &&
(ctx.env.BARE_OS_BOOT_MANIFEST_SIGN === '1' ||
ctx.env.BARE_OS_BOOT_MANIFEST_SIGN === 'true')
if (signOn) {
const sigBuf = await drive.get('/etc/bare-os/boot.manifest.sig')
const pub =
ctx.env && ctx.env.BARE_OS_BOOT_MANIFEST_PUBKEY_HEX
? String(ctx.env.BARE_OS_BOOT_MANIFEST_PUBKEY_HEX).trim()
: ''
const verifyFn = ctx.bareOsVerifyBootManifestSignature
if (typeof verifyFn !== 'function' || !pub) {
console.error(
'[boot] signed manifest requires ctx.bareOsVerifyBootManifestSignature and BARE_OS_BOOT_MANIFEST_PUBKEY_HEX'
)
bootManifestMemo = null
return null
}
if (!verifyFn(buf, sigBuf, pub)) {
console.error('[boot] boot.manifest.json Ed25519 signature verification failed')
bootManifestMemo = null
return null
}
}
bootManifestMemo = JSON.parse(b4a.toString(buf)) bootManifestMemo = JSON.parse(b4a.toString(buf))
return bootManifestMemo return bootManifestMemo
} catch (e) { } catch (e) {
+190 -190
View File
@@ -7,18 +7,18 @@
"b4a" "b4a"
] ]
}, },
{
"path": "/lib/bare/bundles/safetyCatch.js",
"keys": [
"safetyCatch"
]
},
{ {
"path": "/lib/bare/bundles/hypercoreIdEncoding.js", "path": "/lib/bare/bundles/hypercoreIdEncoding.js",
"keys": [ "keys": [
"hypercoreIdEncoding" "hypercoreIdEncoding"
] ]
}, },
{
"path": "/lib/bare/bundles/safetyCatch.js",
"keys": [
"safetyCatch"
]
},
{ {
"path": "/lib/bare/bundles/compactEncoding.js", "path": "/lib/bare/bundles/compactEncoding.js",
"keys": [ "keys": [
@@ -31,6 +31,12 @@
"bareUrl" "bareUrl"
] ]
}, },
{
"path": "/lib/bare/bundles/protomux.js",
"keys": [
"protomux"
]
},
{ {
"path": "/lib/bare/bundles/bareEncoding.js", "path": "/lib/bare/bundles/bareEncoding.js",
"keys": [ "keys": [
@@ -43,12 +49,6 @@
"bareEvents" "bareEvents"
] ]
}, },
{
"path": "/lib/bare/bundles/protomux.js",
"keys": [
"protomux"
]
},
{ {
"path": "/lib/bare/bundles/barePath.js", "path": "/lib/bare/bundles/barePath.js",
"keys": [ "keys": [
@@ -109,12 +109,6 @@
"bareAsyncHooks" "bareAsyncHooks"
] ]
}, },
{
"path": "/lib/bare/bundles/fetch.js",
"keys": [
"fetch"
]
},
{ {
"path": "/lib/bare/bundles/bareAtomics.js", "path": "/lib/bare/bundles/bareAtomics.js",
"keys": [ "keys": [
@@ -127,6 +121,12 @@
"bareAssert" "bareAssert"
] ]
}, },
{
"path": "/lib/bare/bundles/fetch.js",
"keys": [
"fetch"
]
},
{ {
"path": "/lib/bare/bundles/bareBmp.js", "path": "/lib/bare/bundles/bareBmp.js",
"keys": [ "keys": [
@@ -175,24 +175,18 @@
"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": [
"bareBundleId" "bareBundleId"
] ]
}, },
{
"path": "/lib/bare/bundles/bareDebugLog.js",
"keys": [
"bareDebugLog"
]
},
{ {
"path": "/lib/bare/bundles/bareChannel.js", "path": "/lib/bare/bundles/bareChannel.js",
"keys": [ "keys": [
@@ -205,6 +199,12 @@
"bareDelta" "bareDelta"
] ]
}, },
{
"path": "/lib/bare/bundles/bareDaemon.js",
"keys": [
"bareDaemon"
]
},
{ {
"path": "/lib/bare/bundles/bareDns.js", "path": "/lib/bare/bundles/bareDns.js",
"keys": [ "keys": [
@@ -217,6 +217,12 @@
"bareDiagnosticsChannel" "bareDiagnosticsChannel"
] ]
}, },
{
"path": "/lib/bare/bundles/bareCov.js",
"keys": [
"bareCov"
]
},
{ {
"path": "/lib/bare/bundles/bareEnv.js", "path": "/lib/bare/bundles/bareEnv.js",
"keys": [ "keys": [
@@ -229,36 +235,12 @@
"bareExif" "bareExif"
] ]
}, },
{
"path": "/lib/bare/bundles/bareCov.js",
"keys": [
"bareCov"
]
},
{ {
"path": "/lib/bare/bundles/bareDgram.js", "path": "/lib/bare/bundles/bareDgram.js",
"keys": [ "keys": [
"bareDgram" "bareDgram"
] ]
}, },
{
"path": "/lib/bare/bundles/bareFfmpeg.js",
"keys": [
"bareFfmpeg"
]
},
{
"path": "/lib/bare/bundles/bareFfmpegEncodings.js",
"keys": [
"bareFfmpegEncodings"
]
},
{
"path": "/lib/bare/bundles/bareFormat.js",
"keys": [
"bareFormat"
]
},
{ {
"path": "/lib/bare/bundles/bareFormData.js", "path": "/lib/bare/bundles/bareFormData.js",
"keys": [ "keys": [
@@ -266,9 +248,21 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareHeif.js", "path": "/lib/bare/bundles/bareFfmpegEncodings.js",
"keys": [ "keys": [
"bareHeif" "bareFfmpegEncodings"
]
},
{
"path": "/lib/bare/bundles/bareFfmpeg.js",
"keys": [
"bareFfmpeg"
]
},
{
"path": "/lib/bare/bundles/bareFormat.js",
"keys": [
"bareFormat"
] ]
}, },
{ {
@@ -283,6 +277,12 @@
"bareGif" "bareGif"
] ]
}, },
{
"path": "/lib/bare/bundles/bareHeif.js",
"keys": [
"bareHeif"
]
},
{ {
"path": "/lib/bare/bundles/bareGtk.js", "path": "/lib/bare/bundles/bareGtk.js",
"keys": [ "keys": [
@@ -295,42 +295,36 @@
"bareHrtime" "bareHrtime"
] ]
}, },
{
"path": "/lib/bare/bundles/bareFs.js",
"keys": [
"bareFs"
]
},
{ {
"path": "/lib/bare/bundles/bareHttpParser.js", "path": "/lib/bare/bundles/bareHttpParser.js",
"keys": [ "keys": [
"bareHttpParser" "bareHttpParser"
] ]
}, },
{
"path": "/lib/bare/bundles/bareFs.js",
"keys": [
"bareFs"
]
},
{ {
"path": "/lib/bare/bundles/bareIco.js", "path": "/lib/bare/bundles/bareIco.js",
"keys": [ "keys": [
"bareIco" "bareIco"
] ]
}, },
{
"path": "/lib/bare/bundles/bareImageResample.js",
"keys": [
"bareImageResample"
]
},
{
"path": "/lib/bare/bundles/bareInspect.js",
"keys": [
"bareInspect"
]
},
{ {
"path": "/lib/bare/bundles/bareHttp1.js", "path": "/lib/bare/bundles/bareHttp1.js",
"keys": [ "keys": [
"bareHttp1" "bareHttp1"
] ]
}, },
{
"path": "/lib/bare/bundles/bareImageResample.js",
"keys": [
"bareImageResample"
]
},
{ {
"path": "/lib/bare/bundles/bareHttps.js", "path": "/lib/bare/bundles/bareHttps.js",
"keys": [ "keys": [
@@ -338,15 +332,15 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareJpeg.js", "path": "/lib/bare/bundles/bareInspect.js",
"keys": [ "keys": [
"bareJpeg" "bareInspect"
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareIntl.js", "path": "/lib/bare/bundles/bareJpeg.js",
"keys": [ "keys": [
"bareIntl" "bareJpeg"
] ]
}, },
{ {
@@ -355,24 +349,30 @@
"bareIpc" "bareIpc"
] ]
}, },
{
"path": "/lib/bare/bundles/bareIntl.js",
"keys": [
"bareIntl"
]
},
{ {
"path": "/lib/bare/bundles/bareLief.js", "path": "/lib/bare/bundles/bareLief.js",
"keys": [ "keys": [
"bareLief" "bareLief"
] ]
}, },
{
"path": "/lib/bare/bundles/bareLogger.js",
"keys": [
"bareLogger"
]
},
{ {
"path": "/lib/bare/bundles/bareLink.js", "path": "/lib/bare/bundles/bareLink.js",
"keys": [ "keys": [
"bareLink" "bareLink"
] ]
}, },
{
"path": "/lib/bare/bundles/bareLogger.js",
"keys": [
"bareLogger"
]
},
{ {
"path": "/lib/bare/bundles/bareInspector.js", "path": "/lib/bare/bundles/bareInspector.js",
"keys": [ "keys": [
@@ -386,9 +386,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareModuleResolve.js", "path": "/lib/bare/bundles/bareModule.js",
"keys": [ "keys": [
"bareModuleResolve" "bareModule"
] ]
}, },
{ {
@@ -397,24 +397,18 @@
"bareModuleLexer" "bareModuleLexer"
] ]
}, },
{
"path": "/lib/bare/bundles/bareModuleResolve.js",
"keys": [
"bareModuleResolve"
]
},
{ {
"path": "/lib/bare/bundles/bareModuleTraverse.js", "path": "/lib/bare/bundles/bareModuleTraverse.js",
"keys": [ "keys": [
"bareModuleTraverse" "bareModuleTraverse"
] ]
}, },
{
"path": "/lib/bare/bundles/bareModule.js",
"keys": [
"bareModule"
]
},
{
"path": "/lib/bare/bundles/bareNodeFetch.js",
"keys": [
"bareNodeFetch"
]
},
{ {
"path": "/lib/bare/bundles/bareNdk.js", "path": "/lib/bare/bundles/bareNdk.js",
"keys": [ "keys": [
@@ -428,21 +422,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareDev.js", "path": "/lib/bare/bundles/bareNodeFetch.js",
"keys": [ "keys": [
"bareDev" "bareNodeFetch"
]
},
{
"path": "/lib/bare/bundles/bareOs.js",
"keys": [
"bareOs"
]
},
{
"path": "/lib/bare/bundles/bareOpen.js",
"keys": [
"bareOpen"
] ]
}, },
{ {
@@ -458,15 +440,21 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/barePerformance.js", "path": "/lib/bare/bundles/bareOpen.js",
"keys": [ "keys": [
"barePerformance" "bareOpen"
] ]
}, },
{ {
"path": "/lib/bare/bundles/barePng.js", "path": "/lib/bare/bundles/bareOs.js",
"keys": [ "keys": [
"barePng" "bareOs"
]
},
{
"path": "/lib/bare/bundles/barePerformance.js",
"keys": [
"barePerformance"
] ]
}, },
{ {
@@ -476,9 +464,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/barePack.js", "path": "/lib/bare/bundles/barePng.js",
"keys": [ "keys": [
"barePack" "barePng"
] ]
}, },
{ {
@@ -487,18 +475,36 @@
"barePipe" "barePipe"
] ]
}, },
{
"path": "/lib/bare/bundles/barePack.js",
"keys": [
"barePack"
]
},
{ {
"path": "/lib/bare/bundles/barePunycode.js", "path": "/lib/bare/bundles/barePunycode.js",
"keys": [ "keys": [
"barePunycode" "barePunycode"
] ]
}, },
{
"path": "/lib/bare/bundles/bareDev.js",
"keys": [
"bareDev"
]
},
{ {
"path": "/lib/bare/bundles/bareQuerystring.js", "path": "/lib/bare/bundles/bareQuerystring.js",
"keys": [ "keys": [
"bareQuerystring" "bareQuerystring"
] ]
}, },
{
"path": "/lib/bare/bundles/bareNodeRuntime.js",
"keys": [
"bareNodeRuntime"
]
},
{ {
"path": "/lib/bare/bundles/barePrebuild.js", "path": "/lib/bare/bundles/barePrebuild.js",
"keys": [ "keys": [
@@ -512,9 +518,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareNodeRuntime.js", "path": "/lib/bare/bundles/bareProcess.js",
"keys": [ "keys": [
"bareNodeRuntime" "bareProcess"
] ]
}, },
{ {
@@ -524,15 +530,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareProcess.js", "path": "/lib/bare/bundles/bareSdl.js",
"keys": [ "keys": [
"bareProcess" "bareSdl"
]
},
{
"path": "/lib/bare/bundles/barePromClient.js",
"keys": [
"barePromClient"
] ]
}, },
{ {
@@ -542,9 +542,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareRepl.js", "path": "/lib/bare/bundles/barePromClient.js",
"keys": [ "keys": [
"bareRepl" "barePromClient"
] ]
}, },
{ {
@@ -553,18 +553,18 @@
"bareRpc" "bareRpc"
] ]
}, },
{
"path": "/lib/bare/bundles/bareSdl.js",
"keys": [
"bareSdl"
]
},
{ {
"path": "/lib/bare/bundles/bareSemver.js", "path": "/lib/bare/bundles/bareSemver.js",
"keys": [ "keys": [
"bareSemver" "bareSemver"
] ]
}, },
{
"path": "/lib/bare/bundles/bareSidecar.js",
"keys": [
"bareSidecar"
]
},
{ {
"path": "/lib/bare/bundles/bareRun.js", "path": "/lib/bare/bundles/bareRun.js",
"keys": [ "keys": [
@@ -578,27 +578,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareSidecar.js", "path": "/lib/bare/bundles/bareRepl.js",
"keys": [ "keys": [
"bareSidecar" "bareRepl"
]
},
{
"path": "/lib/bare/bundles/bareStream.js",
"keys": [
"bareStream"
]
},
{
"path": "/lib/bare/bundles/bareStorage.js",
"keys": [
"bareStorage"
]
},
{
"path": "/lib/bare/bundles/bareStdio.js",
"keys": [
"bareStdio"
] ]
}, },
{ {
@@ -607,6 +589,12 @@
"bareStringDecoder" "bareStringDecoder"
] ]
}, },
{
"path": "/lib/bare/bundles/bareStorage.js",
"keys": [
"bareStorage"
]
},
{ {
"path": "/lib/bare/bundles/bareSvg.js", "path": "/lib/bare/bundles/bareSvg.js",
"keys": [ "keys": [
@@ -614,9 +602,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareSystemLogger.js", "path": "/lib/bare/bundles/bareStream.js",
"keys": [ "keys": [
"bareSystemLogger" "bareStream"
] ]
}, },
{ {
@@ -626,15 +614,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareTiff.js", "path": "/lib/bare/bundles/bareStdio.js",
"keys": [ "keys": [
"bareTiff" "bareStdio"
]
},
{
"path": "/lib/bare/bundles/bareTap.js",
"keys": [
"bareTap"
] ]
}, },
{ {
@@ -644,15 +626,21 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareThread.js", "path": "/lib/bare/bundles/bareTap.js",
"keys": [ "keys": [
"bareThread" "bareTap"
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareTcp.js", "path": "/lib/bare/bundles/bareTiff.js",
"keys": [ "keys": [
"bareTcp" "bareTiff"
]
},
{
"path": "/lib/bare/bundles/bareSystemLogger.js",
"keys": [
"bareSystemLogger"
] ]
}, },
{ {
@@ -668,15 +656,15 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareTls.js", "path": "/lib/bare/bundles/bareThread.js",
"keys": [ "keys": [
"bareTls" "bareThread"
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareTty.js", "path": "/lib/bare/bundles/bareTcp.js",
"keys": [ "keys": [
"bareTty" "bareTcp"
] ]
}, },
{ {
@@ -692,9 +680,15 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareUnpack.js", "path": "/lib/bare/bundles/bareTty.js",
"keys": [ "keys": [
"bareUnpack" "bareTty"
]
},
{
"path": "/lib/bare/bundles/bareTls.js",
"keys": [
"bareTls"
] ]
}, },
{ {
@@ -704,9 +698,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareWalkHandles.js", "path": "/lib/bare/bundles/bareUnpack.js",
"keys": [ "keys": [
"bareWalkHandles" "bareUnpack"
] ]
}, },
{ {
@@ -716,9 +710,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareUnionBundle.js", "path": "/lib/bare/bundles/bareWalkHandles.js",
"keys": [ "keys": [
"bareUnionBundle" "bareWalkHandles"
] ]
}, },
{ {
@@ -740,9 +734,15 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareWhich.js", "path": "/lib/bare/bundles/bareUnionBundle.js",
"keys": [ "keys": [
"bareWhich" "bareUnionBundle"
]
},
{
"path": "/lib/bare/bundles/bareV8ToIstanbul.js",
"keys": [
"bareV8ToIstanbul"
] ]
}, },
{ {
@@ -752,9 +752,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareV8ToIstanbul.js", "path": "/lib/bare/bundles/bareWhich.js",
"keys": [ "keys": [
"bareV8ToIstanbul" "bareWhich"
] ]
}, },
{ {
@@ -769,12 +769,6 @@
"bareXdiff" "bareXdiff"
] ]
}, },
{
"path": "/lib/bare/bundles/bareWs.js",
"keys": [
"bareWs"
]
},
{ {
"path": "/lib/bare/bundles/bareZlib.js", "path": "/lib/bare/bundles/bareZlib.js",
"keys": [ "keys": [
@@ -787,6 +781,12 @@
"bareWorker" "bareWorker"
] ]
}, },
{
"path": "/lib/bare/bundles/bareWs.js",
"keys": [
"bareWs"
]
},
{ {
"path": "/lib/bare/bundles/bareZmq.js", "path": "/lib/bare/bundles/bareZmq.js",
"keys": [ "keys": [
File diff suppressed because one or more lines are too long
+1 -2
View File
@@ -1243,7 +1243,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz",
"integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"bare-os": "^3.0.1" "bare-os": "^3.0.1"
@@ -2794,7 +2793,6 @@
"version": "5.2.3", "version": "5.2.3",
"resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.3.tgz", "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.3.tgz",
"integrity": "sha512-07wwswlV7M3sC9IykbZRZ/jHAkrXFWVLqdBWGv1y0ojCimtRD9hGwxdHmR5FUFmDUZLNsBmTYJNQqgio5+A85Q==", "integrity": "sha512-07wwswlV7M3sC9IykbZRZ/jHAkrXFWVLqdBWGv1y0ojCimtRD9hGwxdHmR5FUFmDUZLNsBmTYJNQqgio5+A85Q==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"bare-env": "^3.0.0", "bare-env": "^3.0.0",
@@ -7020,6 +7018,7 @@
"b4a": "^1.6.7", "b4a": "^1.6.7",
"bare-os": "^3.8.7", "bare-os": "^3.8.7",
"bare-os-protocol": "*", "bare-os-protocol": "*",
"bare-subprocess": "^5.2.3",
"compact-encoding": "^2.18.0", "compact-encoding": "^2.18.0",
"corestore": "^7.2.1", "corestore": "^7.2.1",
"hyperdrive": "^13.3.2", "hyperdrive": "^13.3.2",
+1
View File
@@ -17,6 +17,7 @@
"pretest": "npm run build -w bare-os-coreutils && npm run build -w bare-os-bare-libs && node scripts/verify-kernel-seeder-parity.mjs && npm run smoke:bare-manifest", "pretest": "npm run build -w bare-os-coreutils && npm run build -w bare-os-bare-libs && node scripts/verify-kernel-seeder-parity.mjs && npm run smoke:bare-manifest",
"test": "npm run test --workspaces --if-present", "test": "npm run test --workspaces --if-present",
"verify-kernel-seeder": "node scripts/verify-kernel-seeder-parity.mjs", "verify-kernel-seeder": "node scripts/verify-kernel-seeder-parity.mjs",
"release-checklist": "node scripts/release-checklist.mjs",
"format": "prettier --write .", "format": "prettier --write .",
"lint": "prettier --check .", "lint": "prettier --check .",
"os:seeder": "npm run build -w bare-os-coreutils && npm run build -w bare-os-bare-libs && node scripts/ensure-pear-node-modules.mjs packages/bare-os-seeder && cd packages/bare-os-seeder && pear run --dev .", "os:seeder": "npm run build -w bare-os-coreutils && npm run build -w bare-os-bare-libs && node scripts/ensure-pear-node-modules.mjs packages/bare-os-seeder && cd packages/bare-os-seeder && pear run --dev .",
+1
View File
@@ -4,6 +4,7 @@
| Version | Booter (workspace) | Notes | | Version | Booter (workspace) | Notes |
| ------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1.9.0 | 0.1.0 | **`bareOsVerifyBootManifestSignature`**, signed manifest (**`BARE_OS_BOOT_MANIFEST_SIGN`**, **`/etc/bare-os/boot.manifest.sig`**), **`/proc/bare_os_replication`**, **`/proc/bare_os_capabilities`** (+ **`.json`**), **`/proc/bare_os_bootstrap`**, **`/run/bare-os/unit-journal/*.ndjson`**, initd **`BARE_OS_INITD_MAX_PARALLEL`**, unit journal + **`OnFailure=`** / **`FailureAction=`** / **`Before=`**, **`bareOsIpc.duplexJsonRoundTrip`**, **`BARE_OS_VFS_BIN_CACHE`**, **`bareOsRequestMirror`**, **`bareOsExportPersonalSnapshot`**, **`bareOsPearIpcEmit`**, sandbox worker hint (**`BARE_OS_SANDBOX_WORKER`**). Seeder: optional **`BARE_OS_HYPER_MULTISIG_VERIFY`** + **`hyper-multisig`**. Coreutils: **`grep -r`** **`--include`/`--exclude`/`--exclude-dir`**, **`sed -z`**. |
| 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.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. |
+159 -2
View File
@@ -18,6 +18,8 @@ import {
runBinCommand, runBinCommand,
runUserScriptFromSource runUserScriptFromSource
} from './lib/kernel-runner.js' } from './lib/kernel-runner.js'
import { verifyBootManifestEd25519 } from '#bare-os-boot-manifest-sig'
import { getBareInitdJournalNdjson } from './lib/bare-initd-journal.js'
import { createBareOsSandboxContext } from './lib/bare-os-sandbox.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'
@@ -76,7 +78,7 @@ 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' import { createHash } from 'bare-crypto'
const { randomUUID, randomBytes } = bareCrypto const { randomUUID, randomBytes } = bareCrypto
@@ -319,7 +321,19 @@ async function executeKernel(disk, store, swarm, initSource) {
'BARE_OS_YES_MAX_LINES', 'BARE_OS_YES_MAX_LINES',
'BARE_OS_SHUF_MAX_LINES', 'BARE_OS_SHUF_MAX_LINES',
'BARE_OS_SPLIT_MAX_FILES', 'BARE_OS_SPLIT_MAX_FILES',
'BARE_OS_NPROC' 'BARE_OS_NPROC',
'BARE_OS_BOOT_MANIFEST',
'BARE_OS_BOOT_MANIFEST_SIGN',
'BARE_OS_BOOT_MANIFEST_PUBKEY_HEX',
'BARE_OS_SANDBOX_SCRIPT',
'BARE_OS_VFS_UNION_PREFIXES',
'BARE_OS_URANDOM_CRYPTO',
'BARE_OS_INITD_MAX_PARALLEL',
'BARE_OS_VFS_BIN_CACHE',
'BARE_OS_SANDBOX_WORKER',
'BARE_OS_BLIND_BOOTSTRAP_URL',
'BARE_OS_BLIND_BOOTSTRAP_JSON',
'BARE_OS_MIRROR_READ_KEY'
]) { ]) {
const v = hostEnv[k] const v = hostEnv[k]
if (v != null && v !== '') shellEnv[k] = v if (v != null && v !== '') shellEnv[k] = v
@@ -538,6 +552,57 @@ async function executeKernel(disk, store, swarm, initSource) {
protocol: 'bare-os-v1' protocol: 'bare-os-v1'
})}\n` })}\n`
}, },
procBareOsReplicationText() {
const tk = topicKey()
const peers = disk.peers?.size ?? 0
const mk = shellEnv.BARE_OS_MIRROR_READ_KEY
return `${JSON.stringify({
topicHex: b4a.toString(tk, 'hex'),
peerCount: peers,
session: sessionStatsRef,
mirrorHint: !!(mk && String(mk).trim()),
atMs: Date.now()
})}\n`
},
procBareOsCapabilitiesText() {
const c = buildBareOsRuntimeCaps(shellEnv)
const paths = (c.pseudoFsPaths || []).join('\n ')
return [
'# bare_os_capabilities (summary; see .json for machine-readable)',
`ctxApiVersion: ${c.ctxApiVersion}`,
'pipeline: ' + JSON.stringify(c.pipeline),
'quotas: ' + JSON.stringify(c.quotas),
'pseudoFsPaths:',
' ' + paths,
'features: ' + JSON.stringify(c.features),
''
].join('\n')
},
procBareOsCapabilitiesJsonText() {
return `${JSON.stringify(buildBareOsRuntimeCaps(shellEnv), null, 2)}\n`
},
procBareOsBootstrapText() {
const url = shellEnv.BARE_OS_BLIND_BOOTSTRAP_URL
const j = shellEnv.BARE_OS_BLIND_BOOTSTRAP_JSON
if (url && String(url).trim()) {
return `${JSON.stringify({
blindBootstrapUrl: String(url).trim(),
source: 'BARE_OS_BLIND_BOOTSTRAP_URL'
})}\n`
}
if (j && String(j).trim()) {
try {
return `${JSON.stringify({
blindBootstrap: JSON.parse(String(j)),
source: 'BARE_OS_BLIND_BOOTSTRAP_JSON'
})}\n`
} catch {
return `${JSON.stringify({ error: 'invalid BARE_OS_BLIND_BOOTSTRAP_JSON' })}\n`
}
}
return '{}\n'
},
getUnitJournalNdjson: (unit) => getBareInitdJournalNdjson(unit),
getVirtualReaders: () => virtualReaderEntries, getVirtualReaders: () => virtualReaderEntries,
unionReadPrefixes: parseUnionReadPrefixes(shellEnv), unionReadPrefixes: parseUnionReadPrefixes(shellEnv),
sysClassNetLoText() { sysClassNetLoText() {
@@ -888,6 +953,18 @@ async function executeKernel(disk, store, swarm, initSource) {
if (shellEnv.BARE_OS_SANDBOX_SCRIPT === '0') { if (shellEnv.BARE_OS_SANDBOX_SCRIPT === '0') {
throw new Error('bareOsSandboxRunScript: disabled by BARE_OS_SANDBOX_SCRIPT=0') throw new Error('bareOsSandboxRunScript: disabled by BARE_OS_SANDBOX_SCRIPT=0')
} }
if (
shellEnv.BARE_OS_SANDBOX_WORKER === '1' ||
shellEnv.BARE_OS_SANDBOX_WORKER === 'true'
) {
try {
this.console?.log?.(
'[bare-os] BARE_OS_SANDBOX_WORKER: running inline (bare-thread needs a file entrypoint)'
)
} catch {
/* ignore */
}
}
const sb = createBareOsSandboxContext(this) const sb = createBareOsSandboxContext(this)
const { raceWithAbortAndTimeout } = await import('./lib/bare-os-abort.js') const { raceWithAbortAndTimeout } = await import('./lib/bare-os-abort.js')
return raceWithAbortAndTimeout( return raceWithAbortAndTimeout(
@@ -957,6 +1034,86 @@ async function executeKernel(disk, store, swarm, initSource) {
bareOsBootFileSha256Hex(buf) { bareOsBootFileSha256Hex(buf) {
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf) const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
return createHash('sha256').update(u8).digest('hex') return createHash('sha256').update(u8).digest('hex')
},
/**
* Ed25519 verify for `/etc/bare-os/boot.manifest.json` when `BARE_OS_BOOT_MANIFEST_SIGN=1`.
* @param {Uint8Array | ArrayBuffer | null | undefined} manifestBytes
* @param {Uint8Array | ArrayBuffer | null | undefined} signatureBytes
* @param {string} [publicKeyHex] optional override; else `BARE_OS_BOOT_MANIFEST_PUBKEY_HEX`
*/
bareOsVerifyBootManifestSignature(manifestBytes, signatureBytes, publicKeyHex) {
const pub =
publicKeyHex != null && String(publicKeyHex).trim()
? String(publicKeyHex).trim()
: shellEnv.BARE_OS_BOOT_MANIFEST_PUBKEY_HEX || ''
const m =
manifestBytes instanceof Uint8Array
? manifestBytes
: manifestBytes
? new Uint8Array(manifestBytes)
: null
const s =
signatureBytes instanceof Uint8Array
? signatureBytes
: signatureBytes
? new Uint8Array(signatureBytes)
: null
return verifyBootManifestEd25519(m, s, pub)
},
/**
* Ask host to attach a mirror read key (Pear / Holepunch); emits process event when available.
* @param {{ key?: string, label?: string }} [opts]
*/
async bareOsRequestMirror(opts = {}) {
if (typeof globalThis.process?.emit === 'function') {
try {
globalThis.process.emit('bare-os:mirror-request', {
key: opts.key != null ? String(opts.key) : '',
label: opts.label != null ? String(opts.label) : ''
})
} catch {
/* ignore */
}
}
return { ok: true, hint: 'Host should listen for process "bare-os:mirror-request".' }
},
/**
* Hint host to export a personal-drive snapshot (corestore-snapshot style).
* @param {{ label?: string }} [opts]
*/
async bareOsExportPersonalSnapshot(opts = {}) {
if (typeof globalThis.process?.emit === 'function') {
try {
globalThis.process.emit('bare-os:export-personal-snapshot', {
label: opts.label != null ? String(opts.label) : 'default',
ts: Date.now()
})
} catch {
/* ignore */
}
}
return {
ok: true,
hint: 'Host listens for process "bare-os:export-personal-snapshot".'
}
},
/**
* Passthrough for Pear IPCstyle host bridges (see developer guide).
* @param {string} channel
* @param {Record<string, unknown>} payload
*/
bareOsPearIpcEmit(channel, payload) {
if (typeof globalThis.process?.emit !== 'function') return false
try {
globalThis.process.emit('bare-os:pear-ipc', {
channel: String(channel || ''),
payload: payload && typeof payload === 'object' ? payload : {},
ts: Date.now()
})
return true
} catch {
return false
}
} }
} }
@@ -0,0 +1,50 @@
/**
* In-memory structured initd journal (NDJSON lines) exposed under /run/bare-os/unit-journal/.
*/
const MAX_LINES_PER_UNIT = 400
const MAX_LINE_UTF8 = 4096
/** @type {Map<string, string[]>} */
const linesByUnit = new Map()
/**
* @param {string} unit
* @param {Record<string, unknown>} rec
*/
export function appendBareInitdJournal(unit, rec) {
if (!unit || !/^[a-zA-Z0-9._-]+$/.test(unit)) return
const payload = { ts: Date.now(), unit, ...rec }
let line
try {
line = JSON.stringify(payload) + '\n'
} catch {
return
}
if (line.length > MAX_LINE_UTF8) return
let arr = linesByUnit.get(unit)
if (!arr) {
arr = []
linesByUnit.set(unit, arr)
}
arr.push(line)
while (arr.length > MAX_LINES_PER_UNIT) arr.shift()
}
/**
* @param {string} unit
* @returns {string}
*/
export function getBareInitdJournalNdjson(unit) {
const arr = linesByUnit.get(unit)
return arr ? arr.join('') : ''
}
/** @returns {string[]} */
export function listBareInitdJournalUnits() {
return [...linesByUnit.keys()].sort()
}
export function clearBareInitdJournalForTests() {
linesByUnit.clear()
}
+33 -4
View File
@@ -12,7 +12,9 @@ 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=. * ExecHealthCmd=, HealthIntervalSec=, HealthFailureThreshold=, BareMaxExecDepth=,
* Before= (reverse edge: listed units start after this one), OnFailure= (execLine after restart exhausted),
* FailureAction=exec|none (default exec when OnFailure is set).
*/ */
export const BARE_INITD_UNITS_DIR = '~/.config/bare-os/units' export const BARE_INITD_UNITS_DIR = '~/.config/bare-os/units'
@@ -43,7 +45,10 @@ export const BARE_INITD_DEFAULT_AFTER = Object.freeze({
* execHealthCmd: string | null, * execHealthCmd: string | null,
* healthIntervalSec: number | null, * healthIntervalSec: number | null,
* healthFailureThreshold: number | null, * healthFailureThreshold: number | null,
* bareMaxExecDepth: number | null * bareMaxExecDepth: number | null,
* before: string[],
* onFailure: string | null,
* failureAction: 'exec' | 'none'
* }} BareInitdUnitDropIn * }} BareInitdUnitDropIn
*/ */
@@ -64,7 +69,10 @@ export function emptyUnitDropIn() {
execHealthCmd: null, execHealthCmd: null,
healthIntervalSec: null, healthIntervalSec: null,
healthFailureThreshold: null, healthFailureThreshold: null,
bareMaxExecDepth: null bareMaxExecDepth: null,
before: [],
onFailure: null,
failureAction: 'exec'
} }
} }
@@ -133,11 +141,20 @@ export function parseUnitDropInText(text) {
} else if (key === 'baremaxexecdepth') { } else if (key === 'baremaxexecdepth') {
const n = Number.parseInt(val, 10) const n = Number.parseInt(val, 10)
if (Number.isFinite(n) && n > 0) out.bareMaxExecDepth = n if (Number.isFinite(n) && n > 0) out.bareMaxExecDepth = n
} else if (key === 'before') {
out.before.push(...parseList(val))
} else if (key === 'onfailure') {
if (val.trim()) out.onFailure = val
} else if (key === 'failureaction') {
const v = val.toLowerCase()
if (v === 'none') out.failureAction = 'none'
else out.failureAction = 'exec'
} }
} }
out.after = [...new Set(out.after)] out.after = [...new Set(out.after)]
out.requires = [...new Set(out.requires)] out.requires = [...new Set(out.requires)]
out.wants = [...new Set(out.wants)] out.wants = [...new Set(out.wants)]
out.before = [...new Set(out.before)]
return out return out
} }
@@ -166,7 +183,10 @@ export function mergeUnitDropIns(base, user) {
healthIntervalSec: user.healthIntervalSec ?? base.healthIntervalSec, healthIntervalSec: user.healthIntervalSec ?? base.healthIntervalSec,
healthFailureThreshold: healthFailureThreshold:
user.healthFailureThreshold ?? base.healthFailureThreshold, user.healthFailureThreshold ?? base.healthFailureThreshold,
bareMaxExecDepth: user.bareMaxExecDepth ?? base.bareMaxExecDepth bareMaxExecDepth: user.bareMaxExecDepth ?? base.bareMaxExecDepth,
before: pickArr(user.before, base.before),
onFailure: user.onFailure ?? base.onFailure,
failureAction: user.failureAction ?? base.failureAction
} }
} }
@@ -271,6 +291,15 @@ export async function loadInitdUnitDropIns(vfs, services, defaultAfter) {
fromFile.after = [...new Set([...def, ...fromFile.after])] fromFile.after = [...new Set([...def, ...fromFile.after])]
map.set(s.name, fromFile) map.set(s.name, fromFile)
} }
for (const [name, di] of map) {
for (const b of di.before) {
if (!/^[a-zA-Z0-9._-]+$/.test(b)) continue
const target = map.get(b)
if (target) {
target.after = [...new Set([...target.after, name])]
}
}
}
return map return map
} }
+171 -67
View File
@@ -9,6 +9,7 @@ import {
INITD_LOG, INITD_LOG,
KERNEL_CONSOLE_LOG KERNEL_CONSOLE_LOG
} from './bare-os-var-log.js' } from './bare-os-var-log.js'
import { appendBareInitdJournal } from './bare-initd-journal.js'
import { import {
BARE_INITD_DEFAULT_AFTER, BARE_INITD_DEFAULT_AFTER,
emptyUnitDropIn, emptyUnitDropIn,
@@ -287,6 +288,7 @@ function scheduleUnitHealth(ctx, name, dropIn) {
/* ignore */ /* ignore */
} }
void appendVarLog(ctx, INITD_LOG, name, msg) void appendVarLog(ctx, INITD_LOG, name, msg)
appendBareInitdJournal(name, { event: 'health_failed', error: msg })
} }
})() })()
}, intervalMs) }, intervalMs)
@@ -370,6 +372,94 @@ export async function restartBareService(ctx, name) {
} }
} }
/**
* @param {BareService} s
* @param {import('./bare-initd-user.js').BareInitdUnitDropIn} dropIn
*/
async function runOnFailureHookForUnit(ctx, s, dropIn) {
if (!dropIn.onFailure?.trim() || dropIn.failureAction === 'none') return
if (typeof ctx.execLine !== 'function') return
try {
await ctx.execLine(dropIn.onFailure.trim())
appendBareInitdJournal(s.name, { event: 'on_failure_ran' })
} catch (e) {
const msg = e?.message || String(e)
try {
ctx.console?.error?.(`[bare-initd] ${s.name} OnFailure: ${msg}`)
} catch {
/* ignore */
}
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {BareService} s
* @param {import('./bare-initd-user.js').BareInitdUnitDropIn} dropIn
*/
async function startNormalBareInitdUnit(ctx, s, dropIn) {
const t0 = Date.now()
const startSec = dropIn.timeoutStartSec
const maxAttempts =
dropIn.restart === 'on-failure' || dropIn.restart === 'always' ? 3 : 1
const restartDelayMs =
dropIn.restartSec != null && dropIn.restartSec >= 0
? Math.round(dropIn.restartSec * 1000)
: 1000
appendBareInitdJournal(s.name, { event: 'start_scheduled' })
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
if (attempt > 0) {
appendBareInitdJournal(s.name, { event: 'restart_attempt', attempt })
await new Promise((r) => setTimeout(r, restartDelayMs))
}
await withTimeoutSec(s.start(ctx), startSec, `start ${s.name}`)
if (dropIn.readinessPath) {
const rsec = dropIn.readinessTimeoutSec ?? 30
await waitForReadinessPath(ctx, dropIn.readinessPath, rsec)
}
runtime.set(s.name, { phase: 'active', startedAtMs: t0 })
appendBareInitdJournal(s.name, { event: 'active', attempt })
const post = dropIn.execStartPost
if (post && typeof ctx.execLine === 'function' && post.trim()) {
try {
await ctx.execLine(post.trim())
} catch (e) {
const msg = e?.message || String(e)
try {
ctx.console?.error?.(
`[bare-initd] ${s.name} ExecStartPost: ${msg}`
)
} catch {
/* ignore */
}
void appendVarLog(ctx, INITD_LOG, s.name, 'ExecStartPost: ' + msg)
}
}
scheduleUnitHealth(ctx, s.name, dropIn)
return
} catch (e) {
const msg = e?.message || String(e)
appendBareInitdJournal(s.name, {
event: 'start_error',
attempt,
error: msg
})
if (attempt === maxAttempts - 1) {
runtime.set(s.name, { phase: 'failed', startedAtMs: t0, error: msg })
try {
ctx.console?.error?.(`[bare-initd] ${s.name}: ${msg}`)
} catch {
/* ignore */
}
void appendVarLog(ctx, INITD_LOG, s.name, msg)
await runOnFailureHookForUnit(ctx, s, dropIn)
appendBareInitdJournal(s.name, { event: 'failed_final', error: msg })
}
}
}
}
/** /**
* @param {Record<string, unknown>} ctx * @param {Record<string, unknown>} ctx
*/ */
@@ -377,10 +467,12 @@ export async function startBareInitd(ctx) {
runtime.clear() runtime.clear()
await ensureBareOsVarLogTree(ctx) await ensureBareOsVarLogTree(ctx)
const vfs = ctx.vfs const vfs = ctx.vfs
/** @type {Map<string, import('./bare-initd-user.js').BareInitdUnitDropIn>} */
let dropInsMap = new Map()
let ordered = registry let ordered = registry
if (vfs && typeof vfs.readFile === 'function') { if (vfs && typeof vfs.readFile === 'function') {
const disabled = await readInitdDisabledSet(vfs) const disabled = await readInitdDisabledSet(vfs)
const dropIns = await loadInitdUnitDropIns( dropInsMap = await loadInitdUnitDropIns(
vfs, vfs,
registry, registry,
BARE_INITD_DEFAULT_AFTER BARE_INITD_DEFAULT_AFTER
@@ -391,7 +483,7 @@ export async function startBareInitd(ctx) {
const wantsMap = new Map() const wantsMap = new Map()
/** @type {Map<string, string[]>} */ /** @type {Map<string, string[]>} */
const afterMap = new Map() const afterMap = new Map()
for (const [name, di] of dropIns) { for (const [name, di] of dropInsMap) {
afterMap.set(name, di.after) afterMap.set(name, di.after)
requiresMap.set(name, di.requires) requiresMap.set(name, di.requires)
wantsMap.set(name, di.wants) wantsMap.set(name, di.wants)
@@ -404,34 +496,88 @@ export async function startBareInitd(ctx) {
wantsMap wantsMap
) )
} }
for (const s of ordered) {
const t0 = Date.now()
/** @type {import('./bare-initd-user.js').BareInitdUnitDropIn} */
const dropIn =
vfs && typeof vfs.readFile === 'function'
? await readUnitDropIn(vfs, s.name)
: emptyUnitDropIn()
if (dropIn.socketActivationIpc && ctx.bareOsIpc) { const rawPar = ctx.env && ctx.env.BARE_OS_INITD_MAX_PARALLEL
const maxP = Math.max(
1,
Math.min(32, Number.parseInt(String(rawPar ?? '1'), 10) || 1)
)
const activeNames = new Set(ordered.map((s) => s.name))
/** @type {Map<string, Set<string>>} */
const prereq = new Map()
for (const s of ordered) {
const di = dropInsMap.get(s.name) || emptyUnitDropIn()
const inc = new Set()
for (const a of di.after) if (activeNames.has(a)) inc.add(a)
for (const r of di.requires) if (activeNames.has(r)) inc.add(r)
for (const w of di.wants) if (activeNames.has(w)) inc.add(w)
prereq.set(s.name, inc)
}
/** @param {string[]} names @returns {string[][]} */
function computeLevels(names) {
const remaining = new Set(names)
/** @type {string[][]} */
const levels = []
while (remaining.size) {
const ready = [...remaining].filter((n) => {
for (const p of prereq.get(n) || []) {
if (remaining.has(p)) return false
}
return true
})
if (!ready.length) {
const n = [...remaining].sort()[0]
levels.push([n])
remaining.delete(n)
continue
}
ready.sort()
levels.push(ready)
for (const n of ready) remaining.delete(n)
}
return levels
}
const levels = computeLevels(ordered.map((s) => s.name))
for (const level of levels) {
/** @type {{ s: BareService, di: import('./bare-initd-user.js').BareInitdUnitDropIn }[]} */
const sockets = []
/** @type {{ s: BareService, di: import('./bare-initd-user.js').BareInitdUnitDropIn }[]} */
const normals = []
for (const name of level) {
const s = registry.find((x) => x.name === name)
if (!s) continue
const di = dropInsMap.get(s.name) || emptyUnitDropIn()
if (di.socketActivationIpc && ctx.bareOsIpc) sockets.push({ s, di })
else normals.push({ s, di })
}
for (const { s, di } of sockets) {
const t0 = Date.now()
appendBareInitdJournal(s.name, { event: 'socket_wait' })
try { try {
ctx.bareOsIpc.create(dropIn.socketActivationIpc) ctx.bareOsIpc.create(di.socketActivationIpc)
} catch { } catch {
/* exists */ /* exists */
} }
runtime.set(s.name, { phase: 'inactive', startedAtMs: t0 }) runtime.set(s.name, { phase: 'inactive', startedAtMs: t0 })
const ipcName = dropIn.socketActivationIpc const ipcName = di.socketActivationIpc
const startSec = dropIn.timeoutStartSec const startSec = di.timeoutStartSec
void (async () => { void (async () => {
try { try {
await ctx.bareOsIpc.take(ipcName) await ctx.bareOsIpc.take(ipcName)
const t1 = Date.now() const t1 = Date.now()
await withTimeoutSec(s.start(ctx), startSec, `start ${s.name}`) await withTimeoutSec(s.start(ctx), startSec, `start ${s.name}`)
if (dropIn.readinessPath) { if (di.readinessPath) {
const rsec = dropIn.readinessTimeoutSec ?? 30 const rsec = di.readinessTimeoutSec ?? 30
await waitForReadinessPath(ctx, dropIn.readinessPath, rsec) await waitForReadinessPath(ctx, di.readinessPath, rsec)
} }
runtime.set(s.name, { phase: 'active', startedAtMs: t1 }) runtime.set(s.name, { phase: 'active', startedAtMs: t1 })
const post = dropIn.execStartPost appendBareInitdJournal(s.name, { event: 'active', socket: true })
const post = di.execStartPost
if (post && typeof ctx.execLine === 'function' && post.trim()) { if (post && typeof ctx.execLine === 'function' && post.trim()) {
try { try {
await ctx.execLine(post.trim()) await ctx.execLine(post.trim())
@@ -447,7 +593,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) scheduleUnitHealth(ctx, s.name, di)
} 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 })
@@ -457,59 +603,17 @@ export async function startBareInitd(ctx) {
/* ignore */ /* ignore */
} }
void appendVarLog(ctx, INITD_LOG, s.name, msg) void appendVarLog(ctx, INITD_LOG, s.name, msg)
appendBareInitdJournal(s.name, { event: 'failed_socket', error: msg })
await runOnFailureHookForUnit(ctx, s, di)
} }
})() })()
continue
} }
const startSec = dropIn.timeoutStartSec for (let i = 0; i < normals.length; i += maxP) {
const maxAttempts = const chunk = normals.slice(i, i + maxP)
dropIn.restart === 'on-failure' || dropIn.restart === 'always' ? 3 : 1 await Promise.all(
const restartDelayMs = chunk.map(({ s, di }) => startNormalBareInitdUnit(ctx, s, di))
dropIn.restartSec != null && dropIn.restartSec >= 0 )
? Math.round(dropIn.restartSec * 1000)
: 1000
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
if (attempt > 0) {
await new Promise((r) => setTimeout(r, restartDelayMs))
}
await withTimeoutSec(s.start(ctx), startSec, `start ${s.name}`)
if (dropIn.readinessPath) {
const rsec = dropIn.readinessTimeoutSec ?? 30
await waitForReadinessPath(ctx, dropIn.readinessPath, rsec)
}
runtime.set(s.name, { phase: 'active', startedAtMs: t0 })
const post = dropIn.execStartPost
if (post && typeof ctx.execLine === 'function' && post.trim()) {
try {
await ctx.execLine(post.trim())
} catch (e) {
const msg = e?.message || String(e)
try {
ctx.console?.error?.(
`[bare-initd] ${s.name} ExecStartPost: ${msg}`
)
} catch {
/* ignore */
}
void appendVarLog(ctx, INITD_LOG, s.name, 'ExecStartPost: ' + msg)
}
}
scheduleUnitHealth(ctx, s.name, dropIn)
break
} catch (e) {
const msg = e?.message || String(e)
if (attempt === maxAttempts - 1) {
runtime.set(s.name, { phase: 'failed', startedAtMs: t0, error: msg })
try {
ctx.console?.error?.(`[bare-initd] ${s.name}: ${msg}`)
} catch {
/* ignore */
}
void appendVarLog(ctx, INITD_LOG, s.name, msg)
}
}
} }
} }
} }
@@ -0,0 +1,9 @@
'use strict'
/**
* CJS bridge: `require('bare-crypto/lib/key.js')` bypasses package `exports` on Node and avoids
* pulling Bare-only addon graphs into ESM static analysis. Pear/Bare executes this file with CJS
* `require` (no Node `module` built-in needed from our ESM entry).
*/
const { verify } = require('bare-crypto')
const { Ed25519PublicKey } = require('bare-crypto/lib/key.js')
module.exports = { verify, Ed25519PublicKey }
@@ -0,0 +1,44 @@
/**
* Pear / Bare: load crypto via CJS bridge (`require` provided by runtime). No `node:module`.
*/
import b4a from 'b4a'
import bridge from './bare-os-boot-manifest-sig-bridge.cjs'
const { verify, Ed25519PublicKey } = bridge
/**
* @param {Uint8Array | null | undefined} manifestBytes
* @param {Uint8Array | null | undefined} signatureBytes
* @param {string} publicKeyHex 64 hex chars (32-byte Ed25519 public key)
* @returns {boolean}
*/
export function verifyBootManifestEd25519(manifestBytes, signatureBytes, publicKeyHex) {
if (!manifestBytes?.length || !signatureBytes?.length) return false
const hex = String(publicKeyHex || '').trim().toLowerCase().replace(/^0x/, '')
if (!/^[0-9a-f]{64}$/.test(hex)) return false
/** @type {Uint8Array} */
let sig
if (signatureBytes.length === 64) {
sig = signatureBytes
} else {
const t = b4a.toString(signatureBytes, 'utf8').trim()
if (/^[0-9a-f]{128}$/i.test(t)) {
sig = b4a.from(t, 'hex')
} else {
try {
sig = b4a.from(t, 'base64')
} catch {
return false
}
}
}
if (sig.length !== 64) return false
try {
const pub = b4a.from(hex, 'hex')
const key = new Ed25519PublicKey(pub)
return verify('ed25519', manifestBytes, key, sig)
} catch {
return false
}
}
@@ -0,0 +1,47 @@
/**
* Node (brittle-node tests / `node index.js`): lazy `require('bare-crypto')` inside try/catch.
* `bare-crypto` is Bare-oriented and typically throws under Node; callers still get a boolean.
*/
import b4a from 'b4a'
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
/**
* @param {Uint8Array | null | undefined} manifestBytes
* @param {Uint8Array | null | undefined} signatureBytes
* @param {string} publicKeyHex 64 hex chars (32-byte Ed25519 public key)
* @returns {boolean}
*/
export function verifyBootManifestEd25519(manifestBytes, signatureBytes, publicKeyHex) {
if (!manifestBytes?.length || !signatureBytes?.length) return false
const hex = String(publicKeyHex || '').trim().toLowerCase().replace(/^0x/, '')
if (!/^[0-9a-f]{64}$/.test(hex)) return false
/** @type {Uint8Array} */
let sig
if (signatureBytes.length === 64) {
sig = signatureBytes
} else {
const t = b4a.toString(signatureBytes, 'utf8').trim()
if (/^[0-9a-f]{128}$/i.test(t)) {
sig = b4a.from(t, 'hex')
} else {
try {
sig = b4a.from(t, 'base64')
} catch {
return false
}
}
}
if (sig.length !== 64) return false
try {
const { verify } = require('bare-crypto')
const { Ed25519PublicKey } = require('bare-crypto/lib/key.js')
const pub = b4a.from(hex, 'hex')
const key = new Ed25519PublicKey(pub)
return verify('ed25519', manifestBytes, key, sig)
} catch {
return false
}
}
@@ -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.8.0' export const BARE_OS_CTX_API_VERSION = '1.9.0'
+18
View File
@@ -56,6 +56,11 @@ export interface BareOsIpc {
take: () => Promise<Uint8Array> take: () => Promise<Uint8Array>
} }
} }
duplexJsonRoundTrip(
baseName: string,
payload: Record<string, unknown>,
opts?: { timeoutMs?: number }
): Promise<Record<string, unknown>>
} }
export interface BareOsHostStats { export interface BareOsHostStats {
@@ -120,6 +125,19 @@ export interface BareOsKernelContext {
opts?: BareOsAbortOpts opts?: BareOsAbortOpts
): Promise<void> ): Promise<void>
bareOsBootFileSha256Hex(buf: Uint8Array | ArrayBuffer): string bareOsBootFileSha256Hex(buf: Uint8Array | ArrayBuffer): string
bareOsVerifyBootManifestSignature(
manifestBytes: Uint8Array | ArrayBuffer | null | undefined,
signatureBytes: Uint8Array | ArrayBuffer | null | undefined,
publicKeyHex?: string
): boolean
bareOsRequestMirror(opts?: {
key?: string
label?: string
}): Promise<{ ok: boolean; hint: string }>
bareOsExportPersonalSnapshot(opts?: {
label?: string
}): Promise<{ ok: boolean; hint: string }>
bareOsPearIpcEmit(channel: string, payload: Record<string, unknown>): boolean
bareOsRequestPearReload(opts?: { bareOsRequestPearReload(opts?: {
persistRequest?: boolean persistRequest?: boolean
}): Promise<{ }): Promise<{
@@ -295,6 +295,31 @@ export function createBareOsIpc(opts = {}) {
take: () => ba.take() take: () => ba.take()
} }
return { left, right } return { left, right }
},
/**
* One JSON line request/response over a {@link createDuplexBridge} side (`bareOsRpc: "2"`).
* @param {{ push: (buf: Uint8Array | ArrayBuffer) => void, take: () => Promise<Uint8Array> }} side
* @param {Record<string, unknown>} request
*/
async duplexJsonRoundTrip(side, request) {
if (!request || typeof request !== 'object')
throw new Error('bare-os ipc: invalid duplex request')
const line =
JSON.stringify({
bareOsRpc: '2',
...request
}) + '\n'
if (line.length > maxJsonLine) {
throw new Error('bare-os ipc: duplex JSON line exceeds max length')
}
side.push(b4a.from(line, 'utf8'))
const u8 = await side.take()
const res = b4a.toString(u8, 'utf8').trim()
if (res.length > maxJsonLine) {
throw new Error('bare-os ipc: duplex response too large')
}
return /** @type {Record<string, unknown>} */ (JSON.parse(res))
} }
} }
} }
@@ -30,6 +30,10 @@ export const BARE_OS_PSEUDO_FS_PATHS = Object.freeze([
'/proc/bare_os_quotas', '/proc/bare_os_quotas',
'/proc/bare_os_resources', '/proc/bare_os_resources',
'/proc/bare_os_swarm', '/proc/bare_os_swarm',
'/proc/bare_os_replication',
'/proc/bare_os_capabilities',
'/proc/bare_os_capabilities.json',
'/proc/bare_os_bootstrap',
'/proc/bare_os_session_stats', '/proc/bare_os_session_stats',
'/proc/bare_os_version', '/proc/bare_os_version',
'/proc/cpuinfo', '/proc/cpuinfo',
@@ -52,6 +56,7 @@ export const BARE_OS_PSEUDO_FS_PATHS = Object.freeze([
'/run/bare-os/ready', '/run/bare-os/ready',
'/run/bare-os/session', '/run/bare-os/session',
'/run/bare-os/units', '/run/bare-os/units',
'/run/bare-os/unit-journal',
'/run/bare-os/virtual', '/run/bare-os/virtual',
'/run/bare-os/ipc', '/run/bare-os/ipc',
'/sys', '/sys',
@@ -165,7 +170,29 @@ export function buildBareOsRuntimeCaps(shellEnv) {
shellEnv.BARE_OS_BOOT_MANIFEST === '1' || shellEnv.BARE_OS_BOOT_MANIFEST === '1' ||
shellEnv.BARE_OS_BOOT_MANIFEST === 'true', shellEnv.BARE_OS_BOOT_MANIFEST === 'true',
bareModuleLockfile: true, bareModuleLockfile: true,
oidcPublishHook: true oidcPublishHook: true,
ipcDuplexJsonRpc: true,
procReplicationSnapshot: true,
procCapabilitiesExport: true,
blindBootstrapProc: true,
initdUnitJournal: true,
initdParallelStart:
Number.parseInt(
String(shellEnv.BARE_OS_INITD_MAX_PARALLEL || '1'),
10
) > 1,
vfsBinReadCache:
shellEnv.BARE_OS_VFS_BIN_CACHE === '1' ||
shellEnv.BARE_OS_VFS_BIN_CACHE === 'true',
bootManifestEd25519:
shellEnv.BARE_OS_BOOT_MANIFEST_SIGN === '1' ||
shellEnv.BARE_OS_BOOT_MANIFEST_SIGN === 'true',
sandboxWorkerHint:
shellEnv.BARE_OS_SANDBOX_WORKER === '1' ||
shellEnv.BARE_OS_SANDBOX_WORKER === 'true',
mirrorRequestHook: true,
personalSnapshotExportHook: true,
pearIpcEmitHook: true
}) })
}) })
} }
+142 -1
View File
@@ -367,6 +367,16 @@ export function tokenize(line) {
} }
continue continue
} }
if (c === '(') {
tokens.push({ type: 'op', value: '(' })
i++
continue
}
if (c === ')') {
tokens.push({ type: 'op', value: ')' })
i++
continue
}
if (c === '<') { if (c === '<') {
if (line[i + 1] === '<' && line[i + 2] === '<') { if (line[i + 1] === '<' && line[i + 2] === '<') {
tokens.push({ type: 'op', value: '<<<' }) tokens.push({ type: 'op', value: '<<<' })
@@ -418,7 +428,9 @@ export function tokenize(line) {
ch === '>' || ch === '>' ||
ch === '<' || ch === '<' ||
ch === ';' || ch === ';' ||
ch === '&' ch === '&' ||
ch === '(' ||
ch === ')'
) )
break break
word += ch word += ch
@@ -1175,6 +1187,8 @@ function splitTopLevelStatements(tokens) {
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 === 'while' || t.value === 'for') depth++
else if (t.value === 'done') depth = Math.max(0, depth - 1) else if (t.value === 'done') depth = Math.max(0, depth - 1)
else if (t.value === 'case') depth++
else if (t.value === 'esac') 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)
@@ -1204,6 +1218,8 @@ function splitTopLevelByAmpersand(tokens) {
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 === 'while' || t.value === 'for') depth++
else if (t.value === 'done') depth = Math.max(0, depth - 1) else if (t.value === 'done') depth = Math.max(0, depth - 1)
else if (t.value === 'case') depth++
else if (t.value === 'esac') 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)
@@ -1447,6 +1463,129 @@ async function execForConstruct(ctx, tokens) {
return 'ok' return 'ok'
} }
/**
* @param {Token[]} toks
* @param {Record<string, string>} env
* @returns {string[]}
*/
function casePatternList(toks, env) {
/** @type {string[]} */
const out = []
/** @type {Token[]} */
let cur = []
for (const t of toks) {
if (t.type === 'op' && t.value === '|') {
if (cur.length) {
const s = cur.map((w) => w.value).join(' ')
out.push(expandWord(s.trim(), env))
cur = []
}
} else if (t.type === 'word') {
cur.push(t)
}
}
if (cur.length) {
const s = cur.map((w) => w.value).join(' ')
out.push(expandWord(s.trim(), env))
}
return out.filter(Boolean)
}
/**
* @param {string} subject
* @param {string} pat
*/
function casePatternMatches(subject, pat) {
if (pat === '*') return true
return subject === pat
}
/**
* `case WORD in pattern) list ;; … esac` bounded branches; patterns support `|` alternation and `*`.
* @param {Record<string, unknown>} ctx
* @param {Token[]} tokens
* @returns {Promise<'exit' | 'ok'>}
*/
async function execCaseConstruct(ctx, tokens) {
const last = tokens[tokens.length - 1]
if (last.type !== 'word' || last.value !== 'esac') {
ctx.console.error('shell: case: missing esac')
ctx.exitCode = 2
return 'ok'
}
if (
tokens.length < 5 ||
tokens[1].type !== 'word' ||
tokens[2].type !== 'word' ||
tokens[2].value !== 'in'
) {
ctx.console.error('shell: case: expected `case WORD in`')
ctx.exitCode = 2
return 'ok'
}
const env = ctx.vfs.env
const subj = expandWord(tokens[1].value, env)
const maxBranches = Number.parseInt(
ctx.vfs?.env?.BARE_OS_SHELL_CASE_MAX_BRANCHES || '32',
10
)
const cap = Number.isFinite(maxBranches) && maxBranches > 0 ? maxBranches : 32
let i = 3
let branches = 0
while (i < tokens.length - 1) {
if (++branches > cap) {
ctx.console.error('shell: case: too many branches (see BARE_OS_SHELL_CASE_MAX_BRANCHES)')
ctx.exitCode = 2
return 'ok'
}
let paren = -1
for (let k = i; k < tokens.length - 1; k++) {
const t = tokens[k]
if (t.type === 'op' && t.value === ')') {
paren = k
break
}
}
if (paren < 0) {
ctx.console.error('shell: case: expected )')
ctx.exitCode = 2
return 'ok'
}
const patToks = tokens.slice(i, paren)
let dsemi = -1
for (let k = paren + 1; k < tokens.length - 1; k++) {
const t = tokens[k]
const n = tokens[k + 1]
if (
t.type === 'op' &&
t.value === ';' &&
n &&
n.type === 'op' &&
n.value === ';'
) {
dsemi = k
break
}
}
if (dsemi < 0) {
ctx.console.error('shell: case: expected ;;')
ctx.exitCode = 2
return 'ok'
}
const bodyToks = tokens.slice(paren + 1, dsemi)
const pats = casePatternList(patToks, env)
const matched = pats.some((p) => casePatternMatches(subj, p))
if (matched) {
const r = await execSemicolonLists(ctx, bodyToks)
if (r === 'exit') return 'exit'
return 'ok'
}
i = dsemi + 2
}
ctx.exitCode = 0
return 'ok'
}
/** /**
* @param {Record<string, unknown>} ctx * @param {Record<string, unknown>} ctx
* @param {Token[]} stmt * @param {Token[]} stmt
@@ -1460,6 +1599,8 @@ async function dispatchShellStatement(ctx, stmt) {
return execWhileConstruct(ctx, stmt) return execWhileConstruct(ctx, stmt)
if (head?.type === 'word' && head.value === 'for') if (head?.type === 'word' && head.value === 'for')
return execForConstruct(ctx, stmt) return execForConstruct(ctx, stmt)
if (head?.type === 'word' && head.value === 'case')
return execCaseConstruct(ctx, stmt)
return execAndOrList(ctx, stmt) return execAndOrList(ctx, stmt)
} }
@@ -16,6 +16,7 @@ import {
readInitdDisabledSet, readInitdDisabledSet,
writeInitdDisabledSet writeInitdDisabledSet
} from './bare-initd-user.js' } from './bare-initd-user.js'
import { getBareInitdJournalNdjson } from './bare-initd-journal.js'
/** @param {Uint8Array | null} buf @param {number} maxLines */ /** @param {Uint8Array | null} buf @param {number} maxLines */
function tailUtf8Lines(buf, maxLines) { function tailUtf8Lines(buf, maxLines) {
@@ -112,6 +113,13 @@ export async function runSystemctlCli(ctx, argv) {
return return
} }
await printLogTail(ctx, def.logPath, lines, 'journalctl') await printLogTail(ctx, def.logPath, lines, 'journalctl')
const jtext = getBareInitdJournalNdjson(unit)
if (jtext.trim()) {
const jl = jtext.trimEnd().split(/\r?\n/)
const tailJ = jl.slice(-lines).join('\n')
ctx.console.log('--- unit journal (NDJSON tail) ---')
ctx.console.log(tailJ)
}
ctx.exitCode = ctx.exitCode ?? 0 ctx.exitCode = ctx.exitCode ?? 0
return return
} }
+148 -1
View File
@@ -1,6 +1,7 @@
import unixPathResolve from 'unix-path-resolve' import unixPathResolve from 'unix-path-resolve'
import b4a from 'b4a' import b4a from 'b4a'
import { raceWithAbortAndTimeout } from './bare-os-abort.js' import { raceWithAbortAndTimeout } from './bare-os-abort.js'
import { listBareInitdJournalUnits } from './bare-initd-journal.js'
import { import {
extractBareOs, extractBareOs,
identityNames, identityNames,
@@ -48,6 +49,11 @@ const DIR_MARKER = '.bareos_empty'
* procBareOsResourcesText?: () => string, * procBareOsResourcesText?: () => string,
* procBareOsFeaturesText?: () => string, * procBareOsFeaturesText?: () => string,
* procBareOsSwarmText?: () => string, * procBareOsSwarmText?: () => string,
* procBareOsReplicationText?: () => string,
* procBareOsCapabilitiesText?: () => string,
* procBareOsCapabilitiesJsonText?: () => string,
* procBareOsBootstrapText?: () => string,
* getUnitJournalNdjson?: (unit: string) => string,
* getVirtualReaders?: () => Map<string, unknown>, * getVirtualReaders?: () => Map<string, unknown>,
* unionReadPrefixes?: readonly string[], * unionReadPrefixes?: readonly string[],
* sysClassNetLoText?: () => string * sysClassNetLoText?: () => string
@@ -118,6 +124,26 @@ export function createVfs(
typeof vfsOptions.procBareOsSwarmText === 'function' typeof vfsOptions.procBareOsSwarmText === 'function'
? vfsOptions.procBareOsSwarmText ? vfsOptions.procBareOsSwarmText
: null : null
const procBareOsReplicationText =
typeof vfsOptions.procBareOsReplicationText === 'function'
? vfsOptions.procBareOsReplicationText
: null
const procBareOsCapabilitiesText =
typeof vfsOptions.procBareOsCapabilitiesText === 'function'
? vfsOptions.procBareOsCapabilitiesText
: null
const procBareOsCapabilitiesJsonText =
typeof vfsOptions.procBareOsCapabilitiesJsonText === 'function'
? vfsOptions.procBareOsCapabilitiesJsonText
: null
const procBareOsBootstrapText =
typeof vfsOptions.procBareOsBootstrapText === 'function'
? vfsOptions.procBareOsBootstrapText
: null
const getUnitJournalNdjson =
typeof vfsOptions.getUnitJournalNdjson === 'function'
? vfsOptions.getUnitJournalNdjson
: null
const getVirtualReaders = const getVirtualReaders =
typeof vfsOptions.getVirtualReaders === 'function' typeof vfsOptions.getVirtualReaders === 'function'
? vfsOptions.getVirtualReaders ? vfsOptions.getVirtualReaders
@@ -127,6 +153,11 @@ export function createVfs(
(s) => typeof s === 'string' && s.startsWith('/') (s) => typeof s === 'string' && s.startsWith('/')
) )
: [] : []
const binCacheEnabled =
env.BARE_OS_VFS_BIN_CACHE === '1' || env.BARE_OS_VFS_BIN_CACHE === 'true'
/** @type {Map<string, Uint8Array>} */
const binReadCache = binCacheEnabled ? new Map() : null
const BIN_READ_CACHE_MAX = 64
const sysClassNetLoText = const sysClassNetLoText =
typeof vfsOptions.sysClassNetLoText === 'function' typeof vfsOptions.sysClassNetLoText === 'function'
? vfsOptions.sysClassNetLoText ? vfsOptions.sysClassNetLoText
@@ -321,6 +352,30 @@ export function createVfs(
const t = procBareOsSwarmText ? procBareOsSwarmText() : '{}\n' const t = procBareOsSwarmText ? procBareOsSwarmText() : '{}\n'
return utf8Encode(t) return utf8Encode(t)
} }
if (f === 'bare_os_replication') {
const t = procBareOsReplicationText
? procBareOsReplicationText()
: '{}\n'
return utf8Encode(t)
}
if (f === 'bare_os_capabilities') {
const t = procBareOsCapabilitiesText
? procBareOsCapabilitiesText()
: '(no snapshot)\n'
return utf8Encode(t)
}
if (f === 'bare_os_capabilities_json') {
const t = procBareOsCapabilitiesJsonText
? procBareOsCapabilitiesJsonText()
: '{}\n'
return utf8Encode(t)
}
if (f === 'bare_os_bootstrap') {
const t = procBareOsBootstrapText
? procBareOsBootstrapText()
: '{}\n'
return utf8Encode(t)
}
if (f === 'net_dev') { if (f === 'net_dev') {
const t = procNetDevText const t = procNetDevText
? procNetDevText() ? procNetDevText()
@@ -338,6 +393,13 @@ export function createVfs(
: '# bare-initd: no snapshot provider\n' : '# bare-initd: no snapshot provider\n'
return utf8Encode(t) return utf8Encode(t)
} }
if (k === 'run' && f === 'unit_journal' && getUnitJournalNdjson) {
const u =
/** @type {{ unitJournalName?: string }} */ (routePseudo).unitJournalName ||
''
const t = getUnitJournalNdjson(u)
return utf8Encode(t || '')
}
if (k === 'run' && f === 'boot_profile') { if (k === 'run' && f === 'boot_profile') {
const t = bootProfileText ? bootProfileText() : '\n' const t = bootProfileText ? bootProfileText() : '\n'
return utf8Encode(t) return utf8Encode(t)
@@ -489,6 +551,38 @@ export function createVfs(
file: 'bare_os_swarm' file: 'bare_os_swarm'
} }
} }
if (sub === 'bare_os_replication') {
return {
virtualPseudo: true,
kind: 'proc',
node: 'file',
file: 'bare_os_replication'
}
}
if (sub === 'bare_os_capabilities') {
return {
virtualPseudo: true,
kind: 'proc',
node: 'file',
file: 'bare_os_capabilities'
}
}
if (sub === 'bare_os_capabilities.json') {
return {
virtualPseudo: true,
kind: 'proc',
node: 'file',
file: 'bare_os_capabilities_json'
}
}
if (sub === 'bare_os_bootstrap') {
return {
virtualPseudo: true,
kind: 'proc',
node: 'file',
file: 'bare_os_bootstrap'
}
}
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' }
} }
@@ -512,6 +606,29 @@ export function createVfs(
if (n === '/run/bare-os/units') { if (n === '/run/bare-os/units') {
return { virtualPseudo: true, kind: 'run', node: 'file', file: 'units' } return { virtualPseudo: true, kind: 'run', node: 'file', file: 'units' }
} }
if (n === '/run/bare-os/unit-journal' || n === '/run/bare-os/unit-journal/') {
return {
virtualPseudo: true,
kind: 'run',
node: 'dir',
dir: 'unit_journal_root'
}
}
{
const uj = '/run/bare-os/unit-journal/'
if (n.startsWith(uj)) {
const seg = n.slice(uj.length).replace(/\/+$/, '')
if (/^[a-zA-Z0-9._-]+\.ndjson$/.test(seg)) {
return {
virtualPseudo: true,
kind: 'run',
node: 'file',
file: 'unit_journal',
unitJournalName: seg.replace(/\.ndjson$/, '')
}
}
}
}
if (n === '/run/bare-os/boot_profile') { if (n === '/run/bare-os/boot_profile') {
return { return {
virtualPseudo: true, virtualPseudo: true,
@@ -1197,8 +1314,12 @@ export function createVfs(
} }
if (pr.kind === 'proc' && pr.node === 'root') { if (pr.kind === 'proc' && pr.node === 'root') {
return [ return [
'bare_os_bootstrap',
'bare_os_capabilities',
'bare_os_capabilities.json',
'bare_os_features', 'bare_os_features',
'bare_os_quotas', 'bare_os_quotas',
'bare_os_replication',
'bare_os_resources', 'bare_os_resources',
'bare_os_session_stats', 'bare_os_session_stats',
'bare_os_swarm', 'bare_os_swarm',
@@ -1245,11 +1366,20 @@ export function createVfs(
'ipc', 'ipc',
'ready', 'ready',
'session', 'session',
'unit-journal',
'units', 'units',
'virtual' 'virtual'
] ]
return bareOsIpc ? base : base.filter((x) => x !== 'ipc') return bareOsIpc ? base : base.filter((x) => x !== 'ipc')
} }
if (
pr.kind === 'run' &&
pr.node === 'dir' &&
pr.dir === 'unit_journal_root' &&
getUnitJournalNdjson
) {
return listBareInitdJournalUnits().map((u) => `${u}.ndjson`)
}
if ( if (
pr.kind === 'run' && pr.kind === 'run' &&
pr.node === 'dir' && pr.node === 'dir' &&
@@ -1546,7 +1676,24 @@ export function createVfs(
} }
} }
} }
return drive.get(p, { follow: true }) if (binReadCache && drive === systemDrive && abs.startsWith('/bin/')) {
const hit = binReadCache.get(abs)
if (hit) return new Uint8Array(hit)
}
const got = await drive.get(p, { follow: true })
if (
binReadCache &&
drive === systemDrive &&
abs.startsWith('/bin/') &&
got
) {
if (binReadCache.size >= BIN_READ_CACHE_MAX) {
const first = binReadCache.keys().next().value
binReadCache.delete(first)
}
binReadCache.set(abs, new Uint8Array(got))
}
return got
})(), })(),
abortOpts, abortOpts,
'vfs.readFile' 'vfs.readFile'
+4
View File
@@ -75,6 +75,10 @@
"node:url": { "node:url": {
"bare": "bare-url", "bare": "bare-url",
"default": "node:url" "default": "node:url"
},
"#bare-os-boot-manifest-sig": {
"bare": "./lib/bare-os-boot-manifest-sig.bare.js",
"default": "./lib/bare-os-boot-manifest-sig.node.js"
} }
}, },
"optionalDependencies": { "optionalDependencies": {
+63
View File
@@ -855,8 +855,12 @@ test('vfs /proc /sys read-only pseudo files; write rejected', async (t) => {
t.ok(root.includes('run')) t.ok(root.includes('run'))
t.ok(root.includes('dev')) t.ok(root.includes('dev'))
t.alike(await vfs.readdir('/proc').then((a) => [...a].sort()), [ t.alike(await vfs.readdir('/proc').then((a) => [...a].sort()), [
'bare_os_bootstrap',
'bare_os_capabilities',
'bare_os_capabilities.json',
'bare_os_features', 'bare_os_features',
'bare_os_quotas', 'bare_os_quotas',
'bare_os_replication',
'bare_os_resources', 'bare_os_resources',
'bare_os_session_stats', 'bare_os_session_stats',
'bare_os_swarm', 'bare_os_swarm',
@@ -917,6 +921,7 @@ test('vfs /proc /sys read-only pseudo files; write rejected', async (t) => {
'ipc', 'ipc',
'ready', 'ready',
'session', 'session',
'unit-journal',
'units', 'units',
'virtual' 'virtual'
]) ])
@@ -1199,6 +1204,7 @@ test('buildBareOsRuntimeCaps matches ctx API version and pipeline env', async (t
t.ok(caps.pseudoFsPaths.includes('/proc/mounts')) t.ok(caps.pseudoFsPaths.includes('/proc/mounts'))
t.ok(caps.pseudoFsPaths.includes('/proc/bare_os_resources')) t.ok(caps.pseudoFsPaths.includes('/proc/bare_os_resources'))
t.ok(caps.pseudoFsPaths.includes('/run/bare-os/virtual')) t.ok(caps.pseudoFsPaths.includes('/run/bare-os/virtual'))
t.ok(caps.pseudoFsPaths.includes('/run/bare-os/unit-journal'))
t.is(caps.features.simulatedPipelines, true) t.is(caps.features.simulatedPipelines, true)
t.is(caps.features.httpDelegate, true) t.is(caps.features.httpDelegate, true)
t.is(caps.features.gitDelegate, true) t.is(caps.features.gitDelegate, true)
@@ -3904,6 +3910,63 @@ test('coreutils gnu-gap batch: paste tac rev md5sum expr tsort numfmt truncate i
rmSync(dir, { recursive: true, force: true }) rmSync(dir, { recursive: true, force: true })
}) })
test('verifyBootManifestEd25519 rejects invalid inputs', async (t) => {
const { verifyBootManifestEd25519 } = await import('#bare-os-boot-manifest-sig')
const msg = b4a.from('manifest-bytes', 'utf8')
t.absent(verifyBootManifestEd25519(msg, null, ''))
t.absent(verifyBootManifestEd25519(msg, msg, '00ff'))
const z64 = '0'.repeat(64)
t.absent(verifyBootManifestEd25519(msg, new Uint8Array(64), z64))
})
test('bareOsIpc.duplexJsonRoundTrip', async (t) => {
const ipc = createBareOsIpc()
const { left, right } = ipc.createDuplexBridge('dupjx')
const respP = ipc.duplexJsonRoundTrip(right, { id: 7, method: 'ping' })
const u8 = await left.take()
const req = JSON.parse(b4a.toString(u8, 'utf8'))
t.is(req.method, 'ping')
left.push(
b4a.from(
JSON.stringify({ bareOsRpc: '2', id: 7, result: 'pong' }) + '\n',
'utf8'
)
)
const out = await respP
t.is(out.result, 'pong')
})
test('unit journal exposed under /run/bare-os/unit-journal', async (t) => {
const {
appendBareInitdJournal,
clearBareInitdJournalForTests,
getBareInitdJournalNdjson
} = await import('./lib/bare-initd-journal.js')
clearBareInitdJournalForTests()
appendBareInitdJournal('demo', { event: 'unit_test' })
const dir = testCorestoreDir('vj')
const store = new Corestore(dir)
const sys = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('pvj'))
await sys.ready()
await personal.ready()
const env = {
HOME: '/home/guest',
PWD: '/home/guest',
PATH: '/bin',
USER: 'guest'
}
const vfs = createVfs(sys, personal, env, null, {
getUnitJournalNdjson: (u) => getBareInitdJournalNdjson(u)
})
t.ok((await vfs.readdir('/run/bare-os/unit-journal')).includes('demo.ndjson'))
const j = b4a.toString(await vfs.readFile('/run/bare-os/unit-journal/demo.ndjson'))
t.ok(j.includes('unit_test'))
clearBareInitdJournalForTests()
await store.close()
rmSync(dir, { recursive: true, force: true })
})
async function readBuiltBin(name) { async function readBuiltBin(name) {
const fs = await import('node:fs/promises') const fs = await import('node:fs/promises')
const p = path.join(__dirname, '../../kernel/bin', name) const p = path.join(__dirname, '../../kernel/bin', name)
+13 -12
View File
@@ -514,12 +514,13 @@ function bareSedMatchAddr(
/** /**
* @param {string[]} lines * @param {string[]} lines
* @param {string[]} scripts * @param {string[]} scripts
* @param {{ silent?: boolean, extended?: boolean, readFile?: (p: string) => string | null, writeFile?: (p: string, chunk: string) => void, lastLineHint?: number }} opts * @param {{ silent?: boolean, extended?: boolean, nullData?: boolean, readFile?: (p: string) => string | null, writeFile?: (p: string, chunk: string) => void, lastLineHint?: number }} opts
* @returns {string} * @returns {string}
*/ */
function bareSedRun(lines, scripts, opts) { function bareSedRun(lines, scripts, opts) {
const silent = !!opts.silent const silent = !!opts.silent
const extended = !!opts.extended const extended = !!opts.extended
const eol = opts.nullData ? '\0' : '\n'
const readF = opts.readFile || (() => null) const readF = opts.readFile || (() => null)
const writeF = opts.writeFile || (() => {}) const writeF = opts.writeFile || (() => {})
const fullScript = scripts.join('\n') const fullScript = scripts.join('\n')
@@ -608,7 +609,7 @@ function bareSedRun(lines, scripts, opts) {
} }
if (count) { if (count) {
ps = res + str.slice(pos) ps = res + str.slice(pos)
if (fl.p) emit(ps + '\n') if (fl.p) emit(ps + eol)
} }
break break
} }
@@ -637,15 +638,15 @@ function bareSedRun(lines, scripts, opts) {
break break
} }
case 'print': case 'print':
emit(ps + '\n') emit(ps + eol)
break break
case 'printFirst': { case 'printFirst': {
const nl = ps.indexOf('\n') const nl = ps.indexOf('\n')
emit((nl === -1 ? ps : ps.slice(0, nl)) + '\n') emit((nl === -1 ? ps : ps.slice(0, nl)) + eol)
break break
} }
case 'nextLine': case 'nextLine':
if (autoPrint && !silent) emit(ps + '\n') if (autoPrint && !silent) emit(ps + eol)
lineIdx++ lineIdx++
nextRead = true nextRead = true
ci = cmds.length ci = cmds.length
@@ -674,25 +675,25 @@ function bareSedRun(lines, scripts, opts) {
break break
} }
case 'quit': case 'quit':
if (autoPrint && !silent) emit(ps + '\n') if (autoPrint && !silent) emit(ps + eol)
quit = /** @type {number} */ (cmd.quitCode) || 0 quit = /** @type {number} */ (cmd.quitCode) || 0
break break
case 'list': case 'list':
emit(bareSedListLine(ps) + '\n') emit(bareSedListLine(ps) + eol)
break break
case 'lineNum': case 'lineNum':
emit(String(lineNo) + '\n') emit(String(lineNo) + eol)
break break
case 'readFile': { case 'readFile': {
const text = readF(/** @type {string} */ (cmd.path)) const text = readF(/** @type {string} */ (cmd.path))
if (text) emit(text.endsWith('\n') ? text : text + '\n') if (text) emit(text.endsWith(eol) ? text : text + eol)
break break
} }
case 'writeFile': case 'writeFile':
writeF(/** @type {string} */ (cmd.path), ps + '\n') writeF(/** @type {string} */ (cmd.path), ps + '\n')
break break
case 'append': case 'append':
emit(/** @type {string} */ (cmd.text) + '\n') emit(/** @type {string} */ (cmd.text) + eol)
break break
case 'insert': case 'insert':
/* handled as emit before line — approximated by prepending to output before autoPrint */ /* handled as emit before line — approximated by prepending to output before autoPrint */
@@ -700,7 +701,7 @@ function bareSedRun(lines, scripts, opts) {
break break
case 'change': case 'change':
autoPrint = false autoPrint = false
emit(/** @type {string} */ (cmd.text) + '\n') emit(/** @type {string} */ (cmd.text) + eol)
delLine = true delLine = true
break break
case 'b': { case 'b': {
@@ -726,7 +727,7 @@ function bareSedRun(lines, scripts, opts) {
if (quit) break if (quit) break
if (nextRead) continue if (nextRead) continue
if (!delLine && autoPrint) emit(ps + '\n') if (!delLine && autoPrint) emit(ps + eol)
lineIdx++ lineIdx++
} }
+82 -6
View File
@@ -26,6 +26,12 @@ async function run(ctx, argv) {
/** @type {'never' | 'always' | 'auto'} */ /** @type {'never' | 'always' | 'auto'} */
let colorMode = 'never' let colorMode = 'never'
let recursive = false let recursive = false
/** @type {string[]} */
const includeGlobs = []
/** @type {string[]} */
const excludeGlobs = []
/** @type {string[]} */
const excludeDirGlobs = []
const args = argv.slice(1) const args = argv.slice(1)
let i = 0 let i = 0
@@ -93,6 +99,36 @@ async function run(ctx, argv) {
i++ i++
continue continue
} }
if (a === '--include' && args[i + 1]) {
includeGlobs.push(args[++i])
i++
continue
}
if (a.startsWith('--include=')) {
includeGlobs.push(a.slice('--include='.length))
i++
continue
}
if (a === '--exclude' && args[i + 1]) {
excludeGlobs.push(args[++i])
i++
continue
}
if (a.startsWith('--exclude=')) {
excludeGlobs.push(a.slice('--exclude='.length))
i++
continue
}
if (a === '--exclude-dir' && args[i + 1]) {
excludeDirGlobs.push(args[++i])
i++
continue
}
if (a.startsWith('--exclude-dir=')) {
excludeDirGlobs.push(a.slice('--exclude-dir='.length))
i++
continue
}
ctx.console.error('grep: unknown option ' + a) ctx.console.error('grep: unknown option ' + a)
ctx.exitCode = 2 ctx.exitCode = 2
return return
@@ -304,7 +340,15 @@ async function run(ctx, argv) {
continue continue
} }
if (st.type === 'directory') { if (st.type === 'directory') {
await grepWalkFiles(ctx, p, acc, suppressErrors) await grepWalkFiles(ctx, p, acc, suppressErrors, {
includeGlobs,
excludeGlobs,
excludeDirGlobs,
filterCap: Number.parseInt(
String(ctx.vfs?.env?.BARE_OS_GREP_FILTER_MAX || '32'),
10
) || 32
})
} else { } else {
acc.push(p) acc.push(p)
} }
@@ -500,8 +544,31 @@ const GREP_RECURSE_MAX_DEPTH = 64
* @param {string[]} acc * @param {string[]} acc
* @param {boolean} suppressErrors * @param {boolean} suppressErrors
*/ */
async function grepWalkFiles(ctx, dir, acc, suppressErrors) { /**
* @param {string} name
* @param {string} pat
*/
function grepSimpleGlobMatch(name, pat) {
if (!pat || pat === '*') return true
if (pat.includes('/')) return name === pat
if (pat.startsWith('*') && pat.length > 1 && pat.endsWith('*')) {
const mid = pat.slice(1, -1)
return mid !== '' && name.includes(mid)
}
if (pat.startsWith('*')) return name.endsWith(pat.slice(1))
if (pat.endsWith('*')) return name.startsWith(pat.slice(0, -1))
return name === pat
}
/**
* @param {{ includeGlobs?: string[], excludeGlobs?: string[], excludeDirGlobs?: string[], filterCap?: number }} [opts]
*/
async function grepWalkFiles(ctx, dir, acc, suppressErrors, opts = {}) {
const vfs = ctx.vfs const vfs = ctx.vfs
const cap = opts.filterCap && opts.filterCap > 0 ? opts.filterCap : 32
const inc = (opts.includeGlobs || []).slice(0, cap)
const exc = (opts.excludeGlobs || []).slice(0, cap)
const excd = (opts.excludeDirGlobs || []).slice(0, cap)
/** @param {string} d @param {number} depth */ /** @param {string} d @param {number} depth */
const walk = async (d, depth) => { const walk = async (d, depth) => {
if (depth > GREP_RECURSE_MAX_DEPTH) return if (depth > GREP_RECURSE_MAX_DEPTH) return
@@ -523,12 +590,21 @@ async function grepWalkFiles(ctx, dir, acc, suppressErrors) {
continue continue
} }
if (!st) continue if (!st) continue
if (st.type === 'directory') await walk(sub, depth + 1) if (st.type === 'directory') {
else if (st.type === 'file') acc.push(sub) if (excd.some((p) => grepSimpleGlobMatch(n, p))) continue
else if (st.type === 'symlink') { await walk(sub, depth + 1)
} else if (st.type === 'file') {
if (inc.length && !inc.some((p) => grepSimpleGlobMatch(n, p))) continue
if (exc.some((p) => grepSimpleGlobMatch(n, p))) continue
acc.push(sub)
} else if (st.type === 'symlink') {
try { try {
const ft = await vfs.stat(sub) const ft = await vfs.stat(sub)
if (ft && ft.type === 'file') acc.push(sub) if (ft && ft.type === 'file') {
if (inc.length && !inc.some((p) => grepSimpleGlobMatch(n, p))) continue
if (exc.some((p) => grepSimpleGlobMatch(n, p))) continue
acc.push(sub)
}
} catch { } catch {
/* skip */ /* skip */
} }
+25 -6
View File
@@ -1,6 +1,7 @@
async function run(ctx, argv) { async function run(ctx, argv) {
let silent = false let silent = false
let extended = false let extended = false
let nullData = false
/** @type {string[]} */ /** @type {string[]} */
const scripts = [] const scripts = []
/** @type {string[]} */ /** @type {string[]} */
@@ -11,6 +12,10 @@ async function run(ctx, argv) {
silent = true silent = true
continue continue
} }
if (a === '-z' || a === '--null-data') {
nullData = true
continue
}
if (a === '-E' || a === '-r') { if (a === '-E' || a === '-r') {
extended = true extended = true
continue continue
@@ -89,6 +94,12 @@ async function run(ctx, argv) {
readCache[rp] = buf ? ctx.b4a.toString(buf) : '' readCache[rp] = buf ? ctx.b4a.toString(buf) : ''
} }
const maxNull =
Number.parseInt(
String(ctx.vfs?.env?.BARE_OS_SED_NULL_MAX_RECORDS || '100000'),
10
) || 100000
/** @type {string[]} */ /** @type {string[]} */
const lines = [] const lines = []
async function pushFile(path) { async function pushFile(path) {
@@ -99,16 +110,22 @@ async function run(ctx, argv) {
return false return false
} }
const t = ctx.b4a.toString(buf) const t = ctx.b4a.toString(buf)
const ls = t.split(/\r?\n/) if (nullData) {
if (ls.length && ls[ls.length - 1] === '') ls.pop() const rec = t.split('\0')
lines.push(...ls) const room = maxNull - lines.length
lines.push(...rec.slice(0, Math.max(0, room)))
} else {
const ls = t.split(/\r?\n/)
if (ls.length && ls[ls.length - 1] === '') ls.pop()
lines.push(...ls)
}
return true return true
} }
if (!files.length) { if (!files.length) {
const s = bareStdin(ctx) const s = bareStdin(ctx)
const ls = s.split(/\r?\n/) const ls = nullData ? s.split('\0').slice(0, maxNull) : s.split(/\r?\n/)
if (ls.length && ls[ls.length - 1] === '') ls.pop() if (!nullData && ls.length && ls[ls.length - 1] === '') ls.pop()
lines.push(...ls) lines.push(...ls)
} else { } else {
for (const f of files) { for (const f of files) {
@@ -121,6 +138,7 @@ async function run(ctx, argv) {
const out = bareSedRun(lines, scripts, { const out = bareSedRun(lines, scripts, {
silent, silent,
extended, extended,
nullData,
readFile: (p) => readCache[p] ?? null, readFile: (p) => readCache[p] ?? null,
writeFile: (p, chunk) => { writeFile: (p, chunk) => {
wAccum[p] = (wAccum[p] || '') + chunk wAccum[p] = (wAccum[p] || '') + chunk
@@ -141,6 +159,7 @@ async function run(ctx, argv) {
} }
} }
const t = out.replace(/\n$/, '') const trail = nullData ? /\0$/ : /\n$/
const t = out.replace(trail, '')
ctx.console.log(t) ctx.console.log(t)
} }
+82 -6
View File
@@ -115,6 +115,12 @@ async function run(ctx, argv) {
/** @type {'never' | 'always' | 'auto'} */ /** @type {'never' | 'always' | 'auto'} */
let colorMode = 'never' let colorMode = 'never'
let recursive = false let recursive = false
/** @type {string[]} */
const includeGlobs = []
/** @type {string[]} */
const excludeGlobs = []
/** @type {string[]} */
const excludeDirGlobs = []
const args = argv.slice(1) const args = argv.slice(1)
let i = 0 let i = 0
@@ -182,6 +188,36 @@ async function run(ctx, argv) {
i++ i++
continue continue
} }
if (a === '--include' && args[i + 1]) {
includeGlobs.push(args[++i])
i++
continue
}
if (a.startsWith('--include=')) {
includeGlobs.push(a.slice('--include='.length))
i++
continue
}
if (a === '--exclude' && args[i + 1]) {
excludeGlobs.push(args[++i])
i++
continue
}
if (a.startsWith('--exclude=')) {
excludeGlobs.push(a.slice('--exclude='.length))
i++
continue
}
if (a === '--exclude-dir' && args[i + 1]) {
excludeDirGlobs.push(args[++i])
i++
continue
}
if (a.startsWith('--exclude-dir=')) {
excludeDirGlobs.push(a.slice('--exclude-dir='.length))
i++
continue
}
ctx.console.error('grep: unknown option ' + a) ctx.console.error('grep: unknown option ' + a)
ctx.exitCode = 2 ctx.exitCode = 2
return return
@@ -393,7 +429,15 @@ async function run(ctx, argv) {
continue continue
} }
if (st.type === 'directory') { if (st.type === 'directory') {
await grepWalkFiles(ctx, p, acc, suppressErrors) await grepWalkFiles(ctx, p, acc, suppressErrors, {
includeGlobs,
excludeGlobs,
excludeDirGlobs,
filterCap: Number.parseInt(
String(ctx.vfs?.env?.BARE_OS_GREP_FILTER_MAX || '32'),
10
) || 32
})
} else { } else {
acc.push(p) acc.push(p)
} }
@@ -589,8 +633,31 @@ const GREP_RECURSE_MAX_DEPTH = 64
* @param {string[]} acc * @param {string[]} acc
* @param {boolean} suppressErrors * @param {boolean} suppressErrors
*/ */
async function grepWalkFiles(ctx, dir, acc, suppressErrors) { /**
* @param {string} name
* @param {string} pat
*/
function grepSimpleGlobMatch(name, pat) {
if (!pat || pat === '*') return true
if (pat.includes('/')) return name === pat
if (pat.startsWith('*') && pat.length > 1 && pat.endsWith('*')) {
const mid = pat.slice(1, -1)
return mid !== '' && name.includes(mid)
}
if (pat.startsWith('*')) return name.endsWith(pat.slice(1))
if (pat.endsWith('*')) return name.startsWith(pat.slice(0, -1))
return name === pat
}
/**
* @param {{ includeGlobs?: string[], excludeGlobs?: string[], excludeDirGlobs?: string[], filterCap?: number }} [opts]
*/
async function grepWalkFiles(ctx, dir, acc, suppressErrors, opts = {}) {
const vfs = ctx.vfs const vfs = ctx.vfs
const cap = opts.filterCap && opts.filterCap > 0 ? opts.filterCap : 32
const inc = (opts.includeGlobs || []).slice(0, cap)
const exc = (opts.excludeGlobs || []).slice(0, cap)
const excd = (opts.excludeDirGlobs || []).slice(0, cap)
/** @param {string} d @param {number} depth */ /** @param {string} d @param {number} depth */
const walk = async (d, depth) => { const walk = async (d, depth) => {
if (depth > GREP_RECURSE_MAX_DEPTH) return if (depth > GREP_RECURSE_MAX_DEPTH) return
@@ -612,12 +679,21 @@ async function grepWalkFiles(ctx, dir, acc, suppressErrors) {
continue continue
} }
if (!st) continue if (!st) continue
if (st.type === 'directory') await walk(sub, depth + 1) if (st.type === 'directory') {
else if (st.type === 'file') acc.push(sub) if (excd.some((p) => grepSimpleGlobMatch(n, p))) continue
else if (st.type === 'symlink') { await walk(sub, depth + 1)
} else if (st.type === 'file') {
if (inc.length && !inc.some((p) => grepSimpleGlobMatch(n, p))) continue
if (exc.some((p) => grepSimpleGlobMatch(n, p))) continue
acc.push(sub)
} else if (st.type === 'symlink') {
try { try {
const ft = await vfs.stat(sub) const ft = await vfs.stat(sub)
if (ft && ft.type === 'file') acc.push(sub) if (ft && ft.type === 'file') {
if (inc.length && !inc.some((p) => grepSimpleGlobMatch(n, p))) continue
if (exc.some((p) => grepSimpleGlobMatch(n, p))) continue
acc.push(sub)
}
} catch { } catch {
/* skip */ /* skip */
} }
+38 -18
View File
@@ -603,12 +603,13 @@ function bareSedMatchAddr(
/** /**
* @param {string[]} lines * @param {string[]} lines
* @param {string[]} scripts * @param {string[]} scripts
* @param {{ silent?: boolean, extended?: boolean, readFile?: (p: string) => string | null, writeFile?: (p: string, chunk: string) => void, lastLineHint?: number }} opts * @param {{ silent?: boolean, extended?: boolean, nullData?: boolean, readFile?: (p: string) => string | null, writeFile?: (p: string, chunk: string) => void, lastLineHint?: number }} opts
* @returns {string} * @returns {string}
*/ */
function bareSedRun(lines, scripts, opts) { function bareSedRun(lines, scripts, opts) {
const silent = !!opts.silent const silent = !!opts.silent
const extended = !!opts.extended const extended = !!opts.extended
const eol = opts.nullData ? '\0' : '\n'
const readF = opts.readFile || (() => null) const readF = opts.readFile || (() => null)
const writeF = opts.writeFile || (() => {}) const writeF = opts.writeFile || (() => {})
const fullScript = scripts.join('\n') const fullScript = scripts.join('\n')
@@ -697,7 +698,7 @@ function bareSedRun(lines, scripts, opts) {
} }
if (count) { if (count) {
ps = res + str.slice(pos) ps = res + str.slice(pos)
if (fl.p) emit(ps + '\n') if (fl.p) emit(ps + eol)
} }
break break
} }
@@ -726,15 +727,15 @@ function bareSedRun(lines, scripts, opts) {
break break
} }
case 'print': case 'print':
emit(ps + '\n') emit(ps + eol)
break break
case 'printFirst': { case 'printFirst': {
const nl = ps.indexOf('\n') const nl = ps.indexOf('\n')
emit((nl === -1 ? ps : ps.slice(0, nl)) + '\n') emit((nl === -1 ? ps : ps.slice(0, nl)) + eol)
break break
} }
case 'nextLine': case 'nextLine':
if (autoPrint && !silent) emit(ps + '\n') if (autoPrint && !silent) emit(ps + eol)
lineIdx++ lineIdx++
nextRead = true nextRead = true
ci = cmds.length ci = cmds.length
@@ -763,25 +764,25 @@ function bareSedRun(lines, scripts, opts) {
break break
} }
case 'quit': case 'quit':
if (autoPrint && !silent) emit(ps + '\n') if (autoPrint && !silent) emit(ps + eol)
quit = /** @type {number} */ (cmd.quitCode) || 0 quit = /** @type {number} */ (cmd.quitCode) || 0
break break
case 'list': case 'list':
emit(bareSedListLine(ps) + '\n') emit(bareSedListLine(ps) + eol)
break break
case 'lineNum': case 'lineNum':
emit(String(lineNo) + '\n') emit(String(lineNo) + eol)
break break
case 'readFile': { case 'readFile': {
const text = readF(/** @type {string} */ (cmd.path)) const text = readF(/** @type {string} */ (cmd.path))
if (text) emit(text.endsWith('\n') ? text : text + '\n') if (text) emit(text.endsWith(eol) ? text : text + eol)
break break
} }
case 'writeFile': case 'writeFile':
writeF(/** @type {string} */ (cmd.path), ps + '\n') writeF(/** @type {string} */ (cmd.path), ps + '\n')
break break
case 'append': case 'append':
emit(/** @type {string} */ (cmd.text) + '\n') emit(/** @type {string} */ (cmd.text) + eol)
break break
case 'insert': case 'insert':
/* handled as emit before line — approximated by prepending to output before autoPrint */ /* handled as emit before line — approximated by prepending to output before autoPrint */
@@ -789,7 +790,7 @@ function bareSedRun(lines, scripts, opts) {
break break
case 'change': case 'change':
autoPrint = false autoPrint = false
emit(/** @type {string} */ (cmd.text) + '\n') emit(/** @type {string} */ (cmd.text) + eol)
delLine = true delLine = true
break break
case 'b': { case 'b': {
@@ -815,7 +816,7 @@ function bareSedRun(lines, scripts, opts) {
if (quit) break if (quit) break
if (nextRead) continue if (nextRead) continue
if (!delLine && autoPrint) emit(ps + '\n') if (!delLine && autoPrint) emit(ps + eol)
lineIdx++ lineIdx++
} }
@@ -825,6 +826,7 @@ function bareSedRun(lines, scripts, opts) {
async function run(ctx, argv) { async function run(ctx, argv) {
let silent = false let silent = false
let extended = false let extended = false
let nullData = false
/** @type {string[]} */ /** @type {string[]} */
const scripts = [] const scripts = []
/** @type {string[]} */ /** @type {string[]} */
@@ -835,6 +837,10 @@ async function run(ctx, argv) {
silent = true silent = true
continue continue
} }
if (a === '-z' || a === '--null-data') {
nullData = true
continue
}
if (a === '-E' || a === '-r') { if (a === '-E' || a === '-r') {
extended = true extended = true
continue continue
@@ -913,6 +919,12 @@ async function run(ctx, argv) {
readCache[rp] = buf ? ctx.b4a.toString(buf) : '' readCache[rp] = buf ? ctx.b4a.toString(buf) : ''
} }
const maxNull =
Number.parseInt(
String(ctx.vfs?.env?.BARE_OS_SED_NULL_MAX_RECORDS || '100000'),
10
) || 100000
/** @type {string[]} */ /** @type {string[]} */
const lines = [] const lines = []
async function pushFile(path) { async function pushFile(path) {
@@ -923,16 +935,22 @@ async function run(ctx, argv) {
return false return false
} }
const t = ctx.b4a.toString(buf) const t = ctx.b4a.toString(buf)
const ls = t.split(/\r?\n/) if (nullData) {
if (ls.length && ls[ls.length - 1] === '') ls.pop() const rec = t.split('\0')
lines.push(...ls) const room = maxNull - lines.length
lines.push(...rec.slice(0, Math.max(0, room)))
} else {
const ls = t.split(/\r?\n/)
if (ls.length && ls[ls.length - 1] === '') ls.pop()
lines.push(...ls)
}
return true return true
} }
if (!files.length) { if (!files.length) {
const s = bareStdin(ctx) const s = bareStdin(ctx)
const ls = s.split(/\r?\n/) const ls = nullData ? s.split('\0').slice(0, maxNull) : s.split(/\r?\n/)
if (ls.length && ls[ls.length - 1] === '') ls.pop() if (!nullData && ls.length && ls[ls.length - 1] === '') ls.pop()
lines.push(...ls) lines.push(...ls)
} else { } else {
for (const f of files) { for (const f of files) {
@@ -945,6 +963,7 @@ async function run(ctx, argv) {
const out = bareSedRun(lines, scripts, { const out = bareSedRun(lines, scripts, {
silent, silent,
extended, extended,
nullData,
readFile: (p) => readCache[p] ?? null, readFile: (p) => readCache[p] ?? null,
writeFile: (p, chunk) => { writeFile: (p, chunk) => {
wAccum[p] = (wAccum[p] || '') + chunk wAccum[p] = (wAccum[p] || '') + chunk
@@ -965,6 +984,7 @@ async function run(ctx, argv) {
} }
} }
const t = out.replace(/\n$/, '') const trail = nullData ? /\0$/ : /\n$/
const t = out.replace(trail, '')
ctx.console.log(t) ctx.console.log(t)
} }
+27
View File
@@ -25,6 +25,9 @@
* BARE_OS_SELFTEST_FORMAT=tap: TAP-style lines on stderr for CI parsers. * BARE_OS_SELFTEST_FORMAT=tap: TAP-style lines on stderr for CI parsers.
* BARE_OS_BOOT_ALLOWLIST=1 and /etc/bare-os/boot.allow: only first-word commands in that file (plus shell builtins) run from trusted rc/onboot snippets. * BARE_OS_BOOT_ALLOWLIST=1 and /etc/bare-os/boot.allow: only first-word commands in that file (plus shell builtins) run from trusted rc/onboot snippets.
* *
* BARE_OS_BOOT_MANIFEST_SIGN=1: verify Ed25519 signature in /etc/bare-os/boot.manifest.sig over the raw
* manifest bytes; public key from BARE_OS_BOOT_MANIFEST_PUBKEY_HEX (64 hex chars). Uses ctx.bareOsVerifyBootManifestSignature.
*
* BARE_OS_BOOT_STRICT=1 or true: first execLine throw in trusted boot snippets calls * BARE_OS_BOOT_STRICT=1 or true: first execLine throw in trusted boot snippets calls
* requestBooterExit(1) and stops further boot phases. * requestBooterExit(1) and stops further boot phases.
* *
@@ -260,6 +263,30 @@ async function loadBootManifest(ctx) {
bootManifestMemo = null bootManifestMemo = null
return null return null
} }
const signOn =
ctx.env &&
(ctx.env.BARE_OS_BOOT_MANIFEST_SIGN === '1' ||
ctx.env.BARE_OS_BOOT_MANIFEST_SIGN === 'true')
if (signOn) {
const sigBuf = await drive.get('/etc/bare-os/boot.manifest.sig')
const pub =
ctx.env && ctx.env.BARE_OS_BOOT_MANIFEST_PUBKEY_HEX
? String(ctx.env.BARE_OS_BOOT_MANIFEST_PUBKEY_HEX).trim()
: ''
const verifyFn = ctx.bareOsVerifyBootManifestSignature
if (typeof verifyFn !== 'function' || !pub) {
console.error(
'[boot] signed manifest requires ctx.bareOsVerifyBootManifestSignature and BARE_OS_BOOT_MANIFEST_PUBKEY_HEX'
)
bootManifestMemo = null
return null
}
if (!verifyFn(buf, sigBuf, pub)) {
console.error('[boot] boot.manifest.json Ed25519 signature verification failed')
bootManifestMemo = null
return null
}
}
bootManifestMemo = JSON.parse(b4a.toString(buf)) bootManifestMemo = JSON.parse(b4a.toString(buf))
return bootManifestMemo return bootManifestMemo
} catch (e) { } catch (e) {
@@ -7,18 +7,18 @@
"b4a" "b4a"
] ]
}, },
{
"path": "/lib/bare/bundles/safetyCatch.js",
"keys": [
"safetyCatch"
]
},
{ {
"path": "/lib/bare/bundles/hypercoreIdEncoding.js", "path": "/lib/bare/bundles/hypercoreIdEncoding.js",
"keys": [ "keys": [
"hypercoreIdEncoding" "hypercoreIdEncoding"
] ]
}, },
{
"path": "/lib/bare/bundles/safetyCatch.js",
"keys": [
"safetyCatch"
]
},
{ {
"path": "/lib/bare/bundles/compactEncoding.js", "path": "/lib/bare/bundles/compactEncoding.js",
"keys": [ "keys": [
@@ -31,6 +31,12 @@
"bareUrl" "bareUrl"
] ]
}, },
{
"path": "/lib/bare/bundles/protomux.js",
"keys": [
"protomux"
]
},
{ {
"path": "/lib/bare/bundles/bareEncoding.js", "path": "/lib/bare/bundles/bareEncoding.js",
"keys": [ "keys": [
@@ -43,12 +49,6 @@
"bareEvents" "bareEvents"
] ]
}, },
{
"path": "/lib/bare/bundles/protomux.js",
"keys": [
"protomux"
]
},
{ {
"path": "/lib/bare/bundles/barePath.js", "path": "/lib/bare/bundles/barePath.js",
"keys": [ "keys": [
@@ -109,12 +109,6 @@
"bareAsyncHooks" "bareAsyncHooks"
] ]
}, },
{
"path": "/lib/bare/bundles/fetch.js",
"keys": [
"fetch"
]
},
{ {
"path": "/lib/bare/bundles/bareAtomics.js", "path": "/lib/bare/bundles/bareAtomics.js",
"keys": [ "keys": [
@@ -127,6 +121,12 @@
"bareAssert" "bareAssert"
] ]
}, },
{
"path": "/lib/bare/bundles/fetch.js",
"keys": [
"fetch"
]
},
{ {
"path": "/lib/bare/bundles/bareBmp.js", "path": "/lib/bare/bundles/bareBmp.js",
"keys": [ "keys": [
@@ -175,24 +175,18 @@
"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": [
"bareBundleId" "bareBundleId"
] ]
}, },
{
"path": "/lib/bare/bundles/bareDebugLog.js",
"keys": [
"bareDebugLog"
]
},
{ {
"path": "/lib/bare/bundles/bareChannel.js", "path": "/lib/bare/bundles/bareChannel.js",
"keys": [ "keys": [
@@ -205,6 +199,12 @@
"bareDelta" "bareDelta"
] ]
}, },
{
"path": "/lib/bare/bundles/bareDaemon.js",
"keys": [
"bareDaemon"
]
},
{ {
"path": "/lib/bare/bundles/bareDns.js", "path": "/lib/bare/bundles/bareDns.js",
"keys": [ "keys": [
@@ -217,6 +217,12 @@
"bareDiagnosticsChannel" "bareDiagnosticsChannel"
] ]
}, },
{
"path": "/lib/bare/bundles/bareCov.js",
"keys": [
"bareCov"
]
},
{ {
"path": "/lib/bare/bundles/bareEnv.js", "path": "/lib/bare/bundles/bareEnv.js",
"keys": [ "keys": [
@@ -229,36 +235,12 @@
"bareExif" "bareExif"
] ]
}, },
{
"path": "/lib/bare/bundles/bareCov.js",
"keys": [
"bareCov"
]
},
{ {
"path": "/lib/bare/bundles/bareDgram.js", "path": "/lib/bare/bundles/bareDgram.js",
"keys": [ "keys": [
"bareDgram" "bareDgram"
] ]
}, },
{
"path": "/lib/bare/bundles/bareFfmpeg.js",
"keys": [
"bareFfmpeg"
]
},
{
"path": "/lib/bare/bundles/bareFfmpegEncodings.js",
"keys": [
"bareFfmpegEncodings"
]
},
{
"path": "/lib/bare/bundles/bareFormat.js",
"keys": [
"bareFormat"
]
},
{ {
"path": "/lib/bare/bundles/bareFormData.js", "path": "/lib/bare/bundles/bareFormData.js",
"keys": [ "keys": [
@@ -266,9 +248,21 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareHeif.js", "path": "/lib/bare/bundles/bareFfmpegEncodings.js",
"keys": [ "keys": [
"bareHeif" "bareFfmpegEncodings"
]
},
{
"path": "/lib/bare/bundles/bareFfmpeg.js",
"keys": [
"bareFfmpeg"
]
},
{
"path": "/lib/bare/bundles/bareFormat.js",
"keys": [
"bareFormat"
] ]
}, },
{ {
@@ -283,6 +277,12 @@
"bareGif" "bareGif"
] ]
}, },
{
"path": "/lib/bare/bundles/bareHeif.js",
"keys": [
"bareHeif"
]
},
{ {
"path": "/lib/bare/bundles/bareGtk.js", "path": "/lib/bare/bundles/bareGtk.js",
"keys": [ "keys": [
@@ -295,42 +295,36 @@
"bareHrtime" "bareHrtime"
] ]
}, },
{
"path": "/lib/bare/bundles/bareFs.js",
"keys": [
"bareFs"
]
},
{ {
"path": "/lib/bare/bundles/bareHttpParser.js", "path": "/lib/bare/bundles/bareHttpParser.js",
"keys": [ "keys": [
"bareHttpParser" "bareHttpParser"
] ]
}, },
{
"path": "/lib/bare/bundles/bareFs.js",
"keys": [
"bareFs"
]
},
{ {
"path": "/lib/bare/bundles/bareIco.js", "path": "/lib/bare/bundles/bareIco.js",
"keys": [ "keys": [
"bareIco" "bareIco"
] ]
}, },
{
"path": "/lib/bare/bundles/bareImageResample.js",
"keys": [
"bareImageResample"
]
},
{
"path": "/lib/bare/bundles/bareInspect.js",
"keys": [
"bareInspect"
]
},
{ {
"path": "/lib/bare/bundles/bareHttp1.js", "path": "/lib/bare/bundles/bareHttp1.js",
"keys": [ "keys": [
"bareHttp1" "bareHttp1"
] ]
}, },
{
"path": "/lib/bare/bundles/bareImageResample.js",
"keys": [
"bareImageResample"
]
},
{ {
"path": "/lib/bare/bundles/bareHttps.js", "path": "/lib/bare/bundles/bareHttps.js",
"keys": [ "keys": [
@@ -338,15 +332,15 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareJpeg.js", "path": "/lib/bare/bundles/bareInspect.js",
"keys": [ "keys": [
"bareJpeg" "bareInspect"
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareIntl.js", "path": "/lib/bare/bundles/bareJpeg.js",
"keys": [ "keys": [
"bareIntl" "bareJpeg"
] ]
}, },
{ {
@@ -355,24 +349,30 @@
"bareIpc" "bareIpc"
] ]
}, },
{
"path": "/lib/bare/bundles/bareIntl.js",
"keys": [
"bareIntl"
]
},
{ {
"path": "/lib/bare/bundles/bareLief.js", "path": "/lib/bare/bundles/bareLief.js",
"keys": [ "keys": [
"bareLief" "bareLief"
] ]
}, },
{
"path": "/lib/bare/bundles/bareLogger.js",
"keys": [
"bareLogger"
]
},
{ {
"path": "/lib/bare/bundles/bareLink.js", "path": "/lib/bare/bundles/bareLink.js",
"keys": [ "keys": [
"bareLink" "bareLink"
] ]
}, },
{
"path": "/lib/bare/bundles/bareLogger.js",
"keys": [
"bareLogger"
]
},
{ {
"path": "/lib/bare/bundles/bareInspector.js", "path": "/lib/bare/bundles/bareInspector.js",
"keys": [ "keys": [
@@ -386,9 +386,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareModuleResolve.js", "path": "/lib/bare/bundles/bareModule.js",
"keys": [ "keys": [
"bareModuleResolve" "bareModule"
] ]
}, },
{ {
@@ -397,24 +397,18 @@
"bareModuleLexer" "bareModuleLexer"
] ]
}, },
{
"path": "/lib/bare/bundles/bareModuleResolve.js",
"keys": [
"bareModuleResolve"
]
},
{ {
"path": "/lib/bare/bundles/bareModuleTraverse.js", "path": "/lib/bare/bundles/bareModuleTraverse.js",
"keys": [ "keys": [
"bareModuleTraverse" "bareModuleTraverse"
] ]
}, },
{
"path": "/lib/bare/bundles/bareModule.js",
"keys": [
"bareModule"
]
},
{
"path": "/lib/bare/bundles/bareNodeFetch.js",
"keys": [
"bareNodeFetch"
]
},
{ {
"path": "/lib/bare/bundles/bareNdk.js", "path": "/lib/bare/bundles/bareNdk.js",
"keys": [ "keys": [
@@ -428,21 +422,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareDev.js", "path": "/lib/bare/bundles/bareNodeFetch.js",
"keys": [ "keys": [
"bareDev" "bareNodeFetch"
]
},
{
"path": "/lib/bare/bundles/bareOs.js",
"keys": [
"bareOs"
]
},
{
"path": "/lib/bare/bundles/bareOpen.js",
"keys": [
"bareOpen"
] ]
}, },
{ {
@@ -458,15 +440,21 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/barePerformance.js", "path": "/lib/bare/bundles/bareOpen.js",
"keys": [ "keys": [
"barePerformance" "bareOpen"
] ]
}, },
{ {
"path": "/lib/bare/bundles/barePng.js", "path": "/lib/bare/bundles/bareOs.js",
"keys": [ "keys": [
"barePng" "bareOs"
]
},
{
"path": "/lib/bare/bundles/barePerformance.js",
"keys": [
"barePerformance"
] ]
}, },
{ {
@@ -476,9 +464,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/barePack.js", "path": "/lib/bare/bundles/barePng.js",
"keys": [ "keys": [
"barePack" "barePng"
] ]
}, },
{ {
@@ -487,18 +475,36 @@
"barePipe" "barePipe"
] ]
}, },
{
"path": "/lib/bare/bundles/barePack.js",
"keys": [
"barePack"
]
},
{ {
"path": "/lib/bare/bundles/barePunycode.js", "path": "/lib/bare/bundles/barePunycode.js",
"keys": [ "keys": [
"barePunycode" "barePunycode"
] ]
}, },
{
"path": "/lib/bare/bundles/bareDev.js",
"keys": [
"bareDev"
]
},
{ {
"path": "/lib/bare/bundles/bareQuerystring.js", "path": "/lib/bare/bundles/bareQuerystring.js",
"keys": [ "keys": [
"bareQuerystring" "bareQuerystring"
] ]
}, },
{
"path": "/lib/bare/bundles/bareNodeRuntime.js",
"keys": [
"bareNodeRuntime"
]
},
{ {
"path": "/lib/bare/bundles/barePrebuild.js", "path": "/lib/bare/bundles/barePrebuild.js",
"keys": [ "keys": [
@@ -512,9 +518,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareNodeRuntime.js", "path": "/lib/bare/bundles/bareProcess.js",
"keys": [ "keys": [
"bareNodeRuntime" "bareProcess"
] ]
}, },
{ {
@@ -524,15 +530,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareProcess.js", "path": "/lib/bare/bundles/bareSdl.js",
"keys": [ "keys": [
"bareProcess" "bareSdl"
]
},
{
"path": "/lib/bare/bundles/barePromClient.js",
"keys": [
"barePromClient"
] ]
}, },
{ {
@@ -542,9 +542,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareRepl.js", "path": "/lib/bare/bundles/barePromClient.js",
"keys": [ "keys": [
"bareRepl" "barePromClient"
] ]
}, },
{ {
@@ -553,18 +553,18 @@
"bareRpc" "bareRpc"
] ]
}, },
{
"path": "/lib/bare/bundles/bareSdl.js",
"keys": [
"bareSdl"
]
},
{ {
"path": "/lib/bare/bundles/bareSemver.js", "path": "/lib/bare/bundles/bareSemver.js",
"keys": [ "keys": [
"bareSemver" "bareSemver"
] ]
}, },
{
"path": "/lib/bare/bundles/bareSidecar.js",
"keys": [
"bareSidecar"
]
},
{ {
"path": "/lib/bare/bundles/bareRun.js", "path": "/lib/bare/bundles/bareRun.js",
"keys": [ "keys": [
@@ -578,27 +578,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareSidecar.js", "path": "/lib/bare/bundles/bareRepl.js",
"keys": [ "keys": [
"bareSidecar" "bareRepl"
]
},
{
"path": "/lib/bare/bundles/bareStream.js",
"keys": [
"bareStream"
]
},
{
"path": "/lib/bare/bundles/bareStorage.js",
"keys": [
"bareStorage"
]
},
{
"path": "/lib/bare/bundles/bareStdio.js",
"keys": [
"bareStdio"
] ]
}, },
{ {
@@ -607,6 +589,12 @@
"bareStringDecoder" "bareStringDecoder"
] ]
}, },
{
"path": "/lib/bare/bundles/bareStorage.js",
"keys": [
"bareStorage"
]
},
{ {
"path": "/lib/bare/bundles/bareSvg.js", "path": "/lib/bare/bundles/bareSvg.js",
"keys": [ "keys": [
@@ -614,9 +602,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareSystemLogger.js", "path": "/lib/bare/bundles/bareStream.js",
"keys": [ "keys": [
"bareSystemLogger" "bareStream"
] ]
}, },
{ {
@@ -626,15 +614,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareTiff.js", "path": "/lib/bare/bundles/bareStdio.js",
"keys": [ "keys": [
"bareTiff" "bareStdio"
]
},
{
"path": "/lib/bare/bundles/bareTap.js",
"keys": [
"bareTap"
] ]
}, },
{ {
@@ -644,15 +626,21 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareThread.js", "path": "/lib/bare/bundles/bareTap.js",
"keys": [ "keys": [
"bareThread" "bareTap"
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareTcp.js", "path": "/lib/bare/bundles/bareTiff.js",
"keys": [ "keys": [
"bareTcp" "bareTiff"
]
},
{
"path": "/lib/bare/bundles/bareSystemLogger.js",
"keys": [
"bareSystemLogger"
] ]
}, },
{ {
@@ -668,15 +656,15 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareTls.js", "path": "/lib/bare/bundles/bareThread.js",
"keys": [ "keys": [
"bareTls" "bareThread"
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareTty.js", "path": "/lib/bare/bundles/bareTcp.js",
"keys": [ "keys": [
"bareTty" "bareTcp"
] ]
}, },
{ {
@@ -692,9 +680,15 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareUnpack.js", "path": "/lib/bare/bundles/bareTty.js",
"keys": [ "keys": [
"bareUnpack" "bareTty"
]
},
{
"path": "/lib/bare/bundles/bareTls.js",
"keys": [
"bareTls"
] ]
}, },
{ {
@@ -704,9 +698,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareWalkHandles.js", "path": "/lib/bare/bundles/bareUnpack.js",
"keys": [ "keys": [
"bareWalkHandles" "bareUnpack"
] ]
}, },
{ {
@@ -716,9 +710,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareUnionBundle.js", "path": "/lib/bare/bundles/bareWalkHandles.js",
"keys": [ "keys": [
"bareUnionBundle" "bareWalkHandles"
] ]
}, },
{ {
@@ -740,9 +734,15 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareWhich.js", "path": "/lib/bare/bundles/bareUnionBundle.js",
"keys": [ "keys": [
"bareWhich" "bareUnionBundle"
]
},
{
"path": "/lib/bare/bundles/bareV8ToIstanbul.js",
"keys": [
"bareV8ToIstanbul"
] ]
}, },
{ {
@@ -752,9 +752,9 @@
] ]
}, },
{ {
"path": "/lib/bare/bundles/bareV8ToIstanbul.js", "path": "/lib/bare/bundles/bareWhich.js",
"keys": [ "keys": [
"bareV8ToIstanbul" "bareWhich"
] ]
}, },
{ {
@@ -769,12 +769,6 @@
"bareXdiff" "bareXdiff"
] ]
}, },
{
"path": "/lib/bare/bundles/bareWs.js",
"keys": [
"bareWs"
]
},
{ {
"path": "/lib/bare/bundles/bareZlib.js", "path": "/lib/bare/bundles/bareZlib.js",
"keys": [ "keys": [
@@ -787,6 +781,12 @@
"bareWorker" "bareWorker"
] ]
}, },
{
"path": "/lib/bare/bundles/bareWs.js",
"keys": [
"bareWs"
]
},
{ {
"path": "/lib/bare/bundles/bareZmq.js", "path": "/lib/bare/bundles/bareZmq.js",
"keys": [ "keys": [
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
import path from 'path' import path from 'path'
import { statSync } from 'fs' import { statSync } from 'fs'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'url'
import os from 'bare-os' import os from 'bare-os'
function cwd() { function cwd() {
@@ -1,9 +1,46 @@
import { readFile } from 'fs/promises' import { readFile } from 'fs/promises'
import path from 'path' import path from 'path'
import b4a from 'b4a'
/**
* @returns {Promise<((cmd: string, args: string[], opts?: object) => object) | null>}
*/
async function loadSpawnSync() {
try {
const m = await import('bare-subprocess')
if (m && typeof m.spawnSync === 'function') return m.spawnSync
} catch {
/* bare-subprocess missing or native addon unavailable */
}
try {
const m = await import('child_process')
if (m && typeof m.spawnSync === 'function') return m.spawnSync
} catch {
/* Node child_process unavailable */
}
return null
}
/**
* @param {object} r spawnSync result (Node or bare-subprocess)
* @param {'stdout' | 'stderr'} key
*/
function pipeText(r, key) {
const v = r[key]
if (v == null) return ''
if (typeof v === 'string') return v
try {
return b4a.toString(v)
} catch {
return String(v)
}
}
/** /**
* When `pear.multisig.json` exists next to the kernel tree, validate shape and log status. * 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). * Mirrors Holepunch `pear-multisig-link` style metadata (operational hint, not cryptography).
* Subprocess: prefers **`bare-subprocess`** (Pear/Bare); falls back to **`child_process`** on Node.
* Do not use **`node:child_process`** it is not resolvable under Bare.
* @param {string} kernelRoot * @param {string} kernelRoot
*/ */
export async function logPearMultisigKernelHint(kernelRoot) { export async function logPearMultisigKernelHint(kernelRoot) {
@@ -24,6 +61,33 @@ export async function logPearMultisigKernelHint(kernelRoot) {
console.log( console.log(
`[seeder] pear.multisig.json OK (${signers.length} signers, quorum ${quorum})` `[seeder] pear.multisig.json OK (${signers.length} signers, quorum ${quorum})`
) )
if (
process.env.BARE_OS_HYPER_MULTISIG_VERIFY === '1' ||
process.env.BARE_OS_HYPER_MULTISIG_VERIFY === 'true'
) {
const spawnSync = await loadSpawnSync()
if (!spawnSync) {
console.warn(
'[seeder] hyper-multisig verify skipped (no subprocess: install bare-subprocess or use Node)'
)
return
}
const r = spawnSync('hyper-multisig', ['verify', f], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe']
})
if (r.error) {
console.warn(
'[seeder] hyper-multisig verify skipped (CLI not on PATH):',
r.error.message
)
} else if (r.status !== 0) {
const msg = (pipeText(r, 'stderr') || pipeText(r, 'stdout')).trim().slice(0, 400)
console.warn('[seeder] hyper-multisig verify failed:', msg || `exit ${r.status}`)
} else {
console.log('[seeder] hyper-multisig verify OK')
}
}
} catch (e) { } catch (e) {
if (/** @type {NodeJS.ErrnoException} */ (e).code === 'ENOENT') return if (/** @type {NodeJS.ErrnoException} */ (e).code === 'ENOENT') return
console.warn('[seeder] pear.multisig.json:', e?.message || e) console.warn('[seeder] pear.multisig.json:', e?.message || e)
+1
View File
@@ -10,6 +10,7 @@
"pear:dev": "node ../../scripts/ensure-pear-node-modules.mjs packages/bare-os-seeder && pear run --dev ." "pear:dev": "node ../../scripts/ensure-pear-node-modules.mjs packages/bare-os-seeder && pear run --dev ."
}, },
"dependencies": { "dependencies": {
"bare-subprocess": "^5.2.3",
"bare-os": "^3.8.7", "bare-os": "^3.8.7",
"bare-os-protocol": "*", "bare-os-protocol": "*",
"b4a": "^1.6.7", "b4a": "^1.6.7",
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env node
/**
* Release checklist: workspace tests + kernel/seeder parity.
* Optional: set CHECK_DOC_LINKS=1 to grep handbook for broken relative links (heuristic).
*/
import { spawnSync } from 'node:child_process'
import { readFileSync, readdirSync, statSync } from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
function run(cmd, args, opts = {}) {
const r = spawnSync(cmd, args, {
cwd: root,
stdio: 'inherit',
...opts
})
if (r.status !== 0) process.exit(r.status ?? 1)
}
console.log('[release-checklist] npm test')
run('npm', ['test'])
console.log('[release-checklist] verify-kernel-seeder-parity')
run('node', ['scripts/verify-kernel-seeder-parity.mjs'])
if (process.env.CHECK_DOC_LINKS === '1') {
console.log('[release-checklist] doc link heuristic (CHECK_DOC_LINKS=1)')
const handbook = path.join(root, 'handbook')
const ref = path.join(root, 'docs', 'reference')
/** @param {string} dir */
function walkMd(dir) {
const out = []
for (const name of readdirSync(dir)) {
const p = path.join(dir, name)
const st = statSync(p)
if (st.isDirectory()) out.push(...walkMd(p))
else if (name.endsWith('.md')) out.push(p)
}
return out
}
let bad = 0
const linkRe = /\]\(([^)#\s]+\.(md|json))(?:#[^)]*)?\)/g
for (const file of [...walkMd(handbook), ...walkMd(ref)]) {
const text = readFileSync(file, 'utf8')
let m
while ((m = linkRe.exec(text))) {
const target = m[1]
if (target.startsWith('http')) continue
const resolved = path.normalize(path.join(path.dirname(file), target))
try {
statSync(resolved)
} catch {
console.warn(`[release-checklist] missing link target: ${path.relative(root, file)} -> ${target}`)
bad++
}
}
}
if (bad) process.exit(1)
}
console.log('[release-checklist] done')