From 9aa5b3bcf9fd509a8170c4acdccdb958b1feb140 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Tue, 16 Jun 2026 21:16:50 -0400 Subject: [PATCH] hydra-builder: move operational settings from CLI flags to a TOML config file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The builder took every tuning knob as a `--flag`, so the NixOS and Darwin modules reconstructed a dozen-argument `ExecStart` and each setting existed twice: once as a module option, once as a CLI flag. The queue runner already solved this with a `--config-path` TOML file; the builder now follows the same shape. `config.rs` splits into three concerns: - `Args` (the clap entry point) carries only `--config-path`, which is meta-config locating the file, plus a flattened `Cli`. - `Cli` keeps the connection/security options that don't belong in a world-readable config file: gateway endpoint, mTLS cert paths, auth token. - `AppConfig` (serde, `camelCase`, `deny_unknown_fields`) holds the operational settings, each defaulting to the old clap default, so a missing file behaves exactly as before. `systems` and `supportedFeatures` stay `Option`: absent means "read from `nix show-config`", an explicit `[]` means "advertise none" — the modules type them as `nullOr (listOf ...)` so that distinction survives into the generated TOML. It was unclear how to take advantage of this `None` for `Some([])` distinction before --- certainly the old NixOS module was not doing it. The modules now generate `/etc/hydra/builder.toml` with `pkgs.formats.toml` and pass only the connection/security flags. This also drops the old `--use-substitutes` always-on bug: the modules guarded it with `useSubstitutes != null`, always true for a non-null bool, so the option never actually took effect. --- Cargo.lock | 1 + darwin-modules/builder-module.nix | 186 +++++++++++------------ nixos-modules/builder-module.nix | 187 +++++++++++------------- subprojects/hydra-builder/Cargo.toml | 1 + subprojects/hydra-builder/src/config.rs | 154 ++++++++++++++----- subprojects/hydra-builder/src/main.rs | 5 +- subprojects/hydra-builder/src/state.rs | 48 +++--- 7 files changed, 315 insertions(+), 267 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45b90d1a6..227a31293 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1800,6 +1800,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", + "toml", "tonic", "tonic-prost", "tower", diff --git a/darwin-modules/builder-module.nix b/darwin-modules/builder-module.nix index da8c1da9a..f50b5ade9 100644 --- a/darwin-modules/builder-module.nix +++ b/darwin-modules/builder-module.nix @@ -7,6 +7,8 @@ let cfg = config.services.hydra-queue-builder-dev; user = config.users.users.hydra-queue-builder; + + format = pkgs.formats.toml { }; in { options = { @@ -18,85 +20,93 @@ in type = lib.types.singleLineStr; }; - pingInterval = lib.mkOption { - description = "Interval in which pings are send to the runner"; - type = lib.types.ints.positive; - default = 10; - }; + settings = lib.mkOption { + description = "Builder settings written to the TOML config file"; + type = lib.types.submodule { + options = { + pingInterval = lib.mkOption { + description = "Interval in which pings are send to the runner"; + type = lib.types.ints.positive; + default = 10; + }; - speedFactor = lib.mkOption { - description = "Additional Speed factor for this machine"; - type = lib.types.oneOf [ - lib.types.ints.positive - lib.types.float - ]; - default = 1; - }; + speedFactor = lib.mkOption { + description = "Additional Speed factor for this machine"; + type = lib.types.oneOf [ + lib.types.ints.positive + lib.types.float + ]; + default = 1; + }; - maxJobs = lib.mkOption { - description = "Maximum allowed of jobs. This only is used if the queue runner uses this metrics for determining free machines."; - type = lib.types.ints.positive; - default = 4; - }; + maxJobs = lib.mkOption { + description = "Maximum allowed of jobs. This only is used if the queue runner uses this metrics for determining free machines."; + type = lib.types.ints.positive; + default = 4; + }; - buildDirAvailThreshold = lib.mkOption { - description = "Threshold in percent for nix build dir before jobs are no longer scheduled on the machine"; - type = lib.types.float; - default = 10.0; - }; + buildDirAvailThreshold = lib.mkOption { + description = "Threshold in percent for nix build dir before jobs are no longer scheduled on the machine"; + type = lib.types.float; + default = 10.0; + }; - storeAvailThreshold = lib.mkOption { - description = "Threshold in percent for /nix/store before jobs are no longer scheduled on the machine"; - type = lib.types.float; - default = 10.0; - }; + storeAvailThreshold = lib.mkOption { + description = "Threshold in percent for /nix/store before jobs are no longer scheduled on the machine"; + type = lib.types.float; + default = 10.0; + }; - load1Threshold = lib.mkOption { - description = "Maximum Load1 threshold before we stop scheduling jobs on that node. Only used if PSI is not available."; - type = lib.types.float; - default = 8.0; - }; + load1Threshold = lib.mkOption { + description = "Maximum Load1 threshold before we stop scheduling jobs on that node. Only used if PSI is not available."; + type = lib.types.float; + default = 8.0; + }; - cpuPsiThreshold = lib.mkOption { - description = "Maximum CPU PSI in the last 10s before we stop scheduling jobs on that node"; - type = lib.types.float; - default = 75.0; - }; + cpuPsiThreshold = lib.mkOption { + description = "Maximum CPU PSI in the last 10s before we stop scheduling jobs on that node"; + type = lib.types.float; + default = 75.0; + }; - memPsiThreshold = lib.mkOption { - description = "Maximum Memory PSI in the last 10s before we stop scheduling jobs on that node"; - type = lib.types.float; - default = 80.0; - }; + memPsiThreshold = lib.mkOption { + description = "Maximum Memory PSI in the last 10s before we stop scheduling jobs on that node"; + type = lib.types.float; + default = 80.0; + }; - ioPsiThreshold = lib.mkOption { - description = "Maximum IO PSI in the last 10s before we stop scheduling jobs on that node. If null then this pressure check is disabled."; - type = lib.types.nullOr lib.types.float; - default = null; - }; + ioPsiThreshold = lib.mkOption { + description = "Maximum IO PSI in the last 10s before we stop scheduling jobs on that node. If null then this pressure check is disabled."; + type = lib.types.nullOr lib.types.float; + default = null; + }; - systems = lib.mkOption { - description = "List of supported systems. If none are passed, system and extra-platforms are read from nix."; - type = lib.types.listOf lib.types.singleLineStr; - default = [ ]; - }; + systems = lib.mkOption { + description = "List of supported systems. If null, system and extra-platforms are read from nix; an empty list means none."; + type = lib.types.nullOr (lib.types.listOf lib.types.singleLineStr); + default = null; + }; - supportedFeatures = lib.mkOption { - description = "Pass supported features to the builder. If none are passed, system features will be used."; - type = lib.types.listOf lib.types.singleLineStr; - default = [ ]; - }; + supportedFeatures = lib.mkOption { + description = "Supported features for the builder. If null, system features are read from nix; an empty list means none."; + type = lib.types.nullOr (lib.types.listOf lib.types.singleLineStr); + default = null; + }; - mandatoryFeatures = lib.mkOption { - description = "Pass mandatory features to the builder."; - type = lib.types.listOf lib.types.singleLineStr; - default = [ ]; - }; + mandatoryFeatures = lib.mkOption { + description = "Mandatory features for the builder."; + type = lib.types.listOf lib.types.singleLineStr; + default = [ ]; + }; - useSubstitutes = lib.mkOption { - description = "Use substitution for paths"; - type = lib.types.bool; - default = true; + useSubstitutes = lib.mkOption { + description = "Use substitution for paths"; + type = lib.types.bool; + default = true; + }; + }; + }; + default = { }; }; authorizationFile = lib.mkOption { @@ -154,41 +164,8 @@ in "${cfg.package}/bin/hydra-builder" "--gateway-endpoint" cfg.queueRunnerAddr - "--ping-interval" - cfg.pingInterval - "--speed-factor" - cfg.speedFactor - "--max-jobs" - cfg.maxJobs - "--build-dir-avail-threshold" - cfg.buildDirAvailThreshold - "--store-avail-threshold" - cfg.storeAvailThreshold - "--load1-threshold" - cfg.load1Threshold - "--cpu-psi-threshold" - cfg.cpuPsiThreshold - "--mem-psi-threshold" - cfg.memPsiThreshold - ] - ++ lib.optionals (cfg.ioPsiThreshold != null) [ - "--io-psi-threshold" - cfg.ioPsiThreshold - ] - ++ (builtins.concatMap (v: [ - "--systems" - v - ]) cfg.systems) - ++ (builtins.concatMap (v: [ - "--supported-features" - v - ]) cfg.supportedFeatures) - ++ (builtins.concatMap (v: [ - "--mandatory-features" - v - ]) cfg.mandatoryFeatures) - ++ lib.optionals (cfg.useSubstitutes != null) [ - "--use-substitutes" + "--config-path" + "/etc/hydra/builder.toml" ] ++ lib.optionals (cfg.authorizationFile != null) [ "--authorization-file" @@ -227,6 +204,11 @@ in WorkingDirectory = user.home; }; }; + + environment.etc."hydra/builder.toml".source = format.generate "builder.toml" ( + lib.filterAttrsRecursive (_: v: v != null) cfg.settings + ); + users = { users.hydra-queue-builder = { uid = lib.mkDefault 535; diff --git a/nixos-modules/builder-module.nix b/nixos-modules/builder-module.nix index 9acd1215b..5ca1c06a2 100644 --- a/nixos-modules/builder-module.nix +++ b/nixos-modules/builder-module.nix @@ -7,6 +7,8 @@ let cfg = config.services.hydra-queue-builder-dev; user = "hydra-queue-builder"; + + format = pkgs.formats.toml { }; in { options = { @@ -18,85 +20,93 @@ in type = lib.types.singleLineStr; }; - pingInterval = lib.mkOption { - description = "Interval in which pings are send to the runner"; - type = lib.types.ints.positive; - default = 10; - }; + settings = lib.mkOption { + description = "Builder settings written to the TOML config file"; + type = lib.types.submodule { + options = { + pingInterval = lib.mkOption { + description = "Interval in which pings are send to the runner"; + type = lib.types.ints.positive; + default = 10; + }; - speedFactor = lib.mkOption { - description = "Additional Speed factor for this machine"; - type = lib.types.oneOf [ - lib.types.ints.positive - lib.types.float - ]; - default = 1; - }; + speedFactor = lib.mkOption { + description = "Additional Speed factor for this machine"; + type = lib.types.oneOf [ + lib.types.ints.positive + lib.types.float + ]; + default = 1; + }; - maxJobs = lib.mkOption { - description = "Maximum allowed of jobs. This only is used if the queue runner uses this metrics for determining free machines."; - type = lib.types.ints.positive; - default = 4; - }; + maxJobs = lib.mkOption { + description = "Maximum allowed of jobs. This only is used if the queue runner uses this metrics for determining free machines."; + type = lib.types.ints.positive; + default = 4; + }; - buildDirAvailThreshold = lib.mkOption { - description = "Threshold in percent for nix build dir before jobs are no longer scheduled on the machine"; - type = lib.types.float; - default = 10.0; - }; + buildDirAvailThreshold = lib.mkOption { + description = "Threshold in percent for nix build dir before jobs are no longer scheduled on the machine"; + type = lib.types.float; + default = 10.0; + }; - storeAvailThreshold = lib.mkOption { - description = "Threshold in percent for /nix/store before jobs are no longer scheduled on the machine"; - type = lib.types.float; - default = 10.0; - }; + storeAvailThreshold = lib.mkOption { + description = "Threshold in percent for /nix/store before jobs are no longer scheduled on the machine"; + type = lib.types.float; + default = 10.0; + }; - load1Threshold = lib.mkOption { - description = "Maximum Load1 threshold before we stop scheduling jobs on that node. Only used if PSI is not available."; - type = lib.types.float; - default = 8.0; - }; + load1Threshold = lib.mkOption { + description = "Maximum Load1 threshold before we stop scheduling jobs on that node. Only used if PSI is not available."; + type = lib.types.float; + default = 8.0; + }; - cpuPsiThreshold = lib.mkOption { - description = "Maximum CPU PSI in the last 10s before we stop scheduling jobs on that node"; - type = lib.types.float; - default = 75.0; - }; + cpuPsiThreshold = lib.mkOption { + description = "Maximum CPU PSI in the last 10s before we stop scheduling jobs on that node"; + type = lib.types.float; + default = 75.0; + }; - memPsiThreshold = lib.mkOption { - description = "Maximum Memory PSI in the last 10s before we stop scheduling jobs on that node"; - type = lib.types.float; - default = 80.0; - }; + memPsiThreshold = lib.mkOption { + description = "Maximum Memory PSI in the last 10s before we stop scheduling jobs on that node"; + type = lib.types.float; + default = 80.0; + }; - ioPsiThreshold = lib.mkOption { - description = "Maximum IO PSI in the last 10s before we stop scheduling jobs on that node. If null then this pressure check is disabled."; - type = lib.types.nullOr lib.types.float; - default = null; - }; + ioPsiThreshold = lib.mkOption { + description = "Maximum IO PSI in the last 10s before we stop scheduling jobs on that node. If null then this pressure check is disabled."; + type = lib.types.nullOr lib.types.float; + default = null; + }; - systems = lib.mkOption { - description = "List of supported systems. If none are passed, system and extra-platforms are read from nix."; - type = lib.types.listOf lib.types.singleLineStr; - default = [ ]; - }; + systems = lib.mkOption { + description = "List of supported systems. If null, system and extra-platforms are read from nix; an empty list means none."; + type = lib.types.nullOr (lib.types.listOf lib.types.singleLineStr); + default = null; + }; - supportedFeatures = lib.mkOption { - description = "Pass supported features to the builder. If none are passed, system features will be used."; - type = lib.types.listOf lib.types.singleLineStr; - default = [ ]; - }; + supportedFeatures = lib.mkOption { + description = "Supported features for the builder. If null, system features are read from nix; an empty list means none."; + type = lib.types.nullOr (lib.types.listOf lib.types.singleLineStr); + default = null; + }; - mandatoryFeatures = lib.mkOption { - description = "Pass mandatory features to the builder."; - type = lib.types.listOf lib.types.singleLineStr; - default = [ ]; - }; + mandatoryFeatures = lib.mkOption { + description = "Mandatory features for the builder."; + type = lib.types.listOf lib.types.singleLineStr; + default = [ ]; + }; - useSubstitutes = lib.mkOption { - description = "Use substitution for paths"; - type = lib.types.bool; - default = true; + useSubstitutes = lib.mkOption { + description = "Use substitution for paths"; + type = lib.types.bool; + default = true; + }; + }; + }; + default = { }; }; authorizationFile = lib.mkOption { @@ -146,6 +156,8 @@ in requires = [ "nix-daemon.socket" ]; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; + # The builder has no hot-reload; restart it when the config changes. + restartTriggers = [ config.environment.etc."hydra/builder.toml".source ]; environment = { NIX_REMOTE = "daemon"; @@ -170,41 +182,8 @@ in "${cfg.package}/bin/hydra-builder" "--gateway-endpoint" cfg.queueRunnerAddr - "--ping-interval" - cfg.pingInterval - "--speed-factor" - cfg.speedFactor - "--max-jobs" - cfg.maxJobs - "--build-dir-avail-threshold" - cfg.buildDirAvailThreshold - "--store-avail-threshold" - cfg.storeAvailThreshold - "--load1-threshold" - cfg.load1Threshold - "--cpu-psi-threshold" - cfg.cpuPsiThreshold - "--mem-psi-threshold" - cfg.memPsiThreshold - ] - ++ lib.optionals (cfg.ioPsiThreshold != null) [ - "--io-psi-threshold" - cfg.ioPsiThreshold - ] - ++ (builtins.concatMap (v: [ - "--systems" - v - ]) cfg.systems) - ++ (builtins.concatMap (v: [ - "--supported-features" - v - ]) cfg.supportedFeatures) - ++ (builtins.concatMap (v: [ - "--mandatory-features" - v - ]) cfg.mandatoryFeatures) - ++ lib.optionals (cfg.useSubstitutes != null) [ - "--use-substitutes" + "--config-path" + "/etc/hydra/builder.toml" ] ++ lib.optionals (cfg.authorizationFile != null) [ "--authorization-file" @@ -269,6 +248,10 @@ in }; }; + environment.etc."hydra/builder.toml".source = format.generate "builder.toml" ( + lib.filterAttrsRecursive (_: v: v != null) cfg.settings + ); + systemd.tmpfiles.rules = [ "d /nix/var/nix/gcroots/per-user/${user} 0755 ${user} hydra -" ]; diff --git a/subprojects/hydra-builder/Cargo.toml b/subprojects/hydra-builder/Cargo.toml index 92ef5c319..27ee761b0 100644 --- a/subprojects/hydra-builder/Cargo.toml +++ b/subprojects/hydra-builder/Cargo.toml @@ -35,6 +35,7 @@ url.workspace = true serde.workspace = true serde_json.workspace = true +toml.workspace = true harmonia-protocol.workspace = true harmonia-store-derivation.workspace = true diff --git a/subprojects/hydra-builder/src/config.rs b/subprojects/hydra-builder/src/config.rs index 3ffed3a59..658e2aa59 100644 --- a/subprojects/hydra-builder/src/config.rs +++ b/subprojects/hydra-builder/src/config.rs @@ -9,6 +9,13 @@ pub enum ConfigError { #[error("Reading configuration file")] Reading(#[source] std::io::Error), + #[error("{context}")] + Toml { + context: String, + #[source] + source: toml::de::Error, + }, + #[error( "mTLS configured improperly, please pass all options: \ server_root_ca_cert_path, client_cert_path, client_key_path and domain_name" @@ -16,6 +23,11 @@ pub enum ConfigError { MtlsIncomplete, } +/// Hydra builder: connects to the queue runner and runs build jobs. +// +// `config_path` is meta-config: it only locates the TOML file holding the +// operational settings (`AppConfig`); it is not itself part of either +// config. The connection/security options live in the flattened `Cli`. #[derive(Parser, Debug)] #[clap( author, @@ -23,96 +35,164 @@ pub enum ConfigError { about, long_about = None, )] +pub struct Args { + /// Path to the TOML config file holding the reloadable builder settings + #[clap(short, long, default_value = "config.toml")] + pub config_path: String, + + #[clap(flatten)] + pub cli: Cli, +} + +/// Connection and security options passed on the command line. +#[derive(clap::Args, Debug)] pub struct Cli { /// Gateway endpoint #[clap(short, long, default_value = "http://[::1]:50051")] pub gateway_endpoint: String, + /// Path to Server root ca cert + #[clap(long)] + pub server_root_ca_cert_path: Option, + + /// Path to Client cert + #[clap(long)] + pub client_cert_path: Option, + + /// Path to Client key + #[clap(long)] + pub client_key_path: Option, + + /// Domain name for mtls + #[clap(long)] + pub domain_name: Option, + + /// File to Authorization token, can be use as an alternative to mTLS + #[clap(long)] + pub authorization_file: Option, +} + +const fn default_ping_interval() -> u64 { + 10 +} +const fn default_speed_factor() -> f32 { + 1.0 +} +const fn default_max_jobs() -> u32 { + 4 +} +const fn default_build_dir_avail_threshold() -> f32 { + 10.0 +} +const fn default_store_avail_threshold() -> f32 { + 10.0 +} +const fn default_load1_threshold() -> f32 { + 8.0 +} +const fn default_cpu_psi_threshold() -> f32 { + 75.0 +} +const fn default_mem_psi_threshold() -> f32 { + 80.0 +} + +/// Reloadable builder settings read from the TOML config file. Connection +/// and security options (gateway endpoint, mTLS, auth token) stay on the +/// CLI; everything operational lives here. Empty `systems` / +/// `supported_features` fall back to values read from `nix show-config`. +#[derive(Debug, serde::Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "camelCase")] +pub struct AppConfig { /// Ping interval in seconds - #[clap(short, long, default_value_t = 10)] + #[serde(default = "default_ping_interval")] pub ping_interval: u64, /// Speed factor that is used when joining the queue-runner - #[clap(short, long, default_value_t = 1.0)] + #[serde(default = "default_speed_factor")] pub speed_factor: f32, /// Maximum number of allowed jobs - #[clap(long, default_value_t = 4)] + #[serde(default = "default_max_jobs")] pub max_jobs: u32, /// build dir available storage percentage Threshold - #[clap(long, default_value_t = 10.)] + #[serde(default = "default_build_dir_avail_threshold")] pub build_dir_avail_threshold: f32, /// prefix/store available storage percentage Threshold - #[clap(long, default_value_t = 10.)] + #[serde(default = "default_store_avail_threshold")] pub store_avail_threshold: f32, /// Load1 Threshold - #[clap(long, default_value_t = 8.)] + #[serde(default = "default_load1_threshold")] pub load1_threshold: f32, /// CPU Pressure Threshold - #[clap(long, default_value_t = 75.)] + #[serde(default = "default_cpu_psi_threshold")] pub cpu_psi_threshold: f32, /// Memory Pressure Threshold - #[clap(long, default_value_t = 80.)] + #[serde(default = "default_mem_psi_threshold")] pub mem_psi_threshold: f32, /// IO Pressure Threshold, null disables this pressure check - #[clap(long)] + #[serde(default)] pub io_psi_threshold: Option, - /// Path to Server root ca cert - #[clap(long)] - pub server_root_ca_cert_path: Option, - - /// Path to Client cert - #[clap(long)] - pub client_cert_path: Option, - - /// Path to Client key - #[clap(long)] - pub client_key_path: Option, - - /// Domain name for mtls - #[clap(long)] - pub domain_name: Option, - - /// List of supported systems, defaults to systems from nix and extra-platforms - #[clap(long, default_value = None)] + /// List of supported systems. `None` (absent) falls back to `system` + /// and `extra-platforms` from nix; an explicit empty list means none. + #[serde(default)] pub systems: Option>, - /// List of supported features, defaults to configured system features - #[clap(long, default_value = None)] + /// List of supported features. `None` (absent) falls back to the + /// configured system features from nix; an explicit empty list means none. + #[serde(default)] pub supported_features: Option>, /// List of mandatory features - #[clap(long, default_value = None)] - pub mandatory_features: Option>, + #[serde(default)] + pub mandatory_features: Vec, /// Use substitution over pulling inputs via queue runner - #[clap(long, default_value_t = false)] + #[serde(default)] pub use_substitutes: bool, +} - /// File to Authorization token, can be use as an alternative to mTLS - #[clap(long)] - pub authorization_file: Option, +/// Load the builder settings from `filepath`. A missing file yields the +/// default settings, mirroring the queue runner's behaviour. +#[tracing::instrument(err)] +pub fn load_config(filepath: &str) -> Result { + let content = match fs_err::read_to_string(filepath) { + Ok(content) => content, + Err(_) => { + tracing::warn!("no config file found at {filepath}! Using default config"); + String::new() + } + }; + let config: AppConfig = toml::from_str(&content).map_err(|source| ConfigError::Toml { + context: format!("loading config from '{filepath}'"), + source, + })?; + tracing::info!("Loaded config: {config:?}"); + Ok(config) } -impl Default for Cli { +impl Default for Args { fn default() -> Self { Self::new() } } -impl Cli { +impl Args { #[must_use] pub fn new() -> Self { Self::parse() } +} +impl Cli { #[must_use] pub const fn mtls_enabled(&self) -> bool { self.server_root_ca_cert_path.is_some() diff --git a/subprojects/hydra-builder/src/main.rs b/subprojects/hydra-builder/src/main.rs index 3fddca69b..2a6be28ad 100644 --- a/subprojects/hydra-builder/src/main.rs +++ b/subprojects/hydra-builder/src/main.rs @@ -47,9 +47,10 @@ async fn stop_application( async fn main() -> color_eyre::Result<()> { let _tracing_guard = hydra_tracing::init()?; - let cli = config::Cli::new(); + let args = config::Args::new(); + let app_config = config::load_config(&args.config_path)?; - let state = state::State::new(&cli).await?; + let state = state::State::new(&args.cli, app_config).await?; let task = tokio::spawn({ let state = state.clone(); async move { grpc::start_bidirectional_stream(state.clone()).await } diff --git a/subprojects/hydra-builder/src/state.rs b/subprojects/hydra-builder/src/state.rs index 30ef3e252..6a5c30968 100644 --- a/subprojects/hydra-builder/src/state.rs +++ b/subprojects/hydra-builder/src/state.rs @@ -140,7 +140,10 @@ impl Drop for Gcroot { impl State { #[tracing::instrument(err)] - pub async fn new(cli: &super::config::Cli) -> Result, BuilderError> { + pub async fn new( + cli: &super::config::Cli, + app_config: super::config::AppConfig, + ) -> Result, BuilderError> { let nix_config = crate::nix_config::NixConfig::load().map_err(BuilderError::LoadNixConfig)?; let nix_remote = @@ -170,32 +173,29 @@ impl State { .map_err(BuilderError::Hostname)?, active_builds: parking_lot::RwLock::new(HashMap::with_capacity(10)), config: Config { - ping_interval: cli.ping_interval, - speed_factor: cli.speed_factor, - max_jobs: cli.max_jobs, - build_dir_avail_threshold: cli.build_dir_avail_threshold, - store_avail_threshold: cli.store_avail_threshold, - load1_threshold: cli.load1_threshold, - cpu_psi_threshold: cli.cpu_psi_threshold, - mem_psi_threshold: cli.mem_psi_threshold, - io_psi_threshold: cli.io_psi_threshold, + ping_interval: app_config.ping_interval, + speed_factor: app_config.speed_factor, + max_jobs: app_config.max_jobs, + build_dir_avail_threshold: app_config.build_dir_avail_threshold, + store_avail_threshold: app_config.store_avail_threshold, + load1_threshold: app_config.load1_threshold, + cpu_psi_threshold: app_config.cpu_psi_threshold, + mem_psi_threshold: app_config.mem_psi_threshold, + io_psi_threshold: app_config.io_psi_threshold, gcroots, - systems: cli.systems.as_ref().map_or_else( - || { - let mut out = Vec::with_capacity(8); - out.push(nix_config.system()); - out.extend(nix_config.extra_platforms()); - out - }, - Clone::clone, - ), - supported_features: cli + systems: app_config.systems.clone().unwrap_or_else(|| { + let mut out = Vec::with_capacity(8); + out.push(nix_config.system()); + out.extend(nix_config.extra_platforms()); + out + }), + supported_features: app_config .supported_features - .as_ref() - .map_or_else(|| nix_config.system_features(), Clone::clone), - mandatory_features: cli.mandatory_features.clone().unwrap_or_default(), + .clone() + .unwrap_or_else(|| nix_config.system_features()), + mandatory_features: app_config.mandatory_features.clone(), cgroups: nix_config.use_cgroups(), - use_substitutes: cli.use_substitutes, + use_substitutes: app_config.use_substitutes, substituters: nix_config.substituters(), nix_version: nix_config.nix_version(), build_dir: nix_config.build_dir(),