From c9ccf6f883c52c7bd1b715cbb001b45f92d895cd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 17 Oct 2026 20:40:04 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A6=84=20Sync=202026-10-17=2020:40:04?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dufs/Makefile | 4 +- luci-app-amlogic/Makefile | 4 +- .../resources/view/amlogic/armcpu.js | 1 - .../resources/view/amlogic/config.js | 8 +- luci-app-homeproxy-pro/Makefile | 2 +- .../homeproxy-pro/scripts/runtime/health.sh | 26 ++- luci-app-homeproxy-pro/tests/README.md | 2 +- .../tests/fixtures/runtime/trace.golden.txt | 7 + .../tests/runtime/test_health_probe.sh | 103 ++++++++++ luci-app-open-box/Makefile | 5 +- luci-app-openvpn-plus/Makefile | 2 +- luci-app-openvpn-plus/README.md | 25 +++ luci-app-passwall2/Makefile | 2 +- .../luasrc/controller/passwall2.lua | 29 ++- .../model/cbi/passwall2/client/app_update.lua | 4 +- .../cbi/passwall2/client/type/1_sing-box.lua | 10 + .../cbi/passwall2/client/type/2_xray.lua | 10 + luci-app-passwall2/luasrc/passwall2/api.lua | 194 +++++++++++++----- luci-app-passwall2/luasrc/passwall2/com.lua | 12 ++ .../luasrc/passwall2/util_sing-box.lua | 4 +- .../luasrc/passwall2/util_xray.lua | 4 +- .../view/passwall2/app_update/app_version.htm | 193 ++++++++++++----- luci-app-passwall2/po/fa/passwall2.po | 15 ++ luci-app-passwall2/po/ru/passwall2.po | 15 ++ luci-app-passwall2/po/zh-cn/passwall2.po | 12 ++ luci-app-passwall2/po/zh-tw/passwall2.po | 15 ++ .../root/etc/uci-defaults/luci-app-passwall2 | 2 +- .../root/usr/share/passwall2/app.sh | 13 +- .../root/usr/share/passwall2/lease2hosts.sh | 88 ++++++-- .../root/usr/share/passwall2/luci_update.sh | 37 ++++ .../root/usr/share/passwall2/subscribe.lua | 2 +- luci-app-ssr-plus/Makefile | 2 +- .../root/etc/init.d/shadowsocksr | 6 +- luci-app-ssr-plus/root/usr/bin/ssr-monitor | 12 +- .../resources/view/network/wireless.js | 8 +- .../share/rpcd/acl.d/luci-mod-network.json | 5 +- natflow/Makefile | 4 +- 37 files changed, 716 insertions(+), 171 deletions(-) create mode 100755 luci-app-passwall2/root/usr/share/passwall2/luci_update.sh diff --git a/dufs/Makefile b/dufs/Makefile index 817aee0b6..ca4df9da7 100644 --- a/dufs/Makefile +++ b/dufs/Makefile @@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=dufs -PKG_VERSION:=0.46.0 -PKG_RELEASE:=2 +PKG_VERSION:=0.47.0 +PKG_RELEASE:=3 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/sigoden/dufs/tar.gz/v$(PKG_VERSION)? diff --git a/luci-app-amlogic/Makefile b/luci-app-amlogic/Makefile index 1b23827ed..4df59bdf4 100644 --- a/luci-app-amlogic/Makefile +++ b/luci-app-amlogic/Makefile @@ -22,11 +22,11 @@ PKG_NAME:=luci-app-amlogic # DD = day (01 = 1st) # NN = daily release sequence (01 = first release of the day, 02 = second, ...) # Example below: 26.10.0101 = October 1, 2026, 1st release of the day -PKG_VERSION:=26.10.1501 +PKG_VERSION:=26.10.1602 # PKG_RELEASE marks the LuCI variant; onekey-install.sh relies on it (do NOT bump) # 1 = Lua version (lua branch) → tag: 26.10.0101 # 2 = JS version (main branch) → tag: 26.10.0101-js -PKG_RELEASE:=21 +PKG_RELEASE:=22 PKG_LICENSE:=GPL-2.0 License PKG_MAINTAINER:=ophub diff --git a/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/armcpu.js b/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/armcpu.js index 217e3ad3d..d64898bbf 100644 --- a/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/armcpu.js +++ b/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/armcpu.js @@ -43,7 +43,6 @@ return view.extend({ const m = new form.Map('amlogic', _('CPU Freq Settings'), _('Set CPU Scaling Governor to Max Performance or Balance Mode')); const s = m.section(form.NamedSection, 'armcpu', 'settings'); - s.anonymous = true; // Render an independent tab + form options per CPU cluster (big/little). policies.forEach(function (p) { diff --git a/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/config.js b/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/config.js index 29148b46c..826855b6e 100644 --- a/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/config.js +++ b/luci-app-amlogic/htdocs/luci-static/resources/view/amlogic/config.js @@ -17,7 +17,7 @@ const callPlatform = rpc.declare({ object: 'luci.amlogic', method: 'platform_inf // Query runtime state; kernel_release is used to derive default kernel tag and branch. const callState = rpc.declare({ object: 'luci.amlogic', method: 'state' }); -// This page uses its own Save button; hide LuCI's default Save/Apply/Reset. +// Saves go through LuCI's default footer buttons (Save & Apply / Save / Reset). return view.extend({ // Load platform info + state in parallel, along with the UCI config (we will read/write it). load: function () { @@ -43,7 +43,6 @@ return view.extend({ _('You can customize the github.com download repository of OpenWrt files and kernels in [Online Download Update].') + '
' + _('Tip: The same files as the current OpenWrt system\'s BOARD (such as rock5b) and kernel (such as 5.10) will be downloaded.')); const o = m.section(form.NamedSection, 'config', 'amlogic'); - o.anonymous = true; // 1. Display device platform const dev = o.option(form.DummyValue, 'mydevice', _('Current Device:'), @@ -73,10 +72,11 @@ return view.extend({ suffix.rmempty = false; // 5. Kernel download repository - const kpath = o.option(form.ListValue, 'amlogic_kernel_path', + const kpath = o.option(form.Value, 'amlogic_kernel_path', _('Kernel download repository:'), _('Set the kernel files download repository on github.com in [Online Download Update].')); kpath.value('https://github.com/ophub/kernel'); + kpath.value('https://github.com/breakingbadboy/OpenWrt'); kpath.default = 'https://github.com/ophub/kernel'; kpath.rmempty = false; @@ -102,7 +102,7 @@ return view.extend({ else if (u.indexOf('-beta') >= 0) kernelTagDefault = 'kernel_beta'; else kernelTagDefault = 'kernel_stable'; } - const ktags = o.option(form.ListValue, 'amlogic_kernel_tags', + const ktags = o.option(form.Value, 'amlogic_kernel_tags', _('Kernel download tags:'), _('Set the kernel files download tags on github.com in [Online Download Update].')); Object.keys(knownTags).forEach(function (k) { ktags.value(k, _(knownTags[k])); }); diff --git a/luci-app-homeproxy-pro/Makefile b/luci-app-homeproxy-pro/Makefile index 628be20dd..876d27d33 100644 --- a/luci-app-homeproxy-pro/Makefile +++ b/luci-app-homeproxy-pro/Makefile @@ -41,7 +41,7 @@ LUCI_DEPENDS:= \ PKG_NAME:=luci-app-homeproxy-pro PKG_VERSION:=28.10.1.14 -PKG_RELEASE:=28 +PKG_RELEASE:=29 LUCI_BASENAME:=homeproxy-pro diff --git a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/runtime/health.sh b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/runtime/health.sh index a975b1135..da52ee99c 100644 --- a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/runtime/health.sh +++ b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/runtime/health.sh @@ -328,13 +328,37 @@ hp_probe_dns() { # hp_probe_tcp # 0 something accepted a connection on loopback:, 1 nothing did, # 2 cannot probe here. +# +# This probe has to prove an accept WITHOUT leaving the connection hanging +# around, because it runs inside service_started() while the reload still holds +# the procd lock - a probe that does not return blocks every later status and +# reload behind `flock`. +# +# A mixed/http/socks inbound accepts and then waits for a real proxy request, +# so it never closes the connection. GNU netcat keeps the socket open after +# stdin EOF unless --close is given, and -w does NOT bound that loop: its wait +# is spent on the connect (core_readwrite takes its select budget from -i), so +# the bare probe blocked for as long as the operator left the router alone. +# Measured with a real GNU netcat 0.7.1 against an inbound that accepts and +# holds: bare and `-w 3` both hung past 8s, `-c` returned 0 in 1s. +# +# A nc that advertises no --close is busybox, and busybox must be invoked bare: +# it accepts NO options at all on ImmortalWrt 25.12.2 (busybox 1.37.0), where +# -z/-w/-c all fail with a usage error, so passing -w here would turn a working +# probe into a warning on every reload - and into a rollback loop for anyone +# who sets health_probe_strict. busybox exits on stdin EOF on its own, so the +# bare form already returns. hp_probe_tcp() { local port="$1" [ -n "$port" ] || return 2 command -v nc > "/dev/null" 2>&1 || return 2 - nc 127.0.0.1 "$port" < "/dev/null" > "/dev/null" 2>&1 && return 0 + if nc -h 2>&1 | grep -q -- '--close'; then + nc -c 127.0.0.1 "$port" < "/dev/null" > "/dev/null" 2>&1 && return 0 + else + nc 127.0.0.1 "$port" < "/dev/null" > "/dev/null" 2>&1 && return 0 + fi return 1 } diff --git a/luci-app-homeproxy-pro/tests/README.md b/luci-app-homeproxy-pro/tests/README.md index 6340cf13d..e136c0278 100644 --- a/luci-app-homeproxy-pro/tests/README.md +++ b/luci-app-homeproxy-pro/tests/README.md @@ -297,7 +297,7 @@ Two further host differences are bridged so the remaining checks still run: | `tests/lib/luci-module.js` | The off-target LuCI module loader (`'require x as y';` rewritten into a dependency lookup), shared by the snapshot renderer and the frontend invariant tests. Its default `_()` returns a LuCI String object with `.format`, because the form code calls both halves. | | `tests/runtime/test_config_transaction.sh` | The `runtime/` helpers `init.d/homeproxy-pro` leans on: the known-good copy, the fallback when generation produced nothing, the rollback, and the health probe. Pure shell, runs anywhere. | | `tests/runtime/test_dns_snippets.sh` | The dnsmasq snippet writer's incremental behaviour (review L6). Counts `init.d/dnsmasq restart` calls through a stub and asserts that an identical second write does *not* restart (a restart flushes every client's DNS cache), while a changed resource list, routing mode, DNS port or `ipv6_support` setting does. Also pins that a mode change drops the snippet the previous mode produced, and that removing a resource list removes its snippet so the next call is stable again. Pure shell, runs anywhere. | -| `tests/runtime/test_health_probe.sh` | The functional health probes (`runtime/health.sh`): `hp_probe_dns` / `hp_probe_tcp` answer 0 / 1 / 2 and keep "cannot probe here" (no tool, no port) distinct from "the probe failed"; `hp_run_probes` *reports* by default and only fails the gate when `health_probe_strict` is on, and never fails it for a probe that could not run; `hp_config_port_for_tag` reads the probe ports out of the running configuration in both the generator's pretty shape and the compact one. `nslookup` and `nc` are stubbed, so it is pure shell and runs anywhere. | +| `tests/runtime/test_health_probe.sh` | The functional health probes (`runtime/health.sh`): `hp_probe_dns` / `hp_probe_tcp` answer 0 / 1 / 2 and keep "cannot probe here" (no tool, no port) distinct from "the probe failed"; `hp_run_probes` *reports* by default and only fails the gate when `health_probe_strict` is on, and never fails it for a probe that could not run; `hp_config_port_for_tag` reads the probe ports out of the running configuration in both the generator's pretty shape and the compact one. It also pins that `hp_probe_tcp` *returns* — the probes run inside `service_started()` while the reload holds the procd lock, so one that never finishes blocks every later status and reload behind `flock`, and "failures are only warnings" cannot save a loop. Two `nc` stubs model the flavours it really meets: a GNU netcat that will not answer unless passed `-c` (and that gains nothing from `-w`, whose wait is spent on the connect), and a busybox that accepts no options at all — the shape of ImmortalWrt 25.12.2, where `-z`/`-w`/`-c` are usage errors and a probe that passed them would warn on every reload. `nslookup` and `nc` are stubbed, so it is pure shell and runs anywhere. | | `tests/runtime/test_crontab_perms.sh` | `hp_crontab_drop()` (runtime/service.sh) edits `/etc/crontabs/root` through a temporary file, and `mv` replaces the file rather than its contents - so the new one inherited the shell's umask (0644) while procd creates the root crontab as 0600. The test drives the function against a sandboxed crontab and pins: the entry is dropped, other jobs survive, the mode is 0600 (including a file an older version had already loosened), a missing crontab is reported as a failure and is not created. Pure shell, runs anywhere. | | `tests/runtime/test_resource_update.sh` | The content-verification path of `scripts/update_resources.sh`: the download URL is pinned to a commit, and the file is installed only when its git blob id matches the one GitHub reports for that path and commit. Drives all four outcomes (match, mismatch, no digest from the API, no local digest) with `uclient-fetch` / `jsonfilter` / `ucode` / `uci` / `flock` stubbed and the script's absolute paths rewritten into a sandbox, asserting in each failure case that the installed list and its `.ver` are left untouched - the point of the check is that it refuses, so a driver that only walked the happy path would keep passing with the check deleted. Also covers the two post-processing steps that carry a failure silently: `china_list`'s normalisation ordering, the address-family split (both IP lists come out of one upstream file, so each has to land at its own `.txt` holding only its own family, the combined download must not be left in `RUN_DIR`, and a family with no entries has to be refused rather than installed empty), and the firewall re-render that closes the loop on the nft half. That last one is the reason a test exists at all: the route side goes live on fswatch while `homeproxy_mainland_addr_v4/v6` are rendered into `fw4_post.nft` and otherwise only a start/restart installs them, so "the list updated" and "the kernel sees it" are different events. Both directions are asserted — a successful update must re-render and call `fw4 reload`, and a failed render must leave the previous ruleset in place, must not reach `fw4`, and must not fail the update (a resource that can never refresh is worse than one briefly missing from the firewall). Pure shell, runs anywhere. | | `tests/runtime/test_runtime_extraction.sh` | Drives `init.d/homeproxy-pro` through a stubbed environment (fake `ip` / `nft` / `fw4` / `ucode` / `sing-box` / `uci` / `netstat` / `nslookup` / `nc`, fake procd and jsonfilter state, fixture UCI) across five scenarios and diffs the resulting command + file trace against a baseline. Two baselines exist and are captured with the SAME harness, so the diff between them is exactly the intentional change: `tests/fixtures/runtime/trace.pre-pr05.txt` (the 517-line init script before PHASE 7 moved the plumbing out — the record that the extraction was behaviour-preserving) and `tests/fixtures/runtime/trace.golden.txt` (after the health-gate fix). Scenario D is the P0 regression: a candidate whose `mixed_port` is already taken must be rejected by the health gate, the previous known-good must survive, and the reload must roll back onto it. The generated fixture configuration is pretty-printed like the generator's own `%.J` output, because the runtime reads that file (`hp_config_ports`, `hp_config_port_for_tag`) and a compact fixture would test a shape production never writes. Pure shell, no ucode needed. Regenerate with `HP_UPDATE_GOLDEN=1` (optionally `HP_GOLDEN=` / `HP_INITD=` to target another baseline or revision). | diff --git a/luci-app-homeproxy-pro/tests/fixtures/runtime/trace.golden.txt b/luci-app-homeproxy-pro/tests/fixtures/runtime/trace.golden.txt index 8286ef142..358aa1666 100644 --- a/luci-app-homeproxy-pro/tests/fixtures/runtime/trace.golden.txt +++ b/luci-app-homeproxy-pro/tests/fixtures/runtime/trace.golden.txt @@ -73,6 +73,7 @@ jsonfilter -e @.running jsonfilter -e @.exit_code netstat -ltnup nslookup -port=5333 example.com 127.0.0.1 +nc -h nc 127.0.0.1 5330 chmod 600 var/run/homeproxy-pro/known-good/sing-box-c.json config_load homeproxy-pro @@ -268,6 +269,7 @@ jsonfilter -e @.running jsonfilter -e @.exit_code netstat -ltnup nslookup -port=5333 example.com 127.0.0.1 +nc -h nc 127.0.0.1 5330 jsonfilter -e @['homeproxy-pro'].instances['sing-box-s'] jsonfilter -e @.running @@ -528,6 +530,7 @@ jsonfilter -e @.running jsonfilter -e @.exit_code netstat -ltnup nslookup -port=5333 example.com 127.0.0.1 +nc -h nc 127.0.0.1 5330 chmod 600 var/run/homeproxy-pro/known-good/sing-box-c.json config_load homeproxy-pro @@ -991,6 +994,7 @@ jsonfilter -e @.running jsonfilter -e @.exit_code netstat -ltnup nslookup -port=5333 example.com 127.0.0.1 +nc -h nc 127.0.0.1 5330 log Client is up again; reinstalling the intercept layer. log dnsmasq snippets unchanged, skipping the restart. @@ -1160,6 +1164,7 @@ jsonfilter -e @.running jsonfilter -e @.exit_code netstat -ltnup nslookup -port=5333 example.com 127.0.0.1 +nc -h nc 127.0.0.1 5330 chmod 600 var/run/homeproxy-pro/known-good/sing-box-c.json config_load homeproxy-pro @@ -1698,6 +1703,7 @@ jsonfilter -e @.running jsonfilter -e @.exit_code netstat -ltnup nslookup -port=70000 example.com 127.0.0.1 +nc -h nc 127.0.0.1 5398 log Warning: the DNS snippets were not installed; keeping the previous known-good configuration. sing-box version -n @@ -1878,6 +1884,7 @@ jsonfilter -e @.running jsonfilter -e @.exit_code netstat -ltnup nslookup -port=5333 example.com 127.0.0.1 +nc -h nc 127.0.0.1 5330 jsonfilter -e @['homeproxy-pro'].instances['sing-box-s'] jsonfilter -e @.running diff --git a/luci-app-homeproxy-pro/tests/runtime/test_health_probe.sh b/luci-app-homeproxy-pro/tests/runtime/test_health_probe.sh index 7eb98708d..182583675 100755 --- a/luci-app-homeproxy-pro/tests/runtime/test_health_probe.sh +++ b/luci-app-homeproxy-pro/tests/runtime/test_health_probe.sh @@ -15,6 +15,16 @@ # gate failure. "Cannot probe here" stays non-fatal even then - there is # nothing to conclude from a missing tool. # +# Reporting correctly is only half of it. service_started() runs the probes +# while the reload still holds the procd lock, so a probe that never RETURNS +# blocks every later status and reload behind `flock` - and no amount of +# "failures are only warnings" helps a loop that never finishes. hp_probe_tcp +# therefore has to survive the two nc flavours it actually meets: one that hangs +# unless it is told to close on stdin EOF, and one (busybox) that accepts no +# options at all and would answer a usage error instead of a verdict. The two +# stubs below are shaped to fail if the probe is invoked the way that would hang +# or warn in the field. +# # The probes take explicit ports and shell out to nslookup/nc, so this runs # anywhere without ucode, procd or a router. # @@ -107,6 +117,99 @@ expect "tcp: a refusing listener is 1" "$?" "1" hp_probe_tcp "" expect "tcp: no port means cannot-probe (2), not failure" "$?" "2" +echo "== hp_probe_tcp: the probe has to RETURN, not just succeed ==" +# The probe runs inside service_started() while the reload still holds the procd +# lock, so a probe that never returns blocks every later status and reload +# behind `flock`. A test cannot observe a real hang, so each stub below is +# built to FAIL if it is invoked the way that would hang in the field: +# +# * the GNU stub refuses to answer unless it is passed -c, which is what the +# real GNU netcat needs to leave an inbound that accepts and then holds the +# connection open. It also refuses -w, because -w is spent on the connect +# and does not bound that loop - so "fixing" it with -w alone fails here. +# * the busybox stub rejects every option the way ImmortalWrt's build does, +# so passing -w/-z/-c costs the probe its result. +# +# Both record how they were called, so the test pins the invocation and not +# just the verdict. +BIN_GNU="$WORK/bin-gnu" +BIN_BBB="$WORK/bin-busybox" +mkdir -p "$BIN_GNU" "$BIN_BBB" +NC_ARGS="$WORK/nc_args" + +cat > "$BIN_GNU/nc" <<'EOF' +#!/bin/sh +# GNU netcat 0.7.1: advertises --close/--wait, and needs -c to stop watching a +# socket the peer is holding open. -w is accepted but does not help. +case "$1" in +-h|--help) + echo "GNU netcat 0.7.1" + echo " --close close on EOF from stdin" + echo " --wait SEC timeout for connects and final net reads" + exit 1 + ;; +esac +printf '%s\n' "$*" >> "$HP_PROBE_STUB_DIR/nc_args" +case "$*" in +-c\ *) ;; +*) exit 2 ;; # without -c the real nc would still be running +esac +[ -f "$HP_PROBE_STUB_DIR/tcp_ok" ] || exit 1 +exit 0 +EOF +cat > "$BIN_BBB/nc" <<'EOF' +#!/bin/sh +# ImmortalWrt 25.12.2 (busybox 1.37.0): nc advertises no --close and accepts +# no option at all - -z/-w/-c/-l are all usage errors. It exits on stdin EOF +# by itself, so the bare invocation is both correct and sufficient. +case "$1" in +-h|--help) + echo "Usage: nc [IPADDR PORT]" + exit 1 + ;; +-*) + echo "Usage: nc [IPADDR PORT]" + exit 1 + ;; +esac +printf '%s\n' "$*" >> "$HP_PROBE_STUB_DIR/nc_args" +[ -f "$HP_PROBE_STUB_DIR/tcp_ok" ] || exit 1 +exit 0 +EOF +chmod +x "$BIN_GNU/nc" "$BIN_BBB/nc" + +SAVED_PATH="$PATH" + +: > "$WORK/tcp_ok" +: > "$NC_ARGS" +PATH="$BIN_GNU:$SAVED_PATH" +hp_probe_tcp 5330 +expect "gnu nc: an accepting listener is 0" "$?" "0" +expect "gnu nc: it is called with -c, so the probe can return" \ + "$(head -1 "$NC_ARGS" | cut -d' ' -f1)" "-c" +expect "gnu nc: the target is the mixed inbound, not the capability probe" \ + "$(head -1 "$NC_ARGS" | awk '{print $(NF-1), $NF}')" "127.0.0.1 5330" + +rm -f "$WORK/tcp_ok" +: > "$NC_ARGS" +hp_probe_tcp 5330 +expect "gnu nc: a refusing listener is still 1" "$?" "1" + +: > "$WORK/tcp_ok" +: > "$NC_ARGS" +PATH="$BIN_BBB:$SAVED_PATH" +hp_probe_tcp 5330 +expect "busybox nc: an accepting listener is 0" "$?" "0" +expect "busybox nc: it is called bare, because busybox accepts no options" \ + "$(head -1 "$NC_ARGS")" "127.0.0.1 5330" + +rm -f "$WORK/tcp_ok" +hp_probe_tcp 5330 +expect "busybox nc: a refusing listener is still 1" "$?" "1" + +PATH="$SAVED_PATH" +export PATH + echo "== missing tooling is 'cannot probe', not 'failed' ==" # PATH is replaced, not prepended to: the host this runs on may well have its # own nslookup/nc (macOS ships both), and a prepended empty directory would diff --git a/luci-app-open-box/Makefile b/luci-app-open-box/Makefile index 953cd6e72..2ff5fedc9 100644 --- a/luci-app-open-box/Makefile +++ b/luci-app-open-box/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-open-box PKG_VERSION:=0.1.337 -PKG_RELEASE:=33 +PKG_RELEASE:=34 PKG_MAINTAINER:=liandu2024 PKG_LICENSE:=GPL-3.0-or-later @@ -55,7 +55,7 @@ endef # 3. 安装与打包:同时铺装 LuCI 页面、系统脚本以及 /opt/open-box 核心 define Package/luci-app-open-box/install # 仅复制 _core 子目录内容,避免递归包含 - $(INSTALL_DIR) $(1)/opt/open-box $(1)/opt/open-box/data + $(INSTALL_DIR) $(1)/opt/open-box $(1)/opt/open-box/data $(1)/www $(CP) $(PKG_BUILD_DIR)/_core/* $(1)/opt/open-box/ chmod -R 0755 $(1)/opt/open-box/bin $(1)/opt/open-box/node/bin $(1)/opt/open-box/openwrt/bin 2>/dev/null || true chmod +x $(1)/opt/open-box/update.sh $(1)/opt/open-box/uninstall.sh 2>/dev/null || true @@ -102,7 +102,6 @@ define Package/luci-app-open-box/postrm #!/bin/sh [ -n "$${IPKG_INSTROOT}" ] || { rm -rf /tmp/luci-*cache* 2>/dev/null || true - rm -rf /www/luci-static/resources/view/openbox 2>/dev/null || true uci -q delete firewall.openbox_panel || true uci -q delete firewall.openbox_dns || true uci -q delete firewall.openbox_tun_forward || true diff --git a/luci-app-openvpn-plus/Makefile b/luci-app-openvpn-plus/Makefile index 543230e32..2d3f8b7cf 100755 --- a/luci-app-openvpn-plus/Makefile +++ b/luci-app-openvpn-plus/Makefile @@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-openvpn-plus PKG_VERSION:=1.0.0 -PKG_RELEASE:=4 +PKG_RELEASE:=5 PKG_LICENSE:=Apache-2.0 PKG_MAINTAINER:=Manfred Jaider diff --git a/luci-app-openvpn-plus/README.md b/luci-app-openvpn-plus/README.md index af72d3f06..19674365a 100755 --- a/luci-app-openvpn-plus/README.md +++ b/luci-app-openvpn-plus/README.md @@ -60,6 +60,31 @@ config openvpn 'instance1' option config '/etc/openvpn/luci/instance1.conf' ``` +## Screenshots + + + + + + + + + + + + + + + + + + + + + + +
010304
091013
111208
140605
+ ## Directory Structure ```text diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile index 7d9d33cf1..507ba57f4 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.1 -PKG_RELEASE:=144 +PKG_RELEASE:=145 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall2/luasrc/controller/passwall2.lua b/luci-app-passwall2/luasrc/controller/passwall2.lua index 831e23a10..fdd15dde5 100644 --- a/luci-app-passwall2/luasrc/controller/passwall2.lua +++ b/luci-app-passwall2/luasrc/controller/passwall2.lua @@ -101,10 +101,11 @@ function index() entry({"admin", "services", appname, "delete_select_shunt_rules"}, call("delete_select_shunt_rules")).leaf = true --[[Components update]] - entry({"admin", "services", appname, "check_passwall2"}, call("app_check")).leaf = true + entry({"admin", "services", appname, "check_" .. appname}, call("app_check")).leaf = true + entry({"admin", "services", appname, "update_" .. appname}, call("app_update")).leaf = true local coms = require "luci.passwall2.com" local com - for com, _ in pairs(coms) do + for _, com in ipairs(coms.order) do entry({"admin", "services", appname, "check_" .. com}, call("com_check", com)).leaf = true entry({"admin", "services", appname, "update_" .. com}, call("com_update", com)).leaf = true entry({"admin", "services", appname, "version_" .. com}, call("com_version", com)).leaf = true @@ -747,6 +748,30 @@ function app_check() http_write_json(json) end +function app_update() + local id = http.formvalue("id") + if not id or not id:match("^[%w_-]+$") then + http_write_json({code = 1, error = i18n.translate("Invalid download task.")}) + return + end + local task = http.formvalue("task") + local result + if task == "progress" then + result = api.to_download_progress(appname, id, http.formvalue("total_size")) + elseif task == "install" then + if not luci.dispatcher.test_post_security() then return end + result = api.to_install_self(id, http.formvalue("force"), http.formvalue("languages")) + else + local part = tonumber(http.formvalue("part")) or 1 + if part < 1 or part > 5 or part ~= math.floor(part) then + http_write_json({code = 1, error = i18n.translate("Invalid download task.")}) + return + end + result = api.to_download(appname, http.formvalue("url"), http.formvalue("size"), id .. part, part > 1) + end + http_write_json(result) +end + function com_check(comname) local json = api.to_check("", comname) http_write_json(json) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/app_update.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/app_update.lua index 90089d934..7963c0375 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/app_update.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/app_update.lua @@ -18,8 +18,8 @@ o:value("https://ghproxy.cc/", "ghproxy.cc") o:value("https://mirror.ghproxy.com/", "ghproxy.com") o:value("https://ghfast.top/", "ghfast.top") -local k, v -for k, v in pairs(com) do +for _, k in ipairs(com.order) do + local v = com[k] o = s:option(Value, k:gsub("%-","_") .. "_file", translatef("%s App Path", v.name)) o.default = v.default_path or ("/usr/bin/" .. k) o.placeholder = o.default diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua index b902a483a..86c4f086e 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua @@ -498,6 +498,16 @@ if singbox_tags:find("with_quic") then o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps")) o:depends({ protocol = "hysteria2" }) + o = s:option(Value, "hysteria2_stream_recv_win", translate("QUIC stream receive window") .. " (MB)") + o.datatype = "range(1,1000)" + o.placeholder = "8" + o:depends({ protocol = "hysteria2" }) + + o = s:option(Value, "hysteria2_conn_recv_win", translate("QUIC connection receive window") .. " (MB)") + o.datatype = "range(1,1000)" + o.placeholder = "20" + o:depends({ protocol = "hysteria2" }) + o = s:option(Value, "hysteria2_idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)") o.datatype = "range(4,120)" o:depends({ protocol = "hysteria2"}) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/2_xray.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/2_xray.lua index 338081b87..88840957e 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/2_xray.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/2_xray.lua @@ -403,6 +403,16 @@ o:depends({ protocol = "hysteria2" }) o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps")) o:depends({ protocol = "hysteria2" }) +o = s:option(Value, "hysteria2_stream_recv_win", translate("QUIC stream receive window") .. " (MB)") +o.datatype = "range(1,1000)" +o.placeholder = "8" +o:depends({ protocol = "hysteria2" }) + +o = s:option(Value, "hysteria2_conn_recv_win", translate("QUIC connection receive window") .. " (MB)") +o.datatype = "range(1,1000)" +o.placeholder = "20" +o:depends({ protocol = "hysteria2" }) + o = s:option(Value, "hysteria2_idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)") o.datatype = "range(4,120)" o:depends({ protocol = "hysteria2"}) diff --git a/luci-app-passwall2/luasrc/passwall2/api.lua b/luci-app-passwall2/luasrc/passwall2/api.lua index 46bd70533..8adf54b19 100644 --- a/luci-app-passwall2/luasrc/passwall2/api.lua +++ b/luci-app-passwall2/luasrc/passwall2/api.lua @@ -208,10 +208,11 @@ function get_cache_var(key) return val end -function get_new_port(p) - if not p then p = "auto" end - local cmd_format = ". /usr/share/passwall2/utils.sh ; echo -n $(get_new_port %s tcp,udp)" - return tonumber(sys.exec(string.format(cmd_format, p))) +function get_new_port(start_port, protocol) + start_port = tonumber(start_port) or "auto" + if protocol ~= "tcp" and protocol ~= "udp" then protocol = "tcp,udp" end + local cmd_format = ". /usr/share/passwall2/utils.sh ; echo -n $(get_new_port %s %s)" + return tonumber(sys.exec(string.format(cmd_format, util.shellquote(tostring(start_port)), util.shellquote(protocol)))) end function exec_call(cmd) @@ -448,9 +449,10 @@ function repeat_exist(table, value) end function remove(...) - for index, value in ipairs({...}) do - if value and #value > 0 and value ~= "/" then - sys.call(string.format("rm -rf %s", value)) + for i = 1, select("#", ...) do + local value = select(i, ...) + if type(value) == "string" and #value > 0 and value ~= "/" then + sys.call(string.format("rm -rf -- %s", value)) end end end @@ -1102,9 +1104,16 @@ function compare_versions(ver1, comp, ver2) local n2 = table.getn(av2) if (max < n2) then max = n2 end + local function version_part(value) + local number = tonumber(value) + if number then return number end + local revision = type(value) == "string" and value:match("^[rR](%d+)$") + return tonumber(revision) or 0 + end + for i = 1, max, 1 do - local s1 = tonumber(av1[i] or 0) or 0 - local s2 = tonumber(av2[i] or 0) or 0 + local s1 = version_part(av1[i] or 0) + local s2 = version_part(av2[i] or 0) if comp == "~=" and (s1 ~= s2) then return true end if (comp == "<" or comp == "<=") and (s1 < s2) then return true end @@ -1270,8 +1279,8 @@ function to_check(arch, app_name) } end -function to_download(app_name, url, size, task_id) - local result = check_path(app_name) +function to_download(app_name, url, size, task_id, keep_files) + local result = app_name == appname and {code = 0} or check_path(app_name) if result.code ~= 0 then return result end @@ -1280,7 +1289,7 @@ function to_download(app_name, url, size, task_id) return {code = 1, error = i18n.translate("Download url is required.")} end - sys.call("/bin/rm -f /tmp/".. app_name .."_download.*") + if not keep_files then sys.call("/bin/rm -f /tmp/".. app_name .."_download.*") end local tmp_file if type(task_id) == "string" and task_id:match("^[%w_-]+$") then @@ -1329,6 +1338,12 @@ function to_download_progress(app_name, task_id, total_size) total_size = tonumber(total_size) or 0 local tmp_file = "/tmp/" .. app_name .. "_download." .. task_id local downloaded = tonumber(fs.stat(tmp_file, "size")) or 0 + if app_name == appname then + downloaded = 0 + for part = 1, 5 do + downloaded = downloaded + (tonumber(fs.stat(tmp_file .. part, "size")) or 0) + end + end local percent if total_size > 0 then -- Leave 100% for the download request's success callback. @@ -1465,53 +1480,136 @@ function to_move(app_name,file) end function get_version() - local version = sys.exec("opkg list-installed luci-app-passwall2 2>/dev/null | awk '{print $3}'") - if not version or #version == 0 then - version = sys.exec("apk list luci-app-passwall2 2>/dev/null | awk '/installed/ {print $1}' | cut -d'-' -f4-") + local version_file = CACHE_PATH .. "/passwall2_version" + local version = trim(fs.readfile(version_file) or "") + if version == "" then + version = trim(sys.exec("opkg list-installed luci-app-passwall2 2>/dev/null | awk '{print $3}'") or "") + if version == "" then + version = trim(sys.exec("apk list luci-app-passwall2 2>/dev/null | awk '/installed/ {print $1}' | cut -d'-' -f4-") or "") + end + if version ~= "" then + fs.mkdirr(CACHE_PATH) + fs.writefile(version_file, version) + end end - return (version or ""):gsub("\n", ""):match("^([^-]+)") + return (version:gsub("%-[rR](%d+)$", "-%1")) +end + +local function self_update_languages() + local current = uci:get("luci", "main", "lang") or "auto" + if current == "auto" then + current = _G.L and _G.L.dispatcher and _G.L.dispatcher.lang + if not current or current == "auto" then + local ok, request, accept = pcall(function() + local http = require "luci.http" + return http.getenv("REQUEST_METHOD") or http.getenv("REQUEST_URI"), http.getenv("HTTP_ACCEPT_LANGUAGE") + end) + current = lang + if ok and (request or accept) then + current = "en" + for item in (accept or ""):gmatch("[^,]+") do + local code = item:match("^%s*([%w_-]+)") + local quality = tonumber(item:match(";%s*q%s*=%s*([%d%.]+)")) or 1 + if code and quality > 0 then + code = code:gsub("-", "_") + local lower = code:lower() + local alias = ({zh="zh_Hans", zh_cn="zh_Hans", zh_sg="zh_Hans", zh_tw="zh_Hant", zh_hk="zh_Hant", zh_mo="zh_Hant"})[lower] + if lower:match("^zh_hans") then alias = "zh_Hans" end + if lower:match("^zh_hant") then alias = "zh_Hant" end + local matched + for _, candidate in ipairs({code, lower, alias or "", code:match("^([^_]+)") or ""}) do + if uci:get("luci", "languages", candidate) then current = candidate; matched = true; break end + end + if matched then break end + end + end + end + end + end + current = (current or ""):lower():gsub("_", "-") + if current == "zh" or current == "zh-cn" or current == "zh-sg" or current:match("^zh%-hans") then current = "zh-cn" end + if current == "zh-tw" or current == "zh-hk" or current == "zh-mo" or current:match("^zh%-hant") then current = "zh-tw" end + if current:match("^fa%-") then current = "fa" end + if current:match("^ru%-") then current = "ru" end + local languages = {} + for _, code in ipairs({"fa", "ru", "zh-cn", "zh-tw"}) do + if current == code or is_install("luci-i18n-" .. appname .. "-" .. code) then languages[#languages + 1] = code end + end + return languages end function to_check_self() - local url = "https://raw.githubusercontent.com/Openwrt-Passwall/openwrt-passwall2/main/luci-app-passwall2/Makefile" - local tmp_file = "/tmp/passwall2_makefile" - local gh_proxy = uci_get_c("@global_app[0]", "gh_proxy_url") or "" - local return_code, result - if gh_proxy ~= "" then - url = gh_proxy .. url - return_code, result = curl_direct(url, tmp_file, curl_args) - else - return_code, result = curl_auto(url, tmp_file, curl_args) + local release = get_api_json(com[appname]:get_url()) + if type(release) == "table" and #release > 0 then release = release[1] end + if type(release) ~= "table" or not release.tag_name then + return {code = 1, error = i18n.translate("Get remote version info failed.")} end - result = return_code == 0 - if not result then - exec("/bin/rm", {"-f", tmp_file}) - return { - code = 1, - error = i18n.translatef("Failed") - } + local local_version = get_version() + local remote_version = release.tag_name:gsub("^v", ""):gsub("%-r(%d+)$", "-%1") + local package_version = remote_version:gsub("%-(%d+)$", "-r%1") + local language_version = remote_version:match("^(%d+%.%d+%.%d+)") or remote_version + local apk = sys.call("command -v apk >/dev/null 2>&1") == 0 + local main_name = "luci-app-" .. appname .. (apk and "-" .. package_version .. ".apk" or "_" .. package_version .. "_all.ipk") + local languages = self_update_languages() + local wanted, found = {}, {} + for _, code in ipairs(languages) do + local name = "luci-i18n-" .. appname .. "-" .. code .. (apk and "-" .. language_version .. ".apk" or "_" .. language_version .. "_all.ipk") + wanted[name] = code end - local local_version = get_version() - local remote_version = sys.exec("echo -n $(grep '^PKG_VERSION' /tmp/passwall2_makefile | head -n 1 | awk -F '=' '{print $2}')") - exec("/bin/rm", {"-f", tmp_file}) - - local has_update = compare_versions(local_version, "<", remote_version) - if not has_update then - return { - code = 0, - local_version = local_version, - remote_version = remote_version - } + local main + for _, asset in ipairs(release.assets or {}) do + if asset.name == main_name then main = asset end + if wanted[asset.name] then found[wanted[asset.name]] = asset end end + local selected = {} + for _, code in ipairs(languages) do + if not found[code] then return {code = 1, error = i18n.translate("Get remote version info failed.")} end + selected[#selected + 1] = found[code] + end + if not main then return {code = 1, error = i18n.translate("Get remote version info failed.")} end return { - code = 1, - has_update = true, - local_version = local_version, - remote_version = remote_version, - error = i18n.translatef("The latest version: %s, currently does not support automatic update, if you need to update, please compile or download the ipk and then manually install.", remote_version) + code = 0, has_update = compare_versions(local_version, "<", remote_version), + local_version = local_version, remote_version = remote_version, html_url = release.html_url, + data = main, i18n = selected, languages = table.concat(languages, ",") } end +function to_install_self(id, force, languages) + if type(id) ~= "string" or not id:match("^[%w_-]+$") or (languages and type(languages) ~= "string") then return {code = 1} end + local selected, seen = {}, {} + for code in (languages or ""):gmatch("[^,]+") do + if not ({fa=true, ru=true, ["zh-cn"]=true, ["zh-tw"]=true})[code] or seen[code] then return {code = 1} end + selected[#selected + 1], seen[code] = code, true + end + local dir = "/tmp/" .. appname .. "_luci_update." .. id + local manager = sys.call("command -v apk >/dev/null 2>&1") == 0 and "apk" or "opkg" + local extension = manager == "apk" and ".apk" or ".ipk" + local status_dir = "/www/luci-static/resources/" .. appname .. "-update" + local status = status_dir .. "/" .. id .. ".json" + fs.mkdir(dir) + fs.mkdir(status_dir) + if exec("/bin/mv", {"/tmp/" .. appname .. "_download." .. id .. "1", dir .. "/luci-app-" .. appname .. extension}) ~= 0 then + remove(dir) + return {code = 1} + end + for part, code in ipairs(selected) do + if exec("/bin/mv", {"/tmp/" .. appname .. "_download." .. id .. (part + 1), dir .. "/luci-i18n-" .. appname .. "-" .. code .. extension}) ~= 0 then + remove(dir) + return {code = 1} + end + end + local script = dir .. "/luci_update.sh" + if exec("/bin/cp", {"/usr/share/" .. appname .. "/luci_update.sh", script}) ~= 0 then + remove(dir) + return {code = 1} + end + chmod_755(script) + fs.writefile(status, '{"installing":true}') + sys.call("nohup " .. script .. " " .. dir .. " " .. status .. " " .. manager .. " " .. + (force == "1" and "1" or "0") .. " >/dev/null 2>&1 = 2 and t <= 60) and t .. "s" or nil end)(node.hysteria2_keep_alive_period), - disable_path_mtu_discovery = version_ge_1_14_0 and (tonumber(node.hysteria2_disable_mtu_discovery) == 1) or nil, + stream_receive_window = tonumber(node.hysteria2_stream_recv_win) and tonumber(node.hysteria2_stream_recv_win) .. " MB" or nil, + connection_receive_window = tonumber(node.hysteria2_conn_recv_win) and tonumber(node.hysteria2_conn_recv_win) .. " MB" or nil, + disable_path_mtu_discovery = version_ge_1_14_0 and (node.hysteria2_disable_mtu_discovery == "1") or nil, tls = tls, realm = node.hysteria2_realms and (function() result.server = nil diff --git a/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/luci-app-passwall2/luasrc/passwall2/util_xray.lua index 8a79f3853..11d8eb839 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_xray.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_xray.lua @@ -400,7 +400,9 @@ function gen_outbound(flag, node, tag, proxy_table) t = tonumber(tostring(t or "0"):match("^%d+")) return (t and t >= 2 and t <= 60) and t or nil end)(node.hysteria2_keep_alive_period), - disablePathMTUDiscovery = tonumber(node.hysteria2_disable_mtu_discovery) == 1 + disablePathMTUDiscovery = node.hysteria2_disable_mtu_discovery == "1", + initStreamReceiveWindow = tonumber(node.hysteria2_stream_recv_win) and tonumber(node.hysteria2_stream_recv_win)*1048576 or nil, + initConnectionReceiveWindow = tonumber(node.hysteria2_conn_recv_win) and tonumber(node.hysteria2_conn_recv_win)*1048576 or nil, } end if fragment and fragment_table and ({raw=1, ws=1, httpupgrade=1, grpc=1, xhttp=1})[TP] then 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 0720e935b..3c9584c2c 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 @@ -7,6 +7,7 @@ local com = self.com @@ -228,14 +305,18 @@ local com = self.com
【 <%=api.get_version()%> 】 - - + + +
-<%for k, v in pairs(com) do%> +<%for _, k in ipairs(com.order) do + local v = com[k] +%>