Add multi-player and remote bridge support

This commit is contained in:
2026-08-07 02:58:18 +01:00
parent a4f8a807ff
commit 74318eb34e
58 changed files with 3785 additions and 432 deletions
+204
View File
@@ -0,0 +1,204 @@
# Docker Compose Setup
This folder contains the public Docker Compose definitions for Pulse Signage and the remote player stack.
## Files
- [local.yml](local.yml) - full public stack with web, player, player bridge, and MySQL.
- [remote.yml](remote.yml) - remote player-only stack for machines that sit behind the player bridge.
- [local.env.example](local.env.example) - sample environment values for the public stack.
- [remote.env.example](remote.env.example) - sample environment values for the remote stack.
## Stack Overview
### Public stack
The main compose stack is the most complete setup. It runs:
- `web` - the dashboard and admin app.
- `player` - the local screen player.
- `player-bridge` - the bridge service that proxies dashboard commands and player communication.
- `mysql` - the database used by the web, player, and bridge services.
This is the stack to use when you want the full app running on one machine.
### Remote player
The remote stack runs only the `player` service.
Use it when the player is installed on a remote device and connects back through the player bridge instead of running the full app separately.
## Services
### `web`
The dashboard and admin application.
Responsibilities:
- serves the web UI on port `8080`
- reads and writes application data from MySQL
- forwards player actions through the configured bridge base URL
- renders connected clients, dashboard pages, and admin workflows
Key configuration:
- `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`
- `PULSE_SIGNAGE_SHARED_SECRET`
- `SESSION_MAX_AGE_DAYS`
- `DEFAULT_ADMIN_USERNAME`
- `DEFAULT_ADMIN_NAME`
- `DEFAULT_ADMIN_PASSWORD`
- `PASSWORD_HASH_ITERATIONS`
### `player`
The screen runtime that renders playlists and receives commands.
Responsibilities:
- serves the player UI on port `8081`
- connects to MySQL in local mode
- connects to the bridge in remote mode through `THIN_CLIENT_BASE_URL`
- registers live connections and accepts control commands
Key configuration:
- `PLAYER_PUBLIC_BASE_URL`
- `PLAYER_INTERNAL_BASE_URL`
- `PLAYER_IDENTIFIER`
- `THIN_CLIENT_BASE_URL` in remote mode
- `PULSE_SIGNAGE_SHARED_SECRET`
- database settings in local mode
### `player-bridge`
The bridge layer that connects the dashboard to the player network.
Responsibilities:
- serves the bridge API on port `8090`
- forwards authenticated dashboard commands to registered players
- exposes screen connection snapshots and player registration data
- proxies command traffic between the web app and remote players
Key configuration:
- `PULSE_SIGNAGE_SHARED_SECRET`
- `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`
### `mysql`
The MySQL 8.4 database used by the public stack.
Responsibilities:
- stores application data, screen state, onboarding state, and registry records
- provides persistent storage through `mysql_data`
Key configuration:
- `MYSQL_DATABASE`
- `MYSQL_USER`
- `MYSQL_PASSWORD`
- `MYSQL_ROOT_PASSWORD`
## Environment Files
### `local.env.example`
Use this file as a starting point for the public compose stack.
Important values:
- `PULSE_SIGNAGE_IMAGE` - image to run for all app services
- `PULSE_SIGNAGE_SHARED_SECRET` - shared secret used for request authentication
- `PLAYER_IDENTIFIER` - unique local player identifier
- `DB_*` - MySQL credentials and database name for the stack
- `PLAYER_PUBLIC_BASE_URL` - public URL the player advertises
- `PLAYER_INTERNAL_BASE_URL` - internal URL the web app uses for local player calls
- `SESSION_MAX_AGE_DAYS` - dashboard session lifetime
- `DEFAULT_ADMIN_*` - bootstrap admin account values
- `PASSWORD_HASH_ITERATIONS` - password hashing cost
### `remote.env.example`
Use this file on a remote player device.
Important values:
- `PULSE_SIGNAGE_IMAGE` - image to run on the device
- `PULSE_SIGNAGE_SHARED_SECRET` - must match the public stack
- `PLAYER_IDENTIFIER` - unique remote player identifier
- `PLAYER_PUBLIC_BASE_URL` - public URL for the remote player
- `THIN_CLIENT_BASE_URL` - bridge URL the player connects back to
- `PLAYER_AGENT_RECONNECT_DELAY_MS` - reconnect delay for the player agent
## Main Configuration Variables
| Variable | Used By | Purpose |
| --- | --- | --- |
| `PULSE_SIGNAGE_IMAGE` | all services | Docker image to run for the app services. |
| `PULSE_SIGNAGE_SHARED_SECRET` | web, player, bridge | Shared secret for authenticated requests between services. |
| `DB_HOST` | web, player, bridge | Database host name. |
| `DB_PORT` | web, player, bridge | Database port. |
| `DB_NAME` | web, player, bridge | Database name. |
| `DB_USER` | web, player, bridge | Database user. |
| `DB_PASSWORD` | web, player, bridge | Database password. |
| `MYSQL_ROOT_PASSWORD` | mysql | Root password for the local MySQL container. |
| `PLAYER_PUBLIC_BASE_URL` | player | Public URL advertised by the player. |
| `PLAYER_INTERNAL_BASE_URL` | web, player | Internal player URL used by the dashboard and player runtime. |
| `PLAYER_IDENTIFIER` | player | Stable player identifier. |
| `THIN_CLIENT_BASE_URL` | player, remote player | URL of the bridge service. |
| `SESSION_MAX_AGE_DAYS` | web | Session cookie lifetime. |
| `DEFAULT_ADMIN_USERNAME` | web | Bootstrap admin username. |
| `DEFAULT_ADMIN_NAME` | web | Bootstrap admin display name. |
| `DEFAULT_ADMIN_PASSWORD` | web | Bootstrap admin password. |
| `PASSWORD_HASH_ITERATIONS` | web | Password hashing cost. |
| `PLAYER_AGENT_RECONNECT_DELAY_MS` | remote player | Delay before reconnecting to the bridge. |
## Ports
Public stack ports:
- `8080` - web dashboard
- `8081` - player
- `8090` - player bridge
- `3306` - MySQL
Remote stack ports:
- `8081` - player only
## Volumes
### Public stack
- `mysql_data` - persistent MySQL data.
- `pulse-signage` - shared media and cache volume for the app services.
### Remote stack
- `pulse-signage` - shared media and cache volume for the remote player.
## Networks
Each compose file creates its own named network:
- `pulse-signage` for the public stack
- `pulse-signage-remote` for remote player deployment.
## Notes
- The public stack expects the app services and MySQL to share the same `PULSE_SIGNAGE_SHARED_SECRET`.
- The bridge service is the dashboard-facing command path for connected remote players.
- The remote player should point `THIN_CLIENT_BASE_URL` at the bridge, not at the public web endpoint.
- The `PULSE_SIGNAGE_IMAGE` tag defaults to the published image, but it can be overridden for local builds or custom releases.
## Recommended Setup
1. Copy `local.env.example` to a local `.env` file for the public stack.
2. Copy `remote.env.example` to a device-specific `.env` file for the remote player.
3. Make sure `PULSE_SIGNAGE_SHARED_SECRET` matches everywhere.
4. Start the public stack first, then start the remote player after the bridge is reachable.
5. Verify that the player appears in Connected clients before testing screen commands.
+19
View File
@@ -0,0 +1,19 @@
PULSE_SIGNAGE_IMAGE=git.lzstealth.com/lzstealth/pulse-signage:latest
PULSE_SIGNAGE_SHARED_SECRET=
PLAYER_IDENTIFIER=player-local
DB_HOST=mysql
DB_PORT=3306
DB_NAME=signage
DB_USER=signage_user
DB_PASSWORD=signage_password
MYSQL_ROOT_PASSWORD=root_password
PLAYER_PUBLIC_BASE_URL=http://localhost:8081
PLAYER_INTERNAL_BASE_URL=http://player:8081
SESSION_MAX_AGE_DAYS=14
DEFAULT_ADMIN_USERNAME=admin
DEFAULT_ADMIN_NAME=Admin
DEFAULT_ADMIN_PASSWORD=admin
PASSWORD_HASH_ITERATIONS=310000
+103
View File
@@ -0,0 +1,103 @@
name: pulse-signage
services:
web:
image: ${PULSE_SIGNAGE_IMAGE:-git.lzstealth.com/lzstealth/pulse-signage:latest}
restart: unless-stopped
ports:
- "8080:8080"
environment:
DB_HOST: ${DB_HOST:-mysql}
DB_PORT: ${DB_PORT:-3306}
DB_NAME: ${DB_NAME:-signage}
DB_USER: ${DB_USER:-signage_user}
DB_PASSWORD: ${DB_PASSWORD:-signage_password}
PULSE_SIGNAGE_SHARED_SECRET: ${PULSE_SIGNAGE_SHARED_SECRET:-}
SESSION_MAX_AGE_DAYS: ${SESSION_MAX_AGE_DAYS:-14}
DEFAULT_ADMIN_USERNAME: ${DEFAULT_ADMIN_USERNAME:-admin}
DEFAULT_ADMIN_NAME: ${DEFAULT_ADMIN_NAME:-Admin}
DEFAULT_ADMIN_PASSWORD: ${DEFAULT_ADMIN_PASSWORD:-admin}
PASSWORD_HASH_ITERATIONS: ${PASSWORD_HASH_ITERATIONS:-310000}
volumes:
- pulse-signage:/app/media
command: ["node", "src/web.js"]
depends_on:
mysql:
condition: service_healthy
networks:
- pulse_signage
player:
image: ${PULSE_SIGNAGE_IMAGE:-git.lzstealth.com/lzstealth/pulse-signage:latest}
restart: unless-stopped
ports:
- "8081:8081"
environment:
PLAYER_PUBLIC_BASE_URL: ${PLAYER_PUBLIC_BASE_URL:-http://localhost:8081}
PLAYER_INTERNAL_BASE_URL: ${PLAYER_INTERNAL_BASE_URL:-http://player:8081}
PLAYER_IDENTIFIER: ${PLAYER_IDENTIFIER:-player-local}
PULSE_SIGNAGE_SHARED_SECRET: ${PULSE_SIGNAGE_SHARED_SECRET:-}
DB_HOST: ${DB_HOST:-mysql}
DB_PORT: ${DB_PORT:-3306}
DB_NAME: ${DB_NAME:-signage}
DB_USER: ${DB_USER:-signage_user}
DB_PASSWORD: ${DB_PASSWORD:-signage_password}
volumes:
- pulse-signage:/app/media
command: ["node", "src/player.js"]
depends_on:
mysql:
condition: service_healthy
networks:
- pulse_signage
player-bridge:
image: ${PULSE_SIGNAGE_IMAGE:-git.lzstealth.com/lzstealth/pulse-signage:latest}
restart: unless-stopped
ports:
- "8090:8090"
environment:
PULSE_SIGNAGE_SHARED_SECRET: ${PULSE_SIGNAGE_SHARED_SECRET:-}
DB_HOST: ${DB_HOST:-mysql}
DB_PORT: ${DB_PORT:-3306}
DB_NAME: ${DB_NAME:-signage}
DB_USER: ${DB_USER:-signage_user}
DB_PASSWORD: ${DB_PASSWORD:-signage_password}
command: ["node", "src/player-bridge/index.js"]
depends_on:
mysql:
condition: service_healthy
networks:
- pulse_signage
mysql:
image: mysql:8.4
restart: unless-stopped
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: ${DB_NAME:-signage}
MYSQL_USER: ${DB_USER:-signage_user}
MYSQL_PASSWORD: ${DB_PASSWORD:-signage_password}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root_password}
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -uroot -p$$MYSQL_ROOT_PASSWORD"]
interval: 10s
timeout: 5s
retries: 10
networks:
- pulse_signage
volumes:
mysql_data:
pulse-signage:
networks:
pulse_signage:
name: pulse-signage
external: false
+8
View File
@@ -0,0 +1,8 @@
PULSE_SIGNAGE_IMAGE=git.lzstealth.com/lzstealth/pulse-signage:latest
PULSE_SIGNAGE_SHARED_SECRET=
PLAYER_IDENTIFIER=player-remote
PLAYER_PUBLIC_BASE_URL=http://localhost:8081
THIN_CLIENT_BASE_URL=http://192.168.0.80:8090
PLAYER_AGENT_RECONNECT_DELAY_MS=2000
+28
View File
@@ -0,0 +1,28 @@
name: pulse-signage-remote
services:
player:
image: ${PULSE_SIGNAGE_IMAGE:-git.lzstealth.com/lzstealth/pulse-signage:latest}
restart: unless-stopped
ports:
- "8081:8081"
environment:
PLAYER_PUBLIC_BASE_URL: ${PLAYER_PUBLIC_BASE_URL:-http://localhost:8081}
THIN_CLIENT_BASE_URL: ${THIN_CLIENT_BASE_URL:-}
PULSE_SIGNAGE_SHARED_SECRET: ${PULSE_SIGNAGE_SHARED_SECRET:-}
volumes:
- pulse-signage:/app/media
command: ["node", "src/player.js"]
networks:
- pulse_signage
volumes:
pulse-signage:
networks:
pulse_signage:
name: pulse-signage-remote
external: false