Compare commits

..

4 Commits

Author SHA1 Message Date
zhaoyupeng
760784a5ac wip: 重新整理 install cmd 2026-01-13 20:13:29 +08:00
zhaoyupeng
fcbaa5be2f chore: proxy res icon 2026-01-13 16:52:16 +08:00
zhaoyupeng
da6a846550 feat: 许多变化
1. make apps 逻辑大变更, vendor 成标准传入 args
  2. nginx -> app-helper
2026-01-12 20:01:45 +08:00
zhaoyupeng
ce6ab8ab5f chore: 8443 proxy v2_2 yosguard 2026-01-08 09:55:29 +08:00
63 changed files with 1423 additions and 768 deletions

5
.gitignore vendored
View File

@@ -5,4 +5,7 @@ dist
.vscode .vscode
.idea .idea
forge forge
dev_*.sh dev_*.sh
*.tar
*.tgz
*.tar.gz

5
go.mod
View File

@@ -3,14 +3,13 @@ module yizhisec.com/hsv2/forge
go 1.25.2 go 1.25.2
require ( require (
gitea.loveuer.com/yizhisec/pkg3 v0.0.1 github.com/fatih/color v1.18.0
github.com/google/uuid v1.6.0
github.com/samber/lo v1.52.0 github.com/samber/lo v1.52.0
github.com/spf13/cobra v1.10.1 github.com/spf13/cobra v1.10.1
) )
require ( require (
github.com/fatih/color v1.18.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect

2
go.sum
View File

@@ -1,5 +1,3 @@
gitea.loveuer.com/yizhisec/pkg3 v0.0.1 h1:bcT58K6W7TQ5u7Lt7B5JxrVbU/riXwcrshd2lu+Q22c=
gitea.loveuer.com/yizhisec/pkg3 v0.0.1/go.mod h1:Ws/tNONjDC4BLdOXAe+wPD6xk3H7TIXvh2de8qKqQpc=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=

View File

@@ -1,6 +1,8 @@
package installcmd package installcmd
import ( import (
"os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/installer" "yizhisec.com/hsv2/forge/internal/controller/installer"
) )
@@ -8,7 +10,6 @@ import (
func Check() *cobra.Command { func Check() *cobra.Command {
var ( var (
workdir string workdir string
target string
ignoreDisk bool ignoreDisk bool
ignoreMemory bool ignoreMemory bool
ignoreCPU bool ignoreCPU bool
@@ -18,9 +19,20 @@ func Check() *cobra.Command {
Use: "check", Use: "check",
Short: "Check system requirements", Short: "Check system requirements",
Long: `Check system requirements for the project.`, Long: `Check system requirements for the project.`,
PreRun: func(cmd *cobra.Command, args []string) {
if os.Getenv("FORGE_NO_CHECK_DISK") == "true" {
ignoreDisk = true
}
if os.Getenv("FORGE_NO_CHECK_MEMORY") == "true" {
ignoreMemory = true
}
if os.Getenv("FORGE_NO_CHECK_CPU") == "true" {
ignoreCPU = true
}
},
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
_installer := installer.NewInstaller(workdir, target) _installer := installer.NewInstaller(workdir)
return _installer.Check( return _installer.HardwareCheck(
cmd.Context(), cmd.Context(),
installer.WithIgnoreDiskCheck(ignoreDisk), installer.WithIgnoreDiskCheck(ignoreDisk),
installer.WithIgnoreMemoryCheck(ignoreMemory), installer.WithIgnoreMemoryCheck(ignoreMemory),
@@ -30,10 +42,6 @@ func Check() *cobra.Command {
} }
_cmd.Flags().StringVar(&workdir, "workdir", "/root/hs-installation", "Working directory") _cmd.Flags().StringVar(&workdir, "workdir", "/root/hs-installation", "Working directory")
_cmd.Flags().StringVar(&target, "target", "self", "Target")
_cmd.Flags().BoolVar(&ignoreDisk, "ignore-check-disk", false, "ignore disk requirement check result")
_cmd.Flags().BoolVar(&ignoreMemory, "ignore-check-memory", false, "ignore memory requirement check result")
_cmd.Flags().BoolVar(&ignoreCPU, "ignore-check-cpu", false, "ignore cpu requirement check result")
return _cmd return _cmd
} }

View File

@@ -9,21 +9,23 @@ func K0s() *cobra.Command {
var ( var (
workdir string workdir string
target string
) )
_cmd := &cobra.Command{ _cmd := &cobra.Command{
Use: "k0s", Use: "k0s",
Short: "Install k0s", Short: "Install k0s",
Long: "Install k0s", Long: "Install k0s",
PreRunE: func(cmd *cobra.Command, args []string) error {
i := installer.NewInstaller(workdir)
return i.CheckOK(cmd.Context())
},
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
_installer := installer.NewInstaller(workdir, target) _installer := installer.NewInstaller(workdir)
return _installer.K0s(cmd.Context()) return _installer.K0s(cmd.Context())
}, },
} }
_cmd.PersistentFlags().StringVar(&workdir, "workdir", "/root/hs-installation", "working directory") _cmd.PersistentFlags().StringVar(&workdir, "workdir", "/root/hs-installation", "working directory")
_cmd.PersistentFlags().StringVar(&target, "target", "self", "target directory")
return _cmd return _cmd
} }

View File

@@ -9,22 +9,24 @@ func Prepare() *cobra.Command {
var ( var (
workdir string workdir string
target string
) )
_cmd := &cobra.Command{ _cmd := &cobra.Command{
Use: "prepare", Use: "prepare",
Short: "Prepare for installation", Short: "Prepare for installation",
Long: "Prepare for installation", Long: "Prepare for installation",
PreRunE: func(cmd *cobra.Command, args []string) error {
i := installer.NewInstaller(workdir)
return i.CheckOK(cmd.Context())
},
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
_installer := installer.NewInstaller(workdir, target) _installer := installer.NewInstaller(workdir)
_installer.Prepare(cmd.Context()) _installer.Prepare(cmd.Context())
return nil return nil
}, },
} }
_cmd.Flags().StringVar(&workdir, "workdir", "/root/hs-installation", "Working directory") _cmd.Flags().StringVar(&workdir, "workdir", "/root/hs-installation", "Working directory")
_cmd.Flags().StringVar(&target, "target", "self", "Target")
return _cmd return _cmd
} }

View File

@@ -3,11 +3,11 @@ package cmd
import ( import (
"os" "os"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/cmd/makecmd" "yizhisec.com/hsv2/forge/internal/cmd/makecmd"
"yizhisec.com/hsv2/forge/internal/controller/maker" "yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt" "yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/logger"
) )
func makeCmd() *cobra.Command { func makeCmd() *cobra.Command {

View File

@@ -9,10 +9,10 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker" "yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt" "yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/logger"
tc "yizhisec.com/hsv2/forge/pkg/tool/client" tc "yizhisec.com/hsv2/forge/pkg/tool/client"
) )

View File

@@ -2,6 +2,8 @@ package makecmd
import ( import (
"fmt" "fmt"
"os"
"path/filepath"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker" "yizhisec.com/hsv2/forge/internal/controller/maker"
@@ -24,5 +26,84 @@ func Minio() *cobra.Command {
_cmd.Flags().IntVar(&storage, "storage-size", 100, "Storage size(GB)") _cmd.Flags().IntVar(&storage, "storage-size", 100, "Storage size(GB)")
_cmd.AddCommand(minioBase())
return _cmd
}
func minioBase() *cobra.Command {
var (
push bool
)
_cmd := &cobra.Command{
Use: "base",
Short: "Make Minio Image Rely(minio initer)",
RunE: func(cmd *cobra.Command, args []string) error {
const (
DOCKERFILE = `FROM %s
COPY mc /usr/local/bin/mc
RUN sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.tuna.tsinghua.edu.cn/alpine#g' /etc/apk/repositories && \
apk update && \
apk add curl wget tzdata && chmod +x /usr/local/bin/mc && \
mkdir -p /data && \
wget https://artifactory.yizhisec.com/artifactory/filestore/hsv2/db/ipv4.ipdb -O /data/ipv4.ipdb
`
)
var (
err error
mk = maker.NewMaker(opt.Cfg.Make.Dir)
tmpDir = filepath.Join(os.TempDir(), "minio-base")
mcFile = filepath.Join(tmpDir, "mc")
dockerfile = filepath.Join(tmpDir, "Dockerfile")
)
if err = os.MkdirAll(tmpDir, 0755); err != nil {
return err
}
defer os.RemoveAll(tmpDir)
if err = mk.RunCommand(cmd.Context(),
tmpDir,
fmt.Sprintf("docker run -d --name minio-base %s server /data", opt.IMAGE_MINIO),
); err != nil {
return err
}
defer mk.RunCommand(cmd.Context(), tmpDir, "docker rm -f minio-base")
if err = mk.RunCommand(cmd.Context(),
tmpDir,
fmt.Sprintf("docker cp minio-base:/usr/bin/mc %s", mcFile),
); err != nil {
return err
}
if err = os.WriteFile(dockerfile, []byte(fmt.Sprintf(DOCKERFILE, opt.IMAGE_ALPINE)), 0644); err != nil {
return err
}
if err = mk.RunCommand(cmd.Context(),
tmpDir,
fmt.Sprintf("docker build --network host -t %s -f %s %s", opt.IMAGE_MINIO_BASE, dockerfile, tmpDir),
); err != nil {
return err
}
if push {
if err = mk.RunCommand(cmd.Context(),
tmpDir,
fmt.Sprintf("docker push %s", opt.IMAGE_MINIO_BASE),
); err != nil {
return err
}
}
return nil
},
}
_cmd.Flags().BoolVar(&push, "push", false, "Push image to registry")
return _cmd return _cmd
} }

View File

@@ -3,7 +3,6 @@ package cmd
import ( import (
"context" "context"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/opt" "yizhisec.com/hsv2/forge/internal/opt"
) )
@@ -12,14 +11,6 @@ var rootCmd = &cobra.Command{
Use: "forge", Use: "forge",
Short: "Forge is a tool for building and installing hsv2", Short: "Forge is a tool for building and installing hsv2",
Long: `A tool for managing build and installation workflows for hsv2.`, Long: `A tool for managing build and installation workflows for hsv2.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if opt.Cfg.Debug {
logger.SetLogLevel(logger.LogLevelDebug)
logger.Warn("running in debug mode")
}
return nil
},
} }
func Execute(ctx context.Context) error { func Execute(ctx context.Context) error {

View File

@@ -1,11 +1,18 @@
package installer package installer
import ( import (
"bufio"
"context" "context"
"fmt" "fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/syscheck" "yizhisec.com/hsv2/forge/pkg/syscheck"
"yizhisec.com/hsv2/forge/pkg/tool/human"
) )
type CheckOption func(*checkOpt) type CheckOption func(*checkOpt)
@@ -13,6 +20,7 @@ type checkOpt struct {
ignoreDisk bool ignoreDisk bool
ignoreMemory bool ignoreMemory bool
ignoreCPU bool ignoreCPU bool
noWriteDown bool
} }
func WithIgnoreDiskCheck(ignore bool) CheckOption { func WithIgnoreDiskCheck(ignore bool) CheckOption {
@@ -33,9 +41,177 @@ func WithIgnoreCPUCheck(ignore bool) CheckOption {
} }
} }
func (i *installer) Check(ctx context.Context, opts ...CheckOption) error { func WithNoWriteDown(noWriteDown bool) CheckOption {
return func(o *checkOpt) {
o.noWriteDown = noWriteDown
}
}
type HardwareCheckResult struct {
Timestamp int64 // ms
DiskSize int64 // bytes, if err, -1; if ignore, 0
DiskReadSpeed int64 // bytes/s, if err, -1; if ignore, 0
DiskWriteSpeed int64 // bytes/s, if err, -1; if ignore, 0
MemorySize int64 // bytes, if err, -1; if ignore, 0
MemoryReadSpeed int64 // bytes/s, if err, -1; if ignore, 0
MemoryWriteSpeed int64 // bytes/s, if err, -1; if ignore, 0
CPUCores int64 // if err, -1; if ignore, 0
CPUFrequency int64 // MHz, if err, -1; if ignore, 0
CPUSupportAES bool
CPUIsX86V2 bool
}
func (h *HardwareCheckResult) Write(filename string) error {
content := []byte(fmt.Sprintf(`timestamp=%d
disk_size=%d
disk_read_speed=%d
disk_write_speed=%d
memory_size=%d
memory_read_speed=%d
memory_write_speed=%d
cpu_cores=%d
cpu_frequency=%d
cpu_support_aes=%t
cpu_is_x86_v2=%t`,
h.Timestamp,
h.DiskSize,
h.DiskReadSpeed,
h.DiskWriteSpeed,
h.MemorySize,
h.MemoryReadSpeed,
h.MemoryWriteSpeed,
h.CPUCores,
h.CPUFrequency,
h.CPUSupportAES,
h.CPUIsX86V2,
))
return os.WriteFile(filename, content, 0644)
}
func (h *HardwareCheckResult) Load(filename string) error {
file, err := os.Open(filename)
if err != nil {
return fmt.Errorf("failed to open file: %w", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
switch key {
case "timestamp":
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
h.Timestamp = -1
} else {
h.Timestamp = v
}
case "disk_size":
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
h.DiskSize = -1
} else {
h.DiskSize = v
}
case "disk_read_speed":
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
h.DiskReadSpeed = -1
} else {
h.DiskReadSpeed = v
}
case "disk_write_speed":
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
h.DiskWriteSpeed = -1
} else {
h.DiskWriteSpeed = v
}
case "memory_size":
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
h.MemorySize = -1
} else {
h.MemorySize = v
}
case "memory_read_speed":
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
h.MemoryReadSpeed = -1
} else {
h.MemoryReadSpeed = v
}
case "memory_write_speed":
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
h.MemoryWriteSpeed = -1
} else {
h.MemoryWriteSpeed = v
}
case "cpu_cores":
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
h.CPUCores = -1
} else {
h.CPUCores = v
}
case "cpu_frequency":
v, err := strconv.ParseInt(value, 10, 64)
if err != nil {
h.CPUFrequency = -1
} else {
h.CPUFrequency = v
}
case "cpu_support_aes":
v, err := strconv.ParseBool(value)
if err == nil {
h.CPUSupportAES = v
}
// bool fields don't use -1, keep default false on error
case "cpu_is_x86_v2":
v, err := strconv.ParseBool(value)
if err == nil {
h.CPUIsX86V2 = v
}
// bool fields don't use -1, keep default false on error
}
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("failed to read file: %w", err)
}
return nil
}
func (h *HardwareCheckResult) Pass(opts ...CheckOption) error {
const (
DISK_SIZE int64 = 480 * 1024 * 1024 * 1024 // 480GB
DISK_READ_SPEED int64 = 1024 * 1024 * 1024 // 1024MB/s
DISK_WRITE_SPEED int64 = 500 * 1024 * 1024 // 500MB/s
MEMORY_SIZE int64 = 15.5 * 1024 * 1024 * 1024 // 15.5GB
MEMORY_READ_SPEED int64 = 1024 * 1024 * 1024 // 1024MB/s
MEMORY_WRITE_SPEED int64 = 500 * 1024 * 1024 // 500MB/s
CPUCORES int64 = 8
CPU_FREQUENCY int64 = 2.0 * 1024 // 2.0GHz
CPU_NEED_V2 = true
CPU_NEED_AES = true
)
var ( var (
err error now = time.Now()
o = &checkOpt{} o = &checkOpt{}
) )
@@ -43,98 +219,195 @@ func (i *installer) Check(ctx context.Context, opts ...CheckOption) error {
fn(o) fn(o)
} }
logger.Info("☑️ installer.Check: Starting system checks...") if h.Timestamp < now.AddDate(0, 0, -1).UnixMilli() {
return fmt.Errorf("上次检测结果已失效, 请重新检测")
if err = i.targetOK(ctx); err != nil {
return err
} }
// 1. Check disk space: >= 500GB if o.ignoreDisk || h.DiskSize+h.DiskReadSpeed+h.DiskWriteSpeed == -3 {
logger.Info("☑️ installer.Check: Checking disk space...") goto PASS_MEMORY
diskSpaceResult, err := syscheck.CheckDiskSpace(ctx, i, 500)
if err != nil {
logger.Debug("❌ installer.Check: Failed to check disk space: %v", err)
return fmt.Errorf("failed to check disk space: %w", err)
} }
if !diskSpaceResult.Passed { if h.DiskSize < DISK_SIZE {
logger.Error("❌ %s: %s (Expected: %s, Actual: %s)", diskSpaceResult.Name, diskSpaceResult.Message, diskSpaceResult.Expected, diskSpaceResult.Actual) return fmt.Errorf("磁盘大小不满足, 需要: %s, 实际: %s", human.Size(DISK_SIZE), human.Size(h.DiskSize))
if !o.ignoreDisk {
return fmt.Errorf("disk space check failed: %s", diskSpaceResult.Message)
}
} }
logger.Info("✅ %s: %s", diskSpaceResult.Name, diskSpaceResult.Actual) if h.DiskReadSpeed < DISK_READ_SPEED {
return fmt.Errorf("磁盘读速度不满足, 需要: %s, 实际: %s", human.Size(DISK_READ_SPEED), human.Size(h.DiskReadSpeed))
// 2. Check disk performance: write >= 500MB/s, read >= 500MB/s
logger.Info("☑️ installer.Check: Checking disk performance...")
diskPerfResult, err := syscheck.CheckDiskPerformance(ctx, i, 500, 500)
if err != nil {
logger.Debug("❌ installer.Check: Failed to check disk performance: %v", err)
return fmt.Errorf("failed to check disk performance: %w", err)
} }
if !diskPerfResult.Passed { if h.DiskWriteSpeed < DISK_WRITE_SPEED {
logger.Error("❌ %s: %s (Expected: %s, Actual: %s)", diskPerfResult.Name, diskPerfResult.Message, diskPerfResult.Expected, diskPerfResult.Actual) return fmt.Errorf("磁盘写速度不满足, 需要: %s, 实际: %s", human.Size(DISK_WRITE_SPEED), human.Size(h.DiskWriteSpeed))
if !o.ignoreDisk {
return fmt.Errorf("disk performance check failed: %s", diskPerfResult.Message)
}
} }
logger.Info("✅ %s: %s", diskPerfResult.Name, diskPerfResult.Actual) PASS_MEMORY:
// 3. Check memory size: >= 15.5GB if o.ignoreMemory || h.MemorySize+h.MemoryReadSpeed+h.MemoryWriteSpeed == -3 {
logger.Info("☑️ installer.Check: Checking memory size...") goto PASS_CPU
memResult, err := syscheck.CheckMemory(ctx, i, 15.5)
if err != nil {
logger.Debug("❌ installer.Check: Failed to check memory: %v", err)
return fmt.Errorf("failed to check memory: %w", err)
} }
if !memResult.Passed { if h.MemorySize < MEMORY_SIZE {
logger.Error("❌ %s: %s (Expected: %s, Actual: %s)", memResult.Name, memResult.Message, memResult.Expected, memResult.Actual) return fmt.Errorf("内存大小不满足, 需要: %s, 实际: %s", human.Size(MEMORY_SIZE), human.Size(h.MemorySize))
if !o.ignoreMemory {
return fmt.Errorf("memory check failed: %s", memResult.Message)
}
} }
logger.Info("✅ %s: %s", memResult.Name, memResult.Actual) if h.MemoryReadSpeed < MEMORY_READ_SPEED {
return fmt.Errorf("内存读速度不满足, 需要: %s, 实际: %s", human.Size(MEMORY_READ_SPEED), human.Size(h.MemoryReadSpeed))
// 4. Check CPU cores: >= 8
logger.Info("☑️ installer.Check: Checking CPU cores...")
cpuCoresResult, err := syscheck.CheckCPUCores(ctx, i, 8)
if err != nil {
logger.Debug("❌ installer.Check: Failed to check CPU cores: %v", err)
return fmt.Errorf("failed to check CPU cores: %w", err)
} }
if !cpuCoresResult.Passed { if h.MemoryWriteSpeed < MEMORY_WRITE_SPEED {
logger.Error("❌ %s: %s (Expected: %s, Actual: %s)", cpuCoresResult.Name, cpuCoresResult.Message, cpuCoresResult.Expected, cpuCoresResult.Actual) return fmt.Errorf("内存写速度不满足, 需要: %s, 实际: %s", human.Size(MEMORY_WRITE_SPEED), human.Size(h.MemoryWriteSpeed))
if !o.ignoreCPU {
return fmt.Errorf("CPU cores check failed: %s", cpuCoresResult.Message)
}
} }
logger.Info("✅ %s: %s", cpuCoresResult.Name, cpuCoresResult.Actual) PASS_CPU:
// 5. Check CPU frequency: >= 2GHz if o.ignoreCPU || h.CPUCores+h.CPUFrequency == -2 {
logger.Info("☑️ installer.Check: Checking CPU frequency...") goto END
cpuFreqResult, err := syscheck.CheckCPUFrequency(ctx, i, 2.0) }
if err != nil { if h.CPUCores < CPUCORES {
logger.Debug("❌ installer.Check: Failed to check CPU frequency: %v", err) return fmt.Errorf("cpu 核心数不满足, 需要: %d, 实际: %d", CPUCORES, h.CPUCores)
return fmt.Errorf("failed to check CPU frequency: %w", err)
} }
if !cpuFreqResult.Passed { if h.CPUFrequency < CPU_FREQUENCY {
logger.Error("❌ %s: %s (Expected: %s, Actual: %s)", cpuFreqResult.Name, cpuFreqResult.Message, cpuFreqResult.Expected, cpuFreqResult.Actual) return fmt.Errorf("cpu 频率不满足, 需要: %s, 实际: %s", human.Size(CPU_FREQUENCY), human.Size(h.CPUFrequency))
if !o.ignoreCPU {
return fmt.Errorf("CPU frequency check failed: %s", cpuFreqResult.Message)
}
} }
logger.Info("✅ %s: %s", cpuFreqResult.Name, cpuFreqResult.Actual) if CPU_NEED_V2 && !h.CPUIsX86V2 {
return fmt.Errorf("cpu 不支持aes")
}
logger.Info("✅ installer.Check: All system checks passed successfully!") if CPU_NEED_AES && !h.CPUSupportAES {
return fmt.Errorf("cpu 不支持 x86_v2s")
}
END:
return nil return nil
} }
func (i *installer) HardwareCheck(ctx context.Context, opts ...CheckOption) error {
var (
err error
o = &checkOpt{}
now = time.Now()
result = &HardwareCheckResult{
Timestamp: now.UnixMilli(),
}
cpuInfo syscheck.CPUInfo
dir string
)
for _, fn := range opts {
fn(o)
}
logger.Info("✅ 开始目标机器系统检测...")
if o.ignoreDisk {
logger.Warn("⚠️ 跳过磁盘检测")
result.DiskSize = -1
result.DiskReadSpeed = -1
result.DiskWriteSpeed = -1
goto CHECK_MEMORY
}
if result.DiskSize, err = syscheck.GetDiskSpace(ctx); err != nil {
logger.Debug("❌ installer.HardwareCheck: Failed to get disk space: %v", err)
return err
}
logger.Info("💾 磁盘容量: %s", human.Size(result.DiskSize))
if result.DiskReadSpeed, result.DiskWriteSpeed, err = syscheck.GetDiskSpeed(ctx); err != nil {
logger.Debug("❌ installer.HardwareCheck: Failed to get disk speed: %v", err)
return err
}
logger.Info("💾 磁盘速率: 读取(%s/每秒), 写入(%s/每秒)", human.Size(result.DiskReadSpeed), human.Size(result.DiskWriteSpeed))
CHECK_MEMORY:
if o.ignoreMemory {
logger.Warn("⚠️ 跳过内存检测")
result.MemorySize = -1
result.MemoryReadSpeed = -1
result.MemoryWriteSpeed = -1
goto CHECK_CPU
}
if result.MemorySize, err = syscheck.GetMemorySpace(ctx); err != nil {
logger.Debug("❌ installer.HardwareCheck: Failed to get memory size: %v", err)
return err
}
logger.Info("💿 内存容量: %s", human.Size(result.MemorySize))
if result.MemoryReadSpeed, result.MemoryWriteSpeed, err = syscheck.GetMemorySpeed(ctx); err != nil {
logger.Debug("❌ installer.HardwareCheck: Failed to get memory speed: %v", err)
return err
}
logger.Info("💿 内存速率: 读取(%s/每秒), 写入(%s/每秒)", human.Size(result.MemoryReadSpeed), human.Size(result.MemoryWriteSpeed))
CHECK_CPU:
if o.ignoreCPU {
logger.Warn("⚠️ 跳过 CPU 检测")
result.CPUCores = -1
result.CPUFrequency = -1
result.CPUSupportAES = false
result.CPUIsX86V2 = false
goto END
}
if cpuInfo, err = syscheck.GetCPUInfo(ctx); err != nil {
logger.Debug("❌ installer.HardwareCheck: Failed to get CPU info: %v", err)
return err
}
result.CPUCores = cpuInfo.Cores
result.CPUFrequency = cpuInfo.FrequencyMHz
result.CPUSupportAES = cpuInfo.SupportAES
result.CPUIsX86V2 = cpuInfo.IsX86V2
logger.Info("🧮 CPU 核心数: %d", result.CPUCores)
logger.Info("🧮 CPU 频率: %d Mhz", result.CPUFrequency)
logger.Info("🧮 CPU 支持 AES: %t", result.CPUSupportAES)
END:
if dir, err = os.UserHomeDir(); err != nil {
logger.Debug("❌ installer.HardwareCheck: Failed to get user home directory: %v", err)
return err
}
if err = result.Write(filepath.Join(dir, ".hsv2-installation")); err != nil {
logger.Debug("❌ installer.HardwareCheck: Failed to write installation file: %v", err)
return err
}
if err = result.Pass(opts...); err != nil {
logger.Error("❌ %s", err.Error())
return nil
}
logger.Info("✅ 检测完成")
return nil
}
func (*installer) CheckOK(ctx context.Context) error {
var (
err error
h = &HardwareCheckResult{}
dir string
)
if dir, err = os.UserHomeDir(); err != nil {
logger.Debug("❌ installer.HardwareCheck: Failed to get user home directory: %v", err)
return err
}
if err = h.Load(filepath.Join(dir, ".hsv2-installation")); err != nil {
logger.Debug("❌ installer.HardwareCheck: Failed to load installation file: %v", err)
return err
}
return h.Pass()
}

View File

@@ -2,41 +2,27 @@ package installer
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io"
"os"
"os/exec" "os/exec"
"path/filepath" "strings"
"gitea.loveuer.com/yizhisec/pkg3/logger" "github.com/samber/lo"
) )
type installer struct { type installer struct {
workdir string workdir string
target string
}
func (i *installer) buildCommand(ctx context.Context, cmds ...string) *exec.Cmd {
if len(cmds) == 0 {
return nil
}
if i.target == "self" {
return exec.CommandContext(ctx, cmds[0], cmds[1:]...)
}
sshArgs := append([]string{i.target}, cmds...)
return exec.CommandContext(ctx, "ssh", sshArgs...)
} }
// ExecuteCommand implements syscheck.CommandExecutor interface // ExecuteCommand implements syscheck.CommandExecutor interface
func (i *installer) ExecuteCommand(ctx context.Context, cmds ...string) (string, error) { func (i *installer) ExecuteCommand(ctx context.Context, cmds ...string) (string, error) {
cmd := i.buildCommand(ctx, cmds...) fcs := lo.Filter(cmds, func(item string, _ int) bool { return item != "" })
if cmd == nil {
return "", fmt.Errorf("failed to build command") if len(fcs) == 0 {
return "", fmt.Errorf("empty commands")
} }
cmd := exec.CommandContext(ctx, "sh", "-c", strings.Join(fcs, " "))
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
return "", fmt.Errorf("command failed: %w, output: %s", err, string(output)) return "", fmt.Errorf("command failed: %w, output: %s", err, string(output))
@@ -45,109 +31,6 @@ func (i *installer) ExecuteCommand(ctx context.Context, cmds ...string) (string,
return string(output), nil return string(output), nil
} }
type CopyFileOption func(*copyFileOptions) func NewInstaller(workdir string) *installer {
type copyFileOptions struct { return &installer{workdir: workdir}
addExecutable bool
}
func withCopyFileExecutable() func(*copyFileOptions) {
return func(o *copyFileOptions) {
o.addExecutable = true
}
}
func (i *installer) copyFile(ctx context.Context, src, dst string, opts ...CopyFileOption) error {
logger.Debug("☑️ installer.copyFile: Copying file from %s to %s (target: %s)", src, dst, i.target)
var (
err error
o = &copyFileOptions{}
srcFile, dstFile *os.File
srcInfo os.FileInfo
)
for _, fn := range opts {
fn(o)
}
if i.target == "self" {
// Simply copy file locally
logger.Debug("Copying file locally: %s -> %s", src, dst)
// Open source file
if srcFile, err = os.Open(src); err != nil {
logger.Error("❌ Failed to open source file %s: %v", src, err)
return fmt.Errorf("failed to open source file: %w", err)
}
defer srcFile.Close()
// Create destination directory if needed
dstDir := filepath.Dir(dst)
if err = os.MkdirAll(dstDir, 0755); err != nil {
logger.Error("❌ Failed to create destination directory %s: %v", dstDir, err)
return fmt.Errorf("failed to create destination directory: %w", err)
}
// Create destination file
if dstFile, err = os.Create(dst); err != nil {
logger.Error("❌ Failed to create destination file %s: %v", dst, err)
return fmt.Errorf("failed to create destination file: %w", err)
}
defer dstFile.Close()
// Copy file content
if _, err = io.Copy(dstFile, srcFile); err != nil {
logger.Error("❌ Failed to copy file content: %v", err)
return fmt.Errorf("failed to copy file content: %w", err)
}
// Get source file permissions
if srcInfo, err = os.Stat(src); err == nil {
if err = os.Chmod(dst, srcInfo.Mode()); err != nil {
logger.Debug("⚠️ Failed to set file permissions: %v", err)
}
}
logger.Info("✅ File copied locally: %s -> %s", src, dst)
return nil
}
// Copy file via scp to remote target
logger.Debug("Copying file via scp: %s -> %s:%s", src, i.target, dst)
// Format: scp <src> <target>:<dst>
cmd := exec.CommandContext(ctx, "scp", src, fmt.Sprintf("%s:%s", i.target, dst))
output, err := cmd.CombinedOutput()
if err != nil {
logger.Error("❌ Failed to copy file via scp: %v, output: %s", err, string(output))
return fmt.Errorf("failed to copy file via scp: %w, output: %s", err, string(output))
}
logger.Info("✅ File copied via scp: %s -> %s:%s", src, i.target, dst)
return nil
}
func (i *installer) targetOK(ctx context.Context) error {
cmd := i.buildCommand(ctx, "whoami")
output, err := cmd.CombinedOutput()
if err != nil {
logger.Debug("❌ installer.targetOK: check target %s failed, err = %v", i.target, err)
return err
}
if string(output) != "root\n" {
logger.Debug("❌ installer.targetOK: check target %s failed, output = %s", i.target, string(output))
return errors.New("target is not root user")
}
return nil
}
func NewInstaller(workdir, target string) *installer {
if target == "" {
logger.Warn("🎯 NewInstaller: target empty, set to default(self)")
target = "self"
}
return &installer{workdir: workdir, target: target}
} }

View File

@@ -2,11 +2,7 @@ package installer
import ( import (
"context" "context"
"fmt"
"os"
"path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"github.com/samber/lo" "github.com/samber/lo"
) )
@@ -41,83 +37,5 @@ func WithK0sWorkerTokenFile(filename string) K0sOpt {
} }
func (i *installer) K0s(ctx context.Context, opts ...K0sOpt) error { func (i *installer) K0s(ctx context.Context, opts ...K0sOpt) error {
var ( panic("plz impl")
err error
o = &k0sOpt{
Type: "controller",
controllerAsWorker: false,
WorkerTokenFile: "/etc/k0s/worker.token",
}
)
if err = i.targetOK(ctx); err != nil {
return err
}
for _, fn := range opts {
fn(o)
}
binaries := []string{
"dependency/bin/k0s",
"dependency/bin/k9s", "dependency/bin/kubectl", "dependency/bin/helm"}
if err = i.checkFiles(binaries...); err != nil {
return err
}
// check image tar files:
images := []string{
"dependency/image/k0s.apiserver-network-proxy-agent.tar",
"dependency/image/k0s.cni-node.tar",
"dependency/image/k0s.coredns.tar",
"dependency/image/k0s.kube-proxy.tar",
"dependency/image/k0s.kube-router.tar",
"dependency/image/k0s.metrics-server.tar",
"dependency/image/k0s.pause.tar",
}
if err = i.checkFiles(images...); err != nil {
return err
}
// copy binaries to /usr/local/bin and add executable permissions
if err = i.copyFile(ctx, "dependency/bin/k0s", "/usr/local/bin/k0s", withCopyFileExecutable()); err != nil {
return err
}
if err = i.copyFile(ctx, "dependency/bin/k9s", "/usr/local/bin/k9s", withCopyFileExecutable()); err != nil {
return err
}
if err = i.copyFile(ctx, "dependency/bin/kubectl", "/usr/local/bin/kubectl", withCopyFileExecutable()); err != nil {
return err
}
if err = i.copyFile(ctx, "dependency/bin/helm", "/usr/local/bin/helm", withCopyFileExecutable()); err != nil {
return err
}
i.ExecuteCommand(ctx, "k0s", "")
return nil
}
// checkBinaryFiles checks if the required binary files exist in the dependency/bin directory
func (i *installer) checkFiles(fileBaseName ...string) error {
logger.Info("☑️ installer.checkFiles: Checking files in %s...", i.workdir)
for _, file := range fileBaseName {
filename := filepath.Join(i.workdir, file)
logger.Debug("Checking file: %s", filename)
if _, err := os.Stat(filename); os.IsNotExist(err) {
logger.Error("❌ File not found: %s", filename)
return fmt.Errorf("file not found: %s", filename)
} else if err != nil {
logger.Error("❌ Failed to check file %s: %v", filename, err)
return fmt.Errorf("failed to check file %s: %w", filename, err)
}
logger.Info("✅ File found: %s", file)
}
logger.Info("✅ installer.checkBinaryFiles: All binary files verified successfully!")
return nil
} }

View File

@@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
) )
func (i *installer) Prepare(ctx context.Context) error { func (i *installer) Prepare(ctx context.Context) error {
@@ -14,10 +14,6 @@ func (i *installer) Prepare(ctx context.Context) error {
logger.Info("☑️ installer.Prepare: Starting system preparation...") logger.Info("☑️ installer.Prepare: Starting system preparation...")
if err = i.targetOK(ctx); err != nil {
return err
}
// 1. Set timezone to Asia/Shanghai // 1. Set timezone to Asia/Shanghai
logger.Info("☑️ installer.Prepare: Setting timezone to Asia/Shanghai...") logger.Info("☑️ installer.Prepare: Setting timezone to Asia/Shanghai...")
if err = i.setTimezone(ctx); err != nil { if err = i.setTimezone(ctx); err != nil {
@@ -65,31 +61,23 @@ func (i *installer) Prepare(ctx context.Context) error {
// setTimezone sets the system timezone to Asia/Shanghai // setTimezone sets the system timezone to Asia/Shanghai
func (i *installer) setTimezone(ctx context.Context) error { func (i *installer) setTimezone(ctx context.Context) error {
var (
err error
output string
)
// Check if timezone file exists // Check if timezone file exists
cmd := i.buildCommand(ctx, "test", "-f", "/usr/share/zoneinfo/Asia/Shanghai") if output, err = i.ExecuteCommand(ctx, "test", "-f", "/usr/share/zoneinfo/Asia/Shanghai"); err != nil {
if cmd == nil { return fmt.Errorf("failed to set timezone, err =%s, raw = %s", err.Error(), output)
return fmt.Errorf("failed to build command")
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("timezone file /usr/share/zoneinfo/Asia/Shanghai not found")
} }
// Remove old localtime link/file // Remove old localtime link/file
cmd = i.buildCommand(ctx, "rm", "-f", "/etc/localtime") if output, err = i.ExecuteCommand(ctx, "rm", "-f", "/etc/localtime"); err != nil {
if cmd == nil { return fmt.Errorf("failed to set timezone, err =%s, raw = %s", err.Error(), output)
return fmt.Errorf("failed to build command")
}
if err := cmd.Run(); err != nil {
logger.Debug("Failed to remove /etc/localtime: %v", err)
} }
// Create symlink // Create symlink
cmd = i.buildCommand(ctx, "ln", "-s", "/usr/share/zoneinfo/Asia/Shanghai", "/etc/localtime") if output, err = i.ExecuteCommand(ctx, "ln", "-s", "/usr/share/zoneinfo/Asia/Shanghai", "/etc/localtime"); err != nil {
if cmd == nil { return fmt.Errorf("failed to set timezone, err =%s, raw = %s", err.Error(), output)
return fmt.Errorf("failed to build command")
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to create symlink: %w", err)
} }
return nil return nil
@@ -97,22 +85,19 @@ func (i *installer) setTimezone(ctx context.Context) error {
// disableSwap disables all swap partitions and removes swap entries from /etc/fstab // disableSwap disables all swap partitions and removes swap entries from /etc/fstab
func (i *installer) disableSwap(ctx context.Context) error { func (i *installer) disableSwap(ctx context.Context) error {
var (
err error
output string
)
// Turn off all swap // Turn off all swap
cmd := i.buildCommand(ctx, "swapoff", "-a") if output, err = i.ExecuteCommand(ctx, "swapoff", "-a"); err != nil {
if cmd == nil { logger.Debug("Failed to swapoff: %v (may be already off), raw = %s", err, output)
return fmt.Errorf("failed to build command")
}
if err := cmd.Run(); err != nil {
logger.Debug("Failed to swapoff: %v (may be already off)", err)
} }
// Comment out swap entries in /etc/fstab to make it persistent // Comment out swap entries in /etc/fstab to make it persistent
cmd = i.buildCommand(ctx, "sed", "-i", "/swap/s/^/#/", "/etc/fstab") if output, err = i.ExecuteCommand(ctx, "sed", "-i", "/swap/s/^/#/", "/etc/fstab"); err != nil {
if cmd == nil { logger.Debug("Failed to comment swap in /etc/fstab: %v, raw = %s", err, output)
return fmt.Errorf("failed to build command")
}
if err := cmd.Run(); err != nil {
logger.Debug("Failed to comment swap in /etc/fstab: %v", err)
} }
return nil return nil
@@ -120,23 +105,21 @@ func (i *installer) disableSwap(ctx context.Context) error {
// loadKernelModule loads a kernel module and ensures it's loaded on boot // loadKernelModule loads a kernel module and ensures it's loaded on boot
func (i *installer) loadKernelModule(ctx context.Context, moduleName string) error { func (i *installer) loadKernelModule(ctx context.Context, moduleName string) error {
var (
err error
output string
)
// Load the module immediately // Load the module immediately
cmd := i.buildCommand(ctx, "modprobe", moduleName) if output, err = i.ExecuteCommand(ctx, "modprobe", moduleName); err != nil {
if cmd == nil { return fmt.Errorf("failed to load module %s: %w, raw = %s", moduleName, err, output)
return fmt.Errorf("failed to build command")
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to load module %s: %w", moduleName, err)
} }
// Add to /etc/modules-load.d/ to load on boot // Add to /etc/modules-load.d/ to load on boot
filePath := fmt.Sprintf("/etc/modules-load.d/%s.conf", moduleName) filePath := fmt.Sprintf("/etc/modules-load.d/%s.conf", moduleName)
cmd = i.buildCommand(ctx, "bash", "-c", fmt.Sprintf("echo '%s' > %s", moduleName, filePath)) command := fmt.Sprintf("echo '%s' > %s", moduleName, filePath)
if cmd == nil { if output, err = i.ExecuteCommand(ctx, "bash", "-c", command); err != nil {
return fmt.Errorf("failed to build command") logger.Debug("Failed to add module to modules-load.d: %v, raw = %s", err, output)
}
if err := cmd.Run(); err != nil {
logger.Debug("Failed to add module to modules-load.d: %v", err)
} }
return nil return nil
@@ -144,6 +127,11 @@ func (i *installer) loadKernelModule(ctx context.Context, moduleName string) err
// applySysctlSettings applies required sysctl settings for Kubernetes // applySysctlSettings applies required sysctl settings for Kubernetes
func (i *installer) applySysctlSettings(ctx context.Context) error { func (i *installer) applySysctlSettings(ctx context.Context) error {
var (
err error
output string
)
const sysctlConfig = `# Kubernetes required settings const sysctlConfig = `# Kubernetes required settings
net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-ip6tables = 1
@@ -163,21 +151,14 @@ net.ipv4.neigh.default.gc_thresh3 = 8192
` `
// Write sysctl config file // Write sysctl config file
cmd := i.buildCommand(ctx, "bash", "-c", fmt.Sprintf("cat > /etc/sysctl.d/99-kubernetes.conf << 'EOF'\n%sEOF", sysctlConfig)) command := fmt.Sprintf("cat > /etc/sysctl.d/99-kubernetes.conf << 'EOF'\n%sEOF", sysctlConfig)
if cmd == nil { if output, err = i.ExecuteCommand(ctx, "bash", "-c", command); err != nil {
return fmt.Errorf("failed to build command") return fmt.Errorf("failed to write sysctl config: %w, raw = %s", err, output)
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to write sysctl config: %w", err)
} }
// Apply sysctl settings // Apply sysctl settings
cmd = i.buildCommand(ctx, "sysctl", "--system") if output, err = i.ExecuteCommand(ctx, "sysctl", "--system"); err != nil {
if cmd == nil { return fmt.Errorf("failed to apply sysctl settings: %w, raw = %s", err, output)
return fmt.Errorf("failed to build command")
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to apply sysctl settings: %w", err)
} }
return nil return nil

View File

@@ -6,7 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -6,7 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model" "yizhisec.com/hsv2/forge/pkg/model"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -6,7 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -6,7 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -6,7 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -7,8 +7,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/internal/opt" "yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -7,8 +7,8 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/archiver" "yizhisec.com/hsv2/forge/pkg/archiver"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model" "yizhisec.com/hsv2/forge/pkg/model"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -6,7 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -4,8 +4,8 @@ import (
"context" "context"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/archiver" "yizhisec.com/hsv2/forge/pkg/archiver"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model" "yizhisec.com/hsv2/forge/pkg/model"
) )

View File

@@ -8,8 +8,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"github.com/samber/lo" "github.com/samber/lo"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -5,6 +5,8 @@ import (
"context" "context"
"fmt" "fmt"
"os/exec" "os/exec"
"yizhisec.com/hsv2/forge/pkg/logger"
) )
func (m *maker) RunCommand(ctx context.Context, dir string, _cmds ...string) error { func (m *maker) RunCommand(ctx context.Context, dir string, _cmds ...string) error {
@@ -12,6 +14,8 @@ func (m *maker) RunCommand(ctx context.Context, dir string, _cmds ...string) err
return nil return nil
} }
logger.Debug("maker.RunCommand: dir = %s, cmds = %v", dir, _cmds)
for _, cmdStr := range _cmds { for _, cmdStr := range _cmds {
cmd := exec.CommandContext(ctx, "sh", "-c", cmdStr) cmd := exec.CommandContext(ctx, "sh", "-c", cmdStr)

View File

@@ -7,10 +7,10 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"github.com/samber/lo" "github.com/samber/lo"
"yizhisec.com/hsv2/forge/pkg/downloader" "yizhisec.com/hsv2/forge/pkg/downloader"
"yizhisec.com/hsv2/forge/pkg/extractor" "yizhisec.com/hsv2/forge/pkg/extractor"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/tool/random" "yizhisec.com/hsv2/forge/pkg/tool/random"
) )

