add
This commit is contained in:
@@ -0,0 +1,548 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alireza0/s-ui/database"
|
||||
"github.com/alireza0/s-ui/database/model"
|
||||
"github.com/alireza0/s-ui/logger"
|
||||
"github.com/alireza0/s-ui/util"
|
||||
"github.com/alireza0/s-ui/util/common"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ClientService struct{}
|
||||
|
||||
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 = s.updateLinksWithFixedInbounds(tx, []*model.Client{&client}, hostname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if act == "edit" {
|
||||
// Find changed inbounds
|
||||
inboundIds, err = s.findInboundsChanges(tx, &client, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
err = json.Unmarshal(client.Inbounds, &inboundIds)
|
||||
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, hostname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tx.Save(clients).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "editbulk":
|
||||
var clients []*model.Client
|
||||
err = json.Unmarshal(data, &clients)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, client := range clients {
|
||||
changedInboundIds, err := s.findInboundsChanges(tx, client, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(changedInboundIds) > 0 {
|
||||
inboundIds = common.UnionUintArray(inboundIds, changedInboundIds)
|
||||
}
|
||||
}
|
||||
if len(inboundIds) > 0 {
|
||||
err = s.updateLinksWithFixedInbounds(tx, clients, hostname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
err = tx.Save(clients).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "delbulk":
|
||||
var ids []uint
|
||||
err = json.Unmarshal(data, &ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, id := range ids {
|
||||
var client model.Client
|
||||
err = tx.Where("id = ?", id).First(&client).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var clientInbounds []uint
|
||||
err = json.Unmarshal(client.Inbounds, &clientInbounds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inboundIds = common.UnionUintArray(inboundIds, clientInbounds)
|
||||
}
|
||||
err = tx.Where("id in ?", ids).Delete(model.Client{}).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, hostname string) error {
|
||||
var err error
|
||||
var inbounds []model.Inbound
|
||||
var inboundIds []uint
|
||||
|
||||
err = json.Unmarshal(clients[0].Inbounds, &inboundIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Zero inbounds means removing local links only
|
||||
if len(inboundIds) > 0 {
|
||||
err = tx.Model(model.Inbound{}).Preload("Tls").Where("id in ? and type in ?", inboundIds, 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 non 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 clientIds []uint
|
||||
err := tx.Raw("SELECT clients.id FROM clients, json_each(clients.inbounds) AS je WHERE je.value = ?", id).Scan(&clientIds).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(clientIds) == 0 {
|
||||
return nil
|
||||
}
|
||||
var clients []model.Client
|
||||
err = tx.Model(model.Client{}).Where("id IN ?", clientIds).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, inbounds *[]model.Inbound, hostname string, oldTag string) error {
|
||||
var err error
|
||||
for _, inbound := range *inbounds {
|
||||
var clientIds []uint
|
||||
err = tx.Raw("SELECT clients.id FROM clients, json_each(clients.inbounds) AS je WHERE je.value = ?", inbound.Id).Scan(&clientIds).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(clientIds) == 0 {
|
||||
continue
|
||||
}
|
||||
var clients []model.Client
|
||||
err = tx.Model(model.Client{}).Where("id IN ?", clientIds).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["type"] != "local" || (clientLink["remark"] != inbound.Tag && clientLink["remark"] != oldTag) {
|
||||
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() ([]uint, error) {
|
||||
var err error
|
||||
var clients []model.Client
|
||||
var changes []model.Changes
|
||||
var users []string
|
||||
var inboundIds []uint
|
||||
|
||||
dt := time.Now().Unix()
|
||||
db := database.GetDB()
|
||||
|
||||
tx := db.Begin()
|
||||
defer func() {
|
||||
if err == nil {
|
||||
tx.Commit()
|
||||
if err1 := db.Exec("PRAGMA wal_checkpoint(FULL)").Error; err1 != nil {
|
||||
logger.Error("Error checkpointing WAL: ", err1.Error())
|
||||
}
|
||||
} else {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
// Reset clients
|
||||
inboundIds, err = s.ResetClients(tx, dt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Deplete clients
|
||||
err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", dt).Scan(&clients).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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)
|
||||
// Find changed inbounds
|
||||
inboundIds = common.UnionUintArray(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 < ?))", dt).Update("enable", false).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tx.Model(model.Changes{}).Create(&changes).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
LastUpdate = dt
|
||||
}
|
||||
|
||||
return inboundIds, nil
|
||||
}
|
||||
|
||||
func (s *ClientService) ResetClients(tx *gorm.DB, dt int64) ([]uint, error) {
|
||||
var err error
|
||||
var resetClients, allClients []*model.Client
|
||||
var changes []model.Changes
|
||||
var inboundIds []uint
|
||||
// Set delay start without periodic reset
|
||||
err = tx.Model(model.Client{}).
|
||||
Where("enable = true AND delay_start = true AND auto_reset = false AND (Up + Down) > 0").Find(&resetClients).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, client := range resetClients {
|
||||
client.Expiry = dt + (int64(client.ResetDays) * 86400)
|
||||
client.DelayStart = false
|
||||
changes = append(changes, model.Changes{
|
||||
DateTime: dt,
|
||||
Actor: "ResetJob",
|
||||
Key: "clients",
|
||||
Action: "reset",
|
||||
Obj: json.RawMessage("\"" + client.Name + "\""),
|
||||
})
|
||||
}
|
||||
allClients = append(allClients, resetClients...)
|
||||
|
||||
// Set delay start with periodic reset
|
||||
err = tx.Model(model.Client{}).
|
||||
Where("enable = true AND delay_start = true AND auto_reset = true AND (Up + Down) > 0").Find(&resetClients).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, client := range resetClients {
|
||||
client.NextReset = dt + (int64(client.ResetDays) * 86400)
|
||||
client.DelayStart = false
|
||||
changes = append(changes, model.Changes{
|
||||
DateTime: dt,
|
||||
Actor: "ResetJob",
|
||||
Key: "clients",
|
||||
Action: "reset",
|
||||
Obj: json.RawMessage("\"" + client.Name + "\""),
|
||||
})
|
||||
}
|
||||
allClients = append(allClients, resetClients...)
|
||||
|
||||
// Set periodic reset
|
||||
err = tx.Model(model.Client{}).
|
||||
Where("delay_start = false AND auto_reset = true AND next_reset < ?", dt).Find(&resetClients).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, client := range resetClients {
|
||||
client.NextReset = dt + (int64(client.ResetDays) * 86400)
|
||||
client.TotalUp += client.Up
|
||||
client.TotalDown += client.Down
|
||||
client.Up = 0
|
||||
client.Down = 0
|
||||
if !client.Enable {
|
||||
client.Enable = true
|
||||
var clientInboundIds []uint
|
||||
json.Unmarshal(client.Inbounds, &clientInboundIds)
|
||||
inboundIds = common.UnionUintArray(inboundIds, clientInboundIds)
|
||||
}
|
||||
}
|
||||
allClients = append(allClients, resetClients...)
|
||||
|
||||
// Save clients
|
||||
if len(allClients) > 0 {
|
||||
err = tx.Save(allClients).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Save changes
|
||||
if len(changes) > 0 {
|
||||
err = tx.Model(model.Changes{}).Create(&changes).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
LastUpdate = dt
|
||||
}
|
||||
return inboundIds, nil
|
||||
}
|
||||
|
||||
func (s *ClientService) findInboundsChanges(tx *gorm.DB, client *model.Client, fillOmitted bool) ([]uint, error) {
|
||||
var err error
|
||||
var oldClient model.Client
|
||||
var oldInboundIds, newInboundIds []uint
|
||||
err = tx.Model(model.Client{}).Where("id = ?", client.Id).First(&oldClient).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fillOmitted {
|
||||
client.Links = oldClient.Links
|
||||
client.Config = oldClient.Config
|
||||
}
|
||||
err = json.Unmarshal(oldClient.Inbounds, &oldInboundIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(client.Inbounds, &newInboundIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check client.Config changes
|
||||
if !bytes.Equal(oldClient.Config, client.Config) ||
|
||||
oldClient.Name != client.Name ||
|
||||
oldClient.Enable != client.Enable {
|
||||
return common.UnionUintArray(oldInboundIds, newInboundIds), nil
|
||||
}
|
||||
|
||||
// Check client.Inbounds changes
|
||||
diffInbounds := common.DiffUintArray(oldInboundIds, newInboundIds)
|
||||
|
||||
return diffInbounds, nil
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/alireza0/s-ui/core"
|
||||
"github.com/alireza0/s-ui/database"
|
||||
"github.com/alireza0/s-ui/database/model"
|
||||
"github.com/alireza0/s-ui/logger"
|
||||
"github.com/alireza0/s-ui/util/common"
|
||||
)
|
||||
|
||||
var (
|
||||
LastUpdate int64
|
||||
corePtr *core.Core
|
||||
startCoreMu sync.Mutex
|
||||
startCoreInProgress bool
|
||||
lastStartFailTime time.Time
|
||||
startCooldown = 15 * time.Second
|
||||
)
|
||||
|
||||
type ConfigService struct {
|
||||
ClientService
|
||||
TlsService
|
||||
SettingService
|
||||
InboundService
|
||||
OutboundService
|
||||
ServicesService
|
||||
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"`
|
||||
Services []json.RawMessage `json:"services"`
|
||||
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) (*[]byte, 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.Services, err = s.ServicesService.GetAllConfig(database.GetDB())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
singboxConfig.Endpoints, err = s.EndpointService.GetAllConfig(database.GetDB())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawConfig, err := json.MarshalIndent(singboxConfig, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rawConfig, nil
|
||||
}
|
||||
|
||||
func (s *ConfigService) StartCore() error {
|
||||
if corePtr.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
startCoreMu.Lock()
|
||||
if startCoreInProgress {
|
||||
startCoreMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
if time.Since(lastStartFailTime) < startCooldown {
|
||||
logger.Info("start core cooldown ", startCooldown/time.Second, " seconds")
|
||||
startCoreMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
startCoreInProgress = true
|
||||
startCoreMu.Unlock()
|
||||
defer func() {
|
||||
startCoreMu.Lock()
|
||||
startCoreInProgress = false
|
||||
startCoreMu.Unlock()
|
||||
}()
|
||||
|
||||
logger.Info("starting core")
|
||||
rawConfig, err := s.GetConfig("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = corePtr.Start(*rawConfig)
|
||||
if err != nil {
|
||||
startCoreMu.Lock()
|
||||
lastStartFailTime = time.Now()
|
||||
startCoreMu.Unlock()
|
||||
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 {
|
||||
startCoreMu.Lock()
|
||||
if startCoreInProgress {
|
||||
startCoreMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
startCoreInProgress = true
|
||||
startCoreMu.Unlock()
|
||||
defer func() {
|
||||
startCoreMu.Lock()
|
||||
startCoreInProgress = false
|
||||
startCoreMu.Unlock()
|
||||
}()
|
||||
|
||||
if corePtr.IsRunning() {
|
||||
if err := corePtr.Stop(); err != nil {
|
||||
logger.Error("restart sing-box err (stop):", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
rawConfig, err := s.GetConfig(string(config))
|
||||
if err != nil {
|
||||
logger.Error("restart sing-box err (get config):", err.Error())
|
||||
return err
|
||||
}
|
||||
if err := corePtr.Start(*rawConfig); err != nil {
|
||||
logger.Error("restart sing-box err (start):", err.Error())
|
||||
return err
|
||||
}
|
||||
logger.Info("sing-box restarted with new config")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ConfigService) StopCore() error {
|
||||
err := corePtr.Stop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info("sing-box stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ConfigService) CheckOutbound(tag string, link string) core.CheckOutboundResult {
|
||||
if tag == "" {
|
||||
return core.CheckOutboundResult{Error: "missing query parameter: tag"}
|
||||
}
|
||||
if corePtr == nil || !corePtr.IsRunning() {
|
||||
return core.CheckOutboundResult{Error: "core not running"}
|
||||
}
|
||||
return core.CheckOutbound(corePtr.GetCtx(), tag, link)
|
||||
}
|
||||
|
||||
func (s *ConfigService) Save(obj string, act string, data json.RawMessage, initUsers string, loginUser string, hostname string) ([]string, error) {
|
||||
var err error
|
||||
var objs []string = []string{obj}
|
||||
|
||||
db := database.GetDB()
|
||||
tx := db.Begin()
|
||||
defer func() {
|
||||
if err == nil {
|
||||
tx.Commit()
|
||||
// Try to start core if it is not running
|
||||
if !corePtr.IsRunning() {
|
||||
s.StartCore()
|
||||
}
|
||||
} else {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
switch obj {
|
||||
case "clients":
|
||||
var inboundIds []uint
|
||||
inboundIds, err = s.ClientService.Save(tx, act, data, hostname)
|
||||
if err == nil && len(inboundIds) > 0 {
|
||||
objs = append(objs, "inbounds")
|
||||
err = s.InboundService.RestartInbounds(tx, inboundIds)
|
||||
if err != nil {
|
||||
return nil, common.NewErrorf("failed to update users for inbounds: %v", err)
|
||||
}
|
||||
}
|
||||
case "tls":
|
||||
err = s.TlsService.Save(tx, act, data, hostname)
|
||||
objs = append(objs, "clients", "inbounds")
|
||||
case "inbounds":
|
||||
err = s.InboundService.Save(tx, act, data, initUsers, hostname)
|
||||
objs = append(objs, "clients")
|
||||
case "outbounds":
|
||||
err = s.OutboundService.Save(tx, act, data)
|
||||
case "services":
|
||||
err = s.ServicesService.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
|
||||
}
|
||||
configData := make(json.RawMessage, len(data))
|
||||
copy(configData, data)
|
||||
go func() { _ = s.restartCoreWithConfig(configData) }()
|
||||
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
|
||||
}
|
||||
|
||||
LastUpdate = time.Now().Unix()
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/alireza0/s-ui/database"
|
||||
"github.com/alireza0/s-ui/database/model"
|
||||
"github.com/alireza0/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
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/alireza0/s-ui/database"
|
||||
"github.com/alireza0/s-ui/database/model"
|
||||
"github.com/alireza0/s-ui/util"
|
||||
"github.com/alireza0/s-ui/util/common"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type InboundService struct {
|
||||
ClientService
|
||||
}
|
||||
|
||||
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
|
||||
ss_managed := false
|
||||
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)
|
||||
}
|
||||
if inbound.Type == "shadowsocks" {
|
||||
json.Unmarshal(restFields["managed"], &ss_managed)
|
||||
}
|
||||
}
|
||||
if s.hasUser(inbound.Type) &&
|
||||
!(inbound.Type == "shadowtls" && shadowtls_version < 3) &&
|
||||
!(inbound.Type == "shadowsocks" && ss_managed) {
|
||||
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
|
||||
}
|
||||
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) error {
|
||||
var err error
|
||||
|
||||
switch act {
|
||||
case "new", "edit":
|
||||
var inbound model.Inbound
|
||||
err = inbound.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if inbound.TlsId > 0 {
|
||||
err = tx.Model(model.Tls{}).Where("id = ?", inbound.TlsId).Find(&inbound.Tls).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var oldTag string
|
||||
if act == "edit" {
|
||||
err = tx.Model(model.Inbound{}).Select("tag").Where("id = ?", inbound.Id).Find(&oldTag).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if corePtr.IsRunning() {
|
||||
if act == "edit" {
|
||||
err = corePtr.RemoveInbound(oldTag)
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
inboundConfig, err := inbound.MarshalJSON()
|
||||
if err != nil {
|
||||
return 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 err
|
||||
}
|
||||
|
||||
err = corePtr.AddInbound(inboundConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = util.FillOutJson(&inbound, hostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tx.Save(&inbound).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch act {
|
||||
case "new":
|
||||
err = s.ClientService.UpdateClientsOnInboundAdd(tx, initUserIds, inbound.Id, hostname)
|
||||
case "edit":
|
||||
err = s.ClientService.UpdateLinksByInboundChange(tx, &[]model.Inbound{inbound}, hostname, oldTag)
|
||||
}
|
||||
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.RemoveInbound(tag)
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var id uint
|
||||
err = tx.Model(model.Inbound{}).Select("id").Where("tag = ?", tag).Scan(&id).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.ClientService.UpdateClientsOnInboundDelete(tx, id, tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tx.Where("tag = ?", tag).Delete(model.Inbound{}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return common.NewErrorf("unknown action: %s", act)
|
||||
}
|
||||
return 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) hasUser(inboundType string) bool {
|
||||
switch inboundType {
|
||||
case "mixed", "socks", "http", "shadowsocks", "vmess", "trojan", "naive", "hysteria", "shadowtls", "tuic", "hysteria2", "vless", "anytls":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *InboundService) fetchUsers(db *gorm.DB, inboundType string, condition string, inbound map[string]interface{}) ([]json.RawMessage, error) {
|
||||
if inboundType == "shadowtls" {
|
||||
version, _ := inbound["version"].(float64)
|
||||
if int(version) < 3 {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
if inboundType == "shadowsocks" {
|
||||
method, _ := inbound["method"].(string)
|
||||
if method == "2022-blake3-aes-128-gcm" {
|
||||
inboundType = "shadowsocks16"
|
||||
}
|
||||
}
|
||||
|
||||
var users []string
|
||||
|
||||
err := db.Raw(
|
||||
fmt.Sprintf(`SELECT json_extract(clients.config, "$.%s")
|
||||
FROM clients WHERE enable = true AND %s`,
|
||||
inboundType, condition)).Scan(&users).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var usersJson []json.RawMessage
|
||||
for _, user := range users {
|
||||
if inboundType == "vless" && inbound["tls"] == nil {
|
||||
user = strings.Replace(user, "xtls-rprx-vision", "", -1)
|
||||
}
|
||||
usersJson = append(usersJson, json.RawMessage(user))
|
||||
}
|
||||
return usersJson, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) addUsers(db *gorm.DB, inboundJson []byte, inboundId uint, inboundType string) ([]byte, error) {
|
||||
if !s.hasUser(inboundType) {
|
||||
return inboundJson, nil
|
||||
}
|
||||
|
||||
var inbound map[string]interface{}
|
||||
err := json.Unmarshal(inboundJson, &inbound)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
condition := fmt.Sprintf("%d IN (SELECT json_each.value FROM json_each(clients.inbounds))", inboundId)
|
||||
inbound["users"], err = s.fetchUsers(db, inboundType, condition, inbound)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if !s.hasUser(inboundType) {
|
||||
return inboundJson, nil
|
||||
}
|
||||
|
||||
var inbound map[string]interface{}
|
||||
err := json.Unmarshal(inboundJson, &inbound)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
condition := fmt.Sprintf("id IN (%s)", strings.Join(ClientIds, ","))
|
||||
inbound["users"], err = s.fetchUsers(db, inboundType, condition, inbound)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(inbound)
|
||||
}
|
||||
|
||||
func (s *InboundService) RestartInbounds(tx *gorm.DB, ids []uint) error {
|
||||
if !corePtr.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
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
|
||||
}
|
||||
// Close all existing connections
|
||||
corePtr.GetInstance().ConnTracker().CloseConnByInbound(inbound.Tag)
|
||||
|
||||
inboundConfig, err := inbound.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inboundConfig, err = s.addUsers(tx, inboundConfig, inbound.Id, inbound.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = corePtr.AddInbound(inboundConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/alireza0/s-ui/database"
|
||||
"github.com/alireza0/s-ui/database/model"
|
||||
"github.com/alireza0/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
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/alireza0/s-ui/logger"
|
||||
)
|
||||
|
||||
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)
|
||||
if runtime.GOOS == "windows" {
|
||||
err = p.Kill()
|
||||
} else {
|
||||
err = p.Signal(syscall.SIGHUP)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Error("send signal SIGHUP failed:", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alireza0/s-ui/config"
|
||||
"github.com/alireza0/s-ui/database"
|
||||
"github.com/alireza0/s-ui/database/model"
|
||||
"github.com/alireza0/s-ui/logger"
|
||||
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
"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 "dsk":
|
||||
status["dsk"] = s.GetDiskInfo()
|
||||
case "dio":
|
||||
status["dio"] = s.GetDiskIO()
|
||||
case "swp":
|
||||
status["swp"] = s.GetSwapInfo()
|
||||
case "net":
|
||||
status["net"] = s.GetNetInfo()
|
||||
case "sys":
|
||||
status["sys"] = s.GetSystemInfo()
|
||||
case "sbd":
|
||||
status["sbd"] = s.GetSingboxInfo()
|
||||
case "db":
|
||||
status["db"] = s.GetDatabaseInfo()
|
||||
}
|
||||
}
|
||||
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) 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) GetDiskInfo() map[string]interface{} {
|
||||
info := make(map[string]interface{}, 0)
|
||||
diskInfo, err := disk.Usage("/")
|
||||
if err != nil {
|
||||
logger.Warning("get disk usage failed:", err)
|
||||
} else {
|
||||
info["current"] = diskInfo.Used
|
||||
info["total"] = diskInfo.Total
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func (s *ServerService) GetDiskIO() map[string]interface{} {
|
||||
info := make(map[string]interface{}, 0)
|
||||
ioStats, err := disk.IOCounters()
|
||||
if err != nil {
|
||||
logger.Warning("get disk io counters failed:", err)
|
||||
} else if len(ioStats) > 0 {
|
||||
infoR, infoW := uint64(0), uint64(0)
|
||||
for _, ioStat := range ioStats {
|
||||
infoR += ioStat.ReadBytes
|
||||
infoW += ioStat.WriteBytes
|
||||
}
|
||||
info["read"] = infoR
|
||||
info["write"] = infoW
|
||||
} else {
|
||||
logger.Warning("can not find disk io counters")
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func (s *ServerService) GetSwapInfo() map[string]interface{} {
|
||||
info := make(map[string]interface{}, 0)
|
||||
swapInfo, err := mem.SwapMemory()
|
||||
if err != nil {
|
||||
logger.Warning("get swap memory failed:", err)
|
||||
} else {
|
||||
info["current"] = swapInfo.Used
|
||||
info["total"] = swapInfo.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
|
||||
info["bootTime"], _ = host.BootTime()
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func (s *ServerService) GetLogs(count string, level string) []string {
|
||||
c, err := strconv.Atoi(count)
|
||||
if err != nil {
|
||||
c = 10
|
||||
}
|
||||
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(serverName string) []string {
|
||||
configPem, keyPem, err := tls.ECHKeygenDefault(serverName)
|
||||
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.GenerateCertificate(nil, nil, 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()}
|
||||
}
|
||||
|
||||
func (s *ServerService) GetDatabaseInfo() map[string]int64 {
|
||||
info := make(map[string]int64, 0)
|
||||
db := database.GetDB()
|
||||
if db == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var clientsCount, inboundsCount, outboundsCount, servicesCount, endpointsCount, clientUp, clientDown int64
|
||||
|
||||
db.Model(&model.Client{}).Count(&clientsCount)
|
||||
db.Model(&model.Inbound{}).Count(&inboundsCount)
|
||||
db.Model(&model.Outbound{}).Count(&outboundsCount)
|
||||
db.Model(&model.Service{}).Count(&servicesCount)
|
||||
db.Model(&model.Endpoint{}).Count(&endpointsCount)
|
||||
db.Model(&model.Client{}).Select("COALESCE(SUM(up+total_up),0)").Scan(&clientUp)
|
||||
db.Model(&model.Client{}).Select("COALESCE(SUM(down+total_down),0)").Scan(&clientDown)
|
||||
|
||||
info["clients"] = clientsCount
|
||||
info["inbounds"] = inboundsCount
|
||||
info["outbounds"] = outboundsCount
|
||||
info["services"] = servicesCount
|
||||
info["endpoints"] = endpointsCount
|
||||
info["clientUp"] = clientUp
|
||||
info["clientDown"] = clientDown
|
||||
|
||||
return info
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/alireza0/s-ui/database"
|
||||
"github.com/alireza0/s-ui/database/model"
|
||||
"github.com/alireza0/s-ui/util/common"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ServicesService struct{}
|
||||
|
||||
func (s *ServicesService) GetAll() (*[]map[string]interface{}, error) {
|
||||
db := database.GetDB()
|
||||
services := []model.Service{}
|
||||
err := db.Model(model.Service{}).Scan(&services).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []map[string]interface{}
|
||||
for _, srv := range services {
|
||||
srvData := map[string]interface{}{
|
||||
"id": srv.Id,
|
||||
"type": srv.Type,
|
||||
"tag": srv.Tag,
|
||||
"tls_id": srv.TlsId,
|
||||
}
|
||||
if srv.Options != nil {
|
||||
var restFields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(srv.Options, &restFields); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range restFields {
|
||||
srvData[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
data = append(data, srvData)
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (s *ServicesService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
|
||||
var servicesJson []json.RawMessage
|
||||
var services []*model.Service
|
||||
err := db.Model(model.Service{}).Preload("Tls").Find(&services).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, srv := range services {
|
||||
srvJson, err := srv.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
servicesJson = append(servicesJson, srvJson)
|
||||
}
|
||||
return servicesJson, nil
|
||||
}
|
||||
|
||||
func (s *ServicesService) Save(tx *gorm.DB, act string, data json.RawMessage) error {
|
||||
var err error
|
||||
|
||||
switch act {
|
||||
case "new", "edit":
|
||||
var srv model.Service
|
||||
err = srv.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if srv.TlsId > 0 {
|
||||
err = tx.Model(model.Tls{}).Where("id = ?", srv.TlsId).Find(&srv.Tls).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if corePtr.IsRunning() {
|
||||
configData, err := srv.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if act == "edit" {
|
||||
var oldTag string
|
||||
err = tx.Model(model.Service{}).Select("tag").Where("id = ?", srv.Id).Find(&oldTag).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = corePtr.RemoveService(oldTag)
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = corePtr.AddService(configData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Save(&srv).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.RemoveService(tag)
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = tx.Where("tag = ?", tag).Delete(model.Service{}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return common.NewErrorf("unknown action: %s", act)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServicesService) RestartServices(tx *gorm.DB, ids []uint) error {
|
||||
if !corePtr.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
var services []*model.Service
|
||||
err := tx.Model(model.Service{}).Preload("Tls").Where("id in ?", ids).Find(&services).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, srv := range services {
|
||||
err = corePtr.RemoveService(srv.Tag)
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
return err
|
||||
}
|
||||
srvConfig, err := srv.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = corePtr.AddService(srvConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alireza0/s-ui/config"
|
||||
"github.com/alireza0/s-ui/database"
|
||||
"github.com/alireza0/s-ui/database/model"
|
||||
"github.com/alireza0/s-ui/logger"
|
||||
"github.com/alireza0/s-ui/util/common"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var defaultConfig = `{
|
||||
"log": {
|
||||
"level": "info"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [],
|
||||
"rules": []
|
||||
},
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"action": "sniff"
|
||||
},
|
||||
{
|
||||
"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": "",
|
||||
"subClashExt": "",
|
||||
"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
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
l = "Local"
|
||||
}
|
||||
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 existence 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 += "/"
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all stats if it is set to 0
|
||||
if key == "trafficAge" && obj == "0" {
|
||||
err = tx.Where("id > 0").Delete(model.Stats{}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
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) GetSubClashExt() (string, error) {
|
||||
return s.getString("subClashExt")
|
||||
}
|
||||
|
||||
func (s *SettingService) fileExists(path string) error {
|
||||
_, err := os.Stat(path)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/alireza0/s-ui/database"
|
||||
"github.com/alireza0/s-ui/database/model"
|
||||
|
||||
"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(enableTraffic bool) error {
|
||||
if corePtr == nil || !corePtr.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
box := corePtr.GetInstance()
|
||||
if box == nil {
|
||||
return nil
|
||||
}
|
||||
st := box.StatsTracker()
|
||||
if st == nil {
|
||||
return nil
|
||||
}
|
||||
stats := st.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !enableTraffic {
|
||||
return nil
|
||||
}
|
||||
return tx.Create(&stats).Error
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
result = s.downsampleStats(result, 60) // 60 rows for 30 buckets
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// downsampleStats reduces stats to maxRows rows.
|
||||
// Each bucket outputs two rows (direction false and true) with average Traffic.
|
||||
func (s *StatsService) downsampleStats(stats []model.Stats, maxRows int) []model.Stats {
|
||||
if len(stats) <= maxRows {
|
||||
return stats
|
||||
}
|
||||
numBuckets := int(maxRows / 2)
|
||||
sort.Slice(stats, func(i, j int) bool { return stats[i].DateTime < stats[j].DateTime })
|
||||
timeMin, timeMax := stats[0].DateTime, stats[len(stats)-1].DateTime
|
||||
bucketSpan := (timeMax - timeMin) / int64(numBuckets)
|
||||
if bucketSpan == 0 {
|
||||
bucketSpan = 1
|
||||
}
|
||||
downsampled := make([]model.Stats, 0, maxRows)
|
||||
for i := 0; i < numBuckets; i++ {
|
||||
bucketStart := timeMin + int64(i)*bucketSpan
|
||||
bucketEnd := timeMin + int64(i+1)*bucketSpan
|
||||
if i == numBuckets-1 {
|
||||
bucketEnd = timeMax + 1
|
||||
}
|
||||
for _, dir := range []bool{false, true} {
|
||||
var sum int64
|
||||
var count int
|
||||
for _, r := range stats {
|
||||
if r.DateTime >= bucketStart && r.DateTime < bucketEnd && r.Direction == dir {
|
||||
sum += r.Traffic
|
||||
count++
|
||||
}
|
||||
}
|
||||
avg := int64(0)
|
||||
if count > 0 {
|
||||
avg = sum / int64(count)
|
||||
}
|
||||
downsampled = append(downsampled, model.Stats{
|
||||
DateTime: bucketStart,
|
||||
Resource: stats[0].Resource,
|
||||
Tag: stats[0].Tag,
|
||||
Direction: dir,
|
||||
Traffic: avg,
|
||||
})
|
||||
}
|
||||
}
|
||||
return downsampled
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/alireza0/s-ui/database"
|
||||
"github.com/alireza0/s-ui/database/model"
|
||||
"github.com/alireza0/s-ui/util/common"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type TlsService struct {
|
||||
InboundService
|
||||
ServicesService
|
||||
}
|
||||
|
||||
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, hostname string) error {
|
||||
var err error
|
||||
|
||||
switch action {
|
||||
case "new", "edit":
|
||||
var tls model.Tls
|
||||
err = json.Unmarshal(data, &tls)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tx.Save(&tls).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if action == "edit" {
|
||||
var inbounds []model.Inbound
|
||||
err = tx.Model(model.Inbound{}).Preload("Tls").Where("tls_id = ?", tls.Id).Find(&inbounds).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(inbounds) > 0 {
|
||||
err = s.ClientService.UpdateLinksByInboundChange(tx, &inbounds, hostname, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var inboundIds []uint
|
||||
for _, inbound := range inbounds {
|
||||
inboundIds = append(inboundIds, inbound.Id)
|
||||
}
|
||||
err = s.InboundService.UpdateOutJsons(tx, inboundIds, hostname)
|
||||
if err != nil {
|
||||
return common.NewError("unable to update out_json of inbounds: ", err.Error())
|
||||
}
|
||||
err = s.InboundService.RestartInbounds(tx, inboundIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var serviceIds []uint
|
||||
err = tx.Model(model.Service{}).Where("tls_id = ?", tls.Id).Scan(&serviceIds).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(serviceIds) > 0 {
|
||||
err = s.ServicesService.RestartServices(tx, serviceIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
case "del":
|
||||
var id uint
|
||||
err = json.Unmarshal(data, &id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var inboundCount int64
|
||||
err = tx.Model(model.Inbound{}).Where("tls_id = ?", id).Count(&inboundCount).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var serviceCount int64
|
||||
err = tx.Model(model.Service{}).Where("tls_id = ?", id).Count(&serviceCount).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if inboundCount > 0 || serviceCount > 0 {
|
||||
return common.NewError("tls in use")
|
||||
}
|
||||
err = tx.Where("id = ?", id).Delete(model.Tls{}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/alireza0/s-ui/database"
|
||||
"github.com/alireza0/s-ui/database/model"
|
||||
"github.com/alireza0/s-ui/logger"
|
||||
"github.com/alireza0/s-ui/util/common"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (s *UserService) LoadTokens() ([]byte, error) {
|
||||
db := database.GetDB()
|
||||
var tokens []model.Tokens
|
||||
err := db.Model(model.Tokens{}).Preload("User").Where("expiry == 0 or expiry > ?", time.Now().Unix()).Find(&tokens).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var result []map[string]interface{}
|
||||
for _, t := range tokens {
|
||||
result = append(result, map[string]interface{}{
|
||||
"token": t.Token,
|
||||
"expiry": t.Expiry,
|
||||
"username": t.User.Username,
|
||||
})
|
||||
}
|
||||
jsonResult, _ := json.MarshalIndent(result, "", " ")
|
||||
return jsonResult, nil
|
||||
}
|
||||
|
||||
func (s *UserService) GetUserTokens(username string) (*[]model.Tokens, error) {
|
||||
db := database.GetDB()
|
||||
var token []model.Tokens
|
||||
err := db.Model(model.Tokens{}).Select("id,desc,'****' as token,expiry,user_id").Where("user_id = (select id from users where username = ?)", username).Find(&token).Error
|
||||
if err != nil && !database.IsNotFound(err) {
|
||||
println(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
func (s *UserService) AddToken(username string, expiry int64, desc string) (string, error) {
|
||||
db := database.GetDB()
|
||||
var userId uint
|
||||
err := db.Model(model.User{}).Where("username = ?", username).Select("id").Scan(&userId).Error
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if expiry > 0 {
|
||||
expiry = expiry*86400 + time.Now().Unix()
|
||||
}
|
||||
token := &model.Tokens{
|
||||
Token: common.Random(32),
|
||||
Desc: desc,
|
||||
Expiry: expiry,
|
||||
UserId: userId,
|
||||
}
|
||||
err = db.Create(token).Error
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return token.Token, nil
|
||||
}
|
||||
|
||||
func (s *UserService) DeleteToken(id string) error {
|
||||
db := database.GetDB()
|
||||
return db.Model(model.Tokens{}).Where("id = ?", id).Delete(&model.Tokens{}).Error
|
||||
}
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/alireza0/s-ui/database/model"
|
||||
"github.com/alireza0/s-ui/logger"
|
||||
"github.com/alireza0/s-ui/util/common"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
type WarpService struct{}
|
||||
|
||||
func (s *WarpService) getWarpInfo(deviceId string, accessToken string) ([]byte, error) {
|
||||
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s", deviceId)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
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 || resp.StatusCode != 200 {
|
||||
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
|
||||
}
|
||||
|
||||
warpInfo, err := s.getWarpInfo(deviceId, token)
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
warpData := map[string]interface{}{
|
||||
"access_token": token,
|
||||
"device_id": deviceId,
|
||||
"license_key": license,
|
||||
}
|
||||
|
||||
ep.Ext, err = json.MarshalIndent(warpData, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user