difftreelog
feat nixos-install target
in: trunk
6 files changed
Cargo.lockdiffbeforeafterboth924 "hostname",924 "hostname",925 "human-repr",925 "human-repr",926 "indicatif",926 "indicatif",927 "indoc",928 "itertools 0.13.0",927 "itertools 0.13.0",929 "nix-eval",928 "nix-eval",930 "nixlike",929 "nixlike",958 "fleet-shared",957 "fleet-shared",959 "futures",958 "futures",960 "hostname",959 "hostname",960 "indoc",961 "itertools 0.13.0",961 "itertools 0.13.0",962 "nix-eval",962 "nix-eval",963 "nixlike",963 "nixlike",cmds/fleet/Cargo.tomldiffbeforeafterboth47nix-eval.workspace = true47nix-eval.workspace = true48nom = "7.1.3"48nom = "7.1.3"49fleet-base = { version = "0.1.0", path = "../../crates/fleet-base" }49fleet-base = { version = "0.1.0", path = "../../crates/fleet-base" }50indoc = "2.0.6"515052[features]51[features]53default = ["indicatif"]52default = ["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.tomldiffbeforeafterboth13fleet-shared.workspace = true13fleet-shared.workspace = true14futures = "0.3.30"14futures = "0.3.30"15hostname = "0.4.0"15hostname = "0.4.0"16indoc = "2.0.6"16itertools = "0.13.0"17itertools = "0.13.0"17nix-eval.workspace = true18nix-eval.workspace = true18nixlike.workspace = true19nixlike.workspace = truecrates/fleet-base/src/host.rsdiffbeforeafterboth58 Su,58 Su,59}59}606061#[derive(Clone, PartialEq, Copy)]62pub enum DeployKind {63 /// NixOS => NixOS managed by fleet64 UpgradeToFleet,65 /// NixOS managed by fleet => NixOS managed by fleet66 Fleet,67 /// Remote host has /mnt, /mnt/boot mounted,68 /// generated config is added to fleet configuration.69 NixosInstall,70}7172impl FromStr for DeployKind {73 type Err = anyhow::Error;74 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {75 match s {76 "upgrade-to-fleet" => Ok(Self::UpgradeToFleet),77 "fleet" => Ok(Self::Fleet),78 "nixos-install" => Ok(Self::NixosInstall),79 v => bail!("unknown deploy_kind: {v}; expected on of \"upgrade-to-fleet\", \"fleet\", \"nixos-install\""),80 }81 }82}61pub struct ConfigHost {83pub struct ConfigHost {62 config: Config,84 config: Config,63 pub name: String,85 pub name: String,64 groups: OnceCell<Vec<String>>,86 groups: OnceCell<Vec<String>>,8788 deploy_kind: OnceCell<DeployKind>,658966 pub host_config: Option<Value>,90 pub host_config: Option<Value>,67 pub nixos_config: OnceCell<Value>,91 pub nixos_config: OnceCell<Value>,73}97}74// TODO: Move command helpers away with connectivity refactor98// TODO: Move command helpers away with connectivity refactor75impl ConfigHost {99impl ConfigHost {100 pub fn set_deploy_kind(&self, kind: DeployKind) {101 self.deploy_kind102 .set(kind)103 .ok()104 .expect("deploy kind is already set");105 }106 pub async fn deploy_kind(&self) -> Result<DeployKind> {107 if let Some(kind) = self.deploy_kind.get() {108 return Ok(kind.clone());109 }110 let is_fleet_managed = match self.file_exists("/etc/FLEET_HOST").await {111 Ok(v) => v,112 Err(e) => {113 bail!("failed to query remote system kind: {}", e);114 }115 };116 if !is_fleet_managed {117 bail!(indoc::indoc! {"118 host is not marked as managed by fleet119 if you're not trying to lustrate/install system from scratch,120 you should either121 1. manually create /etc/FLEET_HOST file on the target host,122 2. use ?deploy_kind=fleet host argument if you're upgrading from older version of fleet123 3. use ?deploy_kind=upgrade_to_fleet if you're upgrading from plain nixos to fleet-managed nixos124 "});125 }126 // TOCTOU is possible127 let _ = self.deploy_kind.set(DeployKind::Fleet);128 Ok(self129 .deploy_kind130 .get()131 .expect("deploy kind is just set")132 .clone())133 }76 pub async fn escalation_strategy(&self) -> Result<EscalationStrategy> {134 pub async fn escalation_strategy(&self) -> Result<EscalationStrategy> {77 // Prefer sudo, as run0 has some gotchas with polkit135 // Prefer sudo, as run0 has some gotchas with polkit78 // and too many repeating prompts.136 // and too many repeating prompts.189 Ok(MyCommand::new_on(escalation, cmd, session))247 Ok(MyCommand::new_on(escalation, cmd, session))190 }248 }191 }249 }250 pub async fn nix_cmd(&self) -> Result<MyCommand> {251 let mut nix = self.cmd("nix").await?;252 nix.args([253 "--extra-experimental-features",254 "nix-command",255 "--extra-experimental-features",256 "flakes",257 ]);258 Ok(nix)259 }192260193 pub async fn decrypt(&self, data: SecretData) -> Result<Vec<u8>> {261 pub async fn decrypt(&self, data: SecretData) -> Result<Vec<u8>> {194 ensure!(data.encrypted, "secret is not encrypted");262 ensure!(data.encrypted, "secret is not encrypted");231 EscalationStrategy::Su,299 EscalationStrategy::Su,232 "nix",300 "nix",233 );301 );234 nix.arg("copy")302 nix.arg("copy").arg("--substitute-on-destination");235 .arg("--substitute-on-destination")303304 match self.deploy_kind().await? {305 DeployKind::Fleet | DeployKind::UpgradeToFleet => {236 .comparg("--to", format!("ssh-ng://{}", self.name))306 nix.comparg("--to", format!("ssh-ng://{}", self.name));307 }308 DeployKind::NixosInstall => {309 nix310 // Signature checking makes no sense with remote-store store argument set, as we're not even interacting with remote nix daemon311 .arg("--no-check-sigs")312 .comparg(313 "--to",314 format!("ssh-ng://root@{}-install?remote-store=/mnt", self.name),315 );316 }317 }237 .arg(path);318 nix.arg(path);238 nix.run_nix().await.context("nix copy")?;319 nix.run_nix().await.context("nix copy")?;239 Ok(path.to_owned())320 Ok(path.to_owned())240 }321 }354435355 local: true,436 local: true,356 session: OnceLock::new(),437 session: OnceLock::new(),438 deploy_kind: OnceCell::new(),357 }439 }358 }440 }359441372 // TODO: Remove with connectivit refactor454 // TODO: Remove with connectivit refactor373 local: self.localhost == name,455 local: self.localhost == name,374 session: OnceLock::new(),456 session: OnceLock::new(),457 deploy_kind: OnceCell::new(),375 })458 })376 }459 }377 pub async fn list_hosts(&self) -> Result<Vec<ConfigHost>> {460 pub async fn list_hosts(&self) -> Result<Vec<ConfigHost>> {modules/nixos/meta.nixdiffbeforeafterboth13 ];13 ];141415 # Version of environment (fleet scripts such as rollback) already installed on the host15 # Version of environment (fleet scripts such as rollback) already installed on the host16 config.environment.etc.FLEET_HOST.text = "1";16 config = {17 environment.etc.FLEET_HOST.text = "1";1819 # Flake/nix command support is assumed by fleet, lets add it here to avoid potential problems.20 nix.settings.experimental-features = [21 "nix-command"22 "flakes"23 ];24 };17}25}1826