Installation
Prerequisites
- Linux (x86_64 or aarch64) with a desktop environment (X11 or Wayland), or macOS (arm64 or x86_64) — see macOS (M1) below for the macOS-specific install path
- Rust 1.88+ (MSRV) if installing from source
- Build dependencies for the full daemon (Linux only — macOS needs nothing
beyond Xcode Command Line Tools):
sudo apt install libudev-dev libwayland-dev libmpv-dev pkg-config - If
pkg-configcannot findlibudev, setPKG_CONFIG_PATH=/usr/lib/pkgconfig
Render backend
The software render backend (render_black, render_screensaver ladder stages)
is off by default. The full build below enables it. For a smaller build, omit
render and its libwayland-dev / libmpv-dev dependencies.
- Build: add
--features render—cargo build --release --features render - Dependencies:
libwayland-dev,libmpv-dev, andpkg-config - Without the feature, configs using render stages are rejected at startup with
E_RENDER_UNAVAILABLE; the daemon, CLI, and non-render sensors/displays still build and run normally.
Homebrew
The Legion Works tap publishes one formula per binary for macOS and Linuxbrew. Formulas are versioned with each release.
brew install legion-works/tap/dormantd
brew install legion-works/tap/dormantctl
brew install legion-works/tap/dormant-tray
Arch Linux (AUR)
dormant-bin installs pre-built x86_64 release binaries and the systemd user
units under /usr/lib/systemd/user/.
yay -S dormant-bin
From source
git clone https://github.com/legion-works/dormant.git
cd dormant
sudo apt install libudev-dev libwayland-dev libmpv-dev pkg-config
cargo build --release --features web-ui,render
install -Dm755 target/release/dormantd ~/.local/bin/dormantd
install -Dm755 target/release/dormantctl ~/.local/bin/dormantctl
Binaries land in ~/.local/bin/ — make sure this is on your PATH.
Tray applet
On Linux, dormant-tray is a KDE StatusNotifierItem applet. On macOS, it
is a native NSStatusItem menu-bar item. Both provide status, pause/resume,
and blank/wake controls through the daemon's Unix socket.

