idcoop/nixos_module.nix

199 lines
6.3 KiB
Nix

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"];
};
};
}