Pure Elixir tools for working with systemd.
Hex package name: systemdkit. The Mix application and public modules remain :systemd / Systemd.
{:systemdkit, "~> 0.1.0-pre"}The package exposes a small D-Bus backed manager client:
{:ok, conn} = Systemd.Manager.connect()
{:ok, units} = Systemd.Manager.list_units(conn)
{:ok, unit} = Systemd.Manager.get_unit(conn, "dbus.service")
{:ok, state} = Systemd.UnitObject.state(conn, unit)
{:ok, service_state} = Systemd.UnitObject.service_state(conn, unit)It also includes a NimbleParsec-backed unit file parser/generator:
{:ok, unit_file} = Systemd.UnitFile.parse("[Service]\nExecStart=/bin/app start\n")
Systemd.UnitFile.to_string(unit_file)
unit_file =
Systemd.UnitFile.service(
unit: [description: "My app"],
service: [exec_start: "/bin/app start", restart: :always],
install: [wanted_by: "multi-user.target"]
)The package depends on rebus for the D-Bus wire protocol instead of shelling out to systemctl.
APIs return idiomatic {:ok, value} / {:error, %Systemd.Error{}} tuples. Permission and polkit failures are classified with category: :permission and can be checked with Systemd.Error.permission?/1.
See examples/ for service, timer, and user-bus snippets.
Permissions
Systemd control happens over D-Bus. Read-only calls such as listing units usually work as an unprivileged user. Mutating calls such as daemon reload, starting system units, enabling units, or writing to /etc/systemd/system may require root or a polkit rule for the caller. The package returns structured Systemd.Error values for D-Bus policy failures instead of retrying through sudo.
For user units, pass bus: :session when a systemd user session bus is available:
Systemd.list_units(bus: :session)Unit files
Systemd.UnitFile preserves comments, blank lines, duplicate directives, reset directives, and source spans. Validation is intentionally separate from parsing and includes directive-specific value checks for common service, socket, timer, and install keys:
unit_file = Systemd.UnitFile.parse!("[Service]\nExecStart=/bin/true\n")
:ok = Systemd.UnitFile.validate(unit_file, :service)Development
mix deps.get
mix ci
Integration tests are excluded by default because they require Linux with systemd and a system bus. For local development, run them inside the Lima Debian VM named systemd-test:
~/.local/bin/limactl shell systemd-test
cd /Users/dannote/Development/systemd
SYSTEMD_INTEGRATION=1 mix test
Or from macOS, copy the source into the VM and run the full integration suite:
scripts/integration_test.sh
Quick VM checks:
~/.local/bin/limactl shell systemd-test -- systemctl is-system-running
~/.local/bin/limactl shell systemd-test -- busctl --system list --no-pager
See CONTRIBUTING.md before publishing a release.