From ccd8763bcd19cae353b3fc59686fa17e7514291b Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Sat, 18 Jul 2026 20:20:11 +0800
Subject: [PATCH] =?UTF-8?q?=F0=9F=92=90=20Sync=202026-07-18=2020:20:11?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
luci-app-daede/Makefile | 2 +-
.../luci-static/resources/view/daede/daed.js | 2 +-
luci-app-daede/po/zh-cn/daede.po | 3 +
luci-app-daede/po/zh_Hans/daede.po | 3 +
.../share/luci-app-daede/daed-sub-update.sh | 14 +-
luci-app-openclash/Makefile | 4 +-
.../luasrc/controller/openclash.lua | 1741 +-
.../luasrc/model/cbi/openclash/client.lua | 2 +-
.../model/cbi/openclash/config-overwrite.lua | 10 +-
.../cbi/openclash/config-subscribe-edit.lua | 17 +-
.../luasrc/model/cbi/openclash/config.lua | 5 +-
.../model/cbi/openclash/other-file-edit.lua | 1 +
.../luasrc/model/cbi/openclash/settings.lua | 57 +-
luci-app-openclash/luasrc/openclash.lua | 13 +
.../luasrc/view/openclash/common.htm | 50 +
.../luasrc/view/openclash/config_edit.htm | 402 +-
.../luasrc/view/openclash/config_editor.htm | 266 +-
.../view/openclash/config_merge_editor.htm | 472 +
.../luasrc/view/openclash/config_upload.htm | 317 +-
.../luasrc/view/openclash/debug.htm | 2 +
.../luasrc/view/openclash/developer.htm | 2 +-
.../luasrc/view/openclash/log.htm | 184 +-
.../luasrc/view/openclash/myip.htm | 16 +-
.../luasrc/view/openclash/oix_login.htm | 727 +-
.../luasrc/view/openclash/oixcloud.htm | 11 +-
.../view/openclash/other_stream_option.htm | 55 +-
.../luasrc/view/openclash/select_git_cdn.htm | 61 +-
.../luasrc/view/openclash/status.htm | 827 +-
.../luasrc/view/openclash/sub_info_show.htm | 59 +-
.../view/openclash/subconverter_version.htm | 41 +
.../luasrc/view/openclash/tblsection.htm | 27 +-
.../luasrc/view/openclash/toolbar_show.htm | 35 +-
.../luasrc/view/openclash/update.htm | 40 +-
.../luasrc/view/openclash/update_config.htm | 17 +-
luci-app-openclash/po/es/openclash.es.po | 419 +-
.../po/zh-cn/openclash.zh-cn.po | 311 +-
luci-app-openclash/root/etc/init.d/openclash | 167 +-
.../openclash_custom_chnroute6_pass.list | 10 +-
.../openclash_custom_chnroute_pass.list | 10 +-
.../openclash_custom_domain_dns_policy.list | 10 +-
...nclash_custom_proxy_server_dns_policy.list | 8 +-
.../root/etc/openclash/overwrite/default | 2 +-
.../root/usr/share/openclash/YAML.rb | 197 +-
.../root/usr/share/openclash/clash_version.sh | 3 +-
.../root/usr/share/openclash/openclash.sh | 28 +-
.../usr/share/openclash/openclash_core.sh | 9 +-
.../usr/share/openclash/openclash_curl.sh | 45 +-
.../usr/share/openclash/openclash_debug.sh | 76 +-
.../openclash/openclash_download_dashboard.sh | 116 +-
.../openclash/openclash_streaming_unlock.lua | 30 +-
.../usr/share/openclash/openclash_update.sh | 2 +-
.../usr/share/openclash/openclash_watchdog.sh | 40 +-
.../root/usr/share/openclash/yml_change.sh | 6 +-
.../usr/share/openclash/yml_rules_change.sh | 6 +-
.../resources/openclash/css/oc.css | 13899 ++++++++--------
...sb_Bp.woff2 => TwemojiMozilla-flags.woff2} | Bin
.../resources/openclash/img/oixcloud.png | Bin 0 -> 14905 bytes
.../resources/openclash/js/cm6.min.js | 224 +-
.../resources/openclash/js/common.js | 669 +-
.../resources/openclash/js/oc-icons.js | 68 +
.../openclash/js/subconverter-version.js | 388 +
luci-app-openclash/tools/codemirror/entry.js | 2084 +--
.../tools/codemirror/package.json | 60 +-
luci-app-ssr-plus/Makefile | 2 +-
.../servers_subscribe_url_style.htm | 2 +-
natflow/Makefile | 4 +-
66 files changed, 14022 insertions(+), 10358 deletions(-)
create mode 100644 luci-app-openclash/luasrc/view/openclash/common.htm
create mode 100644 luci-app-openclash/luasrc/view/openclash/config_merge_editor.htm
create mode 100644 luci-app-openclash/luasrc/view/openclash/subconverter_version.htm
rename luci-app-openclash/root/www/luci-static/resources/openclash/fonts/{TwemojiMozilla-flags-B12sb_Bp.woff2 => TwemojiMozilla-flags.woff2} (100%)
create mode 100644 luci-app-openclash/root/www/luci-static/resources/openclash/img/oixcloud.png
create mode 100644 luci-app-openclash/root/www/luci-static/resources/openclash/js/oc-icons.js
create mode 100644 luci-app-openclash/root/www/luci-static/resources/openclash/js/subconverter-version.js
diff --git a/luci-app-daede/Makefile b/luci-app-daede/Makefile
index a96cee009..d9f6c1614 100644
--- a/luci-app-daede/Makefile
+++ b/luci-app-daede/Makefile
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-daede
PKG_VERSION:=1.14.7
-PKG_RELEASE:=32
+PKG_RELEASE:=33
PKG_MAINTAINER:=kenzok8
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
diff --git a/luci-app-daede/htdocs/luci-static/resources/view/daede/daed.js b/luci-app-daede/htdocs/luci-static/resources/view/daede/daed.js
index c6e760973..b24094aa2 100644
--- a/luci-app-daede/htdocs/luci-static/resources/view/daede/daed.js
+++ b/luci-app-daede/htdocs/luci-static/resources/view/daede/daed.js
@@ -99,7 +99,7 @@ function renderDaedSettings() {
const err = (res.stderr || res.stdout || ('exit ' + res.code)).trim().split('\n')[0];
flash(_('Subscription update failed: %s').format(err), 'err', 9000);
} else {
- flash(_('Subscriptions updated'), 'ok');
+ flash(_('Subscriptions updated (daed restarted)'), 'ok');
}
}).catch(function(e) {
flash(_('Subscription update failed: %s').format(e.message || e), 'err', 9000);
diff --git a/luci-app-daede/po/zh-cn/daede.po b/luci-app-daede/po/zh-cn/daede.po
index da1067771..e15506436 100644
--- a/luci-app-daede/po/zh-cn/daede.po
+++ b/luci-app-daede/po/zh-cn/daede.po
@@ -628,6 +628,9 @@ msgstr "更新失败:%s"
msgid "Subscriptions updated (dae reloaded)"
msgstr "订阅已更新(dae 已重载)"
+msgid "Subscriptions updated (daed restarted)"
+msgstr "订阅已更新,daed 已重启"
+
msgid "Section"
msgstr "区块"
diff --git a/luci-app-daede/po/zh_Hans/daede.po b/luci-app-daede/po/zh_Hans/daede.po
index da1067771..e15506436 100644
--- a/luci-app-daede/po/zh_Hans/daede.po
+++ b/luci-app-daede/po/zh_Hans/daede.po
@@ -628,6 +628,9 @@ msgstr "更新失败:%s"
msgid "Subscriptions updated (dae reloaded)"
msgstr "订阅已更新(dae 已重载)"
+msgid "Subscriptions updated (daed restarted)"
+msgstr "订阅已更新,daed 已重启"
+
msgid "Section"
msgstr "区块"
diff --git a/luci-app-daede/root/usr/share/luci-app-daede/daed-sub-update.sh b/luci-app-daede/root/usr/share/luci-app-daede/daed-sub-update.sh
index 1330aaa19..997838f8a 100755
--- a/luci-app-daede/root/usr/share/luci-app-daede/daed-sub-update.sh
+++ b/luci-app-daede/root/usr/share/luci-app-daede/daed-sub-update.sh
@@ -115,4 +115,16 @@ while IFS= read -r id || [ -n "$id" ]; do
done < "$TMPDIR/ids"
log "finished daed subscription update: ok=$ok fail=$fail total=$count"
-[ "$fail" -eq 0 ]
+
+if [ "$fail" -ne 0 ]; then
+ exit 1
+fi
+
+if [ "$count" -gt 0 ] && [ "$ok" -gt 0 ] && /bin/pidof daed >/dev/null 2>&1; then
+ log "restarting daed to apply updated subscriptions"
+ if ! /etc/init.d/daed restart >>"$LOG" 2>&1; then
+ fail 8 "daed restart failed after subscription update"
+ fi
+fi
+
+exit 0
diff --git a/luci-app-openclash/Makefile b/luci-app-openclash/Makefile
index c2a96c470..8d2c91b1e 100644
--- a/luci-app-openclash/Makefile
+++ b/luci-app-openclash/Makefile
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-openclash
-PKG_VERSION:=0.47.116
-PKG_RELEASE:=6
+PKG_VERSION:=0.47.133
+PKG_RELEASE:=7
PKG_MAINTAINER:=vernesong
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
diff --git a/luci-app-openclash/luasrc/controller/openclash.lua b/luci-app-openclash/luasrc/controller/openclash.lua
index 4cd932fae..95a2cfd75 100644
--- a/luci-app-openclash/luasrc/controller/openclash.lua
+++ b/luci-app-openclash/luasrc/controller/openclash.lua
@@ -11,6 +11,7 @@ function index()
page.dependent = true
page.acl_depends = { "luci-app-openclash" }
entry({"admin", "services", "openclash", "client"},form("openclash/client"),_("Overviews"), 20).leaf = true
+ entry({"admin", "services", "openclash", "conn_status"},call("action_conn_status")).leaf=true
entry({"admin", "services", "openclash", "status"},call("action_status")).leaf=true
entry({"admin", "services", "openclash", "startlog"},call("action_start")).leaf=true
entry({"admin", "services", "openclash", "refresh_log"},call("action_refresh_log"))
@@ -96,6 +97,7 @@ function index()
entry({"admin", "services", "openclash", "config_file_save"}, call("action_config_file_save"))
entry({"admin", "services", "openclash", "upload_config"}, call("action_upload_config"))
entry({"admin", "services", "openclash", "add_subscription"}, call("action_add_subscription"))
+ entry({"admin", "services", "openclash", "subconverter_version"}, call("action_subconverter_version"))
entry({"admin", "services", "openclash", "generate_age_key"}, call("action_generate_age_key"))
entry({"admin", "services", "openclash", "cal_age_public_key"}, call("action_cal_age_public_key"))
entry({"admin", "services", "openclash", "add_age_config"}, call("action_add_age_config"))
@@ -110,15 +112,21 @@ function index()
entry({"admin", "services", "openclash", "oix_logout"}, call("oix_logout"))
entry({"admin", "services", "openclash", "oix_login"}, call("oix_login"))
entry({"admin", "services", "openclash", "oix_login_info_save"}, call("oix_login_info_save"))
+ entry({"admin", "services", "openclash", "oix_params_sync"}, call("oix_params_sync"))
+ entry({"admin", "services", "openclash", "oix_params_get"}, call("oix_params_get"))
end
+local SYS = require "luci.sys"
+local HTTP = require "luci.http"
+local DISP = require "luci.dispatcher"
+local UTIL = require "luci.util"
local fs = require "luci.openclash"
local json = require "luci.jsonc"
local uci = require("luci.model.uci").cursor()
local datatype = require "luci.cbi.datatypes"
local opkg
local device_name = uci:get("system", "@system[0]", "hostname")
-local device_arh = luci.sys.exec("uname -m |tr -d '\n'")
+local device_arh = SYS.exec("uname -m |tr -d '\n'")
if pcall(require, "luci.model.ipkg") then
opkg = require "luci.model.ipkg"
@@ -134,9 +142,12 @@ else
end
local function is_running()
- return luci.sys.call("pidof clash >/dev/null") == 0
+ return SYS.call("pidof clash >/dev/null") == 0
end
+local CONFIG_PATH_PREFIX = "/etc/openclash/config/"
+local CONFIG_PATH_PREFIX_LEN = #CONFIG_PATH_PREFIX + 1
+
local function is_start()
return process_status("/etc/init.d/openclash")
end
@@ -170,14 +181,14 @@ local function db_foward_ssl()
end
local function check_lastversion()
- luci.sys.exec("bash /usr/share/openclash/openclash_version.sh 2>/dev/null")
- return luci.sys.exec("sed -n '/^https:/,$p' /tmp/openclash_last_version 2>/dev/null")
+ SYS.exec("bash /usr/share/openclash/openclash_version.sh 2>/dev/null")
+ return SYS.exec("sed -n '/^https:/,$p' /tmp/openclash_last_version 2>/dev/null")
end
local function startlog()
local info = ""
if fs.access("/tmp/openclash_start.log") then
- info = luci.sys.exec("sed -n '$p' /tmp/openclash_start.log 2>/dev/null")
+ info = SYS.exec("sed -n '$p' /tmp/openclash_start.log 2>/dev/null")
if string.len(info) > 0 then
info = trans_line(info)
end
@@ -190,9 +201,9 @@ local function coremodel()
return opkg.info("libc")["libc"]["Architecture"]
else
if fs.pkg_type() == "opkg" then
- return luci.sys.exec("rm -f /var/lock/opkg.lock && opkg status libc 2>/dev/null |grep 'Architecture' |awk -F ': ' '{print $2}' 2>/dev/null")
+ return SYS.exec("rm -f /var/lock/opkg.lock && opkg status libc 2>/dev/null |grep 'Architecture' |awk -F ': ' '{print $2}' 2>/dev/null")
else
- return luci.sys.exec("rm -f /lib/apk/db/lock && apk list libc 2>/dev/null |awk '{print $2}'")
+ return SYS.exec("rm -f /lib/apk/db/lock && apk list libc 2>/dev/null |awk '{print $2}'")
end
end
end
@@ -205,12 +216,61 @@ local function check_core()
end
end
+local function parse_subconverter_version_url(raw_url)
+ raw_url = (raw_url or ""):gsub("^%s+", ""):gsub("%s+$", "")
+ if raw_url == "" then return nil, "empty" end
+ if not raw_url:match("^[a-zA-Z][a-zA-Z0-9+%.%-]*://") then
+ raw_url = "https://" .. raw_url
+ end
+
+ local scheme, authority, path = raw_url:match("^(https?)://([^/?#]+)([^?#]*)")
+ if not scheme or not authority then return nil, "invalid" end
+ if authority:find("@", 1, true) then return nil, "invalid" end
+ if authority:match("%s") then return nil, "invalid" end
+ if raw_url:find("?", 1, true) or raw_url:find("#", 1, true) then return nil, "invalid" end
+
+ local host, port
+ if authority:sub(1, 1) == "[" then
+ host, port = authority:match("^(%[[0-9a-fA-F:%.]+%]):?(%d*)$")
+ else
+ host, port = authority:match("^([^:]+):?(%d*)$")
+ end
+ if not host or host == "" then return nil, "invalid" end
+ if port and port ~= "" then
+ port = tonumber(port)
+ if not port or port < 1 or port > 65535 then return nil, "invalid" end
+ end
+
+ path = (path and path ~= "") and path or "/"
+ if path ~= "/version" and not path:match("/version$") then return nil, "invalid" end
+
+ return scheme .. "://" .. authority .. path
+end
+
+local function sanitize_subconverter_version_text(text)
+ text = tostring(text or ""):gsub("\r", "\n")
+ local lines = {}
+ for line in text:gmatch("[^\n]+") do
+ line = line:gsub("^%s+", ""):gsub("%s+$", "")
+ if line ~= "" then lines[#lines + 1] = line end
+ end
+ text = table.concat(lines, " "):gsub("%s+", " "):gsub("^%s+", ""):gsub("%s+$", "")
+ if text == "" or #text > 220 then return "" end
+ if text:match("<%s*/?%s*[%a!][^>]*>") then
+ return ""
+ end
+ if not text:lower():match("subconverter") and not text:lower():match("backend") and not text:lower():match("version") and not text:match("v?%d+%.%d+") then
+ return ""
+ end
+ return text
+end
+
local function coremetacv()
local v = "0"
if not fs.access(meta_core_path) then
return v
else
- v = luci.sys.exec(string.format("%s -v 2>/dev/null |awk -F ' ' '{print $3}' |head -1 |tr -d '\n'", meta_core_path))
+ v = SYS.exec(string.format("%s -v 2>/dev/null |awk -F ' ' '{print $3}' |head -1 |tr -d '\n'", meta_core_path))
if not v or v == "" then
return "0"
end
@@ -225,11 +285,11 @@ local function corelv()
local oix_token = fs.uci_get_config("config", "oix_token") or ""
if fs.access("/tmp/clash_last_version") then
if core_type == "Oix" or oix_token ~= "" then
- core_meta_lv = luci.sys.exec("sed -n 1p /tmp/clash_last_version 2>/dev/null |tr -d '\n'")
+ core_meta_lv = SYS.exec("sed -n 1p /tmp/clash_last_version 2>/dev/null |tr -d '\n'")
elseif core_smart_enable == "1" then
- core_meta_lv = luci.sys.exec("sed -n 2p /tmp/clash_last_version 2>/dev/null |tr -d '\n'")
+ core_meta_lv = SYS.exec("sed -n 2p /tmp/clash_last_version 2>/dev/null |tr -d '\n'")
else
- core_meta_lv = luci.sys.exec("sed -n 1p /tmp/clash_last_version 2>/dev/null |tr -d '\n'")
+ core_meta_lv = SYS.exec("sed -n 1p /tmp/clash_last_version 2>/dev/null |tr -d '\n'")
end
else
core_meta_lv = "loading..."
@@ -246,9 +306,9 @@ local function opcv()
v = info["luci-app-openclash"]["Version"]
else
if fs.pkg_type() == "opkg" then
- v = luci.sys.exec("rm -f /var/lock/opkg.lock && opkg status luci-app-openclash 2>/dev/null |grep 'Version' |awk -F 'Version: ' '{print $2}' |tr -d '\n'")
+ v = SYS.exec("rm -f /var/lock/opkg.lock && opkg status luci-app-openclash 2>/dev/null |grep 'Version' |awk -F 'Version: ' '{print $2}' |tr -d '\n'")
else
- v = luci.sys.exec("rm -f /lib/apk/db/lock && apk list luci-app-openclash 2>/dev/null|grep 'installed' | grep -oE '[0-9]+(\\.[0-9]+)*' | head -1 |tr -d '\n'")
+ v = SYS.exec("rm -f /lib/apk/db/lock && apk list luci-app-openclash 2>/dev/null|grep 'installed' | grep -oE '[0-9]+(\\.[0-9]+)*' | head -1 |tr -d '\n'")
end
end
if v and v ~= "" then
@@ -262,7 +322,7 @@ local function oplv()
local oplv = ""
if fs.access("/tmp/openclash_last_version") then
- oplv = luci.sys.exec("sed -n 1p /tmp/openclash_last_version 2>/dev/null |tr -d '\n'")
+ oplv = SYS.exec("sed -n 1p /tmp/openclash_last_version 2>/dev/null |tr -d '\n'")
else
oplv = "loading..."
end
@@ -272,14 +332,14 @@ local function oplv()
end
local function opup()
- return luci.sys.call("bash /usr/share/openclash/openclash_update.sh >/dev/null 2>&1 &")
+ return SYS.call("bash /usr/share/openclash/openclash_update.sh >/dev/null 2>&1 &")
end
local function coreup()
uci:set("openclash", "config", "enable", "1")
uci:commit("openclash")
- local type = luci.http.formvalue("core_type")
- return luci.sys.call(string.format("/usr/share/openclash/openclash_core.sh '%s' >/dev/null 2>&1 &", type))
+ local type = HTTP.formvalue("core_type")
+ return SYS.call(string.format("/usr/share/openclash/openclash_core.sh '%s' >/dev/null 2>&1 &", type))
end
local function corever()
@@ -295,14 +355,14 @@ local function smart_enable()
end
local function save_corever_branch()
- if luci.http.formvalue("core_ver") then
- uci:set("openclash", "config", "core_version", luci.http.formvalue("core_ver"))
+ if HTTP.formvalue("core_ver") then
+ uci:set("openclash", "config", "core_version", HTTP.formvalue("core_ver"))
end
- if luci.http.formvalue("release_branch") then
- uci:set("openclash", "config", "release_branch", luci.http.formvalue("release_branch"))
+ if HTTP.formvalue("release_branch") then
+ uci:set("openclash", "config", "release_branch", HTTP.formvalue("release_branch"))
end
- if luci.http.formvalue("smart_enable") then
- uci:set("openclash", "config", "smart_enable", luci.http.formvalue("smart_enable"))
+ if HTTP.formvalue("smart_enable") then
+ uci:set("openclash", "config", "smart_enable", HTTP.formvalue("smart_enable"))
end
uci:commit("openclash")
return "success"
@@ -324,11 +384,11 @@ local function upchecktime()
end
function core_download()
- local cdn_url = luci.http.formvalue("url")
+ local cdn_url = HTTP.formvalue("url")
if cdn_url then
- luci.sys.call(string.format("bash /usr/share/openclash/openclash_core.sh 'Meta' '%s' >/dev/null 2>&1 &", cdn_url))
+ SYS.call(string.format("bash /usr/share/openclash/openclash_core.sh 'Meta' '%s' >/dev/null 2>&1 &", cdn_url))
else
- luci.sys.call("bash /usr/share/openclash/openclash_core.sh 'Meta' >/dev/null 2>&1 &")
+ SYS.call("bash /usr/share/openclash/openclash_core.sh 'Meta' >/dev/null 2>&1 &")
end
end
@@ -340,11 +400,11 @@ function action_flush_dns_cache()
local dase = dase() or ""
local cn_port = cn_port()
if not daip or not cn_port then return end
- fake_ip_state = luci.sys.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XPOST http://"%s":"%s"/cache/fakeip/flush', dase, daip, cn_port))
- dns_state = luci.sys.exec(string.format('curl -sL -m 3 --retry 2-H "Content-Type: application/json" -H "Authorization: Bearer %s" -XPOST http://"%s":"%s"/cache/dns/flush', dase, daip, cn_port))
+ fake_ip_state = SYS.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XPOST http://"%s":"%s"/cache/fakeip/flush', dase, daip, cn_port))
+ dns_state = SYS.exec(string.format('curl -sL -m 3 --retry 2-H "Content-Type: application/json" -H "Authorization: Bearer %s" -XPOST http://"%s":"%s"/cache/dns/flush', dase, daip, cn_port))
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
flush_status = dns_state;
})
end
@@ -356,38 +416,38 @@ function action_flush_smart_cache()
local dase = dase() or ""
local cn_port = cn_port()
if not daip or not cn_port then return end
- flush_state = luci.sys.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XPOST http://"%s":"%s"/cache/smart/flush', dase, daip, cn_port))
+ flush_state = SYS.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XPOST http://"%s":"%s"/cache/smart/flush', dase, daip, cn_port))
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
flush_status = flush_state;
})
end
function action_update_config()
-- filename is basename
- local filename = luci.http.formvalue("filename")
+ local filename = HTTP.formvalue("filename")
- luci.http.prepare_content("application/json")
+ HTTP.prepare_content("application/json")
if not filename then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Config file not found"
})
return
end
- local update_result = luci.sys.call(string.format("/usr/share/openclash/openclash.sh '%s' >/dev/null 2>&1", filename))
+ local update_result = SYS.call(string.format("/usr/share/openclash/openclash.sh '%s' >/dev/null 2>&1", filename))
if update_result == 0 then
- luci.http.write_json({
+ HTTP.write_json({
status = "success",
message = "Config update started successfully",
filename = filename
})
else
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Failed to update config"
})
@@ -397,31 +457,31 @@ end
function action_restore_config()
uci:set("openclash", "config", "enable", "0")
uci:commit("openclash")
- luci.sys.call("mkdir -p /etc/openclash/custom >/dev/null 2>&1")
- luci.sys.call("mkdir -p /etc/openclash/overwrite >/dev/null 2>&1")
- luci.sys.call("mkdir -p /etc/openclash/rule_provider >/dev/null 2>&1")
- luci.sys.call("/etc/init.d/openclash stop >/dev/null 2>&1")
- luci.sys.call("cp -f /usr/share/openclash/backup/openclash /etc/config/openclash >/dev/null 2>&1 &")
- luci.sys.call("cp -f /usr/share/openclash/backup/openclash_custom* /etc/openclash/custom/ >/dev/null 2>&1 &")
- luci.sys.call("cp -f /usr/share/openclash/backup/openclash_force_sniffing* /etc/openclash/custom/ >/dev/null 2>&1 &")
- luci.sys.call("cp -f /usr/share/openclash/backup/openclash_sniffing* /etc/openclash/custom/ >/dev/null 2>&1 &")
- luci.sys.call("cp -f /usr/share/openclash/backup/china_ip_route.ipset /etc/openclash/china_ip_route.ipset >/dev/null 2>&1 &")
- luci.sys.call("cp -f /usr/share/openclash/backup/china_ip6_route.ipset /etc/openclash/china_ip6_route.ipset >/dev/null 2>&1 &")
- luci.sys.call("cp -f /usr/share/openclash/backup/overwrite/default /etc/openclash/overwrite/default >/dev/null 2>&1 &")
- luci.sys.call("cp -f /usr/share/openclash/backup/oc-cn-domain.mrs /etc/openclash/rule_provider/oc-cn-domain.mrs >/dev/null 2>&1 &")
- luci.sys.call("rm -rf /etc/openclash/history/* >/dev/null 2>&1 &")
+ SYS.call("mkdir -p /etc/openclash/custom >/dev/null 2>&1")
+ SYS.call("mkdir -p /etc/openclash/overwrite >/dev/null 2>&1")
+ SYS.call("mkdir -p /etc/openclash/rule_provider >/dev/null 2>&1")
+ SYS.call("/etc/init.d/openclash stop >/dev/null 2>&1")
+ SYS.call("cp -f /usr/share/openclash/backup/openclash /etc/config/openclash >/dev/null 2>&1 &")
+ SYS.call("cp -f /usr/share/openclash/backup/openclash_custom* /etc/openclash/custom/ >/dev/null 2>&1 &")
+ SYS.call("cp -f /usr/share/openclash/backup/openclash_force_sniffing* /etc/openclash/custom/ >/dev/null 2>&1 &")
+ SYS.call("cp -f /usr/share/openclash/backup/openclash_sniffing* /etc/openclash/custom/ >/dev/null 2>&1 &")
+ SYS.call("cp -f /usr/share/openclash/backup/china_ip_route.ipset /etc/openclash/china_ip_route.ipset >/dev/null 2>&1 &")
+ SYS.call("cp -f /usr/share/openclash/backup/china_ip6_route.ipset /etc/openclash/china_ip6_route.ipset >/dev/null 2>&1 &")
+ SYS.call("cp -f /usr/share/openclash/backup/overwrite/default /etc/openclash/overwrite/default >/dev/null 2>&1 &")
+ SYS.call("cp -f /usr/share/openclash/backup/oc-cn-domain.mrs /etc/openclash/rule_provider/oc-cn-domain.mrs >/dev/null 2>&1 &")
+ SYS.call("rm -rf /etc/openclash/history/* >/dev/null 2>&1 &")
end
function action_remove_all_core()
- luci.sys.call("rm -rf /etc/openclash/core/* >/dev/null 2>&1")
+ SYS.call("rm -rf /etc/openclash/core/* >/dev/null 2>&1")
end
function action_one_key_update()
- local cdn_url = luci.http.formvalue("url")
+ local cdn_url = HTTP.formvalue("url")
if cdn_url then
- return luci.sys.call(string.format("bash /usr/share/openclash/openclash_update.sh 'one_key_update' '%s' >/dev/null 2>&1 &", cdn_url))
+ return SYS.call(string.format("bash /usr/share/openclash/openclash_update.sh 'one_key_update' '%s' >/dev/null 2>&1 &", cdn_url))
else
- return luci.sys.call("bash /usr/share/openclash/openclash_update.sh 'one_key_update' >/dev/null 2>&1 &")
+ return SYS.call("bash /usr/share/openclash/openclash_update.sh 'one_key_update' >/dev/null 2>&1 &")
end
end
@@ -434,28 +494,25 @@ local function config_name()
e[t].name=fs.basename(o)
end
end
- return json.parse(json.stringify(e)) or e
+ return e
end
local function config_path()
- if fs.uci_get_config("config", "config_path") then
- return string.sub(fs.uci_get_config("config", "config_path"), 23, -1)
- else
- return ""
- end
+ local p = fs.uci_get_config("config", "config_path")
+ return p and string.sub(p, CONFIG_PATH_PREFIX_LEN) or ""
end
function action_switch_config()
- local config_file = luci.http.formvalue("config_file")
- local config_name = luci.http.formvalue("config_name")
+ local config_file = HTTP.formvalue("config_file")
+ local config_name = HTTP.formvalue("config_name")
if not config_file and config_name then
config_file = "/etc/openclash/config/" .. config_name
end
if not config_file then
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
status = "error",
message = "No config file specified"
})
@@ -463,8 +520,8 @@ function action_switch_config()
end
if not fs.access(config_file) then
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
status = "error",
message = "Config file does not exist: " .. config_file
})
@@ -475,10 +532,10 @@ function action_switch_config()
uci:set("openclash", "config", "enable", "1")
uci:commit("openclash")
- luci.sys.call("/etc/init.d/openclash restart >/dev/null 2>&1 &")
+ SYS.call("/etc/init.d/openclash restart >/dev/null 2>&1 &")
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
status = "success",
message = "Config file switched successfully",
config_file = config_file
@@ -487,8 +544,8 @@ end
function set_subinfo_url()
local filename, url, info
- filename = luci.http.formvalue("filename")
- url = luci.http.formvalue("url")
+ filename = HTTP.formvalue("filename")
+ url = HTTP.formvalue("url")
if not filename then
info = "Oops: The config file name seems to be incorrect"
end
@@ -527,7 +584,7 @@ function set_subinfo_url()
local url_list = {}
for line in string.gmatch(url, "[^\n]+") do
if line ~= "" then
- table.insert(url_list, line)
+ url_list[#url_list + 1] = line
end
end
uci:section("openclash", "subscribe_info", nil, {name = filename, url = url_list})
@@ -536,23 +593,37 @@ function set_subinfo_url()
end
end
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
info = info;
})
end
-function fetch_sub_info(sub_url, sub_ua)
+function fetch_sub_info(sub_url, sub_ua, sub_headers, raw_info)
local info, upload, download, total, day_expire, http_code
local used, expire, day_left, percent, surplus
- info = luci.sys.exec(string.format("curl -sLI -X GET -m 5 --retry 2 -w 'http_code=%%{http_code}' -H 'User-Agent: %s' '%s'", sub_ua, sub_url))
- local http_match = string.match(info, "http_code=(%d+)")
- if not info or not http_match or tonumber(http_match) ~= 200 then
- info = luci.sys.exec(string.format("curl -sLI -X GET -m 5 --retry 2 -w 'http_code=%%{http_code}' -H 'User-Agent: Quantumultx' '%s'", sub_url))
- http_match = string.match(info, "http_code=(%d+)")
+ if raw_info then
+ info = raw_info
+ else
+ local header_args = ""
+ if sub_headers and sub_headers ~= "" then
+ for hdr in sub_headers:gmatch("[^\n]+") do
+ hdr = hdr:match("^%s*(.-)%s*$")
+ if hdr and hdr ~= "" then
+ header_args = header_args .. string.format(" -H '%s'", hdr:gsub("'", "'\\''"))
+ end
+ end
+ end
+
+ info = SYS.exec(string.format("curl -sLI -X GET -m 5 --retry 2 -w 'http_code=%%{http_code}' -H 'User-Agent: %s'%s '%s'", sub_ua, header_args, sub_url))
+ local http_match = string.match(info, "http_code=(%d+)")
+ if not info or not http_match or tonumber(http_match) ~= 200 then
+ info = SYS.exec(string.format("curl -sLI -X GET -m 5 --retry 2 -w 'http_code=%%{http_code}' -H 'User-Agent: Quantumultx'%s '%s'", header_args, sub_url))
+ end
end
+ local http_match = string.match(info, "http_code=(%d+)")
if info and http_match then
http_code = http_match
if tonumber(http_code) == 200 then
@@ -669,21 +740,23 @@ function get_sub_url(filename)
function(s)
if s.name == filename and s.url then
if type(s.url) == "table" then
- info_tb = s.url
+ for _, v in ipairs(s.url) do
+ info_tb[#info_tb + 1] = v
+ end
else
- string.gsub(s.url, '[^\n]+', function(w) table.insert(info_tb, w) end)
+ string.gsub(s.url, '[^\n]+', function(w) info_tb[#info_tb + 1] = w end)
end
if #info_tb == 1 then
local url, name = parse_url_with_name(info_tb[1], filename)
if url ~= info_tb[1] then
- table.insert(providers, {name = name, url = url})
+ providers[#providers + 1] = {name = name, url = url}
else
sub_url = url
end
elseif #info_tb > 1 then
for _, raw in ipairs(info_tb) do
local url, name = parse_url_with_name(raw, filename)
- table.insert(providers, {name = name, url = url})
+ providers[#providers + 1] = {name = name, url = url}
end
end
return false
@@ -703,7 +776,7 @@ function get_sub_url(filename)
local config_path = "/etc/openclash/config/" .. fs.basename(filename .. ".yaml")
if fs.access(config_path) then
- local ruby_result = luci.sys.exec(string.format([[
+ local ruby_result = SYS.exec(string.format([[
ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e '
begin
config = YAML.load_file("%s")
@@ -748,14 +821,14 @@ function get_sub_url(filename)
uci:foreach("openclash", "config_subscribe",
function(s)
if s.name == filename and s.address and string.find(s.address, "http") then
- string.gsub(s.address, '[^\n]+', function(w) table.insert(info_tb, w) end)
+ string.gsub(s.address, '[^\n]+', function(w) info_tb[#info_tb + 1] = w end)
if #info_tb == 1 then
local url, _ = parse_url_with_name(info_tb[1], filename)
sub_url = url
elseif #info_tb > 1 then
for _, raw in ipairs(info_tb) do
local url, name = parse_url_with_name(raw, filename)
- table.insert(providers, {name = name, url = url})
+ providers[#providers + 1] = {name = name, url = url}
end
end
end
@@ -774,17 +847,24 @@ function get_sub_url(filename)
end
function sub_info_get()
- local sub_ua, filename, sub_info, url_result
+ local sub_ua, sub_headers, filename, sub_info, url_result
local providers_data = {}
- filename = luci.http.formvalue("filename")
+ filename = HTTP.formvalue("filename")
sub_info = ""
sub_ua = "Clash"
+ sub_headers = ""
uci:foreach("openclash", "config_subscribe",
function(s)
- if s.name == filename and s.sub_ua then
- sub_ua = s.sub_ua
+ if s.name == filename then
+ if s.sub_ua then
+ sub_ua = s.sub_ua
+ end
+ local raw = uci:get_list("openclash", s['.name'], "sub_headers")
+ if raw then
+ sub_headers = table.concat(raw, "\n")
+ end
return false
end
end
@@ -795,16 +875,109 @@ function sub_info_get()
if url_result then
if url_result.type == "single" then
- local info = fetch_sub_info(url_result.url, sub_ua)
+ local info = fetch_sub_info(url_result.url, sub_ua, sub_headers)
if info then
- table.insert(providers_data, info)
+ providers_data[#providers_data + 1] = info
end
- else
+ elseif #url_result.providers <= 1 then
for i, provider in ipairs(url_result.providers) do
- local info = fetch_sub_info(provider.url, sub_ua)
+ local info = fetch_sub_info(provider.url, sub_ua, sub_headers)
if info then
info.provider_name = provider.name
- table.insert(providers_data, info)
+ providers_data[#providers_data + 1] = info
+ end
+ end
+ else
+ local header_args = ""
+ if sub_headers and sub_headers ~= "" then
+ for hdr in sub_headers:gmatch("[^\n]+") do
+ hdr = hdr:match("^%s*(.-)%s*$")
+ if hdr and hdr ~= "" then
+ header_args = header_args .. string.format(" -H '%s'", hdr:gsub("'", "'\\''"))
+ end
+ end
+ end
+
+ local function _fork_provider_curl(url)
+ local fdi, fdo = nixio.pipe()
+ if not fdi or not fdo then return nil end
+ local child = nixio.fork()
+ if child > 0 then
+ fdo:close()
+ return {pid = child, fdi = fdi, buf = ""}
+ elseif child == 0 then
+ nixio.dup(fdo, nixio.stdout)
+ fdi:close()
+ fdo:close()
+ local cmd = string.format(
+ 'OUT=$(curl -sLI -X GET -m 5 --retry 2 -w "http_code=%%{http_code}" -H "User-Agent: %s"%s "%s" 2>/dev/null); '
+ .. 'CODE=$(echo "$OUT" | grep -o "http_code=[0-9]*" | head -1 | cut -d= -f2); '
+ .. 'if [ -n "$OUT" ] && [ "$CODE" = "200" ]; then echo "$OUT"; '
+ .. 'else curl -sLI -X GET -m 5 --retry 2 -w "http_code=%%{http_code}" -H "User-Agent: Quantumultx"%s "%s" 2>/dev/null; fi',
+ sub_ua, header_args, url, header_args, url
+ )
+ nixio.exec("/bin/sh", "-c", cmd)
+ else
+ if fdi then fdi:close() end
+ if fdo then fdo:close() end
+ return nil
+ end
+ end
+
+ local providers = url_result.providers
+ local jobs = {}
+
+ for i, provider in ipairs(providers) do
+ local job = _fork_provider_curl(provider.url)
+ if job then
+ job.idx = i
+ job.name = provider.name
+ jobs[#jobs + 1] = job
+ end
+ end
+
+ if #jobs > 0 then
+ local done_count = 0
+ local max_wait = 120
+ for _ = 1, max_wait do
+ for _, job in ipairs(jobs) do
+ if not job.done then
+ local buf = job.fdi:read(4096)
+ if buf then job.buf = job.buf .. buf end
+ if nixio.waitpid(job.pid, "nohang") then
+ pcall(job.fdi.close, job.fdi)
+ job.done = true
+ done_count = done_count + 1
+ end
+ end
+ end
+ if done_count >= #jobs then break end
+ nixio.nanosleep(0, 10000000)
+ end
+ for _, job in ipairs(jobs) do
+ if not job.done then
+ pcall(job.fdi.close, job.fdi)
+ nixio.kill(job.pid, 9)
+ end
+ end
+ for _, job in ipairs(jobs) do
+ if job.done and job.buf ~= "" then
+ local info = fetch_sub_info(providers[job.idx].url, sub_ua, sub_headers, job.buf)
+ if info then
+ info.provider_name = job.name
+ providers_data[#providers_data + 1] = info
+ end
+ end
+ end
+ end
+ -- Fallback: if all forks failed, try serial
+ if #providers_data == 0 then
+ for i, provider in ipairs(providers) do
+ local info = fetch_sub_info(provider.url, sub_ua, sub_headers)
+ if info then
+ info.provider_name = provider.name
+ providers_data[#providers_data + 1] = info
+ end
end
end
end
@@ -813,39 +986,38 @@ function sub_info_get()
if #providers_data == 0 then
if not url_result then
- luci.http.status(500, "Subscription information not found")
+ HTTP.status(500, "Subscription information not found")
return
end
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
providers = providers_data,
get_time = os.time(),
url_result = url_result
})
end
-function action_rule_mode()
- local mode, info
+function action_rule_mode(internal)
+ local mode
if is_running() then
- local daip = daip()
- local dase = dase() or ""
- local cn_port = cn_port()
- if not daip or not cn_port then return end
- info = json.parse(luci.sys.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XGET http://"%s":"%s"/configs', dase, daip, cn_port)))
- if info then
- mode = info["mode"]
- else
- mode = fs.uci_get_config("config", "proxy_mode") or "rule"
+ local daip_val = daip()
+ local dase_val = dase() or ""
+ local cn_port_val = cn_port()
+ if daip_val and cn_port_val then
+ local info = json.parse(SYS.exec(string.format(
+ 'curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XGET http://"%s":"%s"/configs',
+ dase_val, daip_val, cn_port_val)))
+ if info and info["mode"] then
+ mode = info["mode"]
+ end
end
- else
- mode = fs.uci_get_config("config", "proxy_mode") or "rule"
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
- mode = mode;
- })
+ mode = mode or fs.uci_get_config("config", "proxy_mode") or "rule"
+ if internal then return { mode = mode } end
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({ mode = mode })
end
function action_switch_rule_mode()
@@ -853,21 +1025,21 @@ function action_switch_rule_mode()
local daip = daip()
local dase = dase() or ""
local cn_port = cn_port()
- mode = luci.http.formvalue("rule_mode")
+ mode = HTTP.formvalue("rule_mode")
if not mode then
- luci.http.status(500, "Missing parameters")
+ HTTP.status(500, "Missing parameters")
return
end
if is_running() then
- if not daip or not cn_port then luci.http.status(500, "Switch Faild") return end
- info = luci.sys.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XPATCH http://"%s":"%s"/configs -d \'{\"mode\": \"%s\"}\'', dase, daip, cn_port, mode))
+ if not daip or not cn_port then HTTP.status(500, "Switch Failed") return end
+ info = SYS.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XPATCH http://"%s":"%s"/configs -d \'{\"mode\": \"%s\"}\'', dase, daip, cn_port, mode))
if info ~= "" then
- luci.http.status(500, "Switch Faild")
+ HTTP.status(500, "Switch Failed")
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
info = info;
})
end
@@ -878,19 +1050,19 @@ end
function action_get_run_mode()
if mode() then
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
mode = mode();
})
else
- luci.http.status(500, "Get Faild")
+ HTTP.status(500, "Get Failed")
return
end
end
function action_switch_run_mode()
local mode, operation_mode
- mode = luci.http.formvalue("run_mode")
+ mode = HTTP.formvalue("run_mode")
operation_mode = fs.uci_get_config("config", "operation_mode")
if operation_mode == "redir-host" then
uci:set("openclash", "config", "en_mode", "redir-host"..mode)
@@ -901,7 +1073,7 @@ function action_switch_run_mode()
end
uci:commit("openclash")
if is_running() then
- luci.sys.exec("/etc/init.d/openclash restart >/dev/null 2>&1 &")
+ SYS.exec("/etc/init.d/openclash restart >/dev/null 2>&1 &")
end
end
@@ -912,7 +1084,7 @@ function action_log_level()
local dase = dase() or ""
local cn_port = cn_port()
if not daip or not cn_port then return end
- info = json.parse(luci.sys.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XGET http://"%s":"%s"/configs', dase, daip, cn_port)))
+ info = json.parse(SYS.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XGET http://"%s":"%s"/configs', dase, daip, cn_port)))
if info then
level = info["log-level"]
else
@@ -921,8 +1093,8 @@ function action_log_level()
else
level = fs.uci_get_config("config", "log_level") or "info"
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
log_level = level;
})
end
@@ -933,44 +1105,43 @@ function action_switch_log()
local daip = daip()
local dase = dase() or ""
local cn_port = cn_port()
- level = luci.http.formvalue("log_level")
- if not daip or not cn_port or not level then luci.http.status(500, "Switch Faild") return end
- info = luci.sys.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XPATCH http://"%s":"%s"/configs -d \'{\"log-level\": \"%s\"}\'', dase, daip, cn_port, level))
+ level = HTTP.formvalue("log_level")
+ if not daip or not cn_port or not level then HTTP.status(500, "Switch Failed") return end
+ info = SYS.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XPATCH http://"%s":"%s"/configs -d \'{\"log-level\": \"%s\"}\'', dase, daip, cn_port, level))
if info ~= "" then
- luci.http.status(500, "Switch Faild")
+ HTTP.status(500, "Switch Failed")
end
else
- luci.http.status(500, "Switch Faild")
+ HTTP.status(500, "Switch Failed")
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
info = info;
})
end
local function s(e)
-local t=0
local a={' B/S',' KB/S',' MB/S',' GB/S',' TB/S',' PB/S'}
+local t=0
if (e<=1024) then
return e..a[1]
else
repeat
e=e/1024
t=t+1
- until(e<=1024)
- return string.format("%.1f",e)..a[t]
+ until(e<=1024)
+ return math.floor(e * 10 + 0.5) / 10 .. a[t]
end
end
function action_toolbar_show_sys()
local cpu = "0"
local load_avg = "0"
- local cpu_count = luci.sys.exec("grep -c ^processor /proc/cpuinfo 2>/dev/null"):gsub("\n", "") or 1
-
- local pid = luci.sys.exec("pgrep -f '^[^ ]*clash' | head -1 | tr -d '\n' 2>/dev/null")
+ local cpu_count = SYS.exec("grep -c ^processor /proc/cpuinfo 2>/dev/null"):gsub("\n", "") or 1
+ local pid = SYS.exec("pgrep -f '^[^ ]*clash' | head -1 | tr -d '\n' 2>/dev/null")
if pid and pid ~= "" then
- cpu = luci.sys.exec(string.format([[
+ cpu = SYS.exec(string.format([[
top -b -n1 | awk -v pid="%s" '
BEGIN { cpu_col=0; }
$0 ~ /%%CPU/ {
@@ -986,22 +1157,22 @@ function action_toolbar_show_sys()
cpu = "0"
end
- load_avg = luci.sys.exec("awk '{print $2; exit}' /proc/loadavg 2>/dev/null"):gsub("\n", "") or "0"
+ load_avg = SYS.exec("awk '{print $2; exit}' /proc/loadavg 2>/dev/null"):gsub("\n", "") or "0"
if not string.match(load_avg, "^[0-9]*%.?[0-9]*$") then
load_avg = "0"
end
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
cpu = cpu,
load_avg = tostring(math.floor(tonumber(load_avg) / tonumber(cpu_count) * 100));
})
end
function action_toolbar_show()
- local pid = luci.sys.exec("pgrep -f '^[^ ]*clash' | head -1 | tr -d '\n' 2>/dev/null")
+ local pid = SYS.exec("pgrep -f '^[^ ]*clash' | head -1 | tr -d '\n' 2>/dev/null")
local traffic, connections, connection, up, down, up_total, down_total, mem, cpu, load_avg, cpu_count
if pid and pid ~= "" then
local daip = daip()
@@ -1009,8 +1180,72 @@ function action_toolbar_show()
local cn_port = cn_port()
if not daip or not cn_port then return end
- traffic = json.parse(luci.sys.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XGET http://"%s":"%s"/traffic', dase, daip, cn_port)))
- connections = json.parse(luci.sys.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XGET http://"%s":"%s"/connections', dase, daip, cn_port)))
+ -- Parallel curl helper
+ local function _fork_curl(endpoint)
+ local fdi, fdo = nixio.pipe()
+ if not fdi or not fdo then return nil end
+ local child = nixio.fork()
+ if child > 0 then
+ fdo:close()
+ return {pid = child, fdi = fdi, buf = ""}
+ elseif child == 0 then
+ nixio.dup(fdo, nixio.stdout)
+ fdi:close()
+ fdo:close()
+ local cmd = string.format(
+ 'curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XGET "http://%s:%s/%s"',
+ dase, daip, cn_port, endpoint
+ )
+ nixio.exec("/bin/sh", "-c", cmd)
+ else
+ if fdi then fdi:close() end
+ if fdo then fdo:close() end
+ return nil
+ end
+ end
+
+ local t_job = _fork_curl("traffic")
+ local c_job = _fork_curl("connections")
+
+ if t_job and c_job then
+ local t_done, c_done = false, false
+ local max_wait = 60
+ for _ = 1, max_wait do
+ if not t_done then
+ local buf = t_job.fdi:read(4096)
+ if buf then t_job.buf = t_job.buf .. buf end
+ if nixio.waitpid(t_job.pid, "nohang") then
+ pcall(t_job.fdi.close, t_job.fdi)
+ t_done = true
+ end
+ end
+ if not c_done then
+ local buf = c_job.fdi:read(4096)
+ if buf then c_job.buf = c_job.buf .. buf end
+ if nixio.waitpid(c_job.pid, "nohang") then
+ pcall(c_job.fdi.close, c_job.fdi)
+ c_done = true
+ end
+ end
+ if t_done and c_done then break end
+ nixio.nanosleep(0, 10000000)
+ end
+ if not t_done then
+ pcall(t_job.fdi.close, t_job.fdi)
+ nixio.kill(t_job.pid, 9)
+ end
+ if not c_done then
+ pcall(c_job.fdi.close, c_job.fdi)
+ nixio.kill(c_job.pid, 9)
+ end
+ traffic = t_done and t_job.buf ~= "" and json.parse(t_job.buf) or nil
+ connections = c_done and c_job.buf ~= "" and json.parse(c_job.buf) or nil
+ else
+ if t_job then pcall(t_job.fdi.close, t_job.fdi) end
+ if c_job then pcall(c_job.fdi.close, c_job.fdi) end
+ traffic = json.parse(SYS.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XGET http://"%s":"%s"/traffic', dase, daip, cn_port)))
+ connections = json.parse(SYS.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XGET http://"%s":"%s"/connections', dase, daip, cn_port)))
+ end
if traffic and connections and connections.connections then
connection = #(connections.connections)
@@ -1026,8 +1261,8 @@ function action_toolbar_show()
connection = "0"
end
- mem = tonumber(luci.sys.exec(string.format("cat /proc/%s/status 2>/dev/null |grep -w VmRSS |awk '{print $2}'", pid)))
- cpu = luci.sys.exec(string.format([[
+ mem = tonumber(SYS.exec(string.format("cat /proc/%s/status 2>/dev/null |grep -w VmRSS |awk '{print $2}'", pid)))
+ cpu = SYS.exec(string.format([[
top -b -n1 | awk -v pid="%s" '
BEGIN { cpu_col=0; }
$0 ~ /%%CPU/ {
@@ -1046,8 +1281,8 @@ function action_toolbar_show()
cpu = "0"
end
- load_avg = luci.sys.exec("awk '{print $2; exit}' /proc/loadavg 2>/dev/null"):gsub("\n", "") or "0"
- cpu_count = luci.sys.exec("grep -c ^processor /proc/cpuinfo 2>/dev/null"):gsub("\n", "") or 1
+ load_avg = SYS.exec("awk '{print $2; exit}' /proc/loadavg 2>/dev/null"):gsub("\n", "") or "0"
+ cpu_count = SYS.exec("grep -c ^processor /proc/cpuinfo 2>/dev/null"):gsub("\n", "") or 1
if not string.match(load_avg, "^[0-9]*%.?[0-9]*$") then
load_avg = "0"
@@ -1056,8 +1291,8 @@ function action_toolbar_show()
return
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
connections = connection,
up = up,
down = down,
@@ -1070,23 +1305,23 @@ function action_toolbar_show()
end
function action_config_name()
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
config_name = config_name(),
config_path = config_path();
})
end
function action_save_corever_branch()
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
save_corever_branch = save_corever_branch();
})
end
function action_one_key_update_check()
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
corever = corever();
})
end
@@ -1098,8 +1333,8 @@ function action_dashboard_type()
if not fs.isdirectory("/usr/share/openclash/ui/" .. default_dashboard) then
default_dashboard = ""
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
dashboard_type = dashboard_type,
yacd_type = yacd_type,
yacd = fs.isdirectory("/usr/share/openclash/ui/yacd"),
@@ -1111,27 +1346,27 @@ function action_dashboard_type()
end
function action_default_dashboard()
- local default_dashboard = luci.http.formvalue("name")
+ local default_dashboard = HTTP.formvalue("name")
if not default_dashboard or (default_dashboard ~= "Dashboard" and default_dashboard ~= "Yacd" and default_dashboard ~= "Metacubexd" and default_dashboard ~= "Zashboard") then
- luci.http.status(500, "Set Failed")
+ HTTP.status(500, "Set Failed")
return
end
if not fs.isdirectory("/usr/share/openclash/ui/" .. string.lower(default_dashboard)) then
- luci.http.status(500, "Set Failed")
+ HTTP.status(500, "Set Failed")
return
end
uci:set("openclash", "config", "default_dashboard", string.lower(default_dashboard))
uci:commit("openclash")
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
default_dashboard = default_dashboard;
})
end
function action_switch_dashboard()
- local switch_name = luci.http.formvalue("name")
- local switch_type = luci.http.formvalue("type")
- local state = luci.sys.call(string.format('/usr/share/openclash/openclash_download_dashboard.sh "%s" "%s" >/dev/null 2>&1', switch_name, switch_type))
+ local switch_name = HTTP.formvalue("name")
+ local switch_type = HTTP.formvalue("type")
+ local state = SYS.call(string.format('/usr/share/openclash/openclash_download_dashboard.sh "%s" "%s" >/dev/null 2>&1', switch_name, switch_type))
if switch_name == "Dashboard" and tonumber(state) == 0 then
if switch_type == "Official" then
uci:set("openclash", "config", "dashboard_type", "Official")
@@ -1149,14 +1384,14 @@ function action_switch_dashboard()
uci:commit("openclash")
end
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
download_state = state;
})
end
function action_delete_dashboard()
- local delete_name = luci.http.formvalue("name")
+ local delete_name = HTTP.formvalue("name")
local delete_path = string.format("/usr/share/openclash/ui/%s", string.lower(delete_name))
local panels = {
@@ -1173,15 +1408,15 @@ function action_delete_dashboard()
end
if existing_panels <= 1 then
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
delete_state = 0,
error = "Cannot delete the last remaining dashboard"
})
return
end
- local state = luci.sys.call(string.format("rm -rf '%s' >/dev/null 2>&1", delete_path)) == 0 and 1 or 0
+ local state = SYS.call(string.format("rm -rf '%s' >/dev/null 2>&1", delete_path)) == 0 and 1 or 0
if tonumber(state) == 1 then
if delete_name == "Dashboard" then
uci:set("openclash", "config", "dashboard_type", "Official")
@@ -1195,16 +1430,16 @@ function action_delete_dashboard()
uci:commit("openclash")
end
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
delete_state = state;
})
end
function action_op_mode()
local op_mode = fs.uci_get_config("config", "operation_mode")
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
op_mode = op_mode;
})
end
@@ -1218,40 +1453,75 @@ function action_switch_mode()
uci:set("openclash", "config", "operation_mode", "redir-host")
uci:commit("openclash")
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
switch_mode = switch_mode;
})
end
-function action_status()
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+function action_conn_status(internal)
+ local data = {
clash = uci:get("openclash", "config", "enable") == "1",
daip = daip(),
dase = dase(),
db_foward_port = db_foward_port(),
db_foward_domain = db_foward_domain(),
+ cn_port = cn_port()
+ }
+ if internal then return data end
+ HTTP.prepare_content("application/json")
+ HTTP.write_json(data)
+end
+
+function action_status()
+ local status_data = action_conn_status(true)
+ local rule_data = action_rule_mode(true)
+ local oc_data = action_oc_settings(true)
+ local proxy_data = action_proxy_info(true)
+
+ local result = {
+ -- status fields
+ clash = status_data.clash,
+ daip = status_data.daip,
+ dase = status_data.dase,
+ db_foward_port = status_data.db_foward_port,
+ db_foward_domain = status_data.db_foward_domain,
db_forward_ssl = db_foward_ssl(),
- cn_port = cn_port(),
+ cn_port = status_data.cn_port,
yacd = fs.isdirectory("/usr/share/openclash/ui/yacd"),
dashboard = fs.isdirectory("/usr/share/openclash/ui/dashboard"),
metacubexd = fs.isdirectory("/usr/share/openclash/ui/metacubexd"),
zashboard = fs.isdirectory("/usr/share/openclash/ui/zashboard"),
- core_type = fs.uci_get_config("config", "core_type") or "Meta";
- })
+ core_type = fs.uci_get_config("config", "core_type") or "Meta",
+ -- run_mode
+ run_mode = mode(),
+ -- rule_mode
+ rule_mode = rule_data.mode,
+ -- oc_settings
+ meta_sniffer = oc_data.meta_sniffer,
+ respect_rules = oc_data.respect_rules,
+ oversea = oc_data.oversea,
+ stream_unlock = oc_data.stream_unlock,
+ -- proxy_info
+ mixed_port = proxy_data.mixed_port,
+ auth_user = proxy_data.auth_user,
+ auth_pass = proxy_data.auth_pass,
+ }
+
+ HTTP.prepare_content("application/json")
+ HTTP.write_json(result)
end
function action_lastversion()
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
lastversion = check_lastversion();
})
end
function action_start()
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
startlog = startlog();
})
end
@@ -1259,19 +1529,19 @@ end
function action_get_last_version()
if not process_status("/usr/share/openclash/clash_version.sh") then
if tonumber(os.time() - (fs.mtime("/tmp/clash_last_version") or 0)) > 1800 then
- luci.sys.call("bash /usr/share/openclash/clash_version.sh &")
+ SYS.call("bash /usr/share/openclash/clash_version.sh &")
end
end
if not process_status("/usr/share/openclash/openclash_version.sh") then
if tonumber(os.time() - (fs.mtime("/tmp/openclash_last_version") or 0)) > 1800 then
- luci.sys.call("bash /usr/share/openclash/openclash_version.sh &")
+ SYS.call("bash /usr/share/openclash/openclash_version.sh &")
end
end
end
function action_update()
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
coremodel = coremodel(),
coremetacv = coremetacv(),
corelv = corelv(),
@@ -1282,8 +1552,8 @@ function action_update()
end
function action_update_info()
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
corever = corever(),
release_branch = release_branch(),
smart_enable = smart_enable();
@@ -1291,59 +1561,60 @@ function action_update_info()
end
function action_update_ma()
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
oplv = oplv(),
pkg_type = fs.pkg_type(),
+ oix_core = fs.uci_get_config("config", "oix_token") ~= "",
corelv = corelv(),
corever = corever();
})
end
function action_opupdate()
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
opup = opup();
})
end
function action_check_core()
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
core_status = check_core();
})
end
function action_coreupdate()
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
coreup = coreup();
})
end
function action_close_all_connection()
- return luci.sys.call("sh /usr/share/openclash/openclash_history_get.sh 'close_all_conection'")
+ return SYS.call("sh /usr/share/openclash/openclash_history_get.sh 'close_all_conection'")
end
function action_reload_firewall()
- return luci.sys.call("/etc/init.d/openclash reload 'manual' >/dev/null 2>&1 &")
+ return SYS.call("/etc/init.d/openclash reload 'manual' >/dev/null 2>&1 &")
end
function action_download_rule()
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
rule_download_status = download_rule();
})
end
function action_refresh_log()
- luci.http.prepare_content("application/json")
+ HTTP.prepare_content("application/json")
local logfile = "/tmp/openclash.log"
- local log_len = tonumber(luci.http.formvalue("log_len")) or 0
- local core_refresh = luci.http.formvalue("core_refresh") == "true"
+ local log_len = tonumber(HTTP.formvalue("log_len")) or 0
+ local core_refresh = HTTP.formvalue("core_refresh") == "true"
if not fs.access(logfile) then
- luci.http.write_json({
+ HTTP.write_json({
len = 0,
update = false,
core_log = "",
@@ -1352,10 +1623,10 @@ function action_refresh_log()
return
end
- local total_lines = tonumber(luci.sys.exec("wc -l < " .. logfile)) or 0
+ local total_lines = tonumber(SYS.exec("wc -l < " .. logfile)) or 0
if total_lines == log_len and log_len > 0 then
- luci.http.write_json({
+ HTTP.write_json({
len = total_lines,
update = false,
core_log = "",
@@ -1374,24 +1645,24 @@ function action_refresh_log()
local oc_logs = {}
core_cmd = string.format(
- "tail -n +%d '%s' | head -n %d | grep -v -E '%s' | grep -E '%s' | tail -n %d",
- start_line, logfile, read_count, exclude_pattern, core_pattern, limit
+ "sed -n '%d,%dp' '%s' | grep -v -E '%s' | grep -E '%s' | tail -n %d",
+ start_line, start_line + read_count - 1, logfile, exclude_pattern, core_pattern, limit
)
oc_cmd = string.format(
- "tail -n +%d '%s' | head -n %d | grep -v -E '%s' | grep -v -E '%s' | tail -n %d",
- start_line, logfile, read_count, exclude_pattern, core_pattern, limit
+ "sed -n '%d,%dp' '%s' | grep -v -E '%s' | grep -v -E '%s' | tail -n %d",
+ start_line, start_line + read_count - 1, logfile, exclude_pattern, core_pattern, limit
)
if core_refresh then
- core_raw = luci.sys.exec(core_cmd)
+ core_raw = SYS.exec(core_cmd)
end
- oc_raw = luci.sys.exec(oc_cmd)
+ oc_raw = SYS.exec(oc_cmd)
if core_raw and core_raw ~= "" then
for line in core_raw:gmatch("[^\n]+") do
- table.insert(core_logs, line)
+ core_logs[#core_logs + 1] = line
end
end
@@ -1400,7 +1671,7 @@ function action_refresh_log()
if not string.match(string.sub(line, 1, 19), "%d%d%d%d%-%d%d%-%d%d %d%d:%d%d:%d%d") then
line = os.date("%Y-%m-%d %H:%M:%S") .. ' [Fatal] ' .. line
end
- table.insert(oc_logs, trans_line(line))
+ oc_logs[#oc_logs + 1] = trans_line(line)
end
end
@@ -1414,7 +1685,7 @@ function action_refresh_log()
local core_log = #core_logs > 0 and table.concat(core_logs, "\n") or ""
local oc_log = #oc_logs > 0 and table.concat(oc_logs, "\n") or ""
- luci.http.write_json({
+ HTTP.write_json({
len = total_lines,
update = true,
core_log = core_log,
@@ -1423,17 +1694,17 @@ function action_refresh_log()
end
function action_del_log()
- local log_type = luci.http.formvalue("type")
+ local log_type = HTTP.formvalue("type")
if log_type == "debug" then
- luci.sys.exec(": > /tmp/openclash_debug.log")
+ SYS.exec(": > /tmp/openclash_debug.log")
else
- luci.sys.exec(": > /tmp/openclash.log")
+ SYS.exec(": > /tmp/openclash.log")
end
return
end
function action_del_start_log()
- luci.sys.exec("echo '##FINISH##' > /tmp/openclash_start.log")
+ SYS.exec("echo '##FINISH##' > /tmp/openclash_start.log")
return
end
@@ -1464,53 +1735,53 @@ function split(str,delimiter)
end
function action_diag_connection()
- local addr = luci.http.formvalue("addr")
+ local addr = HTTP.formvalue("addr")
if addr and (datatype.hostname(addr) or datatype.ipaddr(addr)) then
local cmd = string.format("/usr/share/openclash/openclash_debug_getcon.lua %s", addr)
- luci.http.prepare_content("text/plain")
+ HTTP.prepare_content("text/plain")
local util = io.popen(cmd)
if util and util ~= "" then
while true do
local ln = util:read("*l")
if not ln then break end
- luci.http.write(ln)
- luci.http.write("\n")
+ HTTP.write(ln)
+ HTTP.write("\n")
end
util:close()
end
return
end
- luci.http.status(500, "Bad address")
+ HTTP.status(500, "Bad address")
end
function action_diag_dns()
- local addr = luci.http.formvalue("addr")
+ local addr = HTTP.formvalue("addr")
if addr and datatype.hostname(addr)then
local cmd = string.format("/usr/share/openclash/openclash_debug_dns.lua %s", addr)
- luci.http.prepare_content("text/plain")
+ HTTP.prepare_content("text/plain")
local util = io.popen(cmd)
if util and util ~= "" then
while true do
local ln = util:read("*l")
if not ln then break end
- luci.http.write(ln)
- luci.http.write("\n")
+ HTTP.write(ln)
+ HTTP.write("\n")
end
util:close()
end
return
end
- luci.http.status(500, "Bad address")
+ HTTP.status(500, "Bad address")
end
function action_gen_debug_logs()
- local gen_log = luci.sys.call("/usr/share/openclash/openclash_debug.sh")
+ local gen_log = SYS.call("/usr/share/openclash/openclash_debug.sh")
if not gen_log then return end
local logfile = "/tmp/openclash_debug.log"
if not fs.access(logfile) then
return
end
- luci.http.prepare_content("text/plain; charset=utf-8")
+ HTTP.prepare_content("text/plain; charset=utf-8")
local file=io.open(logfile, "r+")
file:seek("set")
local info = ""
@@ -1522,7 +1793,7 @@ function action_gen_debug_logs()
end
end
file:close()
- luci.http.write(info)
+ HTTP.write(info)
end
function action_get_debug_logs()
@@ -1530,84 +1801,84 @@ function action_get_debug_logs()
if not fs.access(logfile) then
return
end
- luci.http.prepare_content("text/plain; charset=utf-8")
- luci.http.write(fs.readfile(logfile) or "")
+ HTTP.prepare_content("text/plain; charset=utf-8")
+ HTTP.write(fs.readfile(logfile) or "")
end
function action_backup()
- local config = luci.sys.call("cp /etc/config/openclash /etc/openclash/openclash >/dev/null 2>&1")
+ local config = SYS.call("cp /etc/config/openclash /etc/openclash/openclash >/dev/null 2>&1")
local reader = ltn12_popen("tar -C '/etc/openclash/' -cz . 2>/dev/null")
- luci.http.header(
+ HTTP.header(
'Content-Disposition', 'attachment; filename="Backup-OpenClash-%s-%s-%s.tar.gz"' %{
device_name, device_arh, os.date("%Y-%m-%d-%H-%M-%S")
})
- luci.http.prepare_content("application/x-targz")
- luci.ltn12.pump.all(reader, luci.http.write)
- luci.sys.call("rm -rf /etc/openclash/openclash >/dev/null 2>&1")
+ HTTP.prepare_content("application/x-targz")
+ luci.ltn12.pump.all(reader, HTTP.write)
+ SYS.call("rm -rf /etc/openclash/openclash >/dev/null 2>&1")
end
function action_backup_ex_core()
- local config = luci.sys.call("cp /etc/config/openclash /etc/openclash/openclash >/dev/null 2>&1")
+ local config = SYS.call("cp /etc/config/openclash /etc/openclash/openclash >/dev/null 2>&1")
local reader = ltn12_popen("echo 'core' > /tmp/oc_exclude.txt && tar -C '/etc/openclash/' -X '/tmp/oc_exclude.txt' -cz . 2>/dev/null")
- luci.http.header(
+ HTTP.header(
'Content-Disposition', 'attachment; filename="Backup-OpenClash-Exclude-Cores-%s-%s-%s.tar.gz"' %{
device_name, device_arh, os.date("%Y-%m-%d-%H-%M-%S")
})
- luci.http.prepare_content("application/x-targz")
- luci.ltn12.pump.all(reader, luci.http.write)
- luci.sys.call("rm -rf /etc/openclash/openclash >/dev/null 2>&1")
+ HTTP.prepare_content("application/x-targz")
+ luci.ltn12.pump.all(reader, HTTP.write)
+ SYS.call("rm -rf /etc/openclash/openclash >/dev/null 2>&1")
end
function action_backup_only_config()
local reader = ltn12_popen("tar -C '/etc/openclash' -cz './config' 2>/dev/null")
- luci.http.header(
+ HTTP.header(
'Content-Disposition', 'attachment; filename="Backup-OpenClash-Config-%s-%s-%s.tar.gz"' %{
device_name, device_arh, os.date("%Y-%m-%d-%H-%M-%S")
})
- luci.http.prepare_content("application/x-targz")
- luci.ltn12.pump.all(reader, luci.http.write)
+ HTTP.prepare_content("application/x-targz")
+ luci.ltn12.pump.all(reader, HTTP.write)
end
function action_backup_only_core()
local reader = ltn12_popen("tar -C '/etc/openclash' -cz './core' 2>/dev/null")
- luci.http.header(
+ HTTP.header(
'Content-Disposition', 'attachment; filename="Backup-OpenClash-Cores-%s-%s-%s.tar.gz"' %{
device_name, device_arh, os.date("%Y-%m-%d-%H-%M-%S")
})
- luci.http.prepare_content("application/x-targz")
- luci.ltn12.pump.all(reader, luci.http.write)
+ HTTP.prepare_content("application/x-targz")
+ luci.ltn12.pump.all(reader, HTTP.write)
end
function action_backup_only_rule()
local reader = ltn12_popen("tar -C '/etc/openclash' -cz './rule_provider' 2>/dev/null")
- luci.http.header(
+ HTTP.header(
'Content-Disposition', 'attachment; filename="Backup-OpenClash-Only-Rule-Provider-%s-%s-%s.tar.gz"' %{
device_name, device_arh, os.date("%Y-%m-%d-%H-%M-%S")
})
- luci.http.prepare_content("application/x-targz")
- luci.ltn12.pump.all(reader, luci.http.write)
+ HTTP.prepare_content("application/x-targz")
+ luci.ltn12.pump.all(reader, HTTP.write)
end
function action_backup_only_proxy()
local reader = ltn12_popen("tar -C '/etc/openclash' -cz './proxy_provider' 2>/dev/null")
- luci.http.header(
+ HTTP.header(
'Content-Disposition', 'attachment; filename="Backup-OpenClash-Proxy-Provider-%s-%s-%s.tar.gz"' %{
device_name, device_arh, os.date("%Y-%m-%d-%H-%M-%S")
})
- luci.http.prepare_content("application/x-targz")
- luci.ltn12.pump.all(reader, luci.http.write)
+ HTTP.prepare_content("application/x-targz")
+ luci.ltn12.pump.all(reader, HTTP.write)
end
function ltn12_popen(command)
@@ -1641,19 +1912,19 @@ function ltn12_popen(command)
end
function create_file()
- local file_name = luci.http.formvalue("filename")
- local file_path = luci.http.formvalue("filepath")..file_name
+ local file_name = HTTP.formvalue("filename")
+ local file_path = HTTP.formvalue("filepath")..file_name
fs.writefile(file_path, "")
if not fs.isfile(file_path) then
- luci.http.status(500, "Create File Faild")
+ HTTP.status(500, "Create File Failed")
end
return
end
function rename_file()
- local new_file_name = luci.http.formvalue("new_file_name")
- local file_path = luci.http.formvalue("file_path")
- local old_file_name = luci.http.formvalue("file_name")
+ local new_file_name = HTTP.formvalue("new_file_name")
+ local file_path = HTTP.formvalue("file_path")
+ local old_file_name = HTTP.formvalue("file_name")
local old_file_path = file_path .. old_file_name
local new_file_path = file_path .. new_file_name
local old_run_file_path = "/etc/openclash/" .. old_file_name
@@ -1670,24 +1941,24 @@ function rename_file()
fs.config_refs(old_file_name, new_file_name)
end
- luci.http.status(200, "Rename File Successful")
+ HTTP.status(200, "Rename File Successful")
else
- luci.http.status(500, "Rename File Faild")
+ HTTP.status(500, "Rename File Failed")
end
return
end
function manual_stream_unlock_test()
- local type = luci.http.formvalue("type")
+ local type = HTTP.formvalue("type")
local cmd = string.format('/usr/share/openclash/openclash_streaming_unlock.lua "%s"', type)
- luci.http.prepare_content("text/plain; charset=utf-8")
+ HTTP.prepare_content("text/plain; charset=utf-8")
local util = io.popen(cmd)
if util and util ~= "" then
while true do
local ln = util:read("*l")
if ln then
- luci.http.write(trans_line(ln))
- luci.http.write("\n")
+ HTTP.write(trans_line(ln))
+ HTTP.write("\n")
end
if not process_status("openclash_streaming_unlock.lua "..type) or not process_status("openclash_streaming_unlock.lua ") then
break
@@ -1696,20 +1967,20 @@ function manual_stream_unlock_test()
util:close()
return
end
- luci.http.status(500, "Something Wrong While Testing...")
+ HTTP.status(500, "Something Wrong While Testing...")
end
function all_proxies_stream_test()
- local type = luci.http.formvalue("type")
+ local type = HTTP.formvalue("type")
local cmd = string.format('/usr/share/openclash/openclash_streaming_unlock.lua "%s" "%s"', type, "all")
- luci.http.prepare_content("text/plain; charset=utf-8")
+ HTTP.prepare_content("text/plain; charset=utf-8")
local util = io.popen(cmd)
if util and util ~= "" then
while true do
local ln = util:read("*l")
if ln then
- luci.http.write(trans_line(ln))
- luci.http.write("\n")
+ HTTP.write(trans_line(ln))
+ HTTP.write("\n")
end
if not process_status("openclash_streaming_unlock.lua "..type) or not process_status("openclash_streaming_unlock.lua ") then
break
@@ -1718,7 +1989,7 @@ function all_proxies_stream_test()
util:close()
return
end
- luci.http.status(500, "Something Wrong While Testing...")
+ HTTP.status(500, "Something Wrong While Testing...")
end
function trans_line(data)
@@ -1748,35 +2019,35 @@ function trans_line(data)
while pos do
if pos > last_pos then
- table.insert(segments, {
+ segments[#segments + 1] = {
type = "trans",
text = string.sub(data, last_pos, pos - 1)
- })
+ }
end
local close_pos = string.find(data, "】", pos + 1)
if not close_pos then
- table.insert(segments, {
+ segments[#segments + 1] = {
type = "trans",
text = string.sub(data, pos, -1)
- })
+ }
break
end
- table.insert(segments, {
+ segments[#segments + 1] = {
type = "no_trans",
text = string.sub(data, pos, close_pos + 2)
- })
+ }
last_pos = close_pos + 3
pos = string.find(data, "【", last_pos)
end
if last_pos <= string.len(data) then
- table.insert(segments, {
+ segments[#segments + 1] = {
type = "trans",
text = string.sub(data, last_pos, -1)
- })
+ }
end
line_trans = time_part .. level_part
@@ -1793,20 +2064,20 @@ end
function process_status(name)
local cmd = string.format("%s |grep '%s' |grep -v grep", fs.ps_cmd(), name)
- local result = luci.sys.exec(cmd)
+ local result = SYS.exec(cmd)
return result ~= nil and result ~= "" and not result:match("^%s*$")
end
function action_announcement()
if not fs.access("/tmp/openclash_announcement") or fs.readfile("/tmp/openclash_announcement") == "" or fs.mtime("/tmp/openclash_announcement") < (os.time() - 86400) then
- local HTTP_CODE = luci.sys.exec("curl -SsL -m 5 -w '%{http_code}' -o /tmp/openclash_announcement https://raw.githubusercontent.com/vernesong/OpenClash/dev/announcement 2>/dev/null")
+ local HTTP_CODE = SYS.exec("curl -SsL -m 5 -w '%{http_code}' -o /tmp/openclash_announcement https://raw.githubusercontent.com/vernesong/OpenClash/dev/announcement 2>/dev/null")
if HTTP_CODE ~= "200" then
fs.unlink("/tmp/openclash_announcement")
end
end
- local info = luci.sys.exec("cat /tmp/openclash_announcement 2>/dev/null") or ""
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ local info = SYS.exec("cat /tmp/openclash_announcement 2>/dev/null") or ""
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
content = info;
})
end
@@ -1972,8 +2243,8 @@ function action_myip_check()
end
if next(queries) == nil then
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
error = "Failed to create any queries"
})
return
@@ -1981,6 +2252,7 @@ function action_myip_check()
local max_iterations = 140
local iteration = 0
+ local delay = 10000000 -- 10ms initial
local completed = {}
while iteration < max_iterations do
@@ -2006,7 +2278,7 @@ function action_myip_check()
queries[name] = nil
else
- local still_running = luci.sys.call(string.format("kill -0 %d 2>/dev/null", info.query.pid)) == 0
+ local still_running = SYS.call(string.format("kill -0 %d 2>/dev/null", info.query.pid)) == 0
if not still_running then
pcall(info.query.close)
completed[name] = true
@@ -2031,7 +2303,8 @@ function action_myip_check()
break
end
- nixio.nanosleep(0, 50000000)
+ nixio.nanosleep(0, delay)
+ delay = math.min(delay * 2, 100000000) -- grow to 100ms max
end
for name, info in pairs(queries) do
@@ -2048,7 +2321,7 @@ function action_myip_check()
'curl -sL -m 5 --retry 2 -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" "https://api-ipv4.ip.sb/geoip/%s" 2>/dev/null',
result.ipify.ip
)
- local geo_data = luci.sys.exec(geo_cmd)
+ local geo_data = SYS.exec(geo_cmd)
if geo_data and geo_data ~= "" then
local ok_geo, geo_json = pcall(json.parse, geo_data)
@@ -2065,8 +2338,8 @@ function action_myip_check()
end
end
- luci.http.prepare_content("application/json")
- luci.http.write_json(result)
+ HTTP.prepare_content("application/json")
+ HTTP.write_json(result)
end
function latency_test(addr)
@@ -2098,7 +2371,7 @@ function latency_test(addr)
test_url
)
- local output = luci.sys.exec(cmd)
+ local output = SYS.exec(cmd)
if output and output ~= "" then
local http_code, time_total, time_connect, time_appconnect =
output:match("(%d+),([%d%.]+),([%d%.]+),([%d%.]+)")
@@ -2152,11 +2425,11 @@ function latency_test(addr)
end
function action_website_check()
- local domain = luci.http.formvalue("domain")
+ local domain = HTTP.formvalue("domain")
if not domain then
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
success = false,
response_time = 0,
error = "Missing domain parameter"
@@ -2166,11 +2439,11 @@ function action_website_check()
local result = latency_test(domain)
- luci.http.prepare_content("application/json")
- luci.http.write_json(result)
+ HTTP.prepare_content("application/json")
+ HTTP.write_json(result)
end
-function action_proxy_info()
+function action_proxy_info(internal)
local result = {
mixed_port = "",
auth_user = "",
@@ -2197,11 +2470,12 @@ function action_proxy_info()
end
end)
- luci.http.prepare_content("application/json")
- luci.http.write_json(result)
+ if internal then return result end
+ HTTP.prepare_content("application/json")
+ HTTP.write_json(result)
end
-function action_oc_settings()
+function action_oc_settings(internal)
local result = {
meta_sniffer = "0",
respect_rules = "0",
@@ -2233,16 +2507,17 @@ function action_oc_settings()
result.stream_unlock = "1"
end
- luci.http.prepare_content("application/json")
- luci.http.write_json(result)
+ if internal then return result end
+ HTTP.prepare_content("application/json")
+ HTTP.write_json(result)
end
function action_switch_oc_setting()
- local setting = luci.http.formvalue("setting")
- local value = luci.http.formvalue("value")
+ local setting = HTTP.formvalue("setting")
+ local value = HTTP.formvalue("value")
if not setting or not value then
- luci.http.status(500, "Missing parameters")
+ HTTP.status(500, "Missing parameters")
return
end
@@ -2258,13 +2533,13 @@ function action_switch_oc_setting()
local function update_runtime_config(ruby_cmd)
local runtime_config_path = get_runtime_config_path()
if not runtime_config_path then
- luci.http.status(500, "No config path found")
+ HTTP.status(500, "No config path found")
return false
end
- local ruby_result = luci.sys.call(ruby_cmd)
+ local ruby_result = SYS.call(ruby_cmd)
if ruby_result ~= 0 then
- luci.http.status(500, "Failed to modify config file")
+ HTTP.status(500, "Failed to modify config file")
return false
end
@@ -2272,14 +2547,14 @@ function action_switch_oc_setting()
local dase = dase() or ""
local cn_port = cn_port()
if not daip or not cn_port then
- luci.http.status(500, "Switch Failed")
+ HTTP.status(500, "Switch Failed")
return false
end
- local reload_result = luci.sys.exec(string.format('curl -sL -m 5 --connect-timeout 2 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XPUT http://"%s":"%s"/configs?force=true -d \'{"path":"%s"}\' 2>&1', dase, daip, cn_port, runtime_config_path))
+ local reload_result = SYS.exec(string.format('curl -sL -m 5 --connect-timeout 2 --retry 2 -H "Content-Type: application/json" -H "Authorization: Bearer %s" -XPUT http://"%s":"%s"/configs?force=true -d \'{"path":"%s"}\' 2>&1', dase, daip, cn_port, runtime_config_path))
if reload_result ~= "" then
- luci.http.status(500, "Switch Failed")
+ HTTP.status(500, "Switch Failed")
return false
end
@@ -2452,7 +2727,7 @@ function action_switch_oc_setting()
end
uci:set("openclash", "@overwrite[0]", "china_ip_route", value)
uci:commit("openclash")
- luci.sys.exec("/etc/init.d/openclash restart >/dev/null 2>&1 &")
+ SYS.exec("/etc/init.d/openclash restart >/dev/null 2>&1 &")
end
elseif setting == "stream_unlock" then
uci:set("openclash", "config", "stream_auto_select", value)
@@ -2482,12 +2757,12 @@ function action_switch_oc_setting()
end
uci:commit("openclash")
else
- luci.http.status(500, "Invalid setting")
+ HTTP.status(500, "Invalid setting")
return
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
status = "success",
setting = setting,
value = value
@@ -2517,14 +2792,14 @@ function action_generate_pac()
if not proxy_ip then
result.error = luci.i18n.translate("Unable to get proxy IP")
- luci.http.prepare_content("application/json")
- luci.http.write_json(result)
+ HTTP.prepare_content("application/json")
+ HTTP.write_json(result)
return
end
local function generate_random_string()
local random_cmd = "tr -cd 'a-zA-Z0-9' /dev/null| head -c16 || date +%N| md5sum |head -c16"
- local random_string = luci.sys.exec(random_cmd):gsub("\n", "")
+ local random_string = SYS.exec(random_cmd):gsub("\n", "")
return random_string
end
@@ -2552,10 +2827,10 @@ function action_generate_pac()
local random_suffix = nil
local need_update = true
- luci.sys.call("mkdir -p " .. pac_dir)
+ SYS.call("mkdir -p " .. pac_dir)
local find_cmd = "find " .. pac_dir .. " -name 'pac_*' -type f 2>/dev/null"
- local existing_files = luci.sys.exec(find_cmd)
+ local existing_files = SYS.exec(find_cmd)
if existing_files and existing_files ~= "" then
for file_path in existing_files:gmatch("[^\n]+") do
if fs.access(file_path) then
@@ -2579,7 +2854,7 @@ function action_generate_pac()
if file then
file:write(new_pac_content)
file:close()
- luci.sys.call("chmod 644 " .. file_path)
+ SYS.call("chmod 644 " .. file_path)
pac_filename = file_path:match("([^/]+)$")
pac_file_path = file_path
@@ -2607,7 +2882,7 @@ function action_generate_pac()
if file then
file:write(final_content)
file:close()
- luci.sys.call("chmod 644 " .. file_path)
+ SYS.call("chmod 644 " .. file_path)
pac_filename = file_path:match("([^/]+)$")
pac_file_path = file_path
@@ -2623,7 +2898,7 @@ function action_generate_pac()
end
if need_update then
- luci.sys.call("rm -f " .. pac_dir .. "pac_* 2>/dev/null")
+ SYS.call("rm -f " .. pac_dir .. "pac_* 2>/dev/null")
random_suffix = generate_random_string()
pac_filename = "pac_" .. random_suffix
@@ -2634,15 +2909,15 @@ function action_generate_pac()
file:write(new_pac_content)
file:close()
- luci.sys.call("chmod 644 " .. pac_file_path)
+ SYS.call("chmod 644 " .. pac_file_path)
else
result.error = luci.i18n.translate("Failed to write PAC file")
- luci.http.prepare_content("application/json")
- luci.http.write_json(result)
+ HTTP.prepare_content("application/json")
+ HTTP.write_json(result)
return
end
else
- luci.sys.call(string.format("find %s -name 'pac_*' -type f ! -name '%s' -delete 2>/dev/null", pac_dir, pac_filename))
+ SYS.call(string.format("find %s -name 'pac_*' -type f ! -name '%s' -delete 2>/dev/null", pac_dir, pac_filename))
end
local pac_url = generate_pac_url_with_client_info(pac_filename, random_suffix)
@@ -2652,15 +2927,15 @@ function action_generate_pac()
result.error = luci.i18n.translate("No authentication configured, please be aware of the risk of information leakage!")
end
- luci.http.prepare_content("application/json")
- luci.http.write_json(result)
+ HTTP.prepare_content("application/json")
+ HTTP.write_json(result)
end
function generate_pac_url_with_client_info(pac_filename, random_suffix)
- local client_protocol = luci.http.formvalue("client_protocol")
- local client_hostname = luci.http.formvalue("client_hostname")
- local client_host = luci.http.formvalue("client_host")
- local client_port = luci.http.formvalue("client_port")
+ local client_protocol = HTTP.formvalue("client_protocol")
+ local client_hostname = HTTP.formvalue("client_hostname")
+ local client_host = HTTP.formvalue("client_host")
+ local client_port = HTTP.formvalue("client_port")
local request_scheme = "http"
local host = "localhost"
@@ -2668,9 +2943,9 @@ function generate_pac_url_with_client_info(pac_filename, random_suffix)
if client_protocol and (client_protocol == "http" or client_protocol == "https") then
request_scheme = client_protocol
else
- if luci.http.getenv("HTTPS") == "on" or
- luci.http.getenv("HTTP_X_FORWARDED_PROTO") == "https" or
- luci.http.getenv("REQUEST_SCHEME") == "https" then
+ if HTTP.getenv("HTTPS") == "on" or
+ HTTP.getenv("HTTP_X_FORWARDED_PROTO") == "https" or
+ HTTP.getenv("REQUEST_SCHEME") == "https" then
request_scheme = "https"
end
end
@@ -2686,9 +2961,9 @@ function generate_pac_url_with_client_info(pac_filename, random_suffix)
end
end
else
- local server_name = luci.http.getenv("SERVER_NAME")
- local http_host = luci.http.getenv("HTTP_HOST")
- local server_port = luci.http.getenv("SERVER_PORT")
+ local server_name = HTTP.getenv("SERVER_NAME")
+ local http_host = HTTP.getenv("HTTP_HOST")
+ local server_port = HTTP.getenv("SERVER_PORT")
local proxy_ip = daip()
if http_host and http_host ~= "" then
@@ -2898,23 +3173,23 @@ local function is_safe_filename(filename)
end
local function kill_process()
- local cmd = string.format("%s |grep -E 'openclash|clash' |grep -v grep |awk '{print $1}' |xargs -r kill -9 >/dev/null 2>&1", fs.ps_cmd())
- luci.sys.call(cmd)
+ local cmd = string.format("%s |grep -E 'openclash|clash|mihomo' |grep -v grep |awk '{print $1}' |xargs -r kill -9 >/dev/null 2>&1", fs.ps_cmd())
+ SYS.call(cmd)
end
function action_oc_action()
- local action = luci.http.formvalue("action")
- local config_file = luci.http.formvalue("config_file")
+ local action = HTTP.formvalue("action")
+ local config_file = HTTP.formvalue("config_file")
if not action then
- luci.http.status(500, "Missing action parameter")
+ HTTP.status(500, "Missing action parameter")
return
end
if config_file and config_file ~= "" then
local config_path = "/etc/openclash/config/" .. config_file
if not fs.access(config_path) then
- luci.http.status(500, "Config file not found")
+ HTTP.status(500, "Config file not found")
return
end
@@ -2930,9 +3205,9 @@ function action_oc_action()
end
if not is_running() then
kill_process()
- luci.sys.call("/etc/init.d/openclash start >/dev/null 2>&1")
+ SYS.call("/etc/init.d/openclash start >/dev/null 2>&1")
else
- luci.sys.call("/etc/init.d/openclash restart >/dev/null 2>&1")
+ SYS.call("/etc/init.d/openclash restart >/dev/null 2>&1")
end
elseif action == "stop" then
if uci:get("openclash", "config", "enable") ~= "0" then
@@ -2940,29 +3215,29 @@ function action_oc_action()
uci:commit("openclash")
end
kill_process()
- luci.sys.call("/etc/init.d/openclash stop >/dev/null 2>&1")
+ SYS.call("/etc/init.d/openclash stop >/dev/null 2>&1")
elseif action == "restart" then
if uci:get("openclash", "config", "enable") ~= "1" then
uci:set("openclash", "config", "enable", "1")
uci:commit("openclash")
end
kill_process()
- luci.sys.call("/etc/init.d/openclash restart >/dev/null 2>&1")
+ SYS.call("/etc/init.d/openclash restart >/dev/null 2>&1")
else
- luci.http.status(500, "Invalid action parameter")
+ HTTP.status(500, "Invalid action parameter")
return
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({status = "success", action = action})
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({status = "success", action = action})
end
function action_config_file_list()
local config_files = {}
local age_files = {}
local current_config = ""
-
local config_path = fs.uci_get_config("config", "config_path")
+
if config_path then
current_config = config_path
end
@@ -2981,6 +3256,35 @@ function action_config_file_list()
end)
local config_dir = "/etc/openclash/config/"
+ local fingerprint_parts = {}
+ local cache_valid = false
+ local client_fp = HTTP.formvalue("fingerprint")
+
+ if fs.access(config_dir) then
+ local files = fs.dir(config_dir)
+ if files then
+ local yaml_files = {}
+ for _, f in ipairs(files) do
+ if string.match(f, "%.ya?ml$") then
+ local stat = fs.stat(config_dir .. f)
+ if stat and stat.type == "regular" then
+ yaml_files[#yaml_files + 1] = f .. ":" .. (stat.mtime or 0)
+ end
+ end
+ end
+ table.sort(yaml_files)
+ fingerprint_parts[#fingerprint_parts + 1] = table.concat(yaml_files, "|")
+ end
+ end
+ for _, a in ipairs(age_files) do
+ fingerprint_parts[#fingerprint_parts + 1] = "AGE:" .. a.name .. ":" .. (a.secret ~= "" and "1" or "0")
+ end
+ local fingerprint = table.concat(fingerprint_parts, "||")
+
+ if client_fp and client_fp == fingerprint then
+ cache_valid = true
+ end
+
if fs.access(config_dir) then
local files = fs.dir(config_dir)
if files then
@@ -2988,8 +3292,8 @@ function action_config_file_list()
local full_path = config_dir .. file
local stat = fs.stat(full_path)
local name_no_ext = file:match("^(.*)%.ya?ml$")
- if stat and stat.type == "regular" then
- if name_no_ext then
+ if stat and stat.type == "regular" and string.match(file, "%.ya?ml$") then
+ if name_no_ext and #age_files > 0 and not cache_valid then
local cfile = io.open(full_path,"r")
if cfile then
local content = cfile:read(1024)
@@ -3000,43 +3304,42 @@ function action_config_file_list()
break
end
end
+ cfile:close()
end
- cfile:close()
- end
- if string.match(file, "%.ya?ml$") then
- table.insert(config_files, {
- name = file,
- path = full_path,
- size = stat.size,
- mtime = stat.mtime,
- age = stat.age or false
- })
end
+ table.insert(config_files, {
+ name = file,
+ path = full_path,
+ size = stat.size,
+ mtime = stat.mtime,
+ age = stat.age or false
+ })
end
end
end
table.sort(config_files, function(a, b)
- return a.mtime > b.mtime
+ return string.lower(a.name) < string.lower(b.name)
end)
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
config_files = config_files,
current_config = current_config,
- total_count = #config_files
+ total_count = #config_files,
+ fingerprint = fingerprint
})
end
function action_upload_config()
- local upload = luci.http.formvalue("config_file")
- local filename = luci.http.formvalue("filename")
+ local upload = HTTP.formvalue("config_file")
+ local filename = HTTP.formvalue("filename")
- luci.http.prepare_content("application/json")
+ HTTP.prepare_content("application/json")
if not upload or upload == "" then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "No file uploaded"
})
@@ -3048,7 +3351,7 @@ function action_upload_config()
end
if not is_safe_filename(filename) then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Invalid filename"
})
@@ -3063,7 +3366,7 @@ function action_upload_config()
local target_path = config_dir .. filename
if string.len(upload) == 0 then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Uploaded file is empty"
})
@@ -3072,7 +3375,7 @@ function action_upload_config()
local file_size = string.len(upload)
if file_size > 10 * 1024 * 1024 then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = string.format("File size (%s) exceeds 10MB limit", fs.filesize(file_size))
})
@@ -3091,34 +3394,34 @@ function action_upload_config()
end
if not yaml_valid then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Invalid config file format - missing required YAML sections"
})
return
end
- luci.sys.call("mkdir -p " .. config_dir)
+ SYS.call("mkdir -p " .. config_dir)
local fp = io.open(target_path, "w")
if fp then
fp:write(upload)
fp:close()
- luci.sys.call(string.format("chmod 644 '%s'", target_path))
- luci.sys.call(string.format("chown root:root '%s'", target_path))
+ SYS.call(string.format("chmod 644 '%s'", target_path))
+ SYS.call(string.format("chown root:root '%s'", target_path))
local written_content = fs.readfile(target_path)
if not written_content or string.len(written_content) ~= file_size then
fs.unlink(target_path)
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "File write verification failed"
})
return
end
- luci.http.write_json({
+ HTTP.write_json({
status = "success",
message = "Config file uploaded successfully",
filename = filename,
@@ -3127,7 +3430,7 @@ function action_upload_config()
readable_size = fs.filesize(file_size)
})
else
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Failed to save config file to disk"
})
@@ -3135,11 +3438,11 @@ function action_upload_config()
end
function action_config_file_read()
- local config_file = luci.http.formvalue("config_file")
- luci.http.prepare_content("application/json")
+ local config_file = HTTP.formvalue("config_file")
+ HTTP.prepare_content("application/json")
if not config_file then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Missing config_file parameter"
})
@@ -3158,7 +3461,7 @@ function action_config_file_read()
end
if not allow then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Invalid config file path"
})
@@ -3166,7 +3469,7 @@ function action_config_file_read()
end
if not fs.access(config_file) then
- luci.http.write_json({
+ HTTP.write_json({
status = "success",
content = "",
file_info = {
@@ -3182,7 +3485,7 @@ function action_config_file_read()
local stat = fs.stat(config_file)
if not stat or stat.type ~= "regular" then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Config file is not a regular file"
})
@@ -3190,7 +3493,7 @@ function action_config_file_read()
end
if stat.size > 10 * 1024 * 1024 then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Config file too large (max 10MB)"
})
@@ -3199,14 +3502,14 @@ function action_config_file_read()
local content = fs.readfile(config_file)
if content == nil then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Failed to read config file"
})
return
end
- luci.http.write_json({
+ HTTP.write_json({
status = "success",
content = content,
file_info = {
@@ -3220,15 +3523,15 @@ function action_config_file_read()
end
function action_config_file_save()
- local config_file = luci.http.formvalue("config_file")
- local content = luci.http.formvalue("content")
- luci.http.prepare_content("application/json")
+ local config_file = HTTP.formvalue("config_file")
+ local content = HTTP.formvalue("content")
+ HTTP.prepare_content("application/json")
if content then
content = content:gsub("\r\n", "\n"):gsub("\r", "\n")
end
if not config_file then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Missing config_file parameter"
})
@@ -3236,7 +3539,7 @@ function action_config_file_save()
end
if not content then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Missing content parameter"
})
@@ -3247,7 +3550,7 @@ function action_config_file_save()
if not is_overwrite then
if not string.match(config_file, "^/etc/openclash/config/[^/]+%.ya?ml$") or string.find(config_file, "%.%.") then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Invalid config file path"
})
@@ -3255,7 +3558,7 @@ function action_config_file_save()
end
else
if not (config_file == "/etc/openclash/custom/openclash_custom_overwrite.sh" or (config_file:match("^/etc/openclash/overwrite/[^/]+$") and not string.find(config_file, "%.%."))) then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Invalid overwrite file path"
})
@@ -3264,7 +3567,7 @@ function action_config_file_save()
end
if string.len(content) > 10 * 1024 * 1024 then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Content too large (max 10MB)"
})
@@ -3274,9 +3577,9 @@ function action_config_file_save()
local backup_file = nil
if fs.access(config_file) then
backup_file = config_file .. ".backup." .. os.time()
- local backup_success = luci.sys.call(string.format("cp '%s' '%s'", config_file, backup_file))
+ local backup_success = SYS.call(string.format("cp '%s' '%s'", config_file, backup_file))
if backup_success ~= 0 then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Failed to create backup file"
})
@@ -3287,10 +3590,10 @@ function action_config_file_save()
local success = fs.writefile(config_file, content)
if not success then
if backup_file then
- luci.sys.call(string.format("mv '%s' '%s'", backup_file, config_file))
+ SYS.call(string.format("mv '%s' '%s'", backup_file, config_file))
end
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Failed to write config file"
})
@@ -3300,10 +3603,10 @@ function action_config_file_save()
local written_content = fs.readfile(config_file)
if written_content ~= content then
if backup_file then
- luci.sys.call(string.format("mv '%s' '%s'", backup_file, config_file))
+ SYS.call(string.format("mv '%s' '%s'", backup_file, config_file))
end
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "File write verification failed"
})
@@ -3311,12 +3614,12 @@ function action_config_file_save()
end
if not is_overwrite then
- luci.sys.call(string.format("chmod 644 '%s'", config_file))
+ SYS.call(string.format("chmod 644 '%s'", config_file))
end
- luci.sys.call(string.format("chown root:root '%s'", config_file))
+ SYS.call(string.format("chown root:root '%s'", config_file))
if backup_file then
- luci.sys.call(string.format([[
+ SYS.call(string.format([[
(
config_dir="$(dirname '%s')"
config_basename="$(basename '%s')"
@@ -3338,7 +3641,7 @@ function action_config_file_save()
}
end
- luci.http.write_json({
+ HTTP.write_json({
status = "success",
message = "Config file saved successfully",
file_info = file_info,
@@ -3347,27 +3650,28 @@ function action_config_file_save()
end
function action_add_subscription()
- local name = luci.http.formvalue("name")
- local address = luci.http.formvalue("address")
- local sub_ua = luci.http.formvalue("sub_ua") or "clash-verge/v2.4.5"
- local sub_convert = luci.http.formvalue("sub_convert") or "0"
- local convert_address = luci.http.formvalue("convert_address") or ""
- local template = luci.http.formvalue("template") or ""
- local custom_template_url = luci.http.formvalue("custom_template_url") or ""
- local emoji = luci.http.formvalue("emoji") or "false"
- local udp = luci.http.formvalue("udp") or "false"
- local skip_cert_verify = luci.http.formvalue("skip_cert_verify") or "false"
- local sort = luci.http.formvalue("sort") or "false"
- local node_type = luci.http.formvalue("node_type") or "false"
- local rule_provider = luci.http.formvalue("rule_provider") or "false"
- local custom_params = luci.http.formvalue("custom_params") or ""
- local keyword = luci.http.formvalue("keyword") or ""
- local ex_keyword = luci.http.formvalue("ex_keyword") or ""
- local de_ex_keyword = luci.http.formvalue("de_ex_keyword") or ""
- luci.http.prepare_content("application/json")
+ local name = HTTP.formvalue("name")
+ local address = HTTP.formvalue("address")
+ local sub_ua = HTTP.formvalue("sub_ua") or "clash-verge/v2.4.5"
+ local sub_convert = HTTP.formvalue("sub_convert") or "0"
+ local convert_address = HTTP.formvalue("convert_address") or ""
+ local template = HTTP.formvalue("template") or ""
+ local custom_template_url = HTTP.formvalue("custom_template_url") or ""
+ local emoji = HTTP.formvalue("emoji") or "false"
+ local udp = HTTP.formvalue("udp") or "false"
+ local skip_cert_verify = HTTP.formvalue("skip_cert_verify") or "false"
+ local sort = HTTP.formvalue("sort") or "false"
+ local node_type = HTTP.formvalue("node_type") or "false"
+ local rule_provider = HTTP.formvalue("rule_provider") or "false"
+ local custom_params = HTTP.formvalue("custom_params") or ""
+ local keyword = HTTP.formvalue("keyword") or ""
+ local ex_keyword = HTTP.formvalue("ex_keyword") or ""
+ local de_ex_keyword = HTTP.formvalue("de_ex_keyword") or ""
+ local sub_headers = HTTP.formvalue("sub_headers") or ""
+ HTTP.prepare_content("application/json")
if not name then
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Missing name parameter"
})
@@ -3427,7 +3731,7 @@ function action_add_subscription()
error_msg = "Invalid subscription URL format. Only single HTTP/HTTPS subscription URL is supported when subscription conversion is disabled"
end
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = error_msg
})
@@ -3480,6 +3784,21 @@ function action_add_subscription()
uci:delete("openclash", section_id, "address")
end
uci:set("openclash", section_id, "sub_ua", sub_ua)
+
+ uci:delete("openclash", section_id, "sub_headers")
+ if sub_headers and sub_headers ~= "" then
+ local headers = {}
+ for line in sub_headers:gmatch("[^\n]+") do
+ local h = line:match("^%s*(.-)%s*$")
+ if h and h ~= "" then
+ table.insert(headers, h)
+ end
+ end
+ if #headers > 0 then
+ uci:set_list("openclash", section_id, "sub_headers", headers)
+ end
+ end
+
uci:set("openclash", section_id, "sub_convert", sub_convert)
if sub_convert == "1" then
uci:set("openclash", section_id, "convert_address", convert_address)
@@ -3548,7 +3867,7 @@ function action_add_subscription()
uci:commit("openclash")
local action_msg = existing_section_id and "Subscription updated successfully" or "Subscription added successfully"
- luci.http.write_json({
+ HTTP.write_json({
status = "success",
message = action_msg,
name = name,
@@ -3558,7 +3877,7 @@ function action_add_subscription()
multiple_links = sub_convert == "1" and (string.find(normalized_address, "\n") and true or false)
})
else
- luci.http.write_json({
+ HTTP.write_json({
status = "error",
message = "Failed to add/update subscription configuration"
})
@@ -3566,10 +3885,10 @@ function action_add_subscription()
end
function action_upload_overwrite()
- local upload = luci.http.formvalue("config_file")
- local filename = luci.http.formvalue("filename")
+ local upload = HTTP.formvalue("config_file")
+ local filename = HTTP.formvalue("filename")
local config_values = {}
- local raw_config = luci.http.formvalue("config") or ""
+ local raw_config = HTTP.formvalue("config") or ""
if raw_config ~= "" then
for line in raw_config:gmatch("[^\n]+") do
local config_value = line:match("^%s*(.-)%s*$")
@@ -3578,42 +3897,42 @@ function action_upload_overwrite()
end
end
end
- local enable = luci.http.formvalue("enable")
- local order = luci.http.formvalue("order")
- luci.http.prepare_content("application/json")
+ local enable = HTTP.formvalue("enable")
+ local order = HTTP.formvalue("order")
+ HTTP.prepare_content("application/json")
if not upload or upload == "" then
- luci.http.write_json({status = "error", message = "No file uploaded"})
+ HTTP.write_json({status = "error", message = "No file uploaded"})
return
end
if not filename or filename == "" then
filename = "upload_" .. os.date("%Y%m%d_%H%M%S")
end
if not is_safe_filename(filename) then
- luci.http.write_json({status = "error", message = "Invalid filename"})
+ HTTP.write_json({status = "error", message = "Invalid filename"})
return
end
local overwrite_dir = "/etc/openclash/overwrite/"
- luci.sys.call("mkdir -p " .. overwrite_dir)
+ SYS.call("mkdir -p " .. overwrite_dir)
local target_path = overwrite_dir .. filename
if string.len(upload) == 0 then
- luci.http.write_json({status = "error", message = "Uploaded file is empty"})
+ HTTP.write_json({status = "error", message = "Uploaded file is empty"})
return
end
local file_size = string.len(upload)
if file_size > 10 * 1024 * 1024 then
- luci.http.write_json({status = "error", message = string.format("File size (%s) exceeds 10MB limit", require("luci.openclash").filesize(file_size))})
+ HTTP.write_json({status = "error", message = string.format("File size (%s) exceeds 10MB limit", require("luci.openclash").filesize(file_size))})
return
end
local fp = io.open(target_path, "w")
if fp then
fp:write(upload)
fp:close()
- luci.sys.call(string.format("chmod 644 '%s'", target_path))
- luci.sys.call(string.format("chown root:root '%s'", target_path))
+ SYS.call(string.format("chmod 644 '%s'", target_path))
+ SYS.call(string.format("chown root:root '%s'", target_path))
local written_content = fs.readfile(target_path)
if not written_content or string.len(written_content) ~= file_size then
fs.unlink(target_path)
- luci.http.write_json({status = "error", message = "File write verification failed"})
+ HTTP.write_json({status = "error", message = "File write verification failed"})
return
end
@@ -3676,7 +3995,7 @@ function action_upload_overwrite()
uci:commit("openclash")
- luci.http.write_json({
+ HTTP.write_json({
status = "success",
message = "Overwrite file uploaded successfully",
filename = filename,
@@ -3685,21 +4004,21 @@ function action_upload_overwrite()
readable_size = fs.filesize(file_size)
})
else
- luci.http.write_json({status = "error", message = "Failed to save file to disk"})
+ HTTP.write_json({status = "error", message = "Failed to save file to disk"})
end
end
function action_overwrite_subscribe_info()
- local method = luci.http.getenv("REQUEST_METHOD")
- local filename = luci.http.formvalue("filename")
- local old_filename = luci.http.formvalue("old_filename")
- local typ = luci.http.formvalue("type") or "file"
+ local method = HTTP.getenv("REQUEST_METHOD")
+ local filename = HTTP.formvalue("filename")
+ local old_filename = HTTP.formvalue("old_filename")
+ local typ = HTTP.formvalue("type") or "file"
local section_name = nil
local old_section_name = nil
if filename and not is_safe_filename(filename) then
- luci.http.prepare_content("application/json")
- luci.http.write_json({status = "error", message = "Invalid filename"})
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({status = "error", message = "Invalid filename"})
return
end
@@ -3739,21 +4058,21 @@ function action_overwrite_subscribe_info()
}
end
end)
- luci.http.prepare_content("application/json")
- luci.http.write_json({status="success", data=result})
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({status="success", data=result})
return
elseif method == "POST" then
if not section_name then
- luci.http.status(500, "Missing filename")
+ HTTP.status(500, "Missing filename")
return
end
- local url = luci.http.formvalue("url") or ""
- local update_days = luci.http.formvalue("update_days") or ""
- local update_hour = luci.http.formvalue("update_hour") or ""
- local order = luci.http.formvalue("order")
- local param = luci.http.formvalue("param") or ""
+ local url = HTTP.formvalue("url") or ""
+ local update_days = HTTP.formvalue("update_days") or ""
+ local update_hour = HTTP.formvalue("update_hour") or ""
+ local order = HTTP.formvalue("order")
+ local param = HTTP.formvalue("param") or ""
local config_values = {}
- local raw_config = luci.http.formvalue("config") or ""
+ local raw_config = HTTP.formvalue("config") or ""
if raw_config ~= "" then
for line in raw_config:gmatch("[^\n]+") do
local config_value = line:match("^%s*(.-)%s*$")
@@ -3762,13 +4081,13 @@ function action_overwrite_subscribe_info()
end
end
end
- typ = luci.http.formvalue("type") or typ or "file"
- local enable = luci.http.formvalue("enable")
+ typ = HTTP.formvalue("type") or typ or "file"
+ local enable = HTTP.formvalue("enable")
if typ == "http" then
if not url or url == "" then
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
status = "error",
message = "Subscribe URL cannot be empty"
})
@@ -3779,8 +4098,8 @@ function action_overwrite_subscribe_info()
is_valid_url = true
end
if not is_valid_url then
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
status = "error",
message = "Invalid subscribe URL format, only single HTTP/HTTPS link is supported"
})
@@ -3832,8 +4151,8 @@ function action_overwrite_subscribe_info()
fs.rename(old_file, new_file)
end
uci:commit("openclash")
- luci.http.prepare_content("application/json")
- luci.http.write_json({status="success"})
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({status="success"})
return
end
if not found then
@@ -3910,13 +4229,13 @@ function action_overwrite_subscribe_info()
local file_path = overwrite_dir .. section_name
if url and url ~= "" then
local cmd = string.format('curl -sL --connect-timeout 5 -m 15 --retry 2 "%s" -o "%s"', url, file_path)
- local ret = luci.sys.call(cmd)
+ local ret = SYS.call(cmd)
if not fs.access(file_path) then
fs.writefile(file_path, "")
end
if ret ~= 0 or not fs.access(file_path) or fs.stat(file_path).size == 0 then
- luci.http.prepare_content("application/json")
- luci.http.write_json({status="error", message="Download failed"})
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({status="error", message="Download failed"})
return
end
else
@@ -3926,11 +4245,11 @@ function action_overwrite_subscribe_info()
end
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({status="success"})
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({status="success"})
return
else
- luci.http.status(500, "Method Not Allowed")
+ HTTP.status(500, "Method Not Allowed")
end
end
@@ -3973,18 +4292,18 @@ function action_overwrite_file_list()
return (a.mtime or 0) > (b.mtime or 0)
end)
- luci.http.prepare_content("application/json")
- luci.http.write_json({
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({
overwrite_files = overwrite_files,
total_count = #overwrite_files
})
end
function delete_overwrite_file()
- local filename = luci.http.formvalue("filename")
+ local filename = HTTP.formvalue("filename")
if not filename or filename == "" then
- luci.http.prepare_content("application/json")
- luci.http.write_json({status="error", message="Missing filename"})
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({status="error", message="Missing filename"})
return
end
local overwrite_dir = "/etc/openclash/overwrite/"
@@ -4012,14 +4331,14 @@ function delete_overwrite_file()
end
uci:commit("openclash")
- luci.http.prepare_content("application/json")
- luci.http.write_json({status="success"})
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({status="success"})
end
function action_get_subscribe_data()
- local filename = luci.http.formvalue("filename")
+ local filename = HTTP.formvalue("filename")
if not filename then
- luci.http.status(500, "Bad Request")
+ HTTP.status(500, "Bad Request")
return
end
@@ -4027,6 +4346,14 @@ function action_get_subscribe_data()
uci:foreach("openclash", "config_subscribe", function(s)
if s.name == filename then
data = s
+ -- UCI list fields: convert to newline-separated strings for frontend
+ local sid = s['.name']
+ for _, field in ipairs({"sub_headers", "keyword", "ex_keyword", "custom_params"}) do
+ local raw = uci:get_list("openclash", sid, field)
+ if raw then
+ data[field] = table.concat(raw, "\n")
+ end
+ end
return false
end
end)
@@ -4044,62 +4371,91 @@ function action_get_subscribe_data()
end
end)
- luci.http.prepare_content("application/json")
- luci.http.write_json(data)
+ HTTP.prepare_content("application/json")
+ HTTP.write_json(data)
end
function action_get_subscribe_info_data()
- local filename = luci.http.formvalue("filename")
+ local filename = HTTP.formvalue("filename")
if not filename then
- luci.http.status(500, "Bad Request")
+ HTTP.status(500, "Bad Request")
return
end
- luci.http.prepare_content("application/json")
- luci.http.write_json(get_sub_url(filename))
+ HTTP.prepare_content("application/json")
+ HTTP.write_json(get_sub_url(filename))
+end
+
+function action_subconverter_version()
+ local raw_url = HTTP.formvalue("url") or ""
+ local version_url, err = parse_subconverter_version_url(raw_url)
+ HTTP.prepare_content("application/json")
+
+ if not version_url then
+ HTTP.write_json({status = "error", message = err or "invalid"})
+ return
+ end
+
+ local cmd = table.concat({
+ "curl -fsS --connect-timeout 3 -m 6 --retry 0",
+ "-H " .. util.shellquote("Accept: text/plain, */*"),
+ "-H " .. util.shellquote("Origin: https://openclash.local"),
+ "-H " .. util.shellquote("Sec-Fetch-Mode: cors"),
+ "-H " .. util.shellquote("Sec-Fetch-Dest: empty"),
+ "-H " .. util.shellquote("User-Agent: OpenClash Subconverter Version Check"),
+ util.shellquote(version_url),
+ "2>/dev/null | head -c 4096"
+ }, " ")
+ local version = sanitize_subconverter_version_text(SYS.exec(cmd))
+
+ if version == "" then
+ HTTP.write_json({status = "unrecognized"})
+ else
+ HTTP.write_json({status = "success", version = version})
+ end
end
function action_generate_age_key()
- local algo = luci.http.formvalue("algo") or "keygen"
+ local algo = HTTP.formvalue("algo") or "keygen"
local cmd = string.format("%s age %s", meta_core_path, (algo == "pq" and "keygen-pq" or "keygen"))
- local out = luci.sys.exec(cmd .. " 2>/dev/null")
+ local out = SYS.exec(cmd .. " 2>/dev/null")
local secret = out:match("(AGE%-SECRET%-KEY%-%S+)")
local public = out:match("# public key: ([^\n\r]+)")
- luci.http.prepare_content("application/json")
+ HTTP.prepare_content("application/json")
if not secret then
- luci.http.write_json({status = "error", message = "Failed to generate age key", output = out})
+ HTTP.write_json({status = "error", message = "Failed to generate age key", output = out})
return
end
- luci.http.write_json({status = "success", secret = secret, public = public})
+ HTTP.write_json({status = "success", secret = secret, public = public})
end
function action_cal_age_public_key()
- local secret = luci.http.formvalue("secret") or ""
+ local secret = HTTP.formvalue("secret") or ""
if secret == "" then
- luci.http.prepare_content("application/json")
- luci.http.write_json({status = "error", message = "Secret key is required"})
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({status = "error", message = "Secret key is required"})
return
end
local cmd = string.format("%s age convert %s", meta_core_path, secret)
- local out = luci.sys.exec(cmd .. " 2>/dev/null")
- luci.http.prepare_content("application/json")
+ local out = SYS.exec(cmd .. " 2>/dev/null")
+ HTTP.prepare_content("application/json")
if out and out:match("^age") then
- luci.http.write_json({status = "success", public = out})
+ HTTP.write_json({status = "success", public = out})
else
- luci.http.write_json({status = "error", message = "Failed to calculate public key, invalid secret key", output = out})
+ HTTP.write_json({status = "error", message = "Failed to calculate public key, invalid secret key", output = out})
end
end
function action_add_age_config()
- local name = luci.http.formvalue("name")
- local age_secret = luci.http.formvalue("age_secret") or ""
- local age_public = luci.http.formvalue("age_public") or ""
- local age_algo = luci.http.formvalue("age_algo") or ""
+ local name = HTTP.formvalue("name")
+ local age_secret = HTTP.formvalue("age_secret") or ""
+ local age_public = HTTP.formvalue("age_public") or ""
+ local age_algo = HTTP.formvalue("age_algo") or ""
local age_section_id, age_section_hidden
- luci.http.prepare_content("application/json")
+ HTTP.prepare_content("application/json")
if not name or name == "" then
- luci.http.write_json({status = "error", message = "Missing name parameter"})
+ HTTP.write_json({status = "error", message = "Missing name parameter"})
return
end
@@ -4112,7 +4468,7 @@ function action_add_age_config()
end)
if age_section_hidden then
- luci.http.write_json({status = "error", message = "Cannot modify hidden age configuration"})
+ HTTP.write_json({status = "error", message = "Cannot modify hidden age configuration"})
return
end
@@ -4146,143 +4502,256 @@ function action_add_age_config()
uci:commit("openclash")
end
- luci.http.write_json({status = "success"})
+ HTTP.write_json({status = "success"})
end
function oix_login_info_save()
- uci:set("openclash", "config", "oix_email", luci.http.formvalue("email"))
- uci:set("openclash", "config", "oix_passwd", luci.http.formvalue("passwd"))
- uci:set("openclash", "config", "oix_checkin", luci.http.formvalue("checkin"))
- uci:set("openclash", "config", "oix_checkin_interval", luci.http.formvalue("interval"))
- if tonumber(luci.http.formvalue("multiple")) > 100 then
- uci:set("openclash", "config", "oix_checkin_multiple", "100")
- elseif tonumber(luci.http.formvalue("multiple")) < 1 or not tonumber(luci.http.formvalue("multiple")) then
- uci:set("openclash", "config", "oix_checkin_multiple", "1")
+ local token = HTTP.formvalue("token")
+ if token and token ~= "" then
+ uci:set("openclash", "config", "oix_token", token)
else
- uci:set("openclash", "config", "oix_checkin_multiple", luci.http.formvalue("multiple"))
+ local email = HTTP.formvalue("email")
+ local passwd = HTTP.formvalue("passwd")
+ if email then uci:set("openclash", "config", "oix_email", email) end
+ if passwd then uci:set("openclash", "config", "oix_passwd", passwd) end
end
+ local checkin = HTTP.formvalue("checkin")
+ if checkin then uci:set("openclash", "config", "oix_checkin", checkin) end
+ local interval = tonumber(HTTP.formvalue("interval"))
+ if interval then
+ if interval < 1 then interval = 1 end
+ if interval > 720 then interval = 720 end
+ uci:set("openclash", "config", "oix_checkin_interval", tostring(interval))
+ end
+ local multiple = tonumber(HTTP.formvalue("multiple"))
+ if multiple then
+ if multiple < 1 then multiple = 1 end
+ if multiple > 100 then multiple = 100 end
+ uci:set("openclash", "config", "oix_checkin_multiple", tostring(multiple))
+ end
+ local show_info_page = HTTP.formvalue("show_info_page")
+ if show_info_page then uci:set("openclash", "config", "oix_show_info_page", show_info_page) end
+ local default_params = HTTP.formvalue("default_params")
+ if default_params then uci:set("openclash", "config", "oix_default_params", default_params) end
uci:commit("openclash")
- luci.http.prepare_content("application/json")
- luci.http.write_json({status = "success"})
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({status = "success"})
+end
+
+function oix_params_sync()
+ local params = HTTP.formvalue("params") or ""
+ if #params > 8192 then
+ HTTP.status(400, "Params too long")
+ HTTP.write_json({status = "error", msg = "params too long"})
+ return
+ end
+
+ uci:set("openclash", "config", "oix_params", params)
+ uci:commit("openclash")
+
+ if is_running() then
+ local dase_val = dase() or ""
+ local daip_val = daip()
+ local cn_port_val = cn_port()
+ local auth_header = ""
+ if dase_val and dase_val ~= "" then
+ auth_header = string.format('-H "Authorization: Bearer %s"', dase_val)
+ end
+
+ if params == "" then
+ SYS.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" %s -XDELETE http://"%s":"%s"/oix/options', auth_header, daip_val, cn_port_val))
+ else
+ local encoded = params:gsub('"', '\\"'):gsub('\n', '')
+ SYS.exec(string.format('curl -sL -m 3 --retry 2 -H "Content-Type: application/json" %s -XPUT -d \'{"params":"%s"}\' http://"%s":"%s"/oix/options', auth_header, encoded, daip_val, cn_port_val))
+ end
+ end
+
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({status = "success"})
+end
+
+function oix_params_get()
+ local result = {params = "", default_params = ""}
+ local home_dir = "/etc/openclash"
+ local params_file = home_dir .. "/.oix_params"
+ local default_params_file = home_dir .. "/.oix_default_params"
+
+ if fs.access(params_file) then
+ local content = fs.readfile(params_file)
+ if content then
+ result.params = content:gsub("%s+", "")
+ end
+ end
+ if fs.access(default_params_file) then
+ local content = fs.readfile(default_params_file)
+ if content then
+ result.default_params = content:gsub("%s+", "")
+ end
+ end
+
+ HTTP.prepare_content("application/json")
+ HTTP.write_json(result)
+end
+
+local function fetch_oix_sub(token)
+ local get_sub = string.format("curl -sL -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -X POST https://oix-api.dler.io/api/v1/managed/clash", token)
+ local sub_info = SYS.exec(get_sub)
+ if sub_info then sub_info = json.parse(sub_info) end
+ if sub_info and sub_info.ret == 200 then
+ local sub_key = {"openclash"}
+ for _,v in ipairs(sub_key) do
+ while true do
+ local sub_match, sub_convert, sid = false, false, nil
+ uci:foreach("openclash", "config_subscribe",
+ function(s)
+ if s.name == "oixCloud - smart" and s.address == sub_info[v] then
+ sub_match = true; return false
+ end
+ if s.name == "oixCloud - smart" and s.address ~= sub_info[v] then
+ sub_convert = true; sid = s['.name']; return false
+ end
+ end)
+ if sub_match then break end
+ if sub_convert then uci:set("openclash", sid, "address", sub_info[v])
+ elseif sub_info[v] then
+ sid = uci:add("openclash", "config_subscribe")
+ uci:set("openclash", sid, "name", "oixCloud - smart")
+ uci:set("openclash", sid, "address", sub_info[v])
+ end
+ uci:commit("openclash")
+ break
+ end
+ if sub_info[v] then
+ SYS.exec(string.format('curl -sL -m 10 --retry 2 --user-agent "clash" "%s" -o "/etc/openclash/config/oixCloud - smart.yaml" >/dev/null 2>&1', sub_info[v]))
+ local core = coremetacv()
+ if core ~= "0" and not string.match(core, "oix") then
+ SYS.exec("/usr/share/openclash/openclash_core.sh Oix")
+ else
+ SYS.call("/etc/init.d/openclash restart >/dev/null 2>&1 &")
+ end
+ end
+ end
+ return true
+ end
+ return false
end
function oix_login()
- local result, info, token, get_sub, sub_info, sub_key, sub_match, sub_convert, sid, sub_file, SIGNATURE
+ local result, info, token
+ local input_token = HTTP.formvalue("token")
local email = fs.uci_get_config("config", "oix_email")
local passwd = fs.uci_get_config("config", "oix_passwd")
- token = fs.uci_get_config("config", "oix_token")
- if email and passwd then
- info = luci.sys.exec(string.format("curl -sL -H 'Content-Type: application/json' -H 'User-Agent: OpenClash' -d '{\"email\":\"%s\", \"passwd\":\"%s\", \"token_expire\":\"365\" }' -X POST https://oix-api.dler.io/api/v1/login", email, passwd))
- if info then
- info = json.parse(info)
+ if input_token and input_token ~= "" then
+ -- Token direct login mode
+ uci:set("openclash", "config", "oix_token", input_token)
+ uci:commit("openclash")
+ token = input_token
+ if fetch_oix_sub(token) then
+ result = 200
+ else
+ result = "invalid token"
end
- if info and info.ret == 200 then
- -- because of uci cache, need reload after delete if get new
- if token and token ~= "" then
- oix_logout(token)
+ else
+ -- Email/password login mode
+ token = fs.uci_get_config("config", "oix_token")
+ if email and passwd then
+ info = SYS.exec(string.format("curl -sL -H 'Content-Type: application/json' -H 'User-Agent: OpenClash for oixCloud' -d '{\"email\":\"%s\", \"passwd\":\"%s\", \"token_expire\":\"365\" }' -X POST https://oix-api.dler.io/api/v1/login", email, passwd))
+ if info then
+ info = json.parse(info)
end
- token = info.data.token
- uci:set("openclash", "config", "oix_token", token)
- uci:commit("openclash")
- get_sub = string.format("curl -sL -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -X POST https://oix-api.dler.io/api/v1/managed/clash", token)
- sub_info = luci.sys.exec(get_sub)
- if sub_info then
- sub_info = json.parse(sub_info)
- end
- if sub_info and sub_info.ret == 200 then
- sub_key = {"openclash"}
- for _,v in ipairs(sub_key) do
- while true do
- sub_match = false
- sub_convert = false
- uci:foreach("openclash", "config_subscribe",
- function(s)
- if s.name == "oixCloud - smart" and s.address == sub_info[v] then
- sub_match = true
- return false
- end
- if s.name == "oixCloud - smart" and s.address ~= sub_info[v] then
- sub_convert = true
- sid = s['.name']
- return false
- end
- end)
- if sub_match then break end
- if sub_convert then
- uci:set("openclash", sid, "address", sub_info[v])
- elseif sub_info[v] then
- sid = uci:add("openclash", "config_subscribe")
- uci:set("openclash", sid, "name", "oixCloud - smart")
- uci:set("openclash", sid, "address", sub_info[v])
- end
- uci:commit("openclash")
- break
- end
- if sub_info[v] then
- luci.sys.exec(string.format('curl -sL -m 10 --retry 2 --user-agent "clash" "%s" -o "/etc/openclash/config/oixCloud - smart.yaml" >/dev/null 2>&1', sub_info[v]))
- luci.sys.call("/etc/init.d/openclash restart >/dev/null 2>&1 &")
- end
+ if info and info.ret == 200 then
+ if token and token ~= "" then
+ oix_logout(token)
end
+ token = info.data.token
+ uci:set("openclash", "config", "oix_token", token)
+ uci:commit("openclash")
+ result = info.ret
+ fetch_oix_sub(token)
+ else
+ uci:delete("openclash", "config", "oix_token")
+ uci:commit("openclash")
+ fs.unlink("/tmp/oix_checkin")
+ fs.unlink("/tmp/oix_info")
+ if info and info.msg then
+ result = info.msg
+ else
+ result = "login failed"
+ end
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({result = result})
+ return
end
- result = info.ret
else
uci:delete("openclash", "config", "oix_token")
uci:commit("openclash")
fs.unlink("/tmp/oix_checkin")
fs.unlink("/tmp/oix_info")
- if info and info.msg then
- result = info.msg
- else
- result = "login faild"
- end
+ result = "email or passwd is wrong"
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({result = result})
+ return
end
- else
- uci:delete("openclash", "config", "oix_token")
- uci:commit("openclash")
- fs.unlink("/tmp/oix_checkin")
- fs.unlink("/tmp/oix_info")
- result = "email or passwd is wrong"
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({result = result})
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({result = result})
end
function oix_logout(oldtoken)
local info, result, token
+ local is_token_login = false
if not oldtoken then
token = fs.uci_get_config("config", "oix_token")
+ is_token_login = not fs.uci_get_config("config", "oix_email")
else
token = oldtoken
end
if token then
- info = luci.sys.exec(string.format("curl -sL -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -X POST https://oix-api.dler.io/api/v1/logout", token))
- if info then
- info = json.parse(info)
- end
- if info and info.ret == 200 then
+ if is_token_login then
uci:delete("openclash", "config", "oix_token")
- if not oldtoken then
- uci:delete("openclash", "config", "oix_checkin")
- uci:delete("openclash", "config", "oix_checkin_interval")
- uci:delete("openclash", "config", "oix_checkin_multiple")
- end
+ uci:delete("openclash", "config", "oix_checkin")
+ uci:delete("openclash", "config", "oix_checkin_interval")
+ uci:delete("openclash", "config", "oix_checkin_multiple")
+ uci:delete("openclash", "config", "oix_params")
+ uci:delete("openclash", "config", "oix_default_params")
uci:commit("openclash")
fs.unlink("/tmp/oix_checkin")
fs.unlink("/tmp/oix_info")
- result = info.ret
+ result = 200
else
- if info and info.msg then
- result = info.msg
+ info = SYS.exec(string.format("curl -sL -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -X POST https://oix-api.dler.io/api/v1/logout", token))
+ if info then
+ info = json.parse(info)
+ end
+ if info and info.ret == 200 then
+ uci:delete("openclash", "config", "oix_token")
+ if not oldtoken then
+ uci:delete("openclash", "config", "oix_email")
+ uci:delete("openclash", "config", "oix_passwd")
+ uci:delete("openclash", "config", "oix_checkin")
+ uci:delete("openclash", "config", "oix_checkin_interval")
+ uci:delete("openclash", "config", "oix_checkin_multiple")
+ uci:delete("openclash", "config", "oix_params")
+ uci:delete("openclash", "config", "oix_default_params")
+ end
+ uci:commit("openclash")
+ fs.unlink("/tmp/oix_checkin")
+ fs.unlink("/tmp/oix_info")
+ result = info.ret
else
- result = "logout faild"
+ if info and info.msg then
+ result = info.msg
+ else
+ result = "logout failed"
+ end
end
end
else
- result = "logout faild"
+ result = "logout failed"
end
if not oldtoken then
- luci.http.prepare_content("application/json")
- luci.http.write_json({result = result})
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({result = result})
end
end
@@ -4294,13 +4763,13 @@ function oix_info()
if token then
get_info = string.format("curl -sL -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -X POST https://oix-api.dler.io/api/v1/information -o %s", token, path)
if not fs.access(path) then
- luci.sys.exec(get_info)
+ SYS.exec(get_info)
else
if fs.readfile(path) == "" or not fs.readfile(path) then
- luci.sys.exec(get_info)
+ SYS.exec(get_info)
else
if (os.time() - fs.mtime(path) > 900) then
- luci.sys.exec(get_info)
+ SYS.exec(get_info)
end
end
end
@@ -4316,8 +4785,8 @@ function oix_info()
fs.unlink(path)
end
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({result = result})
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({result = result})
end
function oix_checkin()
@@ -4326,26 +4795,26 @@ function oix_checkin()
local token = fs.uci_get_config("config", "oix_token")
local multiple = fs.uci_get_config("config", "oix_checkin_multiple") or 1
if token then
- info = luci.sys.exec(string.format("curl -sL -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -d '{\"multiple\":\"%s\"}' -X POST https://oix-api.dler.io/api/v1/checkin", token, token, multiple))
+ info = SYS.exec(string.format("curl -sL -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -d '{\"multiple\":\"%s\"}' -X POST https://oix-api.dler.io/api/v1/checkin", token, multiple))
if info then
info = json.parse(info)
end
if info and info.ret == 200 then
fs.unlink("/tmp/oix_info")
fs.writefile(path, info)
- luci.sys.exec(string.format("echo -e %s oixCloud Checkin Successful, Result:【%s】 >> /tmp/openclash.log", os.date("%Y-%m-%d %H:%M:%S"), info.data.checkin))
+ SYS.exec(string.format("echo -e %s [Info] oixCloud Checkin Successful, Result:【%s】 >> /tmp/openclash.log", os.date("%Y-%m-%d %H:%M:%S"), info.data.checkin))
result = info
else
if info and info.msg then
- luci.sys.exec(string.format("echo -e %s oixCloud Checkin Failed, Result:【%s】 >> /tmp/openclash.log", os.date("%Y-%m-%d %H:%M:%S"), info.msg))
+ SYS.exec(string.format("echo -e %s [Info] oixCloud Checkin Failed, Result:【%s】 >> /tmp/openclash.log", os.date("%Y-%m-%d %H:%M:%S"), info.msg))
else
- luci.sys.exec(string.format("echo -e %s oixCloud Checkin Failed! Please Check And Try Again... >> /tmp/openclash.log",os.date("%Y-%m-%d %H:%M:%S")))
+ SYS.exec(string.format("echo -e %s [Info] oixCloud Checkin Failed! Please Check And Try Again... >> /tmp/openclash.log",os.date("%Y-%m-%d %H:%M:%S")))
end
result = info
end
else
result = "error"
end
- luci.http.prepare_content("application/json")
- luci.http.write_json({result = result})
-end
\ No newline at end of file
+ HTTP.prepare_content("application/json")
+ HTTP.write_json({result = result})
+end
diff --git a/luci-app-openclash/luasrc/model/cbi/openclash/client.lua b/luci-app-openclash/luasrc/model/cbi/openclash/client.lua
index 9ff273be0..439231301 100644
--- a/luci-app-openclash/luasrc/model/cbi/openclash/client.lua
+++ b/luci-app-openclash/luasrc/model/cbi/openclash/client.lua
@@ -13,7 +13,7 @@ m.reset = false
m.submit = false
m:section(SimpleSection).template = "openclash/status"
-if fs.uci_get_config("config", "oix_token") then
+if fs.uci_get_config("config", "oix_token") and fs.uci_get_config("config", "oix_show_info_page") ~= "0" then
m:append(Template("openclash/oixcloud"))
end
m:append(Template("openclash/myip"))
diff --git a/luci-app-openclash/luasrc/model/cbi/openclash/config-overwrite.lua b/luci-app-openclash/luasrc/model/cbi/openclash/config-overwrite.lua
index 9bd7ee0c1..8ef9f6e0d 100644
--- a/luci-app-openclash/luasrc/model/cbi/openclash/config-overwrite.lua
+++ b/luci-app-openclash/luasrc/model/cbi/openclash/config-overwrite.lua
@@ -94,7 +94,7 @@ o:value("http://captive.apple.com/generate_204")
o.default = "0"
o = s:taboption("settings", Value, "github_address_mod", translate("Github Address Modify"))
-o.description = translate("Modify The Github Address In The Config And OpenClash With Proxy(CDN) To Prevent File Download Faild. Format Reference:").." ".."https://ghfast.top/"
+o.description = translate("Modify The Github Address In The Config And OpenClash With Proxy(CDN) To Prevent File Download Failed. Format Reference:").." ".."https://ghfast.top/"
o:value("0", translate("Disable"))
o:value("https://fastly.jsdelivr.net/")
o:value("https://testingcf.jsdelivr.net/")
@@ -215,6 +215,7 @@ function custom_fallback_filter.write(self, section, value)
fs.writefile("/etc/openclash/custom/openclash_custom_fallback_filter.yaml", value)
end
end
+ return true
end
if op_mode == "fake-ip" then
@@ -247,6 +248,7 @@ function custom_fake_black.write(self, section, value)
fs.writefile("/etc/openclash/custom/openclash_custom_fake_filter.list", value)
end
end
+ return true
end
end
@@ -271,6 +273,7 @@ function custom_domain_dns_policy.write(self, section, value)
fs.writefile("/etc/openclash/custom/openclash_custom_domain_dns_policy.list", value)
end
end
+ return true
end
o = s:taboption("dns", Flag, "custom_proxy_server_policy", translate("Proxy-Server-Nameserver-Policy"))
@@ -294,6 +297,7 @@ function custom_proxy_server_dns_policy.write(self, section, value)
fs.writefile("/etc/openclash/custom/openclash_custom_proxy_server_dns_policy.list", value)
end
end
+ return true
end
o = s:taboption("dns", Flag, "custom_host", translate("Hosts"))
@@ -317,6 +321,7 @@ function custom_hosts.write(self, section, value)
fs.writefile("/etc/openclash/custom/openclash_custom_hosts.list", value)
end
end
+ return true
end
-- Meta
@@ -384,6 +389,7 @@ function sniffer_custom.write(self, section, value)
fs.writefile("/etc/openclash/custom/openclash_custom_sniffer.yaml", value)
end
end
+ return true
end
-- Smart Settings
@@ -476,6 +482,7 @@ function custom_rules.write(self, section, value)
fs.writefile("/etc/openclash/custom/openclash_custom_rules.list", value)
end
end
+ return true
end
custom_rules_2 = s:taboption("rules", Value, "custom_rules_2")
@@ -496,6 +503,7 @@ function custom_rules_2.write(self, section, value)
fs.writefile("/etc/openclash/custom/openclash_custom_rules_2.list", value)
end
end
+ return true
end
-- [[ Edit Custom DNS ]] --
diff --git a/luci-app-openclash/luasrc/model/cbi/openclash/config-subscribe-edit.lua b/luci-app-openclash/luasrc/model/cbi/openclash/config-subscribe-edit.lua
index 40995f958..875302c28 100644
--- a/luci-app-openclash/luasrc/model/cbi/openclash/config-subscribe-edit.lua
+++ b/luci-app-openclash/luasrc/model/cbi/openclash/config-subscribe-edit.lua
@@ -9,6 +9,14 @@ local HTTP = require "luci.http"
local DISP = require "luci.dispatcher"
local sid = arg[1]
local age_section
+local backend_version_status = [[
+
+
+
+
+
+
+]]
font_red = [[]]
font_off = [[]]
@@ -67,6 +75,12 @@ o:value("clash.meta/1.19.20")
o:value("Clash")
o.rmempty = true
+---- Custom Headers
+o = s:option(DynamicList, "sub_headers", translate("Custom Headers"))
+o.description = font_red..bold_on..translate("Custom HTTP request headers, one per line, format: Header-Name: value")..bold_off..font_off
+o.rmempty = true
+o.placeholder = "x-hwid: my-device-id"
+
o = s:option(ListValue, "config_age_algo", translate("Age Key Type"))
o.description = font_red..bold_on..translate("Age Encryption For Config, Click For More:")..bold_off..font_off.." "..""..translate("Age Encryption Introduce")..""
o:value("keygen", "x25519")
@@ -197,7 +211,7 @@ o.default = 0
---- Convert Address
o = s:option(Value, "convert_address", translate("Convert Address"))
o.rmempty = true
-o.description = font_red..bold_on..translate("Note: There is A Risk of Privacy Leakage in Online Convert")..bold_off..font_off
+o.description = backend_version_status..font_red..bold_on..translate("Note: There is A Risk of Privacy Leakage in Online Convert")..bold_off..font_off
o:depends("sub_convert", "1")
o:value("https://api.asailor.org/sub", translate("api.asailor.org"))
o:value("https://api.wcc.best/sub", translate("api.wcc.best"))
@@ -331,4 +345,5 @@ o.write = function()
end
m:append(Template("openclash/toolbar_show"))
+m:append(Template("openclash/subconverter_version"))
return m
diff --git a/luci-app-openclash/luasrc/model/cbi/openclash/config.lua b/luci-app-openclash/luasrc/model/cbi/openclash/config.lua
index 2d7607c60..fd616a0a1 100644
--- a/luci-app-openclash/luasrc/model/cbi/openclash/config.lua
+++ b/luci-app-openclash/luasrc/model/cbi/openclash/config.lua
@@ -350,7 +350,6 @@ local tab = {
}
s = m:section(Table, tab)
-s.description = align_mid..translate("Support syntax check, press").." "..font_green..bold_on.."F10"..bold_off..font_off.." "..translate("to control diff option, press").." "..font_green..bold_on.."F11"..bold_off..font_off.." "..translate("to enter full screen editing mode")..align_mid_off
s.anonymous = true
s.addremove = false
@@ -361,6 +360,8 @@ local conf_name = fs.basename(conf)
if not conf_name then conf_name = "config.yaml" end
local sconf = "/etc/openclash/"..conf_name
+s.description = align_mid..translate("Current Config")..": "..font_green..bold_on..conf_name..bold_off..font_off..align_mid_off
+
sev = s:option(TextValue, "user")
---sev.description = align_mid..translate("Modify Your Config file:").." "..font_green..bold_on..conf_name..bold_off..font_off.." "..translate("Here, Except The Settings That Were Taken Over")..align_mid_off
sev.rows = 40
@@ -421,7 +422,7 @@ o.write = function()
HTTP.redirect(DISP.build_url("admin", "services", "openclash"))
end
-m:append(Template("openclash/config_editor"))
+m:append(Template("openclash/config_merge_editor"))
m:append(Template("openclash/config_upload"))
return ful , form , p , m
diff --git a/luci-app-openclash/luasrc/model/cbi/openclash/other-file-edit.lua b/luci-app-openclash/luasrc/model/cbi/openclash/other-file-edit.lua
index 0c28e0958..77a43e25d 100644
--- a/luci-app-openclash/luasrc/model/cbi/openclash/other-file-edit.lua
+++ b/luci-app-openclash/luasrc/model/cbi/openclash/other-file-edit.lua
@@ -29,6 +29,7 @@ function o.write(self, section, value)
fs.writefile(file_path, value)
end
end
+ return true
end
function o.cfgvalue(self, section)
diff --git a/luci-app-openclash/luasrc/model/cbi/openclash/settings.lua b/luci-app-openclash/luasrc/model/cbi/openclash/settings.lua
index b7c9ddbab..1142c997f 100644
--- a/luci-app-openclash/luasrc/model/cbi/openclash/settings.lua
+++ b/luci-app-openclash/luasrc/model/cbi/openclash/settings.lua
@@ -229,6 +229,7 @@ function custom_domain_dns.write(self, section, value)
fs.writefile("/etc/openclash/custom/openclash_custom_domain_dns.list", value)
end
end
+ return true
end
---- Access Control
@@ -488,7 +489,7 @@ o.default = 0
o = s:taboption("traffic_control", Value, "common_ports", font_red..bold_on..translate("Common Ports Proxy Mode")..bold_off..font_off)
o.description = translate("Only Common Ports, Prevent BT/P2P Passing")
o:value("0", translate("Disable"))
-o:value("21 22 23 53 80 123 143 194 443 465 587 853 993 995 998 2052 2053 2082 2083 2086 2095 2096 5222 5228 5229 5230 8080 8443 8880 8888 8889", translate("Default Common Ports"))
+o:value("21 22 23 53 80 123 143 194 443 465 587 853 993 995 998 2052 2053 2082 2083 2086 2095 2096 2197 5222 5223 5228 5229 5230 8080 8443 8880 8888 8889", translate("Default Common Ports"))
o.default = 0
o.placeholder = translate("443 or 21-443, Use Space to Separate")
o:depends("en_mode", "redir-host")
@@ -538,6 +539,7 @@ function o.write(self, section, value)
fs.writefile("/etc/openclash/custom/openclash_custom_localnetwork_ipv4.list", value)
end
end
+ return true
end
o = s:taboption("traffic_control", Value, "chnroute_pass", translate("Chnroute Bypassed List"))
@@ -559,6 +561,7 @@ function o.write(self, section, value)
fs.writefile("/etc/openclash/custom/openclash_custom_chnroute_pass.list", value)
end
end
+ return true
end
--Stream Enhance
@@ -1366,6 +1369,7 @@ function o.write(self, section, value)
fs.writefile("/etc/openclash/custom/openclash_custom_localnetwork_ipv6.list", value)
end
end
+ return true
end
o = s:taboption("ipv6", Value, "chnroute6_pass", translate("Chnroute6 Bypassed List"))
@@ -1386,6 +1390,7 @@ function o.write(self, section, value)
fs.writefile("/etc/openclash/custom/openclash_custom_chnroute6_pass.list", value)
end
end
+ return true
end
---- version update
@@ -1410,6 +1415,7 @@ function o.write(self, section, value)
fs.writefile("/etc/openclash/custom/openclash_custom_firewall_rules.sh", value)
end
end
+ return true
end
---- debug
@@ -1417,52 +1423,9 @@ o = s:taboption("debug", DummyValue, "", nil)
o.template = "openclash/debug"
---- oixcloud
-o = s:taboption("oixcloud", Value, "oix_email")
-o.title = translate("Account Email Address")
-o.rmempty = true
-
-o = s:taboption("oixcloud", Value, "oix_passwd")
-o.title = translate("Account Password")
-o.password = true
-o.rmempty = true
-
-if fs.uci_get_config("config", "oix_token") then
- o = s:taboption("oixcloud", Flag, "oix_checkin")
- o.title = translate("Checkin")
- o.default = 0
- o.rmempty = true
-end
-
-o = s:taboption("oixcloud", Value, "oix_checkin_interval")
-o.title = translate("Checkin Interval (hour)")
-o:depends("oix_checkin", "1")
-o.default = "1"
-o.rmempty = true
-
-o = s:taboption("oixcloud", Value, "oix_checkin_multiple")
-o.title = translate("Checkin Multiple")
-o.datatype = "uinteger"
-o.default = "1"
-o:depends("oix_checkin", "1")
-o.rmempty = true
-o.description = font_green..bold_on..translate("Multiple Must Be a Positive Integer and No More Than 100")..bold_off..font_off
-function o.validate(self, value)
- if tonumber(value) < 1 then
- return "1"
- end
- if tonumber(value) > 100 then
- return "100"
- end
- return value
-end
-
-o = s:taboption("oixcloud", DummyValue, "oix_login", translate("Account Login"))
-o.template = "openclash/oix_login"
-if fs.uci_get_config("config", "oix_token") then
- o.value = font_green..bold_on..translate("Account logged in")..bold_off..font_off
-else
- o.value = font_red..bold_on..translate("Account not logged in")..bold_off..font_off
-end
+oixcloud_panel = s:taboption("oixcloud", DummyValue, "", nil)
+oixcloud_panel.template = "openclash/oix_login"
+oixcloud_panel.rawhtml = true
local t = {
{Commit, Apply}
diff --git a/luci-app-openclash/luasrc/openclash.lua b/luci-app-openclash/luasrc/openclash.lua
index b38bee381..919280e27 100644
--- a/luci-app-openclash/luasrc/openclash.lua
+++ b/luci-app-openclash/luasrc/openclash.lua
@@ -603,7 +603,19 @@ end
--- Returns the installed version of luci-app-openclash.
-- Supports both opkg and apk package managers.
-- @return String containing the version number, or "0" if not found
+-- NOTE: The module-level cache (_oc_version_cache) provides per-request
+-- deduplication. In OpenWrt LuCI CGI mode each HTTP request spawns a new Lua
+-- process, so cross-request caching is impossible.
+-- Since require() returns the same module instance (package.loaded), the
+-- first call caches the result and subsequent calls avoid redundant shell
+-- commands. No time-based expiration is needed — the version is immutable
+-- for the lifetime of a request.
+local _oc_version_cache = nil
+
function oc_version()
+ if _oc_version_cache ~= nil then
+ return _oc_version_cache
+ end
local v
if pkg_type() == "opkg" then
v = SYS.exec("rm -f /var/lock/opkg.lock && opkg status luci-app-openclash 2>/dev/null |grep '^Version:' |awk '{print $2}' |tr -d '\n'")
@@ -613,6 +625,7 @@ function oc_version()
if v == "" then
v = "0"
end
+ _oc_version_cache = v
return v
end
diff --git a/luci-app-openclash/luasrc/view/openclash/common.htm b/luci-app-openclash/luasrc/view/openclash/common.htm
new file mode 100644
index 000000000..97a4b63e7
--- /dev/null
+++ b/luci-app-openclash/luasrc/view/openclash/common.htm
@@ -0,0 +1,50 @@
+
diff --git a/luci-app-openclash/luasrc/view/openclash/config_edit.htm b/luci-app-openclash/luasrc/view/openclash/config_edit.htm
index 384c0c4cd..b92cd98e3 100644
--- a/luci-app-openclash/luasrc/view/openclash/config_edit.htm
+++ b/luci-app-openclash/luasrc/view/openclash/config_edit.htm
@@ -12,40 +12,22 @@
-
+
<%:You are editing the overwrite script, please note that some settings may cause the abnormal, be careful with the modification!%>
@@ -72,10 +54,6 @@
-
-
- <%:Loading config file...%>
-
@@ -89,7 +67,7 @@
<%:Ready%>
- <%:Press F11 for fullscreen, Esc to exit fullscreen, Ctrl+Mouse Wheel to zoom%>
+ <%:Press%> F11 <%:for fullscreen,%> Esc <%:to exit fullscreen,%> Ctrl + <%:Mouse Wheel%> <%:to zoom%>
@@ -97,22 +75,26 @@
+
+
+<%+openclash/common%>
\ No newline at end of file
diff --git a/luci-app-openclash/luasrc/view/openclash/config_editor.htm b/luci-app-openclash/luasrc/view/openclash/config_editor.htm
index bb7668827..0aaedf95c 100644
--- a/luci-app-openclash/luasrc/view/openclash/config_editor.htm
+++ b/luci-app-openclash/luasrc/view/openclash/config_editor.htm
@@ -2,91 +2,33 @@
local fs = require "luci.openclash"
local plugin_version = fs.oc_version()
%>
+
-
+
<%-
-local uci = require("luci.model.uci").cursor()
-local fs = require "luci.openclash"
-local conf = fs.uci_get_config("config", "config_path")
-if not conf then conf = "/etc/openclash/config/config.yaml" end
-local conf_name = fs.basename(conf)
-if not conf_name then conf_name = "config.yaml" end
-local sconf = "/etc/openclash/"..conf_name
local path_info = os.getenv("PATH_INFO") or ""
local sid = path_info:match("(cfg[0-9a-f]+)") or ""
-%>
-
-
-
<%:Modify Your Config file:%> <%=conf_name%> <%:Here, Except The Settings That Were Taken Over%>