├── luci-app-passwall2 ├── po │ └── zh_Hans ├── root │ ├── etc │ │ ├── config │ │ │ └── passwall2_server │ │ ├── init.d │ │ │ ├── passwall2_server │ │ │ └── passwall2 │ │ ├── hotplug.d │ │ │ └── iface │ │ │ │ └── 98-passwall2 │ │ └── uci-defaults │ │ │ └── luci-passwall2 │ └── usr │ │ └── share │ │ ├── ucitrack │ │ ├── luci-app-passwall2.json │ │ └── luci-app-passwall2-server.json │ │ ├── rpcd │ │ └── acl.d │ │ │ └── luci-app-passwall2.json │ │ └── passwall2 │ │ ├── domains_excluded │ │ ├── haproxy_check.sh │ │ ├── lease2hosts.sh │ │ ├── monitor.sh │ │ ├── test.sh │ │ ├── tasks.sh │ │ ├── rule_update.lua │ │ ├── socks_auto_switch.sh │ │ ├── 0_default_config │ │ ├── haproxy.lua │ │ └── helper_dnsmasq.lua ├── luasrc │ ├── model │ │ └── cbi │ │ │ └── passwall2 │ │ │ ├── client │ │ │ ├── log.lua │ │ │ ├── geoview.lua │ │ │ ├── node_list.lua │ │ │ ├── type │ │ │ │ ├── naive.lua │ │ │ │ ├── ss.lua │ │ │ │ ├── ssr.lua │ │ │ │ ├── ss-rust.lua │ │ │ │ ├── hysteria2.lua │ │ │ │ └── tuic.lua │ │ │ ├── app_update.lua │ │ │ ├── node_config.lua │ │ │ ├── acl.lua │ │ │ ├── rule.lua │ │ │ ├── socks_config.lua │ │ │ ├── haproxy.lua │ │ │ ├── node_subscribe.lua │ │ │ ├── node_subscribe_config.lua │ │ │ └── shunt_rules.lua │ │ │ └── server │ │ │ ├── user.lua │ │ │ ├── type │ │ │ ├── ss-rust.lua │ │ │ ├── ss.lua │ │ │ ├── hysteria2.lua │ │ │ └── ssr.lua │ │ │ └── index.lua │ ├── view │ │ └── passwall2 │ │ │ ├── cbi │ │ │ └── hidevalue.htm │ │ │ ├── haproxy │ │ │ └── status.htm │ │ │ ├── log │ │ │ └── log.htm │ │ │ ├── server │ │ │ ├── log.htm │ │ │ └── users_list_status.htm │ │ │ ├── socks_auto_switch │ │ │ └── btn.htm │ │ │ ├── rule │ │ │ ├── rule_version.htm │ │ │ └── geoview.htm │ │ │ ├── global │ │ │ ├── faq.htm │ │ │ ├── footer.htm │ │ │ └── backup.htm │ │ │ ├── node_subscribe │ │ │ └── js.htm │ │ │ ├── app_update │ │ │ └── app_version.htm │ │ │ └── node_list │ │ │ └── link_add_node.htm │ └── passwall2 │ │ ├── util_naiveproxy.lua │ │ ├── com.lua │ │ ├── util_tuic.lua │ │ ├── util_shadowsocks.lua │ │ ├── util_hysteria2.lua │ │ └── server_app.lua └── Makefile ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature.yml │ └── bug.yml └── workflows │ ├── translate.yml │ └── Close stale issues and Prs.yml └── translate_files.py /luci-app-passwall2/po/zh_Hans: -------------------------------------------------------------------------------- 1 | zh-cn -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /luci-app-passwall2/root/etc/config/passwall2_server: -------------------------------------------------------------------------------- 1 | 2 | config global 'global' 3 | option enable '0' 4 | 5 | -------------------------------------------------------------------------------- /luci-app-passwall2/root/usr/share/ucitrack/luci-app-passwall2.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": "passwall2", 3 | "init": "passwall2" 4 | } 5 | -------------------------------------------------------------------------------- /luci-app-passwall2/root/usr/share/ucitrack/luci-app-passwall2-server.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": "passwall2_server", 3 | "init": "passwall2_server" 4 | } 5 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/log.lua: -------------------------------------------------------------------------------- 1 | local appname = "passwall2" 2 | 3 | f = SimpleForm(appname) 4 | f.reset = false 5 | f.submit = false 6 | f:append(Template(appname .. "/log/log")) 7 | 8 | return f -------------------------------------------------------------------------------- /luci-app-passwall2/root/etc/init.d/passwall2_server: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | 3 | START=99 4 | 5 | start() { 6 | lua /usr/lib/lua/luci/passwall2/server_app.lua start 7 | } 8 | 9 | stop() { 10 | lua /usr/lib/lua/luci/passwall2/server_app.lua stop 11 | } 12 | 13 | restart() { 14 | stop 15 | start 16 | } -------------------------------------------------------------------------------- /luci-app-passwall2/root/usr/share/rpcd/acl.d/luci-app-passwall2.json: -------------------------------------------------------------------------------- 1 | { 2 | "luci-app-passwall2": { 3 | "description": "Grant UCI access for luci-app-passwall2", 4 | "read": { 5 | "uci": [ "passwall2", "passwall2_server" ] 6 | }, 7 | "write": { 8 | "uci": [ "passwall2", "passwall2_server" ] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/view/passwall2/cbi/hidevalue.htm: -------------------------------------------------------------------------------- 1 |
" data-index="<%=self.index%>" data-depends="<%=pcdata(self:deplist2json(section))%>" style="display: none !important"> 2 | " /> 3 |
4 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/geoview.lua: -------------------------------------------------------------------------------- 1 | local api = require "luci.passwall2.api" 2 | local appname = "passwall2" 3 | local fs = api.fs 4 | local uci = api.uci 5 | 6 | local geo_dir = (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") 7 | local geosite_path = geo_dir .. "/geosite.dat" 8 | local geoip_path = geo_dir .. "/geoip.dat" 9 | if fs.access(geosite_path) and fs.access(geoip_path) then 10 | f = SimpleForm(appname) 11 | f.reset = false 12 | f.submit = false 13 | f:append(Template(appname .. "/rule/geoview")) 14 | end 15 | 16 | return f -------------------------------------------------------------------------------- /luci-app-passwall2/root/usr/share/passwall2/domains_excluded: -------------------------------------------------------------------------------- 1 | courier.push.apple.com 2 | rbsxbxp-mim.vivox.com 3 | rbsxbxp.www.vivox.com 4 | rbsxbxp-ws.vivox.com 5 | rbspsxp.www.vivox.com 6 | rbspsxp-mim.vivox.com 7 | rbspsxp-ws.vivox.com 8 | rbswxp.www.vivox.com 9 | rbswxp-mim.vivox.com 10 | disp-rbspsp-5-1.vivox.com 11 | disp-rbsxbp-5-1.vivox.com 12 | proxy.rbsxbp.vivox.com 13 | proxy.rbspsp.vivox.com 14 | proxy.rbswp.vivox.com 15 | rbswp.vivox.com 16 | rbsxbp.vivox.com 17 | rbspsp.vivox.com 18 | rbspsp.www.vivox.com 19 | rbswp.www.vivox.com 20 | rbsxbp.www.vivox.com 21 | rbsxbxp.vivox.com 22 | rbspsxp.vivox.com 23 | rbswxp.vivox.com 24 | Mijia Cloud 25 | dlg.io.mi.com 26 | marscdn.c2c.wechat.com 27 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua: -------------------------------------------------------------------------------- 1 | local api = require "luci.passwall2.api" 2 | local appname = api.appname 3 | local datatypes = api.datatypes 4 | local sys = api.sys 5 | 6 | m = Map(appname) 7 | api.set_apply_on_parse(m) 8 | 9 | -- [[ Other Settings ]]-- 10 | s = m:section(TypedSection, "global_other") 11 | s.anonymous = true 12 | 13 | o = s:option(ListValue, "auto_detection_time", translate("Automatic detection delay")) 14 | o:value("0", translate("Close")) 15 | o:value("icmp", "Ping") 16 | o:value("tcping", "TCP Ping") 17 | 18 | o = s:option(Flag, "show_node_info", translate("Show server address and port")) 19 | o.default = "0" 20 | 21 | -- [[ Add the node via the link ]]-- 22 | s:append(Template(appname .. "/node_list/link_add_node")) 23 | 24 | m:append(Template(appname .. "/node_list/node_list")) 25 | 26 | return m 27 | -------------------------------------------------------------------------------- /luci-app-passwall2/root/etc/hotplug.d/iface/98-passwall2: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | [[ "$ACTION" == "ifup" && $(uci get "passwall2.@global[0].enabled") == "1" ]] && [ -f /var/lock/passwall2_ready.lock ] && { 4 | default_device=$(ip route | grep default | awk -F 'dev ' '{print $2}' | awk '{print $1}') 5 | [ "$default_device" == "$DEVICE" ] && { 6 | LOCK_FILE_DIR=/var/lock 7 | [ ! -d ${LOCK_FILE_DIR} ] && mkdir -p ${LOCK_FILE_DIR} 8 | LOCK_FILE="${LOCK_FILE_DIR}/passwall2_ifup.lock" 9 | if [ -s ${LOCK_FILE} ]; then 10 | SPID=$(cat ${LOCK_FILE}) 11 | if [ -e /proc/${SPID}/status ]; then 12 | exit 1 13 | fi 14 | cat /dev/null > ${LOCK_FILE} 15 | fi 16 | echo $$ > ${LOCK_FILE} 17 | 18 | /etc/init.d/passwall2 restart >/dev/null 2>&1 & 19 | logger -p notice -t network -s "passwall2: restart when $INTERFACE ifup" 20 | 21 | rm -rf ${LOCK_FILE} 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/view/passwall2/haproxy/status.htm: -------------------------------------------------------------------------------- 1 | <% 2 | local api = require "luci.passwall2.api" 3 | local console_port = api.uci_get_type("global_haproxy", "console_port", "") 4 | -%> 5 |

6 | 7 | 27 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/naive.lua: -------------------------------------------------------------------------------- 1 | local m, s = ... 2 | 3 | local api = require "luci.passwall2.api" 4 | 5 | if not api.is_finded("naive") then 6 | return 7 | end 8 | 9 | local type_name = "Naiveproxy" 10 | 11 | local option_prefix = "naive_" 12 | 13 | local function _n(name) 14 | return option_prefix .. name 15 | end 16 | 17 | -- [[ Naive ]] 18 | 19 | s.fields["type"]:value(type_name, translate("NaiveProxy")) 20 | 21 | o = s:option(ListValue, _n("protocol"), translate("Protocol")) 22 | o:value("https", translate("HTTPS")) 23 | o:value("quic", translate("QUIC")) 24 | 25 | o = s:option(Value, _n("address"), translate("Address (Support Domain Name)")) 26 | 27 | o = s:option(Value, _n("port"), translate("Port")) 28 | o.datatype = "port" 29 | 30 | o = s:option(Value, _n("username"), translate("Username")) 31 | 32 | o = s:option(Value, _n("password"), translate("Password")) 33 | o.password = true 34 | 35 | api.luci_types(arg[1], m, s, type_name, option_prefix) 36 | -------------------------------------------------------------------------------- /.github/workflows/translate.yml: -------------------------------------------------------------------------------- 1 | name: Translate Chinese to English 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | translate: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup Python 18 | uses: actions/setup-python@v4 19 | with: 20 | python-version: 3.x 21 | 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | pip install deep-translator 26 | 27 | - name: Find and Translate Files 28 | run: | 29 | python translate_files.py 30 | 31 | - name: Commit and Push Changes 32 | run: | 33 | git config --global user.name "GitHub Actions" 34 | git config --global user.email "actions@github.com" 35 | git add . 36 | git commit -m "Translate Chinese content to English" 37 | git push 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/server/user.lua: -------------------------------------------------------------------------------- 1 | local api = require "luci.passwall2.api" 2 | local fs = require "nixio.fs" 3 | local types_dir = "/usr/lib/lua/luci/model/cbi/passwall2/server/type/" 4 | 5 | m = Map("passwall2_server", translate("Server Config")) 6 | m.redirect = api.url("server") 7 | api.set_apply_on_parse(m) 8 | 9 | s = m:section(NamedSection, arg[1], "user", "") 10 | s.addremove = false 11 | s.dynamic = false 12 | 13 | o = s:option(Flag, "enable", translate("Enable")) 14 | o.default = "1" 15 | o.rmempty = false 16 | 17 | o = s:option(Value, "remarks", translate("Remarks")) 18 | o.default = translate("Remarks") 19 | o.rmempty = false 20 | 21 | o = s:option(ListValue, "type", translate("Type")) 22 | 23 | local type_table = {} 24 | for filename in fs.dir(types_dir) do 25 | table.insert(type_table, filename) 26 | end 27 | table.sort(type_table) 28 | 29 | for index, value in ipairs(type_table) do 30 | local p_func = loadfile(types_dir .. value) 31 | setfenv(p_func, getfenv(1))(m, s) 32 | end 33 | 34 | return m 35 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/app_update.lua: -------------------------------------------------------------------------------- 1 | local api = require "luci.passwall2.api" 2 | local appname = api.appname 3 | 4 | m = Map(appname) 5 | api.set_apply_on_parse(m) 6 | 7 | -- [[ App Settings ]]-- 8 | s = m:section(TypedSection, "global_app", translate("App Update"), 9 | "" .. 10 | translate("Please confirm that your firmware supports FPU.") .. 11 | "") 12 | s.anonymous = true 13 | s:append(Template(appname .. "/app_update/app_version")) 14 | 15 | local k, v 16 | local com = require "luci.passwall2.com" 17 | for k, v in pairs(com) do 18 | o = s:option(Value, k:gsub("%-","_") .. "_file", translatef("%s App Path", v.name)) 19 | o.default = v.default_path or ("/usr/bin/" .. k) 20 | o.rmempty = false 21 | end 22 | 23 | o = s:option(DummyValue, "tips", " ") 24 | o.rawhtml = true 25 | o.cfgvalue = function(t, n) 26 | return string.format('%s', translate("if you want to run from memory, change the path, /tmp beginning then save the application and update it manually.")) 27 | end 28 | 29 | return m 30 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/view/passwall2/log/log.htm: -------------------------------------------------------------------------------- 1 | <% 2 | local api = require "luci.passwall2.api" 3 | -%> 4 | 27 |
28 | 29 | 30 |
31 | -------------------------------------------------------------------------------- /.github/workflows/Close stale issues and Prs.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues and PRs" 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-22.04 9 | steps: 10 | - uses: actions/stale@v7.0.0 11 | with: 12 | stale-issue-message: "Stale Issue" 13 | stale-pr-message: "Stale PR" 14 | stale-issue-label: "no-issue-activity" 15 | exempt-issue-labels: "awaiting-approval,awaiting,work-in-progress" 16 | stale-pr-label: "no-pr-activity" 17 | exempt-pr-labels: "awaiting-approval,awaiting,work-in-progress,automated-pr" 18 | # only-labels: 'bug,enhancement' 19 | days-before-issue-stale: 10 20 | days-before-pr-stale: 10 21 | days-before-issue-close: 5 22 | days-before-pr-close: -1 23 | operations-per-run: 500 24 | 25 | # - name: Delete workflow runs 26 | # uses: Mattraks/delete-workflow-runs@main 27 | # with: 28 | # token: ${{ github.token }} 29 | # repository: ${{ github.repository }} 30 | # retain_days: 1 31 | # keep_minimum_runs: 0 32 | -------------------------------------------------------------------------------- /luci-app-passwall2/root/usr/share/passwall2/haproxy_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export PATH=/usr/sbin:/usr/bin:/sbin:/bin:/root/bin 4 | CONFIG=passwall2 5 | 6 | listen_address=$1 7 | listen_port=$2 8 | server_address=$3 9 | server_port=$4 10 | 11 | pgrep -af "${CONFIG}/" | awk '/app\.sh.*(start|stop)/ || /nftables\.sh/ || /iptables\.sh/ { found = 1 } END { exit !found }' && { 12 | # Not detected during specific task execution 13 | exit 0 14 | } 15 | 16 | probe_file="/tmp/etc/${CONFIG}/haproxy/Probe_URL" 17 | probeUrl="https://www.google.com/generate_204" 18 | if [ -f "$probe_file" ]; then 19 | firstLine=$(head -n 1 "$probe_file" | tr -d ' \t\n') 20 | [ -n "$firstLine" ] && probeUrl="$firstLine" 21 | fi 22 | 23 | extra_params="-x socks5h://${server_address}:${server_port}" 24 | if /usr/bin/curl --help all | grep -q "\-\-retry-all-errors"; then 25 | extra_params="${extra_params} --retry-all-errors" 26 | fi 27 | 28 | status=$(/usr/bin/curl -I -o /dev/null -skL ${extra_params} --connect-timeout 3 --retry 2 --max-time 10 -w "%{http_code}" "${probeUrl}") 29 | 30 | case "$status" in 31 | 200|204) 32 | exit 0 33 | ;; 34 | *) 35 | exit 1 36 | ;; 37 | esac 38 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/passwall2/util_naiveproxy.lua: -------------------------------------------------------------------------------- 1 | module("luci.passwall2.util_navieproxy", package.seeall) 2 | local api = require "luci.passwall2.api" 3 | local uci = api.uci 4 | local jsonc = api.jsonc 5 | 6 | function gen_config(var) 7 | local node_id = var["-node"] 8 | if not node_id then 9 | print("-node 不能为空") 10 | return 11 | end 12 | local node = uci:get_all("passwall2", node_id) 13 | local run_type = var["-run_type"] 14 | local local_addr = var["-local_addr"] 15 | local local_port = var["-local_port"] 16 | local server_host = var["-server_host"] or node.address 17 | local server_port = var["-server_port"] or node.port 18 | 19 | if api.is_ipv6(server_host) then 20 | server_host = api.get_ipv6_full(server_host) 21 | end 22 | local server = server_host .. ":" .. server_port 23 | 24 | local config = { 25 | listen = run_type .. "://" .. local_addr .. ":" .. local_port, 26 | proxy = node.protocol .. "://" .. node.username .. ":" .. node.password .. "@" .. server 27 | } 28 | 29 | return jsonc.stringify(config, 1) 30 | end 31 | 32 | _G.gen_config = gen_config 33 | 34 | if arg[1] then 35 | local func =_G[arg[1]] 36 | if func then 37 | print(func(api.get_function_args(arg))) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/view/passwall2/server/log.htm: -------------------------------------------------------------------------------- 1 | <% 2 | local api = require "luci.passwall2.api" 3 | -%> 4 | 28 |
29 | 30 | <%:Logs%> 31 | 32 | 33 | 34 |
-------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: Feature Requests 2 | description: Propose new feature ideas for this project. 3 | title: "[Feature Request]: " 4 | labels: enhancement 5 | 6 | body: 7 | - type: textarea 8 | id: description 9 | attributes: 10 | label: Describe the new feature you want 11 | description: Describe the new feature you want or the problem you want solved as simply and clearly as possible. 12 | validations: 13 | required: true 14 | 15 | - type: textarea 16 | id: ideal-solution 17 | attributes: 18 | label: Describe the solution you want 19 | description: Describe the new feature you want as simply and clearly as possible. 20 | validations: 21 | required: true 22 | 23 | - type: textarea 24 | id: other-solutions 25 | attributes: 26 | label: Describe the alternatives you considered 27 | description: Describe any alternative solutions or features you considered as simply and clearly as possible. 28 | validations: 29 | required: false 30 | 31 | - type: textarea 32 | id: supplements 33 | attributes: 34 | label: Other Information 35 | description: Add any other information or screenshots about your feature request here. 36 | validations: 37 | required: false 38 | -------------------------------------------------------------------------------- /luci-app-passwall2/root/usr/share/passwall2/lease2hosts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # dhcp.leases to hosts 4 | 5 | CONFIG=passwall2 6 | TMP_PATH=/tmp/etc/${CONFIG} 7 | TMP_PATH2=/tmp/etc/${CONFIG}_tmp 8 | LOCK_FILE=/tmp/lock/${CONFIG}_lease2hosts.lock 9 | LEASE_FILE="/tmp/dhcp.leases" 10 | HOSTS_FILE="$TMP_PATH2/dhcp-hosts" 11 | TMP_FILE="/tmp/dhcp-hosts.tmp" 12 | 13 | exec 99>"$LOCK_FILE" 14 | flock -n 99 15 | if [ "$?" != 0 ]; then 16 | exit 0 17 | fi 18 | 19 | reload_dnsmasq_pids() { 20 | local pidfile pid 21 | find $TMP_PATH/acl -type f -name 'dnsmasq.pid' 2>/dev/null | while read pidfile; do 22 | if [ -s "$pidfile" ]; then 23 | read pid < "$pidfile" 24 | if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then 25 | kill -HUP "$pid" 26 | fi 27 | fi 28 | done 29 | } 30 | 31 | while true; do 32 | 33 | if [ -f "$LEASE_FILE" ]; then 34 | awk 'NF >= 4 && $4 != "*" {print $3" "$4}' "$LEASE_FILE" | sort > "$TMP_FILE" 35 | if [ -s "$TMP_FILE" ]; then 36 | if [ ! -f "$HOSTS_FILE" ] || ! cmp -s "$TMP_FILE" "$HOSTS_FILE"; then 37 | mv "$TMP_FILE" "$HOSTS_FILE" 38 | reload_dnsmasq_pids 39 | else 40 | rm -f "$TMP_FILE" 41 | fi 42 | else 43 | if [ -s "$HOSTS_FILE" ]; then 44 | : > "$HOSTS_FILE" 45 | reload_dnsmasq_pids 46 | fi 47 | rm -f "$TMP_FILE" 48 | fi 49 | fi 50 | 51 | sleep 60 52 | 53 | done 2>/dev/null 54 | -------------------------------------------------------------------------------- /luci-app-passwall2/root/usr/share/passwall2/monitor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CONFIG=passwall2 4 | TMP_PATH=/tmp/etc/$CONFIG 5 | TMP_SCRIPT_FUNC_PATH=$TMP_PATH/script_func 6 | LOCK_FILE_DIR=/tmp/lock 7 | LOCK_FILE=${LOCK_FILE_DIR}/${CONFIG}_script.lock 8 | 9 | config_n_get() { 10 | local ret=$(uci -q get $CONFIG.$1.$2 2>/dev/null) 11 | echo ${ret:=$3} 12 | } 13 | 14 | config_t_get() { 15 | local index=0 16 | [ -n "$4" ] && index=$4 17 | local ret=$(uci -q get $CONFIG.@$1[$index].$2 2>/dev/null) 18 | echo ${ret:=$3} 19 | } 20 | 21 | ENABLED=$(config_t_get global enabled 0) 22 | [ "$ENABLED" != 1 ] && return 1 23 | ENABLED=$(config_t_get global_delay start_daemon 0) 24 | [ "$ENABLED" != 1 ] && return 1 25 | sleep 58s 26 | while [ "$ENABLED" -eq 1 ]; do 27 | [ -f "$LOCK_FILE" ] && { 28 | sleep 6s 29 | continue 30 | } 31 | touch $LOCK_FILE 32 | [ -d ${TMP_SCRIPT_FUNC_PATH} ] && { 33 | for filename in $(ls ${TMP_SCRIPT_FUNC_PATH} | grep -v "^_"); do 34 | cmd=$(cat ${TMP_SCRIPT_FUNC_PATH}/${filename}) 35 | cmd_check=$(echo $cmd | awk -F '>' '{print $1}') 36 | [ -n "$(echo $cmd_check | grep "dns2socks")" ] && cmd_check=$(echo $cmd_check | sed "s#:# #g") 37 | icount=$(pgrep -f "$(echo $cmd_check)" | wc -l) 38 | if [ $icount = 0 ]; then 39 | #echo "${cmd} Process hangs,Restart" >> /tmp/log/passwall2.log 40 | eval $(echo "nohup ${cmd} 2>&1 &") >/dev/null 2>&1 & 41 | fi 42 | done 43 | } 44 | 45 | rm -f $LOCK_FILE 46 | sleep 58s 47 | done 48 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/view/passwall2/socks_auto_switch/btn.htm: -------------------------------------------------------------------------------- 1 | <% 2 | local api = require "luci.passwall2.api" 3 | -%> 4 | 5 | 22 |
" data-index="<%=self.index%>" data-depends="<%=pcdata(self:deplist2json(section))%>"> 23 | 24 |
25 | 26 | 27 |
28 |
29 | -------------------------------------------------------------------------------- /luci-app-passwall2/root/etc/init.d/passwall2: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | 3 | START=99 4 | STOP=15 5 | 6 | CONFIG=passwall2 7 | APP_FILE=/usr/share/${CONFIG}/app.sh 8 | LOCK_FILE_DIR=/var/lock 9 | LOCK_FILE=${LOCK_FILE_DIR}/${CONFIG}.lock 10 | 11 | set_lock() { 12 | [ ! -d "$LOCK_FILE_DIR" ] && mkdir -p $LOCK_FILE_DIR 13 | exec 999>"$LOCK_FILE" 14 | flock -xn 999 15 | } 16 | 17 | unset_lock() { 18 | flock -u 999 19 | rm -rf "$LOCK_FILE" 20 | } 21 | 22 | unlock() { 23 | failcount=1 24 | while [ "$failcount" -le 10 ]; do 25 | if [ -f "$LOCK_FILE" ]; then 26 | let "failcount++" 27 | sleep 1s 28 | [ "$failcount" -ge 10 ] && unset_lock 29 | else 30 | break 31 | fi 32 | done 33 | } 34 | 35 | boot_func() { 36 | local delay=$(uci -q get ${CONFIG}.@global_delay[0].start_delay || echo 1) 37 | if [ "$delay" -gt 0 ]; then 38 | $APP_FILE echolog "执行启动延时 $delay 秒后再启动!" 39 | sleep $delay 40 | fi 41 | restart 42 | touch ${LOCK_FILE_DIR}/${CONFIG}_ready.lock 43 | } 44 | 45 | boot() { 46 | boot_func >/dev/null 2>&1 & 47 | } 48 | 49 | start() { 50 | set_lock 51 | [ $? == 1 ] && $APP_FILE echolog "脚本已经在运行,不重复运行,退出." && exit 0 52 | $APP_FILE start 53 | unset_lock 54 | } 55 | 56 | stop() { 57 | unlock 58 | set_lock 59 | [ $? == 1 ] && $APP_FILE echolog "停止脚本等待超时,不重复运行,退出." && exit 0 60 | $APP_FILE stop 61 | unset_lock 62 | } 63 | 64 | restart() { 65 | set_lock 66 | [ $? == 1 ] && $APP_FILE echolog "脚本已经在运行,不重复运行,退出." && exit 0 67 | $APP_FILE stop 68 | $APP_FILE start 69 | unset_lock 70 | } 71 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/view/passwall2/server/users_list_status.htm: -------------------------------------------------------------------------------- 1 | <% 2 | local api = require "luci.passwall2.api" 3 | -%> 4 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_config.lua: -------------------------------------------------------------------------------- 1 | local api = require "luci.passwall2.api" 2 | local appname = api.appname 3 | 4 | m = Map(appname, translate("Node Config")) 5 | m.redirect = api.url() 6 | api.set_apply_on_parse(m) 7 | 8 | if not arg[1] or not m:get(arg[1]) then 9 | luci.http.redirect(api.url("node_list")) 10 | end 11 | 12 | s = m:section(NamedSection, arg[1], "nodes", "") 13 | s.addremove = false 14 | s.dynamic = false 15 | 16 | o = s:option(DummyValue, "passwall2", " ") 17 | o.rawhtml = true 18 | o.template = "passwall2/node_list/link_share_man" 19 | o.value = arg[1] 20 | 21 | o = s:option(Value, "remarks", translate("Node Remarks")) 22 | o.default = translate("Remarks") 23 | o.rmempty = false 24 | 25 | o = s:option(Value, "group", translate("Group Name")) 26 | o.default = "" 27 | o:value("", translate("default")) 28 | local groups = {} 29 | m.uci:foreach(appname, "nodes", function(s) 30 | if s[".name"] ~= arg[1] then 31 | if s.group and s.group ~= "" then 32 | groups[s.group] = true 33 | end 34 | end 35 | end) 36 | for k, v in pairs(groups) do 37 | o:value(k) 38 | end 39 | 40 | local fs = require "nixio.fs" 41 | local types_dir = "/usr/lib/lua/luci/model/cbi/passwall2/client/type/" 42 | 43 | o = s:option(ListValue, "type", translate("Type")) 44 | 45 | local type_table = {} 46 | for filename in fs.dir(types_dir) do 47 | table.insert(type_table, filename) 48 | end 49 | table.sort(type_table) 50 | 51 | for index, value in ipairs(type_table) do 52 | local p_func = loadfile(types_dir .. value) 53 | setfenv(p_func, getfenv(1))(m, s) 54 | end 55 | 56 | return m 57 | -------------------------------------------------------------------------------- /translate_files.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import time 4 | from deep_translator import GoogleTranslator 5 | from multiprocessing import Pool, cpu_count 6 | 7 | target_folder = "luci-app-passwall2/root/usr/share/passwall2" 8 | 9 | translator = GoogleTranslator(source='zh-CN', target='en') 10 | 11 | chinese_pattern = re.compile(r'[\u4e00-\u9fff]+') 12 | 13 | def translate_file(file_path): 14 | print(f"Translating file: {file_path}") 15 | with open(file_path, "r", encoding="utf-8") as f: 16 | content = f.read() 17 | 18 | def translate_match(match): 19 | try: 20 | return translator.translate(match.group(0)) 21 | except Exception as e: 22 | print(f"Error translating text: {match.group(0)}. Skipping...") 23 | return match.group(0) 24 | 25 | translated_content = chinese_pattern.sub(translate_match, content) 26 | 27 | with open(file_path, "w", encoding="utf-8") as f: 28 | f.write(translated_content) 29 | 30 | def process_files(files): 31 | with Pool(cpu_count()) as pool: 32 | pool.map(translate_file, files) 33 | 34 | if __name__ == "__main__": 35 | files_to_translate = [] 36 | for root, _, files in os.walk(target_folder): 37 | for file_name in files: 38 | file_path = os.path.join(root, file_name) 39 | if file_path.endswith((".txt", ".log", ".json", ".lua", ".sh")): 40 | files_to_translate.append(file_path) 41 | 42 | print(f"Found {len(files_to_translate)} files to translate.") 43 | process_files(files_to_translate) 44 | print("Translation completed.") 45 | -------------------------------------------------------------------------------- /luci-app-passwall2/root/etc/uci-defaults/luci-passwall2: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | uci -q batch <<-EOF >/dev/null 4 | set dhcp.@dnsmasq[0].localuse=1 5 | commit dhcp 6 | [ -e "/etc/config/ucitrack" ] && { 7 | delete ucitrack.@passwall2[-1] 8 | add ucitrack passwall2 9 | set ucitrack.@passwall2[-1].init=passwall2 10 | commit ucitrack 11 | } 12 | delete firewall.passwall2 13 | set firewall.passwall2=include 14 | set firewall.passwall2.type=script 15 | set firewall.passwall2.path=/var/etc/passwall2.include 16 | set firewall.passwall2.reload=1 17 | commit firewall 18 | [ -e "/etc/config/ucitrack" ] && { 19 | delete ucitrack.@passwall2_server[-1] 20 | add ucitrack passwall2_server 21 | set ucitrack.@passwall2_server[-1].init=passwall2_server 22 | commit ucitrack 23 | } 24 | delete firewall.passwall2_server 25 | set firewall.passwall2_server=include 26 | set firewall.passwall2_server.type=script 27 | set firewall.passwall2_server.path=/var/etc/passwall2_server.include 28 | set firewall.passwall2_server.reload=1 29 | commit firewall 30 | set uhttpd.main.max_requests=50 31 | commit uhttpd 32 | EOF 33 | 34 | [ ! -s "/etc/config/passwall2" ] && cp -f /usr/share/passwall2/0_default_config /etc/config/passwall2 35 | 36 | chmod +x /usr/share/passwall2/*.sh 37 | 38 | [ "$(uci -q get passwall2.@global_xray[0].sniffing)" == "1" ] && [ "$(uci -q get passwall2.@global_xray[0].route_only)" != "1" ] && uci -q set passwall2.@global_xray[0].sniffing_override_dest=1 39 | uci -q delete passwall2.@global_xray[0].sniffing 40 | uci -q delete passwall2.@global_xray[0].route_only 41 | uci -q commit passwall2 42 | 43 | sed -i "s#add_from#group#g" /etc/config/passwall2 2>/dev/null 44 | 45 | rm -f /tmp/luci-indexcache 46 | rm -rf /tmp/luci-modulecache/ 47 | killall -HUP rpcd 2>/dev/null 48 | 49 | exit 0 50 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl.lua: -------------------------------------------------------------------------------- 1 | local api = require "luci.passwall2.api" 2 | local appname = api.appname 3 | local sys = api.sys 4 | 5 | m = Map(appname) 6 | api.set_apply_on_parse(m) 7 | 8 | s = m:section(TypedSection, "global", translate("ACLs"), "" .. translate("ACLs is a tools which used to designate specific IP proxy mode.") .. "") 9 | s.anonymous = true 10 | 11 | o = s:option(Flag, "acl_enable", translate("Main switch")) 12 | o.rmempty = false 13 | o.default = false 14 | 15 | -- [[ ACLs Settings ]]-- 16 | s = m:section(TypedSection, "acl_rule") 17 | s.template = "cbi/tblsection" 18 | s.sortable = true 19 | s.anonymous = true 20 | s.addremove = true 21 | s.extedit = api.url("acl_config", "%s") 22 | function s.create(e, t) 23 | t = TypedSection.create(e, t) 24 | luci.http.redirect(e.extedit:format(t)) 25 | end 26 | 27 | ---- Enable 28 | o = s:option(Flag, "enabled", translate("Enable")) 29 | o.default = 1 30 | o.rmempty = false 31 | 32 | ---- Remarks 33 | o = s:option(Value, "remarks", translate("Remarks")) 34 | o.rmempty = true 35 | 36 | local mac_t = {} 37 | sys.net.mac_hints(function(e, t) 38 | mac_t[e] = { 39 | ip = t, 40 | mac = e 41 | } 42 | end) 43 | 44 | o = s:option(DummyValue, "sources", translate("Source")) 45 | o.rawhtml = true 46 | o.cfgvalue = function(t, n) 47 | local e = '' 48 | local v = Value.cfgvalue(t, n) or '-' 49 | string.gsub(v, '[^' .. " " .. ']+', function(w) 50 | local a = w 51 | if mac_t[w] then 52 | a = a .. ' (' .. mac_t[w].ip .. ')' 53 | end 54 | if #e > 0 then 55 | e = e .. "
" 56 | end 57 | e = e .. a 58 | end) 59 | return e 60 | end 61 | 62 | i = s:option(DummyValue, "interface", translate("Source Interface")) 63 | i.cfgvalue = function(t, n) 64 | local v = Value.cfgvalue(t, n) or '-' 65 | return v 66 | end 67 | 68 | return m 69 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/passwall2/com.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | local function gh_release_url(self) 4 | --return "https://api.github.com/repos/" .. self.repo .. "/releases/latest" 5 | return "https://github.com/xiaorouji/openwrt-passwall-packages/releases/download/api-cache/" .. string.lower(self.name) .. "-release-api.json" 6 | end 7 | 8 | local function gh_pre_release_url(self) 9 | --return "https://api.github.com/repos/" .. self.repo .. "/releases?per_page=1" 10 | return "https://github.com/xiaorouji/openwrt-passwall-packages/releases/download/api-cache/" .. string.lower(self.name) .. "-pre-release-api.json" 11 | end 12 | 13 | _M.hysteria = { 14 | name = "Hysteria", 15 | repo = "HyNetwork/hysteria", 16 | get_url = gh_release_url, 17 | cmd_version = "version | awk '/^Version:/ {print $2}'", 18 | remote_version_str_replace = "app/", 19 | zipped = false, 20 | default_path = "/usr/bin/hysteria", 21 | match_fmt_str = "linux%%-%s$", 22 | file_tree = { 23 | armv6 = "arm", 24 | armv7 = "arm" 25 | } 26 | } 27 | 28 | _M["sing-box"] = { 29 | name = "Sing-Box", 30 | repo = "SagerNet/sing-box", 31 | get_url = gh_release_url, 32 | cmd_version = "version | awk '{print $3}' | sed -n 1P", 33 | zipped = true, 34 | zipped_suffix = "tar.gz", 35 | default_path = "/usr/bin/sing-box", 36 | match_fmt_str = "linux%%-%s", 37 | file_tree = { 38 | x86_64 = "amd64", 39 | mips64el = "mips64le" 40 | } 41 | } 42 | 43 | _M.xray = { 44 | name = "Xray", 45 | repo = "XTLS/Xray-core", 46 | get_url = gh_pre_release_url, 47 | cmd_version = "version | awk '{print $2}' | sed -n 1P", 48 | zipped = true, 49 | default_path = "/usr/bin/xray", 50 | match_fmt_str = "linux%%-%s", 51 | file_tree = { 52 | x86_64 = "64", 53 | x86 = "32", 54 | mips = "mips32", 55 | mipsel = "mips32le", 56 | mips64el = "mips64le" 57 | } 58 | } 59 | 60 | return _M 61 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/view/passwall2/rule/rule_version.htm: -------------------------------------------------------------------------------- 1 | <% 2 | local api = require "luci.passwall2.api" 3 | 4 | local geoip_update = api.uci_get_type("global_rules", "geoip_update", "1") == "1" and "checked='checked'" or "" 5 | local geosite_update = api.uci_get_type("global_rules", "geosite_update", "1") == "1" and "checked='checked'" or "" 6 | -%> 7 | 8 | 39 |
40 | 43 |
44 |
45 | 49 | 53 | 54 |
55 |
56 |
57 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/passwall2/util_tuic.lua: -------------------------------------------------------------------------------- 1 | module("luci.passwall2.util_tuic", package.seeall) 2 | local api = require "luci.passwall2.api" 3 | local uci = api.uci 4 | local json = api.jsonc 5 | 6 | function gen_config(var) 7 | local node_id = var["-node"] 8 | if not node_id then 9 | print("-node 不能为空") 10 | return 11 | end 12 | local node = uci:get_all("passwall2", node_id) 13 | local local_addr = var["-local_addr"] 14 | local local_port = var["-local_port"] 15 | local server_host = var["-server_host"] or node.address 16 | local server_port = var["-server_port"] or node.port 17 | local loglevel = var["-loglevel"] or "warn" 18 | 19 | local tuic= { 20 | relay = { 21 | server = server_host .. ":" .. server_port, 22 | ip = node.tuic_ip, 23 | uuid = node.uuid, 24 | password = node.tuic_password, 25 | -- certificates = node.tuic_certificate and { node.tuic_certpath } or nil, 26 | udp_relay_mode = node.tuic_udp_relay_mode, 27 | congestion_control = node.tuic_congestion_control, 28 | heartbeat = node.tuic_heartbeat .. "s", 29 | timeout = node.tuic_timeout .. "s", 30 | gc_interval = node.tuic_gc_interval .. "s", 31 | gc_lifetime = node.tuic_gc_lifetime .. "s", 32 | alpn = node.tuic_tls_alpn, 33 | disable_sni = (node.tuic_disable_sni == "1"), 34 | zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1"), 35 | send_window = tonumber(node.tuic_send_window), 36 | receive_window = tonumber(node.tuic_receive_window) 37 | }, 38 | ["local"] = { 39 | server = "[::]:" .. local_port, 40 | username = node.tuic_socks_username, 41 | password = node.tuic_socks_password, 42 | dual_stack = (node.tuic_dual_stack == "1") and true or false, 43 | max_packet_size = tonumber(node.tuic_max_package_size) 44 | }, 45 | log_level = loglevel 46 | } 47 | return json.stringify(tuic, 1) 48 | end 49 | 50 | _G.gen_config = gen_config 51 | 52 | if arg[1] then 53 | local func =_G[arg[1]] 54 | if func then 55 | print(func(api.get_function_args(arg))) 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ss.lua: -------------------------------------------------------------------------------- 1 | local m, s = ... 2 | 3 | local api = require "luci.passwall2.api" 4 | 5 | if not api.is_finded("ss-local") then 6 | return 7 | end 8 | 9 | local type_name = "SS" 10 | 11 | local option_prefix = "ss_" 12 | 13 | local function _n(name) 14 | return option_prefix .. name 15 | end 16 | 17 | local ss_encrypt_method_list = { 18 | "rc4-md5", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", 19 | "aes-192-ctr", "aes-256-ctr", "bf-cfb", "salsa20", "chacha20", "chacha20-ietf", 20 | "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", 21 | "xchacha20-ietf-poly1305" 22 | } 23 | 24 | -- [[ Shadowsocks Libev ]] 25 | 26 | s.fields["type"]:value(type_name, translate("Shadowsocks Libev")) 27 | 28 | o = s:option(Value, _n("address"), translate("Address (Support Domain Name)")) 29 | 30 | o = s:option(Value, _n("port"), translate("Port")) 31 | o.datatype = "port" 32 | 33 | o = s:option(Value, _n("password"), translate("Password")) 34 | o.password = true 35 | 36 | o = s:option(Value, _n("method"), translate("Encrypt Method")) 37 | for a, t in ipairs(ss_encrypt_method_list) do o:value(t) end 38 | 39 | o = s:option(Value, _n("timeout"), translate("Connection Timeout")) 40 | o.datatype = "uinteger" 41 | o.default = 300 42 | 43 | o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required")) 44 | o:value("false") 45 | o:value("true") 46 | 47 | o = s:option(Flag, _n("plugin_enabled"), translate("plugin")) 48 | o.default = 0 49 | 50 | o = s:option(ListValue, _n("plugin"), "SIP003 " .. translate("plugin")) 51 | o.default = "none" 52 | o:value("none", translate("none")) 53 | if api.is_finded("xray-plugin") then o:value("xray-plugin") end 54 | if api.is_finded("v2ray-plugin") then o:value("v2ray-plugin") end 55 | if api.is_finded("obfs-local") then o:value("obfs-local") end 56 | o:depends({ [_n("plugin_enabled")] = true }) 57 | 58 | o = s:option(Value, _n("plugin_opts"), translate("opts")) 59 | o:depends({ [_n("plugin_enabled")] = true }) 60 | 61 | api.luci_types(arg[1], m, s, type_name, option_prefix) 62 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug Reporting 2 | description: Report any Bugs you encounter to help us improve. 3 | title: "[Bug]: " 4 | labels: bug 5 | 6 | body: 7 | - type: markdown 8 | id: tips 9 | attributes: 10 | value: | 11 | Please fill in this specification. Effective feedback will help us better solve your problem. 12 | 13 | - type: textarea 14 | id: description 15 | attributes: 16 | label: Describe the Bug you encountered 17 | description: Please describe the problem as simply and clearly as possible. 18 | validations: 19 | required: true 20 | 21 | - type: textarea 22 | id: steps 23 | attributes: 24 | label: Steps to reproduce this Bug 25 | placeholder: | 26 | 1. Select Menu'...' 27 | 2. Click the button'....' 28 | 3. Then... 29 | 4. Appear: 30 | validations: 31 | required: true 32 | 33 | - type: textarea 34 | id: purpose 35 | attributes: 36 | label: What you want to implement 37 | description: Please describe what you expect to happen as simply and clearly as possible. 38 | validations: 39 | required: true 40 | 41 | - type: textarea 42 | id: logs 43 | attributes: 44 | label: Log information 45 | description: Submit all system log information related to the problem (this is very important). 46 | validations: 47 | required: true 48 | 49 | - type: textarea 50 | id: pics 51 | attributes: 52 | label: Screenshot 53 | description: If applicable, you can add screenshots to better explain your problem. 54 | validations: 55 | required: false 56 | 57 | - type: textarea 58 | id: environment 59 | attributes: 60 | label: System related information 61 | description: Fill in relevant information to better identify the problem. 62 | placeholder: | 63 | - Passwall2 version: 64 | - Browser version (such as Chrome 96.0.4664.45 64-bit official version): 65 | - Other: 66 | validations: 67 | required: true 68 | 69 | - type: textarea 70 | id: supplements 71 | attributes: 72 | label: Other Information 73 | description: Other information related to this Bug. 74 | validations: 75 | required: false 76 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ss-rust.lua: -------------------------------------------------------------------------------- 1 | local m, s = ... 2 | 3 | local api = require "luci.passwall2.api" 4 | 5 | if not api.is_finded("ssserver") then 6 | return 7 | end 8 | 9 | local type_name = "SS-Rust" 10 | 11 | local option_prefix = "ssrust_" 12 | 13 | local function _n(name) 14 | return option_prefix .. name 15 | end 16 | 17 | local ssrust_encrypt_method_list = { 18 | "plain", "none", 19 | "aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", 20 | "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha8-poly1305", "2022-blake3-chacha20-poly1305" 21 | } 22 | 23 | -- [[ Shadowsocks Rust ]] 24 | 25 | s.fields["type"]:value(type_name, translate("Shadowsocks Rust")) 26 | 27 | o = s:option(Flag, _n("custom"), translate("Use Custom Config")) 28 | 29 | o = s:option(Value, _n("port"), translate("Listen Port")) 30 | o.datatype = "port" 31 | o:depends({ [_n("custom")] = false }) 32 | 33 | o = s:option(Value, _n("password"), translate("Password")) 34 | o.password = true 35 | o:depends({ [_n("custom")] = false }) 36 | 37 | o = s:option(ListValue, _n("method"), translate("Encrypt Method")) 38 | for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end 39 | o:depends({ [_n("custom")] = false }) 40 | 41 | o = s:option(Value, _n("timeout"), translate("Connection Timeout")) 42 | o.datatype = "uinteger" 43 | o.default = 300 44 | o:depends({ [_n("custom")] = false }) 45 | 46 | o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open")) 47 | o.default = "0" 48 | o:depends({ [_n("custom")] = false }) 49 | 50 | o = s:option(TextValue, _n("custom_config"), translate("Custom Config")) 51 | o.rows = 10 52 | o.wrap = "off" 53 | o:depends({ [_n("custom")] = true }) 54 | o.validate = function(self, value, t) 55 | if value and api.jsonc.parse(value) then 56 | return value 57 | else 58 | return nil, translate("Must be JSON text!") 59 | end 60 | end 61 | o.custom_cfgvalue = function(self, section, value) 62 | local config_str = m:get(section, "config_str") 63 | if config_str then 64 | return api.base64Decode(config_str) 65 | end 66 | end 67 | o.custom_write = function(self, section, value) 68 | m:set(section, "config_str", api.base64Encode(value)) 69 | end 70 | 71 | o = s:option(Flag, _n("log"), translate("Log")) 72 | o.default = "1" 73 | o.rmempty = false 74 | 75 | api.luci_types(arg[1], m, s, type_name, option_prefix) 76 | -------------------------------------------------------------------------------- /luci-app-passwall2/root/usr/share/passwall2/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CONFIG=passwall2 4 | LOG_FILE=/tmp/log/$CONFIG.log 5 | 6 | echolog() { 7 | local d="$(date "+%Y-%m-%d %H:%M:%S")" 8 | #echo -e "$d: $1" 9 | echo -e "$d: $1" >> $LOG_FILE 10 | } 11 | 12 | config_n_get() { 13 | local ret=$(uci -q get "${CONFIG}.${1}.${2}" 2>/dev/null) 14 | echo "${ret:=$3}" 15 | } 16 | 17 | config_t_get() { 18 | local index=0 19 | [ -n "$4" ] && index=$4 20 | local ret=$(uci -q get $CONFIG.@$1[$index].$2 2>/dev/null) 21 | echo ${ret:=$3} 22 | } 23 | 24 | test_url() { 25 | local url=$1 26 | local try=1 27 | [ -n "$2" ] && try=$2 28 | local timeout=2 29 | [ -n "$3" ] && timeout=$3 30 | local extra_params=$4 31 | curl --help all | grep "\-\-retry-all-errors" > /dev/null 32 | [ $? == 0 ] && extra_params="--retry-all-errors ${extra_params}" 33 | status=$(/usr/bin/curl -I -o /dev/null -skL $extra_params --connect-timeout ${timeout} --retry ${try} -w %{http_code} "$url") 34 | case "$status" in 35 | 204|\ 36 | 200) 37 | status=200 38 | ;; 39 | esac 40 | echo $status 41 | } 42 | 43 | test_proxy() { 44 | result=0 45 | status=$(test_url "https://www.google.com/generate_204" ${retry_num} ${connect_timeout}) 46 | if [ "$status" = "200" ]; then 47 | result=0 48 | else 49 | status2=$(test_url "https://www.baidu.com" ${retry_num} ${connect_timeout}) 50 | if [ "$status2" = "200" ]; then 51 | result=1 52 | else 53 | result=2 54 | ping -c 3 -W 1 223.5.5.5 > /dev/null 2>&1 55 | [ $? -eq 0 ] && { 56 | result=1 57 | } 58 | fi 59 | fi 60 | echo $result 61 | } 62 | 63 | url_test_node() { 64 | result=0 65 | local node_id=$1 66 | local _type=$(echo $(config_n_get ${node_id} type) | tr 'A-Z' 'a-z') 67 | [ -n "${_type}" ] && { 68 | local _tmp_port=$(/usr/share/${CONFIG}/app.sh get_new_port 61080 tcp,udp) 69 | /usr/share/${CONFIG}/app.sh run_socks flag="url_test_${node_id}" node=${node_id} bind=127.0.0.1 socks_port=${_tmp_port} config_file=url_test_${node_id}.json 70 | local curlx="socks5h://127.0.0.1:${_tmp_port}" 71 | sleep 1s 72 | result=$(curl --connect-timeout 3 -o /dev/null -I -skL -w "%{http_code}:%{time_starttransfer}" -x $curlx "https://www.google.com/generate_204") 73 | pgrep -af "url_test_${node_id}" | awk '! /test\.sh/{print $1}' | xargs kill -9 >/dev/null 2>&1 74 | rm -rf /tmp/etc/${CONFIG}/*url_test_${node_id}*.json 75 | } 76 | echo $result 77 | } 78 | 79 | arg1=$1 80 | shift 81 | case $arg1 in 82 | test_url) 83 | test_url $@ 84 | ;; 85 | url_test_node) 86 | url_test_node $@ 87 | ;; 88 | esac 89 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ssr.lua: -------------------------------------------------------------------------------- 1 | local m, s = ... 2 | 3 | local api = require "luci.passwall2.api" 4 | 5 | if not api.is_finded("ssr-local") then 6 | return 7 | end 8 | 9 | local type_name = "SSR" 10 | 11 | local option_prefix = "ssr_" 12 | 13 | local function _n(name) 14 | return option_prefix .. name 15 | end 16 | 17 | local ssr_encrypt_method_list = { 18 | "none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb", 19 | "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", 20 | "bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", 21 | "cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20", 22 | "chacha20-ietf" 23 | } 24 | 25 | local ssr_protocol_list = { 26 | "origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple", 27 | "auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5", 28 | "auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c", 29 | "auth_chain_d", "auth_chain_e", "auth_chain_f" 30 | } 31 | local ssr_obfs_list = { 32 | "plain", "http_simple", "http_post", "random_head", "tls_simple", 33 | "tls1.0_session_auth", "tls1.2_ticket_auth" 34 | } 35 | 36 | -- [[ ShadowsocksR Libev ]] 37 | 38 | s.fields["type"]:value(type_name, translate("ShadowsocksR Libev")) 39 | 40 | o = s:option(Value, _n("address"), translate("Address (Support Domain Name)")) 41 | 42 | o = s:option(Value, _n("port"), translate("Port")) 43 | o.datatype = "port" 44 | 45 | o = s:option(Value, _n("password"), translate("Password")) 46 | o.password = true 47 | 48 | o = s:option(ListValue, _n("method"), translate("Encrypt Method")) 49 | for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end 50 | 51 | o = s:option(ListValue, _n("protocol"), translate("Protocol")) 52 | for a, t in ipairs(ssr_protocol_list) do o:value(t) end 53 | 54 | o = s:option(Value, _n("protocol_param"), translate("Protocol_param")) 55 | 56 | o = s:option(ListValue, _n("obfs"), translate("Obfs")) 57 | for a, t in ipairs(ssr_obfs_list) do o:value(t) end 58 | 59 | o = s:option(Value, _n("obfs_param"), translate("Obfs_param")) 60 | 61 | o = s:option(Value, _n("timeout"), translate("Connection Timeout")) 62 | o.datatype = "uinteger" 63 | o.default = 300 64 | 65 | o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required")) 66 | o:value("false") 67 | o:value("true") 68 | 69 | api.luci_types(arg[1], m, s, type_name, option_prefix) 70 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ss.lua: -------------------------------------------------------------------------------- 1 | local m, s = ... 2 | 3 | local api = require "luci.passwall2.api" 4 | 5 | if not api.is_finded("ss-server") then 6 | return 7 | end 8 | 9 | local type_name = "SS" 10 | 11 | local option_prefix = "ss_" 12 | 13 | local function _n(name) 14 | return option_prefix .. name 15 | end 16 | 17 | local ss_encrypt_method_list = { 18 | "rc4-md5", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", 19 | "aes-192-ctr", "aes-256-ctr", "bf-cfb", "camellia-128-cfb", 20 | "camellia-192-cfb", "camellia-256-cfb", "salsa20", "chacha20", 21 | "chacha20-ietf", -- aead 22 | "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", 23 | "xchacha20-ietf-poly1305" 24 | } 25 | 26 | -- [[ Shadowsocks ]] 27 | 28 | s.fields["type"]:value(type_name, translate("Shadowsocks")) 29 | 30 | o = s:option(Flag, _n("custom"), translate("Use Custom Config")) 31 | 32 | o = s:option(Value, _n("port"), translate("Listen Port")) 33 | o.datatype = "port" 34 | o:depends({ [_n("custom")] = false }) 35 | 36 | o = s:option(Value, _n("password"), translate("Password")) 37 | o.password = true 38 | o:depends({ [_n("custom")] = false }) 39 | 40 | o = s:option(ListValue, _n("method"), translate("Encrypt Method")) 41 | for a, t in ipairs(ss_encrypt_method_list) do o:value(t) end 42 | o:depends({ [_n("custom")] = false }) 43 | 44 | o = s:option(Value, _n("timeout"), translate("Connection Timeout")) 45 | o.datatype = "uinteger" 46 | o.default = 300 47 | o:depends({ [_n("custom")] = false }) 48 | 49 | o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open")) 50 | o.default = "0" 51 | o:depends({ [_n("custom")] = false }) 52 | 53 | o = s:option(TextValue, _n("custom_config"), translate("Custom Config")) 54 | o.rows = 10 55 | o.wrap = "off" 56 | o:depends({ [_n("custom")] = true }) 57 | o.validate = function(self, value, t) 58 | if value and api.jsonc.parse(value) then 59 | return value 60 | else 61 | return nil, translate("Must be JSON text!") 62 | end 63 | end 64 | o.custom_cfgvalue = function(self, section, value) 65 | local config_str = m:get(section, "config_str") 66 | if config_str then 67 | return api.base64Decode(config_str) 68 | end 69 | end 70 | o.custom_write = function(self, section, value) 71 | m:set(section, "config_str", api.base64Encode(value)) 72 | end 73 | 74 | o = s:option(Flag, _n("log"), translate("Log")) 75 | o.default = "1" 76 | o.rmempty = false 77 | 78 | api.luci_types(arg[1], m, s, type_name, option_prefix) 79 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ss-rust.lua: -------------------------------------------------------------------------------- 1 | local m, s = ... 2 | 3 | local api = require "luci.passwall2.api" 4 | 5 | if not api.is_finded("sslocal") then 6 | return 7 | end 8 | 9 | local type_name = "SS-Rust" 10 | 11 | local option_prefix = "ssrust_" 12 | 13 | local function _n(name) 14 | return option_prefix .. name 15 | end 16 | 17 | local ssrust_encrypt_method_list = { 18 | "none", "plain", 19 | "aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", 20 | "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha8-poly1305", "2022-blake3-chacha20-poly1305" 21 | } 22 | 23 | -- [[ Shadowsocks Rust ]] 24 | 25 | s.fields["type"]:value(type_name, translate("Shadowsocks Rust")) 26 | 27 | o = s:option(Value, _n("address"), translate("Address (Support Domain Name)")) 28 | 29 | o = s:option(Value, _n("port"), translate("Port")) 30 | o.datatype = "port" 31 | 32 | o = s:option(Value, _n("password"), translate("Password")) 33 | o.password = true 34 | 35 | o = s:option(Value, _n("method"), translate("Encrypt Method")) 36 | for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end 37 | 38 | o = s:option(Value, _n("timeout"), translate("Connection Timeout")) 39 | o.datatype = "uinteger" 40 | o.default = 300 41 | 42 | o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required")) 43 | o:value("false") 44 | o:value("true") 45 | 46 | o = s:option(Flag, _n("plugin_enabled"), translate("plugin")) 47 | o.default = 0 48 | 49 | o = s:option(Value, _n("plugin"), "SIP003 " .. translate("plugin"), translate("Supports custom SIP003 plugins, Make sure the plugin is installed.")) 50 | o.default = "none" 51 | o:value("none", translate("none")) 52 | if api.is_finded("xray-plugin") then o:value("xray-plugin") end 53 | if api.is_finded("v2ray-plugin") then o:value("v2ray-plugin") end 54 | if api.is_finded("obfs-local") then o:value("obfs-local") end 55 | if api.is_finded("shadow-tls") then o:value("shadow-tls") end 56 | o:depends({ [_n("plugin_enabled")] = true }) 57 | o.validate = function(self, value, t) 58 | if value and value ~= "" and value ~= "none" then 59 | if not api.is_finded(value) then 60 | return nil, value .. ": " .. translate("Can't find this file!") 61 | else 62 | return value 63 | end 64 | end 65 | return nil 66 | end 67 | 68 | o = s:option(Value, _n("plugin_opts"), translate("opts")) 69 | o:depends({ [_n("plugin_enabled")] = true }) 70 | 71 | api.luci_types(arg[1], m, s, type_name, option_prefix) 72 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/server/index.lua: -------------------------------------------------------------------------------- 1 | local api = require "luci.passwall2.api" 2 | 3 | m = Map("passwall2_server", translate("Server-Side")) 4 | api.set_apply_on_parse(m) 5 | 6 | t = m:section(NamedSection, "global", "global") 7 | t.anonymous = true 8 | t.addremove = false 9 | 10 | e = t:option(Flag, "enable", translate("Enable")) 11 | e.rmempty = false 12 | 13 | t = m:section(TypedSection, "user", translate("Users Manager")) 14 | t.anonymous = true 15 | t.addremove = true 16 | t.sortable = true 17 | t.template = "cbi/tblsection" 18 | t.extedit = api.url("server_user", "%s") 19 | function t.create(e, t) 20 | local uuid = api.gen_uuid() 21 | t = uuid 22 | TypedSection.create(e, t) 23 | luci.http.redirect(e.extedit:format(t)) 24 | end 25 | function t.remove(e, t) 26 | e.map.proceed = true 27 | e.map:del(t) 28 | luci.http.redirect(api.url("server")) 29 | end 30 | 31 | e = t:option(Flag, "enable", translate("Enable")) 32 | e.width = "5%" 33 | e.rmempty = false 34 | 35 | e = t:option(DummyValue, "status", translate("Status")) 36 | e.rawhtml = true 37 | e.cfgvalue = function(t, n) 38 | return string.format('%s', translate("Collecting data...")) 39 | end 40 | 41 | e = t:option(DummyValue, "remarks", translate("Remarks")) 42 | e.width = "15%" 43 | 44 | e = t:option(DummyValue, "type", translate("Type")) 45 | e.width = "20%" 46 | e.rawhtml = true 47 | e.cfgvalue = function(t, n) 48 | local str = "" 49 | local type = m:get(n, "type") or "" 50 | if type == "sing-box" or type == "Xray" then 51 | local protocol = m:get(n, "protocol") or "" 52 | if protocol == "vmess" then 53 | protocol = "VMess" 54 | elseif protocol == "vless" then 55 | protocol = "VLESS" 56 | elseif protocol == "shadowsocks" then 57 | protocol = "SS" 58 | elseif protocol == "shadowsocksr" then 59 | protocol = "SSR" 60 | elseif protocol == "wireguard" then 61 | protocol = "WG" 62 | elseif protocol == "hysteria" then 63 | protocol = "HY" 64 | elseif protocol == "hysteria2" then 65 | protocol = "HY2" 66 | elseif protocol == "anytls" then 67 | protocol = "AnyTLS" 68 | else 69 | protocol = protocol:gsub("^%l",string.upper) 70 | local custom = m:get(n, "custom") or "0" 71 | if custom == "1" then 72 | protocol = translate("Custom Config") 73 | end 74 | end 75 | if type == "sing-box" then type = "Sing-Box" end 76 | type = type .. " " .. protocol 77 | end 78 | str = str .. translate(type) 79 | return str 80 | end 81 | 82 | e = t:option(DummyValue, "port", translate("Port")) 83 | 84 | e = t:option(Flag, "log", translate("Log")) 85 | e.default = "1" 86 | e.rmempty = false 87 | 88 | m:append(Template("passwall2/server/log")) 89 | 90 | m:append(Template("passwall2/server/users_list_status")) 91 | return m 92 | 93 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/hysteria2.lua: -------------------------------------------------------------------------------- 1 | local m, s = ... 2 | 3 | local api = require "luci.passwall2.api" 4 | 5 | if not api.finded_com("hysteria") then 6 | return 7 | end 8 | 9 | local type_name = "Hysteria2" 10 | 11 | local option_prefix = "hysteria2_" 12 | 13 | local function _n(name) 14 | return option_prefix .. name 15 | end 16 | 17 | -- [[ Hysteria2 ]] 18 | 19 | s.fields["type"]:value(type_name, "Hysteria2") 20 | 21 | o = s:option(ListValue, _n("protocol"), translate("Protocol")) 22 | o:value("udp", "UDP") 23 | 24 | o = s:option(Value, _n("address"), translate("Address (Support Domain Name)")) 25 | 26 | o = s:option(Value, _n("port"), translate("Port")) 27 | o.datatype = "port" 28 | 29 | o = s:option(Value, _n("hop"), translate("Port hopping range")) 30 | o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).") 31 | o.rewrite_option = o.option 32 | 33 | o = s:option(Value, _n("hop_interval"), translate("Hop Interval"), translate("Example:") .. "30s (≥5s)") 34 | o.placeholder = "30s" 35 | o.default = "30s" 36 | o.rewrite_option = o.option 37 | 38 | o = s:option(Value, _n("obfs"), translate("Obfs Password")) 39 | o.rewrite_option = o.option 40 | 41 | o = s:option(Value, _n("auth_password"), translate("Auth Password")) 42 | o.password = true 43 | o.rewrite_option = o.option 44 | 45 | o = s:option(Flag, _n("fast_open"), translate("Fast Open")) 46 | o.default = "0" 47 | 48 | o = s:option(Value, _n("tls_serverName"), translate("Domain")) 49 | 50 | o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped.")) 51 | o.default = "0" 52 | 53 | o = s:option(Value, _n("tls_pinSHA256"), translate("PinSHA256"),translate("Certificate fingerprint")) 54 | o.rewrite_option = o.option 55 | 56 | o = s:option(Value, _n("up_mbps"), translate("Max upload Mbps")) 57 | o.rewrite_option = o.option 58 | 59 | o = s:option(Value, _n("down_mbps"), translate("Max download Mbps")) 60 | o.rewrite_option = o.option 61 | 62 | o = s:option(Value, _n("recv_window"), translate("QUIC stream receive window")) 63 | o.rewrite_option = o.option 64 | 65 | o = s:option(Value, _n("recv_window_conn"), translate("QUIC connection receive window")) 66 | o.rewrite_option = o.option 67 | 68 | o = s:option(Value, _n("idle_timeout"), translate("Idle Timeout"), translate("Example:") .. "30s (4s-120s)") 69 | o.rewrite_option = o.option 70 | 71 | o = s:option(Flag, _n("disable_mtu_discovery"), translate("Disable MTU detection")) 72 | o.default = "0" 73 | o.rewrite_option = o.option 74 | 75 | o = s:option(Flag, _n("lazy_start"), translate("Lazy Start")) 76 | o.default = "0" 77 | o.rewrite_option = o.option 78 | 79 | api.luci_types(arg[1], m, s, type_name, option_prefix) 80 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/view/passwall2/global/faq.htm: -------------------------------------------------------------------------------- 1 | <% 2 | local api = require "luci.passwall2.api" 3 | -%> 4 | 31 |
32 |
33 | 44 |
45 |
46 |
47 | 48 | 67 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/view/passwall2/node_subscribe/js.htm: -------------------------------------------------------------------------------- 1 | <% 2 | local api = require "luci.passwall2.api" 3 | -%> 4 | 112 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/view/passwall2/rule/geoview.htm: -------------------------------------------------------------------------------- 1 | <% 2 | local api = require "luci.passwall2.api" 3 | -%> 4 | 5 | 17 | 18 |
19 | 25 |
26 |
27 |
28 | 29 | 32 |
33 |
34 | <%:Enter a domain or IP to query the Geo rule list they belong to.%> 35 |
36 |
37 |
38 |
39 |
40 | 41 | 44 |
45 |
46 | <%:Enter a GeoIP or Geosite to extract the domains/IPs they contain. Format: geoip:cn or geosite:gfw%> 47 |
48 |
49 |
50 |
51 | 52 |
53 | 54 | 97 | -------------------------------------------------------------------------------- /luci-app-passwall2/root/usr/share/passwall2/tasks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## Loop update script 4 | 5 | CONFIG=passwall2 6 | APP_PATH=/usr/share/$CONFIG 7 | TMP_PATH=/tmp/etc/$CONFIG 8 | LOCK_FILE=/tmp/lock/${CONFIG}_tasks.lock 9 | CFG_UPDATE_INT=0 10 | 11 | config_n_get() { 12 | local ret=$(uci -q get "${CONFIG}.${1}.${2}" 2>/dev/null) 13 | echo "${ret:=$3}" 14 | } 15 | 16 | config_t_get() { 17 | local index=${4:-0} 18 | local ret=$(uci -q get "${CONFIG}.@${1}[${index}].${2}" 2>/dev/null) 19 | echo "${ret:=${3}}" 20 | } 21 | 22 | exec 99>"$LOCK_FILE" 23 | flock -n 99 24 | if [ "$?" != 0 ]; then 25 | exit 0 26 | fi 27 | 28 | while true 29 | do 30 | 31 | if [ "$CFG_UPDATE_INT" -ne 0 ]; then 32 | 33 | stop_week_mode=$(config_t_get global_delay stop_week_mode) 34 | stop_interval_mode=$(config_t_get global_delay stop_interval_mode) 35 | stop_interval_mode=$(expr "$stop_interval_mode" \* 60) 36 | if [ -n "$stop_week_mode" ]; then 37 | [ "$stop_week_mode" = "8" ] && { 38 | [ "$(expr "$CFG_UPDATE_INT" % "$stop_interval_mode")" -eq 0 ] && /etc/init.d/$CONFIG stop > /dev/null 2>&1 & 39 | } 40 | fi 41 | 42 | start_week_mode=$(config_t_get global_delay start_week_mode) 43 | start_interval_mode=$(config_t_get global_delay start_interval_mode) 44 | start_interval_mode=$(expr "$start_interval_mode" \* 60) 45 | if [ -n "$start_week_mode" ]; then 46 | [ "$start_week_mode" = "8" ] && { 47 | [ "$(expr "$CFG_UPDATE_INT" % "$start_interval_mode")" -eq 0 ] && /etc/init.d/$CONFIG start > /dev/null 2>&1 & 48 | } 49 | fi 50 | 51 | restart_week_mode=$(config_t_get global_delay restart_week_mode) 52 | restart_interval_mode=$(config_t_get global_delay restart_interval_mode) 53 | restart_interval_mode=$(expr "$restart_interval_mode" \* 60) 54 | if [ -n "$restart_week_mode" ]; then 55 | [ "$restart_week_mode" = "8" ] && { 56 | [ "$(expr "$CFG_UPDATE_INT" % "$restart_interval_mode")" -eq 0 ] && /etc/init.d/$CONFIG restart > /dev/null 2>&1 & 57 | } 58 | fi 59 | 60 | autoupdate=$(config_t_get global_rules auto_update) 61 | weekupdate=$(config_t_get global_rules week_update) 62 | hourupdate=$(config_t_get global_rules interval_update) 63 | hourupdate=$(expr "$hourupdate" \* 60) 64 | if [ "$autoupdate" = "1" ]; then 65 | [ "$weekupdate" = "8" ] && { 66 | [ "$(expr "$CFG_UPDATE_INT" % "$hourupdate")" -eq 0 ] && lua $APP_PATH/rule_update.lua log all cron > /dev/null 2>&1 & 67 | } 68 | fi 69 | 70 | TMP_SUB_PATH=$TMP_PATH/sub_tasks 71 | mkdir -p $TMP_SUB_PATH 72 | for item in $(uci show ${CONFIG} | grep "=subscribe_list" | cut -d '.' -sf 2 | cut -d '=' -sf 1); do 73 | if [ "$(config_n_get $item auto_update 0)" = "1" ]; then 74 | cfgid=$(uci show ${CONFIG}.$item | head -n 1 | cut -d '.' -sf 2 | cut -d '=' -sf 1) 75 | remark=$(config_n_get $item remark) 76 | week_update=$(config_n_get $item week_update) 77 | hour_update=$(config_n_get $item interval_update) 78 | echo "$cfgid" >> $TMP_SUB_PATH/${week_update}_${hour_update} 79 | fi 80 | done 81 | 82 | [ -d "${TMP_SUB_PATH}" ] && { 83 | for name in $(ls ${TMP_SUB_PATH}); do 84 | week_update=$(echo $name | awk -F '_' '{print $1}') 85 | hour_update=$(echo $name | awk -F '_' '{print $2}') 86 | hour_update=$(expr "$hour_update" \* 60) 87 | cfgids=$(echo -n $(cat ${TMP_SUB_PATH}/${name}) | sed 's# #,#g') 88 | [ "$week_update" = "8" ] && { 89 | [ "$(expr "$CFG_UPDATE_INT" % "$hour_update")" -eq 0 ] && lua $APP_PATH/subscribe.lua start $cfgids cron > /dev/null 2>&1 & 90 | } 91 | 92 | done 93 | rm -rf $TMP_SUB_PATH 94 | } 95 | 96 | fi 97 | 98 | CFG_UPDATE_INT=$(expr "$CFG_UPDATE_INT" + 10) 99 | 100 | sleep 600 101 | 102 | done 2>/dev/null 103 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/hysteria2.lua: -------------------------------------------------------------------------------- 1 | local m, s = ... 2 | 3 | local api = require "luci.passwall2.api" 4 | 5 | if not api.finded_com("hysteria") then 6 | return 7 | end 8 | 9 | local type_name = "Hysteria2" 10 | 11 | local option_prefix = "hysteria2_" 12 | 13 | local function _n(name) 14 | return option_prefix .. name 15 | end 16 | 17 | -- [[ Hysteria2 ]] 18 | 19 | s.fields["type"]:value(type_name, "Hysteria2") 20 | 21 | o = s:option(Flag, _n("custom"), translate("Use Custom Config")) 22 | 23 | o = s:option(Value, _n("port"), translate("Listen Port")) 24 | o.datatype = "port" 25 | o:depends({ [_n("custom")] = false }) 26 | 27 | o = s:option(Value, _n("obfs"), translate("Obfs Password")) 28 | o.rewrite_option = o.option 29 | o:depends({ [_n("custom")] = false }) 30 | 31 | o = s:option(Value, _n("auth_password"), translate("Auth Password")) 32 | o.password = true 33 | o.rewrite_option = o.option 34 | o:depends({ [_n("custom")] = false }) 35 | 36 | o = s:option(Flag, _n("udp"), translate("UDP")) 37 | o.default = "1" 38 | o.rewrite_option = o.option 39 | o:depends({ [_n("custom")] = false }) 40 | 41 | o = s:option(Value, _n("up_mbps"), translate("Max upload Mbps")) 42 | o.rewrite_option = o.option 43 | o:depends({ [_n("custom")] = false }) 44 | 45 | o = s:option(Value, _n("down_mbps"), translate("Max download Mbps")) 46 | o.rewrite_option = o.option 47 | o:depends({ [_n("custom")] = false }) 48 | 49 | o = s:option(Flag, _n("ignoreClientBandwidth"), translate("ignoreClientBandwidth")) 50 | o.default = "0" 51 | o.rewrite_option = o.option 52 | o:depends({ [_n("custom")] = false }) 53 | 54 | o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem") 55 | o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem" 56 | if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end 57 | o.validate = function(self, value, t) 58 | if value and value ~= "" then 59 | if not nixio.fs.access(value) then 60 | return nil, translate("Can't find this file!") 61 | else 62 | return value 63 | end 64 | end 65 | return nil 66 | end 67 | o:depends({ [_n("custom")] = false }) 68 | 69 | o = s:option(FileUpload, _n("tls_keyFile"), translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key") 70 | o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key" 71 | if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end 72 | o.validate = function(self, value, t) 73 | if value and value ~= "" then 74 | if not nixio.fs.access(value) then 75 | return nil, translate("Can't find this file!") 76 | else 77 | return value 78 | end 79 | end 80 | return nil 81 | end 82 | o:depends({ [_n("custom")] = false }) 83 | 84 | o = s:option(TextValue, _n("custom_config"), translate("Custom Config")) 85 | o.rows = 10 86 | o.wrap = "off" 87 | o:depends({ [_n("custom")] = true }) 88 | o.validate = function(self, value, t) 89 | if value and api.jsonc.parse(value) then 90 | return value 91 | else 92 | return nil, translate("Must be JSON text!") 93 | end 94 | end 95 | o.custom_cfgvalue = function(self, section, value) 96 | local config_str = m:get(section, "config_str") 97 | if config_str then 98 | return api.base64Decode(config_str) 99 | end 100 | end 101 | o.custom_write = function(self, section, value) 102 | m:set(section, "config_str", api.base64Encode(value)) 103 | end 104 | 105 | o = s:option(Flag, _n("log"), translate("Log")) 106 | o.default = "1" 107 | o.rmempty = false 108 | 109 | api.luci_types(arg[1], m, s, type_name, option_prefix) 110 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/rule.lua: -------------------------------------------------------------------------------- 1 | local api = require "luci.passwall2.api" 2 | local appname = api.appname 3 | 4 | m = Map(appname) 5 | api.set_apply_on_parse(m) 6 | 7 | -- [[ Rule Settings ]]-- 8 | s = m:section(TypedSection, "global_rules", translate("Rule status")) 9 | s.anonymous = true 10 | 11 | o = s:option(Value, "v2ray_location_asset", translate("Location of V2ray/Xray asset"), translate("This variable specifies a directory where geoip.dat and geosite.dat files are.")) 12 | o.default = "/usr/share/v2ray/" 13 | o.rmempty = false 14 | 15 | ---- Custom geo file url 16 | o = s:option(Value, "geoip_url", translate("Custom geoip URL")) 17 | o.default = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest" 18 | o.rmempty = false 19 | 20 | o = s:option(Value, "geosite_url", translate("Custom geosite URL")) 21 | o.default = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest" 22 | o.rmempty = false 23 | ---- 24 | 25 | if api.is_finded("geoview") then 26 | o = s:option(Flag, "enable_geoview", translate("Enable Geo Data Parsing")) 27 | o.default = 0 28 | o.rmempty = false 29 | o.description = "" 34 | end 35 | 36 | s:append(Template(appname .. "/rule/rule_version")) 37 | 38 | ---- Auto Update 39 | o = s:option(Flag, "auto_update", translate("Enable auto update rules")) 40 | o.default = 0 41 | o.rmempty = false 42 | 43 | ---- Week Update 44 | o = s:option(ListValue, "week_update", translate("Update Mode")) 45 | o:value(8, translate("Loop Mode")) 46 | o:value(7, translate("Every day")) 47 | o:value(1, translate("Every Monday")) 48 | o:value(2, translate("Every Tuesday")) 49 | o:value(3, translate("Every Wednesday")) 50 | o:value(4, translate("Every Thursday")) 51 | o:value(5, translate("Every Friday")) 52 | o:value(6, translate("Every Saturday")) 53 | o:value(0, translate("Every Sunday")) 54 | o.default = 7 55 | o:depends("auto_update", true) 56 | o.rmempty = true 57 | 58 | ---- Time Update 59 | o = s:option(ListValue, "time_update", translate("Update Time(every day)")) 60 | for t = 0, 23 do o:value(t, t .. ":00") end 61 | o.default = 0 62 | o:depends("week_update", "0") 63 | o:depends("week_update", "1") 64 | o:depends("week_update", "2") 65 | o:depends("week_update", "3") 66 | o:depends("week_update", "4") 67 | o:depends("week_update", "5") 68 | o:depends("week_update", "6") 69 | o:depends("week_update", "7") 70 | o.rmempty = true 71 | 72 | ---- Interval Update 73 | o = s:option(ListValue, "interval_update", translate("Update Interval(hour)")) 74 | for t = 1, 24 do o:value(t, t .. " " .. translate("hour")) end 75 | o.default = 2 76 | o:depends("week_update", "8") 77 | o.rmempty = true 78 | 79 | s = m:section(TypedSection, "shunt_rules", "Sing-Box/Xray " .. translate("Shunt Rule"), "" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "") 80 | s.template = "cbi/tblsection" 81 | s.anonymous = false 82 | s.addremove = true 83 | s.sortable = true 84 | s.extedit = api.url("shunt_rules", "%s") 85 | function s.create(e, t) 86 | TypedSection.create(e, t) 87 | luci.http.redirect(e.extedit:format(t)) 88 | end 89 | function s.remove(e, t) 90 | m.uci:foreach(appname, "nodes", function(s) 91 | if s["protocol"] and s["protocol"] == "_shunt" then 92 | m:del(s[".name"], t) 93 | end 94 | end) 95 | TypedSection.remove(e, t) 96 | end 97 | 98 | o = s:option(DummyValue, "remarks", translate("Remarks")) 99 | 100 | return m 101 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ssr.lua: -------------------------------------------------------------------------------- 1 | local m, s = ... 2 | 3 | local api = require "luci.passwall2.api" 4 | 5 | if not api.is_finded("ssr-server") then 6 | return 7 | end 8 | 9 | local type_name = "SSR" 10 | 11 | local option_prefix = "ssr_" 12 | 13 | local function _n(name) 14 | return option_prefix .. name 15 | end 16 | 17 | local ssr_encrypt_method_list = { 18 | "none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb", 19 | "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", 20 | "bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", 21 | "cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20", 22 | "chacha20-ietf" 23 | } 24 | 25 | local ssr_protocol_list = { 26 | "origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple", 27 | "auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5", 28 | "auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c", 29 | "auth_chain_d", "auth_chain_e", "auth_chain_f" 30 | } 31 | local ssr_obfs_list = { 32 | "plain", "http_simple", "http_post", "random_head", "tls_simple", 33 | "tls1.0_session_auth", "tls1.2_ticket_auth" 34 | } 35 | 36 | -- [[ ShadowsocksR ]] 37 | 38 | s.fields["type"]:value(type_name, translate("ShadowsocksR")) 39 | 40 | o = s:option(Flag, _n("custom"), translate("Use Custom Config")) 41 | 42 | o = s:option(Value, _n("port"), translate("Listen Port")) 43 | o.datatype = "port" 44 | o:depends({ [_n("custom")] = false }) 45 | 46 | o = s:option(Value, _n("password"), translate("Password")) 47 | o.password = true 48 | o:depends({ [_n("custom")] = false }) 49 | 50 | o = s:option(ListValue, _n("method"), translate("Encrypt Method")) 51 | for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end 52 | o:depends({ [_n("custom")] = false }) 53 | 54 | o = s:option(ListValue, _n("protocol"), translate("Protocol")) 55 | for a, t in ipairs(ssr_protocol_list) do o:value(t) end 56 | o:depends({ [_n("custom")] = false }) 57 | 58 | o = s:option(Value, _n("protocol_param"), translate("Protocol_param")) 59 | o:depends({ [_n("custom")] = false }) 60 | 61 | o = s:option(ListValue, _n("obfs"), translate("Obfs")) 62 | for a, t in ipairs(ssr_obfs_list) do o:value(t) end 63 | o:depends({ [_n("custom")] = false }) 64 | 65 | o = s:option(Value, _n("obfs_param"), translate("Obfs_param")) 66 | o:depends({ [_n("custom")] = false }) 67 | 68 | o = s:option(Value, _n("timeout"), translate("Connection Timeout")) 69 | o.datatype = "uinteger" 70 | o.default = 300 71 | o:depends({ [_n("custom")] = false }) 72 | 73 | o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open")) 74 | o.default = "0" 75 | o:depends({ [_n("custom")] = false }) 76 | 77 | o = s:option(TextValue, _n("custom_config"), translate("Custom Config")) 78 | o.rows = 10 79 | o.wrap = "off" 80 | o:depends({ [_n("custom")] = true }) 81 | o.validate = function(self, value, t) 82 | if value and api.jsonc.parse(value) then 83 | return value 84 | else 85 | return nil, translate("Must be JSON text!") 86 | end 87 | end 88 | o.custom_cfgvalue = function(self, section, value) 89 | local config_str = m:get(section, "config_str") 90 | if config_str then 91 | return api.base64Decode(config_str) 92 | end 93 | end 94 | o.custom_write = function(self, section, value) 95 | m:set(section, "config_str", api.base64Encode(value)) 96 | end 97 | 98 | o = s:option(Flag, _n("udp_forward"), translate("UDP Forward")) 99 | o.default = "1" 100 | o.rmempty = false 101 | 102 | o = s:option(Flag, _n("log"), translate("Log")) 103 | o.default = "1" 104 | o.rmempty = false 105 | 106 | api.luci_types(arg[1], m, s, type_name, option_prefix) 107 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua: -------------------------------------------------------------------------------- 1 | local api = require "luci.passwall2.api" 2 | local appname = api.appname 3 | 4 | m = Map(appname) 5 | api.set_apply_on_parse(m) 6 | 7 | if not arg[1] or not m:get(arg[1]) then 8 | luci.http.redirect(api.url()) 9 | end 10 | 11 | local has_singbox = api.finded_com("sing-box") 12 | local has_xray = api.finded_com("xray") 13 | 14 | local nodes_table = {} 15 | for k, e in ipairs(api.get_valid_nodes()) do 16 | nodes_table[#nodes_table + 1] = e 17 | end 18 | 19 | s = m:section(NamedSection, arg[1], translate("Socks Config"), translate("Socks Config")) 20 | s.addremove = false 21 | s.dynamic = false 22 | 23 | ---- Enable 24 | o = s:option(Flag, "enabled", translate("Enable")) 25 | o.default = 1 26 | o.rmempty = false 27 | 28 | local auto_switch_tip 29 | local current_node = api.get_cache_var("socks_" .. arg[1]) 30 | if current_node then 31 | local n = m:get(current_node) 32 | if n then 33 | if tonumber(m:get(arg[1], "enable_autoswitch") or 0) == 1 then 34 | if n then 35 | local remarks = api.get_node_remarks(n) 36 | local url = api.url("node_config", n[".name"]) 37 | auto_switch_tip = translatef("Current node: %s", string.format('%s', url, remarks)) .. "
" 38 | end 39 | end 40 | end 41 | end 42 | 43 | socks_node = s:option(ListValue, "node", translate("Node")) 44 | if auto_switch_tip then 45 | socks_node.description = auto_switch_tip 46 | end 47 | 48 | o = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed localhost.")) 49 | o.default = "0" 50 | 51 | local n = 1 52 | m.uci:foreach(appname, "socks", function(s) 53 | if s[".name"] == section then 54 | return false 55 | end 56 | n = n + 1 57 | end) 58 | 59 | o = s:option(Value, "port", "Socks " .. translate("Listen Port")) 60 | o.default = n + 1080 61 | o.datatype = "port" 62 | o.rmempty = false 63 | 64 | if has_singbox or has_xray then 65 | o = s:option(Value, "http_port", "HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use")) 66 | o.default = 0 67 | o.datatype = "port" 68 | end 69 | 70 | o = s:option(Flag, "log", translate("Enable") .. " " .. translate("Log")) 71 | o.default = 1 72 | o.rmempty = false 73 | 74 | o = s:option(Flag, "enable_autoswitch", translate("Auto Switch")) 75 | o.default = 0 76 | o.rmempty = false 77 | 78 | o = s:option(Value, "autoswitch_testing_time", translate("How often to test"), translate("Units:seconds")) 79 | o.datatype = "min(10)" 80 | o.default = 30 81 | o:depends("enable_autoswitch", true) 82 | 83 | o = s:option(Value, "autoswitch_connect_timeout", translate("Timeout seconds"), translate("Units:seconds")) 84 | o.datatype = "min(1)" 85 | o.default = 3 86 | o:depends("enable_autoswitch", true) 87 | 88 | o = s:option(Value, "autoswitch_retry_num", translate("Timeout retry num")) 89 | o.datatype = "min(1)" 90 | o.default = 1 91 | o:depends("enable_autoswitch", true) 92 | 93 | autoswitch_backup_node = s:option(DynamicList, "autoswitch_backup_node", translate("List of backup nodes")) 94 | autoswitch_backup_node:depends("enable_autoswitch", true) 95 | function o.write(self, section, value) 96 | local t = {} 97 | local t2 = {} 98 | if type(value) == "table" then 99 | local x 100 | for _, x in ipairs(value) do 101 | if x and #x > 0 then 102 | if not t2[x] then 103 | t2[x] = x 104 | t[#t+1] = x 105 | end 106 | end 107 | end 108 | else 109 | t = { value } 110 | end 111 | return DynamicList.write(self, section, t) 112 | end 113 | 114 | o = s:option(Flag, "autoswitch_restore_switch", translate("Restore Switch"), translate("When detects main node is available, switch back to the main node.")) 115 | o:depends("enable_autoswitch", true) 116 | 117 | o = s:option(Value, "autoswitch_probe_url", translate("Probe URL"), translate("The URL used to detect the connection status.")) 118 | o.default = "https://www.google.com/generate_204" 119 | o:depends("enable_autoswitch", true) 120 | 121 | for k, v in pairs(nodes_table) do 122 | autoswitch_backup_node:value(v.id, v["remark"]) 123 | socks_node:value(v.id, v["remark"]) 124 | end 125 | 126 | o = s:option(DummyValue, "btn", " ") 127 | o.template = appname .. "/socks_auto_switch/btn" 128 | o:depends("enable_autoswitch", true) 129 | 130 | return m 131 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/tuic.lua: -------------------------------------------------------------------------------- 1 | local m, s = ... 2 | 3 | local api = require "luci.passwall2.api" 4 | 5 | if not api.is_finded("tuic-client") then 6 | return 7 | end 8 | 9 | local type_name = "TUIC" 10 | 11 | local option_prefix = "tuic_" 12 | 13 | local function _n(name) 14 | return option_prefix .. name 15 | end 16 | 17 | -- [[ TUIC ]] 18 | 19 | s.fields["type"]:value(type_name, translate("TUIC")) 20 | 21 | o = s:option(Value, _n("address"), translate("Address (Support Domain Name)")) 22 | 23 | o = s:option(Value, _n("port"), translate("Port")) 24 | o.datatype = "port" 25 | 26 | o = s:option(Value, _n("uuid"), translate("ID")) 27 | o.password = true 28 | 29 | -- Tuic Password for remote server connect 30 | o = s:option(Value, _n("password"), translate("TUIC User Password For Connect Remote Server")) 31 | o.password = true 32 | o.rmempty = true 33 | o.default = "" 34 | o.rewrite_option = o.option 35 | 36 | --[[ 37 | -- Tuic username for local socks connect 38 | o = s:option(Value, _n("socks_username"), translate("TUIC UserName For Local Socks")) 39 | o.rmempty = true 40 | o.default = "" 41 | o.rewrite_option = o.option 42 | 43 | -- Tuic Password for local socks connect 44 | o = s:option(Value, _n("socks_password"), translate("TUIC Password For Local Socks")) 45 | o.password = true 46 | o.rmempty = true 47 | o.default = "" 48 | o.rewrite_option = o.option 49 | --]] 50 | 51 | o = s:option(Value, _n("ip"), translate("Set the TUIC proxy server ip address")) 52 | o.datatype = "ipaddr" 53 | o.rmempty = true 54 | o.rewrite_option = o.option 55 | 56 | o = s:option(ListValue, _n("udp_relay_mode"), translate("UDP relay mode")) 57 | o:value("native", translate("native")) 58 | o:value("quic", translate("QUIC")) 59 | o.default = "native" 60 | o.rmempty = true 61 | o.rewrite_option = o.option 62 | 63 | o = s:option(ListValue, _n("congestion_control"), translate("Congestion control algorithm")) 64 | o:value("bbr", translate("BBR")) 65 | o:value("cubic", translate("CUBIC")) 66 | o:value("new_reno", translate("New Reno")) 67 | o.default = "cubic" 68 | o.rmempty = true 69 | o.rewrite_option = o.option 70 | 71 | o = s:option(Value, _n("heartbeat"), translate("Heartbeat interval(second)")) 72 | o.datatype = "uinteger" 73 | o.default = "3" 74 | o.rmempty = true 75 | o.rewrite_option = o.option 76 | 77 | o = s:option(Value, _n("timeout"), translate("Timeout for establishing a connection to server(second)")) 78 | o.datatype = "uinteger" 79 | o.default = "8" 80 | o.rmempty = true 81 | o.rewrite_option = o.option 82 | 83 | o = s:option(Value, _n("gc_interval"), translate("Garbage collection interval(second)")) 84 | o.datatype = "uinteger" 85 | o.default = "3" 86 | o.rmempty = true 87 | o.rewrite_option = o.option 88 | 89 | o = s:option(Value, _n("gc_lifetime"), translate("Garbage collection lifetime(second)")) 90 | o.datatype = "uinteger" 91 | o.default = "15" 92 | o.rmempty = true 93 | o.rewrite_option = o.option 94 | 95 | o = s:option(Value, _n("send_window"), translate("TUIC send window")) 96 | o.datatype = "uinteger" 97 | o.default = 20971520 98 | o.rmempty = true 99 | o.rewrite_option = o.option 100 | 101 | o = s:option(Value, _n("receive_window"), translate("TUIC receive window")) 102 | o.datatype = "uinteger" 103 | o.default = 10485760 104 | o.rmempty = true 105 | o.rewrite_option = o.option 106 | 107 | o = s:option(Value, _n("max_package_size"), translate("TUIC Maximum packet size the socks5 server can receive from external, in bytes")) 108 | o.datatype = "uinteger" 109 | o.default = 1500 110 | o.rmempty = true 111 | o.rewrite_option = o.option 112 | 113 | --Tuic settings for the local inbound socks5 server 114 | o = s:option(Flag, _n("dual_stack"), translate("Set if the listening socket should be dual-stack")) 115 | o.default = 0 116 | o.rmempty = true 117 | o.rewrite_option = o.option 118 | 119 | o = s:option(Flag, _n("disable_sni"), translate("Disable SNI")) 120 | o.default = 0 121 | o.rmempty = true 122 | o.rewrite_option = o.option 123 | 124 | o = s:option(Flag, _n("zero_rtt_handshake"), translate("Enable 0-RTT QUIC handshake")) 125 | o.default = 0 126 | o.rmempty = true 127 | o.rewrite_option = o.option 128 | 129 | o = s:option(DynamicList, _n("tls_alpn"), translate("TLS ALPN")) 130 | o.rmempty = true 131 | o.rewrite_option = o.option 132 | 133 | api.luci_types(arg[1], m, s, type_name, option_prefix) 134 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/passwall2/util_shadowsocks.lua: -------------------------------------------------------------------------------- 1 | module("luci.passwall2.util_shadowsocks", package.seeall) 2 | local api = require "luci.passwall2.api" 3 | local uci = api.uci 4 | local jsonc = api.jsonc 5 | 6 | function gen_config_server(node) 7 | local config = {} 8 | config.server_port = tonumber(node.port) 9 | config.password = node.password 10 | config.timeout = tonumber(node.timeout) 11 | config.fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false 12 | config.method = node.method 13 | 14 | if node.type == "SS-Rust" then 15 | config.server = "::" 16 | config.mode = "tcp_and_udp" 17 | else 18 | config.server = {"[::0]", "0.0.0.0"} 19 | end 20 | 21 | if node.type == "SSR" then 22 | config.protocol = node.protocol 23 | config.protocol_param = node.protocol_param 24 | config.obfs = node.obfs 25 | config.obfs_param = node.obfs_param 26 | end 27 | 28 | return config 29 | end 30 | 31 | local plugin_sh, plugin_bin 32 | 33 | function gen_config(var) 34 | local node_id = var["-node"] 35 | if not node_id then 36 | print("-node 不能为空") 37 | return 38 | end 39 | local node = uci:get_all("passwall2", node_id) 40 | local server_host = var["-server_host"] or node.address 41 | local server_port = var["-server_port"] or node.port 42 | local local_addr = var["-local_addr"] 43 | local local_port = var["-local_port"] 44 | local mode = var["-mode"] 45 | local local_socks_address = var["-local_socks_address"] or "0.0.0.0" 46 | local local_socks_port = var["-local_socks_port"] 47 | local local_socks_username = var["-local_socks_username"] 48 | local local_socks_password = var["-local_socks_password"] 49 | local local_http_address = var["-local_http_address"] or "0.0.0.0" 50 | local local_http_port = var["-local_http_port"] 51 | local local_http_username = var["-local_http_username"] 52 | local local_http_password = var["-local_http_password"] 53 | 54 | if api.is_ipv6(server_host) then 55 | server_host = api.get_ipv6_only(server_host) 56 | end 57 | local server = server_host 58 | 59 | local plugin_file 60 | if node.plugin and node.plugin ~= "" and node.plugin ~= "none" then 61 | plugin_sh = var["-plugin_sh"] or "" 62 | plugin_file = (plugin_sh ~="") and plugin_sh or node.plugin 63 | plugin_bin = node.plugin 64 | end 65 | 66 | local config = { 67 | server = server, 68 | server_port = tonumber(server_port), 69 | local_address = local_addr, 70 | local_port = tonumber(local_port), 71 | password = node.password, 72 | method = node.method, 73 | timeout = tonumber(node.timeout), 74 | fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false, 75 | reuse_port = true 76 | } 77 | 78 | if node.type == "SS" then 79 | config.plugin = plugin_file or nil 80 | config.plugin_opts = (plugin_file) and node.plugin_opts or nil 81 | config.mode = mode 82 | elseif node.type == "SSR" then 83 | config.protocol = node.protocol 84 | config.protocol_param = node.protocol_param 85 | config.obfs = node.obfs 86 | config.obfs_param = node.obfs_param 87 | elseif node.type == "SS-Rust" then 88 | config = { 89 | servers = { 90 | { 91 | address = server, 92 | port = tonumber(server_port), 93 | method = node.method, 94 | password = node.password, 95 | timeout = tonumber(node.timeout), 96 | plugin = plugin_file or nil, 97 | plugin_opts = (plugin_file) and node.plugin_opts or nil 98 | } 99 | }, 100 | locals = {}, 101 | fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false 102 | } 103 | if local_socks_address and local_socks_port then 104 | table.insert(config.locals, { 105 | local_address = local_socks_address, 106 | local_port = tonumber(local_socks_port), 107 | mode = "tcp_and_udp" 108 | }) 109 | end 110 | if local_http_address and local_http_port then 111 | table.insert(config.locals, { 112 | protocol = "http", 113 | local_address = local_http_address, 114 | local_port = tonumber(local_http_port) 115 | }) 116 | end 117 | end 118 | 119 | return jsonc.stringify(config, 1) 120 | end 121 | 122 | _G.gen_config = gen_config 123 | 124 | if arg[1] then 125 | local func =_G[arg[1]] 126 | if func then 127 | print(func(api.get_function_args(arg))) 128 | if plugin_sh and plugin_sh ~="" and plugin_bin then 129 | local f = io.open(plugin_sh, "w") 130 | f:write("#!/bin/sh\n") 131 | f:write("export PATH=/usr/sbin:/usr/bin:/sbin:/bin:/root/bin:$PATH\n") 132 | f:write(plugin_bin .. " $@ &\n") 133 | f:write("echo $! > " .. plugin_sh:gsub("%.sh$", ".pid") .. "\n") 134 | f:write("wait\n") 135 | f:close() 136 | luci.sys.call("chmod +x " .. plugin_sh) 137 | end 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/passwall2/util_hysteria2.lua: -------------------------------------------------------------------------------- 1 | module("luci.passwall2.util_hysteria2", package.seeall) 2 | local api = require "luci.passwall2.api" 3 | local uci = api.uci 4 | local jsonc = api.jsonc 5 | 6 | function gen_config_server(node) 7 | local config = { 8 | listen = ":" .. node.port, 9 | tls = { 10 | cert = node.tls_certificateFile, 11 | key = node.tls_keyFile, 12 | }, 13 | obfs = (node.hysteria2_obfs) and { 14 | type = "salamander", 15 | salamander = { 16 | password = node.hysteria2_obfs 17 | } 18 | } or nil, 19 | auth = { 20 | type = "password", 21 | password = node.hysteria2_auth_password 22 | }, 23 | bandwidth = (node.hysteria2_up_mbps or node.hysteria2_down_mbps) and { 24 | up = node.hysteria2_up_mbps and node.hysteria2_up_mbps .. " mbps" or nil, 25 | down = node.hysteria2_down_mbps and node.hysteria2_down_mbps .. " mbps" or nil 26 | } or nil, 27 | ignoreClientBandwidth = (node.hysteria2_ignoreClientBandwidth == "1") and true or false, 28 | disableUDP = (node.hysteria2_udp == "0") and true or false, 29 | } 30 | return config 31 | end 32 | 33 | function gen_config(var) 34 | local node_id = var["-node"] 35 | if not node_id then 36 | print("-node 不能为空") 37 | return 38 | end 39 | local node = uci:get_all("passwall2", node_id) 40 | local local_socks_address = var["-local_socks_address"] or "0.0.0.0" 41 | local local_socks_port = var["-local_socks_port"] 42 | local local_socks_username = var["-local_socks_username"] 43 | local local_socks_password = var["-local_socks_password"] 44 | local local_http_address = var["-local_http_address"] or "0.0.0.0" 45 | local local_http_port = var["-local_http_port"] 46 | local local_http_username = var["-local_http_username"] 47 | local local_http_password = var["-local_http_password"] 48 | local server_host = var["-server_host"] or node.address 49 | local server_port = var["-server_port"] or node.port 50 | 51 | if api.is_ipv6(server_host) then 52 | server_host = api.get_ipv6_full(server_host) 53 | end 54 | local server = server_host .. ":" .. server_port 55 | 56 | if (node.hysteria2_hop) then 57 | server = server .. "," .. string.gsub(node.hysteria2_hop, ":", "-") 58 | end 59 | 60 | local config = { 61 | server = server, 62 | transport = { 63 | type = node.protocol or "udp", 64 | udp = { 65 | hopInterval = (function() 66 | local HopIntervalStr = tostring(node.hysteria2_hop_interval or "30s") 67 | local HopInterval = tonumber(HopIntervalStr:match("^%d+")) 68 | if HopInterval and HopInterval >= 5 then 69 | return tostring(HopInterval) .. "s" 70 | end 71 | return "30s" 72 | end)(), 73 | } 74 | }, 75 | obfs = (node.hysteria2_obfs) and { 76 | type = "salamander", 77 | salamander = { 78 | password = node.hysteria2_obfs 79 | } 80 | } or nil, 81 | auth = node.hysteria2_auth_password, 82 | tls = { 83 | sni = node.tls_serverName, 84 | insecure = (node.tls_allowInsecure == "1") and true or false, 85 | pinSHA256 = (node.hysteria2_tls_pinSHA256) and node.hysteria2_tls_pinSHA256 or nil, 86 | }, 87 | quic = { 88 | initStreamReceiveWindow = (node.hysteria2_recv_window) and tonumber(node.hysteria2_recv_window) or nil, 89 | initConnReceiveWindow = (node.hysteria2_recv_window_conn) and tonumber(node.hysteria2_recv_window_conn) or nil, 90 | maxIdleTimeout = (function() 91 | local timeoutStr = tostring(node.hysteria2_idle_timeout or "") 92 | local timeout = tonumber(timeoutStr:match("^%d+")) 93 | if timeout and timeout >= 4 and timeout <= 120 then 94 | return tostring(timeout) .. "s" 95 | end 96 | return nil 97 | end)(), 98 | disablePathMTUDiscovery = (node.hysteria2_disable_mtu_discovery) and true or false, 99 | }, 100 | bandwidth = (node.hysteria2_up_mbps or node.hysteria2_down_mbps) and { 101 | up = node.hysteria2_up_mbps and node.hysteria2_up_mbps .. " mbps" or nil, 102 | down = node.hysteria2_down_mbps and node.hysteria2_down_mbps .. " mbps" or nil 103 | } or nil, 104 | fast_open = (node.fast_open == "1") and true or false, 105 | lazy = (node.hysteria2_lazy_start == "1") and true or false, 106 | socks5 = (local_socks_address and local_socks_port) and { 107 | listen = local_socks_address .. ":" .. local_socks_port, 108 | username = (local_socks_username and local_socks_password) and local_socks_username or nil, 109 | password = (local_socks_username and local_socks_password) and local_socks_password or nil, 110 | disableUDP = false, 111 | } or nil, 112 | http = (local_http_address and local_http_port) and { 113 | listen = local_http_address .. ":" .. local_http_port, 114 | username = (local_http_username and local_http_password) and local_http_username or nil, 115 | password = (local_http_username and local_http_password) and local_http_password or nil, 116 | } or nil 117 | } 118 | 119 | return jsonc.stringify(config, 1) 120 | end 121 | 122 | _G.gen_config = gen_config 123 | 124 | if arg[1] then 125 | local func =_G[arg[1]] 126 | if func then 127 | print(func(api.get_function_args(arg))) 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /luci-app-passwall2/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022-2025 xiaorouji 2 | # 3 | # This is free software, licensed under the GNU General Public License v3. 4 | 5 | include $(TOPDIR)/rules.mk 6 | 7 | PKG_NAME:=luci-app-passwall2 8 | PKG_VERSION:=25.11.2 9 | PKG_RELEASE:=1 10 | PKG_PO_VERSION:=$(PKG_VERSION) 11 | 12 | PKG_CONFIG_DEPENDS:= \ 13 | CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \ 14 | CONFIG_PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy \ 15 | CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Haproxy \ 16 | CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria \ 17 | CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_IPv6_Nat \ 18 | CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy \ 19 | CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client \ 20 | CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server \ 21 | CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client \ 22 | CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server \ 23 | CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client \ 24 | CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server \ 25 | CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs \ 26 | CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_SingBox \ 27 | CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_tuic_client \ 28 | CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin 29 | 30 | LUCI_TITLE:=LuCI support for PassWall 2 31 | LUCI_PKGARCH:=all 32 | LUCI_DEPENDS:=+coreutils +coreutils-base64 +coreutils-nohup +curl \ 33 | +ip-full +libuci-lua +lua +luci-compat +luci-lib-jsonc +resolveip +tcping \ 34 | +xray-core +geoview +v2ray-geoip +v2ray-geosite \ 35 | +unzip \ 36 | +PACKAGE_$(PKG_NAME)_INCLUDE_IPv6_Nat:ip6tables-mod-nat 37 | 38 | define Package/$(PKG_NAME)/config 39 | menu "Configuration" 40 | 41 | config PACKAGE_$(PKG_NAME)_INCLUDE_IPv6_Nat 42 | depends on PACKAGE_ip6tables 43 | bool "Include IPv6 Nat" 44 | default n 45 | 46 | if PACKAGE_$(PKG_NAME) 47 | 48 | config PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy 49 | bool "Iptables Transparent Proxy" 50 | select PACKAGE_chinadns-ng 51 | select PACKAGE_dnsmasq-full 52 | select PACKAGE_dnsmasq_full_ipset 53 | select PACKAGE_ipset 54 | select PACKAGE_iptables 55 | select PACKAGE_iptables-nft 56 | select PACKAGE_iptables-zz-legacy 57 | select PACKAGE_iptables-mod-conntrack-extra 58 | select PACKAGE_iptables-mod-iprange 59 | select PACKAGE_iptables-mod-socket 60 | select PACKAGE_iptables-mod-tproxy 61 | select PACKAGE_kmod-ipt-nat 62 | default y if ! PACKAGE_firewall4 63 | 64 | config PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy 65 | bool "Nftables Transparent Proxy" 66 | select PACKAGE_chinadns-ng 67 | select PACKAGE_dnsmasq-full 68 | select PACKAGE_dnsmasq_full_nftset 69 | select PACKAGE_nftables 70 | select PACKAGE_kmod-nft-socket 71 | select PACKAGE_kmod-nft-tproxy 72 | select PACKAGE_kmod-nft-nat 73 | default y if PACKAGE_firewall4 74 | 75 | config PACKAGE_$(PKG_NAME)_INCLUDE_Haproxy 76 | bool "Include Haproxy" 77 | select PACKAGE_haproxy 78 | default y if aarch64||arm||i386||x86_64 79 | 80 | config PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria 81 | bool "Include Hysteria" 82 | select PACKAGE_hysteria 83 | default n 84 | 85 | config PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy 86 | bool "Include NaiveProxy" 87 | depends on !(arc||armeb||loongarch64||mips||mips64||powerpc||TARGET_gemini) 88 | select PACKAGE_naiveproxy 89 | default n 90 | 91 | config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client 92 | bool "Include Shadowsocks Libev Client" 93 | select PACKAGE_shadowsocks-libev-ss-local 94 | select PACKAGE_shadowsocks-libev-ss-redir 95 | default y 96 | 97 | config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server 98 | bool "Include Shadowsocks Libev Server" 99 | select PACKAGE_shadowsocks-libev-ss-server 100 | default n 101 | 102 | config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client 103 | bool "Include Shadowsocks Rust Client" 104 | depends on !i386 105 | select PACKAGE_shadowsocks-rust-sslocal 106 | default y if aarch64||x86_64 107 | 108 | config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server 109 | bool "Include Shadowsocks Rust Server" 110 | depends on !i386 111 | select PACKAGE_shadowsocks-rust-ssserver 112 | default n 113 | 114 | config PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client 115 | bool "Include ShadowsocksR Libev Client" 116 | select PACKAGE_shadowsocksr-libev-ssr-local 117 | select PACKAGE_shadowsocksr-libev-ssr-redir 118 | default y 119 | 120 | config PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server 121 | bool "Include ShadowsocksR Libev Server" 122 | select PACKAGE_shadowsocksr-libev-ssr-server 123 | default n 124 | 125 | config PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs 126 | bool "Include Simple-Obfs (Shadowsocks Plugin)" 127 | select PACKAGE_simple-obfs-client 128 | default y 129 | 130 | config PACKAGE_$(PKG_NAME)_INCLUDE_SingBox 131 | bool "Include Sing-Box" 132 | select PACKAGE_sing-box 133 | default y if aarch64||arm||i386||x86_64 134 | 135 | config PACKAGE_$(PKG_NAME)_INCLUDE_tuic_client 136 | bool "Include tuic-client" 137 | depends on aarch64||arm||i386||x86_64 138 | select PACKAGE_tuic-client 139 | default n 140 | 141 | config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin 142 | bool "Include V2ray-Plugin (Shadowsocks Plugin)" 143 | select PACKAGE_v2ray-plugin 144 | default y if aarch64||arm||i386||x86_64 145 | 146 | endif 147 | endmenu 148 | endef 149 | 150 | define Package/$(PKG_NAME)/conffiles 151 | /etc/config/passwall2 152 | /etc/config/passwall2_server 153 | /usr/share/passwall2/domains_excluded 154 | endef 155 | 156 | include $(TOPDIR)/feeds/luci/luci.mk 157 | 158 | # call BuildPackage - OpenWrt buildroot signature 159 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/view/passwall2/global/footer.htm: -------------------------------------------------------------------------------- 1 | <% 2 | local api = require "luci.passwall2.api" 3 | -%> 4 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/haproxy.lua: -------------------------------------------------------------------------------- 1 | local api = require "luci.passwall2.api" 2 | local appname = api.appname 3 | local datatypes = api.datatypes 4 | local net = require "luci.model.network".init() 5 | 6 | local nodes_table = {} 7 | for k, e in ipairs(api.get_valid_nodes()) do 8 | if e.node_type == "normal" then 9 | nodes_table[#nodes_table + 1] = { 10 | id = e[".name"], 11 | obj = e, 12 | remarks = e["remark"] 13 | } 14 | end 15 | end 16 | 17 | m = Map(appname) 18 | api.set_apply_on_parse(m) 19 | 20 | -- [[ Haproxy Settings ]]-- 21 | s = m:section(TypedSection, "global_haproxy", translate("Basic Settings")) 22 | s.anonymous = true 23 | 24 | s:append(Template(appname .. "/haproxy/status")) 25 | 26 | ---- Balancing Enable 27 | o = s:option(Flag, "balancing_enable", translate("Enable Load Balancing")) 28 | o.rmempty = false 29 | o.default = false 30 | 31 | ---- Console Login Auth 32 | o = s:option(Flag, "console_auth", translate("Console Login Auth")) 33 | o.default = false 34 | o:depends("balancing_enable", true) 35 | 36 | ---- Console Username 37 | o = s:option(Value, "console_user", translate("Console Username")) 38 | o.default = "" 39 | o:depends("console_auth", true) 40 | 41 | ---- Console Password 42 | o = s:option(Value, "console_password", translate("Console Password")) 43 | o.password = true 44 | o.default = "" 45 | o:depends("console_auth", true) 46 | 47 | ---- Console Port 48 | o = s:option(Value, "console_port", translate("Console Port"), translate( 49 | "In the browser input routing IP plus port access, such as:192.168.1.1:1188")) 50 | o.default = "1188" 51 | o:depends("balancing_enable", true) 52 | 53 | o = s:option(Flag, "bind_local", translate("Haproxy Port") .. " " .. translate("Bind Local"), translate("When selected, it can only be accessed localhost.")) 54 | o.default = "0" 55 | o:depends("balancing_enable", true) 56 | 57 | ---- Health Check Type 58 | o = s:option(ListValue, "health_check_type", translate("Health Check Type")) 59 | o.default = "passwall_logic" 60 | o:value("tcp", "TCP") 61 | o:value("passwall_logic", translate("URL Test") .. string.format("(passwall %s)", translate("Inner implement"))) 62 | o:depends("balancing_enable", true) 63 | 64 | ---- Passwall Inner implement Probe URL 65 | o = s:option(Value, "health_probe_url", translate("Probe URL")) 66 | o.default = "https://www.google.com/generate_204" 67 | o:value("https://cp.cloudflare.com/", "Cloudflare") 68 | o:value("https://www.gstatic.com/generate_204", "Gstatic") 69 | o:value("https://www.google.com/generate_204", "Google") 70 | o:value("https://www.youtube.com/generate_204", "YouTube") 71 | o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)") 72 | o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)") 73 | o.description = translate("The URL used to detect the connection status.") 74 | o:depends("health_check_type", "passwall_logic") 75 | 76 | ---- Health Check Inter 77 | o = s:option(Value, "health_check_inter", translate("Health Check Inter"), translate("Units:seconds")) 78 | o.default = "60" 79 | o:depends("balancing_enable", true) 80 | 81 | o = s:option(DummyValue, "health_check_tips", " ") 82 | o.rawhtml = true 83 | o.cfgvalue = function(t, n) 84 | return string.format('%s', translate("When the URL test is used, the load balancing node will be converted into a Socks node. when node list set customizing, must be a Socks node, otherwise the health check will be invalid.")) 85 | end 86 | o:depends("health_check_type", "passwall_logic") 87 | 88 | -- [[ Balancing Settings ]]-- 89 | s = m:section(TypedSection, "haproxy_config", translate("Node List"), 90 | "" .. 91 | translate("Add a node, Export Of Multi WAN Only support Multi Wan. Load specific gravity range 1-256. Multiple primary servers can be load balanced, standby will only be enabled when the primary server is offline! Multiple groups can be set, Haproxy port same one for each group.") .. 92 | "\n" .. translate("Note that the node configuration parameters for load balancing must be consistent when use TCP health check type, otherwise it cannot be used normally!") .. 93 | "") 94 | s.template = "cbi/tblsection" 95 | s.sortable = true 96 | s.anonymous = true 97 | s.addremove = true 98 | 99 | s.create = function(e, t) 100 | TypedSection.create(e, api.gen_short_uuid()) 101 | end 102 | 103 | s.remove = function(self, section) 104 | for k, v in pairs(self.children) do 105 | v.rmempty = true 106 | v.validate = nil 107 | end 108 | TypedSection.remove(self, section) 109 | end 110 | 111 | ---- Enable 112 | o = s:option(Flag, "enabled", translate("Enable")) 113 | o.default = 1 114 | o.rmempty = false 115 | 116 | ---- Node Address 117 | o = s:option(Value, "lbss", translate("Node Address")) 118 | for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) end 119 | o.rmempty = false 120 | o.validate = function(self, value) 121 | if not value then return nil end 122 | local t = m:get(value) or nil 123 | if t and t[".type"] == "nodes" then 124 | return value 125 | end 126 | if datatypes.hostport(value) or datatypes.ip4addrport(value) then 127 | return value 128 | end 129 | if api.is_ipv6addrport(value) then 130 | return value 131 | end 132 | return nil, value 133 | end 134 | 135 | ---- Haproxy Port 136 | o = s:option(Value, "haproxy_port", translate("Haproxy Port")) 137 | o.datatype = "port" 138 | o.default = 1181 139 | o.rmempty = false 140 | 141 | ---- Node Weight 142 | o = s:option(Value, "lbweight", translate("Node Weight")) 143 | o.datatype = "uinteger" 144 | o.default = 5 145 | o.rmempty = false 146 | 147 | ---- Export 148 | o = s:option(ListValue, "export", translate("Export Of Multi WAN")) 149 | o:value(0, translate("Auto")) 150 | local wa = require "luci.tools.webadmin" 151 | wa.cbi_add_networks(o) 152 | o.default = 0 153 | o.rmempty = false 154 | 155 | ---- Mode 156 | o = s:option(ListValue, "backup", translate("Mode")) 157 | o:value(0, translate("Primary")) 158 | o:value(1, translate("Standby")) 159 | o.rmempty = false 160 | 161 | return m 162 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/view/passwall2/app_update/app_version.htm: -------------------------------------------------------------------------------- 1 | <% 2 | local api = require "luci.passwall2.api" 3 | local com = require "luci.passwall2.com" 4 | local version = {} 5 | -%> 6 | 7 | 179 | 180 |
181 | 182 |
183 |
184 | 【 <%=api.get_version()%> 】 185 | 187 | 188 |
189 |
190 |
191 | 192 | <%for k, v in pairs(com) do 193 | version[k] = api.get_app_version(k)%> 194 |
195 | 198 |
199 |
200 | 【 <%=version[k] ~="" and version[k] or translate("Null") %> 】 201 | 203 | 205 | 206 |
207 |
208 |
209 | <%end%> 210 | -------------------------------------------------------------------------------- /luci-app-passwall2/root/usr/share/passwall2/rule_update.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | local api = require "luci.passwall2.api" 4 | local name = api.appname 5 | local fs = api.fs 6 | local sys = api.sys 7 | local uci = api.uci 8 | local jsonc = api.jsonc 9 | 10 | local arg1 = arg[1] 11 | local arg2 = arg[2] 12 | local arg3 = arg[3] 13 | 14 | local reboot = 0 15 | local geoip_update = 0 16 | local geosite_update = 0 17 | local asset_location = uci:get_first(name, 'global_rules', "v2ray_location_asset", "/usr/share/v2ray/") 18 | 19 | -- Custom geo file 20 | local geoip_api = uci:get_first(name, 'global_rules', "geoip_url", "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest") 21 | local geosite_api = uci:get_first(name, 'global_rules', "geosite_url", "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest") 22 | -- 23 | local use_nft = uci:get(name, "@global_forwarding[0]", "use_nft") or "0" 24 | 25 | if arg3 == "cron" then 26 | arg2 = nil 27 | end 28 | 29 | local log = function(...) 30 | if arg1 then 31 | if arg1 == "log" then 32 | api.log(...) 33 | elseif arg1 == "print" then 34 | local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ") 35 | print(result) 36 | end 37 | end 38 | end 39 | 40 | -- curl 41 | local function curl(url, file) 42 | local args = { 43 | "-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3", "--max-time 300", "--speed-limit 51200 --speed-time 15" 44 | } 45 | if file then 46 | args[#args + 1] = "-o " .. file 47 | end 48 | local return_code, result = api.curl_logic(url, nil, args) 49 | return tonumber(result) 50 | end 51 | 52 | --Getgeoip 53 | local function fetch_geoip() 54 | --askgeoip 55 | xpcall(function() 56 | local return_code, content = api.curl_logic(geoip_api) 57 | local json = jsonc.parse(content) 58 | if json.tag_name and json.assets then 59 | for _, v in ipairs(json.assets) do 60 | if v.name and v.name == "geoip.dat.sha256sum" then 61 | local sret = curl(v.browser_download_url, "/tmp/geoip.dat.sha256sum") 62 | if sret == 200 then 63 | local f = io.open("/tmp/geoip.dat.sha256sum", "r") 64 | local content = f:read() 65 | f:close() 66 | f = io.open("/tmp/geoip.dat.sha256sum", "w") 67 | f:write(content:gsub("geoip.dat", "/tmp/geoip.dat"), "") 68 | f:close() 69 | 70 | if fs.access(asset_location .. "geoip.dat") then 71 | sys.call(string.format("cp -f %s %s", asset_location .. "geoip.dat", "/tmp/geoip.dat")) 72 | if sys.call('sha256sum -c /tmp/geoip.dat.sha256sum > /dev/null 2>&1') == 0 then 73 | log("geoip Version consistent,No update required。") 74 | return 1 75 | end 76 | end 77 | for _2, v2 in ipairs(json.assets) do 78 | if v2.name and v2.name == "geoip.dat" then 79 | sret = curl(v2.browser_download_url, "/tmp/geoip.dat") 80 | if sys.call('sha256sum -c /tmp/geoip.dat.sha256sum > /dev/null 2>&1') == 0 then 81 | sys.call(string.format("mkdir -p %s && cp -f %s %s", asset_location, "/tmp/geoip.dat", asset_location .. "geoip.dat")) 82 | reboot = 1 83 | log("geoip Update successful。") 84 | return 1 85 | else 86 | log("geoip Update failed,Please try again later。") 87 | end 88 | break 89 | end 90 | end 91 | end 92 | break 93 | end 94 | end 95 | end 96 | if json.message then 97 | log(json.message) 98 | end 99 | end, 100 | function(e) 101 | end) 102 | 103 | return 0 104 | end 105 | 106 | --Getgeosite 107 | local function fetch_geosite() 108 | --askgeosite 109 | xpcall(function() 110 | local return_code, content = api.curl_logic(geosite_api) 111 | local json = jsonc.parse(content) 112 | if json.tag_name and json.assets then 113 | for _, v in ipairs(json.assets) do 114 | if v.name and (v.name == "geosite.dat.sha256sum" or v.name == "dlc.dat.sha256sum") then 115 | local sret = curl(v.browser_download_url, "/tmp/geosite.dat.sha256sum") 116 | if sret == 200 then 117 | local f = io.open("/tmp/geosite.dat.sha256sum", "r") 118 | local content = f:read() 119 | f:close() 120 | f = io.open("/tmp/geosite.dat.sha256sum", "w") 121 | f:write(content:gsub("[^%s]+.dat", "/tmp/geosite.dat"), "") 122 | f:close() 123 | 124 | if fs.access(asset_location .. "geosite.dat") then 125 | sys.call(string.format("cp -f %s %s", asset_location .. "geosite.dat", "/tmp/geosite.dat")) 126 | if sys.call('sha256sum -c /tmp/geosite.dat.sha256sum > /dev/null 2>&1') == 0 then 127 | log("geosite Version consistent,No update required。") 128 | return 1 129 | end 130 | end 131 | for _2, v2 in ipairs(json.assets) do 132 | if v2.name and (v2.name == "geosite.dat" or v2.name == "dlc.dat") then 133 | sret = curl(v2.browser_download_url, "/tmp/geosite.dat") 134 | if sys.call('sha256sum -c /tmp/geosite.dat.sha256sum > /dev/null 2>&1') == 0 then 135 | sys.call(string.format("mkdir -p %s && cp -f %s %s", asset_location, "/tmp/geosite.dat", asset_location .. "geosite.dat")) 136 | reboot = 1 137 | log("geosite Update successful。") 138 | return 1 139 | else 140 | log("geosite Update failed,Please try again later。") 141 | end 142 | break 143 | end 144 | end 145 | end 146 | break 147 | end 148 | end 149 | end 150 | if json.message then 151 | log(json.message) 152 | end 153 | end, 154 | function(e) 155 | end) 156 | 157 | return 0 158 | end 159 | 160 | if arg2 then 161 | string.gsub(arg2, '[^' .. "," .. ']+', function(w) 162 | if w == "geoip" then 163 | geoip_update = 1 164 | end 165 | if w == "geosite" then 166 | geosite_update = 1 167 | end 168 | end) 169 | else 170 | geoip_update = uci:get_first(name, 'global_rules', "geoip_update", 1) 171 | geosite_update = uci:get_first(name, 'global_rules', "geosite_update", 1) 172 | end 173 | if geoip_update == 0 and geosite_update == 0 then 174 | os.exit(0) 175 | end 176 | 177 | log("Start updating rules...") 178 | 179 | if tonumber(geoip_update) == 1 then 180 | log("geoip Start updating...") 181 | local status = fetch_geoip() 182 | os.remove("/tmp/geoip.dat") 183 | os.remove("/tmp/geoip.dat.sha256sum") 184 | end 185 | 186 | if tonumber(geosite_update) == 1 then 187 | log("geosite Start updating...") 188 | local status = fetch_geosite() 189 | os.remove("/tmp/geosite.dat") 190 | os.remove("/tmp/geosite.dat.sha256sum") 191 | end 192 | 193 | uci:set(name, uci:get_first(name, 'global_rules'), "geoip_update", geoip_update) 194 | uci:set(name, uci:get_first(name, 'global_rules'), "geosite_update", geosite_update) 195 | api.uci_save(uci, name, true) 196 | 197 | if reboot == 1 then 198 | if arg3 == "cron" then 199 | if not fs.access("/var/lock/" .. name .. ".lock") then 200 | sys.call("touch /tmp/lock/" .. name .. "_cron.lock") 201 | end 202 | end 203 | 204 | log("Restart service,Apply new rules。") 205 | uci:set(name, "@global[0]", "flush_set", "1") 206 | api.uci_save(uci, name, true, true) 207 | end 208 | log("Rules updated...") 209 | -------------------------------------------------------------------------------- /luci-app-passwall2/root/usr/share/passwall2/socks_auto_switch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CONFIG=passwall2 4 | LOG_FILE=/tmp/log/$CONFIG.log 5 | LOCK_FILE_DIR=/tmp/lock 6 | 7 | flag=0 8 | 9 | echolog() { 10 | local d="$(date "+%Y-%m-%d %H:%M:%S")" 11 | #echo -e "$d: $1" 12 | echo -e "$d: $1" >> $LOG_FILE 13 | } 14 | 15 | config_n_get() { 16 | local ret=$(uci -q get "${CONFIG}.${1}.${2}" 2>/dev/null) 17 | echo "${ret:=$3}" 18 | } 19 | 20 | test_url() { 21 | local url=$1 22 | local try=1 23 | [ -n "$2" ] && try=$2 24 | local timeout=2 25 | [ -n "$3" ] && timeout=$3 26 | local extra_params=$4 27 | if /usr/bin/curl --help all | grep -q "\-\-retry-all-errors"; then 28 | extra_params="--retry-all-errors ${extra_params}" 29 | fi 30 | local status=$(/usr/bin/curl -I -o /dev/null -skL ${extra_params} --connect-timeout ${timeout} --retry ${try} -w %{http_code} "$url") 31 | case "$status" in 32 | 204) 33 | status=200 34 | ;; 35 | esac 36 | echo $status 37 | } 38 | 39 | test_proxy() { 40 | local result=0 41 | local status=$(test_url "${probe_url}" ${retry_num} ${connect_timeout} "-x socks5h://127.0.0.1:${socks_port}") 42 | if [ "$status" = "200" ]; then 43 | result=0 44 | else 45 | local status2=$(test_url "https://www.baidu.com" ${retry_num} ${connect_timeout}) 46 | if [ "$status2" = "200" ]; then 47 | result=1 48 | else 49 | result=2 50 | ping -c 3 -W 1 223.5.5.5 > /dev/null 2>&1 51 | [ $? -eq 0 ] && { 52 | result=1 53 | } 54 | fi 55 | fi 56 | echo $result 57 | } 58 | 59 | test_node() { 60 | local node_id=$1 61 | local _type=$(echo $(config_n_get ${node_id} type) | tr 'A-Z' 'a-z') 62 | [ -n "${_type}" ] && { 63 | local _tmp_port=$(/usr/share/${CONFIG}/app.sh get_new_port 61080 tcp,udp) 64 | /usr/share/${CONFIG}/app.sh run_socks flag="test_node_${node_id}" node=${node_id} bind=127.0.0.1 socks_port=${_tmp_port} config_file=test_node_${node_id}.json 65 | local curlx="socks5h://127.0.0.1:${_tmp_port}" 66 | sleep 1s 67 | local _proxy_status=$(test_url "${probe_url}" ${retry_num} ${connect_timeout} "-x $curlx") 68 | # Finish SS Plug-in process 69 | local pid_file="/tmp/etc/${CONFIG}/test_node_${node_id}_plugin.pid" 70 | [ -s "$pid_file" ] && kill -9 "$(head -n 1 "$pid_file")" >/dev/null 2>&1 71 | pgrep -af "test_node_${node_id}" | awk '! /socks_auto_switch\.sh/{print $1}' | xargs kill -9 >/dev/null 2>&1 72 | rm -rf /tmp/etc/${CONFIG}/test_node_${node_id}*.* 73 | if [ "${_proxy_status}" -eq 200 ]; then 74 | return 0 75 | fi 76 | } 77 | return 1 78 | } 79 | 80 | test_auto_switch() { 81 | flag=$((flag + 1)) 82 | local b_nodes=$1 83 | local now_node=$2 84 | [ -z "$now_node" ] && { 85 | if [ -n "$(/usr/share/${CONFIG}/app.sh get_cache_var "socks_${id}")" ]; then 86 | now_node=$(/usr/share/${CONFIG}/app.sh get_cache_var "socks_${id}") 87 | else 88 | #echolog "SocksSwitch detection:unknown error" 89 | return 1 90 | fi 91 | } 92 | 93 | [ $flag -le 1 ] && { 94 | main_node=$now_node 95 | } 96 | 97 | local status=$(test_proxy) 98 | if [ "$status" = "2" ]; then 99 | echolog "SocksSwitch detection:Unable to connect to network,Please check if the network is normal!" 100 | return 2 101 | fi 102 | 103 | #Check whether the master node can be used 104 | if [ "$restore_switch" = "1" ] && [ -n "$main_node" ] && [ "$now_node" != "$main_node" ]; then 105 | test_node ${main_node} 106 | [ $? -eq 0 ] && { 107 | #Master node is normal,Switch to master node 108 | echolog "SocksSwitch detection:${id}master node【$(config_n_get $main_node type):[$(config_n_get $main_node remarks)]】normal,Switch to master node!" 109 | /usr/share/${CONFIG}/app.sh socks_node_switch flag=${id} new_node=${main_node} 110 | [ $? -eq 0 ] && { 111 | echolog "SocksSwitch detection:${id}Node switching completed!" 112 | } 113 | return 0 114 | } 115 | fi 116 | 117 | if [ "$status" = "0" ]; then 118 | #echolog "SocksSwitch detection:${id}【$(config_n_get $now_node type):[$(config_n_get $now_node remarks)]】normal。" 119 | return 0 120 | elif [ "$status" = "1" ]; then 121 | local new_node msg 122 | if [ "$backup_node_num" -gt 1 ]; then 123 | # When there are multiple backup nodes 124 | local first_node found node 125 | for node in $b_nodes; do 126 | [ -z "$first_node" ] && first_node="$node" # Record the first node 127 | [ "$found" = "1" ] && { new_node="$node"; break; } # After finding the current node, remove the next one 128 | [ "$node" = "$now_node" ] && found=1 # Mark the current node found 129 | done 130 | # If the current node is not found,Or the current node is the last one,Just take the first node 131 | [ -z "$new_node" ] && new_node="$first_node" 132 | msg="switch to$([ "$now_node" = "$main_node" ] && echo Standby node || echo next standby node)Detection!" 133 | else 134 | # When there is only one backup node,Polling with the master node 135 | new_node=$([ "$now_node" = "$main_node" ] && echo "$b_nodes" || echo "$main_node") 136 | msg="switch to$([ "$now_node" = "$main_node" ] && echo Standby node || echo master node)Detection!" 137 | fi 138 | echolog "SocksSwitch detection:${id}【$(config_n_get $now_node type):[$(config_n_get $now_node remarks)]】abnormal,$msg" 139 | test_node ${new_node} 140 | if [ $? -eq 0 ]; then 141 | # [ "$restore_switch" = "0" ] && { 142 | # uci set $CONFIG.${id}.node=$new_node 143 | # [ -z "$(echo $b_nodes | grep $main_node)" ] && uci add_list $CONFIG.${id}.autoswitch_backup_node=$main_node 144 | # uci commit $CONFIG 145 | # } 146 | echolog "SocksSwitch detection:${id}【$(config_n_get $new_node type):[$(config_n_get $new_node remarks)]】normal,switch to this node!" 147 | /usr/share/${CONFIG}/app.sh socks_node_switch flag=${id} new_node=${new_node} 148 | [ $? -eq 0 ] && { 149 | echolog "SocksSwitch detection:${id}Node switching completed!" 150 | } 151 | return 0 152 | else 153 | test_auto_switch "${b_nodes}" ${new_node} 154 | fi 155 | fi 156 | } 157 | 158 | start() { 159 | id=$1 160 | LOCK_FILE=${LOCK_FILE_DIR}/${CONFIG}_socks_auto_switch_${id}.lock 161 | main_node=$(config_n_get $id node) 162 | socks_port=$(config_n_get $id port 0) 163 | delay=$(config_n_get $id autoswitch_testing_time 30) 164 | connect_timeout=$(config_n_get $id autoswitch_connect_timeout 3) 165 | retry_num=$(config_n_get $id autoswitch_retry_num 1) 166 | restore_switch=$(config_n_get $id autoswitch_restore_switch 0) 167 | probe_url=$(config_n_get $id autoswitch_probe_url "https://www.google.com/generate_204") 168 | backup_node=$(config_n_get $id autoswitch_backup_node) 169 | if [ -n "$backup_node" ]; then 170 | backup_node=$(echo "$backup_node" | tr -s ' ' '\n' | uniq | tr -s '\n' ' ') 171 | backup_node_num=$(printf "%s\n" "$backup_node" | wc -w) 172 | if [ "$backup_node_num" -eq 1 ]; then 173 | [ "$main_node" = "$backup_node" ] && return 174 | fi 175 | else 176 | return 177 | fi 178 | while [ -n "$backup_node" ]; do 179 | [ -f "$LOCK_FILE" ] && { 180 | sleep 6s 181 | continue 182 | } 183 | pgrep -af "${CONFIG}/" | awk '/app\.sh.*(start|stop)/ || /nftables\.sh/ || /iptables\.sh/ { found = 1 } END { exit !found }' && { 184 | # Not detected during specific task execution 185 | sleep 6s 186 | continue 187 | } 188 | touch $LOCK_FILE 189 | test_auto_switch "$backup_node" 190 | rm -f $LOCK_FILE 191 | sleep ${delay} 192 | done 193 | } 194 | 195 | start $@ 196 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/view/passwall2/global/backup.htm: -------------------------------------------------------------------------------- 1 | <% 2 | local api = require "luci.passwall2.api" 3 | -%> 4 | 5 |
6 |

<%:Backup and Restore%>

7 |
8 | <%:Backup or Restore Client and Server Configurations.%> 9 |
10 | <%:Note: Restoring configurations across different versions may cause compatibility issues.%> 11 |
12 |
13 | 14 |
15 | 16 |
17 | 18 |
19 |
20 | 21 |
22 | 23 |
24 | 25 |
26 |
27 | 28 |
29 | 30 |
31 | 32 |
33 |
34 | 35 |
36 | 37 | 49 | 50 | 92 | 93 | 225 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua: -------------------------------------------------------------------------------- 1 | local api = require "luci.passwall2.api" 2 | local appname = api.appname 3 | local uci = api.uci 4 | local has_ss = api.is_finded("ss-redir") 5 | local has_ss_rust = api.is_finded("sslocal") 6 | local has_singbox = api.finded_com("sing-box") 7 | local has_xray = api.finded_com("xray") 8 | local has_hysteria2 = api.finded_com("hysteria") 9 | local ss_type = {} 10 | local trojan_type = {} 11 | local vmess_type = {} 12 | local vless_type = {} 13 | local hysteria2_type = {} 14 | if has_ss then 15 | local s = "shadowsocks-libev" 16 | table.insert(ss_type, s) 17 | end 18 | if has_ss_rust then 19 | local s = "shadowsocks-rust" 20 | table.insert(ss_type, s) 21 | end 22 | if has_singbox then 23 | local s = "sing-box" 24 | table.insert(trojan_type, s) 25 | table.insert(ss_type, s) 26 | table.insert(vmess_type, s) 27 | table.insert(vless_type, s) 28 | table.insert(hysteria2_type, s) 29 | end 30 | if has_xray then 31 | local s = "xray" 32 | table.insert(trojan_type, s) 33 | table.insert(ss_type, s) 34 | table.insert(vmess_type, s) 35 | table.insert(vless_type, s) 36 | end 37 | if has_hysteria2 then 38 | local s = "hysteria2" 39 | table.insert(hysteria2_type, s) 40 | end 41 | 42 | m = Map(appname) 43 | api.set_apply_on_parse(m) 44 | 45 | if api.is_js_luci() then 46 | m.on_after_apply = function(self) 47 | uci:foreach(appname, "subscribe_list", function(e) 48 | uci:delete(appname, e[".name"], "md5") 49 | end) 50 | uci:commit(appname) 51 | end 52 | end 53 | 54 | m.render = function(self, ...) 55 | Map.render(self, ...) 56 | api.optimize_cbi_ui() 57 | end 58 | 59 | -- [[ Subscribe Settings ]]-- 60 | s = m:section(TypedSection, "global_subscribe", "") 61 | s.anonymous = true 62 | 63 | function m.commit_handler(self) 64 | if self.no_commit then 65 | return 66 | end 67 | self.uci:foreach(appname, "subscribe_list", function(e) 68 | self:del(e[".name"], "md5") 69 | end) 70 | end 71 | 72 | o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode")) 73 | o:value("0", translate("Close")) 74 | o:value("1", translate("Discard List")) 75 | o:value("2", translate("Keep List")) 76 | o:value("3", translate("Discard List,But Keep List First")) 77 | o:value("4", translate("Keep List,But Discard List First")) 78 | 79 | o = s:option(DynamicList, "filter_discard_list", translate("Discard List")) 80 | 81 | o = s:option(DynamicList, "filter_keep_list", translate("Keep List")) 82 | 83 | if #ss_type > 0 then 84 | o = s:option(ListValue, "ss_type", translatef("%s Node Use Type", "Shadowsocks")) 85 | for key, value in pairs(ss_type) do 86 | o:value(value) 87 | end 88 | end 89 | 90 | if #trojan_type > 0 then 91 | o = s:option(ListValue, "trojan_type", translatef("%s Node Use Type", "Trojan")) 92 | for key, value in pairs(trojan_type) do 93 | o:value(value) 94 | end 95 | end 96 | 97 | if #vmess_type > 0 then 98 | o = s:option(ListValue, "vmess_type", translatef("%s Node Use Type", "VMess")) 99 | for key, value in pairs(vmess_type) do 100 | o:value(value) 101 | end 102 | if has_xray then 103 | o.default = "xray" 104 | end 105 | end 106 | 107 | if #vless_type > 0 then 108 | o = s:option(ListValue, "vless_type", translatef("%s Node Use Type", "VLESS")) 109 | for key, value in pairs(vless_type) do 110 | o:value(value) 111 | end 112 | if has_xray then 113 | o.default = "xray" 114 | end 115 | end 116 | 117 | if #hysteria2_type > 0 then 118 | o = s:option(ListValue, "hysteria2_type", translatef("%s Node Use Type", "Hysteria2")) 119 | for key, value in pairs(hysteria2_type) do 120 | o:value(value) 121 | end 122 | if has_hysteria2 then 123 | o.default = "hysteria2" 124 | end 125 | end 126 | 127 | if #ss_type > 0 or #trojan_type > 0 or #vmess_type > 0 or #vless_type > 0 or #hysteria2_type > 0 then 128 | o.description = string.format("%s", 129 | translate("The configured type also applies to the core specified when manually importing nodes.")) 130 | end 131 | 132 | o = s:option(ListValue, "domain_strategy", "Sing-box " .. translate("Domain Strategy"), translate("Set the default domain resolution strategy for the sing-box node.")) 133 | o.default = "" 134 | o:value("", translate("Auto")) 135 | o:value("prefer_ipv4", translate("Prefer IPv4")) 136 | o:value("prefer_ipv6", translate("Prefer IPv6")) 137 | o:value("ipv4_only", translate("IPv4 Only")) 138 | o:value("ipv6_only", translate("IPv6 Only")) 139 | 140 | ---- Subscribe Delete All 141 | o = s:option(DummyValue, "_stop", translate("Delete All Subscribe Node")) 142 | o.rawhtml = true 143 | function o.cfgvalue(self, section) 144 | return string.format( 145 | [[]], 146 | translate("Delete All Subscribe Node")) 147 | end 148 | 149 | o = s:option(DummyValue, "_update", translate("Manual subscription All")) 150 | o.rawhtml = true 151 | o.cfgvalue = function(self, section) 152 | return string.format([[ 153 | ]], 154 | translate("Manual subscription All")) 155 | end 156 | 157 | s = m:section(TypedSection, "subscribe_list", "", "" .. translate("When adding a new subscription, please save and apply before manually subscribing. If you only change the subscription URL, you can subscribe manually, and the system will save it automatically.") .. "") 158 | s.addremove = true 159 | s.anonymous = true 160 | s.sortable = true 161 | s.template = "cbi/tblsection" 162 | s.extedit = api.url("node_subscribe_config", "%s") 163 | function s.create(e, t) 164 | local id = TypedSection.create(e, t) 165 | luci.http.redirect(e.extedit:format(id)) 166 | end 167 | 168 | o = s:option(Value, "remark", translate("Remarks")) 169 | o.width = "auto" 170 | o.rmempty = false 171 | o.validate = function(self, value, t) 172 | if value then 173 | local count = 0 174 | m.uci:foreach(appname, "subscribe_list", function(e) 175 | if e[".name"] ~= t and e["remark"] == value then 176 | count = count + 1 177 | end 178 | end) 179 | if count > 0 then 180 | return nil, translate("This remark already exists, please change a new remark.") 181 | end 182 | return value 183 | end 184 | end 185 | 186 | o = s:option(DummyValue, "_node_count", translate("Subscribe Info")) 187 | o.rawhtml = true 188 | o.cfgvalue = function(t, n) 189 | local remark = m:get(n, "remark") or "" 190 | local str = m:get(n, "rem_traffic") or "" 191 | local expired_date = m:get(n, "expired_date") or "" 192 | if expired_date ~= "" then 193 | str = str .. (str ~= "" and "/" or "") .. expired_date 194 | end 195 | str = str ~= "" and "
" .. str or "" 196 | local num = 0 197 | m.uci:foreach(appname, "nodes", function(s) 198 | if s["group"] ~= "" and s["group"] == remark then 199 | num = num + 1 200 | end 201 | end) 202 | return string.format("%s%s", translate("Node num") .. ": " .. num, str) 203 | end 204 | 205 | o = s:option(Value, "url", translate("Subscribe URL")) 206 | o.width = "auto" 207 | o.rmempty = false 208 | 209 | o = s:option(DummyValue, "_remove", translate("Delete the subscribed node")) 210 | o.rawhtml = true 211 | function o.cfgvalue(self, section) 212 | local remark = m:get(section, "remark") or "" 213 | return string.format( 214 | [[]], 215 | remark, translate("Delete the subscribed node")) 216 | end 217 | 218 | o = s:option(DummyValue, "_update", translate("Manual subscription")) 219 | o.rawhtml = true 220 | o.cfgvalue = function(self, section) 221 | return string.format([[ 222 | ]], 223 | section, translate("Manual subscription")) 224 | end 225 | 226 | s:append(Template(appname .. "/node_subscribe/js")) 227 | 228 | return m 229 | -------------------------------------------------------------------------------- /luci-app-passwall2/root/usr/share/passwall2/0_default_config: -------------------------------------------------------------------------------- 1 | 2 | config global 3 | option enabled '0' 4 | option node_socks_port '1070' 5 | option localhost_proxy '1' 6 | option client_proxy '1' 7 | option socks_enabled '0' 8 | option acl_enable '0' 9 | option node 'myshunt' 10 | option direct_dns_protocol 'auto' 11 | option direct_dns_query_strategy 'UseIP' 12 | option remote_dns_protocol 'tcp' 13 | option remote_dns '1.1.1.1' 14 | option remote_dns_query_strategy 'UseIPv4' 15 | option dns_hosts 'cloudflare-dns.com 1.1.1.1 16 | dns.google.com 8.8.8.8' 17 | option log_node '1' 18 | option loglevel 'error' 19 | 20 | config global_haproxy 21 | option balancing_enable '0' 22 | 23 | config global_delay 24 | option start_daemon '1' 25 | option start_delay '60' 26 | 27 | config global_forwarding 28 | option tcp_no_redir_ports 'disable' 29 | option udp_no_redir_ports 'disable' 30 | option tcp_redir_ports '22,25,53,80,143,443,465,587,853,873,993,995,5222,8080,8443,9418' 31 | option udp_redir_ports '1:65535' 32 | option accept_icmp '0' 33 | option use_nft '0' 34 | option tcp_proxy_way 'redirect' 35 | option ipv6_tproxy '0' 36 | 37 | config global_xray 38 | option sniffing_override_dest '0' 39 | 40 | config global_other 41 | option auto_detection_time 'tcping' 42 | option show_node_info '0' 43 | 44 | config global_rules 45 | option auto_update '0' 46 | option geosite_update '1' 47 | option geoip_update '1' 48 | option v2ray_location_asset '/usr/share/v2ray/' 49 | option enable_geoview '1' 50 | 51 | config global_app 52 | option xray_file '/usr/bin/xray' 53 | option hysteria_file '/usr/bin/hysteria' 54 | option sing_box_file '/usr/bin/sing-box' 55 | 56 | config global_subscribe 57 | option filter_keyword_mode '1' 58 | list filter_discard_list '距离下次重置剩余' 59 | list filter_discard_list '套餐到期' 60 | list filter_discard_list '过期时间' 61 | list filter_discard_list '剩余流量' 62 | list filter_discard_list 'QQ群' 63 | list filter_discard_list '官网' 64 | 65 | config global_singbox 66 | option sniff_override_destination '0' 67 | option geoip_path '/usr/share/singbox/geoip.db' 68 | option geoip_url 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.db' 69 | option geosite_path '/usr/share/singbox/geosite.db' 70 | option geosite_url 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.db' 71 | 72 | config nodes 'myshunt' 73 | option remarks '分流总节点' 74 | option type 'Xray' 75 | option protocol '_shunt' 76 | option DirectGame '_direct' 77 | option ProxyGame '_default' 78 | option Direct '_direct' 79 | option GooglePlay '_default' 80 | option Proxy '_default' 81 | option China '_direct' 82 | option QUIC '_blackhole' 83 | option default_node '_direct' 84 | option domainStrategy 'IPOnDemand' 85 | option domainMatcher 'hybrid' 86 | 87 | config shunt_rules 'DirectGame' 88 | option remarks 'DirectGame' 89 | option network 'tcp,udp' 90 | option domain_list '# steam直连域名获取国内CDN走国内线路下载 91 | cm.steampowered.com 92 | steamserver.net 93 | 94 | # steam国内CDN华为云 95 | steampipe.steamcontent.tnkjmec.com 96 | # steam国内CDN白山云 97 | st.dl.eccdnx.com 98 | st.dl.bscstorage.net 99 | st.dl.pinyuncloud.com 100 | # steam国内CDN新流云(原金山云)(支持ipv6) 101 | dl.steam.clngaa.com 102 | # steam国内CDN网宿 103 | cdn.mileweb.cs.steampowered.com.8686c.com 104 | cdn-ws.content.steamchina.com 105 | # steam国内CDN腾讯云 (蒸汽中国独占) 106 | cdn-qc.content.steamchina.com 107 | # steam国内CDN阿里云(支持ipv6) 108 | cdn-ali.content.steamchina.com 109 | xz.pphimalayanrt.com 110 | lv.queniujq.cn 111 | alibaba.cdn.steampipe.steamcontent.com 112 | 113 | # 国内游戏geosite域名 114 | geosite:category-games@cn 115 | rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geosite/geosite-category-games%40cn.srs 116 | ' 117 | 118 | option ip_list '# steam直连IP 119 | 45.121.184.0/24 120 | 103.10.124.0/23 121 | 103.28.54.0/24 122 | 146.66.152.0/24 123 | 146.66.155.0/24 124 | 153.254.86.0/24 125 | 155.133.224.0/22 126 | 155.133.230.0/24 127 | 155.133.232.0/23 128 | 155.133.234.0/24 129 | 155.133.236.0/22 130 | 155.133.240.0/23 131 | 155.133.244.0/23 132 | 155.133.246.0/24 133 | 155.133.248.0/21 134 | 162.254.192.0/21 135 | 185.25.182.0/23 136 | 190.217.32.0/22 137 | 192.69.96.0/22 138 | 205.196.6.0/24 139 | 208.64.200.0/22 140 | 208.78.164.0/22 141 | 205.185.194.0/24' 142 | 143 | config shunt_rules 'ProxyGame' 144 | option remarks 'ProxyGame' 145 | option domain_list '# steam 商店/客服/聊天/网页布局/API/二维码 代理URL 146 | steamcommunity.com 147 | www.steamcommunity.com 148 | store.steampowered.com 149 | checkout.steampowered.com 150 | api.steampowered.com 151 | help.steampowered.com 152 | login.steampowered.com 153 | store.akamai.steamstatic.com 154 | steambroadcast.akamaized.net 155 | steamvideo-a.akamaihd.net 156 | steamusercontent-a.akamaihd.net 157 | steamstore-a.akamaihd.net 158 | steamcommunity-a.akamaihd.net 159 | steamcdn-a.akamaihd.net 160 | steamuserimages-a.akamaihd.net 161 | community.akamai.steamstatic.com 162 | avatars.akamai.steamstatic.com 163 | community.steamstatic.com 164 | cdn.akamai.steamstatic.com 165 | avatars.steamstatic.com 166 | shared.akamai.steamstatic.com 167 | clan.akamai.steamstatic.com 168 | cdn.cloudflare.steamstatic.com 169 | community.cloudflare.steamstatic.com 170 | store.cloudflare.steamstatic.com 171 | avatars.cloudflare.steamstatic.com 172 | clan.cloudflare.steamstatic.com 173 | shared.cloudflare.steamstatic.com 174 | steam-chat.com 175 | steamcloud-ugc.storage.googleapis.com 176 | steamcloud-eu-ams.storage.googleapis.com 177 | steamcloud-eu-fra.storage.googleapis.com 178 | steamcloud-finland.storage.googleapis.com 179 | steamcloud-saopaulo.storage.googleapis.com 180 | steamcloud-singapore.storage.googleapis.com 181 | steamcloud-sydney.storage.googleapis.com 182 | steamcloud-taiwan.storage.googleapis.com 183 | steamcloud-eu.storage.googleapis.com 184 | 185 | geosite:category-games 186 | rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geosite/geosite-category-games.srs' 187 | 188 | config shunt_rules 'Direct' 189 | option network 'tcp,udp' 190 | option remarks 'Direct' 191 | option ip_list '114.114.114.114 192 | 114.114.115.115 193 | 223.5.5.5 194 | 223.6.6.6 195 | 119.29.29.29 196 | 180.76.76.76 197 | ' 198 | option domain_list 'apple.com 199 | microsoft.com 200 | dyndns.com 201 | steamcontent.com 202 | dl.steam.clngaa.com 203 | dl.steam.ksyna.com 204 | st.dl.bscstorage.net 205 | st.dl.eccdnx.com 206 | st.dl.pinyuncloud.com 207 | cdn.mileweb.cs.steampowered.com.8686c.com 208 | cdn-ws.content.steamchina.com 209 | cdn-qc.content.steamchina.com 210 | cdn-ali.content.steamchina.com 211 | epicgames-download1-1251447533.file.myqcloud.com' 212 | 213 | config shunt_rules 'GooglePlay' 214 | option remarks 'GooglePlay' 215 | option network 'tcp,udp' 216 | option domain_list 'domain:googleapis.cn 217 | domain:googleapis.com 218 | domain:xn--ngstr-lra8j.com' 219 | 220 | config shunt_rules 'Netflix' 221 | option remarks 'Netflix' 222 | option network 'tcp,udp' 223 | option domain_list 'geosite:netflix 224 | rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geosite/geosite-netflix.srs' 225 | 226 | config shunt_rules 'OpenAI' 227 | option remarks 'OpenAI' 228 | option network 'tcp,udp' 229 | option domain_list 'geosite:openai 230 | rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geosite/geosite-openai.srs' 231 | 232 | config shunt_rules 'Proxy' 233 | option network 'tcp,udp' 234 | option remarks 'Proxy' 235 | option domain_list 'geosite:geolocation-!cn 236 | rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geosite/geosite-geolocation-!cn.srs' 237 | option ip_list '149.154.160.0/20 238 | 91.108.4.0/22 239 | 91.108.56.0/24 240 | 109.239.140.0/24 241 | 67.198.55.0/24 242 | 8.8.4.4 243 | 8.8.8.8 244 | 208.67.222.222 245 | 208.67.220.220 246 | 1.1.1.1 247 | 1.1.1.2 248 | 1.0.0.1 249 | 9.9.9.9 250 | 149.112.112.112 251 | 2001:67c:4e8::/48 252 | 2001:b28:f23c::/48 253 | 2001:b28:f23d::/48 254 | 2001:b28:f23f::/48 255 | 2001:b28:f242::/48 256 | 2001:4860:4860::8888 257 | 2001:4860:4860::8844 258 | 2606:4700:4700::1111 259 | 2606:4700:4700::1001 260 | ' 261 | 262 | config shunt_rules 'China' 263 | option remarks 'China' 264 | option network 'tcp,udp' 265 | option ip_list 'geoip:cn 266 | rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geoip/geoip-cn.srs' 267 | option domain_list 'geosite:cn 268 | rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geosite/geosite-cn.srs' 269 | 270 | config shunt_rules 'QUIC' 271 | option remarks 'QUIC' 272 | option port '443' 273 | option network 'udp' 274 | 275 | config shunt_rules 'UDP' 276 | option remarks 'UDP' 277 | option network 'udp' 278 | -------------------------------------------------------------------------------- /luci-app-passwall2/root/usr/share/passwall2/haproxy.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | local api = require ("luci.passwall2.api") 4 | local appname = "passwall2" 5 | local fs = api.fs 6 | local jsonc = api.jsonc 7 | local uci = api.uci 8 | local sys = api.sys 9 | 10 | local log = function(...) 11 | api.log(...) 12 | end 13 | 14 | function get_ip_port_from(str) 15 | local result_port = sys.exec("echo -n " .. str .. " | sed -n 's/^.*[:#]\\([0-9]*\\)$/\\1/p'") 16 | local result_ip = sys.exec(string.format("__host=%s;__varport=%s;", str, result_port) .. "echo -n ${__host%%${__varport:+[:#]${__varport}*}}") 17 | return result_ip, result_port 18 | end 19 | 20 | local new_port 21 | local function get_new_port() 22 | if new_port then 23 | new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port %s tcp)", appname, new_port + 1))) 24 | else 25 | new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port auto tcp)", appname))) 26 | end 27 | return new_port 28 | end 29 | 30 | local var = api.get_args(arg) 31 | local haproxy_path = var["-path"] 32 | local haproxy_conf = var["-conf"] 33 | local haproxy_dns = var["-dns"] or "119.29.29.29:53,223.5.5.5:53" 34 | 35 | local cpu_thread = sys.exec('echo -n $(cat /proc/cpuinfo | grep "processor" | wc -l)') or "1" 36 | local health_check_type = uci:get(appname, "@global_haproxy[0]", "health_check_type") or "tcp" 37 | local health_check_inter = uci:get(appname, "@global_haproxy[0]", "health_check_inter") or "10" 38 | local console_port = uci:get(appname, "@global_haproxy[0]", "console_port") 39 | local bind_local = uci:get(appname, "@global_haproxy[0]", "bind_local") or "0" 40 | local bind_address = "0.0.0.0" 41 | if bind_local == "1" then bind_address = "127.0.0.1" end 42 | 43 | log("HAPROXY load balancing:") 44 | log(string.format(" * console port:%s", console_port)) 45 | fs.mkdir(haproxy_path) 46 | local haproxy_file = haproxy_path .. "/" .. haproxy_conf 47 | 48 | local f_out = io.open(haproxy_file, "a") 49 | 50 | local haproxy_config = [[ 51 | global 52 | daemon 53 | log 127.0.0.1 local2 54 | maxconn 60000 55 | stats socket {{path}}/haproxy.sock 56 | nbthread {{nbthread}} 57 | external-check 58 | insecure-fork-wanted 59 | 60 | defaults 61 | mode tcp 62 | log global 63 | option tcplog 64 | option dontlognull 65 | option http-server-close 66 | #option forwardfor except 127.0.0.0/8 67 | option redispatch 68 | retries 2 69 | timeout http-request 10s 70 | timeout queue 1m 71 | timeout connect 10s 72 | timeout client 1m 73 | timeout server 1m 74 | timeout http-keep-alive 10s 75 | timeout check 10s 76 | maxconn 3000 77 | 78 | resolvers mydns 79 | resolve_retries 1 80 | timeout resolve 5s 81 | hold valid 600s 82 | {{dns}} 83 | ]] 84 | 85 | haproxy_config = haproxy_config:gsub("{{path}}", haproxy_path) 86 | haproxy_config = haproxy_config:gsub("{{nbthread}}", cpu_thread) 87 | 88 | local mydns = "" 89 | local index = 0 90 | string.gsub(haproxy_dns, '[^' .. "," .. ']+', function(w) 91 | index = index + 1 92 | local s = w:gsub("#", ":") 93 | if not s:find(":") then 94 | s = s .. ":53" 95 | end 96 | mydns = mydns .. (index > 1 and "\n" or "") .. " " .. string.format("nameserver dns%s %s", index, s) 97 | end) 98 | haproxy_config = haproxy_config:gsub("{{dns}}", mydns) 99 | 100 | f_out:write(haproxy_config) 101 | 102 | local listens = {} 103 | 104 | uci:foreach(appname, "haproxy_config", function(t) 105 | if t.enabled == "1" then 106 | local server_remark 107 | local server_address 108 | local server_port 109 | local lbss = t.lbss 110 | local listen_port = tonumber(t.haproxy_port) or 0 111 | local server_node = uci:get_all(appname, lbss) 112 | if server_node and server_node.address and server_node.port then 113 | server_remark = server_node.address .. ":" .. server_node.port 114 | server_address = server_node.address 115 | server_port = server_node.port 116 | t.origin_address = server_address 117 | t.origin_port = server_port 118 | if health_check_type == "passwall_logic" then 119 | if server_node.type ~= "Socks" then 120 | local relay_port = server_node.port 121 | new_port = get_new_port() 122 | local config_file = string.format("haproxy_%s_%s.json", t[".name"], new_port) 123 | sys.call(string.format('/usr/share/%s/app.sh run_socks "%s"> /dev/null', 124 | appname, 125 | string.format("flag=%s node=%s bind=%s socks_port=%s config_file=%s", 126 | new_port, --flag 127 | server_node[".name"], --node 128 | "127.0.0.1", --bind 129 | new_port, --socks port 130 | config_file --config file 131 | ) 132 | ) 133 | ) 134 | server_address = "127.0.0.1" 135 | server_port = new_port 136 | end 137 | end 138 | else 139 | server_address, server_port = get_ip_port_from(lbss) 140 | server_remark = server_address .. ":" .. server_port 141 | t.origin_address = server_address 142 | t.origin_port = server_port 143 | end 144 | if server_address and server_port and listen_port > 0 then 145 | if not listens[listen_port] then 146 | listens[listen_port] = {} 147 | end 148 | t.server_remark = server_remark 149 | t.server_address = server_address 150 | t.server_port = server_port 151 | table.insert(listens[listen_port], t) 152 | else 153 | log(" - throw away1obviously invalid node") 154 | end 155 | end 156 | end) 157 | 158 | local sortTable = {} 159 | for i in pairs(listens) do 160 | if i ~= nil then 161 | table.insert(sortTable, i) 162 | end 163 | end 164 | table.sort(sortTable, function(a,b) return (a < b) end) 165 | 166 | for i, port in pairs(sortTable) do 167 | log(" + Entrance %s:%s" % {bind_address, port}) 168 | 169 | f_out:write("\n" .. string.format([[ 170 | listen %s 171 | bind %s:%s 172 | mode tcp 173 | balance roundrobin 174 | ]], port, bind_address, port)) 175 | 176 | if health_check_type == "passwall_logic" then 177 | f_out:write(string.format([[ 178 | option external-check 179 | external-check command "/usr/share/passwall2/haproxy_check.sh" 180 | ]], port, port)) 181 | end 182 | 183 | local count_M, count_B = 1, 1 184 | for i, o in ipairs(listens[port]) do 185 | local remark = o.server_remark or "" 186 | -- Prevent duplicate names from causing inability to run 187 | if tostring(o.backup) ~= "1" then 188 | remark = "M" .. count_M .. "-" .. remark 189 | count_M = count_M + 1 190 | else 191 | remark = "B" .. count_B .. "-" .. remark 192 | count_B = count_B + 1 193 | end 194 | local server = o.server_address .. ":" .. o.server_port 195 | local server_conf = "server {{remark}} {{server}} weight {{weight}} {{resolvers}} check inter {{inter}} rise 1 fall 3 {{backup}}" 196 | server_conf = server_conf:gsub("{{remark}}", remark) 197 | server_conf = server_conf:gsub("{{server}}", server) 198 | server_conf = server_conf:gsub("{{weight}}", o.lbweight) 199 | local resolvers = "resolvers mydns" 200 | if api.is_ip(o.server_address) then 201 | resolvers = "" 202 | end 203 | server_conf = server_conf:gsub("{{resolvers}}", resolvers) 204 | server_conf = server_conf:gsub("{{inter}}", tonumber(health_check_inter) .. "s") 205 | server_conf = server_conf:gsub("{{backup}}", tostring(o.backup) == "1" and "backup" or "") 206 | 207 | f_out:write(" " .. server_conf .. "\n") 208 | 209 | if o.export ~= "0" then 210 | sys.call(string.format("/usr/share/passwall2/app.sh add_ip2route %s %s", o.origin_address, o.export)) 211 | end 212 | 213 | log(string.format(" | - exit node:%s:%s,weight:%s", o.origin_address, o.origin_port, o.lbweight)) 214 | end 215 | end 216 | 217 | --Console configuration 218 | local console_user = uci:get(appname, "@global_haproxy[0]", "console_user") 219 | local console_password = uci:get(appname, "@global_haproxy[0]", "console_password") 220 | local str = [[ 221 | listen console 222 | bind 0.0.0.0:%s 223 | mode http 224 | stats refresh 30s 225 | stats uri / 226 | stats admin if TRUE 227 | %s 228 | ]] 229 | f_out:write("\n" .. string.format(str, console_port, (console_user and console_user ~= "" and console_password and console_password ~= "") and "stats auth " .. console_user .. ":" .. console_password or "")) 230 | 231 | f_out:close() 232 | 233 | --Built-in health checksURL 234 | if health_check_type == "passwall_logic" then 235 | local probeUrl = uci:get(appname, "@global_haproxy[0]", "health_probe_url") or "https://www.google.com/generate_204" 236 | local f_url = io.open(haproxy_path .. "/Probe_URL", "w") 237 | f_url:write(probeUrl) 238 | f_url:close() 239 | end 240 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua: -------------------------------------------------------------------------------- 1 | local api = require "luci.passwall2.api" 2 | local appname = api.appname 3 | 4 | m = Map(appname) 5 | m.redirect = api.url("node_subscribe") 6 | api.set_apply_on_parse(m) 7 | 8 | if not arg[1] or not m:get(arg[1]) then 9 | luci.http.redirect(m.redirect) 10 | end 11 | 12 | m.render = function(self, ...) 13 | Map.render(self, ...) 14 | api.optimize_cbi_ui() 15 | end 16 | 17 | local has_ss = api.is_finded("ss-redir") 18 | local has_ss_rust = api.is_finded("sslocal") 19 | local has_singbox = api.finded_com("sing-box") 20 | local has_xray = api.finded_com("xray") 21 | local has_hysteria2 = api.finded_com("hysteria") 22 | local ss_type = {} 23 | local trojan_type = {} 24 | local vmess_type = {} 25 | local vless_type = {} 26 | local hysteria2_type = {} 27 | if has_ss then 28 | local s = "shadowsocks-libev" 29 | table.insert(ss_type, s) 30 | end 31 | if has_ss_rust then 32 | local s = "shadowsocks-rust" 33 | table.insert(ss_type, s) 34 | end 35 | if has_singbox then 36 | local s = "sing-box" 37 | table.insert(trojan_type, s) 38 | table.insert(ss_type, s) 39 | table.insert(vmess_type, s) 40 | table.insert(vless_type, s) 41 | table.insert(hysteria2_type, s) 42 | end 43 | if has_xray then 44 | local s = "xray" 45 | table.insert(trojan_type, s) 46 | table.insert(ss_type, s) 47 | table.insert(vmess_type, s) 48 | table.insert(vless_type, s) 49 | end 50 | if has_hysteria2 then 51 | local s = "hysteria2" 52 | table.insert(hysteria2_type, s) 53 | end 54 | local nodes_table = {} 55 | for k, e in ipairs(api.get_valid_nodes()) do 56 | if e.node_type == "normal" then 57 | nodes_table[#nodes_table + 1] = { 58 | id = e[".name"], 59 | remark = e["remark"], 60 | type = e["type"], 61 | add_mode = e["add_mode"], 62 | chain_proxy = e["chain_proxy"] 63 | } 64 | end 65 | end 66 | 67 | s = m:section(NamedSection, arg[1]) 68 | s.addremove = false 69 | s.dynamic = false 70 | 71 | function m.commit_handler(self) 72 | self:del(arg[1], "md5") 73 | end 74 | 75 | o = s:option(Value, "remark", translate("Subscribe Remark")) 76 | o.rmempty = false 77 | 78 | o = s:option(TextValue, "url", translate("Subscribe URL")) 79 | o.rows = 5 80 | o.rmempty = false 81 | o.validate = function(self, value) 82 | if not value or value == "" then 83 | return nil, translate("URL cannot be empty") 84 | end 85 | return value:gsub("%s+", ""):gsub("%z", "") 86 | end 87 | 88 | o = s:option(Flag, "allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped.")) 89 | o.default = "1" 90 | o.rmempty = false 91 | 92 | o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode")) 93 | o.default = "5" 94 | o:value("0", translate("Close")) 95 | o:value("1", translate("Discard List")) 96 | o:value("2", translate("Keep List")) 97 | o:value("3", translate("Discard List,But Keep List First")) 98 | o:value("4", translate("Keep List,But Discard List First")) 99 | o:value("5", translate("Use global config")) 100 | 101 | o = s:option(DynamicList, "filter_discard_list", translate("Discard List")) 102 | o:depends("filter_keyword_mode", "1") 103 | o:depends("filter_keyword_mode", "3") 104 | o:depends("filter_keyword_mode", "4") 105 | 106 | o = s:option(DynamicList, "filter_keep_list", translate("Keep List")) 107 | o:depends("filter_keyword_mode", "2") 108 | o:depends("filter_keyword_mode", "3") 109 | o:depends("filter_keyword_mode", "4") 110 | 111 | if #ss_type > 0 then 112 | o = s:option(ListValue, "ss_type", translatef("%s Node Use Type", "Shadowsocks")) 113 | o.default = "global" 114 | o:value("global", translate("Use global config")) 115 | for key, value in pairs(ss_type) do 116 | o:value(value) 117 | end 118 | end 119 | 120 | if #trojan_type > 0 then 121 | o = s:option(ListValue, "trojan_type", translatef("%s Node Use Type", "Trojan")) 122 | o.default = "global" 123 | o:value("global", translate("Use global config")) 124 | for key, value in pairs(trojan_type) do 125 | o:value(value) 126 | end 127 | end 128 | 129 | if #vmess_type > 0 then 130 | o = s:option(ListValue, "vmess_type", translatef("%s Node Use Type", "VMess")) 131 | o.default = "global" 132 | o:value("global", translate("Use global config")) 133 | for key, value in pairs(vmess_type) do 134 | o:value(value) 135 | end 136 | end 137 | 138 | if #vless_type > 0 then 139 | o = s:option(ListValue, "vless_type", translatef("%s Node Use Type", "VLESS")) 140 | o.default = "global" 141 | o:value("global", translate("Use global config")) 142 | for key, value in pairs(vless_type) do 143 | o:value(value) 144 | end 145 | end 146 | 147 | if #hysteria2_type > 0 then 148 | o = s:option(ListValue, "hysteria2_type", translatef("%s Node Use Type", "Hysteria2")) 149 | o.default = "global" 150 | o:value("global", translate("Use global config")) 151 | for key, value in pairs(hysteria2_type) do 152 | o:value(value) 153 | end 154 | end 155 | 156 | o = s:option(ListValue, "domain_strategy", "Sing-box " .. translate("Domain Strategy"), translate("Set the default domain resolution strategy for the sing-box node.")) 157 | o.default = "global" 158 | o:value("global", translate("Use global config")) 159 | o:value("", translate("Auto")) 160 | o:value("prefer_ipv4", translate("Prefer IPv4")) 161 | o:value("prefer_ipv6", translate("Prefer IPv6")) 162 | o:value("ipv4_only", translate("IPv4 Only")) 163 | o:value("ipv6_only", translate("IPv6 Only")) 164 | 165 | ---- Enable auto update subscribe 166 | o = s:option(Flag, "auto_update", translate("Enable auto update subscribe")) 167 | o.default = 0 168 | o.rmempty = false 169 | 170 | ---- Week Update 171 | o = s:option(ListValue, "week_update", translate("Update Mode")) 172 | o:value(8, translate("Loop Mode")) 173 | o:value(7, translate("Every day")) 174 | o:value(1, translate("Every Monday")) 175 | o:value(2, translate("Every Tuesday")) 176 | o:value(3, translate("Every Wednesday")) 177 | o:value(4, translate("Every Thursday")) 178 | o:value(5, translate("Every Friday")) 179 | o:value(6, translate("Every Saturday")) 180 | o:value(0, translate("Every Sunday")) 181 | o.default = 7 182 | o:depends("auto_update", true) 183 | o.rmempty = true 184 | 185 | ---- Time Update 186 | o = s:option(ListValue, "time_update", translate("Update Time(every day)")) 187 | for t = 0, 23 do o:value(t, t .. ":00") end 188 | o.default = 0 189 | o:depends("week_update", "0") 190 | o:depends("week_update", "1") 191 | o:depends("week_update", "2") 192 | o:depends("week_update", "3") 193 | o:depends("week_update", "4") 194 | o:depends("week_update", "5") 195 | o:depends("week_update", "6") 196 | o:depends("week_update", "7") 197 | o.rmempty = true 198 | 199 | ---- Interval Update 200 | o = s:option(ListValue, "interval_update", translate("Update Interval(hour)")) 201 | for t = 1, 24 do o:value(t, t .. " " .. translate("hour")) end 202 | o.default = 2 203 | o:depends("week_update", "8") 204 | o.rmempty = true 205 | 206 | o = s:option(ListValue, "access_mode", translate("Subscribe URL Access Method")) 207 | o.default = "" 208 | o:value("", translate("Auto")) 209 | o:value("direct", translate("Direct Connection")) 210 | o:value("proxy", translate("Proxy")) 211 | 212 | o = s:option(Value, "user_agent", translate("User-Agent")) 213 | o.default = "v2rayN/9.99" 214 | o:value("curl", "Curl") 215 | o:value("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Linux") 216 | o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Windows") 217 | o:value("Passwall2/OpenWrt", "PassWall2") 218 | o:value("v2rayN/9.99", "v2rayN") 219 | 220 | o = s:option(ListValue, "chain_proxy", translate("Chain Proxy")) 221 | o:value("", translate("Close(Not use)")) 222 | o:value("1", translate("Preproxy Node")) 223 | o:value("2", translate("Landing Node")) 224 | 225 | local descrStr = "Chained proxy works only with Xray or Sing-box nodes.
" 226 | descrStr = descrStr .. "The chained node must be the same type as your subscription node (Xray with Xray, Sing-box with Sing-box).
" 227 | descrStr = descrStr .. "You can only use manual or imported nodes as chained nodes." 228 | descrStr = translate(descrStr) .. "
" .. translate("Only support a layer of proxy.") 229 | 230 | o = s:option(ListValue, "preproxy_node", translate("Preproxy Node")) 231 | o:depends({ ["chain_proxy"] = "1" }) 232 | o.description = descrStr 233 | 234 | o = s:option(ListValue, "to_node", translate("Landing Node")) 235 | o:depends({ ["chain_proxy"] = "2" }) 236 | o.description = descrStr 237 | 238 | for k, v in pairs(nodes_table) do 239 | if (v.type == "Xray" or v.type == "sing-box") and (not v.chain_proxy or v.chain_proxy == "") and v.add_mode ~= "2" then 240 | s.fields["preproxy_node"]:value(v.id, v.remark) 241 | s.fields["to_node"]:value(v.id, v.remark) 242 | end 243 | end 244 | 245 | return m 246 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/model/cbi/passwall2/client/shunt_rules.lua: -------------------------------------------------------------------------------- 1 | local api = require "luci.passwall2.api" 2 | local appname = api.appname 3 | local datatypes = api.datatypes 4 | 5 | m = Map(appname, "Sing-Box/Xray " .. translate("Shunt Rule")) 6 | m.redirect = api.url("rule") 7 | api.set_apply_on_parse(m) 8 | 9 | if not arg[1] or not m:get(arg[1]) then 10 | luci.http.redirect(m.redirect) 11 | end 12 | 13 | -- Add inline CSS to map description 14 | m.description = (m.description or "") .. [[ 15 | 27 | ]] 28 | 29 | function clean_text(text) 30 | local nbsp = string.char(0xC2, 0xA0) -- 不间断空格(U+00A0) 31 | local fullwidth_space = string.char(0xE3, 0x80, 0x80) -- 全角空格(U+3000) 32 | return text 33 | :gsub("\t", " ") 34 | :gsub(nbsp, " ") 35 | :gsub(fullwidth_space, " ") 36 | :gsub("^%s+", "") 37 | :gsub("%s+$", "\n") 38 | :gsub("\r\n", "\n") 39 | :gsub("[ \t]*\n[ \t]*", "\n") 40 | end 41 | 42 | s = m:section(NamedSection, arg[1], "shunt_rules", "") 43 | s.addremove = false 44 | s.dynamic = false 45 | 46 | remarks = s:option(Value, "remarks", translate("Remarks")) 47 | remarks.default = arg[1] 48 | remarks.rmempty = false 49 | 50 | protocol = s:option(MultiValue, "protocol", translate("Protocol")) 51 | protocol:value("http") 52 | protocol:value("tls") 53 | protocol:value("bittorrent") 54 | protocol.widget = "checkbox" 55 | protocol.default = nil 56 | protocol.optional = false 57 | 58 | o = s:option(MultiValue, "inbound", translate("Inbound Tag")) 59 | o:value("tproxy", translate("Transparent proxy")) 60 | o:value("socks", "Socks") 61 | o.widget = "checkbox" 62 | o.default = nil 63 | o.optional = false 64 | 65 | network = s:option(ListValue, "network", translate("Network")) 66 | network:value("tcp,udp", "TCP UDP") 67 | network:value("tcp", "TCP") 68 | network:value("udp", "UDP") 69 | 70 | source = s:option(DynamicList, "source", translate("Source")) 71 | source.description = "" 76 | source.cast = "string" 77 | source.cfgvalue = function(self, section) 78 | local value 79 | if self.tag_error[section] then 80 | value = self:formvalue(section) 81 | else 82 | value = self.map:get(section, self.option) 83 | if type(value) == "string" then 84 | local value2 = {} 85 | string.gsub(value, '[^' .. " " .. ']+', function(w) table.insert(value2, w) end) 86 | value = value2 87 | end 88 | end 89 | return value 90 | end 91 | source.validate = function(self, value, t) 92 | local err = {} 93 | for _, v in ipairs(value) do 94 | local flag = false 95 | if datatypes.ip4addr(v) then 96 | flag = true 97 | end 98 | 99 | if flag == false and v:find("geoip:") and v:find("geoip:") == 1 then 100 | flag = true 101 | end 102 | 103 | if flag == false then 104 | err[#err + 1] = v 105 | end 106 | end 107 | 108 | if #err > 0 then 109 | self:add_error(t, "invalid", translate("Not true format, please re-enter!")) 110 | for _, v in ipairs(err) do 111 | self:add_error(t, "invalid", v) 112 | end 113 | end 114 | 115 | return value 116 | end 117 | 118 | local dynamicList_write = function(self, section, value) 119 | local t = {} 120 | local t2 = {} 121 | if type(value) == "table" then 122 | local x 123 | for _, x in ipairs(value) do 124 | if x and #x > 0 then 125 | if not t2[x] then 126 | t2[x] = x 127 | t[#t+1] = x 128 | end 129 | end 130 | end 131 | else 132 | t = { value } 133 | end 134 | t = table.concat(t, " ") 135 | return DynamicList.write(self, section, t) 136 | end 137 | 138 | source.write = dynamicList_write 139 | 140 | sourcePort = s:option(Value, "sourcePort", translate("Source port")) 141 | 142 | port = s:option(Value, "port", translate("port")) 143 | 144 | domain_list = s:option(TextValue, "domain_list", translate("Domain")) 145 | domain_list.rows = 10 146 | domain_list.wrap = "off" 147 | domain_list.validate = function(self, value) 148 | local hosts= {} 149 | value = clean_text(value) 150 | string.gsub(value, "[^\r\n]+", function(w) table.insert(hosts, w) end) 151 | for index, host in ipairs(hosts) do 152 | local flag = 1 153 | local tmp_host = host 154 | if not host:find("#") and host:find("%s") then 155 | elseif host:find("regexp:") and host:find("regexp:") == 1 then 156 | flag = 0 157 | elseif host:find("domain:.") and host:find("domain:.") == 1 then 158 | tmp_host = host:gsub("domain:", "") 159 | elseif host:find("full:.") and host:find("full:.") == 1 then 160 | tmp_host = host:gsub("full:", "") 161 | elseif host:find("geosite:") and host:find("geosite:") == 1 then 162 | flag = 0 163 | elseif host:find("ext:") and host:find("ext:") == 1 then 164 | flag = 0 165 | elseif host:find("rule-set:", 1, true) == 1 or host:find("rs:") == 1 then 166 | local w = host:sub(host:find(":") + 1, #host) 167 | if w:find("local:") == 1 or w:find("remote:") == 1 then 168 | flag = 0 169 | end 170 | elseif host:find("#") and host:find("#") == 1 then 171 | flag = 0 172 | end 173 | if flag == 1 then 174 | if not datatypes.hostname(tmp_host) then 175 | return nil, tmp_host .. " " .. translate("Not valid domain name, please re-enter!") 176 | end 177 | end 178 | end 179 | return value 180 | end 181 | domain_list.description = "
" 196 | ip_list = s:option(TextValue, "ip_list", "IP") 197 | ip_list.rows = 10 198 | ip_list.wrap = "off" 199 | ip_list.validate = function(self, value) 200 | local ipmasks= {} 201 | value = clean_text(value) 202 | string.gsub(value, "[^\r\n]+", function(w) table.insert(ipmasks, w) end) 203 | for index, ipmask in ipairs(ipmasks) do 204 | if ipmask:find("geoip:") and ipmask:find("geoip:") == 1 and not ipmask:find("%s") then 205 | elseif ipmask:find("ext:") and ipmask:find("ext:") == 1 and not ipmask:find("%s") then 206 | elseif ipmask:find("rule-set:", 1, true) == 1 or ipmask:find("rs:") == 1 then 207 | local w = ipmask:sub(ipmask:find(":") + 1, #ipmask) 208 | if w:find("local:") == 1 or w:find("remote:") == 1 then 209 | flag = 0 210 | end 211 | elseif ipmask:find("#") and ipmask:find("#") == 1 then 212 | else 213 | if not (datatypes.ipmask4(ipmask) or datatypes.ipmask6(ipmask)) then 214 | return nil, ipmask .. " " .. translate("Not valid IP format, please re-enter!") 215 | end 216 | end 217 | end 218 | return value 219 | end 220 | ip_list.description = "
" 233 | 234 | return m 235 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/passwall2/server_app.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | local action = arg[1] 4 | local api = require "luci.passwall2.api" 5 | local sys = api.sys 6 | local uci = api.uci 7 | local jsonc = api.jsonc 8 | 9 | local CONFIG = "passwall2_server" 10 | local CONFIG_PATH = "/tmp/etc/" .. CONFIG 11 | local NFT_INCLUDE_FILE = CONFIG_PATH .. "/" .. CONFIG .. ".nft" 12 | local LOG_APP_FILE = "/tmp/log/" .. CONFIG .. ".log" 13 | local TMP_BIN_PATH = CONFIG_PATH .. "/bin" 14 | local require_dir = "luci.passwall2." 15 | 16 | local ipt_bin = sys.exec("echo -n $(/usr/share/passwall2/iptables.sh get_ipt_bin)") 17 | local ip6t_bin = sys.exec("echo -n $(/usr/share/passwall2/iptables.sh get_ip6t_bin)") 18 | 19 | local nft_flag = api.is_finded("fw4") and "1" or "0" 20 | 21 | local function log(...) 22 | local f, err = io.open(LOG_APP_FILE, "a") 23 | if f and err == nil then 24 | local str = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ") 25 | f:write(str .. "\n") 26 | f:close() 27 | end 28 | end 29 | 30 | local function cmd(cmd) 31 | sys.call(cmd) 32 | end 33 | 34 | local function ipt(arg) 35 | if ipt_bin and #ipt_bin > 0 then 36 | cmd(ipt_bin .. " -w " .. arg) 37 | end 38 | end 39 | 40 | local function ip6t(arg) 41 | if ip6t_bin and #ip6t_bin > 0 then 42 | cmd(ip6t_bin .. " -w " .. arg) 43 | end 44 | end 45 | 46 | local function ln_run(s, d, command, output) 47 | if not output then 48 | output = "/dev/null" 49 | end 50 | d = TMP_BIN_PATH .. "/" .. d 51 | cmd(string.format('[ ! -f "%s" ] && ln -s %s %s 2>/dev/null', d, s, d)) 52 | return string.format("%s >%s 2>&1 &", d .. " " ..command, output) 53 | end 54 | 55 | local function gen_include() 56 | cmd(string.format("echo '#!/bin/sh' > /tmp/etc/%s.include", CONFIG)) 57 | local function extract_rules(n, a) 58 | local _ipt = ipt_bin 59 | if n == "6" then 60 | _ipt = ip6t_bin 61 | end 62 | local result = "*" .. a 63 | result = result .. "\n" .. sys.exec(_ipt .. '-save -t ' .. a .. ' | grep "PSW2-SERVER" | sed -e "s/^-A \\(INPUT\\)/-I \\1 1/"') 64 | result = result .. "COMMIT" 65 | return result 66 | end 67 | local f, err = io.open("/tmp/etc/" .. CONFIG .. ".include", "a") 68 | if f and err == nil then 69 | if nft_flag == "0" then 70 | f:write(ipt_bin .. '-save -c | grep -v "PSW2-SERVER" | ' .. ipt_bin .. '-restore -c' .. "\n") 71 | f:write(ipt_bin .. '-restore -n <<-EOT' .. "\n") 72 | f:write(extract_rules("4", "filter") .. "\n") 73 | f:write("EOT" .. "\n") 74 | f:write(ip6t_bin .. '-save -c | grep -v "PSW2-SERVER" | ' .. ip6t_bin .. '-restore -c' .. "\n") 75 | f:write(ip6t_bin .. '-restore -n <<-EOT' .. "\n") 76 | f:write(extract_rules("6", "filter") .. "\n") 77 | f:write("EOT" .. "\n") 78 | f:close() 79 | else 80 | f:write("nft -f " .. NFT_INCLUDE_FILE .. "\n") 81 | f:close() 82 | end 83 | end 84 | end 85 | 86 | local function start() 87 | local enabled = tonumber(uci:get(CONFIG, "@global[0]", "enable") or 0) 88 | if enabled == nil or enabled == 0 then 89 | return 90 | end 91 | cmd(string.format("mkdir -p %s %s", CONFIG_PATH, TMP_BIN_PATH)) 92 | cmd(string.format("touch %s", LOG_APP_FILE)) 93 | if nft_flag == "0" then 94 | ipt("-N PSW2-SERVER") 95 | ipt("-I INPUT -j PSW2-SERVER") 96 | ip6t("-N PSW2-SERVER") 97 | ip6t("-I INPUT -j PSW2-SERVER") 98 | else 99 | nft_file, err = io.open(NFT_INCLUDE_FILE, "w") 100 | nft_file:write('#!/usr/sbin/nft -f\n') 101 | nft_file:write('add chain inet fw4 PSW2-SERVER\n') 102 | nft_file:write('flush chain inet fw4 PSW2-SERVER\n') 103 | nft_file:write('insert rule inet fw4 input position 0 jump PSW2-SERVER comment "PSW2-SERVER"\n') 104 | end 105 | uci:foreach(CONFIG, "user", function(user) 106 | local id = user[".name"] 107 | local enable = user.enable 108 | if enable and tonumber(enable) == 1 then 109 | local enable_log = user.log 110 | local log_path = nil 111 | if enable_log and enable_log == "1" then 112 | log_path = CONFIG_PATH .. "/" .. id .. ".log" 113 | else 114 | log_path = nil 115 | end 116 | local remarks = user.remarks 117 | local port = tonumber(user.port) 118 | local bin 119 | local config = {} 120 | local config_file = CONFIG_PATH .. "/" .. id .. ".json" 121 | local udp_forward = 1 122 | local type = user.type or "" 123 | if type == "SS" or type == "SSR" then 124 | if user.custom == "1" and user.config_str then 125 | config = jsonc.parse(api.base64Decode(user.config_str)) 126 | else 127 | config = require(require_dir .. "util_shadowsocks").gen_config_server(user) 128 | end 129 | local udp_param = "" 130 | udp_forward = tonumber(user.udp_forward) or 1 131 | if udp_forward == 1 then 132 | udp_param = "-u" 133 | end 134 | type = type:lower() 135 | bin = ln_run("/usr/bin/" .. type .. "-server", type .. "-server", "-c " .. config_file .. " " .. udp_param, log_path) 136 | elseif type == "SS-Rust" then 137 | if user.custom == "1" and user.config_str then 138 | config = jsonc.parse(api.base64Decode(user.config_str)) 139 | else 140 | config = require(require_dir .. "util_shadowsocks").gen_config_server(user) 141 | end 142 | bin = ln_run("/usr/bin/ssserver", "ssserver", "-c " .. config_file, log_path) 143 | elseif type == "Xray" then 144 | if user.custom == "1" and user.config_str then 145 | config = jsonc.parse(api.base64Decode(user.config_str)) 146 | if log_path then 147 | if not config.log then 148 | config.log = {} 149 | end 150 | config.log.loglevel = user.loglevel 151 | end 152 | else 153 | config = require(require_dir .. "util_xray").gen_config_server(user) 154 | end 155 | bin = ln_run(api.get_app_path("xray"), "xray", "run -c " .. config_file, log_path) 156 | elseif type == "sing-box" then 157 | if user.custom == "1" and user.config_str then 158 | config = jsonc.parse(api.base64Decode(user.config_str)) 159 | if log_path then 160 | if not config.log then 161 | config.log = {} 162 | end 163 | config.log.timestamp = true 164 | config.log.disabled = false 165 | config.log.level = user.loglevel 166 | config.log.output = log_path 167 | end 168 | else 169 | config = require(require_dir .. "util_sing-box").gen_config_server(user) 170 | end 171 | bin = ln_run(api.get_app_path("sing-box"), "sing-box", "run -c " .. config_file, log_path) 172 | elseif type == "Hysteria2" then 173 | if user.custom == "1" and user.config_str then 174 | config = jsonc.parse(api.base64Decode(user.config_str)) 175 | else 176 | config = require(require_dir .. "util_hysteria2").gen_config_server(user) 177 | end 178 | bin = ln_run(api.get_app_path("hysteria"), "hysteria", "-c " .. config_file .. " server", log_path) 179 | end 180 | 181 | if next(config) then 182 | local f, err = io.open(config_file, "w") 183 | if f and err == nil then 184 | f:write(jsonc.stringify(config, 1)) 185 | f:close() 186 | end 187 | log(string.format("%s 生成配置文件并运行 - %s", remarks, config_file)) 188 | end 189 | 190 | if bin then 191 | cmd(bin) 192 | end 193 | 194 | local bind_local = user.bind_local or 0 195 | if bind_local and tonumber(bind_local) ~= 1 and port then 196 | if nft_flag == "0" then 197 | ipt(string.format('-A PSW2-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) 198 | ip6t(string.format('-A PSW2-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) 199 | if udp_forward == 1 then 200 | ipt(string.format('-A PSW2-SERVER -p udp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) 201 | ip6t(string.format('-A PSW2-SERVER -p udp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks)) 202 | end 203 | else 204 | nft_file:write(string.format('add rule inet fw4 PSW2-SERVER meta l4proto tcp tcp dport {%s} counter accept comment "%s"\n', port, remarks)) 205 | if udp_forward == 1 then 206 | nft_file:write(string.format('add rule inet fw4 PSW2-SERVER meta l4proto udp udp dport {%s} counter accept comment "%s"\n', port, remarks)) 207 | end 208 | end 209 | end 210 | end 211 | end) 212 | if nft_flag == "1" then 213 | nft_file:write("add rule inet fw4 PSW2-SERVER return\n") 214 | nft_file:close() 215 | cmd("nft -f " .. NFT_INCLUDE_FILE) 216 | end 217 | gen_include() 218 | end 219 | 220 | local function stop() 221 | cmd(string.format("/bin/busybox top -bn1 | grep -v 'grep' | grep '%s/' | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1", CONFIG_PATH)) 222 | if nft_flag == "0" then 223 | ipt("-D INPUT -j PSW2-SERVER 2>/dev/null") 224 | ipt("-F PSW2-SERVER 2>/dev/null") 225 | ipt("-X PSW2-SERVER 2>/dev/null") 226 | ip6t("-D INPUT -j PSW2-SERVER 2>/dev/null") 227 | ip6t("-F PSW2-SERVER 2>/dev/null") 228 | ip6t("-X PSW2-SERVER 2>/dev/null") 229 | else 230 | local nft_cmd = "handles=$(nft -a list chain inet fw4 input | grep -E \"PSW2-SERVER\" | awk -F '# handle ' '{print$2}')\n for handle in $handles; do\n nft delete rule inet fw4 input handle ${handle} 2>/dev/null\n done" 231 | cmd(nft_cmd) 232 | cmd("nft flush chain inet fw4 PSW2-SERVER 2>/dev/null") 233 | cmd("nft delete chain inet fw4 PSW2-SERVER 2>/dev/null") 234 | end 235 | cmd(string.format("rm -rf %s %s /tmp/etc/%s.include", CONFIG_PATH, LOG_APP_FILE, CONFIG)) 236 | end 237 | 238 | if action then 239 | if action == "start" then 240 | start() 241 | elseif action == "stop" then 242 | stop() 243 | end 244 | end 245 | -------------------------------------------------------------------------------- /luci-app-passwall2/luasrc/view/passwall2/node_list/link_add_node.htm: -------------------------------------------------------------------------------- 1 | <% 2 | local api = require "luci.passwall2.api" 3 | -%> 4 | 5 | 176 | 177 | 208 | 209 |
210 | 211 |
212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 |
221 |
222 | 223 | 370 | -------------------------------------------------------------------------------- /luci-app-passwall2/root/usr/share/passwall2/helper_dnsmasq.lua: -------------------------------------------------------------------------------- 1 | local api = require "luci.passwall2.api" 2 | local appname = "passwall2" 3 | local uci = api.uci 4 | local sys = api.sys 5 | local fs = api.fs 6 | local datatypes = api.datatypes 7 | local TMP = {} 8 | 9 | local function tinsert(table_name, val) 10 | if table_name and type(table_name) == "table" then 11 | if not TMP[table_name] then 12 | TMP[table_name] = {} 13 | end 14 | if TMP[table_name][val] then 15 | return false 16 | end 17 | table.insert(table_name, val) 18 | TMP[table_name][val] = true 19 | return true 20 | end 21 | return false 22 | end 23 | 24 | local function backup_servers() 25 | local DNSMASQ_DNS = uci:get("dhcp", "@dnsmasq[0]", "server") 26 | if DNSMASQ_DNS and #DNSMASQ_DNS > 0 then 27 | uci:set(appname, "@global[0]", "dnsmasq_servers", DNSMASQ_DNS) 28 | api.uci_save(uci, appname, true) 29 | end 30 | end 31 | 32 | local function restore_servers() 33 | local dns_table = {} 34 | local DNSMASQ_DNS = uci:get("dhcp", "@dnsmasq[0]", "server") 35 | if DNSMASQ_DNS and #DNSMASQ_DNS > 0 then 36 | for k, v in ipairs(DNSMASQ_DNS) do 37 | tinsert(dns_table, v) 38 | end 39 | end 40 | local OLD_SERVER = uci:get(appname, "@global[0]", "dnsmasq_servers") 41 | if OLD_SERVER and #OLD_SERVER > 0 then 42 | for k, v in ipairs(OLD_SERVER) do 43 | tinsert(dns_table, v) 44 | end 45 | uci:delete(appname, "@global[0]", "dnsmasq_servers") 46 | api.uci_save(uci, appname, true) 47 | end 48 | if dns_table and #dns_table > 0 then 49 | uci:set_list("dhcp", "@dnsmasq[0]", "server", dns_table) 50 | api.uci_save(uci, "dhcp", true) 51 | end 52 | end 53 | 54 | function stretch() 55 | local dnsmasq_server = uci:get("dhcp", "@dnsmasq[0]", "server") 56 | local dnsmasq_noresolv = uci:get("dhcp", "@dnsmasq[0]", "noresolv") 57 | local _flag 58 | if dnsmasq_server and #dnsmasq_server > 0 then 59 | for k, v in ipairs(dnsmasq_server) do 60 | if not v:find("/") then 61 | _flag = true 62 | end 63 | end 64 | end 65 | if not _flag and dnsmasq_noresolv == "1" then 66 | uci:delete("dhcp", "@dnsmasq[0]", "noresolv") 67 | local RESOLVFILE = "/tmp/resolv.conf.d/resolv.conf.auto" 68 | local file = io.open(RESOLVFILE, "r") 69 | if not file then 70 | RESOLVFILE = "/tmp/resolv.conf.auto" 71 | else 72 | local size = file:seek("end") 73 | file:close() 74 | if size == 0 then 75 | RESOLVFILE = "/tmp/resolv.conf.auto" 76 | end 77 | end 78 | uci:set("dhcp", "@dnsmasq[0]", "resolvfile", RESOLVFILE) 79 | api.uci_save(uci, "dhcp", true) 80 | end 81 | end 82 | 83 | function restart(var) 84 | local LOG = var["-LOG"] 85 | sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1") 86 | if LOG == "1" then 87 | api.log("Restart dnsmasq Serve") 88 | end 89 | end 90 | 91 | function logic_restart(var) 92 | local LOG = var["-LOG"] 93 | local DEFAULT_DNS = api.get_cache_var("DEFAULT_DNS") 94 | if DEFAULT_DNS then 95 | backup_servers() 96 | --sys.call("sed -i '/list server/d' /etc/config/dhcp >/dev/null 2>&1") 97 | local dns_table = {} 98 | local dnsmasq_server = uci:get("dhcp", "@dnsmasq[0]", "server") 99 | if dnsmasq_server and #dnsmasq_server > 0 then 100 | for k, v in ipairs(dnsmasq_server) do 101 | if v:find("/") then 102 | tinsert(dns_table, v) 103 | end 104 | end 105 | uci:set_list("dhcp", "@dnsmasq[0]", "server", dns_table) 106 | api.uci_save(uci, "dhcp", true) 107 | end 108 | sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1") 109 | restore_servers() 110 | else 111 | sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1") 112 | end 113 | if LOG == "1" then 114 | api.log("Restart dnsmasq Serve") 115 | end 116 | end 117 | 118 | function copy_instance(var) 119 | local LISTEN_PORT = var["-LISTEN_PORT"] 120 | local TMP_DNSMASQ_PATH = var["-TMP_DNSMASQ_PATH"] 121 | local conf_lines = {} 122 | local DEFAULT_DNSMASQ_CFGID = sys.exec("echo -n $(uci -q show dhcp.@dnsmasq[0] | awk 'NR==1 {split($0, conf, /[.=]/); print conf[2]}')") 123 | for line in io.lines("/tmp/etc/dnsmasq.conf." .. DEFAULT_DNSMASQ_CFGID) do 124 | local filter 125 | if line:find("passwall2") then filter = true end 126 | if line:find("ubus") then filter = true end 127 | if line:find("dhcp") then filter = true end 128 | if line:find("server=") == 1 then filter = true end 129 | if line:find("port=") == 1 then filter = true end 130 | if line:find("conf%-dir=") == 1 then 131 | filter = true 132 | if TMP_DNSMASQ_PATH then 133 | local tmp_path = line:sub(1 + #"conf-dir=") 134 | sys.call(string.format("cp -r %s/* %s/ 2>/dev/null", tmp_path, TMP_DNSMASQ_PATH)) 135 | end 136 | end 137 | if line:find("address=") == 1 or (line:find("server=") == 1 and line:find("/")) then filter = nil end 138 | if not filter then 139 | tinsert(conf_lines, line) 140 | end 141 | end 142 | tinsert(conf_lines, "port=" .. LISTEN_PORT) 143 | if TMP_DNSMASQ_PATH then 144 | sys.call("rm -rf " .. TMP_DNSMASQ_PATH .. "/*passwall2*") 145 | end 146 | if var["-return"] == "1" then 147 | return conf_lines 148 | end 149 | if #conf_lines > 0 then 150 | local DNSMASQ_CONF = var["-DNSMASQ_CONF"] 151 | local conf_out = io.open(DNSMASQ_CONF, "a") 152 | conf_out:write(table.concat(conf_lines, "\n")) 153 | conf_out:write("\n") 154 | conf_out:close() 155 | end 156 | end 157 | 158 | function add_rule(var) 159 | local FLAG = var["-FLAG"] 160 | local TMP_DNSMASQ_PATH = var["-TMP_DNSMASQ_PATH"] 161 | local DNSMASQ_CONF_FILE = var["-DNSMASQ_CONF_FILE"] 162 | local LISTEN_PORT = var["-LISTEN_PORT"] 163 | local DEFAULT_DNS = var["-DEFAULT_DNS"] 164 | local LOCAL_DNS = var["-LOCAL_DNS"] 165 | local TUN_DNS = var["-TUN_DNS"] 166 | local NO_LOGIC_LOG = var["-NO_LOGIC_LOG"] 167 | local NFTFLAG = var["-NFTFLAG"] 168 | local CACHE_PATH = api.CACHE_PATH 169 | local CACHE_FLAG = "dnsmasq_" .. FLAG 170 | local CACHE_DNS_PATH = CACHE_PATH .. "/" .. CACHE_FLAG 171 | local CACHE_TEXT_FILE = CACHE_DNS_PATH .. ".txt" 172 | 173 | local list1 = {} 174 | local excluded_domain = {} 175 | local excluded_domain_str = "!" 176 | 177 | local function check_dns(domain, dns) 178 | if domain == "" or domain:find("#") then 179 | return false 180 | end 181 | if not dns then 182 | return 183 | end 184 | for k,v in ipairs(list1[domain].dns) do 185 | if dns == v then 186 | return true 187 | end 188 | end 189 | return false 190 | end 191 | 192 | local function check_ipset(domain, ipset) 193 | if domain == "" or domain:find("#") then 194 | return false 195 | end 196 | if not ipset then 197 | return 198 | end 199 | for k,v in ipairs(list1[domain].ipsets) do 200 | if ipset == v then 201 | return true 202 | end 203 | end 204 | return false 205 | end 206 | 207 | local function set_domain_dns(domain, dns) 208 | if domain == "" or domain:find("#") then 209 | return 210 | end 211 | if not dns then 212 | return 213 | end 214 | if not list1[domain] then 215 | list1[domain] = { 216 | dns = {}, 217 | ipsets = {} 218 | } 219 | end 220 | for line in string.gmatch(dns, '[^' .. "," .. ']+') do 221 | if not check_dns(domain, line) then 222 | table.insert(list1[domain].dns, line) 223 | end 224 | end 225 | end 226 | 227 | local function set_domain_ipset(domain, ipset) 228 | if domain == "" or domain:find("#") then 229 | return 230 | end 231 | if not ipset then 232 | return 233 | end 234 | if not list1[domain] then 235 | list1[domain] = { 236 | dns = {}, 237 | ipsets = {} 238 | } 239 | end 240 | for line in string.gmatch(ipset, '[^' .. "," .. ']+') do 241 | if not check_ipset(domain, line) then 242 | table.insert(list1[domain].ipsets, line) 243 | end 244 | end 245 | end 246 | 247 | local cache_text = "" 248 | local nodes_address_md5 = sys.exec("echo -n $(uci show passwall2 | grep '\\.address') | md5sum") 249 | local new_text = TMP_DNSMASQ_PATH .. DNSMASQ_CONF_FILE .. DEFAULT_DNS .. LOCAL_DNS .. TUN_DNS .. nodes_address_md5 .. NFTFLAG 250 | if fs.access(CACHE_TEXT_FILE) then 251 | for line in io.lines(CACHE_TEXT_FILE) do 252 | cache_text = line 253 | end 254 | end 255 | 256 | if cache_text ~= new_text then 257 | api.remove(CACHE_DNS_PATH .. "*") 258 | end 259 | 260 | local dnsmasq_default_dns = TUN_DNS 261 | 262 | local setflag_4= (NFTFLAG == "1") and "4#inet#passwall2#" or "" 263 | local setflag_6= (NFTFLAG == "1") and "6#inet#passwall2#" or "" 264 | 265 | if not fs.access(CACHE_DNS_PATH) then 266 | fs.mkdir(CACHE_DNS_PATH) 267 | 268 | local fwd_dns 269 | 270 | --Always use domesticDNSResolve node domain name 271 | if true then 272 | fwd_dns = LOCAL_DNS 273 | uci:foreach(appname, "nodes", function(t) 274 | local function process_address(address) 275 | if address == "engage.cloudflareclient.com" then return end 276 | if datatypes.hostname(address) then 277 | set_domain_dns(address, fwd_dns) 278 | set_domain_ipset(address, setflag_4 .. "passwall2_vps," .. setflag_6 .. "passwall2_vps6") 279 | end 280 | end 281 | process_address(t.address) 282 | process_address(t.download_address) 283 | end) 284 | end 285 | 286 | if list1 and next(list1) then 287 | local server_out = io.open(CACHE_DNS_PATH .. "/001-server.conf", "a") 288 | local ipset_out = io.open(CACHE_DNS_PATH .. "/ipset.conf", "a") 289 | local set_name = "ipset" 290 | if NFTFLAG == "1" then 291 | set_name = "nftset" 292 | end 293 | for key, value in pairs(list1) do 294 | if value.dns and #value.dns > 0 then 295 | for i, dns in ipairs(value.dns) do 296 | server_out:write(string.format("server=/.%s/%s", key, dns) .. "\n") 297 | end 298 | end 299 | if value.ipsets and #value.ipsets > 0 then 300 | local ipsets_str = "" 301 | for i, ipset in ipairs(value.ipsets) do 302 | ipsets_str = ipsets_str .. ipset .. "," 303 | end 304 | ipsets_str = ipsets_str:sub(1, #ipsets_str - 1) 305 | ipset_out:write(string.format("%s=/.%s/%s", set_name, key, ipsets_str) .. "\n") 306 | end 307 | end 308 | server_out:close() 309 | ipset_out:close() 310 | end 311 | 312 | local f_out = io.open(CACHE_TEXT_FILE, "a") 313 | f_out:write(new_text) 314 | f_out:close() 315 | end 316 | 317 | api.remove(TMP_DNSMASQ_PATH) 318 | fs.symlink(CACHE_DNS_PATH, TMP_DNSMASQ_PATH) 319 | 320 | if DNSMASQ_CONF_FILE ~= "nil" then 321 | local conf_lines = {} 322 | if LISTEN_PORT then 323 | --Copy dnsmasq instance 324 | conf_lines = copy_instance({["-LISTEN_PORT"] = LISTEN_PORT, ["-TMP_DNSMASQ_PATH"] = TMP_DNSMASQ_PATH, ["-return"] = "1"}) 325 | --dhcp.leases to hostsMore actions 326 | local hosts = "/tmp/etc/" .. appname .. "_tmp/dhcp-hosts" 327 | sys.call("touch " .. hosts) 328 | tinsert(conf_lines, "addn-hosts=" .. hosts) 329 | else 330 | --Modify the default dnsmasq service 331 | end 332 | tinsert(conf_lines, string.format("conf-dir=%s", TMP_DNSMASQ_PATH)) 333 | if dnsmasq_default_dns then 334 | for s in string.gmatch(dnsmasq_default_dns, '[^' .. "," .. ']+') do 335 | tinsert(conf_lines, string.format("server=%s", s)) 336 | end 337 | tinsert(conf_lines, "all-servers") 338 | tinsert(conf_lines, "no-poll") 339 | tinsert(conf_lines, "no-resolv") 340 | 341 | if FLAG == "default" then 342 | api.set_cache_var("DEFAULT_DNS", DEFAULT_DNS) 343 | end 344 | end 345 | if #conf_lines > 0 then 346 | local conf_out = io.open(DNSMASQ_CONF_FILE, "a") 347 | conf_out:write(table.concat(conf_lines, "\n")) 348 | conf_out:write("\n") 349 | conf_out:close() 350 | end 351 | end 352 | end 353 | 354 | _G.stretch = stretch 355 | _G.restart = restart 356 | _G.logic_restart = logic_restart 357 | _G.copy_instance = copy_instance 358 | _G.add_rule = add_rule 359 | 360 | if arg[1] then 361 | local func =_G[arg[1]] 362 | if func then 363 | func(api.get_function_args(arg)) 364 | end 365 | end 366 | --------------------------------------------------------------------------------