Deploying the signaling server
The server is a single statically-linkable Rust binary. Deployment is “get the binary onto a host and put TLS in front of it.”
Build
cd server
cargo build --release
# → target/release/omnicall-signaling (LTO, 1 codegen unit, stripped)For a fully static musl binary (portable across distros):
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-muslBuilding on Windows
The Windows dev machine has a link.exe collision - GNU coreutils link
shadows the MSVC linker - so the server is built on the Linux host, not the
dev laptop. If you hit this, prepend the MSVC toolchain’s bin directory to
PATH or build in WSL.
Run
./omnicall-signaling
# =========================================
# WebRTC Signaling Server Running (Rust)
# Bound Address: 0.0.0.0:8080
# =========================================Environment:
| Var | Default | Notes |
|---|---|---|
HOST | 0.0.0.0 | Bind address. Set to 127.0.0.1 when a reverse proxy is on the same host. |
PORT | 8080 | |
RUST_LOG | info,tower_http=warn | debug logs every relayed frame. |
The process handles SIGINT (Ctrl-C) with a graceful shutdown.
systemd unit
# /etc/systemd/system/omnicall.service
[Unit]
Description=Viora signaling server
After=network.target
[Service]
Type=simple
User=omnicall
Environment=HOST=127.0.0.1
Environment=PORT=8080
Environment=RUST_LOG=info
ExecStart=/opt/omnicall/omnicall-signaling
Restart=on-failure
RestartSec=2
# hardening - the process needs nothing but a socket
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
[Install]
WantedBy=multi-user.targetsudo systemctl enable --now omnicallTLS + WebSocket proxy
The Android app connects with wss:// - you must terminate TLS in front of
the binary. The proxy has to pass the WebSocket Upgrade/Connection headers
through.
Caddy
call.example.com {
reverse_proxy 127.0.0.1:8080
}Caddy handles the certificate and WebSocket upgrade automatically. This is the whole config.
Point the app at it
Settings → Server → Signaling server URL:
wss://call.example.com/The trailing / matters - the client connects to the server root, which is
where the upgrade is served.
Verify
# from the server host
curl -i http://127.0.0.1:8080/ # → 404 Not Found (correct - not an upgrade)
# WebSocket smoke test (needs websocat or wscat)
websocat wss://call.example.com/
> {"type":"join","roomId":"test","peerId":"a","payload":{"name":"A"}}
< {"type":"room-joined","roomId":"test","peerId":"a","peers":[]}Resource footprint
- Memory: a few MB idle; each room is a
HashMapentry plus one unbounded channel per peer. A handful of concurrent calls is kilobytes. - CPU: effectively zero - it copies small JSON frames between channels.
- Network: signaling only. Media never touches the server, so bandwidth is negligible regardless of call resolution.
- Disk: none. No logs are written unless you redirect them; no state is persisted.
A $5/month VPS runs this comfortably for any number of pairs you’d realistically have.
What is not handled
- No rate limiting. A malicious client can spam frames. Put it behind a proxy with connection limits if that matters.
- No auth. Anyone who knows a room code and your server URL can join that room. Use unguessable room codes.
- No horizontal scaling. State is in-process. Two server instances don’t share rooms. For Viora’s scale (pairs of people you know) this is fine.