openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/api.lua

1262 lines
31 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

module("luci.passwall2.api", package.seeall)
local com = require "luci.passwall2.com"
bin = require "nixio".bin
fs = require "nixio.fs"
sys = require "luci.sys"
uci = require"luci.model.uci".cursor()
util = require "luci.util"
datatypes = require "luci.cbi.datatypes"
jsonc = require "luci.jsonc"
i18n = require "luci.i18n"
appname = "passwall2"
curl_args = { "-skfL", "--connect-timeout 3", "--retry 3" }
command_timeout = 300
OPENWRT_ARCH = nil
DISTRIB_ARCH = nil
LOG_FILE = "/tmp/log/passwall2.log"
CACHE_PATH = "/tmp/etc/passwall2_tmp"
TMP_PATH = "/tmp/etc/" .. appname
TMP_IFACE_PATH = TMP_PATH .. "/iface"
function log(...)
local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
local f, err = io.open(LOG_FILE, "a")
if f and err == nil then
f:write(result .. "\n")
f:close()
end
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_save(cursor, config, commit, apply)
if is_old_uci() then
cursor:save(config)
if commit then
cursor:commit(config)
if apply then
sys.call("/etc/init.d/" .. config .. " reload > /dev/null 2>&1 &")
end
end
else
commit = true
if commit then
if apply then
cursor:commit(config)
else
sh_uci_commit(config)
end
end
end
end
function sh_uci_get(config, section, option)
exec_call(string.format("uci -q get %s.%s.%s", config, section, option))
end
function sh_uci_set(config, section, option, val, commit)
exec_call(string.format("uci -q set %s.%s.%s=\"%s\"", config, section, option, val))
if commit then sh_uci_commit(config) end
end
function sh_uci_del(config, section, option, commit)
exec_call(string.format("uci -q delete %s.%s.%s", config, section, option))
if commit then sh_uci_commit(config) end
end
function sh_uci_add_list(config, section, option, val, commit)
exec_call(string.format("uci -q del_list %s.%s.%s=\"%s\"", config, section, option, val))
exec_call(string.format("uci -q add_list %s.%s.%s=\"%s\"", config, section, option, val))
if commit then sh_uci_commit(config) end
end
function sh_uci_commit(config)
exec_call(string.format("uci -q commit %s", config))
end
function set_cache_var(key, val)
sys.call(string.format('/usr/share/passwall2/app.sh set_cache_var %s "%s"', key, val))
end
function get_cache_var(key)
local val = sys.exec(string.format('echo -n $(/usr/share/passwall2/app.sh get_cache_var %s)', key))
if val == "" then val = nil end
return val
end
function exec_call(cmd)
local process = io.popen(cmd .. '; echo -e "\n$?"')
local lines = {}
local result = ""
local return_code
for line in process:lines() do
lines[#lines + 1] = line
end
process:close()
if #lines > 0 then
return_code = lines[#lines]
for i = 1, #lines - 1 do
result = result .. lines[i] .. ((i == #lines - 1) and "" or "\n")
end
end
return tonumber(return_code), trim(result)
end
function base64Decode(text)
local raw = text
if not text then return '' end
text = text:gsub("%z", "")
text = text:gsub("%c", "")
text = text:gsub("_", "/")
text = text:gsub("-", "+")
local mod4 = #text % 4
text = text .. string.sub('====', mod4 + 1)
local result = nixio.bin.b64decode(text)
if result then
return result:gsub("%z", "")
else
return raw
end
end
--提取URL中的域名和端口(no ip)
function get_domain_port_from_url(url)
local scheme, domain, port = string.match(url, "^(https?)://([%w%.%-]+):?(%d*)")
if not domain then
scheme, domain, port = string.match(url, "^(https?)://(%b[])([^:/]*)/?")
end
if not domain then return nil, nil end
if domain:sub(1, 1) == "[" then domain = domain:sub(2, -2) end
port = port ~= "" and tonumber(port) or (scheme == "https" and 443 or 80)
if datatypes.ipaddr(domain) or datatypes.ip6addr(domain) then return nil, nil end
return domain, port
end
--解析域名
function domainToIPv4(domain, dns)
local Dns = dns or "223.5.5.5"
local IPs = luci.sys.exec('nslookup %s %s | awk \'/^Name:/{getline; if ($1 == "Address:") print $2}\'' % { domain, Dns })
for IP in string.gmatch(IPs, "%S+") do
if datatypes.ipaddr(IP) and not datatypes.ip6addr(IP) then return IP end
end
return nil
end
function curl_base(url, file, args)
if not args then args = {} end
if file then
args[#args + 1] = "-o " .. file
end
local cmd = string.format('curl %s "%s"', table_join(args), url)
return exec_call(cmd)
end
function curl_proxy(url, file, args)
--使用代理
local socks_server = get_cache_var("GLOBAL_SOCKS_server")
if socks_server and socks_server ~= "" then
if not args then args = {} end
local tmp_args = clone(args)
tmp_args[#tmp_args + 1] = "-x socks5h://" .. socks_server
return curl_base(url, file, tmp_args)
end
return nil, nil
end
function curl_logic(url, file, args)
local return_code, result = curl_proxy(url, file, args)
if not return_code or return_code ~= 0 then
return_code, result = curl_base(url, file, args)
end
return return_code, result
end
function curl_direct(url, file, args)
--直连访问
if not args then args = {} end
local tmp_args = clone(args)
local domain, port = get_domain_port_from_url(url)
if domain then
local ip = domainToIPv4(domain)
if ip then
tmp_args[#tmp_args + 1] = "--resolve " .. domain .. ":" .. port .. ":" .. ip
end
end
return curl_base(url, file, tmp_args)
end
function curl_auto(url, file, args)
local return_code, result = curl_proxy(url, file, args)
if not return_code or return_code ~= 0 then
return_code, result = curl_direct(url, file, args)
end
return return_code, result
end
function url(...)
local url = string.format("admin/services/%s", appname)
local args = { ... }
for i, v in pairs(args) do
if v ~= "" then
url = url .. "/" .. v
end
end
return require "luci.dispatcher".build_url(url)
end
function trim(s)
return (s:gsub("^%s*(.-)%s*$", "%1"))
end
-- 分割字符串
function split(full, sep)
if full then
full = full:gsub("%z", "") -- 这里不是很清楚 有时候结尾带个\0
local off, result = 1, {}
while true do
local nStart, nEnd = full:find(sep, off)
if not nEnd then
local res = string.sub(full, off, string.len(full))
if #res > 0 then -- 过滤掉 \0
table.insert(result, res)
end
break
else
table.insert(result, string.sub(full, off, nStart - 1))
off = nEnd + 1
end
end
return result
end
return {}
end
function is_exist(table, value)
for index, k in ipairs(table) do
if k == value then
return true
end
end
return false
end
function repeat_exist(table, value)
local count = 0
for index, k in ipairs(table) do
if k:find("-") and k == value then
count = count + 1
end
end
if count > 1 then
return true
end
return false
end
function remove(...)
for index, value in ipairs({...}) do
if value and #value > 0 and value ~= "/" then
sys.call(string.format("rm -rf %s", value))
end
end
end
function is_install(package)
if package and #package > 0 then
local file_path = "/usr/lib/opkg/info"
local file_ext = ".control"
local has = sys.call("[ -d " .. file_path .. " ]")
if has ~= 0 then
file_path = "/lib/apk/packages"
file_ext = ".list"
end
return sys.call(string.format('[ -s "%s/%s%s" ]', file_path, package, file_ext)) == 0
end
return false
end
function get_args(arg)
local var = {}
for i, arg_k in pairs(arg) do
if i > 0 then
local v = arg[i + 1]
if v then
if repeat_exist(arg, v) == false then
var[arg_k] = v
end
end
end
end
return var
end
function get_function_args(arg)
local var = nil
if arg and #arg > 1 then
local param = {}
for i = 2, #arg do
param[#param + 1] = arg[i]
end
var = get_args(param)
end
return var
end
function strToTable(str)
if str == nil or type(str) ~= "string" then
return {}
end
return loadstring("return " .. str)()
end
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") then
return false
end
return true
end
function is_special_node(e)
return is_normal_node(e) == false
end
function is_ip(val)
if is_ipv6(val) then
val = get_ipv6_only(val)
end
return datatypes.ipaddr(val)
end
function is_ipv6(val)
local str = val
local address = val:match('%[(.*)%]')
if address then
str = address
end
if datatypes.ip6addr(str) then
return true
end
return false
end
function is_ipv6addrport(val)
if is_ipv6(val) then
local address, port = val:match('%[(.*)%]:([^:]+)$')
if port then
return datatypes.port(port)
end
end
return false
end
function get_ipv6_only(val)
local result = ""
if is_ipv6(val) then
result = val
if val:match('%[(.*)%]') then
result = val:match('%[(.*)%]')
end
end
return result
end
function get_ipv6_full(val)
local result = ""
if is_ipv6(val) then
result = val
if not val:match('%[(.*)%]') then
result = "[" .. result .. "]"
end
end
return result
end
function get_ip_type(val)
if is_ipv6(val) then
return "6"
elseif datatypes.ip4addr(val) then
return "4"
end
return ""
end
function is_mac(val)
return datatypes.macaddr(val)
end
function ip_or_mac(val)
if val then
if get_ip_type(val) == "4" then
return "ip"
end
if is_mac(val) then
return "mac"
end
end
return ""
end
function iprange(val)
if val then
local ipStart, ipEnd = val:match("^([^/]+)-([^/]+)$")
if (ipStart and datatypes.ip4addr(ipStart)) and (ipEnd and datatypes.ip4addr(ipEnd)) then
return true
end
end
return false
end
function get_domain_from_url(url)
local domain = string.match(url, "//([^/]+)")
if domain then
return domain
end
return url
end
function get_node_name(node_id)
local e
if type(node_id) == "table" then
e = node_id
else
e = uci:get_all(appname, node_id)
end
if e then
if e.type and e.remarks then
if e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface") then
local type = e.type
if type == "sing-box" then type = "Sing-Box" end
local remark = "%s[%s] " % {type .. " " .. i18n.translatef(e.protocol), e.remarks}
return remark
end
end
end
return ""
end
function get_valid_nodes()
local show_node_info = uci_get_type("global_other", "show_node_info") or "0"
local nodes = {}
uci:foreach(appname, "nodes", function(e)
e.id = e[".name"]
if e.type and e.remarks then
if e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface") then
local type = e.type
if type == "sing-box" then type = "Sing-Box" end
e["remark"] = "%s[%s] " % {type .. " " .. i18n.translatef(e.protocol), e.remarks}
e["node_type"] = "special"
nodes[#nodes + 1] = e
end
if e.port and e.address then
local address = e.address
if is_ip(address) or datatypes.hostname(address) then
local type = e.type
if (type == "sing-box" or type == "Xray") and e.protocol then
local protocol = e.protocol
if protocol == "vmess" then
protocol = "VMess"
elseif protocol == "vless" then
protocol = "VLESS"
elseif protocol == "shadowsocks" then
protocol = "SS"
elseif protocol == "shadowsocksr" then
protocol = "SSR"
elseif protocol == "wireguard" then
protocol = "WG"
elseif protocol == "hysteria" then
protocol = "HY"
elseif protocol == "hysteria2" then
protocol = "HY2"
else
protocol = protocol:gsub("^%l",string.upper)
end
if type == "sing-box" then type = "Sing-Box" end
type = type .. " " .. protocol
end
if is_ipv6(address) then address = get_ipv6_full(address) end
e["remark"] = "%s[%s]" % {type, e.remarks}
if show_node_info == "1" then
e["remark"] = "%s[%s] %s:%s" % {type, e.remarks, address, e.port}
end
e.node_type = "normal"
nodes[#nodes + 1] = e
end
end
end
end)
return nodes
end
function get_node_remarks(n)
local remarks = ""
if n then
if n.protocol and (n.protocol == "_balancing" or n.protocol == "_shunt" or n.protocol == "_iface") then
remarks = "%s[%s] " % {n.type .. " " .. i18n.translatef(n.protocol), n.remarks}
else
local type2 = n.type
if (n.type == "sing-box" or n.type == "Xray") and n.protocol then
local protocol = n.protocol
if protocol == "vmess" then
protocol = "VMess"
elseif protocol == "vless" then
protocol = "VLESS"
else
protocol = protocol:gsub("^%l",string.upper)
end
type2 = type2 .. " " .. protocol
end
remarks = "%s[%s]" % {type2, n.remarks}
end
end
return remarks
end
function get_full_node_remarks(n)
local remarks = get_node_remarks(n)
if #remarks > 0 then
if n.address and n.port then
remarks = remarks .. " " .. n.address .. ":" .. n.port
end
end
return remarks
end
function gen_uuid(format)
local uuid = sys.exec("echo -n $(cat /proc/sys/kernel/random/uuid)")
if format == nil then
uuid = string.gsub(uuid, "-", "")
end
return uuid
end
function gen_short_uuid()
return sys.exec("echo -n $(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 8)")
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
function chmod_755(file)
if file and file ~= "" then
if not fs.access(file, "rwx", "rx", "rx") then
fs.chmod(file, 755)
end
end
end
function get_customed_path(e)
return uci_get_type("global_app", e .. "_file")
end
function finded_com(e)
local bin = get_app_path(e)
if not bin then return end
local s = luci.sys.exec('echo -n $(type -t -p "%s" | head -n1)' % { bin })
if s == "" then
s = nil
end
return s
end
function finded(e)
return luci.sys.exec('echo -n $(type -t -p "/bin/%s" -p "/usr/bin/%s" "%s" | head -n1)' % {e, e, e})
end
function is_finded(e)
return finded(e) ~= "" and true or false
end
function clone(org)
local function copy(org, res)
for k,v in pairs(org) do
if type(v) ~= "table" then
res[k] = v;
else
res[k] = {};
copy(v, res[k])
end
end
end
local res = {}
copy(org, res)
return res
end
local function get_bin_version_cache(file, cmd)
sys.call("mkdir -p /tmp/etc/passwall2_tmp")
if fs.access(file) then
chmod_755(file)
local md5 = sys.exec("echo -n $(md5sum " .. file .. " | awk '{print $1}')")
if fs.access("/tmp/etc/passwall2_tmp/" .. md5) then
return sys.exec("echo -n $(cat /tmp/etc/passwall2_tmp/%s)" % 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/passwall2_tmp/" .. md5)
return version
end
end
end
return ""
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")
path = path and (#path>0 and path or def_path) or def_path
return path
end
end
function get_app_version(app_name, file)
if file == nil then file = get_app_path(app_name) end
return get_bin_version_cache(file, com[app_name].cmd_version)
end
function is_file(path)
if path and #path > 1 then
if sys.exec('[ -f "%s" ] && echo -n 1' % path) == "1" then
return true
end
end
return nil
end
function is_dir(path)
if path and #path > 1 then
if sys.exec('[ -d "%s" ] && echo -n 1' % path) == "1" then
return true
end
end
return nil
end
function get_final_dir(path)
if is_dir(path) then
return path
else
return get_final_dir(fs.dirname(path))
end
end
function get_free_space(dir)
if dir == nil then dir = "/" end
if sys.call("df -k " .. dir .. " >/dev/null 2>&1") == 0 then
return tonumber(sys.exec("echo -n $(df -k " .. dir .. " | awk 'NR>1' | awk '{print $4}')"))
end
return 0
end
function get_file_space(file)
if file == nil then return 0 end
if fs.access(file) then
return tonumber(sys.exec("echo -n $(du -k " .. file .. " | awk '{print $1}')"))
end
return 0
end
function _unpack(t, i)
i = i or 1
if t[i] ~= nil then return t[i], _unpack(t, i + 1) end
end
function table_join(t, s)
if not s then
s = " "
end
local str = ""
for index, value in ipairs(t) do
str = str .. t[index] .. (index == #t and "" or s)
end
return str
end
function exec(cmd, args, writer, timeout)
local os = require "os"
local nixio = require "nixio"
local fdi, fdo = nixio.pipe()
local pid = nixio.fork()
if pid > 0 then
fdo:close()
if writer or timeout then
local starttime = os.time()
while true do
if timeout and os.difftime(os.time(), starttime) >= timeout then
nixio.kill(pid, nixio.const.SIGTERM)
return 1
end
if writer then
local buffer = fdi:read(2048)
if buffer and #buffer > 0 then
writer(buffer)
end
end
local wpid, stat, code = nixio.waitpid(pid, "nohang")
if wpid and stat == "exited" then return code end
if not writer and timeout then nixio.nanosleep(1) end
end
else
local wpid, stat, code = nixio.waitpid(pid)
return wpid and stat == "exited" and code
end
elseif pid == 0 then
nixio.dup(fdo, nixio.stdout)
fdi:close()
fdo:close()
nixio.exece(cmd, args, nil)
nixio.stdout:close()
os.exit(1)
end
end
function parseURL(url)
if not url or url == "" then
return nil
end
local pattern = "^(%w+)://"
local protocol = url:match(pattern)
if not protocol then
--error("Invalid URL: " .. url)
return nil
end
local auth_host_port = url:sub(#protocol + 4)
local auth_pattern = "^([^@]+)@"
local auth = auth_host_port:match(auth_pattern)
local username, password
if auth then
username, password = auth:match("^([^:]+):([^:]+)$")
auth_host_port = auth_host_port:sub(#auth + 2)
end
local host, port = auth_host_port:match("^([^:]+):(%d+)$")
if not host or not port then
--error("Invalid URL: " .. url)
return nil
end
return {
protocol = protocol,
username = username,
password = password,
host = host,
port = tonumber(port)
}
end
function compare_versions(ver1, comp, ver2)
local table = table
if not ver1 then ver1 = "" end
if not ver2 then ver2 = "" end
local av1 = util.split(ver1, "[%.%-]", nil, true)
local av2 = util.split(ver2, "[%.%-]", nil, true)
local max = table.getn(av1)
local n2 = table.getn(av2)
if (max < n2) then max = n2 end
for i = 1, max, 1 do
local s1 = tonumber(av1[i] or 0) or 0
local s2 = tonumber(av2[i] or 0) or 0
if comp == "~=" and (s1 ~= s2) then return true end
if (comp == "<" or comp == "<=") and (s1 < s2) then return true end
if (comp == ">" or comp == ">=") and (s1 > s2) then return true end
if (s1 ~= s2) then return false end
end
return not (comp == "<" or comp == ">")
end
local function auto_get_arch()
local arch = nixio.uname().machine or ""
if not OPENWRT_ARCH and fs.access("/usr/lib/os-release") then
OPENWRT_ARCH = sys.exec("echo -n $(grep 'OPENWRT_ARCH' /usr/lib/os-release | awk -F '[\\042\\047]' '{print $2}')")
if OPENWRT_ARCH == "" then OPENWRT_ARCH = nil end
end
if not DISTRIB_ARCH and fs.access("/etc/openwrt_release") then
DISTRIB_ARCH = sys.exec("echo -n $(grep 'DISTRIB_ARCH' /etc/openwrt_release | awk -F '[\\042\\047]' '{print $2}')")
if DISTRIB_ARCH == "" then DISTRIB_ARCH = nil end
end
if arch:match("^i[%d]86$") then
arch = "x86"
elseif arch:match("armv5") then -- armv5l
arch = "armv5"
elseif arch:match("armv6") then
arch = "armv6"
elseif arch:match("armv7") then -- armv7l
arch = "armv7"
end
if OPENWRT_ARCH or DISTRIB_ARCH then
if arch == "mips" then
if OPENWRT_ARCH and OPENWRT_ARCH:match("mipsel") == "mipsel"
or DISTRIB_ARCH and DISTRIB_ARCH:match("mipsel") == "mipsel" then
arch = "mipsel"
end
elseif arch == "armv7" then
if OPENWRT_ARCH and not OPENWRT_ARCH:match("vfp") and not OPENWRT_ARCH:match("neon")
or DISTRIB_ARCH and not DISTRIB_ARCH:match("vfp") and not DISTRIB_ARCH:match("neon") then
arch = "armv5"
end
end
end
return util.trim(arch)
end
local default_file_tree = {
x86_64 = "amd64",
x86 = "386",
aarch64 = "arm64",
rockchip = "arm64",
mips = "mips",
mips64 = "mips64",
mipsel = "mipsel",
mips64el = "mips64el",
armv5 = "arm.*5",
armv6 = "arm.*6[^4]*",
armv7 = "arm.*7",
armv8 = "arm64"
}
function get_api_json(url)
local jsonc = require "luci.jsonc"
local return_code, content = curl_logic(url, nil, curl_args)
if return_code ~= 0 or content == "" then return {} end
return jsonc.parse(content) or {}
end
local function check_path(app_name)
local path = get_app_path(app_name) or ""
if path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", app_name)
}
end
return {
code = 0,
app_path = path
}
end
function to_check(arch, app_name)
local result = check_path(app_name)
if result.code ~= 0 then
return result
end
if not arch or arch == "" then arch = auto_get_arch() end
local file_tree = com[app_name].file_tree[arch] or default_file_tree[arch] or ""
if file_tree == "" then
return {
code = 1,
error = i18n.translate("Can't determine ARCH, or ARCH not supported.")
}
end
local local_version = get_app_version(app_name)
local match_file_name = string.format(com[app_name].match_fmt_str, file_tree)
local json = get_api_json(com[app_name]:get_url())
if #json > 0 then
json = json[1]
end
if json.tag_name == nil then
return {
code = 1,
error = i18n.translate("Get remote version info failed.")
}
end
local remote_version = json.tag_name
if com[app_name].remote_version_str_replace then
remote_version = remote_version:gsub(com[app_name].remote_version_str_replace, "")
end
local has_update = compare_versions(local_version:match("[^v]+"), "<", remote_version:match("[^v]+"))
--[[
if not has_update then
return {
code = 0,
local_version = local_version,
remote_version = remote_version
}
end
]]
local asset = {}
for _, v in ipairs(json.assets) do
if v.name and v.name:match(match_file_name) then
asset = v
break
end
end
if not asset.browser_download_url then
return {
code = 1,
local_version = local_version,
remote_version = remote_version,
html_url = json.html_url,
data = asset,
error = i18n.translate("New version found, but failed to get new version download url.")
}
end
return {
code = 0,
has_update = has_update,
local_version = local_version,
remote_version = remote_version,
html_url = json.html_url,
data = asset
}
end
function to_download(app_name, url, size)
local result = check_path(app_name)
if result.code ~= 0 then
return result
end
if not url or url == "" then
return {code = 1, error = i18n.translate("Download url is required.")}
end
sys.call("/bin/rm -f /tmp/".. app_name .."_download.*")
local tmp_file = util.trim(util.exec("mktemp -u -t ".. app_name .."_download.XXXXXX"))
if size then
local kb1 = get_free_space("/tmp")
if tonumber(size) > tonumber(kb1) then
return {code = 1, error = i18n.translatef("%s not enough space.", "/tmp")}
end
end
local _curl_args = clone(curl_args)
table.insert(_curl_args, "-m 60")
local return_code, result = curl_logic(url, tmp_file, _curl_args)
result = return_code == 0
if not result then
exec("/bin/rm", {"-f", tmp_file})
return {
code = 1,
error = i18n.translatef("File download failed or timed out: %s", url)
}
end
return {code = 0, file = tmp_file, zip = com[app_name].zipped }
end
function to_extract(app_name, file, subfix)
local result = check_path(app_name)
if result.code ~= 0 then
return result
end
if not file or file == "" or not fs.access(file) then
return {code = 1, error = i18n.translate("File path required.")}
end
local tools_name
if com[app_name].zipped then
if not com[app_name].zipped_suffix or com[app_name].zipped_suffix == "zip" then
tools_name = "unzip"
end
if com[app_name].zipped_suffix and com[app_name].zipped_suffix == "tar.gz" then
tools_name = "tar"
end
if tools_name then
if sys.exec("echo -n $(command -v %s)" % { tools_name }) == "" then
exec("/bin/rm", {"-f", file})
return {
code = 1,
error = i18n.translate("Not installed %s, Can't unzip!" % { tools_name })
}
end
end
end
sys.call("/bin/rm -rf /tmp/".. app_name .."_extract.*")
local new_file_size = get_file_space(file)
local tmp_free_size = get_free_space("/tmp")
if tmp_free_size <= 0 or tmp_free_size <= new_file_size then
return {code = 1, error = i18n.translatef("%s not enough space.", "/tmp")}
end
local tmp_dir = util.trim(util.exec("mktemp -d -t ".. app_name .."_extract.XXXXXX"))
local output = {}
if tools_name then
if tools_name == "unzip" then
local bin = sys.exec("echo -n $(command -v unzip)")
exec(bin, {"-o", file, app_name, "-d", tmp_dir}, function(chunk) output[#output + 1] = chunk end)
elseif tools_name == "tar" then
local bin = sys.exec("echo -n $(command -v tar)")
if com[app_name].zipped_suffix == "tar.gz" then
exec(bin, {"-zxf", file, "-C", tmp_dir}, function(chunk) output[#output + 1] = chunk end)
sys.call("/bin/mv -f " .. tmp_dir .. "/*/" .. com[app_name].name:lower() .. " " .. tmp_dir)
end
end
end
local files = util.split(table.concat(output))
exec("/bin/rm", {"-f", file})
return {code = 0, file = tmp_dir}
end
function to_move(app_name,file)
local result = check_path(app_name)
if result.code ~= 0 then
return result
end
local app_path = result.app_path
local bin_path = file
local cmd_rm_tmp = "/bin/rm -rf /tmp/" .. app_name .. "_download.*"
if fs.stat(file, "type") == "dir" then
bin_path = file .. "/" .. com[app_name].name:lower()
cmd_rm_tmp = "/bin/rm -rf /tmp/" .. app_name .. "_extract.*"
end
if not file or file == "" then
sys.call(cmd_rm_tmp)
return {code = 1, error = i18n.translate("Client file is required.")}
end
local new_version = get_app_version(app_name, bin_path)
if new_version == "" then
sys.call(cmd_rm_tmp)
return {
code = 1,
error = i18n.translate("The client file is not suitable for current device.")..app_name.."__"..bin_path
}
end
local flag = sys.call('pgrep -af "passwall2/.*'.. app_name ..'" >/dev/null')
if flag == 0 then
sys.call("/etc/init.d/passwall2 stop")
end
local old_app_size = 0
if fs.access(app_path) then
old_app_size = get_file_space(app_path)
end
local new_app_size = get_file_space(bin_path)
local final_dir = get_final_dir(app_path)
local final_dir_free_size = get_free_space(final_dir)
if final_dir_free_size > 0 then
final_dir_free_size = final_dir_free_size + old_app_size
if new_app_size > final_dir_free_size then
sys.call(cmd_rm_tmp)
return {code = 1, error = i18n.translatef("%s not enough space.", final_dir)}
end
end
result = exec("/bin/mv", { "-f", bin_path, app_path }, nil, command_timeout) == 0
sys.call(cmd_rm_tmp)
if flag == 0 then
sys.call("/etc/init.d/passwall2 restart >/dev/null 2>&1 &")
end
if not result or not fs.access(app_path) then
return {
code = 1,
error = i18n.translatef("Can't move new file to path: %s", app_path)
}
end
return {code = 0}
end
function get_version()
local version = sys.exec("opkg list-installed luci-app-passwall2 2>/dev/null | awk '{print $3}'")
if not version or #version == 0 then
version = sys.exec("apk info -L luci-app-passwall2 2>/dev/null | awk 'NR == 1 {print $1}' | cut -d'-' -f4-")
end
return version or ""
end
function to_check_self()
local url = "https://raw.githubusercontent.com/xiaorouji/openwrt-passwall2/main/luci-app-passwall2/Makefile"
local tmp_file = "/tmp/passwall2_makefile"
local return_code, result = curl_logic(url, tmp_file, curl_args)
result = return_code == 0
if not result then
exec("/bin/rm", {"-f", tmp_file})
return {
code = 1,
error = i18n.translatef("Failed")
}
end
local local_version = get_version()
local remote_version = sys.exec("echo -n $(grep 'PKG_VERSION' /tmp/passwall2_makefile|awk -F '=' '{print $2}')")
.. "-" .. sys.exec("echo -n $(grep 'PKG_RELEASE' /tmp/passwall2_makefile|awk -F '=' '{print $2}')")
local has_update = compare_versions(local_version, "<", remote_version)
if not has_update then
return {
code = 0,
local_version = local_version,
remote_version = remote_version
}
end
return {
code = 1,
has_update = true,
local_version = local_version,
remote_version = remote_version,
error = i18n.translatef("The latest version: %s, currently does not support automatic update, if you need to update, please compile or download the ipk and then manually install.", remote_version)
}
end
function cacheFileCompareToLogic(file, str)
local result = nil
if file and str then
local file_str = ""
if fs.access(file) then
file_str = sys.exec("cat " .. file)
end
if file_str ~= str then
sys.call("rm -f " .. file)
result = false
else
result = true
end
local f_out = io.open(file, "w")
if f_out then
f_out:write(str)
f_out:close()
end
end
return result
end
function is_js_luci()
return sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0
end
function set_apply_on_parse(map)
if is_js_luci() == true then
map.apply_on_parse = false
map.on_after_apply = function(self)
if self.redirect then
os.execute("sleep 1")
luci.http.redirect(self.redirect)
end
end
end
end
function luci_types(id, m, s, type_name, option_prefix)
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
else
rewrite_option_table[s.fields[key].rewrite_option] = rewrite_option_table[s.fields[key].rewrite_option] + 1
end
end
s.fields[key].cfgvalue = function(self, section)
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
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
if self.rewrite_option then
m:set(section, self.rewrite_option, value)
else
if self.option:find(option_prefix) == 1 then
m:set(section, self.option:sub(1 + #option_prefix), value)
end
end
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)
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
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
end
else
s.fields[key]:depends({ type = type_name })
end
end
end
end