Install the binaries
Each release shipsoh (the CLI) and ohd (the daemon) as
self-contained executables — the bundled runtime, the daemon, the web
app, and the compiled SQLite addon are all inside the binary. No
Node.js required.
- macOS
- Linux
- Docker
~/.local/bin. Binaries currently ship for Apple silicon
(mac-arm64).<state dir>/sea/<build>/ — a native module can only load from disk —
with checksum-verified, crash-safe unpacking; set
OH_DAEMON_UNPACK_DIR to move that base.
The service lifecycle
ohd install writes a user service unit — a launchd LaunchAgent
on macOS, a systemd user unit on Linux — and persists the config flags
it is given into daemon.json. Re-run it with new flags at any time
to reconfigure: an omitted flag keeps its persisted value
(--no-trusted-proxy / --no-allow-insecure-lan clear the booleans),
and ohd restart applies the result. ohd start is a no-op while the
service already runs.
On Linux, install also enables the unit for boot and turns on user
lingering (systemctl --user enable oh-daemon.service,
loginctl enable-linger) so the daemon survives reboots and outlives
the SSH session that installed it; if either command needs privileges,
the exact manual command is printed instead. On macOS the LaunchAgent
starts at login.
Foreground instead
ohd run runs the daemon in the foreground — it is exactly what the
service unit execs. Ctrl-C / SIGTERM shuts it down cleanly. Handy
under container supervisors and for trying things out before
installing a service unit.
Where everything lives
Move the data dir with
--data-dir (persisted like every install
flag). Everything the daemon persists lives under it.
Configuration precedence
Precedence, highest first: argv → env →daemon.json → defaults.
daemon.json is the durable configuration — ohd install writes the
flags it is given into it, the service unit carries only --config,
and every ohd command (status, show-token, the daemon boot
itself) reads the same file.
Runtime settings are separate
Runtime settings live instorage.json, not daemon.json. The CLI
exposes the MCP switches and the update switch, all off by default:
mcp.enabled, mcp.allowObserve, mcp.allowWrite,
mcp.allowExecute, mcp.allowSecrets, updates.autoUpdate. Bind and
network options are not settings — they persist through the
ohd install flags above. config set refuses while the daemon runs
(storage.json is single-writer); a running daemon takes settings
changes from a connected admin surface instead. Reads work anytime.
The web app
The daemon serves the Open Headers web app — the same Workbench UI the desktop app and extension run — as static files on its bind: openhttp://<daemon-host>:8137/ in a browser. Distributions built with
the web bundle serve it out of the box; point --web-root at a
different built bundle to serve that instead. An explicitly configured
web root must contain an index.html, or the daemon refuses to boot;
without any web root the daemon runs headless-only and / answers 400.
Next
- Making it reachable beyond loopback is a deliberate step: LAN vs TLS proxy.
- Clients join with a token: Tokens & pairing.