This is the operator handbook for driving a hyper-derp relay through the einheit CLI. The hyper-derp deb bundles einheit at /usr/bin/einheit and the hdcli wrapper at /usr/bin/hdcli — typical operators just run hdcli. This handbook covers the underlying einheit invocation for cases where you need finer control (different role, custom endpoints, scripting).
einheit (DEALER) ────► hyper-derp (ROUTER on ipc:///tmp/einheit/hd-relay.ctl)
◄─── catalog (describe handshake)
◄─── command responses
◄──── events (PUB on ipc:///tmp/einheit/hd-relay.pub)
The CLI knows almost nothing about the daemon at compile time. On startup it issues a describe call and merges the daemon's command catalog into its tree, which is why a hyper-derp release that adds a new wire verb shows up in the CLI immediately — no client rebuild.
- Daemon listening on its einheit endpoints (
einheit.ctl_endpoint/einheit.pub_endpointindist/hyper-derp.yaml). einheitbinary, with--adapter hd-relayselected.EINHEIT_ROLEset when you need anything beyondshow status/show peers/show peer. The daemon enforces an advisory role gate — see Roles below.
EINHEIT_ROLE=admin einheit \
--adapter hd-relay \
--endpoint ipc:///tmp/einheit/hd-relay.ctl \
--event-endpoint ipc:///tmp/einheit/hd-relay.pubThe hyper-derp deb ships a wrapper at /usr/bin/hdcli that
bakes those flags in (and exports the chafa-branded banner):
hdcli # interactive REPL, admin role
hdcli show status # one-shot
hdcli show fleet
hdcli wg show # wg-relay-mode counters (mode: wireguard only)~/.ssh/config entries on the operator workstation:
Host hd-r1-cli
HostName 192.168.122.13
User worker
IdentityFile ~/.ssh/id_ed25519_targets
RequestTTY yes
RemoteCommand /usr/bin/hdcli
Then:
ssh hd-r1-cli # drops into the einheit shell
ssh hd-r1 hdcli show status # oneshot via plain sshEINHEIT_ROLE selects what the daemon will let you do:
| Value | Permits |
|---|---|
| (unset) | show status, show peers, show peer |
operator |
+ show audit/counters/config/fleet/commits/commit, |
all peer ... verbs |
|
admin |
+ the candidate-config lifecycle, relay init, |
daemon restart / daemon stop |
--role admin on the command line is equivalent to setting the env. The gate is advisory — the daemon trusts the caller's stated role; a SO_PEERCRED-based check is a future iteration.
| Path | Role | What |
|---|---|---|
show status |
any | Worker count, byte counters, hd peer count |
show peers |
any | All HD peers with state / fd / peer_id |
show peer <key> |
any | One peer's detail + policy + forwarding rules |
show fleet |
operator | Self relay_id + remote relay-table entries + hops |
show config |
operator | Redacted runtime config snapshot |
show config <pre> |
operator | Same, filtered by path prefix |
show counters |
operator | Per-worker recv / send / drops + HD enroll counters |
show audit |
operator | Last 32 routing-policy decisions |
show commits |
operator | Persisted commit log (id, ts, set/delete counts) |
show commit <id> |
operator | One commit with diff vs. cumulative prior state |
show schema |
(local) | Every configurable path with type + help text |
show env |
(local) | Terminal caps, theme, aliases, target |
show schema and show env are framework-local — no wire round-trip.
Keys are accepted as ck_<hex>... (Curve25519 client key), rk_<hex>... (relay key), or raw 64-hex.
Live-mutable settings go through a Junos-style candidate workflow:
configure # opens a session, returns session_id
set <path> <value> # validates + stages
delete <path> # stages a default-restore
commit # applies + persists, ends the session
rollback candidate # discards staged changes
rollback previous # synthesizes an inverse of the last commit
Both dotted and space-separated paths are accepted equivalently:
set hd.relay_policy.max_direct_peers 64
set hd relay_policy max_direct_peers 64
Validation runs at set time; the daemon refuses values that don't match the path's type. Apply runs at commit time, under the relevant locks (e.g. hd_peers.mutex for HD-policy fields).
| Path | Type | Default |
|---|---|---|
log_level |
enum: trace/debug/info/warn/error/critical/off | info |
hd.enroll_mode |
enum: auto / manual | manual |
hd.relay_policy.default_mode |
enum: prefer_direct/require_direct/prefer_relay/require_relay | prefer_direct |
hd.relay_policy.forbid_direct |
bool | false |
hd.relay_policy.forbid_relayed |
bool | false |
hd.relay_policy.max_direct_peers |
int ≥ 0 | 0 (unlimited) |
hd.relay_policy.audit_relayed_traffic |
bool | true |
hd.federation.fleet_id |
string | "" |
peer_rate_limit |
uint64 | 0 (unlimited) |
Anything not in the table needs a yaml edit + restart — set will refuse it with not_live_mutable and a hint. Identity fields (port, workers, tls_cert/key, hd.relay_key, hd.relay_id) are deliberately excluded.
Set einheit.commit_log_path: <file> in the daemon config to keep a tab-separated record of every commit:
1 2026-04-23T18:22:41.340Z S hd.relay_policy.forbid_relayed true ...
2 2026-04-23T18:23:09.124Z S hd.relay_policy.max_direct_peers 256 ...
Replayed at startup (last-write-wins per key), so committed changes survive restarts. Partial last lines from a power cut are dropped silently.
rollback candidate discards an in-flight session — no wire effect.
rollback previous builds a new commit whose ops invert the last record: keys it set get their prior value back (or a delete to default if there was no prior); keys it deleted get restored. The inverse is itself a commit, so rollback previous is idempotent and shows up in show commits.
rollback previous requires a non-empty commit log (it has nothing to invert without history).
| Path | What |
|---|---|
peer approve <key> |
Move a pending peer to approved |
peer deny <key> |
Reject a pending peer |
peer revoke <key> |
Denylist + disconnect |
peer redirect <key> <dst> |
Send a Redirect frame to the peer |
peer policy set <key> <intent> |
Pin a routing intent on a peer |
peer policy clear <key> |
Remove the pin |
peer rule add <src> <dst> |
Add a forwarding rule |
<intent> is one of prefer_direct, require_direct, prefer_relay, require_relay.
The CLI has both wire and local verbs for service management. Wire verbs work over the einheit channel and are dispatched by the daemon; local verbs shell out to systemctl --user hyper-derp.
| Path | Where | When to use |
|---|---|---|
daemon restart |
wire | Daemon is up; you want to restart it. Reply flushes before exit; the CLI's DEALER auto-reconnects to the new process. |
daemon stop |
wire | Daemon is up; you want it down. |
daemon start |
local | Daemon is down; can't be a wire verb. |
daemon status |
local | Cheaper than a round-trip; works when daemon is unreachable. |
Both wire verbs require admin role and prompt for confirmation. The CLI's DEALER socket is restart-tolerant by design — daemon restart does not end your session.
The daemon's PUB socket emits hierarchical topics. Subscribe with watch <command> from the shell or with any external ZMQ SUB consumer.
| Topic prefix | Body |
|---|---|
state.metrics.{recv_bytes,...} |
Cumulative counters (10× per second) |
state.peers.<ck_hex> |
`key=...\nstate=approved |
state.config.committed |
commit_id=N\n |
state.config.rolled_back |
`scope=candidate |
/tmp is tmpfs on most distros; the daemon's IPC socket directory got wiped and never recreated. Use the user-mode unit at dist/hyper-derp.user.service (it has ExecStartPre=mkdir -p /tmp/einheit) or create the directory before starting the daemon.
Set tls_cert / tls_key in the yaml. The daemon will not accept HD peers over plaintext.
tls kernel module isn't loaded. sudo modprobe tls, then make it persistent with echo tls | sudo tee /etc/modules-load.d/tls.conf.
Daemon-side role gate. Set EINHEIT_ROLE=admin (or --role admin).
The path you tried to set requires a yaml edit + daemon restart. The path table above shows what is and isn't live-mutable.
Pre-existing daemon shutdown bug — SIGTERM handler is installed but something in the cleanup path hangs. Workaround: the user-mode unit sets TimeoutStopSec=5s so systemd SIGKILLs sooner. Filed as a follow-up.
You're on an old einheit-cli (pre-a0dcf0b). Update the binary; recent versions fall back to schema path-completion when the partial doesn't name a leaf.
| Path | What |
|---|---|
~/hd/hyper-derp.yaml |
Daemon config |
~/hd/commits.log |
Persisted candidate-config commit log |
~/hd/audit.log |
Routing-policy decision audit |
~/hd/cert.pem / ~/hd/key.pem |
kTLS material |
~/.config/systemd/user/hyper-derp.service |
User-mode unit |
/tmp/einheit/hd-relay.ctl |
Control IPC socket (ROUTER ↔ DEALER) |
/tmp/einheit/hd-relay.pub |
Event PUB socket |