Compare commits

...

5 commits

Author SHA1 Message Date
MrTeeett
c3fc2851ac
Merge 0e006653f2 into 169b216d7e 2026-04-02 03:03:24 +08:00
Yunheng Liu
169b216d7e
perf: replace /dev/urandom | tr with openssl rand to fix CPU spike (#3887)
Some checks failed
Release 3X-UI / Analyze Go code (push) Has been cancelled
Release 3X-UI / build (386) (push) Has been cancelled
Release 3X-UI / build (amd64) (push) Has been cancelled
Release 3X-UI / build (arm64) (push) Has been cancelled
Release 3X-UI / build (armv5) (push) Has been cancelled
Release 3X-UI / build (armv6) (push) Has been cancelled
Release 3X-UI / build (armv7) (push) Has been cancelled
Release 3X-UI / build (s390x) (push) Has been cancelled
Release 3X-UI / Build for Windows (push) Has been cancelled
2026-04-01 13:59:48 +02:00
MHSanaei
7e6d80efa5
Bump Go and dependency versions
Update go toolchain to 1.26.1 and upgrade multiple direct and indirect modules (examples: github.com/gin-contrib/gzip v1.2.6, github.com/gin-contrib/sessions v1.1.0, github.com/go-ldap/ldap/v3 v3.4.13, github.com/goccy/go-json v0.10.6, github.com/pelletier/go-toml/v2 v2.3.0, github.com/shirou/gopsutil/v4 v4.26.3, github.com/xtls/xray-core v1.260327.0, golang.org/x/crypto v0.49.0, google.golang.org/grpc v1.80.0). go.sum updated accordingly to lock the new versions. Routine dependency refresh to pull in fixes and improvements.
2026-04-01 13:47:27 +02:00
Sanaei
0e006653f2
Merge branch 'main' into feature/subscription-host-overrides 2026-03-04 19:04:21 +01:00
Daniil Osincev
cb83e57b2a Add configurable host overrides for exported client links 2026-02-16 20:11:55 +03:00
33 changed files with 436 additions and 226 deletions

View file

@ -117,6 +117,7 @@ type Client struct {
Enable bool `json:"enable" form:"enable"` // Whether the client is enabled Enable bool `json:"enable" form:"enable"` // Whether the client is enabled
TgID int64 `json:"tgId" form:"tgId"` // Telegram user ID for notifications TgID int64 `json:"tgId" form:"tgId"` // Telegram user ID for notifications
SubID string `json:"subId" form:"subId"` // Subscription identifier SubID string `json:"subId" form:"subId"` // Subscription identifier
SubHost string `json:"subHost,omitempty"` // Optional host/IP override for exported client links
Comment string `json:"comment" form:"comment"` // Client comment Comment string `json:"comment" form:"comment"` // Client comment
Reset int `json:"reset" form:"reset"` // Reset period in days Reset int `json:"reset" form:"reset"` // Reset period in days
CreatedAt int64 `json:"created_at,omitempty"` // Creation timestamp CreatedAt int64 `json:"created_at,omitempty"` // Creation timestamp

64
go.mod
View file

@ -1,52 +1,52 @@
module github.com/mhsanaei/3x-ui/v2 module github.com/mhsanaei/3x-ui/v2
go 1.26.0 go 1.26.1
require ( require (
github.com/gin-contrib/gzip v1.2.5 github.com/gin-contrib/gzip v1.2.6
github.com/gin-contrib/sessions v1.0.4 github.com/gin-contrib/sessions v1.1.0
github.com/gin-gonic/gin v1.12.0 github.com/gin-gonic/gin v1.12.0
github.com/go-ldap/ldap/v3 v3.4.12 github.com/go-ldap/ldap/v3 v3.4.13
github.com/goccy/go-json v0.10.5 github.com/goccy/go-json v0.10.6
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/mymmrac/telego v1.7.0 github.com/mymmrac/telego v1.7.0
github.com/nicksnyder/go-i18n/v2 v2.6.1 github.com/nicksnyder/go-i18n/v2 v2.6.1
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.2.4 github.com/pelletier/go-toml/v2 v2.3.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v4 v4.26.2 github.com/shirou/gopsutil/v4 v4.26.3
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/valyala/fasthttp v1.69.0 github.com/valyala/fasthttp v1.69.0
github.com/xlzd/gotp v0.1.0 github.com/xlzd/gotp v0.1.0
github.com/xtls/xray-core v1.260206.0 github.com/xtls/xray-core v1.260327.0
go.uber.org/atomic v1.11.0 go.uber.org/atomic v1.11.0
golang.org/x/crypto v0.48.0 golang.org/x/crypto v0.49.0
golang.org/x/sys v0.41.0 golang.org/x/sys v0.42.0
golang.org/x/text v0.34.0 golang.org/x/text v0.35.0
google.golang.org/grpc v1.79.1 google.golang.org/grpc v1.80.0
gorm.io/driver/sqlite v1.6.0 gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.1 gorm.io/gorm v1.31.1
) )
require ( require (
github.com/Azure/go-ntlmssp v0.1.0 // indirect github.com/Azure/go-ntlmssp v0.1.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect github.com/andybalholm/brotli v1.2.1 // indirect
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 // indirect github.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/gopkg v0.1.4 // indirect
github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/bytedance/sonic/loader v0.5.1 // indirect
github.com/cloudflare/circl v1.6.3 // indirect github.com/cloudflare/circl v1.6.3 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect github.com/cloudwego/base64x v0.1.6 // indirect
github.com/ebitengine/purego v0.10.0 // indirect github.com/ebitengine/purego v0.10.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-contrib/sse v1.1.1 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/go-playground/validator/v10 v10.30.2 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect github.com/goccy/go-yaml v1.19.2 // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
github.com/gorilla/context v1.1.2 // indirect github.com/gorilla/context v1.1.2 // indirect
@ -57,12 +57,12 @@ require (
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/juju/ratelimit v1.0.2 // indirect github.com/juju/ratelimit v1.0.2 // indirect
github.com/klauspost/compress v1.18.4 // indirect github.com/klauspost/compress v1.18.5 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.34 // indirect github.com/mattn/go-sqlite3 v1.14.38 // indirect
github.com/miekg/dns v1.1.72 // indirect github.com/miekg/dns v1.1.72 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
@ -70,9 +70,9 @@ require (
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // 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/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect
github.com/refraction-networking/utls v1.8.2 // indirect github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagernet/sing v0.8.1 // indirect github.com/sagernet/sing v0.8.4 // indirect
github.com/sagernet/sing-shadowsocks v0.2.9 // indirect github.com/sagernet/sing-shadowsocks v0.2.9 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect github.com/tklauser/numcpus v0.11.0 // indirect
@ -82,20 +82,20 @@ require (
github.com/valyala/fastjson v1.6.10 // indirect github.com/valyala/fastjson v1.6.10 // indirect
github.com/vishvananda/netlink v1.3.1 // indirect github.com/vishvananda/netlink v1.3.1 // indirect
github.com/vishvananda/netns v0.0.5 // indirect github.com/vishvananda/netns v0.0.5 // indirect
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 // indirect github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.24.0 // indirect golang.org/x/arch v0.25.0 // indirect
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect
golang.org/x/mod v0.33.0 // indirect golang.org/x/mod v0.34.0 // indirect
golang.org/x/net v0.51.0 // indirect golang.org/x/net v0.52.0 // indirect
golang.org/x/sync v0.19.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/time v0.14.0 // indirect golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.42.0 // indirect golang.org/x/tools v0.43.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.11 // indirect
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect
lukechampine.com/blake3 v1.4.1 // indirect lukechampine.com/blake3 v1.4.1 // indirect

128
go.sum
View file

@ -4,16 +4,16 @@ github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 h1:bSq8n+gX4oO/qnM3MKf4kroW75n+phO9Qp6nigJKZ1E= github.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22 h1:00ziBGnLWQEcR9LThDwvxOznJJquJ9bYUdmBFnawLMU=
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178/go.mod h1:N1WIjPphkqs4efXWuyDNQ6OjjIK04vM3h+bEgwV+eVU= github.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= 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 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.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
@ -29,18 +29,18 @@ github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI= github.com/gin-contrib/gzip v1.2.6 h1:OtN8DplD5DNZCSLAnQ5HxRkD2qZ5VU+JhOrcfJrcRvg=
github.com/gin-contrib/gzip v1.2.5/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw= github.com/gin-contrib/gzip v1.2.6/go.mod h1:BQy8/+JApnRjAVUplSGZiVtD2k8GmIE2e9rYu/hLzzU=
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U= github.com/gin-contrib/sessions v1.1.0 h1:00mhHfNEGF5sP2fwxa98aRqj1FOJdL6IkR86n2hOiBo=
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs= github.com/gin-contrib/sessions v1.1.0/go.mod h1:TyYZDIs6qCQg2SOoYPgMT9pAkmZceVNEJMcv5qbIy60=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4= github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ=
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo= github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@ -54,10 +54,10 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
@ -107,8 +107,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI= github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@ -117,12 +117,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM= github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak=
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= github.com/mattn/go-sqlite3 v1.14.38 h1:tDUzL85kMvOrvpCt8P64SbGgVFtJB11GPi2AdmITgb4=
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.38/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= 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/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-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -138,8 +138,8 @@ github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0C
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 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.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pelletier/go-toml/v2 v2.3.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4= github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -150,18 +150,18 @@ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= 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.3-0.20260301010127-aa6edf4b11af h1:er2acxbi3N1nvEq6HXHUAR1nTWEJmQfqiGR8EVT9rfs=
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= 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/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 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sagernet/sing v0.8.1 h1:Li+zg4xdiMsvdX4j50TPqmSG8LF/TB9US2qlAN40izU= github.com/sagernet/sing v0.8.4 h1:Fj+jlY3F8vhcRfz/G/P3Dwcs5wqnmyNPT7u1RVVmjFI=
github.com/sagernet/sing v0.8.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.8.4/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 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4zOYYlIxMkM=
github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8= github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8=
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI= github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc=
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= 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/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= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -195,10 +195,10 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po= github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= 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-20260322125925-9234c772ba8f h1:iy2JRioxmUpoJ3SzbFPyTxHZMbR/rSHP7dOOgYaq1O8=
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ= github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
github.com/xtls/xray-core v1.260206.0 h1:gY8IV6u76CW93txL9QmacgZ0Udxr2Q3e9qUxXAhdHqI= github.com/xtls/xray-core v1.260327.0 h1:g4TzxMwyPrxslZh6uD+FiG3lXKTrnNO+b4ky2OhogHE=
github.com/xtls/xray-core v1.260206.0/go.mod h1:GyFIgVGRJkt3eyV/NMcdxOKXcJPqGGpyupHzy16uJhU= github.com/xtls/xray-core v1.260327.0/go.mod h1:OXMlhBloFry8mw0KwWLWLd3RQyXJzEYsCGlgsX36h60=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
@ -225,42 +225,42 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y= golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE=
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= 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.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View file

@ -76,37 +76,38 @@ is_port_in_use() {
install_base() { install_base() {
case "${release}" in case "${release}" in
ubuntu | debian | armbian) ubuntu | debian | armbian)
apt-get update && apt-get install -y -q cron curl tar tzdata socat ca-certificates apt-get update && apt-get install -y -q cron curl tar tzdata socat ca-certificates openssl
;; ;;
fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol) fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
dnf -y update && dnf install -y -q curl tar tzdata socat ca-certificates dnf -y update && dnf install -y -q curl tar tzdata socat ca-certificates openssl
;; ;;
centos) centos)
if [[ "${VERSION_ID}" =~ ^7 ]]; then if [[ "${VERSION_ID}" =~ ^7 ]]; then
yum -y update && yum install -y curl tar tzdata socat ca-certificates yum -y update && yum install -y curl tar tzdata socat ca-certificates openssl
else else
dnf -y update && dnf install -y -q curl tar tzdata socat ca-certificates dnf -y update && dnf install -y -q curl tar tzdata socat ca-certificates openssl
fi fi
;; ;;
arch | manjaro | parch) arch | manjaro | parch)
pacman -Syu && pacman -Syu --noconfirm curl tar tzdata socat ca-certificates pacman -Syu && pacman -Syu --noconfirm curl tar tzdata socat ca-certificates openssl
;; ;;
opensuse-tumbleweed | opensuse-leap) opensuse-tumbleweed | opensuse-leap)
zypper refresh && zypper -q install -y curl tar timezone socat ca-certificates zypper refresh && zypper -q install -y curl tar timezone socat ca-certificates openssl
;; ;;
alpine) alpine)
apk update && apk add curl tar tzdata socat ca-certificates apk update && apk add curl tar tzdata socat ca-certificates openssl
;; ;;
*) *)
apt-get update && apt-get install -y -q curl tar tzdata socat ca-certificates apt-get update && apt-get install -y -q curl tar tzdata socat ca-certificates openssl
;; ;;
esac esac
} }
gen_random_string() { gen_random_string() {
local length="$1" local length="$1"
local random_string=$(LC_ALL=C tr -dc 'a-zA-Z0-9' </dev/urandom | fold -w "$length" | head -n 1) openssl rand -base64 $(( length * 2 )) \
echo "$random_string" | tr -dc 'a-zA-Z0-9' \
| head -c "$length"
} }
install_acme() { install_acme() {

View file

@ -76,6 +76,8 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
if err != nil || len(inbounds) == 0 { if err != nil || len(inbounds) == 0 {
return "", "", err return "", "", err
} }
requestHost := strings.TrimSpace(host)
defaultClientHost := s.SubService.getDefaultClientHost()
var header string var header string
var traffic xray.ClientTraffic var traffic xray.ClientTraffic
@ -103,7 +105,8 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
for _, client := range clients { for _, client := range clients {
if client.Enable && client.SubID == subId { if client.Enable && client.SubID == subId {
clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email)) clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
newConfigs := s.getConfig(inbound, client, host) clientHost := s.SubService.ResolveClientHostWithDefault(inbound, client, requestHost, defaultClientHost)
newConfigs := s.getConfig(inbound, client, clientHost)
configArray = append(configArray, newConfigs...) configArray = append(configArray, newConfigs...)
} }
} }

