feat(modules): add AdGuard Home module with DoH and ClientID support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
30d5ce8134
commit
1ed9acdcda
1 changed files with 177 additions and 0 deletions
177
modules/adguard.nix
Normal file
177
modules/adguard.nix
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
# 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 = [
|
||||
"--cap-drop=ALL"
|
||||
"--read-only"
|
||||
"--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
|
||||
|
||||
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" ];
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue