Skip to content

Host your own roam server ​

Host your own, like in the old days: no cloud required. A roam (driving the same place with friends: the phone's Postcards) normally runs through our server. You can run your own instead: nip-roam, one small program with no account, no database and nothing kept. Run it on a computer at home for a LAN party, or on a server for friends far away.

Download ​

Get nip-roam for your computer from the downloads:

macOS (Apple silicon and Intel)nip-roam-<version>-macos-universal
Linux, x86-64nip-roam-<version>-linux-x86_64
Linux, ARM (a Raspberry Pi 4 or 5 with a 64-bit system)nip-roam-<version>-linux-arm64
Windowsnip-roam-<version>-windows-x86_64.exe

SHA256SUMS.txt beside them holds their checksums (shasum -a 256 -c SHA256SUMS.txt --ignore-missing). Use the server from the same release as the game. Each one says which roam protocol it speaks, and a game and a server that speak different ones say which of them to update.

Run it ​

sh
chmod +x nip-roam-*-linux-x86_64          # (macOS and Linux: make it runnable once)
./nip-roam-*-linux-x86_64

On macOS the first start is blocked because the file isn't signed. Right-click it, choose Open, or run xattr -d com.apple.quarantine nip-roam-*. On Windows, run the .exe and allow it on private networks when the firewall asks.

It listens on port 47200 and prints the addresses your friends on the same network can use:

level=INFO msg="nip-roam listening" addr=[::]:47200 version=0.9.3 protocol=1 relay=ws ...
level=INFO msg="players on your network: Postcards, Server: Your own" address=192.168.0.22:47200

Stop it with Ctrl+C. When the server stops, the roams on it end.

In the game ​

  1. Open the phone's Postcards, then Server, then Your own server, and type its address: 192.168.0.22:47200, or just 192.168.0.22 (47200 is the default port). The game remembers it. Our server switches back.
  2. Start a roam. On your own server its code comes with the address, for example K7QM4X@192.168.0.22:47200. C copies both.
  3. Friends choose Join with a code and paste all of it. A code with an address joins on that server, whichever server they chose themselves.

Friends' invites ("Wish you were here") go through our server, so on your own server you share the code and the address yourself.

Your own server needs no friends list and no key. It gets nothing of yours but the name you play under (Me), your game's version and your region, plus everything a roam sends to the others while it lasts. It hands your game a random token of its own, so you can join again if your connection drops.

Over the internet ​

Friends outside your network reach your server by your public address. Forward TCP port 47200 on your router to the computer that runs it, or run it on a server with a public address. Then tell friends CODE@your.address:47200.

TLS: for the browser ​

The downloaded game connects over plain WebSocket (ws://) and needs nothing more, on a LAN or over the internet.

The browser player (the site's Play page) can only reach a server with TLS (wss://): a page on https can't open plain connections. For it, give the server a certificate for its name:

sh
./nip-roam -tls-cert /etc/letsencrypt/live/roam.example.org/fullchain.pem \
           -tls-key  /etc/letsencrypt/live/roam.example.org/privkey.pem

Everyone then uses https://roam.example.org:47200 as the server's address, in the downloaded game too: with https:// it speaks TLS, and without it plain. The browser player only reaches the server you open it with: https://nowhereinparticular.pl/play/?roam=roam.example.org:47200 (its Postcards then use that server). A certificate from Let's Encrypt (certbot) is free. An IP address won't do: browsers want the certificate's name.

Behind a proxy that does TLS itself (Caddy, nginx, Traefik), run nip-roam without TLS and tell it the address players use, so the relay's links point there: -public-url https://roam.example.org. The proxy must pass WebSocket upgrades on /roam/v1/relay/.

Limits and load ​

The server lets in a new roam or a new player only while it has room for them. It measures its CPU, its memory, what it sends a second (bytes and messages) and how late its timers fire, and counts the roams and players, all against limits sized to the computer it runs on (a container's limits where it has them). When it's full, the game says so plainly: "The server is full right now: try again in a few minutes", or "This roam is full". Nobody already in a roam is dropped, and someone whose connection dropped is always let back in. New roams stop at 85 % of each limit (the reserve), so the roams already open have room for friends who join later.

FlagEnvironmentDefault
-listenNIP_ROAM_LISTEN:47200the address and port
-tls-cert, -tls-keyNIP_ROAM_TLS_CERT, NIP_ROAM_TLS_KEYTLS (wss), for the browser
-public-urlNIP_ROAM_PUBLIC_URLthe address players use, behind a proxy
-originsNIP_ROAM_ORIGINSthe site'sbrowser pages allowed to use it (*: any)
-roam-sizeNIP_ROAM_ROAM_SIZE8players in one roam (up to 255)
-max-roomsNIP_ROAM_MAX_ROOMShalf the playersroams open at once
-max-playersNIP_ROAM_MAX_PLAYERS64 per CPU, up to 2048players in all roams
-max-cpuNIP_ROAM_MAX_CPU80% of the CPUs it may use
-max-memoryNIP_ROAM_MAX_MEMORYhalf the memoryMiB
-max-outNIP_ROAM_MAX_OUT48 per playerKiB a second it sends
-max-messagesNIP_ROAM_MAX_MESSAGES240 per playermessages a second it sends
-max-lagNIP_ROAM_MAX_LAG100ms its timers may run late
-reserveNIP_ROAM_RESERVE85% of each limit new roams stop at

Use -1 for no limit. For a small LAN party, -max-rooms 1 keeps it to one roam. A flag given wins over its environment variable. ./nip-roam -help lists them all.

Health: http://<server>:47200/healthz shows the load as JSON: status is ok (new roams welcome), busy (friends may still join the roams open, no new roams) or full (only reconnects), with why, the roams and players, what it measures and its limits. It always answers 200, for monitoring. /readyz answers 503 while it takes no new roams, so a container platform or a load balancer can send new players elsewhere. Roams live in one server's memory: run more than one only with each under its own address.

As a service (Linux, systemd) ​

ini
# /etc/systemd/system/nip-roam.service
[Unit]
Description=Nowhere in Particular roam server
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/nip-roam
Environment=NIP_ROAM_MAX_ROOMS=4
DynamicUser=yes
Restart=on-failure
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes

[Install]
WantedBy=multi-user.target
sh
sudo install -m 755 nip-roam-*-linux-x86_64 /usr/local/bin/nip-roam
sudo systemctl enable --now nip-roam
journalctl -u nip-roam -f

In a container ​

The source has an image file (services/api/Dockerfile.roam: a static binary on distroless, non-root). Build and run it with Docker, Podman or Apple's container:

sh
docker build -f services/api/Dockerfile.roam -t nip-roam services/api
docker run -d --name nip-roam -p 47200:47200 -e NIP_ROAM_MAX_ROOMS=4 nip-roam

Or, without building anything, the downloaded Linux binary on any small image:

sh
docker run -d --name nip-roam -p 47200:47200 -v "$PWD/nip-roam-<version>-linux-x86_64:/nip-roam:ro" \
  debian:stable-slim /nip-roam

Inside a container it reads the container's CPU and memory limits, and sizes its own limits to them.

Privacy ​

A roam on your server is yours: the game sends your server what it would send ours for a roam, and nothing reaches us. The server keeps nothing on disk, and its log has no players' names. If you run it for people other than your friends, tell them what you do with it. See the privacy policy.