difftreelog
refactor perform build using nix repl
in: trunk
8 files changed
cmds/fleet/src/better_nix_eval.rsdiffbeforeafterboth1use std::collections::HashMap;1use std::ffi::{OsStr, OsString};2use std::ffi::{OsStr, OsString};2use std::fmt::Display;3use std::fmt::{self, Display};4use std::path::PathBuf;3use std::process::Stdio;5use std::process::Stdio;4use std::sync::{Arc, OnceLock};6use std::sync::{Arc, OnceLock};578use itertools::Itertools;10use itertools::Itertools;9use r2d2::{Pool, PooledConnection};11use r2d2::{Pool, PooledConnection};10use serde::de::DeserializeOwned;12use serde::de::DeserializeOwned;11use serde::Deserialize;13use serde::{Deserialize, Serialize};12use tokio::io::AsyncWriteExt;14use tokio::io::AsyncWriteExt;13use tokio::process::{ChildStderr, ChildStdin, ChildStdout, Command};15use tokio::process::{ChildStderr, ChildStdin, ChildStdout, Command};14use tokio::select;16use tokio::select;150 }158 }151}159}160161struct WarnHandler;162impl Handler for WarnHandler {163 fn handle_line(&mut self, e: &str) {164 warn!(target: "nix", "{e}")165 }166}152167153impl NixSessionInner {168impl NixSessionInner {154 async fn new(flake: &OsStr, extra_args: impl IntoIterator<Item = &OsStr>) -> Result<Self> {169 async fn new(flake: &OsStr, extra_args: impl IntoIterator<Item = &OsStr>) -> Result<Self> {174 stdin.flush().await?;189 stdin.flush().await?;175 let nix_handler = NixHandler::default();190 let nix_handler = NixHandler::default();176 let mut full_delimiter = None;191 let mut full_delimiter = None;192 let mut errors = vec![];177 while let Some(line) = out.next().await {193 while let Some(line) = out.next().await {178 let line = match line {194 let line = match line {179 OutputLine::Out(o) => o,195 OutputLine::Out(o) => o,180 OutputLine::Err(_e) => {196 OutputLine::Err(_e) => {181 // Handle startup errors, but skip repl hello?197 // Handle startup errors, but skip repl hello?182 //nix_handler.handle_line(&e);198 errors.push(_e);183 continue;199 continue;184 }200 }185 };201 };190 }206 }191 }207 }192 let Some(full_delimiter) = full_delimiter else {208 let Some(full_delimiter) = full_delimiter else {209 for e in errors {210 error!("{e}");211 }193 bail!("failed to discover delimiter");212 bail!("failed to discover delimiter");194 };213 };195 let mut res = Self {214 let mut res = Self {342#[derive(Clone)]361#[derive(Clone)]343pub struct NixSession(Arc<tokio::sync::Mutex<PooledConnection<NixSessionPoolInner>>>);362pub struct NixSession(Arc<tokio::sync::Mutex<PooledConnection<NixSessionPoolInner>>>);363364#[macro_export]365macro_rules! nix_path {366 (@o($o:ident) $var:ident $($tt:tt)*) => {{367 $o.push(Index::var(stringify!($var)));368 nix_path!(@o($o) $($tt)*);369 }};370 (@o($o:ident) . $var:ident $($tt:tt)*) => {{371 $o.push(Index::attr(stringify!($var)));372 nix_path!(@o($o) $($tt)*);373 }};374 (@o($o:ident) . $var:literal $($tt:tt)*) => {{375 $o.push(Index::attr($var));376 nix_path!(@o($o) $($tt)*);377 }};378 (@o($o:ident) . { $var:expr } $($tt:tt)*) => {{379 $o.push(Index::attr($var));380 nix_path!(@o($o) $($tt)*);381 }};382 (@o($o:ident) [ $var:literal ] $($tt:tt)*) => {{383 $o.push(Index::idx($var));384 nix_path!(@o($o) $($tt)*);385 }};386 (@o($o:ident) ($e:expr) $($tt:tt)*) => {387 $o.push(Index::apply($e));388 nix_path!(@o($o) $($tt)*);389 };390 (@o($o:ident)) => {};391 ($($tt:tt)+) => {{392 use $crate::{nix_path, better_nix_eval::Index};393 let mut out = vec![];394 nix_path!(@o(out) $($tt)*);395 out396 }}397}344398345#[derive(Clone)]399#[derive(Clone)]346enum Index {400pub enum Index {401 Var(String),347 String(String),402 String(String),348 // Idx(u32),403 Apply(String),404 Idx(u32),349}405}406impl Index {407 pub fn var(v: impl AsRef<str>) -> Self {408 let v = v.as_ref();409 assert!(410 !(v.contains('.') | v.contains(' ')),411 "bad variable name: {v}"412 );413 Self::Var(v.to_owned())414 }415 pub fn attr(v: impl AsRef<str>) -> Self {416 Self::String(v.as_ref().to_owned())417 }418 pub fn idx(v: u32) -> Self {419 Self::Idx(v)420 }421 pub fn apply(v: impl Serialize) -> Self {422 let serialized = nixlike::serialize(v).expect("invalid value for apply");423 Self::Apply(serialized)424 }425}350impl Display for Index {426impl Display for Index {351 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {427 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {352 match self {428 match self {429 Index::Var(v) => {430 write!(f, "{v}")431 }353 Index::String(k) => {432 Index::String(k) => {354 let v = nixlike::format_identifier(k.as_str());433 let v = nixlike::format_identifier(k.as_str());355 write!(f, ".{v}")434 write!(f, ".{v}")356 }435 }436 Index::Apply(o) => {437 let v = nixlike::serialize(o).map_err(|_| fmt::Error)?;438 write!(f, "<apply>({v})")439 }440 Index::Idx(i) => {441 write!(f, "[{i}]")442 }357 }443 }358 }444 }359}445}446impl fmt::Debug for Index {447 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {448 write!(f, "{self}")449 }450}360struct PathDisplay<'i>(&'i [Index]);451struct PathDisplay<'i>(&'i [Index]);361impl Display for PathDisplay<'_> {452impl Display for PathDisplay<'_> {362 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {453 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {381 }472 }382 }473 }383 pub async fn field(session: NixSession, field: &str) -> Result<Self> {474 pub async fn field(session: NixSession, field: &str) -> Result<Self> {384 Self::root(session).get_field_deep([field]).await475 Self::root(session)476 .select([Index::var(field)])477 .await385 }478 }386 pub async fn get_json_deep<'a, V: DeserializeOwned>(479 pub async fn get_json_deep<'a, V: DeserializeOwned>(387 &self,480 &self,388 name: impl IntoIterator<Item = &'a str>,481 name: impl IntoIterator<Item = Index>,389 ) -> Result<V> {482 ) -> Result<V> {390 let field = self.get_field_deep(name).await?;483 let field = self.select(name).await?;391 field.as_json().await484 field.as_json().await392 }485 }393 pub async fn get_field(&self, name: &str) -> Result<Self> {394 self.get_field_deep([name]).await395 }396 pub async fn get_field_deep<'a>(486 pub async fn select<'a>(&self, name: impl IntoIterator<Item = Index>) -> Result<Self> {397 &self,398 name: impl IntoIterator<Item = &'a str>,399 ) -> Result<Self> {400 let mut iter = name.into_iter();487 let mut name = name.into_iter();401488402 let mut full_path = self.full_path.clone();489 let mut full_path = self.full_path.clone();403 let mut query = if let Some(id) = self.value {490 let mut query = if let Some(id) = self.value {404 format!("sess_field_{id}")491 format!("sess_field_{id}")405 } else {492 } else {406 let first = iter.next().expect("name not empty");493 let first = name.next();407 ensure!(494 if let Some(Index::Var(i)) = first {408 !(first.contains('.') | first.contains(' ')),409 "bad name for root query: {first}"410 );411 full_path.push(Index::String(first.to_string()));495 full_path.push(Index::Var(i.clone()));412 first.to_string()496 i.clone()497 } else {498 panic!("first path item should be variable, got {first:?}")499 }413 };500 };414 for v in iter {501 for v in name {415 full_path.push(Index::String(v.to_string()));502 full_path.push(v.clone());416 // Escape503 match v {504 Index::Var(_) => panic!("var item may only be first"),505 Index::String(s) => {417 let escaped = nixlike::serialize(v)?;506 let escaped = nixlike::serialize(s)?;418 let escaped = escaped.trim();507 query.push('.');508 query.push_str(escaped.trim());509 }510 Index::Apply(a) => {419 query.push('.');511 query.push(' ');420 query.push_str(escaped);512 query.push_str(&a);513 }514 Index::Idx(idx) => {515 query = format!("builtins.elemAt ({query}) {idx}");516 }517 }421 }518 }422519423 let vid = self520 let vid = self454 .await551 .await455 .with_context(|| format!("full path: {}", PathDisplay(&self.full_path)))552 .with_context(|| format!("full path: {}", PathDisplay(&self.full_path)))456 }553 }554 pub async fn build(&self) -> Result<HashMap<String, PathBuf>> {555 let id = self.value.expect("can't use build on not-value");556 let vid = self557 .session558 .0559 .lock()560 .await561 .execute_expression_raw(&format!(":b sess_field_{id}"), &mut NixHandler::default())562 .await?;563 ensure!(!vid.is_empty(), "build failed");564 let Some(vid) = vid.strip_prefix("This derivation produced the following outputs:\n")565 else {566 panic!("unexpected build output: {vid:?}");567 };568 let outputs = vid569 .split('\n')570 .filter(|v| !v.is_empty())571 .map(|v| v.split_once(" -> ").expect("unexpected build output"))572 .map(|(a, b)| (a.trim_start().to_owned(), PathBuf::from(b)))573 .collect();574 Ok(outputs)575 }457}576}458impl Drop for Field {577impl Drop for Field {459 fn drop(&mut self) {578 fn drop(&mut self) {cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth1use std::os::unix::fs::symlink;1use std::path::PathBuf;2use std::path::PathBuf;2use std::{env::current_dir, time::Duration};3use std::{env::current_dir, time::Duration};344use crate::command::MyCommand;5use crate::command::MyCommand;5use crate::host::Config;6use crate::host::Config;7use crate::nix_path;6use anyhow::{anyhow, Result};8use anyhow::{anyhow, Result};7use clap::Parser;9use clap::Parser;8use itertools::Itertools;10use itertools::Itertools;111312#[derive(Parser, Clone)]14#[derive(Parser, Clone)]13pub struct BuildSystems {15pub struct BuildSystems {14 /// Do not continue on error15 #[clap(long)]16 fail_fast: bool,17 /// Disable automatic rollback16 /// Disable automatic rollback18 #[clap(long)]17 #[clap(long)]19 disable_rollback: bool,18 disable_rollback: bool,20 /// Run builds as sudo21 #[clap(long)]22 privileged_build: bool,23 #[clap(subcommand)]19 #[clap(subcommand)]24 subcommand: Subcommand,20 subcommand: Subcommand,25}21}294 async fn build_task(self, config: Config, host: String) -> Result<()> {290 async fn build_task(self, config: Config, host: String) -> Result<()> {295 info!("building");291 info!("building");296 let action = Action::from(self.subcommand.clone());292 let action = Action::from(self.subcommand.clone());297 let built = {298 let dir = tempfile::tempdir()?;299 dir.path().to_owned()300 };301302 let mut nix_build = MyCommand::new("nix");293 let drv = config303 nix_build304 .args([305 "build",306 "--impure",307 "--json",308 // "--show-trace",309 "--no-link",310 ])311 .comparg("--out-link", &built)294 .fleet_field312 .arg(295 .select(nix_path!(.buildSystems.{action.build_attr()}.{&host}))313 config.configuration_attr_name(&format!(314 "buildSystems.{}.{host}",315 action.build_attr()316 )),317 )318 .args(&config.nix_args);296 .await?;319320 if self.privileged_build {321 nix_build = nix_build.sudo();322 }323324 nix_build.run_nix().await.map_err(|e| {297 let outputs = drv.build().await.map_err(|e| {325 if action.build_attr() == "sdImage" {298 if action.build_attr() == "sdImage" {326 info!("sd-image build failed");299 info!("sd-image build failed");327 info!("Make sure you have imported modulesPath/installer/sd-card/sd-image-<arch>[-installer].nix (For installer, you may want to check config)");300 info!("Make sure you have imported modulesPath/installer/sd-card/sd-image-<arch>[-installer].nix (For installer, you may want to check config)");328 info!("This module was automatically imported before, but was removed for better customization")301 info!("This module was automatically imported before, but was removed for better customization")329 }302 }330 e303 e331 })?;304 })?;332 let built = std::fs::canonicalize(built)?;305 let out_output = outputs306 .get("out")307 .ok_or_else(|| anyhow!("system build should produce \"out\" output"))?;333308334 match action {309 match action {335 Action::Upload { action } => {310 Action::Upload { action } => {342 .arg("sign")317 .arg("sign")343 .comparg("--key-file", "/etc/nix/private-key")318 .comparg("--key-file", "/etc/nix/private-key")344 .arg("-r")319 .arg("-r")345 .arg(&built);320 .arg(out_output);346 if let Err(e) = sign.sudo().run_nix().await {321 if let Err(e) = sign.sudo().run_nix().await {347 warn!("Failed to sign store paths: {e}");322 warn!("Failed to sign store paths: {e}");348 };323 };353 nix.arg("copy")328 nix.arg("copy")354 .arg("--substitute-on-destination")329 .arg("--substitute-on-destination")355 .comparg("--to", format!("ssh-ng://{host}"))330 .comparg("--to", format!("ssh-ng://{host}"))356 .arg(&built);331 .arg(out_output);357 match nix.run_nix().await {332 match nix.run_nix().await {358 Ok(()) => break,333 Ok(()) => break,359 Err(e) if tries < 3 => {334 Err(e) if tries < 3 => {366 }341 }367 }342 }368 if let Some(action) = action {343 if let Some(action) = action {369 execute_upload(&self, &config, action, &host, built).await?344 execute_upload(&self, &config, action, &host, out_output.clone()).await?370 }345 }371 }346 }372 Action::Package(PackageAction::SdImage) => {347 Action::Package(PackageAction::SdImage) => {373 let mut out = current_dir()?;348 let mut out = current_dir()?;374 out.push(format!("sd-image-{}", host));349 out.push(format!("sd-image-{}", host));375350376 info!("building sd image to {:?}", out);351 info!("linking sd image to {:?}", out);377 let mut nix_build = MyCommand::new("nix");378 nix_build379 .args(["build", "--impure", "--no-link"])380 .comparg("--out-link", &out)352 symlink(out_output, out)?;381 .arg(config.configuration_attr_name(&format!("buildSystems.sdImage.{}", host,)))382 .args(&config.nix_args);383 if !self.fail_fast {384 nix_build.arg("--keep-going");385 }386 if self.privileged_build {387 nix_build = nix_build.sudo();388 }389390 nix_build.run_nix().await?;391 }353 }392 Action::Package(PackageAction::InstallationCd) => {354 Action::Package(PackageAction::InstallationCd) => {393 let mut out = current_dir()?;355 let mut out = current_dir()?;394 out.push(format!("installation-cd-{}", host));356 out.push(format!("installation-cd-{}", host));395357396 info!("building sd image to {:?}", out);358 info!("linking iso image to {:?}", out);397 let mut nix_build = MyCommand::new("nix");398 nix_build399 .args(["build", "--impure", "--no-link"])400 .comparg("--out-link", &out)359 symlink(out_output, out)?;401 .arg(402 config.configuration_attr_name(&format!(403 "buildSystems.installationCd.{}",404 host,405 )),406 )407 .args(&config.nix_args);408 if !self.fail_fast {409 nix_build.arg("--keep-going");410 }411 if self.privileged_build {412 nix_build = nix_build.sudo();413 }414415 nix_build.run_nix().await?;416 }360 }417 };361 };418 Ok(())362 Ok(())cmds/fleet/src/cmds/info.rsdiffbeforeafterboth1use std::collections::BTreeSet;1use std::collections::BTreeSet;223use crate::host::Config;3use crate::host::Config;4use crate::nix_path;4use anyhow::{ensure, Result};5use anyhow::{ensure, Result};5use clap::Parser;6use clap::Parser;6738 if !tagged.is_empty() {39 if !tagged.is_empty() {39 let tags: Vec<String> = config40 let tags: Vec<String> = config40 .fleet_field41 .fleet_field41 .get_field_deep(["configuredSystems", &host.name, "config", "tags"])42 .select(nix_path!(.configuredSystems.{&host.name}.config.tags))42 .await?43 .await?43 .as_json()44 .as_json()44 .await?;45 .await?;64 let host = config.system_config(&host).await?;65 let host = config.system_config(&host).await?;65 if external {66 if external {66 out.extend(67 out.extend(67 host.get_field_deep(["network", "externalIps"])68 host.select(nix_path!(.network.externalIps))68 .await?69 .await?69 .as_json::<Vec<String>>()70 .as_json::<Vec<String>>()70 .await?,71 .await?,71 );72 );72 }73 }73 if internal {74 if internal {74 out.extend(75 out.extend(75 host.get_field_deep(["network", "internalIps"])76 host.select(nix_path!(.network.internalIps))76 .await?77 .await?77 .as_json::<Vec<String>>()78 .as_json::<Vec<String>>()78 .await?,79 .await?,cmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth1use crate::{1use crate::{2 fleetdata::{FleetSecret, FleetSharedSecret},2 fleetdata::{FleetSecret, FleetSharedSecret},3 host::Config,3 host::Config, nix_path,4};4};5use anyhow::{bail, ensure, Context, Result};5use anyhow::{bail, ensure, Context, Result};6use chrono::Utc;6use chrono::Utc;339 let mut data = config.shared_secret(name)?;339 let mut data = config.shared_secret(name)?;340 let expected_owners: Vec<String> = config340 let expected_owners: Vec<String> = config341 .config_field341 .config_field342 .get_json_deep(["sharedSecrets", name, "expectedOwners"])342 .get_json_deep(nix_path!(sharedSecrets.{name}.expectedOwners))343 .await?;343 .await?;344 if expected_owners.is_empty() {344 if expected_owners.is_empty() {345 warn!("secret was removed from fleet config: {name}, removing from data");345 warn!("secret was removed from fleet config: {name}, removing from data");352 if set != expected_set {352 if set != expected_set {353 let owner_dependent: bool = config353 let owner_dependent: bool = config354 .config_field354 .config_field355 .get_json_deep(["sharedSecrets", name, "ownerDependent"])355 .get_json_deep(nix_path!(.sharedSecrets.{name}.ownerDependent))356 .await?;356 .await?;357 if !owner_dependent {357 if !owner_dependent {358 warn!("reencrypting secret '{name}' for new owner set");358 warn!("reencrypting secret '{name}' for new owner set");cmds/fleet/src/command.rsdiffbeforeafterboth1use std::{1use std::{2 borrow::Cow,3 collections::HashMap,2 collections::HashMap,4 ffi::OsStr,3 ffi::OsStr,5 process::Stdio,4 process::Stdio,247pub struct NixHandler {246pub struct NixHandler {248 spans: HashMap<u64, Span>,247 spans: HashMap<u64, Span>,249}248}250fn process_message(m: &str) -> Cow<'_, str> {249fn process_message(m: &str) -> String {251 static OSC_CLEANER: Lazy<Regex> =250 static OSC_CLEANER: Lazy<Regex> =252 Lazy::new(|| Regex::new(r"\x1B\]([^\x07\x1C]*[\x07\x1C])?|\r").unwrap());251 Lazy::new(|| Regex::new(r"\x1B\]([^\x07\x1C]*[\x07\x1C])?|\r").unwrap());252 static DETABBER: Lazy<Regex> = Lazy::new(|| Regex::new(r"\t").unwrap());253 OSC_CLEANER.replace_all(m, "")253 let m = OSC_CLEANER.replace_all(m, "");254 // Indicatif can't format tabs. This is not the correct tab formatting, as correct one should be aligned,255 // and not just be replaced with the constant number of spaces, but it's ok for now, as statuses are single-line.256 DETABBER.replace_all(m.as_ref(), " ").to_string()254}257}255impl Handler for NixHandler {258impl Handler for NixHandler {256 fn handle_line(&mut self, e: &str) {259 fn handle_line(&mut self, e: &str) {cmds/fleet/src/host.rsdiffbeforeafterboth13use tempfile::NamedTempFile;13use tempfile::NamedTempFile;141415use crate::{15use crate::{16 better_nix_eval::{Field, NixSessionPool},16 better_nix_eval::{Field, Index, NixSessionPool},17 command::MyCommand,17 command::MyCommand,18 fleetdata::{FleetData, FleetSecret, FleetSharedSecret},18 fleetdata::{FleetData, FleetSecret, FleetSharedSecret},19 nix_path,19};20};202121pub struct FleetConfigInternals {22pub struct FleetConfigInternals {24 pub opts: FleetOpts,25 pub opts: FleetOpts,25 pub data: Mutex<FleetData>,26 pub data: Mutex<FleetData>,26 pub nix_args: Vec<OsString>,27 pub nix_args: Vec<OsString>,27 // fleetConfigurations.<name>28 /// fleetConfigurations.<name>.<localSystem>28 pub fleet_field: Field,29 pub fleet_field: Field,29 // fleet_config.configUnchecked30 /// fleet_config.configUnchecked30 pub config_field: Field,31 pub config_field: Field,31}32}323393 command.run_string().await94 command.run_string().await94 }95 }9596 pub fn configuration_attr_name(&self, name: &str) -> OsString {97 let mut str = self.directory.as_os_str().to_owned();98 str.push("#");99 str.push(&format!(100 "fleetConfigurations.default.{}.{}",101 self.local_system, name102 ));103 str104 }10596106 pub async fn list_hosts(&self) -> Result<Vec<ConfigHost>> {97 pub async fn list_hosts(&self) -> Result<Vec<ConfigHost>> {107 let names = self98 let names = self108 .fleet_field99 .fleet_field109 .get_field_deep(["configuredHosts"])100 .select(nix_path!(.configuredHosts))110 .await?101 .await?111 .list_fields()102 .list_fields()112 .await?;103 .await?;118 }109 }119 pub async fn system_config(&self, host: &str) -> Result<Field> {110 pub async fn system_config(&self, host: &str) -> Result<Field> {120 self.fleet_field111 self.fleet_field121 .get_field_deep(["configuredSystems", host, "config"])112 .select(nix_path!(.configuredSystems.{host}.config))122 .await113 .await123 }114 }124115131 /// Shared secrets configured in fleet.nix or in flake122 /// Shared secrets configured in fleet.nix or in flake132 pub async fn list_configured_shared(&self) -> Result<Vec<String>> {123 pub async fn list_configured_shared(&self) -> Result<Vec<String>> {133 self.config_field124 self.config_field134 .get_field("sharedSecrets")125 .select(nix_path!(.sharedSecrets))135 .await?126 .await?136 .list_fields()127 .list_fields()137 .await128 .await221 }212 }222 pub async fn shared_secret_expected_owners(&self, secret: &str) -> Result<Vec<String>> {213 pub async fn shared_secret_expected_owners(&self, secret: &str) -> Result<Vec<String>> {223 self.config_field214 self.config_field224 .get_field_deep(["sharedSecrets", secret, "expectedOwners"])215 .select(nix_path!(.sharedSecrets.{secret}.expectedOwners))225 .await?216 .await?226 .as_json()217 .as_json()227 .await218 .await279270280 if self.local_system == "detect" {271 if self.local_system == "detect" {281 let builtins_field = Field::field(root_field.clone(), "builtins").await?;272 let builtins_field = Field::field(root_field.clone(), "builtins").await?;282 let system = builtins_field.get_field("currentSystem").await?;273 let system = builtins_field274 .select(nix_path!(.currentSystem))275 .await?;283 self.local_system = system.as_json().await?;276 self.local_system = system.as_json().await?;284 }277 }287 let fleet_root = Field::field(root_field, "fleetConfigurations").await?;280 let fleet_root = Field::field(root_field, "fleetConfigurations").await?;288281289 let fleet_field = fleet_root282 let fleet_field = fleet_root290 .get_field_deep(["default", &local_system])283 .select(nix_path!(.default.{&local_system}))291 .await?;284 .await?;292 let config_field = fleet_field.get_field("configUnchecked").await?;285 let config_field = fleet_field286 .select(nix_path!(.configUnchecked))287 .await?;293288294 let mut fleet_data_path = directory.clone();289 let mut fleet_data_path = directory.clone();cmds/fleet/src/main.rsdiffbeforeafterboth1#![recursion_limit = "512"]1#![feature(try_blocks)]2#![feature(try_blocks)]233pub(crate) mod cmds;4pub(crate) mod cmds;flake.nixdiffbeforeafterboth19 rustPlatform = pkgs.makeRustPlatform { cargo = rust; rustc = rust; };19 rustPlatform = pkgs.makeRustPlatform { cargo = rust; rustc = rust; };20 in20 in21 {21 {22 packages = (import ./pkgs) pkgs pkgs;22 devShell = (pkgs.mkShell.override { stdenv = llvmPkgs.stdenv; }) {23 devShell = (pkgs.mkShell.override { stdenv = llvmPkgs.stdenv; }) {23 nativeBuildInputs = with pkgs; [24 nativeBuildInputs = with pkgs; [24 rust25 rust