# This setups a SSH server.
{
  outputs,
  config,
  lib,
  ...
}:
let
  # Record containing all the hosts
  hosts = outputs.nixosConfigurations;

  # Name of the current hostname
  hostname = config.networking.hostName;

  # Function from hostname to relative path to public ssh key
  pubKey = host: ../../${host}/keys/ssh_host_ed25519_key.pub;
in
{
  services.openssh = {
    enable = true;

    settings = {
      PermitRootLogin = lib.mkDefault "no"; # Forbid root login through SSH.
      PasswordAuthentication = lib.mkDefault false; # Use keys only.
    };

    # Automatically remove stale sockets
    extraConfig = ''
      StreamLocalBindUnlink yes
    '';

    # Generate ssh key
    hostKeys =
      let
        mkKey =
          type: path: extra:
          { inherit type path; } // extra;
      in
      [
        (mkKey "ed25519" "/persist/state/etc/ssh/ssh_host_ed25519_key" { })
        (mkKey "rsa" "/persist/state/etc/ssh/ssh_host_rsa_key" { bits = 4096; })
      ];
  };

  # Add each host in this repo to the knownHosts list
  programs.ssh = {
    knownHosts = lib.pipe hosts [
      # attrsetof host -> attrsetof { ... }
      (builtins.mapAttrs
        # string -> host -> { ... }
        (
          name: _: {
            publicKeyFile = pubKey name;
            extraHostNames = lib.optional (name == hostname) "localhost";
          }
        )
      )

      # attrsetof { ... } -> attrsetof { ... }
      (lib.attrsets.filterAttrs
        # string -> { ... } -> bool
        (_: { publicKeyFile, ... }: builtins.pathExists publicKeyFile)
      )
    ];
  };

  # By default, this will ban failed ssh attempts
  services.fail2ban.enable = true;

  # Makes it easy to copy host keys at install time without messing up permissions
  systemd.tmpfiles.rules = [
    "d /persist/state/etc/ssh"
  ] ++ (lib.lists.forEach config.services.openssh.hostKeys (key: "e ${key.path} 0700"));
}