diff --git a/deploy-rs.nix b/deploy-rs.nix new file mode 100644 index 0000000..59566a2 --- /dev/null +++ b/deploy-rs.nix @@ -0,0 +1,58 @@ +{ lib, config, ... }: +let + inherit (builtins) replaceStrings attrNames mapAttrs; + inherit (lib) nameValuePair mapAttrs' intersectLists filterAttrs mkOption; + + cfg = config.deploy-rs; + + systems = config.nixos.systems; + # deploy can't handle the `@` + homes = mapAttrs' (n: v: nameValuePair (replaceStrings ["@"] ["-at-"] n) v) config.home-manager.homes; + + nodesFor = systemsOrHomes: filterAttrs (_: m: m != null) (mapAttrs (_: c: + if c.configuration.config.my.deploy.enable + # Since we're using the submodule, we need to take the defintions. By importing them, the submodule type checking + # and merging can still work at this level. Also gotta make the module a function or else it'll just be treated as + # configuration only (aka shorthandOnlyDefinesConfig = true) + then { ... }: { imports = c.configuration.options.my.deploy.node.definitions; } + else null + ) systemsOrHomes); +in +{ + options.deploy-rs = with lib.types; { + inherit (lib.my.deploy-rs) deploy; + rendered = mkOption { + type = attrsOf unspecified; + default = null; + internal = true; + description = "Rendered deploy-rs configuration."; + }; + }; + + config = { + assertions = [ + (let + duplicates = intersectLists (attrNames systems) (attrNames homes); + in + { + assertion = duplicates == [ ]; + message = "Duplicate-ly named NixOS systems: ${toString duplicates}"; + }) + ]; + + deploy-rs = { + deploy = { + nodes = ( + (nodesFor systems) // + (nodesFor homes) + ); + + autoRollback = true; + magicRollback = true; + }; + + # Filter out null values so deploy merges overriding options correctly + rendered = lib.my.deploy-rs.filterOpts cfg.deploy; + }; + }; +} diff --git a/flake.nix b/flake.nix index 2e1b0ab..5a46e4d 100644 --- a/flake.nix +++ b/flake.nix @@ -40,7 +40,7 @@ }: let inherit (builtins) mapAttrs; - inherit (lib) recurseIntoAttrs filterAttrs evalModules; + inherit (lib) recurseIntoAttrs evalModules; inherit (lib.flake) flattenTree eachDefaultSystem; inherit (lib.my) mkDefaultSystemsPkgs flakePackageOverlay; @@ -108,8 +108,12 @@ }; } + # Not an internal part of the module system apparently, but it doesn't have any dependencies other than lib + "${pkgsFlakes.unstable}/nixos/modules/misc/assertions.nix" + ./nixos ./home-manager + ./deploy-rs.nix ] ++ configs; }; in @@ -125,13 +129,7 @@ nixosConfigurations = mapAttrs (_: s: s.configuration) nixfiles.config.nixos.systems; homeConfigurations = mapAttrs (_: s: s.configuration) nixfiles.config.home-manager.homes; - deploy = { - nodes = filterAttrs (_: n: n != null) - (mapAttrs (_: system: system.config.my.deploy.rendered) self.nixosConfigurations); - - autoRollback = true; - magicRollback = true; - }; + deploy = nixfiles.config.deploy-rs.rendered; } // (eachDefaultSystem (system: let diff --git a/home-manager/configs/castle.nix b/home-manager/configs/castle.nix index 77e0ccd..2a0368d 100644 --- a/home-manager/configs/castle.nix +++ b/home-manager/configs/castle.nix @@ -11,6 +11,10 @@ targets.genericLinux.enable = true; my = { + deploy.node = { + hostname = "h.nul.ie"; + sshOpts = [ "-4" "-p" "8022" ]; + }; ssh.matchBlocks = { home = { host = diff --git a/home-manager/modules/_list.nix b/home-manager/modules/_list.nix index 12c913a..24eb912 100644 --- a/home-manager/modules/_list.nix +++ b/home-manager/modules/_list.nix @@ -2,5 +2,6 @@ home-manager.modules = { common = ./common.nix; gui = ./gui.nix; + deploy-rs = ./deploy-rs.nix; }; } diff --git a/home-manager/modules/common.nix b/home-manager/modules/common.nix index b266084..427a1d0 100644 --- a/home-manager/modules/common.nix +++ b/home-manager/modules/common.nix @@ -1,4 +1,4 @@ -{ lib, pkgs, pkgs', inputs, options, config, ... }@args: +{ lib, pkgs, pkgs', inputs, config, ... }@args: let inherit (builtins) mapAttrs readFile; inherit (lib) concatMapStrings concatStringsSep optionalAttrs versionAtLeast mkMerge mkIf mkDefault mkOption; @@ -153,6 +153,7 @@ in home = { packages = with pkgs; [ + file tree iperf3 ]; @@ -170,7 +171,9 @@ in (mkIf (config.my.isStandalone || !args.osConfig.home-manager.useGlobalPkgs) { # Note: If globalPkgs mode is on, then these will be overridden by the NixOS equivalents of these options nixpkgs = { - overlays = [ ]; + overlays = [ + inputs.deploy-rs.overlay + ]; config = { allowUnfree = true; }; diff --git a/home-manager/modules/deploy-rs.nix b/home-manager/modules/deploy-rs.nix new file mode 100644 index 0000000..fa79753 --- /dev/null +++ b/home-manager/modules/deploy-rs.nix @@ -0,0 +1,38 @@ +{ lib, pkgs, config, ... }: +let + inherit (builtins) head; + inherit (lib) mkMerge mkIf mkDefault; + inherit (lib.my) mkBoolOpt'; + + cfg = config.my.deploy; +in +{ + options.my.deploy = with lib.types; { + enable = mkBoolOpt' true "Whether to expose deploy-rs configuration for this home configuration."; + inherit (lib.my.deploy-rs) node; + + generate = { + home.enable = mkBoolOpt' true "Whether to generate a deploy-rs profile for this home config."; + }; + }; + + config = mkMerge [ + { + my.deploy.enable = mkIf (!config.my.isStandalone) false; + } + (mkIf cfg.enable { + my.deploy.node = { + profiles = { + home = mkIf cfg.generate.home.enable { + path = pkgs.deploy-rs.lib.activate.home-manager { inherit (config.home) activationPackage; }; + profilePath = "/nix/var/nix/profiles/per-user/${config.home.username}/profile"; + }; + }; + + sshUser = mkDefault config.home.username; + user = config.home.username; + sudo = mkDefault "sudo -u"; + }; + }) + ]; +} diff --git a/lib.nix b/lib.nix index 617571a..9f4845e 100644 --- a/lib.nix +++ b/lib.nix @@ -91,7 +91,7 @@ rec { sshUser = nullOrOpt' str "Username deploy-rs will deploy with."; user = nullOrOpt' str "Username deploy-rs will deploy with."; sudo = nullOrOpt' str "Command to elevate privileges with (used if the deployment user != profile user)."; - sshOpts = nullOrOpt' (listOf str) + sshOpts = mkOpt' (listOf str) [ ] "Options deploy-rs will pass to ssh. Note: overriding at a lower level _merges_ options."; fastConnection = nullOrOpt' bool "Whether to copy the whole closure instead of using substitution."; autoRollback = nullOrOpt' bool "Whether to roll back the profile if activation fails."; @@ -104,18 +104,30 @@ rec { path = mkOpt' package "" "Derivation to build (should include activation script)."; profilePath = nullOrOpt' str "Path to profile location"; } // globalOpts; - profile = submodule { options = profileOpts; }; + profileType = submodule { options = profileOpts; }; nodeOpts = { hostname = mkOpt' str "" "Hostname deploy-rs will connect to."; profilesOrder = nullOrOpt' (listOf str) "Order to deploy profiles in (remainder will be deployed in arbitrary order)."; - profiles = mkOpt' (attrsOf profile) { } "Profiles to deploy."; + profiles = mkOpt' (attrsOf profileType) { } "Profiles to deploy."; } // globalOpts; + nodeType = submodule { options = nodeOpts; }; + + deployOpts = { + nodes = mkOption { + type = attrsOf nodeType; + default = { }; + internal = true; + description = "deploy-rs node configurations."; + }; + } // globalOpts; + deployType = submodule { options = deployOpts; }; in - rec { - inherit profile; - node = submodule { options = nodeOpts; }; + { + inherit globalOpts; + node = mkOpt' nodeType { } "deploy-rs node configuration."; + deploy = mkOpt' deployType { } "deploy-rs configuration."; filterOpts = filterAttrsRecursive (_: v: v != null); }; diff --git a/nixos/modules/deploy-rs.nix b/nixos/modules/deploy-rs.nix index 0976a19..4c425d5 100644 --- a/nixos/modules/deploy-rs.nix +++ b/nixos/modules/deploy-rs.nix @@ -1,56 +1,47 @@ -{ lib, extendModules, pkgs, options, config, baseModules, ... }: +{ lib, pkgs, config, ... }: let inherit (builtins) head; - inherit (lib) mkOption mkMerge mkIf mkDefault; + inherit (lib) mkMerge mkIf mkDefault; inherit (lib.my) mkOpt' mkBoolOpt'; cfg = config.my.deploy; in { - options.my.deploy = with lib.types; rec { + options.my.deploy = with lib.types; { authorizedKeys = { keys = mkOpt' (listOf singleLineStr) [ ] "SSH public keys to add to the default deployment user."; keyFiles = mkOpt' (listOf str) [ ] "SSH public key files to add to the default deployment user."; }; enable = mkBoolOpt' true "Whether to expose deploy-rs configuration for this system."; - node = mkOpt' lib.my.deploy-rs.node { } "deploy-rs node configuration."; + inherit (lib.my.deploy-rs) node; generate = { system.enable = mkBoolOpt' true "Whether to generate a deploy-rs profile for this system's config."; }; - rendered = mkOption { - type = nullOr (attrsOf anything); - default = null; - internal = true; - description = "Rendered deploy-rs node configuration."; - }; }; config = mkMerge [ { - my.deploy = { - enable = mkIf config.my.build.isDevVM false; - - node = { - hostname = mkDefault config.networking.fqdn; - profiles = { - system = mkIf cfg.generate.system.enable { - path = pkgs.deploy-rs.lib.activate.nixos { inherit config; }; - - user = "root"; - }; - }; - - sshUser = "deploy"; - user = mkDefault "root"; - sudo = mkDefault (if config.security.doas.enable then "doas -u" else "sudo -u"); - sshOpts = mkDefault [ "-p" (toString (head config.services.openssh.ports)) ]; - }; - rendered = mkIf cfg.enable (lib.my.deploy-rs.filterOpts cfg.node); - }; + my.deploy.enable = mkIf config.my.build.isDevVM false; } (mkIf cfg.enable { + my.deploy.node = { + hostname = mkDefault config.networking.fqdn; + profiles = { + system = mkIf cfg.generate.system.enable { + path = pkgs.deploy-rs.lib.activate.nixos { inherit config; }; + + user = "root"; + }; + }; + + sshUser = "deploy"; + user = mkDefault "root"; + sudo = mkDefault (if config.security.doas.enable then "doas -u" else "sudo -u"); + sshOpts = mkDefault [ "-p" (toString (head config.services.openssh.ports)) ]; + }; + users = { users."${cfg.node.sshUser}" = { isSystemUser = true;