difftreelog
feat prefetch command
in: trunk
3 files changed
cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth1use 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}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 "--option",124 "log-lines",125 "200",126 ])127 .comparg("--out-link", &built)128 .arg(129 config.configuration_attr_name(&format!(130 "buildSystems.{}.{host}",131 action.build_attr()132 )),133 )134 .args(&config.nix_args);135136 if self.privileged_build {137 nix_build = nix_build.sudo();138 }139140 nix_build.run_nix().await.map_err(|e| {141 if action.build_attr() == "sdImage" {142 info!("sd-image build failed");143 info!("Make sure you have imported modulesPath/installer/sd-card/sd-image-<arch>[-installer].nix (For installer, you may want to check config)");144 info!("This module was automatically imported before, but was removed for better customization")145 }146 e147 })?;148 let built = std::fs::canonicalize(built)?;149150 match action {151 Action::Upload { action } => {152 if !config.is_local(&host) {153 info!("uploading system closure");154 let mut tries = 0;155 loop {156 let mut nix = MyCommand::new("nix");157 nix.arg("copy")158 .comparg("--to", format!("ssh://root@{host}"))159 .arg(&built);160 match nix.run_nix().await {161 Ok(()) => break,162 Err(e) if tries < 3 => {163 tries += 1;164 warn!("Copy failure ({}/3): {}", tries, e);165 sleep(Duration::from_millis(5000)).await;166 }167 Err(e) => return Err(e),168 }169 }170 }171 if let Some(action) = action {172 if action.should_switch_profile() {173 info!("switching generation");174 let mut cmd = MyCommand::new("nix-env");175 cmd.comparg("--profile", "/nix/var/nix/profiles/system")176 .comparg("--set", &built);177 config.run_on(&host, cmd, true).await?;178 }179 if action.should_activate() {180 info!("executing activation script");181 let mut switch_script = built.clone();182 switch_script.push("bin");183 switch_script.push("switch-to-configuration");184 let mut cmd = MyCommand::new(switch_script);185 cmd.arg(action.name());186 config.run_on(&host, cmd, true).await?;187 }188 }189 }190 Action::Package(PackageAction::SdImage) => {191 let mut out = current_dir()?;192 out.push(format!("sd-image-{}", host));193194 info!("building sd image to {:?}", out);195 let mut nix_build = MyCommand::new("nix");196 nix_build197 .args(["build", "--impure", "--no-link"])198 .comparg("--out-link", &out)199 .arg(config.configuration_attr_name(&format!("buildSystems.sdImage.{}", host,)))200 .args(&config.nix_args);201 if !self.fail_fast {202 nix_build.arg("--keep-going");203 }204 if self.privileged_build {205 nix_build = nix_build.sudo();206 }207208 nix_build.run_nix().await?;209 }210 Action::Package(PackageAction::InstallationCd) => {211 let mut out = current_dir()?;212 out.push(format!("installation-cd-{}", host));213214 info!("building sd image to {:?}", out);215 let mut nix_build = MyCommand::new("nix");216 nix_build217 .args(["build", "--impure", "--no-link"])218 .comparg("--out-link", &out)219 .arg(220 config.configuration_attr_name(&format!(221 "buildSystems.installationCd.{}",222 host,223 )),224 )225 .args(&config.nix_args);226 if !self.fail_fast {227 nix_build.arg("--keep-going");228 }229 if self.privileged_build {230 nix_build = nix_build.sudo();231 }232233 nix_build.run_nix().await?;234 }235 };236 Ok(())237 }238239 pub async fn run(self, config: &Config) -> Result<()> {240 let hosts = config.list_hosts().await?;241 let set = LocalSet::new();242 let this = &self;243 for host in hosts.iter() {244 if config.should_skip(host) {245 continue;246 }247 let config = config.clone();248 let host = host.clone();249 let this = this.clone();250 let span = info_span!("deployment", host = field::display(&host));251 set.spawn_local(252 (async move {253 match this.build_task(config, host).await {254 Ok(_) => {}255 Err(e) => {256 error!("failed to deploy host: {}", e)257 }258 }259 })260 .instrument(span),261 );262 }263 set.await;264 Ok(())265 }266}cmds/fleet/src/command.rsdiffbeforeafterboth--- a/cmds/fleet/src/command.rs
+++ b/cmds/fleet/src/command.rs
@@ -254,9 +254,12 @@
NixLog::Start { text, level: 1, typ: 111, .. } if text.starts_with("waiting for a machine to build ") => {
// Useless repeating notification about build
}
- NixLog::Start { text, level: 3, typ: 111, .. } if text.starts_with("resolved derivation: ") => {
+ NixLog::Start { text, level: 3, typ: 111, .. } if text.starts_with("resolved derivation: ") => {
// CA resolved
}
+ NixLog::Start { text, level: 1, typ: 111, .. } if text.starts_with("waiting for lock on ") => {
+ // Concurrent build of the same message
+ }
NixLog::Stop { .. } => {},
NixLog::Result { .. } => {},
_ => warn!("unknown log: {:?}", log)
cmds/fleet/src/main.rsdiffbeforeafterboth--- a/cmds/fleet/src/main.rs
+++ b/cmds/fleet/src/main.rs
@@ -5,23 +5,56 @@
mod fleetdata;
+use std::ffi::OsString;
use std::io;
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, bail, Result};
use clap::Parser;
use cmds::{build_systems::BuildSystems, info::Info, secrets::Secrets};
use host::{Config, FleetOpts};
+use tokio::fs;
+use tokio::process::Command;
use tracing::{info, metadata::LevelFilter};
use tracing_subscriber::EnvFilter;
#[derive(Parser)]
+struct Prefetch {}
+impl Prefetch {
+ async fn run(&self, config: &Config) -> Result<()> {
+ let mut prefetch_dir = config.directory.to_path_buf();
+ prefetch_dir.push("prefetch");
+ if !prefetch_dir.is_dir() {
+ info!("nothing to prefetch: no prefetch directory");
+ return Ok(());
+ }
+ for entry in std::fs::read_dir(&prefetch_dir)? {
+ let entry = entry?;
+ if !entry.metadata()?.is_file() {
+ bail!("only files should exist in prefetch directory");
+ }
+ info!("prefetching {:?}", entry.file_name());
+ let mut path = OsString::new();
+ path.push("file://");
+ path.push(entry.path());
+ let status = Command::new("nix-prefetch-url").arg(path).status().await?;
+ if !status.success() {
+ bail!("failed with {status}");
+ }
+ }
+ Ok(())
+ }
+}
+
+#[derive(Parser)]
enum Opts {
/// Prepare systems for deployments
BuildSystems(BuildSystems),
/// Secret management
#[clap(subcommand)]
Secrets(Secrets),
+ /// Upload prefetch directory to the nix store
+ Prefetch(Prefetch),
/// Config parsing
Info(Info),
}
@@ -40,6 +73,7 @@
Opts::BuildSystems(c) => c.run(config).await?,
Opts::Secrets(s) => s.run(config).await?,
Opts::Info(i) => i.run(config).await?,
+ Opts::Prefetch(p) => p.run(config).await?,
};
Ok(())
}