flake: { config, pkgs, ... }: let inherit (flake.packages.${pkgs.stdenv.hostPlatform.system}) idcoop; inherit (pkgs.lib) mkOption mkDefault mkIf types literalExpression mdDoc; inherit (pkgs.writers) writeTOML; inherit (builtins) mapAttrs; defaultUser = "idcoop"; cfg = config.services.idcoop; format = pkgs.formats.toml { }; oidcClientSubmodule = types.submodule { # freeformType = format.type; — one day we may want to enable freeform types, but for now just keep it strongly defined options = { name = mkOption { type = types.str; description = '' User-friendly name of the OIDC Client. ''; }; redirect_uris = mkOption { type = types.listOf types.str; description = '' List of redirect URIs that the client can use to redirect login attempts back to itself. Consult the documentation for the other service if you aren't sure. ''; }; allow_user_classes = mkOption { type = types.listOf types.str; description = '' List of user classes which are authorised (allowed) to use this client (access this service). As of idCoop v0.0.1, this setting is unimplemented and has no effect. ''; }; }; }; in { options.services.idcoop = { enable = mkOption { type = types.bool; default = false; description = '' Whether to enable idCoop, a simple identity provider. ''; }; configurePostgres = mkOption { type = types.bool; default = true; description = '' Whether to configure a Postgres database for idCoop. Enabled by default. ''; }; user = mkOption { type = types.str; default = defaultUser; description = '' User to run the service as. Will be created automatically if it is left at the default. ''; }; group = mkOption { type = types.str; default = defaultUser; description = '' User to run the service as. Will be created automatically if it is left at the default. ''; }; secretsPath = mkOption { type = types.path; default = builtins.toFile "blank.toml" ""; description = '' Path to a file containing secrets. This file should be kept out of the Nix store. Consult the idCoop documentation for the format of the secrets file. ''; }; settings = mkOption { default = {}; description = "idCoop configuration."; type = types.submodule { # freeformType = format.type; — one day we may want to enable freeform types, but for now just keep it strongly defined options = { listen = { bind = mkOption { type = types.str; default = "127.0.0.1:8072"; description = '' Host and port combination upon which to bind the web interface. ''; }; public_base_uri = mkOption { type = types.str; default = "http://${cfg.settings.listen.bind}"; defaultText = "`http://{listen.bind}`"; description = '' Public-facing HTTP(S) base URL. ''; }; }; oidc = { issuer = mkOption { type = types.str; default = cfg.settings.listen.public_base_uri; defaultText = "`listen.public_base_uri`"; description = '' The identity provider's 'issuer' identifier, as used in OpenID Connect. This should be configured in clients (relying parties) and should likely not be changed. ''; }; rsa_keypair = mkOption { type = types.path; description = '' Path to an RSA keypair used for signing JSON Web Tokens. ''; }; clients = mkOption { type = types.attrsOf oidcClientSubmodule; default = {}; description = '' OpenID Connect 'clients' (also known as relying parties). These entries are for the different services you want users to be able to log in to using idCoop. ''; }; }; postgres = { connect = mkOption { type = types.str; default = "postgres:"; description = mdDoc '' Connection string for the Postgres database. The default of `postgres:` uses the [libpq environment variables] to form a connection; usually this by default connects to the local UNIX socket with the current user's name as a username and database name, if no environment variables are set. [libpq environment variables]: https://www.postgresql.org/docs/current/libpq-envars.html ''; }; }; }; }; }; }; config = let configPath = writeTOML "idcoop_config.toml" cfg.settings; in { users.users.idcoop = mkIf (cfg.enable && cfg.user == defaultUser) { isSystemUser = true; group = cfg.group; home = mkDefault "/var/lib/idcoop"; createHome = true; packages = [ # Add a wrapper for the idcoop command so the user can use the CLI conveniently (pkgs.writeShellScriptBin "idcoop" '' IDCOOP_CONFIG=${pkgs.lib.escapeShellArg configPath} IDCOOP_SECRETS=${pkgs.lib.escapeShellArg cfg.secretsPath} exec ${idcoop}/bin/idcoop "$@" '') ]; }; users.groups.idcoop = mkIf (cfg.enable && cfg.group == defaultUser) {}; systemd.services.idcoop = mkIf cfg.enable { description = "idCoop: simple identity provider"; wantedBy = [ "multi-user.target" ]; after = [ "networking.target" "network-online.target" "postgresql.service" ]; serviceConfig = { ExecStart = "${idcoop}/bin/idcoop --config ${pkgs.lib.escapeShellArg configPath} --secrets ${pkgs.lib.escapeShellArg cfg.secretsPath} serve"; User = cfg.user; Group = cfg.group; }; }; services.postgresql = mkIf cfg.configurePostgres { ensureUsers = [ { name = "idcoop"; ensureDBOwnership = true; } ]; ensureDatabases = ["idcoop"]; }; }; }