difftreelog
feat nixos-install target
in: trunk
6 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -924,7 +924,6 @@
"hostname",
"human-repr",
"indicatif",
- "indoc",
"itertools 0.13.0",
"nix-eval",
"nixlike",
@@ -958,6 +957,7 @@
"fleet-shared",
"futures",
"hostname",
+ "indoc",
"itertools 0.13.0",
"nix-eval",
"nixlike",
cmds/fleet/Cargo.tomldiffbeforeafterboth--- a/cmds/fleet/Cargo.toml
+++ b/cmds/fleet/Cargo.toml
@@ -47,7 +47,6 @@
nix-eval.workspace = true
nom = "7.1.3"
fleet-base = { version = "0.1.0", path = "../../crates/fleet-base" }
-indoc = "2.0.6"
[features]
default = ["indicatif"]
cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth1use std::{env::current_dir, os::unix::fs::symlink, path::PathBuf, str::FromStr, time::Duration};1use std::{env::current_dir, os::unix::fs::symlink, path::PathBuf, time::Duration};223use anyhow::{anyhow, bail, Result};3use anyhow::{anyhow, bail, Result};4use clap::{Parser, ValueEnum};4use clap::{Parser, ValueEnum};5use fleet_base::{5use fleet_base::{6 host::{Config, ConfigHost},6 host::{Config, ConfigHost, DeployKind},7 opts::FleetOpts,7 opts::FleetOpts,8};8};9use itertools::Itertools as _;9use itertools::Itertools as _;131 specialisation: Option<String>,131 specialisation: Option<String>,132 disable_rollback: bool,132 disable_rollback: bool,133) -> Result<()> {133) -> Result<()> {134 let deploy_kind = host.deploy_kind().await?;135 if deploy_kind == DeployKind::NixosInstall136 && !matches!(action, DeployAction::Boot | DeployAction::Upload)137 {138 bail!("nixos-install deploy kind only supports boot and upload actions");139 }140134 let mut failed = false;141 let mut failed = false;135142178 }185 }179 }186 }180181 if action.should_switch_profile() && !failed {187 if deploy_kind == DeployKind::NixosInstall {182 info!("switching system profile generation");188 info!(183 // It would also be possible to update profile atomically during copy:189 "running nixos-install to switch profile, install bootloader, and perform activation"184 // https://github.com/NixOS/nix/pull/11657190 );185 let mut cmd = host.cmd("nix").await?;191 let mut cmd = host.cmd("nixos-install").await?;186 cmd.arg("build");192 cmd.arg("--system").arg(&built).args([187 cmd.comparg("--profile", "/nix/var/nix/profiles/system");193 // Channels here aren't fleet host system channels, but channels embedded in installation cd, which might be old.188 cmd.arg(&built);194 // It is possible to copy host channels, but I would prefer non-flake nix just to be unsupported.189 if let Err(e) = cmd.sudo().run_nix().await {195 "--no-channel-copy",190 error!("failed to switch system profile generation: {e}");196 "--root",191 failed = true;197 "/mnt",192 }198 ]);199 if let Err(e) = cmd.sudo().run().await {200 error!("failed to execute nixos-install: {e}");201 failed = true;202 }193 }203 } else {204 if action.should_switch_profile() && !failed {205 info!("switching system profile generation");206207 // To avoid even more problems, using nixos-install for now.208 // // nix build is unable to work with --store argument for some reason, and nix until 2.26 didn't support copy with --profile argument,209 // // falling back to using nix-env command210 // // After stable NixOS starts using 2.26 - use `nix --store /mnt copy --from /mnt --profile ...` here, and instead of nix build below.211 // let mut cmd = host.cmd("nix-env").await?;212 // cmd.args([213 // "--store",214 // "/mnt",215 // "--profile",216 // "/mnt/nix/var/nix/profiles/system",217 // "--set",218 // ])219 // .arg(&built);220 // if let Err(e) = cmd.sudo().run_nix().await {221 // error!("failed to switch system profile generation: {e}");222 // failed = true;223 // }224 // It would also be possible to update profile atomically during copy:225 // https://github.com/NixOS/nix/pull/11657226 let mut cmd = host.nix_cmd().await?;227 cmd.arg("build");228 cmd.comparg("--profile", "/nix/var/nix/profiles/system");229 cmd.arg(&built);230 if let Err(e) = cmd.sudo().run_nix().await {231 error!("failed to switch system profile generation: {e}");232 failed = true;233 }234 }194235195 // FIXME: Connection might be disconnected after activation run236 // FIXME: Connection might be disconnected after activation run196237212 failed = true;253 failed = true;213 }254 }214 }255 }256 }215 if action.should_create_rollback_marker() {257 if action.should_create_rollback_marker() {216 if !disable_rollback {258 if !disable_rollback {217 if failed {259 if failed {333 }375 }334}376}335336#[derive(Clone, PartialEq, Copy)]337enum DeployKind {338 // NixOS => NixOS managed by fleet339 UpgradeToFleet,340 // NixOS managed by fleet => NixOS managed by fleet341 Fleet,342}343impl FromStr for DeployKind {344 type Err = anyhow::Error;345 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {346 match s {347 "upgrade-to-fleet" => Ok(Self::UpgradeToFleet),348 "fleet" => Ok(Self::Fleet),349 v => bail!("unknown deploy_kind: {v}; expected on of \"upgrade-to-fleet\", \"fleet\""),350 }351 }352}353377354impl Deploy {378impl Deploy {355 pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {379 pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {367 let local_host = config.local_host();391 let local_host = config.local_host();368 let opts = opts.clone();392 let opts = opts.clone();369 let batch = batch.clone();393 let batch = batch.clone();370 let mut deploy_kind: Option<DeployKind> =394 if let Some(deploy_kind) = opts.action_attr::<DeployKind>(&host, "deploy_kind").await? {371 opts.action_attr(&host, "deploy_kind").await?;395 host.set_deploy_kind(deploy_kind);396 };372397373 set.spawn_local(398 set.spawn_local(374 (async move {399 (async move {382 }407 }383 };408 };409384 if deploy_kind == None {410 let deploy_kind = match host.deploy_kind().await {385 let is_fleet_managed = match host.file_exists("/etc/FLEET_HOST").await {386 Ok(v) => v,411 Ok(v) => v,387 Err(e) => {412 Err(e) => {388 error!("failed to query remote system kind: {}", e);413 error!("failed to query target deploy kind: {e}");389 return;414 return;390 },415 }391 };416 };392 if !is_fleet_managed {393 error!(indoc::indoc!{"394 host is not marked as managed by fleet395 if you're not trying to lustrate/install system from scratch,396 you should either397 1. manually create /etc/FLEET_HOST file on the target host,398 2. use ?deploy_kind=fleet host argument if you're upgrading from older version of fleet399 3. use ?deploy_kind=upgrade_to_fleet if you're upgrading from plain nixos to fleet-managed nixos400 "});401 return;402 }403 deploy_kind = Some(DeployKind::Fleet);404 }405 let deploy_kind = deploy_kind.expect("deploy_kind is set");406417407 // TODO: Make disable_rollback a host attribute instead418 // TODO: Make disable_rollback a host attribute instead408 let mut disable_rollback = self.disable_rollback;419 let mut disable_rollback = self.disable_rollback;crates/fleet-base/Cargo.tomldiffbeforeafterboth--- a/crates/fleet-base/Cargo.toml
+++ b/crates/fleet-base/Cargo.toml
@@ -13,6 +13,7 @@
fleet-shared.workspace = true
futures = "0.3.30"
hostname = "0.4.0"
+indoc = "2.0.6"
itertools = "0.13.0"
nix-eval.workspace = true
nixlike.workspace = true
crates/fleet-base/src/host.rsdiffbeforeafterboth--- a/crates/fleet-base/src/host.rs
+++ b/crates/fleet-base/src/host.rs
@@ -58,11 +58,35 @@
Su,
}
+#[derive(Clone, PartialEq, Copy)]
+pub enum DeployKind {
+ /// NixOS => NixOS managed by fleet
+ UpgradeToFleet,
+ /// NixOS managed by fleet => NixOS managed by fleet
+ Fleet,
+ /// Remote host has /mnt, /mnt/boot mounted,
+ /// generated config is added to fleet configuration.
+ NixosInstall,
+}
+
+impl FromStr for DeployKind {
+ type Err = anyhow::Error;
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ match s {
+ "upgrade-to-fleet" => Ok(Self::UpgradeToFleet),
+ "fleet" => Ok(Self::Fleet),
+ "nixos-install" => Ok(Self::NixosInstall),
+ v => bail!("unknown deploy_kind: {v}; expected on of \"upgrade-to-fleet\", \"fleet\", \"nixos-install\""),
+ }
+ }
+}
pub struct ConfigHost {
config: Config,
pub name: String,
groups: OnceCell<Vec<String>>,
+ deploy_kind: OnceCell<DeployKind>,
+
pub host_config: Option<Value>,
pub nixos_config: OnceCell<Value>,
pub pkgs_override: Option<Value>,
@@ -73,6 +97,40 @@
}
// TODO: Move command helpers away with connectivity refactor
impl ConfigHost {
+ pub fn set_deploy_kind(&self, kind: DeployKind) {
+ self.deploy_kind
+ .set(kind)
+ .ok()
+ .expect("deploy kind is already set");
+ }
+ pub async fn deploy_kind(&self) -> Result<DeployKind> {
+ if let Some(kind) = self.deploy_kind.get() {
+ return Ok(kind.clone());
+ }
+ let is_fleet_managed = match self.file_exists("/etc/FLEET_HOST").await {
+ Ok(v) => v,
+ Err(e) => {
+ bail!("failed to query remote system kind: {}", e);
+ }
+ };
+ if !is_fleet_managed {
+ bail!(indoc::indoc! {"
+ host is not marked as managed by fleet
+ if you're not trying to lustrate/install system from scratch,
+ you should either
+ 1. manually create /etc/FLEET_HOST file on the target host,
+ 2. use ?deploy_kind=fleet host argument if you're upgrading from older version of fleet
+ 3. use ?deploy_kind=upgrade_to_fleet if you're upgrading from plain nixos to fleet-managed nixos
+ "});
+ }
+ // TOCTOU is possible
+ let _ = self.deploy_kind.set(DeployKind::Fleet);
+ Ok(self
+ .deploy_kind
+ .get()
+ .expect("deploy kind is just set")
+ .clone())
+ }
pub async fn escalation_strategy(&self) -> Result<EscalationStrategy> {
// Prefer sudo, as run0 has some gotchas with polkit
// and too many repeating prompts.
@@ -189,6 +247,16 @@
Ok(MyCommand::new_on(escalation, cmd, session))
}
}
+ pub async fn nix_cmd(&self) -> Result<MyCommand> {
+ let mut nix = self.cmd("nix").await?;
+ nix.args([
+ "--extra-experimental-features",
+ "nix-command",
+ "--extra-experimental-features",
+ "flakes",
+ ]);
+ Ok(nix)
+ }
pub async fn decrypt(&self, data: SecretData) -> Result<Vec<u8>> {
ensure!(data.encrypted, "secret is not encrypted");
@@ -231,10 +299,23 @@
EscalationStrategy::Su,
"nix",
);
- nix.arg("copy")
- .arg("--substitute-on-destination")
- .comparg("--to", format!("ssh-ng://{}", self.name))
- .arg(path);
+ nix.arg("copy").arg("--substitute-on-destination");
+
+ match self.deploy_kind().await? {
+ DeployKind::Fleet | DeployKind::UpgradeToFleet => {
+ nix.comparg("--to", format!("ssh-ng://{}", self.name));
+ }
+ DeployKind::NixosInstall => {
+ nix
+ // Signature checking makes no sense with remote-store store argument set, as we're not even interacting with remote nix daemon
+ .arg("--no-check-sigs")
+ .comparg(
+ "--to",
+ format!("ssh-ng://root@{}-install?remote-store=/mnt", self.name),
+ );
+ }
+ }
+ nix.arg(path);
nix.run_nix().await.context("nix copy")?;
Ok(path.to_owned())
}
@@ -354,6 +435,7 @@
local: true,
session: OnceLock::new(),
+ deploy_kind: OnceCell::new(),
}
}
@@ -372,6 +454,7 @@
// TODO: Remove with connectivit refactor
local: self.localhost == name,
session: OnceLock::new(),
+ deploy_kind: OnceCell::new(),
})
}
pub async fn list_hosts(&self) -> Result<Vec<ConfigHost>> {
modules/nixos/meta.nixdiffbeforeafterboth--- a/modules/nixos/meta.nix
+++ b/modules/nixos/meta.nix
@@ -13,5 +13,13 @@
];
# Version of environment (fleet scripts such as rollback) already installed on the host
- config.environment.etc.FLEET_HOST.text = "1";
+ config = {
+ environment.etc.FLEET_HOST.text = "1";
+
+ # Flake/nix command support is assumed by fleet, lets add it here to avoid potential problems.
+ nix.settings.experimental-features = [
+ "nix-command"
+ "flakes"
+ ];
+ };
}