qsu 0.10.1

Service subsystem utilities and runtime wrapper.
Documentation
use std::{fs, path::Path};

use crate::err::Error;

/// Generate a systemd service unit file.
///
/// # Errors
/// [`Error::IO`] will be returned if file I/O failed when trying to generate
/// unit file.
pub fn install(ctx: super::RegSvc) -> Result<(), Error> {
  let service_binary_path = ::std::env::current_exe()?
    .to_str()
    .ok_or_else(|| Error::bad_format("Executable pathname is not utf-8"))?
    .to_string();

  //
  // [Unit]
  //
  let mut unit_lines: Vec<String> = vec![];
  unit_lines.push("[Unit]".into());
  if let Some(ref desc) = ctx.description {
    unit_lines.push(format!("Description={desc}"));
  }
  if ctx.netservice {
    // Note: The After=network.target does _not_ enure that the network is up
    //       and running before starting the service.  It's more suited to
    //       ensuring that the service gets _shut down_ before the network is.
    //
    // Trying to use the service subsystem to wait for the network is a bad
    // idea -- the service application should do it instead.
    //
    // See: https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/
    unit_lines.push("After=network.target".into());
  }

  //
  // [Service]
  //
  let mut svc_lines: Vec<String> = vec![];
  svc_lines.push("[Service]".into());
  if ctx.conf_reload {
    svc_lines.push("Type=notify-reload".into());
  } else {
    svc_lines.push("Type=notify".into());
  }

  if let Some(ref username) = ctx.runas.user {
    svc_lines.push(format!(r#"User="{username}""#));
  }
  if let Some(ref groupname) = ctx.runas.group {
    svc_lines.push(format!(r#"Group="{groupname}""#));
  }
  if let Some(ref umask) = ctx.runas.umask {
    svc_lines.push(format!(r#"UMask="{umask}""#));
  }

  /*
  if ctx.restart_on_failure {
    svc_lines.push(format!("Restart=on-failure"));
  }
  */
  svc_lines.push("#Restart=on-failure".to_string());
  svc_lines.push("#RestartSec=5s".to_string());

  if let Some(ll) = ctx.log_level {
    svc_lines.push(format!(r#"Environment="LOG_LEVEL={ll}""#));
  }
  if let Some(lf) = ctx.log_filter {
    svc_lines.push(format!(r#"Environment="LOG_FILTER={lf}""#));
  }
  if let Some(tf) = ctx.trace_filter {
    svc_lines.push(format!(r#"Environment="TRACE_FILTER={tf}""#));
  }
  if let Some(fname) = ctx.trace_file {
    svc_lines.push(format!(r#"Environment="TRACE_FILE={fname}""#));
  }
  for (key, value) in &ctx.envs {
    svc_lines.push(format!(r#"Environment="{key}={value}""#));
  }
  if let Some(wd) = ctx.workdir {
    svc_lines.push(format!("WorkingDirectory={wd}"));
  }

  //
  // Set up service executable and command line arguments.
  //
  //
  let mut cmdline = vec![service_binary_path];
  for arg in &ctx.args {
    cmdline.push(arg.clone());
  }
  svc_lines.push(format!("ExecStart={}", cmdline.join(" ")));

  //
  // [Install]
  //
  let inst_lines = [
    String::from("[Install]"),
    String::from("WantedBy=multi-user.target")
  ];

  //
  // Putting it all together
  //
  let blocks = [
    unit_lines.join("\n"),
    svc_lines.join("\n"),
    inst_lines.join("\n")
  ];

  let mut filebuf = blocks.join("\n\n");
  filebuf.push('\n');

  // ToDo: Set proper path
  let fname = format!("{}.service", ctx.svcname);
  let fname = Path::new(&fname);

  // ToDo: If file already exists, should it be assumed that the service needs
  //       to be stopped?
  if fname.exists() && !ctx.force {
    Err(Error::io("File already exists."))?;
  }

  fs::write(fname, filebuf)?;

  /*
  if install_location == System {
    "systemctl daemon-reload"

      if autorun {
        "systemctl enable {}.service"
      }
  }
  */

  Ok(())
}

#[expect(clippy::missing_errors_doc)]
#[expect(clippy::missing_const_for_fn)]
pub fn uninstall(_svcname: &str) -> Result<(), Error> {
  /*
  if install_location == System {
    systemctl stop {}.service
    rm {}.service
    systemctl daemon-reload
  }
  */
  Ok(())
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :