use clap::Clap; use std::process::Stdio; use tokio::process::Command; use std::path::Path; extern crate pretty_env_logger; #[macro_use] extern crate log; #[macro_use] extern crate serde_derive; #[macro_use] mod utils; /// Activation portion of the simple Rust Nix deploy tool #[derive(Clap, Debug)] #[clap(version = "1.0", author = "notgne2 ")] struct Opts { profile_path: String, closure: String, /// Command for activating the given profile #[clap(long)] activate_cmd: Option, /// Command for bootstrapping #[clap(long)] bootstrap_cmd: Option, /// Auto rollback if failure #[clap(long)] auto_rollback: bool, } pub async fn activate( profile_path: String, closure: String, activate_cmd: Option, bootstrap_cmd: Option, auto_rollback: bool, ) -> Result<(), Box> { info!("Activating profile"); Command::new("nix-env") .arg("-p") .arg(&profile_path) .arg("--set") .arg(&closure) .stdout(Stdio::null()) .spawn()? .await?; if let (Some(bootstrap_cmd), false) = (bootstrap_cmd, !Path::new(&profile_path).exists()) { let bootstrap_status = Command::new("bash") .arg("-c") .arg(&bootstrap_cmd) .env("PROFILE", &profile_path) .stdout(Stdio::null()) .stderr(Stdio::null()) .status() .await; match bootstrap_status { Ok(s) if s.success() => (), _ => { tokio::fs::remove_file(&profile_path).await?; good_panic!("Failed to execute bootstrap command"); } } } if let Some(activate_cmd) = activate_cmd { let activate_status = Command::new("bash") .arg("-c") .arg(&activate_cmd) .env("PROFILE", &profile_path) .status() .await; match activate_status { Ok(s) if s.success() => (), _ if auto_rollback => { Command::new("nix-env") .arg("-p") .arg(&profile_path) .arg("--rollback") .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn()? .await?; let c = Command::new("nix-env") .arg("-p") .arg(&profile_path) .arg("--list-generations") .output() .await?; let generations_list = String::from_utf8(c.stdout)?; let last_generation_line = generations_list .lines() .last() .expect("Expected to find a generation in list"); let last_generation_id = last_generation_line .split_whitespace() .next() .expect("Expected to get ID from generation entry"); debug!("Removing generation entry {}", last_generation_line); warn!("Removing generation by ID {}", last_generation_id); Command::new("nix-env") .arg("-p") .arg(&profile_path) .arg("--delete-generations") .arg(last_generation_id) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn()? .await?; // TODO: Find some way to make sure this command never changes, otherwise this will not work Command::new("bash") .arg("-c") .arg(&activate_cmd) .spawn()? .await?; good_panic!("Failed to execute activation command"); } _ => {} } } Ok(()) } #[tokio::main] async fn main() -> Result<(), Box> { if std::env::var("DEPLOY_LOG").is_err() { std::env::set_var("DEPLOY_LOG", "info"); } pretty_env_logger::init_custom_env("DEPLOY_LOG"); let opts: Opts = Opts::parse(); activate( opts.profile_path, opts.closure, opts.activate_cmd, opts.bootstrap_cmd, opts.auto_rollback, ) .await?; Ok(()) }