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