OpenDWeb v0.4.2: better device networking, simpler server setup
The first GitHub Release on the opendweb repo, with all six npm packages published. OpenDWeb builds application-level networks: devices join by invitation and the network carries only this application's traffic. One command starts the server, clients configure a single entry, and QUIC direct links come first with a self-hostable relay fallback.
Эта запись написана на английский. Читать на китайский →
$ curl http://localhost:8787/healthz
200
OpenDWeb 0.4.2 is out (2026-09-06, the first GitHub Release on jixoai/opendweb). Machines running the same application form one logical network: a device joins by invitation, and the network carries only this application's traffic, so you never install a system-level VPN on the whole machine just to get one connection. All six npm packages are published. This post walks through them by theme.
One command for the server, one entry for clients
Two machines running the same application can talk after the server starts once and each client is configured once.
# 1. Start the server (gateway + relay)
npx opendweb server
# or: docker run -p 8787:8787 -p 3340:3340 ghcr.io/jixoai/opendweb:0.4.2
# 2. One-time config per client machine (persisted to ~/.opendweb/config.json)
npx @jixo/opendweb-example config set relay http://192.168.2.13:8787
A client only needs the gateway address (8787). The relay address is discovered through the gateway's /services.json, so no client needs to know that port 3340 exists.
Two curl commands confirm the server: curl http://localhost:8787/healthz returns 200, and curl http://localhost:8787/services.json returns the service manifest. The end-to-end regression manual is EXAMPLE.md (Chinese version); it is walked after every release.
Example: two terminals, redeem an invite and chat
# 3. Terminal A: initialize and keep a chat session running (signs invites)
npx @jixo/opendweb-example init --data ~/.dweb-a
npx @jixo/opendweb-example invite --data ~/.dweb-a --ttl 30m # copy the token
npx @jixo/opendweb-example chat --data ~/.dweb-a
# 4. Terminal B (another directory/device): redeem the invite and chat
npx @jixo/opendweb-example join --data ~/.dweb-b <token>
npx @jixo/opendweb-example chat --data ~/.dweb-b
Devices join by invitation, and failures carry stable error codes
A new device cannot join on its own; it redeems an invitation token. Controlled invitations have two hard conditions: a token redeems exactly once, and the issuer's process stays running during redemption (terminal A above holding its chat session open, for example).
Join failures return stable error codes in the error[join/<code>] format. The full set of six covers a token without a path, an offline issuer, a directory mismatch, an expired token, double redemption, and a malformed token; the fault table is in EXAMPLE.md. NO_REACHABLE_PATH fails instantly with guidance. DIAL_TIMEOUT settles within 30 seconds and reports that the issuer is probably offline.
Mechanism: the four layers and where each gets its trust
Identity: one Ed25519 keypair per device, the EndpointId. It is decoupled from network addresses, so a machine keeps its identity when its IP changes; the display form is a z-base-32 string.
Roster: membership is decided by a set of signed facts (Genesis/Grant/Join/Revoke), content-addressed with BLAKE3, with replicas converging by union-merge. Redemption runs a challenge-response proof of key possession, and the invite_id is consumed by CAS so it redeems exactly once.
Session: iroh 1.1 supplies QUIC direct links, NAT traversal, and the self-hosted relay fallback. Dual ALPN separates regular traffic from redeem traffic, both sides gate before data flows, and per-frame resources are capped.
Sync: opaque envelopes travel in both directions, and the layer above interprets them. The Automerge adapter is a separate, later delivery.
Six npm packages, one job each
| npm package | version | role |
|---|---|---|
opendweb | 0.4.2 | Server CLI: npx opendweb server starts the self-hosted gateway (8787) + relay (3340); plugin marketplace host |
@jixo/opendweb-server-binary | 0.3.2 | Server binary wrapper used by the CLI; also exposes a programmatic startServer() |
@jixo/opendweb-client-sdk | 0.3.2 | Node SDK for embedding the opendweb networking kernel in your own app (napi-rs; darwin-arm64 / win32-x64) |
@jixo/opendweb-example | 0.3.2 | Reference two-process client CLI (init / invite / join / chat) |
@jixo/opendweb-config | 0.1.0 | definePlugin helper for local plugin files (runtime-agnostic: deno / bun / node) |
@jixo/opendweb-ext-cf | 1.0.3 | Cloudflare Tunnel plugin: ingress push via API, DNS routing, and end-to-end verification, with optional co-spawned cloudflared |
Windows artifacts (the dweb-server exe and the napi dll) are built fresh by the tag workflow with mingw cross-compilation and ship inside the npm tarballs. No separate download.
Other changes
Plugin installs pin the version explicitly:
opendweb plugin add cfinstalls the plugin and locksname@versioninto~/.opendweb/plugins.json. An ancestor-directorypackage.jsonwith a stale dependency range can no longer hijack version resolution.The ext-cf v1.0 rewrite: a server without a public IP goes behind a Cloudflare Tunnel. Authentication takes one credential, a single browser login or one pasted API token, where v0.x asked for a management token and a connector token.
cf setupis idempotent and converges toward the desired state on re-run; cloudflared can be left to the plugin; the token paste accepts a whole block or a single entry. The plugin itself has zero runtime dependencies.opendweb cf plan --hostname dweb.example.com # zero-side-effect preview (setup also has --dry-run) opendweb cf setup --hostname dweb.example.com # non-interactive: push ingress via API, route DNS, end-to-end checkThe rewrite went through a Codex-reviewed remediation round covering SDK leaf imports, the config-merge PUT, the OAuth persistence contract, and install-path and race fixes. The control plane defaults to a hand-rolled REST client against the Cloudflare API;
CF_CLIENT=sdkswitches to the official SDK for comparison. Ingress is always pushed as a full replacement ending in ahttp_status:404catch-all; the tunnel's config is owned by opendweb, so do not hand-edit it for other purposes.Release automation: from the next tag on, the release workflow's automated release notes take over generating the changelog.
This is the repo's first release record, so the commit statistics cover the whole history: up to 0.4.2 there are 98 commits, 27 feat / 44 fix / 9 docs / 3 test / 5 chore / 2 refactor, with the remaining 8 being release, style, and openspec bookkeeping (counted by conventional-commit prefix). Breaking changes: 5 commits carry the ! marker. Four are early dweb-fabric kernel reworks, and one is the 0.4.0 cf credential split, which the v1.0 single-credential flow replaces. Readers upgrading ext-cf from v0.x re-authenticate once under the single-credential flow.
Upgrade
npx opendweb@0.4.2 server # or: npm i -g opendweb@0.4.2
npm i @jixo/opendweb-client-sdk@0.3.2
docker run -d -p 8787:8787 -p 3340:3340 ghcr.io/jixoai/opendweb:0.4.2
Images live under ghcr.io/jixoai/opendweb. The 0.4.2 image originally shipped from the pre-rename namespace ghcr.io/gaubee/dweb; every historical tag has since been migrated with digests unchanged. The old location still resolves, but point your pulls at the new one.
Acknowledgements
The session layer stands on iroh 1.1, which provides the QUIC direct links, NAT traversal, and the self-hostable relay fallback. The SDK's bindings come from napi-rs.
Links
Changelog: GitHub Release v0.4.2 (this repo's first; automated afterwards)
Docs: opendweb.jixoai.com · README · Chinese README · end-to-end manual EXAMPLE.md · Chinese version
Upgrade: see the Upgrade section above (npx / npm i / docker, pick one)
Discussion: GitHub Issues
On this blog: OpenIWeb v0.1.0 · OpenTray v0.21.1
Chinese version: /zh/blog/2026-09-06-opendweb-v0-4-2/
