1# TODO: create a common module generator for Taler and Libeufin?
2libeufinComponent:
3{
4 lib,
5 pkgs,
6 config,
7 ...
8}:
9{
10 options.services.libeufin.${libeufinComponent} = {
11 enable = lib.mkEnableOption "libeufin core banking system and web interface";
12 package = lib.mkPackageOption pkgs "libeufin" { };
13 debug = lib.mkEnableOption "debug logging";
14 createLocalDatabase = lib.mkEnableOption "automatic creation of a local postgres database";
15 openFirewall = lib.mkOption {
16 type = lib.types.bool;
17 default = false;
18 description = "Whether to open ports in the firewall";
19 };
20 };
21
22 config =
23 let
24 cfg = cfgMain.${libeufinComponent};
25 cfgMain = config.services.libeufin;
26
27 configFile = config.environment.etc."libeufin/libeufin.conf".source;
28 serviceName = "libeufin-${libeufinComponent}";
29 isNexus = libeufinComponent == "nexus";
30
31 # get database name from config
32 # TODO: should this always be the same db? In which case, should this be an option directly under `services.libeufin`?
33 dbName =
34 lib.removePrefix "postgresql:///"
35 cfg.settings."libeufin-${libeufinComponent}db-postgres".CONFIG;
36
37 bankPort = cfg.settings."${if isNexus then "nexus-httpd" else "libeufin-bank"}".PORT;
38 bankHost = lib.elemAt (lib.splitString "/" cfg.settings.libeufin-bank.BASE_URL) 2;
39 in
40 lib.mkIf cfg.enable {
41 services.libeufin.settings = cfg.settings;
42
43 # TODO add system-libeufin.slice?
44 systemd.services = {
45 # Main service
46 "${serviceName}" = {
47 serviceConfig = {
48 DynamicUser = true;
49 ExecStart =
50 let
51 args = lib.cli.toGNUCommandLineShell { } {
52 c = configFile;
53 L = if cfg.debug then "debug" else null;
54 };
55 in
56 "${lib.getExe' cfg.package "libeufin-${libeufinComponent}"} serve ${args}";
57 Restart = "on-failure";
58 RestartSec = "10s";
59 };
60 requires = [ "libeufin-dbinit.service" ];
61 after = [ "libeufin-dbinit.service" ];
62 wantedBy = [ "multi-user.target" ];
63 };
64
65 # Database Initialisation
66 libeufin-dbinit =
67 let
68 dbScript = pkgs.writers.writeText "libeufin-db-permissions.sql" ''
69 GRANT SELECT,INSERT,UPDATE,DELETE ON ALL TABLES IN SCHEMA libeufin_bank TO "${serviceName}";
70 GRANT SELECT,INSERT,UPDATE,DELETE ON ALL TABLES IN SCHEMA libeufin_nexus TO "${serviceName}";
71 GRANT USAGE ON SCHEMA libeufin_bank TO "${serviceName}";
72 GRANT USAGE ON SCHEMA libeufin_nexus TO "${serviceName}";
73 '';
74
75 # Accounts to be created after the bank database initialization.
76 #
77 # For example, if the bank's currency conversion is enabled, it's
78 # required that the exchange account is registered before the
79 # service starts.
80 initialAccountRegistration = lib.concatMapStringsSep "\n" (
81 account:
82 let
83 args = lib.cli.toGNUCommandLineShell { } {
84 c = configFile;
85 inherit (account) username password name;
86 payto_uri = "payto://x-taler-bank/${bankHost}/${account.username}?receiver-name=${account.name}";
87 exchange = lib.toLower account.username == "exchange";
88 };
89 in
90 "${lib.getExe' cfg.package "libeufin-bank"} create-account ${args}"
91 ) cfg.initialAccounts;
92
93 args = lib.cli.toGNUCommandLineShell { } {
94 c = configFile;
95 L = if cfg.debug then "debug" else null;
96 };
97 in
98 {
99 path = [
100 (if cfg.createLocalDatabase then config.services.postgresql.package else pkgs.postgresql)
101 ];
102 serviceConfig = {
103 Type = "oneshot";
104 DynamicUser = true;
105 StateDirectory = "libeufin-dbinit";
106 StateDirectoryMode = "0750";
107 User = dbName;
108 };
109 script = lib.optionalString cfg.enable ''
110 ${lib.getExe' cfg.package "libeufin-${libeufinComponent}"} dbinit ${args}
111 '';
112 # Grant DB permissions after schemas have been created
113 postStart = ''
114 psql -U "${dbName}" -f "${dbScript}"
115 ''
116 + lib.optionalString ((!isNexus) && (cfg.initialAccounts != [ ])) ''
117 # only register initial accounts once
118 if [ ! -e /var/lib/libeufin-dbinit/init ]; then
119 ${initialAccountRegistration}
120
121 touch /var/lib/libeufin-dbinit/init
122 echo "Bank initialisation complete"
123 fi
124 '';
125 requires = lib.optionals cfg.createLocalDatabase [ "postgresql.target" ];
126 after = [ "network.target" ] ++ lib.optionals cfg.createLocalDatabase [ "postgresql.target" ];
127 };
128 };
129
130 networking.firewall = lib.mkIf cfg.openFirewall {
131 allowedTCPPorts = [
132 bankPort
133 ];
134 };
135
136 environment.systemPackages = [ cfg.package ];
137
138 services.postgresql = lib.mkIf cfg.createLocalDatabase {
139 enable = true;
140 ensureDatabases = [ dbName ];
141 ensureUsers = [
142 { name = serviceName; }
143 {
144 name = dbName;
145 ensureDBOwnership = true;
146 }
147 ];
148 };
149
150 assertions = [
151 {
152 assertion =
153 cfg.createLocalDatabase || (cfg.settings."libeufin-${libeufinComponent}db-postgres" ? CONFIG);
154 message = "Libeufin ${libeufinComponent} database is not configured.";
155 }
156 ];
157
158 };
159}