Skip to content

Commit

Permalink
nixos/taler: init module
Browse files Browse the repository at this point in the history
basic config set that makes the service at least start

add secmod helpers and taler-global runtime dir

support for includes

taler denominations

Only enable services if taler is enabled

fix wirewatch service name

use correct permissions for database schema

The current permissions don't work or aren't enough and cause the
wirewatch and closer services to fail.

nixos/libeufin: init module

libeufin: refactor module

libeufin: add main service

nixos/taler: configure settings using options

Works, but can be refactored further

taler: refactor settings options

trim settings defaults to the absolutely necessary ones

nixos/libeufin: refactor and move to separate dir

nixos/libeufin: set defaultText

nixos/libeufin: use getExe

nixos/libeufin-bank: move to own dir

nixos/libeufin: move libeufin related config into its own config file

nixos/libeufin/bank: extract dbinitServiceName into var

nixos/libeufin: move script to ExecStart

nixos/libeufin: fix config file name

nixos/taler: refactor config file

nixos/taler-exchange: grant delete to taler-exchange-aggregator

Would repeatedly attempt to delete in a table where it wasn't allowed to and
cause insane spam in the postgres log.

nixos/taler/exchange: move exchange-specific options to exchange

nixos/taler: move generic taler settings into taler system module

nixos/taler: import exchange in module-list.nix

nixos/taler-exchange: refactor services group name

nixos/taler-exchange: use taler-harness to generate coins

The taler-wallet-cli does not have the deployment subcommand anymore,
but the docs still say that it should be used to generate the keys.

For now, the keys should be generated with taler-harness.

nixos/taler-exchange: add option to enable accounts

nixos/taler: add missing descriptions

nixos/taler(exchange): add description & use getExe'

nixos/taler(merchant): init submodule

nixos/taler: use correct script for db access

nixos/taler: merchant add depositcheck path

nixos/taler: review suggestions

nixos/taler: make runtimeDir into an option, refactor

nixos/taler: init mkTalerModule

nixos/taler: use mkTalerModule for exchange

nixos/taler: exchange fixups

nixos/taler: use mkTalerModule for merchant

nixos/taler: improve how dbInit script is created

nixos/taler: remove exchange enableAccounts option

nixos/taler: explicitly specify psql user

Sometimes the dbinit service fails to find the user.

nixos/taler: add openFirewall option; install package

feat: add assertions, remove throw

feat(taler): use module system instead of functions

Also:
- remove throw from denominateConfig
- rename `utils.nix` to `common.nix`

feat(taler): refactor modules

feat: move taler module to services/finance

refactor(exchange): replace throw with assert

refactor(exchange,merchant): settings options

fix(taler): manpage URLs

fix(exchange): public key assert

refactor(taler): use configFile

feat(taler): include component configs directly

Makes services detect config changes better.
  • Loading branch information
Atemu authored and fricklerhandwerk committed Jan 13, 2025
1 parent f571531 commit 7c51b85
Show file tree
Hide file tree
Showing 5 changed files with 477 additions and 0 deletions.
3 changes: 3 additions & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,9 @@
./services/editors/haste.nix
./services/editors/infinoted.nix
./services/finance/odoo.nix
./services/finance/taler/exchange.nix
./services/finance/taler/merchant.nix
./services/finance/taler/module.nix
./services/games/archisteamfarm.nix
./services/games/armagetronad.nix
./services/games/crossfire-server.nix
Expand Down
119 changes: 119 additions & 0 deletions nixos/modules/services/finance/taler/common.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# 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 { };

configFile = config.environment.etc."taler/taler.conf".source;
componentConfigFile = settingsFormat.generate "generated-taler-${talerComponent}.conf" cfg.settings;

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;
services.taler.includes = [ componentConfigFile ];

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 ${configFile}"
(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.postgresql = {
enable = true;
ensureDatabases = [ dbName ];
ensureUsers = map (service: { name = "taler-${talerComponent}-${service}"; }) servicesDB ++ [
{
name = dbName;
ensureDBOwnership = true;
}
];
};
};
}
154 changes: 154 additions & 0 deletions nixos/modules/services/finance/taler/exchange.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
{
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-exchange.conf(5)`](https://docs.taler.net/manpages/taler-exchange.conf.5.html)
'';
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.enum [ "postgres" ];
default = "postgres";
description = "Plugin to use for the database.";
};
MASTER_PUBLIC_KEY = lib.mkOption {
type = lib.types.str;
default = "";
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.nonEmptyStr;
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 {
assertions = [
{
assertion = cfg.settings.exchange.MASTER_PUBLIC_KEY != "";
message = ''
You must provide `config.services.taler.exchange.settings.exchange.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`, which will print the public key.
'';
}
];

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}
'';
};
}
108 changes: 108 additions & 0 deletions nixos/modules/services/finance/taler/merchant.nix
Original file line number Diff line number Diff line change
@@ -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-merchant.conf(5)`](https://docs.taler.net/manpages/taler-merchant.conf.5.html)
'';
type = lib.types.submodule {
inherit (options.services.taler.settings.type.nestedTypes) freeformType;
options = {
merchant = {
DB = lib.mkOption {
type = lib.types.enum [ "postgres" ];
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.enum [
"tcp"
"unix"
];
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;
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.nonEmptyStr;
default = "postgres:///taler-merchant-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}
'';
};
}
Loading

0 comments on commit 7c51b85

Please sign in to comment.