IFM Rendezvous — Headless Deployment Guide
ifm-rendezvous is a headless, GUI-less peer-discovery service for the IFM network. It answers one question: "Which IFM peers are currently reachable through me?" A new node dials the rendezvous, receives the active peers, and connects to them directly — the rendezvous never proxies radio traffic.
This is the same software an operator deploys as:
| Mode | Flag | Purpose |
|---|---|---|
| Plain rendezvous node | (default) | Serves /ifm/rendezvous/1.0.0 — HELLO / GET_PEERS / HEARTBEAT / GOODBYE; keeps a live, lease-based table of the peers connected through it |
| Master Rendezvous | --master | Adds /ifm/rendezvous/register/1.0.0 — the tiny bootstrap authority rendezvous nodes register with; headless, no dashboard (spec §17 forbids one) |
| Mesh node | --rendezvous <pid>@<addr> / --exchange | Joins the redundant rendezvous mesh: nodes discover each other and exchange bounded samples of active peers (§17–§18) |
| Bootstrap client | --bootstrap <pid>@<addr> | Registers with a Master on startup and keeps the registration alive by heartbeat (§12) |
The binary always runs headless — there is no GUI build.
1. Build
From the repo root:
cargo build --release -p ifm-rendezvous
# → target/release/ifm-rendezvous (~5.7 MB, static-ish single binary)bun scripts/build.mjs and bun scripts/release.mjs also build it and stage it as bin/ifm-rendezvous-<os>-<arch> into the release.
Verify:
./target/release/ifm-rendezvous --help
./target/release/ifm-rendezvous --version2. Quick start (local test)
# Terminal 1 — a Master (or just a plain rendezvous node)
./target/release/ifm-rendezvous --master --data-dir /var/lib/ifm-rvz
# Terminal 2 — a community rendezvous node that registers with it
./target/release/ifm-rendezvous --bootstrap 12D3KooW…@/ip4/127.0.0.1/udp/4001/quic-v1 \
--data-dir /var/lib/ifm-rvz-2
# Terminal 3 — a plain IFM app that seeds from the rendezvous node
# (fetch the node's live manifest — see `--http-port`)
IFM_MANIFEST_URL=http://127.0.0.1:8080/bootstrap.ifm.json your-ifm-appOn startup every node prints its bootstrap manifest entry — the exact peer_id + listen multiaddrs. The node also serves this manifest live at GET /bootstrap.ifm.json on its operator HTTP port (--http-port, e.g. 8080), rebuilt on every request from the node's current addresses — so apps always fetch the entry point dynamically and nothing needs a static copy:
bootstrap.ifm.json entry:
{
"protocol": 1,
"network": "IFM",
"rendezvous": [
{ "addresses": ["/ip4/203.0.113.10/udp/4001/quic-v1"], "peer_id": "12D3KooW…" }
]
}3. Running modes
3.1 Plain rendezvous node (default)
ifm-rendezvous \
--port 4001 \
--data-dir /var/lib/ifm-rvz \
--advertise /ip4/203.0.113.10/udp/4001/quic-v1--data-dirpersistsidentity.key, so the Peer ID is stable across restarts (spec §38) — publish it once, never rotate it.--advertiselists the public addresses for HELLO responses; without it the node advertises its resolved listen addresses (loopback-rewritten).- Defaults: heartbeat 60s, peer lease 180s, max peers 1000, max connections 2048, per-peer rate limit 120 req/min (spec §51).
3.2 Master Rendezvous (the bootstrap authority)
ifm-rendezvous --master \
--port 4001 \
--data-dir /var/lib/ifm-rvz-master \
--advertise /ip4/203.0.113.10/udp/4001/quic-v1- Grants 300s registration leases (
--master-lease), bounds the live peer table at 1000 (--max-peers; the Master's registered-node directory is likewise bounded, default 1000), and hands a new node the active rendezvous peers to dial directly — the Master is bootstrap only, never in the data path. - Community/independent Masters pass their own
--network-id; a node refuses to register with a Master of a different network (spec §14).
3.3 Redundant mesh (multiple rendezvous nodes)
Run several plain nodes and wire them together so discovery survives any one node failing (spec §10):
# node A
ifm-rendezvous --port 4001 --data-dir /var/lib/ifm-rvz-a
# node B joins A
ifm-rendezvous --port 4002 --data-dir /var/lib/ifm-rvz-b \
--rendezvous <A_peer_id>@/ip4/203.0.113.10/udp/4001/quic-v1
# node C joins A (and B through the mesh)
ifm-rendezvous --port 4003 --data-dir /var/lib/ifm-rvz-c \
--rendezvous <A_peer_id>@/ip4/203.0.113.10/udp/4001/quic-v1--rendezvous implies --exchange; nodes periodically exchange bounded samples of active peers and refresh the mesh every 30s.
3.4 Bootstrap client (register with a Master)
ifm-rendezvous --bootstrap <master_peer_id>@/ip4/203.0.113.10/udp/4001/quic-v1 \
--data-dir /var/lib/ifm-rvzThe node registers on startup and heartbeats every 60s. If the Master is unreachable the node keeps running — the Master is bootstrap, not a dependency.
4. Production deployment
4.1 Linux — systemd
# /etc/systemd/system/ifm-rendezvous.service
[Unit]
Description=IFM Rendezvous node
Documentation=https://github.com/EnzoVezzaro/Internet-radio/blob/main/docs/rendezvous
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=ifm-rvz
Group=ifm-rvz
ExecStart=/usr/local/bin/ifm-rendezvous --master --port 4001 \
--data-dir /var/lib/ifm-rvz --advertise /ip4/YOUR_PUBLIC_IP/udp/4001/quic-v1
Restart=always
RestartSec=10
TimeoutStopSec=30
# Security hardening
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/ifm-rvz
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
# Resource limits
LimitNOFILE=65536
MemoryMax=512M
[Install]
WantedBy=multi-user.targetsudo useradd -r -s /bin/false -d /var/lib/ifm-rvz ifm-rvz
sudo mkdir -p /var/lib/ifm-rvz
sudo chown ifm-rvz:ifm-rvz /var/lib/ifm-rvz
sudo systemctl daemon-reload
sudo systemctl enable --now ifm-rendezvous.service
journalctl -u ifm-rendezvous -f4.2 macOS — launchd
<!-- ~/Library/LaunchAgents/network.ifm.rendezvous.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>network.ifm.rendezvous</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/ifm-rendezvous</string>
<string>--master</string>
<string>--port</string>
<string>4001</string>
<string>--data-dir</string>
<string>/usr/local/var/ifm-rvz</string>
<string>--advertise</string>
<string>/ip4/YOUR_PUBLIC_IP/udp/4001/quic-v1</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/usr/local/var/log/ifm-rvz.log</string>
<key>StandardErrorPath</key>
<string>/usr/local/var/log/ifm-rvz.err.log</string>
</dict>
</plist>launchctl load ~/Library/LaunchAgents/network.ifm.rendezvous.plist
launchctl start network.ifm.rendezvous4.3 Docker
FROM rust:1-slim AS builder
WORKDIR /app
COPY . .
RUN cargo build --release -p ifm-rendezvous
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/ifm-rendezvous /usr/local/bin/ifm-rendezvous
RUN useradd -r -s /bin/false ifm-rvz \
&& mkdir -p /var/lib/ifm-rvz \
&& chown -R ifm-rvz:ifm-rvz /var/lib/ifm-rvz
USER ifm-rvz
EXPOSE 4001/udp 4001/tcp
ENTRYPOINT ["ifm-rendezvous"]docker build -t ifm-rendezvous .
docker run -d --name ifm-rvz --restart unless-stopped \
-p 4001:4001/udp -p 4001:4001/tcp \
-v ifm-rvz-data:/var/lib/ifm-rvz \
ifm-rendezvous --master --data-dir /var/lib/ifm-rvz \
--advertise /ip4/YOUR_PUBLIC_IP/udp/4001/quic-v14.4 Firewall
# UDP is the primary transport (QUIC); TCP is the fallback
sudo ufw allow 4001/udp comment "IFM rendezvous QUIC"
sudo ufw allow 4001/tcp comment "IFM rendezvous TCP"5. How the rest of the IFM system uses it
The rendezvous node is a normal libp2p peer on the IFM mesh — a slim entry point and nothing more. Any IFM desktop app or node joins through it with zero rendezvous client code; the mesh then handles peer discovery:
- Remote mode (the default) — the app fetches the node's live manifest at startup (
GET /bootstrap.ifm.json) and dials the listed address(es):bashThe default is our deployment's manifest (IFM_MANIFEST_URL=http://YOUR_NODE/bootstrap.ifm.json ifm-stationhttp://ifm-rendezvous.fly.dev/ bootstrap.ifm.json), so a plain app needs no configuration at all. - LAN mode —
IFM_LAN=1(ornetwork.lan = truein config): no rendezvous; the LAN itself (mDNS) is the entry point. - The dialed entry address seeds Kademlia; the DHT then expands the mesh via provider records on
/ifm/mesh/v1.
Deploy two or more rendezvous nodes in different regions, and let the DHT
- rendezvous mesh do the rest.
6. Operations
Health / status
The headless process logs a summary every 30s:
[ 120s] master: registered=2 connected=3 # --master mode
[ 120s] peers=12 connected=15 roles=station=4 relay=3 listener=8 exchanges=7Readiness probe
# Port open + QUIC listening (macOS: nc -u -z works only for UDP reachability;
# the authoritative check is a successful manifest fetch + entry dial from an app)
nc -z 127.0.0.1 4001 && echo "TCP up"Backups
The only state that matters is identity.key — back it up (or regenerate and republish the bootstrap entry):
cp /var/lib/ifm-rvz/identity.key ~/backups/ifm-rvz-identity.key
chmod 600 ~/backups/ifm-rvz-identity.keyTroubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Apps don't discover each other | Firewall blocking UDP | Open 4001/udp (QUIC is primary) |
| Peer ID changes every restart | --data-dir missing / not writable | Use a persistent --data-dir; check ownership |
| No peers returned by HELLO | New node, empty table | Wait for peers to connect; join the mesh with --rendezvous |
| Registration rejected | Network id mismatch | --network-id must match the Master's |
7. Spec & source
- Protocol:
crates/rendezvous/src/protocol.rs—/ifm/rendezvous/1.0.0,/ifm/rendezvous-exchange/1.0.0,/ifm/rendezvous/register/1.0.0 - Node:
crates/rendezvous/src/node.rs - CLI:
crates/rendezvous/src/main.rs - Tests:
crates/rendezvous/tests/(discovery, exchange, expiration, resilience, master)