From 2e35feb1d80a830e20eba99410ae7e9ca76f7346 Mon Sep 17 00:00:00 2001 From: WuYongJie Date: Mon, 1 Jun 2026 10:21:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E7=B4=A7=20Go=20Agent=20=E5=AE=89?= =?UTF-8?q?=E8=A3=85=E5=85=A5=E5=8F=A3=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flnxi-agent-go/README.md | 15 +- .../__tests__/install-script.test.ts | 23 +++ flnxi-agent-go/install.sh | 15 +- flnxi-agent-go/internal/api/handlers.go | 149 ++++++++++++---- flnxi-agent-go/internal/api/handlers_test.go | 160 ++++++++++++++++++ .../internal/api/install_secrets.go | 35 ++-- 6 files changed, 343 insertions(+), 54 deletions(-) create mode 100644 flnxi-agent-go/__tests__/install-script.test.ts diff --git a/flnxi-agent-go/README.md b/flnxi-agent-go/README.md index c44385d..9b98700 100644 --- a/flnxi-agent-go/README.md +++ b/flnxi-agent-go/README.md @@ -2,12 +2,14 @@ `flnxi-agent-go` 是面向私有化部署场景的 Go 版安装与运维 Agent。 -当前定位:Go 版 Agent 更适合作为后续私有化交付的轻量入口;但当前默认安装脚本还没把认证变量和完整版本链路全部接好,不能直接当成“已经可正式交付”的成品。若现场仍使用 Node 版 Agent,先确认既有安装链路和端口占用,再决定是否切换。 +当前定位:Go 版 Agent 更适合作为后续私有化交付的轻量入口;它已经收紧了 Agent 自身认证、主应用固定版本镜像和许可证运行参数,但管理界面、安装向导、异常恢复和客户现场验收流程仍未完全闭环,不能直接当成“已经可正式交付”的成品。若现场仍使用 Node 版 Agent,先确认既有安装链路和端口占用,再决定是否切换。 重要现状: - `install.sh` 已不再默认拉取旧 Harbor `latest` 镜像,必须通过 `AGENT_IMAGE` 或 `--image` 显式传入完整 Agent 版本镜像。 - Go 版 Agent 内部安装和更新素材系统已改为读取完整版本镜像,不再默认拉取旧 Harbor / `latest`。 +- 安装素材系统时必须提供完整主应用版本镜像,不能使用无版本 tag 或 `latest`。 +- 安装素材系统时必须提供许可证服务参数;缺少关键参数时会在拉镜像前失败,避免客户装出一个启动不了的半成品。 - 版本列表需要通过 `FLNXI_VERSION_LIST_URL` 显式配置来源;未配置时不会伪造 `latest` 或默认版本。 - 当前仍建议先作为内部验证入口,等管理界面和客户安装流程联调完成后,再提升为客户正式安装主入口。 @@ -61,17 +63,26 @@ docker run -d \ | `FLNXI_POSTGRES_IMAGE` | 安装时使用的 PostgreSQL 镜像;也兼容读取 `POSTGRES_IMAGE` | `postgres:15-alpine` | | `FLNXI_MINIO_IMAGE` | 安装时使用的 MinIO 镜像;也兼容读取 `MINIO_IMAGE` | `minio/minio:RELEASE.2025-04-22T22-12-26Z` | | `FLNXI_VERSION_LIST_URL` | Agent 管理界面可选版本列表来源,返回 Registry tags 结构 | 未设置 | +| `LICENSE_SERVER_URL` | 主应用许可证服务地址,安装素材系统时会写入主应用容器 | 未设置 | +| `LICENSE_PROJECT_CODE` | 主应用许可证项目编码,安装素材系统时会写入主应用容器 | 未设置 | +| `LICENSE_REQUEST_SIGNING_SECRET` | 主应用请求许可证服务的签名密钥,至少 32 字符 | 未设置 | +| `LICENSE_PUBLIC_KEY` | 主应用验签用许可证公钥 | 未设置 | +| `LOCAL_ENCRYPTION_KEY` | 主应用本地许可证缓存加密密钥,至少 32 字符;未传入时 Agent 会为本次安装生成 | 自动生成 | 版本说明: - 当前 Go 版 Agent 的版本列表不再写死 Harbor;后续正式交付前,需要决定版本列表来自 Registry tags、许可证服务,还是发布管理接口。 -- 它还没有像 Node 版 Agent 那样完整接入 `LICENSE_SERVER_URL` / `LICENSE_PROJECT_CODE` 的远端版本检查链路。 +- Go 版 Agent 目前会把主应用启动必需的许可证配置写入容器,但还没有把“许可证服务可安装版本、灰度版本、客户授权版本范围”做成最终客户安装向导。 安装素材系统前,至少要给 Agent 容器配置: ```bash -e FLNXI_MATERIAL_IMAGE=registry.example.com/flnxi/material/flnxi-material:1.0.1 -e FLNXI_MATERIAL_IMAGE_REPOSITORY=registry.example.com/flnxi/material/flnxi-material +-e LICENSE_SERVER_URL=https://license.example.com +-e LICENSE_PROJECT_CODE=flnxi-sucai +-e LICENSE_REQUEST_SIGNING_SECRET='' +-e LICENSE_PUBLIC_KEY='' ``` ## 安全建议 diff --git a/flnxi-agent-go/__tests__/install-script.test.ts b/flnxi-agent-go/__tests__/install-script.test.ts new file mode 100644 index 0000000..03db6e1 --- /dev/null +++ b/flnxi-agent-go/__tests__/install-script.test.ts @@ -0,0 +1,23 @@ +import fs from 'fs' +import path from 'path' + +describe('flnxi-agent-go/install.sh', () => { + const script = fs.readFileSync( + path.join(process.cwd(), 'flnxi-agent-go', 'install.sh'), + 'utf8' + ) + + it('不会把 Agent 镜像当作素材系统镜像兜底', () => { + expect(script).toContain('FLNXI_MATERIAL_IMAGE=${FLNXI_MATERIAL_IMAGE:-}') + expect(script).not.toContain('FLNXI_MATERIAL_IMAGE=${FLNXI_MATERIAL_IMAGE:-$AGENT_IMAGE}') + }) + + it('会把素材系统安装必需的许可证配置透传给 Agent 容器', () => { + expect(script).toContain('LICENSE_SERVER_URL=${LICENSE_SERVER_URL:-}') + expect(script).toContain('LICENSE_PROJECT_CODE=${LICENSE_PROJECT_CODE:-}') + expect(script).toContain('LICENSE_REQUEST_SIGNING_SECRET=${LICENSE_REQUEST_SIGNING_SECRET:-}') + expect(script).toContain('LICENSE_PUBLIC_KEY=${LICENSE_PUBLIC_KEY:-}') + expect(script).toContain('LOCAL_ENCRYPTION_KEY=${LOCAL_ENCRYPTION_KEY:-}') + expect(script).toContain('未完整配置许可证服务参数') + }) +}) diff --git a/flnxi-agent-go/install.sh b/flnxi-agent-go/install.sh index 83af259..2468cf3 100644 --- a/flnxi-agent-go/install.sh +++ b/flnxi-agent-go/install.sh @@ -219,7 +219,7 @@ run_agent() { ) run_args+=( - -e "FLNXI_MATERIAL_IMAGE=${FLNXI_MATERIAL_IMAGE:-$AGENT_IMAGE}" + -e "FLNXI_MATERIAL_IMAGE=${FLNXI_MATERIAL_IMAGE:-}" -e "APP_IMAGE=${APP_IMAGE:-}" -e "FLNXI_MATERIAL_IMAGE_REPOSITORY=${FLNXI_MATERIAL_IMAGE_REPOSITORY:-}" -e "FLNXI_POSTGRES_IMAGE=${FLNXI_POSTGRES_IMAGE:-}" @@ -227,8 +227,21 @@ run_agent() { -e "FLNXI_MINIO_IMAGE=${FLNXI_MINIO_IMAGE:-}" -e "MINIO_IMAGE=${MINIO_IMAGE:-}" -e "FLNXI_VERSION_LIST_URL=${FLNXI_VERSION_LIST_URL:-}" + -e "LICENSE_SERVER_URL=${LICENSE_SERVER_URL:-}" + -e "LICENSE_PROJECT_CODE=${LICENSE_PROJECT_CODE:-}" + -e "LICENSE_REQUEST_SIGNING_SECRET=${LICENSE_REQUEST_SIGNING_SECRET:-}" + -e "LICENSE_PUBLIC_KEY=${LICENSE_PUBLIC_KEY:-}" + -e "LOCAL_ENCRYPTION_KEY=${LOCAL_ENCRYPTION_KEY:-}" ) + if [ -z "${FLNXI_MATERIAL_IMAGE:-${APP_IMAGE:-}}" ]; then + print_warning "未配置 FLNXI_MATERIAL_IMAGE / APP_IMAGE,Agent 可启动,但安装素材系统前必须补充完整主应用版本镜像" + fi + + if [ -z "${LICENSE_SERVER_URL:-}" ] || [ -z "${LICENSE_PROJECT_CODE:-}" ] || [ -z "${LICENSE_REQUEST_SIGNING_SECRET:-}" ] || [ -z "${LICENSE_PUBLIC_KEY:-}" ]; then + print_warning "未完整配置许可证服务参数,Agent 安装素材系统时会先拦截并提示补齐" + fi + if [ -n "$AGENT_AUTH_USERNAME" ] && [ -n "$AGENT_AUTH_PASSWORD" ]; then run_args+=( -e "AGENT_AUTH_USERNAME=$AGENT_AUTH_USERNAME" diff --git a/flnxi-agent-go/internal/api/handlers.go b/flnxi-agent-go/internal/api/handlers.go index 5d85ebd..eb3335c 100644 --- a/flnxi-agent-go/internal/api/handlers.go +++ b/flnxi-agent-go/internal/api/handlers.go @@ -112,12 +112,82 @@ func resolveInstallImages(req InstallRequest) (string, string, string, error) { if materialImage == "" { return "", "", "", fmt.Errorf("%s or %s is required", envMaterialImage, envMaterialImageCompat) } + if err := validateFixedImageTag(materialImage); err != nil { + return "", "", "", fmt.Errorf("material image: %w", err) + } postgresImage := firstNonEmpty(req.PostgresImage, os.Getenv(envPostgresImage), os.Getenv(envPostgresImageCompat), "postgres:15-alpine") minioImage := firstNonEmpty(req.MinioImage, os.Getenv(envMinioImage), os.Getenv(envMinioImageCompat), "minio/minio:RELEASE.2025-04-22T22-12-26Z") return materialImage, postgresImage, minioImage, nil } +type InstallLicenseConfig struct { + ServerURL string + ProjectCode string + RequestSigningSecret string + PublicKey string + LocalEncryptionKey string +} + +func resolveInstallLicenseConfig(req InstallRequest, secrets InstallSecrets) (InstallLicenseConfig, error) { + config := InstallLicenseConfig{ + ServerURL: firstNonEmpty(req.LicenseServerURL, os.Getenv(envLicenseServerURL)), + ProjectCode: firstNonEmpty(req.LicenseProjectCode, os.Getenv(envLicenseProjectCode)), + RequestSigningSecret: firstNonEmpty(req.LicenseRequestSigningSecret, os.Getenv(envLicenseRequestSigningSecret)), + PublicKey: firstNonEmpty(req.LicensePublicKey, os.Getenv(envLicensePublicKey)), + LocalEncryptionKey: firstNonEmpty(req.LocalEncryptionKey, os.Getenv(envLocalEncryptionKey), secrets.LocalEncryptionKey), + } + + missing := make([]string, 0) + if config.ServerURL == "" { + missing = append(missing, envLicenseServerURL) + } + if config.ProjectCode == "" { + missing = append(missing, envLicenseProjectCode) + } + if len(config.RequestSigningSecret) < 32 { + missing = append(missing, envLicenseRequestSigningSecret+"(至少32字符)") + } + if config.PublicKey == "" { + missing = append(missing, envLicensePublicKey) + } + if len(config.LocalEncryptionKey) < 32 { + missing = append(missing, envLocalEncryptionKey+"(至少32字符)") + } + if len(missing) > 0 { + return InstallLicenseConfig{}, fmt.Errorf("missing material license config: %s", strings.Join(missing, ", ")) + } + + return config, nil +} + +func materialAppEnv(req InstallRequest, secrets InstallSecrets, licenseConfig InstallLicenseConfig, localIP string, webPort int) []string { + return []string{ + "LICENSE_KEY=" + req.LicenseKey, + "LICENSE_SERVER_URL=" + licenseConfig.ServerURL, + "LICENSE_PROJECT_CODE=" + licenseConfig.ProjectCode, + "LICENSE_REQUEST_SIGNING_SECRET=" + licenseConfig.RequestSigningSecret, + "LICENSE_PUBLIC_KEY=" + licenseConfig.PublicKey, + "LOCAL_ENCRYPTION_KEY=" + licenseConfig.LocalEncryptionKey, + "DATABASE_URL=postgresql://" + secrets.DbUser + ":" + secrets.DbPassword + "@flnxi-db:5432/" + secrets.DbName, + // S3/MinIO 配置(内部访问使用 Docker 网络) + "S3_ENDPOINT=http://flnxi-minio:9000", + "S3_ACCESS_KEY_ID=" + secrets.MinioRootUser, + "S3_SECRET_ACCESS_KEY=" + secrets.MinioRootPassword, + "S3_BUCKET=" + secrets.S3Bucket, + "S3_REGION=us-east-1", + "S3_ENABLE_PATH_STYLE=1", + // S3 公开访问配置(浏览器访问使用宿主机 IP) + "S3_PUBLIC_DOMAIN=http://" + localIP + ":9000/" + secrets.S3Bucket, + "SERVER_IP=" + localIP, + "MINIO_API_PORT=9000", + // NextAuth 配置 + "NEXTAUTH_SECRET=" + secrets.NextAuthSecret, + "NEXTAUTH_URL=http://" + localIP + ":" + fmt.Sprintf("%d", webPort), + "APP_PUBLIC_URL=http://" + localIP + ":" + fmt.Sprintf("%d", webPort), + } +} + func resolveUpdateImage(req UpdateRequest) (string, error) { if image := firstNonEmpty(req.Image); image != "" { if err := validateFixedImageTag(image); err != nil { @@ -173,14 +243,19 @@ func validateFixedImageTag(image string) error { } const ( - envMaterialImage = "FLNXI_MATERIAL_IMAGE" - envMaterialImageCompat = "APP_IMAGE" - envMaterialImageRepository = "FLNXI_MATERIAL_IMAGE_REPOSITORY" - envPostgresImage = "FLNXI_POSTGRES_IMAGE" - envPostgresImageCompat = "POSTGRES_IMAGE" - envMinioImage = "FLNXI_MINIO_IMAGE" - envMinioImageCompat = "MINIO_IMAGE" - envVersionListURL = "FLNXI_VERSION_LIST_URL" + envMaterialImage = "FLNXI_MATERIAL_IMAGE" + envMaterialImageCompat = "APP_IMAGE" + envMaterialImageRepository = "FLNXI_MATERIAL_IMAGE_REPOSITORY" + envPostgresImage = "FLNXI_POSTGRES_IMAGE" + envPostgresImageCompat = "POSTGRES_IMAGE" + envMinioImage = "FLNXI_MINIO_IMAGE" + envMinioImageCompat = "MINIO_IMAGE" + envVersionListURL = "FLNXI_VERSION_LIST_URL" + envLicenseServerURL = "LICENSE_SERVER_URL" + envLicenseProjectCode = "LICENSE_PROJECT_CODE" + envLicenseRequestSigningSecret = "LICENSE_REQUEST_SIGNING_SECRET" + envLicensePublicKey = "LICENSE_PUBLIC_KEY" + envLocalEncryptionKey = "LOCAL_ENCRYPTION_KEY" ) var ( @@ -206,13 +281,18 @@ type UpdateStatus struct { } type InstallRequest struct { - LicenseKey string `json:"licenseKey"` - DataPath string `json:"dataPath"` - WebPort int `json:"webPort"` - AuthToken string `json:"authToken,omitempty"` - MaterialImage string `json:"materialImage,omitempty"` - PostgresImage string `json:"postgresImage,omitempty"` - MinioImage string `json:"minioImage,omitempty"` + LicenseKey string `json:"licenseKey"` + DataPath string `json:"dataPath"` + WebPort int `json:"webPort"` + AuthToken string `json:"authToken,omitempty"` + MaterialImage string `json:"materialImage,omitempty"` + PostgresImage string `json:"postgresImage,omitempty"` + MinioImage string `json:"minioImage,omitempty"` + LicenseServerURL string `json:"licenseServerUrl,omitempty"` + LicenseProjectCode string `json:"licenseProjectCode,omitempty"` + LicenseRequestSigningSecret string `json:"licenseRequestSigningSecret,omitempty"` + LicensePublicKey string `json:"licensePublicKey,omitempty"` + LocalEncryptionKey string `json:"localEncryptionKey,omitempty"` } type UpdateRequest struct { @@ -272,7 +352,14 @@ func GetInstallStatus(dc *docker.Client) gin.HandlerFunc { } } -func runInstall(dc *docker.Client, req InstallRequest) { +type installDockerClient interface { + PullImageWithProgress(ctx context.Context, image string, authStr string, onProgress func(current, total int64, status string)) error + ExecOnHost(ctx context.Context, cmd string) error + CreateNetworkIfNotExists(ctx context.Context, name string) error + CreateAndStartContainer(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, name string) (string, error) +} + +func runInstall(dc installDockerClient, req InstallRequest) { ctx := context.Background() setStep := func(step string, progress int, msg string) { statusMutex.Lock() @@ -302,6 +389,12 @@ func runInstall(dc *docker.Client, req InstallRequest) { return } + licenseConfig, err := resolveInstallLicenseConfig(req, secrets) + if err != nil { + setErr("许可证配置缺失: " + err.Error()) + return + } + // 拉取素材系统镜像 (0-30%) setStep("拉取镜像", 1, "正在拉取素材系统镜像...") err = dc.PullImageWithProgress(ctx, materialImage, authStr, func(current, total int64, status string) { @@ -421,27 +514,9 @@ func runInstall(dc *docker.Client, req InstallRequest) { // 启动素材系统 setStep("启动服务", 90, "启动素材系统...") cfg := &container.Config{ - Image: materialImage, - User: "0", // 以 root 用户运行,确保有权限写入挂载目录 - Env: []string{ - "LICENSE_KEY=" + req.LicenseKey, - "DATABASE_URL=postgresql://" + secrets.DbUser + ":" + secrets.DbPassword + "@flnxi-db:5432/" + secrets.DbName, - // S3/MinIO 配置(内部访问使用 Docker 网络) - "S3_ENDPOINT=http://flnxi-minio:9000", - "S3_ACCESS_KEY_ID=" + secrets.MinioRootUser, - "S3_SECRET_ACCESS_KEY=" + secrets.MinioRootPassword, - "S3_BUCKET=" + secrets.S3Bucket, - "S3_REGION=us-east-1", - "S3_ENABLE_PATH_STYLE=1", - // S3 公开访问配置(浏览器访问使用宿主机 IP) - "S3_PUBLIC_DOMAIN=http://" + localIP + ":9000/" + secrets.S3Bucket, - "SERVER_IP=" + localIP, - "MINIO_API_PORT=9000", - // NextAuth 配置 - "NEXTAUTH_SECRET=" + secrets.NextAuthSecret, - "NEXTAUTH_URL=http://" + localIP + ":" + fmt.Sprintf("%d", webPort), - "APP_PUBLIC_URL=http://" + localIP + ":" + fmt.Sprintf("%d", webPort), - }, + Image: materialImage, + User: "0", // 以 root 用户运行,确保有权限写入挂载目录 + Env: materialAppEnv(req, secrets, licenseConfig, localIP, webPort), ExposedPorts: nat.PortSet{"3000/tcp": struct{}{}}, } hcfg := &container.HostConfig{ diff --git a/flnxi-agent-go/internal/api/handlers_test.go b/flnxi-agent-go/internal/api/handlers_test.go index 786848b..81fa5a6 100644 --- a/flnxi-agent-go/internal/api/handlers_test.go +++ b/flnxi-agent-go/internal/api/handlers_test.go @@ -54,6 +54,121 @@ func TestResolveUpdateImageRejectsLatestVersion(t *testing.T) { } } +func TestResolveInstallImagesRequiresFixedMaterialTag(t *testing.T) { + tests := []struct { + name string + image string + wantErr string + }{ + {name: "missing", image: "", wantErr: envMaterialImage}, + {name: "no tag", image: "registry.local:5000/material/flnxi-material", wantErr: "fixed version tag"}, + {name: "latest", image: "registry.local:5000/material/flnxi-material:latest", wantErr: "latest is not allowed"}, + {name: "fixed tag", image: "registry.local:5000/material/flnxi-material:1.0.1"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, _, _, err := resolveInstallImages(InstallRequest{MaterialImage: tt.image}) + if tt.wantErr != "" { + if err == nil || !strings.Contains(err.Error(), tt.wantErr) { + t.Fatalf("resolveInstallImages() error = %v, want contains %q", err, tt.wantErr) + } + return + } + if err != nil { + t.Fatalf("resolveInstallImages() unexpected error: %v", err) + } + if got != tt.image { + t.Fatalf("resolveInstallImages() material = %q, want %q", got, tt.image) + } + }) + } +} + +func TestResolveInstallLicenseConfigRequiresMaterialRuntimeSecrets(t *testing.T) { + secrets := InstallSecrets{LocalEncryptionKey: strings.Repeat("b", 32)} + + _, err := resolveInstallLicenseConfig(InstallRequest{}, secrets) + if err == nil { + t.Fatal("resolveInstallLicenseConfig() expected error") + } + for _, want := range []string{ + envLicenseServerURL, + envLicenseProjectCode, + envLicenseRequestSigningSecret, + envLicensePublicKey, + } { + if !strings.Contains(err.Error(), want) { + t.Fatalf("resolveInstallLicenseConfig() error = %q, want %q", err.Error(), want) + } + } +} + +func TestMaterialAppEnvIncludesLicenseRuntimeConfig(t *testing.T) { + req := InstallRequest{LicenseKey: "FLNXI-TEST"} + secrets := InstallSecrets{ + DbUser: "flnxi", + DbPassword: "db-pass", + DbName: "flnxi", + MinioRootUser: "minio", + MinioRootPassword: "minio-pass", + S3Bucket: "materials", + NextAuthSecret: "next-secret", + LocalEncryptionKey: strings.Repeat("b", 32), + } + licenseConfig := InstallLicenseConfig{ + ServerURL: "https://license.example.test", + ProjectCode: "flnxi-sucai", + RequestSigningSecret: strings.Repeat("a", 32), + PublicKey: "-----BEGIN PUBLIC KEY-----\\nabc\\n-----END PUBLIC KEY-----", + LocalEncryptionKey: secrets.LocalEncryptionKey, + } + + env := envSliceToMap(materialAppEnv(req, secrets, licenseConfig, "192.168.1.20", 4320)) + for _, want := range []string{ + envLicenseServerURL, + envLicenseProjectCode, + envLicenseRequestSigningSecret, + envLicensePublicKey, + envLocalEncryptionKey, + "DATABASE_URL", + "S3_PUBLIC_DOMAIN", + "NEXTAUTH_URL", + "APP_PUBLIC_URL", + } { + if env[want] == "" { + t.Fatalf("materialAppEnv() missing %s in %v", want, env) + } + } + if env["APP_PUBLIC_URL"] != "http://192.168.1.20:4320" { + t.Fatalf("APP_PUBLIC_URL = %q, want host URL", env["APP_PUBLIC_URL"]) + } +} + +func TestRunInstallStopsBeforePullWhenLicenseConfigMissing(t *testing.T) { + resetInstallStatusForTest() + + fake := &fakeInstallDocker{} + runInstall(fake, InstallRequest{MaterialImage: "registry.local:5000/material/flnxi-material:1.0.1"}) + + statusMutex.RLock() + status := *installStatus + statusMutex.RUnlock() + + if status.Status != "failed" { + t.Fatalf("install status = %q, want failed", status.Status) + } + if !strings.Contains(status.Error, "许可证配置缺失") { + t.Fatalf("install error = %q, want license config error", status.Error) + } + if len(fake.pulledImages) != 0 { + t.Fatalf("pulled images = %v, want none before license config is valid", fake.pulledImages) + } + if len(fake.createdContainers) != 0 { + t.Fatalf("created containers = %v, want none before license config is valid", fake.createdContainers) + } +} + func TestRunUpdateRestoresOldContainerAfterHealthCheckFailure(t *testing.T) { resetUpdateStatusForTest() @@ -116,6 +231,50 @@ func resetUpdateStatusForTest() { statusMutex.Unlock() } +func resetInstallStatusForTest() { + statusMutex.Lock() + installStatus = &InstallStatus{Status: "running", Step: "初始化", Progress: 0} + statusMutex.Unlock() +} + +func envSliceToMap(values []string) map[string]string { + result := make(map[string]string, len(values)) + for _, value := range values { + parts := strings.SplitN(value, "=", 2) + if len(parts) != 2 { + continue + } + result[parts[0]] = parts[1] + } + return result +} + +type fakeInstallDocker struct { + pulledImages []string + createdContainers []string +} + +func (f *fakeInstallDocker) PullImageWithProgress(ctx context.Context, image string, authStr string, onProgress func(current, total int64, status string)) error { + f.pulledImages = append(f.pulledImages, image) + if onProgress != nil { + onProgress(1, 1, "done") + } + return nil +} + +func (f *fakeInstallDocker) ExecOnHost(ctx context.Context, cmd string) error { + return nil +} + +func (f *fakeInstallDocker) CreateNetworkIfNotExists(ctx context.Context, name string) error { + return nil +} + +func (f *fakeInstallDocker) CreateAndStartContainer(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, name string) (string, error) { + f.createdContainers = append(f.createdContainers, name) + return name + "-id", nil +} + type fakeUpdateDocker struct { container *docker.ContainerInfo healthErr error @@ -197,3 +356,4 @@ func contains(values []string, want string) bool { } var _ updateDockerClient = (*fakeUpdateDocker)(nil) +var _ installDockerClient = (*fakeInstallDocker)(nil) diff --git a/flnxi-agent-go/internal/api/install_secrets.go b/flnxi-agent-go/internal/api/install_secrets.go index 8b9a563..0bb06a2 100644 --- a/flnxi-agent-go/internal/api/install_secrets.go +++ b/flnxi-agent-go/internal/api/install_secrets.go @@ -7,13 +7,14 @@ import ( ) type InstallSecrets struct { - DbUser string - DbPassword string - DbName string - MinioRootUser string - MinioRootPassword string - S3Bucket string - NextAuthSecret string + DbUser string + DbPassword string + DbName string + MinioRootUser string + MinioRootPassword string + S3Bucket string + NextAuthSecret string + LocalEncryptionKey string } func generateInstallSecrets() (InstallSecrets, error) { @@ -32,14 +33,20 @@ func generateInstallSecrets() (InstallSecrets, error) { return InstallSecrets{}, err } + localEncryptionKey, err := randomHex(32) + if err != nil { + return InstallSecrets{}, err + } + return InstallSecrets{ - DbUser: "flnxi", - DbPassword: dbPassword, - DbName: "flnxi", - MinioRootUser: "flnxi", - MinioRootPassword: minioPassword, - S3Bucket: "flnxi", - NextAuthSecret: nextAuthSecret, + DbUser: "flnxi", + DbPassword: dbPassword, + DbName: "flnxi", + MinioRootUser: "flnxi", + MinioRootPassword: minioPassword, + S3Bucket: "flnxi", + NextAuthSecret: nextAuthSecret, + LocalEncryptionKey: localEncryptionKey, }, nil }