separate frontend repository

This commit is contained in:
Alireza Ahmadi
2025-01-28 23:57:53 +01:00
parent f4b1b09362
commit 7faa28a89d
194 changed files with 48 additions and 19256 deletions
+371
View File
@@ -0,0 +1,371 @@
package service
import (
"encoding/json"
"s-ui/database"
"s-ui/database/model"
"s-ui/logger"
"s-ui/util"
"s-ui/util/common"
"strings"
"time"
"gorm.io/gorm"
)
type ClientService struct {
InboundService
}
func (s *ClientService) Get(id string) (*[]model.Client, error) {
if id == "" {
return s.GetAll()
}
return s.getById(id)
}
func (s *ClientService) getById(id string) (*[]model.Client, error) {
db := database.GetDB()
var client []model.Client
err := db.Model(model.Client{}).Where("id in ?", strings.Split(id, ",")).Scan(&client).Error
if err != nil {
return nil, err
}
return &client, nil
}
func (s *ClientService) GetAll() (*[]model.Client, error) {
db := database.GetDB()
var clients []model.Client
err := db.Model(model.Client{}).Select("`id`, `enable`, `name`, `desc`, `group`, `inbounds`, `up`, `down`, `volume`, `expiry`").Scan(&clients).Error
if err != nil {
return nil, err
}
return &clients, nil
}
func (s *ClientService) Save(tx *gorm.DB, act string, data json.RawMessage, hostname string) ([]uint, error) {
var err error
var inboundIds []uint
switch act {
case "new", "edit":
var client model.Client
err = json.Unmarshal(data, &client)
if err != nil {
return nil, err
}
err = json.Unmarshal(client.Inbounds, &inboundIds)
if err != nil {
return nil, err
}
err = s.updateLinksWithFixedInbounds(tx, []*model.Client{&client}, inboundIds, hostname)
if err != nil {
return nil, err
}
err = tx.Save(&client).Error
if err != nil {
return nil, err
}
case "addbulk":
var clients []*model.Client
err = json.Unmarshal(data, &clients)
if err != nil {
return nil, err
}
err = json.Unmarshal(clients[0].Inbounds, &inboundIds)
if err != nil {
return nil, err
}
err = s.updateLinksWithFixedInbounds(tx, clients, inboundIds, hostname)
if err != nil {
return nil, err
}
err = tx.Save(clients).Error
if err != nil {
return nil, err
}
case "del":
var id uint
err = json.Unmarshal(data, &id)
if err != nil {
return nil, err
}
var client model.Client
err = tx.Where("id = ?", id).First(&client).Error
if err != nil {
return nil, err
}
err = json.Unmarshal(client.Inbounds, &inboundIds)
if err != nil {
return nil, err
}
err = tx.Where("id = ?", id).Delete(model.Client{}).Error
if err != nil {
return nil, err
}
default:
return nil, common.NewErrorf("unknown action: %s", act)
}
return inboundIds, nil
}
func (s *ClientService) updateLinksWithFixedInbounds(tx *gorm.DB, clients []*model.Client, inbounIds []uint, hostname string) error {
var err error
var inbounds []model.Inbound
// Zero inbounds means removing local links only
if len(inbounIds) > 0 {
err = tx.Model(model.Inbound{}).Preload("Tls").Where("id in ? and type in ?", inbounIds, util.InboundTypeWithLink).Find(&inbounds).Error
if err != nil {
return err
}
}
for index, client := range clients {
var clientLinks []map[string]string
err = json.Unmarshal(client.Links, &clientLinks)
if err != nil {
return err
}
newClientLinks := []map[string]string{}
for _, inbound := range inbounds {
newLinks := util.LinkGenerator(client.Config, &inbound, hostname)
for _, newLink := range newLinks {
newClientLinks = append(newClientLinks, map[string]string{
"remark": inbound.Tag,
"type": "local",
"uri": newLink,
})
}
}
// Add no local links
for _, clientLink := range clientLinks {
if clientLink["type"] != "local" {
newClientLinks = append(newClientLinks, clientLink)
}
}
clients[index].Links, err = json.MarshalIndent(newClientLinks, "", " ")
if err != nil {
return err
}
}
return nil
}
func (s *ClientService) UpdateClientsOnInboundAdd(tx *gorm.DB, initIds string, inboundId uint, hostname string) error {
clientIds := strings.Split(initIds, ",")
var clients []model.Client
err := tx.Model(model.Client{}).Where("id in ?", clientIds).Find(&clients).Error
if err != nil {
return err
}
var inbound model.Inbound
err = tx.Model(model.Inbound{}).Preload("Tls").Where("id = ?", inboundId).Find(&inbound).Error
if err != nil {
return err
}
for _, client := range clients {
// Add inbounds
var clientInbounds []uint
json.Unmarshal(client.Inbounds, &clientInbounds)
clientInbounds = append(clientInbounds, inboundId)
client.Inbounds, err = json.MarshalIndent(clientInbounds, "", " ")
if err != nil {
return err
}
// Add links
var clientLinks, newClientLinks []map[string]string
json.Unmarshal(client.Links, &clientLinks)
newLinks := util.LinkGenerator(client.Config, &inbound, hostname)
for _, newLink := range newLinks {
newClientLinks = append(newClientLinks, map[string]string{
"remark": inbound.Tag,
"type": "local",
"uri": newLink,
})
}
for _, clientLink := range clientLinks {
if clientLink["remark"] != inbound.Tag {
newClientLinks = append(newClientLinks, clientLink)
}
}
client.Links, err = json.MarshalIndent(newClientLinks, "", " ")
if err != nil {
return err
}
err = tx.Save(&client).Error
if err != nil {
return err
}
}
return nil
}
func (s *ClientService) UpdateClientsOnInboundDelete(tx *gorm.DB, id uint, tag string) error {
var clients []model.Client
err := tx.Table("clients").
Where("EXISTS (SELECT 1 FROM json_each(clients.inbounds) WHERE json_each.value = ?)", id).
Find(&clients).Error
if err != nil {
return err
}
for _, client := range clients {
// Delete inbounds
var clientInbounds, newClientInbounds []uint
json.Unmarshal(client.Inbounds, &clientInbounds)
for _, clientInbound := range clientInbounds {
if clientInbound != id {
newClientInbounds = append(newClientInbounds, clientInbound)
}
}
client.Inbounds, err = json.MarshalIndent(newClientInbounds, "", " ")
if err != nil {
return err
}
// Delete links
var clientLinks, newClientLinks []map[string]string
json.Unmarshal(client.Links, &clientLinks)
for _, clientLink := range clientLinks {
if clientLink["remark"] != tag {
newClientLinks = append(newClientLinks, clientLink)
}
}
client.Links, err = json.MarshalIndent(newClientLinks, "", " ")
if err != nil {
return err
}
err = tx.Save(&client).Error
if err != nil {
return err
}
}
return nil
}
func (s *ClientService) UpdateLinksByInboundChange(tx *gorm.DB, inbounIds []uint, hostname string) error {
var inbounds []model.Inbound
err := tx.Model(model.Inbound{}).Preload("Tls").Where("id in ? and type in ?", inbounIds, util.InboundTypeWithLink).Find(&inbounds).Error
if err != nil && database.IsNotFound(err) {
return err
}
for _, inbound := range inbounds {
var clients []model.Client
err = tx.Table("clients").
Where("EXISTS (SELECT 1 FROM json_each(clients.inbounds) WHERE json_each.value = ?)", inbound.Id).
Find(&clients).Error
if err != nil {
return err
}
for _, client := range clients {
var clientLinks, newClientLinks []map[string]string
json.Unmarshal(client.Links, &clientLinks)
newLinks := util.LinkGenerator(client.Config, &inbound, hostname)
for _, newLink := range newLinks {
newClientLinks = append(newClientLinks, map[string]string{
"remark": inbound.Tag,
"type": "local",
"uri": newLink,
})
}
for _, clientLink := range clientLinks {
if clientLink["remark"] != inbound.Tag {
newClientLinks = append(newClientLinks, clientLink)
}
}
client.Links, err = json.MarshalIndent(newClientLinks, "", " ")
if err != nil {
return err
}
err = tx.Save(&client).Error
if err != nil {
return err
}
}
}
return nil
}
func (s *ClientService) DepleteClients() error {
var err error
var clients []model.Client
var changes []model.Changes
var users []string
var inboundIds []uint
now := time.Now().Unix()
db := database.GetDB()
tx := db.Begin()
defer func() {
if err == nil {
tx.Commit()
if len(inboundIds) > 0 && corePtr.IsRunning() {
err1 := s.InboundService.RestartInbounds(db, inboundIds)
if err1 != nil {
logger.Error("unable to restart inbounds: ", err1)
}
}
} else {
tx.Rollback()
}
}()
err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Scan(&clients).Error
if err != nil {
return err
}
dt := time.Now().Unix()
for _, client := range clients {
logger.Debug("Client ", client.Name, " is going to be disabled")
users = append(users, client.Name)
var userInbounds []uint
json.Unmarshal(client.Inbounds, &userInbounds)
inboundIds = s.uniqueAppendInboundIds(inboundIds, userInbounds)
changes = append(changes, model.Changes{
DateTime: dt,
Actor: "DepleteJob",
Key: "clients",
Action: "disable",
Obj: json.RawMessage("\"" + client.Name + "\""),
})
}
// Save changes
if len(changes) > 0 {
err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Update("enable", false).Error
if err != nil {
return err
}
err = tx.Model(model.Changes{}).Create(&changes).Error
if err != nil {
return err
}
LastUpdate = dt
}
return nil
}
// avoid duplicate inboundIds
func (s *ClientService) uniqueAppendInboundIds(a []uint, b []uint) []uint {
m := make(map[uint]bool)
for _, v := range a {
m[v] = true
}
for _, v := range b {
m[v] = true
}
var res []uint
for k := range m {
res = append(res, k)
}
return res
}
+264
View File
@@ -0,0 +1,264 @@
package service
import (
"encoding/json"
"s-ui/core"
"s-ui/database"
"s-ui/database/model"
"s-ui/logger"
"s-ui/util/common"
"strconv"
"time"
)
var (
LastUpdate int64
corePtr *core.Core
)
type ConfigService struct {
ClientService
TlsService
SettingService
InboundService
OutboundService
EndpointService
}
type SingBoxConfig struct {
Log json.RawMessage `json:"log"`
Dns json.RawMessage `json:"dns"`
Ntp json.RawMessage `json:"ntp"`
Inbounds []json.RawMessage `json:"inbounds"`
Outbounds []json.RawMessage `json:"outbounds"`
Endpoints []json.RawMessage `json:"endpoints"`
Route json.RawMessage `json:"route"`
Experimental json.RawMessage `json:"experimental"`
}
func NewConfigService(core *core.Core) *ConfigService {
corePtr = core
return &ConfigService{}
}
func (s *ConfigService) GetConfig(data string) (*SingBoxConfig, error) {
var err error
if len(data) == 0 {
data, err = s.SettingService.GetConfig()
if err != nil {
return nil, err
}
}
singboxConfig := SingBoxConfig{}
err = json.Unmarshal([]byte(data), &singboxConfig)
if err != nil {
return nil, err
}
singboxConfig.Inbounds, err = s.InboundService.GetAllConfig(database.GetDB())
if err != nil {
return nil, err
}
singboxConfig.Outbounds, err = s.OutboundService.GetAllConfig(database.GetDB())
if err != nil {
return nil, err
}
singboxConfig.Endpoints, err = s.EndpointService.GetAllConfig(database.GetDB())
if err != nil {
return nil, err
}
return &singboxConfig, nil
}
func (s *ConfigService) StartCore(defaultConfig string) error {
if corePtr.IsRunning() {
return nil
}
singboxConfig, err := s.GetConfig(defaultConfig)
if err != nil {
return err
}
rawConfig, err := json.MarshalIndent(singboxConfig, "", " ")
if err != nil {
return err
}
err = corePtr.Start(rawConfig)
if err != nil {
logger.Error("start sing-box err:", err.Error())
return err
}
logger.Info("sing-box started")
return nil
}
func (s *ConfigService) RestartCore() error {
err := s.StopCore()
if err != nil {
return err
}
return s.StartCore("")
}
func (s *ConfigService) restartCoreWithConfig(config json.RawMessage) error {
err := s.StopCore()
if err != nil {
return err
}
return s.StartCore(string(config))
}
func (s *ConfigService) StopCore() error {
err := corePtr.Stop()
if err != nil {
return err
}
logger.Info("sing-box stopped")
return nil
}
func (s *ConfigService) Save(obj string, act string, data json.RawMessage, initUsers string, loginUser string, hostname string) ([]string, error) {
var err error
var inboundIds []uint
var inboundId uint
var objs []string = []string{obj}
db := database.GetDB()
tx := db.Begin()
defer func() {
if err == nil {
tx.Commit()
if len(inboundIds) > 0 && corePtr.IsRunning() {
err1 := s.InboundService.RestartInbounds(db, inboundIds)
if err1 != nil {
logger.Error("unable to restart inbounds: ", err1)
}
}
// Try to start core if it is not running
if !corePtr.IsRunning() {
s.StartCore("")
}
} else {
tx.Rollback()
}
}()
switch obj {
case "clients":
inboundIds, err = s.ClientService.Save(tx, act, data, hostname)
objs = append(objs, "inbounds")
case "tls":
inboundIds, err = s.TlsService.Save(tx, act, data)
case "inbounds":
inboundId, err = s.InboundService.Save(tx, act, data, initUsers, hostname)
case "outbounds":
err = s.OutboundService.Save(tx, act, data)
case "endpoints":
err = s.EndpointService.Save(tx, act, data)
case "config":
err = s.SettingService.SaveConfig(tx, data)
if err != nil {
return nil, err
}
err = s.restartCoreWithConfig(data)
case "settings":
err = s.SettingService.Save(tx, data)
default:
return nil, common.NewError("unknown object: ", obj)
}
if err != nil {
return nil, err
}
dt := time.Now().Unix()
err = tx.Create(&model.Changes{
DateTime: dt,
Actor: loginUser,
Key: obj,
Action: act,
Obj: data,
}).Error
if err != nil {
return nil, err
}
// Commit changes so far
tx.Commit()
LastUpdate = time.Now().Unix()
tx = db.Begin()
// Update side changes
// Update client links
if obj == "tls" && len(inboundIds) > 0 {
err = s.ClientService.UpdateLinksByInboundChange(tx, inboundIds, hostname)
if err != nil {
return nil, err
}
objs = append(objs, "clients")
}
if obj == "inbounds" {
switch act {
case "new":
err = s.ClientService.UpdateClientsOnInboundAdd(tx, initUsers, inboundId, hostname)
case "edit":
err = s.ClientService.UpdateLinksByInboundChange(tx, []uint{inboundId}, hostname)
case "del":
var tag string
err = json.Unmarshal(data, &tag)
if err != nil {
return nil, err
}
err = s.ClientService.UpdateClientsOnInboundDelete(tx, inboundId, tag)
}
if err != nil {
return nil, err
}
objs = append(objs, "clients")
}
// Update out_json of inbounds when tls is changed
if obj == "tls" && len(inboundIds) > 0 {
err = s.InboundService.UpdateOutJsons(tx, inboundIds, hostname)
if err != nil {
return nil, common.NewError("unable to update out_json of inbounds: ", err.Error())
}
objs = append(objs, "inbounds")
}
return objs, nil
}
func (s *ConfigService) CheckChanges(lu string) (bool, error) {
if lu == "" {
return true, nil
}
if LastUpdate == 0 {
db := database.GetDB()
var count int64
err := db.Model(model.Changes{}).Where("date_time > " + lu).Count(&count).Error
if err == nil {
LastUpdate = time.Now().Unix()
}
return count > 0, err
} else {
intLu, err := strconv.ParseInt(lu, 10, 64)
return LastUpdate > intLu, err
}
}
func (s *ConfigService) GetChanges(actor string, chngKey string, count string) []model.Changes {
c, _ := strconv.Atoi(count)
whereString := "`id`>0"
if len(actor) > 0 {
whereString += " and `actor`='" + actor + "'"
}
if len(chngKey) > 0 {
whereString += " and `key`='" + chngKey + "'"
}
db := database.GetDB()
var chngs []model.Changes
err := db.Model(model.Changes{}).Where(whereString).Order("`id` desc").Limit(c).Scan(&chngs).Error
if err != nil {
logger.Warning(err)
}
return chngs
}
+139
View File
@@ -0,0 +1,139 @@
package service
import (
"encoding/json"
"os"
"s-ui/database"
"s-ui/database/model"
"s-ui/util/common"
"gorm.io/gorm"
)
type EndpointService struct {
WarpService
}
func (o *EndpointService) GetAll() (*[]map[string]interface{}, error) {
db := database.GetDB()
endpoints := []*model.Endpoint{}
err := db.Model(model.Endpoint{}).Scan(&endpoints).Error
if err != nil {
return nil, err
}
var data []map[string]interface{}
for _, endpoint := range endpoints {
epData := map[string]interface{}{
"id": endpoint.Id,
"type": endpoint.Type,
"tag": endpoint.Tag,
"ext": endpoint.Ext,
}
if endpoint.Options != nil {
var restFields map[string]json.RawMessage
if err := json.Unmarshal(endpoint.Options, &restFields); err != nil {
return nil, err
}
for k, v := range restFields {
epData[k] = v
}
}
data = append(data, epData)
}
return &data, nil
}
func (o *EndpointService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
var endpointsJson []json.RawMessage
var endpoints []*model.Endpoint
err := db.Model(model.Endpoint{}).Scan(&endpoints).Error
if err != nil {
return nil, err
}
for _, endpoint := range endpoints {
endpointJson, err := endpoint.MarshalJSON()
if err != nil {
return nil, err
}
endpointsJson = append(endpointsJson, endpointJson)
}
return endpointsJson, nil
}
func (s *EndpointService) Save(tx *gorm.DB, act string, data json.RawMessage) error {
var err error
switch act {
case "new", "edit":
var endpoint model.Endpoint
err = endpoint.UnmarshalJSON(data)
if err != nil {
return err
}
if endpoint.Type == "warp" {
if act == "new" {
err = s.WarpService.RegisterWarp(&endpoint)
if err != nil {
return err
}
} else {
var old_license string
err = tx.Model(model.Endpoint{}).Select("json_extract(ext, '$.license_key')").Where("id = ?", endpoint.Id).Find(&old_license).Error
if err != nil {
return err
}
err = s.WarpService.SetWarpLicense(old_license, &endpoint)
if err != nil {
return err
}
}
}
if corePtr.IsRunning() {
configData, err := endpoint.MarshalJSON()
if err != nil {
return err
}
if act == "edit" {
var oldTag string
err = tx.Model(model.Endpoint{}).Select("tag").Where("id = ?", endpoint.Id).Find(&oldTag).Error
if err != nil {
return err
}
err = corePtr.RemoveEndpoint(oldTag)
if err != nil && err != os.ErrInvalid {
return err
}
}
err = corePtr.AddEndpoint(configData)
if err != nil {
return err
}
}
err = tx.Save(&endpoint).Error
if err != nil {
return err
}
case "del":
var tag string
err = json.Unmarshal(data, &tag)
if err != nil {
return err
}
if corePtr.IsRunning() {
err = corePtr.RemoveEndpoint(tag)
if err != nil && err != os.ErrInvalid {
return err
}
}
err = tx.Where("tag = ?", tag).Delete(model.Endpoint{}).Error
if err != nil {
return err
}
default:
return common.NewErrorf("unknown action: %s", act)
}
return nil
}
+345
View File
@@ -0,0 +1,345 @@
package service
import (
"encoding/json"
"os"
"s-ui/database"
"s-ui/database/model"
"s-ui/util"
"s-ui/util/common"
"strings"
"gorm.io/gorm"
)
type InboundService struct{}
func (s *InboundService) Get(ids string) (*[]map[string]interface{}, error) {
if ids == "" {
return s.GetAll()
}
return s.getById(ids)
}
func (s *InboundService) getById(ids string) (*[]map[string]interface{}, error) {
var inbound []model.Inbound
var result []map[string]interface{}
db := database.GetDB()
err := db.Model(model.Inbound{}).Where("id in ?", strings.Split(ids, ",")).Scan(&inbound).Error
if err != nil {
return nil, err
}
for _, inb := range inbound {
inbData, err := inb.MarshalFull()
if err != nil {
return nil, err
}
result = append(result, *inbData)
}
return &result, nil
}
func (s *InboundService) GetAll() (*[]map[string]interface{}, error) {
db := database.GetDB()
inbounds := []model.Inbound{}
err := db.Model(model.Inbound{}).Scan(&inbounds).Error
if err != nil {
return nil, err
}
var data []map[string]interface{}
for _, inbound := range inbounds {
var shadowtls_version uint
inbData := map[string]interface{}{
"id": inbound.Id,
"type": inbound.Type,
"tag": inbound.Tag,
"tls_id": inbound.TlsId,
}
if inbound.Options != nil {
var restFields map[string]json.RawMessage
if err := json.Unmarshal(inbound.Options, &restFields); err != nil {
return nil, err
}
inbData["listen"] = restFields["listen"]
inbData["listen_port"] = restFields["listen_port"]
if inbound.Type == "shadowtls" {
json.Unmarshal(restFields["version"], &shadowtls_version)
}
}
switch inbound.Type {
case "mixed", "socks", "http", "shadowsocks", "vmess", "trojan", "naive", "hysteria", "shadowtls", "tuic", "hysteria2", "vless":
if inbound.Type == "shadowtls" && shadowtls_version < 3 {
break
}
users := []string{}
err = db.Raw("SELECT clients.name FROM clients, json_each(clients.inbounds) as je WHERE je.value = ?", inbound.Id).Scan(&users).Error
if err != nil {
return nil, err
}
if len(users) > 0 || inbound.Type != "shadowsocks" {
inbData["users"] = users
}
}
data = append(data, inbData)
}
return &data, nil
}
func (s *InboundService) FromIds(ids []uint) ([]*model.Inbound, error) {
db := database.GetDB()
inbounds := []*model.Inbound{}
err := db.Model(model.Inbound{}).Where("id in ?", ids).Scan(&inbounds).Error
if err != nil {
return nil, err
}
return inbounds, nil
}
func (s *InboundService) Save(tx *gorm.DB, act string, data json.RawMessage, initUserIds string, hostname string) (uint, error) {
var err error
var id uint
switch act {
case "new", "edit":
var inbound model.Inbound
err = inbound.UnmarshalJSON(data)
if err != nil {
return 0, err
}
if inbound.TlsId > 0 {
err = tx.Model(model.Tls{}).Where("id = ?", inbound.TlsId).Find(&inbound.Tls).Error
if err != nil {
return 0, err
}
}
err = util.FillOutJson(&inbound, hostname)
if err != nil {
return 0, err
}
err = tx.Save(&inbound).Error
if err != nil {
return 0, err
}
id = inbound.Id
if corePtr.IsRunning() {
if act == "edit" {
var oldTag string
err = tx.Model(model.Inbound{}).Select("tag").Where("id = ?", inbound.Id).Find(&oldTag).Error
if err != nil {
return 0, err
}
err = corePtr.RemoveInbound(oldTag)
if err != nil && err != os.ErrInvalid {
return 0, err
}
}
inboundConfig, err := inbound.MarshalJSON()
if err != nil {
return 0, err
}
if act == "edit" {
inboundConfig, err = s.addUsers(tx, inboundConfig, inbound.Id, inbound.Type)
} else {
inboundConfig, err = s.initUsers(tx, inboundConfig, initUserIds, inbound.Type)
}
if err != nil {
return 0, err
}
err = corePtr.AddInbound(inboundConfig)
if err != nil {
return 0, err
}
}
case "del":
var tag string
err = json.Unmarshal(data, &tag)
if err != nil {
return 0, err
}
if corePtr.IsRunning() {
err = corePtr.RemoveInbound(tag)
if err != nil && err != os.ErrInvalid {
return 0, err
}
}
err = tx.Model(model.Inbound{}).Select("id").Where("tag = ?", tag).Scan(&id).Error
if err != nil {
return 0, err
}
err = tx.Where("tag = ?", tag).Delete(model.Inbound{}).Error
if err != nil {
return 0, err
}
default:
return 0, common.NewErrorf("unknown action: %s", act)
}
return id, nil
}
func (s *InboundService) UpdateOutJsons(tx *gorm.DB, inboundIds []uint, hostname string) error {
var inbounds []model.Inbound
err := tx.Model(model.Inbound{}).Preload("Tls").Where("id in ?", inboundIds).Find(&inbounds).Error
if err != nil {
return err
}
for _, inbound := range inbounds {
err = util.FillOutJson(&inbound, hostname)
if err != nil {
return err
}
err = tx.Model(model.Inbound{}).Where("tag = ?", inbound.Tag).Update("out_json", inbound.OutJson).Error
if err != nil {
return err
}
}
return nil
}
func (s *InboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
var inboundsJson []json.RawMessage
var inbounds []*model.Inbound
err := db.Model(model.Inbound{}).Preload("Tls").Find(&inbounds).Error
if err != nil {
return nil, err
}
for _, inbound := range inbounds {
inboundJson, err := inbound.MarshalJSON()
if err != nil {
return nil, err
}
inboundJson, err = s.addUsers(db, inboundJson, inbound.Id, inbound.Type)
if err != nil {
return nil, err
}
inboundsJson = append(inboundsJson, inboundJson)
}
return inboundsJson, nil
}
func (s *InboundService) addUsers(db *gorm.DB, inboundJson []byte, inboundId uint, inboundType string) ([]byte, error) {
switch inboundType {
case "mixed", "socks", "http", "shadowsocks", "vmess", "trojan", "naive", "hysteria", "shadowtls", "tuic", "hysteria2", "vless":
break
default:
return inboundJson, nil
}
var inbound map[string]interface{}
err := json.Unmarshal(inboundJson, &inbound)
if err != nil {
return nil, err
}
if inboundType == "shadowtls" {
version, _ := inbound["version"].(float64)
if int(version) < 3 {
return inboundJson, nil
}
}
if inboundType == "shadowsocks" {
method, _ := inbound["method"].(string)
if method == "2022-blake3-aes-128-gcm" {
inboundType = "shadowsocks16"
}
}
var users []string
err = db.Raw(`SELECT json_extract(clients.config, ?)
FROM clients, json_each(clients.inbounds) as je
WHERE clients.enable = true AND je.value = ?;`,
"$."+inboundType, inboundId).Scan(&users).Error
if err != nil {
return nil, err
}
var usersJson []json.RawMessage
for _, user := range users {
usersJson = append(usersJson, json.RawMessage(user))
}
if len(usersJson) > 0 || inboundType != "shadowsocks" {
inbound["users"] = usersJson
}
return json.Marshal(inbound)
}
func (s *InboundService) initUsers(db *gorm.DB, inboundJson []byte, clientIds string, inboundType string) ([]byte, error) {
ClientIds := strings.Split(clientIds, ",")
if len(ClientIds) == 0 {
return inboundJson, nil
}
switch inboundType {
case "mixed", "socks", "http", "shadowsocks", "vmess", "trojan", "naive", "hysteria", "shadowtls", "tuic", "hysteria2", "vless":
break
default:
return inboundJson, nil
}
var inbound map[string]interface{}
err := json.Unmarshal(inboundJson, &inbound)
if err != nil {
return nil, err
}
if inboundType == "shadowtls" {
version, _ := inbound["version"].(float64)
if int(version) < 3 {
return inboundJson, nil
}
}
if inboundType == "shadowsocks" {
method, _ := inbound["method"].(string)
if method == "2022-blake3-aes-128-gcm" {
inboundType = "shadowsocks16"
}
}
var users []string
err = db.Raw(`SELECT json_extract(clients.config, ?)
FROM clients
WHERE enable = true AND id in ?`,
"$."+inboundType, ClientIds).Scan(&users).Error
if err != nil {
return nil, err
}
var usersJson []json.RawMessage
for _, user := range users {
usersJson = append(usersJson, json.RawMessage(user))
}
if len(usersJson) > 0 || inboundType != "shadowsocks" {
inbound["users"] = usersJson
}
return json.Marshal(inbound)
}
func (s *InboundService) RestartInbounds(tx *gorm.DB, ids []uint) error {
var inbounds []*model.Inbound
err := tx.Model(model.Inbound{}).Preload("Tls").Where("id in ?", ids).Find(&inbounds).Error
if err != nil {
return err
}
for _, inbound := range inbounds {
err = corePtr.RemoveInbound(inbound.Tag)
if err != nil && err != os.ErrInvalid {
return err
}
inboundConfig, err := inbound.MarshalJSON()
if err != nil {
return err
}
inboundConfig, err = s.addUsers(tx, inboundConfig, inbound.Id, inbound.Type)
err = corePtr.AddInbound(inboundConfig)
if err != nil {
return err
}
}
return nil
}
+117
View File
@@ -0,0 +1,117 @@
package service
import (
"encoding/json"
"os"
"s-ui/database"
"s-ui/database/model"
"s-ui/util/common"
"gorm.io/gorm"
)
type OutboundService struct{}
func (o *OutboundService) GetAll() (*[]map[string]interface{}, error) {
db := database.GetDB()
outbounds := []*model.Outbound{}
err := db.Model(model.Outbound{}).Scan(&outbounds).Error
if err != nil {
return nil, err
}
var data []map[string]interface{}
for _, outbound := range outbounds {
outData := map[string]interface{}{
"id": outbound.Id,
"type": outbound.Type,
"tag": outbound.Tag,
}
if outbound.Options != nil {
var restFields map[string]json.RawMessage
if err := json.Unmarshal(outbound.Options, &restFields); err != nil {
return nil, err
}
for k, v := range restFields {
outData[k] = v
}
}
data = append(data, outData)
}
return &data, nil
}
func (o *OutboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
var outboundsJson []json.RawMessage
var outbounds []*model.Outbound
err := db.Model(model.Outbound{}).Scan(&outbounds).Error
if err != nil {
return nil, err
}
for _, outbound := range outbounds {
outboundJson, err := outbound.MarshalJSON()
if err != nil {
return nil, err
}
outboundsJson = append(outboundsJson, outboundJson)
}
return outboundsJson, nil
}
func (s *OutboundService) Save(tx *gorm.DB, act string, data json.RawMessage) error {
var err error
switch act {
case "new", "edit":
var outbound model.Outbound
err = outbound.UnmarshalJSON(data)
if err != nil {
return err
}
if corePtr.IsRunning() {
configData, err := outbound.MarshalJSON()
if err != nil {
return err
}
if act == "edit" {
var oldTag string
err = tx.Model(model.Outbound{}).Select("tag").Where("id = ?", outbound.Id).Find(&oldTag).Error
if err != nil {
return err
}
err = corePtr.RemoveOutbound(oldTag)
if err != nil && err != os.ErrInvalid {
return err
}
}
err = corePtr.AddOutbound(configData)
if err != nil {
return err
}
}
err = tx.Save(&outbound).Error
if err != nil {
return err
}
case "del":
var tag string
err = json.Unmarshal(data, &tag)
if err != nil {
return err
}
if corePtr.IsRunning() {
err = corePtr.RemoveOutbound(tag)
if err != nil && err != os.ErrInvalid {
return err
}
}
err = tx.Where("tag = ?", tag).Delete(model.Outbound{}).Error
if err != nil {
return err
}
default:
return common.NewErrorf("unknown action: %s", act)
}
return nil
}
+26
View File
@@ -0,0 +1,26 @@
package service
import (
"os"
"s-ui/logger"
"syscall"
"time"
)
type PanelService struct {
}
func (s *PanelService) RestartPanel(delay time.Duration) error {
p, err := os.FindProcess(syscall.Getpid())
if err != nil {
return err
}
go func() {
time.Sleep(delay)
err := p.Signal(syscall.SIGHUP)
if err != nil {
logger.Error("send signal SIGHUP failed:", err)
}
}()
return nil
}
+208
View File
@@ -0,0 +1,208 @@
package service
import (
"encoding/base64"
"os"
"runtime"
"s-ui/config"
"s-ui/logger"
"strconv"
"strings"
"time"
"github.com/sagernet/sing-box/common/tls"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/host"
"github.com/shirou/gopsutil/v4/mem"
"github.com/shirou/gopsutil/v4/net"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
type ServerService struct{}
func (s *ServerService) GetStatus(request string) *map[string]interface{} {
status := make(map[string]interface{}, 0)
requests := strings.Split(request, ",")
for _, req := range requests {
switch req {
case "cpu":
status["cpu"] = s.GetCpuPercent()
case "mem":
status["mem"] = s.GetMemInfo()
case "net":
status["net"] = s.GetNetInfo()
case "sys":
status["uptime"] = s.GetUptime()
status["sys"] = s.GetSystemInfo()
case "sbd":
status["sbd"] = s.GetSingboxInfo()
}
}
return &status
}
func (s *ServerService) GetCpuPercent() float64 {
percents, err := cpu.Percent(0, false)
if err != nil {
logger.Warning("get cpu percent failed:", err)
return 0
} else {
return percents[0]
}
}
func (s *ServerService) GetUptime() uint64 {
upTime, err := host.Uptime()
if err != nil {
logger.Warning("get uptime failed:", err)
return 0
} else {
return upTime
}
}
func (s *ServerService) GetMemInfo() map[string]interface{} {
info := make(map[string]interface{}, 0)
memInfo, err := mem.VirtualMemory()
if err != nil {
logger.Warning("get virtual memory failed:", err)
} else {
info["current"] = memInfo.Used
info["total"] = memInfo.Total
}
return info
}
func (s *ServerService) GetNetInfo() map[string]interface{} {
info := make(map[string]interface{}, 0)
ioStats, err := net.IOCounters(false)
if err != nil {
logger.Warning("get io counters failed:", err)
} else if len(ioStats) > 0 {
ioStat := ioStats[0]
info["sent"] = ioStat.BytesSent
info["recv"] = ioStat.BytesRecv
info["psent"] = ioStat.PacketsSent
info["precv"] = ioStat.PacketsRecv
} else {
logger.Warning("can not find io counters")
}
return info
}
func (s *ServerService) GetSingboxInfo() map[string]interface{} {
var rtm runtime.MemStats
runtime.ReadMemStats(&rtm)
isRunning := corePtr.IsRunning()
uptime := uint32(0)
if isRunning {
uptime = corePtr.GetInstance().Uptime()
}
return map[string]interface{}{
"running": isRunning,
"stats": map[string]interface{}{
"NumGoroutine": uint32(runtime.NumGoroutine()),
"Alloc": rtm.Alloc,
"Uptime": uptime,
},
}
}
func (s *ServerService) GetSystemInfo() map[string]interface{} {
info := make(map[string]interface{}, 0)
var rtm runtime.MemStats
runtime.ReadMemStats(&rtm)
info["appMem"] = rtm.Sys
info["appThreads"] = uint32(runtime.NumGoroutine())
cpuInfo, err := cpu.Info()
if err == nil {
info["cpuType"] = cpuInfo[0].ModelName
}
info["cpuCount"] = runtime.NumCPU()
info["hostName"], _ = os.Hostname()
info["appVersion"] = config.GetVersion()
ipv4 := make([]string, 0)
ipv6 := make([]string, 0)
// get ip address
netInterfaces, _ := net.Interfaces()
for i := 0; i < len(netInterfaces); i++ {
if len(netInterfaces[i].Flags) > 2 && netInterfaces[i].Flags[0] == "up" && netInterfaces[i].Flags[1] != "loopback" {
addrs := netInterfaces[i].Addrs
for _, address := range addrs {
if strings.Contains(address.Addr, ".") {
ipv4 = append(ipv4, address.Addr)
} else if address.Addr[0:6] != "fe80::" {
ipv6 = append(ipv6, address.Addr)
}
}
}
}
info["ipv4"] = ipv4
info["ipv6"] = ipv6
return info
}
func (s *ServerService) GetLogs(count string, level string) []string {
c, _ := strconv.Atoi(count)
return logger.GetLogs(c, level)
}
func (s *ServerService) GenKeypair(keyType string, options string) []string {
if len(keyType) == 0 {
return []string{"No keypair to generate"}
}
switch keyType {
case "ech":
return s.generateECHKeyPair(options)
case "tls":
return s.generateTLSKeyPair(options)
case "reality":
return s.generateRealityKeyPair()
case "wireguard":
return s.generateWireGuardKey(options)
}
return []string{"Failed to generate keypair"}
}
func (s *ServerService) generateECHKeyPair(options string) []string {
parts := strings.Split(options, ",")
configPem, keyPem, err := tls.ECHKeygenDefault(parts[0], parts[1] == "true")
if err != nil {
return []string{"Failed to generate ECH keypair: ", err.Error()}
}
return append(strings.Split(configPem, "\n"), strings.Split(keyPem, "\n")...)
}
func (s *ServerService) generateTLSKeyPair(serverName string) []string {
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, 12, 0))
if err != nil {
return []string{"Failed to generate TLS keypair: ", err.Error()}
}
return append(strings.Split(string(privateKeyPem), "\n"), strings.Split(string(publicKeyPem), "\n")...)
}
func (s *ServerService) generateRealityKeyPair() []string {
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
return []string{"Failed to generate Reality keypair: ", err.Error()}
}
publicKey := privateKey.PublicKey()
return []string{"PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]), "PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:])}
}
func (s *ServerService) generateWireGuardKey(pk string) []string {
if len(pk) > 0 {
key, _ := wgtypes.ParseKey(pk)
return []string{key.PublicKey().String()}
}
wgKeys, err := wgtypes.GeneratePrivateKey()
if err != nil {
return []string{"Failed to generate wireguard keypair: ", err.Error()}
}
return []string{"PrivateKey: " + wgKeys.String(), "PublicKey: " + wgKeys.PublicKey().String()}
}
+398
View File
@@ -0,0 +1,398 @@
package service
import (
"encoding/json"
"os"
"s-ui/config"
"s-ui/database"
"s-ui/database/model"
"s-ui/logger"
"s-ui/util/common"
"strconv"
"strings"
"time"
"gorm.io/gorm"
)
var defaultConfig = `{
"log": {
"level": "info"
},
"dns": {},
"route": {
"rules": [
{
"protocol": [
"dns"
],
"action": "hijack-dns"
}
]
},
"experimental": {}
}`
var defaultValueMap = map[string]string{
"webListen": "",
"webDomain": "",
"webPort": "2095",
"secret": common.Random(32),
"webCertFile": "",
"webKeyFile": "",
"webPath": "/app/",
"webURI": "",
"sessionMaxAge": "0",
"trafficAge": "30",
"timeLocation": "Asia/Tehran",
"subListen": "",
"subPort": "2096",
"subPath": "/sub/",
"subDomain": "",
"subCertFile": "",
"subKeyFile": "",
"subUpdates": "12",
"subEncode": "true",
"subShowInfo": "false",
"subURI": "",
"subJsonExt": "",
"config": defaultConfig,
"version": config.GetVersion(),
}
type SettingService struct {
}
func (s *SettingService) GetAllSetting() (*map[string]string, error) {
db := database.GetDB()
settings := make([]*model.Setting, 0)
err := db.Model(model.Setting{}).Find(&settings).Error
if err != nil {
return nil, err
}
allSetting := map[string]string{}
for _, setting := range settings {
allSetting[setting.Key] = setting.Value
}
for key, defaultValue := range defaultValueMap {
if _, exists := allSetting[key]; !exists {
err = s.saveSetting(key, defaultValue)
if err != nil {
return nil, err
}
allSetting[key] = defaultValue
}
}
// Due to security principles
delete(allSetting, "secret")
delete(allSetting, "config")
delete(allSetting, "version")
return &allSetting, nil
}
func (s *SettingService) ResetSettings() error {
db := database.GetDB()
return db.Where("1 = 1").Delete(model.Setting{}).Error
}
func (s *SettingService) getSetting(key string) (*model.Setting, error) {
db := database.GetDB()
setting := &model.Setting{}
err := db.Model(model.Setting{}).Where("key = ?", key).First(setting).Error
if err != nil {
return nil, err
}
return setting, nil
}
func (s *SettingService) getString(key string) (string, error) {
setting, err := s.getSetting(key)
if database.IsNotFound(err) {
value, ok := defaultValueMap[key]
if !ok {
return "", common.NewErrorf("key <%v> not in defaultValueMap", key)
}
return value, nil
} else if err != nil {
return "", err
}
return setting.Value, nil
}
func (s *SettingService) saveSetting(key string, value string) error {
setting, err := s.getSetting(key)
db := database.GetDB()
if database.IsNotFound(err) {
return db.Create(&model.Setting{
Key: key,
Value: value,
}).Error
} else if err != nil {
return err
}
setting.Key = key
setting.Value = value
return db.Save(setting).Error
}
func (s *SettingService) setString(key string, value string) error {
return s.saveSetting(key, value)
}
func (s *SettingService) getBool(key string) (bool, error) {
str, err := s.getString(key)
if err != nil {
return false, err
}
return strconv.ParseBool(str)
}
// func (s *SettingService) setBool(key string, value bool) error {
// return s.setString(key, strconv.FormatBool(value))
// }
func (s *SettingService) getInt(key string) (int, error) {
str, err := s.getString(key)
if err != nil {
return 0, err
}
return strconv.Atoi(str)
}
func (s *SettingService) setInt(key string, value int) error {
return s.setString(key, strconv.Itoa(value))
}
func (s *SettingService) GetListen() (string, error) {
return s.getString("webListen")
}
func (s *SettingService) GetWebDomain() (string, error) {
return s.getString("webDomain")
}
func (s *SettingService) GetPort() (int, error) {
return s.getInt("webPort")
}
func (s *SettingService) SetPort(port int) error {
return s.setInt("webPort", port)
}
func (s *SettingService) GetCertFile() (string, error) {
return s.getString("webCertFile")
}
func (s *SettingService) GetKeyFile() (string, error) {
return s.getString("webKeyFile")
}
func (s *SettingService) GetWebPath() (string, error) {
webPath, err := s.getString("webPath")
if err != nil {
return "", err
}
if !strings.HasPrefix(webPath, "/") {
webPath = "/" + webPath
}
if !strings.HasSuffix(webPath, "/") {
webPath += "/"
}
return webPath, nil
}
func (s *SettingService) SetWebPath(webPath string) error {
if !strings.HasPrefix(webPath, "/") {
webPath = "/" + webPath
}
if !strings.HasSuffix(webPath, "/") {
webPath += "/"
}
return s.setString("webPath", webPath)
}
func (s *SettingService) GetSecret() ([]byte, error) {
secret, err := s.getString("secret")
if secret == defaultValueMap["secret"] {
err := s.saveSetting("secret", secret)
if err != nil {
logger.Warning("save secret failed:", err)
}
}
return []byte(secret), err
}
func (s *SettingService) GetSessionMaxAge() (int, error) {
return s.getInt("sessionMaxAge")
}
func (s *SettingService) GetTrafficAge() (int, error) {
return s.getInt("trafficAge")
}
func (s *SettingService) GetTimeLocation() (*time.Location, error) {
l, err := s.getString("timeLocation")
if err != nil {
return nil, err
}
location, err := time.LoadLocation(l)
if err != nil {
defaultLocation := defaultValueMap["timeLocation"]
logger.Errorf("location <%v> not exist, using default location: %v", l, defaultLocation)
return time.LoadLocation(defaultLocation)
}
return location, nil
}
func (s *SettingService) GetSubListen() (string, error) {
return s.getString("subListen")
}
func (s *SettingService) GetSubPort() (int, error) {
return s.getInt("subPort")
}
func (s *SettingService) SetSubPort(subPort int) error {
return s.setInt("subPort", subPort)
}
func (s *SettingService) GetSubPath() (string, error) {
subPath, err := s.getString("subPath")
if err != nil {
return "", err
}
if !strings.HasPrefix(subPath, "/") {
subPath = "/" + subPath
}
if !strings.HasSuffix(subPath, "/") {
subPath += "/"
}
return subPath, nil
}
func (s *SettingService) SetSubPath(subPath string) error {
if !strings.HasPrefix(subPath, "/") {
subPath = "/" + subPath
}
if !strings.HasSuffix(subPath, "/") {
subPath += "/"
}
return s.setString("subPath", subPath)
}
func (s *SettingService) GetSubDomain() (string, error) {
return s.getString("subDomain")
}
func (s *SettingService) GetSubCertFile() (string, error) {
return s.getString("subCertFile")
}
func (s *SettingService) GetSubKeyFile() (string, error) {
return s.getString("subKeyFile")
}
func (s *SettingService) GetSubUpdates() (int, error) {
return s.getInt("subUpdates")
}
func (s *SettingService) GetSubEncode() (bool, error) {
return s.getBool("subEncode")
}
func (s *SettingService) GetSubShowInfo() (bool, error) {
return s.getBool("subShowInfo")
}
func (s *SettingService) GetSubURI() (string, error) {
return s.getString("subURI")
}
func (s *SettingService) GetFinalSubURI(host string) (string, error) {
allSetting, err := s.GetAllSetting()
if err != nil {
return "", err
}
SubURI := (*allSetting)["subURI"]
if SubURI != "" {
return SubURI, nil
}
protocol := "http"
if (*allSetting)["subKeyFile"] != "" && (*allSetting)["subCertFile"] != "" {
protocol = "https"
}
if (*allSetting)["subDomain"] != "" {
host = (*allSetting)["subDomain"]
}
port := ":" + (*allSetting)["subPort"]
if (port == "80" && protocol == "http") || (port == "443" && protocol == "https") {
port = ""
}
return protocol + "://" + host + port + (*allSetting)["subPath"], nil
}
func (s *SettingService) GetConfig() (string, error) {
return s.getString("config")
}
func (s *SettingService) SetConfig(config string) error {
return s.setString("config", config)
}
func (s *SettingService) SaveConfig(tx *gorm.DB, config json.RawMessage) error {
configs, err := json.MarshalIndent(config, "", " ")
if err != nil {
return err
}
return tx.Model(model.Setting{}).Where("key = ?", "config").Update("value", string(configs)).Error
}
func (s *SettingService) Save(tx *gorm.DB, data json.RawMessage) error {
var err error
var settings map[string]string
err = json.Unmarshal(data, &settings)
if err != nil {
return err
}
for key, obj := range settings {
// Secure file existance check
if obj != "" && (key == "webCertFile" ||
key == "webKeyFile" ||
key == "subCertFile" ||
key == "subKeyFile") {
err = s.fileExists(obj)
if err != nil {
return common.NewError(" -> ", obj, " is not exists")
}
}
// Correct Pathes start and ends with `/`
if key == "webPath" ||
key == "subPath" {
if !strings.HasPrefix(obj, "/") {
obj = "/" + obj
}
if !strings.HasSuffix(obj, "/") {
obj += "/"
}
}
err = tx.Model(model.Setting{}).Where("key = ?", key).Update("value", obj).Error
if err != nil {
return err
}
}
return err
}
func (s *SettingService) GetSubJsonExt() (string, error) {
return s.getString("subJsonExt")
}
func (s *SettingService) fileExists(path string) error {
_, err := os.Stat(path)
return err
}
+103
View File
@@ -0,0 +1,103 @@
package service
import (
"s-ui/database"
"s-ui/database/model"
"time"
"gorm.io/gorm"
)
type onlines struct {
Inbound []string `json:"inbound,omitempty"`
User []string `json:"user,omitempty"`
Outbound []string `json:"outbound,omitempty"`
}
var onlineResources = &onlines{}
type StatsService struct {
}
func (s *StatsService) SaveStats() error {
if !corePtr.IsRunning() {
return nil
}
stats := corePtr.GetInstance().ConnTracker().GetStats()
// Reset onlines
onlineResources.Inbound = nil
onlineResources.Outbound = nil
onlineResources.User = nil
if len(*stats) == 0 {
return nil
}
var err error
db := database.GetDB()
tx := db.Begin()
defer func() {
if err == nil {
tx.Commit()
} else {
tx.Rollback()
}
}()
for _, stat := range *stats {
if stat.Resource == "user" {
if stat.Direction {
err = tx.Model(model.Client{}).Where("name = ?", stat.Tag).
UpdateColumn("up", gorm.Expr("up + ?", stat.Traffic)).Error
} else {
err = tx.Model(model.Client{}).Where("name = ?", stat.Tag).
UpdateColumn("down", gorm.Expr("down + ?", stat.Traffic)).Error
}
if err != nil {
return err
}
}
if stat.Direction {
switch stat.Resource {
case "inbound":
onlineResources.Inbound = append(onlineResources.Inbound, stat.Tag)
case "outbound":
onlineResources.Outbound = append(onlineResources.Outbound, stat.Tag)
case "user":
onlineResources.User = append(onlineResources.User, stat.Tag)
}
}
}
err = tx.Create(&stats).Error
return err
}
func (s *StatsService) GetStats(resource string, tag string, limit int) ([]model.Stats, error) {
var err error
var result []model.Stats
currentTime := time.Now().Unix()
timeDiff := currentTime - (int64(limit) * 3600)
db := database.GetDB()
resources := []string{resource}
if resource == "endpoint" {
resources = []string{"inbound", "outbound"}
}
err = db.Model(model.Stats{}).Where("resource in ? AND tag = ? AND date_time > ?", resources, tag, timeDiff).Scan(&result).Error
if err != nil {
return nil, err
}
return result, nil
}
func (s *StatsService) GetOnlines() (onlines, error) {
return *onlineResources, nil
}
func (s *StatsService) DelOldStats(days int) error {
oldTime := time.Now().AddDate(0, 0, -(days)).Unix()
db := database.GetDB()
return db.Where("date_time < ?", oldTime).Delete(model.Stats{}).Error
}
+68
View File
@@ -0,0 +1,68 @@
package service
import (
"encoding/json"
"s-ui/database"
"s-ui/database/model"
"s-ui/util/common"
"gorm.io/gorm"
)
type TlsService struct {
InboundService
}
func (s *TlsService) GetAll() ([]model.Tls, error) {
db := database.GetDB()
tlsConfig := []model.Tls{}
err := db.Model(model.Tls{}).Scan(&tlsConfig).Error
if err != nil {
return nil, err
}
return tlsConfig, nil
}
func (s *TlsService) Save(tx *gorm.DB, action string, data json.RawMessage) ([]uint, error) {
var err error
var inboundIds []uint
switch action {
case "new", "edit":
var tls model.Tls
err = json.Unmarshal(data, &tls)
if err != nil {
return nil, err
}
err = tx.Save(&tls).Error
if err != nil {
return nil, err
}
err = tx.Model(model.Inbound{}).Select("id").Where("tls_id = ?", tls.Id).Scan(&inboundIds).Error
if err != nil {
return nil, err
}
return inboundIds, nil
case "del":
var id uint
err = json.Unmarshal(data, &id)
if err != nil {
return nil, err
}
var inboundCount int64
err = tx.Model(model.Inbound{}).Where("tls_id = ?", id).Count(&inboundCount).Error
if err != nil {
return nil, err
}
if inboundCount > 0 {
return nil, common.NewError("tls in use")
}
err = tx.Where("id = ?", id).Delete(model.Tls{}).Error
if err != nil {
return nil, err
}
}
return nil, nil
}
+101
View File
@@ -0,0 +1,101 @@
package service
import (
"s-ui/database"
"s-ui/database/model"
"s-ui/logger"
"s-ui/util/common"
"time"
)
type UserService struct {
}
func (s *UserService) GetFirstUser() (*model.User, error) {
db := database.GetDB()
user := &model.User{}
err := db.Model(model.User{}).
First(user).
Error
if err != nil {
return nil, err
}
return user, nil
}
func (s *UserService) UpdateFirstUser(username string, password string) error {
if username == "" {
return common.NewError("username can not be empty")
} else if password == "" {
return common.NewError("password can not be empty")
}
db := database.GetDB()
user := &model.User{}
err := db.Model(model.User{}).First(user).Error
if database.IsNotFound(err) {
user.Username = username
user.Password = password
return db.Model(model.User{}).Create(user).Error
} else if err != nil {
return err
}
user.Username = username
user.Password = password
return db.Save(user).Error
}
func (s *UserService) Login(username string, password string, remoteIP string) (string, error) {
user := s.CheckUser(username, password, remoteIP)
if user == nil {
return "", common.NewError("wrong user or password! IP: ", remoteIP)
}
return user.Username, nil
}
func (s *UserService) CheckUser(username string, password string, remoteIP string) *model.User {
db := database.GetDB()
user := &model.User{}
err := db.Model(model.User{}).
Where("username = ? and password = ?", username, password).
First(user).
Error
if database.IsNotFound(err) {
return nil
} else if err != nil {
logger.Warning("check user err:", err, " IP: ", remoteIP)
return nil
}
lastLoginTxt := time.Now().Format("2006-01-02 15:04:05") + " " + remoteIP
err = db.Model(model.User{}).
Where("username = ?", username).
Update("last_logins", &lastLoginTxt).Error
if err != nil {
logger.Warning("unable to log login data", err)
}
return user
}
func (s *UserService) GetUsers() (*[]model.User, error) {
var users []model.User
db := database.GetDB()
err := db.Model(model.User{}).Select("id,username,last_logins").Scan(&users).Error
if err != nil {
return nil, err
}
return &users, nil
}
func (s *UserService) ChangePass(id string, oldPass string, newUser string, newPass string) error {
db := database.GetDB()
user := &model.User{}
err := db.Model(model.User{}).Where("id = ? AND password = ?", id, oldPass).First(user).Error
if err != nil || database.IsNotFound(err) {
return err
}
user.Username = newUser
user.Password = newPass
return db.Save(user).Error
}
+229
View File
@@ -0,0 +1,229 @@
package service
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"s-ui/database/model"
"s-ui/logger"
"s-ui/util/common"
"strconv"
"time"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
type WarpService struct{}
func (s *WarpService) getWarpInfo(ep *model.Endpoint) ([]byte, error) {
var warpData map[string]string
err := json.Unmarshal(ep.Ext, &warpData)
if err != nil {
return nil, err
}
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s", warpData["device_id"])
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
buffer := bytes.NewBuffer(make([]byte, 8192))
buffer.Reset()
_, err = buffer.ReadFrom(resp.Body)
if err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
func (s *WarpService) RegisterWarp(ep *model.Endpoint) error {
tos := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
privateKey, _ := wgtypes.GenerateKey()
publicKey := privateKey.PublicKey().String()
hostName, _ := os.Hostname()
data := fmt.Sprintf(`{"key":"%s","tos":"%s","type": "PC","model": "s-ui", "name": "%s"}`, publicKey, tos, hostName)
url := "https://api.cloudflareclient.com/v0a2158/reg"
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data)))
if err != nil {
return err
}
req.Header.Add("CF-Client-Version", "a-7.21-0721")
req.Header.Add("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
buffer := bytes.NewBuffer(make([]byte, 8192))
buffer.Reset()
_, err = buffer.ReadFrom(resp.Body)
if err != nil {
return err
}
var rspData map[string]interface{}
err = json.Unmarshal(buffer.Bytes(), &rspData)
if err != nil {
return err
}
deviceId := rspData["id"].(string)
token := rspData["token"].(string)
license, ok := rspData["account"].(map[string]interface{})["license"].(string)
if !ok {
logger.Debug("Error accessing license value.")
return err
}
warpData := map[string]string{
"access_token": token,
"device_id": deviceId,
"license_key": license,
}
ep.Ext, err = json.MarshalIndent(warpData, "", " ")
if err != nil {
return err
}
warpInfo, err := s.getWarpInfo(ep)
if err != nil {
return err
}
var warpDetails map[string]interface{}
err = json.Unmarshal(warpInfo, &warpDetails)
if err != nil {
return err
}
warpConfig, _ := warpDetails["config"].(map[string]interface{})
clientId, _ := warpConfig["client_id"].(string)
reserved := s.getReserved(clientId)
interfaceConfig, _ := warpConfig["interface"].(map[string]interface{})
addresses, _ := interfaceConfig["addresses"].(map[string]interface{})
v4, _ := addresses["v4"].(string)
v6, _ := addresses["v6"].(string)
peer, _ := warpConfig["peers"].([]interface{})[0].(map[string]interface{})
peerEndpoint, _ := peer["endpoint"].(map[string]interface{})["host"].(string)
peerEpAddress, peerEpPort, err := net.SplitHostPort(peerEndpoint)
if err != nil {
return err
}
peerPublicKey, _ := peer["public_key"].(string)
peerPort, _ := strconv.Atoi(peerEpPort)
peers := []map[string]interface{}{
{
"address": peerEpAddress,
"port": peerPort,
"public_key": peerPublicKey,
"allowed_ips": []string{"0.0.0.0/0", "::/0"},
"reserved": reserved,
},
}
var epOptions map[string]interface{}
err = json.Unmarshal(ep.Options, &epOptions)
if err != nil {
return err
}
epOptions["private_key"] = privateKey.String()
epOptions["address"] = []string{fmt.Sprintf("%s/32", v4), fmt.Sprintf("%s/128", v6)}
epOptions["listen_port"] = 0
epOptions["peers"] = peers
ep.Options, err = json.MarshalIndent(epOptions, "", " ")
return err
}
func (s *WarpService) getReserved(clientID string) []int {
var reserved []int
decoded, err := base64.StdEncoding.DecodeString(clientID)
if err != nil {
return nil
}
hexString := ""
for _, char := range decoded {
hex := fmt.Sprintf("%02x", char)
hexString += hex
}
for i := 0; i < len(hexString); i += 2 {
hexByte := hexString[i : i+2]
decValue, err := strconv.ParseInt(hexByte, 16, 32)
if err != nil {
return nil
}
reserved = append(reserved, int(decValue))
}
return reserved
}
func (s *WarpService) SetWarpLicense(old_license string, ep *model.Endpoint) error {
var warpData map[string]string
err := json.Unmarshal(ep.Ext, &warpData)
if err != nil {
return err
}
if warpData["license_key"] == old_license {
return nil
}
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s/account", warpData["device_id"])
data := fmt.Sprintf(`{"license": "%s"}`, warpData["license_key"])
req, err := http.NewRequest("PUT", url, bytes.NewBuffer([]byte(data)))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
buffer := bytes.NewBuffer(make([]byte, 8192))
buffer.Reset()
_, err = buffer.ReadFrom(resp.Body)
if err != nil {
return err
}
var response map[string]interface{}
err = json.Unmarshal(buffer.Bytes(), &response)
if err != nil {
return err
}
if success, ok := response["success"].(bool); ok && success == false {
errorArr, _ := response["errors"].([]interface{})
errorObj := errorArr[0].(map[string]interface{})
return common.NewError(errorObj["code"], errorObj["message"])
}
return nil
}