A simple, self-hosted Remote Monitoring & Management tool. It monitors system statistics across devices, gives you remote control (terminal, screen, power, files), wakes devices via Wake-on-LAN (including across LANs/VLANs through relay nodes), discovers devices on the network, and sends monitoring email alerts via Microsoft Graph. The dashboard is protected by Office 365 SSO.
Web UI: a modern dark/light dashboard (fleet overview, per-org devices, slide-in device drawer with live rings/terminal/actions), a 5-step setup wizard, admin settings, and a user account page — with a two-tier appearance system (workspace default + per-user override). Dependency-free vanilla HTML/CSS/JS in
server/app/static/.
Phase 2 (in progress): scripts (in-app editor, categories, attached files), scheduled jobs, and Datto-style monitoring policies — a monitor script that auto-runs a remediation script on failure, with policy variables passed to both as environment.
Status: Phase 1 (core RMM) is implemented and runnable. Phase 2 features (monitors library + auto-response, scripts/scheduled jobs, patch management, compliance evaluation, software audit, scheduled reports) are planned — see Roadmap.
The fastest way to get a working RMM is: run the server with Docker, open the dashboard, then install the agent on each machine you want to monitor.
- A host for the server with Docker + Docker Compose (or Python 3.11+ to run it natively on Linux).
- Machines to monitor (Windows or Linux) with Python 3.9+ for the agent.
- (Optional, for production) a Microsoft Entra/Office 365 app registration for SSO + Graph alert email — see SSO setup. Without it, the server starts in dev-auth mode so you can try it right away.
Option A — prebuilt image (recommended, enables one-click in-UI updates)
# docker-compose.yml
services:
leuffen-rmm:
container_name: leuffen-rmm
image: ghcr.io/mischa323/leuffenrrm-server:latest
network_mode: host
environment:
RMM_TLS_MODE: self-signed
RMM_SERVER_IMAGE: ghcr.io/mischa323/leuffenrrm-server:latest
volumes:
- rmm-data:/data
- /var/run/docker.sock:/var/run/docker.sock # required for in-UI updates
group_add:
- "${DOCKER_GID}" # see .env below
restart: unless-stopped
volumes:
rmm-data:Create a .env file next to docker-compose.yml with your host's Docker socket group ID:
echo "DOCKER_GID=$(stat -c '%g' /var/run/docker.sock)" > .env
docker compose up -dThe DOCKER_GID is host-specific (check with stat -c '%g' /var/run/docker.sock) and should not be committed — keep it in .env only.
Once running, one-click updates are available under Settings → General → About this server.
Option B — build from source
git clone https://github.com/Mischa323/Leuffenrrm.git
cd Leuffenrrm
docker compose up --build -dNo env editing needed — configuration happens in the browser (next step).
Note: building from source uses a local image digest, which disables the in-UI "Update & restart" button. Use Option A (prebuilt image) if you want one-click updates.
Open https://localhost:8000. The server uses HTTPS by default with a self-signed cert, so your browser warns once — accept it. On first boot you're taken to a setup wizard where you enter:
- Public URL — what agents/browsers use to reach the server (baked into installers).
- Administrator email — the global admin.
- HTTPS / TLS — self-signed (default), your own cert files, or behind a reverse proxy.
- Sign-in — Local + Microsoft 365 (default): username/password accounts (PBKDF2-hashed) plus optional Microsoft 365 SSO, matched by email. Or Dev mode (no login, evaluation only).
Settings are saved to the /data volume. Some (SSO, TLS, session secret) take
effect after a quick docker compose restart. A Default organisation is
created automatically.
Prefer environment variables and want to skip the wizard? Set
SESSION_SECRET(and any ofRMM_PUBLIC_URL,RMM_BOOTSTRAP_ADMIN,MS_*) indocker-compose.yml— the wizard is then bypassed. See Configuration reference.Prefer no Docker? See Run natively on Linux.
In the dashboard open an organisation → Downloads, then run the one-liner it shows (the server URL and that org's enrollment key are already baked in):
# Linux (run as root)
curl -fsSL http://YOUR_SERVER:8000/api/orgs/default/install.sh | sudo bash# Windows (PowerShell, as administrator)
iwr http://YOUR_SERVER:8000/api/orgs/default/install.ps1 -UseBasicParsing | iexThe device appears under Devices within a few seconds, auto-placed in its OS group (Windows / Linux / Windows Server), streaming live CPU/RAM/disk stats.
Windows MSI: the Downloads tab also offers a standalone MSI (no Python needed). It's built in CI (
.github/workflows/windows-agent-msi.yml, Windows runner) — push av*tag to publish it to Releases — and configured at install viamsiexecproperties (RMM_SERVER_URL,RMM_API_KEY,RMM_INSECURE_TLS).
- Click a device for inventory, an interactive terminal, power actions and Wake-on-LAN.
- To wake or scan machines on a remote LAN, open a device → Actions → Promote to network node, add its subnet(s), then use Network.
Full details (native install, Docker agent, SSO/Graph, cross-LAN WoL, config reference) are below.
Agents / Nodes (Win/Linux) ──WS──► Server (FastAPI) ◄──WS/REST── Browser dashboard
psutil + control handlers SQLite, alert engine, polished vanilla UI
node: WoL relay + scan MSAL SSO, Graph mailer (global + per-org)
- Server — FastAPI + SQLite. Runs in Docker or natively on Linux.
- Agent — one cross-platform Python agent (Windows + Linux) that holds a single outbound WebSocket to the server (works through NAT/firewalls). It is low-footprint: event-driven, cheap metrics, heavy screen deps loaded only on demand, runs at below-normal priority.
- Nodes — any agent can be promoted to a node from the dashboard to relay Wake-on-LAN to its local subnets and scan the network for devices.
- Multi-org — organisations are logical tenants inside your single Office 365 tenant; users are scoped to orgs by role. A global dashboard aggregates all your orgs and drills into each org's own dashboard.
- Groups — each org auto-creates Windows, Linux, and Windows Server groups; devices are auto-assigned on enrollment by OS. Add custom groups too.
# 1. Edit docker-compose.yml — at minimum change RMM_API_KEY and SESSION_SECRET.
docker compose up --build
# 2. Open http://localhost:8000With no MS_* values set, the server runs in dev-auth mode (auto-signs in a
local admin) so you can try it immediately. Configure SSO for real use (below).
python3 -m venv .venv && . .venv/bin/activate
pip install -r server/requirements.txt
cd server
RMM_API_KEY=change-me RMM_DEV_AUTH=1 \
uvicorn app.main:app --host 0.0.0.0 --port 8000A systemd unit for the server:
# /etc/systemd/system/leuffen-rmm.service
[Unit]
Description=Leuffen RMM Server
After=network-online.target
[Service]
WorkingDirectory=/opt/leuffen-rmm/server
Environment=RMM_API_KEY=change-me
Environment=RMM_DB_PATH=/var/lib/leuffen-rmm/rmm.db
ExecStart=/opt/leuffen-rmm/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
Restart=always
[Install]
WantedBy=multi-user.targetOpen the Downloads tab in an organisation. The agent is a single, self-contained download with the server URL + that org's enrollment key baked in, so it connects and registers with no manual configuration and lands in its default OS group.
# Linux (one-liner)
curl -fsSL http://YOUR_SERVER/api/orgs/<org>/install.sh | sudo bash# Windows (PowerShell, as admin)
iwr http://YOUR_SERVER/api/orgs/<org>/install.ps1 -UseBasicParsing | iexManual: download …/api/orgs/<org>/agent.zip, then python agent.py (the bundled
rmm_config.json carries the connection settings). The agent needs Python 3 with
psutil and websockets; screen control additionally uses mss, Pillow,
pynput (installed from agent/requirements.txt, imported only when used).
Monitor a Synology NAS like any other device — no manual setup. The Downloads tab shows a Package Center source URL for the organisation. In DSM:
- Package Center → Settings → Package Sources → Add — paste the URL
(
https://YOUR_SERVER/syno/<org>/<token>) and give it a name. - Back in Package Center, open the new source and install Leuffen RMM.
The NAS then appears in the device list reporting CPU, memory, storage, uptime,
system temperature, model and DSM version, plus volume/disk health — and supports
remote shell, scripts, the file browser and reboot/shutdown. The .spk is a
pure-stdlib noarch package the server assembles on demand with the connection
settings baked in. The only requirement on the NAS is an installed Python 3
package (official Python3.9, or a SynoCommunity Python3.10+ — install one from
Package Center if you don't already have it). The agent source is fetched live
from the agent repo (like the Windows MSI is proxied from its release) and
cached, so agent changes reach NASes without redeploying the server; it falls back
to the copy bundled in the image if GitHub is unreachable. Set
RMM_SYNOLOGY_AGENT_REF to pin a release tag (e.g. v2.2.22) or to bundled to
use only the in-image copy. Version bumps appear in Package Center as an upgrade. A global admin can hide the source / refuse new installs
from Settings → Agents → Synology NAS (RMM_SYNOLOGY_SOURCE).
docker run -d --name leuffen-agent --pid=host --network=host \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-e RMM_SERVER_URL=http://YOUR_SERVER -e RMM_API_KEY=<org-enroll-key> \
leuffen-rmm-agent--pid=host + --network=host give the agent host visibility and let a node
broadcast Wake-on-LAN. With the Docker socket mounted, the agent also reports the
host's running containers.
Wake-on-LAN is a layer-2 broadcast and cannot cross subnets on its own. Install the agent on a machine in the target LAN and Promote it to a node (device drawer → Actions). Add the LAN's subnets/VLANs (CIDR); the server then sends the magic packet through that node onto the right broadcast address — so the server itself never needs to be on the target LAN. Nodes also scan their subnets and report discovered hosts (IP/MAC/hostname/manufacturer) under Network, where wake-able hosts get a Wake button.
The agent holds a single outbound WebSocket to the server. Two mechanisms harden it beyond plain TLS:
1. TLS certificate pinning — even a self-signed / RMM_INSECURE_TLS agent can
be made MITM-proof by pinning the server's exact certificate.
- Get the server's fingerprint: Settings → Security → Agent certificate pinning
(or
GET /api/server-fingerprintas a global admin; it's also printed in the server log on startup). - Set it on the agent as
RMM_SERVER_FINGERPRINT(machine env var) or theserver_fingerprintkey in the agent'srmm_config.json. Colons/case optional. - When set, the agent accepts only that certificate after connect and refuses anything else as a possible MITM.
2. Per-device secret (trust-on-first-use) — on first connect the server issues
each agent a random secret (stored hashed in the settings table) and the agent
persists it; on every later reconnect the agent must present it, so a stolen
device_id alone can't impersonate a device. No setup needed — it's automatic and
backward compatible.
| Variable | Where | Purpose |
|---|---|---|
SESSION_SECRET |
server | Signing key for session cookies. A non-dev server refuses the insecure default and auto-generates one (persisted), but pin it explicitly in production. |
RMM_SERVER_FINGERPRINT |
agent | SHA-256 of the server's TLS cert → pins it (MITM-proof even with self-signed). Also settable via server_fingerprint in rmm_config.json. |
RMM_REQUIRE_DEVICE_SECRET |
server | Reject agents that present no device secret. Normally managed from Settings → Security → Device identity (this env var, if set, overrides the toggle). New installs default this on; existing installs default off so a not-yet-updated fleet isn't locked out. |
The device-secret requirement is a toggle in Settings → Security → Device identity
(applies immediately, no restart). It's on by default for new installs (detected
by having no enrolled devices yet) and off for existing installs, which you turn
on once the fleet is ready. Setting RMM_REQUIRE_DEVICE_SECRET in the environment
pins the value and hides it from the toggle.
Rollout order (existing installs): update all agents → confirm each has reconnected at least once (so the server has issued + stored its secret) → then enable Settings → Security → Device identity. Enabling it too early rejects legacy agents that haven't received a secret yet. (Reconnects of a device that already has a stored secret are always verified, toggle or not.)
-
App registration (Entra admin center → App registrations):
- Redirect URI (Web):
https://YOUR_SERVER/auth/callback - Note the Application (client) ID and Directory (tenant) ID.
- Create a client secret.
- API permissions → Microsoft Graph → Application →
Mail.Send→ Grant admin consent (used to send alert email from a service mailbox).
- Redirect URI (Web):
-
Server config (env / compose):
Variable Purpose MS_TENANT_IDYour tenant id (single-tenant ⇒ only your org can sign in) MS_CLIENT_ID/MS_CLIENT_SECRETApp registration credentials MS_REDIRECT_URIhttps://YOUR_SERVER/auth/callbackRMM_BOOTSTRAP_ADMINComma-separated emails granted global admin GRAPH_SENDERService mailbox to send alerts from SESSION_SECRETRandom secret for signing session cookies When
MS_CLIENT_IDis set, real SSO is enforced; otherwise dev-auth mode is used. SetRMM_DEV_AUTH=1to force dev mode.
There's no built-in alerting policy — every check is a monitor you add yourself from the Monitors tab, and every monitor can be scoped either to one site or globally (applies fleet-wide, manageable only by global admins). Two kinds:
- Template rules — pick a template (low disk space, high CPU, high memory,
device offline) from the gallery, tweak its threshold/sustained-duration/target,
and it's added as an ordinary rule. Internally this is just a row in
monitor_rules; a background loop evaluates every device against its effective rules (site-scoped + global) onRMM_ALERT_INTERVAL, and a per-device/per-rule state machine emails once on raise and once on clear. - Script policies — run any of your own scripts on a schedule, alert on a non-zero exit, and optionally auto-run a remediation script. Same site/global scoping applies.
Every monitor/rule has a severity (info / warning / critical, shown as a badge wherever it alerts) and a per-monitor email notifications toggle — turn it off to keep tracking state without sending mail. Edit any monitor or rule at any time (its scope stays fixed — delete and recreate to move it between site and global); delete or disable it just as freely. Nothing is hardcoded or seeded by environment variables.
All server settings can be entered in the first-run setup wizard (saved to the
DB) instead of via environment variables. The wizard is shown until completed;
setting SESSION_SECRET in the environment (or RMM_SKIP_SETUP=1) bypasses it.
Explicit environment variables always take precedence over wizard-saved values.
Server
| Variable | Default | Notes |
|---|---|---|
RMM_API_KEY |
(random) | Enrollment key for the seeded Default org |
RMM_SKIP_SETUP |
0 |
Skip the first-run setup wizard |
RMM_AUTH_MODE |
hybrid |
Sign-in: hybrid (local + optional M365, default) | dev |
RMM_LOGIN_MAX_FAILS |
5 |
Failed local logins (per IP+user) before a temporary lock |
RMM_LOGIN_WINDOW |
300 |
Rate-limit window / lock duration (seconds) |
RMM_PUBLIC_URL |
https://localhost:8000 |
Baked into agent downloads / SSO |
RMM_TLS_MODE |
self-signed |
self-signed | file | proxy |
RMM_TLS_CERT / RMM_TLS_KEY |
<data>/tls/* |
Cert/key paths (self-signed/file) |
RMM_HOST / RMM_PORT |
0.0.0.0 / 8000 |
Bind address/port |
RMM_DB_PATH |
server/data/rmm.db |
SQLite location (mount a volume) |
RMM_OFFLINE_AFTER |
120 |
Seconds before a device is "offline" |
RMM_METRIC_RETENTION |
604800 |
Metric retention (seconds) |
RMM_ALERT_INTERVAL |
60 |
Alert evaluation interval (seconds) |
RMM_GH_REPO |
Mischa323/Leuffenrrm |
GitHub owner/repo the dashboard reads agent releases from — i.e. the "Latest" version shown in the device drawer. Point at mischa323/leuffen-rmm-agent when the agent is built/released from its own repo. |
RMM_MSI_URL |
(leuffenrrm latest MSI) | Direct URL the server streams the Windows agent MSI from for downloads and updates. Set this to the agent repo's release asset so updates install the matching build: https://github.com/mischa323/leuffen-rmm-agent/releases/latest/download/leuffen-rmm-agent.msi. Keep it in sync with RMM_GH_REPO — RMM_GH_REPO only controls the version shown, RMM_MSI_URL the installer actually delivered; a mismatch makes the UI show a new version while handing out an old MSI (update fails with MSI 1603). |
MS_*, GRAPH_SENDER, SESSION_SECRET, RMM_BOOTSTRAP_ADMIN |
— | SSO / mail |
Agent
| Variable | Default | Notes |
|---|---|---|
RMM_SERVER_URL |
— | e.g. http://server:8000 (also from rmm_config.json) |
RMM_API_KEY |
— | Org enrollment key (also from rmm_config.json) |
RMM_INTERVAL |
30 |
Metric report interval (seconds) |
The server speaks HTTPS by default. Choose a mode with RMM_TLS_MODE:
| Mode | What it does | Use when |
|---|---|---|
self-signed (default) |
Generates a self-signed cert into the data volume on first boot and serves HTTPS. | Quick start, internal/LAN use. Browsers warn once; agents are auto-configured to trust it. |
file |
Serves HTTPS using your own RMM_TLS_CERT / RMM_TLS_KEY. |
You already have a real cert (e.g. from Let's Encrypt/certbot, or your CA). |
proxy |
Serves plain HTTP and trusts X-Forwarded-*. |
Behind a reverse proxy (Caddy/nginx/Traefik) that terminates TLS. |
Nothing to do — just open https://YOUR_SERVER:8000. Agents installed from this
server are configured with RMM_INSECURE_TLS=1 so they accept the self-signed
cert; everything between agent and server is still encrypted.
A ready-made deployment in deploy/ uses Caddy to obtain and auto-renew a
Let's Encrypt certificate and reverse-proxy to the server (which runs in proxy
mode):
cd deploy
export RMM_DOMAIN=rmm.example.com # must resolve to this host; ports 80+443 open
docker compose -f docker-compose.letsencrypt.yml up --build -dCaddy fetches the cert on first request and renews it automatically. Agents enrolled from this server verify the real certificate normally.
If you run certbot yourself, mount the files and use file mode:
environment:
RMM_TLS_MODE: "file"
RMM_TLS_CERT: "/certs/fullchain.pem"
RMM_TLS_KEY: "/certs/privkey.pem"
volumes:
- /etc/letsencrypt/live/rmm.example.com:/certs:roSet RMM_TLS_MODE=proxy and have nginx/Traefik/Caddy terminate TLS and forward
to the server on port 8000 with X-Forwarded-Proto/X-Forwarded-For headers.
Always set
RMM_PUBLIC_URL(andMS_REDIRECT_URIfor SSO) to the https:// URL clients actually use, so agent downloads and SSO redirects are correct.
SSO protects the dashboard, REST and dashboard WebSockets, and scopes users to
their orgs by role. Agents authenticate with a per-org enrollment key — keep
each secret and rotatable. Remote shell/screen/files are powerful: run behind a
reverse proxy with TLS, and don't expose enrollment keys publicly. The Graph
permission is least-privilege (Mail.Send only).
- Phase 1 (done): monitoring, inventory, two-level dashboards, remote control, Wake-on-LAN + nodes + discovery, orgs/groups/standards, SSO + alert mail, Docker + native Linux, container monitoring.
- Phase 2: monitors library + auto-response; scripts/components + scheduled jobs with history; compliance baseline evaluation; installed-software audit; patch management (scan/report/scheduled apply); scheduled & on-demand reports.
- Phase 3 (optional): user-configurable dashboard widgets.
The full design lives in the approved implementation plan.