A relay is the volunteer infrastructure behind the MeshChat peer-to-peer messenger: it stores messages for offline users, routes them across the DHT, relays calls through NAT (TURN), keeps encrypted media, and forwards group calls. Everything stays end-to-end encrypted — a relay only ever sees opaque blobs.
install on a blank VM — needs only a public IPv4
$ curl -fsSL https://relay-dist.dev.4qube.ru/bootstrap.sh \ | PUBLIC_HOST=<your-numeric-ip> bash
Installs Docker, unpacks into a persistent
~/meshchat-relay, loads prebuilt images, and starts the stack.
Re-run anytime to update — your data/ and
.env are preserved.
wss://<your-domain>/ws — terminate TLS in your own reverse proxy and forward to the relay's WS port.| Port | Service | |
|---|---|---|
| 8767/udp | TURN control | required for calls |
| 49160–49200/udp | TURN media relay | required for calls |
| 8766/tcp | libp2p / DHT | only when federating |
| 8765/tcp | WS API | behind your TLS proxy |
Everything lives in one persistent directory:
~/meshchat-relay/ docker-compose.yml # image-only .env # PUBLIC_HOST, ADMIN_TOKEN, FCM_PROJECT_ID pushproxy-allowlist.json # authorised relay node pubkey(s) data/ # ALL state — bbolt DBs + node keys
Back up data/.
It holds the bbolt databases plus
node_ed25519.key (push-signing identity) and
libp2p_ed25519.key (DHT peer id).
If those keys are lost the node gets a new identity — never delete this directory.
The operator dashboard binds to 127.0.0.1:8768 — loopback only,
never published. Set ADMIN_TOKEN in .env to enable it,
then tunnel in over SSH:
$ ssh -L 8768:localhost:8768 <vm> # then open http://localhost:8768
The bundle ships the pushproxy bridge but leaves it off until you supply an FCM service account — a per-host secret that is never baked into the distribution. The relay runs fine without it; only background wake-ups for the Android app are skipped. To turn push on:
In the Firebase console for your project: ⚙ Project settings → Service accounts → Generate new private key. This downloads a JSON file.
$ sudo install -m600 -o root -g root <downloaded>.json /etc/meshchat/fcm-sa.json
.envFCM_PROJECT_ID=<your-firebase-project-id>
Read the node's signer pubkey from the log and add it. An empty allowlist blocks every wake.
$ docker compose logs relay | grep 'node signer pubkey' $ echo '["<that-pubkey>"]' > ~/meshchat-relay/pushproxy-allowlist.json
It detects the SA file + project id and starts pushproxy automatically.
$ cd ~/meshchat-relay && ./install.sh # or, directly: $ docker compose --profile push up -d
pushproxy and the admin UI both bind to loopback only and are reached via the relay / an SSH tunnel — they are never exposed to the internet.
Re-running bootstrap pulls the latest bundle and recreates the containers while keeping
data/ and .env — safe to run on a live node:
$ curl -fsSL https://relay-dist.dev.4qube.ru/bootstrap.sh | bash
Bundle: relay-deploy.zip · script: bootstrap.sh
Builds of the MeshChat messenger itself. They are deliberately not part of
relay-deploy.zip — that bundle is server-side only.
These builds point at one specific relay.
The endpoint is baked in at build time, so to hand out clients for
your node rebuild from source with your own URL:
./gradlew assembleRelease -PrelayUrls=wss://your-domain/ws.
No iOS download: publishing an iOS build requires a paid Apple Developer
account. Build ios/ from source instead.