git.delta.rocks / jrsonnet / refs/commits / 94ece5cae749

difftreelog

build update to nixos release-25.05

luvuxwnmYaroslav Bolyukin2025-06-28parent: #1470de8.patch.diff
in: trunk

34 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,6 +1,6 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
 name = "abort-on-drop"
@@ -377,29 +377,6 @@
 ]
 
 [[package]]
-name = "bindgen"
-version = "0.69.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
-dependencies = [
- "bitflags",
- "cexpr",
- "clang-sys",
- "itertools 0.12.1",
- "lazy_static",
- "lazycell",
- "log",
- "prettyplease",
- "proc-macro2",
- "quote",
- "regex",
- "rustc-hash",
- "shlex",
- "syn",
- "which",
-]
-
-[[package]]
 name = "bitflags"
 version = "2.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -488,15 +465,6 @@
 ]
 
 [[package]]
-name = "cexpr"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
-dependencies = [
- "nom 7.1.3",
-]
-
-[[package]]
 name = "cfg-if"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -556,17 +524,6 @@
  "crypto-common",
  "inout",
  "zeroize",
-]
-
-[[package]]
-name = "clang-sys"
-version = "1.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
-dependencies = [
- "glob",
- "libc",
- "libloading",
 ]
 
 [[package]]
@@ -1242,12 +1199,6 @@
 version = "0.31.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
-
-[[package]]
-name = "glob"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
 [[package]]
 name = "h2"
@@ -1332,15 +1283,6 @@
 checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
 dependencies = [
  "digest",
-]
-
-[[package]]
-name = "home"
-version = "0.5.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
-dependencies = [
- "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -1648,15 +1590,6 @@
 version = "1.70.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
-
-[[package]]
-name = "itertools"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
-dependencies = [
- "either",
-]
 
 [[package]]
 name = "itertools"
@@ -1701,28 +1634,12 @@
 ]
 
 [[package]]
-name = "lazycell"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
-
-[[package]]
 name = "libc"
 version = "0.2.174"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
 
 [[package]]
-name = "libloading"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
-dependencies = [
- "cfg-if",
- "windows-targets",
-]
-
-[[package]]
 name = "libm"
 version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1887,14 +1804,6 @@
  "tokio-util",
  "tracing",
  "unindent",
-]
-
-[[package]]
-name = "nix-native-eval"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "nixrs",
 ]
 
 [[package]]
@@ -1912,25 +1821,6 @@
 ]
 
 [[package]]
-name = "nixrs"
-version = "0.1.0"
-source = "git+https://github.com/Anillc/nixrs#740fcf4048cc5b6de8c54d18254f12d53909a867"
-dependencies = [
- "libc",
- "nixrs-sys",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "nixrs-sys"
-version = "0.1.0"
-source = "git+https://github.com/Anillc/nixrs#740fcf4048cc5b6de8c54d18254f12d53909a867"
-dependencies = [
- "bindgen",
- "pkg-config",
-]
-
-[[package]]
 name = "nom"
 version = "7.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2224,12 +2114,6 @@
  "der",
  "spki",
 ]
-
-[[package]]
-name = "pkg-config"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
 
 [[package]]
 name = "poly1305"
@@ -3752,18 +3636,6 @@
 dependencies = [
  "js-sys",
  "wasm-bindgen",
-]
-
-[[package]]
-name = "which"
-version = "4.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
-dependencies = [
- "either",
- "home",
- "once_cell",
- "rustix 0.38.40",
 ]
 
 [[package]]
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,8 +2,8 @@
 members = ["crates/*", "cmds/*"]
 resolver = "2"
 package.version = "0.1.0"
-package.edition = "2021"
-package.rust-version = "1.82.0"
+package.edition = "2024"
+package.rust-version = "1.86.0"
 
 [workspace.dependencies]
 better-command = { path = "./crates/better-command" }
modifiedcmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth
--- a/cmds/fleet/src/cmds/build_systems.rs
+++ b/cmds/fleet/src/cmds/build_systems.rs
@@ -1,15 +1,15 @@
 use std::{env::current_dir, os::unix::fs::symlink, path::PathBuf};
 
-use anyhow::{anyhow, Result};
+use anyhow::{Result, anyhow};
 use clap::Parser;
 use fleet_base::{
-	deploy::{deploy_task, upload_task, DeployAction},
+	deploy::{DeployAction, deploy_task, upload_task},
 	host::{Config, DeployKind, GenerationStorage},
 	opts::FleetOpts,
 };
-use nix_eval::{nix_go, NixBuildBatch};
+use nix_eval::{NixBuildBatch, nix_go};
 use tokio::task::LocalSet;
