From 15fdfb71a781838a230f2eb15d7aaa7a6b7e2a33 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 12:11:04 +1000 Subject: [PATCH] use mix module system --- cerulean/default.nix | 20 +++++ cerulean/flake-config/default.nix | 25 ++++++ cerulean/flake-config/nexus.nix | 90 +++++++++++++++++++++ cerulean/flake-config/nodes.nix | 67 ++++++++++++++++ flake.nix | 127 ++---------------------------- 5 files changed, 207 insertions(+), 122 deletions(-) create mode 100644 cerulean/default.nix create mode 100644 cerulean/flake-config/default.nix create mode 100644 cerulean/flake-config/nexus.nix create mode 100644 cerulean/flake-config/nodes.nix diff --git a/cerulean/default.nix b/cerulean/default.nix new file mode 100644 index 0000000..e72374e --- /dev/null +++ b/cerulean/default.nix @@ -0,0 +1,20 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{mix, ...} @ inputs: +mix.mkMod (mix.newMixture {specialArgs = inputs;}) +(mixture: { + includes.public = [ + ./flake-config + ]; +}) diff --git a/cerulean/flake-config/default.nix b/cerulean/flake-config/default.nix new file mode 100644 index 0000000..1a4e885 --- /dev/null +++ b/cerulean/flake-config/default.nix @@ -0,0 +1,25 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{ + mix, + mixture, + ... +}: +mix.mkMod mixture +(mixture: { + includes.public = [ + ./nodes + ./nexus + ]; +}) diff --git a/cerulean/flake-config/nexus.nix b/cerulean/flake-config/nexus.nix new file mode 100644 index 0000000..58c1a99 --- /dev/null +++ b/cerulean/flake-config/nexus.nix @@ -0,0 +1,90 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{ + mixture, + sys, + lib, + deploy-rs, + ... +}: let + inherit + (mixture.nodes) + mapNodes + ; + + inherit + (lib) + nixosSystem + ; + + mkNexus' = config: rec { + nixosConfigurations = mapNodes ( + nodeName: node: + nixosSystem { + system = node.system; + modules = node.modules; + + # nix passes these to every single module + specialArgs = + node.specialArgs + // { + pkgs = sys.pkgsFor node.system; + upkgs = sys.upkgsFor node.system; + }; + } + ); + + deploy.nodes = mapNodes (nodeName: node: let + nixosFor = system: deploy-rs.lib.${system}.activate.nixos; + in { + hostname = node.deploy.ssh.host; + + profilesOrder = ["default"]; # profiles priority + profiles.default = { + path = nixosFor node.system nixosConfigurations.${nodeName}; + + user = node.deploy.user; + sudo = node.deploy.sudo; + interactiveSudo = node.deploy.interactiveSudo; + + fastConnection = false; + + autoRollback = node.deploy.autoRollback; + magicRollback = node.deploy.magicRollback; + activationTimeout = node.deploy.activationTimeout; + confirmTimeout = node.deploy.confirmTimeout; + + remoteBuild = node.deploy.remoteBuild; + sshUser = node.deploy.ssh.user; + sshOpts = + node.deploy.ssh.opts + ++ ( + if builtins.elem "-p" node.deploy.ssh.opts + then [] + else ["-p" (toString node.deploy.ssh.port)] + ) + ++ ( + if builtins.elem "-A" node.deploy.ssh.opts + then [] + else ["-A"] + ); + }; + }); + + checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; + }; +in { + mkNexus = outputs: + (mkNexus' outputs.cerulean) // (removeAttrs outputs ["cerulean"]); +} diff --git a/cerulean/flake-config/nodes.nix b/cerulean/flake-config/nodes.nix new file mode 100644 index 0000000..f6f95fc --- /dev/null +++ b/cerulean/flake-config/nodes.nix @@ -0,0 +1,67 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{nib, ...}: rec { + # abstract node instance that stores all default values + templateNode = name: system: let + Terminal = nib.types.Terminal; + + missing = msg: path: + Terminal (abort '' + Each Cerulean Nexus node is required to specify ${msg}! + Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. + ''); + in { + system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) + modules = missing "its required modules" "modules"; + specialArgs = Terminal {}; + + deploy = { + user = "root"; + sudo = "sudo -u"; + interactiveSudo = false; + + remoteBuild = false; # prefer local builds for remote deploys + + autoRollback = true; # reactivate previous profile if activation fails + magicRollback = true; + + activationTimeout = 500; # timeout in seconds for profile activation + confirmTimeout = 30; # timeout in seconds for profile activation confirmation + + ssh = { + host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; + user = missing "an SSH username for deployment" "deploy.ssh.user"; + port = 22; + opts = []; + }; + }; + }; + + parseNode = name: nodeAttrs: + if !(builtins.isAttrs nodeAttrs) + then + # fail if node is not an attribute set + abort '' + Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! + Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. + '' + else let + templateAttrs = templateNode name nodeAttrs.system; + in + nib.parse.mergeStructs templateAttrs nodeAttrs; + + mapNodes' = f: + builtins.mapAttrs + (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)); +} diff --git a/flake.nix b/flake.nix index 4aaa4a2..be08ecc 100644 --- a/flake.nix +++ b/flake.nix @@ -47,11 +47,12 @@ config.allowUnfree = false; }; }; - in rec { + + cerulean = import ./cerulean {inherit inputs lib sys;}; + in { overlays = [ - # deploy-rs is built from the flake input, not from nixpkgs! - # To take advantage of the nixpkgs binary cache, - # the deploy-rs package can be overwritten: + # build deploy-rs as a package not from the flake input, + # hence we can rely on a nixpkg binary cache. deploy-rs.overlays.default (self: super: { deploy-rs = { @@ -60,123 +61,5 @@ }; }) ]; - - mkNexusConfig = config: let - # abstract node instance that stores all default values - templateNode = name: system: let - Terminal = nib.types.Terminal; - - missing = msg: path: - Terminal (abort '' - Each Cerulean Nexus node is required to specify ${msg}! - Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. - ''); - in { - system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) - modules = missing "its required modules" "modules"; - specialArgs = Terminal {}; - - deploy = { - user = "root"; - sudo = "sudo -u"; - interactiveSudo = false; - - remoteBuild = false; # prefer local builds for remote deploys - - autoRollback = true; # reactivate previous profile if activation fails - magicRollback = true; - - activationTimeout = 500; # timeout in seconds for profile activation - confirmTimeout = 30; # timeout in seconds for profile activation confirmation - - ssh = { - host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; - user = missing "an SSH username for deployment" "deploy.ssh.user"; - port = 22; - opts = []; - }; - }; - }; - - parseNode = name: nodeAttrs: - if !(builtins.isAttrs nodeAttrs) - then - # fail if node is not an attribute set - abort '' - Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! - Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. - '' - else let - templateAttrs = templateNode name nodeAttrs.system; - in - nib.parse.mergeStructs templateAttrs nodeAttrs; - - # mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); - mapNodes = f: - builtins.mapAttrs - (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)) - config.nexus.nodes; - in rec { - nixosConfigurations = mapNodes ( - nodeName: node: - lib.nixosSystem { - system = node.system; - modules = node.modules; - - # nix passes these to every single module - specialArgs = - node.specialArgs - // { - inherit inputs; - pkgs = sys.pkgsFor node.system; - upkgs = sys.upkgsFor node.system; - }; - } - ); - - deploy.nodes = mapNodes (nodeName: node: let - nixosFor = system: deploy-rs.lib.${system}.activate.nixos; - in { - hostname = node.deploy.ssh.host; - - profilesOrder = ["default"]; # profiles priority - profiles.default = { - path = nixosFor node.system nixosConfigurations.${nodeName}; - - user = node.deploy.user; - sudo = node.deploy.sudo; - interactiveSudo = node.deploy.interactiveSudo; - - fastConnection = false; - - autoRollback = node.deploy.autoRollback; - magicRollback = node.deploy.magicRollback; - activationTimeout = node.deploy.activationTimeout; - confirmTimeout = node.deploy.confirmTimeout; - - remoteBuild = node.deploy.remoteBuild; - sshUser = node.deploy.ssh.user; - sshOpts = - node.deploy.ssh.opts - ++ ( - if builtins.elem "-p" node.deploy.ssh.opts - then [] - else ["-p" (toString node.deploy.ssh.port)] - ) - ++ ( - if builtins.elem "-A" node.deploy.ssh.opts - then [] - else ["-A"] - ); - }; - }); - - checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; - }; - - mkNexus = outputs: let - config = outputs.cerulean; - in - (mkNexusConfig config) // (removeAttrs outputs ["cerulean"]); }; }