diff --git a/clashoo/files/usr/share/clashoo/nftables/geoip6_cn.nft b/clashoo/files/usr/share/clashoo/nftables/geoip6_cn.nft index 5cd2612d..daf98391 100644 --- a/clashoo/files/usr/share/clashoo/nftables/geoip6_cn.nft +++ b/clashoo/files/usr/share/clashoo/nftables/geoip6_cn.nft @@ -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, diff --git a/clashoo/files/usr/share/clashoo/nftables/geoip_cn.nft b/clashoo/files/usr/share/clashoo/nftables/geoip_cn.nft index 26ac0cf7..37b39911 100644 --- a/clashoo/files/usr/share/clashoo/nftables/geoip_cn.nft +++ b/clashoo/files/usr/share/clashoo/nftables/geoip_cn.nft @@ -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, diff --git a/clashoo/files/usr/share/clashoo/ruleset/cn-ip.srs b/clashoo/files/usr/share/clashoo/ruleset/cn-ip.srs index ef9118fa..03e9ee7f 100644 Binary files a/clashoo/files/usr/share/clashoo/ruleset/cn-ip.srs and b/clashoo/files/usr/share/clashoo/ruleset/cn-ip.srs differ diff --git a/clashoo/files/usr/share/clashoo/ruleset/cn.mrs b/clashoo/files/usr/share/clashoo/ruleset/cn.mrs index 610f2d2d..f57532f0 100644 Binary files a/clashoo/files/usr/share/clashoo/ruleset/cn.mrs and b/clashoo/files/usr/share/clashoo/ruleset/cn.mrs differ diff --git a/clashoo/files/usr/share/clashoo/ruleset/geolocation-!cn.srs b/clashoo/files/usr/share/clashoo/ruleset/geolocation-!cn.srs index 6f26b898..b1101499 100644 Binary files a/clashoo/files/usr/share/clashoo/ruleset/geolocation-!cn.srs and b/clashoo/files/usr/share/clashoo/ruleset/geolocation-!cn.srs differ diff --git a/clashoo/files/usr/share/clashoo/ruleset/geosite-cn.srs b/clashoo/files/usr/share/clashoo/ruleset/geosite-cn.srs index 216609fe..9e4d515d 100644 Binary files a/clashoo/files/usr/share/clashoo/ruleset/geosite-cn.srs and b/clashoo/files/usr/share/clashoo/ruleset/geosite-cn.srs differ diff --git a/honk/Makefile b/honk/Makefile index 462f634b..ef5ac888 100644 --- a/honk/Makefile +++ b/honk/Makefile @@ -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. diff --git a/honk/files/90-honk-boot b/honk/files/90-honk-boot index 2930b4e1..3ce34cd7 100644 --- a/honk/files/90-honk-boot +++ b/honk/files/90-honk-boot @@ -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 diff --git a/honk/files/control.sh b/honk/files/control.sh index 56c5a4bf..e166d089 100755 --- a/honk/files/control.sh +++ b/honk/files/control.sh @@ -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; } diff --git a/honk/files/honk.config b/honk/files/honk.config index 2bc9c858..ee3bbe47 100644 --- a/honk/files/honk.config +++ b/honk/files/honk.config @@ -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' diff --git a/honk/files/honk.init b/honk/files/honk.init index 073fedcc..3b7e43b4 100755 --- a/honk/files/honk.init +++ b/honk/files/honk.init @@ -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 diff --git a/honk/files/lifecycle.sh b/honk/files/lifecycle.sh index 5ed71cb0..d123a329 100644 --- a/honk/files/lifecycle.sh +++ b/honk/files/lifecycle.sh @@ -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 diff --git a/honk/files/maintenance.sh b/honk/files/maintenance.sh index e5f06e92..35c1950d 100755 --- a/honk/files/maintenance.sh +++ b/honk/files/maintenance.sh @@ -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; } diff --git a/luci-app-eqosplus/.github/workflows/main.yml b/luci-app-eqosplus/.github/workflows/main.yml deleted file mode 100644 index 3258ec29..00000000 --- a/luci-app-eqosplus/.github/workflows/main.yml +++ /dev/null @@ -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" diff --git a/luci-app-eqosplus/LICENSE b/luci-app-eqosplus/LICENSE deleted file mode 100644 index 574c7ac3..00000000 --- a/luci-app-eqosplus/LICENSE +++ /dev/null @@ -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. diff --git a/luci-app-eqosplus/Makefile b/luci-app-eqosplus/Makefile deleted file mode 100644 index 91e501b7..00000000 --- a/luci-app-eqosplus/Makefile +++ /dev/null @@ -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 - -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 - diff --git a/luci-app-eqosplus/README.md b/luci-app-eqosplus/README.md deleted file mode 100644 index d2707f57..00000000 --- a/luci-app-eqosplus/README.md +++ /dev/null @@ -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) - -

-
luci-app-eqosplus
-

- -

-OpenWrt -Chrome -Safari -Firefox - GitHub release -Downloads -

