Last active
October 19, 2024 04:52
-
-
Save dwoffinden/7ffb28874989932b584d1a5a0075f489 to your computer and use it in GitHub Desktop.
Ad-blocking DNS-over-TLS/HTTPS server w/ NixOS & Knot Resolver. Pi-hole alternative.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright 2020 Google LLC. | |
# SPDX-License-Identifier: Apache-2.0 | |
{ config, pkgs, ... }: | |
let | |
# https://github.com/StevenBlack/hosts/issues/451 | |
# https://github.com/ScriptTiger/Hosts-Conversions | |
# https://github.com/ScriptTiger/scripttiger.github.io | |
# https://scripttiger.github.io/alts/ | |
adblockLocalZones = pkgs.stdenv.mkDerivation { | |
name = "adblock-rpz"; | |
src = (pkgs.fetchFromGitHub { | |
owner = "ScriptTiger"; | |
repo = "scripttiger.github.io"; | |
# nix-prefetch-github ScriptTiger scripttiger.github.io | |
rev = "04402a6726f97c5f0d30436a70ac1344dccb7cf1"; | |
sha256 = "iSTR7j7QEr5xYtImyntDlVLbnN2ipcLcTRr4sfdx078="; | |
} + "/alts/rpz/blacklist.txt"); | |
phases = [ "installPhase" ]; | |
installPhase = "install -m 444 -D $src $out"; | |
}; | |
# TODO: config.networking.domain? | |
domain = "FILL ME IN"; | |
certdir = config.security.acme.certs.${domain}.directory; | |
in { | |
networking.firewall.allowedTCPPorts = [ 80 443 853 ]; | |
# TODO: ACME fails before the local resolver is up :/ | |
networking.resolvconf.useLocalResolver = false; | |
services.nginx = { | |
enable = true; | |
virtualHosts.${domain} = { | |
enableACME = true; | |
locations."/" = { return = "204"; }; | |
}; | |
}; | |
security.acme.acceptTerms = true; | |
security.acme.email = "FILL ME IN"; | |
# for acme certs, TODO: make a proper group | |
users.users.knot-resolver.extraGroups = [ "nginx" ]; | |
# https://github.com/NixOS/nixpkgs/issues/81109 | |
nixpkgs.config.packageOverrides = pkgs: rec { | |
knot-resolver = pkgs.knot-resolver.override { extraFeatures = true; }; | |
}; | |
services.kresd = { | |
enable = true; | |
# Plain DNS only from localhost. | |
# You might want to add a LAN or VPN subnet, depending on deployment. | |
listenPlain = [ "127.0.0.1:53" "[::1]:53" ]; | |
listenTLS = [ "0.0.0.0:853" "[::]:853" ]; | |
listenDoH = [ "0.0.0.0:443" "[::]:443" ]; | |
instances = 1; | |
# TODO: chain.pem for stapling: https://gitlab.nic.cz/knot/knot-resolver/-/issues/517 | |
extraConfig = '' | |
policy.add( | |
policy.rpz( | |
policy.DENY_MSG('domain blocked by your resolver operator'), | |
'${adblockLocalZones}', | |
false)) | |
policy.add( | |
policy.all( | |
policy.TLS_FORWARD({ | |
{'8.8.8.8', hostname='dns.google'}, | |
{'8.8.4.4', hostname='dns.google'}}))) | |
net.tls("${certdir}/fullchain.pem", "${certdir}/key.pem") | |
-- refresh certs when ACME updates them | |
-- TODO: could probably do with systemd and a control socket? | |
-- TODO: may fail on first boot if ACME isn't fast, add a systemd dep | |
local notify = require('cqueues.notify') | |
local watcher = notify.opendir('${certdir}') | |
watcher:add('fullchain.pem') | |
worker.coroutine(function () | |
for flags, name in watcher:changes() do | |
for flag in notify.flags(flags) do | |
print('file watcher:', name, notify[flag]) | |
end | |
if name == 'fullchain.pem' then | |
net.tls("${certdir}/fullchain.pem", "${certdir}/key.pem") | |
end | |
end | |
end) | |
-- Below we create a tmpfs FS for the cache -- use almost all of it: | |
cache.size = cache.fssize() - 1*MB | |
''; | |
fileSystems."/var/cache/knot-resolver" = { | |
device = "tmpfs"; fsType = "tmpfs"; | |
options = [ "defaults" "size=512M" ]; | |
}; | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment