python312Packages.aiohomeconnect: 0.10.0 -> 0.11.0 (#374011)
[NixPkgs.git] / nixos / modules / services / networking / adguardhome.nix
blobfc23d2af0b22dd87aa86ce09262df85aba786fc1
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
7 let
8   cfg = config.services.adguardhome;
9   settingsFormat = pkgs.formats.yaml { };
11   args = lib.concatStringsSep " " (
12     [
13       "--no-check-update"
14       "--pidfile /run/AdGuardHome/AdGuardHome.pid"
15       "--work-dir /var/lib/AdGuardHome/"
16       "--config /var/lib/AdGuardHome/AdGuardHome.yaml"
17     ]
18     ++ cfg.extraArgs
19   );
21   settings =
22     if (cfg.settings != null) then
23       cfg.settings
24       // (
25         if cfg.settings.schema_version < 23 then
26           {
27             bind_host = cfg.host;
28             bind_port = cfg.port;
29           }
30         else
31           {
32             http.address = "${cfg.host}:${toString cfg.port}";
33           }
34       )
35     else
36       null;
38   configFile = (settingsFormat.generate "AdGuardHome.yaml" settings).overrideAttrs (_: {
39     checkPhase = "${cfg.package}/bin/adguardhome -c $out --check-config";
40   });
43   options.services.adguardhome = with lib.types; {
44     enable = lib.mkEnableOption "AdGuard Home network-wide ad blocker";
46     package = lib.mkOption {
47       type = package;
48       default = pkgs.adguardhome;
49       defaultText = lib.literalExpression "pkgs.adguardhome";
50       description = ''
51         The package that runs adguardhome.
52       '';
53     };
55     openFirewall = lib.mkOption {
56       default = false;
57       type = bool;
58       description = ''
59         Open ports in the firewall for the AdGuard Home web interface. Does not
60         open the port needed to access the DNS resolver.
61       '';
62     };
64     allowDHCP = lib.mkOption {
65       default = settings.dhcp.enabled or false;
66       defaultText = lib.literalExpression "config.services.adguardhome.settings.dhcp.enabled or false";
67       type = bool;
68       description = ''
69         Allows AdGuard Home to open raw sockets (`CAP_NET_RAW`), which is
70         required for the integrated DHCP server.
72         The default enables this conditionally if the declarative configuration
73         enables the integrated DHCP server. Manually setting this option is only
74         required for non-declarative setups.
75       '';
76     };
78     mutableSettings = lib.mkOption {
79       default = true;
80       type = bool;
81       description = ''
82         Allow changes made on the AdGuard Home web interface to persist between
83         service restarts.
84       '';
85     };
87     host = lib.mkOption {
88       default = "0.0.0.0";
89       type = str;
90       description = ''
91         Host address to bind HTTP server to.
92       '';
93     };
95     port = lib.mkOption {
96       default = 3000;
97       type = port;
98       description = ''
99         Port to serve HTTP pages on.
100       '';
101     };
103     settings = lib.mkOption {
104       default = null;
105       type = nullOr (submodule {
106         freeformType = settingsFormat.type;
107         options = {
108           schema_version = lib.mkOption {
109             default = cfg.package.schema_version;
110             defaultText = lib.literalExpression "cfg.package.schema_version";
111             type = int;
112             description = ''
113               Schema version for the configuration.
114               Defaults to the `schema_version` supplied by `cfg.package`.
115             '';
116           };
117         };
118       });
119       description = ''
120         AdGuard Home configuration. Refer to
121         <https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#configuration-file>
122         for details on supported values.
124         ::: {.note}
125         On start and if {option}`mutableSettings` is `true`,
126         these options are merged into the configuration file on start, taking
127         precedence over configuration changes made on the web interface.
129         Set this to `null` (default) for a non-declarative configuration without any
130         Nix-supplied values.
131         Declarative configurations are supplied with a default `schema_version`, and `http.address`.
132         :::
133       '';
134     };
136     extraArgs = lib.mkOption {
137       default = [ ];
138       type = listOf str;
139       description = ''
140         Extra command line parameters to be passed to the adguardhome binary.
141       '';
142     };
143   };
145   config = lib.mkIf cfg.enable {
146     assertions = [
147       {
148         assertion = cfg.settings != null -> !(lib.hasAttrByPath [ "bind_host" ] cfg.settings);
149         message = "AdGuard option `settings.bind_host' has been superseded by `services.adguardhome.host'";
150       }
151       {
152         assertion = cfg.settings != null -> !(lib.hasAttrByPath [ "bind_port" ] cfg.settings);
153         message = "AdGuard option `settings.bind_port' has been superseded by `services.adguardhome.port'";
154       }
155       {
156         assertion =
157           settings != null -> cfg.mutableSettings || lib.hasAttrByPath [ "dns" "bootstrap_dns" ] settings;
158         message = "AdGuard setting dns.bootstrap_dns needs to be configured for a minimal working configuration";
159       }
160       {
161         assertion =
162           settings != null
163           ->
164             cfg.mutableSettings
165             || lib.hasAttrByPath [ "dns" "bootstrap_dns" ] settings && lib.isList settings.dns.bootstrap_dns;
166         message = "AdGuard setting dns.bootstrap_dns needs to be a list";
167       }
168     ];
170     systemd.services.adguardhome = {
171       description = "AdGuard Home: Network-level blocker";
172       after = [ "network.target" ];
173       wantedBy = [ "multi-user.target" ];
174       unitConfig = {
175         StartLimitIntervalSec = 5;
176         StartLimitBurst = 10;
177       };
179       preStart = lib.optionalString (settings != null) ''
180         if    [ -e "$STATE_DIRECTORY/AdGuardHome.yaml" ] \
181            && [ "${toString cfg.mutableSettings}" = "1" ]; then
182           # First run a schema_version update on the existing configuration
183           # This ensures that both the new config and the existing one have the same schema_version
184           # Note: --check-config has the side effect of modifying the file at rest!
185           ${lib.getExe cfg.package} -c "$STATE_DIRECTORY/AdGuardHome.yaml" --check-config
187           # Writing directly to AdGuardHome.yaml results in empty file
188           ${lib.getExe pkgs.yaml-merge} "$STATE_DIRECTORY/AdGuardHome.yaml" "${configFile}" > "$STATE_DIRECTORY/AdGuardHome.yaml.tmp"
189           mv "$STATE_DIRECTORY/AdGuardHome.yaml.tmp" "$STATE_DIRECTORY/AdGuardHome.yaml"
190         else
191           cp --force "${configFile}" "$STATE_DIRECTORY/AdGuardHome.yaml"
192           chmod 600 "$STATE_DIRECTORY/AdGuardHome.yaml"
193         fi
194       '';
196       serviceConfig = {
197         DynamicUser = true;
198         ExecStart = "${lib.getExe cfg.package} ${args}";
199         AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ] ++ lib.optionals cfg.allowDHCP [ "CAP_NET_RAW" ];
200         Restart = "always";
201         RestartSec = 10;
202         RuntimeDirectory = "AdGuardHome";
203         StateDirectory = "AdGuardHome";
204       };
205     };
207     networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
208   };