install -Dm755 target/release/dormant-tray ~/.local/bin/dormant-tray
See Tray autostart below to run it on every login.
From release
The cargo-dist pipeline publishes shell installers and tarballs for every binary. These URLs always resolve to the newest release; substitute download/vX.Y.Z for a specific one. Install on Linux x86_64 or aarch64:
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/legion-works/dormant/releases/latest/download/dormantd-installer.sh | sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/legion-works/dormant/releases/latest/download/dormantctl-installer.sh | sh
dormant-tray-installer.sh is also available in the same directory. Checksums are published alongside every artifact; verify with:
sha256sum -c dormantd-x86_64-unknown-linux-gnu.tar.xz.sha256
sha256sum -c dormantctl-x86_64-unknown-linux-gnu.tar.xz.sha256
Systemd user unit
dormant runs as a user service — it does not need root. The unit file
(dormant.service) is in the release tarball (under
dormantd-<triple>/systemd/dormant.service) and in the source tree at
crates/dormantd/systemd/dormant.service.
The AUR package installs the unit under /usr/lib/systemd/user/ already. Use
the manual paths below for source builds and release tarballs.
Install it:
mkdir -p ~/.config/systemd/user
# From source:
cp crates/dormantd/systemd/dormant.service ~/.config/systemd/user/
# From a release tarball:
tar -xf dormantd-x86_64-unknown-linux-gnu.tar.xz \
dormantd-x86_64-unknown-linux-gnu/systemd/dormant.service
mv dormantd-x86_64-unknown-linux-gnu/systemd/dormant.service \
~/.config/systemd/user/dormant.service
# Then enable and start:
systemctl --user daemon-reload
systemctl --user enable --now dormant
Check status:
systemctl --user status dormant
journalctl --user -u dormant -f
The unit runs as Type=notify, restarts on failure, and uses a 150-second engine-liveness watchdog. Reload with systemctl --user reload dormant. To stop:
systemctl --user stop dormant
When upgrading from a unit that used Type=simple, install the new
dormantd binary before copying or reloading the new unit. Then run
systemctl --user daemon-reload and systemctl --user restart dormant.
See Watchdog + last-known-good rollback.
Configuration file location
dormant reads config from these paths, first match wins:
--configCLI flag$DORMANT_CONFIGenvironment variable$XDG_CONFIG_HOME/dormant/config.toml(usually~/.config/dormant/config.toml)
MQTT credentials, HA tokens, and Samsung TV tokens go in a separate file with restricted permissions:
$XDG_CONFIG_HOME/dormant/credentials.toml
Set permissions to 600 — dormant will refuse to load a credentials file readable by others:
chmod 600 ~/.config/dormant/credentials.toml
Tray autostart
Linux (systemd)
Run dormant-tray on every graphical session with the provided user
unit — the same mechanism as the daemon. The unit file
(dormant-tray.service) ships in the release tarball under
dormant-tray-<triple>/systemd/dormant-tray.service and in the source
tree at crates/dormant-tray/systemd/dormant-tray.service.
mkdir -p ~/.config/systemd/user
# From source:
cp crates/dormant-tray/systemd/dormant-tray.service ~/.config/systemd/user/
# From a release tarball:
tar -xf dormant-tray-x86_64-unknown-linux-gnu.tar.xz \
dormant-tray-x86_64-unknown-linux-gnu/systemd/dormant-tray.service
mv dormant-tray-x86_64-unknown-linux-gnu/systemd/dormant-tray.service \
~/.config/systemd/user/dormant-tray.service
# Then enable and start:
systemctl --user daemon-reload
systemctl --user enable --now dormant-tray
The unit uses ExecStart=%h/.local/bin/dormant-tray, so systemd expands
the path from your home directory at launch — no reliance on PATH. It
starts after dormant.service and restarts on failure. A plain XDG
.desktop autostart does not work here: the systemd autostart generator
resolves a relative Exec= against a minimal boot PATH that excludes
~/.local/bin, so no unit gets generated.
macOS (launchd)
dormantctl launchd install writes both checked-in LaunchAgent plists to
~/Library/LaunchAgents/: com.legionworks.dormant.plist for the daemon and
com.legionworks.dormant-tray.plist for the tray. It does not bootstrap or
start either job. Start them immediately, without waiting for the next login:
dormantctl launchd install
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.legionworks.dormant.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.legionworks.dormant-tray.plist
The tray agent runs only in an Aqua session. It starts at login, restarts
after an unsuccessful exit, and throttles restarts to one per 10 seconds.
Its stdout and stderr logs are ~/Library/Logs/dormant/dormant-tray.log and
~/Library/Logs/dormant/dormant-tray.err.log.
To remove the managed plist files, first boot out any loaded jobs, then run:
launchctl bootout gui/$(id -u)/com.legionworks.dormant
launchctl bootout gui/$(id -u)/com.legionworks.dormant-tray
dormantctl launchd uninstall
dormantctl launchd uninstall removes the two files only. It does not boot
out loaded jobs, so launchd retains their last-loaded definitions until they
are explicitly booted out.
macOS (M1)
dormant runs natively on macOS (arm64 and x86_64), no root required. This milestone (M1) ships:
- DDC/CI display control (
dormantctl doctor ddcci) - The
macos-gamma-blackandmacos-display-sleepblank controllers - A CoreGraphics-based idle source
- Read-only diagnostics:
dormantctl doctor macos-idle,macos-display-sleep,macos-power
dormant-tray runs as a native NSStatusItem menu-bar item on macOS. See
Tray autostart: macOS (launchd) to install and start its
LaunchAgent with the daemon.
From release (macOS)
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/legion-works/dormant/releases/latest/download/dormantd-installer.sh | sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/legion-works/dormant/releases/latest/download/dormantctl-installer.sh | sh
Binaries land in ~/.local/bin/, same as Linux. The installer also prints
the next step — it deliberately does not run dormantctl launchd install
or launchctl bootstrap for you (that would start a background daemon
before you have configured it).
LaunchAgent (macOS)
dormant ships per-user LaunchAgents — the macOS analog of the systemd user
units above — as checked-in daemon and tray plists:
crates/dormantd/share/com.legionworks.dormant.plist and
crates/dormant-tray/share/com.legionworks.dormant-tray.plist.
dormantctl launchd install embeds byte-identical copies at build time.
Install it (idempotent, non-root — writes only under your home directory):
dormantctl launchd install
This atomically copies both plists to their canonical paths under
~/Library/LaunchAgents/, mode 0644. It does not start either agent —
bootstrap them explicitly:
launchctl bootstrap gui/$UID "$HOME/Library/LaunchAgents/com.legionworks.dormant.plist"
launchctl bootstrap gui/$UID "$HOME/Library/LaunchAgents/com.legionworks.dormant-tray.plist"
Check status and force an immediate (re)start:
launchctl print gui/$UID/com.legionworks.dormant
launchctl kickstart -k gui/$UID/com.legionworks.dormant
Reload config the same way as Linux — signal, not restart:
launchctl kill HUP gui/$UID/com.legionworks.dormant
Daemon logs land at ~/Library/Logs/dormant/dormantd.log (stdout) and
dormantd.err.log (stderr). Tray logs are
~/Library/Logs/dormant/dormant-tray.log (stdout) and
dormant-tray.err.log (stderr). Each LaunchAgent's ProgramArguments
creates that directory and redirects into it before exec-ing the binary,
so the shell never lingers as a supervisor.
To stop and remove:
launchctl bootout gui/$(id -u)/com.legionworks.dormant
launchctl bootout gui/$(id -u)/com.legionworks.dormant-tray
dormantctl launchd uninstall
launchd uninstall only removes the two canonical files. It does not boot
out a still-loaded label, so run bootout first or launchd will keep the
last-loaded definition in memory after the on-disk files are gone.
Lifecycle semantics (see the plist's own comments for the full rationale):
RunAtLoad=true+KeepAlive.SuccessfulExit=false: dormantd starts immediately on bootstrap/login and is relaunched whenever it exits, except a cleanexit 0.ThrottleInterval=10: launchd will not restart dormantd more than once every 10 seconds — the only restart-rate control here, this is a crash-loop rate limiter, not a liveness check.- No watchdog parity. Unlike the systemd unit's
WatchdogSec=150engine-liveness ping, launchd has no built-in mechanism to detect a dormantd that is running but wedged (hung without exiting). A wedged daemon on macOS is invisible to launchd and will not be restarted;dormantctl doctor exercise <display>and the emergency-wake path (see Troubleshooting) are the operator recourse there, independent of the supervisor. - Composition with boot-rollback. The crash-loop / last-known-good
rollback logic is entirely supervisor-agnostic —
it counts dormantd process starts sharing a config fingerprint within a
sliding window from its own state files, not from systemd or launchd.
So on macOS, launchd's 10-second
ThrottleIntervalpaces the restarts the same way systemd'sRestartSec=2does on Linux, and the crash-loop counter behind it still trips and rolls back to the last-known-good config after repeated failed starts — the supervisor differs, the rollback guarantee does not.