Реализован production-hardening по fix1: env/reconfigure, IPv4-only, TLS, secrets, firewall, docs
This commit is contained in:
@@ -48,4 +48,3 @@ func Execute() {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -36,8 +36,12 @@ func runReset(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
if err = dao.UpdateAccount([]int64{1}, map[string]interface{}{
|
||||
"username": username,
|
||||
"pass": util.SHA224String(password),
|
||||
"con_pass": fmt.Sprintf("%s.%s", username, password)}); err != nil {
|
||||
"pass": func() string {
|
||||
hash, _ := util.HashPassword(password)
|
||||
return hash
|
||||
}(),
|
||||
"force_password_change": 1,
|
||||
"con_pass": fmt.Sprintf("%s.%s", username, password)}); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -49,4 +53,3 @@ func runReset(cmd *cobra.Command, args []string) {
|
||||
fmt.Println(fmt.Sprintf("HY2XS admin Login Password: %s", password))
|
||||
fmt.Println(fmt.Sprintf("HY2XS admin Connection Password: %s", fmt.Sprintf("%s.%s", username, password)))
|
||||
}
|
||||
|
||||
|
||||
+18
-8
@@ -11,10 +11,23 @@ import (
|
||||
"hy2xs-admin/router"
|
||||
"hy2xs-admin/service"
|
||||
"hy2xs-admin/util"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func resolveUiBindHost() (string, error) {
|
||||
host := os.Getenv("HY2XS_UI_BIND_HOST")
|
||||
if host == "" {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil || ip.To4() == nil {
|
||||
return "", errors.New("HY2XS_UI_BIND_HOST must be IPv4")
|
||||
}
|
||||
return host, nil
|
||||
}
|
||||
|
||||
func runServer(port string) error {
|
||||
defer releaseResource()
|
||||
|
||||
@@ -32,12 +45,6 @@ func runServer(port string) error {
|
||||
if err := service.InitHysteria2(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := service.InitTableAndChain(); err != nil {
|
||||
logrus.Errorf(err.Error())
|
||||
}
|
||||
if err := service.InitPortHopping(); err != nil {
|
||||
logrus.Errorf(err.Error())
|
||||
}
|
||||
config, err := dao.GetConfig("key = ?", constant.HUIWebContext)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -50,8 +57,12 @@ func runServer(port string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bindHost, err := resolveUiBindHost()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service.InitServer(fmt.Sprintf(":%d", serverPort), r)
|
||||
service.InitServer(fmt.Sprintf("%s:%d", bindHost, serverPort), r)
|
||||
if err := service.StartServer(crtPath, keyPath); err != nil && err != http.ErrServerClosed {
|
||||
logrus.Errorf("start server err: %v", err)
|
||||
return errors.New("start server err")
|
||||
@@ -83,4 +94,3 @@ func initFile() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -20,4 +20,3 @@ func init() {
|
||||
func runVersion(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("HY2XS admin version", constant.Version)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,14 +26,15 @@ func Login(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
token, err := service.Login(*loginDto.Username, util.SHA224String(*loginDto.Pass))
|
||||
token, forcePasswordChange, err := service.Login(*loginDto.Username, *loginDto.Pass)
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
jwtVo := vo.JwtVo{
|
||||
TokenType: constant.TokenType,
|
||||
AccessToken: token,
|
||||
TokenType: constant.TokenType,
|
||||
AccessToken: token,
|
||||
ForcePasswordChange: forcePasswordChange,
|
||||
}
|
||||
vo.Success(jwtVo, c)
|
||||
}
|
||||
@@ -100,7 +101,11 @@ func SaveAccount(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
passEncrypt := util.SHA224String(*accountSaveDto.Pass)
|
||||
passEncrypt, err := util.HashPassword(*accountSaveDto.Pass)
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
conPass := fmt.Sprintf("%s.%s", *accountSaveDto.Username, *accountSaveDto.ConPass)
|
||||
account := entity.Account{
|
||||
Username: accountSaveDto.Username,
|
||||
@@ -167,8 +172,12 @@ func UpdateAccount(c *gin.Context) {
|
||||
|
||||
var passEncrypt *string
|
||||
if accountUpdateDto.Pass != nil && *accountUpdateDto.Pass != "" {
|
||||
passEncryptSha224 := util.SHA224String(*accountUpdateDto.Pass)
|
||||
passEncrypt = &passEncryptSha224
|
||||
passEncryptHash, hashErr := util.HashPassword(*accountUpdateDto.Pass)
|
||||
if hashErr != nil {
|
||||
vo.Fail(hashErr.Error(), c)
|
||||
return
|
||||
}
|
||||
passEncrypt = &passEncryptHash
|
||||
}
|
||||
|
||||
account := entity.Account{
|
||||
@@ -331,6 +340,5 @@ func VerifyDefaultPass(c *gin.Context) {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
vo.Success(account.Pass != nil && *account.Pass == "02f382b76ca1ab7aa06ab03345c7712fd5b971fb0c0f2aef98bac9cd", c)
|
||||
vo.Success(account.Pass != nil && !util.IsBcryptHash(*account.Pass), c)
|
||||
}
|
||||
|
||||
|
||||
+16
-59
@@ -17,7 +17,6 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -35,7 +34,6 @@ func UpdateConfigs(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
needResetPortHopping := false
|
||||
needRestart := false
|
||||
|
||||
for _, item := range configsUpdateDto.ConfigUpdateDtos {
|
||||
@@ -81,19 +79,8 @@ func UpdateConfigs(c *gin.Context) {
|
||||
}
|
||||
|
||||
if key == constant.Hysteria2ConfigPortHopping {
|
||||
re := regexp.MustCompile(`^\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*$`)
|
||||
if value != "" && !re.MatchString(value) {
|
||||
vo.Fail(fmt.Sprintf("port hopping: %s is invalid", value), c)
|
||||
return
|
||||
}
|
||||
hysteria2ConfigPortHopping, err := service.GetConfig(constant.Hysteria2ConfigPortHopping)
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if *hysteria2ConfigPortHopping.Value != value {
|
||||
needResetPortHopping = true
|
||||
}
|
||||
vo.Fail("port hopping out of scope in HY2XS production", c)
|
||||
return
|
||||
}
|
||||
|
||||
if key == constant.ResetTrafficCron {
|
||||
@@ -113,13 +100,6 @@ func UpdateConfigs(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if needResetPortHopping {
|
||||
if err := service.InitPortHopping(); err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if needRestart {
|
||||
go func() {
|
||||
_ = service.StopServer()
|
||||
@@ -197,33 +177,11 @@ func UpdateHysteria2Config(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
hysteria2Config, err := service.GetHysteria2Config()
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
needResetPortHopping := false
|
||||
if hysteria2Config.Listen != nil &&
|
||||
*hysteria2Config.Listen != "" &&
|
||||
hysteria2ServerConfig.Listen != nil &&
|
||||
*hysteria2ServerConfig.Listen != "" &&
|
||||
*hysteria2ServerConfig.Listen != *hysteria2Config.Listen {
|
||||
needResetPortHopping = true
|
||||
}
|
||||
|
||||
if err = service.UpdateHysteria2Config(hysteria2ServerConfig); err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if needResetPortHopping {
|
||||
if err := service.InitPortHopping(); err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
running := service.Hysteria2IsRunning()
|
||||
if running {
|
||||
if err = service.RestartHysteria2(); err != nil {
|
||||
@@ -242,24 +200,24 @@ func ExportHysteria2Config(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Значения по умолчанию
|
||||
config, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.JwtSecret})
|
||||
config, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.Hysteria2TrafficStatsSecret})
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
var hUIWebPort string
|
||||
var jwtSecret string
|
||||
var trafficStatsSecret string
|
||||
for _, item := range config {
|
||||
if *item.Key == constant.HUIWebPort {
|
||||
hUIWebPort = *item.Value
|
||||
} else if *item.Key == constant.JwtSecret {
|
||||
jwtSecret = *item.Value
|
||||
} else if *item.Key == constant.Hysteria2TrafficStatsSecret {
|
||||
trafficStatsSecret = *item.Value
|
||||
}
|
||||
}
|
||||
|
||||
if hUIWebPort == "" || jwtSecret == "" {
|
||||
logrus.Errorf("hUIWebPort or jwtSecret is nil")
|
||||
if hUIWebPort == "" || trafficStatsSecret == "" {
|
||||
logrus.Errorf("hUIWebPort or trafficStatsSecret is nil")
|
||||
vo.Fail(constant.SysError, c)
|
||||
return
|
||||
}
|
||||
@@ -279,7 +237,7 @@ func ExportHysteria2Config(c *gin.Context) {
|
||||
http.Insecure = &authHttpInsecure
|
||||
auth.HTTP = &http
|
||||
hysteria2ServerConfig.Auth = &auth
|
||||
hysteria2ServerConfig.TrafficStats.Secret = &jwtSecret
|
||||
hysteria2ServerConfig.TrafficStats.Secret = &trafficStatsSecret
|
||||
|
||||
fileName := fmt.Sprintf("Hysteria2Config-%s.yaml", time.Now().Format("20060102150405"))
|
||||
filePath := constant.ExportPathDir + fileName
|
||||
@@ -325,24 +283,24 @@ func ImportHysteria2Config(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Значения по умолчанию
|
||||
config, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.JwtSecret})
|
||||
config, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.Hysteria2TrafficStatsSecret})
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
var hUIWebPort string
|
||||
var jwtSecret string
|
||||
var trafficStatsSecret string
|
||||
for _, item := range config {
|
||||
if *item.Key == constant.HUIWebPort {
|
||||
hUIWebPort = *item.Value
|
||||
} else if *item.Key == constant.JwtSecret {
|
||||
jwtSecret = *item.Value
|
||||
} else if *item.Key == constant.Hysteria2TrafficStatsSecret {
|
||||
trafficStatsSecret = *item.Value
|
||||
}
|
||||
}
|
||||
|
||||
if hUIWebPort == "" || jwtSecret == "" {
|
||||
logrus.Errorf("hUIWebPort or jwtSecret is nil")
|
||||
if hUIWebPort == "" || trafficStatsSecret == "" {
|
||||
logrus.Errorf("hUIWebPort or trafficStatsSecret is nil")
|
||||
vo.Fail(constant.SysError, c)
|
||||
return
|
||||
}
|
||||
@@ -362,7 +320,7 @@ func ImportHysteria2Config(c *gin.Context) {
|
||||
http.Insecure = &authHttpInsecure
|
||||
auth.HTTP = &http
|
||||
hysteria2ServerConfig.Auth = &auth
|
||||
hysteria2ServerConfig.TrafficStats.Secret = &jwtSecret
|
||||
hysteria2ServerConfig.TrafficStats.Secret = &trafficStatsSecret
|
||||
|
||||
if err = service.SetHysteria2Config(hysteria2ServerConfig); err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
@@ -501,4 +459,3 @@ func UploadCertFile(c *gin.Context) {
|
||||
}
|
||||
vo.Success(certPath, c)
|
||||
}
|
||||
|
||||
|
||||
@@ -150,4 +150,3 @@ func Hysteria2Subscribe(c *gin.Context) {
|
||||
|
||||
c.String(200, configStr)
|
||||
}
|
||||
|
||||
|
||||
@@ -114,4 +114,3 @@ func ExportLog(c *gin.Context) {
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileName))
|
||||
c.File(filePath)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,4 +23,3 @@ func MonitorHysteria2(c *gin.Context) {
|
||||
}
|
||||
vo.Success(hysteria2MonitorVo, c)
|
||||
}
|
||||
|
||||
|
||||
@@ -39,4 +39,3 @@ func validateField[T interface{}](c *gin.Context, field T) (T, error) {
|
||||
}
|
||||
return field, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -117,4 +117,3 @@ func ListAccount(query interface{}, args ...interface{}) ([]entity.Account, erro
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -64,4 +64,3 @@ func UpsertConfig(configs []entity.Config) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+62
-1
@@ -9,6 +9,8 @@ import (
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
"hy2xs-admin/model/constant"
|
||||
"hy2xs-admin/model/entity"
|
||||
"hy2xs-admin/util"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -69,6 +71,66 @@ func InitSql(port string) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := ensureSecureBootstrapAdmin(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureTrafficStatsSecret(); err != nil {
|
||||
return err
|
||||
}
|
||||
if tx := sqliteDB.Exec("ALTER TABLE account ADD COLUMN force_password_change INTEGER NOT NULL DEFAULT 0"); tx.Error != nil && !strings.Contains(tx.Error.Error(), "duplicate column name") {
|
||||
logrus.Errorf("sqlite exec err: %v", tx.Error)
|
||||
return errors.New("sqlite exec err")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureSecureBootstrapAdmin() error {
|
||||
admin, err := GetAccount("role = 'admin' and deleted = 0")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if admin.Pass == nil {
|
||||
return nil
|
||||
}
|
||||
if !util.IsBcryptHash(*admin.Pass) {
|
||||
password, pwdErr := util.RandomString(18)
|
||||
if pwdErr != nil {
|
||||
return pwdErr
|
||||
}
|
||||
hash, hashErr := util.HashPassword(password)
|
||||
if hashErr != nil {
|
||||
return hashErr
|
||||
}
|
||||
conPass, conErr := util.RandomString(28)
|
||||
if conErr != nil {
|
||||
return conErr
|
||||
}
|
||||
if updateErr := UpdateAccount([]int64{*admin.Id}, map[string]interface{}{
|
||||
"pass": hash,
|
||||
"force_password_change": 1,
|
||||
"con_pass": fmt.Sprintf("%s.%s", *admin.Username, conPass),
|
||||
}); updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureTrafficStatsSecret() error {
|
||||
if _, err := GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret); err == nil {
|
||||
return nil
|
||||
}
|
||||
secret, secErr := util.RandomString(32)
|
||||
if secErr != nil {
|
||||
return secErr
|
||||
}
|
||||
key := constant.Hysteria2TrafficStatsSecret
|
||||
remark := "Hysteria2 trafficStats API secret"
|
||||
if _, saveErr := SaveConfig(entity.Config{Key: &key, Value: &secret, Remark: &remark}); saveErr != nil {
|
||||
return saveErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -117,4 +179,3 @@ func Paginate(pageNum *int64, pageSize *int64) func(db *gorm.DB) *gorm.DB {
|
||||
return db.Offset(int((num - 1) * size)).Limit(int(size))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,4 +73,3 @@ func replaceRelativePaths(htmlContent string, basePath string) string {
|
||||
</script>`, basePath)
|
||||
return strings.Replace(htmlContent, "</head>", injection+"</head>", 1)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,10 @@
|
||||
"@vueuse/core": "^9.1.1",
|
||||
"axios": "^1.3.4",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"echarts": "^5.2.2",
|
||||
"element-plus": "^2.3.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"pinia": "^2.0.33",
|
||||
"screenfull": "^6.0.0",
|
||||
"vue": "^3.2.45",
|
||||
"vue-i18n": "9",
|
||||
"vue-router": "^4.1.6",
|
||||
|
||||
Generated
+2
-58
@@ -20,9 +20,6 @@ importers:
|
||||
copy-to-clipboard:
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3
|
||||
echarts:
|
||||
specifier: ^5.2.2
|
||||
version: 5.2.2
|
||||
element-plus:
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1(vue@3.2.45)
|
||||
@@ -32,15 +29,9 @@ importers:
|
||||
path-browserify:
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1
|
||||
path-to-regexp:
|
||||
specifier: ^6.2.0
|
||||
version: 6.2.0
|
||||
pinia:
|
||||
specifier: ^2.0.33
|
||||
version: 2.0.33(typescript@4.9.3)(vue@3.2.45)
|
||||
screenfull:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
vue:
|
||||
specifier: ^3.2.45
|
||||
version: 3.2.45
|
||||
@@ -89,7 +80,7 @@ importers:
|
||||
version: 9.9.0(eslint@8.34.0)
|
||||
fast-glob:
|
||||
specifier: ^3.2.11
|
||||
version: 3.2.11
|
||||
version: 3.3.1
|
||||
postcss:
|
||||
specifier: ^8.4.21
|
||||
version: 8.4.21
|
||||
@@ -559,28 +550,24 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@swc/core-linux-arm64-musl@1.11.24':
|
||||
resolution: {integrity: sha512-ypXLIdszRo0re7PNNaXN0+2lD454G8l9LPK/rbfRXnhLWDBPURxzKlLlU/YGd2zP98wPcVooMmegRSNOKfvErw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@swc/core-linux-x64-gnu@1.11.24':
|
||||
resolution: {integrity: sha512-IM7d+STVZD48zxcgo69L0yYptfhaaE9cMZ+9OoMxirNafhKKXwoZuufol1+alEFKc+Wbwp+aUPe/DeWC/Lh3dg==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@swc/core-linux-x64-musl@1.11.24':
|
||||
resolution: {integrity: sha512-DZByJaMVzSfjQKKQn3cqSeqwy6lpMaQDQQ4HPlch9FWtDx/dLcpdIhxssqZXcR2rhaQVIaRQsCqwV6orSDGAGw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@swc/core-win32-arm64-msvc@1.11.24':
|
||||
resolution: {integrity: sha512-Q64Ytn23y9aVDKN5iryFi8mRgyHw3/kyjTjT4qFCa8AEb5sGUuSj//AUZ6c0J7hQKMHlg9do5Etvoe61V98/JQ==}
|
||||
@@ -1273,9 +1260,6 @@ packages:
|
||||
duplexer@0.1.2:
|
||||
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
|
||||
|
||||
echarts@5.2.2:
|
||||
resolution: {integrity: sha512-yxuBfeIH5c+0FsoRP60w4De6omXhA06c7eUYBsC1ykB6Ys2yK5fSteIYWvkJ4xJVLQgCvAdO8C4mN6MLeJpBaw==}
|
||||
|
||||
electron-to-chromium@1.4.529:
|
||||
resolution: {integrity: sha512-6uyPyXTo8lkv8SWAmjKFbG42U073TXlzD4R8rW3EzuznhFS2olCIAfjjQtV2dV2ar/vRF55KUd3zQYnCB0dd3A==}
|
||||
|
||||
@@ -1446,10 +1430,6 @@ packages:
|
||||
fast-diff@1.3.0:
|
||||
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
|
||||
|
||||
fast-glob@3.2.11:
|
||||
resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==}
|
||||
engines: {node: '>=8.6.0'}
|
||||
|
||||
fast-glob@3.3.1:
|
||||
resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
|
||||
engines: {node: '>=8.6.0'}
|
||||
@@ -2221,9 +2201,6 @@ packages:
|
||||
path-parse@1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
|
||||
path-to-regexp@6.2.0:
|
||||
resolution: {integrity: sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg==}
|
||||
|
||||
path-type@4.0.0:
|
||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2466,10 +2443,6 @@ packages:
|
||||
engines: {node: '>=12.0.0'}
|
||||
hasBin: true
|
||||
|
||||
screenfull@6.0.0:
|
||||
resolution: {integrity: sha512-LGY0nhNQkC4FX4DT4pZdJ5cZH5EOz9Gfh9KcVMl779pS677k4IV1Wv7sY/CwC9VKFT21fYgCh7zkTVVefi5XKA==}
|
||||
engines: {node: ^14.13.1 || >=16.0.0}
|
||||
|
||||
scule@1.0.0:
|
||||
resolution: {integrity: sha512-4AsO/FrViE/iDNEPaAQlb77tf0csuq27EsVpy6ett584EcRTp6pTDLoGWVxCD77y5iU5FauOvhsI4o1APwPoSQ==}
|
||||
|
||||
@@ -2796,9 +2769,6 @@ packages:
|
||||
tslib@1.14.1:
|
||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||
|
||||
tslib@2.3.0:
|
||||
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
|
||||
|
||||
tsutils@3.21.0:
|
||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -3055,9 +3025,6 @@ packages:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
zrender@5.2.1:
|
||||
resolution: {integrity: sha512-M3bPGZuyLTNBC6LiNKXJwSCtglMp8XUEqEBG+2MdICDI3d1s500Y4P0CzldQGsqpRVB7fkvf3BKQQRxsEaTlsw==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@aashutoshrathi/word-wrap@1.2.6': {}
|
||||
@@ -4313,11 +4280,6 @@ snapshots:
|
||||
|
||||
duplexer@0.1.2: {}
|
||||
|
||||
echarts@5.2.2:
|
||||
dependencies:
|
||||
tslib: 2.3.0
|
||||
zrender: 5.2.1
|
||||
|
||||
electron-to-chromium@1.4.529: {}
|
||||
|
||||
element-plus@2.3.1(vue@3.2.45):
|
||||
@@ -4566,14 +4528,6 @@ snapshots:
|
||||
|
||||
fast-diff@1.3.0: {}
|
||||
|
||||
fast-glob@3.2.11:
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
'@nodelib/fs.walk': 1.2.8
|
||||
glob-parent: 5.1.2
|
||||
merge2: 1.4.1
|
||||
micromatch: 4.0.5
|
||||
|
||||
fast-glob@3.3.1:
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
@@ -4715,7 +4669,7 @@ snapshots:
|
||||
dependencies:
|
||||
array-union: 2.1.0
|
||||
dir-glob: 3.0.1
|
||||
fast-glob: 3.2.11
|
||||
fast-glob: 3.3.1
|
||||
ignore: 5.2.4
|
||||
merge2: 1.4.1
|
||||
slash: 3.0.0
|
||||
@@ -5312,8 +5266,6 @@ snapshots:
|
||||
|
||||
path-parse@1.0.7: {}
|
||||
|
||||
path-to-regexp@6.2.0: {}
|
||||
|
||||
path-type@4.0.0: {}
|
||||
|
||||
pathe@0.2.0: {}
|
||||
@@ -5542,8 +5494,6 @@ snapshots:
|
||||
immutable: 4.3.4
|
||||
source-map-js: 1.0.2
|
||||
|
||||
screenfull@6.0.0: {}
|
||||
|
||||
scule@1.0.0: {}
|
||||
|
||||
semver@5.7.2: {}
|
||||
@@ -5922,8 +5872,6 @@ snapshots:
|
||||
|
||||
tslib@1.14.1: {}
|
||||
|
||||
tslib@2.3.0: {}
|
||||
|
||||
tsutils@3.21.0(typescript@4.9.3):
|
||||
dependencies:
|
||||
tslib: 1.14.1
|
||||
@@ -6226,7 +6174,3 @@ snapshots:
|
||||
yargs-parser@20.2.9: {}
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
zrender@5.2.1:
|
||||
dependencies:
|
||||
tslib: 2.3.0
|
||||
|
||||
+1
-3
@@ -8,12 +8,12 @@ require (
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-playground/validator/v10 v10.14.0
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/google/go-github/v39 v39.2.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/spf13/cobra v1.8.1
|
||||
golang.org/x/crypto v0.18.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/gorm v1.25.9
|
||||
@@ -32,7 +32,6 @@ require (
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
@@ -55,7 +54,6 @@ require (
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
|
||||
-22
@@ -38,18 +38,10 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github/v39 v39.2.0 h1:rNNM311XtPOz5rDdsJXAp2o8F67X9FnROXTvto3aSnQ=
|
||||
github.com/google/go-github/v39 v39.2.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
@@ -129,35 +121,21 @@ github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
|
||||
@@ -5,4 +5,3 @@ import "hy2xs-admin/cmd"
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
|
||||
|
||||
@@ -24,4 +24,3 @@ func AdminHandler() gin.HandlerFunc {
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,4 +31,3 @@ func InitCron() error {
|
||||
c.Start()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -23,4 +23,3 @@ func FilterHandler() gin.HandlerFunc {
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,4 +36,3 @@ func JWTHandler() gin.HandlerFunc {
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,4 +41,3 @@ func LogHandler() gin.HandlerFunc {
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,4 +24,3 @@ func RateLimiterHandler() gin.HandlerFunc {
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,4 +28,3 @@ type AccountExport struct {
|
||||
ConAt int64 `json:"conAt"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
|
||||
@@ -203,4 +203,3 @@ type serverConfigMasquerade struct {
|
||||
ListenHTTPS *string `yaml:"listenHTTPS,omitempty" json:"listenHTTPS" validate:"omitempty"`
|
||||
ForceHTTPS *bool `yaml:"forceHTTPS,omitempty" json:"forceHTTPS" validate:"omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -4,4 +4,3 @@ type Hysteria2UserTraffic struct {
|
||||
Tx int64 `json:"tx"` // upload
|
||||
Rx int64 `json:"rx"` // download
|
||||
}
|
||||
|
||||
|
||||
@@ -25,4 +25,3 @@ type ClashConfig struct {
|
||||
Proxies []interface{} `yaml:"proxies"`
|
||||
ProxyGroups []ProxyGroup `yaml:"proxy-groups"`
|
||||
}
|
||||
|
||||
|
||||
@@ -6,4 +6,3 @@ const (
|
||||
V2rayN = "v2rayn"
|
||||
NekoBox = "nekobox"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,4 +7,3 @@ const (
|
||||
CodeForbiddenError int = 50403
|
||||
CodeInvalidError int = 50001
|
||||
)
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
HUIWebPort = "H_UI_WEB_PORT"
|
||||
HUIWebContext = "H_UI_WEB_CONTEXT"
|
||||
HUICrtPath = "H_UI_CRT_PATH"
|
||||
HUIKeyPath = "H_UI_KEY_PATH"
|
||||
JwtSecret = "JWT_SECRET"
|
||||
Hysteria2Enable = "HYSTERIA2_ENABLE"
|
||||
Hysteria2Config = "HYSTERIA2_CONFIG"
|
||||
Hysteria2TrafficTime = "HYSTERIA2_TRAFFIC_TIME"
|
||||
Hysteria2ConfigRemark = "HYSTERIA2_CONFIG_REMARK"
|
||||
Hysteria2ConfigPortHopping = "HYSTERIA2_CONFIG_PORT_HOPPING"
|
||||
ResetTrafficCron = "RESET_TRAFFIC_CRON"
|
||||
ClashExtension = "CLASH_EXTENSION"
|
||||
HUIWebPort = "H_UI_WEB_PORT"
|
||||
HUIWebContext = "H_UI_WEB_CONTEXT"
|
||||
HUICrtPath = "H_UI_CRT_PATH"
|
||||
HUIKeyPath = "H_UI_KEY_PATH"
|
||||
JwtSecret = "JWT_SECRET"
|
||||
Hysteria2TrafficStatsSecret = "HYSTERIA2_TRAFFIC_STATS_SECRET"
|
||||
Hysteria2Enable = "HYSTERIA2_ENABLE"
|
||||
Hysteria2Config = "HYSTERIA2_CONFIG"
|
||||
Hysteria2TrafficTime = "HYSTERIA2_TRAFFIC_TIME"
|
||||
Hysteria2ConfigRemark = "HYSTERIA2_CONFIG_REMARK"
|
||||
Hysteria2ConfigPortHopping = "HYSTERIA2_CONFIG_PORT_HOPPING"
|
||||
ResetTrafficCron = "RESET_TRAFFIC_CRON"
|
||||
ClashExtension = "CLASH_EXTENSION"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,4 +12,3 @@ const (
|
||||
WrongPassword string = "wrong password"
|
||||
ConfigNotExist string = "config not exist"
|
||||
)
|
||||
|
||||
|
||||
@@ -18,4 +18,3 @@ const (
|
||||
|
||||
Version = "v0.0.22"
|
||||
)
|
||||
|
||||
|
||||
@@ -34,4 +34,3 @@ type AccountUpdateDto struct {
|
||||
Deleted *int64 `json:"deleted" form:"deleted" validate:"omitempty,oneof=0 1"`
|
||||
Remark *string `json:"remark" form:"remark" validate:"omitempty,min=0,max=32"`
|
||||
}
|
||||
|
||||
|
||||
@@ -16,4 +16,3 @@ type ConfigUpdateDto struct {
|
||||
type ConfigsUpdateDto struct {
|
||||
ConfigUpdateDtos []ConfigUpdateDto `json:"configUpdateDtos" form:"configUpdateDtos" validate:"required"`
|
||||
}
|
||||
|
||||
|
||||
@@ -10,4 +10,3 @@ type BaseDto struct {
|
||||
type IdDto struct {
|
||||
Id *int64 `json:"id" form:"id" validate:"required,gt=0"` // Первичный ключ
|
||||
}
|
||||
|
||||
|
||||
@@ -25,4 +25,3 @@ type Hysteria2UrlDto struct {
|
||||
AccountId *int64 `json:"accountId" form:"accountId" validate:"required,gt=0"`
|
||||
Hostname *string `json:"hostname" form:"hostname" validate:"required,min=1,max=255"`
|
||||
}
|
||||
|
||||
|
||||
@@ -7,4 +7,3 @@ type LogDto struct {
|
||||
type LogExportDto struct {
|
||||
Option *int `json:"option" form:"option" validate:"required,oneof=0 1"`
|
||||
}
|
||||
|
||||
|
||||
@@ -3,4 +3,3 @@ package dto
|
||||
type ServerDto struct {
|
||||
Port *int64 `json:"port" form:"port" validate:"required,min=1,max=65535"`
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ type Account struct {
|
||||
Deleted *int64 `gorm:"column:deleted;default:0" json:"deleted"`
|
||||
BaseEntity `gorm:"embedded"`
|
||||
|
||||
LoginAt *int64 `gorm:"column:login_at;default:0" json:"loginAt"`
|
||||
ConAt *int64 `gorm:"column:con_at;default:0" json:"conAt"`
|
||||
Remark *string `gorm:"column:remark;default:''" json:"remark"`
|
||||
LoginAt *int64 `gorm:"column:login_at;default:0" json:"loginAt"`
|
||||
ConAt *int64 `gorm:"column:con_at;default:0" json:"conAt"`
|
||||
Remark *string `gorm:"column:remark;default:''" json:"remark"`
|
||||
ForcePasswordChange *int64 `gorm:"column:force_password_change;default:0" json:"forcePasswordChange"`
|
||||
}
|
||||
|
||||
|
||||
@@ -6,4 +6,3 @@ type Config struct {
|
||||
Remark *string `gorm:"column:remark;default:''" json:"remark"`
|
||||
BaseEntity `gorm:"embedded"`
|
||||
}
|
||||
|
||||
|
||||
@@ -7,4 +7,3 @@ type BaseEntity struct {
|
||||
CreateTime *time.Time `gorm:"column:create_time;default:null" json:"createTime"`
|
||||
UpdateTime *time.Time `gorm:"column:update_time;default:null" json:"updateTime"`
|
||||
}
|
||||
|
||||
|
||||
@@ -29,4 +29,3 @@ type AccountInfoVo struct {
|
||||
Username string `json:"username"`
|
||||
Roles []string `json:"roles"`
|
||||
}
|
||||
|
||||
|
||||
@@ -5,4 +5,3 @@ type ConfigVo struct {
|
||||
Value string `json:"value"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
|
||||
@@ -38,4 +38,3 @@ type Hysteria2AcmePathVo struct {
|
||||
CrtPath string `json:"crtPath"`
|
||||
KeyPath string `json:"keyPath"`
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package vo
|
||||
|
||||
type JwtVo struct {
|
||||
TokenType string `json:"tokenType"`
|
||||
AccessToken string `json:"accessToken"`
|
||||
TokenType string `json:"tokenType"`
|
||||
AccessToken string `json:"accessToken"`
|
||||
ForcePasswordChange bool `json:"forcePasswordChange"`
|
||||
}
|
||||
|
||||
|
||||
@@ -21,4 +21,3 @@ type LogHysteria2Vo struct {
|
||||
Msg string `json:"msg"`
|
||||
Time string `json:"time"`
|
||||
}
|
||||
|
||||
|
||||
@@ -13,4 +13,3 @@ type Hysteria2MonitorVo struct {
|
||||
Version string `json:"version"` // Версия
|
||||
Running bool `json:"running"` // Статус выполнения
|
||||
}
|
||||
|
||||
|
||||
@@ -44,4 +44,3 @@ func Fail(message string, c *gin.Context) {
|
||||
Data: nil,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -6,4 +6,3 @@ type BaseVo struct {
|
||||
Id int64 `json:"id"`
|
||||
CreateTime time.Time `json:"createTime"`
|
||||
}
|
||||
|
||||
|
||||
@@ -56,4 +56,3 @@ func (h *Hysteria2Process) Release() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -134,4 +134,3 @@ func (h *Hysteria2Api) OnlineUsers(secret string) (map[string]int64, error) {
|
||||
}
|
||||
return onlineUsers, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -227,4 +227,3 @@ func (p *process) handleLogs(stdout, stderr io.ReadCloser) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,4 +21,3 @@ func initAccountAdminRouter(accountApi *gin.RouterGroup) {
|
||||
account.GET("/verifyDefaultPass", controller.VerifyDefaultPass)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,4 +11,3 @@ func initAuthRouter(authApi *gin.RouterGroup) {
|
||||
auth.POST("/login", controller.Login)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,4 +22,3 @@ func initConfigRouter(configApi *gin.RouterGroup) {
|
||||
config.POST("/uploadCertFile", controller.UploadCertFile)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,4 +24,3 @@ func initHysteria2Router(hysteria2Api *gin.RouterGroup) {
|
||||
hysteria2.GET("/hysteria2Url", controller.Hysteria2Url)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,4 +13,3 @@ func initLogRouter(accountApi *gin.RouterGroup) {
|
||||
account.POST("/exportLog", controller.ExportLog)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,4 +12,3 @@ func initMonitorRouter(accountApi *gin.RouterGroup) {
|
||||
account.GET("/monitorHysteria2", controller.MonitorHysteria2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,4 +39,3 @@ func Router(router *gin.Engine, huiWebContext *string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+24
-5
@@ -10,20 +10,40 @@ import (
|
||||
"hy2xs-admin/model/dto"
|
||||
"hy2xs-admin/model/entity"
|
||||
"hy2xs-admin/model/vo"
|
||||
"hy2xs-admin/util"
|
||||
)
|
||||
|
||||
func Login(username string, pass string) (string, error) {
|
||||
account, err := dao.GetAccount("username = ? and pass = ? and role = 'admin' and deleted = 0", username, pass)
|
||||
func Login(username string, plainPassword string) (string, bool, error) {
|
||||
account, err := dao.GetAccount("username = ? and role = 'admin' and deleted = 0", username)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
verified, legacy := util.VerifyPassword(plainPassword, *account.Pass)
|
||||
if !verified {
|
||||
return "", false, errors.New(constant.WrongPassword)
|
||||
}
|
||||
|
||||
if legacy {
|
||||
hash, hashErr := util.HashPassword(plainPassword)
|
||||
if hashErr == nil {
|
||||
_ = dao.UpdateAccount([]int64{*account.Id}, map[string]interface{}{"pass": hash})
|
||||
}
|
||||
}
|
||||
|
||||
accountBo := bo.AccountBo{
|
||||
Id: *account.Id,
|
||||
Username: *account.Username,
|
||||
Roles: []string{*account.Role},
|
||||
Deleted: *account.Deleted,
|
||||
}
|
||||
return GenToken(accountBo)
|
||||
token, tokenErr := GenToken(accountBo)
|
||||
if tokenErr != nil {
|
||||
return "", false, tokenErr
|
||||
}
|
||||
|
||||
requirePasswordChange := legacy
|
||||
return token, requirePasswordChange, nil
|
||||
}
|
||||
|
||||
func PageAccount(accountPageDto dto.AccountPageDto) ([]entity.Account, int64, error) {
|
||||
@@ -156,4 +176,3 @@ func GetAccountInfo(c *gin.Context) (vo.AccountInfoVo, error) {
|
||||
Roles: myClaims.AccountBo.Roles,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -74,23 +74,23 @@ func GetHysteria2Config() (bo.Hysteria2ServerConfig, error) {
|
||||
|
||||
func UpdateHysteria2Config(hysteria2ServerConfig bo.Hysteria2ServerConfig) error {
|
||||
// Значения по умолчанию
|
||||
config, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.JwtSecret})
|
||||
config, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.Hysteria2TrafficStatsSecret})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var hUIWebPort string
|
||||
var jwtSecret string
|
||||
var trafficStatsSecret string
|
||||
for _, item := range config {
|
||||
if *item.Key == constant.HUIWebPort {
|
||||
hUIWebPort = *item.Value
|
||||
} else if *item.Key == constant.JwtSecret {
|
||||
jwtSecret = *item.Value
|
||||
} else if *item.Key == constant.Hysteria2TrafficStatsSecret {
|
||||
trafficStatsSecret = *item.Value
|
||||
}
|
||||
}
|
||||
|
||||
if hUIWebPort == "" || jwtSecret == "" {
|
||||
logrus.Errorf("hUIWebPort or jwtSecret is nil")
|
||||
if hUIWebPort == "" || trafficStatsSecret == "" {
|
||||
logrus.Errorf("hUIWebPort or trafficStatsSecret is nil")
|
||||
return errors.New(constant.SysError)
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ func UpdateHysteria2Config(hysteria2ServerConfig bo.Hysteria2ServerConfig) error
|
||||
http.Insecure = &authHttpInsecure
|
||||
auth.HTTP = &http
|
||||
hysteria2ServerConfig.Auth = &auth
|
||||
hysteria2ServerConfig.TrafficStats.Secret = &jwtSecret
|
||||
hysteria2ServerConfig.TrafficStats.Secret = &trafficStatsSecret
|
||||
|
||||
yamlConfig, err := yaml.Marshal(&hysteria2ServerConfig)
|
||||
if err != nil {
|
||||
@@ -195,4 +195,3 @@ func GetAuthHttpUrl() (string, error) {
|
||||
}
|
||||
return fmt.Sprintf("%s://127.0.0.1:%d%s/hui/hysteria2/auth", protocol, port, webContext), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -27,16 +27,16 @@ func CronHandleAccount() {
|
||||
return
|
||||
}
|
||||
|
||||
jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret)
|
||||
trafficSecretConfig, err := dao.GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Сохранение данных трафика
|
||||
go saveAccountTraffic(apiPort, *jwtSecretConfig.Value)
|
||||
go saveAccountTraffic(apiPort, *trafficSecretConfig.Value)
|
||||
|
||||
// Принудительное отключение
|
||||
go kickAccount(apiPort, *jwtSecretConfig.Value)
|
||||
go kickAccount(apiPort, *trafficSecretConfig.Value)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func CronResetTraffic() {
|
||||
}
|
||||
}
|
||||
|
||||
func saveAccountTraffic(apiPort int64, jwtSecret string) {
|
||||
func saveAccountTraffic(apiPort int64, trafficStatsSecret string) {
|
||||
if !trafficMutex.TryLock() {
|
||||
return
|
||||
}
|
||||
@@ -74,7 +74,7 @@ func saveAccountTraffic(apiPort int64, jwtSecret string) {
|
||||
return
|
||||
}
|
||||
|
||||
users, err := proxy.NewHysteria2Api(apiPort).ListUsers(true, jwtSecret)
|
||||
users, err := proxy.NewHysteria2Api(apiPort).ListUsers(true, trafficStatsSecret)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -96,13 +96,13 @@ func saveAccountTraffic(apiPort int64, jwtSecret string) {
|
||||
}
|
||||
}
|
||||
|
||||
func kickAccount(apiPort int64, jwtSecret string) {
|
||||
func kickAccount(apiPort int64, trafficStatsSecret string) {
|
||||
if !kickMutex.TryLock() {
|
||||
return
|
||||
}
|
||||
defer kickMutex.Unlock()
|
||||
|
||||
users, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(jwtSecret)
|
||||
users, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(trafficStatsSecret)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -130,7 +130,7 @@ func kickAccount(apiPort int64, jwtSecret string) {
|
||||
kickUsernames[j] = *item.Username
|
||||
j++
|
||||
}
|
||||
if err = proxy.NewHysteria2Api(apiPort).KickUsers(kickUsernames, jwtSecret); err != nil {
|
||||
if err = proxy.NewHysteria2Api(apiPort).KickUsers(kickUsernames, trafficStatsSecret); err != nil {
|
||||
return
|
||||
}
|
||||
}(usernameList)
|
||||
@@ -138,4 +138,3 @@ func kickAccount(apiPort int64, jwtSecret string) {
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -232,4 +232,3 @@ func iptablesRules(protocol string) ([]string, error) {
|
||||
rules := strings.Split(output, "\n")
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -65,16 +65,33 @@ func setHysteria2ConfigYAML() error {
|
||||
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)
|
||||
tmpPath := fmt.Sprintf("%s.tmp", constant.Hysteria2ConfigPath)
|
||||
file, err := os.OpenFile(tmpPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
logrus.Errorf("create hysteria2 server config file err: %v", err)
|
||||
return errors.New("create hysteria2 server config file err")
|
||||
}
|
||||
defer file.Close()
|
||||
_, 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")
|
||||
}
|
||||
if syncErr := file.Sync(); syncErr != nil {
|
||||
return syncErr
|
||||
}
|
||||
if closeErr := file.Close(); closeErr != nil {
|
||||
return closeErr
|
||||
}
|
||||
if renameErr := os.Rename(tmpPath, constant.Hysteria2ConfigPath); renameErr != nil {
|
||||
return renameErr
|
||||
}
|
||||
if chmodErr := os.Chmod(constant.Hysteria2ConfigPath, 0600); chmodErr != nil {
|
||||
return chmodErr
|
||||
}
|
||||
if chownErr := os.Chown(constant.Hysteria2ConfigPath, 0, 0); chownErr != nil {
|
||||
return chownErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -147,4 +164,3 @@ func Hysteria2AcmePath() (vo.Hysteria2AcmePathVo, error) {
|
||||
}
|
||||
return vo.Hysteria2AcmePathVo{}, errors.New("cert not found")
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,28 @@ import (
|
||||
"hy2xs-admin/model/bo"
|
||||
"hy2xs-admin/model/constant"
|
||||
"hy2xs-admin/proxy"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func parseListenPort(listen string) (int, error) {
|
||||
host, port, err := net.SplitHostPort(listen)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if host == "" || port == "" {
|
||||
return 0, errors.New("invalid listen address")
|
||||
}
|
||||
value, convErr := strconv.Atoi(port)
|
||||
if convErr != nil || value < 1 || value > 65535 {
|
||||
return 0, errors.New("invalid listen port")
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func Hysteria2Auth(conPass string) (int64, string, error) {
|
||||
if !Hysteria2IsRunning() {
|
||||
return 0, "", errors.New("hysteria2 is not running")
|
||||
@@ -45,11 +62,11 @@ func Hysteria2Online() (map[string]int64, error) {
|
||||
if err != nil {
|
||||
return nil, errors.New("get hysteria2 apiPort err")
|
||||
}
|
||||
jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret)
|
||||
trafficSecretConfig, err := dao.GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
onlineUsers, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(*jwtSecretConfig.Value)
|
||||
onlineUsers, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(*trafficSecretConfig.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -76,11 +93,11 @@ func Hysteria2Kick(ids []int64, kickUtilTime int64) error {
|
||||
if err != nil {
|
||||
return errors.New("get hysteria2 apiPort err")
|
||||
}
|
||||
jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret)
|
||||
trafficSecretConfig, err := dao.GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = proxy.NewHysteria2Api(apiPort).KickUsers(keys, *jwtSecretConfig.Value); err != nil {
|
||||
if err = proxy.NewHysteria2Api(apiPort).KickUsers(keys, *trafficSecretConfig.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -125,13 +142,13 @@ func Hysteria2Subscribe(conPass string, clientType string, host string) (string,
|
||||
hysteria2Name = *hysteria2ConfigRemark.Value
|
||||
}
|
||||
|
||||
hysteria2ConfigPortHopping, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigPortHopping)
|
||||
userInfo := ""
|
||||
configStr := ""
|
||||
listenPort, err := parseListenPort(*hysteria2Config.Listen)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
userInfo := ""
|
||||
configStr := ""
|
||||
publicHost := strings.Split(host, ":")[0]
|
||||
if clientType == constant.Shadowrocket || clientType == constant.Clash {
|
||||
userInfo = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d",
|
||||
*account.Upload,
|
||||
@@ -142,9 +159,8 @@ func Hysteria2Subscribe(conPass string, clientType string, host string) (string,
|
||||
hysteria2 := bo.Hysteria2{
|
||||
Name: hysteria2Name,
|
||||
Type: "hysteria2",
|
||||
Server: strings.Split(host, ":")[0],
|
||||
Port: strings.Split(*hysteria2Config.Listen, ":")[1],
|
||||
Ports: *hysteria2ConfigPortHopping.Value,
|
||||
Server: publicHost,
|
||||
Port: strconv.Itoa(listenPort),
|
||||
Password: conPass,
|
||||
}
|
||||
|
||||
@@ -226,6 +242,13 @@ func Hysteria2Url(accountId int64, hostname string) (string, error) {
|
||||
if hysteria2Config.Listen == nil || *hysteria2Config.Listen == "" {
|
||||
return "", errors.New("hysteria2 config is empty")
|
||||
}
|
||||
port, err := parseListenPort(*hysteria2Config.Listen)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if hostname == "" || hostname == "0.0.0.0" {
|
||||
return "", errors.New("invalid public host")
|
||||
}
|
||||
|
||||
account, err := dao.GetAccount("id = ?", accountId)
|
||||
if err != nil {
|
||||
@@ -259,15 +282,6 @@ func Hysteria2Url(accountId int64, hostname string) (string, error) {
|
||||
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
|
||||
@@ -278,6 +292,5 @@ func Hysteria2Url(accountId int64, hostname string) (string, error) {
|
||||
if urlConfig != "" {
|
||||
urlConfig = "/?" + strings.TrimPrefix(urlConfig, "&")
|
||||
}
|
||||
return fmt.Sprintf("hysteria2://%s@%s%s", *account.ConPass, hostname, *hysteria2Config.Listen) + urlConfig, nil
|
||||
return fmt.Sprintf("hysteria2://%s@%s:%d", *account.ConPass, hostname, port) + urlConfig, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -58,4 +58,3 @@ func GetToken(c *gin.Context) string {
|
||||
}
|
||||
return strings.SplitN(tokenStr, " ", 2)[1]
|
||||
}
|
||||
|
||||
|
||||
@@ -62,4 +62,3 @@ func MonitorHysteria2() (vo.Hysteria2MonitorVo, error) {
|
||||
hysteria2MonitorVo.Running = running
|
||||
return hysteria2MonitorVo, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -67,4 +67,3 @@ func GetServerPortAndCert() (int64, string, string, error) {
|
||||
|
||||
return port, crtPath, keyPath, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -30,4 +30,3 @@ func SplitArr[T any](arr []T, num int) [][]T {
|
||||
|
||||
return segments
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,11 @@ package util
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func SHA224String(password string) string {
|
||||
@@ -16,3 +20,25 @@ func SHA224String(password string) string {
|
||||
return str
|
||||
}
|
||||
|
||||
func HashPassword(password string) (string, error) {
|
||||
if len(strings.TrimSpace(password)) < 6 {
|
||||
return "", errors.New("password too short")
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(hash), nil
|
||||
}
|
||||
|
||||
func IsBcryptHash(hash string) bool {
|
||||
return strings.HasPrefix(hash, "$2a$") || strings.HasPrefix(hash, "$2b$") || strings.HasPrefix(hash, "$2y$")
|
||||
}
|
||||
|
||||
func VerifyPassword(password string, storedHash string) (ok bool, legacy bool) {
|
||||
if IsBcryptHash(storedHash) {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(storedHash), []byte(password))
|
||||
return err == nil, false
|
||||
}
|
||||
return SHA224String(password) == storedHash, true
|
||||
}
|
||||
|
||||
@@ -5,4 +5,3 @@ import "testing"
|
||||
func TestSHA224String(t *testing.T) {
|
||||
println(SHA224String("sysadmin"))
|
||||
}
|
||||
|
||||
|
||||
@@ -38,4 +38,3 @@ func ExportFile(filePath string, data any, t int) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -86,4 +86,3 @@ func FindFile(dir, filename string) (string, error) {
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/google/go-github/v39/github"
|
||||
)
|
||||
|
||||
var githubClient *github.Client
|
||||
|
||||
func init() {
|
||||
githubClient = github.NewClient(nil)
|
||||
}
|
||||
|
||||
func GetReleaseAssetURL(owner, repo, version, fileName string) (string, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
var release *github.RepositoryRelease
|
||||
var err error
|
||||
if version != "" {
|
||||
release, _, err = githubClient.Repositories.GetReleaseByTag(ctx, owner, repo, version)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get release for version %s: %v", version, err)
|
||||
}
|
||||
} else {
|
||||
releases, _, err := githubClient.Repositories.ListReleases(ctx, owner, repo, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to list releases: %v", err)
|
||||
}
|
||||
if len(releases) == 0 {
|
||||
return "", fmt.Errorf("no releases found")
|
||||
}
|
||||
release = releases[0]
|
||||
}
|
||||
|
||||
assets, _, err := githubClient.Repositories.ListReleaseAssets(ctx, owner, repo, release.GetID(), nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to list release assets: %v", err)
|
||||
}
|
||||
|
||||
for _, asset := range assets {
|
||||
if asset.GetName() == fileName {
|
||||
return asset.GetBrowserDownloadURL(), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("file '%s' not found in release '%s'", fileName, release.GetTagName())
|
||||
}
|
||||
|
||||
func ListRelease(owner, repo string) ([]*github.RepositoryRelease, error) {
|
||||
ctx := context.Background()
|
||||
releases, _, err := githubClient.Repositories.ListReleases(ctx, owner, repo, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list releases: %v", err)
|
||||
}
|
||||
return releases, nil
|
||||
}
|
||||
|
||||
@@ -1,66 +1,9 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hy2xs-admin/model/constant"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func GetHysteria2BinPath() string {
|
||||
return constant.Hysteria2BinPath
|
||||
}
|
||||
|
||||
func GetHysteria2BinName() string {
|
||||
hysteria2FileName := fmt.Sprintf("hysteria-%s-%s", runtime.GOOS, runtime.GOARCH)
|
||||
if runtime.GOOS == "windows" {
|
||||
hysteria2FileName += ".exe"
|
||||
}
|
||||
return hysteria2FileName
|
||||
}
|
||||
|
||||
func DownloadHysteria2(version string) error {
|
||||
hysteria2BinName := GetHysteria2BinName()
|
||||
hysteria2BinPath := GetHysteria2BinPath()
|
||||
|
||||
// Download the latest version of Hysteria2
|
||||
url, err := GetReleaseAssetURL("apernet", "hysteria", version, hysteria2BinName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.Get(url)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download file: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("failed to download file, status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
if Exists(hysteria2BinPath) {
|
||||
if err = os.Remove(hysteria2BinPath); err != nil {
|
||||
return fmt.Errorf("failed to remove existing file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Create(hysteria2BinPath)
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create file %s: %v", hysteria2BinPath, err)
|
||||
}
|
||||
|
||||
_, err = io.Copy(file, resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to file: %v", err)
|
||||
}
|
||||
|
||||
if err = os.Chmod(hysteria2BinPath, 0755); err != nil {
|
||||
return fmt.Errorf("failed to change file permissions: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -98,4 +98,3 @@ func VerifyPort(port string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -21,4 +21,3 @@ func SplitMap[T any](inputMap map[string]T, chunkSize int) []map[string]T {
|
||||
|
||||
return segments
|
||||
}
|
||||
|
||||
|
||||
@@ -17,4 +17,3 @@ func RandomString(length int) (string, error) {
|
||||
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -34,4 +34,3 @@ func CompareVersion(version1, version2 string) int {
|
||||
// The version number is exactly the same
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user