View File

@@ -7,9 +7,9 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/internal/opt" "yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/archiver" "yizhisec.com/hsv2/forge/pkg/archiver"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model" "yizhisec.com/hsv2/forge/pkg/model"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -5,7 +5,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model" "yizhisec.com/hsv2/forge/pkg/model"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -6,7 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model" "yizhisec.com/hsv2/forge/pkg/model"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -5,8 +5,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/archiver" "yizhisec.com/hsv2/forge/pkg/archiver"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -6,8 +6,8 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"github.com/samber/lo" "github.com/samber/lo"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model" "yizhisec.com/hsv2/forge/pkg/model"
) )

View File

@@ -3,7 +3,7 @@ package maker
import ( import (
"testing" "testing"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
) )
func TestImage(t *testing.T) { func TestImage(t *testing.T) {

View File

@@ -5,7 +5,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -6,8 +6,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/downloader" "yizhisec.com/hsv2/forge/pkg/downloader"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model" "yizhisec.com/hsv2/forge/pkg/model"
) )

View File

@@ -6,7 +6,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model" "yizhisec.com/hsv2/forge/pkg/model"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )
@@ -14,20 +15,20 @@ import (
// todo, remake minio-init image // todo, remake minio-init image
func (m *maker) Minio(ctx context.Context, storage string) error { func (m *maker) Minio(ctx context.Context, storage string) error {
var ( var (
err error err error
location = filepath.Join(m.workdir, "dependency", "minio") workdir = filepath.Join(m.workdir, "dependency", "minio")
) )
logger.Info("☑️ maker.Minio: 开始构建 minio 依赖, workdir = %s", location) logger.Info("☑️ maker.Minio: 开始构建 minio 依赖, workdir = %s", workdir)
logger.Debug("☑️ maker.Minio: 构建工作目录, workdir = %s", workdir)
logger.Debug("☑️ maker.Minio: 构建工作目录, workdir = %s", location) _ = os.RemoveAll(workdir)
if err = os.MkdirAll(location, 0755); err != nil { if err = os.MkdirAll(workdir, 0755); err != nil {
logger.Debug("❌ maker.Minio: 创建工作目录失败, workdir = %s, err = %v", location, err) logger.Debug("❌ maker.Minio: 创建工作目录失败, workdir = %s, err = %v", workdir, err)
return err return err
} }
logger.Debug("✅ maker.Minio: 创建工作目录成功, workdir = %s", location) logger.Debug("✅ maker.Minio: 创建工作目录成功, workdir = %s", workdir)
filename := filepath.Join(location, "minio.yaml") filename := filepath.Join(workdir, "minio.yaml")
logger.Debug("☑️ maker.Minio: 准备资源文件, filename = %s, storage = %s", filename, storage) logger.Debug("☑️ maker.Minio: 准备资源文件, filename = %s, storage = %s", filename, storage)
bs := []byte(fmt.Sprintf(resource.YAMLMinIO, storage)) bs := []byte(fmt.Sprintf(resource.YAMLMinIO, storage))
if err = os.WriteFile(filename, bs, 0644); err != nil { if err = os.WriteFile(filename, bs, 0644); err != nil {
@@ -38,13 +39,13 @@ func (m *maker) Minio(ctx context.Context, storage string) error {
logger.Debug("☑️ maker.Minio: 开始获取所需镜像...") logger.Debug("☑️ maker.Minio: 开始获取所需镜像...")
var images = []*model.Image{ var images = []*model.Image{
{Name: "hub.yizhisec.com/hybridscope/v3/minio-init:latest", Fallback: "", Save: "dep.minio-init.tar"}, {Name: opt.IMAGE_MINIO_BASE, Fallback: "", Save: "minio-init.tar"},
{Name: "hub.yizhisec.com/external/minio:RELEASE.2025-03-12T18-04-18Z", Fallback: "", Save: "dep.minio.tar"}, {Name: opt.IMAGE_MINIO, Fallback: "", Save: "minio.tar"},
} }
for _, image := range images { for _, image := range images {
opts := []ImageOpt{ opts := []ImageOpt{
WithImageFallback(image.Fallback), WithImageFallback(image.Fallback),
WithImageSave(filepath.Join(location, image.Save)), WithImageSave(filepath.Join(workdir, image.Save)),
WithImageForcePull(image.Force), WithImageForcePull(image.Force),
} }
@@ -56,7 +57,7 @@ func (m *maker) Minio(ctx context.Context, storage string) error {
} }
logger.Debug("✅ maker.Minio: 获取所需镜像成功!!!") logger.Debug("✅ maker.Minio: 获取所需镜像成功!!!")
logger.Info("✅ maker.Minio: 构建 minio 依赖成功, workdir = %s", location) logger.Info("✅ maker.Minio: 构建 minio 依赖成功, workdir = %s", workdir)
return nil return nil
} }

View File

@@ -7,9 +7,9 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/internal/opt" "yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/downloader" "yizhisec.com/hsv2/forge/pkg/downloader"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model" "yizhisec.com/hsv2/forge/pkg/model"
) )

View File

@@ -6,8 +6,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/downloader" "yizhisec.com/hsv2/forge/pkg/downloader"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model" "yizhisec.com/hsv2/forge/pkg/model"
) )

View File

@@ -6,9 +6,9 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/internal/opt" "yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/downloader" "yizhisec.com/hsv2/forge/pkg/downloader"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model" "yizhisec.com/hsv2/forge/pkg/model"
) )

