diff --git a/beszel-agent/Makefile b/beszel-agent/Makefile index c2ff42d48..7e3965155 100644 --- a/beszel-agent/Makefile +++ b/beszel-agent/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=beszel PKG_VERSION:=0.21.0 -PKG_RELEASE:=12 +PKG_RELEASE:=13 PKG_SOURCE:=beszel-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/henrygd/beszel/tar.gz/v$(PKG_VERSION)? diff --git a/beszel-agent/patches/001-implement-openwrt-ap-wifi-collection.patch b/beszel-agent/patches/001-implement-openwrt-ap-wifi-collection.patch index 441d3cc23..dd551e297 100644 --- a/beszel-agent/patches/001-implement-openwrt-ap-wifi-collection.patch +++ b/beszel-agent/patches/001-implement-openwrt-ap-wifi-collection.patch @@ -1,14 +1,14 @@ -From 128555126155413ce32b134435c425ad9bb24501 Mon Sep 17 00:00:00 2001 +From 70f6bfc114b419ad1d1d999c4e74906c1ebc3819 Mon Sep 17 00:00:00 2001 From: Jackie Han -Date: Wed, 14 Oct 2026 07:40:03 +0800 -Subject: [PATCH] Refactor collect function to handle wireless devices +Date: Wed, 21 Oct 2026 22:20:40 +0800 +Subject: [PATCH] Refactor WiFi collection logic for Linux --- - agent/wifi/wifi_linux.go | 111 ++++++++++++++++++++++++++++++++++++--- - 1 file changed, 105 insertions(+), 6 deletions(-) + agent/wifi/wifi_linux.go | 164 ++++++++++++++++++++++++++++++++++++--- + 1 file changed, 152 insertions(+), 12 deletions(-) diff --git a/agent/wifi/wifi_linux.go b/agent/wifi/wifi_linux.go -index 926d658fe..ea42b1f2b 100644 +index 926d658fef..1ba134e69c 100644 --- a/agent/wifi/wifi_linux.go +++ b/agent/wifi/wifi_linux.go @@ -5,8 +5,12 @@ package wifi @@ -24,36 +24,38 @@ index 926d658fe..ea42b1f2b 100644 "time" "github.com/henrygd/beszel/internal/entities/system" -@@ -99,15 +103,110 @@ func hasWirelessDevice(dir string) bool { +@@ -99,36 +103,172 @@ func hasWirelessDevice(dir string) bool { } func collect(ctx context.Context) map[string]system.WiFi { - if !hasWirelessDevice(wiphyClassDir) { -- return nil -+ results := make(map[string]system.WiFi) -+ ++ var client linuxClient + if hasWirelessDevice(wiphyClassDir) { -+ if client, err := newNL80211Client(); err == nil { -+ for k, v := range collectLinux(ctx, client) { -+ results[k] = v -+ } -+ client.Close() ++ if c, err := newNL80211Client(); err == nil { ++ client = c ++ defer client.Close() + } - } -- client, err := newNL80211Client() -- if err != nil { ++ } ++ return collectLinuxWithAP(ctx, client, runIwinfo) ++} + -+ if _, err := exec.LookPath("iwinfo"); err == nil { -+ for k, v := range collectOpenWrtAP(ctx) { -+ results[k] = v -+ } ++type iwinfoCommand func(context.Context, ...string) ([]byte, error) ++ ++func runIwinfo(ctx context.Context, args ...string) ([]byte, error) { ++ return exec.CommandContext(ctx, "iwinfo", args...).Output() ++} ++ ++func collectLinuxWithAP(ctx context.Context, client linuxClient, iwinfo iwinfoCommand) map[string]system.WiFi { ++ results, aps := collectLinux(ctx, client) ++ for k, v := range collectOpenWrtAP(ctx, aps, iwinfo) { ++ results[k] = v + } + + if len(results) == 0 { return nil } -- defer client.Close() -- return collectLinux(ctx, client) +- client, err := newNL80211Client() +- if err != nil { + return results +} + @@ -61,7 +63,7 @@ index 926d658fe..ea42b1f2b 100644 + iwinfoIfaceRe = regexp.MustCompile(`(?m)^(\S+)[ \t]+ESSID:`) + iwinfoModeRe = regexp.MustCompile(`Mode:[ \t]*Master`) + iwinfoSSIDRe = regexp.MustCompile(`ESSID:\s*"([^"]+)"`) -+ iwinfoAssocRe = regexp.MustCompile(`(?m)^(?:[0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}.*?(-?\d+)\s+dBm`) ++ iwinfoAssocRe = regexp.MustCompile(`(?m)^(?:[0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}[ \t]+(-?\d+)[ \t]+dBm`) +) + +type iwinfoBlock struct { @@ -112,25 +114,49 @@ index 926d658fe..ea42b1f2b 100644 + return math.Round(float64(sum) / float64(count)), true +} + -+func collectOpenWrtAP(ctx context.Context) map[string]system.WiFi { ++// A nil aps requests discovery for drivers without a usable nl80211 inventory. ++// An empty, non-nil aps means enumeration succeeded and found no APs. ++func collectOpenWrtAP(ctx context.Context, aps []string, iwinfo iwinfoCommand) map[string]system.WiFi { ++ if ctx.Err() != nil || (aps != nil && len(aps) == 0) { + return nil + } +- defer client.Close() +- return collectLinux(ctx, client) + results := make(map[string]system.WiFi) -+ -+ out, err := exec.CommandContext(ctx, "iwinfo").Output() -+ if err != nil && len(out) == 0 { -+ return results ++ var blocks []iwinfoBlock ++ if aps == nil { ++ out, err := iwinfo(ctx) ++ if err != nil && len(out) == 0 { ++ return results ++ } ++ blocks = splitIwinfoBlocks(string(out)) ++ } else { ++ for _, name := range aps { ++ blocks = append(blocks, iwinfoBlock{name: name}) ++ } + } + -+ for _, b := range splitIwinfoBlocks(string(out)) { ++ for _, b := range blocks { + if ctx.Err() != nil { + return results + } ++ if aps != nil { ++ out, err := iwinfo(ctx, b.name, "info") ++ if err != nil { ++ continue ++ } ++ b.text = string(out) ++ } + ssid, isAP := parseIwinfoInfo(b.text) + if !isAP { + continue + } + -+ reading := system.WiFi{SSID: validSSID(ssid)} -+ if assocOut, err := exec.CommandContext(ctx, "iwinfo", b.name, "assoclist").Output(); err == nil { ++ reading := system.WiFi{SSID: validSSID(ssid), AP: true} ++ if ctx.Err() != nil { ++ return results ++ } ++ if assocOut, err := iwinfo(ctx, b.name, "assoclist"); err == nil { + if avg, ok := parseAssocRSSI(string(assocOut)); ok { + reading.Signal = &avg + } @@ -140,4 +166,52 @@ index 926d658fe..ea42b1f2b 100644 + return results } - func collectLinux(ctx context.Context, client linuxClient) map[string]system.WiFi { +-func collectLinux(ctx context.Context, client linuxClient) map[string]system.WiFi { ++// collectLinux returns station readings and AP names from a single interface ++// query. A nil AP list signals that iwinfo discovery may be needed. ++func collectLinux(ctx context.Context, client linuxClient) (map[string]system.WiFi, []string) { + result := make(map[string]system.WiFi) +- if ctx.Err() != nil { +- return result ++ if ctx.Err() != nil || client == nil { ++ return result, nil + } + if deadline, ok := ctx.Deadline(); ok { + if client.SetDeadline(deadline) != nil { +- return result ++ return result, nil + } + } + interfaces, err := client.Interfaces() + if err != nil { +- return result ++ return result, nil + } ++ var aps []string + for _, iface := range interfaces { + if ctx.Err() != nil { + break + } +- if iface == nil || iface.Type != native.InterfaceTypeStation || iface.Name == "" { ++ if iface == nil || iface.Name == "" { ++ continue ++ } ++ // Only an inventory with at least one named interface is usable. ++ // Empty dumps can also come from unsupported proprietary drivers. ++ if aps == nil { ++ aps = make([]string, 0) ++ } ++ if iface.Type == native.InterfaceTypeAP { ++ aps = append(aps, iface.Name) ++ } ++ if iface.Type != native.InterfaceTypeStation { + continue + } + // GET_SCAN reads the kernel's BSS cache, without triggering a scan. +@@ -151,5 +291,5 @@ func collectLinux(ctx context.Context, client linuxClient) map[string]system.WiF + } + result[iface.Name] = reading + } +- return result ++ return result, aps + } diff --git a/beszel-agent/patches/002-fix-wifi-ap.patch b/beszel-agent/patches/002-fix-wifi-ap.patch new file mode 100644 index 000000000..1b1caf404 --- /dev/null +++ b/beszel-agent/patches/002-fix-wifi-ap.patch @@ -0,0 +1,33 @@ +From 79f5586adcfce5015dbce7d77eb4e7e76daed913 Mon Sep 17 00:00:00 2001 +From: Jackie Han +Date: Wed, 21 Oct 2026 23:01:05 +0800 +Subject: [PATCH] Enhance WiFi struct documentation + +Updated WiFi struct documentation to clarify its purpose and behavior regarding access points and associated clients. +--- + internal/entities/system/system.go | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/internal/entities/system/system.go b/internal/entities/system/system.go +index b6b6168f0..b24735e83 100644 +--- a/internal/entities/system/system.go ++++ b/internal/entities/system/system.go +@@ -11,12 +11,15 @@ import ( + "github.com/henrygd/beszel/internal/entities/systemd" + ) + +-// WiFi describes a currently connected station interface. Keys in WiFi maps are +-// OS interface identities, not SSIDs. Signal is native dBm only; nil means the +-// OS confirmed association but could not supply RSSI (never convert quality %). ++// WiFi describes a currently connected station interface, or an access point ++// when AP is set. Keys in WiFi maps are OS interface identities, not SSIDs. ++// Signal is native dBm only; nil means the OS confirmed association but could ++// not supply RSSI (never convert quality %). For an AP, Signal is the average ++// RSSI of associated clients, and nil means no client reported one. + type WiFi struct { + SSID string `json:"s,omitempty" cbor:"0,keyasint,omitempty"` + Signal *float64 `json:"r,omitempty" cbor:"1,keyasint,omitempty"` ++ AP bool `json:"a,omitzero" cbor:"2,keyasint,omitempty"` + } + + type Stats struct { diff --git a/cloudflared-next/Makefile b/cloudflared-next/Makefile index f35c6df02..8f6f8bb3c 100644 --- a/cloudflared-next/Makefile +++ b/cloudflared-next/Makefile @@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=cloudflared-next -PKG_VERSION:=2026.10.2 -PKG_RELEASE:=5 +PKG_VERSION:=2026.10.3 +PKG_RELEASE:=6 # Architecture mapping for pre-built binaries ifeq ($(ARCH),x86_64) diff --git a/gately/Makefile b/gately/Makefile index 3e2d9925d..fb8f34ca2 100644 --- a/gately/Makefile +++ b/gately/Makefile @@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=gately -PKG_VERSION:=0.5.4 -PKG_RELEASE:=4 +PKG_VERSION:=0.5.5.1 +PKG_RELEASE:=5 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/wangwanjie/gately/tar.gz/v$(PKG_VERSION)? @@ -20,11 +20,14 @@ PKG_BUILD_DEPENDS:=golang/host node/host node-pnpm/host PKG_BUILD_PARALLEL:=1 PKG_BUILD_FLAGS:=no-mips16 +BUILD_DATE:=$(shell date -u -d "@$(SOURCE_DATE_EPOCH)" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ") + GO_PKG:=github.com/wangwanjie/gately GO_PKG_BUILD_PKG:=$(GO_PKG)/cmd/gately GO_PKG_LDFLAGS_X:=\ $(GO_PKG)/internal/version.Version=v$(PKG_VERSION) \ - $(GO_PKG)/internal/version.Commit=$(PKG_VERSION) + $(GO_PKG)/internal/version.Commit=$(PKG_VERSION) \ + $(GO_PKG)/internal/version.BuildDate=$(BUILD_DATE) include $(INCLUDE_DIR)/package.mk include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk diff --git a/geth/Makefile b/geth/Makefile index 0993b47ca..bcc3230f7 100644 --- a/geth/Makefile +++ b/geth/Makefile @@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=go-ethereum -PKG_VERSION:=1.17.8 -PKG_RELEASE:=12 +PKG_VERSION:=1.17.9 +PKG_RELEASE:=13 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/ethereum/go-ethereum/tar.gz/v${PKG_VERSION}? diff --git a/gost/Makefile b/gost/Makefile index d6b75acf5..ff65a4da9 100644 --- a/gost/Makefile +++ b/gost/Makefile @@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=gost -PKG_VERSION:=3.3.0 -PKG_RELEASE:=5 +PKG_VERSION:=3.3.1 +PKG_RELEASE:=6 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/go-gost/gost/tar.gz/v$(PKG_VERSION)? diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile index 953a71570..dd4ae6f21 100644 --- a/luci-app-passwall/Makefile +++ b/luci-app-passwall/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall PKG_VERSION:=26.10.16 -PKG_RELEASE:=343 +PKG_RELEASE:=344 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall/luasrc/passwall/api.lua b/luci-app-passwall/luasrc/passwall/api.lua index 2bbc4431b..6c8b0166a 100644 --- a/luci-app-passwall/luasrc/passwall/api.lua +++ b/luci-app-passwall/luasrc/passwall/api.lua @@ -277,6 +277,16 @@ function md5_string(str) return md5 end +-- 校验文件 SHA256 +function verify_sha256(file, digest) + if type(digest) ~= "string" then return false end + local expected = digest:match("^sha256:(%x+)$") + if not expected or #expected ~= 64 then return false end + local code, output = exec_call("sha256sum " .. util.shellquote(file)) + local actual = output:match("^(%x+)%s") + return code == 0 and actual ~= nil and actual:lower() == expected:lower() +end + --提取URL中的域名和端口(no ip) function get_domain_port_from_url(url) local scheme, domain, port = string.match(url, "^(https?)://([%w%.%-]+):?(%d*)") @@ -298,9 +308,14 @@ function resolve_domain(domain, dns) address = (address or dns):gsub("^%[(.-)%]$", "%1") port = port or "53" local server = datatypes.ip6addr(address) and ("[" .. address .. "]") or address + local dns_servers = {server .. ":" .. port, address .. "#" .. port, "223.5.5.5"} + local version = sys.exec("LC_ALL=C busybox nslookup -v 2>&1"):match("BusyBox v(%d+%.%d+%.%d+)") + if version and compare_versions(version, "<", "1.35.0") then + dns_servers[1], dns_servers[2] = dns_servers[2], dns_servers[1] + end local rc, result - for _, dns_server in ipairs({server .. ":" .. port, address .. "#" .. port, "223.5.5.5"}) do - rc, result = exec_call_as_gid("LC_ALL=C nslookup " .. util.shellquote(domain) .. " " .. util.shellquote(dns_server), DIRECT_GID) + for _, dns_server in ipairs(dns_servers) do + rc, result = exec_call_as_gid("LC_ALL=C busybox nslookup " .. util.shellquote(domain) .. " " .. util.shellquote(dns_server), DIRECT_GID) local ips, seen, answer = {}, {}, false for line in result:gmatch("[^\r\n]+") do if line:match("^%s*Name:") then answer = true end @@ -1212,6 +1227,17 @@ local function check_path(app_name) } end +local function cache_update_digests(app_name, assets) + local digests = {} + for _, asset in ipairs(assets) do + if asset.browser_download_url and type(asset.digest) == "string" then + digests[asset.browser_download_url] = asset.digest + end + end + fs.mkdirr(CACHE_PATH) + fs.writefile(CACHE_PATH .. "/update_" .. app_name .. ".json", jsonc.stringify(digests)) +end + function to_check(arch, app_name) local result = check_path(app_name) if result.code ~= 0 then @@ -1276,6 +1302,7 @@ function to_check(arch, app_name) } end + cache_update_digests(app_name, {asset}) return { code = 0, has_update = has_update, @@ -1296,6 +1323,12 @@ function to_download(app_name, url, size, task_id, keep_files) return {code = 1, error = i18n.translate("Download url is required.")} end + local digests = jsonc.parse(fs.readfile(CACHE_PATH .. "/update_" .. app_name .. ".json") or "{}") + local digest = type(digests) == "table" and digests[url] + if type(digest) ~= "string" then + return {code = 1, error = i18n.translate("File verification failed.")} + end + if not keep_files then remove("/tmp/" .. app_name .. "_download.*") end local tmp_file @@ -1334,6 +1367,11 @@ function to_download(app_name, url, size, task_id, keep_files) } end + if not verify_sha256(tmp_file, digest) then + exec("/bin/rm", {"-f", tmp_file}) + return {code = 1, error = i18n.translate("File verification failed.")} + end + return {code = 0, file = tmp_file, zip = com[app_name].zipped } end @@ -1541,6 +1579,7 @@ function to_check_self() if not main or not i18n_package then return {code = 1, error = i18n.translate("Get remote version info failed.")} end + cache_update_digests(appname, {main, i18n_package}) return { code = 0, has_update = has_update, local_version = local_version, remote_version = remote_version, html_url = release.html_url, diff --git a/luci-app-passwall/luasrc/view/passwall/app_update/app_version.htm b/luci-app-passwall/luasrc/view/passwall/app_update/app_version.htm index c64fee752..ba7479c9e 100644 --- a/luci-app-passwall/luasrc/view/passwall/app_update/app_version.htm +++ b/luci-app-passwall/luasrc/view/passwall/app_update/app_version.htm @@ -86,7 +86,7 @@ local com = self.com var app = btn.getAttribute('data-update-app'); var detailId = app === appname ? appname + '-check_btn' : '_' + app + '-check_btn'; var ckeckDetailElm = document.getElementById(detailId + '-detail'); - if (app === appname && !checking) errorMessage = errorMessage ? updateFailedText : unknownErrorText; + if (app === appname && !checking && !errorMessage) errorMessage = unknownErrorText; if (errorMessage && ckeckDetailElm) { ckeckDetailElm.textContent = errorMessage } diff --git a/luci-app-passwall/po/zh-cn/passwall.po b/luci-app-passwall/po/zh-cn/passwall.po index 4af6ce031..147967121 100644 --- a/luci-app-passwall/po/zh-cn/passwall.po +++ b/luci-app-passwall/po/zh-cn/passwall.po @@ -1547,6 +1547,9 @@ msgstr "请指定下载地址。" msgid "File download failed or timed out: %s" msgstr "文件下载失败或超时:%s" +msgid "File verification failed." +msgstr "文件校验失败" + msgid "File path required." msgstr "请指定文件路径。" diff --git a/luci-app-passwall/root/usr/share/passwall/rule_update.lua b/luci-app-passwall/root/usr/share/passwall/rule_update.lua index 52bb4cdc2..28a73dbef 100755 --- a/luci-app-passwall/root/usr/share/passwall/rule_update.lua +++ b/luci-app-passwall/root/usr/share/passwall/rule_update.lua @@ -565,31 +565,25 @@ local function fetch_geofile(geo_name, geo_type, url) local sha_url = url:gsub((down_filename:gsub("(%W)", "%%%1")), down_filename .. ".sha256sum") local sha_path = tmp_path .. ".sha256sum" - local function verify_sha256(sha_file) - return sys.call("sha256sum -c " .. sha_file .. " > /dev/null 2>&1") == 0 - end - + local digest local sha_verify = select(1, curl(sha_url, sha_path)) == 0 if sha_verify then local f = io.open(sha_path, "r") if f then local content = f:read("*l") f:close() - if content then - content = content:gsub("(%x+)%s+.+", "%1 " .. tmp_path) - f = io.open(sha_path, "w") - if f then - f:write(content) - f:close() - end + local hash = content and content:match("^(%x+)%s") + if hash and #hash == 64 then + digest = "sha256:" .. hash end end - if fs.access(asset_path) then - sys.call(string.format("cp -f %s %s", asset_path, tmp_path)) - if verify_sha256(sha_path) then - log(geo_type .. " 版本一致,无需更新。") - return 0 - end + if not digest then + log(geo_type .. " SHA256 校验信息无效,更新失败。") + return 1 + end + if fs.access(asset_path) and api.verify_sha256(asset_path, digest) then + log(geo_type .. " 版本一致,无需更新。") + return 0 end end @@ -605,7 +599,7 @@ local function fetch_geofile(geo_name, geo_type, url) end if sret_tmp == 0 then if sha_verify then - if verify_sha256(sha_path) then + if api.verify_sha256(tmp_path, digest) then sys.call(string.format("mkdir -p %s && mv -f %s %s", backup_path, asset_path, backup_path)) sys.call(string.format("mkdir -p %s && mv -f %s %s", asset_location, tmp_path, asset_path)) reboot = 1 diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile index 4a8ca021b..8b031d8d8 100644 --- a/luci-app-passwall2/Makefile +++ b/luci-app-passwall2/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall2 PKG_VERSION:=26.10.20 -PKG_RELEASE:=148 +PKG_RELEASE:=149 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall2/luasrc/passwall2/api.lua b/luci-app-passwall2/luasrc/passwall2/api.lua index d9f841ce4..ce4242496 100644 --- a/luci-app-passwall2/luasrc/passwall2/api.lua +++ b/luci-app-passwall2/luasrc/passwall2/api.lua @@ -307,6 +307,16 @@ function md5_string(str) return md5 end +-- Verify the SHA256 hash of a file. +function verify_sha256(file, digest) + if type(digest) ~= "string" then return false end + local expected = digest:match("^sha256:(%x+)$") + if not expected or #expected ~= 64 then return false end + local code, output = exec_call("sha256sum " .. util.shellquote(file)) + local actual = output:match("^(%x+)%s") + return code == 0 and actual ~= nil and actual:lower() == expected:lower() +end + -- Extract the domain name and port from the URL (no IP address). function get_domain_port_from_url(url) local scheme, domain, port = string.match(url, "^(https?)://([%w%.%-]+):?(%d*)") @@ -328,9 +338,14 @@ function resolve_domain(domain, dns) address = (address or dns):gsub("^%[(.-)%]$", "%1") port = port or "53" local server = datatypes.ip6addr(address) and ("[" .. address .. "]") or address + local dns_servers = {server .. ":" .. port, address .. "#" .. port, "223.5.5.5"} + local version = sys.exec("LC_ALL=C busybox nslookup -v 2>&1"):match("BusyBox v(%d+%.%d+%.%d+)") + if version and compare_versions(version, "<", "1.35.0") then + dns_servers[1], dns_servers[2] = dns_servers[2], dns_servers[1] + end local rc, result - for _, dns_server in ipairs({server .. ":" .. port, address .. "#" .. port, "223.5.5.5"}) do - rc, result = exec_call_as_gid("LC_ALL=C nslookup " .. util.shellquote(domain) .. " " .. util.shellquote(dns_server), DIRECT_GID) + for _, dns_server in ipairs(dns_servers) do + rc, result = exec_call_as_gid("LC_ALL=C busybox nslookup " .. util.shellquote(domain) .. " " .. util.shellquote(dns_server), DIRECT_GID) local ips, seen, answer = {}, {}, false for line in result:gmatch("[^\r\n]+") do if line:match("^%s*Name:") then answer = true end @@ -1224,6 +1239,17 @@ local function check_path(app_name) } end +local function cache_update_digests(app_name, assets) + local digests = {} + for _, asset in ipairs(assets) do + if asset.browser_download_url and type(asset.digest) == "string" then + digests[asset.browser_download_url] = asset.digest + end + end + fs.mkdirr(CACHE_PATH) + fs.writefile(CACHE_PATH .. "/update_" .. app_name .. ".json", jsonc.stringify(digests)) +end + function to_check(arch, app_name) local result = check_path(app_name) if result.code ~= 0 then @@ -1288,6 +1314,7 @@ function to_check(arch, app_name) } end + cache_update_digests(app_name, {asset}) return { code = 0, has_update = has_update, @@ -1308,6 +1335,12 @@ function to_download(app_name, url, size, task_id, keep_files) return {code = 1, error = i18n.translate("Download url is required.")} end + local digests = jsonc.parse(fs.readfile(CACHE_PATH .. "/update_" .. app_name .. ".json") or "{}") + local digest = type(digests) == "table" and digests[url] + if type(digest) ~= "string" then + return {code = 1, error = i18n.translate("File verification failed.")} + end + if not keep_files then sys.call("/bin/rm -f /tmp/".. app_name .."_download.*") end local tmp_file @@ -1346,6 +1379,11 @@ function to_download(app_name, url, size, task_id, keep_files) } end + if not verify_sha256(tmp_file, digest) then + exec("/bin/rm", {"-f", tmp_file}) + return {code = 1, error = i18n.translate("File verification failed.")} + end + return {code = 0, file = tmp_file, zip = com[app_name].zipped } end @@ -1586,6 +1624,9 @@ function to_check_self() selected[#selected + 1] = found[code] end if not main then return {code = 1, error = i18n.translate("Get remote version info failed.")} end + local assets = {main} + for _, asset in ipairs(selected) do assets[#assets + 1] = asset end + cache_update_digests(appname, assets) return { code = 0, has_update = compare_versions(local_version, "<", remote_version), local_version = local_version, remote_version = remote_version, html_url = release.html_url, diff --git a/luci-app-passwall2/luasrc/view/passwall2/app_update/app_version.htm b/luci-app-passwall2/luasrc/view/passwall2/app_update/app_version.htm index 3c9584c2c..531879f69 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/app_update/app_version.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/app_update/app_version.htm @@ -86,7 +86,7 @@ local com = self.com var app = btn.getAttribute('data-update-app'); var detailId = app === appname ? appname + '-check_btn' : '_' + app + '-check_btn'; var ckeckDetailElm = document.getElementById(detailId + '-detail'); - if (app === appname && !checking) errorMessage = errorMessage ? updateFailedText : unknownErrorText; + if (app === appname && !checking && !errorMessage) errorMessage = unknownErrorText; if (errorMessage && ckeckDetailElm) { ckeckDetailElm.textContent = errorMessage } diff --git a/luci-app-passwall2/po/fa/passwall2.po b/luci-app-passwall2/po/fa/passwall2.po index b07cb5f3c..1d13cd92e 100644 --- a/luci-app-passwall2/po/fa/passwall2.po +++ b/luci-app-passwall2/po/fa/passwall2.po @@ -1549,6 +1549,9 @@ msgstr "URL دانلود الزامی است." msgid "File download failed or timed out: %s" msgstr "دانلود فایل ناموفق بود یا زمان آن تمام شد: %s" +msgid "File verification failed." +msgstr "تأیید فایل ناموفق بود." + msgid "File path required." msgstr "مسیر فایل الزامی است." @@ -2285,6 +2288,9 @@ msgstr "شروع به‌روزرسانی قوانین..." msgid "Download file size verification error. Original file size: %sB. Downloaded file size: %sB." msgstr "خطای تأیید اندازه فایل دانلود شده. اندازه فایل اصلی: %sB. اندازه فایل دانلود شده: %sB." +msgid "%s SHA256 verification information is invalid. Update failed." +msgstr "اطلاعات تأیید SHA256 برای %s نامعتبر است. به‌روزرسانی ناموفق بود." + msgid "Downloaded file is empty or an error occurred while reading it." msgstr "فایل دانلود شده خالی است یا خطایی هنگام خواندن آن رخ داده است." diff --git a/luci-app-passwall2/po/ru/passwall2.po b/luci-app-passwall2/po/ru/passwall2.po index 8fcf23000..f2d11753b 100644 --- a/luci-app-passwall2/po/ru/passwall2.po +++ b/luci-app-passwall2/po/ru/passwall2.po @@ -1550,6 +1550,9 @@ msgstr "Необходимо указать URL для загрузки." msgid "File download failed or timed out: %s" msgstr "Ошибка загрузки файла или тайм-аут: %s" +msgid "File verification failed." +msgstr "Ошибка проверки файла." + msgid "File path required." msgstr "Необходимо указать путь к файлу." @@ -2286,6 +2289,9 @@ msgstr "Начало обновления правил..." msgid "Download file size verification error. Original file size: %sB. Downloaded file size: %sB." msgstr "Ошибка проверки размера файла. Исходный размер: %sB, загруженный: %sB." +msgid "%s SHA256 verification information is invalid. Update failed." +msgstr "Недопустимые данные проверки SHA256 для %s. Обновление не выполнено." + msgid "Downloaded file is empty or an error occurred while reading it." msgstr "Загруженный файл пуст или произошла ошибка при чтении." diff --git a/luci-app-passwall2/po/zh-cn/passwall2.po b/luci-app-passwall2/po/zh-cn/passwall2.po index 243f722a9..8e65d2aa2 100644 --- a/luci-app-passwall2/po/zh-cn/passwall2.po +++ b/luci-app-passwall2/po/zh-cn/passwall2.po @@ -1541,6 +1541,9 @@ msgstr "请指定下载地址。" msgid "File download failed or timed out: %s" msgstr "文件下载失败或超时:%s" +msgid "File verification failed." +msgstr "文件校验失败" + msgid "File path required." msgstr "请指定文件路径。" @@ -2271,6 +2274,9 @@ msgstr "开始更新规则..." msgid "Download file size verification error. Original file size: %sB. Downloaded file size: %sB." msgstr "下载文件大小校验出错,原始文件大小 %sB,下载文件大小:%sB。" +msgid "%s SHA256 verification information is invalid. Update failed." +msgstr "%s SHA256 校验信息无效,更新失败。" + msgid "Downloaded file is empty or an error occurred while reading it." msgstr "下载的文件为空或读取文件时发生错误。" diff --git a/luci-app-passwall2/po/zh-tw/passwall2.po b/luci-app-passwall2/po/zh-tw/passwall2.po index 1b1ddd51f..544bc8db9 100644 --- a/luci-app-passwall2/po/zh-tw/passwall2.po +++ b/luci-app-passwall2/po/zh-tw/passwall2.po @@ -1541,6 +1541,9 @@ msgstr "請指定下載地址。" msgid "File download failed or timed out: %s" msgstr "文件下載失敗或超時:%s" +msgid "File verification failed." +msgstr "檔案校驗失敗" + msgid "File path required." msgstr "請指定文件路径。" @@ -2277,6 +2280,9 @@ msgstr "開始更新規則..." msgid "Download file size verification error. Original file size: %sB. Downloaded file size: %sB." msgstr "下載檔案大小校驗出錯,原始檔案大小 %sB,下載檔案大小:%sB。" +msgid "%s SHA256 verification information is invalid. Update failed." +msgstr "%s SHA256 校驗資訊無效,更新失敗。" + msgid "Downloaded file is empty or an error occurred while reading it." msgstr "下載的文件為空或讀取文件時發生錯誤。" diff --git a/luci-app-passwall2/root/usr/share/passwall2/rule_update.lua b/luci-app-passwall2/root/usr/share/passwall2/rule_update.lua index 3deae806b..d854c95cb 100755 --- a/luci-app-passwall2/root/usr/share/passwall2/rule_update.lua +++ b/luci-app-passwall2/root/usr/share/passwall2/rule_update.lua @@ -116,31 +116,25 @@ local function fetch_geofile(geo_name, geo_type, url) local sha_url = url:gsub((down_filename:gsub("(%W)", "%%%1")), down_filename .. ".sha256sum") local sha_path = tmp_path .. ".sha256sum" - local function verify_sha256(sha_file) - return sys.call("sha256sum -c " .. sha_file .. " > /dev/null 2>&1") == 0 - end - + local digest local sha_verify, _ = curl(sha_url, sha_path) == 200 if sha_verify then local f = io.open(sha_path, "r") if f then local content = f:read("*l") f:close() - if content then - content = content:gsub("(%x+)%s+.+", "%1 " .. tmp_path) - f = io.open(sha_path, "w") - if f then - f:write(content) - f:close() - end + local hash = content and content:match("^(%x+)%s") + if hash and #hash == 64 then + digest = "sha256:" .. hash end end - if fs.access(asset_path) then - sys.call(string.format("cp -f %s %s", asset_path, tmp_path)) - if verify_sha256(sha_path) then - log(1, api.i18n.translatef("%s version is the same and does not need to be updated.", geo_type)) - return 0 - end + if not digest then + log(1, api.i18n.translatef("%s SHA256 verification information is invalid. Update failed.", geo_type)) + return 1 + end + if fs.access(asset_path) and api.verify_sha256(asset_path, digest) then + log(1, api.i18n.translatef("%s version is the same and does not need to be updated.", geo_type)) + return 0 end end @@ -156,7 +150,7 @@ local function fetch_geofile(geo_name, geo_type, url) end if sret_tmp == 200 then if sha_verify then - if verify_sha256(sha_path) then + if api.verify_sha256(tmp_path, digest) then sys.call(string.format("mkdir -p %s && mv -f %s %s", backup_path, asset_path, backup_path)) sys.call(string.format("mkdir -p %s && mv -f %s %s", asset_location, tmp_path, asset_path)) reboot = 1 diff --git a/smartdns/Makefile b/smartdns/Makefile index bea0ac88c..1bb6c034d 100644 --- a/smartdns/Makefile +++ b/smartdns/Makefile @@ -1,64 +1,60 @@ -# SPDX-License-Identifier: GPL-3.0-or-later # -# Copyright (c) 2018-2023 Nick Peng (pymumu@gmail.com) +# Copyright (c) 2018-2026 Nick Peng (pymumu@gmail.com) +# This is free software, licensed under the GNU General Public License v3. +# include $(TOPDIR)/rules.mk PKG_NAME:=smartdns PKG_VERSION:=48.4 -PKG_RELEASE:=8 +PKG_RELEASE:=9 -PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz -PKG_SOURCE_URL:=https://codeload.github.com/pymumu/smartdns/tar.gz/Release$(PKG_VERSION)? +PKG_SOURCE_VERSION:=21c940edc65520849ba03544c5cf8d9cf326e680 +PKG_SOURCE:=$(PKG_NAME)-$(PKG_SOURCE_VERSION).tar.gz +PKG_SOURCE_URL:=https://codeload.github.com/pymumu/smartdns/tar.gz +PKG_SOURCE_URL_FILE:=$(PKG_SOURCE_VERSION) PKG_HASH:=skip -PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-Release$(PKG_VERSION) +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_SOURCE_VERSION) + +SMARTDNS_WEBUI_SOURCE_VERSION:=1c06fee693434ec7c71f06010d3f87416baba9f0 +SMARTDNS_WEBUI_SOURCE:=$(PKG_NAME)-webui-$(SMARTDNS_WEBUI_SOURCE_VERSION).tar.gz +SMARTDNS_WEBUI_SOURCE_URL:=https://codeload.github.com/pymumu/smartdns-webui/tar.gz +SMARTDNS_WEBUI_SOURCE_URL_FILE:=$(SMARTDNS_WEBUI_SOURCE_VERSION) +SMARTDNS_WEBUI_HASH:=7a698dd4826750869429e83d91ce99a4e474d0809588a7f4124ef86875be9b1f PKG_MAINTAINER:=Nick Peng PKG_LICENSE:=GPL-3.0-or-later PKG_LICENSE_FILES:=LICENSE -PKG_CPE_ID:=cpe:/a:pymumu:smartdns -PKG_CONFIG_DEPENDS:=CONFIG_PACKAGE_smartdns-ui -PKG_BUILD_DEPENDS:=PACKAGE_smartdns-ui:node/host PACKAGE_smartdns-ui:rust-bindgen/host PKG_BUILD_PARALLEL:=1 -RUST_PKG_FEATURES:=build-release -RUST_PKG_LOCKED:=0 +# node compile is slow, so do not use it, download node manually. +# PACKAGE_smartdns-ui:node/host +PKG_BUILD_DEPENDS:=PACKAGE_smartdns-ui:rust/host -include $(INCLUDE_DIR)/package.mk include $(TOPDIR)/feeds/packages/lang/rust/rust-package.mk +include $(INCLUDE_DIR)/package.mk +MAKE_VARS += VER=$(PKG_VERSION) MAKE_PATH:=src -MAKE_VARS+= VER=$(PKG_VERSION) WITH_ZLIB=0 -define Package/smartdns/Default +define Package/smartdns/default SECTION:=net CATEGORY:=Network SUBMENU:=IP Addresses and Names - TITLE:=smartdns URL:=https://www.github.com/pymumu/smartdns/ endef define Package/smartdns - $(call Package/smartdns/Default) - TITLE+= server - DEPENDS:=+i386:libatomic +libopenssl + $(Package/smartdns/default) + TITLE:=smartdns server + DEPENDS:=+libpthread +libopenssl +libatomic +zlib endef define Package/smartdns/description SmartDNS is a local DNS server which accepts DNS query requests from local network clients, gets DNS query results from multiple upstream DNS servers concurrently, and returns the fastest IP to clients. -Unlike dnsmasq's all-servers, smartdns returns the fastest IP, and encrypt DNS queries with DoT or DoH. -endef - -define Package/smartdns-ui - $(call Package/smartdns/Default) - TITLE+= dashboard - DEPENDS:=+smartdns $(RUST_ARCH_DEPENDS) @!i386 -endef - -define Package/smartdns-ui/description -A dashboard ui for smartdns server. +Unlike dnsmasq's all-servers, smartdns returns the fastest IP, and encrypt DNS queries with DoT or DoH. endef define Package/smartdns/conffiles @@ -70,73 +66,78 @@ define Package/smartdns/conffiles /etc/smartdns/domain-forwarding.list endef -define Download/smartdns-webui - PROTO:=git - URL:=https://github.com/pymumu/smartdns-webui.git - SOURCE_DATE:=2026-07-29 - SOURCE_VERSION:=1c06fee693434ec7c71f06010d3f87416baba9f0 - MIRROR_HASH:=1b718d1288f6ddbdd80e3a948dda1693fd7bc8079fdb65be121f64f23ce53849 - SUBDIR:=smartdns-webui-$$$$(subst -,.,$$$$(SOURCE_DATE))~$$$$(call version_abbrev,$$$$(SOURCE_VERSION)) - FILE:=$$(SUBDIR).tar.zst -endef - -define Build/Prepare - $(call Build/Prepare/Default) - -ifneq ($(CONFIG_PACKAGE_smartdns-ui),) - $(eval $(call Download,smartdns-webui)) - $(eval $(Download/smartdns-webui)) - mkdir -p $(PKG_BUILD_DIR)/smartdns-webui - zstdcat $(DL_DIR)/$(FILE) | tar -C $(PKG_BUILD_DIR)/smartdns-webui $(TAR_OPTIONS) --strip-components=1 -endif -endef - -define Build/Compile - $(call Build/Compile/Default) - -ifneq ($(CONFIG_PACKAGE_smartdns-ui),) - ( \ - pushd $(PKG_BUILD_DIR) ; \ - pushd smartdns-webui ; \ - npm install ; \ - npm run build ; \ - popd ; \ - pushd plugin/smartdns-ui ; \ - $(CARGO_PKG_CONFIG_VARS) \ - MAKEFLAGS="$(PKG_JOBS)" \ - WITH_ZLIB=0 \ - TARGET_CFLAGS="$(filter-out -O%,$(TARGET_CFLAGS)) $(RUSTC_CFLAGS)" \ - BINDGEN_EXTRA_CLANG_ARGS="--sysroot=$(TOOLCHAIN_ROOT_DIR)" \ - cargo build -v --profile $(CARGO_PKG_PROFILE) \ - $(if $(strip $(RUST_PKG_FEATURES)),--features "$(strip $(RUST_PKG_FEATURES))") \ - $(if $(filter --jobserver%,$(PKG_JOBS)),,-j1) \ - $(CARGO_PKG_ARGS) ; \ - popd ; \ - popd ; \ - ) -endif -endef - define Package/smartdns/install - $(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/config $(1)/etc/init.d + $(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/config $(1)/etc/init.d $(INSTALL_DIR) $(1)/etc/smartdns $(1)/etc/smartdns/domain-set $(1)/etc/smartdns/conf.d/ - $(INSTALL_DIR) $(1)/etc/smartdns/ip-set $(1)/etc/smartdns/download $(INSTALL_BIN) $(PKG_BUILD_DIR)/src/smartdns $(1)/usr/sbin/smartdns $(INSTALL_BIN) $(PKG_BUILD_DIR)/package/openwrt/files/etc/init.d/smartdns $(1)/etc/init.d/smartdns $(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/address.conf $(1)/etc/smartdns/address.conf $(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/blacklist-ip.conf $(1)/etc/smartdns/blacklist-ip.conf $(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/custom.conf $(1)/etc/smartdns/custom.conf - $(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/domain-block.list $(1)/etc/smartdns/domain-block.list - $(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/domain-forwarding.list $(1)/etc/smartdns/domain-forwarding.list $(INSTALL_CONF) $(PKG_BUILD_DIR)/package/openwrt/files/etc/config/smartdns $(1)/etc/config/smartdns endef +define Package/smartdns-ui + $(Package/smartdns/default) + TITLE:=smartdns dashboard + DEPENDS:=+smartdns $(RUST_ARCH_DEPENDS) +endef + +define Package/smartdns-ui/description +A dashboard ui for smartdns server. +endef + +define Package/smartdns-ui/conffiles +/etc/config/smartdns +endef + define Package/smartdns-ui/install $(INSTALL_DIR) $(1)/usr/lib - $(INSTALL_DIR) $(1)/usr/share/smartdns - $(CP) $(PKG_BUILD_DIR)/plugin/smartdns-ui/target/$(RUSTC_TARGET_ARCH)/$(CARGO_PKG_PROFILE)/libsmartdns_ui.so $(1)/usr/lib/smartdns_ui.so - $(CP) $(PKG_BUILD_DIR)/smartdns-webui/out $(1)/usr/share/smartdns/wwwroot + $(INSTALL_DIR) $(1)/etc/smartdns/conf.d/ + $(INSTALL_DIR) $(1)/usr/share/smartdns/wwwroot + $(INSTALL_BIN) $(PKG_BUILD_DIR)/plugin/smartdns-ui/target/smartdns_ui.so $(1)/usr/lib/smartdns_ui.so + $(CP) $(PKG_BUILD_DIR)/smartdns-webui/out/* $(1)/usr/share/smartdns/wwwroot +endef + +define Build/Compile/smartdns-webui + which npm || (echo "npm not found, please install npm first" && exit 1) + npm install --prefix $(PKG_BUILD_DIR)/smartdns-webui/ + npm run build --prefix $(PKG_BUILD_DIR)/smartdns-webui/ +endef + +define Build/Compile/smartdns-ui + cargo install --force --locked bindgen-cli + CARGO_BUILD_ARGS="$(if $(strip $(RUST_PKG_FEATURES)),--features "$(strip $(RUST_PKG_FEATURES))") --profile $(CARGO_PKG_PROFILE)" + +$(CARGO_PKG_VARS) CARGO_BUILD_ARGS="$(CARGO_BUILD_ARGS)" CC=$(TARGET_CC) \ + PATH="$$(PATH):$(CARGO_HOME)/bin" \ + AWS_LC_SYS_CFLAGS="-O0" \ + make -C $(PKG_BUILD_DIR)/plugin/smartdns-ui +endef + +define Download/smartdns-webui + FILE:=$(SMARTDNS_WEBUI_SOURCE) + URL:=$(SMARTDNS_WEBUI_SOURCE_URL) + URL_FILE:=$(SMARTDNS_WEBUI_SOURCE_URL_FILE) + HASH:=skip +endef +$(eval $(call Download,smartdns-webui)) + +ifdef CONFIG_PACKAGE_smartdns-ui +define Build/Prepare + $(call Build/Prepare/Default) + $(INSTALL_DIR) $(PKG_BUILD_DIR)/smartdns-webui + $(TAR) -C $(PKG_BUILD_DIR)/smartdns-webui --strip-components=1 -xf $(DL_DIR)/$(SMARTDNS_WEBUI_SOURCE) +endef +endif + +define Build/Compile + $(call Build/Compile/Default,smartdns) +ifdef CONFIG_PACKAGE_smartdns-ui + $(call Build/Compile/smartdns-ui) + $(call Build/Compile/smartdns-webui) +endif endef $(eval $(call BuildPackage,smartdns)) +$(eval $(call RustBinPackage,smartdns-ui)) $(eval $(call BuildPackage,smartdns-ui)) diff --git a/xray-core/Makefile b/xray-core/Makefile index 16bd530ce..f719bdd4e 100644 --- a/xray-core/Makefile +++ b/xray-core/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=Xray-core -PKG_VERSION:=26.10.17 -PKG_RELEASE:=26 +PKG_VERSION:=26.10.21 +PKG_RELEASE:=27 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/XTLS/Xray-core/tar.gz/v$(PKG_VERSION)?