I was wondering if there’s been any discussions on making nixops itself a little bit more modular in a similar sense to Rickard Nilsson - Nix(OS) modules everywhere! (NixCon 2018) - YouTube?
I’ve been trying out something that seems like it’s been working quite well in my remote-builder-network definition.
Specifically this includes:
-
lib.nixopsNetwork
is a function similar tolib.nixosSystem
that builds a regular nixops configuration, but can includeimports = [ ... ]
and options in the network definition. -
nixopsModules
(similar tonixosModules
), exposes network modules from a flake.
Curious if others feel that something like this might help with modernizing nixops in the new world of flakes?
To support spot fleets, auto-scaling groups etc in future, I feel as though having instances
explicit in the schema could be helpful (see example below)?
Also, it might be useful to pave the way for e.g. lib.terraformNetwork
in the future.
Rough example
Roughly the layout I have looks like this:
Flake:
# flake.nix
{
description = "Flake with nixops modules";
inputs = {
# ...
};
outputs = { self, ... }: {
nixosModules = {
myNode = ./nixos-modules/my-node;
};
# nixopsModules is similar to nixosModules
nixopsModules = {
myNetwork = ./nixops-modules/my-network;
};
nixopsConfigurations.default =
let
# lib.nixopsNetwork is similar to lib.nixosSystem
lib = { nixopsNetwork = import ./lib/nixops-network.nix; };
in
lib.nixopsNetwork {
modules = [ ./nixops-configurations ];
specialArgs.flake = self;
inherit nixpkgs;
};
};
}
Lib
# lib/nixops-network.nix
{
nixopsNetwork = { nixpkgs, modules, specialArgs }:
let
baseModule = {
options = with lib.types; {
network = lib.mkOption { type = attrsOf anything; };
resources = lib.mkOption { type = attrsOf anything; };
instances = lib.mkOption { type = attrsOf anything; };
nixpkgs = lib.mkOption { type = anything; };
};
};
networkConfig =
(lib.evalModules {
modules = [ baseModule ] ++ modules;
inherit specialArgs;
}).config;
in
{ inherit (networkConfig) network resources nixpkgs; } // networkConfig.instances;
}
Configuration
# nixops-configurations/default.nix
{ flake, ... }:
{
imports = [
flake.nixopsModules.myNetwork
];
myNetwork = {
name = "my-network";
nodeConfigurations = {
node-1 = flake.nixosModules.node;
};
};
instances.defaults = { pkgs, lib, ... }: {
deployment.keys = {
# ...
};
};
nixpkgs = flake.inputs.nixpkgs;
}
Modules
# nixos-modules/my-node.nix
{ pkgs
, config
, lib
, ...
}:
let
myNetwork = config.myNetwork;
myNode = config.myNode;
myNodeOptions = {
name = lib.mkOption {
type = lib.types.str;
default = myNetwork.name;
};
};
in
{
options.myNode = myNodeOptions;
config = {
networking.firewall = {
enable = true;
};
boot.loader.grub.device = "/dev/xvda";
fileSystems."/" = {
label = "nixos";
fsType = "ext4";
};
# more config ...
};
}
# nixops-modules/my-network.nix
{ flake
, config
, lib
, ...
}:
let
myNetwork = config.myNetwork;
myNetworkOptions = {
name = lib.mkOption {
type = lib.types.str;
default = "my-network";
};
nodeConfigurations = lib.mkOption {
type = lib.types.attrsOf lib.types.anything; # One or more nixos configurations
description = "NixOS configuration of each node.";
};
};
awsInstances = import ./aws/instances.nix {
networkName = myNetwork.name;
inherit (myNetwork.aws) region zone;
};
mkInstance = configuration: { resources, lib, ... }@args: {
imports = [
configuration
];
options.myNetwork = {
inherit (myNetworkOptions) name;
};
config = {
deployment = awsInstances.myNode args;
myNetwork = {
inherit (config.myNetwork) name;
};
};
};
in
{
# Import AWS resources
imports = [
./aws/resources
];
options.myNetwork = myNetworkOptions;
config = {
network.description = lib.mkDefault "${myNetwork.name} network";
instances = lib.mapAttrs (_: mkInstance) myNetwork.nodeConfigurations;
};
}