{ config, lib, shb, ... }: let cfg = config.shb.firefly-iii; in { imports = [ ../blocks/nginx.nix ../blocks/lldap.nix ../../lib/module.nix ]; options.shb.firefly-iii = { enable = lib.mkEnableOption "SHB's firefly-iii module"; subdomain = lib.mkOption { type = lib.types.str; description = '' Subdomain under which firefly-iii will be served. ``` . ``` ''; example = "firefly-iii"; }; domain = lib.mkOption { description = '' Domain under which firefly-iii is served. ``` .[:] ``` ''; type = lib.types.str; example = "domain.com"; }; ssl = lib.mkOption { description = "Path to SSL files"; type = lib.types.nullOr shb.contracts.ssl.certs; default = null; }; siteOwnerEmail = lib.mkOption { description = "Email of the site owner."; type = lib.types.str; example = "mail@example.com"; }; impermanence = lib.mkOption { description = '' Path to save when using impermanence setup. ''; type = lib.types.str; default = config.services.firefly-iii.dataDir; defaultText = "services.firefly-iii.dataDir"; }; backup = lib.mkOption { description = '' Backup configuration. ''; default = { }; type = lib.types.submodule { options = shb.contracts.backup.mkRequester { user = config.services.firefly-iii.user; userText = "services.firefly-iii.user"; sourceDirectories = [ config.services.firefly-iii.dataDir ]; sourceDirectoriesText = '' [ config.services.firefly-iii.dataDir ] ''; }; }; }; appKey = lib.mkOption { description = "Encryption key used for sessions. Must be 32 characters long exactly."; type = lib.types.submodule { options = shb.contracts.secret.mkRequester { mode = "0400"; owner = config.services.firefly-iii.user; ownerText = "services.firefly-iii.user"; restartUnits = [ "firefly-iii-setup.service" ]; }; }; }; dbPassword = lib.mkOption { description = "DB password."; type = lib.types.submodule { options = shb.contracts.secret.mkRequester { mode = "0440"; owner = config.services.firefly-iii.user; ownerText = "services.firefly-iii.user"; group = "postgres"; restartUnits = [ "postgresql.service" "firefly-iii-setup.service" ]; }; }; }; ldap = lib.mkOption { description = '' LDAP Integration ''; default = { }; type = lib.types.submodule { options = { userGroup = lib.mkOption { type = lib.types.str; description = "Group users must belong to to be able to login to Firefly-iii."; default = "firefly-iii_user"; }; adminGroup = lib.mkOption { type = lib.types.str; description = "Group users must belong to to be able to import data user the Firefly-iii data importer."; default = "firefly-iii_admin"; }; }; }; }; sso = lib.mkOption { description = '' SSO Integration ''; default = { }; type = lib.types.submodule { options = { enable = lib.mkEnableOption "SSO integration."; authEndpoint = lib.mkOption { type = lib.types.str; description = "OIDC endpoint for SSO."; example = "https://authelia.example.com"; }; port = lib.mkOption { description = "If given, adds a port to the endpoint."; type = lib.types.nullOr lib.types.port; default = null; }; provider = lib.mkOption { type = lib.types.enum [ "Authelia" ]; description = "OIDC provider name, used for display."; default = "Authelia"; }; clientID = lib.mkOption { type = lib.types.str; description = "Client ID for the OIDC endpoint."; default = "firefly-iii"; }; authorization_policy = lib.mkOption { type = lib.types.enum [ "one_factor" "two_factor" ]; description = "Require one factor (password) or two factor (device) authentication."; default = "one_factor"; }; adminGroup = lib.mkOption { type = lib.types.str; description = "Group admins must belong to to be able to login to Firefly-iii."; default = "firefly-iii_admin"; }; secret = lib.mkOption { description = "OIDC shared secret."; type = lib.types.submodule { options = shb.contracts.secret.mkRequester { mode = "0400"; owner = "firefly-iii"; restartUnits = [ "firefly-iii-setup.service" ]; }; }; }; secretForAuthelia = lib.mkOption { description = "OIDC shared secret. Content must be the same as `secretFile` option."; type = lib.types.submodule { options = shb.contracts.secret.mkRequester { mode = "0400"; owner = "authelia"; }; }; }; }; }; }; smtp = lib.mkOption { description = '' If set, send notifications through smtp. https://docs.firefly-iii.org/how-to/firefly-iii/advanced/notifications/ ''; default = null; type = lib.types.nullOr ( lib.types.submodule { options = { from_address = lib.mkOption { type = lib.types.str; description = "SMTP address from which the emails originate."; example = "authelia@mydomain.com"; }; host = lib.mkOption { type = lib.types.str; description = "SMTP host to send the emails to."; }; port = lib.mkOption { type = lib.types.port; description = "SMTP port to send the emails to."; default = 25; }; username = lib.mkOption { type = lib.types.str; description = "Username to connect to the SMTP host."; }; password = lib.mkOption { description = "File containing the password to connect to the SMTP host."; type = lib.types.submodule { options = shb.contracts.secret.mkRequester { mode = "0400"; owner = config.services.firefly-iii.user; ownerText = "services.firefly-iii.user"; restartUnits = [ "firefly-iii-setup.service" ]; }; }; }; }; } ); }; debug = lib.mkOption { type = lib.types.bool; description = "Enable more verbose logging."; default = false; example = true; }; importer = lib.mkOption { description = '' Configuration for Firefly-iii data importer. ''; default = { }; type = lib.types.submodule { options = { enable = lib.mkEnableOption "Firefly-iii Data Importer." // { default = true; }; subdomain = lib.mkOption { type = lib.types.str; description = '' Subdomain under which the firefly-iii data importer will be served. ''; default = "${cfg.subdomain}-importer"; defaultText = lib.literalExpression "\${shb.firefly-iii.subdomain}-importer"; }; firefly-iii-accessToken = lib.mkOption { type = lib.types.nullOr ( lib.types.submodule { options = shb.contracts.secret.mkRequester { mode = "0400"; owner = config.services.firefly-iii-data-importer.user; ownerText = "services.firefly-iii-data-importer.user"; restartUnits = [ "firefly-iii-data-importer-setup.service" ]; }; } ); description = '' Create a Personal Access Token then set then token in this option. ''; default = null; }; }; }; }; dashboard = lib.mkOption { description = '' Dashboard contract consumer ''; default = { }; type = lib.types.submodule { options = shb.contracts.dashboard.mkRequester { externalUrl = "https://${cfg.subdomain}.${cfg.domain}"; externalUrlText = "https://\${config.shb.firefly-iii.subdomain}.\${config.shb.firefly-iii.domain}"; # This works thanks to the Personal Access Token. internalUrl = "https://${cfg.subdomain}.${cfg.domain}"; internalUrlText = "https://\${config.shb.firefly-iii.subdomain}.\${config.shb.firefly-iii.domain}"; }; }; }; }; config = lib.mkIf cfg.enable ( lib.mkMerge [ { services.firefly-iii = { enable = true; group = "nginx"; virtualHost = "${cfg.subdomain}.${cfg.domain}"; # https://github.com/firefly-iii/firefly-iii/blob/main/.env.example settings = { APP_ENV = "production"; APP_URL = "https://${cfg.subdomain}.${cfg.domain}"; APP_DEBUG = cfg.debug; APP_LOG_LEVEL = if cfg.debug then "debug" else "notice"; LOG_CHANNEL = "stdout"; APP_KEY_FILE = cfg.appKey.result.path; SITE_OWNER = cfg.siteOwnerEmail; DB_CONNECTION = "pgsql"; DB_HOST = "localhost"; DB_PORT = config.services.postgresql.settings.port; DB_DATABASE = "firefly-iii"; DB_USERNAME = "firefly-iii"; DB_PASSWORD_FILE = cfg.dbPassword.result.path; # MAP_DEFAULT_LAT = "51.983333"; # MAP_DEFAULT_LONG = "5.916667"; # MAP_DEFAULT_ZOOM = "6"; }; }; shb.postgresql.enableTCPIP = true; shb.postgresql.ensures = [ { username = "firefly-iii"; database = "firefly-iii"; passwordFile = cfg.dbPassword.result.path; } ]; # This should be using a contract instead of setting the option directly. shb.lldap = lib.mkIf config.shb.lldap.enable { ensureGroups = { ${cfg.ldap.userGroup} = { }; ${cfg.ldap.adminGroup} = { }; }; }; # We enable the firefly-iii nginx integration and merge it with SHB's nginx configuration. services.firefly-iii.enableNginx = true; shb.nginx.vhosts = [ { inherit (cfg) subdomain domain ssl; } ]; } (lib.mkIf cfg.importer.enable { services.firefly-iii-data-importer = { enable = true; virtualHost = "${cfg.importer.subdomain}.${cfg.domain}"; settings = { FIREFLY_III_URL = "https://${config.services.firefly-iii.virtualHost}"; } // lib.optionalAttrs (cfg.importer.firefly-iii-accessToken != null) { FIREFLY_III_ACCESS_TOKEN_FILE = cfg.importer.firefly-iii-accessToken.result.path; }; }; # We enable the firefly-iii-data-importer nginx integration and merge it with SHB's nginx configuration. services.firefly-iii-data-importer.enableNginx = true; shb.nginx.vhosts = [ { inherit (cfg) domain ssl; subdomain = cfg.importer.subdomain; } ]; }) (lib.mkIf (cfg.smtp != null) { services.firefly-iii.settings = { MAIL_MAILER = "smtp"; MAIL_HOST = cfg.smtp.host; MAIL_PORT = cfg.smtp.port; MAIL_FROM = cfg.smtp.from_address; MAIL_USERNAME = cfg.smtp.username; MAIL_PASSWORD_FILE = cfg.smtp.password.result.path; MAIL_ENCRYPTION = "tls"; }; }) (lib.mkIf cfg.sso.enable { services.firefly-iii.settings = { AUTHENTICATION_GUARD = "remote_user_guard"; AUTHENTICATION_GUARD_HEADER = "HTTP_X_FORWARDED_USER"; }; shb.nginx.vhosts = [ { inherit (cfg) subdomain domain ssl; inherit (cfg.sso) authEndpoint; phpForwardAuth = true; autheliaRules = [ { domain = "${cfg.subdomain}.${cfg.domain}"; policy = "bypass"; resources = [ "^/api" ]; } { domain = "${cfg.subdomain}.${cfg.domain}"; policy = cfg.sso.authorization_policy; subject = [ "group:${cfg.ldap.userGroup}" "group:${cfg.ldap.adminGroup}" ]; } ]; } ]; }) (lib.mkIf (cfg.sso.enable && cfg.importer.enable) { shb.nginx.vhosts = [ { inherit (cfg.importer) subdomain; inherit (cfg) domain ssl; inherit (cfg.sso) authEndpoint; autheliaRules = [ { domain = "${cfg.importer.subdomain}.${cfg.domain}"; policy = cfg.sso.authorization_policy; subject = [ "group:${cfg.ldap.adminGroup}" ]; } ]; } ]; }) ] ); }