- -[中文] | [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 - -| 图飞了 | 图飞了 | -| :-----------------: | :-------------: | -|![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/支付宝.png) | ![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/微信.png) | - - - no - - -![hello](https://visitor-badge-deno.deno.dev/sirpdboy.sirpdboy.svg) [![](https://img.shields.io/badge/TGGroup-ClickJoin-FFFFFF.svg)](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ) diff --git a/luci-app-eqosplus/README_CN.md b/luci-app-eqosplus/README_CN.md deleted file mode 100644 index 8da16627..00000000 --- a/luci-app-eqosplus/README_CN.md +++ /dev/null @@ -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) - -

-
luci-app-eqosplus
-

- -

- - - - -

- -[中文] | [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) - -| 图飞了 | 图飞了 | -| :-----------------: | :-------------: | -|![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/支付宝.png) | ![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/微信.png) | - - - 图飞了 - - -![](https://visitor-badge-deno.deno.dev/sirpdboy.sirpdboy.svg) [![](https://img.shields.io/badge/TG群-点击加入-FFFFFF.svg)](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ) - diff --git a/luci-app-eqosplus/doc/eqosplus0.png b/luci-app-eqosplus/doc/eqosplus0.png deleted file mode 100644 index 978f4abd..00000000 Binary files a/luci-app-eqosplus/doc/eqosplus0.png and /dev/null differ diff --git a/luci-app-eqosplus/doc/eqosplus1.png b/luci-app-eqosplus/doc/eqosplus1.png deleted file mode 100644 index c603fcda..00000000 Binary files a/luci-app-eqosplus/doc/eqosplus1.png and /dev/null differ diff --git a/luci-app-eqosplus/luasrc/controller/eqosplus.lua b/luci-app-eqosplus/luasrc/controller/eqosplus.lua deleted file mode 100644 index cbe01b70..00000000 --- a/luci-app-eqosplus/luasrc/controller/eqosplus.lua +++ /dev/null @@ -1,19 +0,0 @@ -module("luci.controller.eqosplus", package.seeall) --- Copyright 2022-2023 sirpdboy -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 diff --git a/luci-app-eqosplus/luasrc/model/cbi/eqosplus.lua b/luci-app-eqosplus/luasrc/model/cbi/eqosplus.lua deleted file mode 100644 index 32c3433c..00000000 --- a/luci-app-eqosplus/luasrc/model/cbi/eqosplus.lua +++ /dev/null @@ -1,190 +0,0 @@ --- Copyright 2022-2023 sirpdboy --- 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 diff --git a/luci-app-eqosplus/luasrc/view/eqosplus/eqosplus.htm b/luci-app-eqosplus/luasrc/view/eqosplus/eqosplus.htm deleted file mode 100644 index 4445143f..00000000 --- a/luci-app-eqosplus/luasrc/view/eqosplus/eqosplus.htm +++ /dev/null @@ -1,3 +0,0 @@ -<%+cbi/valueheader%> -<%=pcdata(self:cfgvalue(section) or self.default or "")%> -<%+cbi/valuefooter%> \ No newline at end of file diff --git a/luci-app-eqosplus/luasrc/view/eqosplus/index.htm b/luci-app-eqosplus/luasrc/view/eqosplus/index.htm deleted file mode 100644 index c4b8a072..00000000 --- a/luci-app-eqosplus/luasrc/view/eqosplus/index.htm +++ /dev/null @@ -1,12 +0,0 @@ -<% include("cbi/map") %> - \ No newline at end of file diff --git a/luci-app-eqosplus/po/templates/eqosplus.pot b/luci-app-eqosplus/po/templates/eqosplus.pot deleted file mode 100644 index 151cee8d..00000000 --- a/luci-app-eqosplus/po/templates/eqosplus.pot +++ /dev/null @@ -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 "" - diff --git a/luci-app-eqosplus/po/zh-cn/eqosplus.po b/luci-app-eqosplus/po/zh-cn/eqosplus.po deleted file mode 100644 index 0fa56393..00000000 --- a/luci-app-eqosplus/po/zh-cn/eqosplus.po +++ /dev/null @@ -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限制用户上传/下载的网速。速度单位为 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 "自动设置" - diff --git a/luci-app-eqosplus/po/zh_Hans/eqosplus.po b/luci-app-eqosplus/po/zh_Hans/eqosplus.po deleted file mode 100644 index 840559fc..00000000 --- a/luci-app-eqosplus/po/zh_Hans/eqosplus.po +++ /dev/null @@ -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 "自动设置" - diff --git a/luci-app-eqosplus/root/etc/config/eqosplus b/luci-app-eqosplus/root/etc/config/eqosplus deleted file mode 100644 index 722434ae..00000000 --- a/luci-app-eqosplus/root/etc/config/eqosplus +++ /dev/null @@ -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' - diff --git a/luci-app-eqosplus/root/etc/hotplug.d/iface/10-eqosplus b/luci-app-eqosplus/root/etc/hotplug.d/iface/10-eqosplus deleted file mode 100644 index 91059ec8..00000000 --- a/luci-app-eqosplus/root/etc/hotplug.d/iface/10-eqosplus +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -[ "$ACTION" = "ifup" ] && [ "$INTERFACE" = "wan" ] && { - sleep 5 - /etc/init.d/eqosplus restart -} \ No newline at end of file diff --git a/luci-app-eqosplus/root/etc/init.d/eqosplus b/luci-app-eqosplus/root/etc/init.d/eqosplus deleted file mode 100644 index f74b5fdd..00000000 --- a/luci-app-eqosplus/root/etc/init.d/eqosplus +++ /dev/null @@ -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 -} - diff --git a/luci-app-eqosplus/root/etc/uci-defaults/luci-eqosplus b/luci-app-eqosplus/root/etc/uci-defaults/luci-eqosplus deleted file mode 100644 index f22cd0c6..00000000 --- a/luci-app-eqosplus/root/etc/uci-defaults/luci-eqosplus +++ /dev/null @@ -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 diff --git a/luci-app-eqosplus/root/usr/bin/eqosplus b/luci-app-eqosplus/root/usr/bin/eqosplus deleted file mode 100644 index 721fafa0..00000000 --- a/luci-app-eqosplus/root/usr/bin/eqosplus +++ /dev/null @@ -1,379 +0,0 @@ -#!/bin/bash - -# Copyright (C) 2006 OpenWrt.org -# Copyright 2022-2025 sirpdboy - -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 \ No newline at end of file diff --git a/luci-app-eqosplus/root/usr/bin/eqosplusctrl b/luci-app-eqosplus/root/usr/bin/eqosplusctrl deleted file mode 100644 index 9b5721d6..00000000 --- a/luci-app-eqosplus/root/usr/bin/eqosplusctrl +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/sh - -# Copyright (C) 2006 OpenWrt.org -# Copyright 2022-2025 sirpdboy -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 \ No newline at end of file diff --git a/luci-app-eqosplus/root/usr/share/rpcd/acl.d/luci-app-eqosplus.json b/luci-app-eqosplus/root/usr/share/rpcd/acl.d/luci-app-eqosplus.json deleted file mode 100644 index b879f4de..00000000 --- a/luci-app-eqosplus/root/usr/share/rpcd/acl.d/luci-app-eqosplus.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "luci-app-eqosplus": { - "description": "Grant UCI access for luci-app-eqosplus", - "read": { - "uci": [ "eqosplus" ] - }, - "write": { - "uci": [ "eqosplus" ] - } - } -} diff --git a/luci-app-eqosplus/界面.png b/luci-app-eqosplus/界面.png deleted file mode 100644 index d346c698..00000000 Binary files a/luci-app-eqosplus/界面.png and /dev/null differ diff --git a/luci-app-honk/Makefile b/luci-app-honk/Makefile index ba436484..781bfab5 100644 --- a/luci-app-honk/Makefile +++ b/luci-app-honk/Makefile @@ -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 diff --git a/luci-app-honk/htdocs/luci-static/resources/view/honk/configuration.js b/luci-app-honk/htdocs/luci-static/resources/view/honk/configuration.js index 0e5c2938..1811f15b 100644 --- a/luci-app-honk/htdocs/luci-static/resources/view/honk/configuration.js +++ b/luci-app-honk/htdocs/luci-static/resources/view/honk/configuration.js @@ -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; } }); diff --git a/luci-app-honk/htdocs/luci-static/resources/view/honk/honk.css b/luci-app-honk/htdocs/luci-static/resources/view/honk/honk.css index 6c3a2c72..5cccfb2f 100644 --- a/luci-app-honk/htdocs/luci-static/resources/view/honk/honk.css +++ b/luci-app-honk/htdocs/luci-static/resources/view/honk/honk.css @@ -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; + } +} diff --git a/luci-app-honk/htdocs/luci-static/resources/view/honk/overview.js b/luci-app-honk/htdocs/luci-static/resources/view/honk/overview.js index 35aadb41..2dcc5337 100644 --- a/luci-app-honk/htdocs/luci-static/resources/view/honk/overview.js +++ b/luci-app-honk/htdocs/luci-static/resources/view/honk/overview.js @@ -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'))); diff --git a/luci-app-honk/htdocs/luci-static/resources/view/honk/rpc.js b/luci-app-honk/htdocs/luci-static/resources/view/honk/rpc.js index c1255de7..5aaed206 100644 --- a/luci-app-honk/htdocs/luci-static/resources/view/honk/rpc.js +++ b/luci-app-honk/htdocs/luci-static/resources/view/honk/rpc.js @@ -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, diff --git a/luci-app-honk/htdocs/luci-static/resources/view/honk/settings.js b/luci-app-honk/htdocs/luci-static/resources/view/honk/settings.js index 13529f38..f259c43e 100644 --- a/luci-app-honk/htdocs/luci-static/resources/view/honk/settings.js +++ b/luci-app-honk/htdocs/luci-static/resources/view/honk/settings.js @@ -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))); diff --git a/luci-app-honk/po/zh-cn/honk.po b/luci-app-honk/po/zh-cn/honk.po index 4684f63d..ffd10771 100644 --- a/luci-app-honk/po/zh-cn/honk.po +++ b/luci-app-honk/po/zh-cn/honk.po @@ -9,72 +9,435 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Language: zh_CN\n" -msgid "Available after Honk is initialized, running, and its API is ready." -msgstr "Honk 完成初始化、正在运行且 API 就绪后即可使用。" +msgid "%d node(s) imported." +msgstr "已导入 %d 个节点。" + +msgid "%s (code: %s)" +msgstr "%s(错误码:%s)" + +msgid "%s imported and Honk is running." +msgstr "%s 已导入,Honk 正在运行。" + +msgid "/etc/honk/config.dae 不存在或不是普通文件。" +msgstr "/etc/honk/config.dae 不存在或不是普通文件。" + +msgid "A compatible update is available, but a complete verified rollback package set is not available yet." +msgstr "发现兼容更新,但尚无完整且已验证的回滚软件包组。" + +msgid "A compatible update is available." +msgstr "发现兼容更新。" msgid "A previous operation did not finish cleanly." msgstr "上一次操作未能正常结束。" +msgid "A private upload location could not be prepared." +msgstr "无法准备私有上传目录。" + +msgid "A secret is required when enabling Clash API." +msgstr "启用 Clash API 时需要设置密钥。" + +msgid "A secret is required when enabling Clash API. Use 6–128 letters, numbers, dots, underscores or hyphens." +msgstr "启用 Clash API 必须设置密钥,6–128 位字母、数字、点、下划线或连字符。" + +msgid "A secret is set. Leave blank to keep it." +msgstr "已设置密钥,留空则保持原值。" + +msgid "A secret is set. Leave blank to keep it. Use your existing HTTPS reverse proxy for external access." +msgstr "已设置密钥,留空则保持原值;外网访问请使用现有 HTTPS 反向代理。" + +msgid "A share link is invalid or conflicts with an existing node. Review the input and try again." +msgstr "分享链接无效或与现有节点冲突,请检查内容后重试。" + +msgid "A valid HTTP or HTTPS URL is required." +msgstr "请输入有效的 HTTP 或 HTTPS 链接。" + +msgid "API and UI settings" +msgstr "API 与界面设置" + +msgid "Adapted in the editor: %s" +msgstr "已在编辑器里适配:%s" + +msgid "Add nodes" +msgstr "添加节点" + +msgid "Add subscription" +msgstr "添加订阅" + +msgid "Add subscriptions and nodes, or import and edit a complete configuration." +msgstr "添加订阅和节点,或导入、编辑完整配置。" + +msgid "Adding subscription…" +msgstr "正在添加订阅…" + +msgid "Administrator cleared. Open the Overview page and run the first setup again." +msgstr "管理员已清空,请到「概览」页重新完成首次设置。" + +msgid "All levels" +msgstr "所有级别" + +msgid "An old manual draft from a previous version is kept on disk." +msgstr "磁盘上保留了旧版本的手动草稿。" + +msgid "An unapplied draft exists." +msgstr "存在未应用的草稿。" + +msgid "An unmanaged Honk core process is running." +msgstr "检测到非托管的 Honk 核心进程正在运行。" + msgid "Another Honk operation is in progress." msgstr "Honk 正在执行其他操作。" +msgid "Append keeps your rules · validated before applying · rolled back when startup fails" +msgstr "追加不覆盖规则 · 应用前先校验 · 启动失败自动回滚" + +msgid "Apply them in the editor now?" +msgstr "现在就在编辑器里改掉吗?" + +msgid "Applying configuration…" +msgstr "正在应用配置……" + +msgid "Applying the reviewed configuration…" +msgstr "正在应用已审核的配置……" + +msgid "Applying the staged data…" +msgstr "正在应用暂存的数据……" + +msgid "Auto-update" +msgstr "自动更新" + +msgid "Backup created." +msgstr "备份已创建。" + +msgid "Backup downloaded." +msgstr "备份已下载。" + +msgid "Backup failed." +msgstr "备份失败。" + +msgid "Backup restored." +msgstr "备份已恢复。" + +msgid "Cancel" +msgstr "取消" + +msgid "Check configuration" +msgstr "校验配置" + +msgid "Check for updates again to refresh the signed manifest." +msgstr "请重新检查更新,以刷新签名清单。" + +msgid "Checking the configuration…" +msgstr "正在校验配置…" + +msgid "Checking the signed update source…" +msgstr "正在检查签名更新源……" + +msgid "Checking the subscription before saving…" +msgstr "保存前先检查订阅…" + +msgid "Checking the updated configuration…" +msgstr "正在检查更新后的配置……" + +msgid "Checking…" +msgstr "正在检查……" + +msgid "Checks Honk system components and reports detected issues." +msgstr "检查 Honk 系统组件并报告发现的问题。" + +msgid "Choose a .dae file first." +msgstr "请先选择 .dae 文件。" + +msgid "Choose a LAN network and a port from 1024 to 65535." +msgstr "请选择 LAN 网络,并将端口设置为 1024 至 65535。" + +msgid "Choose file" +msgstr "选择文件" + +msgid "Choose the LAN interface" +msgstr "选择 LAN 网卡" + +msgid "Clash API %s" +msgstr "Clash API %s" + +msgid "Clash API port" +msgstr "Clash API 端口" + +msgid "Clash API port must be between 1024 and 65535." +msgstr "Clash API 端口需要在 1024~65535 之间。" + +msgid "Clash API secret" +msgstr "Clash API 密钥" + +msgid "Clear the Honk administrator account so the first-setup page opens again? Subscriptions, nodes and routing are kept. Honk will be stopped." +msgstr "清掉 Honk 管理员账号、让首次设置页面重新出现?订阅、节点与路由都会保留;Honk 会被停止。" + msgid "Complete initial setup before enabling Honk." msgstr "请先完成首次设置,再启用 Honk。" msgid "Complete initial setup before starting Honk." msgstr "请先完成首次设置,再启动 Honk。" -msgid "Complete initial setup from the management panel." -msgstr "请先在管理面板完成首次设置。" +msgid "Config backup" +msgstr "配置备份" + +msgid "Configuration" +msgstr "配置" + +msgid "Configuration applied and Honk is running." +msgstr "配置已生效,Honk 已启动。" + +msgid "Configuration applied." +msgstr "配置已应用。" + +msgid "Configuration file loaded into the editor." +msgstr "Configuration file loaded into the editor." + +msgid "Configuration imported and Honk reloaded." +msgstr "配置已导入并重载 Honk。" + +msgid "Configuration is valid." +msgstr "配置校验通过。" + +msgid "Configuration is valid: %s" +msgstr "配置校验通过:%s" + +msgid "Configuration loaded." +msgstr "Configuration loaded." + +msgid "Configuration management" +msgstr "配置管理" + +msgid "Configuration saved. The running service is unchanged until you apply it." +msgstr "Configuration saved. The running service is unchanged until you apply it." + +msgid "Configuration validation failed." +msgstr "配置校验失败。" + +msgid "Configuration was written, but Honk is not running: %s" +msgstr "配置已写入,但 Honk 没有运行:%s" + +msgid "Core" +msgstr "核心" + +msgid "Core version" +msgstr "核心版本" + +msgid "Could not add the subscription." +msgstr "无法添加订阅。" + +msgid "Could not apply the configuration." +msgstr "无法应用配置。" + +msgid "Could not confirm that Honk stopped. The operation was aborted and the snapshot was kept." +msgstr "无法确认 Honk 已停止,操作已中止,快照已保留。" + +msgid "Could not import nodes." +msgstr "无法导入节点。" + +msgid "Could not load LAN networks: %s" +msgstr "无法读取 LAN 网络:%s" + +msgid "Could not read Honk logs." +msgstr "无法读取 Honk 日志。" msgid "Could not read Honk status." msgstr "无法读取 Honk 状态。" +msgid "Could not read current settings: %s" +msgstr "无法读取当前设置:%s" + +msgid "Could not read the configuration file." +msgstr "无法读取配置文件。" + +msgid "Could not read the draft." +msgstr "读取草稿失败。" + +msgid "Could not read the list of network interfaces." +msgstr "无法读取当前网络接口列表。" + +msgid "Could not read the saved configuration." +msgstr "无法读取已保存的配置。" + +msgid "Could not reset the administrator." +msgstr "无法重置管理员。" + msgid "Could not save Honk settings." msgstr "无法保存 Honk 设置。" -msgid "The curl command is missing. Install the curl package and try again." -msgstr "缺少 curl 命令,请先安装 curl 包(apk add curl 或 opkg install curl)后重试。" +msgid "Could not save the configuration." +msgstr "无法保存配置。" -msgid "Save failed. Check the settings and system status." -msgstr "保存失败,请检查设置和系统状态。" +msgid "Could not save the draft." +msgstr "保存草稿失败。" -msgid "System check failed." -msgstr "系统检查失败。" +msgid "Could not write the configuration file." +msgstr "无法写入配置文件。" -msgid "Repair failed." -msgstr "修复失败。" +msgid "Could not write the configuration. The snapshot was kept." +msgstr "无法写入配置,快照已保留。" -msgid "Backup failed." -msgstr "备份失败。" +msgid "Create the Honk administrator account. The password is sent only for this request and is not saved by this page." +msgstr "创建 Honk 管理员账号。密码只会随本次请求发送,本页面不会保存密码。" -msgid "Restore failed." -msgstr "恢复失败。" +msgid "Creating backup…" +msgstr "正在创建备份……" -msgid "Doona is not ready yet." -msgstr "Doona 尚未就绪。" +msgid "Current configuration loaded from /etc/honk/config.dae." +msgstr "已载入 /etc/honk/config.dae 的当前配置。" + +msgid "Custom" +msgstr "自定义" + +msgid "Daily" +msgstr "每天" + +msgid "Data source and auto-update" +msgstr "数据源与自动更新" + +msgid "Debug and above" +msgstr "调试及以上" + +msgid "Detected %s · %d node(s): %s" +msgstr "识别为 %s · %d 个节点:%s" + +msgid "Detected %s." +msgstr "识别为 %s。" + +msgid "Disabled" +msgstr "关闭" + +msgid "Discard the unsaved editor changes?" +msgstr "放弃编辑器中未保存的修改?" + +msgid "Doona" +msgstr "Doona" + +msgid "Doona %s" +msgstr "Doona 面板 %s" msgid "Doona is not ready yet. Check the overview for status." msgstr "Doona 尚未就绪,请在概况页查看状态。" +msgid "Doona version" +msgstr "Doona 版本" + +msgid "Download redacted log" +msgstr "下载脱敏日志" + +msgid "Downloading the signed package set…" +msgstr "正在下载签名软件包组……" + +msgid "Draft loaded into the editor. It is not active until you apply it." +msgstr "草稿已载入编辑器,应用前不会生效。" + +msgid "Draft saved. It is not active until you apply it." +msgstr "草稿已保存,应用前不会生效。" + +msgid "Edit config" +msgstr "编辑配置" + +msgid "Enable Clash API" +msgstr "启用 Clash API" + +msgid "Enter a different Clash API port and a secret of 6–128 letters, numbers, dots, underscores or hyphens." +msgstr "请设置不同的 Clash API 端口,以及 6~128 位英文字母、数字、点、下划线或短横线组成的密钥。" + +msgid "Enter both a username and password." +msgstr "请输入用户名和密码。" + +msgid "Errors only" +msgstr "仅错误" + +msgid "Export" +msgstr "导出" + +msgid "Export a backup archive, import one from your computer, or restore Honk to its default configuration." +msgstr "导出备份归档、从电脑导入,或将 Honk 恢复为默认配置。" + +msgid "Finish the first setup first: Honk does not start without an administrator." +msgstr "请先完成首次设置:没有管理员账号时 Honk 不会启动。" + +msgid "Finishing the transaction…" +msgstr "正在完成事务……" + +msgid "For a complete configuration, check lan_interface and the group names against your nodes." +msgstr "完整配置请核对 lan_interface 与分组名称是否对得上节点。" + +msgid "Full dae configuration" +msgstr "完整 dae 配置" + +msgid "Geo data" +msgstr "Geo 数据" + +msgid "Geo data source saved." +msgstr "Geo 数据源已保存。" + +msgid "GeoIP URL" +msgstr "GeoIP 地址" + +msgid "GeoIP and GeoSite routing data can be updated here. Updates run in the background and reload Honk when done." +msgstr "可在此更新 GeoIP 与 GeoSite 路由数据。更新在后台运行,完成后自动重载 Honk。" + +msgid "GeoSite URL" +msgstr "GeoSite 地址" + msgid "Go to overview" msgstr "前往概况页" +msgid "Honk" +msgstr "Honk" + +msgid "Honk configuration is unavailable." +msgstr "Honk 配置不可用。" + +msgid "Honk core and LuCI can be upgraded here without leaving the management page. The package index refreshes in the background." +msgstr "无需离开管理页面即可升级 Honk 核心与 LuCI。软件包索引会在后台刷新。" + +msgid "Honk could not load the proposed configuration." +msgstr "Honk 无法加载准备写入的配置。" + +msgid "Honk could not prepare a private configuration upload directory." +msgstr "Honk could not prepare a private configuration upload directory." + +msgid "Honk could not prepare the upload location. Check the upload directory permissions and try again." +msgstr "Honk could not prepare the upload location. Check the upload directory permissions and try again." + +msgid "Honk could not read the current configuration." +msgstr "Honk could not read the current configuration." + +msgid "Honk could not restart or restore the previous service. Check its status and backup." +msgstr "Honk 未能重启,也未能恢复原服务,请检查服务状态和备份。" + msgid "Honk could not restart." msgstr "Honk 重启失败。" +msgid "Honk could not restart. The previous configuration and service were restored." +msgstr "Honk 未能重启,已恢复原配置和服务。" + +msgid "Honk could not start with this configuration. The previous file was restored." +msgstr "Honk 用这份配置起不来,已自动恢复上一版配置。" + msgid "Honk could not start." msgstr "Honk 启动失败。" msgid "Honk could not stop." msgstr "Honk 停止失败。" -msgid "Honk is disabled." -msgstr "Honk 当前未启用。" +msgid "Honk data reset. The service is stopped." +msgstr "Honk 数据已重置,服务已停止。" + +msgid "Honk initialized." +msgstr "Honk 已完成初始化。" msgid "Honk is already running." msgstr "Honk 已在运行。" +msgid "Honk is already up to date." +msgstr "Honk 已是最新版本。" + +msgid "Honk is disabled." +msgstr "Honk 当前未启用。" + msgid "Honk is enabled but currently stopped." msgstr "Honk 已启用,但当前未运行。" @@ -84,9 +447,21 @@ msgstr "Honk 正在运行,但管理界面尚未就绪。" msgid "Honk is stopped. Start it from the overview to open Doona." msgstr "Honk 已停止,请先到概况页启动服务,再打开 Doona。" +msgid "Honk logs are unavailable." +msgstr "Honk 日志暂不可用。" + +msgid "Honk or Doona changed the configuration during this operation. Try again." +msgstr "操作期间 Honk 或 Doona 修改了配置,请重试。" + +msgid "Honk reported a system issue: %s" +msgstr "Honk 报告了系统问题:%s" + msgid "Honk restarted." msgstr "Honk 已重启。" +msgid "Honk settings" +msgstr "Honk 设置" + msgid "Honk started." msgstr "Honk 已启动。" @@ -96,659 +471,77 @@ msgstr "Honk 已停止。" msgid "Honk system configuration is unavailable." msgstr "Honk 系统配置不可用。" -msgid "Honk configuration is unavailable." -msgstr "Honk 配置不可用。" +msgid "Import" +msgstr "导入" -msgid "Honk reported a system issue." -msgstr "Honk 检测到系统问题。" +msgid "Import and start" +msgstr "导入并启动" -msgid "Initialization failed. Check the system status." -msgstr "初始化失败,请检查系统状态。" +msgid "Import cancelled." +msgstr "已取消导入。" -msgid "Operation failed. Check Honk status." -msgstr "操作失败,请检查 Honk 状态。" +msgid "Import config" +msgstr "导入配置" -msgid "Settings saved and applied." -msgstr "设置已保存并应用。" +msgid "Import nodes" +msgstr "导入节点" -msgid "Refreshing status…" -msgstr "正在刷新状态……" +msgid "Importing %d node(s)…" +msgstr "正在导入 %d 个节点…" -msgid "Settings could not be applied. The previous settings were restored." -msgstr "设置应用失败,已恢复到之前的设置。" +msgid "Importing a complete configuration replaces the current one; back it up first, startup failures roll back." +msgstr "导入完整配置会替换当前配置,先备份;启动失败自动回滚。" -msgid "Start Honk to open Doona." -msgstr "启动 Honk 后即可打开 Doona。" - -msgid "System maintenance needs recovery." -msgstr "系统维护需要恢复处理。" - -msgid "The listen port is already in use." -msgstr "监听端口已被占用。" - -msgid "The listen port is invalid." -msgstr "监听端口无效。" - -msgid "The managed Honk service is not healthy." -msgstr "Honk 托管服务状态异常。" - -msgid "The selected LAN network is invalid or unavailable." -msgstr "所选 LAN 网络无效或不可用。" - -msgid "The selected LAN network is unavailable." -msgstr "所选 LAN 网络不可用。" - -msgid "An unmanaged Honk core process is running." -msgstr "检测到非托管的 Honk 核心进程正在运行。" - -msgid "Version information" -msgstr "版本信息" - -msgid "Backup and restore" -msgstr "备份与恢复" - -msgid "Backup downloaded." -msgstr "备份已下载。" - -msgid "Backup failed: %s" -msgstr "备份失败:%s" - -msgid "Backup restored." -msgstr "备份已恢复。" - -msgid "Choose a backup archive to upload…" -msgstr "请选择要上传的备份文件……" - -msgid "Choose a LAN network and a port from 1024 to 65535." -msgstr "请选择 LAN 网络,并将端口设置为 1024 至 65535。" - -msgid "Checks Honk system components and reports detected issues." -msgstr "检查 Honk 系统组件并报告发现的问题。" - -msgid "Checking…" -msgstr "正在检查……" - -msgid "Could not load LAN networks: %s" -msgstr "无法读取 LAN 网络:%s" - -msgid "Could not read current settings: %s" -msgstr "无法读取当前设置:%s" - -msgid "Could not read Honk status: %s" -msgstr "无法读取 Honk 状态:%s" - -msgid "Core version" -msgstr "核心版本" - -msgid "Create a backup archive or restore one from your computer." -msgstr "创建备份文件,或从电脑上传备份进行恢复。" - -msgid "Download backup" -msgstr "下载备份" - -msgid "Create the Honk administrator account. The password is sent only for this request and is not saved by this page." -msgstr "创建 Honk 管理员账号。密码只会随本次请求发送,本页面不会保存密码。" - -msgid "Dashboard" -msgstr "初始化" - -msgid "Doona version" -msgstr "Doona 版本" - -msgid "Doona web interface" -msgstr "Doona 网页管理界面" - -msgid "Enable Honk" -msgstr "启用 Honk" - -msgid "Enabled" -msgstr "已启用" - -msgid "Enter both a username and password." -msgstr "请输入用户名和密码。" - -msgid "Errors" -msgstr "错误信息" - -msgid "Honk" -msgstr "Honk" - -msgid "Honk initialized." -msgstr "Honk 已完成初始化。" - -msgid "Honk settings" -msgstr "Honk 设置" - -msgid "Honk status" -msgstr "Honk 状态" - -msgid "Initial setup" -msgstr "首次设置" - -msgid "Initialized" -msgstr "已初始化" - -msgid "Initialization" -msgstr "初始化状态" - -msgid "Initialize Honk" -msgstr "初始化 Honk" - -msgid "Initialization failed. Check the account details and system status." -msgstr "初始化失败,请检查账号信息和系统状态。" - -msgid "Initializing…" -msgstr "正在初始化……" - -msgid "Kernel" -msgstr "内核" - -msgid "LAN network" -msgstr "LAN 网络" - -msgid "Listen port" -msgstr "监听端口" - -msgid "Maintenance" -msgstr "维护" - -msgid "No network interfaces found" -msgstr "未找到网络接口" - -msgid "No reported errors." -msgstr "没有检测到错误。" - -msgid "Not initialized" -msgstr "尚未初始化" - -msgid "Open Doona" -msgstr "打开 Doona" - -msgid "Overview" -msgstr "概览" - -msgid "Operation completed." -msgstr "操作已完成。" - -msgid "Operation failed: %s" -msgstr "操作失败:%s" - -msgid "Password" -msgstr "密码" - -msgid "Preparing backup…" -msgstr "正在准备备份……" - -msgid "Refresh" -msgstr "刷新" - -msgid "Repair" -msgstr "修复" - -msgid "Repair completed." -msgstr "修复已完成。" - -msgid "Repair failed: %s" -msgstr "修复失败:%s" - -msgid "Repair replaces missing or damaged Honk system files and does not overwrite user configuration." -msgstr "修复会补齐或替换缺失、损坏的 Honk 系统文件,不会覆盖用户配置。" - -msgid "Repair system files" -msgstr "修复系统文件" - -msgid "Restart" -msgstr "重启" - -msgid "Repairing…" -msgstr "正在修复……" - -msgid "Restore backup" -msgstr "恢复备份" - -msgid "Restore failed: %s" -msgstr "恢复失败:%s" - -msgid "Restoring…" -msgstr "正在恢复……" - -msgid "Run system check" -msgstr "运行系统检查" - -msgid "Running" -msgstr "运行中" - -msgid "Save failed: %s" -msgstr "保存失败:%s" - -msgid "Save settings" -msgstr "保存设置" - -msgid "Service" -msgstr "服务状态" - -msgid "Saving…" -msgstr "正在保存……" - -msgid "Settings" -msgstr "设置" - -msgid "Settings saved." -msgstr "设置已保存。" - -msgid "Start at boot" -msgstr "开机启动" - -msgid "Start Honk at boot" -msgstr "开机时启动 Honk" - -msgid "Start" -msgstr "启动" - -msgid "Stopped" -msgstr "已停止" - -msgid "Stop" -msgstr "停止" - -msgid "Stop Honk before restoring. Restoring replaces Honk user data. Continue?" -msgstr "恢复前请先停止 Honk 服务。恢复操作会替换 Honk 用户数据,是否继续?" - -msgid "System check" -msgstr "系统检查" - -msgid "System check failed: %s" -msgstr "系统检查失败:%s" - -msgid "System check passed." -msgstr "系统检查通过。" - -msgid "The backend did not confirm the check." -msgstr "后端未确认检查结果。" - -msgid "The backend did not confirm the operation." -msgstr "后端未确认操作结果。" - -msgid "The backend returned an unexpected backup path." -msgstr "后端返回了意外的备份文件路径。" - -msgid "Username" -msgstr "用户名" - -msgid "Yes" -msgstr "是" - -msgid "No" -msgstr "否" - -msgid "Working…" -msgstr "正在处理……" - -msgid "Configuration" -msgstr "配置" - -msgid "Configuration import is unavailable in this core build." -msgstr "此版本核心程序尚未提供配置导入功能。" - -msgid "The offline preview and transactional apply interface is not installed yet. No subscription, node, DNS, or proxy setting was changed." -msgstr "离线预览与事务式应用接口尚未安装。本页面没有修改订阅、节点、DNS 或代理设置。" - -msgid "Logs" -msgstr "日志" - -msgid "Honk log streaming is unavailable in this core build." -msgstr "此版本核心程序尚未提供 Honk 日志读取功能。" - -msgid "This page will only read Honk-specific, size-limited logs after the core log interface is installed." -msgstr "核心日志接口安装后,本页面只会读取 Honk 专属且有大小限制的日志。" - -msgid "Service settings" -msgstr "服务设置" - -msgid "Save service settings" -msgstr "保存服务设置" - -msgid "Service settings saved." -msgstr "服务设置已保存。" - -msgid "Doona opens its own sign-in page." -msgstr "Doona 会打开独立的登录页面。" - -msgid "Doona is not ready yet. Check the service status." -msgstr "Doona 尚未就绪,请检查服务状态。" - -msgid "Honk is stopped. Start it to open Doona." -msgstr "Honk 已停止,请先启动服务再打开 Doona。" - -msgid "Settings could not be applied." -msgstr "设置应用失败。" - -msgid "Add subscription" -msgstr "添加订阅" - -msgid "Name" -msgstr "名称" - -msgid "Subscription URL" -msgstr "订阅地址" - -msgid "Preview subscription" -msgstr "预览订阅" - -msgid "Import share links" -msgstr "导入分享链接" - -msgid "Preview share links" -msgstr "预览分享链接" - -msgid "Import dae business configuration" -msgstr "导入 dae 业务配置" - -msgid "Only nodes, subscriptions, groups, routing, and DNS sections can be previewed for import. Global settings and external includes are not imported." -msgstr "预览导入仅处理节点、订阅、策略组、路由和 DNS 部分。全局设置与外部 include 不会导入。" - -msgid "Checking import support…" -msgstr "正在检查核心程序的导入能力……" - -msgid "Checking file and calculating SHA-256…" -msgstr "正在检查文件并计算 SHA-256……" - -msgid "Uploading the selected dae file…" -msgstr "正在上传选中的 dae 文件……" - -msgid "Browser SHA-256 is unavailable. Open LuCI over HTTPS to preview a dae file." -msgstr "浏览器无法计算 SHA-256,请通过 HTTPS 打开 LuCI 后再预览 dae 文件。" - -msgid "File upload failed." -msgstr "文件上传失败。" - -msgid "Preview failed." -msgstr "预览失败。" - -msgid "Preview is ready. No configuration has been applied." -msgstr "预览已就绪,尚未应用任何配置。" - -msgid "Preparing preview…" -msgstr "正在准备预览……" - -msgid "Choose a dae file first." -msgstr "请先选择 dae 文件。" - -msgid "The dae file exceeds the 2 MiB limit." -msgstr "dae 文件超过 2 MiB 限制。" - -msgid "Share links exceed the 16 KiB per request limit. Submit them in smaller batches." -msgstr "分享链接超过每次 16 KiB 的限制,请拆分成多批提交。" - -msgid "One link per line. A single request is limited to 16 KiB; larger lists can be submitted in batches. Conflicts are reported for review." -msgstr "每行填写一个链接。单次请求限制为 16 KiB,较大的列表可分批提交;冲突会列出供检查。" - -msgid "One file up to 2 MiB. The browser calculates SHA-256 before upload; the core verifies the uploaded file again." -msgstr "单个文件最大 2 MiB。浏览器上传前会计算 SHA-256,核心程序还会再次校验文件。" - -msgid "Only nodes, subscriptions, groups, routing, and DNS sections can be previewed. Global settings and external includes are excluded or rejected." -msgstr "预览仅处理节点、订阅、策略组、路由和 DNS 部分。全局设置会排除,外部 include 会拒绝。" - -msgid "Imports append subscriptions and nodes by default. Dae import replaces only the listed business sections after preview." -msgstr "订阅和节点默认追加。dae 导入经过预览后,只替换列出的业务配置部分。" - -msgid "Import does not start Honk or change DNS settings. After import, assign items to groups and review Doona update behavior." -msgstr "导入不会启动 Honk 或更改 DNS 设置。导入后请分配策略组,并检查 Doona 的更新行为。" - -msgid "Saving subscriptions and share links does not start Honk or change routing, DNS, or the enabled state. Dae import replaces business routing and DNS only as shown in the preview; it does not overwrite global or managed system settings." -msgstr "保存订阅和分享链接不会启动 Honk,也不会更改路由、DNS 或启用状态。dae 导入只按预览替换业务路由和 DNS,不会覆盖全局设置或受管理的系统设置。" - -msgid "The preview is available when supported by the installed core. Apply remains disabled until a verified transactional apply is available." -msgstr "当前核心程序支持时可使用预览。经过验证的事务式应用功能就绪前,应用操作保持禁用。" - -msgid "This core supports preview and apply." -msgstr "此核心程序支持预览和应用。" - -msgid "This core supports preview; transactional apply is not installed yet." -msgstr "此核心程序支持预览,但尚未安装事务式应用功能。" - -msgid "Import is unavailable in this installed core. Existing service controls remain available." -msgstr "当前安装的核心程序不支持配置导入,现有服务控制仍可使用。" - -msgid "Preview summary" -msgstr "预览摘要" - -msgid "Candidate: %d nodes, %d subscriptions, %d files changed." -msgstr "候选配置:%d 个节点、%d 个订阅、%d 个文件有更改。" - -msgid "Imported sections: %s" -msgstr "导入部分:%s" - -msgid "Excluded sections: %s" -msgstr "排除部分:%s" - -msgid "Existing sections replaced: %s" -msgstr "替换的现有配置:%s" - -msgid "Parser and configuration checks passed. Runtime validation: %s." -msgstr "解析和配置检查已通过。运行时校验:%s。" - -msgid "Runtime validation was skipped because candidate listeners and background network tasks are not isolated." -msgstr "候选配置的监听器和后台网络任务尚未隔离,因此跳过了运行时校验。" - -msgid "Apply this preview" -msgstr "应用此预览" - -msgid "Apply the reviewed preview? Any running Honk service will be stopped during the change and its previous running state will be restored afterward." -msgstr "确认应用已审核的预览吗?更改期间会停止正在运行的 Honk 服务,完成后恢复之前的运行状态。" - -msgid "Starting configuration apply…" -msgstr "正在启动配置应用……" - -msgid "Configuration applied. Review group assignment and update subscriptions in Doona." -msgstr "配置已应用。请在 Doona 中检查策略组分配并更新订阅。" - -msgid "Configuration apply failed." -msgstr "配置应用失败。" - -msgid "Waiting for the operation to start…" -msgstr "正在等待操作开始……" - -msgid "Preparing configuration apply…" -msgstr "正在准备应用配置……" - -msgid "Stopping Honk safely…" -msgstr "正在安全停止 Honk……" - -msgid "Applying the reviewed configuration…" -msgstr "正在应用已审核的配置……" - -msgid "Checking the updated configuration…" -msgstr "正在检查更新后的配置……" - -msgid "Restoring the previous service state…" -msgstr "正在恢复之前的服务状态……" - -msgid "Restoring the previous configuration…" -msgstr "正在恢复之前的配置……" - -msgid "Finishing the transaction…" -msgstr "正在完成事务……" - -msgid "Recovery is required before another operation." -msgstr "需要先完成恢复,才能执行其他操作。" - -msgid "Applying configuration…" -msgstr "正在应用配置……" - -msgid "passed" -msgstr "已通过" - -msgid "not run" -msgstr "未运行" - -msgid "Apply is disabled until this core reports transactional import support." -msgstr "核心程序报告支持事务式导入前,应用操作保持禁用。" - -msgid "Save any browser draft in Doona before applying configuration changes." -msgstr "应用配置更改前,请先在 Doona 保存浏览器中的草稿。" - -msgid "Imported subscriptions and nodes are not assigned to groups automatically. Review group assignment and Doona update behavior after import." -msgstr "导入的订阅和节点不会自动分配到策略组。导入后请检查策略组分配及 Doona 的更新行为。" - -msgid "Imported items are not assigned to groups automatically." -msgstr "导入的项目不会自动分配到策略组。" - -msgid "This installed core does not support the requested import operation." -msgstr "当前安装的核心程序不支持此导入操作。" - -msgid "The import request is incomplete or invalid." -msgstr "导入请求不完整或无效。" - -msgid "The core could not prepare this import preview." -msgstr "核心程序无法准备此导入预览。" - -msgid "Share links exceed the 16 KiB per request limit." -msgstr "分享链接超过单次 16 KiB 的限制。" - -msgid "A share link is invalid or conflicts with an existing node. Review the input and try again." -msgstr "分享链接无效或与现有节点冲突,请检查内容后重试。" - -msgid "The subscription name or URL is invalid." -msgstr "订阅名称或地址无效。" - -msgid "The subscription is invalid or its name already exists." -msgstr "订阅无效或名称已存在。" - -msgid "The dae file contains invalid sections or unsupported includes." -msgstr "dae 文件包含无效配置段或不支持的 include。" - -msgid "Honk could not load the proposed configuration." -msgstr "Honk 无法加载准备写入的配置。" - -msgid "The imported configuration failed core validation." -msgstr "导入配置未通过核心程序校验。" - -msgid "Honk or Doona changed the configuration during this operation. Try again." -msgstr "操作期间 Honk 或 Doona 修改了配置,请重试。" - -msgid "The preview expired. Create a new preview." -msgstr "预览已过期,请重新预览。" - -msgid "The saved preview is damaged. Create a new preview." -msgstr "已保存的预览已损坏,请重新预览。" - -msgid "The dae upload is missing or unavailable. Upload the file again." -msgstr "dae 上传文件缺失或不可用,请重新上传。" - -msgid "The uploaded dae file is invalid." -msgstr "上传的 dae 文件无效。" - -msgid "The uploaded dae file changed. Upload it again." -msgstr "上传的 dae 文件已更改,请重新上传。" - -msgid "A private upload location could not be prepared." -msgstr "无法准备私有上传目录。" - -msgid "Preview dae file" -msgstr "预览 dae 文件" - -msgid "Subscriptions and nodes are appended by default. Import does not start Honk or change DNS settings." -msgstr "订阅和节点默认采用追加方式。导入不会启动 Honk,也不会更改 DNS 设置。" - -msgid "Save pending edits in Doona before applying a configuration change." -msgstr "应用配置更改前,请先在 Doona 保存尚未提交的编辑。" - -msgid "Log source" -msgstr "日志来源" - -msgid "All levels" -msgstr "所有级别" - -msgid "Minimum level" -msgstr "最低级别" - -msgid "Warnings and errors" -msgstr "警告和错误" - -msgid "Errors only" -msgstr "仅错误" - -msgid "Pause" -msgstr "暂停" - -msgid "Download redacted log" -msgstr "下载脱敏日志" - -msgid "Shows the latest 200 lines, capped at 256 KiB, and refreshes every 5 seconds while live." -msgstr "显示最近 200 行,最多 256 KiB;实时模式下每 5 秒刷新。" - -msgid "Log output is unavailable." -msgstr "日志内容暂不可用。" - -msgid "Reset Honk data" -msgstr "重置 Honk 数据" - -msgid "Reset clears Honk subscriptions, nodes, policies, DNS data, history, and cache, then restores default configuration. It keeps the administrator account and does not change OpenWrt services." -msgstr "重置会清除 Honk 订阅、节点、策略、DNS 数据、历史记录和缓存,并恢复默认配置。管理员账号会保留,也不会更改 OpenWrt 服务。" - -msgid "Reset is unavailable until the pinned core reset action is installed." -msgstr "固定版本核心程序尚未安装重置操作,因此目前无法重置。" - -msgid "Honk updates" -msgstr "Honk 更新" - -msgid "Updates install the matching Honk core, LuCI, Doona assets, and Chinese translation as one package set." -msgstr "更新会一次安装相互匹配的 Honk 核心、LuCI、Doona 资源和简体中文翻译包。" - -msgid "No trusted signed feed and compatibility manifest are configured, so no update is available." -msgstr "尚未配置可信签名软件源和兼容性清单,因此目前没有可用更新。" - -msgid "Check for updates" -msgstr "检查更新" - -msgid "A compatible update is available: %s" -msgstr "发现兼容更新:%s" - -msgid "A previous maintenance operation needs recovery before write operations can continue." -msgstr "上次维护操作需要先恢复,才能继续执行写入操作。" - -msgid "Applying the staged data…" -msgstr "正在应用暂存的数据……" - -msgid "Backup created." -msgstr "备份已创建。" - -msgid "Checking update source…" -msgstr "正在检查更新源……" - -msgid "Core" -msgstr "核心" - -msgid "Could not read Honk logs." -msgstr "无法读取 Honk 日志。" - -msgid "Creating backup…" -msgstr "正在创建备份……" - -msgid "Debug and above" -msgstr "调试及以上" - -msgid "Executed checks: %s" -msgstr "已执行的检查:%s" - -msgid "Honk data reset. The service is stopped." -msgstr "Honk 数据已重置,服务已停止。" - -msgid "Honk logs are unavailable." -msgstr "Honk 日志暂不可用。" +msgid "In the editor, checking only parses; a draft stays inactive until you save and apply." +msgstr "编辑器里:校验只查语法,草稿不生效,保存并应用才写入。" msgid "Info and above" msgstr "信息及以上" -msgid "Install the verified Honk, LuCI, Doona, and Chinese translation package set? If installation or startup fails, Honk will remain stopped and show recovery instructions." -msgstr "要安装已验证且相互匹配的 Honk、LuCI、Doona 和简体中文翻译包吗?如果安装或启动失败,Honk 会保持停止并提示恢复方法。" +msgid "Initial setup" +msgstr "首次设置" -msgid "Install update" -msgstr "安装更新" +msgid "Initialization failed." +msgstr "初始化失败。" -msgid "Installing the verified update…" -msgstr "正在安装已验证的更新……" +msgid "Initialization failed. Check the system status." +msgstr "初始化失败,请检查系统状态。" + +msgid "Initialize Honk" +msgstr "初始化 Honk" + +msgid "Initializing…" +msgstr "正在初始化……" + +msgid "Invalid %s: must be an http(s) URL." +msgstr "无效的 %s:必须是 http(s) 链接。" + +msgid "Kernel" +msgstr "内核" + +msgid "LAN %s" +msgstr "局域网接口 %s" + +msgid "LAN network" +msgstr "LAN 网络" + +msgid "Line %d" +msgstr "第 %d 行" + +msgid "Listen port" +msgstr "监听端口" + +msgid "Live log" +msgstr "实时日志" + +msgid "Load draft" +msgstr "载入草稿" + +msgid "Load old draft" +msgstr "载入旧草稿" + +msgid "Loaded %s into the editor." +msgstr "已把 %s 载入编辑器。" msgid "Loading logs…" msgstr "正在读取日志……" @@ -759,26 +552,119 @@ msgstr "日志自动刷新已暂停。" msgid "Log polling resumed." msgstr "日志自动刷新已恢复。" +msgid "Log source" +msgstr "日志来源" + +msgid "Logs" +msgstr "日志" + +msgid "Maintenance" +msgstr "维护" + +msgid "Minimum level" +msgstr "最低级别" + +msgid "Name" +msgstr "名称" + +msgid "Name is required." +msgstr "请输入名称。" + +msgid "Name, e.g. MyAirport" +msgstr "名称,例如 MyAirport" + +msgid "Needs your attention:" +msgstr "需要你处理:" + +msgid "Network interface, listen port and boot behaviour for the Honk service." +msgstr "Honk 服务的网络接口、监听端口与开机启动行为。" + msgid "No compatible update is available." msgstr "没有可用的兼容更新。" +msgid "No file chosen" +msgstr "未选择任何文件" + msgid "No matching Honk log entries." msgstr "没有匹配的 Honk 日志。" +msgid "No network interfaces found" +msgstr "未找到网络接口" + +msgid "No nodes could be parsed from the input." +msgstr "无法从输入中解析出节点。" + msgid "No trusted update source is configured." msgstr "尚未配置可信更新源。" +msgid "Nodes saved." +msgstr "节点已保存。" + +msgid "None of the active interfaces belongs to the configured LAN network. Pick the one Honk should see as LAN:" +msgstr "当前活跃的网卡都不属于 LuCI 里配置的 LAN 网络。请挑一个 Honk 应该当作 LAN 的网卡:" + +msgid "Not initialized yet. Finish the first setup below, then Honk can be started." +msgstr "还没完成首次设置。先做完下面的首次设置,Honk 才能启动。" + +msgid "Open Clash panel" +msgstr "打开 Clash 面板" + +msgid "Open Doona" +msgstr "打开 Doona" + +msgid "Operation completed." +msgstr "操作已完成。" + +msgid "Operation failed. Check Honk status." +msgstr "操作失败,请检查 Honk 状态。" + msgid "Output was capped; showing the latest %d entries." msgstr "日志输出已达到上限,当前显示最新的 %d 条。" +msgid "Package updates" +msgstr "软件包更新" + +msgid "Package updates, config backup and system recovery for the Honk installation." +msgstr "Honk 安装的软件包更新、配置备份与系统恢复。" + +msgid "Parsing nodes…" +msgstr "正在解析节点…" + +msgid "Password" +msgstr "密码" + +msgid "Paste Clash YAML, Surge INI, Base64 or share links." +msgstr "粘贴 Clash YAML、Surge INI、Base64 或分享链接。" + +msgid "Paste nodes or subscription content first." +msgstr "请先粘贴节点或订阅内容。" + +msgid "Pause" +msgstr "暂停" + +msgid "Preparing backup…" +msgstr "正在准备备份……" + +msgid "Preparing configuration apply…" +msgstr "正在准备应用配置……" + msgid "Preparing download…" msgstr "正在准备下载……" msgid "Preparing maintenance…" msgstr "正在准备维护操作……" -msgid "Preparing the signed update…" -msgstr "正在准备签名更新……" +msgid "Probing…" +msgstr "检查中…" + +msgid "Read before use" +msgstr "使用前确认" + +msgid "Reading %s…" +msgstr "正在读取 %s…" + +msgid "Recovery is required before another operation." +msgstr "需要先完成恢复,才能执行其他操作。" msgid "Recovery is required before another write operation." msgstr "需要先完成恢复,才能进行其他写入操作。" @@ -789,8 +675,29 @@ msgstr "需要先恢复系统,再执行其他维护操作。" msgid "Refresh now" msgstr "立即刷新" -msgid "Reset clears Honk subscriptions, nodes, policies, DNS data, history, and cache, then restores default configuration. It keeps the administrator account and leaves Honk stopped with boot disabled." -msgstr "重置会清除 Honk 订阅、节点、策略、DNS 数据、历史记录和缓存,并恢复默认配置;管理员账号会保留,Honk 服务和开机启动都会关闭。" +msgid "Refreshing status…" +msgstr "正在刷新状态……" + +msgid "Repair" +msgstr "修复" + +msgid "Repair completed." +msgstr "修复已完成。" + +msgid "Repair failed." +msgstr "修复失败。" + +msgid "Repair replaces missing or damaged Honk system files and does not overwrite user configuration." +msgstr "修复会补齐或替换缺失、损坏的 Honk 系统文件,不会覆盖用户配置。" + +msgid "Repairing…" +msgstr "正在修复……" + +msgid "Required when enabling. Use 6–128 letters, numbers, dots, underscores or hyphens." +msgstr "启用时必填,6–128 位字母、数字、点、下划线或连字符。" + +msgid "Reset administrator" +msgstr "重置管理员" msgid "Reset failed and automatic recovery did not finish. Run system recovery before retrying." msgstr "重置失败,自动恢复未能完成。请先恢复系统,再重试。" @@ -801,36 +708,240 @@ msgstr "重置失败。" msgid "Reset failed; the previous service state was restored." msgstr "重置失败,服务已恢复到之前的状态。" -msgid "Resetting Honk data…" -msgstr "正在重置 Honk 数据……" +msgid "Resetting the administrator…" +msgstr "正在重置管理员…" + +msgid "Restart" +msgstr "重启" + +msgid "Restore config" +msgstr "恢复配置" msgid "Restore failed and automatic recovery did not finish. Run system recovery before retrying." msgstr "恢复失败,自动恢复未能完成。请先恢复系统,再重试。" +msgid "Restore failed." +msgstr "恢复失败。" + msgid "Restoring Honk data…" msgstr "正在恢复 Honk 数据……" +msgid "Restoring default config…" +msgstr "正在恢复默认配置…" + +msgid "Restoring the previous configuration…" +msgstr "正在恢复之前的配置……" + +msgid "Restoring the previous service state…" +msgstr "正在恢复之前的服务状态……" + msgid "Restoring the previous state…" msgstr "正在恢复之前的状态……" +msgid "Restoring…" +msgstr "正在恢复……" + msgid "Resume" msgstr "继续" +msgid "Run system check" +msgstr "运行系统检查" + +msgid "Running" +msgstr "运行中" + +msgid "Save" +msgstr "保存" + +msgid "Save and apply" +msgstr "保存并应用" + +msgid "Save cancelled." +msgstr "已取消保存。" + +msgid "Save draft" +msgstr "保存草稿" + +msgid "Save failed" +msgstr "保存失败" + +msgid "Save failed. Check the settings and system status." +msgstr "保存失败,请检查设置和系统状态。" + +msgid "Save service settings" +msgstr "保存服务设置" + +msgid "Save settings" +msgstr "保存设置" + +msgid "Save the subscription anyway?" +msgstr "仍然保存这份订阅吗?" + +msgid "Saving draft…" +msgstr "正在保存草稿…" + +msgid "Saving will adapt for this router: %s" +msgstr "保存时会按本机适配:%s" + +msgid "Saving…" +msgstr "正在保存……" + +msgid "Service" +msgstr "服务状态" + +msgid "Service settings" +msgstr "服务设置" + +msgid "Service settings saved." +msgstr "服务设置已保存。" + +msgid "Settings" +msgstr "设置" + +msgid "Settings could not be applied." +msgstr "设置应用失败。" + +msgid "Settings could not be applied. The previous settings were restored." +msgstr "设置应用失败,已恢复到之前的设置。" + +msgid "Settings saved and applied." +msgstr "设置已保存并应用。" + +msgid "Share links exceed the 16 KiB per request limit." +msgstr "分享链接超过单次 16 KiB 的限制。" + msgid "Showing the latest %d matching entries." msgstr "当前显示最新的 %d 条匹配日志。" msgid "Shows up to 200 Honk-specific lines, capped at 32 KiB, and refreshes every 5 seconds while live. Sensitive URLs and credentials are redacted before display." msgstr "最多显示 200 行 Honk 日志,数据上限为 32 KiB;实时模式下每 5 秒刷新,并在显示前隐藏敏感网址和凭据。" -msgid "Skipped checks: %s" -msgstr "跳过的检查:%s" +msgid "Source" +msgstr "数据源" + +msgid "Start" +msgstr "启动" msgid "Starting Honk…" msgstr "正在启动 Honk……" +msgid "Startup failed and the automatic rollback did not finish. The snapshot was kept; run system recovery." +msgstr "启动失败且自动回滚未完成,快照已保留,请到「维护」执行系统恢复。" + +msgid "Status" +msgstr "状态" + +msgid "Stop" +msgstr "停止" + +msgid "Stop Honk before restoring. Restoring replaces Honk user data. Continue?" +msgstr "恢复前请先停止 Honk 服务。恢复操作会替换 Honk 用户数据,是否继续?" + +msgid "Stopped" +msgstr "已停止" + +msgid "Stopping Honk safely…" +msgstr "正在安全停止 Honk……" + msgid "Stopping Honk…" msgstr "正在停止 Honk……" +msgid "Subscription URL" +msgstr "订阅地址" + +msgid "Subscription saved with usable nodes." +msgstr "订阅已保存,检测到可用节点。" + +msgid "Subscriptions and nodes must use a protocol Honk supports; SSR is not supported." +msgstr "订阅与节点需使用 Honk 支持的协议,不支持 SSR。" + +msgid "System check and repair" +msgstr "系统检查与修复" + +msgid "System check failed." +msgstr "系统检查失败。" + +msgid "System check passed." +msgstr "系统检查通过。" + +msgid "System check: %s" +msgstr "系统检查结果:%s" + +msgid "Temporary upload cleared." +msgstr "Temporary upload cleared." + +msgid "The Clash API listener is unavailable. Check the service logs." +msgstr "Clash API 监听不可用,请检查服务日志。" + +msgid "The Clash API port is already in use." +msgstr "Clash API 端口已被占用。" + +msgid "The Clash API port must differ from the listen port." +msgstr "Clash API 端口不能和监听端口相同。" + +msgid "The backend did not confirm the check." +msgstr "后端未确认检查结果。" + +msgid "The backend did not confirm the operation." +msgstr "后端未确认操作结果。" + +msgid "The configuration changed during the operation. Nothing was applied." +msgstr "操作期间配置被其他进程改动,本次没有写入。" + +msgid "The configuration did not pass static validation." +msgstr "The configuration did not pass static validation." + +msgid "The configuration file exceeds the 2 MiB limit." +msgstr "The configuration file exceeds the 2 MiB limit." + +msgid "The configuration file is missing." +msgstr "配置文件不存在。" + +msgid "The configuration request is empty or invalid." +msgstr "The configuration request is empty or invalid." + +msgid "The core could not prepare this import preview." +msgstr "核心程序无法准备此导入预览。" + +msgid "The core validator did not return a result." +msgstr "The core validator did not return a result." + +msgid "The curl command is missing. Install the curl package and try again." +msgstr "缺少 curl 命令,请先安装 curl 包(apk add curl 或 opkg install curl)后重试。" + +msgid "The dae file contains invalid sections or unsupported includes." +msgstr "dae 文件包含无效配置段或不支持的 include。" + +msgid "The dae file exceeds the 2 MiB limit." +msgstr "dae 文件超过 2 MiB 限制。" + +msgid "The dae upload is missing or unavailable. Upload the file again." +msgstr "dae 上传文件缺失或不可用,请重新上传。" + +msgid "The editor changed while checking. Nothing was applied; check again." +msgstr "检查期间编辑器内容被改动了,没有应用任何修改,请重新检查。" + +msgid "The editor changed while checking. Nothing was applied; import the file again." +msgstr "检查期间编辑器内容被改动了,没有应用任何修改,请重新导入文件。" + +msgid "The editor is empty." +msgstr "编辑器内容为空。" + +msgid "The file is empty." +msgstr "文件内容为空。" + +msgid "The import request is incomplete or invalid." +msgstr "导入请求不完整或无效。" + +msgid "The imported configuration failed core validation." +msgstr "导入配置未通过核心程序校验。" + +msgid "The listen port is already in use." +msgstr "监听端口已被占用。" + +msgid "The listen port is invalid." +msgstr "监听端口无效。" + msgid "The maintenance operation failed." msgstr "维护操作失败。" @@ -840,20 +951,128 @@ msgstr "维护操作超时。" msgid "The maintenance worker stopped unexpectedly; check system recovery status before retrying." msgstr "维护进程意外停止。请先检查系统恢复状态,再重试。" +msgid "The managed Honk service is not healthy." +msgstr "Honk 托管服务状态异常。" + +msgid "The operation failed." +msgstr "操作失败。" + +msgid "The password must be between 8 and 128 characters." +msgstr "密码长度必须在 8 到 128 个字符之间。" + +msgid "The pasted content is too large." +msgstr "粘贴内容过大。" + +msgid "The preview expired. Create a new preview." +msgstr "预览已过期,请重新预览。" + +msgid "The saved draft is not a valid regular file." +msgstr "保存的草稿不是普通文件。" + +msgid "The saved preview is damaged. Create a new preview." +msgstr "已保存的预览已损坏,请重新预览。" + +msgid "The secret must be 6-128 letters, numbers, dots, underscores or hyphens." +msgstr "密钥需要 6~128 位,只能包含字母、数字、点、下划线或短横线。" + +msgid "The selected LAN network is invalid or unavailable." +msgstr "所选 LAN 网络无效或不可用。" + +msgid "The selected LAN network is unavailable." +msgstr "所选 LAN 网络不可用。" + +msgid "The signed manifest expired. Check for updates again." +msgstr "签名清单已过期,请重新检查更新。" + +msgid "The signed update could not be verified for this device." +msgstr "无法验证适用于此设备的签名更新。" + +msgid "The signed update did not finish. Keep Honk stopped and reinstall a complete verified package set before retrying." +msgstr "签名更新未能完成。请保持 Honk 停止,重新安装完整且已验证的软件包组后再试。" + +msgid "The signed update manifest could not be fetched or verified." +msgstr "无法下载或验证签名更新清单。" + +msgid "The signed update manifest does not match this device." +msgstr "签名更新清单与此设备不兼容。" + +msgid "The subscription could not be added: the name must be 1-64 characters without single quotes, and the URL must be a valid http(s) link." +msgstr "无法添加订阅:名称 1-64 个字符且不能含单引号;链接必须是有效的 http(s) 地址。" + +msgid "The subscription is invalid or its name already exists." +msgstr "订阅无效或名称已存在。" + +msgid "The subscription returned no usable nodes. The configuration was not changed." +msgstr "订阅没有返回可用节点,配置未改动。" + +msgid "The system check did not finish (interface timeout or error). Retry, or check the core log under Maintenance → Logs." +msgstr "系统检查没有跑完(接口超时或出错),请重试;或到「维护 → 日志」查看核心日志。" + +msgid "The system check did not finish. Retry, or check the core log under Maintenance → Logs." +msgstr "系统检查没有跑完,请重试;或到「维护 → 日志」查看核心日志。" + +msgid "The system check passed. If it keeps failing, check the core log under Maintenance → Logs and report the code above." +msgstr "系统检查通过。若反复失败,请到「维护 → 日志」查看核心日志并把上面的错误码发给维护者。" + +msgid "The temporary configuration upload is unavailable. Try again." +msgstr "The temporary configuration upload is unavailable. Try again." + +msgid "The update could not be installed." +msgstr "无法安装更新。" + +msgid "The uploaded dae file changed. Upload it again." +msgstr "上传的 dae 文件已更改,请重新上传。" + +msgid "The uploaded dae file is invalid." +msgstr "上传的 dae 文件无效。" + +msgid "There is no saved draft to apply." +msgstr "没有可应用的已保存草稿。" + +msgid "These lines will be changed for this router:" +msgstr "这几行会按本机修改:" + +msgid "This core cannot use any of these nodes, so the subscription was not saved." +msgstr "这份订阅里的节点本内核一个都用不了,所以没有保存。" + +msgid "This file replaces the current configuration.\n\nIt defines: %s\n\nBack up the current configuration first if you are unsure. Continue?" +msgstr "这个文件会替换当前配置。\n\n包含:%s\n\n不确定的话先备份当前配置。继续吗?" + +msgid "This installed core does not support the requested import operation." +msgstr "当前安装的核心程序不支持此导入操作。" + msgid "This permanently clears Honk subscriptions, nodes, policies, DNS data, history, and cache, then restores the default configuration. The administrator account is kept, and Honk will be stopped. Continue?" msgstr "此操作会永久清除 Honk 订阅、节点、策略、DNS 数据、历史记录和缓存,并恢复默认配置,但保留管理员账号并停止 Honk。是否继续?" -msgid "Update check failed." -msgstr "检查更新失败。" +msgid "Transparent proxy core for OpenWrt. Start, stop and manage the Honk service." +msgstr "OpenWrt 的透明代理核心。启动、停止并管理 Honk 服务。" -msgid "Update failed." -msgstr "更新失败。" +msgid "Unavailable" +msgstr "不可用" -msgid "Update installed." -msgstr "更新已安装。" +msgid "Update" +msgstr "更新" -msgid "Updates remain unavailable until a trusted signed feed and compatibility manifest are configured." -msgstr "配置可信签名软件源和兼容性清单后,才能使用更新功能。" +msgid "Upgrade" +msgstr "升级" + +msgid "Upload location ready." +msgstr "Upload location ready." + +msgid "Use this interface" +msgstr "就用这个网卡" + +msgid "Use your existing HTTPS reverse proxy for external access. Enter the secret in Zashboard when connecting." +msgstr "外网访问请使用现有 HTTPS 反向代理,并在 Zashboard 中填写密钥。" + +msgid "Username" +msgstr "用户名" + +msgid "Username may only contain letters, digits, underscore, dot or hyphen (max 64 characters)." +msgstr "用户名只能包含字母、数字、下划线、点、连字符,最长 64 个字符。" + +msgid "Username: letters, digits, underscore, dot or hyphen (max 64 characters). Password: 8 to 128 characters." +msgstr "用户名:字母、数字、下划线、点、连字符(最长 64 个字符)。密码:8 到 128 个字符。" msgid "Validating the candidate in an isolated runtime…" msgstr "正在隔离环境中验证候选配置……" @@ -861,794 +1080,218 @@ msgstr "正在隔离环境中验证候选配置……" msgid "Validating the new state…" msgstr "正在检查更新后的状态……" -msgid "Checking the signed update source…" -msgstr "正在检查签名更新源……" +msgid "Waiting for the operation to start…" +msgstr "正在等待操作开始……" -msgid "Downloading the signed package set…" -msgstr "正在下载签名软件包组……" - -msgid "A compatible update is available, but a complete verified rollback package set is not available yet." -msgstr "发现兼容更新,但尚无完整且已验证的回滚软件包组。" - -msgid "The signed update did not finish. Keep Honk stopped and reinstall a complete verified package set before retrying." -msgstr "签名更新未能完成。请保持 Honk 停止,重新安装完整且已验证的软件包组后再试。" - -msgid "The signed update manifest does not match this device." -msgstr "签名更新清单与此设备不兼容。" - -msgid "The signed update manifest could not be fetched or verified." -msgstr "无法下载或验证签名更新清单。" - -msgid "A compatible update is available." -msgstr "发现兼容更新。" - -msgid "Honk is already up to date." -msgstr "Honk 已是最新版本。" - -msgid "The update could not be installed." -msgstr "无法安装更新。" - -msgid "The updater verifies a signed compatibility manifest. Installation stays disabled until a complete verified rollback package set is available and the device transaction path has passed validation." -msgstr "更新程序会验证签名兼容性清单。完整且已验证的回滚软件包组准备好、并通过设备事务验证后,才会开放安装。" - -msgid "The signed update could not be verified for this device." -msgstr "无法验证适用于此设备的签名更新。" - -msgid "Check for updates again to refresh the signed manifest." -msgstr "请重新检查更新,以刷新签名清单。" - -msgid "The signed manifest expired. Check for updates again." -msgstr "签名清单已过期,请重新检查更新。" - -msgid "A request is limited to 16 KiB. Add larger lists in batches; name or node conflicts are shown in the preview." -msgstr "单次请求最多 16 KiB。较大的列表请分批添加;名称或节点冲突会在预览中显示。" - -msgid "Add nodes" -msgstr "添加节点" - -msgid "Applying a preview is not available in this installed core." -msgstr "当前安装的核心暂不支持应用预览结果。" - -msgid "Before applying" -msgstr "应用前请注意" - -msgid "Choose one .dae file up to 2 MiB." -msgstr "请选择一个不超过 2 MiB 的 .dae 文件。" - -msgid "Configuration file" -msgstr "配置文件" - -msgid "Configuration input type" -msgstr "配置输入类型" - -msgid "Enter a subscription name." -msgstr "请输入订阅名称。" - -msgid "Enter a valid HTTP or HTTPS URL." -msgstr "请输入有效的 HTTP 或 HTTPS 地址。" - -msgid "Enter one or more share links." -msgstr "请输入一个或多个分享链接。" - -msgid "Import configuration" -msgstr "导入配置" - -msgid "Imported items are not assigned to groups automatically. Review group assignment and subscription updates in Doona." -msgstr "导入内容不会自动分配到分组。请在 Doona 中检查分组分配和订阅更新。" - -msgid "Inputs changed. Create a new preview before applying." -msgstr "输入内容已更改。请重新预览后再应用。" - -msgid "Preview is available. Apply stays disabled until transactional import is supported by this core." -msgstr "可以预览;当前核心尚不支持事务式导入,应用功能暂不可用。" - -msgid "Save any Doona browser draft before applying configuration changes." -msgstr "应用配置前,请先保存 Doona 浏览器中的草稿。" - -msgid "Share links, one per line" -msgstr "分享链接(每行一个)" - -msgid "Subscriptions and nodes append without changing routing, DNS, or service state. Dae import replaces business sections shown in the preview; global settings stay excluded." -msgstr "订阅和节点会追加,不会更改路由、DNS 或服务状态。DAE 导入会替换预览中列出的业务配置段,并排除全局设置。" - -msgid "This core supports preview and transactional apply." -msgstr "当前核心支持预览和事务式应用。" - -msgid "This replaces the business sections listed in the preview. Global settings are excluded, and external includes are rejected." -msgstr "这会替换预览中列出的业务配置段。全局设置会被排除,外部 include 会被拒绝。" - -msgid "Use an HTTP or HTTPS subscription address." -msgstr "请使用 HTTP 或 HTTPS 订阅地址。" - -msgid "Validation details" -msgstr "验证详情" - -msgid "System check and repair" -msgstr "系统检查与修复" - -msgid "The updater verifies a signed compatibility manifest. Installation remains unavailable until a complete verified rollback package set is available and the device transaction path has passed validation." -msgstr "更新程序会验证签名兼容性清单。完整且已验证的回滚软件包组准备好、并通过设备事务验证后,才会开放安装。" - -msgid "Check for updates first. Installation requires signature and rollback validation." -msgstr "请先检查更新。安装需要通过签名与回滚验证。" - -msgid "Update requirements" -msgstr "更新条件" - -msgid "Complete initial setup before opening Doona." -msgstr "请先完成初始化设置,再打开 Doona。" - -msgid "Status" -msgstr "状态" - -msgid "Live log" -msgstr "实时日志" - -msgid "About this import" -msgstr "关于本次导入" - -msgid "Doona" -msgstr "Doona" - -msgid "Transparent proxy core for OpenWrt. Start, stop and manage the Honk service." -msgstr "OpenWrt 的透明代理核心。启动、停止并管理 Honk 服务。" - -msgid "Add subscriptions, nodes or import a dae business configuration with a preview and transactional apply." -msgstr "添加订阅、节点,或以预览加事务式应用的方式导入 dae 业务配置。" - -msgid "Updates, backups and system recovery for the Honk installation." -msgstr "Honk 安装的更新、备份与系统恢复。" - -msgid "Network interface, listen port and boot behaviour for the Honk service." -msgstr "Honk 服务的网络接口、监听端口与开机启动行为。" - -msgid "Package updates" -msgstr "软件包更新" - -msgid "Probing…" -msgstr "检查中…" - -msgid "Upgrade" -msgstr "升级" - -msgid "not installed via package manager" -msgstr "未通过软件包管理器安装" - -msgid "Unavailable" -msgstr "不可用" - -msgid "latest version unknown" -msgstr "最新版本未知" - -msgid "installed" -msgstr "已安装" - -msgid "latest" -msgstr "最新" - -msgid "up to date" -msgstr "已是最新" - -msgid "Export" -msgstr "导出" - -msgid "Restore config" -msgstr "恢复配置" - -msgid "Config backup" -msgstr "配置备份" - -msgid "Restoring default config…" -msgstr "正在恢复默认配置…" - -msgid "Honk core and LuCI can be upgraded here without leaving the management page. The package index refreshes in the background." -msgstr "无需离开管理页面即可升级 Honk 核心与 LuCI。软件包索引会在后台刷新。" - -msgid "Export a backup archive, import one from your computer, or restore Honk to its default configuration." -msgstr "导出备份归档、从电脑导入,或将 Honk 恢复为默认配置。" - -msgid "Package updates, config backup and system recovery for the Honk installation." -msgstr "Honk 安装的软件包更新、配置备份与系统恢复。" - -msgid "Geo data" -msgstr "Geo 数据" - -msgid "GeoIP and GeoSite routing data can be updated here. Updates run in the background and reload Honk when done." -msgstr "可在此更新 GeoIP 与 GeoSite 路由数据。更新在后台运行,完成后自动重载 Honk。" - -msgid "missing — click Update to fetch" -msgstr "缺失 — 点击更新获取" - -msgid "Custom" -msgstr "自定义" - -msgid "Disabled" -msgstr "关闭" - -msgid "Daily" -msgstr "每天" +msgid "Warnings and errors" +msgstr "警告和错误" msgid "Weekly" msgstr "每周" -msgid "Save" -msgstr "保存" - -msgid "Source" -msgstr "数据源" - -msgid "Auto-update" -msgstr "自动更新" - -msgid "GeoIP URL" -msgstr "GeoIP 地址" - -msgid "GeoSite URL" -msgstr "GeoSite 地址" - -msgid "Data source and auto-update" -msgstr "数据源与自动更新" - -msgid "Geo data source saved." -msgstr "Geo 数据源已保存。" - -msgid "Save failed" -msgstr "保存失败" - -msgid "Import" -msgstr "导入" - -msgid "Update" -msgstr "更新" - -msgid "Import nodes" -msgstr "导入节点" - -msgid "Import the pasted nodes? Any running Honk service will be stopped during the change and its previous running state will be restored afterward." -msgstr "导入粘贴的节点?导入过程中会停止正在运行的 Honk 服务,完成后恢复之前的状态。" - -msgid "Preparing import…" -msgstr "正在准备导入…" - -msgid "Applying %d imported nodes…" -msgstr "正在应用 %d 个导入节点…" - -msgid "Imported %d nodes." -msgstr "已导入 %d 个节点。" - -msgid "Import failed." -msgstr "导入失败。" - -msgid "Paste nodes" -msgstr "粘贴节点" - -msgid "Clash YAML, Surge INI, Base64 or share links" -msgstr "Clash YAML、Surge INI、Base64 或分享链接" - -msgid "Paste subscription content or share links; they are parsed locally into nodes. A parsed request is limited to 1 MiB." -msgstr "粘贴订阅内容或分享链接,本地解析为节点。解析后的请求限制为 1 MiB。" - -msgid "Paste subscription content or share links." -msgstr "请粘贴订阅内容或分享链接。" - -msgid "The pasted content is too large. Paste a smaller selection." -msgstr "粘贴内容过大,请粘贴较小的内容。" - -msgid "Parsing pasted content…" -msgstr "正在解析粘贴内容…" - -msgid "No importable nodes were found in the pasted content." -msgstr "未在粘贴内容中找到可导入的节点。" - -msgid "Parsed nodes exceed the 1 MiB per request limit. Paste them in smaller batches." -msgstr "解析出的节点超过每请求 1 MiB 限制,请分批粘贴。" - -msgid "Failed to parse the pasted content." -msgstr "解析粘贴内容失败。" - -msgid "YAML parser failed to load" -msgstr "YAML 解析器加载失败" - -msgid "Direct configuration" -msgstr "直接配置" - -msgid "Honk reads its nodes, subscriptions and routing from /etc/honk/config.dae." -msgstr "Honk 从 /etc/honk/config.dae 读取节点、订阅和路由。" - -msgid "Add nodes as %s or subscriptions as %s in /etc/honk/config.dae, then reload Honk" -msgstr "在 /etc/honk/config.dae 中以 %s 添加节点或以 %s 添加订阅,然后重载 Honk" - -msgid "The subscription returned no usable nodes. The configuration was not changed." -msgstr "订阅没有返回可用节点,配置未改动。" - -msgid "Subscription saved with usable nodes." -msgstr "订阅已保存,检测到可用节点。" - -msgid "Nodes saved." -msgstr "节点已保存。" - -msgid "Honk could not restart. The previous configuration and service were restored." -msgstr "Honk 未能重启,已恢复原配置和服务。" - -msgid "Honk could not restart or restore the previous service. Check its status and backup." -msgstr "Honk 未能重启,也未能恢复原服务,请检查服务状态和备份。" - -msgid "Configuration imported and Honk reloaded." -msgstr "配置已导入并重载 Honk。" - -msgid "Name, e.g. MyAirport" -msgstr "名称,例如 MyAirport" - -msgid "A valid HTTP or HTTPS URL is required." -msgstr "请输入有效的 HTTP 或 HTTPS 链接。" - -msgid "Adding subscription…" -msgstr "正在添加订阅…" - -msgid "Parsing nodes…" -msgstr "正在解析节点…" - -msgid "No nodes could be parsed from the input." -msgstr "无法从输入中解析出节点。" - -msgid "Importing %d node(s)…" -msgstr "正在导入 %d 个节点…" - -msgid "%d node(s) imported." -msgstr "已导入 %d 个节点。" - -msgid "Paste nodes or subscription content first." -msgstr "请先粘贴节点或订阅内容。" - -msgid "The pasted content is too large." -msgstr "粘贴内容过大。" - -msgid "Choose a .dae file first." -msgstr "请先选择 .dae 文件。" - -msgid "The dae file exceeds the 2 MiB limit." -msgstr "dae 文件超过 2 MiB 限制。" - -msgid "Uploading configuration…" -msgstr "正在上传配置…" - -msgid "Importing configuration…" -msgstr "正在导入配置…" - -msgid "Could not import the configuration." -msgstr "无法导入配置。" - -msgid "Import a native dae config.dae (e.g. exported from Tower). Its node block is merged into /etc/honk/config.dae." -msgstr "导入原生 dae config.dae(例如 Tower 导出的)。其 node 块会合并到 /etc/honk/config.dae。" - -msgid "After the first valid subscription or node import, the fresh-install policy sends local and Chinese traffic direct and other traffic through the proxy group. You can adjust it in Doona." -msgstr "首次成功导入订阅或节点后,新装默认策略让局域网和国内流量直连,其余走代理组。之后可在 Doona 调整。" - -msgid "Could not add the subscription." -msgstr "无法添加订阅。" - -msgid "Could not import nodes." -msgstr "无法导入节点。" - -msgid "Could not write the configuration file." -msgstr "无法写入配置文件。" - -msgid "The configuration file is missing." -msgstr "配置文件不存在。" - -msgid "Enter a name and subscription URL. Honk checks for usable nodes before saving." -msgstr "输入名称和订阅链接,Honk 会先检查是否有可用节点,再保存配置。" - -msgid "Invalid %s: must be an http(s) URL." -msgstr "无效的 %s:必须是 http(s) 链接。" - -msgid "Name is required." -msgstr "请输入名称。" - -msgid "Paste Clash YAML, Surge INI, Base64 or share links." -msgstr "粘贴 Clash YAML、Surge INI、Base64 或分享链接。" - -msgid "Paste Clash YAML, Surge INI, Base64 or share links; they are parsed locally into nodes and written to /etc/honk/config.dae." -msgstr "粘贴 Clash YAML、Surge INI、Base64 或分享链接,会在本地解析为节点并写入 /etc/honk/config.dae。" - -msgid "Open Clash panel" -msgstr "打开 Clash 面板" - -msgid "Clash API" -msgstr "Clash API" - -msgid "Off" -msgstr "关闭" - -msgid "Zashboard is still downloading or unavailable." -msgstr "Zashboard 仍在下载或暂不可用。" - -msgid "The Clash API listener is unavailable. Check the service logs." -msgstr "Clash API 监听不可用,请检查服务日志。" - -msgid "Enable Clash API" -msgstr "启用 Clash API" - -msgid "Clash API port" -msgstr "Clash API 端口" - -msgid "Clash API secret" -msgstr "Clash API 密钥" - -msgid "A secret is set. Leave blank to keep it." -msgstr "已设置密钥,留空则保持原值。" - -msgid "Required when enabling. Use 8–128 letters, numbers, dots, underscores or hyphens." -msgstr "启用时必填,使用 6~128 位英文字母、数字、点、下划线或短横线。" - -msgid "Use your existing HTTPS reverse proxy for external access. Enter the secret in Zashboard when connecting." -msgstr "外网访问请使用现有 HTTPS 反向代理,并在 Zashboard 中填写密钥。" - -msgid "Choose a different Clash API port and enter a valid secret." -msgstr "请选择不同的 Clash API 端口并填写有效密钥。" - -msgid "A secret is set. Leave blank to keep it. Use your existing HTTPS reverse proxy for external access." -msgstr "已设置密钥,留空则保持原值;外网访问请使用现有 HTTPS 反向代理。" - -msgid "A secret is required when enabling Clash API. Use 8–128 letters, numbers, dots, underscores or hyphens." -msgstr "启用 Clash API 时必须填写密钥,使用 6~128 位英文字母、数字、点、下划线或短横线。" - -msgid "The Clash API port is already in use." -msgstr "Clash API 端口已被占用。" - -msgid "Enter a different Clash API port and a secret of 6–128 letters, numbers, dots, underscores or hyphens." -msgstr "请设置不同的 Clash API 端口,以及 6~128 位英文字母、数字、点、下划线或短横线组成的密钥。" - -msgid "Clash API port must be between 1024 and 65535." -msgstr "Clash API 端口需要在 1024~65535 之间。" - -msgid "The Clash API port must differ from the listen port." -msgstr "Clash API 端口不能和监听端口相同。" - -msgid "A secret is required when enabling Clash API." -msgstr "启用 Clash API 时需要设置密钥。" - -msgid "The secret must be 6-128 letters, numbers, dots, underscores or hyphens." -msgstr "密钥需要 6~128 位,只能包含字母、数字、点、下划线或短横线。" - -msgid "Check configuration" -msgstr "校验配置" - -msgid "Checking the configuration…" -msgstr "正在校验配置…" - -msgid "Clash API %s" -msgstr "Clash API %s" - -msgid "Configuration applied and Honk is running." -msgstr "配置已生效,Honk 已启动。" - -msgid "Configuration is valid." -msgstr "配置校验通过。" - -msgid "Configuration is valid: %s" -msgstr "配置校验通过:%s" - -msgid "Configuration validation failed." -msgstr "配置校验失败。" - -msgid "Could not load the configuration file." -msgstr "无法载入配置文件。" - -msgid "Could not read the saved configuration." -msgstr "无法读取已保存的配置。" - -msgid "Could not restore managed mode." -msgstr "无法恢复托管模式。" - -msgid "Could not save the configuration." -msgstr "无法保存配置。" - -msgid "Current configuration loaded from /etc/honk/config.dae." -msgstr "已载入 /etc/honk/config.dae 的当前配置。" - -msgid "Discard the unsaved editor changes?" -msgstr "放弃编辑器中未保存的修改?" - -msgid "Doona %s" -msgstr "Doona 面板 %s" - -msgid "Full dae configuration" -msgstr "完整 dae 配置" - -msgid "LAN %s" -msgstr "局域网接口 %s" - -msgid "Line %d" -msgstr "第 %d 行" - -msgid "Managed mode restored." -msgstr "已恢复托管模式。" - -msgid "Managed mode: the subscription and paste-nodes tools are active. Saving here switches Honk to manual mode." -msgstr "托管模式:订阅与粘贴节点工具可用。在这里保存会切换为手动模式。" - -msgid "Reading %s…" -msgstr "正在读取 %s…" - -msgid "Reload from disk" -msgstr "从磁盘重新载入" - -msgid "Restore managed mode" -msgstr "恢复托管模式" - -msgid "Restore the managed configuration and return to the subscription tools? Honk restarts if it is running." -msgstr "恢复之前保存的托管配置并回到订阅工具?若 Honk 正在运行会重启。" - -msgid "Restoring the managed configuration…" -msgstr "正在恢复托管配置…" - -msgid "Save and apply" -msgstr "保存并应用" - -msgid "The editor is empty." -msgstr "编辑器内容为空。" - -msgid "The file is empty." -msgstr "文件内容为空。" - -msgid "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." -msgstr "在这里编辑或粘贴完整的 dae 配置。「校验配置」只做语法检查;「保存并应用」会写入 /etc/honk/config.dae 并重启 Honk,启动失败自动回滚。" - -msgid "Configuration applied." -msgstr "配置已应用。" - -msgid "Configuration was written, but Honk is not running: %s" -msgstr "配置已写入,但 Honk 没有运行:%s" - -msgid "Could not apply the configuration." -msgstr "无法应用配置。" - -msgid "The file opens Doona on port %s, but LuCI opens it on port %s, so the Doona button will not connect. Use 127.0.0.1:%s." -msgstr "配置把 Doona 面板开在 %s 端口,而 LuCI 按 %s 端口打开,Doona 按钮会连不上。建议写成 127.0.0.1:%s。" - -msgid "These settings will most likely stop Honk on this router:" -msgstr "下面几处设置很可能让 Honk 在本机起不来:" - -msgid "Writing the configuration…" -msgstr "正在写入配置…" - -msgid "lan_interface %s does not exist on this router (available: %s), so Honk would not see the LAN." -msgstr "配置里的 lan_interface %s 在本机不存在(可用:%s),Honk 会抓不到局域网流量。" - -msgid "native_api.ui is embedded, but this firmware builds Honk without the native-ui feature, so the core exits on startup. Use /usr/share/doona instead." -msgstr "配置里的 native_api.ui 是 embedded,但本固件编译的内核没有 native-ui 特性,内核会直接退出。请改成 /usr/share/doona。" - -msgid "unknown reason" -msgstr "原因未知" - -msgid "Honk could not start with this configuration. The previous file was restored." -msgstr "Honk 用这份配置起不来,已自动恢复上一版配置。" - -msgid "Startup failed and the automatic rollback did not finish. The snapshot was kept; run system recovery." -msgstr "启动失败且自动回滚未完成,快照已保留,请到「维护」执行系统恢复。" - -msgid "The configuration changed during the operation. Nothing was applied." -msgstr "操作期间配置被其他进程改动,本次没有写入。" - -msgid "Could not write the configuration. The snapshot was kept." -msgstr "无法写入配置,快照已保留。" - -msgid "Could not confirm that Honk stopped. The operation was aborted and the snapshot was kept." -msgstr "无法确认 Honk 已停止,操作已中止,快照已保留。" - -msgid "Manual mode is active. Restore managed mode before using the subscription tools." -msgstr "当前是手动模式,请先恢复托管模式再使用订阅工具。" - -msgid "There is no saved managed configuration to restore." -msgstr "没有可恢复的托管配置快照。" - -msgid "Could not read the list of network interfaces." -msgstr "无法读取当前网络接口列表。" - -msgid "Manual mode: the file below owns nodes, groups, routing and DNS." -msgstr "手动模式:下面的文件决定节点、分组、路由与 DNS。" - -msgid "%s imported and Honk is running." -msgstr "%s 已导入,Honk 正在运行。" - -msgid "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." -msgstr "上传一份完整的 dae 文件(含订阅、节点与分流规则),Honk 会直接用它启动:先校验,再写入 /etc/honk/config.dae,然后重启服务。想手工编辑或粘贴请用下面的编辑器。" +msgid "Working…" +msgstr "正在处理……" msgid "Write this configuration to /etc/honk/config.dae and start Honk? The previous file is restored if startup fails." msgstr "将把这份配置写入 /etc/honk/config.dae 并启动 Honk。启动失败会自动恢复原文件,继续?" -msgid "Could not read the configuration file." -msgstr "无法读取配置文件。" +msgid "Writing the configuration…" +msgstr "正在写入配置…" -msgid "Import a configuration" -msgstr "导入配置" +msgid "YAML parser failed to load" +msgstr "YAML 解析器加载失败" -msgid "Import and start" -msgstr "导入并启动" +msgid "Zashboard is still downloading or unavailable." +msgstr "Zashboard 仍在下载或暂不可用。" -msgid "Manual mode: this tool is disabled until you restore managed mode (link at the top)." -msgstr "手动模式下这个工具不可用,点页面顶部的「恢复托管模式」后再用。" +msgid "global settings" +msgstr "全局设置" -msgid "Manual mode is active, so this tool cannot edit the configuration. Select Restore managed mode first." -msgstr "当前是手动模式,这个工具不会改动配置。请先点「恢复托管模式」。" - -msgid "Could not switch back to managed mode. The previous configuration was restored." -msgstr "切回托管模式失败,已恢复上一版配置。" - -msgid "Switching back to managed mode failed and the rollback did not finish. The snapshot was kept." -msgstr "切回托管模式失败且回滚未完成,快照已保留,请到「维护」执行系统恢复。" - -msgid "The saved managed configuration is not a valid regular file." -msgstr "保存的托管配置不是普通文件,无法恢复。" - -msgid "There is no saved draft to apply." -msgstr "没有可应用的已保存草稿。" - -msgid "The saved draft is not a valid regular file." -msgstr "保存的草稿不是普通文件。" - -msgid "Manual mode: these settings belong to config.dae, so they are read-only here. Restore managed mode on the Configuration page to change them." -msgstr "手动模式:这些设置由 config.dae 决定,这里只读。要改的话先到「配置」页点「恢复托管模式」。" - -msgid "Manual mode is active, so these settings cannot be saved. Restore managed mode on the Configuration page first." -msgstr "当前是手动模式,设置无法保存。请先到「配置」页恢复托管模式。" - -msgid "(adapted for this router: %s)" -msgstr "(已按本机适配:%s)" - -msgid "(will adapt for this router: %s)" -msgstr "(将按本机适配:%s)" - -msgid "Saving will adapt for this router: %s" -msgstr "保存时会按本机适配:%s" - -msgid "This configuration needs fixes for this router:" -msgstr "这份配置有几处需要按本机修正:" - -msgid "Apply them in the editor now?" -msgstr "现在就在编辑器里改掉吗?" - -msgid "Adapted in the editor: %s" -msgstr "已在编辑器里适配:%s" - -msgid "Save cancelled." -msgstr "已取消保存。" - -msgid "Loaded %s into the editor." -msgstr "已把 %s 载入编辑器。" - -msgid "These lines will be changed for this router:" -msgstr "这几行会按本机修改:" - -msgid "Needs your attention:" -msgstr "需要你处理:" - -msgid "Choose the LAN interface" -msgstr "选择 LAN 网卡" - -msgid "None of the active interfaces belongs to the configured LAN network. Pick the one Honk should see as LAN:" -msgstr "当前活跃的网卡都不属于 LuCI 里配置的 LAN 网络。请挑一个 Honk 应该当作 LAN 的网卡:" - -msgid "Use this interface" -msgstr "就用这个网卡" - -msgid "lan_interface: cannot tell which interface is the LAN here (candidates: %s); set it yourself" -msgstr "lan_interface:无法判断本机哪块网卡是 LAN(候选:%s),请自行确认" - -msgid "The editor changed while checking. Nothing was applied; check again." -msgstr "检查期间编辑器内容被改动了,没有应用任何修改,请重新检查。" - -msgid "The editor changed while checking. Nothing was applied; import the file again." -msgstr "检查期间编辑器内容被改动了,没有应用任何修改,请重新导入文件。" - -msgid "Import cancelled." -msgstr "已取消导入。" - -msgid "Checking the subscription before saving…" -msgstr "保存前先检查订阅…" - -msgid "Detected %s · %d node(s): %s" -msgstr "识别为 %s · %d 个节点:%s" - -msgid "Detected %s." -msgstr "识别为 %s。" - -msgid "unknown format" -msgstr "未知格式" - -msgid "This core cannot use any of these nodes, so the subscription was not saved." -msgstr "这份订阅里的节点本内核一个都用不了,所以没有保存。" - -msgid "Save the subscription anyway?" -msgstr "仍然保存这份订阅吗?" - -msgid "unsupported" -msgstr "不支持" - -msgid "Choose file" -msgstr "选择文件" - -msgid "No file chosen" -msgstr "未选择任何文件" - -msgid "Honk could not prepare the upload location. Check the upload directory permissions and try again." -msgstr "Honk could not prepare the upload location. Check the upload directory permissions and try again." - -msgid "Honk could not prepare a private configuration upload directory." -msgstr "Honk could not prepare a private configuration upload directory." - -msgid "The temporary configuration upload is unavailable. Try again." -msgstr "The temporary configuration upload is unavailable. Try again." - -msgid "The configuration file exceeds the 2 MiB limit." -msgstr "The configuration file exceeds the 2 MiB limit." - -msgid "已经初始化过了,请用原来的账号登录 Doona。忘记密码的处理方式:先停止 Honk,再执行 honk-core admin reset(只删除管理员记录、重新开放首次设置,不动订阅和规则),然后回到本页重新初始化。" -msgstr "已经初始化过了,请用原来的账号登录 Doona。忘记密码的处理方式:先停止 Honk,再执行 honk-core admin reset(只删除管理员记录、重新开放首次设置,不动订阅和规则),然后回到本页重新初始化。" - -msgid "创建 Doona 管理员失败,请到「维护 → 日志」查看核心日志。" -msgstr "创建 Doona 管理员失败,请到「维护 → 日志」查看核心日志。" - -msgid "用户名只能包含字母、数字、下划线、点、连字符(不超过 64 个字符);密码长度必须是 8 到 128 个字符,可以包含空格。" -msgstr "用户名只能包含字母、数字、下划线、点、连字符(不超过 64 个字符);密码长度必须是 8 到 128 个字符,可以包含空格。" - -msgid "请先停止 Honk 服务再初始化。" -msgstr "请先停止 Honk 服务再初始化。" - -msgid "系统里还留着 dae0 / daens 等运行痕迹,请先停止服务或执行系统恢复。" -msgstr "系统里还留着 dae0 / daens 等运行痕迹,请先停止服务或执行系统恢复。" - -msgid "找不到 /usr/bin/honk-core,请重新安装 honk 包。" -msgstr "找不到 /usr/bin/honk-core,请重新安装 honk 包。" +msgid "groups" +msgstr "分组" msgid "honk-core 已经在运行,请先停止服务。" msgstr "honk-core 已经在运行,请先停止服务。" -msgid "缺少 libubox 组件(/usr/share/libubox/jshn.sh),请检查固件是否完整。" -msgstr "缺少 libubox 组件(/usr/share/libubox/jshn.sh),请检查固件是否完整。" +msgid "included files" +msgstr "被 include 的文件" + +msgid "installed" +msgstr "已安装" + +msgid "lan_interface: cannot tell which interface is the LAN here (candidates: %s); set it yourself" +msgstr "lan_interface:无法判断本机哪块网卡是 LAN(候选:%s),请自行确认" + +msgid "latest" +msgstr "最新" + +msgid "latest version unknown" +msgstr "最新版本未知" + +msgid "luci-app-honk 的软件包配置不完整,请重新安装。" +msgstr "luci-app-honk 的软件包配置不完整,请重新安装。" + +msgid "missing — click Update to fetch" +msgstr "缺失 — 点击更新获取" + +msgid "no recognised sections" +msgstr "未识别到段落" + +msgid "nodes" +msgstr "节点" + +msgid "not installed via package manager" +msgstr "未通过软件包管理器安装" + +msgid "routing" +msgstr "路由" + +msgid "subscriptions" +msgstr "订阅" + +msgid "system.dae 不是普通文件(可能是软链),拒绝操作。" +msgstr "system.dae 不是普通文件(可能是软链),拒绝操作。" + +msgid "unknown format" +msgstr "未知格式" + +msgid "unknown reason" +msgstr "原因未知" + +msgid "unsupported" +msgstr "不支持" + +msgid "up to date" +msgstr "已是最新" + +msgid "上传目录已准备好。" +msgstr "上传目录已准备好。" msgid "临时目录 /tmp 不可写或空间不足。" msgstr "临时目录 /tmp 不可写或空间不足。" +msgid "任务存储不可用(/tmp 空间不足?)。" +msgstr "任务存储不可用(/tmp 空间不足?)。" + +msgid "任务编号不合法。" +msgstr "任务编号不合法。" + +msgid "修复 system.dae 失败且回滚未完成,快照已保留。" +msgstr "修复 system.dae 失败且回滚未完成,快照已保留。" + +msgid "修复 system.dae 失败,已回滚。" +msgstr "修复 system.dae 失败,已回滚。" + +msgid "修复后重启失败且回滚未完成,快照已保留。" +msgstr "修复后重启失败且回滚未完成,快照已保留。" + +msgid "修复后重启失败,已回滚。" +msgstr "修复后重启失败,已回滚。" + +msgid "停止服务失败且回滚未完成,快照已保留。" +msgstr "停止服务失败且回滚未完成,快照已保留。" + +msgid "停止服务失败,已回滚。" +msgstr "停止服务失败,已回滚。" + +msgid "写入 UCI 失败,已恢复原来的监听配置。" +msgstr "写入 UCI 失败,已恢复原来的监听配置。" + +msgid "写入 system.dae 失败。" +msgstr "写入 system.dae 失败。" + +msgid "写入 system.dae 失败且回滚未完成,快照已保留。" +msgstr "写入 system.dae 失败且回滚未完成,快照已保留。" + +msgid "写入 system.dae 失败,已回滚。" +msgstr "写入 system.dae 失败,已回滚。" + +msgid "写入 system.dae 的监听地址失败,请检查文件权限。" +msgstr "写入 system.dae 的监听地址失败,请检查文件权限。" + +msgid "创建 Doona 管理员失败,请到「维护 → 日志」查看核心日志。" +msgstr "创建 Doona 管理员失败,请到「维护 → 日志」查看核心日志。" + +msgid "创建目录失败,请检查磁盘空间与权限。" +msgstr "创建目录失败,请检查磁盘空间与权限。" + +msgid "初始化后登录验证失败,请重试或查看日志。" +msgstr "初始化后登录验证失败,请重试或查看日志。" + +msgid "初始化后的自检没通过,请查看日志。" +msgstr "初始化后的自检没通过,请查看日志。" + +msgid "初始化结果没有落盘(可能磁盘空间不足)。" +msgstr "初始化结果没有落盘(可能磁盘空间不足)。" + +msgid "备份 UCI 配置失败,请检查磁盘空间。" +msgstr "备份 UCI 配置失败,请检查磁盘空间。" + msgid "备份现有 system.dae 失败,请检查磁盘空间。" msgstr "备份现有 system.dae 失败,请检查磁盘空间。" msgid "备份系统配置失败,请检查磁盘空间。" msgstr "备份系统配置失败,请检查磁盘空间。" -msgid "备份 UCI 配置失败,请检查磁盘空间。" -msgstr "备份 UCI 配置失败,请检查磁盘空间。" +msgid "应用设置失败且回滚未完成,快照已保留,请执行系统恢复。" +msgstr "应用设置失败且回滚未完成,快照已保留,请执行系统恢复。" -msgid "写入 system.dae 的监听地址失败,请检查文件权限。" -msgstr "写入 system.dae 的监听地址失败,请检查文件权限。" +msgid "开关类设置取值不合法。" +msgstr "开关类设置取值不合法。" -msgid "/etc/honk/config.dae 不存在或不是普通文件。" -msgstr "/etc/honk/config.dae 不存在或不是普通文件。" +msgid "找不到 /usr/bin/honk-core,请重新安装 honk 包。" +msgstr "找不到 /usr/bin/honk-core,请重新安装 honk 包。" + +msgid "找不到该任务。" +msgstr "找不到该任务。" + +msgid "抓不到这个订阅地址:网络不通、需要代理,或链接已失效。" +msgstr "抓不到这个订阅地址:网络不通、需要代理,或链接已失效。" + +msgid "拿不到任务编号。" +msgstr "拿不到任务编号。" + +msgid "提交的设置数据过大。" +msgstr "提交的设置数据过大。" + +msgid "操作期间 LAN 网络被改动,已中止。" +msgstr "操作期间 LAN 网络被改动,已中止。" msgid "操作期间 config.dae 被其他进程改动,已中止。" msgstr "操作期间 config.dae 被其他进程改动,已中止。" -msgid "luci-app-honk 的软件包配置不完整,请重新安装。" -msgstr "luci-app-honk 的软件包配置不完整,请重新安装。" +msgid "操作期间服务状态发生变化,已中止。" +msgstr "操作期间服务状态发生变化,已中止。" -msgid "配置路径不是 /etc/honk/config.dae,拒绝操作。" -msgstr "配置路径不是 /etc/honk/config.dae,拒绝操作。" +msgid "操作期间设置被改动,已中止。" +msgstr "操作期间设置被改动,已中止。" -msgid "手动模式配置有效。" -msgstr "手动模式配置有效。" +msgid "操作期间配置被改动且重启失败,快照已保留。" +msgstr "操作期间配置被改动且重启失败,快照已保留。" -msgid "手动模式配置无效,请先修正 config.dae。" -msgstr "手动模式配置无效,请先修正 config.dae。" +msgid "日志来源不合法。" +msgstr "日志来源不合法。" -msgid "手动模式的配置缺少 include { system.dae },无法修复托管设置。" -msgstr "手动模式的配置缺少 include { system.dae },无法修复托管设置。" +msgid "日志级别不合法。" +msgstr "日志级别不合法。" -msgid "读不到核心的 API 状态,请查看日志。" -msgstr "读不到核心的 API 状态,请查看日志。" +msgid "日志行数不合法。" +msgstr "日志行数不合法。" -msgid "缺少 Doona 静态文件(/usr/share/doona),请重新安装 luci-app-honk。" -msgstr "缺少 Doona 静态文件(/usr/share/doona),请重新安装 luci-app-honk。" +msgid "更新开机自启链接失败且回滚未完成,快照已保留。" +msgstr "更新开机自启链接失败且回滚未完成,快照已保留。" + +msgid "更新开机自启链接失败,已回滚。" +msgstr "更新开机自启链接失败,已回滚。" + +msgid "未知的接口方法。" +msgstr "未知的接口方法。" + +msgid "未知的操作类型。" +msgstr "未知的操作类型。" + +msgid "测试核心停止失败,已恢复原来的监听配置。" +msgstr "测试核心停止失败,已恢复原来的监听配置。" msgid "测试用核心启动失败:通常是内核缺 BTF(/sys/kernel/btf/vmlinux)或 eBPF 依赖模块。" msgstr "测试用核心启动失败:通常是内核缺 BTF(/sys/kernel/btf/vmlinux)或 eBPF 依赖模块。" @@ -1659,119 +1302,23 @@ msgstr "测试用核心无法正常停止,请稍后重试。" msgid "测试用核心重启失败,请查看日志。" msgstr "测试用核心重启失败,请查看日志。" -msgid "测试核心停止失败,已恢复原来的监听配置。" -msgstr "测试核心停止失败,已恢复原来的监听配置。" - msgid "生成的配置校验不通过,已恢复原来的监听配置。" msgstr "生成的配置校验不通过,已恢复原来的监听配置。" -msgid "写入 UCI 失败,已恢复原来的监听配置。" -msgstr "写入 UCI 失败,已恢复原来的监听配置。" - msgid "用局域网地址访问 API 验证失败(网卡或 IP 可能对不上),已恢复。" msgstr "用局域网地址访问 API 验证失败(网卡或 IP 可能对不上),已恢复。" -msgid "初始化后登录验证失败,请重试或查看日志。" -msgstr "初始化后登录验证失败,请重试或查看日志。" +msgid "用户名只能包含字母、数字、下划线、点、连字符(不超过 64 个字符);密码长度必须是 8 到 128 个字符,可以包含空格。" +msgstr "用户名只能包含字母、数字、下划线、点、连字符(不超过 64 个字符);密码长度必须是 8 到 128 个字符,可以包含空格。" -msgid "初始化结果没有落盘(可能磁盘空间不足)。" -msgstr "初始化结果没有落盘(可能磁盘空间不足)。" +msgid "系统里还留着 dae0 / daens 等运行痕迹,请先停止服务或执行系统恢复。" +msgstr "系统里还留着 dae0 / daens 等运行痕迹,请先停止服务或执行系统恢复。" -msgid "初始化后的自检没通过,请查看日志。" -msgstr "初始化后的自检没通过,请查看日志。" +msgid "缺少 Doona 静态文件(/usr/share/doona),请重新安装 luci-app-honk。" +msgstr "缺少 Doona 静态文件(/usr/share/doona),请重新安装 luci-app-honk。" -msgid "选中的 LAN 网络没有 IPv4 地址。" -msgstr "选中的 LAN 网络没有 IPv4 地址。" - -msgid "创建目录失败,请检查磁盘空间与权限。" -msgstr "创建目录失败,请检查磁盘空间与权限。" - -msgid "手动模式下这些设置由 config.dae 决定,无法保存。" -msgstr "手动模式下这些设置由 config.dae 决定,无法保存。" - -msgid "提交的设置数据过大。" -msgstr "提交的设置数据过大。" - -msgid "设置的请求格式不正确。" -msgstr "设置的请求格式不正确。" - -msgid "开关类设置取值不合法。" -msgstr "开关类设置取值不合法。" - -msgid "日志级别不合法。" -msgstr "日志级别不合法。" - -msgid "日志行数不合法。" -msgstr "日志行数不合法。" - -msgid "日志来源不合法。" -msgstr "日志来源不合法。" - -msgid "未知的操作类型。" -msgstr "未知的操作类型。" - -msgid "未知的接口方法。" -msgstr "未知的接口方法。" - -msgid "操作期间设置被改动,已中止。" -msgstr "操作期间设置被改动,已中止。" - -msgid "操作期间 LAN 网络被改动,已中止。" -msgstr "操作期间 LAN 网络被改动,已中止。" - -msgid "操作期间 LAN 网络被改动,已中止。" -msgstr "操作期间 LAN 网络被改动,已中止。" - -msgid "操作期间服务状态发生变化,已中止。" -msgstr "操作期间服务状态发生变化,已中止。" - -msgid "设置写入失败,已回滚到原值。" -msgstr "设置写入失败,已回滚到原值。" - -msgid "设置写入失败且回滚未完成,快照已保留,请执行系统恢复。" -msgstr "设置写入失败且回滚未完成,快照已保留,请执行系统恢复。" - -msgid "应用设置失败且回滚未完成,快照已保留,请执行系统恢复。" -msgstr "应用设置失败且回滚未完成,快照已保留,请执行系统恢复。" - -msgid "停止服务失败,已回滚。" -msgstr "停止服务失败,已回滚。" - -msgid "停止服务失败且回滚未完成,快照已保留。" -msgstr "停止服务失败且回滚未完成,快照已保留。" - -msgid "写入 system.dae 失败。" -msgstr "写入 system.dae 失败。" - -msgid "写入 system.dae 失败,已回滚。" -msgstr "写入 system.dae 失败,已回滚。" - -msgid "写入 system.dae 失败且回滚未完成,快照已保留。" -msgstr "写入 system.dae 失败且回滚未完成,快照已保留。" - -msgid "修复 system.dae 失败,已回滚。" -msgstr "修复 system.dae 失败,已回滚。" - -msgid "修复 system.dae 失败且回滚未完成,快照已保留。" -msgstr "修复 system.dae 失败且回滚未完成,快照已保留。" - -msgid "system.dae 不是普通文件(可能是软链),拒绝操作。" -msgstr "system.dae 不是普通文件(可能是软链),拒绝操作。" - -msgid "更新开机自启链接失败,已回滚。" -msgstr "更新开机自启链接失败,已回滚。" - -msgid "更新开机自启链接失败且回滚未完成,快照已保留。" -msgstr "更新开机自启链接失败且回滚未完成,快照已保留。" - -msgid "修复后重启失败,已回滚。" -msgstr "修复后重启失败,已回滚。" - -msgid "修复后重启失败且回滚未完成,快照已保留。" -msgstr "修复后重启失败且回滚未完成,快照已保留。" - -msgid "抓不到这个订阅地址:网络不通、需要代理,或链接已失效。" -msgstr "抓不到这个订阅地址:网络不通、需要代理,或链接已失效。" +msgid "缺少 libubox 组件(/usr/share/libubox/jshn.sh),请检查固件是否完整。" +msgstr "缺少 libubox 组件(/usr/share/libubox/jshn.sh),请检查固件是否完整。" msgid "订阅内容是空的。" msgstr "订阅内容是空的。" @@ -1779,137 +1326,53 @@ msgstr "订阅内容是空的。" msgid "订阅内容超过 4 MiB,拒绝处理。" msgstr "订阅内容超过 4 MiB,拒绝处理。" -msgid "配置没有通过静态校验。" -msgstr "配置没有通过静态校验。" +msgid "设置写入失败且回滚未完成,快照已保留,请执行系统恢复。" +msgstr "设置写入失败且回滚未完成,快照已保留,请执行系统恢复。" -msgid "操作期间配置被改动且重启失败,快照已保留。" -msgstr "操作期间配置被改动且重启失败,快照已保留。" +msgid "设置写入失败,已回滚到原值。" +msgstr "设置写入失败,已回滚到原值。" -msgid "任务编号不合法。" -msgstr "任务编号不合法。" +msgid "设置的请求格式不正确。" +msgstr "设置的请求格式不正确。" -msgid "拿不到任务编号。" -msgstr "拿不到任务编号。" - -msgid "找不到该任务。" -msgstr "找不到该任务。" +msgid "请先停止 Honk 服务再初始化。" +msgstr "请先停止 Honk 服务再初始化。" msgid "读不到任务状态。" msgstr "读不到任务状态。" -msgid "任务存储不可用(/tmp 空间不足?)。" -msgstr "任务存储不可用(/tmp 空间不足?)。" +msgid "读不到核心的 API 状态,请查看日志。" +msgstr "读不到核心的 API 状态,请查看日志。" -msgid "上传目录已准备好。" -msgstr "上传目录已准备好。" +msgid "选中的 LAN 网络没有 IPv4 地址。" +msgstr "选中的 LAN 网络没有 IPv4 地址。" + +msgid "配置没有通过静态校验。" +msgstr "配置没有通过静态校验。" + +msgid "配置路径不是 /etc/honk/config.dae,拒绝操作。" +msgstr "配置路径不是 /etc/honk/config.dae,拒绝操作。" msgid "预检失败,请稍后重试。" msgstr "预检失败,请稍后重试。" -msgid "Honk could not read the current configuration." -msgstr "Honk could not read the current configuration." +msgid "DNS" +msgstr "DNS" -msgid "The configuration request is empty or invalid." -msgstr "The configuration request is empty or invalid." +msgid "已经初始化过了,请用原来的账号登录 Doona。忘记密码:到「维护 → 重置管理员」清掉管理员记录(不动订阅和规则),再回到本页重新初始化。" +msgstr "已经初始化过了,请用原来的账号登录 Doona。忘记密码:到「维护 → 重置管理员」清掉管理员记录(不动订阅和规则),再回到本页重新初始化。" -msgid "The configuration did not pass static validation." -msgstr "The configuration did not pass static validation." +msgid "当前配置没有 include { system.dae },这些设置请到「编辑配置」里修改。" +msgstr "当前配置没有 include { system.dae },这些设置请到「编辑配置」里修改。" -msgid "The core validator did not return a result." -msgstr "The core validator did not return a result." +msgid "这些设置由当前配置文件决定,请到「配置 → 编辑配置」里修改。" +msgstr "这些设置由当前配置文件决定,请到「配置 → 编辑配置」里修改。" -msgid "Configuration saved. The running service is unchanged until you apply it." -msgstr "Configuration saved. The running service is unchanged until you apply it." +msgid "配置没有通过校验。" +msgstr "配置没有通过校验。" -msgid "Configuration loaded." -msgstr "Configuration loaded." +msgid "配置通过校验。" +msgstr "配置通过校验。" -msgid "Configuration file loaded into the editor." -msgstr "Configuration file loaded into the editor." - -msgid "Upload location ready." -msgstr "Upload location ready." - -msgid "Temporary upload cleared." -msgstr "Temporary upload cleared." - -msgid "%s (code: %s)" -msgstr "%s(错误码:%s)" - -msgid "Honk reported a system issue: %s" -msgstr "Honk 报告了系统问题:%s" - -msgid "The operation failed." -msgstr "操作失败。" - -msgid "Initialization failed." -msgstr "初始化失败。" - -msgid "System check: %s" -msgstr "系统检查结果:%s" - -msgid "The system check passed. If it keeps failing, check the core log under Maintenance → Logs and report the code above." -msgstr "系统检查通过。若反复失败,请到「维护 → 日志」查看核心日志并把上面的错误码发给维护者。" - -msgid "Username may only contain letters, digits, underscore, dot or hyphen (max 64 characters)." -msgstr "用户名只能包含字母、数字、下划线、点、连字符,最长 64 个字符。" - -msgid "The password must be between 8 and 128 characters." -msgstr "密码长度必须在 8 到 128 个字符之间。" - -msgid "Username: letters, digits, underscore, dot or hyphen (max 64 characters). Password: 8 to 128 characters." -msgstr "用户名:字母、数字、下划线、点、连字符(最长 64 个字符)。密码:8 到 128 个字符。" - -msgid "The system check did not finish. Retry, or check the core log under Maintenance → Logs." -msgstr "系统检查没有跑完,请重试;或到「维护 → 日志」查看核心日志。" - -msgid "The system check did not finish (interface timeout or error). Retry, or check the core log under Maintenance → Logs." -msgstr "系统检查没有跑完(接口超时或出错),请重试;或到「维护 → 日志」查看核心日志。" - -msgid "系统检查没有发现问题。若反复失败,请到「维护 → 日志」查看核心日志并把提示里的错误码发给维护者。" -msgstr "系统检查通过。若反复失败,请到「维护 → 日志」查看核心日志并把上面的错误码发给维护者。" - -msgid "Reset administrator" -msgstr "重置管理员" - -msgid "Clear the Honk administrator account so the first-setup page opens again? Subscriptions, nodes and routing are kept. Honk will be stopped." -msgstr "清掉 Honk 管理员账号、让首次设置页面重新出现?订阅、节点与路由都会保留;Honk 会被停止。" - -msgid "Resetting the administrator…" -msgstr "正在重置管理员…" - -msgid "Administrator cleared. Open the Overview page and run the first setup again." -msgstr "管理员已清空,请到「概览」页重新完成首次设置。" - -msgid "Could not reset the administrator." -msgstr "无法重置管理员。" - -msgid "Administrator reset." -msgstr "管理员已重置。" - -msgid "administrator_reset_failed" -msgstr "重置管理员失败,请查看核心日志。" - -msgid "Core is running; stop Honk before resetting the administrator." -msgstr "核心正在运行,请先停止 Honk 再重置管理员。" - -msgid "The subscription could not be added: the name must be 1-64 characters without single quotes, and the URL must be a valid http(s) link." -msgstr "无法添加订阅:名称 1-64 个字符且不能含单引号;链接必须是有效的 http(s) 地址。" - -msgid "Import config" -msgstr "导入配置" - -msgid "Edit config" -msgstr "编辑配置" - -msgid "Paste Clash YAML, Surge INI, Base64 or share links. They are parsed locally into nodes and appended to /etc/honk/config.dae." -msgstr "粘贴 Clash YAML、Surge INI、Base64 或分享链接,会在本地解析成节点并追加到 /etc/honk/config.dae。" - -msgid "Doona opens after Honk starts." -msgstr "启动 Honk 后即可打开 Doona。" - -msgid "Finish the first setup first: Honk does not start without an administrator." -msgstr "请先完成首次设置:没有管理员账号时 Honk 不会启动。" - -msgid "Not initialized yet. Finish the first setup below, then Honk can be started." -msgstr "还没完成首次设置。先做完下面的首次设置,Honk 才能启动。" +msgid "当前配置不支持快捷追加,请在「编辑配置」中修改。" +msgstr "当前配置不支持快捷追加,请在「编辑配置」中修改。" diff --git a/luci-app-honk/root/usr/libexec/rpcd/honk b/luci-app-honk/root/usr/libexec/rpcd/honk index b67c0873..d9d3fa80 100755 --- a/luci-app-honk/root/usr/libexec/rpcd/honk +++ b/luci-app-honk/root/usr/libexec/rpcd/honk @@ -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" < "$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 ;; diff --git a/luci-app-honk/root/usr/share/luci-app-honk/update-pkg.sh b/luci-app-honk/root/usr/share/luci-app-honk/update-pkg.sh index 057d83fa..9599608f 100755 --- a/luci-app-honk/root/usr/share/luci-app-honk/update-pkg.sh +++ b/luci-app-honk/root/usr/share/luci-app-honk/update-pkg.sh @@ -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 2>&1 & diff --git a/luci-app-honk/root/usr/share/rpcd/acl.d/luci-app-honk.json b/luci-app-honk/root/usr/share/rpcd/acl.d/luci-app-honk.json index d591ec51..cd17410a 100644 --- a/luci-app-honk/root/usr/share/rpcd/acl.d/luci-app-honk.json +++ b/luci-app-honk/root/usr/share/rpcd/acl.d/luci-app-honk.json @@ -81,8 +81,7 @@ "update_apply", "config_read", "config_draft_read", - "config_apply", - "config_use_managed" + "config_apply" ] }, "file": { diff --git a/luci-app-smartdns/Makefile b/luci-app-smartdns/Makefile index e804577c..b9ddd32b 100644 --- a/luci-app-smartdns/Makefile +++ b/luci-app-smartdns/Makefile @@ -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 diff --git a/luci-app-smartdns/po/fa/smartdns.po b/luci-app-smartdns/po/fa/smartdns.po index cdf0ccf7..bd5b8100 100644 --- a/luci-app-smartdns/po/fa/smartdns.po +++ b/luci-app-smartdns/po/fa/smartdns.po @@ -1,83 +1,1255 @@ msgid "" -msgstr "Content-Type: text/plain; charset=UTF-8\n" +msgstr "" +"Project-Id-Version: luci-app-smartdns\n" +"POT-Creation-Date: 2025-11-13 01:05+0000\n" +"PO-Revision-Date: 2026-11-02 13:17+0330\n" +"Last-Translator: ChatGPT\n" +"Language-Team: Persian\n" +"Language: fa_IR\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: ChatGPT\n" +"X-Poedit-Basepath: ../../htdocs/luci-static/resources/view/smartdns\n" +"X-Poedit-SearchPath-0: .\n" -msgid "SmartDNS" -msgstr "اسمارت‌دی‌ان‌اس (SmartDNS)" +#: log.js:80 +msgid "Loading..." +msgstr "در حال بارگذاری..." -msgid "Settings" -msgstr "تنظیمات" +#: log.js:82 smartdns.js:146 +msgid "Collecting data ..." +msgstr "در حال جمع‌آوری داده‌ها..." -msgid "General Settings" -msgstr "تنظیمات عمومی" +#: log.js:92 +msgid "Clear Logs..." +msgstr "پاک‌سازی گزارش‌ها..." -msgid "Advanced Settings" -msgstr "تنظیمات پیشرفته" +#: log.js:95 +msgid "Logs cleared successfully!" +msgstr "گزارش‌ها با موفقیت پاک شدند!" -msgid "Second Server Settings" -msgstr "تنظیمات سرور دوم" +#: log.js:98 log.js:108 log.js:112 +msgid "Clear Logs" +msgstr "پاک‌سازی گزارش‌ها" -msgid "DNS64 Server Settings" -msgstr "تنظیمات سرور DNS64" +#: log.js:101 log.js:119 +msgid "Log is clean." +msgstr "گزارشی برای نمایش وجود ندارد." -msgid "Download Files Setting" -msgstr "تنظیمات دریافت فایل‌ها" +#: log.js:105 +msgid "Failed to clear log." +msgstr "پاک‌سازی گزارش ناموفق بود." -msgid "Proxy Server Settings" -msgstr "تنظیمات سرور پروکسی" +#: log.js:127 +msgid "Log file does not exist." +msgstr "فایل گزارش وجود ندارد." -msgid "Custom Settings" -msgstr "تنظیمات سفارشی" +#: log.js:129 +#, javascript-format +msgid "Unknown error: %s" +msgstr "خطای ناشناخته: %s" -msgid "Enable" -msgstr "فعال‌سازی" +#: log.js:142 +msgid "Back SmartDNS" +msgstr "بازگشت به SmartDNS" -msgid "Enable or disable smartdns server" -msgstr "فعال یا غیرفعال کردن سرویس SmartDNS" - -msgid "Server Name" -msgstr "نام سرور" - -msgid "Local Port" -msgstr "پورت محلی" - -msgid "Automatically Set Dnsmasq" -msgstr "تنظیم خودکار Dnsmasq" - -msgid "Automatically set as upstream of dnsmasq when port changes." -msgstr "تنظیم خودکار به عنوان سرور بالادست Dnsmasq هنگام تغییر پورت." - -msgid "Speed Check Mode" -msgstr "حالت بررسی سرعت (Speed Check)" - -msgid "Proxy Server" -msgstr "سرور پروکسی" - -msgid "Proxy Server URL, format: [socks5|http]://user:pass@ip:port." -msgstr "آدرس پروکسی سرور به فرمت: [socks5|http]://user:pass@ip:port" - -msgid "Use Proxy" -msgstr "استفاده از پروکسی" - -msgid "Use proxy to connect to upstream DNS server." -msgstr "استفاده از تونل پروکسی برای ارسال درخواست‌های DNS به سرورهای بالادست (ضد جعل و پویزنینگ)." - -msgid "Upstream Servers" -msgstr "سرورهای بالادست (Upstream Servers)" - -msgid "Server Name" -msgstr "نام سرور" - -msgid "Server IP" -msgstr "آدرس IP سرور" - -msgid "Server Port" -msgstr "پورت سرور" - -msgid "Server Type" -msgstr "نوع پروتکل (UDP/TCP/TLS/HTTPS)" +#: log.js:151 +#, javascript-format +msgid "Refresh every %s seconds." +msgstr "تازه‌سازی هر %s ثانیه." +#: smartdns.js:68 msgid "RUNNING" msgstr "در حال اجرا" +#: smartdns.js:74 +msgid "Open the WebUI" +msgstr "باز کردن رابط وب" + +#: smartdns.js:77 msgid "NOT RUNNING" -msgstr "متوقف شده" +msgstr "متوقف" + +#: smartdns.js:79 +msgid "Please check the system logs and check if the configuration is valid." +msgstr "گزارش‌های سیستم را بررسی کنید و از معتبر بودن پیکربندی اطمینان حاصل کنید." + +#: smartdns.js:91 +msgid "Dnsmasq Forwarded To Smartdns Failure" +msgstr "ارسال درخواست‌های Dnsmasq به SmartDNS ناموفق بود" + +#: smartdns.js:120 +msgid "SmartDNS" +msgstr "SmartDNS" + +#: smartdns.js:121 +msgid "SmartDNS Server" +msgstr "سرور SmartDNS" + +#: smartdns.js:122 +msgid "" +"SmartDNS is a local high-performance DNS server, supports finding fastest " +"IP, supports ad filtering, and supports avoiding DNS poisoning." +msgstr "SmartDNS یک سرور DNS محلی و پربازده است که از یافتن سریع‌ترین IP، فیلترکردن تبلیغات و جلوگیری از مسموم‌سازی DNS پشتیبانی می‌کند." + +#: smartdns.js:153 +msgid "Settings" +msgstr "تنظیمات" + +#: smartdns.js:153 smartdns.js:156 smartdns.js:888 +msgid "General Settings" +msgstr "تنظیمات عمومی" + +#: smartdns.js:157 smartdns.js:889 smartdns.js:1074 +msgid "Advanced Settings" +msgstr "تنظیمات پیشرفته" + +#: smartdns.js:158 +msgid "Second Server Settings" +msgstr "تنظیمات سرور دوم" + +#: smartdns.js:159 +msgid "DNS64 Server Settings" +msgstr "تنظیمات سرور DNS64" + +msgid "DDR" +msgstr "DDR" + +#: smartdns.js:160 +msgid "Download Files Setting" +msgstr "تنظیمات دریافت فایل‌ها" + +#: smartdns.js:160 +msgid "" +"Download domain list files for domain-rule and include config files, please " +"refresh the page after download to take effect." +msgstr "فایل‌های فهرست دامنه موردنیاز برای قوانین دامنه و فایل‌های پیکربندی را دریافت کنید. پس از پایان دریافت، برای اعمال تغییرات صفحه را تازه‌سازی کنید." + +#: smartdns.js:161 +msgid "Proxy Server Settings" +msgstr "تنظیمات سرور پراکسی" + +#: smartdns.js:162 +msgid "Custom Settings" +msgstr "تنظیمات سفارشی" + +#: smartdns.js:167 smartdns.js:535 smartdns.js:892 smartdns.js:1077 +#: smartdns.js:1486 smartdns.js:1677 +msgid "Enable" +msgstr "فعال‌سازی" + +#: smartdns.js:167 +msgid "Enable or disable smartdns server" +msgstr "فعال یا غیرفعال کردن سرویس SmartDNS" + +#: smartdns.js:172 +msgid "Server Name" +msgstr "نام سرور" + +#: smartdns.js:172 +msgid "Smartdns server name" +msgstr "نام سرور SmartDNS؛ مقدار پیش‌فرض smartdns است و در صورت خالی گذاشتن، نام میزبان استفاده می‌شود." + +#: smartdns.js:178 smartdns.js:541 +msgid "Local Port" +msgstr "درگاه محلی" + +#: smartdns.js:179 +msgid "" +"Smartdns local server port, smartdns will be automatically set as main dns " +"when the port is 53." +msgstr "درگاه محلی سرویس SmartDNS. اگر شماره درگاه 53 باشد، SmartDNS به‌طور خودکار به‌عنوان DNS اصلی تنظیم می‌شود." + +#: smartdns.js:186 +msgid "Automatically Set Dnsmasq" +msgstr "تنظیم خودکار Dnsmasq" + +#: smartdns.js:186 +msgid "Automatically set as upstream of dnsmasq when port changes." +msgstr "با تغییر درگاه، SmartDNS به‌طور خودکار به‌عنوان سرور بالادستی Dnsmasq تنظیم می‌شود." + +#: smartdns.js:192 +msgid "Enable WebUI" +msgstr "فعال‌سازی رابط وب" + +#: smartdns.js:192 +msgid "Enable or disable smartdns webui plugin." +msgstr "افزونه رابط وب SmartDNS را فعال یا غیرفعال می‌کند." + +#: smartdns.js:196 +msgid "WebUI Port" +msgstr "درگاه رابط وب" + +#: smartdns.js:196 +msgid "WebUI server port." +msgstr "درگاه سرور رابط وب." + +#: smartdns.js:202 +msgid "WebUI Data Dir" +msgstr "مسیر داده‌های رابط وب" + +#: smartdns.js:202 +msgid "Directory for storing the webui database." +msgstr "پوشه‌ای برای ذخیره پایگاه داده رابط وب." + +#: smartdns.js:208 +msgid "WebUI Log Retention" +msgstr "مدت نگهداری گزارش‌های رابط وب" + +#: smartdns.js:208 +msgid "Number of days to retain webui logs." +msgstr "تعداد روزهایی که گزارش‌های رابط وب نگهداری می‌شوند." + +#: smartdns.js:219 smartdns.js:1139 smartdns.js:1309 smartdns.js:1543 +msgid "Speed Check Mode" +msgstr "حالت سنجش سرعت" + +#: smartdns.js:219 smartdns.js:1139 smartdns.js:1309 smartdns.js:1543 +msgid "Smartdns speed check mode." +msgstr "حالت سنجش سرعت در SmartDNS." + +#: smartdns.js:222 smartdns.js:274 smartdns.js:806 smartdns.js:1142 +#: smartdns.js:1312 smartdns.js:1365 smartdns.js:1539 smartdns.js:1547 +msgid "default" +msgstr "پیش‌فرض" + +#: smartdns.js:229 smartdns.js:1149 smartdns.js:1319 smartdns.js:1527 +#: smartdns.js:1554 +msgid "None" +msgstr "هیچ‌کدام" + +#: smartdns.js:250 smartdns.js:1170 smartdns.js:1340 smartdns.js:1575 +msgid "TCP port is empty" +msgstr "درگاه TCP خالی است." + +#: smartdns.js:258 smartdns.js:1178 smartdns.js:1348 smartdns.js:1583 +msgid "TCP SYN port is empty" +msgstr "درگاه TCP SYN خالی است." + +#: smartdns.js:263 smartdns.js:1183 smartdns.js:1353 smartdns.js:1588 +msgid "Speed check mode is invalid." +msgstr "حالت سنجش سرعت نامعتبر است." + +#: smartdns.js:270 +msgid "Response Mode" +msgstr "حالت پاسخ‌دهی" + +#: smartdns.js:271 +msgid "" +"Smartdns response mode, First Ping: return the first ping IP, Fastest IP: " +"return the fastest IP, Fastest Response: return the fastest DNS response." +msgstr "حالت پاسخ‌دهی SmartDNS: «اولین پاسخ پینگ»، نخستین IP دارای پاسخ پینگ را برمی‌گرداند؛ «سریع‌ترین IP»، سریع‌ترین IP را انتخاب می‌کند و ممکن است زمان جست‌وجو را افزایش دهد؛ «سریع‌ترین پاسخ»، نتیجه‌ای را برمی‌گرداند که سریع‌ترین پاسخ DNS را داشته باشد و زمان جست‌وجو را کاهش می‌دهد." + +#: smartdns.js:275 +msgid "First Ping" +msgstr "اولین پاسخ پینگ" + +#: smartdns.js:276 +msgid "Fastest IP" +msgstr "سریع‌ترین IP" + +#: smartdns.js:277 +msgid "Fastest Response" +msgstr "سریع‌ترین پاسخ" + +#: smartdns.js:280 smartdns.js:548 +msgid "TCP Server" +msgstr "سرور TCP" + +#: smartdns.js:280 smartdns.js:548 +msgid "Enable TCP DNS Server" +msgstr "فعال‌سازی سرور DNS روی TCP" + +#: smartdns.js:285 +msgid "DOT Server" +msgstr "سرور DoT" + +#: smartdns.js:285 +msgid "Enable DOT DNS Server" +msgstr "فعال‌سازی سرور DNS روی DoT" + +#: smartdns.js:289 +msgid "DOT Server Port" +msgstr "درگاه سرور DoT" + +#: smartdns.js:289 +msgid "Smartdns DOT server port." +msgstr "شماره درگاه سرور DoT در SmartDNS." + +#: smartdns.js:297 +msgid "DOH Server" +msgstr "سرور DoH" + +#: smartdns.js:297 +msgid "Enable DOH DNS Server" +msgstr "فعال‌سازی سرور DNS روی DoH" + +msgid "Enable Discovery of Designated Resolvers for enabled local DNS services." +msgstr "قابلیت کشف حل‌کننده‌های DNS تعیین‌شده را برای سرویس‌های DNS محلی فعال‌شده، فعال می‌کند." + +#: smartdns.js:301 +msgid "DOH Server Port" +msgstr "درگاه سرور DoH" + +#: smartdns.js:301 +msgid "Smartdns DOH server port." +msgstr "شماره درگاه سرور DoH در SmartDNS." + +#: smartdns.js:308 +msgid "Server Cert" +msgstr "گواهی‌نامه سرور" + +#: smartdns.js:308 +msgid "Server certificate file path." +msgstr "مسیر فایل گواهی‌نامه سرور." + +#: smartdns.js:315 +msgid "Server Cert Key" +msgstr "کلید خصوصی گواهی‌نامه سرور" + +#: smartdns.js:315 +msgid "Server certificate key file path." +msgstr "مسیر فایل کلید خصوصی گواهی‌نامه سرور." + +#: smartdns.js:322 +msgid "Server Cert Key Pass" +msgstr "گذرواژه کلید خصوصی گواهی‌نامه سرور" + +#: smartdns.js:322 +msgid "Server certificate key file password." +msgstr "گذرواژه فایل کلید خصوصی گواهی‌نامه سرور." + +#: smartdns.js:329 +msgid "IPv6 Server" +msgstr "سرور IPv6" + +#: smartdns.js:329 +msgid "Enable IPv6 DNS Server" +msgstr "فعال‌سازی سرور DNS مبتنی بر IPv6" + +#: smartdns.js:334 +msgid "Bind Device" +msgstr "محدودسازی به رابط شبکه" + +#: smartdns.js:334 +msgid "Listen only on the specified interfaces." +msgstr "سرور فقط روی رابط‌های شبکه مشخص‌شده درخواست‌ها را دریافت می‌کند." + +#: smartdns.js:339 +msgid "Bind Device Name" +msgstr "نام رابط شبکه" + +#: smartdns.js:339 +msgid "Name of device name listen on." +msgstr "نام رابط شبکه‌ای که سرور روی آن به درخواست‌ها گوش می‌دهد." + +#: smartdns.js:345 smartdns.js:1190 smartdns.js:1360 smartdns.js:1534 +msgid "Dual-stack IP Selection" +msgstr "انتخاب IP در حالت دوپشته" + +#: smartdns.js:346 smartdns.js:1191 smartdns.js:1361 smartdns.js:1535 +msgid "Enable IP selection between IPv4 and IPv6" +msgstr "انتخاب IP بین IPv4 و IPv6 را فعال می‌کند." + +#: smartdns.js:351 +msgid "Domain prefetch" +msgstr "پیش‌دریافت دامنه" + +#: smartdns.js:352 +msgid "Enable domain prefetch, accelerate domain response speed." +msgstr "پیش‌دریافت دامنه را فعال می‌کند تا سرعت پاسخ‌گویی به درخواست‌های دامنه افزایش یابد." + +#: smartdns.js:357 +msgid "Serve expired" +msgstr "ارائه پاسخ‌های منقضی‌شده از حافظه نهان" + +#: smartdns.js:358 +msgid "" +"Attempts to serve old responses from cache with a TTL of 0 in the response " +"without waiting for the actual resolution to finish." +msgstr "برای بهبود کارایی، هنگام دریافت درخواست تلاش می‌کند پاسخ‌های قدیمی موجود در حافظه نهان را بدون انتظار برای پایان فرایند حل نام، با TTL برابر صفر ارائه کند." + +#: smartdns.js:363 +msgid "Cache Size" +msgstr "اندازه حافظه نهان" + +#: smartdns.js:363 +msgid "DNS domain result cache size" +msgstr "ظرفیت حافظه نهان نتایج DNS. با تنظیم مقدار صفر، ذخیره‌سازی در حافظه نهان غیرفعال می‌شود." + +#: smartdns.js:367 +msgid "Cache Persist" +msgstr "ماندگاری حافظه نهان" + +#: smartdns.js:367 +msgid "Write cache to disk on exit and load on startup." +msgstr "هنگام خروج، حافظه نهان را روی دیسک ذخیره می‌کند و هنگام راه‌اندازی آن را بارگذاری می‌کند." + +#: smartdns.js:372 +msgid "Resolve Local Hostnames" +msgstr "حل نام میزبان‌های محلی" + +#: smartdns.js:372 +msgid "Resolve local hostnames by reading Dnsmasq or odhcpd lease files." +msgstr "با خواندن فایل‌های اجاره‌نامه Dnsmasq یا odhcpd، نام میزبان‌های محلی را حل می‌کند." + +#: smartdns.js:377 +msgid "mDNS Lookup" +msgstr "جست‌وجوی mDNS" + +#: smartdns.js:377 +msgid "Resolve local network hostname via mDNS protocol." +msgstr "نام میزبان‌های شبکه محلی را با پروتکل mDNS حل می‌کند." + +#: smartdns.js:382 smartdns.js:600 smartdns.js:1196 smartdns.js:1369 +#: smartdns.js:1594 +msgid "Force AAAA SOA" +msgstr "بازگرداندن SOA برای درخواست‌های AAAA" + +#: smartdns.js:382 smartdns.js:600 smartdns.js:1196 smartdns.js:1369 +#: smartdns.js:1594 +msgid "Force AAAA SOA." +msgstr "برای درخواست‌های رکورد AAAA، پاسخ SOA برگردانده می‌شود." + +#: smartdns.js:387 smartdns.js:605 smartdns.js:1201 +msgid "Force HTTPS SOA" +msgstr "بازگرداندن SOA برای درخواست‌های HTTPS" + +#: smartdns.js:387 smartdns.js:605 smartdns.js:1201 +msgid "Force HTTPS SOA." +msgstr "برای درخواست‌های رکورد HTTPS، پاسخ SOA برگردانده می‌شود." + +#: smartdns.js:392 smartdns.js:613 smartdns.js:1206 smartdns.js:1373 +#: smartdns.js:1600 +msgid "IPset Name" +msgstr "نام مجموعه IPset" + +#: smartdns.js:392 smartdns.js:613 smartdns.js:1206 smartdns.js:1373 +#: smartdns.js:1600 +msgid "IPset name." +msgstr "نام مجموعه IPset." + +#: smartdns.js:404 smartdns.js:425 smartdns.js:625 smartdns.js:1218 +#: smartdns.js:1385 +msgid "ipset name format error, format: [#[4|6]:]ipsetname" +msgstr "قالب نام IPset نامعتبر است. قالب صحیح: [#[4|6]:]ipsetname" + +#: smartdns.js:412 +msgid "No Speed IPset Name" +msgstr "نام IPset در صورت ناموفق‌بودن سنجش سرعت" + +#: smartdns.js:413 +msgid "Ipset name, Add domain result to ipset when speed check fails." +msgstr "نام IPset؛ اگر سنجش سرعت ناموفق باشد، نتیجه IP مربوط به دامنه به این مجموعه IPset اضافه می‌شود." + +#: smartdns.js:433 smartdns.js:632 smartdns.js:1226 smartdns.js:1392 +#: smartdns.js:1606 +msgid "NFTset Name" +msgstr "نام مجموعه NFTset" + +#: smartdns.js:433 smartdns.js:632 smartdns.js:1226 smartdns.js:1392 +#: smartdns.js:1606 +msgid "NFTset name, format: [#[4|6]:[family#table#set]]" +msgstr "نام NFTset. قالب: [#[4|6]:[family#table#set]]" + +#: smartdns.js:445 smartdns.js:466 smartdns.js:644 smartdns.js:1238 +#: smartdns.js:1404 smartdns.js:1619 +msgid "NFTset name format error, format: [#[4|6]:[family#table#set]]" +msgstr "قالب نام NFTset نامعتبر است. قالب صحیح: [#[4|6]:[family#table#set]]" + +#: smartdns.js:453 +msgid "No Speed NFTset Name" +msgstr "نام NFTset در صورت ناموفق‌بودن سنجش سرعت" + +#: smartdns.js:454 +msgid "" +"Nftset name, Add domain result to nftset when speed check fails, format: " +"[#[4|6]:[family#table#set]]" +msgstr "نام NFTset؛ اگر سنجش سرعت ناموفق باشد، نتیجه IP مربوط به دامنه به این مجموعه NFTset اضافه می‌شود. قالب: [#[4|6]:[family#table#set]]" + +#: smartdns.js:474 +msgid "Domain TTL" +msgstr "TTL دامنه" + +#: smartdns.js:474 +msgid "TTL for all domain result." +msgstr "مقدار TTL برای تمام نتایج دامنه." + +#: smartdns.js:478 +msgid "Domain TTL Min" +msgstr "حداقل TTL دامنه" + +#: smartdns.js:479 +msgid "Minimum TTL for all domain result." +msgstr "حداقل مقدار TTL برای تمام نتایج دامنه." + +#: smartdns.js:486 +msgid "Domain TTL Max" +msgstr "حداکثر TTL دامنه" + +#: smartdns.js:487 +msgid "Maximum TTL for all domain result." +msgstr "حداکثر مقدار TTL برای تمام نتایج دامنه." + +#: smartdns.js:491 +msgid "Reply Domain TTL Max" +msgstr "حداکثر TTL دامنه در پاسخ" + +#: smartdns.js:492 +msgid "Reply maximum TTL for all domain result." +msgstr "حداکثر مقدار TTL که برای تمام نتایج دامنه به کلاینت بازگردانده می‌شود." + +#: smartdns.js:496 smartdns.js:652 smartdns.js:1060 +msgid "Additional Server Args" +msgstr "پارامترهای اضافی سرور" + +#: smartdns.js:497 smartdns.js:653 +msgid "" +"Additional server args, refer to the help description of the bind option." +msgstr "پارامترهای اضافی سرور؛ برای اطلاعات بیشتر، توضیحات راهنمای گزینه bind را ببینید." + +#: smartdns.js:503 smartdns.js:1247 +msgid "Include Config Files
/etc/smartdns/conf.d" +msgstr "افزودن فایل‌های پیکربندی
/etc/smartdns/conf.d" + +#: smartdns.js:504 smartdns.js:1248 +msgid "" +"Include other config files from /etc/smartdns/conf.d or custom path, can be " +"downloaded from the download page." +msgstr "فایل‌های پیکربندی دیگر را از مسیر /etc/smartdns/conf.d یا یک مسیر سفارشی اضافه می‌کند. امکان تنظیم دریافت خودکار این فایل‌ها از صفحه دریافت فایل‌ها وجود دارد." + +#: smartdns.js:517 +msgid "Hosts File" +msgstr "فایل Hosts" + +#: smartdns.js:517 +msgid "Include hosts file." +msgstr "فایل hosts را بارگذاری و در پیکربندی لحاظ می‌کند." + +#: smartdns.js:536 +msgid "Enable or disable second DNS server." +msgstr "سرور DNS دوم را فعال یا غیرفعال می‌کند." + +#: smartdns.js:541 +msgid "Smartdns local server port" +msgstr "درگاه محلی سرور SmartDNS" + +#: smartdns.js:553 smartdns.js:928 smartdns.js:1114 smartdns.js:1284 +#: smartdns.js:1494 +msgid "Server Group" +msgstr "گروه سرورها" + +#: smartdns.js:554 +msgid "Query DNS through specific dns server group, such as office, home." +msgstr "درخواست‌های DNS را از طریق یک گروه سرور مشخص، مانند office یا home، ارسال می‌کند." + +#: smartdns.js:560 +msgid "Skip Speed Check" +msgstr "رد کردن سنجش سرعت" + +#: smartdns.js:561 +msgid "Do not check speed." +msgstr "سنجش سرعت انجام نشود." + +#: smartdns.js:566 +msgid "Skip Address Rules" +msgstr "نادیده‌گرفتن قوانین address" + +#: smartdns.js:567 +msgid "Skip address rules." +msgstr "قوانین address نادیده گرفته شوند." + +#: smartdns.js:572 +msgid "Skip Nameserver Rule" +msgstr "نادیده‌گرفتن قوانین Nameserver" + +#: smartdns.js:573 +msgid "Skip nameserver rules." +msgstr "قوانین nameserver نادیده گرفته شوند." + +#: smartdns.js:578 +msgid "Skip Ipset Rule" +msgstr "نادیده‌گرفتن قوانین ipset" + +#: smartdns.js:579 +msgid "Skip ipset rules." +msgstr "قوانین ipset نادیده گرفته شوند." + +#: smartdns.js:584 +msgid "Skip SOA Address Rule" +msgstr "نادیده‌گرفتن قوانین SOA مربوط به address" + +#: smartdns.js:585 +msgid "Skip SOA address rules." +msgstr "قوانین SOA مربوط به address نادیده گرفته شوند." + +#: smartdns.js:589 +msgid "Skip Dualstack Selection" +msgstr "رد کردن انتخاب دوپشته" + +#: smartdns.js:590 +msgid "Skip Dualstack Selection." +msgstr "انتخاب در حالت دوپشته نادیده گرفته شود." + +#: smartdns.js:595 +msgid "Skip Cache" +msgstr "نادیده‌گرفتن حافظه نهان" + +#: smartdns.js:595 +msgid "Skip Cache." +msgstr "حافظه نهان نادیده گرفته شود." + +#: smartdns.js:609 +msgid "Skip IP Alias" +msgstr "نادیده‌گرفتن نام مستعار IP" + +#: smartdns.js:660 +msgid "DNS64" +msgstr "DNS64" + +#: smartdns.js:668 +msgid "Enable Auto Update" +msgstr "فعال‌سازی به‌روزرسانی خودکار" + +#: smartdns.js:668 +msgid "Enable daily (weekly) auto update." +msgstr "به‌روزرسانی خودکار روزانه یا هفتگی را فعال می‌کند." + +#: smartdns.js:673 +msgid "Update Time (Every Week)" +msgstr "زمان به‌روزرسانی (هفتگی)" + +#: smartdns.js:674 +msgid "Every Day" +msgstr "هر روز" + +#: smartdns.js:675 +msgid "Every Monday" +msgstr "هر دوشنبه" + +#: smartdns.js:676 +msgid "Every Tuesday" +msgstr "هر سه‌شنبه" + +#: smartdns.js:677 +msgid "Every Wednesday" +msgstr "هر چهارشنبه" + +#: smartdns.js:678 +msgid "Every Thursday" +msgstr "هر پنج‌شنبه" + +#: smartdns.js:679 +msgid "Every Friday" +msgstr "هر جمعه" + +#: smartdns.js:680 +msgid "Every Saturday" +msgstr "هر شنبه" + +#: smartdns.js:681 +msgid "Every Sunday" +msgstr "هر یکشنبه" + +#: smartdns.js:685 +msgid "Update time (every day)" +msgstr "زمان به‌روزرسانی (روزانه)" + +#: smartdns.js:691 +msgid "Upload Config File" +msgstr "بارگذاری فایل پیکربندی" + +#: smartdns.js:692 +msgid "Upload smartdns config file to /etc/smartdns/conf.d" +msgstr "فایل پیکربندی SmartDNS را در مسیر /etc/smartdns/conf.d بارگذاری می‌کند." + +#: smartdns.js:698 +msgid "Upload Domain List File" +msgstr "بارگذاری فایل فهرست دامنه‌ها" + +#: smartdns.js:699 +msgid "Upload domain list file to /etc/smartdns/domain-set" +msgstr "فایل فهرست دامنه‌ها را در مسیر /etc/smartdns/domain-set بارگذاری می‌کند." + +#: smartdns.js:705 +msgid "Upload File" +msgstr "بارگذاری فایل" + +#: smartdns.js:711 +msgid "Update Files" +msgstr "به‌روزرسانی فایل‌ها" + +#: smartdns.js:720 +msgid "Update" +msgstr "به‌روزرسانی" + +#: smartdns.js:723 +msgid "Download Files" +msgstr "دریافت فایل‌ها" + +#: smartdns.js:724 +msgid "List of files to download." +msgstr "فهرست فایل‌هایی که باید دریافت شوند." + +#: smartdns.js:732 +msgid "File Name" +msgstr "نام فایل" + +#: smartdns.js:736 +msgid "URL" +msgstr "نشانی URL" + +#: smartdns.js:745 +msgid "URL format error, format: http:// or https://" +msgstr "قالب URL نامعتبر است. قالب مجاز باید با http:// یا https:// آغاز شود." + +#: smartdns.js:751 smartdns.js:916 +msgid "type" +msgstr "نوع" + +#: smartdns.js:751 +msgid "File Type" +msgstr "نوع فایل" + +#: smartdns.js:752 +msgid "domain list (/etc/smartdns/domain-set)" +msgstr "فهرست دامنه‌ها (/etc/smartdns/domain-set)" + +#: smartdns.js:753 +msgid "smartdns config (/etc/smartdns/conf.d)" +msgstr "پیکربندی SmartDNS (/etc/smartdns/conf.d)" + +#: smartdns.js:754 +msgid "ip-set file (/etc/smartdns/ip-set)" +msgstr "فایل مجموعه IP (/etc/smartdns/ip-set)" + +#: smartdns.js:755 +msgid "other file (/etc/smartdns/download)" +msgstr "سایر فایل‌ها (/etc/smartdns/download)" + +#: smartdns.js:759 +msgid "Description" +msgstr "توضیحات" + +#: smartdns.js:766 +msgid "Proxy Server" +msgstr "سرور پراکسی" + +#: smartdns.js:766 +msgid "Proxy Server URL, format: [socks5|http]://user:pass@ip:port." +msgstr "نشانی سرور پراکسی. قالب: [socks5|http]://user:pass@ip:port" + +#: smartdns.js:774 +msgid "" +"Proxy server URL format error, format: [socks5|http]://user:pass@ip:port." +msgstr "قالب نشانی سرور پراکسی نامعتبر است. قالب صحیح: [socks5|http]://user:pass@ip:port" + +#: smartdns.js:784 +msgid "smartdns custom settings" +msgstr "تنظیمات سفارشی SmartDNS؛ برای آشنایی با پارامترهای پیکربندی، به راهنمای SmartDNS مراجعه کنید." + +#: smartdns.js:798 +msgid "Generate Coredump" +msgstr "ایجاد فایل Coredump" + +#: smartdns.js:799 +msgid "" +"Generate Coredump file when smartdns crash, coredump file is located at /tmp/" +"smartdns.xxx.core." +msgstr "در صورت ازکارافتادن SmartDNS، فایل Coredump ایجاد می‌شود. این فایل در مسیر /tmp/smartdns.xxx.core قرار می‌گیرد." + +#: smartdns.js:803 +msgid "Log Level" +msgstr "سطح گزارش‌گیری" + +#: smartdns.js:815 +msgid "Log Output Mode" +msgstr "حالت خروجی گزارش" + +#: smartdns.js:818 smartdns.js:859 +msgid "file" +msgstr "فایل" + +#: smartdns.js:819 smartdns.js:860 +msgid "syslog" +msgstr "گزارش سیستمی (syslog)" + +#: smartdns.js:821 +msgid "Log Size" +msgstr "اندازه گزارش" + +#: smartdns.js:826 +msgid "Log Number" +msgstr "تعداد فایل‌های گزارش" + +#: smartdns.js:831 +msgid "Log File" +msgstr "مسیر فایل گزارش" + +#: smartdns.js:836 smartdns.js:844 +msgid "View Log" +msgstr "مشاهده گزارش" + +#: smartdns.js:851 +msgid "Enable Audit Log" +msgstr "فعال‌سازی گزارش ممیزی" + +#: smartdns.js:856 +msgid "Audit Log Output Mode" +msgstr "حالت خروجی گزارش ممیزی" + +#: smartdns.js:863 +msgid "Audit Log Size" +msgstr "اندازه گزارش ممیزی" + +#: smartdns.js:868 +msgid "Audit Log Number" +msgstr "تعداد فایل‌های گزارش ممیزی" + +#: smartdns.js:873 +msgid "Audit Log File" +msgstr "مسیر فایل گزارش ممیزی" + +#: smartdns.js:881 +msgid "Upstream Servers" +msgstr "سرورهای بالادستی" + +#: smartdns.js:882 +msgid "" +"Upstream Servers, support UDP, TCP, TLS, HTTPS, QUIC and HTTP3 protocol. " +"Please configure multiple DNS servers, including multiple foreign DNS " +"servers." +msgstr "سرورهای DNS بالادستی از پروتکل‌های UDP، TCP، TLS، HTTPS، QUIC و HTTP/3 پشتیبانی می‌کنند. چند سرور DNS پیکربندی کنید و در میان آن‌ها چند سرور DNS خارجی نیز قرار دهید." + +#: smartdns.js:898 +msgid "DNS Server Name" +msgstr "نام سرور DNS" + +#: smartdns.js:901 +msgid "ip" +msgstr "IP" + +#: smartdns.js:901 +msgid "DNS Server ip" +msgstr "IP سرور DNS" + +#: smartdns.js:906 +msgid "port" +msgstr "درگاه" + +#: smartdns.js:906 +msgid "DNS Server port" +msgstr "درگاه سرور DNS" + +#: smartdns.js:916 +msgid "DNS Server type" +msgstr "نوع پروتکل سرور DNS" + +#: smartdns.js:918 +msgid "udp" +msgstr "udp" + +#: smartdns.js:919 +msgid "tcp" +msgstr "tcp" + +#: smartdns.js:920 +msgid "tls" +msgstr "tls" + +#: smartdns.js:921 +msgid "https" +msgstr "https" + +#: smartdns.js:922 +msgid "quic" +msgstr "quic" + +#: smartdns.js:923 +msgid "http3" +msgstr "http3" + +#: smartdns.js:928 +msgid "DNS Server group" +msgstr "گروه سرور DNS" + +#: smartdns.js:947 +msgid "Exclude Default Group" +msgstr "خارج‌کردن از گروه پیش‌فرض" + +#: smartdns.js:947 +msgid "Exclude DNS Server from default group." +msgstr "این سرور DNS را از گروه پیش‌فرض خارج می‌کند." + +#: smartdns.js:954 +msgid "IP Blacklist Filtering" +msgstr "فیلترکردن فهرست سیاه IP" + +#: smartdns.js:955 +msgid "Filtering IP with blacklist" +msgstr "IPها را با استفاده از فهرست سیاه فیلتر می‌کند." + +#: smartdns.js:961 +msgid "TLS Hostname Verify" +msgstr "بررسی نام میزبان TLS" + +#: smartdns.js:962 +msgid "Set TLS hostname to verify." +msgstr "نام میزبان TLS را برای بررسی اعتبار گواهی تعیین می‌کند." + +#: smartdns.js:973 +msgid "No check certificate" +msgstr "عدم بررسی گواهی‌نامه" + +#: smartdns.js:974 +msgid "Do not check certificate." +msgstr "اعتبار گواهی‌نامه بررسی نشود." + +#: smartdns.js:984 +msgid "TLS SNI name" +msgstr "نام SNI در TLS" + +#: smartdns.js:985 +msgid "Sets the server name indication for query. '-' for disable SNI name." +msgstr "نام SNI سرور را برای درخواست تعیین می‌کند. برای غیرفعال‌کردن SNI، مقدار '-' را وارد کنید." + +#: smartdns.js:996 +msgid "HTTP Host" +msgstr "میزبان HTTP" + +#: smartdns.js:997 +msgid "" +"Set the HTTP host used for the query. Use this parameter when the host of " +"the URL address is an IP address." +msgstr "مقدار Host در HTTP را برای درخواست تعیین می‌کند. هنگامی از این پارامتر استفاده کنید که میزبان موجود در نشانی URL یک آدرس IP باشد." + +#: smartdns.js:1006 +msgid "TLS SPKI Pinning" +msgstr "تثبیت اثرانگشت SPKI در TLS" + +#: smartdns.js:1007 +msgid "" +"Used to verify the validity of the TLS server, The value is Base64 encoded " +"SPKI fingerprint, leaving blank to indicate that the validity of TLS is not " +"verified." +msgstr "برای بررسی اعتبار سرور TLS استفاده می‌شود. مقدار آن اثرانگشت SPKI کدگذاری‌شده با Base64 است. خالی گذاشتن این فیلد به معنی انجام‌نشدن بررسی اعتبار TLS است." + +#: smartdns.js:1019 +msgid "Marking Packets" +msgstr "علامت‌گذاری بسته‌ها" + +#: smartdns.js:1020 +msgid "Set mark on packets." +msgstr "برای بسته‌ها علامت (mark) تعیین می‌کند." + +#: smartdns.js:1027 +msgid "Use Proxy" +msgstr "استفاده از پراکسی" + +#: smartdns.js:1028 +msgid "Use proxy to connect to upstream DNS server." +msgstr "برای اتصال به سرور DNS بالادستی از پراکسی استفاده می‌کند." + +#: smartdns.js:1042 +msgid "Please set proxy server first." +msgstr "ابتدا سرور پراکسی را تنظیم کنید." + +#: smartdns.js:1046 +msgid "Only socks5 proxy support UDP, QUIC and HTTP3 server." +msgstr "فقط پراکسی SOCKS5 از سرورهای UDP، QUIC و HTTP/3 پشتیبانی می‌کند." + +#: smartdns.js:1053 +msgid "Fallback" +msgstr "سرور جایگزین" + +#: smartdns.js:1054 +msgid "" +"Mark this server as a fallback server, use it only when default servers fail." +msgstr "این سرور را به‌عنوان سرور جایگزین مشخص می‌کند تا فقط هنگام ازکارافتادن سرورهای پیش‌فرض استفاده شود." + +#: smartdns.js:1061 +msgid "Additional Args for upstream dns servers" +msgstr "پارامترهای اضافی سرورهای DNS بالادستی" + +#: smartdns.js:1069 +msgid "Client Rules" +msgstr "قوانین کلاینت" + +#: smartdns.js:1069 +msgid "Client Rules Settings, can achieve parental control functionality." +msgstr "تنظیمات قوانین کلاینت؛ از این قابلیت می‌توان برای کنترل والدین استفاده کرد." + +#: smartdns.js:1073 +msgid "Basic Settings" +msgstr "تنظیمات پایه" + +#: smartdns.js:1075 smartdns.js:1277 +msgid "DNS Block Setting" +msgstr "تنظیمات مسدودسازی DNS" + +#: smartdns.js:1081 +msgid "Client Address" +msgstr "نشانی کلاینت" + +#: smartdns.js:1082 +msgid "" +"If a client address is specified, only that client will apply this rule. You " +"can enter an IP address, such as 1.2.3.4, or a MAC address, such as aa:bb:cc:" +"dd:ee:ff." +msgstr "اگر نشانی یک کلاینت مشخص شود، این قانون فقط برای همان کلاینت اعمال خواهد شد. می‌توانید یک نشانی IP مانند 1.2.3.4 یا یک نشانی MAC مانند aa:bb:cc:dd:ee:ff وارد کنید." + +#: smartdns.js:1103 +msgid "Client address format error, please input ip adress or mac address." +msgstr "قالب نشانی کلاینت نامعتبر است. لطفاً نشانی IP یا MAC وارد کنید." + +#: smartdns.js:1106 +msgid "Client Address File" +msgstr "فایل نشانی‌های کلاینت" + +#: smartdns.js:1107 +msgid "Upload client address file, same as Client Address function." +msgstr "فایل نشانی کلاینت‌ها را بارگذاری می‌کند؛ عملکرد آن مشابه گزینه «نشانی کلاینت» است." + +#: smartdns.js:1114 smartdns.js:1284 smartdns.js:1494 +msgid "DNS Server group belongs to, such as office, home." +msgstr "گروه سروری را که این DNS به آن تعلق دارد مشخص کنید؛ برای مثال office یا home." + +#: smartdns.js:1134 smartdns.js:1304 smartdns.js:1514 +#, javascript-format +msgid "Server Group %s not exists" +msgstr "گروه سرور %s وجود ندارد." + +#: smartdns.js:1261 smartdns.js:1418 smartdns.js:1448 smartdns.js:1518 +msgid "Domain List File" +msgstr "فایل فهرست دامنه‌ها" + +#: smartdns.js:1261 smartdns.js:1448 +msgid "Upload domain list file." +msgstr "فایل فهرست دامنه‌ها را بارگذاری می‌کند." + +#: smartdns.js:1272 +msgid "Domain Rules" +msgstr "قوانین دامنه" + +#: smartdns.js:1272 +msgid "Domain Rules Settings" +msgstr "تنظیمات قوانین دامنه" + +#: smartdns.js:1276 +msgid "DNS Forwarding Setting" +msgstr "تنظیمات هدایت DNS" + +#: smartdns.js:1278 smartdns.js:1476 +msgid "Domain Rule List" +msgstr "فهرست قوانین دامنه" + +#: smartdns.js:1278 +msgid "Set Specific domain rule list." +msgstr "فهرست قوانین را برای دامنه‌های مشخص تعیین می‌کند." + +#: smartdns.js:1279 +msgid "Domain Address" +msgstr "نشانی دامنه" + +#: smartdns.js:1279 +msgid "Set Specific domain ip address." +msgstr "نشانی IP مشخصی را برای دامنه تعیین می‌کند." + +#: smartdns.js:1366 smartdns.js:1540 +msgid "Yes" +msgstr "بله" + +#: smartdns.js:1367 smartdns.js:1541 +msgid "No" +msgstr "خیر" + +#: smartdns.js:1412 smartdns.js:1627 smartdns.js:1724 +msgid "Additional Rule Flag" +msgstr "پرچم‌های اضافی قانون" + +#: smartdns.js:1413 smartdns.js:1628 +msgid "" +"Additional Flags for rules, read help on domain-rule for more information." +msgstr "پرچم‌های اضافی قوانین. برای اطلاعات بیشتر، راهنمای domain-rule را مطالعه کنید." + +#: smartdns.js:1419 smartdns.js:1519 +msgid "" +"Upload domain list file, or configure auto download from Download File " +"Setting page." +msgstr "فایل فهرست دامنه‌ها را بارگذاری کنید یا دریافت خودکار آن را در صفحه تنظیمات دریافت فایل‌ها پیکربندی کنید." + +#: smartdns.js:1427 smartdns.js:1456 +msgid "Domain List" +msgstr "فهرست دامنه‌ها" + +#: smartdns.js:1427 +msgid "Configure forwarding domain name list." +msgstr "فهرست نام دامنه‌هایی را که باید هدایت شوند پیکربندی می‌کند." + +#: smartdns.js:1456 +msgid "Configure block domain list." +msgstr "فهرست دامنه‌های مسدودشده را پیکربندی می‌کند." + +#: smartdns.js:1477 +msgid "Configure domain rule list." +msgstr "فهرست قوانین دامنه را پیکربندی می‌کند." + +#: smartdns.js:1492 +msgid "Domain Rule Name" +msgstr "نام قانون دامنه" + +#: smartdns.js:1525 +msgid "Block domain" +msgstr "مسدودسازی دامنه" + +#: smartdns.js:1525 +msgid "Block domain." +msgstr "دامنه را مسدود می‌کند." + +#: smartdns.js:1638 +msgid "" +"Specify an IP address to return for any host in the given domains, Queries " +"in the domains are never forwarded and always replied to with the specified " +"IP address which may be IPv4 or IPv6." +msgstr "یک نشانی IP تعیین کنید تا برای تمام میزبان‌های دامنه‌های مشخص‌شده بازگردانده شود. درخواست‌های این دامنه‌ها هرگز به سرور بالادستی هدایت نمی‌شوند و همیشه با همان نشانی IP تعیین‌شده پاسخ داده می‌شوند؛ این نشانی می‌تواند IPv4 یا IPv6 باشد." + +#: smartdns.js:1657 +msgid "IP Rules" +msgstr "قوانین IP" + +#: smartdns.js:1657 +msgid "IP Rules Settings" +msgstr "تنظیمات قوانین IP" + +#: smartdns.js:1661 smartdns.js:1667 +msgid "IP Rule List" +msgstr "فهرست قوانین IP" + +#: smartdns.js:1661 +msgid "Set Specific ip rule list." +msgstr "فهرست قوانین را برای IPهای مشخص تعیین می‌کند." + +#: smartdns.js:1662 +msgid "IP Blacklist" +msgstr "فهرست سیاه IP" + +#: smartdns.js:1662 +msgid "Set Specific ip blacklist." +msgstr "فهرست سیاه IPهای مشخص را تعیین می‌کند." + +#: smartdns.js:1668 +msgid "Configure ip rule list." +msgstr "فهرست قوانین IP را پیکربندی می‌کند." + +#: smartdns.js:1683 +msgid "IP Rule Name" +msgstr "نام قانون IP" + +#: smartdns.js:1687 +msgid "IP Set File" +msgstr "فایل مجموعه IP" + +#: smartdns.js:1687 +msgid "Upload IP set file." +msgstr "فایل مجموعه IP را بارگذاری می‌کند." + +#: smartdns.js:1693 +msgid "IP Addresses" +msgstr "نشانی‌های IP" + +#: smartdns.js:1693 +msgid "IP addresses, CIDR format." +msgstr "نشانی‌های IP با قالب CIDR." + +#: smartdns.js:1698 +msgid "Whitelist IP" +msgstr "فهرست سفید IP" + +#: smartdns.js:1698 +msgid "Whitelist IP Rule, Accept IP addresses within the range." +msgstr "قانون فهرست سفید IP؛ نشانی‌های IP در محدوده تعیین‌شده پذیرفته می‌شوند." + +#: smartdns.js:1703 +msgid "Blacklist IP" +msgstr "فهرست سیاه IP" + +#: smartdns.js:1703 +msgid "Blacklist IP Rule, Decline IP addresses within the range." +msgstr "قانون فهرست سیاه IP؛ نشانی‌های IP در محدوده تعیین‌شده رد می‌شوند." + +#: smartdns.js:1708 +msgid "Ignore IP" +msgstr "نادیده‌گرفتن IP" + +#: smartdns.js:1708 +msgid "Do not use these IP addresses." +msgstr "از این نشانی‌های IP استفاده نکن." + +#: smartdns.js:1713 +msgid "Bogus nxdomain" +msgstr "پاسخ جعلی NXDOMAIN" + +#: smartdns.js:1713 +msgid "Return SOA when the requested result contains a specified IP address." +msgstr "اگر نتیجه درخواست شامل یک نشانی IP مشخص‌شده باشد، پاسخ SOA برگردانده می‌شود." + +#: smartdns.js:1718 +msgid "IP alias" +msgstr "نام مستعار IP" + +#: smartdns.js:1718 +msgid "" +"IP Address Mapping, Can be used for CDN acceleration with Anycast IP, such " +"as Cloudflare's CDN." +msgstr "نگاشت نشانی IP؛ می‌توان از آن برای شتاب‌دهی CDNهایی که از IP از نوع Anycast استفاده می‌کنند، مانند CDN کلودفلر، بهره برد." + +#: smartdns.js:1725 +msgid "Additional Flags for rules, read help on ip-rule for more information." +msgstr "پرچم‌های اضافی قوانین. برای اطلاعات بیشتر، راهنمای ip-rule را مطالعه کنید." + +#: smartdns.js:1734 +msgid "" +"Configure IP blacklists that will be filtered from the results of specific " +"DNS server." +msgstr "فهرست‌های سیاه IP را پیکربندی می‌کند تا این نشانی‌ها از نتایج سرور DNS مشخص‌شده حذف شوند." + +#: smartdns.js:1751 +msgid "Technical Support" +msgstr "پشتیبانی فنی" + +#: smartdns.js:1752 +msgid "If you like this software, please buy me a cup of coffee." +msgstr "اگر این نرم‌افزار را مفید می‌دانید، با حمایت مالی و خرید یک فنجان قهوه از توسعه‌دهنده پشتیبانی کنید." + +#: smartdns.js:1756 +msgid "SmartDNS official website" +msgstr "وب‌سایت رسمی SmartDNS" + +#: smartdns.js:1757 +msgid "open website" +msgstr "باز کردن وب‌سایت" + +#: smartdns.js:1764 smartdns.js:1765 +msgid "Report bugs" +msgstr "گزارش اشکال‌ها" + +#: smartdns.js:1772 +msgid "Donate to smartdns" +msgstr "حمایت مالی از پروژه SmartDNS" + +#: smartdns.js:1773 +msgid "Donate" +msgstr "حمایت مالی" + +#: smartdns.js:1779 +msgid "Restart Service" +msgstr "راه‌اندازی مجدد سرویس" + +#: smartdns.js:1788 +msgid "Restart" +msgstr "راه‌اندازی مجدد" + +#~ msgid "Grant access to LuCI app smartdns" +#~ msgstr "اعطای دسترسی به برنامه LuCI با نام smartdns" diff --git a/luci-theme-footstrap/mangle-tokens.sh b/luci-theme-footstrap/mangle-tokens.sh index c0cf9ff6..dbb47ac3 100755 --- a/luci-theme-footstrap/mangle-tokens.sh +++ b/luci-theme-footstrap/mangle-tokens.sh @@ -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 diff --git a/luci-theme-footstrap/styles/theme/17-glass.css b/luci-theme-footstrap/styles/theme/17-glass.css index 91efcc19..168438ab 100644 --- a/luci-theme-footstrap/styles/theme/17-glass.css +++ b/luci-theme-footstrap/styles/theme/17-glass.css @@ -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) */ diff --git a/luci-theme-footstrap/styles/theme/30-tables.css b/luci-theme-footstrap/styles/theme/30-tables.css index 30ff6f79..cf7f0d69 100644 --- a/luci-theme-footstrap/styles/theme/30-tables.css +++ b/luci-theme-footstrap/styles/theme/30-tables.css @@ -334,9 +334,10 @@ flex-wrap: wrap; gap: var(--fs-space-2); padding-top: var(--fs-space-2); } /* LuCI groups buttons in ONE inner
, 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. */ diff --git a/luci-theme-footstrap/styles/theme/65-dropdown.css b/luci-theme-footstrap/styles/theme/65-dropdown.css index 3f19f936..38ddf242 100644 --- a/luci-theme-footstrap/styles/theme/65-dropdown.css +++ b/luci-theme-footstrap/styles/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. */ diff --git a/luci-theme-footstrap/styles/theme/95-a11y-media.css b/luci-theme-footstrap/styles/theme/95-a11y-media.css index 83393e86..1edf60b8 100644 --- a/luci-theme-footstrap/styles/theme/95-a11y-media.css +++ b/luci-theme-footstrap/styles/theme/95-a11y-media.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 */ diff --git a/luci-theme-footstrap/ucode/template/themes/footstrap/partials/head.ut b/luci-theme-footstrap/ucode/template/themes/footstrap/partials/head.ut index 122db639..45679a30 100644 --- a/luci-theme-footstrap/ucode/template/themes/footstrap/partials/head.ut +++ b/luci-theme-footstrap/ucode/template/themes/footstrap/partials/head.ut @@ -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'); diff --git a/upx-static/Makefile b/upx-static/Makefile index 4cf938cd..c340c385 100755 --- a/upx-static/Makefile +++ b/upx-static/Makefile @@ -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 diff --git a/v2raya/Makefile b/v2raya/Makefile index e47d4f49..6feb041c 100644 --- a/v2raya/Makefile +++ b/v2raya/Makefile @@ -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