1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.bird-lg;
7
8 stringOrConcat = sep: v: if builtins.isString v then v else concatStringsSep sep v;
9
10 frontend_args = let
11 fe = cfg.frontend;
12 in {
13 "--servers" = concatStringsSep "," fe.servers;
14 "--domain" = fe.domain;
15 "--listen" = fe.listenAddress;
16 "--proxy-port" = fe.proxyPort;
17 "--whois" = fe.whois;
18 "--dns-interface" = fe.dnsInterface;
19 "--bgpmap-info" = concatStringsSep "," cfg.frontend.bgpMapInfo;
20 "--title-brand" = fe.titleBrand;
21 "--navbar-brand" = fe.navbar.brand;
22 "--navbar-brand-url" = fe.navbar.brandURL;
23 "--navbar-all-servers" = fe.navbar.allServers;
24 "--navbar-all-url" = fe.navbar.allServersURL;
25 "--net-specific-mode" = fe.netSpecificMode;
26 "--protocol-filter" = concatStringsSep "," cfg.frontend.protocolFilter;
27 };
28
29 proxy_args = let
30 px = cfg.proxy;
31 in {
32 "--allowed" = concatStringsSep "," px.allowedIPs;
33 "--bird" = px.birdSocket;
34 "--listen" = px.listenAddress;
35 "--traceroute_bin" = px.traceroute.binary;
36 "--traceroute_flags" = concatStringsSep " " px.traceroute.flags;
37 "--traceroute_raw" = px.traceroute.rawOutput;
38 };
39
40 mkArgValue = value:
41 if isString value
42 then escapeShellArg value
43 else if isBool value
44 then boolToString value
45 else toString value;
46
47 filterNull = filterAttrs (_: v: v != "" && v != null && v != []);
48
49 argsAttrToList = args: mapAttrsToList (name: value: "${name} " + mkArgValue value ) (filterNull args);
50in
51{
52 options = {
53 services.bird-lg = {
54 package = mkPackageOption pkgs "bird-lg" { };
55
56 user = mkOption {
57 type = types.str;
58 default = "bird-lg";
59 description = "User to run the service.";
60 };
61
62 group = mkOption {
63 type = types.str;
64 default = "bird-lg";
65 description = "Group to run the service.";
66 };
67
68 frontend = {
69 enable = mkEnableOption "Bird Looking Glass Frontend Webserver";
70
71 listenAddress = mkOption {
72 type = types.str;
73 default = "127.0.0.1:5000";
74 description = "Address to listen on.";
75 };
76
77 proxyPort = mkOption {
78 type = types.port;
79 default = 8000;
80 description = "Port bird-lg-proxy is running on.";
81 };
82
83 domain = mkOption {
84 type = types.str;
85 example = "dn42.lantian.pub";
86 description = "Server name domain suffixes.";
87 };
88
89 servers = mkOption {
90 type = types.listOf types.str;
91 example = [ "gigsgigscloud" "hostdare" ];
92 description = "Server name prefixes.";
93 };
94
95 whois = mkOption {
96 type = types.str;
97 default = "whois.verisign-grs.com";
98 description = "Whois server for queries.";
99 };
100
101 dnsInterface = mkOption {
102 type = types.str;
103 default = "asn.cymru.com";
104 description = "DNS zone to query ASN information.";
105 };
106
107 bgpMapInfo = mkOption {
108 type = types.listOf types.str;
109 default = [ "asn" "as-name" "ASName" "descr" ];
110 description = "Information displayed in bgpmap.";
111 };
112
113 titleBrand = mkOption {
114 type = types.str;
115 default = "Bird-lg Go";
116 description = "Prefix of page titles in browser tabs.";
117 };
118
119 netSpecificMode = mkOption {
120 type = types.str;
121 default = "";
122 example = "dn42";
123 description = "Apply network-specific changes for some networks.";
124 };
125
126 protocolFilter = mkOption {
127 type = types.listOf types.str;
128 default = [ ];
129 example = [ "ospf" ];
130 description = "Information displayed in bgpmap.";
131 };
132
133 nameFilter = mkOption {
134 type = types.str;
135 default = "";
136 example = "^ospf";
137 description = "Protocol names to hide in summary tables (RE2 syntax),";
138 };
139
140 timeout = mkOption {
141 type = types.int;
142 default = 120;
143 description = "Time before request timed out, in seconds.";
144 };
145
146 navbar = {
147 brand = mkOption {
148 type = types.str;
149 default = "Bird-lg Go";
150 description = "Brand to show in the navigation bar .";
151 };
152
153 brandURL = mkOption {
154 type = types.str;
155 default = "/";
156 description = "URL of the brand to show in the navigation bar.";
157 };
158
159 allServers = mkOption {
160 type = types.str;
161 default = "ALL Servers";
162 description = "Text of 'All server' button in the navigation bar.";
163 };
164
165 allServersURL = mkOption {
166 type = types.str;
167 default = "all";
168 description = "URL of 'All servers' button.";
169 };
170 };
171
172 extraArgs = mkOption {
173 type = with types; either lines (listOf str);
174 default = [ ];
175 description = ''
176 Extra parameters documented [here](https://github.com/xddxdd/bird-lg-go#frontend).
177
178 :::{.note}
179 Passing lines (plain strings) is deprecated in favour of passing lists of strings.
180 :::
181 '';
182 };
183 };
184
185 proxy = {
186 enable = mkEnableOption "Bird Looking Glass Proxy";
187
188 listenAddress = mkOption {
189 type = types.str;
190 default = "127.0.0.1:8000";
191 description = "Address to listen on.";
192 };
193
194 allowedIPs = mkOption {
195 type = types.listOf types.str;
196 default = [ ];
197 example = [ "192.168.25.52" "192.168.25.53" "192.168.0.0/24" ];
198 description = "List of IPs or networks to allow (default all allowed).";
199 };
200
201 birdSocket = mkOption {
202 type = types.str;
203 default = "/var/run/bird/bird.ctl";
204 description = "Bird control socket path.";
205 };
206
207 traceroute = {
208 binary = mkOption {
209 type = types.str;
210 default = "${pkgs.traceroute}/bin/traceroute";
211 defaultText = literalExpression ''"''${pkgs.traceroute}/bin/traceroute"'';
212 description = "Traceroute's binary path.";
213 };
214
215 flags = mkOption {
216 type = with types; listOf str;
217 default = [ ];
218 description = "Flags for traceroute process";
219 };
220
221 rawOutput = mkOption {
222 type = types.bool;
223 default = false;
224 description = "Display traceroute output in raw format.";
225 };
226 };
227
228 extraArgs = mkOption {
229 type = with types; either lines (listOf str);
230 default = [ ];
231 description = ''
232 Extra parameters documented [here](https://github.com/xddxdd/bird-lg-go#proxy).
233
234 :::{.note}
235 Passing lines (plain strings) is deprecated in favour of passing lists of strings.
236 :::
237 '';
238 };
239 };
240 };
241 };
242
243 ###### implementation
244
245 config = {
246
247 warnings =
248 lib.optional (cfg.frontend.enable && builtins.isString cfg.frontend.extraArgs) ''
249 Passing strings to `services.bird-lg.frontend.extraOptions' is deprecated. Please pass a list of strings instead.
250 ''
251 ++ lib.optional (cfg.proxy.enable && builtins.isString cfg.proxy.extraArgs) ''
252 Passing strings to `services.bird-lg.proxy.extraOptions' is deprecated. Please pass a list of strings instead.
253 ''
254 ;
255
256 systemd.services = {
257 bird-lg-frontend = mkIf cfg.frontend.enable {
258 enable = true;
259 after = [ "network.target" ];
260 wantedBy = [ "multi-user.target" ];
261 description = "Bird Looking Glass Frontend Webserver";
262 serviceConfig = {
263 Type = "simple";
264 Restart = "on-failure";
265 ProtectSystem = "full";
266 ProtectHome = "yes";
267 MemoryDenyWriteExecute = "yes";
268 User = cfg.user;
269 Group = cfg.group;
270 };
271 script = ''
272 ${cfg.package}/bin/frontend \
273 ${concatStringsSep " \\\n " (argsAttrToList frontend_args)} \
274 ${stringOrConcat " " cfg.frontend.extraArgs}
275 '';
276 };
277
278 bird-lg-proxy = mkIf cfg.proxy.enable {
279 enable = true;
280 after = [ "network.target" ];
281 wantedBy = [ "multi-user.target" ];
282 description = "Bird Looking Glass Proxy";
283 serviceConfig = {
284 Type = "simple";
285 Restart = "on-failure";
286 ProtectSystem = "full";
287 ProtectHome = "yes";
288 MemoryDenyWriteExecute = "yes";
289 User = cfg.user;
290 Group = cfg.group;
291 };
292 script = ''
293 ${cfg.package}/bin/proxy \
294 ${concatStringsSep " \\\n " (argsAttrToList proxy_args)} \
295 ${stringOrConcat " " cfg.proxy.extraArgs}
296 '';
297 };
298 };
299 users = mkIf (cfg.frontend.enable || cfg.proxy.enable) {
300 groups."bird-lg" = mkIf (cfg.group == "bird-lg") { };
301 users."bird-lg" = mkIf (cfg.user == "bird-lg") {
302 description = "Bird Looking Glass user";
303 extraGroups = lib.optionals (config.services.bird2.enable) [ "bird2" ];
304 group = cfg.group;
305 isSystemUser = true;
306 };
307 };
308 };
309
310 meta.maintainers = with lib.maintainers; [
311 e1mo
312 tchekda
313 ];
314}