Skip to content

OpenDWeb v0.4.2: logical networks, not a system-level VPN

jixoai v0.4.2 opendweb

The first GitHub Release on the opendweb repo, publishing all six npm packages. OpenDWeb builds application-level networks: devices join by invitation, and the network carries only this application's traffic. The server is one command, clients configure one entry, and QUIC direct comes first with a self-hostable relay fallback.

Эта запись написана на английский. Читать на китайский →

$ curl http://localhost:8787/healthz
200

OpenDWeb v0.4.2 is out (2026-09-06, the first GitHub Release on jixoai/opendweb). It joins several machines into one application-level logical network: a device joins only by invitation, the network carries only this application's traffic, and you do not have to install a system-level VPN on the whole machine and hand over every device's entire traffic just to get one connection.

One command for the server, one entry for clients

Two machines running the same application can talk to each other after starting the server once and configuring each client 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 to know the gateway address (8787). The relay address is discovered automatically through the gateway's /services.json, so no client ever has to know that port 3340 exists.

Two curl commands confirm the server is up: curl http://localhost:8787/healthz returns 200, and curl http://localhost:8787/services.json returns the service manifest. The end-to-end regression has a manual, EXAMPLE.md (Chinese version); walk it 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 the inviter has to be online

A new device cannot walk in on its own; it redeems an invitation token. The inviter's process must be running during redemption (for example, terminal A above holding its chat session open). That is the hard condition behind a controlled invitation: a token can be redeemed once, and the inviter has to be present when it happens.

Join failures carry stable error codes, returned as error[join/<code>]. NO_REACHABLE_PATH fails immediately with guidance. DIAL_TIMEOUT notes that the inviter is probably offline.

Four layers, each with its own job. Each derives its trust from somewhere different.

  • Identity: one Ed25519 keypair per device, which is 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: who is in the network is decided by a set of signed facts (Genesis/Grant/Join/Revoke), content-addressed with BLAKE3, with replicas converging by union-merge. Redeeming an invite uses challenge-response to prove possession of the key, and CAS consumption of the invite_id to guarantee a single redemption.

  • 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 transferring data, and per-frame resources are capped.

  • Sync: opaque envelopes go in both directions, and the layer above interprets them. The Automerge adapter is a separate, later change.

Six npm packages, one job each

npm packageversionrole
opendweb0.4.2Server CLI: npx opendweb server starts the self-hosted gateway (8787) + relay (3340); plugin marketplace host
@jixo/opendweb-server-binary0.3.2Server binary wrapper used by the CLI; also exposes a programmatic startServer()
@jixo/opendweb-client-sdk0.3.2Node SDK for embedding the opendweb networking kernel in your own app (napi-rs; darwin-arm64 / win32-x64)
@jixo/opendweb-example0.3.2Reference two-process client CLI (init / invite / join / chat)
@jixo/opendweb-config0.1.0definePlugin helper for local plugin files (runtime-agnostic: deno / bun / node)
@jixo/opendweb-ext-cf1.0.3Cloudflare Tunnel plugin: ingress push via API, DNS routing, and end-to-end verification, with optional co-spawned cloudflared

Windows artifacts (the dweb-server exe + napi dll) are built fresh by the tag workflow (mingw cross-compilation) and ship inside the npm tarballs. No separate download.

Other changes

  • Plugin installs pin the version explicitly: opendweb plugin add cf installs and locks name@version. An ancestor-directory package.json with a stale dependency range can no longer hijack version resolution.

  • The ext-cf 1.0 rewrite: putting a server without a public IP behind a Cloudflare Tunnel takes one browser login or one pasted API token. The control plane goes through an SDK, cloudflared can be left to the plugin, and the token-paste UX accepts a whole block or a single entry. The rewrite went through a Codex-reviewed remediation round (SDK leaf imports, config-merge PUT, the OAuth persistence contract, install-path and race fixes).

    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 check
    
  • Release automation: from the next tag on, the release workflow's automated release notes take over generating the changelog.

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 now live under ghcr.io/jixoai/opendweb: v0.4.2 originally shipped from the pre-rename namespace ghcr.io/gaubee/dweb, and every historical tag has since been migrated over 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: it provides the QUIC direct links, NAT traversal, and the self-hostable relay fallback. The SDK's bindings come from napi-rs.

Links