🌴 Sync 2026-03-05 23:51:42

This commit is contained in:
github-actions[bot] committed 2026-03-05 23:51:42 +08:00
1 parent 97c049ed0a
commit 0acff03804
14253 files changed
+3893239

No files matched your search

@@ -0,0 +1,93 @@
-- Copyright (C) 2014-2017 Jian Chang <aa65535@live.com>
-- Copyright (C) 2020-2023 honwen <https://github.com/honwen>
-- Licensed to the public under the GNU General Public License v3.
module("luci.controller.shadowsocks", package.seeall)
function index()
if not nixio.fs.access("/etc/config/shadowsocks") then
return
end
page = entry({"admin", "services", "shadowsocks"},
alias("admin", "services", "shadowsocks", "general"),
_("ShadowSocks"), 10)
page.dependent = true
page.acl_depends = { "luci-app-shadowsocks" }
page = entry({"admin", "services", "shadowsocks", "general"},
cbi("shadowsocks/general"),
_("General Settings"), 10)
page.leaf = true
page.acl_depends = { "luci-app-shadowsocks" }
page = entry({"admin", "services", "shadowsocks", "status"},
call("action_status"))
page.leaf = true
page.acl_depends = { "luci-app-shadowsocks" }
page = entry({"admin", "services", "shadowsocks", "servers"},
arcombine(cbi("shadowsocks/servers"), cbi("shadowsocks/servers-details")),
_("Servers Manage"), 20)
page.leaf = true
page.acl_depends = { "luci-app-shadowsocks" }
if luci.sys.call("command -v sslocal >/dev/null") ~= 0 then
return
end
page = entry({"admin", "services", "shadowsocks", "access-control"},
cbi("shadowsocks/access-control"),
_("Access Control"), 30)
page.leaf = true
page.acl_depends = { "luci-app-shadowsocks" }
entry({"admin", "services", "shadowsocks", "log"},
call("action_log"),
_("System Log"), 90)
page.leaf = true
page.acl_depends = { "luci-app-shadowsocks" }
if luci.sys.call("command -v /etc/init.d/dnsmasq-extra >/dev/null") ~= 0 then
return
end
page = entry({"admin", "services", "shadowsocks", "gfwlist"},
call("action_gfw"),
_("GFW-List"), 60)
page.leaf = true
page.acl_depends = { "luci-app-shadowsocks" }
page = entry({"admin", "services", "shadowsocks", "custom"},
cbi("shadowsocks/gfwlist-custom"),
_("Custom-List"), 50)
page.leaf = true
page.acl_depends = { "luci-app-shadowsocks" }
end
local function is_running(name)
return luci.sys.call("pgrep -f '%s' >/dev/null" %{name}) == 0
end
function action_status()
luci.http.prepare_content("application/json")
luci.http.write_json({
ss_redir = is_running("/var/run/ss-redir"),
ss_http = is_running("/var/run/ss-local-http"),
ss_socks = is_running("/var/run/ss-local-socks"),
ss_tunnel = is_running("/var/run/ss-tunnel")
})
end
function action_log()
local conffile = "/var/log/shadowsocks_healthcheck.log"
local healthcheck = nixio.fs.readfile(conffile) or ""
luci.template.render("shadowsocks/plain", {content=healthcheck})
end
function action_gfw()
local conffile = "/etc/dnsmasq-extra.d/gfwlist"
local gfwlist = nixio.fs.readfile(conffile) or luci.sys.exec("cat %s.gz | gunzip -c" %{conffile}) or ""
luci.template.render("shadowsocks/plain", {content=gfwlist})
end
@@ -0,0 +1,136 @@
-- Copyright (C) 2016-2017 Jian Chang <aa65535@live.com>
-- Licensed to the public under the GNU General Public License v3.
local m, s, o
local shadowsocks = "shadowsocks"
local uci = luci.model.uci.cursor()
local nwm = require("luci.model.network").init()
local gfwroute = luci.sys.call("command -v /etc/init.d/dnsmasq-extra >/dev/null") == 0
local chnroute = gfwroute and "/etc/chinadns_chnroute.txt" or uci:get_first("chinadns", "chinadns", "chnroute")
local lan_ifaces = {}
local io = require "io"
local function ipv4_hints(callback)
local hosts = {}
uci:foreach("dhcp", "dnsmasq", function(s)
if s.leasefile and nixio.fs.access(s.leasefile) then
for e in io.lines(s.leasefile) do
mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
if mac and ip then
hosts[ip] = name ~= "*" and name or mac:upper()
end
end
end
end)
uci:foreach("dhcp", "host", function(s)
for mac in luci.util.imatch(s.mac) do
if s.ip then
hosts[s.ip] = s.name or mac:upper()
end
end
end)
for ip, name in pairs(hosts) do
callback(ip, name)
end
end
for _, net in ipairs(nwm:get_networks()) do
if net:name() ~= "loopback" and string.find(net:name(), "wan") ~= 1 then
net = nwm:get_network(net:name())
local device = net and net:get_interface()
if device then
lan_ifaces[device:name()] = device:get_i18n()
end
end
end
m = Map(shadowsocks, "%s - %s" %{translate("ShadowSocks"), translate("Access Control")})
-- [[ Zone WAN ]]--
s = m:section(TypedSection, "access_control", translate("Zone WAN"))
s.anonymous = true
o = s:option(Value, "wan_bp_list", translate("Bypassed IP List"))
o:value("/dev/null", translate("NULL - As Global Proxy"))
if gfwroute then o:value("/dev/flag_gfwlist", translate("Only Proxy GFW-List")) end
if chnroute then o:value(chnroute, translate("ChinaDNS CHNRoute")) end
-- o.datatype = "or(file, '/dev/null')"
o.default = "/dev/null"
o.rmempty = false
o = s:option(DynamicList, "wan_bp_ips", translate("Bypassed IP"))
o.datatype = "ip4addr"
o.rmempty = true
o = s:option(Value, "wan_fw_list", translate("Forwarded IP List"))
o.datatype = "or(file, '/dev/null')"
o.rmempty = true
o = s:option(DynamicList, "wan_fw_ips", translate("Forwarded IP"))
o.datatype = "ip4addr"
o.rmempty = true
-- [[ Zone LAN ]]--
s = m:section(TypedSection, "access_control", translate("Zone LAN"))
s.anonymous = true
o = s:option(MultiValue, "lan_ifaces", translate("Interface"))
function o.cfgvalue(...)
local v = MultiValue.cfgvalue(...)
if v then
return v
else
local names = {}
for name, _ in pairs(lan_ifaces) do
names[#names+1] = name
end
return table.concat(names, " ")
end
end
for name, i18n in pairs(lan_ifaces) do
o:value(name, i18n)
end
o = s:option(ListValue, "lan_target", translate("Proxy Type"))
o:value("SS_SPEC_WAN_AC", translate("Normal"))
o:value("RETURN", translate("Direct"))
o:value("SS_SPEC_WAN_FW", translate("Global"))
o.rmempty = false
o = s:option(ListValue, "self_proxy", translate("Self Proxy"))
o:value("1", translate("Normal"))
o:value("0", translate("Direct"))
o:value("2", translate("Global"))
o.rmempty = false
o = s:option(Value, "ipt_ext", translate("Extra arguments"),
translate("Passes additional arguments to iptables. Use with care!"))
o:value("", translate("None"))
o:value("--dport 22:1023", translatef("Proxy port numbers %s only", "22~1023"))
o:value("-m multiport --dport 22:1023,8000:8443", translatef("Proxy port numbers %s only", "22~1023,8000~8443"))
o:value("-m multiport --dports 53,80,443,853", translatef("Proxy port numbers %s only", "53,80,443,853"))
-- [[ LAN Hosts ]]--
s = m:section(TypedSection, "lan_hosts", translate("LAN Hosts"))
s.template = "cbi/tblsection"
s.addremove = true
s.anonymous = true
o = s:option(Value, "host", translate("Host"))
ipv4_hints(function(ip, name)
o:value(ip, "%s (%s)" %{ip, name})
end)
o.datatype = "ip4addr"
o.rmempty = false
o = s:option(ListValue, "type", translate("Proxy Type"))
o:value("b", translate("Direct"))
o:value("g", translate("Global"))
o:value("n", translate("Normal"))
o.rmempty = false
o = s:option(Flag, "enable", translate("Enable"))
o.default = "1"
o.rmempty = false
return m
@@ -0,0 +1,203 @@
-- Copyright (C) 2014-2018 Jian Chang <aa65535@live.com>
-- Copyright (C) 2020-2023 honwen <https://github.com/honwen>
-- Licensed to the public under the GNU General Public License v3.
local m, s, o
local shadowsocks = "shadowsocks"
local uci = luci.model.uci.cursor()
local servers = {}
local function has_bin(name)
return luci.sys.call("command -v %s >/dev/null" %{name}) == 0
end
local function has_udp_relay()
return luci.sys.call("lsmod | grep -q TPROXY && command -v ip >/dev/null") == 0
end
local function support_fast_open()
local bit = luci.sys.exec("cat /proc/sys/net/ipv4/tcp_fastopen 2>/dev/null"):trim()
return bit == "1" or bit == "3"
end
local has_ss = has_bin("sslocal")
if not has_ss then
return Map(shadowsocks, "%s - %s" %{translate("ShadowSocks"),
translate("General Settings")}, '<b style="color:red">shadowsocks-rust binary file not found.</b>')
end
local function is_running(name)
return luci.sys.call("pgrep -f '%s' >/dev/null" %{name}) == 0
end
local function get_status(name)
return is_running(name) and translate("RUNNING") or translate("NOT RUNNING")
end
uci:foreach(shadowsocks, "servers", function(s)
if s.server and s.server_port then
servers[#servers+1] = {name = s[".name"], alias = s.alias or "%s:%s" %{s.server, s.server_port}}
end
end)
m = Map(shadowsocks, "%s - %s" %{translate("ShadowSocks"), translate("General Settings")})
m.template = "shadowsocks/general"
-- [[ Running Status ]]--
s = m:section(TypedSection, "general", translate("Running Status"))
s.anonymous = true
o = s:option(DummyValue, "_redir_status", translate("Transparent Proxy"))
o.value = "<span id=\"_redir_status\">%s</span>" %{get_status("/var/run/ss-redir")}
o.rawhtml = true
o = s:option(DummyValue, "http_status", translate("HTTP Proxy"))
o.value = "<span id=\"_http_status\">%s</span>" %{get_status("/var/run/ss-local-http")}
o.rawhtml = true
o = s:option(DummyValue, "socks_status", translate("SOCKS5 Proxy"))
o.value = "<span id=\"_socks_status\">%s</span>" %{get_status("/var/run/ss-local-socks")}
o.rawhtml = true
o = s:option(DummyValue, "_tunnel_status", translate("Port Forward"))
o.value = "<span id=\"_tunnel_status\">%s</span>" %{get_status("/var/run/ss-tunnel")}
o.rawhtml = true
s = m:section(TypedSection, "general", translate("Global Settings"))
s.anonymous = true
o = s:option(Value, "startup_delay", translate("Startup Delay"))
o:value(0, translate("Not enabled"))
for _, v in ipairs({3, 5, 10, 15, 25, 40}) do
o:value(v, translatef("%u seconds", v))
end
o.datatype = "uinteger"
o.default = 0
o.rmempty = false
o = s:option(Value, "udp_timeout", translate("Timeout for UDP Associations"))
for _, v in ipairs({10, 30, 60, 120, 180, 300, 600, 1800}) do
o:value(v, translatef("%u seconds", v))
end
o.default = 300
o.rmempty = false
o = s:option(Value, "udp_max_associations", translate("Maximum UDP Associations"))
o:value(0, translate("Unlimited"))
for _, v in ipairs({64, 256, 1024, 5120, 8192, 32768}) do
o:value(v)
end
o.default = 512
o.rmempty = false
o = s:option(Flag, "no_dns_injection", translate("Disable DNS Auto-Injection"))
o.rmempty = false
-- [[ Transparent Proxy ]]--
s = m:section(TypedSection, "transparent_proxy", translate("Transparent Proxy"))
s.anonymous = true
o = s:option(Flag, "no_delay", translate("TCP no-delay"))
o.rmempty = false
if support_fast_open() then
o = s:option(Flag, "fast_open", translate("TCP Fast Open"))
o.rmempty = false
end
o = s:option(DynamicList, "main_server", translate("Main Server"))
o:value("nil", translate("Disable"))
for _, s in ipairs(servers) do o:value(s.name, s.alias) end
o.default = "nil"
o.rmempty = false
o = s:option(ListValue, "udp_relay_server", translate("UDP-Relay Server"))
if has_udp_relay() then
o:value("nil", translate("Disable"))
o:value("same", translate("Same as Main Server"))
for _, s in ipairs(servers) do o:value(s.name, s.alias) end
else
o:value("nil", translate("Unusable - Missing iptables-mod-tproxy or ip"))
end
o.default = "nil"
o.rmempty = false
o = s:option(Value, "local_port", translate("Local Port"))
o.datatype = "port"
o.default = 1234
o.rmempty = false
o = s:option(Flag, "no_healthcheck", translate("Disable HealthCheck"))
o.rmempty = false
-- [[ HTTP Proxy ]]--
s = m:section(TypedSection, "http_proxy", translate("HTTP Proxy"))
s.anonymous = true
o = s:option(Flag, "no_delay", translate("TCP no-delay"))
o.rmempty = false
if support_fast_open() then
o = s:option(Flag, "fast_open", translate("TCP Fast Open"))
o.rmempty = false
end
o = s:option(DynamicList, "server", translate("Server"))
o:value("nil", translate("Disable"))
for _, s in ipairs(servers) do o:value(s.name, s.alias) end
o.default = "nil"
o.rmempty = false
o = s:option(Value, "local_port", translate("Local Port"))
o.datatype = "port"
o.default = 1080
o.rmempty = false
-- [[ SOCKS5 Proxy ]]--
s = m:section(TypedSection, "socks_proxy", translate("SOCKS5 Proxy"))
s.anonymous = true
o = s:option(Flag, "no_delay", translate("TCP no-delay"))
o.rmempty = false
if support_fast_open() then
o = s:option(Flag, "fast_open", translate("TCP Fast Open"))
o.rmempty = false
end
o = s:option(DynamicList, "server", translate("Server"))
o:value("nil", translate("Disable"))
for _, s in ipairs(servers) do o:value(s.name, s.alias) end
o.default = "nil"
o.rmempty = false
o = s:option(Value, "local_port", translate("Local Port"))
o.datatype = "port"
o.default = 1080
o.rmempty = false
-- [[ Port Forward ]]--
s = m:section(TypedSection, "port_forward", translate("Port Forward"))
s.anonymous = true
o = s:option(Flag, "no_delay", translate("TCP no-delay"))
o.rmempty = false
if support_fast_open() then
o = s:option(Flag, "fast_open", translate("TCP Fast Open"))
o.rmempty = false
end
o = s:option(DynamicList, "server", translate("Server"))
o:value("nil", translate("Disable"))
for _, s in ipairs(servers) do o:value(s.name, s.alias) end
o.default = "nil"
o.rmempty = false
o = s:option(Value, "local_port", translate("Local Port"))
o.datatype = "port"
o.default = 5300
o.rmempty = false
o = s:option(Value, "destination", translate("Destination"))
o.default = "8.8.4.4:53"
o.rmempty = false
return m
@@ -0,0 +1,23 @@
local fs = require "nixio.fs"
local conffile = "/etc/dnsmasq-extra.d/custom.conf"
f = SimpleForm("custom", translate("Be Careful!"), translate("Dnsmasq Config for Shadowsocks. This may CRASH DNSMASQ!"))
t = f:field(TextValue, "conf")
t.rmempty = true
t.rows = 30
function t.cfgvalue()
return fs.readfile(conffile) or ""
end
function f.handle(self, state, data)
if state == FORM_VALID then
if data.conf then
fs.writefile(conffile, data.conf:gsub("\r\n", "\n"))
luci.sys.call("/etc/init.d/dnsmasq-extra restart")
end
end
return true
end
return f
@@ -0,0 +1,92 @@
-- Copyright (C) 2016-2017 Jian Chang <aa65535@live.com>
-- Copyright (C) 2020-2023 honwen <https://github.com/honwen>
-- Licensed to the public under the GNU General Public License v3.
local m, s, o
local shadowsocks = "shadowsocks"
local sid = arg[1]
local encrypt_methods = {
"aes-256-gcm",
"aes-128-gcm",
"chacha20-ietf-poly1305",
"2022-blake3-aes-128-gcm",
"2022-blake3-aes-256-gcm",
"2022-blake3-chacha20-poly1305",
"rc4-md5",
"aes-128-cfb",
"aes-192-cfb",
"aes-256-cfb",
"aes-128-ctr",
"aes-192-ctr",
"aes-256-ctr",
"camellia-128-cfb",
"camellia-192-cfb",
"camellia-256-cfb",
"chacha20-ietf",
"plain",
"none",
}
m = Map(shadowsocks, "%s - %s" %{translate("ShadowSocks"), translate("Edit Server")})
m.redirect = luci.dispatcher.build_url("admin/services/shadowsocks/servers")
m.sid = sid
m.template = "shadowsocks/servers-details"
if m.uci:get(shadowsocks, sid) ~= "servers" then
luci.http.redirect(m.redirect)
return
end
-- [[ Edit Server ]]--
s = m:section(NamedSection, sid, "servers")
s.anonymous = true
s.addremove = false
o = s:option(Value, "alias", translate("Alias(optional)"))
o.rmempty = true
o = s:option(Value, "server", translate("Server Address"))
o.datatype = "host"
o.rmempty = false
o = s:option(Value, "server_port", translate("Server Port"))
o.datatype = "port"
o.rmempty = false
o = s:option(Value, "timeout", translate("TCP Connection Timeout"))
o.datatype = "uinteger"
o.default = 60
o.rmempty = false
o = s:option(Value, "password", translate("Password"))
o.password = true
o = s:option(ListValue, "encrypt_method", translate("Encrypt Method"))
for _, v in ipairs(encrypt_methods) do o:value(v, v:upper()) end
o.rmempty = false
o = s:option(Value, "plugin", translate("Plugin Name"))
o.placeholder = "eg: v2ray-plugin"
o = s:option(Value, "plugin_opts", translate("Plugin Arguments"))
o.placeholder = "eg: tls;host=www.bing.com;path=/websocket"
o = s:option(ListValue, "tcp_weight", translate("TCP Weight"))
o.datatype = "ufloat"
o:value(0, translate("0%(UDP ONLY)"))
for _, v in ipairs({0.05, 0.1, 0.2, 0.3, 0.5, 0.7, 0.8, 0.9, 0.95, 1.0}) do
o:value(v, translatef("%u%%", v*100.0))
end
o.default = 1.0
o.rmempty = false
o = s:option(ListValue, "udp_weight", translate("UDP Weight"))
o.datatype = "ufloat"
o:value(0, translate("0%(TCP ONLY)"))
for _, v in ipairs({0.05, 0.1, 0.2, 0.3, 0.5, 0.7, 0.8, 0.9, 0.95, 1.0}) do
o:value(v, translatef("%u%%", v * 100.0))
end
o.default = 1.0
o.rmempty = false
return m
@@ -0,0 +1,50 @@
-- Copyright (C) 2016-2017 Jian Chang <aa65535@live.com>
-- Licensed to the public under the GNU General Public License v3.
local m, s, o
local shadowsocks = "shadowsocks"
m = Map(shadowsocks, "%s - %s" %{translate("ShadowSocks"), translate("Servers Manage")})
-- [[ Servers Manage ]]--
s = m:section(TypedSection, "servers")
s.anonymous = true
s.addremove = true
s.sortable = true
s.template = "cbi/tblsection"
s.extedit = luci.dispatcher.build_url("admin/services/shadowsocks/servers/%s")
function s.create(...)
local sid = TypedSection.create(...)
if sid then
luci.http.redirect(s.extedit % sid)
return
end
end
o = s:option(DummyValue, "alias", translate("Alias"))
function o.cfgvalue(...)
return Value.cfgvalue(...) or translate("None")
end
o = s:option(DummyValue, "server", translate("Server Address"))
function o.cfgvalue(...)
return Value.cfgvalue(...) or "?"
end
o = s:option(DummyValue, "server_port", translate("Server Port"))
function o.cfgvalue(...)
return Value.cfgvalue(...) or "?"
end
o = s:option(DummyValue, "encrypt_method", translate("Encrypt Method"))
function o.cfgvalue(...)
local v = Value.cfgvalue(...)
return v and v:upper() or "?"
end
o = s:option(DummyValue, "plugin", translate("Plugin"))
function o.cfgvalue(...)
return Value.cfgvalue(...) or translate("None")
end
return m
@@ -0,0 +1,92 @@
<%#
Copyright (C) 2017-2018 Jian Chang <aa65535@live.com>
Copyright (C) 2020-2023 honwen <https://github.com/honwen>
Licensed to the public under the GNU General Public License v3.
-%>
<% include("cbi/map") %>
<script type="text/javascript">//<![CDATA[
(function() {
window.cbi_combobox = function(id, values, def, man, focus) {
var selid = "cbi.combobox." + id;
if (document.getElementById(selid)) {
return;
}
var obj = document.getElementById(id);
var sel = document.createElement("select");
sel.id = selid;
sel.index = obj.index;
sel.className = obj.className.replace(/cbi-input-text/, 'cbi-input-select');
if (obj.nextSibling) {
obj.parentNode.insertBefore(sel, obj.nextSibling);
} else {
obj.parentNode.appendChild(sel);
}
var dt = obj.getAttribute('cbi_datatype');
var op = obj.getAttribute('cbi_optional');
if (!values[obj.value]) {
if (obj.value != "") {
var opt = document.createElement("option");
opt.value = obj.value;
opt.selected = "selected";
opt.appendChild(document.createTextNode(obj.value));
sel.appendChild(opt);
}
}
for (var i in values) {
var opt = document.createElement("option");
opt.value = i;
if (obj.value == i) {
opt.selected = "selected";
}
opt.appendChild(document.createTextNode(values[i]));
sel.appendChild(opt);
}
obj.style.display = "none";
if (dt)
cbi_validate_field(sel, op == 'true', dt);
cbi_bind(sel, "change", function() {
obj.value = sel.options[sel.selectedIndex].value;
try {
cbi_d_update();
} catch (e) {
//Do nothing
}
})
// Retrigger validation in select
if (focus !== false) {
sel.focus();
sel.blur();
}
};
var elements = {
ss_redir: document.getElementById("_redir_status"),
ss_http: document.getElementById("_http_status"),
ss_socks: document.getElementById("_socks_status"),
ss_tunnel: document.getElementById("_tunnel_status"),
};
XHR.poll(5, "<%=luci.dispatcher.build_url("admin", "services", "shadowsocks", "status")%>", null, function(xhr, status) {
if (xhr.status === 200) {
for (var k in elements) {
if (elements[k]) {
elements[k].textContent = status[k] ? "<%:RUNNING%>" : "<%:NOT RUNNING%>";
}
}
}
});
}());
//]]></script>
@@ -0,0 +1,9 @@
<%+header%>
<textarea
style="background: #fafafa; color: #666; width: 100%;"
readonly="readonly"
wrap="off"
rows="<%=content:cmatch("\n")+2%>">
<%=content:pcdata()%>
</textarea>
<%+footer%>
@@ -0,0 +1,35 @@
<%#
Copyright (C) 2017-2018 Jian Chang <aa65535@live.com>
Licensed to the public under the GNU General Public License v3.
-%>
<% include("cbi/map") %>
<script type="text/javascript">//<![CDATA[
(function($) {
var password = $("password");
var key = $("key");
var form = key.form;
var checkCryptoConfig = function() {
password.className = password.className.replace(/ cbi-input-invalid/g, "");
key.className = key.className.replace(/ cbi-input-invalid/g, "");
if (password.value.length == 0 && key.value.length == 0) {
password.className += " cbi-input-invalid";
key.className += " cbi-input-invalid";
return false;
}
return true;
};
password.onkeyup = checkCryptoConfig;
key.onkeyup = checkCryptoConfig;
if (form) {
if (!form.cbi_validators) {
form.cbi_validators = [];
}
form.cbi_validators.push(checkCryptoConfig);
}
checkCryptoConfig();
}(function(id) {
return document.getElementById("cbid.<%=self.config%>.<%=self.sid%>." + id);
}));
//]]></script>