Skip to content

Commit

Permalink
nixos/fedimintd: init services
Browse files Browse the repository at this point in the history
  • Loading branch information
dpc committed Oct 2, 2024
1 parent d37afd1 commit 16b2ae6
Show file tree
Hide file tree
Showing 4 changed files with 372 additions and 0 deletions.
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,7 @@
./services/networking/expressvpn.nix
./services/networking/fakeroute.nix
./services/networking/fastnetmon-advanced.nix
./services/networking/fedimintd.nix
./services/networking/ferm.nix
./services/networking/firefox-syncserver.nix
./services/networking/fireqos.nix
Expand Down
333 changes: 333 additions & 0 deletions nixos/modules/services/networking/fedimintd.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
{ config
, lib
, pkgs
, ...
}:
let
inherit (lib)
concatLists
filterAttrs
mapAttrs'
mapAttrsToList
mkEnableOption
mkIf
mkOption
mkOverride
mkPackageOption
nameValuePair
recursiveUpdate
types;

fedimintdOpts =
{ config
, lib
, name
, ...
}:
{
options = {
enable = mkEnableOption "fedimintd";

package = mkPackageOption pkgs "fedimint" { };

user = mkOption {
type = types.str;
default = "fedimintd-${name}";
description = "The user as which to run fedimintd.";
};

group = mkOption {
type = types.str;
default = config.user;
description = "The group as which to run fedimintd.";
};

environment = mkOption {
type = types.attrsOf types.str;
description = "Extra Environment variables to pass to the fedimintd.";
default = {
RUST_BACKTRACE = "1";
};
example = {
RUST_LOG = "info,fm=debug";
RUST_BACKTRACE = "1";
};
};

p2p = {
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Opens port in firewall for fedimintd's p2p port";
};
port = mkOption {
type = types.port;
default = 8173;
description = "Port to bind on for p2p connections from peers";
};
bind = mkOption {
type = types.str;
default = "0.0.0.0";
description = "Address to bind on for p2p connections from peers";
};
url = mkOption {
type = types.str;
example = "fedimint://p2p.myfedimint.com";
description = ''
Public address for p2p connections from peers
'';
};
};
api = {
openFirewall = mkOption {
type = types.bool;
default = false;
description = "Opens port in firewall for fedimintd's api port";
};
port = mkOption {
type = types.port;
default = 8174;
description = "Port to bind on for API connections relied by the reverse proxy/tls terminator.";
};
bind = mkOption {
type = types.str;
default = "127.0.0.1";
description = "Address to bind on for API connections relied by the reverse proxy/tls terminator.";
};
url = mkOption {
type = types.str;
description = ''
Public URL of the API address of the reverse proxy/tls terminator. Usually starting with `wss://`.
'';
};
};
bitcoin = {
network = mkOption {
type = types.str;
default = "signet";
example = "bitcoin";
description = "Bitcoin network to participate in.";
};
rpc = {
url = mkOption {
type = types.str;
default = "http://127.0.0.1:38332";
example = "signet";
description = "Bitcoin node (bitcoind/electrum/esplora) address to connect to";
};

kind = mkOption {
type = types.str;
default = "bitcoind";
example = "electrum";
description = "Kind of a bitcoin node.";
};

secretFile = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
If set the URL specified in `bitcoin.rpc.url` will get the content of this file added
as an URL password, so `http://[email protected]` will turn into `http://user:[email protected]`.
Example:
`/etc/nix-bitcoin-secrets/bitcoin-rpcpassword-public` (for nix-bitcoin default)
'';
};
};
};

consensus.finalityDelay = mkOption {
type = types.ints.unsigned;
default = 10;
description = "Consensus peg-in finality delay.";
};

dataDir = mkOption {
type = types.str;
default = "/var/lib/fedimintd-${name}/";
readOnly = true;
description = ''
Path to the data dir fedimintd will use to store its data.
Note that due to using the DynamicUser feature of systemd, this value should not be changed
and is set to be read only.
'';
};

