Documentation

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

shell
# 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

shell
./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 hardened lillithd.service

3. Create the first admin

shell
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.

Reverse proxy? The UI works behind one. Set 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-gnu target 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:

shell
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 /healthz stay available over HTTP for agent bootstrap and updates.
  • HSTS is sent on HTTPS responses, and session cookies become Secure automatically.
  • 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:

/etc/lillith/lillith.env
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.

Break-glass path. Local sign-in bypasses Entra assignment and app-role checks. Keep local passwords strong, review them regularly, and set 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.

windows · elevated
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.achi.tech, 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 / variablePurpose
--serviceRun the SYSTEM service (default long-running mode).
--trayRun the per-user tray process.
--installCopy to Program Files, register the task, start tray and service, register in Apps & features.
--uninstallRemove scheduled tasks, Run entries, firewall rules, Apps & features entry, and state; leaves the executable.
--registerOne-shot enrollment.
--updateCheck and stage an agent update.
--diagnosePrint state and one handshake attempt.
--upload-logsUpload an agent log bundle to the server.
--reset-pinForget the pinned server public key after an approved certificate change.
--statusOpen the status window.
--server URLServer address. Also LILLITH_SERVER.
--token TOKENOptional enrollment token. Also LILLITH_TOKEN.
--site SITESite label. Also LILLITH_SITE.
LILLITH_STATE_DIROverride %ProgramData%\Lillith.
LILLITH_DEFAULT_SERVEROverride the tokenless default server.
LILLITH_UPDATE_ON_START=0Disable the startup update check.

Configuration reference

Server settings are environment variables read at startup from /etc/lillith/lillith.env under systemd.

VariableDefaultPurpose
LILLITH_LISTEN127.0.0.1:8443Comma-separated HTTP bind addresses.
LILLITH_TLS_LISTEN0.0.0.0:443HTTPS bind address; empty disables built-in TLS.
LILLITH_DBsqlite://lillith.dbSQLite URL.
LILLITH_PACKAGES_DIRpackagesInstaller storage.
LILLITH_SCREENSHOTS_DIRsibling screenshotsScreenshot storage.
LILLITH_VNC_DIRsibling vnc-recordingsVNC session recordings.
LILLITH_ACME_DIRsibling acmeACME account and certificate storage.
LILLITH_LOG_DIRlogsDaily-rotated server logs.
LILLITH_AGENT_LOGS_DIRsibling agent-logsLog bundles uploaded by agents.
LILLITH_SESSION_HOURS1Local browser session lifetime, clamped 1–168 hours.
LILLITH_PENDING_TTL_HOURS24Idle hours before a pending approval expires, 1–720.
LILLITH_TIME_SYNC_HOURS168Automatic fleet time-sync interval; 0 disables.
LILLITH_TIME_DRIFT_HOURS6Clock-drift sampling interval; 0 disables.
LILLITH_AGENT_BINlillith-agent.exeAgent binary served for downloads and updates.
LILLITH_AGENT_VERSIONserver versionVersion advertised for that binary.
LILLITH_UPDATE_BETA_TAGbetaHost tag that receives agent updates first; empty updates everyone.
LILLITH_PUBLIC_URLrequest hostBase URL in generated install commands.
LILLITH_CANONICAL_HOSTOIDC callback host, else public URLHost browser pages are redirected to.
LILLITH_LOCAL_LOGINonSet 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/callbackMust match an app redirect URI.
LILLITH_OIDC_ISSUERtenant v2.0 issuerOverride the OpenID issuer.
LILLITH_OIDC_ADMIN_ROLELillith.AdminApp role that grants admin.
LILLITH_OIDC_SCOPESopenid profile emailSpace-separated scopes.
LILLITH_SECURE_COOKIESoffSet 1 when the UI is HTTPS-only behind a proxy.
LILLITH_TRUST_PROXYoffTrust 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.

RouteAuthPurpose
POST /api/v1/enrollEnrollment tokenExchange a token for a machine ID and secret.
GET /agent/connectMachine secretAgent WebSocket.
GET /download/agentMachine secretAgent binary and manifest.
GET /download/package/{id}Machine secretInstaller stream.
GET /auth/microsoft/callback—OIDC callback.
GET /auth/login—Local or Microsoft 365 sign-in.
POST /auth/logoutSessionEnd the session.
GET /machines, /machines/:idSessionFleet list and machine detail.
GET /jobs, /queue, /packagesSessionJob history, offline queue, and package library.
POST /machines/:id/jobsSession (admin)Invoke a typed operation.
POST /jobs/batchSession (admin)Invoke an operation across many machines.
GET /ui/eventsSessionUI 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.

Backups are sensitive. They contain password hashes, agent secrets, the Cloudflare API token, and the TLS private key. Server logs, the agent binary, and the server binary are not included.

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.