View File

@@ -6,7 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -6,8 +6,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/internal/opt" "yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model" "yizhisec.com/hsv2/forge/pkg/model"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )
@@ -123,7 +123,7 @@ kubectl rollout restart deployment backup-seafile-deployment -n seafile`
ServerHostname: "cloud.hybridscope.com", ServerHostname: "cloud.hybridscope.com",
Storage: "50Gi", Storage: "50Gi",
} }
workdir = filepath.Join(m.workdir, "dependency", "seafile") workdir = filepath.Join(m.workdir, "dependency", "seafile")
) )
for _, fn := range opts { for _, fn := range opts {
@@ -187,7 +187,7 @@ kubectl rollout restart deployment backup-seafile-deployment -n seafile`
// 7. prepare images // 7. prepare images
logger.Debug("☑️ make.Seafile: 准备 images") logger.Debug("☑️ make.Seafile: 准备 images")
imgDir := filepath.Join(m.workdir, "dependency", "image") imgDir := filepath.Join(m.workdir, "dependency", "image")
if err = os.MkdirAll(imgDir, 0755); err != nil { if err = os.MkdirAll(imgDir, 0755); err != nil {
logger.Error("❌ make.Seafile: 准备 images 目录: %s 失败, err = %v", imgDir, err) logger.Error("❌ make.Seafile: 准备 images 目录: %s 失败, err = %v", imgDir, err)
return err return err

View File

@@ -6,8 +6,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/downloader" "yizhisec.com/hsv2/forge/pkg/downloader"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource" "yizhisec.com/hsv2/forge/pkg/resource"
) )

