Ver código fonte

fix: for router bug

Dew-OF-Aurora 17 horas atrás
pai
commit
2de6fb18ec

+ 1 - 0
CLAUDE.md

@@ -71,6 +71,7 @@ GIT_FORCE_COMMIT=1 bash scripts/run_update_and_commit.sh config.server.json
 ```bash
 sh scripts/router_local_update.sh ./router_local.conf
 sh scripts/router_local_http.sh ./router_local.conf
+./build_router_package.sh
 ```
 
 ### Debian systemd install/uninstall

+ 42 - 7
README.md

@@ -29,6 +29,8 @@
   BusyBox `sh` 路由器入口,执行 `cfst` 并写出 `cfip_runtime`
 - `scripts/router_local_http.sh`
   BusyBox `sh` 路由器 HTTP 暴露入口,优先使用 BusyBox `httpd` 暴露运行时文件
+- `build_router_package.sh`
+  路由器部署包打包脚本,按 `/jffs/vmess` 推荐目录结构生成 `vmess.tar.gz`
 
 配置文件:
 
@@ -270,8 +272,8 @@ sudo bash scripts/uninstall_debian.sh --keep-auth-files
 
 它当前默认指向:
 
-- `./cfst_darwin_arm64/cfst`
-- 结果文件 `./cfst_darwin_arm64/result.csv`
+- `./cfst_linux_armv5/cfst`
+- 结果文件 `./cfst_linux_armv5/result.csv`
 - 输出目录 `./cfip_runtime`
 
 你通常只需要修改:
@@ -402,9 +404,16 @@ sh scripts/router_local_update.sh ./router_local.conf
 成功时会输出类似:
 
 ```text
-[router-local] selected ip: x.x.x.x
+[router-local] update_at=2026-05-08T08:00:00Z selected_ip=x.x.x.x candidates=10
+[router-local] candidate_1 ip=x.x.x.x sent=... recv=... loss=... latency=... speed=... region=...
 ```
 
+日志行为说明:
+
+- 终端手动执行(stdout 是 TTY)时,会显示 `cfst` 原始输出,便于排错
+- 重定向到日志或后台执行时(例如 `>> /tmp/router_local_update.log 2>&1`),默认静默 `cfst` 原始输出,仅保留脚本摘要日志
+- 可通过 `CFST_SHOW_LOG_ON_TTY`、`CFST_QUIET_LOG`、`CFST_DEBUG` 组合控制日志详细程度
+
 生成文件默认在:
 
 - `cfip_runtime/current_ip.txt`
@@ -489,7 +498,18 @@ start_vmess_http() {
 }
 
 cd "$VMESS_DIR" || exit 1
-sh scripts/router_local_update.sh "$CONFIG" >> "$UPDATE_LOG" 2>&1
+
+# 开机后先等待系统和网络稳定,避免 cfst 启动太早拿不到结果。
+sleep 60
+
+i=1
+while [ "$i" -le 5 ]; do
+  if sh scripts/router_local_update.sh "$CONFIG" >> "$UPDATE_LOG" 2>&1; then
+    break
+  fi
+  sleep 20
+  i=$((i + 1))
+done
 
 cru d vmess_rotate
 cru a vmess_rotate "0 */3 * * * cd $VMESS_DIR && sh scripts/router_local_update.sh $CONFIG >> $UPDATE_LOG 2>&1"