View file

@ -22,10 +22,8 @@ import (
// SubService provides business logic for generating subscription links and managing subscription data. // SubService provides business logic for generating subscription links and managing subscription data.
type SubService struct { type SubService struct {
address string
showInfo bool showInfo bool
remarkModel string remarkModel string
datepicker string
inboundService service.InboundService inboundService service.InboundService
settingService service.SettingService settingService service.SettingService
} }
@ -40,7 +38,8 @@ func NewSubService(showInfo bool, remarkModel string) *SubService {
// GetSubs retrieves subscription links for a given subscription ID and host. // GetSubs retrieves subscription links for a given subscription ID and host.
func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.ClientTraffic, error) { func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.ClientTraffic, error) {
s.address = host requestHost := strings.TrimSpace(host)
defaultClientHost := s.getDefaultClientHost()
var result []string var result []string
var traffic xray.ClientTraffic var traffic xray.ClientTraffic
var lastOnline int64 var lastOnline int64
@ -54,10 +53,6 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
return nil, 0, traffic, common.NewError("No inbounds found with ", subId) return nil, 0, traffic, common.NewError("No inbounds found with ", subId)
} }
s.datepicker, err = s.settingService.GetDatepicker()
if err != nil {
s.datepicker = "gregorian"
}
for _, inbound := range inbounds { for _, inbound := range inbounds {
clients, err := s.inboundService.GetClients(inbound) clients, err := s.inboundService.GetClients(inbound)
if err != nil { if err != nil {
@ -76,7 +71,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
} }
for _, client := range clients { for _, client := range clients {
if client.Enable && client.SubID == subId { if client.Enable && client.SubID == subId {
link := s.getLink(inbound, client.Email) link := s.getLink(inbound, client.Email, requestHost, defaultClientHost)
result = append(result, link) result = append(result, link)
ct := s.getClientTraffics(inbound.ClientStats, client.Email) ct := s.getClientTraffics(inbound.ClientStats, client.Email)
clientTraffics = append(clientTraffics, ct) clientTraffics = append(clientTraffics, ct)
@ -161,33 +156,87 @@ func (s *SubService) getFallbackMaster(dest string, streamSettings string) (stri
return inbound.Listen, inbound.Port, string(modifiedStream), nil return inbound.Listen, inbound.Port, string(modifiedStream), nil
} }
func (s *SubService) getLink(inbound *model.Inbound, email string) string { func (s *SubService) getDefaultClientHost() string {
defaultHost, err := s.settingService.GetSubDefaultHost()
if err != nil {
return ""
}
return strings.TrimSpace(defaultHost)
}
func isWildcardListen(listen string) bool {
switch strings.ToLower(strings.TrimSpace(listen)) {
case "", "0.0.0.0", "::", "::0":
return true
default:
return false
}
}
func (s *SubService) getInboundSubHost(inbound *model.Inbound) string {
var settings map[string]any
if err := json.Unmarshal([]byte(inbound.Settings), &settings); err != nil {
return ""
}
subHost, _ := settings["subHost"].(string)
return strings.TrimSpace(subHost)
}
func (s *SubService) resolveAddress(inbound *model.Inbound, client model.Client, requestHost string, defaultClientHost string) string {
if host := strings.TrimSpace(client.SubHost); host != "" {
return host
}
if host := s.getInboundSubHost(inbound); host != "" {
return host
}
if host := strings.TrimSpace(defaultClientHost); host != "" {
return host
}
if !isWildcardListen(inbound.Listen) {
return inbound.Listen
}
return requestHost
}
func (s *SubService) ResolveClientHost(inbound *model.Inbound, client model.Client, requestHost string) string {
return s.ResolveClientHostWithDefault(inbound, client, requestHost, s.getDefaultClientHost())
}
func (s *SubService) ResolveClientHostWithDefault(inbound *model.Inbound, client model.Client, requestHost string, defaultClientHost string) string {
host := strings.TrimSpace(requestHost)
return s.resolveAddress(inbound, client, host, defaultClientHost)
}
func findClientByEmail(clients []model.Client, email string) (model.Client, int) {
for i, client := range clients {
if client.Email == email {
return client, i
}
}
return model.Client{}, -1
}
func (s *SubService) getLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
switch inbound.Protocol { switch inbound.Protocol {
case "vmess": case "vmess":
return s.genVmessLink(inbound, email) return s.genVmessLink(inbound, email, requestHost, defaultClientHost)
case "vless": case "vless":
return s.genVlessLink(inbound, email) return s.genVlessLink(inbound, email, requestHost, defaultClientHost)
case "trojan": case "trojan":
return s.genTrojanLink(inbound, email) return s.genTrojanLink(inbound, email, requestHost, defaultClientHost)
case "shadowsocks": case "shadowsocks":
return s.genShadowsocksLink(inbound, email) return s.genShadowsocksLink(inbound, email, requestHost, defaultClientHost)
} }
return "" return ""
} }
func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string { func (s *SubService) genVmessLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
if inbound.Protocol != model.VMESS { if inbound.Protocol != model.VMESS {
return "" return ""
} }
var address string
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
address = s.address
} else {
address = inbound.Listen
}
obj := map[string]any{ obj := map[string]any{
"v": "2", "v": "2",
"add": address, "add": "",
"port": inbound.Port, "port": inbound.Port,
"type": "none", "type": "none",
} }
@ -274,15 +323,13 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
} }
clients, _ := s.inboundService.GetClients(inbound) clients, _ := s.inboundService.GetClients(inbound)
clientIndex := -1 client, clientIndex := findClientByEmail(clients, email)
for i, client := range clients { if clientIndex < 0 {
if client.Email == email { return ""
clientIndex = i
break
}
} }
obj["id"] = clients[clientIndex].ID obj["add"] = s.resolveAddress(inbound, client, requestHost, defaultClientHost)
obj["scy"] = clients[clientIndex].Security obj["id"] = client.ID
obj["scy"] = client.Security
externalProxies, _ := stream["externalProxy"].([]any) externalProxies, _ := stream["externalProxy"].([]any)
@ -319,28 +366,18 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr) return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
} }
func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string { func (s *SubService) genVlessLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
var address string
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
address = s.address
} else {
address = inbound.Listen
}
if inbound.Protocol != model.VLESS { if inbound.Protocol != model.VLESS {
return "" return ""
} }
var stream map[string]any var stream map[string]any
json.Unmarshal([]byte(inbound.StreamSettings), &stream) json.Unmarshal([]byte(inbound.StreamSettings), &stream)
clients, _ := s.inboundService.GetClients(inbound) clients, _ := s.inboundService.GetClients(inbound)
clientIndex := -1 client, clientIndex := findClientByEmail(clients, email)
for i, client := range clients { if clientIndex < 0 {
if client.Email == email { return ""
clientIndex = i
break
}
} }
uuid := clients[clientIndex].ID uuid := client.ID
port := inbound.Port port := inbound.Port
streamNetwork := stream["network"].(string) streamNetwork := stream["network"].(string)
params := make(map[string]string) params := make(map[string]string)
@ -430,8 +467,8 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
} }
} }
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { if streamNetwork == "tcp" && len(client.Flow) > 0 {
params["flow"] = clients[clientIndex].Flow params["flow"] = client.Flow
} }
} }
@ -464,8 +501,8 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
params["spx"] = "/" + random.Seq(15) params["spx"] = "/" + random.Seq(15)
} }
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { if streamNetwork == "tcp" && len(client.Flow) > 0 {
params["flow"] = clients[clientIndex].Flow params["flow"] = client.Flow
} }
} }
@ -508,6 +545,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
return strings.Join(links, "\n") return strings.Join(links, "\n")
} }
address := s.resolveAddress(inbound, client, requestHost, defaultClientHost)
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port) link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
url, _ := url.Parse(link) url, _ := url.Parse(link)
q := url.Query() q := url.Query()
@ -523,27 +561,18 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
return url.String() return url.String()
} }
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string { func (s *SubService) genTrojanLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
var address string
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
address = s.address
} else {
address = inbound.Listen
}
if inbound.Protocol != model.Trojan { if inbound.Protocol != model.Trojan {
return "" return ""
} }
var stream map[string]any var stream map[string]any
json.Unmarshal([]byte(inbound.StreamSettings), &stream) json.Unmarshal([]byte(inbound.StreamSettings), &stream)
clients, _ := s.inboundService.GetClients(inbound) clients, _ := s.inboundService.GetClients(inbound)
clientIndex := -1 client, clientIndex := findClientByEmail(clients, email)
for i, client := range clients { if clientIndex < 0 {
if client.Email == email { return ""
clientIndex = i
break
}
} }
password := clients[clientIndex].Password password := client.Password
port := inbound.Port port := inbound.Port
streamNetwork := stream["network"].(string) streamNetwork := stream["network"].(string)
params := make(map[string]string) params := make(map[string]string)
@ -656,8 +685,8 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
params["spx"] = "/" + random.Seq(15) params["spx"] = "/" + random.Seq(15)
} }
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { if streamNetwork == "tcp" && len(client.Flow) > 0 {
params["flow"] = clients[clientIndex].Flow params["flow"] = client.Flow
} }
} }
@ -703,6 +732,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
return links return links
} }
address := s.resolveAddress(inbound, client, requestHost, defaultClientHost)
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port) link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
url, _ := url.Parse(link) url, _ := url.Parse(link)
@ -719,13 +749,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
return url.String() return url.String()
} }
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string { func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
var address string
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
address = s.address
} else {
address = inbound.Listen
}
if inbound.Protocol != model.Shadowsocks { if inbound.Protocol != model.Shadowsocks {
return "" return ""
} }
@ -737,12 +761,9 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
json.Unmarshal([]byte(inbound.Settings), &settings) json.Unmarshal([]byte(inbound.Settings), &settings)
inboundPassword := settings["password"].(string) inboundPassword := settings["password"].(string)
method := settings["method"].(string) method := settings["method"].(string)
clientIndex := -1 client, clientIndex := findClientByEmail(clients, email)
for i, client := range clients { if clientIndex < 0 {
if client.Email == email { return ""
clientIndex = i
break
}
} }
streamNetwork := stream["network"].(string) streamNetwork := stream["network"].(string)
params := make(map[string]string) params := make(map[string]string)
@ -827,9 +848,9 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
} }
} }
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password) encPart := fmt.Sprintf("%s:%s", method, client.Password)
if method[0] == '2' { if method[0] == '2' {
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password) encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, client.Password)
} }
externalProxies, _ := stream["externalProxy"].([]any) externalProxies, _ := stream["externalProxy"].([]any)
@ -870,6 +891,7 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
return links return links
} }
address := s.resolveAddress(inbound, client, requestHost, defaultClientHost)
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port) link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
url, _ := url.Parse(link) url, _ := url.Parse(link)
q := url.Query() q := url.Query()
@ -1161,8 +1183,8 @@ func (s *SubService) BuildPageData(subId string, hostHeader string, traffic xray
remained = common.FormatTraffic(left) remained = common.FormatTraffic(left)
} }
datepicker := s.datepicker datepicker, err := s.settingService.GetDatepicker()
if datepicker == "" { if err != nil || datepicker == "" {
datepicker = "gregorian" datepicker = "gregorian"
} }

