ohd running as a service, an
administrator account you created in the browser, and the browser
extension, desktop app, or CLI syncing against your own server. No
account with us, no cloud relay — the daemon makes zero outbound
connections except ones you configure.
Install
Each release shipsoh (the CLI) and ohd (the daemon) as
self-contained executables — no Node.js required. The same binaries
are downloadable from the
releases page.
- macOS
- Linux
- Docker
The install script verifies SHA-256 checksums and installs Binaries currently ship for Apple silicon (
oh
and ohd to ~/.local/bin:mac-arm64). The
service lifecycle below runs on launchd — the LaunchAgent starts
at login.Start the service
install also enables the unit for boot and turns on user
lingering, so the daemon survives reboots and outlives the SSH session
that installed it; if a step needs privileges, the exact manual command
is printed instead. On macOS the LaunchAgent starts at login.
Claim the server
A fresh server has no users and no administrator. The first browser to reach it creates one — open it on the machine that runs it:ohd status repeats it for as long
as the server stays unclaimed:
ohd status.
Once the claim succeeds the setup screen is gone for good: every later
browser is asked to sign in, a second claim is refused, and further
accounts are created from the admin console or provisioned through
SSO.
There is no self-service password reset on the sign-in page. If the
first administrator’s password is lost, reset it on the server with
ohd user set-password <email> (daemon stopped).Claiming a headless server
Most servers have no browser on them. Two ways in, in order of preference:- Forward loopback over SSH.
ssh -L 8137:127.0.0.1:8137 you@server, then openhttp://127.0.0.1:8137/on your own machine. The daemon sees a loopback peer and the browser sees a secure origin, so this needs no setup code and no TLS. - Put a TLS reverse proxy in front first, then claim at
https://<your-host>/with the setup code fromohd status— see LAN vs TLS proxy.
http://<server>:8137/ cannot claim the server:
the browser refuses to start the web app on a non-loopback HTTP origin
at all (see below).
If the claim unpairs a device
Tokens minted withohd show-token before the claim are unbound —
they act as the server operator, which after a claim would be a way
around the administrator you just created. So the claim revokes every
one of them and the setup screen says how many devices it unpaired.
Pair them again from the admin console.
Connect a client
A person signs in from their client on the server’s own page. In the browser extension or the desktop app, open Settings → Backup and Sync → Sign in to a server…, add the server’s address, and click Sign in on …: the server’s page opens, you sign in there with your email and password, approve the device, and the client is signed in as you. Those clients dialws://<server>:8137 from any machine on
the network, with no TLS involved — the normal way to use a headless
server. For the CLI:
oh connect --token <secret>. See
Tokens & pairing.
The daemon serves the Workbench as a web app on its bind, but a
browser only runs it from a secure origin: http://127.0.0.1:8137/
on the server’s own machine, or https://… behind a
TLS reverse proxy. A plain LAN URL like
http://<server>:8137/ loads and then refuses to start — that is a
browser rule (the Workbench needs crypto.subtle, which browsers
withhold on non-loopback HTTP), and it applies to the web app only, not
to the extension, desktop, or CLI clients.
The CLI installs on any client machine — the same script as above on
macOS and Linux (without --with-daemon), or the PowerShell script on
Windows:
Reach it from the LAN
The daemon binds127.0.0.1:8137 by default — loopback only. To make
it LAN-reachable you must also say how the connection is protected:
either a TLS-terminating reverse proxy in front, or an explicit
acknowledgment that cleartext on a trusted network is acceptable:
0.0.0.0 bind refuses to boot rather than
serve auth tokens and pairing secrets unencrypted by accident.
ohd install only persists the flags — a service that is already
running keeps its old bind until ohd restart. With the LAN bind in
effect and clients still unable to connect, they are being stopped
before the daemon (which logs every connection it refuses, so a failure
with no log line never reached it): check that the host firewall
(ufw/firewalld) admits port 8137.
For anything beyond a trusted LAN, terminate TLS at a reverse proxy —
Caddy makes it two lines:
--trusted-proxy semantics — is
LAN vs TLS proxy.
Reconfigure at any time
daemon.json is the durable configuration. ohd install persists the
flags it is given into it and may be re-run at any time to reconfigure —
an omitted flag keeps its persisted value — and ohd restart applies
the result. Runtime settings (like the MCP switches) are separate: