From aee7473b97a4c4714d560f31c1ff25417641e988 Mon Sep 17 00:00:00 2001 From: milangree <63492019+milangree@users.noreply.github.com> Date: Mon, 13 Apr 2026 00:05:13 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E7=9B=91=E6=8E=A7=E5=AE=88=E6=8A=A4?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=8F=8A=E6=B8=85=E7=90=86Coloros=E8=A7=84?= =?UTF-8?q?=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- box/scripts/box.iptables | 47 +++-- box/scripts/box.monitor | 428 +++++++++++++++++++++++++++++++++++++++ box/scripts/box.service | 428 +++++++++++++++++++++++---------------- box/scripts/net.inotify | 12 +- 4 files changed, 721 insertions(+), 194 deletions(-) create mode 100644 box/scripts/box.monitor diff --git a/box/scripts/box.iptables b/box/scripts/box.iptables index c284e4c1..0e3453db 100755 --- a/box/scripts/box.iptables +++ b/box/scripts/box.iptables @@ -560,6 +560,23 @@ probe_tun_device() { busybox ifconfig | grep -q "${tun_device}" || return 1 } +# 等待 TUN 设备就绪(核心启动后异步创建,需轮询) +# 参数: $1=超时秒数(默认15) +wait_tun_device() { + local timeout="${1:-15}" + local elapsed=0 + while [ "${elapsed}" -lt "${timeout}" ]; do + if probe_tun_device; then + log Info "TUN 设备 (${tun_device}) 已就绪 (等待 ${elapsed}s)" + return 0 + fi + sleep 1 + elapsed=$((elapsed + 1)) + done + log Error "等待 TUN 设备 (${tun_device}) 超时 (${timeout}s),设备未创建" + return 1 +} + forward() { local action=$1 @@ -841,19 +858,19 @@ start_tproxy() { # NAT DNS 劫持链在后续(本地防回环规则之后)统一创建,避免重复插入 # 跳过已被 TProxy 处理的流量,若默认路由接口有公网 IP,省略这些规则会导致本地流量代理异常,可能拖慢全网 - # if [ "${cap_socket_match}" = "true" ]; then - # if [ ${network_mode} != "enhance" ] && [ "${proxy_tcp}" = "true" ]; then - # ensure_rule_append mangle BOX_EXTERNAL -p tcp -m socket --transparent -j MARK --set-xmark ${fwmark} - # fi - # if [ "${proxy_udp}" = "true" ]; then - # ensure_rule_append mangle BOX_EXTERNAL -p udp -m socket --transparent -j MARK --set-xmark ${fwmark} - # fi - # if [ "${proxy_tcp}" = "true" ] || [ "${proxy_udp}" = "true" ]; then - # ensure_rule_append mangle BOX_EXTERNAL -m socket -j RETURN - # fi - # else - # [ "${iptables}" = "$IPV" ] && log Warning "未检测到 socket match,跳过部分优化规则(不影响基本功能)" - # fi + if [ "${cap_socket_match}" = "true" ]; then + if [ ${network_mode} != "enhance" ] && [ "${proxy_tcp}" = "true" ]; then + ensure_rule_append mangle BOX_EXTERNAL -p tcp -m socket --transparent -j MARK --set-xmark ${fwmark} + fi + if [ "${proxy_udp}" = "true" ]; then + ensure_rule_append mangle BOX_EXTERNAL -p udp -m socket --transparent -j MARK --set-xmark ${fwmark} + fi + if [ "${proxy_tcp}" = "true" ] || [ "${proxy_udp}" = "true" ]; then + ensure_rule_append mangle BOX_EXTERNAL -m socket -j RETURN + fi + else + [ "${iptables}" = "$IPV" ] && log Warning "未检测到 socket match,跳过部分优化规则(不影响基本功能)" + fi # 跳过内网,兼容性可用 su -c 'zcat /proc/config.gz | grep -i addrtype' 检查 # ${iptables} -t mangle -A BOX_EXTERNAL -m addrtype --dst-type LOCAL -j RETURN @@ -1356,7 +1373,7 @@ if [[ "${network_mode}" == @(redirect|mixed|tproxy|enhance) ]]; then log Info "正在创建 iptables 透明代理规则。" iptables="$IPV" - probe_tun_device || log Error "未找到 TUN 设备: (${tun_device})" + wait_tun_device forward -I || forward -D > /dev/null 2>&1 if start_redirect; then @@ -1454,7 +1471,7 @@ else } log_iptables_results cleanup_iptables - probe_tun_device || log Error "未找到 TUN 设备: (${tun_device})" + wait_tun_device [ $1 = "renew" ] && log Warning "正在清理 TUN 规则。" iptables="$IPV" diff --git a/box/scripts/box.monitor b/box/scripts/box.monitor new file mode 100644 index 00000000..7a92d0a2 --- /dev/null +++ b/box/scripts/box.monitor @@ -0,0 +1,428 @@ +#!/system/bin/sh +# box.monitor — 核心监控守护脚本 +# 支持:无网络自动重启 / CPU 占用过高自动重启 / 定时重启 +# 通过 settings.ini 中以下选项控制: +# +# enable_network_watchdog="true" # 启用无网络自动重启 +# network_watchdog_timeout=5 # 连续 N 次检测失败后重启 +# network_test_host="https://cp.cloudflare.com" +# network_check_interval="60" # 检测间隔(秒),最小 10s +# +# enable_cpu_watchdog="false" # 启用 CPU 占用过高自动重启 +# cpu_watchdog_threshold=90 # CPU 占用超过 N% 触发计时 +# cpu_watchdog_duration=120 # 持续超标秒数达到此值后重启 +# +# enable_scheduled_restart="false" # 启用定时重启 +# scheduled_restart_cron="0 4 * * *" # cron 表达式,默认每天凌晨 4:00 + +scripts_dir="${0%/*}" +source /data/adb/box/settings.ini + +# ── 默认值 ──────────────────────────────────────────────────────────────── +enable_network_watchdog="${enable_network_watchdog:-true}" +network_watchdog_timeout="${network_watchdog_timeout:-5}" +network_test_host="${network_test_host:-https://cp.cloudflare.com}" +network_check_interval="${network_check_interval:-60}" + +enable_cpu_watchdog="${enable_cpu_watchdog:-false}" +cpu_watchdog_threshold="${cpu_watchdog_threshold:-90}" +cpu_watchdog_duration="${cpu_watchdog_duration:-120}" + +enable_scheduled_restart="${enable_scheduled_restart:-false}" +scheduled_restart_cron="${scheduled_restart_cron:-0 4 * * *}" + +# 强制最小间隔 10s +[ "${network_check_interval}" -lt 10 ] 2>/dev/null && network_check_interval=10 + +# ── 状态文件路径 ─────────────────────────────────────────────────────────── +MONITOR_STATE_DIR="${box_run_state}" +NO_NET_COUNT_FILE="${MONITOR_STATE_DIR}/monitor.nonet.count" +CPU_HIGH_START_FILE="${MONITOR_STATE_DIR}/monitor.cpu_high.start" +MONITOR_LOG="${box_run}/monitor.log" +DAEMON_PID_FILE="${MONITOR_STATE_DIR}/monitor.daemon.pid" + +# ── 日志函数 ─────────────────────────────────────────────────────────────── +# 日志级别权重:数字越大越严重 +_mlog_level_weight() { + case "$1" in + Info) echo 1 ;; + Warning) echo 2 ;; + Error) echo 3 ;; + *) echo 0 ;; + esac +} + +_mlog() { + local level="$1"; shift + local ts="$(date '+%Y-%m-%d %H:%M:%S')" + local msg="${ts} [${level}]: (monitor) $*" + + # monitor.log:始终全量写入 + echo "${msg}" >> "${MONITOR_LOG}" + + # runs.log:按 monitor_runs_log_level 过滤 + # 可选值: all(默认) / warning / error / none + local runs_filter="${monitor_runs_log_level:-all}" + local write_runs=false + case "${runs_filter}" in + all) write_runs=true ;; + warning) [ "$(_mlog_level_weight "${level}")" -ge 2 ] && write_runs=true ;; + error) [ "$(_mlog_level_weight "${level}")" -ge 3 ] && write_runs=true ;; + none) write_runs=false ;; + *) write_runs=true ;; + esac + [ "${write_runs}" = "true" ] && echo "${msg}" >> "${box_log}" + + if [ -t 1 ]; then + log "${level}" "(monitor) $*" 2>/dev/null || true + fi +} + +_mlog_divider() { + local line="$(date '+%Y-%m-%d %H:%M:%S') ────────────────────────────────────" + echo "${line}" >> "${MONITOR_LOG}" + # 分隔线也遵守过滤规则:仅在 all 时写入 runs.log + [ "${monitor_runs_log_level:-all}" = "all" ] && echo "${line}" >> "${box_log}" +} + +# 强制写入 runs.log(无视 monitor_runs_log_level,用于重要的启动摘要和状态恢复) +_mlog_force() { + local level="$1"; shift + local ts="$(date '+%Y-%m-%d %H:%M:%S')" + local msg="${ts} [${level}]: (monitor) $*" + echo "${msg}" >> "${MONITOR_LOG}" + echo "${msg}" >> "${box_log}" + if [ -t 1 ]; then + log "${level}" "(monitor) $*" 2>/dev/null || true + fi +} + +# 限制 monitor.log 大小(保留最后 500 行) +_trim_log() { + if [ -f "${MONITOR_LOG}" ] && [ "$(busybox wc -l < "${MONITOR_LOG}" 2>/dev/null)" -gt 600 ]; then + local tmp="${MONITOR_LOG}.tmp" + tail -500 "${MONITOR_LOG}" > "${tmp}" && mv "${tmp}" "${MONITOR_LOG}" + fi +} + +# ── cron 表达式转中文 ────────────────────────────────────────────────────── +_cron_to_human() { + local expr="$1" + local m h dom mon dow + m="$(echo "${expr}" | busybox awk '{print $1}')" + h="$(echo "${expr}" | busybox awk '{print $2}')" + dom="$(echo "${expr}" | busybox awk '{print $3}')" + mon="$(echo "${expr}" | busybox awk '{print $4}')" + dow="$(echo "${expr}" | busybox awk '{print $5}')" + + # 仅处理常见模式,其余原样返回 + # 每 N 分钟: */N * * * * + if echo "${m}" | grep -qE '^\*/[0-9]+$' && [ "${h}" = "*" ] && [ "${dom}" = "*" ] && [ "${mon}" = "*" ] && [ "${dow}" = "*" ]; then + local n="${m#*/}" + echo "每${n}分钟" + return + fi + # 每小时整点: 0 * * * * + if [ "${m}" = "0" ] && [ "${h}" = "*" ] && [ "${dom}" = "*" ] && [ "${mon}" = "*" ] && [ "${dow}" = "*" ]; then + echo "每小时整点" + return + fi + # 每 N 小时: 0 */N * * * + if [ "${m}" = "0" ] && echo "${h}" | grep -qE '^\*/[0-9]+$' && [ "${dom}" = "*" ] && [ "${mon}" = "*" ] && [ "${dow}" = "*" ]; then + local n="${h#*/}" + echo "每${n}小时" + return + fi + # 每天固定时刻: 0 H * * *(H 为数字) + if [ "${m}" = "0" ] && echo "${h}" | grep -qE '^[0-9]+$' && [ "${dom}" = "*" ] && [ "${mon}" = "*" ] && [ "${dow}" = "*" ]; then + local hh + hh=$(printf "%02d" "${h}") + echo "每天 ${hh}:00" + return + fi + # 每天多个时刻: 0 H1,H2,... * * * + if [ "${m}" = "0" ] && echo "${h}" | grep -qE '^[0-9]+(,[0-9]+)+$' && [ "${dom}" = "*" ] && [ "${mon}" = "*" ] && [ "${dow}" = "*" ]; then + local times="" + for hh in $(echo "${h}" | tr ',' ' '); do + times="${times}$(printf "%02d" "${hh}"):00 " + done + echo "每天 ${times% }" + return + fi + # 每 N 分钟(非整除型): M */H * * * 或其他复杂形式 → 原样 + echo "${expr}" +} + +# ── 启动摘要 ─────────────────────────────────────────────────────────────── +_log_startup_summary() { + local pid + pid=$(busybox pidof "${bin_name}" 2>/dev/null | busybox awk '{print $1}') + local core_state + [ -n "${pid}" ] && core_state="运行中 (PID: ${pid})" || core_state="未运行" + + local sched_human + sched_human="$(_cron_to_human "${scheduled_restart_cron}")" + + _mlog_divider + _mlog Info "▶ 检查 | ${bin_name}[${core_state}] | 网络:${enable_network_watchdog} CPU:${enable_cpu_watchdog} 定时重启:${enable_scheduled_restart}(${sched_human})" +} + +# ── 重启核心(调用 box.service restart-core,不杀死监控守护)──────────────── +_restart_core() { + local reason="$1" + + # ── 并发锁:防止多个触发源同时重启(锁超过 120s 视为过期)─────────── + local RESTART_LOCK="${MONITOR_STATE_DIR}/restart.lock" + if [ -f "${RESTART_LOCK}" ]; then + local lock_age lock_ts + lock_ts=$(cat "${RESTART_LOCK}" 2>/dev/null || echo 0) + lock_age=$(( $(date +%s) - lock_ts )) + if [ "${lock_age}" -lt 120 ]; then + _mlog Warning "重启已在进行中 (${lock_age}s),跳过: ${reason}" + return 0 + fi + _mlog Warning "发现过期锁 (${lock_age}s),强制清除" + fi + date +%s > "${RESTART_LOCK}" + + _mlog_divider + _mlog Warning "⚡ 重启 ${bin_name} | 原因: ${reason}" + + # 直接传递原因参数给 box.service restart-core + "${scripts_dir}/box.service" restart-core "${reason}" >> "${MONITOR_LOG}" 2>&1 + + rm -f "${NO_NET_COUNT_FILE}" "${CPU_HIGH_START_FILE}" "${RESTART_LOCK}" + _mlog_divider +} + +# ── 1. 无网络检测 ────────────────────────────────────────────────────────── +check_network() { + [ "${enable_network_watchdog}" = "true" ] || return 0 + + local pid + pid=$(busybox pidof "${bin_name}" 2>/dev/null | busybox awk '{print $1}') + if [ -z "${pid}" ]; then + _mlog Warning "网络监控: 核心 ${bin_name} 未运行,跳过检测" + return 0 + fi + + local http_code + if command -v curl >/dev/null 2>&1; then + http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 --insecure "${network_test_host}" 2>/dev/null) + else + http_code=$(busybox wget -T 5 -q --spider "${network_test_host}" 2>/dev/null && echo "200" || echo "000") + fi + + if [ "${http_code}" = "204" ] || [ "${http_code}" = "200" ]; then + if [ -f "${NO_NET_COUNT_FILE}" ]; then + local prev_count + prev_count=$(cat "${NO_NET_COUNT_FILE}" 2>/dev/null || echo 0) + _mlog_force Info "网络监控: ✓ 已恢复 (HTTP ${http_code}) | 此前累计失败 ${prev_count} 次" + rm -f "${NO_NET_COUNT_FILE}" + fi + # 正常时不重复记录,降低日志噪音 + return 0 + fi + + local count=0 + [ -f "${NO_NET_COUNT_FILE}" ] && count=$(cat "${NO_NET_COUNT_FILE}" 2>/dev/null || echo 0) + count=$((count + 1)) + echo "${count}" > "${NO_NET_COUNT_FILE}" + + local remain=$((network_watchdog_timeout - count)) + if [ "${count}" -ge "${network_watchdog_timeout}" ]; then + _mlog Warning "网络监控: ✗ HTTP ${http_code:-000} | 累计: ${count}/${network_watchdog_timeout} | 触发重启..." + _restart_core "连续 ${count} 次网络检测失败 (HTTP ${http_code:-000})" + else + _mlog Warning "网络监控: ✗ HTTP ${http_code:-000} | 累计: ${count}/${network_watchdog_timeout} | 还需失败 ${remain} 次" + fi +} + +# ── 获取进程瞬时 CPU 占用(per-core 百分比,与 ps/top/进程监控显示一致)──── +# 原理:对 /proc//stat 和 /proc/stat 做 1 秒双采样,计算真实瞬时占用。 +# 避免 ps %cpu 使用"启动以来累计平均值"导致的严重低估问题。 +# 返回值:整数百分比(单核满载=100%,多核可超过100%),失败时返回空字符串。 +_get_proc_cpu() { + local pid="$1" + local stat_file="/proc/${pid}/stat" + [ -f "${stat_file}" ] || { echo ""; return 1; } + + # ── T1 采样 ──────────────────────────────────────────────────────────── + local p1 t1 + p1=$(busybox awk '{print $14+$15}' "${stat_file}" 2>/dev/null) + t1=$(busybox awk 'NR==1{s=0; for(i=2;i<=NF;i++) s+=$i; print s}' /proc/stat 2>/dev/null) + [ -z "${p1}" ] || [ -z "${t1}" ] && { echo ""; return 1; } + + sleep 1 + + # ── T2 采样(sleep 期间进程可能退出)────────────────────────────────── + [ -f "${stat_file}" ] || { echo ""; return 1; } + local p2 t2 + p2=$(busybox awk '{print $14+$15}' "${stat_file}" 2>/dev/null) + t2=$(busybox awk 'NR==1{s=0; for(i=2;i<=NF;i++) s+=$i; print s}' /proc/stat 2>/dev/null) + [ -z "${p2}" ] || [ -z "${t2}" ] && { echo ""; return 1; } + + local dp dt nproc + dp=$(( p2 - p1 )) + dt=$(( t2 - t1 )) + [ "${dt}" -le 0 ] && { echo ""; return 1; } + + # 核心数,用于换算 per-core 百分比 + nproc=$(busybox nproc 2>/dev/null \ + || busybox grep -c '^processor' /proc/cpuinfo 2>/dev/null \ + || echo 1) + [ "${nproc}" -le 0 ] 2>/dev/null && nproc=1 + + # per-core % = (进程增量 ticks / 总增量 ticks) × 核心数 × 100 + # 与 ps %CPU / top %CPU 显示一致(使满单核 = 100%) + printf "%d" $(( dp * nproc * 100 / dt )) +} + +# ── 2. CPU 占用过高检测 ──────────────────────────────────────────────────── +check_cpu() { + [ "${enable_cpu_watchdog}" = "true" ] || return 0 + + local pid + pid=$(busybox pidof "${bin_name}" 2>/dev/null | busybox awk '{print $1}') + if [ -z "${pid}" ]; then + _mlog Warning "CPU 监控: 核心 ${bin_name} 未运行,跳过检测" + return 0 + fi + + # 优先使用 /proc/stat 双采样(瞬时值,与 ps/top/软件显示一致) + local cpu + cpu=$(_get_proc_cpu "${pid}") + + # 降级:/proc 读取失败时回退到 ps(仅作保底,值可能偏低) + if [ -z "${cpu}" ]; then + cpu=$(ps -p "${pid}" -o %cpu 2>/dev/null | busybox awk 'NR==2{printf "%d", $1+0}') + [ -n "${cpu}" ] && _mlog Warning "CPU 监控: /proc 采样失败,已降级使用 ps(值可能偏低)" + fi + + if [ -z "${cpu}" ]; then + _mlog Warning "CPU 监控: 无法读取 ${bin_name}(PID: ${pid}) 的 CPU 占用" + return 0 + fi + + if [ "${cpu}" -ge "${cpu_watchdog_threshold}" ]; then + if [ ! -f "${CPU_HIGH_START_FILE}" ]; then + date +%s > "${CPU_HIGH_START_FILE}" + _mlog Warning "CPU 监控: ⚠ 占用 ${cpu}% ≥ 阈值 ${cpu_watchdog_threshold}% | ${bin_name}(PID: ${pid}) | 开始计时 (持续 ${cpu_watchdog_duration}s 后重启)" + else + local start_ts now elapsed remain_s + start_ts=$(cat "${CPU_HIGH_START_FILE}" 2>/dev/null) + now=$(date +%s) + elapsed=$((now - start_ts)) + remain_s=$((cpu_watchdog_duration - elapsed)) + _mlog Warning "CPU 监控: ⚠ 占用 ${cpu}% | ${bin_name}(PID: ${pid}) | 已持续 ${elapsed}s / ${cpu_watchdog_duration}s | 剩余 ${remain_s}s 触发重启" + if [ "${elapsed}" -ge "${cpu_watchdog_duration}" ]; then + _restart_core "CPU 占用 ${cpu}% 持续超过 ${cpu_watchdog_duration}s" + fi + fi + else + if [ -f "${CPU_HIGH_START_FILE}" ]; then + local start_ts now elapsed + start_ts=$(cat "${CPU_HIGH_START_FILE}" 2>/dev/null) + now=$(date +%s) + elapsed=$((now - start_ts)) + _mlog_force Info "CPU 监控: ✓ 占用恢复正常 ${cpu}% | ${bin_name}(PID: ${pid}) | 高占用持续了 ${elapsed}s" + rm -f "${CPU_HIGH_START_FILE}" + # 正常时不重复记录,降低日志噪音 + fi + fi +} + +# ── 3. 定时重启(由 cron 以 scheduled 参数调用)──────────────────────────── +scheduled_restart() { + [ "${enable_scheduled_restart}" = "true" ] || { + _mlog Warning "定时重启: 已禁用 (enable_scheduled_restart=false)" + return 0 + } + local sched_human + sched_human="$(_cron_to_human "${scheduled_restart_cron}")" + _mlog Info "定时重启: 按计划执行 (${sched_human})" + _restart_core "定时重启 (${sched_human})" +} + +# ── 4. 后台 daemon 模式(由 box.service 启动,sleep loop)───────────────── +run_daemon() { + # 记录自己的 PID + echo -n $$ > "${DAEMON_PID_FILE}" + + local sched_human + sched_human="$(_cron_to_human "${scheduled_restart_cron}")" + + _mlog_divider + _mlog_force Info "★ 监控 daemon 启动 (PID:$$, 间隔:${network_check_interval}s) | 网络:${enable_network_watchdog} CPU:${enable_cpu_watchdog} 定时重启:${enable_scheduled_restart}(${sched_human})" + + # ── 等待核心完全启动且 iptables 就绪(runtime_save 写入 runtime.iptables.env + # 对应日志 "${bin_name} 已连接。")后才开始第一次检测 ─────────────────────── + local runtime_env="${box_run_state}/runtime.iptables.env" + local wait_i=0 + local wait_max=120 # 最多等 120s + _mlog_force Info " 等待核心 ${bin_name} 启动及 iptables 就绪..." + while [ "${wait_i}" -lt "${wait_max}" ]; do + if busybox pidof "${bin_name}" >/dev/null 2>&1 && [ -f "${box_pid}" ] && [ -f "${runtime_env}" ]; then + _mlog_force Info " 核心 ${bin_name} 已连接,iptables 就绪 (等待 ${wait_i}s),开始监控" + break + fi + sleep 1 + wait_i=$((wait_i + 1)) + done + if [ "${wait_i}" -ge "${wait_max}" ]; then + _mlog_force Warning " 等待超时 (${wait_max}s),iptables 可能未就绪,监控继续运行" + fi + _mlog_divider + + while true; do + # 重新加载 settings.ini(支持热更新配置) + source /data/adb/box/settings.ini + enable_network_watchdog="${enable_network_watchdog:-true}" + network_watchdog_timeout="${network_watchdog_timeout:-5}" + network_test_host="${network_test_host:-https://cp.cloudflare.com}" + network_check_interval="${network_check_interval:-60}" + enable_cpu_watchdog="${enable_cpu_watchdog:-false}" + cpu_watchdog_threshold="${cpu_watchdog_threshold:-90}" + cpu_watchdog_duration="${cpu_watchdog_duration:-120}" + [ "${network_check_interval}" -lt 10 ] 2>/dev/null && network_check_interval=10 + + _trim_log + check_network + check_cpu + + sleep "${network_check_interval}" + done +} + +# ── 入口 ─────────────────────────────────────────────────────────────────── +mkdir -p "${MONITOR_STATE_DIR}" 2>/dev/null || true + +case "${1:-check}" in + daemon) + run_daemon + ;; + check) + _trim_log + _log_startup_summary + check_network + check_cpu + ;; + network) + check_network + ;; + cpu) + check_cpu + ;; + --scheduled|scheduled) + scheduled_restart + ;; + *) + echo "用法: $0 {daemon|check|network|cpu|scheduled}" + echo " daemon — 后台持续监控 (由 box.service 启动,间隔 network_check_interval 秒)" + echo " check — 单次检测网络与 CPU" + echo " network — 仅检测网络连通性" + echo " cpu — 仅检测 CPU 占用" + echo " scheduled — 执行定时重启 (由 cron 按 scheduled_restart_cron 调用)" + exit 1 + ;; +esac \ No newline at end of file diff --git a/box/scripts/box.service b/box/scripts/box.service index c2f6b38f..146b7f9b 100755 --- a/box/scripts/box.service +++ b/box/scripts/box.service @@ -4,65 +4,34 @@ scripts_dir="${0%/*}" source /data/adb/box/settings.ini PROPFILE="/data/adb/modules/box_for_root/module.prop" +RESTART_LOG="${box_run}/restart.log" # 与内核日志同目录 find_packages_uid() { - log Info "代理模式: ${proxy_mode}" - if [ "${proxy_mode}" = "core" ]; then - log Info "核心模式已启用,跳过 package 和 gid 处理" > "${uid_list}" return 0 fi - log Info "开始处理 UID/GID 列表" - log Debug "应用列表 (来自 package.list.cfg): ${packages_list[*]}" - log Debug "GID 列表 (来自 gid.list.cfg): ${gid_list[*]}" - > "${uid_list}" - - local packages_file + local packages_file ok=0 fail=0 packages_file="${system_packages_file:-/data/system/packages.list}" - for entry in "${packages_list[@]}"; do - local prefix="0" - local package="${entry}" - - case "${entry}" in - *:*) - prefix="${entry%%:*}" - package="${entry#*:}" - ;; - esac - - case "${prefix}" in - ''|*[!0-9]*) - log Warning "Failure: 用户 \"${prefix}\" 应用: \"${package}\" -> 用户前缀无效,已回退为 0" - prefix="0" - ;; - esac + for entry in "${packages_list[@]}"; do + local prefix="0" package="${entry}" + case "${entry}" in *:*) prefix="${entry%%:*}"; package="${entry#*:}" ;; esac + case "${prefix}" in ''|*[!0-9]*) prefix="0" ;; esac if [ -z "${package}" ] || [ "${package}" = "${entry}" ] && [ "${entry}" = "${prefix}" ]; then - log Warning "Failure: 用户 \"${prefix}\" 应用: \"${entry}\" -> 包名无效" - continue + fail=$((fail + 1)); continue fi - local base_uid base_uid=$(busybox awk -v pkg="${package}" '$1 == pkg {print $2; exit}' "${packages_file}" 2>/dev/null) - - case "${base_uid}" in - ''|*[!0-9]*) - log Warning "Failure: 用户 \"${prefix}\" 应用: \"${package}\" -> 未找到" - continue - ;; - esac - - local multiuser_uid=$((base_uid + prefix * 100000)) - echo "${multiuser_uid}" >> "${uid_list}" - log Info "Success: 用户 \"${prefix}\" 应用: \"${package}\" -> ${multiuser_uid}" + case "${base_uid}" in ''|*[!0-9]*) fail=$((fail + 1)); continue ;; esac + echo "$((base_uid + prefix * 100000))" >> "${uid_list}" + ok=$((ok + 1)) done if [ ${#gid_list[@]} -gt 0 ]; then printf "%s\n" "${gid_list[@]}" >> "${uid_list}" - log Info "Success: GID: ${gid_list[*]} -> 已添加" fi if [ -s "${uid_list}" ]; then @@ -70,6 +39,8 @@ find_packages_uid() { busybox sort -un "${uid_list}" > "${temp_uid_list}" mv "${temp_uid_list}" "${uid_list}" fi + + [ $((ok + fail)) -gt 0 ] && log Debug "UID 处理: ${ok} 成功 / ${fail} 未找到 | 模式: ${proxy_mode}" } log_service_results() { @@ -90,31 +61,43 @@ log_service_results() { } box_check_logs() { - # 删除所有内核日志并备份 - log Info "删除并备份日志文件" ( - for bin in "${bin_list[@]}"; do - if [ -f "${box_run}/${bin}.log" ]; then - mv "${box_run}/${bin}.log" "${box_run}/${bin}.old.log" - fi - done - # 删除其他日志文件 + # 过期日志(超过1天)无论设置都清理,避免磁盘占用 + find "${box_run}" -maxdepth 1 -type f \( -name "*.log" -o -name "*.old.log" \) -mtime +1 -exec rm -f {} + + # 临时状态文件始终清理 find "${box_run}" -maxdepth 1 -type f \( -name "root" -o -name "*.inotify.log" \) -exec rm -f {} + find "${box_run_state}" -maxdepth 1 -type f \( -name "*.list" -a ! -name "appuid.list" \) -exec rm -f {} + - # 删除一天前的日志 - find "${box_run}" -maxdepth 1 -type f \( -name "*.log" -o -name "*.old.log" \) -mtime +1 -exec rm -f {} + + + # clear_restart_log 控制是否在每次启动时清空/备份内核日志 + # 默认 true(与原行为一致),设为 false 则保留上次日志 + if [ "${clear_restart_log:-true}" = "true" ]; then + for bin in "${bin_list[@]}"; do + if [ -f "${box_run}/${bin}.log" ]; then + mv "${box_run}/${bin}.log" "${box_run}/${bin}.old.log" + fi + done + # 仅在完整启动(非 restart-core)时备份 monitor.log,避免打断正在运行的监控守护 + if [ "${SKIP_MONITOR_START:-false}" != "true" ] && [ -f "${box_run}/monitor.log" ]; then + mv "${box_run}/monitor.log" "${box_run}/monitor.old.log" + fi + log Debug "内核日志已备份 (clear_restart_log=true)" + else + log Debug "保留上次内核日志 (clear_restart_log=false)" + fi ) & } box_bin_alive() { - local PID=$(<"${box_pid}" 2>/dev/null) - if ! kill -0 "$PID" >/dev/null 2>&1; then - log Error "$(<"${box_run}/${bin_name}.log")" - log Error "${bin_name} 服务未运行。" - log Error "请检查 ${bin_name}.log 获取详细信息。" - log Error "清理无效 pid $PID" + local PID + PID=$(<"${box_pid}" 2>/dev/null) + if ! kill -0 "${PID}" >/dev/null 2>&1; then + # 只取日志末尾几行,避免把整个大文件写入 runs.log + log Error "${bin_name} (PID:${PID:-无}) 未运行,详情见: ${box_run}/${bin_name}.log" + tail -5 "${box_run}/${bin_name}.log" 2>/dev/null | while IFS= read -r line; do + log Error " ${line}" + done for bin in "${bin_list[@]}"; do - killall -15 "${bin}" >/dev/null || busybox pkill -15 "${bin}" >/dev/null 2>&1 + killall -15 "${bin}" >/dev/null 2>&1 || busybox pkill -15 "${bin}" >/dev/null 2>&1 done "${scripts_dir}/box.iptables" disable >/dev/null 2>&1 [ -f "${box_pid}" ] && rm -f "${box_pid}" @@ -129,17 +112,52 @@ box_run_crontab() { busybox crontab -c "${box_run_state}" -r echo "# root 的定时任务" > "${box_run_state}/root" + # 默认值(settings.ini 未配置时生效) + local _net_watch="${enable_network_watchdog:-true}" + local _cpu_watch="${enable_cpu_watchdog:-false}" + local _sched_restart="${enable_scheduled_restart:-false}" + local _sched_cron="${scheduled_restart_cron:-0 4 * * *}" + + local need_crond=false + if [ "${run_crontab}" = "true" ]; then - log Debug "crond 已启用" + need_crond=true if [ "$update_subscription" = "true" ] || [ "$update_geo" = "true" ]; then - log Debug "定时任务间隔: ${interva_update}." echo "${interva_update} ${scripts_dir}/box.tool geosub" >> "${box_run_state}/root" - log Info "${bin_name} geox 更新: ${update_geo}." - if [ "${bin_name}" = "mihomo" ]; then - log Info "${bin_name} 订阅更新: ${update_subscription}." - fi + log Debug "定时更新: geo=${update_geo} sub=${update_subscription} 间隔=${interva_update}" fi cat "${box_dir}/crontab.cfg" >> "${box_run_state}/root" + # 确保 crontab.cfg 末尾有换行,避免后续追加行粘连 + echo "" >> "${box_run_state}/root" + fi + + # ── 监控守护进程(后台 daemon,间隔由 network_check_interval 秒控制)──── + local _net_watch="${enable_network_watchdog:-true}" + local _cpu_watch="${enable_cpu_watchdog:-false}" + local _sched_restart="${enable_scheduled_restart:-false}" + local _sched_cron="${scheduled_restart_cron:-0 4 * * *}" + + if [ "${_net_watch}" = "true" ] || [ "${_cpu_watch}" = "true" ]; then + # 先杀掉旧的 monitor daemon + local old_mon_pid + old_mon_pid=$(cat "${box_run_state}/monitor.daemon.pid" 2>/dev/null) + if [ -n "${old_mon_pid}" ] && kill -0 "${old_mon_pid}" 2>/dev/null; then + kill -9 "${old_mon_pid}" >/dev/null 2>&1 + fi + rm -f "${box_run_state}/monitor.daemon.pid" + nohup "${scripts_dir}/box.monitor" daemon >/dev/null 2>&1 & + echo -n $! > "${box_run_state}/monitor.daemon.pid" + log Debug "监控守护已启动 (网络:${_net_watch} CPU:${_cpu_watch} 间隔:${network_check_interval:-60}s)" + fi + + # ── 定时重启核心(保留 cron)──────────────────────────────────────────── + if [ "${_sched_restart}" = "true" ] && [ -n "${_sched_cron}" ]; then + need_crond=true + echo "${_sched_cron} ${scripts_dir}/box.monitor scheduled" >> "${box_run_state}/root" + log Info "定时重启已启用: [${_sched_cron}]" + fi + + if [ "${need_crond}" = "true" ]; then chmod 0644 "${box_run_state}/root" nohup busybox crond -c "${box_run_state}" >/dev/null 2>&1 & else @@ -251,6 +269,7 @@ prepare_singbox() { "auto_route": true, "strict_route": true, "auto_redirect": true, + "include_android_user": [0,10,999], "include_uid": [], "exclude_uid": [] }]' -i --output-format=json "${sing_config}" @@ -522,7 +541,7 @@ box_run_bin() { ;; xray) if [[ "${network_mode}" != "tproxy" ]]; then - sed -i 's/\(network_mode=\)"[^\"]*"/\1"tproxy"/g' ${settings} + sed -i 's/\(network_mode=\)"[^"]*"/\1"tproxy"/g' ${settings} fi if [ ! -f "${xray_config}" ]; then log Error "未找到配置文件:${xray_config}" @@ -605,79 +624,46 @@ box_cgroup() { # 显示进程状态信息 box_bin_status() { - # 获取进程 PID - local PID=$(busybox pidof ${bin_name}) - - if [ -z "$PID" ]; then + local PID + PID=$(busybox pidof "${bin_name}") + if [ -z "${PID}" ]; then log Error "${bin_name} 未在运行" return 1 fi - stack=$(if [ "${bin_name}" != "mihomo" ]; then find "/data/adb/box/sing-box" -type f -name "*.json" -exec busybox awk -F'"' '/"stack"/{print $4}' {} +; else busybox awk '!/^ *#/ && /stack: / { print $2;found=1; exit}' "${mihomo_config}"; fi) - TOAST=1 log Info "${bin_name} 服务正在运行" - - log Info "代理模式: ${proxy_mode} + 网络模式: ${network_mode} + $(if [[ "${network_mode}" == @(mixed|tun) ]]; then echo "协议栈: ${stack}"; fi)" - - # 获取内存使用情况 - rss=$(grep VmRSS /proc/$PID/status | busybox awk '{ print $2 }') - [ "${rss}" -ge 1024 ] && bin_rss="$(expr ${rss} / 1024) MB" || bin_rss="${rss} KB" - swap=$(grep VmSwap /proc/$PID/status | busybox awk '{ print $2 }') - [ "${swap}" -ge 1024 ] && bin_swap="$(expr ${swap} / 1024) MB" || bin_swap="${swap} KB" - - # 获取用户组信息 - user_group=$(stat -c %U:%G /proc/$PID) - - # 记录信息到日志 - log Info "${bin_name} 以 '${user_group}' 用户组启动" - log Info "${bin_name} 状态: ${state} (PID: $PID)" - log Info "${bin_name} 内存占用: ${bin_rss}, 交换: ${bin_swap}" - - # 获取 CPU 使用率 - cpu=$(ps -p $PID -o %cpu | busybox awk 'NR==2{print $1}' 2>/dev/null) - - cpus_allowed=$(grep Cpus_allowed_list /proc/$PID/status | busybox awk '{ print $2" "$3 }') - core=$(busybox awk '{print $39}' /proc/$PID/stat 2>/dev/null) - - if [ -n "${cpu}" ]; then - log Info "${bin_name} CPU 使用率: ${cpu}%" - else - log Info "${bin_name} CPU 使用率: 无法获取" - fi - if [ -n "${cpus_allowed}" ]; then - log Info "${bin_name} 允许使用的 CPU 核心: ${cpus_allowed}" - log Info "${bin_name} 进程 $PID 最后运行在 CPU 核心: ${core}" - else - log Info "${bin_name} 运行的 CPU 核心: 无法获取" - fi - - # 检查电池温度 - temperature_celsius=$(($(cat /sys/class/power_supply/battery/temp) / 10)) - log Info "电池温度: ${temperature_celsius}°C" - - # 获取运行时间 - running_time=$(busybox ps -o comm,etime | grep ${bin_name} | busybox awk '{print $2}') - if [ -n "${running_time}" ]; then - log Info "${bin_name} 运行时间: ${running_time}" + # 内存占用 + local rss swap bin_rss bin_swap + rss=$(busybox awk '/VmRSS/{print $2}' /proc/${PID}/status 2>/dev/null || echo 0) + swap=$(busybox awk '/VmSwap/{print $2}' /proc/${PID}/status 2>/dev/null || echo 0) + [ "${rss:-0}" -ge 1024 ] && bin_rss="$(( rss / 1024 )) MB" || bin_rss="${rss:-0} KB" + [ "${swap:-0}" -ge 1024 ] && bin_swap="$(( swap / 1024 )) MB" || bin_swap="${swap:-0} KB" + + # 协议栈 / 运行时长 / 温度 + local stack user_group running_time temperature_celsius + if [ "${bin_name}" != "mihomo" ]; then + stack=$(find "/data/adb/box/sing-box" -type f -name "*.json" -exec busybox awk -F'"' '/"stack"/{print $4}' {} + 2>/dev/null | head -1) else - log Info "${bin_name} 运行时间: 无法获取" + stack=$(busybox awk '!/^ *#/ && /stack: /{print $2; exit}' "${mihomo_config}" 2>/dev/null) fi - - ( - # 本地 IP - localIP=($(ip -4 a | awk '/inet / && !/127.0.0.1/ { split($2, a, "/"); print a[1] }')) - log Info "本地 IP: ${localIP[*]}" - # 本地 DNS - localDNS=($(dumpsys connectivity | awk -F'[ ,]' '/DnsAddresses:/ { for (i=1; i<=NF; i++) if ($i ~ /^\/.*$/) { dns=substr($i, 2); if (dns ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) print dns } }')) - log Info "本地 DNS: ${localDNS[*]}" + user_group=$(stat -c %U:%G /proc/${PID} 2>/dev/null) + running_time=$(busybox ps -o comm,etime 2>/dev/null | busybox grep "${bin_name}" | busybox awk '{print $2}' | head -1) + temperature_celsius=$(( $(cat /sys/class/power_supply/battery/temp 2>/dev/null || echo 0) / 10 )) + + TOAST=1 log Info "${bin_name} 运行中 (PID:${PID}) | 用户:${user_group:-未知} | 时长:${running_time:-未知} | 电池:${temperature_celsius}°C" + local mode_info="代理:${proxy_mode} 网络:${network_mode}" + [[ "${network_mode}" == @(mixed|tun) ]] && mode_info="${mode_info} 栈:${stack:-未知}" + log Info "${mode_info} | 内存:${bin_rss} 交换:${bin_swap}" + + ( # 异步获取网络信息,避免阻塞 + local localIP localDNS + localIP=$(ip -4 a 2>/dev/null | busybox awk '/inet / && !/127.0.0.1/{split($2,a,"/"); printf "%s ",a[1]}') + localDNS=$(dumpsys connectivity 2>/dev/null | busybox awk -F'[ ,]' '/DnsAddresses:/{for(i=1;i<=NF;i++) if($i~/^\/[0-9]+\./) printf "%s ",substr($i,2)}') + log Debug "本地 IP:${localIP:-未知} | DNS:${localDNS:-未知}" ) & - - # 保存进程 ID 到 pid 文件 - if [ -n "$PID" ]; then - get_dns_mode - set_description running - echo -n "$PID" > "${box_pid}" - fi + get_dns_mode + set_description running + echo -n "${PID}" > "${box_pid}" } set_description() { @@ -763,26 +749,84 @@ get_dns_mode() { export dns_mode } +# ==================== 新增:仅重启核心(不影响监控守护和 crond) ==================== +_stop_core() { + # 停止核心进程(发送 SIGTERM) + for bin in "${bin_list[@]}"; do + busybox pkill -15 "${bin}" >/dev/null 2>&1 + done + + # 等待进程退出 + local i=0 + while busybox pidof "${bin_name}" >/dev/null 2>&1 && [ "$i" -lt 6 ]; do + sleep 1 + i=$((i + 1)) + done + + # 若未退出则强制结束 + if busybox pidof "${bin_name}" >/dev/null 2>&1; then + log Warning "核心 ${bin_name} 未正常退出,强制结束..." + for bin in "${bin_list[@]}"; do + busybox pkill -9 "${bin}" >/dev/null 2>&1 + done + fi + + # 清理 iptables 规则 + "${scripts_dir}/box.iptables" disable >/dev/null 2>&1 + + rm -f "${box_pid}" + log Info "核心已停止" +} + +_start_core() { + # 设置环境变量 SKIP_MONITOR_START=1 阻止 start_box 中启动监控守护和 crond + SKIP_MONITOR_START=1 start_box +} + +restart_core_only() { + local reason="${1:-手动执行 restart-core}" + log Info "开始仅重启核心(不重启监控守护)..." + _stop_core + _start_core + + # 等待核心进程出现并恢复 iptables(由 start_box 内的逻辑完成,但需额外等待 TUN 设备) + local pid_after="" + local i=0 + while [ "$i" -lt 15 ]; do + pid_after=$(busybox pidof "${bin_name}" 2>/dev/null | busybox awk '{print $1}') + [ -n "$pid_after" ] && break + sleep 1 + i=$((i + 1)) + done + + if [ -n "$pid_after" ]; then + log Info "核心重启完成,新 PID: $pid_after" + # 记录重启日志到独立文件 + echo "$(date '+%Y-%m-%d %H:%M:%S') [重启] 原因: ${reason} | 结果: 成功 (新PID: ${pid_after})" >> "${RESTART_LOG}" + else + log Error "核心重启失败,未检测到进程" + echo "$(date '+%Y-%m-%d %H:%M:%S') [重启] 原因: ${reason} | 结果: 失败 (未检测到进程)" >> "${RESTART_LOG}" + fi +} +# ======================================================================== + start_box() { + # 如果 SKIP_MONITOR_START 已设置,则跳过监控守护的启动 + local skip_monitor="${SKIP_MONITOR_START:-false}" find_packages_uid get_dns_mode set_description running echo -n "" > "${box_log}" - box_version=$(busybox awk '!/^ *#/ && /version=/ { print $0 }' "/data/adb/modules/box_for_root/module.prop" 2>/dev/null) - + # monitor.log 由 box.monitor 管理,重启时不清空,避免覆盖进行中的重启日志 + local box_version + box_version=$(busybox awk '!/^ *#/ && /version=/{print $0}' "/data/adb/modules/box_for_root/module.prop" 2>/dev/null) + local start_banner="${box_version}($(getprop ro.product.cpu.abi)) | $(getprop gsm.sim.operator.alpha) $(getprop gsm.network.type) | $(date +%H:%M:%S)" if [ -t 1 ]; then - echo -e "${yellow}$(getprop persist.sys.timezone) $(date)${normal}" - echo -e "${yellow}$(getprop gsm.sim.operator.alpha) $(getprop gsm.network.type)${normal}" - echo -e "${yellow}${box_version}($(getprop ro.product.cpu.abi))${normal}" + echo -e "${yellow}${start_banner}${normal}" echo -e "${white}━━━━━━━━━━━━━━━━━━${normal}" else - { - echo "$(getprop persist.sys.timezone) $(date)" - echo "$(getprop gsm.sim.operator.alpha) $(getprop gsm.network.type)" - echo "${box_version}($(getprop ro.product.cpu.abi))" - echo "━━━━━━━━━━━━━━━━━━" - } | tee -a "${box_log}" >/dev/null 2>&1 + echo "${start_banner}" | tee -a "${box_log}" >/dev/null 2>&1 fi # 如果 bin_name 仍在运行则更新 iptables @@ -796,14 +840,11 @@ start_box() { done if [ -n "$PID" ]; then - pid_name="${box_dir}/run/pid_name.txt" - ps -p $PID -o comm= > "${pid_name}" - sed -i '/^[[:space:]]*$/d' "${pid_name}" - log Debug "$(<"${pid_name}")(PID: $PID) 服务仍在运行,自动重启 BOX" - rm -f "${pid_name}" + local running_name + running_name=$(ps -p "${PID}" -o comm= 2>/dev/null | head -1) + log Debug "${running_name:-unknown}(PID:${PID}) 仍在运行,先停止再启动" stop_box - start_box && "${scripts_dir}/box.iptables" renew - exit 1 + # 不递归调用 start_box;停止后让当前 start 流程继续 fi # 检查 bin_name 是否已定义 @@ -820,18 +861,21 @@ start_box() { # busybox 检查 busybox_code=$(busybox | busybox grep -oE '[0-9.]*' | head -n 1) if [ -n "${busybox_code}" ] && [ "$(echo "${busybox_code}" | busybox awk -F. '{printf "%03d%03d%03d\n", $1, $2, $3}')" -lt "$(echo "1.36.1" | busybox awk -F. '{printf "%03d%03d%03d\n", $1, $2, $3}')" ]; then - log Info "当前 $(which busybox) v${busybox_code}" - log Warning "请将 busybox 更新到 v1.36.1+" + log Warning "busybox v${busybox_code} 建议升级到 v1.36.1+" else - log Info "当前 $(which busybox) v${busybox_code}" + log Debug "busybox v${busybox_code}" fi box_permission box_check_bin box_check_logs - # 如果 run_crontab 不等于 "false" 则执行 box_run_crontab - [ "${run_crontab}" = "true" ] && box_run_crontab || log Info "定时任务已禁用" + # 如果 run_crontab 不等于 "false" 且未跳过监控守护,则执行 box_run_crontab + if [ "${run_crontab}" = "true" ] && [ "$skip_monitor" != "true" ]; then + box_run_crontab + else + log Info "定时任务和监控守护已跳过(内部重启)" + fi if [ -z "${proxy_mode}" ]; then M1=$(busybox awk '!/^ *#/ && /mode:/{print $0}' "${pkg_config}") @@ -839,6 +883,27 @@ start_box() { log Debug "代理模式为空,在 ${pkg_config} 中添加 mode:white" fi + # === ColorOS Google Firewall Fixer === + if [ "${clean_gfw_rules}" = "true" ]; then + _remove_reject_rules() { + local table=$1 chain=$2 proto=$3 cmd removed=0 + case "$proto" in ipv4) cmd="iptables" ;; ipv6) cmd="ip6tables" ;; esac + command -v "$cmd" > /dev/null 2>&1 || return 0 + echo "$cmd -t $table -nvL $chain --line-numbers 2>/dev/null | grep REJECT" | sh | awk '{print $1}' | sort -nr | while read -r n; do + [ -n "$n" ] && [ "$n" -gt 0 ] && $cmd -t "$table" -D "$chain" "$n" 2>/dev/null + done + } + local _gfw_cleaned=0 + for _chain in fw_INPUT fw_OUTPUT; do + _remove_reject_rules filter "$_chain" ipv4 && _gfw_cleaned=$((_gfw_cleaned+1)) + _remove_reject_rules filter "$_chain" ipv6 + done + log Debug "Google 防火墙规则清理完成" + else + log Debug "Google 防火墙规则清理已禁用(clean_gfw_rules=false)" + fi + # ===================================== + # 执行 box_cgroup, box_run_bin, box_detected_port, box_bin_alive 函数 box_run_bin box_cgroup @@ -855,34 +920,23 @@ start_box() { stop_box() { stop_cron for bin in "${bin_list[@]}"; do - if busybox pgrep "${bin}" >/dev/null; then - if busybox pkill -15 "${bin}" >/dev/null; then - : - else - killall -15 "${bin}" >/dev/null 2>&1 || kill -15 "$(busybox pidof "${bin}")" >/dev/null 2>&1 - fi - fi + busybox pgrep "${bin}" >/dev/null || continue + busybox pkill -15 "${bin}" >/dev/null 2>&1 || killall -15 "${bin}" >/dev/null 2>&1 || kill -15 "$(busybox pidof "${bin}")" >/dev/null 2>&1 done - count=0 - while [ $count -lt 3 ]; do - if ! busybox pidof "${bin_name}" >/dev/null 2>&1; then - break - fi - sleep 1 - count=$((count + 1)) + # 等待进程真正退出(最多 6s),避免重启时新旧实例并存 + local i=0 + while busybox pidof "${bin_name}" >/dev/null 2>&1 && [ $i -lt 6 ]; do + sleep 1; i=$((i + 1)) done - if ! busybox pidof "${bin_name}" >/dev/null; then - if [ -f "${box_pid}" ]; then - rm -f "${box_pid}" - fi - log Warning "${bin_name} 已关闭,服务已停止。" - TOAST=1 log Warning "${bin_name} 已断开连接。" - [ -t 1 ] && echo -e "${white}━━━━━━━━━━━━━━━━━━${normal}" - else - log Warning "${bin_name} 未完全停止,可能正在关闭或关闭失败。" + if busybox pidof "${bin_name}" >/dev/null 2>&1; then + log Warning "${bin_name} ${i}s 内未退出,强制结束..." force_stop + else + [ -f "${box_pid}" ] && rm -f "${box_pid}" + TOAST=1 log Warning "${bin_name} 已停止" + [ -t 1 ] && echo -e "${white}━━━━━━━━━━━━━━━━━━${normal}" fi set_description stopped @@ -895,6 +949,13 @@ stop_cron() { kill -15 "${cron}" >/dev/null 2>&1 done fi + # 停止监控 daemon + local mon_pid + mon_pid=$(cat "${box_run_state}/monitor.daemon.pid" 2>/dev/null) + if [ -n "${mon_pid}" ] && kill -0 "${mon_pid}" 2>/dev/null; then + kill -9 "${mon_pid}" >/dev/null 2>&1 + fi + rm -f "${box_run_state}/monitor.daemon.pid" } force_stop() { @@ -939,6 +1000,17 @@ case "$1" in $scripts_dir/box.tool webroot >/dev/null 2>&1 fi ;; + restart-core) + local reason="" + if [ -n "$2" ]; then + reason="$2" + elif [ -f "${box_run_state}/restart_reason" ]; then + reason=$(head -n1 "${box_run_state}/restart_reason" 2>/dev/null) + rm -f "${box_run_state}/restart_reason" + fi + [ -z "$reason" ] && reason="手动执行 restart-core" + restart_core_only "$reason" + ;; status) # 检查服务是否在运行 if busybox pidof "${bin_name}" >/dev/null; then @@ -961,6 +1033,6 @@ case "$1" in ;; *) echo "${red}$0 '$1' 未找到${normal}" >&2 - echo "${yellow}用法${normal}: ${green}$0${normal} {${yellow}start|stop|restart|status|cron|kcron${normal}}" >&2 + echo "${yellow}用法${normal}: ${green}$0${normal} {${yellow}start|stop|restart|restart-core|status|cron|kcron${normal}}" >&2 ;; -esac \ No newline at end of file +esac diff --git a/box/scripts/net.inotify b/box/scripts/net.inotify index 82fbbf9c..d8841173 100644 --- a/box/scripts/net.inotify +++ b/box/scripts/net.inotify @@ -79,6 +79,16 @@ ensure_rule_append() { ${ipt_cmd} -t "${table_name}" -A "${chain_name}" "$@" >/dev/null 2>&1 } +# 等待 tun 接口 +until ip link show sing >/dev/null 2>&1; do sleep 0.5; done + +# 在路由表 8000 中添加默认路由 +ip -6 route add default dev sing table 8000 metric 1 + +# 添加高优先级的策略路由规则:所有 IPv6 流量都查表 8000 +ip -6 rule add from all lookup 8000 priority 1000 2>/dev/null +ip -6 rule add fwmark 0x8000/0x8000 lookup 8000 priority 1001 2>/dev/null + rules_add() { mkdir -p "${logs}" >/dev/null 2>&1 || true @@ -120,4 +130,4 @@ if [ "$events" = "w" ]; then rules_add fi fi -fi +fi \ No newline at end of file From e7ad825cf5027d7d1414e0ff62934ae1ab8b6e2c Mon Sep 17 00:00:00 2001 From: milangree <63492019+milangree@users.noreply.github.com> Date: Mon, 13 Apr 2026 00:06:59 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E7=9B=91=E6=8E=A7=E5=AE=88=E6=8A=A4?= =?UTF-8?q?=E9=85=8D=E5=A5=97=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- box/settings.ini | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/box/settings.ini b/box/settings.ini index 559e64c3..95503684 100755 --- a/box/settings.ini +++ b/box/settings.ini @@ -184,6 +184,52 @@ run_crontab="false" # 定时任务时间 interva_update="0 0,6,12,18 * * *" +# ════════════════════ +# 监控守护配置 (box.monitor) +# ════════════════════ + +# ── 无网络自动重启 +# 每隔 network_check_interval 秒检测一次,连续失败 N 次后自动重启核心 +enable_network_watchdog="true" +# 检测间隔(秒),最小 10s +network_check_interval="60" +# 连续失败次数阈值 +network_watchdog_timeout="5" +# HTTP 检测目标 +network_test_host="https://cp.cloudflare.com" + +# ── runs.log 输出过滤(仅针对 CPU 和网络监控,不影响 monitor.log)────── +# all — 写入全部日志(Info / Warning / Error) +# warning — 仅写入 Warning 和 Error +# error — 仅写入 Error +# none — 不写入 runs.log +monitor_runs_log_level="warning" + +# ── 重启日志(restart.log) +# restart.log 持续追加,不随每次启动备份轮换 +# true — 每次写入后清空 restart.log(只保留本次记录) +# false — 持续追加,不自动清除(默认) +clear_restart_log="false" + +# ── CPU 占用过高自动重启 +# 检测核心进程 CPU 占用,持续超标则重启 +enable_cpu_watchdog="true" +# 触发阈值(%),超过此值开始计时 +cpu_watchdog_threshold="90" +# 持续超标秒数达到此值后重启 +cpu_watchdog_duration="120" + +# ── 定时重启核心 +# 按 cron 表达式定时重启核心(独立于 run_crontab) +enable_scheduled_restart="false" +# cron 表达式(默认每天凌晨 4:00) +scheduled_restart_cron="0 4 * * *" + +# 启动时清理 ColorOS/OxygenOS 等系统注入的 Google 防火墙 REJECT 规则 +# true: 自动清理 fw_INPUT/fw_OUTPUT 链中的 REJECT 规则(推荐开启,解决特定机型代理失效问题) +# false: 跳过清理,对非 ColorOS 设备可关闭以节省启动时间 +clean_gfw_rules="false" + # ════════════════════ # 下载设置 # ════════════════════ @@ -249,4 +295,3 @@ log() { true fi } - From 93362ef0e782570ec8d7b871db2f77d1f2b3ee6e Mon Sep 17 00:00:00 2001 From: milangree Date: Tue, 14 Apr 2026 11:32:56 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E6=94=AF=E6=8C=81jsonc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- box/scripts/box.service | 378 +++++++++++++++++++++++++++++++--------- box/settings.ini | 70 ++++++-- 2 files changed, 352 insertions(+), 96 deletions(-) diff --git a/box/scripts/box.service b/box/scripts/box.service index 146b7f9b..6a6ba0d8 100755 --- a/box/scripts/box.service +++ b/box/scripts/box.service @@ -6,6 +6,206 @@ source /data/adb/box/settings.ini PROPFILE="/data/adb/modules/box_for_root/module.prop" RESTART_LOG="${box_run}/restart.log" # 与内核日志同目录 +prepare_jsonc_for_yq() { + local input_file="$1" + local stripped_file="$2" + local comments_file="$3" + + : > "${stripped_file}" + : > "${comments_file}" + + busybox awk -v stripped="${stripped_file}" -v comments="${comments_file}" ' + function add_comment(text) { + if (text == "") { + return + } + pending = pending text "\n" + } + function leading_ws(text, matched) { + if (match(text, /^[[:space:]]*/)) { + matched = substr(text, RSTART, RLENGTH) + return matched + } + return "" + } + function flush_pending(out, trimmed, indent) { + if (pending == "") { + return + } + + comment_id++ + print "__BOX_COMMENT_BLOCK__" >> comments + print comment_id >> comments + printf "%s", pending >> comments + print "__BOX_COMMENT_END__" >> comments + + indent = leading_ws(out) + trimmed = out + gsub(/^[[:space:]]+/, "", trimmed) + + if (trimmed ~ /^"[^"]+"[[:space:]]*:/) { + print indent "\"__box_comment_" comment_id "__\": \"__BOX_PLACEHOLDER__\"," >> stripped + } else { + print indent "\"__box_comment_" comment_id "__\"," >> stripped + } + + pending = "" + } + BEGIN { + pending = "" + block_comment = 0 + block_text = "" + comment_id = 0 + } + { + line = $0 + out = "" + in_string = 0 + escape = 0 + i = 1 + + while (i <= length(line)) { + c = substr(line, i, 1) + n = (i < length(line) ? substr(line, i + 1, 1) : "") + + if (block_comment) { + if (c == "*" && n == "/") { + block_text = block_text "*/" + add_comment(block_text) + block_text = "" + block_comment = 0 + i += 2 + } else { + block_text = block_text c + i++ + } + continue + } + + if (in_string) { + out = out c + if (escape) { + escape = 0 + } else if (c == "\\") { + escape = 1 + } else if (c == "\"") { + in_string = 0 + } + i++ + continue + } + + if (c == "\"") { + in_string = 1 + out = out c + i++ + continue + } + + if (c == "/" && n == "/") { + add_comment(substr(line, i)) + break + } + + if (c == "/" && n == "*") { + block_comment = 1 + block_text = "/*" + i += 2 + continue + } + + out = out c + i++ + } + + if (out ~ /[^[:space:]]/) { + flush_pending(out) + } + + print out >> stripped + + if (block_comment) { + block_text = block_text "\n" + } + } + END { + if (block_text != "") { + add_comment(block_text) + } + + if (pending != "") { + print "__BOX_COMMENT_BLOCK__" >> comments + print "EOF" >> comments + printf "%s", pending >> comments + print "__BOX_COMMENT_END__" >> comments + } + } + ' "${input_file}" +} + +restore_json_comments_file() { + local input_file="$1" + local comments_file="$2" + local tmp_file="${box_run_state}/.$(basename "${input_file}").restore" + + [ -s "${comments_file}" ] || return 0 + + busybox awk -v comments="${comments_file}" ' + BEGIN { + eof_block = "" + while ((getline line < comments) > 0) { + if (line != "__BOX_COMMENT_BLOCK__") { + continue + } + + if ((getline comment_id < comments) <= 0) { + break + } + + block = "" + while ((getline body < comments) > 0 && body != "__BOX_COMMENT_END__") { + block = block body "\n" + } + + if (comment_id == "EOF") { + eof_block = block + } else { + blocks[comment_id] = block + } + } + close(comments) + } + { + if (match($0, /^[[:space:]]*"__box_comment_([0-9]+)__"[[:space:]]*:[[:space:]]*"__BOX_PLACEHOLDER__"[[:space:]]*,?[[:space:]]*$/)) { + line = substr($0, RSTART, RLENGTH) + sub(/^[[:space:]]*"__box_comment_/, "", line) + sub(/__.*/, "", line) + if (line in blocks) { + printf "%s", blocks[line] + next + } + } + + if (match($0, /^[[:space:]]*"__box_comment_([0-9]+)__"[[:space:]]*,?[[:space:]]*$/)) { + line = substr($0, RSTART, RLENGTH) + sub(/^[[:space:]]*"__box_comment_/, "", line) + sub(/__.*/, "", line) + if (line in blocks) { + printf "%s", blocks[line] + next + } + } + + print $0 + } + END { + if (eof_block != "") { + printf "%s", eof_block + } + } + ' "${input_file}" > "${tmp_file}" && mv "${tmp_file}" "${input_file}" +} + find_packages_uid() { if [ "${proxy_mode}" = "core" ]; then > "${uid_list}" @@ -245,6 +445,22 @@ prepare_singbox() { yq="${box_dir}/bin/yq" fi + local stripped_sing_config="${box_run_state}/.$(basename "${sing_config}").json" + local sing_comments_file="${box_run_state}/.$(basename "${sing_config}").comments" + + # 兼容带注释的 JSONC 配置:先提取注释,再生成无注释 JSON 供 yq 修改 + if ! prepare_jsonc_for_yq "${sing_config}" "${stripped_sing_config}" "${sing_comments_file}"; then + log Error "预处理 ${sing_config} 失败,无法解析 JSON 注释" + set_description error "配置预处理失败" + exit 1 + fi + + if ! mv "${stripped_sing_config}" "${sing_config}"; then + log Error "写入 ${sing_config} 失败" + set_description error "配置预处理失败" + exit 1 + fi + # 设置 auto_detect_interface/auto_route ${yq} '.route.auto_detect_interface = true' -i --output-format=json "${sing_config}" ${yq} '(.inbounds[] | select(.type == "tun") | .auto_route) |= true' -i --output-format=json "${sing_config}" @@ -252,94 +468,98 @@ prepare_singbox() { # 确保日志路径是绝对路径 ${yq} '.log.output = "'"${box_run}/${bin_name}.log"'"' -i --output-format=json "${sing_config}" 2>/dev/null || true - # 检查 sing-box 配置 - if ( cd "$(dirname "${sing_config}")" && "${bin_path}" check -c "$(basename "${sing_config}")" ) > "${box_run}/${bin_name}.log" 2>&1; then - # 根据 network_mode 设置 auto_route - if [[ "${network_mode}" == @(mixed|tun) ]]; then - # 检查配置中是否有 "type": "tun" - if ! busybox grep -q '"type": "tun"' "${sing_config}"; then - # 若无则添加 "tun" 配置 - ${yq} '.inbounds += [{ - "type": "tun", - "tag": "tun-in", - "interface_name": "sing", - "address": ["172.18.0.1/30","fdfe:dcba:9876::1/126"], - "mtu": 9000, - "stack": "mixed", - "auto_route": true, - "strict_route": true, - "auto_redirect": true, - "include_android_user": [0,10,999], - "include_uid": [], - "exclude_uid": [] - }]' -i --output-format=json "${sing_config}" - log Debug "已为 ${sing_config} 添加 [Tun] 配置" - fi - else - # 非 tun 模式,关闭 auto_route - # sed -i -e 's/auto_route": true/auto_route": false/g' -e 's/"auto_detect_interface": true/"auto_detect_interface": false/g' "${sing_config}" - - # 检查是否有 tproxy 配置 - if ! busybox grep -q '"type": "tproxy"' "${sing_config}"; then - # 若无则添加 tproxy 配置 - ${yq} '.inbounds += [{"type": "tproxy", "tag": "tproxy-in", "listen": "::", "listen_port": '"${tproxy_port}"'}]' -i --output-format=json "${sing_config}" - log Debug "已为 ${sing_config} 添加 [Tproxy] 配置" - fi + # 根据 network_mode 设置 auto_route + if [[ "${network_mode}" == @(mixed|tun) ]]; then + # 检查配置中是否有 "type": "tun" + if ! busybox grep -q '"type": "tun"' "${sing_config}"; then + # 若无则添加 "tun" 配置 + ${yq} '.inbounds += [{ + "type": "tun", + "tag": "tun-in", + "interface_name": "sing", + "address": ["172.18.0.1/30","fdfe:dcba:9876::1/126"], + "mtu": 9000, + "stack": "mixed", + "auto_route": true, + "strict_route": true, + "auto_redirect": true, + "include_android_user": [0,10,999], + "include_uid": [], + "exclude_uid": [] + }]' -i --output-format=json "${sing_config}" + log Debug "已为 ${sing_config} 添加 [Tun] 配置" + fi + else + # 非 tun 模式,关闭 auto_route + # sed -i -e 's/auto_route": true/auto_route": false/g' -e 's/"auto_detect_interface": true/"auto_detect_interface": false/g' "${sing_config}" + + # 检查是否有 tproxy 配置 + if ! busybox grep -q '"type": "tproxy"' "${sing_config}"; then + # 若无则添加 tproxy 配置 + ${yq} '.inbounds += [{"type": "tproxy", "tag": "tproxy-in", "listen": "::", "listen_port": '"${tproxy_port}"'}]' -i --output-format=json "${sing_config}" + log Debug "已为 ${sing_config} 添加 [Tproxy] 配置" + fi - # 删除 tun 配置 - ${yq} 'del(.inbounds[] | select(.type == "tun"))' -i --output-format=json "${sing_config}" + # 删除 tun 配置 + ${yq} 'del(.inbounds[] | select(.type == "tun"))' -i --output-format=json "${sing_config}" - # 同步 tproxy 端口 - if busybox grep -q '"type": "tproxy"' "${sing_config}"; then - ${yq} '(.inbounds[] | select(.type == "tproxy") | .listen_port) = '"${tproxy_port}" -i --output-format=json "${sing_config}" - fi + # 同步 tproxy 端口 + if busybox grep -q '"type": "tproxy"' "${sing_config}"; then + ${yq} '(.inbounds[] | select(.type == "tproxy") | .listen_port) = '"${tproxy_port}" -i --output-format=json "${sing_config}" fi + fi - if [[ "${network_mode}" == @(mixed|tun) ]]; then - # core 模式:跳过 UID 处理 - if [ "${proxy_mode}" = "core" ]; then - yq_filter='(.inbounds[] | select(.type == "tun") | .include_uid) = [] | - (.inbounds[] | select(.type == "tun") | .exclude_uid) = []' - "${yq}" eval "$yq_filter" -i --output-format=json "${sing_config}" > /dev/null 2>&1 - log_service_results "sing-box" "core" "" - else - # 为 tun 添加 include_uid/exclude_uid 字段 - yq_filter='(.inbounds[] | select(.type == "tun") | .include_uid) = [] | - (.inbounds[] | select(.type == "tun") | .exclude_uid) = []' - - [[ ${proxy_mode} = "blacklist" || ${proxy_mode} = "black" ]] && mode="exclude" || mode="include" - - local uids_to_add="" - if [ -s "${uid_list}" ]; then - uids_to_add=$(busybox tr '\n' ',' < "${uid_list}" | sed 's/,$//') - if [ -n "$uids_to_add" ]; then - yq_filter="$yq_filter | (.inbounds[] | select(.type == \"tun\") | .${mode}_uid) = [${uids_to_add}]" - fi + if [[ "${network_mode}" == @(mixed|tun) ]]; then + # core 模式:跳过 UID 处理 + if [ "${proxy_mode}" = "core" ]; then + yq_filter='(.inbounds[] | select(.type == "tun") | .include_uid) = [] | + (.inbounds[] | select(.type == "tun") | .exclude_uid) = []' + "${yq}" eval "$yq_filter" -i --output-format=json "${sing_config}" > /dev/null 2>&1 + log_service_results "sing-box" "core" "" + else + # 为 tun 添加 include_uid/exclude_uid 字段 + yq_filter='(.inbounds[] | select(.type == "tun") | .include_uid) = [] | + (.inbounds[] | select(.type == "tun") | .exclude_uid) = []' + + [[ ${proxy_mode} = "blacklist" || ${proxy_mode} = "black" ]] && mode="exclude" || mode="include" + + local uids_to_add="" + if [ -s "${uid_list}" ]; then + uids_to_add=$(busybox tr '\n' ',' < "${uid_list}" | sed 's/,$//') + if [ -n "$uids_to_add" ]; then + yq_filter="$yq_filter | (.inbounds[] | select(.type == \"tun\") | .${mode}_uid) = [${uids_to_add}]" fi - "${yq}" eval "$yq_filter" -i --output-format=json "${sing_config}" > /dev/null 2>&1 - log_service_results "sing-box" "$mode" "$uids_to_add" fi + "${yq}" eval "$yq_filter" -i --output-format=json "${sing_config}" > /dev/null 2>&1 + log_service_results "sing-box" "$mode" "$uids_to_add" fi + fi - # 按 network_mode 添加 redirect 配置 - if [[ "${network_mode}" == @(mixed|enhance|redirect) ]]; then - if ! busybox grep -q '"type": "redirect"' "${sing_config}"; then - # 若无则添加 redirect 配置 - ${yq} '.inbounds += [{ - "type": "redirect", - "tag": "redirect-in", - "listen": "::", - "listen_port": '"${redir_port}"' - }]' -i --output-format=json "${sing_config}" - log Debug "已为 ${sing_config} 添加 [Redirect] 配置" - fi + # 按 network_mode 添加 redirect 配置 + if [[ "${network_mode}" == @(mixed|enhance|redirect) ]]; then + if ! busybox grep -q '"type": "redirect"' "${sing_config}"; then + # 若无则添加 redirect 配置 + ${yq} '.inbounds += [{ + "type": "redirect", + "tag": "redirect-in", + "listen": "::", + "listen_port": '"${redir_port}"' + }]' -i --output-format=json "${sing_config}" + log Debug "已为 ${sing_config} 添加 [Redirect] 配置" + fi - # 同步 redir_port 端口 - if busybox grep -q '"type": "redirect"' "${sing_config}"; then - ${yq} '(.inbounds[] | select(.type == "redirect") | .listen_port) = '"${redir_port}" -i --output-format=json "${sing_config}" - fi + # 同步 redir_port 端口 + if busybox grep -q '"type": "redirect"' "${sing_config}"; then + ${yq} '(.inbounds[] | select(.type == "redirect") | .listen_port) = '"${redir_port}" -i --output-format=json "${sing_config}" fi - else + fi + + if ! restore_json_comments_file "${sing_config}" "${sing_comments_file}"; then + log Warning "恢复 ${sing_config} 注释失败,已保留 yq 处理后的配置" + fi + + # 在所有 yq 修改和注释恢复完成后再检查 sing-box 配置 + if ! ( cd "$(dirname "${sing_config}")" && "${bin_path}" check -c "$(basename "${sing_config}")" ) > "${box_run}/${bin_name}.log" 2>&1; then log Error "$(<"${box_run}/${bin_name}.log")" log Error "配置失败,请检查 ${box_run}/${bin_name}.log 文件。" set_description error @@ -641,7 +861,7 @@ box_bin_status() { # 协议栈 / 运行时长 / 温度 local stack user_group running_time temperature_celsius if [ "${bin_name}" != "mihomo" ]; then - stack=$(find "/data/adb/box/sing-box" -type f -name "*.json" -exec busybox awk -F'"' '/"stack"/{print $4}' {} + 2>/dev/null | head -1) + stack=$(busybox awk -F'"' '/"stack"/{print $4; exit}' "${sing_config}" 2>/dev/null) else stack=$(busybox awk '!/^ *#/ && /stack: /{print $2; exit}' "${mihomo_config}" 2>/dev/null) fi diff --git a/box/settings.ini b/box/settings.ini index 95503684..d0ed99dd 100755 --- a/box/settings.ini +++ b/box/settings.ini @@ -39,7 +39,7 @@ box_user_group="root:net_admin" # 代理核心配置 # ════════════════════ -bin_name="mihomo" +bin_name="sing-box" bin_list=("mihomo" "sing-box" "xray" "v2fly" "hysteria") bin_path="${bin_dir}/${bin_name}" bin_log="${box_run}/${bin_name}.log" @@ -138,8 +138,8 @@ inotify_log_enabled="true" cgroup_memcg="false" memcg_limit="100M" # CPU 核心分配 -cgroup_cpuset="false" -allow_cpu="0-7" +cgroup_cpuset="true" +allow_cpu="0-5" # 磁盘 I/O 权重 cgroup_blkio="false" weight="" @@ -153,7 +153,7 @@ name_mihomo_config="config.yaml" mihomo_config="${box_dir}/mihomo/${name_mihomo_config}" # Sing-box 配置文件 -name_sing_config="config.json" +name_sing_config="config-test.json" sing_config="${box_dir}/sing-box/${name_sing_config}" # Xray 配置文件 @@ -175,12 +175,12 @@ hysteria_config="${box_dir}/hysteria/${name_hysteria_config}" # true: 覆盖配置文件, false: 只更新订阅 renew="false" # 是否启用订阅自动更新 -update_subscription="false" +update_subscription="true" # 是否启用 GeoX 数据自动更新 update_geo="false" # 是否启用定时任务 -run_crontab="false" +run_crontab="true" # 定时任务时间 interva_update="0 0,6,12,18 * * *" @@ -237,9 +237,9 @@ clean_gfw_rules="false" # GitHub 访问令牌 githubtoken="" # 是否使用镜像加速 GitHub 下载 -use_ghproxy="false" +use_ghproxy="true" # 镜像加速地址 -url_ghproxy="https://ghfast.top" +url_ghproxy="https://v6.gh-proxy.org/" # 核心版本: enable (稳定版) / disable (预发行版) mihomo_stable="enable" @@ -264,6 +264,25 @@ gid_list=($(busybox awk '!/^#/ && /^[0-9]+$/ {print $1}' "${gid_config}")) ap_list=($(busybox awk '/^allow / {print $2}' "${write_listap}")) ignore_ap_list=($(busybox awk '/^ignore / {print $2}' "${write_listap}")) +# ════════════════════ +# 日志系统开关 +# ════════════════════ + +# box.service 日志过滤(控制 Debug/Info 等级别写入 runs.log 的策略) +# all — 写入全部日志(Info / Debug / Warning / Error) +# nodebug — 过滤 Debug,只写 Info / Warning / Error +# warning — 仅写入 Warning 和 Error +# error — 仅写入 Error +# none — 不写入 runs.log +service_log_filter="debug" + +# runs.log 最大行数(超过后保留末尾 N 行),0 表示不限制 +runs_log_max_lines="1000" +# monitor.log 最大行数 +monitor_log_max_lines="500" +# restart.log 最大行数 +restart_log_max_lines="200" + # ════════════════════ # 日志系统(请勿修改) # ════════════════════ @@ -274,24 +293,41 @@ log() { local green="\033[1;32m" local yellow="\033[1;33m" local blue="\033[1;34m" + local cyan="\033[1;36m" local color - + case $1 in - Info) color="${blue}" ;; - Error) color="${red}" ;; + Info) color="${blue}" ;; + Error) color="${red}" ;; Warning) color="${yellow}" ;; - *) color="${green}" ;; + Debug) color="${cyan}" ;; + *) color="${green}" ;; esac - - local message="${current_time} [$1]: $2" - + + # 每次调用都取当前时间,避免使用 source 时固化的 current_time + local _ts + _ts="$(date '+%Y-%m-%d %H:%M:%S')" + local message="${_ts} [$1]: $2" + if [ -t 1 ]; then echo -e "${color}${message}${normal}" else - echo "${message}" | tee -a "${box_log}" + # 按 service_log_filter 过滤写入 runs.log + # all(默认) / nodebug / warning / error / none + local _filter="${service_log_filter:-all}" + local _do_write=true + case "${_filter}" in + all) _do_write=true ;; + nodebug) [ "$1" = "Debug" ] && _do_write=false ;; + warning) [ "$1" != "Warning" ] && [ "$1" != "Error" ] && _do_write=false ;; + error) [ "$1" != "Error" ] && _do_write=false ;; + none) _do_write=false ;; + esac + [ "${_do_write}" = "true" ] && echo "${message}" | tee -a "${box_log}" fi - + if [[ $TOAST ]]; then true fi } + From f5827442d1278e5979151b2831a7fb06a8d0239a Mon Sep 17 00:00:00 2001 From: milangree <63492019+milangree@users.noreply.github.com> Date: Wed, 15 Apr 2026 08:44:30 +0800 Subject: [PATCH 4/4] Update settings.ini --- box/settings.ini | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/box/settings.ini b/box/settings.ini index d0ed99dd..f1cf2a5f 100755 --- a/box/settings.ini +++ b/box/settings.ini @@ -39,7 +39,7 @@ box_user_group="root:net_admin" # 代理核心配置 # ════════════════════ -bin_name="sing-box" +bin_name="mihomo" bin_list=("mihomo" "sing-box" "xray" "v2fly" "hysteria") bin_path="${bin_dir}/${bin_name}" bin_log="${box_run}/${bin_name}.log" @@ -175,12 +175,12 @@ hysteria_config="${box_dir}/hysteria/${name_hysteria_config}" # true: 覆盖配置文件, false: 只更新订阅 renew="false" # 是否启用订阅自动更新 -update_subscription="true" +update_subscription="false" # 是否启用 GeoX 数据自动更新 update_geo="false" # 是否启用定时任务 -run_crontab="true" +run_crontab="false" # 定时任务时间 interva_update="0 0,6,12,18 * * *" @@ -190,7 +190,7 @@ interva_update="0 0,6,12,18 * * *" # ── 无网络自动重启 # 每隔 network_check_interval 秒检测一次,连续失败 N 次后自动重启核心 -enable_network_watchdog="true" +enable_network_watchdog="false" # 检测间隔(秒),最小 10s network_check_interval="60" # 连续失败次数阈值 @@ -213,7 +213,7 @@ clear_restart_log="false" # ── CPU 占用过高自动重启 # 检测核心进程 CPU 占用,持续超标则重启 -enable_cpu_watchdog="true" +enable_cpu_watchdog="false" # 触发阈值(%),超过此值开始计时 cpu_watchdog_threshold="90" # 持续超标秒数达到此值后重启