all repos — nixfiles @ 524904051df123be3f6ed164c85929eefc61d999

System and user configuration, managed by nix and home-manager

system/hosts/linde.nix (view raw)

# 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,
  searchix,
  ...
}:

with lib;
let
  netif = "eth0";
  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-mask6 = 64;
  net-gw6 = "fe80::1";
  domain = "alin.ovh";
  oldDomain = "alanpearce.eu";
  email = "alan@alanpearce.eu";
  ts-domain = "fin-marlin.ts.net";
in
{
  imports = [
    # Include the results of the hardware scan.
    ./linde-hardware.nix

    ../settings/configuration/nix-linux.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;
    powerdns.file = ../../secrets/powerdns.age;
    redis-website.file = ../../secrets/redis-website.age;
    cifs-paperless.file = ../../secrets/cifs-paperless.age;
    cifs-transmission.file = ../../secrets/cifs-transmission.age;
    forgejo-actions-runner.file = ../../secrets/forgejo-actions-runner.age;
    rauthy.file = ../../secrets/rauthy.age;
  };

  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
    lsof
    powerdns
    litecli
    knot-dns

    nixpkgs-review
    nix-output-monitor
  ];

  # Initial empty root password for easy login:
  users.users.root.initialHashedPassword = "";
  services.openssh = {
    enable = true;
  };
  services.sshguard = {
    enable = true;
  };
  programs.ssh.extraConfig = ''
    Host ${domain} git.${domain}
      Hostname git.${domain}
      User gitolite
  '';

  programs.htop = {
    enable = true;
    settings = {
      header_margin = 0;
      tree_view = true;
      hide_userland_threads = true;
      hide_kernel_threads = true;
    };
  };

  nix = {
    settings = {
      max-jobs = 2;
      trusted-users = [
        "root"
        "nixremote"
      ];
    };
    gc = {
      dates = "08:15";
    };
    optimise = {
      automatic = true;
      dates = [ "02:30" ];
    };
  };

  system.autoUpgrade = {
    dates = "02:10";
    randomizedDelaySec = "59 min";
    allowReboot = true;
    flake = "git+file://${config.services.gitolite.dataDir}/repositories/nixfiles.git?submodules=1";
  };

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

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

  networking = {
    hostName = hostname;
    inherit domain;
    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
        "redis"
      ];
      ${net-ip6} = [
        "${hostname}.${domain}"
        hostname
        "redis"
      ];
    };
    defaultGateway = {
      address = net-gw;
      interface = netif;
    };
    defaultGateway6 = {
      address = net-gw6;
      interface = netif;
    };
    interfaces.${netif} = {
      ipv4 = {
        addresses = [
          {
            address = net-ip4;
            prefixLength = net-mask4;
          }
        ];
        routes = [
          {
            address = net-gw;
            prefixLength = 32;
          }
        ];
      };
      ipv6 = {
        addresses = [
          {
            address = net-ip6;
            prefixLength = net-mask6;
          }
        ];
      };
    };
    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"
        "podman0"
      ];
    };
    resolvconf = {
      enable = false;
      useLocalResolver = false;
    };
  };
  services.cloud-init.network.enable = false;
  services.resolved = {
    enable = true;
    llmnr = "false";
    dnssec = "true";
  };
  systemd.network.wait-online.enable = lib.mkForce true;

  services.tailscale = {
    enable = true;
    openFirewall = true;
    extraUpFlags = [ "--accept-routes" ];
    extraSetFlags = [
      "--advertise-exit-node"
      "--ssh"
    ];
    useRoutingFeatures = "both";
  };
  services.golink = {
    enable = true;
  };

  services.mycal = {
    enable = true;
    port = 8002;

    settings = {
      timezone = "Europe/Berlin";
      name = "Alin";
      inherit email;
    };
  };

  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.extraConfig = ''
    Defaults:root,%wheel env_keep+=EDITOR
  '';

  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"
      "podman"
      "laminar"
      "transmission"
    ];
    isNormalUser = true;
    home = "/home/alan";
    createHome = true;

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

  systemd.services.update-home-manager = {
    startAt = "weekly";
    path = with pkgs; [
      openssh
      nix
      git
    ];
    serviceConfig = rec {
      Type = "oneshot";
      User = "alan";
      ExecStart = "${lib.getExe pkgs.home-manager} switch --flake git://localhost/nixfiles?submodules=1#${User}@%H";
    };
  };

  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"
      "-ratelimit"
      "api-count:100/1"
    ];
  };

  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 ]}
        expand-alias=yes
        resolver=1.1.1.1
        local-address=${net-ip4} ${net-ip6}
        log-dns-details=no
        log-dns-queries=no
        loglevel=5
        primary=yes
        send-signed-notify=no

        default-api-rectify=yes
        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 = {
    enable = true;
    enableSubmission = true;
    submissionOptions = {
      smtpd_client_restrictions = "permit_inet_interfaces,reject";
      smtpd_tls_security_level = "none";
    };
    enableSubmissions = true;
    virtual = ''
      @${config.networking.hostName}.${domain} alan
      alan ${email}
    '';
    settings = {
      main = {
        mydomain = domain;
        mydestination = [ ];
        inet_interfaces = "loopback-only";
        smtp_bind_address = "0.0.0.0";
        smtp_bind_address6 = "::";
      };
      master."10.88.0.1:587" = {
        type = "inet";
        private = false;
        command = "smtpd";
        args = [
          "-o"
          "inet_interfaces=10.88.0.1"
          "-o"
          "mynetworks=10.88.0.0/16"
          "-o"
          "smtpd_client_restrictions=permit_mynetworks,reject"
          "-o"
          "smtpd_tls_security_level=none"
        ];
      };
    };
  };

  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 = {
      inherit email;
      dnsProvider = "pdns";
      credentialsFile = config.age.secrets.acme.path;
      reloadServices = [ "caddy" ];
      validMinDays = 32;
    };
    acceptTerms = true;
    certs."${oldDomain}" = {
      extraDomainNames = [ "*.${oldDomain}" ];
    };
    certs."stats.${domain}" = {
      extraDomainNames = [ "*.stats.${domain}" ];
    };
    certs."redis.${domain}" = {
      group = "redis-website";
      reloadServices = [ "redis-website" ];
    };
  };
  users.groups.acme.members = [
    "caddy"
  ];

  services.haproxy = {
    enable = true;
    config = ''
      defaults
        mode tcp
        timeout connect 5000ms
        timeout client 50000ms
        timeout server 50000ms

      frontend https
        bind ${net-ip4}:443

        tcp-request inspect-delay 5s
        acl sniff_https req.ssl_hello_type 1
        acl sniff_ssh req.payload(0,7) -m str "SSH-2.0"
        tcp-request content accept if sniff_https
        use_backend ssh if sniff_ssh
        use_backend ssh if { req_ssl_sni -i ssh.alin.ovh }

        default_backend caddy

      backend caddy
        server caddy [${net-ip6}]:443

      backend ssh
        mode tcp
        server ssh [${net-ip6}]:22
        timeout server 2h
    '';
  };
  services.caddy = {
    enable = true;
    group = "caddy";
    package = pkgs.nur.repos.xyenon.caddy.withPlugins {
      plugins = [
        "github.com/caddyserver/cache-handler@v0.16.0"
      ];
      hash = "sha256-MfzzRfL30c65XbeALnCZvGJaHa6e6icMjy4DUCBV284=";
    };
    globalConfig = ''
      cache
      default_bind [${net-ip6}]
    '';
    virtualHosts =
      let
        inherit (import ../../lib/caddy.nix { inherit lib; }) security-headers;
      in
      {
        "http://" = {
          # Needed for HTTP->HTTPS servers
        };
        "${hostname}.${domain}" = {
          serverAliases = [ "https://" ];
          extraConfig = ''
            respond * 204
            ${security-headers { }}
          '';
        };
        "pdns.${domain}" = {
          extraConfig = ''
            log {
              output discard
            }
            reverse_proxy 127.0.0.1:8081
          '';
        };
        "iam.alin.ovh" = {
          extraConfig = ''
            encode zstd gzip
            reverse_proxy http://127.0.0.1:8080
          '';
        };
        "files.${domain}" = {
          extraConfig = ''
            encode zstd gzip
            ${security-headers { }}
            root * /srv/http/files
            file_server browse
          '';
        };
        "searchix.${oldDomain}" = {
          extraConfig = ''
            redir https://searchix.ovh{uri} permanent
          '';
        };
        "searchix.ovh" = {
          extraConfig = ''
            root ${pkgs.searchix}/lib/searchix
            handle /static/* {
              file_server
            }
            handle_errors {
              rewrite * /error.html
              templates
              file_server
            }
            handle {
              reverse_proxy localhost:${toString config.services.searchix.settings.web.port} {
                health_uri /health
                health_status 2xx
                health_interval 10s
                health_timeout 1s
                health_fails 2
              }
            }
            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*
              }
            }
          '';
        };
        "stable.searchix.ovh" = {
          extraConfig = ''
            root ${pkgs.searchix}/lib/searchix
            handle /static/* {
              file_server
            }
            handle_errors {
              rewrite * /error.html
              templates
              file_server
            }
            handle {
              reverse_proxy localhost:51314 {
                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.${domain}" =
          let
            ns = config.services.nix-serve;
          in
          {
            extraConfig = ''
              reverse_proxy ${ns.bindAddress}:${toString ns.port}
            '';
          };
        "ci.${domain}" =
          let
            srv = config.services.laminar;
          in
          {
            extraConfig = ''
              reverse_proxy ${srv.settings.bindHTTP}
              handle_path /archive/* {
                root * ${srv.homeDir}/archive
                file_server browse
              }
            '';
          };
        "cal.${domain}" = {
          extraConfig =
            let
              srv = config.services.mycal;
            in
            ''
              encode zstd gzip
              cache {
                ttl 15m
              }
              reverse_proxy ${srv.host}:${toString srv.port}
            '';
        };
        "stats.${domain}" =
          let
            srv = config.services.goatcounter;
          in
          {
            useACMEHost = "stats.${domain}";
            serverAliases = [ "*.stats.${domain}" ];
            extraConfig = ''
              reverse_proxy ${srv.address}:${toString srv.port}
            '';
          };
        "go.${domain}" = {
          extraConfig = ''
            encode zstd gzip
            ${security-headers { }}
            root * /srv/http/go.alin.ovh
            file_server
          '';
        };
        "go.${oldDomain}" = {
          extraConfig = ''
            encode zstd gzip
            ${security-headers { }}
            root * /srv/http/go
            file_server
          '';
        };
        "glitch.${domain}" =
          let
            srv = config.services.glitchtip;
          in
          {
            extraConfig = ''
              encode zstd gzip
              ${security-headers { }}
              reverse_proxy ${srv.listenAddress}:${toString srv.port}
            '';
          };
        "*.${oldDomain}" = {
          useACMEHost = "${oldDomain}";
          extraConfig = ''
            redir https://{labels.2}.${domain}{uri}
          '';
        };
      };
  };
  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.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";
      tsHostname = "papers.${ts-domain}";
      tsPort = 41642;
      hostConfig = config;
    in
    {
      autoStart = true;
      enableTun = true;
      privateNetwork = true;
      hostAddress6 = "fc00::1";
      localAddress6 = "fc00::2";
      hostAddress = "10.6.42.1";
      localAddress = "10.6.42.2";
      forwardPorts = [
        {
          hostPort = tsPort;
        }
      ];
      bindMounts = {
        ${config.services.paperless.dataDir} = {
          hostPath = hostConfig.services.paperless.dataDir;
          isReadOnly = false;
        };
        ${externalDir} = {
          hostPath = externalDir;
          isReadOnly = false;
        };
      };
      config =
        { config, 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 = {
              ${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.groups.rauthy = { };
  users.users.rauthy = {
    isSystemUser = true;
    createHome = true;
    home = "/srv/rauthy";
    group = "rauthy";
  };
  virtualisation.oci-containers = {
    backend = "podman";
    containers = {
      rauthy =
        let
          domain = "alin.ovh";
          toml = pkgs.formats.toml { };
          cfg = {
            bootstrap = {
              admin_email = "admin@${domain}";
            };
            cluster = {
              node_id = 1;
              wal_ignore_lock = true;
            };
            encryption = {
              keys = [ "4babe063/HXY+N6xpGNaCZ5QsfIJB7jJscqmZfy/9UrGc3V2Kyi8=" ];
              key_active = "9f93e3c7";
            };
            email = {
              smtp_url = "host.containers.internal";
              smtp_port = 587;
              smtp_from = "Rauthy ";
              smtp_username = "rauthy";
              smtp_password = "rauthy";
              smtp_danger_insecure = true;
            };
            server = {
              scheme = "http";
              pub_url = "iam.${domain}";
              proxy_mode = true;
              trusted_proxies = [
                "10.0.0.0/8"
              ];
            };
            webauthn = {
              rp_id = domain;
              rp_origin = "https://iam.${domain}:443";
            };
          };
        in
        {
          image = "ghcr.io/sebadob/rauthy:0.30.1";
          ports = [ "8080:8080" ];
          user = "971:969";
          volumes = [
            "${toml.generate "config.toml" cfg}:/app/config.toml"
            "/srv/rauthy:/app/data"
          ];
          environment = {
            SMTP_URL = "host.containers.internal";
            SMTP_DANGER_INSECURE = "true";
          };
          environmentFiles = [ config.age.secrets.rauthy.path ];
          serviceName = "rauthy";
        };
    };
  };

  services.redis = {
    servers = {
      website = {
        enable = true;
        port = 0;
        bind = null;
        databases = 1;
        maxclients = 20;
        requirePassFile = config.age.secrets.redis-website.path;
        settings = {
          notify-keyspace-events = "KEA";
          tls-port = 6379;
          tls-cert-file = "/var/lib/acme/redis.${domain}/cert.pem";
          tls-key-file = "/var/lib/acme/redis.${domain}/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;
    environment = {
      GOMEMLIMIT = "2000MiB";
    };
    settings = {
      web =
        let
          baseURL = "https://searchix.ovh";
        in
        {
          inherit baseURL;
          sentryDSN = "https://b7abe00ca3f349f6bf6594141b934e01@glitch.${domain}/1";
          searchTimeout = "2s";
          contentSecurityPolicy =
            let
              self = "'self'";
            in
            {
              script-src = [
                (baseURL + "/static/")
                "'unsafe-inline'"
                "https://searchix.stats.${domain}"
                "https://browser.sentry-cdn.com"
              ];
              img-src = [
                self
                "https://searchix.stats.${domain}"
              ];
              connect-src = [
                self
                "https://searchix.stats.${domain}/count"
                "https://glitch.${domain}"
              ];
              worker-src = [
                "blob:"
              ];
            };
          extraHeadHTML = ''
            
            
            
          '';
        };

      importer.timeout = "2h";
      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 = "1h";
        };
        nixos = {
          enable = true;
          fetcher = "channel-nixpkgs";
          channel = "nixos-unstable";
        };
        nur.enable = true;
      };
    };
  };
  systemd.services.searchix.serviceConfig = {
    MemoryHigh = "1.5G";
    MemoryMax = "2G";
  };

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

  services.glitchtip = {
    enable = true;
    redis.createLocally = true;
    database.createLocally = true;
    settings = {
      GLITCHTIP_DOMAIN = "https://glitch.${domain}";
      ENABLE_ORGANIZATION_CREATION = true;
      DEFAULT_FROM_EMAIL = "glitch@${domain}";
      EMAIL_URL = "smtp://localhost:25";
      I_PAID_FOR_GLITCHTIP = !false;
    };
  };

  systemd.services.laminar.environment = {
    NIX_PATH = "nixpkgs=flake:nixpkgs";
  };
  services.laminar = {
    enable = true;
    path = with pkgs; [
      fish
      bash
      gnutar
      bzip2
      gzip
      git
      cached-nix-shell
      nix
      nix-build-uncached
      config.programs.ssh.package
      config.services.laminar.package
      moreutils
      flock
      just
      rsync
      go
    ];
    settings = {
      title = "CI";
      bindHTTP = "[::1]:8002";
      keepRundirs = 1;
      archiveURL = "https://ci.${domain}/archive/";
    };
    timers = {
      update-flake-nix-packages = {
        startAt = "Saturday";
        accuracy = "6 hours";
        reason = "Weekly flake update";
      };
    };
  };
  users.users.laminar = {
    homeMode = "770";
  };
  users.groups.laminar.members = [ "caddy" ];

  services.gitea-actions-runner = {
    package = pkgs.forgejo-actions-runner;
    instances.linde = {
      name = "linde";
      enable = true;
      url = "https://codeberg.org";
      tokenFile = config.age.secrets.forgejo-actions-runner.path;
      labels = [
        "docker:docker://node:24-alpine"
        "alpine-latest:docker://node:24-alpine"
        "nixos:host"
      ];

      hostPackages = with pkgs; [
        bash
        coreutils
        curl
        gawk
        gitMinimal
        gnused
        nix
        nodejs
        wget
      ];
    };
  };

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

  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.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;
      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 "";
            };
          };
        };
    };

  containers.searchix-stable = {
    autoStart = true;
    config =
      { ... }:
      {
        imports = [
          searchix.nixosModules.web
        ];

        system.stateVersion = "25.05";

        services.searchix = {
          enable = true;
          environment = {
            GOMEMLIMIT = "2000MiB";
          };
          settings = {
            web = {
              baseURL = "https://stable.searchix.ovh";
              port = 51314;
              sentryDSN = "https://c17b225c82834b878cac25158b294ed0@glitch.alin.ovh/3";
              contentSecurityPolicy = {
                script-src = [
                  (config.services.searchix.settings.web.baseURL + "/static/")
                  "'unsafe-inline'"
                  "https://searchix-stable.stats.${domain}"
                  "https://browser.sentry-cdn.com"
                ];
                img-src = [
                  "'self'"
                  "https://searchix-stable.stats.${domain}"
                ];
                connect-src = [
                  "'self'"
                  "https://searchix-stable.stats.${domain}/count"
                  "https://glitch.${domain}"
                ];
                worker-src = [
                  "blob:"
                ];
              };
              extraHeadHTML = ''
                
                
                
              '';
            };

            importer = {
              timeout = "45m";
              updateAt = "07:00:00";
              sources =
                let
                  stableVersion = "25.05";
                in
                {
                  nixpkgs = {
                    enable = true;
                    fetcher = "channel-nixpkgs";
                    channel = "nixos-${stableVersion}";
                    timeout = "30m";
                    manpages = {
                      enable = true;
                      path = "/doc/manpage-urls.json";
                    };
                  };
                  nixos = {
                    enable = true;
                    fetcher = "channel-nixpkgs";
                    channel = "nixos-${stableVersion}";
                  };
                };
            };
          };
        };
      };
  };
}