From 8a4c9a98cbf8efce7c2efd1e3daa4c996830b30c Mon Sep 17 00:00:00 2001
From: Sam Mosleh <41725025+sam-mosleh@users.noreply.github.com>
Date: Tue, 27 Jan 2026 02:05:15 +0400
Subject: [PATCH 01/13] Fix modifying default CA (#3708)
---
install.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/install.sh b/install.sh
index 62c8ea37..e01cff99 100644
--- a/install.sh
+++ b/install.sh
@@ -147,7 +147,7 @@ setup_ssl_certificate() {
echo -e "${green}Issuing SSL certificate for ${domain}...${plain}"
echo -e "${yellow}Note: Port 80 must be open and accessible from the internet${plain}"
- ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt >/dev/null 2>&1
+ ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force >/dev/null 2>&1
~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport 80 --force
if [ $? -ne 0 ]; then
From fd5f5917378129d7a555a246d42a2a572476b948 Mon Sep 17 00:00:00 2001
From: "Danil S." <135337715+sh1shd@users.noreply.github.com>
Date: Tue, 27 Jan 2026 05:06:01 +0700
Subject: [PATCH 02/13] feat: more subscription information fields (#3701)
* feat: more subscription information fields
* fix: incorrect translation
* feat: implement field for Happ custom routing rules
---
sub/sub.go | 28 +++++++-
sub/subController.go | 66 ++++++++++++++-----
web/assets/js/model/setting.js | 5 ++
web/entity/entity.go | 5 ++
.../settings/panel/subscription/general.html | 51 ++++++++++++--
web/service/setting.go | 25 +++++++
web/translation/translate.ar_EG.toml | 10 +++
web/translation/translate.en_US.toml | 10 +++
web/translation/translate.es_ES.toml | 10 +++
web/translation/translate.fa_IR.toml | 10 +++
web/translation/translate.id_ID.toml | 10 +++
web/translation/translate.ja_JP.toml | 10 +++
web/translation/translate.pt_BR.toml | 10 +++
web/translation/translate.ru_RU.toml | 12 +++-
web/translation/translate.tr_TR.toml | 10 +++
web/translation/translate.uk_UA.toml | 10 +++
web/translation/translate.vi_VN.toml | 10 +++
web/translation/translate.zh_CN.toml | 10 +++
web/translation/translate.zh_TW.toml | 10 +++
19 files changed, 285 insertions(+), 27 deletions(-)
diff --git a/sub/sub.go b/sub/sub.go
index 0605c8b9..1dcd9601 100644
--- a/sub/sub.go
+++ b/sub/sub.go
@@ -153,6 +153,31 @@ func (s *Server) initRouter() (*gin.Engine, error) {
SubTitle = ""
}
+ SubSupportUrl, err := s.settingService.GetSubSupportUrl()
+ if err != nil {
+ SubSupportUrl = ""
+ }
+
+ SubProfileUrl, err := s.settingService.GetSubProfileUrl()
+ if err != nil {
+ SubProfileUrl = ""
+ }
+
+ SubAnnounce, err := s.settingService.GetSubAnnounce()
+ if err != nil {
+ SubAnnounce = ""
+ }
+
+ SubEnableRouting, err := s.settingService.GetSubEnableRouting()
+ if err != nil {
+ return nil, err
+ }
+
+ SubRoutingRules, err := s.settingService.GetSubRoutingRules()
+ if err != nil {
+ SubRoutingRules = ""
+ }
+
// set per-request localizer from headers/cookies
engine.Use(locale.LocalizerMiddleware())
@@ -231,7 +256,8 @@ func (s *Server) initRouter() (*gin.Engine, error) {
s.sub = NewSUBController(
g, LinksPath, JsonPath, subJsonEnable, Encrypt, ShowInfo, RemarkModel, SubUpdates,
- SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules, SubTitle)
+ SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules, SubTitle, SubSupportUrl,
+ SubProfileUrl, SubAnnounce, SubEnableRouting, SubRoutingRules)
return engine, nil
}
diff --git a/sub/subController.go b/sub/subController.go
index ec574d6e..7653a4e1 100644
--- a/sub/subController.go
+++ b/sub/subController.go
@@ -4,6 +4,7 @@ import (
"encoding/base64"
"fmt"
"strings"
+ "strconv"
"github.com/mhsanaei/3x-ui/v2/config"
@@ -12,12 +13,17 @@ import (
// SUBController handles HTTP requests for subscription links and JSON configurations.
type SUBController struct {
- subTitle string
- subPath string
- subJsonPath string
- jsonEnabled bool
- subEncrypt bool
- updateInterval string
+ subTitle string
+ subSupportUrl string
+ subProfileUrl string
+ subAnnounce string
+ subEnableRouting bool
+ subRoutingRules string
+ subPath string
+ subJsonPath string
+ jsonEnabled bool
+ subEncrypt bool
+ updateInterval string
subService *SubService
subJsonService *SubJsonService
@@ -38,18 +44,28 @@ func NewSUBController(
jsonMux string,
jsonRules string,
subTitle string,
+ subSupportUrl string,
+ subProfileUrl string,
+ subAnnounce string,
+ subEnableRouting bool,
+ subRoutingRules string,
) *SUBController {
sub := NewSubService(showInfo, rModel)
a := &SUBController{
- subTitle: subTitle,
- subPath: subPath,
- subJsonPath: jsonPath,
- jsonEnabled: jsonEnabled,
- subEncrypt: encrypt,
- updateInterval: update,
+ subTitle: subTitle,
+ subSupportUrl: subSupportUrl,
+ subProfileUrl: subProfileUrl,
+ subAnnounce: subAnnounce,
+ subEnableRouting: subEnableRouting,
+ subRoutingRules: subRoutingRules,
+ subPath: subPath,
+ subJsonPath: jsonPath,
+ jsonEnabled: jsonEnabled,
+ subEncrypt: encrypt,
+ updateInterval: update,
- subService: sub,
- subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
+ subService: sub,
+ subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
}
a.initRouter(g)
return a
@@ -127,7 +143,7 @@ func (a *SUBController) subs(c *gin.Context) {
// Add headers
header := fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
- a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle)
+ a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle, a.subSupportUrl, a.subProfileUrl, a.subAnnounce, a.subEnableRouting, a.subRoutingRules)
if a.subEncrypt {
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
@@ -145,17 +161,31 @@ func (a *SUBController) subJsons(c *gin.Context) {
if err != nil || len(jsonSub) == 0 {
c.String(400, "Error!")
} else {
-
// Add headers
- a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle)
+ a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle, a.subSupportUrl, a.subProfileUrl, a.subAnnounce, a.subEnableRouting, a.subRoutingRules)
c.String(200, jsonSub)
}
}
// ApplyCommonHeaders sets common HTTP headers for subscription responses including user info, update interval, and profile title.
-func (a *SUBController) ApplyCommonHeaders(c *gin.Context, header, updateInterval, profileTitle string) {
+func (a *SUBController) ApplyCommonHeaders(
+ c *gin.Context,
+ header,
+ updateInterval,
+ profileTitle string,
+ profileSupportUrl string,
+ profileUrl string,
+ profileAnnounce string,
+ profileEnableRouting bool,
+ profileRoutingRules string,
+) {
c.Writer.Header().Set("Subscription-Userinfo", header)
c.Writer.Header().Set("Profile-Update-Interval", updateInterval)
c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileTitle)))
+ c.Writer.Header().Set("Support-Url", profileSupportUrl)
+ c.Writer.Header().Set("Profile-Web-Page-Url", profileUrl)
+ c.Writer.Header().Set("Announce", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileAnnounce)))
+ c.Writer.Header().Set("Routing-Enable", strconv.FormatBool(profileEnableRouting))
+ c.Writer.Header().Set("Routing", profileRoutingRules)
}
diff --git a/web/assets/js/model/setting.js b/web/assets/js/model/setting.js
index 53ffae1a..af80a63e 100644
--- a/web/assets/js/model/setting.js
+++ b/web/assets/js/model/setting.js
@@ -29,6 +29,11 @@ class AllSetting {
this.subEnable = true;
this.subJsonEnable = false;
this.subTitle = "";
+ this.subSupportUrl = "";
+ this.subProfileUrl = "";
+ this.subAnnounce = "";
+ this.subEnableRouting = true;
+ this.subRoutingRules = "";
this.subListen = "";
this.subPort = 2096;
this.subPath = "/sub/";
diff --git a/web/entity/entity.go b/web/entity/entity.go
index 42e2df85..40294925 100644
--- a/web/entity/entity.go
+++ b/web/entity/entity.go
@@ -57,6 +57,11 @@ type AllSetting struct {
SubEnable bool `json:"subEnable" form:"subEnable"` // Enable subscription server
SubJsonEnable bool `json:"subJsonEnable" form:"subJsonEnable"` // Enable JSON subscription endpoint
SubTitle string `json:"subTitle" form:"subTitle"` // Subscription title
+ SubSupportUrl string `json:"subSupportUrl" form:"subSupportUrl"` // Subscription support URL
+ SubProfileUrl string `json:"subProfileUrl" form:"subProfileUrl"` // Subscription profile URL
+ SubAnnounce string `json:"subAnnounce" form:"subAnnounce"` // Subscription announce
+ SubEnableRouting bool `json:"subEnableRouting" form:"subEnableRouting"` // Enable routing for subscription
+ SubRoutingRules string `json:"subRoutingRules" form:"subRoutingRules"` // Subscription global routing rules (Only for Happ)
SubListen string `json:"subListen" form:"subListen"` // Subscription server listen IP
SubPort int `json:"subPort" form:"subPort"` // Subscription server port
SubPath string `json:"subPath" form:"subPath"` // Base path for subscription URLs
diff --git a/web/html/settings/panel/subscription/general.html b/web/html/settings/panel/subscription/general.html
index e65b2738..5d83aa37 100644
--- a/web/html/settings/panel/subscription/general.html
+++ b/web/html/settings/panel/subscription/general.html
@@ -15,13 +15,6 @@
-
- {{ i18n "pages.settings.subTitle"}}
- {{ i18n "pages.settings.subTitleDesc"}}
-
-
-
-
{{ i18n "pages.settings.subListen"}}
{{ i18n "pages.settings.subListenDesc"}}
@@ -78,6 +71,50 @@
+ {{ i18n "pages.xray.basicTemplate"}}
+
+ {{ i18n "pages.settings.subTitle"}}
+ {{ i18n "pages.settings.subTitleDesc"}}
+
+
+
+
+
+ {{ i18n "pages.settings.subSupportUrl"}}
+ {{ i18n "pages.settings.subSupportUrlDesc"}}
+
+
+
+
+
+ {{ i18n "pages.settings.subProfileUrl"}}
+ {{ i18n "pages.settings.subProfileUrlDesc"}}
+
+
+
+
+
+ {{ i18n "pages.settings.subAnnounce"}}
+ {{ i18n "pages.settings.subAnnounceDesc"}}
+
+
+
+
+ {{ i18n "pages.xray.advancedTemplate"}} (Happ)
+
+ {{ i18n "pages.settings.subEnableRouting"}}
+ {{ i18n "pages.settings.subEnableRoutingDesc"}}
+
+
+
+
+
+ {{ i18n "pages.settings.subRoutingRules"}}
+ {{ i18n "pages.settings.subRoutingRulesDesc"}}
+
+
+
+
diff --git a/web/service/setting.go b/web/service/setting.go
index 56db346d..3fa37f44 100644
--- a/web/service/setting.go
+++ b/web/service/setting.go
@@ -53,6 +53,11 @@ var defaultValueMap = map[string]string{
"subEnable": "true",
"subJsonEnable": "false",
"subTitle": "",
+ "subSupportUrl": "",
+ "subProfileUrl": "",
+ "subAnnounce": "",
+ "subEnableRouting": "true",
+ "subRoutingRules": "",
"subListen": "",
"subPort": "2096",
"subPath": "/sub/",
@@ -459,6 +464,26 @@ func (s *SettingService) GetSubTitle() (string, error) {
return s.getString("subTitle")
}
+func (s *SettingService) GetSubSupportUrl() (string, error) {
+ return s.getString("subSupportUrl")
+}
+
+func (s *SettingService) GetSubProfileUrl() (string, error) {
+ return s.getString("subProfileUrl")
+}
+
+func (s *SettingService) GetSubAnnounce() (string, error) {
+ return s.getString("subAnnounce")
+}
+
+func (s *SettingService) GetSubEnableRouting() (bool, error) {
+ return s.getBool("subEnableRouting")
+}
+
+func (s *SettingService) GetSubRoutingRules() (string, error) {
+ return s.getString("subRoutingRules")
+}
+
func (s *SettingService) GetSubListen() (string, error) {
return s.getString("subListen")
}
diff --git a/web/translation/translate.ar_EG.toml b/web/translation/translate.ar_EG.toml
index 06a3e937..6d75d196 100644
--- a/web/translation/translate.ar_EG.toml
+++ b/web/translation/translate.ar_EG.toml
@@ -374,6 +374,16 @@
"subJsonEnable" = "تمكين/تعطيل نقطة نهاية اشتراك JSON بشكل مستقل."
"subTitle" = "عنوان الاشتراك"
"subTitleDesc" = "العنوان اللي هيظهر في عميل VPN"
+"subSupportUrl" = "رابط الدعم"
+"subSupportUrlDesc" = "رابط الدعم الفني المعروض في عميل VPN"
+"subProfileUrl" = "رابط الملف الشخصي"
+"subProfileUrlDesc" = "رابط لموقعك الإلكتروني يظهر في عميل VPN"
+"subAnnounce" = "إعلان"
+"subAnnounceDesc" = "نص الإعلان المعروض في عميل VPN"
+"subEnableRouting" = "تفعيل التوجيه"
+"subEnableRoutingDesc" = "إعداد عام لتمكين التوجيه (Routing) في عميل VPN. (فقط لـ Happ)"
+"subRoutingRules" = "قواعد التوجيه"
+"subRoutingRulesDesc" = "قواعد التوجيه العامة لعميل VPN. (فقط لـ Happ)"
"subListen" = "IP الاستماع"
"subListenDesc" = "عنوان IP لخدمة الاشتراك. (سيبه فاضي عشان يستمع على كل الـ IPs)"
"subPort" = "بورت الاستماع"
diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml
index 6da9185b..244e6f2c 100644
--- a/web/translation/translate.en_US.toml
+++ b/web/translation/translate.en_US.toml
@@ -374,6 +374,16 @@
"subJsonEnable" = "Enable/Disable the JSON subscription endpoint independently."
"subTitle" = "Subscription Title"
"subTitleDesc" = "Title shown in VPN client"
+"subSupportUrl" = "Support URL"
+"subSupportUrlDesc" = "Technical support link shown in the VPN client"
+"subProfileUrl" = "Profile URL"
+"subProfileUrlDesc" = "A link to your website displayed in the VPN client"
+"subAnnounce" = "Announce"
+"subAnnounceDesc" = "The text of the announce displayed in the VPN client"
+"subEnableRouting" = "Enable routing"
+"subEnableRoutingDesc" = "Global setting to enable routing in the VPN client. (Only for Happ)"
+"subRoutingRules" = "Routing rules"
+"subRoutingRulesDesc" = "Global routing rules for the VPN client. (Only for Happ)"
"subListen" = "Listen IP"
"subListenDesc" = "The IP address for the subscription service. (leave blank to listen on all IPs)"
"subPort" = "Listen Port"
diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml
index ccd5beff..b0dde898 100644
--- a/web/translation/translate.es_ES.toml
+++ b/web/translation/translate.es_ES.toml
@@ -374,6 +374,16 @@
"subJsonEnable" = "Habilitar/Deshabilitar el endpoint de suscripción JSON de forma independiente."
"subTitle" = "Título de la Suscripción"
"subTitleDesc" = "Título mostrado en el cliente de VPN"
+"subSupportUrl" = "URL de soporte"
+"subSupportUrlDesc" = "Enlace de soporte técnico mostrado en el cliente VPN"
+"subProfileUrl" = "URL del perfil"
+"subProfileUrlDesc" = "Un enlace a tu sitio web mostrado en el cliente VPN"
+"subAnnounce" = "Anuncio"
+"subAnnounceDesc" = "El texto del anuncio mostrado en el cliente VPN"
+"subEnableRouting" = "Habilitar enrutamiento"
+"subEnableRoutingDesc" = "Configuración global para habilitar el enrutamiento en el cliente VPN. (Solo para Happ)"
+"subRoutingRules" = "Reglas de enrutamiento"
+"subRoutingRulesDesc" = "Reglas de enrutamiento globales para el cliente VPN. (Solo para Happ)"
"subListen" = "Listening IP"
"subListenDesc" = "Dejar en blanco por defecto para monitorear todas las IPs."
"subPort" = "Puerto de Suscripción"
diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml
index a25484d1..1eda5fb5 100644
--- a/web/translation/translate.fa_IR.toml
+++ b/web/translation/translate.fa_IR.toml
@@ -374,6 +374,16 @@
"subJsonEnable" = "فعال/غیرفعالسازی مستقل نقطه دسترسی سابسکریپشن JSON."
"subTitle" = "عنوان اشتراک"
"subTitleDesc" = "عنوان نمایش داده شده در کلاینت VPN"
+"subSupportUrl" = "آدرس پشتیبانی"
+"subSupportUrlDesc" = "لینک پشتیبانی فنی که در کلاینت VPN نمایش داده میشود"
+"subProfileUrl" = "آدرس پروفایل"
+"subProfileUrlDesc" = "لینک وبسایت شما که در کلاینت VPN نمایش داده میشود"
+"subAnnounce" = "اعلان"
+"subAnnounceDesc" = "متن اعلانی که در کلاینت VPN نمایش داده میشود"
+"subEnableRouting" = "فعالسازی مسیریابی"
+"subEnableRoutingDesc" = "تنظیمات سراسری برای فعالسازی مسیریابی در کلاینت VPN. (فقط برای Happ)"
+"subRoutingRules" = "قوانین مسیریابی"
+"subRoutingRulesDesc" = "قوانین مسیریابی سراسری برای کلاینت VPN. (فقط برای Happ)"
"subListen" = "آدرس آیپی"
"subListenDesc" = "آدرس آیپی برای سرویس سابسکریپشن. برای گوش دادن بهتمام آیپیها خالیبگذارید"
"subPort" = "پورت"
diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml
index 7bd7aae0..8804ef04 100644
--- a/web/translation/translate.id_ID.toml
+++ b/web/translation/translate.id_ID.toml
@@ -374,6 +374,16 @@
"subJsonEnable" = "Aktifkan/Nonaktifkan endpoint langganan JSON secara mandiri."
"subTitle" = "Judul Langganan"
"subTitleDesc" = "Judul yang ditampilkan di klien VPN"
+"subSupportUrl" = "URL Dukungan"
+"subSupportUrlDesc" = "Tautan dukungan teknis yang ditampilkan di klien VPN"
+"subProfileUrl" = "URL Profil"
+"subProfileUrlDesc" = "Tautan ke situs web Anda yang ditampilkan di klien VPN"
+"subAnnounce" = "Pengumuman"
+"subAnnounceDesc" = "Teks pengumuman yang ditampilkan di klien VPN"
+"subEnableRouting" = "Aktifkan perutean"
+"subEnableRoutingDesc" = "Pengaturan global untuk mengaktifkan perutean (routing) di klien VPN. (Hanya untuk Happ)"
+"subRoutingRules" = "Aturan routing"
+"subRoutingRulesDesc" = "Aturan routing global untuk klien VPN. (Hanya untuk Happ)"
"subListen" = "IP Pendengar"
"subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)"
"subPort" = "Port Pendengar"
diff --git a/web/translation/translate.ja_JP.toml b/web/translation/translate.ja_JP.toml
index bc9c294a..8bba24c0 100644
--- a/web/translation/translate.ja_JP.toml
+++ b/web/translation/translate.ja_JP.toml
@@ -374,6 +374,16 @@
"subJsonEnable" = "JSON サブスクリプションのエンドポイントを個別に有効/無効にする。"
"subTitle" = "サブスクリプションタイトル"
"subTitleDesc" = "VPNクライアントに表示されるタイトル"
+"subSupportUrl" = "サポートURL"
+"subSupportUrlDesc" = "VPNクライアントに表示されるテクニカルサポートへのリンク"
+"subProfileUrl" = "プロフィールURL"
+"subProfileUrlDesc" = "VPNクライアントに表示されるWebサイトへのリンク"
+"subAnnounce" = "お知らせ"
+"subAnnounceDesc" = "VPNクライアントに表示されるお知らせのテキスト"
+"subEnableRouting" = "ルーティングを有効化"
+"subEnableRoutingDesc" = "VPNクライアントでルーティングを有効にするためのグローバル設定。(Happのみ)"
+"subRoutingRules" = "ルーティングルール"
+"subRoutingRulesDesc" = "VPNクライアントのグローバルルーティングルール。(Happのみ)"
"subListen" = "監視IP"
"subListenDesc" = "サブスクリプションサービスが監視するIPアドレス(空白にするとすべてのIPを監視)"
"subPort" = "監視ポート"
diff --git a/web/translation/translate.pt_BR.toml b/web/translation/translate.pt_BR.toml
index 86b3b97b..1b173e6e 100644
--- a/web/translation/translate.pt_BR.toml
+++ b/web/translation/translate.pt_BR.toml
@@ -374,6 +374,16 @@
"subJsonEnable" = "Ativar/Desativar o endpoint de assinatura JSON de forma independente."
"subTitle" = "Título da Assinatura"
"subTitleDesc" = "Título exibido no cliente VPN"
+"subSupportUrl" = "URL de Suporte"
+"subSupportUrlDesc" = "Link de suporte técnico exibido no cliente VPN"
+"subProfileUrl" = "URL de Perfil"
+"subProfileUrlDesc" = "Um link para o seu site exibido no cliente VPN"
+"subAnnounce" = "Anúncio"
+"subAnnounceDesc" = "O texto do anúncio exibido no cliente VPN"
+"subEnableRouting" = "Ativar roteamento"
+"subEnableRoutingDesc" = "Configuração global para habilitar o roteamento no cliente VPN. (Apenas para Happ)"
+"subRoutingRules" = "Regras de roteamento"
+"subRoutingRulesDesc" = "Regras de roteamento globais para o cliente VPN. (Apenas para Happ)"
"subListen" = "IP de Escuta"
"subListenDesc" = "O endereço IP para o serviço de assinatura. (deixe em branco para escutar em todos os IPs)"
"subPort" = "Porta de Escuta"
diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml
index 63fbbbb6..895734f5 100644
--- a/web/translation/translate.ru_RU.toml
+++ b/web/translation/translate.ru_RU.toml
@@ -373,7 +373,17 @@
"subEnableDesc" = "Функция подписки с отдельной конфигурацией"
"subJsonEnable" = "Включить/отключить JSON-эндпоинт подписки независимо."
"subTitle" = "Заголовок подписки"
-"subTitleDesc" = "Название подписки, которое видит клиент в VPN клиенте"
+"subTitleDesc" = "Название подписки, которое видит клиент в VPN-клиенте"
+"subSupportUrl" = "URL поддержки"
+"subSupportUrlDesc" = "Ссылка на техническую поддержку, отображаемая в VPN-клиенте"
+"subProfileUrl" = "URL профиля"
+"subProfileUrlDesc" = "Ссылка на ваш сайт, отображаемая в VPN-клиенте"
+"subAnnounce" = "Объявление"
+"subAnnounceDesc" = "Текст объявления, отображаемый в VPN-клиенте"
+"subEnableRouting" = "Включить маршрутизацию"
+"subEnableRoutingDesc" = "Глобальная настройка для включения маршрутизации в VPN-клиенте. (Только для Happ)"
+"subRoutingRules" = "Правила маршрутизации"
+"subRoutingRulesDesc" = "Глобальные правила маршрутизации для VPN-клиента. (Только для Happ)"
"subListen" = "Прослушивание IP"
"subListenDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса"
"subPort" = "Порт подписки"
diff --git a/web/translation/translate.tr_TR.toml b/web/translation/translate.tr_TR.toml
index 25b8de9b..50639358 100644
--- a/web/translation/translate.tr_TR.toml
+++ b/web/translation/translate.tr_TR.toml
@@ -374,6 +374,16 @@
"subJsonEnable" = "JSON abonelik uç noktasını bağımsız olarak Etkinleştir/Devre Dışı bırak."
"subTitle" = "Abonelik Başlığı"
"subTitleDesc" = "VPN istemcisinde gösterilen başlık"
+"subSupportUrl" = "Destek URL'si"
+"subSupportUrlDesc" = "VPN istemcisinde gösterilen teknik destek bağlantısı"
+"subProfileUrl" = "Profil URL'si"
+"subProfileUrlDesc" = "VPN istemcisinde görüntülenen web sitenize giden bağlantı"
+"subAnnounce" = "Duyuru"
+"subAnnounceDesc" = "VPN istemcisinde görüntülenen duyuru metni"
+"subEnableRouting" = "Yönlendirmeyi etkinleştir"
+"subEnableRoutingDesc" = "VPN istemcisinde yönlendirmeyi etkinleştirmek için genel ayar. (Yalnızca Happ için)"
+"subRoutingRules" = "Yönlendirme kuralları"
+"subRoutingRulesDesc" = "VPN istemcisi için genel yönlendirme kuralları. (Yalnızca Happ için)"
"subListen" = "Dinleme IP"
"subListenDesc" = "Abonelik hizmeti için IP adresi. (tüm IP'leri dinlemek için boş bırakın)"
"subPort" = "Dinleme Portu"
diff --git a/web/translation/translate.uk_UA.toml b/web/translation/translate.uk_UA.toml
index c32854ae..54d45889 100644
--- a/web/translation/translate.uk_UA.toml
+++ b/web/translation/translate.uk_UA.toml
@@ -374,6 +374,16 @@
"subJsonEnable" = "Увімкнути/вимкнути JSON-кінець підписки незалежно."
"subTitle" = "Назва Підписки"
"subTitleDesc" = "Назва, яка відображається у VPN-клієнті"
+"subSupportUrl" = "URL підтримки"
+"subSupportUrlDesc" = "Посилання на технічну підтримку, що відображається у VPN-клієнті"
+"subProfileUrl" = "URL профілю"
+"subProfileUrlDesc" = "Посилання на ваш вебсайт, що відображається у VPN-клієнті"
+"subAnnounce" = "Оголошення"
+"subAnnounceDesc" = "Текст оголошення, що відображається у VPN-клієнті"
+"subEnableRouting" = "Увімкнути маршрутизацію"
+"subEnableRoutingDesc" = "Глобальне налаштування для увімкнення маршрутизації у VPN-клієнті. (Тільки для Happ)"
+"subRoutingRules" = "Правила маршрутизації"
+"subRoutingRulesDesc" = "Глобальні правила маршрутизації для VPN-клієнта. (Тільки для Happ)"
"subListen" = "Слухати IP"
"subListenDesc" = "IP-адреса для служби підписки. (залиште порожнім, щоб слухати всі IP-адреси)"
"subPort" = "Слухати порт"
diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml
index 1187548e..3fa63bb1 100644
--- a/web/translation/translate.vi_VN.toml
+++ b/web/translation/translate.vi_VN.toml
@@ -374,6 +374,16 @@
"subJsonEnable" = "Bật/Tắt điểm cuối đăng ký JSON độc lập."
"subTitle" = "Tiêu đề Đăng ký"
"subTitleDesc" = "Tiêu đề hiển thị trong ứng dụng VPN"
+"subSupportUrl" = "URL Hỗ trợ"
+"subSupportUrlDesc" = "Liên kết hỗ trợ kỹ thuật hiển thị trong ứng dụng VPN"
+"subProfileUrl" = "URL Hồ sơ"
+"subProfileUrlDesc" = "Liên kết đến trang web của bạn hiển thị trong ứng dụng VPN"
+"subAnnounce" = "Thông báo"
+"subAnnounceDesc" = "Văn bản thông báo hiển thị trong ứng dụng VPN"
+"subEnableRouting" = "Bật định tuyến"
+"subEnableRoutingDesc" = "Cài đặt toàn cục để bật định tuyến trong ứng dụng khách VPN. (Chỉ dành cho Happ)"
+"subRoutingRules" = "Quy tắc định tuyến"
+"subRoutingRulesDesc" = "Quy tắc định tuyến toàn cầu cho client VPN. (Chỉ dành cho Happ)"
"subListen" = "Listening IP"
"subListenDesc" = "Mặc định để trống để nghe tất cả các IP"
"subPort" = "Cổng gói đăng ký"
diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml
index 923cc21b..d6b82b93 100644
--- a/web/translation/translate.zh_CN.toml
+++ b/web/translation/translate.zh_CN.toml
@@ -374,6 +374,16 @@
"subJsonEnable" = "单独启用/禁用 JSON 订阅端点。"
"subTitle" = "订阅标题"
"subTitleDesc" = "在VPN客户端中显示的标题"
+"subSupportUrl" = "支持链接"
+"subSupportUrlDesc" = "VPN 客户端中显示的技术支持链接"
+"subProfileUrl" = "个人资料链接"
+"subProfileUrlDesc" = "VPN 客户端中显示的网站链接"
+"subAnnounce" = "公告"
+"subAnnounceDesc" = "VPN 客户端中显示的公告文本"
+"subEnableRouting" = "启用路由"
+"subEnableRoutingDesc" = "在 VPN 客户端中启用路由的全局设置。(僅限 Happ)"
+"subRoutingRules" = "路由規則"
+"subRoutingRulesDesc" = "VPN 用戶端的全域路由規則。(僅限 Happ)"
"subListen" = "监听 IP"
"subListenDesc" = "订阅服务监听的 IP 地址(留空表示监听所有 IP)"
"subPort" = "监听端口"
diff --git a/web/translation/translate.zh_TW.toml b/web/translation/translate.zh_TW.toml
index df32d742..616f2322 100644
--- a/web/translation/translate.zh_TW.toml
+++ b/web/translation/translate.zh_TW.toml
@@ -374,6 +374,16 @@
"subJsonEnable" = "獨立啟用/停用 JSON 訂閱端點。"
"subTitle" = "訂閱標題"
"subTitleDesc" = "在VPN客戶端中顯示的標題"
+"subSupportUrl" = "支援連結"
+"subSupportUrlDesc" = "VPN 用戶端中顯示的技術支援連結"
+"subProfileUrl" = "個人資料連結"
+"subProfileUrlDesc" = "VPN 用戶端中顯示的網站連結"
+"subAnnounce" = "公告"
+"subAnnounceDesc" = "VPN 用戶端中顯示的公告文字"
+"subEnableRouting" = "啟用路由"
+"subEnableRoutingDesc" = "在 VPN 用戶端中啟用路由的全域設定。(僅限 Happ)"
+"subRoutingRules" = "路由規則"
+"subRoutingRulesDesc" = "VPN 用戶端的全域路由規則。(僅限 Happ)"
"subListen" = "監聽 IP"
"subListenDesc" = "訂閱服務監聽的 IP 地址(留空表示監聽所有 IP)"
"subPort" = "監聽埠"
From d5ea8d0f3832512f8430e3c54056a7db36482e6a Mon Sep 17 00:00:00 2001
From: Sam Mosleh <41725025+sam-mosleh@users.noreply.github.com>
Date: Fri, 30 Jan 2026 19:35:24 +0400
Subject: [PATCH 03/13] Fix default CA by enforcing it everywhere (#3719)
---
install.sh | 4 ++--
update.sh | 6 +++---
x-ui.sh | 6 +++---
3 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/install.sh b/install.sh
index e01cff99..852e128a 100644
--- a/install.sh
+++ b/install.sh
@@ -272,7 +272,7 @@ setup_ip_certificate() {
# Issue certificate with shortlived profile
echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}"
- ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt >/dev/null 2>&1
+ ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force >/dev/null 2>&1
~/.acme.sh/acme.sh --issue \
${domain_args} \
@@ -414,7 +414,7 @@ ssl_cert_issue() {
systemctl stop x-ui 2>/dev/null || rc-service x-ui stop 2>/dev/null
# issue the certificate
- ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
+ ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force
~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force
if [ $? -ne 0 ]; then
echo -e "${red}Issuing certificate failed, please check logs.${plain}"
diff --git a/update.sh b/update.sh
index 3781c365..0c4bb725 100755
--- a/update.sh
+++ b/update.sh
@@ -173,7 +173,7 @@ setup_ssl_certificate() {
echo -e "${green}Issuing SSL certificate for ${domain}...${plain}"
echo -e "${yellow}Note: Port 80 must be open and accessible from the internet${plain}"
- ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt >/dev/null 2>&1
+ ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force >/dev/null 2>&1
~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport 80 --force
if [ $? -ne 0 ]; then
@@ -297,7 +297,7 @@ setup_ip_certificate() {
# Issue certificate with shortlived profile
echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}"
- ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt >/dev/null 2>&1
+ ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force >/dev/null 2>&1
~/.acme.sh/acme.sh --issue \
${domain_args} \
@@ -437,7 +437,7 @@ ssl_cert_issue() {
systemctl stop x-ui 2>/dev/null || rc-service x-ui stop 2>/dev/null
# issue the certificate
- ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
+ ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force
~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force
if [ $? -ne 0 ]; then
echo -e "${red}Issuing certificate failed, please check logs.${plain}"
diff --git a/x-ui.sh b/x-ui.sh
index 42dbb601..22d02358 100644
--- a/x-ui.sh
+++ b/x-ui.sh
@@ -1226,7 +1226,7 @@ ssl_cert_issue_for_ip() {
local reloadCmd="systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null"
# issue the certificate for IP with shortlived profile
- ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
+ ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force
~/.acme.sh/acme.sh --issue \
${domain_args} \
--standalone \
@@ -1391,7 +1391,7 @@ ssl_cert_issue() {
LOGI "Will use port: ${WebPort} to issue certificates. Please make sure this port is open."
# issue the certificate
- ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
+ ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force
~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force
if [ $? -ne 0 ]; then
LOGE "Issuing certificate failed, please check logs."
@@ -1518,7 +1518,7 @@ ssl_cert_issue_CF() {
LOGD "Your registered email address is: ${CF_AccountEmail}"
# Set the default CA to Let's Encrypt
- ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
+ ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force
if [ $? -ne 0 ]; then
LOGE "Default CA, Let'sEncrypt fail, script exiting..."
exit 1
From ea0da32e810417d4b66d48edd34bac0ab74ee4a5 Mon Sep 17 00:00:00 2001
From: "Farhad H. P. Shirvan" <9374298+farhadh@users.noreply.github.com>
Date: Sat, 31 Jan 2026 19:50:08 +0100
Subject: [PATCH 04/13] fix: rename `verifyPeerCertInNames` to
`verifyPeerCertByName` to be compatible with xray-core v26.1.31 (#3723)
---
web/assets/js/model/inbound.js | 8 ++++----
web/html/form/tls_settings.html | 4 ++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js
index e5fe368f..58cc7421 100644
--- a/web/assets/js/model/inbound.js
+++ b/web/assets/js/model/inbound.js
@@ -554,7 +554,7 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion = TLS_VERSION_OPTION.TLS13,
cipherSuites = '',
rejectUnknownSni = false,
- verifyPeerCertInNames = ['dns.google', 'cloudflare-dns.com'],
+ verifyPeerCertByName = ['dns.google', 'cloudflare-dns.com'],
disableSystemRoot = false,
enableSessionResumption = false,
certificates = [new TlsStreamSettings.Cert()],
@@ -569,7 +569,7 @@ class TlsStreamSettings extends XrayCommonClass {
this.maxVersion = maxVersion;
this.cipherSuites = cipherSuites;
this.rejectUnknownSni = rejectUnknownSni;
- this.verifyPeerCertInNames = Array.isArray(verifyPeerCertInNames) ? verifyPeerCertInNames.join(",") : verifyPeerCertInNames;
+ this.verifyPeerCertByName = Array.isArray(verifyPeerCertByName) ? verifyPeerCertByName.join(",") : verifyPeerCertByName;
this.disableSystemRoot = disableSystemRoot;
this.enableSessionResumption = enableSessionResumption;
this.certs = certificates;
@@ -603,7 +603,7 @@ class TlsStreamSettings extends XrayCommonClass {
json.maxVersion,
json.cipherSuites,
json.rejectUnknownSni,
- json.verifyPeerCertInNames,
+ json.verifyPeerCertByName,
json.disableSystemRoot,
json.enableSessionResumption,
certs,
@@ -621,7 +621,7 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion: this.maxVersion,
cipherSuites: this.cipherSuites,
rejectUnknownSni: this.rejectUnknownSni,
- verifyPeerCertInNames: this.verifyPeerCertInNames.split(","),
+ verifyPeerCertByName: this.verifyPeerCertByName.split(","),
disableSystemRoot: this.disableSystemRoot,
enableSessionResumption: this.enableSessionResumption,
certificates: TlsStreamSettings.toJsonArray(this.certs),
diff --git a/web/html/form/tls_settings.html b/web/html/form/tls_settings.html
index 3723130e..c14c4831 100644
--- a/web/html/form/tls_settings.html
+++ b/web/html/form/tls_settings.html
@@ -57,8 +57,8 @@
-
-
+
+
From 6b3da4fe5ea072cdadd46f5d1ba8de514c45d206 Mon Sep 17 00:00:00 2001
From: lillinlin <126753730+lillinlin@users.noreply.github.com>
Date: Sun, 1 Feb 2026 06:50:29 +0800
Subject: [PATCH 05/13] Update reality_targets.js (#3724)
---
web/assets/js/model/reality_targets.js | 24 ++++++++++--------------
1 file changed, 10 insertions(+), 14 deletions(-)
diff --git a/web/assets/js/model/reality_targets.js b/web/assets/js/model/reality_targets.js
index cfe65afc..d17bcfc1 100644
--- a/web/assets/js/model/reality_targets.js
+++ b/web/assets/js/model/reality_targets.js
@@ -1,18 +1,15 @@
// List of popular services for VLESS Reality Target/SNI randomization
const REALITY_TARGETS = [
- { target: 'www.icloud.com:443', sni: 'www.icloud.com,icloud.com' },
- { target: 'www.apple.com:443', sni: 'www.apple.com,apple.com' },
- { target: 'www.tesla.com:443', sni: 'www.tesla.com,tesla.com' },
- { target: 'www.sony.com:443', sni: 'www.sony.com,sony.com' },
- { target: 'www.nvidia.com:443', sni: 'www.nvidia.com,nvidia.com' },
- { target: 'www.amd.com:443', sni: 'www.amd.com,amd.com' },
- { target: 'azure.microsoft.com:443', sni: 'azure.microsoft.com,www.azure.com' },
- { target: 'aws.amazon.com:443', sni: 'aws.amazon.com,amazon.com' },
- { target: 'www.bing.com:443', sni: 'www.bing.com,bing.com' },
- { target: 'www.oracle.com:443', sni: 'www.oracle.com,oracle.com' },
- { target: 'www.intel.com:443', sni: 'www.intel.com,intel.com' },
- { target: 'www.microsoft.com:443', sni: 'www.microsoft.com,microsoft.com' },
- { target: 'www.amazon.com:443', sni: 'www.amazon.com,amazon.com' }
+ { target: 'www.apple.com:443', sni: 'www.apple.com' },
+ { target: 'www.icloud.com:443', sni: 'www.icloud.com' },
+ { target: 'www.amazon.com:443', sni: 'www.amazon.com' },
+ { target: 'aws.amazon.com:443', sni: 'aws.amazon.com' },
+ { target: 'www.oracle.com:443', sni: 'www.oracle.com' },
+ { target: 'www.nvidia.com:443', sni: 'www.nvidia.com' },
+ { target: 'www.amd.com:443', sni: 'www.amd.com' },
+ { target: 'www.intel.com:443', sni: 'www.intel.com' },
+ { target: 'www.tesla.com:443', sni: 'www.tesla.com' },
+ { target: 'www.sony.com:443', sni: 'www.sony.com' }
];
/**
@@ -28,4 +25,3 @@ function getRandomRealityTarget() {
sni: selected.sni
};
}
-
From c59f54bb0ef9ef06ade11aa75fba61224830be7e Mon Sep 17 00:00:00 2001
From: MHSanaei
Date: Sun, 1 Feb 2026 01:56:23 +0100
Subject: [PATCH 06/13] outbound: finalmask
---
web/assets/js/model/inbound.js | 4 +-
web/assets/js/model/outbound.js | 93 ++++++++++++++++++++++++---------
web/html/form/outbound.html | 57 ++++++++++++++++----
3 files changed, 115 insertions(+), 39 deletions(-)
diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js
index 58cc7421..9077ea53 100644
--- a/web/assets/js/model/inbound.js
+++ b/web/assets/js/model/inbound.js
@@ -318,7 +318,7 @@ TcpStreamSettings.TcpResponse = class extends XrayCommonClass {
class KcpStreamSettings extends XrayCommonClass {
constructor(
- mtu = 1250,
+ mtu = 1350,
tti = 50,
uplinkCapacity = 5,
downlinkCapacity = 20,
@@ -2509,7 +2509,7 @@ Inbound.HttpSettings.HttpAccount = class extends XrayCommonClass {
Inbound.WireguardSettings = class extends XrayCommonClass {
constructor(
protocol,
- mtu = 1250,
+ mtu = 1420,
secretKey = Wireguard.generateKeypair().privateKey,
peers = [new Inbound.WireguardSettings.Peer()],
noKernelTun = false
diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js
index c6529560..3e5374e3 100644
--- a/web/assets/js/model/outbound.js
+++ b/web/assets/js/model/outbound.js
@@ -165,7 +165,7 @@ class TcpStreamSettings extends CommonClass {
class KcpStreamSettings extends CommonClass {
constructor(
- mtu = 1250,
+ mtu = 1350,
tti = 50,
uplinkCapacity = 5,
downlinkCapacity = 20,
@@ -558,27 +558,49 @@ class SockoptStreamSettings extends CommonClass {
}
}
-class UdpMask extends CommonClass {
- constructor(type = 'salamander', password = '') {
+class FinalMask extends CommonClass {
+ constructor(type = 'salamander', settings = {}) {
super();
this.type = type;
- this.password = password;
+ this.settings = this._getDefaultSettings(type, settings);
+ }
+
+ _getDefaultSettings(type, settings = {}) {
+ switch (type) {
+ case 'salamander':
+ case 'mkcp-aes128gcm':
+ return { password: settings.password || '' };
+ case 'header-dns':
+ case 'xdns':
+ return { domain: settings.domain || '' };
+ case 'mkcp-original':
+ case 'header-dtls':
+ case 'header-srtp':
+ case 'header-utp':
+ case 'header-wechat':
+ case 'header-wireguard':
+ return {}; // No settings needed
+ default:
+ return settings;
+ }
}
static fromJson(json = {}) {
- return new UdpMask(
- json.type,
- json.settings?.password || ''
+ return new FinalMask(
+ json.type || 'salamander',
+ json.settings || {}
);
}
toJson() {
- return {
- type: this.type,
- settings: {
- password: this.password
- }
+ const result = {
+ type: this.type
};
+ // Only include settings if they exist and are not empty
+ if (this.settings && Object.keys(this.settings).length > 0) {
+ result.settings = this.settings;
+ }
+ return result;
}
}
@@ -595,7 +617,7 @@ class StreamSettings extends CommonClass {
httpupgradeSettings = new HttpUpgradeStreamSettings(),
xhttpSettings = new xHTTPStreamSettings(),
hysteriaSettings = new HysteriaStreamSettings(),
- udpmasks = [],
+ finalmask = { udp: [] },
sockopt = undefined,
) {
super();
@@ -610,16 +632,21 @@ class StreamSettings extends CommonClass {
this.httpupgrade = httpupgradeSettings;
this.xhttp = xhttpSettings;
this.hysteria = hysteriaSettings;
- this.udpmasks = udpmasks;
+ this.finalmask = finalmask;
this.sockopt = sockopt;
}
- addUdpMask() {
- this.udpmasks.push(new UdpMask());
+ addUdpMask(type = 'salamander') {
+ if (!this.finalmask.udp) {
+ this.finalmask.udp = [];
+ }
+ this.finalmask.udp.push(new FinalMask(type));
}
delUdpMask(index) {
- this.udpmasks.splice(index, 1);
+ if (this.finalmask.udp) {
+ this.finalmask.udp.splice(index, 1);
+ }
}
get isTls() {
@@ -639,7 +666,16 @@ class StreamSettings extends CommonClass {
}
static fromJson(json = {}) {
- const udpmasks = json.udpmasks ? json.udpmasks.map(mask => UdpMask.fromJson(mask)) : [];
+ let finalmask = { udp: [] };
+ if (json.finalmask) {
+ if (Array.isArray(json.finalmask)) {
+ // Legacy format: direct array (backward compatibility)
+ finalmask.udp = json.finalmask.map(mask => FinalMask.fromJson(mask));
+ } else if (json.finalmask.udp) {
+ // New format: object with udp array
+ finalmask.udp = json.finalmask.udp.map(mask => FinalMask.fromJson(mask));
+ }
+ }
return new StreamSettings(
json.network,
json.security,
@@ -652,7 +688,7 @@ class StreamSettings extends CommonClass {
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
xHTTPStreamSettings.fromJson(json.xhttpSettings),
HysteriaStreamSettings.fromJson(json.hysteriaSettings),
- udpmasks,
+ finalmask,
SockoptStreamSettings.fromJson(json.sockopt),
);
}
@@ -671,7 +707,9 @@ class StreamSettings extends CommonClass {
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
xhttpSettings: network === 'xhttp' ? this.xhttp.toJson() : undefined,
hysteriaSettings: network === 'hysteria' ? this.hysteria.toJson() : undefined,
- udpmasks: this.udpmasks.length > 0 ? this.udpmasks.map(mask => mask.toJson()) : undefined,
+ finalmask: (this.finalmask.udp && this.finalmask.udp.length > 0) ? {
+ udp: this.finalmask.udp.map(mask => mask.toJson())
+ } : undefined,
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
};
}
@@ -1285,11 +1323,14 @@ Outbound.VLESSSettings = class extends CommonClass {
flow: this.flow,
encryption: this.encryption,
};
- if (this.testpre > 0) {
- result.testpre = this.testpre;
- }
- if (this.testseed && this.testseed.length >= 4) {
- result.testseed = this.testseed;
+ // Only include Vision settings when flow is set
+ if (this.flow && this.flow !== '') {
+ if (this.testpre > 0) {
+ result.testpre = this.testpre;
+ }
+ if (this.testseed && this.testseed.length >= 4) {
+ result.testseed = this.testseed;
+ }
}
return result;
}
@@ -1422,7 +1463,7 @@ Outbound.HttpSettings = class extends CommonClass {
Outbound.WireguardSettings = class extends CommonClass {
constructor(
- mtu = 1250,
+ mtu = 1420,
secretKey = '',
address = [''],
workers = 2,
diff --git a/web/html/form/outbound.html b/web/html/form/outbound.html
index 688a3564..074137f7 100644
--- a/web/html/form/outbound.html
+++ b/web/html/form/outbound.html
@@ -546,8 +546,9 @@
-
- BBR (Auto)
+
+ BBR (Auto)
Brutal
@@ -602,25 +603,59 @@
-
+
-
+
-
-
+
UDP Mask [[ index + 1 ]]
- outbound.stream.delUdpMask(index)"
+ outbound.stream.delUdpMask(index)"
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }">
-
- Salamander
+ mask.settings = mask._getDefaultSettings(type, {})"
+ :dropdown-class-name="themeSwitcher.currentTheme">
+
+ Salamander (Hysteria2)
+
+ mKCP AES-128-GCM
+
+ Header DNS
+
+ Header DTLS 1.2
+
+ Header SRTP
+
+ Header uTP
+
+ Header WeChat Video
+
+ Header WireGuard
+
+ mKCP Original
+
+ xDNS (Experimental)
-
-
+
+
+
+
+
+
+
From 3af6497577b7da4149a6432ea83ae4f229da6e45 Mon Sep 17 00:00:00 2001
From: MHSanaei
Date: Sun, 1 Feb 2026 02:34:23 +0100
Subject: [PATCH 07/13] inbound : finalmask
---
web/assets/js/model/inbound.js | 92 ++++++++++++++++++----
web/assets/js/model/outbound.js | 16 +---
web/html/form/outbound.html | 51 ++++++------
web/html/form/stream/stream_finalmask.html | 70 ++++++++++++++++
web/html/form/stream/stream_kcp.html | 46 ++++-------
web/html/form/stream/stream_settings.html | 12 ++-
6 files changed, 201 insertions(+), 86 deletions(-)
create mode 100644 web/html/form/stream/stream_finalmask.html
diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js
index 9077ea53..6d65a11c 100644
--- a/web/assets/js/model/inbound.js
+++ b/web/assets/js/model/inbound.js
@@ -319,14 +319,12 @@ TcpStreamSettings.TcpResponse = class extends XrayCommonClass {
class KcpStreamSettings extends XrayCommonClass {
constructor(
mtu = 1350,
- tti = 50,
+ tti = 20,
uplinkCapacity = 5,
downlinkCapacity = 20,
congestion = false,
- readBufferSize = 2,
- writeBufferSize = 2,
- type = 'none',
- seed = RandomUtil.randomSeq(10),
+ readBufferSize = 1,
+ writeBufferSize = 1,
) {
super();
this.mtu = mtu;
@@ -336,8 +334,6 @@ class KcpStreamSettings extends XrayCommonClass {
this.congestion = congestion;
this.readBuffer = readBufferSize;
this.writeBuffer = writeBufferSize;
- this.type = type;
- this.seed = seed;
}
static fromJson(json = {}) {
@@ -349,8 +345,6 @@ class KcpStreamSettings extends XrayCommonClass {
json.congestion,
json.readBufferSize,
json.writeBufferSize,
- ObjectUtil.isEmpty(json.header) ? 'none' : json.header.type,
- json.seed,
);
}
@@ -363,10 +357,6 @@ class KcpStreamSettings extends XrayCommonClass {
congestion: this.congestion,
readBufferSize: this.readBuffer,
writeBufferSize: this.writeBuffer,
- header: {
- type: this.type,
- },
- seed: this.seed,
};
}
}
@@ -929,6 +919,51 @@ class SockoptStreamSettings extends XrayCommonClass {
}
}
+class FinalMask extends XrayCommonClass {
+ constructor(type = 'salamander', settings = {}) {
+ super();
+ this.type = type;
+ this.settings = this._getDefaultSettings(type, settings);
+ }
+
+ _getDefaultSettings(type, settings = {}) {
+ switch (type) {
+ case 'salamander':
+ case 'mkcp-aes128gcm':
+ return { password: settings.password || '' };
+ case 'header-dns':
+ case 'xdns':
+ return { domain: settings.domain || '' };
+ case 'mkcp-original':
+ case 'header-dtls':
+ case 'header-srtp':
+ case 'header-utp':
+ case 'header-wechat':
+ case 'header-wireguard':
+ return {};
+ default:
+ return settings;
+ }
+ }
+
+ static fromJson(json = {}) {
+ return new FinalMask(
+ json.type || 'salamander',
+ json.settings || {}
+ );
+ }
+
+ toJson() {
+ const result = {
+ type: this.type
+ };
+ if (this.settings && Object.keys(this.settings).length > 0) {
+ result.settings = this.settings;
+ }
+ return result;
+ }
+}
+
class StreamSettings extends XrayCommonClass {
constructor(network = 'tcp',
security = 'none',
@@ -941,6 +976,7 @@ class StreamSettings extends XrayCommonClass {
grpcSettings = new GrpcStreamSettings(),
httpupgradeSettings = new HTTPUpgradeStreamSettings(),
xhttpSettings = new xHTTPStreamSettings(),
+ finalmask = { udp: [] },
sockopt = undefined,
) {
super();
@@ -955,9 +991,23 @@ class StreamSettings extends XrayCommonClass {
this.grpc = grpcSettings;
this.httpupgrade = httpupgradeSettings;
this.xhttp = xhttpSettings;
+ this.finalmask = finalmask;
this.sockopt = sockopt;
}
+ addUdpMask(type = 'salamander') {
+ if (!this.finalmask.udp) {
+ this.finalmask.udp = [];
+ }
+ this.finalmask.udp.push(new FinalMask(type));
+ }
+
+ delUdpMask(index) {
+ if (this.finalmask.udp) {
+ this.finalmask.udp.splice(index, 1);
+ }
+ }
+
get isTls() {
return this.security === "tls";
}
@@ -992,6 +1042,14 @@ class StreamSettings extends XrayCommonClass {
}
static fromJson(json = {}) {
+ let finalmask = { udp: [] };
+ if (json.finalmask) {
+ if (Array.isArray(json.finalmask)) {
+ finalmask.udp = json.finalmask.map(mask => FinalMask.fromJson(mask));
+ } else if (json.finalmask.udp) {
+ finalmask.udp = json.finalmask.udp.map(mask => FinalMask.fromJson(mask));
+ }
+ }
return new StreamSettings(
json.network,
json.security,
@@ -1004,6 +1062,7 @@ class StreamSettings extends XrayCommonClass {
GrpcStreamSettings.fromJson(json.grpcSettings),
HTTPUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
xHTTPStreamSettings.fromJson(json.xhttpSettings),
+ finalmask,
SockoptStreamSettings.fromJson(json.sockopt),
);
}
@@ -1022,6 +1081,9 @@ class StreamSettings extends XrayCommonClass {
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
xhttpSettings: network === 'xhttp' ? this.xhttp.toJson() : undefined,
+ finalmask: (this.finalmask.udp && this.finalmask.udp.length > 0) ? {
+ udp: this.finalmask.udp.map(mask => mask.toJson())
+ } : undefined,
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
};
}
@@ -1947,7 +2009,9 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
json.selectedAuth = this.selectedAuth;
}
- if (this.testseed && this.testseed.length >= 4) {
+ // Only include testseed if at least one client has a flow set
+ const hasFlow = this.vlesses && this.vlesses.some(vless => vless.flow && vless.flow !== '');
+ if (hasFlow && this.testseed && this.testseed.length >= 4) {
json.testseed = this.testseed;
}
diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js
index 3e5374e3..bac886d3 100644
--- a/web/assets/js/model/outbound.js
+++ b/web/assets/js/model/outbound.js
@@ -166,14 +166,12 @@ class TcpStreamSettings extends CommonClass {
class KcpStreamSettings extends CommonClass {
constructor(
mtu = 1350,
- tti = 50,
+ tti = 20,
uplinkCapacity = 5,
downlinkCapacity = 20,
congestion = false,
- readBufferSize = 2,
- writeBufferSize = 2,
- type = 'none',
- seed = '',
+ readBufferSize = 1,
+ writeBufferSize = 1,
) {
super();
this.mtu = mtu;
@@ -183,8 +181,6 @@ class KcpStreamSettings extends CommonClass {
this.congestion = congestion;
this.readBuffer = readBufferSize;
this.writeBuffer = writeBufferSize;
- this.type = type;
- this.seed = seed;
}
static fromJson(json = {}) {
@@ -196,8 +192,6 @@ class KcpStreamSettings extends CommonClass {
json.congestion,
json.readBufferSize,
json.writeBufferSize,
- ObjectUtil.isEmpty(json.header) ? 'none' : json.header.type,
- json.seed,
);
}
@@ -210,10 +204,6 @@ class KcpStreamSettings extends CommonClass {
congestion: this.congestion,
readBufferSize: this.readBuffer,
writeBufferSize: this.writeBuffer,
- header: {
- type: this.type,
- },
- seed: this.seed,
};
}
}
diff --git a/web/html/form/outbound.html b/web/html/form/outbound.html
index 074137f7..9ca0eab6 100644
--- a/web/html/form/outbound.html
+++ b/web/html/form/outbound.html
@@ -407,21 +407,6 @@
-
-
- None
- SRTP
- uTP
- WeChat
- DTLS 1.2
- WireGuard
- DNS
-
-
-
-
-
@@ -607,7 +592,7 @@
+ @click="outbound.stream.addUdpMask(outbound.protocol === Protocols.Hysteria ? 'salamander' : (outbound.stream.network === 'kcp' ? 'mkcp-aes128gcm' : 'xdns'))">
@@ -623,25 +608,39 @@
mask.settings = mask._getDefaultSettings(type, {})"
:dropdown-class-name="themeSwitcher.currentTheme">
-
+
+
Salamander (Hysteria2)
-
+
+
mKCP AES-128-GCM
-
+
Header DNS
-
+
Header DTLS 1.2
-
+
Header SRTP
-
+
Header uTP
-
+
Header WeChat Video
-
+
Header WireGuard
-
+
mKCP Original
-
+
+
xDNS (Experimental)
diff --git a/web/html/form/stream/stream_finalmask.html b/web/html/form/stream/stream_finalmask.html
new file mode 100644
index 00000000..4ed7d6a1
--- /dev/null
+++ b/web/html/form/stream/stream_finalmask.html
@@ -0,0 +1,70 @@
+{{define "form/streamFinalMask"}}
+
+
+
+
+
+
+
+ UDP Mask [[ index + 1 ]]
+ inbound.stream.delUdpMask(index)"
+ :style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }">
+
+
+ mask.settings = mask._getDefaultSettings(type, {})"
+ :dropdown-class-name="themeSwitcher.currentTheme">
+
+
+ mKCP AES-128-GCM
+
+ Header DNS
+
+ Header DTLS 1.2
+
+ Header SRTP
+
+ Header uTP
+
+ Header WeChat Video
+
+ Header WireGuard
+
+ mKCP Original
+
+
+ xDNS (Experimental)
+
+
+
+
+
+
+
+
+
+
+
+
+
+{{end}}
diff --git a/web/html/form/stream/stream_kcp.html b/web/html/form/stream/stream_kcp.html
index 50794574..11f89ebd 100644
--- a/web/html/form/stream/stream_kcp.html
+++ b/web/html/form/stream/stream_kcp.html
@@ -1,48 +1,32 @@
{{define "form/streamKCP"}}
-
-
-
- None
- SRTP
- uTP
- WeChat
- DTLS 1.2
- WireGuard
- DNS
-
-
-
-
-
-
- {{ i18n "reset" }}
-
- {{ i18n "password" }}
-
-
-
-
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
{{end}}
diff --git a/web/html/form/stream/stream_settings.html b/web/html/form/stream/stream_settings.html
index f6b17cc6..5b00ef25 100644
--- a/web/html/form/stream/stream_settings.html
+++ b/web/html/form/stream/stream_settings.html
@@ -1,8 +1,10 @@
{{define "form/streamSettings"}}
-
+
-
TCP (RAW)
mKCP
@@ -48,4 +50,10 @@
{{template "form/streamSockopt"}}
+
+
+
+ {{template "form/streamFinalMask"}}
+
{{end}}
From a973fa6d6886b7f6af14d7848b31964c46ee151d Mon Sep 17 00:00:00 2001
From: MHSanaei
Date: Sun, 1 Feb 2026 02:58:18 +0100
Subject: [PATCH 08/13] XHTTP transport: New options for bypassing CDN's
detection
https://github.com/XTLS/Xray-core/pull/5414
---
web/assets/js/model/inbound.js | 52 ++++++++++
web/html/form/stream/stream_xhttp.html | 125 ++++++++++++++++++++++---
2 files changed, 165 insertions(+), 12 deletions(-)
diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js
index 6d65a11c..47f32f5c 100644
--- a/web/assets/js/model/inbound.js
+++ b/web/assets/js/model/inbound.js
@@ -487,6 +487,19 @@ class xHTTPStreamSettings extends XrayCommonClass {
noSSEHeader = false,
xPaddingBytes = "100-1000",
mode = MODE_OPTION.AUTO,
+ xPaddingObfsMode = false,
+ xPaddingKey = '',
+ xPaddingHeader = '',
+ xPaddingPlacement = '',
+ xPaddingMethod = '',
+ uplinkHTTPMethod = '',
+ sessionPlacement = '',
+ sessionKey = '',
+ seqPlacement = '',
+ seqKey = '',
+ uplinkDataPlacement = '',
+ uplinkDataKey = '',
+ uplinkChunkSize = 0,
) {
super();
this.path = path;
@@ -498,6 +511,19 @@ class xHTTPStreamSettings extends XrayCommonClass {
this.noSSEHeader = noSSEHeader;
this.xPaddingBytes = xPaddingBytes;
this.mode = mode;
+ this.xPaddingObfsMode = xPaddingObfsMode;
+ this.xPaddingKey = xPaddingKey;
+ this.xPaddingHeader = xPaddingHeader;
+ this.xPaddingPlacement = xPaddingPlacement;
+ this.xPaddingMethod = xPaddingMethod;
+ this.uplinkHTTPMethod = uplinkHTTPMethod;
+ this.sessionPlacement = sessionPlacement;
+ this.sessionKey = sessionKey;
+ this.seqPlacement = seqPlacement;
+ this.seqKey = seqKey;
+ this.uplinkDataPlacement = uplinkDataPlacement;
+ this.uplinkDataKey = uplinkDataKey;
+ this.uplinkChunkSize = uplinkChunkSize;
}
addHeader(name, value) {
@@ -519,6 +545,19 @@ class xHTTPStreamSettings extends XrayCommonClass {
json.noSSEHeader,
json.xPaddingBytes,
json.mode,
+ json.xPaddingObfsMode,
+ json.xPaddingKey,
+ json.xPaddingHeader,
+ json.xPaddingPlacement,
+ json.xPaddingMethod,
+ json.uplinkHTTPMethod,
+ json.sessionPlacement,
+ json.sessionKey,
+ json.seqPlacement,
+ json.seqKey,
+ json.uplinkDataPlacement,
+ json.uplinkDataKey,
+ json.uplinkChunkSize,
);
}
@@ -533,6 +572,19 @@ class xHTTPStreamSettings extends XrayCommonClass {
noSSEHeader: this.noSSEHeader,
xPaddingBytes: this.xPaddingBytes,
mode: this.mode,
+ xPaddingObfsMode: this.xPaddingObfsMode,
+ xPaddingKey: this.xPaddingKey,
+ xPaddingHeader: this.xPaddingHeader,
+ xPaddingPlacement: this.xPaddingPlacement,
+ xPaddingMethod: this.xPaddingMethod,
+ uplinkHTTPMethod: this.uplinkHTTPMethod,
+ sessionPlacement: this.sessionPlacement,
+ sessionKey: this.sessionKey,
+ seqPlacement: this.seqPlacement,
+ seqKey: this.seqKey,
+ uplinkDataPlacement: this.uplinkDataPlacement,
+ uplinkDataKey: this.uplinkDataKey,
+ uplinkChunkSize: this.uplinkChunkSize,
};
}
}
diff --git a/web/html/form/stream/stream_xhttp.html b/web/html/form/stream/stream_xhttp.html
index 4b3052b6..447612c9 100644
--- a/web/html/form/stream/stream_xhttp.html
+++ b/web/html/form/stream/stream_xhttp.html
@@ -1,5 +1,6 @@
{{define "form/streamXHTTP"}}
-
+
@@ -7,38 +8,138 @@
-
+
-
+
- [[ index+1 ]]
+ [[ index+1
+ ]]
-
+
- [[ key ]]
+ [[ key
+ ]]
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Default (queryInHeader)
+ queryInHeader
+ header
+
+
+
+
+ Default (repeat-x)
+ repeat-x
+ tokenish
+
+
+
+
+
+ Default (POST)
+ POST
+ PUT
+ GET (packet-up only)
+
+
+
+
+ Default (path)
+ path
+ header
+ cookie
+ query
+
+
+
+
+
+
+
+ Default (path)
+ path
+ header
+ cookie
+ query
+
+
+
+
+
+
+
+ Default (body)
+ body
+ header
+ query
+
+
+
+
+
+
+
+
From 9d603c5ad20503c11e6b79aa8e0203d45e835358 Mon Sep 17 00:00:00 2001
From: MHSanaei
Date: Sun, 1 Feb 2026 03:12:54 +0100
Subject: [PATCH 09/13] Add pinnedPeerCertSha256 support to TLS settings
Introduces the pinnedPeerCertSha256 field to TlsStreamSettings in the JS model and adds a corresponding input in the TLS settings form. This allows users to specify SHA256 fingerprints for peer certificate pinning, enhancing security configuration options.
---
web/assets/js/model/inbound.js | 4 ++
web/html/form/tls_settings.html | 89 ++++++++++++++++++++++-----------
2 files changed, 64 insertions(+), 29 deletions(-)
diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js
index 47f32f5c..1e3c0357 100644
--- a/web/assets/js/model/inbound.js
+++ b/web/assets/js/model/inbound.js
@@ -597,6 +597,7 @@ class TlsStreamSettings extends XrayCommonClass {
cipherSuites = '',
rejectUnknownSni = false,
verifyPeerCertByName = ['dns.google', 'cloudflare-dns.com'],
+ pinnedPeerCertSha256 = [],
disableSystemRoot = false,
enableSessionResumption = false,
certificates = [new TlsStreamSettings.Cert()],
@@ -612,6 +613,7 @@ class TlsStreamSettings extends XrayCommonClass {
this.cipherSuites = cipherSuites;
this.rejectUnknownSni = rejectUnknownSni;
this.verifyPeerCertByName = Array.isArray(verifyPeerCertByName) ? verifyPeerCertByName.join(",") : verifyPeerCertByName;
+ this.pinnedPeerCertSha256 = pinnedPeerCertSha256;
this.disableSystemRoot = disableSystemRoot;
this.enableSessionResumption = enableSessionResumption;
this.certs = certificates;
@@ -646,6 +648,7 @@ class TlsStreamSettings extends XrayCommonClass {
json.cipherSuites,
json.rejectUnknownSni,
json.verifyPeerCertByName,
+ json.pinnedPeerCertSha256 || [],
json.disableSystemRoot,
json.enableSessionResumption,
certs,
@@ -664,6 +667,7 @@ class TlsStreamSettings extends XrayCommonClass {
cipherSuites: this.cipherSuites,
rejectUnknownSni: this.rejectUnknownSni,
verifyPeerCertByName: this.verifyPeerCertByName.split(","),
+ pinnedPeerCertSha256: this.pinnedPeerCertSha256.length > 0 ? this.pinnedPeerCertSha256 : undefined,
disableSystemRoot: this.disableSystemRoot,
enableSessionResumption: this.enableSessionResumption,
certificates: TlsStreamSettings.toJsonArray(this.certs),
diff --git a/web/html/form/tls_settings.html b/web/html/form/tls_settings.html
index c14c4831..24b994f6 100644
--- a/web/html/form/tls_settings.html
+++ b/web/html/form/tls_settings.html
@@ -1,11 +1,13 @@
{{define "form/tlsSettings"}}
-
+
{{ i18n "none" }}
- Reality
+ Reality
TLS
@@ -16,33 +18,44 @@
-
- Auto
- [[ value ]]
+
+ Auto
+ [[
+ value ]]
-
- [[ key ]]
+ [[ key
+ ]]
-
- [[ key ]]
+ [[ key
+ ]]
-
- None
- [[ key ]]
+ None
+ [[ key
+ ]]
-
- [[ alpn ]]
+
+ [[ alpn
+ ]]
@@ -60,18 +73,31 @@
+
+
+
+
-
- {{ i18n "pages.inbounds.certificatePath" }}
- {{ i18n "pages.inbounds.certificateContent" }}
+
+ {{
+ i18n "pages.inbounds.certificatePath" }}
+ {{
+ i18n "pages.inbounds.certificateContent" }}
-
-
+
@@ -83,7 +109,8 @@
-
+
{{ i18n "pages.inbounds.setDefaultCert" }}
@@ -99,8 +126,10 @@
-
- [[ key ]]
+
+ [[ key
+ ]]
@@ -108,20 +137,22 @@
-
+
-
+
-
- [[ key ]]
-
+
+ [[
+ key ]]
+
- Get New ECH Cert
+ Get New
+ ECH Cert
Clear
From aa6a886977dc25e7f1e75cc5ba2b22c91b31e711 Mon Sep 17 00:00:00 2001
From: MHSanaei
Date: Sun, 1 Feb 2026 03:20:29 +0100
Subject: [PATCH 10/13] Add UDP hop interval min/max support for Hysteria
Replaces single UDP hop interval with separate min and max values in Hysteria stream settings. Updates model, JSON serialization, URL param parsing, and form fields for backward compatibility and enhanced configuration flexibility.
---
web/assets/js/model/outbound.js | 34 ++++++++++++++++++++++++++-------
web/html/form/outbound.html | 10 ++++++++--
2 files changed, 35 insertions(+), 9 deletions(-)
diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js
index bac886d3..21d6c393 100644
--- a/web/assets/js/model/outbound.js
+++ b/web/assets/js/model/outbound.js
@@ -424,7 +424,8 @@ class HysteriaStreamSettings extends CommonClass {
up = '0',
down = '0',
udphopPort = '',
- udphopInterval = 30,
+ udphopIntervalMin = 30,
+ udphopIntervalMax = 30,
initStreamReceiveWindow = 8388608,
maxStreamReceiveWindow = 8388608,
initConnectionReceiveWindow = 20971520,
@@ -440,7 +441,8 @@ class HysteriaStreamSettings extends CommonClass {
this.up = up;
this.down = down;
this.udphopPort = udphopPort;
- this.udphopInterval = udphopInterval;
+ this.udphopIntervalMin = udphopIntervalMin;
+ this.udphopIntervalMax = udphopIntervalMax;
this.initStreamReceiveWindow = initStreamReceiveWindow;
this.maxStreamReceiveWindow = maxStreamReceiveWindow;
this.initConnectionReceiveWindow = initConnectionReceiveWindow;
@@ -452,10 +454,18 @@ class HysteriaStreamSettings extends CommonClass {
static fromJson(json = {}) {
let udphopPort = '';
- let udphopInterval = 30;
+ let udphopIntervalMin = 30;
+ let udphopIntervalMax = 30;
if (json.udphop) {
udphopPort = json.udphop.port || '';
- udphopInterval = json.udphop.interval || 30;
+ // Backward compatibility: if old 'interval' exists, use it for both min/max
+ if (json.udphop.interval !== undefined) {
+ udphopIntervalMin = json.udphop.interval;
+ udphopIntervalMax = json.udphop.interval;
+ } else {
+ udphopIntervalMin = json.udphop.intervalMin || 30;
+ udphopIntervalMax = json.udphop.intervalMax || 30;
+ }
}
return new HysteriaStreamSettings(
json.version,
@@ -464,7 +474,8 @@ class HysteriaStreamSettings extends CommonClass {
json.up,
json.down,
udphopPort,
- udphopInterval,
+ udphopIntervalMin,
+ udphopIntervalMax,
json.initStreamReceiveWindow,
json.maxStreamReceiveWindow,
json.initConnectionReceiveWindow,
@@ -493,7 +504,8 @@ class HysteriaStreamSettings extends CommonClass {
if (this.udphopPort) {
result.udphop = {
port: this.udphopPort,
- interval: this.udphopInterval
+ intervalMin: this.udphopIntervalMin,
+ intervalMax: this.udphopIntervalMax
};
}
return result;
@@ -1024,7 +1036,15 @@ class Outbound extends CommonClass {
stream.hysteria.up = urlParams.get('up') ?? '0';
stream.hysteria.down = urlParams.get('down') ?? '0';
stream.hysteria.udphopPort = urlParams.get('udphopPort') ?? '';
- stream.hysteria.udphopInterval = parseInt(urlParams.get('udphopInterval') ?? '30');
+ // Support both old single interval and new min/max range
+ if (urlParams.has('udphopInterval')) {
+ const interval = parseInt(urlParams.get('udphopInterval'));
+ stream.hysteria.udphopIntervalMin = interval;
+ stream.hysteria.udphopIntervalMax = interval;
+ } else {
+ stream.hysteria.udphopIntervalMin = parseInt(urlParams.get('udphopIntervalMin') ?? '30');
+ stream.hysteria.udphopIntervalMax = parseInt(urlParams.get('udphopIntervalMax') ?? '30');
+ }
// Optional QUIC parameters
if (urlParams.has('initStreamReceiveWindow')) {
diff --git a/web/html/form/outbound.html b/web/html/form/outbound.html
index 9ca0eab6..ce917b21 100644
--- a/web/html/form/outbound.html
+++ b/web/html/form/outbound.html
@@ -549,10 +549,16 @@
-
+
+
+
From e35213bc739b14e67b8270913655153cf0ef74f3 Mon Sep 17 00:00:00 2001
From: MHSanaei
Date: Sun, 1 Feb 2026 03:30:09 +0100
Subject: [PATCH 11/13] Update Xray-core to v26.1.31 and related dependencies
Bump Xray-core version to v26.1.31 in build scripts and server logic. Update Go dependencies including gopsutil, bytedance/sonic, circl, miekg/dns, go-proxyproto, sagernet/sing, and others to their latest versions. Adjust version check in GetXrayVersions to require at least v26.1.31.
---
.github/workflows/release.yml | 4 +--
DockerInit.sh | 2 +-
go.mod | 24 +++++++----------
go.sum | 51 ++++++++++++++---------------------
web/service/server.go | 2 +-
5 files changed, 34 insertions(+), 49 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a744b9f3..bf332fb3 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -89,7 +89,7 @@ jobs:
cd x-ui/bin
# Download dependencies
- Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.1.18/"
+ Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.1.31/"
if [ "${{ matrix.platform }}" == "amd64" ]; then
wget -q ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip
@@ -187,7 +187,7 @@ jobs:
cd x-ui\bin
# Download Xray for Windows
- $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.1.18/"
+ $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.1.31/"
Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip"
Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath .
Remove-Item "Xray-windows-64.zip"
diff --git a/DockerInit.sh b/DockerInit.sh
index 176e1261..d8acad77 100755
--- a/DockerInit.sh
+++ b/DockerInit.sh
@@ -27,7 +27,7 @@ case $1 in
esac
mkdir -p build/bin
cd build/bin
-curl -sfLRO "https://github.com/XTLS/Xray-core/releases/download/v26.1.18/Xray-linux-${ARCH}.zip"
+curl -sfLRO "https://github.com/XTLS/Xray-core/releases/download/v26.1.31/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
mv xray "xray-linux-${FNAME}"
diff --git a/go.mod b/go.mod
index 7d602b11..4f031534 100644
--- a/go.mod
+++ b/go.mod
@@ -16,11 +16,11 @@ require (
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.2.4
github.com/robfig/cron/v3 v3.0.1
- github.com/shirou/gopsutil/v4 v4.25.12
+ github.com/shirou/gopsutil/v4 v4.26.1
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/valyala/fasthttp v1.69.0
github.com/xlzd/gotp v0.1.0
- github.com/xtls/xray-core v1.260118.0
+ github.com/xtls/xray-core v1.260131.0
go.uber.org/atomic v1.11.0
golang.org/x/crypto v0.47.0
golang.org/x/sys v0.40.0
@@ -35,11 +35,10 @@ require (
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
- github.com/bytedance/sonic v1.14.2 // indirect
- github.com/bytedance/sonic/loader v0.4.0 // indirect
- github.com/cloudflare/circl v1.6.2 // indirect
+ github.com/bytedance/sonic v1.15.0 // indirect
+ github.com/bytedance/sonic/loader v0.5.0 // indirect
+ github.com/cloudflare/circl v1.6.3 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
- github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
@@ -64,24 +63,21 @@ require (
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.33 // indirect
- github.com/miekg/dns v1.1.70 // indirect
+ github.com/miekg/dns v1.1.72 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
- github.com/pires/go-proxyproto v0.8.1 // indirect
+ github.com/pires/go-proxyproto v0.9.2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/refraction-networking/utls v1.8.2 // indirect
- github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
- github.com/sagernet/sing v0.7.14 // indirect
+ github.com/sagernet/sing v0.7.18 // indirect
github.com/sagernet/sing-shadowsocks v0.2.9 // indirect
- github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
- github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fastjson v1.6.7 // indirect
github.com/vishvananda/netlink v1.3.1 // indirect
@@ -98,8 +94,8 @@ require (
golang.org/x/tools v0.41.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
google.golang.org/protobuf v1.36.11 // indirect
- gvisor.dev/gvisor v0.0.0-20260109181451-4be7c433dae2 // indirect
+ gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect
lukechampine.com/blake3 v1.4.1 // indirect
)
diff --git a/go.sum b/go.sum
index e711c95d..59e6c7f3 100644
--- a/go.sum
+++ b/go.sum
@@ -10,20 +10,17 @@ github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 h1:bSq8n+gX4oO/
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178/go.mod h1:N1WIjPphkqs4efXWuyDNQ6OjjIK04vM3h+bEgwV+eVU=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
-github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
-github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
-github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
-github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
-github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ=
-github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
+github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
+github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
+github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
+github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
+github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
+github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
-github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mTEIGbvhcYU3S8+uSNkuMjx/qZFfhtM=
-github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
@@ -124,8 +121,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
-github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
-github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
+github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
+github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -141,8 +138,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
-github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
-github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
+github.com/pires/go-proxyproto v0.9.2 h1:H1UdHn695zUVVmB0lQ354lOWHOy6TZSpzBl3tgN0s1U=
+github.com/pires/go-proxyproto v0.9.2/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
@@ -153,20 +150,16 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
-github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
-github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
-github.com/sagernet/sing v0.7.14 h1:5QQRDCUvYNOMyVp3LuK/hYEBAIv0VsbD3x/l9zH467s=
-github.com/sagernet/sing v0.7.14/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
+github.com/sagernet/sing v0.7.18 h1:iZHkaru1/MoHugx3G+9S3WG4owMewKO/KvieE2Pzk4E=
+github.com/sagernet/sing v0.7.18/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4zOYYlIxMkM=
github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8=
-github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
-github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
-github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
-github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
+github.com/shirou/gopsutil/v4 v4.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo=
+github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -174,7 +167,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
@@ -189,8 +181,6 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
-github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
-github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
@@ -205,8 +195,8 @@ github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 h1:UXjrmniKlY+ZbIqpN91lejB3pszQQQRVu1vqH/p/aGM=
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ=
-github.com/xtls/xray-core v1.260118.0 h1:RJtgIbQ3ykFRcH1CKeoCgQ5WvhsMFu+lnvLF/fFHagE=
-github.com/xtls/xray-core v1.260118.0/go.mod h1:A5k7TXE2KfAjT8dAq6Ir4mMP1q0OTh+8VMmUdqWMQpg=
+github.com/xtls/xray-core v1.260131.0 h1:gPBykLhUvRZ8sfubNerkwWqV3c15UtmSYQG2cgKqrV4=
+github.com/xtls/xray-core v1.260131.0/go.mod h1:cxzYFZrxu1B1NtPjHsqv4UzgDvRA71mV4rXYH4KtO7Q=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
@@ -263,8 +253,8 @@ golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+Z
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 h1:C4WAdL+FbjnGlpp2S+HMVhBeCq2Lcib4xZqfPNF6OoQ=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
@@ -275,14 +265,13 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
-gvisor.dev/gvisor v0.0.0-20260109181451-4be7c433dae2 h1:fr6L00yGG2RP5NMea6njWpdC+bm+cMdFClrSpaicp1c=
-gvisor.dev/gvisor v0.0.0-20260109181451-4be7c433dae2/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
+gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h1:Lk6hARj5UPY47dBep70OD/TIMwikJ5fGUGX0Rm3Xigk=
+gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
diff --git a/web/service/server.go b/web/service/server.go
index ead3e2f0..ce9374c8 100644
--- a/web/service/server.go
+++ b/web/service/server.go
@@ -567,7 +567,7 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
continue
}
- if major > 26 || (major == 26 && minor > 1) || (major == 26 && minor == 1 && patch >= 18) {
+ if major > 26 || (major == 26 && minor > 1) || (major == 26 && minor == 1 && patch >= 31) {
versions = append(versions, release.TagName)
}
}
From 06c49b92f8ca29601b77b3198bc23f02c52fc9b9 Mon Sep 17 00:00:00 2001
From: MHSanaei
Date: Sun, 1 Feb 2026 03:31:16 +0100
Subject: [PATCH 12/13] v2.8.9
---
config/version | 2 +-
web/assets/js/model/inbound.js | 8 ++++----
web/html/form/tls_settings.html | 4 ++--
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/config/version b/config/version
index c3e2bd4c..0d367c86 100644
--- a/config/version
+++ b/config/version
@@ -1 +1 @@
-2.8.8
\ No newline at end of file
+2.8.9
\ No newline at end of file
diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js
index 1e3c0357..eb2b0f96 100644
--- a/web/assets/js/model/inbound.js
+++ b/web/assets/js/model/inbound.js
@@ -596,7 +596,7 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion = TLS_VERSION_OPTION.TLS13,
cipherSuites = '',
rejectUnknownSni = false,
- verifyPeerCertByName = ['dns.google', 'cloudflare-dns.com'],
+ verifyPeerCertByNames = ['dns.google', 'cloudflare-dns.com'],
pinnedPeerCertSha256 = [],
disableSystemRoot = false,
enableSessionResumption = false,
@@ -612,7 +612,7 @@ class TlsStreamSettings extends XrayCommonClass {
this.maxVersion = maxVersion;
this.cipherSuites = cipherSuites;
this.rejectUnknownSni = rejectUnknownSni;
- this.verifyPeerCertByName = Array.isArray(verifyPeerCertByName) ? verifyPeerCertByName.join(",") : verifyPeerCertByName;
+ this.verifyPeerCertByNames = Array.isArray(verifyPeerCertByNames) ? verifyPeerCertByNames.join(",") : verifyPeerCertByNames;
this.pinnedPeerCertSha256 = pinnedPeerCertSha256;
this.disableSystemRoot = disableSystemRoot;
this.enableSessionResumption = enableSessionResumption;
@@ -647,7 +647,7 @@ class TlsStreamSettings extends XrayCommonClass {
json.maxVersion,
json.cipherSuites,
json.rejectUnknownSni,
- json.verifyPeerCertByName,
+ json.verifyPeerCertByNames,
json.pinnedPeerCertSha256 || [],
json.disableSystemRoot,
json.enableSessionResumption,
@@ -666,7 +666,7 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion: this.maxVersion,
cipherSuites: this.cipherSuites,
rejectUnknownSni: this.rejectUnknownSni,
- verifyPeerCertByName: this.verifyPeerCertByName.split(","),
+ verifyPeerCertByNames: this.verifyPeerCertByNames.split(","),
pinnedPeerCertSha256: this.pinnedPeerCertSha256.length > 0 ? this.pinnedPeerCertSha256 : undefined,
disableSystemRoot: this.disableSystemRoot,
enableSessionResumption: this.enableSessionResumption,
diff --git a/web/html/form/tls_settings.html b/web/html/form/tls_settings.html
index 24b994f6..7b5d53b8 100644
--- a/web/html/form/tls_settings.html
+++ b/web/html/form/tls_settings.html
@@ -70,8 +70,8 @@
-
-
+
+
Date: Sun, 1 Feb 2026 14:03:46 +0100
Subject: [PATCH 13/13] Refactor TLS peer cert verification settings
Removed verifyPeerCertByNames and pinnedPeerCertSha256 from inbound TLS settings and UI. Added verifyPeerCertByName and pinnedPeerCertSha256 to outbound TLS settings and updated the outbound form to support these fields. This change streamlines and clarifies certificate verification configuration between inbound and outbound settings.
---
web/assets/js/model/inbound.js | 8 --------
web/assets/js/model/outbound.js | 10 +++++++++-
web/html/form/outbound.html | 9 +++++++++
web/html/form/tls_settings.html | 9 ---------
4 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js
index eb2b0f96..3f3f8831 100644
--- a/web/assets/js/model/inbound.js
+++ b/web/assets/js/model/inbound.js
@@ -596,8 +596,6 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion = TLS_VERSION_OPTION.TLS13,
cipherSuites = '',
rejectUnknownSni = false,
- verifyPeerCertByNames = ['dns.google', 'cloudflare-dns.com'],
- pinnedPeerCertSha256 = [],
disableSystemRoot = false,
enableSessionResumption = false,
certificates = [new TlsStreamSettings.Cert()],
@@ -612,8 +610,6 @@ class TlsStreamSettings extends XrayCommonClass {
this.maxVersion = maxVersion;
this.cipherSuites = cipherSuites;
this.rejectUnknownSni = rejectUnknownSni;
- this.verifyPeerCertByNames = Array.isArray(verifyPeerCertByNames) ? verifyPeerCertByNames.join(",") : verifyPeerCertByNames;
- this.pinnedPeerCertSha256 = pinnedPeerCertSha256;
this.disableSystemRoot = disableSystemRoot;
this.enableSessionResumption = enableSessionResumption;
this.certs = certificates;
@@ -647,8 +643,6 @@ class TlsStreamSettings extends XrayCommonClass {
json.maxVersion,
json.cipherSuites,
json.rejectUnknownSni,
- json.verifyPeerCertByNames,
- json.pinnedPeerCertSha256 || [],
json.disableSystemRoot,
json.enableSessionResumption,
certs,
@@ -666,8 +660,6 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion: this.maxVersion,
cipherSuites: this.cipherSuites,
rejectUnknownSni: this.rejectUnknownSni,
- verifyPeerCertByNames: this.verifyPeerCertByNames.split(","),
- pinnedPeerCertSha256: this.pinnedPeerCertSha256.length > 0 ? this.pinnedPeerCertSha256 : undefined,
disableSystemRoot: this.disableSystemRoot,
enableSessionResumption: this.enableSessionResumption,
certificates: TlsStreamSettings.toJsonArray(this.certs),
diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js
index 21d6c393..3e0dd0d4 100644
--- a/web/assets/js/model/outbound.js
+++ b/web/assets/js/model/outbound.js
@@ -347,6 +347,8 @@ class TlsStreamSettings extends CommonClass {
fingerprint = '',
allowInsecure = false,
echConfigList = '',
+ verifyPeerCertByName = 'cloudflare-dns.com',
+ pinnedPeerCertSha256 = '',
) {
super();
this.serverName = serverName;
@@ -354,6 +356,8 @@ class TlsStreamSettings extends CommonClass {
this.fingerprint = fingerprint;
this.allowInsecure = allowInsecure;
this.echConfigList = echConfigList;
+ this.verifyPeerCertByName = verifyPeerCertByName;
+ this.pinnedPeerCertSha256 = pinnedPeerCertSha256;
}
static fromJson(json = {}) {
@@ -363,6 +367,8 @@ class TlsStreamSettings extends CommonClass {
json.fingerprint,
json.allowInsecure,
json.echConfigList,
+ json.verifyPeerCertByName,
+ json.pinnedPeerCertSha256,
);
}
@@ -372,7 +378,9 @@ class TlsStreamSettings extends CommonClass {
alpn: this.alpn,
fingerprint: this.fingerprint,
allowInsecure: this.allowInsecure,
- echConfigList: this.echConfigList
+ echConfigList: this.echConfigList,
+ verifyPeerCertByName: this.verifyPeerCertByName,
+ pinnedPeerCertSha256: this.pinnedPeerCertSha256
};
}
}
diff --git a/web/html/form/outbound.html b/web/html/form/outbound.html
index ce917b21..4df095d4 100644
--- a/web/html/form/outbound.html
+++ b/web/html/form/outbound.html
@@ -703,6 +703,15 @@
+
+
+
+
+
+
+
diff --git a/web/html/form/tls_settings.html b/web/html/form/tls_settings.html
index 7b5d53b8..b2368d4f 100644
--- a/web/html/form/tls_settings.html
+++ b/web/html/form/tls_settings.html
@@ -70,15 +70,6 @@
-
-
-
-
-
-
-