# Edit this configuration file to define what should be installed on
# your system.  Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running `nixos-help`).

{ config, lib, pkgs, ... }:

with lib;
let
  netif = "enp1s0";
  hostname = "linde";
  net-ip4 = "116.203.248.56";
  net-mask4 = "32";
  net-gw = "172.31.1.1";
  net-ip6 = "2a01:4f8:c012:23a4::1";
  net-redisip = "2a01:4f8:c012:23a4::6379";
  net-mask6 = "64";
  net-gw6 = "fe80::1";
  domain = "alanpearce.eu";
  ts-domain = "hydra-pinecone.ts.net";
in
{
  imports =
    [
      # Include the results of the hardware scan.
      ./linde-hardware.nix

      ./settings/configuration/nix.nix
      ./settings/services/git-server.nix
    ];
  age.secrets = {
    paperless =
      let
        cfg = config.services.paperless;
      in
      {
        file = ../secrets/paperless.age;
        path = "${cfg.dataDir}/nixos-paperless-secret-key";
        owner = cfg.user;
        mode = "400";
        symlink = false;
      };
    acme.file = ../secrets/acme.age;
    binarycache.file = ../secrets/binarycache.age;
    dex.file = ../secrets/dex.age;
    powerdns.file = ../secrets/powerdns.age;
    redis-website.file = ../secrets/redis-website.age;
    photoprism.file = ../secrets/photoprism.age;
    cifs-photoprism.file = ../secrets/cifs-photoprism.age;
    cifs-paperless.file = ../secrets/cifs-paperless.age;
    cifs-transmission.file = ../secrets/cifs-transmission.age;
    golink = let golink = config.services.golink; in {
      # hope this doesn't collide...
      path = "${golink.dataDir}/.config/tsnet-golink/auth.key";
      owner = golink.user;
      mode = "400";
      symlink = false;
      file = ../secrets/golink.age;
    };
  };

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

  time.timeZone = "Europe/Berlin";

  i18n.defaultLocale = "en_GB.UTF-8";

  environment.homeBinInPath = true;
  environment.localBinInPath = true;
  environment.systemPackages = with pkgs; [
    cifs-utils
    htop
    lsof
    powerdns
    sqlite-interactive
    knot-dns

    nixpkgs-review
    nix-output-monitor
  ];

  # Initial empty root password for easy login:
  users.users.root.initialHashedPassword = "";
  services.openssh = {
    enable = true;
    settings = {
      PermitRootLogin = "without-password";
      PasswordAuthentication = false;
      KbdInteractiveAuthentication = false;
    };
  };
  services.sshguard = {
    enable = true;
    services = [ "sshd" ];
  };
  programs.mosh.enable = true;

  system.autoUpgrade = {
    enable = true;
    dates = "02:10";
    randomizedDelaySec = "59 min";
    allowReboot = true;
    flake = "git+file://${config.services.gitolite.dataDir}/repositories/nixfiles.git?submodules=1";
    flags = [
      "--no-write-lock-file"
      "--impure"
      "--update-input"
      "nixpkgs"
      "--update-input"
      "nixpkgs-small"
    ];
  };

  nix = {
    settings = {
      max-jobs = 2;
      auto-optimise-store = true;
      trusted-users = [ "root" "nixremote" ];
    };
    gc = {
      automatic = true;
      dates = "08:15";
      options = "--delete-older-than 14d";
    };
    optimise = {
      automatic = true;
      dates = [ "02:30" ];
    };
  };

  services.nix-serve = {
    enable = true;
    package = pkgs.nix-serve-ng;
    secretKeyFile = config.age.secrets.binarycache.path;
  };

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

  networking = {
    hostName = hostname;
    inherit domain;
    useDHCP = false;
    dhcpcd.enable = false;
    nameservers = [
      "2606:4700:4700::1111"
      "2606:4700:4700::1001"
      "1.1.1.1"
      "1.0.0.1"
    ];
    hosts = lib.mkForce {
      ${net-ip4} = [ "${hostname}.${domain}" hostname ];
      ${net-ip6} = [ "${hostname}.${domain}" hostname ];
      ${net-redisip} = [ "redis" ];
    };
    firewall = {
      enable = true;
      allowPing = true;
      pingLimit = "--limit 60/minute --limit-burst 30";
      logRefusedConnections = false;
      allowedTCPPorts = [
        22
        80
        443
        53
        853
        6379
        9418
        6922
        config.services.transmission.settings.peer-port
      ];
      allowedUDPPorts = [
        53
        443 # HTTP/3 (QUIC)
        3478
        6885 # DHT
        6922
        config.services.transmission.settings.peer-port
      ];
      trustedInterfaces = [ "tailscale0" ];
    };
    resolvconf = {
      enable = false;
      useLocalResolver = false;
    };
  };
  services.resolved = {
    enable = true;
    llmnr = "false";
    dnssec = "true";
  };
  systemd.network = {
    enable = true;
    networks.${netif} =
      {
        name = netif;
        routes = [
          {
            Gateway = net-gw6;
            PreferredSource = net-ip6;
            QuickAck = true;
            InitialCongestionWindow = 30;
            InitialAdvertisedReceiveWindow = 30;
          }
          {
            Gateway = net-gw;
            QuickAck = true;
            InitialCongestionWindow = 30;
            InitialAdvertisedReceiveWindow = 30;
          }
        ];
        address = [
          "${net-ip6}/${net-mask6}"
          "${net-redisip}/${net-mask6}"
        ];
        addresses = [{
          Address = "${net-ip4}/${net-mask4}";
          Peer = "${net-gw}/32";
        }];
      };
    wait-online = {
      extraArgs = [ "--interface=${netif}" ];
    };
  };

  services.tailscale = {
    enable = true;
    extraUpFlags = [ "--accept-routes" ];
    extraSetFlags = [ "--advertise-exit-node" ];
    useRoutingFeatures = "client";
  };
  services.golink = {
    enable = true;
    tailscaleAuthKeyFile = config.age.secrets.golink.path;
  };

  services.journald.extraConfig = ''
    MaxRetentionSec=1 month
  '';

  zramSwap = {
    enable = true;
    algorithm = "zstd";
  };

  boot.kernel.sysctl =
    let
      buffer_size = 16 * 1024 * 1024;
      server_count = 2;
      max_clients = 100;
      page_size = 4096;
      # This server might have 100 clients simultaneously, so:
      #   max(tcp_wmem) * 2 * 100 / 4096
      mem = toString (buffer_size * server_count * max_clients / page_size);
    in
    {
      "net.ipv4.tcp_allowed_congestion_control" = "bbr illinois reno";
      "net.ipv4.tcp_congestion_control" = "bbr";
      "net.core.default_qdisc" = "fq";

      # Provide adequate buffer memory.
      # rmem_max and wmem_max are TCP max buffer size
      # settable with setsockopt(), in bytes
      # tcp_rmem and tcp_wmem are per socket in bytes.
      # tcp_mem is for all TCP streams, in 4096-byte pages.
      # The following are suggested on IBM's
      # High Performance Computing page
      "net.core.rmem_max" = buffer_size;
      "net.core.wmem_max" = buffer_size;
      "net.core.rmem_default" = buffer_size;
      "net.core.wmem_default" = buffer_size;
      "net.ipv4.tcp_rmem" = "4096 87380 ${toString buffer_size}";
      "net.ipv4.tcp_wmem" = "4096 87380 ${toString buffer_size}";
      "net.ipv4.tcp_mem" = "${mem} ${mem} ${mem}";

      "net.ipv4.tcp_sack" = false;
      "net.ipv4.tcp_dsack" = false;

      "net.ipv4.tcp_slow_start_after_idle" = false;
    };

  security.sudo.execWheelOnly = true;
  security.sudo.extraConfig = ''
    Defaults:root,%wheel env_keep+=EDITOR
  '';

  nixpkgs = {
    config.allowUnfree = true;
  };

  programs.fish = {
    enable = true;
    interactiveShellInit = ''
      set --universal fish_greeting ""
    '';
  };
  users.users.root = {
    shell = "/run/current-system/sw/bin/fish";
    openssh.authorizedKeys.keys = [
      "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHYUyDdw92TNXguAxcmcmZmn/7ECGdRp6ckjxU+5zCw3BCnsS5+xEvHBVnnFdJRoH2XpfMeJjE+fi67zFVhlbn4= root@secretive.marvin"
    ];
  };
  users.users.alan = {
    shell = "/run/current-system/sw/bin/fish";
    extraGroups = [ "wheel" "caddy" "docker" "laminar" "transmission" ];
    isNormalUser = true;
    home = "/home/alan";
    createHome = true;

    openssh.authorizedKeys.keys = [
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII8VIII+598QOBxi/52O1Kb19RdUdX0aZmS1/dNoyqc5 alan@hetzner.strongbox"
      "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJVREjPey2TOIPzfYJoG9yIR4Rui7tNJK2QIKa+pbgsyXg31hhPIw37LRRIic+l53mW8eahHxX3Y1IeTjcMw8IU= alan@secretive.marvin"
    ];
  };

  users.users.nixremote = {
    shell = "/bin/sh";
    isNormalUser = true;
    home = "/var/lib/nixremote/";
    createHome = true;
    openssh.authorizedKeys.keys = [
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPKGzXGgFm/4Da2KNl1wb7TC2YOu/baP/2eFVBaJY0Wq root@marvin"
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIy9jFioBvV0JA0lc+De2N+vDOABGHgCECW6vkD33CE4 sourcehut"
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIII7sWEwsm8JZiJ0LUnjSt0Kg1RXypG6p5AzP/R2n5ca actions@github.com"
    ];
  };

  # This value determines the NixOS release from which the default
  # settings for stateful data, like file locations and database versions
  # on your system were taken. It's perfectly fine and recommended to leave
  # this value at the release version of the first install of this system.
  # Before changing this value read the documentation for this option
  # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
  system.stateVersion = "23.05"; # Did you read the comment?

  services.goatcounter = {
    enable = true;
    address = "localhost";
    port = 8082;
    proxy = true;
    extraArgs = [
      "-db"
      "sqlite3+db/goatcounter.sqlite3"
      "-websocket"
      "-automigrate"
      "-smtp"
      "smtp://localhost:25"
    ];
  };

  services.powerdns =
    let
      inherit (lib.lists) flatten;
      inherit (lib.strings) concatStringsSep;
      he = {
        notify = "216.218.130.2";
        axfr = [
          "216.218.133.2"
          "2001:470:600::2"
        ];
      };
      iplist = ips: concatStringsSep "," (flatten ips);
    in
    {
      enable = true;
      secretFile = config.age.secrets.powerdns.path;
      extraConfig = ''
        launch=gsqlite3
        dnsupdate=yes
        allow-dnsupdate-from=0.0.0.0/0,::/0
        only-notify=
        also-notify=${iplist [ he.notify ]}
        allow-axfr-ips=${iplist [ he.axfr ]}
        outgoing-axfr-expand-alias=yes
        expand-alias=yes
        resolver=1.1.1.1
        local-address=${net-ip4} ${net-ip6}
        reuseport=yes
        log-dns-details=no
        log-dns-queries=no
        loglevel=5
        primary=yes
        secondary=yes
        send-signed-notify=no
        prevent-self-notification=no

        default-soa-edit=inception-increment

        api=yes
        # replaced by secretFile/envsubst
        api-key=$API_KEY

        gsqlite3-database=/var/db/pdns/zones.db
        gsqlite3-pragma-foreign-keys=yes
        gsqlite3-dnssec=yes
      '';
    };

  services.postfix =
    let
      localUser = "alan";
      forwardingAddress = "alan@alanpearce.eu";
    in
    {
      enable = true;
      destination = [ ];
      domain = config.networking.domain;
      virtual = ''
        @${config.networking.hostName}.${config.networking.domain} ${localUser}
        ${localUser} ${forwardingAddress}
      '';
      config = {
        inet_interfaces = "loopback-only";
      };
    };

  users.groups.ntfy = { };
  users.users.ntfy = {
    isSystemUser = true;
    group = "ntfy";
  };
  services.ntfy-sh = {
    enable = true;
    user = "ntfy";
    group = "ntfy";
    settings = {
      base-url = "https://ntfy.alanpearce.eu";
      listen-http = ":2586";
      behind-proxy = true;
      manager-interval = "1h";
      cache-startup-queries = ''
        PRAGMA journal_mode = WAL;
        PRAGMA busy_timeout = 5000;
        PRAGMA synchronous = NORMAL;
      '';
      cache-file = "/var/cache/ntfy/cache.db";
      attachment-cache-dir = "/var/cache/ntfy/attachments";
      auth-default-access = "deny-all";
      auth-file = "/var/lib/ntfy/user.db";
      upstream-base-url = "https://ntfy.sh";
    };
  };
  systemd.services.ntfy-sh = {
    serviceConfig = {
      DynamicUser = lib.mkForce false;
      StateDirectory = lib.mkForce "ntfy";
      RuntimeDirectory = "ntfy";
      CacheDirectory = "ntfy";
    };
  };

  systemd.services.backup-gitolite = {
    startAt = "daily";
    path = with pkgs; [
      openssh
    ];
    serviceConfig = {
      Type = "oneshot";
      ExecStart = "${lib.getExe pkgs.rdiff-backup} --api-version 201 backup ${config.services.gitolite.dataDir} ${hostname}@nano.${ts-domain}::gitolite";
      ExecStartPost = "-${lib.getExe pkgs.rdiff-backup} --api-version 201 remove increments --older-than 3M ${hostname}@nano.${ts-domain}::gitolite";
    };
  };

  systemd.services.backup-paperless = {
    startAt = "daily";
    path = with pkgs; [
      openssh
    ];
    serviceConfig = {
      Type = "oneshot";
      WorkingDirectory = config.services.paperless.dataDir;
      ExecStart = [
        "systemd-run --machine=papers sudo -u paperless ./paperless-manage document_exporter --delete --use-filename-format --no-archive --no-thumbnail --no-progress-bar ./export  "
        "${lib.getExe pkgs.rdiff-backup} --api-version 201 backup /srv/paperless/export ${hostname}@nano.${ts-domain}::paperless"
      ];
      ExecStartPost = "-${lib.getExe pkgs.rdiff-backup} --api-version 201 remove increments --older-than 3M ${hostname}@nano.${ts-domain}::paperless";
    };
  };

  security.acme = {
    defaults = {
      email = "alan@alanpearce.eu";
      dnsProvider = "pdns";
      dnsResolver = "1.1.1.1:53";
      credentialsFile = config.age.secrets.acme.path;
      reloadServices = [ "caddy" ];
      validMinDays = 32;
    };
    acceptTerms = true;
    certs."alanpearce.eu" = {
      extraDomainNames = [ "*.alanpearce.eu" "*.linde.alanpearce.eu" ];
    };
    certs."stats.alanpearce.eu" = {
      extraDomainNames = [ "*.stats.alanpearce.eu" ];
    };
    certs."redis.alanpearce.eu" = {
      group = "redis-website";
    };
  };
  users.groups.acme.members = [
    "caddy"
  ];

  services.caddy = {
    enable = true;
    group = "caddy";
    globalConfig = ''
      auto_https disable_certs
      default_bind ${net-ip6} ${net-ip4}
    '';
    virtualHosts =
      let
        inherit (import ../lib/caddy.nix { inherit lib; }) security-headers;
      in
      {
        "http://" = {
          # Needed for HTTP->HTTPS servers
        };
        "alanpearce.eu" = {
          serverAliases = [ "www.alanpearce.eu" "test.alanpearce.eu" ];
          useACMEHost = "alanpearce.eu";
          extraConfig = ''
            encode zstd gzip
            root * /srv/http/website/public
            file_server
            ${security-headers {}}
            handle_errors {
              rewrite * /404.html
              file_server
            }
          '';
        };
        "${hostname}.alanpearce.eu" = {
          serverAliases = [ "https://" ];
          useACMEHost = "alanpearce.eu";
          extraConfig = ''
            respond * 204
            ${security-headers {}}
          '';
        };
        "pdns.alanpearce.eu" = {
          useACMEHost = "alanpearce.eu";
          extraConfig = ''
            log {
              output discard
            }
            reverse_proxy 127.0.0.1:8081
          '';
        };
        "id.alanpearce.eu" = {
          useACMEHost = "alanpearce.eu";
          extraConfig = ''
            encode zstd gzip
            ${security-headers {}}
            reverse_proxy http://${config.services.dex.settings.web.http}
          '';
        };
        "dns.alanpearce.eu" = {
          useACMEHost = "alanpearce.eu";
          extraConfig = ''
            log {
              output discard
            }
            encode zstd gzip
            reverse_proxy localhost:443 {
              transport http {
                tls_server_name dns.alanpearce.eu
              }
            }
          '';
        };
        "files.alanpearce.eu" = {
          useACMEHost = "alanpearce.eu";
          extraConfig = ''
            encode zstd gzip
            ${security-headers {}}
            root * /srv/http/files
            file_server browse
          '';
        };
        "ntfy.alanpearce.eu" = {
          useACMEHost = "alanpearce.eu";
          extraConfig = ''
            encode zstd gzip
            ${security-headers {}}
            reverse_proxy localhost${config.services.ntfy-sh.settings.listen-http} {
              health_uri /v1/health
              health_body `"healthy":true`
            }
          '';
        };
        "searchix.alanpearce.eu" = {
          useACMEHost = "alanpearce.eu";
          serverAliases = [ "searchix.linde.alanpearce.eu" ];
          extraConfig = ''
            reverse_proxy localhost:${toString config.services.searchix.settings.web.port} {
              health_uri /health
              health_status 2xx
            }
            encode zstd gzip {
              match {
                header Content-Type text/*
                header Content-Type application/json*
                header Content-Type application/javascript*
                header Content-Type application/opensearchdescription+xml
                header Content-Type application/atom+xml*
                header Content-Type application/rss+xml*
                header Content-Type image/svg+xml*
              }
            }
          '';
        };
        "binarycache.alanpearce.eu" =
          let
            ns = config.services.nix-serve;
          in
          {
            useACMEHost = "alanpearce.eu";
            extraConfig = ''
              reverse_proxy ${ns.bindAddress}:${toString ns.port}
            '';
          };
        "ci.alanpearce.eu" =
          let
            srv = config.services.laminar;
          in
          {
            useACMEHost = "alanpearce.eu";
            extraConfig = ''
              reverse_proxy ${srv.settings.bindHTTP}
            '';
          };
        "stats.alanpearce.eu" =
          let
            srv = config.services.goatcounter;
          in
          {
            useACMEHost = "stats.alanpearce.eu";
            serverAliases = [ "*.stats.alanpearce.eu" ];
            extraConfig = ''
              reverse_proxy ${srv.address}:${toString srv.port}
            '';
          };
        "go.alanpearce.eu" = {
          useACMEHost = "alanpearce.eu";
          extraConfig = ''
            encode zstd gzip
            ${security-headers {}}
            root * /srv/http/go
            file_server
          '';
        };
        "photos.alanpearce.eu" =
          let
            srv = config.services.photoprism;
          in
          {
            useACMEHost = "alanpearce.eu";
            extraConfig = ''
              encode zstd gzip
              ${security-headers {}}
              reverse_proxy ${srv.address}:${toString srv.port}
              handle_errors {
                respond "{err.status_code} {err.status_text}"
              }
            '';
          };
      };
  };
  systemd.services.caddy.serviceConfig = {
    UMask = "007";
  };

  networking.nat = {
    enable = true;
    internalInterfaces = [ "ve-+" ];
    externalInterface = netif;
    enableIPv6 = true;
  };

  users.users.paperless = {
    group = "paperless";
    uid = config.ids.uids.paperless;
    home = "/srv/paperless";
  };
  users.groups.paperless.members = [ "alan" "syncthing" ];

  fileSystems."/srv/paperless" = {
    device = "//u439959-sub3.your-storagebox.de/u439959-sub3";
    fsType = "smb3";
    options =
      let
        # prevents hanging on network split
        automount_opts = [
          "x-systemd.automount"
          "noauto"
          "x-systemd.idle-timeout=1h"
          "x-systemd.mount-timeout=5s"
        ];
        uid = config.ids.uids.paperless;
      in
      automount_opts ++ [
        "credentials=${config.age.secrets.cifs-paperless.path}"
        "seal"
        "multichannel"
        "nobrl" # needed for sqlite
        "forceuid"
        "forcegid"
        "uid=${toString uid}"
        "gid=${toString uid}"
      ];
  };
  containers.papers =
    let
      externalDir = "/srv/paperless";
      localAddress6 = "fc00::2";
      tsHostname = "papers.${ts-domain}";
      tsPort = 41642;
      hostConfig = config;
    in
    {
      autoStart = true;
      # does TS need this?
      enableTun = true;
      privateNetwork = true;
      hostAddress6 = "fc00::1";
      inherit localAddress6;
      forwardPorts = [{
        hostPort = tsPort;
      }];
      bindMounts = {
        ${config.services.paperless.dataDir} = {
          hostPath = hostConfig.services.paperless.dataDir;
          isReadOnly = false;
        };
        ${externalDir} = {
          hostPath = externalDir;
          isReadOnly = false;
        };
      };
      config = { config, lib, pkgs, ... }: {
        environment.systemPackages = with pkgs; [
          lsof
        ];
        networking = {
          useHostResolvConf = false;
          resolvconf.enable = false;
          firewall.trustedInterfaces = [ "tailscale0" ];
          firewall.rejectPackets = true;
          nameservers = hostConfig.networking.nameservers;
        };
        services.resolved = {
          enable = true;
          llmnr = "false";
        };
        services.tailscale = {
          enable = true;
          openFirewall = true;
          permitCertUid = "caddy";
          port = tsPort;
        };
        services.tailscaleAuth = {
          enable = true;
          group = "caddy";
        };
        services.caddy = {
          enable = true;
          email = "caddy@alanpearce.eu";
          virtualHosts = {
            "http://" = {
              # avoid logging to an awkward file name based on the attribute name i.e. http://
              hostName = "papers";
              extraConfig = ''
                redir ${tsHostname}{uri}
              '';
            };
            ${tsHostname} = {
              extraConfig = ''
                encode zstd gzip
                tls {
                  get_certificate tailscale
                }
                handle_path /static/* {
                  root * ${config.services.paperless.package}/lib/paperless-ngx/static
                  file_server
                }
                forward_auth unix//run/tailscale-nginx-auth/tailscale-nginx-auth.sock {
                  uri /auth
                  header_up Expected-Tailnet "${ts-domain}."
                  header_up Remote-Addr {remote_host}
                  header_up Remote-Port {remote_port}
                  header_up Original-URI {uri}
                  copy_headers {
                    Tailscale-User>X-Webauth-User
                    Tailscale-Name>X-Webauth-Name
                    Tailscale-Login>X-Webauth-Login
                    Tailscale-Tailnet>X-Webauth-Tailnet
                    Tailscale-Profile-Picture>X-Webauth-Profile-Picture
                  }
                }
                reverse_proxy [::1]:${toString config.services.paperless.port}
              '';
            };
          };
        };
        services.paperless = {
          enable = true;
          address = "[::1]";
          mediaDir = "${externalDir}/media";
          settings = {
            PAPERLESS_DBENGINE = "sqlite";
            PAPERLESS_TIME_ZONE = "Europe/Berlin";

            PAPERLESS_URL = "https://${tsHostname}";
            PAPERLESS_TRUSTED_PROXIES = "[::1]";
            PAPERLESS_USE_X_FORWARD_HOST = true;
            PAPERLESS_USE_X_FORWARD_PORT = true;
            PAPERLESS_PROXY_SSL_HEADER = [ "HTTP_X_FORWARDED_PROTO" "https" ];
            PAPERLESS_ENABLE_COMPRESSION = false; # let caddy do it

            PAPERLESS_ENABLE_HTTP_REMOTE_USER = true;
            PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME = "HTTP_X_WEBAUTH_LOGIN";

            PAPERLESS_OCR_SKIP_ARCHIVE_FILE = "with_text";
            PAPERLESS_OCR_LANGUAGE = "deu+eng";
            PAPERLESS_IGNORE_DATES = "09.08.90";

            PAPERLESS_TASK_WORKERS = 2;
            PAPERLESS_THREADS_PER_WORKER = 1;
            PAPERLESS_NUMBER_OF_SUGGESTED_DATES = 4;

            PAPERLESS_CONSUMER_IGNORE_PATTERN = [ ".DS_STORE/*" "desktop.ini" ".stfolder/*" ".stversions/*" ];

            PAPERLESS_FILENAME_FORMAT = "{correspondent}/{created} {title} {asn}";
            PAPERLESS_FILENAME_FORMAT_REMOVE_NONE = true;
          };
        };
        system.stateVersion = "24.11";
      };
    };

  users.users.dex = {
    home = "/var/lib/dex";
    createHome = true;
    isSystemUser = true;
    group = "dex";
  };
  users.groups.dex = { };
  services.dex =
    let
      issuer = "https://id.alanpearce.eu/";
    in
    {
      enable = true;
      environmentFile = config.age.secrets.dex.path;
      settings = {
        inherit issuer;
        storage = {
          type = "sqlite3";
          config.file = "/var/lib/dex/storage.sqlite";
        };
        web.http = "127.0.0.1:5556";
        connectors = [{
          type = "github";
          id = "github";
          name = "GitHub";
          config = {
            clientID = "$GITHUB_CLIENT_ID";
            clientSecret = "$GITHUB_CLIENT_SECRET";
            redirectURI = "${issuer}callback";
            orgs = [{
              name = "alan-pearce";
            }];
            teamNameField = "slug";
            useLoginAsID = true;
          };
        }];
        staticClients = [
          {
            name = "Tailscale";
            id = "oCaiv7aije1thaep0eib";
            secretEnv = "TAILSCALE_CLIENT_SECRET";
            redirectURIs = [ "https://login.tailscale.com/a/oauth_response" ];
          }
        ];
      };
    };
  systemd.services.dex.serviceConfig =
    let
      user = config.users.users.dex;
    in
    {
      ReadWritePaths = [ user.home ];
      DynamicUser = lib.mkForce false;
      User = user.name;
      Group = user.group;
    };

  services.redis = {
    servers = {
      website = {
        enable = true;
        port = 0;
        bind = net-redisip;
        databases = 1;
        maxclients = 6;
        requirePassFile = config.age.secrets.redis-website.path;
        settings = {
          tls-port = 6379;
          tls-cert-file = "/var/lib/acme/redis.alanpearce.eu/cert.pem";
          tls-key-file = "/var/lib/acme/redis.alanpearce.eu/key.pem";
          tls-ca-cert-file = "/etc/ssl/certs/ca-certificates.crt";
          tls-auth-clients = false;
        };
      };
    };
  };

  services.syncthing = {
    enable = true;
    dataDir = "/srv/syncthing";
    configDir = "/var/lib/syncthing";
    guiAddress = "[::]:8384";
    openDefaultPorts = true;
    overrideDevices = false;
    overrideFolders = false;
  };

  services.searchix = {
    enable = true;
    settings = {
      web =
        let
          baseURL = "https://searchix.alanpearce.eu";
        in
        {
          inherit baseURL;
          sentryDSN = "https://26d4cd8d20157ae2f6b4726ceae1a563@o4507187730120704.ingest.de.sentry.io/4507187734970448";
          contentSecurityPolicy =
            let
              self = "'self'";
            in
            {
              script-src = [
                (baseURL + "/static/")
                "https://searchix.stats.alanpearce.eu"
                "https://js-de.sentry-cdn.com"
                "https://browser.sentry-cdn.com"
              ];
              img-src = [
                self
                "https://searchix.stats.alanpearce.eu"
              ];
              connect-src = [
                self
                "https://searchix.stats.alanpearce.eu/count"
                "*.sentry.io"
              ];
              worker-src = [
                "blob:"
              ];
            };
          extraHeadHTML = ''
            <script async
              src="https://js-de.sentry-cdn.com/d735e99613a86e1625fb85d0e8e762de.min.js"
              crossorigin="anonymous"></script>
            <script data-goatcounter="https://searchix.stats.alanpearce.eu/count"
                    async src="//searchix.stats.alanpearce.eu/count.v4.js"
                    crossorigin="anonymous"
                    integrity="sha384-nRw6qfbWyJha9LhsOtSb2YJDyZdKvvCFh0fJYlkquSFjUxp9FVNugbfy8q1jdxI+"></script>
          '';
        };

      importer.timeout = "45m";
      importer.sources = {
        darwin = {
          enable = true;
          fetcher = "download";
          url = "https://alanpearce.github.io/nix-options/darwin";
        };
        home-manager = {
          enable = true;
          fetcher = "download";
          url = "https://alanpearce.github.io/nix-options/home-manager";
        };
        nixpkgs = {
          enable = true;
          fetcher = "channel-nixpkgs";
          channel = "nixos-unstable";
          timeout = "30m";
        };
        nixos = {
          enable = true;
          fetcher = "channel-nixpkgs";
          channel = "nixos-unstable";
        };
      };
    };
  };

  programs.git = {
    enable = true;
    package = pkgs.gitMinimal;
    config = {
      advice = {
        detachedHead = false;
        mergeConflict = false;
      };
    };
  };

  systemd.services.laminar.environment = {
    NIX_PATH = "nixpkgs=flake:nixpkgs";
  };
  services.laminar = {
    enable = true;
    path = with pkgs; [
      bash
      coreutils
      git
      cached-nix-shell
      nix
      config.programs.ssh.package
      moreutils
      flock
      just
    ];
    settings = {
      bindHTTP = "[::1]:8002";
      keepRundirs = 1;
    };
  };
  users.users.laminar = {
    homeMode = "770";
  };

  virtualisation.containers = {
    enable = true;
    policy = {
      default = [{ type = "insecureAcceptAnything"; }];
    };
  };

  fileSystems."/srv/photoprism" = {
    device = "//u439959-sub1.your-storagebox.de/u439959-sub1";
    fsType = "smb3";
    options =
      let
        # prevents hanging on network split
        automount_opts = [
          "x-systemd.automount"
          "noauto"
          "x-systemd.idle-timeout=1h"
          "x-systemd.mount-timeout=5s"
        ];
        uid = 64600;
      in
      automount_opts ++ [
        "credentials=${config.age.secrets.cifs-photoprism.path}"
        "seal"
        "multichannel"
        "nobrl" # needed for sqlite
        "forceuid"
        "forcegid"
        "uid=${toString uid}"
        "gid=${toString uid}"
      ];
  };
  services.photoprism = {
    enable = true;
    passwordFile = config.age.secrets.photoprism.path;
    originalsPath = "/srv/photoprism/originals";
    importPath = "/srv/photoprism/import";
    settings = {
      PHOTOPRISM_SITE_URL = "https://photos.alanpearce.eu";
      PHOTOPRISM_SITE_CAPTION = "Alan‘s Photos";
      PHOTOPRISM_DISABLE_TLS = "true";
      PHOTOPRISM_SIDECAR_PATH = "/srv/photoprism/sidecar";
      PHOTOPRISM_SPONSOR = "true";
    };
  };
  systemd.services.photoprism = {
    unitConfig.RequiresMountsFor = "/srv/photoprism";
    serviceConfig.ReadWritePaths = [
      "/srv/photoprism/sidecar"
    ];
  };

  fileSystems."/srv/transmission" = {
    device = "//u439959-sub4.your-storagebox.de/u439959-sub4";
    fsType = "smb3";
    options =
      let
        # prevents hanging on network split
        automount_opts = [
          "x-systemd.automount"
          "noauto"
          "x-systemd.idle-timeout=1h"
          "x-systemd.mount-timeout=5s"
        ];
      in
      automount_opts ++ [
        "credentials=${config.age.secrets.cifs-transmission.path}"
        "seal"
        "multichannel"
        "nobrl" # needed for sqlite
        "forceuid"
        "forcegid"
        "uid=${toString config.ids.uids.transmission}"
        "gid=${toString config.ids.gids.transmission}"
      ];
  };
  containers.bt =
    let
      externalDir = "/srv/transmission";
      tsHostname = "bt.${ts-domain}";
      tsPort = 41643;
      hostConfig = config;
    in
    {
      autoStart = true;
      # does TS need this?
      enableTun = true;
      privateNetwork = true;
      hostAddress6 = "fc00::1";
      localAddress6 = "fc00::9091";
      hostAddress = "10.231.91.1";
      localAddress = "10.231.91.10";
      forwardPorts = [
        { hostPort = tsPort; }
        { hostPort = config.services.transmission.settings.peer-port; }
      ];
      bindMounts = {
        ${config.services.transmission.home} = {
          hostPath = hostConfig.services.transmission.home;
          isReadOnly = false;
        };
        ${externalDir} = {
          hostPath = externalDir;
          isReadOnly = false;
        };
      };
      config = { config, lib, pkgs, ... }: {
        system.stateVersion = "24.11";
        networking = {
          useHostResolvConf = false;
          resolvconf.enable = false;
          firewall.trustedInterfaces = [ "tailscale0" ];
          firewall.rejectPackets = true;
          nameservers = hostConfig.networking.nameservers;
        };
        services.resolved = {
          enable = true;
          llmnr = "false";
        };
        services.tailscale = {
          enable = true;
          openFirewall = true;
          permitCertUid = "caddy";
          port = tsPort;
        };
        services.caddy = {
          enable = true;
          email = "caddy@alanpearce.eu";
          virtualHosts = {
            "http://" = {
              hostName = "bt";
              extraConfig = ''
                redir ${tsHostname}{uri}
              '';
            };
            ${tsHostname} = {
              extraConfig = ''
                encode zstd gzip
                tls {
                  get_certificate tailscale
                }
                reverse_proxy localhost:${toString config.services.transmission.settings.rpc-port}
              '';
            };
          };
        };
        services.transmission = {
          enable = true;
          openFirewall = true;
          webHome = pkgs.flood-for-transmission;
          settings = {
            utp-enabled = true;
            incomplete-dir-enabled = true;
            incomplete-dir = "/srv/transmission/leeching";
            download-dir = "/srv/transmission/seeding";
            watch-dir = "/srv/transmission/watch";
            watch-dir-enabled = true;
            rpc-bind-address = "::1";
            rpc-whitelist-enabled = false;
            rpc-host-whitelist = tsHostname;
            rpc-host-whitelist-enabled = true;
          };
        };
        systemd.services.transmission = {
          serviceConfig = {
            RootDirectory = lib.mkForce "";
          };
        };
      };
    };
}