depl 2.4.3

Toolkit for a bunch of local and remote CI/CD actions
Documentation
//! Systemd service module.

use colored::Colorize;

use crate::entities::environment::RunEnvironment;
use crate::pipelines::DescribedPipeline;
use crate::project::DeployerProjectOptions;

const SYSTEMD_SERVICE_TEMPLATE: &str = r#"# Generated by Deployer 2.X
[Unit]
Description={description}
After=network.target{after}

[Service]
Type=notify
NotifyAccess=all
{restart_policy}
{restart_sec}
{user_field}

WorkingDirectory={working_dir}
{use_defined_shell}
Environment=DEPLOYER_NOTIFY_SYSTEMD=1
Environment=DEPLOYER_NOTIFY_SYSTEMD_AFTER_SECS=3
ExecStart={depl_path}{cache_and_storage_paths} run {pipeline_title}{cli_options}

[Install]
WantedBy=multi-user.target
"#;

/// Exports given pipeline to systemd service file.
pub fn export(
  config: &DeployerProjectOptions,
  pipeline: &DescribedPipeline,
  env: &RunEnvironment<'_>,
) -> anyhow::Result<()> {
  let project_dir = env.project_dir.ok_or(anyhow::anyhow!("Can't get project dir!"))?;

  let working_dir = project_dir
    .canonicalize()?
    .to_str()
    .ok_or(anyhow::anyhow!("Can't convert project dir to string!"))?
    .to_string();

  let depl_path = std::env::current_exe()?
    .canonicalize()?
    .to_str()
    .ok_or(anyhow::anyhow!("Can't get deployer executable path!"))?
    .to_string();

  let description = pipeline
    .desc
    .clone()
    .unwrap_or_else(|| format!("Run `{}` pipeline with Deployer", env.master_pipeline));

  let opts = pipeline.systemd_opts.clone().unwrap_or_default();

  let after = if !opts.after.is_empty() {
    format!(" {}", opts.after.join(" "))
  } else {
    String::new()
  };

  let use_defined_shell = if opts.use_defined_shell
    && let Ok(sh_path) = std::env::var("DEPLOYER_SH_PATH")
  {
    format!("Environment=DEPLOYER_SH_PATH={sh_path}")
  } else {
    String::new()
  };

  let cache_and_storage_paths = if opts.user.preserve_current_cache_rule() {
    format!(
      " --cache-folder {} --storage-folder {}",
      env.cache_dir.canonicalize()?.to_string_lossy(),
      env.storage_dir.canonicalize()?.to_string_lossy(),
    )
  } else {
    String::new()
  };

  let cli_options = if !opts.cli_options.is_empty() {
    format!(" {}", opts.cli_options)
  } else {
    String::new()
  };

  let (restart_policy, restart_sec) = if let Some(interval) = &opts.restart_interval {
    ("Restart=always".to_string(), format!("RestartSec={}", interval))
  } else {
    ("Restart=on-failure".to_string(), "RestartSec=5".to_string())
  };

  let service_content = SYSTEMD_SERVICE_TEMPLATE
    .replace("{description}", &description)
    .replace("{after}", &after)
    .replace("{restart_policy}", &restart_policy)
    .replace("{restart_sec}", &restart_sec)
    .replace("{user_field}", &opts.user.user_name_rule()?)
    .replace("{working_dir}", &working_dir)
    .replace("{use_defined_shell}", &use_defined_shell)
    .replace("{depl_path}", &depl_path)
    .replace("{cache_and_storage_paths}", &cache_and_storage_paths)
    .replace("{pipeline_title}", env.master_pipeline)
    .replace("{cli_options}", &cli_options);

  let service_name = format!("{}-{}", config.project_name, env.master_pipeline);
  let service_filename = format!("{}.service", service_name);
  std::fs::write(project_dir.join(&service_filename), service_content.as_str())?;

  println!("Systemd service file created: {}", service_filename.blue());
  println!();
  println!("To install the service, run:");
  println!("  sudo cp {service_filename} /etc/systemd/system/");
  println!("  sudo systemctl daemon-reload");
  println!("  sudo systemctl enable {}", service_name);
  println!("  sudo systemctl start {}", service_name);

  Ok(())
}