difftreelog
refactor provide cross system using argument
in: trunk
5 files changed
cmds/fleet/src/better_nix_eval.rsdiffbeforeafterboth--- a/cmds/fleet/src/better_nix_eval.rs
+++ b/cmds/fleet/src/better_nix_eval.rs
@@ -16,7 +16,7 @@
use tokio::select;
use tokio::sync::{mpsc, oneshot};
use tokio_util::codec::{FramedRead, LinesCodec};
-use tracing::{debug, error, warn};
+use tracing::{debug, error, warn, Level};
use crate::command::{ClonableHandler, Handler, NixHandler, NoopHandler};
@@ -247,6 +247,10 @@
Ok(())
}
async fn send_command(&mut self, cmd: impl AsRef<[u8]>) -> Result<()> {
+ if tracing::enabled!(Level::DEBUG) {
+ let cmd_str = String::from_utf8_lossy(cmd.as_ref());
+ tracing::debug!("{cmd_str}");
+ };
self.stdin.write_all(cmd.as_ref()).await?;
self.stdin.write_all(b"\n").await?;
Ok(())
@@ -420,7 +424,7 @@
}
pub fn apply(v: impl Serialize) -> Self {
let serialized = nixlike::serialize(v).expect("invalid value for apply");
- Self::Apply(serialized)
+ Self::Apply(serialized.trim_end().to_owned())
}
}
impl Display for Index {
@@ -434,8 +438,7 @@
write!(f, ".{v}")
}
Index::Apply(o) => {
- let v = nixlike::serialize(o).map_err(|_| fmt::Error)?;
- write!(f, "<apply>({v})")
+ write!(f, "<apply>({o})")
}
Index::Idx(i) => {
write!(f, "[{i}]")
@@ -451,7 +454,6 @@
struct PathDisplay<'i>(&'i [Index]);
impl Display for PathDisplay<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "flake")?;
for i in self.0 {
write!(f, "{i}")?;
}
@@ -508,8 +510,8 @@
query.push_str(escaped.trim());
}
Index::Apply(a) => {
- query.push(' ');
- query.push_str(&a);
+ // In cases like `a {}.b` first `{}.b` will be evaluated, so `a {}` should be encased in `()`
+ query = format!("({query} {a})");
}
Index::Idx(idx) => {
query = format!("builtins.elemAt ({query}) {idx}");
@@ -560,7 +562,7 @@
.await
.execute_expression_raw(&format!(":b sess_field_{id}"), &mut NixHandler::default())
.await?;
- ensure!(!vid.is_empty(), "build failed");
+ ensure!(!vid.is_empty(), "build failed: {}", PathDisplay(&self.full_path));
let Some(vid) = vid.strip_prefix("This derivation produced the following outputs:\n")
else {
panic!("unexpected build output: {vid:?}");
cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth--- a/cmds/fleet/src/cmds/build_systems.rs
+++ b/cmds/fleet/src/cmds/build_systems.rs
@@ -5,7 +5,7 @@
use crate::command::MyCommand;
use crate::host::Config;
use crate::nix_path;
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, Result, Context};
use clap::Parser;
use itertools::Itertools;
use tokio::{task::LocalSet, time::sleep};
@@ -292,13 +292,14 @@
let action = Action::from(self.subcommand.clone());
let drv = config
.fleet_field
- .select(nix_path!(.buildSystems.{action.build_attr()}.{&host}))
- .await?;
+ .select(nix_path!(.buildSystems((serde_json::json!({
+ "localSystem": config.local_system.clone(),
+ }))).{action.build_attr()}.{&host}))
+ .await.context("system attribute")?;
let outputs = drv.build().await.map_err(|e| {
if action.build_attr() == "sdImage" {
info!("sd-image build failed");
info!("Make sure you have imported modulesPath/installer/sd-card/sd-image-<arch>[-installer].nix (For installer, you may want to check config)");
- info!("This module was automatically imported before, but was removed for better customization")
}
e
})?;
@@ -311,6 +312,10 @@
if !config.is_local(&host) {
info!("uploading system closure");
{
+ // Alternatively, nix store make-content-addressed can be used,
+ // at least for the first deployment, to provide trusted store key.
+ //
+ // It is much slower, yet doesn't require root on the deployer machine.
let mut sign = MyCommand::new("nix");
// Private key for host machine is registered in nix-sign.nix
sign.arg("store")
cmds/fleet/src/host.rsdiffbeforeafterboth1use std::{2 env::current_dir,3 ffi::OsString,4 io::Write,5 ops::Deref,6 path::PathBuf,7 sync::{Arc, Mutex, MutexGuard},8};910use anyhow::{anyhow, bail, Context, Result};11use clap::{ArgGroup, Parser};12use openssh::SessionBuilder;13use tempfile::NamedTempFile;1415use crate::{16 better_nix_eval::{Field, Index, NixSessionPool},17 command::MyCommand,18 fleetdata::{FleetData, FleetSecret, FleetSharedSecret},19 nix_path,20};2122pub struct FleetConfigInternals {23 pub local_system: String,24 pub directory: PathBuf,25 pub opts: FleetOpts,26 pub data: Mutex<FleetData>,27 pub nix_args: Vec<OsString>,28 /// fleetConfigurations.<name>.<localSystem>29 pub fleet_field: Field,30 /// fleet_config.configUnchecked31 pub config_field: Field,32}3334#[derive(Clone)]35pub struct Config(Arc<FleetConfigInternals>);3637impl Deref for Config {38 type Target = FleetConfigInternals;3940 fn deref(&self) -> &Self::Target {41 &self.042 }43}4445pub struct ConfigHost {46 pub name: String,47}48impl ConfigHost {49 async fn open_session(&self) -> Result<openssh::Session> {50 let mut session = SessionBuilder::default();5152 session53 .connect(&self.name)54 .await55 .map_err(|e| anyhow!("ssh error: {e}"))56 }57}5859impl Config {60 pub fn should_skip(&self, host: &str) -> bool {61 if !self.opts.skip.is_empty() {62 self.opts.skip.iter().any(|h| h as &str == host)63 } else if !self.opts.only.is_empty() {64 !self.opts.only.iter().any(|h| h as &str == host)65 } else {66 false67 }68 }69 pub fn is_local(&self, host: &str) -> bool {70 self.opts.localhost.as_ref().map(|s| s as &str) == Some(host)71 }7273 pub async fn run_on(&self, host: &str, mut command: MyCommand, sudo: bool) -> Result<()> {74 if sudo {75 command = command.sudo();76 }77 if !self.is_local(host) {78 command = command.ssh(host);79 }80 command.run().await81 }82 pub async fn run_string_on(83 &self,84 host: &str,85 mut command: MyCommand,86 sudo: bool,87 ) -> Result<String> {88 if sudo {89 command = command.sudo();90 }91 if !self.is_local(host) {92 command = command.ssh(host);93 }94 command.run_string().await95 }9697 pub async fn list_hosts(&self) -> Result<Vec<ConfigHost>> {98 let names = self99 .fleet_field100 .select(nix_path!(.configuredHosts))101 .await?102 .list_fields()103 .await?;104 let mut out = vec![];105 for name in names {106 out.push(ConfigHost { name })107 }108 Ok(out)109 }110 pub async fn system_config(&self, host: &str) -> Result<Field> {111 self.fleet_field112 .select(nix_path!(.configuredSystems.{host}.config))113 .await114 }115116 pub(super) fn data(&self) -> MutexGuard<FleetData> {117 self.data.lock().unwrap()118 }119 pub(super) fn data_mut(&self) -> MutexGuard<FleetData> {120 self.data.lock().unwrap()121 }122 /// Shared secrets configured in fleet.nix or in flake123 pub async fn list_configured_shared(&self) -> Result<Vec<String>> {124 self.config_field125 .select(nix_path!(.sharedSecrets))126 .await?127 .list_fields()128 .await129 }130 /// Shared secrets configured in fleet.nix131 pub fn list_shared(&self) -> Vec<String> {132 let data = self.data();133 data.shared_secrets.keys().cloned().collect()134 }135 pub fn has_shared(&self, name: &str) -> bool {136 let data = self.data();137 data.shared_secrets.contains_key(name)138 }139 pub fn replace_shared(&self, name: String, shared: FleetSharedSecret) {140 let mut data = self.data_mut();141 data.shared_secrets.insert(name.to_owned(), shared);142 }143 pub fn remove_shared(&self, secret: &str) {144 let mut data = self.data_mut();145 data.shared_secrets.remove(secret);146 }147148 pub fn has_secret(&self, host: &str, secret: &str) -> bool {149 let data = self.data();150 let Some(host_secrets) = data.host_secrets.get(host) else {151 return false;152 };153 host_secrets.contains_key(secret)154 }155 pub fn insert_secret(&self, host: &str, secret: String, value: FleetSecret) {156 let mut data = self.data_mut();157 let host_secrets = data.host_secrets.entry(host.to_owned()).or_default();158 host_secrets.insert(secret, value);159 }160161 pub async fn decrypt_on_host(&self, host: &str, data: Vec<u8>) -> Result<Vec<u8>> {162 let data = z85::encode(&data);163 let mut cmd = MyCommand::new("fleet-install-secrets");164 cmd.arg("decrypt").eqarg("--secret", data);165 cmd = cmd.sudo().ssh(host);166 let encoded = cmd167 .run_string()168 .await169 .context("failed to call remote host for decrypt")?170 .trim()171 .to_owned();172 z85::decode(encoded).context("bad encoded data? outdated host?")173 }174 pub async fn reencrypt_on_host(175 &self,176 host: &str,177 data: Vec<u8>,178 targets: Vec<String>,179 ) -> Result<Vec<u8>> {180 let data = z85::encode(&data);181 let mut recmd = MyCommand::new("fleet-install-secrets");182 recmd.arg("reencrypt").eqarg("--secret", data);183 for target in targets {184 recmd.eqarg("--targets", target);185 }186 recmd = recmd.sudo().ssh(host);187 let encoded = recmd188 .run_string()189 .await190 .context("failed to call remote host for decrypt")?191 .trim()192 .to_owned();193 z85::decode(encoded).context("bad encoded data? outdated host?")194 }195196 pub fn host_secret(&self, host: &str, secret: &str) -> Result<FleetSecret> {197 let data = self.data();198 let Some(host_secrets) = data.host_secrets.get(host) else {199 bail!("no secrets for machine {host}");200 };201 let Some(secret) = host_secrets.get(secret) else {202 bail!("machine {host} has no secret {secret}");203 };204 Ok(secret.clone())205 }206 pub fn shared_secret(&self, secret: &str) -> Result<FleetSharedSecret> {207 let data = self.data();208 let Some(secret) = data.shared_secrets.get(secret) else {209 bail!("no shared secret {secret}");210 };211 Ok(secret.clone())212 }213 pub async fn shared_secret_expected_owners(&self, secret: &str) -> Result<Vec<String>> {214 self.config_field215 .select(nix_path!(.sharedSecrets.{secret}.expectedOwners))216 .await?217 .as_json()218 .await219 }220221 pub fn save(&self) -> Result<()> {222 let mut tempfile = NamedTempFile::new_in(self.directory.clone())?;223 let data = nixlike::serialize(&self.data() as &FleetData)?;224 tempfile.write_all(225 format!(226 "# This file contains fleet state and shouldn't be edited by hand\n\n{}\n\n# vim: ts=2 et nowrap\n",227 data228 )229 .as_bytes(),230 )?;231 let mut fleet_data_path = self.directory.clone();232 fleet_data_path.push("fleet.nix");233 tempfile.persist(fleet_data_path)?;234 Ok(())235 }236}237238#[derive(Parser, Clone)]239#[clap(group = ArgGroup::new("target_hosts"))]240pub struct FleetOpts {241 /// All hosts except those would be skipped242 #[clap(long, number_of_values = 1, group = "target_hosts")]243 only: Vec<String>,244245 /// Hosts to skip246 #[clap(long, number_of_values = 1, group = "target_hosts")]247 skip: Vec<String>,248249 /// Host, which should be threaten as current machine250 #[clap(long)]251 pub localhost: Option<String>,252253 // TODO: unhardcode x86_64-linux254 /// Override detected system for host, to perform builds via255 /// binfmt-declared qemu instead of trying to crosscompile256 #[clap(long, default_value = "detect")]257 pub local_system: String,258}259260impl FleetOpts {261 pub async fn build(mut self, nix_args: Vec<OsString>) -> Result<Config> {262 if self.localhost.is_none() {263 self.localhost264 .replace(hostname::get().unwrap().to_str().unwrap().to_owned());265 }266 let directory = current_dir()?;267268 let pool = NixSessionPool::new(directory.as_os_str().to_owned(), nix_args.clone()).await?;269 let root_field = pool.get().await?;270271 if self.local_system == "detect" {272 let builtins_field = Field::field(root_field.clone(), "builtins").await?;273 let system = builtins_field274 .select(nix_path!(.currentSystem))275 .await?;276 self.local_system = system.as_json().await?;277 }278 let local_system = self.local_system.clone();279280 let fleet_root = Field::field(root_field, "fleetConfigurations").await?;281282 let fleet_field = fleet_root283 .select(nix_path!(.default.{&local_system}))284 .await?;285 let config_field = fleet_field286 .select(nix_path!(.configUnchecked))287 .await?;288289 let mut fleet_data_path = directory.clone();290 fleet_data_path.push("fleet.nix");291 let bytes = std::fs::read_to_string(fleet_data_path)?;292 let data = nixlike::parse_str(&bytes)?;293294 Ok(Config(Arc::new(FleetConfigInternals {295 opts: self,296 directory,297 data,298 local_system,299 nix_args,300 fleet_field,301 config_field,302 })))303 }304}cmds/fleet/src/main.rsdiffbeforeafterboth--- a/cmds/fleet/src/main.rs
+++ b/cmds/fleet/src/main.rs
@@ -24,7 +24,7 @@
use host::{Config, FleetOpts};
use human_repr::HumanCount;
use indicatif::{ProgressState, ProgressStyle};
-use tracing::{info, metadata::LevelFilter};
+use tracing::info;
use tracing::{info_span, Instrument};
use tracing_indicatif::IndicatifLayer;
use tracing_subscriber::{prelude::*, EnvFilter};
@@ -99,27 +99,6 @@
Ok(())
}
-// fn main() -> Result<()> {
-// let pool = r2d2::Builder::<NixSessionPool>::new()
-// .min_idle(Some(1))
-// .max_lifetime(Some(Duration::from_secs(10)))
-// .build(NixSessionPool {
-// flake: ".".to_owned(),
-// nix_args: vec![],
-// })?;
-// let conn = pool.get()?;
-// let field = Field::root(conn);
-// // let builtins = field.get_field("builtins")?;
-// let cur_sys: String = field.get_field("builtins")?.as_json()?;
-// eprintln!("current system = {cur_sys}");
-// let v = field.get_field("fleetConfigurations")?;
-// eprintln!("configs = {:?}", v.list_fields()?);
-// let d = v.get_field("default")?;
-// dbg!(d.list_fields());
-// Ok(())
-// }
-//
-
fn setup_logging() {
let indicatif_layer = IndicatifLayer::new().with_progress_style(
ProgressStyle::with_template(
@@ -157,7 +136,7 @@
),
);
- let filter = EnvFilter::from_default_env().add_directive(LevelFilter::INFO.into());
+ let filter = EnvFilter::from_default_env();
tracing_subscriber::registry()
.with(
lib/default.nixdiffbeforeafterboth--- a/lib/default.nix
+++ b/lib/default.nix
@@ -11,8 +11,7 @@
inherit nixpkgs hostNames;
};
in
- # Top-level arg is the builder system (not the target system!)
- nixpkgs.lib.genAttrs flake-utils.lib.defaultSystems (system: let
+ let
withData = data: rec {
root = nixpkgs.lib.evalModules {
modules = (import ../modules/fleet/_modules.nix) ++ [config data];
@@ -36,21 +35,7 @@
inherit name;
value = nixpkgs.lib.nixosSystem {
system = configuredHosts.${name}.system;
- modules =
- configuredHosts.${name}.modules
- ++ extraModules
- ++ [
- ({...}: {
- nixpkgs.system = system;
- nixpkgs.localSystem.system = system;
- nixpkgs.crossSystem =
- if system == configuredHosts.${name}.system
- then null
- else {
- system = configuredHosts.${name}.system;
- };
- })
- ];
+ modules = configuredHosts.${name}.modules ++ extraModules;
specialArgs = {
inherit fleetLib;
fleet = fleetLib.hostsToAttrs (host: configuredSystems.${host}.config);
@@ -60,19 +45,28 @@
)
(builtins.attrNames rootAssertWarn.config.hosts)
);
- buildSystems = {
+ buildSystems = {localSystem}: let
+ buildConfigurationModule = {config, ...}: {
+ # Equivalent to nixpkgs.localSystem
+ # nixpkgs.system = localSystem;
+ nixpkgs.buildPlatform.system = localSystem;
+ };
+ in {
toplevel = builtins.mapAttrs (_name: value: value.config.system.build.toplevel) (configuredSystemsWithExtraModules [
+ buildConfigurationModule
({...}: {
buildTarget = "toplevel";
})
]);
sdImage = builtins.mapAttrs (_name: value: value.config.system.build.sdImage) (configuredSystemsWithExtraModules [
+ buildConfigurationModule
#(nixpkgs + "/nixos/modules/installer/sd-card/sd-image-aarch64-installer.nix")
({...}: {
buildTarget = "sd-image";
})
]);
installationCd = builtins.mapAttrs (_name: value: value.config.system.build.isoImage) (configuredSystemsWithExtraModules [
+ buildConfigurationModule
(nixpkgs + "/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix")
({lib, ...}: {
buildTarget = "installation-cd";
@@ -91,5 +85,5 @@
in {
inherit (injectedData) configuredHosts configuredSecrets configuredSystems buildSystems configUnchecked;
};
- });
+ };
}