difftreelog
fix legacy ssh store support
in: trunk
4 files changed
cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth1use 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}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}crates/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>> {
crates/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};
flake.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