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