summary refs log tree commit diff stats
path: root/system/nanopi.nix
diff options
context:
space:
mode:
Diffstat (limited to 'system/nanopi.nix')
-rwxr-xr-xsystem/nanopi.nix859
1 files changed, 859 insertions, 0 deletions
diff --git a/system/nanopi.nix b/system/nanopi.nix
new file mode 100755
index 00000000..3b10e606
--- /dev/null
+++ b/system/nanopi.nix
@@ -0,0 +1,859 @@
+{ config
+, pkgs
+, lib
+, inputs
+, ...
+}:
+let
+  fsTypes = [ "f2fs" "ext" "exfat" "vfat" ];
+in
+{
+  imports = [
+    ./nanopi-hardware.nix
+    (inputs.nixos-hardware + "/friendlyarm/nanopi-r5s")
+  ];
+
+  age.secrets = {
+    dyndns.file = ../secrets/dyndns.age;
+    acme.file = ../secrets/acme.age;
+    syncthing.file = ../secrets/syncthing.age;
+  };
+
+  boot = {
+    supportedFilesystems = fsTypes;
+    initrd.supportedFilesystems = fsTypes;
+
+    loader.timeout = 1;
+    kernelPatches = lib.mkForce [ ];
+  };
+
+  systemd.services."irqbalance-oneshot" = {
+    enable = true;
+    description = "Distribute interrupts after boot using \"irqbalance --oneshot\"";
+    documentation = [ "man:irqbalance" ];
+    wantedBy = [ "sysinit.target" ];
+    serviceConfig = {
+      Type = "oneshot";
+      RemainAfterExit = true;
+      ExecStart = "${pkgs.irqbalance.out}/bin/irqbalance --foreground --oneshot";
+    };
+  };
+  systemd.tmpfiles.settings."leds-off" = {
+    "/sys/class/leds/green:*/brightness" = {
+      w = {
+        argument = "0";
+      };
+    };
+  };
+
+  services.udev.extraRules = ''
+    # set scheduler for NVMe
+    ACTION=="add|change", KERNEL=="nvme[0-9]n[0-9]", ATTR{queue/scheduler}="kyber"
+    # set scheduler for SSD and eMMC
+    ACTION=="add|change", KERNEL=="sd[a-z]|mmcblk[0-9]*", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="mq-deadline"
+    # set scheduler for rotating disks
+    ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="kyber"
+  '';
+
+  systemd.services.dynamic-dns-update = {
+    enable = true;
+    startAt = [ "hourly" ];
+    description = "Update IP addresses";
+    path = with pkgs; [ curl iproute2 dig.dnsutils miller ];
+    after = [ "sys-devices-platform-fe2a0000.ethernet-net-wan0.device" ];
+    bindsTo = [ "sys-devices-platform-fe2a0000.ethernet-net-wan0.device" ];
+    serviceConfig = {
+      Type = "oneshot";
+      ExecStart = "/bin/sh /etc/nixos/update-ip ${config.age.secrets.dyndns.path}";
+    };
+  };
+
+  services.journald.extraConfig = ''
+    MaxRetentionSec=1 month
+  '';
+
+  environment.systemPackages = with pkgs; [
+    kitty.terminfo
+    htop
+    lsof
+    usbutils
+    lzop
+    zstd
+    sqlite
+  ];
+
+  systemd.network.config.networkConfig = {
+    SpeedMeter = true;
+  };
+  networking = {
+    hostName = "nanopi";
+    domain = "lan";
+    useDHCP = false;
+    useNetworkd = true;
+    nameservers = [
+      "176.9.93.198"
+      "176.9.1.117"
+      "2a01:4f8:151:34aa::198"
+      "2a01:4f8:141:316d::117"
+    ];
+    firewall = {
+      enable = true;
+      rejectPackets = true;
+      logRefusedConnections = false;
+      pingLimit = "5/second";
+      filterForward = true; # we are a router
+      allowedUDPPorts = [
+        53
+        123
+      ];
+      allowedTCPPorts = [
+        53
+        123
+        80
+        443
+      ];
+      interfaces.bridge0 = {
+        allowedTCPPorts = [
+          53
+          67
+          139
+          445
+          1883
+          3689
+          5357
+          5533 # SmartDNS
+          8096
+          9091 # Transmission
+          8096 # Jellyfin
+        ];
+        allowedUDPPorts = [
+          53
+          67
+          69
+          137
+          4011 # PXE
+          5533 # SmartDNS
+          5353
+          5355 # LLMNR
+          1900 # DLNA Jellyfin
+          3702 # Samba WSDD
+          21027 # Syncthing LNDP
+          41641
+          51827
+        ];
+      };
+      interfaces.wan0 = {
+        allowedTCPPorts = [
+          6980 # aria2c
+        ];
+        allowedUDPPorts = [
+          6976
+          6980
+          41641
+        ];
+      };
+      extraForwardRules = ''
+        iifname { "wan0", "wlan0", "wwan0" } oifname { "lan1", "lan2", "bridge0" } icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, mld-listener-query, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
+        iifname { "lan1", "lan2", "bridge0" } oifname { "wan0", "wlan0", "wwan0" } accept
+      '';
+    };
+    nftables = {
+      enable = true;
+      tables = {
+        firewall = {
+          family = "inet";
+          content = ''
+            chain postrouting {
+              type nat hook postrouting priority srcnat; policy accept;
+              oifname { "wan0", "wlan0", "wwan0" } masquerade
+            }
+            chain prerouting {
+              type nat hook prerouting priority dstnat;
+              iifname "wan0" tcp dport { 6922, 51413 } dnat ip to 10.0.0.42
+            }
+          '';
+        };
+      };
+    };
+    wireless = {
+      enable = true;
+      # iwd = {
+      #   enable = true;
+      #   settings = {
+      #     Network = {
+      #       RoutePriorityOffset = 300;
+      #     };
+      #   };
+      # };
+    };
+  };
+  services.resolved.enable = false;
+
+  programs.command-not-found.enable = false;
+
+  services.openssh = {
+    enable = true;
+    openFirewall = true;
+    startWhenNeeded = false;
+  };
+  programs.mosh.enable = true;
+  services.sshguard = {
+    enable = true;
+    services = [ "sshd" ];
+  };
+
+  systemd.network = {
+    enable = true;
+    wait-online = {
+      ignoredInterfaces = [ "wan0" "wlan0" "wwan0" ];
+    };
+    links = {
+      "10-name-lan1" = {
+        matchConfig.Path = "platform-3c0000000.pcie-pci-0000:01:00.0";
+        linkConfig = {
+          Name = "lan1";
+          MACAddress = "a8:95:85:0d:67:38";
+        };
+      };
+      "10-name-lan2" = {
+        matchConfig.Path = "platform-3c0400000.pcie-pci-0001:01:00.0";
+        linkConfig = {
+          Name = "lan2";
+          MACAddress = "a8:95:85:0d:67:39";
+        };
+      };
+      "10-name-wan0" = {
+        matchConfig.Path = "platform-fe2a0000.ethernet";
+        linkConfig = {
+          Name = "wan0";
+          MACAddress = "a8:95:85:0d:67:3a";
+        };
+      };
+      "10-name-wlan0" = {
+        matchConfig.MACAddress = "9c:53:22:33:bf:e9";
+        linkConfig = {
+          Name = "wlan0";
+        };
+      };
+      "10-name-wwan0" = {
+        matchConfig.MACAddress = "34:4b:50:00:00:00";
+        linkConfig = {
+          Name = "wwan0";
+        };
+      };
+    };
+    netdevs = {
+      "20-bridge" = {
+        netdevConfig = {
+          Kind = "bridge";
+          Name = "bridge0";
+        };
+      };
+    };
+    networks = {
+      "30-lan-ports" = {
+        matchConfig.Name = "lan*";
+        bridge = [ "bridge0" ];
+        linkConfig = {
+          MACAddress = "82:E0:06:9C:8E:7C";
+          RequiredForOnline = "no";
+        };
+        networkConfig.LinkLocalAddressing = "no";
+      };
+      "40-bridge0" = {
+        matchConfig.Name = "bridge0";
+        linkConfig.RequiredForOnline = "routable";
+        address = [
+          "10.0.0.1/20"
+          "fd12:d04f:65d:42::1/56"
+        ];
+        networkConfig = {
+          IPv6AcceptRA = false;
+          IPv6SendRA = true;
+          DHCPPrefixDelegation = true;
+          ConfigureWithoutCarrier = true;
+        };
+        dhcpPrefixDelegationConfig = {
+          UplinkInterface = "wan0";
+          SubnetId = "42";
+          Assign = true;
+          Token = "::1";
+        };
+        ipv6SendRAConfig = {
+          RouterLifetimeSec = 1800;
+          EmitDNS = true;
+          DNS = "fd12:d04f:65d:42::1";
+          EmitDomains = true;
+          Domains = [ config.networking.domain ];
+        };
+      };
+      "50-wwan0" = {
+        matchConfig.Name = "wwan0";
+        linkConfig.RequiredForOnline = false;
+        networkConfig = {
+          DHCP = "yes";
+          IPv6AcceptRA = true;
+          IPForward = "yes";
+        };
+        dhcpV4Config = {
+          UseDNS = false;
+          SendHostname = false;
+          RouteMetric = 2048;
+        };
+        ipv6AcceptRAConfig.UseDNS = false;
+        routes = [
+          {
+            routeConfig = {
+              Gateway = "_dhcp4";
+              QuickAck = true;
+              InitialCongestionWindow = 30;
+              InitialAdvertisedReceiveWindow = 30;
+            };
+          }
+        ];
+        cakeConfig = {
+          Bandwidth = "1M";
+          OverheadBytes = 18;
+          MPUBytes = 64;
+          CompensationMode = "none";
+          NAT = true;
+          PriorityQueueingPreset = "diffserv8";
+        };
+      };
+      "50-wan" = {
+        matchConfig.Name = "wan0";
+        linkConfig.RequiredForOnline = "no";
+        networkConfig = {
+          DHCP = "yes";
+          IPv6AcceptRA = true;
+          IPForward = "yes";
+        };
+        dhcpV4Config = {
+          UseDNS = false;
+          SendHostname = false;
+          SendRelease = false;
+          UseHostname = false;
+          # Label = "wan0:1";
+        };
+        dhcpV6Config = {
+          UseDNS = false;
+          RapidCommit = true;
+          PrefixDelegationHint = "::/56";
+        };
+        dhcpPrefixDelegationConfig = {
+          UplinkInterface = ":self";
+        };
+        ipv6AcceptRAConfig = {
+          UseDNS = false;
+        };
+        addresses = [
+          {
+            addressConfig = {
+              Address = "192.168.100.10/24";
+              # Peer = "192.168.100.1/32";
+              Label = "wan0:0";
+              # Scope = "link";
+            };
+          }
+        ];
+        cakeConfig = {
+          Bandwidth = "24M";
+          OverheadBytes = 18;
+          MPUBytes = 64;
+          CompensationMode = "none";
+          NAT = true;
+          PriorityQueueingPreset = "diffserv8";
+        };
+      };
+      "60-wlan" = {
+        matchConfig.MACAddress = "9c:53:22:33:bf:e9";
+        linkConfig.RequiredForOnline = "no";
+        networkConfig = {
+          DHCP = "yes";
+          IPForward = "yes";
+          IgnoreCarrierLoss = "3s";
+        };
+        dhcpV4Config = {
+          UseDNS = false;
+          SendHostname = false;
+          SendRelease = true;
+          UseHostname = false;
+          RouteMetric = 2048;
+        };
+        routes = [
+          {
+            routeConfig = {
+              Metric = 2048;
+              Gateway = "_dhcp4";
+              QuickAck = true;
+              InitialCongestionWindow = 30;
+              InitialAdvertisedReceiveWindow = 30;
+            };
+          }
+        ];
+        cakeConfig = {
+          Bandwidth = "1M";
+          OverheadBytes = 18;
+          MPUBytes = 64;
+          CompensationMode = "none";
+          NAT = true;
+          PriorityQueueingPreset = "diffserv8";
+        };
+      };
+    };
+  };
+  boot.kernelModules = [
+    "tcp_lp"
+  ];
+  boot.kernel.sysctl = {
+    "net.ipv4.conf.bridge0.send_redirects" = 1;
+    "net.ipv4.conf.bridge0.accept_source_route" = 1;
+    "net.ipv4.tcp_slow_start_after_idle" = 0;
+    "net.ipv4.tcp_ecn" = 1;
+    "net.ipv4.tcp_fastopen" = "0x3";
+    "net.ipv4.tcp_allowed_congestion_control" = "reno cubic lp";
+    "net.core.default_qdisc" = "fq";
+  };
+
+  services.dnsmasq = {
+    enable = true;
+    resolveLocalQueries = true;
+    alwaysKeepRunning = true;
+    settings = {
+      local-ttl = 60;
+      domain = "lan";
+      dhcp-fqdn = false;
+      domain-needed = true;
+      bogus-priv = true;
+      no-resolv = true;
+      no-negcache = true;
+      strict-order = true;
+      log-queries = false;
+      server = [
+        "9.9.9.9"
+        "149.112.112.112"
+        "2620::fe:fe"
+        "2620::fe:9"
+        "116.203.248.56"
+        "2a01:4f8:c012:23a4::1"
+        # "127.0.0.1#5553"
+        # "::1#5553"
+        "127.0.0.1#5533"
+        "::1#5533"
+      ];
+      localise-queries = true;
+      cname = [
+        "homeassistant,ha"
+      ];
+      interface-name = [
+        "home.alanpearce.eu,wan0"
+        "nanopi.alanpearce.eu,wan0"
+        "nanopi.lan.alanpearce.eu,bridge0"
+        "syncthing.lan.alanpearce.eu,bridge0"
+        "wan,wan0"
+        "wlan,wlan0"
+        "wwan,wwan0"
+      ];
+      interface = [
+        "bridge0"
+      ];
+      # auth-zone = "lan,wan0";
+      # auth-server = [
+      #   "nanopi.alanpearce.eu,wan0"
+      # ];
+      bind-interfaces = false;
+
+      no-hosts = true;
+
+      enable-ra = true;
+      dhcp-lease-max = 240;
+      dhcp-authoritative = true;
+      dhcp-rapid-commit = true;
+      dhcp-range = [
+        "10.0.1.0,10.0.1.250,12h"
+        "::, constructor:bridge0, ra-stateless, 48h"
+        "fd12:d04f:65d::, ra-stateless, ra-names, 48h"
+      ];
+      dhcp-host = [
+        "00:a0:de:b3:0c:01,10.0.0.50,wxa-50"
+        "10:f0:68:12:b1:e0,10.0.0.11,Ruckus"
+        "9c:93:4e:ad:05:c8,10.0.0.210,xerox-b210"
+        "00:08:9b:f5:b8:25,10.0.0.42,dontpanic"
+        "d8:3a:dd:34:85:cc,d8:3a:dd:34:85:cd,10.0.0.81,ha"
+      ];
+      dhcp-option = [
+        "option:ntp-server,0.0.0.0"
+        "option:dns-server,0.0.0.0,10.0.0.81"
+        "option:tftp-server,0.0.0.0"
+        "option:ip-forward-enable,0" # ip-forwarding
+        "252,\"\\n\""
+      ];
+      dhcp-name-match = "set:wpad-ignore,wpad";
+      dhcp-ignore-names = "tag:wpad-ignore";
+
+      tftp-root = "/srv/tftp/";
+      dhcp-boot = [
+        "tag:bios,netboot.xyz.kpxe"
+        "tag:efi32,netboot.xyz.efi"
+        "tag:efi32-1,netboot.xyz.efi"
+        "tag:efi64,netboot.xyz.efi"
+        "tag:efi64-1,netboot.xyz.efi"
+        "tag:efi64-2,netboot.xyz.efi"
+      ];
+      dhcp-match = [
+        "set:bios,60,PXEClient:Arch:00000"
+        "set:efi32,60,PXEClient:Arch:00002"
+        "set:efi32-1,60,PXEClient:Arch:00006"
+        "set:efi64,60,PXEClient:Arch:00007"
+        "set:efi64-1,60,PXEClient:Arch:00008"
+        "set:efi64-2,60,PXEClient:Arch:00009"
+      ];
+    };
+  };
+  systemd.services.dnsmasq.wants = [ "network-online.target" ];
+
+  services.networkd-dispatcher = {
+    enable = true;
+    rules = {
+      update-home-address = {
+        onState = [ "configured" "configuring" ];
+        script = ''
+          #!${pkgs.runtimeShell}
+          set -eu
+
+          if [[ $IFACE == "wan0" && $OperationalState == "routable" ]]
+          then
+            systemctl start dynamic-dns-update.service
+          fi
+          exit 0
+        '';
+      };
+    };
+  };
+
+  system.stateVersion = "23.05";
+
+  programs.fish = {
+    enable = true;
+  };
+  programs.neovim = {
+    enable = true;
+    defaultEditor = true;
+    vimAlias = true;
+    viAlias = true;
+  };
+
+  users.users.root.shell = "${pkgs.fish}/bin/fish";
+
+  users.users.alan = {
+    description = "Alan Pearce";
+    isNormalUser = true;
+    extraGroups = [ "wheel" "lp" "scanner" "dialout" ];
+    shell = "${pkgs.fish}/bin/fish";
+    home = "/home/alan";
+    uid = 1000;
+    openssh.authorizedKeys.keys = [
+      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMvcW4Z9VxOQgEJjsRC1uSMwEJ4vru9BwjT+Z50nawp4 lan"
+    ];
+  };
+
+  users.groups = {
+    linde.members = [ ];
+  };
+  users.users = {
+    linde = {
+      group = "linde";
+      description = "Backup user for system 'linde'";
+      isSystemUser = true;
+      shell = "/bin/sh";
+      home = "/srv/backup/linde";
+      createHome = true;
+      packages = with pkgs; [ rdiff-backup ];
+      openssh.authorizedKeys.keys = [
+        "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ74cPdIX9OlDkzHb6Y1E5sWqtIqMaf0z/SN3Tfy1Fjl root@linde"
+        "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINNXwIdGcP1vKyjmgeLw/sJntn7lajaZivepgdzaXvOt rdiff-backup"
+      ];
+    };
+  };
+
+  nix = {
+    distributedBuilds = true;
+    buildMachines = [
+      {
+        protocol = "ssh-ng";
+        sshUser = "nixremote";
+        hostName = "linde.alanpearce.eu";
+        system = "aarch64-linux";
+        sshKey = "/root/.ssh/id_buche.alanpearce.eu_nixremote";
+        maxJobs = 2;
+        speedFactor = 4;
+        supportedFeatures = [ ];
+      }
+    ];
+    settings = {
+      builders-use-substitutes = true;
+      max-jobs = 2;
+      auto-optimise-store = true;
+      experimental-features = [ "nix-command" "flakes" ];
+      substituters = [ "https://binarycache.alanpearce.eu" ];
+      trusted-public-keys = [
+        "mba-1:CxokFjx7YAQWPWMJJKcP50ZpcPUCAFEOrtWdNUMTVjw="
+        "binarycache.alanpearce.eu:ZwqO3XMuajPictjwih8OY2+RXnOKpjZEZFHJjGSxAI4="
+      ];
+    };
+    daemonCPUSchedPolicy = "batch";
+    daemonIOSchedPriority = 6;
+    gc = {
+      automatic = true;
+      dates = "weekly";
+      options = "--delete-older-than 30d";
+    };
+    optimise = {
+      automatic = true;
+      dates = [ "04:00" ];
+    };
+  };
+  nixpkgs.config.allowUnfree = true;
+  nixpkgs.overlays = [ ];
+  system.autoUpgrade = {
+    enable = false;
+    dates = "01:00";
+    randomizedDelaySec = "59 min";
+    channel = "https://nixos.org/channels/nixos-unstable-small";
+    allowReboot = true;
+    rebootWindow = {
+      lower = "01:00";
+      upper = "05:00";
+    };
+  };
+
+  services.miniupnpd = {
+    enable = false;
+    natpmp = true;
+    internalIPs = [ "bridge0" ];
+    externalInterface = "wan0";
+  };
+
+  users.groups.videos = {
+    members = [ "alan" "jellyfin" ];
+  };
+  services.jellyfin = {
+    enable = true;
+  };
+
+  users.users.syncthing = {
+    isSystemUser = true;
+    group = "syncthing";
+    homeMode = "0755";
+  };
+  users.groups.syncthing.members = [ "alan" ];
+  services.syncthing = {
+    enable = true;
+    openDefaultPorts = true;
+    dataDir = "/srv/syncthing";
+    user = "syncthing";
+    group = "syncthing";
+    key = config.age.secrets.syncthing.path;
+    cert = toString (pkgs.writeText "syncthing.crt" ''
+      -----BEGIN CERTIFICATE-----
+      MIIBmjCCASCgAwIBAgIIUOEmXGFrrX0wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ
+      c3luY3RoaW5nMB4XDTIyMDcxMzEwMzIxOVoXDTQ5MTIzMTIzNTk1OVowFDESMBAG
+      A1UEAxMJc3luY3RoaW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEPiJT41NqucQf
+      UXiBwt+yPYnMg9G8oTt9XNA72V99K46D7mIs1F/5oESlDiCSAngXPsajxRY7wyZV
+      VoiWegfiaBOGZmq+TyaLlQ5bq/hm/Mp/jVED/rUA+BggohoZZMa2oz8wPTAOBgNV
+      HQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1Ud
+      EwEB/wQCMAAwCgYIKoZIzj0EAwIDaAAwZQIwLp4Gv5EEmjRO9EphbYJ4jxEJks7E
+      oblgnTmhfWmVWmf9avJyeGB212VYu4X8cCKDAjEAn7tTB9Y6LZvYPaLSwUKY3EzF
+      hKTYCb7VA/P1dU3tTR1vSQxnu1DsiliD/XcKe2IK
+      -----END CERTIFICATE-----
+    '');
+    settings = {
+      options = {
+        maxRecvKbps = 10240;
+        maxSendKbps = 1024;
+        globalAnnounceEnabled = false;
+        relaysEnabled = false;
+        natEnabled = false;
+        urAccepted = 4;
+        trafficClass = 1;
+      };
+      overrideFolders = false;
+      overrideDevices = false;
+    };
+  };
+
+  time.timeZone = "Europe/Berlin";
+
+  services.chrony = {
+    enable = true;
+    extraConfig = ''
+      rtcdevice /dev/rtc0
+      rtcfile /var/lib/chrony/rtc
+      rtcautotrim 30
+
+      allow 10.0.0.0/8
+      allow fd12:d04f:65d:42::0/56
+    '';
+  };
+
+  services.avahi = {
+    enable = true;
+    nssmdns4 = true;
+    denyInterfaces = [ "wan0" "wwan0" "wlan0" ];
+    browseDomains = [
+      "alanpearce.eu"
+    ];
+    publish = {
+      enable = true;
+      hinfo = true;
+      addresses = true;
+      userServices = true;
+      workstation = true;
+    };
+  };
+
+  services.samba = {
+    enable = true;
+    enableNmbd = false;
+    extraConfig = ''
+      log level = 1
+
+      interfaces = bridge0
+
+      min protocol = SMB2
+      disable netbios = yes
+      smb ports = 445
+
+      socket options = IPTOS_LOWDELAY TCP_NODELAY SO_KEEPALIVE SO_RCVBUF=65536 SO_SNDBUF=65536
+      max xmit = 131072
+      min receivefile size = 131072
+
+      aio read size = 1
+      aio write size = 1
+
+      load printers = no
+      disable spoolss = yes
+
+      mdns name = mdns
+
+      follow symlinks = yes
+
+      veto files = /Thumbs.db/.DS_Store/._.DS_Store/.apdisk/
+      delete veto files = yes
+    '';
+    shares = {
+      public = {
+        path = "/srv/public";
+        browseable = "yes";
+        "guest ok" = "yes";
+        "create mask" = "0666";
+        "directory mask" = "0777";
+        "read only" = "no";
+      };
+      Homes = {
+        "read only" = "no";
+        "valid users" = "%S";
+        "inherit acls" = "yes";
+      };
+      Videos = {
+        path = "/srv/videos";
+        "valid users" = "alan";
+        "create mask" = "0664";
+        "directory mask" = "0775";
+        "writeable" = "yes";
+      };
+    };
+  };
+  services.samba-wsdd = {
+    enable = true;
+    interface = "bridge0";
+  };
+
+  security.acme = {
+    acceptTerms = true;
+    defaults.email = "tls@alanpearce.eu";
+    certs."dns.alanpearce.eu" = {
+      reloadServices = map (x: "kresd@${toString x}") (lib.range 1 config.services.kresd.instances);
+      dnsProvider = "pdns";
+      dnsResolver = "1.1.1.1:53";
+      credentialsFile = config.age.secrets.acme.path;
+      group = "knot-resolver";
+    };
+  };
+
+  services.smartdns = {
+    enable = false;
+    bindPort = "5533";
+    settings = {
+      bind = "[::]:5533";
+      address = [
+        "/use-application-dns.net/#"
+      ];
+      server = [
+        "[::1]:5553"
+        "10.0.0.1:53 -group lan -exclude-default-group"
+      ];
+      nameserver = [
+        "/lan/lan"
+      ];
+      dualstack-ip-selection = true;
+      dualstack-ip-selection-threshold = 10;
+      dualstack-ip-allow-force-AAAA = false;
+      dnsmasq-lease-file = "/var/lib/dnsmasq/dnsmasq.leases";
+      mdns-lookup = true;
+    };
+  };
+
+  services.kresd = {
+    enable = true;
+    instances = 4;
+    listenPlain = [ "[::1]:5553" ];
+    # listenTLS = [ "853" ];
+    listenDoH = [ "[::1]:5443" ];
+    extraConfig = ''
+      net.tls(
+        '/var/lib/acme/dns.alanpearce.eu/cert.pem',
+        '/var/lib/acme/dns.alanpearce.eu/key.pem'
+      )
+
+      -- Load useful modules
+      modules = {
+        'serve_stale < cache',
+        'workarounds < iterate',
+        'hints > iterate',
+        'nsid',
+      }
+
+      local systemd_instance = os.getenv("SYSTEMD_INSTANCE")
+      nsid.name(systemd_instance)
+
+      -- Cache size
+      cache.size = 500 * MB
+
+      local internalDomains = policy.todnames({'lan.alanpearce.eu.', '10.in-addr.arpa.', '.172.in-addr.arpa.', '.168.192.in-addr.arpa.'})
+      policy.add(policy.suffix(policy.FLAGS({'NO_CACHE'}), internalDomains))
+      policy.add(policy.suffix(policy.STUB({'10.0.0.1'}), internalDomains))
+
+      -- disable duplicate DNSSEC validation when using Quad9 or private
+      trust_anchors.remove('.')
+
+      -- policy.add(policy.all(policy.TLS_FORWARD({
+      --   { "23.88.111.219", hostname="dns.alanpearce.eu" },
+      --   { "2a01:4f8:c0c:d9ce::1", hostname="dns.alanpearce.eu" },
+      -- })))
+
+      policy.add(policy.all(policy.TLS_FORWARD({
+        {'9.9.9.11', hostname='dns11.quad9.net'},
+        {'149.112.122.11', hostname='dns11.quad9.net'},
+        {'2620:fe::11', hostname='dns11.quad9.net'},
+        {'2620:fe::fe:11', hostname='dns11.quad9.net'}
+      })))
+
+      -- policy.add(policy.rpz(
+      -- 	policy.DENY_MSG('domain blocked by hblock'),
+      -- 	'/etc/knot-resolver/blocklist.rpz',
+      -- 	true
+      -- ))
+    '';
+  };
+}