difftreelog
feat build specializations
in: trunk
2 files changed
cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth1use std::{env::current_dir, process::Stdio, time::Duration};23use crate::{command::CommandExt, host::Config};4use anyhow::Result;5use clap::Parser;6use tokio::{process::Command, task::LocalSet, time::sleep};7use tracing::{error, field, info, info_span, warn, Instrument};89#[derive(Parser, Clone)]10pub struct BuildSystems {11 /// Jobs to run locally12 #[clap(long)]13 jobs: Option<usize>,14 /// Do not continue on error15 #[clap(long)]16 fail_fast: bool,17 /// Run builds as sudo18 #[clap(long)]19 privileged_build: bool,20 #[clap(subcommand)]21 subcommand: Subcommand,2223 /// --builders arg for nix24 #[clap(long)]25 builders: Option<String>,26 /// --show-trace arg for nix27 #[structopt(long)]28 show_trace: bool,29}3031enum UploadAction {32 Test,33 Boot,34 Switch,35}36impl UploadAction {37 fn name(&self) -> &'static str {38 match self {39 UploadAction::Test => "test",40 UploadAction::Boot => "boot",41 UploadAction::Switch => "switch",42 }43 }4445 pub(crate) fn should_switch_profile(&self) -> bool {46 matches!(self, Self::Switch | Self::Test)47 }48}4950enum PackageAction {51 SdImage,52}5354enum Action {55 Upload(Option<UploadAction>),56 Package(PackageAction),57}5859impl From<Subcommand> for Action {60 fn from(s: Subcommand) -> Self {61 match s {62 Subcommand::Upload => Self::Upload(None),63 Subcommand::Test => Self::Upload(Some(UploadAction::Test)),64 Subcommand::Boot => Self::Upload(Some(UploadAction::Boot)),65 Subcommand::Switch => Self::Upload(Some(UploadAction::Switch)),66 Subcommand::SdImage => Self::Package(PackageAction::SdImage),67 }68 }69}7071#[derive(Parser, Clone)]72enum Subcommand {73 /// Upload, but do not switch74 Upload,75 /// Upload + switch to built system until reboot76 Test,77 /// Upload + switch to built system after reboot78 Boot,79 /// Upload + test + boot80 Switch,8182 /// Build sd image83 SdImage,84}8586impl BuildSystems {87 async fn build_task(self, config: Config, host: String) -> Result<()> {88 info!("building");89 let built = {90 let dir = tempfile::tempdir()?;91 dir.path().to_owned()92 };9394 let mut nix_build = if self.privileged_build {95 let mut out = Command::new("sudo");96 out.arg("nix");97 out98 } else {99 Command::new("nix")100 };101 nix_build102 .args(&[103 "build",104 "--impure",105 "--json",106 // "--show-trace",107 "--no-link",108 "--out-link",109 ])110 .arg(&built)111 .arg(config.configuration_attr_name(&format!(112 "configuredSystems.{}.config.system.build.toplevel",113 host114 )));115116 if self.show_trace {117 nix_build.arg("--show-trace");118 }119 if let Some(builders) = &self.builders {120 nix_build.arg("--builders").arg(builders);121 }122 if let Some(jobs) = &self.jobs {123 nix_build.arg("--max-jobs");124 nix_build.arg(format!("{}", jobs));125 }126 if !self.fail_fast {127 nix_build.arg("--keep-going");128 }129130 nix_build.run_nix().await?;131 let built = std::fs::canonicalize(built)?;132133 let action = Action::from(self.subcommand.clone());134135 match action {136 Action::Upload(action) => {137 if !config.is_local(&host) {138 info!("uploading system closure");139 let mut tries = 0;140 loop {141 match Command::new("nix")142 .args(&["copy", "--to"])143 .arg(format!("ssh://root@{}", host))144 .arg(&built)145 .inherit_stdio()146 .run_nix()147 .await148 {149 Ok(()) => break,150 Err(e) if tries < 3 => {151 tries += 1;152 warn!("Copy failure ({}/3): {}", tries, e);153 sleep(Duration::from_millis(5000)).await;154 }155 Err(e) => return Err(e),156 }157 }158 }159 if let Some(action) = action {160 if action.should_switch_profile() {161 info!("switching generation");162 config163 .command_on(&host, "nix-env", true)164 .args(&["-p", "/nix/var/nix/profiles/system", "--set"])165 .arg(&built)166 .inherit_stdio()167 .run()168 .await?;169 }170 info!("executing activation script");171 let mut switch_script = built.clone();172 switch_script.push("bin");173 switch_script.push("switch-to-configuration");174 config175 .command_on(&host, switch_script, true)176 .arg(action.name())177 .stdout(Stdio::inherit())178 .run()179 .await?;180 }181 }182 Action::Package(PackageAction::SdImage) => {183 let mut out = current_dir()?;184 out.push(format!("sd-image-{}", host));185186 info!("building sd image to {:?}", out);187 let mut nix_build = if self.privileged_build {188 let mut out = Command::new("sudo");189 out.arg("nix");190 out191 } else {192 Command::new("nix")193 };194 nix_build195 .args(&["build", "--impure", "--no-link", "--out-link"])196 .arg(&out)197 .arg(config.configuration_attr_name(&format!(198 "configuredSystems.{}.config.system.build.sdImage",199 host,200 )));201 if let Some(builders) = &self.builders {202 nix_build.arg("--builders").arg(builders);203 }204 if let Some(jobs) = &self.jobs {205 nix_build.arg("--max-jobs");206 nix_build.arg(format!("{}", jobs));207 }208 if !self.fail_fast {209 nix_build.arg("--keep-going");210 }211212 nix_build.inherit_stdio().run_nix().await?;213 }214 };215 Ok(())216 }217218 pub async fn run(self, config: &Config) -> Result<()> {219 let hosts = config.list_hosts().await?;220 let set = LocalSet::new();221 let this = &self;222 for host in hosts.iter() {223 if config.should_skip(host) {224 continue;225 }226 let config = config.clone();227 let host = host.clone();228 let this = this.clone();229 let span = info_span!("deployment", host = field::display(&host));230 set.spawn_local(231 (async move {232 match this.build_task(config, host).await {233 Ok(_) => {}234 Err(e) => {235 error!("failed to deploy host: {}", e)236 }237 }238 })239 .instrument(span),240 );241 }242 set.await;243 Ok(())244 }245}1use std::{env::current_dir, process::Stdio, time::Duration};23use crate::{command::CommandExt, host::Config};4use anyhow::Result;5use clap::Parser;6use tokio::{process::Command, task::LocalSet, time::sleep};7use tracing::{error, field, info, info_span, warn, Instrument};89#[derive(Parser, Clone)]10pub struct BuildSystems {11 /// Jobs to run locally12 #[clap(long)]13 jobs: Option<usize>,14 /// Do not continue on error15 #[clap(long)]16 fail_fast: bool,17 /// Run builds as sudo18 #[clap(long)]19 privileged_build: bool,20 #[clap(subcommand)]21 subcommand: Subcommand,2223 /// --builders arg for nix24 #[clap(long)]25 builders: Option<String>,26 /// --show-trace arg for nix27 #[structopt(long)]28 show_trace: bool,29}3031enum UploadAction {32 Test,33 Boot,34 Switch,35}36impl UploadAction {37 fn name(&self) -> &'static str {38 match self {39 UploadAction::Test => "test",40 UploadAction::Boot => "boot",41 UploadAction::Switch => "switch",42 }43 }4445 pub(crate) fn should_switch_profile(&self) -> bool {46 matches!(self, Self::Switch | Self::Test)47 }48}4950enum PackageAction {51 SdImage,52 InstallationCd,53}54impl PackageAction {55 fn build_attr(&self) -> String {56 match self {57 PackageAction::SdImage => "sdImage".to_owned(),58 PackageAction::InstallationCd => "installationCd".to_owned(),59 }60 }61}6263enum Action {64 Upload { action: Option<UploadAction> },65 Package(PackageAction),66}67impl Action {68 fn build_attr(&self) -> String {69 match self {70 Action::Upload { .. } => "toplevel".to_owned(),71 Action::Package(p) => p.build_attr(),72 }73 }74}7576impl From<Subcommand> for Action {77 fn from(s: Subcommand) -> Self {78 match s {79 Subcommand::Upload => Self::Upload { action: None },80 Subcommand::Test => Self::Upload {81 action: Some(UploadAction::Test),82 },83 Subcommand::Boot => Self::Upload {84 action: Some(UploadAction::Boot),85 },86 Subcommand::Switch => Self::Upload {87 action: Some(UploadAction::Switch),88 },89 Subcommand::SdImage => Self::Package(PackageAction::SdImage),90 Subcommand::InstallationCd => Self::Package(PackageAction::InstallationCd),91 }92 }93}9495#[derive(Parser, Clone)]96enum Subcommand {97 /// Upload, but do not switch98 Upload,99 /// Upload + switch to built system until reboot100 Test,101 /// Upload + switch to built system after reboot102 Boot,103 /// Upload + test + boot104 Switch,105106 /// Build SD .img image107 SdImage,108 /// Build an installation cd ISO image109 InstallationCd,110}111112impl BuildSystems {113 async fn build_task(self, config: Config, host: String) -> Result<()> {114 info!("building");115 let action = Action::from(self.subcommand.clone());116 let built = {117 let dir = tempfile::tempdir()?;118 dir.path().to_owned()119 };120121 let mut nix_build = if self.privileged_build {122 let mut out = Command::new("sudo");123 out.arg("nix");124 out125 } else {126 Command::new("nix")127 };128 nix_build129 .args(&[130 "build",131 "--impure",132 "--json",133 // "--show-trace",134 "--no-link",135 "--out-link",136 ])137 .arg(&built)138 .arg(139 config.configuration_attr_name(&format!(140 "buildSystems.{}.{host}",141 action.build_attr()142 )),143 );144145 if self.show_trace {146 nix_build.arg("--show-trace");147 }148 if let Some(builders) = &self.builders {149 nix_build.arg("--builders").arg(builders);150 }151 if let Some(jobs) = &self.jobs {152 nix_build.arg("--max-jobs");153 nix_build.arg(format!("{}", jobs));154 }155 if !self.fail_fast {156 nix_build.arg("--keep-going");157 }158159 nix_build.run_nix().await?;160 let built = std::fs::canonicalize(built)?;161162 match action {163 Action::Upload { action } => {164 if !config.is_local(&host) {165 info!("uploading system closure");166 let mut tries = 0;167 loop {168 match Command::new("nix")169 .args(&["copy", "--to"])170 .arg(format!("ssh://root@{}", host))171 .arg(&built)172 .inherit_stdio()173 .run_nix()174 .await175 {176 Ok(()) => break,177 Err(e) if tries < 3 => {178 tries += 1;179 warn!("Copy failure ({}/3): {}", tries, e);180 sleep(Duration::from_millis(5000)).await;181 }182 Err(e) => return Err(e),183 }184 }185 }186 if let Some(action) = action {187 if action.should_switch_profile() {188 info!("switching generation");189 config190 .command_on(&host, "nix-env", true)191 .args(&["-p", "/nix/var/nix/profiles/system", "--set"])192 .arg(&built)193 .inherit_stdio()194 .run()195 .await?;196 }197 info!("executing activation script");198 let mut switch_script = built.clone();199 switch_script.push("bin");200 switch_script.push("switch-to-configuration");201 config202 .command_on(&host, switch_script, true)203 .arg(action.name())204 .stdout(Stdio::inherit())205 .run()206 .await?;207 }208 }209 Action::Package(PackageAction::SdImage) => {210 let mut out = current_dir()?;211 out.push(format!("sd-image-{}", host));212213 info!("building sd image to {:?}", out);214 let mut nix_build = if self.privileged_build {215 let mut out = Command::new("sudo");216 out.arg("nix");217 out218 } else {219 Command::new("nix")220 };221 nix_build222 .args(&["build", "--impure", "--no-link", "--out-link"])223 .arg(&out)224 .arg(225 config.configuration_attr_name(&format!("buildSystems.sdImage.{}", host,)),226 );227 if let Some(builders) = &self.builders {228 nix_build.arg("--builders").arg(builders);229 }230 if let Some(jobs) = &self.jobs {231 nix_build.arg("--max-jobs");232 nix_build.arg(format!("{}", jobs));233 }234 if !self.fail_fast {235 nix_build.arg("--keep-going");236 }237238 nix_build.inherit_stdio().run_nix().await?;239 }240 Action::Package(PackageAction::InstallationCd) => {241 let mut out = current_dir()?;242 out.push(format!("installation-cd-{}", host));243244 info!("building sd image to {:?}", out);245 let mut nix_build = if self.privileged_build {246 let mut out = Command::new("sudo");247 out.arg("nix");248 out249 } else {250 Command::new("nix")251 };252 nix_build253 .args(&["build", "--impure", "--no-link", "--out-link"])254 .arg(&out)255 .arg(256 config.configuration_attr_name(&format!(257 "buildSystems.installationCd.{}",258 host,259 )),260 );261 if let Some(builders) = &self.builders {262 nix_build.arg("--builders").arg(builders);263 }264 if let Some(jobs) = &self.jobs {265 nix_build.arg("--max-jobs");266 nix_build.arg(format!("{}", jobs));267 }268 if !self.fail_fast {269 nix_build.arg("--keep-going");270 }271272 nix_build.inherit_stdio().run_nix().await?;273 }274 };275 Ok(())276 }277278 pub async fn run(self, config: &Config) -> Result<()> {279 let hosts = config.list_hosts().await?;280 let set = LocalSet::new();281 let this = &self;282 for host in hosts.iter() {283 if config.should_skip(host) {284 continue;285 }286 let config = config.clone();287 let host = host.clone();288 let this = this.clone();289 let span = info_span!("deployment", host = field::display(&host));290 set.spawn_local(291 (async move {292 match this.build_task(config, host).await {293 Ok(_) => {}294 Err(e) => {295 error!("failed to deploy host: {}", e)296 }297 }298 })299 .instrument(span),300 );301 }302 set.await;303 Ok(())304 }305}lib/default.nixdiffbeforeafterboth--- a/lib/default.nix
+++ b/lib/default.nix
@@ -20,21 +20,17 @@
if failedAssertions != [ ]
then throw "Failed assertions:\n${nixpkgs.lib.concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}"
else nixpkgs.lib.showWarnings root.config.warnings root;
- in
- rec {
configuredHosts = rootAssertWarn.config.hosts;
configuredSecrets = rootAssertWarn.config.secrets;
- configuredSystems = nixpkgs.lib.listToAttrs (
+ configuredSystems = configuredSystemsWithExtraModules [ ];
+ configuredSystemsWithExtraModules = extraModules: nixpkgs.lib.listToAttrs (
map
(
name: {
inherit name;
value = nixpkgs.lib.nixosSystem {
system = configuredHosts.${name}.system;
- modules = configuredHosts.${name}.modules ++ (
- if configuredHosts.${name}.system == "aarch64-linux" then [ (nixpkgs + "/nixos/modules/installer/sd-card/sd-image-aarch64-installer.nix") ]
- else [ ]
- ) ++ [
+ modules = configuredHosts.${name}.modules ++ extraModules ++ [
({ ... }: {
nixpkgs.system = system;
nixpkgs.localSystem.system = system;
@@ -51,6 +47,22 @@
}
)
(builtins.attrNames rootAssertWarn.config.hosts)
- ); #nixpkgs.lib.nixosSystem {}
+ );
+ in
+ rec {
+ inherit configuredHosts configuredSecrets configuredSystems;
+ buildSystems = {
+ toplevel = builtins.mapAttrs (_name: value: value.config.system.build.toplevel) (configuredSystemsWithExtraModules [ ]);
+ sdImage = builtins.mapAttrs (_name: value: value.config.system.build.sdImage) (configuredSystemsWithExtraModules [
+ (nixpkgs + "/nixos/modules/installer/sd-card/sd-image-aarch64-installer.nix")
+ ]);
+ installationCd = builtins.mapAttrs (_name: value: value.config.system.build.isoImage) (configuredSystemsWithExtraModules [
+ (nixpkgs + "/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix")
+ ({ lib, ... }: {
+ # Needed for https://github.com/NixOS/nixpkgs/issues/58959
+ boot.supportedFilesystems = lib.mkForce [ "btrfs" "reiserfs" "vfat" "f2fs" "xfs" "ntfs" "cifs" ];
+ })
+ ]);
+ };
});
}