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