View file

@ -100,37 +100,38 @@ is_port_in_use() {
gen_random_string() { gen_random_string() {
local length="$1" local length="$1"
local random_string=$(LC_ALL=C tr -dc 'a-zA-Z0-9' </dev/urandom | fold -w "$length" | head -n 1) openssl rand -base64 $(( length * 2 )) \
echo "$random_string" | tr -dc 'a-zA-Z0-9' \
| head -c "$length"
} }
install_base() { install_base() {
echo -e "${green}Updating and install dependency packages...${plain}" echo -e "${green}Updating and install dependency packages...${plain}"
case "${release}" in case "${release}" in
ubuntu | debian | armbian) ubuntu | debian | armbian)
apt-get update >/dev/null 2>&1 && apt-get install -y -q curl tar tzdata socat >/dev/null 2>&1 apt-get update >/dev/null 2>&1 && apt-get install -y -q curl tar tzdata socat openssl >/dev/null 2>&1
;; ;;
fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol) fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
dnf -y update >/dev/null 2>&1 && dnf install -y -q curl tar tzdata socat >/dev/null 2>&1 dnf -y update >/dev/null 2>&1 && dnf install -y -q curl tar tzdata socat openssl >/dev/null 2>&1
;; ;;
centos) centos)
if [[ "${VERSION_ID}" =~ ^7 ]]; then if [[ "${VERSION_ID}" =~ ^7 ]]; then
yum -y update >/dev/null 2>&1 && yum install -y -q curl tar tzdata socat >/dev/null 2>&1 yum -y update >/dev/null 2>&1 && yum install -y -q curl tar tzdata socat openssl >/dev/null 2>&1
else else
dnf -y update >/dev/null 2>&1 && dnf install -y -q curl tar tzdata socat >/dev/null 2>&1 dnf -y update >/dev/null 2>&1 && dnf install -y -q curl tar tzdata socat openssl >/dev/null 2>&1
fi fi
;; ;;
arch | manjaro | parch) arch | manjaro | parch)
pacman -Syu >/dev/null 2>&1 && pacman -Syu --noconfirm curl tar tzdata socat >/dev/null 2>&1 pacman -Syu >/dev/null 2>&1 && pacman -Syu --noconfirm curl tar tzdata socat openssl >/dev/null 2>&1
;; ;;
opensuse-tumbleweed | opensuse-leap) opensuse-tumbleweed | opensuse-leap)
zypper refresh >/dev/null 2>&1 && zypper -q install -y curl tar timezone socat >/dev/null 2>&1 zypper refresh >/dev/null 2>&1 && zypper -q install -y curl tar timezone socat openssl >/dev/null 2>&1
;; ;;
alpine) alpine)
apk update >/dev/null 2>&1 && apk add curl tar tzdata socat >/dev/null 2>&1 apk update >/dev/null 2>&1 && apk add curl tar tzdata socat openssl>/dev/null 2>&1
;; ;;
*) *)
apt-get update >/dev/null 2>&1 && apt install -y -q curl tar tzdata socat >/dev/null 2>&1 apt-get update >/dev/null 2>&1 && apt install -y -q curl tar tzdata socat openssl >/dev/null 2>&1
;; ;;
esac esac
} }

