Self-hosted live screen monitoring for a fleet of machines. A small client on each machine captures the screen and streams it to a central server, which serves a live web dashboard — thumbnails of every machine, click-to-zoom live view, on-change screenshot history, per-client capture settings, and offline detection.
Built in Rust: one static server binary, one static client binary, no runtime dependencies.
(2.0.1: fixes the Windows installer build and teaches it to write the token into config.toml.)
- Shared-token authentication on every route (dashboard and the client→server ingest
path). Set
SCREEN_MONITOR_TOKENon the server; clients send it back. Without it the server ran wide open to anyone who could reach the port. - Automatic TLS: the server generates a self-signed
cert.pem/key.pemon first run (viarcgen) so it works out of the box — no more manualopensslstep just to start it. - Runs on Linux and macOS, not just Windows — the server and client now build for all three, with CI-built release binaries attached to every tag.
- Env-configurable bind address / public host / TLS name (were hard-coded constants).
cargo testcoverage for the token, path-sanitizer and tiled-protocol logic; clippy-clean.
⚠️ Read Security model before deploying. This tool captures screens and runs on managed machines. Run it on networks and machines you are authorized to monitor, with the token enabled.
┌─────────────┐ HTTPS (tiled JPEG deltas + heartbeats) ┌──────────────────┐
│ client │ ─────────────────────────────────────────▶ │ server │
│ (each PC) │ Authorization: Bearer <token> │ (one host) │
│ │ ◀───────────────────────────────────────── │ │
└─────────────┘ per-client capture config (pull) │ • composites │
│ tiles → JPEG │
┌─────────────┐ HTTPS + sm_token cookie │ • MJPEG stream │
│ dashboard │ ◀────────────────────────────────────────── │ • screenshot │
│ (browser) │ live view, thumbnails, screenshots │ history (disk)│
└─────────────┘ └──────────────────┘
- The client sends only changed tiles (128×128 JPEG blocks, keyed by a BLAKE3 hash), with a periodic keyframe, so idle screens cost almost no bandwidth. The server keeps a per-client canvas, pastes tiles in, and re-encodes one composite JPEG for viewers.
- A separate screenshot channel stores full-resolution, high-quality captures on disk (on an interval or on-change), with a per-client history cap.
- The client prevents sleep/screensaver and, on Windows, captures the active console session so it keeps working across the login screen / session switches.
Download the archive for your OS from the latest release
and unpack it. Each archive contains screen-monitor-server, screen-monitor-client, this
README and the example config.
Server (any machine reachable by the clients):
export SCREEN_MONITOR_TOKEN="a-long-random-secret" # strongly recommended
export SCREEN_MONITOR_PUBLIC_HOST="monitoring.example.com:3000"
./screen-monitor-server
# First run writes a self-signed cert.pem/key.pem next to the binary.
# Dashboard: https://<PUBLIC_HOST>/ (sign in with the token)
# Health: https://<PUBLIC_HOST>/healthWindows — two packages (both in the release):
-
Portable zip —
screen-monitor-2.0.1-windows-x64.zipcontainsscreen-monitor-server.exeandscreen-monitor-client.exe. Run the server withrun-server.bat. -
Client installer —
screen-monitor-2.0.1-windows-x64-setup.exe, a silent Inno Setup package for rolling the client out across a fleet. It writesconfig.toml, registers and starts a scheduled task (fromscreen-monitor.xml) so the client survives reboots and logon, and cleans up any olderscreen-monitor*tasks/installs first. Interactive runs prompt for the server URL and access token; silent installs take them on the command line and write both (plusaccept_invalid_certs = true) intoconfig.toml:screen-monitor-2.0.1-windows-x64-setup.exe /SILENT /ServerUrl="https://mon.example:3000" /Token="a-long-random-secret"
Client (each monitored machine) — create config.toml next to the client binary:
server_url = "https://monitoring.example.com:3000"
token = "a-long-random-secret" # must equal the server's SCREEN_MONITOR_TOKEN
# client_id = "office-pc-01" # defaults to the hostname
accept_invalid_certs = true # needed for the auto-generated self-signed certThen run ./screen-monitor-client (or run-client.bat / create-config.bat on Windows).
| Variable | Default | Purpose |
|---|---|---|
SCREEN_MONITOR_TOKEN |
(unset) | Shared secret. When set, every request must present it. Unset = no auth (loud warning at startup). |
SCREEN_MONITOR_BIND |
0.0.0.0:3000 |
Socket to listen on. |
SCREEN_MONITOR_PUBLIC_HOST |
localhost:3000 |
Host shown in the startup banner (cosmetic). |
SCREEN_MONITOR_TLS_CN |
host part of PUBLIC_HOST |
CN/SAN baked into the auto-generated certificate. |
Runtime state is written next to the binary: cert.pem/key.pem, hosts.yaml (known
machines), client_configs.json (per-client settings), and screenshots/<client>/.
Per-client capture settings (fps, JPEG quality, scale, change threshold, screenshot mode & history, expected resolution) are edited live from the dashboard and pulled by the client.
One shared token guards everything. Clients authenticate with
Authorization: Bearer <token>; browsers sign in at /login, which sets an
HttpOnly; Secure; SameSite=Strict cookie. ?token=<token> is also accepted for scripting.
/health and /login stay open. The comparison is length-checked and constant-time-ish.
This is deliberately simple — a bearer secret, not per-user accounts. It stops anyone who doesn't hold the token; it is not a substitute for network controls.
The server speaks HTTPS only. On first run it writes a self-signed cert.pem/key.pem
(SAN = SCREEN_MONITOR_TLS_CN + localhost + hostname). Clients that don't trust it must
set accept_invalid_certs = true. For production, replace the two files with a certificate
from your internal CA — see ssl_guide.txt and cert.cnf. Existing files are never
overwritten; delete both to regenerate.
- Do: run behind the token, over the network segment you control, on machines you are
authorized to monitor. Distribute
cert.pemas trusted, or use a real CA cert, rather than leavingaccept_invalid_certs = truelong-term. - Limits: one shared token (no per-user identity or audit);
accept_invalid_certs = truedisables certificate verification (trusted networks only); the dashboard can change client capture configs, so anyone with the token has full control; screenshots are stored unencrypted on the server disk. Path traversal on client IDs and screenshot names is rejected; payload sizes and tile counts are bounded. - Not included: end-to-end encryption beyond TLS, tamper-proof logging, rate limiting.
# Linux needs XCB dev headers for screen capture (scrap):
sudo apt-get install -y libxcb1-dev libxcb-shm0-dev libxcb-randr0-dev
cargo build --release --bin screen-monitor-server --bin screen-monitor-client
cargo test --release
# Windows: build.batBinaries land in target/release/. The GitHub workflow builds Windows / Linux / macOS on
every v* tag (and via Run workflow with a tag input), runs the tests, builds the
Windows Inno Setup installer, and attaches the archives to the release.
| Path | |
|---|---|
src/main-server.rs |
axum server: ingest, compositing, MJPEG stream, screenshot store, auth, TLS |
src/main-client.rs |
screen capture (scrap), tiled-delta encoder, screenshot uploader |
src/dashboard.html |
single-page dashboard (served by the server) |
screen-monitor.iss |
Inno Setup installer (silent, registers a scheduled task) |
config.toml.example |
client config template |
ssl_guide.txt, cert.cnf |
bringing your own TLS certificate |