difftreelog
refactor provide cross system using argument
in: trunk
5 files changed
cmds/fleet/src/better_nix_eval.rsdiffbeforeafterboth--- a/cmds/fleet/src/better_nix_eval.rs
+++ b/cmds/fleet/src/better_nix_eval.rs
@@ -16,7 +16,7 @@
use tokio::select;
use tokio::sync::{mpsc, oneshot};
use tokio_util::codec::{FramedRead, LinesCodec};
-use tracing::{debug, error, warn};
+use tracing::{debug, error, warn, Level};
use crate::command::{ClonableHandler, Handler, NixHandler, NoopHandler};
@@ -247,6 +247,10 @@
Ok(())
}
async fn send_command(&mut self, cmd: impl AsRef<[u8]>) -> Result<()> {
+ if tracing::enabled!(Level::DEBUG) {
+ let cmd_str = String::from_utf8_lossy(cmd.as_ref());
+ tracing::debug!("{cmd_str}");
+ };
self.stdin.write_all(cmd.as_ref()).await?;
self.stdin.write_all(b"\n").await?;
Ok(())
@@ -420,7 +424,7 @@
}
pub fn apply(v: impl Serialize) -> Self {
let serialized = nixlike::serialize(v).expect("invalid value for apply");
- Self::Apply(serialized)
+ Self::Apply(serialized.trim_end().to_owned())
}
}
impl Display for Index {
@@ -434,8 +438,7 @@
write!(f, ".{v}")
}
Index::Apply(o) => {
- let v = nixlike::serialize(o).map_err(|_| fmt::Error)?;
- write!(f, "<apply>({v})")
+ write!(f, "<apply>({o})")
}
Index::Idx(i) => {
write!(f, "[{i}]")
@@ -451,7 +454,6 @@
struct PathDisplay<'i>(&'i [Index]);
impl Display for PathDisplay<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "flake")?;
for i in self.0 {
write!(f, "{i}")?;
}
@@ -508,8 +510,8 @@
query.push_str(escaped.trim());
}
Index::Apply(a) => {
- query.push(' ');
- query.push_str(&a);
+ // In cases like `a {}.b` first `{}.b` will be evaluated, so `a {}` should be encased in `()`
+ query = format!("({query} {a})");
}
Index::Idx(idx) => {
query = format!("builtins.elemAt ({query}) {idx}");
@@ -560,7 +562,7 @@
.await
.execute_expression_raw(&format!(":b sess_field_{id}"), &mut NixHandler::default())
.await?;
- ensure!(!vid.is_empty(), "build failed");
+ ensure!(!vid.is_empty(), "build failed: {}", PathDisplay(&self.full_path));
let Some(vid) = vid.strip_prefix("This derivation produced the following outputs:\n")
else {
panic!("unexpected build output: {vid:?}");
cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth1use std::os::unix::fs::symlink;2use std::path::PathBuf;3use std::{env::current_dir, time::Duration};45use crate::command::MyCommand;6use crate::host::Config;7use crate::nix_path;8use anyhow::{anyhow, Result};9use clap::Parser;10use itertools::Itertools;11use tokio::{task::LocalSet, time::sleep};12use tracing::{error, field, info, info_span, warn, Instrument};1314#[derive(Parser, Clone)]15pub struct BuildSystems {16 /// Disable automatic rollback17 #[clap(long)]18 disable_rollback: bool,19 #[clap(subcommand)]20 subcommand: Subcommand,21}2223enum UploadAction {24 Test,25 Boot,26 Switch,27}28impl UploadAction {29 fn name(&self) -> &'static str {30 match self {31 UploadAction::Test => "test",32 UploadAction::Boot => "boot",33 UploadAction::Switch => "switch",34 }35 }3637 pub(crate) fn should_switch_profile(&self) -> bool {38 matches!(self, Self::Switch | Self::Boot)39 }40 pub(crate) fn should_activate(&self) -> bool {41 matches!(self, Self::Switch | Self::Test)42 }43 pub(crate) fn should_schedule_rollback_run(&self) -> bool {44 matches!(self, Self::Switch | Self::Test)45 }46}4748enum PackageAction {49 SdImage,50 InstallationCd,51}52impl PackageAction {53 fn build_attr(&self) -> String {54 match self {55 PackageAction::SdImage => "sdImage".to_owned(),56 PackageAction::InstallationCd => "installationCd".to_owned(),57 }58 }59}6061enum Action {62 Upload { action: Option<UploadAction> },63 Package(PackageAction),64}65impl Action {66 fn build_attr(&self) -> String {67 match self {68 Action::Upload { .. } => "toplevel".to_owned(),69 Action::Package(p) => p.build_attr(),70 }71 }72}7374impl From<Subcommand> for Action {75 fn from(s: Subcommand) -> Self {76 match s {77 Subcommand::Upload => Self::Upload { action: None },78 Subcommand::Test => Self::Upload {79 action: Some(UploadAction::Test),80 },81 Subcommand::Boot => Self::Upload {82 action: Some(UploadAction::Boot),83 },84 Subcommand::Switch => Self::Upload {85 action: Some(UploadAction::Switch),86 },87 Subcommand::SdImage => Self::Package(PackageAction::SdImage),88 Subcommand::InstallationCd => Self::Package(PackageAction::InstallationCd),89 }90 }91}9293#[derive(Parser, Clone)]94enum Subcommand {95 /// Upload, but do not switch96 Upload,97 /// Upload + switch to built system until reboot98 Test,99 /// Upload + switch to built system after reboot100 Boot,101 /// Upload + test + boot102 Switch,103104 /// Build SD .img image105 SdImage,106 /// Build an installation cd ISO image107 InstallationCd,108}109110struct Generation {111 id: u32,112 current: bool,113 datetime: String,114}115async fn get_current_generation(config: &Config, host: &str) -> Result<Generation> {116 let mut cmd = MyCommand::new("nix-env");117 cmd.comparg("--profile", "/nix/var/nix/profiles/system")118 .arg("--list-generations");119 // Sudo is required due to --list-generations acquiring lock on the profile.120 let data = config.run_string_on(host, cmd, true).await?;121 let generations = data122 .split('\n')123 .map(|e| e.trim())124 .filter(|&l| !l.is_empty())125 .filter_map(|g| {126 let gen: Option<Generation> = try {127 let mut parts = g.split_whitespace();128 let id = parts.next()?;129 let id: u32 = id.parse().ok()?;130 let date = parts.next()?;131 let time = parts.next()?;132 let current = if let Some(current) = parts.next() {133 if current == "(current)" {134 Some(true)135 } else {136 None137 }138 } else {139 Some(false)140 };141 let current = current?;142 if parts.next().is_some() {143 warn!("unexpected text after generation: {g}");144 }145 Generation {146 id,147 current,148 datetime: format!("{date} {time}"),149 }150 };151 if gen.is_none() {152 warn!("bad generation: {g}")153 }154 gen155 })156 .collect::<Vec<_>>();157 let current = generations158 .into_iter()159 .filter(|g| g.current)160 .at_most_one()161 .map_err(|_e| anyhow!("bad list-generations output"))?162 .ok_or_else(|| anyhow!("failed to find generation"))?;163 Ok(current)164}165166async fn systemctl_stop(config: &Config, host: &str, unit: &str) -> Result<()> {167 let mut cmd = MyCommand::new("systemctl");168 cmd.arg("stop").arg(unit);169 config.run_on(host, cmd, true).await170}171172async fn systemctl_start(config: &Config, host: &str, unit: &str) -> Result<()> {173 let mut cmd = MyCommand::new("systemctl");174 cmd.arg("start").arg(unit);175 config.run_on(host, cmd, true).await176}177178async fn execute_upload(179 build: &BuildSystems,180 config: &Config,181 action: UploadAction,182 host: &str,183 built: PathBuf,184) -> Result<()> {185 let mut failed = false;186 // TODO: Lockfile, to prevent concurrent system switch?187 // TODO: If rollback target exists - bail, it should be removed. Lockfile will not work in case if rollback188 // is scheduler on next boot (default behavior). On current boot - rollback activator will fail due to189 // unit name conflict in systemd-run190 // This code is tied to rollback.nix191 if !build.disable_rollback {192 let _span = info_span!("preparing").entered();193 info!("preparing for rollback");194 let generation = get_current_generation(config, host).await?;195 info!(196 "rollback target would be {} {}",197 generation.id, generation.datetime198 );199 {200 let mut cmd = MyCommand::new("sh");201 cmd.arg("-c").arg(format!("mark=$(mktemp -p /etc -t fleet_rollback_marker.XXXXX) && echo -n {} > $mark && mv --no-clobber $mark /etc/fleet_rollback_marker", generation.id));202 if let Err(e) = config.run_on(host, cmd, true).await {203 error!("failed to set rollback marker: {e}");204 failed = true;205 }206 }207 // Activation script also starts rollback-watchdog.timer, however, it is possible that it won't be started.208 // Kicking it on manually will work best.209 //210 // There wouldn't be conflict, because here we trigger start of the primary service, and systemd will211 // only allow one instance of it.212213 // TODO: We should also watch how this process is going.214 // After running this command, we have less than 3 minutes to deploy everything,215 // if we fail to perform generation switch in time, then we will still call the activation script, and this may break something.216 // Anyway, reboot will still help in this case.217 if action.should_schedule_rollback_run() {218 let mut cmd = MyCommand::new("systemd-run");219 cmd.comparg("--on-active", "3min")220 .comparg("--unit", "rollback-watchdog-run")221 .arg("systemctl")222 .arg("start")223 .arg("rollback-watchdog.service");224 if let Err(e) = config.run_on(host, cmd, true).await {225 error!("failed to schedule rollback run: {e}");226 failed = true;227 }228 }229 }230 if action.should_switch_profile() && !failed {231 info!("switching generation");232 let mut cmd = MyCommand::new("nix-env");233 cmd.comparg("--profile", "/nix/var/nix/profiles/system")234 .comparg("--set", &built);235 if let Err(e) = config.run_on(host, cmd, true).await {236 error!("failed to switch generation: {e}");237 failed = true;238 }239 }240 if action.should_activate() && !failed {241 let _span = info_span!("activating").entered();242 info!("executing activation script");243 let mut switch_script = built.clone();244 switch_script.push("bin");245 switch_script.push("switch-to-configuration");246 let mut cmd = MyCommand::new(switch_script);247 cmd.arg(action.name());248 if let Err(e) = config.run_on(host, cmd, true).in_current_span().await {249 error!("failed to activate: {e}");250 failed = true;251 }252 }253 if !build.disable_rollback {254 if failed {255 info!("executing rollback");256 if let Err(e) = systemctl_start(config, host, "rollback-watchdog.service")257 .instrument(info_span!("rollback"))258 .await259 {260 error!("failed to trigger rollback: {e}")261 }262 } else {263 info!("trying to mark upgrade as successful");264 let mut cmd = MyCommand::new("rm");265 cmd.arg("-f").arg("/etc/fleet_rollback_marker");266 if let Err(e) = config.run_on(host, cmd, true).in_current_span().await {267 error!("failed to remove rollback marker. This is bad, as the system will be rolled back by watchdog: {e}")268 }269 }270 info!("disarming watchdog, just in case");271 if let Err(_e) = systemctl_stop(config, host, "rollback-watchdog.timer").await {272 // It is ok, if there was no reboot - then timer might not be running.273 }274 if action.should_schedule_rollback_run() {275 if let Err(e) = systemctl_stop(config, host, "rollback-watchdog-run.timer").await {276 error!("failed to disarm rollback run: {e}");277 }278 }279 } else {280 let mut cmd = MyCommand::new("rm");281 cmd.arg("-f").arg("/etc/fleet_rollback_marker");282 if let Err(_e) = config.run_on(host, cmd, true).in_current_span().await {283 // Marker might not exist, yet better try to remove it.284 }285 }286 Ok(())287}288289impl BuildSystems {290 async fn build_task(self, config: Config, host: String) -> Result<()> {291 info!("building");292 let action = Action::from(self.subcommand.clone());293 let drv = config294 .fleet_field295 .select(nix_path!(.buildSystems.{action.build_attr()}.{&host}))296 .await?;297 let outputs = drv.build().await.map_err(|e| {298 if action.build_attr() == "sdImage" {299 info!("sd-image build failed");300 info!("Make sure you have imported modulesPath/installer/sd-card/sd-image-<arch>[-installer].nix (For installer, you may want to check config)");301 info!("This module was automatically imported before, but was removed for better customization")302 }303 e304 })?;305 let out_output = outputs306 .get("out")307 .ok_or_else(|| anyhow!("system build should produce \"out\" output"))?;308309 match action {310 Action::Upload { action } => {311 if !config.is_local(&host) {312 info!("uploading system closure");313 {314 let mut sign = MyCommand::new("nix");315 // Private key for host machine is registered in nix-sign.nix316 sign.arg("store")317 .arg("sign")318 .comparg("--key-file", "/etc/nix/private-key")319 .arg("-r")320 .arg(out_output);321 if let Err(e) = sign.sudo().run_nix().await {322 warn!("Failed to sign store paths: {e}");323 };324 }325 let mut tries = 0;326 loop {327 let mut nix = MyCommand::new("nix");328 nix.arg("copy")329 .arg("--substitute-on-destination")330 .comparg("--to", format!("ssh-ng://{host}"))331 .arg(out_output);332 match nix.run_nix().await {333 Ok(()) => break,334 Err(e) if tries < 3 => {335 tries += 1;336 warn!("Copy failure ({}/3): {}", tries, e);337 sleep(Duration::from_millis(5000)).await;338 }339 Err(e) => return Err(e),340 }341 }342 }343 if let Some(action) = action {344 execute_upload(&self, &config, action, &host, out_output.clone()).await?345 }346 }347 Action::Package(PackageAction::SdImage) => {348 let mut out = current_dir()?;349 out.push(format!("sd-image-{}", host));350351 info!("linking sd image to {:?}", out);352 symlink(out_output, out)?;353 }354 Action::Package(PackageAction::InstallationCd) => {355 let mut out = current_dir()?;356 out.push(format!("installation-cd-{}", host));357358 info!("linking iso image to {:?}", out);359 symlink(out_output, out)?;360 }361 };362 Ok(())363 }364365 pub async fn run(self, config: &Config) -> Result<()> {366 let hosts = config.list_hosts().await?;367 let set = LocalSet::new();368 let this = &self;369 for host in hosts.into_iter() {370 if config.should_skip(&host.name) {371 continue;372 }373 let config = config.clone();374 let this = this.clone();375 let span = info_span!("deployment", host = field::display(&host.name));376 let hostname = host.name;377 set.spawn_local(378 (async move {379 match this.build_task(config, hostname).await {380 Ok(_) => {}381 Err(e) => {382 error!("failed to deploy host: {}", e)383 }384 }385 })386 .instrument(span),387 );388 }389 set.await;390 Ok(())391 }392}1use std::os::unix::fs::symlink;2use std::path::PathBuf;3use std::{env::current_dir, time::Duration};45use crate::command::MyCommand;6use crate::host::Config;7use crate::nix_path;8use anyhow::{anyhow, Result, Context};9use clap::Parser;10use itertools::Itertools;11use tokio::{task::LocalSet, time::sleep};12use tracing::{error, field, info, info_span, warn, Instrument};1314#[derive(Parser, Clone)]15pub struct BuildSystems {16 /// Disable automatic rollback17 #[clap(long)]18 disable_rollback: bool,19 #[clap(subcommand)]20 subcommand: Subcommand,21}2223enum UploadAction {24 Test,25 Boot,26 Switch,27}28impl UploadAction {29 fn name(&self) -> &'static str {30 match self {31 UploadAction::Test => "test",32 UploadAction::Boot => "boot",33 UploadAction::Switch => "switch",34 }35 }3637 pub(crate) fn should_switch_profile(&self) -> bool {38 matches!(self, Self::Switch | Self::Boot)39 }40 pub(crate) fn should_activate(&self) -> bool {41 matches!(self, Self::Switch | Self::Test)42 }43 pub(crate) fn should_schedule_rollback_run(&self) -> bool {44 matches!(self, Self::Switch | Self::Test)45 }46}4748enum PackageAction {49 SdImage,50 InstallationCd,51}52impl PackageAction {53 fn build_attr(&self) -> String {54 match self {55 PackageAction::SdImage => "sdImage".to_owned(),56 PackageAction::InstallationCd => "installationCd".to_owned(),57 }58 }59}6061enum Action {62 Upload { action: Option<UploadAction> },63 Package(PackageAction),64}65impl Action {66 fn build_attr(&self) -> String {67 match self {68 Action::Upload { .. } => "toplevel".to_owned(),69 Action::Package(p) => p.build_attr(),70 }71 }72}7374impl From<Subcommand> for Action {75 fn from(s: Subcommand) -> Self {76 match s {77 Subcommand::Upload => Self::Upload { action: None },78 Subcommand::Test => Self::Upload {79 action: Some(UploadAction::Test),80 },81 Subcommand::Boot => Self::Upload {82 action: Some(UploadAction::Boot),83 },84 Subcommand::Switch => Self::Upload {85 action: Some(UploadAction::Switch),86 },87 Subcommand::SdImage => Self::Package(PackageAction::SdImage),88 Subcommand::InstallationCd => Self::Package(PackageAction::InstallationCd),89 }90 }91}9293#[derive(Parser, Clone)]94enum Subcommand {95 /// Upload, but do not switch96 Upload,97 /// Upload + switch to built system until reboot98 Test,99 /// Upload + switch to built system after reboot100 Boot,101 /// Upload + test + boot102 Switch,103104 /// Build SD .img image105 SdImage,106 /// Build an installation cd ISO image107 InstallationCd,108}109110struct Generation {111 id: u32,112 current: bool,113 datetime: String,114}115async fn get_current_generation(config: &Config, host: &str) -> Result<Generation> {116 let mut cmd = MyCommand::new("nix-env");117 cmd.comparg("--profile", "/nix/var/nix/profiles/system")118 .arg("--list-generations");119 // Sudo is required due to --list-generations acquiring lock on the profile.120 let data = config.run_string_on(host, cmd, true).await?;121 let generations = data122 .split('\n')123 .map(|e| e.trim())124 .filter(|&l| !l.is_empty())125 .filter_map(|g| {126 let gen: Option<Generation> = try {127 let mut parts = g.split_whitespace();128 let id = parts.next()?;129 let id: u32 = id.parse().ok()?;130 let date = parts.next()?;131 let time = parts.next()?;132 let current = if let Some(current) = parts.next() {133 if current == "(current)" {134 Some(true)135 } else {136 None137 }138 } else {139 Some(false)140 };141 let current = current?;142 if parts.next().is_some() {143 warn!("unexpected text after generation: {g}");144 }145 Generation {146 id,147 current,148 datetime: format!("{date} {time}"),149 }150 };151 if gen.is_none() {152 warn!("bad generation: {g}")153 }154 gen155 })156 .collect::<Vec<_>>();157 let current = generations158 .into_iter()159 .filter(|g| g.current)160 .at_most_one()161 .map_err(|_e| anyhow!("bad list-generations output"))?162 .ok_or_else(|| anyhow!("failed to find generation"))?;163 Ok(current)164}165166async fn systemctl_stop(config: &Config, host: &str, unit: &str) -> Result<()> {167 let mut cmd = MyCommand::new("systemctl");168 cmd.arg("stop").arg(unit);169 config.run_on(host, cmd, true).await170}171172async fn systemctl_start(config: &Config, host: &str, unit: &str) -> Result<()> {173 let mut cmd = MyCommand::new("systemctl");174 cmd.arg("start").arg(unit);175 config.run_on(host, cmd, true).await176}177178async fn execute_upload(179 build: &BuildSystems,180 config: &Config,181 action: UploadAction,182 host: &str,183 built: PathBuf,184) -> Result<()> {185 let mut failed = false;186 // TODO: Lockfile, to prevent concurrent system switch?187 // TODO: If rollback target exists - bail, it should be removed. Lockfile will not work in case if rollback188 // is scheduler on next boot (default behavior). On current boot - rollback activator will fail due to189 // unit name conflict in systemd-run190 // This code is tied to rollback.nix191 if !build.disable_rollback {192 let _span = info_span!("preparing").entered();193 info!("preparing for rollback");194 let generation = get_current_generation(config, host).await?;195 info!(196 "rollback target would be {} {}",197 generation.id, generation.datetime198 );199 {200 let mut cmd = MyCommand::new("sh");201 cmd.arg("-c").arg(format!("mark=$(mktemp -p /etc -t fleet_rollback_marker.XXXXX) && echo -n {} > $mark && mv --no-clobber $mark /etc/fleet_rollback_marker", generation.id));202 if let Err(e) = config.run_on(host, cmd, true).await {203 error!("failed to set rollback marker: {e}");204 failed = true;205 }206 }207 // Activation script also starts rollback-watchdog.timer, however, it is possible that it won't be started.208 // Kicking it on manually will work best.209 //210 // There wouldn't be conflict, because here we trigger start of the primary service, and systemd will211 // only allow one instance of it.212213 // TODO: We should also watch how this process is going.214 // After running this command, we have less than 3 minutes to deploy everything,215 // if we fail to perform generation switch in time, then we will still call the activation script, and this may break something.216 // Anyway, reboot will still help in this case.217 if action.should_schedule_rollback_run() {218 let mut cmd = MyCommand::new("systemd-run");219 cmd.comparg("--on-active", "3min")220 .comparg("--unit", "rollback-watchdog-run")221 .arg("systemctl")222 .arg("start")223 .arg("rollback-watchdog.service");224 if let Err(e) = config.run_on(host, cmd, true).await {225 error!("failed to schedule rollback run: {e}");226 failed = true;227 }228 }229 }230 if action.should_switch_profile() && !failed {231 info!("switching generation");232 let mut cmd = MyCommand::new("nix-env");233 cmd.comparg("--profile", "/nix/var/nix/profiles/system")234 .comparg("--set", &built);235 if let Err(e) = config.run_on(host, cmd, true).await {236 error!("failed to switch generation: {e}");237 failed = true;238 }239 }240 if action.should_activate() && !failed {241 let _span = info_span!("activating").entered();242 info!("executing activation script");243 let mut switch_script = built.clone();244 switch_script.push("bin");245 switch_script.push("switch-to-configuration");246 let mut cmd = MyCommand::new(switch_script);247 cmd.arg(action.name());248 if let Err(e) = config.run_on(host, cmd, true).in_current_span().await {249 error!("failed to activate: {e}");250 failed = true;251 }252 }253 if !build.disable_rollback {254 if failed {255 info!("executing rollback");256 if let Err(e) = systemctl_start(config, host, "rollback-watchdog.service")257 .instrument(info_span!("rollback"))258 .await259 {260 error!("failed to trigger rollback: {e}")261 }262 } else {263 info!("trying to mark upgrade as successful");264 let mut cmd = MyCommand::new("rm");265 cmd.arg("-f").arg("/etc/fleet_rollback_marker");266 if let Err(e) = config.run_on(host, cmd, true).in_current_span().await {267 error!("failed to remove rollback marker. This is bad, as the system will be rolled back by watchdog: {e}")268 }269 }270 info!("disarming watchdog, just in case");271 if let Err(_e) = systemctl_stop(config, host, "rollback-watchdog.timer").await {272 // It is ok, if there was no reboot - then timer might not be running.273 }274 if action.should_schedule_rollback_run() {275 if let Err(e) = systemctl_stop(config, host, "rollback-watchdog-run.timer").await {276 error!("failed to disarm rollback run: {e}");277 }278 }279 } else {280 let mut cmd = MyCommand::new("rm");281 cmd.arg("-f").arg("/etc/fleet_rollback_marker");282 if let Err(_e) = config.run_on(host, cmd, true).in_current_span().await {283 // Marker might not exist, yet better try to remove it.284 }285 }286 Ok(())287}288289impl BuildSystems {290 async fn build_task(self, config: Config, host: String) -> Result<()> {291 info!("building");292 let action = Action::from(self.subcommand.clone());293 let drv = config294 .fleet_field295 .select(nix_path!(.buildSystems((serde_json::json!({296 "localSystem": config.local_system.clone(),297 }))).{action.build_attr()}.{&host}))298 .await.context("system attribute")?;299 let outputs = drv.build().await.map_err(|e| {300 if action.build_attr() == "sdImage" {301 info!("sd-image build failed");302 info!("Make sure you have imported modulesPath/installer/sd-card/sd-image-<arch>[-installer].nix (For installer, you may want to check config)");303 }304 e305 })?;306 let out_output = outputs307 .get("out")308 .ok_or_else(|| anyhow!("system build should produce \"out\" output"))?;309310 match action {311 Action::Upload { action } => {312 if !config.is_local(&host) {313 info!("uploading system closure");314 {315 // Alternatively, nix store make-content-addressed can be used,316 // at least for the first deployment, to provide trusted store key.317 //318 // It is much slower, yet doesn't require root on the deployer machine.319 let mut sign = MyCommand::new("nix");320 // Private key for host machine is registered in nix-sign.nix321 sign.arg("store")322 .arg("sign")323 .comparg("--key-file", "/etc/nix/private-key")324 .arg("-r")325 .arg(out_output);326 if let Err(e) = sign.sudo().run_nix().await {327 warn!("Failed to sign store paths: {e}");328 };329 }330 let mut tries = 0;331 loop {332 let mut nix = MyCommand::new("nix");333 nix.arg("copy")334 .arg("--substitute-on-destination")335 .comparg("--to", format!("ssh-ng://{host}"))336 .arg(out_output);337 match nix.run_nix().await {338 Ok(()) => break,339 Err(e) if tries < 3 => {340 tries += 1;341 warn!("Copy failure ({}/3): {}", tries, e);342 sleep(Duration::from_millis(5000)).await;343 }344 Err(e) => return Err(e),345 }346 }347 }348 if let Some(action) = action {349 execute_upload(&self, &config, action, &host, out_output.clone()).await?350 }351 }352 Action::Package(PackageAction::SdImage) => {353 let mut out = current_dir()?;354 out.push(format!("sd-image-{}", host));355356 info!("linking sd image to {:?}", out);357 symlink(out_output, out)?;358 }359 Action::Package(PackageAction::InstallationCd) => {360 let mut out = current_dir()?;361 out.push(format!("installation-cd-{}", host));362363 info!("linking iso image to {:?}", out);364 symlink(out_output, out)?;365 }366 };367 Ok(())368 }369370 pub async fn run(self, config: &Config) -> Result<()> {371 let hosts = config.list_hosts().await?;372 let set = LocalSet::new();373 let this = &self;374 for host in hosts.into_iter() {375 if config.should_skip(&host.name) {376 continue;377 }378 let config = config.clone();379 let this = this.clone();380 let span = info_span!("deployment", host = field::display(&host.name));381 let hostname = host.name;382 set.spawn_local(383 (async move {384 match this.build_task(config, hostname).await {385 Ok(_) => {}386 Err(e) => {387 error!("failed to deploy host: {}", e)388 }389 }390 })391 .instrument(span),392 );393 }394 set.await;395 Ok(())396 }397}cmds/fleet/src/host.rsdiffbeforeafterboth--- a/cmds/fleet/src/host.rs
+++ b/cmds/fleet/src/host.rs
@@ -13,7 +13,7 @@
use tempfile::NamedTempFile;
use crate::{
- better_nix_eval::{Field, Index, NixSessionPool},
+ better_nix_eval::{Field, NixSessionPool},
command::MyCommand,
fleetdata::{FleetData, FleetSecret, FleetSharedSecret},
nix_path,
@@ -250,7 +250,6 @@
#[clap(long)]
pub localhost: Option<String>,
- // TODO: unhardcode x86_64-linux
/// Override detected system for host, to perform builds via
/// binfmt-declared qemu instead of trying to crosscompile
#[clap(long, default_value = "detect")]
@@ -280,7 +279,7 @@
let fleet_root = Field::field(root_field, "fleetConfigurations").await?;
let fleet_field = fleet_root
- .select(nix_path!(.default.{&local_system}))
+ .select(nix_path!(.default))
.await?;
let config_field = fleet_field
.select(nix_path!(.configUnchecked))
cmds/fleet/src/main.rsdiffbeforeafterboth--- a/cmds/fleet/src/main.rs
+++ b/cmds/fleet/src/main.rs
@@ -24,7 +24,7 @@
use host::{Config, FleetOpts};
use human_repr::HumanCount;
use indicatif::{ProgressState, ProgressStyle};
-use tracing::{info, metadata::LevelFilter};
+use tracing::info;
use tracing::{info_span, Instrument};
use tracing_indicatif::IndicatifLayer;
use tracing_subscriber::{prelude::*, EnvFilter};
@@ -99,27 +99,6 @@
Ok(())
}
-// fn main() -> Result<()> {
-// let pool = r2d2::Builder::<NixSessionPool>::new()
-// .min_idle(Some(1))
-// .max_lifetime(Some(Duration::from_secs(10)))
-// .build(NixSessionPool {
-// flake: ".".to_owned(),
-// nix_args: vec![],
-// })?;
-// let conn = pool.get()?;
-// let field = Field::root(conn);
-// // let builtins = field.get_field("builtins")?;
-// let cur_sys: String = field.get_field("builtins")?.as_json()?;
-// eprintln!("current system = {cur_sys}");
-// let v = field.get_field("fleetConfigurations")?;
-// eprintln!("configs = {:?}", v.list_fields()?);
-// let d = v.get_field("default")?;
-// dbg!(d.list_fields());
-// Ok(())
-// }
-//
-
fn setup_logging() {
let indicatif_layer = IndicatifLayer::new().with_progress_style(
ProgressStyle::with_template(
@@ -157,7 +136,7 @@
),
);
- let filter = EnvFilter::from_default_env().add_directive(LevelFilter::INFO.into());
+ let filter = EnvFilter::from_default_env();
tracing_subscriber::registry()
.with(
lib/default.nixdiffbeforeafterboth--- a/lib/default.nix
+++ b/lib/default.nix
@@ -11,8 +11,7 @@
inherit nixpkgs hostNames;
};
in
- # Top-level arg is the builder system (not the target system!)
- nixpkgs.lib.genAttrs flake-utils.lib.defaultSystems (system: let
+ let
withData = data: rec {
root = nixpkgs.lib.evalModules {
modules = (import ../modules/fleet/_modules.nix) ++ [config data];
@@ -36,21 +35,7 @@
inherit name;
value = nixpkgs.lib.nixosSystem {
system = configuredHosts.${name}.system;
- modules =
- configuredHosts.${name}.modules
- ++ extraModules
- ++ [
- ({...}: {
- nixpkgs.system = system;
- nixpkgs.localSystem.system = system;
- nixpkgs.crossSystem =
- if system == configuredHosts.${name}.system
- then null
- else {
- system = configuredHosts.${name}.system;
- };
- })
- ];
+ modules = configuredHosts.${name}.modules ++ extraModules;
specialArgs = {
inherit fleetLib;
fleet = fleetLib.hostsToAttrs (host: configuredSystems.${host}.config);
@@ -60,19 +45,28 @@
)
(builtins.attrNames rootAssertWarn.config.hosts)
);
- buildSystems = {
+ buildSystems = {localSystem}: let
+ buildConfigurationModule = {config, ...}: {
+ # Equivalent to nixpkgs.localSystem
+ # nixpkgs.system = localSystem;
+ nixpkgs.buildPlatform.system = localSystem;
+ };
+ in {
toplevel = builtins.mapAttrs (_name: value: value.config.system.build.toplevel) (configuredSystemsWithExtraModules [
+ buildConfigurationModule
({...}: {
buildTarget = "toplevel";
})
]);
sdImage = builtins.mapAttrs (_name: value: value.config.system.build.sdImage) (configuredSystemsWithExtraModules [
+ buildConfigurationModule
#(nixpkgs + "/nixos/modules/installer/sd-card/sd-image-aarch64-installer.nix")
({...}: {
buildTarget = "sd-image";
})
]);
installationCd = builtins.mapAttrs (_name: value: value.config.system.build.isoImage) (configuredSystemsWithExtraModules [
+ buildConfigurationModule
(nixpkgs + "/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix")
({lib, ...}: {
buildTarget = "installation-cd";
@@ -91,5 +85,5 @@
in {
inherit (injectedData) configuredHosts configuredSecrets configuredSystems buildSystems configUnchecked;
};
- });
+ };
}