Подготовить HY2XS к production-сборке

This commit is contained in:
2026-04-25 23:13:12 +05:00
commit 84a4e94567
277 changed files with 26513 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
package util
func ArrContain[T comparable](arr []T, key T) bool {
for _, item := range arr {
if item == key {
return true
}
}
return false
}
func SplitArr[T any](arr []T, num int) [][]T {
length := len(arr)
if length <= num {
return [][]T{arr}
}
quantity := (length + num - 1) / num
segments := make([][]T, 0, quantity)
for i := 0; i < quantity; i++ {
end := (i + 1) * num
if end > length {
end = length
}
segment := arr[i*num : end]
segments = append(segments, segment)
}
return segments
}
+17
View File
@@ -0,0 +1,17 @@
package util
import (
"crypto/sha256"
"fmt"
)
func SHA224String(password string) string {
hash := sha256.New224()
hash.Write([]byte(password))
val := hash.Sum(nil)
str := ""
for _, v := range val {
str += fmt.Sprintf("%02x", v)
}
return str
}
+7
View File
@@ -0,0 +1,7 @@
package util
import "testing"
func TestSHA224String(t *testing.T) {
println(SHA224String("sysadmin"))
}
+40
View File
@@ -0,0 +1,40 @@
package util
import (
"encoding/json"
"errors"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
"hy2xs-admin/model/constant"
"os"
)
// ExportFile t 0/json 1/yaml
func ExportFile(filePath string, data any, t int) error {
file, err := os.Create(filePath)
if err != nil {
logrus.Errorf("ExportFile create file err filePath: %s err: %v", filePath, err)
return errors.New(constant.SysError)
}
defer file.Close()
var bytes []byte
if t == 0 {
bytes, err = json.MarshalIndent(data, "", " ")
if err != nil {
logrus.Errorf("ExportFile Marshal json err filePath: %s err: %v", filePath, err)
return errors.New(constant.SysError)
}
} else if t == 1 {
bytes, err = yaml.Marshal(&data)
if err != nil {
logrus.Errorf("ExportFile Marshal yaml err filePath: %s err: %v", filePath, err)
return errors.New(constant.SysError)
}
}
_, err = file.Write(bytes)
if err != nil {
logrus.Errorf("ExportFile writer WriteString err filePath: %s err: %v", filePath, err)
return errors.New(constant.SysError)
}
return nil
}
+88
View File
@@ -0,0 +1,88 @@
package util
import (
"bufio"
"errors"
"fmt"
"os"
"path/filepath"
)
func Exists(path string) bool {
_, err := os.Stat(path)
if err != nil {
if os.IsExist(err) {
return true
}
return false
}
return true
}
func RemoveFile(fileName string) error {
if Exists(fileName) {
if err := os.Remove(fileName); err != nil {
return errors.New("failed to delete file")
}
}
return nil
}
// ReadLinesFromBottom Read the file contents sequentially from bottom to top and return the specified number of lines
func ReadLinesFromBottom(filePath string, numLines int) ([]string, int, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, 0, err
}
defer file.Close()
var lines []string
scanner := bufio.NewScanner(file)
// Read the file contents line by line and reverse the order of the lines
total := 0
for scanner.Scan() {
lines = append(lines, scanner.Text())
total++
}
if err := scanner.Err(); err != nil {
return nil, 0, err
}
// Reverse row order
for i, j := 0, len(lines)-1; i < j; i, j = i+1, j-1 {
lines[i], lines[j] = lines[j], lines[i]
}
// Returns the specified number of rows
if len(lines) < numLines {
numLines = len(lines)
}
return lines[:numLines], total, nil
}
func FindFile(dir, filename string) (string, error) {
var result string
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && info.Name() == filename {
absPath, err := filepath.Abs(path)
if err != nil {
return err
}
result = absPath
return errors.New("file found")
}
return nil
})
if err != nil && err.Error() != "file found" {
return "", err
}
if result == "" {
return "", fmt.Errorf("file %s not found in directory %s", filename, dir)
}
return result, nil
}
+57
View File
@@ -0,0 +1,57 @@
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
}
+65
View File
@@ -0,0 +1,65 @@
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
}
+100
View File
@@ -0,0 +1,100 @@
package util
import (
"errors"
"fmt"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/mem"
"github.com/sirupsen/logrus"
"net"
"os"
"os/exec"
"strconv"
"time"
)
func Exec(cmd string) (string, error) {
command := exec.Command("bash", "-c", cmd)
command.Env = os.Environ()
output, err := command.CombinedOutput()
if err != nil {
logrus.Errorf("execute command failed cmd: %s err: %v", cmd, err)
return "", fmt.Errorf("execute command failed cmd: %s", cmd)
}
return string(output), nil
}
func Systemctl(action string, unit string) error {
_, err := Exec(fmt.Sprintf("systemctl %s %s", action, unit))
return err
}
func IsPortAvailable(port uint, network string) bool {
if network == "tcp" {
listener, err := net.ListenTCP(network, &net.TCPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: int(port),
})
defer func() {
if listener != nil {
listener.Close()
}
}()
if err != nil {
logrus.Errorf("port %d is taken err: %s", port, err)
return false
}
}
if network == "udp" {
listener, err := net.ListenUDP("udp", &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: int(port),
})
defer func() {
if listener != nil {
listener.Close()
}
}()
if err != nil {
logrus.Errorf("port %d is taken err: %s", port, err)
return false
}
}
return true
}
func GetCpuPercent() (float64, error) {
var err error
percent, err := cpu.Percent(time.Second, false)
value, err := strconv.ParseFloat(fmt.Sprintf("%.1f", percent[0]), 64)
return value, err
}
func GetMemPercent() (float64, error) {
var err error
memInfo, err := mem.VirtualMemory()
value, err := strconv.ParseFloat(fmt.Sprintf("%.1f", memInfo.UsedPercent), 64)
return value, err
}
func GetDiskPercent() (float64, error) {
var err error
parts, err := disk.Partitions(true)
diskInfo, err := disk.Usage(parts[0].Mountpoint)
value, err := strconv.ParseFloat(fmt.Sprintf("%.1f", diskInfo.UsedPercent), 64)
return value, err
}
func VerifyPort(port string) error {
if port != "" {
value, err := strconv.ParseInt(port, 10, 64)
if err != nil {
return errors.New("invalid port value")
}
if value <= 0 || value > 65535 {
return errors.New("the port range is between 0-65535")
}
}
return nil
}
+23
View File
@@ -0,0 +1,23 @@
package util
func SplitMap[T any](inputMap map[string]T, chunkSize int) []map[string]T {
length := len(inputMap)
quantity := (length + chunkSize - 1) / chunkSize
segments := make([]map[string]T, 0, quantity)
var groupIndex int
currentGroup := make(map[string]T)
for key, value := range inputMap {
currentGroup[key] = value
if len(currentGroup) == chunkSize || groupIndex+1 == quantity {
// When the current group is full or the last group is reached, add the mapping to the result slice
segments = append(segments, currentGroup)
currentGroup = make(map[string]T) // Initialize a new mapping
groupIndex++
}
}
return segments
}
+19
View File
@@ -0,0 +1,19 @@
package util
import "crypto/rand"
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func RandomString(length int) (string, error) {
bytes := make([]byte, length)
_, err := rand.Read(bytes)
if err != nil {
return "", err
}
for i := range bytes {
bytes[i] = charset[int(bytes[i])%len(charset)]
}
return string(bytes), nil
}
+36
View File
@@ -0,0 +1,36 @@
package util
import "strings"
func CompareVersion(version1, version2 string) int {
v1 := strings.Split(version1, ".")
v2 := strings.Split(version2, ".")
// Compare major version numbers
if v1[0] > v2[0] {
return 1
} else if v1[0] < v2[0] {
return -1
}
// If the major version numbers are the same, compare the minor version numbers
if len(v1) > 1 && len(v2) > 1 {
if v1[1] > v2[1] {
return 1
} else if v1[1] < v2[1] {
return -1
}
}
// If the major and minor versions are the same, compare the revision numbers
if len(v1) > 2 && len(v2) > 2 {
if v1[2] > v2[2] {
return 1
} else if v1[2] < v2[2] {
return -1
}
}
// The version number is exactly the same
return 0
}