# vim: si ai sts=2 sw=2
{ config, lib, pkgs, ... }:

let
  wan = "wan0";
  lan = "lan0";
  hostName = "nano";
  domain = "home.arpa";
  tailnet = "hydra-pinecone.ts.net";
  dnsmasqEnable = true;
in
{
  imports = [
    ./settings/configuration/nix-linux.nix
  ];
  age.secrets = {
    dyndns.file = ../secrets/dyndns.age;
    acme.file = ../secrets/acme.age;
    syncthing.file = ../secrets/syncthing.age;
  };

  boot.loader.timeout = lib.mkForce 1;
  boot.loader.efi.canTouchEfiVariables = false; # is r/o for some reason
  boot.loader.systemd-boot = {
    installDeviceTree = true;
    edk2-uefi-shell = {
      enable = true;
    };
  };

  srvos.boot.consoles = [ "ttyS2,1500000" ];

  hardware.deviceTree = {
    enable = true;
    name = "rockchip/rk3588s-nanopi-r6c.dtb";
  };

  boot.kernelModules = [
    "tcp_lp"
  ];
  boot.kernel.sysctl = {
    "net.ipv6.conf.all.accept_ra" = 0;
    "net.ipv6.conf.all.autoconf" = 0;
    "net.ipv6.conf.all.use_tempaddr" = 0;

    "net.ipv6.conf.${wan}.accept_ra" = 2;
    "net.ipv6.conf.${wan}.autoconf" = 1;

    "net.ipv4.tcp_slow_start_after_idle" = 0;
    "net.ipv4.tcp_ecn" = 1;
    "net.ipv4.tcp_fastopen" = "0x3";
    "net.ipv4.tcp_allowed_congestion_control" = "reno cubic lp";
    "net.core.default_qdisc" = "fq";
  };
  networking = {
    useDHCP = false;
    inherit domain hostName;
    hosts = {
      "fd7a:115c:a1e0::53" = [ "tailscale" "ts" ];
      "192.168.100.1" = [ "modem" "pyur" ];
    };
    nameservers = [
      "9.9.9.11"
      "149.112.112.11"
      "2620:fe::11"
      "2620:fe::fe:11"
    ];
    firewall = {
      trustedInterfaces = [
        lan
        "tailscale0"
      ];
      filterForward = true;
      extraForwardRules = ''
        iifname "tailscale0" oifname "${lan}" accept
        iifname "${lan}" oifname "tailscale0" accept
      '';
    };
    nftables.enable = true;
    nat = {
      enable = true;
      externalInterface = wan;
      internalInterfaces = [
        lan
        "tailscale0"
      ];
    };
  };
  systemd.network = {
    enable = true;
    config = {
      networkConfig = {
        IPv6Forwarding = true;
      };
    };
    links = {
      "10-${lan}" = {
        matchConfig.Path = "platform-a40c00000.pcie-pci-0003:31:00.0";
        linkConfig.Name = lan;
      };
      "10-${wan}" = {
        matchConfig.Path = "platform-fe1c0000.ethernet";
        linkConfig.Name = wan;
      };
    };
    networks = {
      "50-${lan}" = rec {
        matchConfig.Name = lan;
        address = [
          "10.0.0.1/16"
          "fd12:d04f:65d:42::1/56"
        ];
        addresses = [
          {
            Address = "fe80::1/64";
            Scope = "link";
          }
        ];
        networkConfig = {
          IPv6AcceptRA = false;
          DHCPPrefixDelegation = true;
          ConfigureWithoutCarrier = true;
          MulticastDNS = true;
          Domains = [ config.networking.domain ];
          IPv6SendRA = !dnsmasqEnable;
          DHCPServer = !dnsmasqEnable;
          DNS = map (a: builtins.head (lib.strings.splitString "/" a)) address;
          DNSDefaultRoute = false;
        };
        dhcpPrefixDelegationConfig = {
          UplinkInterface = wan;
          SubnetId = "42";
          Assign = true;
          Token = "::1";
        };
        dhcpServerConfig = {
          DefaultLeaseTimeSec = 86400;
          MaxLeaseTimeSec = 86400;
          DNS = "_server_address";
          IPv6OnlyPreferredSec = 900;
        };
      };
      "50-${wan}" = {
        matchConfig.Name = wan;
        networkConfig = {
          DHCP = true;
          IPv6AcceptRA = true;
          IPv4Forwarding = true;
          MulticastDNS = false;
          DNSDefaultRoute = true;
          DNSOverTLS = true;
          DNS = map (ns: "${ns}#dns11.quad9.net") config.networking.nameservers;
        };
        dhcpV4Config = {
          UseDNS = false;
          SendHostname = false;
          UseHostname = false;
          Label = "${wan}:1";
        };
        dhcpV6Config = {
          UseDNS = false;
          SendHostname = false;
          RapidCommit = true;
          PrefixDelegationHint = "::/56";
        };
        dhcpPrefixDelegationConfig = {
          UplinkInterface = ":self";
        };
        ipv6AcceptRAConfig = {
          UseDNS = false;
        };
        addresses = [{
          Address = "192.168.100.10/24";
          Peer = "192.168.100.1/32";
          Label = "${wan}:0";
          Scope = "link";
        }];
        cakeConfig = {
          Bandwidth = "24M";
          OverheadBytes = 18;
          MPUBytes = 64;
          CompensationMode = "none";
          NAT = true;
          PriorityQueueingPreset = "diffserv8";
        };
      };
    };
  };
  services.resolved = {
    enable = true;
    extraConfig = ''
      DNS =
      LLMNR = false
      MulticastDNS = true
    '';
  };

  services.openssh.openFirewall = false;

  services.dnsmasq = {
    enable = dnsmasqEnable;
    alwaysKeepRunning = true;
    settings = {
      inherit domain;
      interface = lan;
      except-interface = "lo";
      bind-interfaces = true;
      dhcp-fqdn = true;
      dhcp-authoritative = true;
      dhcp-rapid-commit = true;
      dhcp-range = [
        "10.0.1.10,10.0.250.250,48h"
        "fd12:d04f:65d:42::,slaac,ra-names,48h"
        "::,constructor:${lan},ra-stateless"
      ];
      quiet-dhcp = true;
      quiet-dhcp6 = true;
      quiet-ra = true;
      enable-ra = true;

      cache-size = 0;
      no-resolv = true;
      server = [ "127.0.0.53" ];

      expand-hosts = true;
      localise-queries = true;
      interface-name = [
        "${hostName}.${domain},${lan}"
        "ca.${domain},${lan}"
        "wan.${domain},${wan}"
      ];
    };
  };
  systemd.services.dnsmasq.after = [ "network-online.target" ];
  systemd.services.dnsmasq.wants = [ "network-online.target" ];

  # TODO find script
  # systemd.services.dynamic-dns-update = {
  #   enable = true;
  #   startAt = [ "hourly" ];
  #   description = "Update IP addresses";
  #   path = with pkgs; [ curl iproute2 dig.dnsutils miller ];
  #   after = [ "sys-subsystem-net-devices-${wan}.device" ];
  #   bindsTo = [ "sys-subsystem-net-devices-${wan}.device" ];
  #   serviceConfig = {
  #     Type = "oneshot";
  #     ExecStart = "/bin/sh /etc/nixos/update-ip ${config.age.secrets.dyndns.path}";
  #   };
  # };
  # services.networkd-dispatcher = {
  #   # broken?
  #   enable = true;
  #   rules = {
  #     update-home-address = {
  #       onState = [ "configured" "configuring" ];
  #       script = ''
  #         #!${pkgs.runtimeShell}
  #         set -eu

  #         if [[ $IFACE == "${wan}" && $OperationalState == "routable" ]]
  #         then
  #           systemctl start dynamic-dns-update.service
  #         fi
  #         exit 0
  #       '';
  #     };
  #     tailscale-subnet-router-optimisation = {
  #       onState = [ "routable" ];
  #       script = ''
  #         #!${pkgs.runtimeShell}
  #         set -eu

  #         if [[ $IFACE == "${wan}" && $OperationalState == "routable" ]]
  #         then
  #           ${pkgs.ethtool}/bin/ethtool -K $IFACE rx-udp-gro-forwarding on rx-gro-list off
  #         fi
  #       '';
  #     };
  #   };
  # };

  services.tailscale = {
    enable = true;
    extraUpFlags = [
      "--advertise-exit-node"
      "--advertise-routes=10.0.0.0/16,fd12:d04f:65d:42::/56"
    ];
  };

  time.timeZone = "Europe/Berlin";
  i18n.defaultLocale = "en_GB.UTF-8";

  programs.vim.defaultEditor = false;
  programs.neovim = {
    enable = true;
    defaultEditor = true;
    vimAlias = true;
    viAlias = true;
  };

  environment.systemPackages = with pkgs; [
    file
    tree
    lsof
    knot-dns
    ethtool
    tcpdump
    conntrack-tools
  ];

  programs.fish.enable = true;
  users.defaultUserShell = pkgs.fish;
  users.mutableUsers = lib.mkForce true;

  users.users.alan = {
    isNormalUser = true;
    packages = with pkgs; [ ];
    openssh.authorizedKeys.keys = [
      "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJVREjPey2TOIPzfYJoG9yIR4Rui7tNJK2QIKa+pbgsyXg31hhPIw37LRRIic+l53mW8eahHxX3Y1IeTjcMw8IU= alan@secretive.marvin.local"
    ];
  };

  users.users.root = {
    openssh.authorizedKeys.keys = [
      "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHYUyDdw92TNXguAxcmcmZmn/7ECGdRp6ckjxU+5zCw3BCnsS5+xEvHBVnnFdJRoH2XpfMeJjE+fi67zFVhlbn4= root@secretive.marvin.local"
    ];
  };

  services.caddy = {
    enable = true;
    globalConfig = ''
      pki {
        ca home {
          name "Home CA"
        }
      }
    '';
    virtualHosts = {
      "${hostName}.${domain}" = {
        serverAliases = [ "${hostName}.${tailnet}" ];
        extraConfig = ''
          tls {
            issuer internal {
              ca home
            }
          }
          root /var/lib/caddy/ca
          file_server browse
        '';
      };
      "ca.${domain}" = {
        extraConfig = ''
          tls {
            issuer internal {
              ca home
            }
          }
          acme_server {
            allow {
              domains *.test *.${domain}
            }
          }
        '';
      };
    };
  };

  users.groups.linde.members = [ ];
  users.users.linde = {
    group = "linde";
    description = "Backup user for system 'linde'";
    isSystemUser = true;
    shell = "/bin/sh";
    home = "/srv/backup/linde";
    homeMode = "755";
    createHome = true;
    packages = with pkgs; [ rdiff-backup ];
    openssh.authorizedKeys.keys = [
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ74cPdIX9OlDkzHb6Y1E5sWqtIqMaf0z/SN3Tfy1Fjl root@linde"
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINNXwIdGcP1vKyjmgeLw/sJntn7lajaZivepgdzaXvOt rdiff-backup"
    ];
  };

  systemd.services.backup-golink = {
    enable = true;
    startAt = "daily";
    description = "Export short links from golink";
    path = with pkgs; [ curl gitMinimal ];
    script = ''
      [ -d golink ] || git init --quiet golink --initial-branch=main --shared=world
      git config --global user.email linde@alanpearce.eu
      cd golink
      curl https://go.${tailnet}/.export > links.json
      git add links.json
      git commit -m $(date +%F)
    '';
    serviceConfig = {
      Type = "oneshot";
      User = "linde";
      WorkingDirectory = config.users.users.linde.home;
    };
  };

  nix = {
    distributedBuilds = true;
    buildMachines = [
      {
        protocol = "ssh-ng";
        sshUser = "nixremote";
        hostName = "linde.alanpearce.eu";
        system = "aarch64-linux";
        # TODO make secret
        sshKey = "/root/.ssh/id_buche.alanpearce.eu_nixremote";
        maxJobs = 2;
        speedFactor = 4;
        supportedFeatures = [ ];
      }
    ];
    settings = {
      max-jobs = 2;
      builders-use-substitutes = true;
      trusted-public-keys = [
        "mba-1:CxokFjx7YAQWPWMJJKcP50ZpcPUCAFEOrtWdNUMTVjw="
      ];
    };
  };

  system.autoUpgrade = {
    dates = "04:15";
    randomizedDelaySec = "59 min";
    flake = "git+https://git.alanpearce.eu/nixfiles";
    allowReboot = true;
    rebootWindow = {
      lower = "01:00";
      upper = "06:00";
    };
  };

  users.users.syncthing = {
    isSystemUser = true;
    group = "syncthing";
    homeMode = "0755";
  };
  users.groups.syncthing.members = [ "alan" ];
  services.syncthing = {
    enable = true;
    openDefaultPorts = true;
    dataDir = "/srv/syncthing";
    user = "syncthing";
    group = "syncthing";
    key = config.age.secrets.syncthing.path;
    cert = toString (pkgs.writeText "syncthing.crt" ''
      -----BEGIN CERTIFICATE-----
      MIIBmjCCASCgAwIBAgIIUOEmXGFrrX0wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ
      c3luY3RoaW5nMB4XDTIyMDcxMzEwMzIxOVoXDTQ5MTIzMTIzNTk1OVowFDESMBAG
      A1UEAxMJc3luY3RoaW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEPiJT41NqucQf
      UXiBwt+yPYnMg9G8oTt9XNA72V99K46D7mIs1F/5oESlDiCSAngXPsajxRY7wyZV
      VoiWegfiaBOGZmq+TyaLlQ5bq/hm/Mp/jVED/rUA+BggohoZZMa2oz8wPTAOBgNV
      HQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1Ud
      EwEB/wQCMAAwCgYIKoZIzj0EAwIDaAAwZQIwLp4Gv5EEmjRO9EphbYJ4jxEJks7E
      oblgnTmhfWmVWmf9avJyeGB212VYu4X8cCKDAjEAn7tTB9Y6LZvYPaLSwUKY3EzF
      hKTYCb7VA/P1dU3tTR1vSQxnu1DsiliD/XcKe2IK
      -----END CERTIFICATE-----
    '');
    overrideFolders = false;
    overrideDevices = false;
    settings = {
      options = {
        maxRecvKbps = 10240;
        maxSendKbps = 1024;
        globalAnnounceEnabled = false;
        relaysEnabled = false;
        natEnabled = false;
        urAccepted = 4;
        trafficClass = 1;
      };
    };
  };

  services.timesyncd.enable = false;
  services.chrony = {
    enable = true;
    extraConfig = ''
      rtcdevice /dev/rtc0
      rtcfile /var/lib/chrony/rtc
      rtcautotrim 30

      allow 10.0.0.0/8
      allow fd12:d04f:65d:42::0/56
    '';
  };

  services.samba = {
    # TODO restore /srv
    enable = false;
    nmbd.enable = false;
    settings = {
      global = {
        "log level" = 1;

        "interfaces" = lan;

        "min protocol" = "SMB2";
        "disable netbios" = true;
        "smb ports" = 445;

        "socket options" = "IPTOS_LOWDELAY TCP_NODELAY SO_KEEPALIVE SO_RCVBUF=65536 SO_SNDBUF=65536";
        "max xmit" = 131072;
        "min receivefile size" = 131072;

        "aio read size" = 1;
        "aio write size" = 1;

        "load printers" = false;
        "disable spoolss" = true;

        "mdns name" = "mdns";

        "follow symlinks" = true;

        "veto files" = "/Thumbs.db/.DS_Store/._.DS_Store/.apdisk/";
        "delete veto files" = true;
      };
      public = {
        path = "/srv/public";
        browseable = "yes";
        "guest ok" = "yes";
        "create mask" = "0666";
        "directory mask" = "0777";
        "read only" = "no";
      };
      Homes = {
        "read only" = "no";
        "valid users" = "%S";
        "inherit acls" = "yes";
      };
      Videos = {
        path = "/srv/videos";
        "valid users" = "alan";
        "create mask" = "0664";
        "directory mask" = "0775";
        "writeable" = "yes";
      };
    };
  };
  services.samba-wsdd = {
    enable = true;
    interface = lan;
  };

  system.stateVersion = "24.11";
}