git.delta.rocks / jrsonnet / refs/commits / 3972fee37ee3

difftreelog

feat explicitly mark hosts as managed by fleet

Lach2025-04-05parent: #a1a72ce.patch.diff
in: trunk

7 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -924,6 +924,7 @@
  "hostname",
  "human-repr",
  "indicatif",
+ "indoc",
  "itertools 0.13.0",
  "nix-eval",
  "nixlike",
@@ -1537,6 +1538,12 @@
 ]
 
 [[package]]
+name = "indoc"
+version = "2.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
+
+[[package]]
 name = "inout"
 version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
modifiedcmds/fleet/Cargo.tomldiffbeforeafterboth
before · cmds/fleet/Cargo.toml
1[package]2name = "fleet"3description = "NixOS configuration management"4version = "0.2.0"5authors = ["Yaroslav Bolyukin <iam@lach.pw>"]6edition.workspace = true7rust-version.workspace = true89[dependencies]10nixlike.workspace = true11better-command.workspace = true12tokio.workspace = true13clap.workspace = true14clap_complete.workspace = true15age = { workspace = true, features = ["armor"] }16anyhow.workspace = true17tracing.workspace = true18tracing-subscriber.workspace = true19serde.workspace = true20serde_json.workspace = true21tempfile.workspace = true22time = { version = "0.3", features = ["serde"] }23hostname = "0.4.0"24age-core = "0.11"25peg = "0.8"26base64 = "0.22.1"27chrono = { version = "0.4", features = ["serde"] }28tokio-util = { version = "0.7", features = ["codec"] }29async-trait = "0.1"30futures = "0.3"31itertools = "0.13"32shlex = "1.3"33tabled = { version = "0.16" }34owo-colors = { version = "4.0", features = [35	"supports-color",36	"supports-colors",37] }38abort-on-drop = "0.2"39regex = "1.10"40openssh = "0.11"41crossterm = { version = "0.28.0", features = ["use-dev-tty"] }42fleet-shared.workspace = true4344tracing-indicatif = { version = "0.3", optional = true }45human-repr = { version = "1.1", optional = true }46indicatif = { version = "0.17", optional = true }47nix-eval.workspace = true48nom = "7.1.3"49fleet-base = { version = "0.1.0", path = "../../crates/fleet-base" }5051[features]52default = ["indicatif"]53# Not quite stable54indicatif = [55	"dep:tracing-indicatif",56	"dep:indicatif",57	"dep:human-repr",58	"better-command/indicatif",59]
modifiedcmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth
--- a/cmds/fleet/src/cmds/build_systems.rs
+++ b/cmds/fleet/src/cmds/build_systems.rs
@@ -1,6 +1,6 @@
-use std::{env::current_dir, os::unix::fs::symlink, path::PathBuf, time::Duration};
+use std::{env::current_dir, os::unix::fs::symlink, path::PathBuf, str::FromStr, time::Duration};
 
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, bail, Result};
 use clap::{Parser, ValueEnum};
 use fleet_base::{
 	host::{Config, ConfigHost},
@@ -132,6 +132,7 @@
 	disable_rollback: bool,
 ) -> Result<()> {
 	let mut failed = false;
+
 	// TODO: Lockfile, to prevent concurrent system switch?
 	// TODO: If rollback target exists - bail, it should be removed. Lockfile will not work in case if rollback
 	// is scheduler on next boot (default behavior). On current boot - rollback activator will fail due to
@@ -332,6 +333,24 @@
 	}
 }
 
