From ac229a0bbe06ce126143b76b76cbdaf9c0d638bd Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Fri, 19 May 2023 17:37:54 +0200 Subject: [PATCH] flake: add mono-node and multi-node tests --- Cargo.toml | 2 + flake.lock | 187 +++++++++++++++++++++++++++++++++++++++ flake.nix | 64 +++++++++++--- nix/modules/sable.nix | 94 ++++++++++++++++++++ nix/tests/sable.nix | 201 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 538 insertions(+), 10 deletions(-) create mode 100644 flake.lock create mode 100644 nix/modules/sable.nix create mode 100644 nix/tests/sable.nix diff --git a/Cargo.toml b/Cargo.toml index 8584281d..960ea9b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,6 @@ [workspace] +package.name = "sable" +package.version = "0.0.1" members = [ "sable_macros", "sable_network", diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..db6f3481 --- /dev/null +++ b/flake.lock @@ -0,0 +1,187 @@ +{ + "nodes": { + "crane": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ], + "rust-overlay": "rust-overlay" + }, + "locked": { + "lastModified": 1683505101, + "narHash": "sha256-VBU64Jfu2V4sUR5+tuQS9erBRAe/QEYUxdVMcJGMZZs=", + "owner": "ipetkov", + "repo": "crane", + "rev": "7b5bd9e5acb2bb0cfba2d65f34d8568a894cdb6c", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1684045209, + "narHash": "sha256-d6WRNsb5+P7wocC/wCMjkvF0pycKuZ7SHgj5NGmESNQ=", + "owner": "nix-community", + "repo": "fenix", + "rev": "7c8be7e3312ba06dc95491a44c3c9ba71a175bc4", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1683560683, + "narHash": "sha256-XAygPMN5Xnk/W2c1aW0jyEa6lfMDZWlQgiNtmHXytPc=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "006c75898cf814ef9497252b022e91c946ba8e17", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1684049129, + "narHash": "sha256-7WB9LpnPNAS8oI7hMoHeKLNhRX7k3CI9uWBRSfmOCCE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "0470f36b02ef01d4f43c641bbf07020bcab71bf1", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "crane": "crane", + "fenix": "fenix", + "flake-parts": "flake-parts", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1684005690, + "narHash": "sha256-m0Dya15vVk4swQdWKqDYHmdU3cirySRjaQ6EgMa2Mcg=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "cbd14e98403dc5e19f19fdf913808656d81a0516", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": [ + "crane", + "flake-utils" + ], + "nixpkgs": [ + "crane", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1683080331, + "narHash": "sha256-nGDvJ1DAxZIwdn6ww8IFwzoHb2rqBP4wv/65Wt5vflk=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "d59c3fa0cba8336e115b376c2d9e91053aa59e56", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix index e2f2843c..b15503bf 100644 --- a/flake.nix +++ b/flake.nix @@ -12,18 +12,62 @@ inputs.nixpkgs.follows = "nixpkgs"; }; flake-utils.url = "github:numtide/flake-utils"; + flake-parts.url = "github:hercules-ci/flake-parts"; + flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs"; nixpkgs.url = "nixpkgs/nixos-unstable"; }; - outputs = { self, crane, fenix, flake-utils, nixpkgs }: - flake-utils.lib.eachDefaultSystem (system: { - packages.default = - let - craneLib = crane.lib.${system}.overrideToolchain - fenix.packages.${system}.minimal.toolchain; - in - craneLib.buildPackage { - src = ./sable_ircd; + outputs = { self, crane, fenix, flake-utils, flake-parts, nixpkgs }@inputs: flake-parts.lib.mkFlake { inherit inputs; } ({ moduleWithSystem, ... }: { + imports = [ + inputs.flake-parts.flakeModules.easyOverlay + ]; + + flake.nixosModules.sable = moduleWithSystem ( + { config }: + { ... }: { + imports = [ + ./nix/modules/sable.nix + ]; + } + ); + + systems = [ + "x86_64-linux" + + # Not actively tested, but may work: + # "aarch64-linux" + ]; + + perSystem = { config, system, pkgs, ... }: + let + pkgs = import nixpkgs { + system = system; + overlays = [ + self.overlays.default + ]; + }; + inherit (pkgs) lib; + in { + packages.sable = + let + craneLib = crane.lib.${system}.overrideToolchain + (fenix.packages.${system}.fromToolchainFile { + file = ./rust-toolchain.toml; + sha256 = "sha256-kadEI6Hg6v+Xw68334b8XpfNah1pZAJZQ+i6ViN+QyQ="; + }); + in + craneLib.buildPackage { + src = ./.; + }; + + overlayAttrs = { + inherit (config.packages) sable; + }; + + checks = (import ./nix/tests/sable.nix { + inherit pkgs; + sableModule = self.nixosModules.sable; + }); }; - }); + }); } diff --git a/nix/modules/sable.nix b/nix/modules/sable.nix new file mode 100644 index 00000000..27622857 --- /dev/null +++ b/nix/modules/sable.nix @@ -0,0 +1,94 @@ +{ lib, config, pkgs, ... }: +with lib; +let + cfg = config.services.sable; + + settingsFormat = pkgs.formats.json { }; + serverConfigFile = settingsFormat.generate "server.json" cfg.server.settings; + networkConfigFile = + settingsFormat.generate "network.json" cfg.network.settings; + bootstrapFile = + settingsFormat.generate "bootstrap.json" cfg.network.bootstrap; +in { + options.services.sable = { + enable = mkEnableOption "the Libera Chat's Sable IRCd"; + + network = { + configFile = mkOption { + type = types.path; + default = networkConfigFile; + }; + + settings = mkOption rec { + type = types.submodule { freeformType = settingsFormat.type; }; + + default = { }; + }; + + bootstrap = mkOption { + type = types.nullOr (types.submodule { freeformType = settingsFormat.type; }); + default = null; + }; + }; + + server.configFile = mkOption { + type = types.path; + default = serverConfigFile; + }; + + server.settings = mkOption { + type = types.submodule { freeformType = settingsFormat.type; }; + + default = { }; + + description = '' + Server configuration of the IRCd. + ''; + }; + }; + + config = mkIf cfg.enable { + services.sable.server.settings.log = lib.mapAttrs (n: lib.mkDefault) { + dir = "/var/log/sable/"; + # stdout = "stdout.log"; + # stderr = "stderr.log"; + pidfile = "/run/sable/sable.pid"; + module-levels = { + tokio = "trace"; + runtime = "trace"; + rustls = "error"; + tracing = "warn"; + sable = "trace"; + "" = "debug"; + }; + + targets = [ + { + target = "stdout"; + level = "trace"; + modules = [ "sable"]; + } + { + target = { filename = "sable.log"; level = "info"; }; + level = "info"; + } + { + target = { filename = "trace.log"; level = "trace"; }; + level = "trace"; + } + ]; + + console-address = "127.0.0.1:9999"; + }; + systemd.services.sable-ircd = { + description = "Sable IRC daemon"; + wantedBy = [ "multi-user.target" ]; + restartTriggers = [ cfg.network.configFile cfg.server.configFile ]; + serviceConfig = { + LogsDirectory = "sable"; + PIDFile = "sable.pid"; + ExecStart = "${pkgs.sable}/bin/sable_ircd --foreground ${optionalString (cfg.network.bootstrap != null) "--bootstrap-network ${bootstrapFile}"} --network-conf ${cfg.network.configFile} --server-conf ${cfg.server.configFile}"; + }; + }; + }; +} diff --git a/nix/tests/sable.nix b/nix/tests/sable.nix new file mode 100644 index 00000000..cdc774aa --- /dev/null +++ b/nix/tests/sable.nix @@ -0,0 +1,201 @@ +{ pkgs, sableModule }: + +let + inherit (pkgs) lib system; + + deriveFingerprint = certFile: builtins.readFile (pkgs.runCommand "openssl-get-sha1-fp" '' + ${lib.getExe pkgs.openssl} x509 -in ${certFile} -fingerprint -noout | sed 's/.*=//;s/://g;s/.*/Ł&/' > $out + ''); + nodeConfig = { serverId, serverName, serverIP, caFile, managementClientCaFile, keyFile, certFile + , bootstrapped ? false, peers ? null }: { ... }: + let myFingerprint = ""; # deriveFingerprint certFile; # compute it + in { + imports = [ + sableModule + ]; + + networking.interfaces.eth1.ipv4.addresses = [ + { address = serverIP; prefixLength = 24; } + ]; + + services.sable = { + enable = true; + network.bootstrap = if bootstrapped then + (lib.importJSON ../../configs/network_config.json) + else + null; + network.settings = { + fanout = 1; + ca_file = caFile; + peers = if (peers != null) then + peers + else ([{ + name = serverName; + address = "0.0.0.0:6668"; + fingerprint = myFingerprint; + }]); + }; + server.settings = let + keys = { + key_file = keyFile; + cert_file = certFile; + }; + in { + server_id = serverId; + server_name = serverName; + + management = { + address = "0.0.0.0:8888"; + client_ca = managementClientCaFile; + authorised_fingerprints = [{ + name = "user1"; + fingerprint = "ef6d9512ce159d8ef0ec56e5769b76f537936862"; + }]; + }; + + server.listeners = [ + { address = "0.0.0.0:6667"; } + { + address = "0.0.0.0:6697"; + tls = true; + } + ]; + + tls_config = keys; + node_config = keys // { listen_addr = "0.0.0.0:6668"; }; + }; + }; + }; + + mkMultiNodeTest = { name, servers ? { }, client ? { }, testScript }: + pkgs.nixosTest { + inherit name testScript; + nodes = servers // { + client = { lib, ... }: { + imports = [ sableModule client ]; + + systemd.services.weechat-headless = { + serviceConfig.StateDirectory = "weechat"; + script = '' + ${pkgs.weechat}/bin/weechat-headless --stdout -d /var/lib/weechat --run "/logger set 9; /set fifo.file.path /tmp/weechat_fifo; /plugin unload fifo; /plugin load fifo; /fifo enable; /logger set 9"''; + wantedBy = [ "multi-user.target" ]; + wants = [ "sable-ircd.service" ]; + after = [ "sable-ircd.service" ]; + }; + }; + }; + }; + + mkBasicTest = { name, machine ? { }, testScript }: + pkgs.nixosTest { + inherit name testScript; + nodes.machine = { lib, ... }: { + imports = [ + sableModule + (nodeConfig { + serverId = 1; + serverName = "server1.test"; + bootstrapped = true; + caFile = ../../configs/ca_cert.pem; + managementClientCaFile = ../../configs/ca_cert.pem; + keyFile = ../../configs/server1.key; + certFile = ../../configs/server1.pem; + }) + machine + ]; + + systemd.services.weechat-headless = { + serviceConfig.StateDirectory = "weechat"; + script = '' + ${pkgs.weechat}/bin/weechat-headless --stdout -d /var/lib/weechat --run "/logger set 9; /set fifo.file.path /tmp/weechat_fifo; /plugin unload fifo; /plugin load fifo; /fifo enable; /logger set 9"''; + wantedBy = [ "multi-user.target" ]; + wants = [ "sable-ircd.service" ]; + after = [ "sable-ircd.service" ]; + }; + }; + }; +in { + monoNode = mkBasicTest { + name = "mononode-sable"; + testScript = '' + machine.start() + machine.wait_for_unit("sable-ircd.service") + machine.wait_for_unit("weechat-headless.service") + + def remote_weechat(command: str): + return machine.succeed(f"echo \"{command}\" > /tmp/weechat_fifo") + + print(machine.succeed("sleep 1")) + remote_weechat(" */server add test-ircd-nossl 127.0.1.2/6667") + remote_weechat(" */connect test-ircd-nossl") + remote_weechat("irc.server.test-ircd-nossl */nick test") + remote_weechat("irc.server.test-ircd-nossl */join #hello") + remote_weechat("irc.test-ircd-nossl.#hello *Hello world!") + ''; + }; + + basicMultiNodes = + let + peers = [ + { + name = "server1.test"; + address = "192.168.1.10:6668"; + fingerprint = "961090b178e037be12a77c0a83876740e3222abd"; + } + { + name = "server2.test"; + address = "192.168.1.11:6668"; + fingerprint = "000fd90df3da5619563eb49228229ac410acc09c"; + } + ]; + servers = { + server1 = nodeConfig { + serverId = 1; + serverName = "server1.test"; + bootstrapped = true; + serverIP = "192.168.1.10"; + caFile = ../../configs/ca_cert.pem; + managementClientCaFile = ../../configs/ca_cert.pem; + keyFile = ../../configs/server1.key; + certFile = ../../configs/server1.key; + inherit peers; + }; + server2 = nodeConfig { + serverId = 2; + serverName = "server2.test"; + bootstrapped = false; + serverIP = "192.168.1.11"; + caFile = ../../configs/ca_cert.pem; + managementClientCaFile = ../../configs/ca_cert.pem; + keyFile = ../../configs/server1.key; + certFile = ../../configs/server1.key; + inherit peers; + }; + }; + serverNames = [ "server1" "server2" ]; + in + mkMultiNodeTest { + name = "basic-multinodes-sable"; + inherit servers; + testScript = '' + start_all() + + servers = [globals()[server_name] for server_name in [${lib.concatMapStringsSep ", " (name: "\"${name}\"") serverNames}]] + + for server in servers: + server.wait_for_unit("sable-ircd.service") + + def remote_weechat(command: str): + return client.succeed(f"echo \"{command}\" > /tmp/weechat_fifo") + + def test_server(name: str, server: str, nick: str = "test", channel_to_join: str = "hello"): + remote_weechat(f" */server add {name} {server}/6667") + remote_weechat(f" */connect {name}") + remote_weechat(f"irc.server.{name} */nick {nick}") + remote_weechat(f"irc.server.{name} */join #{channel_to_join}") + + test_server("server-a", "server1", "test-from-a") + test_server("server-b", "server1", "test-from-b") + ''; + }; +}