View file

@ -144,8 +144,8 @@ class DBInbound {
} }
} }
genInboundLinks(remarkModel) { genInboundLinks(remarkModel, defaultHost = '') {
const inbound = this.toInbound(); const inbound = this.toInbound();
return inbound.genInboundLinks(this.remark, remarkModel); return inbound.genInboundLinks(this.remark, remarkModel, defaultHost);
} }
} }

View file

@ -1725,10 +1725,25 @@ class Inbound extends XrayCommonClass {
} }
} }
genAllLinks(remark = '', remarkModel = '-ieo', client) { resolveLinkHost(client, defaultHost = '') {
if (client?.subHost && client.subHost.trim().length > 0) {
return client.subHost.trim();
}
if (this.settings?.subHost && this.settings.subHost.trim().length > 0) {
return this.settings.subHost.trim();
}
if (defaultHost && defaultHost.trim().length > 0) {
return defaultHost.trim();
}
return !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" && this.listen !== "::" && this.listen !== "::0"
? this.listen
: location.hostname;
}
genAllLinks(remark = '', remarkModel = '-ieo', client, defaultHost = '') {
let result = []; let result = [];
let email = client ? client.email : ''; let email = client ? client.email : '';
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname; let addr = this.resolveLinkHost(client, defaultHost);
let port = this.port; let port = this.port;
const separationChar = remarkModel.charAt(0); const separationChar = remarkModel.charAt(0);
const orderChars = remarkModel.slice(1); const orderChars = remarkModel.slice(1);
@ -1756,12 +1771,12 @@ class Inbound extends XrayCommonClass {
return result; return result;
} }
genInboundLinks(remark = '', remarkModel = '-ieo') { genInboundLinks(remark = '', remarkModel = '-ieo', defaultHost = '') {
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname; let addr = this.resolveLinkHost(null, defaultHost);
if (this.clients) { if (this.clients) {
let links = []; let links = [];
this.clients.forEach((client) => { this.clients.forEach((client) => {
this.genAllLinks(remark, remarkModel, client).forEach(l => { this.genAllLinks(remark, remarkModel, client, defaultHost).forEach(l => {
links.push(l.link); links.push(l.link);
}) })
}); });
@ -1811,9 +1826,10 @@ class Inbound extends XrayCommonClass {
} }
Inbound.Settings = class extends XrayCommonClass { Inbound.Settings = class extends XrayCommonClass {
constructor(protocol) { constructor(protocol, subHost = '') {
super(); super();
this.protocol = protocol; this.protocol = protocol;
this.subHost = subHost;
} }
static getSettings(protocol) { static getSettings(protocol) {
@ -1877,15 +1893,18 @@ Inbound.VmessSettings = class extends Inbound.Settings {
} }
static fromJson(json = {}) { static fromJson(json = {}) {
return new Inbound.VmessSettings( const obj = new Inbound.VmessSettings(
Protocols.VMESS, Protocols.VMESS,
json.clients.map(client => Inbound.VmessSettings.VMESS.fromJson(client)), json.clients.map(client => Inbound.VmessSettings.VMESS.fromJson(client)),
); );
obj.subHost = json.subHost || '';
return obj;
} }
toJson() { toJson() {
return { return {
clients: Inbound.VmessSettings.toJsonArray(this.vmesses), clients: Inbound.VmessSettings.toJsonArray(this.vmesses),
subHost: this.subHost || undefined,
}; };
} }
}; };
@ -1901,6 +1920,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
enable = true, enable = true,
tgId = '', tgId = '',
subId = RandomUtil.randomLowerAndNum(16), subId = RandomUtil.randomLowerAndNum(16),
subHost = '',
comment = '', comment = '',
reset = 0, reset = 0,
created_at = undefined, created_at = undefined,
@ -1916,6 +1936,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
this.enable = enable; this.enable = enable;
this.tgId = tgId; this.tgId = tgId;
this.subId = subId; this.subId = subId;
this.subHost = subHost;
this.comment = comment; this.comment = comment;
this.reset = reset; this.reset = reset;
this.created_at = created_at; this.created_at = created_at;
@ -1933,6 +1954,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
json.enable, json.enable,
json.tgId, json.tgId,
json.subId, json.subId,
json.subHost,
json.comment, json.comment,
json.reset, json.reset,
json.created_at, json.created_at,
@ -2009,6 +2031,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
json.selectedAuth, json.selectedAuth,
testseed testseed
); );
obj.subHost = json.subHost || '';
return obj; return obj;
} }
@ -2032,6 +2055,9 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
if (this.selectedAuth) { if (this.selectedAuth) {
json.selectedAuth = this.selectedAuth; json.selectedAuth = this.selectedAuth;
} }
if (this.subHost) {
json.subHost = this.subHost;
}
// Only include testseed if at least one client has a flow set // Only include testseed if at least one client has a flow set
const hasFlow = this.vlesses && this.vlesses.some(vless => vless.flow && vless.flow !== ''); const hasFlow = this.vlesses && this.vlesses.some(vless => vless.flow && vless.flow !== '');
@ -2056,6 +2082,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
enable = true, enable = true,
tgId = '', tgId = '',
subId = RandomUtil.randomLowerAndNum(16), subId = RandomUtil.randomLowerAndNum(16),
subHost = '',
comment = '', comment = '',
reset = 0, reset = 0,
created_at = undefined, created_at = undefined,
@ -2071,6 +2098,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
this.enable = enable; this.enable = enable;
this.tgId = tgId; this.tgId = tgId;
this.subId = subId; this.subId = subId;
this.subHost = subHost;
this.comment = comment; this.comment = comment;
this.reset = reset; this.reset = reset;
this.created_at = created_at; this.created_at = created_at;
@ -2088,6 +2116,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
json.enable, json.enable,
json.tgId, json.tgId,
json.subId, json.subId,
json.subHost,
json.comment, json.comment,
json.reset, json.reset,
json.created_at, json.created_at,
@ -2177,16 +2206,19 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
} }
static fromJson(json = {}) { static fromJson(json = {}) {
return new Inbound.TrojanSettings( const obj = new Inbound.TrojanSettings(
Protocols.TROJAN, Protocols.TROJAN,
json.clients.map(client => Inbound.TrojanSettings.Trojan.fromJson(client)), json.clients.map(client => Inbound.TrojanSettings.Trojan.fromJson(client)),
Inbound.TrojanSettings.Fallback.fromJson(json.fallbacks),); Inbound.TrojanSettings.Fallback.fromJson(json.fallbacks),);
obj.subHost = json.subHost || '';
return obj;
} }
toJson() { toJson() {
return { return {
clients: Inbound.TrojanSettings.toJsonArray(this.trojans), clients: Inbound.TrojanSettings.toJsonArray(this.trojans),
fallbacks: Inbound.TrojanSettings.toJsonArray(this.fallbacks) fallbacks: Inbound.TrojanSettings.toJsonArray(this.fallbacks),
subHost: this.subHost || undefined,
}; };
} }
}; };
@ -2201,6 +2233,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
enable = true, enable = true,
tgId = '', tgId = '',
subId = RandomUtil.randomLowerAndNum(16), subId = RandomUtil.randomLowerAndNum(16),
subHost = '',
comment = '', comment = '',
reset = 0, reset = 0,
created_at = undefined, created_at = undefined,
@ -2215,6 +2248,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
this.enable = enable; this.enable = enable;
this.tgId = tgId; this.tgId = tgId;
this.subId = subId; this.subId = subId;
this.subHost = subHost;
this.comment = comment; this.comment = comment;
this.reset = reset; this.reset = reset;
this.created_at = created_at; this.created_at = created_at;
@ -2231,6 +2265,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
enable: this.enable, enable: this.enable,
tgId: this.tgId, tgId: this.tgId,
subId: this.subId, subId: this.subId,
subHost: this.subHost,
comment: this.comment, comment: this.comment,
reset: this.reset, reset: this.reset,
created_at: this.created_at, created_at: this.created_at,
@ -2248,6 +2283,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
json.enable, json.enable,
json.tgId, json.tgId,
json.subId, json.subId,
json.subHost,
json.comment, json.comment,
json.reset, json.reset,
json.created_at, json.created_at,
@ -2338,7 +2374,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
} }
static fromJson(json = {}) { static fromJson(json = {}) {
return new Inbound.ShadowsocksSettings( const obj = new Inbound.ShadowsocksSettings(
Protocols.SHADOWSOCKS, Protocols.SHADOWSOCKS,
json.method, json.method,
json.password, json.password,
@ -2346,6 +2382,8 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
json.clients.map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)), json.clients.map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)),
json.ivCheck, json.ivCheck,
); );
obj.subHost = json.subHost || '';
return obj;
} }
toJson() { toJson() {
@ -2355,6 +2393,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
network: this.network, network: this.network,
clients: Inbound.ShadowsocksSettings.toJsonArray(this.shadowsockses), clients: Inbound.ShadowsocksSettings.toJsonArray(this.shadowsockses),
ivCheck: this.ivCheck, ivCheck: this.ivCheck,
subHost: this.subHost || undefined,
}; };
} }
}; };
@ -2370,6 +2409,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
enable = true, enable = true,
tgId = '', tgId = '',
subId = RandomUtil.randomLowerAndNum(16), subId = RandomUtil.randomLowerAndNum(16),
subHost = '',
comment = '', comment = '',
reset = 0, reset = 0,
created_at = undefined, created_at = undefined,
@ -2385,6 +2425,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
this.enable = enable; this.enable = enable;
this.tgId = tgId; this.tgId = tgId;
this.subId = subId; this.subId = subId;
this.subHost = subHost;
this.comment = comment; this.comment = comment;
this.reset = reset; this.reset = reset;
this.created_at = created_at; this.created_at = created_at;
@ -2402,6 +2443,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
enable: this.enable, enable: this.enable,
tgId: this.tgId, tgId: this.tgId,
subId: this.subId, subId: this.subId,
subHost: this.subHost,
comment: this.comment, comment: this.comment,
reset: this.reset, reset: this.reset,
created_at: this.created_at, created_at: this.created_at,
@ -2420,6 +2462,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
json.enable, json.enable,
json.tgId, json.tgId,
json.subId, json.subId,
json.subHost,
json.comment, json.comment,
json.reset, json.reset,
json.created_at, json.created_at,

