Skip to content

Commit c760c83

Browse files
authored
nixos/fedimintd: init service (#322815)
2 parents 2a5cba2 + ba72798 commit c760c83

File tree

5 files changed

+345
-0
lines changed

5 files changed

+345
-0
lines changed

nixos/doc/manual/release-notes/rl-2411.section.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@
169169

170170
- [Veilid](https://veilid.com), a headless server that enables privacy-focused data sharing and messaging on a peer-to-peer network. Available as [services.veilid](#opt-services.veilid.enable).
171171

172+
- [Fedimint](https://github.com/fedimint/fedimint), a module based system for building federated applications (Federated E-Cash Mint). Available as [services.fedimintd](#opt-services.fedimintd).
173+
172174
## Backward Incompatibilities {#sec-release-24.11-incompatibilities}
173175

174176
- The `sound` options have been removed or renamed, as they had a lot of unintended side effects. See [below](#sec-release-24.11-migration-sound) for details.

nixos/modules/module-list.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,7 @@
10311031
./services/networking/expressvpn.nix
10321032
./services/networking/fakeroute.nix
10331033
./services/networking/fastnetmon-advanced.nix
1034+
./services/networking/fedimintd.nix
10341035
./services/networking/ferm.nix
10351036
./services/networking/firefox-syncserver.nix
10361037
./services/networking/fireqos.nix
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
{
2+
config,
3+
lib,
4+
pkgs,
5+
...
6+
}:
7+
let
8+
inherit (lib)
9+
concatLists
10+
filterAttrs
11+
mapAttrs'
12+
mapAttrsToList
13+
mkEnableOption
14+
mkIf
15+
mkOption
16+
mkOverride
17+
mkPackageOption
18+
nameValuePair
19+
recursiveUpdate
20+
types
21+
;
22+
23+
fedimintdOpts =
24+
{
25+
config,
26+
lib,
27+
name,
28+
...
29+
}:
30+
{
31+
options = {
32+
enable = mkEnableOption "fedimintd";
33+
34+
package = mkPackageOption pkgs "fedimint" { };
35+
36+
environment = mkOption {
37+
type = types.attrsOf types.str;
38+
description = "Extra Environment variables to pass to the fedimintd.";
39+
default = {
40+
RUST_BACKTRACE = "1";
41+
};
42+
example = {
43+
RUST_LOG = "info,fm=debug";
44+
RUST_BACKTRACE = "1";
45+
};
46+
};
47+
48+
p2p = {
49+
openFirewall = mkOption {
50+
type = types.bool;
51+
default = true;
52+
description = "Opens port in firewall for fedimintd's p2p port";
53+
};
54+
port = mkOption {
55+
type = types.port;
56+
default = 8173;
57+
description = "Port to bind on for p2p connections from peers";
58+
};
59+
bind = mkOption {
60+
type = types.str;
61+
default = "0.0.0.0";
62+
description = "Address to bind on for p2p connections from peers";
63+
};
64+
url = mkOption {
65+
type = types.str;
66+
example = "fedimint://p2p.myfedimint.com";
67+
description = ''
68+
Public address for p2p connections from peers
69+
'';
70+
};
71+
};
72+
api = {
73+
openFirewall = mkOption {
74+
type = types.bool;
75+
default = false;
76+
description = "Opens port in firewall for fedimintd's api port";
77+
};
78+
port = mkOption {
79+
type = types.port;
80+
default = 8174;
81+
description = "Port to bind on for API connections relied by the reverse proxy/tls terminator.";
82+
};
83+
bind = mkOption {
84+
type = types.str;
85+
default = "127.0.0.1";
86+
description = "Address to bind on for API connections relied by the reverse proxy/tls terminator.";
87+
};
88+
url = mkOption {
89+
type = types.str;
90+
description = ''
91+
Public URL of the API address of the reverse proxy/tls terminator. Usually starting with `wss://`.
92+
'';
93+
};
94+
};
95+
bitcoin = {
96+
network = mkOption {
97+
type = types.str;
98+
default = "signet";
99+
example = "bitcoin";
100+
description = "Bitcoin network to participate in.";
101+
};
102+
rpc = {
103+
url = mkOption {
104+
type = types.str;
105+
default = "http://127.0.0.1:38332";
106+
example = "signet";
107+
description = "Bitcoin node (bitcoind/electrum/esplora) address to connect to";
108+
};
109+
110+
kind = mkOption {
111+
type = types.str;
112+
default = "bitcoind";
113+
example = "electrum";
114+
description = "Kind of a bitcoin node.";
115+
};
116+
117+
secretFile = mkOption {
118+
type = types.nullOr types.path;
119+
default = null;
120+
description = ''
121+
If set the URL specified in `bitcoin.rpc.url` will get the content of this file added
122+
as an URL password, so `http://[email protected]` will turn into `http://user:[email protected]`.
123+
124+
Example:
125+
126+
`/etc/nix-bitcoin-secrets/bitcoin-rpcpassword-public` (for nix-bitcoin default)
127+
'';
128+
};
129+
};
130+
};
131+
132+
consensus.finalityDelay = mkOption {
133+
type = types.ints.unsigned;
134+
default = 10;
135+
description = "Consensus peg-in finality delay.";
136+
};
137+
138+
dataDir = mkOption {
139+
type = types.path;
140+
default = "/var/lib/fedimintd-${name}/";
141+
readOnly = true;
142+
description = ''
143+
Path to the data dir fedimintd will use to store its data.
144+
Note that due to using the DynamicUser feature of systemd, this value should not be changed
145+
and is set to be read only.
146+
'';
147+
};
148+
149+
nginx = {
150+
enable = mkOption {
151+
type = types.bool;
152+
default = false;
153+
description = ''
154+
Whether to configure nginx for fedimintd
155+
'';
156+
};
157+
fqdn = mkOption {
158+
type = types.str;
159+
example = "api.myfedimint.com";
160+
description = "Public domain of the API address of the reverse proxy/tls terminator.";
161+
};
162+
config = mkOption {
163+
type = types.submodule (
164+
recursiveUpdate (import ../web-servers/nginx/vhost-options.nix {
165+
inherit config lib;
166+
}) { }
167+
);
168+
default = { };
169+
description = "Overrides to the nginx vhost section for api";
170+
};
171+
};
172+
};
173+
};
174+
in
175+
{
176+
options = {
177+
services.fedimintd = mkOption {
178+
type = types.attrsOf (types.submodule fedimintdOpts);
179+
default = { };
180+
description = "Specification of one or more fedimintd instances.";
181+
};
182+
};
183+
184+
config =
185+
let
186+
eachFedimintd = filterAttrs (fedimintdName: cfg: cfg.enable) config.services.fedimintd;
187+
eachFedimintdNginx = filterAttrs (fedimintdName: cfg: cfg.nginx.enable) eachFedimintd;
188+
in
189+
mkIf (eachFedimintd != { }) {
190+
191+
networking.firewall.allowedTCPPorts = concatLists (
192+
mapAttrsToList (
193+
fedimintdName: cfg:
194+
(lib.optional cfg.api.openFirewall cfg.api.port ++ lib.optional cfg.p2p.openFirewall cfg.p2p.port)
195+
) eachFedimintd
196+
);
197+
198+
systemd.services = mapAttrs' (
199+
fedimintdName: cfg:
200+
(nameValuePair "fedimintd-${fedimintdName}" (
201+
let
202+
startScript = pkgs.writeShellScript "fedimintd-start" (
203+
(
204+
if cfg.bitcoin.rpc.secretFile != null then
205+
''
206+
secret=$(${pkgs.coreutils}/bin/head -n 1 "${cfg.bitcoin.rpc.secretFile}")
207+
prefix="''${FM_BITCOIN_RPC_URL%*@*}" # Everything before the last '@'
208+
suffix="''${FM_BITCOIN_RPC_URL##*@}" # Everything after the last '@'
209+
FM_BITCOIN_RPC_URL="''${prefix}:''${secret}@''${suffix}"
210+
''
211+
else
212+
""
213+
)
214+
+ ''
215+
exec ${cfg.package}/bin/fedimintd
216+
''
217+
);
218+
in
219+
{
220+
description = "Fedimint Server";
221+
documentation = [ "https://github.com/fedimint/fedimint/" ];
222+
wantedBy = [ "multi-user.target" ];
223+
environment = lib.mkMerge [
224+
{
225+
FM_BIND_P2P = "${cfg.p2p.bind}:${toString cfg.p2p.port}";
226+
FM_BIND_API = "${cfg.api.bind}:${toString cfg.api.port}";
227+
FM_P2P_URL = cfg.p2p.url;
228+
FM_API_URL = cfg.api.url;
229+
FM_DATA_DIR = cfg.dataDir;
230+
FM_BITCOIN_NETWORK = cfg.bitcoin.network;
231+
FM_BITCOIN_RPC_URL = cfg.bitcoin.rpc.url;
232+
FM_BITCOIN_RPC_KIND = cfg.bitcoin.rpc.kind;
233+
}
234+
cfg.environment
235+
];
236+
serviceConfig = {
237+
DynamicUser = true;
238+
239+
StateDirectory = "fedimintd-${fedimintdName}";
240+
StateDirectoryMode = "0700";
241+
ExecStart = startScript;
242+
243+
Restart = "always";
244+
RestartSec = 10;
245+
StartLimitBurst = 5;
246+
UMask = "007";
247+
LimitNOFILE = "100000";
248+
249+
LockPersonality = true;
250+
MemoryDenyWriteExecute = true;
251+
NoNewPrivileges = true;
252+
PrivateDevices = true;
253+
PrivateMounts = true;
254+
PrivateTmp = true;
255+
ProtectClock = true;
256+
ProtectControlGroups = true;
257+
ProtectHostname = true;
258+
ProtectKernelLogs = true;
259+
ProtectKernelModules = true;
260+
ProtectKernelTunables = true;
261+
ProtectSystem = "full";
262+
RestrictAddressFamilies = [
263+
"AF_INET"
264+
"AF_INET6"
265+
];
266+
RestrictNamespaces = true;
267+
RestrictRealtime = true;
268+
SystemCallArchitectures = "native";
269+
SystemCallFilter = [
270+
"@system-service"
271+
"~@privileged"
272+
];
273+
};
274+
}
275+
))
276+
) eachFedimintd;
277+
278+
services.nginx.virtualHosts = mapAttrs' (
279+
fedimintdName: cfg:
280+
(nameValuePair cfg.nginx.fqdn (
281+
lib.mkMerge [
282+
cfg.nginx.config
283+
284+
{
285+
# Note: we want by default to enable OpenSSL, but it seems anything 100 and above is
286+
# overriden by default value from vhost-options.nix
287+
enableACME = mkOverride 99 true;
288+
forceSSL = mkOverride 99 true;
289+
# Currently Fedimint API only support JsonRPC on `/ws/` endpoint, so no need to handle `/`
290+
locations."/ws/" = {
291+
proxyPass = "http://127.0.0.1:${toString cfg.api.port}/";
292+
proxyWebsockets = true;
293+
extraConfig = ''
294+
proxy_pass_header Authorization;
295+
'';
296+
};
297+
}
298+
]
299+
))
300+
) eachFedimintdNginx;
301+
};
302+
303+
meta.maintainers = with lib.maintainers; [ dpc ];
304+
}

nixos/tests/all-tests.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ in {
322322
fancontrol = handleTest ./fancontrol.nix {};
323323
fanout = handleTest ./fanout.nix {};
324324
fcitx5 = handleTest ./fcitx5 {};
325+
fedimintd = runTest ./fedimintd.nix;
325326
fenics = handleTest ./fenics.nix {};
326327
ferm = handleTest ./ferm.nix {};
327328
ferretdb = handleTest ./ferretdb.nix {};

nixos/tests/fedimintd.nix

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# This test runs the fedimintd and verifies that it starts
2+
3+
{ pkgs, ... }:
4+
5+
{
6+
name = "fedimintd";
7+
8+
meta = with pkgs.lib.maintainers; {
9+
maintainers = [ dpc ];
10+
};
11+
12+
nodes.machine =
13+
{ ... }:
14+
{
15+
services.fedimintd."mainnet" = {
16+
enable = true;
17+
p2p = {
18+
url = "fedimint://example.com";
19+
};
20+
api = {
21+
url = "wss://example.com";
22+
};
23+
environment = {
24+
"FM_REL_NOTES_ACK" = "0_4_xyz";
25+
};
26+
};
27+
};
28+
29+
testScript =
30+
{ nodes, ... }:
31+
''
32+
start_all()
33+
34+
machine.wait_for_unit("fedimintd-mainnet.service")
35+
machine.wait_for_open_port(${toString nodes.machine.services.fedimintd.mainnet.api.port})
36+
'';
37+
}

0 commit comments

Comments
 (0)