Compare commits

..

5 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
zhaoyupeng
aafe60ee35 chore: 完善个模块打包 2026-01-07 18:55:22 +08:00
74 changed files with 2271 additions and 1052 deletions

6
.gitignore vendored
View File

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

View File

@@ -4,7 +4,8 @@
## 环境要求
- Go `1.25.2`
- Linux 环境,能够访问外部下载源(如 `artifactory.yizhisec.com``docker.io`
- Linux 环境,能够访问外部下载源(如 `artifactory.yizhisec.com``docker.io`
- 基础组件: Docker, 7za 等
## 快速开始
```bash
@@ -23,40 +24,4 @@ go build ./...
默认构建目录为 `/root/hsv2-installation`,可通过 `--dir` 修改。
## 常用命令
- `forge`:根命令,支持全局调试开关 `--debug`(参考 `internal/cmd/root.go:12`)。
- `forge make`:构建依赖与资源(参考 `internal/cmd/make.go:13`)。
- 全局选项:
- `--dir`:输出基础目录(默认 `/root/hsv2-installation`,参考 `internal/cmd/make.go:40`
- `--disable-dependency-check`:禁用依赖检查(参考 `internal/cmd/make.go:38`
- 子命令(节选):
- `images`:准备镜像相关资源
- `binaries`:下载并解压 K8s 相关二进制
- `debs`:下载并解压 Debian 包Docker
- `flannel`:生成 Flannel YAML参考 `internal/cmd/make.flannel.go`
- `longhorn`:下载 Longhorn chart 并生成 `values.yaml`
- `mysql`:下载 MySQL operator 并生成 `cluster.yaml`,支持 `--replica-count``--storage-size`
- `redis`:下载 Redis chart 并生成 `values.yaml`,支持 `--replica-count``--password`
- `emqx`:生成 EMQX 配置
- `es`:准备 Elasticsearch/Kibana 相关资源
- `yosguard`:生成 YOSGuard 配置与服务文件
- `less-dns`:生成 DNS 相关资源
- `hs-net`:占位命令,当前仅创建目录(参考 `internal/cmd/make.hsnet.go:23`
- `forge install`:安装占位命令(参考 `internal/cmd/install.go`)。
## 目录结构(简要)
- `internal/cmd`:命令行入口与各 `make` 子命令实现
- `internal/controller/maker`:构建流程控制
- `pkg/resource`:内置 YAML/SQL/bash 等资源模板
- `pkg/downloader``pkg/archiver`:下载与解压工具
## 开发与维护
- 构建:`go build ./...`
- 测试:`go test ./...`
- 代码检查:`go vet ./...`
- 格式化:`go fmt ./...`
- 依赖清理:`go mod tidy`
## 注意事项
- 部分子命令会从公司内网仓库下载资源,请确保网络可达。
- 如果仅试运行流程,可使用 `--disable-dependency-check` 快速跳过依赖检查。
## 基础镜像准备

7
debug/manual.md Normal file
View File

@@ -0,0 +1,7 @@
# 手动打包
## 打包某个(或多个) app 的 pkg
- 1. `go run . make app xx`
- 2. `cd /root/hsv2-installation/app`
- 3. `7z a apps.pkg -pRrPt7Uo9WM1dkXOJmHps56T8BZY2qA4g *`

3
debug/mysql.md Normal file
View File

@@ -0,0 +1,3 @@
- 1. after reboot, mysql-master can't be ready
* exec into mysql-master
* `update sys_operator.status set value = 1 where name="configured";`

5
go.mod
View File

@@ -3,14 +3,13 @@ module yizhisec.com/hsv2/forge
go 1.25.2
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/spf13/cobra v1.10.1
)
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/mattn/go-colorable v0.1.13 // 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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=

View File

@@ -1,6 +1,8 @@
package installcmd
import (
"os"
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/installer"
)
@@ -8,7 +10,6 @@ import (
func Check() *cobra.Command {
var (
workdir string
target string
ignoreDisk bool
ignoreMemory bool
ignoreCPU bool
@@ -18,9 +19,20 @@ func Check() *cobra.Command {
Use: "check",
Short: "Check system requirements",
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 {
_installer := installer.NewInstaller(workdir, target)
return _installer.Check(
_installer := installer.NewInstaller(workdir)
return _installer.HardwareCheck(
cmd.Context(),
installer.WithIgnoreDiskCheck(ignoreDisk),
installer.WithIgnoreMemoryCheck(ignoreMemory),
@@ -30,10 +42,6 @@ func Check() *cobra.Command {
}
_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
}

View File

@@ -9,21 +9,23 @@ func K0s() *cobra.Command {
var (
workdir string
target string
)
_cmd := &cobra.Command{
Use: "k0s",
Short: "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 {
_installer := installer.NewInstaller(workdir, target)
_installer := installer.NewInstaller(workdir)
return _installer.K0s(cmd.Context())
},
}
_cmd.PersistentFlags().StringVar(&workdir, "workdir", "/root/hs-installation", "working directory")
_cmd.PersistentFlags().StringVar(&target, "target", "self", "target directory")
return _cmd
}

View File

@@ -9,22 +9,24 @@ func Prepare() *cobra.Command {
var (
workdir string
target string
)
_cmd := &cobra.Command{
Use: "prepare",
Short: "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 {
_installer := installer.NewInstaller(workdir, target)
_installer := installer.NewInstaller(workdir)
_installer.Prepare(cmd.Context())
return nil
},
}
_cmd.Flags().StringVar(&workdir, "workdir", "/root/hs-installation", "Working directory")
_cmd.Flags().StringVar(&target, "target", "self", "Target")
return _cmd
}

View File

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

View File

@@ -1,6 +1,9 @@
package makecmd
import (
"fmt"
"time"
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
@@ -9,6 +12,9 @@ import (
func ALL() *cobra.Command {
var (
_workdir string
_vendor string
_version string
_replica int
)
_cmd := &cobra.Command{
@@ -20,10 +26,6 @@ func ALL() *cobra.Command {
mk = maker.NewMaker(_workdir)
)
if err = mk.Images(cmd.Context()); err != nil {
return err
}
if err = mk.K0s(cmd.Context()); err != nil {
return err
}
@@ -92,31 +94,35 @@ func ALL() *cobra.Command {
return err
}
if err = mk.AppOEM(cmd.Context(), "standard", 2); err != nil {
if err = mk.AppOEM(cmd.Context(), _version, _vendor, _replica); err != nil {
return err
}
if err = mk.AppUser(cmd.Context(), 2); err != nil {
if err = mk.AppUser(cmd.Context(), _version, _replica); err != nil {
return err
}
if err = mk.AppClient(cmd.Context(), 2); err != nil {
if err = mk.AppClient(cmd.Context(), _version, _replica); err != nil {
return err
}
if err = mk.AppGateway(cmd.Context(), 2); err != nil {
if err = mk.AppGateway(cmd.Context(), _version, _replica); err != nil {
return err
}
if err = mk.AppFront(cmd.Context(), "standard", 2); err != nil {
if err = mk.AppFront(cmd.Context(), _version, _vendor, _replica); err != nil {
return err
}
if err = mk.AppMie(cmd.Context(), 2); err != nil {
if err = mk.AppMie(cmd.Context(), _version, _replica); err != nil {
return err
}
if err = mk.AppNginx(cmd.Context()); err != nil {
if err = mk.AppNginx(cmd.Context(), _version, _vendor, _replica, true); err != nil {
return err
}
if err = mk.AppHelper(cmd.Context(), _version, _vendor, _replica); err != nil {
return err
}
@@ -124,7 +130,10 @@ func ALL() *cobra.Command {
},
}
_cmd.PersistentFlags().StringVar(&_workdir, "workdir", opt.DefaultWorkdir, "Work directory")
_cmd.Flags().StringVar(&_workdir, "workdir", opt.DefaultWorkdir, "Work directory")
_cmd.Flags().StringVar(&_vendor, "vendor", "standard", "Vendor name")
_cmd.Flags().StringVar(&_version, "version", fmt.Sprintf("v2.2.0-%d", time.Now().Unix()), "Version, default: v2.2.0-<timestamp>")
_cmd.Flags().IntVar(&_replica, "replica", 2, "Replica count")
return _cmd
}

View File

@@ -1,9 +1,14 @@
package makecmd
import (
"fmt"
"path/filepath"
"time"
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/model"
)
func App() *cobra.Command {
@@ -20,14 +25,96 @@ func App() *cobra.Command {
appOEM(),
appFront(),
appNginx(),
appHelper(),
)
return _cmd
}
func Apps() *cobra.Command {
var (
_pkg bool
_replica int
_vendor string
_version string
)
_cmd := &cobra.Command{
Use: "apps",
Short: "Make Apps(user, client, gateway, mie, nginx, etc...)",
PreRunE: func(cmd *cobra.Command, args []string) error {
if model.GetVendor(_vendor) == nil {
return fmt.Errorf("invalid vendor: %s", _vendor)
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
var (
err error
mk = maker.NewMaker(opt.Cfg.Make.Dir)
)
if err = mk.AppUser(cmd.Context(), _version, _replica); err != nil {
return err
}
if err = mk.AppClient(cmd.Context(), _version, _replica); err != nil {
return err
}
if err = mk.AppGateway(cmd.Context(), _version, _replica); err != nil {
return err
}
if err = mk.AppMie(cmd.Context(), _version, _replica); err != nil {
return err
}
if err = mk.AppFront(cmd.Context(), _version, _vendor, _replica); err != nil {
return err
}
if err = mk.AppNginx(cmd.Context(), _version, _vendor, _replica, false); err != nil {
return err
}
if err = mk.AppOEM(cmd.Context(), _version, _vendor, _replica); err != nil {
return err
}
if err = mk.AppHelper(cmd.Context(), _version, _vendor, _replica); err != nil {
return err
}
if _pkg {
var (
dir = filepath.Join(opt.Cfg.Make.Dir, "app")
_cmd = "7za a -pRrPt7Uo9WM1dkXOJmHps56T8BZY2qA4g -mhe=on apps.pkg *"
)
if err = mk.RunCommand(cmd.Context(), dir, _cmd); err != nil {
return err
}
}
return nil
},
}
_cmd.Flags().BoolVar(&_pkg, "pkg", false, "Make upgrade pkg")
_cmd.Flags().IntVar(&_replica, "replica-count", 2, "Replica count")
_cmd.Flags().StringVar(&_vendor, "vendor", "standard", "Vendor name")
_cmd.Flags().StringVar(&_version, "version", fmt.Sprintf("v2.2.0-%d", time.Now().Unix()), "Apps version, default: v2.2.0-<timestamp>")
return _cmd
}
func appUser() *cobra.Command {
var (
replica int
replica int
_version string
)
_cmd := &cobra.Command{
@@ -35,18 +122,20 @@ func appUser() *cobra.Command {
Short: "Make User App",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.AppUser(cmd.Context(), replica)
return mk.AppUser(cmd.Context(), _version, replica)
},
}
_cmd.Flags().IntVar(&replica, "replica-count", 2, "Replica count")
_cmd.Flags().StringVar(&_version, "version", fmt.Sprintf("v2.2.0-%d", time.Now().Unix()), "App user version, default: v2.2.0-<timestamp>")
return _cmd
}
func appClient() *cobra.Command {
var (
replica int
replica int
_version string
)
_cmd := &cobra.Command{
@@ -54,18 +143,20 @@ func appClient() *cobra.Command {
Short: "Make Client App",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.AppClient(cmd.Context(), replica)
return mk.AppClient(cmd.Context(), _version, replica)
},
}
_cmd.Flags().IntVar(&replica, "replica-count", 2, "Replica count")
_cmd.Flags().StringVar(&_version, "version", fmt.Sprintf("v2.2.0-%d", time.Now().Unix()), "App client version, default: v2.2.0-<timestamp>")
return _cmd
}
func appGateway() *cobra.Command {
var (
replica int
replica int
_version string
)
_cmd := &cobra.Command{
@@ -73,18 +164,20 @@ func appGateway() *cobra.Command {
Short: "Make Gateway App",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.AppGateway(cmd.Context(), replica)
return mk.AppGateway(cmd.Context(), _version, replica)
},
}
_cmd.Flags().IntVar(&replica, "replica-count", 2, "Replica count")
_cmd.Flags().StringVar(&_version, "version", fmt.Sprintf("v2.2.0-%d", time.Now().Unix()), "App gateway version, default: v2.2.0-<timestamp>")
return _cmd
}
func appMie() *cobra.Command {
var (
replica int
replica int
_version string
)
_cmd := &cobra.Command{
@@ -92,19 +185,21 @@ func appMie() *cobra.Command {
Short: "Make Mie App",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.AppMie(cmd.Context(), replica)
return mk.AppMie(cmd.Context(), _version, replica)
},
}
_cmd.Flags().IntVar(&replica, "replica-count", 2, "Replica count")
_cmd.Flags().StringVar(&_version, "version", fmt.Sprintf("v2.2.0-%d", time.Now().Unix()), "App mie version, default: v2.2.0-<timestamp>")
return _cmd
}
func appOEM() *cobra.Command {
var (
replica int
vendor string
replica int
vendor string
_version string
)
_cmd := &cobra.Command{
@@ -112,20 +207,22 @@ func appOEM() *cobra.Command {
Short: "Make OEM App",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.AppOEM(cmd.Context(), vendor, replica)
return mk.AppOEM(cmd.Context(), _version, vendor, replica)
},
}
_cmd.Flags().IntVar(&replica, "replica-count", 2, "Replica count")
_cmd.Flags().StringVar(&vendor, "vendor", "standard", "Vendor name")
_cmd.Flags().StringVar(&_version, "version", fmt.Sprintf("v2.2.0-%d", time.Now().Unix()), "App oem version, default: v2.2.0-<timestamp>")
return _cmd
}
func appFront() *cobra.Command {
var (
replica int
vendor string
replica int
vendor string
_version string
)
_cmd := &cobra.Command{
@@ -133,12 +230,13 @@ func appFront() *cobra.Command {
Short: "Make Front App",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.AppFront(cmd.Context(), vendor, replica)
return mk.AppFront(cmd.Context(), _version, vendor, replica)
},
}
_cmd.Flags().IntVar(&replica, "replica-count", 2, "Replica count")
_cmd.Flags().StringVar(&vendor, "vendor", "standard", "Vendor name")
_cmd.Flags().StringVar(&_version, "version", fmt.Sprintf("v2.2.0-%d", time.Now().Unix()), "App front version, default: v2.2.0-<timestamp>")
return _cmd
}
@@ -146,26 +244,47 @@ func appFront() *cobra.Command {
func appNginx() *cobra.Command {
var (
replica int
disableSeafile bool
vendor string
_version string
_include_image bool
)
_cmd := &cobra.Command{
Use: "nginx",
Short: "Make Nginx App",
RunE: func(cmd *cobra.Command, args []string) error {
opts := []maker.NginxOpt{
maker.WithNginxReplica(replica),
}
if disableSeafile {
opts = append(opts, maker.WithoutNginxSeafile())
}
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.AppNginx(cmd.Context(), opts...)
return mk.AppNginx(cmd.Context(), _version, vendor, replica, _include_image)
},
}
_cmd.Flags().IntVar(&replica, "replica-count", 2, "Replica count")
_cmd.Flags().BoolVar(&disableSeafile, "disable-seafile", false, "Disable seafile")
_cmd.Flags().StringVar(&vendor, "vendor", "standard", "Vendor name")
_cmd.Flags().StringVar(&_version, "version", fmt.Sprintf("v2.2.0-%d", time.Now().Unix()), "App nginx version, default: v2.2.0-<timestamp>")
_cmd.Flags().BoolVar(&_include_image, "include-image", false, "Include image")
return _cmd
}
func appHelper() *cobra.Command {
var (
_replica int
_vendor string
_version string
)
_cmd := &cobra.Command{
Use: "helper",
Short: "Make Helper App",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.AppHelper(cmd.Context(), _version, _vendor, _replica)
},
}
_cmd.Flags().IntVar(&_replica, "replica-count", 2, "Replica count")
_cmd.Flags().StringVar(&_vendor, "vendor", "standard", "Vendor name")
_cmd.Flags().StringVar(&_version, "version", fmt.Sprintf("v2.2.0-%d", time.Now().Unix()), "App helper version, default: v2.2.0-<timestamp>")
return _cmd
}

View File

@@ -5,11 +5,14 @@ import (
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/logger"
tc "yizhisec.com/hsv2/forge/pkg/tool/client"
)
@@ -23,21 +26,26 @@ func Client() *cobra.Command {
clientMac(),
clientWin(),
clientLinux(),
clientBaseImage(),
)
return _cmd
}
func clientMac() *cobra.Command {
func clientWin() *cobra.Command {
var (
_version string
_pkg bool
)
_cmd := &cobra.Command{
Use: "mac",
Aliases: []string{"macos"},
Short: "make client-mac pkg",
Use: "win",
Short: "make client-win pkg",
RunE: func(cmd *cobra.Command, args []string) error {
const (
DMG_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/hs_appleclient-csgElink/release/2.1.0-std/hybridscope-client-mac.dmg"
PKG_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/hs_appleclient-csgElink/release/2.1.0-std/hybridscope-client-mac.pkg"
VERSION_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/hs_appleclient/release/2.1.0-std/mac_version.json"
ZIP_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/hs_client-csgElink/release/2.1.0-std/hs_client_csgElink.zip"
VERSION_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/hs_client-csgElink/release/2.1.0-std/windows_version.json"
RC_JSON_URL = "https://artifactory.yizhisec.com:443/artifactory/filestore/hsv2/win/win-rc.json"
)
var (
@@ -49,39 +57,9 @@ func clientMac() *cobra.Command {
return err
}
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.ClientPKG(
cmd.Context(),
"mac",
version,
"/api/v2_2/_client/mac",
maker.WithClientPKGDownload(DMG_URL, "hybridscope-client-mac.dmg"),
maker.WithClientPKGDownload(PKG_URL, "hybridscope-client-mac.pkg"),
maker.WithClientPKGDownload(VERSION_URL, "version.json"),
)
},
}
return _cmd
}
func clientWin() *cobra.Command {
_cmd := &cobra.Command{
Use: "win",
Short: "make client-win pkg",
RunE: func(cmd *cobra.Command, args []string) error {
const (
ZIP_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/hs_client-yizhianquan/release/2.1.0-std/hs_client.zip"
VERSION_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/hs_client-yizhianquan/release/2.1.0-std/windows_version.json"
)
var (
err error
version string
)
if version, err = clientVersion(VERSION_URL); err != nil {
return err
if _version != "" {
logger.InfoCtx(cmd.Context(), "clientWin: using manual version: %s => %s", version, _version)
version = _version
}
mk := maker.NewMaker(opt.Cfg.Make.Dir)
@@ -90,30 +68,51 @@ func clientWin() *cobra.Command {
"win",
version,
"/api/v2_2/_client/win",
maker.WithClientPkgMakePkg(_pkg),
maker.WithClientPKGDownload(ZIP_URL, "hs_client.zip"),
maker.WithClientPKGDownload(VERSION_URL, "version.json"),
maker.WithClientPKGCMD("unzip /data/hs_client.zip"),
maker.WithClientPKGCMD("mv resources/app.7z /data/app.7z"),
maker.WithClientPKGCMD("mv resources/hybridscope_offline_installer.exe /data/hybridscope_offline_installer.exe"),
maker.WithClientPKGCMD("mv resources/hybridscope-client-setup-zh.exe /data/hybridscope-client-setup-zh.exe"),
maker.WithClientPKGCMD("mv resources/hybridscope-client-setup-en.exe /data/hybridscope-client-setup-en.exe"),
maker.WithClientPKGCMD("rm -rf /data/hs_client.zip"),
maker.WithClientPKGCMD("rm -rf resources"),
maker.WithClientPKGDownload(RC_JSON_URL, "rc.json"),
maker.WithClientPKGCMDs("unzip /data/hs_client.zip"),
maker.WithClientPKGCMDs("mv resources/app.7z /data/app.7z"),
maker.WithClientPKGCMDs("7z x /data/app.7z -o/tmp/app"),
maker.WithClientPKGCMDs("rm -rf /data/app.7z"),
maker.WithClientPKGCMDs("cp /data/version.json /tmp/app/x64/config/windows_version.json"),
maker.WithClientPKGCMDs("cp resources/hybridscope-client-setup-zh.exe /tmp/app/x64/hybridscope-client-setup.exe"),
maker.WithClientPKGCMDs("7z a /data/app.7z /tmp/app/x64"),
maker.WithClientPKGCMDs("rm -rf /tmp/app"),
maker.WithClientPKGCMDs("md5sum /data/app.7z | awk '{print $1}' > /data/app.7z.md5"),
maker.WithClientPKGCMDs(`sh -c 'echo "{\"url\":\"/api/v2_2/_client/win/app.7z\",\"md5\":\"$(cat /data/app.7z.md5)\"}" > /data/check.json'`),
maker.WithClientPKGCMDs("mv resources/hybridscope_offline_installer.exe /data/hybridscope_offline_installer.exe"),
maker.WithClientPKGCMDs("mv resources/hybridscope-client-setup-zh.exe /data/hybridscope-client-setup-zh.exe"),
maker.WithClientPKGCMDs("mv resources/hybridscope-client-setup-en.exe /data/hybridscope-client-setup-en.exe"),
maker.WithClientPKGCMDs("rm -rf /data/hs_client.zip"),
maker.WithClientPKGCMDs("rm -rf resources"),
)
},
}
_cmd.Flags().StringVar(&_version, "version", "", "手动指定版本")
_cmd.Flags().BoolVar(&_pkg, "pkg", false, "是否生成升级 pkg")
return _cmd
}
func clientLinux() *cobra.Command {
func clientMac() *cobra.Command {
var (
_version string
_pkg bool
)
_cmd := &cobra.Command{
Use: "linux",
Short: "make client-linux pkg",
Use: "mac",
Aliases: []string{"macos"},
Short: "make client-mac pkg",
RunE: func(cmd *cobra.Command, args []string) error {
const (
DEB_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/universal-hsclient-linux/release/2.1.0/hscore-linux-2.1.0-csgElink-amd64.deb"
VERSION_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/universal-hsclient-linux/release/2.1.0/hscore-linux-2.1.0-std-amd64.json"
DMG_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/hs_appleclient-csgElink/release/2.1.0-std/hybridscope-client-mac.dmg"
PKG_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/hs_appleclient-csgElink/release/2.1.0-std/hybridscope-client-mac.pkg"
VERSION_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/hs_appleclient-csgElink/release/2.1.0-std/mac_version.json"
// std: VERSION_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/hs_appleclient/release/2.1.0-std/mac_version.json"
)
var (
@@ -125,18 +124,76 @@ func clientLinux() *cobra.Command {
return err
}
if _version != "" {
logger.InfoCtx(cmd.Context(), "clientMac: using manual version: %s => %s", version, _version)
version = _version
}
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.ClientPKG(
cmd.Context(),
"mac",
version,
"/api/v2_2/_client/mac",
maker.WithClientPkgMakePkg(_pkg),
maker.WithClientPKGDownload(DMG_URL, "hybridscope-client-mac.dmg"),
maker.WithClientPKGDownload(PKG_URL, "hybridscope-client-mac.pkg"),
)
},
}
_cmd.Flags().StringVar(&_version, "version", "", "手动指定版本")
_cmd.Flags().BoolVar(&_pkg, "pkg", false, "是否生成升级 pkg")
return _cmd
}
func clientLinux() *cobra.Command {
var (
_version string
_pkg bool
)
_cmd := &cobra.Command{
Use: "linux",
Short: "make client-linux pkg",
RunE: func(cmd *cobra.Command, args []string) error {
const (
DEB_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/universal-hsclient-linux/release/2.1.0/hscore-linux-2.1.0-csgElink-amd64.deb"
VERSION_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/universal-hsclient-linux/release/2.1.0/hscore-linux-2.1.0-csgElink-amd64.json"
// std: VERSION_URL = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/universal-hsclient-linux/release/2.1.0/hscore-linux-2.1.0-std-amd64.json"
)
var (
err error
version string
)
if version, err = clientVersion(VERSION_URL); err != nil {
return err
}
if _version != "" {
logger.InfoCtx(cmd.Context(), "clientLinux: using manual version: %s => %s", version, _version)
version = _version
}
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.ClientPKG(
cmd.Context(),
"linux",
version,
"/api/v2_2/_client/linux",
maker.WithClientPkgMakePkg(_pkg),
maker.WithClientPKGDownload(DEB_URL, "hybridscope-client-linux.deb"),
maker.WithClientPKGDownload(VERSION_URL, "version.json"),
)
},
}
_cmd.Flags().StringVar(&_version, "version", "", "手动指定版本")
_cmd.Flags().BoolVar(&_pkg, "pkg", false, "是否生成升级 pkg")
return _cmd
}
@@ -174,3 +231,68 @@ func clientVersion(_url string) (string, error) {
return res.Version, nil
}
func clientBaseImage() *cobra.Command {
const (
dbs = `
FROM %s
RUN sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.tuna.tsinghua.edu.cn/alpine#g' /etc/apk/repositories
RUN apk update && apk add curl wget tzdata unzip p7zip
ENV TZ=Asia/Shanghai
`
_command = "docker build -f Dockerfile -t hub.yizhisec.com/hsv2/base/nginx:latest ."
)
var (
_push bool
)
_cmd := &cobra.Command{
Use: "base",
Short: "make client base image",
RunE: func(cmd *cobra.Command, args []string) error {
var (
err error
tmpDir = filepath.Join(os.TempDir(), "hsv2-client-base")
output []byte
)
if err = os.MkdirAll(tmpDir, 0755); err != nil {
return err
}
defer os.RemoveAll(tmpDir)
dockerfile := filepath.Join(tmpDir, "Dockerfile")
if err = os.WriteFile(dockerfile, []byte(fmt.Sprintf(dbs, opt.IMAGE_NGINX)), 0644); err != nil {
return err
}
rc := exec.CommandContext(cmd.Context(), "sh", "-c", _command)
rc.Dir = tmpDir
if output, err = rc.CombinedOutput(); err != nil {
logger.Debug("❌ clientBaseImage: build image failed, output = %s, err = %v", string(output), err)
return err
}
logger.Debug("☑️ clientBaseImage: build image output = %s", string(output))
if _push {
rc = exec.CommandContext(cmd.Context(), "sh", "-c", "docker push hub.yizhisec.com/hsv2/base/nginx:latest")
if output, err = rc.CombinedOutput(); err != nil {
logger.Debug("❌ clientBaseImage: push image failed, output = %s, err = %v", string(output), err)
return err
}
logger.Debug("☑️ clientBaseImage: push image output = %s", string(output))
}
logger.Info("️✅ clientBaseImage: build image success!!!")
return nil
},
}
_cmd.Flags().BoolVar(&_push, "push", false, "push image")
return _cmd
}

View File

@@ -2,6 +2,8 @@ package makecmd
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
"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.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
}

View File

@@ -25,7 +25,7 @@ func Redis() *cobra.Command {
cmd.Context(),
maker.WithRedisReplicaCount(replicas),
maker.WithRedisPassword(password),
maker.WithRedisStorage(fmt.Sprintf("%dGi")),
maker.WithRedisStorage(fmt.Sprintf("%dGi", storage)),
)
},
}

View File

@@ -7,15 +7,21 @@ import (
)
func Yosguard() *cobra.Command {
var (
_pkg bool // make yosguard upgrade pkg
)
_cmd := &cobra.Command{
Use: "yosguard",
Aliases: []string{"YOS"},
Short: "Make Yosguard",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.Yosguard(cmd.Context())
return mk.Yosguard(cmd.Context(), maker.WithYosguardPkg(_pkg))
},
}
_cmd.Flags().BoolVar(&_pkg, "pkg", false, "make yosguard upgrade pkg")
return _cmd
}

View File

@@ -3,7 +3,6 @@ package cmd
import (
"context"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/opt"
)
@@ -12,14 +11,6 @@ var rootCmd = &cobra.Command{
Use: "forge",
Short: "Forge is a tool for building and installing 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 {

View File

@@ -1,11 +1,18 @@
package installer
import (
"bufio"
"context"
"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/tool/human"
)
type CheckOption func(*checkOpt)
@@ -13,6 +20,7 @@ type checkOpt struct {
ignoreDisk bool
ignoreMemory bool
ignoreCPU bool
noWriteDown bool
}
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 (
err error
now = time.Now()
o = &checkOpt{}
)
@@ -43,98 +219,195 @@ func (i *installer) Check(ctx context.Context, opts ...CheckOption) error {
fn(o)
}
logger.Info("☑️ installer.Check: Starting system checks...")
if err = i.targetOK(ctx); err != nil {
return err
if h.Timestamp < now.AddDate(0, 0, -1).UnixMilli() {
return fmt.Errorf("上次检测结果已失效, 请重新检测")
}
// 1. Check disk space: >= 500GB
logger.Info("☑️ installer.Check: Checking disk space...")
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 o.ignoreDisk || h.DiskSize+h.DiskReadSpeed+h.DiskWriteSpeed == -3 {
goto PASS_MEMORY
}
if !diskSpaceResult.Passed {
logger.Error("❌ %s: %s (Expected: %s, Actual: %s)", diskSpaceResult.Name, diskSpaceResult.Message, diskSpaceResult.Expected, diskSpaceResult.Actual)
if !o.ignoreDisk {
return fmt.Errorf("disk space check failed: %s", diskSpaceResult.Message)
}
if h.DiskSize < DISK_SIZE {
return fmt.Errorf("磁盘大小不满足, 需要: %s, 实际: %s", human.Size(DISK_SIZE), human.Size(h.DiskSize))
}
logger.Info("✅ %s: %s", diskSpaceResult.Name, diskSpaceResult.Actual)
// 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 h.DiskReadSpeed < DISK_READ_SPEED {
return fmt.Errorf("磁盘读速度不满足, 需要: %s, 实际: %s", human.Size(DISK_READ_SPEED), human.Size(h.DiskReadSpeed))
}
if !diskPerfResult.Passed {
logger.Error("❌ %s: %s (Expected: %s, Actual: %s)", diskPerfResult.Name, diskPerfResult.Message, diskPerfResult.Expected, diskPerfResult.Actual)
if !o.ignoreDisk {
return fmt.Errorf("disk performance check failed: %s", diskPerfResult.Message)
}
if h.DiskWriteSpeed < DISK_WRITE_SPEED {
return fmt.Errorf("磁盘写速度不满足, 需要: %s, 实际: %s", human.Size(DISK_WRITE_SPEED), human.Size(h.DiskWriteSpeed))
}
logger.Info("✅ %s: %s", diskPerfResult.Name, diskPerfResult.Actual)
PASS_MEMORY:
// 3. Check memory size: >= 15.5GB
logger.Info("☑️ installer.Check: Checking memory size...")
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 o.ignoreMemory || h.MemorySize+h.MemoryReadSpeed+h.MemoryWriteSpeed == -3 {
goto PASS_CPU
}
if !memResult.Passed {
logger.Error("❌ %s: %s (Expected: %s, Actual: %s)", memResult.Name, memResult.Message, memResult.Expected, memResult.Actual)
if !o.ignoreMemory {
return fmt.Errorf("memory check failed: %s", memResult.Message)
}
if h.MemorySize < MEMORY_SIZE {
return fmt.Errorf("内存大小不满足, 需要: %s, 实际: %s", human.Size(MEMORY_SIZE), human.Size(h.MemorySize))
}
logger.Info("✅ %s: %s", memResult.Name, memResult.Actual)
// 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 h.MemoryReadSpeed < MEMORY_READ_SPEED {
return fmt.Errorf("内存读速度不满足, 需要: %s, 实际: %s", human.Size(MEMORY_READ_SPEED), human.Size(h.MemoryReadSpeed))
}
if !cpuCoresResult.Passed {
logger.Error("❌ %s: %s (Expected: %s, Actual: %s)", cpuCoresResult.Name, cpuCoresResult.Message, cpuCoresResult.Expected, cpuCoresResult.Actual)
if !o.ignoreCPU {
return fmt.Errorf("CPU cores check failed: %s", cpuCoresResult.Message)
}
if h.MemoryWriteSpeed < MEMORY_WRITE_SPEED {
return fmt.Errorf("内存写速度不满足, 需要: %s, 实际: %s", human.Size(MEMORY_WRITE_SPEED), human.Size(h.MemoryWriteSpeed))
}
logger.Info("✅ %s: %s", cpuCoresResult.Name, cpuCoresResult.Actual)
PASS_CPU:
// 5. Check CPU frequency: >= 2GHz
logger.Info("☑️ installer.Check: Checking CPU frequency...")
cpuFreqResult, err := syscheck.CheckCPUFrequency(ctx, i, 2.0)
if err != nil {
logger.Debug("❌ installer.Check: Failed to check CPU frequency: %v", err)
return fmt.Errorf("failed to check CPU frequency: %w", err)
if o.ignoreCPU || h.CPUCores+h.CPUFrequency == -2 {
goto END
}
if h.CPUCores < CPUCORES {
return fmt.Errorf("cpu 核心数不满足, 需要: %d, 实际: %d", CPUCORES, h.CPUCores)
}
if !cpuFreqResult.Passed {
logger.Error("❌ %s: %s (Expected: %s, Actual: %s)", cpuFreqResult.Name, cpuFreqResult.Message, cpuFreqResult.Expected, cpuFreqResult.Actual)
if !o.ignoreCPU {
return fmt.Errorf("CPU frequency check failed: %s", cpuFreqResult.Message)
}
if h.CPUFrequency < CPU_FREQUENCY {
return fmt.Errorf("cpu 频率不满足, 需要: %s, 实际: %s", human.Size(CPU_FREQUENCY), human.Size(h.CPUFrequency))
}
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
}
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 (
"context"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"github.com/samber/lo"
)
type installer struct {
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
func (i *installer) ExecuteCommand(ctx context.Context, cmds ...string) (string, error) {
cmd := i.buildCommand(ctx, cmds...)
if cmd == nil {
return "", fmt.Errorf("failed to build command")
fcs := lo.Filter(cmds, func(item string, _ int) bool { return item != "" })
if len(fcs) == 0 {
return "", fmt.Errorf("empty commands")
}
cmd := exec.CommandContext(ctx, "sh", "-c", strings.Join(fcs, " "))
output, err := cmd.CombinedOutput()
if err != nil {
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
}
type CopyFileOption func(*copyFileOptions)
type copyFileOptions struct {
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}
func NewInstaller(workdir string) *installer {
return &installer{workdir: workdir}
}

View File

@@ -2,11 +2,7 @@ package installer
import (
"context"
"fmt"
"os"
"path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"github.com/samber/lo"
)
@@ -41,83 +37,5 @@ func WithK0sWorkerTokenFile(filename string) K0sOpt {
}
func (i *installer) K0s(ctx context.Context, opts ...K0sOpt) error {
var (
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
panic("plz impl")
}

View File

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

View File

@@ -6,11 +6,11 @@ import (
"os"
"path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource"
)
func (m *maker) AppClient(ctx context.Context, replica int) error {
func (m *maker) AppClient(ctx context.Context, version string, replica int) error {
const (
_config = `Version: "3"
APNs: /yizhisec/hs_nginx/data/443/oem/data.json
@@ -206,7 +206,8 @@ kubectl rollout restart deployment client-deployment -n hsv2`
workdir = filepath.Join(m.workdir, "app", "client")
)
logger.Info("☑️ maker.AppClient: 开始构建 client 应用..., dir = %s", workdir)
logger.Info("☑️ maker.AppClient: 开始构建 client 应用..., version = %s, dir = %s", version, workdir)
_ = os.RemoveAll(workdir)
logger.Debug("☑️ maker.AppClient: 开始创建工作目录 = %s", workdir)
if err = os.MkdirAll(workdir, 0755); err != nil {
logger.Debug("❌ maker.AppClient: 创建目录失败: %v", err)
@@ -214,6 +215,17 @@ kubectl rollout restart deployment client-deployment -n hsv2`
}
logger.Debug("✅ maker.AppClient: 创建工作目录成功 = %s", workdir)
imgName := "hub.yizhisec.com/hybridscope/client_server:latest"
logger.Debug("☑️ maker.AppClient: start pull image = %s", imgName)
if err = m.Image(ctx, imgName,
WithImageForcePull(true),
WithImageSave(filepath.Join(workdir, "client.tar")),
); err != nil {
logger.Debug("❌ maker.AppClient: 拉取镜像失败: %v", err)
return err
}
logger.Debug("✅ maker.AppClient: pull image success = %s", imgName)
if replica < 1 {
replica = 1
}
@@ -240,6 +252,11 @@ kubectl rollout restart deployment client-deployment -n hsv2`
}
logger.Debug("✅ maker.AppClient: 构建 upsert.sh 脚本成功")
logger.Info("✅ maker.AppClient: 构建 client 应用成功!!!")
if err = os.WriteFile(filepath.Join(workdir, "version.txt"), []byte(version), 0644); err != nil {
logger.Debug("❌ maker.AppClient: 写入 version.txt 失败: %v", err)
return err
}
logger.Info("✅ maker.AppClient: 构建 client 应用成功!!! version = %s", version)
return nil
}

View File

@@ -6,12 +6,12 @@ import (
"os"
"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/resource"
)
func (m *maker) AppFront(ctx context.Context, vendor string, replica int) error {
func (m *maker) AppFront(ctx context.Context, version string, vendor string, replica int) error {
var (
err error
location = filepath.Join(m.workdir, "app", "front")
@@ -19,55 +19,53 @@ func (m *maker) AppFront(ctx context.Context, vendor string, replica int) error
_vendor = model.GetVendor(vendor)
)
logger.Info("☑️ maker.Front: 开始构建 front app..., workdir = %s", location)
logger.Info("☑️ maker.Front: 开始构建 front app[%s]..., version = %s, vendor = %s, workdir = %s", vendor, version, location)
if _vendor == nil {
logger.Debug("❌ maker.Front: vendor not supported, vendor = %s", vendor)
return fmt.Errorf("vendor not supported: %s", vendor)
}
_ = os.RemoveAll(location)
if err = os.MkdirAll(location, 0755); err != nil {
logger.Debug("❌ maker.Front: 创建目录失败: path = %s, err = %v", location, err)
return err
}
path := filepath.Join(location, "front.user.yaml")
logger.Debug("☑️ maker.Front: writing front.user.yaml, path = %s", path)
path := filepath.Join(location, "deployment.user.yaml")
logger.Debug("☑️ maker.Front: writing deployment.user.yaml, path = %s", path)
bs = []byte(fmt.Sprintf(resource.YAMLAppFrontUser, replica))
if err = os.WriteFile(path, bs, 0644); err != nil {
logger.Debug("❌ maker.Front: 写入 front.user.yaml 失败: path = %s, err = %v", path, err)
logger.Debug("❌ maker.Front: 写入 deployment.user.yaml 失败: path = %s, err = %v", path, err)
return err
}
logger.Debug("✅ maker.Front: write front.user.yaml success, path = %s", path)
logger.Debug("✅ maker.Front: write deployment.user.yaml success, path = %s", path)
path = filepath.Join(location, "front.admin.yaml")
logger.Debug("☑️ maker.Front: writing front.admin.yaml, path = %s", path)
path = filepath.Join(location, "deployment.admin.yaml")
logger.Debug("☑️ maker.Front: writing deployment.admin.yaml, path = %s", path)
bs = []byte(fmt.Sprintf(resource.YAMLAppFrontAdmin, replica))
if err = os.WriteFile(path, bs, 0644); err != nil {
logger.Debug("❌ maker.Front: 写入 front.admin.yaml 失败: path = %s, err = %v", path, err)
return err
}
logger.Debug("✅ maker.Front: write front.admin.yaml success, path = %s", path)
// todo, pull front images
// 1. make image dir
imgDir := filepath.Join(m.workdir, "dependency", "image")
if err = os.MkdirAll(imgDir, 0755); err != nil {
logger.Debug("❌ maker.Front: 创建目录失败: path = %s, err = %v", imgDir, err)
logger.Debug("❌ maker.Front: 写入 deployment.admin.yaml 失败: path = %s, err = %v", path, err)
return err
}
logger.Debug("✅ maker.Front: write deployment.admin.yaml success, path = %s", path)
logger.Debug("☑️ maker.Front: pulling front images, vendor = %s", vendor)
if err = m.Image(ctx, _vendor.AppFrontUserImageName, WithImageSave(filepath.Join(imgDir, "app.front.user.tar")), WithImageForcePull(true)); err != nil {
if err = m.Image(ctx, _vendor.AppFrontUserImageName, WithImageSave(filepath.Join(location, "front.user.tar")), WithImageForcePull(true)); err != nil {
logger.Debug("❌ maker.Front: 拉取 front 用户镜像失败: %s, err = %v", _vendor.AppFrontUserImageName, err)
return err
}
if err = m.Image(ctx, _vendor.AppFrontAdminImageName, WithImageSave(filepath.Join(imgDir, "app.front.admin.tar")), WithImageForcePull(true)); err != nil {
if err = m.Image(ctx, _vendor.AppFrontAdminImageName, WithImageSave(filepath.Join(location, "front.admin.tar")), WithImageForcePull(true)); err != nil {
logger.Debug("❌ maker.Front: 拉取 front 管理镜像失败: %s, err = %v", _vendor.AppFrontAdminImageName, err)
return err
}
logger.Info("✅ maker.Front: 构建 front app 完成")
if err = os.WriteFile(filepath.Join(location, "version.txt"), []byte(version), 0644); err != nil {
logger.Debug("❌ maker.Front: 写入 version.txt 失败: %v", err)
return err
}
logger.Info("✅ maker.Front: 构建 front app[%s] 完成, version = %s", vendor, version)
return nil
}

View File

@@ -6,11 +6,11 @@ import (
"os"
"path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource"
)
func (m *maker) AppGateway(ctx context.Context, replica int) error {
func (m *maker) AppGateway(ctx context.Context, version string, replica int) error {
const (
_config = `Version: "3"
Database:
@@ -59,7 +59,8 @@ kubectl rollout restart deployment gateway-deployment -n hsv2`
workdir = filepath.Join(m.workdir, "app", "gateway")
)
logger.Info("☑️ maker.AppGateway: 开始构建 gateway 应用..., dir = %s", workdir)
logger.Info("☑️ maker.AppGateway: 开始构建 gateway 应用..., version = %s, dir = %s", version, workdir)
_ = os.RemoveAll(workdir)
logger.Debug("☑️ maker.AppGateway: 开始创建工作目录 = %s", workdir)
if err = os.MkdirAll(workdir, 0755); err != nil {
logger.Debug("❌ maker.AppGateway: 创建目录失败: %v", err)
@@ -67,6 +68,17 @@ kubectl rollout restart deployment gateway-deployment -n hsv2`
}
logger.Debug("✅ maker.AppGateway: 创建工作目录成功 = %s", workdir)
imgName := "hub.yizhisec.com/hybridscope/gateway_controller:latest"
logger.Debug("☑️ maker.AppGateway: start pull image = %s", imgName)
if err = m.Image(ctx, imgName,
WithImageForcePull(true),
WithImageSave(filepath.Join(workdir, "gateway.tar")),
); err != nil {
logger.Debug("❌ maker.AppGateway: 拉取镜像失败: %v", err)
return err
}
logger.Debug("✅ maker.AppGateway: pull image success = %s", imgName)
if replica < 1 {
replica = 1
}
@@ -93,6 +105,16 @@ kubectl rollout restart deployment gateway-deployment -n hsv2`
}
logger.Debug("✅ maker.AppGateway: 构建 upsert.sh 脚本成功")
logger.Info("✅ maker.AppGateway: 构建 gateway 应用成功!!!")
if err = os.WriteFile(filepath.Join(workdir, "version.txt"), []byte(version), 0644); err != nil {
logger.Debug("❌ maker.AppGateway: 写入 version.txt 失败: %v", err)
return err
}
if err = os.WriteFile(filepath.Join(workdir, "version.txt"), []byte(version), 0644); err != nil {
logger.Debug("❌ maker.AppGateway: 写入 version.txt 失败: %v", err)
return err
}
logger.Info("✅ maker.AppGateway: 构建 gateway 应用成功!!! version = %s", version)
return nil
}

View File

@@ -0,0 +1,73 @@
package maker
import (
"context"
"fmt"
"os"
"path/filepath"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource"
)
// AppHelper
func (m *maker) AppHelper(ctx context.Context, version string, vendor string, replica int) error {
const (
// _config = ``
_upsert = `#!/bin/bash
kubectl apply -f deployment.yaml
kubectl rollout restart deployment app-helper-deployment -n hsv2`
)
var (
err error
workdir = filepath.Join(m.workdir, "app", "helper")
)
logger.Info("☑️ maker.AppHelper: 开始构建 helper 应用..., version = %s, vendor = %s, dir = %s", version, vendor, workdir)
_ = os.RemoveAll(workdir)
logger.Debug("☑️ maker.AppHelper: 开始创建工作目录 = %s", workdir)
if err = os.MkdirAll(workdir, 0755); err != nil {
logger.Debug("❌ maker.AppHelper: 创建目录失败: %v", err)
return err
}
logger.Debug("✅ maker.AppHelper: 创建工作目录成功 = %s", workdir)
imgName := "hub.yizhisec.com/hsv2/app/helper:latest"
logger.Debug("☑️ maker.AppHelper: start pull image = %s", imgName)
if err = m.Image(ctx, imgName,
WithImageForcePull(true),
WithImageSave(filepath.Join(workdir, "helper.tar")),
); err != nil {
logger.Debug("❌ maker.AppHelper: 拉取镜像失败: %v", err)
return err
}
logger.Debug("✅ maker.AppHelper: pull image success = %s", imgName)
if replica < 1 {
replica = 1
}
logger.Debug("☑️ maker.AppHelper: 开始构建 yaml 资源文件")
content := []byte(fmt.Sprintf(resource.YAMLAppHelper, replica, version))
if err = os.WriteFile(filepath.Join(workdir, "deployment.yaml"), content, 0644); err != nil {
logger.Debug("❌ maker.AppHelper: 写入 deployment.yaml 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppHelper: 开始 yaml 资源文件成功")
logger.Debug("☑️ maker.AppHelper: 开始构建 upsert.sh 脚本")
if err = os.WriteFile(filepath.Join(workdir, "upsert.sh"), []byte(_upsert), 0755); err != nil {
logger.Debug("❌ maker.AppHelper: 写入 upsert.sh 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppHelper: 构建 upsert.sh 脚本成功")
if err = os.WriteFile(filepath.Join(workdir, "version.txt"), []byte(version), 0644); err != nil {
logger.Debug("❌ maker.AppHelper: 写入 version.txt 失败: %v", err)
return err
}
logger.Info("✅ maker.AppHelper: 构建 helper 应用成功!!! version = %s", version)
return nil
}

View File

@@ -6,11 +6,11 @@ import (
"os"
"path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource"
)
func (m *maker) AppMie(ctx context.Context, replica int) error {
func (m *maker) AppMie(ctx context.Context, version string, replica int) error {
const (
_config = `Version: "3"
BackupSeafile:
@@ -136,7 +136,8 @@ kubectl rollout restart deployment api-deployment -n hsv2`
workdir = filepath.Join(m.workdir, "app", "mie")
)
logger.Info("☑️ maker.AppMie: 开始构建 mie ... workdir = %s", workdir)
logger.Info("☑️ maker.AppMie: 开始构建 mie ... version = %s, workdir = %s", version, workdir)
_ = os.RemoveAll(workdir)
logger.Debug("☑️ maker.AppMie: 开始创建工作目录 = %s", workdir)
if err = os.MkdirAll(workdir, 0755); err != nil {
logger.Debug("❌ maker.AppMie: 创建目录失败: %v", err)
@@ -144,6 +145,17 @@ kubectl rollout restart deployment api-deployment -n hsv2`
}
logger.Debug("✅ maker.AppMie: 创建工作目录成功 = %s", workdir)
imgName := "hub.yizhisec.com/hybridscope/mie-server:latest"
logger.Debug("☑️ maker.AppMie: start pull image = %s", imgName)
if err = m.Image(ctx, imgName,
WithImageForcePull(true),
WithImageSave(filepath.Join(workdir, "mie.tar")),
); err != nil {
logger.Debug("❌ maker.AppMie: 拉取镜像失败: %v", err)
return err
}
logger.Debug("✅ maker.AppMie: pull image success = %s", imgName)
if replica < 1 {
replica = 1
}
@@ -191,6 +203,11 @@ kubectl rollout restart deployment api-deployment -n hsv2`
}
logger.Debug("✅ maker.AppMie: 写入 upsert.sh 文件成功, dest = %s", filepath.Join(workdir, "upsert.sh"))
logger.Info("✅ maker.AppMie: 构建 mie 成功!!! workdir = %s", workdir)
if err = os.WriteFile(filepath.Join(workdir, "version.txt"), []byte(version), 0644); err != nil {
logger.Debug("❌ maker.AppMie: 写入 version.txt 失败: %v", err)
return err
}
logger.Info("✅ maker.AppMie: 构建 mie 成功!!! version = %s, workdir = %s", version, workdir)
return nil
}

View File

@@ -5,34 +5,14 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"github.com/samber/lo"
"yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource"
)
type NginxOpt func(*nginxOpt)
type nginxOpt struct {
WithoutSeafile bool
Replica int
}
func WithNginxReplica(replica int) NginxOpt {
return func(o *nginxOpt) {
if replica >= 0 {
o.Replica = replica
}
}
}
func WithoutNginxSeafile() NginxOpt {
return func(o *nginxOpt) {
o.WithoutSeafile = true
}
}
func (m *maker) AppNginx(ctx context.Context, opts ...NginxOpt) error {
func (m *maker) AppNginx(ctx context.Context, version string, vendor string, replica int, inlcudeImage bool) error {
const (
_upsert = `#!/bin/bash
@@ -59,31 +39,40 @@ kubectl create configmap ssl-client-server-key --namespace hsv2 --from-file=clie
kubectl create configmap ssl-web-server-crt --namespace hsv2 --from-file=web.server.crt=./ssl/web.server.crt --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap ssl-web-server-key --namespace hsv2 --from-file=web.server.key=./ssl/web.server.key --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f nginx.yaml
kubectl apply -f deployment.yaml
kubectl rollout restart deployment nginx-deployment -n hsv2`
)
var (
err error
workdir = filepath.Join(m.workdir, "app", "nginx")
o = nginxOpt{
WithoutSeafile: false,
Replica: 2,
}
err error
workdir = filepath.Join(m.workdir, "app", "nginx")
applySeafile = "kubectl create configmap nginx-seafile --namespace hsv2 --from-file=seafile.conf=./conf/seafile.conf --dry-run=client -o yaml | kubectl apply -f -"
)
logger.Info(" ☑️ maker.AppNginx: 开始构建 nginx, workdir = %s", workdir)
logger.Info("☑️ maker.AppNginx: 开始构建 nginx, version = %s, vendor = %s, workdir = %s", version, vendor, workdir)
for _, fn := range opts {
fn(&o)
}
logger.Debug(" ☑️ maker.AppNginx: 创建工作目录 = %s", workdir)
logger.Debug("☑️ maker.AppNginx: 创建工作目录 = %s", workdir)
_ = os.RemoveAll(workdir)
if err = os.MkdirAll(workdir, 0755); err != nil {
return err
}
logger.Debug("✅ maker.AppNginx: 创建工作目录成功 = %s", workdir)
if inlcudeImage {
logger.Debug("☑️ maker.AppNginx: start pull image = %s", opt.IMAGE_NGINX)
if err = m.Image(ctx, opt.IMAGE_NGINX, WithImageSave(filepath.Join(workdir, "nginx.tar"))); err != nil {
logger.Debug("❌ maker.AppNginx: 拉取镜像失败: %v", err)
return err
}
logger.Debug("✅ maker.AppNginx: pull image success = %s", opt.IMAGE_NGINX)
tag := strings.Split(opt.IMAGE_NGINX, ":")[1]
logger.Debug("☑️ maker.AppNginx: write tag.txt = %s", tag)
if err = os.WriteFile(filepath.Join(workdir, "tag.txt"), []byte(tag), 0644); err != nil {
logger.Debug("❌ maker.AppNginx: 写入 tag.txt 失败: %v", err)
return err
}
}
// 子目录: conf 与 ssl
confDir := filepath.Join(workdir, "conf")
sslDir := filepath.Join(workdir, "ssl")
@@ -113,8 +102,8 @@ kubectl rollout restart deployment nginx-deployment -n hsv2`
{"common.conf", resource.NGINXCommon},
}
// 过滤 seafile.conf 文件
if !o.WithoutSeafile {
// vendor != elink, append seafile conf
if vendor != "elink" {
confFiles = append(confFiles, struct {
name string
content []byte
@@ -157,23 +146,31 @@ kubectl rollout restart deployment nginx-deployment -n hsv2`
}
// write nginx deployment yaml
dest := filepath.Join(workdir, "nginx.yaml")
content := []byte(fmt.Sprintf(resource.YAMLAppNGINX, o.Replica))
dest := filepath.Join(workdir, "deployment.yaml")
content := []byte(fmt.Sprintf(resource.YAMLAppNGINX, replica))
if err = os.WriteFile(dest, content, 0644); err != nil {
logger.Debug("❌ maker.AppNginx: 写入 nginx.yaml 失败: %v", err)
logger.Debug("❌ maker.AppNginx: 写入 deployment.yaml 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppNginx: 写入 nginx.yaml 成功, dest = %s", dest)
logger.Debug("✅ maker.AppNginx: 写入 deployment.yaml 成功, dest = %s", dest)
// write nginx upsert script
dest = filepath.Join(workdir, "upsert.sh")
content = []byte(fmt.Sprintf(_upsert, lo.If(o.WithoutSeafile, "").Else(applySeafile)))
if vendor == "elink" {
applySeafile = ""
}
content = []byte(fmt.Sprintf(_upsert, applySeafile))
if err = os.WriteFile(dest, content, 0755); err != nil {
logger.Debug("❌ maker.AppNginx: 写入 upsert.sh 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppNginx: 写入 upsert.sh 成功, dest = %s", dest)
logger.Info("✅ maker.AppNginx: nginx 构建完成")
if err = os.WriteFile(filepath.Join(workdir, "version.txt"), []byte(version), 0644); err != nil {
logger.Debug("❌ maker.AppNginx: 写入 version.txt 失败: %v", err)
return err
}
logger.Info("✅ maker.AppNginx: nginx 构建完成, version = %s, vendor = %s", version, vendor)
return nil
}

View File

@@ -7,13 +7,13 @@ import (
"os/exec"
"path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/archiver"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model"
"yizhisec.com/hsv2/forge/pkg/resource"
)
func (m *maker) AppOEM(ctx context.Context, vendor string, replica int) error {
func (m *maker) AppOEM(ctx context.Context, version string, vendor string, replica int) error {
const (
_nginx = `user root;
worker_processes auto;
@@ -61,7 +61,7 @@ CMD ["nginx", "-g", "daemon off;"]`
output []byte
)
logger.Info("☑️ maker.AppOEM: 开始构建 oem[%s], workdir = %s", vendor, workdir)
logger.Info("☑️ maker.AppOEM: 开始构建 oem[%s], version = %s, ,workdir = %s", vendor, version, workdir)
if _vendor == nil {
supported := model.GetVendorNames()
@@ -71,6 +71,7 @@ CMD ["nginx", "-g", "daemon off;"]`
// 1. make workdir
logger.Debug("☑️ maker.AppOEM: 开始创建 workdir = %s", workdir)
_ = os.RemoveAll(workdir)
if err = os.MkdirAll(workdir, 0o755); err != nil {
return err
}
@@ -92,6 +93,7 @@ CMD ["nginx", "-g", "daemon off;"]`
}
}
logger.Debug("✅ maker.AppOEM: oem[%s] tar 下载成功", vendor)
defer os.RemoveAll(filepath.Join(workdir, "oem"))
// 3. write nginx.conf
logger.Debug("☑️ maker.AppOEM: 开始写入 nginx.conf")
@@ -127,14 +129,10 @@ CMD ["nginx", "-g", "daemon off;"]`
return err
}
logger.Debug("✅ maker.AppOEM: docker image 构建成功, image = %s", imageName)
defer os.RemoveAll(filepath.Join(workdir, "Dockerfile"))
defer os.RemoveAll(filepath.Join(workdir, "nginx.conf"))
// 6. save docker image to image dir
logger.Debug("☑️ maker.AppOEM: 开始保存 docker image = %s 到 %s", imageName, filepath.Join(m.workdir, "image", imageName))
if err = os.MkdirAll(filepath.Join(m.workdir, "dependency", "image"), 0o755); err != nil {
logger.Debug("❌ maker.AppOEM: image 目录创建失败, err = %v", err)
return err
}
if err = exec.CommandContext(ctx, "docker", "save", "-o", filepath.Join(m.workdir, "dependency", "image", "app.oem.tar"), imageName).Run(); err != nil {
if err = exec.CommandContext(ctx, "docker", "save", "-o", filepath.Join(workdir, "oem.tar"), imageName).Run(); err != nil {
logger.Debug("❌ maker.AppOEM: docker image 保存失败, err = %v", err)
return err
}
@@ -153,7 +151,12 @@ CMD ["nginx", "-g", "daemon off;"]`
}
logger.Debug("✅ maker.AppOEM: deployment.yaml 写入成功")
logger.Info("✅ maker.AppOEM: 开始构建 oem[%s] 成功!!!", vendor)
if err = os.WriteFile(filepath.Join(workdir, "version.txt"), []byte(version), 0644); err != nil {
logger.Debug("❌ maker.AppOEM: 写入 version.txt 失败: %v", err)
return err
}
logger.Info("✅ maker.AppOEM: 开始构建 oem[%s] 成功!!! version = %s", vendor, version)
return nil
}

View File

@@ -6,11 +6,11 @@ import (
"os"
"path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource"
)
func (m *maker) AppUser(ctx context.Context, replica int) error {
func (m *maker) AppUser(ctx context.Context, version string, replica int) error {
const (
_config = `Version: "3"
Database:
@@ -87,15 +87,27 @@ kubectl rollout restart deployment user-deployment -n hsv2`
workdir = filepath.Join(m.workdir, "app", "user")
)
logger.Info("☑️ maker.AppUser: 开始构建 user 应用..., dir = %s", workdir)
logger.Info("☑️ maker.AppUser: 开始构建 user 应用..., version = %s, dir = %s", version, workdir)
logger.Debug("☑️ maker.AppUser: 开始创建工作目录 = %s", workdir)
_ = os.RemoveAll(workdir)
if err = os.MkdirAll(workdir, 0755); err != nil {
logger.Debug("❌ maker.AppUser: 创建目录失败: %v", err)
return err
}
logger.Debug("✅ maker.AppUser: 创建工作目录成功 = %s", workdir)
imgName := "hub.yizhisec.com/hybridscope/user_management:latest"
logger.Debug("☑️ maker.AppUser: start pull image = %s", imgName)
if err = m.Image(ctx, imgName,
WithImageForcePull(true),
WithImageSave(filepath.Join(workdir, "user.tar")),
); err != nil {
logger.Debug("❌ maker.AppUser: 拉取镜像失败: %v", err)
return err
}
logger.Debug("✅ maker.AppUser: pull image success = %s", imgName)
if replica < 1 {
replica = 1
}
@@ -123,6 +135,11 @@ kubectl rollout restart deployment user-deployment -n hsv2`
}
logger.Debug("✅ maker.AppUser: 构建 upsert.sh 脚本成功")
logger.Info("✅ maker.AppUser: 构建 user 应用成功!!!")
if err = os.WriteFile(filepath.Join(workdir, "version.txt"), []byte(version), 0644); err != nil {
logger.Debug("❌ maker.AppUser: 写入 version.txt 失败: %v", err)
return err
}
logger.Info("✅ maker.AppUser: 构建 user 应用成功!!! version = %s", version)
return nil
}

View File

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

View File

@@ -2,13 +2,14 @@ package maker
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"github.com/samber/lo"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource"
)
@@ -21,6 +22,13 @@ type ClientPKGOption func(*clientPKGOption)
type clientPKGOption struct {
Downloads []*clientPKGDownload
CMDs []string
MakePkg bool
}
func WithClientPkgMakePkg(pkg bool) ClientPKGOption {
return func(o *clientPKGOption) {
o.MakePkg = pkg
}
}
func WithClientPKGDownload(url, location string) ClientPKGOption {
@@ -36,22 +44,21 @@ func WithClientPKGDownload(url, location string) ClientPKGOption {
}
}
func WithClientPKGCMD(cmd string) ClientPKGOption {
func WithClientPKGCMDs(cmds ...string) ClientPKGOption {
return func(o *clientPKGOption) {
if cmd == "" {
return
}
o.CMDs = append(o.CMDs, cmd)
adds := lo.Filter(cmds, func(cmd string, _ int) bool { return cmd != "" })
o.CMDs = append(o.CMDs, adds...)
}
}
func (m *maker) ClientPKG(ctx context.Context, _os string, _version string, api string, opts ...ClientPKGOption) error {
const (
Dockerfile = `
FROM hub.yizhisec.com/external/nginx:1.29.4-alpine3.23
FROM hub.yizhisec.com/hsv2/base/nginx:latest
RUN mkdir -p /data
%s
COPY version.json /data/version.json
%s
COPY nginx.conf /etc/nginx/nginx.conf
@@ -71,7 +78,6 @@ COPY nginx.conf /etc/nginx/nginx.conf
location = filepath.Join(m.workdir, "client", _os)
_file string
_content string
_cmds = ""
)
for _, fn := range opts {
@@ -80,16 +86,31 @@ COPY nginx.conf /etc/nginx/nginx.conf
logger.Info("☑️ maker.ClientPKG: start build client pkg, os = %s, version = %s, location = %s", _os, _version, location)
if err = os.RemoveAll(location); err != nil {
logger.Debug("❌ maker.ClientPKG: remove directory failed, directory = %s, err = %s", location, err.Error())
return err
}
if err = os.MkdirAll(location, 0755); err != nil {
logger.Debug("❌ maker.ClientPKG: create directory failed, directory = %s, err = %s", location, err.Error())
return err
}
vd := map[string]string{
"version": _version,
}
vbs, _ := json.Marshal(vd)
if err = os.WriteFile(filepath.Join(location, "version.txt"), []byte(_version), 0644); err != nil {
logger.Debug("❌ maker.ClientPKG: write file failed, file = %s, err = %s", filepath.Join(location, "version.txt"), err.Error())
return err
}
if err = os.WriteFile(filepath.Join(location, "version.json"), []byte(vbs), 0644); err != nil {
logger.Debug("❌ maker.ClientPKG: write file failed, file = %s, err = %s", filepath.Join(location, "version.json"), err.Error())
return err
}
_file = filepath.Join(location, "nginx.conf")
_content = fmt.Sprintf(resource.NGINXClientPKG, api)
logger.Debug("☑️ maker.ClientPKG: start write file = %s", _file)
@@ -100,15 +121,15 @@ COPY nginx.conf /etc/nginx/nginx.conf
logger.Debug("✅ maker.ClientPKG: write file success, file = %s", _file)
lines := lo.Map(o.Downloads, func(d *clientPKGDownload, index int) string {
return fmt.Sprintf("RUN wget -O /data/%s %s", d.Name, d.URL)
return fmt.Sprintf("wget -O /data/%s %s", d.Name, d.URL)
})
if len(o.CMDs) > 0 {
_cmds = fmt.Sprintf("RUN %s", strings.Join(o.CMDs, " && "))
lines = append(lines, o.CMDs...)
}
_file = filepath.Join(location, "Dockerfile")
_content = fmt.Sprintf(Dockerfile, strings.Join(lines, "\n"), _cmds)
_content = fmt.Sprintf(Dockerfile, "RUN "+strings.Join(lines, " && "))
logger.Debug("☑️ maker.ClientPKG: start write file = %s", _file)
if os.WriteFile(_file, []byte(_content), 0644); err != nil {
logger.Debug("❌ maker.ClientPKG: write file failed, file = %s, err = %s", _file, err.Error())
@@ -148,6 +169,15 @@ COPY nginx.conf /etc/nginx/nginx.conf
return err
}
if o.MakePkg {
_cmd := fmt.Sprintf("7za a -pRrPt7Uo9WM1dkXOJmHps56T8BZY2qA4g -mhe=on client.%s.pkg *", _os)
logger.Debug("☑️ maker.ClientPKG: start create client.%s.pkg by 7zip(7za), cmd = '%s'", _os, _cmd)
if err = m.RunCommand(ctx, location, _cmd); err != nil {
logger.Debug("❌ maker.ClientPKG: run command failed, cmd = %s, err = %s", _cmd, err.Error())
return err
}
}
logger.Info("️✅ maker.ClientPKG: build client pkg success, os = %s, version = %s, location = %s", _os, _version, location)
return nil

View File

@@ -1,9 +1,12 @@
package maker
import (
"bytes"
"context"
"fmt"
"os/exec"
"yizhisec.com/hsv2/forge/pkg/logger"
)
func (m *maker) RunCommand(ctx context.Context, dir string, _cmds ...string) error {
@@ -11,6 +14,8 @@ func (m *maker) RunCommand(ctx context.Context, dir string, _cmds ...string) err
return nil
}
logger.Debug("maker.RunCommand: dir = %s, cmds = %v", dir, _cmds)
for _, cmdStr := range _cmds {
cmd := exec.CommandContext(ctx, "sh", "-c", cmdStr)
@@ -18,9 +23,13 @@ func (m *maker) RunCommand(ctx context.Context, dir string, _cmds ...string) err
cmd.Dir = dir
}
var buf bytes.Buffer
cmd.Stdout = &buf
cmd.Stderr = &buf
// Execute the command
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to run command '%s' in directory '%s': %w", cmdStr, dir, err)
return fmt.Errorf("failed to run command '%s' in directory '%s': %w, output = %s", cmdStr, dir, err, buf.String())
}
}

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import (
"os"
"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/resource"
)

View File

@@ -6,7 +6,7 @@ import (
"os"
"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/resource"
)

View File

@@ -5,8 +5,8 @@ import (
"os"
"path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/archiver"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource"
)
@@ -19,7 +19,7 @@ After=network.target containerd.service
[Service]
# 启动前清理旧容器
# ExecStartPre=-/usr/local/bin/k0s ctr -n hs-net task kill hs-net
ExecStartPre=-/usr/local/bin/k0s ctr -n hs-net task kill hs-net
ExecStartPre=-/usr/local/bin/k0s ctr namespace create hs-net
ExecStartPre=-/usr/local/bin/k0s ctr -n hs-net container rm hs-net
@@ -59,7 +59,7 @@ StandardError=journal
SyslogIdentifier=hs-net
# 清理退出的容器
# ExecStop=/usr/local/bin/k0s ctr -n hs-net task kill hs-net
ExecStop=/usr/local/bin/k0s ctr -n hs-net task kill hs-net
ExecStopPost=/usr/local/bin/k0s ctr -n hs-net container rm hs-net
[Install]
@@ -166,11 +166,35 @@ tcp_mode_disable: false
logger.Info("☑️ MakeHSNet: 开始构建 hs-net, workdir = %s", workdir)
_ = os.RemoveAll(workdir)
if err = os.MkdirAll(workdir, 0755); err != nil {
logger.Debug("❌ MakeHSNet: 创建目录失败: %s", err.Error())
return err
}
// {Name: "", Fallback: "", Save: "app.less_dns.tar", Force: true},
imgName := "hub.yizhisec.com/hybridscope/hsnet:release_2.1.0-std"
logger.Debug("☑️ maker.HSNet: start pull image = %s", imgName)
if err = m.Image(ctx, imgName,
WithImageForcePull(true),
WithImageSave(filepath.Join(workdir, "hsnet.tar")),
); err != nil {
logger.Debug("❌ maker.HSNet: 拉取镜像失败: %v", err)
return err
}
logger.Debug("✅ maker.HSNet: pull image success = %s", imgName)
imgName = "hub.yizhisec.com/hybridscope/less_dns_service:latest"
logger.Debug("☑️ maker.HSNet: start pull image = %s", imgName)
if err = m.Image(ctx, imgName,
WithImageForcePull(true),
WithImageSave(filepath.Join(workdir, "less-dns.tar")),
); err != nil {
logger.Debug("❌ maker.HSNet: 拉取镜像失败: %v", err)
return err
}
logger.Debug("✅ maker.HSNet: pull image success = %s", imgName)
if err = archiver.DownloadAndExtract(ctx, _url, workdir); err != nil {
logger.Debug("❌ MakeHSNet: 下载和解压失败: %s", err.Error())
return err
@@ -225,15 +249,6 @@ tcp_mode_disable: false
return err
}
imgName := "hub.yizhisec.com/hybridscope/hsnet:release_2.1.0-std"
imgPath := filepath.Join(workdir, "hs-net.tar")
logger.Debug("☑️ MakeHSNet: 构建镜像 %s 到 %s", imgName, imgPath)
if err = m.Image(ctx, imgName, WithImageSave(imgPath), WithImageForcePull(true)); err != nil {
logger.Debug("❌ MakeHSNet: 构建镜像失败: %s", err.Error())
return err
}
logger.Debug("✅ MakeHSNet: 构建镜像 %s 到 %s 成功", imgName, imgPath)
// write hs-net.service
if err = os.WriteFile(filepath.Join(workdir, "hs-net.service"), []byte(_service), 0644); err != nil {
logger.Debug("❌ MakeHSNet: 写入服务文件失败: %s", err.Error())

View File

@@ -6,8 +6,8 @@ import (
"os/exec"
"path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"github.com/samber/lo"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model"
)
@@ -112,14 +112,6 @@ func (m *maker) Images(ctx context.Context) error {
var images = []*model.Image{
{Name: "hub.yizhisec.com/external/alpine:3.22.2", Fallback: "", Save: "alpine.tar", Force: true},
{Name: "hub.yizhisec.com/external/nginx:1.29.1-alpine3.22", Fallback: "", Save: "nginx.1.29.1-alpine3.22.tar"},
{Name: "hub.yizhisec.com/hybridscope/user_management:latest", Fallback: "", Save: "app.user.tar", Force: true},
{Name: "hub.yizhisec.com/hybridscope/gateway_controller:latest", Fallback: "", Save: "app.gateway.tar", Force: true},
{Name: "hub.yizhisec.com/hybridscope/client_server:latest", Fallback: "", Save: "app.client.tar", Force: true},
{Name: "hub.yizhisec.com/hybridscope/mie-server:latest", Fallback: "", Save: "app.mie.tar", Force: true},
{Name: "hub.yizhisec.com/hybridscope/less_dns_service:latest", Fallback: "", Save: "app.less_dns.tar", Force: true},
{Name: "hub.yizhisec.com/hybridscope/hsnet:release_2.1.0-std", Fallback: "", Save: "app.hsnet.tar", Force: true},
}
for _, image := range images {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,8 +6,8 @@ import (
"os"
"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/resource"
)
@@ -123,7 +123,7 @@ kubectl rollout restart deployment backup-seafile-deployment -n seafile`
ServerHostname: "cloud.hybridscope.com",
Storage: "50Gi",
}
workdir = filepath.Join(m.workdir, "dependency", "seafile")
workdir = filepath.Join(m.workdir, "dependency", "seafile")
)
for _, fn := range opts {
@@ -187,7 +187,7 @@ kubectl rollout restart deployment backup-seafile-deployment -n seafile`
// 7. prepare 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 {
logger.Error("❌ make.Seafile: 准备 images 目录: %s 失败, err = %v", imgDir, err)
return err

View File

@@ -2,34 +2,38 @@ package maker
import (
"context"
"fmt"
"os"
"path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/downloader"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource"
)
type YosguardOpt func(*yosguardOpt)
type yosguardOpt struct{}
type yosguardOpt struct {
Pkg bool
}
func WithYosguardPkg(makePkg bool) YosguardOpt {
return func(o *yosguardOpt) {
o.Pkg = makePkg
}
}
func (m *maker) Yosguard(ctx context.Context, opts ...YosguardOpt) error {
const (
_config = `
AsController: true
AsGateway: false
ControllerServer:
Host: dasheng.zhsftech.debug
Port: 443
Web:
Host: __ip__
Port: 7788
Database:
SQLite:
DBPath: /etc/yosguard/db/yosguard.db
HeartbeatDuration: 5
UUIDFilePath: /etc/yosguard/uuid
Web:
Host: __ip__
Port: 7788
`
systemdService = `
@@ -52,10 +56,23 @@ WantedBy=multi-user.target`
binURL = "https://artifactory.yizhisec.com:443/artifactory/filestore/hsv2/bin/yosguard"
)
location := filepath.Join(m.workdir, "dependency", "yosguard")
var (
location = filepath.Join(m.workdir, "dependency", "yosguard")
o = &yosguardOpt{}
)
for _, fn := range opts {
fn(o)
}
logger.Info("☑️ maker.Yosguard: 开始构建 yosguard...")
logger.Debug("☑️ maker.Yosguard: 创建目录 %s", location)
if err := os.RemoveAll(location); err != nil {
logger.Debug("❌ maker.Yosguard: 删除 yosguard 目录失败, dir = %s, err = %v", location, err)
return err
}
if err := os.MkdirAll(location, 0755); err != nil {
logger.Debug("❌ maker.Yosguard: 创建 yosguard 目录失败: %v", err)
return err
@@ -96,6 +113,15 @@ WantedBy=multi-user.target`
}
logger.Debug("✅ maker.Yosguard: 写入 yosguard.service 文件成功, dest = %s", filepath.Join(location, "yosguard.service"))
if o.Pkg {
_cmd := fmt.Sprintf("cd %s && 7za a -pRrPt7Uo9WM1dkXOJmHps56T8BZY2qA4g -mhe=on yosguard.pkg *", location)
logger.Debug("☑️ maker.Yosguard: start create yosguard.pkg by 7zip, cmd = '%s'", _cmd)
if err := m.RunCommand(ctx, location, _cmd); err != nil {
logger.Debug("❌ maker.Yosguard: 创建 yosguard.pkg 失败, err = %v", err)
return err
}
}
logger.Info("✅ maker.Yosguard: 构建 yosguard 成功!!!")
return nil
}

8
internal/opt/var.go Normal file
View File

@@ -0,0 +1,8 @@
package opt
const (
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"
"strings"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/logger"
)
// Options defines options for downloading and extracting archives

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ import (
"testing"
"time"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/logger"
)
// 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

@@ -10,28 +10,28 @@ type Vendor struct {
var (
vendorMap = map[string]*Vendor{
"standard": &Vendor{
Name: "Standard",
"standard": {
Name: "standard",
OEMUrl: "https://artifactory.yizhisec.com/artifactory/yizhisec-release/oem/release/2.1.0-std/oem.tar.gz",
OEMDir: "oem",
AppFrontUserImageName: "hub.yizhisec.com/hybridscope/v2/front-user:latest",
AppFrontAdminImageName: "hub.yizhisec.com/build/hybirdscope/front/admin:latest",
},
"elink": &Vendor{
"elink": {
Name: "elink",
OEMUrl: "https://artifactory.yizhisec.com/artifactory/yizhisec-release/oem/release/2.1.0-std/oem_csgElink.tar.gz",
OEMDir: "oem_csgElink",
AppFrontUserImageName: "hub.yizhisec.com/hybridscope/v2/front-user-elink:latest",
AppFrontAdminImageName: "hub.yizhisec.com/build/hybirdscope/front/admin:latest",
},
"noah": &Vendor{
"noah": {
Name: "noah",
OEMUrl: "https://artifactory.yizhisec.com/artifactory/yizhisec-release/oem/release/2.1.0-std/oem_noah.tar.gz",
OEMDir: "oem_noah",
AppFrontUserImageName: "hub.yizhisec.com/hybridscope/v2/front-user:latest",
AppFrontAdminImageName: "hub.yizhisec.com/build/hybirdscope/front/admin:latest",
},
"heishuimeng": &Vendor{
"heishuimeng": {
Name: "heishuimeng",
OEMUrl: "https://artifactory.yizhisec.com/artifactory/yizhisec-release/oem/release/2.1.0-std/oem_heishuimeng.tar.gz",
OEMDir: "oem_heishuimeng",

View File

@@ -33,16 +33,16 @@ server {
client_max_body_size 50M;
location /api/v2_2 {
proxy_pass http://u-api-service;
}
location /api/v1/pkg/archive {
proxy_pass http://u-api-service/api/v2_2/client/download/check;
proxy_pass http://client-win-service.hsv2/api/v2_2/_client/win/check.json;
}
location /api/v1/version {
proxy_pass http://u-api-service/api/v2_2/client/version;
location /api/v1/pkg/config/setup {
proxy_pass http://app-helper-service.hsv2/api/v2_2/_client/win/config/setup.json;
}
location /api/v2_2/_client/win {
proxy_pass http://app-helper-service.hsv2;
}
location /api/ {
@@ -148,24 +148,25 @@ server {
client_max_body_size 50M;
location /api/v2_2 {
proxy_pass http://u-api-service;
}
location /api/v1/pkg/archive {
proxy_pass http://u-api-service/api/v2_2/client/download/check;
location /api/v1/pkg/config/setup {
proxy_pass http://app-helper-service.hsv2/api/v2_2/_client/win/config/setup.json;
}
location /api/v1/pkg/archive/version {
proxy_pass http://u-api-service/api/v2_2/client/version;
proxy_pass http://app-helper-service.hsv2/api/v2_2/_client/win/old/version.json;
}
location /api/v1/version {
proxy_pass http://u-api-service/api/v2_2/client/version;
location /api/v1/pkg/archive {
proxy_pass http://client-win-service.hsv2/api/v2_2/_client/win/check.json;
}
location /static/config/rc.json {
proxy_pass http://u-api-service/api/v2_2/client/rc/json?os=win;
proxy_pass http://client-win-service.hsv2/api/v2_2/_client/win/rc.json;
}
location /api/v2_2/_client/win {
proxy_pass http://app-helper-service.hsv2;
}
# location = /api/v1/version {

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/ {
auth_request /token_auth;

View File

@@ -32,19 +32,27 @@ server {
}
location /api/my/sys/client/installer {
proxy_pass http://u-api-service/api/v2_2/client/download/list;
proxy_pass http://app-helper-service.hsv2/api/v2_2/client/download/list;
}
location /api/system/version {
proxy_pass http://u-api-service/api/v2_2/system/version;
proxy_pass http://app-helper-service.hsv2/api/v2_2/system/version;
}
location /api/v2_2/client {
proxy_pass http://u-api-service;
location /api/v2_2/system/elink {
proxy_pass http://app-helper-service.hsv2;
}
location /api/v2_2/system {
proxy_pass http://u-api-service;
location /api/v2_2/_client/win {
proxy_pass http://app-helper-service.hsv2;
}
location /api/v2_2/_client/mac{
proxy_pass http://client-mac-service;
}
location /api/v2_2/_client/linux{
proxy_pass http://client-linux-service;
}
location / {
@@ -70,23 +78,48 @@ server {
server_tokens off;
location / {
proxy_pass http://front-admin-service;
}
location /api/system/version {
proxy_pass http://u-api-service/api/v2_2/system/version;
proxy_pass http://app-helper-service/api/v2_2/system/version;
}
location /oem {
proxy_pass http://oem-service;
}
location /api/v2_2/system/elink {
proxy_pass http://app-helper-service.hsv2;
}
# location /wm/ {
# alias /data/wm/;
# expires 30d;
# add_header Cache-Control public;
# }
location /user/avatar/ {
proxy_pass http://app-helper-service.hsv2/api/v2_2/_obj/;
}
location / {
proxy_pass http://front-admin-service;
location /api/account/profile/avatar {
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 {
@@ -97,6 +130,22 @@ server {
proxy_read_timeout 300s;
}
location /api/v2_2/_client/win {
proxy_pass http://app-helper-service.hsv2;
}
location /api/v2_2/_client/mac{
proxy_pass http://client-mac-service;
}
location /api/v2_2/_client/linux{
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;
location /ws {
@@ -107,28 +156,11 @@ server {
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 {
auth_request /token_auth;
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 {
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>';

View File

@@ -71,6 +71,9 @@ var (
//go:embed yaml/app.nginx.yaml
YAMLAppNGINX string
//go:embed yaml/app.helper.yaml
YAMLAppHelper string
//go:embed yaml/client.pkg.yaml
YAMLClientPKG string

View File

@@ -0,0 +1,87 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-helper-sa
namespace: hsv2
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-helper-role
namespace: hsv2
rules:
- apiGroups: ["apps"]
resources: ["deployments", "deployments/status"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-helper-rolebinding
namespace: hsv2
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: app-helper-role
subjects:
- kind: ServiceAccount
name: app-helper-sa
namespace: hsv2
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-helper-deployment
namespace: hsv2
spec:
replicas: %d
selector:
matchLabels:
app: app-helper
template:
metadata:
labels:
app: app-helper
spec:
serviceAccountName: app-helper-sa
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: app-helper
containers:
- name: app-helper
image: hub.yizhisec.com/hsv2/app/helper:%s
imagePullPolicy: IfNotPresent
command:
- app_helper
- --debug
- --redis-host
- "redis-master.db-redis"
- --redis-password
- "HybridScope0xRed1s."
ports:
- containerPort: 80
volumeMounts:
- name: config-version
mountPath: /app/version
volumes:
- name: config-version
configMap:
name: config-version
---
apiVersion: v1
kind: Service
metadata:
name: app-helper-service
namespace: hsv2
spec:
selector:
app: app-helper
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP

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)
}
}