Change AdGuard Home DNS listener to bind to 127.0.0.1:5353 to avoid conflicting with existing services on port 53, since we only expose DoH via Nginx.
158 lines
No EOL
3.9 KiB
Nix
158 lines
No EOL
3.9 KiB
Nix
# AdGuard Home Module
|
|
# Provides: Private DNS-over-HTTPS with ClientID-based access control
|
|
#
|
|
# Usage:
|
|
# myModules.adguard = {
|
|
# enable = true;
|
|
# domain = "dns.example.com";
|
|
# clients = [
|
|
# { name = "phone"; idSecret = "adguard_client_phone"; }
|
|
# ];
|
|
# };
|
|
|
|
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
let
|
|
cfg = config.myModules.adguard;
|
|
in
|
|
{
|
|
options.myModules.adguard = {
|
|
enable = lib.mkEnableOption "AdGuard Home DNS server";
|
|
|
|
domain = lib.mkOption {
|
|
type = lib.types.str;
|
|
example = "dns.example.com";
|
|
description = "Public domain name for DoH endpoint";
|
|
};
|
|
|
|
port = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 3000;
|
|
description = "Internal port for AdGuard HTTP/DoH listener";
|
|
};
|
|
|
|
upstreamDoh = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "https://dns.mullvad.net/dns-query";
|
|
description = "Upstream DoH server URL";
|
|
};
|
|
|
|
bootstrapDns = lib.mkOption {
|
|
type = lib.types.listOf (lib.types.str);
|
|
default = [ "194.242.2.2" "2a07:e340::2" ];
|
|
description = "Bootstrap DNS servers for resolving DoH upstream";
|
|
};
|
|
|
|
clients = lib.mkOption {
|
|
type = lib.types.listOf (lib.types.submodule {
|
|
options = {
|
|
name = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Friendly name for client device";
|
|
};
|
|
idSecret = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "SOPS secret name containing the ClientID";
|
|
};
|
|
};
|
|
});
|
|
default = [ ];
|
|
description = "List of clients with their ClientID secrets";
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
services.adguardhome = {
|
|
enable = true;
|
|
host = "127.0.0.1";
|
|
port = cfg.port;
|
|
settings = {
|
|
dns = {
|
|
bind_hosts = [ "127.0.0.1" ];
|
|
port = 5353;
|
|
upstream_dns = [ cfg.upstreamDoh ];
|
|
bootstrap_dns = cfg.bootstrapDns;
|
|
querylog_enabled = true;
|
|
querylog_file_enabled = true;
|
|
statistics_enabled = true;
|
|
};
|
|
|
|
filtering = {
|
|
protection_enabled = true;
|
|
filtering_enabled = true;
|
|
};
|
|
|
|
safebrowsing = {
|
|
enabled = false;
|
|
};
|
|
|
|
parental = {
|
|
enabled = false;
|
|
};
|
|
|
|
safesearch = {
|
|
enabled = false;
|
|
};
|
|
|
|
log = {
|
|
file = "";
|
|
max_backups = 0;
|
|
max_size = 100;
|
|
compress = false;
|
|
local_time = false;
|
|
verbose = false;
|
|
};
|
|
} // lib.optionalAttrs (lib.length cfg.clients == 0) {
|
|
clients = {
|
|
persistent = [ ];
|
|
};
|
|
};
|
|
};
|
|
|
|
# Auto-declare SOPS secrets for each client
|
|
sops.secrets = lib.mkMerge (
|
|
map (client: {
|
|
${client.idSecret} = { };
|
|
}) cfg.clients
|
|
);
|
|
|
|
# Nginx configuration for DoH endpoint
|
|
services.nginx.virtualHosts."${cfg.domain}" = {
|
|
enableACME = true;
|
|
forceSSL = true;
|
|
|
|
# Regex location to match /dns-query and /dns-query/{clientId}
|
|
locations."~ ^/dns-query" = {
|
|
proxyPass = "http://127.0.0.1:${toString cfg.port}";
|
|
extraConfig = ''
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
# DoH uses POST with application/dns-message
|
|
proxy_pass_request_body on;
|
|
proxy_set_header Content-Type "application/dns-message";
|
|
|
|
# Buffer settings for DNS queries
|
|
proxy_buffers 8 16k;
|
|
proxy_buffer_size 32k;
|
|
'';
|
|
};
|
|
|
|
# Block all other paths including admin UI
|
|
locations."/" = {
|
|
return = "404";
|
|
};
|
|
};
|
|
|
|
# Ensure nginx user can access ACME certs
|
|
users.users.nginx.extraGroups = [ "acme" ];
|
|
};
|
|
} |