# 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-rdnsip = "2a01:4f8:c012:23a4::53"; net-mask6 = "64"; net-gw6 = "fe80::1"; in { imports = [ # Include the results of the hardware scan. ./linde-hardware.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; 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.enableAllTerminfo = true; environment.homeBinInPath = true; environment.localBinInPath = true; environment.systemPackages = with pkgs; [ htop lsof gitMinimal powerdns sqlite-interactive knot-dns nixpkgs-review nix-output-monitor ]; programs.ssh = with pkgs; { knownHostsFiles = [ (writeText "github.keys" '' # github.com:22 SSH-2.0-babeld-05989c77 # github.com:22 SSH-2.0-babeld-05989c77 # github.com:22 SSH-2.0-babeld-05989c77 # github.com:22 SSH-2.0-babeld-05989c77 # github.com:22 SSH-2.0-babeld-05989c77 github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk= github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg= github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl '') (writeText "gitlab.keys" '' # gitlab.com:22 SSH-2.0-GitLab-SSHD # gitlab.com:22 SSH-2.0-GitLab-SSHD # gitlab.com:22 SSH-2.0-GitLab-SSHD # gitlab.com:22 SSH-2.0-GitLab-SSHD # gitlab.com:22 SSH-2.0-GitLab-SSHD gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9 gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY= gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf '') (writeText "codeberg.keys" '' # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2 # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2 # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2 # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2 # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2 codeberg.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8hZi7K1/2E2uBX8gwPRJAHvRAob+3Sn+y2hxiEhN0buv1igjYFTgFO2qQD8vLfU/HT/P/rqvEeTvaDfY1y/vcvQ8+YuUYyTwE2UaVU5aJv89y6PEZBYycaJCPdGIfZlLMmjilh/Sk8IWSEK6dQr+g686lu5cSWrFW60ixWpHpEVB26eRWin3lKYWSQGMwwKv4LwmW3ouqqs4Z4vsqRFqXJ/eCi3yhpT+nOjljXvZKiYTpYajqUC48IHAxTWugrKe1vXWOPxVXXMQEPsaIRc2hpK+v1LmfB7GnEGvF1UAKnEZbUuiD9PBEeD5a1MZQIzcoPWCrTxipEpuXQ5Tni4mN codeberg.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL2pDxWr18SoiDJCGZ5LmxPygTlPu+cCKSkpqkvCyQzl5xmIMeKNdfdBpfbCGDPoZQghePzFZkKJNR/v9Win3Sc= codeberg.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIVIC02vnjFyL+I4RHfvIGNtOgJMe769VTF1VR4EB3ZB '') (writeText "sr.ht.keys" '' # git.sr.ht:22 SSH-2.0-OpenSSH_9.6 # git.sr.ht:22 SSH-2.0-OpenSSH_9.6 # git.sr.ht:22 SSH-2.0-OpenSSH_9.6 # git.sr.ht:22 SSH-2.0-OpenSSH_9.6 # git.sr.ht:22 SSH-2.0-OpenSSH_9.6 git.sr.ht ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZ+l/lvYmaeOAPeijHL8d4794Am0MOvmXPyvHTtrqvgmvCJB8pen/qkQX2S1fgl9VkMGSNxbp7NF7HmKgs5ajTGV9mB5A5zq+161lcp5+f1qmn3Dp1MWKp/AzejWXKW+dwPBd3kkudDBA1fa3uK6g1gK5nLw3qcuv/V4emX9zv3P2ZNlq9XRvBxGY2KzaCyCXVkL48RVTTJJnYbVdRuq8/jQkDRA8lHvGvKI+jqnljmZi2aIrK9OGT2gkCtfyTw2GvNDV6aZ0bEza7nDLU/I+xmByAOO79R1Uk4EYCvSc1WXDZqhiuO2sZRmVxa0pQSBDn1DB3rpvqPYW+UvKB3SOz git.sr.ht ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCj6y+cJlqK3BHZRLZuM+KP2zGPrh4H66DacfliU1E2DHAd1GGwF4g1jwu3L8gOZUTIvUptqWTkmglpYhFp4Iy4= git.sr.ht ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZvRd4EtM7R+IHVMWmDkVU3VLQTSwQDSAvW0t2Tkj60 '') ]; }; # Initial empty root password for easy login: users.users.root.initialHashedPassword = ""; services.openssh = { enable = true; settings = { PermitRootLogin = "no"; 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"; flags = [ "--no-write-lock-file" "--impure" "--update-input" "nixpkgs-small" "--update-input" "searchix" ]; }; nix = { daemonCPUSchedPolicy = "batch"; daemonIOSchedPriority = 6; settings = { max-jobs = 2; auto-optimise-store = true; trusted-users = [ "root" "nixremote" ]; experimental-features = [ "nix-command" "flakes" ]; substituters = [ "https://nix-community.cachix.org" ]; trusted-public-keys = [ "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" ]; }; 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; 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}.alanpearce.eu" hostname ]; ${net-ip6} = [ "${hostname}.alanpearce.eu" hostname ]; ${net-rdnsip} = [ "dns" ]; }; firewall = { enable = true; allowPing = true; pingLimit = "--limit 60/minute --limit-burst 30"; logRefusedConnections = false; allowedTCPPorts = [ 22 80 443 53 853 9418 6922 ]; allowedUDPPorts = [ 53 3478 6885 # DHT 6922 ]; }; resolvconf = { enable = false; useLocalResolver = false; }; }; services.resolved = { enable = true; llmnr = "false"; dnssec = "true"; }; systemd.network = { enable = true; networks.${netif} = { name = netif; routes = [ { routeConfig = { Gateway = net-gw6; PreferredSource = net-ip6; QuickAck = true; InitialCongestionWindow = 30; InitialAdvertisedReceiveWindow = 30; }; } { routeConfig = { Gateway = net-gw; QuickAck = true; InitialCongestionWindow = 30; InitialAdvertisedReceiveWindow = 30; }; } ]; address = [ "${net-ip6}/${net-mask6}" "${net-rdnsip}/${net-mask6}" ]; addresses = [{ addressConfig = { Address = "${net-ip4}/${net-mask4}"; Peer = "${net-gw}/32"; }; }]; }; }; services.tailscale = { enable = true; extraUpFlags = [ "--accept-routes" ]; useRoutingFeatures = "client"; }; services.golink = { enable = true; tailscaleAuthKeyFile = config.age.secrets.golink.path; }; services.journald.extraConfig = '' MaxRetentionSec=1 month ''; 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 "" ''; }; programs.zsh.enable = true; users.users.root.shell = "${pkgs.fish}/bin/fish"; users.users.alan = { shell = "${pkgs.fish}/bin/fish"; extraGroups = [ "wheel" "caddy" "docker" ]; isNormalUser = true; home = "/home/alan"; createHome = true; openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII8VIII+598QOBxi/52O1Kb19RdUdX0aZmS1/dNoyqc5 alan@hetzner.strongbox" ]; }; users.users.nixremote = { shell = "/bin/sh"; isNormalUser = true; home = "/var/lib/nixremote/"; createHome = true; openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBxa7lxDu0M4chats/VvpFzjT3ruexKa3J9UC6ASo3bN root@NanoPi.lan" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBmDSZnUzIPQowLrKSa24eSb1WFQe7yPjTcDPPe3UY0Q nix@mba" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE9of82WBHK8nr8L9RGeieLMfcAWaFCeCkmvYHM9LCuT nanopi" "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.powerdns = { 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=216.218.130.2 allow-axfr-ips=216.218.133.2,2001:470:600::2 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 ''; }; systemd.services.hagezi-blocklist-update = { enable = true; startAt = "daily"; serviceConfig = { CacheDirectory = "blocklist"; UMask = "0077"; DynamicUser = "yes"; ProtectSystem = "strict"; ProtectHome = true; PrivateTmp = true; PrivateDevices = true; PrivateUsers = true; ProtectClock = true; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectKernelLogs = true; ProtectControlGroups = true; ProtectProc = "invisible"; RestrictAddressFamilies = "AF_INET AF_INET6"; RestrictNamespaces = true; RestrictRealtime = true; LockPersonality = true; MemoryDenyWriteExecute = "true"; SystemCallFilter = [ "~@clock" "~@cpu-emulation" "~@debug" "~@module" "~@mount" "~@obsolete" "~@privileged" "~@raw-io" "~@reboot" "~@resources" "~@swap" ]; SystemCallArchitectures = "native"; CapabilityBoundingSet = ""; DevicePolicy = "closed"; ProcSubset = "pid"; NoNewPrivileges = true; ExecStart = "${pkgs.curl}/bin/curl --no-progress-meter --output %C/blocklist/hagezi.rpz https://raw.githubusercontent.com/hagezi/dns-blocklists/main/rpz/pro.plus.txt"; # https://raw.githubusercontent.com/hagezi/dns-blocklists/main/rpz/pro.plus.txt" ExecStartPost = [ "+/bin/sh -c 'exec install --compare --mode=644 %C/blocklist/hagezi.rpz /etc/knot-resolver/blocklist.rpz'" "-/bin/sh -c 'exec rm -f %C/blocklist/hagezi.rpz'" ]; Environment = [ "HOME=%C/blocklist" ]; }; }; services.kresd = { enable = true; # package = pkgs.knot-resolver.override { extraFeatures = true; }; listenPlain = [ "[${net-rdnsip}]:53" ]; listenTLS = [ "127.0.0.1:853" "[::1]:853" "${net-ip4}:853" "[${net-ip6}]:853" ]; listenDoH = [ "[::1]:443" "127.0.0.1:443" ]; instances = 2; extraConfig = '' modules = { 'rebinding < iterate', 'hints > iterate', 'serve_stale < cache', 'stats', predict = { window = 30, period = 24 * (60/30), }, 'nsid', } local systemd_instance = os.getenv("SYSTEMD_INSTANCE") nsid.name(systemd_instance) log_groups({ 'policy' }) cache.size = 500 * MB net.tls( '/var/lib/acme/dns.alanpearce.eu/cert.pem', '/var/lib/acme/dns.alanpearce.eu/key.pem' ) -- override blocklist policy.add(policy.suffix(policy.PASS, policy.todnames({ }))) policy.add(policy.rpz( policy.DENY_MSG('domain blocked by hagezi'), '/etc/knot-resolver/blocklist.rpz', false -- needs wrapped kresd -- true -- will watch the file for updates )) policy.add(policy.domains(policy.REFUSE, policy.todnames({ 'use-application-dns.net', 'telemetry.astro.build', }))) -- disable DNSSEC when using Quad9 since they do it -- trust_anchors.remove('.') -- policy.add(policy.all(policy.TLS_FORWARD({ -- {'2620:fe::fe', hostname='dns.quad9.net'}, -- {'2620:fe::9', hostname='dns.quad9.net'}, -- {'9.9.9.9', hostname='dns.quad9.net'}, -- {'149.112.122.122', hostname='dns.quad9.net'}, -- }))) ''; }; 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-etc-nixos = { startAt = "04:30"; path = with pkgs; [ rdiff-backup openssh ]; script = '' rdiff-backup --api-version 201 backup /etc/nixos ${hostname}@home.alanpearce.eu::nixos rdiff-backup --api-version 201 remove increments --older-than 3M ${hostname}@home.alanpearce.eu::nixos ''; serviceConfig = { Type = "oneshot"; }; }; systemd.services.backup-gitolite = { startAt = "daily"; path = with pkgs; [ rdiff-backup openssh ]; script = '' rdiff-backup --api-version 201 backup ${config.services.gitolite.dataDir} ${hostname}@home.alanpearce.eu::gitolite rdiff-backup --api-version 201 remove increments --older-than 3M ${hostname}@home.alanpearce.eu::gitolite ''; serviceConfig.Type = "oneshot"; }; systemd.services.backup-paperless = { startAt = "daily"; path = with pkgs; [ sudo rdiff-backup openssh ]; script = '' sudo -u paperless ./paperless-manage document_exporter --delete --use-filename-format --no-archive --no-thumbnail --no-progress-bar ./export rdiff-backup --api-version 201 backup ./export ${hostname}@home.alanpearce.eu::paperless rdiff-backup --api-version 201 remove increments --older-than 3M ${hostname}@home.alanpearce.eu::paperless ''; serviceConfig = { Type = "oneshot"; WorkingDirectory = config.services.paperless.dataDir; }; }; 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" ]; }; certs."dns.alanpearce.eu" = { reloadServices = map (x: "kresd@${toString x}") (range 1 config.services.kresd.instances); group = "knot-resolver"; }; }; users.groups.acme.members = [ "caddy" ]; services.caddy = { enable = true; group = "caddy"; globalConfig = '' auto_https disable_certs default_bind ${net-ip6} ${net-ip4} ''; virtualHosts = let subValue = v: if builtins.isList v then builtins.concatStringsSep " " (builtins.map (v: (if lib.strings.hasPrefix "http" v then v else "'${v}'")) v) else toString v; headerValue = sep: val: if builtins.isAttrs val then builtins.concatStringsSep "; " (lib.attrsets.mapAttrsToList (k: v: if builtins.isBool v then k else "${k}${sep}${subValue v}" ) val) else toString val; genHeader = header: let sep = if header == "content-security-policy" then " " else "="; in value: "${header} \"${headerValue sep value}\""; headers = matcher: headers: '' header ${matcher} { ${builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList genHeader headers)} } ''; security-headers = { matcher ? "", overrides ? { } }: headers matcher ({ strict-transport-security = { max-age = 2 * 365 * 24 * 60 * 60; }; x-content-type-options = "nosniff"; x-frame-options = "DENY"; } // overrides); in { "http://" = { # Needed for HTTP->HTTPS servers }; "${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 ''; }; "git.alanpearce.eu" = let fcgi = config.services.fcgiwrap; fcgisocket = "${fcgi.socketType}/${fcgi.socketAddress}"; in { useACMEHost = "alanpearce.eu"; extraConfig = '' root * ${pkgs.cgit-pink}/cgit/ encode zstd gzip ${security-headers { overrides.content-security-policy = { default-src = [ "none" ]; base-uri = [ "none" ]; style-src = [ "self" "unsafe-inline" ]; script-src = [ "self" "unsafe-inline" ]; form-action = [ "self" ]; connect-src = [ "self" ]; img-src = [ "https" ]; object-src = [ "none" ]; }; }} handle_path /custom/* { file_server { root /srv/http/cgit/ } } rewrite /robots.txt /assets/robots.txt handle_path /assets/* { file_server { hide cgit.cgi } } @git_http_backend path_regexp "^.*/(HEAD|info/refs|objects/info/[^/]+|git-upload-pack)$" handle @git_http_backend { reverse_proxy ${fcgisocket} { transport fastcgi { env SCRIPT_FILENAME ${pkgs.git}/libexec/git-core/git-http-backend env GIT_PROJECT_ROOT ${config.services.gitolite.dataDir}/repositories } } } handle { reverse_proxy ${fcgisocket} { transport fastcgi { env SCRIPT_FILENAME {http.vars.root}/cgit.cgi env CGIT_CONFIG ${pkgs.writeText "cgitrc" '' head-include=/srv/http/cgit/responsive-cgit-css-master/head.html css=/custom/responsive-cgit-css-master/cgit.css virtual-root=/ logo= readme=:README.md source-filter=${pkgs.cgit-pink}/lib/cgit/filters/syntax-highlighting.py about-filter=${pkgs.cgit-pink}/lib/cgit/filters/about-formatting.sh enable-git-config=1 enable-index-owner=0 enable-index-links=1 enable-follow-links=0 enable-log-linecount=1 max-stats=year snapshots=tar.lz tar.zst zip cache-size=10240 enable-http-clone=1 enable-commit-graph=1 mimetype-file=${pkgs.nginx}/conf/mime.types section-from-path=1 noplainemail=1 repository-sort=age root-title=my personal projects clone-url=git://git.alanpearce.eu/$CGIT_REPO_URL https://git.alanpearce.eu/$CGIT_REPO_URL remove-suffix=1 strict-export=git-daemon-export-ok scan-path=${config.services.gitolite.dataDir}/repositories/ ''} } } } ''; }; "ntfy.alanpearce.eu" = { useACMEHost = "alanpearce.eu"; extraConfig = '' encode zstd gzip ${security-headers {}} reverse_proxy localhost${config.services.ntfy-sh.settings.listen-http} ''; }; "searchix.alanpearce.eu" = { useACMEHost = "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* } } ''; }; "legit.alanpearce.eu" = let server = config.services.legit.settings.server; in { useACMEHost = "alanpearce.eu"; extraConfig = '' encode zstd gzip handle_path /static/* { root * /srv/http/legit/src/static file_server } ${security-headers { overrides.content-security-policy = { default-src = [ "none" ]; base-uri = [ "none" ]; style-src = [ "self" ]; script-src = [ "none" ]; form-action = [ "self" ]; connect-src = [ "self" ]; img-src = [ "https" ]; object-src = [ "none" ]; }; }} reverse_proxy ${server.host}:${toString server.port} ''; }; "papers.alanpearce.eu" = { extraConfig = '' encode zstd gzip handle_path /static/* { root * ${config.services.paperless.package}/lib/paperless-ngx/static file_server } reverse_proxy localhost:${toString config.services.paperless.port} ''; }; "binarycache.alanpearce.eu" = let ns = config.services.nix-serve; in { extraConfig = '' reverse_proxy ${ns.bindAddress}:${toString ns.port} ''; }; }; }; systemd.services.caddy.serviceConfig = { UMask = "007"; }; services.fcgiwrap = { enable = true; user = "gitolite"; group = "gitolite"; preforkProcesses = 2; socketType = "tcp6"; socketAddress = "[::1]:9000"; }; services.gitolite = { enable = true; adminPubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII8VIII+598QOBxi/52O1Kb19RdUdX0aZmS1/dNoyqc5 alan@hetzner.strongbox"; extraGitoliteRc = '' $RC{UMASK} = 0027; $RC{LOG_EXTRA} = 0; $RC{HOSTNAME} = "${config.networking.hostName}"; $RC{LOCAL_CODE} = "$rc{GL_ADMIN_BASE}/local"; push( @{$RC{ENABLE}}, 'D' ); push( @{$RC{ENABLE}}, 'Shell alan' ); push( @{$RC{ENABLE}}, 'cgit' ); push( @{$RC{ENABLE}}, 'repo-specific-hooks' ); ''; }; services.legit = { enable = true; group = "gitolite"; settings = { server.name = "legit.alanpearce.eu"; dirs = { templates = "/srv/http/legit/src/templates"; }; repo = { scanPath = "/srv/http/legit/repos"; readme = [ "readme" "readme.md" "README.md" ]; }; }; }; users.groups.git.gid = config.ids.gids.git; services.gitDaemon = { enable = true; user = "gitolite"; group = "gitolite"; basePath = "${config.services.gitolite.dataDir}/repositories/"; }; users.groups.paperless.members = [ "alan" "syncthing" ]; services.paperless = { enable = true; package = pkgs.paperless-ngx; dataDir = "/srv/paperless"; settings = { PAPERLESS_DBENGINE = "sqlite"; PAPERLESS_TIME_ZONE = "Europe/Berlin"; PAPERLESS_URL = "https://papers.alanpearce.eu"; PAPERLESS_TRUSTED_PROXIES = "127.0.0.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_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; }; }; services.etcd = { enable = true; initialClusterState = "existing"; dataDir = "/var/lib/etcd"; # TODO backup }; services.dex = let issuer = "https://id.alanpearce.eu/"; in { enable = true; environmentFile = config.age.secrets.dex.path; settings = { inherit issuer; storage = { type = "etcd"; config = { endpoints = config.services.etcd.listenClientUrls; namespace = "dex/"; }; }; 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" ]; } ]; }; }; services.syncthing = { enable = true; dataDir = "/srv/syncthing"; configDir = "/var/lib/syncthing"; 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://gc.zgo.at" "https://js-de.sentry-cdn.com" "https://browser.sentry-cdn.com" ]; img-src = [ self "https://gc.zgo.at" ]; connect-src = [ self "https://searchix.goatcounter.com/count" "*.sentry.io" ]; worker-src = [ "blob:" ]; }; extraHeadHTML = '' ''; }; 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"; }; nixos = { enable = true; fetcher = "channel-nixpkgs"; channel = "nixos-unstable"; }; }; }; }; }