Подготовить HY2XS к production-сборке
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"hy2xs-admin/dao"
|
||||
"hy2xs-admin/model/bo"
|
||||
"hy2xs-admin/model/constant"
|
||||
"hy2xs-admin/model/dto"
|
||||
"hy2xs-admin/model/entity"
|
||||
"hy2xs-admin/model/vo"
|
||||
)
|
||||
|
||||
func Login(username string, pass string) (string, error) {
|
||||
account, err := dao.GetAccount("username = ? and pass = ? and role = 'admin' and deleted = 0", username, pass)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
accountBo := bo.AccountBo{
|
||||
Id: *account.Id,
|
||||
Username: *account.Username,
|
||||
Roles: []string{*account.Role},
|
||||
Deleted: *account.Deleted,
|
||||
}
|
||||
return GenToken(accountBo)
|
||||
}
|
||||
|
||||
func PageAccount(accountPageDto dto.AccountPageDto) ([]entity.Account, int64, error) {
|
||||
return dao.PageAccount(accountPageDto)
|
||||
}
|
||||
|
||||
func SaveAccount(account entity.Account) error {
|
||||
_, err := dao.SaveAccount(account)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteAccount(ids []int64) error {
|
||||
return dao.DeleteAccount(ids)
|
||||
}
|
||||
|
||||
func UpdateAccount(account entity.Account) error {
|
||||
updates := map[string]interface{}{}
|
||||
if account.Username != nil && *account.Username != "" {
|
||||
updates["username"] = *account.Username
|
||||
}
|
||||
if account.Pass != nil && *account.Pass != "" {
|
||||
updates["pass"] = *account.Pass
|
||||
}
|
||||
if account.ConPass != nil && *account.ConPass != "" {
|
||||
updates["con_pass"] = fmt.Sprintf("%s.%s", *account.Username, *account.ConPass)
|
||||
}
|
||||
if account.Quota != nil {
|
||||
updates["quota"] = *account.Quota
|
||||
}
|
||||
if account.ExpireTime != nil {
|
||||
updates["expire_time"] = *account.ExpireTime
|
||||
}
|
||||
if account.Download != nil {
|
||||
updates["download"] = *account.Download
|
||||
}
|
||||
if account.Upload != nil {
|
||||
updates["upload"] = *account.Upload
|
||||
}
|
||||
if account.DeviceNo != nil {
|
||||
updates["device_no"] = *account.DeviceNo
|
||||
}
|
||||
if account.Deleted != nil {
|
||||
updates["deleted"] = *account.Deleted
|
||||
}
|
||||
if account.LoginAt != nil && *account.LoginAt > 0 {
|
||||
updates["login_at"] = *account.LoginAt
|
||||
}
|
||||
if account.ConAt != nil && *account.ConAt > 0 {
|
||||
updates["con_at"] = *account.ConAt
|
||||
}
|
||||
if account.Remark != nil {
|
||||
updates["remark"] = *account.Remark
|
||||
}
|
||||
return dao.UpdateAccount([]int64{*account.Id}, updates)
|
||||
}
|
||||
|
||||
func ResetTraffic(id int64) error {
|
||||
return dao.UpdateAccount([]int64{id}, map[string]interface{}{"download": 0, "upload": 0})
|
||||
}
|
||||
|
||||
func ExistAccountUsername(username string, id int64) bool {
|
||||
var err error
|
||||
if id != 0 {
|
||||
_, err = dao.GetAccount("username = ? and id != ?", username, id)
|
||||
} else {
|
||||
_, err = dao.GetAccount("username = ?", username)
|
||||
}
|
||||
if err != nil {
|
||||
if err.Error() == constant.WrongPassword {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func GetAccount(id int64) (entity.Account, error) {
|
||||
return dao.GetAccount("id = ?", id)
|
||||
}
|
||||
|
||||
func ListExportAccount() ([]bo.AccountExport, error) {
|
||||
accounts, err := dao.ListAccount(nil, nil)
|
||||
if err != nil {
|
||||
return nil, errors.New(constant.SysError)
|
||||
}
|
||||
var accountExports []bo.AccountExport
|
||||
for _, item := range accounts {
|
||||
accountExport := bo.AccountExport{
|
||||
Id: *item.Id,
|
||||
Username: *item.Username,
|
||||
Pass: *item.Pass,
|
||||
ConPass: *item.ConPass,
|
||||
Quota: *item.Quota,
|
||||
Download: *item.Download,
|
||||
Upload: *item.Upload,
|
||||
ExpireTime: *item.ExpireTime,
|
||||
DeviceNo: *item.DeviceNo,
|
||||
KickUtilTime: *item.KickUtilTime,
|
||||
Role: *item.Role,
|
||||
Deleted: *item.Deleted,
|
||||
CreateTime: *item.CreateTime,
|
||||
UpdateTime: *item.UpdateTime,
|
||||
LoginAt: *item.LoginAt,
|
||||
ConAt: *item.ConAt,
|
||||
Remark: *item.Remark,
|
||||
}
|
||||
accountExports = append(accountExports, accountExport)
|
||||
}
|
||||
return accountExports, nil
|
||||
}
|
||||
|
||||
func ReleaseKickAccount(id int64) error {
|
||||
return dao.UpdateAccount([]int64{id}, map[string]interface{}{"kick_util_time": 0})
|
||||
}
|
||||
|
||||
func UpsertAccount(accounts []entity.Account) error {
|
||||
return dao.UpsertAccount(accounts)
|
||||
}
|
||||
|
||||
func GetAccountInfo(c *gin.Context) (vo.AccountInfoVo, error) {
|
||||
myClaims, err := ParseToken(GetToken(c))
|
||||
if err != nil {
|
||||
return vo.AccountInfoVo{}, err
|
||||
}
|
||||
if myClaims.AccountBo.Deleted != 0 {
|
||||
return vo.AccountInfoVo{}, errors.New("this account has been disabled")
|
||||
}
|
||||
return vo.AccountInfoVo{
|
||||
Id: myClaims.AccountBo.Id,
|
||||
Username: myClaims.AccountBo.Username,
|
||||
Roles: myClaims.AccountBo.Roles,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
"hy2xs-admin/dao"
|
||||
"hy2xs-admin/model/bo"
|
||||
"hy2xs-admin/model/constant"
|
||||
"hy2xs-admin/model/entity"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func UpdateConfig(key string, value string) error {
|
||||
if key == constant.Hysteria2Enable {
|
||||
if value == "1" {
|
||||
hysteria2Config, err := GetHysteria2Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hysteria2Config.Listen == nil || *hysteria2Config.Listen == "" {
|
||||
logrus.Errorf("hysteria2 config is empty")
|
||||
return errors.New("hysteria2 config is empty")
|
||||
}
|
||||
// 启动Hysteria2
|
||||
if err = StartHysteria2(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := StopHysteria2(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return dao.UpdateConfig([]string{key}, map[string]interface{}{"value": value})
|
||||
}
|
||||
|
||||
func GetConfig(key string) (entity.Config, error) {
|
||||
return dao.GetConfig("key = ?", key)
|
||||
}
|
||||
|
||||
func ListConfig(keys []string) ([]entity.Config, error) {
|
||||
return dao.ListConfig("key in ?", keys)
|
||||
}
|
||||
|
||||
func ListConfigNotIn(keys []string) ([]entity.Config, error) {
|
||||
return dao.ListConfig("key not in ?", keys)
|
||||
}
|
||||
|
||||
func GetHysteria2Config() (bo.Hysteria2ServerConfig, error) {
|
||||
var serverConfig bo.Hysteria2ServerConfig
|
||||
config, err := dao.GetConfig("key = ?", constant.Hysteria2Config)
|
||||
if err != nil {
|
||||
return serverConfig, err
|
||||
}
|
||||
if config.Value == nil || strings.TrimSpace(*config.Value) == "" {
|
||||
content, readErr := os.ReadFile(constant.Hysteria2ConfigPath)
|
||||
if readErr != nil {
|
||||
return serverConfig, readErr
|
||||
}
|
||||
if err = yaml.Unmarshal(content, &serverConfig); err != nil {
|
||||
return serverConfig, err
|
||||
}
|
||||
return serverConfig, nil
|
||||
}
|
||||
if err = yaml.Unmarshal([]byte(*config.Value), &serverConfig); err != nil {
|
||||
return serverConfig, err
|
||||
}
|
||||
return serverConfig, nil
|
||||
}
|
||||
|
||||
func UpdateHysteria2Config(hysteria2ServerConfig bo.Hysteria2ServerConfig) error {
|
||||
// 默认值
|
||||
config, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.JwtSecret})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var hUIWebPort string
|
||||
var jwtSecret string
|
||||
for _, item := range config {
|
||||
if *item.Key == constant.HUIWebPort {
|
||||
hUIWebPort = *item.Value
|
||||
} else if *item.Key == constant.JwtSecret {
|
||||
jwtSecret = *item.Value
|
||||
}
|
||||
}
|
||||
|
||||
if hUIWebPort == "" || jwtSecret == "" {
|
||||
logrus.Errorf("hUIWebPort or jwtSecret is nil")
|
||||
return errors.New(constant.SysError)
|
||||
}
|
||||
|
||||
authHttpUrl, err := GetAuthHttpUrl()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authType := "http"
|
||||
authHttpInsecure := true
|
||||
var auth bo.ServerConfigAuth
|
||||
auth.Type = &authType
|
||||
var http bo.ServerConfigAuthHTTP
|
||||
http.URL = &authHttpUrl
|
||||
http.Insecure = &authHttpInsecure
|
||||
auth.HTTP = &http
|
||||
hysteria2ServerConfig.Auth = &auth
|
||||
hysteria2ServerConfig.TrafficStats.Secret = &jwtSecret
|
||||
|
||||
yamlConfig, err := yaml.Marshal(&hysteria2ServerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dao.UpdateConfig([]string{constant.Hysteria2Config}, map[string]interface{}{"value": string(yamlConfig)})
|
||||
}
|
||||
|
||||
func SetHysteria2Config(hysteria2ServerConfig bo.Hysteria2ServerConfig) error {
|
||||
config, err := yaml.Marshal(&hysteria2ServerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dao.UpdateConfig([]string{constant.Hysteria2Config}, map[string]interface{}{"value": string(config)})
|
||||
}
|
||||
|
||||
func UpsertConfig(configs []entity.Config) error {
|
||||
return dao.UpsertConfig(configs)
|
||||
}
|
||||
|
||||
func GetHysteria2ApiPort() (int64, error) {
|
||||
hysteria2Config, err := GetHysteria2Config()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if hysteria2Config.TrafficStats == nil || hysteria2Config.TrafficStats.Listen == nil {
|
||||
errMsg := "hysteria2 Traffic Stats API (HTTP) Listen is nil"
|
||||
logrus.Errorf(errMsg)
|
||||
return 0, errors.New(errMsg)
|
||||
}
|
||||
apiPort, err := strconv.ParseInt(strings.Split(*hysteria2Config.TrafficStats.Listen, ":")[1], 10, 64)
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("apiPort: %s is invalid", *hysteria2Config.TrafficStats.Listen)
|
||||
logrus.Errorf(errMsg)
|
||||
return 0, errors.New(errMsg)
|
||||
}
|
||||
return apiPort, nil
|
||||
}
|
||||
|
||||
func GetPortAndCert() (int64, string, string, error) {
|
||||
configs, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.HUICrtPath, constant.HUIKeyPath})
|
||||
if err != nil {
|
||||
return 0, "", "", err
|
||||
}
|
||||
port := ""
|
||||
crtPath := ""
|
||||
keyPath := ""
|
||||
for _, config := range configs {
|
||||
value := *config.Value
|
||||
if *config.Key == constant.HUIWebPort {
|
||||
port = value
|
||||
} else if *config.Key == constant.HUICrtPath {
|
||||
crtPath = value
|
||||
} else if *config.Key == constant.HUIKeyPath {
|
||||
keyPath = value
|
||||
}
|
||||
}
|
||||
|
||||
portInt, err := strconv.ParseInt(port, 10, 64)
|
||||
if err != nil {
|
||||
logrus.Errorf("port: %s is invalid", port)
|
||||
return 0, "", "", errors.New(fmt.Sprintf("port: %s is invalid", port))
|
||||
}
|
||||
|
||||
return portInt, crtPath, keyPath, nil
|
||||
}
|
||||
|
||||
func GetAuthHttpUrl() (string, error) {
|
||||
port, crtPath, keyPath, err := GetPortAndCert()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
protocol := "http"
|
||||
if crtPath != "" && keyPath != "" {
|
||||
protocol = "https"
|
||||
}
|
||||
config, err := dao.GetConfig("key = ?", constant.HUIWebContext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
webContext := ""
|
||||
if config.Value != nil && *config.Value != "/" && strings.HasPrefix(*config.Value, "/") {
|
||||
webContext = *config.Value
|
||||
}
|
||||
return fmt.Sprintf("%s://127.0.0.1:%d%s/hui/hysteria2/auth", protocol, port, webContext), nil
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"hy2xs-admin/dao"
|
||||
"hy2xs-admin/model/bo"
|
||||
"hy2xs-admin/model/constant"
|
||||
"hy2xs-admin/proxy"
|
||||
"hy2xs-admin/util"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var trafficMutex sync.Mutex
|
||||
var kickMutex sync.Mutex
|
||||
|
||||
func CronHandleAccount() {
|
||||
go func() {
|
||||
hysteriaEnable, err := dao.GetConfig("key = ?", constant.Hysteria2Enable)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if hysteriaEnable.Value != nil && *hysteriaEnable.Value == "1" {
|
||||
apiPort, err := GetHysteria2ApiPort()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 保存流量数据
|
||||
go saveAccountTraffic(apiPort, *jwtSecretConfig.Value)
|
||||
|
||||
// 踢下线
|
||||
go kickAccount(apiPort, *jwtSecretConfig.Value)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func CronResetTraffic() {
|
||||
accounts, err := dao.ListAccount(nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var ids []int64
|
||||
for _, item := range accounts {
|
||||
ids = append(ids, *item.Id)
|
||||
}
|
||||
idsList := util.SplitArr(ids, 100)
|
||||
for _, item := range idsList {
|
||||
if err := dao.UpdateAccount(item, map[string]interface{}{"download": 0, "upload": 0}); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func saveAccountTraffic(apiPort int64, jwtSecret string) {
|
||||
if !trafficMutex.TryLock() {
|
||||
return
|
||||
}
|
||||
defer trafficMutex.Unlock()
|
||||
|
||||
hysteria2TrafficTime, err := dao.GetConfig("key = ?", constant.Hysteria2TrafficTime)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
hysteria2TrafficTimeFloat, err := strconv.ParseFloat(*hysteria2TrafficTime.Value, 64)
|
||||
if err != nil {
|
||||
logrus.Errorf("hysteria2TrafficTime string conv int64 err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
users, err := proxy.NewHysteria2Api(apiPort).ListUsers(true, jwtSecret)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(users) > 0 {
|
||||
userLists := util.SplitMap(users, 10)
|
||||
var wg sync.WaitGroup
|
||||
for _, userList := range userLists {
|
||||
wg.Add(1)
|
||||
go func(userList map[string]bo.Hysteria2UserTraffic) {
|
||||
defer wg.Done()
|
||||
for username, traffic := range userList {
|
||||
if err = dao.UpdateAccountTraffic(username, int64(float64(traffic.Rx)*hysteria2TrafficTimeFloat), int64(float64(traffic.Tx)*hysteria2TrafficTimeFloat)); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}(userList)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func kickAccount(apiPort int64, jwtSecret string) {
|
||||
if !kickMutex.TryLock() {
|
||||
return
|
||||
}
|
||||
defer kickMutex.Unlock()
|
||||
|
||||
users, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(jwtSecret)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(users) > 0 {
|
||||
i := 0
|
||||
usernames := make([]string, len(users))
|
||||
for k := range users {
|
||||
usernames[i] = k
|
||||
i++
|
||||
}
|
||||
usernameLists := util.SplitArr(usernames, 10)
|
||||
var wg sync.WaitGroup
|
||||
for _, usernameList := range usernameLists {
|
||||
wg.Add(1)
|
||||
go func(usernameList []string) {
|
||||
defer wg.Done()
|
||||
now := time.Now().UnixMilli()
|
||||
accounts, err := dao.ListAccount("username in ? and (deleted = 1 or (quota > 0 and quota < download + upload)) or ? > expire_time or ? < kick_util_time", usernameList, now, now)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
kickUsernames := make([]string, len(accounts))
|
||||
j := 0
|
||||
for _, item := range accounts {
|
||||
kickUsernames[j] = *item.Username
|
||||
j++
|
||||
}
|
||||
if err = proxy.NewHysteria2Api(apiPort).KickUsers(kickUsernames, jwtSecret); err != nil {
|
||||
return
|
||||
}
|
||||
}(usernameList)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hy2xs-admin/dao"
|
||||
"hy2xs-admin/model/constant"
|
||||
"hy2xs-admin/util"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
netManager string
|
||||
ingressInterface string
|
||||
Add = "add"
|
||||
Delete = "delete"
|
||||
Table = "hui_porthopping"
|
||||
Comment = "hui_hysteria_porthopping"
|
||||
)
|
||||
|
||||
func InitForward() {
|
||||
if nft, err := util.Exec("command -v nft"); err == nil && strings.TrimSpace(nft) != "" {
|
||||
netManager = "nft"
|
||||
} else if iptables, err := util.Exec("command -v iptables"); err == nil && strings.TrimSpace(iptables) != "" {
|
||||
netManager = "iptables"
|
||||
}
|
||||
|
||||
if ii, err := util.Exec("ls /sys/class/net | grep -E '^en|^eth'"); err == nil && strings.TrimSpace(ii) != "" {
|
||||
iiList := strings.Split(ii, "\n")
|
||||
ingressInterface = strings.TrimSpace(iiList[0])
|
||||
}
|
||||
}
|
||||
|
||||
func InitTableAndChain() error {
|
||||
if netManager == "nft" {
|
||||
_, err := util.Exec(fmt.Sprintf("nft add table inet %s", Table))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = util.Exec(fmt.Sprintf("nft add chain inet %s prerouting { type nat hook prerouting priority dstnat\\; policy accept\\; }", Table))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func InitPortHopping() error {
|
||||
if err := RemoveByComment(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hysteria2Config, err := GetHysteria2Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set port forward
|
||||
hysteria2ConfigPortHopping, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigPortHopping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *hysteria2ConfigPortHopping.Value != "" {
|
||||
listen := strings.Split(*hysteria2Config.Listen, ":")
|
||||
if len(listen) == 2 {
|
||||
portHoppings := strings.Split(*hysteria2ConfigPortHopping.Value, ",")
|
||||
for _, item := range portHoppings {
|
||||
if err := portForward(item, listen[1], Add); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func portForward(rules string, target string, option string) error {
|
||||
switch netManager {
|
||||
case "nft":
|
||||
switch option {
|
||||
case Add, Delete:
|
||||
return nftForward(rules, target, option)
|
||||
default:
|
||||
return errors.New("unsupported command option")
|
||||
}
|
||||
case "iptables":
|
||||
switch option {
|
||||
case Add:
|
||||
return iptablesForward(rules, target, "-A")
|
||||
case Delete:
|
||||
return iptablesForward(rules, target, "-D")
|
||||
default:
|
||||
return errors.New("unsupported command option")
|
||||
}
|
||||
default:
|
||||
return errors.New("port hopping not supported on this system")
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveByComment() error {
|
||||
switch netManager {
|
||||
case "nft":
|
||||
return ntfRemoveByComment(Comment)
|
||||
case "iptables":
|
||||
return iptablesRemoveByComment(Comment)
|
||||
default:
|
||||
return errors.New("port hopping not supported on this system")
|
||||
}
|
||||
}
|
||||
|
||||
func nftForward(rules string, target string, option string) error {
|
||||
if ingressInterface == "" {
|
||||
return fmt.Errorf("no network interface detected")
|
||||
}
|
||||
// nft list ruleset
|
||||
// 创建表:nft add table inet hui_hysteria_porthopping
|
||||
// 创建链:nft add chain inet hui_hysteria_porthopping prerouting { type nat hook prerouting priority dstnat\; policy accept\; }
|
||||
// 添加规则:nft add rule inet hui_hysteria_porthopping prerouting iifname enp1s0 udp dport {30000-40000} counter redirect to :444 comment hui_hysteria_porthopping
|
||||
_, err := util.Exec(fmt.Sprintf("nft %s rule inet %s prerouting iifname %s udp dport {%s} counter redirect to :%s comment %s", option, Table, ingressInterface, rules, target, Comment))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ntfRemoveByComment(comment string) error {
|
||||
rules, err := nftRules()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rule := range rules {
|
||||
if strings.Contains(rule, comment) {
|
||||
parts := strings.Fields(rule)
|
||||
handle := parts[len(parts)-1]
|
||||
_, err := util.Exec(fmt.Sprintf("nft delete rule inet %s prerouting handle %s", Table, strings.TrimSpace(handle)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nftRules() ([]string, error) {
|
||||
listOutput, err := util.Exec(fmt.Sprintf("nft list ruleset | grep -q %s && echo 'found' || echo 'not found'", Comment))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strings.TrimSpace(listOutput) == "not found" {
|
||||
return []string{}, nil
|
||||
}
|
||||
output, err := util.Exec(fmt.Sprintf("nft --handle list chain inet %s prerouting", Table))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rules := strings.Split(output, "\n")
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func iptablesForward(rules string, target string, option string) error {
|
||||
if ingressInterface == "" {
|
||||
return fmt.Errorf("no network interface detected")
|
||||
}
|
||||
|
||||
rulePairs := strings.Split(rules, ",")
|
||||
for _, pair := range rulePairs {
|
||||
ports := ""
|
||||
portRange := strings.Split(pair, "-")
|
||||
if len(portRange) == 1 {
|
||||
ports = strings.TrimSpace(portRange[0])
|
||||
} else if len(portRange) == 2 {
|
||||
startPort := strings.TrimSpace(portRange[0])
|
||||
endPort := strings.TrimSpace(portRange[1])
|
||||
ports = startPort + ":" + endPort
|
||||
} else {
|
||||
return fmt.Errorf("invalid port range format: %s", pair)
|
||||
}
|
||||
|
||||
if len(ports) != 0 {
|
||||
if err := iptablesAddRule(option, ports, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func iptablesAddRule(option, ports, target string) error {
|
||||
protocols := [2]string{"iptables", "ip6tables"}
|
||||
for _, protocol := range protocols {
|
||||
// iptables -t nat -A PREROUTING -i enp1s0 -p udp --dport 30000:40000 -j REDIRECT --to-port 444 -m comment --comment hui_hysteria_porthopping
|
||||
_, err := util.Exec(fmt.Sprintf("%s -t nat %s PREROUTING -i %s -p udp --dport %s -j REDIRECT --to-port %s -m comment --comment %s", protocol, option, ingressInterface, ports, target, Comment))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func iptablesRemoveByComment(comment string) error {
|
||||
protocols := [2]string{"iptables", "ip6tables"}
|
||||
for _, protocol := range protocols {
|
||||
rules, err := iptablesRules(protocol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rule := range rules {
|
||||
if strings.Contains(rule, comment) {
|
||||
parts := strings.Fields(rule)
|
||||
handle := parts[0]
|
||||
_, err := util.Exec(fmt.Sprintf("%s -t nat -D PREROUTING %s", protocol, strings.TrimSpace(handle)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func iptablesRules(protocol string) ([]string, error) {
|
||||
// iptables -t nat -L PREROUTING -v --line-numbers
|
||||
output, err := util.Exec(fmt.Sprintf("%s -t nat -L PREROUTING -v --line-numbers", protocol))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rules := strings.Split(output, "\n")
|
||||
return rules, nil
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
"hy2xs-admin/dao"
|
||||
"hy2xs-admin/model/constant"
|
||||
"hy2xs-admin/model/vo"
|
||||
"hy2xs-admin/util"
|
||||
"os"
|
||||
)
|
||||
|
||||
func InitHysteria2() error {
|
||||
if !util.Exists(util.GetHysteria2BinPath()) {
|
||||
return errors.New("systemd-managed hysteria binary not found")
|
||||
}
|
||||
|
||||
config, err := dao.GetConfig("key = ?", constant.Hysteria2Enable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *config.Value == "1" {
|
||||
logrus.Infof("hysteria2 lifecycle is managed by systemd in HY2XS production package")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setHysteria2ConfigYAML() error {
|
||||
serverConfig, err := GetHysteria2Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if serverConfig.Listen == nil || *serverConfig.Listen == "" {
|
||||
return errors.New("hysteria2 config is empty")
|
||||
}
|
||||
|
||||
authHttpUrl, err := GetAuthHttpUrl()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if serverConfig.Auth == nil || serverConfig.Auth.HTTP == nil || serverConfig.Auth.HTTP.URL == nil {
|
||||
if err := UpdateHysteria2Config(serverConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
serverConfig, err = GetHysteria2Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// update auth http url
|
||||
if *serverConfig.Auth.HTTP.URL != authHttpUrl {
|
||||
serverConfig.Auth.HTTP.URL = &authHttpUrl
|
||||
if err := UpdateHysteria2Config(serverConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
hysteria2Config, err := yaml.Marshal(&serverConfig)
|
||||
if err != nil {
|
||||
logrus.Errorf("marshal hysteria2 config err: %v", err)
|
||||
return errors.New("marshal hysteria2 config err")
|
||||
}
|
||||
file, err := os.OpenFile(constant.Hysteria2ConfigPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
logrus.Errorf("create hysteria2 server config file err: %v", err)
|
||||
return errors.New("create hysteria2 server config file err")
|
||||
}
|
||||
_, err = file.WriteString(string(hysteria2Config))
|
||||
if err != nil {
|
||||
logrus.Errorf("write hysteria2 config.json file err: %v", err)
|
||||
return errors.New("hysteria2 config.json file write err")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Hysteria2IsRunning() bool {
|
||||
_, err := util.Exec("systemctl is-active --quiet hysteria-server")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func StartHysteria2() error {
|
||||
if err := setHysteria2ConfigYAML(); err != nil {
|
||||
return err
|
||||
}
|
||||
return util.Systemctl("restart", "hysteria-server")
|
||||
}
|
||||
|
||||
func StopHysteria2() error {
|
||||
return util.Systemctl("stop", "hysteria-server")
|
||||
}
|
||||
|
||||
func RestartHysteria2() error {
|
||||
if err := StopHysteria2(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := StartHysteria2(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReleaseHysteria2() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Hysteria2AcmePath() (vo.Hysteria2AcmePathVo, error) {
|
||||
hysteria2AcmePathVo := vo.Hysteria2AcmePathVo{}
|
||||
hysteria2Config, err := GetHysteria2Config()
|
||||
if err != nil {
|
||||
return hysteria2AcmePathVo, err
|
||||
}
|
||||
if hysteria2Config.TLS != nil &&
|
||||
hysteria2Config.TLS.Cert != nil && *hysteria2Config.TLS.Cert != "" &&
|
||||
hysteria2Config.TLS.Key != nil && *hysteria2Config.TLS.Key != "" {
|
||||
if util.Exists(*hysteria2Config.TLS.Cert) && util.Exists(*hysteria2Config.TLS.Key) {
|
||||
hysteria2AcmePathVo.CrtPath = *hysteria2Config.TLS.Cert
|
||||
hysteria2AcmePathVo.KeyPath = *hysteria2Config.TLS.Key
|
||||
return hysteria2AcmePathVo, nil
|
||||
}
|
||||
return hysteria2AcmePathVo, errors.New("cert not found")
|
||||
} else if hysteria2Config.ACME != nil &&
|
||||
hysteria2Config.ACME.Domains != nil &&
|
||||
len(hysteria2Config.ACME.Domains) > 0 &&
|
||||
hysteria2Config.ACME.CA != nil &&
|
||||
*hysteria2Config.ACME.CA != "" &&
|
||||
hysteria2Config.ACME.Dir != nil &&
|
||||
*hysteria2Config.ACME.Dir != "" {
|
||||
acmeDir := *hysteria2Config.ACME.Dir
|
||||
for _, domain := range hysteria2Config.ACME.Domains {
|
||||
crtPath, err := util.FindFile(acmeDir, fmt.Sprintf("%s.crt", domain))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
keyPath, err := util.FindFile(acmeDir, fmt.Sprintf("%s.key", domain))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
hysteria2AcmePathVo.CrtPath = crtPath
|
||||
hysteria2AcmePathVo.KeyPath = keyPath
|
||||
return hysteria2AcmePathVo, nil
|
||||
}
|
||||
}
|
||||
return vo.Hysteria2AcmePathVo{}, errors.New("cert not found")
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"gopkg.in/yaml.v3"
|
||||
"hy2xs-admin/dao"
|
||||
"hy2xs-admin/model/bo"
|
||||
"hy2xs-admin/model/constant"
|
||||
"hy2xs-admin/proxy"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Hysteria2Auth(conPass string) (int64, string, error) {
|
||||
if !Hysteria2IsRunning() {
|
||||
return 0, "", errors.New("hysteria2 is not running")
|
||||
}
|
||||
|
||||
now := time.Now().UnixMilli()
|
||||
account, err := dao.GetAccount("con_pass = ? and deleted = 0 and (quota < 0 or quota > download + upload) and ? < expire_time and ? > kick_util_time", conPass, now, now)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
// 限制设备数
|
||||
onlineUsers, err := Hysteria2Online()
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
device, exist := onlineUsers[*account.Username]
|
||||
if exist && *account.DeviceNo <= device {
|
||||
return 0, "", errors.New("device limited")
|
||||
}
|
||||
|
||||
return *account.Id, *account.Username, nil
|
||||
}
|
||||
|
||||
func Hysteria2Online() (map[string]int64, error) {
|
||||
if !Hysteria2IsRunning() {
|
||||
return map[string]int64{}, nil
|
||||
}
|
||||
apiPort, err := GetHysteria2ApiPort()
|
||||
if err != nil {
|
||||
return nil, errors.New("get hysteria2 apiPort err")
|
||||
}
|
||||
jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
onlineUsers, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(*jwtSecretConfig.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return onlineUsers, nil
|
||||
}
|
||||
|
||||
func Hysteria2Kick(ids []int64, kickUtilTime int64) error {
|
||||
if !Hysteria2IsRunning() {
|
||||
return errors.New("hysteria2 is not running")
|
||||
}
|
||||
if err := dao.UpdateAccount(ids, map[string]interface{}{"kick_util_time": kickUtilTime}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accounts, err := dao.ListAccount("id in ?", ids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var keys []string
|
||||
for _, item := range accounts {
|
||||
keys = append(keys, *item.Username)
|
||||
}
|
||||
apiPort, err := GetHysteria2ApiPort()
|
||||
if err != nil {
|
||||
return errors.New("get hysteria2 apiPort err")
|
||||
}
|
||||
jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = proxy.NewHysteria2Api(apiPort).KickUsers(keys, *jwtSecretConfig.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Hysteria2SubscribeUrl(accountId int64, protocol string, host string) (string, error) {
|
||||
account, err := dao.GetAccount("id = ?", accountId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
config, err := dao.GetConfig("key = ?", constant.HUIWebContext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
webContext := ""
|
||||
if config.Value != nil && *config.Value != "/" && strings.HasPrefix(*config.Value, "/") {
|
||||
webContext = *config.Value
|
||||
}
|
||||
return fmt.Sprintf("%s//%s%s/hui/%s", protocol, host, webContext, url.QueryEscape(*account.ConPass)), nil
|
||||
}
|
||||
|
||||
func Hysteria2Subscribe(conPass string, clientType string, host string) (string, string, error) {
|
||||
hysteria2Config, err := GetHysteria2Config()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if hysteria2Config.Listen == nil || *hysteria2Config.Listen == "" {
|
||||
return "", "", errors.New("hysteria2 config is empty")
|
||||
}
|
||||
|
||||
account, err := dao.GetAccount("con_pass = ?", conPass)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
hysteria2Name := "hysteria2"
|
||||
hysteria2ConfigRemark, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigRemark)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if *hysteria2ConfigRemark.Value != "" {
|
||||
hysteria2Name = *hysteria2ConfigRemark.Value
|
||||
}
|
||||
|
||||
hysteria2ConfigPortHopping, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigPortHopping)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
userInfo := ""
|
||||
configStr := ""
|
||||
if clientType == constant.Shadowrocket || clientType == constant.Clash {
|
||||
userInfo = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d",
|
||||
*account.Upload,
|
||||
*account.Download,
|
||||
*account.Quota,
|
||||
*account.ExpireTime/1000)
|
||||
|
||||
hysteria2 := bo.Hysteria2{
|
||||
Name: hysteria2Name,
|
||||
Type: "hysteria2",
|
||||
Server: strings.Split(host, ":")[0],
|
||||
Port: strings.Split(*hysteria2Config.Listen, ":")[1],
|
||||
Ports: *hysteria2ConfigPortHopping.Value,
|
||||
Password: conPass,
|
||||
}
|
||||
|
||||
if hysteria2Config.Bandwidth != nil {
|
||||
if hysteria2Config.Bandwidth.Up != nil &&
|
||||
*hysteria2Config.Bandwidth.Up != "" {
|
||||
hysteria2.Up = *hysteria2Config.Bandwidth.Up
|
||||
}
|
||||
if hysteria2Config.Bandwidth.Down != nil &&
|
||||
*hysteria2Config.Bandwidth.Down != "" {
|
||||
hysteria2.Down = *hysteria2Config.Bandwidth.Down
|
||||
}
|
||||
}
|
||||
|
||||
if hysteria2Config.Obfs != nil &&
|
||||
hysteria2Config.Obfs.Type != nil &&
|
||||
*hysteria2Config.Obfs.Type == "salamander" &&
|
||||
hysteria2Config.Obfs.Salamander != nil &&
|
||||
hysteria2Config.Obfs.Salamander.Password != nil &&
|
||||
*hysteria2Config.Obfs.Salamander.Password != "" {
|
||||
if clientType == constant.Shadowrocket {
|
||||
hysteria2.Obfs = *hysteria2Config.Obfs.Salamander.Password
|
||||
} else {
|
||||
hysteria2.Obfs = "salamander"
|
||||
hysteria2.ObfsPassword = *hysteria2Config.Obfs.Salamander.Password
|
||||
}
|
||||
}
|
||||
|
||||
if hysteria2Config.ACME != nil &&
|
||||
hysteria2Config.ACME.Domains != nil &&
|
||||
len(hysteria2Config.ACME.Domains) > 0 {
|
||||
hysteria2.Sni = hysteria2Config.ACME.Domains[0]
|
||||
}
|
||||
|
||||
hysteria2.SkipCertVerify = false
|
||||
|
||||
proxyGroup := bo.ProxyGroup{
|
||||
Name: "PROXY",
|
||||
Type: "select",
|
||||
Proxies: []string{hysteria2Name},
|
||||
}
|
||||
|
||||
clashConfig := bo.ClashConfig{
|
||||
ProxyGroups: []bo.ProxyGroup{
|
||||
proxyGroup,
|
||||
},
|
||||
Proxies: []interface{}{hysteria2},
|
||||
}
|
||||
clashConfigYaml, err := yaml.Marshal(&clashConfig)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
configStr = string(clashConfigYaml)
|
||||
if clientType == constant.Clash {
|
||||
clashExtension, err := GetConfig(constant.ClashExtension)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if clashExtension.Value != nil && *clashExtension.Value != "" {
|
||||
configStr = fmt.Sprintf("%s%s", configStr, *clashExtension.Value)
|
||||
}
|
||||
}
|
||||
} else if clientType == constant.V2rayN {
|
||||
hysteria2Url, err := Hysteria2Url(*account.Id, strings.Split(host, ":")[0])
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
configStr = hysteria2Url
|
||||
}
|
||||
|
||||
return userInfo, configStr, nil
|
||||
}
|
||||
|
||||
func Hysteria2Url(accountId int64, hostname string) (string, error) {
|
||||
hysteria2Config, err := GetHysteria2Config()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if hysteria2Config.Listen == nil || *hysteria2Config.Listen == "" {
|
||||
return "", errors.New("hysteria2 config is empty")
|
||||
}
|
||||
|
||||
account, err := dao.GetAccount("id = ?", accountId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
urlConfig := ""
|
||||
if hysteria2Config.Obfs != nil &&
|
||||
hysteria2Config.Obfs.Type != nil &&
|
||||
*hysteria2Config.Obfs.Type == "salamander" &&
|
||||
hysteria2Config.Obfs.Salamander != nil &&
|
||||
hysteria2Config.Obfs.Salamander.Password != nil &&
|
||||
*hysteria2Config.Obfs.Salamander.Password != "" {
|
||||
urlConfig += fmt.Sprintf("&obfs=salamander&obfs-password=%s", *hysteria2Config.Obfs.Salamander.Password)
|
||||
}
|
||||
|
||||
if hysteria2Config.ACME != nil &&
|
||||
hysteria2Config.ACME.Domains != nil &&
|
||||
len(hysteria2Config.ACME.Domains) > 0 {
|
||||
urlConfig += fmt.Sprintf("&sni=%s", hysteria2Config.ACME.Domains[0])
|
||||
// shadowrocket
|
||||
urlConfig += fmt.Sprintf("&peer=%s", hysteria2Config.ACME.Domains[0])
|
||||
}
|
||||
|
||||
urlConfig += "&insecure=0"
|
||||
|
||||
if hysteria2Config.Bandwidth != nil &&
|
||||
hysteria2Config.Bandwidth.Down != nil &&
|
||||
*hysteria2Config.Bandwidth.Down != "" {
|
||||
// shadowrocket
|
||||
urlConfig += fmt.Sprintf("&downmbps=%s", url.PathEscape(*hysteria2Config.Bandwidth.Down))
|
||||
}
|
||||
|
||||
hysteria2ConfigPortHopping, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigPortHopping)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if *hysteria2ConfigPortHopping.Value != "" {
|
||||
// shadowrocket
|
||||
urlConfig += fmt.Sprintf("&mport=%s", *hysteria2ConfigPortHopping.Value)
|
||||
}
|
||||
|
||||
hysteria2ConfigRemark, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigRemark)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if *hysteria2ConfigRemark.Value != "" {
|
||||
urlConfig += fmt.Sprintf("#%s", *hysteria2ConfigRemark.Value)
|
||||
}
|
||||
if urlConfig != "" {
|
||||
urlConfig = "/?" + strings.TrimPrefix(urlConfig, "&")
|
||||
}
|
||||
return fmt.Sprintf("hysteria2://%s@%s%s", *account.ConPass, hostname, *hysteria2Config.Listen) + urlConfig, nil
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"hy2xs-admin/dao"
|
||||
"hy2xs-admin/model/bo"
|
||||
"hy2xs-admin/model/constant"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const TokenExpireDuration = time.Hour * 24
|
||||
|
||||
type MyClaims struct {
|
||||
AccountBo bo.AccountBo `json:"account"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
func GenToken(accountBo bo.AccountBo) (string, error) {
|
||||
c := MyClaims{
|
||||
AccountBo: accountBo,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(),
|
||||
Issuer: "hy2xs-admin",
|
||||
},
|
||||
}
|
||||
config, err := dao.GetConfig("key = ?", constant.JwtSecret)
|
||||
if err != nil {
|
||||
return "", errors.New(constant.SysError)
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
|
||||
return token.SignedString([]byte(*config.Value))
|
||||
}
|
||||
|
||||
func ParseToken(tokenString string) (*MyClaims, error) {
|
||||
config, err := dao.GetConfig("key = ?", constant.JwtSecret)
|
||||
if err != nil {
|
||||
return nil, errors.New(constant.SysError)
|
||||
}
|
||||
token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (i interface{}, err error) {
|
||||
return []byte(*config.Value), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.New(constant.IllegalTokenError)
|
||||
}
|
||||
if claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
return nil, errors.New(constant.TokenExpiredError)
|
||||
}
|
||||
|
||||
func GetToken(c *gin.Context) string {
|
||||
tokenStr := c.Request.Header.Get("Authorization")
|
||||
if tokenStr == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.SplitN(tokenStr, " ", 2)[1]
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hy2xs-admin/model/constant"
|
||||
"hy2xs-admin/model/vo"
|
||||
"hy2xs-admin/util"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func MonitorSystem() (vo.SystemMonitorVo, error) {
|
||||
cpuPercent, err := util.GetCpuPercent()
|
||||
if err != nil {
|
||||
return vo.SystemMonitorVo{}, errors.New("cpu query failed")
|
||||
}
|
||||
memPercent, err := util.GetMemPercent()
|
||||
if err != nil {
|
||||
return vo.SystemMonitorVo{}, errors.New("mem query failed")
|
||||
}
|
||||
diskPercent, err := util.GetDiskPercent()
|
||||
if err != nil {
|
||||
return vo.SystemMonitorVo{}, errors.New("disk query failed")
|
||||
}
|
||||
return vo.SystemMonitorVo{
|
||||
HUIVersion: constant.Version,
|
||||
CpuPercent: cpuPercent,
|
||||
MemPercent: memPercent,
|
||||
DiskPercent: diskPercent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func MonitorHysteria2() (vo.Hysteria2MonitorVo, error) {
|
||||
var hysteria2MonitorVo vo.Hysteria2MonitorVo
|
||||
onlineUsers, err := Hysteria2Online()
|
||||
if err != nil {
|
||||
return hysteria2MonitorVo, err
|
||||
}
|
||||
|
||||
if len(onlineUsers) > 0 {
|
||||
hysteria2MonitorVo.UserTotal = int64(len(onlineUsers))
|
||||
var deviceTotal int64 = 0
|
||||
for _, value := range onlineUsers {
|
||||
deviceTotal += value
|
||||
}
|
||||
hysteria2MonitorVo.DeviceTotal = deviceTotal
|
||||
}
|
||||
|
||||
hysteria2MonitorVo.Version = "-"
|
||||
content, err := util.Exec(fmt.Sprintf("%s version", util.GetHysteria2BinPath()))
|
||||
if err == nil {
|
||||
pattern := `v\d+\.\d+\.\d+`
|
||||
re := regexp.MustCompile(pattern)
|
||||
matches := re.FindAllString(strings.TrimSpace(content), -1)
|
||||
if len(matches) > 0 {
|
||||
hysteria2MonitorVo.Version = matches[0]
|
||||
}
|
||||
}
|
||||
|
||||
running := Hysteria2IsRunning()
|
||||
hysteria2MonitorVo.Running = running
|
||||
return hysteria2MonitorVo, nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"hy2xs-admin/util"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var server *http.Server
|
||||
|
||||
func InitServer(addr string, handler http.Handler) {
|
||||
server = &http.Server{
|
||||
Addr: addr,
|
||||
Handler: handler,
|
||||
}
|
||||
}
|
||||
|
||||
func StartServer(crtPath string, keyPath string) error {
|
||||
if crtPath != "" && keyPath != "" {
|
||||
return server.ListenAndServeTLS(crtPath, keyPath)
|
||||
}
|
||||
return server.ListenAndServe()
|
||||
}
|
||||
|
||||
func StopServer() error {
|
||||
if err := StopHysteria2(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
if err := server.Shutdown(ctx); err != nil {
|
||||
logrus.Errorf("failed to shutdown server: %v", err)
|
||||
return errors.New("failed to shutdown server")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetServerPortAndCert() (int64, string, string, error) {
|
||||
port, crtPath, keyPath, err := GetPortAndCert()
|
||||
if err != nil {
|
||||
return 0, "", "", err
|
||||
}
|
||||
|
||||
if !util.IsPortAvailable(uint(port), "tcp") {
|
||||
errMsg := fmt.Sprintf("port %d is taken", port)
|
||||
logrus.Errorf(errMsg)
|
||||
return 0, "", "", errors.New(errMsg)
|
||||
}
|
||||
|
||||
if crtPath != "" && !util.Exists(crtPath) {
|
||||
errMsg := fmt.Sprintf("crt path: %s does not exist", crtPath)
|
||||
logrus.Errorf(errMsg)
|
||||
return 0, "", "", errors.New(errMsg)
|
||||
}
|
||||
|
||||
if keyPath != "" && !util.Exists(keyPath) {
|
||||
errMsg := fmt.Sprintf("key path: %s does not exist", keyPath)
|
||||
logrus.Errorf(errMsg)
|
||||
return 0, "", "", errors.New(errMsg)
|
||||
}
|
||||
|
||||
return port, crtPath, keyPath, nil
|
||||
}
|
||||
Reference in New Issue
Block a user