Deploy the control plane.
Prerequisites, build steps, the first admin account, enrollment, and the full configuration reference. Everything here reflects the 0.6.10 codebase.
Quick start
Lillith is a single Rust workspace: a shared protocol crate, the lillithd server, and the Windows agent. The server embeds the web UI, so there are no separate static files to copy.
1. Build the workspace
# check and test everything
make check
make test
# release builds
make server
make agent-windows
# or invoke cargo directly
cargo build --release --workspace
cargo build --release -p lillith-agent --target x86_64-pc-windows-gnu
2. Deploy to the server
./deploy/deploy.sh user@server
# or, after building:
./deploy/deploy.sh user@server --skip-build
The script installs:
/opt/lillith/lillithd— the server binary/opt/lillith/lillith-agent.exe— served at/download/agent/etc/lillith/lillith.env— created once; later deploys leave it alone/var/lib/lillith/{lillith.db,packages,screenshots,acme}/var/log/lillith, plus a hardenedlillithd.service
3. Create the first admin
sudo -u lillith env LILLITH_DB=sqlite:///var/lib/lillith/lillith.db \
/opt/lillith/lillithd user add --username admin
# optional read-only account
sudo -u lillith env LILLITH_DB=sqlite:///var/lib/lillith/lillith.db \
/opt/lillith/lillithd user add --username viewer --role viewer
The service listens on 127.0.0.1:8443 by default. Change LILLITH_LISTEN in /etc/lillith/lillith.env for remote access. Once a certificate is issued, HTTPS listens on 0.0.0.0:443.
LILLITH_SECURE_COOKIES=1 when the browser only reaches it over HTTPS, and optionally LILLITH_TRUST_PROXY=1 to trust X-Forwarded-For.
Requirements
- Rust 1.90 or newer, plus a C linker.
- The
x86_64-pc-windows-gnutarget and mingw-w64 to build the Windows agent. - A Linux host for
lillithd(systemd unit included). - Windows clients that can reach the server over the LAN or VPN. They dial out; nothing listens inbound.
- Optional: a Cloudflare-managed domain for built-in ACME / Let's Encrypt certificates.
- Optional: a Microsoft Entra ID app registration for OIDC sign-in.
The server is designed for tens to a few hundred machines. SQLite is the only database requirement.
Server install
deploy/deploy.sh builds and copies the server and agent, writes the environment file if absent, creates data directories, and installs a hardened systemd unit. The unit runs as a dedicated user with ProtectSystem=strict, PrivateTmp, NoNewPrivileges, and ambient CAP_NET_BIND_SERVICE so it can bind port 443.
Edit /etc/lillith/lillith.env, then restart the service:
sudo systemctl restart lillithd
sudo systemctl status lillithd
sudo journalctl -u lillithd -f
HTTPS and ACME
Configure ACME under Settings: domain, contact email, staging or production, and a Cloudflare API token with Zone → DNS → Edit on the relevant zone. Test credentials writes a temporary TXT record to verify access. Certificates renew when older than 60 days, checked every 12 hours, and the HTTPS listener reloads.
- Once TLS is active, plain HTTP browser traffic is permanently redirected to HTTPS.
/api/*,/agent/*,/download/*, and/healthzstay available over HTTP for agent bootstrap and updates.- HSTS is sent on HTTPS responses, and session cookies become
Secureautomatically. - Certificate and key PEMs can be downloaded for a reverse proxy; the key is admin-only.
Accounts & sign-in
Local accounts store Argon2id password hashes. Two roles exist: admin can change the fleet and manage users; viewer can browse every page while every mutating request is rejected with 403 Forbidden — except saving their own UI preferences.
Microsoft 365 sign-in uses OpenID Connect authorization-code + PKCE. The Lillith.Admin app role maps to admin; every other assigned user is provisioned as a viewer. Set the four required variables and restart:
LILLITH_OIDC_TENANT_ID=<tenant-id>
LILLITH_OIDC_CLIENT_ID=<client-id>
LILLITH_OIDC_CLIENT_SECRET=<secret-value>
LILLITH_OIDC_REDIRECT_URI=https://lillith.example.com/auth/microsoft/callback
Add the exact callback URL under Authentication → Web → Redirect URIs, grant delegated openid, profile, and email permissions with admin consent, and set Assignment required? to Yes so Entra enforces assignment.
LILLITH_LOCAL_LOGIN=0 once local access is no longer needed.
Sessions default to one hour (LILLITH_SESSION_HOURS, clamped 1–168). Admins can revoke individual sessions or all other sessions immediately from Settings → Manage active sessions.
Enrollment
An agent can enroll with a token or request approval. Tokens are scoped to a site with an expiry and use limit; their values are shown once and stored hashed.
With no token, the client appears on the Pending page. Permit lets the admin edit the hostname and choose the site, defaulting to the requested values. Deny blocks that fingerprint for future tokenless attempts. Pending requests expire after LILLITH_PENDING_TTL_HOURS of inactivity (default 24) and revive if the agent polls again.
lillith-agent.exe --install --server https://lillith.example.com --site "Site A" --token <token>
# tokenless: omit --token and approve on the Pending page
lillith-agent.exe --install --server https://lillith.example.com --site "Site A"
With no token and no saved configuration, the agent defaults to https://lillith.nodomainrequired.com, overridable with LILLITH_DEFAULT_SERVER. Use tokens for unattended rollouts; the tray icon turns pink while a client is pending.
Permitted fingerprints receive a one-time machine secret. It is stored as a DPAPI machine-scope blob at %ProgramData%\Lillith, so copying state.json to another PC does not expose it.
Agent flags
One binary runs as a boot-time SYSTEM service and as a per-user tray. A bare run does not install; use --install or the generated install script.
| Flag / variable | Purpose |
|---|---|
| --service | Run the SYSTEM service (default long-running mode). |
| --tray | Run the per-user tray process. |
| --install | Copy to Program Files, register the task, start tray and service, register in Apps & features. |
| --uninstall | Remove scheduled tasks, Run entries, firewall rules, Apps & features entry, and state; leaves the executable. |
| --register | One-shot enrollment. |
| --update | Check and stage an agent update. |
| --diagnose | Print state and one handshake attempt. |
| --upload-logs | Upload an agent log bundle to the server. |
| --reset-pin | Forget the pinned server public key after an approved certificate change. |
| --status | Open the status window. |
| --server URL | Server address. Also LILLITH_SERVER. |
| --token TOKEN | Optional enrollment token. Also LILLITH_TOKEN. |
| --site SITE | Site label. Also LILLITH_SITE. |
| LILLITH_STATE_DIR | Override %ProgramData%\Lillith. |
| LILLITH_DEFAULT_SERVER | Override the tokenless default server. |
| LILLITH_UPDATE_ON_START=0 | Disable the startup update check. |
Configuration reference
Server settings are environment variables read at startup from /etc/lillith/lillith.env under systemd.
| Variable | Default | Purpose |
|---|---|---|
| LILLITH_LISTEN | 127.0.0.1:8443 | Comma-separated HTTP bind addresses. |
| LILLITH_TLS_LISTEN | 0.0.0.0:443 | HTTPS bind address; empty disables built-in TLS. |
| LILLITH_DB | sqlite://lillith.db | SQLite URL. |
| LILLITH_PACKAGES_DIR | packages | Installer storage. |
| LILLITH_SCREENSHOTS_DIR | sibling screenshots | Screenshot storage. |
| LILLITH_VNC_DIR | sibling vnc-recordings | VNC session recordings. |
| LILLITH_ACME_DIR | sibling acme | ACME account and certificate storage. |
| LILLITH_LOG_DIR | logs | Daily-rotated server logs. |
| LILLITH_AGENT_LOGS_DIR | sibling agent-logs | Log bundles uploaded by agents. |
| LILLITH_SESSION_HOURS | 1 | Local browser session lifetime, clamped 1–168 hours. |
| LILLITH_PENDING_TTL_HOURS | 24 | Idle hours before a pending approval expires, 1–720. |
| LILLITH_TIME_SYNC_HOURS | 168 | Automatic fleet time-sync interval; 0 disables. |
| LILLITH_TIME_DRIFT_HOURS | 6 | Clock-drift sampling interval; 0 disables. |
| LILLITH_AGENT_BIN | lillith-agent.exe | Agent binary served for downloads and updates. |
| LILLITH_AGENT_VERSION | server version | Version advertised for that binary. |
| LILLITH_UPDATE_BETA_TAG | beta | Host tag that receives agent updates first; empty updates everyone. |
| LILLITH_PUBLIC_URL | request host | Base URL in generated install commands. |
| LILLITH_CANONICAL_HOST | OIDC callback host, else public URL | Host browser pages are redirected to. |
| LILLITH_LOCAL_LOGIN | on | Set 0 to require Microsoft 365 when OIDC is configured. |
| LILLITH_OIDC_TENANT_ID | — | Entra tenant ID; enables Microsoft 365 sign-in. |
| LILLITH_OIDC_CLIENT_ID | — | Entra application (client) ID. |
| LILLITH_OIDC_CLIENT_SECRET | — | Entra client secret value. |
| LILLITH_OIDC_REDIRECT_URI | <public URL>/auth/microsoft/callback | Must match an app redirect URI. |
| LILLITH_OIDC_ISSUER | tenant v2.0 issuer | Override the OpenID issuer. |
| LILLITH_OIDC_ADMIN_ROLE | Lillith.Admin | App role that grants admin. |
| LILLITH_OIDC_SCOPES | openid profile email | Space-separated scopes. |
| LILLITH_SECURE_COOKIES | off | Set 1 when the UI is HTTPS-only behind a proxy. |
| LILLITH_TRUST_PROXY | off | Trust X-Forwarded-For behind a proxy. |
Endpoints
The browser UI, agent protocol, and binary downloads are served by the same process. Browser pages require a session; agent routes require a machine secret.
| Route | Auth | Purpose |
|---|---|---|
| POST /api/v1/enroll | Enrollment token | Exchange a token for a machine ID and secret. |
| GET /agent/connect | Machine secret | Agent WebSocket. |
| GET /download/agent | Machine secret | Agent binary and manifest. |
| GET /download/package/{id} | Machine secret | Installer stream. |
| GET /auth/microsoft/callback | — | OIDC callback. |
| GET /auth/login | — | Local or Microsoft 365 sign-in. |
| POST /auth/logout | Session | End the session. |
| GET /machines, /machines/:id | Session | Fleet list and machine detail. |
| GET /jobs, /queue, /packages | Session | Job history, offline queue, and package library. |
| POST /machines/:id/jobs | Session (admin) | Invoke a typed operation. |
| POST /jobs/batch | Session (admin) | Invoke an operation across many machines. |
| GET /ui/events | Session | UI live-update WebSocket. |
| GET /healthz | — | Liveness probe. |
Operations
No operation accepts a shell string. Each is a named request/response pair with a typed argument shape. The server only offers operations the connected agent advertised.
Read
system.info— hostname, OS name and build, uptime, users, IP/MAC, disks, RAM, CPU.programs.list— installed programs from the uninstall registry, 32- and 64-bit HKLM plus HKCU.program.version.get— fuzzy display-name matches with versions.rustdesk.status— RustDesk ID, version, and relay configuration.agent.status— agent version, service uptime, last sync, connection state.
Mutating
Mutating operations are gated server-side, require an authenticated admin, and are recorded in the audit log. Package installs download from lillithd, verify SHA-256, run with stored silent arguments, then re-query and report the version.
Everything else — services, updates, time, files, registry, screenshots, VNC, reboot, rename, Wake-on-LAN — follows the same pattern: a named operation, typed arguments, a deadline, and a result.
Backup & restore
Backup downloads one .tar.gz containing a consistent SQLite snapshot, uploaded packages, screenshots, VNC recordings, the ACME directory, a manifest, and a restore README. The database snapshot uses VACUUM INTO, so it is safe while the server runs, and the archive streams to the browser without holding the whole file in memory.
Restore accepts the same archive and validates the manifest, database integrity, schema, paths, and unfinished downloads before staging. Staged data is applied on the next server start, so the live connection pool is never swapped underneath clients. Under systemd the service restarts automatically; otherwise restart lillithd yourself.
The Backup page also reports database size, page count, and reclaimable free pages, and can run a WAL checkpoint plus VACUUM and PRAGMA optimize. Retention limits for jobs, events, audit rows, and screenshots live on Settings; 0 days keeps data forever.
Troubleshooting
A machine never appears in the fleet
Run lillith-agent.exe --diagnose on the host. It prints saved state and performs one handshake attempt. Check that the agent service is running, that the host can reach the server URL, and that the client has not been denied. Tokenless clients wait on the Pending page until permitted.
The certificate changed and an agent will not reconnect
If the server public key was pinned and the certificate changed by an approved process, run lillith-agent.exe --reset-pin on the host. Otherwise verify the certificate chain and the configured URL before resetting anything.
An agent saved HTTPS but cannot connect
The agent falls back to its plain-HTTP bootstrap port for enrollment and updates, and re-discovers the correct HTTPS base URL from /api/v1/server/info. Before persisting a switch it verifies the certificate and completes a real TLS WebSocket handshake, so a broken TLS stack cannot strand it.
Sign-in fails after configuring Microsoft 365
Check the exact redirect URI in Entra, the client secret value (not its ID), tenant ID, and app assignment. If discovery fails, the login page reports that sign-in is unavailable; the server log has the reason. With LILLITH_LOCAL_LOGIN=0, the local password form is hidden, so correct the OIDC configuration or temporarily re-enable local login.
A package deploy button is disabled
The host has a pending reboot. Lillith blocks package deployment in that state and shows a Reboot Pending tooltip; the server also rejects the request. Reboot the host, wait for the next inventory, then deploy.
VNC will not start
Remote control requires admin, a logged-in interactive user (the tray hosts the RFB server), and no other pending or active VNC session on that host. Check that the tray is running; the service cannot see the interactive desktop. Review the VNC tab for the session state and any cancellation.
The database needs reclaiming
Use the compaction action on the Backup page. It runs a WAL checkpoint, VACUUM, and PRAGMA optimize, and reports the reclaimable free pages before you do.
FAQ
Does Lillith require Active Directory?
No. There is no domain dependency. The agent is the management fabric, and local accounts or Entra ID handle administrative sign-in.
Does it need internet access?
No for core fleet management on a LAN or VPN. Internet is needed for ACME certificate issuance and for Microsoft 365 sign-in if you enable OIDC.
Can I run arbitrary commands?
No. There is deliberately no shell primitive in the protocol. Every action is a named, typed operation in a server-enforced catalog.
Can viewers make changes?
No. Every mutating request is rejected with 403, remote-control sessions and backup downloads require admin, and the UI hides write controls behind a read-only banner.
How large a fleet does it support?
It is designed for small-to-medium fleets — tens to a few hundred machines — across a handful of sites. A single server and SQLite are intentional choices at that scale.
What about macOS or Linux endpoints?
The agent builds on Linux for development and protocol tests, but the managed features are Windows-oriented. There is no macOS agent.
Is it open source?
No. Lillith is proprietary software. See the repository owner for license terms.