package seed import ( "crypto/rand" "log" "os" "path/filepath" "opsxloki/models" "opsxloki/pkg/crypto" "opsxloki/repository" ) // InitPasswordFile 首次启动生成的随机管理员密码,存放于数据卷根目录。 // 用户名 admin 首次登录并修改密码后,该文件会被自动删除(见 CleanupInitPassword)。 const InitPasswordFile = "initial-admin-password.txt" // randomPassword 生成 n 位随机密码(大小写字母 + 数字,排除 0/O/1/l/I 等易混淆字符) func randomPassword(n int) (string, error) { const charset = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789" buf := make([]byte, n) if _, err := rand.Read(buf); err != nil { return "", err } for i := range buf { buf[i] = charset[int(buf[i])%len(charset)] } return string(buf), nil } func dataDir() string { if d := os.Getenv("DATA_DIR"); d != "" { return d } return "/app/data" } func Seed() { db := repository.GetDB() db.RLock() hasUsers := len(db.Users) > 0 db.RUnlock() if !hasUsers { // 优先级:环境变量 ADMIN_INIT_PASSWORD > 随机生成 pwd := os.Getenv("ADMIN_INIT_PASSWORD") generated := false if pwd == "" { var err error if pwd, err = randomPassword(16); err != nil { // 不降级到默认口令:宁可启动失败,也不留公开默认密码 log.Fatalf("[SEED] 生成随机初始密码失败: %v;请通过 ADMIN_INIT_PASSWORD 环境变量指定初始密码", err) } generated = true } hash, _ := crypto.HashPassword(pwd) repository.GetDB().CreateUser(&models.User{ Username: "admin", Password: hash, Nickname: "Administrator", Role: "admin", }) if generated { path := filepath.Join(dataDir(), InitPasswordFile) if err := os.WriteFile(path, []byte(pwd+"\n"), 0600); err != nil { log.Printf("[SEED] 写入初始密码文件失败: %v", err) } log.Printf("==================================================") log.Printf(" 首次启动:已生成管理员账号") log.Printf(" 用户名 : admin") log.Printf(" 密 码 : %s", pwd) log.Printf(" 已保存 : %s", path) log.Printf(" 获取方式:") log.Printf(" docker compose exec <服务名> cat %s", path) log.Printf(" kubectl -n exec deploy/opsx-loki -- cat %s", path) log.Printf(" ⚠️ 首次登录后请立即修改密码(改密后本文件自动删除)") log.Printf("==================================================") } else { log.Printf("[SEED] 管理员初始密码来自环境变量 ADMIN_INIT_PASSWORD") } } // 内置告警规则:有 managed 数据源(部署过 Loki)时补齐通用模板(默认禁用) SeedAlertRules() } // CleanupInitPassword 在 admin 首次修改密码后调用,删除明文密码文件。 // 幂等:文件不存在时静默返回。 func CleanupInitPassword() { path := filepath.Join(dataDir(), InitPasswordFile) if _, err := os.Stat(path); err != nil { return } if err := os.Remove(path); err != nil { log.Printf("[SEED] 删除初始密码文件失败: %v", err) return } log.Printf("[SEED] 初始密码文件已删除: %s", path) }