git.delta.rocks / jrsonnet / refs/commits / fcad02a8426f

difftreelog

feat create gc root per built system

Yaroslav Bolyukin2024-11-19parent: #353fd26.patch.diff
in: trunk

5 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -962,6 +962,7 @@
  "nixlike",
  "nom",
  "openssh",
+ "rand",
  "serde",
  "serde_json",
  "tempfile",
modifiedcmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth
--- a/cmds/fleet/src/cmds/build_systems.rs
+++ b/cmds/fleet/src/cmds/build_systems.rs
@@ -253,12 +253,12 @@
 
 async fn build_task(
 	config: Config,
-	host: String,
+	hostname: String,
 	build_attr: &str,
 	batch: Option<NixBuildBatch>,
 ) -> Result<PathBuf> {
 	info!("building");
-	let host = config.host(&host).await?;
+	let host = config.host(&hostname).await?;
 	// let action = Action::from(self.subcommand.clone());
 	let nixos = host.nixos_config().await?;
 	let drv = nix_go!(nixos.system.build[{ build_attr }]);
@@ -267,6 +267,21 @@
 		.get("out")
 		.ok_or_else(|| anyhow!("system build should produce \"out\" output"))?;
 
+	{
+		info!("adding gc root");
+		let mut cmd = config.local_host().cmd("nix").await?;
+		cmd.arg("build")
+			.comparg(
+				"--profile",
+				format!(
+					"/nix/var/nix/profiles/{}-{hostname}",
+					config.data().gc_root_prefix
+				),
+			)
+			.arg(out_output);
+		cmd.sudo().run_nix().await?;
+	}
+
 	Ok(out_output.clone())
 }
 
modifiedcrates/fleet-base/Cargo.tomldiffbeforeafterboth
--- a/crates/fleet-base/Cargo.toml
+++ b/crates/fleet-base/Cargo.toml
@@ -17,6 +17,7 @@
 nixlike.workspace = true
 nom = "7.1.3"
 openssh = "0.11.0"
+rand = "0.8.5"
 serde.workspace = true
 serde_json = "1.0.127"
 tempfile.workspace = true
