Compare commits

...

15 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
zhaoyupeng
eb87d6fbed feat: 🚛 完成了 client 资源创建 2025-12-31 18:58:52 +08:00
zhaoyupeng
0bcb138fd5 feat: 添加了 imager 工具 package
refactor: 将  images 的获取分散到各个组件里面
2025-12-29 23:01:42 +08:00
zhaoyupeng
c53c15fa8c chore: caddy config file(json)
nginx: proxy version api
2025-12-05 18:39:30 +08:00
zhaoyupeng
f4f3590aec feat(hsnet): add upsert.sh script for hs-net deployment automation
- Implement upsert.sh script to automate hs-net deployment steps
- Copy token file to /etc/yizhisec/token
- Detect local IP and update /etc/hosts accordingly
- Replace placeholder IPs in server.conf files
- Create required directories including /mnt/huge and workspace paths
- Copy configuration files and binaries based on CPU AVX support
- Copy lastVersion.txt to workspace
- Load hs-net container image using k0s ctr
- Install, enable, and start hs-net systemd service
- Write upsert.sh file to workdir with appropriate permissions
2025-12-01 17:47:58 +08:00
zhaoyupeng
3a29e6221d feat: add hs-net make
wip: hs-net upsert.sh
2025-11-28 19:39:26 +08:00
zhaoyupeng
38def02bf4 feat(front): add front app build command and minio support
- Add new command "front" with flags for replica count and vendor
- Implement front app build logic in maker.AppFront method
- Add minio to make command list
- Add minio and minio-init images to image list
- Change EMQX dependency path to "dependency/emqx"
- Update app OEM logic to use model.GetVendor for vendor info
- Fix app OEM download and rename logic with updated vendor fields
- Modify nginx deployment manifest to allow configurable replicas
- Update user app mysql address to mysql-cluster-mysql-master.db-mysql:3306
- Add server_license_init.conf generation script for configmap upsert
- Clean and reformat imports across several files
- Remove unused package files for make.mysql.go, make.redis.go, make.longhorn.go
2025-11-27 17:35:01 +08:00
zhaoyupeng
fdad0eb36c refactor: 整理结构和 maker 构建方式 2025-11-27 11:06:38 +08:00
zhaoyupeng
11523e3e48 feat: 🎉 complete maker nginx(app) 2025-11-26 22:47:00 +08:00
zhaoyupeng
4ec58ce4e5 feat: 🏡 make apps 2025-11-26 16:17:38 +08:00
zhaoyupeng
1d3c159c00 feat: add dep.proxy 2025-11-25 23:08:52 +08:00
143 changed files with 8824 additions and 414 deletions

7
.gitignore vendored
View File

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

View File

@@ -1,47 +0,0 @@
# AGENTS.md
This file provides guidance to Qoder (qoder.com) when working with code in this repository.
## Project Overview
This is a Go project (`yizhisec.com/hsv2/forge`) using Go 1.25.2.
## Development Commands
### Build
```bash
go build ./...
```
### Run Tests
```bash
go test ./...
```
### Run Single Test
```bash
go test -run TestName ./path/to/package
```
### Lint
```bash
go vet ./...
```
### Format Code
```bash
go fmt ./...
```
### Tidy Dependencies
```bash
go mod tidy
```
## Architecture
The codebase structure has not yet been established. As the project grows, update this section with:
- Main package organization
- Key architectural patterns
- Important design decisions
- Module relationships

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";`

BIN
forge

Binary file not shown.

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

@@ -0,0 +1,3 @@
FROM docker-mirror.yizhisec.com/library/nginx:1.29.4-alpine3.23
RUN apk add curl wget tzdata unzip

View File

@@ -1,9 +1,8 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/cmd/installcmd"
)
func installCmd() *cobra.Command {
@@ -11,15 +10,12 @@ func installCmd() *cobra.Command {
Use: "install",
Short: "Install the project",
Long: `Install the built project to the specified location.`,
RunE: func(cmd *cobra.Command, args []string) error {
return runInstall(args)
},
}
_cmd.AddCommand(
installcmd.Check(),
installcmd.Prepare(),
)
return _cmd
}
func runInstall(args []string) error {
fmt.Println("Running install command...")
return nil
}

View File

@@ -0,0 +1,47 @@
package installcmd
import (
"os"
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/installer"
)
func Check() *cobra.Command {
var (
workdir string
ignoreDisk bool
ignoreMemory bool
ignoreCPU bool
)
_cmd := &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)
return _installer.HardwareCheck(
cmd.Context(),
installer.WithIgnoreDiskCheck(ignoreDisk),
installer.WithIgnoreMemoryCheck(ignoreMemory),
installer.WithIgnoreCPUCheck(ignoreCPU),
)
},
}
_cmd.Flags().StringVar(&workdir, "workdir", "/root/hs-installation", "Working directory")
return _cmd
}

View File

@@ -0,0 +1,31 @@
package installcmd
import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/installer"
)
func K0s() *cobra.Command {
var (
workdir 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)
return _installer.K0s(cmd.Context())
},
}
_cmd.PersistentFlags().StringVar(&workdir, "workdir", "/root/hs-installation", "working directory")
return _cmd
}

View File

