package database import ( "log" "os" "path/filepath" "github.com/glebarez/sqlite" "gorm.io/gorm" "flowstack/internal/crypto" "flowstack/internal/model" ) var DB *gorm.DB // DataDir 数据目录(密码文件等运行时产物存放处) var DataDir string // Init 打开 SQLite、自动建表并写入示例数据(仅首次) func Init() error { DataDir = filepath.Join(mustDir(), "data") if err := os.MkdirAll(DataDir, 0o755); err != nil { return err } db, err := gorm.Open(sqlite.Open(filepath.Join(DataDir, "flowstack.db")), &gorm.Config{}) if err != nil { return err } DB = db if err := DB.AutoMigrate( &model.User{}, &model.Pipeline{}, &model.PipelineNode{}, &model.PipelineEdge{}, &model.PipelineRun{}, &model.RunStage{}, &model.LogEntry{}, &model.Application{}, &model.Plugin{}, ); err != nil { return err } seedAdmin() seedDemoData() return nil } // mustDir 返回二进制所在目录;go run 时回退到当前目录 func mustDir() string { if exe, err := os.Executable(); err == nil { return filepath.Dir(exe) } return "." } // seedAdmin 首次启动生成随机密码的管理员账号: // 密码打印到日志("已生成管理员账号",grep -A6 可见)并写入 data/initial-admin-password.txt。 func seedAdmin() { var count int64 DB.Model(&model.User{}).Count(&count) if count > 0 { return } password, err := crypto.RandomPassword(16) if err != nil { log.Printf("[seed] 管理员密码生成失败: %v", err) return } hash, err := crypto.HashPassword(password) if err != nil { log.Printf("[seed] 管理员密码哈希失败: %v", err) return } DB.Create(&model.User{ Username: "admin", Password: hash, Nickname: "Administrator", Role: "admin", }) pwdFile := filepath.Join(DataDir, "initial-admin-password.txt") if err := os.WriteFile(pwdFile, []byte("admin / "+password+"\n"), 0o600); err != nil { log.Printf("[seed] 密码文件写入失败: %v", err) } log.Println("========================================================") log.Println("[seed] 已生成管理员账号(首次启动)") log.Printf("[seed] 用户名: admin") log.Printf("[seed] 初始密码: %s", password) log.Printf("[seed] 密码已写入: %s", pwdFile) log.Println("[seed] 请登录后立即修改密码") log.Println("========================================================") } func seedDemoData() { var count int64 DB.Model(&model.Pipeline{}).Count(&count) if count > 0 { return } log.Println("[seed] 首次启动,写入示例数据…") // ---- 流水线 1:aiops-backend(Go + K8s) ---- backend := model.Pipeline{ Name: "aiops-backend", Description: "AIOps 后端服务(Go + K8s 滚动发布)", Type: "native", SourceRepo: "https://gitee.com/attacker/aiops.git", SourceBranch: "master", DeployType: "k8s", Env: "production", } DB.Create(&backend) DB.Create([]model.PipelineNode{ {PipelineID: backend.ID, NodeID: "git-clone-1", Type: "source", Name: "git-clone", Category: "source", PositionX: 60, PositionY: 220, Width: 220, Height: 120, Config: map[string]interface{}{"url": "https://gitee.com/attacker/aiops.git", "revision": "master"}}, {PipelineID: backend.ID, NodeID: "unit-test-1", Type: "test", Name: "unit-test", Category: "test", PositionX: 400, PositionY: 60, Width: 220, Height: 120, Config: map[string]interface{}{"test-command": "go test -v -race ./..."}}, {PipelineID: backend.ID, NodeID: "kaniko-build-1", Type: "build", Name: "kaniko-build", Category: "build", PositionX: 400, PositionY: 380, Width: 220, Height: 120, Config: map[string]interface{}{"dockerfile": "Dockerfile", "image": "registry.example.com/aiops/aiops-backend"}}, {PipelineID: backend.ID, NodeID: "trivy-scan-1", Type: "security", Name: "trivy-scan", Category: "security", PositionX: 740, PositionY: 380, Width: 220, Height: 120, Config: map[string]interface{}{"image": "registry.example.com/aiops/aiops-backend", "severity": "HIGH,CRITICAL"}}, {PipelineID: backend.ID, NodeID: "kubectl-deploy-1", Type: "deploy", Name: "kubectl-deploy", Category: "deploy", PositionX: 1080, PositionY: 220, Width: 220, Height: 120, Config: map[string]interface{}{"manifest": "k8s/production/", "namespace": "aiops"}}, }) DB.Create([]model.PipelineEdge{ {PipelineID: backend.ID, EdgeID: "e-clone-test", Source: "git-clone-1", Target: "unit-test-1", Animated: true}, {PipelineID: backend.ID, EdgeID: "e-clone-build", Source: "git-clone-1", Target: "kaniko-build-1", Animated: true}, {PipelineID: backend.ID, EdgeID: "e-build-scan", Source: "kaniko-build-1", Target: "trivy-scan-1", Animated: true}, {PipelineID: backend.ID, EdgeID: "e-test-deploy", Source: "unit-test-1", Target: "kubectl-deploy-1", Animated: true}, {PipelineID: backend.ID, EdgeID: "e-scan-deploy", Source: "trivy-scan-1", Target: "kubectl-deploy-1", Animated: true}, }) // ---- 流水线 2:aiops-web(Vue + 主机部署) ---- web := model.Pipeline{ Name: "aiops-web", Description: "AIOps 前端(Vue + 主机 Nginx 发布)", Type: "native", SourceRepo: "https://gitee.com/attacker/aiops-web.git", SourceBranch: "master", DeployType: "host", Env: "production", } DB.Create(&web) DB.Create([]model.PipelineNode{ {PipelineID: web.ID, NodeID: "git-clone-1", Type: "source", Name: "git-clone", Category: "source", PositionX: 60, PositionY: 200, Width: 220, Height: 120, Config: map[string]interface{}{"url": "https://gitee.com/attacker/aiops-web.git", "revision": "master"}}, {PipelineID: web.ID, NodeID: "npm-build-1", Type: "build", Name: "npm build", Category: "build", PositionX: 400, PositionY: 60, Width: 220, Height: 120, Config: map[string]interface{}{"script": "npm ci && npm run build"}}, {PipelineID: web.ID, NodeID: "npm-audit-1", Type: "security", Name: "npm audit", Category: "security", PositionX: 400, PositionY: 340, Width: 220, Height: 120, Config: map[string]interface{}{"severity": "high"}}, {PipelineID: web.ID, NodeID: "host-deploy-1", Type: "deploy", Name: "主机部署", Category: "deploy", PositionX: 760, PositionY: 200, Width: 220, Height: 120, Config: map[string]interface{}{"主机组": "production-group", "目标路径": "/usr/share/nginx/html", "分批数量": "2"}}, {PipelineID: web.ID, NodeID: "cdn-refresh-1", Type: "custom", Name: "刷新 CDN", Category: "custom", PositionX: 1120, PositionY: 200, Width: 220, Height: 120, Config: map[string]interface{}{"刷新URL": "https://aiops.example.com/*"}}, }) DB.Create([]model.PipelineEdge{ {PipelineID: web.ID, EdgeID: "e-clone-npm", Source: "git-clone-1", Target: "npm-build-1", Animated: true}, {PipelineID: web.ID, EdgeID: "e-clone-audit", Source: "git-clone-1", Target: "npm-audit-1", Animated: true}, {PipelineID: web.ID, EdgeID: "e-npm-deploy", Source: "npm-build-1", Target: "host-deploy-1", Animated: true}, {PipelineID: web.ID, EdgeID: "e-audit-deploy", Source: "npm-audit-1", Target: "host-deploy-1", Animated: true}, {PipelineID: web.ID, EdgeID: "e-deploy-cdn", Source: "host-deploy-1", Target: "cdn-refresh-1", Animated: true}, }) // ---- GitOps 应用 ---- DB.Create([]model.Application{ {Name: "aiops-backend", Repo: "https://gitea.91ai.io/rlwj-lab/aiops-gitops.git", Branch: "main", Path: "apps/aiops-backend", Cluster: "ack-production", Namespace: "aiops", ManifestType: "kustomize", SyncPolicy: "automated", AutoPrune: true, SelfHeal: true}, {Name: "aiops-web", Repo: "https://gitea.91ai.io/rlwj-lab/aiops-gitops.git", Branch: "main", Path: "apps/aiops-web", Cluster: "ack-production", Namespace: "aiops", ManifestType: "kustomize", SyncPolicy: "manual"}, }) // ---- 已安装插件 ---- DB.Create([]model.Plugin{ {PluginID: "jenkins-import", Name: "Jenkins 导入", Description: "一键导入既有 Jenkins Job 为 FlowStack 流水线", Version: "1.2.0", Author: "FlowStack Team", Category: "部署", Enabled: true}, {PluginID: "webhook-notify", Name: "Webhook 通知", Description: "发布成功/失败后回调企业微信、钉钉", Version: "1.0.3", Author: "FlowStack Team", Category: "通知", Enabled: true}, {PluginID: "k8s-rollout", Name: "K8s 滚动发布", Description: "Deployment 滚动更新与回滚", Version: "2.3.1", Author: "CloudNative", Category: "构建", Enabled: false}, }) }