Start transient systemd services using dbus, just like systemd-run. https://docs.rs/systemd-transient
  • Rust 93.8%
  • Python 6%
  • Shell 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-20 14:47:28 +02:00
src handle: watch unit exit from before StartTransientUnit 2026-10-20 14:47:28 +02:00
tools handle: watch unit exit from before StartTransientUnit 2026-10-20 14:47:28 +02:00
.gitignore initial commit 2026-10-12 19:36:58 +02:00
.rustfmt.toml initial commit 2026-10-12 19:36:58 +02:00
Cargo.toml handle: watch unit exit from before StartTransientUnit 2026-10-20 14:47:28 +02:00
gen.sh handle: watch unit exit from before StartTransientUnit 2026-10-20 14:47:28 +02:00
LICENSE-APACHE initial commit 2026-10-12 19:36:58 +02:00
LICENSE-MIT initial commit 2026-10-12 19:36:58 +02:00
README.md handle: watch unit exit from before StartTransientUnit 2026-10-20 14:47:28 +02:00

systemd-transient

Start transient systemd services using dbus, just like systemd-run.

Usage

use systemd_transient::{ExecCommand, ServiceType, SystemdTransientManager, UnitExit, UnitProperty};

let manager = SystemdTransientManager::session().await?;

let unit = manager
  .start(
    "my-job".into(), // becomes my-job.service
    vec![
      UnitProperty::Description("example job".into()),
      UnitProperty::Type(ServiceType::Exec),
      UnitProperty::ExecStart(vec![ExecCommand {
        path: "/usr/bin/sleep".into(),
        argv: vec!["sleep".into(), "10".into()], // argv[0] included
        ignore_failure: false,
      }]),
    ],
  )
  .await?;

match unit.wait_for_exit().await? {
  UnitExit::Inactive => println!("exited cleanly"),
  UnitExit::Failed => println!("failed"),
  _ => println!("other outcome"),
}

start returns once the start job has finished, i.e. when systemd considers the unit started; for Type=oneshot that is after the command has run. Dropping or releasing the handle does not stop the unit.

start and stop have no client-side timeout. systemd applies the unit's TimeoutStartUSec, TimeoutStopUSec and JobTimeoutUSec and fails the job with JobWaitFailed { reason: "timeout", .. }. A Type=oneshot unit has no start timeout unless you set one. If you wrap start in your own timeout and drop the future, the job keeps running and you lose the handle.

start reuses the name of a unit that is loaded but failed or inactive, and fails with UnitAlreadyRunning if the unit is running.

The handle starts the unit with AddRef=true, so systemd keeps it loaded after it exits and wait_for_exit and stop can report its final state at any time. Release the reference with handle.release().await or by dropping the handle (released in a background task). Until then, start with the same name fails with UnitStillLoaded. With Restart= and the default RestartMode=normal, the unit passes through failed or inactive between runs, so wait_for_exit returns after the first run.

stop only stops the run the handle started. If someone else started the unit again (systemctl start), it returns UnitRestarted and leaves the new run alone.

Properties not covered by UnitProperty can be passed as UnitProperty::Custom(name, value).

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.