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
before · cmds/fleet/src/cmds/secrets/mod.rs
1use std::{2	collections::{BTreeMap, BTreeSet, HashSet},3	io::{self, stdin, stdout, Read, Write},4	path::PathBuf,5};67use age::Recipient;8use anyhow::{anyhow, bail, ensure, Context, Result};9use chrono::{DateTime, Utc};10use clap::Parser;11use fleet_base::{12	fleetdata::{encrypt_secret_data, FleetSecret, FleetSecretPart, FleetSharedSecret},13	host::Config,14	opts::FleetOpts,15};16use fleet_shared::SecretData;17use nix_eval::{nix_go, nix_go_json, NixBuildBatch, Value};18use owo_colors::OwoColorize;19use serde::Deserialize;20use tabled::{Table, Tabled};21use tokio::fs::read;22use tracing::{error, info, info_span, warn, Instrument};2324#[derive(Parser)]25pub enum Secret {26	/// Force load host keys for all defined hosts27	ForceKeys,28	/// Add secret, data should be provided in stdin29	AddShared {30		/// Secret name31		name: String,32		/// Secret owners33		#[clap(long, short)]34		machines: Vec<String>,35		/// Override secret if already present36		#[clap(long)]37		force: bool,38		/// Secret public part39		#[clap(long)]40		public: Option<String>,41		/// Load public part from specified file42		#[clap(long)]43		public_file: Option<PathBuf>,4445		/// Create a notification on secret expiration46		#[clap(long)]47		expires_at: Option<DateTime<Utc>>,4849		/// Secret with this name already exists, override its value while keeping the same owners.50		#[clap(long)]51		re_add: bool,5253		/// How to name public secret part54		#[clap(long, short = 'p', default_value = "public")]55		public_part: String,56		/// How to name private secret part57		#[clap(short = 's', long, default_value = "secret")]58		part: String,59	},60	/// Add secret, data should be provided in stdin61	Add {62		/// Secret name63		name: String,64		/// Secret owner65		#[clap(short = 'm', long)]66		machine: String,67		/// Replace secret if already present68		#[clap(long)]69		replace: bool,70		/// Add new parts to existing secret71		#[clap(long)]72		merge: bool,73		/// Secret public part74		#[clap(long)]75		public: Option<String>,76		/// Load public part from specified file77		#[clap(long)]78		public_file: Option<PathBuf>,7980		/// How to name public secret part81		#[clap(short = 'p', long, default_value = "public")]82		public_part: String,83		/// How to name private secret part84		#[clap(short = 's', long, default_value = "secret")]85		part: String,86	},87	/// Read secret from remote host, requires sudo on said host88	Read {89		name: String,90		#[clap(short = 'm', long)]91		machine: String,9293		/// Which private secret part to read94		#[clap(short = 'p', long, default_value = "secret")]95		part: String,96	},97	/// Read secret from remote host, requires sudo on said host98	ReadShared {99		name: String,100		/// Which private secret part to read101		#[clap(short = 'p', long, default_value = "secret")]102		part: String,103		/// Which host should we use to decrypt, in case if reencryption is required, without104		/// regeneration105		#[clap(long)]106		prefer_identities: Vec<String>,107	},108	UpdateShared {109		name: String,110111		#[clap(short = 'm', long)]112		machine: Option<Vec<String>>,113114		#[clap(long)]115		add_machine: Vec<String>,116		#[clap(long)]117		remove_machine: Vec<String>,118119		/// Which host should we use to decrypt120		#[clap(long)]121		prefer_identities: Vec<String>,122	},123	Regenerate {124		/// Which host should we use to decrypt, in case if reencryption is required, without125		/// regeneration126		#[clap(long)]127		prefer_identities: Vec<String>,128		/// Only regenerate shared secrets129		#[clap(long)]130		skip_hosts: bool,131	},132	List {},133	Edit {134		name: String,135		#[clap(short = 'm', long)]136		machine: String,137138		#[clap(long)]139		add: bool,140141		/// Which private secret part to read142		#[clap(short = 'p', long, default_value = "secret")]143		part: String,144	},145}146147fn secret_needs_regeneration(148	secret: &FleetSecret,149	expected_generation_data: &serde_json::Value,150) -> bool {151	let data_is_expected = secret.generation_data == *expected_generation_data;152	// TODO: Leeway?153	let expired = secret154		.expires_at155		.map(|expiration| expiration < Utc::now())156		.unwrap_or(false);157	expired || !data_is_expected158}159160#[allow(clippy::too_many_arguments)]161#[tracing::instrument(skip(config, secret, field, prefer_identities, batch))]162async fn maybe_regenerate_shared_secret(163	secret_name: &str,164	config: &Config,165	mut secret: FleetSharedSecret,166	field: Value,167	expected_owners: &[String],168	expected_generation_data: serde_json::Value,169	prefer_identities: &[String],170	batch: Option<NixBuildBatch>,171) -> Result<FleetSharedSecret> {172	let original_set = secret.owners.clone();173174	let set = original_set.iter().collect::<BTreeSet<_>>();175	let expected_set = expected_owners.iter().collect::<BTreeSet<_>>();176177	let regeneration_required =178		secret_needs_regeneration(&secret.secret, &expected_generation_data);179180	if set == expected_set && !regeneration_required {181		info!("no need to update owner list, it is already correct");182		return Ok(secret);183	}184185	let should_regenerate = if regeneration_required {186		info!("secret has its generation data changed, regeneration is required");187		true188	} else if set.difference(&expected_set).next().is_some() {189		// TODO: Remove this warning for revokable secrets.190		warn!("host was removed from secret owners, but until this host rebuild, the secret will still be stored on it.");191		nix_go_json!(field.regenerateOnOwnerRemoved)192	} else if expected_set.difference(&set).next().is_some() {193		nix_go_json!(field.regenerateOnOwnerAdded)194	} else {195		false196	};197198	if should_regenerate {199		info!("secret needs to be regenerated");200		let generated = generate_shared(201			config,202			secret_name,203			field,204			expected_owners.to_vec(),205			expected_generation_data,206			batch,207		)208		.await?;209		Ok(generated)210	} else {211		drop(batch);212		let identity_holder = if !prefer_identities.is_empty() {213			prefer_identities214				.iter()215				.find(|i| original_set.iter().any(|s| s == *i))216		} else {217			secret.owners.first()218		};219		let Some(identity_holder) = identity_holder else {220			bail!("no available holder found");221		};222223		for (part_name, part) in secret.secret.parts.iter_mut() {224			let _span = info_span!("part reencryption", part_name);225			if !part.raw.encrypted {226				continue;227			}228			let host = config.host(identity_holder).await?;229			let encrypted = host230				.reencrypt(part.raw.clone(), expected_owners.to_vec())231				.await?;232			part.raw = encrypted;233		}234235		secret.owners = expected_owners.to_vec();236		Ok(secret)237	}238}239240#[derive(Deserialize)]241#[serde(rename_all = "camelCase")]242enum GeneratorKind {243	Impure,244	Pure,245}246247async fn generate_pure(248	_config: &Config,249	_display_name: &str,250	_secret: Value,251	_default_generator: Value,252	_owners: &[String],253) -> Result<FleetSecret> {254	bail!("pure generators are broken for now")255}256async fn generate_impure(257	config: &Config,258	_display_name: &str,259	secret: Value,260	default_generator: Value,261	expected_owners: &[String],262	expected_generation_data: serde_json::Value,263	batch: Option<NixBuildBatch>,264) -> Result<FleetSecret> {265	let generator = nix_go!(secret.generator);266	let on: Option<String> = nix_go_json!(default_generator.impureOn);267268	let nixpkgs = &config.nixpkgs;269270	let host = if let Some(on) = &on {271		config.host(on).await?272	} else {273		config.local_host()274	};275	let on_pkgs = host.pkgs().await?;276	let mk_secret_generators = nix_go!(on_pkgs.mkSecretGenerators);277278	let mut recipients = Vec::new();279	for owner in expected_owners {280		let key = config.key(owner).await?;281		recipients.push(key);282	}283	let generators = nix_go!(mk_secret_generators(Obj { recipients }));284	let pkgs_and_generators = nix_go!(on_pkgs + generators);285286	let call_package = nix_go!(nixpkgs.lib.callPackageWith(pkgs_and_generators));287288	let generator = nix_go!(call_package(generator)(Obj {}));289290	let generator = generator.build_maybe_batch(batch).await?;291	let generator = generator292		.get("out")293		.ok_or_else(|| anyhow!("missing generateImpure out"))?;294	let generator = host.remote_derivation(generator).await?;295296	let out_parent = host.mktemp_dir().await?;297	let out = format!("{out_parent}/out");298299	let mut gen = host.cmd(generator).await?;300	gen.env("out", &out);301	if on.is_none() {302		// This path is local, thus we can feed `OsString` directly to env var... But I don't think that's necessary to handle.303		let project_path: String = config304			.directory305			.clone()306			.into_os_string()307			.into_string()308			.map_err(|s| anyhow!("fleet project path is not utf-8: {s:?}"))?;309		gen.env("FLEET_PROJECT", project_path);310	}311	gen.run().await.context("impure generator")?;312313	{314		let marker = host.read_file_text(format!("{out}/marker")).await?;315		ensure!(marker == "SUCCESS", "generation not succeeded");316	}317318	let mut parts = BTreeMap::new();319	for part in host.read_dir(&out).await? {320		if part == "created_at" || part == "expires_at" || part == "marker" {321			continue;322		}323		let contents: SecretData = host324			.read_file_text(format!("{out}/{part}"))325			.await?326			.parse()327			.map_err(|e| anyhow!("failed to decode secret {out:?} part {part:?}: {e}"))?;328		parts.insert(part.to_owned(), FleetSecretPart { raw: contents });329	}330331	let created_at = host.read_file_value(format!("{out}/created_at")).await?;332	let expires_at = host.read_file_value(format!("{out}/expires_at")).await.ok();333334	Ok(FleetSecret {335		created_at,336		expires_at,337		parts,338		generation_data: expected_generation_data,339	})340}341async fn generate(342	config: &Config,343	display_name: &str,344	secret: Value,345	expected_owners: &[String],346	expected_generation_data: serde_json::Value,347	batch: Option<NixBuildBatch>,348) -> Result<FleetSecret> {349	let generator = nix_go!(secret.generator);350	// Can't properly check on nix module system level351	{352		let gen_ty = generator.type_of().await?;353		if gen_ty == "null" {354			bail!("secret has no generator defined, can't automatically generate it.");355		}356		if gen_ty == "set" {357			if !generator.has_field("__functor").await? {358				bail!("generator should be functor, got {gen_ty}");359			}360		} else if gen_ty != "lambda" {361			bail!("generator should be functor, got {gen_ty}");362		}363	}364	let nixpkgs = &config.nixpkgs;365	let default_pkgs = &config.default_pkgs;366	let default_mk_secret_generators = nix_go!(default_pkgs.mkSecretGenerators);367	// Generators provide additional information in passthru, to access368	// passthru we should call generator, but information about where this generator is supposed to build369	// is located in passthru... Thus evaluating generator on host.370	//371	// Maybe it is also possible to do some magic with __functor?372	//373	// I don't want to make modules always responsible for additional secret data anyway,374	// so it should be in derivation, and not in the secret data itself.375	let generators = nix_go!(default_mk_secret_generators(Obj {376		recipients: <Vec<String>>::new(),377	}));378	let pkgs_and_generators = nix_go!(default_pkgs + generators);379380	let call_package = nix_go!(nixpkgs.lib.callPackageWith(pkgs_and_generators));381	let default_generator = nix_go!(call_package(generator)(Obj {}));382383	let kind: GeneratorKind = nix_go_json!(default_generator.generatorKind);384385	match kind {386		GeneratorKind::Impure => {387			generate_impure(388				config,389				display_name,390				secret,391				default_generator,392				expected_owners,393				expected_generation_data,394				batch,395			)396			.await397		}398		GeneratorKind::Pure => {399			generate_pure(400				config,401				display_name,402				secret,403				default_generator,404				expected_owners,405			)406			.await407		}408	}409}410async fn generate_shared(411	config: &Config,412	display_name: &str,413	secret: Value,414	expected_owners: Vec<String>,415	expected_generation_data: serde_json::Value,416	batch: Option<NixBuildBatch>,417) -> Result<FleetSharedSecret> {418	// let owners: Vec<String> = nix_go_json!(secret.expectedOwners);419	Ok(FleetSharedSecret {420		secret: generate(421			config,422			display_name,423			secret,424			&expected_owners,425			expected_generation_data,426			batch,427		)428		.await?,429		owners: expected_owners,430	})431}432433async fn parse_public(434	public: Option<String>,435	public_file: Option<PathBuf>,436) -> Result<Option<SecretData>> {437	Ok(match (public, public_file) {438		(Some(v), None) => Some(SecretData {439			data: v.into(),440			encrypted: false,441		}),442		(None, Some(v)) => Some(SecretData {443			data: read(v).await?,444			encrypted: false,445		}),446		(Some(_), Some(_)) => {447			bail!("only public or public_file should be set")448		}449		(None, None) => None,450	})451}452453async fn parse_secret() -> Result<Option<Vec<u8>>> {454	let mut input = vec![];455	stdin().read_to_end(&mut input)?;456	if input.is_empty() {457		Ok(None)458	} else {459		Ok(Some(input))460	}461}462463fn parse_machines(464	initial: Vec<String>,465	machines: Option<Vec<String>>,466	mut add_machines: Vec<String>,467	mut remove_machines: Vec<String>,468) -> Result<Vec<String>> {469	if machines.is_none() && add_machines.is_empty() && remove_machines.is_empty() {470		bail!("no operation");471	}472473	let initial_machines = initial.clone();474	let mut target_machines = initial;475	info!("Currently encrypted for {initial_machines:?}");476477	// ensure!(machines.is_some() || !add_machines.is_empty() || )478	if let Some(machines) = machines {479		ensure!(480			add_machines.is_empty() && remove_machines.is_empty(),481			"can't combine --machines and --add-machines/--remove-machines"482		);483		let target = initial_machines.iter().collect::<HashSet<_>>();484		let source = machines.iter().collect::<HashSet<_>>();485		for removed in target.difference(&source) {486			remove_machines.push((*removed).clone());487		}488		for added in source.difference(&target) {489			add_machines.push((*added).clone());490		}491	}492493	for machine in &remove_machines {494		let mut removed = false;495		while let Some(pos) = target_machines.iter().position(|m| m == machine) {496			target_machines.swap_remove(pos);497			removed = true;498		}499		if !removed {500			warn!("secret is not enabled for {machine}");501		}502	}503	for machine in &add_machines {504		if target_machines.iter().any(|m| m == machine) {505			warn!("secret is already added to {machine}");506		} else {507			target_machines.push(machine.to_owned());508		}509	}510	if !remove_machines.is_empty() {511		// TODO: maybe force secret regeneration?512		// Not that useful without revokation.513		warn!("secret will not be regenerated for removed machines, and until host rebuild, they will still possess the ability to decode secret");514	}515	Ok(target_machines)516}517impl Secret {518	pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {519		match self {520			Secret::ForceKeys => {521				for host in config.list_hosts().await? {522					if opts.should_skip(&host).await? {523						continue;524					}525					config.key(&host.name).await?;526				}527			}528			Secret::AddShared {529				mut machines,530				name,531				force,532				public,533				public_part: public_name,534				public_file,535				expires_at,536				re_add,537				part: part_name,538			} => {539				// TODO: Forbid updating secrets with set expectedOwners (= not user-managed).540541				let exists = config.has_shared(&name);542				if exists && !force && !re_add {543					bail!("secret already defined");544				}545				if re_add {546					// Fixme: use clap to limit this usage547					ensure!(!force, "--force and --readd are not compatible");548					ensure!(exists, "secret doesn't exists");549					ensure!(550						machines.is_empty(),551						"you can't use machines argument for --readd"552					);553					let shared = config.shared_secret(&name)?;554					machines = shared.owners;555				}556557				let recipients = config.recipients(machines.clone()).await?;558559				let mut parts = BTreeMap::new();560561				let mut input = vec![];562				io::stdin().read_to_end(&mut input)?;563564				if !input.is_empty() {565					let encrypted =566						encrypt_secret_data(recipients.iter().map(|r| r as &dyn Recipient), input)567							.ok_or_else(|| anyhow!("no recipients provided"))?;568					parts.insert(part_name, FleetSecretPart { raw: encrypted });569				}570571				if let Some(public) = parse_public(public, public_file).await? {572					parts.insert(public_name, FleetSecretPart { raw: public });573				}574575				config.replace_shared(576					name,577					FleetSharedSecret {578						owners: machines,579						secret: FleetSecret {580							created_at: Utc::now(),581							expires_at,582							parts,583							generation_data: serde_json::Value::Null,584						},585					},586				);587			}588			Secret::Add {589				machine,590				name,591				replace,592				merge,593				public,594				public_part: public_name,595				public_file,596				part: part_name,597			} => {598				if config.has_secret(&machine, &name) && !replace && !merge {599					bail!("secret already defined.\nUse --replace to override, or --merge to add new parts to existing secret");600				}601602				let mut out = if merge && !replace {603					config604						.host_secret(&machine, &name)605						.context("failed to read existing secret for --merge")?606				} else {607					FleetSecret {608						created_at: Utc::now(),609						expires_at: None,610						parts: BTreeMap::new(),611						generation_data: serde_json::Value::Null,612					}613				};614615				if let Some(secret) = parse_secret().await? {616					let recipient = config.recipient(&machine).await?;617					let encrypted = encrypt_secret_data([&recipient as &dyn Recipient], secret)618						.expect("recipient provided");619					if out620						.parts621						.insert(part_name.clone(), FleetSecretPart { raw: encrypted })622						.is_some() && !replace623					{624						bail!("part {part_name:?} is already defined");625					}626				}627628				if let Some(public) = parse_public(public, public_file).await? {629					if out630						.parts631						.insert(public_name.clone(), FleetSecretPart { raw: public })632						.is_some() && !replace633					{634						bail!("part {public_name:?} is already defined");635					}636				};637638				config.insert_secret(&machine, name, out);639			}640			#[allow(clippy::await_holding_refcell_ref)]641			Secret::Read {642				name,643				machine,644				part: part_name,645			} => {646				let secret = config.host_secret(&machine, &name)?;647				let Some(secret) = secret.parts.get(&part_name) else {648					bail!("no part {part_name} in secret {name}");649				};650				let data = if secret.raw.encrypted {651					let host = config.host(&machine).await?;652					host.decrypt(secret.raw.clone()).await?653				} else {654					secret.raw.data.clone()655				};656657				stdout().write_all(&data)?;658			}659			Secret::ReadShared {660				name,661				part: part_name,662				prefer_identities,663			} => {664				let secret = config.shared_secret(&name)?;665				let Some(part) = secret.secret.parts.get(&part_name) else {666					bail!("no part {part_name} in secret {name}");667				};668				let data = if part.raw.encrypted {669					let identity_holder = if !prefer_identities.is_empty() {670						prefer_identities671							.iter()672							.find(|i| secret.owners.iter().any(|s| s == *i))673					} else {674						secret.owners.first()675					};676					let Some(identity_holder) = identity_holder else {677						bail!("no available holder found");678					};679					let host = config.host(identity_holder).await?;680					host.decrypt(part.raw.clone()).await?681				} else {682					part.raw.data.clone()683				};684				stdout().write_all(&data)?;685			}686			Secret::UpdateShared {687				name,688				machine,689				add_machine,690				remove_machine,691				prefer_identities,692			} => {693				// TODO: Forbid updating secrets with set expectedOwners (= not user-managed).694695				let secret = config.shared_secret(&name)?;696				if secret.secret.parts.values().all(|v| !v.raw.encrypted) {697					bail!("no secret");698				}699700				let initial_machines = secret.owners.clone();701				let target_machines = parse_machines(702					initial_machines.clone(),703					machine,704					add_machine,705					remove_machine,706				)?;707708				if target_machines.is_empty() {709					info!("no machines left for secret, removing it");710					config.remove_shared(&name);711					return Ok(());712				}713714				let config_field = &config.config_field;715				let field = nix_go!(config_field.sharedSecrets[{ name }]);716				let expected_generation_data = nix_go_json!(field.expectedGenerationData);717718				let updated = maybe_regenerate_shared_secret(719					&name,720					config,721					secret,722					field,723					&target_machines,724					expected_generation_data,725					&prefer_identities,726					None,727				)728				.await?;729				config.replace_shared(name, updated);730			}731			Secret::Regenerate {732				prefer_identities,733				skip_hosts,734			} => {735				info!("checking for secrets to regenerate");736				let stored_shared_set = config.list_shared().into_iter().collect::<HashSet<_>>();737				{738					// Generate missing shared739					let shared_batch = None;740					let _span = info_span!("shared").entered();741					let expected_shared_set = config742						.list_configured_shared()743						.await?744						.into_iter()745						.collect::<HashSet<_>>();746					for missing in expected_shared_set.difference(&stored_shared_set) {747						let config_field = &config.config_field;748						let secret = nix_go!(config_field.sharedSecrets[{ missing }]);749						let expected_generation_data: serde_json::Value =750							nix_go_json!(secret.expectedGenerationData);751						let expected_owners: Option<Vec<String>> =752							nix_go_json!(secret.expectedOwners);753						let Some(expected_owners) = expected_owners else {754							// Can't generate this missing secret, as it has no defined owners.755							continue;756						};757						info!("generating secret: {missing}");758						let shared = generate_shared(759							config,760							missing,761							secret,762							expected_owners,763							expected_generation_data,764							shared_batch.clone(),765						)766						.in_current_span()767						.await?;768						config.replace_shared(missing.to_string(), shared)769					}770				}771				if !skip_hosts {772					let hosts_batch = None;773					for host in config.list_hosts().await? {774						if opts.should_skip(&host).await? {775							continue;776						}777778						let _span = info_span!("host", host = host.name).entered();779						let expected_set = host780							.list_configured_secrets()781							.in_current_span()782							.await?783							.into_iter()784							.collect::<HashSet<_>>();785						let stored_set = config786							.list_secrets(&host.name)787							.into_iter()788							.collect::<HashSet<_>>();789						for missing in expected_set.difference(&stored_set) {790							info!("generating secret: {missing}");791							let secret = host.secret_field(missing).in_current_span().await?;792							let expected_generation_data =793								nix_go_json!(secret.expectedGenerationData);794							let generated = match generate(795								config,796								missing,797								secret,798								&[host.name.clone()],799								expected_generation_data,800								hosts_batch.clone(),801							)802							.in_current_span()803							.await804							{805								Ok(v) => v,806								Err(e) => {807									error!("{e:?}");808									continue;809								}810							};811							config.insert_secret(&host.name, missing.to_string(), generated)812						}813						for name in stored_set {814							info!("updating secret: {name}");815							let data = config.host_secret(&host.name, &name)?;816							let secret = host.secret_field(&name).in_current_span().await?;817							let expected_generation_data =818								nix_go_json!(secret.expectedGenerationData);819							if secret_needs_regeneration(&data, &expected_generation_data) {820								let generated = match generate(821									config,822									&name,823									secret,824									&[host.name.clone()],825									expected_generation_data,826									hosts_batch.clone(),827								)828								.in_current_span()829								.await830								{831									Ok(v) => v,832									Err(e) => {833										error!("{e:?}");834										continue;835									}836								};837								config.insert_secret(&host.name, name.to_string(), generated)838							}839						}840					}841				}842				let mut to_remove = Vec::new();843				for name in &stored_shared_set {844					info!("updating secret: {name}");845					let data = config.shared_secret(name)?;846					let config_field = &config.config_field;847					let expected_owners: Option<Vec<String>> =848						nix_go_json!(config_field.sharedSecrets[{ name }].expectedOwners);849					let Some(expected_owners) = expected_owners else {850						warn!("secret was removed from fleet config: {name}, removing from data");851						to_remove.push(name.to_string());852						continue;853					};854855					let secret = nix_go!(config_field.sharedSecrets[{ name }]);856					let expected_generation_data = nix_go_json!(secret.expectedGenerationData);857					config.replace_shared(858						name.to_owned(),859						maybe_regenerate_shared_secret(860							name,861							config,862							data,863							secret,864							&expected_owners,865							expected_generation_data,866							&prefer_identities,867							None,868						)869						.await?,870					);871				}872				for k in to_remove {873					config.remove_shared(&k);874				}875			}876			Secret::List {} => {877				let _span = info_span!("loading secrets").entered();878				let configured = config.list_configured_shared().await?;879				#[derive(Tabled)]880				struct SecretDisplay {881					#[tabled(rename = "Name")]882					name: String,883					#[tabled(rename = "Owners")]884					owners: String,885				}886				let mut table = vec![];887				for name in configured.iter().cloned() {888					let config = config.clone();889					let expected_owners = config.shared_secret_expected_owners(&name).await?;890					let data = config.shared_secret(&name)?;891					let owners = data892						.owners893						.iter()894						.map(|o| {895							if expected_owners.contains(o) {896								o.green().to_string()897							} else {898								o.red().to_string()899							}900						})901						.collect::<Vec<_>>();902					table.push(SecretDisplay {903						owners: owners.join(", "),904						name,905					})906				}907				info!("loaded\n{}", Table::new(table).to_string())908			}909			Secret::Edit {910				name,911				machine,912				part,913				add,914			} => {915				let secret = config.host_secret(&machine, &name)?;916				if let Some(data) = secret.parts.get(&part) {917					let host = config.host(&machine).await?;918					let secret = host.decrypt(data.raw.clone()).await?;919					String::from_utf8(secret).context("secret is not utf8")?920				} else if add {921					String::new()922				} else {923					bail!("part {part} not found in secret {name}. Did you mean to `--add` it?");924				};925			}926		}927		Ok(())928	}929}930931/*932async fn edit_temp_file(933	builder: tempfile::Builder<'_, '_>,934	r: Vec<u8>,935	header: &str,936	comment: &str,937) -> Result<(Vec<u8>, Option<String>), anyhow::Error> {938	if !stdin().is_tty() {939		// TODO: Also try to open /dev/tty directly?940		bail!("stdin is not tty, can't open editor");941	}942943	use std::fmt::Write;944	let mut file = builder.tempfile()?;945946	let mut full_header = String::new();947	let mut had = false;948	for line in header.trim_end().lines() {949		had = true;950		writeln!(&mut full_header, "{comment}{line}")?;951	}952	if had {953		writeln!(&mut full_header, "{}", comment.trim_end())?;954	}955	writeln!(956		&mut full_header,957		"{comment}Do not touch this header! It will be removed automatically"958	)?;959960	file.write_all(full_header.as_bytes())?;961	file.write_all(&r)?;962963	let abs_path = file.into_temp_path();964	let editor = std::env::var_os("VISUAL")965		.or_else(|| std::env::var_os("EDITOR"))966		.unwrap_or_else(|| "vi".into());967	let editor_args = shlex::bytes::split(editor.as_encoded_bytes())968		.ok_or_else(|| anyhow!("EDITOR env var has wrong syntax"))?;969	let editor_args = editor_args970		.into_iter()971		.map(|v| {972			// Only ASCII subsequences are replaced973			unsafe { OsString::from_encoded_bytes_unchecked(v) }974		})975		.collect_vec();976	let Some((editor, args)) = editor_args.split_first() else {977		bail!("EDITOR env var has no command");978	};979	let mut command = Command::new(editor);980	command.args(args);981982	let path_arg = abs_path.canonicalize()?;983984	// TODO: Save full state, using tcget/_getmode/_setmode985	let was_raw = terminal::is_raw_mode_enabled()?;986	terminal::enable_raw_mode()?;987988	let status = command.arg(path_arg).status().await;989990	if !was_raw {991		terminal::disable_raw_mode()?;992	}993994	let success = match status {995		Ok(s) => s.success(),996		Err(e) if e.kind() == io::ErrorKind::NotFound => {997			bail!("editor not found")998		}999		Err(e) => bail!("editor spawn error: {e}"),1000	};10011002	let mut file = std::fs::read(&abs_path).context("read editor output")?;1003	let Some(v) = file.strip_prefix(full_header.as_bytes()) else {1004		todo!();1005	};1006	todo!();10071008	// Ok((success, abs_path))1009}1010*/
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
--- a/crates/nix-eval/src/session.rs
+++ b/crates/nix-eval/src/session.rs
@@ -3,16 +3,16 @@
 use better_command::{ClonableHandler, Handler, NixHandler, NoopHandler};
 use futures::StreamExt;
 use itertools::Itertools as _;
-use serde::{de::DeserializeOwned, Deserialize};
+use serde::{Deserialize, de::DeserializeOwned};
 use thiserror::Error;
 use tokio::{
 	io::AsyncWriteExt,
 	process::{ChildStderr, ChildStdin, ChildStdout, Command},
 	select,
-	sync::{mpsc, oneshot, Mutex},
+	sync::{Mutex, mpsc, oneshot},
 };
 use tokio_util::codec::{FramedRead, LinesCodec};
-use tracing::{debug, error, warn, Level};
+use tracing::{Level, debug, error, warn};
 
 #[derive(Error, Debug, Clone)]
 pub enum Error {
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"