{
  lib,
  pkgs,
  ...
}:
{
  name = "reaction";

  nodes.server = _: {
    services.reaction = {
      enable = true;
      stopForFirewall = false;
      # example.jsonnet or example.yml can be copied and modified from ${pkgs.reaction}/share/examples
      settingsFiles = [ "${pkgs.reaction}/share/examples/example.jsonnet" ];
      runAsRoot = false;
    };
    services.openssh.enable = true;
    # If not running as root you need to give the reaction user and service the proper permissions

    # allows reading journal logs of processess
    users.users.reaction.extraGroups = [ "systemd-journal" ];

    # allows modifying ip firewall rules
    systemd.services.reaction.unitConfig.ConditionCapability = "CAP_NET_ADMIN";
    systemd.services.reaction.serviceConfig = {
      CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
      AmbientCapabilities = [ "CAP_NET_ADMIN" ];
    };

    users.users.nixos.isNormalUser = true; # neeeded to establish a ssh connection, by default root login is succeeding without any password
  };

  nodes.client = _: {
    environment.systemPackages = [
      pkgs.sshpass
      pkgs.libressl.nc
    ];
  };

  testScript =
    { nodes, ... }: # py
    ''
      start_all()

      # Wait for everything to be ready.
      server.wait_for_unit("multi-user.target")
      server.wait_for_unit("reaction")
      server.wait_for_unit("sshd")

      client_addr = "${(lib.head nodes.client.networking.interfaces.eth1.ipv4.addresses).address}"
      server_addr = "${(lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses).address}"

      # Verify there is not ban and the port is reachable from the client.
      server.succeed(f"reaction show | grep -q {client_addr} || test $? -eq 1")
      client.succeed(f"nc -w3 -z {server_addr} 22")

      # Cause authentication failure log entries.
      for _ in range(2):
        client.fail(f"""
          sshpass -p 'wrongpassword' \
            ssh -o StrictHostKeyChecking=no \
              -o User=nixos \
              -o ServerAliveInterval=1 \
              -o ServerAliveCountMax=2 \
              {server_addr}
        """)

      # Verify there is a ban and the port is unreachable from the client.
      server.sleep(2)
      output = server.succeed("reaction show")
      print(output)
      assert client_addr in output, f"client did not get banned, {client_addr}"

      client.fail(f"nc -w3 -z {server_addr} 22")

      # Check that unbanning works
      output = server.succeed("reaction flush")
      print(output)

      client.succeed(f"nc -w3 -z {server_addr} 22")
    '';

  # Debug interactively with:
  # - nix run .#nixosTests.reaction.driverInteractive -L
  # - run_tests()
  interactive.sshBackdoor.enable = true;

  interactive.nodes.server =
    { config, ... }:
    {
      # not needed, only for manual interactive debugging
      virtualisation.memorySize = 4096;
      virtualisation.graphics = false;
      environment.systemPackages = with pkgs; [
        btop
        sysz
        sshpass
        libressl.nc
      ];
    };

  meta.maintainers =
    with lib.maintainers;
    [
      ppom
    ]
    ++ lib.teams.ngi.members;
}
