difftreelog
refactor declare configuration using flake parts
in: trunk
29 files changed
Cargo.lockdiffbeforeafterboth1432name = "nix-eval"1432name = "nix-eval"1433version = "0.1.0"1433version = "0.1.0"1434dependencies = [1434dependencies = [1435 "anyhow",1435 "better-command",1436 "better-command",1436 "futures",1437 "futures",1437 "itertools",1438 "itertools",README.adocdiffbeforeafterboth24 url = "github:CertainLach/fleet";24 url = "github:CertainLach/fleet";25 inputs.nixpkgs.follows = "nixpkgs";25 inputs.nixpkgs.follows = "nixpkgs";26 };26 };27 flake-parts.url = "github:hercules-ci/flake-parts";27 lanzaboote = {28 lanzaboote = {28 url = "github:nix-community/lanzaboote/v0.3.0";29 url = "github:nix-community/lanzaboote/v0.3.0";29 inputs.nixpkgs.follows = "nixpkgs";30 inputs.nixpkgs.follows = "nixpkgs";30 };31 };31 };32 };32 outputs = {33 outputs = inputs: flake-parts.lib.mkFlake { inherit inputs; } {33 nixpkgs,34 fleet,35 lanzaboote,36 ...37 }: {38 # TODO: This section of documentation needs to use flake-utils.39 formatter.x86_64-linux = let40 pkgs = import nixpkgs {system = "x86_64-linux";};41 in42 pkgs.alejandra;34 imports = [inputs.fleet.flakeModules.default];433544 devShell.x86_64-linux = let36 perSystem = {pkgs, system, ...}: {45 pkgs = import nixpkgs {37 _module.args.pkgs = import nixpkgs { inherit system; };46 system = "x86_64-linux";3847 };39 formatter = pkgs.alejandra;48 in40 devShells.default = pkgs.mkShell {49 pkgs.mkShell {50 buildInputs = with pkgs; [41 packages = [51 fleet.packages.x86_64-linux.fleet42 inputs.fleet.packages.${system}.fleet52 ];43 ];53 };44 };45 };544655 # Single flake may contain multiple fleet configurations, default one is called... `default`47 # Single flake may contain multiple fleet configurations, default one is called... `default`56 fleetConfigurations.default = fleet.lib.fleetConfiguration {48 fleetConfigurations.default = {57 # nixpkgs used to build the systems49 # nixpkgs used to build the systems58 inherit nixpkgs;50 nixpkgs.buildUsing = nixpkgs;59 # fleet wants to pass some data, like secrets, to do that - fleet writes all the encrypted secrets to fleet.nix60 # treat the contents of this file as implementation detail61 data = import ./fleet.nix;62 51 63 # nixosModules section of fleet config declares modules, which are used for all configured nixos hosts.52 # nixos option section of fleet config declares module, which is used for all configured nixos hosts.64 nixosModules = [53 nixos.imports = [65 lanzaboote.nixosModules.lanzaboote54 lanzaboote.nixosModules.lanzaboote66 {55 {67 # Make `nix shell nixpkgs#thing` use the same nixpkgs, as used to build the system.56 # Make `nix shell nixpkgs#thing` use the same nixpkgs, as used to build the system.77 # Is I.e wiring up the mesh VPN, or deploying kubernetes, or other things.66 # Is I.e wiring up the mesh VPN, or deploying kubernetes, or other things.78 #67 #79 # Modules use the same semantics as standard nixos module system, they are just configuring all the hosts at once.68 # Modules use the same semantics as standard nixos module system, they are just configuring all the hosts at once.80 fleetModules = [69 imports = [81 ./wireguard70 ./wireguard82 # Multi-instancible modules example71 # Multi-instancible modules example83 (import ./kubernetes {hosts = ["a" "b"];})72 (import ./kubernetes {hosts = ["a" "b"];})89 # Every host has some system, for which the system configuration needs to be built78 # Every host has some system, for which the system configuration needs to be built90 system = "x86_64-linux";79 system = "x86_64-linux";91 # And nixos modules80 # And nixos modules92 nixosModules = [81 nixos.imports = [93 ./controlplane-1/hardware-configuration.nix82 ./controlplane-1/hardware-configuration.nix94 ./controlplane-1/configuration.nix83 ./controlplane-1/configuration.nix95 # Configuration may also be specified inline, as in any nixos config.84 # Configuration may also be specified inline, as in any nixos config.cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth254 let host = config.host(&host).await?;254 let host = config.host(&host).await?;255 // let action = Action::from(self.subcommand.clone());255 // let action = Action::from(self.subcommand.clone());256 let fleet_config = &config.config_field;256 let fleet_config = &config.config_field;257 let nixos = host.nixos_config().await?;257 let drv = nix_go!(258 let drv = nix_go!(nixos.system.build[{ build_attr }]);258 fleet_config.hosts[{ &host.name }]259 .nixosSystem260 .config335 let config = config.clone();330 let config = config.clone();336 let span = info_span!("deploy", host = field::display(&host.name));331 let span = info_span!("deploy", host = field::display(&host.name));337 let hostname = host.name.clone();332 let hostname = host.name.clone();333 let local_host = config.local_host();338 // FIXME: Fix repl concurrency (see build-systems)334 // FIXME: Fix repl concurrency (see build-systems)339 set.spawn_local(335 set.spawn_local(340 (async move {336 (async move {354 // at least for the first deployment, to provide trusted store key.350 // at least for the first deployment, to provide trusted store key.355 //351 //356 // It is much slower, yet doesn't require root on the deployer machine.352 // It is much slower, yet doesn't require root on the deployer machine.357 let mut sign = MyCommand::new("nix");353 let Ok(mut sign) = local_host.cmd("nix").await else {354 error!("failed to setup local");355 return;356 };358 // Private key for host machine is registered in nix-sign.nix357 // Private key for host machine is registered in nix-sign.nix359 sign.arg("store")358 sign.arg("store")360 .arg("sign")359 .arg("sign")361 .comparg("--key-file", "/etc/nix/private-key")360 .comparg("--key-file", "/etc/nix/private-key")362 .arg("-r")361 .arg("-r")363 .arg(&built);362 .arg(&built);364 if let Err(e) = sign.sudo().run_nix().await {363 if let Err(e) = sign.sudo().run_nix().await {365 warn!("Failed to sign store paths: {e}");364 warn!("failed to sign store paths: {e}");366 };365 };367 }366 }368 let mut tries = 0;367 let mut tries = 0;cmds/fleet/src/cmds/info.rsdiffbeforeafterboth38 InfoCmd::ListHosts { ref tagged } => {38 InfoCmd::ListHosts { ref tagged } => {39 'host: for host in config.list_hosts().await? {39 'host: for host in config.list_hosts().await? {40 if !tagged.is_empty() {40 if !tagged.is_empty() {41 let config = &config.config_unchecked_field;41 let config = &config.config_field;42 let tags: Vec<String> =42 let tags: Vec<String> =43 nix_go_json!(config.hosts[{ host.name }].nixosSystem.config.tags);43 nix_go_json!(config.hosts[{ host.name }].tags);44 for tag in tagged {44 for tag in tagged {45 if !tags.contains(tag) {45 if !tags.contains(tag) {46 continue 'host;46 continue 'host;cmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth598 return Ok(());598 return Ok(());599 }599 }600600601 let config_field = &config.config_unchecked_field;601 let config_field = &config.config_field;602 let field = nix_go!(config_field.sharedSecrets[{ name }]);602 let field = nix_go!(config_field.sharedSecrets[{ name }]);603603604 let updated = update_owner_set(604 let updated = update_owner_set(623 .collect::<HashSet<_>>();623 .collect::<HashSet<_>>();624 let shared_set = config.list_shared().into_iter().collect::<HashSet<_>>();624 let shared_set = config.list_shared().into_iter().collect::<HashSet<_>>();625 for missing in expected_shared_set.difference(&shared_set) {625 for missing in expected_shared_set.difference(&shared_set) {626 let config_field = &config.config_unchecked_field;626 let config_field = &config.config_field;627 let secret = nix_go!(config_field.sharedSecrets[{ missing }]);627 let secret = nix_go!(config_field.sharedSecrets[{ missing }]);628 let expected_owners: Option<Vec<String>> =628 let expected_owners: Option<Vec<String>> =629 nix_go_json!(secret.expectedOwners);629 nix_go_json!(secret.expectedOwners);675 for name in &config.list_shared() {675 for name in &config.list_shared() {676 info!("updating secret: {name}");676 info!("updating secret: {name}");677 let data = config.shared_secret(name)?;677 let data = config.shared_secret(name)?;678 let config_field = &config.config_unchecked_field;678 let config_field = &config.config_field;679 let expected_owners: Vec<String> =679 let expected_owners: Vec<String> =680 nix_go_json!(config_field.sharedSecrets[{ name }].expectedOwners);680 nix_go_json!(config_field.sharedSecrets[{ name }].expectedOwners);681 if expected_owners.is_empty() {681 if expected_owners.is_empty() {cmds/fleet/src/command.rsdiffbeforeafterboth9use tokio_util::codec::{BytesCodec, FramedRead, LinesCodec};9use tokio_util::codec::{BytesCodec, FramedRead, LinesCodec};10use tracing::debug;10use tracing::debug;1112use crate::host::EscalationStrategy;111312fn escape_bash(input: &str, out: &mut String) {14fn escape_bash(input: &str, out: &mut String) {13 const TO_ESCAPE: &str = "$ !\"#&'()*,;<>?[\\]^`{|}";15 const TO_ESCAPE: &str = "$ !\"#&'()*,;<>?[\\]^`{|}";28 os.as_ref().to_str().expect("non-utf8 data").to_owned()30 os.as_ref().to_str().expect("non-utf8 data").to_owned()29}31}3230#[derive(Clone)]33#[derive(Clone, Debug)]31pub struct MyCommand {34pub struct MyCommand {32 command: String,35 command: String,33 args: Vec<String>,36 args: Vec<String>,34 env: Vec<(String, String)>,37 env: Vec<(String, String)>,35 ssh_session: Option<Arc<Session>>,38 ssh_session: Option<Arc<Session>>,39 escalation: EscalationStrategy,40 escalate: bool,36}41}37impl MyCommand {42impl MyCommand {38 pub fn new_on(cmd: impl AsRef<OsStr>, session: Arc<Session>) -> Self {43 pub fn new_on(44 escalation: EscalationStrategy,45 cmd: impl AsRef<OsStr>,46 session: Arc<Session>,47 ) -> Self {39 assert!(!cmd.as_ref().is_empty());48 assert!(!cmd.as_ref().is_empty());40 Self {49 Self {41 command: ostoutf8(cmd),50 command: ostoutf8(cmd),42 args: vec![],51 args: vec![],43 env: vec![],52 env: vec![],44 ssh_session: Some(session),53 ssh_session: Some(session),54 escalation,55 escalate: false,45 }56 }46 }57 }47 pub fn new(cmd: impl AsRef<OsStr>) -> Self {58 pub fn new(escalation: EscalationStrategy, cmd: impl AsRef<OsStr>) -> Self {48 assert!(!cmd.as_ref().is_empty());59 assert!(!cmd.as_ref().is_empty());49 Self {60 Self {50 command: ostoutf8(cmd),61 command: ostoutf8(cmd),51 args: vec![],62 args: vec![],52 env: vec![],63 env: vec![],53 ssh_session: None,64 ssh_session: None,65 escalation,66 escalate: false,54 }67 }55 }68 }69 fn new_here(&self, cmd: impl AsRef<OsStr>) -> Self {70 if let Some(ssh_session) = self.ssh_session.clone() {71 Self::new_on(self.escalation, cmd, ssh_session)72 } else {73 Self::new(self.escalation, cmd)74 }75 }7656 fn into_args(self) -> Vec<String> {77 fn into_args(self) -> Vec<String> {57 let mut out = Vec::new();78 let mut out = Vec::new();76 if self.env.is_empty() {97 if self.env.is_empty() {77 return self;98 return self;78 }99 }79 let mut out = Self::new("env");100 let mut out = self.new_here("env");80 out.ssh_session = self.ssh_session;81 for (k, v) in self.env {101 for (k, v) in self.env {82 assert!(!k.contains('='));102 assert!(!k.contains('='));83 out.arg(format!("{k}={v}"));103 out.arg(format!("{k}={v}"));159 }179 }160 self180 self161 }181 }162 pub fn sudo(mut self) -> Self {182 pub fn sudo(mut self) -> Self {163 // TODO: Multiple escalation strategies.183 self.escalate = true;164 // Maybe escalation should be moved to ConfigHost, to also support cases184 self165 // when there is no sudo on remote machine, but instead we can reconnect185 }166 // as root using ssh?167 if std::env::var_os("NO_SUDO").is_some() {186 fn wrap_sudo_if_needed(self) -> Self {168 let mut out = Self::new("su");169 out.ssh_session = self.ssh_session.take();187 if !self.escalate {188 return self;189 }190 match self.escalation {191 EscalationStrategy::Su => {192 let mut out = self.new_here("su");170 out.arg("-c").arg(self.into_string());193 out.arg("-c").arg(self.into_string());171 out194 out195 }196 EscalationStrategy::Sudo => {197 let mut out = self.new_here("sudo");198 out.args(self.into_args());199 out200 }172 } else {201 EscalationStrategy::Run0 => {202 // run0 wants interactive authentication by default.173 let mut out = Self::new("sudo");203 let mut run0 = self.new_here("run0");174 out.ssh_session = self.ssh_session.take();204 let mut out = self.new_here("script");205206 // Red backgrounds messes with fleet formatting207 run0.arg("--background=");175 out.args(self.into_args());208 run0.args(self.into_args());209210 out.arg("-q");211 out.arg("/dev/null");212 out.arg("-c");213 out.arg(run0.into_string());214 dbg!(&out);176 out215 out177 }216 }178 }217 }218 }179219180 pub async fn run(self) -> Result<()> {220 pub async fn run(self) -> Result<()> {181 let str = self.clone().into_string();221 let str = self.clone().into_string();182 let cmd = self.into_command_new()?;222 let cmd = self.wrap_sudo_if_needed().into_command_new()?;183 match cmd {223 match cmd {184 Either::Left(cmd) => run_nix_inner(str, cmd, &mut PlainHandler).await?,224 Either::Left(cmd) => run_nix_inner(str, cmd, &mut PlainHandler).await?,185 Either::Right(cmd) => run_nix_inner_ssh(str, cmd, &mut PlainHandler).await?,225 Either::Right(cmd) => run_nix_inner_ssh(str, cmd, &mut PlainHandler).await?,192 }232 }193 pub async fn run_bytes(self) -> Result<Vec<u8>> {233 pub async fn run_bytes(self) -> Result<Vec<u8>> {194 let str = self.clone().into_string();234 let str = self.clone().into_string();195 let cmd = self.into_command_new()?;235 let cmd = self.wrap_sudo_if_needed().into_command_new()?;196 let v = match cmd {236 let v = match cmd {197 Either::Left(cmd) => run_nix_inner_stdout(str, cmd, &mut PlainHandler).await?,237 Either::Left(cmd) => run_nix_inner_stdout(str, cmd, &mut PlainHandler).await?,198 Either::Right(cmd) => run_nix_inner_stdout_ssh(str, cmd, &mut PlainHandler).await?,238 Either::Right(cmd) => run_nix_inner_stdout_ssh(str, cmd, &mut PlainHandler).await?,199 };239 };200 Ok(v)240 Ok(v)201 }241 }202242203 pub async fn run_nix_string(self) -> Result<String> {243 pub async fn run_nix_string(mut self) -> Result<String> {204 let str = self.clone().into_string();244 let str = self.clone().into_string();205 let mut cmd = self.into_command();245 self.arg("--log-format").arg("internal-json");206 cmd.arg("--log-format").arg("internal-json");246 let mut cmd = self.wrap_sudo_if_needed().into_command();207 let bytes = run_nix_inner_stdout(str, cmd, &mut NixHandler::default()).await?;247 let bytes = run_nix_inner_stdout(str, cmd, &mut NixHandler::default()).await?;208 Ok(String::from_utf8(bytes)?)248 Ok(String::from_utf8(bytes)?)209 }249 }210 pub async fn run_nix(self) -> Result<()> {250 pub async fn run_nix(mut self) -> Result<()> {211 let str = self.clone().into_string();251 let str = self.clone().into_string();212 let mut cmd = self.into_command();252 self.arg("--log-format").arg("internal-json");213 cmd.arg("--log-format").arg("internal-json");253 let mut cmd = self.wrap_sudo_if_needed().into_command();214 cmd.stdout(Stdio::inherit());254 cmd.stdout(Stdio::inherit());215 run_nix_inner(str, cmd, &mut NixHandler::default()).await255 run_nix_inner(str, cmd, &mut NixHandler::default()).await216 }256 }cmds/fleet/src/host.rsdiffbeforeafterboth1use std::{1use std::{2 cell::OnceCell,2 cell::{LazyCell, OnceCell},3 collections::BTreeMap,3 collections::BTreeMap,4 env::current_dir,4 env::current_dir,5 ffi::{OsStr, OsString},5 ffi::{OsStr, OsString},14use anyhow::{anyhow, bail, ensure, Context, Result};14use anyhow::{anyhow, bail, ensure, Context, Result};15use clap::Parser;15use clap::Parser;16use fleet_shared::SecretData;16use fleet_shared::SecretData;17use nix_eval::{nix_go, nix_go_json, NixSessionPool, Value};17use nix_eval::{nix_go, nix_go_json, util::assert_warn, NixSessionPool, Value};18use nom::{18use nom::{19 bytes::complete::take_while1,19 bytes::complete::take_while1,20 character::complete::char,20 character::complete::char,25use openssh::SessionBuilder;25use openssh::SessionBuilder;26use serde::de::DeserializeOwned;26use serde::de::DeserializeOwned;27use tempfile::NamedTempFile;27use tempfile::NamedTempFile;28use tracing::error;282929use crate::{30use crate::{30 command::MyCommand,31 command::MyCommand,39 pub nix_args: Vec<OsString>,40 pub nix_args: Vec<OsString>,40 /// fleet_config.config41 /// fleet_config.config41 pub config_field: Value,42 pub config_field: Value,42 /// fleet_config.unchecked.config43 pub config_unchecked_field: Value,444345 /// import nixpkgs {system = local};44 /// import nixpkgs {system = local};46 pub default_pkgs: Value,45 pub default_pkgs: Value,57 }56 }58}57}5859#[derive(Clone, Copy, Debug)]60pub enum EscalationStrategy {61 Sudo,62 Run0,63 Su,64}596560pub struct ConfigHost {66pub struct ConfigHost {61 config: Config,67 config: Config,64 pub session: OnceLock<Arc<openssh::Session>>,70 pub session: OnceLock<Arc<openssh::Session>>,65 groups: OnceCell<Vec<String>>,71 groups: OnceCell<Vec<String>>,667267 pub nixos_config: Option<Value>,73 pub host_config: Option<Value>,74 pub nixos_config: OnceCell<Value>,68}75}69impl ConfigHost {76impl ConfigHost {77 pub async fn escalation_strategy(&self) -> Result<EscalationStrategy> {78 // Prefer sudo, as run0 has some gotchas with polkit79 // and too many repeating prompts.80 if let Ok(_) = self.find_in_path("sudo").await {81 return Ok(EscalationStrategy::Sudo);82 }83 if let Ok(_) = self.find_in_path("run0").await {84 return Ok(EscalationStrategy::Run0);85 }86 Ok(EscalationStrategy::Su)87 }88 // TOCTOU is possible here in case if config is changed, but this case is not handled anywhere anyway,89 // assuming getting tags always returns the same value.70 pub async fn tags(&self) -> Result<Vec<String>> {90 pub async fn tags(&self) -> Result<Vec<String>> {71 if let Some(v) = self.groups.get() {91 if let Some(v) = self.groups.get() {72 return Ok(v.clone());92 return Ok(v.clone());73 }93 }74 // TOCTOU is possible here in case if config is changed, but this case is not handled anywhere anyway,75 // assuming getting tags always returns the same value.76 let Some(nixos_config) = &self.nixos_config else {94 let Some(host_config) = &self.host_config else {77 return Ok(vec![]);95 return Ok(vec![]);78 };96 };79 let tags: Vec<String> = nix_go_json!(nixos_config.tags);97 let tags: Vec<String> = nix_go_json!(host_config.tags);809881 let _ = self.groups.set(tags.clone());99 let _ = self.groups.set(tags.clone());8210083 Ok(tags)101 Ok(tags)84 }102 }103 pub async fn nixos_config(&self) -> Result<Value> {104 if let Some(v) = self.nixos_config.get() {105 return Ok(v.clone());106 }107 let Some(host_config) = &self.host_config else {108 bail!("local host has no nixos_config");109 };110 let nixos_config = nix_go!(host_config.nixos.config);111 assert_warn("nixos config evaluation", &nixos_config).await?;112113 let _ = self.nixos_config.set(nixos_config.clone());114115 Ok(nixos_config)116 }85 async fn open_session(&self) -> Result<Arc<openssh::Session>> {117 async fn open_session(&self) -> Result<Arc<openssh::Session>> {86 assert!(!self.local, "do not open ssh connection to local session");118 assert!(!self.local, "do not open ssh connection to local session");87 // FIXME: TOCTOU119 // FIXME: TOCTOU88 if let Some(session) = &self.session.get() {120 if let Some(session) = &self.session.get() {89 return Ok((*session).clone());121 return Ok((*session).clone());90 };122 };91 let session = SessionBuilder::default();123 let mut session = SessionBuilder::default();9293 let session = session124 let session = session94 .connect(&self.name)125 .connect(&self.name)129 let text = self.read_file_text(path).await?;160 let text = self.read_file_text(path).await?;130 Ok(serde_json::from_str(&text)?)161 Ok(serde_json::from_str(&text)?)131 }162 }163 pub async fn read_env(&self, env: &str) -> Result<String> {164 let mut cmd = self.cmd("printenv").await?;165 cmd.arg(env);166 Ok(cmd.run_string().await?)167 }168 pub async fn find_in_path(&self, command: &str) -> Result<String> {169 // // `which` is not a part of coreutils, and it might not exist on machine.170 // let path = self.read_env("PATH").await?;171 // // Assuming delimiter is :, we don't work with windows host, this check will be much172 // // more sophisticated in remowt backend (and quicker, since actual PATH search will be done on remote machine)173 // for ele in path.split(':') {174 // let test_path = format!("{ele}/{cmd}");175 // test -x etc176 // }177 // let mut cmd = self.cmd("printenv").await?;178 // cmd.arg(env);179 // Ok(cmd.run_string().await?)180 // Assuming this is an environment issue if which doesn't exist, will be fixed with remowt.181 let mut cmd = self182 .cmd_escalation(183 // Not used184 EscalationStrategy::Su,185 "which",186 )187 .await?;188 cmd.arg(command);189 cmd.run_string().await190 }132 pub async fn read_file_value<D: FromStr>(&self, path: impl AsRef<OsStr>) -> Result<D>191 pub async fn read_file_value<D: FromStr>(&self, path: impl AsRef<OsStr>) -> Result<D>133 where192 where134 <D as FromStr>::Err: Display,193 <D as FromStr>::Err: Display,135 {194 {136 let text = self.read_file_text(path).await?;195 let text = self.read_file_text(path).await?;137 D::from_str(&text).map_err(|e| anyhow!("failed to parse value: {e}"))196 D::from_str(&text).map_err(|e| anyhow!("failed to parse value: {e}"))138 }197 }139 pub async fn cmd(&self, cmd: impl AsRef<OsStr>) -> Result<MyCommand> {198 pub async fn cmd(&self, cmd: impl AsRef<OsStr>) -> Result<MyCommand> {199 self.cmd_escalation(self.escalation_strategy().await?, cmd)200 .await201 }202 pub async fn cmd_escalation(203 &self,204 escalation: EscalationStrategy,205 cmd: impl AsRef<OsStr>,206 ) -> Result<MyCommand> {140 if self.local {207 if self.local {141 Ok(MyCommand::new(cmd))208 Ok(MyCommand::new(escalation, cmd))142 } else {209 } else {143 let session = self.open_session().await?;210 let session = self.open_session().await?;144 Ok(MyCommand::new_on(cmd, session))211 Ok(MyCommand::new_on(escalation, cmd, session))145 }212 }146 }213 }147214182 return Ok(path.to_owned());249 return Ok(path.to_owned());183 }250 }184 let mut nix = MyCommand::new("nix");251 let mut nix = MyCommand::new(252 // Not used253 EscalationStrategy::Su,254 "nix",255 );185 nix.arg("copy")256 nix.arg("copy")186 .arg("--substitute-on-destination")257 .arg("--substitute-on-destination")210 }281 }211282212 pub async fn list_configured_secrets(&self) -> Result<Vec<String>> {283 pub async fn list_configured_secrets(&self) -> Result<Vec<String>> {213 let Some(nixos) = &self.nixos_config else {284 let nixos = self.nixos_config().await?;214 return Ok(vec![]);215 };216 let secrets = nix_go!(nixos.secrets);285 let secrets = nix_go!(nixos.secrets);217 let mut out = Vec::new();286 let mut out = Vec::new();218 for name in secrets.list_fields().await? {287 for name in secrets.list_fields().await? {226 Ok(out)295 Ok(out)227 }296 }228 pub async fn secret_field(&self, name: &str) -> Result<Value> {297 pub async fn secret_field(&self, name: &str) -> Result<Value> {229 let Some(nixos) = &self.nixos_config else {298 let nixos = self.nixos_config().await?;230 bail!("host is virtual and has no secrets");231 };232 Ok(nix_go!(nixos.secrets[{ name }]))299 Ok(nix_go!(nixos.secrets[{ name }]))233 }300 }234301235 /// Packages for this host, resolved with nixpkgs overlays302 /// Packages for this host, resolved with nixpkgs overlays236 pub async fn pkgs(&self) -> Result<Value> {303 pub async fn pkgs(&self) -> Result<Value> {237 let Some(nixos) = &self.nixos_config else {304 let nixos = self.nixos_config().await?;238 return Ok(self.config.default_pkgs.clone());239 };240 Ok(nix_go!(nixos.nixpkgs.resolvedPkgs))305 Ok(nix_go!(nixos._resolvedPkgs))241 }306 }242}307}243308317 name: "<virtual localhost>".to_owned(),382 name: "<virtual localhost>".to_owned(),318 local: true,383 local: true,319 session: OnceLock::new(),384 session: OnceLock::new(),320 nixos_config: None,385 host_config: None,386 nixos_config: OnceCell::new(),321 groups: {387 groups: {322 let cell = OnceCell::new();388 let cell = OnceCell::new();323 let _ = cell.set(vec![]);389 let _ = cell.set(vec![]);327 }393 }328394329 pub async fn host(&self, name: &str) -> Result<ConfigHost> {395 pub async fn host(&self, name: &str) -> Result<ConfigHost> {330 let config = &self.config_unchecked_field;396 let config = &self.config_field;331 let nixos_config = nix_go!(config.hosts[{ name }].nixosSystem.config);397 let host_config = nix_go!(config.hosts[{ name }]);398399332 Ok(ConfigHost {400 Ok(ConfigHost {333 config: self.clone(),401 config: self.clone(),334 name: name.to_owned(),402 name: name.to_owned(),335 local: self.is_local(name),403 local: self.is_local(name),336 session: OnceLock::new(),404 session: OnceLock::new(),337 nixos_config: Some(nixos_config),405 host_config: Some(host_config),406 nixos_config: OnceCell::new(),338 groups: OnceCell::new(),407 groups: OnceCell::new(),339 })408 })340 }409 }341 pub async fn list_hosts(&self) -> Result<Vec<ConfigHost>> {410 pub async fn list_hosts(&self) -> Result<Vec<ConfigHost>> {342 let config = &self.config_unchecked_field;411 let config = &self.config_field;343 let names = nix_go!(config.hosts).list_fields().await?;412 let names = nix_go!(config.hosts).list_fields().await?;344 let mut out = vec![];413 let mut out = vec![];345 for name in names {414 for name in names {348 Ok(out)417 Ok(out)349 }418 }350 pub async fn system_config(&self, host: &str) -> Result<Value> {419 pub async fn system_config(&self, host: &str) -> Result<Value> {351 let fleet_field = &self.config_unchecked_field;420 let fleet_field = &self.config_field;352 Ok(nix_go!(fleet_field.hosts[{ host }].nixosSystem.config))421 Ok(nix_go!(fleet_field.hosts[{ host }].nixos.config))353 }422 }354423355 pub(super) fn data(&self) -> MutexGuard<FleetData> {424 pub(super) fn data(&self) -> MutexGuard<FleetData> {360 }429 }361 /// Shared secrets configured in fleet.nix or in flake430 /// Shared secrets configured in fleet.nix or in flake362 pub async fn list_configured_shared(&self) -> Result<Vec<String>> {431 pub async fn list_configured_shared(&self) -> Result<Vec<String>> {363 let config_field = &self.config_unchecked_field;432 let config_field = &self.config_field;364 Ok(nix_go!(config_field.sharedSecrets).list_fields().await?)433 Ok(nix_go!(config_field.sharedSecrets).list_fields().await?)365 }434 }366 /// Shared secrets configured in fleet.nix435 /// Shared secrets configured in fleet.nix420 Ok(secret.clone())489 Ok(secret.clone())421 }490 }422 pub async fn shared_secret_expected_owners(&self, secret: &str) -> Result<Vec<String>> {491 pub async fn shared_secret_expected_owners(&self, secret: &str) -> Result<Vec<String>> {423 let config_field = &self.config_unchecked_field;492 let config_field = &self.config_field;424 Ok(nix_go_json!(493 Ok(nix_go_json!(425 config_field.sharedSecrets[{ secret }].expectedOwners494 config_field.sharedSecrets[{ secret }].expectedOwners426 ))495 ))525 }594 }526 let local_system = self.local_system.clone();595 let local_system = self.local_system.clone();596597 let mut fleet_data_path = directory.clone();598 fleet_data_path.push("fleet.nix");599 let bytes = std::fs::read_to_string(fleet_data_path)?;600 let data: Mutex<FleetData> = nixlike::parse_str(&bytes)?;527601528 let fleet_root = Value::binding(root_field, "fleetConfigurations").await?;602 let fleet_root = Value::binding(root_field, "fleetConfigurations").await?;529 let fleet_field = nix_go!(fleet_root.default);603 let fleet_field = nix_go!(fleet_root.default({ data }));530604531 let config_field = nix_go!(fleet_field.config);605 let config_field = nix_go!(fleet_field.config);606532 let config_unchecked_field = nix_go!(fleet_field.unchecked.config);607 assert_warn("fleet config evaluation", &config_field).await?;533608534 let import = nix_go!(builtins_field.import);609 let import = nix_go!(builtins_field.import);535 let overlays = nix_go!(config_unchecked_field.overlays);610 let overlays = nix_go!(config_field.nixpkgs.overlays);536 let nixpkgs = nix_go!(fleet_field.nixpkgs | import);611 let nixpkgs = nix_go!(fleet_field.nixpkgs.buildUsing | import);537612538 let default_pkgs = nix_go!(nixpkgs(Obj {613 let default_pkgs = nix_go!(nixpkgs(Obj {539 overlays,614 overlays,540 system: { self.local_system.clone() },615 system: { self.local_system.clone() },541 }));616 }));542543 let mut fleet_data_path = directory.clone();544 fleet_data_path.push("fleet.nix");545 let bytes = std::fs::read_to_string(fleet_data_path)?;546 let data = nixlike::parse_str(&bytes)?;547617548 Ok(Config(Arc::new(FleetConfigInternals {618 Ok(Config(Arc::new(FleetConfigInternals {549 opts: self,619 opts: self,552 local_system,622 local_system,553 nix_args,623 nix_args,554 config_field,624 config_field,555 config_unchecked_field,556 default_pkgs,625 default_pkgs,557 })))626 })))558 }627 }cmds/fleet/src/main.rsdiffbeforeafterboth58 path.push("file://");58 path.push("file://");59 path.push(entry.path());59 path.push(entry.path());606061 let mut status = MyCommand::new("nix");61 let mut status = config.local_host().cmd("nix").await?;62 status.args(&config.nix_args);62 status.args(&config.nix_args);63 status.arg("store").arg("prefetch-file").arg(path);63 status.arg("store").arg("prefetch-file").arg(path);64 status.run_nix_string().instrument(span).await?;64 status.run_nix_string().instrument(span).await?;crates/nix-eval/Cargo.tomldiffbeforeafterboth5build = "build.rs"5build = "build.rs"667[dependencies]7[dependencies]8anyhow.workspace = true8better-command.workspace = true9better-command.workspace = true9futures = "0.3.30"10futures = "0.3.30"10itertools = "0.13.0"11itertools = "0.13.0"crates/nix-eval/src/lib.rsdiffbeforeafterboth17// Contains macros helpers17// Contains macros helpers18#[doc(hidden)]18#[doc(hidden)]19pub mod macros;19pub mod macros;20pub mod util;20// #[allow(non_upper_case_globals, non_camel_case_types, non_snake_case)]21// #[allow(non_upper_case_globals, non_camel_case_types, non_snake_case)]21// mod nix_raw {22// mod nix_raw {22// include!(concat!(env!("OUT_DIR"), "/bindings.rs"));23// include!(concat!(env!("OUT_DIR"), "/bindings.rs"));crates/nix-eval/src/util.rsdiffbeforeafterbothno changes
crates/nix-eval/src/value.rsdiffbeforeafterboth44 let v = nixlike::format_identifier(k.as_str());44 let v = nixlike::format_identifier(k.as_str());45 write!(f, ".{v}")45 write!(f, ".{v}")46 }46 }47 Index::Apply(o) => {47 Index::Apply(_) => {48 write!(f, "<apply>({o})")48 write!(f, "<apply>(...)")49 }49 }50 Index::Expr(e) => {50 Index::Expr(e) => {51 write!(f, "[{}]", e.out)51 write!(f, "[{}]", e.out)52 }52 }53 Index::ExprApply(e) => {53 Index::ExprApply(_) => {54 write!(f, "<apply>({})", e.out)54 write!(f, "<apply>(...)")55 }55 }56 Index::Pipe(e) => {56 Index::Pipe(e) => {57 write!(f, "<map>({})", e.out)57 write!(f, "<map>({})", e.out)flake.nixdiffbeforeafterboth25 flake-parts.lib.mkFlake {25 flake-parts.lib.mkFlake {26 inherit inputs;26 inherit inputs;27 } {27 } {28 flake = let28 flake = rec {29 lib =30 (import ./lib {29 inherit (inputs.nixpkgs.lib) mapAttrs;31 inherit (inputs.nixpkgs) lib;30 in {32 })31 lib = import ./lib {33 // {34 fleetConfiguration = throw "function-based interface is deprecated, use flake-parts syntax instead";35 };32 fleetPkgsForPkgs = pkgs:36 flakeModules.default = (import ./lib/flakePart.nix {33 import ./pkgs {34 inherit (pkgs) callPackage;37 inherit crane;35 craneLib = crane.mkLib pkgs;36 };38 });37 };39 flakeModule = flakeModules.default;4038 # To be used with https://github.com/NixOS/nix/pull/889241 # To be used with https://github.com/NixOS/nix/pull/889239 schemas = {42 schemas = let43 inherit (inputs.nixpkgs.lib) mapAttrs;44 in {40 fleetConfigurations = {45 fleetConfigurations = {41 version = 1;46 version = 1;42 doc = ''47 doc = ''60 };65 };61 };66 };62 };67 };63 };68 };64 # Supported and tested list of deployment targets.69 # Supported and tested list of deployment targets.65 systems = ["x86_64-linux" "aarch64-linux" "armv7l-linux" "armv6l-linux"];70 systems = ["x86_64-linux" "aarch64-linux" "armv7l-linux" "armv6l-linux"];66 perSystem = {71 perSystem = {69 pkgs,74 pkgs,70 ...75 ...71 }: let76 }: let72 inherit (lib) mapAttrs' elem;77 inherit (lib.attrSets) mapAttrs';78 inherit (lib.lists) elem;73 # Can also be built for darwin, through it is not usual to deploy nixos systems from macos machines.79 # Can also be built for darwin, through it is not usual to deploy nixos systems from macos machines.74 # I have no hardware for such testing, thus only adding machines I actually have and use.80 # I have no hardware for such testing, thus only adding machines I actually have and use.75 #81 #108 pkg-config114 pkg-config109 openssl115 openssl110 bacon116 bacon117 nil111 ];118 ];112 };119 };113 };120 };lib/default.nixdiffbeforeafterboth1# Shared functions for fleet configuration, available as `fleet` module argument1{fleetPkgsForPkgs}: {2{lib}: let2 fleetConfiguration = {3 # TODO: Provide by fleet, instead of requesting user to provide it.4 # This is not good that user needs to provide it, as it becomes a flake data, and fleet arbitrarily rewriting it11 fleetModules,12 nixosModules ? [],13 extraFleetLib ? {},14 }: let15 hostNames = nixpkgs.lib.attrNames hosts;3 inherit (lib.trivial) isFunction;16 fleetLib =4 inherit (lib.options) mkOption mergeOneOption;17 (import ./fleetLib.nix {18 inherit nixpkgs hostNames;5 inherit (lib.modules) mkOverride;19 })6 inherit (lib.types) listOf submodule attrsOf mkOptionType;20 // extraFleetLib;7 inherit (lib.strings) optionalString;21 in let8in rec {22 root = nixpkgs.lib.evalModules {9 types = {23 modules =10 overlay = mkOptionType {24 (import ../modules/fleet/_modules.nix)25 ++ [26 data27 ({...}: {28 inherit nixosModules hosts;11 name = "nixpkgs-overlay";29 overlays = [(final: prev: (fleetPkgsForPkgs final))] ++ overlays;12 description = "nixpkgs overlay";13 check = isFunction;14 merge = mergeOneOption;30 })15 };31 ]16 listOfOverlay = listOf types.overlay;32 ++ fleetModules;1733 specialArgs = {18 mkHostsType = module: attrsOf (submodule module);34 inherit nixpkgs fleetLib;35 };36 };19 };2037 failedAssertions = map (x: x.message) (nixpkgs.lib.filter (x: !x.assertion) root.config.assertions);21 options = {22 mkHostsOption = module:23 mkOption {24 type = types.mkHostsType module;25 };26 };2728 inherit (options) mkHostsOption;2938 checkedRoot =30 modules = {39 if failedAssertions != []31 # mkDefault = mkOverride 100040 then throw "Fleet failed assertions:\n${nixpkgs.lib.concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}"32 # For places, where fleet knows better than nixpkgs defaults.33 mkFleetDefault = mkOverride 999;34 # Some generators use mkDefault, but optionDefault is set by nixpkgs.35 mkFleetGeneratorDefault = mkOverride 1001;41 else nixpkgs.lib.showWarnings root.config.warnings root;36 };42 withData = {3743 root,44 data,45 }: {46 config = root.config;38 inherit (modules) mkFleetDefault mkFleetGeneratorDefault;3940 secrets = {41 mkPassword = {size ? 32}: {42 coreutils,43 mkSecretGenerator,44 ...45 }:46 mkSecretGenerator {47 script = ''48 mkdir $out49 gh generate password -o $out/secret --size ${toString size}50 '';47 };51 };5253 mkEd25519 = {54 noEmbedPublic ? false,55 encoding ? null,56 }: {mkSecretGenerator, ...}:57 mkSecretGenerator {58 script = ''59 mkdir $out60 gh generate ed25519 -p $out/public -s $out/secret \61 ${optionalString noEmbedPublic "--no-embed-public"} \62 ${optionalString (encoding != null) "--encoding=${encoding}"}63 '';64 };6566 mkX25519 = {encoding ? null}: {mkSecretGenerator, ...}:67 mkSecretGenerator {68 script = ''69 mkdir $out70 gh generate x25519 -p $out/public -s $out/secret \71 ${optionalString (encoding != null) "--encoding=${encoding}"}72 '';73 };7475 mkRsa = {size ? 4096}: {76 openssl,77 mkSecretGenerator,78 ...79 }:80 mkSecretGenerator {81 script = ''82 mkdir $out8384 ${openssl}/bin/openssl genrsa -out rsa_private.key ${toString size}85 ${openssl}/bin/openssl rsa -in rsa_private.key -pubout -out rsa_public.key8687 cat rsa_private.key | gh private -o $out/secret88 cat rsa_public.key | gh public -o $out/public89 '';90 };9192 mkBytes = {93 count ? 32,94 encoding,95 noNuls ? false,96 }: {mkSecretGenerator, ...}:97 mkSecretGenerator {98 script = ''99 mkdir $out100 gh generate bytes --count=${toString count} --encoding=${encoding} -o $out/secret \101 ${optionalString noNuls "--no-nuls"}102 '';103 };48 defaultData = withData {104 mkHexBytes = {count ? 32}:105 mkBytes {49 inherit data;106 inherit count;50 root = checkedRoot;107 encoding = "hex";51 };108 };52 uncheckedData = withData {inherit data root;};109 mkBase64Bytes = {count ? 32}:53 in {110 mkBytes {54 inherit nixpkgs overlays;111 inherit count;55 inherit (defaultData) config;112 encoding = "base64";56 unchecked = {113 };57 inherit (uncheckedData) config;11458 };115 # Wireguard59 };116 # mkWireguard = {}: mkX25519 {encoding = "base64";};60}117 # mkWireguardPsk = {}: mkBase64Bytes {count = 32;};118 };119120 inherit (secrets) mkPassword mkEd25519 mkX25519 mkRsa mkBytes mkHexBytes mkBase64Bytes;121}61122lib/flakePart.nixdiffbeforeafterbothno changes
lib/fleetLib.nixdiffbeforeafterbothno changes
modules/fleet/_modules.nixdiffbeforeafterboth1[1[2 ./assertions.nix2 ./assertions.nix3 ./fleetLib.nix4 ./hosts.nix3 ./meta.nix5 ./meta.nix6 ./nixos.nix7 ./nixpkgs.nix4 ./secrets.nix8 ./secrets.nix5]9]610modules/fleet/assertions.nixdiffbeforeafterboth1{lib, ...}: let1{2 lib,3 config,4 ...5}: let2 inherit (lib) mkOption;6 inherit (lib.options) mkOption;3 inherit (lib.types) listOf unspecified str;7 inherit (lib.types) listOf unspecified str;8 inherit (lib.lists) map filter;4in {9in {5 options = {10 options = {6 assertions = mkOption {11 assertions = mkOption {30 the evaluation of the system configuration.35 the evaluation of the system configuration.31 '';36 '';32 };37 };38 errors = mkOption {39 type = listOf str;40 internal = true;41 description = ''42 Similar to warnings, however build will fail if any error exists.43 '';44 };33 };45 };34 # impl of assertions is in <fleet/lib/default.nix>46 config.errors =47 map (v: v.message)48 (filter (v: !v.assertion) config.assertions);35}49}3650modules/fleet/fleetLib.nixdiffbeforeafterbothno changes
modules/fleet/hosts.nixdiffbeforeafterbothno changes
modules/fleet/meta.nixdiffbeforeafterboth1{1{lib, ...}: let2 lib,3 fleetLib,4 config,5 nixpkgs,6 ...7}: let8 inherit (fleetLib) hostsToAttrs mkFleetGeneratorDefault;2 inherit (lib.modules) mkRemovedOptionModule;9 inherit (fleetLib.types) listOfAnyModule;10 inherit (lib) mkOption mkOptionType;11 inherit (lib.types) str unspecified attrsOf listOf submodule;12 hostModule = {...} @ hostConfig: let13 hostName = hostConfig.config._module.args.name;14 in {15 options = {16 nixosModules = mkOption {17 # Not too strict, but nixos module system will fix everything.18 type =19 listOfAnyModule;2021 description = "List of nixos modules";22 default = [];23 };24 system = mkOption {25 type = str;26 description = "Type of system";27 };28 encryptionKey = mkOption {29 type = str;30 description = "Encryption key";31 };32 nixosSystem = mkOption {33 type = unspecified;34 description = "Nixos configuration";35 };36 nixpkgs = mkOption {37 type = unspecified;38 description = "Nixpkgs override";39 default = nixpkgs;40 };41 };42 config = {43 nixosSystem = hostConfig.config.nixpkgs.lib.nixosSystem {44 inherit (hostConfig.config) system;45 modules = hostConfig.config.nixosModules;46 specialArgs = {47 inherit fleetLib;48 fleet = hostsToAttrs (host: config.hosts.${host}.nixosSystem.config);49 };50 };51 nixosModules.networking.hostName = mkFleetGeneratorDefault hostName;52 };53 };54 overlayType = mkOptionType {55 name = "nixpkgs-overlay";56 description = "nixpkgs overlay";57 check = lib.isFunction;58 merge = lib.mergeOneOption;59 };60in {3in {61 options = {4 imports = [62 hosts = mkOption {63 type = attrsOf (submodule hostModule);5 (mkRemovedOptionModule ["fleetModules"] "replaced with imports.")64 default = {};65 description = "Configurations of individual hosts";66 };67 nixosModules = mkOption {68 type = listOfAnyModule;69 description = "Modules, which should be added to every system";70 default = [];71 };72 overlays = mkOption {73 default = [];74 type = listOf overlayType;75 };76 };77 config = {78 hosts = hostsToAttrs (host: {6 (mkRemovedOptionModule ["data"] "data is now provided by fleet itself, you can remove your import.")79 nixosModules =80 config.nixosModules81 ++ [82 {83 nixpkgs.overlays = config.overlays;84 }85 ];86 });87 nixosModules = import ../../nixos/modules/module-list.nix;88 };7 ];89}8}909modules/fleet/nixos.nixdiffbeforeafterbothno changes
modules/fleet/nixpkgs.nixdiffbeforeafterbothno changes
modules/fleet/secrets.nixdiffbeforeafterboth4 config,4 config,5 ...5 ...6}: let6}: let7 inherit (fleetLib) hostsToAttrs;7 inherit (fleetLib.options) mkHostsOption;8 inherit (lib) mkOption mapAttrsToList mapAttrs filterAttrs concatStringsSep;8 inherit (lib.options) mkOption;9 inherit (lib.types) lazyAttrsOf unspecified nullOr listOf str bool attrsOf submodule;9 inherit (lib.types) lazyAttrsOf unspecified nullOr listOf str bool attrsOf submodule;10 inherit (lib.lists) sort elem;11 inherit (lib.attrsets) mapAttrsToList mapAttrs filterAttrs;12 inherit (lib.strings) toJSON concatStringsSep;101311 sharedSecret = {config, ...}: {14 sharedSecret = {config, ...}: {12 freeformType = lazyAttrsOf unspecified;15 freeformType = lazyAttrsOf unspecified;82 };85 };83 };86 };84 };87 };88 inherit (config) hostSecrets sharedSecrets;85in {89in {86 options = {90 options = {87 version = mkOption {91 version = mkOption {88 type = str;92 type = str;89 default = "";90 internal = true;93 internal = true;91 };94 };92 sharedSecrets = mkOption {95 sharedSecrets = mkOption {100 description = "Host secrets. Imported from fleet.nix";103 description = "Host secrets. Imported from fleet.nix";101 internal = true;104 internal = true;102 };105 };106 hosts = mkHostsOption ({config, ...}: {107 nixos = {108 secrets = let109 host = config._module.args.name;110 processSecret = v:111 (removeAttrs v ["createdAt" "expiresAt" "expectedOwners" "owners" "regenerateOnOwnerAdded" "regenerateOnOwnerRemoved"])112 // {113 shared = true;114 };115 in116 (117 mapAttrs (_: processSecret)118 (filterAttrs (_: v: elem host v.owners) sharedSecrets)119 )120 // (mapAttrs (_: processSecret) (hostSecrets.${host} or {}));121 _file = ./secrets.nix;122 };123 });103 };124 };104 config = {125 config = {105 assertions =126 assertions =106 mapAttrsToList127 mapAttrsToList107 (name: secret: {128 (name: secret: {108 assertion = secret.expectedOwners == null || builtins.sort (a: b: a < b) secret.owners == builtins.sort (a: b: a < b) secret.expectedOwners;129 assertion = secret.expectedOwners == null || sort (a: b: a < b) secret.owners == sort (a: b: a < b) secret.expectedOwners;109 message = "Shared secret ${name} is expected to be encrypted for ${builtins.toJSON secret.expectedOwners}, but it is encrypted for ${builtins.toJSON secret.owners}. Run fleet secrets regenerate to fix";130 message = "Shared secret ${name} is expected to be encrypted for ${toJSON secret.expectedOwners}, but it is encrypted for ${toJSON secret.owners}. Run fleet secrets regenerate to fix";110 })131 })111 config.sharedSecrets;132 config.sharedSecrets;112 hosts = hostsToAttrs (host: {113 nixosModules = let114 # processPart115 processSecret = v:116 (removeAttrs v ["createdAt" "expiresAt" "expectedOwners" "owners" "regenerateOnOwnerAdded" "regenerateOnOwnerRemoved"])117 // {118 shared = true;119 };120 in [121 {122 secrets =123 (124 mapAttrs (_: processSecret)125 (filterAttrs (_: v: builtins.elem host v.owners) config.sharedSecrets)126 )127 // (mapAttrs (_: processSecret) (config.hostSecrets.${host} or {}));128 }129 ];130 });131 # TODO: Should this attribute be moved to `nixpkgs.overlays`?132 overlays = [133 nixpkgs.overlays = [133 (final: prev: {134 (final: prev: {134 mkSecretGenerators = {recipients}: rec {135 mkSecretGenerators = {recipients}: rec {135 # TODO: Merge both generators to one with consistent options syntax?136 # TODO: Merge both generators to one with consistent options syntax?nixos/assertions.nixdiffbeforeafterbothno changes
nixos/meta.nixdiffbeforeafterboth3 pkgs,3 pkgs,4 ...4 ...5}: let5}: let6 inherit (lib) mkOption;6 inherit (lib.options) mkOption;7 inherit (lib.types) listOf str submodule;7 inherit (lib.types) listOf str submodule;8 inherit (lib.modules) mkRemovedOptionModule;8in {9in {9 options = {10 options = {11 # TODO: Give a real name.12 # Previously it was nixpkgs.resolvedPkgs, which was erroreously merged with nixpkgs override attribute.10 nixpkgs.resolvedPkgs = mkOption {13 _resolvedPkgs = mkOption {11 type = lib.types.pkgs // {description = "nixpkgs.pkgs";};14 type = lib.types.pkgs // {description = "nixpkgs.pkgs";};12 description = "Value of pkgs";15 description = "Value of pkgs";13 };16 };14 tags = mkOption {15 type = listOf str;16 description = "Host tags";17 default = [];18 };19 network = mkOption {17 network = mkOption {20 type = submodule {18 type = submodule {21 options = {19 options = {34 description = "Network definition of host";32 description = "Network definition of host";35 };33 };36 };34 };35 imports = [36 (mkRemovedOptionModule ["tags"] "tags are now defined at the host level, not the nixos system level for fast filtering without evaluating unnecessary hosts.")37 ];37 config = {38 config = {38 tags = ["all"];39 network = {};39 network = {};40 nixpkgs.resolvedPkgs = pkgs;40 _resolvedPkgs = pkgs;41 };41 };42}42}4343nixos/modules/module-list.nixdiffbeforeafterboth1[1[2 ../assertions.nix2 ../meta.nix3 ../meta.nix3 ../secrets.nix4 ../secrets.nix4 ../rollback.nix5 ../rollback.nixnixos/nix-sign.nixdiffbeforeafterboth1# Required for nix copy in build_systems.rs1# Required for nix copy in build_systems.rs2{config, ...}: {2{lib, config, ...}:3let4 inherit (lib.modules) mkIf;5 hasPersistentHostname = config.networking.hostName != "";6in7{3 # https://github.com/NixOS/nix/issues/30238 # https://github.com/NixOS/nix/issues/30234 systemd.services.generate-nix-cache-key = {9 systemd.services.generate-nix-cache-key = mkIf hasPersistentHostname {5 wantedBy = ["multi-user.target"];10 wantedBy = ["multi-user.target"];6 serviceConfig.Type = "oneshot";11 serviceConfig.Type = "oneshot";7 path = [config.nix.package];12 path = [config.nix.package];10 nix-store --generate-binary-cache-key ${config.networking.hostName}-1 /etc/nix/private-key /etc/nix/public-key15 nix-store --generate-binary-cache-key ${config.networking.hostName}-1 /etc/nix/private-key /etc/nix/public-key11 '';16 '';12 };17 };13 nix.settings.secret-key-files = "/etc/nix/private-key";18 nix.settings.secret-key-files = mkIf hasPersistentHostname "/etc/nix/private-key";14}19}1520nixos/secrets.nixdiffbeforeafterboth5 ...5 ...6}: let6}: let7 inherit (lib.strings) hasPrefix removePrefix;7 inherit (lib.strings) hasPrefix removePrefix;8 inherit (lib.stringsWithDeps) stringAfter;9 inherit (lib.options) mkOption;10 inherit (lib.lists) optional;11 inherit (lib.attrsets) mapAttrs;8 inherit (lib) mkOption mkOptionDefault mapAttrs stringAfter;12 inherit (lib.modules) mkOptionDefault mkIf;9 inherit (lib.types) submodule str attrsOf nullOr unspecified lazyAttrsOf;13 inherit (lib.types) submodule str attrsOf nullOr unspecified lazyAttrsOf;10 plaintextPrefix = "<PLAINTEXT>";14 plaintextPrefix = "<PLAINTEXT>";11 plaintextNewlinePrefix = "<PLAINTEXT-NL>";15 plaintextNewlinePrefix = "<PLAINTEXT-NL>";110 builtins.toJSON (mapAttrs (_: processSecret)114 builtins.toJSON (mapAttrs (_: processSecret)111 config.secrets);115 config.secrets);112 };116 };117 useSysusers = (config.systemd ? sysusers && config.systemd.sysusers.enable) || (config ? userborn && config.userborn.enable);113in {118in {114 options = {119 options = {115 secrets = mkOption {120 secrets = mkOption {121 config = {126 config = {122 environment.systemPackages = [pkgs.fleet-install-secrets];127 environment.systemPackages = [pkgs.fleet-install-secrets];128129 systemd.services.fleet-install-secrets = mkIf useSysusers {130 wantedBy = ["sysinit.target"];131 after = ["systemd-sysusers.service"];132 restartTriggers = [133 secretsFile134 ];135 aliases = [136 "sops-install-secrets"137 "agenix-install-secrets"138 ];139140 unitConfig.DefaultDependencies = false;141142 serviceConfig = {143 Type = "oneshot";144 RemainAfterExit = true;145 ExecStart = "${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}";146 };147 };123 system.activationScripts.decryptSecrets =148 system.activationScripts.decryptSecrets =149 mkIf (!useSysusers)150 (124 stringAfter (151 stringAfter (125 [152 [126 # secrets are owned by user/group, thus we need to refer to those153 # secrets are owned by user/group, thus we need to refer to those131 # nixos-impermanence compatibility: secrets are encrypted by host-key,158 # nixos-impermanence compatibility: secrets are encrypted by host-key,132 # but with impermanence we expect that the host-key is installed by159 # but with impermanence we expect that the host-key is installed by133 # persist-file activation script.160 # persist-file activation script.134 ++ (lib.optional (config.system.activationScripts ? "persist-files") "persist-files")161 ++ (optional (config.system.activationScripts ? "persist-files") "persist-files")135 ) ''162 ) ''136 1>&2 echo "setting up secrets"163 1>&2 echo "setting up secrets"137 ${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}164 ${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}165 ''138 '';166 );139 };167 };140}168}141169