grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / programs / captive-browser.nix
blob8f0aa2fe1e6a0c35b1211ca9254c6ad9fc16576d
1 { config, lib, pkgs, ... }:
3 let
4   cfg = config.programs.captive-browser;
6   inherit (lib)
7     concatStringsSep escapeShellArgs optionalString
8     literalExpression mkEnableOption mkPackageOption mkIf mkOption
9     mkOptionDefault types;
11   requiresSetcapWrapper = config.boot.kernelPackages.kernelOlder "5.7" && cfg.bindInterface;
13   browserDefault = chromium: concatStringsSep " " [
14     ''env XDG_CONFIG_HOME="$PREV_CONFIG_HOME"''
15     ''${chromium}/bin/chromium''
16     ''--user-data-dir=''${XDG_DATA_HOME:-$HOME/.local/share}/chromium-captive''
17     ''--proxy-server="socks5://$PROXY"''
18     ''--host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE localhost"''
19     ''--no-first-run''
20     ''--new-window''
21     ''--incognito''
22     ''-no-default-browser-check''
23     ''http://cache.nixos.org/''
24   ];
26   desktopItem = pkgs.makeDesktopItem {
27     name = "captive-browser";
28     desktopName = "Captive Portal Browser";
29     exec = "captive-browser";
30     icon = "nix-snowflake";
31     categories = [ "Network" ];
32   };
34   captive-browser-configured = pkgs.writeShellScriptBin "captive-browser" ''
35     export PREV_CONFIG_HOME="$XDG_CONFIG_HOME"
36     export XDG_CONFIG_HOME=${pkgs.writeTextDir "captive-browser.toml" ''
37       browser = """${cfg.browser}"""
38       dhcp-dns = """${cfg.dhcp-dns}"""
39       socks5-addr = """${cfg.socks5-addr}"""
40       ${optionalString cfg.bindInterface ''
41         bind-device = """${cfg.interface}"""
42       ''}
43     ''}
44     exec ${cfg.package}/bin/captive-browser
45   '';
48   ###### interface
50   options = {
51     programs.captive-browser = {
52       enable = mkEnableOption "captive browser, a dedicated Chrome instance to log into captive portals without messing with DNS settings";
54       package = mkPackageOption pkgs "captive-browser" { };
56       interface = mkOption {
57         type = types.str;
58         description = "your public network interface (wlp3s0, wlan0, eth0, ...)";
59       };
61       # the options below are the same as in "captive-browser.toml"
62       browser = mkOption {
63         type = types.str;
64         default = browserDefault pkgs.chromium;
65         defaultText = literalExpression (browserDefault "\${pkgs.chromium}");
66         description = ''
67           The shell (/bin/sh) command executed once the proxy starts.
68           When browser exits, the proxy exits. An extra env var PROXY is available.
70           Here, we use a separate Chrome instance in Incognito mode, so that
71           it can run (and be waited for) alongside the default one, and that
72           it maintains no state across runs. To configure this browser open a
73           normal window in it, settings will be preserved.
75           @volth: chromium is to open a plain HTTP (not HTTPS nor redirect to HTTPS!) website.
76                   upstream uses http://example.com but I have seen captive portals whose DNS server resolves "example.com" to 127.0.0.1
77         '';
78       };
80       dhcp-dns = mkOption {
81         type = types.str;
82         description = ''
83           The shell (/bin/sh) command executed to obtain the DHCP
84           DNS server address. The first match of an IPv4 regex is used.
85           IPv4 only, because let's be real, it's a captive portal.
86         '';
87       };
89       socks5-addr = mkOption {
90         type = types.str;
91         default = "localhost:1666";
92         description = "the listen address for the SOCKS5 proxy server";
93       };
95       bindInterface = mkOption {
96         default = true;
97         type = types.bool;
98         description = ''
99           Binds `captive-browser` to the network interface declared in
100           `cfg.interface`. This can be used to avoid collisions
101           with private subnets.
102         '';
103       };
104     };
105   };
107   ###### implementation
109   config = mkIf cfg.enable {
110     environment.systemPackages = [
111       (pkgs.runCommand "captive-browser-desktop-item" { } ''
112         install -Dm444 -t $out/share/applications ${desktopItem}/share/applications/*.desktop
113       '')
114       captive-browser-configured
115     ];
117     programs.captive-browser.dhcp-dns =
118       let
119         iface = prefixes:
120           optionalString cfg.bindInterface (escapeShellArgs (prefixes ++ [ cfg.interface ]));
121       in
122       mkOptionDefault (
123         if config.networking.networkmanager.enable then
124           "${pkgs.networkmanager}/bin/nmcli dev show ${iface []} | ${pkgs.gnugrep}/bin/fgrep IP4.DNS"
125         else if config.networking.dhcpcd.enable then
126           "${pkgs.dhcpcd}/bin/dhcpcd ${iface ["-U"]} | ${pkgs.gnugrep}/bin/fgrep domain_name_servers"
127         else if config.networking.useNetworkd then
128           "${cfg.package}/bin/systemd-networkd-dns ${iface []}"
129         else
130           "${config.security.wrapperDir}/udhcpc --quit --now -f ${iface ["-i"]} -O dns --script ${
131           pkgs.writeShellScript "udhcp-script" ''
132             if [ "$1" = bound ]; then
133               echo "$dns"
134             fi
135           ''}"
136       );
138     security.wrappers.udhcpc = {
139       owner = "root";
140       group = "root";
141       capabilities = "cap_net_raw+p";
142       source = "${pkgs.busybox}/bin/udhcpc";
143     };
145     security.wrappers.captive-browser = mkIf requiresSetcapWrapper {
146       owner = "root";
147       group = "root";
148       capabilities = "cap_net_raw+p";
149       source = "${captive-browser-configured}/bin/captive-browser";
150     };
151   };