{ pkgs, lib, config, hostName, credentials, ... }:
let
  wgIface = "wg-intra";
  peers = import wg-intra/peers.nix;
  wg = config.networking.wireguard.interfaces.${wgIface};
in
{
# Each peer select the other peers allowed to connect to it
options.networking.wireguard.${wgIface}.peers =
  lib.genAttrs (lib.attrNames peers) (peerName: {
    enable = lib.mkEnableOption "this peer";
  });
config = {
systemd.services."wireguard-${wgIface}".serviceConfig.LoadCredentialEncrypted = "privateKey:${credentials}/wireguard/${wgIface}/privateKey.secret";
networking.wireguard.interfaces.${wgIface} = lib.recursiveUpdate
  (removeAttrs peers.${hostName} ["ipv4" "persistentKeepalive" "peer"])
  {
    peers =
      lib.mapAttrsToList (peerName: peer:
        lib.recursiveUpdate
          {
            persistentKeepalive =
              peer.persistentKeepalive # Useful if this peer is behind a NAT
              or peers.${hostName}.persistentKeepalive # Useful if this host is behind a NAT
              or null;
          }
          peer.peer)
        (removeAttrs
          (lib.filterAttrs (peerName: _: config.networking.wireguard.${wgIface}.peers.${peerName}.enable) peers)
          [hostName]);
    privateKeyFile = "$CREDENTIALS_DIRECTORY/privateKey";

    # Set the MTU to a minimum
    # (IPv4 requires at least 68 but it's 1280 for IPv6).
    # This prevents connections to stall on huge packets,
    # or delaying their initializing due to TCP PMTU probing.
    postSetup = ''
      ip link set dev ${wgIface} mtu 1280
    '';
  };
networking.hosts = lib.mkMerge [
  (lib.mapAttrs' (hostName: host:
    lib.nameValuePair host.ipv4 [ "${hostName}.wg" ]) peers)
  {
    "${peers.losurdo.ipv4}" = [
      "nix-extracache.losurdo.wg"
      "nix-localcache.losurdo.wg"
      "sftp.losurdo.wg"
    ];
  }
];
networking.firewall.extraCommands = lib.optionalString (wg.listenPort != null) ''
  ip46tables -A nixos-fw -i any -p udp -m udp --dport ${toString wg.listenPort} -j ACCEPT
'';

networking.nftables.ruleset = lib.optionalString (wg.listenPort != null) ''
  table inet filter {
    chain input-lan {
      udp dport ${toString wg.listenPort} counter accept comment "Wireguard ${wgIface} input from peers"
    }
    chain input-net {
      udp dport ${toString wg.listenPort} counter accept comment "Wireguard ${wgIface} input from peers"
    }
    chain input-intra {
      ${lib.optionalString (peers.${hostName}.peer.endpointsUpdater.enable or false) ''
        tcp dport ${toString peers.${hostName}.listenPort} ip daddr ${peers.${hostName}.ipv4} counter accept comment "Wireguard ${wgIface} from peers to endpointUpdater"
        ''
      }
    }
    chain input {
      iifname ${wgIface} jump input-intra
      iifname ${wgIface} log level warn prefix "input-intra: " counter drop
    }

    chain output-lan {
      udp sport ${toString wg.listenPort} counter accept comment "Wireguard ${wgIface} output to peers"
    }
    chain output-net {
      udp sport ${toString wg.listenPort} counter accept comment "Wireguard ${wgIface} output to peers"
    }
    chain output-intra {
      ${lib.concatStringsSep "\n"
          (lib.mapAttrsToList (peerName: peer: ''
            tcp dport ${toString peer.listenPort} ip daddr ${peer.ipv4} counter accept comment "Wireguard ${wgIface} to endpointUpdater ${peerName}"
            '')
            (lib.filterAttrs (peerName: peer:
              config.networking.wireguard.${wgIface}.peers.${peerName}.enable &&
              (peers.${peerName}.peer.endpointsUpdater.enable or false))
              peers))
      }
    }
    chain output {
      oifname ${wgIface} jump output-intra
      oifname ${wgIface} log level warn prefix "output-intra: " counter drop
    }
  }
'';

services.fail2ban.ignoreIP = lib.concatMap
  (host: host.peer.allowedIPs)
  (lib.attrValues peers);
networking.networkmanager.unmanaged = [ wgIface ];
systemd.services.sshd.after = ["wireguard-${wgIface}.service"];
};
}