difftreelog
feat create gc root per built system
in: trunk
5 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -962,6 +962,7 @@
"nixlike",
"nom",
"openssh",
+ "rand",
"serde",
"serde_json",
"tempfile",
cmds/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())
}
crates/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
crates/fleet-base/src/fleetdata.rsdiffbeforeafterboth1use 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}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}modules/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 {