git.delta.rocks / jrsonnet / refs/commits / 35053409e3f1

difftreelog

chore update flake

Lach2025-03-23parent: #cd1bdf7.patch.diff
in: trunk

16 files changed

modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,8 @@
 members = ["crates/*", "cmds/*"]
 resolver = "2"
 package.version = "0.1.0"
+package.edition = "2021"
+package.rust-version = "1.82.0"
 
 [workspace.dependencies]
 nixlike = { path = "./crates/nixlike" }
modifiedcmds/fleet/Cargo.tomldiffbeforeafterboth
--- a/cmds/fleet/Cargo.toml
+++ b/cmds/fleet/Cargo.toml
@@ -3,7 +3,8 @@
 description = "NixOS configuration management"
 version = "0.2.0"
 authors = ["Yaroslav Bolyukin <iam@lach.pw>"]
-edition = "2021"
+edition.workspace = true
+rust-version.workspace = true
 
 [dependencies]
 nixlike.workspace = true
modifiedcmds/generator-helper/Cargo.tomldiffbeforeafterboth
--- a/cmds/generator-helper/Cargo.toml
+++ b/cmds/generator-helper/Cargo.toml
@@ -1,7 +1,8 @@
 [package]
 name = "fleet-generator-helper"
-edition = "2021"
 version.workspace = true
+edition.workspace = true
+rust-version.workspace = true
 
 [dependencies]
 age.workspace = true
