{ lib, config, inputs, pkgs, ... }: let inherit (lib) mkOption types; inherit (lib.types) attrTag attrsOf deferredModuleWith functionTo nullOr optionType raw str submodule ; functionType = import ./function.nix; application-resources = submodule { options.resources = mkOption { # TODO: maybe transpose, and group the resources by type instead type = attrsOf ( attrTag ( lib.mapAttrs (_name: resource: mkOption { type = submodule resource.request; }) config.resources ) ); }; }; nixops4Deployment = types.deferredModuleWith { staticModules = [ inputs.nixops4.modules.nixops4Deployment.default { _class = "nixops4Deployment"; _module.args = { resourceProviderSystem = pkgs.system; resources = { }; }; } ]; }; nixos-configuration = mkOption { description = "A NixOS configuration."; type = raw; }; host-ssh = mkOption { description = "SSH connection info to connect to a single host."; type = submodule { options = { host = mkOption { description = "the host to access by SSH"; type = str; }; username = mkOption { description = "the SSH user to use"; type = nullOr str; default = null; }; key-file = mkOption { description = "path to the user's SSH private key"; type = nullOr str; example = "/root/.ssh/id_ed25519"; }; }; }; }; deployment-type = attrTag { ssh-host = mkOption { description = "A deployment by SSH to update a single existing NixOS host."; type = submodule (ssh-host: { options = { inherit nixos-configuration; ssh = host-ssh; module = mkOption { description = "The module to call to obtain the NixOS configuration from."; type = types.str; }; args = mkOption { description = "The arguments with which to call the module to obtain the NixOS configuration."; type = types.attrs; }; deployment-name = mkOption { description = "The name of the deployment for which to obtain the NixOS configuration."; type = types.str; }; root-path = mkOption { description = "The path to the root of the repository."; type = types.path; }; run = mkOption { # type = types.path; type = types.str; # readOnly = true; default = let # inherit (ssh-host.config) ssh nixos-configuration; inherit (ssh-host.config) ssh module args deployment-name root-path ; # inherit (ssh-host.config) ssh module args; inherit (ssh) host username key-file; # inherit (import ./nixos.nix { # # inherit system; # system = pkgs.system; # XXX recheck this is the right one # configuration = nixos-configuration; # # commandFn = outPath: ''''; # }) drv_path; # command environment = { # inherit (ssh-host) host username key-file; # inherit host username drv_path; key_file = key-file; deployment_name = deployment-name; root_path = root-path; system = pkgs.system; # XXX recheck this is the right one # config_nix = nixos-configuration; # config_tf = {}; # inherit host username; inherit host username module args # root_path ; deployment_type = "ssh-host"; # module = ; # args = ; # deployment_name = ; # deployment_type = ; # root_path = builtins.toString ./..; # root_path = ; }; in # error: cannot coerce a function to a string: «lambda mkNixosConfiguration @ /nix/store/ifj5ykvb5hv05m9qcr4r1aah4s4f9pdi-source/deployment/check/common/data-model.nix:106:15» # '' # env ${toString (lib.mapAttrsToList (k: v: lib.trace (if k == "config_nix" then v {} else k) "${k}='${v}'") environment)} ${./run/ssh-single-host/run.sh}"; # ''; # '' # env ${toString (lib.mapAttrsToList (k: v: lib.trace k "${k}=${lib.strings.toJSON v}") environment)} ${./run/ssh-single-host/run.sh}"; # ''; # if v == null then toString v else # lib.traceVal # '' # env ${toString (lib.mapAttrsToList (k: v: lib.trace k "${k}='${v}'") (lib.filterAttrs (_: v: v != null) environment))} ${./run/ssh-single-host/run.sh} # ''; lib.traceVal '' env ${ toString ( lib.mapAttrsToList ( k: v: lib.trace k "${k}=\"${ lib.replaceStrings [ "\"" ] [ "\\\\\"" ] ( if lib.isAttrs v then lib.strings.toJSON v else if lib.isPath v then toString v else v ) }\"" ) (lib.filterAttrs (_: v: v != null) environment) ) } bash ./deployment/run/ssh-single-host/run.sh ''; }; }; }); }; nixops4 = mkOption { description = "A NixOps4 NixOS deployment. For an example, see https://github.com/nixops4/nixops4-nixos/blob/main/example/deployment.nix."; type = nixops4Deployment; }; }; in { options = { resources = mkOption { description = "Collection of deployment resources that can be required by applications and policed by hosting providers"; type = attrsOf ( submodule ( { ... }: { _class = "fediversity-resource"; options = { description = mkOption { description = "Description of the resource to help application module authors and hosting providers to work with it"; type = types.str; }; request = mkOption { description = "Options for declaring resource requirements by an application, a description of how the resource is consumed or accessed"; type = deferredModuleWith { staticModules = [ { _class = "fediversity-resource-request"; } ]; }; }; policy = mkOption { description = "Options for configuring the resource policy for the hosting provider, a description of how the resource is made available"; type = deferredModuleWith { staticModules = [ (policy: { _class = "fediversity-resource-policy"; options.resource-type = mkOption { description = "The type of resource this policy configures"; type = types.optionType; }; # TODO(@fricklerhandwerk): we may want to make the function type explicit here: `application-resources -> resource-type` options.apply = mkOption { description = "Apply the policy to a request"; type = functionTo policy.config.resource-type; }; }) ]; }; }; }; } ) ); }; applications = mkOption { description = "Collection of Fediversity applications"; type = attrsOf ( submodule (application: { _class = "fediversity-application"; options = { description = mkOption { description = "Description to be shown in the application overview"; type = types.str; }; module = mkOption { description = "Operator-facing configuration options for the application"; type = deferredModuleWith { staticModules = [ { _class = "fediversity-application-config"; } ]; }; }; implementation = mkOption { description = "Mapping of application configuration to deployment resources, a description of what an application needs to run"; type = application.config.config-mapping.function-type; }; resources = mkOption { description = "Compute resources required by an application"; type = functionTo application.config.config-mapping.output-type; readOnly = true; default = input: (application.config.implementation input).output; }; # TODO(@fricklerhandwerk): this needs a better name, it's just the type config-mapping = mkOption { description = "Function type for the mapping from application configuration to required resources"; type = submodule functionType; readOnly = true; default = { input-type = submodule application.config.module; output-type = application-resources; }; }; }; }) ); }; environments = mkOption { description = "Run-time environments for Fediversity applications to be deployed to"; type = attrsOf ( submodule (environment: { _class = "fediversity-environment"; options = { resources = mkOption { description = '' Resources made available by the hosting provider, and their policies. Setting this is optional, but provides a place to declare that information for programmatic use in the resource mapping. ''; # TODO: maybe transpose, and group the resources by type instead type = attrsOf ( attrTag ( lib.mapAttrs (_name: resource: mkOption { type = submodule resource.policy; }) config.resources ) ); }; implementation = mkOption { description = "Mapping of resources required by applications to available resources; the result can be deployed"; type = environment.config.resource-mapping.function-type; }; resource-mapping = mkOption { description = "Function type for the mapping from resources to a deployment"; type = submodule functionType; readOnly = true; default = { input-type = submodule { options = { deployment-name = mkOption { type = types.str; }; root-path = mkOption { type = types.str; }; required-resources = mkOption { type = attrsOf application-resources; }; }; }; output-type = deployment-type; }; }; # TODO(@fricklerhandwerk): maybe this should be a separate thing such as `fediversity-setup`, # which makes explicit which applications and environments are available. # then the deployments can simply be the result of the function application baked into this module. deployment = mkOption { description = "Generate a deployment from a configuration, by applying an environment's resource policies to the applications' resource mappings"; # type = functionTo (functionTo (environment.config.resource-mapping.output-type)); type = functionTo (environment.config.resource-mapping.output-type); readOnly = true; default = { deployment-name, root-path, configuration, }: # deployment-name: cfg: # TODO: check cfg.enable.true let required-resources = lib.mapAttrs ( name: application-settings: config.applications.${name}.resources application-settings ) configuration.applications; in (environment.config.implementation { inherit required-resources deployment-name root-path; }) .output; }; }; }) ); }; configuration = mkOption { description = "Configuration type declaring options to be set by operators"; type = optionType; readOnly = true; default = submodule { options = { enable = lib.mkEnableOption { description = "your Fediversity configuration"; }; applications = lib.mapAttrs ( _name: application: mkOption { description = application.description; type = submodule application.module; default = { }; } ) config.applications; }; }; }; }; }