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

difftreelog

refactor declare configuration using flake parts

Yaroslav Bolyukin2024-08-14parent: #f779c26.patch.diff
in: trunk

29 files changed

modifiedCargo.lockdiffbeforeafterboth
1432name = "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",
modifiedREADME.adocdiffbeforeafterboth
24 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 = let
40 pkgs = import nixpkgs {system = "x86_64-linux";};
41 in
42 pkgs.alejandra;34 imports = [inputs.fleet.flakeModules.default];
4335
44 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";38
47 };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}.fleet
52 ];43 ];
53 };44 };
45 };
5446
55 # 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 systems
58 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.nix
60 # treat the contents of this file as implementation detail
61 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.lanzaboote
66 {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 ./wireguard
82 # Multi-instancible modules example71 # Multi-instancible modules example
83 (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 built
90 system = "x86_64-linux";79 system = "x86_64-linux";
91 # And nixos modules80 # And nixos modules
92 nixosModules = [81 nixos.imports = [
93 ./controlplane-1/hardware-configuration.nix82 ./controlplane-1/hardware-configuration.nix
94 ./controlplane-1/configuration.nix83 ./controlplane-1/configuration.nix
95 # Configuration may also be specified inline, as in any nixos config.84 # Configuration may also be specified inline, as in any nixos config.
modifiedcmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth
254 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 .nixosSystem
260 .config
335 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.nix
359 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;
modifiedcmds/fleet/src/cmds/info.rsdiffbeforeafterboth
38 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;
modifiedcmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth
598 return Ok(());598 return Ok(());
599 }599 }
600600
601 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 }]);
603603
604 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() {
modifiedcmds/fleet/src/command.rsdiffbeforeafterboth
9use tokio_util::codec::{BytesCodec, FramedRead, LinesCodec};9use tokio_util::codec::{BytesCodec, FramedRead, LinesCodec};
10use tracing::debug;10use tracing::debug;
11
12use crate::host::EscalationStrategy;
1113
12fn 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}
32
30#[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 }
76
56 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 self
161 }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 self
165 // 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 out
195 }
196 EscalationStrategy::Sudo => {
197 let mut out = self.new_here("sudo");
198 out.args(self.into_args());
199 out
200 }
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");
205
206 // Red backgrounds messes with fleet formatting
207 run0.arg("--background=");
175 out.args(self.into_args());208 run0.args(self.into_args());
209
210 out.arg("-q");
211 out.arg("/dev/null");
212 out.arg("-c");
213 out.arg(run0.into_string());
214 dbg!(&out);
176 out215 out
177 }216 }
178 }217 }
218 }
179219
180 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 }
202242
203 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()).await
216 }256 }
modifiedcmds/fleet/src/host.rsdiffbeforeafterboth
1use 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;
2829
29use 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.config
41 pub config_field: Value,42 pub config_field: Value,
42 /// fleet_config.unchecked.config
43 pub config_unchecked_field: Value,
4443
45 /// import nixpkgs {system = local};44 /// import nixpkgs {system = local};
46 pub default_pkgs: Value,45 pub default_pkgs: Value,
57 }56 }
58}57}
58
59#[derive(Clone, Copy, Debug)]
60pub enum EscalationStrategy {
61 Sudo,
62 Run0,
63 Su,
64}
5965
60pub 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>>,
6672
67 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 polkit
79 // 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);
8098
81 let _ = self.groups.set(tags.clone());99 let _ = self.groups.set(tags.clone());
82100
83 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?;
112
113 let _ = self.nixos_config.set(nixos_config.clone());
114
115 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: TOCTOU
88 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();
92
93 let session = session124 let session = session
94 .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 much
172 // // 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 etc
176 // }
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 = self
182 .cmd_escalation(
183 // Not used
184 EscalationStrategy::Su,
185 "which",
186 )
187 .await?;
188 cmd.arg(command);
189 cmd.run_string().await
190 }
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 where
134 <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 .await
201 }
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 }
147214
182 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 used
253 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 }
211282
212 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 }
234301
235 /// Packages for this host, resolved with nixpkgs overlays302 /// Packages for this host, resolved with nixpkgs overlays
236 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}
243308
317 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 }
328394
329 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 }]);
398
399
332 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 }
354423
355 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 flake
362 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.nix
420 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 }].expectedOwners
426 ))495 ))
525 }594 }
526 let local_system = self.local_system.clone();595 let local_system = self.local_system.clone();
596
597 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)?;
527601
528 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 }));
530604
531 let config_field = nix_go!(fleet_field.config);605 let config_field = nix_go!(fleet_field.config);
606
532 let config_unchecked_field = nix_go!(fleet_field.unchecked.config);607 assert_warn("fleet config evaluation", &config_field).await?;
533608
534 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);
537612
538 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 }));
542
543 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)?;
547617
548 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 }
modifiedcmds/fleet/src/main.rsdiffbeforeafterboth
58 path.push("file://");58 path.push("file://");
59 path.push(entry.path());59 path.push(entry.path());
6060
61 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?;
modifiedcrates/nix-eval/Cargo.tomldiffbeforeafterboth
5build = "build.rs"5build = "build.rs"
66
7[dependencies]7[dependencies]
8anyhow.workspace = true
8better-command.workspace = true9better-command.workspace = true
9futures = "0.3.30"10futures = "0.3.30"
10itertools = "0.13.0"11itertools = "0.13.0"
modifiedcrates/nix-eval/src/lib.rsdiffbeforeafterboth
17// Contains macros helpers17// Contains macros helpers
18#[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"));
addedcrates/nix-eval/src/util.rsdiffbeforeafterboth