@@ -0,0 +1,32 @@
package installcmd
import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/installer"
)
func Prepare() *cobra.Command {
var (
workdir 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)
_installer.Prepare(cmd.Context())
return nil
},
}
_cmd.Flags().StringVar(&workdir, "workdir", "/root/hs-installation", "Working directory")
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 {
@@ -35,7 +35,7 @@ func makeCmd() *cobra.Command {
return nil
}
mk := maker.NewMaker()
mk := maker.NewMaker(opt.Cfg.Make.Dir)
if err = mk.DependencyCheck(cmd.Context()); err != nil {
return err
}
@@ -50,20 +50,26 @@ func makeCmd() *cobra.Command {
_cmd.PersistentFlags().StringVar(&opt.Cfg.Make.Dir, "dir", "/root/hsv2-installation", "make base directory")
_cmd.AddCommand(
makecmd.ALL(),
makecmd.Images(),
makecmd.Binaries(),
makecmd.Debs(),
makecmd.K0s(),
makecmd.Flannel(),
makecmd.Longhorn(),
makecmd.Mysql(),
makecmd.Redis(),
makecmd.ES(),
makecmd.EMQX(),
makecmd.Minio(),
makecmd.Yosguard(),
makecmd.Registry(),
makecmd.LessDNS(),
makecmd.HSNet(),
makecmd.ConfigMap(),
makecmd.Proxy(),
makecmd.Seafile(),
makecmd.App(),
makecmd.Apps(),
makecmd.Client(),
)
return _cmd

139
internal/cmd/makecmd/all.go Normal file
View File

@@ -0,0 +1,139 @@
package makecmd
import (
"fmt"
"time"
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
func ALL() *cobra.Command {
var (
_workdir string
_vendor string
_version string
_replica int
)
_cmd := &cobra.Command{
Use: "all",
Short: "Make all",
RunE: func(cmd *cobra.Command, args []string) error {
var (
err error
mk = maker.NewMaker(_workdir)
)
if err = mk.K0s(cmd.Context()); err != nil {
return err
}
if err = mk.Registry(cmd.Context(), "50Gi"); err != nil {
return err
}
if err = mk.Flannel(cmd.Context(), "host-gw"); err != nil {
return err
}
if err = mk.Longhorn(cmd.Context(), 2); err != nil {
return err
}
if err = mk.MySQL(cmd.Context()); err != nil {
return err
}
if err = mk.Redis(cmd.Context()); err != nil {
return err
}
if err = mk.Elastic(cmd.Context()); err != nil {
return err
}
if err = mk.EMQX(cmd.Context()); err != nil {
return err
}
if err = mk.Minio(cmd.Context(), "100Gi"); err != nil {
return err
}
if err = mk.Yosguard(cmd.Context()); err != nil {
return err
}
if err = mk.LessDNS(cmd.Context()); err != nil {
return err
}
if err = mk.HSNet(cmd.Context()); err != nil {
return err
}
if err = mk.ConfigMap(cmd.Context()); err != nil {
return err
}
if err = mk.Proxy(cmd.Context()); err != nil {
return err
}
if err = mk.Seafile(cmd.Context()); err != nil {
return err
}
if err = mk.Proxy(cmd.Context()); err != nil {
return err
}
if err = mk.Seafile(cmd.Context()); err != nil {
return err
}
if err = mk.AppOEM(cmd.Context(), _version, _vendor, _replica); err != nil {
return err
}
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.AppFront(cmd.Context(), _version, _vendor, _replica); err != nil {
return err
}
if err = mk.AppMie(cmd.Context(), _version, _replica); err != nil {
return err
}
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
}
return nil
},
}
_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,7 +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 {
@@ -12,7 +19,272 @@ func App() *cobra.Command {
_cmd.AddCommand(
appUser(),
appClient(),
appGateway(),
appMie(),
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
_version string
)
_cmd := &cobra.Command{
Use: "user",
Short: "Make User App",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
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
_version string
)
_cmd := &cobra.Command{
Use: "client",
Short: "Make Client App",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
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
_version string
)
_cmd := &cobra.Command{
Use: "gateway",
Short: "Make Gateway App",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
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
_version string
)
_cmd := &cobra.Command{
Use: "mie",
Short: "Make Mie App",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
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
_version string
)
_cmd := &cobra.Command{
Use: "oem",
Short: "Make OEM App",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
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
_version string
)
_cmd := &cobra.Command{
Use: "front",
Short: "Make Front App",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
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
}
func appNginx() *cobra.Command {
var (
replica int
vendor string
_version string
_include_image bool
)
_cmd := &cobra.Command{
Use: "nginx",
Short: "Make Nginx App",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.AppNginx(cmd.Context(), _version, vendor, replica, _include_image)
},
}
_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 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

@@ -1,25 +0,0 @@
package makecmd
import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
)
func appUser() *cobra.Command {
var (
replica int
)
_cmd := &cobra.Command{
Use: "user",
Short: "Make User App",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker()
return mk.AppUser(cmd.Context(), replica)
},
}
_cmd.Flags().IntVar(&replica, "replica-count", 2, "Replica count")
return _cmd
}

View File

@@ -3,17 +3,16 @@ package makecmd
import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
func Binaries() *cobra.Command {
func K0s() *cobra.Command {
cmd := &cobra.Command{
Use: "binaries",
Aliases: []string{"bin", "B"},
Short: "Build binary files",
Long: `Build all required binary files for the project.`,
Use: "k0s",
Short: "make k0s files(binary, images)",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker()
return mk.Binary(cmd.Context())
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.K0s(cmd.Context())
},
}

View File

@@ -0,0 +1,298 @@
package makecmd
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"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"
)
func Client() *cobra.Command {
_cmd := &cobra.Command{
Use: "client",
Short: "make client pkg",
}
_cmd.AddCommand(
clientMac(),
clientWin(),
clientLinux(),
clientBaseImage(),
)
return _cmd
}
func clientWin() *cobra.Command {
var (
_version string
_pkg bool
)
_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-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 (
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)
return mk.ClientPKG(
cmd.Context(),
"win",
version,
"/api/v2_2/_client/win",
maker.WithClientPkgMakePkg(_pkg),
maker.WithClientPKGDownload(ZIP_URL, "hs_client.zip"),
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 clientMac() *cobra.Command {
var (
_version string
_pkg bool
)
_cmd := &cobra.Command{
Use: "mac",
Aliases: []string{"macos"},
Short: "make client-mac 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-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 (
err error
version string
)
if version, err = clientVersion(VERSION_URL); err != nil {
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"),
)
},
}
_cmd.Flags().StringVar(&_version, "version", "", "手动指定版本")
_cmd.Flags().BoolVar(&_pkg, "pkg", false, "是否生成升级 pkg")
return _cmd
}
func clientVersion(_url string) (string, error) {
type Res struct {
Version string `json:"version"`
}
var (
err error
rr *http.Response
res Res
bs []byte
)
logger.Debug("clientVersion: getting client version with url = %s", _url)
if rr, err = tc.HttpClient().Get(_url); err != nil {
return "", err
}
defer rr.Body.Close()
if bs, err = io.ReadAll(rr.Body); err != nil {
return "", err
}
logger.Debug("clientVersion: got client version body raw: %s, url = %s", string(bs), _url)
if err = json.Unmarshal(bs, &res); err != nil {
return "", err
}
if res.Version == "" {
return "", fmt.Errorf("get version empty, url = %s", _url)
}
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

@@ -3,6 +3,7 @@ package makecmd
import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
func ConfigMap() *cobra.Command {
@@ -11,7 +12,7 @@ func ConfigMap() *cobra.Command {
Aliases: []string{"cm"},
Short: "构建 ConfigMap",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker()
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.ConfigMap(cmd.Context())
},
}

View File

@@ -1,21 +0,0 @@
package makecmd
import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
)
func Debs() *cobra.Command {
cmd := &cobra.Command{
Use: "debs",
Aliases: []string{"deb"},
Short: "Build Debian packages",
Long: `Build all required Debian packages for the project.`,
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker()
return mk.Deb(cmd.Context())
},
}
return cmd
}

View File

@@ -3,6 +3,7 @@ package makecmd
import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
func EMQX() *cobra.Command {
@@ -10,7 +11,7 @@ func EMQX() *cobra.Command {
Use: "emqx",
Short: "Make EMQX",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker()
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.EMQX(cmd.Context())
},
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
func ES() *cobra.Command {
@@ -18,7 +19,7 @@ func ES() *cobra.Command {
Use: "es",
Aliases: []string{"elastic", "elasticsearch"},
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker()
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.Elastic(
cmd.Context(),
maker.WithElasticMakeHelper(makeHelper),

View File

@@ -3,6 +3,7 @@ package makecmd
import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
func Flannel() *cobra.Command {
@@ -15,7 +16,7 @@ func Flannel() *cobra.Command {
Short: "Build Flannel resources",
Long: `Build and prepare Flannel network resources.`,
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker()
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.Flannel(cmd.Context(), _mode)
},
}

View File

@@ -3,6 +3,7 @@ package makecmd
import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
func HSNet() *cobra.Command {
@@ -11,7 +12,7 @@ func HSNet() *cobra.Command {
Short: "Build hs-net",
Long: `Build hs-net`,
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker()
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.HSNet(cmd.Context())
},
}

View File

@@ -21,7 +21,7 @@ func Images() *cobra.Command {
return err
}
mk := maker.NewMaker()
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.Images(cmd.Context())
},

View File

@@ -3,6 +3,7 @@ package makecmd
import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
func LessDNS() *cobra.Command {
@@ -11,7 +12,7 @@ func LessDNS() *cobra.Command {
Short: "Build lessdns",
Long: `Build lessdns`,
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker()
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.LessDNS(cmd.Context())
},
}

View File

@@ -3,6 +3,7 @@ package makecmd
import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
func Longhorn() *cobra.Command {
@@ -15,7 +16,7 @@ func Longhorn() *cobra.Command {
Short: "Build Longhorn resources",
Long: `Build and prepare Longhorn storage resources.`,
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker()
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.Longhorn(cmd.Context(), replicaCount)
},
}

View File

@@ -0,0 +1,109 @@
package makecmd
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
func Minio() *cobra.Command {
var (
storage int
)
_cmd := &cobra.Command{
Use: "minio",
Short: "Make Minio",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.Minio(cmd.Context(), fmt.Sprintf("%dGi", storage))
},
}
_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

@@ -5,6 +5,7 @@ import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
func Mysql() *cobra.Command {
@@ -23,7 +24,7 @@ func Mysql() *cobra.Command {
maker.WithMySQLStorage(fmt.Sprintf("%dGi", storage)),
}
mk := maker.NewMaker()
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.MySQL(cmd.Context(), opts...)
},
}

View File

@@ -0,0 +1,20 @@
package makecmd
import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
func Proxy() *cobra.Command {
_cmd := &cobra.Command{
Use: "proxy",
Short: "Make Proxy(by caddy)",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.Proxy(cmd.Context())
},
}
return _cmd
}

View File

@@ -1,15 +1,18 @@
package makecmd
import (
"fmt"
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
func Redis() *cobra.Command {
var (
replicas int
password string
storage string
storage int
)
_cmd := &cobra.Command{
@@ -17,19 +20,19 @@ func Redis() *cobra.Command {
Short: "Build Redis resources",
Long: `Build and prepare Redis cache resources.`,
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker()
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.Redis(
cmd.Context(),
maker.WithRedisReplicaCount(replicas),
maker.WithRedisPassword(password),
maker.WithRedisStorage(storage),
maker.WithRedisStorage(fmt.Sprintf("%dGi", storage)),
)
},
}
_cmd.Flags().IntVar(&replicas, "replica-count", 2, "Redis 副本数")
_cmd.Flags().StringVar(&password, "password", "", "Redis 密码")
_cmd.Flags().StringVar(&storage, "storage-size", "5Gi", "Redis 存储大小(如: 5Gi)")
_cmd.Flags().IntVar(&storage, "storage-size", 5, "Redis 存储大小(单位Gi)如: 5")
return _cmd
}

View File

@@ -0,0 +1,28 @@
package makecmd
import (
"fmt"
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
func Registry() *cobra.Command {
var (
size int
)
_cmd := &cobra.Command{
Use: "registry",
Short: "Make registry dependency",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.Registry(cmd.Context(), fmt.Sprintf("%dGi", size))
},
}
_cmd.Flags().IntVar(&size, "storage-size", 50, "Redis 存储大小(单位Gi)如: 100")
return _cmd
}

View File

@@ -0,0 +1,27 @@
package makecmd
import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
func Seafile() *cobra.Command {
var (
storage int
)
_cmd := &cobra.Command{
Use: "seafile",
Short: "make seafile dependency",
RunE: func(cmd *cobra.Command, args []string) error {
mk := maker.NewMaker(opt.Cfg.Make.Dir)
return mk.Seafile(cmd.Context())
},
}
_cmd.Flags().IntVar(&storage, "storage-size", 50, "指定 seafile 空间大小(单位GB)")
return _cmd
}

View File

@@ -3,18 +3,25 @@ package makecmd
import (
"github.com/spf13/cobra"
"yizhisec.com/hsv2/forge/internal/controller/maker"
"yizhisec.com/hsv2/forge/internal/opt"
)
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()
return mk.Yosguard(cmd.Context())
mk := maker.NewMaker(opt.Cfg.Make.Dir)
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

@@ -0,0 +1,413 @@
package installer
import (
"bufio"
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/syscheck"
"yizhisec.com/hsv2/forge/pkg/tool/human"
)
type CheckOption func(*checkOpt)
type checkOpt struct {
ignoreDisk bool
ignoreMemory bool
ignoreCPU bool
noWriteDown bool
}
func WithIgnoreDiskCheck(ignore bool) CheckOption {
return func(o *checkOpt) {
o.ignoreDisk = ignore
}
}
func WithIgnoreMemoryCheck(ignore bool) CheckOption {
return func(o *checkOpt) {
o.ignoreMemory = ignore
}
}
func WithIgnoreCPUCheck(ignore bool) CheckOption {
return func(o *checkOpt) {
o.ignoreCPU = ignore
}
}
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 (
now = time.Now()
o = &checkOpt{}
)
for _, fn := range opts {
fn(o)
}
if h.Timestamp < now.AddDate(0, 0, -1).UnixMilli() {
return fmt.Errorf("上次检测结果已失效, 请重新检测")
}
if o.ignoreDisk || h.DiskSize+h.DiskReadSpeed+h.DiskWriteSpeed == -3 {
goto PASS_MEMORY
}
if h.DiskSize < DISK_SIZE {
return fmt.Errorf("磁盘大小不满足, 需要: %s, 实际: %s", human.Size(DISK_SIZE), human.Size(h.DiskSize))
}
if h.DiskReadSpeed < DISK_READ_SPEED {
return fmt.Errorf("磁盘读速度不满足, 需要: %s, 实际: %s", human.Size(DISK_READ_SPEED), human.Size(h.DiskReadSpeed))
}
if h.DiskWriteSpeed < DISK_WRITE_SPEED {
return fmt.Errorf("磁盘写速度不满足, 需要: %s, 实际: %s", human.Size(DISK_WRITE_SPEED), human.Size(h.DiskWriteSpeed))
}
PASS_MEMORY:
if o.ignoreMemory || h.MemorySize+h.MemoryReadSpeed+h.MemoryWriteSpeed == -3 {
goto PASS_CPU
}
if h.MemorySize < MEMORY_SIZE {
return fmt.Errorf("内存大小不满足, 需要: %s, 实际: %s", human.Size(MEMORY_SIZE), human.Size(h.MemorySize))
}
if h.MemoryReadSpeed < MEMORY_READ_SPEED {
return fmt.Errorf("内存读速度不满足, 需要: %s, 实际: %s", human.Size(MEMORY_READ_SPEED), human.Size(h.MemoryReadSpeed))
}
if h.MemoryWriteSpeed < MEMORY_WRITE_SPEED {
return fmt.Errorf("内存写速度不满足, 需要: %s, 实际: %s", human.Size(MEMORY_WRITE_SPEED), human.Size(h.MemoryWriteSpeed))
}
PASS_CPU:
if o.ignoreCPU || h.CPUCores+h.CPUFrequency == -2 {
goto END
}
if h.CPUCores < CPUCORES {
return fmt.Errorf("cpu 核心数不满足, 需要: %d, 实际: %d", CPUCORES, h.CPUCores)
}
if h.CPUFrequency < CPU_FREQUENCY {
return fmt.Errorf("cpu 频率不满足, 需要: %s, 实际: %s", human.Size(CPU_FREQUENCY), human.Size(h.CPUFrequency))
}
if CPU_NEED_V2 && !h.CPUIsX86V2 {
return fmt.Errorf("cpu 不支持aes")
}
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

@@ -1,8 +1,36 @@
package controller
package installer
import (
"context"
"fmt"
"os/exec"
"strings"
"github.com/samber/lo"
)
type installer struct {
workdir string
}
func NewInstaller() *installer {
return &installer{}
// ExecuteCommand implements syscheck.CommandExecutor interface
func (i *installer) ExecuteCommand(ctx context.Context, cmds ...string) (string, error) {
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))
}
return string(output), nil
}
func NewInstaller(workdir string) *installer {
return &installer{workdir: workdir}
}

View File

@@ -0,0 +1,41 @@
package installer
import (
"context"
"github.com/samber/lo"
)
type K0sOpt func(*k0sOpt)
type k0sOpt struct {
Type string // controller, worker
controllerAsWorker bool
WorkerTokenFile string
}
func WithK0sType(t string) K0sOpt {
types := []string{"controller", "worker"}
return func(o *k0sOpt) {
if lo.Contains(types, t) {
o.Type = t
}
}
}
func WithK0sControllerAsWorker() K0sOpt {
return func(o *k0sOpt) {
o.controllerAsWorker = true
}
}
func WithK0sWorkerTokenFile(filename string) K0sOpt {
return func(o *k0sOpt) {
if filename != "" {
o.WorkerTokenFile = filename
}
}
}
func (i *installer) K0s(ctx context.Context, opts ...K0sOpt) error {
panic("plz impl")
}

View File

@@ -0,0 +1,165 @@
package installer
import (
"context"
"fmt"
"yizhisec.com/hsv2/forge/pkg/logger"
)
func (i *installer) Prepare(ctx context.Context) error {
var (
err error
)
logger.Info("☑️ installer.Prepare: Starting system preparation...")
// 1. Set timezone to Asia/Shanghai
logger.Info("☑️ installer.Prepare: Setting timezone to Asia/Shanghai...")
if err = i.setTimezone(ctx); err != nil {
logger.Debug("❌ installer.Prepare: Failed to set timezone: %v", err)
return fmt.Errorf("failed to set timezone: %w", err)
}
logger.Info("✅ installer.Prepare: Timezone set successfully")
// 2. Disable swap
logger.Info("☑️ installer.Prepare: Disabling swap...")
if err = i.disableSwap(ctx); err != nil {
logger.Debug("❌ installer.Prepare: Failed to disable swap: %v", err)
return fmt.Errorf("failed to disable swap: %w", err)
}
logger.Info("✅ installer.Prepare: Swap disabled successfully")
// 3. Load module: iscsi_tcp
logger.Info("☑️ installer.Prepare: Loading kernel module iscsi_tcp...")
if err = i.loadKernelModule(ctx, "iscsi_tcp"); err != nil {
logger.Debug("❌ installer.Prepare: Failed to load iscsi_tcp module: %v", err)
return fmt.Errorf("failed to load iscsi_tcp module: %w", err)
}
logger.Info("✅ installer.Prepare: iscsi_tcp module loaded successfully")
// 4. Load module: br_netfilter
logger.Info("☑️ installer.Prepare: Loading kernel module br_netfilter...")
if err = i.loadKernelModule(ctx, "br_netfilter"); err != nil {
logger.Debug("❌ installer.Prepare: Failed to load br_netfilter module: %v", err)
return fmt.Errorf("failed to load br_netfilter module: %w", err)
}
logger.Info("✅ installer.Prepare: br_netfilter module loaded successfully")
// 5. Apply sysctl settings
logger.Info("☑️ installer.Prepare: Applying sysctl settings...")
if err = i.applySysctlSettings(ctx); err != nil {
logger.Debug("❌ installer.Prepare: Failed to apply sysctl settings: %v", err)
return fmt.Errorf("failed to apply sysctl settings: %w", err)
}
logger.Info("✅ installer.Prepare: Sysctl settings applied successfully")
logger.Info("✅ installer.Prepare: System preparation completed successfully!")
return nil
}
// 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
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
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
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
}
// 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
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
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
}
// 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
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)
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
}
// 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
net.ipv4.ip_forward = 1
net.ipv4.conf.all.forwarding = 1
net.ipv6.conf.all.forwarding = 1
vm.swappiness = 0
vm.overcommit_memory = 1
vm.panic_on_oom = 0
fs.file-max = 1000000
fs.inotify.max_user_watches = 2099999999
fs.inotify.max_user_instances = 2099999999
fs.inotify.max_queued_events = 2099999999
net.ipv4.neigh.default.gc_thresh1 = 1024
net.ipv4.neigh.default.gc_thresh2 = 4096
net.ipv4.neigh.default.gc_thresh3 = 8192
`
// Write sysctl config file
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
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

@@ -0,0 +1,5 @@
package installer
func (i *installer) Redis() error {
return nil
}

View File

@@ -0,0 +1,7 @@
package installer
import "context"
func (i *installer) YosGuard(ctx context.Context) error {
return nil
}

View File

@@ -0,0 +1,262 @@
package maker
import (
"context"
"fmt"
"os"
"path/filepath"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource"
)
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
BackupSeafile:
Host: hs-resource-server
Port: 19980
ClientDir:
CompatibleAppFile: /yizhisec/hs_nginx/resource/compatible_apps.csv
OEMFilePath: /yizhisec/hs_nginx/data/443/oem/data.json
StorageDir: /data/storage/client_pkg
WindowsClient:
App7zDir: app7z_0
Dir: windows
Database:
Elastic:
Address: http://es-service.db-es:9200
IPDBFile: /etc/client_server/ipv4.ipdb
Mysql:
Address: mysql.db-mysql:3306
DBName: mie
Password: L0hMysql.
UserName: root
Redis:
Address: redis-master.db-redis:6379
Password: HybridScope0xRed1s.
DisabledFeatureFilePath: /etc/yizhisec/disabled_features
ExportWithBlindWatermark: 1
ExternalOA:
Host: ""
Port: 0
ExternalOASecret:
HsID: ""
HsSecret: ""
GatewayLinkPort: 23209
Key:
Token: TtKVnSzEHO3jRv/GWg3f5k3H1OVfMnPZ1Ke9E6MSCXk=
LicensePubKey: /etc/yizhisec/license/pub_key
Log:
Dir: ./log
Level: 1
Name: client_server
MQTTServer:
Host: emqx-service.db-emqx
Port: 1883
NginxEnvFilePath: /yizhisec/hs_nginx/.env
Pipelines:
- processor:
Script: diA9IGpzb24uZGVjb2RlKGV2ZW50KQpjID0ganNvbi5kZWNvZGUoR2V0UmVzb3VyY2VDb25maWcoImNvbnRyb2xsZXIiKSkKY3B1dCA9IGNbImhhcmR3YXJlX3Jlc291cmNlX3RocmVob2xkIl1bImNwdSJdCm1lbXQgPSBjWyJoYXJkd2FyZV9yZXNvdXJjZV90aHJlaG9sZCJdWyJtZW1vcnkiXQpkaXNrdCA9IGNbImhhcmR3YXJlX3Jlc291cmNlX3RocmVob2xkIl1bImRpc2siXQpzd2l0Y2ggPSBjWyJzd2l0Y2giXQpkZWJ1Z19pbmZvID0ge30KZGVmIGNoZWNrKGNwdSwgbWVtb3J5LCBkaXNrKToKICBpZiBzd2l0Y2ggPT0gRmFsc2U6CiAgICByZXR1cm4KICBtc2cgPSAiIgogIGV2dCA9IHt9CiAgaWYgY3B1ID4gY3B1dDoKICAgICAgbXNnID0gIkNQVeWNoOeUqOi2hei/hyIgKyBzdHIoY3B1dCkgKyAiJSIKICBpZiBtZW1vcnkgPiBtZW10OgogICAgaWYgbGVuKG1zZykgIT0gMDoKICAgICAgbXNnID0gbXNnICsgIu+8jCAiCiAgICBtc2cgPSBtc2cgKyAi5YaF5a2Y5Y2g55So6LaF6L+HIiArIHN0cihtZW10KSArICIlIgogIGlmIGRpc2sgPiBkaXNrdDoKICAgIGlmIGxlbihtc2cpICE9IDA6CiAgICAgIG1zZyA9IG1zZyArICLvvIwgIgogICAgbXNnID0gbXNnICsgIuejgeebmOWNoOeUqOi2hei/hyIgKyBzdHIoZGlza3QpICsgIiUiCiAgaWYgbGVuKG1zZykgIT0gMDoKICAgIG1zZyA9IG1zZyArICLjgILor7flj4rml7bmn6XnnIvmnI3liqHlmajnmoTkvb/nlKjmg4XlhrXmiJbph43mlrDosIPmlbTpmIjlgLzjgIIiCiAgaWYgbGVuKG1zZykgPT0gMDoKICAgIHJldHVybiBldnQKICBpZ25vcmUgPSBDb21wYXJlQW5kU2V0KCJfcGlwZWxpbmVfbHN0X2N0IiwgR2V0VGltZVNlYygpLCA2MCo2MCkKICBpZiBpZ25vcmUgPT0gMDoKICAgIGRlYnVnX2luZm9bImluZm8iXSA9ICJyYXRlIGxpbWl0IGlnbm9yZSIKICAgIHJldHVybgogIG1zZyA9ICLnrZbnlaXmjqfliLblmajnmoQiICsgbXNnCiAgZXZ0WyJkZXRhaWwiXSA9IG1zZwogIGV2dFsiZG9tYWluX2lkIl0gPSAwCiAgZXZ0WyJtZXNzYWdlX3R5cGUiXSA9IDEwMQogIGV2dFsiY3JlYXRlX3RpbWUiXSA9IEdldFRpbWVTZWMoKQogIHJldHVybiBldnQKCm91dHB1dCA9IGpzb24uZW5jb2RlKGNoZWNrKHZbImNwdSJdWyJwZXJjZW50Il0sIHZbIm1lbSJdWyJwZXJjZW50Il0sIHZbImRpc2siXVsicGVyY2VudCJdKSk=
Type: starlark
sink:
Cols:
- create_time
- detail
- domain_id
- message_type
DB: mie
Host: mysql.db-mysql:3306
Pwd: L0hMysql.
Table: message_content
Type: mysql_sink
User: root
source:
Host: redis-master.db-redis:6379
Key: evt_server_state:controller
Pwd: HybridScope0xRed1s.
Tick: 3
Type: redis_source
- processor:
Script: diA9IGpzb24uZGVjb2RlKGV2ZW50KQpjID0ganNvbi5kZWNvZGUoR2V0UmVzb3VyY2VDb25maWcoImdhdGV3YXkiKSkKY3B1dCA9IGNbImhhcmR3YXJlX3Jlc291cmNlX3RocmVob2xkIl1bImNwdSJdCm1lbXQgPSBjWyJoYXJkd2FyZV9yZXNvdXJjZV90aHJlaG9sZCJdWyJtZW1vcnkiXQpkaXNrdCA9IGNbImhhcmR3YXJlX3Jlc291cmNlX3RocmVob2xkIl1bImRpc2siXQpzd2l0Y2ggPSBjWyJzd2l0Y2giXQpkZWJ1Z19pbmZvID0ge30KZGVmIGNoZWNrKCk6CiAgaWYgc3dpdGNoID09IEZhbHNlOgogICAgcmV0dXJuCiAga3MgPSB2LmtleXMoKQogIGlmIGxlbihrcykgPiAwOgogICAgayA9IGtzWzBdCiAgY3B1ID0gdltrXVsiY3B1Il0KICBtZW0gPSB2W2tdWyJtZW0iXQogIGRpc2sgPSB2W2tdWyJkaXNrIl0KICBtc2cgPSAiIgogIGV2dCA9IHt9CiAgaWYgY3B1ID4gY3B1dDoKICAgICAgbXNnID0gIkNQVeWNoOeUqOi2hei/hyIgKyBzdHIoY3B1dCkgKyAiJSIKICBpZiBtZW0gPiBtZW10OgogICAgaWYgbGVuKG1zZykgIT0gMDoKICAgICAgbXNnID0gbXNnICsgIu+8jCAiCiAgICBtc2cgPSBtc2cgKyAi5YaF5a2Y5Y2g55So6LaF6L+HIiArIHN0cihtZW10KSArICIlIgogIGlmIGRpc2sgPiBkaXNrdDoKICAgIGlmIGxlbihtc2cpICE9IDA6CiAgICAgIG1zZyA9IG1zZyArICLvvIwgIgogICAgbXNnID0gbXNnICsgIuejgeebmOWNoOeUqOi2hei/hyIgKyBzdHIoZGlza3QpICsgIiUiCiAgaWYgbGVuKG1zZykgIT0gMDoKICAgIG1zZyA9IG1zZyArICLjgILor7flj4rml7bmn6XnnIvmnI3liqHlmajnmoTkvb/nlKjmg4XlhrXmiJbph43mlrDosIPmlbTpmIjlgLzjgIIiCiAgaWYgbGVuKG1zZykgPT0gMDoKICAgIHJldHVybiBldnQKICBpZ25vcmUgPSBDb21wYXJlQW5kU2V0KCJfcGlwZWxpbmVfbHN0X2d0IiwgR2V0VGltZVNlYygpLCA2MCo2MCkKICBpZiBpZ25vcmUgPT0gMDoKICAgIGRlYnVnX2luZm9bImluZm8iXSA9ICJyYXRlIGxpbWl0IGlnbm9yZSIKICAgIHJldHVybgogIG1zZyA9ICLnvZHlhbPnmoQiICsgbXNnCiAgZXZ0WyJkZXRhaWwiXSA9IG1zZwogIGV2dFsiZG9tYWluX2lkIl0gPSAwCiAgZXZ0WyJtZXNzYWdlX3R5cGUiXSA9IDEwMgogIGV2dFsiY3JlYXRlX3RpbWUiXSA9IEdldFRpbWVTZWMoKQogIHJldHVybiBldnQKCm91dHB1dCA9IGpzb24uZW5jb2RlKGNoZWNrKCkp
Type: starlark
sink:
Cols:
- create_time
- detail
- domain_id
- message_type
DB: mie
Host: mysql.db-mysql:3306
Pwd: L0hMysql.
Table: message_content
Type: mysql_sink
User: root
source:
Host: redis-master.db-redis:6379
Key: evt_server_state:gateway
Pwd: HybridScope0xRed1s.
Tick: 3
Type: redis_source
- processor:
Script: diA9IGpzb24uZGVjb2RlKGV2ZW50KQpjID0ganNvbi5kZWNvZGUoR2V0TGljZW5zZUNvbmZpZygibGljZW5zZSIpKQpsZWZ0ID0gY1sibGljZW5zZV90aHJlaG9sZCJdWyJyZW1haW5pbmdfZGF5Il0Kc3dpdGNoID0gY1sic3dpdGNoIl0KZGVidWdfaW5mbyA9IHt9CmRlZiBjaGVjaygpOgogIGlmIHN3aXRjaCA9PSBGYWxzZToKICAgIHJldHVybgogIAogIGV0ID0gdlsiZXhwaXJlX3RpbWUiXQogIGV2dCA9IHt9CiAgY3VyID0gR2V0VGltZVNlYygpCiAgaWYgZXQgPCAoY3VyICsgbGVmdCAqIDg2NDAwKToKICAgIGlnbm9yZSA9IENvbXBhcmVBbmRTZXQoIl9waXBlbGluZV9sc3RfbCIsIEdldFRpbWVTZWMoKSwgNjAqNjAqMjQpCiAgICBpZiBpZ25vcmUgPT0gMDoKICAgICAgZGVidWdfaW5mb1siaW5mbyJdID0gInJhdGUgbGltaXQgaWdub3JlIgogICAgICByZXR1cm4KICAgIGV2dFsiZGV0YWlsIl0gPSAi5Lqn5ZOB5o6I5p2D5Ymp5L2Z5aSp5pWw5bCP5LqOIiArIHN0cihsZWZ0KSArICLlpKnjgILor7flj4rml7bmn6XnnIvkvb/nlKjmg4XlhrXjgIHph43mlrDosIPmlbTpmIjlgLzmiJbogZTns7vnrqHnkIblkZjmt7vliqDmjojmnYPjgIIiCiAgICBldnRbImRvbWFpbl9pZCJdID0gMAogICAgZXZ0WyJtZXNzYWdlX3R5cGUiXSA9IDIwMgogICAgZXZ0WyJjcmVhdGVfdGltZSJdID0gR2V0VGltZVNlYygpCiAgICByZXR1cm4gZXZ0Cm91dHB1dCA9IGpzb24uZW5jb2RlKGNoZWNrKCkp
Type: starlark
sink:
Cols:
- create_time
- detail
- domain_id
- message_type
DB: mie
Host: mysql.db-mysql:3306
Pwd: L0hMysql.
Table: message_content
Type: mysql_sink
User: root
source:
Host: redis-master.db-redis:6379
Key: license_state_cache:expire
Pwd: HybridScope0xRed1s.
Tick: 3
Type: redis_source
- processor:
Script: diA9IGpzb24uZGVjb2RlKGV2ZW50KQpjID0ganNvbi5kZWNvZGUoR2V0TGljZW5zZUNvbmZpZygibGljZW5zZV9kZXZpY2UiKSkKY2wgPSBjWyJsaWNlbnNlX2RldmljZV90aHJlaG9sZCJdWyJjbGllbnRfbGVmdCJdCm1sID0gY1sibGljZW5zZV9kZXZpY2VfdGhyZWhvbGQiXVsibW9iaWxlX2xlZnQiXQptYyA9IHZbIm1heF9jbGllbnRfY291bnQiXQptYiA9IHZbIm1heF9tb2JpbGVfY2xpZW50X2NvdW50Il0KY2MgPSB2WyJjdXJyZW50X2NsaWVudF9jb3VudCJdCmNtID0gdlsiY3VycmVudF9tb2JpbGVfY2xpZW50X2NvdW50Il0Kc3dpdGNoID0gY1sic3dpdGNoIl0KZGVidWdfaW5mbyA9IHt9CmRlZiBjaGVjaygpOgogIGlmIHN3aXRjaCA9PSBGYWxzZToKICAgIHJldHVybgogIGV2dCA9IHt9CiAgbXNnID0gIiIKICBpZiAobWMtY2MpIDwgY2w6CiAgICBtc2cgPSBtc2cgKyAi5a6i5oi356uv5L2/55So5pWw6YeP5bCR5LqOIitzdHIoY2wpKyLkuKoiCiAgaWYgbGVuKG1zZykgPiAwOgogICAgbXNnID0gbXNnICsgIu+8jCIKICBpZiAobWItY20pIDwgbWw6CiAgICBtc2cgPSBtc2cgKyAi56e75Yqo56uv5L2/55So5pWw6YeP5bCR5LqOIitzdHIobWwpKyLkuKrjgIIiCiAgaWYgbGVuKG1zZykgPiAwOgogICAgaWdub3JlID0gQ29tcGFyZUFuZFNldCgiX3BpcGVsaW5lX2xzdF9sZCIsIEdldFRpbWVTZWMoKSwgNjAqNjApCiAgICBpZiBpZ25vcmUgPT0gMDoKICAgICAgZGVidWdfaW5mb1siaW5mbyJdID0gInJhdGUgbGltaXQgaWdub3JlIgogICAgICByZXR1cm4KICAgIGV2dFsiZGV0YWlsIl0gPSBtc2cgKyAi6K+35Y+K5pe25p+l55yL5L2/55So5oOF5Ya144CB6YeN5paw6LCD5pW06ZiI5YC85oiW6IGU57O7566h55CG5ZGY5re75Yqg5o6I5p2D44CCIgogICAgZXZ0WyJkb21haW5faWQiXSA9IDAKICAgIGV2dFsibWVzc2FnZV90eXBlIl0gPSAyMDIKICAgIGV2dFsiY3JlYXRlX3RpbWUiXSA9IEdldFRpbWVTZWMoKQogICAgcmV0dXJuIGV2dApvdXRwdXQgPSBqc29uLmVuY29kZShjaGVjaygpKQ==
Type: starlark
sink:
Cols:
- create_time
- detail
- domain_id
- message_type
DB: mie
Host: mysql.db-mysql:3306
Pwd: L0hMysql.
Table: message_content
Type: mysql_sink
User: root
source:
Host: redis-master.db-redis:6379
Key: license_state_cache:online
Pwd: HybridScope0xRed1s.
Tick: 3
Type: redis_source
- processor:
Script: diA9IGpzb24uZGVjb2RlKGV2ZW50KQpkZWJ1Z19pbmZvID0ge30KZGVmIGNoZWNrKCk6CiAgZXZ0ID0ge30KICBtc2cgPSAiIgogIAogIGRpZCA9IHZbImRvbWFpbl9pZCJdCiAgdWlkID0gdlsidXNlcl9pZCJdCiAgdW5hbWUgPSBHZXRVc2VyTmFtZSh1aWQpCiAgbGltaXQgPSB2WyJsaW1pdCJdCiAgcmFuZ2UgPSB2WyJyYW5nZSJdCiAgcnUgPSB2WyJyYW5nZV91bml0Il0KICBzdSA9IHZbInNpemVfdW5pdCJdCiAgZSA9IHZbImV2dCJdCiAgaWYgZSA9PSAiY291bnQiOgogICAgZXZ0WyJtZXNzYWdlX3R5cGUiXSA9IDMwMQogICAgdW5pdCA9IHN0cihyYW5nZSkKICAgIGlmIHJ1ID09ICJIIiBvciBydSA9PSAiaCI6CiAgICAgICAgdW5pdCA9IHVuaXQgKyAi5bCP5pe2IgogICAgaWYgcnUgPT0gIkQiIG9yIHJ1ID09ICJkIjoKICAgICAgICB1bml0ID0gdW5pdCArICLlpKkiCiAgICBtc2cgPSAi5ZyoIiArIHVuaXQgKyAi5YaF77yMIiArIHVuYW1lICsgIueahOWfn+WGheaWh+S7tuWvvOWHuuaVsOmHj+i2hei/hyIgKyBzdHIobGltaXQpICsgIuS4qu+8jCIKICBpZiBlID09ICJzaXplIjoKICAgIGV2dFsibWVzc2FnZV90eXBlIl0gPSAzMDIKICAgIHVuaXQgPSBzdHIocmFuZ2UpCiAgICBpZiBydSA9PSAiSCIgb3IgcnUgPT0gImgiOgogICAgICAgIHVuaXQgPSB1bml0ICsgIuWwj+aXtiIKICAgIGlmIHJ1ID09ICJEIiBvciBydSA9PSAiZCI6CiAgICAgICAgdW5pdCA9IHVuaXQgKyAi5aSpIgogICAgbXNnID0gIuWcqCIgKyB1bml0ICsgIuWGhe+8jCIgKyB1bmFtZSArICLnmoTln5/lhoXmlofku7blr7zlh7rlpKflsI/otoXov4ciICsgc3RyKGxpbWl0KQogICAgaWYgc3UgPT0gIk0iIG9yIHN1ID09ICJtIjoKICAgICAgICBtc2cgPSBtc2cgKyAiTULvvIwiCiAgICBpZiBzdSA9PSAiRyIgb3Igc3UgPT0gImciOgogICAgICAgIG1zZyA9IG1zZyArICJHQu+8jCIKICAgIGlmIHN1ID09ICJUIiBvciBzdSA9PSAidCI6CiAgICAgICAgbXNnID0gbXNnICsgIlRC77yMIgoKICBpZiBsZW4obXNnKSA+IDA6CiAgICBldnRbImRldGFpbCJdID0gbXNnICsgIuivt+WPiuaXtui3n+i4quWvvOWHuuaWh+S7tueahOaVj+aEn+eoi+W6puaIlumHjeaWsOiwg+aVtOmYiOWAvOOAgiIKICAgIGV2dFsiZG9tYWluX2lkIl0gPSBkaWQKICAgIGV2dFsiY3JlYXRlX3RpbWUiXSA9IEdldFRpbWVTZWMoKQogICAgcmV0dXJuIGV2dApvdXRwdXQgPSBqc29uLmVuY29kZShjaGVjaygpKQ==
Type: starlark
sink:
Cols:
- create_time
- detail
- domain_id
- message_type
DB: mie
Host: mysql.db-mysql:3306
Pwd: L0hMysql.
Table: message_content
Type: mysql_sink
User: root
source:
DB: mie
Host: mysql.db-mysql:3306
Pwd: L0hMysql.
Table: evt_export_file_over
Type: mysql_source
User: root
Sentry:
TracesSampleRate: 1
StaticURLPathPrefix:
NetworkAppIcon: /user/avatar
Storage:
PublicFolderFileDir: /data/storage/public_folder_file
TmpDir: /data/storage/tmp
UploadedFilesDir: /data/storage/uploaded_files
TranslationPath: translation.csv
UpgradeCheckFilePath: /yizhisec/hs_nginx/resource/release_version_record.csv
UserManagement:
Host: user-service
Port: 9013
WatermarkServer:
Host: hs-watermark
Port: 9014
Web:
Host: 0.0.0.0
Mode: release
Port: 9129
Web2:
Host: 0.0.0.0
Mode: release
Port: 9024
WebMessages:
Host: 0.0.0.0
Mode: release
Port: 9025
WorkDir: /yizhisec/client_server
YosGuard:
Host: 172.17.0.1
Port: 7788`
_upsert = `#!/bin/bash
kubectl create configmap config-client --namespace hsv2 --from-file=config.yml=./config.yml --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f deployment.yaml
kubectl rollout restart deployment client-deployment -n hsv2`
)
var (
err error
workdir = filepath.Join(m.workdir, "app", "client")
)
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)
return err
}
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
}
logger.Debug("☑️ maker.AppClient: 开始构建 yaml 资源文件")
content := []byte(fmt.Sprintf(resource.YAMLAppClient, replica))
if err = os.WriteFile(filepath.Join(workdir, "deployment.yaml"), content, 0644); err != nil {
logger.Debug("❌ maker.AppClient: 写入 deployment.yaml 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppClient: 开始 yaml 资源文件成功")
logger.Debug("☑️ maker.AppClient: 开始构建 config 文件")
if err = os.WriteFile(filepath.Join(workdir, "config.yml"), []byte(_config), 0644); err != nil {
logger.Debug("❌ maker.AppClient: 写入 config.yml 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppClient: 构建 config 文件成功")
logger.Debug("☑️ maker.AppClient: 开始构建 upsert.sh 脚本")
if err = os.WriteFile(filepath.Join(workdir, "upsert.sh"), []byte(_upsert), 0755); err != nil {
logger.Debug("❌ maker.AppClient: 写入 upsert.sh 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppClient: 构建 upsert.sh 脚本成功")
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

@@ -0,0 +1,71 @@
package maker
import (
"context"
"fmt"
"os"
"path/filepath"
"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, version string, vendor string, replica int) error {
var (
err error
location = filepath.Join(m.workdir, "app", "front")
bs []byte
_vendor = model.GetVendor(vendor)
)
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, "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: 写入 deployment.user.yaml 失败: path = %s, err = %v", path, err)
return err
}
logger.Debug("✅ maker.Front: write deployment.user.yaml success, 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: 写入 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(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(location, "front.admin.tar")), WithImageForcePull(true)); err != nil {
logger.Debug("❌ maker.Front: 拉取 front 管理镜像失败: %s, err = %v", _vendor.AppFrontAdminImageName, err)
return err
}
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

@@ -0,0 +1,120 @@
package maker
import (
"context"
"fmt"
"os"
"path/filepath"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource"
)
func (m *maker) AppGateway(ctx context.Context, version string, replica int) error {
const (
_config = `Version: "3"
Database:
Elastic:
Address: http://es-service.db-es:9200
Mysql:
Address: mysql.db-mysql:3306
DBName: mie
Password: L0hMysql.
UserName: root
Redis:
Address: redis-master.db-redis:6379
Password: HybridScope0xRed1s.
Gateway:
Cert:
ClientCrt: /yizhisec/ssl/client.crt
ClientKey: /yizhisec/ssl/client.key
TokenFilePath: /etc/yizhisec/token
Key:
Token: TtKVnSzEHO3jRv/GWg3f5k3H1OVfMnPZ1Ke9E6MSCXk=
Log:
Dir: ./log
Level: 1
Name: gateway_controller
Sentry:
TracesSampleRate: 1
UserManagement:
Host: user-service
Port: 9013
Web:
Host: 0.0.0.0
Mode: release
Port: 9012
WorkDir: /yizhisec/gateway_controller/workspace
YosGuard:
Host: 172.17.0.1
Port: 7788`
_upsert = `#!/bin/bash
kubectl create configmap config-gateway --namespace hsv2 --from-file=config.yml=./config.yml --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f deployment.yaml
kubectl rollout restart deployment gateway-deployment -n hsv2`
)
var (
err error
workdir = filepath.Join(m.workdir, "app", "gateway")
)
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)
return err
}
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
}
logger.Debug("☑️ maker.AppGateway: 开始构建 yaml 资源文件")
content := []byte(fmt.Sprintf(resource.YAMLAppGateway, replica))
if err = os.WriteFile(filepath.Join(workdir, "deployment.yaml"), content, 0644); err != nil {
logger.Debug("❌ maker.AppGateway: 写入 deployment.yaml 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppGateway: 开始 yaml 资源文件成功")
logger.Debug("☑️ maker.AppGateway: 开始构建 config 文件")
if err = os.WriteFile(filepath.Join(workdir, "config.yml"), []byte(_config), 0644); err != nil {
logger.Debug("❌ maker.AppGateway: 写入 config.yml 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppGateway: 构建 config 文件成功")
logger.Debug("☑️ maker.AppGateway: 开始构建 upsert.sh 脚本")
if err = os.WriteFile(filepath.Join(workdir, "upsert.sh"), []byte(_upsert), 0755); err != nil {
logger.Debug("❌ maker.AppGateway: 写入 upsert.sh 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppGateway: 构建 upsert.sh 脚本成功")
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

@@ -0,0 +1,213 @@
package maker
import (
"context"
"fmt"
"os"
"path/filepath"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource"
)
func (m *maker) AppMie(ctx context.Context, version string, replica int) error {
const (
_config = `Version: "3"
BackupSeafile:
Host: hs-resource-server
Port: 19980
account_manager:
address: http://user-service:9013
client_server:
msg: http://client-service:9025
api: http://client-service:9024
web: http://client-service:9129
backend_queue_names:
request: request_que
web: web_que
backup_database_server:
host: hs-backup-server
port: 9349
backup_seafile_server:
host: hs-resource-server
port: 19980
clientPKG:
android:
client_pkg_dir: /data/storage/client_pkg/android
client_pkg_file_path: /data/storage/client_pkg/android/SecureApplication-Client-Android.apk
client_pkg_name: SecureApplication-Client-Android.apk
client_version_file_path: /data/storage/client_pkg/android/android_version.json
dir: /data/storage/client_pkg
ios:
client_pkg_dir: /data/storage/client_pkg/ios
client_pkg_file_path: ''
client_pkg_name: ''
client_version_file_path: /data/storage/client_pkg/ios/ios_version.json
linux:
client_pkg_dir: /data/storage/client_pkg/linux
client_pkg_file_path: /data/storage/client_pkg/linux/hscore-ubuntu-22.04-amd64.deb
client_pkg_name: hscore-ubuntu-22.04-amd64.deb
client_version_file_path: /data/storage/client_pkg/linux/linux_version.json
mac:
client_pkg_beta_file_path: /yizhisec/hs_nginx/resource/hybridscope-client-mac-beta.pkg
client_pkg_beta_name: hybridscope-client-mac-beta.pkg
client_pkg_dir: /data/storage/client_pkg/mac
client_pkg_file_path: /yizhisec/hs_nginx/resource/hybridscope-client-mac.pkg
client_pkg_name: hybridscope-client-mac.pkg
client_version_file_path: /data/storage/client_pkg/mac/mac_version.json
oem_dir: /yizhisec/hs_nginx/data/443/oem
oem_file_path: /yizhisec/hs_nginx/data/443/oem/data.json
windows:
client_main_zip_name: app.7z
client_pkg_cfg_file_name: login.conf
client_pkg_dir: /data/storage/client_pkg/windows
client_pkg_unzip_dir: package
client_pkg_zip: /data/storage/client_pkg/windows/dsclient.zip
client_zip_version: version
databases:
elasticsearch:
host: es-service.db-es
port: 9200
ipdb:
path: /etc/mie-server/ipdb/ip.ipdb
mysql:
db: mie
host: mysql-cluster-mysql-master.db-mysql
password: L0hMysql.
port: 3306
username: root
redis:
host: redis-master.db-redis
password: HybridScope0xRed1s.
port: 6379
username: ''
exe_root_license:
path: /etc/mie-server/root.pem
gateway_service:
host: gateway-service.hsv2
port: 9012
host: 0.0.0.0
license:
version: 3
license_init_conf: /etc/mie-server/server_license_init.conf
public_key: /etc/mie-server/license/pub_key
log_level: 20
mqtt_server:
host: emqx-service.db-emqx
port: 1883
port: 9002
resource_server:
address: http://hs-resource-server:19980
secret_key: i345piuh48776lkjsdhfsdfljho
sentry_dsn: null
static_urlpath_prefix:
network_app_icon: /user/avatar
storage:
avatar_dir: /data/storage/avatar
mobile_app_dir: /yizhisec/hs_nginx
network_app_icon: network_app
patch_dir: /data/storage/patch
public_folder_file_dir: /data/storage/public_folder_file
share_file_storage: /data/storage/share_file
sync_avatar_dir: sync
tmp_dir: /data/storage/tmp
upload_avatar_dir: local
uploaded_files: /data/storage/uploaded_files
token_key: TtKVnSzEHO3jRv/GWg3f5k3H1OVfMnPZ1Ke9E6MSCXk=
translation_path: /etc/mie-server/translation.csv
yosguard_service:
host: 172.17.0.1
port: 7788
ElinkLogin: true
export_with_blind_watermark: true`
_upsert = `#!/bin/bash
kubectl create configmap config-api --namespace hsv2 --from-file=conf.yml=./conf.yml --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f deployment-api.yaml
kubectl apply -f deployment-sweeper.yaml
kubectl apply -f deployment-worker.yaml
kubectl apply -f deployment-cron.yaml
kubectl rollout restart deployment api-deployment -n hsv2`
)
var (
err error
workdir = filepath.Join(m.workdir, "app", "mie")
)
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)
return err
}
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
}
logger.Debug("☑️ maker.AppMie: 写入 conf.yml 文件..., dest = %s", filepath.Join(workdir, "conf.yml"))
if err = os.WriteFile(filepath.Join(workdir, "conf.yml"), []byte(_config), 0644); err != nil {
logger.Debug("❌ maker.AppMie: 写入 conf.yml 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppMie: 写入 conf.yml 文件成功, dest = %s", filepath.Join(workdir, "conf.yml"))
logger.Debug("☑️ maker.AppMie: 写入 deployment-api.yaml 文件..., dest = %s", filepath.Join(workdir, "deployment-api.yaml"))
apiYaml := []byte(fmt.Sprintf(resource.YAMLAppMieAPI, replica))
if err = os.WriteFile(filepath.Join(workdir, "deployment-api.yaml"), apiYaml, 0644); err != nil {
logger.Debug("❌ maker.AppMie: 写入 deployment-api.yaml 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppMie: 写入 deployment-api.yaml 文件成功, dest = %s", filepath.Join(workdir, "deployment-api.yaml"))
logger.Debug("☑️ maker.AppMie: 写入 deployment-sweeper.yaml 文件..., dest = %s", filepath.Join(workdir, "deployment-sweeper.yaml"))
if err = os.WriteFile(filepath.Join(workdir, "deployment-sweeper.yaml"), resource.YAMLAppMieSweeper, 0644); err != nil {
logger.Debug("❌ maker.AppMie: 写入 deployment-sweeper.yaml 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppMie: 写入 deployment-sweeper.yaml 文件成功, dest = %s", filepath.Join(workdir, "deployment-sweeper.yaml"))
logger.Debug("☑️ maker.AppMie: 写入 deployment-worker.yaml 文件..., dest = %s", filepath.Join(workdir, "deployment-worker.yaml"))
if err = os.WriteFile(filepath.Join(workdir, "deployment-worker.yaml"), resource.YAMLAppMieWorker, 0644); err != nil {
logger.Debug("❌ maker.AppMie: 写入 deployment-worker.yaml 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppMie: 写入 deployment-worker.yaml 文件成功, dest = %s", filepath.Join(workdir, "deployment-worker.yaml"))
logger.Debug("☑️ maker.AppMie: 写入 deployment-cron.yaml 文件..., dest = %s", filepath.Join(workdir, "deployment-cron.yaml"))
if err = os.WriteFile(filepath.Join(workdir, "deployment-cron.yaml"), resource.YAMLAppMieCron, 0644); err != nil {
logger.Debug("❌ maker.AppMie: 写入 deployment-cron.yaml 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppMie: 写入 deployment-cron.yaml 文件成功, dest = %s", filepath.Join(workdir, "deployment-cron.yaml"))
logger.Debug("☑️ maker.AppMie: 写入 upsert.sh 文件..., dest = %s", filepath.Join(workdir, "upsert.sh"))
if err = os.WriteFile(filepath.Join(workdir, "upsert.sh"), []byte(_upsert), 0755); err != nil {
logger.Debug("❌ maker.AppMie: 写入 upsert.sh 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppMie: 写入 upsert.sh 文件成功, dest = %s", filepath.Join(workdir, "upsert.sh"))
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

@@ -0,0 +1,176 @@
package maker
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource"
)
func (m *maker) AppNginx(ctx context.Context, version string, vendor string, replica int, inlcudeImage bool) error {
const (
_upsert = `#!/bin/bash
kubectl create configmap nginx-main --namespace hsv2 --from-file=nginx.conf=./conf/nginx.conf --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap nginx-user --namespace hsv2 --from-file=user.conf=./conf/user.conf --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap nginx-gateway --namespace hsv2 --from-file=gateway.conf=./conf/gateway.conf --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap nginx-web --namespace hsv2 --from-file=web.conf=./conf/web.conf --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap nginx-client --namespace hsv2 --from-file=client.conf=./conf/client.conf --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap nginx-common --namespace hsv2 --from-file=common.conf=./conf/common.conf --dry-run=client -o yaml | kubectl apply -f -
%s
kubectl create configmap ssl-ca-crt --namespace hsv2 --from-file=ca.crt=./ssl/ca.crt --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap ssl-ffdhe2048 --namespace hsv2 --from-file=ffdhe2048.txt=./ssl/ffdhe2048.txt --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap ssl-server-crt --namespace hsv2 --from-file=server.crt=./ssl/server.crt --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap ssl-server-key --namespace hsv2 --from-file=server.key=./ssl/server.key --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap ssl-mqtt-crt --namespace hsv2 --from-file=mqtt.server.crt=./ssl/mqtt.server.crt --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap ssl-mqtt-key --namespace hsv2 --from-file=mqtt.server.key=./ssl/mqtt.server.key --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap ssl-client-server-crt --namespace hsv2 --from-file=client.server.crt=./ssl/client.server.crt --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap ssl-client-server-key --namespace hsv2 --from-file=client.server.key=./ssl/client.server.key --dry-run=client -o yaml | kubectl apply -f -
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 deployment.yaml
kubectl rollout restart deployment nginx-deployment -n hsv2`
)
var (
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, version = %s, vendor = %s, workdir = %s", version, vendor, 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")
logger.Debug(" ☑️ maker.AppNginx: 创建 conf 与 ssl 子目录")
if err = os.MkdirAll(confDir, 0755); err != nil {
logger.Debug("❌ maker.AppNginx: 创建 conf 目录失败: %v", err)
return err
}
if err = os.MkdirAll(sslDir, 0755); err != nil {
logger.Debug("❌ maker.AppNginx: 创建 ssl 目录失败: %v", err)
return err
}
logger.Debug("✅ maker.AppNginx: 创建 conf 与 ssl 子目录成功")
// 写入 nginx 配置文件到 conf 子目录(列表 + for 循环)
logger.Debug(" ☑️ maker.AppNginx: 写入 nginx 配置文件到 conf 子目录")
confFiles := []struct {
name string
content []byte
}{
{"nginx.conf", resource.NGINXMain},
{"user.conf", resource.NGINXUser},
{"gateway.conf", resource.NGINXGateway},
{"web.conf", resource.NGINXWeb},
{"client.conf", resource.NGINXClient},
{"common.conf", resource.NGINXCommon},
}
// vendor != elink, append seafile conf
if vendor != "elink" {
confFiles = append(confFiles, struct {
name string
content []byte
}{
"seafile.conf", resource.NGINXSeafile,
})
}
for _, f := range confFiles {
dest := filepath.Join(confDir, f.name)
if err = os.WriteFile(dest, f.content, 0644); err != nil {
logger.Debug("❌ maker.AppNginx: 写入 %s 失败: %v", f.name, err)
return err
}
logger.Debug("✅ maker.AppNginx: 写入 %s 成功, dest = %s", f.name, dest)
}
logger.Debug("✅ maker.AppNginx: 写入 nginx 配置文件成功")
// 写入 ssl 文件到 ssl 子目录
logger.Debug(" ☑️ maker.AppNginx: 写入 SSL 证书与密钥到 ssl 子目录")
sslFiles := []struct{ name, content string }{
{"ffdhe2048.txt", resource.SSLFFDHE2048},
{"ca.crt", resource.SSLCaCrt},
{"server.crt", resource.SSLServerCrt},
{"server.key", resource.SSLServerKey},
{"mqtt.server.crt", resource.SSLMQTTServerCrt},
{"mqtt.server.key", resource.SSLMQTTServerKey},
{"client.server.crt", resource.SSLClientServerCrt},
{"client.server.key", resource.SSLClientServerKey},
{"web.server.crt", resource.SSLWebServerCrt},
{"web.server.key", resource.SSLWebServerKey},
}
for _, f := range sslFiles {
dest := filepath.Join(sslDir, f.name)
if err = os.WriteFile(dest, []byte(f.content), 0644); err != nil {
logger.Debug("❌ maker.AppNginx: 写入 %s 失败: %v", f.name, err)
return err
}
logger.Debug("✅ maker.AppNginx: 写入 %s 成功, dest = %s", f.name, dest)
}
// write nginx deployment yaml
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: 写入 deployment.yaml 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppNginx: 写入 deployment.yaml 成功, dest = %s", dest)
// write nginx upsert script
dest = filepath.Join(workdir, "upsert.sh")
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)
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

@@ -0,0 +1,162 @@
package maker
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"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, version string, vendor string, replica int) error {
const (
_nginx = `user root;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 512;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
sendfile on;
keepalive_timeout 65;
client_max_body_size 10M;
server {
listen 80;
root /data;
location / {
try_files \$uri \$uri/ =404;
}
}
}`
_dockerfile = `FROM hub.yizhisec.com/external/nginx:1.29.1-alpine3.22
WORKDIR /data
COPY oem /data/oem
COPY nginx.conf /etc/nginx/nginx.conf
CMD ["nginx", "-g", "daemon off;"]`
_image = "hub.yizhisec.com/hybridscope/v2/oem-%s:latest"
)
var (
err error
_vendor = model.GetVendor(vendor)
workdir = filepath.Join(m.workdir, "app", "oem")
output []byte
)
logger.Info("☑️ maker.AppOEM: 开始构建 oem[%s], version = %s, ,workdir = %s", vendor, version, workdir)
if _vendor == nil {
supported := model.GetVendorNames()
logger.Debug("❌ maker.AppOEM: vendor not supported, 支持的 vendor 有: %v", supported)
return fmt.Errorf("请检查 vendor 是否正确, 支持的 vendor 有: %v", supported)
}
// 1. make workdir
logger.Debug("☑️ maker.AppOEM: 开始创建 workdir = %s", workdir)
_ = os.RemoveAll(workdir)
if err = os.MkdirAll(workdir, 0o755); err != nil {
return err
}
logger.Debug("✅ maker.AppOEM: workdir 创建成功 = %s", workdir)
// 2. download oem.tar.gz
logger.Debug("☑️ maker.AppOEM: 开始下载 oem[%s] url = %s", vendor, _vendor)
if err = archiver.DownloadAndExtract(ctx, _vendor.OEMUrl, workdir); err != nil {
logger.Debug("❌ maker.AppOEM: oem[%s] tar 下载失败, url = %s, err = %v", vendor, _vendor.OEMUrl, err)
return err
}
if _vendor.OEMDir != "oem" {
if err = os.Rename(
filepath.Join(workdir, _vendor.OEMDir),
filepath.Join(workdir, "oem"),
); err != nil {
logger.Debug("❌ maker.AppOEM: oem[%s] tar 重命名失败, err = %v", vendor, err)
return err
}
}
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")
if err = os.WriteFile(
filepath.Join(workdir, "nginx.conf"),
[]byte(_nginx),
0o644,
); err != nil {
logger.Debug("❌ maker.AppOEM: nginx.conf 写入失败, err = %v", err)
return err
}
logger.Debug("✅ maker.AppOEM: nginx.conf 写入成功")
// 4. write Dockerfile
logger.Debug("☑️ maker.AppOEM: 开始写入 Dockerfile")
if err = os.WriteFile(
filepath.Join(workdir, "Dockerfile"),
[]byte(_dockerfile),
0o644,
); err != nil {
logger.Debug("❌ maker.AppOEM: Dockerfile 写入失败, err = %v", err)
return err
}
logger.Debug("✅ maker.AppOEM: Dockerfile 写入成功")
// 5. build docker image
imageName := fmt.Sprintf(_image, vendor)
logger.Debug("☑️ maker.AppOEM: 开始构建 docker image = %s", imageName)
// docker build -t <image_name> -f <workdir/Dockerfile> <workdir>
_cmd := exec.CommandContext(ctx, "docker", "build", "-t", imageName, "-f", filepath.Join(workdir, "Dockerfile"), workdir)
if output, err = _cmd.CombinedOutput(); err != nil {
logger.Debug("❌ maker.AppOEM: docker image 构建失败, err = %v, output = %s", err, string(output))
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"))
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
}
logger.Debug("✅ maker.AppOEM: docker image 保存成功, image = %s", imageName)
// 7. render oem.yaml
logger.Debug("☑️ maker.AppOEM: 开始渲染 deployment.yaml")
oemYAML := fmt.Sprintf(resource.YAMLAppOEM, replica, imageName)
if err = os.WriteFile(
filepath.Join(workdir, "deployment.yaml"),
[]byte(oemYAML),
0o644,
); err != nil {
logger.Debug("❌ maker.AppOEM: deployment.yaml 写入失败, err = %v", err)
return err
}
logger.Debug("✅ maker.AppOEM: deployment.yaml 写入成功")
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

@@ -2,8 +2,144 @@ package maker
import (
"context"
"fmt"
"os"
"path/filepath"
"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:
Elastic:
Address: http://es-service.db-es:9200
IPDB:
Path: /etc/hs_user_management/ipdb/ip.ipdb
Mysql:
Address: mysql-cluster-mysql-master.db-mysql:3306
DBName: mie
Password: L0hMysql.
UserName: root
Redis:
Address: redis-master.db-redis:6379
Password: HybridScope0xRed1s.
DisabledFeatureFilePath: /etc/yizhisec/disabled_features
EnableTenant: false
Key:
Token: TtKVnSzEHO3jRv/GWg3f5k3H1OVfMnPZ1Ke9E6MSCXk=
LicensePubKey: /etc/yizhisec/license/pub_key
Log:
Dir: ./log
Level: 1
Name: hs_user_management
Sentry:
TracesSampleRate: 1
Sso:
DingTalk:
ApiHost: oapi.dingtalk.com
LoginUrl: https://oapi.dingtalk.com/connect/qrconnect
Feishu:
ApiHost: open.feishu.cn
LoginUrl: https://open.feishu.cn/open-apis/authen/v1/index
Proxy:
CallbackHost: hssso.yizhisec.com:33443
Cert:
ClientCrt: /etc/hs_user_management/proxy/certs/client.crt
ClientKey: /etc/hs_user_management/proxy/certs/client.key
ServiceHost: hssso.yizhisec.com:33444
RedirectPath:
BoundFailed: /#/accountSettings/thirdAccount
BoundSuccess: /#/accountSettings/thirdAccount
LoginFailed: /#/thirdError
LoginNeedBoundUser: /#/bind
LoginSuccess: /#/
WorkWeiXin:
ApiHost: qyapi.weixin.qq.com
LoginUrl: https://login.work.weixin.qq.com/wwlogin/sso/login
Storage:
Avatar:
ADSyncDir: ad
Base: /data/storage/avatar
LDAPSyncDir: ldap
LocalDir: local
SyncDir: sync
TranslationPath: translation.csv
Web:
Host: 0.0.0.0
Mode: release
Port: 9013
WorkDir: /yizhisec/hs_user_management/workspace
YosGuard:
Host: 172.17.0.1
Port: 7788
ElinkLogin: false`
_upsert = `#!/bin/bash
kubectl create configmap config-user --namespace hsv2 --from-file=config.yml=./config.yml --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f deployment.yaml
kubectl rollout restart deployment user-deployment -n hsv2`
)
var (
err error
workdir = filepath.Join(m.workdir, "app", "user")
)
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
}
logger.Debug("☑️ maker.AppUser: 开始构建 yaml 资源文件")
content := []byte(fmt.Sprintf(resource.YAMLAppUser, replica))
if err = os.WriteFile(filepath.Join(workdir, "deployment.yaml"), []byte(content), 0644); err != nil {
logger.Debug("❌ maker.AppUser: 写入 deployment.yaml 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppUser: 开始 yaml 资源文件成功")
// 写入 config.yml
logger.Debug("☑️ maker.AppUser: 开始构建 config 文件")
if err = os.WriteFile(filepath.Join(workdir, "config.yml"), []byte(_config), 0644); err != nil {
logger.Debug("❌ maker.AppUser: 写入 config.yml 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppUser: 构建 config 文件成功")
logger.Debug("☑️ maker.AppUser: 开始构建 upsert.sh 脚本")
if err = os.WriteFile(filepath.Join(workdir, "upsert.sh"), []byte(_upsert), 0755); err != nil {
logger.Debug("❌ maker.AppUser: 写入 upsert.sh 失败: %v", err)
return err
}
logger.Debug("✅ maker.AppUser: 构建 upsert.sh 脚本成功")
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,25 +4,25 @@ import (
"context"
"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"
)
func (m *maker) Binary(ctx context.Context) error {
func (m *maker) K0s(ctx context.Context) error {
var (
tarURL = "https://artifactory.yizhisec.com:443/artifactory/filestore/hsv3/k8s-bin.tar"
binDir = filepath.Join(opt.Cfg.Make.Dir, "dependency")
tarURL = "https://artifactory.yizhisec.com:443/artifactory/filestore/hsv3/k8s-bin.tar"
location = filepath.Join(m.workdir, "dependency", "k0s")
)
logger.Info("☑️ 开始准备 k8s 二进制文件...")
logger.Info("☑️ 开始准备 k0s...")
logger.Debug("下载地址: %s", tarURL)
logger.Debug("目标目录: %s", binDir)
logger.Debug("目标目录: %s", location)
if err := archiver.DownloadAndExtract(
ctx,
tarURL,
binDir,
location,
archiver.WithInsecureSkipVerify(),
archiver.WithGzipCompression(true),
); err != nil {
@@ -30,7 +30,32 @@ func (m *maker) Binary(ctx context.Context) error {
return err
}
logger.Info("✅ 准备 k8s 二进制文件成功!!!")
logger.Debug("☑️ maker.K0s: 开始准备相关镜像...")
var images = []*model.Image{
{Name: "quay.io/k0sproject/apiserver-network-proxy-agent:v0.32.0", Fallback: "hub.yizhisec.com/external/apiserver-network-proxy-agent:v0.32.0", Save: "k0s.apiserver-network-proxy-agent.tar"},
{Name: "quay.io/k0sproject/cni-node:1.7.1-k0s.0", Fallback: "", Save: "k0s.cni-node.tar"},
{Name: "quay.io/k0sproject/coredns:1.12.2", Fallback: "", Save: "k0s.coredns.tar"},
{Name: "quay.io/k0sproject/kube-proxy:v1.33.4", Fallback: "", Save: "k0s.kube-proxy.tar"},
{Name: "quay.io/k0sproject/kube-router:v2.5.0-iptables1.8.11-0", Fallback: "", Save: "k0s.kube-router.tar"},
{Name: "quay.io/k0sproject/metrics-server:v0.7.2-0", Fallback: "", Save: "k0s.metrics-server.tar"},
{Name: "quay.io/k0sproject/pause:3.10.1", Fallback: "", Save: "k0s.pause.tar"},
}
for _, image := range images {
opts := []ImageOpt{
WithImageFallback(image.Fallback),
WithImageSave(filepath.Join(location, image.Save)),
WithImageForcePull(image.Force),
}
if err := m.Image(ctx, image.Name, opts...); err != nil {
logger.Error("❌ maker.K0s: 获取镜像失败: %s, 可以手动获取后重试", image.Name)
logger.Debug("❌ maker.K0s: 获取镜像失败: %s, %v", image.Name, err)
return err
}
}
logger.Debug("✅ maker.K0s: 准备相关镜像成功!!!")
logger.Info("✅ 准备 k0s 成功!!!")
return nil
}

View File

@@ -22,6 +22,10 @@ func (m *maker) DependencyCheck(ctx context.Context) error {
return fmt.Errorf("docker 命令未找到: %w", err)
}
if err := checkCommand(ctx, "7z"); err != nil {
return fmt.Errorf("7z 命令未找到: %w", err)
}
return nil
}

View File

@@ -0,0 +1,184 @@
package maker
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/samber/lo"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/resource"
)
type clientPKGDownload struct {
URL string
Name string
}
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 {
return func(o *clientPKGOption) {
if url == "" || location == "" {
return
}
o.Downloads = append(o.Downloads, &clientPKGDownload{
URL: url,
Name: location,
})
}
}
func WithClientPKGCMDs(cmds ...string) ClientPKGOption {
return func(o *clientPKGOption) {
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/hsv2/base/nginx:latest
RUN mkdir -p /data
COPY version.json /data/version.json
%s
COPY nginx.conf /etc/nginx/nginx.conf
`
)
if !lo.Contains([]string{"mac", "win", "linux"}, _os) {
return fmt.Errorf("invalid os: %s", _os)
}
var (
o = &clientPKGOption{
Downloads: []*clientPKGDownload{},
CMDs: []string{},
}
err error
location = filepath.Join(m.workdir, "client", _os)
_file string
_content string
)
for _, fn := range opts {
fn(o)
}
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)
if os.WriteFile(_file, []byte(_content), 0644); err != nil {
logger.Debug("❌ maker.ClientPKG: write file failed, file = %s, err = %s", _file, err.Error())
return err
}
logger.Debug("✅ maker.ClientPKG: write file success, file = %s", _file)
lines := lo.Map(o.Downloads, func(d *clientPKGDownload, index int) string {
return fmt.Sprintf("wget -O /data/%s %s", d.Name, d.URL)
})
if len(o.CMDs) > 0 {
lines = append(lines, o.CMDs...)
}
_file = filepath.Join(location, "Dockerfile")
_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())
return err
}
logger.Debug("✅ maker.ClientPKG: write file success, file = %s", _file)
imgName := fmt.Sprintf("hub.yizhisec.com/hsv2/client/%s:%s", _os, _version)
logger.Debug("☑️ maker.ClientPKG: build docker image, os = %s, version = %s, full_name = %s", _os, _version, imgName)
_cmd := fmt.Sprintf("docker build --network host -t %s -f Dockerfile .", imgName)
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
}
imgLocation := filepath.Join(location, _os+".tar")
logger.Debug("☑️ maker.ClientPKG: save img to %s", imgLocation)
_cmd = fmt.Sprintf("docker save hub.yizhisec.com/hsv2/client/%s:%s -o %s", _os, _version, imgLocation)
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
}
imgNameInCluster := fmt.Sprintf("10.96.123.45:80/hsv2/client/%s:%s", _os, _version)
deployment := filepath.Join(location, "deployment.yaml")
_content = strings.ReplaceAll(
fmt.Sprintf(
resource.YAMLClientPKG,
1, // replica 先默认 1 就够了
imgNameInCluster,
),
"__os__",
_os,
)
if err = os.WriteFile(deployment, []byte(_content), 0644); err != nil {
logger.Debug("❌ maker.ClientPKG: write file failed, file = %s, err = %s", deployment, err.Error())
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

@@ -0,0 +1,37 @@
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 {
if len(_cmds) == 0 {
return nil
}
logger.Debug("maker.RunCommand: dir = %s, cmds = %v", dir, _cmds)
for _, cmdStr := range _cmds {
cmd := exec.CommandContext(ctx, "sh", "-c", cmdStr)
if dir != "" {
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, output = %s", cmdStr, dir, err, buf.String())
}
}
return nil
}

View File

@@ -7,11 +7,10 @@ import (
"path/filepath"
"strings"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"github.com/samber/lo"
"yizhisec.com/hsv2/forge/internal/opt"
"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"
)
@@ -86,6 +85,11 @@ EEuYRYXDouPJ1F//rYraSoJ4mtaipB6z1A==
-----END EC PRIVATE KEY-----`
upsert = `#!/bin/bash
# Generate server_license_init.conf
uuid=$(cat /proc/sys/kernel/random/uuid)
now=$(date +%s)
echo "{\"uuid\": \"$uuid\", \"install_time\": $now}" > ./server_license_init.conf
kubectl create configmap config-token --namespace hsv2 --from-file=token=./token --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap config-license-init --namespace hsv2 --from-file=server_license_init.conf=./server_license_init.conf --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap config-oem-data --namespace hsv2 --from-file=data.json=./oem_data.json --dry-run=client -o yaml | kubectl apply -f -
@@ -96,12 +100,22 @@ kubectl create configmap ssl-client-key --namespace hsv2 --from-file=client.key=
kubectl create configmap ssl-client-ca-crt --namespace hsv2 --from-file=client.ca.crt=./ssl_client_ca.crt --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap ssl-client-ca-key --namespace hsv2 --from-file=client.ca.key=./ssl_client_ca.key --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap ssl-web-crt --namespace hsv2 --from-file=web.server.crt=./ssl_web.crt --dry-run=client -o yaml | kubectl apply -f -
`
_version_yaml = `
apiVersion: v1
kind: ConfigMap
metadata:
name: config-version
namespace: hsv2
data:
version.txt: |
__version__
`
)
var (
err error
dir = filepath.Join(opt.Cfg.Make.Dir, "configmap")
dir = filepath.Join(m.workdir, "configmap")
vendorUrlMap = map[string]string{
"standard": "https://artifactory.yizhisec.com/artifactory/yizhisec-release/oem/release/2.1.0-std/oem.tar.gz",
"elink": "https://artifactory.yizhisec.com/artifactory/yizhisec-release/oem/release/2.1.0-std/oem_csgElink.tar.gz",
@@ -204,6 +218,12 @@ kubectl create configmap ssl-web-crt --namespace hsv2 --from-file=web.server.crt
}
logger.Debug("✅ maker.ConfigMap: 写入 ssl_client_ca.key 文件: %s 成功", filepath.Join(dir, "ssl_client_ca.key"))
if err = os.WriteFile(filepath.Join(dir, "version.yaml"), []byte(_version_yaml), 0644); err != nil {
logger.Debug("❌ maker.ConfigMap: 写入 version.yaml 文件: %s 失败, 错误: %v", filepath.Join(dir, "version.yaml"), err)
return err
}
logger.Debug("✅ maker.ConfigMap: 写入 version.yaml 文件: %s 成功", filepath.Join(dir, "version.yaml"))
// upsert configmap
logger.Debug("☑️ maker.ConfigMap: 执行 upsert 脚本: %s", filepath.Join(dir, "upsert.sh"))
if err = os.WriteFile(filepath.Join(dir, "upsert.sh"), []byte(upsert), 0755); err != nil {

View File

@@ -1,37 +0,0 @@
package maker
import (
"context"
"path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/archiver"
)
func (m *maker) Deb(ctx context.Context) error {
var (
tarURL = "https://artifactory.yizhisec.com:443/artifactory/filestore/hsv3/deb/docker.tar.gz"
binDir = filepath.Join(opt.Cfg.Make.Dir, "dependency", "deb")
)
logger.Info("☑️ 开始准备 deb(docker) 文件...")
logger.Debug("下载地址: %s", tarURL)
logger.Debug("目标目录: %s", binDir)
if err := archiver.DownloadAndExtract(
ctx,
tarURL,
binDir,
archiver.WithInsecureSkipVerify(),
archiver.WithGzipCompression(true),
); err != nil {
logger.Info("❌ 下载并解压 deb(docker) 文件失败")
logger.Debug("下载并解压 deb(docker) 文件失败: %v", err)
return err
}
logger.Info("✅ 准备 deb(docker) 文件成功!!!")
return nil
}

View File

@@ -6,11 +6,11 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"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"
)
@@ -38,7 +38,7 @@ func WithElasticMemRate(memRate int) ElasticOpt {
func WithElasticStorageGi(storage string) ElasticOpt {
return func(o *elasticOpt) {
if matched, _ := regexp.MatchString(`^\d+(\.\d+)?[EPTGMK]i?$`, storage); matched {
if opt.StorageSizeReg.MatchString(storage) {
o.Storage = storage
}
}
@@ -66,8 +66,8 @@ RUN chmod +x /data/create_index.sh
var (
err error
output []byte
location = filepath.Join(opt.Cfg.Make.Dir, "dependency", "elastic")
helperTarLocation = filepath.Join(opt.Cfg.Make.Dir, "dependency", "image", "es-init-helper.alpine-3.22.2.tar")
location = filepath.Join(m.workdir, "dependency", "elastic")
helperTarLocation = filepath.Join(m.workdir, "dependency", "image", "es-init-helper.alpine-3.22.2.tar")
)
opt := &elasticOpt{
@@ -179,6 +179,28 @@ RUN chmod +x /data/create_index.sh
}
logger.Debug("✅ maker.Elastic: 写入 kibana.yaml 文件成功")
logger.Debug("☑️ maker.Elastic: 开始获取所需镜像...")
var images = []*model.Image{
{Name: "hub.yizhisec.com/external/elasticsearch:7.17.28", Fallback: "", Save: "elasticsearch.7.17.28.tar"},
{Name: "hub.yizhisec.com/external/kibana:7.17.28", Fallback: "", Save: "kibana.7.17.28.tar"},
}
for _, image := range images {
opts := []ImageOpt{
WithImageFallback(image.Fallback),
WithImageSave(filepath.Join(location, image.Save)),
WithImageForcePull(image.Force),
}
if err := m.Image(ctx, image.Name, opts...); err != nil {
logger.Error("❌ maker.Elastic: 获取镜像失败: %s, 可以手动获取后重试", image.Name)
logger.Debug("❌ maker.Elastic: 获取镜像失败: %s, %v", image.Name, err)
return err
}
}
logger.Debug("✅ maker.Elastic: 获取所需镜像成功!!!")
logger.Info("✅ maker.Elastic: 构建 elastic(es) 依赖成功!!!")
return nil
}

View File

@@ -5,15 +5,15 @@ 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"
)
func (m *maker) EMQX(ctx context.Context) error {
var (
err error
location = filepath.Join(opt.Cfg.Make.Dir, "emqx")
location = filepath.Join(m.workdir, "dependency", "emqx")
)
logger.Info("☑️ maker.EMQX: 开始构建 emqx(mqtt) 依赖...")
@@ -32,6 +32,14 @@ func (m *maker) EMQX(ctx context.Context) error {
}
logger.Debug("✅ maker.EMQX: 写入 emqx.yaml 文件成功, dest = %s", filepath.Join(location, "emqx.yaml"))
logger.Debug("☑️ maker.EMQX: 开始获取所需镜像...")
var img = &model.Image{Name: "hub.yizhisec.com/external/emqx:5.1", Fallback: "", Save: "emqx.5.1.tar"}
if err = m.Image(ctx, img.Name, WithImageFallback(img.Fallback), WithImageSave(filepath.Join(location, img.Save))); err != nil {
logger.Debug("❌ maker.EMQX: 获取镜像失败: %s, %v", img.Name, err)
return err
}
logger.Debug("✅ maker.EMQX: 获取所需镜像成功!!!")
logger.Info("✅ maker.EMQX: 构建 emqx(mqtt) 依赖成功!!!")
return nil
}

View File

@@ -6,15 +6,15 @@ 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"
)
func (m *maker) Flannel(ctx context.Context, mode string) error {
var (
err error
location = filepath.Join(opt.Cfg.Make.Dir, "dependency", "flannel")
location = filepath.Join(m.workdir, "dependency", "flannel")
)
logger.Info("☑️ 开始构建 flannel 资源...")
@@ -34,6 +34,27 @@ func (m *maker) Flannel(ctx context.Context, mode string) error {
return err
}
var images = []*model.Image{
{Name: "ghcr.io/flannel-io/flannel:v0.27.4", Fallback: "swr.cn-north-4.myhuaweicloud.com/ddn-k8s/ghcr.io/flannel-io/flannel:v0.27.4", Save: "flannel.tar"},
{Name: "ghcr.io/flannel-io/flannel-cni-plugin:v1.8.0-flannel1", Fallback: "swr.cn-north-4.myhuaweicloud.com/ddn-k8s/ghcr.io/flannel-io/flannel-cni-plugin:v1.8.0-flannel1", Save: "flannel-cni-plugin.tar"},
}
logger.Debug("MakeFlannel: 开始获取镜像...")
for _, image := range images {
opts := []ImageOpt{
WithImageFallback(image.Fallback),
WithImageSave(filepath.Join(location, image.Save)),
WithImageForcePull(image.Force),
}
if err := m.Image(ctx, image.Name, opts...); err != nil {
logger.Error("❌ MakeFlannel: 获取镜像失败: %s, 可以手动获取后重试", image.Name)
logger.Debug("❌ MakeFlannel: 获取镜像失败: %s, %v", image.Name, err)
return err
}
}
logger.Debug("MakeFlannel: 获取镜像成功!!!")
logger.Info("✅ 构建 flannel 成功!!!")
return nil

View File

@@ -5,20 +5,22 @@ import (
"os"
"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/resource"
)
func (m *maker) HSNet(ctx context.Context) error {
const (
service = `[Unit]
_service = `[Unit]
Description=hs-net Container Service
Documentation=https://docs.containerd.io
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
# 拉取最新镜像(按需启用/注释)
@@ -28,9 +30,7 @@ ExecStartPre=-/usr/local/bin/k0s ctr -n hs-net container rm hs-net
ExecStart=/usr/local/bin/k0s ctr -n hs-net run \
--net-host \
--privileged \
--cgroup host \
--env LD_LIBRARY_PATH=/yizhisec/hs_net \
--env RUSTFLAGS="-C target-cpu=nehalem" \
--env RUST_BACKTRACE=1 \
--mount type=bind,src=/etc/localtime,dst=/etc/localtime,options=rbind:ro \
--mount type=bind,src=/etc/hosts,dst=/etc/hosts,options=rbind:ro \
@@ -43,11 +43,11 @@ ExecStart=/usr/local/bin/k0s ctr -n hs-net run \
--mount type=bind,src=/yizhisec/hs_net/conf,dst=/etc/hs_net,options=rbind:rw \
hub.yizhisec.com/hybridscope/hsnet:release_2.1.0-std hs-net
# --cgroup host \
# --env RUSTFLAGS="-C target-cpu=nehalem" \
# 重启策略
Restart=on-failure
Restart=always
RestartSec=5s
StartLimitInterval=60s
StartLimitBurst=5
# 资源限制(按需调整)
MemoryLimit=2G
@@ -59,24 +59,311 @@ 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]
WantedBy=multi-user.target`
_conf_out = `log:
level: info
controller:
protocol: https
registerHost: hs-gateway-register-controller
host: hs-gateway-controller
port: 443
tokenFilePath: /etc/yizhisec/token
registerRetry: 30
wg:
private_key: qPfOaNKrV11kzaGQiNQNyiu6wMQGUpIM+/xqboVAnng=
private_network: 246.0.0.1/8
listen_port: 23209
mtu: 1300
obf_key: 0
keep_alive: 61
tun_name: wg_tun
yosGuard:
host: __ip__
port: 7788
mqtt:
protocol: tls
host: mqtt.yizhisec.com
port: '443'
cert: /yizhisec/hs_net/conf/ssl/mqtt.client.crt
key: /yizhisec/hs_net/conf/ssl/mqtt.client.key
keep_alive: 60
paseto_key: TtKVnSzEHO3jRv/GWg3f5k3H1OVfMnPZ1Ke9E6MSCXk=
resource_server: hs-gateway-controller
dns_cache:
Address: 127.0.0.1:9028
gatewayVersionFile: /etc/yizhisec/gateway_version.json
lastVersion: null
workDir: /yizhisec/hs_net/workspace
eth_names: []
tag: ''
cluster_mock: ''
openobserve_uri: ''
tcp_mode_disable: false
`
_conf_in = `{
"LogLevel": "info",
"LogFile": "/yizhisec/hs_net/workspace/log/wireguard",
"DnsVirtNetwork": null,
"DnsVirtNetworkV6": null,
"Foreground": false,
"WithPprof": false,
"DnsCache": {
"Address": "127.0.0.1:9028"
},
"log": {
"level": "info"
},
"yosGuard": {
"host": "__ip__",
"port": 7788
},
"controller": {
"protocol": "https",
"host": "hs-gateway-controller",
"registerHost": "hs-gateway-register-controller",
"port": 443,
"registerRetry": 30,
"tokenFilePath": "/etc/yizhisec/token"
},
"wg": {
"private_key": "qPfOaNKrV11kzaGQiNQNyiu6wMQGUpIM+/xqboVAnng=",
"private_network": "246.0.0.1/8",
"listen_port": 23209,
"mtu": 1380,
"obf_key": 0,
"keep_alive": 60,
"tun_name": "wg_tun"
},
"mqtt": {
"protocol": "tls",
"host": "mqtt.yizhisec.com",
"port": 443,
"cert": "/yizhisec/hs_net/conf/ssl/mqtt.client.crt",
"key": "/yizhisec/hs_net/conf/ssl/mqtt.client.key",
"keep_alive": 60
},
"paseto_key": "TtKVnSzEHO3jRv/GWg3f5k3H1OVfMnPZ1Ke9E6MSCXk=",
"resource_server": "hs-gateway-controller",
"gatewayVersionFile": "/etc/yizhisec/gateway_version.json",
"lastVersion": null,
"workDir": "/yizhisec/hs_net/workspace",
"dns_cache": {
"Address": "127.0.0.1:9028"
}
}
`
_url = "https://artifactory.yizhisec.com/artifactory/yizhisec-release/hs_net/release/2.1.0-std/package.tar.gz"
)
var (
err error
location = filepath.Join(opt.Cfg.Make.Dir, "dependency", "hs-net")
err error
workdir = filepath.Join(m.workdir, "dependency", "hs_net")
)
if err = os.MkdirAll(location, 0755); err != nil {
logger.Error("MakeHSNet: 创建目录失败s")
logger.Debug("MakeHSNet: 创建目录失败: %s", err.Error())
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
}
logger.Fatal("MakeHSNet: 构建 hs-net 失败!!!(怎么完善,怎么完善,怎么完善???)")
// {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
}
// mv workdir/package/server workdir/
// mv workdir/package/server_aes workdir/
if err = os.Rename(filepath.Join(workdir, "package", "server"), filepath.Join(workdir, "server")); err != nil {
logger.Debug("❌ MakeHSNet: 重命名文件失败: %s", err.Error())
return err
}
if err = os.Rename(filepath.Join(workdir, "package", "server_aes"), filepath.Join(workdir, "server_aes")); err != nil {
logger.Debug("❌ MakeHSNet: 重命名文件失败: %s", err.Error())
return err
}
// write down conf_out to server.conf
if err = os.WriteFile(filepath.Join(workdir, "server.conf"), []byte(_conf_out), 0644); err != nil {
logger.Debug("❌ MakeHSNet: 写入配置文件失败: %s", err.Error())
return err
}
// write down conf_in to conf/server.conf
if err = os.MkdirAll(filepath.Join(workdir, "conf", "ssl"), 0755); err != nil {
logger.Debug("❌ MakeHSNet: 创建目录失败: %s", err.Error())
return err
}
if err = os.WriteFile(filepath.Join(workdir, "conf", "server.conf"), []byte(_conf_in), 0644); err != nil {
logger.Debug("❌ MakeHSNet: 写入配置文件失败: %s", err.Error())
return err
}
// write resource.SSLMQTTClientCrt
if err = os.WriteFile(filepath.Join(workdir, "conf", "ssl", "mqtt.client.crt"), resource.SSLMQTTClientCrt, 0644); err != nil {
logger.Debug("❌ MakeHSNet: 写入配置文件失败: %s", err.Error())
return err
}
if err = os.WriteFile(filepath.Join(workdir, "conf", "ssl", "mqtt.client.key"), resource.SSLMQTTClientKey, 0644); err != nil {
logger.Debug("❌ MakeHSNet: 写入配置文件失败: %s", err.Error())
return err
}
// mkdir workspace
if err = os.MkdirAll(filepath.Join(workdir, "workspace"), 0755); err != nil {
logger.Debug("❌ MakeHSNet: 创建目录失败: %s", err.Error())
return err
}
// new empty file lastVersion.txt
if err = os.WriteFile(filepath.Join(workdir, "lastVersion.txt"), []byte{}, 0644); err != nil {
logger.Debug("❌ MakeHSNet: 创建空文件失败: %s", err.Error())
return err
}
// 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())
return err
}
// upsert.sh script
const _upsert = `#!/bin/bash
set -e
echo "Starting hs-net deployment..."
# 1. Copy token file
echo "Copying token file..."
mkdir -p /etc/yizhisec
cp ../../configmap/token /etc/yizhisec/token
echo "Token file copied successfully"
# 2. Get local IP address
echo "Detecting local IP address..."
LocalIP=$(ip route get 1.1.1.1 | grep -oP 'src \K\S+')
if [ -z "$LocalIP" ]; then
echo "Error: Failed to detect local IP address"
exit 1
fi
echo "Local IP detected: $LocalIP"
# 3. Update /etc/hosts with required entries
echo "Updating /etc/hosts..."
for host in "hs-gateway-register-controller" "hs-gateway-controller" "mqtt.yizhisec.com"; do
if grep -q "$host" /etc/hosts; then
sed -i "/$host/d" /etc/hosts
fi
echo "$LocalIP $host" >> /etc/hosts
echo "Added: $LocalIP $host"
done
echo "/etc/hosts updated successfully"
# 4. Replace __ip__ in server.conf
echo "Updating server.conf..."
sed -i "s/__ip__/$LocalIP/g" server.conf
echo "server.conf updated successfully"
# 5. Replace __ip__ in conf/server.conf
echo "Updating conf/server.conf..."
sed -i "s/__ip__/$LocalIP/g" conf/server.conf
echo "conf/server.conf updated successfully"
# 6. Create /mnt/huge directory
echo "Creating /mnt/huge directory..."
mkdir -p /mnt/huge
echo "/mnt/huge directory created successfully"
# 7. Create workspace directories
echo "Creating workspace directories..."
mkdir -p /yizhisec/hs_net/workspace/log
mkdir -p /yizhisec/hs_net/conf
echo "Workspace directories created successfully"
# 8. Copy configuration files
echo "Copying configuration files..."
cp -r conf/* /yizhisec/hs_net/conf/
echo "Configuration files copied successfully"
# 9. Copy binaries based on CPU AVX support
echo "Detecting CPU AVX support..."
if grep -q avx /proc/cpuinfo; then
echo "AVX support detected, using server_aes"
cp server_aes /yizhisec/hs_net/server
chmod +x /yizhisec/hs_net/server
echo "server_aes copied to /yizhisec/hs_net/server with execute permission"
else
echo "AVX not supported, using server"
cp server /yizhisec/hs_net/server
chmod +x /yizhisec/hs_net/server
echo "server copied to /yizhisec/hs_net/server with execute permission"
fi
echo "Binary copied successfully"
# 10. Copy lastVersion.txt
echo "Copying lastVersion.txt..."
cp lastVersion.txt /yizhisec/hs_net/
echo "lastVersion.txt copied successfully"
# 11. Load container image
echo "Loading hs-net container image..."
k0s ctr -n hs-net images import hs-net.tar
echo "Container image loaded successfully"
# 12. Install and enable systemd service
echo "Installing hs-net systemd service..."
cp hs-net.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable hs-net.service
echo "hs-net service installed and enabled"
# 13. Start the service
echo "Starting hs-net service..."
systemctl restart hs-net.service
echo "hs-net service started successfully"
echo "hs-net deployment completed successfully!"
echo "You can check the service status with: systemctl status hs-net.service"
`
// Write upsert.sh
logger.Debug("☑️ MakeHSNet: 写入 upsert.sh 脚本...")
if err = os.WriteFile(filepath.Join(workdir, "upsert.sh"), []byte(_upsert), 0755); err != nil {
logger.Debug("❌ MakeHSNet: 写入 upsert.sh 失败: %s", err.Error())
return err
}
logger.Debug("✅ MakeHSNet: 写入 upsert.sh 成功")
logger.Info("✅ MakeHSNet: 构建 hs-net 成功, workdir = %s", workdir)
return nil
}

