update 2026-11-02 20:03:25

This commit is contained in:
action committed 2026-11-02 20:03:25 +08:00
1 parent d9d19b2880
commit 8a02ade817
55 files changed
+3023 -3443

No files matched your search

@@ -1091,7 +1091,6 @@ set clashoo_china6 {
2402:e2c0::/32,
2402:e3c0::/32,
2402:e480::/32,
2402:e540::/32,
2402:e680::/32,
2402:e740::/32,
2402:e780::/32,
@@ -2657,7 +2656,6 @@ set clashoo_china6 {
2406:840:fee8::/46,
2406:840:feec::/48,
2406:840:feee::/47,
2406:840:feff::/48,
2406:840:ff00::/40,
2406:880::/32,
2406:8c0::/32,
@@ -3387,7 +3385,16 @@ set clashoo_china6 {
2a0a:d685:220::/43,
2a0a:d685:240::/42,
2a0a:d685:280::/41,
2a0a:d685:400::/38,
2a0a:d685:400::/40,
2a0a:d685:500::/45,
2a0a:d685:508::/48,
2a0a:d685:50a::/47,
2a0a:d685:50c::/46,
2a0a:d685:510::/44,
2a0a:d685:520::/43,
2a0a:d685:540::/42,
2a0a:d685:580::/41,
2a0a:d685:600::/39,
2a0a:d685:800::/37,
2a0a:d685:1000::/36,
2a0a:d685:2000::/35,
@@ -394,13 +394,16 @@ set clashoo_china {
43.92.0.0/15,
43.94.0.0/18,
43.96.0.0/23,
43.96.2.130/32,
43.96.3.0/24,
43.96.4.0/22,
43.96.8.0/22,
43.96.16.130/32,
43.96.18.0/24,
43.96.20.0/22,
43.96.24.0/22,
43.96.32.0/22,
43.96.36.130/32,
43.96.40.0/24,
43.96.42.0/24,
43.96.44.0/23,
@@ -411,6 +414,7 @@ set clashoo_china {
43.96.62.0/23,
43.96.64.0/21,
43.96.72.0/22,
43.96.77.131/32,
43.96.78.0/24,
43.96.80.0/23,
43.96.85.0/24,
@@ -1007,21 +1011,12 @@ set clashoo_china {
47.77.128.0/17,
47.78.0.0/15,
47.80.0.0/14,
47.84.0.0/16,
47.85.0.0/17,
47.85.128.0/23,
47.85.131.0/24,
47.85.132.0/22,
47.85.136.0/21,
47.85.144.0/20,
47.85.160.0/19,
47.85.192.0/18,
47.84.0.0/15,
47.86.0.0/16,
47.87.0.0/20,
47.87.16.0/21,
47.87.32.0/19,
47.87.64.0/21,
47.87.72.0/24,
47.87.80.0/20,
47.87.96.0/19,
47.87.128.0/20,
@@ -1528,7 +1523,8 @@ set clashoo_china {
103.19.40.0/21,
103.19.64.0/21,
103.19.72.0/22,
103.19.232.0/22,
103.19.233.0/24,
103.19.234.0/23,
103.20.12.0/22,
103.20.32.0/24,
103.20.34.0/23,
@@ -2214,7 +2210,7 @@ set clashoo_china {
103.98.232.0/22,
103.98.240.0/20,
103.99.53.0/24,
103.99.54.0/23,
103.99.55.0/24,
103.99.56.0/22,
103.99.62.0/24,
103.99.104.0/22,
@@ -2267,7 +2263,6 @@ set clashoo_china {
103.104.104.0/22,
103.104.152.0/22,
103.104.188.0/22,
103.104.198.0/23,
103.104.252.0/22,
103.105.0.0/21,
103.105.12.0/22,
@@ -2678,7 +2673,6 @@ set clashoo_china {
103.186.228.0/23,
103.189.92.0/23,
103.189.152.0/22,
103.189.197.0/24,
103.190.20.0/23,
103.190.71.0/24,
103.190.104.0/23,
@@ -3639,7 +3633,7 @@ set clashoo_china {
117.134.128.0/18,
117.134.208.0/23,
117.134.212.0/23,
117.134.216.0/22,
117.134.218.0/23,
117.134.222.0/23,
117.134.232.0/21,
117.134.240.0/20,
@@ -3729,7 +3723,9 @@ set clashoo_china {
119.19.0.0/16,
119.20.0.0/14,
119.27.64.0/18,
119.27.128.0/17,
119.27.128.0/18,
119.27.208.0/20,
119.27.224.0/20,
119.28.28.0/24,
119.28.240.0/20,
119.29.0.0/16,
@@ -4240,7 +4236,7 @@ set clashoo_china {
142.86.224.0/20,
142.86.240.0/21,
142.86.248.0/22,
142.86.254.0/23,
142.86.255.0/24,
143.64.0.0/16,
144.0.0.0/16,
144.7.0.0/16,
@@ -4514,7 +4510,6 @@ set clashoo_china {
163.204.0.0/16,
163.223.28.0/23,
163.223.32.0/23,
163.223.72.0/23,
163.223.124.0/23,
163.223.222.0/23,
163.227.34.0/23,
@@ -6299,8 +6294,7 @@ set clashoo_china {
211.144.0.0/14,
211.148.0.0/16,
211.149.0.0/19,
211.149.33.0/24,
211.149.34.0/23,
211.149.32.0/22,
211.149.48.0/21,
211.149.64.0/21,
211.149.80.0/21,
Binary file not shown.
Binary file not shown.
+6 -6
View File
@@ -5,7 +5,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=honk
PKG_VERSION:=2026.11.01
PKG_VERSION:=2026.11.02
PKG_RELEASE:=1
# Pinned honk-core revision (upstream daeuniverse/honk main), cross-compiled by
@@ -14,10 +14,10 @@ PKG_RELEASE:=1
# assets behind CORE_COMMIT, so a plain feed build downloads and verifies them
# without any generated file. A staged run may still override them through
# honk/generated-stage.mk, which is generated and never committed.
CORE_COMMIT:=54b2e1cfbe1e7f8a93dba3a0f64fabf2a9f88e38
CORE_COMMIT:=ce0380dc65d1eb933da7241bf88c287231efce70
# Immutable source asset tag holding this pin's core archives.
HONK_SOURCE_TAG?=source-41281523460
HONK_SOURCE_TAG?=source-41400973213
HONK_SOURCE_URL?=https://github.com/kenzok8/openwrt-honk/releases/download/$(HONK_SOURCE_TAG)
-include $(CURDIR)/generated-stage.mk
@@ -25,9 +25,9 @@ HONK_SOURCE_URL?=https://github.com/kenzok8/openwrt-honk/releases/download/$(HON
HONK_ASSET_X86_64?=honk-core-$(CORE_COMMIT)-x86_64.tar.gz
HONK_ASSET_AARCH64?=honk-core-$(CORE_COMMIT)-aarch64.tar.gz
HONK_ASSET_ARMV7?=honk-core-$(CORE_COMMIT)-armv7.tar.gz
HONK_HASH_X86_64?=a5e46463505cc6ca15d704d77b57eba37b3ffe5d71498af928f052f612724deb
HONK_HASH_AARCH64?=98084529a1186ecc60d0b4882a0d419cc127b7d7c86ad5952ae0eda236709274
HONK_HASH_ARMV7?=e1f2a1dff427220301e4a343202e2c2c2b5efd8e996423ca981c6e0145540d60
HONK_HASH_X86_64?=cd465147cf1db1754a651be23d31124cca1cf8c1562744c4aacb37eff12acb43
HONK_HASH_AARCH64?=28eb047028d0f5f9b8f6e4b44b0834ca3f5c8231b681b3b288b865abb5aa5f44
HONK_HASH_ARMV7?=65ca485ceb09002bc96fd88c886f768483defd5ce3d0976136f0764028a6b875
# ARCH_PACKAGES is the package arch (e.g. arm_cortex-a7_neon-vfpv4);
# ARCH is the CPU arch (x86_64/aarch64/arm) and coincides for 64-bit.
+6
View File
@@ -2,4 +2,10 @@
# 开机自启是内置行为:装上就 enable,是否真正启动由 honk.main.enabled 决定,
# 不再提供单独的 boot_enabled 开关。
/etc/init.d/honk enable
# 配置模式已取消:只清遗留键,活动配置与启停状态一概不动。
legacy_mode=$(uci -q get honk.main.config_mode 2>/dev/null)
if [ -n "$legacy_mode" ]; then
uci -q delete honk.main.config_mode && uci -q commit honk
fi
exit 0
+16 -21
View File
@@ -22,13 +22,13 @@ load_settings() {
config_get_bool enabled main enabled 0
config_get_bool boot_enabled main boot_enabled 0
config_get_bool initialized main initialized 0
config_get config_mode main config_mode managed
config_get config_file main config_file /etc/honk/config.dae
config_get listen_port main listen_port 9527
config_get_bool clash_api_enabled main clash_api_enabled 0
config_get clash_api_port main clash_api_port 9090
config_get clash_api_secret main clash_api_secret ''
config_get lan_network main lan_network lan
settings_owner=$(config_owner "$config_file")
}
valid_clash_api() {
@@ -149,17 +149,11 @@ sync_local_direct() {
check_config() {
[ "$config_file" = /etc/honk/config.dae ] || { fail "配置路径必须为 /etc/honk/config.dae"; return 1; }
[ -f /etc/honk/config.dae ] && [ ! -L /etc/honk/config.dae ] || { fail "/etc/honk/config.dae 不是普通文件(可能被替换成软链)"; return 1; }
case "$config_mode" in
manual)
"$HONK_CORE" config validate --config /etc/honk/config.dae >/dev/null || { fail "手动模式的 config.dae 未通过静态校验,请先修正配置"; return 1; }
return 0
;;
managed) ;;
*) fail "配置模式取值非法(应为 managed 或 manual)"; return 1 ;;
esac
"$HONK_CORE" config validate --config /etc/honk/config.dae >/dev/null || { fail "config.dae 未通过静态校验,请先修正配置"; return 1; }
[ -r /usr/share/doona/index.html ] || { fail "缺少 Doona 静态文件(/usr/share/doona/index.html),请重新安装 luci-app-honk"; return 1; }
[ "$settings_owner" = system ] || return 0
[ -f /etc/honk/system.dae ] && [ ! -L /etc/honk/system.dae ] || { fail "/etc/honk/system.dae 不是普通文件(可能被替换成软链)"; return 1; }
system_config_readonly || { fail "/etc/honk/system.dae 权限不对,应为 0600"; return 1; }
[ -r /usr/share/doona/index.html ] || { fail "缺少 Doona 静态文件(/usr/share/doona/index.html),请重新安装 luci-app-honk"; return 1; }
case "$listen_port" in
''|*[!0-9]*) fail "监听端口必须是数字"; return 1 ;;
esac
@@ -276,13 +270,14 @@ lock_owner_invalid() {
preflight() {
load_settings
[ "$enabled" = 1 ] && [ "$initialized" = 1 ] || { fail "请先在 LuCI 里启用并完成初始化"; return 1; }
if [ "$config_mode" = manual ]; then
maintenance_start_guard && check_config && check_kernel && no_honk_resources && check_conflicts
# 只有 system.dae 负责这些字段时才检查 UCI 端口与网络;完整配置自行定义时不碰。
if [ "$settings_owner" = system ]; then
maintenance_start_guard && check_config && check_network && check_kernel && no_honk_resources && check_conflicts && check_port && {
[ "$clash_api_enabled" = 0 ] || check_port "$clash_api_port"
}
return $?
fi
maintenance_start_guard && check_config && check_network && check_kernel && no_honk_resources && check_conflicts && check_port && {
[ "$clash_api_enabled" = 0 ] || check_port "$clash_api_port"
}
maintenance_start_guard && check_config && check_kernel && no_honk_resources && check_conflicts
}
prepare_runtime() {
@@ -454,7 +449,7 @@ settings_rollback() {
settings() {
if [ -e /etc/.honk-update/journal ] || [ -L /etc/.honk-update/journal ]; then echo '{"ok":false,"message":"update_recovery_required"}'; return 1; fi
load_settings
[ "$config_mode" = manual ] && { echo '{"ok":false,"message":"settings_unavailable_in_manual_mode"}'; return 1; }
[ "$settings_owner" = system ] || { echo '{"ok":false,"message":"config_settings_owned_by_file"}'; return 1; }
. /usr/share/libubox/jshn.sh || { echo '{"ok":false,"message":"jshn_unavailable"}'; return 1; }
# Bound stdin to 64KiB (DoS protection); read one extra byte to detect truncation.
settings_json=$(head -c 65537)
@@ -600,7 +595,7 @@ diagnose() {
managed_core_pid >/dev/null 2>&1 && running=1 || running=0
check_config >/dev/null 2>&1 || errors="$errors invalid_package_config"
check_kernel >/dev/null 2>&1 || errors="$errors kernel_requirements_unmet"
if [ "$config_mode" = manual ]; then
if [ "$settings_owner" != system ]; then
if [ "$running" != 1 ]; then
no_honk_resources >/dev/null 2>&1 || errors="$errors honk_resources_occupied"
pidof honk-core >/dev/null 2>&1 && errors="$errors unmanaged_core_running"
@@ -659,9 +654,9 @@ clash_system_matches() {
repair() {
if [ -e /etc/.honk-update/journal ] || [ -L /etc/.honk-update/journal ]; then echo '{"ok":false,"message":"update_recovery_required"}'; return 1; fi
load_settings
if [ "$config_mode" = manual ]; then
check_config && { echo '{"ok":true,"message":"manual_config_valid"}'; return 0; }
echo '{"ok":false,"message":"manual_config_invalid"}'
if [ "$settings_owner" != system ]; then
check_config && { echo '{"ok":true,"message":"config_valid"}'; return 0; }
echo '{"ok":false,"message":"config_invalid"}'
return 1
fi
old_network=$lan_network
@@ -675,7 +670,7 @@ repair() {
[ "$config_file" = /etc/honk/config.dae ] || { echo '{"ok":false,"message":"invalid_config_path"}'; return 1; }
[ -f /etc/honk/config.dae ] && [ ! -L /etc/honk/config.dae ] || { echo '{"ok":false,"message":"main_config_unavailable"}'; return 1; }
main_hash=$(sha256sum /etc/honk/config.dae | awk '{print $1}')
main_has_system_include || { echo '{"ok":false,"message":"required_system_include_missing_manual_repair"}'; return 1; }
main_has_system_include || { echo '{"ok":false,"message":"required_system_include_missing"}'; return 1; }
valid_network_name || { echo '{"ok":false,"message":"invalid_lan_network"}'; return 1; }
case "$listen_port" in ''|*[!0-9]*) echo '{"ok":false,"message":"invalid_listen_port"}'; return 1 ;; esac
[ "$listen_port" -ge 1024 ] && [ "$listen_port" -le 65535 ] || { echo '{"ok":false,"message":"invalid_listen_port"}'; return 1; }
-1
View File
@@ -2,7 +2,6 @@ config honk 'main'
option enabled '0'
option boot_enabled '0'
option initialized '0'
option config_mode 'managed'
option config_file '/etc/honk/config.dae'
option listen_port '9527'
option clash_api_enabled '0'
+25 -29
View File
@@ -16,7 +16,7 @@ boot() {
}
start_service() {
local enabled initialized config_file config_mode ready_token ready_hash ready_runtime ready_api_enabled ready_api_listen ready_clash_api_listen ready_clash_api_secret_set old_ready_token
local enabled initialized config_file ready_token ready_hash ready_runtime ready_api_enabled ready_api_listen ready_clash_api_listen ready_clash_api_secret_set old_ready_token
config_load honk
config_get_bool enabled main enabled 0
config_get_bool initialized main initialized 0
@@ -25,7 +25,6 @@ start_service() {
return 1
}
config_get config_file main config_file "$HONK_CONFIG"
config_get config_mode main config_mode managed
[ "$config_file" = "$HONK_CONFIG" ] || {
logger -t honk "refusing non-default config path"
return 1
@@ -34,39 +33,36 @@ start_service() {
"$HONK_CONTROL" local-direct || logger -t honk "local-direct sync skipped"
unset HONK_MAINT_CAP
if [ "$config_mode" = manual ]; then
ready_token=$(cat /proc/sys/kernel/random/uuid 2>/dev/null) || return 1
case "$ready_token" in *[!0-9a-f-]*|'') return 1 ;; esac
ready_hash=$(sha256sum "$HONK_CONFIG" 2>/dev/null | awk '{print $1}') || return 1
case "$ready_hash" in *[!0-9a-f]*|'') return 1 ;; esac
ready_runtime=$(/usr/bin/honk-core config validate --config "$HONK_CONFIG" 2>/dev/null) || return 1
ready_api_enabled=$(printf '%s' "$ready_runtime" | jsonfilter -e '@.runtime.native_api_enabled' 2>/dev/null)
ready_api_listen=$(printf '%s' "$ready_runtime" | jsonfilter -e '@.runtime.native_api_listen' 2>/dev/null)
ready_clash_api_listen=$(printf '%s' "$ready_runtime" | jsonfilter -e '@.runtime.clash_api_listen' 2>/dev/null)
ready_clash_api_secret_set=$(printf '%s' "$ready_runtime" | jsonfilter -e '@.runtime.clash_api_secret_set' 2>/dev/null)
case "$ready_api_enabled" in true|false) ;; *) return 1 ;; esac
case "$ready_api_listen" in *[!A-Za-z0-9:._-]*) return 1 ;; esac
case "$ready_clash_api_listen" in *[!A-Za-z0-9:._-]*) return 1 ;; esac
case "$ready_clash_api_secret_set" in true|false) ;; *) return 1 ;; esac
mkdir -p /var/run/honk || return 1
chmod 0700 /var/run/honk || return 1
if [ -f /var/run/honk/readiness ] && [ ! -L /var/run/honk/readiness ]; then
read -r old_ready_token _ < /var/run/honk/readiness
case "$old_ready_token" in ''|*[!0-9a-f-]*) ;; *) rm -f "/var/run/honk/core-ready-$old_ready_token" ;; esac
fi
printf '%s %s\n' "$ready_token" "$ready_hash" > /var/run/honk/readiness || return 1
chmod 0600 /var/run/honk/readiness || return 1
# 所有配置都走 readiness 标记:状态页据此判断真正就绪,而不是只看进程在不在。
ready_token=$(cat /proc/sys/kernel/random/uuid 2>/dev/null) || return 1
case "$ready_token" in *[!0-9a-f-]*|'') return 1 ;; esac
ready_hash=$(sha256sum "$HONK_CONFIG" 2>/dev/null | awk '{print $1}') || return 1
case "$ready_hash" in *[!0-9a-f]*|'') return 1 ;; esac
ready_runtime=$(/usr/bin/honk-core config validate --config "$HONK_CONFIG" 2>/dev/null) || return 1
ready_api_enabled=$(printf '%s' "$ready_runtime" | jsonfilter -e '@.runtime.native_api_enabled' 2>/dev/null)
ready_api_listen=$(printf '%s' "$ready_runtime" | jsonfilter -e '@.runtime.native_api_listen' 2>/dev/null)
ready_clash_api_listen=$(printf '%s' "$ready_runtime" | jsonfilter -e '@.runtime.clash_api_listen' 2>/dev/null)
ready_clash_api_secret_set=$(printf '%s' "$ready_runtime" | jsonfilter -e '@.runtime.clash_api_secret_set' 2>/dev/null)
case "$ready_api_enabled" in true|false) ;; *) return 1 ;; esac
case "$ready_api_listen" in *[!A-Za-z0-9:._-]*) return 1 ;; esac
case "$ready_clash_api_listen" in *[!A-Za-z0-9:._-]*) return 1 ;; esac
case "$ready_clash_api_secret_set" in true|false) ;; *) return 1 ;; esac
mkdir -p /var/run/honk || return 1
chmod 0700 /var/run/honk || return 1
if [ -f /var/run/honk/readiness ] && [ ! -L /var/run/honk/readiness ]; then
read -r old_ready_token _ < /var/run/honk/readiness
case "$old_ready_token" in ''|*[!0-9a-f-]*) ;; *) rm -f "/var/run/honk/core-ready-$old_ready_token" ;; esac
fi
printf '%s %s\n' "$ready_token" "$ready_hash" > /var/run/honk/readiness || return 1
chmod 0600 /var/run/honk/readiness || return 1
procd_open_instance main
procd_set_param command /usr/bin/honk-core --config "$HONK_CONFIG" --disable-timestamp
# One env call, no empty values: procd drops the whole list when a KEY has an
# empty value, which silently stripped the readiness hash the core requires.
ready_env="DAE_LOCATION_ASSET=/usr/share/v2ray"
if [ "$config_mode" = manual ]; then
ready_env="$ready_env HONK_READY_TOKEN=$ready_token HONK_READY_CONFIG_HASH=$ready_hash HONK_READY_API_ENABLED=$ready_api_enabled HONK_READY_CLASH_API_SECRET_SET=$ready_clash_api_secret_set"
[ -n "$ready_api_listen" ] && ready_env="$ready_env HONK_READY_API_LISTEN=$ready_api_listen"
[ -n "$ready_clash_api_listen" ] && ready_env="$ready_env HONK_READY_CLASH_API_LISTEN=$ready_clash_api_listen"
fi
ready_env="$ready_env HONK_READY_TOKEN=$ready_token HONK_READY_CONFIG_HASH=$ready_hash HONK_READY_API_ENABLED=$ready_api_enabled HONK_READY_CLASH_API_SECRET_SET=$ready_clash_api_secret_set"
[ -n "$ready_api_listen" ] && ready_env="$ready_env HONK_READY_API_LISTEN=$ready_api_listen"
[ -n "$ready_clash_api_listen" ] && ready_env="$ready_env HONK_READY_CLASH_API_LISTEN=$ready_clash_api_listen"
# shellcheck disable=SC2086
procd_set_param env $ready_env
procd_set_param stdout 1
+55 -41
View File
@@ -2,8 +2,51 @@
HONK_LIFECYCLE_CORE=/usr/bin/honk-core
honk_lifecycle_procd_pid() {
ubus call service list '{"name":"honk"}' 2>/dev/null | jsonfilter -e '@.honk.instances.main.pid' 2>/dev/null
# 字段归属:自带 global/experimental → file;只 include system.dae(且写法规范)→ system;
# 其余一律 unknown。写法不规范时不猜,直接拒绝。
config_owner() {
config_owner_file=${1:-/etc/honk/config.dae}
[ -f "$config_owner_file" ] && [ ! -L "$config_owner_file" ] || { echo unknown; return 0; }
own=$(awk '/^[[:space:]]*(global|experimental)[[:space:]]*\{/ { print "1"; exit }' "$config_owner_file")
inc=$(awk '
{
line = $0
sub(/\r$/, "", line)
if (!in_block) {
# 只接受「include {」独占一行;同行带内容一律算不规范
if (line ~ /^[[:space:]]*include[[:space:]]*\{[[:space:]]*$/) { in_block = 1; next }
if (line ~ /^[[:space:]]*include[[:space:]]*\{/) { print "irregular"; next }
next
}
if (line ~ /^[[:space:]]*\}[[:space:]]*$/) { in_block = 0; next }
if (line ~ /^[[:space:]]*\}/ || line ~ /\{/) { print "irregular"; next }
entry = line
sub(/#.*/, "", entry)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", entry)
gsub(/["\047]/, "", entry)
if (entry == "") next
if (entry == "system.dae") print "system"
else if (entry ~ /^[A-Za-z0-9._-]+$/) print "other"
else print "irregular"
}
END { if (in_block) print "irregular" }
' "$config_owner_file")
has_system=
has_bad=
case "$inc" in *system*) has_system=1 ;; esac
case "$inc" in *other*|*irregular*) has_bad=1 ;; esac
if [ -n "$own" ]; then
[ -z "$inc" ] && echo file || echo unknown
elif [ -n "$has_bad" ]; then
echo unknown
elif [ -n "$has_system" ]; then
echo system
else
echo unknown
fi
}
honk_lifecycle_procd_pid() { ubus call service list '{"name":"honk"}' 2>/dev/null | jsonfilter -e '@.honk.instances.main.pid' 2>/dev/null
}
honk_lifecycle_procd_running() {
@@ -29,35 +72,18 @@ honk_lifecycle_starttime() {
}
honk_lifecycle_listen() {
config_load honk
config_get config_mode main config_mode managed
if [ "$config_mode" = manual ]; then
[ -f /var/run/honk/readiness ] && [ ! -L /var/run/honk/readiness ] || return 1
read -r hlp_token _ < /var/run/honk/readiness || return 1
case "$hlp_token" in ''|*[!0-9a-f-]*) return 1 ;; esac
sed -n 's/^api_listen=//p' "/var/run/honk/core-ready-$hlp_token" 2>/dev/null
return
fi
sed -n "s/^[[:space:]]*listen:[[:space:]]*'\([^']*\)'.*/\1/p" /etc/honk/system.dae 2>/dev/null | head -n 1
[ -f /var/run/honk/readiness ] && [ ! -L /var/run/honk/readiness ] || return 1
read -r hlp_token _ < /var/run/honk/readiness || return 1
case "$hlp_token" in ''|*[!0-9a-f-]*) return 1 ;; esac
sed -n 's/^api_listen=//p' "/var/run/honk/core-ready-$hlp_token" 2>/dev/null
}
honk_lifecycle_parse_listen() {
config_load honk
config_get config_mode main config_mode managed
if [ "$config_mode" = manual ]; then
HONK_LIFECYCLE_LISTEN=$(honk_lifecycle_listen)
if [ -z "$HONK_LIFECYCLE_LISTEN" ]; then
HONK_LIFECYCLE_PORT=0
return 0
fi
case "$HONK_LIFECYCLE_LISTEN" in *:*) ;; *) return 1 ;; esac
HONK_LIFECYCLE_PORT=${HONK_LIFECYCLE_LISTEN##*:}
case "$HONK_LIFECYCLE_LISTEN" in ''|*[!A-Za-z0-9:._-]*) return 1 ;; esac
case "$HONK_LIFECYCLE_PORT" in ''|*[!0-9]*) return 1 ;; esac
[ "$HONK_LIFECYCLE_PORT" -ge 1024 ] && [ "$HONK_LIFECYCLE_PORT" -le 65535 ]
return $?
fi
HONK_LIFECYCLE_LISTEN=$(honk_lifecycle_listen)
if [ -z "$HONK_LIFECYCLE_LISTEN" ]; then
HONK_LIFECYCLE_PORT=0
return 0
fi
case "$HONK_LIFECYCLE_LISTEN" in *:*) ;; *) return 1 ;; esac
HONK_LIFECYCLE_PORT=${HONK_LIFECYCLE_LISTEN##*:}
case "$HONK_LIFECYCLE_LISTEN" in ''|*[!A-Za-z0-9:._-]*) return 1 ;; esac
@@ -116,14 +142,7 @@ honk_lifecycle_api_owned() {
}
honk_lifecycle_runtime_owned() {
hlp_pid=$1
config_load honk
config_get config_mode main config_mode managed
if [ "$config_mode" = manual ]; then
honk_lifecycle_ready_owned "$hlp_pid"
else
honk_lifecycle_api_owned "$hlp_pid"
fi
honk_lifecycle_ready_owned "$1"
}
honk_lifecycle_resources_clear() {
@@ -195,9 +214,6 @@ honk_lifecycle_start() {
honk_lifecycle_parse_listen || { echo "honk lifecycle: invalid configured listener" >&2; return 1; }
hlp_listen=$HONK_LIFECYCLE_LISTEN
hlp_port=$HONK_LIFECYCLE_PORT
config_load honk
config_get config_mode main config_mode managed
if [ "$config_mode" != manual ] && honk_lifecycle_port_open "$hlp_port"; then echo "honk lifecycle: listener port remains occupied" >&2; return 1; fi
if [ -n "${HONK_MAINT_CAP:-}" ]; then export HONK_MAINT_CAP; fi
/etc/init.d/honk start >/dev/null 2>&1
hlp_start_status=$?
@@ -225,9 +241,7 @@ honk_lifecycle_start() {
hlp_new_starttime=$(honk_lifecycle_starttime "$hlp_new_pid") || hlp_new_starttime=
if [ -n "$hlp_new_starttime" ] && \
{ [ -z "${HONK_LIFECYCLE_STOP_PID:-}" ] || [ "$hlp_new_pid:$hlp_new_starttime" != "$HONK_LIFECYCLE_STOP_PID:$HONK_LIFECYCLE_STOP_STARTTIME" ]; } && \
{ [ "$config_mode" = manual ] && honk_lifecycle_ready_owned "$hlp_new_pid" || \
{ [ "$config_mode" != manual ] && honk_lifecycle_listener_owned "$hlp_new_pid" "$hlp_port" && \
curl -fsS --noproxy '*' --connect-timeout 1 --max-time 2 "http://$hlp_listen/api" -o /dev/null 2>/dev/null; }; }; then
{ honk_lifecycle_ready_owned "$hlp_new_pid"; }; then
if [ "$hlp_pid:$hlp_starttime" = "$hlp_new_pid:$hlp_new_starttime" ]; then
hlp_stable=$((hlp_stable + 1))
else
+5 -6
View File
@@ -25,7 +25,6 @@ if [ "${HONK_MAINT_LIBRARY:-0}" != 1 ]; then
. /usr/share/honk/lock.sh || exit 1
. /usr/share/honk/lifecycle.sh || exit 1
. /usr/share/libubox/jshn.sh || exit 1
# lifecycle.sh 用 config_load/config_get 读模式,job 路径没有 rc.common 帮它加载。
[ -r /lib/functions.sh ] && . /lib/functions.sh
fi
@@ -445,7 +444,7 @@ load_uci_values() {
u_geo_auto=$(get_uci geo_auto 2>/dev/null || printf 0)
u_geo_auto_freq=$(get_uci geo_auto_freq 2>/dev/null || printf daily)
case "$u_enabled:$u_boot:$u_initialized" in 0:0:0|0:0:1|0:1:0|0:1:1|1:0:0|1:0:1|1:1:0|1:1:1) ;; *) return 1 ;; esac
case "$u_config_mode" in managed|manual) ;; *) return 1 ;; esac
case "$u_config_mode" in ''|managed|manual) ;; *) return 1 ;; esac
case "$u_geo_auto:$u_geo_auto_freq" in 0:daily|0:weekly|1:daily|1:weekly) ;; *) return 1 ;; esac
[ "$u_config" = /etc/honk/config.dae ] || return 1
case "$u_port" in ''|*[!0-9]*) return 1 ;; esac
@@ -541,7 +540,7 @@ validate_config_tree() {
main=$config_root/config.dae
system=$config_root/system.dae
[ -f "$main" ] && [ ! -L "$main" ] && [ -f "$system" ] && [ ! -L "$system" ] || return 1
if [ "$u_config_mode" = manual ]; then
if [ "$(config_owner "$main")" != system ]; then
"$HONK_CORE" config validate --config "$main" >/dev/null 2>&1 || return 2
find "$config_root" -type l -print 2>/dev/null | grep -q . && return 2
find "$config_root" ! -type f ! -type d -print 2>/dev/null | grep -q . && return 2
@@ -822,7 +821,7 @@ validate_candidate() {
config_tree=$candidate/etc/honk
mkdir -p "$temp_dir/uci-tmp" || return 1
load_uci_values uci "$uci_dir" || return 1
if [ "$u_config_mode" = manual ]; then
if [ "$(config_owner "$config_tree/config.dae")" != system ]; then
validate_config_tree "$config_tree" '' '' "$u_port" "$u_initialized" || return 2
if [ "$state" = present ] || { [ "$state" = preview ] && { [ -e "$config_tree/state/honk.db" ] || [ -L "$config_tree/state/honk.db" ]; }; }; then
archive_sqlite_check "$config_tree/state/honk.db" || return 1
@@ -1025,7 +1024,7 @@ restore() {
fi
[ "$old_running" != 1 ] || [ "$restore_initialized" = 1 ] || { error running_service_requires_initialized_restore; return 1; }
[ "$(sha_of "$RESTORE")" = "$archive_hash" ] || { error restore_archive_changed; return 1; }
if [ "$u_config_mode" = managed ]; then
if [ "$(config_owner "$restore_stage/etc/honk/config.dae")" = system ]; then
write_live_system "$restore_stage/etc/honk/system.dae" "$candidate_device" "$candidate_ip" "$u_port" "$restore_initialized" || { error system_config_render_failed; return 1; }
chmod 0600 "$restore_stage/etc/honk/system.dae" || { error system_config_render_failed; return 1; }
fi
@@ -1107,7 +1106,7 @@ reset_data() {
uci -c "$reset_uci" -t "$temp_dir/uci-tmp" import honk < "$temp_dir/old-uci" || { error reset_profile_stage_failed; return 1; }
uci -c "$reset_uci" -t "$temp_dir/uci-tmp" set honk.main.enabled=0 || { error reset_profile_stage_failed; return 1; }
uci -c "$reset_uci" -t "$temp_dir/uci-tmp" set honk.main.boot_enabled=0 || { error reset_profile_stage_failed; return 1; }
uci -c "$reset_uci" -t "$temp_dir/uci-tmp" set honk.main.config_mode=managed || { error reset_profile_stage_failed; return 1; }
uci -c "$reset_uci" -t "$temp_dir/uci-tmp" delete honk.main.config_mode >/dev/null 2>&1 || :
uci -c "$reset_uci" -t "$temp_dir/uci-tmp" set honk.main.listen_port=9527 || { error reset_profile_stage_failed; return 1; }
uci -c "$reset_uci" -t "$temp_dir/uci-tmp" set honk.main.lan_network=lan || { error reset_profile_stage_failed; return 1; }
uci -c "$reset_uci" -t "$temp_dir/uci-tmp" set honk.main.clash_api_enabled=0 || { error reset_profile_stage_failed; return 1; }
-47
View File
@@ -1,47 +0,0 @@
name: Build
on:
push:
tags:
- v*
jobs:
build:
name: Build ${{ matrix.arch }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch:
- aarch64_generic
- i386_pentium-mmx
- loongarch64_generic
- mips_mips32
- mipsel_24kc
- mipsel_mips32
- x86_64
steps:
- uses: actions/checkout@main
with:
fetch-depth: 0
- name: Building packages
uses: sbwml/openwrt-gh-action-sdk@go1.24
env:
ARCH: ${{ matrix.arch }}-openwrt-24.10
FEEDNAME: packages_ci
PACKAGES: luci-app-eqosplus
NO_REFRESH_CHECK: true
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.arch }}
path: bin/packages/${{ matrix.arch }}/packages_ci/*.ipk
- name: Upload packages
uses: ncipollo/release-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
allowUpdates: true
replacesArtifacts: true
artifacts: "bin/packages/${{ matrix.arch }}/packages_ci/*.ipk"
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2025 sirpdboy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-39
View File
@@ -1,39 +0,0 @@
#
# Copyright (C) 2006-2017 OpenWrt.org#
# Copyright (C) 2022-2025 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/luci-app-eqosplus
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
THEME_NAME:=eqosplus
PKG_NAME:=luci-app-$(THEME_NAME)
PKG_LICENSE:=Apache-2.0
LUCI_TITLE:=LuCI support for eqosplus.
LUCI_DESCRIPTION:=LuCI support for Easy eqosplus(Support speed limit based on IP address).
LUCI_DEPENDS:=+ip-full +tc +nftables +bc +kmod-ifb
LUCI_PKGARCH:=all
PKG_VERSION:=1.3.0
PKG_RELEASE:=20251128
PKG_MAINTAINER:=sirpdboy <herboy2008@gmail.com>
define Build/Compile
endef
define Package/$(PKG_NAME)/postinst
#!/bin/sh
rm -f /tmp/luci-*
endef
define Package/$(PKG_NAME)/conffiles
/etc/config/eqosplus
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
-91
View File
@@ -1,91 +0,0 @@
![hello](https://views.whatilearened.today/views/github/sirpdboy/deplives.svg) [![](https://img.shields.io/badge/TG群-点击加入-FFFFFF.svg)](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ)
<h1 align="center">
<br>luci-app-eqosplus<br>
</h1>
<p align="center">
<a href="https://openwrt.org"><img alt="OpenWrt" src="https://img.shields.io/badge/OpenWrt-%E2%89%A519.07-ff0000?logo=openwrt&logoColor=white"></a>
<a href="https://www.google.com/chrome/"><img alt="Chrome" src="https://img.shields.io/badge/Chrome-%E2%89%A5111-4285F3?logo=googlechrome&logoColor=white"></a>
<a href="https://www.apple.com/safari/"><img alt="Safari" src="https://img.shields.io/badge/Safari-%E2%89%A516.4-000000?logo=safari&logoColor=white"></a>
<a href="https://www.mozilla.org/firefox/"><img alt="Firefox" src="https://img.shields.io/badge/Firefox-%E2%89%A5128-FF7138?logo=firefoxbrowser&logoColor=white"></a>
<a target="_blank" href="https://github.com/sirpdboy/luci-app-eqosplus/releases"> <img alt="GitHub release" src="https://img.shields.io/github/v/release/sirpdboy/luci-app-eqosplus"></a>
<a href="https://github.com/sirpdboy/luci-app-eqosplus/releases"><img alt="Downloads" src="https://img.shields.io/github/downloads/sirpdboy/luci-app-eqosplus/total"></a>
</p>
[中文] | [English](README.md)
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明1.jpg)
Please read this page carefully, which includes precautions and instructions on how to use it.
## Enhanced version of EQOS with timed speed limit and added features such as timed limit.
### Method for downloading source code:
```Brach
# downloading
git clone https://github.com/sirpdboy/luci-app-eqosplus package/luci-app-eqosplus
make menuconfig
```
### Configuration Menu
```Brach
make menuconfig
# find LuCI -> Applications, select luci-app-eqosplus, save and exit
```
### compile
```Brach
# compile
make package/luci-app-eqosplus/compile V=s
```
## describe
- luci-app-eqosplus : https://github.com/sirpdboy/luci-app-eqosplus
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明2.jpg)
## interface
![screenshots](./doc/eqosplus0.png)
![screenshots](./doc/eqosplus1.png)
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/eqosplus.png)
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/eqosplus2.png)
# My other project
- Watch Dog : https://github.com/sirpdboy/luci-app-watchdog
- Net Speedtest : https://github.com/sirpdboy/luci-app-netspeedtest
- Task Plan : https://github.com/sirpdboy/luci-app-taskplan
- Power Off Device : https://github.com/sirpdboy/luci-app-poweroffdevice
- OpentoPD Theme : https://github.com/sirpdboy/luci-theme-opentopd
- KuCat Theme : https://github.com/sirpdboy/luci-theme-kucat
- KuCat Theme Config : https://github.com/sirpdboy/luci-app-kucat-config
- NFT Time Control : https://github.com/sirpdboy/luci-app-timecontrol
- Parent Control: https://github.com/sirpdboy/luci-theme-parentcontrol
- Eqos Plus: https://github.com/sirpdboy/luci-app-eqosplus
- Advanced : https://github.com/sirpdboy/luci-app-advanced
- ddns-go : https://github.com/sirpdboy/luci-app-ddns-go
- Advanced Plus): https://github.com/sirpdboy/luci-app-advancedplus
- Net Wizard: https://github.com/sirpdboy/luci-app-netwizard
- Part Exp: https://github.com/sirpdboy/luci-app-partexp
- Lukcy: https://github.com/sirpdboy/luci-app-lukcy
## HELP
| <img src="https://img.shields.io/badge/-Alipay-F5F5F5.svg" href="#赞助支持本项目-" height="25" alt="图飞了"/> | <img src="https://img.shields.io/badge/-WeChat-F5F5F5.svg" height="25" alt="图飞了" href="#赞助支持本项目-"/> |
| :-----------------: | :-------------: |
|![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/支付宝.png) | ![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/微信.png) |
<a href="#readme">
<img src="https://img.shields.io/badge/-TOP-orange.svg" alt="no" title="Return TOP" align="right"/>
</a>
![hello](https://visitor-badge-deno.deno.dev/sirpdboy.sirpdboy.svg) [![](https://img.shields.io/badge/TGGroup-ClickJoin-FFFFFF.svg)](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ)
-109
View File
@@ -1,109 +0,0 @@
![hello](https://views.whatilearened.today/views/github/sirpdboy/deplives.svg) [![](https://img.shields.io/badge/TG群-点击加入-FFFFFF.svg)](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ)
<h1 align="center">
<br>luci-app-eqosplus<br>
</h1>
<p align="center">
<a target="_blank" href="https://github.com/sirpdboy/luci-app-eqosplus/releases">
<img src="https://img.shields.io/github/release/sirpdboy/luci-app-eqosplus.svg?style=flat-square&label=luci-app-eqosplus&colorB=green">
</a>
</p>
[中文] | [English](README.md)
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明1.jpg)
请 **认真阅读完毕** 本页面,本页面包含注意事项和如何使用。
## 功能说明:
### 定时限速1.2.2版
#### 2023.7.19 定时限速1.2.2:增加更多日期:工作日和休息日,自定义日期1,2,3中间用逗号分隔;加入MAC地址限速,从此不用担心IPV6和IPV4的限速问题。
### 定时限速1.0版
#### 2022.12.24 定时限速在eqos的加强版,加入定时限制等功能。
## 编译使用方法 [![](https://img.shields.io/badge/-编译使用方法-F5F5F5.svg)](#编译使用方法-)
将luci-app-eqosplus添加至 LEDE/OpenWRT 源码的方法。
### 下载源码方法一:
编辑源码文件夹根目录feeds.conf.default并加入如下内容:
```Brach
# feeds获取源码:
src-git eqosplus https://github.com/sirpdboy/luci-app-eqosplus
```
```Brach
# 更新feeds,并安装主题:
scripts/feeds update eqosplus
scripts/feeds install luci-app-eqosplus
```
### 下载源码方法:
```Brach
# 下载源码
git clone https://github.com/sirpdboy/luci-app-eqosplus package/luci-app-eqosplus
make menuconfig
```
### 配置菜单
```Brach
make menuconfig
# 找到 LuCI -> Applications, 选择 luci-app-eqosplus, 保存后退出。
```
### 编译
```Brach
# 编译固件
make package/luci-app-eqosplus/compile V=s
```
## 说明 [![](https://img.shields.io/badge/-说明-F5F5F5.svg)](#说明-)
源码来源:https://github.com/sirpdboy/luci-app-eqosplus
- 你可以随意使用其中的源码,但请注明出处。
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明2.jpg)
## 界面
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/eqosplus.png)
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/eqosplus2.png)
# My other project
- 路由安全看门狗 :https://github.com/sirpdboy/luci-app-watchdog
- 网络速度测试 :https://github.com/sirpdboy/luci-app-netspeedtest
- 计划任务插件(原定时设置) : https://github.com/sirpdboy/luci-app-taskplan
- 关机功能插件 : https://github.com/sirpdboy/luci-app-poweroffdevice
- opentopd主题 : https://github.com/sirpdboy/luci-theme-opentopd
- kucat酷猫主题: https://github.com/sirpdboy/luci-theme-kucat
- kucat酷猫主题设置工具: https://github.com/sirpdboy/luci-app-kucat-config
- NFT版上网时间控制插件: https://github.com/sirpdboy/luci-app-timecontrol
- 家长控制: https://github.com/sirpdboy/luci-theme-parentcontrol
- 定时限速: https://github.com/sirpdboy/luci-app-eqosplus
- 系统高级设置 : https://github.com/sirpdboy/luci-app-advanced
- ddns-go动态域名: https://github.com/sirpdboy/luci-app-ddns-go
- 进阶设置(系统高级设置+主题设置kucat/agron/opentopd): https://github.com/sirpdboy/luci-app-advancedplus
- 网络设置向导: https://github.com/sirpdboy/luci-app-netwizard
- 一键分区扩容: https://github.com/sirpdboy/luci-app-partexp
- lukcy大吉: https://github.com/sirpdboy/luci-app-lukcy
## 捐助
![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明3.jpg)
| <img src="https://img.shields.io/badge/-支付宝-F5F5F5.svg" href="#赞助支持本项目-" height="25" alt="图飞了"/> | <img src="https://img.shields.io/badge/-微信-F5F5F5.svg" height="25" alt="图飞了" href="#赞助支持本项目-"/> |
| :-----------------: | :-------------: |
|![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/支付宝.png) | ![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/微信.png) |
<a href="#readme">
<img src="https://img.shields.io/badge/-返回顶部-orange.svg" alt="图飞了" title="返回顶部" align="right"/>
</a>
![](https://visitor-badge-deno.deno.dev/sirpdboy.sirpdboy.svg) [![](https://img.shields.io/badge/TG群-点击加入-FFFFFF.svg)](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 645 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 664 KiB

@@ -1,19 +0,0 @@
module("luci.controller.eqosplus", package.seeall)
-- Copyright 2022-2023 sirpdboy <herboy2008@gmail.com>
function index()
if not nixio.fs.access("/etc/config/eqosplus") then return end
entry({"admin", "control"}, firstchild(), "Control", 44).dependent = false
local e = entry({"admin", "control", "eqosplus"}, cbi("eqosplus"), _("Eqosplus"), 10)
e.dependent=false
e.acl_depends = { "luci-app-eqosplus" }
entry({"admin", "control", "eqosplus", "status"}, call("act_status")).leaf = true
end
function act_status()
local sys = require "luci.sys"
local e = {}
e.status = sys.call(" busybox ps -w | grep eqosplus | grep -v grep >/dev/null ") == 0
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
@@ -1,190 +0,0 @@
-- Copyright 2022-2023 sirpdboy <herboy2008@gmail.com>
-- Licensed to the public under the Apache License 2.0.
local sys = require "luci.sys"
local ifaces = sys.net:devices()
local WADM = require "luci.tools.webadmin"
local ipc = require "luci.ip"
local a, t, e
a = Map("eqosplus", translate("Network speed limit"))
a.description = translate("Users can limit the network speed for uploading/downloading through MAC, IP, and IP segments (192.168.110.00-192.168.10.200). The speed unit is MB/second.")
a.template = "eqosplus/index"
t = a:section(TypedSection, "eqosplus")
t.anonymous = true
e = t:option(DummyValue, "eqosplus_status", translate("Status"))
e.template = "eqosplus/eqosplus"
e.value = translate("Collecting data...")
ipi = t:option(ListValue, "ifname", translate("Interface"), translate("Set the interface used for restriction, use pppoe-wan for dialing, use WAN hardware interface for DHCP mode (such as eth1), and use br-lan for bypass mode"))
ipi.default = "1"
ipi:value(1,translate("Automatic settings"))
ipi:value("br-lan", translate("br-lan (LAN Bridge)"))
local function get_wan_interfaces()
local result = {}
local ubus = require "ubus"
local conn = ubus.connect()
if not conn then
return result
end
local network_status = conn:call("network.interface", "dump", {})
for _, iface in ipairs(network_status.interface) do
if iface.interface:match("^wan") or iface.interface:match("^pppoe") or iface.proto == "pppoe" then
local dev = iface.l3_device or iface.device
if dev then
table.insert(result, {
name = dev,
proto = iface.proto,
logical_name = iface.interface
})
end
end
end
conn:close()
return result
end
local wan_ifaces = get_wan_interfaces()
for _, iface in ipairs(wan_ifaces) do
ipi:value(iface.name, translate(iface.name) .. (iface.proto == "pppoe" and " (PPPoE)" or " (WAN)"))
end
t = a:section(TypedSection, "device")
t.template = "cbi/tblsection"
t.anonymous = true
t.addremove = true
comment = t:option(Value, "comment", translate("Comment"))
comment.size = 8
e = t:option(Flag, "enable", translate("Enabled"))
e.rmempty = false
e.size = 4
ip = t:option(Value, "mac", translate("IP/MAC"))
ip.size = 8
local function get_devices()
local devices = {}
local seen_ips = {}
local ubus = require "ubus"
local conn = ubus.connect()
local function get_hostname(ip)
local f = io.popen("nslookup "..ip.." 2>/dev/null | grep 'name =' | cut -d'=' -f2 | sed 's/\\.$//'")
if f then
local name = f:read("*l")
f:close()
if name and name ~= "" then
return name:match("^%s*(.-)%s*$")
end
end
local leases_file = io.open("/tmp/dhcp.leases", "r")
if leases_file then
for line in leases_file:lines() do
local mac, ip_lease, _, hostname = line:match("^(%S+)%s+(%S+)%s+(%S+)%s+(%S+)")
if ip_lease == ip and hostname ~= "*" then
leases_file:close()
return hostname
end
end
leases_file:close()
end
return "unknown"
end
if conn then
local leases = conn:call("dhcp", "ipv4leases", {}) or {}
for _, lease in ipairs(leases) do
if lease.ipaddr and lease.mac then
local hostname = lease.hostname or get_hostname(lease.ipaddr)
devices[#devices+1] = {
ip = lease.ipaddr,
mac = lease.mac:upper(),
hostname = hostname,
display = string.format("%s (%s) - %s", lease.ipaddr, lease.mac:upper(), hostname)
}
seen_ips[lease.ipaddr] = true
end
end
conn:close()
end
local arp_cmd = io.popen("ip -4 neigh show dev br-lan 2>/dev/null")
if arp_cmd then
for line in arp_cmd:lines() do
local ip_addr, mac = line:match("^(%S+)%s+.+%s+(%S+)%s+")
if ip_addr and mac and mac ~= "00:00:00:00:00:00" and not seen_ips[ip_addr] then
mac = mac:upper()
local hostname = get_hostname(ip_addr)
devices[#devices+1] = {
ip = ip_addr,
mac = mac,
hostname = hostname,
display = string.format("%s (%s) - %s", ip_addr, mac, hostname)
}
seen_ips[ip_addr] = true
end
end
arp_cmd:close()
end
table.sort(devices, function(a, b) return a.ip < b.ip end)
return devices
end
local devices = get_devices()
for _, dev in ipairs(devices) do
ip:value(dev.ip, dev.display)
end
dl = t:option(Value, "download", translate("Downloads"))
dl.default = '0.1'
dl.size = 4
ul = t:option(Value, "upload", translate("Uploads"))
ul.default = '0.1'
ul.size = 4
function validate_time(self, value, section)
local hh, mm, ss
hh, mm, ss = string.match (value, "^(%d?%d):(%d%d)$")
hh = tonumber (hh)
mm = tonumber (mm)
if hh and mm and hh <= 23 and mm <= 59 then
return value
else
return nil, "Time HH:MM or space"
end
end
e = t:option(Value, "timestart", translate("Start control time"))
e.placeholder = '00:00'
e.default = '00:00'
e.validate = validate_time
e.rmempty = true
e.size = 4
e = t:option(Value, "timeend", translate("Stop control time"))
e.placeholder = '00:00'
e.default = '00:00'
e.validate = validate_time
e.rmempty = true
e.size = 4
week=t:option(Value,"week",translate("Week Day(1~7)"))
week.rmempty = true
week:value('0',translate("Everyday"))
week:value(1,translate("Monday"))
week:value(2,translate("Tuesday"))
week:value(3,translate("Wednesday"))
week:value(4,translate("Thursday"))
week:value(5,translate("Friday"))
week:value(6,translate("Saturday"))
week:value(7,translate("Sunday"))
week:value('1,2,3,4,5',translate("Workday"))
week:value('6,7',translate("Rest Day"))
week.default='0'
week.size = 6
return a
@@ -1,3 +0,0 @@
<%+cbi/valueheader%>
<font class="eqosplus_status"><%=pcdata(self:cfgvalue(section) or self.default or "")%></font>
<%+cbi/valuefooter%>
@@ -1,12 +0,0 @@
<% include("cbi/map") %>
<script type="text/javascript">//<![CDATA[
XHR.poll(2, '<%=luci.dispatcher.build_url("admin", "control", "eqosplus", "status" )%>', null,
function (x, result) {
var status = document.getElementsByClassName('eqosplus_status')[0];
status.setAttribute("style", "font-weight:bold;");
status.setAttribute("color", result.status ? "green" : "red");
status.innerHTML = result.status ? '<%=translate("RUNNING")%>' : '<%=translate("NOT RUNNING")%>';
}
)
//]]>
</script>
@@ -1,61 +0,0 @@
msgid "Eqosplus"
msgstr ""
msgid "Network speed limit"
msgstr ""
msgid "Running state"
msgstr ""
msgid "Not running"
msgstr ""
msgid "Running"
msgstr ""
msgid "Users can limit the network speed for uploading/downloading through MAC, IP, and IP segments (192.168.110.00-192.168.10.200). The speed unit is MB/second."
msgstr ""
msgid "MAC/IP"
msgstr ""
msgid "Downloads"
msgstr ""
msgid "Uploads"
msgstr ""
msgid "Comment"
msgstr ""
msgid "Upload bandwidth(Mbit/s)"
msgstr ""
msgid "Download bandwidth(Mbit/s)"
msgstr ""
msgid "Start control time"
msgstr ""
msgid "Stop control time"
msgstr ""
msgid "Week Day(1~7)"
msgstr ""
msgid "Suggested feedback:"
msgstr ""
msgid "Set the interface used for restriction, use pppoe-wan for dialing, use WAN hardware interface for DHCP mode (such as eth1), and use br-lan for bypass mode"
msgstr ""
msgid "Rest Day"
msgstr ""
msgid "Workday"
msgstr ""
msgid "Automatic settings"
msgstr ""
-61
View File
@@ -1,61 +0,0 @@
msgid "Eqosplus"
msgstr "定时限速"
msgid "Network speed limit"
msgstr "定时网速限制"
msgid "Running state"
msgstr "运行状态"
msgid "Not running"
msgstr "未运行"
msgid "Running"
msgstr "已运行"
msgid "Users can limit the network speed for uploading/downloading through MAC, IP.The speed unit is MB/second."
msgstr "可以通过MAC,IP限制用户上传/下载的网速。速度单位为<font color=\"red\"><b> MB/秒 </b></font>。"
msgid "MAC/IP"
msgstr "MAC/IP"
msgid "Downloads"
msgstr "下载速度"
msgid "Uploads"
msgstr "上传速度"
msgid "Comment"
msgstr "备注"
msgid "Upload bandwidth(Mbit/s)"
msgstr "上传总带宽(Mbit/s)"
msgid "Download bandwidth(Mbit/s)"
msgstr "下载总带宽(Mbit/s)"
msgid "Start control time"
msgstr "起控时间"
msgid "Stop control time"
msgstr "停控时间"
msgid "Week Day(1~7)"
msgstr "星期(1~7)"
msgid "Suggested feedback:"
msgstr "建议反馈:"
msgid "Set the interface used for restriction, use pppoe-wan for dialing, use WAN hardware interface for DHCP mode (such as eth1), and use br-lan for bypass mode"
msgstr "设定用来限制网络速度的接口,拨号使用pppoe-wan,DHCP模式使用WAN口的硬件接口(如:eth1),通用使用br-lan接口"
msgid "Rest Day"
msgstr "休息日"
msgid "Workday"
msgstr "工作日"
msgid "Automatic settings"
msgstr "自动设置"
-61
View File
@@ -1,61 +0,0 @@
msgid "Eqosplus"
msgstr "定时限速"
msgid "Network speed limit"
msgstr "定时网速限制"
msgid "Running state"
msgstr "运行状态"
msgid "Not running"
msgstr "未运行"
msgid "Running"
msgstr "已运行"
msgid "Users can limit the network speed for uploading/downloading through MAC, IP, and IP segments (192.168.110.00-192.168.10.200). The speed unit is MB/second."
msgstr "用户可以通过MAC、IP、IP段(192.168.10.100-192.168.10.200),限制上传/下载的网络速度。速度单位为MB/秒。。"
msgid "MAC/IP"
msgstr "MAC/IP"
msgid "Downloads"
msgstr "下载速度"
msgid "Uploads"
msgstr "上传速度"
msgid "Comment"
msgstr "备注"
msgid "Upload bandwidth(Mbit/s)"
msgstr "上传总带宽(Mbit/s)"
msgid "Download bandwidth(Mbit/s)"
msgstr "下载总带宽(Mbit/s)"
msgid "Start control time"
msgstr "起控时间"
msgid "Stop control time"
msgstr "停控时间"
msgid "Week Day(1~7)"
msgstr "星期(1~7)"
msgid "Suggested feedback:"
msgstr "建议反馈:"
msgid "Set the interface used for restriction, use pppoe-wan for dialing, use WAN hardware interface for DHCP mode (such as eth1), and use br-lan for bypass mode"
msgstr "设定用来限制网络速度的接口,拨号使用pppoe-wan,DHCP模式使用WAN口的硬件接口(如:eth1),通用使用br-lan接口"
msgid "Rest Day"
msgstr "休息日"
msgid "Workday"
msgstr "工作日"
msgid "Automatic settings"
msgstr "自动设置"
@@ -1,25 +0,0 @@
config eqosplus
option enabled '0'
option download '1000'
option upload '30'
option ifname '1'
config device
option timestart '00:00'
option week '0'
option download '1'
option timeend '23:55'
option upload '1'
option mac '192.168.10.10'
option enable '0'
config device
option mac '192.168.10.10-192.168.10.200'
option timestart '00:00'
option timeend '00:00'
option week '0'
option download '0.1'
option upload '0.1'
option enable '0'
@@ -1,5 +0,0 @@
#!/bin/sh
[ "$ACTION" = "ifup" ] && [ "$INTERFACE" = "wan" ] && {
sleep 5
/etc/init.d/eqosplus restart
}
@@ -1,54 +0,0 @@
#!/bin/sh /etc/rc.common
#
# Copyright (C) 2025 sirpdboy herboy2008@gmail.com https://github.com/sirpdboy/luci-app-eqosplus
#
START=99
USE_PROCD=1
NAME=eqosplus
LOCK="/var/lock/$NAME.lock"
CR=/etc/crontabs/root
start_instance() {
procd_open_instance
procd_set_param command /usr/bin/eqosplusctrl
procd_set_param respawn
procd_set_param stderr 1
procd_close_instance
}
_eqosplus_start() {
if [ "$(grep -c 'option enable .1.' /etc/config/$NAME 2>/dev/null)" -gt "0" ]; then
if [ x$(uci -q get turboacc.config.sw_flow) = 'x1' ] || [ x$(uci -q get turboacc.config.sfe_flow) = 'x1' ] ;then
uci -q set turboacc.config.sw_flow='0'
uci -q set turboacc.config.sfe_flow='0'
uci -q set turboacc.config.hw_flow='0'
uci commit turboacc
/etc/init.d/turboacc restart
fi
touch $LOCK
eqosplus start
start_instance
(crontab -l ; echo "00 1 * * * /etc/init.d/eqosplus start") | sort - | uniq - | crontab -
fi
}
start_service(){
[ -f $LOCK ] && exit
stop_service
_eqosplus_start
rm -f $LOCK
}
service_triggers() {
procd_add_reload_trigger 'eqosplus'
}
stop_service(){
kill -9 $(busybox ps -w | grep 'eqosplusctrl' | grep -v 'grep' | awk '{print $1}') >/dev/null 2>&1
sed -i '/eqosplus/d' $CR >/dev/null 2>&1
rm -f $LOCK 2>/dev/null
eqosplus stop
}
@@ -1,13 +0,0 @@
#!/bin/sh
chmod +x /etc/init.d/eqosplus /usr/bin/eqosplus*
uci -q batch <<-EOF >/dev/null
delete ucitrack.@eqosplus[-1]
add ucitrack eqosplus
set ucitrack.@eqosplus[-1].init=eqosplus
commit ucitrack
EOF
[ -s /etc/config/eqosplus ] || echo "config eqosplus" > /etc/config/eqosplus
rm -f /tmp/luci-indexcache
exit 0
-379
View File
@@ -1,379 +0,0 @@
#!/bin/bash
# Copyright (C) 2006 OpenWrt.org
# Copyright 2022-2025 sirpdboy <herboy2008@gmail.com>
crrun=$1
crid=$2
NAME=eqosplus
IDLIST="/var/$NAME.idlist"
LOCK="/var/lock/$NAME.lock"
TMPID="/var/$NAME.tmpid"
LOG="/var/$NAME_log.log"
# idlist=`uci show $NAME | grep "enable='1'" | grep "device" | grep -oE '\[.*?\]' | grep -o '[0-9]' `
if [ x$(uci get $NAME.@$NAME[0].ifname 2>/dev/null) = 'x1' ]; then
ifname=$(uci -q get network.lan.ifname)
[ "x$ifname" = "x" ] && ifname="device" || ifname="ifname"
dev=$(uci -q get network.wan.$ifname)
[ ! "$dev" ] && dev=br-lan
else
dev=$(uci -q get $NAME.@$NAME[0].ifname 2>/dev/null)
[ ! "$dev" ] && dev=br-lan
fi
bin_nft=$(which nft)
bin_tc=$(which tc)
bin_ip=$(which ip)
DEBUG=1
nft_type() {
if command -v nft >/dev/null; then
nftables_ver="true"
fi
}
# Debug functions
dbg_nft() {
[ "${DEBUG:-0}" -eq 0 ] || echo "DEBUG: nft $*"
$bin_nft "$@"
}
dbg_tc() {
[ "${DEBUG:-0}" -eq 0 ] || echo "DEBUG: tc $*"
$bin_tc "$@"
}
dbg_ip() {
[ "${DEBUG:-0}" -eq 0 ] || echo "DEBUG: ip $*"
$bin_ip "$@"
}
is_macaddr() {
ret=1
if echo "$1" | grep -qE '^([0-9A-Fa-f]{2}[-:]){5}[0-9A-Fa-f]{2}$'; then
ret=0
fi
return $ret
}
# 数值比较函数
is_gt_zero() {
local value=$1
# 使用bc进行浮点数比较
echo "$value > 0" | bc -l 2>/dev/null | grep -q 1
}
# 转换为kbit
to_kbit() {
local mb=$1
# MB/s 转 kbit/s: 1 MB/s = 8192 kbit/s
echo "scale=0; $mb * 8192 / 1" | bc 2>/dev/null || echo "0"
}
# Default commands
tc="dbg_tc"
ip="dbg_ip"
nft="dbg_nft"
nft_type
stop_qos() {
for face in $(tc qdisc show 2>/dev/null | grep htb | awk '{print $5}'); do
$tc qdisc del dev $face root 2>/dev/null
done
$tc qdisc del dev ${dev} root 2>/dev/null
$tc qdisc del dev ${dev}_ifb root 2>/dev/null
$tc qdisc del dev ${dev} ingress 2>/dev/null
$ip link del dev ${dev}_ifb 2>/dev/null 2>&1
if [ -n "$nftables_ver" ]; then
$nft delete table inet ${NAME} 2>/dev/null
$nft delete table bridge ${NAME} 2>/dev/null
fi
echo "" > "$IDLIST" 2>/dev/null
}
init_qosplus() {
insmod sch_htb 2>/dev/null
insmod act_mirred 2>/dev/null
insmod ifb 2>/dev/null
$ip link del dev ${dev}_ifb 2>/dev/null
$ip link add dev ${dev}_ifb name ${dev}_ifb type ifb
$ip link set dev ${dev}_ifb up
$tc qdisc del dev ${dev} root 2>/dev/null
$tc qdisc add dev ${dev} root handle 1:0 htb default 1
$tc class add dev ${dev} parent 1:0 classid 1:1 htb rate 1000mbit ceil 1000mbit
$tc qdisc del dev ${dev}_ifb root 2>/dev/null
$tc qdisc add dev ${dev}_ifb root handle 1:0 htb default 1
$tc class add dev ${dev}_ifb parent 1:0 classid 1:1 htb rate 1000mbit ceil 1000mbit
lanipaddr=$(uci -q get network.lan.ipaddr 2>/dev/null | awk -F '.' '{print $1"."$2"."$3".0/24"}')
if [ -n "$lanipaddr" ]; then
$tc filter add dev $dev parent 1:0 protocol ip prio 1 u32 match ip src "$lanipaddr" match ip dst "$lanipaddr" flowid 1:1
$tc filter add dev ${dev}_ifb parent 1:0 protocol ip prio 1 u32 match ip src "$lanipaddr" match ip dst "$lanipaddr" flowid 1:1
fi
$tc qdisc del dev ${dev} ingress 2>/dev/null
$tc qdisc add dev ${dev} ingress
$tc filter add dev ${dev} parent ffff: protocol all prio 2 u32 match u32 0 0 flowid 1:1 action mirred egress redirect dev ${dev}_ifb
if [ -n "$nftables_ver" ]; then
$nft delete table inet ${NAME} 2>/dev/null
$nft delete table bridge ${NAME} 2>/dev/null
$nft add table inet ${NAME}
$nft add chain inet ${NAME} mark_forward { type filter hook forward priority -200\; policy accept\; }
$nft add chain inet ${NAME} mark_output { type filter hook output priority -200\; policy accept\; }
$nft add chain inet ${NAME} mark_postrouting { type filter hook postrouting priority -200\; policy accept\; }
fi
return 0
}
del_id() {
local list_id=$1
id=$((list_id * 10 + 1000))
[ "${DEBUG:-0}" -eq 0 ] || echo "D: del_id $@ --$id --$mac"
$tc filter del dev ${dev} parent 1: handle 800::$(printf "%x" $id) 2>/dev/null
$tc class del dev ${dev} parent 1:1 classid 1:$id 2>/dev/null
$tc filter del dev ${dev}_ifb parent 1: handle 800::$(printf "%x" $id) 2>/dev/null
$tc class del dev ${dev}_ifb parent 1:1 classid 1:$id 2>/dev/null
mac=$(uci -q get $NAME.@device[$list_id].mac 2>/dev/null)
if [ -n "$nftables_ver" ] && [ -n "$mac" ]; then
if is_macaddr "$mac"; then
$nft delete rule inet ${NAME} mark_forward ether saddr $mac counter 2>/dev/null
$nft delete rule inet ${NAME} mark_output ether saddr $mac counter 2>/dev/null
$nft delete rule inet ${NAME} mark_postrouting ether saddr $mac counter 2>/dev/null
else
Z=$(echo $mac | awk -F '[/]' '{print $2}')
[ -n "$Z" ] && mac=$(echo $mac | awk -F '[/]' '{print $1}') || Z=32
$nft delete rule inet ${NAME} mark_forward ip saddr $mac/$Z counter 2>/dev/null
$nft delete rule inet ${NAME} mark_output ip saddr $mac/$Z counter 2>/dev/null
$nft delete rule inet ${NAME} mark_postrouting ip saddr $mac/$Z counter 2>/dev/null
fi
fi
}
add_mac() {
local list_id=$1
id=$((list_id * 10 + 1000))
M0=$(echo $mac | cut -d : -f 1)$(echo $mac | cut -d : -f 2)
M1=$(echo $mac | cut -d : -f 3)$(echo $mac | cut -d : -f 4)
M2=$(echo $mac | cut -d : -f 5)$(echo $mac | cut -d : -f 6)
[ "${DEBUG:-0}" -eq 0 ] || echo "D: add_mac $@ --id:$id --mac:$mac M012--$M0-$M1-$M2"
if is_gt_zero "$UL"; then
if [ -n "$nftables_ver" ]; then
$nft add rule inet ${NAME} mark_forward ether saddr $mac counter meta mark set $id
$nft add rule inet ${NAME} mark_output ether saddr $mac counter meta mark set $id
$nft add rule inet ${NAME} mark_postrouting ether saddr $mac counter meta mark set $id
fi
$tc class add dev ${dev}_ifb parent 1:1 classid 1:$id htb rate "${UL}kbit" ceil "${UL}kbit" prio $id burst 15k cburst 15k
$tc qdisc add dev ${dev}_ifb parent 1:$id handle ${id}: sfq perturb 10
$tc filter add dev ${dev}_ifb parent 1: protocol ip prio $id handle $id fw flowid 1:$id
$tc filter add dev ${dev}_ifb parent 1: protocol ip prio $((id + 100)) u32 \
match u16 0x0800 0xFFFF at -2 \
match u16 0x${M2} 0xFFFF at -4 \
match u32 0x${M0}${M1} 0xFFFFFFFF at -8 \
flowid 1:$id
fi
if is_gt_zero "$DL"; then
$tc class add dev ${dev} parent 1:1 classid 1:$id htb rate "${DL}kbit" ceil "${DL}kbit" prio $id burst 15k cburst 15k
$tc qdisc add dev ${dev} parent 1:$id handle ${id}: sfq perturb 10
$tc filter add dev ${dev} parent 1: protocol ip prio $id u32 \
match u16 0x0800 0xFFFF at -2 \
match u32 0x${M1}${M2} 0xFFFFFFFF at -12 \
match u16 0x${M0} 0xFFFF at -14 \
flowid 1:$id
fi
}
add_ip() {
local list_id=$1
id=$((list_id * 10 + 1000))
[ "${DEBUG:-0}" -eq 0 ] || echo "D: add_ip $@ --$id --$mac"
Z=$(echo $mac | awk -F '[/]' '{print $2}')
[ -n "$Z" ] && mac=$(echo $mac | awk -F '[/]' '{print $1}') || Z=32
if is_gt_zero "$UL"; then
if [ -n "$nftables_ver" ]; then
$nft add rule inet ${NAME} mark_forward ip saddr $mac/$Z counter meta mark set $id
$nft add rule inet ${NAME} mark_output ip saddr $mac/$Z counter meta mark set $id
$nft add rule inet ${NAME} mark_postrouting ip saddr $mac/$Z counter meta mark set $id
fi
$tc class add dev ${dev}_ifb parent 1:1 classid 1:$id htb rate "${UL}kbit" ceil "${UL}kbit" prio $id burst 15k cburst 15k
$tc qdisc add dev ${dev}_ifb parent 1:$id handle ${id}: sfq perturb 10
$tc filter add dev ${dev}_ifb parent 1: protocol ip prio $id handle $id fw flowid 1:$id
$tc filter add dev ${dev}_ifb parent 1:0 prio $((id + 100)) protocol ip u32 \
match ip src "$mac"/"$Z" \
flowid 1:$id
fi
if is_gt_zero "$DL"; then
$tc class add dev ${dev} parent 1:1 classid 1:$id htb rate "${DL}kbit" ceil "${DL}kbit" prio $id burst 15k cburst 15k
$tc qdisc add dev ${dev} parent 1:$id handle ${id}: sfq perturb 10
$tc filter add dev ${dev} parent 1:0 prio $id protocol ip u32 \
match ip dst "$mac"/"$Z" \
flowid 1:$id
fi
}
check_time() {
local start=$1
local end=$2
local current=$(date +%H%M)
local start_min=$((10#${start:0:2}*60 + 10#${start:3:2}))
local end_min=$((10#${end:0:2}*60 + 10#${end:3:2}))
local current_min=$((10#${current:0:2}*60 + 10#${current:2:2}))
if [[ $start_min -lt $end_min ]]; then
[[ $current_min -ge $start_min && $current_min -lt $end_min ]]
else
[[ $current_min -ge $start_min || $current_min -lt $end_min ]]
fi
}
check_list() {
local i=$1
local start_time=$(uci -q get $NAME.@device[$i].timestart 2>/dev/null)
local end_time=$(uci -q get $NAME.@device[$i].timeend 2>/dev/null)
local wweek=$(uci -q get $NAME.@device[$i].week 2>/dev/null)
local current_weekday=$(date +%u)
[ -z "$start_time" ] && [ -z "$end_time" ] && [ -z "$wweek" ] && return 0
if [ -n "$wweek" ] && [ "$wweek" != "0" ]; then
local day_match=0
for day in $(echo $wweek | tr ',' ' '); do
[ "$day" -eq "$current_weekday" ] && day_match=1 && break
done
[ "$day_match" -eq 0 ] && return 1
fi
if [ -n "$start_time" ] && [ -n "$end_time" ]; then
check_time "$start_time" "$end_time" || return 1
fi
return 0
}
case "$crrun" in
"stop")
stop_qos
;;
"start")
stop_qos > /dev/null 2>&1
sleep 2
init_qosplus
idlist=$(uci show $NAME 2>/dev/null | grep "enable='1'" | grep "device" | grep -oE '\[.*?\]' | grep -o '[0-9]' | sort -nu | sed -e 's/^/!/g' -e 's/$/!/g' > $IDLIST; cat $IDLIST 2>/dev/null | sed -e 's/!//g')
if [ ! -s "$IDLIST" ]; then
return 1
fi
device_count=0
for list in $(echo $idlist | sed -e 's/!//g'); do
if check_list $list; then
mac=$(uci -q get $NAME.@device[$list].mac 2>/dev/null)
download_mb=$(uci -q get $NAME.@device[$list].download 2>/dev/null)
upload_mb=$(uci -q get $NAME.@device[$list].upload 2>/dev/null)
comment=$(uci -q get $NAME.@device[$list].comment 2>/dev/null)
# 转换为kbit
DL=$(to_kbit "$download_mb")
UL=$(to_kbit "$upload_mb")
if [ -n "$mac" ]; then
if is_macaddr "$mac"; then
add_mac $list >>$LOG
echo "✅ MAC限速: $comment ($mac) - 下载:${download_mb}MB/s(${DL}kbit/s) 上传:${upload_mb}MB/s(${UL}kbit/s)" >>$LOG
else
add_ip $list >>$LOG
echo "✅ IP限速: $comment ($mac) - 下载:${download_mb}MB/s(${DL}kbit/s) 上传:${upload_mb}MB/s(${UL}kbit/s)" >>$LOG
fi
device_count=$((device_count + 1))
fi
else
[ -f "$IDLIST" ] && [ $(grep "!${list}!" "$IDLIST" 2>/dev/null | wc -l) -gt 0 ] && {
del_id $list >>$LOG
sed -i "/!$list!/d" "$IDLIST" >/dev/null 2>&1
comment=$(uci -q get $NAME.@device[$list].comment 2>/dev/null)
}
fi
done
echo "EQOSPLUS限速服务启动完成,共配置 $device_count 个设备" >>$LOG
echo ""
echo "当前限速状态:" >>$LOG
tc -s class show dev $dev | grep "rate" | head -5 >>$LOG
tc -s class show dev ${dev}_ifb | grep "rate" | head -5 >>$LOG
;;
"add")
echo "➕ 添加限速规则..."
for list in $(echo $crid | sed -e 's/!//g' | sed 's/,/ /g'); do
mac=$(uci -q get $NAME.@device[$list].mac 2>/dev/null)
download_mb=$(uci -q get $NAME.@device[$list].download 2>/dev/null)
upload_mb=$(uci -q get $NAME.@device[$list].upload 2>/dev/null)
comment=$(uci -q get $NAME.@device[$list].comment 2>/dev/null)
DL=$(to_kbit "$download_mb")
UL=$(to_kbit "$upload_mb")
if is_macaddr $mac; then
add_mac $list
echo "✅ 添加MAC限速: $comment ($mac)" >>$LOG
else
add_ip $list
echo "✅ 添加IP限速: $comment ($mac)" >>$LOG
fi
done
;;
"del")
for list in $(echo $crid | sed -e 's/!//g' | sed 's/,/ /g'); do
comment=$(uci -q get $NAME.@device[$list].comment 2>/dev/null)
mac=$(uci -q get $NAME.@device[$list].mac 2>/dev/null)
del_id $list
done
;;
"status")
echo "EQOSPLUS限速服务状态"
echo "### 网络接口: $dev ###"
echo "# 下载队列统计 #"
tc -s qdisc show dev $dev 2>/dev/null || echo "无下载队列"
echo "# 下载类统计 #"
tc -s class show dev $dev 2>/dev/null | grep -E "class htb|rate [0-9]" || echo "无下载类"
echo "# 下载过滤器 #"
tc -s filter show dev $dev parent 1: 2>/dev/null | head -10 || echo "无下载过滤器"
echo "### 虚拟接口: ${dev}_ifb ###"
echo "# 上传队列统计 #"
tc -s qdisc show dev ${dev}_ifb 2>/dev/null || echo "无上传队列"
echo "# 上传类统计 #"
tc -s class show dev ${dev}_ifb 2>/dev/null | grep -E "class htb|rate [0-9]" || echo "无上传类"
echo "# 上传过滤器 #"
tc -s filter show dev ${dev}_ifb parent 1: 2>/dev/null | head -10 || echo "无上传过滤器"
if [ -n "$nftables_ver" ]; then
echo "### NFTables规则 ###"
nft list table inet ${NAME} 2>/dev/null || echo "没有NFTables规则"
else
echo "NFTables不可用"
fi
# 显示流量统计
echo "### 流量统计 ###"
echo "下载流量:"
tc -s class show dev $dev 2>/dev/null | grep "Sent" | head -5
echo "上传流量:"
tc -s class show dev ${dev}_ifb 2>/dev/null | grep "Sent" | head -5
;;
esac
@@ -1,63 +0,0 @@
#!/bin/sh
# Copyright (C) 2006 OpenWrt.org
# Copyright 2022-2025 sirpdboy <herboy2008@gmail.com>
NAME=eqosplus
IDLIST="/var/$NAME.idlist"
TMPID="/var/$NAME.tmpid"
idlist=`uci show $NAME | grep "enable='1'" | grep "device" | grep -oE '\[.*?\]' | grep -o '[0-9]' `
check_list() {
i=$1
checki='0'
start_time=$(uci -q get $NAME.@device[$i].timestart 2>/dev/null)
end_time=$(uci -q get $NAME.@device[$i].timeend 2>/dev/null)
wweek=`uci -q get $NAME.@device[$i].week `
current_time=$(date +%H:%M)
current_weekday=$(date +%u)
for ww in `echo $wweek | sed 's/,/ /g' `; do
if [ $current_weekday = $ww ] || [ 'x0' = x$ww ] ;then
[ "$start_time" = "$end_time" ] && checki='1' || {
if [ "$start_time" \< "$end_time" ]; then
if [ "$current_time" \> "$start_time" ] && [ "$current_time" \< "$end_time" ] ; then
checki='1'
fi
else
if [ "$start_time" \> "$end_time" ] ; then
if [ "$current_time" \< "$start_time" ] && [ "$current_time" \> "$end_time" ] ; then
checki='1'
fi
fi
fi
}
fi
done
return
}
idlistusr(){
checki='0'
[ -s $IDLIST ] || touch $IDLIST
for list in $idlist ;do
check_list $list
if [ $checki == '1' ] ; then
[ `cat $IDLIST 2>/dev/null | grep "!${list}!" | wc -l ` -gt 0 ] || {
eqosplus add $list
echo "!${list}!" >> $IDLIST ; cat $IDLIST | sort | uniq > $TMPID ;cat $TMPID >$IDLIST ;rm -rf $TMPID
}
else
[ `cat $IDLIST 2>/dev/null | grep "!${list}!" | wc -l ` -gt 0 ] && {
eqosplus del $list
sed -i "/!$list!/d" $IDLIST >/dev/null 2>&1
}
fi
done
}
while :;do
sleep 30
idlistusr
sleep 30
done
@@ -1,11 +0,0 @@
{
"luci-app-eqosplus": {
"description": "Grant UCI access for luci-app-eqosplus",
"read": {
"uci": [ "eqosplus" ]
},
"write": {
"uci": [ "eqosplus" ]
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 585 KiB

+4 -4
View File
@@ -5,18 +5,18 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-honk
PKG_VERSION:=2026.11.01
PKG_VERSION:=2026.11.02
PKG_RELEASE:=1
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
PKG_SOURCE:=doona-4569bd91-57b2b25287ba.tar.gz
PKG_SOURCE:=doona-08b2a3e1-567a62ed5cac.tar.gz
# Each package Makefile is evaluated on its own, so this tag is defined here too
# instead of relying on the honk package's variable. It must match honk/Makefile:
# scripts/check.sh enforces that.
HONK_SOURCE_TAG?=source-41281523460
HONK_SOURCE_TAG?=source-41400973213
PKG_SOURCE_URL?=https://github.com/kenzok8/openwrt-honk/releases/download/$(HONK_SOURCE_TAG)
PKG_SOURCE_SUBDIR:=doona
PKG_HASH:=57b2b25287bae791e9a01e14e0c2deb1351e4135933959214d8996ed05458838
PKG_HASH:=567a62ed5cac88be540d73d9b41b09b309538b7cedc3b69d56fef880a084d96e
-include $(CURDIR)/generated-doona-stage.mk
@@ -24,6 +24,25 @@ function isHttpUrl(value) {
}
}
/* 导入完整配置前要让人看清会替换掉哪些段落:按候选文件里实际出现的顶层块列。 */
const SCOPE_LABELS = {
node: 'nodes', subscription: 'subscriptions', group: 'groups',
routing: 'routing', dns: 'DNS', global: 'global settings',
experimental: 'API and UI settings', include: 'included files'
};
function describeConfigScope(text) {
const found = [];
String(text || '').split('\n').forEach(function(line) {
const block = /^([A-Za-z_]+)[ \t]*\{/.exec(line);
if (!block || !SCOPE_LABELS[block[1]])
return;
const label = _(SCOPE_LABELS[block[1]]);
if (found.indexOf(label) < 0)
found.push(label);
});
return found.length ? found.join(' · ') : _('no recognised sections');
}
function loadYamlParser() {
if (window.jsyaml && window.jsyaml.load)
return Promise.resolve(window.jsyaml);
@@ -93,13 +112,26 @@ return view.extend({
const page = E('div', { 'class': 'cbi-map honk-page' });
page.appendChild(E('div', { 'class': 'honk-header' }, [
E('h2', {}, _('Configuration')),
E('p', { 'class': 'honk-header-sub' }, _('Honk reads its nodes, subscriptions and routing from /etc/honk/config.dae.'))
E('h2', {}, _('Configuration'))
]));
const modeText = E('span', {}, '');
const modeNote = E('p', { 'class': 'honk-note honk-mode-note' }, [ modeText ]);
page.appendChild(modeNote);
const draftNote = E('div', { 'class': 'honk-note honk-draft-note' });
draftNote.hidden = true;
page.appendChild(E('section', { 'class': 'honk-card honk-config-hero' }, [
E('p', { 'class': 'honk-hero-eyebrow' }, _('Configuration management')),
E('p', { 'class': 'honk-hero-lead' }, _('Add subscriptions and nodes, or import and edit a complete configuration.')),
E('p', { 'class': 'honk-hero-points' }, _('Append keeps your rules · validated before applying · rolled back when startup fails')),
E('details', { 'class': 'honk-hero-details' }, [
E('summary', {}, _('Read before use')),
E('ul', {}, [
E('li', {}, _('Subscriptions and nodes must use a protocol Honk supports; SSR is not supported.')),
E('li', {}, _('For a complete configuration, check lan_interface and the group names against your nodes.')),
E('li', {}, _('Importing a complete configuration replaces the current one; back it up first, startup failures roll back.')),
E('li', {}, _('In the editor, checking only parses; a draft stays inactive until you save and apply.'))
])
]),
draftNote
]));
/* --- Add subscription --- */
const subStatus = E('p', { 'class': 'honk-note', 'role': 'status', 'aria-live': 'polite' }, '');
@@ -136,12 +168,18 @@ return view.extend({
}).finally(function() { subBtn.disabled = false; });
});
const subModeHint = E('p', { 'class': 'honk-note honk-mode-hint' }, _('Manual mode: this tool is disabled until you restore managed mode (link at the top).'));
subModeHint.hidden = true;
nameInput.id = 'honk-sub-name';
urlInput.id = 'honk-sub-url';
const subSection = E('section', { 'class': 'honk-card' }, [
E('p', { 'class': 'honk-note' }, _('Enter a name and subscription URL. Honk checks for usable nodes before saving.')),
subModeHint,
E('div', { 'class': 'honk-subscription-add' }, [ nameInput, urlInput, subBtn ]),
E('div', { 'class': 'honk-form-row' }, [
E('label', { 'class': 'honk-form-label', 'for': 'honk-sub-name' }, _('Name')),
E('div', { 'class': 'honk-form-field' }, [ nameInput ])
]),
E('div', { 'class': 'honk-form-row' }, [
E('label', { 'class': 'honk-form-label', 'for': 'honk-sub-url' }, _('Subscription URL')),
E('div', { 'class': 'honk-form-field' }, [ urlInput ])
]),
E('div', { 'class': 'honk-form-actions' }, [ subBtn ]),
subStatus
]);
@@ -273,11 +311,7 @@ return view.extend({
}).finally(function() { pasteBtn.disabled = false; });
});
const pasteModeHint = E('p', { 'class': 'honk-note honk-mode-hint' }, _('Manual mode: this tool is disabled until you restore managed mode (link at the top).'));
pasteModeHint.hidden = true;
const pasteSection = E('section', { 'class': 'honk-card' }, [
E('p', { 'class': 'honk-note' }, _('Paste Clash YAML, Surge INI, Base64 or share links. They are parsed locally into nodes and appended to /etc/honk/config.dae.')),
pasteModeHint,
pasteInput,
E('div', { 'class': 'honk-actions' }, [ pasteBtn ]),
pasteStatus
@@ -297,7 +331,6 @@ return view.extend({
const importBtn = E('button', { 'class': 'cbi-button cbi-button-apply', 'type': 'button' }, [ _('Import and start') ]);
const importSection = E('section', { 'class': 'honk-card' }, [
E('p', { 'class': 'honk-note' }, _('Upload a complete dae file (subscriptions, nodes and routing included) and Honk starts with it: the file is validated, written to /etc/honk/config.dae and the service is restarted. To hand-edit or paste instead, use the editor below.')),
E('div', { 'class': 'honk-file-row' }, [ filePick, fileName, importBtn ]),
importStatus
]);
@@ -309,11 +342,8 @@ return view.extend({
'spellcheck': 'false', 'wrap': 'off', 'aria-label': _('Full dae configuration')
});
const saveBtn = E('button', { 'class': 'cbi-button cbi-button-positive', 'type': 'button' }, [ _('Save and apply') ]);
const reloadBtn = E('button', { 'class': 'cbi-button', 'type': 'button' }, [ _('Reload from disk') ]);
const draftBtn = E('button', { 'class': 'cbi-button', 'type': 'button' }, [ _('Save draft') ]);
const validateBtn = E('button', { 'class': 'cbi-button cbi-button-link', 'type': 'button' }, [ _('Check configuration') ]);
const managedBtn = E('a', { 'href': '#', 'class': 'honk-inline-link', 'role': 'button' }, [ _('Restore managed mode') ]);
managedBtn.hidden = true;
modeNote.appendChild(managedBtn);
let editorBaseline = '';
@@ -608,39 +638,9 @@ return view.extend({
/* A failed apply rolls the file back, so reload what is really on
disk — but keep the outcome message the user needs to read. */
const outcome = target.textContent;
return refreshMode().then(function() {
return loadCurrent(outcome === editorStatus.textContent ? outcome : '', true).catch(function() {});
});
});
}
function refreshMode() {
return honk.status().then(function(status) {
const manual = !!status && status.config_mode === 'manual';
managedBtn.hidden = !manual;
const tabState = page._honkTabs;
if (tabState) {
tabState.tabs.forEach(function(tab) {
const disabled = manual && tab.managedOnly;
tab.button.disabled = disabled;
tab.panel.hidden = tab.id !== tabState.getActive();
});
if (manual && ['sub', 'nodes'].indexOf(tabState.getActive()) >= 0)
tabState.select('edit');
}
subModeHint.hidden = !manual;
pasteModeHint.hidden = !manual;
subBtn.disabled = manual;
pasteBtn.disabled = manual;
nameInput.disabled = manual;
urlInput.disabled = manual;
pasteInput.disabled = manual;
modeText.textContent = manual
? _('Manual mode: the file below owns nodes, groups, routing and DNS.')
: _('Managed mode: the subscription and paste-nodes tools are active. Saving here switches Honk to manual mode.');
return status;
}).catch(function() {
modeText.textContent = '';
return loadCurrent(outcome === editorStatus.textContent ? outcome : '', true)
.then(renderDraftNote)
.catch(function() {});
});
}
@@ -689,6 +689,8 @@ return view.extend({
}
if (plan.notes.length)
setEditorContent(plan.text, _('Adapted in the editor: %s').format(plan.notes.join('; ')));
if (!confirm(_('This file replaces the current configuration.\n\nIt defines: %s\n\nBack up the current configuration first if you are unsure. Continue?').format(describeConfigScope(editor.value))))
return null;
return applyContent(editor.value, importStatus).then(function() {
if (importStatus.textContent === _('Configuration applied and Honk is running.'))
importStatus.textContent = _('%s imported and Honk is running.').format(file.name);
@@ -699,15 +701,6 @@ return view.extend({
}).finally(function() { importBtn.disabled = false; });
});
reloadBtn.addEventListener('click', function(ev) {
ev.preventDefault();
if (!confirmEditorReplace()) return;
reloadBtn.disabled = true;
loadCurrent(null, true).catch(function(error) {
editorStatus.textContent = honk.errorMessage(error, _('Could not read the saved configuration.'));
}).finally(function() { reloadBtn.disabled = false; });
});
validateBtn.addEventListener('click', function(ev) {
ev.preventDefault();
if (!editor.value.trim()) { editorStatus.textContent = _('The editor is empty.'); return; }
@@ -727,40 +720,88 @@ return view.extend({
adaptEditor().then(function(proceed) {
if (!proceed) { editorStatus.textContent = _('Save cancelled.'); return null; }
return applyContent(editor.value, editorStatus);
}).finally(function() { saveBtn.disabled = false; });
}).finally(function() {
saveBtn.disabled = false;
renderDraftNote();
});
});
managedBtn.addEventListener('click', function(ev) {
/* 草稿只备份到 /etc/honk/config-draft.dae:不动活动配置,也不重启服务。 */
draftBtn.addEventListener('click', function(ev) {
ev.preventDefault();
if (!confirm(_('Restore the managed configuration and return to the subscription tools? Honk restarts if it is running.'))) return;
managedBtn.disabled = true;
editorStatus.textContent = _('Restoring the managed configuration…');
withLongTimeout(function() { return honk.configUseManaged().then(honk.ensureOk); }).then(function(result) {
editorStatus.textContent = honk.resultMessage(result, _('Managed mode restored.'));
return refreshMode().then(function() { return loadCurrent(null, true); });
if (!editor.value.trim()) { editorStatus.textContent = _('The editor is empty.'); return; }
draftBtn.disabled = true;
editorStatus.textContent = _('Saving draft…');
honk.configSave(editor.value).then(function(result) {
editorStatus.textContent = isOk(result)
? _('Draft saved. It is not active until you apply it.')
: validationText(result);
return renderDraftNote();
}).catch(function(error) {
editorStatus.textContent = honk.errorMessage(error, _('Could not restore managed mode.'));
}).finally(function() { managedBtn.disabled = false; });
editorStatus.textContent = honk.errorMessage(error, _('Could not save the draft.'));
}).finally(function() { draftBtn.disabled = false; });
});
const editorSection = E('section', { 'class': 'honk-card' }, [
E('p', { 'class': 'honk-note' }, _('Edit or paste a complete dae file. Check configuration only parses the text; Save and apply writes /etc/honk/config.dae, restarts Honk, and rolls back if startup fails.')),
E('div', { 'class': 'honk-subscription-add' }, [ saveBtn, reloadBtn, validateBtn ]),
E('div', { 'class': 'honk-actions' }, [ saveBtn, draftBtn, validateBtn ]),
editor,
editorStatus
]);
/* 草稿提示:只提示存在与差异,载入必须由用户明确点击。 */
function draftButton(label, source) {
const button = E('button', { 'class': 'cbi-button cbi-button-link', 'type': 'button' }, [ label ]);
button.addEventListener('click', function(ev) {
ev.preventDefault();
loadDraft(source);
});
return button;
}
function renderDraftNote() {
return honk.status().then(function(status) {
const parts = [];
if (status && status.draft_pending) {
parts.push(E('span', {}, _('An unapplied draft exists.')));
parts.push(draftButton(_('Load draft'), 'draft'));
}
if (status && status.legacy_draft) {
parts.push(E('span', {}, _('An old manual draft from a previous version is kept on disk.')));
parts.push(draftButton(_('Load old draft'), 'legacy'));
}
draftNote.textContent = '';
if (!parts.length) {
draftNote.hidden = true;
return status;
}
draftNote.hidden = false;
parts.forEach(function(node) { draftNote.appendChild(node); });
return status;
}).catch(function() {
draftNote.hidden = true;
});
}
function loadDraft(source) {
if (!confirmEditorReplace()) return;
honk.configDraftRead(source).then(honk.ensureOk).then(function(result) {
setEditorContent(result.content || '', _('Draft loaded into the editor. It is not active until you apply it.'));
}).catch(function(error) {
editorStatus.textContent = honk.errorMessage(error, _('Could not read the draft.'));
});
}
loadCurrent().catch(function(error) {
editorStatus.textContent = honk.errorMessage(error, _('Could not read the saved configuration.'));
});
refreshMode();
renderDraftNote();
/* 一排标签页,只显示当前板块:四张卡片竖着堆太占屏,也不好一眼看出"有几种做法"。 */
const tabs = [
{ id: 'sub', label: _('Add subscription'), panel: subSection, managedOnly: true },
{ id: 'nodes', label: _('Add nodes'), panel: pasteSection, managedOnly: true },
{ id: 'import', label: _('Import config'), panel: importSection, managedOnly: false },
{ id: 'edit', label: _('Edit config'), panel: editorSection, managedOnly: false }
{ id: 'sub', label: _('Add subscription'), panel: subSection },
{ id: 'nodes', label: _('Add nodes'), panel: pasteSection },
{ id: 'import', label: _('Import config'), panel: importSection },
{ id: 'edit', label: _('Edit config'), panel: editorSection }
];
let activeTab = 'sub';
const tabBar = E('div', { 'class': 'honk-seg honk-tabs', 'role': 'tablist' });
@@ -791,8 +832,6 @@ return view.extend({
tabs.forEach(function(tab) { page.appendChild(tab.panel); });
selectTab(activeTab);
/* 曝光给 refreshMode:手动模式下订阅/节点两个标签不可用,自动切到编辑配置。 */
page._honkTabs = { tabs: tabs, select: selectTab, getActive: function() { return activeTab; } };
return page;
}
});
@@ -256,21 +256,6 @@
align-items: center;
}
.honk-subscription-add {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
margin-top: 10px;
}
/* 加 .honk-page 前缀压过后面的 `.honk-page .cbi-input-text{min-width:0}`,
否则手机上两个输入框会被挤成一条窄缝而不是换行。 */
.honk-page .honk-subscription-add .cbi-input-text {
flex: 1 1 12rem;
min-width: 12rem;
}
/* ---------- Meta line (mono, muted) ---------- */
.honk-meta {
@@ -622,14 +607,6 @@
/* ---------- Notes & alerts ---------- */
.honk-mode-note .honk-inline-link {
margin-left: 8px;
color: var(--hk-accent-border);
text-decoration: underline;
text-underline-offset: 2px;
font-size: 12.5px;
}
.honk-note {
font-size: 12.5px;
line-height: 1.55;
@@ -937,6 +914,127 @@
margin: 0;
}
/* ---------- Configuration page ---------- */
/* 顶部说明卡片:一眼看出"能做什么",折叠区只放真正需要确认的三条。 */
.honk-config-hero {
display: flex;
flex-direction: column;
gap: 6px;
padding: 20px 22px;
}
.honk-hero-eyebrow {
margin: 0;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--hk-muted);
}
.honk-hero-lead {
margin: 0;
font-size: 15px;
line-height: 1.55;
}
.honk-hero-points {
margin: 0;
font-size: 12.5px;
color: var(--hk-muted);
}
.honk-hero-details {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid var(--hk-card-border);
font-size: 12.5px;
line-height: 1.6;
color: var(--hk-muted);
}
.honk-hero-details summary {
cursor: pointer;
list-style: none;
color: var(--hk-accent-border);
}
.honk-hero-details summary::-webkit-details-marker {
display: none;
}
.honk-hero-details summary::before {
content: '\25B8';
display: inline-block;
margin-right: 6px;
transition: transform 0.15s ease;
}
.honk-hero-details[open] summary::before {
transform: rotate(90deg);
}
.honk-hero-details ul {
margin: 8px 0 0;
padding-left: 18px;
max-width: 72ch;
}
/* 订阅表单:桌面两行、标签右对齐;移动端上下排列(见下方媒体查询)。 */
.honk-form-row {
display: flex;
align-items: flex-start;
gap: 14px;
margin-bottom: 12px;
}
.honk-form-label {
flex: 0 0 7.5rem;
padding-top: 8px;
font-size: 12.5px;
text-align: right;
color: var(--hk-muted);
}
.honk-form-field {
flex: 1 1 auto;
min-width: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.honk-page .honk-form-field .cbi-input-text {
width: 100%;
min-width: 0;
}
.honk-form-hint {
margin: 0;
font-size: 12px;
color: var(--hk-muted);
}
.honk-form-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
.honk-draft-note {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.honk-page .honk-config-editor {
height: 46vh;
max-height: 560px;
}
/* ---------- Responsive ---------- */
@media (max-width: 640px) {
@@ -1047,3 +1145,36 @@ html[data-darkmode="true"] .honk-page .honk-log-output {
transform: none !important;
}
}
@media (max-width: 640px) {
.honk-form-row {
flex-direction: column;
align-items: stretch;
gap: 6px;
margin-bottom: 14px;
}
.honk-form-label {
flex: none;
padding-top: 0;
text-align: left;
}
.honk-form-actions {
justify-content: stretch;
}
.honk-form-actions .cbi-button {
flex: 1 1 auto;
}
.honk-config-hero {
padding: 16px;
}
.honk-page .honk-config-editor {
height: auto;
min-height: 300px;
max-height: 60vh;
}
}
@@ -230,15 +230,13 @@ return view.extend({
clashPortInput.value = status.clash_api_port ? String(status.clash_api_port) : '9090';
card.appendChild(E('h3', { 'class': 'honk-card-title' }, _('Service settings')));
/* Manual mode owns the whole runtime from config.dae, so the backend
rejects these settings. Disable them here instead of failing on save. */
const manualMode = status.config_mode === 'manual';
if (manualMode) {
/* 字段归属由当前配置决定,非 system.dae 提供时这些设置只读。 */
const owner = status.settings_owner || 'unknown';
if (owner !== 'system') {
[ networkSelect, portInput, clashEnabledInput, clashPortInput, clashSecretInput ].forEach(function(input) {
input.disabled = true;
});
save.disabled = true;
card.appendChild(E('p', { 'class': 'honk-note honk-mode-hint' }, _('Manual mode: these settings belong to config.dae, so they are read-only here. Restore managed mode on the Configuration page to change them.')));
}
if (!(data.networks || []).length)
networkSelect.appendChild(E('option', { 'value': '' }, _('No network interfaces found')));
@@ -58,12 +58,16 @@ const callImportDae = rpc.declare({
expect: { '': {} }
});
const callConfigRead = rpc.declare({ object: 'honk', method: 'config_read', expect: { '': {} } });
const callConfigDraftRead = rpc.declare({ object: 'honk', method: 'config_draft_read', expect: { '': {} } });
const callConfigDraftRead = rpc.declare({
object: 'honk',
method: 'config_draft_read',
params: [ 'source' ],
expect: { '': {} }
});
const callLanCandidates = rpc.declare({ object: 'honk', method: 'lan_candidates', expect: { '': {} } });
const callConfigValidate = rpc.declare({ object: 'honk', method: 'config_validate', params: [ 'content' ], expect: { '': {} } });
const callConfigSave = rpc.declare({ object: 'honk', method: 'config_save', params: [ 'content' ], expect: { '': {} } });
const callConfigApply = rpc.declare({ object: 'honk', method: 'config_apply', expect: { '': {} } });
const callConfigUseManaged = rpc.declare({ object: 'honk', method: 'config_use_managed', expect: { '': {} } });
const callRepair = rpc.declare({ object: 'honk', method: 'repair', expect: { '': {} } });
const callBackup = rpc.declare({ object: 'honk', method: 'backup', expect: { '': {} } });
const callRestore = rpc.declare({ object: 'honk', method: 'restore', expect: { '': {} } });
@@ -248,12 +252,10 @@ function tokenMessage(token) {
config_too_large: _('The configuration file exceeds the 2 MiB limit.'),
config_applied: _('Configuration applied and Honk is running.'),
config_apply_failed_rolled_back: _('Honk could not start with this configuration. The previous file was restored.'),
config_apply_rollback_failed_snapshot_retained: _('Startup failed and the automatic rollback did not finish. The snapshot was kept; run system recovery.'),
config_apply_rollback_failed: _('Startup failed and the automatic rollback did not finish. The snapshot was kept; run system recovery.'),
config_changed_concurrently: _('The configuration changed during the operation. Nothing was applied.'),
config_write_failed_snapshot_retained: _('Could not write the configuration. The snapshot was kept.'),
service_stop_unconfirmed_snapshot_retained: _('Could not confirm that Honk stopped. The operation was aborted and the snapshot was kept.'),
manual_mode_required: _('Manual mode is active. Restore managed mode before using the subscription tools.'),
managed_edit_unavailable: _('Manual mode is active, so this tool cannot edit the configuration. Select Restore managed mode first.'),
service_stop_unconfirmed: _('Could not confirm that Honk stopped. The operation was aborted and the snapshot was kept.'),
administrator_exists_reuse_credentials_or_reset_state: _('已经初始化过了,请用原来的账号登录 Doona。忘记密码:到「维护 → 重置管理员」清掉管理员记录(不动订阅和规则),再回到本页重新初始化。'),
administrator_setup_failed: _('创建 Doona 管理员失败,请到「维护 → 日志」查看核心日志。'),
invalid_credentials: _('用户名只能包含字母、数字、下划线、点、连字符(不超过 64 个字符);密码长度必须是 8 到 128 个字符,可以包含空格。'),
@@ -271,9 +273,7 @@ function tokenMessage(token) {
main_config_changed_concurrently: _('操作期间 config.dae 被其他进程改动,已中止。'),
invalid_package_config: _('luci-app-honk 的软件包配置不完整,请重新安装。'),
invalid_config_path: _('配置路径不是 /etc/honk/config.dae,拒绝操作。'),
manual_config_valid: _('手动模式配置有效。'),
manual_config_invalid: _('手动模式配置无效,请先修正 config.dae。'),
required_system_include_missing_manual_repair: _('手动模式的配置缺少 include { system.dae },无法修复托管设置。'),
required_system_include_missing: _('当前配置没有 include { system.dae },这些设置请到「编辑配置」里修改。'),
native_api_setup_state_unavailable: _('读不到核心的 API 状态,请查看日志。'),
ui_assets_missing: _('缺少 Doona 静态文件(/usr/share/doona),请重新安装 luci-app-honk。'),
mock_core_start_failed: _('测试用核心启动失败:通常是内核缺 BTF(/sys/kernel/btf/vmlinux)或 eBPF 依赖模块。'),
@@ -288,7 +288,6 @@ function tokenMessage(token) {
setup_verification_failed: _('初始化后的自检没通过,请查看日志。'),
lan_ipv4_unavailable: _('选中的 LAN 网络没有 IPv4 地址。'),
mkdir_failed: _('创建目录失败,请检查磁盘空间与权限。'),
settings_unavailable_in_manual_mode: _('手动模式下这些设置由 config.dae 决定,无法保存。'),
settings_too_large: _('提交的设置数据过大。'),
invalid_settings_json: _('设置的请求格式不正确。'),
invalid_boolean_settings: _('开关类设置取值不合法。'),
@@ -320,7 +319,7 @@ function tokenMessage(token) {
subscription_empty: _('订阅内容是空的。'),
subscription_too_large: _('订阅内容超过 4 MiB,拒绝处理。'),
configuration_validation_failed: _('配置没有通过静态校验。'),
config_changed_concurrently_restart_failed_snapshot_retained: _('操作期间配置被改动且重启失败,快照已保留。'),
config_changed_concurrently_restart_failed: _('操作期间配置被改动且重启失败,快照已保留。'),
invalid_job_id: _('任务编号不合法。'),
job_id_unavailable: _('拿不到任务编号。'),
job_not_found: _('找不到该任务。'),
@@ -328,19 +327,18 @@ function tokenMessage(token) {
job_storage_unavailable: _('任务存储不可用(/tmp 空间不足?)。'),
prepared: _('上传目录已准备好。'),
probe_failed: _('预检失败,请稍后重试。'),
managed_switch_failed_rolled_back: _('Could not switch back to managed mode. The previous configuration was restored.'),
managed_switch_rollback_failed_snapshot_retained: _('Switching back to managed mode failed and the rollback did not finish. The snapshot was kept.'),
managed_snapshot_invalid: _('The saved managed configuration is not a valid regular file.'),
config_draft_missing: _('There is no saved draft to apply.'),
config_draft_invalid: _('The saved draft is not a valid regular file.'),
managed_mode_restored: _('Managed mode restored.'),
managed_snapshot_missing: _('There is no saved managed configuration to restore.'),
lan_candidates_unavailable: _('Could not read the list of network interfaces.'),
config_read_failed: _('Honk could not read the current configuration.'),
config_request_invalid: _('The configuration request is empty or invalid.'),
config_validation_failed: _('The configuration did not pass static validation.'),
config_validation_unavailable: _('The core validator did not return a result.'),
config_settings_owned_by_file: _('这些设置由当前配置文件决定,请到「配置 → 编辑配置」里修改。'),
config_valid: _('配置通过校验。'),
config_invalid: _('配置没有通过校验。'),
config_saved_pending_apply: _('Configuration saved. The running service is unchanged until you apply it.'),
quick_add_unsupported: _('当前配置不支持快捷追加,请在「编辑配置」中修改。'),
config_loaded: _('Configuration loaded.'),
dae_loaded: _('Configuration file loaded into the editor.'),
upload_ready: _('Upload location ready.'),
@@ -467,7 +465,6 @@ return baseclass.extend({
configValidate: callConfigValidate,
configSave: callConfigSave,
configApply: callConfigApply,
configUseManaged: callConfigUseManaged,
repair: callRepair,
backup: callBackup,
restore: callRestore,
@@ -69,16 +69,13 @@ return view.extend({
section.appendChild(E('h3', { 'class': 'honk-card-title' }, _('Honk settings')));
/* In manual mode the whole runtime comes from config.dae, so the backend
refuses these settings. Disable them instead of letting the save fail
with an error the user cannot act on. */
const manualMode = !!(data.status && data.status.config_mode === 'manual');
if (manualMode) {
/* 配置自带 global/experimental 时这些值归文件,后端会拒绝写入。 */
const owner = (data.status && data.status.settings_owner) || 'unknown';
if (owner !== 'system') {
[ networkSelect, portInput, clashEnabledInput, clashPortInput, clashSecretInput ].forEach(function(input) {
input.disabled = true;
});
save.disabled = true;
section.appendChild(E('p', { 'class': 'honk-note honk-mode-hint' }, _('Manual mode: these settings belong to config.dae, so they are read-only here. Restore managed mode on the Configuration page to change them.')));
}
if (data.statusError)
section.appendChild(E('div', { 'class': 'alert-message' }, _('Could not read current settings: %s').format(data.statusError)));
File diff suppressed because it is too large. Load diff
+144 -164
View File
@@ -30,14 +30,6 @@ emit_result() {
json_dump
}
require_managed_mode() {
managed_mode=$(uci -q get honk.main.config_mode 2>/dev/null)
[ -n "$managed_mode" ] || managed_mode=managed
[ "$managed_mode" = managed ] && return 0
emit_result false managed_edit_unavailable
return 1
}
emit_errors() {
ok=$1
[ "$ok" = true ] && ok=1 || ok=0
@@ -59,12 +51,11 @@ lock_owner_invalid() {
}
status() {
local enabled initialized boot_enabled running api_url api_ready core_version core_commit doona_version kernel lan_network listen_port errors listen clash_api_enabled clash_api_port clash_api_secret clash_api_url clash_api_ready clash_ui_ready clash_secret_set config_mode api_enabled core_ready config_pending manual_runtime clash_listen_addr
local enabled initialized boot_enabled running api_url api_ready core_version core_commit doona_version kernel lan_network listen_port errors listen clash_api_enabled clash_api_port clash_api_secret clash_api_url clash_api_ready clash_ui_ready clash_secret_set settings_owner api_enabled core_ready config_pending draft_pending legacy_draft clash_listen_addr
config_load honk
config_get_bool enabled main enabled 0
config_get_bool initialized main initialized 0
config_get_bool boot_enabled main boot_enabled 0
config_get config_mode main config_mode managed
config_get listen_port main listen_port 9527
config_get lan_network main lan_network lan
config_get_bool clash_api_enabled main clash_api_enabled 0
@@ -72,77 +63,70 @@ status() {
config_get clash_api_secret main clash_api_secret ''
managed_pid=$(procd_core_pid)
if is_managed_core "$managed_pid"; then running=1; else running=0; fi
api_enabled=1
# 只有 system.dae 负责这些字段时,UCI 里的端口/密钥才有意义。
settings_owner=$(config_owner /etc/honk/config.dae)
api_enabled=0
clash_api_enabled=0
clash_api_port=0
clash_secret_set=0
clash_listen_addr=
core_ready=0
config_pending=0
if [ "$config_mode" = manual ]; then
api_enabled=0
clash_api_enabled=0
clash_api_port=0
clash_secret_set=0
clash_listen_addr=
manual_runtime=$("$CORE" config validate --config /etc/honk/config.dae 2>/dev/null)
if [ -n "$manual_runtime" ]; then
json_load "$manual_runtime" 2>/dev/null
json_select runtime 2>/dev/null
json_get_var api_enabled native_api_enabled
json_get_var listen native_api_listen
json_get_var clash_listen_addr clash_api_listen
json_get_var clash_secret_set clash_api_secret_set
json_select .. 2>/dev/null
fi
[ "$api_enabled" = 1 ] || api_enabled=0
if [ -n "$managed_pid" ] && honk_lifecycle_ready_owned "$managed_pid"; then core_ready=1; fi
if [ "$core_ready" = 1 ]; then
runtime_token=$(awk 'NR == 1 { print $1 }' /var/run/honk/readiness 2>/dev/null)
runtime_marker=/var/run/honk/core-ready-$runtime_token
runtime_api_enabled=$(sed -n 's/^api_enabled=//p' "$runtime_marker" 2>/dev/null)
runtime_listen=$(sed -n 's/^api_listen=//p' "$runtime_marker" 2>/dev/null)
runtime_clash_listen=$(sed -n 's/^clash_api_listen=//p' "$runtime_marker" 2>/dev/null)
runtime_clash_secret_set=$(sed -n 's/^clash_api_secret_set=//p' "$runtime_marker" 2>/dev/null)
case "$runtime_api_enabled" in true) api_enabled=1 ;; false) api_enabled=0 ;; esac
[ -n "$runtime_listen" ] && listen=$runtime_listen
clash_listen_addr=$runtime_clash_listen
case "$runtime_clash_secret_set" in true) clash_secret_set=1 ;; false) clash_secret_set=0 ;; esac
fi
if [ -n "$clash_listen_addr" ]; then
clash_api_enabled=1
clash_api_port=${clash_listen_addr##*:}
fi
active_hash=$(sha256sum /etc/honk/config.dae 2>/dev/null | awk '{print $1}')
runtime_hash=$(awk 'NR == 1 { print $2 }' /var/run/honk/readiness 2>/dev/null)
[ -n "$runtime_hash" ] && [ "$runtime_hash" = "$active_hash" ] || { [ "$running" = 1 ] && config_pending=1; }
if [ -f /etc/honk/manual-config.dae ] && [ ! -L /etc/honk/manual-config.dae ]; then
draft_hash=$(sha256sum /etc/honk/manual-config.dae 2>/dev/null | awk '{print $1}')
[ "$draft_hash" = "$active_hash" ] || config_pending=1
fi
else
listen=$(sed -n "s/^[[:space:]]*listen:[[:space:]]*'\\([^']*\\)'.*/\\1/p" /etc/honk/system.dae | head -n 1)
draft_pending=0
legacy_draft=0
listen=
runtime_config=$("$CORE" config validate --config /etc/honk/config.dae 2>/dev/null)
if [ -n "$runtime_config" ]; then
json_load "$runtime_config" 2>/dev/null
json_select runtime 2>/dev/null
json_get_var api_enabled native_api_enabled
json_get_var listen native_api_listen
json_get_var clash_listen_addr clash_api_listen
json_get_var clash_secret_set clash_api_secret_set
json_select .. 2>/dev/null
fi
case "$api_enabled" in true) api_enabled=1 ;; *) api_enabled=0 ;; esac
case "$clash_secret_set" in true) clash_secret_set=1 ;; *) clash_secret_set=0 ;; esac
if [ -n "$managed_pid" ] && honk_lifecycle_ready_owned "$managed_pid"; then core_ready=1; fi
if [ "$core_ready" = 1 ]; then
runtime_token=$(awk 'NR == 1 { print $1 }' /var/run/honk/readiness 2>/dev/null)
runtime_marker=/var/run/honk/core-ready-$runtime_token
runtime_api_enabled=$(sed -n 's/^api_enabled=//p' "$runtime_marker" 2>/dev/null)
runtime_listen=$(sed -n 's/^api_listen=//p' "$runtime_marker" 2>/dev/null)
runtime_clash_listen=$(sed -n 's/^clash_api_listen=//p' "$runtime_marker" 2>/dev/null)
runtime_clash_secret_set=$(sed -n 's/^clash_api_secret_set=//p' "$runtime_marker" 2>/dev/null)
case "$runtime_api_enabled" in true) api_enabled=1 ;; false) api_enabled=0 ;; esac
[ -n "$runtime_listen" ] && listen=$runtime_listen
clash_listen_addr=$runtime_clash_listen
case "$runtime_clash_secret_set" in true) clash_secret_set=1 ;; false) clash_secret_set=0 ;; esac
fi
if [ -n "$clash_listen_addr" ]; then
clash_api_enabled=1
clash_api_port=${clash_listen_addr##*:}
fi
case "$listen" in
''|*[!0-9.:]*) listen="127.0.0.1:$listen_port" ;;
esac
api_url="http://$listen"
if [ "$config_mode" = manual ] && [ "$api_enabled" != 1 ]; then api_url=""; fi
case "$clash_api_port" in
''|*[!0-9]*) clash_api_port=0 ;;
esac
if [ "$config_mode" = manual ]; then
clash_api_url=
[ -n "$clash_listen_addr" ] && clash_api_url="http://$clash_listen_addr"
else
clash_api_url="http://${listen%:*}:$clash_api_port"
[ "$api_enabled" = 1 ] || api_url=""
clash_api_url=
[ -n "$clash_listen_addr" ] && clash_api_url="http://$clash_listen_addr"
active_hash=$(sha256sum /etc/honk/config.dae 2>/dev/null | awk '{print $1}')
runtime_hash=$(awk 'NR == 1 { print $2 }' /var/run/honk/readiness 2>/dev/null)
[ -n "$runtime_hash" ] && [ "$runtime_hash" = "$active_hash" ] || { [ "$running" = 1 ] && config_pending=1; }
# 草稿与活动配置分开标记:草稿存在不代表已生效,也不参与运行态判断。
if [ -f /etc/honk/config-draft.dae ] && [ ! -L /etc/honk/config-draft.dae ]; then
draft_hash=$(sha256sum /etc/honk/config-draft.dae 2>/dev/null | awk '{print $1}')
[ "$draft_hash" = "$active_hash" ] || draft_pending=1
fi
[ -f /etc/honk/manual-config.dae ] && [ ! -L /etc/honk/manual-config.dae ] && legacy_draft=1
clash_api_ready=0
if [ "$running" = 1 ] && [ "$clash_api_enabled" = 1 ] && [ "$clash_api_port" -gt 0 ] && honk_lifecycle_listener_owned "$managed_pid" "$clash_api_port"; then clash_api_ready=1; fi
clash_ui_ready=0
[ -s /usr/share/honk/zashboard/index.html ] && clash_ui_ready=1
# UCI 里的 secret 只对托管模式有意义:手动模式的密码在 config.dae 里,
# 已经由运行标记读取过,这里不能再拿 UCI 覆盖。
if [ "$config_mode" != manual ]; then
clash_secret_set=0
[ -n "$clash_api_secret" ] && clash_secret_set=1
fi
case "$clash_api_port" in
''|*[!0-9]*) clash_api_port=0 ;;
esac
if [ "$running" = 1 ] && honk_lifecycle_api_owned "$managed_pid"; then api_ready=1; else api_ready=0; fi
core_version=$($CORE --version 2>/dev/null | sed -n '1p')
[ -n "$core_version" ] || core_version=unknown
@@ -155,8 +139,8 @@ status() {
if [ "$enabled" != 1 ]; then errors="$errors disabled"; fi
if [ "$initialized" != 1 ]; then errors="$errors not_initialized"; fi
if [ "$enabled" = 1 ] && [ "$initialized" = 1 ] && [ "$running" != 1 ]; then errors="$errors service_not_running"; fi
if [ "$running" = 1 ] && [ "$api_ready" != 1 ] && { [ "$config_mode" != manual ] || [ "$api_enabled" = 1 ]; }; then errors="$errors api_unavailable"; fi
if [ "$config_mode" = manual ] && [ "$running" = 1 ] && [ "$core_ready" != 1 ]; then errors="$errors core_not_ready"; fi
if [ "$running" = 1 ] && [ "$api_ready" != 1 ] && [ "$api_enabled" = 1 ]; then errors="$errors api_unavailable"; fi
if [ "$running" = 1 ] && [ "$core_ready" != 1 ]; then errors="$errors core_not_ready"; fi
if [ -d "$HONK_LOCK_DIR" ]; then
if lock_owner_invalid || honk_lock_is_stale; then errors="$errors stale_operation_lock maintenance_recovery_required"; else errors="$errors operation_in_progress"; fi
fi
@@ -167,8 +151,10 @@ status() {
json_add_boolean initialized "$initialized"
json_add_boolean boot_enabled "$boot_enabled"
json_add_boolean running "$running"
json_add_string config_mode "$config_mode"
json_add_string settings_owner "$settings_owner"
json_add_boolean config_pending "$config_pending"
json_add_boolean draft_pending "$draft_pending"
json_add_boolean legacy_draft "$legacy_draft"
json_add_boolean core_ready "$core_ready"
json_add_boolean api_enabled "$api_enabled"
json_add_boolean api_ready "$api_ready"
@@ -194,7 +180,6 @@ status() {
json_dump
}
service_ready_config() {
local enabled initialized
config_load honk
@@ -331,9 +316,48 @@ logs() {
rm -f "$log_file"
}
# 判出厂配置时忽略本机直连区块:启动脚本写入本机地址后逐字比较必然失败。
strip_local_direct() {
awk -v begin=' # honk:local-direct-begin' -v end=' # honk:local-direct-end' '
$0 == begin {
if (skip) bad = 1
b++
skip = 1
print
next
}
$0 == end {
if (!skip) bad = 1
e++
skip = 0
print
next
}
skip { next }
{ print }
END { if (bad || skip || b != 1 || e != 1) exit 1 }
' "$1"
}
# 标记缺失、重复或不成对时一律不认定为出厂配置,避免误改用户出口。
is_factory_config() {
local current=$1 factory=/usr/share/honk/default-config.dae a b rc
[ -f "$factory" ] && [ ! -L "$factory" ] || return 1
a=$(mktemp /tmp/honk-route.XXXXXX) || return 1
b=$(mktemp /tmp/honk-route.XXXXXX) || { rm -f "$a"; return 1; }
if strip_local_direct "$current" > "$a" && strip_local_direct "$factory" > "$b"; then
cmp -s "$a" "$b"
rc=$?
else
rc=1
fi
rm -f "$a" "$b"
return "$rc"
}
promote_default_route() {
local current=$1 candidate=$2 dns_block
if cmp -s "$current" /usr/share/honk/default-config.dae; then
if is_factory_config "$current"; then
sed 's/^ fallback: direct$/ fallback: proxy/' "$candidate" > "$candidate.route" || return 1
elif [ "$(sha256sum "$current" | awk '{print $1}')" = 83d1cc32bfe5b44ecae4b05c2c82c81ce9a36ab29f391d1e1d1d6b4993e17d2b ]; then
# The r1 package had only an include and a direct fallback. Preserve the
@@ -387,9 +411,18 @@ validate_proxy_nodes() (
trap 'validator_cleanup' EXIT
trap 'exit 1' HUP INT TERM
port=$((20000 + $(hexdump -n 2 -e '1/2 "%u"' /dev/urandom) % 20000))
if ! cp -p /etc/honk/*.dae "$temp/" || ! cp "$candidate" "$temp/config.dae"; then
# 先带完整文件树校验原始候选(候选名不是 .dae 结尾,复制成 candidate-original.dae)。
if ! cp -p /etc/honk/*.dae "$temp/" || ! cp -p "$candidate" "$temp/candidate-original.dae"; then
rm -rf "$temp"
return 1
fi
if ! "$CORE" config validate --config "$temp/candidate-original.dae" >/dev/null 2>&1; then
rm -rf "$temp"
return 2
fi
# 候选原样作为被测配置,落盘路径与 API 由我们写的 system.dae 提供。
cp -p "$candidate" "$temp/config.dae" || { rm -rf "$temp"; return 1; }
chmod 0600 "$temp/config.dae" || { rm -rf "$temp"; return 1; }
cat > "$temp/system.dae" <<EOF
global {
wan_interface: auto
@@ -409,7 +442,8 @@ experimental {
}
}
EOF
DAE_LOCATION_ASSET=/usr/share/v2ray "$CORE" --config "$temp/config.dae" --mock-ebpf > "$temp/core.log" 2>&1 &
DAE_LOCATION_ASSET=/usr/share/v2ray "$CORE" --config "$temp/config.dae" --mock-ebpf \
--data-dir "$temp" --log-file "$temp/core.log" > "$temp/core.console" 2>&1 &
core_pid=$!
ready=0
result=2
@@ -576,7 +610,8 @@ add_subscription() {
cfg=/etc/honk/config.dae
[ -f "$cfg" ] || { emit_result false config_missing; return 1; }
begin_mutation
require_managed_mode || return 1
# 快捷追加只支持系统托管结构;自带 global/experimental 或 include 其他文件一律拒绝。
[ "$(config_owner "$cfg")" = system ] || { emit_result false quick_add_unsupported; return 1; }
original_hash=$(sha256sum "$cfg" | awk '{print $1}')
if grep -qF "$sub_tag:" "$cfg" || grep -qF "'$sub_name':" "$cfg"; then
@@ -634,7 +669,8 @@ add_nodes() {
cfg=/etc/honk/config.dae
[ -f "$cfg" ] || { emit_result false config_missing; return 1; }
begin_mutation
require_managed_mode || return 1
# 快捷追加只支持系统托管结构;自带 global/experimental 或 include 其他文件一律拒绝。
[ "$(config_owner "$cfg")" = system ] || { emit_result false quick_add_unsupported; return 1; }
original_hash=$(sha256sum "$cfg" | awk '{print $1}')
node_prefix="node_$(hexdump -n 4 -e '4/1 "%02x"' /dev/urandom)_"
@@ -714,7 +750,6 @@ import_dae() {
cfg=/etc/honk/config.dae
[ -f "$cfg" ] || { emit_result false config_missing; return 1; }
begin_mutation
require_managed_mode || return 1
# 提取 node 块(过滤 honk-core 不支持的 ssr/snell 协议)
node_block=$(awk '
@@ -859,25 +894,35 @@ config_read() {
[ "$config_size" -le "$DAE_UPLOAD_LIMIT" ] || { emit_result false config_too_large; return 1; }
config_content=$(cat /etc/honk/config.dae; printf '\001') || { emit_result false config_read_failed; return 1; }
config_content=${config_content%?}
config_load honk
config_get config_mode main config_mode managed
json_init
json_add_boolean ok 1
json_add_string message config_loaded
json_add_string mode "$config_mode"
json_add_string content "$config_content"
json_dump
}
# 只读草稿,不触发任何应用。source=legacy 时读旧 manual-config.dae(用户明确选择才调)。
config_draft_read() {
[ -f /etc/honk/manual-config.dae ] && [ ! -L /etc/honk/manual-config.dae ] || { emit_result false config_draft_missing; return 1; }
config_size=$(wc -c < /etc/honk/manual-config.dae)
request=$(cat)
draft_source=draft
if [ -n "$request" ]; then
json_load "$request" 2>/dev/null || { emit_result false invalid_request; return 1; }
json_get_var draft_source source
fi
case "$draft_source" in
draft) draft_file=/etc/honk/config-draft.dae ;;
legacy) draft_file=/etc/honk/manual-config.dae ;;
*) emit_result false invalid_request; return 1 ;;
esac
[ -f "$draft_file" ] && [ ! -L "$draft_file" ] || { emit_result false config_draft_missing; return 1; }
config_size=$(wc -c < "$draft_file")
[ "$config_size" -le "$DAE_UPLOAD_LIMIT" ] || { emit_result false config_too_large; return 1; }
config_content=$(cat /etc/honk/manual-config.dae; printf '\001') || { emit_result false config_read_failed; return 1; }
config_content=$(cat "$draft_file"; printf '\001') || { emit_result false config_read_failed; return 1; }
config_content=${config_content%?}
json_init
json_add_boolean ok 1
json_add_string message config_draft_loaded
json_add_string source "$draft_source"
json_add_string content "$config_content"
json_dump
}
@@ -981,16 +1026,16 @@ config_save() {
[ -n "$config_validation" ] && printf '%s\n' "$config_validation" || emit_result false config_validation_unavailable
return 0
fi
draft_path=/etc/honk/manual-config.dae
draft_path=/etc/honk/config-draft.dae
draft_backup=
if [ -e "$draft_path" ] || [ -L "$draft_path" ]; then
[ -f "$draft_path" ] && [ ! -L "$draft_path" ] || { rm -f "$config_candidate"; emit_result false config_draft_invalid; return 1; }
draft_backup=$(mktemp /etc/honk/.manual-config-backup.XXXXXX) || { rm -f "$config_candidate"; emit_result false config_write_failed; return 1; }
draft_backup=$(mktemp /etc/honk/.config-draft-backup.XXXXXX) || { rm -f "$config_candidate"; emit_result false config_write_failed; return 1; }
cp -p "$draft_path" "$draft_backup" || { rm -f "$config_candidate" "$draft_backup"; emit_result false config_write_failed; return 1; }
fi
if ! mv -f "$config_candidate" "$draft_path"; then
if [ -n "$draft_backup" ]; then
draft_restore=$(mktemp /etc/honk/.manual-config-restore.XXXXXX)
draft_restore=$(mktemp /etc/honk/.config-draft-restore.XXXXXX)
cp -p "$draft_backup" "$draft_restore" && mv -f "$draft_restore" "$draft_path"
rm -f "$draft_restore"
fi
@@ -1000,16 +1045,15 @@ config_save() {
fi
chmod 0600 "$draft_path" || { emit_result false config_write_failed; return 1; }
rm -f "$draft_backup"
rm -f /etc/honk/manual-system.dae
emit_result true config_saved_pending_apply
}
config_apply() {
begin_mutation
draft_path=/etc/honk/manual-config.dae
draft_path=/etc/honk/config-draft.dae
[ -f "$draft_path" ] && [ ! -L "$draft_path" ] || { emit_result false config_draft_missing; return 1; }
activate_dir=$(mktemp -d /etc/honk/.manual-activate.XXXXXX) || { emit_result false config_write_failed; return 1; }
activate_file=/etc/honk/.manual-config-${activate_dir##*.manual-activate.}.dae
activate_dir=$(mktemp -d /etc/honk/.config-activate.XXXXXX) || { emit_result false config_write_failed; return 1; }
activate_file=/etc/honk/.config-${activate_dir##*.config-activate.}.dae
(set -C; cp "$draft_path" "$activate_file") || { rm -rf "$activate_dir"; emit_result false config_write_failed; return 1; }
rmdir "$activate_dir" || { rm -rf "$activate_dir"; rm -f "$activate_file"; emit_result false config_write_failed; return 1; }
chmod 0600 "$activate_file" || { rm -f "$activate_file"; emit_result false config_write_failed; return 1; }
@@ -1021,7 +1065,6 @@ config_apply() {
fi
candidate_hash=$(sha256sum "$activate_file" | awk '{print $1}')
old_hash=$(sha256sum /etc/honk/config.dae 2>/dev/null | awk '{print $1}')
old_mode=$(uci -q get honk.main.config_mode || echo managed)
old_running=0
old_pid=$(honk_lifecycle_managed_pid 2>/dev/null)
if [ -n "$old_pid" ]; then
@@ -1031,42 +1074,29 @@ config_apply() {
emit_result false unmanaged_core_running
return 1
fi
backup_dir=$(mktemp -d /etc/honk/.manual-apply-backup.XXXXXX) || { rm -f "$activate_file"; emit_result false config_write_failed; return 1; }
backup_dir=$(mktemp -d /etc/honk/.config-apply-backup.XXXXXX) || { rm -f "$activate_file"; emit_result false config_write_failed; return 1; }
chmod 0700 "$backup_dir" || { rm -rf "$backup_dir"; rm -f "$activate_file"; emit_result false config_write_failed; return 1; }
cp -p /etc/honk/config.dae "$backup_dir/config.dae" && cp -p /etc/config/honk "$backup_dir/honk.uci" || {
rm -rf "$backup_dir" "$activate_file"
emit_result false config_write_failed
return 1
}
managed_snapshot=/etc/honk/managed-config.dae
if [ "$old_mode" = managed ] && { [ -e "$managed_snapshot" ] || [ -L "$managed_snapshot" ]; }; then
[ -f "$managed_snapshot" ] && [ ! -L "$managed_snapshot" ] || { rm -rf "$backup_dir" "$activate_file"; emit_result false managed_snapshot_invalid; return 1; }
fi
if [ "$old_mode" = managed ]; then
managed_snapshot_tmp=$(mktemp /etc/honk/.managed-config.XXXXXX) || { rm -rf "$backup_dir" "$activate_file"; emit_result false config_write_failed; return 1; }
cp -p "$backup_dir/config.dae" "$managed_snapshot_tmp" && chmod 0600 "$managed_snapshot_tmp" && mv -f "$managed_snapshot_tmp" "$managed_snapshot" || {
rm -f "$managed_snapshot_tmp"
rm -rf "$backup_dir" "$activate_file"
emit_result false config_write_failed
return 1
}
fi
if [ "$old_running" = 1 ] && ! honk_lifecycle_stop 30; then
rm -f "$activate_file"
emit_result false service_stop_unconfirmed_snapshot_retained
emit_result false service_stop_unconfirmed
return 1
fi
if [ "$(sha256sum /etc/honk/config.dae 2>/dev/null | awk '{print $1}')" != "$old_hash" ]; then
if [ "$old_running" = 1 ]; then honk_lifecycle_start 20 || { emit_result false config_changed_concurrently_restart_failed_snapshot_retained; return 1; }; fi
if [ "$old_running" = 1 ]; then honk_lifecycle_start 20 || { emit_result false config_changed_concurrently_restart_failed; return 1; }; fi
rm -rf "$backup_dir" "$activate_file"
emit_result false config_changed_concurrently
return 1
fi
if ! mv -f "$activate_file" /etc/honk/config.dae; then
emit_result false config_write_failed_snapshot_retained
emit_result false config_write_failed
return 1
fi
if ! uci set honk.main.config_mode=manual || ! uci set honk.main.enabled=1 || ! uci set honk.main.initialized=1 || ! uci commit honk || ! chmod 0600 /etc/config/honk; then
if ! uci set honk.main.enabled=1 || ! uci set honk.main.initialized=1 || ! uci commit honk || ! chmod 0600 /etc/config/honk; then
apply_failed=1
else
apply_failed=0
@@ -1079,7 +1109,7 @@ config_apply() {
[ "$apply_failed" = 1 ] || honk_lifecycle_stop 30 >/dev/null 2>&1 || :
rollback_ok=1
if [ "$(sha256sum /etc/honk/config.dae 2>/dev/null | awk '{print $1}')" = "$candidate_hash" ]; then
rollback_tmp=$(mktemp /etc/honk/.manual-rollback.XXXXXX) || rollback_ok=0
rollback_tmp=$(mktemp /etc/honk/.config-rollback.XXXXXX) || rollback_ok=0
[ "$rollback_ok" = 0 ] || { cp -p "$backup_dir/config.dae" "$rollback_tmp" && mv -f "$rollback_tmp" /etc/honk/config.dae || rollback_ok=0; }
else
rollback_ok=0
@@ -1094,62 +1124,15 @@ config_apply() {
rm -rf "$backup_dir"
emit_result false config_apply_failed_rolled_back
else
emit_result false config_apply_rollback_failed_snapshot_retained
emit_result false config_apply_rollback_failed
fi
return 1
}
config_use_managed() {
begin_mutation
current_mode=$(uci -q get honk.main.config_mode 2>/dev/null)
[ "$current_mode" = manual ] || { emit_result false manual_mode_required; return 1; }
managed_snapshot=/etc/honk/managed-config.dae
[ -f "$managed_snapshot" ] && [ ! -L "$managed_snapshot" ] || { emit_result false managed_snapshot_missing; return 1; }
activate_dir=$(mktemp -d /etc/honk/.managed-activate.XXXXXX) || { emit_result false config_write_failed; return 1; }
activate_file=/etc/honk/.managed-config-${activate_dir##*.managed-activate.}.dae
(set -C; cp "$managed_snapshot" "$activate_file") || { rm -rf "$activate_dir"; emit_result false config_write_failed; return 1; }
rmdir "$activate_dir" || { rm -rf "$activate_dir"; rm -f "$activate_file"; emit_result false config_write_failed; return 1; }
chmod 0600 "$activate_file" || { rm -f "$activate_file"; emit_result false config_write_failed; return 1; }
config_candidate=$activate_file
if ! validate_config_candidate; then rm -f "$activate_file"; [ -n "$config_validation" ] && printf '%s\n' "$config_validation" || emit_result false config_validation_unavailable; return 1; fi
candidate_hash=$(sha256sum "$activate_file" | awk '{print $1}')
old_hash=$(sha256sum /etc/honk/config.dae 2>/dev/null | awk '{print $1}')
old_running=0
old_pid=$(honk_lifecycle_managed_pid 2>/dev/null)
if [ -n "$old_pid" ]; then old_running=1; elif pidof honk-core >/dev/null 2>&1; then rm -f "$activate_file"; emit_result false unmanaged_core_running; return 1; fi
backup_dir=$(mktemp -d /etc/honk/.managed-switch-backup.XXXXXX) || { rm -f "$activate_file"; emit_result false config_write_failed; return 1; }
chmod 0700 "$backup_dir" || { rm -rf "$backup_dir" "$activate_file"; emit_result false config_write_failed; return 1; }
cp -p /etc/honk/config.dae "$backup_dir/config.dae" && cp -p /etc/config/honk "$backup_dir/honk.uci" || { rm -rf "$backup_dir" "$activate_file"; emit_result false config_write_failed; return 1; }
if [ "$old_running" = 1 ] && ! honk_lifecycle_stop 30; then rm -f "$activate_file"; emit_result false service_stop_unconfirmed_snapshot_retained; return 1; fi
if [ "$(sha256sum /etc/honk/config.dae 2>/dev/null | awk '{print $1}')" != "$old_hash" ]; then
if [ "$old_running" = 1 ]; then honk_lifecycle_start 20 || { emit_result false config_changed_concurrently_restart_failed_snapshot_retained; return 1; }; fi
rm -rf "$backup_dir" "$activate_file"
emit_result false config_changed_concurrently
return 1
fi
if ! mv -f "$activate_file" /etc/honk/config.dae || ! uci set honk.main.config_mode=managed || ! uci commit honk || ! chmod 0600 /etc/config/honk; then
switch_failed=1
else
switch_failed=0
fi
if [ "$switch_failed" = 0 ] && { [ "$old_running" = 0 ] || honk_lifecycle_start 45; }; then rm -rf "$backup_dir"; emit_result true managed_mode_restored; return 0; fi
[ "$switch_failed" = 1 ] || honk_lifecycle_stop 30 >/dev/null 2>&1 || :
rollback_ok=1
rollback_tmp=$(mktemp /etc/honk/.managed-rollback.XXXXXX) || rollback_ok=0
[ "$rollback_ok" = 0 ] || { cp -p "$backup_dir/config.dae" "$rollback_tmp" && mv -f "$rollback_tmp" /etc/honk/config.dae || rollback_ok=0; }
uci_tmp=$(mktemp /etc/config/.honk-rollback.XXXXXX) || rollback_ok=0
[ "$rollback_ok" = 0 ] || { cp -p "$backup_dir/honk.uci" "$uci_tmp" && mv -f "$uci_tmp" /etc/config/honk || rollback_ok=0; }
[ "$rollback_ok" = 0 ] || [ "$(sha256sum /etc/config/honk 2>/dev/null | awk '{print $1}')" = "$(sha256sum "$backup_dir/honk.uci" | awk '{print $1}')" ] || rollback_ok=0
uci revert honk 2>/dev/null || :
if [ "$rollback_ok" = 1 ] && [ "$old_running" = 1 ]; then honk_lifecycle_start 10 || rollback_ok=0; fi
rm -f "$rollback_tmp" "$uci_tmp"
if [ "$rollback_ok" = 1 ]; then rm -rf "$backup_dir"; emit_result false managed_switch_failed_rolled_back; else emit_result false managed_switch_rollback_failed_snapshot_retained; fi
return 1
}
case "$1" in
list)
echo '{"status":{},"check":{},"initialize":{"username":"String","password":"String"},"start":{},"stop":{},"restart":{},"settings":{"lan_network":"String","listen_port":"Integer","boot_enabled":"Boolean","clash_api_enabled":"Boolean","clash_api_port":"Integer","clash_api_secret":"String"},"add_subscription":{"name":"String","url":"String"},"probe_subscription":{"url":"String"},"reset_admin":{},"add_nodes":{"links":"String"},"import_dae":{},"config_read":{},"config_draft_read":{},"lan_candidates":{},"config_validate":{"content":"String"},"config_save":{"content":"String"},"config_apply":{},"config_use_managed":{},"repair":{},"backup":{},"restore":{},"restore_prepare":{},"reset":{},"job_status":{"job_id":"String"},"logs":{"source":"String","level":"String","limit":"Integer"},"update_check":{},"update_apply":{}}'
echo '{"status":{},"check":{},"initialize":{"username":"String","password":"String"},"start":{},"stop":{},"restart":{},"settings":{"lan_network":"String","listen_port":"Integer","boot_enabled":"Boolean","clash_api_enabled":"Boolean","clash_api_port":"Integer","clash_api_secret":"String"},"add_subscription":{"name":"String","url":"String"},"probe_subscription":{"url":"String"},"reset_admin":{},"add_nodes":{"links":"String"},"import_dae":{},"config_read":{},"config_draft_read":{},"lan_candidates":{},"config_validate":{"content":"String"},"config_save":{"content":"String"},"config_apply":{},"repair":{},"backup":{},"restore":{},"restore_prepare":{},"reset":{},"job_status":{"job_id":"String"},"logs":{"source":"String","level":"String","limit":"Integer"},"update_check":{},"update_apply":{}}'
;;
call)
case "$2" in
@@ -1198,9 +1181,6 @@ list)
config_apply)
config_apply
;;
config_use_managed)
config_use_managed
;;
repair)
exec "$CONTROL" repair
;;
@@ -26,6 +26,34 @@ esac
LOCK="/tmp/luci-app-honk.pkg-${PKG}.lock"
LOG="/tmp/luci-app-honk.pkg-${PKG}.log"
. /usr/share/honk/lifecycle.sh 2>/dev/null || :
# 运行状态与恢复交给 lifecycle:它已经处理 dae0/BPF 排空、崩溃重启和就绪判定。
service_running() {
honk_lifecycle_managed_pid >/dev/null 2>&1
}
restore_service_state() {
if [ "$was_running" = 1 ]; then
echo "--- 升级前为运行状态,重启加载新内核 ---"
if honk_lifecycle_restart 30 >/dev/null 2>&1; then
echo "result: 服务已恢复运行"
return 0
fi
echo "result: 服务重启失败"
return 1
fi
if service_running; then
echo "--- 升级前为停止状态,停止服务 ---"
honk_lifecycle_stop 30 >/dev/null 2>&1 || {
echo "result: 服务停止或资源排空失败"
return 1
}
fi
echo "result: 服务保持停止"
return 0
}
if [ -f "$LOCK" ]; then
mtime=$(date -r "$LOCK" +%s 2>/dev/null || echo 0)
age=$(( $(date +%s) - mtime ))
@@ -46,6 +74,9 @@ fi
trap 'rm -f "$LOCK"; [ "${0#/tmp/.honk-upd-}" != "$0" ] && rm -f "$0"' EXIT INT TERM
echo "$(date '+%F %T') begin upgrade: $PKG"
was_running=0
service_running && was_running=1
echo "升级前服务状态: $([ "$was_running" = 1 ] && echo 运行中 || echo 已停止)"
if command -v apk >/dev/null 2>&1; then
(
@@ -85,11 +116,20 @@ fi
exit 3
fi
if [ "$rc" = 0 ]; then echo "$(date '+%F %T') ✓ 完成"; else echo "$(date '+%F %T') ✗ 失败 (rc=$rc)"; fi
if [ "$PKG" = "luci-app-honk" ] && [ "$rc" = "0" ]; then
echo "reloading rpcd to pick up new ACL"
service_rc=0
if [ "$rc" = "0" ]; then
echo "reloading rpcd to pick up new scripts"
/etc/init.d/rpcd reload 2>&1
restore_service_state || service_rc=$?
fi
# 前端轮询见到 ✓/✗ 即停止,结论只在这里输出一次。
if [ "$rc" != "0" ]; then
echo "$(date '+%F %T') ✗ 升级失败 (rc=$rc)"
elif [ "$service_rc" != "0" ]; then
echo "$(date '+%F %T') ✗ 软件包升级成功,但服务启动失败,请查看日志后手动启动"
else
echo "$(date '+%F %T') ✓ 完成"
fi
) </dev/null >/dev/null 2>&1 &
@@ -81,8 +81,7 @@
"update_apply",
"config_read",
"config_draft_read",
"config_apply",
"config_use_managed"
"config_apply"
]
},
"file": {
+2 -2
View File
@@ -5,8 +5,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-smartdns
PKG_VERSION:=1.2026.48.2
PKG_RELEASE:=2
PKG_VERSION:=1.2026.48.3
PKG_RELEASE:=1
PKG_LICENSE:=GPL-3.0-or-later
PKG_MAINTAINER:=Nick Peng <pymumu@gmail.com>
File diff suppressed because it is too large. Load diff
+5
View File
@@ -161,3 +161,8 @@ while IFS= read -r f; do
done < "$LIST"
rm -f "$MAP.ord"
echo "mangle-tokens: seam renamed in $touched file(s)"
# A name the map missed: one built at run time ('--fs-' + k), which the sheet no longer reads after
# the rename (issue #89), or one no rule declares. Prose may still say `--fs-*`.
if left=$(tr '\n' '\0' < "$LIST" | xargs -0 grep -HnE -- '--fs-([A-Za-z0-9_$"'"'"'`]|$)'); then
printf 'mangle-tokens: an --fs- name survived the rename:\n%s\n' "$left" >&2; exit 1
fi
+14 -8
View File
@@ -45,8 +45,8 @@
--fs-glass-bar-panel: color-mix(in srgb, var(--fs-panel) var(--fs-bar-opacity), transparent);
--fs-glass-bar-inset: var(--fs-glass-inset-on);
}
/* Flat: everything keeps its own colour at the same opacity, layers compound, and only what the
* page scrolls under is blurred */
/* Flat: everything keeps its own colour at the same opacity, layers compound, and nothing is
* blurred but the sticky table cells; the menu follows the cards' material, Flat bar included */
:root[data-wallpaper][data-material="flat"]:is([data-glass], [data-glass-bar]) {
--fs-glass-inset-on: var(--fs-glass-inset);
--fs-glass-track-on: var(--fs-glass-track);
@@ -190,18 +190,24 @@
:root[data-wallpaper][data-glass-bar]:not([data-material="flat"]) .fs-sidebar ul.nav ul {
backdrop-filter: blur(var(--fs-glass-blur)) saturate(var(--fs-glass-sat));
}
/* what covers the page's text is blurred in Flat too, plainly and at the base radius, as Flat has
* no Blur slider: the bar the page scrolls under, and every popup */
:root[data-wallpaper][data-glass-bar][data-material="flat"]:is([data-layout="top"], [data-narrow]) .fs-sidebar::before,
:root[data-wallpaper][data-glass-bar][data-material="flat"] .fs-sidebar ul.nav ul {
backdrop-filter: blur(var(--fs-glass-blur-base));
}
/* the rail's name bubbles are labels over the page, not menus: solid */
:root[data-wallpaper][data-glass-bar] .fs-sidebar ul.nav > li > a::after {
--fs-panel: var(--fs-glass-o-panel);
--fs-border: var(--fs-glass-o-border);
--fs-hairline: 1px solid var(--fs-glass-o-border);
}
/* Acrylic's grain, lit edge and shadow on the menu as on the cards: without them the bar read as
* Frosted with a stronger blur. The bar paints on its layer, the vertical sidebar on itself. */
:root[data-wallpaper][data-glass-bar][data-material="acrylic"]:is([data-layout="top"], [data-narrow]) .fs-sidebar::before,
:root[data-wallpaper][data-glass-bar][data-material="acrylic"][data-sb] .fs-sidebar {
background-image: var(--fs-glass-noise), linear-gradient(160deg, color-mix(in srgb, #fff 7%, transparent), transparent 45%);
box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 10%, transparent), 0 14px 32px -18px rgb(0 0 0 / .55);
}
:root[data-wallpaper][data-glass-bar][data-material="acrylic"]:not([data-darkmode="true"]):is([data-layout="top"], [data-narrow]) .fs-sidebar::before,
:root[data-wallpaper][data-glass-bar][data-material="acrylic"]:not([data-darkmode="true"])[data-sb] .fs-sidebar {
background-image: var(--fs-glass-noise), linear-gradient(160deg, color-mix(in srgb, #fff 45%, transparent), transparent 50%);
box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 70%, transparent), 0 14px 32px -20px rgb(0 0 0 / .3);
}
@media (min-width: 521px) {
/* the rail is static: the layer needs it as its containing block, and no z-index, so the
* flyouts still rise over the page on their own (20-shell.css, issue #7) */
@@ -334,9 +334,10 @@
flex-wrap: wrap; gap: var(--fs-space-2); padding-top: var(--fs-space-2);
}
/* LuCI groups buttons in ONE inner <div>, so this flex-wrap never fires and .fs-main clipped
* the tail button. Copy in theme/65-dropdown.css. */
* the tail button. No grow: a plain button sized to its label, like the ComboButton below,
* where a stretched one stood a card wide beside it. Copy in theme/65-dropdown.css. */
.table.fs-stacked .td.cbi-section-actions > :not(.cbi-dropdown) {
flex: 1 1 auto; display: flex; flex-wrap: wrap; gap: var(--fs-space-2);
flex: 0 1 auto; display: flex; flex-wrap: wrap; gap: var(--fs-space-2);
}
/* width: 0 — the label's nowrap min-content (288px for a Russian item at 360px) sizes the table-row-group
* past the card (td right 442 vs 315); basis `content` still sizes the item to its label (it was 266px, a whole card, with `1 1 auto`). Copy in theme/65-dropdown.css. */
@@ -357,9 +357,10 @@
}
.table.cbi-section-table .td.cbi-section-actions::before { content: none; }
/* LuCI groups the row's buttons in ONE inner div, so the td's own wrap never fires — the
* div is a single flex item. Copy in theme/30-tables.css; same trap on Startup. */
* div is a single flex item. No grow: sized to its label like the ComboButton below.
* Copy in theme/30-tables.css; same trap on Startup. */
.table.cbi-section-table .td.cbi-section-actions > :not(.cbi-dropdown) {
flex: 1 1 auto; display: flex; flex-wrap: wrap; gap: var(--fs-space-2);
flex: 0 1 auto; display: flex; flex-wrap: wrap; gap: var(--fs-space-2);
}
/* width: 0 — the label's nowrap min-content (288px for a Russian item at 360px) sizes the table-row-group
* past the card (td right 442 vs 315); basis `content` still sizes the item to its label (it was 266px, a whole card, with `1 1 auto`). Copy in theme/30-tables.css. */
@@ -1,14 +1,17 @@
@layer theme {
/* honoured globally, every transition here is decoration. This file is the one place for flags against the theme's own rules,
* and there are two: this block (a layer cannot reach base's own transitions) and the forced-colors focus outline below
* .01ms not 0s: a zero duration never fires transitionend, LuCI's teardown waits for one. */
* and there are two: this block (a layer cannot reach base's own transitions) and the forced-colors focus outline below.
* Transitions 0s: a nonzero duration on every element animates `transition-property: all`, the initial value, so WebKit
* stepped an inherited font-size down the tree for ~200ms and a floor measured mid-way kept the old height. */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: .01ms !important;
animation-iteration-count: 1 !important;
transition-duration: .01ms !important;
transition-duration: 0s !important;
scroll-behavior: auto !important;
}
/* .01ms, not 0s: a zero duration never fires transitionend, and ui.Dropdown focuses the open list on it */
.cbi-dropdown[open] > ul.dropdown { transition-duration: .01ms !important; }
}
/* re-states the tokens so every rule reading them follows — docs/css.md "A11y: prefers-contrast token shifts, and the numbers behind them" has why each number */
@@ -263,11 +263,13 @@
const mq = (q) => window.matchMedia(q).matches;
const solid = mq('(prefers-contrast: more)') || mq('(forced-colors: active)');
const rt = mq('(prefers-reduced-transparency: reduce)');
const glass = (k, a) => {
if (num(k, 0, 100, 100, '--fs-' + k, '%') && !solid && !(rt && lsGet(k) === null)) attr(a, '');
/* `p` written out, never built from `k`: mangle-tokens.sh renames only whole literals, so a
built name set a property the sheet no longer reads (issue #89) */
const glass = (k, p, a) => {
if (num(k, 0, 100, 100, p, '%') && !solid && !(rt && lsGet(k) === null)) attr(a, '');
};
glass('card-opacity', 'glass');
glass('bar-opacity', 'glass-bar');
glass('card-opacity', '--fs-card-opacity', 'glass');
glass('bar-opacity', '--fs-bar-opacity', 'glass-bar');
list('material', [ 'flat', 'acrylic' ]);
/* Frosted's and Acrylic's blur, same shape as the tint strength */
num('blur', 0, 200, 100, '--fs-blur');
+12 -12
View File
@@ -5,9 +5,9 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=upx-static
PKG_VERSION:=5.2.1
PKG_VERSION:=5.3.0
PKG_RELEASE:=1
PKG_SOURCE_VERSION:=v5.2.1
PKG_SOURCE_VERSION:=v5.3.0
# Map OpenWrt $(ARCH) to UPX pre-built binary arch name.
# NOTE: OpenWrt uses "aarch64", UPX tarball uses "arm64".
@@ -39,25 +39,25 @@ PKG_SOURCE_URL:=https://github.com/upx/upx/releases/download/$(PKG_SOURCE_VERSIO
# AUTO-GENERATED HASHES BEGIN
ifeq ($(UPX_ARCH),amd64)
PKG_HASH:=402162aad30af47e60dbd767fb2e64ca394ace9727ba1f40283641f1d1b91657
PKG_HASH:=79cea8a50b6fa102d10c7b79a419cd77dead3cc2d78661de2dc5e5815dc505c1
else ifeq ($(UPX_ARCH),i386)
PKG_HASH:=44b505d881337ef17ad03f03fd80f2ecdebc3a36077e1dd260ae1b359c713ba5
PKG_HASH:=d5ff15dfd7649d08970ffa86c9bae656b9fe3831285dd62db0ba7eabc6a3eae9
else ifeq ($(UPX_ARCH),arm64)
PKG_HASH:=a72d112c5970a904a31da0b9c84f919bc16b9a311787c12245508544a78c7d36
PKG_HASH:=2a4afa860ac7aa6968f88df7841823746e5d00ba56affdf9342c2f5254fb776f
else ifeq ($(UPX_ARCH),arm)
PKG_HASH:=d74c0d8bfdd7aad9114470efaa14fa889158f3dbfbc7128ba76c403aee545ef8
PKG_HASH:=541c8fc5e0ab43ae57273374215e6efe023aa43c2d5cf1e52a316a94a367bee5
else ifeq ($(UPX_ARCH),armeb)
PKG_HASH:=31ac41b612c4c1166dc7a7c194e2ad8fadcf9fdf25aa4bd3c14799823b9e5f9e
PKG_HASH:=770e62b1a60b7fd281c34556e321566827f57a5eb77cd4976280b92df565b6f0
else ifeq ($(UPX_ARCH),mipsel)
PKG_HASH:=84fc6c0c33ed668c099d4f82d72ac7fd4070b19797ebb990f999278267b94e0d
PKG_HASH:=a4e6dc515aede12aeec7e8169a5f4c96da41cc58b5c834d8b6b7bddd015bb59d
else ifeq ($(UPX_ARCH),mips)
PKG_HASH:=d2cd981ff725a31f9f596f80fcc5c1682e648a59231e220a3967c661c6d1fafb
PKG_HASH:=84c3203cf70d17ced90b777bf843e412ebc7a586cb4ee1e4c9a4801227131bc6
else ifeq ($(UPX_ARCH),powerpc)
PKG_HASH:=5f89a57f6e76c5b16016330ee5a2ca87f9bfbdcca5420bc042a1fde1798818ed
PKG_HASH:=2996e46af638fc20aa019f6c950ac91a829af0dae33fbfeca62012a310b995bb
else ifeq ($(UPX_ARCH),powerpc64le)
PKG_HASH:=e914046633567c3255a6440bae4d6c13d9f90d458445b47b8474b048ca4072b7
PKG_HASH:=2102aa6c2e9fcdb147b27404bf87fc8f545327982ec9685efa5418be55be3480
else ifeq ($(UPX_ARCH),riscv64)
PKG_HASH:=a8663b4cb6523104a8a807362d0da6f5d6d55f6083152f78fa27fa9c05443c17
PKG_HASH:=5a0663f369078cb069ac50252c9c8ca65a146307b7e6078c6f54f3bd146441ca
else
$(error Unsupported UPX_ARCH '$(UPX_ARCH)' for upx-static)
endif
+3 -3
View File
@@ -5,12 +5,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=v2rayA
PKG_VERSION:=2.5.10
PKG_VERSION:=2.5.13
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/v2rayA/v2rayA/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=046ff46ca35b102334c0a74c303867d1724d628ca45c258816b1bcaa9dfc6971
PKG_HASH:=20f03a9143231e2f04b74cdcb7125c6a44b450a4a28fac92b3e64c13916ec4d9
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)/service
PKG_LICENSE:=AGPL-3.0-only
@@ -60,7 +60,7 @@ define Download/v2raya-web
URL:=https://github.com/v2rayA/v2rayA/releases/download/v$(PKG_VERSION)/
URL_FILE:=web.tar.gz
FILE:=$(WEB_FILE)
HASH:=c25071a27f7b98eebb1a80d44b8d6a65d7533cf4cdff475ec86343d7deca5c86
HASH:=76774f195ee74604fbc43a48fe74e5f3363e81ec68af02d1c26feeeacc0f63ae
endef
define Build/Prepare