no changes

modifiedcrates/nix-eval/src/value.rsdiffbeforeafterboth
44 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)
modifiedflake.nixdiffbeforeafterboth
25 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;
40
38 # To be used with https://github.com/NixOS/nix/pull/889241 # To be used with https://github.com/NixOS/nix/pull/8892
39 schemas = {42 schemas = let
43 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 }: let
72 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-config
109 openssl115 openssl
110 bacon116 bacon
117 nil
111 ];118 ];
112 };119 };
113 };120 };
modifiedlib/default.nixdiffbeforeafterboth
1# Shared functions for fleet configuration, available as `fleet` module argument
1{fleetPkgsForPkgs}: {2{lib}: let
2 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 it
11 fleetModules,
12 nixosModules ? [],
13 extraFleetLib ? {},
14 }: let
15 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 data
27 ({...}: {
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;17
33 specialArgs = {18 mkHostsType = module: attrsOf (submodule module);
34 inherit nixpkgs fleetLib;
35 };
36 };19 };
20
37 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 };
27
28 inherit (options) mkHostsOption;
29
38 checkedRoot =30 modules = {
39 if failedAssertions != []31 # mkDefault = mkOverride 1000
40 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 = {37
43 root,
44 data,
45 }: {
46 config = root.config;38 inherit (modules) mkFleetDefault mkFleetGeneratorDefault;
39
40 secrets = {
41 mkPassword = {size ? 32}: {
42 coreutils,
43 mkSecretGenerator,
44 ...
45 }:
46 mkSecretGenerator {
47 script = ''
48 mkdir $out
49 gh generate password -o $out/secret --size ${toString size}
50 '';
47 };51 };
52
53 mkEd25519 = {
54 noEmbedPublic ? false,
55 encoding ? null,
56 }: {mkSecretGenerator, ...}:
57 mkSecretGenerator {
58 script = ''
59 mkdir $out
60 gh generate ed25519 -p $out/public -s $out/secret \
61 ${optionalString noEmbedPublic "--no-embed-public"} \
62 ${optionalString (encoding != null) "--encoding=${encoding}"}
63 '';
64 };
65
66 mkX25519 = {encoding ? null}: {mkSecretGenerator, ...}:
67 mkSecretGenerator {
68 script = ''
69 mkdir $out
70 gh generate x25519 -p $out/public -s $out/secret \
71 ${optionalString (encoding != null) "--encoding=${encoding}"}
72 '';
73 };
74
75 mkRsa = {size ? 4096}: {
76 openssl,
77 mkSecretGenerator,
78 ...
79 }:
80 mkSecretGenerator {
81 script = ''
82 mkdir $out
83
84 ${openssl}/bin/openssl genrsa -out rsa_private.key ${toString size}
85 ${openssl}/bin/openssl rsa -in rsa_private.key -pubout -out rsa_public.key
86
87 cat rsa_private.key | gh private -o $out/secret
88 cat rsa_public.key | gh public -o $out/public
89 '';
90 };
91
92 mkBytes = {
93 count ? 32,
94 encoding,
95 noNuls ? false,
96 }: {mkSecretGenerator, ...}:
97 mkSecretGenerator {
98 script = ''
99 mkdir $out
100 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;114
58 };115 # Wireguard
59 };116 # mkWireguard = {}: mkX25519 {encoding = "base64";};
60}117 # mkWireguardPsk = {}: mkBase64Bytes {count = 32;};
118 };
119
120 inherit (secrets) mkPassword mkEd25519 mkX25519 mkRsa mkBytes mkHexBytes mkBase64Bytes;
121}
61122
addedlib/flakePart.nixdiffbeforeafterboth

