fix(warp): префлайт-проверка Cloudflare вместо XHR timeout

На сетях, где api.cloudflareclient.com недоступен (типичный случай из
issue #39), генератор сжигал весь временной бюджет - фронтенд умирал по
XHR timeout через 30 секунд, не показывая причины.

- префлайт: одна проба (4с connect / 6с max) напрямую и через доступный
  транспорт; если Cloudflare недостижим - мгновенная понятная ошибка
  «Cloudflare API недоступен...» вместо минутного зависания
- бюджеты ужаты до 20с на call_api и 20с на всю регистрацию, чтобы
  худший случай укладывался в XHR-таймаут LuCI
- все ошибочные выходы теперь печатают JSON-конверт и exit(0): фронтенд
  парсит data.message и показывает настоящую причину (ненулевой код
  заставлял его писать безликое Unknown error)
This commit is contained in:
Dushnilin
2026-08-22 10:07:40 +03:00
parent e446b370e3
commit c788c86184
2 changed files with 64 additions and 5 deletions
@@ -99,7 +99,7 @@ function call_api(method, url, auth_header, body_file) {
// Hard wall-clock budget: without it the full retry matrix (transports x
// resolve IPs x fallbacks) can block for minutes and trip the LuCI XHR
// timeout before any answer is produced.
let deadline = now_ms() + 40000;
let deadline = now_ms() + 20000;
let transport_flags = [];
if (proxy_interface != "") {
@@ -114,6 +114,26 @@ function call_api(method, url, auth_header, body_file) {
push(transport_flags, "-x socks5h://127.0.0.1:4534");
}
// Pre-flight: when the Cloudflare API is unreachable from this router
// (blocked ISP, dead proxy), the registration matrix below would burn its
// whole time budget and trip the LuCI XHR timeout before producing a
// readable answer. Probe once, fail fast with an actionable message.
let probe_cmd = "curl -s --connect-timeout 4 -m 6 -o /dev/null -w '%{http_code}' https://api.cloudflareclient.com/ 2>/dev/null";
let probe = trim(exec_output(probe_cmd));
if (probe == "" || probe == "000") {
let via_proxy = "";
for (let tr in transport_flags) {
if (index(tr, "socks5h") >= 0 || index(tr, "--interface") >= 0) {
via_proxy = exec_output("curl -s --connect-timeout 4 -m 6 -o /dev/null -w '%{http_code}' " + tr + " https://api.cloudflareclient.com/ 2>/dev/null");
break;
}
}
if (trim(via_proxy) == "" || trim(via_proxy) == "000") {
error_response("Cloudflare API недоступен с этого роутера (ни напрямую, ни через прокси). Генерация WARP невозможна — проверьте интернет или сгенерируйте конфиг через рабочую сеть.");
exit(0);
}
}
// 1. Try selected tunnel transport(s)
for (let tr in transport_flags) {
if (now_ms() > deadline) break;
@@ -233,7 +253,7 @@ if (!private_key || !public_key) {
if (!private_key || !public_key) {
restore_original_server();
error_response("Failed to generate WireGuard keypair");
exit(1);
exit(0);
}
let install_id = "";
@@ -255,7 +275,7 @@ let tmp_req_file = exec_output("mktemp 2>/dev/null") || "/tmp/warp_req.json";
if (fs.writefile(tmp_req_file, sprintf("%J", reg_request)) == null) {
restore_original_server();
error_response("Failed to write temporary registration request file");
exit(1);
exit(0);
}
let api_prefixes = [
@@ -278,7 +298,7 @@ let chosen_prefix = "";
let server_try_index = 0;
// Overall budget for the whole registration attempt matrix: each call_api()
// is bounded, but the prefix list is long enough to multiply into minutes.
let registration_deadline = now_ms() + 90000;
let registration_deadline = now_ms() + 20000;
for (let prefix in api_prefixes) {
if (now_ms() > registration_deadline) {
@@ -333,7 +353,7 @@ if (!response_data) {
if (warp_proxy_section == "")
hint = " Провайдер может блокировать Cloudflare API на WAN. Выберите секцию с туннелем (WireGuard/AWG/VPN) или включите Mixed-прокси на прокси-секции в 'Расширенных настройках'.";
error_response("Ошибка регистрации в Cloudflare WARP API: " + last_error + hint);
exit(1);
exit(0);
}
if (!response_data.warp_enabled) {
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -eo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
WARP="$ROOT_DIR/tachyon/files/usr/lib/service/warp_generator.uc"
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
[ -f "$WARP" ] || fail "warp_generator.uc not found"
# 1. Pre-flight probe: when the Cloudflare API is unreachable the generator
# must fail fast with an actionable message instead of burning its whole
# time budget and tripping the LuCI XHR timeout.
grep -Fq 'Cloudflare API недоступен с этого роутера' "$WARP" ||
fail "generator must report an unreachable Cloudflare API explicitly"
grep -Fq "connect-timeout 4 -m 6" "$WARP" ||
fail "pre-flight probe must be bounded to a few seconds"
# 2. Time budgets must stay under the LuCI XHR timeout (~30 s): per-call 20s,
# whole registration 20s, so worst case fits inside one browser request.
grep -Fq 'let deadline = now_ms() + 20000;' "$WARP" ||
fail "call_api budget must be 20s"
grep -Fq 'let registration_deadline = now_ms() + 20000;' "$WARP" ||
fail "registration budget must be 20s"
# 3. Error paths print the JSON envelope and exit 0: the frontend parses
# stdout (data.success/message) and shows the real reason; a nonzero exit
# makes it discard stdout for "Unknown error".
[ "$(grep -c 'exit(1);' "$WARP")" -eq 0 ] ||
fail "error paths must exit(0) after printing the JSON error envelope"
# 4. Clash API calls stay bounded.
grep -Fq 'curl -s -m 5 ' "$WARP" ||
fail "local Clash API curls must carry -m 5"
printf 'warp preflight checks passed\n'