-use tracing::{error, field, info, info_span, warn, Instrument};
+use tracing::{Instrument, error, field, info, info_span, warn};
 
 #[derive(Parser)]
 pub struct Deploy {
@@ -167,11 +167,12 @@
 						self.action,
 						&host,
 						remote_path,
-						if let Ok(v) = opts.action_attr(&host, "specialisation").await {
-							v
-						} else {
-							error!("unreachable? failed to get specialization");
-							return;
+						match opts.action_attr(&host, "specialisation").await {
+							Ok(v) => v,
+							_ => {
+								error!("unreachable? failed to get specialization");
+								return;
+							}
 						},
 						disable_rollback,
 					)
modifiedcmds/fleet/src/cmds/info.rsdiffbeforeafterboth
--- a/cmds/fleet/src/cmds/info.rs
+++ b/cmds/fleet/src/cmds/info.rs
@@ -1,6 +1,6 @@
 use std::collections::BTreeSet;
 
-use anyhow::{ensure, Result};
+use anyhow::{Result, ensure};
 use clap::Parser;
 use fleet_base::host::Config;
 use nix_eval::nix_go_json;
modifiedcmds/fleet/src/cmds/mod.rsdiffbeforeafterboth
--- a/cmds/fleet/src/cmds/mod.rs
+++ b/cmds/fleet/src/cmds/mod.rs
@@ -1,6 +1,6 @@
 pub mod build_systems;
 pub mod complete;
 pub mod info;
+pub mod rollback;
 pub mod secrets;
 pub mod tf;
-pub mod rollback;
\ No newline at end of file
modifiedcmds/fleet/src/cmds/rollback.rsdiffbeforeafterboth
--- a/cmds/fleet/src/cmds/rollback.rs
+++ b/cmds/fleet/src/cmds/rollback.rs
@@ -1,9 +1,9 @@
 use std::collections::HashSet;
 
-use anyhow::{bail, Result};
+use anyhow::{Result, bail};
 use clap::Parser;
 use fleet_base::{
-	deploy::{deploy_task, upload_task, DeployAction},
+	deploy::{DeployAction, deploy_task, upload_task},
 	host::{Config, ConfigHost, Generation, GenerationStorage},
 	opts::FleetOpts,
 };
modifiedcmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth
--- a/cmds/fleet/src/cmds/secrets/mod.rs
+++ b/cmds/fleet/src/cmds/secrets/mod.rs
@@ -1,25 +1,25 @@
 use std::{
 	collections::{BTreeMap, BTreeSet, HashSet},
-	io::{self, stdin, stdout, Read, Write},
+	io::{self, Read, Write, stdin, stdout},
 	path::PathBuf,
 };
 
 use age::Recipient;
-use anyhow::{anyhow, bail, ensure, Context, Result};
+use anyhow::{Context, Result, anyhow, bail, ensure};
 use chrono::{DateTime, Utc};
 use clap::Parser;
 use fleet_base::{
-	fleetdata::{encrypt_secret_data, FleetSecret, FleetSecretPart, FleetSharedSecret},
+	fleetdata::{FleetSecret, FleetSecretPart, FleetSharedSecret, encrypt_secret_data},
 	host::Config,
 	opts::FleetOpts,
 };
 use fleet_shared::SecretData;
-use nix_eval::{nix_go, nix_go_json, NixBuildBatch, Value};
+use nix_eval::{NixBuildBatch, Value, nix_go, nix_go_json};
 use owo_colors::OwoColorize;
 use serde::Deserialize;
 use tabled::{Table, Tabled};
 use tokio::fs::read;
-use tracing::{error, info, info_span, warn, Instrument};
+use tracing::{Instrument, error, info, info_span, warn};
 
 #[derive(Parser)]
 pub enum Secret {
@@ -187,7 +187,9 @@
 		true
 	} else if set.difference(&expected_set).next().is_some() {
 		// TODO: Remove this warning for revokable secrets.
-		warn!("host was removed from secret owners, but until this host rebuild, the secret will still be stored on it.");
+		warn!(
+			"host was removed from secret owners, but until this host rebuild, the secret will still be stored on it."
+		);
 		nix_go_json!(field.regenerateOnOwnerRemoved)
 	} else if expected_set.difference(&set).next().is_some() {
 		nix_go_json!(field.regenerateOnOwnerAdded)
@@ -296,8 +298,8 @@
 	let out_parent = host.mktemp_dir().await?;
 	let out = format!("{out_parent}/out");
 
-	let mut gen = host.cmd(generator).await?;
-	gen.env("out", &out);
+	let mut r#gen = host.cmd(generator).await?;
+	r#gen.env("out", &out);
 	if on.is_none() {
 		// This path is local, thus we can feed `OsString` directly to env var... But I don't think that's necessary to handle.
 		let project_path: String = config
@@ -306,9 +308,9 @@
 			.into_os_string()
 			.into_string()
 			.map_err(|s| anyhow!("fleet project path is not utf-8: {s:?}"))?;
-		gen.env("FLEET_PROJECT", project_path);
+		r#gen.env("FLEET_PROJECT", project_path);
 	}
-	gen.run().await.context("impure generator")?;
+	r#gen.run().await.context("impure generator")?;
 
 	{
 		let marker = host.read_file_text(format!("{out}/marker")).await?;
@@ -510,7 +512,9 @@
 	if !remove_machines.is_empty() {
 		// TODO: maybe force secret regeneration?
 		// Not that useful without revokation.
-		warn!("secret will not be regenerated for removed machines, and until host rebuild, they will still possess the ability to decode secret");
+		warn!(
+			"secret will not be regenerated for removed machines, and until host rebuild, they will still possess the ability to decode secret"
+		);
 	}
 	Ok(target_machines)
 }
@@ -596,7 +600,9 @@
 				part: part_name,
 			} => {
 				if config.has_secret(&machine, &name) && !replace && !merge {
-					bail!("secret already defined.\nUse --replace to override, or --merge to add new parts to existing secret");
+					bail!(
+						"secret already defined.\nUse --replace to override, or --merge to add new parts to existing secret"
+					);
 				}
 
 				let mut out = if merge && !replace {
modifiedcmds/fleet/src/extra_args.rsdiffbeforeafterboth
--- a/cmds/fleet/src/extra_args.rs
+++ b/cmds/fleet/src/extra_args.rs
@@ -1,6 +1,6 @@
 use std::ffi::{OsStr, OsString};
 
-use anyhow::{anyhow, Result};
+use anyhow::{Result, anyhow};
 
 pub fn parse_os(os: &OsStr) -> Result<Vec<OsString>> {
 	Ok(shlex::bytes::split(os.as_encoded_bytes())
modifiedcmds/fleet/src/main.rsdiffbeforeafterboth
--- a/cmds/fleet/src/main.rs
+++ b/cmds/fleet/src/main.rs
@@ -6,27 +6,27 @@
 
 use std::{ffi::OsString, process::ExitCode};
 
-use anyhow::{bail, Result};
+use anyhow::{Result, bail};
 use clap::{CommandFactory, Parser};
 use cmds::{
 	build_systems::{BuildSystems, Deploy},
-	rollback::RollbackSingle,
 	complete::Complete,
 	info::Info,
+	rollback::RollbackSingle,
 	secrets::Secret,
 	tf::Tf,
 };
 use fleet_base::{host::Config, opts::FleetOpts};
-use futures::{future::LocalBoxFuture, stream::FuturesUnordered, TryStreamExt};
+use futures::{TryStreamExt, future::LocalBoxFuture, stream::FuturesUnordered};
 // use host::Config;
 #[cfg(feature = "indicatif")]
 use human_repr::HumanCount;
 #[cfg(feature = "indicatif")]
 use indicatif::{ProgressState, ProgressStyle};
-use tracing::{error, info, info_span, Instrument};
+use tracing::{Instrument, error, info, info_span};
 #[cfg(feature = "indicatif")]
 use tracing_indicatif::IndicatifLayer;
-use tracing_subscriber::{prelude::*, EnvFilter};
+use tracing_subscriber::{EnvFilter, prelude::*};
 
 #[derive(Parser)]
 struct Prefetch {}
modifiedcmds/generator-helper/src/main.rsdiffbeforeafterboth
--- a/cmds/generator-helper/src/main.rs
+++ b/cmds/generator-helper/src/main.rs
@@ -1,21 +1,22 @@
 use std::{
 	env,
 	fs::{File, OpenOptions},
-	io::{self, copy, stdin, stdout, Read, Write},
+	io::{self, Read, Write, copy, stdin, stdout},
 	str::FromStr,
 };
 
 use age::{
+	Encryptor, Recipient,
 	ssh::{ParseRecipientKeyError, Recipient as SshRecipient},
-	Encryptor, Recipient,
 };
-use anyhow::{anyhow, bail, ensure, Context, Result};
+use anyhow::{Context, Result, anyhow, bail, ensure};
 use clap::{Parser, ValueEnum};
 use ed25519_dalek::SecretKey;
 use fleet_shared::SecretData;
 use rand::{
+	RngCore,
 	distr::{Alphanumeric, Distribution, SampleString, Uniform},
-	rng, RngCore,
+	rng,
 };
 
 fn write_output_file(out: &str) -> Result<File> {
@@ -78,7 +79,9 @@
 	let list = match list {
 		Ok(v) => v,
 		Err(env::VarError::NotPresent) => {
-			bail!("gh is only intended to be used from secret generator scripts, but if you really want to use it somewhere else - set GENERATOR_HELPER_IDENTITIES to list of newline-delimited ssh identities");
+			bail!(
+				"gh is only intended to be used from secret generator scripts, but if you really want to use it somewhere else - set GENERATOR_HELPER_IDENTITIES to list of newline-delimited ssh identities"
+			);
 		}
 		Err(e) => bail!("somehow, identities list is not utf-8: {e}"),
 	};
@@ -254,13 +257,7 @@
 					write_private(
 						&recipients,
 						&private,
-						&key[..{
-							if no_embed_public {
-								32
-							} else {
-								64
-							}
-						}],
+						&key[..{ if no_embed_public { 32 } else { 64 } }],
 						encoding,
 					)?;
 				}
modifiedcmds/install-secrets/src/main.rsdiffbeforeafterboth
--- a/cmds/install-secrets/src/main.rs
+++ b/cmds/install-secrets/src/main.rs
@@ -5,20 +5,20 @@
 	iter,
 	os::unix::prelude::PermissionsExt,
 	path::{Path, PathBuf},
-	str::{from_utf8, FromStr},
+	str::{FromStr, from_utf8},
 };
 
 use age::{
-	ssh::{Identity as SshIdentity, Recipient as SshRecipient},
 	Decryptor, Encryptor, Identity, Recipient,
+	ssh::{Identity as SshIdentity, Recipient as SshRecipient},
 };
-use anyhow::{anyhow, bail, ensure, Context, Result};
+use anyhow::{Context, Result, anyhow, bail, ensure};
 use clap::Parser;
 use fleet_shared::SecretData;
-use nix::unistd::{chown, Group, User};
+use nix::unistd::{Group, User, chown};
 use serde::Deserialize;
 use tracing::{error, info, info_span};
-use tracing_subscriber::{filter::LevelFilter, EnvFilter};
+use tracing_subscriber::{EnvFilter, filter::LevelFilter};
 
 #[derive(Parser)]
 #[clap(author)]
modifiedcmds/terraform-provider-fleet/src/main.rsdiffbeforeafterboth
--- a/cmds/terraform-provider-fleet/src/main.rs
+++ b/cmds/terraform-provider-fleet/src/main.rs
@@ -1,3 +1,5 @@
 fn main() {
-	panic!("this is a stub, real provider is in development, I just don't want to keep it in different branch.")
+	panic!(
+		"this is a stub, real provider is in development, I just don't want to keep it in different branch."
+	)
 }
modifiedcrates/better-command/src/handler.rsdiffbeforeafterboth
--- a/crates/better-command/src/handler.rs
+++ b/crates/better-command/src/handler.rs
@@ -7,7 +7,7 @@
 
 use regex::Regex;
 use serde::Deserialize;
-use tracing::{info, info_span, warn, Span};
+use tracing::{Span, info, info_span, warn};
 #[cfg(feature = "indicatif")]
 use tracing_indicatif::span_ext::IndicatifSpanExt as _;
 
@@ -112,9 +112,13 @@
 			match log {
 				NixLog::Msg { msg, raw_msg, .. } => {
 					#[allow(clippy::nonminimal_bool)]
-					if !(msg.starts_with("\u{1b}[35;1mwarning:\u{1b}[0m Git tree '") && msg.ends_with("' is dirty"))
-					&& !msg.starts_with("\u{1b}[35;1mwarning:\u{1b}[0m not writing modified lock file of flake")
-					&& msg != "\u{1b}[35;1mwarning:\u{1b}[0m \u{1b}[31;1merror:\u{1b}[0m SQLite database '\u{1b}[35;1m/nix/var/nix/db/db.sqlite\u{1b}[0m' is busy" {
+					if !(msg.starts_with("\u{1b}[35;1mwarning:\u{1b}[0m Git tree '")
+						&& msg.ends_with("' is dirty"))
+						&& !msg.starts_with(
+							"\u{1b}[35;1mwarning:\u{1b}[0m not writing modified lock file of flake",
+						) && msg
+						!= "\u{1b}[35;1mwarning:\u{1b}[0m \u{1b}[31;1merror:\u{1b}[0m SQLite database '\u{1b}[35;1m/nix/var/nix/db/db.sqlite\u{1b}[0m' is busy"
+					{
 						if let Some(raw_msg) = raw_msg {
 							if !msg.is_empty() {
 								info!(target: "nix", "{}\n{}", raw_msg.trim_end(), msg.trim_end())
@@ -156,8 +160,12 @@
 					id,
 					..
 				} if typ == 100 && fields.len() >= 3 => {
-					if let [LogField::String(drv), LogField::String(from), LogField::String(to), ..] =
-						&fields[..]
+					if let [
+						LogField::String(drv),
+						LogField::String(from),
+						LogField::String(to),
+						..,
+					] = &fields[..]
 					{
 						let mut drv = drv.as_str();
 
@@ -289,8 +297,12 @@
 				}
 				NixLog::Result { fields, id, typ } if typ == 105 && fields.len() >= 4 => {
 					if let Some(span) = self.spans.get(&id) {
-						if let [LogField::Num(done), LogField::Num(expected), LogField::Num(_running), LogField::Num(_failed)] =
-							&fields[..4]
+						if let [
+							LogField::Num(done),
+							LogField::Num(expected),
+							LogField::Num(_running),
+							LogField::Num(_failed),
+						] = &fields[..4]
 						{
 							#[cfg(feature = "indicatif")]
 							{
modifiedcrates/fleet-base/src/command.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/command.rs
+++ b/crates/fleet-base/src/command.rs
@@ -1,6 +1,6 @@
 use std::{ffi::OsStr, pin, process::Stdio, sync::Arc, task::Poll};
 
-use anyhow::{anyhow, Result};
+use anyhow::{Result, anyhow};
 use better_command::{Handler, NixHandler, PlainHandler};
 use futures::StreamExt;
 use itertools::Either;
@@ -68,10 +68,9 @@
 		}
 	}
 	fn new_here(&self, cmd: impl AsRef<OsStr>) -> Self {
-		if let Some(ssh_session) = self.ssh_session.clone() {
-			Self::new_on(self.escalation, cmd, ssh_session)
-		} else {
-			Self::new(self.escalation, cmd)
+		match self.ssh_session.clone() {
+			Some(ssh_session) => Self::new_on(self.escalation, cmd, ssh_session),
+			_ => Self::new(self.escalation, cmd),
 		}
 	}
 
@@ -139,15 +138,18 @@
 		out
 	}
 	fn into_command(self) -> Result<Either<Command, openssh::OwningCommand<Arc<Session>>>> {
-		Ok(if let Some(session) = self.ssh_session.clone() {
-			let cmd = self.translate_env_into_env().into_command_unchecked_local();
-			Either::Right(
-				cmd.over_ssh(session)
-					.map_err(|e| anyhow!("ssh error: {e}"))?,
-			)
-		} else {
-			let cmd = self.into_command_unchecked_local();
-			Either::Left(cmd)
+		Ok(match self.ssh_session.clone() {
+			Some(session) => {
+				let cmd = self.translate_env_into_env().into_command_unchecked_local();
+				Either::Right(
+					cmd.over_ssh(session)
+						.map_err(|e| anyhow!("ssh error: {e}"))?,
+				)
+			}
+			_ => {
+				let cmd = self.into_command_unchecked_local();
+				Either::Left(cmd)
+			}
 		})
 	}
 	pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
modifiedcrates/fleet-base/src/deploy.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/deploy.rs
+++ b/crates/fleet-base/src/deploy.rs
@@ -1,10 +1,10 @@
 use std::{path::PathBuf, time::Duration};
 
-use anyhow::{anyhow, bail, Context as _, Result};
+use anyhow::{Context as _, Result, anyhow, bail};
 use clap::ValueEnum;
 use itertools::Itertools;
 use tokio::time::sleep;
-use tracing::{error, info, info_span, warn, Instrument as _};
+use tracing::{Instrument as _, error, info, info_span, warn};
 
 use crate::host::{Config, ConfigHost, DeployKind, Generation, GenerationStorage};
 
@@ -221,7 +221,9 @@
 					.in_current_span()
 					.await
 				{
-					error!("failed to remove rollback marker. This is bad, as the system will be rolled back by watchdog: {e}")
+					error!(
+						"failed to remove rollback marker. This is bad, as the system will be rolled back by watchdog: {e}"
+					)
 				}
 			}
 			info!("disarming watchdog, just in case");
@@ -233,12 +235,17 @@
 					error!("failed to disarm rollback run: {e}");
 				}
 			}
-		} else if let Err(_e) = host
-			.rm_file("/etc/fleet_rollback_marker", true)
-			.in_current_span()
-			.await
-		{
-			// Marker might not exist, yet better try to remove it.
+		} else {
+			match host
+				.rm_file("/etc/fleet_rollback_marker", true)
+				.in_current_span()
+				.await
+			{
+				Err(_e) => {
+					// Marker might not exist, yet better try to remove it.
+				}
+				_ => {}
+			}
 		}
 	}
 	Ok(())
modifiedcrates/fleet-base/src/fleetdata.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/fleetdata.rs
+++ b/crates/fleet-base/src/fleetdata.rs
@@ -10,7 +10,7 @@
 	distr::{Alphanumeric, SampleString as _},
 	rng,
 };
-use serde::{de::Error, Deserialize, Serialize};
+use serde::{Deserialize, Serialize, de::Error};
 use serde_json::Value;
 
 #[derive(Serialize, Deserialize, Default)]
modifiedcrates/fleet-base/src/host.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/host.rs
+++ b/crates/fleet-base/src/host.rs
@@ -10,14 +10,14 @@
 	sync::{Arc, Mutex, MutexGuard, OnceLock},
 };
 
-use anyhow::{anyhow, bail, ensure, Context, Result};
+use anyhow::{Context, Result, anyhow, bail, ensure};
 use fleet_shared::SecretData;
-use nix_eval::{nix_go, nix_go_json, util::assert_warn, NixSession, Value};
+use nix_eval::{NixSession, Value, nix_go, nix_go_json, util::assert_warn};
 use openssh::SessionBuilder;
 use serde::de::DeserializeOwned;
 use tabled::Tabled;
 use tempfile::NamedTempFile;
-use time::{format_description, UtcDateTime};
+use time::{UtcDateTime, format_description};
 use tracing::warn;
 
 use crate::{
@@ -87,7 +87,9 @@
 			"fleet" => Ok(Self::Fleet),
 			"nixos-install" => Ok(Self::NixosInstall),
 			"nixos-lustrate" => Ok(Self::NixosLustrate),
-			v => bail!("unknown deploy_kind: {v}; expected on of \"upgrade-to-fleet\", \"fleet\", \"nixos-install\", \"nixos-lustrate\""),
+			v => bail!(
+				"unknown deploy_kind: {v}; expected on of \"upgrade-to-fleet\", \"fleet\", \"nixos-install\", \"nixos-lustrate\""
+			),
 		}
 	}
 }
@@ -189,11 +191,11 @@
 			.map(|e| e.trim())
 			.filter(|&l| !l.is_empty())
 			.filter_map(|g| {
-				let gen = parse_generation_line(g);
-				if gen.is_none() {
+				let generation = parse_generation_line(g);
+				if generation.is_none() {
 					warn!("bad generation: {g}");
 				};
-				gen
+				generation
 			})
 			.collect::<Vec<_>>();
 		for ele in generations.iter_mut() {
modifiedcrates/fleet-base/src/keys.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/keys.rs
+++ b/crates/fleet-base/src/keys.rs
@@ -1,7 +1,7 @@
 use std::str::FromStr as _;
 
 use age::Recipient;
-use anyhow::{anyhow, Result};
+use anyhow::{Result, anyhow};
 use futures::{StreamExt as _, TryStreamExt as _};
 use itertools::Itertools as _;
 use tracing::warn;
@@ -39,12 +39,12 @@
 		}
 	}
 	/// Insecure, requires root
-	pub async fn recipient(&self, host: &str) -> anyhow::Result<impl Recipient> {
+	pub async fn recipient(&self, host: &str) -> anyhow::Result<impl Recipient + use<>> {
 		let key = self.key(host).await?;
 		age::ssh::Recipient::from_str(&key).map_err(|e| anyhow!("parse recipient error: {:?}", e))
 	}
 
-	pub async fn recipients(&self, hosts: Vec<String>) -> Result<Vec<impl Recipient>> {
+	pub async fn recipients(&self, hosts: Vec<String>) -> Result<Vec<impl Recipient + use<>>> {
 		let hosts = self.expand_owner_set(hosts).await?;
 		futures::stream::iter(hosts.iter())
 			.then(|m| self.recipient(m.as_ref()))
modifiedcrates/fleet-base/src/lib.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/lib.rs
+++ b/crates/fleet-base/src/lib.rs
@@ -1,6 +1,6 @@
 pub mod command;
+pub mod deploy;
 pub mod fleetdata;
 pub mod host;
 mod keys;
 pub mod opts;
-pub mod deploy;
\ No newline at end of file
modifiedcrates/fleet-base/src/opts.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/opts.rs
+++ b/crates/fleet-base/src/opts.rs
@@ -6,15 +6,15 @@
 	sync::{Arc, Mutex},
 };
 
-use anyhow::{bail, Context, Result};
-use nix_eval::{nix_go, util::assert_warn, NixSessionPool, Value};
+use anyhow::{Context, Result, bail};
+use nix_eval::{NixSessionPool, Value, nix_go, util::assert_warn};
 use nom::{
+	Parser,
 	bytes::complete::take_while1,
 	character::complete::char,
 	combinator::{map, opt},
 	multi::separated_list1,
 	sequence::{preceded, separated_pair},
-	Parser,
 };
 
 use crate::{
@@ -44,7 +44,8 @@
 	let (input, name) = map(
 		take_while1(|v| v != ',' && v != '?' && v != '@'),
 		str::to_owned,
-	).parse_complete(input)
+	)
+	.parse_complete(input)
 	.map_err(err_to_string)?;
 
 	let kw_item = separated_pair(
modifiedcrates/fleet-shared/src/encoding.rsdiffbeforeafterboth
--- a/crates/fleet-shared/src/encoding.rs
+++ b/crates/fleet-shared/src/encoding.rs
@@ -3,8 +3,8 @@
 	str::FromStr,
 };
 
-use base64::engine::{general_purpose::STANDARD_NO_PAD, Engine};
-use serde::{de::Error, Deserialize, Deserializer, Serialize};
+use base64::engine::{Engine, general_purpose::STANDARD_NO_PAD};
+use serde::{Deserialize, Deserializer, Serialize, de::Error};
 use unicode_categories::UnicodeCategories;
 
 #[derive(Debug, PartialEq, Clone)]
modifiedcrates/nix-eval/src/pool.rsdiffbeforeafterboth
--- a/crates/nix-eval/src/pool.rs
+++ b/crates/nix-eval/src/pool.rs
@@ -5,7 +5,7 @@
 
 use r2d2::Pool;
 
-use crate::{session::NixSessionInner, Error, NixSession, Result};
+use crate::{Error, NixSession, Result, session::NixSessionInner};
 
 pub struct NixSessionPool(Pool<NixSessionPoolInner>);
 impl NixSessionPool {
modifiedcrates/nix-eval/src/session.rsdiffbeforeafterboth
before · crates/nix-eval/src/session.rs
1use std::{ffi::OsStr, num::ParseIntError, process::Stdio, sync::Arc};23use better_command::{ClonableHandler, Handler, NixHandler, NoopHandler};4use futures::StreamExt;5use itertools::Itertools as _;6use serde::{de::DeserializeOwned, Deserialize};7use thiserror::Error;8use tokio::{9	io::AsyncWriteExt,10	process::{ChildStderr, ChildStdin, ChildStdout, Command},11	select,12	sync::{mpsc, oneshot, Mutex},13};14use tokio_util::codec::{FramedRead, LinesCodec};15use tracing::{debug, error, warn, Level};1617#[derive(Error, Debug, Clone)]18pub enum Error {19	#[error("failed to create nix repl session: {0}")]20	SessionInit(&'static str),21	#[error("unexpected end of output, nix crashed?")]22	MissingDelimiter,2324	#[error("expression did'nt produce any output")]25	ExpectedOutput,26	#[error("expression produced output, which is unexpected")]27	UnexpectedOutput,2829	#[error("unexpected expression output type")]30	InvalidType,3132	#[error("failed to build attr {attribute}:\n{error}")]33	BuildFailed { attribute: String, error: String },3435	#[error("output: {0}")]36	Json(Arc<serde_json::Error>),37	// int outputs are too specific, and should not be used,38	// thus error is ok to be not informative.39	#[error("int output: {0}")]40	Int(ParseIntError),41	#[error("pool: {0}")]42	Pool(Arc<r2d2::Error>),43	#[error("io: {0}")]44	Io(Arc<std::io::Error>),4546	// TODO: Should be done by wrapper/in different type.47	#[error("at {0}: {1}")]48	InContext(String, Box<Self>),4950	#[error("error: {0}")]51	NixError(String),52}53impl From<r2d2::Error> for Error {54	fn from(value: r2d2::Error) -> Self {55		Self::Pool(Arc::new(value))56	}57}58impl From<std::io::Error> for Error {59	fn from(value: std::io::Error) -> Self {60		Self::Io(Arc::new(value))61	}62}63impl From<serde_json::Error> for Error {64	fn from(value: serde_json::Error) -> Self {65		Self::Json(Arc::new(value))66	}67}68impl Error {69	pub(crate) fn context(self, context: String) -> Self {70		Self::InContext(context, Box::new(self))71	}72}73pub type Result<T, E = Error> = std::result::Result<T, E>;7475enum OutputLine {76	Out(String),77	Err(String),78}79struct OutputHandler {80	rx: mpsc::Receiver<OutputLine>,81	_cancel_handle: oneshot::Receiver<()>,82}83impl OutputHandler {84	fn new(out: ChildStdout, err: ChildStderr) -> Self {85		let mut out = FramedRead::new(out, LinesCodec::new());86		let mut err = FramedRead::new(err, LinesCodec::new());87		let (tx, rx) = mpsc::channel(20);88		let (mut cancelled, _cancel_handle) = oneshot::channel();89		tokio::spawn(async move {90			loop {91				select! {92					// We should receive errors earlier than synchronization93					biased;94					e = err.next() => {95						let Some(Ok(e)) = e else {96							if e.is_some() {97								error!("bad repl stderr: {e:?}");98							}99							continue;100						};101						let _ = tx.send(OutputLine::Err(e)).await;102					}103					o = out.next() => {104						let Some(Ok(o)) = o else {105							if o.is_some() {106								error!("bad repl stdout: {o:?}");107							}108							continue;109						};110						let _ = tx.send(OutputLine::Out(o)).await;111					}112					// Reader doesn't care about stdout, as this is cancelled.113					// Error still might be useful, to process leftover span closures?114					_ = cancelled.closed() => {115						break;116					}117				}118			}119		});120		Self { rx, _cancel_handle }121	}122	async fn next(&mut self) -> Option<OutputLine> {123		self.rx.recv().await124	}125}126127#[must_use]128struct ErrorCollector<'i, H> {129	collected: Vec<String>,130	inner: &'i mut H,131}132impl<'i, H> ErrorCollector<'i, H> {133	fn new(inner: &'i mut H) -> Self {134		Self {135			collected: vec![],136			inner,137		}138	}139}140impl<H> ErrorCollector<'_, H> {141	fn handle_line_inner(&mut self, msg: &str) -> bool {142		let Some(msg) = msg.strip_prefix("@nix ") else {143			return false;144		};145		#[derive(Deserialize)]146		struct ErrorAction {147			action: String,148			level: u32,149			msg: String,150		}151		let Ok(act) = serde_json::from_str::<ErrorAction>(msg) else {152			return false;153		};154		if act.action != "msg" || act.level != 0 {155			return false;156		}157		self.collected.push(act.msg);158		true159	}160	fn finish(self) -> Result<()> {161		// fn dedent(s: String) -> String {162		// 	s.split('\n').filter(|s| !s.trim().is_empty()).map(|v| v.)163		// }164		if !self.collected.is_empty() {165			return Err(Error::NixError(166				self.collected167					.iter()168					.map(|v| {169						if let Some(f) = v.strip_prefix("\u{1b}[31;1merror:\u{1b}[0m ") {170							let v = unindent::unindent(f.trim_start());171							v.trim().to_owned()172						} else {173							v.to_owned()174						}175					})176					.join("\n")177					.to_string(),178			));179		}180		Ok(())181	}182	fn flush(self) {183		for line in self.collected {184			warn!("{line}");185		}186	}187}188impl<H: Handler> Handler for ErrorCollector<'_, H> {189	fn handle_line(&mut self, e: &str) {190		if self.handle_line_inner(e) {191			return;192		}193		self.inner.handle_line(e)194	}195}196197pub struct NixSessionInner {198	full_delimiter: String,199	nix_handler: ClonableHandler<NixHandler>,200	out: OutputHandler,201	stdin: ChildStdin,202	string_wrapping: (String, String),203	number_wrapping: (String, String),204205	executing_command: Arc<Mutex<()>>,206207	next_id: u32,208	pub(crate) free_list: Vec<u32>,209210	pub nix_system: String,211}212213/// Discover inter-message repl delimiter214const REPL_DELIMITER: &str = "\"FLEET_MAGIC_REPL_DELIMITER\"";215/// Discover formatting around strings216const TRAIN_STRING: &str = "\"TRAIN_STRING\"";217/// Discover formatting around numbers218const TRAIN_NUMBER: &str = "13141516";219// Other types of formatting are not discovered, because they are not used, JSON serialization is used instead220// Techically, number training is also not required, because numbers can be converted to string too...221// Eh, I'll remove it later.222223impl NixSessionInner {224	pub(crate) async fn new(225		flake: &OsStr,226		extra_args: impl IntoIterator<Item = &OsStr>,227		nix_system: String,228		fail_fast: bool,229	) -> Result<Self> {230		let mut cmd = Command::new("nix");231		cmd.arg("repl")232			.args(["--option", "pure-eval", "true"])233			.arg(flake)234			.arg("--log-format")235			.arg("internal-json");236		if !fail_fast {237			cmd.arg("--keep-going");238		}239		for arg in extra_args {240			cmd.arg(arg);241		}242		cmd.stdin(Stdio::piped());243		cmd.stdout(Stdio::piped());244		cmd.stderr(Stdio::piped());245		let cmd = cmd.spawn()?;246		let stdout = cmd.stdout.unwrap();247		let stderr = cmd.stderr.unwrap();248		let mut out = OutputHandler::new(stdout, stderr);249		let mut stdin = cmd.stdin.unwrap();250		// Standard repl hello doesn't work with internal-json logger251		stdin.write_all(REPL_DELIMITER.as_bytes()).await?;252		stdin.write_all(b"\n").await?;253		stdin.flush().await?;254		let nix_handler = NixHandler::default();255		let mut full_delimiter = None;256		let mut errors = vec![];257		while let Some(line) = out.next().await {258			let line = match line {259				OutputLine::Out(o) => o,260				OutputLine::Err(_e) => {261					// Handle startup errors, but skip repl hello?262					errors.push(_e);263					continue;264				}265			};266			if line.contains(REPL_DELIMITER) {267				debug!("discovered repl delimiter with added colors: {line}");268				full_delimiter = Some(line.to_owned());269				break;270			}271		}272		let Some(full_delimiter) = full_delimiter else {273			for e in errors {274				error!("{e}");275			}276			return Err(Error::SessionInit("failed to discover delimiter"));277		};278		let mut res = Self {279			full_delimiter,280			nix_handler: ClonableHandler::new(nix_handler),281			out,282			stdin,283			string_wrapping: Default::default(),284			number_wrapping: Default::default(),285286			executing_command: Arc::new(Mutex::new(())),287288			next_id: 0,289			free_list: vec![],290291			nix_system,292		};293		res.train().await?;294		Ok(res)295	}296	async fn train(&mut self) -> Result<()> {297		{298			let full_string = self299				.execute_expression_raw(TRAIN_STRING, &mut NoopHandler)300				.await?;301			let string_offset = full_string.find(TRAIN_STRING).expect("contained");302			let string_prefix = &full_string[..string_offset];303			let string_suffix = &full_string[string_offset + TRAIN_STRING.len()..];304			self.string_wrapping = (string_prefix.to_owned(), string_suffix.to_owned());305		}306		{307			let full_number = self308				.execute_expression_raw(TRAIN_NUMBER, &mut NoopHandler)309				.await?;310			let number_offset = full_number.find(TRAIN_NUMBER).expect("contained");311			let number_prefix = &full_number[..number_offset];312			let number_suffix = &full_number[number_offset + TRAIN_NUMBER.len()..];313			self.number_wrapping = (number_prefix.to_owned(), number_suffix.to_owned());314		}315		Ok(())316	}317	async fn send_command(&mut self, cmd: impl AsRef<[u8]>) -> Result<()> {318		if tracing::enabled!(Level::DEBUG) && cmd.as_ref() != REPL_DELIMITER.as_bytes() {319			let cmd_str = String::from_utf8_lossy(cmd.as_ref());320			tracing::debug!("{cmd_str}");321		};322		self.stdin.write_all(cmd.as_ref()).await?;323		self.stdin.write_all(b"\n").await?;324		Ok(())325	}326	async fn read_until_delimiter(&mut self, err_handler: &mut dyn Handler) -> Result<String> {327		let mut out = String::new();328		while let Some(line) = self.out.next().await {329			let line = match line {330				OutputLine::Out(out) => out,331				OutputLine::Err(err) => {332					err_handler.handle_line(&err);333					continue;334				}335			};336			if line == self.full_delimiter {337				return Ok(out);338			}339			if !out.is_empty() {340				out.push('\n');341			}342			out.push_str(&line);343		}344		Err(Error::MissingDelimiter)345	}346	pub(crate) async fn execute_expression_number(347		&mut self,348		expr: impl AsRef<[u8]>,349	) -> Result<u64> {350		let num = self.number_wrapping.clone();351		let n = self.execute_expression_wrapping(expr, &num).await?;352		n.parse::<u64>().map_err(Error::Int)353	}354	async fn execute_expression_string(&mut self, expr: impl AsRef<[u8]>) -> Result<String> {355		// builtins.toJSON escapes some thing in incorrect way, e.g escaped "$" in "\${" is being outputed as "\$",356		// while this escape should be removed as it is intended for nix itself, not for json output.357		//358		// This regex only allows \$ in the beginning of the string, it is easier to implement correctly.359		// TODO: Add peg parser for nix-produced JSON?..360		let regex = regex::Regex::new(r#"(?<prefix>[: {,\[]\\")\\\$"#).expect("fixup json");361362		let num = self.string_wrapping.clone();363		let n = self.execute_expression_wrapping(expr, &num).await?;364		let n = regex.replace_all(&n, "$prefix$$");365		let str: String = serde_json::from_str(&n)?;366		Ok(str)367	}368	pub(crate) async fn execute_expression_to_json<V: DeserializeOwned>(369		&mut self,370		expr: impl AsRef<[u8]>,371	) -> Result<V> {372		let mut fexpr = b"builtins.toJSON (".to_vec();373		fexpr.extend_from_slice(expr.as_ref());374		fexpr.push(b')');375376		Ok(serde_json::from_str(377			&self.execute_expression_string(fexpr).await?,378		)?)379	}380	async fn execute_expression_wrapping(381		&mut self,382		expr: impl AsRef<[u8]>,383		wrapping: &(String, String),384	) -> Result<String> {385		let mut nix_handler = self.nix_handler.clone();386		let mut collected = ErrorCollector::new(&mut nix_handler);387		let res = self.execute_expression_raw(expr, &mut collected).await?;388		if res.is_empty() {389			collected.finish()?;390			return Err(Error::ExpectedOutput);391		} else {392			collected.flush()393		};394		let Some(res) = res.strip_prefix(&wrapping.0) else {395			return Err(Error::InvalidType);396		};397		let Some(res) = res.strip_suffix(&wrapping.1) else {398			return Err(Error::InvalidType);399		};400		Ok(res.to_owned())401	}402	async fn execute_expression_empty(&mut self, expr: impl AsRef<[u8]>) -> Result<()> {403		let mut nix_handler = self.nix_handler.clone();404		let mut collected = ErrorCollector::new(&mut nix_handler);405		let v = self.execute_expression_raw(expr, &mut collected).await?;406		collected.finish()?;407		if !v.is_empty() {408			return Err(Error::UnexpectedOutput);409		}410		Ok(())411	}412	pub(crate) async fn execute_expression_raw(413		&mut self,414		expr: impl AsRef<[u8]>,415		err_handler: &mut dyn Handler,416	) -> Result<String> {417		// Prevent two commands from being executed in parallel, messing with each other.418		let _lock = self.executing_command.clone();419		let _guard = _lock.lock().await;420421		self.send_command(expr).await?;422		// It will be echoed423		self.send_command(REPL_DELIMITER).await?;424		self.read_until_delimiter(err_handler).await425	}426	pub(crate) async fn execute_assign(&mut self, expr: impl AsRef<str>) -> Result<u32> {427		let id = self.allocate_id();428		self.execute_expression_empty(format!("sess_field_{id} = {}", expr.as_ref()))429			.await?;430		Ok(id)431	}432433	/// Id should be immediately used434	fn allocate_id(&mut self) -> u32 {435		if let Some(free) = self.free_list.pop() {436			free437		} else {438			let v = self.next_id;439			self.next_id += 1;440			v441		}442	}443	// Nix has no way to deallocate variable, yet GC will correct everything not reachable.444	// async fn free_id(&mut self, id: u32) -> Result<()> {445	// 	self.execute_expression_empty(format!("sess_field_{id} = null"))446	// 		.await?;447	// 	self.free_list.push(id);448	// 	Ok(())449	// }450}
after · crates/nix-eval/src/session.rs
1use std::{ffi::OsStr, num::ParseIntError, process::Stdio, sync::Arc};23use better_command::{ClonableHandler, Handler, NixHandler, NoopHandler};4use futures::StreamExt;5use itertools::Itertools as _;6use serde::{Deserialize, de::DeserializeOwned};7use thiserror::Error;8use tokio::{9	io::AsyncWriteExt,10	process::{ChildStderr, ChildStdin, ChildStdout, Command},11	select,12	sync::{Mutex, mpsc, oneshot},13};14use tokio_util::codec::{FramedRead, LinesCodec};15use tracing::{Level, debug, error, warn};1617#[derive(Error, Debug, Clone)]18pub enum Error {19	#[error("failed to create nix repl session: {0}")]20	SessionInit(&'static str),21	#[error("unexpected end of output, nix crashed?")]22	MissingDelimiter,2324	#[error("expression did'nt produce any output")]25	ExpectedOutput,26	#[error("expression produced output, which is unexpected")]27	UnexpectedOutput,2829	#[error("unexpected expression output type")]30	InvalidType,3132	#[error("failed to build attr {attribute}:\n{error}")]33	BuildFailed { attribute: String, error: String },3435	#[error("output: {0}")]36	Json(Arc<serde_json::Error>),37	// int outputs are too specific, and should not be used,38	// thus error is ok to be not informative.39	#[error("int output: {0}")]40	Int(ParseIntError),41	#[error("pool: {0}")]42	Pool(Arc<r2d2::Error>),43	#[error("io: {0}")]44	Io(Arc<std::io::Error>),4546	// TODO: Should be done by wrapper/in different type.47	#[error("at {0}: {1}")]48	InContext(String, Box<Self>),4950	#[error("error: {0}")]51	NixError(String),52}53impl From<r2d2::Error> for Error {54	fn from(value: r2d2::Error) -> Self {55		Self::Pool(Arc::new(value))56	}57}58impl From<std::io::Error> for Error {59	fn from(value: std::io::Error) -> Self {60		Self::Io(Arc::new(value))61	}62}63impl From<serde_json::Error> for Error {64	fn from(value: serde_json::Error) -> Self {65		Self::Json(Arc::new(value))66	}67}68impl Error {69	pub(crate) fn context(self, context: String) -> Self {70		Self::InContext(context, Box::new(self))71	}72}73pub type Result<T, E = Error> = std::result::Result<T, E>;7475enum OutputLine {76	Out(String),77	Err(String),78}79struct OutputHandler {80	rx: mpsc::Receiver<OutputLine>,81	_cancel_handle: oneshot::Receiver<()>,82}83impl OutputHandler {84	fn new(out: ChildStdout, err: ChildStderr) -> Self {85		let mut out = FramedRead::new(out, LinesCodec::new());86		let mut err = FramedRead::new(err, LinesCodec::new());87		let (tx, rx) = mpsc::channel(20);88		let (mut cancelled, _cancel_handle) = oneshot::channel();89		tokio::spawn(async move {90			loop {91				select! {92					// We should receive errors earlier than synchronization93					biased;94					e = err.next() => {95						let Some(Ok(e)) = e else {96							if e.is_some() {97								error!("bad repl stderr: {e:?}");98							}99							continue;100						};101						let _ = tx.send(OutputLine::Err(e)).await;102					}103					o = out.next() => {104						let Some(Ok(o)) = o else {105							if o.is_some() {106								error!("bad repl stdout: {o:?}");107							}108							continue;109						};110						let _ = tx.send(OutputLine::Out(o)).await;111					}112					// Reader doesn't care about stdout, as this is cancelled.113					// Error still might be useful, to process leftover span closures?114					_ = cancelled.closed() => {115						break;116					}117				}118			}119		});120		Self { rx, _cancel_handle }121	}122	async fn next(&mut self) -> Option<OutputLine> {123		self.rx.recv().await124	}125}126127#[must_use]128struct ErrorCollector<'i, H> {129	collected: Vec<String>,130	inner: &'i mut H,131}132impl<'i, H> ErrorCollector<'i, H> {133	fn new(inner: &'i mut H) -> Self {134		Self {135			collected: vec![],136			inner,137		}138	}139}140impl<H> ErrorCollector<'_, H> {141	fn handle_line_inner(&mut self, msg: &str) -> bool {142		let Some(msg) = msg.strip_prefix("@nix ") else {143			return false;144		};145		#[derive(Deserialize)]146		struct ErrorAction {147			action: String,148			level: u32,149			msg: String,150		}151		let Ok(act) = serde_json::from_str::<ErrorAction>(msg) else {152			return false;153		};154		if act.action != "msg" || act.level != 0 {155			return false;156		}157		self.collected.push(act.msg);158		true159	}160	fn finish(self) -> Result<()> {161		// fn dedent(s: String) -> String {162		// 	s.split('\n').filter(|s| !s.trim().is_empty()).map(|v| v.)163		// }164		if !self.collected.is_empty() {165			return Err(Error::NixError(166				self.collected167					.iter()168					.map(|v| {169						if let Some(f) = v.strip_prefix("\u{1b}[31;1merror:\u{1b}[0m ") {170							let v = unindent::unindent(f.trim_start());171							v.trim().to_owned()172						} else {173							v.to_owned()174						}175					})176					.join("\n")177					.to_string(),178			));179		}180		Ok(())181	}182	fn flush(self) {183		for line in self.collected {184			warn!("{line}");185		}186	}187}188impl<H: Handler> Handler for ErrorCollector<'_, H> {189	fn handle_line(&mut self, e: &str) {190		if self.handle_line_inner(e) {191			return;192		}193		self.inner.handle_line(e)194	}195}196197pub struct NixSessionInner {198	full_delimiter: String,199	nix_handler: ClonableHandler<NixHandler>,200	out: OutputHandler,201	stdin: ChildStdin,202	string_wrapping: (String, String),203	number_wrapping: (String, String),204205	executing_command: Arc<Mutex<()>>,206207	next_id: u32,208	pub(crate) free_list: Vec<u32>,209210	pub nix_system: String,211}212213/// Discover inter-message repl delimiter214const REPL_DELIMITER: &str = "\"FLEET_MAGIC_REPL_DELIMITER\"";215/// Discover formatting around strings216const TRAIN_STRING: &str = "\"TRAIN_STRING\"";217/// Discover formatting around numbers218const TRAIN_NUMBER: &str = "13141516";219// Other types of formatting are not discovered, because they are not used, JSON serialization is used instead220// Techically, number training is also not required, because numbers can be converted to string too...221// Eh, I'll remove it later.222223impl NixSessionInner {224	pub(crate) async fn new(225		flake: &OsStr,226		extra_args: impl IntoIterator<Item = &OsStr>,227		nix_system: String,228		fail_fast: bool,229	) -> Result<Self> {230		let mut cmd = Command::new("nix");231		cmd.arg("repl")232			.args(["--option", "pure-eval", "true"])233			.arg(flake)234			.arg("--log-format")235			.arg("internal-json");236		if !fail_fast {237			cmd.arg("--keep-going");238		}239		for arg in extra_args {240			cmd.arg(arg);241		}242		cmd.stdin(Stdio::piped());243		cmd.stdout(Stdio::piped());244		cmd.stderr(Stdio::piped());245		let cmd = cmd.spawn()?;246		let stdout = cmd.stdout.unwrap();247		let stderr = cmd.stderr.unwrap();248		let mut out = OutputHandler::new(stdout, stderr);249		let mut stdin = cmd.stdin.unwrap();250		// Standard repl hello doesn't work with internal-json logger251		stdin.write_all(REPL_DELIMITER.as_bytes()).await?;252		stdin.write_all(b"\n").await?;253		stdin.flush().await?;254		let nix_handler = NixHandler::default();255		let mut full_delimiter = None;256		let mut errors = vec![];257		while let Some(line) = out.next().await {258			let line = match line {259				OutputLine::Out(o) => o,260				OutputLine::Err(_e) => {261					// Handle startup errors, but skip repl hello?262					errors.push(_e);263					continue;264				}265			};266			if line.contains(REPL_DELIMITER) {267				debug!("discovered repl delimiter with added colors: {line}");268				full_delimiter = Some(line.to_owned());269				break;270			}271		}272		let Some(full_delimiter) = full_delimiter else {273			for e in errors {274				error!("{e}");275			}276			return Err(Error::SessionInit("failed to discover delimiter"));277		};278		let mut res = Self {279			full_delimiter,280			nix_handler: ClonableHandler::new(nix_handler),281			out,282			stdin,283			string_wrapping: Default::default(),284			number_wrapping: Default::default(),285286			executing_command: Arc::new(Mutex::new(())),287288			next_id: 0,289			free_list: vec![],290291			nix_system,292		};293		res.train().await?;294		Ok(res)295	}296	async fn train(&mut self) -> Result<()> {297		{298			let full_string = self299				.execute_expression_raw(TRAIN_STRING, &mut NoopHandler)300				.await?;301			let string_offset = full_string.find(TRAIN_STRING).expect("contained");302			let string_prefix = &full_string[..string_offset];303			let string_suffix = &full_string[string_offset + TRAIN_STRING.len()..];304			self.string_wrapping = (string_prefix.to_owned(), string_suffix.to_owned());305		}306		{307			let full_number = self308				.execute_expression_raw(TRAIN_NUMBER, &mut NoopHandler)309				.await?;310			let number_offset = full_number.find(TRAIN_NUMBER).expect("contained");311			let number_prefix = &full_number[..number_offset];312			let number_suffix = &full_number[number_offset + TRAIN_NUMBER.len()..];313			self.number_wrapping = (number_prefix.to_owned(), number_suffix.to_owned());314		}315		Ok(())316	}317	async fn send_command(&mut self, cmd: impl AsRef<[u8]>) -> Result<()> {318		if tracing::enabled!(Level::DEBUG) && cmd.as_ref() != REPL_DELIMITER.as_bytes() {319			let cmd_str = String::from_utf8_lossy(cmd.as_ref());320			tracing::debug!("{cmd_str}");321		};322		self.stdin.write_all(cmd.as_ref()).await?;323		self.stdin.write_all(b"\n").await?;324		Ok(())325	}326	async fn read_until_delimiter(&mut self, err_handler: &mut dyn Handler) -> Result<String> {327		let mut out = String::new();328		while let Some(line) = self.out.next().await {329			let line = match line {330				OutputLine::Out(out) => out,331				OutputLine::Err(err) => {332					err_handler.handle_line(&err);333					continue;334				}335			};336			if line == self.full_delimiter {337				return Ok(out);338			}339			if !out.is_empty() {340				out.push('\n');341			}342			out.push_str(&line);343		}344		Err(Error::MissingDelimiter)345	}346	pub(crate) async fn execute_expression_number(347		&mut self,348		expr: impl AsRef<[u8]>,349	) -> Result<u64> {350		let num = self.number_wrapping.clone();351		let n = self.execute_expression_wrapping(expr, &num).await?;352		n.parse::<u64>().map_err(Error::Int)353	}354	async fn execute_expression_string(&mut self, expr: impl AsRef<[u8]>) -> Result<String> {355		// builtins.toJSON escapes some thing in incorrect way, e.g escaped "$" in "\${" is being outputed as "\$",356		// while this escape should be removed as it is intended for nix itself, not for json output.357		//358		// This regex only allows \$ in the beginning of the string, it is easier to implement correctly.359		// TODO: Add peg parser for nix-produced JSON?..360		let regex = regex::Regex::new(r#"(?<prefix>[: {,\[]\\")\\\$"#).expect("fixup json");361362		let num = self.string_wrapping.clone();363		let n = self.execute_expression_wrapping(expr, &num).await?;364		let n = regex.replace_all(&n, "$prefix$$");365		let str: String = serde_json::from_str(&n)?;366		Ok(str)367	}368	pub(crate) async fn execute_expression_to_json<V: DeserializeOwned>(369		&mut self,370		expr: impl AsRef<[u8]>,371	) -> Result<V> {372		let mut fexpr = b"builtins.toJSON (".to_vec();373		fexpr.extend_from_slice(expr.as_ref());374		fexpr.push(b')');375376		Ok(serde_json::from_str(377			&self.execute_expression_string(fexpr).await?,378		)?)379	}380	async fn execute_expression_wrapping(381		&mut self,382		expr: impl AsRef<[u8]>,383		wrapping: &(String, String),384	) -> Result<String> {385		let mut nix_handler = self.nix_handler.clone();386		let mut collected = ErrorCollector::new(&mut nix_handler);387		let res = self.execute_expression_raw(expr, &mut collected).await?;388		if res.is_empty() {389			collected.finish()?;390			return Err(Error::ExpectedOutput);391		} else {392			collected.flush()393		};394		let Some(res) = res.strip_prefix(&wrapping.0) else {395			return Err(Error::InvalidType);396		};397		let Some(res) = res.strip_suffix(&wrapping.1) else {398			return Err(Error::InvalidType);399		};400		Ok(res.to_owned())401	}402	async fn execute_expression_empty(&mut self, expr: impl AsRef<[u8]>) -> Result<()> {403		let mut nix_handler = self.nix_handler.clone();404		let mut collected = ErrorCollector::new(&mut nix_handler);405		let v = self.execute_expression_raw(expr, &mut collected).await?;406		collected.finish()?;407		if !v.is_empty() {408			return Err(Error::UnexpectedOutput);409		}410		Ok(())411	}412	pub(crate) async fn execute_expression_raw(413		&mut self,414		expr: impl AsRef<[u8]>,415		err_handler: &mut dyn Handler,416	) -> Result<String> {417		// Prevent two commands from being executed in parallel, messing with each other.418		let _lock = self.executing_command.clone();419		let _guard = _lock.lock().await;420421		self.send_command(expr).await?;422		// It will be echoed423		self.send_command(REPL_DELIMITER).await?;424		self.read_until_delimiter(err_handler).await425	}426	pub(crate) async fn execute_assign(&mut self, expr: impl AsRef<str>) -> Result<u32> {427		let id = self.allocate_id();428		self.execute_expression_empty(format!("sess_field_{id} = {}", expr.as_ref()))429			.await?;430		Ok(id)431	}432433	/// Id should be immediately used434	fn allocate_id(&mut self) -> u32 {435		if let Some(free) = self.free_list.pop() {436			free437		} else {438			let v = self.next_id;439			self.next_id += 1;440			v441		}442	}443	// Nix has no way to deallocate variable, yet GC will correct everything not reachable.444	// async fn free_id(&mut self, id: u32) -> Result<()> {445	// 	self.execute_expression_empty(format!("sess_field_{id} = null"))446	// 		.await?;447	// 	self.free_list.push(id);448	// 	Ok(())449	// }450}
modifiedcrates/nix-eval/src/util.rsdiffbeforeafterboth
--- a/crates/nix-eval/src/util.rs
+++ b/crates/nix-eval/src/util.rs
@@ -3,7 +3,7 @@
 use anyhow::bail;
 use tracing::{debug, warn};
 
-use crate::{nix_go_json, Value};
+use crate::{Value, nix_go_json};
 
 #[tracing::instrument(level = "info", skip(val))]
 pub async fn assert_warn(action: &str, val: &Value) -> anyhow::Result<()> {
modifiedcrates/nix-eval/src/value.rsdiffbeforeafterboth
--- a/crates/nix-eval/src/value.rs
+++ b/crates/nix-eval/src/value.rs
@@ -1,9 +1,9 @@
 use std::{collections::HashMap, fmt, path::PathBuf, sync::Arc};
 
 use better_command::NixHandler;
-use serde::{de::DeserializeOwned, Serialize};
+use serde::{Serialize, de::DeserializeOwned};
 
-use crate::{macros::NixExprBuilder, nix_go, Error, NixBuildBatch, NixSession, Result};
+use crate::{Error, NixBuildBatch, NixSession, Result, macros::NixExprBuilder, nix_go};
 
 #[derive(Clone)]
 pub enum Index {
deletedcrates/nix-native-eval/Cargo.tomldiffbeforeafterboth
--- a/crates/nix-native-eval/Cargo.toml
+++ /dev/null
@@ -1,10 +0,0 @@
-[package]
-name = "nix-native-eval"
-version.workspace = true
-edition.workspace = true
-rust-version.workspace = true
-
-[dependencies]
-anyhow.workspace = true
-
-nixrs = { git = "https://github.com/Anillc/nixrs", version = "0.1.0" }
deletedcrates/nix-native-eval/src/lib.rsdiffbeforeafterboth
--- a/crates/nix-native-eval/src/lib.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-use anyhow::Result;
-use nixrs::{State, Store};
-
-pub fn init() -> Result<()> {
-	nixrs::init()?;
-	let store = Store::new("daemon")?;
-	let state = State::new(store)?;
-	let _ = state;
-
-	Ok(())
-}
modifiedcrates/nixlike/fuzz/Cargo.tomldiffbeforeafterboth
--- a/crates/nixlike/fuzz/Cargo.toml
+++ b/crates/nixlike/fuzz/Cargo.toml
@@ -4,7 +4,7 @@
 version = "0.0.0"
 authors = ["Automatically generated"]
 publish = false
-edition = "2021"
+edition = "2024"
 
 [package.metadata]
 cargo-fuzz = true
modifiedcrates/nixlike/src/de_impl.rsdiffbeforeafterboth
--- a/crates/nixlike/src/de_impl.rs
+++ b/crates/nixlike/src/de_impl.rs
@@ -2,8 +2,8 @@
 
 use linked_hash_map::LinkedHashMap;
 use serde::{
+	Deserializer,
 	de::{self, MapAccess, SeqAccess},
-	Deserializer,
 };
 
 use crate::{Error, Value};
@@ -28,11 +28,12 @@
 	where
 		K: de::DeserializeSeed<'de>,
 	{
-		if let Some((k, v)) = self.iter.next() {
-			let _ = self.value.insert(v);
-			Ok(Some(seed.deserialize(Value::String(k))?))
-		} else {
-			Ok(None)
+		match self.iter.next() {
+			Some((k, v)) => {
+				let _ = self.value.insert(v);
+				Ok(Some(seed.deserialize(Value::String(k))?))
+			}
+			_ => Ok(None),
 		}
 	}
 
@@ -62,10 +63,9 @@
 	where
 		T: de::DeserializeSeed<'de>,
 	{
-		if let Some(v) = self.iter.next() {
-			Ok(Some(seed.deserialize(v)?))
-		} else {
-			Ok(None)
+		match self.iter.next() {
+			Some(v) => Ok(Some(seed.deserialize(v)?)),
+			_ => Ok(None),
 		}
 	}
 }
modifiedcrates/nixlike/src/se_impl.rsdiffbeforeafterboth
--- a/crates/nixlike/src/se_impl.rs
+++ b/crates/nixlike/src/se_impl.rs
@@ -2,11 +2,11 @@
 
 use linked_hash_map::LinkedHashMap;
 use serde::{
+	Serializer,
 	ser::{
 		self, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple,
 		SerializeTupleStruct, SerializeTupleVariant,
 	},
-	Serializer,
 };
 
 use crate::{Error, Value};
@@ -90,9 +90,7 @@
 
 	fn end(self) -> Result<Self::Ok, Self::Error> {
 		Ok(Value::Object(
-			vec![(self.0, Value::Array(self.1 .0))]
-				.into_iter()
-				.collect(),
+			vec![(self.0, Value::Array(self.1.0))].into_iter().collect(),
 		))
 	}
 }
modifiedflake.lockdiffbeforeafterboth
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
   "nodes": {
     "crane": {
       "locked": {
-        "lastModified": 1739936662,
-        "narHash": "sha256-x4syUjNUuRblR07nDPeLDP7DpphaBVbUaSoeZkFbGSk=",
+        "lastModified": 1750266157,
+        "narHash": "sha256-tL42YoNg9y30u7zAqtoGDNdTyXTi8EALDeCB13FtbQA=",
         "owner": "ipetkov",
         "repo": "crane",
-        "rev": "19de14aaeb869287647d9461cbd389187d8ecdb7",
+        "rev": "e37c943371b73ed87faf33f7583860f81f1d5a48",
         "type": "github"
       },
       "original": {
@@ -22,11 +22,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1738453229,
-        "narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=",
+        "lastModified": 1749398372,
+        "narHash": "sha256-tYBdgS56eXYaWVW3fsnPQ/nFlgWi/Z2Ymhyu21zVM98=",
         "owner": "hercules-ci",
         "repo": "flake-parts",
-        "rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd",
+        "rev": "9305fe4e5c2a6fcf5ba6a3ff155720fbe4076569",
         "type": "github"
       },
       "original": {
@@ -37,16 +37,16 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1740339700,
-        "narHash": "sha256-cbrw7EgQhcdFnu6iS3vane53bEagZQy/xyIkDWpCgVE=",
+        "lastModified": 1750895632,
+        "narHash": "sha256-EPZWiRmaSTxoBArK5dQyRlSNVLXiBt2hmsYIPgMf3zk=",
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "04ef94c4c1582fd485bbfdb8c4a8ba250e359195",
+        "rev": "6ac57ce7fee0d80226095a57ccb7519855ad7c5e",
         "type": "github"
       },
       "original": {
         "owner": "nixos",
-        "ref": "release-24.11",
+        "ref": "release-25.05",
         "repo": "nixpkgs",
         "type": "github"
       }
@@ -68,11 +68,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1740277845,
-        "narHash": "sha256-NNU0CdiaSbAeZ8tpDG4aFi9qtcdlItRvk8Xns9oBrVU=",
+        "lastModified": 1750819193,
+        "narHash": "sha256-XvkupGPZqD54HuKhN/2WhbKjAHeTl1UEnWspzUzRFfA=",
         "owner": "oxalica",
         "repo": "rust-overlay",
-        "rev": "f933070c29f9c1c5457447a51903f27f76ebb519",
+        "rev": "1ba3b9c59b68a4b00156827ad46393127b51b808",
         "type": "github"
       },
       "original": {
@@ -103,11 +103,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1744961264,
-        "narHash": "sha256-aRmUh0AMwcbdjJHnytg1e5h5ECcaWtIFQa6d9gI85AI=",
+        "lastModified": 1749194973,
+        "narHash": "sha256-eEy8cuS0mZ2j/r/FE0/LYBSBcIs/MKOIVakwHVuqTfk=",
         "owner": "numtide",
         "repo": "treefmt-nix",
-        "rev": "8d404a69efe76146368885110f29a2ca3700bee6",
+        "rev": "a05be418a1af1198ca0f63facb13c985db4cb3c5",
         "type": "github"
       },
       "original": {
modifiedflake.nixdiffbeforeafterboth
--- a/flake.nix
+++ b/flake.nix
@@ -2,7 +2,7 @@
   description = "NixOS cluster configuration management";
 
   inputs = {
-    nixpkgs.url = "github:nixos/nixpkgs/release-24.11";
+    nixpkgs.url = "github:nixos/nixpkgs/release-25.05";
     rust-overlay = {
       url = "github:oxalica/rust-overlay";
       inputs.nixpkgs.follows = "nixpkgs";
@@ -156,7 +156,7 @@
                 bacon
                 nil
                 rustPlatform.bindgenHook
-                nixVersions.nix_2_22
+                # nixVersions.nix_2_22
               ];
               environment.PROTOC = "${pkgs.protobuf}/bin/protoc";
             };
modifiedrust-toolchain.tomldiffbeforeafterboth
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,3 +1,3 @@
 [toolchain]
-channel = "1.85.0"
+channel = "1.86.0"
 components = ["rustfmt", "clippy", "rust-analyzer", "rust-src"]
modifiedrustfmt.tomldiffbeforeafterboth
--- a/rustfmt.toml
+++ b/rustfmt.toml
@@ -1,3 +1,3 @@
 hard_tabs = true
-imports_granularity = "Crate"
-group_imports = "StdExternalCrate"
+# imports_granularity = "Crate"
+# group_imports = "StdExternalCrate"