⬡ Lumamesh

Strengthen the mesh.
Run a relay node.

Lumamesh is a decentralised network — no company runs it, no server owns it. Every node someone adds makes it harder to stop. If enough people run nodes, the network becomes as unstoppable as BitTorrent or Bitcoin.

A relay node is a single Go binary — one UDP port, zero database, no HTTP by default. It only handles signaling (introducing browsers to each other). Once two browsers have exchanged SDP the relay drops out completely — your node never sees the actual data.

You can run one on a spare machine at home, a Raspberry Pi, or a $3/mo VPS. The whole setup takes about 5 minutes.

Requirements: A machine reachable on a public IP (or port-forwarded to one) with one UDP port open (default 3478). Go 1.22+ to build from source. CGNAT home connections won't work for inbound sessions — use a VPS or a home connection with a real public IP and port-forwarding.

One-command install

Clone the repo and run the installer. It builds the binary, generates your identity files, installs a systemd service, backs up your keys, and encodes your relay config — all in one pass.

git clone https://github.com/lumamesh/lumamesh.git
cd lumamesh
PUBLIC_IP=YOUR.PUBLIC.IP bash install.sh

The installer writes a full log to operator/install.log and saves your config values to operator/setup.env for reference.

Don't know your public IP? Run curl https://api.ipify.org — or leave PUBLIC_IP unset and the installer will detect and confirm it for you.

What the installer does

  1. Detects your public IP (or uses the one you provide)
  2. Checks Go is installed (installs via snap if not)
  3. Runs go test ./... — all tests must pass
  4. Builds the lumamesh-relay binary
  5. Generates your DTLS cert, ICE password, and Ed25519 node identity key
  6. Encodes your relay config → operator/relay.txt
  7. Installs and starts a systemd service (auto-restart on reboot)
  8. Backs up identity files to operator/keys-backup/
  9. Runs a health check and prints your next steps

Identity files — back these up

These four files in pion-server/ are your node's entire identity. Copy operator/keys-backup/ somewhere offline (USB, encrypted cloud):

server.crt          # DTLS certificate  (fingerprint changes if regenerated)
server.key          # DTLS private key
server.key.icepwd   # ICE password
node.key            # Ed25519 node identity (nk changes if regenerated)

Manual install (step by step)

If you prefer to do it yourself or are on a non-systemd system:

1. Build

git clone https://github.com/lumamesh/lumamesh.git
cd lumamesh/pion-server
go test ./...
go build -o lumamesh-relay .

2. Open the UDP port

sudo ufw allow 3478/udp

3. Run

PUBLIC_IP=YOUR.PUBLIC.IP UDP_PORT=3478 ./lumamesh-relay

Run as a systemd service

# /etc/systemd/system/lumamesh-relay.service
[Unit]
Description=Lumamesh Relay
After=network-online.target
Wants=network-online.target

[Service]
WorkingDirectory=/opt/lumamesh
ExecStart=/opt/lumamesh/lumamesh-relay
Environment=PUBLIC_IP=YOUR.PUBLIC.IP
Environment=UDP_PORT=3478
Environment=HEALTH_LISTEN=127.0.0.1:7401
Restart=always
RestartSec=3
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/opt/lumamesh
PrivateTmp=true

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now lumamesh-relay
sudo journalctl -u lumamesh-relay -f

Verify it's working

# Health check (requires HEALTH_LISTEN)
curl http://127.0.0.1:7401/healthz
# {"nodeId":"...","ok":true,"ts":...}

# Mesh state
curl http://127.0.0.1:7401/statsz | jq

Mesh with other nodes (optional)

Multiple relays sharing a MESH_SALT gossip room hints over TCP so browsers always find each other regardless of which node they land on.

# Node A
PUBLIC_IP=203.0.113.10 MESH_SALT=pick-a-long-random-string \
MESH_LISTEN=0.0.0.0:7400 MESH_PEERS=203.0.113.20:7400 ./lumamesh-relay

# Node B
PUBLIC_IP=203.0.113.20 MESH_SALT=pick-a-long-random-string \
MESH_LISTEN=0.0.0.0:7400 MESH_PEERS=203.0.113.10:7400 ./lumamesh-relay
Every node in a mesh cluster must share the identical MESH_SALT value. Different salts = rooms never converge.

Add your node to the network

Once your relay is running, open a pull request on GitHub to add it to the public relay list at lumamesh.com/relay.txt. Include your node's ip (or host), port, fingerprint, and nk — all printed by the relay on startup.

The curated list is just the bootstrap entry point. Once a browser connects to any node it automatically discovers all others via the nodes gossip action and caches them locally. The more nodes exist, the harder the network is to stop. There is no central authority — anyone can add a node, and the network routes around failures automatically.

Environment variables

VarDefaultPurpose
PUBLIC_IP127.0.0.1Public IPv4 browsers dial. Required.
PUBLIC_HOSTDNS name (resolved client-side via DoH). Use for dynamic DNS.
UDP_PORT3478Browser-facing UDP port.
ICE_UFRAGlumaICE username fragment.
ICE_PWDautoICE password. Auto-generated and persisted if unset.
CERT_FILEserver.crtDTLS cert path.
KEY_FILEserver.keyDTLS key path.
NODE_KEYnode.keyEd25519 identity path.
HEALTH_LISTENhost:port for /healthz + /statsz.
MESH_SALTEnables mesh gossip. Identical across all mesh nodes.
MESH_LISTENhost:port to accept gossip peers (TCP).
MESH_PEERSComma-separated host:port of peer nodes to dial.
MAX_SESSIONS1000Concurrent browser sessions.
MAX_ROOM_SIZE250Members per room.