🦄 Sync 2026-10-17 20:40:04

This commit is contained in:
github-actions[bot] committed 2026-10-17 20:40:04 +08:00
1 parent 315f273687
commit c9ccf6f883
37 files changed
+716 -171

No files matched your search

+2 -2
View File
@@ -5,8 +5,8 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=dufs PKG_NAME:=dufs
PKG_VERSION:=0.46.0 PKG_VERSION:=0.47.0
PKG_RELEASE:=2 PKG_RELEASE:=3
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/sigoden/dufs/tar.gz/v$(PKG_VERSION)? PKG_SOURCE_URL:=https://codeload.github.com/sigoden/dufs/tar.gz/v$(PKG_VERSION)?
+2 -2
View File
@@ -22,11 +22,11 @@ PKG_NAME:=luci-app-amlogic
# DD = day (01 = 1st) # DD = day (01 = 1st)
# NN = daily release sequence (01 = first release of the day, 02 = second, ...) # 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 # 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) # PKG_RELEASE marks the LuCI variant; onekey-install.sh relies on it (do NOT bump)
# 1 = Lua version (lua branch) → tag: 26.10.0101 # 1 = Lua version (lua branch) → tag: 26.10.0101
# 2 = JS version (main branch) → tag: 26.10.0101-js # 2 = JS version (main branch) → tag: 26.10.0101-js
PKG_RELEASE:=21 PKG_RELEASE:=22
PKG_LICENSE:=GPL-2.0 License PKG_LICENSE:=GPL-2.0 License
PKG_MAINTAINER:=ophub <https://github.com/ophub/luci-app-amlogic> PKG_MAINTAINER:=ophub <https://github.com/ophub/luci-app-amlogic>
@@ -43,7 +43,6 @@ return view.extend({
const m = new form.Map('amlogic', _('CPU Freq Settings'), const m = new form.Map('amlogic', _('CPU Freq Settings'),
_('Set CPU Scaling Governor to Max Performance or Balance Mode')); _('Set CPU Scaling Governor to Max Performance or Balance Mode'));
const s = m.section(form.NamedSection, 'armcpu', 'settings'); const s = m.section(form.NamedSection, 'armcpu', 'settings');
s.anonymous = true;
// Render an independent tab + form options per CPU cluster (big/little). // Render an independent tab + form options per CPU cluster (big/little).
policies.forEach(function (p) { policies.forEach(function (p) {
@@ -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. // Query runtime state; kernel_release is used to derive default kernel tag and branch.
const callState = rpc.declare({ object: 'luci.amlogic', method: 'state' }); 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({ return view.extend({
// Load platform info + state in parallel, along with the UCI config (we will read/write it). // Load platform info + state in parallel, along with the UCI config (we will read/write it).
load: function () { 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].') + _('You can customize the github.com download repository of OpenWrt files and kernels in [Online Download Update].') +
'<br />' + _('Tip: The same files as the current OpenWrt system\'s BOARD (such as rock5b) and kernel (such as 5.10) will be downloaded.')); '<br />' + _('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'); const o = m.section(form.NamedSection, 'config', 'amlogic');
o.anonymous = true;
// 1. Display device platform // 1. Display device platform
const dev = o.option(form.DummyValue, 'mydevice', _('Current Device:'), const dev = o.option(form.DummyValue, 'mydevice', _('Current Device:'),
@@ -73,10 +72,11 @@ return view.extend({
suffix.rmempty = false; suffix.rmempty = false;
// 5. Kernel download repository // 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:'), _('Kernel download repository:'),
_('Set the kernel files download repository on github.com in [Online Download Update].')); _('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/ophub/kernel');
kpath.value('https://github.com/breakingbadboy/OpenWrt');
kpath.default = 'https://github.com/ophub/kernel'; kpath.default = 'https://github.com/ophub/kernel';
kpath.rmempty = false; kpath.rmempty = false;
@@ -102,7 +102,7 @@ return view.extend({
else if (u.indexOf('-beta') >= 0) kernelTagDefault = 'kernel_beta'; else if (u.indexOf('-beta') >= 0) kernelTagDefault = 'kernel_beta';
else kernelTagDefault = 'kernel_stable'; 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:'), _('Kernel download tags:'),
_('Set the kernel files download tags on github.com in [Online Download Update].')); _('Set the kernel files download tags on github.com in [Online Download Update].'));
Object.keys(knownTags).forEach(function (k) { ktags.value(k, _(knownTags[k])); }); Object.keys(knownTags).forEach(function (k) { ktags.value(k, _(knownTags[k])); });
+1 -1
View File
@@ -41,7 +41,7 @@ LUCI_DEPENDS:= \
PKG_NAME:=luci-app-homeproxy-pro PKG_NAME:=luci-app-homeproxy-pro
PKG_VERSION:=28.10.1.14 PKG_VERSION:=28.10.1.14
PKG_RELEASE:=28 PKG_RELEASE:=29
LUCI_BASENAME:=homeproxy-pro LUCI_BASENAME:=homeproxy-pro
@@ -328,13 +328,37 @@ hp_probe_dns() {
# hp_probe_tcp <port> # hp_probe_tcp <port>
# 0 something accepted a connection on loopback:<port>, 1 nothing did, # 0 something accepted a connection on loopback:<port>, 1 nothing did,
# 2 cannot probe here. # 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() { hp_probe_tcp() {
local port="$1" local port="$1"
[ -n "$port" ] || return 2 [ -n "$port" ] || return 2
command -v nc > "/dev/null" 2>&1 || 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 return 1
} }
+1 -1
View File
@@ -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/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_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_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_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_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). | | `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). |
@@ -73,6 +73,7 @@ jsonfilter -e @.running
jsonfilter -e @.exit_code jsonfilter -e @.exit_code
netstat -ltnup netstat -ltnup
nslookup -port=5333 example.com 127.0.0.1 nslookup -port=5333 example.com 127.0.0.1
nc -h
nc 127.0.0.1 5330 nc 127.0.0.1 5330
chmod 600 var/run/homeproxy-pro/known-good/sing-box-c.json chmod 600 var/run/homeproxy-pro/known-good/sing-box-c.json
config_load homeproxy-pro config_load homeproxy-pro
@@ -268,6 +269,7 @@ jsonfilter -e @.running
jsonfilter -e @.exit_code jsonfilter -e @.exit_code
netstat -ltnup netstat -ltnup
nslookup -port=5333 example.com 127.0.0.1 nslookup -port=5333 example.com 127.0.0.1
nc -h
nc 127.0.0.1 5330 nc 127.0.0.1 5330
jsonfilter -e @['homeproxy-pro'].instances['sing-box-s'] jsonfilter -e @['homeproxy-pro'].instances['sing-box-s']
jsonfilter -e @.running jsonfilter -e @.running
@@ -528,6 +530,7 @@ jsonfilter -e @.running
jsonfilter -e @.exit_code jsonfilter -e @.exit_code
netstat -ltnup netstat -ltnup
nslookup -port=5333 example.com 127.0.0.1 nslookup -port=5333 example.com 127.0.0.1
nc -h
nc 127.0.0.1 5330 nc 127.0.0.1 5330
chmod 600 var/run/homeproxy-pro/known-good/sing-box-c.json chmod 600 var/run/homeproxy-pro/known-good/sing-box-c.json
config_load homeproxy-pro config_load homeproxy-pro
@@ -991,6 +994,7 @@ jsonfilter -e @.running
jsonfilter -e @.exit_code jsonfilter -e @.exit_code
netstat -ltnup netstat -ltnup
nslookup -port=5333 example.com 127.0.0.1 nslookup -port=5333 example.com 127.0.0.1
nc -h
nc 127.0.0.1 5330 nc 127.0.0.1 5330
log Client is up again; reinstalling the intercept layer. log Client is up again; reinstalling the intercept layer.
log dnsmasq snippets unchanged, skipping the restart. log dnsmasq snippets unchanged, skipping the restart.
@@ -1160,6 +1164,7 @@ jsonfilter -e @.running
jsonfilter -e @.exit_code jsonfilter -e @.exit_code
netstat -ltnup netstat -ltnup
nslookup -port=5333 example.com 127.0.0.1 nslookup -port=5333 example.com 127.0.0.1
nc -h
nc 127.0.0.1 5330 nc 127.0.0.1 5330
chmod 600 var/run/homeproxy-pro/known-good/sing-box-c.json chmod 600 var/run/homeproxy-pro/known-good/sing-box-c.json
config_load homeproxy-pro config_load homeproxy-pro
@@ -1698,6 +1703,7 @@ jsonfilter -e @.running
jsonfilter -e @.exit_code jsonfilter -e @.exit_code
netstat -ltnup netstat -ltnup
nslookup -port=70000 example.com 127.0.0.1 nslookup -port=70000 example.com 127.0.0.1
nc -h
nc 127.0.0.1 5398 nc 127.0.0.1 5398
log Warning: the DNS snippets were not installed; keeping the previous known-good configuration. log Warning: the DNS snippets were not installed; keeping the previous known-good configuration.
sing-box version -n sing-box version -n
@@ -1878,6 +1884,7 @@ jsonfilter -e @.running
jsonfilter -e @.exit_code jsonfilter -e @.exit_code
netstat -ltnup netstat -ltnup
nslookup -port=5333 example.com 127.0.0.1 nslookup -port=5333 example.com 127.0.0.1
nc -h
nc 127.0.0.1 5330 nc 127.0.0.1 5330
jsonfilter -e @['homeproxy-pro'].instances['sing-box-s'] jsonfilter -e @['homeproxy-pro'].instances['sing-box-s']
jsonfilter -e @.running jsonfilter -e @.running
@@ -15,6 +15,16 @@
# gate failure. "Cannot probe here" stays non-fatal even then - there is # gate failure. "Cannot probe here" stays non-fatal even then - there is
# nothing to conclude from a missing tool. # 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 # The probes take explicit ports and shell out to nslookup/nc, so this runs
# anywhere without ucode, procd or a router. # anywhere without ucode, procd or a router.
# #
@@ -107,6 +117,99 @@ expect "tcp: a refusing listener is 1" "$?" "1"
hp_probe_tcp "" hp_probe_tcp ""
expect "tcp: no port means cannot-probe (2), not failure" "$?" "2" 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' ==" echo "== missing tooling is 'cannot probe', not 'failed' =="
# PATH is replaced, not prepended to: the host this runs on may well have its # 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 # own nslookup/nc (macOS ships both), and a prepended empty directory would
+2 -3
View File
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-open-box PKG_NAME:=luci-app-open-box
PKG_VERSION:=0.1.337 PKG_VERSION:=0.1.337
PKG_RELEASE:=33 PKG_RELEASE:=34
PKG_MAINTAINER:=liandu2024 PKG_MAINTAINER:=liandu2024
PKG_LICENSE:=GPL-3.0-or-later PKG_LICENSE:=GPL-3.0-or-later
@@ -55,7 +55,7 @@ endef
# 3. 安装与打包:同时铺装 LuCI 页面、系统脚本以及 /opt/open-box 核心 # 3. 安装与打包:同时铺装 LuCI 页面、系统脚本以及 /opt/open-box 核心
define Package/luci-app-open-box/install define Package/luci-app-open-box/install
# 仅复制 _core 子目录内容,避免递归包含 # 仅复制 _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/ $(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 -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 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 #!/bin/sh
[ -n "$${IPKG_INSTROOT}" ] || { [ -n "$${IPKG_INSTROOT}" ] || {
rm -rf /tmp/luci-*cache* 2>/dev/null || true 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_panel || true
uci -q delete firewall.openbox_dns || true uci -q delete firewall.openbox_dns || true
uci -q delete firewall.openbox_tun_forward || true uci -q delete firewall.openbox_tun_forward || true
+1 -1
View File
@@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-openvpn-plus PKG_NAME:=luci-app-openvpn-plus
PKG_VERSION:=1.0.0 PKG_VERSION:=1.0.0
PKG_RELEASE:=4 PKG_RELEASE:=5
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=Manfred Jaider <info@masmbit.com> PKG_MAINTAINER:=Manfred Jaider <info@masmbit.com>
+25
View File
@@ -60,6 +60,31 @@ config openvpn 'instance1'
option config '/etc/openvpn/luci/instance1.conf' option config '/etc/openvpn/luci/instance1.conf'
``` ```
## Screenshots
<table>
<tr>
<td><img width="2442" height="1844" alt="01" src="https://github.com/user-attachments/assets/5f215704-c18e-42ba-9228-c100635d3836" /></td>
<td><img width="2398" height="1700" alt="03" src="https://github.com/user-attachments/assets/10ee2c26-6ca8-42c5-9f1e-04f73fb9c91c" /></td>
<td><img width="2384" height="1706" alt="04" src="https://github.com/user-attachments/assets/86f7c341-37e2-4be9-8b7a-458cff1cc722" /></td>
</tr>
<tr>
<td><img width="1796" height="556" alt="09" src="https://github.com/user-attachments/assets/c2d2b23d-b3b8-40f3-a1bf-b9d57f1ca4e3" /></td>
<td><img width="1798" height="496" alt="10" src="https://github.com/user-attachments/assets/30ff6070-def5-4dd8-a5a9-4de11fa14706" /></td>
<td><img width="1798" height="754" alt="13" src="https://github.com/user-attachments/assets/9d4d897c-18b1-44dc-93de-035984acf5db" /></td>
</tr>
<tr>
<td><img width="1800" height="1352" alt="11" src="https://github.com/user-attachments/assets/ae0c5c01-10f3-496a-b687-7b6a734ad322" /></td>
<td><img width="1798" height="1728" alt="12" src="https://github.com/user-attachments/assets/f5023929-99b5-438a-94e2-6042c3d352a9" /></td>
<td><img width="2412" height="1692" alt="08" src="https://github.com/user-attachments/assets/937b8f11-4743-4aca-bf63-8b5003ce7cda" /></td>
</tr>
</td>
<td><img width="2406" height="1834" alt="14" src="https://github.com/user-attachments/assets/f9d64535-4177-4b86-bd6a-f1611dc41da8" /></td>
<td><img width="2394" height="1808" alt="06" src="https://github.com/user-attachments/assets/008c6f5b-0636-43e2-afe6-1de835eab4af" /></td>
<td><img width="2382" height="1792" alt="05" src="https://github.com/user-attachments/assets/5694833e-2b03-43a2-b1ff-8f3afe11c8e0" /></td>
</tr>
</table>
## Directory Structure ## Directory Structure
```text ```text
+1 -1
View File
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2 PKG_NAME:=luci-app-passwall2
PKG_VERSION:=26.10.1 PKG_VERSION:=26.10.1
PKG_RELEASE:=144 PKG_RELEASE:=145
PKG_PO_VERSION:=$(PKG_VERSION) PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \ PKG_CONFIG_DEPENDS:= \
@@ -101,10 +101,11 @@ function index()
entry({"admin", "services", appname, "delete_select_shunt_rules"}, call("delete_select_shunt_rules")).leaf = true entry({"admin", "services", appname, "delete_select_shunt_rules"}, call("delete_select_shunt_rules")).leaf = true
--[[Components update]] --[[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 coms = require "luci.passwall2.com"
local 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, "check_" .. com}, call("com_check", com)).leaf = true
entry({"admin", "services", appname, "update_" .. com}, call("com_update", 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 entry({"admin", "services", appname, "version_" .. com}, call("com_version", com)).leaf = true
@@ -747,6 +748,30 @@ function app_check()
http_write_json(json) http_write_json(json)
end 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) function com_check(comname)
local json = api.to_check("", comname) local json = api.to_check("", comname)
http_write_json(json) http_write_json(json)
@@ -18,8 +18,8 @@ o:value("https://ghproxy.cc/", "ghproxy.cc")
o:value("https://mirror.ghproxy.com/", "ghproxy.com") o:value("https://mirror.ghproxy.com/", "ghproxy.com")
o:value("https://ghfast.top/", "ghfast.top") o:value("https://ghfast.top/", "ghfast.top")
local k, v for _, k in ipairs(com.order) do
for k, v in pairs(com) do local v = com[k]
o = s:option(Value, k:gsub("%-","_") .. "_file", translatef("%s App Path", v.name)) o = s:option(Value, k:gsub("%-","_") .. "_file", translatef("%s App Path", v.name))
o.default = v.default_path or ("/usr/bin/" .. k) o.default = v.default_path or ("/usr/bin/" .. k)
o.placeholder = o.default o.placeholder = o.default
@@ -498,6 +498,16 @@ if singbox_tags:find("with_quic") then
o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps")) o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps"))
o:depends({ protocol = "hysteria2" }) 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 = s:option(Value, "hysteria2_idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
o.datatype = "range(4,120)" o.datatype = "range(4,120)"
o:depends({ protocol = "hysteria2"}) o:depends({ protocol = "hysteria2"})
@@ -403,6 +403,16 @@ o:depends({ protocol = "hysteria2" })
o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps")) o = s:option(Value, "hysteria2_down_mbps", translate("Max download Mbps"))
o:depends({ protocol = "hysteria2" }) 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 = s:option(Value, "hysteria2_idle_timeout", translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
o.datatype = "range(4,120)" o.datatype = "range(4,120)"
o:depends({ protocol = "hysteria2"}) o:depends({ protocol = "hysteria2"})
+146 -48
View File
@@ -208,10 +208,11 @@ function get_cache_var(key)
return val return val
end end
function get_new_port(p) function get_new_port(start_port, protocol)
if not p then p = "auto" end start_port = tonumber(start_port) or "auto"
local cmd_format = ". /usr/share/passwall2/utils.sh ; echo -n $(get_new_port %s tcp,udp)" if protocol ~= "tcp" and protocol ~= "udp" then protocol = "tcp,udp" end
return tonumber(sys.exec(string.format(cmd_format, p))) 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 end
function exec_call(cmd) function exec_call(cmd)
@@ -448,9 +449,10 @@ function repeat_exist(table, value)
end end
function remove(...) function remove(...)
for index, value in ipairs({...}) do for i = 1, select("#", ...) do
if value and #value > 0 and value ~= "/" then local value = select(i, ...)
sys.call(string.format("rm -rf %s", value)) if type(value) == "string" and #value > 0 and value ~= "/" then
sys.call(string.format("rm -rf -- %s", value))
end end
end end
end end
@@ -1102,9 +1104,16 @@ function compare_versions(ver1, comp, ver2)
local n2 = table.getn(av2) local n2 = table.getn(av2)
if (max < n2) then max = n2 end 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 for i = 1, max, 1 do
local s1 = tonumber(av1[i] or 0) or 0 local s1 = version_part(av1[i] or 0)
local s2 = tonumber(av2[i] or 0) or 0 local s2 = version_part(av2[i] or 0)
if comp == "~=" and (s1 ~= s2) then return true end if comp == "~=" and (s1 ~= s2) then return true end
if (comp == "<" or 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 end
function to_download(app_name, url, size, task_id) function to_download(app_name, url, size, task_id, keep_files)
local result = check_path(app_name) local result = app_name == appname and {code = 0} or check_path(app_name)
if result.code ~= 0 then if result.code ~= 0 then
return result return result
end end
@@ -1280,7 +1289,7 @@ function to_download(app_name, url, size, task_id)
return {code = 1, error = i18n.translate("Download url is required.")} return {code = 1, error = i18n.translate("Download url is required.")}
end 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 local tmp_file
if type(task_id) == "string" and task_id:match("^[%w_-]+$") then 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 total_size = tonumber(total_size) or 0
local tmp_file = "/tmp/" .. app_name .. "_download." .. task_id local tmp_file = "/tmp/" .. app_name .. "_download." .. task_id
local downloaded = tonumber(fs.stat(tmp_file, "size")) or 0 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 local percent
if total_size > 0 then if total_size > 0 then
-- Leave 100% for the download request's success callback. -- Leave 100% for the download request's success callback.
@@ -1465,53 +1480,136 @@ function to_move(app_name,file)
end end
function get_version() function get_version()
local version = sys.exec("opkg list-installed luci-app-passwall2 2>/dev/null | awk '{print $3}'") local version_file = CACHE_PATH .. "/passwall2_version"
if not version or #version == 0 then local version = trim(fs.readfile(version_file) or "")
version = sys.exec("apk list luci-app-passwall2 2>/dev/null | awk '/installed/ {print $1}' | cut -d'-' -f4-") 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 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 end
function to_check_self() function to_check_self()
local url = "https://raw.githubusercontent.com/Openwrt-Passwall/openwrt-passwall2/main/luci-app-passwall2/Makefile" local release = get_api_json(com[appname]:get_url())
local tmp_file = "/tmp/passwall2_makefile" if type(release) == "table" and #release > 0 then release = release[1] end
local gh_proxy = uci_get_c("@global_app[0]", "gh_proxy_url") or "" if type(release) ~= "table" or not release.tag_name then
local return_code, result return {code = 1, error = i18n.translate("Get remote version info failed.")}
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)
end end
result = return_code == 0 local local_version = get_version()
if not result then local remote_version = release.tag_name:gsub("^v", ""):gsub("%-r(%d+)$", "-%1")
exec("/bin/rm", {"-f", tmp_file}) local package_version = remote_version:gsub("%-(%d+)$", "-r%1")
return { local language_version = remote_version:match("^(%d+%.%d+%.%d+)") or remote_version
code = 1, local apk = sys.call("command -v apk >/dev/null 2>&1") == 0
error = i18n.translatef("Failed") 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 end
local local_version = get_version() local main
local remote_version = sys.exec("echo -n $(grep '^PKG_VERSION' /tmp/passwall2_makefile | head -n 1 | awk -F '=' '{print $2}')") for _, asset in ipairs(release.assets or {}) do
exec("/bin/rm", {"-f", tmp_file}) if asset.name == main_name then main = asset end
if wanted[asset.name] then found[wanted[asset.name]] = asset end
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
}
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 { return {
code = 1, code = 0, has_update = compare_versions(local_version, "<", remote_version),
has_update = true, local_version = local_version, remote_version = remote_version, html_url = release.html_url,
local_version = local_version, data = main, i18n = selected, languages = table.concat(languages, ",")
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)
} }
end 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 </dev/null &")
return {code = 0}
end
function cacheFileCompareToLogic(file, str) function cacheFileCompareToLogic(file, str)
local result = nil local result = nil
if file and str then if file and str then
@@ -10,6 +10,18 @@ local function gh_pre_release_url(self)
return "https://github.com/Openwrt-Passwall/openwrt-passwall-packages/releases/download/api-cache/" .. string.lower(self.name) .. "-pre-release-api.json" return "https://github.com/Openwrt-Passwall/openwrt-passwall-packages/releases/download/api-cache/" .. string.lower(self.name) .. "-pre-release-api.json"
end end
_M.order = {
"geoview",
"xray",
"sing-box"
}
-- The LuCI package has its own update flow and is not a binary component.
_M.passwall2 = {
name = "PassWall2",
get_url = gh_release_url
}
_M["sing-box"] = { _M["sing-box"] = {
name = "Sing-Box", name = "Sing-Box",
repo = "SagerNet/sing-box", repo = "SagerNet/sing-box",
@@ -635,7 +635,9 @@ function gen_outbound(flag, node, tag, proxy_table)
t = tonumber(tostring(t or "0"):match("^%d+")) t = tonumber(tostring(t or "0"):match("^%d+"))
return (t and t >= 2 and t <= 60) and t .. "s" or nil return (t and t >= 2 and t <= 60) and t .. "s" or nil
end)(node.hysteria2_keep_alive_period), 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, tls = tls,
realm = node.hysteria2_realms and (function() realm = node.hysteria2_realms and (function()
result.server = nil result.server = nil
@@ -400,7 +400,9 @@ function gen_outbound(flag, node, tag, proxy_table)
t = tonumber(tostring(t or "0"):match("^%d+")) t = tonumber(tostring(t or "0"):match("^%d+"))
return (t and t >= 2 and t <= 60) and t or nil return (t and t >= 2 and t <= 60) and t or nil
end)(node.hysteria2_keep_alive_period), 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 end
if fragment and fragment_table and ({raw=1, ws=1, httpupgrade=1, grpc=1, xhttp=1})[TP] then if fragment and fragment_table and ({raw=1, ws=1, httpupgrade=1, grpc=1, xhttp=1})[TP] then
@@ -7,6 +7,7 @@ local com = self.com
<script type="text/javascript"> <script type="text/javascript">
//<![CDATA[ //<![CDATA[
var appname = '<%=api.appname%>';
var appInfoList = new Array(); var appInfoList = new Array();
var inProgressCount = 0; var inProgressCount = 0;
var tokenStr = '<%=token%>'; var tokenStr = '<%=token%>';
@@ -22,6 +23,25 @@ local com = self.com
var downloadingText = '<%:Downloading...%>'; var downloadingText = '<%:Downloading...%>';
var decompressioningText = '<%:Unpacking...%>'; var decompressioningText = '<%:Unpacking...%>';
var movingText = '<%:Moving...%>'; var movingText = '<%:Moving...%>';
var installingText = '<%:Installing...%>';
var installingNotice = '<%:Do not refresh or switch pages during installation.%>';
var updateFailedText = '<%:Update failed%>';
var unknownErrorText = '<%:Unknown error%>';
var componentUpdates = 0, luciUpdating = false;
function syncUpdateButtons() {
var buttons = document.querySelectorAll('#app_version input[data-update-app]');
for (var i = 0; i < buttons.length; i++) {
var btn = buttons[i];
btn.disabled = !!btn.updateDisabled || luciUpdating ||
(btn.getAttribute('data-update-app') === appname && componentUpdates > 0);
}
}
function setButtonDisabled(btn, disabled) {
btn.updateDisabled = disabled;
syncUpdateButtons();
}
//window.onload = function () {}; //window.onload = function () {};
@@ -35,8 +55,11 @@ local com = self.com
inProgressCount++; inProgressCount++;
} }
function removePageNotice() { function removePageNotice(app) {
if (app === appname) luciUpdating = false;
else if (app) componentUpdates--;
inProgressCount--; inProgressCount--;
syncUpdateButtons();
if (inProgressCount === 0) { if (inProgressCount === 0) {
window.onbeforeunload = undefined; window.onbeforeunload = undefined;
} }
@@ -46,21 +69,24 @@ local com = self.com
if (btn) { if (btn) {
btn.value = updateSuccessText; btn.value = updateSuccessText;
btn.placeholder = updateSuccessText; btn.placeholder = updateSuccessText;
btn.disabled = true; setButtonDisabled(btn, true);
} }
if (inProgressCount === 0) { if (inProgressCount === 0) {
window.setTimeout(function () { window.setTimeout(function () {
window.location.reload(); if (inProgressCount === 0 && !luciUpdating) window.location.reload();
}, 1000); }, 1000);
} }
} }
function onRequestError(btn, errorMessage) { function onRequestError(btn, errorMessage, checking) {
btn.disabled = false; setButtonDisabled(btn, false);
btn.value = retryText; btn.value = retryText;
var ckeckDetailElm = document.getElementById(btn.id + '-detail'); 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 (errorMessage && ckeckDetailElm) { if (errorMessage && ckeckDetailElm) {
ckeckDetailElm.textContent = errorMessage ckeckDetailElm.textContent = errorMessage
} }
@@ -75,7 +101,7 @@ local com = self.com
} }
function checkUpdate(btn, app) { function checkUpdate(btn, app) {
btn.disabled = true; setButtonDisabled(btn, true);
btn.value = inProgressText; btn.value = inProgressText;
addPageNotice(); addPageNotice();
@@ -89,13 +115,13 @@ local com = self.com
arch: '' arch: ''
}, function (x, json) { }, function (x, json) {
removePageNotice(); removePageNotice();
if (json.code) { if (!json || json.code) {
appInfoList[app] = undefined; appInfoList[app] = undefined;
onRequestError(btn, json.error); onRequestError(btn, json && json.error, true);
} else { } else {
appInfoList[app] = json; appInfoList[app] = json;
if (json.has_update) { if (json.has_update) {
btn.disabled = false; setButtonDisabled(btn, false);
btn.value = clickToUpdateText; btn.value = clickToUpdateText;
btn.placeholder = clickToUpdateText; btn.placeholder = clickToUpdateText;
@@ -110,7 +136,7 @@ local com = self.com
ckeckDetailElm.innerHTML = urlNode; ckeckDetailElm.innerHTML = urlNode;
} }
} else { } else {
btn.disabled = true; setButtonDisabled(btn, true);
btn.value = noUpdateText; btn.value = noUpdateText;
var forceBtn = document.getElementById('_' + app + '-force_btn'); var forceBtn = document.getElementById('_' + app + '-force_btn');
if (forceBtn) { if (forceBtn) {
@@ -122,7 +148,10 @@ local com = self.com
} }
function doUpdate(btn, app) { function doUpdate(btn, app) {
btn.disabled = true; if (luciUpdating || (app === appname && componentUpdates > 0)) return;
if (app === appname) luciUpdating = true;
else componentUpdates++;
setButtonDisabled(btn, true);
btn.value = downloadingText; btn.value = downloadingText;
addPageNotice(); addPageNotice();
@@ -130,7 +159,10 @@ local com = self.com
var appUpdateUrl = '<%=api.url("update_")%>' + app; var appUpdateUrl = '<%=api.url("update_")%>' + app;
var appInfo = appInfoList[app]; var appInfo = appInfoList[app];
var downloadId = Date.now().toString(36) + Math.random().toString(36).substring(2, 8); var downloadId = Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
var totalSize = appInfo && appInfo.data ? Number(appInfo.data.size) : 0; var downloadList = appInfo && appInfo.data ? [appInfo.data] : [];
if (app === appname && Array.isArray(appInfo.i18n)) downloadList = downloadList.concat(appInfo.i18n);
var totalSize = 0;
for (var i = 0; i < downloadList.length; i++) totalSize += Number(downloadList[i].size) || 0;
if (!isFinite(totalSize) || totalSize <= 0) { if (!isFinite(totalSize) || totalSize <= 0) {
totalSize = 0; totalSize = 0;
} }
@@ -169,41 +201,44 @@ local com = self.com
progressTimer = setInterval(updateDownloadProgress, 1000); progressTimer = setInterval(updateDownloadProgress, 1000);
} }
// Download file function download(part) {
XHR.get(appUpdateUrl, { var asset = downloadList[part - 1];
token: tokenStr, XHR.get(appUpdateUrl, {
url: appInfo ? appInfo.data.browser_download_url : '', token: tokenStr, url: asset.browser_download_url,
size: appInfo ? appInfo.data.size / 1024 : null, size: (app === appname && part === 1 ? totalSize : asset.size) / 1024,
id: downloadId id: downloadId, part: part
}, function (x, json) { }, function (x, json) {
stopDownloadProgress(); if (json && !json.code && app === appname && part < downloadList.length) {
if (json.code) { download(part + 1);
removePageNotice(); return;
onRequestError(btn, json.error); }
} else if (json.zip) { stopDownloadProgress();
btn.value = decompressioningText; if (!json || json.code) {
removePageNotice(app);
// Extract file onRequestError(btn, json && (json.error || updateFailedText));
XHR.get(appUpdateUrl, { } else if (app === appname) {
token: tokenStr, installLuci(btn, appUpdateUrl, downloadId);
task: 'extract', } else if (json.zip) {
file: json.file, btn.value = decompressioningText;
subfix: appInfo ? appInfo.type : '' XHR.get(appUpdateUrl, {
}, function (x, json) { token: tokenStr, task: 'extract', file: json.file, subfix: appInfo.type || ''
if (json.code) { }, function (x, json) {
removePageNotice(); if (!json || json.code) {
onRequestError(btn, json.error); removePageNotice(app);
} else { onRequestError(btn, json && json.error);
move(btn, appUpdateUrl, json.file); } else {
} move(btn, app, appUpdateUrl, json.file);
}, 300) }
} else { }, 300);
move(btn, appUpdateUrl, json.file); } else {
} move(btn, app, appUpdateUrl, json.file);
}, 300) }
}, 300);
}
download(1);
} }
function move(btn, url, file) { function move(btn, app, url, file) {
btn.value = movingText; btn.value = movingText;
// Move file to target dir // Move file to target dir
@@ -212,14 +247,56 @@ local com = self.com
task: 'move', task: 'move',
file: file file: file
}, function (x, json) { }, function (x, json) {
removePageNotice(); removePageNotice(app);
if (json.code) { if (!json || json.code) {
onRequestError(btn, json.error); onRequestError(btn, json && json.error);
} else { } else {
onUpdateSuccess(btn); onUpdateSuccess(btn);
} }
}, 300) }, 300)
} }
function installLuci(btn, url, id) {
btn.value = installingText;
var detail = document.getElementById(appname + '-check_btn-detail');
detail.textContent = installingNotice;
// Read the tiny result file even if rpcd restart invalidates the session.
var pending = false, finished = false;
var timer = setInterval(function() {
if (pending) return;
pending = true;
var xhr = new XMLHttpRequest();
xhr.open('GET', '<%=resource%>/' + appname + '-update/' + id + '.json?_=' + Date.now(), true);
xhr.timeout = 5000;
xhr.onerror = xhr.ontimeout = function() { pending = false; };
xhr.onload = function() {
pending = false;
if (finished) return;
var json;
try { json = JSON.parse(xhr.responseText); } catch (e) { return; }
if (typeof json.code !== 'number') return;
finished = true;
clearInterval(timer);
removePageNotice(appname);
if (json.code) onRequestError(btn, updateFailedText);
else {
window.onbeforeunload = undefined;
window.location.reload();
}
};
xhr.send();
}, 1000);
XHR.post(url, {token: tokenStr, task: 'install', id: id,
force: appInfoList[appname].has_update ? '0' : '1',
languages: appInfoList[appname].languages || ''}, function(x, json) {
if (!finished && json && json.code) {
finished = true;
clearInterval(timer);
removePageNotice(appname);
onRequestError(btn, updateFailedText);
}
}, 300);
}
//]]> //]]>
</script> </script>
@@ -228,14 +305,18 @@ local com = self.com
<div class="cbi-value-field"> <div class="cbi-value-field">
<div class="cbi-value-description"> <div class="cbi-value-description">
<span>【 <%=api.get_version()%> 】</span> <span>【 <%=api.get_version()%> 】</span>
<input class="btn cbi-button cbi-button-apply" type="button" id="passwall2-check_btn" <input class="btn cbi-button cbi-button-apply" type="button" id="<%=api.appname%>-check_btn" data-update-app="<%=api.appname%>"
onclick="onBtnClick(this,'passwall2');" value="<%:Check update%>" /> onclick="onBtnClick(this, appname);" value="<%:Check update%>" />
<span id="passwall2-check_btn-detail"></span> <input class="btn cbi-button cbi-button-apply" type="button" id="_<%=api.appname%>-force_btn" data-update-app="<%=api.appname%>"
onclick="doUpdate(this, appname);" value="<%:Force update%>" style="display:none" />
<span id="<%=api.appname%>-check_btn-detail"></span>
</div> </div>
</div> </div>
</div> </div>
<%for k, v in pairs(com) do%> <%for _, k in ipairs(com.order) do
local v = com[k]
%>
<div class="cbi-value"> <div class="cbi-value">
<label class="cbi-value-title"><%=v.name%> <label class="cbi-value-title"><%=v.name%>
<%:Version%> <%:Version%>
@@ -243,9 +324,9 @@ local com = self.com
<div class="cbi-value-field"> <div class="cbi-value-field">
<div class="cbi-value-description"> <div class="cbi-value-description">
<span>【 <span class="com_version" key="<%=k%>"><%=translate("Null")%></span> 】</span> <span>【 <span class="com_version" key="<%=k%>"><%=translate("Null")%></span> 】</span>
<input class="btn cbi-button cbi-button-apply" type="button" id="_<%=k%>-check_btn" <input class="btn cbi-button cbi-button-apply" type="button" id="_<%=k%>-check_btn" data-update-app="<%=k%>"
onclick="onBtnClick(this,'<%=k%>');" value="<%:Check update%>" /> onclick="onBtnClick(this,'<%=k%>');" value="<%:Check update%>" />
<input class="btn cbi-button cbi-button-apply" type="button" id="_<%=k%>-force_btn" <input class="btn cbi-button cbi-button-apply" type="button" id="_<%=k%>-force_btn" data-update-app="<%=k%>"
onclick="doUpdate(this,'<%=k%>');" value="<%:Force update%>" style="display:none"/> onclick="doUpdate(this,'<%=k%>');" value="<%:Force update%>" style="display:none"/>
<span id="_<%=k%>-check_btn-detail"></span> <span id="_<%=k%>-check_btn-detail"></span>
</div> </div>
@@ -262,7 +343,7 @@ local com = self.com
let key = coms[i].getAttribute("key"); let key = coms[i].getAttribute("key");
coms[i].innerHTML = "<%=translate("Collecting data...")%>"; coms[i].innerHTML = "<%=translate("Collecting data...")%>";
XHR.get('<%=api.url("version_")%>' + key, null, function(x, result) { XHR.get('<%=api.url("version_")%>' + key, null, function(x, result) {
if (x.status == 200 && result.code == 1) { if (x && x.status == 200 && result && result.code == 1) {
let data = result.data; let data = result.data;
let t = "<%=translate("Null")%>"; let t = "<%=translate("Null")%>";
if (data && data != "") if (data && data != "")
+15
View File
@@ -2486,6 +2486,9 @@ msgstr "فهرست مجموعه‌های رمز پشتیبانی‌شده را
msgid "This username already exists." msgid "This username already exists."
msgstr "این نام کاربری از قبل وجود دارد." msgstr "این نام کاربری از قبل وجود دارد."
msgid "Update Options"
msgstr "گزینه‌های به‌روزرسانی"
msgid "MASQUE Path" msgid "MASQUE Path"
msgstr "مسیر MASQUE" msgstr "مسیر MASQUE"
@@ -2500,3 +2503,15 @@ msgstr "کلید خصوصی دستگاه WARP"
msgid "WARP Endpoint Public Key" msgid "WARP Endpoint Public Key"
msgstr "کلید عمومی سرور WARP" msgstr "کلید عمومی سرور WARP"
msgid "Installing..."
msgstr "در حال نصب..."
msgid "Do not refresh or switch pages during installation."
msgstr "در حین نصب، صفحه را بازخوانی نکنید و به صفحه دیگری نروید."
msgid "Update failed"
msgstr "به‌روزرسانی ناموفق بود"
msgid "Unknown error"
msgstr "خطای ناشناخته"
+15
View File
@@ -2487,6 +2487,9 @@ msgstr "Задает список поддерживаемых наборов ш
msgid "This username already exists." msgid "This username already exists."
msgstr "Это имя пользователя уже существует." msgstr "Это имя пользователя уже существует."
msgid "Update Options"
msgstr "Параметры обновления"
msgid "MASQUE Path" msgid "MASQUE Path"
msgstr "Путь MASQUE" msgstr "Путь MASQUE"
@@ -2501,3 +2504,15 @@ msgstr "Приватный ключ устройства WARP"
msgid "WARP Endpoint Public Key" msgid "WARP Endpoint Public Key"
msgstr "Публичный ключ сервера WARP" msgstr "Публичный ключ сервера WARP"
msgid "Installing..."
msgstr "Установка..."
msgid "Do not refresh or switch pages during installation."
msgstr "Не обновляйте страницу и не переходите на другие страницы во время установки."
msgid "Update failed"
msgstr "Ошибка обновления"
msgid "Unknown error"
msgstr "Неизвестная ошибка"
+12
View File
@@ -2490,3 +2490,15 @@ msgstr "WARP 设备私钥"
msgid "WARP Endpoint Public Key" msgid "WARP Endpoint Public Key"
msgstr "WARP 服务端公钥" msgstr "WARP 服务端公钥"
msgid "Installing..."
msgstr "安装中"
msgid "Do not refresh or switch pages during installation."
msgstr "安装时请勿刷新页面和切换页面。"
msgid "Update failed"
msgstr "更新失败"
msgid "Unknown error"
msgstr "未知错误"
+15
View File
@@ -2478,6 +2478,9 @@ msgstr "配置支援的密碼套件列表。"
msgid "This username already exists." msgid "This username already exists."
msgstr "該用戶名已存在。" msgstr "該用戶名已存在。"
msgid "Update Options"
msgstr "更新選項"
msgid "MASQUE Path" msgid "MASQUE Path"
msgstr "MASQUE 路徑" msgstr "MASQUE 路徑"
@@ -2492,3 +2495,15 @@ msgstr "WARP 裝置私鑰"
msgid "WARP Endpoint Public Key" msgid "WARP Endpoint Public Key"
msgstr "WARP 伺服器公鑰" msgstr "WARP 伺服器公鑰"
msgid "Installing..."
msgstr "安裝中"
msgid "Do not refresh or switch pages during installation."
msgstr "安裝時請勿重新整理或切換頁面。"
msgid "Update failed"
msgstr "更新失敗"
msgid "Unknown error"
msgstr "未知錯誤"
@@ -42,7 +42,7 @@ uci commit firewall
chmod +x /usr/share/passwall2/*.sh chmod +x /usr/share/passwall2/*.sh
rm -f /tmp/luci-indexcache /tmp/luci-indexcache.* rm -f /tmp/etc/passwall2_tmp/passwall2_version /tmp/luci-indexcache /tmp/luci-indexcache.*
rm -rf /tmp/luci-modulecache/ rm -rf /tmp/luci-modulecache/
killall -HUP rpcd 2>/dev/null killall -HUP rpcd 2>/dev/null
@@ -619,6 +619,9 @@ start_crontab() {
return return
} }
local setsid_cmd=""
command -v setsid >/dev/null 2>&1 && setsid_cmd="setsid "
stop_week_mode=$(config_n_get @global_delay[0] stop_week_mode) stop_week_mode=$(config_n_get @global_delay[0] stop_week_mode)
stop_time_mode=$(config_n_get @global_delay[0] stop_time_mode) stop_time_mode=$(config_n_get @global_delay[0] stop_time_mode)
if [ -n "$stop_week_mode" ]; then if [ -n "$stop_week_mode" ]; then
@@ -626,7 +629,7 @@ start_crontab() {
stop_time_mm=$(echo $stop_time_mode | awk -F ':' '{print $2}') stop_time_mm=$(echo $stop_time_mode | awk -F ':' '{print $2}')
local t="$stop_time_mm $stop_time_hh * * $stop_week_mode" local t="$stop_time_mm $stop_time_hh * * $stop_week_mode"
[ "$stop_week_mode" = "7" ] && t="$stop_time_mm $stop_time_hh * * *" [ "$stop_week_mode" = "7" ] && t="$stop_time_mm $stop_time_hh * * *"
echo "$t /etc/init.d/$CONFIG stop cron > /dev/null 2>&1 &" >>/etc/crontabs/root echo "$t ${setsid_cmd}/etc/init.d/$CONFIG stop cron > /dev/null 2>&1 &" >>/etc/crontabs/root
log_i18n 0 "Scheduled tasks: Auto stop service." log_i18n 0 "Scheduled tasks: Auto stop service."
fi fi
@@ -637,7 +640,7 @@ start_crontab() {
start_time_mm=$(echo $start_time_mode | awk -F ':' '{print $2}') start_time_mm=$(echo $start_time_mode | awk -F ':' '{print $2}')
local t="$start_time_mm $start_time_hh * * $start_week_mode" local t="$start_time_mm $start_time_hh * * $start_week_mode"
[ "$start_week_mode" = "7" ] && t="$start_time_mm $start_time_hh * * *" [ "$start_week_mode" = "7" ] && t="$start_time_mm $start_time_hh * * *"
echo "$t /etc/init.d/$CONFIG start cron > /dev/null 2>&1 &" >>/etc/crontabs/root echo "$t ${setsid_cmd}/etc/init.d/$CONFIG start cron > /dev/null 2>&1 &" >>/etc/crontabs/root
log_i18n 0 "Scheduled tasks: Auto start service." log_i18n 0 "Scheduled tasks: Auto start service."
fi fi
@@ -651,7 +654,7 @@ start_crontab() {
if [ "$restart_week_mode" = "8" ]; then if [ "$restart_week_mode" = "8" ]; then
update_loop=1 update_loop=1
else else
echo "$t /etc/init.d/$CONFIG restart cron > /dev/null 2>&1 &" >>/etc/crontabs/root echo "$t ${setsid_cmd}/etc/init.d/$CONFIG restart cron > /dev/null 2>&1 &" >>/etc/crontabs/root
fi fi
log_i18n 0 "Scheduled tasks: Auto restart service." log_i18n 0 "Scheduled tasks: Auto restart service."
fi fi
@@ -666,7 +669,7 @@ start_crontab() {
if [ "$rules_update_week_mode" = "8" ]; then if [ "$rules_update_week_mode" = "8" ]; then
update_loop=1 update_loop=1
else else
echo "$t lua $APP_PATH/rule_update.lua log all cron > /dev/null 2>&1 &" >>/etc/crontabs/root echo "$t ${setsid_cmd}lua $APP_PATH/rule_update.lua log all cron > /dev/null 2>&1 &" >>/etc/crontabs/root
fi fi
log_i18n 0 "Scheduled tasks: Auto update rules." log_i18n 0 "Scheduled tasks: Auto update rules."
fi fi
@@ -695,7 +698,7 @@ start_crontab() {
if [ "$sub_update_week_mode" = "8" ]; then if [ "$sub_update_week_mode" = "8" ]; then
update_loop=1 update_loop=1
else else
echo "$t lua $APP_PATH/subscribe.lua start $cfgids cron > /dev/null 2>&1 &" >>/etc/crontabs/root echo "$t ${setsid_cmd}lua $APP_PATH/subscribe.lua start $cfgids cron > /dev/null 2>&1 &" >>/etc/crontabs/root
fi fi
done done
rm -rf $TMP_SUB_PATH rm -rf $TMP_SUB_PATH
@@ -1,17 +1,61 @@
#!/bin/sh #!/bin/sh
# dhcp.leases to hosts
# DHCP leases and static hosts to hosts
. /lib/functions.sh
. /usr/share/passwall2/utils.sh . /usr/share/passwall2/utils.sh
LOCK_FILE=${LOCK_PATH}/${CONFIG}_lease2hosts.lock LOCK_FILE=${LOCK_PATH}/${CONFIG}_lease2hosts.lock
LEASE_FILE=$(uci -q get dhcp.@dnsmasq[0].leasefile || echo "/tmp/dhcp.leases") DNSMASQ_LEASE_FILE=$(uci -q get 'dhcp.@dnsmasq[0].leasefile')
DNSMASQ_LEASE_FILE=${DNSMASQ_LEASE_FILE:-/tmp/dhcp.leases}
ODHCPD_LEASE_FILE=$(uci -q get dhcp.odhcpd.leasefile)
ODHCPD_LEASE_FILE=${ODHCPD_LEASE_FILE:-/tmp/odhcpd.leases}
HOSTS_FILE="$TMP_PATH2/dhcp-hosts" HOSTS_FILE="$TMP_PATH2/dhcp-hosts"
TMP_FILE="/tmp/dhcp-hosts.tmp" TMP_FILE="/tmp/dhcp-hosts.tmp"
exec 99>"$LOCK_FILE" exec 9>"$LOCK_FILE"
flock -n 99 flock -n 9 || exit 0
if [ "$?" != 0 ]; then
exit 0 read_static_host() {
fi local name ip
config_get name "$1" name
config_get ip "$1" ip
[ -n "$name" ] && [ "$name" != "*" ] && [ -n "$ip" ] && [ "$ip" != "ignore" ] || return
printf '%s %s\n' "$ip" "$name"
}
# Static DHCP configuration only needs to be read once at startup.
STATIC_HOSTS=$(
config_load dhcp
config_foreach read_static_host host
)
read_odhcpd_leases() {
[ -f "$ODHCPD_LEASE_FILE" ] || return
awk -v now="$(date +%s)" '
function valid_name(name) {
return name ~ /^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/
}
# State records: # interface DUID IAID hostname expiry assigned length address/prefix ...
$1 == "#" {
if (NF < 9 || !valid_name($5) || ($6 != -1 && $6 <= now))
next
# Only host addresses, never delegated IPv6 prefixes.
if (!(($4 == "ipv4" && $8 == 32) || ($4 != "ipv4" && $8 == 128)))
next
for (i = 9; i <= NF; i++) {
split($i, addr, "/")
print addr[1] " " $5
}
next
}
# Older odhcpd versions also include hosts-format lines, with optional aliases.
NF >= 2 && $1 ~ /^[0-9a-fA-F:.]+$/ {
for (i = 2; i <= NF && $i !~ /^#/; i++)
if (valid_name($i))
print $1 " " $i
}
' "$ODHCPD_LEASE_FILE"
}
reload_dnsmasq_pids() { reload_dnsmasq_pids() {
local pidfile pid local pidfile pid
@@ -27,22 +71,26 @@ reload_dnsmasq_pids() {
while true; do while true; do
if [ -f "$LEASE_FILE" ]; then {
awk 'NF >= 4 && $4 != "*" {print $3" "$4}' "$LEASE_FILE" | sort > "$TMP_FILE" [ -n "$STATIC_HOSTS" ] && printf '%s\n' "$STATIC_HOSTS"
if [ -s "$TMP_FILE" ]; then if [ -f "$DNSMASQ_LEASE_FILE" ]; then
if [ ! -f "$HOSTS_FILE" ] || ! cmp -s "$TMP_FILE" "$HOSTS_FILE"; then awk 'NF >= 4 && $4 != "*" {print $3" "$4}' "$DNSMASQ_LEASE_FILE"
mv "$TMP_FILE" "$HOSTS_FILE" fi
reload_dnsmasq_pids read_odhcpd_leases
else } | sort -u > "$TMP_FILE"
rm -f "$TMP_FILE" if [ -s "$TMP_FILE" ]; then
fi if [ ! -f "$HOSTS_FILE" ] || ! cmp -s "$TMP_FILE" "$HOSTS_FILE"; then
mv "$TMP_FILE" "$HOSTS_FILE"
reload_dnsmasq_pids
else else
if [ -s "$HOSTS_FILE" ]; then
: > "$HOSTS_FILE"
reload_dnsmasq_pids
fi
rm -f "$TMP_FILE" rm -f "$TMP_FILE"
fi fi
else
if [ -s "$HOSTS_FILE" ]; then
: > "$HOSTS_FILE"
reload_dnsmasq_pids
fi
rm -f "$TMP_FILE"
fi fi
sleep 60 sleep 60
+37
View File
@@ -0,0 +1,37 @@
#!/bin/sh
CONFIG=passwall2
dir="$1"
status="$2"
manager="$3"
force="$4"
[ "$manager" = apk ] && extension=apk || extension=ipk
set -- "$dir/luci-app-${CONFIG}.${extension}"
for package in "$dir"/luci-i18n-${CONFIG}-*.${extension}; do
[ -f "$package" ] && set -- "$@" "$package"
done
if [ "$manager" = apk ]; then
extra=""
[ "$force" = 1 ] && extra="--force-reinstall --force-overwrite"
apk add --allow-untrusted $extra "$@"
result=$?
else
extra=""
[ "$force" = 1 ] && extra="--force-reinstall"
opkg install $extra "$@"
result=$?
fi
rm -rf "$dir"
if [ "$result" = 0 ]; then
rm -f "/tmp/etc/${CONFIG}_tmp/${CONFIG}_version" /tmp/luci-indexcache /tmp/luci-indexcache.*
if [ -x /etc/init.d/rpcd ]; then
/etc/init.d/rpcd restart >/dev/null 2>&1
fi
if [ -x /etc/init.d/uhttpd ]; then
/etc/init.d/uhttpd reload >/dev/null 2>&1
fi
fi
[ "$result" = 0 ] && echo '{"code":0}' > "$status" || echo '{"code":1}' > "$status"
(sleep 60; rm -f "$status") >/dev/null 2>&1 &
@@ -2141,7 +2141,7 @@ local function curl(url, file, ua, mode, hwid)
"-fskL", "-w %{http_code}", "--retry 3", "--connect-timeout 3", "-H 'Accept: */*'", "-H 'Accept-Encoding: identity'" "-fskL", "-w %{http_code}", "--retry 3", "--connect-timeout 3", "-H 'Accept: */*'", "-H 'Accept-Encoding: identity'"
} }
if ua and ua ~= "" and ua ~= "curl" then if ua and ua ~= "" and ua ~= "curl" then
ua = (ua == "passwall2") and ("passwall2/" .. api.get_version()) or ua ua = (ua == "passwall2") and ("passwall2/" .. (api.get_version():match("^([^-]+)") or "")) or ua
curl_args[#curl_args + 1] = '--user-agent "' .. ua .. '"' curl_args[#curl_args + 1] = '--user-agent "' .. ua .. '"'
end end
if hwid == "1" then if hwid == "1" then
+1 -1
View File
@@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus
LUCI_PKGARCH:=all LUCI_PKGARCH:=all
PKG_NAME:=luci-app-ssr-plus PKG_NAME:=luci-app-ssr-plus
PKG_VERSION:=196 PKG_VERSION:=196
PKG_RELEASE:=90 PKG_RELEASE:=91
PKG_CONFIG_DEPENDS:= \ PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \ CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
@@ -2974,8 +2974,8 @@ start_rules() {
esac esac
} }
if [ "$(uci_get_by_type global dports)" = "3" ]; then if [ "$(uci_get_by_type global dports 1)" = "3" ]; then
custom_ports=$(uci_get_by_name "$GLOBAL_SERVER" custom_ports) # custom_ports 存储了用户自定义的端口 custom_ports=$(uci_get_by_type global custom_ports) # custom_ports 存储了用户自定义的端口
if [ -n "$custom_ports" ]; then if [ -n "$custom_ports" ]; then
proxyport="-m multiport --dports $custom_ports" proxyport="-m multiport --dports $custom_ports"
fi fi
@@ -3017,7 +3017,7 @@ start_rules() {
-G "$(uci_get_by_type access_control lan_gm_ips)" \ -G "$(uci_get_by_type access_control lan_gm_ips)" \
-m "$(uci_get_by_type access_control Interface)" \ -m "$(uci_get_by_type access_control Interface)" \
-D "$proxyport" \ -D "$proxyport" \
"$(get_arg_out)" "$(gfwmode)" "$ARG_UDP" "$ARG_UDP_RULES" "$ARG_A" $(get_arg_out) $(gfwmode) $ARG_UDP $ARG_UDP_RULES $ARG_A
return $? return $?
} }
+6 -6
View File
@@ -87,7 +87,7 @@ while [ "1" == "1" ]; do #死循环
#redir tcp #redir tcp
if [ "$redir_tcp_process" -gt 0 ]; then if [ "$redir_tcp_process" -gt 0 ]; then
icount=$(redir_tcp_running) icount=$(redir_tcp_running)
local tcp_expected="$redir_tcp_process" tcp_expected="$redir_tcp_process"
if [ "$GLOBAL_TYPE" = "socks5" ]; then if [ "$GLOBAL_TYPE" = "socks5" ]; then
tcp_expected="$EXPECTED_THREADS" tcp_expected="$EXPECTED_THREADS"
fi fi
@@ -101,7 +101,7 @@ while [ "1" == "1" ]; do #死循环
#redir udp #redir udp
if [ "$redir_udp_process" -gt 0 ]; then if [ "$redir_udp_process" -gt 0 ]; then
icount=$(redir_udp_running) icount=$(redir_udp_running)
local udp_expected="$redir_udp_process" udp_expected="$redir_udp_process"
if [ "$GLOBAL_TYPE" = "socks5" ]; then if [ "$GLOBAL_TYPE" = "socks5" ]; then
udp_expected="$EXPECTED_THREADS" udp_expected="$EXPECTED_THREADS"
fi fi
@@ -172,12 +172,12 @@ while [ "1" == "1" ]; do #死循环
echolog "chinadns-ng $dnsserver tunnel error.restart!" echolog "chinadns-ng $dnsserver tunnel error.restart!"
dnsserver=$(uci_get_by_type global chinadns_ng_tunnel_forward 8.8.4.4:53) dnsserver=$(uci_get_by_type global chinadns_ng_tunnel_forward 8.8.4.4:53)
kill -9 $(ps_list | grep "chinadns-ng -b 127.0.0.1 -l $tmp_dns_port" | grep -v grep | awk '{print $1}') >/dev/null 2>&1 kill -9 $(ps_list | grep "chinadns-ng -b 127.0.0.1 -l $tmp_dns_port" | grep -v grep | awk '{print $1}') >/dev/null 2>&1
local chinadns_ng_proto="$(uci_get_by_type global chinadns_ng_proto)" chinadns_ng_proto="$(uci_get_by_type global chinadns_ng_proto)"
local chinadns_ng_dns="" chinadns_ng_dns=""
IFS=',' IFS=','
for chinadns_ng_server in $dnsserver; do for chinadns_ng_server in $dnsserver; do
local chinadns_ng_ip="${chinadns_ng_server%%:*}" chinadns_ng_ip="${chinadns_ng_server%%:*}"
local chinadns_ng_port="${chinadns_ng_server##*:}" chinadns_ng_port="${chinadns_ng_server##*:}"
[ "$chinadns_ng_ip" = "$chinadns_ng_port" ] && chinadns_ng_port="53" [ "$chinadns_ng_ip" = "$chinadns_ng_port" ] && chinadns_ng_port="53"
chinadns_ng_tls_port="853" chinadns_ng_tls_port="853"
case "$chinadns_ng_proto" in case "$chinadns_ng_proto" in
@@ -1755,7 +1755,8 @@ var CBIWifiFrequencyValue = form.Value.extend({
configOnly ? {} : this.callDeviceInfo(device_section), configOnly ? {} : this.callDeviceInfo(device_section),
configOnly ? {} : L.resolveDefault(this.callWirelessStatus(), {}), configOnly ? {} : L.resolveDefault(this.callWirelessStatus(), {}),
!configOnly && isQcaWifiHwtype(hwtype) ? L.resolveDefault(fs.exec_direct('/usr/sbin/iw', [ 'dev' ]), '') : '', !configOnly && isQcaWifiHwtype(hwtype) ? L.resolveDefault(fs.exec_direct('/usr/sbin/iw', [ 'dev' ]), '') : '',
!configOnly && isQcaWifiHwtype(hwtype) ? L.resolveDefault(fs.exec_direct('/usr/sbin/iw', [ 'phy' ]), '') : '' !configOnly && isQcaWifiHwtype(hwtype) ? L.resolveDefault(fs.exec_direct('/usr/sbin/iw', [ 'phy' ]), '') : '',
isQcaWifiHwtype(hwtype) ? L.resolveDefault(fs.read('/sys/class/net/' + (uci.get('wireless', device_section, 'phy') || device_section) + '/device/uevent'), '') : ''
]).then(L.bind(function(data) { ]).then(L.bind(function(data) {
const wifidevs = data[0]; const wifidevs = data[0];
const freqlist = data[1]; const freqlist = data[1];
@@ -1775,6 +1776,7 @@ var CBIWifiFrequencyValue = form.Value.extend({
this.devinfo = devinfo; this.devinfo = devinfo;
this.devcfg = devcfg; this.devcfg = devcfg;
this.iwPhyMaxWidth = iwPhyName ? iwPhyWidthMap[iwPhyName] : null; this.iwPhyMaxWidth = iwPhyName ? iwPhyWidthMap[iwPhyName] : null;
this.qca8074v1 = /^OF_COMPATIBLE_[0-9]+=qcom,cnss-qca8074$/m.test(data[6]);
this.hwtype = hwtype; this.hwtype = hwtype;
this.channels = { this.channels = {
@@ -1889,7 +1891,7 @@ var CBIWifiFrequencyValue = form.Value.extend({
const qca_has_ac = qca_has_ax || has_ac || /^11ac/.test(hwval); const qca_has_ac = qca_has_ax || has_ac || /^11ac/.test(hwval);
const qca_has_n = qca_has_ac || hwmodelist.n || /^11n/.test(hwval); const qca_has_n = qca_has_ac || hwmodelist.n || /^11n/.test(hwval);
const qca_has_htinfo = (Object.keys(htmodelist).length > 0); const qca_has_htinfo = (Object.keys(htmodelist).length > 0);
const qca_max_width = this.iwPhyMaxWidth || Infinity; const qca_max_width = this.qca8074v1 ? 80 : (this.iwPhyMaxWidth || Infinity);
const qca_ht20 = !!(!qca_has_htinfo || htmodelist.HT20 || htmodelist.VHT20 || htmodelist.HE20 || htmodelist.EHT20 || /^HT20$/.test(htval)); const qca_ht20 = !!(!qca_has_htinfo || htmodelist.HT20 || htmodelist.VHT20 || htmodelist.HE20 || htmodelist.EHT20 || /^HT20$/.test(htval));
const qca_ht40 = !!(!qca_has_htinfo || htmodelist.HT40 || htmodelist.VHT40 || htmodelist.HE40 || htmodelist.EHT40 || /^HT40$/.test(htval)); const qca_ht40 = !!(!qca_has_htinfo || htmodelist.HT40 || htmodelist.VHT40 || htmodelist.HE40 || htmodelist.EHT40 || /^HT40$/.test(htval));
const qca_ht80 = (!!(!qca_has_htinfo || htmodelist.VHT80 || htmodelist.HE80 || htmodelist.EHT80 || /^HT80$/.test(htval)) && qca_max_width >= 80); const qca_ht80 = (!!(!qca_has_htinfo || htmodelist.VHT80 || htmodelist.HE80 || htmodelist.EHT80 || /^HT80$/.test(htval)) && qca_max_width >= 80);
@@ -1915,7 +1917,7 @@ var CBIWifiFrequencyValue = form.Value.extend({
'HT40', '40 MHz', { available: qca_ht40 }, 'HT40', '40 MHz', { available: qca_ht40 },
'HT80', '80 MHz', { available: qca_ht80 }, 'HT80', '80 MHz', { available: qca_ht80 },
'HT160', '160 MHz', { available: qca_ht160 }, 'HT160', '160 MHz', { available: qca_ht160 },
'HT80_80', '80+80 MHz', { available: /^HT80_80$/.test(htval) } 'HT80_80', '80+80 MHz', { available: !this.qca8074v1 && /^HT80_80$/.test(htval) }
], ],
'ax': [ 'ax': [
'HT20', '20 MHz', { available: qca_ht20 }, 'HT20', '20 MHz', { available: qca_ht20 },
@@ -11,10 +11,11 @@
"/usr/libexec/luci-peeraddr": [ "exec" ], "/usr/libexec/luci-peeraddr": [ "exec" ],
"/usr/sbin/wlanconfig": [ "exec" ], "/usr/sbin/wlanconfig": [ "exec" ],
"/proc/sys/net/ipv[46]/conf/*": [ "read" ], "/proc/sys/net/ipv[46]/conf/*": [ "read" ],
"/sys/class/net/*/brport/*": [ "read" ] "/sys/class/net/*/brport/*": [ "read" ],
"/sys/class/net/*/device/uevent": [ "read" ]
}, },
"ubus": { "ubus": {
"file": [ "exec" ], "file": [ "exec", "read" ],
"iwinfo": [ "assoclist", "countrylist", "devices", "freqlist", "info", "txpowerlist" ], "iwinfo": [ "assoclist", "countrylist", "devices", "freqlist", "info", "txpowerlist" ],
"luci": [ "getSwconfigFeatures", "getSwconfigPortState" ], "luci": [ "getSwconfigFeatures", "getSwconfigPortState" ],
"network.wireless": [ "status" ] "network.wireless": [ "status" ]
+2 -2
View File
@@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=natflow PKG_NAME:=natflow
PKG_VERSION:=20260930 PKG_VERSION:=20260930
PKG_RELEASE:=93 PKG_RELEASE:=94
PKG_SOURCE:=$(PKG_VERSION).tar.xz PKG_SOURCE:=$(PKG_VERSION).tar.xz
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
PKG_SOURCE_PROTO:=git PKG_SOURCE_PROTO:=git
PKG_SOURCE_VERSION:=252deb61bc1df3f928cfc98b9474f2d3d59e62f2 PKG_SOURCE_VERSION:=68cc4c87a23a1afbdb194bd3d3f9bb64d767f6ba
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com> PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
PKG_LICENSE:=GPL-2.0 PKG_LICENSE:=GPL-2.0