@@ -501,7 +521,7 @@ cru d vmess_watchdog
 cru a vmess_watchdog "*/5 * * * * if ! ps | grep -q '$HTTP_PROCESS_PATTERN'; then cd $VMESS_DIR && nohup sh scripts/router_local_http.sh $CONFIG >> $HTTP_LOG 2>&1 & fi"
 ```
 
-这里用 `[r]outer_local_http.sh` 作为 `grep` 关键字,是为了避免 watchdog 的 shell 命令在 `ps` 里匹配到自己。`router_local_http.sh` 会保留 wrapper 进程并等待子进程里的完整 BusyBox `httpd`,所以 watchdog 用这个关键字可以正常判断服务是否仍在运行。示例里先 `cru d` 再 `cru a`,避免 `services-start` 被重复触发后留下旧的定时任务定义;启动 HTTP 前也会先检查进程,避免重复监听同一个端口。
+这里用 `[r]outer_local_http.sh` 作为 `grep` 关键字,是为了避免 watchdog 的 shell 命令在 `ps` 里匹配到自己。`router_local_http.sh` 会保留 wrapper 进程并等待子进程里的完整 BusyBox `httpd`,所以 watchdog 用这个关键字可以正常判断服务是否仍在运行。示例里先 `cru d` 再 `cru a`,避免 `services-start` 被重复触发后留下旧的定时任务定义;启动 HTTP 前也会先检查进程,避免重复监听同一个端口。开机首次更新前加入 `sleep + retry`,是为了避免网络尚未就绪时出现 `result file missing or empty`。
 
 如果你的固件没有 KoolShare,删除 `/koolshare/bin/ks-services-start.sh` 那一行即可;如果 Web UI 里有“启用 JFFS 自定义脚本”的选项,需要先开启,否则 `/jffs/scripts/services-start` 不会在开机时执行。
 
@@ -510,7 +530,7 @@ cru a vmess_watchdog "*/5 * * * * if ! ps | grep -q '$HTTP_PROCESS_PATTERN'; the
 可以用 BusyBox `crond` 定时更新,例如每 15 分钟执行一次:
 
 ```cron
-*/15 * * * * cd /tmp/home/root/vmess-domain-rotator && sh scripts/router_local_update.sh ./router_local.conf >> /tmp/router_local_update.log 2>&1
+*/15 * * * * cd /jffs/vmess && sh scripts/router_local_update.sh ./router_local.conf >> /tmp/router_local_update.log 2>&1
 ```
 
 ### 6.8 手动常驻 HTTP
@@ -518,7 +538,7 @@ cru a vmess_watchdog "*/5 * * * * if ! ps | grep -q '$HTTP_PROCESS_PATTERN'; the
 HTTP 服务如果需要手动后台启动:
 
 ```bash
-cd /tmp/home/root/vmess-domain-rotator
+cd /jffs/vmess
 nohup sh scripts/router_local_http.sh ./router_local.conf >/tmp/router_http.log 2>&1 &
 ```
 
@@ -543,6 +563,21 @@ cat cfip_runtime/current_ip.txt
 cat cfip_runtime/current_ip.json
 ```
 
+路由器包打包(按 `/jffs/vmess` 目录结构):
+
+```bash
+./build_router_package.sh
+```
+
+可选覆盖:
+
+```bash
+BUSYBOX_SRC="/path/to/busybox_armv7l" \
+CFST_SRC_DIR="/path/to/cfst_dir" \
+OUT_TAR="/path/to/vmess.tar.gz" \
+./build_router_package.sh
+```
+
 ## 8. 注意事项
 
 1. 服务器模式和路由器模式的配置不要混用。

+ 139 - 0
build_router_package.sh

