{ inputs, lib, config, hostPkgs, sources, ... }: let inherit (builtins) concatStringsSep toJSON ; inherit (lib) fileset mkOption genAttrs attrNames optionalString ; inherit (hostPkgs) runCommandNoCC writeText system ; forConcat = xs: f: concatStringsSep "\n" (map f xs); ## The whole repository, with the flake at its root. ## FIXME: We could probably have fileset be the union of ./. with flake.nix ## and flake.lock - I doubt we need anything else. src = fileset.toSource { fileset = config.pathToRoot; root = config.pathToRoot; }; ## We will need to override some inputs by the empty flake, so we make one. emptyFlake = runCommandNoCC "empty-flake" { } '' mkdir $out echo "{ outputs = { self }: {}; }" > $out/flake.nix ''; in { _class = "nixosTest"; imports = [ ./sharedOptions.nix ]; options = { ## FIXME: I wish I could just use `testScript` but with something like ## `mkOrder` to put this module's string before something else. extraTestScript = mkOption { }; }; config = { acmeNodeIP = config.nodes.acme.networking.primaryIPAddress; nodes = { deployer = { imports = [ ./deployerNode.nix ]; _module.args = { inherit inputs sources; }; enableAcme = config.enableAcme; acmeNodeIP = config.nodes.acme.networking.primaryIPAddress; }; } // ( if config.enableAcme then { acme = { ## FIXME: This makes `nodes.acme` into a local resolver. Maybe this will ## break things once we play with DNS? imports = [ "${inputs.nixpkgs}/nixos/tests/common/acme/server" ]; ## We aren't testing ACME - we just want certificates. systemd.services.pebble.environment.PEBBLE_VA_ALWAYS_VALID = "1"; }; } else { } ) // genAttrs config.targetMachines (_: { imports = [ ./targetNode.nix ]; _module.args = { inherit inputs sources; }; enableAcme = config.enableAcme; acmeNodeIP = if config.enableAcme then config.nodes.acme.networking.primaryIPAddress else null; }); testScript = '' ${forConcat (attrNames config.nodes) (n: '' ${n}.start() '')} ${forConcat (attrNames config.nodes) (n: '' ${n}.wait_for_unit("multi-user.target") '')} with subtest("Unpacking"): deployer.succeed("cp -r --no-preserve=mode ${src}/* .") with subtest("Configure the network"): ${forConcat config.targetMachines ( tm: let targetNetworkJSON = writeText "target-network.json" ( toJSON config.nodes.${tm}.system.build.networkConfig ); in '' deployer.copy_from_host("${targetNetworkJSON}", "${config.pathFromRoot}/${tm}-network.json") '' )} with subtest("Configure the deployer key"): deployer.succeed("""mkdir -p ~/.ssh && ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa""") deployer_key = deployer.succeed("cat ~/.ssh/id_rsa.pub").strip() ${forConcat config.targetMachines (tm: '' ${tm}.succeed(f"mkdir -p /root/.ssh && echo '{deployer_key}' >> /root/.ssh/authorized_keys") '')} with subtest("Configure the target host key"): ${forConcat config.targetMachines (tm: '' host_key = ${tm}.succeed("ssh-keyscan ${tm} | grep -v '^#' | cut -f 2- -d ' ' | head -n 1") deployer.succeed(f"echo '{host_key}' > ${config.pathFromRoot}/${tm}_host_key.pub") '')} ## NOTE: This is super slow. It could probably be optimised in Nix, for ## instance by allowing to grab things directly from the host's store. with subtest("Override the lock"): deployer.succeed(""" nix flake lock --extra-experimental-features 'flakes nix-command' \ --offline -v \ --override-input nixops4 ${inputs.nixops4.packages.${system}.flake-in-a-bottle} \ \ --override-input nixops4-nixos ${inputs.nixops4-nixos} \ --override-input nixops4-nixos/flake-parts ${inputs.nixops4-nixos.inputs.flake-parts} \ --override-input nixops4-nixos/flake-parts/nixpkgs-lib ${inputs.nixops4-nixos.inputs.flake-parts.inputs.nixpkgs-lib} \ --override-input nixops4-nixos/nixops4-nixos ${emptyFlake} \ --override-input nixops4-nixos/nixpkgs ${inputs.nixops4-nixos.inputs.nixpkgs} \ --override-input nixops4-nixos/nixops4 ${ inputs.nixops4-nixos.inputs.nixops4.packages.${system}.flake-in-a-bottle } \ --override-input nixops4-nixos/git-hooks-nix ${emptyFlake} \ ; """) ${optionalString config.enableAcme '' with subtest("Set up handmade DNS"): deployer.succeed("echo '${config.nodes.acme.networking.primaryIPAddress}' > ${config.pathFromRoot}/acme_server_ip") ''} ${config.extraTestScript} ''; }; }