View File

@@ -1,5 +1,8 @@
package opt package opt
const ( const (
IMAGE_NGINX = "docker-mirror.yizhisec.com/library/nginx:1.29.4-alpine3.23" IMAGE_NGINX = "docker-mirror.yizhisec.com/library/nginx:1.29.4-alpine3.23"
IMAGE_MINIO = "hub.yizhisec.com/external/minio:RELEASE.2025-03-12T18-04-18Z"
IMAGE_ALPINE = "docker-mirror.yizhisec.com/library/alpine:3.23.2"
IMAGE_MINIO_BASE = "hub.yizhisec.com/hsv2/base/minio-init:latest"
) )

View File

@@ -12,7 +12,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
) )
// Options defines options for downloading and extracting archives // Options defines options for downloading and extracting archives

View File

@@ -4,7 +4,7 @@ import (
"context" "context"
"testing" "testing"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
) )
func TestDownloadAndExtract(t *testing.T) { func TestDownloadAndExtract(t *testing.T) {

View File

@@ -9,7 +9,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
) )
// Options defines options for downloading files // Options defines options for downloading files

View File

@@ -10,7 +10,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
) )
type Options struct { type Options struct {

View File

@@ -13,7 +13,7 @@ import (
"strings" "strings"
"time" "time"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
) )
// PullOption is a functional option for configuring image pull // PullOption is a functional option for configuring image pull

View File

@@ -7,7 +7,7 @@ import (
"testing" "testing"
"time" "time"
"gitea.loveuer.com/yizhisec/pkg3/logger" "yizhisec.com/hsv2/forge/pkg/logger"
) )
// TestPullImage_PublicImage tests pulling a public image from Docker Hub // TestPullImage_PublicImage tests pulling a public image from Docker Hub

