diff --git a/backend/api/api.go b/backend/api/api.go
index da0dc4c..a883947 100644
--- a/backend/api/api.go
+++ b/backend/api/api.go
@@ -15,6 +15,7 @@ type APIHandler struct {
service.UserService
service.ConfigService
service.ClientService
+ service.TlsService
service.PanelService
service.StatsService
service.ServerService
@@ -159,7 +160,7 @@ func (a *APIHandler) getHandler(c *gin.Context) {
func (a *APIHandler) loadData(c *gin.Context) (string, error) {
var data string
lu := c.Query("lu")
- isUpdated, err := a.ConfigService.CheckChnages(lu)
+ isUpdated, err := a.ConfigService.CheckChanges(lu)
if err != nil {
return "", err
}
@@ -176,11 +177,15 @@ func (a *APIHandler) loadData(c *gin.Context) (string, error) {
if err != nil {
return "", err
}
+ tlsConfigs, err := a.TlsService.GetAll()
+ if err != nil {
+ return "", err
+ }
subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0])
if err != nil {
return "", err
}
- data = fmt.Sprintf(`{"config": %s,"clients": %s,"subURI": "%s", "onlines": %s}`, string(*config), clients, subURI, onlines)
+ data = fmt.Sprintf(`{"config": %s, "clients": %s, "tls": %s, "subURI": "%s", "onlines": %s}`, string(*config), clients, tlsConfigs, subURI, onlines)
} else {
data = fmt.Sprintf(`{"onlines": %s}`, onlines)
}
diff --git a/backend/database/db.go b/backend/database/db.go
index a6b1d96..efbe99b 100644
--- a/backend/database/db.go
+++ b/backend/database/db.go
@@ -54,6 +54,7 @@ func InitDB(dbPath string) error {
err = db.AutoMigrate(
&model.Setting{},
+ &model.Tls{},
&model.User{},
&model.Stats{},
&model.Client{},
diff --git a/backend/database/model/model.go b/backend/database/model/model.go
index 8e42549..9f06842 100644
--- a/backend/database/model/model.go
+++ b/backend/database/model/model.go
@@ -8,6 +8,14 @@ type Setting struct {
Value string `json:"value" form:"value"`
}
+type Tls struct {
+ Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
+ Name string `json:"name" form:"name"`
+ Inbounds json.RawMessage `json:"inbounds" form:"inbounds"`
+ Server json.RawMessage `json:"server" form:"server"`
+ Client json.RawMessage `json:"client" form:"client"`
+}
+
type User struct {
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
Username string `json:"username" form:"username"`
diff --git a/backend/service/config.go b/backend/service/config.go
index 0816afb..d30c0cf 100644
--- a/backend/service/config.go
+++ b/backend/service/config.go
@@ -16,6 +16,7 @@ var LastUpdate int64
type ConfigService struct {
ClientService
+ TlsService
singbox.Controller
SettingService
}
@@ -67,13 +68,19 @@ func (s *ConfigService) GetConfig() (*[]byte, error) {
func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error {
var err error
- var clientChanges, settingChanges, configChanges []model.Changes
+ var clientChanges, tlsChanges, settingChanges, configChanges []model.Changes
if _, ok := changes["clients"]; ok {
err = json.Unmarshal([]byte(changes["clients"]), &clientChanges)
if err != nil {
return err
}
}
+ if _, ok := changes["tls"]; ok {
+ err = json.Unmarshal([]byte(changes["tls"]), &tlsChanges)
+ if err != nil {
+ return err
+ }
+ }
if _, ok := changes["settings"]; ok {
err = json.Unmarshal([]byte(changes["settings"]), &settingChanges)
if err != nil {
@@ -103,6 +110,12 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
return err
}
}
+ if len(tlsChanges) > 0 {
+ err = s.TlsService.Save(tx, tlsChanges)
+ if err != nil {
+ return err
+ }
+ }
if len(settingChanges) > 0 {
err = s.SettingService.Save(tx, settingChanges)
if err != nil {
@@ -169,7 +182,7 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
// Log changes
dt := time.Now().Unix()
- allChanges := append(append(clientChanges, settingChanges...), configChanges...)
+ allChanges := append(append(clientChanges, settingChanges...), append(configChanges, tlsChanges...)...)
for index := range allChanges {
allChanges[index].DateTime = dt
allChanges[index].Actor = loginUser
diff --git a/backend/service/tls.go b/backend/service/tls.go
new file mode 100644
index 0000000..578aef1
--- /dev/null
+++ b/backend/service/tls.go
@@ -0,0 +1,49 @@
+package service
+
+import (
+ "encoding/json"
+ "s-ui/database"
+ "s-ui/database/model"
+
+ "gorm.io/gorm"
+)
+
+type TlsService struct {
+}
+
+func (s *TlsService) GetAll() (string, error) {
+ db := database.GetDB()
+ tlsConfig := []model.Tls{}
+ err := db.Model(model.Tls{}).Scan(&tlsConfig).Error
+ if err != nil {
+ return "", err
+ }
+ data, err := json.Marshal(tlsConfig)
+ if err != nil {
+ return "", err
+ }
+ return string(data), nil
+}
+
+func (s *TlsService) Save(tx *gorm.DB, changes []model.Changes) error {
+ var err error
+ for _, change := range changes {
+ tlsConfig := model.Tls{}
+ err = json.Unmarshal(change.Obj, &tlsConfig)
+ if err != nil {
+ return err
+ }
+ switch change.Action {
+ case "new":
+ err = tx.Create(&tlsConfig).Error
+ case "del":
+ err = tx.Where("id = ?", change.Index).Delete(model.Tls{}).Error
+ default:
+ err = tx.Save(tlsConfig).Error
+ }
+ if err != nil {
+ return err
+ }
+ }
+ return err
+}
diff --git a/frontend/src/components/Acme.vue b/frontend/src/components/Acme.vue
new file mode 100644
index 0000000..9fcc3d3
--- /dev/null
+++ b/frontend/src/components/Acme.vue
@@ -0,0 +1,252 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('tls.acme.options') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/Ech.vue b/frontend/src/components/Ech.vue
new file mode 100644
index 0000000..1c0edb9
--- /dev/null
+++ b/frontend/src/components/Ech.vue
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('tls.usePath') }}
+ {{ $t('tls.useText') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/InTLS.vue b/frontend/src/components/InTLS.vue
index 09cb00b..87732a4 100644
--- a/frontend/src/components/InTLS.vue
+++ b/frontend/src/components/InTLS.vue
@@ -1,11 +1,20 @@
-
-
+
+
+
+
+
+
-
+
-
+
-
- {{ $t('tls.options') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ {{ $t('tls.options') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -136,11 +145,11 @@
\ No newline at end of file
diff --git a/frontend/src/locales/en.ts b/frontend/src/locales/en.ts
index 67808b5..18cb2a9 100644
--- a/frontend/src/locales/en.ts
+++ b/frontend/src/locales/en.ts
@@ -21,6 +21,7 @@ export default {
invalidLogin: "Invalid Login!",
online: "Online",
version: "Version",
+ email: "Email",
commaSeparated: "(comma separated)",
error: {
dplData: "Duplicate Data",
@@ -32,6 +33,7 @@ export default {
outbounds: "Outbounds",
clients: "Clients",
rules: "Rules",
+ tls: "TLS Settings",
basics: "Basics",
admins: "Admins",
settings: "Settings",
@@ -326,9 +328,26 @@ export default {
minVer: "Minimum Version",
maxVer: "Maximum Version",
cs: "Cipher suits",
+ privKey: "Private Key",
pubKey: "Public Key",
disableSni: "Disable SNI",
insecure: "Allow Insecure",
+ acme: {
+ options: "ACME Options",
+ dataDir: "Data Directory",
+ defaultDomain: "Default Domain",
+ disableChallenges: "Disable Challenges",
+ httpChallenge: "Disable HTTP Challenge",
+ tlsChallenge: "Disable TLS Challenge",
+ altPorts: "Alternative Ports",
+ altHport: "Alternative HTTP Port",
+ altTport: "Alternative TLS Port",
+ caProvider: "CA Provider",
+ customCa: "Custom CA Provider",
+ extAcc: "External Account",
+ dns01: "DNS01 Challenge",
+ dns01Provider: "DNS01 Challenge Provider",
+ },
},
stats: {
upload: "Upload",
diff --git a/frontend/src/locales/fa.ts b/frontend/src/locales/fa.ts
index a1bcaa7..1d8f063 100644
--- a/frontend/src/locales/fa.ts
+++ b/frontend/src/locales/fa.ts
@@ -21,6 +21,7 @@ export default {
invalidLogin: "ورود نامعتبر!",
online: "آنلاین",
version: "نسخه",
+ email: "ایمیل",
commaSeparated: "(جداشده با کاما)",
error: {
dplData: "داده تکراری",
@@ -32,6 +33,7 @@ export default {
outbounds: "خروجیها",
clients: "کاربران",
rules: "قوانین",
+ tls: "رمزنگاریها",
basics: "ترازها",
admins: "ادمینها",
settings: "پیکربندی",
@@ -325,9 +327,26 @@ export default {
minVer: "کمینه نسخه",
maxVer: "بیشینه نسخه",
cs: "مدلهای رمزنگاری",
+ privKey: "کلید خصوصی",
pubKey: "کلید عمومی",
disableSni: "غیرفعالسازی SNI",
insecure: "تایید ارتباط ناامن",
+ acme: {
+ options: "گزینههای ACME",
+ dataDir: "مسیر دادهها",
+ defaultDomain: "دامنه پیشفرض",
+ disableChallenges: "بستن چالشها",
+ httpChallenge: "بستن چالش HTTP",
+ tlsChallenge: "بستن چالش TLS",
+ altPorts: "پورتهای جایگزین",
+ altHport: "پورت جایگزین HTTP",
+ altTport: "پورت جایگزین TLS",
+ caProvider: "فراهم کننده گواهی",
+ customCa: "فراهم کننده دیگر",
+ extAcc: "حساب خارجی",
+ dns01: "چالش DNS01",
+ dns01Provider: "فراهم کننده چالش DNS01",
+ },
},
stats: {
upload: "آپلود",
diff --git a/frontend/src/locales/vi.ts b/frontend/src/locales/vi.ts
index ec98c8d..5cf95fb 100644
--- a/frontend/src/locales/vi.ts
+++ b/frontend/src/locales/vi.ts
@@ -21,6 +21,7 @@ export default {
invalidLogin: "Đăng nhập không hợp lệ!",
online: "Trực tuyến",
version: "Phiên bản",
+ email: "Email",
commaSeparated: "(được phân tách bằng dấu phẩy)",
error: {
dplData: "Dữ liệu trùng lặp",
@@ -32,6 +33,7 @@ export default {
outbounds: "Đầu ra",
clients: "Khách hàng",
rules: "Quy tắc",
+ tls: "Cài đặt TLS",
basics: "Cơ bản",
admins: "Quản trị viên",
settings: "Cài đặt",
@@ -327,9 +329,26 @@ export default {
minVer: "Phiên bản Tối thiểu",
maxVer: "Phiên bản Tối đa",
cs: "Các bộ mã hóa",
+ privKey: "Khóa riêng",
pubKey: "Khóa Công khai",
disableSni: "Tắt SNI",
insecure: "Cho phép Không an toàn",
+ acme: {
+ options: "Tùy chọn ACME",
+ dataDir: "Thư mục Dữ liệu",
+ defaultDomain: "Tên miền Mặc định",
+ disableChallenges: "Vô hiệu hóa Thách thức",
+ httpChallenge: "Vô hiệu hóa Thách thức HTTP",
+ tlsChallenge: "Vô hiệu hóa Thách thức TLS",
+ altPorts: "Cổng Thay thế",
+ altHport: "Cổng HTTP Thay thế",
+ altTport: "Cổng TLS Thay thế",
+ caProvider: "Nhà cung cấp CA",
+ customCa: "Nhà cung cấp CA Tùy chỉnh",
+ extAcc: "Tài khoản Bên ngoài",
+ dns01: "Thách thức DNS01",
+ dns01Provider: "Nhà cung cấp Thách thức DNS01"
+ },
},
stats: {
upload: "Tải lên",
diff --git a/frontend/src/locales/zhcn.ts b/frontend/src/locales/zhcn.ts
index 0b2cdad..30dcb18 100644
--- a/frontend/src/locales/zhcn.ts
+++ b/frontend/src/locales/zhcn.ts
@@ -21,6 +21,7 @@ export default {
invalidLogin: "登录无效!",
online: "在线",
version: "版本",
+ email: "电子邮件",
commaSeparated: "(逗号分隔)",
error: {
dplData: "重复数据",
@@ -32,6 +33,7 @@ export default {
outbounds: "出站管理",
clients: "用户管理",
rules: "路由列表",
+ tls: "TLS 设置",
basics: "基础信息",
admins: "管理员",
settings: "设置",
@@ -327,9 +329,26 @@ export default {
minVer: "最低版本",
maxVer: "最高版本",
cs: "密码套件",
+ privKey: "私钥",
pubKey: "公钥",
disableSni: "禁用SNI",
insecure: "允许不安全",
+ acme: {
+ options: "ACME 选项",
+ dataDir: "数据目录",
+ defaultDomain: "默认域名",
+ disableChallenges: "禁用挑战",
+ httpChallenge: "禁用 HTTP 挑战",
+ tlsChallenge: "禁用 TLS 挑战",
+ altPorts: "替代端口",
+ altHport: "替代 HTTP 端口",
+ altTport: "替代 TLS 端口",
+ caProvider: "CA 提供商",
+ customCa: "自定义 CA 提供商",
+ extAcc: "外部账户",
+ dns01: "DNS01 挑战",
+ dns01Provider: "DNS01 挑战提供商"
+ },
},
stats: {
upload: "上传",
diff --git a/frontend/src/locales/zhtw.ts b/frontend/src/locales/zhtw.ts
index da2bfdc..75bfbcf 100644
--- a/frontend/src/locales/zhtw.ts
+++ b/frontend/src/locales/zhtw.ts
@@ -22,6 +22,7 @@ export default {
invalidLogin: "登錄無效!",
online: "在線",
version: "版本",
+ email: "電子郵件",
commaSeparated: "(逗號分隔)",
error: {
dplData: "重複數據",
@@ -33,6 +34,7 @@ export default {
outbounds: "出站管理",
clients: "用戶管理",
rules: "路由列表",
+ tls: "TLS 設置",
basics: "基礎信息",
admins: "管理員",
settings: "設置",
@@ -328,9 +330,26 @@ export default {
minVer: "最低版本",
maxVer: "最高版本",
cs: "加密套件",
+ privKey: "私鑰",
pubKey: "公鑰",
disableSni: "停用 SNI",
insecure: "允許不安全連線",
+ acme: {
+ options: "ACME 選項",
+ dataDir: "數據目錄",
+ defaultDomain: "默認域名",
+ disableChallenges: "禁用挑戰",
+ httpChallenge: "禁用 HTTP 挑戰",
+ tlsChallenge: "禁用 TLS 挑戰",
+ altPorts: "替代端口",
+ altHport: "替代 HTTP 端口",
+ altTport: "替代 TLS 端口",
+ caProvider: "CA 提供商",
+ customCa: "自定義 CA 提供商",
+ extAcc: "外部賬戶",
+ dns01: "DNS01 挑戰",
+ dns01Provider: "DNS01 挑戰提供商"
+ },
},
stats: {
upload: "上傳",
diff --git a/frontend/src/plugins/link.ts b/frontend/src/plugins/link.ts
index 3e98b90..c8f576e 100644
--- a/frontend/src/plugins/link.ts
+++ b/frontend/src/plugins/link.ts
@@ -1,5 +1,5 @@
import { Hysteria, Hysteria2, InTypes, Inbound, Naive, Shadowsocks, TUIC, Trojan, VLESS, VMess } from "@/types/inbounds"
-import { HTTP, WebSocket, QUIC, gRPC, HTTPUpgrade, Transport, TrspTypes } from "@/types/transport";
+import { HTTP, WebSocket, gRPC, HTTPUpgrade, Transport, TrspTypes } from "@/types/transport";
export interface Link {
type: "local" | "external" | "sub"
@@ -13,25 +13,25 @@ function utf8ToBase64(utf8String: string): string {
}
export namespace LinkUtil {
- export function linkGenerator(user: string, inbound: Inbound): string {
+ export function linkGenerator(user: string, inbound: Inbound, tlsClient: any = null): string {
const addr = location.hostname
switch(inbound.type){
case InTypes.Shadowsocks:
- return shadowsocksLink(user,inbound,addr)
+ return shadowsocksLink(user,inbound, addr)
case InTypes.Naive:
- return naiveLink(user,inbound,addr)
+ return naiveLink(user,inbound, addr, tlsClient)
case InTypes.Hysteria:
- return hysteriaLink(user,inbound,addr)
+ return hysteriaLink(user,inbound, addr, tlsClient)
case InTypes.Hysteria2:
- return hysteria2Link(user,inbound,addr)
+ return hysteria2Link(user,inbound, addr, tlsClient)
case InTypes.TUIC:
- return tuicLink(user,inbound,addr)
+ return tuicLink(user,inbound, addr, tlsClient)
case InTypes.VLESS:
- return vlessLink(user,inbound,addr)
+ return vlessLink(user,inbound, addr, tlsClient)
case InTypes.Trojan:
- return trojanLink(user,inbound,addr)
+ return trojanLink(user,inbound, addr, tlsClient)
case InTypes.VMess:
- return vmessLink(user,inbound,addr)
+ return vmessLink(user,inbound, addr, tlsClient)
}
return ''
}
@@ -40,7 +40,6 @@ export namespace LinkUtil {
const userPass = inbound.users?.find(i => i.name == user)?.password
const password = [userPass]
if (inbound.method.startsWith('2022')) password.push(inbound.password)
-
const params = {
tfo: inbound.tcp_fast_open? 1 : null,
network: inbound.network?? null
@@ -56,7 +55,7 @@ export namespace LinkUtil {
return uri.toString()
}
- function hysteriaLink(user: string, inbound: Hysteria, addr: string): string {
+ function hysteriaLink(user: string, inbound: Hysteria, addr: string, tlsClient: any): string {
const auth = inbound.users.find(i => i.name == user)?.auth_str
const params = {
upmbps: inbound.up_mbps?? null,
@@ -65,7 +64,8 @@ export namespace LinkUtil {
peer: inbound.tls.server_name?? null,
alpn: inbound.tls.alpn?.join(',')?? null,
obfsParam: inbound.obfs?? null,
- fastopen: inbound.tcp_fast_open? 1 : 0
+ fastopen: inbound.tcp_fast_open? 1 : 0,
+ insecure: tlsClient?.insecure ? 1 : null
}
const uri = new URL(`hysteria://${addr}:${inbound.listen_port}`)
for (const [key, value] of Object.entries(params)){
@@ -77,7 +77,7 @@ export namespace LinkUtil {
return uri.toString()
}
- function hysteria2Link(user: string, inbound: Hysteria2, addr: string): string {
+ function hysteria2Link(user: string, inbound: Hysteria2, addr: string, tlsClient: any): string {
const password = inbound.users.find(i => i.name == user)?.password
const params = {
upmbps: inbound.up_mbps?? null,
@@ -86,7 +86,8 @@ export namespace LinkUtil {
alpn: inbound.tls.alpn?.join(',')?? null,
obfs: inbound.obfs?.type?? null,
'obfs-password': inbound.obfs?.password?? null,
- fastopen: inbound.tcp_fast_open? 1 : 0
+ fastopen: inbound.tcp_fast_open? 1 : 0,
+ insecure: tlsClient?.insecure ? 1 : null
}
const uri = new URL(`hysteria2://${password}@${addr}:${inbound.listen_port}`)
for (const [key, value] of Object.entries(params)){
@@ -98,13 +99,14 @@ export namespace LinkUtil {
return uri.toString()
}
- function naiveLink(user: string, inbound: Naive, addr: string): string {
+ function naiveLink(user: string, inbound: Naive, addr: string, tlsClient: any): string {
const password = inbound.users.find(i => i.username == user)?.password
const params = {
padding: 1,
peer: inbound.tls.server_name?? null,
alpn: inbound.tls.alpn?.join(',')?? null,
- tfo: inbound.tcp_fast_open? 1 : 0
+ tfo: inbound.tcp_fast_open? 1 : 0,
+ allowInsecure: tlsClient?.insecure ? 1 : null
}
const uri = `http2://${utf8ToBase64(user + ":" + password + "@" + addr + ":" + inbound.listen_port)}`
const paramsArray = []
@@ -116,12 +118,14 @@ export namespace LinkUtil {
return uri.toString() + "?" + paramsArray.join('&') + "#" + inbound.tag
}
- function tuicLink(user: string, inbound: TUIC, addr: string): string {
+ function tuicLink(user: string, inbound: TUIC, addr: string, tlsClient: any): string {
const u = inbound.users.find(i => i.name == user)
const params = {
sni: inbound.tls.server_name?? null,
alpn: inbound.tls.alpn?.join(',')?? null,
- congestion_control: inbound.congestion_control?? null
+ congestion_control: inbound.congestion_control?? null,
+ allowInsecure: tlsClient?.insecure ? 1 : null,
+ disable_sni: tlsClient?.disable_sni ? 1 : null
}
const uri = new URL(`tuic://${u?.uuid}:${u?.password}@${addr}:${inbound.listen_port}`)
for (const [key, value] of Object.entries(params)){
@@ -166,7 +170,7 @@ export namespace LinkUtil {
return params
}
- function vlessLink(user: string, inbound: VLESS, addr: string): string {
+ function vlessLink(user: string, inbound: VLESS, addr: string, tlsClient: any): string {
const u = inbound.users.find(i => i.name == user)
const transport = inbound.transport
@@ -174,10 +178,14 @@ export namespace LinkUtil {
const params = {
type: transport?.type?? 'tcp',
- security: inbound.tls?.enabled? 'tls' : null,
+ security: inbound.tls?.enabled? inbound.tls?.reality?.enabled ? 'reality' : 'tls' : null,
alpn: inbound.tls?.alpn?.join(',')?? null,
sni: inbound.tls?.server_name?? null,
- flow: inbound.tls?.enabled ? u?.flow?? null : null
+ flow: inbound.tls?.enabled ? u?.flow?? null : null,
+ allowInsecure: tlsClient?.insecure ? 1 : null,
+ fp: tlsClient?.utls?.enabled ? tlsClient.utls.fingerprint : null,
+ pbk: tlsClient?.reality?.public_key?? null,
+ sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[0] : null) : null
}
const uri = new URL(`vless://${u?.uuid}@${addr}:${inbound.listen_port}`)
for (const [key, value] of Object.entries({...params, ...tParams})){
@@ -189,7 +197,7 @@ export namespace LinkUtil {
return uri.toString()
}
- function trojanLink(user: string, inbound: Trojan, addr: string): string {
+ function trojanLink(user: string, inbound: Trojan, addr: string, tlsClient: any): string {
const u = inbound.users.find(i => i.name == user)
const transport = inbound.transport
@@ -197,9 +205,13 @@ export namespace LinkUtil {
const params = {
type: transport?.type?? 'tcp',
- security: inbound.tls?.enabled? 'tls' : null,
+ security: inbound.tls?.enabled? inbound.tls?.reality?.enabled ? 'reality' : 'tls' : null,
alpn: inbound.tls?.alpn?.join(',')?? null,
sni: inbound.tls?.server_name?? null,
+ allowInsecure: tlsClient?.insecure ? 1 : null,
+ fp: tlsClient?.utls?.enabled ? tlsClient.utls.fingerprint : null,
+ pbk: tlsClient?.reality?.public_key?? null,
+ sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[0] : null) : null
}
const uri = new URL(`trojan://${u?.password}@${addr}:${inbound.listen_port}`)
for (const [key, value] of Object.entries({...params, ...tParams})){
@@ -211,7 +223,7 @@ export namespace LinkUtil {
return uri.toString()
}
- function vmessLink(user: string, inbound: VMess, addr: string): string {
+ function vmessLink(user: string, inbound: VMess, addr: string, tlsClient: any): string {
const u = inbound.users.find(i => i.name == user)
const transport = inbound.transport
@@ -224,13 +236,14 @@ export namespace LinkUtil {
aid: u?.alterId,
host: tParams.host?? undefined,
id: u?.uuid,
- net: transport?.type == undefined || transport?.type == 'http' ? 'tcp' : transport.type,
+ net: transport?.type == undefined || transport?.type == 'http' ? 'tcp' : transport.type,
type: transport?.type == 'http' ? 'http' : undefined,
path: tParams.path?? undefined,
port: inbound.listen_port,
ps: inbound.tag,
sni: inbound.tls.server_name?? undefined,
- tls: Object.keys(inbound.tls).length>0? 'tls' : 'none'
+ tls: Object.keys(inbound.tls).length>0? 'tls' : 'none',
+ allowInsecure: tlsClient?.insecure ? 1 : undefined
}
return 'vmess://' + utf8ToBase64(JSON.stringify(params, null, 2))
}
diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts
index bf53188..1f74bab 100644
--- a/frontend/src/router/index.ts
+++ b/frontend/src/router/index.ts
@@ -39,6 +39,11 @@ const routes = [
name: 'pages.rules',
component: () => import('@/views/Rules.vue'),
},
+ {
+ path: '/tls',
+ name: 'pages.tls',
+ component: () => import('@/views/Tls.vue'),
+ },
{
path: '/basics',
name: 'pages.basics',
diff --git a/frontend/src/store/modules/data.ts b/frontend/src/store/modules/data.ts
index c03cf39..3a39b4e 100644
--- a/frontend/src/store/modules/data.ts
+++ b/frontend/src/store/modules/data.ts
@@ -1,7 +1,6 @@
import { FindDiff } from '@/plugins/utils'
import HttpUtils from '@/plugins/httputil'
import { defineStore } from 'pinia'
-import { onMounted } from 'vue'
const Data = defineStore('Data', {
state: () => ({
@@ -9,9 +8,10 @@ const Data = defineStore('Data', {
reloadItems: localStorage.getItem("reloadItems")?.split(',')?? [],
subURI: "",
onlines: {inbound: [], outbound: [], user: []},
- oldData: <{config: any, clients: any[]}>{},
+ oldData: <{config: any, clients: any[], tlsConfigs: any[]}>{},
config: {},
clients: [],
+ tlsConfigs: [],
}),
actions: {
async loadData() {
@@ -21,20 +21,23 @@ const Data = defineStore('Data', {
// Set new data
const data = JSON.parse(msg.obj)
+ if (data.subURI) this.subURI = data.subURI
if (data.config) this.config = data.config
if (data.clients) this.clients = data.clients
- if (data.subURI) this.subURI = data.subURI
+ if (data.tls) this.tlsConfigs = data.tls
this.onlines = data.onlines
// To avoid ref copy
if (data.config) this.oldData.config = { ...JSON.parse(msg.obj).config }
if (data.clients) this.oldData.clients = [ ...JSON.parse(msg.obj).clients ]
+ if (data.tls) this.oldData.tlsConfigs = [ ...JSON.parse(msg.obj).tls ]
}
},
async pushData() {
const diff = {
config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config)),
clients: JSON.stringify(FindDiff.Clients(this.clients,this.oldData.clients)),
+ tls: JSON.stringify(FindDiff.Clients(this.tlsConfigs,this.oldData.tlsConfigs)),
}
const msg = await HttpUtils.post('api/save',diff)
if(msg.success) {
@@ -45,6 +48,7 @@ const Data = defineStore('Data', {
const diff = {
config: JSON.stringify([{key: "inbounds", action: "del", index: index, obj: null}]),
clients: JSON.stringify(FindDiff.Clients(this.clients,this.oldData.clients)),
+ tls: JSON.stringify(FindDiff.Clients(this.tlsConfigs,this.oldData.tlsConfigs)),
}
const msg = await HttpUtils.post('api/save',diff)
if(msg.success) {
@@ -69,6 +73,15 @@ const Data = defineStore('Data', {
if(msg.success) {
this.loadData()
}
+ },
+ async delTls(id: number) {
+ const diff = {
+ tls:JSON.stringify([{key: "tls", action: "del", index: id, obj: null}]),
+ }
+ const msg = await HttpUtils.post('api/save',diff)
+ if(msg.success) {
+ this.loadData()
+ }
}
},
})
diff --git a/frontend/src/types/inTls.ts b/frontend/src/types/inTls.ts
index 6072b88..a0114d5 100644
--- a/frontend/src/types/inTls.ts
+++ b/frontend/src/types/inTls.ts
@@ -1,3 +1,5 @@
+import { Dial } from "./dial"
+
export interface iTls {
enabled?: boolean
server_name?: string
@@ -9,6 +11,50 @@ export interface iTls {
certificate_path?: string
key?: string[]
key_path?: string
+ acme?: acme
+ ech?: ech
+ reality?: reality
+}
+
+export interface acme {
+ domain: string[]
+ data_directory?: string
+ default_server_name?: string
+ email?: string
+ provider?: string
+ disable_http_challenge?: boolean
+ disable_tls_alpn_challenge?: boolean
+ alternative_http_port?: number
+ alternative_tls_port?: number
+ external_account?: {
+ key_id: string
+ mac_key: string
+ }
+ dns01_challenge?: {
+ provider: string
+ [key: string]: string
+ }
+}
+
+export interface ech {
+ enabled: boolean
+ pq_signature_schemes_enabled?: boolean
+ dynamic_record_sizing_disabled?: boolean
+ key?: string[]
+ key_path?: string
+}
+
+interface realityHanshake extends Dial {
+ server: string
+ server_port: number
+}
+
+export interface reality {
+ enabled: boolean
+ handshake: realityHanshake
+ private_key: string
+ short_id: string[]
+ max_time_difference?: string
}
export const defaultInTls: iTls = {
diff --git a/frontend/src/views/Clients.vue b/frontend/src/views/Clients.vue
index cd4bb71..5abd3ea 100644
--- a/frontend/src/views/Clients.vue
+++ b/frontend/src/views/Clients.vue
@@ -261,7 +261,8 @@ const updateLinks = (c:Client):string => {
const clientInbounds = inbounds.value.filter(i => c.inbounds.split(',').includes(i.tag))
const newLinks = []
clientInbounds.forEach(i =>{
- const uri = LinkUtil.linkGenerator(c.name,i)
+ const tlsConfig = Data().tlsConfigs?.findLast((t:any) => t.inbounds.includes(i.tag))
+ const uri = LinkUtil.linkGenerator(c.name,i,tlsConfig?.client)
if (uri.length>0){
newLinks.push({ type: 'local', remark: i.tag, uri: uri })
}
diff --git a/frontend/src/views/Inbounds.vue b/frontend/src/views/Inbounds.vue
index 1978d7a..088770f 100644
--- a/frontend/src/views/Inbounds.vue
+++ b/frontend/src/views/Inbounds.vue
@@ -7,6 +7,7 @@
:data="modal.data"
:inTags="inTags"
:outTags="outTags"
+ :tlsConfigs="tlsConfigs"
@close="closeModal"
@save="saveModal"
/>
@@ -119,6 +120,10 @@ const inbounds = computed((): Inbound[] => {
return appConfig.value.inbounds
})
+const tlsConfigs = computed((): any[] => {
+ return Data().tlsConfigs
+})
+
const inTags = computed((): string[] => {
return inbounds.value?.map(i => i.tag)
})
@@ -157,7 +162,7 @@ const showModal = (id: number) => {
const closeModal = () => {
modal.value.visible = false
}
-const saveModal = (data:Inbound, stats: boolean) => {
+const saveModal = (data:Inbound, stats: boolean, tls_id: number) => {
// Check duplicate tag
const oldTag = modal.value.id != -1 ? inbounds.value[modal.value.id].tag : null
if (data.tag != oldTag && inTags.value.includes(data.tag)) {
@@ -165,16 +170,30 @@ const saveModal = (data:Inbound, stats: boolean) => {
sb.showMessage(i18n.global.t('error.dplData') + ': ' + i18n.global.t('objects.tag') ,'error', 5000)
return
}
+
// New or Edit
if (modal.value.id == -1) {
inbounds.value.push(data)
if (stats && data.tag.length>0) {
v2rayStats.value.inbounds.push(data.tag)
}
+ // Update tls preset
+ if (tls_id>0) {
+ tlsConfigs.value.findLast(t => t.id == tls_id).inbounds.push(data.tag)
+ }
} else {
const oldTag = inbounds.value[modal.value.id].tag
const sIndex = v2rayStats.value.inbounds.findIndex(i => i == data.tag) // Find if new tag exists
+ // Update tls preset
+ const oldTlsConfigIndex = tlsConfigs?.value.findIndex(t => t.inbounds?.includes(oldTag))
+ if (oldTlsConfigIndex != -1){
+ tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != oldTag)
+ }
+ if (tls_id>0) {
+ tlsConfigs.value.findLast(t => t.id == tls_id).inbounds.push(data.tag)
+ }
+
if (oldTag != data.tag) {
v2rayStats.value.inbounds = v2rayStats.value.inbounds.filter(item => item != oldTag)
changeClientInboundsTag(oldTag,data.tag)
@@ -206,7 +225,8 @@ const updateLinks = (i: InboundWithUser) => {
const clientInbounds = inbounds.value.filter(inb => client?.inbounds.split(',').includes(inb.tag))
const newLinks = []
clientInbounds.forEach(i =>{
- const uri = LinkUtil.linkGenerator(client.name,i)
+ const tlsClient = tlsConfigs?.value.findLast((t:any) => t.inbounds.includes(i.tag))?.client?? null
+ const uri = LinkUtil.linkGenerator(client.name,i, tlsClient)
if (uri.length>0){
newLinks.push({ type: 'local', remark: i.tag, uri: uri })
}
@@ -224,7 +244,7 @@ const delInbound = (index: number) => {
inbounds.value.splice(index,1)
const tag = inb.tag
- if (Object.hasOwn(inb,'users')){
+ if (Object.hasOwn(inb,'users')) {
const inbU = inb
if (inbU.users && inbU.users.length>0){
inbU.users.forEach((u:any) => {
@@ -237,6 +257,13 @@ const delInbound = (index: number) => {
}
}
+ // Delete binded tls if exists
+ if (Object.hasOwn(inb,'tls')) {
+ const oldTlsConfigIndex = tlsConfigs?.value.findIndex(t => t.inbounds?.includes(inb.tag))
+ if (oldTlsConfigIndex != -1){
+ tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != inb.tag)
+ }
+ }
// Delete stats if exists and will be orphaned
const tagCounts = inbounds.value.filter(i => i.tag == inb.tag).length
diff --git a/frontend/src/views/Tls.vue b/frontend/src/views/Tls.vue
new file mode 100644
index 0000000..030648e
--- /dev/null
+++ b/frontend/src/views/Tls.vue
@@ -0,0 +1,160 @@
+
+
+
+
+ {{ $t('actions.add') }}
+
+
+
+
+
+
+ {{ item.server?.server_name?.length>0 ? item.server.server_name : "-" }}
+
+
+
+ {{ $t('pages.inbounds') }}
+
+
+ {{ i }}
+
+ {{ item.inbounds?.length }}
+
+
+
+ ACME
+
+ {{ $t(item.server?.acme == undefined ? 'no' : 'yes') }}
+
+
+
+ ECH
+
+ {{ $t(item.server?.ech == undefined ? 'no' : 'yes') }}
+
+
+
+ Reality
+
+ {{ $t(item.server?.reality == undefined ? 'no' : 'yes') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('confirm') }}
+
+ {{ $t('yes') }}
+ {{ $t('no') }}
+
+
+
+
+
+
+
+
+
+