From 24088cfaee01d2650d90377257cece3344d20aa7 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Giraudeau Date: Fri, 5 Nov 2021 21:56:11 +0100 Subject: [PATCH] Add nixos service and scripts --- .gitignore | 3 + lib/cli/src/Cardano/CLI.hs | 2 +- nix/nixos/cardano-wallet-service.nix | 197 +++++++++++++++++++++++++ nix/nixos/default.nix | 1 + nix/nixos/module-list.nix | 3 + nix/nixos/tests/default.nix | 17 +++ nix/nixos/tests/service-basic-test.nix | 59 ++++++++ nix/release-build.nix | 9 +- nix/scripts.nix | 69 +++++++++ 9 files changed, 356 insertions(+), 4 deletions(-) create mode 100644 nix/nixos/cardano-wallet-service.nix create mode 100644 nix/nixos/default.nix create mode 100644 nix/nixos/module-list.nix create mode 100644 nix/nixos/tests/default.nix create mode 100644 nix/nixos/tests/service-basic-test.nix create mode 100644 nix/scripts.nix diff --git a/.gitignore b/.gitignore index ec161c5b5b2..3667a137dd1 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,6 @@ hie.yaml ### Docs build /_build/ + +### databases of scripts +/db-cardano-wallet-* diff --git a/lib/cli/src/Cardano/CLI.hs b/lib/cli/src/Cardano/CLI.hs index f0f326c8bd7..73bcf8a1bdd 100644 --- a/lib/cli/src/Cardano/CLI.hs +++ b/lib/cli/src/Cardano/CLI.hs @@ -1186,7 +1186,7 @@ hostPreferenceOption = option str $ mempty <> long "listen-address" <> metavar "HOST" <> help - ("Specification of which host to the bind API server to. " <> + ("Specification of which host to bind the API server to. " <> "Can be an IPv[46] address, hostname, or '*'.") <> value "127.0.0.1" <> showDefaultWith (const "127.0.0.1") diff --git a/nix/nixos/cardano-wallet-service.nix b/nix/nixos/cardano-wallet-service.nix new file mode 100644 index 00000000000..b1dcf17997f --- /dev/null +++ b/nix/nixos/cardano-wallet-service.nix @@ -0,0 +1,197 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.services.cardano-wallet; + inherit (lib) mkIf mkEnableOption mkOption types; + logLevels = types.enum [ "debug" "info" "notice" "warning" "error" "critical" "alert" "emergency" "off" ]; +in +{ + options.services.cardano-wallet = { + + enable = mkEnableOption "Cardano Wallet service"; + + serverArgs = mkOption { + description = "Command-line to launch cardano-wallet server."; + type = types.separatedString " "; + default = lib.concatStringsSep " " ([ + "--listen-address" + (lib.escapeShellArg cfg.listenAddress) + "--port" + (toString cfg.port) + "--node-socket" + (lib.escapeShellArg "$CARDANO_NODE_SOCKET_PATH") + "--sync-tolerance" + "${toString cfg.syncTolerance}s" + "--log-level" + cfg.logLevel + "--pool-metadata-fetching" + (if (cfg.poolMetadataFetching.enable) + then + (if cfg.poolMetadataFetching.smashUrl != null + then cfg.poolMetadataFetching.smashUrl else "direct") + else "none") + "--${cfg.walletMode}" + ] ++ lib.optional (cfg.walletMode != "mainnet") + (lib.escapeShellArg cfg.genesisFile) + ++ lib.optionals (cfg.tokenMetadataServer != null) + [ "--token-metadata-server" cfg.tokenMetadataServer ] + ++ lib.optionals (cfg.database != null) + [ "--database" (lib.escapeShellArg cfg.database) ] + ++ lib.mapAttrsToList + (name: level: "--trace-${name}=${level}") + cfg.trace + ); + }; + + command = mkOption { + type = types.str; + internal = true; + default = lib.concatStringsSep " " ([ + "${cfg.package}/bin/${cfg.package.exeName}" + "serve" + cfg.serverArgs + ] ++ lib.optionals (cfg.rtsOpts != "") [ "+RTS" cfg.rtsOpts "-RTS" ]); + }; + + package = mkOption { + type = types.package; + default = ((import ../.. { }).legacyPackages.${pkgs.system}).hsPkgs.cardano-wallet.components.exes.cardano-wallet; + description = "Package for the cardano wallet executable."; + }; + + genesisFile = mkOption { + type = types.nullOr (types.either types.str types.path); + default = null; + description = "Path to genesis file, if not running on mainnet."; + }; + + listenAddress = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Which host to bind the API server to."; + }; + + port = mkOption { + type = types.port; + default = 8090; + description = "The port on which the cardano-wallet HTTP API server will listen."; + }; + + nodeSocket = mkOption { + type = types.str; + default = "/run/cardano-node/node.socket"; + description = ''Cardano-Node local communication socket path.''; + }; + + walletMode = mkOption { + type = types.enum [ "mainnet" "staging" "testnet" ]; + default = "mainnet"; + description = "Which mode to start wallet in: --mainnet, --staging or --testnet"; + }; + + database = mkOption { + type = types.nullOr types.str; + default = "$STATE_DIRECTORY"; + description = ''Directory for storing wallets. + Run in-memory if null. + Default to '/var/lib/cardano-wallet'. + ''; + }; + + syncTolerance = mkOption { + type = types.ints.positive; + default = 300; + description = "Time duration within which we consider being synced with the network. Expressed in seconds."; + }; + + poolMetadataFetching = mkOption { + type = types.submodule { + options = { + enable = mkEnableOption "Stake pool metadata fetching."; + smashUrl = mkOption { + description = '' + URL of SMASH metadata proxy to use. + If null, metadata will be fetched directly from the + stake pool's URL. + ''; + type = types.nullOr types.str; + default = null; + }; + }; + }; + default = { enable = false; }; + example = { + enable = true; + smashUrl = "https://smash.cardano-mainnet.iohk.io"; + }; + }; + + tokenMetadataServer = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Sets the URL of the token metadata server, + default to 'metadataUrl' of the 'environnement' attribute, if exists. + If unset, metadata will not be fetched. By using this + option, you are fully trusting the operator of the + metadata server to provide authentic token metadata. + ''; + }; + + logLevel = mkOption { + type = logLevels; + default = "info"; + description = "Global minimum severity for a message to be logged."; + }; + + trace = mkOption { + type = types.attrsOf logLevels; + default = { }; + description = '' + For each tracer, minimum severity for a message to be logged, or + "off" to disable the tracer". + ''; + }; + + rtsOpts = mkOption { + type = types.separatedString " "; + default = "-N2"; + example = "-M2G -N4"; + description = '' + GHC runtime-system options for the cardano-wallet process. + See https://downloads.haskell.org/ghc/8.10.7/docs/html/users_guide/runtime_control.html#setting-rts-options-on-the-command-line for documentation. + ''; + }; + + }; + + config = mkIf cfg.enable { + + assertions = [ + { + assertion = (cfg.walletMode == "mainnet") == (cfg.genesisFile == null); + message = ''The option services.cardano-wallet.genesisFile must be set + if, and only if, services.cardano-wallet.walletMode is not \"mainnet\". + ''; + } + { + assertion = !(lib.hasPrefix "/" cfg.database || lib.hasPrefix ".." cfg.database); + message = "The option services.cardano-node.database should be a relative path (of /var/lib/)."; + } + ]; + + systemd.services.cardano-wallet = { + description = "cardano-wallet daemon"; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + DynamicUser = true; + ExecStart = cfg.command; + StateDirectory = if cfg.database == "$STATE_DIRECTORY" then "cardano-wallet" else cfg.database; + }; + + environment = { + CARDANO_NODE_SOCKET_PATH = cfg.nodeSocket; + }; + }; + }; +} diff --git a/nix/nixos/default.nix b/nix/nixos/default.nix new file mode 100644 index 00000000000..db413ea0ba9 --- /dev/null +++ b/nix/nixos/default.nix @@ -0,0 +1 @@ +{ imports = import ./module-list.nix; } diff --git a/nix/nixos/module-list.nix b/nix/nixos/module-list.nix new file mode 100644 index 00000000000..b97b96536a7 --- /dev/null +++ b/nix/nixos/module-list.nix @@ -0,0 +1,3 @@ +[ + ./cardano-wallet-service.nix +] diff --git a/nix/nixos/tests/default.nix b/nix/nixos/tests/default.nix new file mode 100644 index 00000000000..3be323cd8a6 --- /dev/null +++ b/nix/nixos/tests/default.nix @@ -0,0 +1,17 @@ +{ pkgs +, project +}: +let + importTest = fn: args: + let + imported = import fn; + test = import (pkgs.path + "/nixos/tests/make-test-python.nix") imported; + in + test ({ + inherit pkgs project; + inherit (pkgs) system config; + } // args); +in +{ + basicTest = importTest ./service-basic-test.nix { }; +} diff --git a/nix/nixos/tests/service-basic-test.nix b/nix/nixos/tests/service-basic-test.nix new file mode 100644 index 00000000000..9a5864341cb --- /dev/null +++ b/nix/nixos/tests/service-basic-test.nix @@ -0,0 +1,59 @@ +{ pkgs, project, lib, ... }: with pkgs; +let + inherit (project.hsPkgs.cardano-wallet.components.exes) cardano-wallet; + inherit (pkgs) cardanoLib; +in +{ + name = "wallet-nixos-test"; + nodes = { + machine = { config, ... }: { + nixpkgs.pkgs = pkgs; + imports = [ + ../. + (project.pkg-set.config.packages.cardano-node.src + "/nix/nixos") + ]; + services.cardano-wallet = { + enable = true; + package = cardano-wallet; + walletMode = "mainnet"; + nodeSocket = config.services.cardano-node.socketPath; + poolMetadataFetching = { + enable = true; + smashUrl = cardanoLib.environments.mainnet.smashUrl; + }; + tokenMetadataServer = cardanoLib.environments.mainnet.metadataUrl; + }; + services.cardano-node = { + enable = true; + environment = "mainnet"; + environments = { mainnet = { }; }; + package = project.hsPkgs.cardano-node.components.exes.cardano-node; + inherit (cardanoLib.environments.mainnet) nodeConfig; + topology = cardanoLib.mkEdgeTopology { + port = 3001; + edgeNodes = [ "127.0.0.1" ]; + }; + systemdSocketActivation = true; + }; + systemd.services.cardano-node.serviceConfig.Restart = lib.mkForce "no"; + systemd.services.cardano-wallet = { + after = [ "cardano-node.service" ]; + serviceConfig = { + Restart = "no"; + SupplementaryGroups = "cardano-node"; + }; + }; + }; + }; + testScript = '' + start_all() + machine.wait_for_unit("cardano-node.service") + machine.wait_for_open_port(3001) + machine.wait_for_unit("cardano-wallet.service") + machine.wait_for_open_port(8090) + machine.succeed( + "${cardano-wallet}/bin/cardano-wallet network information" + ) + ''; + +} diff --git a/nix/release-build.nix b/nix/release-build.nix index 5d808b8240c..b89b7578b34 100644 --- a/nix/release-build.nix +++ b/nix/release-build.nix @@ -11,7 +11,7 @@ with pkgs.lib; -pkgs.stdenv.mkDerivation rec { +let drv = pkgs.stdenv.mkDerivation rec { name = "${exe.identifier.name}-${version}"; version = exe.identifier.version; phases = [ "installPhase" ]; @@ -31,5 +31,8 @@ pkgs.stdenv.mkDerivation rec { '')); meta.platforms = platforms.all; - passthru = optionalAttrs (backend != null) { inherit backend; }; -} + passthru = { + exePath = drv + "/bin/cardano-wallet"; + } // (optionalAttrs (backend != null) { inherit backend; }); +}; +in drv diff --git a/nix/scripts.nix b/nix/scripts.nix new file mode 100644 index 00000000000..58cf5226b81 --- /dev/null +++ b/nix/scripts.nix @@ -0,0 +1,69 @@ +{ evalService +, project +, customConfigs +}: +with project.pkgs; +let + mkScript = envConfig: + let + service = evalService { + inherit pkgs customConfigs; + serviceName = "cardano-wallet"; + modules = [ + ./nixos/cardano-wallet-service.nix + ({ config, ... }: { + services.cardano-wallet = let cfg = config.services.cardano-wallet; in + { + package = lib.mkDefault project.hsPkgs.cardano-wallet.components.exes.cardano-wallet; + walletMode = lib.mkDefault ({ mainnet = "mainnet"; staging = "staging"; }.${envConfig.name} or "testnet"); + genesisFile = lib.mkIf (cfg.walletMode != "mainnet") + (lib.mkDefault envConfig.nodeConfig.ByronGenesisFile); + database = lib.mkDefault "./db-cardano-wallet-${envConfig.name}"; + poolMetadataFetching = lib.mkDefault { + enable = lib.mkDefault true; + smashUrl = lib.mkIf (envConfig ? smashUrl) + (lib.mkDefault envConfig.smashUrl); + }; + tokenMetadataServer = lib.mkIf (envConfig ? metadataUrl) (lib.mkDefault envConfig.metadataUrl); + }; + }) + ]; + }; + + in + writeScriptBin "cardano-wallet-${envConfig.name}" '' + #!${pkgs.runtimeShell} + set -euo pipefail + exec ${service.command} $@ + ''; + + debugDeps = with pkgs; [ + coreutils + findutils + gnugrep + gnused + postgresql + strace + lsof + dnsutils + bashInteractive + iproute + curl + netcat + bat + tree + ]; + +in +cardanoLib.forEnvironments (environment: lib.recurseIntoAttrs ( + let wallet = mkScript environment; + in + { + inherit wallet; + } // lib.optionalAttrs stdenv.buildPlatform.isLinux { + wallet-debug = pkgs.symlinkJoin { + inherit (wallet) name; + paths = [ wallet ] ++ debugDeps; + }; + } +))