1use std::process::Command;23use crate::{4 command::CommandExt,5 db::{keys::list_hosts, secret::SecretDb, Db, DbData},6 nix::SYSTEMS_ATTRIBUTE,7};8use anyhow::Result;9use clap::Clap;10use log::{info, warn};1112#[derive(Clap)]13pub struct BuildSystems {14 15 #[clap(long, number_of_values = 1)]16 skip: Vec<String>,17 18 #[clap(long, env = "FLEET_LOCALHOST")]19 localhost: Option<String>,20 #[clap(subcommand)]21 subcommand: Option<Subcommand>,22}2324#[derive(Clap)]25enum Subcommand {26 27 Test,28 29 Boot,30 31 Switch,32}33impl Subcommand {34 fn should_switch_profile(&self) -> bool {35 matches!(self, Self::Test | Self::Switch)36 }37 fn name(&self) -> &'static str {38 match self {39 Self::Test => "test",40 Self::Boot => "boot",41 Self::Switch => "switch",42 }43 }44}4546impl BuildSystems {47 pub fn run(self) -> Result<()> {48 let db = Db::new(".fleet")?;49 let hosts = list_hosts()?;50 let data = SecretDb::open(&db)?.generate_nix_data()?;5152 for host in hosts.iter() {53 if self.skip.contains(host) {54 warn!("Skipping host {}", host);55 continue;56 }57 let is_local = Some(host) == self.localhost.as_ref();58 info!("Building host {}", host);59 let built = {60 let dir = tempfile::tempdir()?;61 dir.path().to_owned()62 };6364 Command::new("nix")65 .args(&["build", "--impure", "--no-link", "--out-link"])66 .arg(&built)67 .arg(format!(68 "{}.{}.config.system.build.toplevel",69 SYSTEMS_ATTRIBUTE, host,70 ))71 .env("SECRET_DATA", data.clone())72 .inherit_stdio()73 .run()?;74 let built = std::fs::canonicalize(built)?;75 info!("Built closure: {:?}", built);76 if !is_local {77 info!("Uploading system closure");78 Command::new("nix")79 .args(&["copy", "--to"])80 .arg(format!("ssh://root@{}", host))81 .arg(&built)82 .inherit_stdio()83 .run()?;84 }85 if let Some(subcommand) = &self.subcommand {86 if subcommand.should_switch_profile() {87 info!("Switching generation");88 if !is_local {89 Command::ssh_on(host, "nix-env")90 } else {91 Command::new("nix-env")92 }93 .args(&["-p", "/nix/var/nix/profiles/system", "--set"])94 .arg(&built)95 .inherit_stdio()96 .run()?;97 }98 info!("Executing activation script");99 let mut switch_script = built.clone();100 switch_script.push("bin");101 switch_script.push("switch-to-configuration");102 info!("{:?}", switch_script);103 if !is_local {104 Command::ssh_on(host, "sudo")105 } else {106 Command::new("sudo")107 }108 .arg(switch_script)109 .arg(subcommand.name())110 .inherit_stdio()111 .run()?;112 }113 }114 Ok(())115 }116}