6.7 KiB
Chapter 1 — Two runtimes: host (Pear/Node) vs in-image (AsyncFunction)
If you only remember one thing from this guide, remember this: Bare OS runs two different kinds of JavaScript, and they follow different rules.
The confusion in one sentence
You might paste a file into your home directory on the personal Hyperdrive that starts with import fs from 'node:fs' and expect it to run like a Node script. It will not. That file is loaded as a string and executed with the JavaScript AsyncFunction constructor—not as an ES module. There is no module graph, no import resolution, and no automatic node_modules on the drive.
The booter and seeder Pear packages, by contrast, are normal ESM projects: they use import, npm dependencies, and Pear bundling. They run on the host and host the environment that evaluates in-image code.
When the bare-os npm module is available on that host, the booter may attach a read-only ctx.bareOsHostStats snapshot (loadavg, cpus, networkInterfaces, …) and write /proc/bare_os/host_os.json (schema 2, including bare-os 3.9+ and optional bare-posix fields)—still not a general “run Node in the image” escape hatch; see Chapter 2.
Mental model: who loads whom
flowchart TB
subgraph host [Host process Pear or Node]
booterPkg[bare-os-booter package]
seederPkg[bare-os-seeder package]
esm[import and node_modules]
end
subgraph hyper [Hyperdrives]
sys[System drive]
pers[Personal drive]
end
subgraph evalLayer [Evaluated inside booter VM]
kernel["start ctx from boot/init.js"]
binutil["run ctx argv from bin/*"]
userscript["run ctx argv from home script.js"]
end
booterPkg --> esm
booterPkg --> sys
booterPkg --> pers
booterPkg -->|runKernelFromSource| kernel
booterPkg -->|runBinCommand| binutil
booterPkg -->|runBinCommand| userscript
- Host code lives under
packages/bare-os-booter/,packages/bare-os-seeder/, etc. It is trusted in the sense that you built or installed it; it opens Corestore, Hyperswarm, Hyperdrive, and constructsctx. - In-image code is bytes on a drive (
/boot/init.js,/bin/cat,~/mytool.js). The booter reads those bytes as UTF-8 strings and passes them tonew AsyncFunction(...)(seekernel-runner.js).
In-image execution (the AsyncFunction contract)
Two entry shapes matter:
async function start(ctx)—/boot/init.json the system drive —ctx—runKernelFromSourcewraps the source and callsstart(ctx)async function run(ctx, argv)(optional for user scripts) —/bin/*always; or a*.jsfile resolved from the shell —ctx,argv(string array) —runScriptFromSourceruns the file body, then awaitsrun(ctx, argv)if defined
The booter injects ctx and argv. Kernel start is required; for shell scripts, top-level statements may stand alone, or you may define run like /bin utilities. Top-level import is invalid in that evaluated string because the engine is not loading an ES module—it is compiling a function body.
Shebang lines (#!/usr/bin/env bare) are stripped before compile (stripShebang) so the first token the parser sees is valid JavaScript.
Host execution (Pear / Node packages)
When you edit packages/bare-os-booter/index.js, you are writing normal JavaScript for Node or Bare under Pear:
- Use
import Hyperdrive from 'hyperdrive'. - Add dependencies in
package.json. - Use async I/O against real host APIs.
Web Encoding globals: some Bare/Pear builds do not define global TextEncoder / TextDecoder. Booter and in-image code should use b4a for UTF-8 instead (e.g. b4a.from(str, 'utf8'), b4a.toString(buf, 'utf8')), matching curl-cli.js. Relying on new TextEncoder() in booter lib/*.js can break at runtime (for example when statting or reading pseudo files under /proc or /sys).
This code creates ctx and passes it into the kernel. It does not run inside the simulated /bin environment unless you explicitly call runBinCommand(ctx, argv) with the same ctx the shell uses.
Trust: system drive vs personal drive
- The system drive is the replicated OS image:
/boot,/bin,/etc,/share. You should treat its contents as integrity-checked by replication from peers you chose to trust (same discovery key / topic as the rest of the project). - The personal drive holds
$HOME,/.bare, user files, crontab, etc. It is writable by the session. User scripts you write live here by default.
A script you place in ~/exploit.js is your code; the booter will still AsyncFunction-evaluate it with full ctx power. That is convenient and dangerous—see Chapter 9.
Host source policy (Bare-first)
Pear-bundled booter and seeder runtime sources under packages/bare-os-booter/lib/ and packages/bare-os-seeder/lib/ must stay resolvable on Bare as well as Node: CI runs scripts/verify-pear-no-static-node-import.mjs, which rejects from 'node:…' imports, require('node:…'), and import('node:…') in those trees (with path-based exceptions). Use #host-fs, **#host-path**, and **#host-fs-promises** from package imports instead of bare fs / path specifiers. The only deliberate node:module usage today lives in bare-os-boot-manifest-sig.node.js (Node default entry for boot manifest verification); the bare export uses bare-os-boot-manifest-sig.bare.js. For guest-visible behavior, prefer Holepunch **bare-*** modules per Node → Bare module map.
When to use which runtime
- Add a new
/bincommand shipped with the OS image — In-image pattern: coreutilssrc/*.js+ build (Chapter 6) - One-off automation in your home directory — In-image script (top-level and/or optional
run); noimport(Chapters 4–5) - Change how networking, HDMS, or the REPL works — Host booter package (ESM)
- Publish or replicate the system image — Host seeder package