53
pkg/logger/ctx.go Normal file
View File

@@ -0,0 +1,53 @@
package logger
import (
"context"
uuid2 "github.com/google/uuid"
)
type _traceId struct{}
var TraceId = _traceId{}
func traceId(ctx context.Context) string {
if ctx == nil {
uuid, _ := uuid2.NewV7()
return uuid.String()
}
if id, _ := ctx.Value(TraceId).(string); id != "" {
return id
}
uuid, _ := uuid2.NewV7()
return uuid.String()
}
func DebugCtx(ctx context.Context, msg string, data ...any) {
msg = traceId(ctx) + " | " + msg
DefaultLogger.Debug(msg, data...)
}
func InfoCtx(ctx context.Context, msg string, data ...any) {
msg = traceId(ctx) + " | " + msg
DefaultLogger.Info(msg, data...)
}
func WarnCtx(ctx context.Context, msg string, data ...any) {
msg = traceId(ctx) + " | " + msg
DefaultLogger.Warn(msg, data...)
}
func ErrorCtx(ctx context.Context, msg string, data ...any) {
msg = traceId(ctx) + " | " + msg
DefaultLogger.Error(msg, data...)
}
func PanicCtx(ctx context.Context, msg string, data ...any) {
msg = traceId(ctx) + " | " + msg
DefaultLogger.Panic(msg, data...)
}
func FatalCtx(ctx context.Context, msg string, data ...any) {
msg = traceId(ctx) + " | " + msg
DefaultLogger.Fatal(msg, data...)
}

17
pkg/logger/ctx_test.go Normal file
View File

@@ -0,0 +1,17 @@
package logger
import (
"context"
"testing"
)
func TestCtxLog(t *testing.T) {
DebugCtx(nil, "hello %s", "world")
InfoCtx(nil, "hello %s", "world")
WarnCtx(context.Background(), "hello %s", "world")
ctx := context.Background()
ctx = context.WithValue(ctx, TraceId, "value")
SetLogLevel(LogLevelDebug)
DebugCtx(ctx, "hello %s", "world")
ErrorCtx(ctx, "hello %s", "world")
}

67
pkg/logger/default.go Normal file
View File

