From 51ae04a49e3091b671ca12d7c03110c2ddcb6cce Mon Sep 17 00:00:00 2001 From: jhxu Date: Fri, 20 May 2022 19:07:36 +0800 Subject: [PATCH] add file lock --- luasrc/controller/vssr.lua | 48 +++++++++--------- root/etc/init.d/vssr | 38 ++++++++++++-- root/usr/share/vssr/subscribe.lua | 83 ++++++++++++++++--------------- 3 files changed, 101 insertions(+), 68 deletions(-) diff --git a/luasrc/controller/vssr.lua b/luasrc/controller/vssr.lua index 693c43a3..e891313e 100644 --- a/luasrc/controller/vssr.lua +++ b/luasrc/controller/vssr.lua @@ -7,31 +7,31 @@ function index() return end - local page = entry({'admin', 'services', 'vssr'}, alias('admin', 'services', 'vssr', 'client'), _('Hello World'), 0) -- 首页 + local page = entry({ 'admin', 'services', 'vssr' }, alias('admin', 'services', 'vssr', 'client'), _('Hello World'), 0) -- 首页 page.dependent = true page.acl_depends = { "luci-app-vssr" } - entry({'admin', 'services', 'vssr', 'client'}, cbi('vssr/client'), _('SSR Client'), 10).leaf = true -- 基本设置 - entry({'admin', 'services', 'vssr', 'servers'}, cbi('vssr/servers'), _('Severs Nodes'), 11).leaf = true -- 服务器节点 - entry({'admin', 'services', 'vssr', 'servers'}, arcombine(cbi('vssr/servers'), cbi('vssr/client-config')), _('Severs Nodes'), 11).leaf = true -- 编辑节点 - entry({'admin', 'services', 'vssr', 'subscribe_config'}, cbi('vssr/subscribe-config', {hideapplybtn = true, hidesavebtn = true, hideresetbtn = true}), _('Subscribe'), 12).leaf = true -- 订阅设置 - entry({'admin', 'services', 'vssr', 'control'}, cbi('vssr/control'), _('Access Control'), 13).leaf = true -- 访问控制 - entry({'admin', 'services', 'vssr', 'router'}, cbi('vssr/router'), _('Router Config'), 14).leaf = true -- 访问控制 - entry({'admin', 'services', 'vssr', 'socks5'}, cbi('vssr/socks5'), _('Local Proxy'), 15).leaf = true -- Socks5代理 - entry({'admin', 'services', 'vssr', 'advanced'}, cbi('vssr/advanced'), _('Advanced Settings'), 16).leaf = true -- 高级设置 - entry({'admin', 'services', 'vssr', 'server'}, arcombine(cbi('vssr/server'), cbi('vssr/server-config')), _('SSR Server'), 20).leaf = true -- 服务端 - entry({'admin', 'services', 'vssr', 'log'}, cbi('vssr/log'), _('Log'), 30).leaf = true -- 日志 + entry({ 'admin', 'services', 'vssr', 'client' }, cbi('vssr/client'), _('SSR Client'), 10).leaf = true -- 基本设置 + entry({ 'admin', 'services', 'vssr', 'servers' }, cbi('vssr/servers'), _('Severs Nodes'), 11).leaf = true -- 服务器节点 + entry({ 'admin', 'services', 'vssr', 'servers' }, arcombine(cbi('vssr/servers'), cbi('vssr/client-config')), _('Severs Nodes'), 11).leaf = true -- 编辑节点 + entry({ 'admin', 'services', 'vssr', 'subscribe_config' }, cbi('vssr/subscribe-config', { hideapplybtn = true, hidesavebtn = true, hideresetbtn = true }), _('Subscribe'), 12).leaf = true -- 订阅设置 + entry({ 'admin', 'services', 'vssr', 'control' }, cbi('vssr/control'), _('Access Control'), 13).leaf = true -- 访问控制 + entry({ 'admin', 'services', 'vssr', 'router' }, cbi('vssr/router'), _('Router Config'), 14).leaf = true -- 访问控制 + entry({ 'admin', 'services', 'vssr', 'socks5' }, cbi('vssr/socks5'), _('Local Proxy'), 15).leaf = true -- Socks5代理 + entry({ 'admin', 'services', 'vssr', 'advanced' }, cbi('vssr/advanced'), _('Advanced Settings'), 16).leaf = true -- 高级设置 + entry({ 'admin', 'services', 'vssr', 'server' }, arcombine(cbi('vssr/server'), cbi('vssr/server-config')), _('SSR Server'), 20).leaf = true -- 服务端 + entry({ 'admin', 'services', 'vssr', 'log' }, cbi('vssr/log'), _('Log'), 30).leaf = true -- 日志 --entry({'admin', 'services', 'vssr', 'licence'}, template('vssr/licence'), _('Licence'), 40).leaf = true - entry({'admin', 'services', 'vssr', 'refresh'}, call('refresh_data')) -- 更新白名单和GFWLIST - entry({'admin', 'services', 'vssr', 'checkport'}, call('check_port')) -- 检测单个端口并返回Ping - entry({'admin', 'services', 'vssr', 'run'}, call('act_status')) -- 检测全局服务器状态 - entry({'admin', 'services', 'vssr', 'change'}, call('change_node')) -- 切换节点 - entry({'admin', 'services', 'vssr', 'allserver'}, call('get_servers')) -- 获取所有节点Json - entry({'admin', 'services', 'vssr', 'subscribe'}, call('get_subscribe')) -- 执行订阅 - entry({'admin', 'services', 'vssr', 'flag'}, call('get_flag')) -- 获取节点国旗 iso code - entry({'admin', 'services', 'vssr', 'ip'}, call('check_ip')) -- 获取ip情况 - entry({'admin', 'services', 'vssr', 'switch'}, call('switch')) -- 设置节点为自动切换 - entry({'admin', 'services', 'vssr', 'delnode'}, call('del_node')) -- 删除某个节点 + entry({ 'admin', 'services', 'vssr', 'refresh' }, call('refresh_data')) -- 更新白名单和GFWLIST + entry({ 'admin', 'services', 'vssr', 'checkport' }, call('check_port')) -- 检测单个端口并返回Ping + entry({ 'admin', 'services', 'vssr', 'run' }, call('act_status')) -- 检测全局服务器状态 + entry({ 'admin', 'services', 'vssr', 'change' }, call('change_node')) -- 切换节点 + entry({ 'admin', 'services', 'vssr', 'allserver' }, call('get_servers')) -- 获取所有节点Json + entry({ 'admin', 'services', 'vssr', 'subscribe' }, call('get_subscribe')) -- 执行订阅 + entry({ 'admin', 'services', 'vssr', 'flag' }, call('get_flag')) -- 获取节点国旗 iso code + entry({ 'admin', 'services', 'vssr', 'ip' }, call('check_ip')) -- 获取ip情况 + entry({ 'admin', 'services', 'vssr', 'switch' }, call('switch')) -- 设置节点为自动切换 + entry({ 'admin', 'services', 'vssr', 'delnode' }, call('del_node')) -- 删除某个节点 end -- 执行订阅 @@ -166,7 +166,7 @@ function check_port() retstring = tostring(ret) == 'true' and '1' or '0' local tt = t1 - t0 luci.http.prepare_content('application/json') - luci.http.write_json({ret = retstring, used = math.floor(tt * 1000 + 0.5)}) + luci.http.write_json({ ret = retstring, used = math.floor(tt * 1000 + 0.5) }) end -- 读取 GeoLite2 库 国旗图标 @@ -254,7 +254,7 @@ function refresh_data() retstring = '-1' end elseif set == 'ip_data' then - local ip_data_url = uci:get("vssr","@socks5_proxy[0]","ip_data_url") + local ip_data_url = uci:get("vssr", "@socks5_proxy[0]", "ip_data_url") refresh_cmd = "wget-ssl -O- '" .. ip_data_url .. "' > /tmp/china_ssr.txt 2>/dev/null" sret = luci.sys.call(refresh_cmd) icount = luci.sys.exec('cat /tmp/china_ssr.txt | wc -l') @@ -304,5 +304,5 @@ function refresh_data() end end luci.http.prepare_content('application/json') - luci.http.write_json({ret = retstring, retcount = icount}) + luci.http.write_json({ ret = retstring, retcount = icount }) end diff --git a/root/etc/init.d/vssr b/root/etc/init.d/vssr index 94fa9088..f0fd4746 100755 --- a/root/etc/init.d/vssr +++ b/root/etc/init.d/vssr @@ -21,6 +21,8 @@ CONFIG_UDP_FILE=/var/etc/${NAME}_u.json CONFIG_SOCK5_FILE=/var/etc/${NAME}_s.json CRON_FILE=/etc/crontabs/root +LOCK_FILE=/var/lock/vssr.lock + #定义可执行文件路径 VSSR_RULES_BIN=/usr/bin/vssr-rules @@ -39,6 +41,30 @@ switch_server=$1 MAXFD=32768 threads=1 +set_lock() { + exec 1000>"$LOCK_FILE" + flock -xn 1000 +} + +unset_lock() { + flock -u 1000 + rm -rf "$LOCK_FILE" +} + +unlock() { + failcount=1 + while [ "$failcount" -le 10 ]; do + if [ -f "$LOCK_FILE" ]; then + echo "$(date "+%Y-%m-%d %H:%M:%S") wait for file unlock: $failcount count" >> /tmp/vssr.log + let "failcount++" + sleep 1s + [ "$failcount" -ge 10 ] && unset_lock + else + break + fi + done +} + #读取 uci 数据 uci_get_by_name() { @@ -374,8 +400,6 @@ start_redir() { local stype=$(uci_get_by_name $GLOBAL_SERVER type) sscmd=$(find_bin $stype) - - if [ "$(uci_get_by_type global threads 0)" = "0" ]; then threads=$(cat /proc/cpuinfo | grep 'processor' | wc -l) else @@ -576,6 +600,7 @@ rules() { } start() { + set_lock if [ -z "$switch_server" ]; then GLOBAL_SERVER=$(uci_get_by_type global global_server) else @@ -635,6 +660,8 @@ EOF fi fi + unset_lock + ENABLE_SERVER=$(uci_get_by_type global global_server) [ "$ENABLE_SERVER" = "nil" ] && return 1 } @@ -644,6 +671,9 @@ boot() { } stop() { + unlock + set_lock + echo "stop" /usr/bin/vssr-rules -f srulecount=$(iptables -L | grep SSR-SERVER-RULE | wc -l) @@ -665,4 +695,6 @@ stop() { /etc/init.d/dnsmasq restart >/dev/null 2>&1 fi del_cron -} + + unset_lock +} \ No newline at end of file diff --git a/root/usr/share/vssr/subscribe.lua b/root/usr/share/vssr/subscribe.lua index cc826729..1fc21434 100644 --- a/root/usr/share/vssr/subscribe.lua +++ b/root/usr/share/vssr/subscribe.lua @@ -14,21 +14,21 @@ require 'luci.sys' local luci = luci local tinsert = table.insert local ssub, slen, schar, sbyte, sformat, sgsub = string.sub, string.len, - string.char, string.byte, - string.format, string.gsub + string.char, string.byte, + string.format, string.gsub local jsonParse, jsonStringify = luci.jsonc.parse, luci.jsonc.stringify local b64decode = nixio.bin.b64decode local cache = {} -local nodeResult = setmetatable({}, {__index = cache}) -- update result +local nodeResult = setmetatable({}, { __index = cache }) -- update result local name = 'vssr' local uciType = 'servers' local ucic = luci.model.uci.cursor() local proxy = ucic:get_first(name, 'server_subscribe', 'proxy', '0') local switch = '0' local subscribe_url = ucic:get_first(name, 'server_subscribe', 'subscribe_url', - {}) + {}) local filter_words = ucic:get_first(name, 'server_subscribe', 'filter_words', - '过期时间/剩余流量') + '过期时间/剩余流量') function print_r(t) local print_r_cache = {} @@ -41,11 +41,11 @@ function print_r(t) for pos, val in pairs(t) do if (type(val) == "table") then print(indent .. "[" .. pos .. "] => " .. tostring(t) .. - " {") + " {") sub_print_r(val, indent .. - string.rep(" ", string.len(pos) + 8)) + string.rep(" ", string.len(pos) + 8)) print(indent .. string.rep(" ", string.len(pos) + 6) .. - "}") + "}") elseif (type(val) == "string") then print(indent .. "[" .. pos .. '] => "' .. val .. '"') else @@ -57,6 +57,7 @@ function print_r(t) end end end + if (type(t) == "table") then print(tostring(t) .. " {") sub_print_r(t, " ") @@ -68,7 +69,7 @@ function print_r(t) end local log = function(...) - print(os.date('%Y-%m-%d %H:%M:%S ') .. table.concat({...}, ' ')) + print(os.date('%Y-%m-%d %H:%M:%S ') .. table.concat({ ... }, ' ')) end -- 分割字符串 local function split(full, sep) @@ -108,6 +109,7 @@ local function clone(object) end return setmetatable(new_table, getmetatable(object)) end + return copyObj(object) end @@ -136,6 +138,7 @@ local function urlEncode(szText) end local function get_urldecode(h) return schar(tonumber(h, 16)) end + local function UrlDecode(szText) return szText:gsub('+', ' '):gsub('%%(%x%x)', get_urldecode) end @@ -145,13 +148,15 @@ local function trim(text) if not text or text == '' then return '' end return (sgsub(text, '^%s*(.-)%s*$', '%1')) end + -- md5 local function md5(content) local stdout = luci.sys.exec('echo "' .. urlEncode(content) .. - '" | md5sum | cut -d " " -f1') + '" | md5sum | cut -d " " -f1') -- assert(nixio.errno() == 0) return trim(stdout) end + -- base64 local function base64Decode(text) local raw = text @@ -168,6 +173,7 @@ local function base64Decode(text) return raw end end + -- 处理数据 local function processData(szType, content, groupName) local result = { @@ -274,8 +280,7 @@ local function processData(szType, content, groupName) local idx_pn = plugin_info:find(';') if idx_pn then result.plugin = plugin_info:sub(1, idx_pn - 1) - result.plugin_opts = - plugin_info:sub(idx_pn + 1, #plugin_info) + result.plugin_opts = plugin_info:sub(idx_pn + 1, #plugin_info) else result.plugin = plugin_info end @@ -326,12 +331,12 @@ local function processData(szType, content, groupName) result.password = content.password result.encrypt_method_ss = content.encryption if content.plugin == "simple-obfs" then - result.plugin = "obfs-local" - else - result.plugin = content.plugin - end + result.plugin = "obfs-local" + else + result.plugin = content.plugin + end result.plugin_opts = content.plugin_options - + result.alias = '[' .. content.airport .. '] ' .. content.remarks end if not result.alias then @@ -354,11 +359,12 @@ local function processData(szType, content, groupName) return result end + -- wget local function wget(url) local stdout = luci.sys.exec( - 'wget-ssl -q --user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36" --no-check-certificate -t 3 -T 10 -O- "' .. - url .. '"') + 'wget-ssl -q --user-agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36" --no-check-certificate -t 3 -T 10 -O- "' .. + url .. '"') return trim(stdout) end @@ -368,7 +374,7 @@ local function check_filer(result) for i, v in pairs(filter_word) do if result.alias:find(v) then log('订阅节点关键字过滤:“' .. v .. - '” ,该节点被丢弃') + '” ,该节点被丢弃') return true end end @@ -385,8 +391,7 @@ local execute = function() for k, url in ipairs(subscribe_url) do local groupName = "" urlTable = split(url, ",") - groupName = - table.getn(urlTable) > 1 and '[' .. urlTable[1] .. '] ' or "" + groupName = table.getn(urlTable) > 1 and '[' .. urlTable[1] .. '] ' or "" url = table.getn(urlTable) > 1 and urlTable[2] or url local raw = wget(url) if #raw > 0 then @@ -410,7 +415,7 @@ local execute = function() local servers = {} -- SS里面包着 干脆直接这样 for _, server in ipairs(nodes.servers) do - tinsert(servers, setmetatable(server, {__index = extra})) + tinsert(servers, setmetatable(server, { __index = extra })) end nodes = servers else @@ -427,12 +432,11 @@ local execute = function() local dat = split(node, '://') if dat and dat[1] and dat[2] then if dat[1] == 'ss' then - result = - processData(dat[1], dat[2], groupName) + result = processData(dat[1], dat[2], groupName) else result = processData(dat[1], - base64Decode(dat[2]), - groupName) + base64Decode(dat[2]), + groupName) end end else @@ -445,14 +449,13 @@ local execute = function() result.server:match("[^0-9a-zA-Z%-%.%s]") -- 中文做地址的 也没有人拿中文域名搞,就算中文域也有Puny Code SB 机场 then log('丢弃无效节点: ' .. result.type .. - ' 节点, ' .. result.alias) + ' 节点, ' .. result.alias) else log('成功解析: ' .. result.type .. - ' 节点, ' .. result.alias) + ' 节点, ' .. result.alias) result.grouphashkey = groupHash tinsert(nodeResult[index], result) - cache[groupHash][result.hashkey] = - nodeResult[index][#nodeResult[index]] + cache[groupHash][result.hashkey] = nodeResult[index][#nodeResult[index]] end end end @@ -485,7 +488,7 @@ local execute = function() ucic:tset(name, old['.name'], dat) -- 标记一下 setmetatable(nodeResult[old.grouphashkey][old.hashkey], - {__index = {_ignore = true}}) + { __index = { _ignore = true } }) end else if not old.alias then @@ -520,19 +523,18 @@ local execute = function() if not ucic:get(name, globalServer) then if firstServer then ucic:set(name, ucic:get_first(name, 'global'), 'global_server', - firstServer) + firstServer) ucic:commit(name) log('当前主服务器已更新,正在自动更换。') end end if firstServer then - luci.sys.call('/etc/init.d/' .. name .. - ' restart > /dev/null 2>&1 &') -- 不加&的话日志会出现的更早 + luci.sys.call('/etc/init.d/' .. name .. ' restart > /dev/null 2>&1') -- 不加&的话日志会出现的更早 else - luci.sys.call('/etc/init.d/' .. name .. ' stop > /dev/null 2>&1 &') -- 不加&的话日志会出现的更早 + luci.sys.call('/etc/init.d/' .. name .. ' stop > /dev/null 2>&1') -- 不加&的话日志会出现的更早 end log('新增节点数量: ' .. add, '删除节点数量: ' .. del) - log('更新成功服务正在启动') + log('更新成功服务启动成功') log('END SUBSCRIBE') end end @@ -540,15 +542,14 @@ end if subscribe_url and #subscribe_url > 0 then xpcall(execute, function(e) log(e) - -- log(debug.traceback()) + log(debug.traceback()) log('发生错误, 正在恢复服务') log('END SUBSCRIBE') local firstServer = ucic:get_first(name, uciType) if firstServer then - luci.sys.call('/etc/init.d/' .. name .. - ' restart > /dev/null 2>&1 &') -- 不加&的话日志会出现的更早 + luci.sys.call('/etc/init.d/' .. name .. ' restart > /dev/null 2>&1') -- 不加&的话日志会出现的更早 else - luci.sys.call('/etc/init.d/' .. name .. ' stop > /dev/null 2>&1 &') -- 不加&的话日志会出现的更早 + luci.sys.call('/etc/init.d/' .. name .. ' stop > /dev/null 2>&1') -- 不加&的话日志会出现的更早 end end) end