diff --git a/htdocs/luci-static/resources/view/tailscale/base.js b/htdocs/luci-static/resources/view/tailscale/base.js index efbdbe0..32e24d8 100644 --- a/htdocs/luci-static/resources/view/tailscale/base.js +++ b/htdocs/luci-static/resources/view/tailscale/base.js @@ -19,7 +19,7 @@ var callServiceList = rpc.declare({ }); function getServiceStatus() { - return L.resolveDefault(callServiceList('tailscale'), {}).then(function (res) { + return Promise.resolve(callServiceList('tailscale')).then(function (res) { var isRunning = false; try { isRunning = res['tailscale']['instances']['instance1']['running']; @@ -83,8 +83,9 @@ return view.extend({ var m, s, o; var isRunning = data[1]; - m = new form.Map('tailscale', _('Tailscale'), - _('Tailscale is a cross-platform and easy to use virtual LAN.')); + var tailscaleLink = E('a', { href: 'https://login.tailscale.com/admin/machines', target: '_blank' }, _('Tailscale')); + var description = E('span', {}, _(' is a cross-platform and easy to use virtual LAN.')); + m = new form.Map('tailscale', _('Tailscale'), [tailscaleLink, description]); s = m.section(form.TypedSection); s.anonymous = true; @@ -95,23 +96,84 @@ return view.extend({ } s = m.section(form.NamedSection, 'settings', 'config'); + s.title = _('Basic Settings'); o = s.option(form.Flag, 'enabled', _('Enable')); o.default = o.disabled; o.rmempty = false; - + o = s.option(form.DummyValue, 'login_status', _('Login Status')); o.depends('enabled', '1'); o.renderWidget = function(section_id, option_id) { poll.add(function() { - return L.resolveDefault(getLoginStatus()).then(function(res) { + return Promise.resolve(getLoginStatus()).then(function(res) { document.getElementById('login_status_div').innerHTML = renderLogin(res.backendState, res.authURL); }); }); - + return E('div', { 'id': 'login_status_div' }, _('Collecting data ...')); }; + o = s.option(form.Value, 'port', _('Port'), _('Set the Tailscale port number.')); + o.datatype = 'port'; + o.default = '41641'; + o.rmempty = false; + + o = s.option(form.Value, 'config_path', _('Workdir'), _('The working directory contains config files, audit logs, and runtime info.')); + o.default = '/etc/tailscale'; + o.rmempty = false; + + o = s.option(form.ListValue, 'fw_mode', _('Firewall Mode')); + o.value('nftables', 'nftables'); + o.value('iptables', 'iptables'); + o.default = 'nftables'; + o.rmempty = false; + + o = s.option(form.Flag, 'log_stdout', _('Output Log'), _('Logging program activities.')); + o.default = o.enabled; + o.rmempty = false; + + o = s.option(form.Flag, 'log_stderr', _('Error Log'), _('Logging program errors and exceptions.')); + o.default = o.enabled; + o.rmempty = false; + + s = m.section(form.NamedSection, 'settings', 'config'); + s.title = _('Advanced Settings'); + + o = s.option(form.Flag, 'acceptRoutes', _('Auto NAT clients'), _('Expose physical network routes onto Tailscale.')); + o.default = o.disabled; + o.rmempty = false; + + o = s.option(form.Value, 'hostname', _('Hostname'), + _("Leave blank to use the device's hostname.")); + o.default = ''; + o.rmempty = true; + + o = s.option(form.Value, 'advertiseRoutes', _('Expose Subnets'), _('e.g. 10.0.0.0/24')); + o.datatype = 'cidr4'; + o.default = ''; + o.rmempty = true; + + o = s.option(form.MultiValue, 'access', _('Access Control')); + o.value('tsfwlan', _('Tailscale access LAN')); + o.value('tsfwwan', _('Tailscale access WAN')); + o.value('lanfwts', _('LAN access Tailscale')); + o.value('wanfwts', _('WAN access Tailscale')); + o.default = "tsfwlan tsfwwan lanfwts"; + o.depends('acceptRoutes', '1'); + o.rmempty = false; + + s = m.section(form.NamedSection, 'settings', 'config'); + s.title = _('Custom Server Settings'); + + o = s.option(form.Value, 'loginServer', _('Server address')); + o.default = ''; + o.rmempty = true; + + o = s.option(form.Value, 'authKey', _('Auth Key')); + o.default = ''; + o.rmempty = true; + return m.render(); } }); diff --git a/root/etc/init.d/tailscale b/root/etc/init.d/tailscale index d14af29..907be38 100755 --- a/root/etc/init.d/tailscale +++ b/root/etc/init.d/tailscale @@ -4,7 +4,8 @@ START=90 USE_PROCD=1 -PROG=/usr/sbin/tailscaled +PROG=/usr/sbin/tailscale +PROGD=/usr/sbin/tailscaled CONFIG_PATH=/var/lib/tailscale service_triggers() { @@ -17,9 +18,115 @@ section_enabled() { [ $enabled -gt 0 ] } +custom_instance() { + local cfg="$1" + local port config_path fw_mode std_out std_err state_file + local ARGS=" up --reset" + + if ! section_enabled "$cfg"; then + echo "disabled in config" + return 1 + fi + + config_get_bool acceptRoutes $cfg 'acceptRoutes' + config_get hostname $cfg 'hostname' + config_get advertiseRoutes $cfg 'advertiseRoutes' + config_get loginServer $cfg 'loginServer' + config_get authkey $cfg 'authkey' + config_get_bool std_out $cfg 'log_stdout' + config_get_bool std_err $cfg 'log_stderr' + + [ -n "$acceptRoutes" ] && ARGS="$ARGS --accept-routes true" + [ -n "$hostname" ] && ARGS="$ARGS --hostname $hostname" + [ -n "$advertiseRoutes" ] && ARGS="$ARGS --advertise-routes $advertiseRoutes" + [ -n "$loginServer" ] && ARGS="$ARGS --login-server $loginServer" + [ -n "$authkey" ] && ARGS="$ARGS --authkey $authkey" + + procd_open_instance + procd_set_param command $PROG $ARGS + procd_set_param stdout "$std_out" + procd_set_param stderr "$std_err" + procd_close_instance + ( + [ -f "/var/run/tailscale.wait.pid" ] && return + touch /var/run/tailscale.wait.pid + count=0 + while [ -z "$(ifconfig | grep 'tailscale' | awk '{print $1}')" ] + do + sleep 2 + let count++ + [ "${count}" -ge 5 ] && { rm /var/run/tailscale.wait.pid; exit 19; } + done + ts0=$(ifconfig | grep 'tailscale' | awk '{print $1}') + for i in ${ts0} + do + echo "tailscale interface $i is started!" + if [ -z "$(uci -q get network.$i)" ]; then + uci set network.$i=interface + uci set network.$i.proto='static' + uci set network.$i.device=$i + fi + done + + config_get_bool acceptRoutes $cfg 'acceptRoutes' + if [ "$acceptRoutes" == "1" ]; then + if [ -z "$(uci -q get firewall.tszone)" ]; then + uci set firewall.tszone=zone + uci set firewall.tszone.input='ACCEPT' + uci set firewall.tszone.output='ACCEPT' + uci set firewall.tszone.forward='REJECT' + uci set firewall.tszone.masq='1' + uci set firewall.tszone.name='tailscale' + if [ "$ts0" = *$'\n'* ]; then + printf '%s\n' "$ts0" | IFS=$'\n' read -ra ts0_array + uci add_list firewall.tszone.network ${ts0_array[@]} + else + uci set firewall.tszone.network=$ts0 + fi + fi + else + uci -q delete firewall.tszone + fi + + config_get access $cfg 'access' + if [ "${access//tsfwlan/}" != "$access" ]; then + uci set firewall.tsfwlan=forwarding + uci set firewall.tsfwlan.dest='lan' + uci set firewall.tsfwlan.src='tailscale' + else + uci -q delete firewall.tsfwlan + fi + if [ "${access//tsfwwan/}" != "$access" ]; then + uci set firewall.tsfwwan=forwarding + uci set firewall.tsfwwan.dest='wan' + uci set firewall.tsfwwan.src='tailscale' + else + uci -q delete firewall.tsfwwan + fi + if [ "${access//lanfwts/}" != "$access" ]; then + uci set firewall.lanfwts=forwarding + uci set firewall.lanfwts.dest='tailscale' + uci set firewall.lanfwts.src='lan' + else + uci -q delete firewall.lanfwts + fi + if [ "${access//wanfwts/}" != "$access" ]; then + uci set firewall.wanfwts=forwarding + uci set firewall.wanfwts.dest='tailscale' + uci set firewall.wanfwts.src='wan' + else + uci -q delete firewall.wanfwts + fi + + [ -n "$(uci changes network)" ] && uci commit network && /etc/init.d/network reload + [ -n "$(uci changes firewall)" ] && uci commit firewall && /etc/init.d/firewall reload + rm /var/run/tailscale.wait.pid + ) & +} + start_instance() { local cfg="$1" - local port std_err std_out config_path + local port config_path fw_mode std_out std_err state_file local ARGS="" if ! section_enabled "$cfg"; then @@ -27,34 +134,23 @@ start_instance() { return 1 fi - [ -d /etc/tailscale ] || mkdir -p /etc/tailscale - config_path=/etc/tailscale + config_get port $cfg 'port' + config_get config_path $cfg 'config_path' + config_get fw_mode $cfg 'fw_mode' + config_get_bool std_out $cfg 'log_stdout' + config_get_bool std_err $cfg 'log_stderr' - config_get_bool std_out $cfg log_stdout 1 - config_get_bool std_err $cfg log_stderr 1 - config_get port $cfg port 41641 - config_get state_file $cfg state_file /etc/tailscale/tailscaled.state - config_get fw_mode $cfg fw_mode nftables + [ -d $config_path ] || mkdir -p $config_path + [ -d $CONFIG_PATH ] || mkdir -p $CONFIG_PATH + state_file=$config_path/tailscaled.state /usr/sbin/tailscaled --cleanup - # Remove existing link or folder - rm -rf $CONFIG_PATH - - # Create link from CONFIG_PATH to config_path - if [ -n "$config_path" -a "$config_path" != $CONFIG_PATH ]; then - if [ ! -d "$config_path" ]; then - echo "Tailscale config_path does not exist: $config_path" - return - fi - ln -s $config_path $CONFIG_PATH - fi - - [ -n "$port" ] && ARGS="$ARGS --port $port" + [ -n "$port" ] && ARGS="$ARGS --port $port" [ -n "$state_file" ] && ARGS="$ARGS --state $state_file" procd_open_instance - procd_set_param command $PROG $ARGS + procd_set_param command $PROGD $ARGS procd_set_param env TS_DEBUG_FIREWALL_MODE="$fw_mode" @@ -67,14 +163,30 @@ start_instance() { start_service() { config_load 'tailscale' config_foreach start_instance 'tailscale' + config_foreach custom_instance 'tailscale' } stop_instance() { local cfg="$1" /usr/sbin/tailscaled --cleanup + # Remove network interfaces + ts0="$(ifconfig | grep 'tailscale' | awk '{print $1}')" + for i in ${ts0} + do + uci -q delete network.$i + done + + # Remove firewall settings + uci -q delete firewall.tszone + uci -q delete firewall.tsfwlan + uci -q delete firewall.tsfwwan + uci -q delete firewall.lanfwts + uci -q delete firewall.wanfwts + [ -n "$(uci changes network)" ] && uci commit network && /etc/init.d/network reload + [ -n "$(uci changes firewall)" ] && uci commit firewall && /etc/init.d/firewall reload + # Remove existing link or folder - rm -rf $CONFIG_PATH/*.log* rm -rf $CONFIG_PATH }