Skip to content

Quickstart

This walkthrough takes you from built binaries to a live end-to-end HTTP request over openhost. You’ll need two machines and about five minutes.

  • The three binaries from Installopenhostd, openhost-dial, openhost-resolve — on your PATH.
  • A host machine (the computer that runs the service you want to reach) with an HTTP service listening on 127.0.0.1. Any will do; for this walkthrough we’ll use python3 -m http.server 8000 in an empty directory.
  • A client machine (a different computer, ideally on a different network) with at least openhost-dial installed.

Both machines need outbound internet; inbound port forwarding is not required.

In one terminal on the host machine:

Terminal window
mkdir /tmp/openhost-demo && cd /tmp/openhost-demo
echo "hello from openhost" > index.html
python3 -m http.server 8000

This exposes a trivial HTTP service on http://127.0.0.1:8000/.

Create ~/.config/openhost/daemon.toml on the host machine:

[identity]
store = { kind = "fs", path = "~/.config/openhost/identity.key" }
[pkarr]
# Relays default to the bundled public list; leaving this empty uses them.
relays = []
republish_secs = 1800
[pkarr.offer_poll]
# Start permissive for the smoke test. Flip this to `true` after step 5
# and pair your client explicitly.
enforce_allowlist = false
# Poll known client zones once per second for offer records.
poll_secs = 1
# Add your client's pubkey here after step 3 so the daemon knows
# whose zone to watch. Until then the list is empty.
watched_clients = []
[dtls]
cert_path = "~/.config/openhost/dtls.pem"
[forward]
# Point this at the upstream service you started in step 1.
target = "http://127.0.0.1:8000"

The daemon expands ~ automatically; the parent directory is created on first run.

Terminal window
openhostd run

On first boot the daemon generates a fresh Ed25519 identity, a self-signed DTLS certificate, and publishes a signed record to the public Pkarr relays. Watch the log for:

INFO openhost_daemon::app: openhostd: DTLS certificate generated
INFO openhost_pkarr::publisher: openhost-pkarr: initial publish succeeded attempt=1
INFO openhost_daemon::app: openhostd: up pubkey=… dtls_fp=…

The pubkey=… value on the “up” line is what the client will dial. Copy it.

In a separate terminal on the host:

Terminal window
openhostd identity show

prints the same pubkey in a predictable location. Keep that terminal open; you’ll need it.

Switch to the client machine. In a terminal:

Terminal window
openhost-dial oh://<paste-pubkey-here>/

You should see the response on stderr (status line + headers) and the body on stdout:

openhost-dial: client_pk=<ephemeral-client-pk> dialing GET
HTTP/1.1 200 OK
Content-Type: text/html
Server: SimpleHTTP/0.6 Python/3.12.0
<!DOCTYPE html>
<html>...<li><a href="index.html">index.html</a></li>...

That’s a real HTTP request, traversing the client’s NAT, hole-punched through WebRTC, authenticated with channel binding, forwarded to the upstream service on 127.0.0.1:8000 by the daemon.

5. Pair the client (switch to enforced mode)

Section titled “5. Pair the client (switch to enforced mode)”

Ephemeral keys are fine for the smoke test; for actual use the daemon should only accept your client’s identity. openhost-dial accepts a 32-byte raw Ed25519 seed via --identity, in the same on-disk format the daemon’s filesystem identity store uses.

On the client machine:

Terminal window
# Persist a client-side identity seed.
install -d -m 0700 ~/.config/openhost
dd if=/dev/urandom of=~/.config/openhost/client.key bs=32 count=1 2>/dev/null
chmod 0600 ~/.config/openhost/client.key
# Dial once with --identity so the log line prints the matching pubkey.
# The first line openhost-dial writes to stderr is:
# openhost-dial: client_pk=<zbase32-pubkey> dialing GET
openhost-dial --identity ~/.config/openhost/client.key oh://<daemon-pubkey>/ 2>&1 | head -1

Copy the <zbase32-pubkey> from that log line. That’s your stable client identity; every --identity dial with the same seed file yields the same pubkey.

There is no standalone keygen CLI at v0.1.0 — deriving the pubkey by dialing once (even if the dial itself fails because the client isn’t paired yet) is the supported workflow. A dedicated helper may be added in a future release.

On the host machine:

Terminal window
openhostd pair add <client-pubkey-zbase32>

The CLI prints a one-line confirmation noting that the running daemon will pick this up automatically — the pair-DB file watcher reloads within ~250 ms without a SIGHUP.

Then tighten the daemon config:

[pkarr.offer_poll]
enforce_allowlist = true # was: false
watched_clients = ["<client-pubkey-zbase32>"]

Restart the daemon so the new watched_clients takes effect (the file-watcher reloads pair entries, not offer-poll config). From now on, only paired clients succeed.

If a dial fails, run the debug resolver first:

Terminal window
openhost-resolve oh://<daemon-pubkey>/ --json

This fetches the host’s signed pkarr record and prints the decoded contents. If this succeeds, discovery is working and the problem is downstream. See Troubleshooting for what to check next.

At v0.1.0 a full WebRTC answer SDP — after ICE trickles fully — can still exceed the BEP44 1000-byte packet budget on some configurations, even after PR #15’s answer-record fragmentation. If openhost-dial reliably returns PollAnswerTimeout against a real-world daemon, that’s the residual size gap; it is the next line item after Phase 2 on ROADMAP.md. For v0.1.0 testing, the server-side flow is still fully exercised and the daemon’s log will confirm the answer was queued.