modifiedcrates/fleet-base/src/fleetdata.rsdiffbeforeafterboth
before · crates/fleet-base/src/fleetdata.rs
1use std::{2	collections::BTreeMap,3	io::{self, Cursor},4};56use age::Recipient;7use chrono::{DateTime, Utc};8use fleet_shared::SecretData;9use serde::{de::Error, Deserialize, Serialize};10use serde_json::Value;1112#[derive(Serialize, Deserialize, Default)]13#[serde(rename_all = "camelCase")]14pub struct HostData {15	#[serde(default)]16	#[serde(skip_serializing_if = "String::is_empty")]17	pub encryption_key: String,18}1920const VERSION: &str = "0.1.0";21pub struct FleetDataVersion;22impl Serialize for FleetDataVersion {23	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>24	where25		S: serde::Serializer,26	{27		VERSION.serialize(serializer)28	}29}30impl<'de> Deserialize<'de> for FleetDataVersion {31	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>32	where33		D: serde::Deserializer<'de>,34	{35		let version = String::deserialize(deserializer)?;36		if version != VERSION {37			return Err(D::Error::custom(format!(38				"fleet.nix data version mismatch, expected {VERSION}, got {version}.\nFollow the docs for migration instruction"39			)));40		}41		Ok(Self)42	}43}4445#[derive(Serialize, Deserialize)]46#[serde(rename_all = "camelCase")]47pub struct FleetData {48	pub version: FleetDataVersion,4950	#[serde(default)]51	pub hosts: BTreeMap<String, HostData>,52	#[serde(default)]53	#[serde(skip_serializing_if = "BTreeMap::is_empty")]54	pub shared_secrets: BTreeMap<String, FleetSharedSecret>,55	#[serde(default)]56	#[serde(skip_serializing_if = "BTreeMap::is_empty")]57	pub host_secrets: BTreeMap<String, BTreeMap<String, FleetSecret>>,5859	// extra_name => anything60	#[serde(default)]61	#[serde(skip_serializing_if = "BTreeMap::is_empty")]62	pub extra: BTreeMap<String, Value>,63}6465#[derive(Serialize, Deserialize, Clone)]66#[serde(rename_all = "camelCase")]67#[must_use]68pub struct FleetSharedSecret {69	pub owners: Vec<String>,70	#[serde(flatten)]71	pub secret: FleetSecret,72}7374/// Returns None if recipients.is_empty()75pub fn encrypt_secret_data<'a>(76	recipients: impl IntoIterator<Item = &'a dyn Recipient>,77	data: Vec<u8>,78) -> Option<SecretData> {79	let mut encrypted = vec![];80	let mut encryptor = age::Encryptor::with_recipients(recipients.into_iter())81		.ok()?82		.wrap_output(&mut encrypted)83		.expect("in memory write");84	io::copy(&mut Cursor::new(data), &mut encryptor).expect("in memory copy");85	encryptor.finish().expect("in memory flush");86	Some(SecretData {87		data: encrypted,88		encrypted: true,89	})90}9192#[derive(Serialize, Deserialize, Clone)]93pub struct FleetSecretPart {94	pub raw: SecretData,95}9697#[derive(Serialize, Deserialize, Clone)]98#[serde(rename_all = "camelCase")]99#[must_use]100pub struct FleetSecret {101	#[serde(default = "Utc::now")]102	pub created_at: DateTime<Utc>,103	#[serde(default)]104	#[serde(skip_serializing_if = "Option::is_none", alias = "expire_at")]105	pub expires_at: Option<DateTime<Utc>>,106107	#[serde(flatten)]108	pub parts: BTreeMap<String, FleetSecretPart>,109}
after · crates/fleet-base/src/fleetdata.rs
1use std::{2	collections::BTreeMap,3	io::{self, Cursor},4};56use age::Recipient;7use chrono::{DateTime, Utc};8use fleet_shared::SecretData;9use rand::{10	distributions::{Alphanumeric, DistString},11	thread_rng,12};13use serde::{de::Error, Deserialize, Serialize};14use serde_json::Value;1516#[derive(Serialize, Deserialize, Default)]17#[serde(rename_all = "camelCase")]18pub struct HostData {19	#[serde(default)]20	#[serde(skip_serializing_if = "String::is_empty")]21	pub encryption_key: String,22}2324const VERSION: &str = "0.1.0";25pub struct FleetDataVersion;26impl Serialize for FleetDataVersion {27	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>28	where29		S: serde::Serializer,30	{31		VERSION.serialize(serializer)32	}33}34impl<'de> Deserialize<'de> for FleetDataVersion {35	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>36	where37		D: serde::Deserializer<'de>,38	{39		let version = String::deserialize(deserializer)?;40		if version != VERSION {41			return Err(D::Error::custom(format!(42				"fleet.nix data version mismatch, expected {VERSION}, got {version}.\nFollow the docs for migration instruction"43			)));44		}45		Ok(Self)46	}47}4849fn generate_gc_prefix() -> String {50	let id = Alphanumeric.sample_string(&mut thread_rng(), 8);51	format!("fleet-gc-{id}")52}5354#[derive(Serialize, Deserialize)]55#[serde(rename_all = "camelCase")]56pub struct FleetData {57	pub version: FleetDataVersion,58	#[serde(default = "generate_gc_prefix")]59	pub gc_root_prefix: String,6061	#[serde(default)]62	pub hosts: BTreeMap<String, HostData>,63	#[serde(default)]64	#[serde(skip_serializing_if = "BTreeMap::is_empty")]65	pub shared_secrets: BTreeMap<String, FleetSharedSecret>,66	#[serde(default)]67	#[serde(skip_serializing_if = "BTreeMap::is_empty")]68	pub host_secrets: BTreeMap<String, BTreeMap<String, FleetSecret>>,6970	// extra_name => anything71	#[serde(default)]72	#[serde(skip_serializing_if = "BTreeMap::is_empty")]73	pub extra: BTreeMap<String, Value>,74}7576#[derive(Serialize, Deserialize, Clone)]77#[serde(rename_all = "camelCase")]78#[must_use]79pub struct FleetSharedSecret {80	pub owners: Vec<String>,81	#[serde(flatten)]82	pub secret: FleetSecret,83}8485/// Returns None if recipients.is_empty()86pub fn encrypt_secret_data<'a>(87	recipients: impl IntoIterator<Item = &'a dyn Recipient>,88	data: Vec<u8>,89) -> Option<SecretData> {90	let mut encrypted = vec![];91	let mut encryptor = age::Encryptor::with_recipients(recipients.into_iter())92		.ok()?93		.wrap_output(&mut encrypted)94		.expect("in memory write");95	io::copy(&mut Cursor::new(data), &mut encryptor).expect("in memory copy");96	encryptor.finish().expect("in memory flush");97	Some(SecretData {98		data: encrypted,99		encrypted: true,100	})101}102103#[derive(Serialize, Deserialize, Clone)]104pub struct FleetSecretPart {105	pub raw: SecretData,106}107108#[derive(Serialize, Deserialize, Clone)]109#[serde(rename_all = "camelCase")]110#[must_use]111pub struct FleetSecret {112	#[serde(default = "Utc::now")]113	pub created_at: DateTime<Utc>,114	#[serde(default)]115	#[serde(skip_serializing_if = "Option::is_none", alias = "expire_at")]116	pub expires_at: Option<DateTime<Utc>>,117118	#[serde(flatten)]119	pub parts: BTreeMap<String, FleetSecretPart>,120}
modifiedmodules/hosts.nixdiffbeforeafterboth
--- a/modules/hosts.nix
+++ b/modules/hosts.nix
@@ -16,6 +16,10 @@
             type = str;
             internal = true;
           };
+          gcRootPrefix = mkOption {
+            type = str;
+            internal = true;
+          };
           hosts = mkOption {
             type = attrsOf (submodule {
               options.encryptionKey = mkOption {