git.delta.rocks / jrsonnet / refs/commits / eee08d567ee4

difftreelog

source

cmds/fleet/src/cmds/build_systems.rs6.4 KiBsourcehistory
1use std::{env::current_dir, time::Duration};23use crate::command::MyCommand;4use crate::host::Config;5use anyhow::Result;6use clap::Parser;7use tokio::{task::LocalSet, time::sleep};8use tracing::{error, field, info, info_span, warn, Instrument};910#[derive(Parser, Clone)]11pub struct BuildSystems {12	/// Do not continue on error13	#[clap(long)]14	fail_fast: bool,15	/// Run builds as sudo16	#[clap(long)]17	privileged_build: bool,18	#[clap(subcommand)]19	subcommand: Subcommand,20}2122enum UploadAction {23	Test,24	Boot,25	Switch,26}27impl UploadAction {28	fn name(&self) -> &'static str {29		match self {30			UploadAction::Test => "test",31			UploadAction::Boot => "boot",32			UploadAction::Switch => "switch",33		}34	}3536	pub(crate) fn should_switch_profile(&self) -> bool {37		matches!(self, Self::Switch | Self::Boot)38	}39	pub(crate) fn should_activate(&self) -> bool {40		matches!(self, Self::Switch | Self::Test)41	}42}4344enum PackageAction {45	SdImage,46	InstallationCd,47}48impl PackageAction {49	fn build_attr(&self) -> String {50		match self {51			PackageAction::SdImage => "sdImage".to_owned(),52			PackageAction::InstallationCd => "installationCd".to_owned(),53		}54	}55}5657enum Action {58	Upload { action: Option<UploadAction> },59	Package(PackageAction),60}61impl Action {62	fn build_attr(&self) -> String {63		match self {64			Action::Upload { .. } => "toplevel".to_owned(),65			Action::Package(p) => p.build_attr(),66		}67	}68}6970impl From<Subcommand> for Action {71	fn from(s: Subcommand) -> Self {72		match s {73			Subcommand::Upload => Self::Upload { action: None },74			Subcommand::Test => Self::Upload {75				action: Some(UploadAction::Test),76			},77			Subcommand::Boot => Self::Upload {78				action: Some(UploadAction::Boot),79			},80			Subcommand::Switch => Self::Upload {81				action: Some(UploadAction::Switch),82			},83			Subcommand::SdImage => Self::Package(PackageAction::SdImage),84			Subcommand::InstallationCd => Self::Package(PackageAction::InstallationCd),85		}86	}87}8889#[derive(Parser, Clone)]90enum Subcommand {91	/// Upload, but do not switch92	Upload,93	/// Upload + switch to built system until reboot94	Test,95	/// Upload + switch to built system after reboot96	Boot,97	/// Upload + test + boot98	Switch,99100	/// Build SD .img image101	SdImage,102	/// Build an installation cd ISO image103	InstallationCd,104}105106impl BuildSystems {107	async fn build_task(self, config: Config, host: String) -> Result<()> {108		info!("building");109		let action = Action::from(self.subcommand.clone());110		let built = {111			let dir = tempfile::tempdir()?;112			dir.path().to_owned()113		};114115		let mut nix_build = MyCommand::new("nix");116		nix_build117			.args([118				"build",119				"--impure",120				"--json",121				// "--show-trace",122				"--no-link",123			])124			.comparg("--out-link", &built)125			.arg(126				config.configuration_attr_name(&format!(127					"buildSystems.{}.{host}",128					action.build_attr()129				)),130			)131			.args(&config.nix_args);132133		if self.privileged_build {134			nix_build = nix_build.sudo();135		}136137		nix_build.run_nix().await.map_err(|e| {138			if action.build_attr() == "sdImage" {139				info!("sd-image build failed");140				info!("Make sure you have imported modulesPath/installer/sd-card/sd-image-<arch>[-installer].nix (For installer, you may want to check config)");141				info!("This module was automatically imported before, but was removed for better customization")142			}143			e144		})?;145		let built = std::fs::canonicalize(built)?;146147		match action {148			Action::Upload { action } => {149				if !config.is_local(&host) {150					info!("uploading system closure");151					let mut tries = 0;152					loop {153						let mut nix = MyCommand::new("nix");154						nix.arg("copy")155							.comparg("--to", format!("ssh://root@{host}"))156							.arg(&built);157						match nix.run_nix().await {158							Ok(()) => break,159							Err(e) if tries < 3 => {160								tries += 1;161								warn!("Copy failure ({}/3): {}", tries, e);162								sleep(Duration::from_millis(5000)).await;163							}164							Err(e) => return Err(e),165						}166					}167				}168				if let Some(action) = action {169					if action.should_switch_profile() {170						info!("switching generation");171						let mut cmd = MyCommand::new("nix-env");172						cmd.comparg("--profile", "/nix/var/nix/profiles/system")173							.comparg("--set", &built);174						config.run_on(&host, cmd, true).await?;175					}176					if action.should_activate() {177						info!("executing activation script");178						let mut switch_script = built.clone();179						switch_script.push("bin");180						switch_script.push("switch-to-configuration");181						let mut cmd = MyCommand::new(switch_script);182						cmd.arg(action.name());183						config.run_on(&host, cmd, true).await?;184					}185				}186			}187			Action::Package(PackageAction::SdImage) => {188				let mut out = current_dir()?;189				out.push(format!("sd-image-{}", host));190191				info!("building sd image to {:?}", out);192				let mut nix_build = MyCommand::new("nix");193				nix_build194					.args(["build", "--impure", "--no-link"])195					.comparg("--out-link", &out)196					.arg(config.configuration_attr_name(&format!("buildSystems.sdImage.{}", host,)))197					.args(&config.nix_args);198				if !self.fail_fast {199					nix_build.arg("--keep-going");200				}201				if self.privileged_build {202					nix_build = nix_build.sudo();203				}204205				nix_build.run_nix().await?;206			}207			Action::Package(PackageAction::InstallationCd) => {208				let mut out = current_dir()?;209				out.push(format!("installation-cd-{}", host));210211				info!("building sd image to {:?}", out);212				let mut nix_build = MyCommand::new("nix");213				nix_build214					.args(["build", "--impure", "--no-link"])215					.comparg("--out-link", &out)216					.arg(217						config.configuration_attr_name(&format!(218							"buildSystems.installationCd.{}",219							host,220						)),221					)222					.args(&config.nix_args);223				if !self.fail_fast {224					nix_build.arg("--keep-going");225				}226				if self.privileged_build {227					nix_build = nix_build.sudo();228				}229230				nix_build.run_nix().await?;231			}232		};233		Ok(())234	}235236	pub async fn run(self, config: &Config) -> Result<()> {237		let hosts = config.list_hosts().await?;238		let set = LocalSet::new();239		let this = &self;240		for host in hosts.iter() {241			if config.should_skip(host) {242				continue;243			}244			let config = config.clone();245			let host = host.clone();246			let this = this.clone();247			let span = info_span!("deployment", host = field::display(&host));248			set.spawn_local(249				(async move {250					match this.build_task(config, host).await {251						Ok(_) => {}252						Err(e) => {253							error!("failed to deploy host: {}", e)254						}255					}256				})257				.instrument(span),258			);259		}260		set.await;261		Ok(())262	}263}