main
1{
2 lib,
3 den,
4 ...
5}: {
6 den.aspects.services.proxy.dae = {
7 settings = {
8 interfaces = {
9 wan = lib.mkOption {
10 description = "The WAN interface to bind. Use it if you want to proxy localhost.";
11 type = lib.types.listOf lib.types.str;
12 default = ["auto"];
13 };
14 lan = lib.mkOption {
15 description = "The LAN interface to bind. Use it if you want to proxy LAN.";
16 type = lib.types.listOf lib.types.str;
17 default = [];
18 };
19 };
20 };
21
22 nixos = {
23 host,
24 pkgs,
25 config,
26 ...
27 }: let
28 cfg = host.settings.services.proxy.dae;
29
30 allHosts = builtins.concatMap builtins.attrValues (builtins.attrValues den.hosts);
31 otherHosts = builtins.filter (h: h.name != host.name) allHosts;
32
33 hostsWithIpv4ClearText = builtins.filter (h: h.address.ipv4.clearText != null) otherHosts;
34 hostsWithIpv4Secret = builtins.filter (h: h.address.ipv4.secret.name != null && h.address.ipv4.secret.file != null) otherHosts;
35 hostsWithIpv6ClearText = builtins.filter (h: h.address.ipv6.clearText != null) otherHosts;
36 hostsWithIpv6Secret = builtins.filter (h: h.address.ipv6.secret.name != null && h.address.ipv6.secret.file != null) otherHosts;
37
38 clearTextIps =
39 (map (h: h.address.ipv4.clearText) hostsWithIpv4ClearText)
40 ++ (map (h: h.address.ipv6.clearText) hostsWithIpv6ClearText);
41
42 secretIpPlaceholders =
43 (map (h: config.vaultix.placeholder.${h.address.ipv4.secret.name}) hostsWithIpv4Secret)
44 ++ (map (h: config.vaultix.placeholder.${h.address.ipv6.secret.name}) hostsWithIpv6Secret);
45
46 allHostIps = clearTextIps ++ secretIpPlaceholders;
47
48 hostDirectRule =
49 lib.optionalString (allHostIps != [])
50 "dip(${builtins.concatStringsSep ", " allHostIps}) -> direct";
51
52 v2ray-geoip = pkgs.fetchurl rec {
53 pname = "v2ray-geoip";
54 version = "202606262300";
55 url = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/${version}/geoip.dat";
56 downloadToTemp = true;
57 recursiveHash = true;
58 postFetch = ''
59 install -D "$downloadedFile" "$out/share/v2ray/geoip.dat"
60 '';
61 hash = "sha256-0z0qNvvN8I5PY14DWZgk4blZcgLoWIChZefkgeuWM1c=";
62 };
63 v2ray-geosite = pkgs.fetchurl rec {
64 pname = "v2ray-geosite";
65 version = "202606262300";
66 url = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/${version}/geosite.dat";
67 downloadToTemp = true;
68 recursiveHash = true;
69 postFetch = ''
70 install -D "$downloadedFile" "$out/share/v2ray/geosite.dat"
71 '';
72 hash = "sha256-NytCOfetMwjKFlc8bEz31KskcSnEkvSmvTp+Y8RvVC0=";
73 };
74 in {
75 services.dae = {
76 enable = true;
77 package = pkgs.dae.overrideAttrs {
78 version = "1.1.0";
79 src = pkgs.fetchFromGitHub {
80 owner = "daeuniverse";
81 repo = "dae";
82 tag = "v1.1.0";
83 hash = "sha256-Kc51VQuqObxKXVXGv5CnDm4/3XYqjPvrpAQSVb2vxSM=";
84 fetchSubmodules = true;
85 };
86 vendorHash = "sha256-juxIsZt1T33epN8CbzDc02MmlW5PtYa4pcGxuX9OpH4=";
87 };
88 assets = [v2ray-geoip v2ray-geosite];
89 config = ''
90 include {
91 # For security reasons, dae requires included files to be under the entry config directory.
92 routing.dae
93 }
94
95 global {
96 ${
97 if cfg.interfaces.wan != []
98 then "wan_interface: ${builtins.concatStringsSep "," cfg.interfaces.wan}"
99 else ""
100 }
101 ${
102 if cfg.interfaces.lan != []
103 then "lan_interface: ${builtins.concatStringsSep "," cfg.interfaces.lan}"
104 else ""
105 }
106 log_level: info
107 allow_insecure: false
108 check_interval: 60s
109 check_tolerance: 100ms
110 dial_mode: domain
111 }
112
113 dns {
114 upstream {
115 alidns: 'udp://dns.alidns.com:53'
116 googledns: 'tcp+udp://dns.google:53'
117 tailscale: 'udp://100.100.100.100:53'
118 }
119 routing {
120 request {
121 qname(keyword: _acme-challenge) -> googledns
122 qname(suffix: ts.net, suffix: net.trin.one) -> tailscale
123 qname(geosite:category-ads-all) -> reject
124 qtype(https) -> reject
125 qname(geosite:cn) -> alidns
126 fallback: googledns
127 }
128 }
129 }
130
131 subscription {
132 common: "http://127.0.0.1:${toString host.settings.services.proxy.sub-store.port}/backend/download/collection/common?target=V2Ray"
133 auxiliary: "http://127.0.0.1:${toString host.settings.services.proxy.sub-store.port}/backend/download/collection/auxiliary?target=V2Ray"
134 }
135
136 group {
137 proxy {
138 filter: !subtag(auxiliary) && name(regex: '香港|台湾|美国|日本|新加坡')
139 filter: subtag(auxiliary) && name(regex: '香港|台湾|美国|日本|新加坡') [add_latency: 300ms]
140 policy: min_moving_avg
141 }
142 proxy_download {
143 filter: !subtag(auxiliary) && !name(regex: 'IEPL|家宽') && name(regex: '香港|美国|日本|新加坡|韩国|德国')
144 policy: min_avg10
145 }
146 }
147 '';
148 };
149
150 systemd.services.dae = {
151 serviceConfig.LoadCredential = ["routing.dae:${config.vaultix.templates.dae-routing.path}"];
152 };
153
154 vaultix.secrets = lib.mkMerge (
155 (map (h: {${h.address.ipv4.secret.name}.file = h.address.ipv4.secret.file;}) hostsWithIpv4Secret)
156 ++ (map (h: {${h.address.ipv6.secret.name}.file = h.address.ipv6.secret.file;}) hostsWithIpv6Secret)
157 );
158
159 vaultix.templates.dae-routing = {
160 content = ''
161 routing {
162 # == System ==
163 pname(NetworkManager, systemd-networkd, systemd-resolved,
164 dnsmasq, cloudflared) -> must_direct
165 pname(ssh) -> direct
166 dport(22) -> direct
167
168 # == DNS ==
169 dip(223.5.5.5, 223.6.6.6) -> direct
170 domain(full:dns.alidns.com) -> direct
171 dip(119.29.29.29, 119.28.28.28) -> direct
172 domain(full:doh.pub) -> direct
173 dip(8.8.8.8, 8.8.4.4) -> proxy
174 domain(full:dns.google) -> proxy
175 dip(1.1.1.1, 1.0.0.1) -> proxy
176 domain(full:one.one.one.one, full:cloudflare-dns.com) -> proxy
177
178 # == Private ==
179 dip(geoip:private) -> direct
180 domain(geosite:private) -> direct
181
182 # == Tailscale ==
183 dip(100.64.0.0/10) -> direct
184 pname(tailscale, tailscaled) -> direct
185 l4proto(udp) && dport(3478) -> direct
186 l4proto(udp) && sport(41641) -> direct
187
188 # == My Hosts ==
189 ${hostDirectRule}
190
191 # == AI ==
192 domain(geosite:category-ai-!cn) -> proxy
193
194 # == Big Company ==
195 domain(geosite:microsoft@cn) -> direct
196 domain(geosite:microsoft) -> proxy
197 domain(geosite:apple@cn) -> direct
198 domain(geosite:apple) -> proxy
199 domain(geosite:google@cn) -> direct
200 domain(geosite:google) -> proxy
201 domain(geosite:cloudflare) -> proxy
202
203 # == Game ==
204 domain(suffix:cm.steampowered.com) -> direct
205 domain(suffix:steamserver.net) -> direct
206 domain(geosite:steam@cn) -> direct
207 domain(geosite:category-games-cn) -> direct
208 domain(geosite:category-game-platforms-download@cn) -> direct
209
210 # == Download ==
211 domain(geosite:mega) -> proxy_download
212 domain(keyword:dl-a10b-, dl-z01a-, dl-b07-) -> proxy_download
213
214 # == China ==
215 domain(geosite:tld-cn, geosite:geolocation-cn, geosite:cn) -> direct
216 dip(geoip:cn) -> direct
217 domain(geosite:gfw) -> proxy
218
219 # == Other ==
220 domain(suffix: kagi.com, suffix: codeberg.org, suffix: mxrouting.net) -> direct
221
222 # == Fallback ==
223 fallback: proxy
224 }
225 '';
226 };
227 };
228 };
229}