From 04f5c7144f8e8f8462a885f300f6cb1182246dd9 Mon Sep 17 00:00:00 2001 From: Dushnilin Date: Fri, 17 Jul 2026 16:21:48 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA?= =?UTF-8?q?=D1=83=20=D0=B4=D0=B5=D0=B9=D1=81=D1=82=D0=B2=D0=B8=D1=8F=20WAR?= =?UTF-8?q?P=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D1=8D=D0=BD=D0=B4=D0=BF?= =?UTF-8?q?=D0=BE=D0=B8=D0=BD=D1=82=D1=8B=20sing-box-extended?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 6 +- fe-app-tachyon/locales/tachyon.pot | 18 ++++++ fe-app-tachyon/locales/tachyon.ru.po | 18 ++++++ .../resources/view/tachyon/section.js | 61 +++++++++++++++++++ luci-app-tachyon/po/ru/tachyon.po | 18 ++++++ luci-app-tachyon/po/templates/tachyon.pot | 18 ++++++ tachyon/files/usr/lib/config/validator.uc | 6 +- tachyon/files/usr/lib/singbox/generator.uc | 32 +++++++++- 8 files changed, 168 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 5eb50263..3734cbd6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,10 @@ -.idea -.mimocode fe-app-tachyon/node_modules fe-app-tachyon/.yarn/ fe-app-tachyon/.env .DS_Store shellcheck-v0.10.0/ -AGENTS.md sandbox/ .wsl-build/ dist/ -build-out/ - +build-out/ \ No newline at end of file diff --git a/fe-app-tachyon/locales/tachyon.pot b/fe-app-tachyon/locales/tachyon.pot index c5fcfd79..a9e26caa 100644 --- a/fe-app-tachyon/locales/tachyon.pot +++ b/fe-app-tachyon/locales/tachyon.pot @@ -3640,3 +3640,21 @@ msgstr "" #: src/tachyon/tabs/diagnostic/checks/runZapret2Check.ts:107 msgid "Zapret2 sing-box outbound is not configured" msgstr "" + +msgid "WARP Private Key" +msgstr "" + +msgid "WireGuard private key from your Cloudflare WARP account" +msgstr "" + +msgid "WARP Account ID" +msgstr "" + +msgid "Cloudflare WARP account / device ID" +msgstr "" + +msgid "WARP Access Token" +msgstr "" + +msgid "Cloudflare WARP API access token" +msgstr "" diff --git a/fe-app-tachyon/locales/tachyon.ru.po b/fe-app-tachyon/locales/tachyon.ru.po index fcc2af7a..e9cfb0ff 100644 --- a/fe-app-tachyon/locales/tachyon.ru.po +++ b/fe-app-tachyon/locales/tachyon.ru.po @@ -2539,3 +2539,21 @@ msgstr "Запуск:" msgid "Stopping:" msgstr "Остановка:" + +msgid "WARP Private Key" +msgstr "Приватный ключ WARP" + +msgid "WireGuard private key from your Cloudflare WARP account" +msgstr "Приватный ключ WireGuard от вашего аккаунта Cloudflare WARP" + +msgid "WARP Account ID" +msgstr "ID аккаунта WARP" + +msgid "Cloudflare WARP account / device ID" +msgstr "ID аккаунта / устройства Cloudflare WARP" + +msgid "WARP Access Token" +msgstr "Токен доступа WARP" + +msgid "Cloudflare WARP API access token" +msgstr "Токен доступа к API Cloudflare WARP" diff --git a/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js b/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js index ea89f161..f6a6201c 100644 --- a/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js +++ b/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js @@ -425,6 +425,7 @@ const actionProvidersAvailabilityState = { zapretInstalled: false, zapret2Installed: false, byedpiInstalled: false, + singBoxExtended: false, }; let actionProvidersAvailabilityPromise = null; let actionProvidersAvailabilityLoader = null; @@ -468,6 +469,12 @@ function updateActionProvidersAvailabilityState(nextState) { ); } + if (typeof nextState.singBoxExtended !== "undefined") { + actionProvidersAvailabilityState.singBoxExtended = Boolean( + nextState.singBoxExtended, + ); + } + actionProvidersAvailabilityPromise = null; } @@ -480,6 +487,7 @@ function updateActionProvidersAvailabilityFromSystemInfo(systemInfo) { zapretInstalled: Boolean(systemInfo.zapret_installed), zapret2Installed: Boolean(systemInfo.zapret2_installed), byedpiInstalled: Boolean(systemInfo.byedpi_installed), + singBoxExtended: Boolean(systemInfo.sing_box_extended), }); } @@ -3942,6 +3950,7 @@ function ensureActionProvidersAvailabilityLoaded() { zapretInstalled: Boolean(capabilities?.zapretInstalled), zapret2Installed: Boolean(capabilities?.zapret2Installed), byedpiInstalled: Boolean(capabilities?.byedpiInstalled), + singBoxExtended: Boolean(capabilities?.singBoxExtended), }); return actionProvidersAvailabilityState; }) @@ -3996,6 +4005,7 @@ function ensureActionProvidersAvailabilityLoaded() { actionProvidersAvailabilityState.zapretInstalled = false; actionProvidersAvailabilityState.zapret2Installed = false; actionProvidersAvailabilityState.byedpiInstalled = false; + // singBoxExtended is set via systemInfo, keep existing value return actionProvidersAvailabilityState; }) .finally(() => { @@ -4017,6 +4027,10 @@ function isByedpiInstalledForUi() { return actionProvidersAvailabilityState.byedpiInstalled; } +function isSingBoxExtendedForUi() { + return actionProvidersAvailabilityState.singBoxExtended; +} + function getRuleConfiguredAction(section_id) { const action = uci.get(UCI_PACKAGE, section_id, "action"); return action ? `${action}` : null; @@ -4046,6 +4060,8 @@ function getActionOptionLabel(action) { return "ByeDPI"; case "awg": return "AmneziaWG"; + case "warp": + return "WARP"; case "outbound": return _("JSON outbound"); case "proxy": @@ -4073,6 +4089,10 @@ function getRuleActionDisplayValue(section_id) { return "AmneziaWG"; } + if (action === "warp") { + return "WARP"; + } + return getActionOptionLabel(action); } @@ -4086,6 +4106,9 @@ function populateActionOptionValues(option) { option.value("connection", getActionOptionLabel("connection")); option.value("awg", getActionOptionLabel("awg")); + if (isSingBoxExtendedForUi()) { + option.value("warp", getActionOptionLabel("warp")); + } option.value("bypass", "Bypass"); option.value("block", "Block"); option.value("dns", "DNS"); @@ -7296,6 +7319,44 @@ function createSectionContent(section) { o.rmempty = false; o.depends("action", "awg"); + // ── WARP (Cloudflare WARP via sing-box-extended) ────────────────────────── + + o = section.taboption( + "settings", + form.Value, + "warp_private_key", + _("WARP Private Key"), + _("WireGuard private key from your Cloudflare WARP account"), + ); + o.modalonly = true; + o.rmempty = false; + o.depends("action", "warp"); + o.validate = validateRequiredText; + + o = section.taboption( + "settings", + form.Value, + "warp_account_id", + _("WARP Account ID"), + _("Cloudflare WARP account / device ID"), + ); + o.modalonly = true; + o.rmempty = false; + o.depends("action", "warp"); + o.validate = validateRequiredText; + + o = section.taboption( + "settings", + form.Value, + "warp_access_token", + _("WARP Access Token"), + _("Cloudflare WARP API access token"), + ); + o.modalonly = true; + o.rmempty = false; + o.depends("action", "warp"); + o.validate = validateRequiredText; + o = section.taboption( "settings", form.DynamicList, diff --git a/luci-app-tachyon/po/ru/tachyon.po b/luci-app-tachyon/po/ru/tachyon.po index fcc2af7a..e9cfb0ff 100644 --- a/luci-app-tachyon/po/ru/tachyon.po +++ b/luci-app-tachyon/po/ru/tachyon.po @@ -2539,3 +2539,21 @@ msgstr "Запуск:" msgid "Stopping:" msgstr "Остановка:" + +msgid "WARP Private Key" +msgstr "Приватный ключ WARP" + +msgid "WireGuard private key from your Cloudflare WARP account" +msgstr "Приватный ключ WireGuard от вашего аккаунта Cloudflare WARP" + +msgid "WARP Account ID" +msgstr "ID аккаунта WARP" + +msgid "Cloudflare WARP account / device ID" +msgstr "ID аккаунта / устройства Cloudflare WARP" + +msgid "WARP Access Token" +msgstr "Токен доступа WARP" + +msgid "Cloudflare WARP API access token" +msgstr "Токен доступа к API Cloudflare WARP" diff --git a/luci-app-tachyon/po/templates/tachyon.pot b/luci-app-tachyon/po/templates/tachyon.pot index c5fcfd79..a9e26caa 100644 --- a/luci-app-tachyon/po/templates/tachyon.pot +++ b/luci-app-tachyon/po/templates/tachyon.pot @@ -3640,3 +3640,21 @@ msgstr "" #: src/tachyon/tabs/diagnostic/checks/runZapret2Check.ts:107 msgid "Zapret2 sing-box outbound is not configured" msgstr "" + +msgid "WARP Private Key" +msgstr "" + +msgid "WireGuard private key from your Cloudflare WARP account" +msgstr "" + +msgid "WARP Account ID" +msgstr "" + +msgid "Cloudflare WARP account / device ID" +msgstr "" + +msgid "WARP Access Token" +msgstr "" + +msgid "Cloudflare WARP API access token" +msgstr "" diff --git a/tachyon/files/usr/lib/config/validator.uc b/tachyon/files/usr/lib/config/validator.uc index f66642ac..01a64dc2 100644 --- a/tachyon/files/usr/lib/config/validator.uc +++ b/tachyon/files/usr/lib/config/validator.uc @@ -556,6 +556,8 @@ function download_section_action_available(action, byedpi_installed, zapret_inst action = as_string(action); if (connections.is_connections_action(action)) return true; + if (action == "awg" || action == "warp") + return true; if (action == "byedpi") return bool_flag(byedpi_installed); if (action == "zapret") @@ -807,11 +809,11 @@ function rule_action(section) { } function rule_action_supported(action) { - return contains([ "connection", "proxy", "outbound", "vpn", "awg", "bypass", "block", "dns", "zapret", "zapret2", "byedpi" ], as_string(action)); + return contains([ "connection", "proxy", "outbound", "vpn", "awg", "warp", "bypass", "block", "dns", "zapret", "zapret2", "byedpi" ], as_string(action)); } function server_routing_section_action_supported(action) { - return contains([ "connection", "proxy", "outbound", "vpn", "awg", "zapret", "zapret2", "byedpi" ], as_string(action)); + return contains([ "connection", "proxy", "outbound", "vpn", "awg", "warp", "zapret", "zapret2", "byedpi" ], as_string(action)); } function duration_to_seconds_value(value) { diff --git a/tachyon/files/usr/lib/singbox/generator.uc b/tachyon/files/usr/lib/singbox/generator.uc index e35aa90b..e3ab1b0c 100644 --- a/tachyon/files/usr/lib/singbox/generator.uc +++ b/tachyon/files/usr/lib/singbox/generator.uc @@ -2287,6 +2287,32 @@ function add_awg_outbound(config, section) { push(config.outbounds, outbound); } +function add_warp_endpoint(config, section) { + let tag = outbound_tag(section[".name"]); + let account_id = option(section, "warp_account_id", ""); + let access_token = option(section, "warp_access_token", ""); + let private_key = option(section, "warp_private_key", ""); + + if (private_key == "") + runtime_generate_unsupported("WARP section '" + section[".name"] + "' missing warp_private_key"); + if (account_id == "") + runtime_generate_unsupported("WARP section '" + section[".name"] + "' missing warp_account_id"); + if (access_token == "") + runtime_generate_unsupported("WARP section '" + section[".name"] + "' missing warp_access_token"); + + push(config.endpoints, { + type: "warp", + tag, + name: tag, + profiles: [{ + id: account_id, + auth_token: access_token, + private_key + }] + }); +} + + function add_zapret_outbound(config, section, sections) { let index = enabled_action_index(sections, section, "zapret"); if (index <= 0) @@ -2782,6 +2808,8 @@ function add_outbound_for_section(config, section, taken, sections) { add_connections_outbound(config, section, taken); else if (action == "awg") add_awg_outbound(config, section); + else if (action == "warp") + add_warp_endpoint(config, section); else if (action == "zapret") add_zapret_outbound(config, section, sections); else if (action == "zapret2") @@ -2806,7 +2834,7 @@ function reserve_section_outbound_tags(sections, taken) { for (let section in sections) { let action = option(section, "action", ""); if (connections.is_connections_action(action) || - action == "awg" || action == "byedpi" || action == "zapret" || action == "zapret2") + action == "awg" || action == "warp" || action == "byedpi" || action == "zapret" || action == "zapret2") taken[outbound_tag(section[".name"])] = true; if (!connections.is_connections_action(action)) @@ -2831,7 +2859,7 @@ function add_service_route_rules(config, sections) { for (let section in sections) { let action = option(section, "action", ""); if (connections.is_connections_action(action) || - action == "awg" || action == "byedpi" || action == "zapret" || action == "zapret2") { + action == "awg" || action == "warp" || action == "byedpi" || action == "zapret" || action == "zapret2") { first = section; break; }