@@ -0,0 +1,67 @@
package logger
import (
"fmt"
"os"
"sync"
)
var (
nilLogger = func(prefix, timestamp, msg string, data ...any) {}
normalLogger = func(prefix, timestamp, msg string, data ...any) {
fmt.Printf(prefix+"| "+timestamp+" | "+msg+"\n", data...)
}
panicLogger = func(prefix, timestamp, msg string, data ...any) {
panic(fmt.Sprintf(prefix+"| "+timestamp+" | "+msg+"\n", data...))
}
fatalLogger = func(prefix, timestamp, msg string, data ...any) {
fmt.Printf(prefix+"| "+timestamp+" | "+msg+"\n", data...)
os.Exit(1)
}
DefaultLogger = &logger{
Mutex: sync.Mutex{},
timeFormat: "2006-01-02T15:04:05",
writer: os.Stdout,
level: LogLevelInfo,
debug: nilLogger,
info: normalLogger,
warn: normalLogger,
error: normalLogger,
panic: panicLogger,
fatal: fatalLogger,
}
)
func SetTimeFormat(format string) {
DefaultLogger.SetTimeFormat(format)
}
func SetLogLevel(level LogLevel) {
DefaultLogger.SetLogLevel(level)
}
func Debug(msg string, data ...any) {
DefaultLogger.Debug(msg, data...)
}
func Info(msg string, data ...any) {
DefaultLogger.Info(msg, data...)
}
func Warn(msg string, data ...any) {
DefaultLogger.Warn(msg, data...)
}
func Error(msg string, data ...any) {
DefaultLogger.Error(msg, data...)
}
func Panic(msg string, data ...any) {
DefaultLogger.Panic(msg, data...)
}
func Fatal(msg string, data ...any) {
DefaultLogger.Fatal(msg, data...)
}

115
pkg/logger/logger.go Normal file
View File

@@ -0,0 +1,115 @@
package logger
import (
"github.com/fatih/color"
"io"
"sync"
"time"
)
type LogLevel uint32
const (
LogLevelDebug = iota
LogLevelInfo
LogLevelWarn
LogLevelError
LogLevelPanic
LogLevelFatal
)
type logger struct {
sync.Mutex
timeFormat string
writer io.Writer
level LogLevel
debug func(prefix, timestamp, msg string, data ...any)
info func(prefix, timestamp, msg string, data ...any)
warn func(prefix, timestamp, msg string, data ...any)
error func(prefix, timestamp, msg string, data ...any)
panic func(prefix, timestamp, msg string, data ...any)
fatal func(prefix, timestamp, msg string, data ...any)
}
var (
red = color.New(color.FgRed)
hired = color.New(color.FgHiRed)
green = color.New(color.FgGreen)
yellow = color.New(color.FgYellow)
white = color.New(color.FgWhite)
)
func (l *logger) SetTimeFormat(format string) {
l.Lock()
defer l.Unlock()
l.timeFormat = format
}
func (l *logger) SetLogLevel(level LogLevel) {
l.Lock()
defer l.Unlock()
if level > LogLevelDebug {
l.debug = nilLogger
} else {
l.debug = normalLogger
}
if level > LogLevelInfo {
l.info = nilLogger
} else {
l.info = normalLogger
}
if level > LogLevelWarn {
l.warn = nilLogger
} else {
l.warn = normalLogger
}
if level > LogLevelError {
l.error = nilLogger
} else {
l.error = normalLogger
}
if level > LogLevelPanic {
l.panic = nilLogger
} else {
l.panic = panicLogger
}
if level > LogLevelFatal {
l.fatal = nilLogger
} else {
l.fatal = fatalLogger
}
}
func (l *logger) Debug(msg string, data ...any) {
l.debug(white.Sprint("Debug "), time.Now().Format(l.timeFormat), msg, data...)
}
func (l *logger) Info(msg string, data ...any) {
l.info(green.Sprint("Info "), time.Now().Format(l.timeFormat), msg, data...)
}
func (l *logger) Warn(msg string, data ...any) {
l.warn(yellow.Sprint("Warn "), time.Now().Format(l.timeFormat), msg, data...)
}
func (l *logger) Error(msg string, data ...any) {
l.error(red.Sprint("Error "), time.Now().Format(l.timeFormat), msg, data...)
}
func (l *logger) Panic(msg string, data ...any) {
l.panic(hired.Sprint("Panic "), time.Now().Format(l.timeFormat), msg, data...)
}
func (l *logger) Fatal(msg string, data ...any) {
l.fatal(hired.Sprint("Fatal "), time.Now().Format(l.timeFormat), msg, data...)
}
type WroteLogger interface {
Info(msg string, data ...any)
}

21
pkg/logger/new.go Normal file
View File

@@ -0,0 +1,21 @@
package logger
import (
"os"
"sync"
)
func New() *logger {
return &logger{
Mutex: sync.Mutex{},
timeFormat: "2006-01-02T15:04:05",
writer: os.Stdout,
level: LogLevelInfo,
debug: nilLogger,
info: normalLogger,
warn: normalLogger,
error: normalLogger,
panic: panicLogger,
fatal: fatalLogger,
}
}

View File

