Кнопка Generate WARP Config: исправление binary UCI и стилизация
- Добавлен генератор WARP конфигурации (warp_generator.uc) - Исправлена запись binary UCI значений (<b 0x...>) в поля awg_i1-i5: warp_generator.uc использует to_hex(), section.js очищает значения перед записью - generator.uc: добавлена защита uci_bin_to_hex() при чтении awg параметров - generator.uc: поля awg_i3/i4/i5 со значением '0' пропускаются - section.js: кнопка Generate WARP Config переработана в стиле Tachyon UI (cbi-button-neutral, состояния loading/success/error с анимациями)
This commit is contained in:
@@ -8,3 +8,4 @@ sandbox/
|
|||||||
.wsl-build/
|
.wsl-build/
|
||||||
dist/
|
dist/
|
||||||
build-out/
|
build-out/
|
||||||
|
scratch/
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# RU translations for TACHYON package.
|
# RU translations for TACHYON package.
|
||||||
# Copyright (C) 2026 THE TACHYON COPYRIGHT HOLDER
|
# Copyright (C) 2026 THE TACHYON COPYRIGHT HOLDER
|
||||||
# This file is distributed under the same license as the TACHYON package.
|
# This file is distributed under the same license as the TACHYON package.
|
||||||
# Dushnilin, 2026.
|
# Dushnilin, 2026.
|
||||||
@@ -2665,3 +2665,28 @@ msgstr "Разрешить небезопасный TLS"
|
|||||||
|
|
||||||
msgid "Skip TLS certificate verification"
|
msgid "Skip TLS certificate verification"
|
||||||
msgstr "Пропустить проверку TLS сертификата"
|
msgstr "Пропустить проверку TLS сертификата"
|
||||||
|
|
||||||
|
msgid "AmneziaWG Generator"
|
||||||
|
msgstr "Генератор AmneziaWG"
|
||||||
|
|
||||||
|
msgid "Automatically generate a Cloudflare WARP account and configure all AmneziaWG settings."
|
||||||
|
msgstr "Автоматически сгенерировать аккаунт Cloudflare WARP и настроить все параметры AmneziaWG."
|
||||||
|
|
||||||
|
msgid "Generate WARP Config"
|
||||||
|
msgstr "Сгенерировать конфиг WARP"
|
||||||
|
|
||||||
|
msgid "Generating..."
|
||||||
|
msgstr "Генерация..."
|
||||||
|
|
||||||
|
msgid "Failed to generate WARP config: "
|
||||||
|
msgstr "Не удалось сгенерировать конфиг WARP: "
|
||||||
|
|
||||||
|
msgid "WARP Configuration generated and loaded successfully!"
|
||||||
|
msgstr "Конфигурация WARP успешно сгенерирована и загружена!"
|
||||||
|
|
||||||
|
msgid "Failed to parse WARP config: "
|
||||||
|
msgstr "Не удалось разобрать конфиг WARP: "
|
||||||
|
|
||||||
|
msgid "Error running generator: "
|
||||||
|
msgstr "Ошибка при запуске генератора: "
|
||||||
|
|
||||||
|
|||||||
@@ -7199,6 +7199,164 @@ function createSectionContent(section) {
|
|||||||
};
|
};
|
||||||
configureTextareaOption(o, analyzeByedpiStrategy);
|
configureTextareaOption(o, analyzeByedpiStrategy);
|
||||||
|
|
||||||
|
|
||||||
|
o = section.taboption(
|
||||||
|
"settings",
|
||||||
|
form.Button,
|
||||||
|
"_generate_warp",
|
||||||
|
_("AmneziaWG Generator"),
|
||||||
|
_("Automatically generate a Cloudflare WARP account and configure all AmneziaWG settings.")
|
||||||
|
);
|
||||||
|
o.modalonly = true;
|
||||||
|
o.inputtitle = _("Generate WARP Config");
|
||||||
|
o.depends("action", "awg");
|
||||||
|
|
||||||
|
// Custom render: beautiful gradient button with loading / success / error states
|
||||||
|
o.renderWidget = function(section_id) {
|
||||||
|
if (!document.getElementById("tachyon-warp-btn-css")) {
|
||||||
|
const s = document.createElement("style");
|
||||||
|
s.id = "tachyon-warp-btn-css";
|
||||||
|
s.textContent = `
|
||||||
|
.twg-spinner {
|
||||||
|
width: 13px; height: 13px;
|
||||||
|
border: 1.5px solid currentColor;
|
||||||
|
border-top-color: transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
animation: twg-spin 0.7s linear infinite;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.twg-loading .twg-spinner { display: inline-block; }
|
||||||
|
.twg-loading .twg-icon { display: none; }
|
||||||
|
.twg-icon { flex-shrink: 0; }
|
||||||
|
.twg-btn.twg-loading { opacity: 0.65; }
|
||||||
|
.twg-btn.twg-success { color: #4caf50 !important; border-color: #4caf50 !important; }
|
||||||
|
.twg-btn.twg-error { color: #ef5350 !important; border-color: #ef5350 !important; animation: twg-shake 0.4s ease; }
|
||||||
|
@keyframes twg-spin { to { transform: rotate(360deg); } }
|
||||||
|
@keyframes twg-shake {
|
||||||
|
0%,100% { transform: translateX(0); }
|
||||||
|
20% { transform: translateX(-5px); }
|
||||||
|
60% { transform: translateX(5px); }
|
||||||
|
80% { transform: translateX(-3px); }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
const spinner = E("span", { "class": "twg-spinner" });
|
||||||
|
const icon = E("span", { "class": "twg-icon" }, ["↺"]);
|
||||||
|
const label = E("span", { "class": "twg-label" }, [_("Generate WARP Config")]);
|
||||||
|
const btn = E("button", {
|
||||||
|
"class": "btn cbi-button cbi-button-neutral twg-btn",
|
||||||
|
"type": "button",
|
||||||
|
"style": "display:inline-flex;align-items:center;gap:8px;"
|
||||||
|
}, [spinner, icon, label]);
|
||||||
|
|
||||||
|
btn.addEventListener("click", (ev) => this.onclick && this.onclick.call(this, ev, section_id));
|
||||||
|
return btn;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
o.onclick = function(ev, section_id) {
|
||||||
|
const btn = ev.target.closest(".twg-btn");
|
||||||
|
const label = btn ? btn.querySelector(".twg-label") : null;
|
||||||
|
const origText = _("Generate WARP Config");
|
||||||
|
|
||||||
|
const setState = (state, text) => {
|
||||||
|
if (!btn) return;
|
||||||
|
btn.className = "btn cbi-button cbi-button-neutral twg-btn" + (state ? " twg-" + state : "");
|
||||||
|
btn.disabled = (state === "loading");
|
||||||
|
if (label && text) label.textContent = text;
|
||||||
|
};
|
||||||
|
|
||||||
|
const showStatusModal = (title, msg, isError) =>
|
||||||
|
ui.addNotification(title, E("p", {}, msg), isError ? "danger" : "success");
|
||||||
|
|
||||||
|
const resetAfter = (ms) =>
|
||||||
|
setTimeout(() => setState("", origText), ms);
|
||||||
|
|
||||||
|
setState("loading", _("Generating…"));
|
||||||
|
|
||||||
|
fs.exec("/usr/bin/tachyon", ["generate_warp"])
|
||||||
|
.then(function(response) {
|
||||||
|
if (!response || (response.code ?? 0) !== 0 || !response.stdout) {
|
||||||
|
setState("error", _("Error"));
|
||||||
|
resetAfter(2500);
|
||||||
|
showStatusModal(_("Error"), _("Failed to generate WARP config: ") + (response?.stderr || "Unknown error"), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(response.stdout);
|
||||||
|
if (!data.success) {
|
||||||
|
setState("error", _("Error"));
|
||||||
|
resetAfter(2500);
|
||||||
|
showStatusModal(_("Error"), _("Failed to generate WARP config: ") + (data.message || "Unknown error"), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getControlWidget = (opt) => {
|
||||||
|
const widget = document.getElementById(`widget.cbid.${UCI_PACKAGE}.${section_id}.${opt}`);
|
||||||
|
if (!widget) return null;
|
||||||
|
if (/^(INPUT|SELECT|TEXTAREA)$/.test(widget.tagName)) return widget;
|
||||||
|
return widget.querySelector("input:not([type='hidden']), select, textarea");
|
||||||
|
};
|
||||||
|
|
||||||
|
const stripUciBinary = (val) => {
|
||||||
|
if (typeof val !== "string") return val;
|
||||||
|
return val.replace(/<b 0x/g, "").replace(/>/g, "").replace(/\s/g, "");
|
||||||
|
};
|
||||||
|
|
||||||
|
const setWidgetValue = (opt, val) => {
|
||||||
|
const widget = getControlWidget(opt);
|
||||||
|
if (widget) {
|
||||||
|
widget.value = val;
|
||||||
|
widget.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
widget.dispatchEvent(new Event("change", { bubbles: true }));
|
||||||
|
}
|
||||||
|
uci.set(UCI_PACKAGE, section_id, opt, val);
|
||||||
|
};
|
||||||
|
|
||||||
|
setWidgetValue("awg_local_address", data.local_address);
|
||||||
|
setWidgetValue("awg_private_key", data.private_key);
|
||||||
|
setWidgetValue("awg_peer_public_key", data.peer_public_key);
|
||||||
|
setWidgetValue("awg_server_address", data.server_address);
|
||||||
|
setWidgetValue("awg_server_port", data.server_port);
|
||||||
|
setWidgetValue("awg_jc", `${data.awg_jc}`);
|
||||||
|
setWidgetValue("awg_jmin", `${data.awg_jmin}`);
|
||||||
|
setWidgetValue("awg_jmax", `${data.awg_jmax}`);
|
||||||
|
setWidgetValue("awg_s1", `${data.awg_s1}`);
|
||||||
|
setWidgetValue("awg_s2", `${data.awg_s2}`);
|
||||||
|
setWidgetValue("awg_h1", `${data.awg_h1}`);
|
||||||
|
setWidgetValue("awg_h2", `${data.awg_h2}`);
|
||||||
|
setWidgetValue("awg_h3", `${data.awg_h3}`);
|
||||||
|
setWidgetValue("awg_h4", `${data.awg_h4}`);
|
||||||
|
setWidgetValue("awg_s3", `${data.awg_s3}`);
|
||||||
|
setWidgetValue("awg_s4", `${data.awg_s4}`);
|
||||||
|
setWidgetValue("awg_i1", stripUciBinary(data.awg_i1));
|
||||||
|
setWidgetValue("awg_i2", stripUciBinary(data.awg_i2));
|
||||||
|
setWidgetValue("awg_i3", stripUciBinary(data.awg_i3));
|
||||||
|
setWidgetValue("awg_i4", stripUciBinary(data.awg_i4));
|
||||||
|
setWidgetValue("awg_i5", stripUciBinary(data.awg_i5));
|
||||||
|
|
||||||
|
setState("success", _("Generated!"));
|
||||||
|
resetAfter(2500);
|
||||||
|
showStatusModal(_("Success"), _("WARP Configuration generated and loaded successfully!"), false);
|
||||||
|
} catch (err) {
|
||||||
|
setState("error", _("Error"));
|
||||||
|
resetAfter(2500);
|
||||||
|
showStatusModal(_("Error"), _("Failed to parse WARP config: ") + err.message, true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
setState("error", _("Error"));
|
||||||
|
resetAfter(2500);
|
||||||
|
showStatusModal(_("Error"), _("Error running generator: ") + error.message, true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
o = section.taboption(
|
o = section.taboption(
|
||||||
"settings",
|
"settings",
|
||||||
form.Value,
|
form.Value,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# RU translations for TACHYON package.
|
# RU translations for TACHYON package.
|
||||||
# Copyright (C) 2026 THE TACHYON COPYRIGHT HOLDER
|
# Copyright (C) 2026 THE TACHYON COPYRIGHT HOLDER
|
||||||
# This file is distributed under the same license as the TACHYON package.
|
# This file is distributed under the same license as the TACHYON package.
|
||||||
# Dushnilin, 2026.
|
# Dushnilin, 2026.
|
||||||
@@ -2665,3 +2665,28 @@ msgstr "Разрешить небезопасный TLS"
|
|||||||
|
|
||||||
msgid "Skip TLS certificate verification"
|
msgid "Skip TLS certificate verification"
|
||||||
msgstr "Пропустить проверку TLS сертификата"
|
msgstr "Пропустить проверку TLS сертификата"
|
||||||
|
|
||||||
|
msgid "AmneziaWG Generator"
|
||||||
|
msgstr "Генератор AmneziaWG"
|
||||||
|
|
||||||
|
msgid "Automatically generate a Cloudflare WARP account and configure all AmneziaWG settings."
|
||||||
|
msgstr "Автоматически сгенерировать аккаунт Cloudflare WARP и настроить все параметры AmneziaWG."
|
||||||
|
|
||||||
|
msgid "Generate WARP Config"
|
||||||
|
msgstr "Сгенерировать конфиг WARP"
|
||||||
|
|
||||||
|
msgid "Generating..."
|
||||||
|
msgstr "Генерация..."
|
||||||
|
|
||||||
|
msgid "Failed to generate WARP config: "
|
||||||
|
msgstr "Не удалось сгенерировать конфиг WARP: "
|
||||||
|
|
||||||
|
msgid "WARP Configuration generated and loaded successfully!"
|
||||||
|
msgstr "Конфигурация WARP успешно сгенерирована и загружена!"
|
||||||
|
|
||||||
|
msgid "Failed to parse WARP config: "
|
||||||
|
msgstr "Не удалось разобрать конфиг WARP: "
|
||||||
|
|
||||||
|
msgid "Error running generator: "
|
||||||
|
msgstr "Ошибка при запуске генератора: "
|
||||||
|
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ function command_spec(command) {
|
|||||||
validate_nfqws_strategy_json: [ "diagnostics/runtime.uc", "validate-nfqws-strategy-json", 1 ],
|
validate_nfqws_strategy_json: [ "diagnostics/runtime.uc", "validate-nfqws-strategy-json", 1 ],
|
||||||
validate_nfqws2_strategy_json: [ "diagnostics/runtime.uc", "validate-nfqws2-strategy-json", 1 ],
|
validate_nfqws2_strategy_json: [ "diagnostics/runtime.uc", "validate-nfqws2-strategy-json", 1 ],
|
||||||
validate_byedpi_strategy_json: [ "providers/byedpi/validator.uc", "validate-json", 1 ],
|
validate_byedpi_strategy_json: [ "providers/byedpi/validator.uc", "validate-json", 1 ],
|
||||||
|
generate_warp: [ "service/warp_generator.uc", "", 1 ],
|
||||||
package_prerm: [ "service/package.uc", "prerm", 1 ],
|
package_prerm: [ "service/package.uc", "prerm", 1 ],
|
||||||
package_postinst: [ "service/package.uc", "postinst", 0 ],
|
package_postinst: [ "service/package.uc", "postinst", 0 ],
|
||||||
luci_postinst: [ "service/package.uc", "luci-postinst", 0 ]
|
luci_postinst: [ "service/package.uc", "luci-postinst", 0 ]
|
||||||
|
|||||||
@@ -0,0 +1,340 @@
|
|||||||
|
#!/usr/bin/env ucode
|
||||||
|
|
||||||
|
let fs = require("fs");
|
||||||
|
|
||||||
|
function as_string(value) {
|
||||||
|
return value == null ? "" : "" + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function trim(str) {
|
||||||
|
return replace(as_string(str), /^[ \t\r\n]+|[ \t\r\n]+$/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function write_json(value) {
|
||||||
|
print(sprintf("%J", value), "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert <b 0x...> ucode binary literals to plain hex string
|
||||||
|
function to_hex(s) {
|
||||||
|
s = replace(as_string(s), "<b 0x", "");
|
||||||
|
s = replace(s, ">", "");
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
function error_response(message) {
|
||||||
|
write_json({
|
||||||
|
success: false,
|
||||||
|
message: as_string(message)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function exec_output(command) {
|
||||||
|
let pipe = fs.popen(command, "r");
|
||||||
|
if (!pipe) return "";
|
||||||
|
let data = pipe.read("all");
|
||||||
|
pipe.close();
|
||||||
|
return data == null ? "" : trim(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function contains(arr, val) {
|
||||||
|
for (let x in arr) {
|
||||||
|
if (x == val) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function call_api(method, url, auth_header, body_file) {
|
||||||
|
let resolve_ips = [ "162.159.192.1", "162.159.193.1", "188.114.98.1", "188.114.99.1" ];
|
||||||
|
let proxy_opts = [ "", "-x socks5h://127.0.0.1:4534" ];
|
||||||
|
let last_res = "";
|
||||||
|
|
||||||
|
for (let proxy in proxy_opts) {
|
||||||
|
for (let ip in resolve_ips) {
|
||||||
|
let auth_str = auth_header != "" ? "-H 'Authorization: Bearer " + auth_header + "' " : "";
|
||||||
|
let data_str = body_file != "" ? "-d @" + body_file + " " : "";
|
||||||
|
let cmd = "curl -s --connect-timeout 6 --max-time 12 " + proxy + " --resolve api.cloudflareclient.com:443:" + ip + " -X " + method + " -H 'Content-Type: application/json' -H 'User-Agent: okhttp/3.12.1' " + auth_str + data_str + url;
|
||||||
|
let res = exec_output(cmd);
|
||||||
|
if (res != "") {
|
||||||
|
try {
|
||||||
|
let parsed = json(res);
|
||||||
|
if (parsed) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
last_res = res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return last_res != "" ? { success: false, message: last_res } : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query current proxy selection from Clash API for automatic rate limit rotation if proxy is used
|
||||||
|
let original_server = "";
|
||||||
|
let available_servers = [];
|
||||||
|
let clash_data_str = exec_output("curl -s http://192.168.2.1:9090/proxies/MAIN-out");
|
||||||
|
if (clash_data_str != "") {
|
||||||
|
try {
|
||||||
|
let parsed = json(clash_data_str);
|
||||||
|
if (parsed) {
|
||||||
|
original_server = parsed.now || "";
|
||||||
|
available_servers = parsed.all || [];
|
||||||
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function restore_original_server() {
|
||||||
|
if (original_server != "") {
|
||||||
|
let body = { name: original_server };
|
||||||
|
let tmp_file = "/tmp/restore_clash.json";
|
||||||
|
fs.writefile(tmp_file, sprintf("%J", body));
|
||||||
|
exec_output("curl -s -X PUT -H 'Content-Type: application/json' -d @" + tmp_file + " http://192.168.2.1:9090/proxies/MAIN-out");
|
||||||
|
fs.unlink(tmp_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function switch_server(srv_name) {
|
||||||
|
let body = { name: srv_name };
|
||||||
|
let tmp_file = "/tmp/switch_clash.json";
|
||||||
|
fs.writefile(tmp_file, sprintf("%J", body));
|
||||||
|
exec_output("curl -s -X PUT -H 'Content-Type: application/json' -d @" + tmp_file + " http://192.168.2.1:9090/proxies/MAIN-out");
|
||||||
|
fs.unlink(tmp_file);
|
||||||
|
exec_output("sleep 1.5");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Generate WireGuard keys
|
||||||
|
let keypair_data = exec_output("/usr/bin/sing-box generate wg-keypair 2>/dev/null");
|
||||||
|
let private_key = null;
|
||||||
|
let public_key = null;
|
||||||
|
|
||||||
|
for (let line in split(keypair_data, "\n")) {
|
||||||
|
let m_priv = match(line, /^PrivateKey:\s*(.+)$/);
|
||||||
|
if (m_priv) private_key = trim(m_priv[1]);
|
||||||
|
let m_pub = match(line, /^PublicKey:\s*(.+)$/);
|
||||||
|
if (m_pub) public_key = trim(m_pub[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to wg genkey/pubkey if sing-box generate wg-keypair fails
|
||||||
|
if (!private_key || !public_key) {
|
||||||
|
let priv = exec_output("wg genkey 2>/dev/null");
|
||||||
|
if (priv != "") {
|
||||||
|
private_key = priv;
|
||||||
|
public_key = exec_output("echo '" + priv + "' | wg pubkey 2>/dev/null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!private_key || !public_key) {
|
||||||
|
restore_original_server();
|
||||||
|
error_response("Failed to generate WireGuard keypair");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Perform registration
|
||||||
|
let install_id = "";
|
||||||
|
let fcm_token = "";
|
||||||
|
let tos = exec_output("date -u +'%Y-%m-%dT%H:%M:%SZ' 2>/dev/null") || "2026-07-17T20:52:00Z";
|
||||||
|
let serial_number = exec_output("head -c 8 /dev/urandom | hexdump -ve '1/1 \"%02x\"' 2>/dev/null") || "abcdef1234567890";
|
||||||
|
|
||||||
|
let reg_request = {
|
||||||
|
key: public_key,
|
||||||
|
install_id: install_id,
|
||||||
|
fcm_token: fcm_token,
|
||||||
|
tos: tos,
|
||||||
|
model: "PC",
|
||||||
|
serial_number: serial_number,
|
||||||
|
locale: "en_US"
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
let api_prefixes = [
|
||||||
|
"v0i1909051800",
|
||||||
|
"v0a1909051800",
|
||||||
|
"v0b1909051800",
|
||||||
|
"v0c1909051800",
|
||||||
|
"v0d1909051800",
|
||||||
|
"v0e1909051800",
|
||||||
|
"v0f1909051800",
|
||||||
|
"v0g1909051800",
|
||||||
|
"v0h1909051800",
|
||||||
|
"v0i1604021500",
|
||||||
|
"v0a1604021500"
|
||||||
|
];
|
||||||
|
|
||||||
|
let response_data = null;
|
||||||
|
let last_error = "";
|
||||||
|
let chosen_prefix = "";
|
||||||
|
let server_try_index = 0;
|
||||||
|
|
||||||
|
for (let prefix in api_prefixes) {
|
||||||
|
let url = "https://api.cloudflareclient.com/" + prefix + "/reg";
|
||||||
|
let parsed = call_api("POST", url, "", tmp_req_file);
|
||||||
|
|
||||||
|
if (!parsed) {
|
||||||
|
last_error = "Timeout or connection failure (prefix: " + prefix + ")";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.success && parsed.result) {
|
||||||
|
response_data = parsed.result;
|
||||||
|
chosen_prefix = prefix;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = parsed.message || (parsed.errors ? parsed.errors[0].message : "Unsuccessful registration");
|
||||||
|
let code = parsed.errors ? parsed.errors[0].code : null;
|
||||||
|
|
||||||
|
if (code == 1015 || index(as_string(msg), "1015") >= 0) {
|
||||||
|
// Rotate proxy server if available
|
||||||
|
if (length(available_servers) > 0 && server_try_index < 4) {
|
||||||
|
let next_srv = available_servers[(server_try_index + 1) % length(available_servers)];
|
||||||
|
server_try_index++;
|
||||||
|
switch_server(next_srv);
|
||||||
|
|
||||||
|
// Retry request with rotated proxy server
|
||||||
|
parsed = call_api("POST", url, "", tmp_req_file);
|
||||||
|
if (parsed && parsed.success && parsed.result) {
|
||||||
|
response_data = parsed.result;
|
||||||
|
chosen_prefix = prefix;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
msg = parsed ? (parsed.message || (parsed.errors ? parsed.errors[0].message : "Unsuccessful registration")) : "Timeout after proxy rotation";
|
||||||
|
code = parsed && parsed.errors ? parsed.errors[0].code : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code == 1015 || index(as_string(msg), "1015") >= 0) {
|
||||||
|
msg = "Rate limit exceeded (Cloudflare error 1015). Cloudflare blocks registrations from this IP. Please switch your active server in the Tachyon Dashboard tab and try again.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
last_error = msg;
|
||||||
|
}
|
||||||
|
fs.unlink(tmp_req_file);
|
||||||
|
|
||||||
|
if (!response_data) {
|
||||||
|
restore_original_server();
|
||||||
|
error_response("Failed to register with Cloudflare WARP API: " + last_error);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Make sure warp_enabled is true
|
||||||
|
if (!response_data.warp_enabled) {
|
||||||
|
let patch_body = { warp_enabled: true };
|
||||||
|
let tmp_patch = exec_output("mktemp 2>/dev/null") || "/tmp/warp_patch.json";
|
||||||
|
fs.writefile(tmp_patch, sprintf("%J", patch_body));
|
||||||
|
|
||||||
|
let url = "https://api.cloudflareclient.com/" + chosen_prefix + "/reg/" + response_data.id;
|
||||||
|
call_api("PATCH", url, response_data.token, tmp_patch);
|
||||||
|
fs.unlink(tmp_patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Optionally apply WARP+ license key if provided as first arg
|
||||||
|
let license_key = ARGV[0] || "";
|
||||||
|
if (license_key != "") {
|
||||||
|
let patch_body = { license_key: license_key };
|
||||||
|
let tmp_patch = exec_output("mktemp 2>/dev/null") || "/tmp/warp_patch.json";
|
||||||
|
fs.writefile(tmp_patch, sprintf("%J", patch_body));
|
||||||
|
|
||||||
|
let url = "https://api.cloudflareclient.com/" + chosen_prefix + "/reg/" + response_data.id;
|
||||||
|
call_api("PATCH", url, response_data.token, tmp_patch);
|
||||||
|
fs.unlink(tmp_patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore original proxy server selection
|
||||||
|
restore_original_server();
|
||||||
|
|
||||||
|
// 5. Select fastest endpoint IP using TCP latency probes (via curl)
|
||||||
|
let probe_ips = [
|
||||||
|
"162.159.192.1", "162.159.192.6",
|
||||||
|
"162.159.193.1", "162.159.193.6",
|
||||||
|
"188.114.98.1", "188.114.98.6",
|
||||||
|
"188.114.99.1", "188.114.99.6",
|
||||||
|
"8.6.112.0"
|
||||||
|
];
|
||||||
|
|
||||||
|
// Try to resolve engage.cloudflareclient.com and add resolved IPs
|
||||||
|
let resolved_ips = exec_output("nslookup engage.cloudflareclient.com 2>/dev/null | grep -E 'Address[ 0-9]*:' | awk '{print $2}'");
|
||||||
|
for (let ip in split(resolved_ips, "\n")) {
|
||||||
|
ip = trim(ip);
|
||||||
|
if (ip != "" && index(ip, "127.0.0.") < 0 && index(ip, ":") < 0 && !contains(probe_ips, ip)) {
|
||||||
|
push(probe_ips, ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let best_ip = "162.159.192.1";
|
||||||
|
let best_time = 9999.0;
|
||||||
|
|
||||||
|
for (let ip in probe_ips) {
|
||||||
|
ip = trim(ip);
|
||||||
|
if (ip == "") continue;
|
||||||
|
|
||||||
|
let cmd = "curl -w '%{time_connect}' -o /dev/null -s --max-time 1.0 https://" + ip;
|
||||||
|
let t_str = exec_output(cmd);
|
||||||
|
let t = (t_str || "9999.0") * 1.0;
|
||||||
|
if (t > 0.0 && t < best_time) {
|
||||||
|
best_time = t;
|
||||||
|
best_ip = ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let warp_ports = [
|
||||||
|
854, 859, 864, 878, 880, 890, 891, 894, 903, 908,
|
||||||
|
928, 934, 939, 942, 943, 945, 946, 955, 968, 987,
|
||||||
|
988, 1002, 1010, 1014, 1018, 1070, 1074, 1180, 1387, 1843,
|
||||||
|
2371, 2506, 3138, 3476, 3581, 3854, 4177, 4198, 4233, 5279,
|
||||||
|
5956, 7103, 7152, 7156, 7281, 7559, 8319, 8742, 8854, 8886, 2408
|
||||||
|
];
|
||||||
|
let random_port = warp_ports[time() % length(warp_ports)];
|
||||||
|
|
||||||
|
// 6. Formulate CPS packets
|
||||||
|
let warp_cps_payloads = [
|
||||||
|
"<b 0xce000000010897a297ecc34cd6dd000044d0ec2e2e1ea2991f467ace4222129b5a098823784694b4897b9986ae0b7280135fa85e196d9ad980b150122129ce2a9379531b0fd3e871ca5fdb883c369832f730e272d7b8b74f393f9f0fa43f11e510ecb2219a52984410c204cf875585340c62238e14ad04dff382f2c200e0ee22fe743b9c6b8b043121c5710ec289f471c91ee414fca8b8be8419ae8ce7ffc53837f6ade262891895f3f4cecd31bc93ac5599e18e4f01b472362b8056c3172b513051f8322d1062997ef4a383b01706598d08d48c221d30e74c7ce000cdad36b706b1bf9b0607c32ec4b3203a4ee21ab64df336212b9758280803fcab14933b0e7ee1e04a7becce3e2633f4852585c567894a5f9efe9706a151b615856647e8b7dba69ab357b3982f554549bef9256111b2d67afde0b496f16962d4957ff654232aa9e845b61463908309cfd9de0a6abf5f425f577d7e5f6440652aa8da5f73588e82e9470f3b21b27b28c649506ae1a7f5f15b876f56abc4615f49911549b9bb39dd804fde182bd2dcec0c33bad9b138ca07d4a4a1650a2c2686acea05727e2a78962a840ae428f55627516e73c83dd8893b02358e81b524b4d99fda6df52b3a8d7a5291326e7ac9d773c5b43b8444554ef5aea104a738ed650aa979674bbed38da58ac29d87c29d387d80b526065baeb073ce65f075ccb56e47533aef357dceaa8293a523c5f6f790be90e4731123d3c6152a70576e90b4ab5bc5ead01576c68ab633ff7d36dcde2a0b2c68897e1acfc4d6483aaaeb635dd63c96b2b6a7a2bfe042f6aed82e5363aa850aace12ee3b1a93f30d8ab9537df483152a5527faca21efc9981b304f11fc95336f5b9637b174c5a0659e2b22e159a9fed4b8e93047371175b1d6d9cc8ab745f3b2281537d1c75fb9451871864efa5d184c38c185fd203de206751b92620f7c369e031d2041e152040920ac2c5ab5340bfc9d0561176abf10a147287ea90758575ac6a9f5ac9f390d0d5b23ee12af583383d994e22c0cf42383834bcd3ada1b3825a0664d8f3fb678261d57601ddf94a8a68a7c273a18c08aa99c7ad8c6c42eab67718843597ec9930457359dfdfbce024afc2dcf9348579a57d8d3490b2fa99f278f1c37d87dad9b221acd575192ffae1784f8e60ec7cee4068b6b988f0433d96d6a1b1865f4e155e9fe020279f434f3bf1bd117b717b92f6cd1cc9bea7d45978bcc3f24bda631a36910110a6ec06da35f8966c9279d130347594f13e9e07514fa370754d1424c0a1545c5070ef9fb2acd14233e8a50bfc5978b5bdf8bc1714731f798d21e2004117c61f2989dd44f0cf027b27d4019e81ed4b5c31db347c4a3a4d85048d7093cf16753d7b0d15e078f5c7a5205dc2f87e330a1f716738dce1c6180e9d02869b5546f1c4d2748f8c90d9693cba4e0079297d22fd61402dea32ff0eb69ebd65a5d0b687d87e3a8b2c42b648aa723c7c7daf37abcc4bb85caea2ee8f55bec20e913b3324ab8f5c3304f820d42ad1b9f2ffc1a3af9927136b4419e1e579ab4c2ae3c776d293d397d575df181e6cae0a4ada5d67ecea171cca3288d57c7bbdaee3befe745fb7d634f70386d873b90c4d6c6596bb65af68f9e5121e67ebf0d89d3c909ceedfb32ce9575a7758ff080724e1ab5d5f43074ecb53a479af21ed03d7b6899c36631c0166f9d47e5e1d4528a5d3d3f744029c4b1c190cbfbad06f5f83f7ad0429fa9a2719c56ffe3783460e166de2d8>",
|
||||||
|
"<b 0xc7000000010809a1ed4edbbe7615000044d017a61a0d774f04290f119e701ef0035df2b0ed571b0b575e6a07246b856eb6ec036fef07f1e07b861251ad737abeb67e64be714c1dcd865312b1b6c35c089c997aeb5c18f808696fe97289513945d84ca846467603e94e44224877f2c1d3261e4ac18740be4bd064369c94fc08978d99b54bf615250998639010c1284248e1d73004b81fcb20b559d8a17eced7eab3964b5b88ca7a3b8579fc8c1c934189e77143b4ac434138114b1048651b56545b87acbef0952763538f3ddeb37cfc6d58b4881c3b719d7ff78f6ee1324a2914a32381c05a64c700466d280be007253bb030d179c4f1b3dc221e1974e2ee6d6e2b9e8d709159b5ef22e1783dbba845c20ca1c83b066c73835920ad70b806df0aee0351e3fc9ab1e42e8b2a30fe235ff0612eee19744949cecee0463b76514ad90c1f7ceaa557c18586ab561d49482e73c85d0143785da14a441bf82f78783b61cccd44aecb1947516e79b5ca5a6b3a8aed6040fae0eeabdc55a88dc19ade832d99fca90c7a629cacc07192d7e47e3c6a271b95b0ea3392562a06a1cab79f40ea92916ebee197b7b5f14b251824e1ed20ff2ca80b1f03a43e45157589bc61b978e97851025b3b7ccc17d291e1cb60fe48a5c26829dce11dd23c2e73265a9ebf8617c985e4fee4681e863f990061f4dea465a7d2524bd0edcf4b48d4b8f25fc359b15babd2637284a4774077dca60091f1a781cfee1bef9713dd5943a579d7470bc5970542fbb27fdf77880a8d8751b1f642c7a3f019a05ab94bf63d3525ef34e9290b5c8d477f2714e6d6e3e4d35c1983f5e16fda57fcdf071b513f8f088dbe8d5a97577d17a5383a496c3f313adfdd47c962bbaebd6aa13b46439eb742622c29ca067db0ec1853064c3cbbffe0a215a19fce47d49703ed58ebbd89721172d256d1cf30188106fb2f863186511401fad54d087aa2fb3d1b85768db386bd7102e8060ac157bac011acdcdae2799b9aee1467c3424013455bd028fcaacdc3c77d28ea199967d617ea7d0d0815f3cc407934a76d1293dccba210d1709a13e5dd67c9ba47cd113f5bdd740358eff13164159fd09bc2f7ec6cfa64d9df7e2e2f88706b0ff3a92ccf6f078456cfe0bdd89292cfe2680badc1eac9f7d36efe8eb6912c7b164508d13e6c0911c15f73c233cbe4fc70ff2ade1e1be4bbb738e0939159e2078a9438f05b756a003371f4861481c38f1cdd2d7b06deb62869e9fe79a8abaa920646fa2e8fa28f0d80c136376c7b56046bae4c05c0cdf64efb8c47bbfc5a1a4c0b045061ef0d71618e0d206a1d7f245fd5c03191b152673ba8dff8e1b8de7c50234a93cba91e3888adb228cc02beded4b1c0946797d3ef02dec2edb6ad0ac21f89f4be364c317da7c22440e9f358d512203f4b7ab20388af68b8915d0152db2c8a0687bfaea870f7529bb92a22b35bd79bc6d490591406346ecd78342ee3563c4883a8251679691c2d4e963397e24653520795511b018915374c954bddb940a9d7a16d1c8bd798fc7dbfb0599a7074e13f87e14efa8d511bb2579ec029b1bda18fe971b30fbe19e986ff2686a69bf3f1bb929de93ae70345ebca998b11e0a2b41890cba628d8f6e7c4e94790735e5299b4ff07cd3080f7d53c9cbe1911d2cd5925b3213e033c272506a87886cf761a283a779564d3241e3c28f632e166b5d756e1786ce077614c4444e3f2aed5decb3613b925ea3e558c21d4faf8ba54edd0f3a5d4>",
|
||||||
|
"<b 0xc10000000114367096bb0fb3f58f3a3fb8aaacd61d63a1c8a40e14f7374b8a62dccba6431716c3abf6f5afbcfb39bd008000047c32e268567c652e6f4db58bff759bc8c5aaca183b87cb4d22938fe7d8dca22a679a79e4d9ee62e4bbb3a380dd78d4e8e48f26b38a1d42d76b371a5a9a0444827a69d1ab5872a85749f65a4104e931740b4dc1e2dd77733fc7fac4f93011cd622f2bb47e85f71992e2d585f8dc765a7a12ddeb879746a267393ad023d267c4bd79f258703e27345155268bd3cc0506ebd72e2e3c6b5b0f005299cd94b67ddabe30389c4f9b5c2d512dcc298c14f14e9b7f931e1dc397926c31fbb7cebfc668349c218672501031ecce151d4cb03c4c660b6c6fe7754e75446cd7de09a8c81030c5f6fb377203f551864f3d83e27de7b86499736cbbb549b2f37f436db1cae0a4ea39930f0534aacdd1e3534bc87877e2afabe959ced261f228d6362e6fd277c88c312d966c8b9f67e4a92e757773db0b0862fb8108d1d8fa262a40a1b4171961f0704c8ba314da2482ac8ed9bd28d4b50f7432d89fd800c25a50c5e2f5c0710544fef5273401116aa0572366d8e49ad758fcb29e6a92912e644dbe227c247cb3417eabfab2db16796b2fba420de3b1dc94e8361f1f324a331ddaf1e626553138860757fd0bf687566108b77b70fb9f8f8962eca599c4a70ed373666961a8cb506b96756d9e28b94122b20f16b54f118c0e603ce0b831efea614ad836df6cf9affbdd09596412547496967da758cec9080295d853b0861670b71d9abde0d562b1a6de82782a5b0c14d297f27283a895abc889a5f6703f0e6eb95f67b2da45f150d0d8ab805612d570c2d5cb6997ac3a7756226c2f5c8982ffbd480c5004b0660a3c9468945efde90864019a2b519458724b55d766e16b0da25c0557c01f3c11ddeb024b62e303640e17fdd57dedb3aeb4a2c1b7c93059f9c1d7118d77caac1cd0f6556e46cbc991c1bb16970273dea833d01e5090d061a0c6d25af2415cd2878af97f6d0e7f1f936247b394ecb9bd484da6be936dee9b0b92dc90101a1b4295e97a9772f2263eb09431995aa173df4ca2abd687d87706f0f93eaa5e13cbe3b574fa3cfe94502ace25265778da6960d561381769c24e0cbd7aac73c16f95ae74ff7ec38124f7c722b9cb151d4b6841343f29be8f35145e1b27021056820fed77003df8554b4155716c8cf6049ef5e318481460a8ce3be7c7bfac695255be84dc491c19e9dedc449dd3471728cd2a3ee51324ccb3eef121e3e08f8e18f0006ea8957371d9f2f739f0b89e4db11e5c6430ada61572e589519fbad4498b460ce6e4407fc2d8f2dd4293a50a0cb8fcaaf35cd9a8cc097e3603fbfa08d9036f52b3e7fcce11b83ad28a4ac12dba0395a0cc871cefd1a2856fffb3f28d82ce35cf80579974778bab13d9b3578d8c75a2d196087a2cd439aff2bb33f2db24ac175fff4ed91d36a4cdbfaf3f83074f03894ea40f17034629890da3efdbb41141b38368ab532209b69f057ddc559c19bc8ae62bf3fd564c9a35d9a83d14a95834a92bae6d9a29ae5e8ece07910d16433e4c6230c9bd7d68b47de0de9843988af6cdc88b5301820443bd4d0537778bf6b4c1dd067fcf14b81015f2a67c7f2a28f9cb7e0684d3cb4b1c24d9b343122a086611b489532f1c3a26779da1706c6759d96d8ab>"
|
||||||
|
];
|
||||||
|
|
||||||
|
function generate_random_hex(len) {
|
||||||
|
return exec_output("head -c " + int(len) + " /dev/urandom | hexdump -ve '1/1 \"%02x\"' 2>/dev/null") || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function generate_extra_cps() {
|
||||||
|
let rand_val = (time() + int(exec_output("echo $PPID 2>/dev/null") || "1")) % 3 + 1;
|
||||||
|
let result = "";
|
||||||
|
for (let i = 0; i < rand_val; i++) {
|
||||||
|
let length = (time() + i * 7) % 48 + 16;
|
||||||
|
result += "<b 0x" + generate_random_hex(length) + ">";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let i1 = to_hex(warp_cps_payloads[time() % length(warp_cps_payloads)]);
|
||||||
|
let i2 = to_hex(generate_extra_cps());
|
||||||
|
let i3 = to_hex(generate_extra_cps());
|
||||||
|
let i4 = to_hex(generate_extra_cps());
|
||||||
|
let i5 = to_hex(generate_extra_cps());
|
||||||
|
|
||||||
|
// 7. Output result
|
||||||
|
write_json({
|
||||||
|
success: true,
|
||||||
|
local_address: response_data.config.interface.addresses.v4 + "/32 " + response_data.config.interface.addresses.v6 + "/128",
|
||||||
|
private_key: private_key,
|
||||||
|
peer_public_key: response_data.config.peers[0].public_key,
|
||||||
|
server_address: best_ip,
|
||||||
|
server_port: random_port,
|
||||||
|
|
||||||
|
awg_jc: 4,
|
||||||
|
awg_jmin: 40,
|
||||||
|
awg_jmax: 70,
|
||||||
|
awg_s1: 0, awg_s2: 0, awg_s3: 0, awg_s4: 0,
|
||||||
|
awg_h1: 1, awg_h2: 2, awg_h3: 3, awg_h4: 4,
|
||||||
|
|
||||||
|
awg_i1: i1,
|
||||||
|
awg_i2: i2,
|
||||||
|
awg_i3: i3,
|
||||||
|
awg_i4: i4,
|
||||||
|
awg_i5: i5
|
||||||
|
});
|
||||||
@@ -48,6 +48,18 @@ let url_query_params = runtime_url.query_params;
|
|||||||
|
|
||||||
const CONFIG_NAME = "tachyon";
|
const CONFIG_NAME = "tachyon";
|
||||||
|
|
||||||
|
// Convert a UCI value that may be a binary buffer (<b 0x...>) to a plain hex string.
|
||||||
|
// UCI stores values written as '<b 0x...>' strings as binary, which when
|
||||||
|
// serialized back to JSON appear as '<b 0x...>' — invalid for sing-box.
|
||||||
|
function uci_bin_to_hex(val) {
|
||||||
|
if (val == null || val == "") return "";
|
||||||
|
// Coerce to string — binary buffer becomes "<b 0x...>"
|
||||||
|
let s = replace("" + val, "<b 0x", "");
|
||||||
|
s = replace(s, ">", "");
|
||||||
|
s = replace(s, " ", "");
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
function parent_dir(path) {
|
function parent_dir(path) {
|
||||||
path = as_string(path);
|
path = as_string(path);
|
||||||
let slash = rindex(path, "/");
|
let slash = rindex(path, "/");
|
||||||
@@ -2283,16 +2295,16 @@ function add_awg_endpoint(config, section) {
|
|||||||
s3: int_option(section, "awg_s3", "0"),
|
s3: int_option(section, "awg_s3", "0"),
|
||||||
s4: int_option(section, "awg_s4", "0")
|
s4: int_option(section, "awg_s4", "0")
|
||||||
};
|
};
|
||||||
let i1 = option(section, "awg_i1", "");
|
let i1 = uci_bin_to_hex(option(section, "awg_i1", ""));
|
||||||
let i2 = option(section, "awg_i2", "");
|
let i2 = uci_bin_to_hex(option(section, "awg_i2", ""));
|
||||||
let i3 = option(section, "awg_i3", "");
|
let i3 = uci_bin_to_hex(option(section, "awg_i3", ""));
|
||||||
let i4 = option(section, "awg_i4", "");
|
let i4 = uci_bin_to_hex(option(section, "awg_i4", ""));
|
||||||
let i5 = option(section, "awg_i5", "");
|
let i5 = uci_bin_to_hex(option(section, "awg_i5", ""));
|
||||||
if (i1 != "") amnezia.i1 = i1;
|
if (i1 != "") amnezia.i1 = i1;
|
||||||
if (i2 != "") amnezia.i2 = i2;
|
if (i2 != "") amnezia.i2 = i2;
|
||||||
if (i3 != "") amnezia.i3 = i3;
|
if (i3 != "" && i3 != "0") amnezia.i3 = i3;
|
||||||
if (i4 != "") amnezia.i4 = i4;
|
if (i4 != "" && i4 != "0") amnezia.i4 = i4;
|
||||||
if (i5 != "") amnezia.i5 = i5;
|
if (i5 != "" && i5 != "0") amnezia.i5 = i5;
|
||||||
|
|
||||||
endpoint.amnezia = amnezia;
|
endpoint.amnezia = amnezia;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user