diff --git a/configurations/preview.nix b/configurations/preview.nix deleted file mode 100644 index 27ced7a..0000000 --- a/configurations/preview.nix +++ /dev/null @@ -1,6 +0,0 @@ -{ - cardano = { - enable = true; - network = "preview"; - }; -} diff --git a/configurations/preview.nix b/configurations/preview.nix new file mode 120000 index 0000000..f3afdb6 --- /dev/null +++ b/configurations/preview.nix @@ -0,0 +1 @@ +../template/preview.nix \ No newline at end of file diff --git a/configurations/vm.nix b/configurations/vm.nix deleted file mode 100644 index 493f688..0000000 --- a/configurations/vm.nix +++ /dev/null @@ -1,31 +0,0 @@ -{modulesPath, ...}: { - imports = [ - (modulesPath + "/virtualisation/qemu-vm.nix") - (modulesPath + "/profiles/qemu-guest.nix") - ]; - - # WARNING: don't use this in production - # Allow root login without password, auto login - users.users.root.password = ""; - services.getty.autologinUser = "root"; - - virtualisation = { - cores = 2; - memorySize = 2048; - diskSize = 100 * 1024; - forwardPorts = [ - { - # cardano-node - from = "host"; - host.port = 3001; - guest.port = 3001; - } - { - # ogmios - from = "host"; - host.port = 1337; - guest.port = 1337; - } - ]; - }; -} diff --git a/configurations/vm.nix b/configurations/vm.nix new file mode 120000 index 0000000..b523562 --- /dev/null +++ b/configurations/vm.nix @@ -0,0 +1 @@ +../template/vm.nix \ No newline at end of file diff --git a/docs/getting-started/deploy.md b/docs/getting-started/deploy.md index 156bc2c..cccd64d 100644 --- a/docs/getting-started/deploy.md +++ b/docs/getting-started/deploy.md @@ -29,7 +29,7 @@ This [Nix Flake](https://zero-to-nix.com/concepts/flakes) is the entry point to - a NixOS configuration for the virtual machine, under `nixosConfigurations.server-vm` - an app to run the virtual machine as above, under `apps.x86_64-linux.server-vm` -#### `configuration.nix` +#### `preview.nix` This is the [NixOS configuration](https://zero-to-nix.com/concepts/nixos#configuration) to run cardano services for the machine. @@ -39,7 +39,7 @@ This NixOS configuration sets virtual machine options such as cores, memory and ### Customize -To learn more, browse available [NixOS options in nixpkgs](https://search.nixos.org/options) and [NixOS options provided by cardano.nix](../../reference/module-options/cardano/) (see other modules in the menu on the left). You can ad these options to `configuration.nix` to configure the system. +To learn more, browse available [NixOS options in nixpkgs](https://search.nixos.org/options) and [NixOS options provided by cardano.nix](../../reference/module-options/cardano/) (see other modules in the menu on the left). You can ad these options to `preview.nix` to configure the system. ### Deployment options diff --git a/docs/getting-started/vm.md b/docs/getting-started/vm.md index 1c35cb7..2737255 100644 --- a/docs/getting-started/vm.md +++ b/docs/getting-started/vm.md @@ -8,6 +8,7 @@ A virtual machine will be started with the following services, and the following | ------------ | ---- | | cardano-node | 3001 | | ogmios | 1337 | +| kupo | 1442 | You can log in with user `root`. The password is empty. In the virtual machine, `cardano-cli` is available to query the node. diff --git a/flake.nix b/flake.nix index c4637e3..3c44314 100644 --- a/flake.nix +++ b/flake.nix @@ -9,6 +9,7 @@ url = "github:intersectmbo/cardano-node?ref=8.7.3"; }; cardano-configurations = { + # This version is compatible with cardano-node above and likely needs to be updated together. url = "github:input-output-hk/cardano-configurations/21249e0d5c68b4e8f3661b250aa8272a8785d678"; flake = false; }; @@ -49,9 +50,11 @@ }; }; outputs = inputs @ {flake-parts, ...}: - flake-parts.lib.mkFlake { + flake-parts.lib.mkFlake + { inherit inputs; - } { + } + { debug = true; imports = [ ./checks diff --git a/modules/cardano.nix b/modules/cardano.nix index 1314748..cf1fa69 100644 --- a/modules/cardano.nix +++ b/modules/cardano.nix @@ -10,7 +10,7 @@ in # assert cfg.networkNumbers ? cfg.network; { options.cardano = { - enable = lib.mkEnableOption "all Cardano services and HTTP proxy."; + enable = lib.mkEnableOption "all Cardano services and HTTP proxy"; network = lib.mkOption { description = "Cardano network to operate on."; type = types.enum (lib.attrNames cfg.networkNumbers); diff --git a/modules/default.nix b/modules/default.nix index d3f2aaf..beeb00a 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -37,6 +37,15 @@ config.flake.overlays.ogmios ]; }; + kupo = { + imports = [ + ./services/kupo.nix + ./kupo.nix + ]; + nixpkgs.overlays = [ + config.flake.overlays.kupo + ]; + }; http = { imports = [ ./services/http-proxy.nix diff --git a/modules/http.nix b/modules/http.nix index 35f90e3..9b4bb1d 100644 --- a/modules/http.nix +++ b/modules/http.nix @@ -26,6 +26,10 @@ in { inherit (config.services.ogmios) port; inherit (config.services.ogmios.package) version; }; + kupo = { + inherit (config.services.kupo) port; + inherit (config.services.kupo.package) version; + }; }; }; }; diff --git a/modules/kupo.nix b/modules/kupo.nix new file mode 100644 index 0000000..a735652 --- /dev/null +++ b/modules/kupo.nix @@ -0,0 +1,40 @@ +{ + config, + lib, + ... +}: let + cfg = config.cardano.kupo; +in { + options.cardano.kupo = { + enable = + lib.mkEnableOption "Kupo chain-indexer" + // {default = config.cardano.enable or false;}; + }; + + config = lib.mkIf cfg.enable { + services.kupo = { + enable = true; + nodeSocketPath = + lib.mkIf (config.cardano.node.enable or false) + config.cardano.node.socketPath; + nodeConfigPath = + lib.mkIf (config.cardano.node.enable or false) + config.cardano.node.configPath; + ogmiosHost = + lib.mkIf (config.cardano.ogmios.enable or false) + "127.0.0.1"; + ogmiosPort = + lib.mkIf (config.cardano.ogmios.enable or false) + config.services.ogmios.port; + }; + + systemd.services.kupo = { + 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"; + }; + }; +} diff --git a/modules/ogmios.nix b/modules/ogmios.nix index 4f9c0bf..968fa70 100644 --- a/modules/ogmios.nix +++ b/modules/ogmios.nix @@ -14,6 +14,9 @@ in { config = lib.mkIf cfg.enable { services.ogmios = { enable = true; + nodeSocketPath = + lib.mkIf (config.cardano.node.enable or false) + config.cardano.node.socketPath or null; nodeConfigPath = lib.mkIf (config.cardano.node.enable or false) config.cardano.node.configPath or null; diff --git a/modules/services/kupo.nix b/modules/services/kupo.nix new file mode 100644 index 0000000..cc2b180 --- /dev/null +++ b/modules/services/kupo.nix @@ -0,0 +1,190 @@ +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.services.kupo; + inherit (lib) escapeShellArgs flatten types; +in + with lib; { + options.services.kupo = { + enable = mkEnableOption "Kupo Cardano chain-indexer"; + + package = mkOption { + description = "Kupo package."; + type = types.package; + default = pkgs.kupo; + }; + + user = mkOption { + description = "User to run kupo service as."; + type = types.nonEmptyStr; + default = "kupo"; + }; + + group = mkOption { + description = "Group to run kupo service as."; + type = types.nonEmptyStr; + default = "kupo"; + }; + + workDir = mkOption { + description = "Directory to start the kupo and store its data. Must start with `/var/lib/`."; + type = types.path; + default = "/var/lib/kupo"; + }; + + host = mkOption { + description = "Host address or name to listen on."; + type = types.nonEmptyStr; + default = "127.0.0.1"; + }; + + port = mkOption { + description = "TCP port to listen on."; + type = types.port; + default = 1442; + }; + + nodeSocketPath = mkOption { + description = "Path to cardano-node IPC socket. Ignored if `ogmiosHost` is not `null`."; + type = types.nullOr types.path; + default = "/run/cardano-node/node.socket"; + }; + + nodeConfigPath = mkOption { + description = "Path to cardano-node config.json file. Ignored if `ogmiosHost` is not `null`"; + type = types.path; + default = "/etc/cardano-node/config.json"; + }; + + ogmiosHost = mkOption { + description = "Ogmios host name. Optional, will connect to cardano-node if `null`."; + type = types.nullOr types.nonEmptyStr; + default = null; + }; + + ogmiosPort = mkOption { + description = "Ogmios port. Ignored if `ogmiosHost` is `null`."; + type = types.port; + default = 1337; + }; + + hydraHost = mkOption { + description = "Hydra host name. Optional."; + type = types.nullOr types.nonEmptyStr; + default = null; + }; + + hydraPort = mkOption { + description = "Hydra port. Ignored if `hydraHost` is `null`."; + type = types.port; + }; + + matches = mkOption { + description = "The list of addresses to watch."; + type = types.listOf types.nonEmptyStr; + default = ["*"]; + }; + + since = mkOption { + description = "Watching depth."; + type = types.nonEmptyStr; + default = "origin"; + }; + + pruneUtxo = mkOption { + description = "Automatically remove inputs that are spent on-chain."; + type = types.bool; + default = false; + }; + + extraArgs = mkOption { + description = "Extra arguments to kupo command."; + type = types.listOf types.str; + default = []; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = lib.hasPrefix "/var/lib/" cfg.workDir; + message = "`workDir` must start with `/var/lib/`"; + } + ]; + + users.users.kupo = mkIf (cfg.user == "kupo") { + isSystemUser = true; + inherit (cfg) group; + extraGroups = ["cardano-node"]; + }; + users.groups.kupo = mkIf (cfg.group == "kupo") {}; + + systemd.services.kupo = { + enable = true; + after = ["cardano-node.service" "ogmios.service"]; + wantedBy = ["multi-user.target"]; + + script = escapeShellArgs (flatten [ + ["${cfg.package}/bin/kupo"] + ["--workdir" cfg.workDir] + ["--host" cfg.host] + ["--port" cfg.port] + (optional (cfg.ogmiosHost == null) [ + ["--node-socket" cfg.nodeSocketPath] + ["--node-config" cfg.nodeConfigPath] + ]) + (optional (cfg.ogmiosHost != null) [ + ["--ogmios-host" cfg.ogmiosHost] + ["--ogmios-port" cfg.ogmiosPort] + ]) + (optional (cfg.hydraHost != null) [ + ["--hydra-host" cfg.hydraHost] + ["--hydra-port" cfg.hydraPort] + ]) + ["--since" cfg.since] + (map (m: ["--match" m]) cfg.matches) + (optional cfg.pruneUtxo "--prune-utxo") + cfg.extraArgs + ]); + + serviceConfig = { + User = cfg.user; + Group = cfg.group; + WorkingDirectory = cfg.workDir; + StateDirectory = lib.removePrefix "/var/lib/" cfg.workDir; + # Security + UMask = "0077"; + AmbientCapabilities = ["CAP_NET_BIND_SERVICE"]; + CapabilityBoundingSet = ["CAP_NET_BIND_SERVICE"]; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = ["~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid"]; + }; + }; + }; + } diff --git a/packages/default.nix b/packages/default.nix index 7193728..7a27545 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -6,11 +6,13 @@ in { imports = [ ./cardano.nix ./ogmios.nix + ./kupo.nix ]; flake.overlays = { cardano-cli = mkOverlay "cardano-cli"; cardano-node = mkOverlay "cardano-node"; cardano-configurations = mkOverlay "cardano-configurations"; ogmios = mkOverlay "ogmios"; + kupo = mkOverlay "kupo"; }; } diff --git a/packages/kupo.nix b/packages/kupo.nix new file mode 100644 index 0000000..f45c402 --- /dev/null +++ b/packages/kupo.nix @@ -0,0 +1,34 @@ +{ + perSystem = { + system, + pkgs, + ... + }: { + packages.kupo = let + version = "2.8.0"; + mkUrl = variant: "https://github.com/CardanoSolutions/kupo/releases/download/v2.8/kupo-${version}-${variant}.tar.gz"; + src = + pkgs.fetchurl + { + "x86_64-linux" = { + url = mkUrl "amd64-Linux"; + hash = "sha256-i4D0tWWBns3+L4UjfA0/UaLNqf4Jxb9v9CLamMnRQ24="; + }; + "aarch64-linux" = { + url = mkUrl "arm64-Linux"; + hash = "sha256-BpUD476g3Ilyv1CWyh1JSYIly8YIrXro+UQe9Pk/Teo="; + }; + } + .${system}; + in + pkgs.runCommandNoCC "kupo-${version}" { + inherit version; + meta.mainProgram = "kupo"; + } '' + mkdir $out + cd $out + ${pkgs.gnutar}/bin/tar -xvzf ${src} + chmod a+x $out/bin/kupo + ''; + }; +} diff --git a/packages/ogmios.nix b/packages/ogmios.nix index 44e77f4..8c9e142 100644 --- a/packages/ogmios.nix +++ b/packages/ogmios.nix @@ -4,30 +4,26 @@ pkgs, ... }: { - # Doesn't buid with haskell.nix but master contains a fix. Move to haskell.nix build on next release. - packages.ogmios = pkgs.stdenv.mkDerivation rec { - pname = "ogmios"; - version = "6.2.0"; + packages.ogmios = let + version = "6.1.0"; src = pkgs.fetchurl { url = "https://github.com/CardanoSolutions/ogmios/releases/download/v${version}/ogmios-v${version}-${system}.zip"; hash = - if system == "x86_64-linux" - then "sha256-Ryfuzu7JzbR7ivh2Sl9xyOuWh4btjCVSPhXfDLmepHk=u" - else if system == "aarch64-linux" - then "sha256-SJQWbIkXF2e4jJtq2hQFqSPO/EhbmLYeZx3aEaXv/gI=" - else abort "Ogmios release not available for system ${system}"; + { + "x86_64-linux" = "sha256-JQJTaws+gihwHpTBtUqcNhgbhFDUAdiiXrO2kMX4ZkY="; + "aarch64-linux" = "sha256-gWNTzUZHdp5rvbU0aIA3/GJ+YZeFx66h05ugJN4kMco="; + } + .${system}; }; - nativeBuildInputs = [pkgs.unzip]; - unpackPhase = '' - unzip $src + in + pkgs.runCommandNoCC "ogmios-${version}" { + inherit version; + meta.mainProgram = "ogmios"; + } '' + mkdir $out + cd $out + ${pkgs.unzip}/bin/unzip ${src} + chmod a+x $out/bin/ogmios ''; - buildPhase = '' - chmod a+x bin/ogmios - ''; - installPhase = '' - mkdir -p $out - mv bin share $out/ - ''; - }; }; } diff --git a/template/flake.nix b/template/flake.nix index 6cc598d..585cf60 100644 --- a/template/flake.nix +++ b/template/flake.nix @@ -10,7 +10,7 @@ system = "x86_64-linux"; modules = [ inputs.cardano-nix.nixosModules.default - ./configuration.nix + ./preview.nix ./vm.nix ]; }; diff --git a/template/configuration.nix b/template/preview.nix similarity index 100% rename from template/configuration.nix rename to template/preview.nix diff --git a/template/vm.nix b/template/vm.nix index b58c2ee..f064dd4 100644 --- a/template/vm.nix +++ b/template/vm.nix @@ -17,12 +17,20 @@ { # cardano-node from = "host"; - port = 3001; + host.port = 3001; + guest.port = 3001; } { # ogmios from = "host"; - port = 1337; + host.port = 1337; + guest.port = 1337; + } + { + # kupo + from = "host"; + host.port = 1442; + guest.port = 1442; } ]; }; diff --git a/tests/default.nix b/tests/default.nix index 0e044f0..c4ee832 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -3,6 +3,7 @@ ./cardano-cli.nix ./cardano-node.nix ./ogmios.nix + ./kupo.nix ./http.nix ]; } diff --git a/tests/http.nix b/tests/http.nix index 50e9c85..801930c 100644 --- a/tests/http.nix +++ b/tests/http.nix @@ -26,9 +26,9 @@ testScript = {nodes, ...}: '' start_all() node.wait_for_unit("ogmios") - node.wait_until_succeeds('curl --fail http://127.0.0.1:1337/health') + node.wait_until_succeeds('curl --silent --fail http://127.0.0.1:1337/health') proxy.wait_for_unit("nginx") - client.wait_until_succeeds('curl --fail -H "Host: ogmios" http://proxy/health') + client.wait_until_succeeds('curl --silent --fail -H "Host: ogmios" http://proxy/health') client.succeed('[ "${nodes.node.services.ogmios.package.version}" == "$(curl --silent --fail -H "Host: ogmios" http://proxy/version)" ]') ''; }; diff --git a/tests/kupo.nix b/tests/kupo.nix new file mode 100644 index 0000000..2d0f275 --- /dev/null +++ b/tests/kupo.nix @@ -0,0 +1,29 @@ +{ + perSystem.vmTests.tests.kupo = { + impure = true; + module = { + nodes .machine = {pkgs, ...}: { + cardano = { + network = "preview"; + cli.enable = true; + node.enable = true; + ogmios.enable = true; + kupo.enable = true; + }; + + environment.systemPackages = with pkgs; [jq bc curl]; + }; + + testScript = {nodes, ...}: let + magic = toString nodes.machine.config.cardano.networkNumber; + in '' + machine.wait_for_unit("cardano-node") + machine.wait_for_unit("cardano-node-socket") + machine.wait_until_succeeds("""[[ $(echo "$(cardano-cli query tip --testnet-magic ${magic} | jq '.syncProgress' --raw-output) > 0.001" | bc) == "1" ]]""") + machine.wait_for_unit("kupo") + machine.succeed("curl --silent --fail http://localhost:1442/health") + print(machine.succeed("systemd-analyze security kupo")) + ''; + }; + }; +} diff --git a/tests/ogmios.nix b/tests/ogmios.nix index 0d9c615..7435aee 100644 --- a/tests/ogmios.nix +++ b/tests/ogmios.nix @@ -20,8 +20,8 @@ machine.wait_for_unit("cardano-node-socket") machine.wait_until_succeeds("""[[ $(echo "$(cardano-cli query tip --testnet-magic ${magic} | jq '.syncProgress' --raw-output) > 0.001" | bc) == "1" ]]""") machine.wait_for_unit("ogmios") - machine.succeed("curl --fail http://localhost:1337/health") - machine.wait_until_succeeds("""[[ $(echo "$(curl --fail http://localhost:1337/health | jq '.networkSynchronization' --raw-output) > 0.00001" | bc) == "1" ]]""") + machine.succeed("curl --silent --fail http://localhost:1337/health") + machine.wait_until_succeeds("""[[ $(echo "$(curl --silent --fail http://localhost:1337/health | jq '.networkSynchronization' --raw-output) > 0.00001" | bc) == "1" ]]""") print(machine.succeed("systemd-analyze security ogmios")) ''; };