Full deploy guide for Zerolatency Live Player on Debian 12 servers, with multi-tenant support (multiple stadiums/servers deployable from the same script).
Automatic deployment: after the initial provisioning, deploy is a single
deploy-remote.shcommand that picks the target (interactive menu or--target), syncs sources via rsync, builds, restarts and verifies.
1. Deploy architecture
┌─────────────────────────┐ rsync over SSH ┌─────────────────────────────┐
│ Your Mac (dev) │ ─────────────────►│ Debian 12 server (target) │
│ └─ zerolatency-live- │ │ │
│ player-deploy/ │ │ <remote_dir>/ │
│ ├─ deploy.sh │ │ ├─ zerolatency-live-player-│
│ ├─ deploy-remote.sh │ │ │ api/ (Fastify+WS) │
│ ├─ targets.conf │ │ ├─ zerolatency-live-player-│
│ └─ ... │ │ │ regia/ (Vite build) │
│ │ │ └─ zerolatency-live-player-│
│ │ │ deploy/ (scripts) │
│ │ │ <www_dir>/ → static Regia │
└─────────────────────────┘ │ MongoDB, PM2, nginx, UFW │
└──────────────────────────────┘Deployed components:
- Backend API (
zerolatency-live-player-api) — Fastify + WebSocket + MongoDB, managed by PM2 (zerolatency-live-player-api) - Control Room UI (
zerolatency-live-player-regia) — Vite build copied to<www_dir>served by nginx with HTTP Basic Auth - nginx — reverse proxy: API (
/api), WebSocket (/ws), docs (/docs), static Control Room (root) - MongoDB — target database (name
db_namefromtargets.conf) - Backup — daily cron at 02:30 (
backup.sh) zerolatency-live-player-keygen— NEVER copied to the server (excluded from rsync)
2. Multi-tenant: targets
Each stadium/server is a target defined in zerolatency-live-player-deploy/targets.conf — one line per target:
name | user@host | ssh_host_alias | ssh_key | remote_dir | www_dir | nginx_site | db_nameExample (default olimpico target):
olimpico|debian@192.168.1.127|olimpico|~/.ssh/id_ed25519_olimpico|/opt/olimpico/olimpico-live|/var/www/olimpico|olimpico|liveplayerTarget selection precedence
bash deploy-remote.sh --target=NAMEorDEPLOY_TARGET=NAME— explicit targetOLIMPICO_SERVER/OLIMPICO_USER/OLIMPICO_SSH_HOST/OLIMPICO_SSH_KEY— legacy override- Interactive menu — appears when the script runs from a terminal without flags
- First target in
targets.conf— non-interactive default (CI)
Point overrides (backward compatible)
You can change a single field of the chosen target without touching the file:
export DEPLOY_TARGET_SERVER=10.0.0.5
export DEPLOY_TARGET_WWW_DIR=/var/www/zerolatency-firenze
bash deploy-remote.sh --target=stadio_firenze3. Prerequisites (from your Mac)
| Requirement | Verify |
|---|---|
| bash 4+ | bash --version |
| rsync | rsync --version |
| sshpass (only if deploying with a password) | command -v sshpass → brew install sshpass |
| SSH access to target servers | ssh <user>@<host> |
| Correct monorepo branch | git branch → agent |
~/.ssh/config with target aliases | (recommended, see § 5) |
4. Server provisioning (one-time)
Automatic (recommended) — from any Mac with SSH access to the target:
bash deploy-remote.sh --target=olimpico --provisionUploads deploy.sh to the server and runs it as sudo (installs Node.js 22, MongoDB 7.0, nginx, PM2, Control Room Basic Auth, backup cron), then resumes the normal deploy. The SSH user must have sudo privileges.
Manual (alternative) — run as root/sudo, from the repo root:
# Copy the provisioning script to the server
scp zerolatency-live-player-deploy/deploy.sh debian@<HOST>:/tmp/
# SSH and run (INSTALL_DIR = base dir, see targets.conf remote_dir)
ssh debian@<HOST>
sudo bash /tmp/deploy.sh /opt/olimpicoInstalls: Node.js 22, MongoDB 7.0, nginx, PM2, curl, gnupg, UFW, backup cron. Creates the structure in <remote_dir>/ and the .env template.
⚠️ Do not repeat provisioning on already-deployed servers — if it exists, skip and go directly to § 6.
5. Before deploying: license key and .env
5.1 License Key (on your Mac)
cd zerolatency-live-player-keygen
# Strong secret for the HMAC (use it on the server too)
export LICENSE_SECRET="$(openssl rand -hex 16)"
# Generate the key for the client/stadium
npx tsx src/generate-license.ts "Client" 2027-12-31⚠️ Each client/stadium has its own license key generated with its own LICENSE_SECRET. Do not reuse the same key across different targets.
5.2 Populate the .env on the server
The .env is never synced via rsync and must be edited manually on the server:
ssh <alias>@<host>
sudo nano <remote_dir>/zerolatency-live-player-api/.envMinimum content:
PORT=3002
HOST=0.0.0.0
MONGODB_URI=mongodb://localhost:27017/<db_name>
NODE_ENV=production
LICENSE_KEY=OLIMP-...-....-....
LICENSE_SECRET=<same secret used to generate>
API_KEY=<key for REST/WS auth> # optional but recommendedWithout a valid
LICENSE_KEYthe backend does not start in production. SetAPI_KEYif you want to protect REST + WebSocket.
6. Running the deploy
cd zerolatency-live-player-deploy
# 0th choice: provisioning + deploy (new server, one-time)
bash deploy-remote.sh --target=olimpico --provision
# 1st choice: interactive menu
bash deploy-remote.sh
# 2nd choice: direct target
bash deploy-remote.sh --target=olimpico
# 3rd choice: env var (useful in CI)
DEPLOY_TARGET=stadio_firenze bash deploy-remote.sh
# 4th choice: legacy (custom server without targets.conf)
OLIMPICO_SERVER=10.0.0.5 OLIMPICO_USER=admin OLIMPICO_SSH_KEY=~/.ssh/id_ed25519_x bash deploy-remote.shAutomatic pipeline (6 steps):
- rsync of sources Mac →
<remote_dir>(excludenode_modules,dist,.git,.env, keygen) - Backend:
npm install+npm run build(tsc) +pm2 restart zerolatency-live-player-api(orpm2 start) - Control Room:
npm install+npm run build(vite) + copy static files to<www_dir> - nginx: site root,
try_files, HTTP Basic Auth for the Control Room (creates/updates.htpasswd-regiawithregia/1234by default, injectable via theREGIA_HTPASSWDenv, ignored withREGIA_PRESERVE=1), template copied and adapted to the target - Verification: backend health check (
/api/devices), Control Room on port 80,pm2 status - Summary with URL and log of the target used
No manual restart required: deploy-remote.sh handles pm2 save, nginx -t, nginx reload and cron.
7. Post-deploy verification
# API health check (via nginx)
curl http://<host>/api/devices # expected: []
curl -I http://<host>/health
# Swagger UI
open http://<host>/docs
# Control Room UI
open http://<host>/ # requires Basic Auth login
# PM2 status
ssh <alias> 'pm2 status'
# zerolatency-live-player-api → online
# Log
ssh <alias> 'pm2 logs zerolatency-live-player-api --lines 30'→ Full test with a physical iPad: Post-deploy
8. Adding a new stadium (multi-tenant)
- On the new server: run provisioning (§ 4)
- Add a line in
targets.conf(copy/uncomment an example, adapt host/path/db) and configure the SSH alias (§ 5) - Configure
~/.ssh/configwith the new target's alias - Generate a dedicated license key for the new client (§ 5.1)
deploy.shoptions on the new server with itsremote_dir(§ 4)- Normal deploy:
bash deploy-remote.sh --target=<new>
Example:
# ~/.ssh/config
Host stadio_firenze
Hostname 10.10.0.20
User debian
IdentityFile ~/.ssh/id_ed25519_stadio_firenze9. Backup and restore
- Backup:
backup.shevery night at 02:30 (cron) →/var/backups/<target>/<db_name>_*.gz, 7-day retention - Manual:
sudo bash <remote_dir>/zerolatency-live-player-deploy/backup.sh - Restore:
sudo bash <remote_dir>/zerolatency-live-player-deploy/restore.sh <file.gz>
⚠️ DB rename (only on an already-deployed server): the database is now called
liveplayer(wasolimpico_live). On an existing server rename the collections via mongosh (see Pre-deploy — Server migration) ormongodump+mongorestore --dropbefore pointing to the newMONGODB_URI.
10. Rollback / DT
Rollback to the previous deploy
The previous code is not archived by the deploy (rsync with --delete). To go back to a previous version:
git checkout <right_commit> # on your Mac, on the code branch
bash deploy-remote.sh --target=<target>Quick recovery
| Symptom | Action |
|---|---|
| Backend down after deploy | ssh <alias> 'pm2 logs zerolatency-live-player-api --lines 50' |
pm2 status error | check LICENSE_KEY/.env, then pm2 restart zerolatency-live-player-api --update-env |
| nginx invalid | ssh <alias> 'sudo nginx -t' |
| MongoDB not responding | ssh <alias> 'sudo systemctl status mongod' |
| Slow deploy / Timeout | Check remote_dir/www_dir in targets.conf + network and bandwidth |
11. Quick troubleshooting
| Problem | Likely cause | Solution |
|---|---|---|
Cannot connect to <host> | unreachable host / missing key | ping, nc -zv <host> 22, set up ~/.ssh/config, SSH key |
Target 'X' not found | name not in targets.conf | bash deploy-remote.sh --list |
| Deploy finishes but backend does not start | invalid license / .env | § 5.2 |
pm2 save warning | non-blocking warning only | ignore |
| Control Room does not serve UI | nginx not reloaded | sudo systemctl reload nginx || sudo nginx -s reload |