# services/adguard.nix — AdGuard Home network-wide DNS ad blocker { config, lib, pkgs, ... }: let # AdGuard Home uses Type=simple, so systemd considers it started before its # DNS listeners are necessarily accepting queries. Network-dependent units # ordered after adguardhome.service need a stronger readiness boundary. waitForDns = pkgs.writeShellScript "wait-for-adguard-dns" '' attempt=0 while [ "$attempt" -lt 30 ]; do # dnsmasq may answer on loopback while AdGuard is still starting, so # probe AdGuard's exclusive LAN listener instead. if answer="$(${pkgs.dnsutils}/bin/dig +short +time=1 +tries=1 @10.0.0.1 example.com A 2>/dev/null)"; then if [ -n "$answer" ]; then exit 0 fi fi attempt=$((attempt + 1)) ${pkgs.coreutils}/bin/sleep 1 done echo "AdGuard Home did not provide working upstream DNS within 30 seconds" >&2 exit 1 ''; in { config = lib.mkIf (config.networking.hostName == "FredOS-Mediaserver") { services.adguardhome = { enable = true; # Web UI bound to localhost; nginx reverse-proxies at adguard.nordhammer.it host = "127.0.0.1"; port = 3000; # Nix is authoritative: settings below overwrite UI-made changes on rebuild mutableSettings = false; settings = { dns = { bind_hosts = [ "10.0.0.1" "127.0.0.1" ]; port = 53; # Query all upstreams in parallel; take the fastest response upstream_mode = "parallel"; # Mix of DoH (encrypted) and plain UDP (low-latency) upstreams upstream_dns = [ "https://dns.cloudflare.com/dns-query" "https://dns.quad9.net/dns-query" "1.1.1.1" "9.9.9.9" ]; bootstrap_dns = [ "1.1.1.1" "9.9.9.9" ]; cache_size = 4194304; cache_ttl_min = 60; }; filters = [ { enabled = true; id = 1; name = "AdGuard DNS filter"; url = "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt"; } { enabled = true; id = 2; name = "AdAway Default Blocklist"; url = "https://adaway.org/hosts.txt"; } { enabled = true; id = 3; name = "OISD Big"; url = "https://big.oisd.nl/"; } ]; # Resolve hosted service subdomains directly to the router. Keep the # bare nordhammer.it name public: Valheim/Steam identifies the server # by that public endpoint, and router.nix reflects only the Pelican # game-port range back into the LAN. Rewriting the bare name here made # gameplay work but left the in-game browser showing a red status icon. # `enabled` was added in AdGuard's recent schema and defaults to false. filtering.rewrites = [ { domain = "*.nordhammer.it"; answer = "10.0.0.1"; enabled = true; } ]; }; }; # Delay completion of adguardhome.service startup until DNS is genuinely # usable, rather than merely until the AdGuard process has been spawned. systemd.services.adguardhome.serviceConfig.ExecStartPost = [ waitForDns ]; # LAN clients reach :53 via the nftables "LAN trusted" rule in router.nix; # WAN:53 is dropped there. }; }