{
  config,
  lib,
  pkgs,
  options,
  ...
}:
let
  inherit (lib) mkIf;
  metricsEnable = false;
  mkAcl = src: dst: {
    action = "accept";
    inherit src dst;
  };
  mkSshAcl = src: dst: users: {
    action = "accept";
    inherit src dst users;
  };
in
mkIf config.services.headscale.enable {
  environment.systemPackages = [ config.services.headscale.package ];

  networking.firewall.allowedTCPPorts = [
    3478 # DERP
  ];

  services = {
    headscale = {
      address = "127.0.0.1";
      port = 8521;

      settings = {
        server_url = "https://hs.ccnlc.eu";
        tls_cert_path = null;
        tls_key_path = null;
        ip_prefixes = [
          "100.64.0.0/10"
          "fd7a:115c:a1e0::/48"
        ];
        ephemeral_node_inactivity_timeout = "30m";
        node_update_check_interval = "10s";
        metrics_listen_addr = mkIf metricsEnable "127.0.0.1:8086";
        # logging
        log = {
          format = "text";
          level = "info";
        };

        logtail.enabled = false;

        dns_config = {
          override_local_dns = true;
          magic_dns = true;
          nameservers = [
            "100.64.0.4"
          ];
          extra_records =
            let
              mkRecords = map (sub: {
                name = "${sub}.ccnlc.eu";
                type = "A";
                value = "100.64.0.4";
              });
            in
            [
              {
                name = "ccnlc.eu";
                type = "A";
                value = "100.64.0.4";
              }
            ]
            # Tailscale doesn't seem to support wildcard A/AAAA records
            # - https://github.com/juanfont/headscale/issues/2159#issuecomment-2393406444
            ++ mkRecords [
              "immich"
              "adguard"
              "nextcloud"
              "kitchenowl"
              "navidrome"
              "subsonic"
              "nextcloud"
              "paperless"
              "truenas"
              "fritz"
            ];
        };

        derp = {
          # Reference: https://github.com/juanfont/headscale/issues/1326#issuecomment-1505487881
          server = {
            enabled = true;
            stun_listen_addr = "0.0.0.0:3478";

            # Region code and name are displayed in the Tailscale UI to identify a DERP region
            region_code = "headscale";
            region_name = "Headscale Embedded DERP";
            region_id = 999;
          };

          urls = [ ];
          paths = [ ];

          auto_update_enabled = false;
          update_frequency = "6h";
        };
      };
    };

    nginx.virtualHosts."hs.ccnlc.eu" = {
      forceSSL = true;
      enableACME = true;
      #quic = true;
      http3 = true;

      locations = {
        "/" = {
          proxyPass = "http://localhost:${toString config.services.headscale.port}";
          proxyWebsockets = true;
        };

        "/metrics" = mkIf metricsEnable {
          proxyPass = "http://${toString config.services.headscale.settings.metrics_listen_addr}/metrics";
        };
      };

      extraConfig = ''
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
      '';
    };
  };

  systemd.services = {
    tailscaled.after = [ "headscale.service" ];
    headscale = {
      environment = {
        # required to use ssh
        HEADSCALE_EXPERIMENTAL_FEATURE_SSH = "1";
      };
    };
  };

  services.headscale.settings.acl_policy_path = pkgs.writeTextFile {
    name = "headscale-acl.hujson";
    text = builtins.toJSON {
      acls = [
        (mkAcl [ "tag:client" ] [
          "tag:client:*"
          "tag:server:*"
        ]) # client -> {client, server}
        (mkAcl [
          "tag:client"
          "tag:server"
        ] [ "tag:backup:${toString options.modules.server.rsync-daemon.port.default}" ])
      ];

      ssh = [
        (mkSshAcl [ "tag:client" ] [
          "tag:server"
          "tag:client"
        ] [ "ny" ]) # client -> {client, server}
      ];

      tags = [
        "tag:client"
        "tag:server"
        "tag:backup"
      ];

      tagOwners =
        let
          users = [ "ny" ];
          tags = map (name: "tag:${name}") [
            "server"
            "client"
            "backup"
          ];
        in
        lib.genAttrs tags (_: users);

      autoApprovers = {
        exitNode = [ "*" ];
      };
    };
  };
}