1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.crab-hole;
9
10 settingsFormat = pkgs.formats.toml { };
11
12 checkConfig =
13 file:
14 pkgs.runCommand "check-config"
15 {
16 nativeBuildInputs = [
17 cfg.package
18 pkgs.cacert
19 pkgs.dig
20 ];
21 }
22 ''
23 ln -s ${file} $out
24
25 ln -s ${file} ./config.toml
26 export CRAB_HOLE_DIR=$(pwd)
27
28 ${lib.getExe cfg.package} validate-config
29 '';
30in
31{
32 options = {
33 services.crab-hole = {
34 enable = lib.mkEnableOption "Crab-hole Service";
35
36 package = lib.mkPackageOption pkgs "crab-hole" { };
37
38 supplementaryGroups = lib.mkOption {
39 type = lib.types.listOf lib.types.str;
40 default = [ ];
41 example = [ "acme" ];
42 description = "Adds additional groups to the crab-hole service. Can be useful to prevent permission issues.";
43 };
44
45 settings = lib.mkOption {
46 description = "Crab-holes config. See big example https://github.com/LuckyTurtleDev/crab-hole/blob/main/example-config.toml";
47
48 example = {
49 downstream = [
50 {
51 listen = "localhost";
52 port = 8080;
53 protocol = "udp";
54 }
55 {
56 certificate = "dns.example.com.crt";
57 dns_hostname = "dns.example.com";
58 key = "dns.example.com.key";
59 listen = "[::]";
60 port = 8055;
61 protocol = "https";
62 timeout_ms = 3000;
63 }
64 ];
65 api = {
66 admin_key = "1234";
67 listen = "127.0.0.1";
68 port = 8080;
69 show_doc = true;
70 };
71 blocklist = {
72 allow_list = [
73 "file:///allowed.txt"
74 ];
75 include_subdomains = true;
76 lists = [
77 "https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling-porn/hosts"
78 "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt"
79 "file:///blocked.txt"
80 ];
81 };
82 upstream = {
83 name_servers = [
84 {
85 protocol = "tls";
86 socket_addr = "[2606:4700:4700::1111]:853";
87 tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com";
88 trust_nx_responses = false;
89 }
90 {
91 protocol = "tls";
92 socket_addr = "1.1.1.1:853";
93 tls_dns_name = "1dot1dot1dot1.cloudflare-dns.com";
94 trust_nx_responses = false;
95 }
96 ];
97 options = {
98 validate = false;
99 };
100 };
101 };
102
103 type = lib.types.submodule {
104 freeformType = settingsFormat.type;
105 options = {
106 blocklist =
107 let
108 listOption =
109 name:
110 lib.mkOption {
111 type = lib.types.listOf (lib.types.either lib.types.str lib.types.path);
112 default = [ ];
113 description = "List of ${name}. If files are added via url, make sure the service has access to them!";
114 apply = map (v: if builtins.isPath v then "file://${v}" else v);
115 };
116 in
117 {
118 include_subdomains = lib.mkEnableOption "Include subdomains";
119 lists = listOption "blocklists";
120 allow_list = listOption "allowlists";
121 };
122 };
123 };
124 };
125
126 configFile = lib.mkOption {
127 type = lib.types.path;
128 description = ''
129 The config file of crab-hole.
130
131 If files are added via url, make sure the service has access to them.
132 Setting this option will override any configuration applied by the settings option.
133 '';
134 };
135 };
136 };
137
138 config = lib.mkIf cfg.enable {
139 # Warning due to DNSSec issue in crab-hole
140 warnings = lib.optional (cfg.settings.upstream.options.validate or false) ''
141 Validate options will ONLY allow DNSSec domains. See https://github.com/LuckyTurtleDev/crab-hole/issues/29
142 '';
143
144 services.crab-hole.configFile = lib.mkDefault (
145 checkConfig (settingsFormat.generate "crab-hole.toml" cfg.settings)
146 );
147 environment.etc."crab-hole.toml".source = cfg.configFile;
148
149 systemd.services.crab-hole = {
150 wantedBy = [ "multi-user.target" ];
151 after = [ "network-online.target" ];
152 wants = [ "network-online.target" ];
153 description = "Crab-hole dns server";
154 environment.HOME = "/var/lib/crab-hole";
155 restartTriggers = [ cfg.configFile ];
156 serviceConfig = {
157 Type = "simple";
158 DynamicUser = true;
159 SupplementaryGroups = cfg.supplementaryGroups;
160
161 StateDirectory = "crab-hole";
162 WorkingDirectory = "/var/lib/crab-hole";
163
164 ExecStart = lib.getExe cfg.package;
165
166 AmbientCapabilities = "CAP_NET_BIND_SERVICE";
167 CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
168
169 Restart = "on-failure";
170 RestartSec = 1;
171 };
172 };
173 };
174
175 meta.maintainers = [
176 lib.maintainers.NiklasVousten
177 ];
178 # Readme from upstream
179 meta.doc = ./crab-hole.md;
180}