diff --git a/docs/render.nix b/docs/render.nix index ce48b3b..b2b6371 100644 --- a/docs/render.nix +++ b/docs/render.nix @@ -209,7 +209,11 @@ in evaluated = lib.evalModules { modules = modules ++ [ { - imports = builtins.import "${inputs.nixpkgs}/nixos/modules/module-list.nix"; + # We need standard NixOS modules _plus_ `cardano.providers` inside context of evaluation, because lof of modules + # refers to them in `default` statement. + # Also we couldn't use `builtins.import`, because it raise a conflict in option definitions, + # so use nested module with `imports = []` statement. + imports = builtins.import "${inputs.nixpkgs}/nixos/modules/module-list.nix" ++ [ { imports = [ ../modules/providers.nix ]; } ]; nixpkgs.system = system; } ]; diff --git a/flake.lock b/flake.lock index 55be294..0420d5b 100644 --- a/flake.lock +++ b/flake.lock @@ -564,6 +564,22 @@ "type": "github" } }, + "demeter-run-cli": { + "flake": false, + "locked": { + "lastModified": 1740775656, + "narHash": "sha256-bCyNwn+nmU/wtWvwLig/uFmKlGkjIcpJJgetZJGn3hk=", + "owner": "demeter-run", + "repo": "cli", + "rev": "1b9ef1c4b864dcb22c37b07e9162736b920553eb", + "type": "github" + }, + "original": { + "owner": "demeter-run", + "repo": "cli", + "type": "github" + } + }, "devour-flake": { "flake": false, "locked": { @@ -2775,6 +2791,7 @@ "cardano-node": "cardano-node", "cardano-node-nixos-module-fixed": "cardano-node-nixos-module-fixed", "crane": "crane", + "demeter-run-cli": "demeter-run-cli", "devour-flake": "devour-flake", "devshell": "devshell", "flake-parts": "flake-parts", diff --git a/flake.nix b/flake.nix index 88f0c2d..f625e92 100644 --- a/flake.nix +++ b/flake.nix @@ -24,6 +24,10 @@ url = "github:txpipe/oura/v1.9.4"; inputs.crane.follows = "crane"; }; + demeter-run-cli = { + url = "github:demeter-run/cli"; + flake = false; + }; crane = { url = "github:ipetkov/crane"; }; diff --git a/modules/db-sync.nix b/modules/db-sync.nix index bf181b6..bee8b08 100644 --- a/modules/db-sync.nix +++ b/modules/db-sync.nix @@ -46,7 +46,7 @@ in services.cardano-db-sync = { enable = true; environment = config.services.cardano-node.environments.${config.cardano.network}; - inherit (config.cardano.node) socketPath; + inherit (config.cardano.providers.node) socketPath; postgres = { user = "cardano-db-sync"; # use first socket from postgresql settings or default to /run/postgresql @@ -59,6 +59,8 @@ in systemd.services.cardano-db-sync = { serviceConfig = { User = "cardano-db-sync"; + # Default db-sync service hardcode "cardano-node" + SupplementaryGroups = config.cardano.providers.node.accessGroup; # Security UMask = "0077"; CapabilityBoundingSet = ""; @@ -90,10 +92,10 @@ in }; }; }) - (mkIf (cfg.enable && config.cardano.node.enable or false) { + (mkIf (cfg.enable && config.cardano.providers.node.active) { systemd.services.cardano-db-sync = { - after = [ "cardano-node-socket.service" ]; - requires = [ "cardano-node-socket.service" ]; + after = [ config.cardano.providers.node.after ]; + requires = [ config.cardano.providers.node.requires ]; }; }) (mkIf (cfg.enable && cfg.postgres.enable) { diff --git a/modules/default.nix b/modules/default.nix index 2e2677a..17bf460 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -10,6 +10,7 @@ cardano = { imports = [ ./cardano.nix + ./providers.nix ]; }; cli = { @@ -72,6 +73,12 @@ ./monitoring.nix ]; }; + demeter-run = { + imports = [ + ./demeter-run.nix + ./services/demeter-run.nix + ]; + }; # the default module imports all modules default = { imports = [ diff --git a/modules/demeter-run.nix b/modules/demeter-run.nix new file mode 100644 index 0000000..c4272ed --- /dev/null +++ b/modules/demeter-run.nix @@ -0,0 +1,54 @@ +{ config, lib, ... }: + +let + cfg = config.cardano.demeter-run; + dmtr_cfg = config.services.demeter-run; + inherit (lib) + mkEnableOption + mkIf + mkOption + types + ; +in +{ + options.cardano.demeter-run = { + node = { + enable = mkEnableOption "Demeter run tunnel"; + instance = mkOption { + type = types.str; + }; + + configFile = mkOption { + type = types.str; + description = '' + Config file for demeter setup (contain secrets, use agenix or sops) + + This is config file generated by dmtrctl init ... command invocation, + contains random generated id, and token is obviously a secret obtained from demeter service during init. + ''; + }; + }; + + # FIXME: not implemented yet + kupo = { }; + + # FIXME: not implemented yet + ogmios = { }; + }; + + config = mkIf cfg.node.enable { + services.demeter-run = { + enable = true; + inherit (cfg.node) instance; + inherit (cfg.node) configFile; + }; + + # Register as cardano-node socket provider + cardano.providers.node = { + socketPath = dmtr_cfg.socket; + accessGroup = dmtr_cfg.group; + requires = "demeter-run.service"; + after = "demeter-run.service"; + }; + }; +} diff --git a/modules/kupo.nix b/modules/kupo.nix index 9a030ac..4d2e558 100644 --- a/modules/kupo.nix +++ b/modules/kupo.nix @@ -24,5 +24,13 @@ in after = lib.optional (config.cardano.node.enable or false) "cardano-node-socket.service" ++ lib.optional (config.cardano.ogmios.enable or false) "ogmios.service"; requires = lib.optional (config.cardano.node.enable or false) "cardano-node-socket.service" ++ lib.optional (config.cardano.ogmios.enable or false) "ogmios.service"; }; + + # Register as default Kupo provider for others `cardano.nix` consumers + cardano.providers.kupo = { + active = true; + inherit (config.service.kupo) host port; + after = "kupo.service"; + requires = "kupo.service"; + }; }; } diff --git a/modules/node.nix b/modules/node.nix index ee1e9c4..78eb684 100644 --- a/modules/node.nix +++ b/modules/node.nix @@ -111,5 +111,14 @@ in chmod g+rw ${cfg.socketPath} ''; }; + + # Register as default node socket for others `cardano.nix` consumers + cardano.providers.node = { + active = true; + inherit (cfg) socketPath; + accessGroup = "cardano-node"; + requires = "cardano-node-socket.service"; + after = "cardano-node-socket.service"; + }; }; } diff --git a/modules/ogmios.nix b/modules/ogmios.nix index 5dd6e11..3a94e1e 100644 --- a/modules/ogmios.nix +++ b/modules/ogmios.nix @@ -22,5 +22,13 @@ in after = [ "cardano-node-socket.service" ]; requires = [ "cardano-node-socket.service" ]; }; + + # Register as default Ogmios provider for others `cardano.nix` consumers + cardano.providers.ogmios = { + active = true; + inherit (config.service.ogmios) host port; + after = "ogmios.service"; + requires = "ogmios.service"; + }; }; } diff --git a/modules/oura.nix b/modules/oura.nix index f4e3b41..dd58893 100644 --- a/modules/oura.nix +++ b/modules/oura.nix @@ -31,7 +31,7 @@ in type = "N2C"; address = [ "Unix" - config.cardano.node.socketPath + config.cardano.providers.node.socketPath ]; magic = config.cardano.network; }; @@ -39,9 +39,9 @@ in }; }; - systemd.services.oura = lib.mkIf (config.cardano.node.enable or false) { - after = [ "cardano-node-socket.service" ]; - requires = [ "cardano-node-socket.service" ]; + systemd.services.oura = lib.mkIf config.cardano.providers.node.active { + after = [ config.cardano.providers.node.after ]; + requires = [ config.cardano.providers.node.requires ]; }; }; } diff --git a/modules/providers.nix b/modules/providers.nix new file mode 100644 index 0000000..41585bf --- /dev/null +++ b/modules/providers.nix @@ -0,0 +1,84 @@ +# Design defence: +# The goal of this module is to introduce an indirection layer for service +# dependencies: consumers reference a provider instead of binding to a specific +# systemd unit. This makes it possible to transparently switch between a local +# cardano-node and a remote tunnel (e.g. from Demeter run). Common options are +# factored into `sharedOptions` to unify the contract and reduce duplication. +# Options are currently marked as `internal = true` until proper documentation +# is written. +{ + lib, + ... +}: +let + inherit (lib) mkOption types; + sharedOptions = { + active = mkOption { + type = types.bool; + internal = true; + default = false; + description = '' + Mark service provider as active + ''; + }; + requires = mkOption { + type = types.str; + internal = true; + description = '' + Systemd's service name to add to `requires` by all consumers + ''; + }; + after = mkOption { + type = types.str; + internal = true; + description = '' + Systemd's service name to add to `after` by all consumers + ''; + }; + }; + socketOptions = name: { + accessGroup = mkOption { + type = types.str; + internal = true; + description = '' + Group to access ${name} service provider + ''; + }; + socketPath = mkOption { + type = types.path; + internal = true; + description = '' + Path to ${name} socket path, to refer by consumers + ''; + }; + }; + tcpOptions = name: { + host = mkOption { + type = types.str; + internal = true; + description = '' + Host address for TCP connection to ${name}, to refer by consumers. + ''; + }; + port = mkOption { + type = types.port; + internal = true; + description = '' + Port address for TCP connection to ${name}, to refer by consumers. + ''; + }; + }; +in +{ + options.cardano.providers = mkOption { + description = "Abstraction layer to plug in different providers of cardano-node/ogmios/kupo/etc"; + internal = true; + type = types.submodule { + options = { + node = sharedOptions // socketOptions "cardano node"; + ogmios = sharedOptions // tcpOptions "Ogmios"; + kupo = sharedOptions // tcpOptions "Kupo"; + }; + }; + }; +} diff --git a/modules/services/demeter-run.nix b/modules/services/demeter-run.nix new file mode 100644 index 0000000..b8f9c0e --- /dev/null +++ b/modules/services/demeter-run.nix @@ -0,0 +1,99 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.demeter-run; +in +{ + options.services.demeter-run = with lib; { + enable = mkEnableOption "Demeter run tunnel"; + + package = mkOption { + type = types.package; + description = "The demeter-run-cli package."; + default = pkgs.demeter-run-cli; + }; + + user = mkOption { + type = types.str; + default = "demeter-run"; + description = "User account under which demeter-run is run"; + }; + + group = mkOption { + type = types.str; + default = "demeter-run"; + description = "Group account under which demeter-run is run"; + }; + + socket = mkOption { + type = types.str; + default = "${cfg.socketDir}/node.socket"; + }; + + socketDir = mkOption { + type = types.str; + default = "/run/demeter-run"; + }; + + instance = mkOption { + type = types.str; + }; + + configFile = mkOption { + type = types.path; + description = '' + Config file for demeter setup (contain secrets, use agenix or sops) + ''; + }; + }; + config = lib.mkIf cfg.enable { + environment.systemPackages = [ pkgs.demeter-run-cli ]; + users.users.${cfg.user} = { + inherit (cfg) group; + isSystemUser = true; + }; + + users.groups.${cfg.group} = { }; + systemd.tmpfiles.rules = [ + "d '${cfg.socketDir}' - ${cfg.user} ${cfg.group} - -" + ]; + + systemd.services.demeter-run = { + enable = true; + path = [ cfg.package ]; + wants = [ + "network-online.target" + ]; + + script = '' + rm -f ${cfg.socket} + ${cfg.package}/bin/dmtrctl ports tunnel --socket ${cfg.socket} ${cfg.instance} + ''; + + preStart = '' + ${pkgs.coreutils}/bin/install -m 0700 -g ${cfg.group} -o ${cfg.user} ${cfg.configFile} ${cfg.socketDir}/config.toml + ''; + + # Prevent secret leaking + postStop = '' + rm ${cfg.socketDir}/config.toml + ''; + + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Restart = "always"; + RestartSec = 10; + Group = cfg.group; + User = cfg.user; + Environment = [ + "DMTR_ROOT_DIR=${cfg.socketDir}" + ]; + UMask = "006"; + }; + }; + }; +} diff --git a/packages/default.nix b/packages/default.nix index 0131145..cd77f5c 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -2,6 +2,7 @@ { imports = [ ./cardano.nix + ./demeter-run-cli.nix ./ogmios.nix ./kupo.nix ./oura.nix @@ -11,6 +12,7 @@ inherit ((config.perSystem final.system).packages) cardano-cli cardano-node + demeter-run-cli ogmios kupo oura diff --git a/packages/demeter-run-cli.nix b/packages/demeter-run-cli.nix new file mode 100644 index 0000000..b698df1 --- /dev/null +++ b/packages/demeter-run-cli.nix @@ -0,0 +1,26 @@ +{ inputs, ... }: +{ + perSystem = + { pkgs, ... }: + { + packages = + let + craneLib = inputs.crane.mkLib pkgs; + commonArgs = { + pname = "demeter-run-cli"; + version = "0-unstable-git-${inputs.demeter-run-cli.shortRev}"; + strictDeps = true; + src = inputs.demeter-run-cli.outPath; + }; + demeter-run-cli = craneLib.buildPackage ( + commonArgs + // { + cargoArtifacts = craneLib.buildDepsOnly commonArgs; + } + ); + in + { + inherit demeter-run-cli; + }; + }; +}