Skip to content

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.sh command 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_name from targets.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_name

Example (default olimpico target):

olimpico|debian@192.168.1.127|olimpico|~/.ssh/id_ed25519_olimpico|/opt/olimpico/olimpico-live|/var/www/olimpico|olimpico|liveplayer

Target selection precedence

  1. bash deploy-remote.sh --target=NAME or DEPLOY_TARGET=NAME — explicit target
  2. OLIMPICO_SERVER / OLIMPICO_USER / OLIMPICO_SSH_HOST / OLIMPICO_SSH_KEY — legacy override
  3. Interactive menu — appears when the script runs from a terminal without flags
  4. 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:

bash
export DEPLOY_TARGET_SERVER=10.0.0.5
export DEPLOY_TARGET_WWW_DIR=/var/www/zerolatency-firenze
bash deploy-remote.sh --target=stadio_firenze

3. Prerequisites (from your Mac)

RequirementVerify
bash 4+bash --version
rsyncrsync --version
sshpass (only if deploying with a password)command -v sshpassbrew install sshpass
SSH access to target serversssh <user>@<host>
Correct monorepo branchgit branchagent
~/.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
bash deploy-remote.sh --target=olimpico --provision

Uploads 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:

bash
# 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/olimpico

Installs: 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)

bash
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:

bash
ssh <alias>@<host>
sudo nano <remote_dir>/zerolatency-live-player-api/.env

Minimum content:

ini
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 recommended

Without a valid LICENSE_KEY the backend does not start in production. Set API_KEY if you want to protect REST + WebSocket.

6. Running the deploy

bash
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.sh

Automatic pipeline (6 steps):

  1. rsync of sources Mac → <remote_dir> (exclude node_modules, dist, .git, .env, keygen)
  2. Backend: npm install + npm run build (tsc) + pm2 restart zerolatency-live-player-api (or pm2 start)
  3. Control Room: npm install + npm run build (vite) + copy static files to <www_dir>
  4. nginx: site root, try_files, HTTP Basic Auth for the Control Room (creates/updates .htpasswd-regia with regia / 1234 by default, injectable via the REGIA_HTPASSWD env, ignored with REGIA_PRESERVE=1), template copied and adapted to the target
  5. Verification: backend health check (/api/devices), Control Room on port 80, pm2 status
  6. 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

bash
# 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)

  1. On the new server: run provisioning (§ 4)
  2. Add a line in targets.conf (copy/uncomment an example, adapt host/path/db) and configure the SSH alias (§ 5)
  3. Configure ~/.ssh/config with the new target's alias
  4. Generate a dedicated license key for the new client (§ 5.1)
  5. deploy.sh options on the new server with its remote_dir (§ 4)
  6. Normal deploy: bash deploy-remote.sh --target=<new>

Example:

text
# ~/.ssh/config
Host stadio_firenze
  Hostname 10.10.0.20
  User debian
  IdentityFile ~/.ssh/id_ed25519_stadio_firenze

9. Backup and restore

  • Backup: backup.sh every 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 (was olimpico_live). On an existing server rename the collections via mongosh (see Pre-deploy — Server migration) or mongodump + mongorestore --drop before pointing to the new MONGODB_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:

bash
git checkout <right_commit>   # on your Mac, on the code branch
bash deploy-remote.sh --target=<target>

Quick recovery

SymptomAction
Backend down after deployssh <alias> 'pm2 logs zerolatency-live-player-api --lines 50'
pm2 status errorcheck LICENSE_KEY/.env, then pm2 restart zerolatency-live-player-api --update-env
nginx invalidssh <alias> 'sudo nginx -t'
MongoDB not respondingssh <alias> 'sudo systemctl status mongod'
Slow deploy / TimeoutCheck remote_dir/www_dir in targets.conf + network and bandwidth

11. Quick troubleshooting

ProblemLikely causeSolution
Cannot connect to <host>unreachable host / missing keyping, nc -zv <host> 22, set up ~/.ssh/config, SSH key
Target 'X' not foundname not in targets.confbash deploy-remote.sh --list
Deploy finishes but backend does not startinvalid license / .env§ 5.2
pm2 save warningnon-blocking warning onlyignore
Control Room does not serve UInginx not reloadedsudo systemctl reload nginx || sudo nginx -s reload

© 2026 Zerolatency — All Rights Reserved