View file

@ -39,6 +39,7 @@ class AllSetting {
this.subPath = "/sub/"; this.subPath = "/sub/";
this.subJsonPath = "/json/"; this.subJsonPath = "/json/";
this.subDomain = ""; this.subDomain = "";
this.subDefaultHost = "";
this.externalTrafficInformEnable = false; this.externalTrafficInformEnable = false;
this.externalTrafficInformURI = ""; this.externalTrafficInformURI = "";
this.subCertFile = ""; this.subCertFile = "";
@ -86,4 +87,4 @@ class AllSetting {
equals(other) { equals(other) {
return ObjectUtil.equals(this, other); return ObjectUtil.equals(this, other);
} }
} }

View file

@ -66,6 +66,7 @@ type AllSetting struct {
SubPort int `json:"subPort" form:"subPort"` // Subscription server port SubPort int `json:"subPort" form:"subPort"` // Subscription server port
SubPath string `json:"subPath" form:"subPath"` // Base path for subscription URLs SubPath string `json:"subPath" form:"subPath"` // Base path for subscription URLs
SubDomain string `json:"subDomain" form:"subDomain"` // Domain for subscription server validation SubDomain string `json:"subDomain" form:"subDomain"` // Domain for subscription server validation
SubDefaultHost string `json:"subDefaultHost" form:"subDefaultHost"` // Default host/IP used in exported client links
SubCertFile string `json:"subCertFile" form:"subCertFile"` // SSL certificate file for subscription server SubCertFile string `json:"subCertFile" form:"subCertFile"` // SSL certificate file for subscription server
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"` // SSL private key file for subscription server SubKeyFile string `json:"subKeyFile" form:"subKeyFile"` // SSL private key file for subscription server
SubUpdates int `json:"subUpdates" form:"subUpdates"` // Subscription update interval in minutes SubUpdates int `json:"subUpdates" form:"subUpdates"` // Subscription update interval in minutes

View file

@ -56,6 +56,18 @@
</template> </template>
<a-input v-model.trim="client.subId"></a-input> <a-input v-model.trim="client.subId"></a-input>
</a-form-item> </a-form-item>
<a-form-item v-if="client.email && app.subSettings?.enable">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.settings.subClientHostDesc" }}</span>
</template>
{{ i18n "pages.settings.subClientHost" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="client.subHost" placeholder="client.example.com"></a-input>
</a-form-item>
<a-form-item v-if="client.email && app.tgBotEnable"> <a-form-item v-if="client.email && app.tgBotEnable">
<template slot="label"> <template slot="label">
<a-tooltip> <a-tooltip>
@ -169,4 +181,4 @@
<a-input-number v-model.number="client.reset" :min="0"></a-input-number> <a-input-number v-model.number="client.reset" :min="0"></a-input-number>
</a-form-item> </a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View file

@ -34,6 +34,19 @@
<a-input-number v-model.number="inbound.port" :min="1" <a-input-number v-model.number="inbound.port" :min="1"
:max="65535"></a-input-number> :max="65535"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item
v-if="[Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(inbound.protocol)">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.settings.subInboundHostDesc" }}</span>
</template>
{{ i18n "pages.settings.subInboundHost" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="inbound.settings.subHost" placeholder="cdn.example.com"></a-input>
</a-form-item>
<a-form-item> <a-form-item>
<template slot="label"> <template slot="label">
@ -170,4 +183,4 @@
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
{{end}} {{end}}

View file

@ -734,6 +734,7 @@
subURI: '', subURI: '',
subJsonURI: '', subJsonURI: '',
subJsonEnable: false, subJsonEnable: false,
defaultHost: '',
}, },
remarkModel: '-ieo', remarkModel: '-ieo',
datepicker: 'gregorian', datepicker: 'gregorian',
@ -791,6 +792,7 @@
subURI: subURI, subURI: subURI,
subJsonURI: subJsonURI, subJsonURI: subJsonURI,
subJsonEnable: subJsonEnable, subJsonEnable: subJsonEnable,
defaultHost: subDefaultHost,
}; };
this.pageSize = pageSize; this.pageSize = pageSize;
this.remarkModel = remarkModel; this.remarkModel = remarkModel;
@ -1471,7 +1473,7 @@
inboundLinks(dbInboundId) { inboundLinks(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
newDbInbound = this.checkFallback(dbInbound); newDbInbound = this.checkFallback(dbInbound);
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(this.remarkModel), newDbInbound.remark); txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(this.remarkModel, this.subSettings.defaultHost), newDbInbound.remark);
}, },
exportSubs(dbInboundId) { exportSubs(dbInboundId) {
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
@ -1520,7 +1522,7 @@
exportAllLinks() { exportAllLinks() {
let copyText = []; let copyText = [];
for (const dbInbound of this.dbInbounds) { for (const dbInbound of this.dbInbounds) {
copyText.push(dbInbound.genInboundLinks(this.remarkModel)); copyText.push(dbInbound.genInboundLinks(this.remarkModel, this.subSettings.defaultHost));
} }
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText.join('\r\n'), 'All-Inbounds'); txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText.join('\r\n'), 'All-Inbounds');
}, },
@ -1697,4 +1699,4 @@
}, },
}); });
</script> </script>
{{ template "page/body_end" .}} {{ template "page/body_end" .}}

