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