From ba727987d4a88c199bf627bc543e57cfd199a187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ci=C4=99=C5=BCarkiewicz?= Date: Wed, 26 Jun 2024 22:17:12 -0700 Subject: [PATCH] nixos/fedimintd: init services --- .../manual/release-notes/rl-2411.section.md | 2 + nixos/modules/module-list.nix | 1 + .../modules/services/networking/fedimintd.nix | 304 ++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/fedimintd.nix | 37 +++ 5 files changed, 345 insertions(+) create mode 100644 nixos/modules/services/networking/fedimintd.nix create mode 100644 nixos/tests/fedimintd.nix diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md index 9155b8f04715..b059a143544b 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -163,6 +163,8 @@ - [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). +- [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). + ## Backward Incompatibilities {#sec-release-24.11-incompatibilities} - 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. diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index fe67d39e70f9..dabeebb34e1e 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1031,6 +1031,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 diff --git a/nixos/modules/services/networking/fedimintd.nix b/nixos/modules/services/networking/fedimintd.nix new file mode 100644 index 000000000000..1ede3a50553d --- /dev/null +++ b/nixos/modules/services/networking/fedimintd.nix @@ -0,0 +1,304 @@ +{ + 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" { }; + + 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.path; + 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://user@example.com` will turn into `http://user:SOMESECRET@example.com`. + + 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.path; + 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 = { + DynamicUser = true; + + 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; + + 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 ]; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index af9d79f649bd..84b38708f7b7 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -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 {}; diff --git a/nixos/tests/fedimintd.nix b/nixos/tests/fedimintd.nix new file mode 100644 index 000000000000..19e92b43da65 --- /dev/null +++ b/nixos/tests/fedimintd.nix @@ -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}) + ''; +}