router_local_update.sh 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. #!/bin/sh
  2. set -eu
  3. SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
  4. APP_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
  5. CONFIG_PATH=${1:-"$APP_DIR/router_local.conf"}
  6. if [ ! -r "$CONFIG_PATH" ]; then
  7. echo "[router-local] config not found: $CONFIG_PATH" >&2
  8. exit 1
  9. fi
  10. # shellcheck disable=SC1090
  11. . "$CONFIG_PATH"
  12. CFST_WORK_DIR=${CFST_WORK_DIR:-"$APP_DIR/cfst"}
  13. CFST_BIN=${CFST_BIN:-"./cfst"}
  14. CFST_IP_FILE=${CFST_IP_FILE:-"ip.txt"}
  15. CFST_RESULT_FILE=${CFST_RESULT_FILE:-"result.csv"}
  16. CFST_DISPLAY_COUNT=${CFST_DISPLAY_COUNT:-10}
  17. CFST_THREADS=${CFST_THREADS:-}
  18. CFST_TEST_COUNT=${CFST_TEST_COUNT:-}
  19. CFST_DOWNLOAD_COUNT=${CFST_DOWNLOAD_COUNT:-}
  20. CFST_DOWNLOAD_TIME=${CFST_DOWNLOAD_TIME:-}
  21. CFST_PORT=${CFST_PORT:-443}
  22. CFST_URL=${CFST_URL:-}
  23. CFST_HTTPING=${CFST_HTTPING:-0}
  24. CFST_HTTPING_CODE=${CFST_HTTPING_CODE:-}
  25. CFST_CFCOLO=${CFST_CFCOLO:-}
  26. CFST_LATENCY_LIMIT=${CFST_LATENCY_LIMIT:-}
  27. CFST_LATENCY_LOWER=${CFST_LATENCY_LOWER:-}
  28. CFST_LOSS_LIMIT=${CFST_LOSS_LIMIT:-}
  29. CFST_SPEED_LIMIT=${CFST_SPEED_LIMIT:-}
  30. CFST_DISABLE_DOWNLOAD=${CFST_DISABLE_DOWNLOAD:-0}
  31. CFST_ALL_IP=${CFST_ALL_IP:-0}
  32. CFST_DEBUG=${CFST_DEBUG:-0}
  33. CFST_SKIP_RUN=${CFST_SKIP_RUN:-0}
  34. TOP_N=${TOP_N:-3}
  35. RUNTIME_DIR=${RUNTIME_DIR:-"$APP_DIR/cfip_runtime"}
  36. VALUE_TEXT_FILE=${VALUE_TEXT_FILE:-"current_ip.txt"}
  37. VALUE_JSON_FILE=${VALUE_JSON_FILE:-"current_ip.json"}
  38. STATE_FILE=${STATE_FILE:-"state.json"}
  39. EXPORT_VARS_FILE=${EXPORT_VARS_FILE:-"substore_vars.json"}
  40. VALUE_JSON_KEY=${VALUE_JSON_KEY:-ip}
  41. STATE_LAST_GOOD_KEY=${STATE_LAST_GOOD_KEY:-last_good_ip}
  42. EXPORT_VALUE_KEY=${EXPORT_VALUE_KEY:-AUTO_CFIP}
  43. CURRENT_TEXT_PATH="$RUNTIME_DIR/$VALUE_TEXT_FILE"
  44. CURRENT_JSON_PATH="$RUNTIME_DIR/$VALUE_JSON_FILE"
  45. STATE_PATH="$RUNTIME_DIR/$STATE_FILE"
  46. EXPORT_VARS_PATH="$RUNTIME_DIR/$EXPORT_VARS_FILE"
  47. INDEX_PATH="$RUNTIME_DIR/index.html"
  48. RESULT_PATH="$CFST_WORK_DIR/$CFST_RESULT_FILE"
  49. UPDATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
  50. mkdir -p "$RUNTIME_DIR"
  51. json_get_string() {
  52. key="$1"
  53. file="$2"
  54. if [ ! -f "$file" ]; then
  55. return 0
  56. fi
  57. sed -n "s/.*\"$key\":[[:space:]]*\"\([^\"]*\)\".*/\1/p" "$file" | head -n 1
  58. }
  59. json_escape() {
  60. printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
  61. }
  62. trim_cr() {
  63. printf '%s' "$1" | tr -d '\r'
  64. }
  65. build_top_candidates_json() {
  66. awk -F',' -v top_n="$TOP_N" '
  67. function trim(v) {
  68. gsub(/\r/, "", v)
  69. sub(/^[[:space:]]+/, "", v)
  70. sub(/[[:space:]]+$/, "", v)
  71. return v
  72. }
  73. function esc(v) {
  74. gsub(/\\/,"\\\\",v)
  75. gsub(/"/,"\\\"",v)
  76. return v
  77. }
  78. NR == 1 { next }
  79. count >= top_n { exit }
  80. NF < 1 { next }
  81. {
  82. ip = esc(trim($1))
  83. sent = esc(trim($2))
  84. received = esc(trim($3))
  85. loss = esc(trim($4))
  86. latency = esc(trim($5))
  87. speed = esc(trim($6))
  88. region = esc(trim($7))
  89. if (count > 0) {
  90. printf(",")
  91. }
  92. printf("{\"ip\":\"%s\",\"domain\":\"%s\",\"sent\":\"%s\",\"received\":\"%s\",\"loss_rate\":\"%s\",\"avg_latency\":\"%s\",\"download_speed\":\"%s\",\"region\":\"%s\"}", ip, ip, sent, received, loss, latency, speed, region)
  93. count++
  94. }
  95. END {
  96. if (count == 0) {
  97. printf("")
  98. }
  99. }
  100. ' "$RESULT_PATH"
  101. }
  102. write_text_value() {
  103. selected_ip="$1"
  104. printf '%s\n' "$selected_ip" > "$CURRENT_TEXT_PATH"
  105. rm -f "$INDEX_PATH"
  106. cp "$CURRENT_TEXT_PATH" "$INDEX_PATH"
  107. }
  108. write_success_outputs() {
  109. selected_ip="$1"
  110. top_candidates_json="$2"
  111. source_count="$3"
  112. escaped_ip=$(json_escape "$selected_ip")
  113. write_text_value "$selected_ip"
  114. cat > "$CURRENT_JSON_PATH" <<EOF
  115. {
  116. "$VALUE_JSON_KEY": "$escaped_ip",
  117. "updated_at": "$UPDATED_AT",
  118. "status": "ok",
  119. "source_type": "cfst_local_busybox",
  120. "source_count": $source_count,
  121. "checked_count": 0,
  122. "top_candidates": [${top_candidates_json}]
  123. }
  124. EOF
  125. cat > "$STATE_PATH" <<EOF
  126. {
  127. "updated_at": "$UPDATED_AT",
  128. "$STATE_LAST_GOOD_KEY": "$escaped_ip",
  129. "status": "ok",
  130. "source_count": $source_count,
  131. "checked_count": 0,
  132. "source_type": "cfst_local_busybox"
  133. }
  134. EOF
  135. cat > "$EXPORT_VARS_PATH" <<EOF
  136. {
  137. "$EXPORT_VALUE_KEY": "$escaped_ip",
  138. "UPDATED_AT": "$UPDATED_AT",
  139. "STATUS": "ok"
  140. }
  141. EOF
  142. }
  143. write_error_with_fallback() {
  144. last_good_ip="$1"
  145. error_message="$2"
  146. escaped_ip=$(json_escape "$last_good_ip")
  147. escaped_error=$(json_escape "$error_message")
  148. write_text_value "$last_good_ip"
  149. cat > "$CURRENT_JSON_PATH" <<EOF
  150. {
  151. "$VALUE_JSON_KEY": "$escaped_ip",
  152. "updated_at": "$UPDATED_AT",
  153. "status": "error_use_last_good",
  154. "error": "$escaped_error",
  155. "source_type": "cfst_local_busybox"
  156. }
  157. EOF
  158. cat > "$STATE_PATH" <<EOF
  159. {
  160. "updated_at": "$UPDATED_AT",
  161. "$STATE_LAST_GOOD_KEY": "$escaped_ip",
  162. "status": "error",
  163. "error": "$escaped_error",
  164. "source_type": "cfst_local_busybox"
  165. }
  166. EOF
  167. cat > "$EXPORT_VARS_PATH" <<EOF
  168. {
  169. "$EXPORT_VALUE_KEY": "$escaped_ip",
  170. "UPDATED_AT": "$UPDATED_AT",
  171. "STATUS": "error_use_last_good"
  172. }
  173. EOF
  174. }
  175. run_cfst() (
  176. cd "$CFST_WORK_DIR"
  177. set -- "$CFST_BIN" -f "$CFST_IP_FILE" -o "$CFST_RESULT_FILE" -p "$CFST_DISPLAY_COUNT" -tp "$CFST_PORT"
  178. if [ -n "$CFST_THREADS" ]; then
  179. set -- "$@" -n "$CFST_THREADS"
  180. fi
  181. if [ -n "$CFST_TEST_COUNT" ]; then
  182. set -- "$@" -t "$CFST_TEST_COUNT"
  183. fi
  184. if [ -n "$CFST_DOWNLOAD_COUNT" ]; then
  185. set -- "$@" -dn "$CFST_DOWNLOAD_COUNT"
  186. fi
  187. if [ -n "$CFST_DOWNLOAD_TIME" ]; then
  188. set -- "$@" -dt "$CFST_DOWNLOAD_TIME"
  189. fi
  190. if [ -n "$CFST_URL" ]; then
  191. set -- "$@" -url "$CFST_URL"
  192. fi
  193. if [ "$CFST_HTTPING" = "1" ]; then
  194. set -- "$@" -httping
  195. fi
  196. if [ -n "$CFST_HTTPING_CODE" ]; then
  197. set -- "$@" -httping-code "$CFST_HTTPING_CODE"
  198. fi
  199. if [ -n "$CFST_CFCOLO" ]; then
  200. set -- "$@" -cfcolo "$CFST_CFCOLO"
  201. fi
  202. if [ -n "$CFST_LATENCY_LIMIT" ]; then
  203. set -- "$@" -tl "$CFST_LATENCY_LIMIT"
  204. fi
  205. if [ -n "$CFST_LATENCY_LOWER" ]; then
  206. set -- "$@" -tll "$CFST_LATENCY_LOWER"
  207. fi
  208. if [ -n "$CFST_LOSS_LIMIT" ]; then
  209. set -- "$@" -tlr "$CFST_LOSS_LIMIT"
  210. fi
  211. if [ -n "$CFST_SPEED_LIMIT" ]; then
  212. set -- "$@" -sl "$CFST_SPEED_LIMIT"
  213. fi
  214. if [ "$CFST_DISABLE_DOWNLOAD" = "1" ]; then
  215. set -- "$@" -dd
  216. fi
  217. if [ "$CFST_ALL_IP" = "1" ]; then
  218. set -- "$@" -allip
  219. fi
  220. if [ "$CFST_DEBUG" = "1" ]; then
  221. set -- "$@" -debug
  222. fi
  223. "$@"
  224. )
  225. LAST_GOOD_IP=$(json_get_string "$STATE_LAST_GOOD_KEY" "$STATE_PATH")
  226. if [ "$CFST_SKIP_RUN" != "1" ]; then
  227. if ! run_cfst; then
  228. if [ -n "$LAST_GOOD_IP" ]; then
  229. write_error_with_fallback "$LAST_GOOD_IP" "cfst run failed"
  230. echo "[router-local] cfst run failed, fallback to last good ip: $LAST_GOOD_IP"
  231. exit 0
  232. fi
  233. echo "[router-local] cfst run failed and no last good ip available" >&2
  234. exit 1
  235. fi
  236. fi
  237. if [ ! -s "$RESULT_PATH" ]; then
  238. if [ -n "$LAST_GOOD_IP" ]; then
  239. write_error_with_fallback "$LAST_GOOD_IP" "cfst result file missing or empty"
  240. echo "[router-local] empty result, fallback to last good ip: $LAST_GOOD_IP"
  241. exit 0
  242. fi
  243. echo "[router-local] result file missing or empty: $RESULT_PATH" >&2
  244. exit 1
  245. fi
  246. BEST_LINE=$(sed -n '2p' "$RESULT_PATH" | tr -d '\r')
  247. BEST_IP=$(printf '%s' "$BEST_LINE" | cut -d',' -f1 | tr -d ' ')
  248. SOURCE_COUNT=$(awk -F',' 'NR > 1 && NF > 0 { count++ } END { print count + 0 }' "$RESULT_PATH")
  249. TOP_CANDIDATES_JSON=$(build_top_candidates_json)
  250. if [ -z "$BEST_IP" ]; then
  251. if [ -n "$LAST_GOOD_IP" ]; then
  252. write_error_with_fallback "$LAST_GOOD_IP" "no valid ip found in cfst result"
  253. echo "[router-local] no valid ip found, fallback to last good ip: $LAST_GOOD_IP"
  254. exit 0
  255. fi
  256. echo "[router-local] no valid ip found in result file" >&2
  257. exit 1
  258. fi
  259. write_success_outputs "$BEST_IP" "$TOP_CANDIDATES_JSON" "$SOURCE_COUNT"
  260. echo "[router-local] selected ip: $BEST_IP"