nginx = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to configure nginx for fedimintd
'';
};
fqdn = mkOption {
type = types.str;
example = "api.myfedimint.com";
description = "Public domain of the API address of the reverse proxy/tls terminator.";
};
config = mkOption {
type = types.submodule (
recursiveUpdate (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) { }
);
default = { };
description = "Overrides to the nginx vhost section for api";
};
};
};
};
in
{
options = {
services.fedimintd = mkOption {
type = types.attrsOf (types.submodule fedimintdOpts);
default = { };
description = "Specification of one or more fedimintd instances.";
};
};

config =
let
eachFedimintd = filterAttrs (fedimintdName: cfg: cfg.enable) config.services.fedimintd;
eachFedimintdNginx = filterAttrs (fedimintdName: cfg: cfg.nginx.enable) eachFedimintd;
in
mkIf (eachFedimintd != { }) {

networking.firewall.allowedTCPPorts = concatLists (
mapAttrsToList
(
fedimintdName: cfg:
(lib.optional cfg.api.openFirewall cfg.api.port ++ lib.optional cfg.p2p.openFirewall cfg.p2p.port)
)
eachFedimintd
);

systemd.services = mapAttrs'
(
fedimintdName: cfg:
(nameValuePair "fedimintd-${fedimintdName}" (
let
startScript = pkgs.writeShellScript "fedimintd-start" (
(
if cfg.bitcoin.rpc.secretFile != null then
''
secret=$(${pkgs.coreutils}/bin/head -n 1 "${cfg.bitcoin.rpc.secretFile}")
prefix="''${FM_BITCOIN_RPC_URL%*@*}" # Everything before the last '@'
suffix="''${FM_BITCOIN_RPC_URL##*@}" # Everything after the last '@'
FM_BITCOIN_RPC_URL="''${prefix}:''${secret}@''${suffix}"
''
else
""
)
+ ''
exec ${cfg.package}/bin/fedimintd
''
);
in
{
description = "Fedimint Server";
documentation = [ "https://github.com/fedimint/fedimint/" ];
wantedBy = [ "multi-user.target" ];
environment = lib.mkMerge [
{
FM_BIND_P2P = "${cfg.p2p.bind}:${toString cfg.p2p.port}";
FM_BIND_API = "${cfg.api.bind}:${toString cfg.api.port}";
FM_P2P_URL = cfg.p2p.url;
FM_API_URL = cfg.api.url;
FM_DATA_DIR = cfg.dataDir;
FM_BITCOIN_NETWORK = cfg.bitcoin.network;
FM_BITCOIN_RPC_URL = cfg.bitcoin.rpc.url;
FM_BITCOIN_RPC_KIND = cfg.bitcoin.rpc.kind;
}
cfg.environment
];
serviceConfig = {
User = cfg.user;
Group = cfg.group;

StateDirectory = "fedimintd-${fedimintdName}";
StateDirectoryMode = "0700";
ExecStart = startScript;

Restart = "always";
RestartSec = 10;
StartLimitBurst = 5;
UMask = "007";
LimitNOFILE = "100000";

LockPersonality = true;
MemoryDenyWriteExecute = "true";
NoNewPrivileges = "true";
PrivateDevices = "true";
PrivateMounts = true;
PrivateTmp = "true";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectSystem = "full";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true;
RestrictRealtime = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
];
};
}
))
)
eachFedimintd;

users.users = mapAttrs'
(
fedimintdName: cfg:
(nameValuePair "fedimintd-${fedimintdName}" {
name = cfg.user;
group = cfg.group;
description = "Fedimint daemon user";
home = cfg.dataDir;
isSystemUser = true;
})
)
eachFedimintd;

users.groups = mapAttrs' (fedimintdName: cfg: (nameValuePair "${cfg.group}" { })) eachFedimintd;

services.nginx.virtualHosts = mapAttrs'
(
fedimintdName: cfg:
(nameValuePair cfg.nginx.fqdn (
lib.mkMerge [
cfg.nginx.config

{
# Note: we want by default to enable OpenSSL, but it seems anything 100 and above is
# overriden by default value from vhost-options.nix
enableACME = mkOverride 99 true;
forceSSL = mkOverride 99 true;
# Currently Fedimint API only support JsonRPC on `/ws/` endpoint, so no need to handle `/`
locations."/ws/" = {
proxyPass = "http://127.0.0.1:${toString cfg.api.port}/";
proxyWebsockets = true;
extraConfig = ''
proxy_pass_header Authorization;
'';
};
}
]
))
)
eachFedimintdNginx;
};

meta.maintainers = with lib.maintainers; [ dpc ];
}
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ in {
fancontrol = handleTest ./fancontrol.nix {};
fanout = handleTest ./fanout.nix {};
fcitx5 = handleTest ./fcitx5 {};
fedimintd = runTest ./fedimintd.nix;
fenics = handleTest ./fenics.nix {};
ferm = handleTest ./ferm.nix {};
ferretdb = handleTest ./ferretdb.nix {};
Expand Down
37 changes: 37 additions & 0 deletions nixos/tests/fedimintd.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# This test runs the fedimintd and verifies that it starts

{ pkgs, ... }:

{
name = "fedimintd";

meta = with pkgs.lib.maintainers; {
maintainers = [ dpc ];
};

nodes.machine =
{ ... }:
{
services.fedimintd."mainnet" = {
enable = true;
p2p = {
url = "fedimint://example.com";
};
api = {
url = "wss://example.com";
};
environment = {
"FM_REL_NOTES_ACK" = "0_4_xyz";
};
};
};

testScript =
{ nodes, ... }:
''
start_all()
machine.wait_for_unit("fedimintd-mainnet.service")
machine.wait_for_open_port(${toString nodes.machine.services.fedimintd.mainnet.api.port})
'';
}

0 comments on commit 16b2ae6

Please sign in to comment.