modifiedcmds/generator-helper/src/main.rsdiffbeforeafterboth
before · cmds/generator-helper/src/main.rs
1use std::{2	env,3	fs::{File, OpenOptions},4	io::{self, copy, stdin, stdout, Read, Write},5	str::FromStr,6};78use age::{9	ssh::{ParseRecipientKeyError, Recipient as SshRecipient},10	Encryptor, Recipient,11};12use anyhow::{anyhow, bail, ensure, Context, Result};13use clap::{Parser, ValueEnum};14use fleet_shared::SecretData;15use rand::{16	distributions::{Alphanumeric, DistString, Distribution, Uniform},17	thread_rng, RngCore,18};1920fn write_output_file(out: &str) -> Result<File> {21	let file = OpenOptions::new()22		.create_new(true)23		.write(true)24		.open(out)25		.with_context(|| format!("failed to open output {out:?}"))?;26	Ok(file)27}28fn write_public(out: &str, mut input: impl Read, encoding: OutputEncoding) -> Result<()> {29	let mut output = write_output_file(out)?;3031	let mut data = Vec::new();32	copy(&mut input, &mut wrap_encoder(&mut data, encoding))?;3334	output.write_all(35		SecretData {36			data,37			encrypted: false,38		}39		.to_string()40		.as_bytes(),41	)?;42	Ok(())43}44fn write_private(45	identities: &Identities,46	out: &str,47	mut input: impl Read,48	encoding: OutputEncoding,49) -> Result<()> {50	let mut output = write_output_file(out)?;51	let encryptor = make_encryptor(identities)?;5253	let mut data = Vec::new();54	{55		let mut encrypted_writer = encryptor.wrap_output(&mut data)?;56		copy(57			&mut input,58			&mut wrap_encoder(&mut encrypted_writer, encoding),59		)?;60		encrypted_writer.finish()?;61	};6263	output.write_all(64		SecretData {65			data,66			encrypted: true,67		}68		.to_string()69		.as_bytes(),70	)?;71	Ok(())72}7374type Identities = Vec<SshRecipient>;75fn load_identities() -> Result<Identities> {76	let list = env::var("GENERATOR_HELPER_IDENTITIES");77	let list = match list {78		Ok(v) => v,79		Err(env::VarError::NotPresent) => {80			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");81		}82		Err(e) => bail!("somehow, identities list is not utf-8: {e}"),83	};84	let list = list.trim();85	ensure!(!list.is_empty(), "no identities passed, can't encrypt data");86	list.lines()87		.map(age::ssh::Recipient::from_str)88		.collect::<Result<Identities, ParseRecipientKeyError>>()89		.map_err(|e| anyhow!("parse recipients: {e:?}"))90}91fn make_encryptor(r: &Identities) -> Result<Encryptor> {92	Ok(93		Encryptor::with_recipients(r.iter().map(|v| v as &dyn Recipient))94			.expect("list is not empty"),95	)96}97fn wrap_encoder<'t>(w: impl Write + 't, encoding: OutputEncoding) -> impl Write + 't {98	fn coerce<'t>(w: impl Write + 't) -> Box<dyn Write + 't> {99		Box::new(w)100	}101	match encoding {102		OutputEncoding::Raw => coerce(w),103		OutputEncoding::Base64 => {104			use base64::{engine::general_purpose::STANDARD, write::EncoderWriter};105106			let writer = EncoderWriter::new(w, &STANDARD);107			coerce(writer)108		}109		OutputEncoding::Hex => {110			struct HexWriter<W>(W);111			impl<W> Write for HexWriter<W>112			where113				W: Write,114			{115				fn write(&mut self, buf: &[u8]) -> io::Result<usize> {116					let encoded = hex::encode(buf);117					self.0.write_all(encoded.as_bytes())?;118					Ok(buf.len())119				}120121				fn flush(&mut self) -> io::Result<()> {122					self.0.flush()123				}124			}125			coerce(HexWriter(w))126		}127	}128}129130#[derive(Clone, Copy, ValueEnum, Default)]131enum OutputEncoding {132	/// Do not encode data, store as is.133	#[default]134	Raw,135	/// Encode as base64 (with padding).136	Base64,137	/// Encode as hex (without leading 0x)138	Hex,139}140141#[derive(Parser)]142enum Generate {143	/// Generate public, private keys without wrapping, in standard ed25519 schema144	/// (64 bytes private (due to merge with private), 32 bytes public)145	Ed25519 {146		#[arg(long, short = 'p')]147		public: String,148		#[arg(long, short = 's')]149		private: String,150		/// Private key should be just the private key (32 bytes), not standard private+public.151		#[arg(long)]152		no_embed_public: bool,153		#[arg(long, short = 'e', value_enum, default_value_t)]154		encoding: OutputEncoding,155	},156	/// Generate public, private keys without wrapping, in standard x25519 schema157	/// (32 bytes private, 32 bytes public)158	X25519 {159		#[arg(long, short = 'p')]160		public: String,161		#[arg(long, short = 's')]162		private: String,163		#[arg(long, short = 'e', value_enum, default_value_t)]164		encoding: OutputEncoding,165	},166	Password {167		#[arg(long, short = 'o')]168		output: String,169		#[arg(long)]170		size: usize,171		#[arg(long, short = 'n')]172		no_symbols: bool,173		#[arg(long, short = 'e', value_enum, default_value_t)]174		encoding: OutputEncoding,175	},176	Bytes {177		#[arg(long, short = 'o')]178		output: String,179		#[arg(long, short = 'c')]180		count: usize,181		/// Ensure there is no NULs in bytestring.182		#[arg(long)]183		no_nuls: bool,184		#[arg(long, short = 'e', value_enum, default_value_t)]185		encoding: OutputEncoding,186	},187}188189#[derive(Parser)]190enum Opts {191	/// Encode public part from stdin.192	Public {193		#[arg(long, short = 'o')]194		output: String,195		#[arg(long, short = 'e', value_enum, default_value_t)]196		encoding: OutputEncoding,197	},198	/// Encrypt private part from stdin.199	Private {200		#[arg(long, short = 'o')]201		output: String,202		#[arg(long, short = 'e', value_enum, default_value_t)]203		encoding: OutputEncoding,204	},205	/// Sometimes you also need to reencode secret, this command allows you to get raw data from206	/// secret encoded using `gh public`... I would like if I knew a better design for some sort of207	/// such thing. Ideally there should be no need to decode secrets back, but garage wants both208	/// raw pubkey and raw secret key, yet also requires node id which is hex-reencoded public key.209	Decode {210		#[arg(long, short = 'i')]211		input: String,212	},213	/// Generate keys in well-known schemas.214	///215	/// Note that this command is only intended to be used in fleet secret generator,216	/// otherwise you should ensure noone is able to read generated files, they don't have any mode set by default.217	///218	/// Fleet also doesn't zeroize memory/assumes good OsRng/makes other assumptions, which makes it only suitable to219	/// be used in nix sandbox.220	#[command(subcommand)]221	Generate(Generate),222}223224fn main() -> Result<()> {225	let opts = Opts::parse();226	// Assumed to be secure, seeded from secure OsRng+reseeded.227	let mut rng = thread_rng();228229	match opts {230		Opts::Public { output, encoding } => {231			write_public(&output, stdin(), encoding)?;232		}233		Opts::Private { output, encoding } => {234			let recipients = load_identities()?;235			write_private(&recipients, &output, stdin(), encoding)?;236		}237		Opts::Generate(gen) => {238			match gen {239				Generate::Ed25519 {240					public,241					private,242					no_embed_public,243					encoding,244				} => {245					use ed25519_dalek::SigningKey;246247					let recipients = load_identities()?;248					let key = SigningKey::generate(&mut rng).to_keypair_bytes();249					write_public(&public, &key[32..], encoding)?;250					write_private(251						&recipients,252						&private,253						&key[..{254							if no_embed_public {255								32256							} else {257								64258							}259						}],260						encoding,261					)?;262				}263				Generate::X25519 {264					public,265					private,266					encoding,267				} => {268					use x25519_dalek::{PublicKey, StaticSecret};269270					let recipients = load_identities()?;271					let key = StaticSecret::random_from_rng(rng);272					let public_key: PublicKey = (&key).into();273					write_public(&public, public_key.as_bytes().as_slice(), encoding)?;274					write_private(&recipients, &private, key.as_bytes().as_slice(), encoding)?;275				}276				Generate::Password {277					size,278					no_symbols,279					output,280					encoding,281				} => {282					ensure!(283						size >= 6,284						"misconfiguration? password is shorter than 6 chars"285					);286					let recipients = load_identities()?;287					let out = if no_symbols {288						Alphanumeric.sample_string(&mut rng, size)289					} else {290						// Alphabet of Alphanumberic + symbols291						const GEN_ASCII_SYMBOLS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";292						let uniform = Uniform::new(0, GEN_ASCII_SYMBOLS.len());293						(0..size)294							.map(|_| uniform.sample(&mut rng))295							.map(|i| GEN_ASCII_SYMBOLS[i] as char)296							.collect::<String>()297					};298					write_private(&recipients, &output, out.as_bytes(), encoding)?;299				}300				Generate::Bytes {301					output,302					count,303					no_nuls,304					encoding,305				} => {306					ensure!(307						count >= 6,308						"misconfiguration? bytestring is shorter than 6 chars"309					);310					let recipients = load_identities()?;311					let mut bytes = vec![0u8; count];312					if no_nuls {313						let rand = Uniform::new_inclusive(0x1u8, 0xffu8).sample_iter(&mut rng);314						for (byte, rand) in bytes.iter_mut().zip(rand) {315							*byte = rand;316						}317					} else {318						rng.fill_bytes(&mut bytes);319					};320					write_private(&recipients, &output, bytes.as_slice(), encoding)?;321				}322			}323		}324		Opts::Decode { input } => {325			let mut data = Vec::new();326			File::open(input)?.read_to_end(&mut data)?;327			let data = String::from_utf8(data).context(328				"encoded data is always utf-8, you are trying to use decode the wrong way.",329			)?;330			let data =331				SecretData::from_str(&data).map_err(|e| anyhow!("failed to decode data: {e}"))?;332			ensure!(333				!data.encrypted,334				"you can not decrypt secret data, only decode public."335			);336			stdout().write_all(&data.data)?;337		}338	}339	Ok(())340}
after · cmds/generator-helper/src/main.rs
1use std::{2	env,3	fs::{File, OpenOptions},4	io::{self, copy, stdin, stdout, Read, Write},5	str::FromStr,6};78use age::{9	ssh::{ParseRecipientKeyError, Recipient as SshRecipient},10	Encryptor, Recipient,11};12use anyhow::{anyhow, bail, ensure, Context, Result};13use clap::{Parser, ValueEnum};14use fleet_shared::SecretData;15use rand::{16	distributions::{Alphanumeric, DistString, Distribution, Uniform},17	thread_rng, RngCore,18};1920fn write_output_file(out: &str) -> Result<File> {21	let file = OpenOptions::new()22		.create_new(true)23		.write(true)24		.open(out)25		.with_context(|| format!("failed to open output {out:?}"))?;26	Ok(file)27}28fn write_public(out: &str, mut input: impl Read, encoding: OutputEncoding) -> Result<()> {29	let mut output = write_output_file(out)?;3031	let mut data = Vec::new();32	copy(&mut input, &mut wrap_encoder(&mut data, encoding))?;3334	output.write_all(35		SecretData {36			data,37			encrypted: false,38		}39		.to_string()40		.as_bytes(),41	)?;42	Ok(())43}44fn write_private(45	identities: &Identities,46	out: &str,47	mut input: impl Read,48	encoding: OutputEncoding,49) -> Result<()> {50	let mut output = write_output_file(out)?;51	let encryptor = make_encryptor(identities)?;5253	let mut data = Vec::new();54	{55		let mut encrypted_writer = encryptor.wrap_output(&mut data)?;56		copy(57			&mut input,58			&mut wrap_encoder(&mut encrypted_writer, encoding),59		)?;60		encrypted_writer.finish()?;61	};6263	output.write_all(64		SecretData {65			data,66			encrypted: true,67		}68		.to_string()69		.as_bytes(),70	)?;71	Ok(())72}7374type Identities = Vec<SshRecipient>;75fn load_identities() -> Result<Identities> {76	let list = env::var("GENERATOR_HELPER_IDENTITIES");77	let list = match list {78		Ok(v) => v,79		Err(env::VarError::NotPresent) => {80			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");81		}82		Err(e) => bail!("somehow, identities list is not utf-8: {e}"),83	};84	let list = list.trim();85	ensure!(!list.is_empty(), "no identities passed, can't encrypt data");86	list.lines()87		.map(age::ssh::Recipient::from_str)88		.collect::<Result<Identities, ParseRecipientKeyError>>()89		.map_err(|e| anyhow!("parse recipients: {e:?}"))90}91fn make_encryptor(r: &Identities) -> Result<Encryptor> {92	Ok(93		Encryptor::with_recipients(r.iter().map(|v| v as &dyn Recipient))94			.expect("list is not empty"),95	)96}97fn wrap_encoder<'t>(w: impl Write + 't, encoding: OutputEncoding) -> impl Write + 't {98	fn coerce<'t>(w: impl Write + 't) -> Box<dyn Write + 't> {99		Box::new(w)100	}101	match encoding {102		OutputEncoding::Raw => coerce(w),103		OutputEncoding::Base64 => {104			use base64::{engine::general_purpose::STANDARD, write::EncoderWriter};105106			let writer = EncoderWriter::new(w, &STANDARD);107			coerce(writer)108		}109		OutputEncoding::Hex => {110			struct HexWriter<W>(W);111			impl<W> Write for HexWriter<W>112			where113				W: Write,114			{115				fn write(&mut self, buf: &[u8]) -> io::Result<usize> {116					let encoded = hex::encode(buf);117					self.0.write_all(encoded.as_bytes())?;118					Ok(buf.len())119				}120121				fn flush(&mut self) -> io::Result<()> {122					self.0.flush()123				}124			}125			coerce(HexWriter(w))126		}127	}128}129130#[derive(Clone, Copy, ValueEnum, Default)]131enum OutputEncoding {132	/// Do not encode data, store as is.133	#[default]134	Raw,135	/// Encode as base64 (with padding).136	Base64,137	/// Encode as hex (without leading 0x)138	Hex,139}140141#[derive(Parser)]142enum Generate {143	/// Generate public, private keys without wrapping, in standard ed25519 schema144	/// (64 bytes private (due to merge with private), 32 bytes public)145	Ed25519 {146		#[arg(long, short = 'p')]147		public: String,148		#[arg(long, short = 's')]149		private: String,150		/// Private key should be just the private key (32 bytes), not standard private+public.151		#[arg(long)]152		no_embed_public: bool,153		#[arg(long, short = 'e', value_enum, default_value_t)]154		encoding: OutputEncoding,155	},156	/// Generate public, private keys without wrapping, in standard x25519 schema157	/// (32 bytes private, 32 bytes public)158	X25519 {159		#[arg(long, short = 'p')]160		public: String,161		#[arg(long, short = 's')]162		private: String,163		#[arg(long, short = 'e', value_enum, default_value_t)]164		encoding: OutputEncoding,165	},166	Password {167		#[arg(long, short = 'o')]168		output: String,169		#[arg(long)]170		size: usize,171		#[arg(long, short = 'n')]172		no_symbols: bool,173		#[arg(long, short = 'e', value_enum, default_value_t)]174		encoding: OutputEncoding,175	},176	Bytes {177		#[arg(long, short = 'o')]178		output: String,179		#[arg(long, short = 'c')]180		count: usize,181		/// Ensure there is no NULs in bytestring.182		#[arg(long)]183		no_nuls: bool,184		#[arg(long, short = 'e', value_enum, default_value_t)]185		encoding: OutputEncoding,186	},187}188189#[derive(Parser)]190enum Opts {191	/// Encode public part from stdin.192	Public {193		#[arg(long, short = 'o')]194		output: String,195		#[arg(long, short = 'e', value_enum, default_value_t)]196		encoding: OutputEncoding,197	},198	/// Encrypt private part from stdin.199	Private {200		#[arg(long, short = 'o')]201		output: String,202		#[arg(long, short = 'e', value_enum, default_value_t)]203		encoding: OutputEncoding,204	},205	/// Sometimes you also need to reencode secret, this command allows you to get raw data from206	/// secret encoded using `gh public`... I would like if I knew a better design for some sort of207	/// such thing. Ideally there should be no need to decode secrets back, but garage wants both208	/// raw pubkey and raw secret key, yet also requires node id which is hex-reencoded public key.209	Decode {210		#[arg(long, short = 'i')]211		input: String,212	},213	/// Generate keys in well-known schemas.214	///215	/// Note that this command is only intended to be used in fleet secret generator,216	/// otherwise you should ensure noone is able to read generated files, they don't have any mode set by default.217	///218	/// Fleet also doesn't zeroize memory/assumes good OsRng/makes other assumptions, which makes it only suitable to219	/// be used in nix sandbox.220	#[command(subcommand)]221	Generate(Generate),222}223224fn main() -> Result<()> {225	let opts = Opts::parse();226	// Assumed to be secure, seeded from secure OsRng+reseeded.227	let mut rng = thread_rng();228229	match opts {230		Opts::Public { output, encoding } => {231			write_public(&output, stdin(), encoding)?;232		}233		Opts::Private { output, encoding } => {234			let recipients = load_identities()?;235			write_private(&recipients, &output, stdin(), encoding)?;236		}237		Opts::Generate(generate) => {238			match generate {239				Generate::Ed25519 {240					public,241					private,242					no_embed_public,243					encoding,244				} => {245					use ed25519_dalek::SigningKey;246247					let recipients = load_identities()?;248					let key = SigningKey::generate(&mut rng).to_keypair_bytes();249					write_public(&public, &key[32..], encoding)?;250					write_private(251						&recipients,252						&private,253						&key[..{254							if no_embed_public {255								32256							} else {257								64258							}259						}],260						encoding,261					)?;262				}263				Generate::X25519 {264					public,265					private,266					encoding,267				} => {268					use x25519_dalek::{PublicKey, StaticSecret};269270					let recipients = load_identities()?;271					let key = StaticSecret::random_from_rng(rng);272					let public_key: PublicKey = (&key).into();273					write_public(&public, public_key.as_bytes().as_slice(), encoding)?;274					write_private(&recipients, &private, key.as_bytes().as_slice(), encoding)?;275				}276				Generate::Password {277					size,278					no_symbols,279					output,280					encoding,281				} => {282					ensure!(283						size >= 6,284						"misconfiguration? password is shorter than 6 chars"285					);286					let recipients = load_identities()?;287					let out = if no_symbols {288						Alphanumeric.sample_string(&mut rng, size)289					} else {290						// Alphabet of Alphanumberic + symbols291						const GEN_ASCII_SYMBOLS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";292						let uniform = Uniform::new(0, GEN_ASCII_SYMBOLS.len());293						(0..size)294							.map(|_| uniform.sample(&mut rng))295							.map(|i| GEN_ASCII_SYMBOLS[i] as char)296							.collect::<String>()297					};298					write_private(&recipients, &output, out.as_bytes(), encoding)?;299				}300				Generate::Bytes {301					output,302					count,303					no_nuls,304					encoding,305				} => {306					ensure!(307						count >= 6,308						"misconfiguration? bytestring is shorter than 6 chars"309					);310					let recipients = load_identities()?;311					let mut bytes = vec![0u8; count];312					if no_nuls {313						let rand = Uniform::new_inclusive(0x1u8, 0xffu8).sample_iter(&mut rng);314						for (byte, rand) in bytes.iter_mut().zip(rand) {315							*byte = rand;316						}317					} else {318						rng.fill_bytes(&mut bytes);319					};320					write_private(&recipients, &output, bytes.as_slice(), encoding)?;321				}322			}323		}324		Opts::Decode { input } => {325			let mut data = Vec::new();326			File::open(input)?.read_to_end(&mut data)?;327			let data = String::from_utf8(data).context(328				"encoded data is always utf-8, you are trying to use decode the wrong way.",329			)?;330			let data =331				SecretData::from_str(&data).map_err(|e| anyhow!("failed to decode data: {e}"))?;332			ensure!(333				!data.encrypted,334				"you can not decrypt secret data, only decode public."335			);336			stdout().write_all(&data.data)?;337		}338	}339	Ok(())340}
modifiedcmds/install-secrets/Cargo.tomldiffbeforeafterboth
--- a/cmds/install-secrets/Cargo.toml
+++ b/cmds/install-secrets/Cargo.toml
@@ -1,8 +1,8 @@
 [package]
 name = "fleet-install-secrets"
 version = "0.1.0"
-edition = "2021"
-rust-version = "1.77.2"
+edition.workspace = true
+rust-version.workspace = true
 
 [dependencies]
 clap.workspace = true
modifiedcmds/terraform-provider-fleet/Cargo.tomldiffbeforeafterboth
--- a/cmds/terraform-provider-fleet/Cargo.toml
+++ b/cmds/terraform-provider-fleet/Cargo.toml
@@ -1,7 +1,8 @@
 [package]
 name = "terraform-provider-fleet"
-edition = "2021"
 version.workspace = true
+edition.workspace = true
+rust-version.workspace = true
 
 [dependencies]
 anyhow.workspace = true
modifiedcrates/better-command/Cargo.tomldiffbeforeafterboth
--- a/crates/better-command/Cargo.toml
+++ b/crates/better-command/Cargo.toml
@@ -1,7 +1,8 @@
 [package]
 name = "better-command"
 version = "0.1.0"
-edition = "2021"
+edition.workspace = true
+rust-version.workspace = true
 
 [dependencies]
 regex = "1.10"
modifiedcrates/fleet-base/Cargo.tomldiffbeforeafterboth
--- a/crates/fleet-base/Cargo.toml
+++ b/crates/fleet-base/Cargo.toml
@@ -1,7 +1,8 @@
 [package]
 name = "fleet-base"
-edition = "2021"
 version.workspace = true
+edition.workspace = true
+rust-version.workspace = true
 
 [dependencies]
 age.workspace = true
modifiedcrates/fleet-shared/Cargo.tomldiffbeforeafterboth
--- a/crates/fleet-shared/Cargo.toml
+++ b/crates/fleet-shared/Cargo.toml
@@ -1,7 +1,8 @@
 [package]
 name = "fleet-shared"
-edition = "2021"
 version.workspace = true
+edition.workspace = true
+rust-version.workspace = true
 
 [dependencies]
 base64 = "0.22.1"
modifiedcrates/nix-eval/Cargo.tomldiffbeforeafterboth
--- a/crates/nix-eval/Cargo.toml
+++ b/crates/nix-eval/Cargo.toml
@@ -1,8 +1,9 @@
 [package]
 name = "nix-eval"
-edition = "2021"
 version.workspace = true
 build = "build.rs"
+edition.workspace = true
+rust-version.workspace = true
 
 [dependencies]
 anyhow.workspace = true
modifiedcrates/nix-native-eval/Cargo.tomldiffbeforeafterboth
--- a/crates/nix-native-eval/Cargo.toml
+++ b/crates/nix-native-eval/Cargo.toml
@@ -1,7 +1,8 @@
 [package]
 name = "nix-native-eval"
-edition = "2021"
 version.workspace = true
+edition.workspace = true
+rust-version.workspace = true
 
 [dependencies]
 anyhow.workspace = true
modifiedcrates/nixlike/Cargo.tomldiffbeforeafterboth
--- a/crates/nixlike/Cargo.toml
+++ b/crates/nixlike/Cargo.toml
@@ -1,7 +1,8 @@
 [package]
 name = "nixlike"
 version = "0.1.0"
-edition = "2021"
+edition.workspace = true
+rust-version.workspace = true
 
 [dependencies]
 thiserror.workspace = true
modifiedflake.lockdiffbeforeafterboth
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
   "nodes": {
     "crane": {
       "locked": {
-        "lastModified": 1731098351,
-        "narHash": "sha256-HQkYvKvaLQqNa10KEFGgWHfMAbWBfFp+4cAgkut+NNE=",
+        "lastModified": 1739936662,
+        "narHash": "sha256-x4syUjNUuRblR07nDPeLDP7DpphaBVbUaSoeZkFbGSk=",
         "owner": "ipetkov",
         "repo": "crane",
-        "rev": "ef80ead953c1b28316cc3f8613904edc2eb90c28",
+        "rev": "19de14aaeb869287647d9461cbd389187d8ecdb7",
         "type": "github"
       },
       "original": {
@@ -22,11 +22,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1730504689,
-        "narHash": "sha256-hgmguH29K2fvs9szpq2r3pz2/8cJd2LPS+b4tfNFCwE=",
+        "lastModified": 1738453229,
+        "narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=",
         "owner": "hercules-ci",
         "repo": "flake-parts",
-        "rev": "506278e768c2a08bec68eb62932193e341f55c90",
+        "rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd",
         "type": "github"
       },
       "original": {
@@ -37,16 +37,16 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1731873344,
-        "narHash": "sha256-bKfFggwcvvh9gmOsaMCXKVAGBfXCZZ6QrxLq9Nb1/vw=",
+        "lastModified": 1740339700,
+        "narHash": "sha256-cbrw7EgQhcdFnu6iS3vane53bEagZQy/xyIkDWpCgVE=",
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "39e98fadd66c2564ac85b1f65bab89e044302c62",
+        "rev": "04ef94c4c1582fd485bbfdb8c4a8ba250e359195",
         "type": "github"
       },
       "original": {
         "owner": "nixos",
-        "ref": "master",
+        "ref": "release-24.11",
         "repo": "nixpkgs",
         "type": "github"
       }
@@ -56,7 +56,8 @@
         "crane": "crane",
         "flake-parts": "flake-parts",
         "nixpkgs": "nixpkgs",
-        "rust-overlay": "rust-overlay"
+        "rust-overlay": "rust-overlay",
+        "shelly": "shelly"
       }
     },
     "rust-overlay": {
@@ -66,11 +67,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1731820690,
-        "narHash": "sha256-/hHFMTD+FGURXZ4JtfXoIgpy87zL505pVi6AL76Wc+U=",
+        "lastModified": 1740277845,
+        "narHash": "sha256-NNU0CdiaSbAeZ8tpDG4aFi9qtcdlItRvk8Xns9oBrVU=",
         "owner": "oxalica",
         "repo": "rust-overlay",
-        "rev": "bbab2ab9e1932133b1996baa1dc00fefe924ca81",
+        "rev": "f933070c29f9c1c5457447a51903f27f76ebb519",
         "type": "github"
       },
       "original": {
@@ -78,6 +79,21 @@
         "repo": "rust-overlay",
         "type": "github"
       }
+    },
+    "shelly": {
+      "locked": {
+        "lastModified": 1737089050,
+        "narHash": "sha256-SQ22z9L7fSC8FWNIlImpKeBGBUPoWpsTy9uc75o7uK0=",
+        "owner": "CertainLach",
+        "repo": "shelly",
+        "rev": "b5fa6bb56dcd1541ec399ceab1e95a74872ecd34",
+        "type": "github"
+      },
+      "original": {
+        "owner": "CertainLach",
+        "repo": "shelly",
+        "type": "github"
+      }
     }
   },
   "root": "root",
modifiedflake.nixdiffbeforeafterboth
--- a/flake.nix
+++ b/flake.nix
@@ -2,7 +2,7 @@
   description = "NixOS configuration management";
 
   inputs = {
-    nixpkgs.url = "github:nixos/nixpkgs/master";
+    nixpkgs.url = "github:nixos/nixpkgs/release-24.11";
     rust-overlay = {
       url = "github:oxalica/rust-overlay";
       inputs.nixpkgs.follows = "nixpkgs";
@@ -12,126 +12,146 @@
       inputs.nixpkgs-lib.follows = "nixpkgs";
     };
     crane.url = "github:ipetkov/crane";
+    shelly.url = "github:CertainLach/shelly";
   };
-  outputs = inputs:
-    inputs.flake-parts.lib.mkFlake {
-      inherit inputs;
-    } {
-      flake = rec {
-        lib =
-          (import ./lib {
-            inherit (inputs.nixpkgs) lib;
-          })
-          // {
-            fleetConfiguration = throw "function-based interface is deprecated, use flake-parts syntax instead";
+  outputs =
+    inputs:
+    inputs.flake-parts.lib.mkFlake
+      {
+        inherit inputs;
+      }
+      {
+        imports = [ inputs.shelly.flakeModule ];
+        flake = rec {
+          lib =
+            (import ./lib {
+              inherit (inputs.nixpkgs) lib;
+            })
+            // {
+              fleetConfiguration = throw "function-based interface is deprecated, use flake-parts syntax instead";
+            };
+          flakeModules.default = import ./lib/flakePart.nix {
+            inherit (inputs) crane;
           };
-        flakeModules.default = import ./lib/flakePart.nix {
-          inherit (inputs) crane;
-        };
-        flakeModule = flakeModules.default;
+          flakeModule = flakeModules.default;
 
-        fleetModules.tf = ./modules/extras/tf.nix;
+          fleetModules.tf = ./modules/extras/tf.nix;
 
-        # To be used with https://github.com/NixOS/nix/pull/8892
-        schemas = let
-          inherit (inputs.nixpkgs.lib) mapAttrs;
-        in {
-          fleetConfigurations = {
-            version = 1;
-            doc = ''
-              The `fleetConfigurations` flake output defines fleet cluster configurations.
-            '';
-            inventory = output: {
-              children =
-                mapAttrs (configName: cluster: {
-                  what = "fleet cluster configuration";
+          # To be used with https://github.com/NixOS/nix/pull/8892
+          schemas =
+            let
+              inherit (inputs.nixpkgs.lib) mapAttrs;
+            in
+            {
+              fleetConfigurations = {
+                version = 1;
+                doc = ''
+                  The `fleetConfigurations` flake output defines fleet cluster configurations.
+                '';
+                inventory = output: {
+                  children = mapAttrs (configName: cluster: {
+                    what = "fleet cluster configuration";
 
-                  children =
-                    mapAttrs (hostName: host: {
+                    children = mapAttrs (hostName: host: {
                       what = "host [${host.system}]";
-                    })
-                    cluster.config.hosts;
-                  # It is possible to implement this inventory right now, but I want to
-                  # get rid of `fleet.nix` file in the future.
-                  # children.secrets = { };
-                })
-                output;
+                    }) cluster.config.hosts;
+                    # It is possible to implement this inventory right now, but I want to
+                    # get rid of `fleet.nix` file in the future.
+                    # children.secrets = { };
+                  }) output;
+                };
+              };
             };
-          };
         };
-      };
-      # Supported and tested list of deployment targets.
-      systems = ["x86_64-linux" "aarch64-linux" "armv7l-linux" "armv6l-linux"];
-      perSystem = {
-        config,
-        system,
-        pkgs,
-        ...
-      }: let
-        inherit (lib.attrsets) mapAttrs';
-        inherit (lib.lists) elem;
-        # Can also be built for darwin, through it is not usual to deploy nixos systems from macos machines.
-        # I have no hardware for such testing, thus only adding machines I actually have and use.
-        #
-        # It is not possible to deploy any host from armv6/armv7 hardware, and I don't think it even makes sense.
-        deployerSystems = ["aarch64-linux" "x86_64-linux"];
-        deployerSystem = elem system deployerSystems;
-        lib = pkgs.lib;
-        rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
-        craneLib = (inputs.crane.mkLib pkgs).overrideToolchain rust;
-      in {
-        _module.args.pkgs = import inputs.nixpkgs {
-          inherit system;
-          overlays = [(inputs.rust-overlay.overlays.default)];
-        };
-        # Reference fleet package should be built with nightly rust, specified in rust-toolchain.toml.
-        packages = lib.mkIf deployerSystem (let
-          packages = pkgs.callPackages ./pkgs {
-            inherit craneLib;
-          };
-        in
-          packages // {default = packages.fleet;});
-        # fleet-install-secrets will not be built normally, because they are not ran directly by user most of the time.
-        # checks there build packages for default nixpkgs rustPlatform packages.
-        checks = let
-          packages = pkgs.callPackages ./pkgs {
-            inherit craneLib;
-          };
-          prefixAttrs = prefix: attrs:
-            mapAttrs' (name: value: {
-              name = "${prefix}${name}";
-              value = value.overrideAttrs (prev: {
-                pname = "${prefix}${prev.pname}";
+        # Supported and tested list of deployment targets.
+        systems = [
+          "x86_64-linux"
+          "aarch64-linux"
+          "armv7l-linux"
+          "armv6l-linux"
+        ];
+        perSystem =
+          {
+            config,
+            system,
+            pkgs,
+            ...
+          }:
+          let
+            inherit (lib.attrsets) mapAttrs';
+            inherit (lib.lists) elem;
+            # Can also be built for darwin, through it is not usual to deploy nixos systems from macos machines.
+            # I have no hardware for such testing, thus only adding machines I actually have and use.
+            #
+            # It is not possible to deploy any host from armv6/armv7 hardware, and I don't think it even makes sense.
+            deployerSystems = [
+              "aarch64-linux"
+              "x86_64-linux"
+            ];
+            deployerSystem = elem system deployerSystems;
+            lib = pkgs.lib;
+            rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
+            craneLib = (inputs.crane.mkLib pkgs).overrideToolchain rust;
+          in
+          {
+            _module.args.pkgs = import inputs.nixpkgs {
+              inherit system;
+              overlays = [ (inputs.rust-overlay.overlays.default) ];
+            };
+            # Reference fleet package should be built with nightly rust, specified in rust-toolchain.toml.
+            packages = lib.mkIf deployerSystem (
+              let
+                packages = pkgs.callPackages ./pkgs {
+                  inherit craneLib;
+                };
+              in
+              packages // { default = packages.fleet; }
+            );
+            # fleet-install-secrets will not be built normally, because they are not ran directly by user most of the time.
+            # checks there build packages for default nixpkgs rustPlatform packages.
+            checks =
+              let
+                nixpkgsCraneLib = inputs.crane.mkLib pkgs;
+                packages = pkgs.callPackages ./pkgs {
+                  craneLib = nixpkgsCraneLib;
+                };
+                prefixAttrs =
+                  prefix: attrs:
+                  mapAttrs' (name: value: {
+                    name = "${prefix}${name}";
+                    value = value.overrideAttrs (prev: {
+                      pname = "${prefix}${prev.pname}";
+                    });
+                  }) attrs;
+              in
+              # fleet-install-secrets is installed to remote systems, thus needs to work
+              # with rust in nixpkgs.
+              (prefixAttrs "nixpkgs-" {
+                inherit (packages) fleet-install-secrets;
               });
-            })
-            attrs;
-        in
-          # `fleet` crate wants nightly rust, also little sense of supporting it on stable nixpkgs.
-          (prefixAttrs "nixpkgs-" (removeAttrs packages ["fleet"]));
-        # TODO: It should be possible to move lib.mkIf to default attribute, instead of disabling the whole
-        # devShells block, yet nix flake check fails here, due to no default shell found. It is nix or flake-parts bug?
-        devShells = lib.mkIf deployerSystem {
-          default = craneLib.devShell {
-            packages = with pkgs; [
-              rust
-              alejandra
-              cargo-edit
-              cargo-udeps
-              cargo-fuzz
-              cargo-watch
-              cargo-outdated
+            # TODO: It should be possible to move lib.mkIf to default attribute, instead of disabling the whole
+            # devShells block, yet nix flake check fails here, due to no default shell found. It is nix or flake-parts bug?
+            shelly.shells.default = lib.mkIf deployerSystem {
+              factory = craneLib.devShell;
+              packages = with pkgs; [
+                rust
+                alejandra
+                cargo-edit
+                cargo-udeps
+                cargo-fuzz
+                cargo-watch
+                cargo-outdated
 
-              pkg-config
-              openssl
-              bacon
-              nil
-              rustPlatform.bindgenHook
-              nixVersions.nix_2_22
-            ];
-            env.PROTOC = "${pkgs.protobuf}/bin/protoc";
+                pkg-config
+                openssl
+                bacon
+                nil
+                rustPlatform.bindgenHook
+                nixVersions.nix_2_22
+              ];
+              environment.PROTOC = "${pkgs.protobuf}/bin/protoc";
+            };
+            formatter = pkgs.alejandra;
           };
-        };
-        formatter = pkgs.alejandra;
       };
-    };
 }
modifiedlib/flakePart.nixdiffbeforeafterboth
--- a/lib/flakePart.nix
+++ b/lib/flakePart.nix
@@ -65,11 +65,12 @@
                       else data;
                     nixpkgs.buildUsing = mkOptionDefault bootstrapNixpkgs;
                     nixpkgs.overlays = [
-                      (final: prev:
-                        import ../pkgs {
+                      (final: prev: {
+                        inherit (import ../pkgs {
                           inherit (prev) callPackage;
                           craneLib = crane.mkLib prev;
-                        })
+                        }) fleet-install-secrets;
+                      })
                     ];
                   };
                 }
modifiedrust-toolchain.tomldiffbeforeafterboth
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,3 +1,3 @@
 [toolchain]
-channel = "nightly-2024-11-12"
+channel = "1.85.0"
 components = ["rustfmt", "clippy", "rust-analyzer", "rust-src"]