Files
FlowStack/backend/internal/database/database.go
T
Logan.Li 6841c1104a feat: 工具配置 Ubuntu 化 + nvm 管理 Node 版本
- 工具目录重写为 Ubuntu 构建环境导向(7 项),每项带安装命令提示
  (apt/nvm,未安装时一键复制)
- 新增 nvm 工具条目(真实检测,本机验证 0.40.6);Node.js 改为
  nvm 管理:默认版本可调,构建时通过 nvm install $NODE_VERSION
  灵活指定版本
- DeployTool 新增 installHint 字段(模型/更新白名单/seed)
- 新增 deploy/Dockerfile.buildnode:Ubuntu 22.04 + JDK17/Maven/Git/
  Docker CLI/Golang + nvm 默认 Node(--build-arg NODE_VERSION 可改)
- 工具页顶部说明条更新构建环境建议
2026-10-27 12:15:16 +08:00

244 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package database
import (
"log"
"os"
"path/filepath"
"time"
"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
}
if err := crypto.InitEncryptionKey(DataDir); 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{},
&model.Host{}, &model.K8sCluster{}, &model.Credential{}, &model.BuildNode{},
&model.DeployTool{}, &model.WebhookEndpoint{}, &model.JenkinsServer{}, &model.SystemSetting{},
); err != nil {
return err
}
seedAdmin()
seedDemoData()
seedResources()
seedTools()
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 应用 ----
now := time.Now()
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, SyncStatus: "Synced", HealthStatus: "Healthy", LastSyncAt: &now},
{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", SyncStatus: "OutOfSync", HealthStatus: "Progressing"},
})
// ---- 已安装插件 ----
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},
})
}
// seedResources 资源示例数据(主机/集群/凭证/构建节点/Webhook,各表独立判空)
func seedResources() {
var n int64
DB.Model(&model.Host{}).Count(&n)
if n == 0 {
DB.Create([]model.Host{
{Name: "build-host-01", IP: "10.0.8.11", SSHPort: 22, Username: "root", Tags: `["build","docker"]`, Status: "online", OS: "Ubuntu 22.04", Remark: "通用构建机"},
{Name: "prod-web-01", IP: "10.0.1.21", SSHPort: 22, Username: "admin", Tags: `["production","web"]`, Status: "online", OS: "CentOS 7.9", Remark: "Nginx 前端"},
})
}
DB.Model(&model.K8sCluster{}).Count(&n)
if n == 0 {
DB.Create([]model.K8sCluster{
{Name: "ack-production", Context: "ack-production", APIServer: "https://k8s.prod:6443", Version: "v1.30.1", NodeCount: 3, Namespaces: `["aiops","default","kube-system"]`, Status: "disconnected"},
})
}
DB.Model(&model.Credential{}).Count(&n)
if n == 0 {
enc1, _ := crypto.EncryptField(`{"private_key":"CHANGE_ME","ssh_user":"root","ssh_port":22}`)
DB.Create([]model.Credential{
{Name: "prod-ssh-key", Type: "ssh", Scope: "production-group", Content: enc1, Summary: "ssh_key root@22"},
{Name: "registry-example", Type: "docker", Scope: "全局", Summary: "docker registry.example.com aiops-pull"},
})
}
DB.Model(&model.BuildNode{}).Count(&n)
if n == 0 {
hb := time.Now().Add(-10 * time.Second)
DB.Create([]model.BuildNode{
{Name: "build-host-01", IP: "10.0.8.11", Labels: `["docker","go","java"]`, MaxConcurrency: 4, Status: "online", CurrentJobs: 1, DiskInfo: "78G/200G", LastHeartbeat: &hb},
{Name: "build-host-02", IP: "10.0.8.12", Labels: `["docker","nodejs"]`, MaxConcurrency: 2, Status: "busy", CurrentJobs: 2, DiskInfo: "156G/500G", LastHeartbeat: &hb},
{Name: "build-host-03", IP: "10.0.8.13", Labels: `["python"]`, MaxConcurrency: 2, Status: "offline", DiskInfo: "45G/100G", LastHeartbeat: &hb},
})
}
DB.Model(&model.WebhookEndpoint{}).Count(&n)
if n == 0 {
DB.Create([]model.WebhookEndpoint{
{Name: "发布成功通知", URL: "https://open.feishu.cn/open-apis/bot/v2/hook/CHANGE_ME", Events: `["pipeline.success","pipeline.failed"]`, Active: true, LastStatus: "none"},
})
}
DB.Model(&model.SystemSetting{}).Count(&n)
if n == 0 {
DB.Create(&model.SystemSetting{
ID: 1, ToolMode: "local",
Theme: `{"mode":"light","primaryColor":"#0969da","fontSize":"medium","density":"comfortable","sidebarStyle":"full","borderRadius":"soft"}`,
Notification: `{"email":false,"browser":true,"pipelineSuccess":true,"pipelineFailed":true,"securityAlert":false}`,
})
}
}
// seedTools 工具清单(Ubuntu 构建环境导向;Node 由 nvm 管理默认版本,构建时可按需 nvm install)
func seedTools() {
var n int64
DB.Model(&model.DeployTool{}).Count(&n)
if n > 0 {
return
}
DB.Create([]model.DeployTool{
{ToolKey: "jdk", DisplayName: "JDK", Category: "build", Version: "17", InstallMode: "preinstalled", Path: "/usr/lib/jvm/java-17-openjdk-amd64", CheckCommand: "java -version", InstallHint: "apt update && apt install -y openjdk-17-jdk", SortOrder: 1},
{ToolKey: "maven", DisplayName: "Maven", Category: "build", Version: "3.8", InstallMode: "lazy", Path: "/usr/share/maven", CheckCommand: "mvn -version", InstallHint: "apt install -y maven", SortOrder: 2},
{ToolKey: "golang", DisplayName: "Golang", Category: "build", Version: "1.24", InstallMode: "preinstalled", Path: "/usr/local/go", CheckCommand: "go version", InstallHint: "apt install -y golang-1.24-go 或官方 tar 包解压至 /usr/local/go", SortOrder: 3},
{ToolKey: "nvm", DisplayName: "nvm(Node 版本管理)", Category: "build", Version: "0.40", InstallMode: "preinstalled", Path: "$HOME/.nvm", CheckCommand: `. "$HOME/.nvm/nvm.sh" && nvm --version`, InstallHint: "curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash", SortOrder: 4},
{ToolKey: "nodejs", DisplayName: "Node.js(nvm 管理)", Category: "build", Version: "20", InstallMode: "lazy", Path: "$HOME/.nvm/versions/node", CheckCommand: "node -v", InstallHint: "nvm install 20 && nvm alias default 20(构建时可用 nvm install 18 按需切版本)", SortOrder: 5},
{ToolKey: "git", DisplayName: "Git", Category: "build", Version: "2.40", InstallMode: "preinstalled", Path: "/usr/bin/git", CheckCommand: "git --version", InstallHint: "apt install -y git", SortOrder: 6},
{ToolKey: "docker-cli", DisplayName: "Docker CLI", Category: "deploy", Version: "26", InstallMode: "lazy", Path: "/usr/bin/docker", CheckCommand: "docker --version", InstallHint: "apt install -y docker.io", SortOrder: 7},
})
}