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/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/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/install.sh b/install.sh
index 62c8ea37..852e128a 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
@@ -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/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/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/web/assets/js/model/inbound.js b/web/assets/js/model/inbound.js
index e5fe368f..3f3f8831 100644
--- a/web/assets/js/model/inbound.js
+++ b/web/assets/js/model/inbound.js
@@ -318,15 +318,13 @@ TcpStreamSettings.TcpResponse = class extends XrayCommonClass {
class KcpStreamSettings extends XrayCommonClass {
constructor(
- mtu = 1250,
- tti = 50,
+ mtu = 1350,
+ 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,
};
}
}
@@ -497,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;
@@ -508,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) {
@@ -529,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,
);
}
@@ -543,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,
};
}
}
@@ -554,7 +596,6 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion = TLS_VERSION_OPTION.TLS13,
cipherSuites = '',
rejectUnknownSni = false,
- verifyPeerCertInNames = ['dns.google', 'cloudflare-dns.com'],
disableSystemRoot = false,
enableSessionResumption = false,
certificates = [new TlsStreamSettings.Cert()],
@@ -569,7 +610,6 @@ class TlsStreamSettings extends XrayCommonClass {
this.maxVersion = maxVersion;
this.cipherSuites = cipherSuites;
this.rejectUnknownSni = rejectUnknownSni;
- this.verifyPeerCertInNames = Array.isArray(verifyPeerCertInNames) ? verifyPeerCertInNames.join(",") : verifyPeerCertInNames;
this.disableSystemRoot = disableSystemRoot;
this.enableSessionResumption = enableSessionResumption;
this.certs = certificates;
@@ -603,7 +643,6 @@ class TlsStreamSettings extends XrayCommonClass {
json.maxVersion,
json.cipherSuites,
json.rejectUnknownSni,
- json.verifyPeerCertInNames,
json.disableSystemRoot,
json.enableSessionResumption,
certs,
@@ -621,7 +660,6 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion: this.maxVersion,
cipherSuites: this.cipherSuites,
rejectUnknownSni: this.rejectUnknownSni,
- verifyPeerCertInNames: this.verifyPeerCertInNames.split(","),
disableSystemRoot: this.disableSystemRoot,
enableSessionResumption: this.enableSessionResumption,
certificates: TlsStreamSettings.toJsonArray(this.certs),
@@ -929,6 +967,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 +1024,7 @@ class StreamSettings extends XrayCommonClass {
grpcSettings = new GrpcStreamSettings(),
httpupgradeSettings = new HTTPUpgradeStreamSettings(),
xhttpSettings = new xHTTPStreamSettings(),
+ finalmask = { udp: [] },
sockopt = undefined,
) {
super();
@@ -955,9 +1039,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 +1090,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 +1110,7 @@ class StreamSettings extends XrayCommonClass {
GrpcStreamSettings.fromJson(json.grpcSettings),
HTTPUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
xHTTPStreamSettings.fromJson(json.xhttpSettings),
+ finalmask,
SockoptStreamSettings.fromJson(json.sockopt),
);
}
@@ -1022,6 +1129,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 +2057,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;
}
@@ -2509,7 +2621,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..3e0dd0d4 100644
--- a/web/assets/js/model/outbound.js
+++ b/web/assets/js/model/outbound.js
@@ -165,15 +165,13 @@ class TcpStreamSettings extends CommonClass {
class KcpStreamSettings extends CommonClass {
constructor(
- mtu = 1250,
- tti = 50,
+ mtu = 1350,
+ 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,
};
}
}
@@ -357,6 +347,8 @@ class TlsStreamSettings extends CommonClass {
fingerprint = '',
allowInsecure = false,
echConfigList = '',
+ verifyPeerCertByName = 'cloudflare-dns.com',
+ pinnedPeerCertSha256 = '',
) {
super();
this.serverName = serverName;
@@ -364,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 = {}) {
@@ -373,6 +367,8 @@ class TlsStreamSettings extends CommonClass {
json.fingerprint,
json.allowInsecure,
json.echConfigList,
+ json.verifyPeerCertByName,
+ json.pinnedPeerCertSha256,
);
}
@@ -382,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
};
}
}
@@ -434,7 +432,8 @@ class HysteriaStreamSettings extends CommonClass {
up = '0',
down = '0',
udphopPort = '',
- udphopInterval = 30,
+ udphopIntervalMin = 30,
+ udphopIntervalMax = 30,
initStreamReceiveWindow = 8388608,
maxStreamReceiveWindow = 8388608,
initConnectionReceiveWindow = 20971520,
@@ -450,7 +449,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;
@@ -462,10 +462,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,
@@ -474,7 +482,8 @@ class HysteriaStreamSettings extends CommonClass {
json.up,
json.down,
udphopPort,
- udphopInterval,
+ udphopIntervalMin,
+ udphopIntervalMax,
json.initStreamReceiveWindow,
json.maxStreamReceiveWindow,
json.initConnectionReceiveWindow,
@@ -503,7 +512,8 @@ class HysteriaStreamSettings extends CommonClass {
if (this.udphopPort) {
result.udphop = {
port: this.udphopPort,
- interval: this.udphopInterval
+ intervalMin: this.udphopIntervalMin,
+ intervalMax: this.udphopIntervalMax
};
}
return result;
@@ -558,27 +568,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 +627,7 @@ class StreamSettings extends CommonClass {
httpupgradeSettings = new HttpUpgradeStreamSettings(),
xhttpSettings = new xHTTPStreamSettings(),
hysteriaSettings = new HysteriaStreamSettings(),
- udpmasks = [],
+ finalmask = { udp: [] },
sockopt = undefined,
) {
super();
@@ -610,16 +642,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 +676,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 +698,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 +717,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,
};
}
@@ -996,7 +1044,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')) {
@@ -1285,11 +1341,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 +1481,7 @@ Outbound.HttpSettings = class extends CommonClass {
Outbound.WireguardSettings = class extends CommonClass {
constructor(
- mtu = 1250,
+ mtu = 1420,
secretKey = '',
address = [''],
workers = 2,
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
};
}
-
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/form/outbound.html b/web/html/form/outbound.html
index 688a3564..4df095d4 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
-
-
-
-
-
@@ -546,8 +531,9 @@
-
- BBR (Auto)
+
+ BBR (Auto)
Brutal
@@ -563,10 +549,16 @@
-
+
+
+
@@ -602,25 +594,73 @@
-
+
-
+
-
-
+
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)
-
-
+
+
+
+
+
+
+
@@ -663,6 +703,15 @@
+
+
+
+
+
+
+
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}}
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
+
+
+
+
+
+
+
+
diff --git a/web/html/form/tls_settings.html b/web/html/form/tls_settings.html
index 3723130e..b2368d4f 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
+ ]]
@@ -57,21 +70,25 @@
-
-
-
-
- {{ i18n "pages.inbounds.certificatePath" }}
- {{ i18n "pages.inbounds.certificateContent" }}
+
+ {{
+ i18n "pages.inbounds.certificatePath" }}
+ {{
+ i18n "pages.inbounds.certificateContent" }}
-
-
+
@@ -83,7 +100,8 @@
-
+
{{ i18n "pages.inbounds.setDefaultCert" }}
@@ -99,8 +117,10 @@
-
- [[ key ]]
+
+ [[ key
+ ]]
@@ -108,20 +128,22 @@
-
+
-
+
-
- [[ key ]]
-
+
+ [[
+ key ]]
+
- Get New ECH Cert
+ Get New
+ ECH Cert
Clear
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/server.go b/web/service/server.go
index fae1c66a..793f541b 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)
}
}
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" = "監聽埠"
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