git.delta.rocks / jrsonnet / refs/commits / 3a7032e3bf89

difftreelog

fix legacy ssh store support

lwkltrupYaroslav Bolyukin2025-09-15parent: #79b689b.patch.diff
in: trunk

4 files changed

modifiedcmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth
before · cmds/fleet/src/cmds/build_systems.rs
1use std::{env::current_dir, os::unix::fs::symlink, path::PathBuf};23use anyhow::Result;4use clap::Parser;5use fleet_base::{6	deploy::{DeployAction, deploy_task, upload_task},7	host::{Config, DeployKind, GenerationStorage},8	opts::FleetOpts,9};10use nix_eval::nix_go;11use tokio::task::LocalSet;12use tracing::{Instrument, error, field, info, info_span, warn};1314#[derive(Parser)]15pub struct Deploy {16	/// Disable automatic rollback17	#[clap(long)]18	disable_rollback: bool,19	/// Action to execute after system is built20	action: DeployAction,21}2223#[derive(Parser, Clone)]24pub struct BuildSystems {25	/// Attribute to build. Systems are deployed from "toplevel" attr, well-known used attributes26	/// are "sdImage"/"isoImage", and your configuration may include any other build attributes.27	#[clap(long, default_value = "toplevel")]28	build_attr: String,29}3031async fn build_task(config: Config, hostname: String, build_attr: &str) -> Result<PathBuf> {32	info!("building");33	let host = config.host(&hostname).await?;34	// let action = Action::from(self.subcommand.clone());35	let nixos = host.nixos_config().await?;36	let drv = nix_go!(nixos.system.build[{ build_attr }]);37	let out_output = drv.build("out").await?;3839	// We already have system profiles for backups.40	if !host.local {41		info!("adding gc root");42		let mut cmd = config.local_host().cmd("nix").await?;43		cmd.arg("build")44			.comparg(45				"--profile",46				format!(47					"/nix/var/nix/profiles/{}-{hostname}",48					config.data().gc_root_prefix49				),50			)51			.arg(&out_output);52		cmd.sudo().run_nix().await?;53	}5455	Ok(out_output)56}5758impl BuildSystems {59	pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {60		let hosts = opts.filter_skipped(config.list_hosts().await?).await?;61		let set = LocalSet::new();62		let build_attr = self.build_attr.clone();63		for host in hosts {64			let config = config.clone();65			let span = info_span!("build", host = field::display(&host.name));66			let hostname = host.name;67			let build_attr = build_attr.clone();68			set.spawn_local(69				(async move {70					let built = match build_task(config, hostname.clone(), &build_attr).await {71						Ok(path) => path,72						Err(e) => {73							error!("failed to deploy host: {}", e);74							return;75						}76					};77					// TODO: Handle error78					let mut out = current_dir().expect("cwd exists");79					out.push(format!("built-{hostname}"));8081					info!("linking iso image to {:?}", out);82					if let Err(e) = symlink(built, out) {83						error!("failed to symlink: {e}")84					}85				})86				.instrument(span),87			);88		}89		set.await;90		Ok(())91	}92}9394impl Deploy {95	pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {96		let hosts = opts.filter_skipped(config.list_hosts().await?).await?;97		let set = LocalSet::new();98		for host in hosts.into_iter() {99			let config = config.clone();100			let span = info_span!("deploy", host = field::display(&host.name));101			let hostname = host.name.clone();102			let opts = opts.clone();103			if let Some(deploy_kind) = opts.action_attr::<DeployKind>(&host, "deploy_kind").await? {104				host.set_deploy_kind(deploy_kind);105			};106			if let Some(destination) = opts.action_attr::<String>(&host, "dest").await? {107				host.set_session_destination(destination);108			};109110			set.spawn_local(111				(async move {112					let built = match build_task(config.clone(), hostname.clone(), "toplevel").await113					{114						Ok(path) => path,115						Err(e) => {116							error!("failed to build host system closure: {:#}", e);117							return;118						}119					};120121					let deploy_kind = match host.deploy_kind().await {122						Ok(v) => v,123						Err(e) => {124							error!("failed to query target deploy kind: {e}");125							return;126						}127					};128129					// TODO: Make disable_rollback a host attribute instead130					let mut disable_rollback = self.disable_rollback;131					if !disable_rollback && deploy_kind != DeployKind::Fleet {132						warn!("disabling rollback, as not supported by non-fleet deployment kinds");133						disable_rollback = true;134					}135136					let remote_path =137						match upload_task(&config, &host, GenerationStorage::Deployer, built).await138						{139							Ok(v) => v,140							Err(e) => {141								error!("upload failed: {e}");142								return;143							}144						};145146					if let Err(e) = deploy_task(147						self.action,148						&host,149						remote_path,150						match opts.action_attr(&host, "specialisation").await {151							Ok(v) => v,152							_ => {153								error!("unreachable? failed to get specialization");154								return;155							}156						},157						disable_rollback,158					)159					.await160					{161						error!("activation failed: {e}");162					}163				})164				.instrument(span),165			);166		}167		set.await;168		Ok(())169	}170}
after · cmds/fleet/src/cmds/build_systems.rs
1use std::{env::current_dir, os::unix::fs::symlink, path::PathBuf};23use anyhow::Result;4use clap::Parser;5use fleet_base::{6	deploy::{DeployAction, deploy_task, upload_task},7	host::{Config, DeployKind, GenerationStorage},8	opts::FleetOpts,9};10use nix_eval::nix_go;11use tokio::task::LocalSet;12use tracing::{Instrument, error, field, info, info_span, warn};1314#[derive(Parser)]15pub struct Deploy {16	/// Disable automatic rollback17	#[clap(long)]18	disable_rollback: bool,19	/// Action to execute after system is built20	action: DeployAction,21}2223#[derive(Parser, Clone)]24pub struct BuildSystems {25	/// Attribute to build. Systems are deployed from "toplevel" attr, well-known used attributes26	/// are "sdImage"/"isoImage", and your configuration may include any other build attributes.27	#[clap(long, default_value = "toplevel")]28	build_attr: String,29}3031async fn build_task(config: Config, hostname: String, build_attr: &str) -> Result<PathBuf> {32	info!("building");33	let host = config.host(&hostname).await?;34	// let action = Action::from(self.subcommand.clone());35	let nixos = host.nixos_config().await?;36	let drv = nix_go!(nixos.system.build[{ build_attr }]);37	let out_output = drv.build("out").await?;3839	// We already have system profiles for backups.40	if !host.local {41		info!("adding gc root");42		let mut cmd = config.local_host().cmd("nix").await?;43		cmd.arg("build")44			.comparg(45				"--profile",46				format!(47					"/nix/var/nix/profiles/{}-{hostname}",48					config.data().gc_root_prefix49				),50			)51			.arg(&out_output);52		cmd.sudo().run_nix().await?;53	}5455	Ok(out_output)56}5758impl BuildSystems {59	pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {60		let hosts = opts.filter_skipped(config.list_hosts().await?).await?;61		let set = LocalSet::new();62		let build_attr = self.build_attr.clone();63		for host in hosts {64			let config = config.clone();65			let span = info_span!("build", host = field::display(&host.name));66			let hostname = host.name;67			let build_attr = build_attr.clone();68			set.spawn_local(69				(async move {70					let built = match build_task(config, hostname.clone(), &build_attr).await {71						Ok(path) => path,72						Err(e) => {73							error!("failed to deploy host: {}", e);74							return;75						}76					};77					// TODO: Handle error78					let mut out = current_dir().expect("cwd exists");79					out.push(format!("built-{hostname}"));8081					info!("linking iso image to {:?}", out);82					if let Err(e) = symlink(built, out) {83						error!("failed to symlink: {e}")84					}85				})86				.instrument(span),87			);88		}89		set.await;90		Ok(())91	}92}9394impl Deploy {95	pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {96		let hosts = opts.filter_skipped(config.list_hosts().await?).await?;97		let set = LocalSet::new();98		for host in hosts.into_iter() {99			let config = config.clone();100			let span = info_span!("deploy", host = field::display(&host.name));101			let hostname = host.name.clone();102			let opts = opts.clone();103			if let Some(deploy_kind) = opts.action_attr::<DeployKind>(&host, "deploy_kind").await? {104				host.set_deploy_kind(deploy_kind);105			};106			if let Some(destination) = opts.action_attr::<String>(&host, "dest").await? {107				host.set_session_destination(destination);108			};109			if let Some(legacy) = opts.action_attr::<bool>(&host, "legacy_ssh_store").await? {110				host.set_legacy_ssh_store(legacy);111			};112113			set.spawn_local(114				(async move {115					let built = match build_task(config.clone(), hostname.clone(), "toplevel").await116					{117						Ok(path) => path,118						Err(e) => {119							error!("failed to build host system closure: {:#}", e);120							return;121						}122					};123124					let deploy_kind = match host.deploy_kind().await {125						Ok(v) => v,126						Err(e) => {127							error!("failed to query target deploy kind: {e}");128							return;129						}130					};131132					// TODO: Make disable_rollback a host attribute instead133					let mut disable_rollback = self.disable_rollback;134					if !disable_rollback && deploy_kind != DeployKind::Fleet {135						warn!("disabling rollback, as not supported by non-fleet deployment kinds");136						disable_rollback = true;137					}138139					let remote_path =140						match upload_task(&config, &host, GenerationStorage::Deployer, built).await141						{142							Ok(v) => v,143							Err(e) => {144								error!("upload failed: {e}");145								return;146							}147						};148149					if let Err(e) = deploy_task(150						self.action,151						&host,152						remote_path,153						match opts.action_attr(&host, "specialisation").await {154							Ok(v) => v,155							_ => {156								error!("unreachable? failed to get specialization");157								return;158							}159						},160						disable_rollback,161					)162					.await163					{164						error!("activation failed: {e}");165					}166				})167				.instrument(span),168			);169		}170		set.await;171		Ok(())172	}173}
modifiedcrates/fleet-base/src/host.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/host.rs
+++ b/crates/fleet-base/src/host.rs
@@ -13,7 +13,7 @@
 use anyhow::{Context, Result, anyhow, bail, ensure};
 use fleet_shared::SecretData;
 use nix_eval::{Value, nix_go, nix_go_json, util::assert_warn};