@@ -0,0 +1,139 @@
+#!/usr/bin/env sh
+set -eu
+
+SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
+APP_DIR="$SCRIPT_DIR"
+
+PKG_ROOT_NAME=${PKG_ROOT_NAME:-vmess}
+OUT_TAR=${OUT_TAR:-"$APP_DIR/vmess.tar.gz"}
+TMP_DIR=${TMP_DIR:-"/tmp/vmess_pkg.$$"}
+
+ROUTER_CONF=${ROUTER_CONF:-"$APP_DIR/router_local.conf"}
+HTTP_SCRIPT=${HTTP_SCRIPT:-"$APP_DIR/scripts/router_local_http.sh"}
+UPDATE_SCRIPT=${UPDATE_SCRIPT:-"$APP_DIR/scripts/router_local_update.sh"}
+
+# Defaults aligned with current repo layout.
+BUSYBOX_SRC=${BUSYBOX_SRC:-"$APP_DIR/busybox/busybox-armv7l"}
+CFST_SRC_DIR=${CFST_SRC_DIR:-"$APP_DIR/cfst_linux_armv5"}
+
+VERBOSE=${VERBOSE:-1}
+
+log() {
+  if [ "${VERBOSE}" = "1" ]; then
+    printf '%s\n' "$*"
+  fi
+}
+
+warn() {
+  printf '[warn] %s\n' "$*" >&2
+}
+
+err() {
+  printf '[error] %s\n' "$*" >&2
+  exit 1
+}
+
+require_file() {
+  p="$1"
+  [ -f "$p" ] || err "required file missing: $p"
+}
+
+require_dir() {
+  p="$1"
+  [ -d "$p" ] || err "required directory missing: $p"
+}
+
+cleanup() {
+  rm -rf "$TMP_DIR"
+}
+trap cleanup EXIT INT TERM
+
+parse_conf_var() {
+  conf="$1"
+  key="$2"
+  if [ ! -r "$conf" ]; then
+    return 1
+  fi
+  sed -n "s|^${key}=\"\\(.*\\)\"$|\\1|p" "$conf" | head -n 1
+}
+
+resolve_from_conf_dir() {
+  conf="$1"
+  val="$2"
+  conf_dir=$(CDPATH= cd -- "$(dirname -- "$conf")" && pwd)
+  case "$val" in
+    "") printf '%s\n' "" ;;
+    /*) printf '%s\n' "$val" ;;
+    *) printf '%s/%s\n' "$conf_dir" "$val" ;;
+  esac
+}
+
+check_busybox_httpd() {
+  bb="$1"
+  if [ ! -x "$bb" ]; then
+    warn "BusyBox is not executable: $bb (run: chmod +x \"$bb\")"
+    return 1
+  fi
+  if "$bb" --list 2>/dev/null | grep '^httpd$' >/dev/null 2>&1; then
+    return 0
+  fi
+  warn "BusyBox has no httpd applet: $bb"
+  return 1
+}
+
+log "[pkg] app dir: $APP_DIR"
+log "[pkg] output: $OUT_TAR"
+
+require_file "$ROUTER_CONF"
+require_file "$HTTP_SCRIPT"
+require_file "$UPDATE_SCRIPT"
+require_dir "$CFST_SRC_DIR"
+require_file "$BUSYBOX_SRC"
+
+bb_from_conf_raw=$(parse_conf_var "$ROUTER_CONF" "BUSYBOX_BIN" || true)
+if [ -n "${bb_from_conf_raw:-}" ]; then
+  bb_from_conf=$(resolve_from_conf_dir "$ROUTER_CONF" "$bb_from_conf_raw")
+  if [ "$bb_from_conf" != "$BUSYBOX_SRC" ]; then
+    warn "BUSYBOX_BIN in conf points to: $bb_from_conf_raw -> $bb_from_conf"
+    warn "packaging source busybox is: $BUSYBOX_SRC"
+    warn "if needed, set BUSYBOX_SRC to keep packaging and runtime consistent"
+  fi
+fi
+
+if ! check_busybox_httpd "$BUSYBOX_SRC"; then
+  warn "continuing packaging, but router HTTP may fail later"
+fi
+
+mkdir -p "$TMP_DIR/$PKG_ROOT_NAME/scripts" "$TMP_DIR/$PKG_ROOT_NAME/cfst"
+
+cp "$ROUTER_CONF" "$TMP_DIR/$PKG_ROOT_NAME/router_local.conf"
+cp "$HTTP_SCRIPT" "$TMP_DIR/$PKG_ROOT_NAME/scripts/router_local_http.sh"
+cp "$UPDATE_SCRIPT" "$TMP_DIR/$PKG_ROOT_NAME/scripts/router_local_update.sh"
+cp "$BUSYBOX_SRC" "$TMP_DIR/$PKG_ROOT_NAME/busybox_armv7l"
+cp -R "$CFST_SRC_DIR"/. "$TMP_DIR/$PKG_ROOT_NAME/cfst/"
+
+chmod +x \
+  "$TMP_DIR/$PKG_ROOT_NAME/busybox_armv7l" \
+  "$TMP_DIR/$PKG_ROOT_NAME/scripts/router_local_http.sh" \
+  "$TMP_DIR/$PKG_ROOT_NAME/scripts/router_local_update.sh" || true
+
+rm -f "$OUT_TAR"
+tar -czf "$OUT_TAR" -C "$TMP_DIR" "$PKG_ROOT_NAME"
+
+log "[pkg] tar created: $OUT_TAR"
+log "[pkg] tar size: $(ls -lh "$OUT_TAR" | awk '{print $5}')"
+
+log "[pkg] tar content preview:"
+tar -tzf "$OUT_TAR" | sed -n '1,120p'
+
+if ! tar -tzf "$OUT_TAR" | grep "^$PKG_ROOT_NAME/busybox_armv7l$" >/dev/null; then
+  err "package validation failed: busybox_armv7l missing"
+fi
+if ! tar -tzf "$OUT_TAR" | grep "^$PKG_ROOT_NAME/scripts/router_local_http.sh$" >/dev/null; then
+  err "package validation failed: router_local_http.sh missing"
+fi
+if ! tar -tzf "$OUT_TAR" | grep "^$PKG_ROOT_NAME/cfst/cfst$" >/dev/null; then
+  err "package validation failed: cfst binary missing"
+fi
+
+log "[pkg] validation passed"

+ 1 - 1
config.router.json

@@ -3,7 +3,7 @@
     "type": "cfst_local"
   },
   "cfst_local": {
-    "work_dir": "./cfst_darwin_arm64",
+    "work_dir": "./cfst_linux_armv5",
     "binary": "./cfst",
     "run_args": [
       "-f",

+ 59 - 1
scripts/router_local_http.sh

@@ -4,6 +4,7 @@ set -eu
 SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
 APP_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
 CONFIG_PATH=${1:-"$APP_DIR/router_local.conf"}
+CONFIG_DIR=$(CDPATH= cd -- "$(dirname -- "$CONFIG_PATH")" && pwd)
 
 if [ ! -r "$CONFIG_PATH" ]; then
   echo "[router-http] config not found: $CONFIG_PATH" >&2
@@ -18,10 +19,33 @@ RUNTIME_DIR=${RUNTIME_DIR:-"$APP_DIR/cfip_runtime"}
 VALUE_TEXT_FILE=${VALUE_TEXT_FILE:-"current_ip.txt"}
 HTTP_PORT=${HTTP_PORT:-8080}
 
+# Trim possible CRLF endings from config values edited on Windows/Web UI.
+CR=$(printf '\r')
+BUSYBOX_BIN=${BUSYBOX_BIN%"$CR"}
+RUNTIME_DIR=${RUNTIME_DIR%"$CR"}
+VALUE_TEXT_FILE=${VALUE_TEXT_FILE%"$CR"}
+HTTP_PORT=${HTTP_PORT%"$CR"}
+
 TEXT_PATH="$RUNTIME_DIR/$VALUE_TEXT_FILE"
 INDEX_PATH="$RUNTIME_DIR/index.html"
 
+resolve_path_from_config() {
+  p="$1"
+  case "$p" in
+    /*) printf '%s\n' "$p" ;;
+    *) printf '%s/%s\n' "$CONFIG_DIR" "$p" ;;
+  esac
+}
+
 resolve_busybox() {
+  if [ -n "$BUSYBOX_BIN" ]; then
+    busybox_from_cfg=$(resolve_path_from_config "$BUSYBOX_BIN")
+    if [ -x "$busybox_from_cfg" ]; then
+      printf '%s\n' "$busybox_from_cfg"
+      return 0
+    fi
+  fi
+
   if [ -n "$BUSYBOX_BIN" ] && [ -x "$BUSYBOX_BIN" ]; then
     printf '%s\n' "$BUSYBOX_BIN"
     return 0
@@ -40,10 +64,43 @@ resolve_busybox() {
   return 1
 }
 
+print_busybox_debug() {
+  echo "[router-http] debug CONFIG_PATH=$CONFIG_PATH" >&2
+  echo "[router-http] debug CONFIG_DIR=$CONFIG_DIR" >&2
+  echo "[router-http] debug APP_DIR=$APP_DIR" >&2
+  echo "[router-http] debug BUSYBOX_BIN(raw)=$BUSYBOX_BIN" >&2
+
+  resolved_from_cfg=$(resolve_path_from_config "$BUSYBOX_BIN")
+  echo "[router-http] debug BUSYBOX_BIN(resolved)=$resolved_from_cfg" >&2
+
+  if [ -e "$resolved_from_cfg" ]; then
+    if [ -x "$resolved_from_cfg" ]; then
+      echo "[router-http] debug resolved BusyBox exists and is executable" >&2
+    else
+      echo "[router-http] debug resolved BusyBox exists but is NOT executable; run: chmod +x $resolved_from_cfg" >&2
+    fi
+  else
+    echo "[router-http] debug resolved BusyBox does not exist" >&2
+  fi
+
+  if [ -e "$APP_DIR/busybox_armv7l" ]; then
+    if [ -x "$APP_DIR/busybox_armv7l" ]; then
+      echo "[router-http] debug fallback $APP_DIR/busybox_armv7l exists and is executable" >&2
+    else
+      echo "[router-http] debug fallback $APP_DIR/busybox_armv7l exists but is NOT executable; run: chmod +x $APP_DIR/busybox_armv7l" >&2
+    fi
+  else
+    echo "[router-http] debug fallback $APP_DIR/busybox_armv7l does not exist" >&2
+  fi
+}
+
 busybox_has_applet() {
   busybox_bin="$1"
   applet="$2"
-  "$busybox_bin" --list 2>/dev/null | grep "^$applet$" >/dev/null
+  if "$busybox_bin" --list 2>/dev/null | grep "^$applet$" >/dev/null; then
+    return 0
+  fi
+  "$busybox_bin" 2>/dev/null | grep "[[:space:]]$applet[,[:space:]]" >/dev/null
 }
 
 ensure_index() {
@@ -74,6 +131,7 @@ start_httpd() {
     echo "[router-http] ASUS firmware httpd is usually the router admin web server and does not support serving an arbitrary -h directory" >&2
   fi
 
+  print_busybox_debug
   echo "[router-http] usable BusyBox httpd applet not found" >&2
   echo "[router-http] set BUSYBOX_BIN in router_local.conf to your full BusyBox binary, for example: BUSYBOX_BIN=\"./busybox_armv7l\"" >&2
   exit 1

+ 42 - 6
scripts/router_local_update.sh

@@ -35,6 +35,8 @@ CFST_DISABLE_DOWNLOAD=${CFST_DISABLE_DOWNLOAD:-0}
 CFST_ALL_IP=${CFST_ALL_IP:-0}
 CFST_DEBUG=${CFST_DEBUG:-0}
 CFST_SKIP_RUN=${CFST_SKIP_RUN:-0}
+CFST_QUIET_LOG=${CFST_QUIET_LOG:-1}
+CFST_SHOW_LOG_ON_TTY=${CFST_SHOW_LOG_ON_TTY:-1}
 
 TOP_N=${TOP_N:-3}
 RUNTIME_DIR=${RUNTIME_DIR:-"$APP_DIR/cfip_runtime"}
@@ -69,10 +71,6 @@ json_escape() {
   printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
 }
 
-trim_cr() {
-  printf '%s' "$1" | tr -d '\r'
-}
-
 build_top_candidates_json() {
   awk -F',' -v top_n="$TOP_N" '
     function trim(v) {
@@ -243,9 +241,47 @@ run_cfst() (
     set -- "$@" -debug
   fi
 
-  "$@"
+  # Show cfst live logs for manual interactive runs, keep quiet for redirected/background runs.
+  is_interactive_tty=0
+  if [ "$CFST_SHOW_LOG_ON_TTY" = "1" ] && [ -t 1 ]; then
+    is_interactive_tty=1
+  fi
+
+  if [ "$CFST_QUIET_LOG" = "1" ] && [ "$CFST_DEBUG" != "1" ] && [ "$is_interactive_tty" != "1" ]; then
+    "$@" >/dev/null 2>&1
+  else
+    "$@"
+  fi
 )
 
+log_update_summary() {
+  selected_ip="$1"
+  source_count="$2"
+  echo "[router-local] update_at=$UPDATED_AT selected_ip=$selected_ip candidates=$source_count"
+  awk -F',' -v top_n="$TOP_N" '
+    function trim(v) {
+      gsub(/\r/, "", v)
+      sub(/^[[:space:]]+/, "", v)
+      sub(/[[:space:]]+$/, "", v)
+      return v
+    }
+    NR == 1 { next }
+    count >= top_n { exit }
+    NF < 1 { next }
+    {
+      ip = trim($1)
+      sent = trim($2)
+      received = trim($3)
+      loss = trim($4)
+      latency = trim($5)
+      speed = trim($6)
+      region = trim($7)
+      count++
+      printf("[router-local] candidate_%d ip=%s sent=%s recv=%s loss=%s latency=%s speed=%s region=%s\n", count, ip, sent, received, loss, latency, speed, region)
+    }
+  ' "$RESULT_PATH"
+}
+
 LAST_GOOD_IP=$(json_get_string "$STATE_LAST_GOOD_KEY" "$STATE_PATH")
 
 if [ "$CFST_SKIP_RUN" != "1" ]; then
@@ -286,4 +322,4 @@ if [ -z "$BEST_IP" ]; then
 fi
 
 write_success_outputs "$BEST_IP" "$TOP_CANDIDATES_JSON" "$SOURCE_COUNT"
-echo "[router-local] selected ip: $BEST_IP"
+log_update_summary "$BEST_IP" "$SOURCE_COUNT"

+ 25 - 13
substore/operator_template.js

@@ -58,8 +58,9 @@ function parseRouterSpeedMBps(routerObj) {
     return { mbPerSec: null, raw: "", status: "missing", unit: "" };
   }
 
-  const normalized = raw.toLowerCase().replace(/\s+/g, "");
-  const match = normalized.match(/([0-9]+(?:\.[0-9]+)?)/);
+  const compact = raw.replace(/\s+/g, "");
+  const normalized = compact.toLowerCase();
+  const match = compact.match(/([0-9]+(?:\.[0-9]+)?)/);
   if (!match) {
     return { mbPerSec: null, raw, status: "invalid", unit: "" };
   }
@@ -70,24 +71,35 @@ function parseRouterSpeedMBps(routerObj) {
   }
 
   let mbPerSec = numeric;
-  let unit = "unknown";
+  let unit = "MB/s";
 
-  if (normalized.includes("gb/s") || normalized.includes("g/s") || normalized.endsWith("gb") || normalized.endsWith("g")) {
-    mbPerSec = numeric * 1024;
-    unit = "gb/s";
-  } else if (normalized.includes("mb/s") || normalized.includes("m/s") || normalized.endsWith("mb") || normalized.endsWith("m")) {
+  if (compact.includes("MB/s")) {
     mbPerSec = numeric;
-    unit = "mb/s";
-  } else if (normalized.includes("kb/s") || normalized.includes("k/s") || normalized.endsWith("kb") || normalized.endsWith("k")) {
+    unit = "MB/s";
+  } else if (compact.includes("KB/s")) {
     mbPerSec = numeric / 1024;
-    unit = "kb/s";
-  } else if (normalized.includes("b/s")) {
+    unit = "KB/s";
+  } else if (compact.includes("GB/s")) {
+    mbPerSec = numeric * 1024;
+    unit = "GB/s";
+  } else if (compact.includes("B/s")) {
     mbPerSec = numeric / (1024 * 1024);
+    unit = "B/s";
+  } else if (normalized.includes("mbps") || normalized.includes("mb/s")) {
+    mbPerSec = numeric / 8;
+    unit = "Mbps";
+  } else if (normalized.includes("gbps") || normalized.includes("gb/s")) {
+    mbPerSec = (numeric * 1024) / 8;
+    unit = "Gbps";
+  } else if (normalized.includes("kbps") || normalized.includes("kb/s")) {
+    mbPerSec = (numeric / 1024) / 8;
+    unit = "Kbps";
+  } else if (normalized.includes("bps") || normalized.includes("b/s")) {
+    mbPerSec = (numeric / (1024 * 1024)) / 8;
     unit = "b/s";
   }
 
-  const status = unit === "unknown" ? "unknown_unit" : "ok";
-  return { mbPerSec, raw, status, unit };
+  return { mbPerSec, raw, status: "ok", unit };
 }