build_router_package.sh 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. #!/usr/bin/env sh
  2. set -eu
  3. SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
  4. APP_DIR="$SCRIPT_DIR"
  5. PKG_ROOT_NAME=${PKG_ROOT_NAME:-vmess}
  6. OUT_TAR=${OUT_TAR:-"$APP_DIR/vmess.tar.gz"}
  7. TMP_DIR=${TMP_DIR:-"/tmp/vmess_pkg.$$"}
  8. ROUTER_CONF=${ROUTER_CONF:-}
  9. if [ -z "$ROUTER_CONF" ]; then
  10. if [ -f "$APP_DIR/configs/config_router.conf" ]; then
  11. ROUTER_CONF="$APP_DIR/configs/config_router.conf"
  12. else
  13. ROUTER_CONF="$APP_DIR/config_router.conf"
  14. fi
  15. elif [ ! -f "$ROUTER_CONF" ] && [ -f "$APP_DIR/configs/$ROUTER_CONF" ]; then
  16. ROUTER_CONF="$APP_DIR/configs/$ROUTER_CONF"
  17. fi
  18. HTTP_SCRIPT=${HTTP_SCRIPT:-"$APP_DIR/scripts/router/router_local_http.sh"}
  19. UPDATE_SCRIPT=${UPDATE_SCRIPT:-"$APP_DIR/scripts/router/router_local_update.sh"}
  20. # Defaults aligned with current repo layout.
  21. BUSYBOX_SRC=${BUSYBOX_SRC:-"$APP_DIR/busybox/busybox-armv7l"}
  22. CFST_SRC_DIR=${CFST_SRC_DIR:-"$APP_DIR/cfst_linux_armv5"}
  23. VERBOSE=${VERBOSE:-1}
  24. SYNC_FROM_CONFIG=${SYNC_FROM_CONFIG:-}
  25. WRITE_BACK=${WRITE_BACK:-0}
  26. while [ $# -gt 0 ]; do
  27. case "$1" in
  28. --sync-from=*|--sync-from-config=*|--sync-from-json=*)
  29. SYNC_FROM_CONFIG="${1#*=}"
  30. shift
  31. ;;
  32. --sync-from|--sync-from-config|--sync-from-json)
  33. [ $# -ge 2 ] || { printf '[error] %s requires a file path argument\n' "$1" >&2; exit 1; }
  34. SYNC_FROM_CONFIG="$2"
  35. shift 2
  36. ;;
  37. --write-back)
  38. WRITE_BACK=1
  39. shift
  40. ;;
  41. *)
  42. shift
  43. ;;
  44. esac
  45. done
  46. log() {
  47. if [ "${VERBOSE}" = "1" ]; then
  48. printf '%s\n' "$*"
  49. fi
  50. }
  51. warn() {
  52. printf '[warn] %s\n' "$*" >&2
  53. }
  54. err() {
  55. printf '[error] %s\n' "$*" >&2
  56. exit 1
  57. }
  58. require_file() {
  59. p="$1"
  60. [ -f "$p" ] || err "required file missing: $p"
  61. }
  62. require_dir() {
  63. p="$1"
  64. [ -d "$p" ] || err "required directory missing: $p"
  65. }
  66. parse_conf_var() {
  67. conf="$1"
  68. key="$2"
  69. if [ ! -r "$conf" ]; then
  70. return 1
  71. fi
  72. sed -n "s|^${key}=\"\\(.*\\)\"$|\\1|p" "$conf" | head -n 1
  73. }
  74. resolve_from_conf_dir() {
  75. conf="$1"
  76. val="$2"
  77. conf_dir=$(CDPATH= cd -- "$(dirname -- "$conf")" && pwd)
  78. case "$val" in
  79. "") printf '%s\n' "" ;;
  80. /*) printf '%s\n' "$val" ;;
  81. *) printf '%s/%s\n' "$conf_dir" "$val" ;;
  82. esac
  83. }
  84. check_busybox_httpd() {
  85. bb="$1"
  86. if [ ! -f "$bb" ]; then
  87. warn "BusyBox binary missing: $bb"
  88. return 1
  89. fi
  90. if [ -x "$bb" ] && "$bb" --list 2>/dev/null | grep '^httpd$' >/dev/null 2>&1; then
  91. return 0
  92. fi
  93. # 跨平台构建(例如在 macOS 上打包 Linux ARM ELF 二进制),回退到静态字符串检查
  94. if strings "$bb" 2>/dev/null | grep -q 'httpd'; then
  95. return 0
  96. fi
  97. warn "BusyBox has no httpd applet: $bb"
  98. return 1
  99. }
  100. log "[pkg] app dir: $APP_DIR"
  101. log "[pkg] output: $OUT_TAR"
  102. require_file "$ROUTER_CONF"
  103. require_file "$HTTP_SCRIPT"
  104. require_file "$UPDATE_SCRIPT"
  105. require_dir "$CFST_SRC_DIR"
  106. require_file "$BUSYBOX_SRC"
  107. # 基础文件校验通过后再安全初始化临时目录与 trap
  108. case "$TMP_DIR" in
  109. ""|"/"|"/tmp"|"/var"|"$APP_DIR"|"$HOME") err "Unsafe TMP_DIR: '$TMP_DIR'" ;;
  110. esac
  111. cleanup() {
  112. rm -rf "$TMP_DIR"
  113. }
  114. trap cleanup EXIT INT TERM
  115. bb_from_conf_raw=$(parse_conf_var "$ROUTER_CONF" "BUSYBOX_BIN" || true)
  116. if [ -n "${bb_from_conf_raw:-}" ]; then
  117. bb_from_conf=$(resolve_from_conf_dir "$ROUTER_CONF" "$bb_from_conf_raw")
  118. if [ "$bb_from_conf" != "$BUSYBOX_SRC" ]; then
  119. warn "BUSYBOX_BIN in conf points to: $bb_from_conf_raw -> $bb_from_conf"
  120. warn "packaging source busybox is: $BUSYBOX_SRC"
  121. warn "if needed, set BUSYBOX_SRC to keep packaging and runtime consistent"
  122. fi
  123. fi
  124. sync_conf_from_json() {
  125. src_conf="$1"
  126. json_file="$2"
  127. dst_conf="$3"
  128. if ! command -v python3 >/dev/null 2>&1; then
  129. warn "python3 not found, skip syncing router config from JSON"
  130. cp "$src_conf" "$dst_conf"
  131. return 0
  132. fi
  133. python3 - "$src_conf" "$json_file" "$dst_conf" <<'EOF'
  134. import json
  135. import re
  136. import sys
  137. src_conf_path, json_path, dst_conf_path = sys.argv[1], sys.argv[2], sys.argv[3]
  138. with open(json_path, "r", encoding="utf-8") as f:
  139. cfg = json.load(f)
  140. updates = {}
  141. out_cfg = cfg.get("output", {})
  142. if "selected_value_file" in out_cfg:
  143. updates["VALUE_TEXT_FILE"] = str(out_cfg["selected_value_file"])
  144. if "selected_value_json" in out_cfg:
  145. updates["VALUE_JSON_FILE"] = str(out_cfg["selected_value_json"])
  146. if "selected_value_json_key" in out_cfg:
  147. updates["VALUE_JSON_KEY"] = str(out_cfg["selected_value_json_key"])
  148. if "state_last_good_key" in out_cfg:
  149. updates["STATE_LAST_GOOD_KEY"] = str(out_cfg["state_last_good_key"])
  150. if "export_vars_file" in out_cfg:
  151. updates["EXPORT_VARS_FILE"] = str(out_cfg["export_vars_file"])
  152. if "substore_value_key" in out_cfg:
  153. updates["EXPORT_VALUE_KEY"] = str(out_cfg["substore_value_key"])
  154. sel_cfg = cfg.get("selection", {})
  155. if "top_n" in sel_cfg:
  156. updates["TOP_N"] = str(sel_cfg["top_n"])
  157. cfst_cfg = cfg.get("cfst_local", {})
  158. run_args = cfst_cfg.get("run_args", [])
  159. if isinstance(run_args, list):
  160. arg_map = {
  161. "-p": "CFST_DISPLAY_COUNT",
  162. "-f": "CFST_IP_FILE",
  163. "-n": "CFST_THREADS",
  164. "-t": "CFST_TEST_COUNT",
  165. "-dn": "CFST_DOWNLOAD_COUNT",
  166. "-dt": "CFST_DOWNLOAD_TIME",
  167. "-tp": "CFST_PORT",
  168. "-url": "CFST_URL",
  169. "-cfcolo": "CFST_CFCOLO",
  170. "-tl": "CFST_LATENCY_LIMIT",
  171. "-tll": "CFST_LATENCY_LOWER",
  172. "-sl": "CFST_LOSS_LIMIT",
  173. "-speedlimit": "CFST_SPEED_LIMIT",
  174. }
  175. i = 0
  176. while i < len(run_args):
  177. flag = str(run_args[i]).strip()
  178. if flag in arg_map and i + 1 < len(run_args):
  179. updates[arg_map[flag]] = str(run_args[i + 1]).strip()
  180. i += 2
  181. elif flag == "-httping":
  182. updates["CFST_HTTPING"] = "1"
  183. i += 1
  184. elif flag == "-dd":
  185. updates["CFST_DISABLE_DOWNLOAD"] = "1"
  186. i += 1
  187. elif flag == "-allip":
  188. updates["CFST_ALL_IP"] = "1"
  189. i += 1
  190. else:
  191. i += 1
  192. with open(src_conf_path, "r", encoding="utf-8") as f:
  193. conf_lines = f.readlines()
  194. new_lines = []
  195. modified_keys = set()
  196. for line in conf_lines:
  197. m = re.match(r'^([A-Za-z0-9_]+)="(.*)"', line.strip())
  198. if m:
  199. key = m.group(1)
  200. if key in updates:
  201. new_lines.append(f'{key}="{updates[key]}"\n')
  202. modified_keys.add(key)
  203. continue
  204. new_lines.append(line)
  205. for key, val in updates.items():
  206. if key not in modified_keys:
  207. new_lines.append(f'{key}="{val}"\n')
  208. with open(dst_conf_path, "w", encoding="utf-8") as f:
  209. f.writelines(new_lines)
  210. EOF
  211. }
  212. if ! check_busybox_httpd "$BUSYBOX_SRC"; then
  213. warn "continuing packaging, but router HTTP may fail later"
  214. fi
  215. mkdir -p "$TMP_DIR/$PKG_ROOT_NAME/scripts" "$TMP_DIR/$PKG_ROOT_NAME/cfst"
  216. if [ -n "$SYNC_FROM_CONFIG" ]; then
  217. if [ ! -f "$SYNC_FROM_CONFIG" ] && [ -f "$APP_DIR/configs/$SYNC_FROM_CONFIG" ]; then
  218. SYNC_FROM_CONFIG="$APP_DIR/configs/$SYNC_FROM_CONFIG"
  219. fi
  220. require_file "$SYNC_FROM_CONFIG"
  221. log "[pkg] syncing router config from $SYNC_FROM_CONFIG"
  222. sync_conf_from_json "$ROUTER_CONF" "$SYNC_FROM_CONFIG" "$TMP_DIR/$PKG_ROOT_NAME/config_router.conf"
  223. if [ "$WRITE_BACK" = "1" ]; then
  224. cp "$TMP_DIR/$PKG_ROOT_NAME/config_router.conf" "$ROUTER_CONF"
  225. log "[pkg] written synced config back to $ROUTER_CONF"
  226. fi
  227. else
  228. cp "$ROUTER_CONF" "$TMP_DIR/$PKG_ROOT_NAME/config_router.conf"
  229. fi
  230. cp "$HTTP_SCRIPT" "$TMP_DIR/$PKG_ROOT_NAME/scripts/router_local_http.sh"
  231. cp "$UPDATE_SCRIPT" "$TMP_DIR/$PKG_ROOT_NAME/scripts/router_local_update.sh"
  232. cp "$BUSYBOX_SRC" "$TMP_DIR/$PKG_ROOT_NAME/busybox_armv7l"
  233. cp -R "$CFST_SRC_DIR"/. "$TMP_DIR/$PKG_ROOT_NAME/cfst/"
  234. chmod +x \
  235. "$TMP_DIR/$PKG_ROOT_NAME/busybox_armv7l" \
  236. "$TMP_DIR/$PKG_ROOT_NAME/scripts/router_local_http.sh" \
  237. "$TMP_DIR/$PKG_ROOT_NAME/scripts/router_local_update.sh" || true
  238. rm -f "$OUT_TAR"
  239. COPYFILE_DISABLE=1 tar --exclude='.DS_Store' --exclude='._*' -czf "$OUT_TAR" -C "$TMP_DIR" "$PKG_ROOT_NAME"
  240. log "[pkg] tar created: $OUT_TAR"
  241. log "[pkg] tar size: $(ls -lh "$OUT_TAR" | awk '{print $5}')"
  242. log "[pkg] tar content preview:"
  243. tar -tzf "$OUT_TAR" | sed -n '1,120p'
  244. if ! tar -tzf "$OUT_TAR" | grep "^$PKG_ROOT_NAME/busybox_armv7l$" >/dev/null; then
  245. err "package validation failed: busybox_armv7l missing"
  246. fi
  247. if ! tar -tzf "$OUT_TAR" | grep "^$PKG_ROOT_NAME/scripts/router_local_http.sh$" >/dev/null; then
  248. err "package validation failed: router_local_http.sh missing"
  249. fi
  250. if ! tar -tzf "$OUT_TAR" | grep "^$PKG_ROOT_NAME/cfst/cfst$" >/dev/null; then
  251. err "package validation failed: cfst binary missing"
  252. fi
  253. log "[pkg] validation passed"