mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-28 03:01:54 +08:00
💐 Sync 2026-06-25 20:51:16
This commit is contained in:
parent
3232f1d574
commit
7a3941cfa5
@ -11,8 +11,8 @@ PKG_ARCH_DDNSTO:=$(ARCH)
|
||||
|
||||
PKG_NAME:=ddnsto
|
||||
# use PKG_SOURCE_DATE instead of PKG_VERSION for compatible
|
||||
PKG_SOURCE_DATE:=4.2.0
|
||||
PKG_RELEASE:=2
|
||||
PKG_SOURCE_DATE:=4.2.2
|
||||
PKG_RELEASE:=3
|
||||
|
||||
# 构建类型:standard 或 lite
|
||||
DDNSTO_TYPE?=standard
|
||||
@ -36,15 +36,15 @@ PKG_SOURCE_VERSION:=$(ARCH_HEXCODE)
|
||||
|
||||
# 根据类型选择下载地址和文件名
|
||||
ifeq ($(DDNSTO_TYPE),lite)
|
||||
PKG_SOURCE:=$(PKG_NAME)-binary-lite-$(PKG_SOURCE_DATE).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/linkease/ddnsto-openwrt-package/raw/refs/heads/main/
|
||||
PKG_SOURCE:=$(PKG_NAME)-lite-$(PKG_SOURCE_DATE).tar.gz
|
||||
PKG_SOURCE_URL:=https://fw0.koolcenter.com/binary/ddnsto/linux-binary/
|
||||
PKG_HASH:=skip
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-binary-lite-$(PKG_SOURCE_DATE)
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-lite-$(PKG_SOURCE_DATE)
|
||||
else
|
||||
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_SOURCE_DATE).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/linkease/ddnsto-openwrt-package/raw/refs/heads/main/
|
||||
PKG_SOURCE:=$(PKG_NAME)-standard-$(PKG_SOURCE_DATE).tar.gz
|
||||
PKG_SOURCE_URL:=https://fw0.koolcenter.com/binary/ddnsto/linux-binary/
|
||||
PKG_HASH:=skip
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-binary-$(PKG_SOURCE_DATE)
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-standard-$(PKG_SOURCE_DATE)
|
||||
endif
|
||||
|
||||
PKG_BUILD_PARALLEL:=1
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=dnsproxy
|
||||
PKG_VERSION:=0.81.4
|
||||
PKG_RELEASE:=10
|
||||
PKG_VERSION:=0.82.0
|
||||
PKG_RELEASE:=11
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/AdguardTeam/dnsproxy/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=fan2go
|
||||
PKG_VERSION:=0.14.0
|
||||
PKG_RELEASE:=5
|
||||
PKG_VERSION:=0.15.0
|
||||
PKG_RELEASE:=6
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/markusressel/fan2go/tar.gz/$(PKG_VERSION)?
|
||||
|
||||
@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk
|
||||
LUCI_TITLE:=LuCI support for ddnsto
|
||||
LUCI_DEPENDS:=+ddnsto +block-mount
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_VERSION:=4.0.3-r1
|
||||
PKG_RELEASE:=2
|
||||
PKG_VERSION:=4.2.2-r1
|
||||
PKG_RELEASE:=3
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
|
||||
@ -70,6 +70,17 @@ local function method_not_allowed()
|
||||
write_json({ ok = false, error = "method not allowed" })
|
||||
end
|
||||
|
||||
local function conflict_request(msg, detail, code)
|
||||
local http = require "luci.http"
|
||||
http.status(409, msg or "conflict")
|
||||
write_json({
|
||||
ok = false,
|
||||
error = msg or "conflict",
|
||||
detail = detail or "",
|
||||
code = code or "conflict",
|
||||
})
|
||||
end
|
||||
|
||||
local function read_json_body()
|
||||
local http = require "luci.http"
|
||||
local jsonc = require "luci.jsonc"
|
||||
@ -123,6 +134,203 @@ local function fetch_device_id(index)
|
||||
return parse_device_id(get_command(cmd))
|
||||
end
|
||||
|
||||
local function shell_quote(value)
|
||||
local raw = tostring(value or "")
|
||||
return "'" .. raw:gsub("'", [['"'"']]) .. "'"
|
||||
end
|
||||
|
||||
local function read_file(path, binary)
|
||||
local mode = binary and "rb" or "r"
|
||||
local fp = io.open(path, mode)
|
||||
if not fp then
|
||||
return nil
|
||||
end
|
||||
local data = fp:read("*a")
|
||||
fp:close()
|
||||
return data
|
||||
end
|
||||
|
||||
local function remove_file(path)
|
||||
if path and #path > 0 then
|
||||
os.remove(path)
|
||||
end
|
||||
end
|
||||
|
||||
local function temp_path(suffix)
|
||||
local random = tostring(math.random(100000, 999999))
|
||||
return string.format("/tmp/ddnsto-luci-%d-%s%s", os.time(), random, suffix or "")
|
||||
end
|
||||
|
||||
local function file_exists(path)
|
||||
local fp = io.open(path, "rb")
|
||||
if fp then
|
||||
fp:close()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function service_running()
|
||||
local sys = require "luci.sys"
|
||||
local jsonc = require "luci.jsonc"
|
||||
local raw = sys.exec([[ubus call service list '{"name":"ddnsto"}' 2>/dev/null]]) or ""
|
||||
local ok, obj = pcall(jsonc.parse, raw)
|
||||
if ok and type(obj) == "table" and type(obj.ddnsto) == "table" and type(obj.ddnsto.instances) == "table" then
|
||||
for _, inst in pairs(obj.ddnsto.instances) do
|
||||
if type(inst) == "table" and inst.running == true then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function read_identity_state()
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local state = {
|
||||
router_id = "",
|
||||
identity_mode = "",
|
||||
device_uuid = "",
|
||||
}
|
||||
uci:foreach("ddnsto", "ddnsto", function(s)
|
||||
state.router_id = s.router_id or state.router_id
|
||||
state.identity_mode = s.identity_mode or state.identity_mode
|
||||
state.device_uuid = s.device_uuid or state.device_uuid
|
||||
end)
|
||||
return state
|
||||
end
|
||||
|
||||
local function run_init_action(action)
|
||||
local sys = require "luci.sys"
|
||||
return sys.call(string.format("/etc/init.d/ddnsto %s >/dev/null 2>&1", shell_quote(action)))
|
||||
end
|
||||
|
||||
local function run_capture(cmd)
|
||||
local sys = require "luci.sys"
|
||||
local stdout_path = temp_path(".stdout")
|
||||
local stderr_path = temp_path(".stderr")
|
||||
local wrapped = string.format("%s >%s 2>%s", cmd, shell_quote(stdout_path), shell_quote(stderr_path))
|
||||
local rc = sys.call(wrapped)
|
||||
local stdout = read_file(stdout_path, true) or ""
|
||||
local stderr = read_file(stderr_path, true) or ""
|
||||
remove_file(stdout_path)
|
||||
remove_file(stderr_path)
|
||||
return rc, stdout, stderr
|
||||
end
|
||||
|
||||
local function diagnostics_logs_via_cli(lines)
|
||||
local jsonc = require "luci.jsonc"
|
||||
local cmd = string.format("/usr/sbin/ddnsto diagnostics logs --tail %d", tonumber(lines) or 200)
|
||||
local rc, stdout = run_capture(cmd)
|
||||
if rc ~= 0 or stdout == "" then
|
||||
return nil
|
||||
end
|
||||
local ok, parsed = pcall(jsonc.parse, stdout)
|
||||
if not ok or type(parsed) ~= "table" or type(parsed.lines) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
return parsed.lines
|
||||
end
|
||||
|
||||
local function diagnostics_bundle_via_cli(output_path)
|
||||
local cmd = string.format(
|
||||
"/usr/sbin/ddnsto diagnostics bundle --output %s --reason %s",
|
||||
shell_quote(output_path),
|
||||
shell_quote("luci-support")
|
||||
)
|
||||
local rc, stdout, stderr = run_capture(cmd)
|
||||
if rc == 0 and file_exists(output_path) then
|
||||
return true, stdout
|
||||
end
|
||||
return false, stderr ~= "" and stderr or stdout
|
||||
end
|
||||
|
||||
local function diagnostics_bundle_via_http(output_path)
|
||||
local jsonc = require "luci.jsonc"
|
||||
local request_body = [[{"reason":"luci-support","tail":500}]]
|
||||
local request_cmd = string.format(
|
||||
"curl -fsS -X POST -H 'Content-Type: application/json' --data %s %s",
|
||||
shell_quote(request_body),
|
||||
shell_quote("http://127.0.0.1:18333/diagnostics/bundle")
|
||||
)
|
||||
local rc, stdout, stderr = run_capture(request_cmd)
|
||||
if rc ~= 0 or stdout == "" then
|
||||
return false, stderr ~= "" and stderr or stdout
|
||||
end
|
||||
|
||||
local ok, parsed = pcall(jsonc.parse, stdout)
|
||||
local download_path = ok and type(parsed) == "table" and parsed.downloadPath or nil
|
||||
if type(download_path) ~= "string" or download_path == "" then
|
||||
return false, "missing downloadPath"
|
||||
end
|
||||
|
||||
local download_cmd = string.format(
|
||||
"curl -fsS %s -o %s",
|
||||
shell_quote("http://127.0.0.1:18333" .. download_path),
|
||||
shell_quote(output_path)
|
||||
)
|
||||
local download_rc, _, download_stderr = run_capture(download_cmd)
|
||||
if download_rc == 0 and file_exists(output_path) then
|
||||
return true, "http"
|
||||
end
|
||||
|
||||
return false, download_stderr ~= "" and download_stderr or "bundle download failed"
|
||||
end
|
||||
|
||||
local function offline_diagnosis_via_http()
|
||||
local jsonc = require "luci.jsonc"
|
||||
local cmd = string.format("curl -fsS %s", shell_quote("http://127.0.0.1:18333/offline-diagnosis"))
|
||||
local rc, stdout, stderr = run_capture(cmd)
|
||||
if rc ~= 0 or stdout == "" then
|
||||
return nil, stderr ~= "" and stderr or stdout
|
||||
end
|
||||
|
||||
local ok, parsed = pcall(jsonc.parse, stdout)
|
||||
if not ok or type(parsed) ~= "table" then
|
||||
return nil, "invalid offline diagnosis response"
|
||||
end
|
||||
return parsed, "http_status"
|
||||
end
|
||||
|
||||
local function offline_diagnosis_via_cli()
|
||||
local jsonc = require "luci.jsonc"
|
||||
local rc, stdout, stderr = run_capture("/usr/sbin/ddnsto offline-diagnosis --json")
|
||||
if rc ~= 0 or stdout == "" then
|
||||
return nil, stderr ~= "" and stderr or stdout
|
||||
end
|
||||
|
||||
local ok, parsed = pcall(jsonc.parse, stdout)
|
||||
if not ok or type(parsed) ~= "table" then
|
||||
return nil, "invalid offline diagnosis response"
|
||||
end
|
||||
return parsed, "cli_fallback"
|
||||
end
|
||||
|
||||
local function stream_download(path, filename, content_type)
|
||||
local http = require "luci.http"
|
||||
local fp = io.open(path, "rb")
|
||||
if not fp then
|
||||
http.status(500, "open failed")
|
||||
http.prepare_content("application/json")
|
||||
http.write('{"ok":false,"error":"bundle open failed"}')
|
||||
return
|
||||
end
|
||||
|
||||
http.header("Content-Disposition", string.format('attachment; filename="%s"', filename))
|
||||
http.header("Cache-Control", "no-store")
|
||||
http.prepare_content(content_type or "application/octet-stream")
|
||||
|
||||
while true do
|
||||
local chunk = fp:read(8192)
|
||||
if not chunk then
|
||||
break
|
||||
end
|
||||
http.write(chunk)
|
||||
end
|
||||
|
||||
fp:close()
|
||||
end
|
||||
|
||||
local function param(body, key)
|
||||
local http = require "luci.http"
|
||||
if type(body) == "table" and body[key] ~= nil then
|
||||
@ -287,6 +495,7 @@ function index()
|
||||
-- entry({"admin", "ddnsto_dev"}, call("action_ddnsto_dev"), _("DDNSTO (Dev)"), 99).leaf = true
|
||||
|
||||
entry({"admin", "services", "ddnsto", "api", "config"}, call("api_config")).leaf = true
|
||||
entry({"admin", "services", "ddnsto", "api", "migrate_identity"}, call("api_migrate_identity")).leaf = true
|
||||
entry({"admin", "services", "ddnsto", "api", "service"}, call("api_service")).leaf = true
|
||||
entry({"admin", "services", "ddnsto", "api", "run"}, call("api_run")).leaf = true
|
||||
entry({"admin", "services", "ddnsto", "api", "restart"}, call("api_restart")).leaf = true
|
||||
@ -296,6 +505,8 @@ function index()
|
||||
entry({"admin", "services", "ddnsto", "api", "connectivity"}, call("api_connectivity")).leaf = true
|
||||
entry({"admin", "services", "ddnsto", "api", "status"}, call("api_status")).leaf = true
|
||||
entry({"admin", "services", "ddnsto", "api", "logs"}, call("api_logs")).leaf = true
|
||||
entry({"admin", "services", "ddnsto", "api", "offline_diagnosis"}, call("api_offline_diagnosis")).leaf = true
|
||||
entry({"admin", "services", "ddnsto", "api", "support_bundle"}, call("api_support_bundle")).leaf = true
|
||||
end
|
||||
|
||||
function action_page()
|
||||
@ -427,6 +638,120 @@ function api_config()
|
||||
write_json({ ok = true })
|
||||
end
|
||||
|
||||
function api_migrate_identity()
|
||||
local http = require "luci.http"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local method = http.getenv("REQUEST_METHOD") or ""
|
||||
|
||||
if method ~= "POST" then
|
||||
method_not_allowed()
|
||||
return
|
||||
end
|
||||
|
||||
if not require_csrf() then return end
|
||||
|
||||
local body = read_json_body()
|
||||
local enabled = param(body, "enabled")
|
||||
local ddnsto_token = param(body, "ddnsto_token")
|
||||
local index = param(body, "index")
|
||||
local logger = param(body, "logger")
|
||||
local feat_enabled = param(body, "feat_enabled")
|
||||
local feat_port = param(body, "feat_port")
|
||||
local feat_username = param(body, "feat_username")
|
||||
local feat_password = param(body, "feat_password")
|
||||
local feat_disk_path_selected = param(body, "feat_disk_path_selected")
|
||||
local confirm_reidentity = param(body, "confirm_reidentity")
|
||||
|
||||
if confirm_reidentity ~= "1" then
|
||||
return bad_request("missing identity migration confirmation")
|
||||
end
|
||||
if enabled and not is_bool01(enabled) then return bad_request("bad enabled") end
|
||||
if logger and not is_bool01(logger) then return bad_request("bad logger") end
|
||||
if feat_enabled and not is_bool01(feat_enabled) then return bad_request("bad feat_enabled") end
|
||||
if ddnsto_token ~= nil and has_space(ddnsto_token) then
|
||||
return bad_request("令牌勿包含空格")
|
||||
end
|
||||
if not is_uint(index) then
|
||||
return bad_request("请填写正确的设备编号,仅允许数字")
|
||||
end
|
||||
local index_num = tonumber(index)
|
||||
if index_num < 0 or index_num > 99 then
|
||||
return bad_request("请填写正确的设备编号,仅允许数字")
|
||||
end
|
||||
|
||||
local enabled_on = enabled == "1"
|
||||
local feat_on = feat_enabled == "1"
|
||||
if enabled_on and is_empty(ddnsto_token) then
|
||||
return bad_request("请填写正确用户Token(令牌)")
|
||||
end
|
||||
if feat_on then
|
||||
if not is_uint(feat_port) then
|
||||
return bad_request("请填写正确的端口")
|
||||
end
|
||||
local port_num = tonumber(feat_port)
|
||||
if not port_num or port_num == 0 or port_num > 65535 then
|
||||
return bad_request("请填写正确的端口")
|
||||
end
|
||||
if is_empty(feat_username) then
|
||||
return bad_request("请填写授权用户名")
|
||||
end
|
||||
if has_space(feat_username) then
|
||||
return bad_request("用户名请勿包含空格")
|
||||
end
|
||||
if is_empty(feat_password) then
|
||||
return bad_request("请填写授权用户密码")
|
||||
end
|
||||
if has_space(feat_password) then
|
||||
return bad_request("用户密码请勿包含空格")
|
||||
end
|
||||
if is_empty(feat_disk_path_selected) then
|
||||
return bad_request("请填写共享磁盘路径")
|
||||
end
|
||||
end
|
||||
|
||||
local old_identity = read_identity_state()
|
||||
local current = read_config()
|
||||
local old_index = tostring(current.index or "0")
|
||||
local identity_missing = is_empty(old_identity.router_id)
|
||||
if tostring(index) == old_index and not identity_missing then
|
||||
return conflict_request("identity migration not required", "index unchanged", "identity_migration_not_required")
|
||||
end
|
||||
local sid = ensure_ddnsto_section()
|
||||
|
||||
run_init_action("stop")
|
||||
|
||||
if enabled then uci:set("ddnsto", sid, "enabled", enabled) end
|
||||
if ddnsto_token ~= nil then uci:set("ddnsto", sid, "token", ddnsto_token) end
|
||||
if index then uci:set("ddnsto", sid, "index", index) end
|
||||
if logger then uci:set("ddnsto", sid, "logger", logger) end
|
||||
if feat_enabled then uci:set("ddnsto", sid, "feat_enabled", feat_enabled) end
|
||||
if feat_port then uci:set("ddnsto", sid, "feat_port", feat_port) end
|
||||
if feat_username then uci:set("ddnsto", sid, "feat_username", feat_username) end
|
||||
if feat_password then uci:set("ddnsto", sid, "feat_password", feat_password) end
|
||||
if feat_disk_path_selected then uci:set("ddnsto", sid, "feat_disk_path_selected", feat_disk_path_selected) end
|
||||
|
||||
uci:delete("ddnsto", sid, "router_id")
|
||||
uci:delete("ddnsto", sid, "identity_mode")
|
||||
uci:delete("ddnsto", sid, "device_uuid")
|
||||
uci:commit("ddnsto")
|
||||
|
||||
local restart_rc = 0
|
||||
if enabled ~= "0" then
|
||||
restart_rc = run_init_action("start")
|
||||
end
|
||||
|
||||
write_json({
|
||||
ok = true,
|
||||
data = {
|
||||
old_index = old_index,
|
||||
new_index = tostring(index),
|
||||
old_router_id = old_identity.router_id or "",
|
||||
service_started = restart_rc == 0 and enabled ~= "0" or false,
|
||||
message = "设备身份已重置,服务正在使用新的设备编号重新生成设备 ID",
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
-- ==========
|
||||
-- API: service
|
||||
-- ==========
|
||||
@ -696,6 +1021,12 @@ function api_logs()
|
||||
if lines < 10 then lines = 10 end
|
||||
if lines > 2000 then lines = 2000 end
|
||||
|
||||
local cli_lines = diagnostics_logs_via_cli(lines)
|
||||
if cli_lines ~= nil then
|
||||
write_json({ ok = true, data = { lines = cli_lines, total = #cli_lines, source = "diagnostics_cli" } })
|
||||
return
|
||||
end
|
||||
|
||||
local cmd = string.format("logread 2>/dev/null | grep -E 'ddnsto|ddnstod' | tail -n %d", lines)
|
||||
local out = sys.exec(cmd) or ""
|
||||
local arr = {}
|
||||
@ -706,7 +1037,69 @@ function api_logs()
|
||||
end
|
||||
end
|
||||
|
||||
write_json({ ok = true, data = { lines = arr, total = #arr } })
|
||||
write_json({ ok = true, data = { lines = arr, total = #arr, source = "logread_fallback" } })
|
||||
end
|
||||
|
||||
function api_offline_diagnosis()
|
||||
local http = require "luci.http"
|
||||
local method = http.getenv("REQUEST_METHOD") or ""
|
||||
|
||||
if method ~= "GET" then
|
||||
method_not_allowed()
|
||||
return
|
||||
end
|
||||
|
||||
if not service_running() then
|
||||
write_json({
|
||||
ok = false,
|
||||
error = "offline diagnosis unavailable",
|
||||
detail = "ddnsto not running",
|
||||
code = "ddnsto_not_running",
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local data, source = offline_diagnosis_via_http()
|
||||
if data ~= nil then
|
||||
write_json({ ok = true, data = data, source = source })
|
||||
return
|
||||
end
|
||||
|
||||
write_json({ ok = false, error = "offline diagnosis unavailable" })
|
||||
end
|
||||
|
||||
function api_support_bundle()
|
||||
local http = require "luci.http"
|
||||
local method = http.getenv("REQUEST_METHOD") or ""
|
||||
|
||||
if method ~= "GET" then
|
||||
method_not_allowed()
|
||||
return
|
||||
end
|
||||
|
||||
if not service_running() then
|
||||
http.status(409, "ddnsto not running")
|
||||
write_json({
|
||||
ok = false,
|
||||
error = "diagnostics bundle unavailable",
|
||||
detail = "ddnsto not running",
|
||||
code = "ddnsto_not_running",
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local output_path = temp_path(".zip")
|
||||
local ok, err = diagnostics_bundle_via_http(output_path)
|
||||
if not ok then
|
||||
remove_file(output_path)
|
||||
http.status(500, "bundle failed")
|
||||
write_json({ ok = false, error = "diagnostics bundle unavailable", detail = err or "" })
|
||||
return
|
||||
end
|
||||
|
||||
local filename = string.format("ddnsto-openwrt-support-%d.zip", os.time())
|
||||
stream_download(output_path, filename, "application/zip")
|
||||
remove_file(output_path)
|
||||
end
|
||||
|
||||
function action_ddnsto_dev()
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall2
|
||||
PKG_VERSION:=26.6.16
|
||||
PKG_RELEASE:=56
|
||||
PKG_RELEASE:=57
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
|
||||
@ -9,9 +9,11 @@ s = m:section(TypedSection, "global_rules", translate("Rule status"))
|
||||
s.anonymous = true
|
||||
|
||||
o = s:option(Value, "geoip_url", translate("GeoIP Update URL"))
|
||||
o:value("https://github.com/Loyalsoldier/geoip/releases/latest/download/geoip.dat", translate("Loyalsoldier/geoip"))
|
||||
o:value("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", translate("Loyalsoldier/geoip"))
|
||||
o:value("https://gh-proxy.org/https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", translate("Loyalsoldier/geoip (gh-proxy)"))
|
||||
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat", translate("Loyalsoldier/geoip (CDN)"))
|
||||
o:value("https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.dat", translate("MetaCubeX/geoip"))
|
||||
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/geoip.dat", translate("Loyalsoldier/geoip (CDN)"))
|
||||
o:value("https://gh-proxy.org/https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geoip.dat", translate("MetaCubeX/geoip (gh-proxy)"))
|
||||
o:value("https://cdn.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat", translate("MetaCubeX/geoip (CDN)"))
|
||||
o:value("https://github.com/Chocolate4U/Iran-v2ray-rules/releases/latest/download/geoip.dat", translate("Chocolate4U/geoip (IR)"))
|
||||
o:value("https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat", translate("runetfreedom/geoip (RU)"))
|
||||
@ -19,8 +21,10 @@ o.default = o.keylist[1]
|
||||
|
||||
o = s:option(Value, "geosite_url", translate("Geosite Update URL"))
|
||||
o:value("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", translate("Loyalsoldier/geosite"))
|
||||
o:value("https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geosite.dat", translate("MetaCubeX/geosite"))
|
||||
o:value("https://gh-proxy.org/https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", translate("Loyalsoldier/geosite (gh-proxy)"))
|
||||
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat", translate("Loyalsoldier/geosite (CDN)"))
|
||||
o:value("https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geosite.dat", translate("MetaCubeX/geosite"))
|
||||
o:value("https://gh-proxy.org/https://github.com/MetaCubeX/meta-rules-dat/releases/latest/download/geosite.dat", translate("MetaCubeX/geosite (gh-proxy)"))
|
||||
o:value("https://cdn.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat", translate("MetaCubeX/geosite (CDN)"))
|
||||
o:value("https://github.com/Chocolate4U/Iran-v2ray-rules/releases/latest/download/geosite.dat", translate("Chocolate4U/geosite (IR)"))
|
||||
o:value("https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat", translate("runetfreedom/geosite (RU)"))
|
||||
|
||||
@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-run
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=6
|
||||
PKG_RELEASE:=7
|
||||
|
||||
LUCI_TITLE:=LuCI support for running installers and packages
|
||||
LUCI_DESCRIPTION:=Upload and execute .run, .sh, .ipk and .apk files from LuCI.
|
||||
|
||||
@ -28,7 +28,11 @@
|
||||
"args_hint": "您可以输入脚本参数或留空",
|
||||
"cancel": "取消",
|
||||
"confirm": "确定",
|
||||
"auto_cleaned": "执行完毕,已自动清理临时文件。"
|
||||
"auto_cleaned": "执行完毕,已自动清理临时文件。",
|
||||
"download_run": "下载并执行 .run",
|
||||
"download_url": "请输入 .run 文件的下载地址",
|
||||
"downloading": "正在下载...",
|
||||
"only_run": "仅支持 .run 文件下载"
|
||||
},
|
||||
"en": {
|
||||
"title": "Run Installer",
|
||||
@ -59,6 +63,10 @@
|
||||
"args_hint": "You can enter script arguments or leave it blank",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "OK",
|
||||
"auto_cleaned": "Execution complete, temporary files cleaned automatically."
|
||||
"auto_cleaned": "Execution complete, temporary files cleaned automatically.",
|
||||
"download_run": "Download and execute .run",
|
||||
"download_url": "Enter .run file download URL",
|
||||
"downloading": "Downloading...",
|
||||
"only_run": "Only .run files are supported"
|
||||
}
|
||||
}
|
||||
@ -89,7 +89,11 @@ function getDefaultText(key) {
|
||||
'args_hint': '您可以输入脚本参数或留空',
|
||||
'cancel': '取消',
|
||||
'confirm': '确定',
|
||||
'auto_cleaned': '执行完毕,已自动清理临时文件。'
|
||||
'auto_cleaned': '执行完毕,已自动清理临时文件。',
|
||||
'download_run': '下载并执行 .run',
|
||||
'download_url': '请输入 .run 文件的下载地址',
|
||||
'downloading': '正在下载...',
|
||||
'only_run': '仅支持 .run 文件下载'
|
||||
};
|
||||
return defaults[key] || null;
|
||||
}
|
||||
@ -144,6 +148,12 @@ var cleanup = rpc.declare({
|
||||
method: 'cleanup'
|
||||
});
|
||||
|
||||
var downloadRun = rpc.declare({
|
||||
object: 'luci-app-run',
|
||||
method: 'download_run',
|
||||
params: ['url']
|
||||
});
|
||||
|
||||
function formatBytes(size) {
|
||||
if (size >= 1024 * 1024)
|
||||
return '%.1f MiB'.format(size / 1024 / 1024);
|
||||
@ -290,6 +300,20 @@ return view.extend({
|
||||
}
|
||||
}, [_('choose_apk')]);
|
||||
|
||||
var downloadButton = E('button', {
|
||||
class: 'cbi-button cbi-button-action run-btn',
|
||||
style: 'margin-left:10px;background:#E65100!important;background-color:#E65100!important;background-image:none!important;color:#fff!important;border-color:#E65100!important;box-shadow:none!important;text-shadow:none!important;opacity:1!important',
|
||||
click: function (ev) {
|
||||
ev.preventDefault();
|
||||
self.showDownloadDialog(function (url) {
|
||||
if (url !== null) {
|
||||
log.textContent = '';
|
||||
self.startDownload(runButton, state, url.trim());
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [_('download_run')]);
|
||||
|
||||
var runButton = E('button', {
|
||||
class: 'cbi-button cbi-button-action run-btn',
|
||||
disabled: true,
|
||||
@ -354,7 +378,7 @@ return view.extend({
|
||||
E('h3', [_('upload_title')]),
|
||||
E('p', [state]),
|
||||
E('p', [pickButton, ipkButton, apkButton]),
|
||||
E('p', { style: 'margin-top:10px' }, [runButton, cleanButton]),
|
||||
E('p', { style: 'margin-top:10px' }, [downloadButton, runButton, cleanButton]),
|
||||
progress,
|
||||
fileInput,
|
||||
ipkInput,
|
||||
@ -543,6 +567,81 @@ return view.extend({
|
||||
document.getElementById('run-args-dialog-input').focus();
|
||||
},
|
||||
|
||||
showDownloadDialog: function (callback) {
|
||||
var dialog = E('div', {
|
||||
style: 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:9999'
|
||||
}, [
|
||||
E('div', {
|
||||
style: 'background:#fff;border-radius:8px;padding:20px;width:400px;box-shadow:0 4px 20px rgba(0,0,0,0.3)'
|
||||
}, [
|
||||
E('input', {
|
||||
type: 'text',
|
||||
placeholder: _('download_url'),
|
||||
style: 'width:100%;padding:10px;margin-bottom:15px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;font-size:14px',
|
||||
id: 'run-download-url-input'
|
||||
}),
|
||||
E('div', {
|
||||
style: 'display:flex;justify-content:flex-end;gap:10px'
|
||||
}, [
|
||||
E('button', {
|
||||
class: 'cbi-button cbi-button-reset',
|
||||
style: 'padding:8px 20px;text-transform:none',
|
||||
click: function () {
|
||||
document.body.removeChild(dialog);
|
||||
callback(null);
|
||||
}
|
||||
}, [_('cancel')]),
|
||||
E('button', {
|
||||
class: 'cbi-button cbi-button-action',
|
||||
style: 'padding:8px 20px;text-transform:none',
|
||||
click: function () {
|
||||
var url = document.getElementById('run-download-url-input').value.trim();
|
||||
if (!url) {
|
||||
document.body.removeChild(dialog);
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var filename = url.split('/').pop().split('?')[0];
|
||||
if (!filename.match(/\.run$/i)) {
|
||||
ui.addNotification(null, E('p', [_('only_run')]), 'danger');
|
||||
return;
|
||||
}
|
||||
|
||||
document.body.removeChild(dialog);
|
||||
callback(url);
|
||||
}
|
||||
}, [_('confirm')])
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
document.body.appendChild(dialog);
|
||||
document.getElementById('run-download-url-input').focus();
|
||||
},
|
||||
|
||||
startDownload: function (runButton, state, url) {
|
||||
var self = this;
|
||||
|
||||
runButton.disabled = true;
|
||||
state.textContent = _('downloading');
|
||||
self.prevRunning = false;
|
||||
self.autoCleanType = null;
|
||||
|
||||
return downloadRun(url).then(function (res) {
|
||||
if (res && res.error) {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
|
||||
self.logOffset = 0;
|
||||
self.prevRunning = true;
|
||||
state.textContent = _('started', res.pid);
|
||||
}).catch(function (err) {
|
||||
runButton.disabled = false;
|
||||
ui.addNotification(null, E('p', [err.message || err]), 'danger');
|
||||
});
|
||||
},
|
||||
|
||||
refreshLog: function (log, state) {
|
||||
var self = this;
|
||||
|
||||
|
||||
@ -75,11 +75,13 @@ write_state() {
|
||||
local file="$2"
|
||||
local log="$3"
|
||||
local started="$4"
|
||||
local type="$5"
|
||||
{
|
||||
printf 'PID=%s\n' "$pid"
|
||||
printf 'FILE=%s\n' "$file"
|
||||
printf 'LOG=%s\n' "$log"
|
||||
printf 'STARTED=%s\n' "$started"
|
||||
printf 'TYPE=%s\n' "$type"
|
||||
} > "$STATE_FILE"
|
||||
}
|
||||
|
||||
@ -136,6 +138,9 @@ method_list() {
|
||||
"read_log": {
|
||||
"offset": "Integer"
|
||||
},
|
||||
"download_run": {
|
||||
"url": "String"
|
||||
},
|
||||
"cleanup": {},
|
||||
"version": {},
|
||||
"capabilities": {}
|
||||
@ -236,6 +241,86 @@ upload_finish() {
|
||||
json_dump
|
||||
}
|
||||
|
||||
download_run() {
|
||||
load_input
|
||||
get_string url
|
||||
|
||||
[ -n "$url" ] || { json_error "Missing URL"; return; }
|
||||
|
||||
local filename id dir file log work pid now
|
||||
|
||||
# Extract filename from URL - get everything after the last /
|
||||
filename="${url##*/}"
|
||||
# Remove query parameters if any
|
||||
filename="${filename%%\?*}"
|
||||
|
||||
case "$filename" in
|
||||
*.run) ;;
|
||||
*) json_error "Only .run files are supported for download"; return ;;
|
||||
esac
|
||||
|
||||
read_state
|
||||
if [ -n "$PID" ] && ! is_running "$PID"; then
|
||||
rm -f "$STATE_FILE"
|
||||
unset PID
|
||||
fi
|
||||
|
||||
if is_running "$PID"; then
|
||||
json_error "Another installer is already running"
|
||||
return
|
||||
fi
|
||||
|
||||
id="download_$(date +%s)"
|
||||
dir="$RUN_DIR/$id"
|
||||
file="$dir/$filename"
|
||||
log="$dir/output.log"
|
||||
|
||||
mkdir -p "$dir" || { json_error "Unable to create download directory"; return; }
|
||||
|
||||
now="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
|
||||
(
|
||||
cd "$dir" || exit 1
|
||||
printf 'luci-app-run: started %s\n' "$now"
|
||||
printf 'luci-app-run: downloading %s\n\n' "$url"
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -L -o "$filename" "$url"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -O "$filename" "$url"
|
||||
else
|
||||
printf 'luci-app-run: error: neither curl nor wget is available\n'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rc=$?
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
printf '\nluci-app-run: download failed with status %s at %s\n' "$rc" "$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
exit "$rc"
|
||||
fi
|
||||
|
||||
printf '\nluci-app-run: download completed at %s\n' "$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
printf '\nluci-app-run: executing %s\n\n' "$file"
|
||||
|
||||
chmod 700 "$file" 2>/dev/null || true
|
||||
|
||||
"$file"
|
||||
rc=$?
|
||||
printf '\nluci-app-run: exited with status %s at %s\n' "$rc" "$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
exit "$rc"
|
||||
) > "$log" 2>&1 &
|
||||
|
||||
pid="$!"
|
||||
write_state "$pid" "$file" "$log" "$now" "download"
|
||||
|
||||
json_init
|
||||
json_add_int code 0
|
||||
json_add_int pid "$pid"
|
||||
json_add_string log "$log"
|
||||
json_add_string filename "$filename"
|
||||
json_dump
|
||||
}
|
||||
|
||||
run_installer() {
|
||||
load_input
|
||||
get_string id
|
||||
@ -317,7 +402,7 @@ run_installer() {
|
||||
esac
|
||||
|
||||
pid="$!"
|
||||
write_state "$pid" "$file" "$log" "$now"
|
||||
write_state "$pid" "$file" "$log" "$now" "upload"
|
||||
|
||||
json_init
|
||||
json_add_int code 0
|
||||
@ -433,6 +518,7 @@ case "$1" in
|
||||
upload_chunk) upload_chunk ;;
|
||||
upload_finish) upload_finish ;;
|
||||
run) run_installer ;;
|
||||
download_run) download_run ;;
|
||||
status) status ;;
|
||||
read_log) read_log ;;
|
||||
cleanup) cleanup ;;
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
"upload_chunk",
|
||||
"upload_finish",
|
||||
"run",
|
||||
"download_run",
|
||||
"cleanup"
|
||||
]
|
||||
}
|
||||
|
||||
@ -17,6 +17,12 @@ var callFileList = rpc.declare({
|
||||
}
|
||||
});
|
||||
|
||||
var callXmmPorts = rpc.declare({
|
||||
object: 'luci.proto.xmm',
|
||||
method: 'ports',
|
||||
expect: { ports: [] }
|
||||
});
|
||||
|
||||
network.registerPatternVirtual(/^xmm-.+$/);
|
||||
network.registerErrorCode('NO_DEVICE_SUPPORT', _('Unsupported modem'));
|
||||
network.registerErrorCode('NO_PORT_FOUND', _('No control device specified'));
|
||||
@ -63,7 +69,15 @@ return network.registerProtocol('xmm', {
|
||||
o.ucioption = 'device';
|
||||
o.rmempty = false;
|
||||
o.load = function(section_id) {
|
||||
return callFileList('/dev/').then(L.bind(function(devices) {
|
||||
return callXmmPorts().then(L.bind(function(devices) {
|
||||
if (!devices || !devices.length) {
|
||||
/* Fallback. Show all ports */
|
||||
return callFileList('/dev/').then(L.bind(function(devs) {
|
||||
for (var i = 0; i < devs.length; i++)
|
||||
this.value(devs[i]);
|
||||
return form.Value.prototype.load.apply(this, [section_id]);
|
||||
}, this));
|
||||
}
|
||||
for (var i = 0; i < devices.length; i++)
|
||||
this.value(devices[i]);
|
||||
return form.Value.prototype.load.apply(this, [section_id]);
|
||||
@ -123,7 +137,5 @@ return network.registerProtocol('xmm', {
|
||||
_('Use DNS servers advertised by peer'),
|
||||
_('If unchecked, the advertised DNS server addresses are ignored'));
|
||||
o.default = o.enabled;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
51
luci-proto-xmm/root/usr/libexec/rpcd/luci.proto.xmm
Executable file
51
luci-proto-xmm/root/usr/libexec/rpcd/luci.proto.xmm
Executable file
@ -0,0 +1,51 @@
|
||||
#!/bin/sh
|
||||
|
||||
# "vidpid" "interface_hex"
|
||||
is_xmm_port() {
|
||||
local vidpid="$1"
|
||||
local iface="$2"
|
||||
case "${vidpid}:${iface}" in
|
||||
8087095a:00|8087095a:02) return 0 ;;
|
||||
0e8d7126:04) return 0 ;;
|
||||
0e8d7127:06) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
list_ports() {
|
||||
local first=1
|
||||
printf '{"ports":['
|
||||
for dev in $(ls /sys/class/tty/ 2>/dev/null | grep -E '^ttyACM|^ttyUSB'); do
|
||||
local real
|
||||
real=$(readlink -f "/sys/class/tty/$dev/device" 2>/dev/null) || continue
|
||||
local iface_path="$real"
|
||||
while [ -n "$iface_path" ] && [ "$iface_path" != "/" ]; do
|
||||
[ -f "$iface_path/bInterfaceNumber" ] && break
|
||||
iface_path=$(dirname "$iface_path")
|
||||
done
|
||||
[ -f "$iface_path/bInterfaceNumber" ] || continue
|
||||
local usb_path
|
||||
usb_path=$(dirname "$iface_path")
|
||||
[ -f "$usb_path/idVendor" ] || continue
|
||||
local vid pid iface
|
||||
vid=$(cat "$usb_path/idVendor" 2>/dev/null)
|
||||
pid=$(cat "$usb_path/idProduct" 2>/dev/null)
|
||||
iface=$(cat "$iface_path/bInterfaceNumber" 2>/dev/null)
|
||||
is_xmm_port "${vid}${pid}" "$iface" || continue
|
||||
[ $first -eq 1 ] || printf ','
|
||||
printf '"/dev/%s"' "$dev"
|
||||
first=0
|
||||
done
|
||||
printf ']}'
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
list)
|
||||
printf '{"ports":{}}'
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
ports) list_ports ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
15
luci-proto-xmm/root/usr/share/rpcd/acl.d/luci-proto-xmm.json
Normal file
15
luci-proto-xmm/root/usr/share/rpcd/acl.d/luci-proto-xmm.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"luci-proto-xmm": {
|
||||
"description": "Grant access to XMM modem protocol resources",
|
||||
"read": {
|
||||
"file": {
|
||||
"/dev/ttyACM*": [ "list" ],
|
||||
"/dev/ttyUSB*": [ "list" ]
|
||||
},
|
||||
"ubus": {
|
||||
"file": [ "list" ],
|
||||
"luci.proto.xmm": [ "ports" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk
|
||||
|
||||
PKG_NAME:=natflow
|
||||
PKG_VERSION:=20260531
|
||||
PKG_RELEASE:=21
|
||||
PKG_RELEASE:=22
|
||||
|
||||
PKG_SOURCE:=$(PKG_VERSION).tar.xz
|
||||
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_VERSION:=486646d3f0fc136db0a9e6caebe290400d18a8c8
|
||||
PKG_SOURCE_VERSION:=215aaea60b314aa3373a513a7d36cdbe37be3d97
|
||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=sing-box
|
||||
PKG_VERSION:=1.13.13
|
||||
PKG_RELEASE:=20
|
||||
PKG_VERSION:=1.13.14
|
||||
PKG_RELEASE:=21
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/SagerNet/sing-box/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
Loading…
Reference in New Issue
Block a user