no changes

deletedlib/fleetLib.nixdiffbeforeafterboth

no changes

modifiedmodules/fleet/_modules.nixdiffbeforeafterboth
1[1[
2 ./assertions.nix2 ./assertions.nix
3 ./fleetLib.nix
4 ./hosts.nix
3 ./meta.nix5 ./meta.nix
6 ./nixos.nix
7 ./nixpkgs.nix
4 ./secrets.nix8 ./secrets.nix
5]9]
610
modifiedmodules/fleet/assertions.nixdiffbeforeafterboth
1{lib, ...}: let1{
2 lib,
3 config,
4 ...
5}: let
2 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}
3650
addedmodules/fleet/fleetLib.nixdiffbeforeafterboth

no changes

addedmodules/fleet/hosts.nixdiffbeforeafterboth

no changes

modifiedmodules/fleet/meta.nixdiffbeforeafterboth
1{1{lib, ...}: let
2 lib,
3 fleetLib,
4 config,
5 nixpkgs,
6 ...
7}: let
8 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: let
13 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;
20
21 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.nixosModules
81 ++ [
82 {
83 nixpkgs.overlays = config.overlays;
84 }
85 ];
86 });
87 nixosModules = import ../../nixos/modules/module-list.nix;
88 };7 ];
89}8}
909
addedmodules/fleet/nixos.nixdiffbeforeafterboth

no changes

addedmodules/fleet/nixpkgs.nixdiffbeforeafterboth

no changes

modifiedmodules/fleet/secrets.nixdiffbeforeafterboth
4 config,4 config,
5 ...5 ...
6}: let6}: let
7 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;
1013
11 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 = let
109 host = config._module.args.name;
110 processSecret = v:
111 (removeAttrs v ["createdAt" "expiresAt" "expectedOwners" "owners" "regenerateOnOwnerAdded" "regenerateOnOwnerRemoved"])
112 // {
113 shared = true;
114 };
115 in
116 (
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 mapAttrsToList
107 (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 = let
114 # processPart
115 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?
addednixos/assertions.nixdiffbeforeafterboth

no changes

modifiednixos/meta.nixdiffbeforeafterboth
3 pkgs,3 pkgs,
4 ...4 ...
5}: let5}: let
6 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}
4343
modifiednixos/modules/module-list.nixdiffbeforeafterboth
1[1[
2 ../assertions.nix
2 ../meta.nix3 ../meta.nix
3 ../secrets.nix4 ../secrets.nix
4 ../rollback.nix5 ../rollback.nix
modifiednixos/nix-sign.nixdiffbeforeafterboth
1# Required for nix copy in build_systems.rs1# Required for nix copy in build_systems.rs
2{config, ...}: {2{lib, config, ...}:
3let
4 inherit (lib.modules) mkIf;
5 hasPersistentHostname = config.networking.hostName != "";
6in
7{
3 # https://github.com/NixOS/nix/issues/30238 # https://github.com/NixOS/nix/issues/3023
4 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-key
11 '';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}
1520
modifiednixos/secrets.nixdiffbeforeafterboth
5 ...5 ...
6}: let6}: let
7 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];
128
129 systemd.services.fleet-install-secrets = mkIf useSysusers {
130 wantedBy = ["sysinit.target"];
131 after = ["systemd-sysusers.service"];
132 restartTriggers = [
133 secretsFile
134 ];
135 aliases = [
136 "sops-install-secrets"
137 "agenix-install-secrets"
138 ];
139
140 unitConfig.DefaultDependencies = false;
141
142 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 those
131 # 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 by
133 # 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