difftreelog
feat introduce toplevel-fleet build attribute
in: trunk
3 files changed
cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth1use std::{env::current_dir, os::unix::fs::symlink, path::PathBuf};23use anyhow::Result;4use clap::Parser;5use fleet_base::{6 deploy::{DeployAction, deploy_task, upload_task},7 host::{Config, DeployKind, GenerationStorage},8 opts::FleetOpts,9};10use nix_eval::nix_go;11use tokio::task::{LocalSet, spawn_blocking};12use tracing::{Instrument, error, field, info, info_span, warn};1314#[derive(Parser)]15pub struct Deploy {16 /// Disable automatic rollback17 #[clap(long)]18 disable_rollback: bool,19 /// Action to execute after system is built20 action: DeployAction,21}2223#[derive(Parser, Clone)]24pub struct BuildSystems {25 /// Attribute to build. Systems are deployed from "toplevel-fleet" attr, well-known used attributes26 /// are "sdImage"/"isoImage", and your configuration may include any other build attributes.27 #[clap(long, default_value = "toplevel-fleet")]28 build_attr: String,29}3031async fn build_task(config: Config, hostname: String, build_attr: &str) -> Result<PathBuf> {32 info!("building");33 let host = config.host(&hostname).await?;34 // let action = Action::from(self.subcommand.clone());35 let nixos = host.nixos_config().await?;36 let drv = nix_go!(nixos.system.build[{ build_attr }]);37 let out_output = spawn_blocking(move || drv.build("out"))38 .await39 .expect("system derivation build should not panic")?;4041 // We already have system profiles for backups.42 if !host.local {43 info!("adding gc root");44 let mut cmd = config.local_host().cmd("nix").await?;45 cmd.arg("build")46 .comparg(47 "--profile",48 format!(49 "/nix/var/nix/profiles/{}-{hostname}",50 config.data().gc_root_prefix51 ),52 )53 .arg(&out_output);54 cmd.sudo().run_nix().await?;55 }5657 Ok(out_output)58}5960impl BuildSystems {61 pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {62 let hosts = opts.filter_skipped(config.list_hosts().await?).await?;63 let set = LocalSet::new();64 let build_attr = self.build_attr.clone();65 for host in hosts {66 let config = config.clone();67 let span = info_span!("build", host = field::display(&host.name));68 let hostname = host.name;69 let build_attr = build_attr.clone();70 set.spawn_local(71 (async move {72 let built = match build_task(config, hostname.clone(), &build_attr).await {73 Ok(path) => path,74 Err(e) => {75 error!("failed to deploy host: {}", e);76 return;77 }78 };79 // TODO: Handle error80 let mut out = current_dir().expect("cwd exists");81 out.push(format!("built-{hostname}"));8283 info!("linking iso image to {:?}", out);84 if let Err(e) = symlink(built, out) {85 error!("failed to symlink: {e}")86 }87 })88 .instrument(span),89 );90 }91 set.await;92 Ok(())93 }94}9596impl Deploy {97 pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {98 let hosts = opts.filter_skipped(config.list_hosts().await?).await?;99 let set = LocalSet::new();100 for host in hosts.into_iter() {101 let config = config.clone();102 let span = info_span!("deploy", host = field::display(&host.name));103 let hostname = host.name.clone();104 let opts = opts.clone();105 if let Some(deploy_kind) = opts.action_attr::<DeployKind>(&host, "deploy_kind").await? {106 host.set_deploy_kind(deploy_kind);107 };108 if let Some(destination) = opts.action_attr::<String>(&host, "dest").await? {109 host.set_session_destination(destination);110 };111 if let Some(legacy) = opts.action_attr::<bool>(&host, "legacy_ssh_store").await? {112 host.set_legacy_ssh_store(legacy);113 };114115 set.spawn_local(116 (async move {117 let built = match build_task(config.clone(), hostname.clone(), "toplevel-fleet").await118 {119 Ok(path) => path,120 Err(e) => {121 error!("failed to build host system closure: {:?}", e);122 return;123 }124 };125126 let deploy_kind = match host.deploy_kind().await {127 Ok(v) => v,128 Err(e) => {129 error!("failed to query target deploy kind: {e}");130 return;131 }132 };133134 // TODO: Make disable_rollback a host attribute instead135 let mut disable_rollback = self.disable_rollback;136 if !disable_rollback && deploy_kind != DeployKind::Fleet {137 warn!("disabling rollback, as not supported by non-fleet deployment kinds");138 disable_rollback = true;139 }140141 let remote_path =142 match upload_task(&config, &host, GenerationStorage::Deployer, built).await143 {144 Ok(v) => v,145 Err(e) => {146 error!("upload failed: {e}");147 return;148 }149 };150151 if let Err(e) = deploy_task(152 self.action,153 &host,154 remote_path,155 match opts.action_attr(&host, "specialisation").await {156 Ok(v) => v,157 _ => {158 error!("unreachable? failed to get specialization");159 return;160 }161 },162 disable_rollback,163 )164 .await165 {166 error!("activation failed: {e}");167 }168 })169 .instrument(span),170 );171 }172 set.await;173 Ok(())174 }175}modules/nixos/module-list.nixdiffbeforeafterboth--- a/modules/nixos/module-list.nix
+++ b/modules/nixos/module-list.nix
@@ -4,4 +4,5 @@
./rollback.nix
./nix-sign.nix
./online.nix
+ ./top-level.nix
]
modules/nixos/top-level.nixdiffbeforeafterboth--- /dev/null
+++ b/modules/nixos/top-level.nix
@@ -0,0 +1,71 @@
+{
+ pkgs,
+ config,
+ lib,
+}:
+let
+ inherit (lib.strings) optionalString;
+ # FIXME: Should not be copy-pasted, instead nixpkgs should export systemBuilder directly
+ systemBuilder = ''
+ mkdir $out
+
+ ${
+ if config.boot.initrd.enable && config.boot.initrd.systemd.enable then
+ ''
+ # This must not be a symlink or the abs_path of the grub builder for the tests
+ # will resolve the symlink and we end up with a path that doesn't point to a
+ # system closure.
+ cp "$systemd/lib/systemd/systemd" $out/init
+
+ ${lib.optionalString (!config.system.nixos-init.enable) ''
+ cp ${config.system.build.bootStage2} $out/prepare-root
+ substituteInPlace $out/prepare-root --subst-var-by systemConfig $out
+ ''}
+ ''
+ else
+ ''
+ cp ${config.system.build.bootStage2} $out/init
+ substituteInPlace $out/init --subst-var-by systemConfig $out
+ ''
+ }
+
+ ln -s ${config.system.build.etc}/etc $out/etc
+
+ ${lib.optionalString config.system.etc.overlay.enable ''
+ ln -s ${config.system.build.etcMetadataImage} $out/etc-metadata-image
+ ln -s ${config.system.build.etcBasedir} $out/etc-basedir
+ ''}
+
+ ln -s ${config.system.path} $out/sw
+ ln -s "$systemd" $out/systemd
+
+ echo -n "systemd ${toString config.systemd.package.interfaceVersion}" > $out/init-interface-version
+ echo -n "$nixosLabel" > $out/nixos-version
+ echo -n "${config.boot.kernelPackages.stdenv.hostPlatform.system}" > $out/system
+
+ ${config.system.systemBuilderCommands}
+
+ cp "$extraDependenciesPath" "$out/extra-dependencies"
+
+ ${config.boot.bootspec.writer}
+ ${optionalString config.boot.bootspec.enableValidation ''${config.boot.bootspec.validator} "$out/${config.boot.bootspec.filename}"''}
+ '';
+in
+{
+ system.build.toplevel-fleet = pkgs.stdenvNoCC.mkDerivation (
+ {
+ name = "nixos-system-${config.system.name}-${config.system.nixos.label}";
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ passAsFile = [ "extraDependencies" ];
+ buildCommand = systemBuilder;
+
+ systemd = config.systemd.package;
+
+ nixosLabel = config.system.nixos.label;
+
+ inherit (config.system) extraDependencies;
+ }
+ // config.system.systemBuilderArgs
+ );
+}