From 2bceeb46fbc97382d8e9b9edba1659ccc98e79c5 Mon Sep 17 00:00:00 2001
From: pover0k <171932393+pover0k@users.noreply.github.com>
Date: Thu, 20 Aug 2026 12:22:38 +0000
Subject: [PATCH] luci-app-passwall: bump to 26.8.19
---
applications/luci-app-passwall/Makefile | 2 +-
.../resources/view/passwall/cbi.js | 12 +
.../resources/view/passwall/func.js | 10 +
.../luasrc/controller/passwall.lua | 405 ++++----
.../luasrc/model/cbi/passwall/client/acl.lua | 52 +-
.../model/cbi/passwall/client/acl_config.lua | 57 +-
.../model/cbi/passwall/client/app_update.lua | 18 +-
.../model/cbi/passwall/client/global.lua | 83 +-
.../model/cbi/passwall/client/haproxy.lua | 35 +-
.../passwall/client/include/shunt_options.lua | 18 +-
.../model/cbi/passwall/client/node_config.lua | 67 +-
.../model/cbi/passwall/client/node_list.lua | 32 +-
.../cbi/passwall/client/node_subscribe.lua | 38 +-
.../passwall/client/node_subscribe_config.lua | 20 +-
.../model/cbi/passwall/client/other.lua | 37 +-
.../luasrc/model/cbi/passwall/client/rule.lua | 29 +-
.../model/cbi/passwall/client/rule_list.lua | 12 +-
.../model/cbi/passwall/client/shunt_rules.lua | 16 +-
.../cbi/passwall/client/socks_config.lua | 33 +-
.../cbi/passwall/client/type/1_sing-box.lua | 917 +++++++++++++++++
.../model/cbi/passwall/client/type/2_xray.lua | 894 +++++++++++++++++
.../cbi/passwall/client/type/3_ss-rust.lua | 72 ++
.../client/type/{ssr.lua => 4_ssr.lua} | 40 +-
.../cbi/passwall/client/type/5_hysteria2.lua | 131 +++
.../cbi/passwall/client/type/6_naive.lua | 33 +
.../cbi/passwall/client/type/hysteria2.lua | 131 ---
.../model/cbi/passwall/client/type/naive.lua | 37 -
.../model/cbi/passwall/client/type/ray.lua | 900 -----------------
.../cbi/passwall/client/type/sing-box.lua | 921 ------------------
.../cbi/passwall/client/type/ss-rust.lua | 76 --
.../model/cbi/passwall/server/index.lua | 106 +-
.../server/{user.lua => server_config.lua} | 33 +-
.../cbi/passwall/server/type/0_socks.lua | 36 +
.../cbi/passwall/server/type/1_sing-box.lua | 528 ++++++++++
.../model/cbi/passwall/server/type/2_xray.lua | 540 ++++++++++
.../cbi/passwall/server/type/3_ss-rust.lua | 79 ++
.../server/type/{ssr.lua => 4_ssr.lua} | 104 +-
.../cbi/passwall/server/type/5_hysteria2.lua | 154 +++
.../cbi/passwall/server/type/hysteria2.lua | 155 ---
.../model/cbi/passwall/server/type/ray.lua | 528 ----------
.../cbi/passwall/server/type/sing-box.lua | 525 ----------
.../model/cbi/passwall/server/type/socks.lua | 51 -
.../cbi/passwall/server/type/ss-rust.lua | 77 --
.../model/cbi/passwall/server/user_config.lua | 54 +
.../luci-app-passwall/luasrc/passwall/api.lua | 442 ++++++---
.../luasrc/passwall/server_app.lua | 74 +-
.../luasrc/passwall/util_hysteria2.lua | 24 +-
.../luasrc/passwall/util_naiveproxy.lua | 3 +-
.../luasrc/passwall/util_shadowsocks.lua | 9 +-
.../luasrc/passwall/util_sing-box.lua | 271 +++---
.../luasrc/passwall/util_xray.lua | 148 +--
.../view/passwall/acl/config_footer.htm | 13 +-
.../view/passwall/app_update/app_version.htm | 3 +-
.../luasrc/view/passwall/cbi/footer.htm | 3 +
.../luasrc/view/passwall/cbi/header.htm | 10 +-
.../view/passwall/cbi/nodes_listvalue.htm | 4 +-
.../luasrc/view/passwall/cbi/sortable.htm | 11 +-
.../luasrc/view/passwall/global/backup.htm | 3 +-
.../luasrc/view/passwall/global/faq.htm | 3 +-
.../luasrc/view/passwall/global/footer.htm | 42 +-
.../luasrc/view/passwall/global/status.htm | 3 +-
.../luasrc/view/passwall/haproxy/js.htm | 4 +-
.../luasrc/view/passwall/haproxy/status.htm | 7 +-
.../view/passwall/include/shunt_options.htm | 13 +-
.../view/passwall/node_config/footer.htm | 26 +-
.../view/passwall/node_config/header.htm | 7 +-
.../passwall/node_config/link_share_man.htm | 6 +-
.../view/passwall/node_list/node_list.htm | 70 +-
.../view/passwall/node_subscribe/js.htm | 3 +-
.../view/passwall/rule/rule_version.htm | 3 +-
.../view/passwall/rule/shunt_rule_list.htm | 66 +-
.../view/passwall/rule_list/geoview.htm | 3 +-
.../luasrc/view/passwall/rule_list/js.htm | 11 +-
.../view/passwall/server/config_footer.htm | 17 +-
.../view/passwall/server/config_header.htm | 7 +-
.../passwall/server/gen_wireguard_key.htm | 28 +
.../luasrc/view/passwall/server/log.htm | 3 +-
.../passwall/server/server_list_status.htm | 41 +
.../passwall/server/users_list_status.htm | 38 -
.../view/passwall/socks_auto_switch/btn.htm | 3 +-
.../luci-app-passwall/po/zh_Hans/passwall.po | 58 +-
.../{luci-passwall => luci-app-passwall} | 19 +-
.../etc/uci-defaults/luci-app-passwall_server | 44 +
.../root/usr/share/passwall/app.sh | 47 +-
.../root/usr/share/passwall/haproxy.lua | 29 +-
.../share/passwall/helper_chinadns_add.lua | 20 +-
.../usr/share/passwall/helper_dnsmasq.lua | 60 +-
.../share/passwall/helper_smartdns_add.lua | 28 +-
.../root/usr/share/passwall/iptables.sh | 52 +-
.../root/usr/share/passwall/monitor.sh | 6 +-
.../root/usr/share/passwall/nftables.sh | 58 +-
.../root/usr/share/passwall/rule_update.lua | 60 +-
.../usr/share/passwall/socks_auto_switch.sh | 4 +-
.../root/usr/share/passwall/subscribe.lua | 209 ++--
.../root/usr/share/passwall/test.sh | 4 +-
.../root/usr/share/passwall/utils.sh | 2 +-
96 files changed, 5291 insertions(+), 4929 deletions(-)
create mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/1_sing-box.lua
create mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/2_xray.lua
create mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/3_ss-rust.lua
rename applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/{ssr.lua => 4_ssr.lua} (55%)
create mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/5_hysteria2.lua
create mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/6_naive.lua
delete mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua
delete mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/naive.lua
delete mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
delete mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua
delete mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua
rename applications/luci-app-passwall/luasrc/model/cbi/passwall/server/{user.lua => server_config.lua} (51%)
create mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/0_socks.lua
create mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/1_sing-box.lua
create mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/2_xray.lua
create mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/3_ss-rust.lua
rename applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/{ssr.lua => 4_ssr.lua} (53%)
create mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/5_hysteria2.lua
delete mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua
delete mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua
delete mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua
delete mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/socks.lua
delete mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss-rust.lua
create mode 100644 applications/luci-app-passwall/luasrc/model/cbi/passwall/server/user_config.lua
create mode 100644 applications/luci-app-passwall/luasrc/view/passwall/server/gen_wireguard_key.htm
create mode 100644 applications/luci-app-passwall/luasrc/view/passwall/server/server_list_status.htm
delete mode 100644 applications/luci-app-passwall/luasrc/view/passwall/server/users_list_status.htm
rename applications/luci-app-passwall/root/etc/uci-defaults/{luci-passwall => luci-app-passwall} (80%)
create mode 100755 applications/luci-app-passwall/root/etc/uci-defaults/luci-app-passwall_server
diff --git a/applications/luci-app-passwall/Makefile b/applications/luci-app-passwall/Makefile
index 948db8ae0de..3b483dbfc6e 100644
--- a/applications/luci-app-passwall/Makefile
+++ b/applications/luci-app-passwall/Makefile
@@ -7,7 +7,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
-PKG_VERSION:=26.8.10
+PKG_VERSION:=26.8.19
PKG_RELEASE:=1
PKG_CONFIG_DEPENDS:= \
diff --git a/applications/luci-app-passwall/htdocs/luci-static/resources/view/passwall/cbi.js b/applications/luci-app-passwall/htdocs/luci-static/resources/view/passwall/cbi.js
index 3f9d6470615..61224808f2d 100644
--- a/applications/luci-app-passwall/htdocs/luci-static/resources/view/passwall/cbi.js
+++ b/applications/luci-app-passwall/htdocs/luci-static/resources/view/passwall/cbi.js
@@ -20,19 +20,31 @@ function is_timehhmm(str) {
}
if (typeof cbi_validators !== 'undefined' && cbi_validators !== null) {
+ cbi_validators['base64'] = function() {
+ return isBase64(this);
+ }
cbi_validators['json'] = function() {
return is_json(this);
}
cbi_validators['timehhmm'] = function() {
return is_timehhmm(this);
}
+ cbi_validators['uuid'] = function() {
+ return isUUID(this);
+ }
} else {
L.require('validation').then(function(validation) {
+ validation.types['base64'] = function() {
+ return this.assert(isBase64(this.value), _('Must be Base64 text!'));
+ }
validation.types['json'] = function() {
return this.assert(is_json(this.value), _('Must be JSON text!'));
}
validation.types['timehhmm'] = function() {
return this.assert(is_timehhmm(this.value), _('valid time (hh:mm)'));
}
+ validation.types['uuid'] = function() {
+ return this.assert(isUUID(this.value), _('Must be UUID format!'));
+ }
});
}
diff --git a/applications/luci-app-passwall/htdocs/luci-static/resources/view/passwall/func.js b/applications/luci-app-passwall/htdocs/luci-static/resources/view/passwall/func.js
index e7923077f04..8c9e27c8fca 100644
--- a/applications/luci-app-passwall/htdocs/luci-static/resources/view/passwall/func.js
+++ b/applications/luci-app-passwall/htdocs/luci-static/resources/view/passwall/func.js
@@ -8,6 +8,11 @@ function arraysEqual(a, b) {
return true;
}
+function isBase64(str) {
+ const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/;
+ return base64Regex.test(str);
+}
+
function decodeIfBase64(str) {
try {
let s = str.replace(/-/g, '+').replace(/_/g, '/');
@@ -24,6 +29,11 @@ function decodeIfBase64(str) {
return str;
}
+function isUUID(str) {
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
+ return uuidRegex.test(str);
+}
+
function waitForElement(selector, callback) {
const el = document.querySelector(selector);
if (el) return callback(el);
diff --git a/applications/luci-app-passwall/luasrc/controller/passwall.lua b/applications/luci-app-passwall/luasrc/controller/passwall.lua
index 92ac0185c14..7d1272da58a 100644
--- a/applications/luci-app-passwall/luasrc/controller/passwall.lua
+++ b/applications/luci-app-passwall/luasrc/controller/passwall.lua
@@ -4,8 +4,9 @@
module("luci.controller.passwall", package.seeall)
local api = require "luci.passwall.api"
-local appname = "passwall" -- not available
-local uci = api.uci -- in funtion index()
+local appname = api.appname -- not available
+local c_config = api.c_config -- not available
+local uci, uci_get, uci_set, uci_del, uci_foreach, uci_save = api.uci, api.uci_get_c, api.uci_set_c, api.uci_del_c, api.uci_foreach_c, api.uci_save_c
local fs = api.fs
local http = require "luci.http"
local util = require "luci.util"
@@ -20,14 +21,13 @@ function index()
else return end
end
local api = require "luci.passwall.api"
- local appname = "passwall" -- global definitions not available
- local uci = api.uci -- in function index()
+ local appname = api.appname -- global definitions not available
local fs = api.fs
entry({"admin", "services", appname}).dependent = true
entry({"admin", "services", appname, "show"}, call("show_menu")).leaf = true
entry({"admin", "services", appname, "hide"}, call("hide_menu")).leaf = true
local e
- if uci:get(appname, "@global[0]", "hide_from_luci") ~= "1" then
+ if api.uci_get_c("@global[0]", "hide_from_luci") ~= "1" then
e = entry({"admin", "services", appname}, alias("admin", "services", appname, "settings"), _("Pass Wall"), -1)
else
e = entry({"admin", "services", appname}, alias("admin", "services", appname, "settings"), nil, -1)
@@ -55,12 +55,13 @@ function index()
--[[ Server ]]
entry({"admin", "services", appname, "server"}, cbi(appname .. "/server/index"), _("Server-Side"), 99).leaf = true
- entry({"admin", "services", appname, "server_user"}, cbi(appname .. "/server/user")).leaf = true
+ entry({"admin", "services", appname, "server_config"}, cbi(appname .. "/server/server_config")).leaf = true
+ entry({"admin", "services", appname, "server_user_config"}, cbi(appname .. "/server/user_config")).leaf = true
--[[ API ]]
entry({"admin", "services", appname, "server_update_config"}, call("server_update_config")).leaf = true
- entry({"admin", "services", appname, "server_user_status"}, call("server_user_status")).leaf = true
- entry({"admin", "services", appname, "server_user_log"}, call("server_user_log")).leaf = true
+ entry({"admin", "services", appname, "server_status"}, call("server_status")).leaf = true
+ entry({"admin", "services", appname, "server_log"}, call("server_log")).leaf = true
entry({"admin", "services", appname, "server_get_log"}, call("server_get_log")).leaf = true
entry({"admin", "services", appname, "server_clear_log"}, call("server_clear_log")).leaf = true
entry({"admin", "services", appname, "link_add_node"}, call("link_add_node")).leaf = true
@@ -87,7 +88,6 @@ function index()
entry({"admin", "services", appname, "delete_select_nodes"}, call("delete_select_nodes")).leaf = true
entry({"admin", "services", appname, "reassign_group"}, call("reassign_group")).leaf = true
entry({"admin", "services", appname, "get_node"}, call("get_node")).leaf = true
- entry({"admin", "services", appname, "save_node_order"}, call("save_node_order")).leaf = true
entry({"admin", "services", appname, "save_node_list_opt"}, call("save_node_list_opt")).leaf = true
entry({"admin", "services", appname, "update_rules"}, call("update_rules")).leaf = true
entry({"admin", "services", appname, "rollback_rules"}, call("rollback_rules")).leaf = true
@@ -99,7 +99,6 @@ function index()
entry({"admin", "services", appname, "get_shunt_rules"}, call("get_shunt_rules")).leaf = true
entry({"admin", "services", appname, "add_shunt_rule"}, call("add_shunt_rule")).leaf = true
entry({"admin", "services", appname, "delete_select_shunt_rules"}, call("delete_select_shunt_rules")).leaf = true
- entry({"admin", "services", appname, "save_shunt_rule_order"}, call("save_shunt_rule_order")).leaf = true
--[[rule_list]]
entry({"admin", "services", appname, "read_rulelist"}, call("read_rulelist")).leaf = true
@@ -123,6 +122,7 @@ function index()
entry({"admin", "services", appname, "geo_view"}, call("geo_view")).leaf = true
entry({"admin", "services", appname, "fetch_certsha256"}, call("fetch_certsha256")).leaf = true
+ entry({"admin", "services", appname, "gen_wireguard_key"}, call("gen_wireguard_key")).leaf = true
end
local function http_write_json(content)
@@ -141,7 +141,7 @@ local function http_write_json_error(data)
end
function reset_config()
- uci:revert(appname)
+ uci:revert(c_config)
luci.sys.call("echo '' > /tmp/log/passwall.log")
luci.sys.call('/etc/init.d/passwall stop')
if luci.sys.call('[ -s "/usr/share/passwall/0_default_config" ]') == 0 then
@@ -153,14 +153,14 @@ function reset_config()
end
function show_menu()
- api.sh_uci_del(appname, "@global[0]", "hide_from_luci", true)
+ api.sh_uci_del(c_config, "@global[0]", "hide_from_luci", true)
luci.sys.call("rm -rf /tmp/luci-*")
luci.sys.call("/etc/init.d/rpcd restart >/dev/null")
http.redirect(api.url())
end
function hide_menu()
- api.sh_uci_set(appname, "@global[0]", "hide_from_luci", "1", true)
+ api.sh_uci_set(c_config, "@global[0]", "hide_from_luci", "1", true)
luci.sys.call("rm -rf /tmp/luci-*")
luci.sys.call("/etc/init.d/rpcd restart >/dev/null")
http.redirect(luci.dispatcher.build_url("admin", "status", "overview"))
@@ -196,10 +196,10 @@ function socks_autoswitch_add_node()
local id = http.formvalue("id")
local key = http.formvalue("key")
if id and id ~= "" and key and key ~= "" then
- uci:set(appname, id, "enable_autoswitch", "1")
- local new_list = uci:get(appname, id, "autoswitch_backup_node") or {}
+ uci_set(id, "enable_autoswitch", "1")
+ local new_list = uci_get(id, "autoswitch_backup_node") or {}
for i = #new_list, 1, -1 do
- if (uci:get(appname, new_list[i], "remarks") or ""):find(key) then
+ if (uci_get(new_list[i], "remarks") or ""):find(key) then
table.remove(new_list, i)
end
end
@@ -208,8 +208,8 @@ function socks_autoswitch_add_node()
table.insert(new_list, e.id)
end
end
- uci:set_list(appname, id, "autoswitch_backup_node", new_list)
- api.uci_save(uci, appname)
+ uci_set(id, "autoswitch_backup_node", new_list)
+ uci_save()
end
http.redirect(api.url("socks_config", id))
end
@@ -218,15 +218,15 @@ function socks_autoswitch_remove_node()
local id = http.formvalue("id")
local key = http.formvalue("key")
if id and id ~= "" and key and key ~= "" then
- uci:set(appname, id, "enable_autoswitch", "1")
- local new_list = uci:get(appname, id, "autoswitch_backup_node") or {}
+ uci_set(id, "enable_autoswitch", "1")
+ local new_list = uci_get(id, "autoswitch_backup_node") or {}
for i = #new_list, 1, -1 do
- if (uci:get(appname, new_list[i], "remarks") or ""):find(key) then
+ if (uci_get(new_list[i], "remarks") or ""):find(key) then
table.remove(new_list, i)
end
end
- uci:set_list(appname, id, "autoswitch_backup_node", new_list)
- api.uci_save(uci, appname)
+ uci_set(id, "autoswitch_backup_node", new_list)
+ uci_save()
end
http.redirect(api.url("socks_config", id))
end
@@ -246,7 +246,7 @@ function gen_client_config()
end
function get_now_use_node()
- local path = "/tmp/etc/passwall/acl/default"
+ local path = api.TMP_PATH .. "/acl/default"
local e = {}
local tcp_node = api.get_cache_var("ACL_GLOBAL_TCP_node")
if tcp_node then
@@ -262,32 +262,32 @@ end
function get_redir_log()
local name = http.formvalue("name")
local proto = http.formvalue("proto"):upper()
- local path = "/tmp/etc/passwall/acl/" .. name
+ local path = api.TMP_PATH .. "/acl/" .. name
local function alert(msg)
http.write(string.format("", i18n.translate(msg)))
end
if name == "default" then
- if proto == "UDP" and (uci:get(appname, "@global[0]", "udp_node") or "nil") == "tcp" and not fs.access(path .. "/" .. proto .. ".log") then
+ if proto == "UDP" and (uci_get("@global[0]", "udp_node") or "nil") == "tcp" and not fs.access(path .. "/" .. proto .. ".log") then
proto = "TCP"
end
else
- local global_tcp = uci:get(appname, "@global[0]", "tcp_node") or "nil"
- local global_udp = uci:get(appname, "@global[0]", "udp_node") or "nil"
- local acl_tcp = uci:get(appname, name, "tcp_node") or "nil"
- local acl_udp = uci:get(appname, name, "udp_node") or "nil"
- local global_enabled = uci:get(appname, "@global[0]", "enabled") == "1"
+ local global_tcp = uci_get("@global[0]", "tcp_node") or "nil"
+ local global_udp = uci_get("@global[0]", "udp_node") or "nil"
+ local acl_tcp = uci_get(name, "tcp_node") or "nil"
+ local acl_udp = uci_get(name, "udp_node") or "nil"
+ local global_enabled = uci_get("@global[0]", "enabled") == "1"
if proto == "TCP" and acl_tcp == global_tcp and global_enabled then
- path = "/tmp/etc/passwall/acl/default"
- if uci:get(appname, "@global[0]", "log_tcp") ~= "1" then
+ path = api.TMP_PATH .. "/acl/default"
+ if uci_get("@global[0]", "log_tcp") ~= "1" then
alert("The access control node is the same as the global node. Please enable global logging.")
return
end
end
if proto == "UDP" and acl_udp == global_udp and global_enabled then
- path = "/tmp/etc/passwall/acl/default"
- if uci:get(appname, "@global[0]", "log_udp") ~= "1" then
+ path = api.TMP_PATH .. "/acl/default"
+ if uci_get("@global[0]", "log_udp") ~= "1" then
alert("The access control node is the same as the global node. Please enable global logging.")
return
end
@@ -308,7 +308,7 @@ end
function get_socks_log()
local name = http.formvalue("name")
- local path = "/tmp/etc/passwall/" .. name .. ".log"
+ local path = api.TMP_PATH .. "/" .. name .. ".log"
if fs.access(path) then
local content = luci.sys.exec("cat ".. path)
content = content:gsub("\n", "
")
@@ -320,13 +320,13 @@ end
function get_chinadns_log()
local flag = http.formvalue("flag")
- local path = "/tmp/etc/passwall/acl/" .. flag .. "/chinadns_ng.log"
+ local path = api.TMP_PATH .. "/acl/" .. flag .. "/chinadns_ng.log"
if flag ~= "default" then
- local global_tcp = uci:get(appname, "@global[0]", "tcp_node") or "nil"
- local acl_tcp = uci:get(appname, flag, "tcp_node") or "nil"
- if acl_tcp == global_tcp and uci:get(appname, "@global[0]", "enabled") == "1" then
- path = "/tmp/etc/passwall/acl/default/chinadns_ng.log"
- if uci:get(appname, "@global[0]", "log_chinadns_ng") ~= "1" then
+ local global_tcp = uci_get("@global[0]", "tcp_node") or "nil"
+ local acl_tcp = uci_get(flag, "tcp_node") or "nil"
+ if acl_tcp == global_tcp and uci_get("@global[0]", "enabled") == "1" then
+ path = api.TMP_PATH .. "/acl/default/chinadns_ng.log"
+ if uci_get("@global[0]", "log_chinadns_ng") ~= "1" then
http.write(string.format("", i18n.translate("The access control node is the same as the global node. Please enable global logging.")))
return
end
@@ -353,34 +353,34 @@ end
function index_status()
local e = {}
- local dns_shunt = uci:get(appname, "@global[0]", "dns_shunt") or "dnsmasq"
+ local dns_shunt = uci_get("@global[0]", "dns_shunt") or "dnsmasq"
if dns_shunt == "smartdns" then
local port = api.get_cache_var("SMARTDNS_LOCAL_PORT") or 0
- e.dns_mode_status = (port ~= 0) and luci.sys.call(string.format("netstat -apn | grep ':%s ' >/dev/null", port)) == 0 or false
+ e.dns_mode_status = (port ~= 0) and luci.sys.call("netstat -apn | grep ':%s ' >/dev/null" % port) == 0 or false
elseif dns_shunt == "chinadns-ng" then
- e.dns_mode_status = luci.sys.call("/bin/busybox top -bn1 | grep -v 'grep' | grep '/tmp/etc/passwall/bin/' | grep 'default' | grep 'chinadns_ng' >/dev/null") == 0
+ e.dns_mode_status = luci.sys.call("/bin/busybox top -bn1 | grep -v 'grep' | grep '%s/bin/' | grep 'default' | grep 'chinadns_ng' >/dev/null" % api.TMP_PATH) == 0
else
e.dns_mode_status = luci.sys.call("netstat -apn | grep ':15353 ' >/dev/null") == 0
end
e.haproxy_status = "-1"
if api.is_finded("haproxy") then
- e.haproxy_status = (luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v grep | grep '%s/bin/' | grep haproxy >/dev/null", appname)) == 0) and "0" or "1"
+ e.haproxy_status = (luci.sys.call("/bin/busybox top -bn1 | grep -v grep | grep '%s/bin/' | grep haproxy >/dev/null" % appname) == 0) and "0" or "1"
end
- e["tcp_node_status"] = luci.sys.call("/bin/busybox top -bn1 | grep -v 'grep' | grep '/tmp/etc/passwall/bin/' | grep 'default' | grep 'TCP' >/dev/null") == 0
+ e["tcp_node_status"] = luci.sys.call("/bin/busybox top -bn1 | grep -v 'grep' | grep '%s/bin/' | grep 'default' | grep 'TCP' >/dev/null" % api.TMP_PATH) == 0
- if (uci:get(appname, "@global[0]", "udp_node") or "nil") == "tcp" then
+ if (uci_get("@global[0]", "udp_node") or "nil") == "tcp" then
e["udp_node_status"] = e["tcp_node_status"]
else
- e["udp_node_status"] = luci.sys.call("/bin/busybox top -bn1 | grep -v 'grep' | grep '/tmp/etc/passwall/bin/' | grep 'default' | grep 'UDP' >/dev/null") == 0
+ e["udp_node_status"] = luci.sys.call("/bin/busybox top -bn1 | grep -v 'grep' | grep '%s/bin/' | grep 'default' | grep 'UDP' >/dev/null" % api.TMP_PATH) == 0
end
http_write_json(e)
end
function haproxy_status()
local e = {}
- e["status"] = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v grep | grep '%s/bin/' | grep haproxy >/dev/null", appname)) == 0
+ e["status"] = luci.sys.call("/bin/busybox top -bn1 | grep -v grep | grep '%s/bin/' | grep haproxy >/dev/null" % appname) == 0
http_write_json(e)
end
@@ -389,12 +389,12 @@ function socks_status()
local index = http.formvalue("index")
local id = http.formvalue("id")
e.index = index
- e.socks_status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '%s' > /dev/null", appname, id)) == 0
- local use_http = uci:get(appname, id, "http_port") or 0
+ e.socks_status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '/%s' > /dev/null", appname, id)) == 0
+ local use_http = uci_get(id, "http_port") or 0
e.use_http = 0
if tonumber(use_http) > 0 then
e.use_http = 1
- e.http_status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v 'grep' | grep '/tmp/etc/passwall/bin/' | grep -v '_acl_' | grep '%s' | grep -E 'HTTP_|HTTP2SOCKS' > /dev/null", id)) == 0
+ e.http_status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '/%s' | grep -E '\\+http|_http' > /dev/null", appname, id)) == 0
end
http_write_json(e)
end
@@ -404,10 +404,10 @@ function connect_status()
e.use_time = ""
local url = http.formvalue("url")
local aliyun = string.find(url, "aliyun")
- local chn_list = uci:get(appname, "@global[0]", "chn_list") or "direct"
- local gfw_list = uci:get(appname, "@global[0]", "use_gfw_list") or "1"
- local proxy_mode = uci:get(appname, "@global[0]", "tcp_proxy_mode") or "proxy"
- local localhost_proxy = uci:get(appname, "@global[0]", "localhost_proxy") or "1"
+ local chn_list = uci_get("@global[0]", "chn_list") or "direct"
+ local gfw_list = uci_get("@global[0]", "use_gfw_list") or "1"
+ local proxy_mode = uci_get("@global[0]", "tcp_proxy_mode") or "proxy"
+ local localhost_proxy = uci_get("@global[0]", "localhost_proxy") or "1"
local socks_server = (localhost_proxy == "0") and api.get_cache_var("GLOBAL_TCP_SOCKS_server") or ""
url = "-w %{http_code}:%{time_pretransfer} " .. url
if socks_server and socks_server ~= "" then
@@ -482,9 +482,9 @@ function update_config()
local data_t = jsonParse(data) or {}
if next(data_t) then
for k, v in pairs(data_t) do
- uci:set(appname, id, k, v)
+ uci_set(id, k, v)
end
- api.uci_save(uci, appname)
+ uci_save()
http_write_json_ok()
return
end
@@ -496,20 +496,20 @@ function add_node()
local redirect = http.formvalue("redirect")
local uid = api.gen_random_char()
- uci:section(appname, "nodes", uid)
+ uci:section(c_config, "nodes", uid)
local group = http.formvalue("group")
if group and group ~= "default" then
- uci:set(appname, uid, "group", group)
+ uci_set(uid, "group", group)
end
- uci:set(appname, uid, "type", "Socks")
+ uci_set(uid, "type", "Socks")
if redirect == "1" then
- api.uci_save(uci, appname)
+ uci_save()
http.redirect(api.url("node_config", uid))
else
- api.uci_save(uci, appname, true, true)
+ uci_save(true, true)
http_write_json({result = uid})
end
end
@@ -517,82 +517,85 @@ end
function set_node()
local protocol = http.formvalue("protocol")
local section = http.formvalue("section")
- uci:set(appname, "@global[0]", protocol .. "_node", section)
+ uci_set("@global[0]", protocol .. "_node", section)
if protocol == "tcp" then
- local node_protocol = uci:get(appname, section, "protocol")
+ local node_protocol = uci_get(section, "protocol")
if node_protocol == "_shunt" then
- local type = uci:get(appname, section, "type")
- local dns_shunt = uci:get(appname, "@global[0]", "dns_shunt")
+ local type = uci_get(section, "type")
+ local dns_shunt = uci_get("@global[0]", "dns_shunt")
local dns_key = (dns_shunt == "smartdns") and "smartdns_dns_mode" or "dns_mode"
- local dns_mode = uci:get(appname, "@global[0]", dns_key)
+ local dns_mode = uci_get("@global[0]", dns_key)
local new_dns_mode = (type == "Xray") and "xray" or "sing-box"
if dns_mode ~= new_dns_mode then
- uci:set(appname, "@global[0]", dns_key, new_dns_mode)
- uci:set(appname, "@global[0]", "v2ray_dns_mode", "tcp")
+ uci_set("@global[0]", dns_key, new_dns_mode)
+ uci_set("@global[0]", "v2ray_dns_mode", "tcp")
end
end
end
- api.uci_save(uci, appname, true, true)
+ uci_save(true, true)
http.redirect(api.url("log"))
end
function copy_node()
local section = http.formvalue("section")
local uid = api.gen_random_char()
- uci:section(appname, "nodes", uid)
- for k, v in pairs(uci:get_all(appname, section)) do
+ uci:section(c_config, "nodes", uid)
+ for k, v in pairs(uci_get(section)) do
if not k:match("^%.") and k ~= "group" then
if k == "remarks" then v = (v or "") .. "(1)" end
- uci:set(appname, uid, k, v)
+ uci_set(uid, k, v)
end
end
- uci:set(appname, uid, "add_mode", 1)
- api.uci_save(uci, appname)
+ uci_set(uid, "add_mode", 1)
+ uci_save()
http.redirect(api.url("node_config", uid))
end
function clear_all_nodes()
- uci:set(appname, '@global[0]', "enabled", "0")
- uci:set(appname, '@global[0]', "socks_enabled", "0")
- uci:set(appname, '@global_haproxy[0]', "balancing_enable", "0")
- uci:delete(appname, '@global[0]', "tcp_node")
- uci:delete(appname, '@global[0]', "udp_node")
- uci:foreach(appname, "socks", function(t)
- uci:delete(appname, t[".name"])
- uci:set_list(appname, t[".name"], "autoswitch_backup_node", {})
+ uci_set('@global[0]', "enabled", "0")
+ uci_set('@global[0]', "socks_enabled", "0")
+ uci_set('@global_haproxy[0]', "balancing_enable", "0")
+ uci_del('@global[0]', "tcp_node")
+ uci_del('@global[0]', "udp_node")
+ uci_foreach("socks", function(t)
+ uci_del(t[".name"])
+ uci_set(t[".name"], "autoswitch_backup_node", {})
end)
- uci:foreach(appname, "haproxy_config", function(t)
- uci:delete(appname, t[".name"])
+ uci_foreach("haproxy_config", function(t)
+ uci_del(t[".name"])
end)
- uci:foreach(appname, "acl_rule", function(t)
- uci:delete(appname, t[".name"], "tcp_node")
- uci:delete(appname, t[".name"], "udp_node")
+ uci_foreach("acl_rule", function(t)
+ uci_del(t[".name"], "tcp_node")
+ uci_del(t[".name"], "udp_node")
end)
- uci:foreach(appname, "nodes", function(node)
- uci:delete(appname, node['.name'])
+ uci_foreach("nodes", function(node)
+ uci_del(node['.name'])
end)
- uci:foreach(appname, "subscribe_list", function(t)
- uci:delete(appname, t[".name"], "md5")
- uci:delete(appname, t[".name"], "chain_proxy")
- uci:delete(appname, t[".name"], "preproxy_node")
- uci:delete(appname, t[".name"], "to_node")
+ uci_foreach("subscribe_list", function(t)
+ uci_del(t[".name"], "md5")
+ uci_del(t[".name"], "chain_proxy")
+ uci_del(t[".name"], "preproxy_node")
+ uci_del(t[".name"], "to_node")
end)
- api.uci_save(uci, appname, true, true)
+ uci_save(true, true)
end
function delete_select_nodes()
local ids = http.formvalue("ids")
local redirect = http.formvalue("redirect")
+ local ids_t = {}
string.gsub(ids, '[^' .. "," .. ']+', function(w)
- local socks
- uci:foreach(appname, "socks", function(t)
- if t["node"] == w then
- uci:delete(appname, t[".name"])
- socks = "Socks_" .. t[".name"]
- end
+ ids_t[#ids_t + 1] = w
+ if (uci_get("@global[0]", "tcp_node") or "") == w then
+ uci_del('@global[0]', "tcp_node")
+ end
+ if (uci_get("@global[0]", "udp_node") or "") == w then
+ uci_del('@global[0]', "udp_node")
+ end
+ uci_foreach("socks", function(t)
local changed = false
- local auto_switch_node_list = uci:get(appname, t[".name"], "autoswitch_backup_node") or {}
+ local auto_switch_node_list = uci_get(t[".name"], "autoswitch_backup_node") or {}
for i = #auto_switch_node_list, 1, -1 do
if w == auto_switch_node_list[i] then
table.remove(auto_switch_node_list, i)
@@ -600,42 +603,42 @@ function delete_select_nodes()
end
end
if changed then
- uci:set_list(appname, t[".name"], "autoswitch_backup_node", auto_switch_node_list)
+ uci_set(t[".name"], "autoswitch_backup_node", auto_switch_node_list)
+ end
+ if t["node"] == w then
+ local new_node = api.get_random_normal_node(ids_t)
+ if new_node then
+ uci_set(t[".name"], "node", new_node[".name"])
+ else
+ uci_set(t[".name"], "enabled", "0")
+ end
end
end)
- local tcp_node = uci:get(appname, "@global[0]", "tcp_node") or ""
- if tcp_node == w or tcp_node == socks then
- uci:delete(appname, '@global[0]', "tcp_node")
- end
- local udp_node = uci:get(appname, "@global[0]", "udp_node") or ""
- if udp_node == w or udp_node == socks then
- uci:delete(appname, '@global[0]', "udp_node")
- end
- uci:foreach(appname, "haproxy_config", function(t)
+ uci_foreach("haproxy_config", function(t)
if t["lbss"] == w then
- uci:delete(appname, t[".name"])
+ uci_del(t[".name"])
end
end)
- uci:foreach(appname, "acl_rule", function(t)
- if t["tcp_node"] == w or t["tcp_node"] == socks then
- uci:delete(appname, t[".name"], "tcp_node")
+ uci_foreach("acl_rule", function(t)
+ if t["tcp_node"] == w then
+ uci_del(t[".name"], "tcp_node")
end
- if t["udp_node"] == w or t["udp_node"] == socks then
- uci:delete(appname, t[".name"], "udp_node")
+ if t["udp_node"] == w then
+ uci_del(t[".name"], "udp_node")
end
end)
- uci:foreach(appname, "nodes", function(t)
+ uci_foreach("nodes", function(t)
if t["preproxy_node"] == w then
- uci:delete(appname, t[".name"], "preproxy_node")
- uci:delete(appname, t[".name"], "chain_proxy")
+ uci_del(t[".name"], "preproxy_node")
+ uci_del(t[".name"], "chain_proxy")
end
if t["to_node"] == w then
- uci:delete(appname, t[".name"], "to_node")
- uci:delete(appname, t[".name"], "chain_proxy")
+ uci_del(t[".name"], "to_node")
+ uci_del(t[".name"], "chain_proxy")
end
local list_name = t["urltest_node"] and "urltest_node" or (t["balancing_node"] and "balancing_node")
if list_name then
- local nodes = uci:get_list(appname, t[".name"], list_name)
+ local nodes = uci:get_list(c_config, t[".name"], list_name)
if nodes then
local changed = false
local new_nodes = {}
@@ -647,48 +650,48 @@ function delete_select_nodes()
end
end
if changed then
- uci:set_list(appname, t[".name"], list_name, new_nodes)
+ uci_set(t[".name"], list_name, new_nodes)
end
end
end
- if t["fallback_node"] == w or t["fallback_node"] == socks then
- uci:delete(appname, t[".name"], "fallback_node")
+ if t["fallback_node"] == w then
+ uci_del(t[".name"], "fallback_node")
end
end)
- uci:foreach(appname, "subscribe_list", function(t)
+ uci_foreach("subscribe_list", function(t)
if t["preproxy_node"] == w then
- uci:delete(appname, t[".name"], "preproxy_node")
- uci:delete(appname, t[".name"], "chain_proxy")
+ uci_del(t[".name"], "preproxy_node")
+ uci_del(t[".name"], "chain_proxy")
end
if t["to_node"] == w then
- uci:delete(appname, t[".name"], "to_node")
- uci:delete(appname, t[".name"], "chain_proxy")
+ uci_del(t[".name"], "to_node")
+ uci_del(t[".name"], "chain_proxy")
end
end)
- if (uci:get(appname, w, "add_mode") or "0") == "2" then
- local group = uci:get(appname, w, "group") or ""
+ if (uci_get(w, "add_mode") or "0") == "2" then
+ local group = uci_get(w, "group") or ""
if group ~= "" then
- uci:foreach(appname, "subscribe_list", function(t)
+ uci_foreach("subscribe_list", function(t)
if t["remark"] == group then
- uci:delete(appname, t[".name"], "md5")
+ uci_del(t[".name"], "md5")
end
end)
end
end
- uci:delete(appname, w)
+ uci_del(w)
end)
if redirect == "1" then
- api.uci_save(uci, appname)
+ uci_save()
http.redirect(api.url("node_list"))
else
- api.uci_save(uci, appname, true, true)
+ uci_save(true, true)
end
end
function get_node()
local id = http.formvalue("id")
local result = {}
- local show_node_info = api.uci_get_type("global_other", "show_node_info", "0")
+ local show_node_info = uci_get("@global_other[0]", "show_node_info") or "0"
local function add_is_ipv6_key(o)
if o and o.address and show_node_info == "1" then
@@ -701,12 +704,12 @@ function get_node()
end
if id then
- result = uci:get_all(appname, id)
+ result = uci_get(id)
add_is_ipv6_key(result)
else
local default_nodes = {}
local other_nodes = {}
- uci:foreach(appname, "nodes", function(t)
+ uci_foreach("nodes", function(t)
add_is_ipv6_key(t)
if not t.group or t.group == "" then
default_nodes[#default_nodes + 1] = t
@@ -720,30 +723,17 @@ function get_node()
http_write_json(result)
end
-function save_node_order()
- local ids = http.formvalue("ids") or ""
- local new_order = {}
- for id in ids:gmatch("([^,]+)") do
- new_order[#new_order + 1] = id
- end
- for idx, name in ipairs(new_order) do
- luci.sys.call(string.format("uci -q reorder %s.%s=%d", appname, name, idx - 1))
- end
- api.sh_uci_commit(appname)
- http_write_json({ status = "ok" })
-end
-
function reassign_group()
local ids = http.formvalue("ids") or ""
local group = http.formvalue("group") or "default"
for id in ids:gmatch("([^,]+)") do
if group ~="" and group ~= "default" then
- api.sh_uci_set(appname, id, "group", group)
+ api.sh_uci_set(c_config, id, "group", group)
else
- api.sh_uci_del(appname, id, "group")
+ api.sh_uci_del(c_config, id, "group")
end
end
- api.sh_uci_commit(appname)
+ api.sh_uci_commit(c_config)
http_write_json({ status = "ok" })
end
@@ -751,7 +741,7 @@ function save_node_list_opt()
local option = http.formvalue("option") or ""
local value = http.formvalue("value") or ""
if option ~= "" then
- api.sh_uci_set(appname, "@global_other[0]", option, value, true)
+ api.sh_uci_set(c_config, "@global_other[0]", option, value, true)
end
http_write_json({ status = "ok" })
end
@@ -774,8 +764,8 @@ function rollback_rules()
return
end
local bak_dir = "/tmp/bak_v2ray/"
- local geo_dir = (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/")
- local geo2rule = uci:get(appname, "@global_rules[0]", "geo2rule") or "0"
+ local geo_dir = (uci_get("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/")
+ local geo2rule = uci_get("@global_rules[0]", "geo2rule") or "0"
fs.move(bak_dir .. arg_type .. ".dat", geo_dir .. arg_type .. ".dat")
fs.rmdir(bak_dir)
if geo2rule == "1" and rules ~= "" then
@@ -791,9 +781,9 @@ function server_update_config()
local data_t = jsonParse(data) or {}
if next(data_t) then
for k, v in pairs(data_t) do
- uci:set(appname .. "_server", id, k, v)
+ api.uci_set_s(id, k, v)
end
- api.uci_save(uci, appname .. "_server")
+ api.uci_save_s()
http_write_json_ok()
return
end
@@ -801,17 +791,18 @@ function server_update_config()
http_write_json_error()
end
-function server_user_status()
+function server_status()
local e = {}
e.index = http.formvalue("index")
e.status = luci.sys.call(string.format("/bin/busybox top -bn1 | grep -v 'grep' | grep '%s/bin/' | grep -i '%s' >/dev/null", appname .. "_server", http.formvalue("id"))) == 0
http_write_json(e)
end
-function server_user_log()
+function server_log()
local id = http.formvalue("id")
- if fs.access("/tmp/etc/passwall_server/" .. id .. ".log") then
- local content = luci.sys.exec("cat /tmp/etc/passwall_server/" .. id .. ".log")
+ local f_file = api.S_TMP_PATH .. "/" .. id .. ".log"
+ if nixio.fs.access(f_file) then
+ local content = luci.sys.exec("cat " .. f_file)
content = content:gsub("\n", "
")
http.write(content)
else
@@ -928,7 +919,7 @@ function restore_backup()
fp:write(decoded)
fp:close()
if chunk_index + 1 == total_chunks then
- uci:revert(appname)
+ uci:revert(c_config)
luci.sys.call("echo '' > /tmp/log/passwall.log")
api.log(" * PassWall 配置文件上传成功…")
local temp_dir = '/tmp/passwall_bak'
@@ -971,7 +962,7 @@ function geo_view()
end
local function get_rules(str, type)
local rules_id = {}
- uci:foreach(appname, "shunt_rules", function(s)
+ uci_foreach("shunt_rules", function(s)
local list
if type == "geoip" then list = s.ip_list else list = s.domain_list end
for line in string.gmatch((list or ""), "[^\r\n]+") do
@@ -988,7 +979,7 @@ function geo_view()
end)
return rules_id
end
- local geo_dir = (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/")
+ local geo_dir = (uci_get("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/")
local geosite_path = geo_dir .. "/geosite.dat"
local geoip_path = geo_dir .. "/geoip.dat"
local geo_type, file_path, cmd
@@ -1060,13 +1051,13 @@ function subscribe_manual()
http_write_json({ success = false, msg = "Missing section or URL, skip." })
return
end
- local uci_url = api.sh_uci_get(appname, section, "url")
+ local uci_url = api.sh_uci_get(c_config, section, "url")
if not uci_url or uci_url == "" then
http_write_json({ success = false, msg = i18n.translate("Please save and apply before manually subscribing.") })
return
end
if uci_url ~= current_url then
- api.sh_uci_set(appname, section, "url", current_url, true)
+ api.sh_uci_set(c_config, section, "url", current_url, true)
end
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start " .. section .. " manual >/dev/null 2>&1 &")
http_write_json({ success = true, msg = "Subscribe triggered." })
@@ -1083,7 +1074,7 @@ function subscribe_manual_all()
local url_list = util.split(urls, ",")
-- 检查是否存在未保存配置
for i, section in ipairs(section_list) do
- local uci_url = api.sh_uci_get(appname, section, "url")
+ local uci_url = api.sh_uci_get(c_config, section, "url")
if not uci_url or uci_url == "" then
http_write_json({ success = false, msg = i18n.translate("Please save and apply before manually subscribing.") })
return
@@ -1092,9 +1083,9 @@ function subscribe_manual_all()
-- 保存有变动的url
for i, section in ipairs(section_list) do
local current_url = url_list[i] or ""
- local uci_url = api.sh_uci_get(appname, section, "url")
+ local uci_url = api.sh_uci_get(c_config, section, "url")
if current_url ~= "" and uci_url ~= current_url then
- api.sh_uci_set(appname, section, "url", current_url, true)
+ api.sh_uci_set(c_config, section, "url", current_url, true)
end
end
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start all manual >/dev/null 2>&1 &")
@@ -1105,10 +1096,10 @@ function flush_set()
local redirect = http.formvalue("redirect") or "0"
local reload = http.formvalue("reload") or "0"
if reload == "1" then
- uci:set(appname, '@global[0]', "flush_set", "1")
- api.uci_save(uci, appname, true, true)
+ uci_set('@global[0]', "flush_set", "1")
+ uci_save(true, true)
else
- api.sh_uci_set(appname, "@global[0]", "flush_set", "1", true)
+ api.sh_uci_set(c_config, "@global[0]", "flush_set", "1", true)
end
if redirect == "1" then
http.redirect(api.url("log"))
@@ -1117,17 +1108,17 @@ end
function fetch_certsha256()
local id = http.formvalue("id") or ""
- local address = (id ~= "") and uci:get(appname, id, "address") or ""
- local port = (id ~= "") and uci:get(appname, id, "port") or 0
- local sni = (id ~= "") and uci:get(appname, id, "tls_serverName") or ""
+ local address = (id ~= "") and uci_get(id, "address") or ""
+ local port = (id ~= "") and uci_get(id, "port") or 0
+ local sni = (id ~= "") and uci_get(id, "tls_serverName") or ""
sni = (sni ~= "") and sni or address
- local protocol = uci:get(appname, id, "protocol")
+ local protocol = uci_get(id, "protocol")
local h3, timeout = false, 10
if protocol == "hysteria2" then
h3 = true
timeout = 60
if port == 0 then
- local hop = uci:get(appname, id, "hysteria2_hop") or "0"
+ local hop = uci_get(id, "hysteria2_hop") or "0"
port = tonumber(hop:match("^%s*(%d+)"))
end
end
@@ -1144,11 +1135,11 @@ function get_shunt_rules()
local result = {}
if id then
- result = uci:get_all(appname, id)
+ result = uci_get(id)
else
local default_items = {}
local other_items = {}
- uci:foreach(appname, "shunt_rules", function(t)
+ uci_foreach("shunt_rules", function(t)
if not t.group or t.group == "" then
default_items[#default_items + 1] = t
else
@@ -1167,26 +1158,26 @@ function add_shunt_rule()
local uid = add_name
if add_name then
- local has = uci:get(appname, uid)
+ local has = uci_get(uid)
if has then
- http_write_json_error({ message = "This ID already exists." })
+ http_write_json_error({ message = i18n.translate("This ID already exists.") })
return
end
else
uid = api.gen_random_char()
end
- uci:section(appname, "shunt_rules", uid)
+ uci:section(c_config, "shunt_rules", uid)
local group = http.formvalue("group")
if group and group ~= "default" then
- uci:set(appname, uid, "group", group)
+ uci_set(uid, "group", group)
end
if redirect == "1" then
- api.uci_save(uci, appname)
+ uci_save()
http.redirect(api.url("shunt_rules", uid))
else
- api.uci_save(uci, appname)
+ uci_save()
http_write_json_ok({uid = uid, redirect_url = api.url("shunt_rules", uid)})
end
end
@@ -1195,30 +1186,26 @@ function delete_select_shunt_rules()
local ids = http.formvalue("ids")
local redirect = http.formvalue("redirect")
string.gsub(ids, '[^' .. "," .. ']+', function(w)
- uci:foreach(appname, "nodes", function(s)
+ uci_foreach("nodes", function(s)
if s["protocol"] and s["protocol"] == "_shunt" then
- uci:delete(appname, s[".name"], w)
+ uci_del(s[".name"], w)
end
end)
- uci:delete(appname, w)
+ uci_del(w)
end)
if redirect == "1" then
- api.uci_save(uci, appname)
+ uci_save()
http.redirect(api.url("rule"))
else
- api.uci_save(uci, appname, true, true)
+ uci_save(true, true)
end
end
-function save_shunt_rule_order()
- local ids = http.formvalue("ids") or ""
- local new_order = {}
- for id in ids:gmatch("([^,]+)") do
- new_order[#new_order + 1] = id
- end
- for idx, name in ipairs(new_order) do
- luci.sys.call(string.format("uci -q reorder %s.%s=%d", appname, name, idx - 1))
+function gen_wireguard_key()
+ local key = api.gen_wireguard_key()
+ if key then
+ http_write_json_ok(key)
+ else
+ http_write_json_error()
end
- api.sh_uci_commit(appname)
- http_write_json({ status = "ok" })
end
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl.lua
index 03765191df5..7ca7b6cccb8 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl.lua
@@ -1,22 +1,17 @@
local api = require "luci.passwall.api"
-local appname = "passwall"
-local sys = api.sys
api.set_default_cbi()
-m = Map(appname)
-api.set_apply_on_parse(m)
+m = Map()
-s = m:section(TypedSection, "global", translate("ACLs"), "" .. translate("ACLs is a tools which used to designate specific IP proxy mode.") .. "")
-s.anonymous = true
+s = m:section(NamedSection, "@global[0]", "global", translate("ACLs"), "" .. translate("ACLs is a tools which used to designate specific IP proxy mode.") .. "")
o = s:option(Flag, "acl_enable", translate("Main switch"))
o.rmempty = false
o.default = false
-- [[ ACLs Settings ]]--
-local cfgname = "acl_rule"
-s = m:section(TypedSection, cfgname)
+s = m:section(TypedSection, "acl_rule")
s.template = "cbi/tblsection"
s.sortable = true
s.anonymous = true
@@ -28,7 +23,7 @@ function s.create(e, t)
luci.http.redirect(e.extedit:format(uid))
end
function s.remove(e, t)
- sys.call("rm -rf /tmp/etc/passwall_tmp/dns_" .. t .. "*")
+ api.sys.call("rm -rf /tmp/etc/passwall_tmp/dns_" .. t .. "*")
TypedSection.remove(e, t)
end
@@ -42,7 +37,7 @@ o = s:option(Value, "remarks", translate("Remarks"))
o.rmempty = true
local mac_t = {}
-sys.net.mac_hints(function(e, t)
+api.sys.net.mac_hints(function(e, t)
mac_t[e] = {
ip = t,
mac = e
@@ -85,41 +80,6 @@ i.cfgvalue = function(t, n)
return translate("No Proxy")
end
---[[
----- TCP No Redir Ports
-o = s:option(Value, "tcp_no_redir_ports", translate("TCP No Redir Ports"))
-o.default = "default"
-o:value("disable", translate("No patterns are used"))
-o:value("default", translate("Default"))
-o:value("1:65535", translate("All"))
-
----- UDP No Redir Ports
-o = s:option(Value, "udp_no_redir_ports", translate("UDP No Redir Ports"))
-o.default = "default"
-o:value("disable", translate("No patterns are used"))
-o:value("default", translate("Default"))
-o:value("1:65535", translate("All"))
-
----- TCP Redir Ports
-o = s:option(Value, "tcp_redir_ports", translate("TCP Redir Ports"))
-o.default = "default"
-o:value("default", translate("Default"))
-o:value("1:65535", translate("All"))
-o:value("80,443", "80,443")
-o:value("80:65535", "80 " .. translate("or more"))
-o:value("1:443", "443 " .. translate("or less"))
-
----- UDP Redir Ports
-o = s:option(Value, "udp_redir_ports", translate("UDP Redir Ports"))
-o.default = "default"
-o:value("default", translate("Default"))
-o:value("1:65535", translate("All"))
-o:value("53", "53")
-]]--
-
-local sortable = Template(appname .. "/cbi/sortable")
-sortable.api = api
-sortable.target_cfgname = cfgname
-m:append(sortable)
+m:appendTemplate("/cbi/sortable", {sectiontype = s.sectiontype})
return api.return_map(m)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua
index 453a7584804..8234f4208aa 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua
@@ -1,26 +1,20 @@
local api = require "luci.passwall.api"
-local appname = "passwall"
-
api.set_default_cbi()
-m = Map(appname)
+m = Map()
m.redirect = api.url("acl")
-api.set_apply_on_parse(m)
-local cfgid = arg[1]
-if not cfgid or not m:get(cfgid) then
+if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(m.redirect)
end
-m:append(Template(appname .. "/cbi/nodes_listvalue_com"))
+m:appendTemplate("/cbi/nodes_listvalue_com")
-local fs = api.fs
-local sys = api.sys
local has_singbox = api.finded_com("sing-box")
local has_xray = api.finded_com("xray")
-local has_gfwlist = fs.access("/usr/share/passwall/rules/gfwlist")
-local has_chnlist = fs.access("/usr/share/passwall/rules/chnlist")
-local has_chnroute = fs.access("/usr/share/passwall/rules/chnroute")
+local has_gfwlist = api.fs.access("/usr/share/passwall/rules/gfwlist")
+local has_chnlist = api.fs.access("/usr/share/passwall/rules/chnlist")
+local has_chnroute = api.fs.access("/usr/share/passwall/rules/chnroute")
local port_validate = function(self, value, t)
return value:gsub("-", ":")
@@ -39,10 +33,10 @@ for _, v in pairs(nodes_table) do
end
local socks_list = {}
-m.uci:foreach(appname, "socks", function(s)
+m:foreach("socks", function(s)
if s.enabled == "1" and s.node then
socks_list[#socks_list + 1] = {
- id = "Socks_" .. s[".name"],
+ id = s[".name"],
remark = translate("Socks Config") .. " " .. string.format("[%s %s]", s.port, translate("Port")),
group = "Socks"
}
@@ -50,7 +44,7 @@ m.uci:foreach(appname, "socks", function(s)
end)
-- [[ ACLs Settings ]]--
-s = m:section(NamedSection, cfgid, translate("ACLs"), translate("ACLs"))
+s = m:section(NamedSection, arg[1], translate("ACLs"), translate("ACLs"))
s.addremove = false
s.dynamic = false
@@ -61,7 +55,7 @@ o.rmempty = false
---- Remarks
o = s:option(Value, "remarks", translate("Remarks"))
-o.default = cfgid
+o.default = arg[1]
o.rmempty = false
o = s:option(Value, "interface", translate("Source Interface"))
@@ -78,7 +72,7 @@ o.validate = function(self, value, section)
end
local mac_t = {}
-sys.net.mac_hints(function(e, t)
+api.sys.net.mac_hints(function(e, t)
mac_t[#mac_t + 1] = {
ip = t,
mac = e
@@ -188,7 +182,7 @@ o:depends("mode", "1")
o.validate = port_validate
o = s:option(DummyValue, "_hide_node_option", "")
-o.template = "passwall/cbi/hidevalue"
+o.template = m:template_path("/cbi/hidevalue")
o.value = "1"
o:depends("mode", "0")
o:depends({ tcp_no_redir_ports = "1:65535", udp_no_redir_ports = "1:65535" })
@@ -204,7 +198,7 @@ o:depends({ _hide_node_option = "1", ['!reverse'] = true })
o = s:option(ListValue, "tcp_node", "" .. translate("TCP Node") .. "")
o.default = ""
o:depends({ _hide_node_option = false, use_global_config = false })
-o.template = appname .. "/cbi/nodes_listvalue"
+o.template = m:template_path("/cbi/nodes_listvalue")
o.group = {}
o.remove = function(self, section)
m:del(section, self.option)
@@ -212,7 +206,7 @@ o.remove = function(self, section)
end
o = s:option(DummyValue, "_tcp_node_bool", "")
-o.template = "passwall/cbi/hidevalue"
+o.template = m:template_path("/cbi/hidevalue")
o.value = "1"
o:depends({ tcp_node = "", ['!reverse'] = true })
@@ -221,7 +215,7 @@ o.default = ""
o:value("", translate("Close"))
o:value("tcp", translate("Same as the tcp node"))
o:depends({ _tcp_node_bool = "1", _node_sel_other = "1" })
-o.template = appname .. "/cbi/nodes_listvalue"
+o.template = m:template_path("/cbi/nodes_listvalue")
o.group = {"",""}
o.remove = function(self, section)
local v = s.fields["shunt_udp_node"]:formvalue(section)
@@ -248,7 +242,7 @@ o.write = function(self, section, value)
end
o = s:option(DummyValue, "_udp_node_bool", "")
-o.template = "passwall/cbi/hidevalue"
+o.template = m:template_path("/cbi/hidevalue")
o.value = "1"
o:depends({ udp_node = "", ['!reverse'] = true })
o:depends({ shunt_udp_node = "tcp" })
@@ -355,17 +349,17 @@ o:value("proxy", translate("Proxy"))
o:depends({ _udp_node_bool = "1" })
o = s:option(DummyValue, "switch_mode", " ")
-o.template = appname .. "/global/proxy"
+o.template = m:template_path("/global/proxy")
o:depends({ _tcp_node_bool = "1" })
-- Node → DNS Depends Settings
o = s:option(DummyValue, "_node_sel_shunt", "")
-o.template = appname .. "/cbi/hidevalue"
+o.template = m:template_path("/cbi/hidevalue")
o.value = "1"
o:depends({ tcp_node = "__always__" })
o = s:option(DummyValue, "_node_sel_other", "")
-o.template = appname .. "/cbi/hidevalue"
+o.template = m:template_path("/cbi/hidevalue")
o.value = "1"
o:depends({ _node_sel_shunt = "1", ['!reverse'] = true })
@@ -525,7 +519,7 @@ o.validate = function(self, value, t)
local _dns_mode = s.fields["dns_mode"]:formvalue(t)
local _tcp_node = s.fields["tcp_node"]:formvalue(t)
if _dns_mode and _tcp_node then
- if (m:get(_tcp_node, "type") or ""):lower() ~= _dns_mode and not _tcp_node:find("Socks_") then
+ if (m:get(_tcp_node, "type") or ""):lower() ~= _dns_mode and not _tcp_node:find("socks_") then
return nil, translatef("TCP node must be '%s' type to use FakeDNS.", _dns_mode)
end
end
@@ -533,6 +527,11 @@ o.validate = function(self, value, t)
return value
end
+o = s:option(Value, "remote_rewrite_ttl", translate("Remote DNS") .. " TTL")
+o.datatype = "min(1)"
+o.default = "30"
+o:depends({dns_mode = "sing-box"})
+
o = s:option(ListValue, "chinadns_ng_default_tag", translate("Default DNS"))
o.default = "none"
o:value("gfw", translate("Remote DNS"))
@@ -594,6 +593,7 @@ for k, v in pairs(nodes_table) do
s.fields["singbox_dns_mode"]:depends({ _tcp_node_bool = "1", tcp_node = v.id })
s.fields["_node_sel_shunt"]:depends({ tcp_node = v.id })
+ s.fields["remote_rewrite_ttl"]:depends({ _tcp_node_bool = "1", tcp_node = v.id })
end
else
tcp:value(v.id, v["remark"])
@@ -603,9 +603,6 @@ for k, v in pairs(nodes_table) do
end
end
-local footer = Template(appname .. "/acl/config_footer")
-footer.api = api
-footer.cfgid = cfgid
-m:append(footer)
+m:appendTemplate("/acl/config_footer", {section = arg[1]})
return api.return_map(m)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua
index 3f2a7760440..c387860051b 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua
@@ -1,21 +1,15 @@
local api = require "luci.passwall.api"
-local com = require "luci.passwall.com"
-local appname = "passwall"
api.set_default_cbi()
-m = Map(appname)
-api.set_apply_on_parse(m)
+local com = require "luci.passwall.com"
+
+m = Map()
-- [[ App Settings ]]--
-s = m:section(TypedSection, "global_app", translate("App Update"))
-s.anonymous = true
-
-local app_version = Template(appname .. "/app_update/app_version")
-app_version.api = api
-app_version.config = m.config
-app_version.com = com
-s:append(app_version)
+s = m:section(NamedSection, "@global_app[0]", "global_app", translate("App Update"))
+
+s:appendTemplate("/app_update/app_version", {com = com})
o = s:option(Flag, "github_proxy", translate("GitHub Proxy"), translate("Use gh-proxy instead of proxy nodes for component updates."))
o.default = 0
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
index f19fcaf3cdf..c35e594b631 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
@@ -1,5 +1,4 @@
api = require "luci.passwall.api"
-appname = "passwall"
datatypes = api.datatypes
local fs = api.fs
has_singbox = api.finded_com("sing-box")
@@ -10,10 +9,9 @@ local has_chnroute = fs.access("/usr/share/passwall/rules/chnroute")
api.set_default_cbi()
-m = Map(appname)
-api.set_apply_on_parse(m)
+m = Map()
-m:append(Template(appname .. "/cbi/nodes_listvalue_com"))
+m:appendTemplate("/cbi/nodes_listvalue_com")
local nodes_table = {}
for _, e in ipairs(api.get_valid_nodes()) do
@@ -51,7 +49,7 @@ socks_table[#socks_table + 1] = {
id = tcp_socks_server,
remark = tcp_socks_server .. " - " .. translate("TCP Node")
}
-m.uci:foreach(appname, "socks", function(s)
+m:foreach("socks", function(s)
if s.enabled == "1" and s.node then
local id, remark
for k, n in pairs(nodes_table) do
@@ -95,13 +93,9 @@ local doh_validate = function(self, value, t)
return nil, translatef("%s request address","DoH") .. " " .. translate("Format must be:") .. " URL,IP"
end
-m:append(Template(appname .. "/global/status"))
+m:appendTemplate("/global/status")
-global_cfgid = (m:get("@global[0]") or {})[".name"] or ""
-
-s = m:section(TypedSection, "global")
-s.anonymous = true
-s.addremove = false
+s = m:section(NamedSection, "@global[0]", "global")
s:tab("Main", translate("Main"))
@@ -111,13 +105,13 @@ o.rmempty = false
---- TCP Node
o = s:taboption("Main", ListValue, "tcp_node", "" .. translate("TCP Node") .. "")
-o.template = appname .. "/cbi/nodes_listvalue"
+o.template = m:template_path("/cbi/nodes_listvalue")
o:value("", translate("Close"))
o.group = {""}
---- UDP Node
o = s:taboption("Main", ListValue, "udp_node", "" .. translate("UDP Node") .. "")
-o.template = appname .. "/cbi/nodes_listvalue"
+o.template = m:template_path("/cbi/nodes_listvalue")
o:value("", translate("Close"))
o:value("tcp", translate("Same as the tcp node"))
o.group = {"",""}
@@ -146,15 +140,16 @@ o.write = function(self, section, value)
return m:set(section, "udp_node", value)
end
+current_node_id = m:get(s.section, "tcp_node")
+current_node = current_node_id and m:get(current_node_id) or {}
+
-- Shunt Start
if (has_singbox or has_xray) and #nodes_table > 0 then
if #normal_list > 0 or #iface_list > 0 then
- current_node_id = m.uci:get(appname, global_cfgid, "tcp_node")
- current_node = current_node_id and m.uci:get_all(appname, current_node_id) or {}
if current_node.protocol == "_shunt" then
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
setfenv(shunt_lua, getfenv(1))(m, s, {
- s_cfgid = s:cfgsections()[1],
+ s_cfgid = s.section,
node_id = current_node_id,
node = current_node,
socks_list = socks_list,
@@ -200,12 +195,12 @@ o:depends({ tcp_node = "", ["!reverse"] = true })
-- Node → DNS Depends Settings
o = s:taboption("Main", DummyValue, "_node_sel_shunt", "")
-o.template = appname .. "/cbi/hidevalue"
+o.template = m:template_path("/cbi/hidevalue")
o.value = "1"
o:depends({ tcp_node = "__always__" })
o = s:taboption("Main", DummyValue, "_node_sel_other", "")
-o.template = appname .. "/cbi/hidevalue"
+o.template = m:template_path("/cbi/hidevalue")
o.value = "1"
o:depends({ _node_sel_shunt = "1", ['!reverse'] = true })
@@ -485,7 +480,7 @@ o.validate = function(self, value, t)
end
local _tcp_node = s.fields["tcp_node"]:formvalue(t)
if _dns_mode and _tcp_node then
- if (m:get(_tcp_node, "type") or ""):lower() ~= _dns_mode and not _tcp_node:find("Socks_") then
+ if (m:get(_tcp_node, "type") or ""):lower() ~= _dns_mode and not _tcp_node:find("socks_") then
return nil, translatef("TCP node must be '%s' type to use FakeDNS.", _dns_mode)
end
end
@@ -493,6 +488,13 @@ o.validate = function(self, value, t)
return value
end
+o = s:taboption("DNS", Value, "remote_rewrite_ttl", translate("Remote DNS") .. " TTL")
+o.datatype = "min(1)"
+o.default = "30"
+o:depends({dns_mode = "sing-box", dns_shunt = "dnsmasq"})
+o:depends({dns_mode = "sing-box", dns_shunt = "chinadns-ng"})
+o:depends({smartdns_dns_mode = "sing-box", dns_shunt = "smartdns"})
+
o = s:taboption("DNS", ListValue, "chinadns_ng_default_tag", translate("Default DNS"))
o.default = "none"
o:value("gfw", translate("Remote DNS"))
@@ -583,7 +585,7 @@ o:value("proxy", translate("Proxy"))
o.default = "proxy"
o = s:taboption("Proxy", DummyValue, "switch_mode", " ")
-o.template = appname .. "/global/proxy"
+o.template = m:template_path("/global/proxy")
---- Check the transparent proxy component
local handle = io.popen("lsmod")
@@ -662,11 +664,11 @@ end
s:tab("faq", "FAQ")
o = s:taboption("faq", DummyValue, "")
-o.template = appname .. "/global/faq"
+o.template = m:template_path("/global/faq")
s:tab("maintain", translate("Maintain"))
o = s:taboption("maintain", DummyValue, "")
-o.template = appname .. "/global/backup"
+o.template = m:template_path("/global/backup")
-- [[ Socks Server ]]--
o = s:taboption("Main", Flag, "socks_enabled", "Socks " .. translate("Main switch"))
@@ -674,6 +676,7 @@ o.rmempty = false
s2 = m:section(TypedSection, "socks", translate("Socks Config"))
s2.template = "cbi/tblsection"
+s2.sortable = true
s2.anonymous = true
s2.addremove = true
s2.extedit = api.url("socks_config", "%s")
@@ -683,46 +686,45 @@ function s2.create(e, t)
luci.http.redirect(e.extedit:format(uid))
end
function s2.remove(e, t)
- local socks = "Socks_" .. t
local new_node = ""
local node0 = m:get("@nodes[0]") or nil
if node0 then
new_node = node0[".name"]
end
- if (m:get("@global[0]", "tcp_node") or "") == socks then
+ if (m:get("@global[0]", "tcp_node") or "") == t then
m:set('@global[0]', "tcp_node", new_node)
end
- if (m:get("@global[0]", "udp_node") or "") == socks then
+ if (m:get("@global[0]", "udp_node") or "") == t then
m:set('@global[0]', "udp_node", new_node)
end
- m.uci:foreach(appname, "acl_rule", function(s)
- if s["tcp_node"] and s["tcp_node"] == socks then
+ m:foreach("acl_rule", function(s)
+ if s["tcp_node"] and s["tcp_node"] == t then
m:set(s[".name"], "tcp_node", "default")
end
- if s["udp_node"] and s["udp_node"] == socks then
+ if s["udp_node"] and s["udp_node"] == t then
m:set(s[".name"], "udp_node", "default")
end
end)
- m.uci:foreach(appname, "nodes", function(s)
+ m:foreach("nodes", function(s)
local list_name = s["urltest_node"] and "urltest_node" or (s["balancing_node"] and "balancing_node")
if list_name then
- local nodes = m.uci:get_list(appname, s[".name"], list_name)
+ local nodes = m.uci:get_list(api.c_config, s[".name"], list_name)
if nodes then
local changed = false
local new_nodes = {}
for _, node in ipairs(nodes) do
- if node ~= socks then
+ if node ~= t then
table.insert(new_nodes, node)
else
changed = true
end
end
if changed then
- m.uci:set_list(appname, s[".name"], list_name, new_nodes)
+ api.uci_set_c(s[".name"], list_name, new_nodes)
end
end
end
- if s["fallback_node"] == socks then
+ if s["fallback_node"] == t then
m:del(s[".name"], "fallback_node")
end
end)
@@ -741,7 +743,7 @@ o.default = 1
o.rmempty = false
o = s2:option(ListValue, "node", translate("Socks Node"))
-o.template = appname .. "/cbi/nodes_listvalue"
+o.template = m:template_path("/cbi/nodes_listvalue")
o.group = {}
o = s2:option(DummyValue, "now_node", translate("Current Node"))
@@ -757,7 +759,7 @@ o.cfgvalue = function(_, n)
end
local n = 1
-m.uci:foreach(appname, "socks", function(s)
+m:foreach("socks", function(s)
if s[".name"] == section then
return false
end
@@ -769,11 +771,13 @@ o.default = n + 1080
o.datatype = "port"
o.rmempty = false
+--[[
if has_singbox or has_xray then
o = s2:option(Value, "http_port", "HTTP " .. translate("Listen Port"))
o.default = 0
o.datatype = "port"
end
+]]--
local tcp = s.fields["tcp_node"]
local udp = s.fields["udp_node"]
@@ -800,6 +804,7 @@ for k, v in pairs(nodes_table) do
s.fields["xray_dns_mode"]:depends({ tcp_node = v.id })
else
s.fields["singbox_dns_mode"]:depends({ tcp_node = v.id })
+ s.fields["remote_rewrite_ttl"]:depends({ tcp_node = v.id })
end
end
else
@@ -819,10 +824,8 @@ for k, v in pairs(nodes_table) do
end
end
-local footer = Template(appname .. "/global/footer")
-footer.api = api
-footer.global_cfgid = global_cfgid
-footer.shunt_list = api.jsonc.stringify(shunt_list)
-m:append(footer)
+m:appendTemplate("/global/footer", {shunt_list = api.jsonc.stringify(shunt_list)})
+
+m:appendTemplate("/cbi/sortable", {sectiontype = s2.sectiontype})
return api.return_map(m)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua
index bc3046cef7f..71b5bacc972 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua
@@ -1,7 +1,6 @@
local api = require "luci.passwall.api"
-local appname = api.appname
local datatypes = api.datatypes
-local net = require "luci.model.network".init()
+api.set_default_cbi()
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
@@ -15,18 +14,14 @@ for k, e in ipairs(api.get_valid_nodes()) do
end
end
-api.set_default_cbi()
-
-m = Map(appname, "HAPROXY " .. translate("Load Balancing"))
-api.set_apply_on_parse(m)
+m = Map(api.c_config, "HAPROXY " .. translate("Load Balancing"))
-m:append(Template(appname .. "/cbi/nodes_value_com"))
+m:appendTemplate("/cbi/nodes_value_com")
-- [[ Haproxy Settings ]]--
-s = m:section(TypedSection, "global_haproxy", translate("Basic Settings"))
-s.anonymous = true
+s = m:section(NamedSection, "@global_haproxy[0]", "global_haproxy", translate("Basic Settings"))
-s:append(Template(appname .. "/haproxy/status"))
+s:appendTemplate("/haproxy/status")
---- Balancing Enable
o = s:option(Flag, "balancing_enable", translate("Enable Load Balancing"))
@@ -69,9 +64,9 @@ o:depends("balancing_enable", true)
---- Health Check Type
o = s:option(ListValue, "health_check_type", translate("Health Check Type"))
-o.default = "passwall_logic"
+o.default = "script_logic"
o:value("tcp", "TCP")
-o:value("passwall_logic", translate("URL Test") .. string.format("(passwall %s)", translate("Inner implement")))
+o:value("script_logic", translate("URL Test") .. string.format("(passwall %s)", translate("Inner implement")))
o:depends("balancing_enable", true)
---- Passwall Inner implement Probe URL
@@ -85,7 +80,7 @@ o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud
o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
o.default = o.keylist[3]
o.description = translate("The URL used to detect the connection status.")
-o:depends("health_check_type", "passwall_logic")
+o:depends("health_check_type", "script_logic")
---- Health Check Inter
o = s:option(Value, "health_check_inter", translate("Health Check Inter"))
@@ -100,11 +95,10 @@ o.rawhtml = true
o.cfgvalue = function(t, n)
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."))
end
-o:depends("health_check_type", "passwall_logic")
+o:depends("health_check_type", "script_logic")
-- [[ Balancing Settings ]]--
-local cfgname = "haproxy_config"
-s = m:section(TypedSection, cfgname, translate("Node List"))
+s = m:section(TypedSection, "haproxy_config", translate("Node List"))
s.description = "" ..
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.") .. "
" ..
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!") .. ""
@@ -133,7 +127,7 @@ o.rmempty = false
---- Node Address
o = s:option(Value, "lbss", translate("Node Address"))
-o.template = appname .. "/cbi/nodes_value"
+o.template = m:template_path("/cbi/nodes_value")
o.group = {}
for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks)
@@ -181,11 +175,8 @@ o:value(0, translate("Primary"))
o:value(1, translate("Standby"))
o.rmempty = false
-local sortable = Template(appname .. "/cbi/sortable")
-sortable.api = api
-sortable.target_cfgname = cfgname
-m:append(sortable)
+m:appendTemplate("/cbi/sortable", {sectiontype = s.sectiontype})
-m:append(Template(appname .. "/haproxy/js"))
+m:appendTemplate("/haproxy/js")
return api.return_map(m)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/include/shunt_options.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/include/shunt_options.lua
index 4867e92fb82..60fff90b432 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/include/shunt_options.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/include/shunt_options.lua
@@ -9,7 +9,7 @@ local current_node_id = data.node_id
local node_list = data.node_list or api.get_node_list()
local groups = {}
-m.uci:foreach(appname, "shunt_rules", function(s)
+m:foreach("shunt_rules", function(s)
if s.group and s.group ~= "" then
groups[s.group] = true
end
@@ -109,7 +109,7 @@ end
local shunt_group_val = m:get(current_node_id, "shunt_group") or ""
shunt_group_val = shunt_group_val:lower()
local shunt_rules = {}
-m.uci:foreach(appname, "shunt_rules", function(e)
+m:foreach("shunt_rules", function(e)
local group = e.group or ""
group = group:lower()
if group == shunt_group_val then
@@ -132,7 +132,7 @@ table.insert(shunt_rules, {
})
s2 = m:section(Table, shunt_rules, " ")
-s2.config = appname
+s2.config = m.config
s2.sectiontype = "shunt_option_list"
o = s2:option(DummyValue, "remarks", translate("Rule"))
@@ -146,7 +146,7 @@ o.cfgvalue = function(self, section)
end
_node = s2:option(Value, "_node", translate("Node"))
-_node.template = appname .. "/cbi/nodes_listvalue"
+_node.template = m:template_path("/cbi/nodes_listvalue")
_node.group = {"","","",""}
_node:value("", translate("Close (Not use)"))
_node:value("_default", translate("Use default node"))
@@ -178,7 +178,7 @@ end
proxy_tag_node = s2:option(ListValue, "_proxy_tag", string.format('%s',
translate("Set the node to be used as a pre-proxy.") .. "\n" .. translate("Each rule has a separate switch that controls whether this rule uses the pre-proxy or not."),
translate("Preproxy")))
-proxy_tag_node.template = appname .. "/cbi/nodes_listvalue"
+proxy_tag_node.template = m:template_path("/cbi/nodes_listvalue")
proxy_tag_node.group = {""}
proxy_tag_node:value("", translate("Close (Not use)"))
proxy_tag_node.cfgvalue = function(self, section)
@@ -203,10 +203,4 @@ for k1, v1 in pairs(node_list) do
end
end
-local footer = Template(appname .. "/include/shunt_options")
-footer.api = api
-footer.config = m.config
-footer.id = current_node_id
-footer.s_cfgid = s_cfgid or current_node_id
-footer.normal_list = api.jsonc.stringify(node_list.normal_list)
-m:append(footer)
+m:appendTemplate("/include/shunt_options", {id = current_node_id, s_cfgid = s_cfgid or current_node_id, normal_list = api.jsonc.stringify(node_list.normal_list)})
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua
index 1f8ba7af408..20f8ff3fcfa 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua
@@ -1,30 +1,21 @@
api = require "luci.passwall.api"
-appname = "passwall"
-
api.set_default_cbi()
-m = Map(appname, translate("Node Config"))
+m = Map()
m.redirect = api.url("node_list")
-api.set_apply_on_parse(m)
if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(m.redirect)
end
-fs = require "nixio.fs"
-formvalue_key = "cbid." .. appname .. "." .. arg[1] .. "."
-
-local header = Template(appname .. "/node_config/header")
-header.api = api
-header.config = m.config
-header.section = arg[1]
-m:append(header)
+formvalue_key = "cbid." .. m.config .. "." .. arg[1] .. "."
-m:append(Template(appname .. "/cbi/nodes_multivalue_com"))
-m:append(Template(appname .. "/cbi/nodes_listvalue_com"))
+m:appendTemplate("/node_config/header", {section = arg[1]})
+m:appendTemplate("/cbi/nodes_multivalue_com")
+m:appendTemplate("/cbi/nodes_listvalue_com")
groups = {}
-m.uci:foreach(appname, "nodes", function(s)
+m:foreach("nodes", function(s)
if s[".name"] ~= arg[1] then
if s.group and s.group ~= "" then
groups[s.group] = true
@@ -32,13 +23,13 @@ m.uci:foreach(appname, "nodes", function(s)
end
end)
-s = m:section(NamedSection, arg[1], "nodes", "")
+local s = m:section(NamedSection, arg[1], "nodes", translate("Node Config"))
s.addremove = false
s.dynamic = false
o = s:option(DummyValue, "passwall", " ")
o.rawhtml = true
-o.template = "passwall/node_config/link_share_man"
+o.template = m:template_path("/node_config/link_share_man")
o.value = arg[1]
o = s:option(Value, "remarks", translate("Node Remarks"))
@@ -67,10 +58,10 @@ o.write = function(self, section, value)
m:set(section, self.option, value)
end
-local types_dir = "/usr/lib/lua/luci/model/cbi/passwall/client/type/"
+local types_dir = "/usr/lib/lua/luci/model/cbi/" .. api.appname .. "/client/type/"
s.val = {}
-s.val["type"] = m.uci:get(appname, arg[1], "type")
-s.val["protocol"] = m.uci:get(appname, arg[1], "protocol")
+s.val["type"] = m:get(arg[1], "type")
+s.val["protocol"] = m:get(arg[1], "protocol")
if luci.http.formvalue("cbi.submit") == "1" then
local formvalue_type = luci.http.formvalue(formvalue_key .. "type")
@@ -82,33 +73,35 @@ end
o = s:option(ListValue, "type", translate("Type"))
if api.is_finded("ipt2socks") then
- s.fields["type"]:value("Socks", translate("Socks"))
+ local type_name = "Socks"
- if s.val["type"] == "Socks" then
- local function _n(name)
- return "socks_" .. name
- end
- o = s:option(ListValue, _n("del_protocol"), " ") --始终隐藏,用于删除 protocol
- o:depends({ [_n("__hide")] = "1" })
+ s.fields["type"]:value(type_name, "Socks")
+
+ if s.val["type"] == type_name then
+ local s2 = NamedSection(m, arg[1], "server")
+ s2.type_name = type_name
+ s2.option_prefix = "socks_"
+
+ o = s2:option(ListValue, "del_protocol", " ") --始终隐藏,用于删除 protocol
+ o:depends({ __hide = "1" })
o.rewrite_option = "protocol"
- o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
+ o = s2:option(Value, "address", translate("Address (Support Domain Name)"))
- o = s:option(Value, _n("port"), translate("Port"))
+ o = s2:option(Value, "port", translate("Port"))
o.datatype = "port"
- o = s:option(Value, _n("username"), translate("Username"))
+ o = s2:option(Value, "username", translate("Username"))
- o = s:option(Value, _n("password"), translate("Password"))
+ o = s2:option(Value, "password", translate("Password"))
o.password = true
- api.luci_types(arg[1], m, s, "Socks", "socks_")
+ api.luci_types(s, s2)
end
-
end
local type_table = {}
-for filename in fs.dir(types_dir) do
+for filename in api.fs.dir(types_dir) do
table.insert(type_table, filename)
end
table.sort(type_table)
@@ -118,10 +111,6 @@ for index, value in ipairs(type_table) do
setfenv(p_func, getfenv(1))(m, s)
end
-local footer = Template(appname .. "/node_config/footer")
-footer.api = api
-footer.config = m.config
-footer.section = arg[1]
-m:append(footer)
+m:appendTemplate("/node_config/footer", {section = arg[1]})
return api.return_map(m)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
index fdbf0c29bd4..debfae77401 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
@@ -1,16 +1,10 @@
local api = require "luci.passwall.api"
-local appname = "passwall"
-local sys = api.sys
-local datatypes = api.datatypes
-
api.set_default_cbi()
-m = Map(appname)
-api.set_apply_on_parse(m)
+m = Map()
-- [[ Other Settings ]]--
-s = m:section(TypedSection, "global_other")
-s.anonymous = true
+s = m:section(NamedSection, "@global_other[0]", "global_other")
o = s:option(ListValue, "auto_detection_time", translate("Automatic detection delay"))
o:value("0", translate("Close"))
@@ -30,6 +24,26 @@ o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud
o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
o.default = o.keylist[3]
-m:append(Template(appname .. "/node_list/node_list"))
+if true then
+ m:appendTemplate("/node_list/node_list")
+
+ if luci.http.formvalue("cbi.submit") == "1" then
+ local group_order = {}
+ group_order = luci.http.formvaluetable("group.order")
+ if group_order then
+ for k, v in pairs(group_order) do
+ if v and v~= "" then
+ local new_order = {}
+ string.gsub(v, "[^" .. " " .. "]+", function(w)
+ new_order[#new_order + 1] = w
+ end)
+ for idx, name in ipairs(new_order) do
+ m.uci:reorder(m.config, name, idx - 1)
+ end
+ end
+ end
+ end
+ end
+end
return api.return_map(m)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua
index 6080e87fc30..d6827d49ad9 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua
@@ -1,6 +1,6 @@
local api = require "luci.passwall.api"
-local uci = api.uci
-local appname = "passwall"
+api.set_default_cbi()
+
local has_ss_rust = api.is_finded("sslocal")
local has_singbox = api.finded_com("sing-box")
local has_xray = api.finded_com("xray")
@@ -38,23 +38,16 @@ if has_hysteria2 then
table.insert(hysteria2_type, s)
end
-api.set_default_cbi()
-
-m = Map(appname)
-api.set_apply_on_parse(m)
+m = Map()
function m.on_before_save(self)
- if self.no_commit then
- return
- end
- self.uci:foreach(appname, "subscribe_list", function(e)
+ self:foreach("subscribe_list", function(e)
self:del(e[".name"], "md5")
end)
end
-- [[ Subscribe Settings ]]--
-s = m:section(TypedSection, "global_subscribe", "")
-s.anonymous = true
+s = m:section(NamedSection, "@global_subscribe[0]", "global_subscribe")
o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode"))
o:value("0", translate("Close"))
@@ -129,15 +122,13 @@ o.cfgvalue = function(self, section)
translate("Manual subscription All"))
end
-local cfgname = "subscribe_list"
-s = m:section(TypedSection, cfgname, "", "" .. 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.") .. "")
+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.") .. "")
s.addremove = true
s.anonymous = true
s.sortable = true
s.template = "cbi/tblsection"
s.extedit = api.url("node_subscribe_config", "%s")
function s.create(e, t)
- m.no_commit = true
local uid = "sub_" .. api.gen_random_char(5)
TypedSection.create(e, uid)
m:set(uid, "hysteria_up_mbps", "100")
@@ -154,7 +145,7 @@ o.validate = function(self, value, section)
return nil, translate("Remark cannot be empty.")
end
local duplicate = false
- m.uci:foreach(appname, cfgname, function(e)
+ m:foreach(s.sectiontype, function(e)
if e[".name"] ~= section and e["remark"] and e["remark"]:lower() == value:lower() then
duplicate = true
return false
@@ -168,9 +159,9 @@ end
o.write = function(self, section, value)
local old = m:get(section, self.option) or ""
if old ~= value then
- m.uci:foreach(appname, "nodes", function(e)
+ m:foreach("nodes", function(e)
if e["group"] and e["group"]:lower() == old:lower() then
- m.uci:set(appname, e[".name"], "group", value)
+ m:set(e[".name"], "group", value)
end
if e["protocol"] and (e["protocol"] == "_balancing" or e["protocol"] == "_urltest") and e["node_group"] then
local gs = ""
@@ -182,7 +173,7 @@ o.write = function(self, section, value)
end
end
gs = api.trim(gs)
- m.uci:set(appname, e[".name"], "node_group", gs)
+ m:set(e[".name"], "node_group", gs)
end
end)
end
@@ -200,7 +191,7 @@ o.cfgvalue = function(t, n)
end
str = str ~= "" and "
" .. str or ""
local num = 0
- m.uci:foreach(appname, "nodes", function(s)
+ m:foreach("nodes", function(s)
if s["group"] and s["group"]:lower() == remark:lower() then
num = num + 1
end
@@ -229,11 +220,8 @@ o.cfgvalue = function(self, section)
section, translate("Manual subscription"))
end
-local sortable = Template(appname .. "/cbi/sortable")
-sortable.api = api
-sortable.target_cfgname = cfgname
-m:append(sortable)
+m:appendTemplate("/cbi/sortable", {sectiontype = s.sectiontype})
-m:append(Template(appname .. "/node_subscribe/js"))
+m:appendTemplate("/node_subscribe/js")
return api.return_map(m)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua
index d980fc17d3e..a4ce2a195d4 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua
@@ -1,12 +1,8 @@
local api = require "luci.passwall.api"
-local uci = api.uci
-local appname = "passwall"
-
api.set_default_cbi()
-m = Map(appname)
+m = Map()
m.redirect = api.url("node_subscribe")
-api.set_apply_on_parse(m)
if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(m.redirect)
@@ -16,7 +12,7 @@ function m.on_before_save(self)
self:del(arg[1], "md5")
end
-m:append(Template(appname .. "/cbi/nodes_listvalue_com"))
+m:appendTemplate("/cbi/nodes_listvalue_com")
local has_ss_rust = api.is_finded("sslocal")
local has_singbox = api.finded_com("sing-box")
@@ -80,7 +76,7 @@ o.validate = function(self, value, section)
return nil, translate("Remark cannot be empty.")
end
local duplicate = false
- m.uci:foreach(appname, "subscribe_list", function(e)
+ m:foreach("subscribe_list", function(e)
if e[".name"] ~= section and e["remark"] and e["remark"]:lower() == value:lower() then
duplicate = true
return false
@@ -94,9 +90,9 @@ end
o.write = function(self, section, value)
local old = m:get(section, self.option) or ""
if old ~= value then
- m.uci:foreach(appname, "nodes", function(e)
+ m:foreach("nodes", function(e)
if e["group"] and e["group"]:lower() == old:lower() then
- m.uci:set(appname, e[".name"], "group", value)
+ self.map:set(e[".name"], "group", value)
end
if e["protocol"] and (e["protocol"] == "_balancing" or e["protocol"] == "_urltest") and e["node_group"] then
local gs = ""
@@ -108,7 +104,7 @@ o.write = function(self, section, value)
end
end
gs = api.trim(gs)
- m.uci:set(appname, e[".name"], "node_group", gs)
+ self.map:set(e[".name"], "node_group", gs)
end
end)
end
@@ -295,13 +291,13 @@ descrStr = translate(descrStr) .. "
" .. translate("Only support a layer of p
o1 = s:option(ListValue, "preproxy_node", translate("Preproxy Node"))
o1:depends({ ["chain_proxy"] = "1" })
o1.description = descrStr
-o1.template = appname .. "/cbi/nodes_listvalue"
+o1.template = m:template_path("/cbi/nodes_listvalue")
o1.group = {}
o2 = s:option(ListValue, "to_node", translate("Landing Node"))
o2:depends({ ["chain_proxy"] = "2" })
o2.description = descrStr
-o2.template = appname .. "/cbi/nodes_listvalue"
+o2.template = m:template_path("/cbi/nodes_listvalue")
o2.group = {}
o3 = s:option(Value, "outbound_iface", translate("Outbound Interface"))
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua
index 9ef39d150d0..eaf95c72916 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua
@@ -1,5 +1,6 @@
local api = require "luci.passwall.api"
-local appname = "passwall"
+api.set_default_cbi()
+
local fs = api.fs
local has_singbox = api.finded_com("sing-box")
local has_xray = api.finded_com("xray")
@@ -8,15 +9,10 @@ local port_validate = function(self, value, t)
return value:gsub("-", ":")
end
-api.set_default_cbi()
-
-m = Map(appname)
-api.set_apply_on_parse(m)
+m = Map()
-- [[ Delay Settings ]]--
-s = m:section(TypedSection, "global_delay", translate("Delay Settings"))
-s.anonymous = true
-s.addremove = false
+s = m:section(NamedSection, "@global_delay[0]", "global_delay", translate("Delay Settings"))
---- Open and close Daemon
o = s:option(Flag, "start_daemon", translate("Open and close Daemon"))
@@ -61,9 +57,7 @@ for index, value in ipairs({"stop", "start", "restart"}) do
end
-- [[ Forwarding Settings ]]--
-s = m:section(TypedSection, "global_forwarding", translate("Forwarding Settings"))
-s.anonymous = true
-s.addremove = false
+s = m:section(NamedSection, "@global_forwarding[0]", "global_forwarding", translate("Forwarding Settings"))
---- TCP No Redir Ports
o = s:option(Value, "tcp_no_redir_ports", translate("TCP No Redir Ports"))
@@ -168,9 +162,7 @@ o:depends("ipv6_tproxy", true)
o.default = 0
if has_xray then
- s_xray = m:section(TypedSection, "global_xray", "Xray " .. translate("Settings"))
- s_xray.anonymous = true
- s_xray.addremove = false
+ s_xray = m:section(NamedSection, "@global_xray[0]", "global_xray", "Xray " .. translate("Settings"))
o = s_xray:option(Flag, "fragment", translate("Fragment"), translate("TCP fragments, which can deceive the censorship system in some cases, such as bypassing SNI blacklists."))
o.default = 0
@@ -204,7 +196,7 @@ if has_xray then
o.default = 0
o.description = translate("Override the connection destination address with the sniffed domain.
Otherwise use sniffed domain for routing only.
If using shunt nodes, configure the domain shunt rules correctly.")
- local domains_excluded = string.format("/usr/share/%s/rules/domains_excluded", appname)
+ local domains_excluded = string.format("/usr/share/%s/domains_excluded", m.config)
o = s_xray:option(TextValue, "excluded_domains", translate("Excluded Domains"), translate("If the traffic sniffing result is in this list, the destination address will not be overridden."))
o.rows = 15
o.wrap = "off"
@@ -227,11 +219,14 @@ if has_xray then
end
s_xray_noise.remove = function(self, section)
- for k, v in pairs(self.children) do
- v.rmempty = true
- v.validate = nil
+ local o = m:get(section) or {}
+ if o[".type"] == self.sectiontype then
+ for k, v in pairs(self.children) do
+ v.rmempty = true
+ v.validate = nil
+ end
+ TypedSection.remove(self, section)
end
- TypedSection.remove(self, section)
end
o = s_xray_noise:option(Flag, "enabled", translate("Enable"))
@@ -255,9 +250,7 @@ if has_xray then
end
if has_singbox then
- s = m:section(TypedSection, "global_singbox", "Sing-Box " .. translate("Settings"))
- s.anonymous = true
- s.addremove = false
+ s = m:section(NamedSection, "@global_singbox[0]", "global_singbox", "Sing-Box " .. translate("Settings"))
o = s:option(Flag, "record_fragment", "TLS Record " .. translate("Fragment"),
translate("Split handshake data into multiple TLS records for better censorship evasion. Low overhead. Recommended to enable first."))
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua
index adad962d55b..c19b53ab5df 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua
@@ -1,16 +1,12 @@
local api = require "luci.passwall.api"
-local appname = "passwall"
local has_xray = api.finded_com("xray")
local has_singbox = api.finded_com("sing-box")
-
api.set_default_cbi()
-m = Map(appname)
-api.set_apply_on_parse(m)
+m = Map()
-- [[ Rule Settings ]]--
-s = m:section(TypedSection, "global_rules", translate("Rule status"))
-s.anonymous = true
+s = m:section(NamedSection, "@global_rules[0]", "global_rules", translate("Rule status"))
--[[
o = s:option(Flag, "adblock", translate("Enable adblock"))
@@ -143,10 +139,27 @@ for _, f in ipairs(flags) do
o.rmempty = false
end
-s:append(Template(appname .. "/rule/rule_version"))
+s:appendTemplate("/rule/rule_version")
if has_xray or has_singbox then
- m:append(Template(appname .. "/rule/shunt_rule_list"))
+ m:appendTemplate("/rule/shunt_rule_list")
+
+ if luci.http.formvalue("cbi.submit") == "1" then
+ local group_order = luci.http.formvaluetable("group.order")
+ if group_order then
+ for k, v in pairs(group_order) do
+ if v and v~= "" then
+ local new_order = {}
+ string.gsub(v, "[^" .. " " .. "]+", function(w)
+ new_order[#new_order + 1] = w
+ end)
+ for idx, name in ipairs(new_order) do
+ m.uci:reorder(m.config, name, idx - 1)
+ end
+ end
+ end
+ end
+ end
end
return api.return_map(m)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/rule_list.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/rule_list.lua
index 901a95460ca..2c08a24c489 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/rule_list.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/rule_list.lua
@@ -1,18 +1,16 @@
local api = require "luci.passwall.api"
-local appname = api.appname
local fs = api.fs
local sys = api.sys
local uci = api.uci
local datatypes = api.datatypes
-local path = string.format("/usr/share/%s/rules/", appname)
+local path = string.format("/usr/share/%s/rules/", api.c_config)
local gfwlist_path = "/usr/share/passwall/rules/gfwlist"
local chnlist_path = "/usr/share/passwall/rules/chnlist"
local chnroute_path = "/usr/share/passwall/rules/chnroute"
api.set_default_cbi()
-m = Map(appname)
-api.set_apply_on_parse(m)
+m = Map()
function clean_text(text)
local nbsp = string.char(0xC2, 0xA0) -- 不间断空格(U+00A0)
@@ -319,9 +317,9 @@ if fs.access(chnroute_path) then
]], translate("Read List"))
end
-m:append(Template(appname .. "/rule_list/js"))
+m:appendTemplate("/rule_list/js")
-local geo_dir = (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/")
+local geo_dir = (api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/")
local geosite_path = geo_dir .. "/geosite.dat"
local geoip_path = geo_dir .. "/geoip.dat"
if api.finded_com("geoview") and fs.access(geosite_path) and fs.access(geoip_path) then
@@ -329,7 +327,7 @@ if api.finded_com("geoview") and fs.access(geosite_path) and fs.access(geoip_pat
s:tab("geoview", translate("Geo View"))
o = s:taboption("geoview", DummyValue, "_geoview_fieldset")
o.rawhtml = true
- o.template = appname .. "/rule_list/geoview"
+ o.template = m:template_path("/rule_list/geoview")
end
end
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua
index 6395451c6bc..b650fc862d2 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua
@@ -1,19 +1,17 @@
local api = require "luci.passwall.api"
-local appname = "passwall"
-local datatypes = api.datatypes
-
api.set_default_cbi()
-m = Map(appname, "Sing-Box/Xray " .. translate("Shunt Rule"))
+local datatypes = api.datatypes
+
+m = Map()
m.redirect = api.url("rule")
-api.set_apply_on_parse(m)
if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(m.redirect)
end
-m.on_before_save = function(self)
- m:set("@global[0]", "flush_set", "1")
+function m.on_before_save(self)
+ self:set("@global[0]", "flush_set", "1")
end
-- Add inline CSS to map description
@@ -46,7 +44,7 @@ end
local remarks_lookup = {}
local groups = {}
-m.uci:foreach(appname, "shunt_rules", function(s)
+m:foreach("shunt_rules", function(s)
if s[".name"] ~= arg[1] then
if s.remarks then
remarks_lookup[s.remarks] = s[".name"]
@@ -57,7 +55,7 @@ m.uci:foreach(appname, "shunt_rules", function(s)
end
end)
-s = m:section(NamedSection, arg[1], "shunt_rules", "")
+s = m:section(NamedSection, arg[1], "shunt_rules", "Sing-Box/Xray " .. translate("Shunt Rule"))
s.addremove = false
s.dynamic = false
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/socks_config.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/socks_config.lua
index 02b97a31848..c8786efbe4f 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/socks_config.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/socks_config.lua
@@ -1,18 +1,15 @@
local api = require "luci.passwall.api"
-local appname = "passwall"
-
api.set_default_cbi()
-m = Map(appname)
+m = Map()
m.redirect = api.url()
-api.set_apply_on_parse(m)
if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(m.redirect)
end
-m:append(Template(appname .. "/cbi/nodes_dynamiclist_com"))
-m:append(Template(appname .. "/cbi/nodes_listvalue_com"))
+m:appendTemplate("/cbi/nodes_dynamiclist_com")
+m:appendTemplate("/cbi/nodes_listvalue_com")
local has_singbox = api.finded_com("sing-box")
local has_xray = api.finded_com("xray")
@@ -50,14 +47,14 @@ socks_node = s:option(ListValue, "node", translate("Node"))
if auto_switch_tip then
socks_node.description = auto_switch_tip
end
-socks_node.template = appname .. "/cbi/nodes_listvalue"
+socks_node.template = m:template_path("/cbi/nodes_listvalue")
socks_node.group = {}
o = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
o.default = "0"
local n = 1
-m.uci:foreach(appname, "socks", function(s)
+m:foreach("socks", function(s)
if s[".name"] == section then
return false
end
@@ -79,6 +76,20 @@ o = s:option(Flag, "log", translate("Enable") .. " " .. translate("Log"))
o.default = 1
o.rmempty = false
+o = s:option(DummyValue, "_log", translate("Log File"))
+o.rawhtml = true
+o.cfgvalue = function(t, n)
+ local log_path = api.TMP_PATH .. "/" .. arg[1] .. ".log"
+ local log_url = api.url("get_socks_log") .. "?name=" .. arg[1]
+ return string.format(
+ '%s ',
+ log_path,
+ translate("View Log"),
+ log_url
+ )
+end
+o:depends("log", true)
+
o = s:option(Flag, "enable_autoswitch", translate("Auto Switch"))
o.default = 0
o.rmempty = false
@@ -91,7 +102,7 @@ o:value("batch", translate("Batch"))
o = s:option(DynamicList, "autoswitch_backup_node", translate("List of backup nodes"))
o:depends("backup_node_add_mode", "manual")
-o.template = appname .. "/cbi/nodes_dynamiclist"
+o.template = m:template_path("/cbi/nodes_dynamiclist")
o.group = {}
o.write = function(self, section, value)
local n = s.fields["node"]:formvalue(section)
@@ -117,7 +128,7 @@ o:depends("backup_node_add_mode", "batch")
o.widget = "checkbox"
o:value("default", translate("default"))
local groups = {}
-m.uci:foreach(appname, "nodes", function(s)
+m:foreach("nodes", function(s)
if s.group and s.group ~= "" then
groups[s.group] = true
end
@@ -165,7 +176,7 @@ o.default = o.keylist[3]
o:depends("enable_autoswitch", true)
o = s:option(DummyValue, "btn")
-o.template = appname .. "/socks_auto_switch/btn"
+o.template = m:template_path("/socks_auto_switch/btn")
o:depends("backup_node_add_mode", "manual")
return api.return_map(m)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/1_sing-box.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/1_sing-box.lua
new file mode 100644
index 00000000000..0c144fdfb51
--- /dev/null
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/1_sing-box.lua
@@ -0,0 +1,917 @@
+local singbox_bin = api.finded_com("sing-box")
+
+if not singbox_bin then
+ return
+end
+
+-- [[ sing-box ]]
+local m, s1 = ...
+local type_name = "sing-box"
+
+s1.fields["type"]:value(type_name, "Sing-Box")
+if not s1.fields["type"].default then
+ s1.fields["type"].default = type_name
+end
+
+if s1.val["type"] ~= type_name then
+ return
+end
+
+local s = NamedSection(m, arg[1], "server")
+s.type_name = type_name
+s.option_prefix = "singbox_"
+
+local formvalue_proto = luci.http.formvalue(formvalue_key .. "protocol")
+
+if formvalue_proto then s1.val["protocol"] = formvalue_proto end
+
+local arg_select_proto = luci.http.formvalue("select_proto") or ""
+
+local ss_method_new_list = {
+ "none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
+}
+
+local ss_method_old_list = {
+ "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "rc4-md5", "chacha20-ietf", "xchacha20",
+}
+
+local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" }
+
+local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'")
+
+local singbox_version = api.get_app_version("sing-box"):match("[^v]+")
+
+o = s:option(ListValue, "protocol", translate("Protocol"))
+o:value("socks", "Socks")
+o:value("http", "HTTP")
+o:value("shadowsocks", "Shadowsocks")
+o:value("vmess", "Vmess")
+o:value("trojan", "Trojan")
+if singbox_tags:find("with_wireguard") then
+ o:value("wireguard", "WireGuard")
+end
+if singbox_tags:find("with_quic") then
+ o:value("hysteria", "Hysteria")
+end
+o:value("vless", "VLESS")
+if singbox_tags:find("with_quic") then
+ o:value("tuic", "TUIC")
+end
+if singbox_tags:find("with_quic") then
+ o:value("hysteria2", "Hysteria2")
+end
+o:value("anytls", "AnyTLS")
+o:value("ssh", "SSH")
+if singbox_tags:find("with_naive_outbound") then
+ o:value("naive", "NaïveProxy")
+end
+o:value("_urltest", translate("URLTest"))
+o:value("_shunt", translate("Shunt"))
+o:value("_iface", translate("Custom Interface"))
+function o.custom_cfgvalue(self, section)
+ if arg_select_proto ~= "" then
+ return arg_select_proto
+ else
+ return m:get(section, self.config_option)
+ end
+end
+
+local load_urltest_options = s1.val["protocol"] == "_urltest" or arg_select_proto == "_urltest"
+local load_shunt_options = s1.val["protocol"] == "_shunt" or arg_select_proto == "_shunt"
+local load_iface_options = s1.val["protocol"] == "_iface" or arg_select_proto == "_iface"
+local load_normal_options = true
+if load_urltest_options or load_shunt_options or load_iface_options then
+ load_normal_options = nil
+end
+if not arg_select_proto:find("_") then
+ load_normal_options = true
+end
+
+local netdev_list = api.get_network_devices()
+local node_list = api.get_node_list()
+
+if load_urltest_options then -- [[ URLTest Start ]]
+ o = s:option(ListValue, "node_add_mode", translate("Node Addition Method"))
+ o:depends({ protocol = "_urltest" })
+ o.default = "manual"
+ o:value("manual", translate("Manual"))
+ o:value("batch", translate("Batch"))
+
+ o = s:option(MultiValue, "urltest_node", translate("URLTest node list"), translate("List of nodes to test, document"))
+ o:depends({ node_add_mode = "manual" })
+ o.widget = "checkbox"
+ o.template = m:template_path("/cbi/nodes_multivalue")
+ o.group = {}
+ for k1, v1 in pairs(node_list) do
+ if k1 == "socks_list" or k1 == "normal_list" then
+ for i, v in ipairs(v1) do
+ o:value(v.id, v.remark)
+ o.group[#o.group+1] = v.group or ""
+ end
+ end
+ end
+ -- 读取旧 DynamicList
+ function o.custom_cfgvalue(self, section)
+ return table.concat(m:get(section, "urltest_node") or {}, " ")
+ end
+ -- 写入保持 DynamicList
+ function o.custom_write(self, section, value)
+ local old = m:get(section, "urltest_node") or {}
+ local new, set = {}, {}
+ for v in value:gmatch("%S+") do
+ new[#new + 1] = v
+ set[v] = 1
+ end
+ for _, v in ipairs(old) do
+ if not set[v] then
+ m:set(section, "urltest_node", new)
+ return
+ end
+ set[v] = nil
+ end
+ for _ in pairs(set) do
+ m:set(section, "urltest_node", new)
+ return
+ end
+ end
+
+ o = s:option(MultiValue, "node_group", translate("Select Group"))
+ o:depends({ node_add_mode = "batch" })
+ o.widget = "checkbox"
+ o:value("default", translate("default"))
+ for k, v in pairs(groups) do
+ o:value(api.UrlEncode(k), k)
+ end
+
+ o = s:option(Value, "node_match_rule", translate("Node Matching Rules"))
+ o:depends({ node_add_mode = "batch" })
+ local descrStr = "Example: ^A && B && !C && D$
"
+ descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).
"
+ descrStr = descrStr .. "Conditions are joined by && (AND), and their order does not affect the result.
"
+ descrStr = descrStr .. "Multiple groups can be separated by || (OR), matching succeeds if any group matches.
"
+ descrStr = descrStr .. "Example: A && B || C && D means (A AND B) OR (C AND D)."
+ o.description = translate(descrStr)
+
+ o = s:option(Value, "urltest_url", translate("Probe URL"))
+ o:depends({ protocol = "_urltest" })
+ o:value("https://cp.cloudflare.com/", "Cloudflare")
+ o:value("https://www.gstatic.com/generate_204", "Gstatic")
+ o:value("https://www.google.com/generate_204", "Google")
+ o:value("https://www.youtube.com/generate_204", "YouTube")
+ o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)")
+ o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)")
+ o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
+ o.default = o.keylist[3]
+ o.description = translate("The URL used to detect the connection status.")
+
+ o = s:option(Value, "urltest_interval", translate("Test interval"))
+ o:depends({ protocol = "_urltest" })
+ o.default = "3m"
+ o.placeholder = "3m"
+ o.description = translate("The interval between initiating probes.") .. "
" ..
+ translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" ..
+ translate("When the unit is not filled in, it defaults to seconds.") .. "
" ..
+ translate("Test interval must be less or equal than idle timeout.")
+
+ o = s:option(Value, "urltest_tolerance", translate("Test tolerance"), translate("The test tolerance in milliseconds."))
+ o:depends({ protocol = "_urltest" })
+ o.datatype = "uinteger"
+ o.placeholder = "50"
+ o.default = "50"
+
+ o = s:option(Value, "urltest_idle_timeout", translate("Idle timeout"))
+ o:depends({ protocol = "_urltest" })
+ o.placeholder = "30m"
+ o.default = "30m"
+ o.description = translate("The idle timeout.") .. "
" ..
+ translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" ..
+ translate("When the unit is not filled in, it defaults to seconds.")
+
+ o = s:option(Flag, "urltest_interrupt_exist_connections", translate("Interrupt existing connections"))
+ o:depends({ protocol = "_urltest" })
+ o.default = "0"
+ o.description = translate("Interrupt existing connections when the selected outbound has changed.")
+end -- [[ URLTest End ]]
+
+if load_iface_options then -- [[ 自定义接口 Start ]]
+ o = s:option(Value, "iface", translate("Interface"))
+ o:depends({ protocol = "_iface" })
+ for _, d in ipairs(netdev_list) do
+ o:value(d.name, d.label)
+ end
+end
+
+
+if load_normal_options then
+
+o = s:option(Value, "address", translate("Address (Support Domain Name)"))
+
+o = s:option(Value, "port", translate("Port"))
+o.datatype = "port"
+
+o = s:option(Value, "uuid", translate("ID"))
+o.password = true
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless" })
+o:depends({ protocol = "tuic" })
+
+o = s:option(Value, "username", translate("Username"))
+o:depends({ protocol = "http" })
+o:depends({ protocol = "socks" })
+o:depends({ protocol = "ssh" })
+o:depends({ protocol = "naive" })
+
+o = s:option(Value, "password", translate("Password"))
+o.password = true
+o:depends({ protocol = "http" })
+o:depends({ protocol = "socks" })
+o:depends({ protocol = "shadowsocks" })
+o:depends({ protocol = "trojan" })
+o:depends({ protocol = "tuic" })
+o:depends({ protocol = "anytls" })
+o:depends({ protocol = "ssh" })
+o:depends({ protocol = "naive" })
+
+o = s:option(ListValue, "security", translate("Encrypt Method"))
+for a, t in ipairs(security_list) do o:value(t) end
+o:depends({ protocol = "vmess" })
+
+o = s:option(ListValue, "ss_method", translate("Encrypt Method"))
+o.rewrite_option = "method"
+for a, t in ipairs(ss_method_new_list) do o:value(t) end
+for a, t in ipairs(ss_method_old_list) do o:value(t) end
+o:depends({ protocol = "shadowsocks" })
+
+o = s:option(Flag, "uot", translate("UDP over TCP"))
+o:depends({ protocol = "socks" })
+o:depends({ protocol = "shadowsocks" })
+o:depends({ protocol = "naive" })
+
+o = s:option(Value, "alter_id", "Alter ID")
+o.datatype = "uinteger"
+o.default = "0"
+o:depends({ protocol = "vmess" })
+
+o = s:option(Flag, "global_padding", "global_padding", translate("Protocol parameter. Will waste traffic randomly if enabled."))
+o.default = "0"
+o:depends({ protocol = "vmess" })
+
+o = s:option(Flag, "authenticated_length", "authenticated_length", translate("Protocol parameter. Enable length block encryption."))
+o.default = "0"
+o:depends({ protocol = "vmess" })
+
+o = s:option(ListValue, "flow", translate("flow"))
+o.default = ""
+o:value("", translate("Disable"))
+o:value("xtls-rprx-vision")
+o:depends({ protocol = "vless", tls = true })
+
+if singbox_tags:find("with_quic") then
+ o = s:option(Value, "hysteria_hop", translate("Port hopping range"))
+ o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
+ o:depends({ protocol = "hysteria" })
+
+ o = s:option(Value, "hysteria_hop_interval", translate("Hop Interval(second)"), translate("Example:") .. "30 (≥5)")
+ o.datatype = "uinteger"
+ o.placeholder = "30"
+ o.default = "30"
+ o:depends({ protocol = "hysteria" })
+
+ o = s:option(Value, "hysteria_obfs", translate("Obfs Password"))
+ o:depends({ protocol = "hysteria" })
+
+ o = s:option(ListValue, "hysteria_auth_type", translate("Auth Type"))
+ o:value("disable", translate("Disable"))
+ o:value("string", translate("STRING"))
+ o:value("base64", translate("BASE64"))
+ o:depends({ protocol = "hysteria" })
+
+ o = s:option(Value, "hysteria_auth_password", translate("Auth Password"))
+ o.password = true
+ o:depends({ protocol = "hysteria", hysteria_auth_type = "string"})
+ o:depends({ protocol = "hysteria", hysteria_auth_type = "base64"})
+
+ o = s:option(Value, "hysteria_up_mbps", translate("Max upload Mbps"))
+ o.default = "10"
+ o:depends({ protocol = "hysteria" })
+
+ o = s:option(Value, "hysteria_down_mbps", translate("Max download Mbps"))
+ o.default = "50"
+ o:depends({ protocol = "hysteria" })
+
+ o = s:option(Value, "hysteria_recv_window_conn", translate("QUIC stream receive window"))
+ o:depends({ protocol = "hysteria" })
+
+ o = s:option(Value, "hysteria_recv_window", translate("QUIC connection receive window"))
+ o:depends({ protocol = "hysteria" })
+
+ o = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection"))
+ o:depends({ protocol = "hysteria" })
+end
+
+if singbox_tags:find("with_quic") then
+ o = s:option(ListValue, "tuic_congestion_control", translate("Congestion control algorithm"))
+ o.default = "cubic"
+ o:value("bbr", translate("BBR"))
+ o:value("cubic", translate("CUBIC"))
+ o:value("new_reno", translate("New Reno"))
+ o:depends({ protocol = "tuic" })
+
+ o = s:option(ListValue, "tuic_udp_relay_mode", translate("UDP relay mode"))
+ o.default = "native"
+ o:value("native", translate("native"))
+ o:value("quic", translate("QUIC"))
+ o:depends({ protocol = "tuic" })
+
+ --[[
+ o = s:option(Flag, "tuic_udp_over_stream", translate("UDP over stream"))
+ o:depends({ protocol = "tuic" })
+ ]]--
+
+ o = s:option(Flag, "tuic_zero_rtt_handshake", translate("Enable 0-RTT QUIC handshake"))
+ o.default = 0
+ o:depends({ protocol = "tuic" })
+
+ o = s:option(Value, "tuic_heartbeat", translate("Heartbeat interval(second)"))
+ o.datatype = "uinteger"
+ o.default = "3"
+ o:depends({ protocol = "tuic" })
+
+ o = s:option(ListValue, "tuic_alpn", translate("QUIC TLS ALPN"))
+ o.default = "default"
+ o:value("default", translate("Default"))
+ o:value("h3")
+ o:value("h2")
+ o:value("h3,h2")
+ o:value("http/1.1")
+ o:value("h2,http/1.1")
+ o:value("h3,h2,http/1.1")
+ o:value("spdy/3.1")
+ o:value("h3,spdy/3.1")
+ o:depends({ protocol = "tuic" })
+end
+
+if singbox_tags:find("with_quic") then
+ o = s:option(Value, "hysteria2_hop", translate("Port hopping range"))
+ o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
+ o:depends({ protocol = "hysteria2", hysteria2_realms = false })
+
+ o = s:option(Value, "hysteria2_hop_interval", translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5."))
+ o.datatype = "or(uinteger,portrange)"
+ o.placeholder = "30"
+ o.default = "30"
+ o:depends({ protocol = "hysteria2", hysteria2_realms = false })
+
+ o = s:option(Flag, "hysteria2_realms", translate("Realms"))
+ o.default = "0"
+ if api.compare_versions(singbox_version, ">=", "1.14.0") then
+ o:depends({ protocol = "hysteria2"})
+ else
+ o:depends({ protocol = "__hide"})
+ end
+
+ o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
+ o:depends({ hysteria2_realms = "1" })
+ o.validate = function(self, value)
+ value = api.trim(value)
+ local realm = api.parse_realm_uri(value)
+ if realm then return value end
+ return nil, translate("Invalid Realm URL.")
+ end
+
+ o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
+ o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
+ o:depends({ hysteria2_realms = "1" })
+
+ o = s:option(Value, "hysteria2_auth_password", translate("Auth Password"))
+ o.password = true
+ o:depends({ protocol = "hysteria2"})
+
+ o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type"))
+ o:value("", translate("Disable"))
+ o:value("salamander")
+ o:value("gecko")
+ o:depends({ protocol = "hysteria2" })
+
+ o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password"))
+ o:depends({ hysteria2_obfs_type = "salamander" })
+ o:depends({ hysteria2_obfs_type = "gecko" })
+
+ o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
+ o.datatype = "uinteger"
+ o.placeholder = "512"
+ o.default = "512"
+ o:depends({ hysteria2_obfs_type = "gecko" })
+
+ o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
+ o.datatype = "uinteger"
+ o.placeholder = "1200"
+ o.default = "1200"
+ o:depends({ hysteria2_obfs_type = "gecko" })
+
+ o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps"))
+ o:depends({ protocol = "hysteria2" })
+
+ o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps"))
+ o:depends({ protocol = "hysteria2" })
+
+ o = s:option(Value, "hysteria2_idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
+ o.datatype = "range(4,120)"
+ o:depends({ protocol = "hysteria2"})
+
+ o = s:option(Value, "hysteria2_keep_alive_period", translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
+ o.datatype = "range(2,60)"
+ o:depends({ protocol = "hysteria2"})
+
+ o = s:option(Flag, "hysteria2_disable_mtu_discovery", translate("Disable MTU detection"))
+ o.default = "0"
+ o:depends({ protocol = "hysteria2"})
+end
+
+-- [[ SSH config start ]] --
+o = s:option(TextValue, "ssh_priv_key", translate("Private Key"))
+o.rows = 5
+o.wrap = "off"
+o:depends({ protocol = "ssh" })
+o.validate = function(self, value)
+ value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
+ return value
+end
+
+o = s:option(Value, "ssh_priv_key_pp", translate("Private Key Passphrase"))
+o.password = true
+o:depends({ protocol = "ssh" })
+
+o = s:option(DynamicList, "ssh_host_key", translate("Host Key"), translate("Accept any if empty."))
+o:depends({ protocol = "ssh" })
+
+o = s:option(DynamicList, "ssh_host_key_algo", translate("Host Key Algorithms"))
+o:depends({ protocol = "ssh" })
+
+o = s:option(Value, "ssh_client_version", translate("Client Version"), translate("Random version will be used if empty."))
+o:depends({ protocol = "ssh" })
+-- [[ SSH config end ]] --
+
+-- [[ naive start ]] --
+o = s:option(Value, "naive_insecure_concurrency", translate("Concurrent Tunnels"))
+o.datatype = "uinteger"
+o.placeholder = "0"
+o.default = "0"
+o:depends({ protocol = "naive" })
+
+o = s:option(Flag, "naive_quic", translate("QUIC"))
+o.default = 0
+o:depends({ protocol = "naive" })
+
+o = s:option(ListValue, "naive_congestion_control", translate("Congestion control algorithm"))
+o.default = "bbr"
+o:value("bbr", translate("BBR"))
+o:value("bbr2", translate("BBRv2"))
+o:value("cubic", translate("CUBIC"))
+o:value("reno", translate("New Reno"))
+o:depends({ naive_quic = "1" })
+-- [[ naive end ]] --
+
+o = s:option(Flag, "tls", translate("TLS"))
+o.default = 0
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless" })
+o:depends({ protocol = "http" })
+o:depends({ protocol = "trojan" })
+o:depends({ protocol = "shadowsocks" })
+o:depends({ protocol = "anytls" })
+
+o = s:option(ListValue, "alpn", translate("ALPN"))
+o.default = "default"
+o:value("default", translate("Default"))
+o:value("h3")
+o:value("h2")
+o:value("h3,h2")
+o:value("http/1.1")
+o:value("h2,http/1.1")
+o:value("h3,h2,http/1.1")
+o:depends({ tls = true })
+o:depends({ protocol = "hysteria" })
+
+o = s:option(Flag, "tls_disable_sni", translate("Disable SNI"), translate("Do not send server name in ClientHello."))
+o.default = "0"
+o:depends({ tls = true })
+o:depends({ protocol = "hysteria"})
+o:depends({ protocol = "tuic" })
+o:depends({ protocol = "hysteria2" })
+
+o = s:option(Value, "tls_serverName", "SNI " .. translate("Domain"))
+o:depends({ tls = true })
+o:depends({ protocol = "hysteria"})
+o:depends({ protocol = "tuic" })
+o:depends({ protocol = "hysteria2" })
+o:depends({ protocol = "naive" })
+
+o = s:option(Flag, "tls_allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
+o.default = "0"
+o:depends({ tls = true })
+o:depends({ protocol = "hysteria"})
+o:depends({ protocol = "tuic" })
+o:depends({ protocol = "hysteria2" })
+
+o = s:option(Flag, "tls_certificate", translate("TLS Certificate (PEM)"))
+o.default = "0"
+o:depends({ tls = true, reality = false })
+o:depends({ protocol = "hysteria"})
+o:depends({ protocol = "tuic" })
+o:depends({ protocol = "hysteria2" })
+o:depends({ protocol = "naive" })
+
+o = s:option(TextValue, "tls_certificate_pem", " ", translate("Full certificate (chain), PEM format."))
+o.default = ""
+o.rows = 5
+o.wrap = "off"
+o:depends({ tls_certificate = true })
+o.validate = function(self, value)
+ value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
+ return value
+end
+
+o = s:option(Value, "cipherSuites", translate("Cipher Suites"), '***' .. " " .. translate("Configures the list of supported cipher suites, separated by :"))
+o:depends({ tls = true })
+
+o = s:option(Flag, "ech", translate("ECH"))
+o.default = "0"
+o:depends({ tls = true, flow = "", reality = false })
+o:depends({ protocol = "tuic" })
+o:depends({ protocol = "hysteria" })
+o:depends({ protocol = "hysteria2", hysteria2_realms = false })
+o:depends({ protocol = "naive" })
+
+o = s:option(TextValue, "ech_config", translate("ECH Config"))
+o.default = ""
+o.rows = 5
+o.wrap = "off"
+o:depends({ ech = true })
+o.validate = function(self, value)
+ value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
+ return value
+end
+
+o = s:option(Value, "ech_query_server_name", translate("ECH Query Domain"), translate("Overrides the domain name used for ECH HTTPS record queries."))
+o:depends({ ech = true })
+
+if singbox_tags:find("with_utls") then
+ o = s:option(Flag, "utls", translate("uTLS"))
+ o.default = "0"
+ o:depends({ tls = true })
+
+ o = s:option(ListValue, "fingerprint", translate("Finger Print"))
+ o:value("chrome")
+ o:value("firefox")
+ o:value("edge")
+ o:value("safari")
+ o:value("360")
+ o:value("qq")
+ o:value("ios")
+ o:value("android")
+ o:value("random")
+ o:value("randomized")
+ o.default = "chrome"
+ o:depends({ utls = true })
+
+ -- [[ REALITY部分 ]] --
+ o = s:option(Flag, "reality", translate("REALITY"))
+ o.default = 0
+ o:depends({ protocol = "vless", tls = true })
+ o:depends({ protocol = "vmess", tls = true })
+ o:depends({ protocol = "shadowsocks", tls = true })
+ o:depends({ protocol = "trojan", tls = true })
+ o:depends({ protocol = "anytls", tls = true })
+
+ o = s:option(Value, "reality_publicKey", translate("Public Key"))
+ o:depends({ reality = true })
+
+ o = s:option(Value, "reality_shortId", translate("Short Id"))
+ o:depends({ reality = true })
+end
+
+o = s:option(ListValue, "transport", translate("Transport"))
+o:value("tcp", "TCP")
+o:value("http", "HTTP")
+o:value("ws", "WebSocket")
+o:value("httpupgrade", "HTTPUpgrade")
+if singbox_tags:find("with_quic") then
+ o:value("quic", "QUIC")
+end
+if singbox_tags:find("with_grpc") then
+ o:value("grpc", "gRPC")
+else o:value("grpc", "gRPC-lite")
+end
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless" })
+o:depends({ protocol = "shadowsocks" })
+o:depends({ protocol = "trojan" })
+
+if singbox_tags:find("with_wireguard") then
+ o = s:option(Value, "wireguard_public_key", translate("Public Key"))
+ o:depends({ protocol = "wireguard" })
+
+ o = s:option(Value, "wireguard_secret_key", translate("Private Key"))
+ o:depends({ protocol = "wireguard" })
+
+ o = s:option(Value, "wireguard_preSharedKey", translate("Pre shared key"))
+ o:depends({ protocol = "wireguard" })
+
+ o = s:option(DynamicList, "wireguard_local_address", translate("Local Address"))
+ o:depends({ protocol = "wireguard" })
+
+ o = s:option(Value, "wireguard_mtu", translate("MTU"))
+ o.default = "1420"
+ o:depends({ protocol = "wireguard" })
+
+ o = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
+ o:depends({ protocol = "wireguard" })
+end
+
+-- [[ TCP部分(模拟) ]]--
+o = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
+o:value("none", "none")
+o:value("http", "http")
+o:depends({ transport = "tcp" })
+
+o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host"))
+o:depends({ tcp_guise = "http" })
+
+o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
+o.placeholder = "/"
+o:depends({ tcp_guise = "http" })
+
+-- [[ HTTP部分 ]]--
+o = s:option(DynamicList, "http_host", translate("HTTP Host"))
+o:depends({ transport = "http" })
+
+o = s:option(Value, "http_path", translate("HTTP Path"))
+o.placeholder = "/"
+o:depends({ transport = "http" })
+
+o = s:option(Flag, "http_h2_health_check", translate("Health check"))
+o:depends({ tls = true, transport = "http" })
+
+o = s:option(Value, "http_h2_read_idle_timeout", translate("Idle timeout"))
+o.default = "15"
+o:depends({ http_h2_health_check = true })
+
+o = s:option(Value, "http_h2_health_check_timeout", translate("Health check timeout"))
+o.default = "15"
+o:depends({ http_h2_health_check = true })
+
+-- [[ WebSocket部分 ]]--
+o = s:option(Value, "ws_host", translate("WebSocket Host"))
+o:depends({ transport = "ws" })
+
+o = s:option(Value, "ws_path", translate("WebSocket Path"))
+o.placeholder = "/"
+o:depends({ transport = "ws" })
+
+o = s:option(Flag, "ws_enableEarlyData", translate("Enable early data"))
+o:depends({ transport = "ws" })
+
+o = s:option(Value, "ws_maxEarlyData", translate("Early data length"))
+o.default = "1024"
+o:depends({ ws_enableEarlyData = true })
+
+o = s:option(Value, "ws_earlyDataHeaderName", translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol"))
+o:depends({ ws_enableEarlyData = true })
+
+-- [[ HTTPUpgrade部分 ]]--
+o = s:option(Value, "httpupgrade_host", translate("HTTPUpgrade Host"))
+o:depends({ transport = "httpupgrade" })
+
+o = s:option(Value, "httpupgrade_path", translate("HTTPUpgrade Path"))
+o.placeholder = "/"
+o:depends({ transport = "httpupgrade" })
+
+-- [[ gRPC部分 ]]--
+o = s:option(Value, "grpc_serviceName", "ServiceName")
+o:depends({ transport = "grpc" })
+
+o = s:option(Flag, "grpc_health_check", translate("Health check"))
+o:depends({ transport = "grpc" })
+
+o = s:option(Value, "grpc_idle_timeout", translate("Idle timeout"))
+o.default = "15"
+o:depends({ grpc_health_check = true })
+
+o = s:option(Value, "grpc_health_check_timeout", translate("Health check timeout"))
+o.default = "15"
+o:depends({ grpc_health_check = true })
+
+o = s:option(Flag, "grpc_permit_without_stream", translate("Permit without stream"))
+o.default = "0"
+o:depends({ grpc_health_check = true })
+
+-- [[ User-Agent ]]--
+o = s:option(Value, "user_agent", translate("User-Agent"))
+o.default = ""
+o:value("", translate("default"))
+o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36", "chrome")
+o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0", "firefox")
+o:value("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15", "safari")
+o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70", "edge")
+o:value("Go-http-client/1.1", "golang")
+o:value("curl/7.68.0", "curl")
+o:depends({ tcp_guise = "http" })
+o:depends({ transport = "http" })
+o:depends({ transport = "ws" })
+o:depends({ transport = "httpupgrade" })
+o:depends({ protocol = "naive" })
+
+-- [[ Mux ]]--
+o = s:option(Flag, "mux", translate("Mux"))
+o.rmempty = false
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless", flow = "" })
+o:depends({ protocol = "shadowsocks", uot = "" })
+o:depends({ protocol = "trojan" })
+
+o = s:option(ListValue, "mux_type", translate("Mux"))
+o:value("smux")
+o:value("yamux")
+o:value("h2mux")
+o:depends({ mux = true })
+
+o = s:option(Value, "mux_concurrency", translate("Mux concurrency"))
+o.default = 4
+o:depends({ mux = true, tcpbrutal = false })
+
+o = s:option(Flag, "mux_padding", translate("Padding"))
+o.default = 0
+o:depends({ mux = true })
+
+-- [[ TCP Brutal ]]--
+o = s:option(Flag, "tcpbrutal", translate("TCP Brutal"))
+o.default = 0
+o:depends({ mux = true })
+
+o = s:option(Value, "tcpbrutal_up_mbps", translate("Max upload Mbps"))
+o.default = "10"
+o:depends({ tcpbrutal = true })
+
+o = s:option(Value, "tcpbrutal_down_mbps", translate("Max download Mbps"))
+o.default = "50"
+o:depends({ tcpbrutal = true })
+
+o = s:option(Flag, "shadowtls", "ShadowTLS")
+o.default = 0
+o:depends({ protocol = "vmess", tls = false })
+o:depends({ protocol = "shadowsocks", tls = false })
+
+o = s:option(ListValue, "shadowtls_version", "ShadowTLS " .. translate("Version"))
+o.default = "1"
+o:value("1", "ShadowTLS v1")
+o:value("2", "ShadowTLS v2")
+o:value("3", "ShadowTLS v3")
+o:depends({ shadowtls = true })
+
+o = s:option(Value, "shadowtls_password", "ShadowTLS " .. translate("Password"))
+o.password = true
+o:depends({ shadowtls = true, shadowtls_version = "2" })
+o:depends({ shadowtls = true, shadowtls_version = "3" })
+
+o = s:option(Value, "shadowtls_serverName", "ShadowTLS " .. translate("Domain"))
+o:depends({ shadowtls = true })
+
+if singbox_tags:find("with_utls") then
+ o = s:option(Flag, "shadowtls_utls", "ShadowTLS " .. translate("uTLS"))
+ o.default = "0"
+ o:depends({ shadowtls = true })
+
+ o = s:option(ListValue, "shadowtls_fingerprint", "ShadowTLS " .. translate("Finger Print"))
+ o:value("chrome")
+ o:value("firefox")
+ o:value("edge")
+ o:value("safari")
+ -- o:value("360")
+ o:value("qq")
+ o:value("ios")
+ -- o:value("android")
+ o:value("random")
+ -- o:value("randomized")
+ o.default = "chrome"
+ o:depends({ shadowtls = true, shadowtls_utls = true })
+end
+
+-- [[ SIP003 plugin ]]--
+o = s:option(Flag, "plugin_enabled", translate("plugin"))
+o.default = 0
+o:depends({ protocol = "shadowsocks" })
+
+o = s:option(ListValue, "plugin", "SIP003 " .. translate("plugin"))
+o.default = "obfs-local"
+o:depends({ plugin_enabled = true })
+o:value("obfs-local")
+o:value("v2ray-plugin")
+
+o = s:option(Value, "plugin_opts", translate("opts"))
+o:depends({ plugin_enabled = true })
+
+o = s:option(ListValue, "domain_resolver", translate("Domain DNS Resolve"))
+o.description = translate("If the node address is a domain name, this DNS will be used for resolution.") .. "
" .. string.format('%s',
+ translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead."))
+o:value("", translate("Auto"))
+o:value("tcp", "TCP")
+o:value("udp", "UDP")
+o:value("https", "DoH")
+
+o = s:option(Value, "domain_resolver_dns", "DNS")
+o.datatype = "or(ipaddr,ipaddrport)"
+o:value("114.114.114.114")
+o:value("223.5.5.5:53")
+o.default = o.keylist[1]
+o:depends({ domain_resolver = "tcp" })
+o:depends({ domain_resolver = "udp" })
+
+o = s:option(Value, "domain_resolver_dns_https", "DNS")
+o:value("https://120.53.53.53/dns-query", "DNSPod")
+o:value("https://223.5.5.5/dns-query", "AliDNS")
+o.default = o.keylist[1]
+o:depends({ domain_resolver = "https" })
+
+o = s:option(ListValue, "domain_strategy", translate("Domain Strategy"), translate("If is domain name, The requested domain name will be resolved to IP before connect."))
+o.default = ""
+o:value("", translate("Auto"))
+o:value("prefer_ipv4", translate("Prefer IPv4"))
+o:value("prefer_ipv6", translate("Prefer IPv6"))
+o:value("ipv4_only", translate("IPv4 Only"))
+o:value("ipv6_only", translate("IPv6 Only"))
+
+
+local protocols = s.fields["protocol"].keylist
+if #protocols > 0 then
+ for i, v in ipairs(protocols) do
+ if not v:find("^_") then
+ local depends_condition = { protocol = v }
+ if v == "hysteria2" then
+ depends_condition["hysteria2_realms"] = false
+ end
+ s.fields["address"]:depends(depends_condition)
+ s.fields["port"]:depends(depends_condition)
+ s.fields["domain_resolver"]:depends(depends_condition)
+ s.fields["domain_strategy"]:depends(depends_condition)
+ end
+ end
+end
+end
+-- [[ Normal single node End ]]
+
+if not load_shunt_options then
+ o = s:option(ListValue, "chain_proxy", translate("Chain Proxy"))
+ o:value("", translate("Close(Not use)"))
+ if not (load_iface_options or load_urltest_options) then
+ -- Special node cannot be use pre-proxy.
+ o:value("1", translate("Preproxy Node"))
+ o:value("3", translate("Outbound Interface"))
+ end
+ o:value("2", translate("Landing Node"))
+
+ o1 = s:option(ListValue, "preproxy_node", translate("Preproxy Node"), translate("Only support a layer of proxy."))
+ o1:depends({ chain_proxy = "1", hysteria2_realms = false })
+ o1.template = m:template_path("/cbi/nodes_listvalue")
+ o1.group = {}
+
+ o3 = s:option(Value, "outbound_iface", translate("Outbound Interface"))
+ o3:depends({ chain_proxy = "3" })
+ o3:value("", translate("All"))
+ for _, d in ipairs(netdev_list) do
+ o3:value(d.name, d.label)
+ end
+
+ o2 = s:option(ListValue, "to_node", translate("Landing Node"), translate("Only support a layer of proxy."))
+ o2:depends({ chain_proxy = "2", hysteria2_realms = false })
+ o2.template = m:template_path("/cbi/nodes_listvalue")
+ o2.group = {}
+
+ for k1, v1 in pairs(node_list) do
+ if k1 ~= "shunt_list" and k1 ~= "iface_list" then
+ for i, v in ipairs(v1) do
+ if v.id ~= arg[1] then
+ o1:value(v.id, v.remark)
+ o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+ if k1 == "normal_list" then
+ -- Landing Node not support use special node.
+ o2:value(v.id, v.remark)
+ o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+ end
+ end
+ end
+ end
+ end
+end
+
+api.luci_types(s1, s)
+
+if load_shunt_options then
+ local current_node = m:get(arg[1]) or {}
+ local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
+ setfenv(shunt_lua, getfenv(1))(m, s1, {
+ node_id = arg[1],
+ node = current_node,
+ node_list = node_list,
+ })
+end
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/2_xray.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/2_xray.lua
new file mode 100644
index 00000000000..1697d1deb50
--- /dev/null
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/2_xray.lua
@@ -0,0 +1,894 @@
+if not api.finded_com("xray") then
+ return
+end
+
+-- [[ Xray ]]
+local m, s1 = ...
+local type_name = "Xray"
+
+s1.fields["type"]:value(type_name, "Xray")
+if not s1.fields["type"].default then
+ s1.fields["type"].default = type_name
+end
+
+if s1.val["type"] ~= type_name then
+ return
+end
+
+local s = NamedSection(m, arg[1], "server")
+s.type_name = type_name
+s.option_prefix = "xray_"
+
+local formvalue_proto = luci.http.formvalue(formvalue_key .. "protocol")
+
+if formvalue_proto then s1.val["protocol"] = formvalue_proto end
+
+local arg_select_proto = luci.http.formvalue("select_proto") or ""
+
+local ss_method_list = {
+ "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "xchacha20-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
+}
+
+local security_list = { "auto", "aes-128-gcm", "chacha20-poly1305" }
+
+local header_type_list = {
+ "none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
+}
+
+local xray_version = api.get_app_version("xray")
+
+o = s:option(ListValue, "protocol", translate("Protocol"))
+o:value("socks", translate("Socks"))
+o:value("http", translate("HTTP"))
+o:value("vmess", translate("Vmess"))
+o:value("vless", translate("VLESS"))
+o:value("shadowsocks", translate("Shadowsocks"))
+o:value("trojan", translate("Trojan"))
+o:value("wireguard", translate("WireGuard"))
+if api.compare_versions(xray_version, ">=", "26.1.13") then
+ o:value("hysteria2", translate("Hysteria2"))
+end
+if api.compare_versions(xray_version, ">=", "1.8.12") then
+ o:value("_balancing", translate("Balancing"))
+end
+o:value("_shunt", translate("Shunt"))
+o:value("_iface", translate("Custom Interface"))
+function o.custom_cfgvalue(self, section)
+ if arg_select_proto ~= "" then
+ return arg_select_proto
+ else
+ return m:get(section, self.config_option)
+ end
+end
+
+local load_balancing_options = s1.val["protocol"] == "_balancing" or arg_select_proto == "_balancing"
+local load_shunt_options = s1.val["protocol"] == "_shunt" or arg_select_proto == "_shunt"
+local load_iface_options = s1.val["protocol"] == "_iface" or arg_select_proto == "_iface"
+local load_normal_options = true
+if load_balancing_options or load_shunt_options or load_iface_options then
+ load_normal_options = nil
+end
+if not arg_select_proto:find("_") then
+ load_normal_options = true
+end
+
+local netdev_list = api.get_network_devices()
+local node_list = api.get_node_list()
+local fallback_list = {}
+local is_balancer = nil
+for k, e in ipairs(node_list.balancing_list or {}) do
+ if e.id ~= arg[1] then
+ fallback_list[#fallback_list + 1] = {
+ id = e["id"],
+ remark = e["remark"],
+ group = e["group"],
+ fallback = e.o["fallback_node"],
+ }
+ else
+ is_balancer = true
+ end
+end
+
+if load_balancing_options then -- [[ Load balancing Start ]]
+ o = s:option(ListValue, "node_add_mode", translate("Node Addition Method"))
+ o:depends({ protocol = "_balancing" })
+ o.default = "manual"
+ o:value("manual", translate("Manual"))
+ o:value("batch", translate("Batch"))
+
+ o = s:option(MultiValue, "balancing_node", translate("Load balancing node list"), translate("Load balancing node list, document"))
+ o:depends({ node_add_mode = "manual" })
+ o.widget = "checkbox"
+ o.template = m:template_path("/cbi/nodes_multivalue")
+ o.group = {}
+ for k1, v1 in pairs(node_list) do
+ if k1 == "socks_list" or k1 == "normal_list" then
+ for i, v in ipairs(v1) do
+ o:value(v.id, v.remark)
+ o.group[#o.group+1] = v.group or ""
+ end
+ end
+ end
+ -- 读取旧 DynamicList
+ function o.custom_cfgvalue(self, section)
+ return table.concat(m:get(section, "balancing_node") or {}, " ")
+ end
+ -- 写入保持 DynamicList
+ function o.custom_write(self, section, value)
+ local old = m:get(section, "balancing_node") or {}
+ local new, set = {}, {}
+ for v in value:gmatch("%S+") do
+ new[#new + 1] = v
+ set[v] = 1
+ end
+ for _, v in ipairs(old) do
+ if not set[v] then
+ m:set(section, "balancing_node", new)
+ return
+ end
+ set[v] = nil
+ end
+ for _ in pairs(set) do
+ m:set(section, "balancing_node", new)
+ return
+ end
+ end
+
+ o = s:option(MultiValue, "node_group", translate("Select Group"))
+ o:depends({ node_add_mode = "batch" })
+ o.widget = "checkbox"
+ o:value("default", translate("default"))
+ for k, v in pairs(groups) do
+ o:value(api.UrlEncode(k), k)
+ end
+
+ o = s:option(Value, "node_match_rule", translate("Node Matching Rules"))
+ o:depends({ node_add_mode = "batch" })
+ local descrStr = "Example: ^A && B && !C && D$
"
+ descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).
"
+ descrStr = descrStr .. "Conditions are joined by && (AND), and their order does not affect the result.
"
+ descrStr = descrStr .. "Multiple groups can be separated by || (OR), matching succeeds if any group matches.
"
+ descrStr = descrStr .. "Example: A && B || C && D means (A AND B) OR (C AND D)."
+ o.description = translate(descrStr)
+
+ o = s:option(ListValue, "balancingStrategy", translate("Balancing Strategy"))
+ o:depends({ protocol = "_balancing" })
+ o:value("random")
+ o:value("roundRobin")
+ o:value("leastPing")
+ o:value("leastLoad")
+ o.default = "random"
+
+ -- Fallback Node
+ o = s:option(ListValue, "fallback_node", translate("Fallback Node"))
+ o.group = {"",""}
+ o:value("", translate("Close(Not use)"))
+ o:value("_direct", translate("Direct Connection"))
+ o:depends({ protocol = "_balancing" })
+ o.template = m:template_path("/cbi/nodes_listvalue")
+ -- 最大 fallback 套娃层数
+ local MAX_FALLBACK_DEPTH = 3
+ -- 检查是否会形成回环
+ local function will_loop(start_id, target_id, depth)
+ depth = depth or 0
+ -- 超过最大深度后停止递归
+ if depth >= MAX_FALLBACK_DEPTH then
+ return false
+ end
+ for _, v in ipairs(fallback_list) do
+ if v.id == target_id then
+ local fb = v.fallback
+ -- 没有 fallback
+ if not fb or fb == "" or fb == "_direct" then
+ return false
+ end
+ -- 检测到回环
+ if fb == start_id then
+ return true
+ end
+ -- 继续递归检查
+ return will_loop(start_id, fb, depth + 1)
+ end
+ end
+ return false
+ end
+ -- 获取 fallback 链深度
+ local function get_fallback_depth(id, depth)
+ depth = depth or 0
+ if depth >= MAX_FALLBACK_DEPTH then
+ return depth
+ end
+ for _, v in ipairs(fallback_list) do
+ if v.id == id then
+ local fb = v.fallback
+ if not fb or fb == "" or fb == "_direct" then
+ return depth
+ end
+ return get_fallback_depth(fb, depth + 1)
+ end
+ end
+ return depth
+ end
+ for _, v in ipairs(fallback_list) do
+ local depth = get_fallback_depth(v.id)
+ -- 超过最大套娃层数后,不允许继续选择 balancer
+ if depth < MAX_FALLBACK_DEPTH
+ and not will_loop(arg[1], v.id)
+ then
+ o:value(v.id, v.remark)
+ o.group[#o.group + 1] = (v.group and v.group ~= "") and v.group or translate("default")
+ end
+ end
+ for k1, v1 in pairs(node_list) do
+ if k1 == "socks_list" or k1 == "normal_list" or k1 == "urltest_list" then
+ for i, v in ipairs(v1) do
+ o:value(v.id, v.remark)
+ o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+ end
+ end
+ end
+
+ -- 探测地址
+ o = s:option(Flag, "useCustomProbeUrl", translate("Use Custom Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL."))
+ o:depends({ protocol = "_balancing" })
+
+ o = s:option(Value, "probeUrl", translate("Probe URL"))
+ o:depends({ useCustomProbeUrl = true })
+ o:value("https://cp.cloudflare.com/", "Cloudflare")
+ o:value("https://www.gstatic.com/generate_204", "Gstatic")
+ o:value("https://www.google.com/generate_204", "Google")
+ o:value("https://www.youtube.com/generate_204", "YouTube")
+ o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)")
+ o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)")
+ o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
+ o.default = o.keylist[3]
+ o.description = translate("The URL used to detect the connection status.")
+
+ -- 探测间隔
+ o = s:option(Value, "probeInterval", translate("Probe Interval"))
+ o:depends({ protocol = "_balancing" })
+ o.default = "1m"
+ o.placeholder = "1m"
+ o.description = translate("The interval between initiating probes.") .. "
" ..
+ translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" ..
+ translate("When the unit is not filled in, it defaults to seconds.")
+
+ o = s:option(Value, "expected", translate("Preferred Node Count"))
+ o:depends({ balancingStrategy = "leastLoad" })
+ o.datatype = "uinteger"
+ o.default = "2"
+ o.placeholder = "2"
+ o.description = translate("The load balancer selects the optimal number of nodes, and traffic is randomly distributed among them.")
+
+ o = s:option(Value, "tolerance", translate("Failure Tolerance (%)"))
+ o:depends({ balancingStrategy = "leastLoad" })
+ o.datatype = "uinteger"
+ o.default = "10"
+ o.placeholder = "10"
+ o.description = translate("The maximum acceptable speed test failure rate. For example, 1 means allowing a 1% failure rate.")
+end -- [[ 负载均衡 End ]]
+
+if load_iface_options then -- [[ 自定义接口 Start ]]
+ o = s:option(Value, "iface", translate("Interface"))
+ o:depends({ protocol = "_iface" })
+ for _, d in ipairs(netdev_list) do
+ o:value(d.name, d.label)
+ end
+end -- [[ 自定义接口 End ]]
+
+
+if load_normal_options then
+
+o = s:option(Value, "address", translate("Address (Support Domain Name)"))
+
+o = s:option(Value, "port", translate("Port"))
+o.datatype = "port"
+
+o = s:option(Value, "uuid", translate("ID"))
+o.password = true
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless" })
+
+o = s:option(Value, "username", translate("Username"))
+o:depends({ protocol = "http" })
+o:depends({ protocol = "socks" })
+
+o = s:option(Value, "password", translate("Password"))
+o.password = true
+o:depends({ protocol = "http" })
+o:depends({ protocol = "socks" })
+o:depends({ protocol = "shadowsocks" })
+o:depends({ protocol = "trojan" })
+
+o = s:option(ListValue, "security", translate("Encrypt Method"))
+for a, t in ipairs(security_list) do o:value(t) end
+o:depends({ protocol = "vmess" })
+
+o = s:option(Value, "encryption", translate("Encrypt Method") .. " (encryption)")
+o.default = "none"
+o.placeholder = "none"
+o:depends({ protocol = "vless" })
+o.validate = function(self, value)
+ value = api.trim(value)
+ return (value == "" and "none" or value)
+end
+
+o = s:option(ListValue, "ss_method", translate("Encrypt Method"))
+o.rewrite_option = "method"
+for a, t in ipairs(ss_method_list) do o:value(t) end
+o:depends({ protocol = "shadowsocks" })
+
+o = s:option(ListValue, "flow", translate("flow"))
+o.default = ""
+o:value("", translate("Disable"))
+o:value("xtls-rprx-vision")
+o:value("xtls-rprx-vision-udp443")
+o:depends({ protocol = "vless" })
+
+---- [[hysteria2]]
+o = s:option(Value, "hysteria2_hop", translate("Port hopping range"))
+o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
+o:depends({ protocol = "hysteria2", hysteria2_realms = false })
+
+o = s:option(Value, "hysteria2_hop_interval", translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5."))
+o.datatype = "or(uinteger,portrange)"
+o.placeholder = "30"
+o.default = "30"
+o:depends({ protocol = "hysteria2", hysteria2_realms = false })
+
+o = s:option(Flag, "hysteria2_realms", translate("Realms"))
+o.default = "0"
+if api.compare_versions(xray_version, ">", "26.5.9") then
+ o:depends({ protocol = "hysteria2"})
+else
+ o:depends({ protocol = "__hide"})
+end
+
+o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
+o:depends({ hysteria2_realms = "1" })
+o.validate = function(self, value)
+ value = api.trim(value)
+ local realm = api.parse_realm_uri(value)
+ if realm then return value end
+ return nil, translate("Invalid Realm URL.")
+end
+
+o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
+o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
+o:depends({ hysteria2_realms = "1" })
+
+o = s:option(Value, "hysteria2_auth_password", translate("Auth Password"))
+o.password = true
+o:depends({ protocol = "hysteria2"})
+
+o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type"))
+o:value("", translate("Disable"))
+o:value("salamander")
+o:value("gecko")
+o:depends({ protocol = "hysteria2" })
+
+o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password"))
+o:depends({ hysteria2_obfs_type = "salamander" })
+o:depends({ hysteria2_obfs_type = "gecko" })
+
+o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
+o.datatype = "uinteger"
+o.placeholder = "512"
+o.default = "512"
+o:depends({ hysteria2_obfs_type = "gecko" })
+
+o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
+o.datatype = "uinteger"
+o.placeholder = "1200"
+o.default = "1200"
+o:depends({ hysteria2_obfs_type = "gecko" })
+
+o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps"))
+o:depends({ protocol = "hysteria2" })
+
+o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps"))
+o:depends({ protocol = "hysteria2" })
+
+o = s:option(Value, "hysteria2_idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
+o.datatype = "range(4,120)"
+o:depends({ protocol = "hysteria2"})
+
+o = s:option(Value, "hysteria2_keep_alive_period", translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
+o.datatype = "range(2,60)"
+o:depends({ protocol = "hysteria2"})
+
+o = s:option(Flag, "hysteria2_disable_mtu_discovery", translate("Disable MTU detection"))
+o.default = "0"
+o:depends({ protocol = "hysteria2"})
+---- [[hysteria2 end]]
+
+o = s:option(Flag, "tls", translate("TLS"))
+o.default = 0
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless" })
+o:depends({ protocol = "trojan" })
+o:depends({ protocol = "shadowsocks" })
+
+o = s:option(Flag, "reality", translate("REALITY"), translate("Only recommend to use with VLESS-TCP-XTLS-Vision."))
+o.default = 0
+o:depends({ tls = true, transport = "raw" })
+o:depends({ tls = true, transport = "ws" })
+o:depends({ tls = true, transport = "grpc" })
+o:depends({ tls = true, transport = "httpupgrade" })
+o:depends({ tls = true, transport = "xhttp" })
+
+o = s:option(ListValue, "alpn", translate("alpn"))
+o.default = "default"
+o:value("default", translate("Default"))
+o:value("h3")
+o:value("h2")
+o:value("h3,h2")
+o:value("http/1.1")
+o:value("h2,http/1.1")
+o:value("h3,h2,http/1.1")
+o:depends({ tls = true, reality = false })
+
+-- o = s:option(Value, "minversion", translate("minversion"))
+-- o.default = "1.3"
+-- o:value("1.3")
+-- o:depends({ tls = true })
+
+o = s:option(Value, "tls_serverName", "SNI " .. translate("Domain"))
+o:depends({ tls = true })
+o:depends({ protocol = "hysteria2" })
+
+o = s:option(Value, "tls_pinSHA256", translate("TLS Chain Fingerprint (SHA256)"))
+o:depends({ tls = true, reality = false })
+o:depends({ protocol = "hysteria2" })
+o.description = translate("Once set, connects only when the server’s chain fingerprint matches.") ..
+ string.format("%s", "→ " .. translate("Fetch Manually"))
+
+o = s:option(Value, "tls_CertByName", translate("TLS Certificate Name (CertName)"), translate("TLS is used to verify the leaf certificate name."))
+o:depends({ tls = true, reality = false })
+o:depends({ protocol = "hysteria2" })
+
+o = s:option(Flag, "tls_certificate", translate("TLS Certificate (PEM)"))
+o.default = "0"
+o:depends({ tls = true, reality = false })
+o:depends({ protocol = "hysteria2" })
+
+o = s:option(TextValue, "tls_certificate_pem", " ", translate("Full certificate (chain), PEM format."))
+o.default = ""
+o.rows = 5
+o.wrap = "off"
+o:depends({ tls_certificate = true })
+o.validate = function(self, value)
+ value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
+ return value
+end
+
+o = s:option(Flag, "ech", translate("ECH"))
+o.default = "0"
+o:depends({ tls = true, reality = false })
+o:depends({ protocol = "hysteria2", hysteria2_realms = false })
+
+o = s:option(TextValue, "ech_config", translate("ECH Config"))
+o.default = ""
+o.rows = 5
+o.wrap = "soft"
+o:depends({ ech = true })
+o.validate = function(self, value)
+ return api.trim(value:gsub("[\r\n]", ""))
+end
+
+-- [[ REALITY部分 ]] --
+o = s:option(Value, "reality_publicKey", translate("Public Key"))
+o:depends({ tls = true, reality = true })
+
+o = s:option(Value, "reality_shortId", translate("Short Id"))
+o:depends({ tls = true, reality = true })
+
+o = s:option(Value, "reality_spiderX", translate("Spider X"))
+o.placeholder = "/"
+o:depends({ tls = true, reality = true })
+
+o = s:option(Flag, "utls", translate("uTLS"))
+o.default = "0"
+o:depends({ tls = true, reality = false })
+
+o = s:option(ListValue, "fingerprint", translate("Finger Print"))
+o:value("chrome")
+o:value("firefox")
+o:value("edge")
+o:value("safari")
+o:value("360")
+o:value("qq")
+o:value("ios")
+o:value("android")
+o:value("random")
+o:value("randomized")
+o:value("unsafe")
+o.default = "chrome"
+o:depends({ tls = true, utls = true })
+o:depends({ tls = true, reality = true })
+
+o = s:option(Value, "cipherSuites", translate("Cipher Suites"), '***' .. " " .. translate("Configures the list of supported cipher suites, separated by :"))
+o:depends({ tls = true, reality = false })
+
+o = s:option(Flag, "use_mldsa65Verify", translate("ML-DSA-65"))
+o.default = "0"
+o:depends({ tls = true, reality = true })
+
+o = s:option(TextValue, "reality_mldsa65Verify", "ML-DSA-65 " .. translate("Public key"))
+o.default = ""
+o.rows = 5
+o.wrap = "soft"
+o:depends({ use_mldsa65Verify = true })
+o.validate = function(self, value)
+ return api.trim(value:gsub("[\r\n]", ""))
+end
+
+o = s:option(ListValue, "transport", translate("Transport"))
+o:value("raw", "RAW (TCP)")
+o:value("mkcp", "mKCP")
+o:value("ws", "WebSocket")
+o:value("grpc", "gRPC")
+o:value("httpupgrade", "HttpUpgrade")
+o:value("xhttp", "XHTTP")
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless" })
+o:depends({ protocol = "shadowsocks" })
+o:depends({ protocol = "trojan" })
+
+o = s:option(Value, "wireguard_public_key", translate("Public Key"))
+o:depends({ protocol = "wireguard" })
+
+o = s:option(Value, "wireguard_secret_key", translate("Private Key"))
+o:depends({ protocol = "wireguard" })
+
+o = s:option(Value, "wireguard_preSharedKey", translate("Pre shared key"))
+o:depends({ protocol = "wireguard" })
+
+o = s:option(DynamicList, "wireguard_local_address", translate("Local Address"))
+o:depends({ protocol = "wireguard" })
+
+o = s:option(Value, "wireguard_mtu", translate("MTU"))
+o.default = "1420"
+o:depends({ protocol = "wireguard" })
+
+if api.compare_versions(xray_version, ">=", "1.8.0") then
+ o = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
+ o:depends({ protocol = "wireguard" })
+end
+
+o = s:option(Value, "wireguard_keepAlive", translate("Keep Alive"))
+o.default = "0"
+o:depends({ protocol = "wireguard" })
+
+-- [[ RAW部分 ]]--
+
+-- TCP伪装
+o = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
+o:value("none", "none")
+o:value("http", "http")
+o:depends({ transport = "raw" })
+
+-- HTTP域名
+o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host"))
+o:depends({ tcp_guise = "http" })
+
+-- HTTP路径
+o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
+o.placeholder = "/"
+o:depends({ tcp_guise = "http" })
+
+-- [[ mKCP部分 ]]--
+
+o = s:option(ListValue, "mkcp_guise", translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)
dns: Disguising traffic as DNS requests.'))
+for a, t in ipairs(header_type_list) do o:value(t) end
+o:depends({ transport = "mkcp" })
+
+o = s:option(Value, "mkcp_domain", translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
+o:depends({ mkcp_guise = "dns" })
+
+o = s:option(Value, "mkcp_mtu", translate("KCP MTU"))
+o.datatype = "uinteger"
+o.default = 1350
+o:depends({ transport = "mkcp" })
+
+o = s:option(Value, "mkcp_seed", translate("KCP Seed"))
+o:depends({ transport = "mkcp" })
+
+-- [[ WebSocket部分 ]]--
+o = s:option(Value, "ws_host", translate("WebSocket Host"))
+o:depends({ transport = "ws" })
+
+o = s:option(Value, "ws_path", translate("WebSocket Path"))
+o.placeholder = "/"
+o:depends({ transport = "ws" })
+
+o = s:option(Value, "ws_heartbeatPeriod", translate("HeartbeatPeriod(second)"))
+o.datatype = "integer"
+o:depends({ transport = "ws" })
+
+-- [[ gRPC部分 ]]--
+o = s:option(Value, "grpc_serviceName", "ServiceName")
+o:depends({ transport = "grpc" })
+
+o = s:option(ListValue, "grpc_mode", "gRPC " .. translate("Transfer mode"))
+o:value("gun")
+o:value("multi")
+o:depends({ transport = "grpc" })
+
+o = s:option(Flag, "grpc_health_check", translate("Health check"))
+o:depends({ transport = "grpc" })
+
+o = s:option(Value, "grpc_idle_timeout", translate("Idle timeout"))
+o.default = "10"
+o:depends({ grpc_health_check = true })
+
+o = s:option(Value, "grpc_health_check_timeout", translate("Health check timeout"))
+o.default = "20"
+o:depends({ grpc_health_check = true })
+
+o = s:option(Flag, "grpc_permit_without_stream", translate("Permit without stream"))
+o.default = "0"
+o:depends({ grpc_health_check = true })
+
+o = s:option(Value, "grpc_initial_windows_size", translate("Initial Windows Size"))
+o.default = "0"
+o:depends({ transport = "grpc" })
+
+-- [[ HttpUpgrade部分 ]]--
+o = s:option(Value, "httpupgrade_host", translate("HttpUpgrade Host"))
+o:depends({ transport = "httpupgrade" })
+
+o = s:option(Value, "httpupgrade_path", translate("HttpUpgrade Path"))
+o.placeholder = "/"
+o:depends({ transport = "httpupgrade" })
+
+-- [[ XHTTP部分 ]]--
+o = s:option(ListValue, "xhttp_mode", "XHTTP " .. translate("Mode"))
+o:depends({ transport = "xhttp" })
+o.default = "auto"
+o:value("auto")
+o:value("packet-up")
+o:value("stream-up")
+o:value("stream-one")
+
+o = s:option(Value, "xhttp_host", translate("XHTTP Host"))
+o:depends({ transport = "xhttp" })
+
+o = s:option(Value, "xhttp_path", translate("XHTTP Path"))
+o.placeholder = "/"
+o:depends({ transport = "xhttp" })
+
+o = s:option(Flag, "use_xhttp_extra", translate("XHTTP Extra"))
+o.default = "0"
+o:depends({ transport = "xhttp" })
+
+o = s:option(TextValue, "xhttp_extra", " ", translate("An XHttpObject in JSON format, used for sharing."))
+o:depends({ use_xhttp_extra = true })
+o.rows = 10
+o.wrap = "off"
+o.datatype = "json"
+local o_validate = o.validate
+o.validate = function(self, value)
+ value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
+ local v = o_validate(self, value)
+ if v then return v end
+ return nil, "XHTTP Extra " .. translate("Must be JSON text!")
+end
+o.custom_cfgvalue = function(self, section, value)
+ local raw = m:get(section, "xhttp_extra")
+ if raw then
+ return api.base64Decode(raw)
+ end
+end
+o.custom_write = function(self, section, value)
+ m:set(section, "xhttp_extra", api.base64Encode(value) or "")
+ local success, data = pcall(api.jsonc.parse, value)
+ if success and data then
+ local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address)
+ or (data.downloadSettings and data.downloadSettings.address)
+ if address and address ~= "" then
+ address = address:gsub("^%[", ""):gsub("%]$", "")
+ m:set(section, "download_address", address)
+ else
+ m:del(section, "download_address")
+ end
+ else
+ m:del(section, "download_address")
+ end
+end
+o.custom_remove = function(self, section, value)
+ m:del(section, "xhttp_extra")
+ m:del(section, "download_address")
+end
+
+-- [[ User-Agent ]]--
+o = s:option(Value, "user_agent", translate("User-Agent"))
+o.default = ""
+o:value("", translate("default"))
+o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36", "chrome")
+o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0", "firefox")
+o:value("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15", "safari")
+o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70", "edge")
+o:value("Go-http-client/1.1", "golang")
+o:value("curl/7.68.0", "curl")
+o:depends({ tcp_guise = "http" })
+o:depends({ transport = "ws" })
+o:depends({ transport = "httpupgrade" })
+o:depends({ transport = "xhttp" })
+o:depends({ transport = "grpc" })
+
+-- [[ Mux.Cool ]]--
+o = s:option(Flag, "mux", "Mux", translate("Enable Mux.Cool"))
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless", transport = "raw" })
+o:depends({ protocol = "vless", transport = "ws" })
+o:depends({ protocol = "vless", transport = "grpc" })
+o:depends({ protocol = "vless", transport = "httpupgrade" })
+o:depends({ protocol = "shadowsocks" })
+o:depends({ protocol = "trojan" })
+
+o = s:option(Value, "mux_concurrency", translate("Mux concurrency"))
+o.default = -1
+o:depends({ mux = true })
+
+o = s:option(Value, "xudp_concurrency", translate("XUDP Mux concurrency"))
+o.default = 8
+o:depends({ mux = true })
+
+--[[FinalMask]]
+o = s:option(Flag, "use_finalmask", "FinalMask")
+o.default = "0"
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless" })
+o:depends({ protocol = "trojan" })
+o:depends({ protocol = "shadowsocks" })
+o:depends({ protocol = "wireguard" })
+o:depends({ protocol = "hysteria2", hysteria2_realms = false })
+
+o = s:option(TextValue, "finalmask", " ")
+o:depends({ use_finalmask = true })
+o.rows = 10
+o.wrap = "off"
+o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "
" ..
+ translate("Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings.")
+o.datatype = "json"
+local o_validate = o.validate
+o.validate = function(self, value)
+ value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
+ local v = o_validate(self, value)
+ if v then return v end
+ return nil, "FinalMask " .. translate("Must be JSON text!")
+end
+o.custom_cfgvalue = function(self, section, value)
+ local raw = m:get(section, "finalmask")
+ if raw then
+ return api.base64Decode(raw)
+ end
+end
+o.custom_write = function(self, section, value)
+ m:set(section, "finalmask", api.base64Encode(value) or "")
+end
+
+--[[Fast Open]]
+o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required"))
+o.default = 0
+
+--[[tcpMptcp]]
+o = s:option(Flag, "tcpMptcp", "tcpMptcp", translate("Enable Multipath TCP, need to be enabled in both server and client configuration."))
+o.default = 0
+
+o = s:option(ListValue, "domain_resolver", translate("Domain DNS Resolve"))
+o.description = translate("If the node address is a domain name, this DNS will be used for resolution.") .. "
" .. string.format('%s',
+ translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead."))
+o:value("", translate("Auto"))
+o:value("tcp", "TCP")
+o:value("udp", "UDP")
+o:value("https", "DoH")
+
+o = s:option(Value, "domain_resolver_dns", "DNS")
+o.datatype = "or(ipaddr,ipaddrport)"
+o:value("114.114.114.114")
+o:value("223.5.5.5:53")
+o.default = o.keylist[1]
+o:depends({ domain_resolver = "tcp" })
+o:depends({ domain_resolver = "udp" })
+
+o = s:option(Value, "domain_resolver_dns_https", "DNS")
+o:value("https://120.53.53.53/dns-query", "DNSPod")
+o:value("https://223.5.5.5/dns-query", "AliDNS")
+o.default = o.keylist[1]
+o:depends({ domain_resolver = "https" })
+
+o = s:option(ListValue, "domain_strategy", translate("Domain Strategy"), translate("If is domain name, The requested domain name will be resolved to IP before connect."))
+o.default = ""
+o:value("", translate("Auto"))
+o:value("UseIPv4v6", translate("Prefer IPv4"))
+o:value("UseIPv6v4", translate("Prefer IPv6"))
+o:value("UseIPv4", translate("IPv4 Only"))
+o:value("UseIPv6", translate("IPv6 Only"))
+
+o = s:option(Flag, "happy_eyeballs", translate("Enable Happy Eyeballs"), translate("Attempts IPv4 and IPv6 simultaneously; automatically uses the faster connection."))
+o.default = 0
+
+local protocols = s.fields["protocol"].keylist
+if #protocols > 0 then
+ for i, v in ipairs(protocols) do
+ if not v:find("^_") then
+ local depends_condition = { protocol = v }
+ if v == "hysteria2" then
+ depends_condition["hysteria2_realms"] = false
+ end
+ s.fields["address"]:depends(depends_condition)
+ s.fields["port"]:depends(depends_condition)
+ s.fields["domain_resolver"]:depends(depends_condition)
+ s.fields["happy_eyeballs"]:depends(depends_condition)
+
+ local strategy_depends = api.clone(depends_condition)
+ strategy_depends["happy_eyeballs"] = false
+ s.fields["domain_strategy"]:depends(strategy_depends)
+
+ if v ~= "hysteria2" then
+ s.fields["tcp_fast_open"]:depends({ protocol = v })
+ s.fields["tcpMptcp"]:depends({ protocol = v })
+ end
+ end
+ end
+end
+end
+-- [[ Normal single node End ]]
+
+if not load_shunt_options then
+ o = s:option(ListValue, "chain_proxy", translate("Chain Proxy"))
+ o:value("", translate("Close(Not use)"))
+ if not (load_iface_options or load_balancing_options) then
+ -- Special node cannot be use pre-proxy.
+ o:value("1", translate("Preproxy Node"))
+ o:value("3", translate("Outbound Interface"))
+ end
+ o:value("2", translate("Landing Node"))
+
+ o1 = s:option(ListValue, "preproxy_node", translate("Preproxy Node"), translate("Only support a layer of proxy."))
+ o1:depends({ chain_proxy = "1", hysteria2_realms = false })
+ o1.template = m:template_path("/cbi/nodes_listvalue")
+ o1.group = {}
+
+ o3 = s:option(Value, "outbound_iface", translate("Outbound Interface"))
+ o3:depends({ chain_proxy = "3" })
+ o3:value("", translate("All"))
+ for _, d in ipairs(netdev_list) do
+ o3:value(d.name, d.label)
+ end
+
+ o2 = s:option(ListValue, "to_node", translate("Landing Node"), translate("Only support a layer of proxy."))
+ o2:depends({ chain_proxy = "2", hysteria2_realms = false })
+ o2.template = m:template_path("/cbi/nodes_listvalue")
+ o2.group = {}
+
+ for k1, v1 in pairs(node_list) do
+ if k1 ~= "shunt_list" and k1 ~= "iface_list" then
+ for i, v in ipairs(v1) do
+ if v.id ~= arg[1] then
+ o1:value(v.id, v.remark)
+ o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+ if k1 == "normal_list" then
+ -- Landing Node not support use special node.
+ o2:value(v.id, v.remark)
+ o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+ end
+ end
+ end
+ end
+ end
+end
+
+api.luci_types(s1, s)
+
+if load_shunt_options then
+ local current_node = m:get(arg[1]) or {}
+ local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
+ setfenv(shunt_lua, getfenv(1))(m, s1, {
+ node_id = arg[1],
+ node = current_node,
+ node_list = node_list,
+ })
+end
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/3_ss-rust.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/3_ss-rust.lua
new file mode 100644
index 00000000000..1c7562dc7a5
--- /dev/null
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/3_ss-rust.lua
@@ -0,0 +1,72 @@
+if not api.is_finded("sslocal") then
+ return
+end
+
+-- [[ Shadowsocks Rust ]]
+local m, s1 = ...
+local type_name = "SS-Rust"
+
+s1.fields["type"]:value(type_name, "Shadowsocks Rust")
+
+if s1.val["type"] ~= type_name then
+ return
+end
+
+local s = NamedSection(m, arg[1], "server")
+s.type_name = type_name
+s.option_prefix = "ssrust_"
+
+local ssrust_encrypt_method_list = {
+ "none", "plain",
+ "aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
+ "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
+}
+
+o = s:option(ListValue, "del_protocol") --始终隐藏,用于删除 protocol
+o:depends({ __hide = "1" })
+o.rewrite_option = "protocol"
+
+o = s:option(Value, "address", translate("Address (Support Domain Name)"))
+
+o = s:option(Value, "port", translate("Port"))
+o.datatype = "port"
+
+o = s:option(Value, "password", translate("Password"))
+o.password = true
+
+o = s:option(Value, "ss_method", translate("Encrypt Method"))
+for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end
+
+o = s:option(Value, "timeout", translate("Connection Timeout"))
+o.datatype = "uinteger"
+o.default = 300
+
+o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required"))
+o.default = 0
+
+o = s:option(Flag, "plugin_enabled", translate("plugin"))
+o.default = 0
+
+o = s:option(Value, "plugin", "SIP003 " .. translate("plugin"), translate("Supports custom SIP003 plugins, Make sure the plugin is installed."))
+o.default = "none"
+o:value("none", translate("none"))
+if api.is_finded("xray-plugin") then o:value("xray-plugin") end
+if api.is_finded("v2ray-plugin") then o:value("v2ray-plugin") end
+if api.is_finded("obfs-local") then o:value("obfs-local") end
+if api.is_finded("shadow-tls") then o:value("shadow-tls") end
+o:depends({ plugin_enabled = true })
+o.validate = function(self, value, t)
+ if value and value ~= "" and value ~= "none" then
+ if not api.is_finded(value) then
+ return nil, value .. ": " .. translate("Can't find this file!")
+ else
+ return value
+ end
+ end
+ return nil
+end
+
+o = s:option(Value, "plugin_opts", translate("opts"))
+o:depends({ plugin_enabled = true })
+
+api.luci_types(s1, s)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ssr.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/4_ssr.lua
similarity index 55%
rename from applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ssr.lua
rename to applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/4_ssr.lua
index 9689c68cf4c..e86e470b4f0 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ssr.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/4_ssr.lua
@@ -1,24 +1,20 @@
-local m, s = ...
-
if not api.is_finded("ssr-local") and not api.is_finded("ssr-redir")then
return
end
-type_name = "SSR"
-
-- [[ ShadowsocksR Libev ]]
+local m, s1 = ...
+local type_name = "SSR"
-s.fields["type"]:value(type_name, "ShadowsocksR Libev")
+s1.fields["type"]:value(type_name, "ShadowsocksR Libev")
-if s.val["type"] ~= type_name then
+if s1.val["type"] ~= type_name then
return
end
-local option_prefix = "ssr_"
-
-local function _n(name)
- return option_prefix .. name
-end
+local s = NamedSection(m, arg[1], "server")
+s.type_name = type_name
+s.option_prefix = "ssr_"
local ssr_encrypt_method_list = {
"none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
@@ -39,32 +35,32 @@ local ssr_obfs_list = {
"tls1.0_session_auth", "tls1.2_ticket_auth"
}
-o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
+o = s:option(Value, "address", translate("Address (Support Domain Name)"))
-o = s:option(Value, _n("port"), translate("Port"))
+o = s:option(Value, "port", translate("Port"))
o.datatype = "port"
-o = s:option(Value, _n("password"), translate("Password"))
+o = s:option(Value, "password", translate("Password"))
o.password = true
-o = s:option(ListValue, _n("method"), translate("Encrypt Method"))
+o = s:option(ListValue, "method", translate("Encrypt Method"))
for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end
-o = s:option(ListValue, _n("protocol"), translate("Protocol"))
+o = s:option(ListValue, "protocol", translate("Protocol"))
for a, t in ipairs(ssr_protocol_list) do o:value(t) end
-o = s:option(Value, _n("protocol_param"), translate("Protocol_param"))
+o = s:option(Value, "protocol_param", translate("Protocol_param"))
-o = s:option(ListValue, _n("obfs"), translate("Obfs"))
+o = s:option(ListValue, "obfs", translate("Obfs"))
for a, t in ipairs(ssr_obfs_list) do o:value(t) end
-o = s:option(Value, _n("obfs_param"), translate("Obfs_param"))
+o = s:option(Value, "obfs_param", translate("Obfs_param"))
-o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
+o = s:option(Value, "timeout", translate("Connection Timeout"))
o.datatype = "uinteger"
o.default = 300
-o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
+o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required"))
o.default = 0
-api.luci_types(arg[1], m, s, type_name, option_prefix)
+api.luci_types(s1, s)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/5_hysteria2.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/5_hysteria2.lua
new file mode 100644
index 00000000000..481fc1f2311
--- /dev/null
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/5_hysteria2.lua
@@ -0,0 +1,131 @@
+if not api.finded_com("hysteria") then
+ return
+end
+
+-- [[ Hysteria2 ]]
+local m, s1 = ...
+local type_name = "Hysteria2"
+
+s1.fields["type"]:value(type_name, "Hysteria2")
+
+if s1.val["type"] ~= type_name then
+ return
+end
+
+local s = NamedSection(m, arg[1], "server")
+s.type_name = type_name
+s.option_prefix = "hysteria2_"
+
+local function _n(name)
+ return s.option_prefix .. name
+end
+
+o = s:option(ListValue, "protocol", translate("Protocol"))
+o:value("udp", "UDP")
+
+o = s:option(Value, "address", translate("Address (Support Domain Name)"))
+o:depends({ realms = false })
+
+o = s:option(Value, "port", translate("Port"))
+o.datatype = "port"
+o:depends({ realms = false })
+
+o = s:option(Value, "hop", translate("Port hopping range"))
+o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
+o.rewrite_option = _n(o.option)
+o:depends({ realms = false })
+
+o = s:option(Value, "hop_interval", translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5."))
+o.datatype = "or(uinteger,portrange)"
+o.placeholder = "30"
+o.default = "30"
+o.rewrite_option = _n(o.option)
+o:depends({ realms = false })
+
+o = s:option(Flag, "realms", translate("Realms"))
+o.default = "0"
+o.rewrite_option = _n(o.option)
+
+o = s:option(Value, "realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
+o.rewrite_option = _n(o.option)
+o:depends({ realms = "1" })
+o.validate = function(self, value)
+ value = api.trim(value)
+ local realm = api.parse_realm_uri(value)
+ if realm then return value end
+ return nil, translate("Invalid Realm URL.")
+end
+
+o = s:option(DynamicList, "realm_stun", translate("Realm STUN"))
+o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
+o.rewrite_option = _n(o.option)
+o:depends({ realms = "1" })
+
+o = s:option(Value, "auth_password", translate("Auth Password"))
+o.password = true
+o.rewrite_option = _n(o.option)
+
+o = s:option(ListValue, "obfs_type", translate("Obfs Type"))
+o:value("", translate("Disable"))
+o:value("salamander")
+o:value("gecko")
+o.rewrite_option = _n(o.option)
+
+o = s:option(Value, "obfs_password", translate("Obfs Password"))
+o.rewrite_option = _n(o.option)
+o:depends({ obfs_type = "salamander" })
+o:depends({ obfs_type = "gecko" })
+
+o = s:option(Value, "obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
+o.datatype = "uinteger"
+o.placeholder = "512"
+o.default = "512"
+o:depends({ obfs_type = "gecko" })
+o.rewrite_option = _n(o.option)
+
+o = s:option(Value, "obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
+o.datatype = "uinteger"
+o.placeholder = "1200"
+o.default = "1200"
+o:depends({ obfs_type = "gecko" })
+o.rewrite_option = _n(o.option)
+
+o = s:option(Flag, "fast_open", translate("Fast Open"))
+o.default = "0"
+
+o = s:option(Value, "tls_serverName", translate("Domain"))
+
+o = s:option(Flag, "tls_allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
+o.default = "0"
+
+o = s:option(Value, "tls_pinSHA256", translate("PinSHA256"),translate("Certificate fingerprint"))
+
+o = s:option(Value, "up_mbps", translate("Max upload Mbps"))
+o.rewrite_option = _n(o.option)
+
+o = s:option(Value, "down_mbps", translate("Max download Mbps"))
+o.rewrite_option = _n(o.option)
+
+o = s:option(Value, "recv_window", translate("QUIC stream receive window"))
+o.rewrite_option = _n(o.option)
+
+o = s:option(Value, "recv_window_conn", translate("QUIC connection receive window"))
+o.rewrite_option = _n(o.option)
+
+o = s:option(Value, "idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
+o.datatype = "range(4,120)"
+o.rewrite_option = _n(o.option)
+
+o = s:option(Value, "keep_alive_period", translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
+o.datatype = "range(2,60)"
+o.rewrite_option = _n(o.option)
+
+o = s:option(Flag, "disable_mtu_discovery", translate("Disable MTU detection"))
+o.default = "0"
+o.rewrite_option = _n(o.option)
+
+o = s:option(Flag, "lazy_start", translate("Lazy Start"))
+o.default = "0"
+o.rewrite_option = _n(o.option)
+
+api.luci_types(s1, s)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/6_naive.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/6_naive.lua
new file mode 100644
index 00000000000..b00c0c3fcfc
--- /dev/null
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/6_naive.lua
@@ -0,0 +1,33 @@
+if not api.is_finded("naive") then
+ return
+end
+
+-- [[ Naive ]]
+local m, s1 = ...
+local type_name = "Naiveproxy"
+
+s1.fields["type"]:value(type_name, "NaïveProxy")
+
+if s1.val["type"] ~= type_name then
+ return
+end
+
+local s = NamedSection(m, arg[1], "server")
+s.type_name = type_name
+s.option_prefix = "naive_"
+
+o = s:option(ListValue, "protocol", translate("Protocol"))
+o:value("https", translate("HTTPS"))
+o:value("quic", translate("QUIC"))
+
+o = s:option(Value, "address", translate("Address (Support Domain Name)"))
+
+o = s:option(Value, "port", translate("Port"))
+o.datatype = "port"
+
+o = s:option(Value, "username", translate("Username"))
+
+o = s:option(Value, "password", translate("Password"))
+o.password = true
+
+api.luci_types(s1, s)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua
deleted file mode 100644
index a0d080d7f19..00000000000
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua
+++ /dev/null
@@ -1,131 +0,0 @@
-local m, s = ...
-
-if not api.finded_com("hysteria") then
- return
-end
-
-type_name = "Hysteria2"
-
--- [[ Hysteria2 ]]
-
-s.fields["type"]:value(type_name, "Hysteria2")
-
-if s.val["type"] ~= type_name then
- return
-end
-
-local option_prefix = "hysteria2_"
-
-local function _n(name)
- return option_prefix .. name
-end
-
-o = s:option(ListValue, _n("protocol"), translate("Protocol"))
-o:value("udp", "UDP")
-
-o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
-o:depends({ [_n("realms")] = false })
-
-o = s:option(Value, _n("port"), translate("Port"))
-o.datatype = "port"
-o:depends({ [_n("realms")] = false })
-
-o = s:option(Value, _n("hop"), translate("Port hopping range"))
-o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
-o.rewrite_option = o.option
-o:depends({ [_n("realms")] = false })
-
-o = s:option(Value, _n("hop_interval"), translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5."))
-o.datatype = "or(uinteger,portrange)"
-o.placeholder = "30"
-o.default = "30"
-o.rewrite_option = o.option
-o:depends({ [_n("realms")] = false })
-
-o = s:option(Flag, _n("realms"), translate("Realms"))
-o.default = "0"
-o.rewrite_option = o.option
-
-o = s:option(Value, _n("realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
-o.rewrite_option = o.option
-o:depends({ [_n("realms")] = "1" })
-o.validate = function(self, value)
- value = api.trim(value)
- local realm = api.parse_realm_uri(value)
- if realm then return value end
- return nil, translate("Invalid Realm URL.")
-end
-
-o = s:option(DynamicList, _n("realm_stun"), translate("Realm STUN"))
-o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
-o.rewrite_option = o.option
-o:depends({ [_n("realms")] = "1" })
-
-o = s:option(Value, _n("auth_password"), translate("Auth Password"))
-o.password = true
-o.rewrite_option = o.option
-
-o = s:option(ListValue, _n("obfs_type"), translate("Obfs Type"))
-o:value("", translate("Disable"))
-o:value("salamander")
-o:value("gecko")
-o.rewrite_option = o.option
-
-o = s:option(Value, _n("obfs_password"), translate("Obfs Password"))
-o.rewrite_option = o.option
-o:depends({ [_n("obfs_type")] = "salamander" })
-o:depends({ [_n("obfs_type")] = "gecko" })
-
-o = s:option(Value, _n("obfs_MinPacketSize"), translate("Gecko Packet Size (min)"))
-o.datatype = "uinteger"
-o.placeholder = "512"
-o.default = "512"
-o:depends({ [_n("obfs_type")] = "gecko" })
-o.rewrite_option = o.option
-
-o = s:option(Value, _n("obfs_MaxPacketSize"), translate("Gecko Packet Size (max)"))
-o.datatype = "uinteger"
-o.placeholder = "1200"
-o.default = "1200"
-o:depends({ [_n("obfs_type")] = "gecko" })
-o.rewrite_option = o.option
-
-o = s:option(Flag, _n("fast_open"), translate("Fast Open"))
-o.default = "0"
-
-o = s:option(Value, _n("tls_serverName"), translate("Domain"))
-
-o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
-o.default = "0"
-
-o = s:option(Value, _n("tls_pinSHA256"), translate("PinSHA256"),translate("Certificate fingerprint"))
-
-o = s:option(Value, _n("up_mbps"), translate("Max upload Mbps"))
-o.rewrite_option = o.option
-
-o = s:option(Value, _n("down_mbps"), translate("Max download Mbps"))
-o.rewrite_option = o.option
-
-o = s:option(Value, _n("recv_window"), translate("QUIC stream receive window"))
-o.rewrite_option = o.option
-
-o = s:option(Value, _n("recv_window_conn"), translate("QUIC connection receive window"))
-o.rewrite_option = o.option
-
-o = s:option(Value, _n("idle_timeout"), translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
-o.datatype = "range(4,120)"
-o.rewrite_option = o.option
-
-o = s:option(Value, _n("keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
-o.datatype = "range(2,60)"
-o.rewrite_option = o.option
-
-o = s:option(Flag, _n("disable_mtu_discovery"), translate("Disable MTU detection"))
-o.default = "0"
-o.rewrite_option = o.option
-
-o = s:option(Flag, _n("lazy_start"), translate("Lazy Start"))
-o.default = "0"
-o.rewrite_option = o.option
-
-api.luci_types(arg[1], m, s, type_name, option_prefix)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/naive.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/naive.lua
deleted file mode 100644
index b2139ad2c62..00000000000
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/naive.lua
+++ /dev/null
@@ -1,37 +0,0 @@
-local m, s = ...
-
-if not api.is_finded("naive") then
- return
-end
-
-type_name = "Naiveproxy"
-
--- [[ Naive ]]
-
-s.fields["type"]:value(type_name, "NaiveProxy")
-
-if s.val["type"] ~= type_name then
- return
-end
-
-local option_prefix = "naive_"
-
-local function _n(name)
- return option_prefix .. name
-end
-
-o = s:option(ListValue, _n("protocol"), translate("Protocol"))
-o:value("https", translate("HTTPS"))
-o:value("quic", translate("QUIC"))
-
-o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
-
-o = s:option(Value, _n("port"), translate("Port"))
-o.datatype = "port"
-
-o = s:option(Value, _n("username"), translate("Username"))
-
-o = s:option(Value, _n("password"), translate("Password"))
-o.password = true
-
-api.luci_types(arg[1], m, s, type_name, option_prefix)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
deleted file mode 100644
index 308a88cea9e..00000000000
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
+++ /dev/null
@@ -1,900 +0,0 @@
-local m, s = ...
-
-if not api.finded_com("xray") then
- return
-end
-
-local jsonc = api.jsonc
-
-type_name = "Xray"
-
--- [[ Xray ]]
-
-s.fields["type"]:value(type_name, "Xray")
-if not s.fields["type"].default then
- s.fields["type"].default = type_name
-end
-
-if s.val["type"] ~= type_name then
- return
-end
-
-local option_prefix = "xray_"
-
-local function _n(name)
- return option_prefix .. name
-end
-
-local formvalue_proto = luci.http.formvalue(formvalue_key .. _n("protocol"))
-
-if formvalue_proto then s.val["protocol"] = formvalue_proto end
-
-local arg_select_proto = luci.http.formvalue("select_proto") or ""
-
-local ss_method_list = {
- "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305", "xchacha20-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
-}
-
-local security_list = { "auto", "aes-128-gcm", "chacha20-poly1305" }
-
-local header_type_list = {
- "none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
-}
-
-local xray_version = api.get_app_version("xray")
-
-o = s:option(ListValue, _n("protocol"), translate("Protocol"))
-o:value("socks", translate("Socks"))
-o:value("http", translate("HTTP"))
-o:value("vmess", translate("Vmess"))
-o:value("vless", translate("VLESS"))
-o:value("shadowsocks", translate("Shadowsocks"))
-o:value("trojan", translate("Trojan"))
-o:value("wireguard", translate("WireGuard"))
-if api.compare_versions(xray_version, ">=", "26.1.13") then
- o:value("hysteria2", translate("Hysteria2"))
-end
-if api.compare_versions(xray_version, ">=", "1.8.12") then
- o:value("_balancing", translate("Balancing"))
-end
-o:value("_shunt", translate("Shunt"))
-o:value("_iface", translate("Custom Interface"))
-function o.custom_cfgvalue(self, section)
- if arg_select_proto ~= "" then
- return arg_select_proto
- else
- return m:get(section, self.option:sub(1 + #option_prefix))
- end
-end
-
-local load_balancing_options = s.val["protocol"] == "_balancing" or arg_select_proto == "_balancing"
-local load_shunt_options = s.val["protocol"] == "_shunt" or arg_select_proto == "_shunt"
-local load_iface_options = s.val["protocol"] == "_iface" or arg_select_proto == "_iface"
-local load_normal_options = true
-if load_balancing_options or load_shunt_options or load_iface_options then
- load_normal_options = nil
-end
-if not arg_select_proto:find("_") then
- load_normal_options = true
-end
-
-local netdev_list = api.get_network_devices()
-local node_list = api.get_node_list()
-local fallback_list = {}
-local is_balancer = nil
-for k, e in ipairs(node_list.balancing_list or {}) do
- if e.id ~= arg[1] then
- fallback_list[#fallback_list + 1] = {
- id = e["id"],
- remark = e["remark"],
- group = e["group"],
- fallback = e.o["fallback_node"],
- }
- else
- is_balancer = true
- end
-end
-
-if load_balancing_options then -- [[ Load balancing Start ]]
- o = s:option(ListValue, _n("node_add_mode"), translate("Node Addition Method"))
- o:depends({ [_n("protocol")] = "_balancing" })
- o.default = "manual"
- o:value("manual", translate("Manual"))
- o:value("batch", translate("Batch"))
-
- o = s:option(MultiValue, _n("balancing_node"), translate("Load balancing node list"), translate("Load balancing node list, document"))
- o:depends({ [_n("node_add_mode")] = "manual" })
- o.widget = "checkbox"
- o.template = appname .. "/cbi/nodes_multivalue"
- o.group = {}
- for k1, v1 in pairs(node_list) do
- if k1 == "socks_list" or k1 == "normal_list" then
- for i, v in ipairs(v1) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = v.group or ""
- end
- end
- end
- -- 读取旧 DynamicList
- function o.custom_cfgvalue(self, section)
- return table.concat(m:get(section, "balancing_node") or {}, " ")
- end
- -- 写入保持 DynamicList
- function o.custom_write(self, section, value)
- local old = m:get(section, "balancing_node") or {}
- local new, set = {}, {}
- for v in value:gmatch("%S+") do
- new[#new + 1] = v
- set[v] = 1
- end
- for _, v in ipairs(old) do
- if not set[v] then
- m:set(section, "balancing_node", new)
- return
- end
- set[v] = nil
- end
- for _ in pairs(set) do
- m:set(section, "balancing_node", new)
- return
- end
- end
-
- o = s:option(MultiValue, _n("node_group"), translate("Select Group"))
- o:depends({ [_n("node_add_mode")] = "batch" })
- o.widget = "checkbox"
- o:value("default", translate("default"))
- for k, v in pairs(groups) do
- o:value(api.UrlEncode(k), k)
- end
-
- o = s:option(Value, _n("node_match_rule"), translate("Node Matching Rules"))
- o:depends({ [_n("node_add_mode")] = "batch" })
- local descrStr = "Example: ^A && B && !C && D$
"
- descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).
"
- descrStr = descrStr .. "Conditions are joined by && (AND), and their order does not affect the result.
"
- descrStr = descrStr .. "Multiple groups can be separated by || (OR), matching succeeds if any group matches.
"
- descrStr = descrStr .. "Example: A && B || C && D means (A AND B) OR (C AND D)."
- o.description = translate(descrStr)
-
- o = s:option(ListValue, _n("balancingStrategy"), translate("Balancing Strategy"))
- o:depends({ [_n("protocol")] = "_balancing" })
- o:value("random")
- o:value("roundRobin")
- o:value("leastPing")
- o:value("leastLoad")
- o.default = "random"
-
- -- Fallback Node
- o = s:option(ListValue, _n("fallback_node"), translate("Fallback Node"))
- o.group = {"",""}
- o:value("", translate("Close(Not use)"))
- o:value("_direct", translate("Direct Connection"))
- o:depends({ [_n("protocol")] = "_balancing" })
- o.template = appname .. "/cbi/nodes_listvalue"
- -- 最大 fallback 套娃层数
- local MAX_FALLBACK_DEPTH = 3
- -- 检查是否会形成回环
- local function will_loop(start_id, target_id, depth)
- depth = depth or 0
- -- 超过最大深度后停止递归
- if depth >= MAX_FALLBACK_DEPTH then
- return false
- end
- for _, v in ipairs(fallback_list) do
- if v.id == target_id then
- local fb = v.fallback
- -- 没有 fallback
- if not fb or fb == "" or fb == "_direct" then
- return false
- end
- -- 检测到回环
- if fb == start_id then
- return true
- end
- -- 继续递归检查
- return will_loop(start_id, fb, depth + 1)
- end
- end
- return false
- end
- -- 获取 fallback 链深度
- local function get_fallback_depth(id, depth)
- depth = depth or 0
- if depth >= MAX_FALLBACK_DEPTH then
- return depth
- end
- for _, v in ipairs(fallback_list) do
- if v.id == id then
- local fb = v.fallback
- if not fb or fb == "" or fb == "_direct" then
- return depth
- end
- return get_fallback_depth(fb, depth + 1)
- end
- end
- return depth
- end
- for _, v in ipairs(fallback_list) do
- local depth = get_fallback_depth(v.id)
- -- 超过最大套娃层数后,不允许继续选择 balancer
- if depth < MAX_FALLBACK_DEPTH
- and not will_loop(arg[1], v.id)
- then
- o:value(v.id, v.remark)
- o.group[#o.group + 1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- end
- for k1, v1 in pairs(node_list) do
- if k1 == "socks_list" or k1 == "normal_list" or k1 == "urltest_list" then
- for i, v in ipairs(v1) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- end
- end
-
- -- 探测地址
- o = s:option(Flag, _n("useCustomProbeUrl"), translate("Use Custom Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL."))
- o:depends({ [_n("protocol")] = "_balancing" })
-
- o = s:option(Value, _n("probeUrl"), translate("Probe URL"))
- o:depends({ [_n("useCustomProbeUrl")] = true })
- o:value("https://cp.cloudflare.com/", "Cloudflare")
- o:value("https://www.gstatic.com/generate_204", "Gstatic")
- o:value("https://www.google.com/generate_204", "Google")
- o:value("https://www.youtube.com/generate_204", "YouTube")
- o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)")
- o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)")
- o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
- o.default = o.keylist[3]
- o.description = translate("The URL used to detect the connection status.")
-
- -- 探测间隔
- o = s:option(Value, _n("probeInterval"), translate("Probe Interval"))
- o:depends({ [_n("protocol")] = "_balancing" })
- o.default = "1m"
- o.placeholder = "1m"
- o.description = translate("The interval between initiating probes.") .. "
" ..
- translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" ..
- translate("When the unit is not filled in, it defaults to seconds.")
-
- o = s:option(Value, _n("expected"), translate("Preferred Node Count"))
- o:depends({ [_n("balancingStrategy")] = "leastLoad" })
- o.datatype = "uinteger"
- o.default = "2"
- o.placeholder = "2"
- o.description = translate("The load balancer selects the optimal number of nodes, and traffic is randomly distributed among them.")
-
- o = s:option(Value, _n("tolerance"), translate("Failure Tolerance (%)"))
- o:depends({ [_n("balancingStrategy")] = "leastLoad" })
- o.datatype = "uinteger"
- o.default = "10"
- o.placeholder = "10"
- o.description = translate("The maximum acceptable speed test failure rate. For example, 1 means allowing a 1% failure rate.")
-end -- [[ 负载均衡 End ]]
-
-if load_iface_options then -- [[ 自定义接口 Start ]]
- o = s:option(Value, _n("iface"), translate("Interface"))
- o:depends({ [_n("protocol")] = "_iface" })
- for _, d in ipairs(netdev_list) do
- o:value(d.name, d.label)
- end
-end -- [[ 自定义接口 End ]]
-
-
-if load_normal_options then
-
-o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
-
-o = s:option(Value, _n("port"), translate("Port"))
-o.datatype = "port"
-
-o = s:option(Value, _n("uuid"), translate("ID"))
-o.password = true
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless" })
-
-o = s:option(Value, _n("username"), translate("Username"))
-o:depends({ [_n("protocol")] = "http" })
-o:depends({ [_n("protocol")] = "socks" })
-
-o = s:option(Value, _n("password"), translate("Password"))
-o.password = true
-o:depends({ [_n("protocol")] = "http" })
-o:depends({ [_n("protocol")] = "socks" })
-o:depends({ [_n("protocol")] = "shadowsocks" })
-o:depends({ [_n("protocol")] = "trojan" })
-
-o = s:option(ListValue, _n("security"), translate("Encrypt Method"))
-for a, t in ipairs(security_list) do o:value(t) end
-o:depends({ [_n("protocol")] = "vmess" })
-
-o = s:option(Value, _n("encryption"), translate("Encrypt Method") .. " (encryption)")
-o.default = "none"
-o.placeholder = "none"
-o:depends({ [_n("protocol")] = "vless" })
-o.validate = function(self, value)
- value = api.trim(value)
- return (value == "" and "none" or value)
-end
-
-o = s:option(ListValue, _n("ss_method"), translate("Encrypt Method"))
-o.rewrite_option = "method"
-for a, t in ipairs(ss_method_list) do o:value(t) end
-o:depends({ [_n("protocol")] = "shadowsocks" })
-
-o = s:option(ListValue, _n("flow"), translate("flow"))
-o.default = ""
-o:value("", translate("Disable"))
-o:value("xtls-rprx-vision")
-o:value("xtls-rprx-vision-udp443")
-o:depends({ [_n("protocol")] = "vless" })
-
----- [[hysteria2]]
-o = s:option(Value, _n("hysteria2_hop"), translate("Port hopping range"))
-o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
-o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
-
-o = s:option(Value, _n("hysteria2_hop_interval"), translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5."))
-o.datatype = "or(uinteger,portrange)"
-o.placeholder = "30"
-o.default = "30"
-o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
-
-o = s:option(Flag, _n("hysteria2_realms"), translate("Realms"))
-o.default = "0"
-if api.compare_versions(xray_version, ">", "26.5.9") then
- o:depends({ [_n("protocol")] = "hysteria2"})
-else
- o:depends({ [_n("protocol")] = "__hide"})
-end
-
-o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
-o:depends({ [_n("hysteria2_realms")] = "1" })
-o.validate = function(self, value)
- value = api.trim(value)
- local realm = api.parse_realm_uri(value)
- if realm then return value end
- return nil, translate("Invalid Realm URL.")
-end
-
-o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
-o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
-o:depends({ [_n("hysteria2_realms")] = "1" })
-
-o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password"))
-o.password = true
-o:depends({ [_n("protocol")] = "hysteria2"})
-
-o = s:option(ListValue, _n("hysteria2_obfs_type"), translate("Obfs Type"))
-o:value("", translate("Disable"))
-o:value("salamander")
-o:value("gecko")
-o:depends({ [_n("protocol")] = "hysteria2" })
-
-o = s:option(Value, _n("hysteria2_obfs_password"), translate("Obfs Password"))
-o:depends({ [_n("hysteria2_obfs_type")] = "salamander" })
-o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
-
-o = s:option(Value, _n("hysteria2_obfs_MinPacketSize"), translate("Gecko Packet Size (min)"))
-o.datatype = "uinteger"
-o.placeholder = "512"
-o.default = "512"
-o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
-
-o = s:option(Value, _n("hysteria2_obfs_MaxPacketSize"), translate("Gecko Packet Size (max)"))
-o.datatype = "uinteger"
-o.placeholder = "1200"
-o.default = "1200"
-o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
-
-o = s:option(Value, _n("hysteria2_up_mbps"), translate("Max upload Mbps"))
-o:depends({ [_n("protocol")] = "hysteria2" })
-
-o = s:option(Value, _n("hysteria2_down_mbps"), translate("Max download Mbps"))
-o:depends({ [_n("protocol")] = "hysteria2" })
-
-o = s:option(Value, _n("hysteria2_idle_timeout"), translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
-o.datatype = "range(4,120)"
-o:depends({ [_n("protocol")] = "hysteria2"})
-
-o = s:option(Value, _n("hysteria2_keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
-o.datatype = "range(2,60)"
-o:depends({ [_n("protocol")] = "hysteria2"})
-
-o = s:option(Flag, _n("hysteria2_disable_mtu_discovery"), translate("Disable MTU detection"))
-o.default = "0"
-o:depends({ [_n("protocol")] = "hysteria2"})
----- [[hysteria2 end]]
-
-o = s:option(Flag, _n("tls"), translate("TLS"))
-o.default = 0
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless" })
-o:depends({ [_n("protocol")] = "trojan" })
-o:depends({ [_n("protocol")] = "shadowsocks" })
-
-o = s:option(Flag, _n("reality"), translate("REALITY"), translate("Only recommend to use with VLESS-TCP-XTLS-Vision."))
-o.default = 0
-o:depends({ [_n("tls")] = true, [_n("transport")] = "raw" })
-o:depends({ [_n("tls")] = true, [_n("transport")] = "ws" })
-o:depends({ [_n("tls")] = true, [_n("transport")] = "grpc" })
-o:depends({ [_n("tls")] = true, [_n("transport")] = "httpupgrade" })
-o:depends({ [_n("tls")] = true, [_n("transport")] = "xhttp" })
-
-o = s:option(ListValue, _n("alpn"), translate("alpn"))
-o.default = "default"
-o:value("default", translate("Default"))
-o:value("h3")
-o:value("h2")
-o:value("h3,h2")
-o:value("http/1.1")
-o:value("h2,http/1.1")
-o:value("h3,h2,http/1.1")
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-
--- o = s:option(Value, _n("minversion"), translate("minversion"))
--- o.default = "1.3"
--- o:value("1.3")
--- o:depends({ [_n("tls")] = true })
-
-o = s:option(Value, _n("tls_serverName"), "SNI " .. translate("Domain"))
-o:depends({ [_n("tls")] = true })
-o:depends({ [_n("protocol")] = "hysteria2" })
-
-o = s:option(Value, _n("tls_pinSHA256"), translate("TLS Chain Fingerprint (SHA256)"))
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-o:depends({ [_n("protocol")] = "hysteria2" })
-o.description = translate("Once set, connects only when the server’s chain fingerprint matches.") ..
- string.format("%s", "→ " .. translate("Fetch Manually"))
-
-o = s:option(Value, _n("tls_CertByName"), translate("TLS Certificate Name (CertName)"), translate("TLS is used to verify the leaf certificate name."))
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-o:depends({ [_n("protocol")] = "hysteria2" })
-
-o = s:option(Flag, _n("tls_certificate"), translate("TLS Certificate (PEM)"))
-o.default = "0"
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-o:depends({ [_n("protocol")] = "hysteria2" })
-
-o = s:option(TextValue, _n("tls_certificate_pem"), " ", translate("Full certificate (chain), PEM format."))
-o.default = ""
-o.rows = 5
-o.wrap = "off"
-o:depends({ [_n("tls_certificate")] = true })
-o.validate = function(self, value)
- value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
- return value
-end
-
-o = s:option(Flag, _n("ech"), translate("ECH"))
-o.default = "0"
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
-
-o = s:option(TextValue, _n("ech_config"), translate("ECH Config"))
-o.default = ""
-o.rows = 5
-o.wrap = "soft"
-o:depends({ [_n("ech")] = true })
-o.validate = function(self, value)
- return api.trim(value:gsub("[\r\n]", ""))
-end
-
--- [[ REALITY部分 ]] --
-o = s:option(Value, _n("reality_publicKey"), translate("Public Key"))
-o:depends({ [_n("tls")] = true, [_n("reality")] = true })
-
-o = s:option(Value, _n("reality_shortId"), translate("Short Id"))
-o:depends({ [_n("tls")] = true, [_n("reality")] = true })
-
-o = s:option(Value, _n("reality_spiderX"), translate("Spider X"))
-o.placeholder = "/"
-o:depends({ [_n("tls")] = true, [_n("reality")] = true })
-
-o = s:option(Flag, _n("utls"), translate("uTLS"))
-o.default = "0"
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-
-o = s:option(ListValue, _n("fingerprint"), translate("Finger Print"))
-o:value("chrome")
-o:value("firefox")
-o:value("edge")
-o:value("safari")
-o:value("360")
-o:value("qq")
-o:value("ios")
-o:value("android")
-o:value("random")
-o:value("randomized")
-o:value("unsafe")
-o.default = "chrome"
-o:depends({ [_n("tls")] = true, [_n("utls")] = true })
-o:depends({ [_n("tls")] = true, [_n("reality")] = true })
-
-o = s:option(Value, _n("cipherSuites"), translate("Cipher Suites"), '***' .. " " .. translate("Configures the list of supported cipher suites, separated by :"))
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-
-o = s:option(Flag, _n("use_mldsa65Verify"), translate("ML-DSA-65"))
-o.default = "0"
-o:depends({ [_n("tls")] = true, [_n("reality")] = true })
-
-o = s:option(TextValue, _n("reality_mldsa65Verify"), "ML-DSA-65 " .. translate("Public key"))
-o.default = ""
-o.rows = 5
-o.wrap = "soft"
-o:depends({ [_n("use_mldsa65Verify")] = true })
-o.validate = function(self, value)
- return api.trim(value:gsub("[\r\n]", ""))
-end
-
-o = s:option(ListValue, _n("transport"), translate("Transport"))
-o:value("raw", "RAW (TCP)")
-o:value("mkcp", "mKCP")
-o:value("ws", "WebSocket")
-o:value("grpc", "gRPC")
-o:value("httpupgrade", "HttpUpgrade")
-o:value("xhttp", "XHTTP")
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless" })
-o:depends({ [_n("protocol")] = "shadowsocks" })
-o:depends({ [_n("protocol")] = "trojan" })
-
-o = s:option(Value, _n("wireguard_public_key"), translate("Public Key"))
-o:depends({ [_n("protocol")] = "wireguard" })
-
-o = s:option(Value, _n("wireguard_secret_key"), translate("Private Key"))
-o:depends({ [_n("protocol")] = "wireguard" })
-
-o = s:option(Value, _n("wireguard_preSharedKey"), translate("Pre shared key"))
-o:depends({ [_n("protocol")] = "wireguard" })
-
-o = s:option(DynamicList, _n("wireguard_local_address"), translate("Local Address"))
-o:depends({ [_n("protocol")] = "wireguard" })
-
-o = s:option(Value, _n("wireguard_mtu"), translate("MTU"))
-o.default = "1420"
-o:depends({ [_n("protocol")] = "wireguard" })
-
-if api.compare_versions(xray_version, ">=", "1.8.0") then
- o = s:option(Value, _n("wireguard_reserved"), translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
- o:depends({ [_n("protocol")] = "wireguard" })
-end
-
-o = s:option(Value, _n("wireguard_keepAlive"), translate("Keep Alive"))
-o.default = "0"
-o:depends({ [_n("protocol")] = "wireguard" })
-
--- [[ RAW部分 ]]--
-
--- TCP伪装
-o = s:option(ListValue, _n("tcp_guise"), translate("Camouflage Type"))
-o:value("none", "none")
-o:value("http", "http")
-o:depends({ [_n("transport")] = "raw" })
-
--- HTTP域名
-o = s:option(DynamicList, _n("tcp_guise_http_host"), translate("HTTP Host"))
-o:depends({ [_n("tcp_guise")] = "http" })
-
--- HTTP路径
-o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path"))
-o.placeholder = "/"
-o:depends({ [_n("tcp_guise")] = "http" })
-
--- [[ mKCP部分 ]]--
-
-o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)
dns: Disguising traffic as DNS requests.'))
-for a, t in ipairs(header_type_list) do o:value(t) end
-o:depends({ [_n("transport")] = "mkcp" })
-
-o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
-o:depends({ [_n("mkcp_guise")] = "dns" })
-
-o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
-o.datatype = "uinteger"
-o.default = 1350
-o:depends({ [_n("transport")] = "mkcp" })
-
-o = s:option(Value, _n("mkcp_seed"), translate("KCP Seed"))
-o:depends({ [_n("transport")] = "mkcp" })
-
--- [[ WebSocket部分 ]]--
-o = s:option(Value, _n("ws_host"), translate("WebSocket Host"))
-o:depends({ [_n("transport")] = "ws" })
-
-o = s:option(Value, _n("ws_path"), translate("WebSocket Path"))
-o.placeholder = "/"
-o:depends({ [_n("transport")] = "ws" })
-
-o = s:option(Value, _n("ws_heartbeatPeriod"), translate("HeartbeatPeriod(second)"))
-o.datatype = "integer"
-o:depends({ [_n("transport")] = "ws" })
-
--- [[ gRPC部分 ]]--
-o = s:option(Value, _n("grpc_serviceName"), "ServiceName")
-o:depends({ [_n("transport")] = "grpc" })
-
-o = s:option(ListValue, _n("grpc_mode"), "gRPC " .. translate("Transfer mode"))
-o:value("gun")
-o:value("multi")
-o:depends({ [_n("transport")] = "grpc" })
-
-o = s:option(Flag, _n("grpc_health_check"), translate("Health check"))
-o:depends({ [_n("transport")] = "grpc" })
-
-o = s:option(Value, _n("grpc_idle_timeout"), translate("Idle timeout"))
-o.default = "10"
-o:depends({ [_n("grpc_health_check")] = true })
-
-o = s:option(Value, _n("grpc_health_check_timeout"), translate("Health check timeout"))
-o.default = "20"
-o:depends({ [_n("grpc_health_check")] = true })
-
-o = s:option(Flag, _n("grpc_permit_without_stream"), translate("Permit without stream"))
-o.default = "0"
-o:depends({ [_n("grpc_health_check")] = true })
-
-o = s:option(Value, _n("grpc_initial_windows_size"), translate("Initial Windows Size"))
-o.default = "0"
-o:depends({ [_n("transport")] = "grpc" })
-
--- [[ HttpUpgrade部分 ]]--
-o = s:option(Value, _n("httpupgrade_host"), translate("HttpUpgrade Host"))
-o:depends({ [_n("transport")] = "httpupgrade" })
-
-o = s:option(Value, _n("httpupgrade_path"), translate("HttpUpgrade Path"))
-o.placeholder = "/"
-o:depends({ [_n("transport")] = "httpupgrade" })
-
--- [[ XHTTP部分 ]]--
-o = s:option(ListValue, _n("xhttp_mode"), "XHTTP " .. translate("Mode"))
-o:depends({ [_n("transport")] = "xhttp" })
-o.default = "auto"
-o:value("auto")
-o:value("packet-up")
-o:value("stream-up")
-o:value("stream-one")
-
-o = s:option(Value, _n("xhttp_host"), translate("XHTTP Host"))
-o:depends({ [_n("transport")] = "xhttp" })
-
-o = s:option(Value, _n("xhttp_path"), translate("XHTTP Path"))
-o.placeholder = "/"
-o:depends({ [_n("transport")] = "xhttp" })
-
-o = s:option(Flag, _n("use_xhttp_extra"), translate("XHTTP Extra"))
-o.default = "0"
-o:depends({ [_n("transport")] = "xhttp" })
-
-o = s:option(TextValue, _n("xhttp_extra"), " ", translate("An XHttpObject in JSON format, used for sharing."))
-o:depends({ [_n("use_xhttp_extra")] = true })
-o.rows = 10
-o.wrap = "off"
-o.datatype = "json"
-local o_validate = o.validate
-o.validate = function(self, value)
- value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
- local v = o_validate(self, value)
- if v then return v end
- return nil, "XHTTP Extra " .. translate("Must be JSON text!")
-end
-o.custom_cfgvalue = function(self, section, value)
- local raw = m:get(section, "xhttp_extra")
- if raw then
- return api.base64Decode(raw)
- end
-end
-o.custom_write = function(self, section, value)
- m:set(section, "xhttp_extra", api.base64Encode(value) or "")
- local success, data = pcall(jsonc.parse, value)
- if success and data then
- local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address)
- or (data.downloadSettings and data.downloadSettings.address)
- if address and address ~= "" then
- address = address:gsub("^%[", ""):gsub("%]$", "")
- m:set(section, "download_address", address)
- else
- m:del(section, "download_address")
- end
- else
- m:del(section, "download_address")
- end
-end
-o.custom_remove = function(self, section, value)
- m:del(section, "xhttp_extra")
- m:del(section, "download_address")
-end
-
--- [[ User-Agent ]]--
-o = s:option(Value, _n("user_agent"), translate("User-Agent"))
-o.default = ""
-o:value("", translate("default"))
-o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36", "chrome")
-o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0", "firefox")
-o:value("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15", "safari")
-o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70", "edge")
-o:value("Go-http-client/1.1", "golang")
-o:value("curl/7.68.0", "curl")
-o:depends({ [_n("tcp_guise")] = "http" })
-o:depends({ [_n("transport")] = "ws" })
-o:depends({ [_n("transport")] = "httpupgrade" })
-o:depends({ [_n("transport")] = "xhttp" })
-o:depends({ [_n("transport")] = "grpc" })
-
--- [[ Mux.Cool ]]--
-o = s:option(Flag, _n("mux"), "Mux", translate("Enable Mux.Cool"))
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "raw" })
-o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "ws" })
-o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "grpc" })
-o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "httpupgrade" })
-o:depends({ [_n("protocol")] = "shadowsocks" })
-o:depends({ [_n("protocol")] = "trojan" })
-
-o = s:option(Value, _n("mux_concurrency"), translate("Mux concurrency"))
-o.default = -1
-o:depends({ [_n("mux")] = true })
-
-o = s:option(Value, _n("xudp_concurrency"), translate("XUDP Mux concurrency"))
-o.default = 8
-o:depends({ [_n("mux")] = true })
-
---[[FinalMask]]
-o = s:option(Flag, _n("use_finalmask"), "FinalMask")
-o.default = "0"
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless" })
-o:depends({ [_n("protocol")] = "trojan" })
-o:depends({ [_n("protocol")] = "shadowsocks" })
-o:depends({ [_n("protocol")] = "wireguard" })
-o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
-
-o = s:option(TextValue, _n("finalmask"), " ")
-o:depends({ [_n("use_finalmask")] = true })
-o.rows = 10
-o.wrap = "off"
-o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "
" ..
- translate("Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings.")
-o.datatype = "json"
-local o_validate = o.validate
-o.validate = function(self, value)
- value = api.trim(value):gsub("\r\n", "\n"):gsub("^[ \t]*\n", ""):gsub("\n[ \t]*$", ""):gsub("\n[ \t]*\n", "\n")
- local v = o_validate(self, value)
- if v then return v end
- return nil, "FinalMask " .. translate("Must be JSON text!")
-end
-o.custom_cfgvalue = function(self, section, value)
- local raw = m:get(section, "finalmask")
- if raw then
- return api.base64Decode(raw)
- end
-end
-o.custom_write = function(self, section, value)
- m:set(section, "finalmask", api.base64Encode(value) or "")
-end
-
---[[Fast Open]]
-o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
-o.default = 0
-
---[[tcpMptcp]]
-o = s:option(Flag, _n("tcpMptcp"), "tcpMptcp", translate("Enable Multipath TCP, need to be enabled in both server and client configuration."))
-o.default = 0
-
-o = s:option(ListValue, _n("domain_resolver"), translate("Domain DNS Resolve"))
-o.description = translate("If the node address is a domain name, this DNS will be used for resolution.") .. "
" .. string.format('%s',
- translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead."))
-o:value("", translate("Auto"))
-o:value("tcp", "TCP")
-o:value("udp", "UDP")
-o:value("https", "DoH")
-
-o = s:option(Value, _n("domain_resolver_dns"), "DNS")
-o.datatype = "or(ipaddr,ipaddrport)"
-o:value("114.114.114.114")
-o:value("223.5.5.5:53")
-o.default = o.keylist[1]
-o:depends({ [_n("domain_resolver")] = "tcp" })
-o:depends({ [_n("domain_resolver")] = "udp" })
-
-o = s:option(Value, _n("domain_resolver_dns_https"), "DNS")
-o:value("https://120.53.53.53/dns-query", "DNSPod")
-o:value("https://223.5.5.5/dns-query", "AliDNS")
-o.default = o.keylist[1]
-o:depends({ [_n("domain_resolver")] = "https" })
-
-o = s:option(ListValue, _n("domain_strategy"), translate("Domain Strategy"), translate("If is domain name, The requested domain name will be resolved to IP before connect."))
-o.default = ""
-o:value("", translate("Auto"))
-o:value("UseIPv4v6", translate("Prefer IPv4"))
-o:value("UseIPv6v4", translate("Prefer IPv6"))
-o:value("UseIPv4", translate("IPv4 Only"))
-o:value("UseIPv6", translate("IPv6 Only"))
-
-o = s:option(Flag, _n("happy_eyeballs"), translate("Enable Happy Eyeballs"), translate("Attempts IPv4 and IPv6 simultaneously; automatically uses the faster connection."))
-o.default = 0
-
-local protocols = s.fields[_n("protocol")].keylist
-if #protocols > 0 then
- for i, v in ipairs(protocols) do
- if not v:find("^_") then
- local depends_condition = { [_n("protocol")] = v }
- if v == "hysteria2" then
- depends_condition[_n("hysteria2_realms")] = false
- end
- s.fields[_n("address")]:depends(depends_condition)
- s.fields[_n("port")]:depends(depends_condition)
- s.fields[_n("domain_resolver")]:depends(depends_condition)
- s.fields[_n("happy_eyeballs")]:depends(depends_condition)
-
- local strategy_depends = api.clone(depends_condition)
- strategy_depends[_n("happy_eyeballs")] = false
- s.fields[_n("domain_strategy")]:depends(strategy_depends)
-
- if v ~= "hysteria2" then
- s.fields[_n("tcp_fast_open")]:depends({ [_n("protocol")] = v })
- s.fields[_n("tcpMptcp")]:depends({ [_n("protocol")] = v })
- end
- end
- end
-end
-end
--- [[ Normal single node End ]]
-
-if not load_shunt_options then
- o = s:option(ListValue, _n("chain_proxy"), translate("Chain Proxy"))
- o:value("", translate("Close(Not use)"))
- if not (load_iface_options or load_balancing_options) then
- -- Special node cannot be use pre-proxy.
- o:value("1", translate("Preproxy Node"))
- o:value("3", translate("Outbound Interface"))
- end
- o:value("2", translate("Landing Node"))
-
- o1 = s:option(ListValue, _n("preproxy_node"), translate("Preproxy Node"), translate("Only support a layer of proxy."))
- o1:depends({ [_n("chain_proxy")] = "1", [_n("hysteria2_realms")] = false })
- o1.template = appname .. "/cbi/nodes_listvalue"
- o1.group = {}
-
- o3 = s:option(Value, _n("outbound_iface"), translate("Outbound Interface"))
- o3:depends({ [_n("chain_proxy")] = "3" })
- o3:value("", translate("All"))
- for _, d in ipairs(netdev_list) do
- o3:value(d.name, d.label)
- end
-
- o2 = s:option(ListValue, _n("to_node"), translate("Landing Node"), translate("Only support a layer of proxy."))
- o2:depends({ [_n("chain_proxy")] = "2", [_n("hysteria2_realms")] = false })
- o2.template = appname .. "/cbi/nodes_listvalue"
- o2.group = {}
-
- for k1, v1 in pairs(node_list) do
- if k1 ~= "shunt_list" and k1 ~= "iface_list" then
- for i, v in ipairs(v1) do
- if v.id ~= arg[1] then
- o1:value(v.id, v.remark)
- o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- if k1 == "normal_list" then
- -- Landing Node not support use special node.
- o2:value(v.id, v.remark)
- o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- end
- end
- end
- end
-end
-
-api.luci_types(arg[1], m, s, type_name, option_prefix)
-
-if load_shunt_options then
- local current_node = m.uci:get_all(appname, arg[1]) or {}
- local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
- setfenv(shunt_lua, getfenv(1))(m, s, {
- node_id = arg[1],
- node = current_node,
- node_list = node_list,
- })
-end
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua
deleted file mode 100644
index db8ed2696f3..00000000000
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua
+++ /dev/null
@@ -1,921 +0,0 @@
-local m, s = ...
-
-local singbox_bin = api.finded_com("sing-box")
-
-if not singbox_bin then
- return
-end
-
-type_name = "sing-box"
-
--- [[ sing-box ]]
-
-s.fields["type"]:value(type_name, "Sing-Box")
-if not s.fields["type"].default then
- s.fields["type"].default = type_name
-end
-
-if s.val["type"] ~= type_name then
- return
-end
-
-local option_prefix = "singbox_"
-
-local function _n(name)
- return option_prefix .. name
-end
-
-local formvalue_proto = luci.http.formvalue(formvalue_key .. _n("protocol"))
-
-if formvalue_proto then s.val["protocol"] = formvalue_proto end
-
-local arg_select_proto = luci.http.formvalue("select_proto") or ""
-
-local ss_method_new_list = {
- "none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
-}
-
-local ss_method_old_list = {
- "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "rc4-md5", "chacha20-ietf", "xchacha20",
-}
-
-local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" }
-
-local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'")
-
-local singbox_version = api.get_app_version("sing-box"):match("[^v]+")
-
-o = s:option(ListValue, _n("protocol"), translate("Protocol"))
-o:value("socks", "Socks")
-o:value("http", "HTTP")
-o:value("shadowsocks", "Shadowsocks")
-o:value("vmess", "Vmess")
-o:value("trojan", "Trojan")
-if singbox_tags:find("with_wireguard") then
- o:value("wireguard", "WireGuard")
-end
-if singbox_tags:find("with_quic") then
- o:value("hysteria", "Hysteria")
-end
-o:value("vless", "VLESS")
-if singbox_tags:find("with_quic") then
- o:value("tuic", "TUIC")
-end
-if singbox_tags:find("with_quic") then
- o:value("hysteria2", "Hysteria2")
-end
-o:value("anytls", "AnyTLS")
-o:value("ssh", "SSH")
-if singbox_tags:find("with_naive_outbound") then
- o:value("naive", "NaïveProxy")
-end
-o:value("_urltest", translate("URLTest"))
-o:value("_shunt", translate("Shunt"))
-o:value("_iface", translate("Custom Interface"))
-function o.custom_cfgvalue(self, section)
- if arg_select_proto ~= "" then
- return arg_select_proto
- else
- return m:get(section, self.option:sub(1 + #option_prefix))
- end
-end
-
-local load_urltest_options = s.val["protocol"] == "_urltest" or arg_select_proto == "_urltest"
-local load_shunt_options = s.val["protocol"] == "_shunt" or arg_select_proto == "_shunt"
-local load_iface_options = s.val["protocol"] == "_iface" or arg_select_proto == "_iface"
-local load_normal_options = true
-if load_urltest_options or load_shunt_options or load_iface_options then
- load_normal_options = nil
-end
-if not arg_select_proto:find("_") then
- load_normal_options = true
-end
-
-local netdev_list = api.get_network_devices()
-local node_list = api.get_node_list()
-
-if load_urltest_options then -- [[ URLTest Start ]]
- o = s:option(ListValue, _n("node_add_mode"), translate("Node Addition Method"))
- o:depends({ [_n("protocol")] = "_urltest" })
- o.default = "manual"
- o:value("manual", translate("Manual"))
- o:value("batch", translate("Batch"))
-
- o = s:option(MultiValue, _n("urltest_node"), translate("URLTest node list"), translate("List of nodes to test, document"))
- o:depends({ [_n("node_add_mode")] = "manual" })
- o.widget = "checkbox"
- o.template = appname .. "/cbi/nodes_multivalue"
- o.group = {}
- for k1, v1 in pairs(node_list) do
- if k1 == "socks_list" or k1 == "normal_list" then
- for i, v in ipairs(v1) do
- o:value(v.id, v.remark)
- o.group[#o.group+1] = v.group or ""
- end
- end
- end
- -- 读取旧 DynamicList
- function o.custom_cfgvalue(self, section)
- return table.concat(m:get(section, "urltest_node") or {}, " ")
- end
- -- 写入保持 DynamicList
- function o.custom_write(self, section, value)
- local old = m:get(section, "urltest_node") or {}
- local new, set = {}, {}
- for v in value:gmatch("%S+") do
- new[#new + 1] = v
- set[v] = 1
- end
- for _, v in ipairs(old) do
- if not set[v] then
- m:set(section, "urltest_node", new)
- return
- end
- set[v] = nil
- end
- for _ in pairs(set) do
- m:set(section, "urltest_node", new)
- return
- end
- end
-
- o = s:option(MultiValue, _n("node_group"), translate("Select Group"))
- o:depends({ [_n("node_add_mode")] = "batch" })
- o.widget = "checkbox"
- o:value("default", translate("default"))
- for k, v in pairs(groups) do
- o:value(api.UrlEncode(k), k)
- end
-
- o = s:option(Value, _n("node_match_rule"), translate("Node Matching Rules"))
- o:depends({ [_n("node_add_mode")] = "batch" })
- local descrStr = "Example: ^A && B && !C && D$
"
- descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).
"
- descrStr = descrStr .. "Conditions are joined by && (AND), and their order does not affect the result.
"
- descrStr = descrStr .. "Multiple groups can be separated by || (OR), matching succeeds if any group matches.
"
- descrStr = descrStr .. "Example: A && B || C && D means (A AND B) OR (C AND D)."
- o.description = translate(descrStr)
-
- o = s:option(Value, _n("urltest_url"), translate("Probe URL"))
- o:depends({ [_n("protocol")] = "_urltest" })
- o:value("https://cp.cloudflare.com/", "Cloudflare")
- o:value("https://www.gstatic.com/generate_204", "Gstatic")
- o:value("https://www.google.com/generate_204", "Google")
- o:value("https://www.youtube.com/generate_204", "YouTube")
- o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)")
- o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)")
- o:value("https://wifi.vivo.com.cn/generate_204", "VIVO (CN)")
- o.default = o.keylist[3]
- o.description = translate("The URL used to detect the connection status.")
-
- o = s:option(Value, _n("urltest_interval"), translate("Test interval"))
- o:depends({ [_n("protocol")] = "_urltest" })
- o.default = "3m"
- o.placeholder = "3m"
- o.description = translate("The interval between initiating probes.") .. "
" ..
- translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" ..
- translate("When the unit is not filled in, it defaults to seconds.") .. "
" ..
- translate("Test interval must be less or equal than idle timeout.")
-
- o = s:option(Value, _n("urltest_tolerance"), translate("Test tolerance"), translate("The test tolerance in milliseconds."))
- o:depends({ [_n("protocol")] = "_urltest" })
- o.datatype = "uinteger"
- o.placeholder = "50"
- o.default = "50"
-
- o = s:option(Value, _n("urltest_idle_timeout"), translate("Idle timeout"))
- o:depends({ [_n("protocol")] = "_urltest" })
- o.placeholder = "30m"
- o.default = "30m"
- o.description = translate("The idle timeout.") .. "
" ..
- translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" ..
- translate("When the unit is not filled in, it defaults to seconds.")
-
- o = s:option(Flag, _n("urltest_interrupt_exist_connections"), translate("Interrupt existing connections"))
- o:depends({ [_n("protocol")] = "_urltest" })
- o.default = "0"
- o.description = translate("Interrupt existing connections when the selected outbound has changed.")
-end -- [[ URLTest End ]]
-
-if load_iface_options then -- [[ 自定义接口 Start ]]
- o = s:option(Value, _n("iface"), translate("Interface"))
- o:depends({ [_n("protocol")] = "_iface" })
- for _, d in ipairs(netdev_list) do
- o:value(d.name, d.label)
- end
-end
-
-
-if load_normal_options then
-
-o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
-
-o = s:option(Value, _n("port"), translate("Port"))
-o.datatype = "port"
-
-o = s:option(Value, _n("uuid"), translate("ID"))
-o.password = true
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless" })
-o:depends({ [_n("protocol")] = "tuic" })
-
-o = s:option(Value, _n("username"), translate("Username"))
-o:depends({ [_n("protocol")] = "http" })
-o:depends({ [_n("protocol")] = "socks" })
-o:depends({ [_n("protocol")] = "ssh" })
-o:depends({ [_n("protocol")] = "naive" })
-
-o = s:option(Value, _n("password"), translate("Password"))
-o.password = true
-o:depends({ [_n("protocol")] = "http" })
-o:depends({ [_n("protocol")] = "socks" })
-o:depends({ [_n("protocol")] = "shadowsocks" })
-o:depends({ [_n("protocol")] = "trojan" })
-o:depends({ [_n("protocol")] = "tuic" })
-o:depends({ [_n("protocol")] = "anytls" })
-o:depends({ [_n("protocol")] = "ssh" })
-o:depends({ [_n("protocol")] = "naive" })
-
-o = s:option(ListValue, _n("security"), translate("Encrypt Method"))
-for a, t in ipairs(security_list) do o:value(t) end
-o:depends({ [_n("protocol")] = "vmess" })
-
-o = s:option(ListValue, _n("ss_method"), translate("Encrypt Method"))
-o.rewrite_option = "method"
-for a, t in ipairs(ss_method_new_list) do o:value(t) end
-for a, t in ipairs(ss_method_old_list) do o:value(t) end
-o:depends({ [_n("protocol")] = "shadowsocks" })
-
-o = s:option(Flag, _n("uot"), translate("UDP over TCP"))
-o:depends({ [_n("protocol")] = "socks" })
-o:depends({ [_n("protocol")] = "shadowsocks" })
-o:depends({ [_n("protocol")] = "naive" })
-
-o = s:option(Value, _n("alter_id"), "Alter ID")
-o.datatype = "uinteger"
-o.default = "0"
-o:depends({ [_n("protocol")] = "vmess" })
-
-o = s:option(Flag, _n("global_padding"), "global_padding", translate("Protocol parameter. Will waste traffic randomly if enabled."))
-o.default = "0"
-o:depends({ [_n("protocol")] = "vmess" })
-
-o = s:option(Flag, _n("authenticated_length"), "authenticated_length", translate("Protocol parameter. Enable length block encryption."))
-o.default = "0"
-o:depends({ [_n("protocol")] = "vmess" })
-
-o = s:option(ListValue, _n("flow"), translate("flow"))
-o.default = ""
-o:value("", translate("Disable"))
-o:value("xtls-rprx-vision")
-o:depends({ [_n("protocol")] = "vless", [_n("tls")] = true })
-
-if singbox_tags:find("with_quic") then
- o = s:option(Value, _n("hysteria_hop"), translate("Port hopping range"))
- o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
- o:depends({ [_n("protocol")] = "hysteria" })
-
- o = s:option(Value, _n("hysteria_hop_interval"), translate("Hop Interval(second)"), translate("Example:") .. "30 (≥5)")
- o.datatype = "uinteger"
- o.placeholder = "30"
- o.default = "30"
- o:depends({ [_n("protocol")] = "hysteria" })
-
- o = s:option(Value, _n("hysteria_obfs"), translate("Obfs Password"))
- o:depends({ [_n("protocol")] = "hysteria" })
-
- o = s:option(ListValue, _n("hysteria_auth_type"), translate("Auth Type"))
- o:value("disable", translate("Disable"))
- o:value("string", translate("STRING"))
- o:value("base64", translate("BASE64"))
- o:depends({ [_n("protocol")] = "hysteria" })
-
- o = s:option(Value, _n("hysteria_auth_password"), translate("Auth Password"))
- o.password = true
- o:depends({ [_n("protocol")] = "hysteria", [_n("hysteria_auth_type")] = "string"})
- o:depends({ [_n("protocol")] = "hysteria", [_n("hysteria_auth_type")] = "base64"})
-
- o = s:option(Value, _n("hysteria_up_mbps"), translate("Max upload Mbps"))
- o.default = "10"
- o:depends({ [_n("protocol")] = "hysteria" })
-
- o = s:option(Value, _n("hysteria_down_mbps"), translate("Max download Mbps"))
- o.default = "50"
- o:depends({ [_n("protocol")] = "hysteria" })
-
- o = s:option(Value, _n("hysteria_recv_window_conn"), translate("QUIC stream receive window"))
- o:depends({ [_n("protocol")] = "hysteria" })
-
- o = s:option(Value, _n("hysteria_recv_window"), translate("QUIC connection receive window"))
- o:depends({ [_n("protocol")] = "hysteria" })
-
- o = s:option(Flag, _n("hysteria_disable_mtu_discovery"), translate("Disable MTU detection"))
- o:depends({ [_n("protocol")] = "hysteria" })
-end
-
-if singbox_tags:find("with_quic") then
- o = s:option(ListValue, _n("tuic_congestion_control"), translate("Congestion control algorithm"))
- o.default = "cubic"
- o:value("bbr", translate("BBR"))
- o:value("cubic", translate("CUBIC"))
- o:value("new_reno", translate("New Reno"))
- o:depends({ [_n("protocol")] = "tuic" })
-
- o = s:option(ListValue, _n("tuic_udp_relay_mode"), translate("UDP relay mode"))
- o.default = "native"
- o:value("native", translate("native"))
- o:value("quic", translate("QUIC"))
- o:depends({ [_n("protocol")] = "tuic" })
-
- --[[
- o = s:option(Flag, _n("tuic_udp_over_stream"), translate("UDP over stream"))
- o:depends({ [_n("protocol")] = "tuic" })
- ]]--
-
- o = s:option(Flag, _n("tuic_zero_rtt_handshake"), translate("Enable 0-RTT QUIC handshake"))
- o.default = 0
- o:depends({ [_n("protocol")] = "tuic" })
-
- o = s:option(Value, _n("tuic_heartbeat"), translate("Heartbeat interval(second)"))
- o.datatype = "uinteger"
- o.default = "3"
- o:depends({ [_n("protocol")] = "tuic" })
-
- o = s:option(ListValue, _n("tuic_alpn"), translate("QUIC TLS ALPN"))
- o.default = "default"
- o:value("default", translate("Default"))
- o:value("h3")
- o:value("h2")
- o:value("h3,h2")
- o:value("http/1.1")
- o:value("h2,http/1.1")
- o:value("h3,h2,http/1.1")
- o:value("spdy/3.1")
- o:value("h3,spdy/3.1")
- o:depends({ [_n("protocol")] = "tuic" })
-end
-
-if singbox_tags:find("with_quic") then
- o = s:option(Value, _n("hysteria2_hop"), translate("Port hopping range"))
- o.description = translate("Format as 1000:2000 or 1000-2000 Multiple groups are separated by commas (,).")
- o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
-
- o = s:option(Value, _n("hysteria2_hop_interval"), translate("Hop Interval(second)"), translate("Supports a fixed value or a random range (e.g., 30, 5-30), minimum 5."))
- o.datatype = "or(uinteger,portrange)"
- o.placeholder = "30"
- o.default = "30"
- o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
-
- o = s:option(Flag, _n("hysteria2_realms"), translate("Realms"))
- o.default = "0"
- if api.compare_versions(singbox_version, ">=", "1.14.0") then
- o:depends({ [_n("protocol")] = "hysteria2"})
- else
- o:depends({ [_n("protocol")] = "__hide"})
- end
-
- o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
- o:depends({ [_n("hysteria2_realms")] = "1" })
- o.validate = function(self, value)
- value = api.trim(value)
- local realm = api.parse_realm_uri(value)
- if realm then return value end
- return nil, translate("Invalid Realm URL.")
- end
-
- o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
- o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
- o:depends({ [_n("hysteria2_realms")] = "1" })
-
- o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password"))
- o.password = true
- o:depends({ [_n("protocol")] = "hysteria2"})
-
- o = s:option(ListValue, _n("hysteria2_obfs_type"), translate("Obfs Type"))
- o:value("", translate("Disable"))
- o:value("salamander")
- o:value("gecko")
- o:depends({ [_n("protocol")] = "hysteria2" })
-
- o = s:option(Value, _n("hysteria2_obfs_password"), translate("Obfs Password"))
- o:depends({ [_n("hysteria2_obfs_type")] = "salamander" })
- o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
-
- o = s:option(Value, _n("hysteria2_obfs_MinPacketSize"), translate("Gecko Packet Size (min)"))
- o.datatype = "uinteger"
- o.placeholder = "512"
- o.default = "512"
- o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
-
- o = s:option(Value, _n("hysteria2_obfs_MaxPacketSize"), translate("Gecko Packet Size (max)"))
- o.datatype = "uinteger"
- o.placeholder = "1200"
- o.default = "1200"
- o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
-
- o = s:option(Value, _n("hysteria2_up_mbps"), translate("Max upload Mbps"))
- o:depends({ [_n("protocol")] = "hysteria2" })
-
- o = s:option(Value, _n("hysteria2_down_mbps"), translate("Max download Mbps"))
- o:depends({ [_n("protocol")] = "hysteria2" })
-
- o = s:option(Value, _n("hysteria2_idle_timeout"), translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
- o.datatype = "range(4,120)"
- o:depends({ [_n("protocol")] = "hysteria2"})
-
- o = s:option(Value, _n("hysteria2_keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
- o.datatype = "range(2,60)"
- o:depends({ [_n("protocol")] = "hysteria2"})
-
- o = s:option(Flag, _n("hysteria2_disable_mtu_discovery"), translate("Disable MTU detection"))
- o.default = "0"
- o:depends({ [_n("protocol")] = "hysteria2"})
-end
-
--- [[ SSH config start ]] --
-o = s:option(TextValue, _n("ssh_priv_key"), translate("Private Key"))
-o.rows = 5
-o.wrap = "off"
-o:depends({ [_n("protocol")] = "ssh" })
-o.validate = function(self, value)
- value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
- return value
-end
-
-o = s:option(Value, _n("ssh_priv_key_pp"), translate("Private Key Passphrase"))
-o.password = true
-o:depends({ [_n("protocol")] = "ssh" })
-
-o = s:option(DynamicList, _n("ssh_host_key"), translate("Host Key"), translate("Accept any if empty."))
-o:depends({ [_n("protocol")] = "ssh" })
-
-o = s:option(DynamicList, _n("ssh_host_key_algo"), translate("Host Key Algorithms"))
-o:depends({ [_n("protocol")] = "ssh" })
-
-o = s:option(Value, _n("ssh_client_version"), translate("Client Version"), translate("Random version will be used if empty."))
-o:depends({ [_n("protocol")] = "ssh" })
--- [[ SSH config end ]] --
-
--- [[ naive start ]] --
-o = s:option(Value, _n("naive_insecure_concurrency"), translate("Concurrent Tunnels"))
-o.datatype = "uinteger"
-o.placeholder = "0"
-o.default = "0"
-o:depends({ [_n("protocol")] = "naive" })
-
-o = s:option(Flag, _n("naive_quic"), translate("QUIC"))
-o.default = 0
-o:depends({ [_n("protocol")] = "naive" })
-
-o = s:option(ListValue, _n("naive_congestion_control"), translate("Congestion control algorithm"))
-o.default = "bbr"
-o:value("bbr", translate("BBR"))
-o:value("bbr2", translate("BBRv2"))
-o:value("cubic", translate("CUBIC"))
-o:value("reno", translate("New Reno"))
-o:depends({ [_n("naive_quic")] = "1" })
--- [[ naive end ]] --
-
-o = s:option(Flag, _n("tls"), translate("TLS"))
-o.default = 0
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless" })
-o:depends({ [_n("protocol")] = "http" })
-o:depends({ [_n("protocol")] = "trojan" })
-o:depends({ [_n("protocol")] = "shadowsocks" })
-o:depends({ [_n("protocol")] = "anytls" })
-
-o = s:option(ListValue, _n("alpn"), translate("ALPN"))
-o.default = "default"
-o:value("default", translate("Default"))
-o:value("h3")
-o:value("h2")
-o:value("h3,h2")
-o:value("http/1.1")
-o:value("h2,http/1.1")
-o:value("h3,h2,http/1.1")
-o:depends({ [_n("tls")] = true })
-o:depends({ [_n("protocol")] = "hysteria" })
-
-o = s:option(Flag, _n("tls_disable_sni"), translate("Disable SNI"), translate("Do not send server name in ClientHello."))
-o.default = "0"
-o:depends({ [_n("tls")] = true })
-o:depends({ [_n("protocol")] = "hysteria"})
-o:depends({ [_n("protocol")] = "tuic" })
-o:depends({ [_n("protocol")] = "hysteria2" })
-
-o = s:option(Value, _n("tls_serverName"), "SNI " .. translate("Domain"))
-o:depends({ [_n("tls")] = true })
-o:depends({ [_n("protocol")] = "hysteria"})
-o:depends({ [_n("protocol")] = "tuic" })
-o:depends({ [_n("protocol")] = "hysteria2" })
-o:depends({ [_n("protocol")] = "naive" })
-
-o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
-o.default = "0"
-o:depends({ [_n("tls")] = true })
-o:depends({ [_n("protocol")] = "hysteria"})
-o:depends({ [_n("protocol")] = "tuic" })
-o:depends({ [_n("protocol")] = "hysteria2" })
-
-o = s:option(Flag, _n("tls_certificate"), translate("TLS Certificate (PEM)"))
-o.default = "0"
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-o:depends({ [_n("protocol")] = "hysteria"})
-o:depends({ [_n("protocol")] = "tuic" })
-o:depends({ [_n("protocol")] = "hysteria2" })
-o:depends({ [_n("protocol")] = "naive" })
-
-o = s:option(TextValue, _n("tls_certificate_pem"), " ", translate("Full certificate (chain), PEM format."))
-o.default = ""
-o.rows = 5
-o.wrap = "off"
-o:depends({ [_n("tls_certificate")] = true })
-o.validate = function(self, value)
- value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
- return value
-end
-
-o = s:option(Value, _n("cipherSuites"), translate("Cipher Suites"), '***' .. " " .. translate("Configures the list of supported cipher suites, separated by :"))
-o:depends({ [_n("tls")] = true })
-
-o = s:option(Flag, _n("ech"), translate("ECH"))
-o.default = "0"
-o:depends({ [_n("tls")] = true, [_n("flow")] = "", [_n("reality")] = false })
-o:depends({ [_n("protocol")] = "tuic" })
-o:depends({ [_n("protocol")] = "hysteria" })
-o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
-o:depends({ [_n("protocol")] = "naive" })
-
-o = s:option(TextValue, _n("ech_config"), translate("ECH Config"))
-o.default = ""
-o.rows = 5
-o.wrap = "off"
-o:depends({ [_n("ech")] = true })
-o.validate = function(self, value)
- value = api.trim(value):gsub("\r\n", "\n"):gsub("[ \t]*\n[ \t]*", "\n"):gsub("\n+", "\n")
- return value
-end
-
-o = s:option(Value, _n("ech_query_server_name"), translate("ECH Query Domain"), translate("Overrides the domain name used for ECH HTTPS record queries."))
-o:depends({ [_n("ech")] = true })
-
-if singbox_tags:find("with_utls") then
- o = s:option(Flag, _n("utls"), translate("uTLS"))
- o.default = "0"
- o:depends({ [_n("tls")] = true })
-
- o = s:option(ListValue, _n("fingerprint"), translate("Finger Print"))
- o:value("chrome")
- o:value("firefox")
- o:value("edge")
- o:value("safari")
- o:value("360")
- o:value("qq")
- o:value("ios")
- o:value("android")
- o:value("random")
- o:value("randomized")
- o.default = "chrome"
- o:depends({ [_n("utls")] = true })
-
- -- [[ REALITY部分 ]] --
- o = s:option(Flag, _n("reality"), translate("REALITY"))
- o.default = 0
- o:depends({ [_n("protocol")] = "vless", [_n("tls")] = true })
- o:depends({ [_n("protocol")] = "vmess", [_n("tls")] = true })
- o:depends({ [_n("protocol")] = "shadowsocks", [_n("tls")] = true })
- o:depends({ [_n("protocol")] = "trojan", [_n("tls")] = true })
- o:depends({ [_n("protocol")] = "anytls", [_n("tls")] = true })
-
- o = s:option(Value, _n("reality_publicKey"), translate("Public Key"))
- o:depends({ [_n("reality")] = true })
-
- o = s:option(Value, _n("reality_shortId"), translate("Short Id"))
- o:depends({ [_n("reality")] = true })
-end
-
-o = s:option(ListValue, _n("transport"), translate("Transport"))
-o:value("tcp", "TCP")
-o:value("http", "HTTP")
-o:value("ws", "WebSocket")
-o:value("httpupgrade", "HTTPUpgrade")
-if singbox_tags:find("with_quic") then
- o:value("quic", "QUIC")
-end
-if singbox_tags:find("with_grpc") then
- o:value("grpc", "gRPC")
-else o:value("grpc", "gRPC-lite")
-end
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless" })
-o:depends({ [_n("protocol")] = "shadowsocks" })
-o:depends({ [_n("protocol")] = "trojan" })
-
-if singbox_tags:find("with_wireguard") then
- o = s:option(Value, _n("wireguard_public_key"), translate("Public Key"))
- o:depends({ [_n("protocol")] = "wireguard" })
-
- o = s:option(Value, _n("wireguard_secret_key"), translate("Private Key"))
- o:depends({ [_n("protocol")] = "wireguard" })
-
- o = s:option(Value, _n("wireguard_preSharedKey"), translate("Pre shared key"))
- o:depends({ [_n("protocol")] = "wireguard" })
-
- o = s:option(DynamicList, _n("wireguard_local_address"), translate("Local Address"))
- o:depends({ [_n("protocol")] = "wireguard" })
-
- o = s:option(Value, _n("wireguard_mtu"), translate("MTU"))
- o.default = "1420"
- o:depends({ [_n("protocol")] = "wireguard" })
-
- o = s:option(Value, _n("wireguard_reserved"), translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
- o:depends({ [_n("protocol")] = "wireguard" })
-end
-
--- [[ TCP部分(模拟) ]]--
-o = s:option(ListValue, _n("tcp_guise"), translate("Camouflage Type"))
-o:value("none", "none")
-o:value("http", "http")
-o:depends({ [_n("transport")] = "tcp" })
-
-o = s:option(DynamicList, _n("tcp_guise_http_host"), translate("HTTP Host"))
-o:depends({ [_n("tcp_guise")] = "http" })
-
-o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path"))
-o.placeholder = "/"
-o:depends({ [_n("tcp_guise")] = "http" })
-
--- [[ HTTP部分 ]]--
-o = s:option(DynamicList, _n("http_host"), translate("HTTP Host"))
-o:depends({ [_n("transport")] = "http" })
-
-o = s:option(Value, _n("http_path"), translate("HTTP Path"))
-o.placeholder = "/"
-o:depends({ [_n("transport")] = "http" })
-
-o = s:option(Flag, _n("http_h2_health_check"), translate("Health check"))
-o:depends({ [_n("tls")] = true, [_n("transport")] = "http" })
-
-o = s:option(Value, _n("http_h2_read_idle_timeout"), translate("Idle timeout"))
-o.default = "15"
-o:depends({ [_n("http_h2_health_check")] = true })
-
-o = s:option(Value, _n("http_h2_health_check_timeout"), translate("Health check timeout"))
-o.default = "15"
-o:depends({ [_n("http_h2_health_check")] = true })
-
--- [[ WebSocket部分 ]]--
-o = s:option(Value, _n("ws_host"), translate("WebSocket Host"))
-o:depends({ [_n("transport")] = "ws" })
-
-o = s:option(Value, _n("ws_path"), translate("WebSocket Path"))
-o.placeholder = "/"
-o:depends({ [_n("transport")] = "ws" })
-
-o = s:option(Flag, _n("ws_enableEarlyData"), translate("Enable early data"))
-o:depends({ [_n("transport")] = "ws" })
-
-o = s:option(Value, _n("ws_maxEarlyData"), translate("Early data length"))
-o.default = "1024"
-o:depends({ [_n("ws_enableEarlyData")] = true })
-
-o = s:option(Value, _n("ws_earlyDataHeaderName"), translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol"))
-o:depends({ [_n("ws_enableEarlyData")] = true })
-
--- [[ HTTPUpgrade部分 ]]--
-o = s:option(Value, _n("httpupgrade_host"), translate("HTTPUpgrade Host"))
-o:depends({ [_n("transport")] = "httpupgrade" })
-
-o = s:option(Value, _n("httpupgrade_path"), translate("HTTPUpgrade Path"))
-o.placeholder = "/"
-o:depends({ [_n("transport")] = "httpupgrade" })
-
--- [[ gRPC部分 ]]--
-o = s:option(Value, _n("grpc_serviceName"), "ServiceName")
-o:depends({ [_n("transport")] = "grpc" })
-
-o = s:option(Flag, _n("grpc_health_check"), translate("Health check"))
-o:depends({ [_n("transport")] = "grpc" })
-
-o = s:option(Value, _n("grpc_idle_timeout"), translate("Idle timeout"))
-o.default = "15"
-o:depends({ [_n("grpc_health_check")] = true })
-
-o = s:option(Value, _n("grpc_health_check_timeout"), translate("Health check timeout"))
-o.default = "15"
-o:depends({ [_n("grpc_health_check")] = true })
-
-o = s:option(Flag, _n("grpc_permit_without_stream"), translate("Permit without stream"))
-o.default = "0"
-o:depends({ [_n("grpc_health_check")] = true })
-
--- [[ User-Agent ]]--
-o = s:option(Value, _n("user_agent"), translate("User-Agent"))
-o.default = ""
-o:value("", translate("default"))
-o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36", "chrome")
-o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0", "firefox")
-o:value("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15", "safari")
-o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70", "edge")
-o:value("Go-http-client/1.1", "golang")
-o:value("curl/7.68.0", "curl")
-o:depends({ [_n("tcp_guise")] = "http" })
-o:depends({ [_n("transport")] = "http" })
-o:depends({ [_n("transport")] = "ws" })
-o:depends({ [_n("transport")] = "httpupgrade" })
-o:depends({ [_n("protocol")] = "naive" })
-
--- [[ Mux ]]--
-o = s:option(Flag, _n("mux"), translate("Mux"))
-o.rmempty = false
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless", [_n("flow")] = "" })
-o:depends({ [_n("protocol")] = "shadowsocks", [_n("uot")] = "" })
-o:depends({ [_n("protocol")] = "trojan" })
-
-o = s:option(ListValue, _n("mux_type"), translate("Mux"))
-o:value("smux")
-o:value("yamux")
-o:value("h2mux")
-o:depends({ [_n("mux")] = true })
-
-o = s:option(Value, _n("mux_concurrency"), translate("Mux concurrency"))
-o.default = 4
-o:depends({ [_n("mux")] = true, [_n("tcpbrutal")] = false })
-
-o = s:option(Flag, _n("mux_padding"), translate("Padding"))
-o.default = 0
-o:depends({ [_n("mux")] = true })
-
--- [[ TCP Brutal ]]--
-o = s:option(Flag, _n("tcpbrutal"), translate("TCP Brutal"))
-o.default = 0
-o:depends({ [_n("mux")] = true })
-
-o = s:option(Value, _n("tcpbrutal_up_mbps"), translate("Max upload Mbps"))
-o.default = "10"
-o:depends({ [_n("tcpbrutal")] = true })
-
-o = s:option(Value, _n("tcpbrutal_down_mbps"), translate("Max download Mbps"))
-o.default = "50"
-o:depends({ [_n("tcpbrutal")] = true })
-
-o = s:option(Flag, _n("shadowtls"), "ShadowTLS")
-o.default = 0
-o:depends({ [_n("protocol")] = "vmess", [_n("tls")] = false })
-o:depends({ [_n("protocol")] = "shadowsocks", [_n("tls")] = false })
-
-o = s:option(ListValue, _n("shadowtls_version"), "ShadowTLS " .. translate("Version"))
-o.default = "1"
-o:value("1", "ShadowTLS v1")
-o:value("2", "ShadowTLS v2")
-o:value("3", "ShadowTLS v3")
-o:depends({ [_n("shadowtls")] = true })
-
-o = s:option(Value, _n("shadowtls_password"), "ShadowTLS " .. translate("Password"))
-o.password = true
-o:depends({ [_n("shadowtls")] = true, [_n("shadowtls_version")] = "2" })
-o:depends({ [_n("shadowtls")] = true, [_n("shadowtls_version")] = "3" })
-
-o = s:option(Value, _n("shadowtls_serverName"), "ShadowTLS " .. translate("Domain"))
-o:depends({ [_n("shadowtls")] = true })
-
-if singbox_tags:find("with_utls") then
- o = s:option(Flag, _n("shadowtls_utls"), "ShadowTLS " .. translate("uTLS"))
- o.default = "0"
- o:depends({ [_n("shadowtls")] = true })
-
- o = s:option(ListValue, _n("shadowtls_fingerprint"), "ShadowTLS " .. translate("Finger Print"))
- o:value("chrome")
- o:value("firefox")
- o:value("edge")
- o:value("safari")
- -- o:value("360")
- o:value("qq")
- o:value("ios")
- -- o:value("android")
- o:value("random")
- -- o:value("randomized")
- o.default = "chrome"
- o:depends({ [_n("shadowtls")] = true, [_n("shadowtls_utls")] = true })
-end
-
--- [[ SIP003 plugin ]]--
-o = s:option(Flag, _n("plugin_enabled"), translate("plugin"))
-o.default = 0
-o:depends({ [_n("protocol")] = "shadowsocks" })
-
-o = s:option(ListValue, _n("plugin"), "SIP003 " .. translate("plugin"))
-o.default = "obfs-local"
-o:depends({ [_n("plugin_enabled")] = true })
-o:value("obfs-local")
-o:value("v2ray-plugin")
-
-o = s:option(Value, _n("plugin_opts"), translate("opts"))
-o:depends({ [_n("plugin_enabled")] = true })
-
-o = s:option(ListValue, _n("domain_resolver"), translate("Domain DNS Resolve"))
-o.description = translate("If the node address is a domain name, this DNS will be used for resolution.") .. "
" .. string.format('%s',
- translate("Note: For node-specific DNS only. Keep Auto to avoid extra overhead."))
-o:value("", translate("Auto"))
-o:value("tcp", "TCP")
-o:value("udp", "UDP")
-o:value("https", "DoH")
-
-o = s:option(Value, _n("domain_resolver_dns"), "DNS")
-o.datatype = "or(ipaddr,ipaddrport)"
-o:value("114.114.114.114")
-o:value("223.5.5.5:53")
-o.default = o.keylist[1]
-o:depends({ [_n("domain_resolver")] = "tcp" })
-o:depends({ [_n("domain_resolver")] = "udp" })
-
-o = s:option(Value, _n("domain_resolver_dns_https"), "DNS")
-o:value("https://120.53.53.53/dns-query", "DNSPod")
-o:value("https://223.5.5.5/dns-query", "AliDNS")
-o.default = o.keylist[1]
-o:depends({ [_n("domain_resolver")] = "https" })
-
-o = s:option(ListValue, _n("domain_strategy"), translate("Domain Strategy"), translate("If is domain name, The requested domain name will be resolved to IP before connect."))
-o.default = ""
-o:value("", translate("Auto"))
-o:value("prefer_ipv4", translate("Prefer IPv4"))
-o:value("prefer_ipv6", translate("Prefer IPv6"))
-o:value("ipv4_only", translate("IPv4 Only"))
-o:value("ipv6_only", translate("IPv6 Only"))
-
-
-local protocols = s.fields[_n("protocol")].keylist
-if #protocols > 0 then
- for i, v in ipairs(protocols) do
- if not v:find("^_") then
- local depends_condition = { [_n("protocol")] = v }
- if v == "hysteria2" then
- depends_condition[_n("hysteria2_realms")] = false
- end
- s.fields[_n("address")]:depends(depends_condition)
- s.fields[_n("port")]:depends(depends_condition)
- s.fields[_n("domain_resolver")]:depends(depends_condition)
- s.fields[_n("domain_strategy")]:depends(depends_condition)
- end
- end
-end
-end
--- [[ Normal single node End ]]
-
-if not load_shunt_options then
- o = s:option(ListValue, _n("chain_proxy"), translate("Chain Proxy"))
- o:value("", translate("Close(Not use)"))
- if not (load_iface_options or load_urltest_options) then
- -- Special node cannot be use pre-proxy.
- o:value("1", translate("Preproxy Node"))
- o:value("3", translate("Outbound Interface"))
- end
- o:value("2", translate("Landing Node"))
-
- o1 = s:option(ListValue, _n("preproxy_node"), translate("Preproxy Node"), translate("Only support a layer of proxy."))
- o1:depends({ [_n("chain_proxy")] = "1", [_n("hysteria2_realms")] = false })
- o1.template = appname .. "/cbi/nodes_listvalue"
- o1.group = {}
-
- o3 = s:option(Value, _n("outbound_iface"), translate("Outbound Interface"))
- o3:depends({ [_n("chain_proxy")] = "3" })
- o3:value("", translate("All"))
- for _, d in ipairs(netdev_list) do
- o3:value(d.name, d.label)
- end
-
- o2 = s:option(ListValue, _n("to_node"), translate("Landing Node"), translate("Only support a layer of proxy."))
- o2:depends({ [_n("chain_proxy")] = "2", [_n("hysteria2_realms")] = false })
- o2.template = appname .. "/cbi/nodes_listvalue"
- o2.group = {}
-
- for k1, v1 in pairs(node_list) do
- if k1 ~= "shunt_list" and k1 ~= "iface_list" then
- for i, v in ipairs(v1) do
- if v.id ~= arg[1] then
- o1:value(v.id, v.remark)
- o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- if k1 == "normal_list" then
- -- Landing Node not support use special node.
- o2:value(v.id, v.remark)
- o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
- end
- end
- end
- end
- end
-end
-
-api.luci_types(arg[1], m, s, type_name, option_prefix)
-
-if load_shunt_options then
- local current_node = m.uci:get_all(appname, arg[1]) or {}
- local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall/client/include/shunt_options.lua")
- setfenv(shunt_lua, getfenv(1))(m, s, {
- node_id = arg[1],
- node = current_node,
- node_list = node_list,
- })
-end
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua
deleted file mode 100644
index 18eac26556c..00000000000
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua
+++ /dev/null
@@ -1,76 +0,0 @@
-local m, s = ...
-
-if not api.is_finded("sslocal") then
- return
-end
-
-type_name = "SS-Rust"
-
--- [[ Shadowsocks Rust ]]
-
-s.fields["type"]:value(type_name, "Shadowsocks Rust")
-
-if s.val["type"] ~= type_name then
- return
-end
-
-local option_prefix = "ssrust_"
-
-local function _n(name)
- return option_prefix .. name
-end
-
-local ssrust_encrypt_method_list = {
- "none", "plain",
- "aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
- "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
-}
-
-o = s:option(ListValue, _n("del_protocol")) --始终隐藏,用于删除 protocol
-o:depends({ [_n("__hide")] = "1" })
-o.rewrite_option = "protocol"
-
-o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
-
-o = s:option(Value, _n("port"), translate("Port"))
-o.datatype = "port"
-
-o = s:option(Value, _n("password"), translate("Password"))
-o.password = true
-
-o = s:option(Value, _n("method"), translate("Encrypt Method"))
-for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end
-
-o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
-o.datatype = "uinteger"
-o.default = 300
-
-o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
-o.default = 0
-
-o = s:option(Flag, _n("plugin_enabled"), translate("plugin"))
-o.default = 0
-
-o = s:option(Value, _n("plugin"), "SIP003 " .. translate("plugin"), translate("Supports custom SIP003 plugins, Make sure the plugin is installed."))
-o.default = "none"
-o:value("none", translate("none"))
-if api.is_finded("xray-plugin") then o:value("xray-plugin") end
-if api.is_finded("v2ray-plugin") then o:value("v2ray-plugin") end
-if api.is_finded("obfs-local") then o:value("obfs-local") end
-if api.is_finded("shadow-tls") then o:value("shadow-tls") end
-o:depends({ [_n("plugin_enabled")] = true })
-o.validate = function(self, value, t)
- if value and value ~= "" and value ~= "none" then
- if not api.is_finded(value) then
- return nil, value .. ": " .. translate("Can't find this file!")
- else
- return value
- end
- end
- return nil
-end
-
-o = s:option(Value, _n("plugin_opts"), translate("opts"))
-o:depends({ [_n("plugin_enabled")] = true })
-
-api.luci_types(arg[1], m, s, type_name, option_prefix)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/index.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/index.lua
index 0cca99bc075..6e615c11811 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/index.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/index.lua
@@ -1,12 +1,9 @@
local api = require "luci.passwall.api"
-local appname = api.appname
-
api.set_default_cbi()
-m = Map("passwall_server", translate("Server-Side"))
-api.set_apply_on_parse(m)
+m = Map(api.s_config)
-t = m:section(NamedSection, "global", "global")
+t = m:section(NamedSection, "global", "global", translate("Server-Side"))
t.anonymous = true
t.addremove = false
@@ -18,38 +15,39 @@ e.default = "90"
e.datatype = "range(0,600)"
e.rmempty = false
-local cfgname = "user"
-t = m:section(TypedSection, cfgname, translate("Users Manager"))
-t.anonymous = true
-t.addremove = true
-t.sortable = true
-t.template = "cbi/tblsection"
-t.extedit = api.url("server_user", "%s")
-function t.create(e, t)
+
+s_server = m:section(TypedSection, "server", translate("Servers Manager"))
+s_server.anonymous = true
+s_server.addremove = true
+s_server.sortable = true
+s_server.template = "cbi/tblsection"
+s_server.extedit = api.url("server_config", "%s")
+function s_server.create(self, section)
local uid = api.gen_random_char()
- TypedSection.create(e, uid)
- luci.http.redirect(e.extedit:format(uid))
+ TypedSection.create(self, uid)
+ luci.http.redirect(self.extedit:format(uid))
end
-function t.remove(e, t)
- e.map.proceed = true
- e.map:del(t)
- luci.http.redirect(api.url("server"))
+function s_server.remove(self, section)
+ local o = m:get(section) or {}
+ if o[".type"] == self.sectiontype then
+ m:del(section)
+ end
end
-e = t:option(Flag, "enable", translate("Enable"))
+e = s_server:option(Flag, "enable", translate("Enable"))
e.width = "5%"
e.rmempty = false
-e = t:option(DummyValue, "status", translate("Status"))
+e = s_server:option(DummyValue, "status", translate("Status"))
e.rawhtml = true
e.cfgvalue = function(t, n)
- return string.format('%s', translate("Collecting data..."))
+ return string.format('%s', translate("Collecting data..."))
end
-e = t:option(DummyValue, "remarks", translate("Remarks"))
+e = s_server:option(DummyValue, "remarks", translate("Remarks"))
e.width = "15%"
-e = t:option(DummyValue, "type", translate("Type"))
+e = s_server:option(DummyValue, "type", translate("Type"))
e.width = "20%"
e.rawhtml = true
e.cfgvalue = function(t, n)
@@ -87,20 +85,62 @@ e.cfgvalue = function(t, n)
return str
end
-e = t:option(DummyValue, "port", translate("Port"))
+e = s_server:option(DummyValue, "port", translate("Port"))
-e = t:option(Flag, "log", translate("Log"))
+e = s_server:option(Flag, "log", translate("Log"))
e.default = "1"
e.rmempty = false
-local sortable = Template(appname .. "/cbi/sortable")
-sortable.api = api
-sortable.appname = m.config
-sortable.target_cfgname = cfgname
-m:append(sortable)
+m:appendTemplate("/cbi/sortable", {sectiontype = s_server.sectiontype})
+
+m:appendTemplate("/server/server_list_status", {sectiontype = s_server.sectiontype})
+
+s_user = m:section(TypedSection, "user", translate("Users Manager"))
+s_user.anonymous = true
+s_user.addremove = true
+s_user.sortable = true
+s_user.template = "cbi/tblsection"
+s_user.extedit = api.url("server_user_config", "%s")
+s_user.create = function(e, section)
+ local uid = api.gen_random_char()
+ TypedSection.create(e, uid)
+ luci.http.redirect(e.extedit:format(uid))
+end
+s_user.remove = function(self, section)
+ local o = m:get(section) or {}
+ if o[".type"] == self.sectiontype then
+ m:foreach("server", function(o)
+ if o.user and section == o.user then
+ m:set(o[".name"], "user", "")
+ end
+ local changed = false
+ local users = o.users or {}
+ for i = #users, 1, -1 do
+ if section == users[i] then
+ table.remove(users, i)
+ changed = true
+ end
+ end
+ if changed then
+ m:set(o[".name"], "users", users)
+ end
+ end)
+ m:del(section)
+ luci.http.redirect(api.url("server"))
+ end
+end
+
+e = s_user:option(DummyValue, "username", translate("Username"))
+e.width = "25%"
+
+e = s_user:option(DummyValue, "password", translate("Password"))
+e.width = "25%"
+
+e = s_user:option(DummyValue, "uuid", "UUID")
+e.width = "25%"
-m:append(Template(appname .. "/server/log"))
+m:appendTemplate("/cbi/sortable", {sectiontype = s_user.sectiontype})
-m:append(Template(appname .. "/server/users_list_status"))
+m:appendTemplate("/server/log")
return api.return_map(m)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/user.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/server_config.lua
similarity index 51%
rename from applications/luci-app-passwall/luasrc/model/cbi/passwall/server/user.lua
rename to applications/luci-app-passwall/luasrc/model/cbi/passwall/server/server_config.lua
index 89b702923f7..bd7d2e48cec 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/user.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/server_config.lua
@@ -1,30 +1,27 @@
api = require "luci.passwall.api"
-appname = api.appname
-fs = api.fs
-
api.set_default_cbi()
-m = Map("passwall_server", translate("Server Config"))
+m = Map(api.s_config)
m.redirect = api.url("server")
-api.set_apply_on_parse(m)
if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(m.redirect)
end
-local header = Template(appname .. "/server/config_header")
-header.api = api
-header.config = m.config
-header.section = arg[1]
-m:append(header)
+m:appendTemplate("/server/config_header", {section = arg[1]})
+
+m:appendTemplate("/cbi/nodes_listvalue_com")
-m:append(Template(appname .. "/cbi/nodes_listvalue_com"))
+user_list = {}
+m:foreach("user", function(s)
+ user_list[#user_list + 1] = s
+end)
-s = m:section(NamedSection, arg[1], "user", "")
+local s = m:section(NamedSection, arg[1], "server", translate("Server Config"))
s.addremove = false
s.dynamic = false
-local types_dir = "/usr/lib/lua/luci/model/cbi/" .. appname .. "/server/type/"
+local types_dir = "/usr/lib/lua/luci/model/cbi/" .. api.appname .. "/server/type/"
s.val = m:get(arg[1]) or {}
o = s:option(Flag, "enable", translate("Enable"))
@@ -38,12 +35,10 @@ o.rmempty = false
o = s:option(ListValue, "type", translate("Type"))
local type_table = {}
-for filename in fs.dir(types_dir) do
+for filename in api.fs.dir(types_dir) do
table.insert(type_table, filename)
end
table.sort(type_table, function(a, b)
- if a == "socks.lua" then return true end
- if b == "socks.lua" then return false end
return a < b
end)
@@ -52,10 +47,6 @@ for index, value in ipairs(type_table) do
setfenv(p_func, getfenv(1))(m, s)
end
-local footer = Template(appname .. "/server/config_footer")
-footer.api = api
-footer.config = m.config
-footer.section = arg[1]
-m:append(footer)
+m:appendTemplate("/server/config_footer", {section = arg[1]})
return api.return_map(m)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/0_socks.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/0_socks.lua
new file mode 100644
index 00000000000..eb32702a157
--- /dev/null
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/0_socks.lua
@@ -0,0 +1,36 @@
+if not api.is_finded("microsocks") then
+ return
+end
+
+-- [[ microsocks ]]
+local m, s1 = ...
+local type_name = "Socks"
+
+s1.fields["type"]:value(type_name, "Socks")
+if not s1.fields["type"].default then
+ s1.fields["type"].default = type_name
+end
+
+if s1.val["type"] and s1.val["type"] ~= type_name then
+ return
+end
+
+local s = NamedSection(m, arg[1], "server")
+s.type_name = type_name
+s.option_prefix = "socks_"
+
+o = s:option(Value, "port", translate("Listen Port"))
+o.datatype = "port"
+
+o = s:option(Flag, "auth", translate("Auth"))
+
+o = s:option(ListValue, "user", translate("User"))
+for i, v in ipairs(user_list) do
+ o:value(v[".name"], v.username)
+end
+o:depends({ auth = true })
+
+o = s:option(Flag, "log", translate("Log"))
+o.default = "1"
+
+api.luci_types(s1, s)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/1_sing-box.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/1_sing-box.lua
new file mode 100644
index 00000000000..c1a313ac8f4
--- /dev/null
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/1_sing-box.lua
@@ -0,0 +1,528 @@
+local singbox_bin = api.finded_com("sing-box")
+
+if not singbox_bin then
+ return
+end
+
+-- [[ Sing-Box ]]
+local m, s1 = ...
+local type_name = "sing-box"
+
+s1.fields["type"]:value(type_name, "Sing-Box")
+if not s1.fields["type"].default then
+ s1.fields["type"].default = type_name
+end
+
+if not s1.val["type"] then
+ s1.val["type"] = type_name
+end
+
+if s1.val["type"] and s1.val["type"] ~= type_name then
+ return
+end
+
+local s = NamedSection(m, arg[1], "server")
+s.type_name = type_name
+s.option_prefix = "singbox_"
+
+local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'")
+
+local ss_method_list = {
+ "none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305",
+ "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
+}
+
+o = s:option(Flag, "custom", translate("Use Custom Config"))
+
+o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
+o.rows = 10
+o.wrap = "off"
+o:depends({ custom = true })
+o.datatype = "json"
+local o_validate = o.validate
+o.validate = function(self, value)
+ local v = o_validate(self, value)
+ if v then return v end
+ return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
+end
+o.custom_cfgvalue = function(self, section, value)
+ local config_str = m:get(section, "config_str")
+ if config_str then
+ return api.base64Decode(config_str)
+ end
+end
+o.custom_write = function(self, section, value)
+ m:set(section, "config_str", api.base64Encode(value) or "")
+end
+
+o = s:option(ListValue, "protocol", translate("Protocol"))
+o:value("mixed", "Mixed")
+o:value("socks", "Socks")
+o:value("http", "HTTP")
+o:value("shadowsocks", "Shadowsocks")
+o:value("vmess", "Vmess")
+o:value("vless", "VLESS")
+o:value("trojan", "Trojan")
+o:value("naive", "Naive")
+if singbox_tags:find("with_quic") then
+ o:value("hysteria", "Hysteria")
+end
+if singbox_tags:find("with_quic") then
+ o:value("tuic", "TUIC")
+end
+if singbox_tags:find("with_quic") then
+ o:value("hysteria2", "Hysteria2")
+end
+o:value("anytls", "AnyTLS")
+if singbox_tags:find("with_wireguard") then
+ o:value("wireguard", "WireGuard")
+end
+o:value("direct", "Direct")
+o:depends({ custom = false })
+
+o = s:option(DummyValue, "is_endpoint", "")
+o.not_rewrite = true
+o.template = m:template_path("/cbi/hidevalue")
+o.value = "1"
+o:depends({ custom = false, protocol = "wireguard" })
+
+o = s:option(Value, "port", translate("Listen Port"))
+o.datatype = "port"
+o:depends({ custom = false })
+
+o = s:option(DynamicList, "users", translate("User"))
+for i, v in ipairs(user_list) do
+ o:value(v[".name"], v.username)
+end
+o:depends({ protocol = "mixed" })
+o:depends({ protocol = "socks" })
+o:depends({ protocol = "http" })
+o:depends({ protocol = "shadowsocks" })
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless" })
+o:depends({ protocol = "trojan" })
+o:depends({ protocol = "naive" })
+o:depends({ protocol = "hysteria" })
+o:depends({ protocol = "tuic" })
+o:depends({ protocol = "hysteria2" })
+o:depends({ protocol = "anytls" })
+o:depends({ protocol = "wireguard" })
+
+if singbox_tags:find("with_quic") then
+ o = s:option(Value, "hysteria_obfs", translate("Obfs Password"))
+ o:depends({ protocol = "hysteria" })
+
+ o = s:option(Value, "hysteria_up_mbps", translate("Max upload Mbps"))
+ o.default = "100"
+ o:depends({ protocol = "hysteria" })
+
+ o = s:option(Value, "hysteria_down_mbps", translate("Max download Mbps"))
+ o.default = "100"
+ o:depends({ protocol = "hysteria" })
+
+ o = s:option(Value, "hysteria_recv_window_conn", translate("QUIC stream receive window"))
+ o:depends({ protocol = "hysteria" })
+
+ o = s:option(Value, "hysteria_recv_window_client", translate("QUIC connection receive window"))
+ o:depends({ protocol = "hysteria" })
+
+ o = s:option(Value, "hysteria_max_conn_client", translate("QUIC concurrent bidirectional streams"))
+ o.default = "1024"
+ o:depends({ protocol = "hysteria" })
+
+ o = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection"))
+ o:depends({ protocol = "hysteria" })
+end
+
+if singbox_tags:find("with_quic") then
+ o = s:option(ListValue, "tuic_congestion_control", translate("Congestion control algorithm"))
+ o.default = "cubic"
+ o:value("bbr", translate("BBR"))
+ o:value("cubic", translate("CUBIC"))
+ o:value("new_reno", translate("New Reno"))
+ o:depends({ protocol = "tuic" })
+
+ o = s:option(Flag, "tuic_zero_rtt_handshake", translate("Enable 0-RTT QUIC handshake"))
+ o.default = 0
+ o:depends({ protocol = "tuic" })
+
+ o = s:option(Value, "tuic_heartbeat", translate("Heartbeat interval(second)"))
+ o.datatype = "uinteger"
+ o.default = "3"
+ o:depends({ protocol = "tuic" })
+
+ o = s:option(ListValue, "tuic_alpn", translate("QUIC TLS ALPN"))
+ o.default = "default"
+ o:value("default", translate("Default"))
+ o:value("h3")
+ o:value("h2")
+ o:value("h3,h2")
+ o:value("http/1.1")
+ o:value("h2,http/1.1")
+ o:value("h3,h2,http/1.1")
+ o:value("spdy/3.1")
+ o:value("h3,spdy/3.1")
+ o:depends({ protocol = "tuic" })
+end
+
+if singbox_tags:find("with_quic") then
+ o = s:option(Flag, "hysteria2_realms", translate("Realms"))
+ o.default = "0"
+ o:depends({ protocol = "hysteria2"})
+
+ o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
+ o:depends({ hysteria2_realms = "1" })
+ o.validate = function(self, value)
+ value = api.trim(value)
+ local realm = api.parse_realm_uri(value)
+ if realm then return value end
+ return nil, translate("Invalid Realm URL.")
+ end
+
+ o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
+ o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
+ o:depends({ hysteria2_realms = "1" })
+
+ o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type"))
+ o:value("", translate("Disable"))
+ o:value("salamander")
+ o:value("gecko")
+ o:depends({ protocol = "hysteria2" })
+
+ o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password"))
+ o:depends({ hysteria2_obfs_type = "salamander" })
+ o:depends({ hysteria2_obfs_type = "gecko" })
+
+ o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
+ o.datatype = "uinteger"
+ o.placeholder = "512"
+ o.default = "512"
+ o:depends({ hysteria2_obfs_type = "gecko" })
+
+ o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
+ o.datatype = "uinteger"
+ o.placeholder = "1200"
+ o.default = "1200"
+ o:depends({ hysteria2_obfs_type = "gecko" })
+
+ o = s:option(Flag, "hysteria2_ignore_client_bandwidth", translate("Client BBR Flow Control"), translate("Commands the client to use the BBR flow control algorithm"))
+ o.default = 0
+ o:depends({ protocol = "hysteria2" })
+
+ o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps"))
+ o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false })
+
+ o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps"))
+ o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false })
+end
+
+o = s:option(ListValue, "d_protocol", translate("Destination protocol"))
+o:value("tcp", "TCP")
+o:value("udp", "UDP")
+o:value("tcp,udp", "TCP,UDP")
+o:depends({ protocol = "direct" })
+
+o = s:option(Value, "d_address", translate("Destination address"))
+o:depends({ protocol = "direct" })
+
+o = s:option(Value, "d_port", translate("Destination port"))
+o.datatype = "port"
+o:depends({ protocol = "direct" })
+
+o = s:option(Value, "decryption", translate("Encrypt Method"))
+o.default = "none"
+o:depends({ protocol = "vless" })
+
+o = s:option(ListValue, "ss_method", translate("Encrypt Method"))
+o.rewrite_option = "method"
+for a, t in ipairs(ss_method_list) do o:value(t) end
+o:depends({ protocol = "shadowsocks" })
+
+o = s:option(Value, "ss_password", translate("Password"))
+o:depends({ protocol = "shadowsocks" })
+
+o = s:option(ListValue, "flow", translate("flow"))
+o.default = ""
+o:value("", translate("Disable"))
+o:value("xtls-rprx-vision")
+o:depends({ protocol = "vless" , tls = true })
+
+o = s:option(Flag, "tls", translate("TLS"))
+o.default = 0
+o.validate = function(self, value, t)
+ if value then
+ local reality = s.fields["reality"] and s.fields["reality"]:formvalue(t) or nil
+ if reality and reality == "1" then return value end
+ if value == "1" then
+ local ca = s.fields["tls_certificateFile"] and s.fields["tls_certificateFile"]:formvalue(t) or ""
+ local key = s.fields["tls_keyFile"] and s.fields["tls_keyFile"]:formvalue(t) or ""
+ if ca == "" or key == "" then
+ return nil, translate("Public key and Private key path can not be empty!")
+ end
+ end
+ return value
+ end
+end
+o:depends({ protocol = "http" })
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless" })
+o:depends({ protocol = "trojan" })
+o:depends({ protocol = "anytls" })
+
+-- https://github.com/SagerNet/sing-box/commit/d2a04c4e41e6cef0937331cb6d10211f431caaab
+if singbox_tags:find("with_utls") then
+ -- [[ REALITY部分 ]] --
+ o = s:option(Flag, "reality", translate("REALITY"))
+ o.default = 0
+ o:depends({ protocol = "http", tls = true })
+ o:depends({ protocol = "vmess", tls = true })
+ o:depends({ protocol = "vless", tls = true })
+ o:depends({ protocol = "trojan", tls = true })
+ o:depends({ protocol = "anytls", tls = true })
+
+ o = s:option(Value, "reality_private_key", translate("Private Key"))
+ o:depends({ reality = true })
+
+ o = s:option(Value, "reality_shortId", translate("Short Id"))
+ o:depends({ reality = true })
+
+ o = s:option(Value, "reality_handshake_server", translate("Handshake Server"))
+ o.default = "google.com"
+ o:depends({ reality = true })
+
+ o = s:option(Value, "reality_handshake_server_port", translate("Handshake Server Port"))
+ o.datatype = "port"
+ o.default = "443"
+ o:depends({ reality = true })
+end
+
+o = s:option(ListValue, "alpn", translate("ALPN"))
+o.default = "default"
+o:value("default", translate("Default"))
+o:value("h3")
+o:value("h2")
+o:value("h3,h2")
+o:value("http/1.1")
+o:value("h2,http/1.1")
+o:value("h3,h2,http/1.1")
+o:depends({ tls = true, reality = false })
+o:depends({ protocol = "hysteria" })
+
+-- [[ TLS部分 ]] --
+
+o = s:option(FileUpload, "tls_certificateFile", translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
+o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
+if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
+o:depends({ tls = true, reality = false })
+o:depends({ protocol = "naive" })
+o:depends({ protocol = "hysteria" })
+o:depends({ protocol = "tuic" })
+o:depends({ protocol = "hysteria2" })
+o.validate = function(self, value, t)
+ if value and value ~= "" then
+ if not api.fs.access(value) then
+ return nil, translate("Can't find this file!")
+ else
+ return value
+ end
+ end
+ return nil
+end
+
+o = s:option(FileUpload, "tls_keyFile", translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
+o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key"
+if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
+o:depends({ tls = true, reality = false })
+o:depends({ protocol = "naive" })
+o:depends({ protocol = "hysteria" })
+o:depends({ protocol = "tuic" })
+o:depends({ protocol = "hysteria2" })
+o.validate = function(self, value, t)
+ if value and value ~= "" then
+ if not api.fs.access(value) then
+ return nil, translate("Can't find this file!")
+ else
+ return value
+ end
+ end
+ return nil
+end
+
+o = s:option(Flag, "ech", translate("ECH"))
+o.default = "0"
+o:depends({ tls = true, flow = "", reality = false })
+o:depends({ protocol = "naive" })
+o:depends({ protocol = "hysteria" })
+o:depends({ protocol = "tuic" })
+o:depends({ protocol = "hysteria2", hysteria2_realms = false })
+
+o = s:option(TextValue, "ech_key", translate("ECH Key"))
+o.default = ""
+o.rows = 5
+o.wrap = "off"
+o:depends({ ech = true })
+o.validate = function(self, value)
+ value = value:gsub("^%s+", ""):gsub("%s+$","\n"):gsub("\r\n","\n"):gsub("[ \t]*\n[ \t]*", "\n")
+ value = value:gsub("^%s*\n", "")
+ if value:sub(-1) == "\n" then
+ value = value:sub(1, -2)
+ end
+ return value
+end
+
+o = s:option(ListValue, "transport", translate("Transport"))
+o:value("tcp", "TCP")
+o:value("http", "HTTP")
+o:value("ws", "WebSocket")
+o:value("httpupgrade", "HTTPUpgrade")
+o:value("quic", "QUIC")
+o:value("grpc", "gRPC")
+o:depends({ protocol = "shadowsocks" })
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless" })
+o:depends({ protocol = "trojan" })
+
+-- [[ HTTP部分 ]]--
+
+o = s:option(DynamicList, "http_host", translate("HTTP Host"))
+o:depends({ transport = "http" })
+
+o = s:option(Value, "http_path", translate("HTTP Path"))
+o:depends({ transport = "http" })
+
+-- [[ WebSocket部分 ]]--
+
+o = s:option(Value, "ws_host", translate("WebSocket Host"))
+o:depends({ transport = "ws" })
+
+o = s:option(Value, "ws_path", translate("WebSocket Path"))
+o:depends({ transport = "ws" })
+
+-- [[ HTTPUpgrade部分 ]]--
+
+o = s:option(Value, "httpupgrade_host", translate("HTTPUpgrade Host"))
+o:depends({ transport = "httpupgrade" })
+
+o = s:option(Value, "httpupgrade_path", translate("HTTPUpgrade Path"))
+o:depends({ transport = "httpupgrade" })
+
+-- [[ gRPC部分 ]]--
+o = s:option(Value, "grpc_serviceName", "ServiceName")
+o:depends({ transport = "grpc" })
+
+-- [[ Mux ]]--
+o = s:option(Flag, "mux", translate("Mux"))
+o.rmempty = false
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless", flow = "" })
+o:depends({ protocol = "shadowsocks" })
+o:depends({ protocol = "trojan" })
+
+-- [[ TCP Brutal ]]--
+o = s:option(Flag, "tcpbrutal", translate("TCP Brutal"))
+o.default = 0
+o:depends({ mux = true })
+
+o = s:option(Value, "tcpbrutal_up_mbps", translate("Max upload Mbps"))
+o.default = "10"
+o:depends({ tcpbrutal = true })
+
+o = s:option(Value, "tcpbrutal_down_mbps", translate("Max download Mbps"))
+o.default = "50"
+o:depends({ tcpbrutal = true })
+
+if singbox_tags:find("with_wireguard") then
+ o = s:option(Flag, "wireguard_system_interface", translate("System interface"))
+ o.default = 0
+ o:depends({ protocol = "wireguard" })
+
+ o = s:option(Value, "wireguard_mtu", "MTU")
+ o.default = "1408"
+ o:depends({ protocol = "wireguard" })
+
+ o = s:option(DynamicList, "wireguard_local_address", translate("Local Address"))
+ o:depends({ protocol = "wireguard" })
+
+ o = s:option(Value, "wireguard_private_key", translate("Private Key"))
+ o.datatype = "base64"
+ o:depends({ protocol = "wireguard" })
+
+ o = s:option(Value, "wireguard_public_key", translate("Public Key"))
+ o.datatype = "base64"
+ o:depends({ protocol = "wireguard" })
+
+ o = s:option(DummyValue, "gen_wireguard_key")
+ o.template = m:template_path("/server/gen_wireguard_key")
+ o:depends({ protocol = "wireguard" })
+end
+
+o = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
+o.default = "0"
+o:depends({ custom = false, is_endpoint = "" })
+
+o = s:option(Flag, "accept_lan", translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
+o.default = "0"
+o:depends({ custom = false })
+
+local nodes_table = {}
+for k, e in ipairs(api.get_valid_nodes()) do
+ if e.node_type == "normal" and e.type == type_name then
+ nodes_table[#nodes_table + 1] = {
+ id = e[".name"],
+ remarks = e["remark"],
+ group = e["group"]
+ }
+ end
+end
+
+o = s:option(ListValue, "outbound_node", translate("outbound node"))
+o:value("", translate("Close"))
+o:value("_socks", translate("Custom Socks"))
+o:value("_http", translate("Custom HTTP"))
+o:value("_iface", translate("Custom Interface"))
+o.template = m:template_path("/cbi/nodes_listvalue")
+o.group = {"","","",""}
+for k, v in pairs(nodes_table) do
+ o:value(v.id, v.remarks)
+ o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+end
+o:depends({ custom = false })
+
+o = s:option(Value, "outbound_node_address", translate("Address (Support Domain Name)"))
+o:depends({ outbound_node = "_socks" })
+o:depends({ outbound_node = "_http" })
+
+o = s:option(Value, "outbound_node_port", translate("Port"))
+o.datatype = "port"
+o:depends({ outbound_node = "_socks" })
+o:depends({ outbound_node = "_http" })
+
+o = s:option(Value, "outbound_node_username", translate("Username"))
+o:depends({ outbound_node = "_socks" })
+o:depends({ outbound_node = "_http" })
+
+o = s:option(Value, "outbound_node_password", translate("Password"))
+o.password = true
+o:depends({ outbound_node = "_socks" })
+o:depends({ outbound_node = "_http" })
+
+o = s:option(Value, "outbound_node_iface", translate("Interface"))
+o:depends({ outbound_node = "_iface" })
+local netdev_list = api.get_network_devices()
+for _, d in ipairs(netdev_list) do
+ o:value(d.name, d.label)
+end
+
+o = s:option(Flag, "log", translate("Log"))
+o.default = "1"
+o.rmempty = false
+
+o = s:option(ListValue, "loglevel", translate("Log Level"))
+o.default = "info"
+o:value("debug")
+o:value("info")
+o:value("warn")
+o:value("error")
+o:depends({ log = true })
+
+api.luci_types(s1, s)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/2_xray.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/2_xray.lua
new file mode 100644
index 00000000000..a27e518c8d4
--- /dev/null
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/2_xray.lua
@@ -0,0 +1,540 @@
+if not api.finded_com("xray") then
+ return
+end
+
+-- [[ Xray ]]
+local m, s1 = ...
+local type_name = "Xray"
+
+s1.fields["type"]:value(type_name, "Xray")
+if not s1.fields["type"].default then
+ s1.fields["type"].default = type_name
+end
+
+if not s1.val["type"] then
+ s1.val["type"] = type_name
+end
+
+if s1.val["type"] and s1.val["type"] ~= type_name then
+ return
+end
+
+local s = NamedSection(m, arg[1], "server")
+s.type_name = type_name
+s.option_prefix = "xray_"
+
+local ss_method_list = {
+ "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
+}
+
+local header_type_list = {
+ "none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
+}
+
+o = s:option(Flag, "custom", translate("Use Custom Config"))
+
+o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
+o.rows = 10
+o.wrap = "off"
+o:depends({ custom = true })
+o.datatype = "json"
+local o_validate = o.validate
+o.validate = function(self, value)
+ local v = o_validate(self, value)
+ if v then return v end
+ return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
+end
+o.custom_cfgvalue = function(self, section, value)
+ local config_str = m:get(section, "config_str")
+ if config_str then
+ return api.base64Decode(config_str)
+ end
+end
+o.custom_write = function(self, section, value)
+ m:set(section, "config_str", api.base64Encode(value) or "")
+end
+
+o = s:option(ListValue, "protocol", translate("Protocol"))
+o:value("socks", "Socks")
+o:value("http", "HTTP")
+o:value("vmess", "Vmess")
+o:value("vless", "VLESS")
+o:value("shadowsocks", "Shadowsocks")
+o:value("trojan", "Trojan")
+o:value("hysteria2", "Hysteria2")
+o:value("wireguard", "WireGuard")
+o:value("tunnel", "Tunnel")
+o:depends({ custom = false })
+
+o = s:option(Value, "port", translate("Listen Port"))
+o.datatype = "port"
+o:depends({ custom = false })
+
+o = s:option(DynamicList, "users", translate("User"))
+for i, v in ipairs(user_list) do
+ o:value(v[".name"], v.username)
+end
+o:depends({ protocol = "http" })
+o:depends({ protocol = "socks" })
+o:depends({ protocol = "shadowsocks" })
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless" })
+o:depends({ protocol = "trojan" })
+o:depends({ protocol = "hysteria2" })
+o:depends({ protocol = "wireguard" })
+
+o = s:option(ListValue, "d_protocol", translate("Destination protocol"))
+o:value("tcp", "TCP")
+o:value("udp", "UDP")
+o:value("tcp,udp", "TCP,UDP")
+o:depends({ protocol = "tunnel" })
+
+o = s:option(Value, "d_address", translate("Destination address"))
+o:depends({ protocol = "tunnel" })
+
+o = s:option(Value, "d_port", translate("Destination port"))
+o.datatype = "port"
+o:depends({ protocol = "tunnel" })
+
+o = s:option(Value, "decryption", translate("Encrypt Method") .. " (decryption)")
+o.default = "none"
+o.placeholder = "none"
+o:depends({ protocol = "vless" })
+o.validate = function(self, value)
+ value = api.trim(value)
+ return (value == "" and "none" or value)
+end
+
+o = s:option(ListValue, "ss_method", translate("Encrypt Method"))
+o.rewrite_option = "method"
+for a, t in ipairs(ss_method_list) do o:value(t) end
+o:depends({ protocol = "shadowsocks" })
+
+o = s:option(Value, "ss_password", translate("Password"))
+o:depends({ protocol = "shadowsocks" })
+
+o = s:option(ListValue, "ss_network", translate("Transport"))
+o.default = "tcp,udp"
+o:value("tcp", "TCP")
+o:value("udp", "UDP")
+o:value("tcp,udp", "TCP,UDP")
+o:depends({ protocol = "shadowsocks" })
+
+o = s:option(Flag, "udp_forward", translate("UDP Forward"))
+o.default = "1"
+o.rmempty = false
+o:depends({ protocol = "socks" })
+
+o = s:option(ListValue, "flow", translate("flow"))
+o.default = ""
+o:value("", translate("Disable"))
+o:value("xtls-rprx-vision")
+o:depends({ protocol = "vless" })
+
+---- [[ hysteria2 ]]
+o = s:option(Flag, "hysteria2_realms", translate("Realms"))
+o.default = "0"
+o:depends({ protocol = "hysteria2"})
+
+o = s:option(Value, "hysteria2_realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
+o:depends({ hysteria2_realms = "1" })
+o.validate = function(self, value)
+ value = api.trim(value)
+ local realm = api.parse_realm_uri(value)
+ if realm then return value end
+ return nil, translate("Invalid Realm URL.")
+end
+
+o = s:option(DynamicList, "hysteria2_realm_stun", translate("Realm STUN"))
+o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
+o:depends({ hysteria2_realms = "1" })
+
+o = s:option(ListValue, "hysteria2_obfs_type", translate("Obfs Type"))
+o:value("", translate("Disable"))
+o:value("salamander")
+o:value("gecko")
+o:depends({ protocol = "hysteria2" })
+
+o = s:option(Value, "hysteria2_obfs_password", translate("Obfs Password"))
+o:depends({ hysteria2_obfs_type = "salamander" })
+o:depends({ hysteria2_obfs_type = "gecko" })
+
+o = s:option(Value, "hysteria2_obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
+o.datatype = "uinteger"
+o.placeholder = "512"
+o.default = "512"
+o:depends({ hysteria2_obfs_type = "gecko" })
+
+o = s:option(Value, "hysteria2_obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
+o.datatype = "uinteger"
+o.placeholder = "1200"
+o.default = "1200"
+o:depends({ hysteria2_obfs_type = "gecko" })
+
+o = s:option(Flag, "hysteria2_ignore_client_bandwidth", translate("Client BBR Flow Control"))
+o.default = 0
+o:depends({ protocol = "hysteria2" })
+
+o = s:option(Value, "hysteria2_up_mbps", translate("Max upload Mbps"))
+o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false })
+
+o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps"))
+o:depends({ protocol = "hysteria2", hysteria2_ignore_client_bandwidth = false })
+
+---- [[ TLS ]]
+o = s:option(Flag, "tls", translate("TLS"))
+o.default = 0
+o.validate = function(self, value, t)
+ if value then
+ local reality = s.fields["reality"] and s.fields["reality"]:formvalue(t) or nil
+ if reality and reality == "1" then return value end
+ if value == "1" then
+ local ca = s.fields["tls_certificateFile"] and s.fields["tls_certificateFile"]:formvalue(t) or ""
+ local key = s.fields["tls_keyFile"] and s.fields["tls_keyFile"]:formvalue(t) or ""
+ if ca == "" or key == "" then
+ return nil, translate("Public key and Private key path can not be empty!")
+ end
+ end
+ return value
+ end
+end
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless" })
+o:depends({ protocol = "shadowsocks" })
+o:depends({ protocol = "trojan" })
+
+-- [[ REALITY部分 ]] --
+o = s:option(Flag, "reality", translate("REALITY"))
+o.default = 0
+o:depends({ tls = true })
+
+o = s:option(Value, "reality_private_key", translate("Private Key"))
+o:depends({ reality = true })
+
+o = s:option(DynamicList, "reality_shortId", translate("Short Id"))
+o:depends({ reality = true })
+
+o = s:option(Value, "reality_dest", translate("Dest"))
+o.default = "google.com:443"
+o:depends({ reality = true })
+
+o = s:option(DynamicList, "reality_serverNames", translate("serverNames"))
+o:depends({ reality = true })
+
+o = s:option(ListValue, "alpn", translate("alpn"))
+o.default = "default"
+o:value("default", translate("Default"))
+o:value("h3")
+o:value("h2")
+o:value("h3,h2")
+o:value("http/1.1")
+o:value("h2,http/1.1")
+o:value("h3,h2,http/1.1")
+o:depends({ tls = true, reality = false })
+
+o = s:option(Flag, "use_mldsa65Seed", translate("ML-DSA-65"))
+o.default = "0"
+o:depends({ reality = true })
+
+o = s:option(TextValue, "reality_mldsa65Seed", "ML-DSA-65 " .. translate("Private Key"))
+o.default = ""
+o.rows = 5
+o.wrap = "soft"
+o:depends({ use_mldsa65Seed = true })
+o.validate = function(self, value)
+ return api.trim(value:gsub("[\r\n]", ""))
+end
+
+-- o = s:option(Value, "minversion", translate("minversion"))
+-- o.default = "1.3"
+-- o:value("1.3")
+--o:depends({ tls = true })
+
+-- [[ TLS部分 ]] --
+
+o = s:option(FileUpload, "tls_certificateFile", translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
+o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
+if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
+o:depends({ tls = true, reality = false })
+o:depends({ protocol = "hysteria2"})
+o.validate = function(self, value, t)
+ if value and value ~= "" then
+ if not api.fs.access(value) then
+ return nil, translate("Can't find this file!")
+ else
+ return value
+ end
+ end
+ return nil
+end
+
+o = s:option(FileUpload, "tls_keyFile", translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
+o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key"
+if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
+o:depends({ tls = true, reality = false })
+o:depends({ protocol = "hysteria2"})
+o.validate = function(self, value, t)
+ if value and value ~= "" then
+ if not api.fs.access(value) then
+ return nil, translate("Can't find this file!")
+ else
+ return value
+ end
+ end
+ return nil
+end
+
+o = s:option(Flag, "ech", translate("ECH"))
+o.default = "0"
+o:depends({ tls = true, reality = false })
+
+o = s:option(TextValue, "ech_key", translate("ECH Key"))
+o.default = ""
+o.rows = 5
+o.wrap = "soft"
+o:depends({ ech = true })
+o.validate = function(self, value)
+ return api.trim(value:gsub("[\r\n]", ""))
+end
+
+o = s:option(ListValue, "transport", translate("Transport"))
+o:value("raw", "RAW")
+o:value("mkcp", "mKCP")
+o:value("ws", "WebSocket")
+o:value("grpc", "gRPC")
+o:value("httpupgrade", "HttpUpgrade")
+o:value("xhttp", "XHTTP")
+o:depends({ protocol = "vmess" })
+o:depends({ protocol = "vless" })
+o:depends({ protocol = "shadowsocks" })
+o:depends({ protocol = "trojan" })
+
+-- [[ WebSocket部分 ]]--
+
+o = s:option(Value, "ws_host", translate("WebSocket Host"))
+o:depends({ transport = "ws" })
+
+o = s:option(Value, "ws_path", translate("WebSocket Path"))
+o:depends({ transport = "ws" })
+
+-- [[ HttpUpgrade部分 ]]--
+o = s:option(Value, "httpupgrade_host", translate("HttpUpgrade Host"))
+o:depends({ transport = "httpupgrade" })
+
+o = s:option(Value, "httpupgrade_path", translate("HttpUpgrade Path"))
+o.placeholder = "/"
+o:depends({ transport = "httpupgrade" })
+
+-- [[ XHTTP部分 ]]--
+o = s:option(Value, "xhttp_host", translate("XHTTP Host"))
+o:depends({ transport = "xhttp" })
+
+o = s:option(Value, "xhttp_path", translate("XHTTP Path"))
+o.placeholder = "/"
+o:depends({ transport = "xhttp" })
+
+o = s:option(Value, "xhttp_maxuploadsize", translate("maxUploadSize"))
+o.default = "1000000"
+o:depends({ transport = "xhttp" })
+
+o = s:option(Value, "xhttp_maxconcurrentuploads", translate("maxConcurrentUploads"))
+o.default = "10"
+o:depends({ transport = "xhttp" })
+
+-- [[ TCP部分 ]]--
+
+-- TCP伪装
+o = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
+o:value("none", "none")
+o:value("http", "http")
+o:depends({ transport = "raw" })
+
+-- HTTP域名
+o = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host"))
+o:depends({ tcp_guise = "http" })
+
+-- HTTP路径
+o = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
+o:depends({ tcp_guise = "http" })
+
+-- [[ mKCP部分 ]]--
+
+o = s:option(ListValue, "mkcp_guise", translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)
dns: Disguising traffic as DNS requests.'))
+for a, t in ipairs(header_type_list) do o:value(t) end
+o:depends({ transport = "mkcp" })
+
+o = s:option(Value, "mkcp_domain", translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
+o:depends({ mkcp_guise = "dns" })
+
+o = s:option(Value, "mkcp_mtu", translate("KCP MTU"))
+o.datatype = "uinteger"
+o.default = 1350
+o:depends({ transport = "mkcp" })
+
+o = s:option(Value, "mkcp_seed", translate("KCP Seed"))
+o:depends({ transport = "mkcp" })
+
+-- [[ gRPC部分 ]]--
+o = s:option(Value, "grpc_serviceName", "ServiceName")
+o:depends({ transport = "grpc" })
+
+--[[FinalMask]]
+o = s:option(Flag, "use_finalmask", "FinalMask")
+o.default = "0"
+o:depends({ custom = false, protocol = "vmess" })
+o:depends({ custom = false, protocol = "vless" })
+o:depends({ custom = false, protocol = "trojan" })
+o:depends({ custom = false, protocol = "shadowsocks" })
+o:depends({ custom = false, protocol = "hysteria2", hysteria2_realms = false })
+
+o = s:option(TextValue, "finalmask", "FinalMask JSON")
+o:depends({ use_finalmask = true })
+o.rows = 10
+o.wrap = "off"
+o.datatype = "json"
+o.custom_cfgvalue = function(self, section, value)
+ local raw = m:get(section, "finalmask")
+ if raw then
+ return api.base64Decode(raw)
+ end
+end
+o.custom_write = function(self, section, value)
+ m:set(section, "finalmask", api.base64Encode(value) or "")
+end
+
+--[[acceptProxyProtocol]]
+o = s:option(Flag, "acceptProxyProtocol", translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used."))
+o.default = "0"
+o:depends({ transport = "raw" })
+o:depends({ transport = "ws" })
+
+--[[Fast Open]]
+o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"))
+o.default = "0"
+o:depends({ protocol = "vmess", custom = false })
+o:depends({ protocol = "vless", custom = false })
+o:depends({ protocol = "shadowsocks", custom = false })
+o:depends({ protocol = "trojan", custom = false })
+
+-- [[ Fallback部分 ]]--
+o = s:option(Flag, "fallback", translate("Fallback"))
+o:depends({ protocol = "vless", transport = "raw" })
+o:depends({ protocol = "trojan", transport = "raw" })
+
+--[[
+o = s:option(Value, "fallback_alpn", "Fallback alpn")
+o:depends({ fallback = true })
+
+o = s:option(Value, "fallback_path", "Fallback path")
+o:depends({ fallback = true })
+
+o = s:option(Value, "fallback_dest", "Fallback dest")
+o:depends({ fallback = true })
+
+o = s:option(Value, "fallback_xver", "Fallback xver")
+o.default = 0
+o:depends({ fallback = true })
+]]--
+
+o = s:option(DynamicList, "fallback_list", "Fallback", translate("format: dest,path,xver"))
+o:depends({ fallback = true })
+
+-- Not supported at present
+--[[
+o = s:option(Flag, "wireguard_system_interface", translate("System interface"))
+o.default = 0
+o:depends({ protocol = "wireguard" })
+]]--
+
+o = s:option(Value, "wireguard_mtu", "MTU")
+o.default = "1420"
+o:depends({ protocol = "wireguard" })
+
+-- Not supported at present
+--[[
+o = s:option(DynamicList, "wireguard_local_address", translate("Local Address"))
+o:depends({ protocol = "wireguard" })
+]]--
+
+o = s:option(Value, "wireguard_private_key", translate("Private Key"))
+o.datatype = "base64"
+o:depends({ protocol = "wireguard" })
+
+o = s:option(Value, "wireguard_public_key", translate("Public Key"))
+o.datatype = "base64"
+o:depends({ protocol = "wireguard" })
+
+o = s:option(DummyValue, "gen_wireguard_key")
+o.template = m:template_path("/server/gen_wireguard_key")
+o:depends({ protocol = "wireguard" })
+
+o = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
+o.default = "0"
+o:depends({ custom = false })
+
+o = s:option(Flag, "accept_lan", translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
+o.default = "0"
+o:depends({ custom = false })
+
+local nodes_table = {}
+for k, e in ipairs(api.get_valid_nodes()) do
+ if e.node_type == "normal" and e.type == type_name then
+ nodes_table[#nodes_table + 1] = {
+ id = e[".name"],
+ remarks = e["remark"],
+ group = e["group"]
+ }
+ end
+end
+
+o = s:option(ListValue, "outbound_node", translate("outbound node"))
+o:value("", translate("Close"))
+o:value("_socks", translate("Custom Socks"))
+o:value("_http", translate("Custom HTTP"))
+o:value("_iface", translate("Custom Interface"))
+o.template = m:template_path("/cbi/nodes_listvalue")
+o.group = {"","","",""}
+for k, v in pairs(nodes_table) do
+ o:value(v.id, v.remarks)
+ o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
+end
+o:depends({ custom = false })
+
+o = s:option(Value, "outbound_node_address", translate("Address (Support Domain Name)"))
+o:depends({ outbound_node = "_socks"})
+o:depends({ outbound_node = "_http"})
+
+o = s:option(Value, "outbound_node_port", translate("Port"))
+o.datatype = "port"
+o:depends({ outbound_node = "_socks"})
+o:depends({ outbound_node = "_http"})
+
+o = s:option(Value, "outbound_node_username", translate("Username"))
+o:depends({ outbound_node = "_socks"})
+o:depends({ outbound_node = "_http"})
+
+o = s:option(Value, "outbound_node_password", translate("Password"))
+o.password = true
+o:depends({ outbound_node = "_socks"})
+o:depends({ outbound_node = "_http"})
+
+o = s:option(Value, "outbound_node_iface", translate("Interface"))
+o:depends({ outbound_node = "_iface"})
+local netdev_list = api.get_network_devices()
+for _, d in ipairs(netdev_list) do
+ o:value(d.name, d.label)
+end
+
+o = s:option(Flag, "log", translate("Log"))
+o.default = "1"
+o.rmempty = false
+
+o = s:option(ListValue, "loglevel", translate("Log Level"))
+o.default = "warning"
+o:value("debug")
+o:value("info")
+o:value("warning")
+o:value("error")
+o:depends({ log = true })
+
+api.luci_types(s1, s)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/3_ss-rust.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/3_ss-rust.lua
new file mode 100644
index 00000000000..40af02de9a2
--- /dev/null
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/3_ss-rust.lua
@@ -0,0 +1,79 @@
+if not api.is_finded("ssserver") then
+ return
+end
+
+-- [[ Shadowsocks Rust ]]
+local m, s1 = ...
+local type_name = "SS-Rust"
+
+s1.fields["type"]:value(type_name, translate("Shadowsocks Rust"))
+
+if not s1.val["type"] then
+ s1.val["type"] = type_name
+end
+
+if s1.val["type"] and s1.val["type"] ~= type_name then
+ return
+end
+
+local s = NamedSection(m, arg[1], "server")
+s.type_name = type_name
+s.option_prefix = "ssrust_"
+
+local ssrust_encrypt_method_list = {
+ "plain", "none",
+ "aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
+ "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
+}
+
+o = s:option(Flag, "custom", translate("Use Custom Config"))
+
+o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
+o.rows = 10
+o.wrap = "off"
+o:depends({ custom = true })
+o.datatype = "json"
+local o_validate = o.validate
+o.validate = function(self, value)
+ local v = o_validate(self, value)
+ if v then return v end
+ return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
+end
+o.custom_cfgvalue = function(self, section, value)
+ local config_str = m:get(section, "config_str")
+ if config_str then
+ return api.base64Decode(config_str)
+ end
+end
+o.custom_write = function(self, section, value)
+ m:set(section, "config_str", api.base64Encode(value) or "")
+end
+
+o = s:option(Value, "port", translate("Listen Port"))
+o.datatype = "port"
+o:depends({ custom = false })
+
+o = s:option(ListValue, "user", translate("User"))
+for i, v in ipairs(user_list) do
+ o:value(v[".name"], v.username)
+end
+o:depends({ custom = false })
+
+o = s:option(ListValue, "method", translate("Encrypt Method"))
+for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end
+o:depends({ custom = false })
+
+o = s:option(Value, "timeout", translate("Connection Timeout"))
+o.datatype = "uinteger"
+o.default = 300
+o:depends({ custom = false })
+
+o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"))
+o.default = "0"
+o:depends({ custom = false })
+
+o = s:option(Flag, "log", translate("Log"))
+o.default = "1"
+o.rmempty = false
+
+api.luci_types(s1, s)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ssr.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/4_ssr.lua
similarity index 53%
rename from applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ssr.lua
rename to applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/4_ssr.lua
index e8db9f6be3d..f74092330f5 100644
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ssr.lua
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/4_ssr.lua
@@ -1,24 +1,24 @@
-local m, s = ...
-
if not api.is_finded("ssr-server") then
return
end
+-- [[ ShadowsocksR ]]
+local m, s1 = ...
local type_name = "SSR"
--- [[ ShadowsocksR ]]
+s1.fields["type"]:value(type_name, translate("ShadowsocksR"))
-s.fields["type"]:value(type_name, translate("ShadowsocksR"))
+if not s1.val["type"] then
+ s1.val["type"] = type_name
+end
-if s.val["type"] and s.val["type"] ~= type_name then
+if s1.val["type"] and s1.val["type"] ~= type_name then
return
end
-local option_prefix = "ssr_"
-
-local function _n(name)
- return option_prefix .. name
-end
+local s = NamedSection(m, arg[1], "server")
+s.type_name = type_name
+s.option_prefix = "ssr_"
local ssr_encrypt_method_list = {
"none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
@@ -39,47 +39,12 @@ local ssr_obfs_list = {
"tls1.0_session_auth", "tls1.2_ticket_auth"
}
-o = s:option(Flag, _n("custom"), translate("Use Custom Config"))
-
-o = s:option(Value, _n("port"), translate("Listen Port"))
-o.datatype = "port"
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Value, _n("password"), translate("Password"))
-o.password = true
-o:depends({ [_n("custom")] = false })
-
-o = s:option(ListValue, _n("method"), translate("Encrypt Method"))
-for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end
-o:depends({ [_n("custom")] = false })
-
-o = s:option(ListValue, _n("protocol"), translate("Protocol"))
-for a, t in ipairs(ssr_protocol_list) do o:value(t) end
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Value, _n("protocol_param"), translate("Protocol_param"))
-o:depends({ [_n("custom")] = false })
-
-o = s:option(ListValue, _n("obfs"), translate("Obfs"))
-for a, t in ipairs(ssr_obfs_list) do o:value(t) end
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Value, _n("obfs_param"), translate("Obfs_param"))
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
-o.datatype = "uinteger"
-o.default = 300
-o:depends({ [_n("custom")] = false })
+o = s:option(Flag, "custom", translate("Use Custom Config"))
-o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
-o.default = "0"
-o:depends({ [_n("custom")] = false })
-
-o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
+o = s:option(TextValue, "custom_config", translate("Custom Config") .. " (JSON)")
o.rows = 10
o.wrap = "off"
-o:depends({ [_n("custom")] = true })
+o:depends({ custom = true })
o.datatype = "json"
local o_validate = o.validate
o.validate = function(self, value)
@@ -97,12 +62,49 @@ o.custom_write = function(self, section, value)
m:set(section, "config_str", api.base64Encode(value) or "")
end
-o = s:option(Flag, _n("udp_forward"), translate("UDP Forward"))
+o = s:option(Value, "port", translate("Listen Port"))
+o.datatype = "port"
+o:depends({ custom = false })
+
+o = s:option(ListValue, "user", translate("User"))
+for i, v in ipairs(user_list) do
+ o:value(v[".name"], v.username)
+end
+o:depends({ custom = false })
+
+o = s:option(ListValue, "method", translate("Encrypt Method"))
+for a, t in ipairs(ssr_encrypt_method_list) do o:value(t) end
+o:depends({ custom = false })
+
+o = s:option(ListValue, "protocol", translate("Protocol"))
+for a, t in ipairs(ssr_protocol_list) do o:value(t) end
+o:depends({ custom = false })
+
+o = s:option(Value, "protocol_param", translate("Protocol_param"))
+o:depends({ custom = false })
+
+o = s:option(ListValue, "obfs", translate("Obfs"))
+for a, t in ipairs(ssr_obfs_list) do o:value(t) end
+o:depends({ custom = false })
+
+o = s:option(Value, "obfs_param", translate("Obfs_param"))
+o:depends({ custom = false })
+
+o = s:option(Value, "timeout", translate("Connection Timeout"))
+o.datatype = "uinteger"
+o.default = 300
+o:depends({ custom = false })
+
+o = s:option(Flag, "tcp_fast_open", "TCP " .. translate("Fast Open"))
+o.default = "0"
+o:depends({ custom = false })
+
+o = s:option(Flag, "udp_forward", translate("UDP Forward"))
o.default = "1"
o.rmempty = false
-o = s:option(Flag, _n("log"), translate("Log"))
+o = s:option(Flag, "log", translate("Log"))
o.default = "1"
o.rmempty = false
-api.luci_types(arg[1], m, s, type_name, option_prefix)
+api.luci_types(s1, s)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/5_hysteria2.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/5_hysteria2.lua
new file mode 100644
index 00000000000..3ba460439a1
--- /dev/null
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/5_hysteria2.lua
@@ -0,0 +1,154 @@
+if not api.finded_com("hysteria") then
+ return
+end
+
+-- [[ Hysteria2 ]]
+local m, s1 = ...
+local type_name = "Hysteria2"
+
+s1.fields["type"]:value(type_name, "Hysteria2")
+
+if s1.val["type"] and s1.val["type"] ~= type_name then
+ return
+end
+
+local s = NamedSection(m, arg[1], "server")
+s.type_name = type_name
+s.option_prefix = "hysteria2_"
+
+local function _n(name)
+ return s.option_prefix .. name
+end
+
+o = s:option(Flag, "custom", translate("Use Custom Config"))
+
+o = s:option(TextValue, "custom_config", translate("Custom Config"))
+o.rows = 10
+o.wrap = "off"
+o:depends({ custom = true })
+o.datatype = "json"
+local o_validate = o.validate
+o.validate = function(self, value)
+ local v = o_validate(self, value)
+ if v then return v end
+ return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
+end
+o.custom_cfgvalue = function(self, section, value)
+ local config_str = m:get(section, "config_str")
+ if config_str then
+ return api.base64Decode(config_str)
+ end
+end
+o.custom_write = function(self, section, value)
+ m:set(section, "config_str", api.base64Encode(value) or "")
+end
+
+o = s:option(Value, "port", translate("Listen Port"))
+o.datatype = "port"
+o:depends({ custom = false })
+
+o = s:option(DynamicList, "users", translate("User"))
+for i, v in ipairs(user_list) do
+ o:value(v[".name"], v.username)
+end
+o:depends({ custom = false })
+
+o = s:option(Flag, "realms", translate("Realms"))
+o.default = "0"
+o.rewrite_option = _n(o.option)
+o:depends({ custom = false })
+
+o = s:option(Value, "realm_url", translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
+o.rewrite_option = _n(o.option)
+o:depends({ realms = "1" })
+o.validate = function(self, value)
+ value = api.trim(value)
+ local realm = api.parse_realm_uri(value)
+ if realm then return value end
+ return nil, translate("Invalid Realm URL.")
+end
+
+o = s:option(DynamicList, "realm_stun", translate("Realm STUN"))
+o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
+o.rewrite_option = _n(o.option)
+o:depends({ realms = "1" })
+
+o = s:option(ListValue, "obfs_type", translate("Obfs Type"))
+o:value("", translate("Disable"))
+o:value("salamander")
+o:value("gecko")
+o.rewrite_option = _n(o.option)
+o:depends({ custom = false })
+
+o = s:option(Value, "obfs_password", translate("Obfs Password"))
+o.rewrite_option = _n(o.option)
+o:depends({ obfs_type = "salamander" })
+o:depends({ obfs_type = "gecko" })
+
+o = s:option(Value, "obfs_MinPacketSize", translate("Gecko Packet Size (min)"))
+o.datatype = "uinteger"
+o.placeholder = "512"
+o.default = "512"
+o:depends({ obfs_type = "gecko" })
+o.rewrite_option = _n(o.option)
+
+o = s:option(Value, "obfs_MaxPacketSize", translate("Gecko Packet Size (max)"))
+o.datatype = "uinteger"
+o.placeholder = "1200"
+o.default = "1200"
+o:depends({ obfs_type = "gecko" })
+o.rewrite_option = _n(o.option)
+
+o = s:option(Flag, "udp", translate("UDP"))
+o.default = "1"
+o.rewrite_option = _n(o.option)
+o:depends({ custom = false })
+
+o = s:option(Value, "up_mbps", translate("Max upload Mbps"))
+o.rewrite_option = _n(o.option)
+o:depends({ custom = false })
+
+o = s:option(Value, "down_mbps", translate("Max download Mbps"))
+o.rewrite_option = _n(o.option)
+o:depends({ custom = false })
+
+o = s:option(Flag, "ignoreClientBandwidth", translate("ignoreClientBandwidth"))
+o.default = "0"
+o.rewrite_option = _n(o.option)
+o:depends({ custom = false })
+
+o = s:option(FileUpload, "tls_certificateFile", translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
+o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
+if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
+o.validate = function(self, value, t)
+ if value and value ~= "" then
+ if not api.fs.access(value) then
+ return nil, translate("Can't find this file!")
+ else
+ return value
+ end
+ end
+ return nil
+end
+o:depends({ custom = false })
+
+o = s:option(FileUpload, "tls_keyFile", translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
+o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key"
+if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
+o.validate = function(self, value, t)
+ if value and value ~= "" then
+ if not api.fs.access(value) then
+ return nil, translate("Can't find this file!")
+ else
+ return value
+ end
+ end
+ return nil
+end
+o:depends({ custom = false })
+
+o = s:option(Flag, "log", translate("Log"))
+o.default = "1"
+o.rmempty = false
+
+api.luci_types(s1, s)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua
deleted file mode 100644
index 2ffc6f8efe2..00000000000
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/hysteria2.lua
+++ /dev/null
@@ -1,155 +0,0 @@
-local m, s = ...
-
-if not api.finded_com("hysteria") then
- return
-end
-
-local fs = api.fs
-
-local type_name = "Hysteria2"
-
--- [[ Hysteria2 ]]
-
-s.fields["type"]:value(type_name, "Hysteria2")
-
-if s.val["type"] and s.val["type"] ~= type_name then
- return
-end
-
-local option_prefix = "hysteria2_"
-
-local function _n(name)
- return option_prefix .. name
-end
-
-o = s:option(Flag, _n("custom"), translate("Use Custom Config"))
-
-o = s:option(Value, _n("port"), translate("Listen Port"))
-o.datatype = "port"
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Flag, _n("realms"), translate("Realms"))
-o.default = "0"
-o.rewrite_option = o.option
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Value, _n("realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
-o.rewrite_option = o.option
-o:depends({ [_n("realms")] = "1" })
-o.validate = function(self, value)
- value = api.trim(value)
- local realm = api.parse_realm_uri(value)
- if realm then return value end
- return nil, translate("Invalid Realm URL.")
-end
-
-o = s:option(DynamicList, _n("realm_stun"), translate("Realm STUN"))
-o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
-o.rewrite_option = o.option
-o:depends({ [_n("realms")] = "1" })
-
-o = s:option(Value, _n("auth_password"), translate("Auth Password"))
-o.password = true
-o.rewrite_option = o.option
-o:depends({ [_n("custom")] = false })
-
-o = s:option(ListValue, _n("obfs_type"), translate("Obfs Type"))
-o:value("", translate("Disable"))
-o:value("salamander")
-o:value("gecko")
-o.rewrite_option = o.option
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Value, _n("obfs_password"), translate("Obfs Password"))
-o.rewrite_option = o.option
-o:depends({ [_n("obfs_type")] = "salamander" })
-o:depends({ [_n("obfs_type")] = "gecko" })
-
-o = s:option(Value, _n("obfs_MinPacketSize"), translate("Gecko Packet Size (min)"))
-o.datatype = "uinteger"
-o.placeholder = "512"
-o.default = "512"
-o:depends({ [_n("obfs_type")] = "gecko" })
-o.rewrite_option = o.option
-
-o = s:option(Value, _n("obfs_MaxPacketSize"), translate("Gecko Packet Size (max)"))
-o.datatype = "uinteger"
-o.placeholder = "1200"
-o.default = "1200"
-o:depends({ [_n("obfs_type")] = "gecko" })
-o.rewrite_option = o.option
-
-o = s:option(Flag, _n("udp"), translate("UDP"))
-o.default = "1"
-o.rewrite_option = o.option
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Value, _n("up_mbps"), translate("Max upload Mbps"))
-o.rewrite_option = o.option
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Value, _n("down_mbps"), translate("Max download Mbps"))
-o.rewrite_option = o.option
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Flag, _n("ignoreClientBandwidth"), translate("ignoreClientBandwidth"))
-o.default = "0"
-o.rewrite_option = o.option
-o:depends({ [_n("custom")] = false })
-
-o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
-o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
-if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
-o.validate = function(self, value, t)
- if value and value ~= "" then
- if not fs.access(value) then
- return nil, translate("Can't find this file!")
- else
- return value
- end
- end
- return nil
-end
-o:depends({ [_n("custom")] = false })
-
-o = s:option(FileUpload, _n("tls_keyFile"), translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
-o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key"
-if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
-o.validate = function(self, value, t)
- if value and value ~= "" then
- if not fs.access(value) then
- return nil, translate("Can't find this file!")
- else
- return value
- end
- end
- return nil
-end
-o:depends({ [_n("custom")] = false })
-
-o = s:option(TextValue, _n("custom_config"), translate("Custom Config"))
-o.rows = 10
-o.wrap = "off"
-o:depends({ [_n("custom")] = true })
-o.datatype = "json"
-local o_validate = o.validate
-o.validate = function(self, value)
- local v = o_validate(self, value)
- if v then return v end
- return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
-end
-o.custom_cfgvalue = function(self, section, value)
- local config_str = m:get(section, "config_str")
- if config_str then
- return api.base64Decode(config_str)
- end
-end
-o.custom_write = function(self, section, value)
- m:set(section, "config_str", api.base64Encode(value) or "")
-end
-
-o = s:option(Flag, _n("log"), translate("Log"))
-o.default = "1"
-o.rmempty = false
-
-api.luci_types(arg[1], m, s, type_name, option_prefix)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua
deleted file mode 100644
index 3bb52c2ba49..00000000000
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ray.lua
+++ /dev/null
@@ -1,528 +0,0 @@
-local m, s = ...
-
-if not api.finded_com("xray") then
- return
-end
-
-local type_name = "Xray"
-
--- [[ Xray ]]
-
-s.fields["type"]:value(type_name, "Xray")
-if not s.fields["type"].default then
- s.fields["type"].default = type_name
-end
-
-if s.val["type"] and s.val["type"] ~= type_name then
- return
-end
-
-local option_prefix = "xray_"
-
-local function _n(name)
- return option_prefix .. name
-end
-
-local x_ss_method_list = {
- "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
-}
-
-local header_type_list = {
- "none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
-}
-
-o = s:option(Flag, _n("custom"), translate("Use Custom Config"))
-
-o = s:option(ListValue, _n("protocol"), translate("Protocol"))
-o:value("socks", "Socks")
-o:value("http", "HTTP")
-o:value("vmess", "Vmess")
-o:value("vless", "VLESS")
-o:value("shadowsocks", "Shadowsocks")
-o:value("trojan", "Trojan")
-o:value("hysteria2", translate("Hysteria2"))
-o:value("dokodemo-door", "dokodemo-door")
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Value, _n("port"), translate("Listen Port"))
-o.datatype = "port"
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Flag, _n("auth"), translate("Auth"))
-o.validate = function(self, value, t)
- if value and value == "1" then
- local user_v = s.fields[_n("username")] and s.fields[_n("username")]:formvalue(t) or ""
- local pass_v = s.fields[_n("password")] and s.fields[_n("password")]:formvalue(t) or ""
- if user_v == "" or pass_v == "" then
- return nil, translate("Username and Password must be used together!")
- end
- end
- return value
-end
-o:depends({ [_n("protocol")] = "socks" })
-o:depends({ [_n("protocol")] = "http" })
-
-o = s:option(Value, _n("username"), translate("Username"))
-o:depends({ [_n("auth")] = true })
-
-o = s:option(Value, _n("password"), translate("Password"))
-o.password = true
-o:depends({ [_n("auth")] = true })
-o:depends({ [_n("protocol")] = "shadowsocks" })
-
-o = s:option(ListValue, _n("d_protocol"), translate("Destination protocol"))
-o:value("tcp", "TCP")
-o:value("udp", "UDP")
-o:value("tcp,udp", "TCP,UDP")
-o:depends({ [_n("protocol")] = "dokodemo-door" })
-
-o = s:option(Value, _n("d_address"), translate("Destination address"))
-o:depends({ [_n("protocol")] = "dokodemo-door" })
-
-o = s:option(Value, _n("d_port"), translate("Destination port"))
-o.datatype = "port"
-o:depends({ [_n("protocol")] = "dokodemo-door" })
-
-o = s:option(Value, _n("decryption"), translate("Encrypt Method") .. " (decryption)")
-o.default = "none"
-o.placeholder = "none"
-o:depends({ [_n("protocol")] = "vless" })
-o.validate = function(self, value)
- value = api.trim(value)
- return (value == "" and "none" or value)
-end
-
-o = s:option(ListValue, _n("x_ss_method"), translate("Encrypt Method"))
-o.rewrite_option = "method"
-for a, t in ipairs(x_ss_method_list) do o:value(t) end
-o:depends({ [_n("protocol")] = "shadowsocks" })
-
-o = s:option(ListValue, _n("ss_network"), translate("Transport"))
-o.default = "tcp,udp"
-o:value("tcp", "TCP")
-o:value("udp", "UDP")
-o:value("tcp,udp", "TCP,UDP")
-o:depends({ [_n("protocol")] = "shadowsocks" })
-
-o = s:option(Flag, _n("udp_forward"), translate("UDP Forward"))
-o.default = "1"
-o.rmempty = false
-o:depends({ [_n("protocol")] = "socks" })
-
-o = s:option(DynamicList, _n("uuid"), translate("ID") .. "/" .. translate("Password"))
-for i = 1, 3 do
- o:value(api.gen_uuid())
-end
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless" })
-o:depends({ [_n("protocol")] = "trojan" })
-
-o = s:option(ListValue, _n("flow"), translate("flow"))
-o.default = ""
-o:value("", translate("Disable"))
-o:value("xtls-rprx-vision")
-o:depends({ [_n("protocol")] = "vless" })
-
----- [[ hysteria2 ]]
-o = s:option(Flag, _n("hysteria2_realms"), translate("Realms"))
-o.default = "0"
-o:depends({ [_n("protocol")] = "hysteria2"})
-
-o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
-o:depends({ [_n("hysteria2_realms")] = "1" })
-o.validate = function(self, value)
- value = api.trim(value)
- local realm = api.parse_realm_uri(value)
- if realm then return value end
- return nil, translate("Invalid Realm URL.")
-end
-
-o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
-o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
-o:depends({ [_n("hysteria2_realms")] = "1" })
-
-o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password"))
-o.password = true
-o:depends({ [_n("protocol")] = "hysteria2"})
-
-o = s:option(ListValue, _n("hysteria2_obfs_type"), translate("Obfs Type"))
-o:value("", translate("Disable"))
-o:value("salamander")
-o:value("gecko")
-o:depends({ [_n("protocol")] = "hysteria2" })
-
-o = s:option(Value, _n("hysteria2_obfs_password"), translate("Obfs Password"))
-o:depends({ [_n("hysteria2_obfs_type")] = "salamander" })
-o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
-
-o = s:option(Value, _n("hysteria2_obfs_MinPacketSize"), translate("Gecko Packet Size (min)"))
-o.datatype = "uinteger"
-o.placeholder = "512"
-o.default = "512"
-o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
-
-o = s:option(Value, _n("hysteria2_obfs_MaxPacketSize"), translate("Gecko Packet Size (max)"))
-o.datatype = "uinteger"
-o.placeholder = "1200"
-o.default = "1200"
-o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
-
-o = s:option(Flag, _n("hysteria2_ignore_client_bandwidth"), translate("Client BBR Flow Control"))
-o.default = 0
-o:depends({ [_n("protocol")] = "hysteria2" })
-
-o = s:option(Value, _n("hysteria2_up_mbps"), translate("Max upload Mbps"))
-o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_ignore_client_bandwidth")] = false })
-
-o = s:option(Value, _n("hysteria2_down_mbps"), translate("Max download Mbps"))
-o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_ignore_client_bandwidth")] = false })
-
----- [[ TLS ]]
-o = s:option(Flag, _n("tls"), translate("TLS"))
-o.default = 0
-o.validate = function(self, value, t)
- if value then
- local reality = s.fields[_n("reality")] and s.fields[_n("reality")]:formvalue(t) or nil
- if reality and reality == "1" then return value end
- if value == "1" then
- local ca = s.fields[_n("tls_certificateFile")] and s.fields[_n("tls_certificateFile")]:formvalue(t) or ""
- local key = s.fields[_n("tls_keyFile")] and s.fields[_n("tls_keyFile")]:formvalue(t) or ""
- if ca == "" or key == "" then
- return nil, translate("Public key and Private key path can not be empty!")
- end
- end
- return value
- end
-end
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless" })
-o:depends({ [_n("protocol")] = "shadowsocks" })
-o:depends({ [_n("protocol")] = "trojan" })
-
--- [[ REALITY部分 ]] --
-o = s:option(Flag, _n("reality"), translate("REALITY"))
-o.default = 0
-o:depends({ [_n("tls")] = true })
-
-o = s:option(Value, _n("reality_private_key"), translate("Private Key"))
-o:depends({ [_n("reality")] = true })
-
-o = s:option(DynamicList, _n("reality_shortId"), translate("Short Id"))
-o:depends({ [_n("reality")] = true })
-
-o = s:option(Value, _n("reality_dest"), translate("Dest"))
-o.default = "google.com:443"
-o:depends({ [_n("reality")] = true })
-
-o = s:option(DynamicList, _n("reality_serverNames"), translate("serverNames"))
-o:depends({ [_n("reality")] = true })
-
-o = s:option(ListValue, _n("alpn"), translate("alpn"))
-o.default = "default"
-o:value("default", translate("Default"))
-o:value("h3")
-o:value("h2")
-o:value("h3,h2")
-o:value("http/1.1")
-o:value("h2,http/1.1")
-o:value("h3,h2,http/1.1")
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-
-o = s:option(Flag, _n("use_mldsa65Seed"), translate("ML-DSA-65"))
-o.default = "0"
-o:depends({ [_n("reality")] = true })
-
-o = s:option(TextValue, _n("reality_mldsa65Seed"), "ML-DSA-65 " .. translate("Private Key"))
-o.default = ""
-o.rows = 5
-o.wrap = "soft"
-o:depends({ [_n("use_mldsa65Seed")] = true })
-o.validate = function(self, value)
- return api.trim(value:gsub("[\r\n]", ""))
-end
-
--- o = s:option(Value, _n("minversion"), translate("minversion"))
--- o.default = "1.3"
--- o:value("1.3")
---o:depends({ [_n("tls")] = true })
-
--- [[ TLS部分 ]] --
-
-o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
-o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
-if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-o:depends({ [_n("protocol")] = "hysteria2"})
-o.validate = function(self, value, t)
- if value and value ~= "" then
- if not fs.access(value) then
- return nil, translate("Can't find this file!")
- else
- return value
- end
- end
- return nil
-end
-
-o = s:option(FileUpload, _n("tls_keyFile"), translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
-o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key"
-if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-o:depends({ [_n("protocol")] = "hysteria2"})
-o.validate = function(self, value, t)
- if value and value ~= "" then
- if not fs.access(value) then
- return nil, translate("Can't find this file!")
- else
- return value
- end
- end
- return nil
-end
-
-o = s:option(Flag, _n("ech"), translate("ECH"))
-o.default = "0"
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-
-o = s:option(TextValue, _n("ech_key"), translate("ECH Key"))
-o.default = ""
-o.rows = 5
-o.wrap = "soft"
-o:depends({ [_n("ech")] = true })
-o.validate = function(self, value)
- return api.trim(value:gsub("[\r\n]", ""))
-end
-
-o = s:option(ListValue, _n("transport"), translate("Transport"))
-o:value("raw", "RAW")
-o:value("mkcp", "mKCP")
-o:value("ws", "WebSocket")
-o:value("grpc", "gRPC")
-o:value("httpupgrade", "HttpUpgrade")
-o:value("xhttp", "XHTTP")
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless" })
-o:depends({ [_n("protocol")] = "shadowsocks" })
-o:depends({ [_n("protocol")] = "trojan" })
-
--- [[ WebSocket部分 ]]--
-
-o = s:option(Value, _n("ws_host"), translate("WebSocket Host"))
-o:depends({ [_n("transport")] = "ws" })
-
-o = s:option(Value, _n("ws_path"), translate("WebSocket Path"))
-o:depends({ [_n("transport")] = "ws" })
-
--- [[ HttpUpgrade部分 ]]--
-o = s:option(Value, _n("httpupgrade_host"), translate("HttpUpgrade Host"))
-o:depends({ [_n("transport")] = "httpupgrade" })
-
-o = s:option(Value, _n("httpupgrade_path"), translate("HttpUpgrade Path"))
-o.placeholder = "/"
-o:depends({ [_n("transport")] = "httpupgrade" })
-
--- [[ XHTTP部分 ]]--
-o = s:option(Value, _n("xhttp_host"), translate("XHTTP Host"))
-o:depends({ [_n("transport")] = "xhttp" })
-
-o = s:option(Value, _n("xhttp_path"), translate("XHTTP Path"))
-o.placeholder = "/"
-o:depends({ [_n("transport")] = "xhttp" })
-
-o = s:option(Value, _n("xhttp_maxuploadsize"), translate("maxUploadSize"))
-o.default = "1000000"
-o:depends({ [_n("transport")] = "xhttp" })
-
-o = s:option(Value, _n("xhttp_maxconcurrentuploads"), translate("maxConcurrentUploads"))
-o.default = "10"
-o:depends({ [_n("transport")] = "xhttp" })
-
--- [[ TCP部分 ]]--
-
--- TCP伪装
-o = s:option(ListValue, _n("tcp_guise"), translate("Camouflage Type"))
-o:value("none", "none")
-o:value("http", "http")
-o:depends({ [_n("transport")] = "raw" })
-
--- HTTP域名
-o = s:option(DynamicList, _n("tcp_guise_http_host"), translate("HTTP Host"))
-o:depends({ [_n("tcp_guise")] = "http" })
-
--- HTTP路径
-o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path"))
-o:depends({ [_n("tcp_guise")] = "http" })
-
--- [[ mKCP部分 ]]--
-
-o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)
dns: Disguising traffic as DNS requests.'))
-for a, t in ipairs(header_type_list) do o:value(t) end
-o:depends({ [_n("transport")] = "mkcp" })
-
-o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
-o:depends({ [_n("mkcp_guise")] = "dns" })
-
-o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
-o.datatype = "uinteger"
-o.default = 1350
-o:depends({ [_n("transport")] = "mkcp" })
-
-o = s:option(Value, _n("mkcp_seed"), translate("KCP Seed"))
-o:depends({ [_n("transport")] = "mkcp" })
-
--- [[ gRPC部分 ]]--
-o = s:option(Value, _n("grpc_serviceName"), "ServiceName")
-o:depends({ [_n("transport")] = "grpc" })
-
---[[FinalMask]]
-o = s:option(Flag, _n("use_finalmask"), "FinalMask")
-o.default = "0"
-o:depends({ [_n("custom")] = false, [_n("protocol")] = "vmess" })
-o:depends({ [_n("custom")] = false, [_n("protocol")] = "vless" })
-o:depends({ [_n("custom")] = false, [_n("protocol")] = "trojan" })
-o:depends({ [_n("custom")] = false, [_n("protocol")] = "shadowsocks" })
-o:depends({ [_n("custom")] = false, [_n("protocol")] = "wireguard" })
-o:depends({ [_n("custom")] = false, [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
-
-o = s:option(TextValue, _n("finalmask"), "FinalMask JSON")
-o:depends({ [_n("use_finalmask")] = true })
-o.rows = 10
-o.wrap = "off"
-o.datatype = "json"
-o.custom_cfgvalue = function(self, section, value)
- local raw = m:get(section, "finalmask")
- if raw then
- return api.base64Decode(raw)
- end
-end
-o.custom_write = function(self, section, value)
- m:set(section, "finalmask", api.base64Encode(value) or "")
-end
-
---[[acceptProxyProtocol]]
-o = s:option(Flag, _n("acceptProxyProtocol"), translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used."))
-o.default = "0"
-o:depends({ [_n("custom")] = false })
-
---[[Fast Open]]
-o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
-o.default = "0"
-o:depends({ [_n("protocol")] = "vmess", [_n("custom")] = false })
-o:depends({ [_n("protocol")] = "vless", [_n("custom")] = false })
-o:depends({ [_n("protocol")] = "shadowsocks", [_n("custom")] = false })
-o:depends({ [_n("protocol")] = "trojan", [_n("custom")] = false })
-
--- [[ Fallback部分 ]]--
-o = s:option(Flag, _n("fallback"), translate("Fallback"))
-o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "raw" })
-o:depends({ [_n("protocol")] = "trojan", [_n("transport")] = "raw" })
-
---[[
-o = s:option(Value, _n("fallback_alpn"), "Fallback alpn")
-o:depends({ [_n("fallback")] = true })
-
-o = s:option(Value, _n("fallback_path"), "Fallback path")
-o:depends({ [_n("fallback")] = true })
-
-o = s:option(Value, _n("fallback_dest"), "Fallback dest")
-o:depends({ [_n("fallback")] = true })
-
-o = s:option(Value, _n("fallback_xver"), "Fallback xver")
-o.default = 0
-o:depends({ [_n("fallback")] = true })
-]]--
-
-o = s:option(DynamicList, _n("fallback_list"), "Fallback", translate("format: dest,path,xver"))
-o:depends({ [_n("fallback")] = true })
-
-o = s:option(Flag, _n("bind_local"), translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
-o.default = "0"
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Flag, _n("accept_lan"), translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
-o.default = "0"
-o:depends({ [_n("custom")] = false })
-
-local nodes_table = {}
-for k, e in ipairs(api.get_valid_nodes()) do
- if e.node_type == "normal" and e.type == type_name then
- nodes_table[#nodes_table + 1] = {
- id = e[".name"],
- remarks = e["remark"],
- group = e["group"]
- }
- end
-end
-
-o = s:option(ListValue, _n("outbound_node"), translate("outbound node"))
-o:value("", translate("Close"))
-o:value("_socks", translate("Custom Socks"))
-o:value("_http", translate("Custom HTTP"))
-o:value("_iface", translate("Custom Interface"))
-o.template = api.appname .. "/cbi/nodes_listvalue"
-o.group = {"","","",""}
-for k, v in pairs(nodes_table) do
- o:value(v.id, v.remarks)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
-end
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Value, _n("outbound_node_address"), translate("Address (Support Domain Name)"))
-o:depends({ [_n("outbound_node")] = "_socks"})
-o:depends({ [_n("outbound_node")] = "_http"})
-
-o = s:option(Value, _n("outbound_node_port"), translate("Port"))
-o.datatype = "port"
-o:depends({ [_n("outbound_node")] = "_socks"})
-o:depends({ [_n("outbound_node")] = "_http"})
-
-o = s:option(Value, _n("outbound_node_username"), translate("Username"))
-o:depends({ [_n("outbound_node")] = "_socks"})
-o:depends({ [_n("outbound_node")] = "_http"})
-
-o = s:option(Value, _n("outbound_node_password"), translate("Password"))
-o.password = true
-o:depends({ [_n("outbound_node")] = "_socks"})
-o:depends({ [_n("outbound_node")] = "_http"})
-
-o = s:option(Value, _n("outbound_node_iface"), translate("Interface"))
-o:depends({ [_n("outbound_node")] = "_iface"})
-local netdev_list = api.get_network_devices()
-for _, d in ipairs(netdev_list) do
- o:value(d.name, d.label)
-end
-
-o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
-o.rows = 10
-o.wrap = "off"
-o:depends({ [_n("custom")] = true })
-o.datatype = "json"
-local o_validate = o.validate
-o.validate = function(self, value)
- local v = o_validate(self, value)
- if v then return v end
- return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
-end
-o.custom_cfgvalue = function(self, section, value)
- local config_str = m:get(section, "config_str")
- if config_str then
- return api.base64Decode(config_str)
- end
-end
-o.custom_write = function(self, section, value)
- m:set(section, "config_str", api.base64Encode(value) or "")
-end
-
-o = s:option(Flag, _n("log"), translate("Log"))
-o.default = "1"
-o.rmempty = false
-
-o = s:option(ListValue, _n("loglevel"), translate("Log Level"))
-o.default = "warning"
-o:value("debug")
-o:value("info")
-o:value("warning")
-o:value("error")
-o:depends({ [_n("log")] = true })
-
-api.luci_types(arg[1], m, s, type_name, option_prefix)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua
deleted file mode 100644
index 74e39e79531..00000000000
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua
+++ /dev/null
@@ -1,525 +0,0 @@
-local m, s = ...
-
-local singbox_bin = api.finded_com("sing-box")
-
-if not singbox_bin then
- return
-end
-
-local type_name = "sing-box"
-
--- [[ Sing-Box ]]
-
-s.fields["type"]:value(type_name, "Sing-Box")
-if not s.fields["type"].default then
- s.fields["type"].default = type_name
-end
-
-if s.val["type"] and s.val["type"] ~= type_name then
- return
-end
-
-local option_prefix = "singbox_"
-
-local function _n(name)
- return option_prefix .. name
-end
-
-local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'")
-
-local ss_method_list = {
- "none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305",
- "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
-}
-
-o = s:option(Flag, _n("custom"), translate("Use Custom Config"))
-
-o = s:option(ListValue, _n("protocol"), translate("Protocol"))
-o:value("mixed", "Mixed")
-o:value("socks", "Socks")
-o:value("http", "HTTP")
-o:value("shadowsocks", "Shadowsocks")
-o:value("vmess", "Vmess")
-o:value("vless", "VLESS")
-o:value("trojan", "Trojan")
-o:value("naive", "Naive")
-if singbox_tags:find("with_quic") then
- o:value("hysteria", "Hysteria")
-end
-if singbox_tags:find("with_quic") then
- o:value("tuic", "TUIC")
-end
-if singbox_tags:find("with_quic") then
- o:value("hysteria2", "Hysteria2")
-end
-o:value("anytls", "AnyTLS")
-o:value("direct", "Direct")
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Value, _n("port"), translate("Listen Port"))
-o.datatype = "port"
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Flag, _n("auth"), translate("Auth"))
-o.validate = function(self, value, t)
- if value and value == "1" then
- local user_v = s.fields[_n("username")] and s.fields[_n("username")]:formvalue(t) or ""
- local pass_v = s.fields[_n("password")] and s.fields[_n("password")]:formvalue(t) or ""
- if user_v == "" or pass_v == "" then
- return nil, translate("Username and Password must be used together!")
- end
- end
- return value
-end
-o:depends({ [_n("protocol")] = "mixed" })
-o:depends({ [_n("protocol")] = "socks" })
-o:depends({ [_n("protocol")] = "http" })
-
-o = s:option(Value, _n("username"), translate("Username"))
-o:depends({ [_n("auth")] = true })
-o:depends({ [_n("protocol")] = "naive" })
-o:depends({ [_n("protocol")] = "anytls" })
-
-o = s:option(Value, _n("password"), translate("Password"))
-o.password = true
-o:depends({ [_n("auth")] = true })
-o:depends({ [_n("protocol")] = "shadowsocks" })
-o:depends({ [_n("protocol")] = "naive" })
-o:depends({ [_n("protocol")] = "tuic" })
-o:depends({ [_n("protocol")] = "anytls" })
-
-if singbox_tags:find("with_quic") then
- o = s:option(Value, _n("hysteria_up_mbps"), translate("Max upload Mbps"))
- o.default = "100"
- o:depends({ [_n("protocol")] = "hysteria" })
-
- o = s:option(Value, _n("hysteria_down_mbps"), translate("Max download Mbps"))
- o.default = "100"
- o:depends({ [_n("protocol")] = "hysteria" })
-
- o = s:option(Value, _n("hysteria_obfs"), translate("Obfs Password"))
- o:depends({ [_n("protocol")] = "hysteria" })
-
- o = s:option(ListValue, _n("hysteria_auth_type"), translate("Auth Type"))
- o:value("disable", translate("Disable"))
- o:value("string", translate("STRING"))
- o:value("base64", translate("BASE64"))
- o:depends({ [_n("protocol")] = "hysteria" })
-
- o = s:option(Value, _n("hysteria_auth_password"), translate("Auth Password"))
- o.password = true
- o:depends({ [_n("protocol")] = "hysteria", [_n("hysteria_auth_type")] = "string"})
- o:depends({ [_n("protocol")] = "hysteria", [_n("hysteria_auth_type")] = "base64"})
-
- o = s:option(Value, _n("hysteria_recv_window_conn"), translate("QUIC stream receive window"))
- o:depends({ [_n("protocol")] = "hysteria" })
-
- o = s:option(Value, _n("hysteria_recv_window_client"), translate("QUIC connection receive window"))
- o:depends({ [_n("protocol")] = "hysteria" })
-
- o = s:option(Value, _n("hysteria_max_conn_client"), translate("QUIC concurrent bidirectional streams"))
- o.default = "1024"
- o:depends({ [_n("protocol")] = "hysteria" })
-
- o = s:option(Flag, _n("hysteria_disable_mtu_discovery"), translate("Disable MTU detection"))
- o:depends({ [_n("protocol")] = "hysteria" })
-end
-
-if singbox_tags:find("with_quic") then
- o = s:option(ListValue, _n("tuic_congestion_control"), translate("Congestion control algorithm"))
- o.default = "cubic"
- o:value("bbr", translate("BBR"))
- o:value("cubic", translate("CUBIC"))
- o:value("new_reno", translate("New Reno"))
- o:depends({ [_n("protocol")] = "tuic" })
-
- o = s:option(Flag, _n("tuic_zero_rtt_handshake"), translate("Enable 0-RTT QUIC handshake"))
- o.default = 0
- o:depends({ [_n("protocol")] = "tuic" })
-
- o = s:option(Value, _n("tuic_heartbeat"), translate("Heartbeat interval(second)"))
- o.datatype = "uinteger"
- o.default = "3"
- o:depends({ [_n("protocol")] = "tuic" })
-
- o = s:option(ListValue, _n("tuic_alpn"), translate("QUIC TLS ALPN"))
- o.default = "default"
- o:value("default", translate("Default"))
- o:value("h3")
- o:value("h2")
- o:value("h3,h2")
- o:value("http/1.1")
- o:value("h2,http/1.1")
- o:value("h3,h2,http/1.1")
- o:value("spdy/3.1")
- o:value("h3,spdy/3.1")
- o:depends({ [_n("protocol")] = "tuic" })
-end
-
-if singbox_tags:find("with_quic") then
- o = s:option(Flag, _n("hysteria2_realms"), translate("Realms"))
- o.default = "0"
- o:depends({ [_n("protocol")] = "hysteria2"})
-
- o = s:option(Value, _n("hysteria2_realm_url"), translate("Realm URL"), translate("Example:") .. "realm://public@realm.hy2.io/your-realm-name")
- o:depends({ [_n("hysteria2_realms")] = "1" })
- o.validate = function(self, value)
- value = api.trim(value)
- local realm = api.parse_realm_uri(value)
- if realm then return value end
- return nil, translate("Invalid Realm URL.")
- end
-
- o = s:option(DynamicList, _n("hysteria2_realm_stun"), translate("Realm STUN"))
- o.default = { "stun.sip.us:3478", "stun.nextcloud.com:3478", "global.stun.twilio.com:3478" }
- o:depends({ [_n("hysteria2_realms")] = "1" })
-
- o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password"))
- o.password = true
- o:depends({ [_n("protocol")] = "hysteria2"})
-
- o = s:option(ListValue, _n("hysteria2_obfs_type"), translate("Obfs Type"))
- o:value("", translate("Disable"))
- o:value("salamander")
- o:value("gecko")
- o:depends({ [_n("protocol")] = "hysteria2" })
-
- o = s:option(Value, _n("hysteria2_obfs_password"), translate("Obfs Password"))
- o:depends({ [_n("hysteria2_obfs_type")] = "salamander" })
- o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
-
- o = s:option(Value, _n("hysteria2_obfs_MinPacketSize"), translate("Gecko Packet Size (min)"))
- o.datatype = "uinteger"
- o.placeholder = "512"
- o.default = "512"
- o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
-
- o = s:option(Value, _n("hysteria2_obfs_MaxPacketSize"), translate("Gecko Packet Size (max)"))
- o.datatype = "uinteger"
- o.placeholder = "1200"
- o.default = "1200"
- o:depends({ [_n("hysteria2_obfs_type")] = "gecko" })
-
- o = s:option(Flag, _n("hysteria2_ignore_client_bandwidth"), translate("Client BBR Flow Control"), translate("Commands the client to use the BBR flow control algorithm"))
- o.default = 0
- o:depends({ [_n("protocol")] = "hysteria2" })
-
- o = s:option(Value, _n("hysteria2_up_mbps"), translate("Max upload Mbps"))
- o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_ignore_client_bandwidth")] = false })
-
- o = s:option(Value, _n("hysteria2_down_mbps"), translate("Max download Mbps"))
- o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_ignore_client_bandwidth")] = false })
-end
-
-o = s:option(ListValue, _n("d_protocol"), translate("Destination protocol"))
-o:value("tcp", "TCP")
-o:value("udp", "UDP")
-o:value("tcp,udp", "TCP,UDP")
-o:depends({ [_n("protocol")] = "direct" })
-
-o = s:option(Value, _n("d_address"), translate("Destination address"))
-o:depends({ [_n("protocol")] = "direct" })
-
-o = s:option(Value, _n("d_port"), translate("Destination port"))
-o.datatype = "port"
-o:depends({ [_n("protocol")] = "direct" })
-
-o = s:option(Value, _n("decryption"), translate("Encrypt Method"))
-o.default = "none"
-o:depends({ [_n("protocol")] = "vless" })
-
-o = s:option(ListValue, _n("ss_method"), translate("Encrypt Method"))
-o.rewrite_option = "method"
-for a, t in ipairs(ss_method_list) do o:value(t) end
-o:depends({ [_n("protocol")] = "shadowsocks" })
-
-o = s:option(DynamicList, _n("uuid"), translate("ID") .. "/" .. translate("Password"))
-for i = 1, 3 do
- o:value(api.gen_uuid())
-end
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless" })
-o:depends({ [_n("protocol")] = "trojan" })
-o:depends({ [_n("protocol")] = "tuic" })
-
-o = s:option(ListValue, _n("flow"), translate("flow"))
-o.default = ""
-o:value("", translate("Disable"))
-o:value("xtls-rprx-vision")
-o:depends({ [_n("protocol")] = "vless" , [_n("tls")] = true })
-
-o = s:option(Flag, _n("tls"), translate("TLS"))
-o.default = 0
-o.validate = function(self, value, t)
- if value then
- local reality = s.fields[_n("reality")] and s.fields[_n("reality")]:formvalue(t) or nil
- if reality and reality == "1" then return value end
- if value == "1" then
- local ca = s.fields[_n("tls_certificateFile")] and s.fields[_n("tls_certificateFile")]:formvalue(t) or ""
- local key = s.fields[_n("tls_keyFile")] and s.fields[_n("tls_keyFile")]:formvalue(t) or ""
- if ca == "" or key == "" then
- return nil, translate("Public key and Private key path can not be empty!")
- end
- end
- return value
- end
-end
-o:depends({ [_n("protocol")] = "http" })
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless" })
-o:depends({ [_n("protocol")] = "trojan" })
-o:depends({ [_n("protocol")] = "anytls" })
-
--- https://github.com/SagerNet/sing-box/commit/d2a04c4e41e6cef0937331cb6d10211f431caaab
-if singbox_tags:find("with_utls") then
- -- [[ REALITY部分 ]] --
- o = s:option(Flag, _n("reality"), translate("REALITY"))
- o.default = 0
- o:depends({ [_n("protocol")] = "http", [_n("tls")] = true })
- o:depends({ [_n("protocol")] = "vmess", [_n("tls")] = true })
- o:depends({ [_n("protocol")] = "vless", [_n("tls")] = true })
- o:depends({ [_n("protocol")] = "trojan", [_n("tls")] = true })
- o:depends({ [_n("protocol")] = "anytls", [_n("tls")] = true })
-
- o = s:option(Value, _n("reality_private_key"), translate("Private Key"))
- o:depends({ [_n("reality")] = true })
-
- o = s:option(Value, _n("reality_shortId"), translate("Short Id"))
- o:depends({ [_n("reality")] = true })
-
- o = s:option(Value, _n("reality_handshake_server"), translate("Handshake Server"))
- o.default = "google.com"
- o:depends({ [_n("reality")] = true })
-
- o = s:option(Value, _n("reality_handshake_server_port"), translate("Handshake Server Port"))
- o.datatype = "port"
- o.default = "443"
- o:depends({ [_n("reality")] = true })
-end
-
-o = s:option(ListValue, _n("alpn"), translate("ALPN"))
-o.default = "default"
-o:value("default", translate("Default"))
-o:value("h3")
-o:value("h2")
-o:value("h3,h2")
-o:value("http/1.1")
-o:value("h2,http/1.1")
-o:value("h3,h2,http/1.1")
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-o:depends({ [_n("protocol")] = "hysteria" })
-
--- [[ TLS部分 ]] --
-
-o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
-o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
-if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-o:depends({ [_n("protocol")] = "naive" })
-o:depends({ [_n("protocol")] = "hysteria" })
-o:depends({ [_n("protocol")] = "tuic" })
-o:depends({ [_n("protocol")] = "hysteria2" })
-o.validate = function(self, value, t)
- if value and value ~= "" then
- if not fs.access(value) then
- return nil, translate("Can't find this file!")
- else
- return value
- end
- end
- return nil
-end
-
-o = s:option(FileUpload, _n("tls_keyFile"), translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
-o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key"
-if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
-o:depends({ [_n("tls")] = true, [_n("reality")] = false })
-o:depends({ [_n("protocol")] = "naive" })
-o:depends({ [_n("protocol")] = "hysteria" })
-o:depends({ [_n("protocol")] = "tuic" })
-o:depends({ [_n("protocol")] = "hysteria2" })
-o.validate = function(self, value, t)
- if value and value ~= "" then
- if not fs.access(value) then
- return nil, translate("Can't find this file!")
- else
- return value
- end
- end
- return nil
-end
-
-o = s:option(Flag, _n("ech"), translate("ECH"))
-o.default = "0"
-o:depends({ [_n("tls")] = true, [_n("flow")] = "", [_n("reality")] = false })
-o:depends({ [_n("protocol")] = "naive" })
-o:depends({ [_n("protocol")] = "hysteria" })
-o:depends({ [_n("protocol")] = "tuic" })
-o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_realms")] = false })
-
-o = s:option(TextValue, _n("ech_key"), translate("ECH Key"))
-o.default = ""
-o.rows = 5
-o.wrap = "off"
-o:depends({ [_n("ech")] = true })
-o.validate = function(self, value)
- value = value:gsub("^%s+", ""):gsub("%s+$","\n"):gsub("\r\n","\n"):gsub("[ \t]*\n[ \t]*", "\n")
- value = value:gsub("^%s*\n", "")
- if value:sub(-1) == "\n" then
- value = value:sub(1, -2)
- end
- return value
-end
-
-o = s:option(ListValue, _n("transport"), translate("Transport"))
-o:value("tcp", "TCP")
-o:value("http", "HTTP")
-o:value("ws", "WebSocket")
-o:value("httpupgrade", "HTTPUpgrade")
-o:value("quic", "QUIC")
-o:value("grpc", "gRPC")
-o:depends({ [_n("protocol")] = "shadowsocks" })
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless" })
-o:depends({ [_n("protocol")] = "trojan" })
-
--- [[ HTTP部分 ]]--
-
-o = s:option(DynamicList, _n("http_host"), translate("HTTP Host"))
-o:depends({ [_n("transport")] = "http" })
-
-o = s:option(Value, _n("http_path"), translate("HTTP Path"))
-o:depends({ [_n("transport")] = "http" })
-
--- [[ WebSocket部分 ]]--
-
-o = s:option(Value, _n("ws_host"), translate("WebSocket Host"))
-o:depends({ [_n("transport")] = "ws" })
-
-o = s:option(Value, _n("ws_path"), translate("WebSocket Path"))
-o:depends({ [_n("transport")] = "ws" })
-
--- [[ HTTPUpgrade部分 ]]--
-
-o = s:option(Value, _n("httpupgrade_host"), translate("HTTPUpgrade Host"))
-o:depends({ [_n("transport")] = "httpupgrade" })
-
-o = s:option(Value, _n("httpupgrade_path"), translate("HTTPUpgrade Path"))
-o:depends({ [_n("transport")] = "httpupgrade" })
-
--- [[ gRPC部分 ]]--
-o = s:option(Value, _n("grpc_serviceName"), "ServiceName")
-o:depends({ [_n("transport")] = "grpc" })
-
--- [[ Mux ]]--
-o = s:option(Flag, _n("mux"), translate("Mux"))
-o.rmempty = false
-o:depends({ [_n("protocol")] = "vmess" })
-o:depends({ [_n("protocol")] = "vless", [_n("flow")] = "" })
-o:depends({ [_n("protocol")] = "shadowsocks" })
-o:depends({ [_n("protocol")] = "trojan" })
-
--- [[ TCP Brutal ]]--
-o = s:option(Flag, _n("tcpbrutal"), translate("TCP Brutal"))
-o.default = 0
-o:depends({ [_n("mux")] = true })
-
-o = s:option(Value, _n("tcpbrutal_up_mbps"), translate("Max upload Mbps"))
-o.default = "10"
-o:depends({ [_n("tcpbrutal")] = true })
-
-o = s:option(Value, _n("tcpbrutal_down_mbps"), translate("Max download Mbps"))
-o.default = "50"
-o:depends({ [_n("tcpbrutal")] = true })
-
-o = s:option(Flag, _n("bind_local"), translate("Bind Local"), translate("When selected, it can only be accessed localhost."))
-o.default = "0"
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Flag, _n("accept_lan"), translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
-o.default = "0"
-o:depends({ [_n("custom")] = false })
-
-local nodes_table = {}
-for k, e in ipairs(api.get_valid_nodes()) do
- if e.node_type == "normal" and e.type == type_name then
- nodes_table[#nodes_table + 1] = {
- id = e[".name"],
- remarks = e["remark"],
- group = e["group"]
- }
- end
-end
-
-o = s:option(ListValue, _n("outbound_node"), translate("outbound node"))
-o:value("", translate("Close"))
-o:value("_socks", translate("Custom Socks"))
-o:value("_http", translate("Custom HTTP"))
-o:value("_iface", translate("Custom Interface"))
-o.template = api.appname .. "/cbi/nodes_listvalue"
-o.group = {"","","",""}
-for k, v in pairs(nodes_table) do
- o:value(v.id, v.remarks)
- o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
-end
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Value, _n("outbound_node_address"), translate("Address (Support Domain Name)"))
-o:depends({ [_n("outbound_node")] = "_socks" })
-o:depends({ [_n("outbound_node")] = "_http" })
-
-o = s:option(Value, _n("outbound_node_port"), translate("Port"))
-o.datatype = "port"
-o:depends({ [_n("outbound_node")] = "_socks" })
-o:depends({ [_n("outbound_node")] = "_http" })
-
-o = s:option(Value, _n("outbound_node_username"), translate("Username"))
-o:depends({ [_n("outbound_node")] = "_socks" })
-o:depends({ [_n("outbound_node")] = "_http" })
-
-o = s:option(Value, _n("outbound_node_password"), translate("Password"))
-o.password = true
-o:depends({ [_n("outbound_node")] = "_socks" })
-o:depends({ [_n("outbound_node")] = "_http" })
-
-o = s:option(Value, _n("outbound_node_iface"), translate("Interface"))
-o:depends({ [_n("outbound_node")] = "_iface" })
-local netdev_list = api.get_network_devices()
-for _, d in ipairs(netdev_list) do
- o:value(d.name, d.label)
-end
-
-o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
-o.rows = 10
-o.wrap = "off"
-o:depends({ [_n("custom")] = true })
-o.datatype = "json"
-local o_validate = o.validate
-o.validate = function(self, value)
- local v = o_validate(self, value)
- if v then return v end
- return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
-end
-o.custom_cfgvalue = function(self, section, value)
- local config_str = m:get(section, "config_str")
- if config_str then
- return api.base64Decode(config_str)
- end
-end
-o.custom_write = function(self, section, value)
- m:set(section, "config_str", api.base64Encode(value) or "")
-end
-
-o = s:option(Flag, _n("log"), translate("Log"))
-o.default = "1"
-o.rmempty = false
-
-o = s:option(ListValue, _n("loglevel"), translate("Log Level"))
-o.default = "info"
-o:value("debug")
-o:value("info")
-o:value("warn")
-o:value("error")
-o:depends({ [_n("log")] = true })
-
-api.luci_types(arg[1], m, s, type_name, option_prefix)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/socks.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/socks.lua
deleted file mode 100644
index 6444b8e7437..00000000000
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/socks.lua
+++ /dev/null
@@ -1,51 +0,0 @@
-local m, s = ...
-
-if not api.is_finded("microsocks") then
- return
-end
-
-local type_name = "Socks"
-
-local option_prefix = "socks_"
-
-local function _n(name)
- return option_prefix .. name
-end
-
--- [[ microsocks ]]
-
-s.fields["type"]:value(type_name, "Socks")
-if not s.fields["type"].default then
- s.fields["type"].default = type_name
-end
-
-if s.val["type"] and s.val["type"] ~= type_name then
- return
-end
-
-o = s:option(Value, _n("port"), translate("Listen Port"))
-o.datatype = "port"
-
-o = s:option(Flag, _n("auth"), translate("Auth"))
-o.validate = function(self, value, t)
- if value and value == "1" then
- local user_v = s.fields[_n("username")] and s.fields[_n("username")]:formvalue(t) or ""
- local pass_v = s.fields[_n("password")] and s.fields[_n("password")]:formvalue(t) or ""
- if user_v == "" or pass_v == "" then
- return nil, translate("Username and Password must be used together!")
- end
- end
- return value
-end
-
-o = s:option(Value, _n("username"), translate("Username"))
-o:depends({ [_n("auth")] = true })
-
-o = s:option(Value, _n("password"), translate("Password"))
-o.password = true
-o:depends({ [_n("auth")] = true })
-
-o = s:option(Flag, _n("log"), translate("Log"))
-o.default = "1"
-
-api.luci_types(arg[1], m, s, type_name, option_prefix)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss-rust.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss-rust.lua
deleted file mode 100644
index 5fb51a744ed..00000000000
--- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/type/ss-rust.lua
+++ /dev/null
@@ -1,77 +0,0 @@
-local m, s = ...
-
-if not api.is_finded("ssserver") then
- return
-end
-
-local type_name = "SS-Rust"
-
--- [[ Shadowsocks Rust ]]
-
-s.fields["type"]:value(type_name, translate("Shadowsocks Rust"))
-
-if s.val["type"] and s.val["type"] ~= type_name then
- return
-end
-
-local option_prefix = "ssrust_"
-
-local function _n(name)
- return option_prefix .. name
-end
-
-local ssrust_encrypt_method_list = {
- "plain", "none",
- "aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
- "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
-}
-
-o = s:option(Flag, _n("custom"), translate("Use Custom Config"))
-
-o = s:option(Value, _n("port"), translate("Listen Port"))
-o.datatype = "port"
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Value, _n("password"), translate("Password"))
-o.password = true
-o:depends({ [_n("custom")] = false })
-
-o = s:option(ListValue, _n("method"), translate("Encrypt Method"))
-for a, t in ipairs(ssrust_encrypt_method_list) do o:value(t) end
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
-o.datatype = "uinteger"
-o.default = 300
-o:depends({ [_n("custom")] = false })
-
-o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
-o.default = "0"
-o:depends({ [_n("custom")] = false })
-
-o = s:option(TextValue, _n("custom_config"), translate("Custom Config") .. " (JSON)")
-o.rows = 10
-o.wrap = "off"
-o:depends({ [_n("custom")] = true })
-o.datatype = "json"
-local o_validate = o.validate
-o.validate = function(self, value)
- local v = o_validate(self, value)
- if v then return v end
- return nil, translate("Custom Config") .. " " .. translate("Must be JSON text!")
-end
-o.custom_cfgvalue = function(self, section, value)
- local config_str = m:get(section, "config_str")
- if config_str then
- return api.base64Decode(config_str)
- end
-end
-o.custom_write = function(self, section, value)
- m:set(section, "config_str", api.base64Encode(value) or "")
-end
-
-o = s:option(Flag, _n("log"), translate("Log"))
-o.default = "1"
-o.rmempty = false
-
-api.luci_types(arg[1], m, s, type_name, option_prefix)
diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/user_config.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/user_config.lua
new file mode 100644
index 00000000000..b544dc54ffd
--- /dev/null
+++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/server/user_config.lua
@@ -0,0 +1,54 @@
+api = require "luci.passwall.api"
+api.set_default_cbi()
+
+m = Map(api.s_config)
+m.redirect = api.url("server")
+
+if not arg[1] or not m:get(arg[1]) then
+ luci.http.redirect(m.redirect)
+end
+
+s = m:section(NamedSection, arg[1], "user", translate("User Config"))
+s.addremove = false
+s.dynamic = false
+
+o = s:option(Value, "username", translate("Username"))
+o.datatype = "and(uciname,maxlength(24))"
+o.rmempty = false
+function o.validate(self, value, section)
+ local exists = false
+ m.uci:foreach("passwall_server", "user", function(s)
+ if s[".name"] ~= section and s.username == value then
+ exists = true
+ return false
+ end
+ end)
+ if exists then
+ return nil, translate("This username already exists.")
+ end
+ return value
+end
+
+o = s:option(Value, "password", translate("Password"))
+o.rmempty = false
+
+o = s:option(Value, "uuid", "UUID")
+o.datatype = "uuid"
+o.default = api.gen_uuid()
+o.rmempty = false
+
+o = s:option(Value, "wireguard_public_key", "Wireguard " .. translate("Public Key"))
+o.datatype = "base64"
+
+o = s:option(Value, "wireguard_private_key", "Wireguard " .. translate("Private Key"))
+o.datatype = "base64"
+
+o = s:option(DummyValue, "gen_wireguard_key")
+o.template = m:template_path("/server/gen_wireguard_key")
+
+o = s:option(Value, "wireguard_pre_shared_key", "Wireguard " .. translate("Pre shared key"))
+o.datatype = "base64"
+
+o = s:option(DynamicList, "allowed_ips", "Wireguard " .. translate("Allowed IPs"))
+
+return api.return_map(m)
diff --git a/applications/luci-app-passwall/luasrc/passwall/api.lua b/applications/luci-app-passwall/luasrc/passwall/api.lua
index 6193e1f9ca6..5ab686ce979 100644
--- a/applications/luci-app-passwall/luasrc/passwall/api.lua
+++ b/applications/luci-app-passwall/luasrc/passwall/api.lua
@@ -1,4 +1,7 @@
module("luci.passwall.api", package.seeall)
+appname = "passwall"
+c_config = "passwall"
+s_config = "passwall_server"
local com = require "luci.passwall.com"
nixio = require "nixio"
fs = require "nixio.fs"
@@ -16,9 +19,11 @@ OPENWRT_ARCH = nil
DISTRIB_ARCH = nil
OPENWRT_BOARD = nil
-CACHE_PATH = "/tmp/etc/" .. appname .. "_tmp"
-LOG_FILE = "/tmp/log/" .. appname .. ".log"
-TMP_PATH = "/tmp/etc/" .. appname
+LOCK_PREFIX = "/tmp/lock/" .. c_config
+LOG_FILE = "/tmp/log/" .. c_config .. ".log"
+TMP_PATH = "/tmp/etc/" .. c_config
+CACHE_PATH = TMP_PATH .. "_tmp"
+S_TMP_PATH = "/tmp/etc/" .. s_config
TMP_IFACE_PATH = TMP_PATH .. "/iface"
function log(...)
@@ -30,15 +35,89 @@ function log(...)
end
end
-function is_js_luci()
- return sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0
-end
-
function is_old_uci()
return sys.call("grep -E 'require[ \t]*\"uci\"' /usr/lib/lua/luci/model/uci.lua >/dev/null 2>&1") == 0
end
+function uci_del(config, section, option)
+ if option then
+ return uci:delete(config, section, option)
+ else
+ return uci:delete(config, section)
+ end
+end
+
+function uci_get(config, section, option)
+ if not section then
+ return uci:get_all(config)
+ elseif option then
+ return uci:get(config, section, option) or nil
+ else
+ return uci:get_all(config, section)
+ end
+end
+
+function uci_set(config, section, option, value)
+ if type(value) == "number" then
+ value = value .. ""
+ end
+ if #value > 0 then
+ if option then
+ if type(value) == "table" then
+ return uci:set_list(config, section, option, value)
+ else
+ return uci:set(config, section, option, value)
+ end
+ else
+ return uci:set(config, section, value)
+ end
+ else
+ return uci_del(config, section, option)
+ end
+end
+
+function uci_del_c(section, option)
+ return uci_del(c_config, section, option)
+end
+
+function uci_foreach_c(stype, func)
+ uci:foreach(c_config, stype, func)
+end
+
+function uci_get_c(section, option)
+ return uci_get(c_config, section, option)
+end
+
+function uci_set_c(section, option, value)
+ return uci_set(c_config, section, option, value)
+end
+
+function uci_save_c(commit, apply)
+ return uci_save(uci, c_config, commit, apply)
+end
+
+function uci_del_s(section, option)
+ return uci_del(s_config, section, option)
+end
+
+function uci_foreach_s(stype, func)
+ uci:foreach(s_config, stype, func)
+end
+
+function uci_get_s(section, option)
+ return uci_get(s_config, section, option)
+end
+
+function uci_set_s(section, option, value)
+ return uci_set(s_config, section, option, value)
+end
+
+function uci_save_s(commit, apply)
+ return uci_save(uci, s_config, commit, apply)
+end
+
function uci_save(cursor, config, commit, apply)
+ if not cursor then cursor = uci end
if is_old_uci() then
cursor:save(config)
if commit then
@@ -199,7 +278,7 @@ end
function curl_direct(url, file, args)
--直连访问
- local chn_list = uci:get(appname, "@global[0]", "chn_list") or "direct"
+ local chn_list = uci_get_c("@global[0]", "chn_list") or "direct"
local Dns = (chn_list == "proxy") and "1.1.1.1" or "223.5.5.5"
if not args then args = {} end
local tmp_args = clone(args)
@@ -214,7 +293,7 @@ function curl_direct(url, file, args)
end
function curl_auto(url, file, args)
- local localhost_proxy = uci:get(appname, "@global[0]", "localhost_proxy") or "1"
+ local localhost_proxy = uci_get_c("@global[0]", "localhost_proxy") or "1"
if localhost_proxy == "1" then
return curl_base(url, file, args) -- 当路由器本机开启代理时,采用passwall规则进行访问
else
@@ -369,6 +448,12 @@ function is_timehhmm(str)
end
datatypes.timehhmm = is_timehhmm
+function is_uuid(str)
+ local pattern = "^%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x$"
+ return string.match(str, pattern) ~= nil
+end
+datatypes.uuid = is_uuid
+
function is_normal_node(e)
if e and e.type and e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface" or e.protocol == "_urltest") then
return false
@@ -476,11 +561,11 @@ function get_domain_from_url(url)
end
function get_valid_nodes()
- local show_node_info = uci_get_type("global_other", "show_node_info", "0")
+ local show_node_info = uci_get_c("@global_other[0]", "show_node_info") or "0"
local nodes = {}
local default_nodes = {}
local other_nodes = {}
- uci:foreach(appname, "nodes", function(e)
+ uci_foreach_c("nodes", function(e)
e.id = e[".name"]
if e.type and e.remarks then
local type_name = e.type
@@ -553,7 +638,7 @@ function get_node_list()
socks_list = {},
normal_list = {},
}
- uci:foreach(appname, "socks", function(s)
+ uci_foreach_c("socks", function(s)
if s.enabled == "1" and s.node then
node_list.socks_list[#node_list.socks_list + 1] = {
id = s[".name"],
@@ -642,6 +727,29 @@ function get_full_node_remarks(n)
return remarks
end
+function get_random_normal_node(exclude_id)
+ local exclude_id_lookup = {}
+ if type(exclude_id) == "table" then
+ for i, v in ipairs(exclude_id) do
+ exclude_id_lookup[v] = true
+ end
+ elseif type(exclude_id) == "string" then
+ exclude_id_lookup[exclude_id] = true
+ end
+ local normal_list = {}
+ for k, e in ipairs(get_valid_nodes()) do
+ if e.node_type == "normal" and not exclude_id_lookup[e[".name"]] then
+ normal_list[#normal_list + 1] = e
+ end
+ end
+ if #normal_list > 0 then
+ math.randomseed(os.time())
+ local num = math.random(1, #normal_list)
+ return normal_list[num]
+ end
+ return nil
+end
+
function gen_uuid()
local uuid = sys.exec("echo -n $(cat /proc/sys/kernel/random/uuid)")
return uuid
@@ -652,23 +760,7 @@ function gen_random_char(length)
return sys.exec("echo -n $(head /dev/urandom | tr -dc A-Za-z0-9 | head -c %s)" % length)
end
-function uci_get_type(type, config, default)
- local value = uci:get_first(appname, type, config, default) or sys.exec("echo -n $(uci -q get " .. appname .. ".@" .. type .."[0]." .. config .. ")")
- if (value == nil or value == "") and (default and default ~= "") then
- value = default
- end
- return value
-end
-
-function uci_get_type_id(id, config, default)
- local value = uci:get(appname, id, config, default) or sys.exec("echo -n $(uci -q get " .. appname .. "." .. id .. "." .. config .. ")")
- if (value == nil or value == "") and (default and default ~= "") then
- value = default
- end
- return value
-end
-
-local function chmod_755(file)
+function chmod_755(file)
if file and file ~= "" then
if not fs.access(file, "rwx", "rx", "rx") then
fs.chmod(file, 755)
@@ -677,7 +769,7 @@ local function chmod_755(file)
end
function get_customed_path(e)
- return uci_get_type("global_app", e .. "_file")
+ return uci_get_c("@global_app[0]", e .. "_file")
end
function finded_com(e)
@@ -716,16 +808,16 @@ function clone(org)
end
function get_bin_version_cache(file, cmd)
- sys.call("mkdir -p /tmp/etc/passwall_tmp")
+ sys.call("mkdir -p " .. CACHE_PATH)
if fs.access(file) then
chmod_755(file)
local md5 = sys.exec("echo -n $(md5sum " .. file .. " | awk '{print $1}')")
- if fs.access("/tmp/etc/passwall_tmp/" .. md5) then
- return sys.exec("echo -n $(cat /tmp/etc/passwall_tmp/%s)" % md5)
+ if fs.access(CACHE_PATH .. "/" .. md5) then
+ return sys.exec("echo -n $(cat %s)" % { CACHE_PATH .. "/" .. md5 })
else
local version = sys.exec(string.format("echo -n $(%s %s)", file, cmd))
if version and version ~= "" then
- sys.call("echo '" .. version .. "' > " .. "/tmp/etc/passwall_tmp/" .. md5)
+ sys.call("echo '%s' > %s" % { version, CACHE_PATH .. "/" .. md5})
return version
end
end
@@ -736,7 +828,7 @@ end
function get_app_path(app_name)
if com[app_name] then
local def_path = com[app_name].default_path
- local path = uci_get_type("global_app", app_name:gsub("%-","_") .. "_file")
+ local path = uci_get_c("@global_app[0]", app_name:gsub("%-","_") .. "_file")
path = path and (#path>0 and path or def_path) or def_path
return path
end
@@ -1014,7 +1106,7 @@ local default_file_tree = {
}
local function get_api_json(url)
- local gh_proxy = uci_get_type("global_app", "github_proxy", "0")
+ local gh_proxy = uci_get_c("@global_app[0]", "github_proxy") or "0"
local return_code, content
if gh_proxy == "1" then
url = "https://gh-proxy.org/" .. url
@@ -1138,7 +1230,7 @@ function to_download(app_name, url, size)
local _curl_args = clone(curl_args)
table.insert(_curl_args, "--speed-limit 51200 --speed-time 15 --max-time 300")
- local gh_proxy = uci_get_type("global_app", "github_proxy", "0")
+ local gh_proxy = uci_get_c("@global_app[0]", "github_proxy") or "0"
local return_code, result
if gh_proxy == "1" then
url = "https://gh-proxy.org/" .. url
@@ -1296,7 +1388,7 @@ end
function to_check_self()
local url = "https://raw.githubusercontent.com/Openwrt-Passwall/openwrt-passwall/main/luci-app-passwall/Makefile"
local tmp_file = "/tmp/passwall_makefile"
- local gh_proxy = uci_get_type("global_app", "github_proxy", "0")
+ local gh_proxy = uci_get_c("@global_app[0]", "github_proxy") or "0"
local return_code, result
if gh_proxy == "1" then
url = "https://gh-proxy.org/" .. url
@@ -1333,14 +1425,73 @@ function to_check_self()
}
end
+function is_js_luci()
+ return sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0
+end
+
function set_default_cbi()
local cbi = require "luci.cbi"
+ if true then
+ --Map
+ local Map = cbi.Map
+ local default_init = Map.__init__
+ function Map.__init__(self, config, ...)
+ if not config then config = c_config end
+ default_init(self, config, ...)
+ self.api = require "luci.passwall.api"
+ end
+ if is_js_luci() == true then
+ local default_parse = Map.parse
+ function Map.parse(self, ...)
+ apply_redirect(self)
+ local old = self.on_after_save
+ self.on_after_save = function(self)
+ if old then old(self) end
+ self:set("@global[0]", "timestamp", os.time())
+ end
+ return default_parse(self, ...)
+ end
+ end
+ function Map.foreach(self, stype, func)
+ self.uci:foreach(self.config, stype, func)
+ end
+ function Map.template_path(self, template)
+ return appname .. template
+ end
+ function Map.appendTemplate(self, template, data)
+ local obj = cbi.Template(self:template_path(template))
+ obj.map = self
+ if data and next(data) then
+ for k, v in pairs(data) do
+ obj[k] = v
+ end
+ end
+ self:append(obj)
+ return obj
+ end
+ end
+ if true then
+ --AbstractSection
+ local AbstractSection = cbi.AbstractSection
+ function AbstractSection.appendTemplate(self, template, data)
+ local obj = cbi.Template(self.map:template_path(template))
+ obj.map = self.map
+ obj.section = self
+ if data and next(data) then
+ for k, v in pairs(data) do
+ obj[k] = v
+ end
+ end
+ self:append(obj)
+ return obj
+ end
+ end
if true then
--TextValue
local TextValue = cbi.TextValue
- local original_init = TextValue.__init__
+ local default_init = TextValue.__init__
function TextValue.__init__(self, ...)
- original_init(self, ...)
+ default_init(self, ...)
self.template = appname .. "/cbi/tvalue"
end
end
@@ -1371,115 +1522,122 @@ function return_map(map)
if true then
-- header
local header = cbi.Template(appname .. "/cbi/header")
- header.api = api
- header.config = map.config
+ header.map = map
table.insert(map.children, 1, header)
end
if true then
-- footer
local footer = cbi.Template(appname .. "/cbi/footer")
- footer.api = api
- footer.config = map.config
+ footer.map = map
map:append(footer)
end
-
return map
end
-function luci_types(id, m, s, type_name, option_prefix)
+function luci_types(s, s2)
local cbi = require "luci.cbi"
+ local m = s.map
+ local id = s2.section
+ local type_name = s2.type_name
+ local option_prefix = s2.option_prefix
local fv_type
local field_type = s.fields["type"]
if field_type then
fv_type = field_type:formvalue(id)
end
- local rewrite_option_table = {}
- for key, value in pairs(s.fields) do
- if key:find(option_prefix) == 1 then
- if not s.fields[key].not_rewrite then
- if s.fields[key].rewrite_option then
- if not rewrite_option_table[s.fields[key].rewrite_option] then
- rewrite_option_table[s.fields[key].rewrite_option] = 1
+ for i, v in ipairs(s2.children) do
+ local o = s2.children[i]
+ o.config_option = o.option
+ o.option_prefix = option_prefix
+ o.option = option_prefix .. o.option
+ if not o.not_rewrite then
+ o.cfgvalue = function(self, section)
+ -- Add a custom `custom_cfgvalue` attribute. If a custom `custom_cfgvalue` function exists, the custom `cfgvalue` logic will be used.
+ if self.custom_cfgvalue then
+ return self:custom_cfgvalue(section)
+ else
+ if self.rewrite_option then
+ return m:get(section, self.rewrite_option)
else
- rewrite_option_table[s.fields[key].rewrite_option] = rewrite_option_table[s.fields[key].rewrite_option] + 1
+ return m:get(section, self.config_option)
end
end
-
- s.fields[key].cfgvalue = function(self, section)
- -- 添加自定义 custom_cfgvalue 属性,如果有自定义的 custom_cfgvalue 函数,则使用自定义的 cfgvalue 逻辑
- if self.custom_cfgvalue then
- return self:custom_cfgvalue(section)
+ end
+ o.write = function(self, section, value)
+ if s.fields["type"]:formvalue(id) == type_name then
+ -- Add a custom `custom_write` attribute; if a custom `custom_write` function exists, then use the custom write logic.
+ if self.custom_write then
+ self:custom_write(section, value)
else
- if self.rewrite_option then
- return m:get(section, self.rewrite_option)
- else
- if self.option:find(option_prefix) == 1 then
- return m:get(section, self.option:sub(1 + #option_prefix))
- end
- end
- end
- end
- s.fields[key].write = function(self, section, value)
- if s.fields["type"]:formvalue(id) == type_name then
- -- 添加自定义 custom_write 属性,如果有自定义的 custom_write 函数,则使用自定义的 write 逻辑
- if self.custom_write then
- self:custom_write(section, value)
- else
- local new_val = value
- if util.instanceof(self, cbi.DynamicList) then
- local new_t = {}
- if type(value) == "table" then
- new_t = table_remove_duplicates(value)
- else
- new_t = { value }
- end
- if self.cast == "string" then
- new_val = table.concat(new_t, " ")
- else
- new_val = new_t
- end
+ local new_val = value
+ if util.instanceof(self, cbi.DynamicList) then
+ local new_t = {}
+ if type(value) == "table" then
+ new_t = table_remove_duplicates(value)
+ else
+ new_t = { value }
end
- if self.rewrite_option then
- m:set(section, self.rewrite_option, new_val)
+ if self.cast == "string" then
+ new_val = table.concat(new_t, " ")
else
- if self.option:find(option_prefix) == 1 then
- m:set(section, self.option:sub(1 + #option_prefix), new_val)
- end
+ new_val = new_t
end
end
+ if self.rewrite_option then
+ m:set(section, self.rewrite_option, new_val)
+ else
+ m:set(section, self.config_option, new_val)
+ end
end
end
- s.fields[key].remove = function(self, section)
- if s.fields["type"]:formvalue(id) == type_name then
- -- 添加自定义 custom_remove 属性,如果有自定义的 custom_remove 函数,则使用自定义的 remove 逻辑
- if self.custom_remove then
- self:custom_remove(section)
+ end
+ o.remove = function(self, section)
+ if s.fields["type"]:formvalue(id) == type_name then
+ -- Add a custom `custom_remove` attribute; if a custom `custom_remove` function exists, use the custom remove logic.
+ if self.custom_remove then
+ self:custom_remove(section)
+ else
+ if self.rewrite_option then
+ m:del(section, self.rewrite_option)
else
- if self.rewrite_option and rewrite_option_table[self.rewrite_option] == 1 then
- m:del(section, self.rewrite_option)
- else
- if self.option:find(option_prefix) == 1 then
- m:del(section, self.option:sub(1 + #option_prefix))
- end
- end
+ m:del(section, self.config_option)
end
end
end
end
+ end
- local deps = s.fields[key].deps
- if #deps > 0 then
- for index, value in ipairs(deps) do
- deps[index]["type"] = type_name
+ local deps = o.deps
+ if #deps > 0 then
+ local function process_deps(dep)
+ local rewrite_deps = {}
+ for k, v in pairs(dep) do
+ if k:find("!") then
+ rewrite_deps[k] = v
+ else
+ rewrite_deps[option_prefix .. k] = v
+ end
end
- else
- s.fields[key]:depends({ type = type_name })
+ if not rewrite_deps['!reverse'] then
+ rewrite_deps["type"] = type_name
+ end
+ return rewrite_deps
end
-
- if fv_type and fv_type ~= type_name then
- s.fields[key].rmempty = true
+ for index, value in ipairs(deps) do
+ local rewrite_deps = process_deps(value)
+ if rewrite_deps then
+ deps[index] = rewrite_deps
+ end
end
+ else
+ o:depends({ type = type_name })
end
+
+ if fv_type and fv_type ~= type_name then
+ o.rmempty = true
+ end
+
+ s:append(o)
end
end
@@ -1563,20 +1721,8 @@ function format_go_time(input, default)
return result
end
-function set_apply_on_parse(map)
- if not map then return end
- if is_js_luci() then
- apply_redirect(map)
- local old = map.on_after_save
- map.on_after_save = function(self)
- if old then old(self) end
- map:set("@global[0]", "timestamp", os.time())
- end
- end
-end
-
function apply_redirect(m)
- local tmp_uci_file = "/etc/config/" .. appname .. "_redirect"
+ local tmp_uci_file = "/etc/config/" .. c_config .. "_redirect"
if m.redirect and m.redirect ~= "" then
if fs.access(tmp_uci_file) then
local redirect
@@ -1591,13 +1737,18 @@ function apply_redirect(m)
else
fs.writefile(tmp_uci_file, "config redirect\n")
end
+ local old = m.on_after_save
m.on_after_save = function(self)
+ if old then
+ old(self)
+ end
local redirect = self.redirect
if redirect and redirect ~= "" then
- uci:set(appname .. "_redirect", "@redirect[0]", "url", redirect)
+ uci:set(c_config .. "_redirect", "@redirect[0]", "url", redirect)
end
end
else
+ uci:revert(c_config .. "_redirect")
sys.call("/bin/rm -f " .. tmp_uci_file)
end
end
@@ -1699,7 +1850,7 @@ end
function get_socks_backup_nodes(id)
id = trim(id)
if id == "" then return "" end
- local socks = uci:get_all(appname, id)
+ local socks = uci_get_c(id)
local nodes
if socks.backup_node_add_mode and socks.backup_node_add_mode == "batch" then
local node = {}
@@ -1720,7 +1871,7 @@ function get_socks_backup_nodes(id)
end
function get_core(field, candidates)
- local v = uci:get(appname, "@global_subscribe[0]", field)
+ local v = uci_get_c("@global_subscribe[0]", field)
if v and v ~= "" then
for _, c in ipairs(candidates) do
if c[2] == v and c[1] then
@@ -1924,3 +2075,36 @@ function table_remove_duplicates(t)
end
return new_t
end
+
+function gen_wireguard_key()
+ if sys.call("command -v wg >/dev/null") == 0 then
+ local private_key = sys.exec('echo -n $(wg genkey)')
+ local public_key = sys.exec('echo -n $(echo "%s" | wg pubkey)' % private_key)
+ return {
+ private_key = private_key,
+ public_key = public_key
+ }
+ end
+ local xray = finded_com("xray")
+ if xray then
+ local result = sys.exec(xray .. " wg | awk -F ': ' '{print $2}'")
+ local s = split(result, "\n")
+ local private_key = s[1]
+ local public_key = s[2]
+ return {
+ private_key = private_key,
+ public_key = public_key
+ }
+ end
+ local sb = finded_com("sing-box")
+ if sb then
+ local result = sys.exec(sb .. " generate wg-keypair | awk '{print $2}'")
+ local s = split(result, "\n")
+ local private_key = s[1]
+ local public_key = s[2]
+ return {
+ private_key = private_key,
+ public_key = public_key
+ }
+ end
+end
diff --git a/applications/luci-app-passwall/luasrc/passwall/server_app.lua b/applications/luci-app-passwall/luasrc/passwall/server_app.lua
index bec17f8e0ab..cdfb22582e0 100755
--- a/applications/luci-app-passwall/luasrc/passwall/server_app.lua
+++ b/applications/luci-app-passwall/luasrc/passwall/server_app.lua
@@ -6,8 +6,8 @@ local sys = api.sys
local uci = api.uci
local jsonc = api.jsonc
-local CONFIG = "passwall_server"
-local CONFIG_PATH = "/tmp/etc/" .. CONFIG
+local CONFIG = api.s_config
+local CONFIG_PATH = api.S_TMP_PATH
local NFT_INCLUDE_FILE = CONFIG_PATH .. "/" .. CONFIG .. ".nft"
local LOG_APP_FILE = "/tmp/log/" .. CONFIG .. ".log"
local TMP_BIN_PATH = CONFIG_PATH .. "/bin"
@@ -53,7 +53,7 @@ local function ln_run(s, d, command, output)
end
local function gen_include()
- cmd(string.format("echo '#!/bin/sh' > /tmp/etc/%s.include", CONFIG))
+ cmd(string.format("echo '#!/bin/sh' > %s.include", CONFIG_PATH))
local function extract_rules(n, a)
local _ipt = ipt_bin
if n == "6" then
@@ -64,7 +64,7 @@ local function gen_include()
result = result .. "COMMIT"
return result
end
- local f, err = io.open("/tmp/etc/" .. CONFIG .. ".include", "a")
+ local f, err = io.open(CONFIG_PATH .. ".include", "a")
if f and err == nil then
if nft_flag == "0" then
f:write(ipt_bin .. '-save -c | grep -v "PSW-SERVER" | ' .. ipt_bin .. '-restore -c' .. "\n")
@@ -84,7 +84,7 @@ local function gen_include()
end
local function start()
- local enabled = tonumber(uci:get(CONFIG, "@global[0]", "enable") or 0)
+ local enabled = tonumber(api.uci_get_s("@global[0]", "enable") or 0)
if enabled == nil or enabled == 0 then
return
end
@@ -102,29 +102,33 @@ local function start()
nft_file:write('flush chain inet fw4 PSW-SERVER\n')
nft_file:write('insert rule inet fw4 input position 0 jump PSW-SERVER comment "PSW-SERVER"\n')
end
- uci:foreach(CONFIG, "user", function(user)
- local id = user[".name"]
- local enable = user.enable
+ api.uci_foreach_s("server", function(server)
+ local id = server[".name"]
+ local enable = server.enable
if enable and tonumber(enable) == 1 then
- local enable_log = user.log
+ local enable_log = server.log
local log_path = nil
if enable_log and enable_log == "1" then
log_path = CONFIG_PATH .. "/" .. id .. ".log"
else
log_path = nil
end
- local remarks = user.remarks
- local port = tonumber(user.port)
+ local remarks = server.remarks
+ local port = tonumber(server.port)
local bin
local config = {}
local config_file = CONFIG_PATH .. "/" .. id .. ".json"
local udp_forward = 1
- local type = user.type or ""
+ local type = server.type or ""
if type == "Socks" then
local auth = ""
- if user.auth and user.auth == "1" then
- local username = user.username or ""
- local password = user.password or ""
+ if server.auth and server.auth == "1" then
+ local user = nil
+ if server.user then
+ user = api.uci_get_s(server.user)
+ end
+ local username = user and user.username or ""
+ local password = user and user.password or ""
if username ~= "" and password ~= "" then
username = "-u " .. username
password = "-P " .. password
@@ -133,59 +137,59 @@ local function start()
end
bin = ln_run("/usr/bin/microsocks", "microsocks_" .. id, string.format("-i :: -p %s %s", port, auth), log_path)
elseif type == "SSR" then
- if user.custom == "1" and user.config_str then
- config = jsonc.parse(api.base64Decode(user.config_str))
+ if server.custom == "1" and server.config_str then
+ config = jsonc.parse(api.base64Decode(server.config_str))
else
- config = require(require_dir .. "util_shadowsocks").gen_config_server(user)
+ config = require(require_dir .. "util_shadowsocks").gen_config_server(server)
end
local udp_param = ""
- udp_forward = tonumber(user.udp_forward) or 1
+ udp_forward = tonumber(server.udp_forward) or 1
if udp_forward == 1 then
udp_param = "-u"
end
type = type:lower()
bin = ln_run("/usr/bin/" .. type .. "-server", type .. "-server", "-c " .. config_file .. " " .. udp_param, log_path)
elseif type == "SS-Rust" then
- if user.custom == "1" and user.config_str then
- config = jsonc.parse(api.base64Decode(user.config_str))
+ if server.custom == "1" and server.config_str then
+ config = jsonc.parse(api.base64Decode(server.config_str))
else
- config = require(require_dir .. "util_shadowsocks").gen_config_server(user)
+ config = require(require_dir .. "util_shadowsocks").gen_config_server(server)
end
bin = ln_run("/usr/bin/ssserver", "ssserver", "-c " .. config_file, log_path)
elseif type == "Xray" then
- if user.custom == "1" and user.config_str then
- config = jsonc.parse(api.base64Decode(user.config_str))
+ if server.custom == "1" and server.config_str then
+ config = jsonc.parse(api.base64Decode(server.config_str))
if log_path then
if not config.log then
config.log = {}
end
- config.log.loglevel = user.loglevel
+ config.log.loglevel = server.loglevel
end
else
- config = require(require_dir .. "util_xray").gen_config_server(user)
+ config = require(require_dir .. "util_xray").gen_config_server(server)
end
bin = ln_run(api.get_app_path("xray"), "xray", "run -c " .. config_file, log_path)
elseif type == "sing-box" then
- if user.custom == "1" and user.config_str then
- config = jsonc.parse(api.base64Decode(user.config_str))
+ if server.custom == "1" and server.config_str then
+ config = jsonc.parse(api.base64Decode(server.config_str))
if log_path then
if not config.log then
config.log = {}
end
config.log.timestamp = true
config.log.disabled = false
- config.log.level = user.loglevel
+ config.log.level = server.loglevel
config.log.output = log_path
end
else
- config = require(require_dir .. "util_sing-box").gen_config_server(user)
+ config = require(require_dir .. "util_sing-box").gen_config_server(server)
end
bin = ln_run(api.get_app_path("sing-box"), "sing-box", "run -c " .. config_file, log_path)
elseif type == "Hysteria2" then
- if user.custom == "1" and user.config_str then
- config = jsonc.parse(api.base64Decode(user.config_str))
+ if server.custom == "1" and server.config_str then
+ config = jsonc.parse(api.base64Decode(server.config_str))
else
- config = require(require_dir .. "util_hysteria2").gen_config_server(user)
+ config = require(require_dir .. "util_hysteria2").gen_config_server(server)
end
bin = ln_run(api.get_app_path("hysteria"), "hysteria", "-c " .. config_file .. " server", log_path)
end
@@ -203,7 +207,7 @@ local function start()
cmd(bin)
end
- local bind_local = user.bind_local or 0
+ local bind_local = server.bind_local or 0
if bind_local and tonumber(bind_local) ~= 1 and port then
if nft_flag == "0" then
ipt(string.format('-A PSW-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks))
@@ -244,7 +248,7 @@ local function stop()
cmd("nft flush chain inet fw4 PSW-SERVER 2>/dev/null")
cmd("nft delete chain inet fw4 PSW-SERVER 2>/dev/null")
end
- cmd(string.format("rm -rf %s %s /tmp/etc/%s.include", CONFIG_PATH, LOG_APP_FILE, CONFIG))
+ cmd(string.format("rm -rf %s %s %s", CONFIG_PATH, LOG_APP_FILE, CONFIG_PATH .. ".include"))
end
if action then
diff --git a/applications/luci-app-passwall/luasrc/passwall/util_hysteria2.lua b/applications/luci-app-passwall/luasrc/passwall/util_hysteria2.lua
index 8c43f8119c6..e61b72e02e1 100644
--- a/applications/luci-app-passwall/luasrc/passwall/util_hysteria2.lua
+++ b/applications/luci-app-passwall/luasrc/passwall/util_hysteria2.lua
@@ -1,9 +1,21 @@
module("luci.passwall.util_hysteria2", package.seeall)
local api = require "luci.passwall.api"
-local uci = api.uci
local jsonc = api.jsonc
function gen_config_server(node)
+ local users = nil
+ if node.users and #node.users > 0 then
+ users = {}
+ for i, v in ipairs(node.users) do
+ local user = api.uci_get_s(v) or {}
+ if user[".type"] == "user" then
+ users[user.username] = user.password
+ end
+ end
+ if not next(users) then
+ users = nil
+ end
+ end
local config = {
listen = (function()
if node.hysteria2_realms and node.hysteria2_realm_url then
@@ -25,10 +37,10 @@ function gen_config_server(node)
password = node.hysteria2_obfs_password
}
} or nil,
- auth = {
- type = "password",
- password = node.hysteria2_auth_password
- },
+ auth = users and {
+ type = "userpass",
+ userpass = users
+ } or nil,
bandwidth = (node.hysteria2_up_mbps or node.hysteria2_down_mbps) and {
up = node.hysteria2_up_mbps and node.hysteria2_up_mbps .. " mbps" or nil,
down = node.hysteria2_down_mbps and node.hysteria2_down_mbps .. " mbps" or nil
@@ -60,7 +72,7 @@ function gen_config(var)
print("node 不能为空")
return
end
- local node = uci:get_all("passwall", node_id)
+ local node = api.uci_get_c(node_id)
local local_tcp_redir_port = var["local_tcp_redir_port"]
local local_udp_redir_port = var["local_udp_redir_port"]
local local_socks_address = var["local_socks_address"] or "0.0.0.0"
diff --git a/applications/luci-app-passwall/luasrc/passwall/util_naiveproxy.lua b/applications/luci-app-passwall/luasrc/passwall/util_naiveproxy.lua
index 02c4d27d75e..a29cfafe62e 100644
--- a/applications/luci-app-passwall/luasrc/passwall/util_naiveproxy.lua
+++ b/applications/luci-app-passwall/luasrc/passwall/util_naiveproxy.lua
@@ -1,6 +1,5 @@
module("luci.passwall.util_naiveproxy", package.seeall)
local api = require "luci.passwall.api"
-local uci = api.uci
local jsonc = api.jsonc
function gen_config(var)
@@ -9,7 +8,7 @@ function gen_config(var)
print("node 不能为空")
return
end
- local node = uci:get_all("passwall", node_id)
+ local node = api.uci_get_c(node_id)
local run_type = var["run_type"]
local local_addr = var["local_addr"]
local local_port = var["local_port"]
diff --git a/applications/luci-app-passwall/luasrc/passwall/util_shadowsocks.lua b/applications/luci-app-passwall/luasrc/passwall/util_shadowsocks.lua
index 819b827848c..9e618a76eb0 100644
--- a/applications/luci-app-passwall/luasrc/passwall/util_shadowsocks.lua
+++ b/applications/luci-app-passwall/luasrc/passwall/util_shadowsocks.lua
@@ -1,12 +1,15 @@
module("luci.passwall.util_shadowsocks", package.seeall)
local api = require "luci.passwall.api"
-local uci = api.uci
local jsonc = api.jsonc
function gen_config_server(node)
+ local user = nil
+ if node.user then
+ user = api.uci_get_s(node.user)
+ end
local config = {}
config.server_port = tonumber(node.port)
- config.password = node.password
+ config.password = user and user.password or ""
config.timeout = tonumber(node.timeout)
config.fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false
config.method = node.method
@@ -36,7 +39,7 @@ function gen_config(var)
print("node 不能为空")
return
end
- local node = uci:get_all("passwall", node_id)
+ local node = api.uci_get_c(node_id)
local server_host = var["server_host"] or (node.address or ""):lower()
local server_port = var["server_port"] or node.port
local local_addr = var["local_addr"]
diff --git a/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua
index 3f9c29c7283..32aba3150b8 100644
--- a/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua
+++ b/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua
@@ -1,10 +1,9 @@
module("luci.passwall.util_sing-box", package.seeall)
local api = require "luci.passwall.api"
-local uci = api.uci
local sys = api.sys
local jsonc = api.jsonc
-local appname = "passwall"
local fs = api.fs
+local CACHE_PATH = api.CACHE_PATH
local ech_domain = {}
local local_version = api.get_app_version("sing-box"):match("[^v]+")
@@ -23,7 +22,7 @@ local GEO_VAR = {
IP_PATH = nil,
SITE_TAGS = {},
IP_TAGS = {},
- TO_SRS_PATH = "/tmp/etc/" .. appname .."_tmp/singbox_srss/"
+ TO_SRS_PATH = CACHE_PATH .."/singbox_srss/"
}
function check_geoview()
@@ -34,7 +33,7 @@ function check_geoview()
if GEO_VAR.OK == 0 then
api.log("!!!注意:缺少 Geoview 组件或版本过低,Sing-Box 分流无法启用!")
else
- GEO_VAR.DIR = GEO_VAR.DIR or (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/")
+ GEO_VAR.DIR = GEO_VAR.DIR or (api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/")
GEO_VAR.SITE_PATH = GEO_VAR.SITE_PATH or (GEO_VAR.DIR .. "/geosite.dat")
GEO_VAR.IP_PATH = GEO_VAR.IP_PATH or (GEO_VAR.DIR .. "/geoip.dat")
if not fs.access(GEO_VAR.TO_SRS_PATH) then
@@ -120,8 +119,7 @@ function gen_outbound(flag, node, tag, proxy_table)
config_file = string.format("%s_%s_%s_%s.json", flag, tag, node_id, new_port)
end
if run_socks_instance then
- sys.call(string.format('/usr/share/%s/app.sh run_socks "%s"> /dev/null',
- appname,
+ sys.call(string.format('/usr/share/passwall/app.sh run_socks "%s"> /dev/null',
string.format("flag=%s node=%s bind=%s socks_port=%s config_file=%s relay_port=%s",
new_port, --flag
node_id, --node
@@ -663,6 +661,8 @@ function gen_outbound(flag, node, tag, proxy_table)
end
function gen_config_server(node)
+ local endpoints = {}
+ local inbounds = {}
local outbounds = {
{ type = "direct", tag = "direct" }
}
@@ -766,39 +766,77 @@ function gen_config_server(node)
listen_port = tonumber(node.port),
}
+ local users = node.users or {}
+ local users = nil
+ if node.users and #node.users > 0 then
+ users = {}
+ for i, v in ipairs(node.users) do
+ local user = api.uci_get_s(v) or {}
+ if user[".type"] == "user" then
+ local u = {}
+ if node.protocol == "mixed" or node.protocol == "socks" or node.protocol == "http" or node.protocol == "naive" then
+ u.username = user.username
+ u.password = user.password
+ end
+ if node.protocol == "shadowsocks" or node.protocol == "trojan" then
+ u.name = user.username
+ u.password = user.password
+ end
+ if node.protocol == "vmess" then
+ u.name = user.username
+ u.uuid = user.uuid
+ u.alterId = 0
+ end
+ if node.protocol == "vless" then
+ u.name = user.username
+ u.uuid = user.uuid
+ u.flow = node.flow
+ end
+ if node.protocol == "hysteria" then
+ u.name = user.username
+ u.auth_str = user.password
+ end
+ if node.protocol == "tuic" then
+ u.name = user.username
+ u.password = user.password
+ u.uuid = user.uuid
+ end
+ if node.protocol == "hysteria2" then
+ u.name = user.username
+ u.password = user.password
+ end
+ if node.protocol == "wireguard" then
+ u.public_key = user.wireguard_public_key
+ u.pre_shared_key = user.wireguard_pre_shared_key
+ u.allowed_ips = user.allowed_ips or {}
+ u.persistent_keepalive_interval = 0
+ end
+ users[#users + 1] = u
+ end
+ end
+ if #users == 0 then
+ users = nil
+ end
+ end
+
local protocol_table = nil
if node.protocol == "mixed" then
protocol_table = {
- users = (node.auth == "1") and {
- {
- username = node.username,
- password = node.password
- }
- } or nil,
+ users = users,
set_system_proxy = false
}
end
if node.protocol == "socks" then
protocol_table = {
- users = (node.auth == "1") and {
- {
- username = node.username,
- password = node.password
- }
- } or nil
+ users = users
}
end
if node.protocol == "http" then
protocol_table = {
- users = (node.auth == "1") and {
- {
- username = node.username,
- password = node.password
- }
- } or nil,
+ users = users,
tls = (node.tls == "1") and tls or nil,
}
end
@@ -806,21 +844,14 @@ function gen_config_server(node)
if node.protocol == "shadowsocks" then
protocol_table = {
method = node.method,
- password = node.password,
+ password = node.ss_password,
+ users = users,
multiplex = mux,
}
end
if node.protocol == "vmess" then
- if node.uuid then
- local users = {}
- for i = 1, #node.uuid do
- users[i] = {
- name = node.uuid[i],
- uuid = node.uuid[i],
- alterId = 0,
- }
- end
+ if users then
protocol_table = {
users = users,
tls = (node.tls == "1") and tls or nil,
@@ -831,15 +862,7 @@ function gen_config_server(node)
end
if node.protocol == "vless" then
- if node.uuid then
- local users = {}
- for i = 1, #node.uuid do
- users[i] = {
- name = node.uuid[i],
- uuid = node.uuid[i],
- flow = node.flow,
- }
- end
+ if users then
protocol_table = {
users = users,
tls = (node.tls == "1") and tls or nil,
@@ -850,14 +873,7 @@ function gen_config_server(node)
end
if node.protocol == "trojan" then
- if node.uuid then
- local users = {}
- for i = 1, #node.uuid do
- users[i] = {
- name = node.uuid[i],
- password = node.uuid[i],
- }
- end
+ if users then
protocol_table = {
users = users,
tls = (node.tls == "1") and tls or nil,
@@ -870,15 +886,12 @@ function gen_config_server(node)
end
if node.protocol == "naive" then
- protocol_table = {
- users = {
- {
- username = node.username,
- password = node.password
- }
- },
- tls = tls,
- }
+ if users then
+ protocol_table = {
+ users = users,
+ tls = tls
+ }
+ end
end
if node.protocol == "hysteria" then
@@ -888,13 +901,7 @@ function gen_config_server(node)
up_mbps = tonumber(node.hysteria_up_mbps),
down_mbps = tonumber(node.hysteria_down_mbps),
obfs = node.hysteria_obfs,
- users = {
- {
- name = "user1",
- auth = (node.hysteria_auth_type == "base64") and node.hysteria_auth_password or nil,
- auth_str = (node.hysteria_auth_type == "string") and node.hysteria_auth_password or nil,
- }
- },
+ users = users,
recv_window_conn = node.hysteria_recv_window_conn and tonumber(node.hysteria_recv_window_conn) or nil, --1.14 to stream_receive_window
recv_window_client = node.hysteria_recv_window_client and tonumber(node.hysteria_recv_window_client) or nil, --1.14 to connection_receive_window
max_conn_client = node.hysteria_max_conn_client and tonumber(node.hysteria_max_conn_client) or nil, --1.14 to max_concurrent_streams
@@ -904,31 +911,21 @@ function gen_config_server(node)
end
if node.protocol == "tuic" then
- if node.uuid then
- local users = {}
- for i = 1, #node.uuid do
- users[i] = {
- name = node.uuid[i],
- uuid = node.uuid[i],
- password = node.password
- }
- end
- tls.alpn = (node.tuic_alpn and node.tuic_alpn ~= "default") and (function()
- local alpn = {}
- string.gsub(node.tuic_alpn, '[^,]+', function(w)
- table.insert(alpn, w)
- end)
- if #alpn > 0 then return alpn end
- return nil
- end)() or nil
- protocol_table = {
- users = users,
- congestion_control = node.tuic_congestion_control or "cubic",
- zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false,
- heartbeat = (tonumber(node.tuic_heartbeat) or 3) .. "s",
- tls = tls
- }
- end
+ tls.alpn = (node.tuic_alpn and node.tuic_alpn ~= "default") and (function()
+ local alpn = {}
+ string.gsub(node.tuic_alpn, '[^,]+', function(w)
+ table.insert(alpn, w)
+ end)
+ if #alpn > 0 then return alpn end
+ return nil
+ end)() or nil
+ protocol_table = {
+ users = users,
+ congestion_control = node.tuic_congestion_control or "cubic",
+ zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false,
+ heartbeat = (tonumber(node.tuic_heartbeat) or 3) .. "s",
+ tls = tls
+ }
end
if node.protocol == "hysteria2" then
@@ -953,12 +950,7 @@ function gen_config_server(node)
end
return o
end)(node.hysteria2_obfs_type),
- users = {
- {
- name = "user1",
- password = node.hysteria2_auth_password or nil,
- }
- },
+ users = users,
ignore_client_bandwidth = (node.hysteria2_ignore_client_bandwidth == "1") and true or false,
tls = tls,
realm = node.hysteria2_realms and (function()
@@ -978,15 +970,24 @@ function gen_config_server(node)
end
if node.protocol == "anytls" then
- protocol_table = {
- users = {
- {
- name = (node.username and node.username ~= "") and node.username or "sekai",
- password = node.password
- }
- },
- tls = tls,
- }
+ if users then
+ protocol_table = {
+ users = users,
+ tls = tls,
+ }
+ end
+ end
+
+ if node.protocol == "wireguard" then
+ inbound.listen = nil
+ inbound.system = node.wireguard_system_interface == "1" and true or false
+ inbound.name = "sbwg_" .. node[".name"]
+ inbound.mtu = tonumber(node.wireguard_mtu or 1408)
+ inbound.address = node.wireguard_local_address
+ inbound.private_key = node.wireguard_private_key
+ if users then
+ inbound.peers = users
+ end
end
if node.protocol == "direct" then
@@ -1003,6 +1004,13 @@ function gen_config_server(node)
end
end
+ if node.protocol == "wireguard" then
+ inbound.listen = nil
+ table.insert(endpoints, inbound)
+ else
+ table.insert(inbounds, inbound)
+ end
+
local route = {
rules = {
{
@@ -1025,7 +1033,7 @@ function gen_config_server(node)
}
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.outbound_node_iface))
else
- local outbound_node_t = uci:get_all("passwall", node.outbound_node)
+ local outbound_node_t = api.uci_get_c(node.outbound_node)
if node.outbound_node == "_socks" or node.outbound_node == "_http" then
outbound_node_t = {
type = node.type,
@@ -1057,7 +1065,8 @@ function gen_config_server(node)
tag = "direct"
}}
},
- inbounds = { inbound },
+ endpoints = endpoints,
+ inbounds = inbounds,
outbounds = outbounds,
route = route
}
@@ -1106,6 +1115,7 @@ function gen_config(var)
local remote_dns_client_ip = var["remote_dns_client_ip"]
local remote_dns_query_strategy = var["remote_dns_query_strategy"]
local remote_dns_fake = var["remote_dns_fake"]
+ local remote_rewrite_ttl = var["remote_rewrite_ttl"] or "30"
local dns_cache = var["dns_cache"]
local dns_socks_address = var["dns_socks_address"]
local dns_socks_port = var["dns_socks_port"]
@@ -1121,7 +1131,7 @@ function gen_config(var)
local rule_set_table = {}
local COMMON = {}
- local singbox_settings = uci:get_all(appname, "@global_singbox[0]") or {}
+ local singbox_settings = api.uci_get_c("@global_singbox[0]") or {}
local route = {
rules = {}
@@ -1183,7 +1193,7 @@ function gen_config(var)
end
if node_id then
- local node = uci:get_all(appname, node_id)
+ local node = api.uci_get_c(node_id)
if node then
if server_host and server_port then
node.address = server_host
@@ -1273,7 +1283,7 @@ function gen_config(var)
function get_node_by_id(node_id)
if not node_id or node_id == "" or node_id == "nil" then return nil end
- local section = uci:get_all(appname, node_id) or {}
+ local section = api.uci_get_c(node_id) or {}
if section[".type"] == "socks" then
local result = {
[".name"] = node_id,
@@ -1550,7 +1560,7 @@ function gen_config(var)
--shunt rule
local function foreach_shunt_rule(callback)
- uci:foreach(appname, "shunt_rules", callback)
+ api.uci_foreach_c("shunt_rules", callback)
if use_gfw_list ~= "1" or chn_list ~= "0" then return end
@@ -1576,7 +1586,7 @@ function gen_config(var)
local bin = api.finded_com("geoview")
if bin then
- local geo_file = (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") .. "/geosite.dat"
+ local geo_file = (api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") .. "/geosite.dat"
if luci.sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" | grep -q "^GFW$"') == 0 then
domain_list = (domain_list == "") and "geosite:gfw" or domain_list .. "\ngeosite:gfw"
end
@@ -1992,7 +2002,7 @@ function gen_config(var)
end
end
dns.final = default_dns_flag
- dns.strategy = default_dns_flag == "remote" and remote_strategy or direct_strategy
+ dns.strategy = "prefer_ipv6"
--按分流顺序DNS
if dns_domain_rules and #dns_domain_rules > 0 then
@@ -2034,7 +2044,7 @@ function gen_config(var)
end
if value.outboundTag ~= "block" and value.outboundTag ~= "direct" then
dns_rule.server = "remote"
- dns_rule.rewrite_ttl = 30
+ dns_rule.rewrite_ttl = tonumber(remote_rewrite_ttl)
if true then
local block_rule
if remote_strategy == "ipv4_only" then
@@ -2082,29 +2092,42 @@ function gen_config(var)
end
end
if default_dns_flag == "remote" then
+ local block_rule
local dns_rule_query_type = { "A", "AAAA" }
if remote_strategy == "ipv4_only" then
+ block_rule = {
+ query_type = { "AAAA" },
+ action = "predefined",
+ rcode = "NOERROR"
+ }
dns_rule_query_type = { "A" }
elseif remote_strategy == "ipv6_only" then
+ block_rule = {
+ query_type = { "A" },
+ action = "predefined",
+ rcode = "NOERROR"
+ }
dns_rule_query_type = { "AAAA" }
end
+ if block_rule then
+ table.insert(dns.rules, block_rule)
+ end
if remote_dns_fake then
-- When default is not direct and enable fakedns, default DNS use FakeDNS.
local fakedns_dns_rule = {
query_type = dns_rule_query_type,
server = fakedns_tag,
disable_cache = true,
- rewrite_ttl = 30
+ rewrite_ttl = tonumber(remote_rewrite_ttl)
}
table.insert(dns.rules, fakedns_dns_rule)
- else
- local remote_dns_rule = {
- query_type = dns_rule_query_type,
- server = "remote",
- disable_cache = true,
- }
- table.insert(dns.rules, remote_dns_rule)
end
+ local remote_dns_rule = {
+ server = "remote",
+ disable_cache = true,
+ rewrite_ttl = tonumber(remote_rewrite_ttl)
+ }
+ table.insert(dns.rules, remote_dns_rule)
end
local dns_in_inbound = {
type = "direct",
diff --git a/applications/luci-app-passwall/luasrc/passwall/util_xray.lua b/applications/luci-app-passwall/luasrc/passwall/util_xray.lua
index c62d12509df..b3b0793b094 100644
--- a/applications/luci-app-passwall/luasrc/passwall/util_xray.lua
+++ b/applications/luci-app-passwall/luasrc/passwall/util_xray.lua
@@ -1,9 +1,7 @@
module("luci.passwall.util_xray", package.seeall)
local api = require "luci.passwall.api"
-local uci = api.uci
local sys = api.sys
local jsonc = api.jsonc
-local appname = "passwall"
local fs = api.fs
local GLOBAL = {
@@ -17,7 +15,7 @@ local xray_version = api.get_app_version("xray")
local xray_min_version = "26.3.27"
local function get_domain_excluded()
- local path = string.format("/usr/share/%s/rules/domains_excluded", appname)
+ local path = "/usr/share/passwall/domains_excluded"
local content = fs.readfile(path)
if not content then return nil end
local hosts = {}
@@ -68,8 +66,7 @@ function gen_outbound(flag, node, tag, proxy_table)
config_file = string.format("%s_%s_%s_%s.json", flag, tag, node_id, new_port)
end
if run_socks_instance then
- sys.call(string.format('/usr/share/%s/app.sh run_socks "%s"> /dev/null',
- appname,
+ sys.call(string.format('/usr/share/passwall/app.sh run_socks "%s"> /dev/null',
string.format("flag=%s node=%s bind=%s socks_port=%s config_file=%s relay_port=%s",
new_port, --flag
node_id, --node
@@ -488,17 +485,52 @@ function gen_config_server(node)
{ protocol = "freedom", tag = "direct", settings = { finalRules = {{ action = "allow" }}}}, { protocol = "blackhole", tag = "blocked" }
}
- if node.protocol == "vmess" or node.protocol == "vless" then
- if node.uuid then
- local users = {}
- for i = 1, #node.uuid do
- users[i] = {
- id = node.uuid[i],
- flow = (node.protocol == "vless"
- and (node.tls == "1" or (node.decryption and node.decryption ~= "" and node.decryption ~= "none"))
- and node.flow and node.flow ~= "") and node.flow or nil
- }
+ local users = node.users or {}
+ local users = nil
+ if node.users and #node.users > 0 then
+ users = {}
+ for i, v in ipairs(node.users) do
+ local user = api.uci_get_s(v) or {}
+ if user[".type"] == "user" then
+ local u = {}
+ if node.protocol == "socks" or node.protocol == "http" then
+ u.user = user.username
+ u.pass = user.password
+ end
+ if node.protocol == "shadowsocks" or node.protocol == "trojan" then
+ u.email = user.username
+ u.password = user.password
+ end
+ if node.protocol == "vmess" then
+ u.email = user.username
+ u.id = user.uuid
+ u.alterId = 0
+ end
+ if node.protocol == "vless" then
+ u.email = user.username
+ u.id = user.uuid
+ u.flow = node.flow
+ end
+ if node.protocol == "hysteria2" then
+ u.email = user.username
+ u.auth = user.password
+ end
+ if node.protocol == "wireguard" then
+ u.publicKey = user.wireguard_public_key
+ u.preSharedKey = user.wireguard_pre_shared_key
+ u.keepAlive = 0
+ u.allowedIPs = user.allowed_ips
+ end
+ users[#users + 1] = u
end
+ end
+ if #users == 0 then
+ users = nil
+ end
+ end
+
+ if node.protocol == "vmess" or node.protocol == "vless" then
+ if users then
settings = {
users = users,
decryption = (node.protocol == "vless") and ((node.decryption and node.decryption ~= "") and node.decryption or "none") or nil
@@ -507,40 +539,25 @@ function gen_config_server(node)
elseif node.protocol == "socks" then
settings = {
udp = ("1" == node.udp_forward) and true or false,
- auth = ("1" == node.auth) and "password" or "noauth",
- users = ("1" == node.auth) and {
- {
- user = node.username,
- pass = node.password
- }
- } or nil
+ auth = users and "password" or "noauth",
+ users = users
}
elseif node.protocol == "http" then
settings = {
allowTransparent = false,
- users = ("1" == node.auth) and {
- {
- user = node.username,
- pass = node.password
- }
- } or nil
+ users = users
}
node.transport = "tcp"
node.tcp_guise = "none"
elseif node.protocol == "shadowsocks" then
settings = {
method = node.method,
- password = node.password,
- network = node.ss_network or "TCP,UDP"
+ password = node.ss_password,
+ users = users,
+ network = node.ss_network or "tcp,udp"
}
elseif node.protocol == "trojan" then
- if node.uuid then
- local users = {}
- for i = 1, #node.uuid do
- users[i] = {
- password = node.uuid[i],
- }
- end
+ if users then
settings = {
users = users
}
@@ -548,15 +565,21 @@ function gen_config_server(node)
elseif node.protocol == "hysteria2" then
settings = {
version = 2,
- users = node.hysteria2_auth_password and {
- { auth = node.hysteria2_auth_password }
- }
+ users = users
+ }
+ elseif node.protocol == "tunnel" then
+ settings = {
+ allowedNetwork = node.d_protocol,
+ rewriteAddress = node.d_address,
+ rewritePort = tonumber(node.d_port)
}
- elseif node.protocol == "dokodemo-door" then
+ elseif node.protocol == "wireguard" then
settings = {
- network = node.d_protocol,
- address = node.d_address,
- port = tonumber(node.d_port)
+ secretKey = node.wireguard_private_key,
+ --address = node.wireguard_local_address,
+ --noKernelTun = node.wireguard_system_interface ~= "1" and true or false,
+ mtu = tonumber(node.wireguard_mtu or 1420),
+ peers = users
}
end
@@ -613,7 +636,7 @@ function gen_config_server(node)
}
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.outbound_node_iface))
else
- local outbound_node_t = uci:get_all("passwall", node.outbound_node)
+ local outbound_node_t = api.uci_get_c(node.outbound_node)
if node.outbound_node == "_socks" or node.outbound_node == "_http" then
outbound_node_t = {
type = node.type,
@@ -889,7 +912,7 @@ function gen_config(var)
local outbounds = {}
local COMMON = {}
- local xray_settings = uci:get_all(appname, "@global_xray[0]") or {}
+ local xray_settings = api.uci_get_c("@global_xray[0]") or {}
if xray_settings.fragment == "1" then
local lengths, delays = {}, {}
@@ -914,7 +937,7 @@ function gen_config(var)
if xray_settings.noise == "1" then
local noises = {}
- uci:foreach(appname, "xray_noise_packets", function(n)
+ api.uci_foreach_c("xray_noise_packets", function(n)
if n.enabled == "1" then
local noise = {
rand = (n.type == "rand" and n.packet) and (n.packet:find("-", 1, true) and n.packet or tonumber(n.packet)) or nil,
@@ -932,7 +955,7 @@ function gen_config(var)
end
if node_id then
- local node = uci:get_all(appname, node_id)
+ local node = api.uci_get_c(node_id)
local balancers = {}
local rules = {}
if node then
@@ -989,7 +1012,7 @@ function gen_config(var)
function get_node_by_id(node_id)
if not node_id or node_id == "" or node_id == "nil" then return nil end
- local section = uci:get_all(appname, node_id) or {}
+ local section = api.uci_get_c(node_id) or {}
if section[".type"] == "socks" then
local result = {
[".name"] = node_id,
@@ -1208,8 +1231,8 @@ function gen_config(var)
tag = in_tag,
listen = "127.0.0.1",
port = new_port,
- protocol = "dokodemo-door",
- settings = {network = "tcp,udp", address = to_node.address, port = tonumber(to_node.port)}
+ protocol = "tunnel",
+ settings = {allowedNetwork = "tcp,udp", rewriteAddress = to_node.address, rewritePort = tonumber(to_node.port)}
})
if to_node.tls_serverName == nil then
to_node.tls_serverName = to_node.address
@@ -1362,7 +1385,7 @@ function gen_config(var)
--shunt rule
local function foreach_shunt_rule(callback)
- uci:foreach(appname, "shunt_rules", callback)
+ api.uci_foreach_c("shunt_rules", callback)
if use_gfw_list ~= "1" or chn_list ~= "0" then return end
@@ -1388,7 +1411,7 @@ function gen_config(var)
local bin = api.finded_com("geoview")
if bin then
- local geo_file = (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") .. "/geosite.dat"
+ local geo_file = (api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") .. "/geosite.dat"
if luci.sys.call('"' .. bin .. '" -type geosite -input "' .. geo_file .. '" | grep -q "^GFW$"') == 0 then
domain_list = (domain_list == "") and "geosite:gfw" or domain_list .. "\ngeosite:gfw"
end
@@ -1564,8 +1587,8 @@ function gen_config(var)
if tcp_redir_port or udp_redir_port then
local inbound = {
- protocol = "dokodemo-door",
- settings = {network = "tcp,udp", followRedirect = true},
+ protocol = "tunnel",
+ settings = {allowedNetwork = "tcp,udp", followRedirect = true},
streamSettings = {sockopt = {tproxy = "tproxy"}},
sniffing = {
enabled = (xray_settings.sniffing_override_dest == "1") or (node and node.protocol == "_shunt") or false
@@ -1591,7 +1614,7 @@ function gen_config(var)
if tcp_redir_port then
local tcp_inbound = api.clone(inbound)
tcp_inbound.tag = "tcp_redir"
- tcp_inbound.settings.network = "tcp"
+ tcp_inbound.settings.allowedNetwork = "tcp"
tcp_inbound.port = tonumber(tcp_redir_port)
tcp_inbound.streamSettings.sockopt.tproxy = tcp_proxy_way
table.insert(inbounds, tcp_inbound)
@@ -1600,7 +1623,7 @@ function gen_config(var)
if udp_redir_port then
local udp_inbound = api.clone(inbound)
udp_inbound.tag = "udp_redir"
- udp_inbound.settings.network = "udp"
+ udp_inbound.settings.allowedNetwork = "udp"
udp_inbound.port = tonumber(udp_redir_port)
table.insert(inbounds, udp_inbound)
end
@@ -1767,16 +1790,15 @@ function gen_config(var)
table.insert(inbounds, {
listen = "127.0.0.1",
port = tonumber(dns_listen_port),
- protocol = "dokodemo-door",
+ protocol = "tunnel",
tag = "dns-in",
settings = {
- address = "0.0.0.0",
- network = "tcp,udp"
+ allowedNetwork = "tcp,udp"
}
})
-- remote dns outbound
- local chn_list = uci:get(appname, "@global[0]", "chn_list") or "direct"
+ local chn_list = api.uci_get_c("@global[0]", "chn_list") or "direct"
remote_dns_outbound = {
tag = "dns-out",
protocol = "dns",
@@ -1967,11 +1989,11 @@ function gen_config(var)
if inbounds or outbounds then
local config = {
env = (function()
- local asset_location = uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"
+ local asset_location = api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"
return { XRAY_LOCATION_ASSET = asset_location }
end)(),
log = {
- -- error = string.format("/tmp/etc/%s/%s.log", appname, node[".name"]),
+ -- error = string.format("/tmp/etc/passwall/%s.log", node[".name"]),
loglevel = get_log_level(loglevel)
},
-- DNS
diff --git a/applications/luci-app-passwall/luasrc/view/passwall/acl/config_footer.htm b/applications/luci-app-passwall/luasrc/view/passwall/acl/config_footer.htm
index 31335478f7a..a5c31942cab 100644
--- a/applications/luci-app-passwall/luasrc/view/passwall/acl/config_footer.htm
+++ b/applications/luci-app-passwall/luasrc/view/passwall/acl/config_footer.htm
@@ -1,6 +1,7 @@
<%
-local api = self.api
-local cfgid = self.cfgid
+local map = self.map
+local api = map.api
+local sid = self.section
-%>
-
+
+
diff --git a/applications/luci-app-passwall/luasrc/view/passwall/node_subscribe/js.htm b/applications/luci-app-passwall/luasrc/view/passwall/node_subscribe/js.htm
index 3965a1bbcb8..c6f41cbb106 100644
--- a/applications/luci-app-passwall/luasrc/view/passwall/node_subscribe/js.htm
+++ b/applications/luci-app-passwall/luasrc/view/passwall/node_subscribe/js.htm
@@ -1,5 +1,6 @@
<%
-local api = require "luci.passwall.api"
+local map = self.map
+local api = map.api
-%>
diff --git a/applications/luci-app-passwall/luasrc/view/passwall/rule_list/geoview.htm b/applications/luci-app-passwall/luasrc/view/passwall/rule_list/geoview.htm
index 098fae35d65..7ea144ad1b6 100644
--- a/applications/luci-app-passwall/luasrc/view/passwall/rule_list/geoview.htm
+++ b/applications/luci-app-passwall/luasrc/view/passwall/rule_list/geoview.htm
@@ -1,5 +1,6 @@
<%
-local api = require "luci.passwall.api"
+local map = self.map
+local api = map.api
-%>