nixos-vps/modules/adguard.nix
ashisgreat22 294b556542 fix(adguard): handle empty clients list
Only render clients section when clients are configured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 19:22:58 +01:00

176 lines
4.6 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 {
# Ensure Podman is enabled
myModules.podman.enable = true;
# AdGuard Home Container
virtualisation.oci-containers.containers."adguard" = {
image = "docker.io/adguard/adguardhome:latest";
ports = [ "127.0.0.1:${toString cfg.port}:3000/tcp" ];
extraOptions = [
"--tmpfs=/tmp"
];
volumes = [
"${config.sops.templates."adguardhome.yaml".path}:/opt/adguardhome/conf/AdGuardHome.yaml:ro"
"adguard-data:/opt/adguardhome/work"
];
};
# SOPS template for AdGuard configuration
sops.templates."adguardhome.yaml" = {
content = ''
http:
address: 0.0.0.0:3000
dns:
upstream_dns:
- ${cfg.upstreamDoh}
bootstrap_dns:
${lib.concatStringsSep "\n " (map (d: "- ${d}") cfg.bootstrapDns)}
querylog_enabled: true
querylog_file_enabled: true
statistics_enabled: true
${lib.optionalString (cfg.clients != []) ''
clients:
persistent:
${lib.concatStringsSep "\n " (
map (client: ''
- name: ${client.name}
ids:
- ${config.sops.placeholder.${client.idSecret}}
'') cfg.clients
)}
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
'';
};
# 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" ];
};
}