-use openssh::SessionBuilder;
+use openssh::{ControlPersist, SessionBuilder};
 use serde::de::DeserializeOwned;
 use tabled::Tabled;
 use tempfile::NamedTempFile;
@@ -99,6 +99,7 @@
 	// TODO: Both of those values are taken from host opts, there should be a cleaner way to specify it
 	deploy_kind: OnceCell<DeployKind>,
 	session_destination: OnceCell<String>,
+	legacy_ssh_store: OnceCell<bool>,
 
 	pub host_config: Option<Value>,
 	pub nixos_config: OnceCell<Value>,
@@ -219,6 +220,11 @@
 			.set(kind)
 			.expect("deploy kind is already set");
 	}
+	pub fn set_legacy_ssh_store(&self, legacy: bool) {
+		self.legacy_ssh_store
+			.set(legacy)
+			.expect("legacy ssh store is already set")
+	}
 	pub async fn deploy_kind(&self) -> Result<DeployKind> {
 		if let Some(kind) = self.deploy_kind.get() {
 			return Ok(*kind);
@@ -263,7 +269,8 @@
 		if let Some(session) = &self.session.get() {
 			return Ok((*session).clone());
 		};
-		let session = SessionBuilder::default();
+		let mut session = SessionBuilder::default();
+		session.control_persist(ControlPersist::ClosedAfterInitialConnection);
 
 		let dest = self.session_destination.get().unwrap_or(&self.name);
 		let session = session
@@ -418,9 +425,15 @@
 		);
 		nix.arg("copy").arg("--substitute-on-destination");
 
+		let proto = if self.legacy_ssh_store.get().cloned().unwrap_or(false) {
+			"ssh"
+		} else {
+			"ssh-ng"
+		};
+
 		match self.deploy_kind().await? {
 			DeployKind::Fleet | DeployKind::UpgradeToFleet | DeployKind::NixosLustrate => {
-				nix.comparg("--to", format!("ssh-ng://{}", self.name));
+				nix.comparg("--to", format!("{proto}://{}", self.name));
 			}
 			DeployKind::NixosInstall => {
 				nix
@@ -428,7 +441,7 @@
 					.arg("--no-check-sigs")
 					.comparg(
 						"--to",
-						format!("ssh-ng://root@{}?remote-store=/mnt", self.name),
+						format!("{proto}://root@{}?remote-store=/mnt", self.name),
 					);
 			}
 		}
@@ -568,6 +581,7 @@
 			session: OnceLock::new(),
 			deploy_kind: OnceCell::new(),
 			session_destination: OnceCell::new(),
+			legacy_ssh_store: OnceCell::new(),
 		}
 	}
 
@@ -589,6 +603,7 @@
 			session: OnceLock::new(),
 			deploy_kind: OnceCell::new(),
 			session_destination: OnceCell::new(),
+			legacy_ssh_store: OnceCell::new(),
 		})
 	}
 	pub async fn list_hosts(&self) -> Result<Vec<ConfigHost>> {
modifiedcrates/fleet-shared/src/encoding.rsdiffbeforeafterboth
--- a/crates/fleet-shared/src/encoding.rs
+++ b/crates/fleet-shared/src/encoding.rs
@@ -1,6 +1,5 @@
 use std::{
-	fmt::{self, Display},
-	str::FromStr,
+	collections::BTreeMap, fmt::{self, Display}, str::FromStr
 };
 
 use base64::engine::{Engine, general_purpose::STANDARD_NO_PAD};
modifiedflake.nixdiffbeforeafterboth
--- a/flake.nix
+++ b/flake.nix
@@ -168,12 +168,9 @@
                 cargo-fuzz
                 cargo-watch
                 cargo-outdated
-                gdb
 
                 pkg-config
                 openssl
-                bacon
-                nil
                 rustPlatform.bindgenHook
                 inputs'.nix.packages.nix-expr-c
                 inputs'.nix.packages.nix-flake-c