View file

@ -51,6 +51,18 @@
</template> </template>
<a-input v-model.trim="clientsBulkModal.subId"></a-input> <a-input v-model.trim="clientsBulkModal.subId"></a-input>
</a-form-item> </a-form-item>
<a-form-item v-if="app.subSettings?.enable">
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.settings.subClientHostDesc" }}</span>
</template>
{{ i18n "pages.settings.subClientHost" }}
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="clientsBulkModal.subHost" placeholder="client.example.com"></a-input>
</a-form-item>
<a-form-item v-if="app.tgBotEnable"> <a-form-item v-if="app.tgBotEnable">
<template slot="label"> <template slot="label">
<a-tooltip> <a-tooltip>
@ -144,6 +156,7 @@
emailPrefix: "", emailPrefix: "",
emailPostfix: "", emailPostfix: "",
subId: "", subId: "",
subHost: "",
tgId: '', tgId: '',
security: "auto", security: "auto",
flow: "", flow: "",
@ -167,6 +180,7 @@
if (method == 4) newClient.email = ""; if (method == 4) newClient.email = "";
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix; newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
if (clientsBulkModal.subId.length > 0) newClient.subId = clientsBulkModal.subId; if (clientsBulkModal.subId.length > 0) newClient.subId = clientsBulkModal.subId;
if (clientsBulkModal.subHost.length > 0) newClient.subHost = clientsBulkModal.subHost;
newClient.tgId = clientsBulkModal.tgId; newClient.tgId = clientsBulkModal.tgId;
newClient.security = clientsBulkModal.security; newClient.security = clientsBulkModal.security;
newClient.limitIp = clientsBulkModal.limitIp; newClient.limitIp = clientsBulkModal.limitIp;
@ -200,6 +214,7 @@
this.emailPrefix = ""; this.emailPrefix = "";
this.emailPostfix = ""; this.emailPostfix = "";
this.subId = ""; this.subId = "";
this.subHost = "";
this.tgId = ''; this.tgId = '';
this.security = "auto"; this.security = "auto";
this.flow = ""; this.flow = "";
@ -247,4 +262,4 @@
}); });
</script> </script>
{{end}} {{end}}

View file