+#[derive(Clone, PartialEq, Copy)]
+enum DeployKind {
+	// NixOS => NixOS managed by fleet
+	UpgradeToFleet,
+	// NixOS managed by fleet => NixOS managed by fleet
+	Fleet,
+}
+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),
+			v => bail!("unknown deploy_kind: {v}; expected on of \"upgrade-to-fleet\", \"fleet\""),
+		}
+	}
+}
+
 impl Deploy {
 	pub async fn run(self, config: &Config, opts: &FleetOpts) -> Result<()> {
 		let hosts = opts.filter_skipped(config.list_hosts().await?).await?;
@@ -348,6 +367,8 @@
 			let local_host = config.local_host();
 			let opts = opts.clone();
 			let batch = batch.clone();
+			let mut deploy_kind: Option<DeployKind> =
+				opts.action_attr(&host, "deploy_kind").await?;
 
 			set.spawn_local(
 				(async move {
@@ -356,10 +377,40 @@
 						{
 							Ok(path) => path,
 							Err(e) => {
-								error!("failed to deploy host: {}", e);
+								error!("failed to build host system closure: {}", e);
 								return;
 							}
 						};
+					if deploy_kind == None {
+						let is_fleet_managed = match host.file_exists("/etc/FLEET_HOST").await {
+							Ok(v) => v,
+							Err(e) => {
+								error!("failed to query remote system kind: {}", e);
+								return;
+							},
+						};
+						if !is_fleet_managed {
+							error!(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
+							"});
+							return;
+						}
+						deploy_kind = Some(DeployKind::Fleet);
+					}
+					let deploy_kind = deploy_kind.expect("deploy_kind is set");
+
+					// TODO: Make disable_rollback a host attribute instead
+					let mut disable_rollback = self.disable_rollback;
+					if !disable_rollback && deploy_kind != DeployKind::Fleet {
+						warn!("disabling rollback, as not supported by non-fleet deployment kinds");
+						disable_rollback = true;
+					}
+
 					if !opts.is_local(&hostname) {
 						info!("uploading system closure");
 						{
@@ -411,7 +462,7 @@
 							error!("unreachable? failed to get specialization");
 							return;
 						},
-						self.disable_rollback,
+						disable_rollback,
 					)
 					.await
 					{
modifiedcmds/fleet/src/main.rsdiffbeforeafterboth
--- a/cmds/fleet/src/main.rs
+++ b/cmds/fleet/src/main.rs
@@ -66,9 +66,9 @@
 
 #[derive(Parser)]
 enum Opts {
-	/// Prepare systems for deployments
+	/// Build system closures
 	BuildSystems(BuildSystems),
-
+	/// Upload and switch system closures
 	Deploy(Deploy),
 	/// Secret management
 	#[clap(subcommand)]
modifiedcrates/fleet-base/src/command.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/command.rs
+++ b/crates/fleet-base/src/command.rs
@@ -5,6 +5,7 @@
 use futures::StreamExt;
 use itertools::Either;
 use openssh::{OverSsh, OwningCommand, Session};
+use serde::de::DeserializeOwned;
 use tokio::{io::AsyncRead, process::Command, select};
 use tokio_util::codec::{BytesCodec, FramedRead, LinesCodec};
 use tracing::debug;
@@ -230,6 +231,10 @@
 		let bytes = self.run_bytes().await?;
 		Ok(String::from_utf8(bytes)?)
 	}
+	pub async fn run_value<T: DeserializeOwned>(self) -> Result<T> {
+		let v = self.run_string().await?;
+		Ok(serde_json::from_str(&v)?)
+	}
 	pub async fn run_bytes(self) -> Result<Vec<u8>> {
 		let str = self.clone().into_string();
 		let cmd = self.wrap_sudo_if_needed().into_command()?;
modifiedcrates/fleet-base/src/host.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/host.rs
+++ b/crates/fleet-base/src/host.rs
@@ -105,6 +105,14 @@
 		let path = cmd.run_string().await?;
 		Ok(path.trim_end().to_owned())
 	}
+	pub async fn file_exists(&self, path: impl AsRef<OsStr>) -> Result<bool> {
+		let mut cmd = self.cmd("sh").await?;
+		cmd.arg("-c")
+			.arg("test -e \"$1\" && echo true || echo false")
+			.arg("_")
+			.arg(path);
+		Ok(cmd.run_value().await?)
+	}
 	pub async fn read_file_bin(&self, path: impl AsRef<OsStr>) -> Result<Vec<u8>> {
 		let mut cmd = self.cmd("cat").await?;
 		cmd.arg(path);
modifiedmodules/nixos/meta.nixdiffbeforeafterboth
--- a/modules/nixos/meta.nix
+++ b/modules/nixos/meta.nix
@@ -1,8 +1,17 @@
-{lib, ...}: let
+{ lib, ... }:
+let
   inherit (lib.modules) mkRemovedOptionModule;
-in {
+in
+{
   imports = [
-    (mkRemovedOptionModule ["tags"] "tags are now defined at the host level, not the nixos system level for fast filtering without evaluating unnecessary hosts.")
-    (mkRemovedOptionModule ["network"] "network is now defined at the host level, not the nixos system level")
+    (mkRemovedOptionModule [ "tags" ]
+      "tags are now defined at the host level, not the nixos system level for fast filtering without evaluating unnecessary hosts."
+    )
+    (mkRemovedOptionModule [
+      "network"
+    ] "network is now defined at the host level, not the nixos system level")
   ];
+
+  # Version of environment (fleet scripts such as rollback) already installed on the host
+  config.environment.etc.FLEET_HOST.text = "1";
 }