mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-02 05:33:56 +08:00
🦄 Sync 2026-10-17 20:40:04
This commit is contained in:
1 parent
315f273687
commit
c9ccf6f883
37 files changed
+716
-171
No files matched your search
+2
-2
@@ -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)?
|
||||||
|
|||||||
@@ -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])); });
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"})
|
||||||
|
|||||||
@@ -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 != "")
|
||||||
|
|||||||
@@ -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 "خطای ناشناخته"
|
||||||
@@ -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 "Неизвестная ошибка"
|
||||||
@@ -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 "未知错误"
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 $?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in new issue
Block a user