From 9bd5e7bb2540664fe1c728c85f80375fe0cbf9fa Mon Sep 17 00:00:00 2001 From: David Arnold Date: Sun, 3 Apr 2022 19:07:14 -0500 Subject: [PATCH] imp: soc of eval, modules & options - the reason for this change is to have more transparent separation of concern between effectuations of the module system and pre-module system effectuations - with improved flakes support down the line, pre-module system effectuations will get more complex - this also allows to patch the aspects of the evaluation individually while tracking other components from upstream. eg. path options & eval but not modules --- flake.nix | 3 + src/nix/hive/eval.nix | 417 ++------------------------------------- src/nix/hive/mod.rs | 22 ++- src/nix/hive/modules.nix | 98 +++++++++ src/nix/hive/options.nix | 287 +++++++++++++++++++++++++++ 5 files changed, 429 insertions(+), 398 deletions(-) create mode 100644 src/nix/hive/modules.nix create mode 100644 src/nix/hive/options.nix diff --git a/flake.nix b/flake.nix index 23ac8a9..7c2d5a2 100644 --- a/flake.nix +++ b/flake.nix @@ -15,8 +15,11 @@ outputs = { self, nixpkgs, utils, ... }: let supportedSystems = [ "x86_64-linux" "i686-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; + colmenaOptions = import ./src/nix/hive/options.nix; + colmenaModules = import ./src/nix/hive/modules.nix; evalNix = import ./src/nix/hive/eval.nix { hermetic = true; + inherit colmenaOptions colmenaModules; }; in utils.lib.eachSystem supportedSystems (system: let pkgs = import nixpkgs { diff --git a/src/nix/hive/eval.nix b/src/nix/hive/eval.nix index fcd1e45..1b38a7c 100644 --- a/src/nix/hive/eval.nix +++ b/src/nix/hive/eval.nix @@ -1,9 +1,12 @@ { rawHive ? null # Colmena Hive attrset , flakeUri ? null # Nix Flake URI with `outputs.colmena` , hermetic ? flakeUri != null # Whether we are allowed to use +, colmenaOptions +, colmenaModules }: with builtins; let + defaultHive = { # Will be set in defaultHiveMeta meta = {}; @@ -14,292 +17,6 @@ let defaults = {}; }; - # Hive-wide options - metaOptions = { lib, ... }: let - inherit (lib) types; - in { - options = { - name = lib.mkOption { - description = '' - The name of the configuration. - ''; - type = types.str; - default = "hive"; - }; - description = lib.mkOption { - description = '' - A short description for the configuration. - ''; - type = types.str; - default = "A Colmena Hive"; - }; - nixpkgs = lib.mkOption { - description = '' - The pinned Nixpkgs package set. Accepts one of the following: - - - A path to a Nixpkgs checkout - - The Nixpkgs lambda (e.g., import ) - - An initialized Nixpkgs attribute set - - This option must be specified when using Flakes. - ''; - type = types.unspecified; - default = if !hermetic then else null; - }; - nodeNixpkgs = lib.mkOption { - description = '' - Node-specific Nixpkgs pins. - ''; - type = types.attrsOf types.unspecified; - default = {}; - }; - machinesFile = lib.mkOption { - description = '' - Use the machines listed in this file when building this hive configuration. - - If your Colmena host has nix configured to allow for remote builds - (for nix-daemon, your user being included in trusted-users) - you can set a machines file that will be passed to the underlying - nix-store command during derivation realization as a builders option. - For example, if you support multiple orginizations each with their own - build machine(s) you can ensure that builds only take place on your - local machine and/or the machines specified in this file. - - See https://nixos.org/manual/nix/stable/#chap-distributed-builds - for the machine specification format. - - This option is ignored when builds are initiated on the remote nodes - themselves via `deployment.buildOnTarget` or `--build-on-target`. To - still use the Nix distributed build functionality, configure the - builders on the target nodes with `nix.buildMachines`. - ''; - default = null; - apply = value: if value == null then null else toString value; - type = types.nullOr types.path; - }; - specialArgs = lib.mkOption { - description = '' - A set of special arguments to be passed to NixOS modules. - - This will be merged into the `specialArgs` used to evaluate - the NixOS configurations. - ''; - default = {}; - type = types.attrsOf types.unspecified; - }; - }; - }; - - # Colmena-specific options - # - # Largely compatible with NixOps/Morph. - deploymentOptions = { name, lib, ... }: let - inherit (lib) types; - in { - options = { - deployment = { - targetHost = lib.mkOption { - description = '' - The target SSH node for deployment. - - By default, the node's attribute name will be used. - If set to null, only local deployment will be supported. - ''; - type = types.nullOr types.str; - default = name; - }; - targetPort = lib.mkOption { - description = '' - The target SSH port for deployment. - - By default, the port is the standard port (22) or taken - from your ssh_config. - ''; - type = types.nullOr types.ints.unsigned; - default = null; - }; - targetUser = lib.mkOption { - description = '' - The user to use to log into the remote node. If null, login as the - current user. - ''; - type = types.nullOr types.str; - default = "root"; - }; - allowLocalDeployment = lib.mkOption { - description = '' - Allow the configuration to be applied locally on the host running - Colmena. - - For local deployment to work, all of the following must be true: - - The node must be running NixOS. - - The node must have deployment.allowLocalDeployment set to true. - - The node's networking.hostName must match the hostname. - - To apply the configurations locally, run `colmena apply-local`. - You can also set deployment.targetHost to null if the nost is not - accessible over SSH (only local deployment will be possible). - ''; - type = types.bool; - default = false; - }; - buildOnTarget = lib.mkOption { - description = '' - Whether to build the system profiles on the target node itself. - - When enabled, Colmena will copy the derivation to the target - node and initiate the build there. This avoids copying back the - build results involved with the native distributed build - feature. Furthermore, the `build` goal will be equivalent to - the `push` goal. Since builds happen on the target node, the - results are automatically "pushed" and won't exist in the local - Nix store. - - You can temporarily override per-node settings by passing - `--build-on-target` (enable for all nodes) or - `--no-build-on-target` (disable for all nodes) on the command - line. - ''; - type = types.bool; - default = false; - }; - tags = lib.mkOption { - description = '' - A list of tags for the node. - - Can be used to select a group of nodes for deployment. - ''; - type = types.listOf types.str; - default = []; - }; - keys = lib.mkOption { - description = '' - A set of secrets to be deployed to the node. - - Secrets are transferred to the node out-of-band and - never ends up in the Nix store. - ''; - type = types.attrsOf (types.submodule keyType); - default = {}; - }; - replaceUnknownProfiles = lib.mkOption { - description = '' - Allow a configuration to be applied to a host running a profile we - have no knowledge of. By setting this option to false, you reduce - the likelyhood of rolling back changes made via another Colmena user. - - Unknown profiles are usually the result of either: - - The node had a profile applied, locally or by another Colmena. - - The host running Colmena garbage-collecting the profile. - - To force profile replacement on all targeted nodes during apply, - use the flag `--force-replace-unknown-profiles`. - ''; - type = types.bool; - default = true; - }; - privilegeEscalationCommand = lib.mkOption { - description = '' - Command to use to elevate privileges when activating the new profiles on SSH hosts. - - This is used on SSH hosts when `deployment.targetUser` is not `root`. - The user must be allowed to use the command non-interactively. - ''; - type = types.listOf types.str; - default = [ "sudo" "-H" "--" ]; - }; - }; - }; - }; - - keyType = { lib, name, config, ... }: let - inherit (lib) types; - in { - options = { - name = lib.mkOption { - description = '' - File name of the key. - ''; - default = name; - type = types.str; - }; - text = lib.mkOption { - description = '' - Content of the key. - One of `text`, `keyCommand` and `keyFile` must be set. - ''; - default = null; - type = types.nullOr types.str; - }; - keyFile = lib.mkOption { - description = '' - Path of the local file to read the key from. - One of `text`, `keyCommand` and `keyFile` must be set. - ''; - default = null; - apply = value: if value == null then null else toString value; - type = types.nullOr types.path; - }; - keyCommand = lib.mkOption { - description = '' - Command to run to generate the key. - One of `text`, `keyCommand` and `keyFile` must be set. - ''; - default = null; - type = let - nonEmptyList = types.addCheck (types.listOf types.str) (l: length l > 0); - in types.nullOr nonEmptyList; - }; - destDir = lib.mkOption { - description = '' - Destination directory on the host. - ''; - default = "/run/keys"; - type = types.path; - }; - path = lib.mkOption { - description = '' - Full path to the destination. - ''; - default = "${config.destDir}/${config.name}"; - type = types.path; - internal = true; - }; - user = lib.mkOption { - description = '' - The group that will own the file. - ''; - default = "root"; - type = types.str; - }; - group = lib.mkOption { - description = '' - The group that will own the file. - ''; - default = "root"; - type = types.str; - }; - permissions = lib.mkOption { - description = '' - Permissions to set for the file. - ''; - default = "0600"; - type = types.str; - }; - uploadAt = lib.mkOption { - description = '' - When to upload the keys. - - - pre-activation (default): Upload the keys before activating the new system profile. - - post-activation: Upload the keys after successfully activating the new system profile. - - For `colmena upload-keys`, all keys are uploaded at the same time regardless of the configuration here. - ''; - default = "pre-activation"; - type = types.enum [ "pre-activation" "post-activation" ]; - }; - }; - }; uncheckedHive = let flakeToHive = flakeUri: let @@ -326,12 +43,15 @@ let # The final hive will always have the meta key instead of network. hive = let userMeta = (lib.modules.evalModules { - modules = [ metaOptions uncheckedUserMeta ]; + modules = [ colmenaOptions.metaOptions uncheckedUserMeta ]; }).config; mergedHive = removeAttrs (defaultHive // uncheckedHive) [ "meta" "network" ]; meta = { - meta = userMeta; + meta = + if !hermetic && userMeta.nixpkgs == null + then userMeta // { nixpkgs = ; } + else userMeta; }; in mergedHive // meta; @@ -377,7 +97,7 @@ let - A Nixpkgs attribute set ''; - pkgs = let + nixpkgs = let # Can't rely on the module system yet nixpkgsConf = if uncheckedUserMeta ? nixpkgs then uncheckedUserMeta.nixpkgs @@ -385,25 +105,15 @@ let else ; in mkNixpkgs "meta.nixpkgs" nixpkgsConf; - lib = pkgs.lib; + lib = nixpkgs.lib; reservedNames = [ "defaults" "network" "meta" ]; evalNode = name: configs: let npkgs = if hasAttr name hive.meta.nodeNixpkgs then mkNixpkgs "meta.nodeNixpkgs.${name}" hive.meta.nodeNixpkgs.${name} - else pkgs; + else nixpkgs; evalConfig = import (npkgs.path + "/nixos/lib/eval-config.nix"); - assertionModule = { config, ... }: { - assertions = lib.mapAttrsToList (key: opts: let - nonNulls = l: filter (x: x != null) l; - in { - assertion = length (nonNulls [opts.text opts.keyCommand opts.keyFile]) == 1; - message = - let prefix = "${name}.deployment.keys.${key}"; - in "Exactly one of `${prefix}.text`, `${prefix}.keyCommand` and `${prefix}.keyFile` must be set."; - }) config.deployment.keys; - }; # Here we need to merge the configurations in meta.nixpkgs # and in machine config. @@ -422,101 +132,16 @@ let lib.optional (length remainingKeys != 0) "The following Nixpkgs configuration keys set in meta.nixpkgs will be ignored: ${toString remainingKeys}"; }; - - # Change the ownership of all keys uploaded pre-activation - # - # This is built as part of the system profile. - # We must be careful not to access `text` / `keyCommand` / `keyFile` here - keyChownModule = { lib, config, ... }: let - preActivationKeys = lib.filterAttrs (name: key: key.uploadAt == "pre-activation") config.deployment.keys; - scriptDeps = if config.system.activationScripts ? groups then [ "groups" ] else [ "users" ]; - - commands = lib.mapAttrsToList (name: key: let - keyPath = "${key.destDir}/${name}"; - in '' - if [ -f "${keyPath}" ]; then - if ! chown ${key.user}:${key.group} "${keyPath}"; then - # Error should be visible in stderr - failed=1 - fi - else - >&2 echo "Key ${keyPath} does not exist. Skipping chown." - fi - '') preActivationKeys; - - script = lib.stringAfter scriptDeps '' - # This script is injected by Colmena to change the ownerships - # of keys (`deployment.keys`) deployed before system activation. - - >&2 echo "setting up key ownerships..." - - # We set the ownership of as many keys as possible before failing - failed= - - ${concatStringsSep "\n" commands} - - if [ -n "$failed" ]; then - >&2 echo "Failed to set the ownership of some keys." - - # The activation script has a trap to handle failed - # commands and print out various debug information. - # Let's trigger that instead of `exit 1`. - false - fi - ''; - in { - system.activationScripts.colmena-chown-keys = lib.mkIf (length commands != 0) script; - }; - - # Create "${name}-key" services for NixOps compatibility - # - # This is built as part of the system profile. - # We must be careful not to access `text` / `keyCommand` / `keyFile` here - # - # Sadly, path units don't automatically deactivate the bound units when - # the key files are deleted, so we use inotifywait in the services' scripts. - # - # - keyServiceModule = { pkgs, lib, config, ... }: { - systemd.paths = lib.mapAttrs' (name: val: { - name = "${name}-key"; - value = { - wantedBy = [ "paths.target" ]; - pathConfig = { - PathExists = val.path; - }; - }; - }) config.deployment.keys; - - systemd.services = lib.mapAttrs' (name: val: { - name = "${name}-key"; - value = { - bindsTo = [ "${name}-key.path" ]; - serviceConfig = { - Restart = "on-failure"; - }; - path = [ pkgs.inotifyTools ]; - script = '' - if [[ ! -e "${val.path}" ]]; then - >&2 echo "${val.path} does not exist" - exit 0 - fi - - inotifywait -qq -e delete_self "${val.path}" - >&2 echo "${val.path} disappeared" - ''; - }; - }) config.deployment.keys; - }; in evalConfig { inherit (npkgs) system; - modules = [ - assertionModule + modules = let + in [ nixpkgsModule - keyChownModule - keyServiceModule - deploymentOptions + colmenaModules.assertionModule + colmenaModules.keyChownModule + colmenaModules.keyServiceModule + colmenaOptions.deploymentOptions hive.defaults ] ++ configs; specialArgs = hive.meta.specialArgs // { @@ -563,7 +188,7 @@ let evalSelectedDrvPaths = names: lib.mapAttrs (k: v: v.drvPath) (evalSelected names); introspect = function: function { - inherit pkgs lib; + inherit nixpkgs lib; nodes = uncheckedNodes; }; in { @@ -574,12 +199,12 @@ in { meta = hive.meta; - nixosModules = { inherit deploymentOptions; }; + nixosModules = { inherit (colmenaOptions) deploymentOptions; }; docs = { deploymentOptions = pkgs: let eval = pkgs.lib.evalModules { - modules = [ deploymentOptions ]; + modules = [ colmenaOptions.deploymentOptions ]; specialArgs = { name = "nixos"; nodes = {}; @@ -589,7 +214,7 @@ in { metaOptions = pkgs: let eval = pkgs.lib.evalModules { - modules = [ metaOptions ]; + modules = [ colmenaOptions.metaOptions ]; }; in eval.options; }; diff --git a/src/nix/hive/mod.rs b/src/nix/hive/mod.rs index 145a477..43b3f84 100644 --- a/src/nix/hive/mod.rs +++ b/src/nix/hive/mod.rs @@ -28,6 +28,8 @@ use crate::job::JobHandle; mod tests; const HIVE_EVAL: &[u8] = include_bytes!("eval.nix"); +const HIVE_OPTIONS: &[u8] = include_bytes!("options.nix"); +const HIVE_MODULES: &[u8] = include_bytes!("modules.nix"); #[derive(Debug)] pub enum HivePath { @@ -54,6 +56,12 @@ pub struct Hive { /// Path to temporary file containing eval.nix. eval_nix: TempPath, + /// Path to temporary file containing options.nix. + options_nix: TempPath, + + /// Path to temporary file containing modules.nix. + modules_nix: TempPath, + /// Whether to pass --show-trace in Nix commands. show_trace: bool, @@ -111,7 +119,11 @@ impl HivePath { impl Hive { pub fn new(path: HivePath) -> ColmenaResult { let mut eval_nix = NamedTempFile::new()?; + let mut options_nix = NamedTempFile::new()?; + let mut modules_nix = NamedTempFile::new()?; eval_nix.write_all(HIVE_EVAL).unwrap(); + options_nix.write_all(HIVE_OPTIONS).unwrap(); + modules_nix.write_all(HIVE_MODULES).unwrap(); let context_dir = path.context_dir(); @@ -119,6 +131,8 @@ impl Hive { path, context_dir, eval_nix: eval_nix.into_temp_path(), + options_nix: options_nix.into_temp_path(), + modules_nix: modules_nix.into_temp_path(), show_trace: false, machines_file: RwLock::new(None), }) @@ -342,16 +356,20 @@ impl Hive { match self.path() { HivePath::Legacy(path) => { format!( - "with builtins; let eval = import {}; hive = eval {{ rawHive = import {}; }}; in ", + "with builtins; let eval = import {}; hive = eval {{ rawHive = import {}; colmenaOptions = import {}; colmenaModules = import {}; }}; in ", self.eval_nix.to_str().unwrap(), path.to_str().unwrap(), + self.options_nix.to_str().unwrap(), + self.modules_nix.to_str().unwrap(), ) } HivePath::Flake(flake) => { format!( - "with builtins; let eval = import {}; hive = eval {{ flakeUri = \"{}\"; }}; in ", + "with builtins; let eval = import {}; hive = eval {{ flakeUri = \"{}\"; colmenaOptions = import {}; colmenaModules = import {}; }}; in ", self.eval_nix.to_str().unwrap(), flake.uri(), + self.options_nix.to_str().unwrap(), + self.modules_nix.to_str().unwrap(), ) } } diff --git a/src/nix/hive/modules.nix b/src/nix/hive/modules.nix new file mode 100644 index 0000000..32f81e0 --- /dev/null +++ b/src/nix/hive/modules.nix @@ -0,0 +1,98 @@ +with builtins; { + assertionModule = { config, lib, ... }: { + assertions = lib.mapAttrsToList (key: opts: let + nonNulls = l: filter (x: x != null) l; + in { + assertion = length (nonNulls [opts.text opts.keyCommand opts.keyFile]) == 1; + message = + let prefix = "${name}.deployment.keys.${key}"; + in "Exactly one of `${prefix}.text`, `${prefix}.keyCommand` and `${prefix}.keyFile` must be set."; + }) config.deployment.keys; + }; + + # Change the ownership of all keys uploaded pre-activation + # + # This is built as part of the system profile. + # We must be careful not to access `text` / `keyCommand` / `keyFile` here + keyChownModule = { lib, config, ... }: let + preActivationKeys = lib.filterAttrs (name: key: key.uploadAt == "pre-activation") config.deployment.keys; + scriptDeps = if config.system.activationScripts ? groups then [ "groups" ] else [ "users" ]; + + commands = lib.mapAttrsToList (name: key: let + keyPath = "${key.destDir}/${name}"; + in '' + if [ -f "${keyPath}" ]; then + if ! chown ${key.user}:${key.group} "${keyPath}"; then + # Error should be visible in stderr + failed=1 + fi + else + >&2 echo "Key ${keyPath} does not exist. Skipping chown." + fi + '') preActivationKeys; + + script = lib.stringAfter scriptDeps '' + # This script is injected by Colmena to change the ownerships + # of keys (`deployment.keys`) deployed before system activation. + + >&2 echo "setting up key ownerships..." + + # We set the ownership of as many keys as possible before failing + failed= + + ${concatStringsSep "\n" commands} + + if [ -n "$failed" ]; then + >&2 echo "Failed to set the ownership of some keys." + + # The activation script has a trap to handle failed + # commands and print out various debug information. + # Let's trigger that instead of `exit 1`. + false + fi + ''; + in { + system.activationScripts.colmena-chown-keys = lib.mkIf (length commands != 0) script; + }; + + # Create "${name}-key" services for NixOps compatibility + # + # This is built as part of the system profile. + # We must be careful not to access `text` / `keyCommand` / `keyFile` here + # + # Sadly, path units don't automatically deactivate the bound units when + # the key files are deleted, so we use inotifywait in the services' scripts. + # + # + keyServiceModule = { pkgs, lib, config, ... }: { + systemd.paths = lib.mapAttrs' (name: val: { + name = "${name}-key"; + value = { + wantedBy = [ "paths.target" ]; + pathConfig = { + PathExists = val.path; + }; + }; + }) config.deployment.keys; + + systemd.services = lib.mapAttrs' (name: val: { + name = "${name}-key"; + value = { + bindsTo = [ "${name}-key.path" ]; + serviceConfig = { + Restart = "on-failure"; + }; + path = [ pkgs.inotifyTools ]; + script = '' + if [[ ! -e "${val.path}" ]]; then + >&2 echo "${val.path} does not exist" + exit 0 + fi + + inotifywait -qq -e delete_self "${val.path}" + >&2 echo "${val.path} disappeared" + ''; + }; + }) config.deployment.keys; + }; +} diff --git a/src/nix/hive/options.nix b/src/nix/hive/options.nix new file mode 100644 index 0000000..a07df1c --- /dev/null +++ b/src/nix/hive/options.nix @@ -0,0 +1,287 @@ +with builtins; rec { + keyType = { lib, name, config, ... }: let + inherit (lib) types; + in { + options = { + name = lib.mkOption { + description = '' + File name of the key. + ''; + default = name; + type = types.str; + }; + text = lib.mkOption { + description = '' + Content of the key. + One of `text`, `keyCommand` and `keyFile` must be set. + ''; + default = null; + type = types.nullOr types.str; + }; + keyFile = lib.mkOption { + description = '' + Path of the local file to read the key from. + One of `text`, `keyCommand` and `keyFile` must be set. + ''; + default = null; + apply = value: if value == null then null else toString value; + type = types.nullOr types.path; + }; + keyCommand = lib.mkOption { + description = '' + Command to run to generate the key. + One of `text`, `keyCommand` and `keyFile` must be set. + ''; + default = null; + type = let + nonEmptyList = types.addCheck (types.listOf types.str) (l: length l > 0); + in types.nullOr nonEmptyList; + }; + destDir = lib.mkOption { + description = '' + Destination directory on the host. + ''; + default = "/run/keys"; + type = types.path; + }; + path = lib.mkOption { + description = '' + Full path to the destination. + ''; + default = "${config.destDir}/${config.name}"; + type = types.path; + internal = true; + }; + user = lib.mkOption { + description = '' + The group that will own the file. + ''; + default = "root"; + type = types.str; + }; + group = lib.mkOption { + description = '' + The group that will own the file. + ''; + default = "root"; + type = types.str; + }; + permissions = lib.mkOption { + description = '' + Permissions to set for the file. + ''; + default = "0600"; + type = types.str; + }; + uploadAt = lib.mkOption { + description = '' + When to upload the keys. + + - pre-activation (default): Upload the keys before activating the new system profile. + - post-activation: Upload the keys after successfully activating the new system profile. + + For `colmena upload-keys`, all keys are uploaded at the same time regardless of the configuration here. + ''; + default = "pre-activation"; + type = types.enum [ "pre-activation" "post-activation" ]; + }; + }; + }; + + # Colmena-specific options + # + # Largely compatible with NixOps/Morph. + deploymentOptions = { name, lib, ... }: let + inherit (lib) types; + in { + options = { + deployment = { + targetHost = lib.mkOption { + description = '' + The target SSH node for deployment. + + By default, the node's attribute name will be used. + If set to null, only local deployment will be supported. + ''; + type = types.nullOr types.str; + default = name; + }; + targetPort = lib.mkOption { + description = '' + The target SSH port for deployment. + + By default, the port is the standard port (22) or taken + from your ssh_config. + ''; + type = types.nullOr types.ints.unsigned; + default = null; + }; + targetUser = lib.mkOption { + description = '' + The user to use to log into the remote node. If null, login as the + current user. + ''; + type = types.nullOr types.str; + default = "root"; + }; + allowLocalDeployment = lib.mkOption { + description = '' + Allow the configuration to be applied locally on the host running + Colmena. + + For local deployment to work, all of the following must be true: + - The node must be running NixOS. + - The node must have deployment.allowLocalDeployment set to true. + - The node's networking.hostName must match the hostname. + + To apply the configurations locally, run `colmena apply-local`. + You can also set deployment.targetHost to null if the nost is not + accessible over SSH (only local deployment will be possible). + ''; + type = types.bool; + default = false; + }; + buildOnTarget = lib.mkOption { + description = '' + Whether to build the system profiles on the target node itself. + + When enabled, Colmena will copy the derivation to the target + node and initiate the build there. This avoids copying back the + build results involved with the native distributed build + feature. Furthermore, the `build` goal will be equivalent to + the `push` goal. Since builds happen on the target node, the + results are automatically "pushed" and won't exist in the local + Nix store. + + You can temporarily override per-node settings by passing + `--build-on-target` (enable for all nodes) or + `--no-build-on-target` (disable for all nodes) on the command + line. + ''; + type = types.bool; + default = false; + }; + tags = lib.mkOption { + description = '' + A list of tags for the node. + + Can be used to select a group of nodes for deployment. + ''; + type = types.listOf types.str; + default = []; + }; + keys = lib.mkOption { + description = '' + A set of secrets to be deployed to the node. + + Secrets are transferred to the node out-of-band and + never ends up in the Nix store. + ''; + type = types.attrsOf (types.submodule keyType); + default = {}; + }; + replaceUnknownProfiles = lib.mkOption { + description = '' + Allow a configuration to be applied to a host running a profile we + have no knowledge of. By setting this option to false, you reduce + the likelyhood of rolling back changes made via another Colmena user. + + Unknown profiles are usually the result of either: + - The node had a profile applied, locally or by another Colmena. + - The host running Colmena garbage-collecting the profile. + + To force profile replacement on all targeted nodes during apply, + use the flag `--force-replace-unknown-profiles`. + ''; + type = types.bool; + default = true; + }; + privilegeEscalationCommand = lib.mkOption { + description = '' + Command to use to elevate privileges when activating the new profiles on SSH hosts. + + This is used on SSH hosts when `deployment.targetUser` is not `root`. + The user must be allowed to use the command non-interactively. + ''; + type = types.listOf types.str; + default = [ "sudo" "-H" "--" ]; + }; + }; + }; + }; + # Hive-wide options + metaOptions = { lib, ... }: let + inherit (lib) types; + in { + options = { + name = lib.mkOption { + description = '' + The name of the configuration. + ''; + type = types.str; + default = "hive"; + }; + description = lib.mkOption { + description = '' + A short description for the configuration. + ''; + type = types.str; + default = "A Colmena Hive"; + }; + nixpkgs = lib.mkOption { + description = '' + The pinned Nixpkgs package set. Accepts one of the following: + + - A path to a Nixpkgs checkout + - The Nixpkgs lambda (e.g., import ) + - An initialized Nixpkgs attribute set + + This option must be specified when using Flakes. + ''; + type = types.unspecified; + default = null; + }; + nodeNixpkgs = lib.mkOption { + description = '' + Node-specific Nixpkgs pins. + ''; + type = types.attrsOf types.unspecified; + default = {}; + }; + machinesFile = lib.mkOption { + description = '' + Use the machines listed in this file when building this hive configuration. + + If your Colmena host has nix configured to allow for remote builds + (for nix-daemon, your user being included in trusted-users) + you can set a machines file that will be passed to the underlying + nix-store command during derivation realization as a builders option. + For example, if you support multiple orginizations each with their own + build machine(s) you can ensure that builds only take place on your + local machine and/or the machines specified in this file. + + See https://nixos.org/manual/nix/stable/#chap-distributed-builds + for the machine specification format. + + This option is ignored when builds are initiated on the remote nodes + themselves via `deployment.buildOnTarget` or `--build-on-target`. To + still use the Nix distributed build functionality, configure the + builders on the target nodes with `nix.buildMachines`. + ''; + default = null; + apply = value: if value == null then null else toString value; + type = types.nullOr types.path; + }; + specialArgs = lib.mkOption { + description = '' + A set of special arguments to be passed to NixOS modules. + + This will be merged into the `specialArgs` used to evaluate + the NixOS configurations. + ''; + default = {}; + type = types.attrsOf types.unspecified; + }; + }; + }; +}