@@ -47,15 +47,6 @@ location /client/dl/ {
} }
} }
location /user/avatar/ {
auth_request /token_auth;
proxy_set_header Cookie $http_cookie;
alias /static/avatar/;
expires 7d;
add_header Cache-Control public;
}
location /file/share/ { location /file/share/ {
auth_request /token_auth; auth_request /token_auth;

View File

@@ -78,6 +78,10 @@ server {
server_tokens off; server_tokens off;
location / {
proxy_pass http://front-admin-service;
}
location /api/system/version { location /api/system/version {
proxy_pass http://app-helper-service/api/v2_2/system/version; proxy_pass http://app-helper-service/api/v2_2/system/version;
} }
@@ -90,14 +94,32 @@ server {
proxy_pass http://app-helper-service.hsv2; proxy_pass http://app-helper-service.hsv2;
} }
# location /wm/ { location /user/avatar/ {
# alias /data/wm/; proxy_pass http://app-helper-service.hsv2/api/v2_2/_obj/;
# expires 30d; }
# add_header Cache-Control public;
# }
location / { location /api/account/profile/avatar {
proxy_pass http://front-admin-service; proxy_pass http://app-helper-service.hsv2/api/v2_2/user/profile/avatar/update;
}
# create layer4 resource
location = /api/admin/business-center/network-app/tunnel {
proxy_pass http://app-helper-service.hsv2/api/v2_2/interceptor/mie/resource4/create/icon$request_uri;
}
# update layer4 resource
location ~ ^/api/admin/business-center/network-app/tunnel/\d+/info$ {
proxy_pass http://app-helper-service.hsv2/api/v2_2/interceptor/mie/resource4/update/icon$request_uri;
}
# create layer7 resource
location = /api/admin/strategy/osi-resource {
proxy_pass http://app-helper-service.hsv2/api/v2_2/interceptor/mie/resource7/create/icon$request_uri;
}
# update layer7 resource
location ~ ^/api/admin/strategy/osi-resource/\d+/info$ {
proxy_pass http://app-helper-service.hsv2/api/v2_2/interceptor/mie/resource7/update/icon$request_uri;
} }
location /api/v2_1/user { location /api/v2_1/user {
@@ -120,6 +142,10 @@ server {
proxy_pass http://client-linux-service; proxy_pass http://client-linux-service;
} }
location /api/v2_2/yosguard {
proxy_pass http://10.118.2.10:7788;
}
include /etc/nginx/common/common.conf; include /etc/nginx/common/common.conf;
location /ws { location /ws {
@@ -130,28 +156,11 @@ server {
proxy_read_timeout 300s; proxy_read_timeout 300s;
} }
# location /backup {
# proxy_pass http://hs-backup-server;
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_read_timeout 300s;
# }
location /api/local/user/import/template { location /api/local/user/import/template {
auth_request /token_auth; auth_request /token_auth;
alias /static/resource/local_user_import_template.xlsx; alias /static/resource/local_user_import_template.xlsx;
} }
# location /wm/api {
# proxy_pass http://hs-watermark-server;
# proxy_http_version 1.1;
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $proxy_protocol_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_read_timeout 300s;
# }
location @my_401 { location @my_401 {
default_type text/html; default_type text/html;
return 401 '<!doctypehtml><html lang=en><meta charset=UTF-8><meta content="width=device-width,initial-scale=1"name=viewport><title>401</title><style>body{display:flex;flex-direction:column;align-items:center;justify-content:center}</style><h1>401 Unauthorized</h1>'; return 401 '<!doctypehtml><html lang=en><meta charset=UTF-8><meta content="width=device-width,initial-scale=1"name=viewport><title>401</title><style>body{display:flex;flex-direction:column;align-items:center;justify-content:center}</style><h1>401 Unauthorized</h1>';

74
pkg/syscheck/cpu.go Normal file
View File

@@ -0,0 +1,74 @@
package syscheck
import (
"bufio"
"context"
"fmt"
"os"
"runtime"
"strconv"
"strings"
"golang.org/x/sys/cpu"
)
type CPUInfo struct {
Cores int64
FrequencyMHz int64
SupportAES bool
IsX86V2 bool
}
func GetCPUInfo(ctx context.Context) (CPUInfo, error) {
info := CPUInfo{
Cores: int64(runtime.NumCPU()),
}
// Parse /proc/cpuinfo to get CPU frequency and model info
file, err := os.Open("/proc/cpuinfo")
if err != nil {
return info, fmt.Errorf("failed to open /proc/cpuinfo: %w", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
// Parse CPU MHz
if strings.HasPrefix(line, "cpu MHz") {
parts := strings.Split(line, ":")
if len(parts) == 2 {
freqStr := strings.TrimSpace(parts[1])
if freq, err := strconv.ParseFloat(freqStr, 64); err == nil {
info.FrequencyMHz = int64(freq)
break // Get first CPU frequency
}
}
}
}
if err := scanner.Err(); err != nil {
return info, fmt.Errorf("failed to read /proc/cpuinfo: %w", err)
}
// Check CPU features using x/sys/cpu package
if runtime.GOARCH == "amd64" || runtime.GOARCH == "386" {
// Check AES-NI support
info.SupportAES = cpu.X86.HasAES
// Check x86-64-v2 support
// x86-64-v2 requires: SSE3, SSSE3, SSE4.1, SSE4.2, POPCNT
info.IsX86V2 = cpu.X86.HasSSE3 &&
cpu.X86.HasSSSE3 &&
cpu.X86.HasSSE41 &&
cpu.X86.HasSSE42 &&
cpu.X86.HasPOPCNT
} else {
// For ARM or other architectures
info.SupportAES = false
info.IsX86V2 = false
}
return info, nil
}

44
pkg/syscheck/cpu_test.go Normal file
View File

@@ -0,0 +1,44 @@
package syscheck
import (
"testing"
)
func TestGetCPUInfo(t *testing.T) {
info, err := GetCPUInfo(t.Context())
if err != nil {
t.Fatalf("Failed to get CPU info: %v", err)
}
// Validate CPU cores
if info.Cores <= 0 {
t.Errorf("expected CPU cores > 0, got %d", info.Cores)
}
// Validate CPU frequency (should be reasonable, e.g., 500MHz - 10000MHz)
if info.FrequencyMHz <= 0 {
t.Errorf("expected CPU frequency > 0, got %.2f", info.FrequencyMHz)
}
if info.FrequencyMHz < 500 || info.FrequencyMHz > 10000 {
t.Logf("Warning: CPU frequency seems unusual: %.2f MHz", info.FrequencyMHz)
}
// Log CPU information
t.Logf("CPU Cores: %d", info.Cores)
t.Logf("CPU Frequency: %.2f MHz (%.2f GHz)", info.FrequencyMHz, info.FrequencyMHz/1000)
t.Logf("AES-NI Support: %v", info.SupportAES)
t.Logf("x86-64-v2 Compatible: %v", info.IsX86V2)
// Log feature support
if info.SupportAES {
t.Log("✓ CPU supports AES-NI hardware acceleration")
} else {
t.Log("✗ CPU does not support AES-NI")
}
if info.IsX86V2 {
t.Log("✓ CPU is x86-64-v2 compatible (SSE3, SSSE3, SSE4.1, SSE4.2, POPCNT)")
} else {
t.Log("✗ CPU is not x86-64-v2 compatible")
}
}

157
pkg/syscheck/disk.go Normal file
View File

@@ -0,0 +1,157 @@
package syscheck
import (
"context"
"crypto/rand"
"fmt"
"io"
"os"
"strconv"
"time"
"golang.org/x/sys/unix"
)
// GetDiskSpace returns available disk space in bytes for the root partition
func GetDiskSpace(ctx context.Context) (int64, error) {
var stat unix.Statfs_t
if err := unix.Statfs("/", &stat); err != nil {
return 0, fmt.Errorf("failed to get disk space: %w", err)
}
// Available space = Available blocks * Block size
availableSpace := int64(stat.Bavail) * int64(stat.Bsize)
return availableSpace, nil
}
// GetDiskSpeed measures disk read/write speed by writing and reading a 1GB test file
// Returns read speed and write speed in bytes per second
func GetDiskSpeed(ctx context.Context) (int64, int64, error) {
const (
testSize = 1024 * 1024 * 1024 // 1GB
bufferSize = 1024 * 1024 // 1MB buffer
)
tmpFile := "/tmp/diskspeed_test_" + strconv.FormatInt(time.Now().UnixNano(), 10)
defer func() {
// Clean up test file
_ = os.Remove(tmpFile)
}()
// Test write speed
writeSpeed, err := measureWriteSpeed(ctx, tmpFile, testSize, bufferSize)
if err != nil {
return 0, 0, fmt.Errorf("failed to measure write speed: %w", err)
}
// Test read speed
readSpeed, err := measureReadSpeed(ctx, tmpFile, bufferSize)
if err != nil {
return 0, 0, fmt.Errorf("failed to measure read speed: %w", err)
}
return readSpeed, writeSpeed, nil
}
// measureWriteSpeed writes test data to a file and measures the speed
func measureWriteSpeed(ctx context.Context, filename string, totalSize, bufferSize int64) (int64, error) {
// Create file
file, err := os.Create(filename)
if err != nil {
return 0, fmt.Errorf("failed to create test file: %w", err)
}
defer file.Close()
// Prepare buffer with random data
buffer := make([]byte, bufferSize)
if _, err := rand.Read(buffer); err != nil {
return 0, fmt.Errorf("failed to generate random data: %w", err)
}
// Start timing
startTime := time.Now()
var written int64
// Write data in chunks
for written < totalSize {
// Check context cancellation
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
}
// Write buffer
n, err := file.Write(buffer)
if err != nil {
return 0, fmt.Errorf("failed to write data: %w", err)
}
written += int64(n)
}
// Sync to ensure data is written to disk
if err := file.Sync(); err != nil {
return 0, fmt.Errorf("failed to sync file: %w", err)
}
// Calculate speed
duration := time.Since(startTime)
speed := int64(float64(written) / duration.Seconds())
return speed, nil
}
// measureReadSpeed reads the test file and measures the speed
func measureReadSpeed(ctx context.Context, filename string, bufferSize int64) (int64, error) {
// Open file
file, err := os.Open(filename)
if err != nil {
return 0, fmt.Errorf("failed to open test file: %w", err)
}
defer file.Close()
// Get file size
fileInfo, err := file.Stat()
if err != nil {
return 0, fmt.Errorf("failed to stat file: %w", err)
}
totalSize := fileInfo.Size()
// Prepare buffer
buffer := make([]byte, bufferSize)
// Start timing
startTime := time.Now()
var totalRead int64
// Read data in chunks
for {
// Check context cancellation
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
}
// Read buffer
n, err := file.Read(buffer)
if err == io.EOF {
break
}
if err != nil {
return 0, fmt.Errorf("failed to read data: %w", err)
}
totalRead += int64(n)
}
// Calculate speed
duration := time.Since(startTime)
speed := int64(float64(totalRead) / duration.Seconds())
// Verify we read the entire file
if totalRead != totalSize {
return 0, fmt.Errorf("read size mismatch: expected %d, got %d", totalSize, totalRead)
}
return speed, nil
}

42
pkg/syscheck/disk_test.go Normal file
View File

@@ -0,0 +1,42 @@
package syscheck
import (
"context"
"testing"
)
func TestGetDiskSpace(t *testing.T) {
size, err := GetDiskSpace(context.Background())
if err != nil {
t.Fatalf("Failed to get disk space: %v", err)
}
if size <= 0 {
t.Errorf("expected disk space to be greater than 0, got %d", size)
}
t.Logf("Available disk space: %d bytes (%.2f GB)", size, float64(size)/(1024*1024*1024))
}
func TestGetDiskSpeed(t *testing.T) {
// Test with real disk I/O (warning: this writes 1GB to disk)
// Skip in short mode
if testing.Short() {
t.Skip("Skipping disk speed test in short mode")
}
rs, ws, err := GetDiskSpeed(context.Background())
if err != nil {
t.Fatalf("Failed to get disk speed: %v", err)
}
if rs <= 0 {
t.Errorf("expected read speed > 0, got %d", rs)
}
if ws <= 0 {
t.Errorf("expected write speed > 0, got %d", ws)
}
t.Logf("Read speed: %d bytes/s (%.2f MB/s)", rs, float64(rs)/(1024*1024))
t.Logf("Write speed: %d bytes/s (%.2f MB/s)", ws, float64(ws)/(1024*1024))
}

144
pkg/syscheck/mem.go Normal file
View File

@@ -0,0 +1,144 @@
package syscheck
import (
"context"
"crypto/rand"
"fmt"
"time"
"golang.org/x/sys/unix"
)
// GetMemorySpace returns total physical memory (RAM) in bytes, excluding swap
func GetMemorySpace(ctx context.Context) (int64, error) {
var info unix.Sysinfo_t
if err := unix.Sysinfo(&info); err != nil {
return 0, fmt.Errorf("failed to get memory info: %w", err)
}
// Total physical RAM (excluding swap)
// info.Totalram is in memory unit size (info.Unit)
totalMemory := int64(info.Totalram) * int64(info.Unit)
return totalMemory, nil
}
// GetMemorySpeed measures memory read/write speed by allocating and accessing memory
// Returns read speed and write speed in bytes per second
func GetMemorySpeed(ctx context.Context) (int64, int64, error) {
const (
testSize = 512 * 1024 * 1024 // 512MB test size
iterations = 5 // Number of iterations for averaging
)
// Test write speed
writeSpeed, err := measureMemoryWriteSpeed(ctx, testSize, iterations)
if err != nil {
return 0, 0, fmt.Errorf("failed to measure memory write speed: %w", err)
}
// Test read speed
readSpeed, err := measureMemoryReadSpeed(ctx, testSize, iterations)
if err != nil {
return 0, 0, fmt.Errorf("failed to measure memory read speed: %w", err)
}
return readSpeed, writeSpeed, nil
}
// measureMemoryWriteSpeed measures memory write speed
func measureMemoryWriteSpeed(ctx context.Context, size int64, iterations int) (int64, error) {
var totalDuration time.Duration
for i := 0; i < iterations; i++ {
// Check context cancellation
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
}
// Allocate memory buffer
buffer := make([]byte, size)
// Generate random data
source := make([]byte, 1024*1024) // 1MB source buffer
if _, err := rand.Read(source); err != nil {
return 0, fmt.Errorf("failed to generate random data: %w", err)
}
// Start timing
startTime := time.Now()
// Write data to memory buffer
for offset := int64(0); offset < size; offset += int64(len(source)) {
remaining := size - offset
if remaining < int64(len(source)) {
copy(buffer[offset:], source[:remaining])
} else {
copy(buffer[offset:offset+int64(len(source))], source)
}
}
// Stop timing
duration := time.Since(startTime)
totalDuration += duration
// Force the buffer to be used to prevent optimization
_ = buffer[0]
}
// Calculate average speed
avgDuration := totalDuration / time.Duration(iterations)
speed := int64(float64(size) / avgDuration.Seconds())
return speed, nil
}
// measureMemoryReadSpeed measures memory read speed
func measureMemoryReadSpeed(ctx context.Context, size int64, iterations int) (int64, error) {
var totalDuration time.Duration
// Pre-allocate and fill buffer
buffer := make([]byte, size)
if _, err := rand.Read(buffer); err != nil {
return 0, fmt.Errorf("failed to initialize buffer: %w", err)
}
for i := 0; i < iterations; i++ {
// Check context cancellation
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
}
// Start timing
startTime := time.Now()
// Read data from memory buffer
var sum int64
for offset := int64(0); offset < size; offset += 1024 {
// Read in chunks to simulate real access patterns
end := offset + 1024
if end > size {
end = size
}
for j := offset; j < end; j++ {
sum += int64(buffer[j])
}
}
// Stop timing
duration := time.Since(startTime)
totalDuration += duration
// Use sum to prevent optimization
_ = sum
}
// Calculate average speed
avgDuration := totalDuration / time.Duration(iterations)
speed := int64(float64(size) / avgDuration.Seconds())
return speed, nil
}

42
pkg/syscheck/mem_test.go Normal file
View File

@@ -0,0 +1,42 @@
package syscheck
import (
"context"
"testing"
)
func TestGetMemorySpace(t *testing.T) {
size, err := GetMemorySpace(context.Background())
if err != nil {
t.Fatalf("Failed to get memory space: %v", err)
}
if size <= 0 {
t.Errorf("expected memory space to be greater than 0, got %d", size)
}
t.Logf("Total physical memory: %d bytes (%.2f GB)", size, float64(size)/(1024*1024*1024))
}
func TestGetMemorySpeed(t *testing.T) {
// Test memory speed (may take a few seconds)
// Skip in short mode
if testing.Short() {
t.Skip("Skipping memory speed test in short mode")
}
rs, ws, err := GetMemorySpeed(context.Background())
if err != nil {
t.Fatalf("Failed to get memory speed: %v", err)
}
if rs <= 0 {
t.Errorf("expected read speed > 0, got %d", rs)
}
if ws <= 0 {
t.Errorf("expected write speed > 0, got %d", ws)
}
t.Logf("Memory read speed: %d bytes/s (%.2f GB/s)", rs, float64(rs)/(1024*1024*1024))
t.Logf("Memory write speed: %d bytes/s (%.2f GB/s)", ws, float64(ws)/(1024*1024*1024))
}

View File

@@ -1,318 +0,0 @@
package syscheck
import (
"context"
"fmt"
"strconv"
"strings"
)
// CheckResult represents the result of a system check
type CheckResult struct {
Name string
Passed bool
Actual string
Expected string
Message string
}
// DiskInfo represents disk information
type DiskInfo struct {
AvailableGB float64
WriteSpeed float64 // MB/s
ReadSpeed float64 // MB/s
}
// MemInfo represents memory information
type MemInfo struct {
TotalGB float64
}
// CPUInfo represents CPU information
type CPUInfo struct {
Cores int
FrequencyMHz float64
}
// CommandExecutor defines interface for executing commands
type CommandExecutor interface {
ExecuteCommand(ctx context.Context, cmds ...string) (string, error)
}
// CheckDiskSpace checks if disk space meets minimum requirements
func CheckDiskSpace(ctx context.Context, executor CommandExecutor, minGB float64) (*CheckResult, error) {
// Use df to check available disk space on root partition
output, err := executor.ExecuteCommand(ctx, "df", "-BG", "/")
if err != nil {
return nil, fmt.Errorf("failed to check disk space: %w", err)
}
lines := strings.Split(strings.TrimSpace(output), "\n")
if len(lines) < 2 {
return nil, fmt.Errorf("unexpected df output format")
}
fields := strings.Fields(lines[1])
if len(fields) < 4 {
return nil, fmt.Errorf("unexpected df fields count")
}
// Parse available space (4th field, format: "500G")
availableStr := strings.TrimSuffix(fields[3], "G")
available, err := strconv.ParseFloat(availableStr, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse available disk space: %w", err)
}
result := &CheckResult{
Name: "Disk Space",
Passed: available >= minGB,
Actual: fmt.Sprintf("%.2f GB", available),
Expected: fmt.Sprintf(">= %.2f GB", minGB),
}
if !result.Passed {
result.Message = fmt.Sprintf("Insufficient disk space: %.2f GB available, %.2f GB required", available, minGB)
}
return result, nil
}
// CheckDiskPerformance checks disk read/write performance
func CheckDiskPerformance(ctx context.Context, executor CommandExecutor, minWriteMBps, minReadMBps float64) (*CheckResult, error) {
// Use dd to test write performance
writeCmd := "dd if=/dev/zero of=/tmp/test_write bs=1M count=1024 oflag=direct 2>&1 | tail -1"
writeOutput, err := executor.ExecuteCommand(ctx, "bash", "-c", writeCmd)
if err != nil {
return nil, fmt.Errorf("failed to check disk write performance: %w", err)
}
// Parse write speed from dd output (format: "... copied, X.XX s, XXX MB/s")
writeSpeed, err := parseDDSpeed(writeOutput)
if err != nil {
return nil, fmt.Errorf("failed to parse write speed: %w, output: %s", err, writeOutput)
}
// Test read performance and clean up test file
readCmd := "dd if=/tmp/test_write of=/dev/null bs=1M count=1024 iflag=direct 2>&1 | tail -1; rm -f /tmp/test_write"
readOutput, err := executor.ExecuteCommand(ctx, "bash", "-c", readCmd)
if err != nil {
return nil, fmt.Errorf("failed to check disk read performance: %w", err)
}
// Parse read speed from dd output
readSpeed, err := parseDDSpeed(readOutput)
if err != nil {
return nil, fmt.Errorf("failed to parse read speed: %w, output: %s", err, readOutput)
}
passed := writeSpeed >= minWriteMBps && readSpeed >= minReadMBps
result := &CheckResult{
Name: "Disk Performance",
Passed: passed,
Actual: fmt.Sprintf("Write: %.2f MB/s, Read: %.2f MB/s", writeSpeed, readSpeed),
Expected: fmt.Sprintf("Write: >= %.2f MB/s, Read: >= %.2f MB/s", minWriteMBps, minReadMBps),
}
if !passed {
result.Message = fmt.Sprintf("Insufficient disk performance")
}
return result, nil
}
// parseDDSpeed parses the speed from dd command output
// Expected format: "104857600 bytes (105 MB, 100 MiB) copied, 0.125749 s, 834 MB/s"
func parseDDSpeed(output string) (float64, error) {
output = strings.TrimSpace(output)
if output == "" {
return 0, fmt.Errorf("empty output")
}
// Find the last occurrence of "MB/s" or "GB/s"
var speed float64
var unit string
// Try to match "XXX MB/s" or "XXX GB/s" pattern
if idx := strings.LastIndex(output, " MB/s"); idx != -1 {
// Extract the number before " MB/s"
fields := strings.Fields(output[:idx])
if len(fields) == 0 {
return 0, fmt.Errorf("no speed value found")
}
speedStr := fields[len(fields)-1]
var err error
speed, err = strconv.ParseFloat(speedStr, 64)
if err != nil {
return 0, fmt.Errorf("failed to parse speed value '%s': %w", speedStr, err)
}
unit = "MB/s"
} else if idx := strings.LastIndex(output, " GB/s"); idx != -1 {
// Extract the number before " GB/s"
fields := strings.Fields(output[:idx])
if len(fields) == 0 {
return 0, fmt.Errorf("no speed value found")
}
speedStr := fields[len(fields)-1]
var err error
speed, err = strconv.ParseFloat(speedStr, 64)
if err != nil {
return 0, fmt.Errorf("failed to parse speed value '%s': %w", speedStr, err)
}
unit = "GB/s"
speed *= 1024 // Convert GB/s to MB/s
} else {
return 0, fmt.Errorf("no MB/s or GB/s found in output")
}
if unit == "MB/s" || unit == "GB/s" {
return speed, nil
}
return 0, fmt.Errorf("unexpected unit: %s", unit)
}
// CheckMemory checks if system memory meets minimum requirements
func CheckMemory(ctx context.Context, executor CommandExecutor, minGB float64) (*CheckResult, error) {
// Use free -m to check memory in MB for better precision
output, err := executor.ExecuteCommand(ctx, "free", "-m")
if err != nil {
return nil, fmt.Errorf("failed to check memory: %w", err)
}
lines := strings.Split(strings.TrimSpace(output), "\n")
if len(lines) < 2 {
return nil, fmt.Errorf("unexpected free output format")
}
fields := strings.Fields(lines[1])
if len(fields) < 2 {
return nil, fmt.Errorf("unexpected free fields count")
}
// Parse total memory in MB
totalMB, err := strconv.ParseFloat(fields[1], 64)
if err != nil {
return nil, fmt.Errorf("failed to parse memory size: %w", err)
}
// Convert MB to GB (1 GB = 1024 MB)
totalGB := totalMB / 1024.0
result := &CheckResult{
Name: "Memory Size",
Passed: totalGB >= minGB,
Actual: fmt.Sprintf("%.2f GB", totalGB),
Expected: fmt.Sprintf(">= %.2f GB", minGB),
}
if !result.Passed {
result.Message = fmt.Sprintf("Insufficient memory: %.2f GB available, %.2f GB required", totalGB, minGB)
}
return result, nil
}
// CheckCPUCores checks if CPU core count meets minimum requirements
func CheckCPUCores(ctx context.Context, executor CommandExecutor, minCores int) (*CheckResult, error) {
// Read /proc/cpuinfo to get CPU core count (more universal than nproc)
output, err := executor.ExecuteCommand(ctx, "cat", "/proc/cpuinfo")
if err != nil {
return nil, fmt.Errorf("failed to check CPU cores: %w", err)
}
// Count the number of "processor" lines
cores := 0
lines := strings.Split(output, "\n")
for _, line := range lines {
if strings.HasPrefix(strings.TrimSpace(line), "processor") {
cores++
}
}
if cores == 0 {
return nil, fmt.Errorf("failed to parse CPU cores from /proc/cpuinfo")
}
result := &CheckResult{
Name: "CPU Cores",
Passed: cores >= minCores,
Actual: fmt.Sprintf("%d cores", cores),
Expected: fmt.Sprintf(">= %d cores", minCores),
}
if !result.Passed {
result.Message = fmt.Sprintf("Insufficient CPU cores: %d cores available, %d cores required", cores, minCores)
}
return result, nil
}
// CheckCPUFrequency checks if CPU frequency meets minimum requirements
func CheckCPUFrequency(ctx context.Context, executor CommandExecutor, minGHz float64) (*CheckResult, error) {
// Read /proc/cpuinfo to get CPU frequency (more universal than lscpu)
output, err := executor.ExecuteCommand(ctx, "cat", "/proc/cpuinfo")
if err != nil {
return nil, fmt.Errorf("failed to check CPU frequency: %w", err)
}
var maxFreqMHz float64
lines := strings.Split(output, "\n")
// Try to parse from "cpu MHz" field (runtime frequency)
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "cpu MHz") {
fields := strings.Split(line, ":")
if len(fields) >= 2 {
freqStr := strings.TrimSpace(fields[1])
freq, err := strconv.ParseFloat(freqStr, 64)
if err == nil && freq > maxFreqMHz {
maxFreqMHz = freq
}
}
}
}
// If not found, try to parse from "model name" field (base frequency)
if maxFreqMHz == 0 {
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "model name") {
// Look for pattern like "@ 2.60GHz"
if idx := strings.Index(line, "@"); idx != -1 {
freqPart := line[idx+1:]
// Extract GHz value
if ghzIdx := strings.Index(freqPart, "GHz"); ghzIdx != -1 {
freqStr := strings.TrimSpace(freqPart[:ghzIdx])
freqGHz, err := strconv.ParseFloat(freqStr, 64)
if err == nil {
maxFreqMHz = freqGHz * 1000.0
break
}
}
}
}
}
}
if maxFreqMHz == 0 {
return nil, fmt.Errorf("failed to parse CPU frequency from /proc/cpuinfo")
}
freqGHz := maxFreqMHz / 1000.0
minMHz := minGHz * 1000.0
result := &CheckResult{
Name: "CPU Frequency",
Passed: maxFreqMHz >= minMHz,
Actual: fmt.Sprintf("%.2f GHz", freqGHz),
Expected: fmt.Sprintf(">= %.2f GHz", minGHz),
}
if !result.Passed {
result.Message = fmt.Sprintf("Insufficient CPU frequency: %.2f GHz available, %.2f GHz required", freqGHz, minGHz)
}
return result, nil
}

