From d798b4b79cb72dbc92bff2828e0e8829edce6b5d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Giraudeau Date: Thu, 18 Feb 2021 14:30:17 +0100 Subject: [PATCH 1/2] nixos: easy systemd customization options for multi-instances setups --- nix/nixos/cardano-node-service.nix | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/nix/nixos/cardano-node-service.nix b/nix/nixos/cardano-node-service.nix index 2fd09968659..2e457e90c84 100644 --- a/nix/nixos/cardano-node-service.nix +++ b/nix/nixos/cardano-node-service.nix @@ -289,6 +289,24 @@ in { description = ''Use systemd socket activation''; }; + extraServiceConfig = mkOption { + # activate type for nixos-21.03: + # type = types.functionTo (types.listOf types.attrs); + default = n: i: {}; + description = '' + Extra systemd service config, given service name and instance index. + ''; + }; + + extraSocketConfig = mkOption { + # activate type for nixos-21.03: + # type = types.functionTo (types.listOf types.attrs); + default = n: i: {}; + description = '' + Extra systemd socket config, given socket name and instance index. + ''; + }; + dbPrefix = mkOption { type = types.str; default = "db-${cfg.environment}"; @@ -419,7 +437,7 @@ in { ## TODO: use http://hackage.haskell.org/package/systemd for: ## 1. only declaring success after we perform meaningful init (local state recovery) ## 2. heartbeat & watchdog functionality - systemd.services = genInstanceConf (n: i: { + systemd.services = genInstanceConf (n: i: recursiveUpdate ({ description = "cardano-node node ${toString i} service"; after = [ "network-online.target" ] ++ lib.optional cfg.systemdSocketActivation "${n}.socket"; @@ -438,12 +456,13 @@ in { NonBlocking = lib.mkIf cfg.systemdSocketActivation true; # time to sleep before restarting a service RestartSec = 1; + KillSignal = "SIGINT"; }; } // optionalAttrs (!cfg.instanced) { wantedBy = [ "multi-user.target" ]; - }); + }) (cfg.extraServiceConfig n i)); - systemd.sockets = genInstanceConf (n: i: lib.mkIf cfg.systemdSocketActivation { + systemd.sockets = genInstanceConf (n: i: lib.mkIf cfg.systemdSocketActivation (recursiveUpdate { description = "Socket of the ${n} service."; wantedBy = [ "sockets.target" ]; socketConfig = { @@ -454,7 +473,7 @@ in { SocketUser = "cardano-node"; SocketGroup = "cardano-node"; }; - }); + } (cfg.extraSocketConfig n i))); assertions = [ { From 91aa09d1e56194830d41ab24defc5c5807926637 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Giraudeau Date: Thu, 18 Feb 2021 16:41:01 +0100 Subject: [PATCH 2/2] nixos: oneshot service start allows to easily control all instances at once --- nix/nixos/cardano-node-service.nix | 144 +++++++++++++++++------------ 1 file changed, 84 insertions(+), 60 deletions(-) diff --git a/nix/nixos/cardano-node-service.nix b/nix/nixos/cardano-node-service.nix index 2e457e90c84..cea845317e5 100644 --- a/nix/nixos/cardano-node-service.nix +++ b/nix/nixos/cardano-node-service.nix @@ -426,64 +426,88 @@ in { stateDirBase = "/var/lib/"; genInstanceConf = f: listToAttrs (if cfg.instances > 1 && !cfg.instanced then genList (i: let n = "${systemdServiceName}-${toString i}"; in nameValuePair n (f n i)) cfg.instances - else [ (nameValuePair systemdServiceName (f systemdServiceName 0)) ]); in { - users.groups.cardano-node.gid = 10016; - users.users.cardano-node = { - description = "cardano-node node daemon user"; - uid = 10016; - group = "cardano-node"; - }; - - ## TODO: use http://hackage.haskell.org/package/systemd for: - ## 1. only declaring success after we perform meaningful init (local state recovery) - ## 2. heartbeat & watchdog functionality - systemd.services = genInstanceConf (n: i: recursiveUpdate ({ - description = "cardano-node node ${toString i} service"; - after = [ "network-online.target" ] - ++ lib.optional cfg.systemdSocketActivation "${n}.socket"; - requires = lib.mkIf cfg.systemdSocketActivation [ "${n}.socket" ]; - wants = [ "network-online.target" ]; - script = mkScript cfg i; - serviceConfig = { - User = "cardano-node"; - Group = "cardano-node"; - Restart = "always"; - RuntimeDirectory = cfg.runtimeDir; - WorkingDirectory = cfg.stateDir; - # This assumes /var/lib/ is a prefix of cfg.stateDir. - # This is checked as an assertion below. - StateDirectory = lib.removePrefix stateDirBase cfg.stateDir; - NonBlocking = lib.mkIf cfg.systemdSocketActivation true; - # time to sleep before restarting a service - RestartSec = 1; - KillSignal = "SIGINT"; - }; - } // optionalAttrs (!cfg.instanced) { - wantedBy = [ "multi-user.target" ]; - }) (cfg.extraServiceConfig n i)); - - systemd.sockets = genInstanceConf (n: i: lib.mkIf cfg.systemdSocketActivation (recursiveUpdate { - description = "Socket of the ${n} service."; - wantedBy = [ "sockets.target" ]; - socketConfig = { - ListenStream = [ "${cfg.hostAddr}:${toString cfg.port}" ] - ++ [(if (i == 0) then cfg.socketPath else "${runtimeDir}/node-${toString i}.socket")]; - ReusePort = "yes"; - SocketMode = "0660"; - SocketUser = "cardano-node"; - SocketGroup = "cardano-node"; - }; - } (cfg.extraSocketConfig n i))); - - assertions = [ - { - assertion = lib.hasPrefix stateDirBase cfg.stateDir; - message = "The option services.cardano-node.stateDir should have ${stateDirBase} as a prefix!"; - } - { - assertion = (cfg.kesKey == null) == (cfg.vrfKey == null) && (cfg.kesKey == null) == (cfg.operationalCertificate == null); - message = "Shelley Era: all of three [operationalCertificate kesKey vrfKey] options must be defined (or none of them)."; - } - ]; - }); + else [ (nameValuePair systemdServiceName (f systemdServiceName 0)) ]); in lib.mkMerge [ + { + users.groups.cardano-node.gid = 10016; + users.users.cardano-node = { + description = "cardano-node node daemon user"; + uid = 10016; + group = "cardano-node"; + }; + + ## TODO: use http://hackage.haskell.org/package/systemd for: + ## 1. only declaring success after we perform meaningful init (local state recovery) + ## 2. heartbeat & watchdog functionality + systemd.services = genInstanceConf (n: i: recursiveUpdate { + description = "cardano-node node ${toString i} service"; + after = [ "network-online.target" ] + ++ (optional cfg.systemdSocketActivation "${n}.socket") + ++ (optional (cfg.instances > 1) "cardano-node.service"); + requires = optional cfg.systemdSocketActivation "${n}.socket" + ++ (optional (cfg.instances > 1) "cardano-node.service"); + wants = [ "network-online.target" ]; + wantedBy = mkIf (!cfg.instanced) [ "multi-user.target" ]; + partOf = mkIf (cfg.instances > 1) ["cardano-node.service"]; + script = mkScript cfg i; + serviceConfig = { + User = "cardano-node"; + Group = "cardano-node"; + Restart = "always"; + RuntimeDirectory = cfg.runtimeDir; + WorkingDirectory = cfg.stateDir; + # This assumes /var/lib/ is a prefix of cfg.stateDir. + # This is checked as an assertion below. + StateDirectory = lib.removePrefix stateDirBase cfg.stateDir; + NonBlocking = lib.mkIf cfg.systemdSocketActivation true; + # time to sleep before restarting a service + RestartSec = 1; + KillSignal = "SIGINT"; + }; + } (cfg.extraServiceConfig n i)); + + systemd.sockets = genInstanceConf (n: i: lib.mkIf cfg.systemdSocketActivation (recursiveUpdate { + description = "Socket of the ${n} service."; + wantedBy = [ "sockets.target" ]; + partOf = [ "${n}.service" ]; + socketConfig = { + ListenStream = [ "${cfg.hostAddr}:${toString cfg.port}" ] + ++ [(if (i == 0) then cfg.socketPath else "${runtimeDir}/node-${toString i}.socket")]; + ReusePort = "yes"; + SocketMode = "0660"; + SocketUser = "cardano-node"; + SocketGroup = "cardano-node"; + }; + } (cfg.extraSocketConfig n i))); + } + { + # oneshot service start allows to easily control all instances at once. + systemd.services.cardano-node = lib.mkIf (cfg.instances > 1) { + description = "Control all ${toString cfg.instances} at once."; + enable = true; + wants = genList (i: "cardano-node-${toString i}.service") cfg.instances; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = "yes"; + User = "cardano-node"; + Group = "cardano-node"; + ExecStart = "${pkgs.coreutils}/bin/echo Starting ${toString cfg.instances} cardano-node instances"; + RuntimeDirectory = cfg.runtimeDir; + WorkingDirectory = cfg.stateDir; + StateDirectory = lib.removePrefix stateDirBase cfg.stateDir; + }; + }; + } + { + assertions = [ + { + assertion = lib.hasPrefix stateDirBase cfg.stateDir; + message = "The option services.cardano-node.stateDir should have ${stateDirBase} as a prefix!"; + } + { + assertion = (cfg.kesKey == null) == (cfg.vrfKey == null) && (cfg.kesKey == null) == (cfg.operationalCertificate == null); + message = "Shelley Era: all of three [operationalCertificate kesKey vrfKey] options must be defined (or none of them)."; + } + ]; + } + ]); }