at 25.11-pre 6.8 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 cfg = config.services.adguardhome; 9 settingsFormat = pkgs.formats.yaml { }; 10 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 ); 20 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; 37 38 configFile = (settingsFormat.generate "AdGuardHome.yaml" settings).overrideAttrs (_: { 39 checkPhase = "${cfg.package}/bin/adguardhome -c $out --check-config"; 40 }); 41in 42{ 43 options.services.adguardhome = with lib.types; { 44 enable = lib.mkEnableOption "AdGuard Home network-wide ad blocker"; 45 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 }; 54 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 }; 63 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. 71 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 }; 77 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 }; 86 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 }; 94 95 port = lib.mkOption { 96 default = 3000; 97 type = port; 98 description = '' 99 Port to serve HTTP pages on. 100 ''; 101 }; 102 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. 123 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. 128 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 }; 135 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 }; 144 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 ]; 169 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 }; 178 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 186 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 ''; 195 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 }; 206 207 networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ]; 208 }; 209}