# This module describes # - clanInternals.inventoryClass.inventory # - clan.inventory # Which is the public interface for the inventory system { lib, clanLib, config, options, ... }: let types = lib.types; metaOptionsWith = name: { name = lib.mkOption { type = types.str; default = name; description = '' Name of the machine or service ''; }; description = lib.mkOption { default = null; type = types.nullOr types.str; description = '' Optional freeform description ''; }; icon = lib.mkOption { default = null; type = types.nullOr types.str; description = '' Under construction, will be used for the UI ''; }; }; in { imports = [ (lib.mkRemovedOptionModule [ "services" ] '' The `inventory.services` option has been removed. Use `inventory.instances` instead. See: https://docs.clan.lol/concepts/inventory/#services '') ]; options = { # Internal things _inventoryFile = lib.mkOption { type = types.path; readOnly = true; internal = true; visible = false; }; noInstanceOptions = lib.mkOption { type = types.bool; internal = true; visible = false; default = false; }; options = lib.mkOption { internal = true; visible = false; type = types.raw; default = options; }; # --------------------------- modules = lib.mkOption { # Don't define the type yet # We manually transform the value with types.deferredModule.merge later to keep them serializable type = types.attrsOf types.raw; default = { }; defaultText = "clanModules of clan-core"; description = '' A mapping of module names to their path. Each module can be referenced by its `attributeName` in the `inventory.services` attribute set. !!! Important Each module MUST fulfill the following requirements to be usable with the inventory: - The module MUST have a `README.md` file with a `description`. - The module MUST have at least `features = [ "inventory" ]` in the frontmatter section. - The module MUST have a subfolder `roles` with at least one `{roleName}.nix` file. For further information see: [Module Authoring Guide](../../guides/services/community.md). ???+ example ```nix clan-core.lib.clan { # 1. Add the module to the available inventory modules inventory.modules = { custom-module = ./modules/my_module; }; # 2. Use the module in the inventory inventory.services = { custom-module.instance_1 = { roles.default.machines = [ "machineA" ]; }; }; }; ``` ''; apply = _: { }; }; assertions = lib.mkOption { type = types.listOf types.unspecified; internal = true; visible = false; default = [ ]; }; meta = lib.mkOption { type = lib.types.submoduleWith { modules = [ ./meta-interface.nix ]; }; }; tags = lib.mkOption { default = { }; description = '' Tags of the inventory are used to group machines together. It is recommended to use [`machine.tags`](#inventory.machines.tags) to define the tags of the machines. This can be used to define custom tags that are either statically set or dynamically computed. #### Static Tags ???+ example "Static Tag Example" ```nix inventory.tags = { foo = [ "machineA" "machineB" ]; }; ``` The tag `foo` will always be added to `machineA` and `machineB`. #### Dynamic Tags It is possible to compute tags based on the machines properties or based on other tags. !!! danger This is a powerful feature and should be used with caution. It is possible to cause infinite recursion by computing tags based on the machines properties or based on other tags. ???+ example "Dynamic Tag Example" allButFoo is a computed tag. It will be added to all machines except 'foo' `all` is a predefined tag. See the docs of [`tags.all`](#inventory.tags.all). ```nix # inventory.tags ↓ ↓ inventory.machines inventory.tags = {config, machines...}: { # ↓↓↓ The "all" tag allButFoo = builtins.filter (name: name != "foo") config.all; }; ``` !!! warning Do NOT compute `tags` from `machine.tags` this will cause infinite recursion. ''; type = types.submoduleWith { specialArgs = { inherit (config) machines; }; modules = [ { freeformType = with lib.types; lazyAttrsOf (listOf str); # Reserved tags # Defined as options here to show them in advance options = { # 'All machines' tag all = lib.mkOption { type = with lib.types; listOf str; defaultText = "[ ]"; description = '' !!! example "Predefined Tag" Will be added to all machines ```nix inventory.machines.machineA.tags = [ "all" ]; ``` ''; }; nixos = lib.mkOption { type = with lib.types; listOf str; defaultText = "[ ]"; description = '' !!! example "Predefined Tag" Will be added to all machines that set `machineClass = "nixos"` ```nix inventory.machines.machineA.tags = [ "nixos" ]; ``` ''; }; darwin = lib.mkOption { type = with lib.types; listOf str; defaultText = "[ ]"; description = '' !!! example "Predefined Tag" Will be added to all machines that set `machineClass = "darwin"` ```nix inventory.machines.machineA.tags = [ "darwin" ]; ``` ''; }; }; } ]; }; }; machines = lib.mkOption { description = '' Machines in the inventory. Each machine declared here can be referencd via its `attributeName` by the `inventory.service`s `roles`. ''; default = { }; type = types.lazyAttrsOf ( types.submoduleWith ({ modules = [ ( { name, ... }: { tags = builtins.attrNames ( # config.tags lib.filterAttrs (_t: tagMembers: builtins.elem name tagMembers) config.tags ); } ) ( { name, ... }: { options = { inherit (metaOptionsWith name) name description icon; machineClass = lib.mkOption { default = "nixos"; type = types.enum [ "nixos" "darwin" ]; description = '' The module system that should be used to construct the machine Set this to `darwin` for macOS machines ''; }; installedAt = lib.mkOption { type = types.nullOr types.int; default = null; description = '' Indicates when the machine was first installed. Timestamp is in unix time (seconds since epoch). ''; }; tags = lib.mkOption { description = '' List of tags for the machine. The machine can be referenced by its tags in `inventory.services` ???+ Example ```nix inventory.machines.machineA.tags = [ "tag1" "tag2" ]; ``` ```nix services.borgbackup."instance_1".roles.client.tags = [ "tag1" ]; ``` !!! Note Tags can be used to determine the membership of the machine in the services. Without changing the service configuration, the machine can be added to a service by adding the correct tags to the machine. ''; default = [ ]; apply = lib.unique; type = types.listOf types.str; }; deploy.targetHost = lib.mkOption { description = "SSH address of the host to deploy the machine to"; default = null; type = types.nullOr types.str; }; deploy.buildHost = lib.mkOption { description = "SSH address of the host to build the machine on"; default = null; type = types.nullOr types.str; }; }; } ) ]; }) ); }; instances = if config.noInstanceOptions then { } else lib.mkOption { description = "Multi host service module instances"; type = types.attrsOf ( types.submoduleWith { modules = [ ( { name, ... }: { options = { # ModuleSpec module = lib.mkOption { default = { }; type = types.submodule { options.input = lib.mkOption { type = types.nullOr types.str; default = null; defaultText = "Name of the input. Default to 'null' which means the module is local"; description = '' Name of the input. Default to 'null' which means the module is local ''; }; options.name = lib.mkOption { type = types.str; default = name; defaultText = ""; description = '' Attribute of the clan service module imported from the chosen input. Defaults to the name of the instance. ''; }; }; }; roles = lib.mkOption { default = { }; type = types.attrsOf ( types.submodule { imports = [ { _file = "inventory/interface"; _module.args = { inherit clanLib; }; } (import ./roles-interface.nix { }) ]; } ); }; }; } ) ]; } ); default = { }; }; }; }