View File

@@ -6,9 +6,9 @@ import (
"os/exec"
"path/filepath"
"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/model"
)
type imageOpt struct {
@@ -32,9 +32,11 @@ func WithImageSave(filename string) ImageOpt {
}
}
func WithImageForcePull() ImageOpt {
func WithImageForcePull(force bool) ImageOpt {
return func(o *imageOpt) {
o.ForcePull = true
if force {
o.ForcePull = true
}
}
}
@@ -107,63 +109,13 @@ SAVE:
}
func (m *maker) Images(ctx context.Context) error {
type Images struct {
Name string
Fallback string
Save string
Force bool
}
var images = []*Images{
{Name: "quay.io/k0sproject/apiserver-network-proxy-agent:v0.32.0", Fallback: "hub.yizhisec.com/external/apiserver-network-proxy-agent:v0.32.0", Save: "k0s.apiserver-network-proxy-agent.tar"},
{Name: "quay.io/k0sproject/cni-node:1.7.1-k0s.0", Fallback: "", Save: "k0s.cni-node.tar"},
{Name: "quay.io/k0sproject/coredns:1.12.2", Fallback: "", Save: "k0s.coredns.tar"},
{Name: "quay.io/k0sproject/kube-proxy:v1.33.4", Fallback: "", Save: "k0s.kube-proxy.tar"},
{Name: "quay.io/k0sproject/kube-router:v2.5.0-iptables1.8.11-0", Fallback: "", Save: "k0s.kube-router.tar"},
{Name: "quay.io/k0sproject/metrics-server:v0.7.2-0", Fallback: "", Save: "k0s.metrics-server.tar"},
{Name: "quay.io/k0sproject/pause:3.10.1", Fallback: "", Save: "k0s.pause.tar"},
{Name: "ghcr.io/flannel-io/flannel:v0.27.4", Fallback: "swr.cn-north-4.myhuaweicloud.com/ddn-k8s/ghcr.io/flannel-io/flannel:v0.27.4", Save: "flannel.tar"},
{Name: "ghcr.io/flannel-io/flannel-cni-plugin:v1.8.0-flannel1", Fallback: "swr.cn-north-4.myhuaweicloud.com/ddn-k8s/ghcr.io/flannel-io/flannel-cni-plugin:v1.8.0-flannel1", Save: "flannel-cni-plugin.tar"},
{Name: "docker.io/longhornio/longhorn-engine:v1.10.0", Fallback: "docker-mirror.yizhisec.com/longhornio/longhorn-engine:v1.10.0", Save: "longhorn.longhorn-engine.tar"},
{Name: "docker.io/longhornio/longhorn-manager:v1.10.0", Fallback: "docker-mirror.yizhisec.com/longhornio/longhorn-manager:v1.10.0", Save: "longhorn.longhorn-manager.tar"},
{Name: "docker.io/longhornio/longhorn-instance-manager:v1.10.0", Fallback: "docker-mirror.yizhisec.com/longhornio/longhorn-instance-manager:v1.10.0", Save: "longhorn.longhorn-instance-manager.tar"},
{Name: "docker.io/longhornio/longhorn-share-manager:v1.10.0", Fallback: "docker-mirror.yizhisec.com/longhornio/longhorn-share-manager:v1.10.0", Save: "longhorn.longhorn-share-manager.tar"},
{Name: "docker.io/longhornio/longhorn-ui:v1.10.0", Fallback: "docker-mirror.yizhisec.com/longhornio/longhorn-ui:v1.10.0", Save: "longhorn.longhorn-ui.tar"},
{Name: "docker.io/longhornio/csi-snapshotter:v8.3.0-20250826", Fallback: "docker-mirror.yizhisec.com/longhornio/csi-snapshotter:v8.3.0-20250826", Save: "longhorn.csi-snapshotter.tar"},
{Name: "docker.io/longhornio/csi-resizer:v1.14.0-20250826", Fallback: "docker-mirror.yizhisec.com/longhornio/csi-resizer:v1.14.0-20250826", Save: "longhorn.csi-resizer.tar"},
{Name: "docker.io/longhornio/csi-provisioner:v5.3.0-20250826", Fallback: "docker-mirror.yizhisec.com/longhornio/csi-provisioner:v5.3.0-20250826", Save: "longhorn.csi-provisioner.tar"},
{Name: "docker.io/longhornio/livenessprobe:v2.16.0-20250826", Fallback: "docker-mirror.yizhisec.com/longhornio/livenessprobe:v2.16.0-20250826", Save: "longhorn.livenessprobe.tar"},
{Name: "docker.io/longhornio/csi-node-driver-registrar:v2.14.0-20250826", Fallback: "docker-mirror.yizhisec.com/longhornio/csi-node-driver-registrar:v2.14.0-20250826", Save: "longhorn.csi-node-driver-registrar.tar"},
{Name: "docker.io/longhornio/csi-attacher:v4.9.0-20250826", Fallback: "docker-mirror.yizhisec.com/longhornio/csi-attacher:v4.9.0-20250826", Save: "longhorn.csi-attacher.tar"},
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: "docker.io/bitpoke/mysql-operator:v0.6.3", Fallback: "docker-mirror.yizhisec.com/bitpoke/mysql-operator:v0.6.3", Save: "bitpoke.mysql-operator.v0.6.3.tar"},
{Name: "docker.io/bitpoke/mysql-operator-orchestrator:v0.6.3", Fallback: "docker-mirror.yizhisec.com/bitpoke/mysql-operator-orchestrator:v0.6.3", Save: "bitpoke.mysql-operator-orchestrator.v0.6.3.tar"},
{Name: "docker.io/bitpoke/mysql-operator-sidecar-8.0:v0.6.3", Fallback: "docker-mirror.yizhisec.com/bitpoke/mysql-operator-sidecar-8.0:v0.6.3", Save: "bitpoke.mysql-operator-sidecar-8.0.v0.6.3.tar"},
{Name: "docker.io/library/percona:8.0.28-20", Fallback: "docker-mirror.yizhisec.com/library/percona:8.0.28-20", Save: "percona.8.0.28-20.tar"},
{Name: "docker.io/prom/mysqld-exporter:v0.13.0", Fallback: "docker-mirror.yizhisec.com/prom/mysqld-exporter:v0.13.0", Save: "prom.mysqld-exporter.v0.13.0.tar"},
{Name: "docker.io/bitnami/redis:8.2.2", Fallback: "hub.yizhisec.com/external/bitnami/redis:8.2.2", Save: "bitnami.redis.8.2.2.tar"},
{Name: "hub.yizhisec.com/external/elasticsearch:7.17.28", Fallback: "", Save: "elasticsearch.7.17.28.tar"},
{Name: "hub.yizhisec.com/external/kibana:7.17.28", Fallback: "", Save: "kibana.7.17.28.tar"},
{Name: "hub.yizhisec.com/external/emqx:5.1", Fallback: "", Save: "emqx.5.1.tar"},
{Name: "hub.yizhisec.com/build/hybirdscope/front/admin:latest", Fallback: "", Save: "app.front.admin.tar", Force: true},
{Name: "hub.yizhisec.com/hybridscope/v2/front-user:latest", Fallback: "", Save: "app.front.user.tar", Force: true},
{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 {
image.Save = filepath.Join(opt.Cfg.Make.Dir, "dependency", "image", image.Save)
image.Save = filepath.Join(m.workdir, "dependency", "image", image.Save)
}
logger.Info("☑️ 开始获取镜像(s)...")
@@ -173,10 +125,7 @@ func (m *maker) Images(ctx context.Context) error {
opts := []ImageOpt{
WithImageFallback(image.Fallback),
WithImageSave(image.Save),
}
if image.Force {
opts = append(opts, WithImageForcePull())
WithImageForcePull(image.Force),
}
logger.Info("☑️ 获取镜像: %s", image.Name)

View File

@@ -3,12 +3,12 @@ package maker
import (
"testing"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/pkg/logger"
)
func TestImage(t *testing.T) {
logger.SetLogLevel(logger.LogLevelDebug)
m := NewMaker()
m := NewMaker("./x-data")
m.Image(t.Context(), "docker.io/nginx:1.29.3-alpine3.22",
WithImageFallback("swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/nginx:1.29.3-alpine3.22"),
WithImageSave("/root/nginx.tar"),

View File

@@ -5,15 +5,14 @@ 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/resource"
)
func (m *maker) LessDNS(ctx context.Context) error {
var (
err error
location = filepath.Join(opt.Cfg.Make.Dir, "dependency", "less-dns")
location = filepath.Join(m.workdir, "dependency", "less-dns")
)
logger.Info("☑️ maker.LessDNS: 开始构建 less-dns...")

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"
)
func (m *maker) Longhorn(ctx context.Context, replica int) error {
@@ -24,18 +24,18 @@ persistence:
)
var (
err error
chartURL = "https://artifactory.yizhisec.com:443/artifactory/filestore/hsv3/charts/longhorn-1.10.0.tgz"
longhornDir = filepath.Join(opt.Cfg.Make.Dir, "dependency", "longhorn")
chartFile = filepath.Join(longhornDir, "longhorn-1.10.0.tgz")
valuesFile = filepath.Join(longhornDir, "values.yaml")
err error
chartURL = "https://artifactory.yizhisec.com:443/artifactory/filestore/hsv3/charts/longhorn-1.10.0.tgz"
location = filepath.Join(m.workdir, "dependency", "longhorn")
chartFile = filepath.Join(location, "longhorn-1.10.0.tgz")
valuesFile = filepath.Join(location, "values.yaml")
)
logger.Info("☑️ 开始准备 Longhorn 资源...")
logger.Debug("下载地址: %s", chartURL)
logger.Debug("目标目录: %s", longhornDir)
logger.Debug("目标目录: %s", location)
if err = os.MkdirAll(filepath.Join(opt.Cfg.Make.Dir, "dependency", "longhorn"), 0755); err != nil {
if err = os.MkdirAll(filepath.Join(m.workdir, "dependency", "longhorn"), 0755); err != nil {
return err
}
@@ -58,6 +58,36 @@ persistence:
return err
}
var images = []*model.Image{
{Name: "docker.io/longhornio/longhorn-engine:v1.10.0", Fallback: "docker-mirror.yizhisec.com/longhornio/longhorn-engine:v1.10.0", Save: "longhorn.longhorn-engine.tar"},
{Name: "docker.io/longhornio/longhorn-manager:v1.10.0", Fallback: "docker-mirror.yizhisec.com/longhornio/longhorn-manager:v1.10.0", Save: "longhorn.longhorn-manager.tar"},
{Name: "docker.io/longhornio/longhorn-instance-manager:v1.10.0", Fallback: "docker-mirror.yizhisec.com/longhornio/longhorn-instance-manager:v1.10.0", Save: "longhorn.longhorn-instance-manager.tar"},
{Name: "docker.io/longhornio/longhorn-share-manager:v1.10.0", Fallback: "docker-mirror.yizhisec.com/longhornio/longhorn-share-manager:v1.10.0", Save: "longhorn.longhorn-share-manager.tar"},
{Name: "docker.io/longhornio/longhorn-ui:v1.10.0", Fallback: "docker-mirror.yizhisec.com/longhornio/longhorn-ui:v1.10.0", Save: "longhorn.longhorn-ui.tar"},
{Name: "docker.io/longhornio/csi-snapshotter:v8.3.0-20250826", Fallback: "docker-mirror.yizhisec.com/longhornio/csi-snapshotter:v8.3.0-20250826", Save: "longhorn.csi-snapshotter.tar"},
{Name: "docker.io/longhornio/csi-resizer:v1.14.0-20250826", Fallback: "docker-mirror.yizhisec.com/longhornio/csi-resizer:v1.14.0-20250826", Save: "longhorn.csi-resizer.tar"},
{Name: "docker.io/longhornio/csi-provisioner:v5.3.0-20250826", Fallback: "docker-mirror.yizhisec.com/longhornio/csi-provisioner:v5.3.0-20250826", Save: "longhorn.csi-provisioner.tar"},
{Name: "docker.io/longhornio/livenessprobe:v2.16.0-20250826", Fallback: "docker-mirror.yizhisec.com/longhornio/livenessprobe:v2.16.0-20250826", Save: "longhorn.livenessprobe.tar"},
{Name: "docker.io/longhornio/csi-node-driver-registrar:v2.14.0-20250826", Fallback: "docker-mirror.yizhisec.com/longhornio/csi-node-driver-registrar:v2.14.0-20250826", Save: "longhorn.csi-node-driver-registrar.tar"},
{Name: "docker.io/longhornio/csi-attacher:v4.9.0-20250826", Fallback: "docker-mirror.yizhisec.com/longhornio/csi-attacher:v4.9.0-20250826", Save: "longhorn.csi-attacher.tar"},
}
logger.Debug("MakeLonghorn: 开始获取镜像...")
for _, image := range images {
opts := []ImageOpt{
WithImageFallback(image.Fallback),
WithImageSave(filepath.Join(location, image.Save)),
WithImageForcePull(image.Force),
}
if err := m.Image(ctx, image.Name, opts...); err != nil {
logger.Error("❌ MakeLonghorn: 获取镜像失败: %s, 可以手动获取后重试", image.Name)
logger.Debug("❌ MakeLonghorn: 获取镜像失败: %s, %v", image.Name, err)
return err
}
}
logger.Debug("MakeLonghorn: 获取镜像成功!!!")
logger.Info("✅ 成功创建 longhorn 资源!!!")
return nil

View File

@@ -1,7 +1,9 @@
package maker
type maker struct{}
func NewMaker() *maker {
return &maker{}
type maker struct {
workdir string
}
func NewMaker(workdir string) *maker {
return &maker{workdir: workdir}
}

View File

@@ -0,0 +1,63 @@
package maker
import (
"context"
"fmt"
"os"
"path/filepath"
"yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model"
"yizhisec.com/hsv2/forge/pkg/resource"
)
// todo, remake minio-init image
func (m *maker) Minio(ctx context.Context, storage string) error {
var (
err error
workdir = filepath.Join(m.workdir, "dependency", "minio")
)
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", workdir)
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 {
logger.Debug("❌ maker.Minio: 写入资源文件失败, filename = %s, err = %v", filename, err)
return err
}
logger.Debug("✅ maker.Minio: 准备资源文件成功, filename = %s", filename)
logger.Debug("☑️ maker.Minio: 开始获取所需镜像...")
var images = []*model.Image{
{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(workdir, image.Save)),
WithImageForcePull(image.Force),
}
if err := m.Image(ctx, image.Name, opts...); err != nil {
logger.Error("❌ maker.Minio: 获取镜像失败: %s, 可以手动获取后重试", image.Name)
logger.Debug("❌ maker.Minio: 获取镜像失败: %s, %v", image.Name, err)
return err
}
}
logger.Debug("✅ maker.Minio: 获取所需镜像成功!!!")
logger.Info("✅ maker.Minio: 构建 minio 依赖成功, workdir = %s", workdir)
return nil
}

View File

@@ -6,11 +6,11 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"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"
)
type MysqlOpt func(*mysqlOpt)
@@ -30,7 +30,7 @@ func WithMySQLReplica(replica int) MysqlOpt {
func WithMySQLStorage(storage string) MysqlOpt {
return func(o *mysqlOpt) {
// validate Kubernetes storage size string (e.g., "50Gi", "100Mi")
if matched, _ := regexp.MatchString(`^\d+(\.\d+)?[EPTGMK]i?$`, storage); matched {
if opt.StorageSizeReg.MatchString(storage) {
o.Storage = storage
}
}
@@ -74,14 +74,14 @@ spec:
resources:
requests:
storage: %s
podSpec:
resources:
requests:
cpu: "250m"
memory: "250Mi"
limits:
cpu: "2"
memory: "4Gi"
podSpec:
resources:
requests:
cpu: "250m"
memory: "250Mi"
limits:
cpu: "2"
memory: "4Gi"
# affinity:
# podAntiAffinity:
# requiredDuringSchedulingIgnoredDuringExecution: # 强制规则
@@ -95,7 +95,6 @@ spec:
# values: ["mysql-cluster"]
# topologyKey: "kubernetes.io/hostname" # 确保不同节点
---
apiVersion: mysql.presslabs.org/v1alpha1
kind: MysqlDatabase
metadata:
@@ -118,9 +117,9 @@ spec:
err error
chartURL = "https://artifactory.yizhisec.com:443/artifactory/filestore/hsv3/charts/mysql-operator-0.6.3.tgz"
mysqlDir = filepath.Join(opt.Cfg.Make.Dir, "dependency", "mysql")
chartFile = filepath.Join(mysqlDir, "mysql-operator-0.6.3.tgz")
clusterFile = filepath.Join(mysqlDir, "cluster.yaml")
location = filepath.Join(m.workdir, "dependency", "mysql")
chartFile = filepath.Join(location, "mysql-operator-0.6.3.tgz")
clusterFile = filepath.Join(location, "cluster.yaml")
)
for _, fn := range opts {
@@ -129,13 +128,13 @@ spec:
logger.Info("☑️ 开始构建 MySQL 资源...")
if err = os.MkdirAll(mysqlDir, 0755); err != nil {
if err = os.MkdirAll(location, 0755); err != nil {
return err
}
o.Password = base64.StdEncoding.EncodeToString([]byte(o.Password))
logger.Debug("正在下载 MySQL operator chart..., url = %s, dir = %s", chartURL, mysqlDir)
logger.Debug("正在下载 MySQL operator chart..., url = %s, dir = %s", chartURL, location)
if err = downloader.Download(
ctx,
chartURL,
@@ -158,6 +157,31 @@ spec:
logger.Debug("✅ 成功创建 database.yaml")
logger.Debug("☑️ MakeMysql: 开始获取所需镜像...")
var images = []*model.Image{
{Name: "docker.io/bitpoke/mysql-operator:v0.6.3", Fallback: "docker-mirror.yizhisec.com/bitpoke/mysql-operator:v0.6.3", Save: "bitpoke.mysql-operator.v0.6.3.tar"},
{Name: "docker.io/bitpoke/mysql-operator-orchestrator:v0.6.3", Fallback: "docker-mirror.yizhisec.com/bitpoke/mysql-operator-orchestrator:v0.6.3", Save: "bitpoke.mysql-operator-orchestrator.v0.6.3.tar"},
{Name: "docker.io/bitpoke/mysql-operator-sidecar-8.0:v0.6.3", Fallback: "docker-mirror.yizhisec.com/bitpoke/mysql-operator-sidecar-8.0:v0.6.3", Save: "bitpoke.mysql-operator-sidecar-8.0.v0.6.3.tar"},
{Name: "docker.io/library/percona:8.0.28-20", Fallback: "docker-mirror.yizhisec.com/library/percona:8.0.28-20", Save: "percona.8.0.28-20.tar"},
{Name: "docker.io/prom/mysqld-exporter:v0.13.0", Fallback: "docker-mirror.yizhisec.com/prom/mysqld-exporter:v0.13.0", Save: "prom.mysqld-exporter.v0.13.0.tar"},
}
for _, image := range images {
opts := []ImageOpt{
WithImageFallback(image.Fallback),
WithImageSave(filepath.Join(location, image.Save)),
WithImageForcePull(image.Force),
}
if err := m.Image(ctx, image.Name, opts...); err != nil {
logger.Error("❌ MakeMysql: 获取镜像失败: %s, 可以手动获取后重试", image.Name)
logger.Debug("❌ MakeMysql: 获取镜像失败: %s, %v", image.Name, err)
return err
}
}
logger.Debug("✅ MakeMysql: 获取镜像成功!!!")
logger.Info("✅ MySQL 资源构建成功!!!")
return nil

View File

@@ -0,0 +1,147 @@
package maker
import (
"context"
"encoding/json"
"os"
"path/filepath"
"yizhisec.com/hsv2/forge/pkg/downloader"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model"
)
func (m *maker) Proxy(ctx context.Context) error {
const (
binURL = "https://artifactory.yizhisec.com:443/artifactory/filestore/hsv2/bin/caddy"
systemdSvc = `[Unit]
Description=YiZhiSec Caddy Reverse Proxy
After=network.target
[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/caddy run --config /etc/caddy/caddy.json
StandardOutput=journal
StandardError=journal
Nice=-20
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target`
)
location := filepath.Join(m.workdir, "dependency", "proxy")
logger.Info("☑️ maker.Proxy: 开始构建 caddy 反向代理...")
logger.Debug("☑️ maker.Proxy: 创建目录 %s", location)
if err := os.MkdirAll(location, 0755); err != nil {
logger.Debug("❌ maker.Proxy: 创建目录失败: %v", err)
return err
}
logger.Debug("✅ maker.Proxy: 创建目录 %s 成功", location)
logger.Debug("☑️ maker.Proxy: 下载 caddy 二进制..., url = %s, dest = %s", binURL, filepath.Join(location, "caddy"))
if err := downloader.Download(
ctx,
binURL,
filepath.Join(location, "caddy"),
downloader.WithInsecureSkipVerify(),
downloader.WithFileMode(0755),
); err != nil {
logger.Debug("❌ maker.Proxy: 下载 caddy 失败, url = %s, err = %v", binURL, err)
return err
}
logger.Debug("✅ maker.Proxy: 下载 caddy 成功, url = %s", binURL)
logger.Debug("☑️ maker.Proxy: 写入 caddy.json 文件..., dest = %s", filepath.Join(location, "caddy.json"))
caddyConfig := model.CaddyConfig{
"apps": &model.CaddyApp{
Layer4: &model.CaddyLayer4{
Servers: map[string]*model.CaddyServer{
"proxy_8443": {
Listen: []string{":8443"},
Routes: []*model.CaddyRoute{
{
Handle: []*model.CaddyHandle{
{
Handler: "proxy",
Upstreams: []*model.CaddyUpstream{
{Dial: []string{"__ip_1__:32443"}},
{Dial: []string{"__ip_2__:32443"}},
},
HealthChecks: &model.CaddyHealthCheck{
Active: &model.CaddyActive{
Interval: "10s",
Timeout: "2s",
Port: 32443,
},
Passive: &model.CaddyPassive{
FailDuration: "30s",
MaxFails: 2,
},
},
LoadBalancing: &model.CaddyLoadBalancing{
Selection: &model.CaddySelection{
Policy: "round_robin",
},
},
},
},
},
},
},
"proxy_443": {
Listen: []string{":443"},
Routes: []*model.CaddyRoute{
{
Handle: []*model.CaddyHandle{
{
Handler: "proxy",
Upstreams: []*model.CaddyUpstream{
{Dial: []string{"__ip_1__:31443"}},
{Dial: []string{"__ip_2__:31443"}},
},
HealthChecks: &model.CaddyHealthCheck{
Active: &model.CaddyActive{
Interval: "10s",
Timeout: "2s",
Port: 31443,
},
Passive: &model.CaddyPassive{
FailDuration: "30s",
MaxFails: 2,
},
},
LoadBalancing: &model.CaddyLoadBalancing{
Selection: &model.CaddySelection{
Policy: "round_robin",
},
},
},
},
},
},
},
},
},
},
}
bs, _ := json.MarshalIndent(caddyConfig, "", " ")
if err := os.WriteFile(filepath.Join(location, "caddy.json"), []byte(bs), 0644); err != nil {
logger.Debug("❌ maker.Proxy: 写入 Caddyfile 失败, dest = %s, err = %v", filepath.Join(location, "caddy.json"), err)
return err
}
logger.Debug("✅ maker.Proxy: 写入 Caddyfile 文件成功, dest = %s", filepath.Join(location, "caddy.json"))
logger.Debug("☑️ maker.Proxy: 写入 caddy.service 文件..., dest = %s", filepath.Join(location, "caddy.service"))
if err := os.WriteFile(filepath.Join(location, "caddy.service"), []byte(systemdSvc), 0644); err != nil {
logger.Debug("❌ maker.Proxy: 写入 caddy.service 失败, dest = %s, err = %v", filepath.Join(location, "caddy.service"), err)
return err
}
logger.Debug("✅ maker.Proxy: 写入 caddy.service 文件成功, dest = %s", filepath.Join(location, "caddy.service"))
logger.Info("✅ maker.Proxy: 构建 caddy 反向代理成功!!!")
return nil
}

View File

@@ -5,11 +5,11 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"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"
)
type RedisOpt func(*redisOpt)
@@ -38,7 +38,7 @@ func WithRedisPassword(password string) RedisOpt {
func WithRedisStorage(storage string) RedisOpt {
return func(o *redisOpt) {
if matched, _ := regexp.MatchString(`^\d+(\.\d+)?[EPTGMK]i?$`, storage); matched {
if opt.StorageSizeReg.MatchString(storage) {
o.Storage = storage
}
}
@@ -91,18 +91,18 @@ metrics:
fn(o)
}
redisDir := filepath.Join(opt.Cfg.Make.Dir, "dependency", "redis")
chartFile := filepath.Join(redisDir, chartFilename)
valuesFile := filepath.Join(redisDir, "values.yaml")
location := filepath.Join(m.workdir, "dependency", "redis")
chartFile := filepath.Join(location, chartFilename)
valuesFile := filepath.Join(location, "values.yaml")
logger.Info("☑️ maker.Redis: 开始构建 Redis 资源...")
logger.Debug("☑️ maker.Redis: 开始构建 Redis 资源..., opt = %#v", o)
logger.Debug("☑️ maker.Redis: 创建目录 %s", redisDir)
if err := os.MkdirAll(redisDir, 0755); err != nil {
logger.Debug("☑️ maker.Redis: 创建目录 %s", location)
if err := os.MkdirAll(location, 0755); err != nil {
logger.Debug("❌ maker.Redis: 创建目录失败: %v", err)
return err
}
logger.Debug("✅ maker.Redis: 创建目录 %s 成功", redisDir)
logger.Debug("✅ maker.Redis: 创建目录 %s 成功", location)
logger.Debug("☑️ maker.Redis: 下载 Redis chart..., url = %s, dest = %s", chartURL, chartFile)
if err := downloader.Download(
@@ -124,6 +124,14 @@ metrics:
}
logger.Debug("✅ maker.Redis: 写入 values.yaml 文件成功, dest = %s", valuesFile)
logger.Debug("☑️ maker.Redis: 开始获取镜像...")
img := &model.Image{Name: "docker.io/bitnami/redis:8.2.2", Fallback: "hub.yizhisec.com/external/bitnami/redis:8.2.2", Save: "bitnami.redis.8.2.2.tar"}
if err := m.Image(ctx, img.Name, WithImageFallback(img.Fallback), WithImageSave(filepath.Join(location, img.Save))); err != nil {
logger.Debug("❌ maker.Redis: 获取镜像失败: %s, %v", img.Name, err)
return err
}
logger.Debug("✅ maker.Redis: 获取镜像成功!!!")
logger.Info("✅ maker.Redis: 构建 Redis 资源成功!!!")
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"
)
func (m *maker) Registry(ctx context.Context, storage string) error {
const (
_registryToml = `
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = ""
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."10.96.123.45:80"]
endpoint = ["http://10.96.123.45:80"]
[plugins."io.containerd.grpc.v1.cri".registry.configs]
[plugins."io.containerd.grpc.v1.cri".registry.configs."10.96.123.45:80".tls]
insecure_skip_verify = true
[plugins."io.containerd.grpc.v1.cri".registry.configs."10.96.123.45:80".auth]`
)
var (
err error
location = filepath.Join(m.workdir, "dependency", "registry")
imgName = "docker.io/library/registry:2.8.3"
uiImgName = "docker.io/quiq/registry-ui:0.11.0"
imgFallback = "docker-mirror.yizhisec.com/library/registry:2.8.3"
uiImgFallback = "docker-mirror.yizhisec.com/quiq/registry-ui:0.11.0"
)
logger.Info("☑️ 开始创建依赖: registry...")
logger.Debug("☑️ 创建 dir: %s ...", location)
if err = os.MkdirAll(location, 0755); err != nil {
logger.Debug("❌ 创建 dir %s 失败: %v", location, err)
return err
}
yamlFile := filepath.Join(location, "registry.yaml")
content := fmt.Sprintf(resource.YAMLRegistry, storage)
logger.Debug("写入 yaml 文件: %s ...", yamlFile)
if os.WriteFile(yamlFile, []byte(content), 0644); err != nil {
logger.Debug("❌ 写入 yaml 文件 %s 失败: %v", yamlFile, err)
return err
}
logger.Debug("开始准备镜像...")
imgFile := filepath.Join(location, "registry.tar")
if err = m.Image(ctx, imgName, WithImageFallback(imgFallback), WithImageSave(imgFile)); err != nil {
logger.Debug("❌ 准备镜像 %s 失败: %v", imgName, err)
return err
}
imgFile = filepath.Join(location, "registry-ui.tar")
if err = m.Image(ctx, uiImgName, WithImageFallback(uiImgFallback), WithImageSave(imgFile)); err != nil {
logger.Debug("❌ 准备镜像 %s 失败: %v", uiImgName, err)
return err
}
logger.Debug("写入 registry.toml 文件: %s ...", filepath.Join(location, "registry.toml"))
if os.WriteFile(filepath.Join(location, "registry.toml"), []byte(_registryToml), 0644); err != nil {
logger.Debug("❌ 写入 registry.toml 文件 %s 失败: %v", filepath.Join(location, "registry.toml"), err)
return err
}
logger.Info("✅ 创建依赖成功: registry!!!")
return nil
}

View File

@@ -0,0 +1,212 @@
package maker
import (
"context"
"fmt"
"os"
"path/filepath"
"yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/logger"
"yizhisec.com/hsv2/forge/pkg/model"
"yizhisec.com/hsv2/forge/pkg/resource"
)
type SeafileOpt func(*seafileOpt)
type seafileOpt struct {
DBHost string
DBPassword string
AdminEmail string
AdminPassword string
ServerHostname string
Storage string
}
func WithSeafileStorage(storage string) SeafileOpt {
return func(o *seafileOpt) {
if opt.StorageSizeReg.MatchString(storage) {
o.Storage = storage
}
}
}
func WithSeafileDBHost(host string) SeafileOpt {
return func(o *seafileOpt) {
if o.DBHost == "" {
o.DBHost = host
}
}
}
func WithSeafileDBPassword(password string) SeafileOpt {
return func(o *seafileOpt) {
if o.DBPassword == "" {
o.DBPassword = password
}
}
}
func WithSeafileAdminEmail(email string) SeafileOpt {
return func(o *seafileOpt) {
if opt.EmailReg.MatchString(email) {
o.AdminEmail = email
}
}
}
func WithSeafileAdminPassword(password string) SeafileOpt {
return func(o *seafileOpt) {
if o.AdminPassword == "" {
o.AdminPassword = password
}
}
}
func WithSeafileHostname(hostname string) SeafileOpt {
return func(o *seafileOpt) {
if o.ServerHostname == "" {
o.ServerHostname = hostname
}
}
}
func (m *maker) Seafile(ctx context.Context, opts ...SeafileOpt) error {
const (
_config = `
ControllerServer:
UserManagement: user-service.hsv2:9013
Database:
Mysql:
Address: %s:3306
DBName: backup_server
Password: %s
SeafileDBName: seafile_db
UserName: root
Log:
Dir: ./log
Level: 1
Name: hs_backup_seafile
SeafileServer:
Admin: %s
AdminPassword: %s
BackupDir: /seafile/backup_data
Host: seafile-service
Port: 80
StorageDir: /seafile/storage
Sentry:
DSN: https://fd7149f063c211eda2b50242ac15001c@sentry.yizhisec.com:13443/7
TracesSampleRate: 1
Web:
Host: 0.0.0.0
Mode: release
Port: 9027
WorkDir: /yizhisec/hs_backup_seafile/workspace
YosGuard:
Host: 172.17.0.1
Port: 7788`
_upsert = `#!/bin/bash
kubectl create configmap config-backup-seafile --namespace seafile --from-file=config.yml=./config.yml --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap nginx-seafile --namespace hsv2 --from-file=seafile.conf=./nginx.conf --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f deployment.yaml
kubectl rollout restart deployment backup-seafile-deployment -n seafile`
)
var (
err error
o = &seafileOpt{
DBHost: "mysql-cluster-mysql-master.db-mysql",
DBPassword: "L0hMysql.",
AdminEmail: "admin@yizhisec.com",
AdminPassword: "asecret",
ServerHostname: "cloud.hybridscope.com",
Storage: "50Gi",
}
workdir = filepath.Join(m.workdir, "dependency", "seafile")
)
for _, fn := range opts {
fn(o)
}
logger.Info("☑️ maker.Seafile: 开始构建 seafile 依赖, dir = %s", workdir)
// 1. 准备工作目录
logger.Debug("☑️ make.Seafile: 准备工作目录: %s", workdir)
if err = os.MkdirAll(workdir, 0755); err != nil {
logger.Error("❌ make.Seafile: 准备工作目录: %s 失败, err = %v", workdir, err)
return err
}
logger.Debug("✅ make.Seafile: 准备工作目录: %s 成功", workdir)
// 2. seafile yaml
logger.Debug("☑️ make.Seafile: 准备 seafile yaml")
bs := []byte(fmt.Sprintf(resource.YAMLSeafile, o.DBHost, o.DBPassword, o.AdminEmail, o.AdminPassword, o.ServerHostname, o.Storage))
if err = os.WriteFile(filepath.Join(workdir, "seafile.yaml"), bs, 0644); err != nil {
logger.Error("❌ make.Seafile: 准备 seafile yaml: %s 失败, err = %v", filepath.Join(workdir, "seafile.yaml"), err)
return err
}
logger.Debug("✅ make.Seafile: 准备 seafile yaml 成功")
// 3. backup-seafile deployment
logger.Debug("☑️ make.Seafile: 准备 backup-seafile deployment")
bs = []byte(resource.YAMLBackupSeafile)
if err = os.WriteFile(filepath.Join(workdir, "deployment.yaml"), bs, 0644); err != nil {
logger.Error("❌ make.Seafile: 准备 backup-seafile deployment: %s 失败, err = %v", filepath.Join(workdir, "deployment.yaml"), err)
return err
}
logger.Debug("✅ make.Seafile: 准备 backup-seafile deployment 成功")
// 4. config.yml
logger.Debug("☑️ make.Seafile: 准备 config.yml")
bs = []byte(fmt.Sprintf(_config, o.DBHost, o.DBPassword, o.AdminEmail, o.AdminPassword))
if err = os.WriteFile(filepath.Join(workdir, "config.yml"), bs, 0644); err != nil {
logger.Error("❌ make.Seafile: 准备 config.yml: %s 失败, err = %v", filepath.Join(workdir, "config.yml"), err)
return err
}
logger.Debug("✅ make.Seafile: 准备 config.yml 成功")
// 5. seafile.conf
logger.Debug("☑️ make.Seafile: 准备 seafile.conf")
bs = resource.NGINXSeafile
if err = os.WriteFile(filepath.Join(workdir, "seafile.conf"), bs, 0644); err != nil {
logger.Error("❌ make.Seafile: 准备 seafile.conf: %s 失败, err = %v", filepath.Join(workdir, "seafile.conf"), err)
return err
}
logger.Debug("✅ make.Seafile: 准备 seafile.conf 成功")
// 6. upsert.sh
logger.Debug("☑️ make.Seafile: 准备 upsert.sh")
bs = []byte(_upsert)
if err = os.WriteFile(filepath.Join(workdir, "upsert.sh"), bs, 0755); err != nil {
logger.Error("❌ make.Seafile: 准备 upsert.sh: %s 失败, err = %v", filepath.Join(workdir, "upsert.sh"), err)
return err
}
logger.Debug("✅ make.Seafile: 准备 upsert.sh 成功")
// 7. prepare images
logger.Debug("☑️ make.Seafile: 准备 images")
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
}
var images = []*model.Image{
{Name: "hub.yizhisec.com/hybridscope/hs_backup_seafile:latest", Fallback: "", Save: "seafile.backup_seafile.tar", Force: true},
{Name: "hub.yizhisec.com/product/hybridscope/memcached:latest", Fallback: "", Save: "seafile.memcached.tar"},
{Name: "hub.yizhisec.com/product/hybridscope/seafile-mc:latest", Fallback: "", Save: "seafile.seafile_mc.tar"},
}
for _, img := range images {
img.Save = filepath.Join(imgDir, img.Save)
if err = m.Image(ctx, img.Name, WithImageSave(img.Save), WithImageForcePull(img.Force)); err != nil {
logger.Error("❌ make.Seafile: 准备 image: %s 失败, err = %v", img.Name, err)
return err
}
}
logger.Debug("✅ make.Seafile: 准备 images 成功")
logger.Info("✅ maker.Seafile: 构建 seafile 依赖成功!!!")
return nil
}

View File

@@ -2,47 +2,39 @@ package maker
import (
"context"
"fmt"
"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/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 (
configTemplate = `
_config = `
Web:
# default listen in docker0
Host: 172.17.0.1
Host: __ip__
Port: 7788
UUIDFilePath: /etc/yosguard/uuid
# 心跳间隔: 单位秒,默认为5
HeartbeatDuration: 5
# 控制器 yosguard 地址
ControllerServer:
Host: dasheng.zhsftech.debug
Port: 443
# True: 作为控制器运行; False: 不作为控制器运行
AsController: true
# True: 作为网关运行; False: 不作为网关运行
AsGateway: false
Database:
SQLite:
DBPath: "/etc/yosguard/db/yosguard.db"
SQLPath: "/etc/yosguard/db/create.sql"`
DBPath: /etc/yosguard/db/yosguard.db
HeartbeatDuration: 5
UUIDFilePath: /etc/yosguard/uuid
`
systemdService = `
[Unit]
@@ -64,10 +56,23 @@ WantedBy=multi-user.target`
binURL = "https://artifactory.yizhisec.com:443/artifactory/filestore/hsv2/bin/yosguard"
)
location := filepath.Join(opt.Cfg.Make.Dir, "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
@@ -87,12 +92,12 @@ WantedBy=multi-user.target`
}
logger.Debug("✅ maker.Yosguard: 下载 yosguard 成功, url = %s", binURL)
logger.Debug("☑️ maker.Yosguard: 写入 config_template.yml 文件..., dest = %s", filepath.Join(location, "config_template.yml"))
if err := os.WriteFile(filepath.Join(location, "config_template.yml"), []byte(configTemplate), 0644); err != nil {
logger.Debug("❌ maker.Yosguard: 写入 config_template.yml 失败, dest = %s, err = %v", filepath.Join(location, "config_template.yml"), err)
logger.Debug("☑️ maker.Yosguard: 写入 config.yml 文件..., dest = %s", filepath.Join(location, "config.yml"))
if err := os.WriteFile(filepath.Join(location, "config.yml"), []byte(_config), 0644); err != nil {
logger.Debug("❌ maker.Yosguard: 写入 config.yml 失败, dest = %s, err = %v", filepath.Join(location, "config.yml"), err)
return err
}
logger.Debug("✅ maker.Yosguard: 写入 config_template.yml 文件成功, dest = %s", filepath.Join(location, "config_template.yml"))
logger.Debug("✅ maker.Yosguard: 写入 config.yml 文件成功, dest = %s", filepath.Join(location, "config.yml"))
logger.Debug("☑️ maker.Yosguard: 写入 create.sql 文件..., dest = %s", filepath.Join(location, "create.sql"))
if err := os.WriteFile(filepath.Join(location, "create.sql"), resource.SQLYosguard, 0644); err != nil {
@@ -108,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
}

View File

@@ -1,5 +1,7 @@
package opt
import "regexp"
type config struct {
Debug bool
Make struct {
@@ -11,3 +13,12 @@ type config struct {
var (
Cfg = &config{}
)
const (
DefaultWorkdir = "/root/hsv2-installation"
)
var (
StorageSizeReg = regexp.MustCompile(`^\d+(\.\d+)?[EPTGMK]i?$`)
EmailReg = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
)

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

@@ -5,10 +5,15 @@ import (
"os"
"os/signal"
"syscall"
"time"
"yizhisec.com/hsv2/forge/internal/cmd"
)
func init() {
time.Local = time.FixedZone("CST", 8*60*60)
}
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()

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 {

648
pkg/imager/pull.go Normal file
View File

@@ -0,0 +1,648 @@
package imager
import (
"archive/tar"
"compress/gzip"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"yizhisec.com/hsv2/forge/pkg/logger"
)
// PullOption is a functional option for configuring image pull
type PullOption func(*pullOption)
type pullOption struct {
Proxy string // http or socks5 proxy
Rename string // pull image and rename it
PlainHTTP bool // use http instead of https
SkipTLSVerify bool // skip TLS certificate verification
Username string // registry username for authentication
Password string // registry password for authentication
RetryTimes int // retry times for sync/proxy registries, default is 3
RetryDelay time.Duration // delay between retries, default is 2 seconds
}
// WithProxy sets the proxy for pulling images
func WithProxy(proxy string) PullOption {
return func(o *pullOption) {
o.Proxy = proxy
}
}
// WithRename sets a new name for the pulled image
func WithRename(name string) PullOption {
return func(o *pullOption) {
o.Rename = name
}
}
// WithPlainHTTP enables plain HTTP (no TLS)
func WithPlainHTTP() PullOption {
return func(o *pullOption) {
o.PlainHTTP = true
}
}
// WithSkipTLSVerify skips TLS certificate verification
func WithSkipTLSVerify() PullOption {
return func(o *pullOption) {
o.SkipTLSVerify = true
}
}
// WithAuth sets authentication credentials
func WithAuth(username, password string) PullOption {
return func(o *pullOption) {
o.Username = username
o.Password = password
}
}
// WithRetry sets the retry times for pulling images
func WithRetry(times int) PullOption {
return func(o *pullOption) {
if times < 0 {
times = 0
}
o.RetryTimes = times
}
}
// WithRetryDelay sets the delay between retries
func WithRetryDelay(delay time.Duration) PullOption {
return func(o *pullOption) {
o.RetryDelay = delay
}
}
// manifestV2 represents Docker manifest v2 schema
type manifestV2 struct {
SchemaVersion int `json:"schemaVersion"`
MediaType string `json:"mediaType"`
Config struct {
MediaType string `json:"mediaType"`
Size int64 `json:"size"`
Digest string `json:"digest"`
} `json:"config"`
Layers []struct {
MediaType string `json:"mediaType"`
Size int64 `json:"size"`
Digest string `json:"digest"`
} `json:"layers"`
}
// imageReference parses image reference into registry, repository, and tag
type imageReference struct {
Registry string
Repository string
Tag string
}
// parseImageReference parses an image name into its components
func parseImageReference(name string) (*imageReference, error) {
ref := &imageReference{
Tag: "latest",
}
// First, split by / to separate registry from repository
// This handles cases like localhost:5000/myimage:tag
slashParts := strings.SplitN(name, "/", 2)
var registryPart, repoPart string
if len(slashParts) == 1 {
// No slash, it's just image:tag (e.g., alpine:3.19)
repoPart = slashParts[0]
ref.Registry = "registry-1.docker.io"
} else {
// Has slash, check if first part is a registry
// Registry contains . or : (for host:port)
if strings.Contains(slashParts[0], ".") || strings.Contains(slashParts[0], ":") {
// First part is a registry
registryPart = slashParts[0]
repoPart = slashParts[1]
ref.Registry = registryPart
} else {
// First part is namespace (e.g., library/alpine)
repoPart = name
ref.Registry = "registry-1.docker.io"
}
}
// Now parse the repository and tag from repoPart
// Split on last : to get tag (in case repository name contains /)
lastColon := strings.LastIndex(repoPart, ":")
if lastColon > 0 {
ref.Repository = repoPart[:lastColon]
ref.Tag = repoPart[lastColon+1:]
} else {
ref.Repository = repoPart
}
// For docker.io, add library/ prefix if no namespace
if ref.Registry == "registry-1.docker.io" && !strings.Contains(ref.Repository, "/") {
ref.Repository = "library/" + ref.Repository
}
return ref, nil
}
// PullImage pulls an OCI image using HTTP requests and saves it as a tar archive
func PullImage(ctx context.Context, name string, store io.Writer, opts ...PullOption) error {
options := &pullOption{
RetryTimes: 3, // default retry times
RetryDelay: 2 * time.Second, // default retry delay
}
for _, opt := range opts {
opt(options)
}
logger.Debug("Pulling image: %s with options: %+v", name, options)
// Parse image reference
ref, err := parseImageReference(name)
if err != nil {
return fmt.Errorf("failed to parse image reference: %w", err)
}
logger.Debug("Parsed image reference: registry=%s, repository=%s, tag=%s", ref.Registry, ref.Repository, ref.Tag)
// Create HTTP client
client := createHTTPClient(options)
// Retry logic for sync/proxy registries
var lastErr error
for attempt := 0; attempt <= options.RetryTimes; attempt++ {
if attempt > 0 {
logger.Debug("Retry attempt %d/%d for image: %s", attempt, options.RetryTimes, name)
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(options.RetryDelay):
// Continue after delay
}
}
// Get auth token if needed
token, err := getAuthToken(ctx, client, ref, options)
if err != nil {
lastErr = fmt.Errorf("failed to get auth token: %w", err)
logger.Debug("Attempt %d failed: %v", attempt+1, lastErr)
continue
}
// Get manifest
manifest, err := getManifest(ctx, client, ref, token, options)
if err != nil {
lastErr = fmt.Errorf("failed to get manifest: %w", err)
logger.Debug("Attempt %d failed: %v", attempt+1, lastErr)
continue
}
logger.Debug("Got manifest with %d layers", len(manifest.Layers))
// Download and create tar archive
if err := createImageTar(ctx, client, ref, manifest, token, store, name, options); err != nil {
lastErr = fmt.Errorf("failed to create image tar: %w", err)
logger.Debug("Attempt %d failed: %v", attempt+1, lastErr)
continue
}
// Success!
logger.Info("Successfully pulled image: %s", name)
return nil
}
// All retries exhausted
if options.RetryTimes > 0 {
return fmt.Errorf("failed to pull image after %d retries: %w", options.RetryTimes+1, lastErr)
}
return lastErr
}
// createHTTPClient creates an HTTP client with the given options
func createHTTPClient(options *pullOption) *http.Client {
client := &http.Client{}
// Configure TLS
if options.SkipTLSVerify {
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client.Transport = transport
}
// Configure proxy
if options.Proxy != "" {
proxyURL, err := url.Parse(options.Proxy)
if err == nil {
if client.Transport == nil {
client.Transport = &http.Transport{}
}
if transport, ok := client.Transport.(*http.Transport); ok {
transport.Proxy = http.ProxyURL(proxyURL)
}
}
}
return client
}
// getAuthToken gets authentication token from registry
func getAuthToken(ctx context.Context, client *http.Client, ref *imageReference, options *pullOption) (string, error) {
// Try to access registry API v2 to get auth challenge
scheme := "https"
if options.PlainHTTP {
scheme = "http"
}
apiURL := fmt.Sprintf("%s://%s/v2/", scheme, ref.Registry)
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
if err != nil {
return "", err
}
// Add basic auth if provided
if options.Username != "" && options.Password != "" {
req.SetBasicAuth(options.Username, options.Password)
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
// If 200 OK, no auth needed
if resp.StatusCode == http.StatusOK {
return "", nil
}
// Check for WWW-Authenticate header
authHeader := resp.Header.Get("WWW-Authenticate")
if authHeader == "" {
return "", nil
}
// Parse auth challenge
token, err := fetchToken(ctx, client, authHeader, ref, options)
if err != nil {
return "", err
}
return token, nil
}
// fetchToken fetches authentication token from auth server
func fetchToken(ctx context.Context, client *http.Client, authHeader string, ref *imageReference, options *pullOption) (string, error) {
// Parse WWW-Authenticate header
// Format: Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/alpine:pull"
if !strings.HasPrefix(authHeader, "Bearer ") {
return "", nil
}
params := make(map[string]string)
parts := strings.Split(authHeader[7:], ",")
for _, part := range parts {
part = strings.TrimSpace(part)
idx := strings.Index(part, "=")
if idx > 0 {
key := part[:idx]
value := strings.Trim(part[idx+1:], "\"")
params[key] = value
}
}
realm, ok := params["realm"]
if !ok {
return "", fmt.Errorf("no realm in auth header")
}
// Build token URL
tokenURL := realm
if service, ok := params["service"]; ok {
tokenURL += "?service=" + url.QueryEscape(service)
}
scope := fmt.Sprintf("repository:%s:pull", ref.Repository)
if params["scope"] != "" {
scope = params["scope"]
}
tokenURL += "&scope=" + url.QueryEscape(scope)
req, err := http.NewRequestWithContext(ctx, "GET", tokenURL, nil)
if err != nil {
return "", err
}
// Add basic auth if provided
if options.Username != "" && options.Password != "" {
req.SetBasicAuth(options.Username, options.Password)
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("failed to get token: %s", resp.Status)
}
var tokenResp struct {
Token string `json:"token"`
}
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
return "", err
}
return tokenResp.Token, nil
}
// getManifest fetches the image manifest
func getManifest(ctx context.Context, client *http.Client, ref *imageReference, token string, options *pullOption) (*manifestV2, error) {
scheme := "https"
if options.PlainHTTP {
scheme = "http"
}
manifestURL := fmt.Sprintf("%s://%s/v2/%s/manifests/%s", scheme, ref.Registry, ref.Repository, ref.Tag)
req, err := http.NewRequestWithContext(ctx, "GET", manifestURL, nil)
if err != nil {
return nil, err
}
// Set accept headers for manifest v2 (add both types)
req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json")
req.Header.Add("Accept", "application/vnd.oci.image.manifest.v1+json")
req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.list.v2+json")
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
logger.Debug("Fetching manifest from: %s", manifestURL)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
logger.Debug("Manifest request failed: %s, body: %s", resp.Status, string(body))
return nil, fmt.Errorf("failed to get manifest: %s", resp.Status)
}
contentType := resp.Header.Get("Content-Type")
logger.Debug("Manifest Content-Type: %s", contentType)
// Read response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// Check if it's a manifest list (multi-arch)
if strings.Contains(contentType, "manifest.list") || strings.Contains(contentType, "image.index") {
// Parse manifest list and select amd64/linux
var manifestList struct {
SchemaVersion int `json:"schemaVersion"`
Manifests []struct {
Digest string `json:"digest"`
Platform struct {
Architecture string `json:"architecture"`
OS string `json:"os"`
} `json:"platform"`
} `json:"manifests"`
}
if err := json.Unmarshal(body, &manifestList); err != nil {
return nil, fmt.Errorf("failed to parse manifest list: %w", err)
}
logger.Debug("Found manifest list with %d manifests", len(manifestList.Manifests))
// Find amd64/linux manifest
var targetDigest string
for _, m := range manifestList.Manifests {
logger.Debug("Checking manifest: arch=%s, os=%s, digest=%s",
m.Platform.Architecture, m.Platform.OS, m.Digest)
if m.Platform.Architecture == "amd64" && m.Platform.OS == "linux" {
targetDigest = m.Digest
break
}
}
if targetDigest == "" && len(manifestList.Manifests) > 0 {
// Fallback to first manifest with known platform
for _, m := range manifestList.Manifests {
if m.Platform.Architecture != "unknown" && m.Platform.OS != "unknown" {
targetDigest = m.Digest
logger.Debug("Using fallback manifest: arch=%s, os=%s",
m.Platform.Architecture, m.Platform.OS)
break
}
}
}
if targetDigest == "" {
return nil, fmt.Errorf("no suitable manifest found in manifest list")
}
logger.Debug("Selected manifest digest: %s", targetDigest)
// Fetch the actual manifest by digest
return getManifestByDigest(ctx, client, ref, targetDigest, token, options)
}
var manifest manifestV2
if err := json.Unmarshal(body, &manifest); err != nil {
return nil, fmt.Errorf("failed to parse manifest: %w", err)
}
logger.Debug("Parsed manifest: schemaVersion=%d, config.digest=%s, layers=%d",
manifest.SchemaVersion, manifest.Config.Digest, len(manifest.Layers))
return &manifest, nil
}
// getManifestByDigest fetches a manifest by its digest
func getManifestByDigest(ctx context.Context, client *http.Client, ref *imageReference, digest, token string, options *pullOption) (*manifestV2, error) {
scheme := "https"
if options.PlainHTTP {
scheme = "http"
}
manifestURL := fmt.Sprintf("%s://%s/v2/%s/manifests/%s", scheme, ref.Registry, ref.Repository, digest)
req, err := http.NewRequestWithContext(ctx, "GET", manifestURL, nil)
if err != nil {
return nil, err
}
req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json")
req.Header.Add("Accept", "application/vnd.oci.image.manifest.v1+json")
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get manifest by digest: %s", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var manifest manifestV2
if err := json.Unmarshal(body, &manifest); err != nil {
return nil, fmt.Errorf("failed to parse manifest: %w", err)
}
return &manifest, nil
}
// createImageTar downloads layers and creates a Docker-compatible tar archive
func createImageTar(ctx context.Context, client *http.Client, ref *imageReference, manifest *manifestV2, token string, store io.Writer, imageName string, options *pullOption) error {
// Create tar writer
tw := tar.NewWriter(store)
defer tw.Close()
scheme := "https"
if options.PlainHTTP {
scheme = "http"
}
// Download config blob
logger.Debug("Downloading config blob: %s", manifest.Config.Digest)
configBlob, err := downloadBlob(ctx, client, scheme, ref.Registry, ref.Repository, manifest.Config.Digest, token)
if err != nil {
return fmt.Errorf("failed to download config: %w", err)
}
// Write config as json file
configFileName := strings.TrimPrefix(manifest.Config.Digest, "sha256:") + ".json"
if err := writeTarEntry(tw, configFileName, configBlob); err != nil {
return err
}
// Download and write each layer
var layerFiles []string
for i, layer := range manifest.Layers {
logger.Debug("Downloading layer %d/%d: %s", i+1, len(manifest.Layers), layer.Digest)
layerBlob, err := downloadBlob(ctx, client, scheme, ref.Registry, ref.Repository, layer.Digest, token)
if err != nil {
return fmt.Errorf("failed to download layer %s: %w", layer.Digest, err)
}
layerFileName := strings.TrimPrefix(layer.Digest, "sha256:") + "/layer.tar"
layerFiles = append(layerFiles, layerFileName)
if err := writeTarEntry(tw, layerFileName, layerBlob); err != nil {
return err
}
}
// Create manifest.json for Docker compatibility
if options.Rename != "" {
imageName = options.Rename
}
manifestJSON := []map[string]interface{}{
{
"Config": configFileName,
"RepoTags": []string{imageName},
"Layers": layerFiles,
},
}
manifestData, err := json.Marshal(manifestJSON)
if err != nil {
return err
}
if err := writeTarEntry(tw, "manifest.json", manifestData); err != nil {
return err
}
// Create repositories file (legacy)
repositories := make(map[string]map[string]string)
repositories[imageName] = map[string]string{ref.Tag: strings.TrimPrefix(manifest.Config.Digest, "sha256:")[:12]}
reposData, err := json.Marshal(repositories)
if err != nil {
return err
}
if err := writeTarEntry(tw, "repositories", reposData); err != nil {
return err
}
return nil
}
// downloadBlob downloads a blob from registry
func downloadBlob(ctx context.Context, client *http.Client, scheme, registry, repository, digest, token string) ([]byte, error) {
blobURL := fmt.Sprintf("%s://%s/v2/%s/blobs/%s", scheme, registry, repository, digest)
req, err := http.NewRequestWithContext(ctx, "GET", blobURL, nil)
if err != nil {
return nil, err
}
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to download blob: %s", resp.Status)
}
// Check if content is gzipped
var reader io.Reader = resp.Body
if resp.Header.Get("Content-Type") == "application/vnd.docker.image.rootfs.diff.tar.gzip" ||
strings.Contains(resp.Header.Get("Content-Encoding"), "gzip") {
gzReader, err := gzip.NewReader(resp.Body)
if err == nil {
defer gzReader.Close()
reader = gzReader
}
}
return io.ReadAll(reader)
}
// writeTarEntry writes a file entry to tar archive
func writeTarEntry(tw *tar.Writer, name string, data []byte) error {
hdr := &tar.Header{
Name: name,
Mode: 0644,
Size: int64(len(data)),
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
if _, err := tw.Write(data); err != nil {
return err
}
return nil
}

429
pkg/imager/pull_test.go Normal file
View File

@@ -0,0 +1,429 @@
package imager
import (
"context"
"os"
"path/filepath"
"testing"
"time"
"yizhisec.com/hsv2/forge/pkg/logger"
)
// TestPullImage_PublicImage tests pulling a public image from Docker Hub
func TestPullImage_PublicImage(t *testing.T) {
logger.SetLogLevel(logger.LogLevelDebug)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
// Create temp directory for test output
tmpDir := t.TempDir()
outputFile := filepath.Join(tmpDir, "alpine.tar")
outputFile = "./redis.alpine.tar"
// Create output file
f, err := os.Create(outputFile)
if err != nil {
t.Fatalf("failed to create output file: %v", err)
}
defer f.Close()
// Pull alpine image (small and commonly available)
imageName := "docker-mirror.yizhisec.com/library/redis:alpine"
err = PullImage(ctx, imageName, f)
if err != nil {
t.Fatalf("failed to pull image %s: %v", imageName, err)
}
// Verify file was created and has content
if err := f.Close(); err != nil {
t.Fatalf("failed to close file: %v", err)
}
info, err := os.Stat(outputFile)
if err != nil {
t.Fatalf("failed to stat output file: %v", err)
}
if info.Size() == 0 {
t.Error("output file is empty")
}
t.Logf("Successfully pulled %s, size: %d bytes", imageName, info.Size())
}
// TestPullImage_WithRegistry tests pulling from a specific registry
func TestPullImage_WithRegistry(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
tmpDir := t.TempDir()
outputFile := filepath.Join(tmpDir, "nginx.tar")
f, err := os.Create(outputFile)
if err != nil {
t.Fatalf("failed to create output file: %v", err)
}
defer f.Close()
// Pull from docker.io with explicit registry
imageName := "docker.io/library/nginx:alpine"
err = PullImage(ctx, imageName, f)
if err != nil {
t.Fatalf("failed to pull image %s: %v", imageName, err)
}
if err := f.Close(); err != nil {
t.Fatalf("failed to close file: %v", err)
}
info, err := os.Stat(outputFile)
if err != nil {
t.Fatalf("failed to stat output file: %v", err)
}
if info.Size() == 0 {
t.Error("output file is empty")
}
t.Logf("Successfully pulled %s, size: %d bytes", imageName, info.Size())
}
// TestPullImage_WithRename tests pulling and renaming an image
func TestPullImage_WithRename(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
tmpDir := t.TempDir()
outputFile := filepath.Join(tmpDir, "renamed.tar")
f, err := os.Create(outputFile)
if err != nil {
t.Fatalf("failed to create output file: %v", err)
}
defer f.Close()
imageName := "alpine:3.19"
newName := "my-custom-alpine:latest"
err = PullImage(ctx, imageName, f, WithRename(newName))
if err != nil {
t.Fatalf("failed to pull image %s: %v", imageName, err)
}
if err := f.Close(); err != nil {
t.Fatalf("failed to close file: %v", err)
}
info, err := os.Stat(outputFile)
if err != nil {
t.Fatalf("failed to stat output file: %v", err)
}
if info.Size() == 0 {
t.Error("output file is empty")
}
t.Logf("Successfully pulled %s and renamed to %s, size: %d bytes", imageName, newName, info.Size())
}
// TestPullImage_WithAuth tests pulling with authentication (skip if no credentials)
func TestPullImage_WithAuth(t *testing.T) {
// This test requires actual credentials, so it's skipped by default
t.Skip("Skipping authentication test - requires valid credentials")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
tmpDir := t.TempDir()
outputFile := filepath.Join(tmpDir, "private.tar")
f, err := os.Create(outputFile)
if err != nil {
t.Fatalf("failed to create output file: %v", err)
}
defer f.Close()
// Replace with your private registry details
imageName := "your-registry.com/your-repo/your-image:tag"
username := "your-username"
password := "your-password"
err = PullImage(ctx, imageName, f, WithAuth(username, password))
if err != nil {
t.Fatalf("failed to pull private image %s: %v", imageName, err)
}
if err := f.Close(); err != nil {
t.Fatalf("failed to close file: %v", err)
}
info, err := os.Stat(outputFile)
if err != nil {
t.Fatalf("failed to stat output file: %v", err)
}
if info.Size() == 0 {
t.Error("output file is empty")
}
t.Logf("Successfully pulled private image %s, size: %d bytes", imageName, info.Size())
}
// TestPullImage_WithRetry tests retry functionality
func TestPullImage_WithRetry(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
tmpDir := t.TempDir()
outputFile := filepath.Join(tmpDir, "retry.tar")
f, err := os.Create(outputFile)
if err != nil {
t.Fatalf("failed to create output file: %v", err)
}
defer f.Close()
// Pull with custom retry settings
imageName := "alpine:3.19"
err = PullImage(ctx, imageName, f,
WithRetry(5), // 5 retries
WithRetryDelay(1*time.Second), // 1 second delay
)
if err != nil {
t.Fatalf("failed to pull image %s: %v", imageName, err)
}
if err := f.Close(); err != nil {
t.Fatalf("failed to close file: %v", err)
}
info, err := os.Stat(outputFile)
if err != nil {
t.Fatalf("failed to stat output file: %v", err)
}
if info.Size() == 0 {
t.Error("output file is empty")
}
t.Logf("Successfully pulled %s with retry, size: %d bytes", imageName, info.Size())
}
// TestPullImage_WithZeroRetry tests with retry disabled
func TestPullImage_WithZeroRetry(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
tmpDir := t.TempDir()
outputFile := filepath.Join(tmpDir, "no-retry.tar")
f, err := os.Create(outputFile)
if err != nil {
t.Fatalf("failed to create output file: %v", err)
}
defer f.Close()
// Pull with retry disabled
imageName := "alpine:3.19"
err = PullImage(ctx, imageName, f, WithRetry(0))
if err != nil {
t.Fatalf("failed to pull image %s: %v", imageName, err)
}
if err := f.Close(); err != nil {
t.Fatalf("failed to close file: %v", err)
}
info, err := os.Stat(outputFile)
if err != nil {
t.Fatalf("failed to stat output file: %v", err)
}
if info.Size() == 0 {
t.Error("output file is empty")
}
t.Logf("Successfully pulled %s without retry, size: %d bytes", imageName, info.Size())
}
// TestPullImage_WithSkipTLSVerify tests pulling with TLS verification disabled
func TestPullImage_WithSkipTLSVerify(t *testing.T) {
// This test is for registries with self-signed certificates
t.Skip("Skipping TLS skip test - requires a registry with self-signed cert")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
tmpDir := t.TempDir()
outputFile := filepath.Join(tmpDir, "insecure.tar")
f, err := os.Create(outputFile)
if err != nil {
t.Fatalf("failed to create output file: %v", err)
}
defer f.Close()
imageName := "your-insecure-registry.local/image:tag"
err = PullImage(ctx, imageName, f, WithSkipTLSVerify())
if err != nil {
t.Fatalf("failed to pull image %s: %v", imageName, err)
}
if err := f.Close(); err != nil {
t.Fatalf("failed to close file: %v", err)
}
info, err := os.Stat(outputFile)
if err != nil {
t.Fatalf("failed to stat output file: %v", err)
}
if info.Size() == 0 {
t.Error("output file is empty")
}
t.Logf("Successfully pulled %s with TLS verification skipped, size: %d bytes", imageName, info.Size())
}
// TestParseImageReference tests the image reference parsing logic
func TestParseImageReference(t *testing.T) {
tests := []struct {
name string
imageName string
wantRegistry string
wantRepository string
wantTag string
wantErr bool
}{
{
name: "simple image with tag",
imageName: "alpine:3.19",
wantRegistry: "registry-1.docker.io",
wantRepository: "library/alpine",
wantTag: "3.19",
wantErr: false,
},
{
name: "simple image without tag",
imageName: "alpine",
wantRegistry: "registry-1.docker.io",
wantRepository: "library/alpine",
wantTag: "latest",
wantErr: false,
},
{
name: "image with namespace",
imageName: "library/nginx:alpine",
wantRegistry: "registry-1.docker.io",
wantRepository: "library/nginx",
wantTag: "alpine",
wantErr: false,
},
{
name: "custom registry",
imageName: "hub.yizhisec.com/external/alpine:3.22.2",
wantRegistry: "hub.yizhisec.com",
wantRepository: "external/alpine",
wantTag: "3.22.2",
wantErr: false,
},
{
name: "docker.io explicit",
imageName: "docker.io/library/redis:8.2.2",
wantRegistry: "docker.io",
wantRepository: "library/redis",
wantTag: "8.2.2",
wantErr: false,
},
{
name: "registry with port",
imageName: "localhost:5000/myimage:v1.0",
wantRegistry: "localhost:5000",
wantRepository: "myimage",
wantTag: "v1.0",
wantErr: false,
},
{
name: "quay.io registry",
imageName: "quay.io/k0sproject/pause:3.10.1",
wantRegistry: "quay.io",
wantRepository: "k0sproject/pause",
wantTag: "3.10.1",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ref, err := parseImageReference(tt.imageName)
if (err != nil) != tt.wantErr {
t.Errorf("parseImageReference() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err != nil {
return
}
if ref.Registry != tt.wantRegistry {
t.Errorf("parseImageReference() registry = %v, want %v", ref.Registry, tt.wantRegistry)
}
if ref.Repository != tt.wantRepository {
t.Errorf("parseImageReference() repository = %v, want %v", ref.Repository, tt.wantRepository)
}
if ref.Tag != tt.wantTag {
t.Errorf("parseImageReference() tag = %v, want %v", ref.Tag, tt.wantTag)
}
})
}
}
// TestPullImage_ContextCancellation tests that context cancellation works
func TestPullImage_ContextCancellation(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
tmpDir := t.TempDir()
outputFile := filepath.Join(tmpDir, "cancelled.tar")
f, err := os.Create(outputFile)
if err != nil {
t.Fatalf("failed to create output file: %v", err)
}
defer f.Close()
// Cancel context immediately
cancel()
imageName := "alpine:3.19"
err = PullImage(ctx, imageName, f)
if err == nil {
t.Error("expected error due to cancelled context, got nil")
}
t.Logf("Context cancellation correctly handled: %v", err)
}
// BenchmarkPullImage benchmarks image pulling performance
func BenchmarkPullImage(b *testing.B) {
ctx := context.Background()
tmpDir := b.TempDir()
imageName := "alpine:3.19"
b.ResetTimer()
for i := 0; i < b.N; i++ {
outputFile := filepath.Join(tmpDir, "alpine_bench.tar")
f, err := os.Create(outputFile)
if err != nil {
b.Fatalf("failed to create output file: %v", err)
}
err = PullImage(ctx, imageName, f)
if err != nil {
b.Fatalf("failed to pull image: %v", err)
}
f.Close()
os.Remove(outputFile)
}
}

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

55
pkg/model/caddy.go Normal file
View File

@@ -0,0 +1,55 @@
package model
type CaddyUpstream struct {
Dial []string `json:"dial"`
}
type CaddyActive struct {
Interval string `json:"interval"`
Timeout string `json:"timeout"`
Port int `json:"port"`
}
type CaddyPassive struct {
MaxFails int `json:"max_fails"`
FailDuration string `json:"fail_duration"`
}
type CaddyHealthCheck struct {
Active *CaddyActive `json:"active"`
Passive *CaddyPassive `json:"passive"`
}
type CaddySelection struct {
Policy string `json:"policy"`
}
type CaddyLoadBalancing struct {
Selection *CaddySelection `json:"selection"`
}
type CaddyHandle struct {
Handler string `json:"handler"`
Upstreams []*CaddyUpstream `json:"upstreams"`
HealthChecks *CaddyHealthCheck `json:"health_checks"`
LoadBalancing *CaddyLoadBalancing `json:"load_balancing"`
}
type CaddyRoute struct {
Handle []*CaddyHandle `json:"handle"`
}
type CaddyServer struct {
Listen []string `json:"listen"`
Routes []*CaddyRoute `json:"routes"`
}
type CaddyLayer4 struct {
Servers map[string]*CaddyServer `json:"servers"`
}
type CaddyApp struct {
Layer4 *CaddyLayer4 `json:"layer4"`
}
type CaddyConfig map[string]*CaddyApp

8
pkg/model/image.go Normal file
View File

@@ -0,0 +1,8 @@
package model
type Image struct {
Name string
Fallback string
Save string
Force bool
}

58
pkg/model/vendor.go Normal file
View File

@@ -0,0 +1,58 @@
package model
type Vendor struct {
Name string
OEMUrl string
OEMDir string
AppFrontUserImageName string
AppFrontAdminImageName string
}
var (
vendorMap = map[string]*Vendor{
"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": {
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": {
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": {
Name: "heishuimeng",
OEMUrl: "https://artifactory.yizhisec.com/artifactory/yizhisec-release/oem/release/2.1.0-std/oem_heishuimeng.tar.gz",
OEMDir: "oem_heishuimeng",
AppFrontUserImageName: "hub.yizhisec.com/hybridscope/v2/front-user:latest",
AppFrontAdminImageName: "hub.yizhisec.com/build/hybirdscope/front/admin:latest",
},
}
)
func GetVendor(name string) *Vendor {
if vendor, ok := vendorMap[name]; ok {
return vendor
}
return nil
}
func GetVendorNames() []string {
names := make([]string, 0, len(vendorMap))
for name := range vendorMap {
names = append(names, name)
}
return names
}

View File

@@ -0,0 +1,73 @@
{
"apps": {
"layer4": {
"servers": {
"proxy_8443_tcp_backends": {
"listen": [":8443"],
"routes": [
{
"handle": [
{
"handler": "proxy",
"upstreams": [
{"dial": ["10.118.2.11:32443"]},
{"dial": ["10.118.2.12:32443"]}
],
"health_checks": {
"active": {
"interval": "5s",
"timeout": "2s",
"port": 32443
},
"passive": {
"max_fails": 1,
"fail_duration": "30s"
}
},
"load_balancing": {
"selection": {
"policy": "round_robin"
}
}
}
]
}
]
},
"proxy_443_tcp_backends": {
"listen": [":443"],
"routes": [
{
"handle": [
{
"handler": "proxy",
"upstreams": [
{"dial": ["10.118.2.11:31443"]},
{"dial": ["10.118.2.12:31443"]}
],
"health_checks": {
"active": {
"interval": "5s",
"timeout": "2s",
"port": 31443
},
"passive": {
"max_fails": 1,
"fail_duration": "30s"
}
},
"load_balancing": {
"selection": {
"policy": "round_robin"
}
}
}
]
}
]
}
}
}
}
}

View File

@@ -0,0 +1,255 @@
upstream hs-client-server {
least_conn;
server client-service:9129 max_fails=3 fail_timeout=10s;
}
upstream hs-client-without-auth-server {
least_conn;
server client-service:9024 max_fails=3 fail_timeout=10s;
}
upstream hs-client-message-server {
least_conn;
server client-service:9025 max_fails=3 fail_timeout=10s;
}
server {
listen 443 ssl
proxy_protocol;
server_name hs-client-api-server hs.client.api.server;
ssl_certificate /etc/nginx/ssl/client.server.crt;
ssl_certificate_key /etc/nginx/ssl/client.server.key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" always;
client_max_body_size 50M;
location /api/v1/pkg/archive {
proxy_pass http://client-win-service.hsv2/api/v2_2/_client/win/check.json;
}
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/ {
proxy_pass http://hs-client-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 /api/v1/ {
proxy_pass http://hs-client-without-auth-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 /api/v1/dl/ {
proxy_pass http://hs-client-without-auth-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 /api/v2/admin/ {
proxy_pass http://hs-client-message-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 /app-store {
# auth_request /app-store-auth;
# rewrite ^/app-store(.*)$ $1 break;
# proxy_pass http://hs-resource-server:19980;
# 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 /app-store-auth {
internal;
proxy_pass http://hs-client-server/auth$request_uri;
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_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Original-Method $request_method;
proxy_set_header X-Original-IP $remote_addr;
proxy_set_header Query-Data $http_query_data;
}
# 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 /static/config/ {
alias /static/config/;
}
location /static/resource/ {
alias /static/resource/;
}
location /user/avatar/ {
alias /static/avatar/;
add_header Cache-Control public;
}
}
server {
listen 443 ssl proxy_protocol;
server_name hs-client-update-server hs.client.update.server;
ssl_certificate /etc/nginx/ssl/client.server.crt;
ssl_certificate_key /etc/nginx/ssl/client.server.key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" always;
client_max_body_size 50M;
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://app-helper-service.hsv2/api/v2_2/_client/win/old/version.json;
}
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://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 {
# proxy_pass http://hs-client-without-auth-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 /api/v1/pkg {
proxy_pass http://hs-client-without-auth-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 /api/v1/dl/ {
proxy_pass http://hs-client-without-auth-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 /user/avatar/ {
alias /static/avatar/;
expires 7d;
add_header Cache-Control public;
}
location /static/config/ {
alias /static/config/;
}
location /static/resource/ {
alias /static/resource/;
}
}
server {
listen 9118 ssl
proxy_protocol;
ssl_certificate /etc/nginx/ssl/client.server.crt;
ssl_certificate_key /etc/nginx/ssl/client.server.key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" always;
client_max_body_size 50M;
location = /api/v1/version {
proxy_pass http://hs-client-without-auth-server;
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 /api/v1/pkg {
proxy_pass http://hs-client-without-auth-server;
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 /api/v1/dl/ {
proxy_pass http://hs-client-without-auth-server;
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;
}
}

View File

@@ -0,0 +1,28 @@
user root;
worker_processes auto;
events {
worker_connections 1024;
}
http {
log_format custom '$time_iso8601 - $remote_addr - $http_host - $status - $request_time - $request_method - $request_uri';
access_log /var/log/nginx/access.log custom;
include /etc/nginx/mime.types;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
gzip on;
server {
listen 80;
location %s {
alias /data;
try_files $uri $uri/ =404;
}
}
}

View File

@@ -0,0 +1,204 @@
ssl_certificate /etc/nginx/ssl/web.server.crt;
ssl_certificate_key /etc/nginx/ssl/web.server.key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
ssl_dhparam /etc/nginx/ssl/ffdhe2048.txt;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" always;
client_header_buffer_size 1k;
client_max_body_size 50M;
location = /token_auth {
internal;
proxy_pass http://hs-api/api/tokenauth;
proxy_http_version 1.1;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
location /client/dl/android {
proxy_set_header Cookie $http_cookie;
default_type application/octet-stream;
alias /static/client/android;
if ($arg_attname ~ "^(.+)") {
add_header Content-Disposition "attachment;filename*=utf-8''$arg_attname";
}
}
location /client/dl/ {
# remove download client auth verify
# auth_request /token_auth;
proxy_set_header Cookie $http_cookie;
default_type application/octet-stream;
alias /static/client/;
if ($arg_attname ~ "^(.+)") {
add_header Content-Disposition "attachment;filename*=utf-8''$arg_attname";
}
}
location /file/share/ {
auth_request /token_auth;
proxy_set_header Cookie $http_cookie;
alias /static/share_file/;
if ($arg_attname ~ "^(.+)") {
add_header Content-Disposition "attachment;filename*=utf-8''$arg_attname";
}
}
location /file/public/ {
auth_request /token_auth;
proxy_set_header Cookie $http_cookie;
alias /static/public_folder/;
if ($arg_attname ~ "^(.+)") {
add_header Content-Disposition "attachment;filename*=utf-8''$arg_attname";
}
}
location /file/clipboard/ {
auth_request /token_auth;
proxy_set_header Cookie $http_cookie;
alias /static/clipboard_file/;
}
location /file/uploaded/ {
auth_request /token_auth;
proxy_set_header Cookie $http_cookie;
alias /static/uploaded_files/;
if ($arg_attname ~ "^(.+)") {
add_header Content-Disposition "attachment;filename*=utf-8''$arg_attname";
}
}
location /resource/update_log.csv {
auth_request /token_auth;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header Cookie $http_cookie;
default_type application/octet-stream;
alias /static/resource/update_log.csv;
}
location /resource/update_timestamp.txt {
auth_request /token_auth;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header Cookie $http_cookie;
default_type application/octet-stream;
alias /static/resource/update_timestamp.txt;
}
location /resource/ {
default_type application/octet-stream;
alias /static/resource/;
}
location /api/ {
proxy_pass http://hs-api;
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 /network-disk {
# set $arg_token ''; # 声明 $arg_token 变量
# if ($args ~* "token=(.*?)(&|$)") {
# set $arg_token $1;
# }
# auth_request /token_auth;
# set $auth $http_authorization;
# if ($http_authorization = "") {
# set $auth "token $arg_authorization";
# }
# rewrite ^/network-disk(.*)$ $1 break;
# proxy_pass http://hs-resource-server:19980;
# proxy_http_version 1.1;
# proxy_set_header Authorization $auth;
# }
# location /app-store {
# set $arg_token ''; # 声明 $arg_token 变量
# if ($args ~* "token=(.*?)(&|$)") {
# set $arg_token $1;
# }
# auth_request /app-store-auth;
# set $auth $http_authorization;
# if ($http_authorization = "") {
# set $auth "token $arg_authorization";
# }
# rewrite ^/app-store(.*)$ $1 break;
# proxy_pass http://hs-resource-server:19980;
# proxy_http_version 1.1;
# proxy_set_header Authorization $auth;
# }
location /app-store-auth {
internal;
set $hs_token $http_hs_token;
if ($hs_token = "") {
set $hs_token $arg_token;
}
proxy_set_header Hs-Token $hs_token;
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_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Original-Method $request_method;
proxy_set_header X-Original-IP $remote_addr;
proxy_set_header Query-Data $http_query_data;
proxy_pass http://hs-api/api$request_uri;
}
error_page 502 /502.json;
error_page 503 /503.json;
location /503.json {
return 503 '{"code": -2, "msg": "服务器未响应", "err": "hs-nginx err"}';
}
error_page 504 /504.json;
location /504.json {
return 504 '{"code": -3, "msg": "服务器未响应", "err": "hs-nginx err"}';
}
error_page 497 301 =307 https://$http_host$request_uri;
error_page 401 @my_401;
error_page 403 @my_403;
error_page 404 @my_404;
error_page 502 @my_502;
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>';
}
location @my_403 {
default_type text/html;
return 403 '<!doctypehtml><html lang=en><meta charset=UTF-8><meta content="width=device-width,initial-scale=1"name=viewport><title>403</title><style>body{display:flex;flex-direction:column;align-items:center;justify-content:center}</style><h1>403 Forbidden</h1>';
}
location @my_404 {
default_type text/html;
return 404 '<!doctypehtml><html lang=en><meta charset=UTF-8><meta content="width=device-width,initial-scale=1"name=viewport><title>404</title><style>body{display:flex;flex-direction:column;align-items:center;justify-content:center}</style><h1>404 Not_Found</h1>';
}
location @my_502 {
default_type text/html;
return 502 '<!doctypehtml><html lang=en><meta charset=UTF-8><meta content="width=device-width,initial-scale=1"name=viewport><title>502</title><style>body{display:flex;flex-direction:column;align-items:center;justify-content:center}</style><h1>502 Bad_Gateway</h1>';
}

View File

@@ -0,0 +1,75 @@
upstream hs-gateway-controller {
least_conn;
server gateway-service:9012 max_fails=3 fail_timeout=10s;
}
server {
listen 443 ssl proxy_protocol;
server_name hs-gateway-controller;
ssl_certificate /yizhisec/ssl/server.crt;
ssl_certificate_key /yizhisec/ssl/server.key;
ssl_client_certificate /yizhisec/ssl/ca.crt;
ssl_verify_client on;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam
ssl_dhparam /etc/nginx/ssl/ffdhe2048.txt;
# intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS (ngx_http_headers_module is required) (63072000 seconds)
add_header Strict-Transport-Security "max-age=63072000" always;
client_max_body_size 50M;
location / {
proxy_pass http://hs-gateway-controller;
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;
}
}
server {
listen 443 ssl proxy_protocol;
server_name hs-gateway-register-controller;
ssl_certificate /yizhisec/ssl/server.crt;
ssl_certificate_key /yizhisec/ssl/server.key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_dhparam /etc/nginx/ssl/ffdhe2048.txt;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" always;
client_max_body_size 50M;
location = /api/v1/gateway/setting {
if ($request_method != POST ) {
return 502 '{"code": -1, "msg": "invalid request"}';
}
proxy_pass http://hs-gateway-controller;
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;
}
}

View File

@@ -0,0 +1,63 @@
user root;
worker_processes auto;
events {
worker_connections 1024;
}
stream {
error_log /var/log/nginx/error.log error;
map $ssl_preread_server_name $backend {
mqtt.yizhisec.com 127.0.0.1:27443;
mqtt-yizhisec-com 127.0.0.1:27443;
default web;
}
upstream web {
server 127.0.0.1:443;
}
server {
listen 27443 ssl proxy_protocol;
# ssl_session_timeout 10m;
ssl_certificate /etc/nginx/ssl/mqtt.server.crt;
ssl_certificate_key /etc/nginx/ssl/mqtt.server.key;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
proxy_pass emqx-service.db-emqx:1883;
}
server {
listen 23443;
proxy_pass $backend;
ssl_preread on;
proxy_protocol on;
}
}
http {
log_format custom '$time_iso8601 - $remote_addr - $http_host - $status - $request_time - $request_method - $request_uri';
access_log /var/log/nginx/access.log custom;
include /etc/nginx/sites-enabled/*.conf;
include mime.types;
default_type application/octet-stream;
sendfile on;
sendfile_max_chunk 512k;
tcp_nopush on;
tcp_nodelay on;
gzip on;
gzip_vary on;
gzip_http_version 1.0;
gzip_min_length 1000;
gzip_comp_level 6;
gzip_disable msie6;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/javascript text/xml application/xml application/rss+xml application/atom+xml;
keepalive_timeout 65;
}

View File

@@ -0,0 +1,81 @@
server {
listen 443 ssl proxy_protocol;
server_name seafile.yizhisec.com cloud.hybridscope.com seafile-yizhise-com cloud-hybridscope-com;
ssl_certificate /etc/nginx/ssl/client.server.crt;
ssl_certificate_key /etc/nginx/ssl/client.server.key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" always;
ssl_trusted_certificate /etc/nginx/ssl/client.ca.crt;
ssl_client_certificate /etc/nginx/ssl/client.ca.crt;
ssl_verify_client on;
client_max_body_size 500M;
# location = /auth-sharing {
# internal;
# proxy_pass http://client-service:9129/api/auth-sharing;
# proxy_http_version 1.1;
# proxy_pass_request_body off;
# proxy_set_header Content-Length "";
# proxy_set_header X-Original-URI $request_uri;
# }
location /f/ {
rewrite ^(.+[^/])$ $1/ last; # 补上末尾的 /,避免重定向两次
# auth_request /auth-sharing;
# proxy_pass http://hs-openresty:13381;
proxy_pass http://seafile-service.seafile;
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 /api/v1/ {
proxy_pass http://backup-seafile-service.seafile:9027;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 300s;
}
location /api2/ {
# proxy_pass http://hs-resource-server:19980;
proxy_pass http://seafile-service.seafile;
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 /api/v2.1/ {
# proxy_pass http://hs-resource-server:19980;
proxy_pass http://seafile-service.seafile;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $proxy_protocol_addr;
proxy_read_timeout 300s;
}
location /seafhttp/ {
# proxy_pass http://hs-resource-server:19980;
proxy_pass http://seafile-service.seafile;
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;
}
}

View File

@@ -0,0 +1,17 @@
server {
listen 80;
server_name hs-user-management-controller;
add_header Strict-Transport-Security "max-age=63072000" always;
client_max_body_size 50M;
location / {
proxy_pass http://user-service:9013;
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;
}
}

189
pkg/resource/nginx/web.conf Normal file
View File

@@ -0,0 +1,189 @@
server {
listen 80;
return 301 https://$host$request_uri;
}
# upstream hs-backup-server {
# least_conn;
# server hs-backup-server:9349 max_fails=3 fail_timeout=10s;
# }
upstream hs-api {
server api-service:9002;
}
server {
listen 9002;
location / {
proxy_pass http://hs-api;
}
}
server {
listen 443 ssl default_server;
location /api/admin/ {
return 404;
}
location /oem {
proxy_pass http://oem-service;
}
location /api/my/sys/client/installer {
proxy_pass http://app-helper-service.hsv2/api/v2_2/client/download/list;
}
location /api/system/version {
proxy_pass http://app-helper-service.hsv2/api/v2_2/system/version;
}
location /api/v2_2/system/elink {
proxy_pass http://app-helper-service.hsv2;
}
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 / {
proxy_pass http://front-user-service;
}
include /etc/nginx/common/common.conf;
error_page 497 301 =307 https://$http_host$request_uri;
}
server {
listen 8443 ssl;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN"; # 或 "DENY"
add_header Content-Security-Policy "img-src * data:; frame-ancestors 'none';" always;
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header X-Permitted-Cross-Domain-Policies "none";
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Download-Options "noopen" always;
add_header X-Content-Type-Options "nosniff" always;
server_tokens off;
location / {
proxy_pass http://front-admin-service;
}
location /api/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 /user/avatar/ {
proxy_pass http://app-helper-service.hsv2/api/v2_2/_obj/;
}
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 {
proxy_pass http://user-service:9013;
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/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 {
proxy_pass http://hs-api/ws;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
}
location /api/local/user/import/template {
auth_request /token_auth;
alias /static/resource/local_user_import_template.xlsx;
}
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>';
}
location @my_403 {
default_type text/html;
return 403 '<!doctypehtml><html lang=en><meta charset=UTF-8><meta content="width=device-width,initial-scale=1"name=viewport><title>403</title><style>body{display:flex;flex-direction:column;align-items:center;justify-content:center}</style><h1>403 Forbidden</h1>';
}
location @my_404 {
default_type text/html;
return 404 '<!doctypehtml><html lang=en><meta charset=UTF-8><meta content="width=device-width,initial-scale=1"name=viewport><title>404</title><style>body{display:flex;flex-direction:column;align-items:center;justify-content:center}</style><h1>404 Not_Found</h1>';
}
location @my_502 {
default_type text/html;
return 502 '<!doctypehtml><html lang=en><meta charset=UTF-8><meta content="width=device-width,initial-scale=1"name=viewport><title>502</title><style>body{display:flex;flex-direction:column;align-items:center;justify-content:center}</style><h1>502 Bad_Gateway</h1>';
}
error_page 497 301 =307 https://$http_host$request_uri;
error_page 401 @my_401;
error_page 403 @my_403;
error_page 404 @my_404;
error_page 502 @my_502;
}

View File

@@ -5,24 +5,135 @@ import (
)
var (
//go:embed flannel.yaml
//go:embed yaml/registry.yaml
YAMLRegistry string
//go:embed yaml/flannel.yaml
YAMLFlannel string
//go:embed es.yaml
//go:embed yaml/es.yaml
YAMLES string
//go:embed kibana.yaml
//go:embed yaml/kibana.yaml
YAMLKibana []byte
//go:embed es.init.sh
//go:embed sh/es.init.sh
BashESInit []byte
//go:embed emqx.yaml
//go:embed yaml/emqx.yaml
YAMLEMQX []byte
//go:embed yosguard.create.sql
//go:embed yaml/minio.yaml
YAMLMinIO string
//go:embed sql/yosguard.create.sql
SQLYosguard []byte
//go:embed less-dns.yaml
//go:embed yaml/less-dns.yaml
YAMLLessDNS []byte
//go:embed yaml/seafile.yaml
YAMLSeafile string
//go:embed yaml/backup-seafile.yaml
YAMLBackupSeafile string
//go:embed yaml/app.user.yaml
YAMLAppUser string
//go:embed yaml/app.gateway.yaml
YAMLAppGateway string
//go:embed yaml/app.client.yaml
YAMLAppClient string
//go:embed yaml/app.mie.api.yaml
YAMLAppMieAPI string
//go:embed yaml/app.mie.worker.yaml
YAMLAppMieWorker []byte
//go:embed yaml/app.mie.cron.yaml
YAMLAppMieCron []byte
//go:embed yaml/app.mie.sweeper.yaml
YAMLAppMieSweeper []byte
//go:embed yaml/app.oem.yaml
YAMLAppOEM string
//go:embed yaml/app.front.user.yaml
YAMLAppFrontUser string
//go:embed yaml/app.front.admin.yaml
YAMLAppFrontAdmin string
//go:embed yaml/app.nginx.yaml
YAMLAppNGINX string
//go:embed yaml/app.helper.yaml
YAMLAppHelper string
//go:embed yaml/client.pkg.yaml
YAMLClientPKG string
//go:embed ssl/ca.crt
SSLCaCrt string
//go:embed ssl/client.server.crt
SSLClientServerCrt string
//go:embed ssl/client.server.key
SSLClientServerKey string
//go:embed ssl/ffdhe2048.txt
SSLFFDHE2048 string
//go:embed ssl/mqtt.server.crt
SSLMQTTServerCrt string
//go:embed ssl/mqtt.server.key
SSLMQTTServerKey string
//go:embed ssl/mqtt.client.crt
SSLMQTTClientCrt []byte
//go:embed ssl/mqtt.client.key
SSLMQTTClientKey []byte
//go:embed ssl/server.crt
SSLServerCrt string
//go:embed ssl/server.key
SSLServerKey string
//go:embed ssl/web.server.crt
SSLWebServerCrt string
//go:embed ssl/web.server.key
SSLWebServerKey string
//go:embed nginx/seafile.conf
NGINXSeafile []byte
//go:embed nginx/common.conf
NGINXCommon []byte
//go:embed nginx/gateway.conf
NGINXGateway []byte
//go:embed nginx/web.conf
NGINXWeb []byte
//go:embed nginx/client.conf
NGINXClient []byte
//go:embed nginx/nginx.conf
NGINXMain []byte
//go:embed nginx/user.conf
NGINXUser []byte
//go:embed nginx/client_pkg.conf
NGINXClientPKG string
)

Some files were not shown because too many files have changed in this diff Show More