@ -668,9 +668,9 @@
} }
} }
if (this.inbound.protocol == Protocols.WIREGUARD) { if (this.inbound.protocol == Protocols.WIREGUARD) {
this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n') this.links = this.inbound.genInboundLinks(dbInbound.remark, app.remarkModel, app.subSettings.defaultHost).split('\r\n')
} else { } else {
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings); this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings, app.subSettings.defaultHost);
} }
if (this.clientSettings) { if (this.clientSettings) {
if (this.clientSettings.subId) { if (this.clientSettings.subId) {

View file

@ -115,7 +115,7 @@
// Reset the status fetched flag when showing the modal // Reset the status fetched flag when showing the modal
if (qrModalApp) qrModalApp.statusFetched = false; if (qrModalApp) qrModalApp.statusFetched = false;
if (this.inbound.protocol == Protocols.WIREGUARD) { if (this.inbound.protocol == Protocols.WIREGUARD) {
this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l, index) => { this.inbound.genInboundLinks(dbInbound.remark, app.remarkModel, app.subSettings.defaultHost).split('\r\n').forEach((l, index) => {
this.qrcodes.push({ this.qrcodes.push({
remark: "Peer " + (index + 1), remark: "Peer " + (index + 1),
link: l, link: l,
@ -124,7 +124,7 @@
}); });
}); });
} else { } else {
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => { this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client, app.subSettings.defaultHost).forEach(l => {
this.qrcodes.push({ this.qrcodes.push({
remark: l.remark, remark: l.remark,
link: l.link, link: l.link,
@ -303,4 +303,4 @@
}); });
} }
</script> </script>
{{end}} {{end}}

View file

@ -29,6 +29,14 @@
<a-input type="text" v-model="allSetting.subDomain"></a-input> <a-input type="text" v-model="allSetting.subDomain"></a-input>
</template> </template>
</a-setting-list-item> </a-setting-list-item>
<a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subDefaultHost"}}</template>
<template #description>{{ i18n "pages.settings.subDefaultHostDesc"}}</template>
<template #control>
<a-input type="text" v-model.trim="allSetting.subDefaultHost"
placeholder="public.example.com"></a-input>
</template>
</a-setting-list-item>
<a-setting-list-item paddings="small"> <a-setting-list-item paddings="small">
<template #title>{{ i18n "pages.settings.subPort"}}</template> <template #title>{{ i18n "pages.settings.subPort"}}</template>
<template #description>{{ i18n "pages.settings.subPortDesc"}}</template> <template #description>{{ i18n "pages.settings.subPortDesc"}}</template>
@ -142,4 +150,4 @@
</a-setting-list-item> </a-setting-list-item>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
{{end}} {{end}}

View file

@ -63,6 +63,7 @@ var defaultValueMap = map[string]string{
"subPort": "2096", "subPort": "2096",
"subPath": "/sub/", "subPath": "/sub/",
"subDomain": "", "subDomain": "",
"subDefaultHost": "",
"subCertFile": "", "subCertFile": "",
"subKeyFile": "", "subKeyFile": "",
"subUpdates": "12", "subUpdates": "12",
@ -515,6 +516,10 @@ func (s *SettingService) GetSubDomain() (string, error) {
return s.getString("subDomain") return s.getString("subDomain")
} }
func (s *SettingService) GetSubDefaultHost() (string, error) {
return s.getString("subDefaultHost")
}
func (s *SettingService) SetSubCertFile(subCertFile string) error { func (s *SettingService) SetSubCertFile(subCertFile string) error {
return s.setString("subCertFile", subCertFile) return s.setString("subCertFile", subCertFile)
} }
@ -754,6 +759,9 @@ func (s *SettingService) GetDefaultSettings(host string) (any, error) {
"subTitle": func() (any, error) { return s.GetSubTitle() }, "subTitle": func() (any, error) { return s.GetSubTitle() },
"subURI": func() (any, error) { return s.GetSubURI() }, "subURI": func() (any, error) { return s.GetSubURI() },
"subJsonURI": func() (any, error) { return s.GetSubJsonURI() }, "subJsonURI": func() (any, error) { return s.GetSubJsonURI() },
"subDefaultHost": func() (any, error) {
return s.GetSubDefaultHost()
},
"remarkModel": func() (any, error) { return s.GetRemarkModel() }, "remarkModel": func() (any, error) { return s.GetRemarkModel() },
"datepicker": func() (any, error) { return s.GetDatepicker() }, "datepicker": func() (any, error) { return s.GetDatepicker() },
"ipLimitEnable": func() (any, error) { return s.GetIpLimitEnable() }, "ipLimitEnable": func() (any, error) { return s.GetIpLimitEnable() },

View file

@ -396,6 +396,12 @@
"subPathDesc" = "مسار URI لخدمة الاشتراك. (يبدأ بـ '/' وبينتهي بـ '/')" "subPathDesc" = "مسار URI لخدمة الاشتراك. (يبدأ بـ '/' وبينتهي بـ '/')"
"subDomain" = "دومين الاستماع" "subDomain" = "دومين الاستماع"
"subDomainDesc" = "اسم الدومين لخدمة الاشتراك. (سيبه فاضي عشان يستمع على كل الدومينات والـ IPs)" "subDomainDesc" = "اسم الدومين لخدمة الاشتراك. (سيبه فاضي عشان يستمع على كل الدومينات والـ IPs)"
"subDefaultHost" = "مضيف العميل الافتراضي"
"subDefaultHostDesc" = "المضيف أو عنوان IP الافتراضي المستخدم في روابط/إعدادات العميل المُصدَّرة."
"subInboundHost" = "مضيف الـ Inbound"
"subInboundHostDesc" = "تجاوز اختياري للمضيف/IP للروابط المُصدَّرة من هذا الـ Inbound."
"subClientHost" = "مضيف العميل"
"subClientHostDesc" = "تجاوز اختياري للمضيف/IP للروابط المُصدَّرة لهذا العميل."
"subUpdates" = "فترات التحديث" "subUpdates" = "فترات التحديث"
"subUpdatesDesc" = "فترات تحديث رابط الاشتراك في تطبيقات العملاء. (الوحدة: ساعة)" "subUpdatesDesc" = "فترات تحديث رابط الاشتراك في تطبيقات العملاء. (الوحدة: ساعة)"
"subEncrypt" = "تشفير" "subEncrypt" = "تشفير"

View file

@ -396,6 +396,12 @@
"subPathDesc" = "The URI path for the subscription service. (begins with / and concludes with /)" "subPathDesc" = "The URI path for the subscription service. (begins with / and concludes with /)"
"subDomain" = "Listen Domain" "subDomain" = "Listen Domain"
"subDomainDesc" = "The domain name for the subscription service. (leave blank to listen on all domains and IPs)" "subDomainDesc" = "The domain name for the subscription service. (leave blank to listen on all domains and IPs)"
"subDefaultHost" = "Default Client Host"
"subDefaultHostDesc" = "Default host or IP used in exported client links/configs."
"subInboundHost" = "Inbound Host"
"subInboundHostDesc" = "Optional host/IP override for links exported from this inbound."
"subClientHost" = "Client Host"
"subClientHostDesc" = "Optional host/IP override for links exported for this client."
"subUpdates" = "Update Intervals" "subUpdates" = "Update Intervals"
"subUpdatesDesc" = "The update intervals of the subscription URL in the client apps. (unit: hour)" "subUpdatesDesc" = "The update intervals of the subscription URL in the client apps. (unit: hour)"
"subEncrypt" = "Encode" "subEncrypt" = "Encode"

View file

@ -396,6 +396,12 @@
"subPathDesc" = "Debe empezar con '/' y terminar con '/'" "subPathDesc" = "Debe empezar con '/' y terminar con '/'"
"subDomain" = "Dominio de Escucha" "subDomain" = "Dominio de Escucha"
"subDomainDesc" = "Dejar en blanco por defecto para monitorear todos los dominios e IPs." "subDomainDesc" = "Dejar en blanco por defecto para monitorear todos los dominios e IPs."
"subDefaultHost" = "Host predeterminado del cliente"
"subDefaultHostDesc" = "Host o IP predeterminado utilizado en enlaces/configuraciones de cliente exportados."
"subInboundHost" = "Host del inbound"
"subInboundHostDesc" = "Reemplazo opcional de host/IP para los enlaces exportados desde este inbound."
"subClientHost" = "Host del cliente"
"subClientHostDesc" = "Reemplazo opcional de host/IP para los enlaces exportados para este cliente."
"subUpdates" = "Intervalos de Actualización de Suscripción" "subUpdates" = "Intervalos de Actualización de Suscripción"
"subUpdatesDesc" = "Horas de intervalo entre actualizaciones en la aplicación del cliente." "subUpdatesDesc" = "Horas de intervalo entre actualizaciones en la aplicación del cliente."
"subEncrypt" = "Encriptar configuraciones" "subEncrypt" = "Encriptar configuraciones"

View file

@ -396,6 +396,12 @@
"subPathDesc" = "برای سرویس سابسکریپشن. با '/' شروع‌ و با '/' خاتمه‌ می‌یابد URI مسیر" "subPathDesc" = "برای سرویس سابسکریپشن. با '/' شروع‌ و با '/' خاتمه‌ می‌یابد URI مسیر"
"subDomain" = "نام دامنه" "subDomain" = "نام دامنه"
"subDomainDesc" = "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنه‌ها و آی‌پی‌ها خالی‌بگذارید‌" "subDomainDesc" = "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنه‌ها و آی‌پی‌ها خالی‌بگذارید‌"
"subDefaultHost" = "هاست پیش‌فرض کاربر"
"subDefaultHostDesc" = "هاست یا IP پیش‌فرضی که در لینک‌ها/پیکربندی‌های خروجی کاربر استفاده می‌شود."
"subInboundHost" = "هاست ورودی"
"subInboundHostDesc" = "جایگزینی اختیاری هاست/IP برای لینک‌های خروجی این ورودی."
"subClientHost" = "هاست کاربر"
"subClientHostDesc" = "جایگزینی اختیاری هاست/IP برای لینک‌های خروجی این کاربر."
"subUpdates" = "فاصله بروزرسانی‌ سابسکریپشن" "subUpdates" = "فاصله بروزرسانی‌ سابسکریپشن"
"subUpdatesDesc" = "(فاصله مابین بروزرسانی در برنامه‌های کاربری. (واحد: ساعت" "subUpdatesDesc" = "(فاصله مابین بروزرسانی در برنامه‌های کاربری. (واحد: ساعت"
"subEncrypt" = "کدگذاری" "subEncrypt" = "کدگذاری"

View file

@ -396,6 +396,12 @@
"subPathDesc" = "URI path untuk layanan langganan. (dimulai dengan / dan diakhiri dengan /)" "subPathDesc" = "URI path untuk layanan langganan. (dimulai dengan / dan diakhiri dengan /)"
"subDomain" = "Domain Pendengar" "subDomain" = "Domain Pendengar"
"subDomainDesc" = "Nama domain untuk layanan langganan. (biarkan kosong untuk mendengarkan semua domain dan IP)" "subDomainDesc" = "Nama domain untuk layanan langganan. (biarkan kosong untuk mendengarkan semua domain dan IP)"
"subDefaultHost" = "Host Klien Default"
"subDefaultHostDesc" = "Host atau IP default yang digunakan pada tautan/konfigurasi klien yang diekspor."
"subInboundHost" = "Host Inbound"
"subInboundHostDesc" = "Override host/IP opsional untuk tautan yang diekspor dari inbound ini."
"subClientHost" = "Host Klien"
"subClientHostDesc" = "Override host/IP opsional untuk tautan yang diekspor untuk klien ini."
"subUpdates" = "Interval Pembaruan" "subUpdates" = "Interval Pembaruan"
"subUpdatesDesc" = "Interval pembaruan URL langganan dalam aplikasi klien. (unit: jam)" "subUpdatesDesc" = "Interval pembaruan URL langganan dalam aplikasi klien. (unit: jam)"
"subEncrypt" = "Encode" "subEncrypt" = "Encode"

View file

@ -396,6 +396,12 @@
"subPathDesc" = "サブスクリプションサービスで使用するURIパス'/'で始まり、'/'で終わる)" "subPathDesc" = "サブスクリプションサービスで使用するURIパス'/'で始まり、'/'で終わる)"
"subDomain" = "監視ドメイン" "subDomain" = "監視ドメイン"
"subDomainDesc" = "サブスクリプションサービスが監視するドメイン空白にするとすべてのドメインとIPを監視" "subDomainDesc" = "サブスクリプションサービスが監視するドメイン空白にするとすべてのドメインとIPを監視"
"subDefaultHost" = "クライアントのデフォルトホスト"
"subDefaultHostDesc" = "エクスポートされるクライアントリンク/設定で使用するデフォルトのホストまたはIP。"
"subInboundHost" = "インバウンドホスト"
"subInboundHostDesc" = "このインバウンドからエクスポートされるリンクに対する任意のホスト/IP上書き。"
"subClientHost" = "クライアントホスト"
"subClientHostDesc" = "このクライアント向けにエクスポートされるリンクに対する任意のホスト/IP上書き。"
"subUpdates" = "更新間隔" "subUpdates" = "更新間隔"
"subUpdatesDesc" = "クライアントアプリケーションでサブスクリプションURLの更新間隔単位時間" "subUpdatesDesc" = "クライアントアプリケーションでサブスクリプションURLの更新間隔単位時間"
"subEncrypt" = "エンコード" "subEncrypt" = "エンコード"

View file

@ -396,6 +396,12 @@
"subPathDesc" = "O caminho URI para o serviço de assinatura. (começa com / e termina com /)" "subPathDesc" = "O caminho URI para o serviço de assinatura. (começa com / e termina com /)"
"subDomain" = "Domínio de Escuta" "subDomain" = "Domínio de Escuta"
"subDomainDesc" = "O nome de domínio para o serviço de assinatura. (deixe em branco para escutar em todos os domínios e IPs)" "subDomainDesc" = "O nome de domínio para o serviço de assinatura. (deixe em branco para escutar em todos os domínios e IPs)"
"subDefaultHost" = "Host padrão do cliente"
"subDefaultHostDesc" = "Host ou IP padrão usado nos links/configurações de cliente exportados."
"subInboundHost" = "Host do inbound"
"subInboundHostDesc" = "Substituição opcional de host/IP para links exportados deste inbound."
"subClientHost" = "Host do cliente"
"subClientHostDesc" = "Substituição opcional de host/IP para links exportados para este cliente."
"subUpdates" = "Intervalos de Atualização" "subUpdates" = "Intervalos de Atualização"
"subUpdatesDesc" = "Os intervalos de atualização da URL de assinatura nos aplicativos de cliente. (unidade: hora)" "subUpdatesDesc" = "Os intervalos de atualização da URL de assinatura nos aplicativos de cliente. (unidade: hora)"
"subEncrypt" = "Codificar" "subEncrypt" = "Codificar"

View file

@ -396,6 +396,12 @@
"subPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'" "subPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'"
"subDomain" = "Домен прослушивания" "subDomain" = "Домен прослушивания"
"subDomainDesc" = "Оставьте пустым по умолчанию, чтобы слушать все домены и IP-адреса" "subDomainDesc" = "Оставьте пустым по умолчанию, чтобы слушать все домены и IP-адреса"
"subDefaultHost" = "Хост клиента по умолчанию"
"subDefaultHostDesc" = "Хост или IP по умолчанию, используемый в экспортируемых ссылках/конфигах клиента."
"subInboundHost" = "Хост входящего подключения"
"subInboundHostDesc" = "Необязательный хост/IP для переопределения в ссылках, экспортируемых из этого входящего подключения."
"subClientHost" = "Хост клиента"
"subClientHostDesc" = "Необязательный хост/IP для переопределения в ссылках, экспортируемых для этого клиента."
"subUpdates" = "Интервалы обновления подписки" "subUpdates" = "Интервалы обновления подписки"
"subUpdatesDesc" = "Интервал между обновлениями в клиентском приложении (в часах)" "subUpdatesDesc" = "Интервал между обновлениями в клиентском приложении (в часах)"
"subEncrypt" = "Шифровать конфиги" "subEncrypt" = "Шифровать конфиги"

View file

@ -396,6 +396,12 @@
"subPathDesc" = "Abonelik hizmeti için URI yolu. ('/' ile başlar ve '/' ile biter)" "subPathDesc" = "Abonelik hizmeti için URI yolu. ('/' ile başlar ve '/' ile biter)"
"subDomain" = "Dinleme Alan Adı" "subDomain" = "Dinleme Alan Adı"
"subDomainDesc" = "Abonelik hizmeti için alan adı. (tüm alan adlarını ve IP'leri dinlemek için boş bırakın)" "subDomainDesc" = "Abonelik hizmeti için alan adı. (tüm alan adlarını ve IP'leri dinlemek için boş bırakın)"
"subDefaultHost" = "Varsayılan İstemci Ana Bilgisayarı"
"subDefaultHostDesc" = "Dışa aktarılan istemci bağlantıları/yapılandırmalarında kullanılan varsayılan ana bilgisayar veya IP."
"subInboundHost" = "Gelen Bağlantı Ana Bilgisayarı"
"subInboundHostDesc" = "Bu gelen bağlantıdan dışa aktarılan bağlantılar için isteğe bağlı ana bilgisayar/IP geçersiz kılması."
"subClientHost" = "İstemci Ana Bilgisayarı"
"subClientHostDesc" = "Bu istemci için dışa aktarılan bağlantılar için isteğe bağlı ana bilgisayar/IP geçersiz kılması."
"subUpdates" = "Güncelleme Aralıkları" "subUpdates" = "Güncelleme Aralıkları"
"subUpdatesDesc" = "Müşteri uygulamalarındaki abonelik URL'sinin güncelleme aralıkları. (birim: saat)" "subUpdatesDesc" = "Müşteri uygulamalarındaki abonelik URL'sinin güncelleme aralıkları. (birim: saat)"
"subEncrypt" = "Şifrele" "subEncrypt" = "Şifrele"

View file

@ -396,6 +396,12 @@
"subPathDesc" = "Шлях URI для служби підписки. (починається з / і закінчується /)" "subPathDesc" = "Шлях URI для служби підписки. (починається з / і закінчується /)"
"subDomain" = "Домен прослуховування" "subDomain" = "Домен прослуховування"
"subDomainDesc" = "Ім'я домену для служби підписки. (залиште порожнім, щоб слухати всі домени та IP-адреси)" "subDomainDesc" = "Ім'я домену для служби підписки. (залиште порожнім, щоб слухати всі домени та IP-адреси)"
"subDefaultHost" = "Хост клієнта за замовчуванням"
"subDefaultHostDesc" = "Хост або IP за замовчуванням, що використовується в експортованих посиланнях/конфігах клієнта."
"subInboundHost" = "Хост вхідного підключення"
"subInboundHostDesc" = "Необов'язкове перевизначення host/IP для посилань, експортованих із цього вхідного підключення."
"subClientHost" = "Хост клієнта"
"subClientHostDesc" = "Необов'язкове перевизначення host/IP для посилань, експортованих для цього клієнта."
"subUpdates" = "Інтервали оновлення" "subUpdates" = "Інтервали оновлення"
"subUpdatesDesc" = "Інтервали оновлення URL-адреси підписки в клієнтських програмах. (одиниця: година)" "subUpdatesDesc" = "Інтервали оновлення URL-адреси підписки в клієнтських програмах. (одиниця: година)"
"subEncrypt" = "Закодувати" "subEncrypt" = "Закодувати"

View file

@ -396,6 +396,12 @@
"subPathDesc" = "Phải bắt đầu và kết thúc bằng '/'" "subPathDesc" = "Phải bắt đầu và kết thúc bằng '/'"
"subDomain" = "Tên miền con" "subDomain" = "Tên miền con"
"subDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP" "subDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP"
"subDefaultHost" = "Host mặc định của khách hàng"
"subDefaultHostDesc" = "Host hoặc IP mặc định được dùng trong liên kết/cấu hình khách hàng đã xuất."
"subInboundHost" = "Host inbound"
"subInboundHostDesc" = "Ghi đè host/IP tùy chọn cho các liên kết được xuất từ inbound này."
"subClientHost" = "Host khách hàng"
"subClientHostDesc" = "Ghi đè host/IP tùy chọn cho các liên kết được xuất cho khách hàng này."
"subUpdates" = "Khoảng thời gian cập nhật gói đăng ký" "subUpdates" = "Khoảng thời gian cập nhật gói đăng ký"
"subUpdatesDesc" = "Số giờ giữa các cập nhật trong ứng dụng khách" "subUpdatesDesc" = "Số giờ giữa các cập nhật trong ứng dụng khách"
"subEncrypt" = "Mã hóa cấu hình" "subEncrypt" = "Mã hóa cấu hình"

View file

@ -396,6 +396,12 @@
"subPathDesc" = "订阅服务使用的 URI 路径(以 '/' 开头,以 '/' 结尾)" "subPathDesc" = "订阅服务使用的 URI 路径(以 '/' 开头,以 '/' 结尾)"
"subDomain" = "监听域名" "subDomain" = "监听域名"
"subDomainDesc" = "订阅服务监听的域名(留空表示监听所有域名和 IP" "subDomainDesc" = "订阅服务监听的域名(留空表示监听所有域名和 IP"
"subDefaultHost" = "默认客户端主机"
"subDefaultHostDesc" = "用于导出客户端链接/配置的默认主机或 IP。"
"subInboundHost" = "入站主机"
"subInboundHostDesc" = "为从该入站导出的链接提供可选的主机/IP 覆盖。"
"subClientHost" = "客户端主机"
"subClientHostDesc" = "为该客户端导出的链接提供可选的主机/IP 覆盖。"
"subUpdates" = "更新间隔" "subUpdates" = "更新间隔"
"subUpdatesDesc" = "客户端应用中订阅 URL 的更新间隔(单位:小时)" "subUpdatesDesc" = "客户端应用中订阅 URL 的更新间隔(单位:小时)"
"subEncrypt" = "编码" "subEncrypt" = "编码"

View file

@ -396,6 +396,12 @@
"subPathDesc" = "訂閱服務使用的 URI 路徑(以 '/' 開頭,以 '/' 結尾)" "subPathDesc" = "訂閱服務使用的 URI 路徑(以 '/' 開頭,以 '/' 結尾)"
"subDomain" = "監聽域名" "subDomain" = "監聽域名"
"subDomainDesc" = "訂閱服務監聽的域名(留空表示監聽所有域名和 IP" "subDomainDesc" = "訂閱服務監聽的域名(留空表示監聽所有域名和 IP"
"subDefaultHost" = "預設客戶端主機"
"subDefaultHostDesc" = "用於匯出客戶端連結/設定的預設主機或 IP。"
"subInboundHost" = "入站主機"
"subInboundHostDesc" = "可選的主機/IP 覆寫,用於從此入站匯出的連結。"
"subClientHost" = "客戶端主機"
"subClientHostDesc" = "可選的主機/IP 覆寫,用於為此客戶端匯出的連結。"
"subUpdates" = "更新間隔" "subUpdates" = "更新間隔"
"subUpdatesDesc" = "客戶端應用中訂閱 URL 的更新間隔(單位:小時)" "subUpdatesDesc" = "客戶端應用中訂閱 URL 的更新間隔(單位:小時)"
"subEncrypt" = "編碼" "subEncrypt" = "編碼"

View file

@ -243,8 +243,9 @@ reset_user() {
gen_random_string() { gen_random_string() {
local length="$1" local length="$1"
local random_string=$(LC_ALL=C tr -dc 'a-zA-Z0-9' </dev/urandom | fold -w "$length" | head -n 1) openssl rand -base64 $(( length * 2 )) \
echo "$random_string" | tr -dc 'a-zA-Z0-9' \
| head -c "$length"
} }
reset_webbasepath() { reset_webbasepath() {