# SPDX-FileCopyrightText: 2023 Luke Granger-Brown <depot@lukegb.com>
#
# SPDX-License-Identifier: Apache-2.0

{ depot, lib, pkgs, config, ... }:
let
  vhostsConfig = {
    int = rec {
      proxy = _apply (value: { extraConfig = ''
        ${bind}
        reverse_proxy ${value}
      ''; }) {
        "http://deluge.int.lukegb.com" = "http://localhost:8112";
        "http://radarr.int.lukegb.com" = "http://localhost:7878";
        "http://sonarr.int.lukegb.com" = "http://localhost:8989";
      };
      serve = _apply (value: { extraConfig = ''
        ${bind}
        root * ${value}
        file_server
      ''; }) {
        "http://int.lukegb.com" = depot.web.int;
        "http://logged-out.int.lukegb.com" = depot.web.logged-out-int;
      };
      other = _apply lib.id {
        "http://content.int.lukegb.com" = {
          serverAliases = ["http://content.int.lukegb.com:18081"];
          extraConfig = ''
            ${bind}
            root * /store/content
            file_server browse
          '';
        };
      };
      _apply = f: builtins.mapAttrs (name: value: lib.recursiveUpdate hostBase (f value));
    };
  };
  vhosts = vhostsConfig.int.proxy // vhostsConfig.int.serve // vhostsConfig.int.other // {
    "https://plex.lukegb.xyz" = {
      extraConfig = ''
        tls /var/lib/acme/plex.lukegb.xyz/fullchain.pem /var/lib/acme/plex.lukegb.xyz/privkey.pem
        redir https://plex.lukegb.xyz:32400{uri}
      '';
    };
    "http://plex.lukegb.xyz" = {
      extraConfig = ''
        redir https://plex.lukegb.xyz:32400{uri}
      '';
    };
  };
  hostBase = {
    extraConfig = ''
      ${bind}
    '';
  };
  bind = "bind [${config.my.ip.tailscale6}] ${config.my.ip.tailscale}";
