aboutsummaryrefslogtreecommitdiff
path: root/modules/adguard.nix
blob: b3a7ee29344779a443dac0117709f711aef2a482 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
{ config, pkgs, lib, ... }:

# AdGuard Home - DNS resolver + optional network-wide ad blocker.
#
# DNS listens on port 53 (udp/tcp) directly on the public IP.
# The web UI listens on 127.0.0.1:3000 and is fronted by Caddy.
#
# Admin password is seeded from a sops secret containing a bcrypt hash.
# To generate the hash on your local machine:
#   htpasswd -nB admin
# Copy the hash portion (everything after "admin:") into secrets.yaml.
{
  services.adguardhome = {
    enable = true;
    mutableSettings = false; # declarative mode - config comes from Nix only

    settings = {
      http = {
        address = "127.0.0.1:3000";
      };

      dns = {
        bind_hosts = [ "0.0.0.0" ];
        port = 53;
        # Upstream DNS resolvers (privacy-respecting)
        upstream_dns = [
          "https://dns.quad9.net/dns-query"
          "https://cloudflare-dns.com/dns-query"
        ];
        bootstrap_dns = [
          "9.9.9.9"
          "1.1.1.1"
        ];
        enable_dnssec = true;
      };

      # Users block: username "admin", password from sops secret.
      # The ExecStartPre below writes the hash into the config on every
      # service start, because mutableSettings=false uses a static config
      # file but the password hash must be injected at runtime (it contains
      # a secret).
      users = [
        {
          name = "admin";
          # Placeholder - replaced at service start by the ExecStartPre below
          password = "REPLACED_AT_ACTIVATION";
        }
      ];

      # Basic filtering
      filtering = {
        enabled = true;
        update_interval = 24;
      };

      # Block lists
      filters = [
        {
          enabled = true;
          url = "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt";
          name = "AdGuard DNS filter";
          id = 1;
        }
        {
          enabled = true;
          url = "https://adaway.org/hosts.txt";
          name = "AdAway Default Blocklist";
          id = 2;
        }
      ];
    };
  };

  # AdGuardHome needs a static system user rather than the module's default
  # DynamicUser=true: sops-nix chowns the "adguard/password_hash" secret to
  # this user *during activation*, which happens while the service is
  # stopped (for a restart) - a DynamicUser only exists while its service is
  # actually running, so that chown would fail to resolve the user otherwise.
  users.users.adguardhome = {
    isSystemUser = true;
    group = "adguardhome";
  };
  users.groups.adguardhome = { };

  # mutableSettings = false makes the module's own ExecStartPre unconditionally
  # `cp --force` the store-generated config (with the literal placeholder)
  # over $STATE_DIRECTORY/AdGuardHome.yaml on every service start. An
  # activation script can't win that race, so inject the real hash as a
  # second ExecStartPre, ordered after the module's via mkAfter, so it always
  # runs right before ExecStart.
  #
  # Plain bash substring substitution + `>` redirect is used instead of
  # `sed -i`: sed -i creates a temp file and chowns/renames it in place, and
  # this unit's hardened SystemCallFilter (~@privileged) blocks the chown
  # syscall, killing sed with SIGSYS ("Bad system call").
  systemd.services.adguardhome.serviceConfig = {
    DynamicUser = lib.mkForce false;
    User = "adguardhome";
    Group = "adguardhome";
    ExecStartPre = lib.mkAfter [
      "${pkgs.writeShellScript "adguard-inject-password" ''
        set -eu
        CFG="$STATE_DIRECTORY/AdGuardHome.yaml"
        HASH=$(cat "${config.sops.secrets."adguard/password_hash".path}")
        CONTENT=$(cat "$CFG")
        printf '%s\n' "''${CONTENT//REPLACED_AT_ACTIVATION/$HASH}" > "$CFG"
      ''}"
    ];
  };
}