🦄 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
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)?
+2 -2
View File
@@ -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 <https://github.com/ophub/luci-app-amlogic>
@@ -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) {
@@ -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].') +
'<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');
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])); });
+1 -1
View File
@@ -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
@@ -328,13 +328,37 @@ hp_probe_dns() {
# hp_probe_tcp <port>
# 0 something accepted a connection on loopback:<port>, 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
}
+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/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). |
@@ -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
@@ -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
+2 -3
View File
@@ -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
+1 -1
View File
@@ -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 <info@masmbit.com>
+25
View File
@@ -60,6 +60,31 @@ config openvpn 'instance1'
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
```text
+1 -1
View File
@@ -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:= \
@@ -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)
@@ -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
@@ -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"})
@@ -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"})
+146 -48
View File
@@ -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 </dev/null &")
return {code = 0}
end
function cacheFileCompareToLogic(file, str)
local result = nil
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"
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"] = {
name = "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+"))
return (t and t >= 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
@@ -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
@@ -7,6 +7,7 @@ local com = self.com
<script type="text/javascript">
//<![CDATA[
var appname = '<%=api.appname%>';
var appInfoList = new Array();
var inProgressCount = 0;
var tokenStr = '<%=token%>';
@@ -22,6 +23,25 @@ local com = self.com
var downloadingText = '<%:Downloading...%>';
var decompressioningText = '<%:Unpacking...%>';
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 () {};
@@ -35,8 +55,11 @@ local com = self.com
inProgressCount++;
}
function removePageNotice() {
function removePageNotice(app) {
if (app === appname) luciUpdating = false;
else if (app) componentUpdates--;
inProgressCount--;
syncUpdateButtons();
if (inProgressCount === 0) {
window.onbeforeunload = undefined;
}
@@ -46,21 +69,24 @@ local com = self.com
if (btn) {
btn.value = updateSuccessText;
btn.placeholder = updateSuccessText;
btn.disabled = true;
setButtonDisabled(btn, true);
}
if (inProgressCount === 0) {
window.setTimeout(function () {
window.location.reload();
if (inProgressCount === 0 && !luciUpdating) window.location.reload();
}, 1000);
}
}
function onRequestError(btn, errorMessage) {
btn.disabled = false;
function onRequestError(btn, errorMessage, checking) {
setButtonDisabled(btn, false);
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) {
ckeckDetailElm.textContent = errorMessage
}
@@ -75,7 +101,7 @@ local com = self.com
}
function checkUpdate(btn, app) {
btn.disabled = true;
setButtonDisabled(btn, true);
btn.value = inProgressText;
addPageNotice();
@@ -89,13 +115,13 @@ local com = self.com
arch: ''
}, function (x, json) {
removePageNotice();
if (json.code) {
if (!json || json.code) {
appInfoList[app] = undefined;
onRequestError(btn, json.error);
onRequestError(btn, json && json.error, true);
} else {
appInfoList[app] = json;
if (json.has_update) {
btn.disabled = false;
setButtonDisabled(btn, false);
btn.value = clickToUpdateText;
btn.placeholder = clickToUpdateText;
@@ -110,7 +136,7 @@ local com = self.com
ckeckDetailElm.innerHTML = urlNode;
}
} else {
btn.disabled = true;
setButtonDisabled(btn, true);
btn.value = noUpdateText;
var forceBtn = document.getElementById('_' + app + '-force_btn');
if (forceBtn) {
@@ -122,7 +148,10 @@ local com = self.com
}
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;
addPageNotice();
@@ -130,7 +159,10 @@ local com = self.com
var appUpdateUrl = '<%=api.url("update_")%>' + app;
var appInfo = appInfoList[app];
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) {
totalSize = 0;
}
@@ -169,41 +201,44 @@ local com = self.com
progressTimer = setInterval(updateDownloadProgress, 1000);
}
// Download file
XHR.get(appUpdateUrl, {
token: tokenStr,
url: appInfo ? appInfo.data.browser_download_url : '',
size: appInfo ? appInfo.data.size / 1024 : null,
id: downloadId
}, function (x, json) {
stopDownloadProgress();
if (json.code) {
removePageNotice();
onRequestError(btn, json.error);
} else if (json.zip) {
btn.value = decompressioningText;
// Extract file
XHR.get(appUpdateUrl, {
token: tokenStr,
task: 'extract',
file: json.file,
subfix: appInfo ? appInfo.type : ''
}, function (x, json) {
if (json.code) {
removePageNotice();
onRequestError(btn, json.error);
} else {
move(btn, appUpdateUrl, json.file);
}
}, 300)
} else {
move(btn, appUpdateUrl, json.file);
}
}, 300)
function download(part) {
var asset = downloadList[part - 1];
XHR.get(appUpdateUrl, {
token: tokenStr, url: asset.browser_download_url,
size: (app === appname && part === 1 ? totalSize : asset.size) / 1024,
id: downloadId, part: part
}, function (x, json) {
if (json && !json.code && app === appname && part < downloadList.length) {
download(part + 1);
return;
}
stopDownloadProgress();
if (!json || json.code) {
removePageNotice(app);
onRequestError(btn, json && (json.error || updateFailedText));
} else if (app === appname) {
installLuci(btn, appUpdateUrl, downloadId);
} else if (json.zip) {
btn.value = decompressioningText;
XHR.get(appUpdateUrl, {
token: tokenStr, task: 'extract', file: json.file, subfix: appInfo.type || ''
}, function (x, json) {
if (!json || json.code) {
removePageNotice(app);
onRequestError(btn, json && json.error);
} else {
move(btn, app, appUpdateUrl, json.file);
}
}, 300);
} else {
move(btn, app, appUpdateUrl, json.file);
}
}, 300);
}
download(1);
}
function move(btn, url, file) {
function move(btn, app, url, file) {
btn.value = movingText;
// Move file to target dir
@@ -212,14 +247,56 @@ local com = self.com
task: 'move',
file: file
}, function (x, json) {
removePageNotice();
if (json.code) {
onRequestError(btn, json.error);
removePageNotice(app);
if (!json || json.code) {
onRequestError(btn, json && json.error);
} else {
onUpdateSuccess(btn);
}
}, 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>
@@ -228,14 +305,18 @@ local com = self.com
<div class="cbi-value-field">
<div class="cbi-value-description">
<span>【 <%=api.get_version()%> 】</span>
<input class="btn cbi-button cbi-button-apply" type="button" id="passwall2-check_btn"
onclick="onBtnClick(this,'passwall2');" value="<%:Check update%>" />
<span id="passwall2-check_btn-detail"></span>
<input class="btn cbi-button cbi-button-apply" type="button" id="<%=api.appname%>-check_btn" data-update-app="<%=api.appname%>"
onclick="onBtnClick(this, appname);" value="<%:Check update%>" />
<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>
<%for k, v in pairs(com) do%>
<%for _, k in ipairs(com.order) do
local v = com[k]
%>
<div class="cbi-value">
<label class="cbi-value-title"><%=v.name%>
<%:Version%>
@@ -243,9 +324,9 @@ local com = self.com
<div class="cbi-value-field">
<div class="cbi-value-description">
<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%>" />
<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"/>
<span id="_<%=k%>-check_btn-detail"></span>
</div>
@@ -262,7 +343,7 @@ local com = self.com
let key = coms[i].getAttribute("key");
coms[i].innerHTML = "<%=translate("Collecting data...")%>";
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 t = "<%=translate("Null")%>";
if (data && data != "")
+15
View File
@@ -2486,6 +2486,9 @@ msgstr "فهرست مجموعه‌های رمز پشتیبانی‌شده را
msgid "This username already exists."
msgstr "این نام کاربری از قبل وجود دارد."
msgid "Update Options"
msgstr "گزینه‌های به‌روزرسانی"
msgid "MASQUE Path"
msgstr "مسیر MASQUE"
@@ -2500,3 +2503,15 @@ msgstr "کلید خصوصی دستگاه WARP"
msgid "WARP Endpoint Public Key"
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."
msgstr "Это имя пользователя уже существует."
msgid "Update Options"
msgstr "Параметры обновления"
msgid "MASQUE Path"
msgstr "Путь MASQUE"
@@ -2501,3 +2504,15 @@ msgstr "Приватный ключ устройства WARP"
msgid "WARP Endpoint Public Key"
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"
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."
msgstr "該用戶名已存在。"
msgid "Update Options"
msgstr "更新選項"
msgid "MASQUE Path"
msgstr "MASQUE 路徑"
@@ -2492,3 +2495,15 @@ msgstr "WARP 裝置私鑰"
msgid "WARP Endpoint Public Key"
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
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/
killall -HUP rpcd 2>/dev/null
@@ -619,6 +619,9 @@ start_crontab() {
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_time_mode=$(config_n_get @global_delay[0] stop_time_mode)
if [ -n "$stop_week_mode" ]; then
@@ -626,7 +629,7 @@ start_crontab() {
stop_time_mm=$(echo $stop_time_mode | awk -F ':' '{print $2}')
local t="$stop_time_mm $stop_time_hh * * $stop_week_mode"
[ "$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."
fi
@@ -637,7 +640,7 @@ start_crontab() {
start_time_mm=$(echo $start_time_mode | awk -F ':' '{print $2}')
local t="$start_time_mm $start_time_hh * * $start_week_mode"
[ "$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."
fi
@@ -651,7 +654,7 @@ start_crontab() {
if [ "$restart_week_mode" = "8" ]; then
update_loop=1
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
log_i18n 0 "Scheduled tasks: Auto restart service."
fi
@@ -666,7 +669,7 @@ start_crontab() {
if [ "$rules_update_week_mode" = "8" ]; then
update_loop=1
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
log_i18n 0 "Scheduled tasks: Auto update rules."
fi
@@ -695,7 +698,7 @@ start_crontab() {
if [ "$sub_update_week_mode" = "8" ]; then
update_loop=1
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
done
rm -rf $TMP_SUB_PATH
@@ -1,17 +1,61 @@
#!/bin/sh
# dhcp.leases to hosts
# DHCP leases and static hosts to hosts
. /lib/functions.sh
. /usr/share/passwall2/utils.sh
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"
TMP_FILE="/tmp/dhcp-hosts.tmp"
exec 99>"$LOCK_FILE"
flock -n 99
if [ "$?" != 0 ]; then
exit 0
fi
exec 9>"$LOCK_FILE"
flock -n 9 || exit 0
read_static_host() {
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() {
local pidfile pid
@@ -27,22 +71,26 @@ reload_dnsmasq_pids() {
while true; do
if [ -f "$LEASE_FILE" ]; then
awk 'NF >= 4 && $4 != "*" {print $3" "$4}' "$LEASE_FILE" | sort > "$TMP_FILE"
if [ -s "$TMP_FILE" ]; then
if [ ! -f "$HOSTS_FILE" ] || ! cmp -s "$TMP_FILE" "$HOSTS_FILE"; then
mv "$TMP_FILE" "$HOSTS_FILE"
reload_dnsmasq_pids
else
rm -f "$TMP_FILE"
fi
{
[ -n "$STATIC_HOSTS" ] && printf '%s\n' "$STATIC_HOSTS"
if [ -f "$DNSMASQ_LEASE_FILE" ]; then
awk 'NF >= 4 && $4 != "*" {print $3" "$4}' "$DNSMASQ_LEASE_FILE"
fi
read_odhcpd_leases
} | sort -u > "$TMP_FILE"
if [ -s "$TMP_FILE" ]; then
if [ ! -f "$HOSTS_FILE" ] || ! cmp -s "$TMP_FILE" "$HOSTS_FILE"; then
mv "$TMP_FILE" "$HOSTS_FILE"
reload_dnsmasq_pids
else
if [ -s "$HOSTS_FILE" ]; then
: > "$HOSTS_FILE"
reload_dnsmasq_pids
fi
rm -f "$TMP_FILE"
fi
else
if [ -s "$HOSTS_FILE" ]; then
: > "$HOSTS_FILE"
reload_dnsmasq_pids
fi
rm -f "$TMP_FILE"
fi
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'"
}
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 .. '"'
end
if hwid == "1" then
+1 -1
View File
@@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-ssr-plus
PKG_VERSION:=196
PKG_RELEASE:=90
PKG_RELEASE:=91
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
@@ -2974,8 +2974,8 @@ start_rules() {
esac
}
if [ "$(uci_get_by_type global dports)" = "3" ]; then
custom_ports=$(uci_get_by_name "$GLOBAL_SERVER" custom_ports) # custom_ports 存储了用户自定义的端口
if [ "$(uci_get_by_type global dports 1)" = "3" ]; then
custom_ports=$(uci_get_by_type global custom_ports) # custom_ports 存储了用户自定义的端口
if [ -n "$custom_ports" ]; then
proxyport="-m multiport --dports $custom_ports"
fi
@@ -3017,7 +3017,7 @@ start_rules() {
-G "$(uci_get_by_type access_control lan_gm_ips)" \
-m "$(uci_get_by_type access_control Interface)" \
-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 $?
}
+6 -6
View File
@@ -87,7 +87,7 @@ while [ "1" == "1" ]; do #死循环
#redir tcp
if [ "$redir_tcp_process" -gt 0 ]; then
icount=$(redir_tcp_running)
local tcp_expected="$redir_tcp_process"
tcp_expected="$redir_tcp_process"
if [ "$GLOBAL_TYPE" = "socks5" ]; then
tcp_expected="$EXPECTED_THREADS"
fi
@@ -101,7 +101,7 @@ while [ "1" == "1" ]; do #死循环
#redir udp
if [ "$redir_udp_process" -gt 0 ]; then
icount=$(redir_udp_running)
local udp_expected="$redir_udp_process"
udp_expected="$redir_udp_process"
if [ "$GLOBAL_TYPE" = "socks5" ]; then
udp_expected="$EXPECTED_THREADS"
fi
@@ -172,12 +172,12 @@ while [ "1" == "1" ]; do #死循环
echolog "chinadns-ng $dnsserver tunnel error.restart!"
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
local chinadns_ng_proto="$(uci_get_by_type global chinadns_ng_proto)"
local chinadns_ng_dns=""
chinadns_ng_proto="$(uci_get_by_type global chinadns_ng_proto)"
chinadns_ng_dns=""
IFS=','
for chinadns_ng_server in $dnsserver; do
local chinadns_ng_ip="${chinadns_ng_server%%:*}"
local chinadns_ng_port="${chinadns_ng_server##*:}"
chinadns_ng_ip="${chinadns_ng_server%%:*}"
chinadns_ng_port="${chinadns_ng_server##*:}"
[ "$chinadns_ng_ip" = "$chinadns_ng_port" ] && chinadns_ng_port="53"
chinadns_ng_tls_port="853"
case "$chinadns_ng_proto" in
@@ -1755,7 +1755,8 @@ var CBIWifiFrequencyValue = form.Value.extend({
configOnly ? {} : this.callDeviceInfo(device_section),
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', [ '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) {
const wifidevs = data[0];
const freqlist = data[1];
@@ -1775,6 +1776,7 @@ var CBIWifiFrequencyValue = form.Value.extend({
this.devinfo = devinfo;
this.devcfg = devcfg;
this.iwPhyMaxWidth = iwPhyName ? iwPhyWidthMap[iwPhyName] : null;
this.qca8074v1 = /^OF_COMPATIBLE_[0-9]+=qcom,cnss-qca8074$/m.test(data[6]);
this.hwtype = hwtype;
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_n = qca_has_ac || hwmodelist.n || /^11n/.test(hwval);
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_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);
@@ -1915,7 +1917,7 @@ var CBIWifiFrequencyValue = form.Value.extend({
'HT40', '40 MHz', { available: qca_ht40 },
'HT80', '80 MHz', { available: qca_ht80 },
'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': [
'HT20', '20 MHz', { available: qca_ht20 },
@@ -11,10 +11,11 @@
"/usr/libexec/luci-peeraddr": [ "exec" ],
"/usr/sbin/wlanconfig": [ "exec" ],
"/proc/sys/net/ipv[46]/conf/*": [ "read" ],
"/sys/class/net/*/brport/*": [ "read" ]
"/sys/class/net/*/brport/*": [ "read" ],
"/sys/class/net/*/device/uevent": [ "read" ]
},
"ubus": {
"file": [ "exec" ],
"file": [ "exec", "read" ],
"iwinfo": [ "assoclist", "countrylist", "devices", "freqlist", "info", "txpowerlist" ],
"luci": [ "getSwconfigFeatures", "getSwconfigPortState" ],
"network.wireless": [ "status" ]
+2 -2
View File
@@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=natflow
PKG_VERSION:=20260930
PKG_RELEASE:=93
PKG_RELEASE:=94
PKG_SOURCE:=$(PKG_VERSION).tar.xz
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
PKG_SOURCE_PROTO:=git
PKG_SOURCE_VERSION:=252deb61bc1df3f928cfc98b9474f2d3d59e62f2
PKG_SOURCE_VERSION:=68cc4c87a23a1afbdb194bd3d3f9bb64d767f6ba
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
PKG_LICENSE:=GPL-2.0