at 25.11-pre 9.8 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 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:8173"; 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://user@example.com` will turn into `http://user:SOMESECRET@example.com`. 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 path = mkOption { 163 type = types.str; 164 example = "/"; 165 default = "/ws/"; 166 description = "Path to host the API on and forward to the daemon's api port"; 167 }; 168 config = mkOption { 169 type = types.submodule ( 170 recursiveUpdate (import ../web-servers/nginx/vhost-options.nix { 171 inherit config lib; 172 }) { } 173 ); 174 default = { }; 175 description = "Overrides to the nginx vhost section for api"; 176 }; 177 }; 178 }; 179 }; 180in 181{ 182 options = { 183 services.fedimintd = mkOption { 184 type = types.attrsOf (types.submodule fedimintdOpts); 185 default = { }; 186 description = "Specification of one or more fedimintd instances."; 187 }; 188 }; 189 190 config = 191 let 192 eachFedimintd = filterAttrs (fedimintdName: cfg: cfg.enable) config.services.fedimintd; 193 eachFedimintdNginx = filterAttrs (fedimintdName: cfg: cfg.nginx.enable) eachFedimintd; 194 in 195 mkIf (eachFedimintd != { }) { 196 197 networking.firewall.allowedTCPPorts = concatLists ( 198 mapAttrsToList ( 199 fedimintdName: cfg: 200 (lib.optional cfg.api.openFirewall cfg.api.port ++ lib.optional cfg.p2p.openFirewall cfg.p2p.port) 201 ) eachFedimintd 202 ); 203 204 systemd.services = mapAttrs' ( 205 fedimintdName: cfg: 206 (nameValuePair "fedimintd-${fedimintdName}" ( 207 let 208 startScript = pkgs.writeShellScript "fedimintd-start" ( 209 ( 210 if cfg.bitcoin.rpc.secretFile != null then 211 '' 212 secret=$(${pkgs.coreutils}/bin/head -n 1 "${cfg.bitcoin.rpc.secretFile}") 213 prefix="''${FM_BITCOIN_RPC_URL%*@*}" # Everything before the last '@' 214 suffix="''${FM_BITCOIN_RPC_URL##*@}" # Everything after the last '@' 215 FM_BITCOIN_RPC_URL="''${prefix}:''${secret}@''${suffix}" 216 '' 217 else 218 "" 219 ) 220 + '' 221 exec ${cfg.package}/bin/fedimintd 222 '' 223 ); 224 in 225 { 226 description = "Fedimint Server"; 227 documentation = [ "https://github.com/fedimint/fedimint/" ]; 228 wantedBy = [ "multi-user.target" ]; 229 environment = lib.mkMerge [ 230 { 231 FM_BIND_P2P = "${cfg.p2p.bind}:${toString cfg.p2p.port}"; 232 FM_BIND_API = "${cfg.api.bind}:${toString cfg.api.port}"; 233 FM_P2P_URL = cfg.p2p.url; 234 FM_API_URL = cfg.api.url; 235 FM_DATA_DIR = cfg.dataDir; 236 FM_BITCOIN_NETWORK = cfg.bitcoin.network; 237 FM_BITCOIN_RPC_URL = cfg.bitcoin.rpc.url; 238 FM_BITCOIN_RPC_KIND = cfg.bitcoin.rpc.kind; 239 } 240 cfg.environment 241 ]; 242 serviceConfig = { 243 DynamicUser = true; 244 245 StateDirectory = "fedimintd-${fedimintdName}"; 246 StateDirectoryMode = "0700"; 247 ExecStart = startScript; 248 249 Restart = "always"; 250 RestartSec = 10; 251 StartLimitBurst = 5; 252 UMask = "007"; 253 LimitNOFILE = "100000"; 254 255 LockPersonality = true; 256 MemoryDenyWriteExecute = true; 257 NoNewPrivileges = true; 258 PrivateDevices = true; 259 PrivateMounts = true; 260 PrivateTmp = true; 261 ProtectClock = true; 262 ProtectControlGroups = true; 263 ProtectHostname = true; 264 ProtectKernelLogs = true; 265 ProtectKernelModules = true; 266 ProtectKernelTunables = true; 267 ProtectSystem = "full"; 268 RestrictAddressFamilies = [ 269 "AF_INET" 270 "AF_INET6" 271 ]; 272 RestrictNamespaces = true; 273 RestrictRealtime = true; 274 SystemCallArchitectures = "native"; 275 SystemCallFilter = [ 276 "@system-service" 277 "~@privileged" 278 ]; 279 }; 280 } 281 )) 282 ) eachFedimintd; 283 284 services.nginx.virtualHosts = mapAttrs' ( 285 fedimintdName: cfg: 286 (nameValuePair cfg.nginx.fqdn ( 287 lib.mkMerge [ 288 cfg.nginx.config 289 290 { 291 # Note: we want by default to enable OpenSSL, but it seems anything 100 and above is 292 # overriden by default value from vhost-options.nix 293 enableACME = mkOverride 99 true; 294 forceSSL = mkOverride 99 true; 295 locations.${cfg.nginx.path} = { 296 proxyPass = "http://127.0.0.1:${toString cfg.api.port}/"; 297 proxyWebsockets = true; 298 extraConfig = '' 299 proxy_pass_header Authorization; 300 ''; 301 }; 302 } 303 ] 304 )) 305 ) eachFedimintdNginx; 306 }; 307 308 meta.maintainers = with lib.maintainers; [ dpc ]; 309}