diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 743ae905388bd..d481dc3588910 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -571,6 +571,9 @@ ./services/editors/emacs.nix ./services/editors/haste.nix ./services/editors/infinoted.nix + ./services/finance/libeufin/bank.nix + ./services/finance/libeufin/module.nix + ./services/finance/libeufin/nexus.nix ./services/finance/odoo.nix ./services/finance/taler/exchange.nix ./services/finance/taler/merchant.nix diff --git a/nixos/modules/services/finance/libeufin/bank.nix b/nixos/modules/services/finance/libeufin/bank.nix new file mode 100644 index 0000000000000..77397d25fd528 --- /dev/null +++ b/nixos/modules/services/finance/libeufin/bank.nix @@ -0,0 +1,92 @@ +{ + lib, + config, + options, + ... +}: +{ + imports = [ (import ./common.nix "bank") ]; + + options.services.libeufin.bank = { + initialAccounts = lib.mkOption { + type = lib.types.listOf lib.types.attrs; + description = '' + Accounts to enable before the bank service starts. + + This is mainly needed for the nexus currency conversion + since the exchange's bank account is expected to be already + registered. + + Don't forget to change the account passwords afterwards. + ''; + default = [ ]; + }; + + settings = lib.mkOption { + description = '' + Configuration options for the libeufin bank system config file. + + For a list of all possible options, please see the man page [`libeufin-bank.conf(5)`](https://docs.taler.net/manpages/libeufin-bank.conf.5.html) + ''; + type = lib.types.submodule { + inherit (options.services.libeufin.settings.type.nestedTypes) freeformType; + options = { + libeufin-bank = { + CURRENCY = lib.mkOption { + type = lib.types.str; + description = '' + The currency under which the libeufin-bank should operate. + + This defaults to the GNU taler module's currency for convenience + but if you run libeufin-bank separately from taler, you must set + this yourself. + ''; + }; + PORT = lib.mkOption { + type = lib.types.port; + default = 8082; + description = '' + The port on which libeufin-bank should listen. + ''; + }; + SUGGESTED_WITHDRAWAL_EXCHANGE = lib.mkOption { + type = lib.types.str; + default = "https://exchange.demo.taler.net/"; + description = '' + Exchange that is suggested to wallets when withdrawing. + + Note that, in order for withdrawals to work, your libeufin-bank + must be able to communicate with and send money etc. to the bank + at which the exchange used for withdrawals has its bank account. + + If you also have your own bank and taler exchange network, you + probably want to set one of your exchange's url here instead of + the demo exchange. + + This setting must always be set in order for the Android app to + not crash during the withdrawal process but the exchange to be + used can always be changed in the app. + ''; + }; + }; + libeufin-bankdb-postgres = { + CONFIG = lib.mkOption { + type = lib.types.str; + description = '' + The database connection string for the libeufin-bank database. + ''; + }; + }; + }; + }; + }; + }; + + config = { + services.libeufin.bank.settings.libeufin-bank.CURRENCY = lib.mkIf ( + config.services.taler.enable && (config.services.taler.settings.taler ? CURRENCY) + ) config.services.taler.settings.taler.CURRENCY; + + services.libeufin.bank.settings.libeufin-bankdb-postgres.CONFIG = lib.mkIf config.services.libeufin.bank.createLocalDatabase "postgresql:///libeufin-bank"; + }; +} diff --git a/nixos/modules/services/finance/libeufin/common.nix b/nixos/modules/services/finance/libeufin/common.nix new file mode 100644 index 0000000000000..d5e72b8ad0c5b --- /dev/null +++ b/nixos/modules/services/finance/libeufin/common.nix @@ -0,0 +1,157 @@ +# TODO: create a common module generator for Taler and Libeufin? +libeufinComponent: +{ + lib, + pkgs, + config, + ... +}: +{ + options.services.libeufin.${libeufinComponent} = { + enable = lib.mkEnableOption "libeufin core banking system and web interface"; + package = lib.mkPackageOption pkgs "libeufin" { }; + debug = lib.mkEnableOption "debug logging"; + createLocalDatabase = lib.mkEnableOption "automatic creation of a local postgres database"; + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to open ports in the firewall"; + }; + }; + + config = + let + cfg = cfgMain.${libeufinComponent}; + cfgMain = config.services.libeufin; + + configFile = config.environment.etc."libeufin/libeufin.conf".source; + serviceName = "libeufin-${libeufinComponent}"; + isNexus = libeufinComponent == "nexus"; + + # get database name from config + # TODO: should this always be the same db? In which case, should this be an option directly under `services.libeufin`? + dbName = + lib.removePrefix "postgresql:///" + cfg.settings."libeufin-${libeufinComponent}db-postgres".CONFIG; + + bankPort = cfg.settings."${if isNexus then "nexus-httpd" else "libeufin-bank"}".PORT; + in + lib.mkIf cfg.enable { + services.libeufin.settings = cfg.settings; + + # TODO add system-libeufin.slice? + systemd.services = { + # Main service + "${serviceName}" = { + serviceConfig = { + DynamicUser = true; + ExecStart = + let + args = lib.cli.toGNUCommandLineShell { } { + c = configFile; + L = if cfg.debug then "debug" else null; + }; + in + "${lib.getExe' cfg.package "libeufin-${libeufinComponent}"} serve ${args}"; + Restart = "on-failure"; + RestartSec = "10s"; + }; + requires = [ "libeufin-dbinit.service" ]; + after = [ "libeufin-dbinit.service" ]; + wantedBy = [ "multi-user.target" ]; + }; + + # Database Initialisation + libeufin-dbinit = + let + dbScript = pkgs.writers.writeText "libeufin-db-permissions.sql" '' + GRANT SELECT,INSERT,UPDATE,DELETE ON ALL TABLES IN SCHEMA libeufin_bank TO "${serviceName}"; + GRANT SELECT,INSERT,UPDATE,DELETE ON ALL TABLES IN SCHEMA libeufin_nexus TO "${serviceName}"; + GRANT USAGE ON SCHEMA libeufin_bank TO "${serviceName}"; + GRANT USAGE ON SCHEMA libeufin_nexus TO "${serviceName}"; + ''; + + # Accounts to be created after the bank database initialization. + # + # For example, if the bank's currency conversion is enabled, it's + # required that the exchange account is registered before the + # service starts. + initialAccountRegistration = lib.concatMapStringsSep "\n" ( + account: + let + args = lib.cli.toGNUCommandLineShell { } { + c = configFile; + inherit (account) username password name; + payto_uri = "payto://x-taler-bank/bank:${toString bankPort}/${account.username}?receiver-name=${account.name}"; + exchange = lib.toLower account.username == "exchange"; + }; + in + "${lib.getExe' cfg.package "libeufin-bank"} create-account ${args}" + ) cfg.initialAccounts; + + args = lib.cli.toGNUCommandLineShell { } { + c = configFile; + L = if cfg.debug then "debug" else null; + }; + in + { + path = [ config.services.postgresql.package ]; + serviceConfig = { + Type = "oneshot"; + DynamicUser = true; + StateDirectory = "libeufin-dbinit"; + StateDirectoryMode = "0750"; + User = dbName; + }; + script = lib.optionalString cfg.enable '' + ${lib.getExe' cfg.package "libeufin-${libeufinComponent}"} dbinit ${args} + ''; + # Grant DB permissions after schemas have been created + postStart = + '' + psql -U "${dbName}" -f "${dbScript}" + '' + + lib.optionalString ((!isNexus) && (cfg.initialAccounts != [ ])) '' + # only register initial accounts once + if [ ! -e /var/lib/libeufin-dbinit/init ]; then + ${initialAccountRegistration} + + touch /var/lib/libeufin-dbinit/init + echo "Bank initialisation complete" + fi + ''; + requires = lib.optionals cfg.createLocalDatabase [ "postgresql.service" ]; + after = [ "network.target" ] ++ lib.optionals cfg.createLocalDatabase [ "postgresql.service" ]; + }; + }; + + networking.firewall = lib.mkIf cfg.openFirewall { + allowedTCPPorts = [ + bankPort + ]; + }; + + environment.systemPackages = [ cfg.package ]; + + services.postgresql = lib.mkIf cfg.createLocalDatabase { + enable = true; + ensureDatabases = [ dbName ]; + ensureUsers = [ + { name = serviceName; } + { + name = dbName; + ensureDBOwnership = true; + } + ]; + }; + + assertions = [ + { + assertion = + cfg.createLocalDatabase || (cfg.settings."libeufin-${libeufinComponent}db-postgres" ? CONFIG); + message = "Libeufin ${libeufinComponent} database is not configured."; + } + ]; + + }; +} diff --git a/nixos/modules/services/finance/libeufin/module.nix b/nixos/modules/services/finance/libeufin/module.nix new file mode 100644 index 0000000000000..1e78e93df3d3c --- /dev/null +++ b/nixos/modules/services/finance/libeufin/module.nix @@ -0,0 +1,26 @@ +{ + lib, + pkgs, + config, + ... +}: + +let + cfg = config.services.libeufin; + settingsFormat = pkgs.formats.ini { }; + configFile = settingsFormat.generate "generated-libeufin.conf" cfg.settings; +in + +{ + options.services.libeufin = { + settings = lib.mkOption { + description = "Global configuration options for the libeufin bank system config file."; + type = lib.types.submodule { freeformType = settingsFormat.type; }; + default = { }; + }; + }; + + config = lib.mkIf (cfg.bank.enable || cfg.nexus.enable) { + environment.etc."libeufin/libeufin.conf".source = configFile; + }; +} diff --git a/nixos/modules/services/finance/libeufin/nexus.nix b/nixos/modules/services/finance/libeufin/nexus.nix new file mode 100644 index 0000000000000..19f877ba6b1b3 --- /dev/null +++ b/nixos/modules/services/finance/libeufin/nexus.nix @@ -0,0 +1,131 @@ +{ + lib, + config, + options, + ... +}: +{ + imports = [ (import ./common.nix "nexus") ]; + + options.services.libeufin.nexus.settings = lib.mkOption { + description = '' + Configuration options for the libeufin nexus config file. + + For a list of all possible options, please see the man page [`libeufin-nexus.conf(5)`](https://docs.taler.net/manpages/libeufin-nexus.conf.5.html) + ''; + type = lib.types.submodule { + inherit (options.services.libeufin.settings.type.nestedTypes) freeformType; + options = { + nexus-ebics = { + # Mandatory configuration values + # https://docs.taler.net/libeufin/nexus-manual.html#setting-up-the-ebics-subscriber + # https://docs.taler.net/libeufin/setup-ebics-at-postfinance.html + CURRENCY = lib.mkOption { + description = "Name of the fiat currency."; + type = lib.types.nonEmptyStr; + example = "CHF"; + }; + HOST_BASE_URL = lib.mkOption { + description = "URL of the EBICS server."; + type = lib.types.nonEmptyStr; + example = "https://ebics.postfinance.ch/ebics/ebics.aspx"; + }; + BANK_DIALECT = lib.mkOption { + description = '' + Name of the following combination: EBICS version and ISO20022 + recommendations that Nexus would honor in the communication with the + bank. + + Currently only the "postfinance" or "gls" value is supported. + ''; + type = lib.types.enum [ + "postfinance" + "gls" + ]; + example = "postfinance"; + }; + HOST_ID = lib.mkOption { + description = "Name of the EBICS host."; + type = lib.types.nonEmptyStr; + example = "PFEBICS"; + }; + USER_ID = lib.mkOption { + description = '' + User ID of the EBICS subscriber. + + This value must be assigned by the bank after having activated a new EBICS subscriber. + ''; + type = lib.types.nonEmptyStr; + example = "PFC00563"; + }; + PARTNER_ID = lib.mkOption { + description = '' + Partner ID of the EBICS subscriber. + + This value must be assigned by the bank after having activated a new EBICS subscriber. + ''; + type = lib.types.nonEmptyStr; + example = "PFC00563"; + }; + IBAN = lib.mkOption { + description = "IBAN of the bank account that is associated with the EBICS subscriber."; + type = lib.types.nonEmptyStr; + example = "CH7789144474425692816"; + }; + BIC = lib.mkOption { + description = "BIC of the bank account that is associated with the EBICS subscriber."; + type = lib.types.nonEmptyStr; + example = "POFICHBEXXX"; + }; + NAME = lib.mkOption { + description = "Legal entity that is associated with the EBICS subscriber."; + type = lib.types.nonEmptyStr; + example = "John Smith S.A."; + }; + BANK_PUBLIC_KEYS_FILE = lib.mkOption { + type = lib.types.path; + default = "/var/lib/libeufin-nexus/bank-ebics-keys.json"; + description = '' + Filesystem location where Nexus should store the bank public keys. + ''; + }; + CLIENT_PRIVATE_KEYS_FILE = lib.mkOption { + type = lib.types.path; + default = "/var/lib/libeufin-nexus/client-ebics-keys.json"; + description = '' + Filesystem location where Nexus should store the subscriber private keys. + ''; + }; + }; + nexus-httpd = { + PORT = lib.mkOption { + type = lib.types.port; + default = 8084; + description = '' + The port on which libeufin-bank should listen. + ''; + }; + }; + libeufin-nexusdb-postgres = { + CONFIG = lib.mkOption { + type = lib.types.str; + description = '' + The database connection string for the libeufin-nexus database. + ''; + }; + }; + }; + }; + }; + + config = + let + cfgMain = config.services.libeufin; + cfg = config.services.libeufin.nexus; + in + lib.mkIf cfg.enable { + services.libeufin.nexus.settings.libeufin-nexusdb-postgres.CONFIG = lib.mkIf ( + cfgMain.bank.enable && cfgMain.bank.createLocalDatabase + ) "postgresql:///libeufin-bank"; + }; +}