in
{
  imports = [
    ../lib/zfs.nix
    ../lib/bgp.nix
    ../lib/whitby-distributed.nix
    ../lib/nixbuild-distributed.nix
    #../lib/gitlab-runner-cacher.nix
    ../lib/coredns/default.nix
    ../lib/deluge.nix
    ../lib/plex.nix
    ./vm-bridge.nix
    #./vxlan-bridge.nix
  ];

  my.plex.customTLS = {
    enable = true;
    domain = "plex.lukegb.xyz";
  };
  users.users.caddy.extraGroups = lib.mkAfter [ "plexcert" ];

  # Otherwise _this_ machine won't enumerate things properly.
  boot.zfs.devNodes = "/dev/disk/by-id";

  boot.initrd = {
    availableKernelModules = [
      "nvme"
      "xhci_pci"
      "ahci"
      "usb_storage"
      "usbhid"
      "sd_mod"
      "sr_mod"
    ];
  };
  boot.kernelModules = [ "kvm-amd" ];
  hardware.cpu.amd.updateMicrocode = true;

  boot.kernel.sysctl = {
    "net.ipv4.ip_forward" = 1;
    "net.ipv6.conf.all.forwarding" = 1;
  };

  # Use the systemd-boot EFI boot loader.
  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;

  boot.blacklistedKernelModules = [ "ib_core" "irdma" ];

  powerManagement.cpuFreqGovernor = lib.mkDefault "performance";
  services.zfs.rollbackOnBoot = {
    enable = true;
    snapshot = "zfast/local/root@blank";
  };

  fileSystems = let
    zfs = device: {
      device = device;
      fsType = "zfs";
    };
  in {
    "/" = zfs "zfast/local/root";
    "/nix" = zfs "zfast/local/nix";
    "/tmp" = zfs "zfast/local/tmp";

    "/persist" = zfs "zfast/safe/persist";
    "/store" = zfs "zslow/safe/store";
    "/home" = (zfs "zslow/safe/home") // { neededForBoot = true; };

    "/boot" = {
      device = "/dev/nvme1n1p1";
      fsType = "vfat";
    };
    "/boot2" = {
      device = "/dev/nvme0n1p1";
      fsType = "vfat";
    };
  };
  boot.loader.systemd-boot.extraInstallCommands = ''
    rsync -a /boot/ /boot2/
  '';

  nix.settings.max-jobs = lib.mkDefault 8;

  # Networking!
  networking = {
    hostName = "cofractal-ams01";
    domain = "as205479.net";
    hostId = "a1cf1a9f";
    useNetworkd = true;

    nameservers = [
      "2001:4860:4860::8888"
      "2001:4860:4860::8844"
      "8.8.8.8"
      "8.8.4.4"
    ];
    bonds.bond0 = {
      interfaces = [ "enp45s0f0np0" "enp45s0f1np1" ];
      driverOptions = {
        miimon = "100";
        mode = "802.3ad";
      };
    };
    defaultGateway6 = {
      address = "2a09:a446:1337:ffff::1";
      interface = "bond0";
    };
    interfaces.bond0 = {
      ipv6.addresses = [
        { address = "2a09:a446:1337::10"; prefixLength = 64; }
        { address = "2a09:a446:1337:ffff::10"; prefixLength = 120; }
      ];
      ipv4.addresses = [
        { address = "199.19.152.160"; prefixLength = 30; }
      ];
    };
    firewall.interfaces.bond0.allowedTCPPorts = [
      32400  # Plex
      4001   # IPFS
      80     # HTTP
      443    # HTTPS
    ];
    firewall.interfaces.bond0.allowedUDPPorts = [
      34197  # factorio
      4001   # IPFS
      443    # HTTP/3
      51821 51822 51823 # wireguard
    ];
    firewall.extraCommands = ''
      # Flush old rules.
      ip46tables -D FORWARD -j lukegb-forward 2>/dev/null || true
      for chain in lukegb-forward lukegb-fwd-accept lukegb-fwd-reject; do
        ip46tables -F "$chain" 2>/dev/null || true
        ip46tables -X "$chain" 2>/dev/null || true
      done

      ip46tables -N lukegb-fwd-accept
      ip46tables -A lukegb-fwd-accept -j ACCEPT

      ip46tables -N lukegb-fwd-reject
      ip46tables -A lukegb-fwd-reject -p tcp ! --syn -j REJECT --reject-with tcp-reset
      ip46tables -A lukegb-fwd-reject -j REJECT

      ip46tables -N lukegb-forward

      ip46tables -A lukegb-forward -i br-public -j lukegb-fwd-accept
      ip46tables -A lukegb-forward -o br-public -j lukegb-fwd-accept

      # Accept from "trusted" quadv2 interface
      ip46tables -A lukegb-forward -i quadv2 -j lukegb-fwd-accept

      # Accept to quadv2 interface if we're multipathing.
      ip46tables -A lukegb-forward -o quadv2 -j lukegb-fwd-accept

      # Accept from established/related connections.
      ip46tables -A lukegb-forward -m conntrack --ctstate ESTABLISHED,RELATED -j lukegb-fwd-accept

      # Set up the firewall.
      ip46tables -A lukegb-forward -j lukegb-fwd-reject
      ip46tables -A FORWARD -j lukegb-forward
    '';
  };
  systemd.network = let
    wireguard = { name, listenPort, privateKey, publicKey, endpoint ? null }: {
      netdevConfig = {
        Name = name;
        Kind = "wireguard";
        Description = "WireGuard tunnel ${name}";
      };
      wireguardConfig = {
        ListenPort = listenPort;
        PrivateKeyFile = privateKey;
      };
      wireguardPeers = [{
        PublicKey = publicKey;
        AllowedIPs = [
          "0.0.0.0/0"
          "::/0"
        ];
        Endpoint = endpoint;
      }];
    };
    swannWireguard = args: wireguard (args // {
      privateKey = config.my.vault.secrets.wg-swann-private.path;
      publicKey = "N7nMSpFl+t+FVluRJY8dGJuB4Yn11mJlBW5+LwFqOhg=";
    });
    rexxarWireguard = args: wireguard (args // {
      privateKey = config.my.vault.secrets.wg-rexxar-private.path;
      publicKey = "Rhzn9S8WLpoohsk0Y2oanQSa9waThlK7dbA7ufzzMSU=";
    });
  in {
    netdevs."40-wg-swann-ee" = swannWireguard {
      name = "wg-swann-ee";
      listenPort = 51821;
    };
    netdevs."40-wg-swann-gnet" = swannWireguard {
      name = "wg-swann-gnet";
      listenPort = 51822;
      endpoint = "185.250.189.20:51822";
    };
    netdevs."40-wg-rexxar" = rexxarWireguard {
      name = "wg-rexxar";
      listenPort = 51823;
    };

    networks."40-wg-swann-ee" = {
      matchConfig.Name = "wg-swann-ee";
      address = [
        "92.118.30.1/31"
        "2a09:a442::1:2/112"
      ];
    };
    networks."40-wg-swann-gnet" = {
      matchConfig.Name = "wg-swann-gnet";
      address = [
        "92.118.30.7/31"
        "2a09:a442::4:2/112"
      ];
    };
    networks."40-wg-rexxar" = {
      matchConfig.Name = "wg-rexxar";
      address = [
        "169.254.200.1/31"
      ];
    };

    networks."40-bond0".linkConfig.RequiredForOnline = "yes";
    networks."40-enp45s0f0np0".linkConfig.RequiredForOnline = "no";
    networks."40-enp45s0f1np1".linkConfig.RequiredForOnline = "no";
    networks."50-tailscale".linkConfig.RequiredForOnline = "no";
    networks."60-lo" = {
      matchConfig.Name = "lo";
      addresses = [{
        Address = "127.0.0.1/8";
        Scope = "host";
      } {
        Address = "::1/128";
      } {
        Address = "92.118.30.252/32";
      } {
        Address = "2a09:a442:2000::/128";
      }];
    };

    netdevs.quadv2 = {
      netdevConfig = {
        Name = "quadv2";
        Kind = "wireguard";
      };

      wireguardConfig = {
        PrivateKeyFile = pkgs.writeText "cofractal-ams01-quadv" depot.ops.secrets.wireguard.quadv2.lukegb.privateKey;
        ListenPort = 51820;
        RouteTable = "off";
      };

      wireguardPeers = [{
        PublicKey = depot.ops.secrets.wireguard.quadv2.quadv.publicKey;
        AllowedIPs = "0.0.0.0/0,::/0";
      }];
    };
    networks.quadv2 = {
      matchConfig.Name = "quadv2";
      networkConfig.Address = "169.254.112.0/31";

      routes = [{
        Gateway = "169.254.112.1";
        Destination = "92.118.31.0/24";
      }];
    };
  };
  my.ip.tailscale = "100.83.36.130";
  my.ip.tailscale6 = "fd7a:115c:a1e0:ab12:4843:cd96:6253:2482";
  my.coredns.bind = [ "bond0" "tailscale0" "127.0.0.1" "::1" ];

  services.openssh.hostKeys = [
    {
      path = "/persist/etc/ssh/ssh_host_ed25519_key";
      type = "ed25519";
    }
    {
      path = "/persist/etc/ssh/ssh_host_rsa_key";
      type = "rsa";
      bits = 4096;
    }
  ];

  systemd.mounts = let
    bindMount' = dir: {
      unitConfig.RequiresMountsFor = dir;
      options = "bind";
      what = "/persist${dir}";
      where = dir;
    };
    bindMountSvc = dir: svc: (bindMount' dir) // {
      requiredBy = [svc];
      before = [svc];
      wantedBy = ["multi-user.target"];
    };
    bindMountSvcDynamic = dir: svc: (bindMount' "/var/lib/private/${dir}") // {
      requiredBy = [svc];
      before = [svc];
      wantedBy = ["multi-user.target"];
    };
    bindMount = dir: (bindMount' dir) // {
      wantedBy = ["multi-user.target"];
    };
  in [
    (bindMountSvc "/var/lib/tailscale" "tailscaled.service")
    (bindMountSvc "/var/lib/private/factorio" "factorio.service")
    (bindMountSvc "/var/lib/libvirt" "libvirt.service")
  ];

  services.lukegbgp = let
    local.asn = 205479;
  in {
    enable = true;
    config = let
      peer.swann = {
        local.asn = 205479;
        remote.bfd = true;
        remote.asn = 205479;
        remote.must_be_next_hop = false;
        remote.export_community = 10;
        remote.export_internal = true;
      };
    in {
      local = {
        routerID = "199.19.152.160";
      };
      export.v4 = [ "92.118.28.0/24" ];
      export.v6 = [ "2a09:a446:1337::/48" "2a09:a442::/48" "2a09:a442:2000::/48" ];

      internal.export.v4 = [ "92.118.30.252/32" ];
      internal.export.v4Extra = ''
        route 92.118.30.0/31 via "wg-swann-ee";
        route 92.118.30.6/31 via "wg-swann-gnet";
      '';
      internal.export.v6 = [ "2a09:a446:1337::/48" "2a09:a442:2000::/48" "2a09:a442::/48" ];
      internal.export.v6Extra = ''
        route 2a09:a442::1:0/112 via "wg-swann-ee";
        route 2a09:a442::4:0/112 via "wg-swann-gnet";
      '';

      peering.swann_ee = lib.mkMerge [peer.swann {
        local.v4 = "92.118.30.1";
        local.v6 = "2a09:a442::1:2";
        remote.routers = [{
          enabled = true;
          v4 = "92.118.30.0";
          v6 = "2a09:a442::1:1";
        }];
        remote.bgp_local_pref = 10;
      }];
      peering.swann_gnet = lib.mkMerge [peer.swann {
        local.v4 = "92.118.30.7";
        local.v6 = "2a09:a442::4:2";
        remote.routers = [{
          enabled = true;
          v4 = "92.118.30.6";
          v6 = "2a09:a442::4:1";
        }];
      }];

      peering.cofractal = {
        local = local // {
          v6 = "2a09:a446:1337:ffff::10";
        };
        v4onv6 = true;
        remote = {
          asn = 26073;
          export_community = 6000;
          routers = [{
            v6 = "2a09:a446:1337:ffff::2";
          } {
            v6 = "2a09:a446:1337:ffff::3";
          }];
        };
      };

      peering.quadv = {
        local = local // {
          v4 = "169.254.112.0";
        };
        remote = {
          asn = 197753;
          export_community = 4099;
          routers = [{
            v4 = "169.254.112.1";
          }];
          prefix_limit.v4 = 10;
          prefix_limit.v6 = 10;
          set_imported_next_hop_to = "2a09:a446:1337:ffff::10";
        };
      };

      bfd = ''
        interface "*" {
          min rx interval 10ms;
          min tx interval 50ms;
          idle tx interval 1s;
          multiplier 20;
        };
        neighbor 92.118.30.0;
        neighbor 2a09:a442::1:1;
        neighbor 92.118.30.6;
        neighbor 2a09:a442::4:1;
      '';
    };
  };

  boot.binfmt.emulatedSystems = [ "aarch64-linux" ];

  services.sonarr.enable = true;
  services.radarr.enable = true;
  services.kubo = {
    enable = false;  # kubo is so expensive for no reason :/
    settings = {
      Discovery.MDNS.Enabled = false;
      Swarm.DisableNatPortMap = true;
      Experimental.FilestoreEnabled = true;
    };
    dataDir = "/store/ipfs";
  };
  services.caddy = {
    enable = true;
    virtualHosts = vhosts;
  };

  services.factorio = {
    inherit (depot.ops.secrets.factorio) username token;
    enable = true;
    saveName = "lukegb20230312-krastorio2";
    game-name = "Briefcase Full of Bees";
    mods = depot.nix.pkgs.factorio-mods._all;
    mods-dat = ./mod-settings.dat;
    admins = ["lukegb"];
    extraSettings = {
      auto_pause = true;
      only_admins_can_pause_the_game = false;
      game_password = depot.ops.secrets.factorioServerPassword;
      non_blocking_saving = true;
      autosave_only_on_server = true;
      autosave_interval = 5;
      autosave_slots = 60;
    };
  };

  virtualisation.libvirtd = {
    enable = true;
  };
  security.polkit.enable = true;
  users.users.lukegb.extraGroups = lib.mkAfter [ "libvirtd" ];

  my.vault.secrets = let
    wireguardSecret = key: {
      group = "systemd-network";
      template = ''
        {{- with secret "kv/apps/wireguard/cofractal-ams01" -}}
        {{- .Data.data.${key} -}}
        {{- end -}}
      '';
    };
  in {
    wg-swann-private = wireguardSecret "privateKeyToSwann";
    wg-rexxar-private = wireguardSecret "privateKeyToRexxar";
  };

  system.stateVersion = "23.05";
}