diff --git a/README.md b/README.md index 2726451..732b848 100644 --- a/README.md +++ b/README.md @@ -23,23 +23,46 @@ Don't edit `~/.config/nix/nix.conf` in your home directory. Don't add users to ` ### Development Shell -`cardano.nix` provides a devshell that includes some useful tools and aliases: +Development is supported on linux systems. Virtual machines are run with `qemu` so `kvm` is recommended. + +`cardano.nix` provides a devshell that includes various tools to build, test, run and update the project: ``` ❯ nix develop ... ❄️ Welcome to the cardano.nix devshell ❄️ -... -[Tools] - build-all - Build all the checks - check - Alias of `nix flake check` - fmt - Format the source tree -... +[documentation] + + docs-build - build documentation + docs-serve - serve documentation web page + +[general commands] + + menu - prints this menu + +[tests] + + build-all - build all packages and checks with `devour-flake` + check - run `nix flake check` + run-vm-test - list and run virtual machine integration tests + +[tools] + + fmt - format the source tree + update-pre-commit-hooks - update git pre-commit hooks ``` A `.envrc` is also provided, using [direnv]() and [nix-direnv](https://github.com/nix-community/nix-direnv) is suggested. +### Running Integration Tests + +From the devshell you can run integration tests with `run-vm-test`, for example the following will start `cardano-node` and `ogmios` on the `preview` testnet and will check for synchronization progress. + +``` +run-vm-test ogmios +``` + ## License information `cardano.nix` released under terms of [Apache-2.0](LICENSES/Apache-2.0.txt) license. diff --git a/checks/default.nix b/checks/default.nix index bec963f..6e0445d 100644 --- a/checks/default.nix +++ b/checks/default.nix @@ -1,6 +1,6 @@ {inputs, ...}: { imports = [ - ./nixosTests.nix + ./vmTests.nix ./licenses.nix ]; perSystem = { @@ -26,15 +26,15 @@ devshells.default.commands = [ { - category = "Tools"; + category = "tests"; name = "build-all"; - help = "Build all the checks"; + help = "build all packages and checks with `devour-flake`"; command = config.apps.nix-build-all.program; } { - category = "Tools"; + category = "tests"; name = "check"; - help = "Alias of `nix flake check`"; + help = "run `nix flake check`"; command = "nix flake check"; } ]; diff --git a/checks/nixosTests.nix b/checks/nixosTests.nix deleted file mode 100644 index bbaa9b2..0000000 --- a/checks/nixosTests.nix +++ /dev/null @@ -1,145 +0,0 @@ -{ - lib, - inputs, - config, - withSystem, - ... -}: let - inherit (lib) mkOption types mapAttrs' nameValuePair; - inherit (config.flake) nixosModules; -in { - perSystem = { - config, - lib, - system, - pkgs, - ... - }: let - cfg = config.nixosTests; - in { - options.nixosTests = { - tests = mkOption { - description = "NixOS tests as modules."; - type = types.lazyAttrsOf (types.submodule ({config, ...}: { - options = { - name = mkOption { - description = "The name of the test."; - type = types.str; - default = config._module.args.name; - internal = true; - }; - systems = mkOption { - description = "The systems to run the tests on."; - type = types.listOf types.str; - default = ["x86_64-linux"]; - }; - module = mkOption { - description = "The test module. Required."; - type = types.deferredModule; - }; - documentation = mkOption { - description = "Wether to generate documentation for the testnixos configuraion. False by default to speed up builds."; - type = types.bool; - default = false; - }; - specialArgs = mkOption { - description = "The specialArgs to pass to the test node."; - type = types.attrsOf types.anything; - default = {}; - }; - impure = mkOption { - description = "Wether the test requires internet access and should be run as an effect instead of a nix build."; - type = types.bool; - default = false; - }; - check = mkOption { - description = "The test derivation composed with _mkCheckFromTest from the module."; - type = types.package; - default = cfg._mkCheckFromTest config; - }; - checkEffect = mkOption { - description = "The test hercules-ci-effect composed with _mkEffectFromTest from the module."; - type = types.package; - default = cfg._mkEffectFromTest config; - }; - }; - })); - }; - runTestScript = mkOption { - type = types.package; - default = pkgs.callPackage ./run-test.nix {inherit (cfg) tests;}; - description = "A convenience script to run tests"; - }; - _nixosLib = mkOption { - type = types.anything; - default = import (inputs.nixpkgs.outPath + "/nixos/lib") {}; - internal = true; - }; - _mkCheckFromTest = mkOption { - type = types.functionTo types.package; - internal = true; - default = test: - (cfg._nixosLib.runTest { - hostPkgs = pkgs; - - defaults.documentation.enable = test.documentation; - - node = { - inherit (test) specialArgs; - }; - - defaults.imports = [ - # import all of our flake nixos modules by default - nixosModules.default - # fix missing pkgs.system in tests - {nixpkgs.overlays = [(_: _: {inherit system;})];} - ]; - - # import the test module - imports = [test.module]; - }) - .config - .result; - }; - _mkEffectFromTest = mkOption { - type = types.functionTo types.package; - internal = true; - default = test: - withSystem system ({hci-effects, ...}: - hci-effects.modularEffect { - mounts."/dev/kvm" = "kvm"; - effectScript = '' - ${test.check.driver}/bin/nixos-test-driver - ''; - }); - }; - }; - - config = { - checks = - mapAttrs' - (name: test: nameValuePair "nixosTests-${test.name}" test.check) - (lib.filterAttrs - (_: v: lib.elem system v.systems && !v.impure) - cfg.tests); - - apps.run-test.program = lib.getExe cfg.runTestScript; - - devshells.default.commands = [ - { - name = "run-test"; - category = "testing"; - help = "Run tests"; - command = "${lib.getExe cfg.runTestScript} $@"; - } - ]; - }; - }; - - herculesCI.onPush.default.outputs.effects = - mapAttrs' - (name: test: nameValuePair "nixosTests-${test.name}" test.checkEffect) - (lib.filterAttrs - (_: v: lib.elem config.defaultEffectSystem v.systems && v.impure) - (config.perSystem config.defaultEffectSystem).nixosTests.tests); -} diff --git a/checks/run-test.nix b/checks/run-vm-test.nix similarity index 69% rename from checks/run-test.nix rename to checks/run-vm-test.nix index 9c8c82e..e18b510 100644 --- a/checks/run-test.nix +++ b/checks/run-vm-test.nix @@ -6,7 +6,7 @@ ... }: writeShellApplication { - name = "run-test"; + name = "run-vm-test"; runtimeInputs = []; @@ -14,21 +14,21 @@ writeShellApplication { cmd_name=$(basename "$0") help() { - echo " build and run a test" + echo " Build and run integration test on a network of virtual machines." echo - echo " usage:" + echo " Usage:" echo " $cmd_name " echo " $cmd_name --interactive" echo " $cmd_name -s " echo - echo " arguments:" + echo " Arguments:" echo " " echo - echo " options:" - echo " -h --help show this screen." - echo " -l --list show available tests." - echo " -s --system specify the target platform [default: ${stdenv.system}]." - echo " -i --interactive run the test interactively." + echo " Options:" + echo " -h --help Show this screen." + echo " -l --list Show available tests." + echo " -s --system Specify the target platform [default: ${stdenv.system}]." + echo " -i --interactive Run the test interactively." echo } @@ -65,6 +65,7 @@ writeShellApplication { shift # build/run the test driver, passing any remaining arguments - nix run ".#checks.$system.testing-$name.driver" "''${nix_args}" -- "''${driver_args[@]}" + # shellcheck disable=SC2068,SC2086 + nix run ".#apps.$system.vmTests-$name" ''${nix_args} -- ''${driver_args[@]} ''; } diff --git a/checks/vmTests.nix b/checks/vmTests.nix new file mode 100644 index 0000000..67f41c4 --- /dev/null +++ b/checks/vmTests.nix @@ -0,0 +1,148 @@ +{ + lib, + inputs, + config, + withSystem, + ... +}: let + inherit (lib) mkOption types mapAttrs' nameValuePair; + inherit (config.flake) nixosModules; +in { + perSystem = { + config, + lib, + system, + pkgs, + ... + }: let + cfg = config.vmTests; + in { + options.vmTests = { + tests = mkOption { + description = "Run integration tests in networks of virtual machines."; + type = types.lazyAttrsOf (types.submodule ({config, ...}: { + options = { + name = mkOption { + description = "The name of the test. Defaults to attribute name."; + internal = true; + type = types.str; + default = config._module.args.name; + }; + systems = mkOption { + description = "The systems to run the test on."; + type = types.listOf types.str; + default = ["x86_64-linux"]; + }; + module = mkOption { + description = "The NixOS test module. Required. See https://nixos.org/manual/nixos/stable/#sec-nixos-tests ."; + type = types.deferredModule; + }; + impure = mkOption { + description = "Wether the test requires internet access and should be run as an effect instead of a nix build."; + type = types.bool; + default = false; + }; + check = mkOption { + description = "The test derivation. Result of calling `_mkCheck` with this test."; + type = types.package; + default = cfg._mkCheck config; + }; + effect = mkOption { + description = "The test hercules-ci-effect. Result of calling `_mkEffect` with this test."; + type = types.package; + default = cfg._mkEffect config; + }; + }; + })); + }; + runVmTestScript = mkOption { + description = "Script that lists and runs integration tests on networks of virtual machines."; + type = types.package; + default = pkgs.callPackage ./run-vm-test.nix {inherit (cfg) tests;}; + }; + _nixosLib = mkOption { + description = "Convenience access to `nixpkgs/nixos/lib`."; + internal = true; + type = types.anything; + default = import (inputs.nixpkgs.outPath + "/nixos/lib") {}; + }; + _mkCheck = mkOption { + description = "Function that takes a test `module` and returns a derivation that runs the test when built."; + internal = true; + type = types.functionTo types.package; + default = test: + (cfg._nixosLib.runTest { + name = lib.mkDefault test.name; + imports = [test.module]; + hostPkgs = pkgs; + defaults = { + imports = [ + # Import all of our NixOS modules by default. + nixosModules.default + # Fix missing `pkgs.system` in tests. + {nixpkgs.overlays = [(_: _: {inherit system;})];} + ]; + documentation.enable = lib.mkDefault false; + }; + }) + .config + .result; + }; + _mkEffect = mkOption { + description = "Function that takes a test `module` and returns a Hercules CI effect that runs the test."; + internal = true; + type = types.functionTo types.package; + default = testModule: + withSystem system ({hci-effects, ...}: + hci-effects.modularEffect { + mounts."/dev/kvm" = "kvm"; + effectScript = '' + ${testModule.check.driver}/bin/nixos-test-driver + ''; + }); + }; + }; + + config = { + checks = + mapAttrs' + (name: test: + nameValuePair + "vmTests-${test.name}" + test.check) + (lib.filterAttrs + (_: v: lib.elem system v.systems && !v.impure) + cfg.tests); + + apps = + {run-vm-tests.program = lib.getExe cfg.runVmTestScript;} + // mapAttrs' + (name: test: + nameValuePair + "vmTests-${test.name}" + {program = "${test.check.driver}/bin/nixos-test-driver";}) + (lib.filterAttrs + (_: v: lib.elem system v.systems) + cfg.tests); + + devshells.default.commands = [ + { + name = "run-vm-test"; + category = "tests"; + help = "list and run virtual machine integration tests"; + command = "${lib.getExe cfg.runVmTestScript} $@"; + } + ]; + }; + }; + + herculesCI.onPush.default.outputs.effects = + mapAttrs' + (name: test: + nameValuePair + "vmTests-${test.name}" + test.effect) + (lib.filterAttrs + (_: v: lib.elem config.defaultEffectSystem v.systems && v.impure) + (config.perSystem config.defaultEffectSystem).vmTests.tests); +} diff --git a/ci/default.nix b/ci/default.nix index 95bb972..4097ea2 100644 --- a/ci/default.nix +++ b/ci/default.nix @@ -22,7 +22,6 @@ perSystem = {config, ...}: { hercules-ci.github-pages.settings.contents = config.packages.docs; }; - herculesCI.ciSystems = ["x86_64-linux" "x86_64-darwin"]; push-cache-effect = { enable = true; @@ -35,9 +34,9 @@ flatten [ (forEach ["apps" "devShells" "packages"] (attr: - forEach ["x86_64-linux" "x86_64-darwin" "aarch64-linux"] + forEach config.systems (system: - collect isDerivation config.flake.${attr}.${system}))) + collect isDerivation (config.flake.${attr}.${system} or {})))) (forEach (attrValues config.flake.nixosConfigurations) (os: os.config.system.build.toplevel)) diff --git a/docs/default.nix b/docs/default.nix index 529abeb..926fcad 100644 --- a/docs/default.nix +++ b/docs/default.nix @@ -32,14 +32,14 @@ chmod +x $out/bin/mkdocs ''; - eachOptions = removeAttrs rootConfig.flake.nixosModules ["default" "cardano-overlay"]; + eachOptions = removeAttrs rootConfig.flake.nixosModules ["default"]; eachOptionsDoc = lib.mapAttrs' ( name: value: lib.nameValuePair - (builtins.head (lib.splitString "." name)) # take foo.options and turn it into just foo - + # take foo.options and turn it into just foo + (builtins.head (lib.splitString "." name)) (pkgs.nixosOptionsDoc { # By default `nixosOptionsDoc` will ignore internal options but we want to show them # This hack will make all the options not internal and visible and optionally append to the @@ -147,18 +147,18 @@ devshells.default = { commands = let - category = "Docs"; + category = "documentation"; in [ { inherit category; name = "docs-serve"; - help = "Serve docs"; + help = "serve documentation web page"; command = "nix run .#docs.serve"; } { inherit category; name = "docs-build"; - help = "Build docs"; + help = "build documentation"; command = "nix build .#docs"; } ]; diff --git a/flake.lock b/flake.lock index 9c99974..71e00a4 100644 --- a/flake.lock +++ b/flake.lock @@ -41,7 +41,9 @@ "nixpkgs": [ "nixpkgs" ], - "nixpkgs-stable": "nixpkgs-stable" + "nixpkgs-stable": [ + "nixpkgs" + ] }, "locked": { "lastModified": 1704159259, @@ -182,6 +184,23 @@ "type": "github" } }, + "cardano-configurations": { + "flake": false, + "locked": { + "lastModified": 1702085095, + "narHash": "sha256-IJChESftdO2tj2pRB+82xMaLP/RqyKHzttE7QMLqvBQ=", + "owner": "input-output-hk", + "repo": "cardano-configurations", + "rev": "21249e0d5c68b4e8f3661b250aa8272a8785d678", + "type": "github" + }, + "original": { + "owner": "input-output-hk", + "repo": "cardano-configurations", + "rev": "21249e0d5c68b4e8f3661b250aa8272a8785d678", + "type": "github" + } + }, "cardano-mainnet-mirror": { "inputs": { "nixpkgs": "nixpkgs_4" @@ -642,26 +661,6 @@ "type": "github" } }, - "flake-parts_2": { - "inputs": { - "nixpkgs-lib": [ - "hercules-ci-effects", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1709336216, - "narHash": "sha256-Dt/wOWeW6Sqm11Yh+2+t0dfEWxoMxGBvv3JpIocFl9E=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2", - "type": "github" - }, - "original": { - "id": "flake-parts", - "type": "indirect" - } - }, "flake-root": { "locked": { "lastModified": 1692742795, @@ -982,8 +981,12 @@ }, "hercules-ci-effects": { "inputs": { - "flake-parts": "flake-parts_2", - "nixpkgs": "nixpkgs_10" + "flake-parts": [ + "flake-parts" + ], + "nixpkgs": [ + "nixpkgs" + ] }, "locked": { "lastModified": 1710396488, @@ -1600,22 +1603,6 @@ "type": "github" } }, - "nixpkgs-stable": { - "locked": { - "lastModified": 1702780907, - "narHash": "sha256-blbrBBXjjZt6OKTcYX1jpe9SRof2P9ZYWPzq22tzXAA=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "1e2e384c5b7c50dbf8e9c441a9e58d85f408b01f", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-23.11", - "repo": "nixpkgs", - "type": "github" - } - }, "nixpkgs-unstable": { "locked": { "lastModified": 1695318763, @@ -1633,22 +1620,6 @@ } }, "nixpkgs_10": { - "locked": { - "lastModified": 1709961763, - "narHash": "sha256-6H95HGJHhEZtyYA3rIQpvamMKAGoa8Yh2rFV29QnuGw=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "3030f185ba6a4bf4f18b87f345f104e6a6961f34", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_11": { "locked": { "lastModified": 1697059129, "narHash": "sha256-9NJcFF9CEYPvHJ5ckE8kvINvI84SZZ87PvqMbH6pro0=", @@ -2049,13 +2020,14 @@ "root": { "inputs": { "attic": "attic", + "cardano-configurations": "cardano-configurations", "cardano-node": "cardano-node", "devour-flake": "devour-flake", "devshell": "devshell_3", "flake-parts": "flake-parts", "flake-root": "flake-root", "hercules-ci-effects": "hercules-ci-effects", - "nixpkgs": "nixpkgs_11", + "nixpkgs": "nixpkgs_10", "pre-commit-hooks-nix": "pre-commit-hooks-nix", "treefmt-nix": "treefmt-nix" } diff --git a/flake.nix b/flake.nix index b108547..3e8305e 100644 --- a/flake.nix +++ b/flake.nix @@ -1,35 +1,51 @@ { inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - flake-parts.url = "github:hercules-ci/flake-parts"; - - # we use effects for CI, documentation and pushing to public cache - hercules-ci-effects.url = "github:mlabs-haskell/hercules-ci-effects/push-cache-effect"; + nixpkgs = { + url = "github:NixOS/nixpkgs/nixos-unstable"; + }; - cardano-node.url = "github:intersectmbo/cardano-node?ref=8.7.3"; + # Cardano-node + cardano-node = { + url = "github:intersectmbo/cardano-node?ref=8.7.3"; + }; + cardano-configurations = { + url = "github:input-output-hk/cardano-configurations/21249e0d5c68b4e8f3661b250aa8272a8785d678"; + flake = false; + }; # Utilities - devshell = { - url = "github:numtide/devshell"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - flake-root.url = "github:srid/flake-root"; - treefmt-nix = { - url = "github:numtide/treefmt-nix"; + attic = { + url = "github:zhaofengli/attic"; inputs.nixpkgs.follows = "nixpkgs"; + inputs.nixpkgs-stable.follows = "nixpkgs"; }; devour-flake = { url = "github:srid/devour-flake"; flake = false; }; - attic = { - url = "github:zhaofengli/attic"; + devshell = { + url = "github:numtide/devshell"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + flake-parts = { + url = "github:hercules-ci/flake-parts"; + }; + flake-root = { + url = "github:srid/flake-root"; + }; + hercules-ci-effects = { + url = "github:mlabs-haskell/hercules-ci-effects/push-cache-effect"; inputs.nixpkgs.follows = "nixpkgs"; + inputs.flake-parts.follows = "flake-parts"; }; pre-commit-hooks-nix = { url = "github:cachix/pre-commit-hooks.nix"; inputs.nixpkgs.follows = "nixpkgs"; - inputs.nixpkgs-stable.follows = "nixpkgs"; # prevent unnecessary download + inputs.nixpkgs-stable.follows = "nixpkgs"; + }; + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = inputs @ {flake-parts, ...}: @@ -51,8 +67,10 @@ systems = [ "x86_64-linux" "aarch64-linux" - "x86_64-darwin" - "aarch64-darwin" + # Ogmios doesn't support it + # "x86_64-darwin" + # We don't have a builder + # "aarch64-darwin" ]; }; } diff --git a/formatter/default.nix b/formatter/default.nix index c3aef65..a9f8786 100644 --- a/formatter/default.nix +++ b/formatter/default.nix @@ -25,9 +25,9 @@ devshells.default.commands = [ { - category = "Tools"; + category = "tools"; name = "fmt"; - help = "Format the source tree"; + help = "format the source tree"; command = lib.getExe config.treefmt.build.wrapper; } ]; diff --git a/modules/cardano-cli/default.nix b/modules/cardano-cli.nix similarity index 100% rename from modules/cardano-cli/default.nix rename to modules/cardano-cli.nix diff --git a/modules/cardano-node.nix b/modules/cardano-node.nix new file mode 100644 index 0000000..e76ea9f --- /dev/null +++ b/modules/cardano-node.nix @@ -0,0 +1,67 @@ +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.cardanoNix.cardano-node; +in { + options.cardanoNix.cardano-node = { + enable = lib.mkEnableOption "cardano-node service"; + + socketPath = lib.mkOption { + description = "Path to cardano-node socket."; + type = lib.types.path; + default = "/run/cardano-node/node.socket"; + }; + + configPath = lib.mkOption { + description = "Path to cardano-node configuration."; + type = lib.types.path; + # TODO: remove "_p2p" after updating cardano-node to >= 8.9.0 + default = "${pkgs.cardano-configurations}/network/${config.cardanoNix.globals.network}_p2p/cardano-node/config.json"; + defaultText = lib.literalExpression "\${pkgs.cardano-configurations}/network/\${config.cardanoNix.globals.network}_p2p/cardano-node/config.json"; + }; + }; + + config = lib.mkIf cfg.enable { + environment.variables = { + CARDANO_NODE_SOCKET_PATH = cfg.socketPath; + }; + + services.cardano-node = { + enable = true; + + inherit (cfg) socketPath; + nodeConfigFile = cfg.configPath; + environment = config.cardanoNix.globals.network; + + # Listen on all interfaces. + hostAddr = lib.mkDefault "0.0.0.0"; + + # Reload unit if p2p and only topology changed. + useSystemdReload = true; + }; + + # Workaround: cardano-node service does not support systemdSocketActivation with p2p topology. + # Socket created by cardano-node is not writable by group . So we wait until it appears and set the permissions. + systemd.services.cardano-node-socket = { + description = "Wait for cardano-node socket to appear and set permissions to allow group read and write."; + after = ["cardano-node.service"]; + requires = ["cardano-node.service"]; + bindsTo = ["cardano-node.service"]; + requiredBy = ["cardano-node.service"]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + # Using a path unit doesn't allow dependencies to be declared correctly, so poll. + script = '' + echo 'Waiting for ${cfg.socketPath} to appear...' + /bin/sh -c 'until test -e ${cfg.socketPath}; do sleep 1; done' + echo 'Changing permissions for ${cfg.socketPath}.' + chmod g+rw ${cfg.socketPath} + ''; + }; + }; +} diff --git a/modules/cardano-node/default.nix b/modules/cardano-node/default.nix deleted file mode 100644 index f44e21a..0000000 --- a/modules/cardano-node/default.nix +++ /dev/null @@ -1,24 +0,0 @@ -{ - config, - lib, - ... -}: let - cfg = config.cardanoNix.cardano-node; -in { - options.cardanoNix.cardano-node = { - enable = lib.mkEnableOption "cardano-node service"; - }; - - config = lib.mkIf cfg.enable { - environment.variables = { - CARDANO_NODE_SOCKET_PATH = config.services.cardano-node.socketPath 0; - }; - - services.cardano-node = { - enable = true; - hostAddr = "0.0.0.0"; - - environment = config.cardanoNix.globals.network; - }; - }; -} diff --git a/modules/default.nix b/modules/default.nix index f95cbe3..3194449 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -3,23 +3,39 @@ config, ... }: { - flake.nixosModules = rec { - globals = ./globals; - cardano-node = { + flake.nixosModules = { + globals = { imports = [ - inputs.cardano-node.nixosModules.cardano-node - cardano-overlay - ./cardano-node + ./globals.nix ]; }; cardano-cli = { imports = [ - cardano-overlay - ./cardano-cli + ./cardano-cli.nix + ]; + nixpkgs.overlays = [ + config.flake.overlays.cardano-cli + ]; + }; + cardano-node = { + imports = [ + inputs.cardano-node.nixosModules.cardano-node + ./cardano-node.nix + ]; + nixpkgs.overlays = [ + config.flake.overlays.cardano-cli + config.flake.overlays.cardano-node + config.flake.overlays.cardano-configurations ]; }; - cardano-overlay = { - nixpkgs.overlays = [config.flake.overlays.default]; + ogmios = { + imports = [ + ./services/ogmios.nix + ./ogmios.nix + ]; + nixpkgs.overlays = [ + config.flake.overlays.ogmios + ]; }; # the default module imports all modules default = { diff --git a/modules/globals/default.nix b/modules/globals.nix similarity index 68% rename from modules/globals/default.nix rename to modules/globals.nix index 3f411ec..af2f6d0 100644 --- a/modules/globals/default.nix +++ b/modules/globals.nix @@ -11,33 +11,27 @@ in { options.cardanoNix.globals = { network = lib.mkOption { - type = types.enum (builtins.attrNames cfg.networkNumbers); + description = "Cardano network to operate on."; + type = types.enum (lib.attrNames cfg.networkNumbers); default = "mainnet"; - description = '' - Cardano network to join/use - ''; }; networkNumber = lib.mkOption { + description = "Cardano network number to operate on. Defaults to the network number of the selected network."; + internal = true; type = types.int; default = cfg.networkNumbers.${cfg.network}; defaultText = lib.literalExpression "config.cardanoNix.globals.networkNumbers.\${config.cardanoNix.globals.net}"; - description = '' - Cardano network number to join/use (should match cardanoNix.globals,network) - ''; - internal = true; }; networkNumbers = lib.mkOption { + description = "Map from network names to network numbers. Selected network must be present in the map"; type = types.attrsOf types.int; default = { - mainnet = 0; + mainnet = 764824073; preprod = 1; preview = 2; sanchonet = 4; private = 42; }; - description = '' - Cardano network numbers - ''; internal = true; }; }; diff --git a/modules/ogmios.nix b/modules/ogmios.nix new file mode 100644 index 0000000..407253c --- /dev/null +++ b/modules/ogmios.nix @@ -0,0 +1,25 @@ +{ + config, + lib, + ... +}: let + cfg = config.cardanoNix.ogmios; +in { + options.cardanoNix.ogmios = { + enable = lib.mkEnableOption "Ogmios bridge interface for cardano-node"; + }; + + config = lib.mkIf cfg.enable { + services.ogmios = { + enable = true; + nodeConfigPath = + lib.mkIf (config.cardanoNix.cardano-node.enable or false) + config.cardanoNix.cardano-node.configPath; + }; + + systemd.services.ogmios = lib.mkIf (config.cardanoNix.cardano-node.enable or false) { + after = ["cardano-node-socket.service"]; + requires = ["cardano-node-socket.service"]; + }; + }; +} diff --git a/modules/services/ogmios.nix b/modules/services/ogmios.nix new file mode 100644 index 0000000..0da0040 --- /dev/null +++ b/modules/services/ogmios.nix @@ -0,0 +1,118 @@ +# NixOS module for configuring Ogmios service. +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.services.ogmios; +in { + options.services.ogmios = with types; { + enable = mkEnableOption "Ogmios bridge interface for cardano-node"; + + package = mkOption { + description = "Ogmios package"; + type = package; + default = pkgs.ogmios; + }; + + user = mkOption { + description = "User to run Ogmios service as."; + type = str; + default = "ogmios"; + }; + + group = mkOption { + description = "Group to run Ogmios service as."; + type = str; + default = "ogmios"; + }; + + nodeSocketPath = mkOption { + description = "Path to cardano-node IPC socket."; + type = path; + default = "/run/cardano-node/node.socket"; + }; + + nodeConfigPath = mkOption { + description = "Path to cardano-node config.json file. Required."; + type = path; + }; + + host = mkOption { + description = "Host address or name to listen on."; + type = str; + default = "localhost"; + }; + + port = mkOption { + description = "TCP port to listen on."; + type = port; + default = 1337; + }; + + extraArgs = mkOption { + description = "Extra arguments to ogmios command."; + type = listOf str; + default = []; + }; + }; + + config = mkIf cfg.enable { + users.users.ogmios = mkIf (cfg.user == "ogmios") { + isSystemUser = true; + inherit (cfg) group; + extraGroups = ["cardano-node"]; + }; + users.groups.ogmios = mkIf (cfg.group == "ogmios") {}; + + systemd.services.ogmios = { + enable = true; + after = ["cardano-node.service"]; + wantedBy = ["multi-user.target"]; + + script = escapeShellArgs (concatLists [ + ["${cfg.package}/bin/ogmios"] + ["--node-socket" cfg.nodeSocketPath] + ["--node-config" cfg.nodeConfigPath] + ["--host" cfg.host] + ["--port" cfg.port] + cfg.extraArgs + ]); + + serviceConfig = { + User = cfg.user; + Group = cfg.group; + # Security + UMask = "0077"; + CapabilityBoundingSet = ""; + ProcSubset = "pid"; + ProtectProc = "invisible"; + NoNewPrivileges = true; + DevicePolicy = "closed"; + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + PrivateUsers = true; + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"]; + RestrictNamespaces = true; + LockPersonality = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + RemoveIPC = true; + PrivateMounts = true; + SystemCallArchitectures = "native"; + SystemCallFilter = ["@system-service" "~@privileged"]; + MemoryDenyWriteExecute = true; + }; + }; + }; +} diff --git a/packages/cardano.nix b/packages/cardano.nix new file mode 100644 index 0000000..9264955 --- /dev/null +++ b/packages/cardano.nix @@ -0,0 +1,13 @@ +{ + inputs, + lib, + ... +}: { + perSystem = {system, ...}: { + packages = lib.filterAttrs (_: v: v != null) { + cardano-cli = inputs.cardano-node.packages.${system}.cardano-cli or null; + cardano-node = inputs.cardano-node.packages.${system}.cardano-node or null; + inherit (inputs) cardano-configurations; + }; + }; +} diff --git a/packages/default.nix b/packages/default.nix index d2452b9..7193728 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -1,15 +1,16 @@ -{inputs, ...}: { - imports = [inputs.flake-parts.flakeModules.easyOverlay]; - perSystem = { - config, - system, - lib, - ... - }: { - packages = let - packageNames = ["cardano-node" "cardano-cli"]; - in - lib.filterAttrs (n: _: builtins.elem n packageNames) (inputs.cardano-node.packages.${system} or {}); - overlayAttrs = config.packages; +{config, ...}: let + mkOverlay = name: ( + final: _prev: {${name} = (config.perSystem final.system).packages.${name};} + ); +in { + imports = [ + ./cardano.nix + ./ogmios.nix + ]; + flake.overlays = { + cardano-cli = mkOverlay "cardano-cli"; + cardano-node = mkOverlay "cardano-node"; + cardano-configurations = mkOverlay "cardano-configurations"; + ogmios = mkOverlay "ogmios"; }; } diff --git a/packages/ogmios.nix b/packages/ogmios.nix new file mode 100644 index 0000000..44e77f4 --- /dev/null +++ b/packages/ogmios.nix @@ -0,0 +1,33 @@ +{ + perSystem = { + system, + 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"; + 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}"; + }; + nativeBuildInputs = [pkgs.unzip]; + unpackPhase = '' + unzip $src + ''; + buildPhase = '' + chmod a+x bin/ogmios + ''; + installPhase = '' + mkdir -p $out + mv bin share $out/ + ''; + }; + }; +} diff --git a/shell/default.nix b/shell/default.nix index 67cd694..dfbb3ba 100644 --- a/shell/default.nix +++ b/shell/default.nix @@ -27,8 +27,8 @@ { name = "update-pre-commit-hooks"; command = config.pre-commit.installationScript; - category = "Tools"; - help = "Update pre-commit-hooks"; + category = "tools"; + help = "update git pre-commit hooks"; } ]; }; diff --git a/tests/cardano-cli.nix b/tests/cardano-cli.nix index 10ee63d..da34bf0 100644 --- a/tests/cardano-cli.nix +++ b/tests/cardano-cli.nix @@ -1,7 +1,5 @@ { - nixosTests.tests.cardano-cli.module = { - name = "cardano-cli-test"; - + perSystem.vmTests.tests.cardano-cli.module = { nodes = { machine = { virtualisation = { diff --git a/tests/cardano-node.nix b/tests/cardano-node.nix index f675268..29cf7d1 100644 --- a/tests/cardano-node.nix +++ b/tests/cardano-node.nix @@ -1,9 +1,7 @@ { - nixosTests.tests.cardano-node = { + perSystem.vmTests.tests.cardano-node = { impure = true; module = { - name = "cardano-node-test"; - nodes = { machine = {pkgs, ...}: { virtualisation = { @@ -22,9 +20,12 @@ }; }; - testScript = '' + testScript = {nodes, ...}: let + magic = toString nodes.machine.config.cardanoNix.globals.networkNumber; + in '' machine.wait_for_unit("cardano-node") - machine.wait_until_succeeds("""[[ $(echo "$(cardano-cli query tip --testnet-magic 2 | jq '.syncProgress' --raw-output) > 0.01" | bc) == "1" ]]""") + machine.wait_until_succeeds("""[[ $(echo "$(cardano-cli query tip --testnet-magic ${magic} | jq '.syncProgress' --raw-output) > 0.001" | bc) == "1" ]]""") + print(machine.succeed("systemd-analyze security cardano-node")) ''; }; }; diff --git a/tests/default.nix b/tests/default.nix index b1e0354..57f6822 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -1,8 +1,7 @@ { - perSystem = _: { - imports = [ - ./cardano-cli.nix - ./cardano-node.nix - ]; - }; + imports = [ + ./cardano-cli.nix + ./cardano-node.nix + ./ogmios.nix + ]; } diff --git a/tests/ogmios.nix b/tests/ogmios.nix new file mode 100644 index 0000000..c69b1a7 --- /dev/null +++ b/tests/ogmios.nix @@ -0,0 +1,35 @@ +{ + perSystem.vmTests.tests.ogmios = { + impure = true; + module = { + nodes = { + machine = {pkgs, ...}: { + virtualisation = { + cores = 1; + memorySize = 1024; + }; + cardanoNix = { + globals.network = "preview"; + cardano-cli.enable = true; + cardano-node.enable = true; + ogmios.enable = true; + }; + + environment.systemPackages = with pkgs; [jq bc curl]; + }; + }; + + testScript = {nodes, ...}: let + magic = toString nodes.machine.config.cardanoNix.globals.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("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" ]]""") + print(machine.succeed("systemd-analyze security ogmios")) + ''; + }; + }; +}