systemd-transient 0.2.0

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

systemd-transient

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

Usage

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

async fn example() -> Result<(), systemd_transient::Error> {
  let manager = SystemdTransientManager::session().await?;

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

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

A name without a unit type suffix becomes a service. Other transient types keep their suffix, e.g. start("vm.slice", []). Start a transient slice before any unit names it in Slice=: a slice that is only referenced is created implicitly and is not transient. Use escape_unit_name for user-supplied parts of a name; in slice names - means nesting.

ExecCommand::new uses the path as argv[0]; build an ExecCommand by hand for a different argv[0] or ignore_failure.

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

Reusing a name

ReusableTransientUnit (from manager.new_reusable(name, props)) starts the same unit again and again. status() reads the last run's state from the exit watch without a D-Bus call, release() drops the reference without stopping the unit, and stop() stops it and releases it.

Taking over units

A handle's reference ends with its bus connection, so units outlive the process that started them. manager.adopt(name), or ReusableTransientUnit::adopt(), takes over such a unit with a new reference and reports its exit as start would. A failed unit stays loaded until it is reset and can be adopted later; a unit that exited cleanly is collected once nothing references it, and adopt then returns None.

manager.list_units(&["prefix-*"]) lists loaded units by pattern, e.g. to find units no handle claims, and manager.stop_unit(name) stops any unit by its full name.

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.