DROP WHEN MERGED: taler modules from #332699

This commit is contained in:
Daniel Olsen
2024-11-14 22:35:44 +01:00
parent e6baa1c725
commit d88dd0a45c
9 changed files with 872 additions and 0 deletions

View File

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

View File

@@ -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}
'';
};
}

View File

@@ -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}
'';
};
}

View File

@@ -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
'';
};
}