50
pkg/tool/human/size.go Normal file
View File

@@ -0,0 +1,50 @@
package human
import "fmt"
func Duration(nano int64) string {
duration := float64(nano)
unit := "ns"
if duration >= 1000 {
duration /= 1000
unit = "us"
}
if duration >= 1000 {
duration /= 1000
unit = "ms"
}
if duration >= 1000 {
duration /= 1000
unit = " s"
}
return fmt.Sprintf("%6.2f%s", duration, unit)
}
func Size(size int64) string {
const (
_ = iota
KB = 1 << (10 * iota) // 1 KB = 1024 bytes
MB // 1 MB = 1024 KB
GB // 1 GB = 1024 MB
TB // 1 TB = 1024 GB
PB // 1 PB = 1024 TB
)
switch {
case size >= PB:
return fmt.Sprintf("%.2f PB", float64(size)/PB)
case size >= TB:
return fmt.Sprintf("%.2f TB", float64(size)/TB)
case size >= GB:
return fmt.Sprintf("%.2f GB", float64(size)/GB)
case size >= MB:
return fmt.Sprintf("%.2f MB", float64(size)/MB)
case size >= KB:
return fmt.Sprintf("%.2f KB", float64(size)/KB)
default:
return fmt.Sprintf("%d bytes", size)
}
}