From d88dd0a45ca00ece8e81af355decc01e7b5daf44 Mon Sep 17 00:00:00 2001 From: Daniel Olsen Date: Thu, 14 Nov 2024 22:35:44 +0100 Subject: [PATCH] DROP WHEN MERGED: taler modules from #332699 --- .../pvvvvvv/modules/libeufin/bank.nix | 93 +++++++++++ .../pvvvvvv/modules/libeufin/common.nix | 156 ++++++++++++++++++ .../pvvvvvv/modules/libeufin/module.nix | 29 ++++ .../pvvvvvv/modules/libeufin/nexus.nix | 127 ++++++++++++++ .../services/pvvvvvv/modules/module-list.nix | 10 ++ .../services/pvvvvvv/modules/taler/common.nix | 117 +++++++++++++ .../pvvvvvv/modules/taler/exchange.nix | 143 ++++++++++++++++ .../pvvvvvv/modules/taler/merchant.nix | 108 ++++++++++++ .../services/pvvvvvv/modules/taler/module.nix | 89 ++++++++++ 9 files changed, 872 insertions(+) create mode 100644 hosts/kvernberg/services/pvvvvvv/modules/libeufin/bank.nix create mode 100644 hosts/kvernberg/services/pvvvvvv/modules/libeufin/common.nix create mode 100644 hosts/kvernberg/services/pvvvvvv/modules/libeufin/module.nix create mode 100644 hosts/kvernberg/services/pvvvvvv/modules/libeufin/nexus.nix create mode 100644 hosts/kvernberg/services/pvvvvvv/modules/module-list.nix create mode 100644 hosts/kvernberg/services/pvvvvvv/modules/taler/common.nix create mode 100644 hosts/kvernberg/services/pvvvvvv/modules/taler/exchange.nix create mode 100644 hosts/kvernberg/services/pvvvvvv/modules/taler/merchant.nix create mode 100644 hosts/kvernberg/services/pvvvvvv/modules/taler/module.nix diff --git a/hosts/kvernberg/services/pvvvvvv/modules/libeufin/bank.nix b/hosts/kvernberg/services/pvvvvvv/modules/libeufin/bank.nix new file mode 100644 index 0000000..6211eb3 --- /dev/null +++ b/hosts/kvernberg/services/pvvvvvv/modules/libeufin/bank.nix @@ -0,0 +1,93 @@ +{ + 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/hosts/kvernberg/services/pvvvvvv/modules/libeufin/common.nix b/hosts/kvernberg/services/pvvvvvv/modules/libeufin/common.nix new file mode 100644 index 0000000..1b2857b --- /dev/null +++ b/hosts/kvernberg/services/pvvvvvv/modules/libeufin/common.nix @@ -0,0 +1,156 @@ +# 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 + cfgMain = config.services.libeufin; + cfg = cfgMain.${libeufinComponent}; + + 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 = cfgMain.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 = cfgMain.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 = cfgMain.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/hosts/kvernberg/services/pvvvvvv/modules/libeufin/module.nix b/hosts/kvernberg/services/pvvvvvv/modules/libeufin/module.nix new file mode 100644 index 0000000..f15ba97 --- /dev/null +++ b/hosts/kvernberg/services/pvvvvvv/modules/libeufin/module.nix @@ -0,0 +1,29 @@ +{ + lib, + pkgs, + config, + ... +}: + +let + cfg = config.services.libeufin; + settingsFormat = pkgs.formats.ini { }; +in + +{ + options.services.libeufin = { + configFile = lib.mkOption { + internal = true; + default = settingsFormat.generate "generated-libeufin.conf" cfg.settings; + }; + 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 = cfg.configFile; + }; +} diff --git a/hosts/kvernberg/services/pvvvvvv/modules/libeufin/nexus.nix b/hosts/kvernberg/services/pvvvvvv/modules/libeufin/nexus.nix new file mode 100644 index 0000000..04504f5 --- /dev/null +++ b/hosts/kvernberg/services/pvvvvvv/modules/libeufin/nexus.nix @@ -0,0 +1,127 @@ +{ + 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"; + }; +} diff --git a/hosts/kvernberg/services/pvvvvvv/modules/module-list.nix b/hosts/kvernberg/services/pvvvvvv/modules/module-list.nix new file mode 100644 index 0000000..f77a20b --- /dev/null +++ b/hosts/kvernberg/services/pvvvvvv/modules/module-list.nix @@ -0,0 +1,10 @@ +{ + imports = [ + ./libeufin/bank.nix + ./libeufin/module.nix + ./libeufin/nexus.nix + ./taler/exchange.nix + ./taler/merchant.nix + ./taler/module.nix + ]; +} diff --git a/hosts/kvernberg/services/pvvvvvv/modules/taler/common.nix b/hosts/kvernberg/services/pvvvvvv/modules/taler/common.nix new file mode 100644 index 0000000..a901828 --- /dev/null +++ b/hosts/kvernberg/services/pvvvvvv/modules/taler/common.nix @@ -0,0 +1,117 @@ +# TODO: create a common module generator for Taler and Libeufin? +{ + talerComponent ? "", + servicesDB ? [ ], + servicesNoDB ? [ ], + ... +}: +{ + lib, + pkgs, + config, + ... +}: +let + cfg = cfgTaler.${talerComponent}; + cfgTaler = config.services.taler; + settingsFormat = pkgs.formats.ini { }; + + services = servicesDB ++ servicesNoDB; + + dbName = "taler-${talerComponent}-httpd"; + groupName = "taler-${talerComponent}-services"; + + inherit (cfgTaler) runtimeDir; +in +{ + options = { + services.taler.${talerComponent} = { + enable = lib.mkEnableOption "the GNU Taler ${talerComponent}"; + package = lib.mkPackageOption pkgs "taler-${talerComponent}" { }; + # TODO: make option accept multiple debugging levels? + debug = lib.mkEnableOption "debug logging"; + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to open ports in the firewall"; + }; + }; + }; + + config = lib.mkIf cfg.enable { + services.taler.enable = cfg.enable; + + systemd.services = lib.mergeAttrsList [ + # Main services + (lib.genAttrs (map (n: "taler-${talerComponent}-${n}") services) (name: { + serviceConfig = { + DynamicUser = true; + User = name; + Group = groupName; + ExecStart = toString [ + (lib.getExe' cfg.package name) + "-c /etc/taler/taler.conf" + (lib.optionalString cfg.debug " -L debug") + ]; + RuntimeDirectory = name; + StateDirectory = name; + CacheDirectory = name; + ReadWritePaths = [ runtimeDir ]; + Restart = "always"; + RestartSec = "10s"; + }; + requires = [ "taler-${talerComponent}-dbinit.service" ]; + after = [ "taler-${talerComponent}-dbinit.service" ]; + wantedBy = [ "multi-user.target" ]; # TODO slice? + })) + # Database Initialisation + { + "taler-${talerComponent}-dbinit" = { + path = [ config.services.postgresql.package ]; + serviceConfig = { + Type = "oneshot"; + DynamicUser = true; + User = dbName; + Restart = "on-failure"; + RestartSec = "5s"; + }; + requires = [ "postgresql.service" ]; + after = [ "postgresql.service" ]; + }; + } + ]; + + users.groups.${groupName} = { }; + systemd.tmpfiles.settings = { + "10-taler-${talerComponent}" = { + "${runtimeDir}" = { + d = { + group = groupName; + user = "nobody"; + mode = "070"; + }; + }; + }; + }; + + networking.firewall = lib.mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.settings."${talerComponent}".PORT ]; + }; + + environment.systemPackages = [ cfg.package ]; + + services.taler.includes = [ "/etc/taler/conf.d/${talerComponent}.conf" ]; + environment.etc."taler/conf.d/${talerComponent}.conf".source = settingsFormat.generate "generated-taler.conf" cfg.settings; + + services.postgresql = { + enable = true; + ensureDatabases = [ dbName ]; + ensureUsers = map (service: { name = "taler-${talerComponent}-${service}"; }) servicesDB ++ [ + { + name = dbName; + ensureDBOwnership = true; + } + ]; + }; + }; +} diff --git a/hosts/kvernberg/services/pvvvvvv/modules/taler/exchange.nix b/hosts/kvernberg/services/pvvvvvv/modules/taler/exchange.nix new file mode 100644 index 0000000..0395090 --- /dev/null +++ b/hosts/kvernberg/services/pvvvvvv/modules/taler/exchange.nix @@ -0,0 +1,143 @@ +{ + lib, + config, + options, + pkgs, + ... +}: + +let + cfg = cfgTaler.exchange; + cfgTaler = config.services.taler; + + talerComponent = "exchange"; + + # https://docs.taler.net/taler-exchange-manual.html#services-users-groups-and-file-system-hierarchy + servicesDB = [ + "httpd" + "aggregator" + "closer" + "wirewatch" + ]; + + servicesNoDB = [ + "secmod-cs" + "secmod-eddsa" + "secmod-rsa" + ]; +in + +{ + imports = [ + (import ./common.nix { inherit talerComponent servicesDB servicesNoDB; }) + ]; + + options.services.taler.exchange = { + settings = lib.mkOption { + description = '' + Configuration options for the taler exchange config file. + For a list of all possible options, please see the man page [`taler.conf(5)`](https://docs.taler.net/manpages/taler.conf.5.html#exchange-options) + ''; + type = lib.types.submodule { + inherit (options.services.taler.settings.type.nestedTypes) freeformType; + options = { + # TODO: do we want this to be a sub-attribute or only define the exchange set of options here + exchange = { + AML_THRESHOLD = lib.mkOption { + type = lib.types.str; + default = "${cfgTaler.settings.taler.CURRENCY}:1000000"; + defaultText = "1000000 in {option}`CURRENCY`"; + description = "Monthly transaction volume until an account is considered suspicious and flagged for AML review."; + }; + DB = lib.mkOption { + type = lib.types.str; + internal = true; + default = "postgres"; + }; + MASTER_PUBLIC_KEY = lib.mkOption { + type = lib.types.str; + default = throw '' + You must provide `MASTER_PUBLIC_KEY` with the public part of your master key. + This will be used by the auditor service to get information about the exchange. + For more information, see https://docs.taler.net/taler-auditor-manual.html#initial-configuration + To generate this key, you must run `taler-exchange-offline setup`. It will print the public key. + ''; + defaultText = "None, you must set this yourself."; + description = "Used by the exchange to verify information signed by the offline system."; + }; + PORT = lib.mkOption { + type = lib.types.port; + default = 8081; + description = "Port on which the HTTP server listens."; + }; + }; + exchangedb-postgres = { + CONFIG = lib.mkOption { + type = lib.types.str; + internal = true; + default = "postgres:///taler-exchange-httpd"; + description = "Database connection URI."; + }; + }; + }; + }; + default = { }; + }; + denominationConfig = lib.mkOption { + type = lib.types.lines; + defaultText = "None, you must set this yourself."; + example = '' + [COIN-KUDOS-n1-t1718140083] + VALUE = KUDOS:0.1 + DURATION_WITHDRAW = 7 days + DURATION_SPEND = 2 years + DURATION_LEGAL = 6 years + FEE_WITHDRAW = KUDOS:0 + FEE_DEPOSIT = KUDOS:0.1 + FEE_REFRESH = KUDOS:0 + FEE_REFUND = KUDOS:0 + RSA_KEYSIZE = 2048 + CIPHER = RSA + ''; + description = '' + This option configures the cash denomination for the coins that the exchange offers. + For more information, consult the [upstream docs](https://docs.taler.net/taler-exchange-manual.html#coins-denomination-keys). + You can either write these manually or you can use the `taler-harness deployment gen-coin-config` + command to generate it. + Warning: Do not modify existing denominations after deployment. + Please see the upstream docs for how to safely do that. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + services.taler.includes = [ + (pkgs.writers.writeText "exchange-denominations.conf" cfg.denominationConfig) + ]; + + systemd.services.taler-exchange-wirewatch = { + requires = [ "taler-exchange-httpd.service" ]; + after = [ "taler-exchange-httpd.service" ]; + }; + + # Taken from https://docs.taler.net/taler-exchange-manual.html#exchange-database-setup + # TODO: Why does aggregator need DELETE? + systemd.services."taler-${talerComponent}-dbinit".script = + let + deletePerm = name: lib.optionalString (name == "aggregator") ",DELETE"; + dbScript = pkgs.writers.writeText "taler-exchange-db-permissions.sql" ( + lib.pipe servicesDB [ + (map (name: '' + GRANT SELECT,INSERT,UPDATE${deletePerm name} ON ALL TABLES IN SCHEMA exchange TO "taler-exchange-${name}"; + GRANT USAGE ON SCHEMA exchange TO "taler-exchange-${name}"; + '')) + lib.concatStrings + ] + ); + in + '' + ${lib.getExe' cfg.package "taler-exchange-dbinit"} + psql -U taler-exchange-httpd -f ${dbScript} + ''; + }; +} diff --git a/hosts/kvernberg/services/pvvvvvv/modules/taler/merchant.nix b/hosts/kvernberg/services/pvvvvvv/modules/taler/merchant.nix new file mode 100644 index 0000000..df41ff6 --- /dev/null +++ b/hosts/kvernberg/services/pvvvvvv/modules/taler/merchant.nix @@ -0,0 +1,108 @@ +{ + lib, + config, + options, + pkgs, + ... +}: +let + cfg = cfgTaler.merchant; + cfgTaler = config.services.taler; + + talerComponent = "merchant"; + + # https://docs.taler.net/taler-merchant-manual.html#launching-the-backend + servicesDB = [ + "httpd" + "webhook" + "wirewatch" + "depositcheck" + "exchange" + ]; +in +{ + imports = [ + (import ./common.nix { inherit talerComponent servicesDB; }) + ]; + + options.services.taler.merchant = { + settings = lib.mkOption { + description = '' + Configuration options for the taler merchant config file. + For a list of all possible options, please see the man page [`taler.conf(5)`](https://docs.taler.net/manpages/taler.conf.5.html#merchant-options) + ''; + type = lib.types.submodule { + inherit (options.services.taler.settings.type.nestedTypes) freeformType; + options = { + # TODO: do we want this to be a sub-attribute or only define the merchant set of options here + merchant = { + DB = lib.mkOption { + type = lib.types.str; + internal = true; + default = "postgres"; + description = "Plugin to use for the database."; + }; + PORT = lib.mkOption { + type = lib.types.port; + default = 8083; + description = "Port on which the HTTP server listens."; + }; + SERVE = lib.mkOption { + type = lib.types.str; + default = "tcp"; + description = '' + Whether the HTTP server should listen on a UNIX domain socket ("unix") or on a TCP socket ("tcp"). + ''; + }; + LEGAL_PRESERVATION = lib.mkOption { + type = lib.types.str; + internal = true; + default = "10 years"; + description = "How long to keep data in the database for tax audits after the transaction has completed."; + }; + }; + merchantdb-postgres = { + CONFIG = lib.mkOption { + type = lib.types.str; + internal = true; + default = "postgres:///taler-${talerComponent}-httpd"; + description = "Database connection URI."; + }; + SQL_DIR = lib.mkOption { + type = lib.types.str; + internal = true; + default = "${cfg.package}/share/taler/sql/merchant/"; + description = "The location for the SQL files to setup the database tables."; + }; + }; + }; + }; + default = { }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.taler-merchant-depositcheck = { + # taler-merchant-depositcheck needs its executable is in the PATH + # NOTE: couldn't use `lib.getExe` to only get that single executable + path = [ cfg.package ]; + }; + + systemd.services."taler-${talerComponent}-dbinit".script = + let + # NOTE: not documented, but is necessary + dbScript = pkgs.writers.writeText "taler-merchant-db-permissions.sql" ( + lib.concatStrings ( + map (name: '' + GRANT SELECT,INSERT,UPDATE,DELETE ON ALL TABLES IN SCHEMA merchant TO "taler-merchant-${name}"; + GRANT USAGE ON SCHEMA merchant TO "taler-merchant-${name}"; + '') servicesDB + ) + ); + in + '' + ${lib.getExe' cfg.package "taler-merchant-dbinit"} + psql -U taler-${talerComponent}-httpd -f ${dbScript} + ''; + }; +} diff --git a/hosts/kvernberg/services/pvvvvvv/modules/taler/module.nix b/hosts/kvernberg/services/pvvvvvv/modules/taler/module.nix new file mode 100644 index 0000000..5e7fc8a --- /dev/null +++ b/hosts/kvernberg/services/pvvvvvv/modules/taler/module.nix @@ -0,0 +1,89 @@ +{ + lib, + pkgs, + config, + ... +}: + +let + cfg = config.services.taler; + settingsFormat = pkgs.formats.ini { }; +in + +{ + # TODO turn this into a generic taler-like service thingy? + options.services.taler = { + enable = lib.mkEnableOption "the GNU Taler system" // lib.mkOption { internal = true; }; + includes = lib.mkOption { + type = lib.types.listOf lib.types.path; + default = [ ]; + description = '' + Files to include into the config file using Taler's `@inline@` directive. + This allows including arbitrary INI files, including imperatively managed ones. + ''; + }; + settings = lib.mkOption { + description = '' + Global configuration options for the taler config file. + For a list of all possible options, please see the man page [`taler.conf(5)`](https://docs.taler.net/manpages/taler.conf.5.html) + ''; + type = lib.types.submodule { + freeformType = settingsFormat.type; + options = { + taler = { + CURRENCY = lib.mkOption { + type = lib.types.nonEmptyStr; + description = '' + The currency which taler services will operate with. This cannot be changed later. + ''; + }; + CURRENCY_ROUND_UNIT = lib.mkOption { + type = lib.types.str; + default = "${cfg.settings.taler.CURRENCY}:0.01"; + defaultText = lib.literalExpression '' + "''${config.services.taler.settings.taler.CURRENCY}:0.01" + ''; + description = '' + Smallest amount in this currency that can be transferred using the underlying RTGS. + You should probably not touch this. + ''; + }; + }; + }; + }; + default = { }; + }; + runtimeDir = lib.mkOption { + type = lib.types.str; + default = "/run/taler-system-runtime/"; + description = '' + Runtime directory shared between the taler services. + Crypto helpers put their sockets here for instance and the httpd + connects to them. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + services.taler.settings.PATHS = { + TALER_DATA_HOME = "\${STATE_DIRECTORY}/"; + TALER_CACHE_HOME = "\${CACHE_DIRECTORY}/"; + TALER_RUNTIME_DIR = cfg.runtimeDir; + }; + + environment.etc."taler/taler.conf".source = + let + includes = pkgs.writers.writeText "includes.conf" ( + lib.concatStringsSep "\n" (map (include: "@inline@ ${include}") cfg.includes) + ); + generatedConfig = settingsFormat.generate "generated-taler.conf" cfg.settings; + in + pkgs.runCommand "taler.conf" { } '' + cat ${includes} > $out + echo >> $out + echo >> $out + cat ${generatedConfig} >> $out + ''; + + }; +}