Files
rill/internal/database/seed.go
T
kevin f440bb22b6 增加工具模块与可信代理配置
- internal/utils:ClientIP 按序枚举 CDN/代理头(含 RFC 7239 Forwarded),仅在可信代理来源时采信,否则回退直连 IP;RemoteIP 取直连地址;RandomString 生成安全随机串
- 新增 server.trusted_proxies 配置(IP/CIDR,ConfigVersion 2→3 自动补全),启动时同步应用到 gin 与 utils
- 初始管理员密码生成改用 utils.RandomString,原密码测试迁至 utils
2026-09-21 20:28:38 +08:00

101 lines
2.7 KiB
Go

package database
import (
"fmt"
"log/slog"
"os"
"path/filepath"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"rill/internal/model"
"rill/internal/utils"
)
const (
adminUsername = "admin"
adminEmail = "admin@example.com"
adminPasswordLen = 16
adminPasswordFilename = "admin_password.txt"
)
// seedAdminUser 创建初始管理员用户并加入 admin 组,密码随机生成且仅在迁移时打印一次。
func seedAdminUser(tx *gorm.DB) error {
var count int64
if err := tx.Model(&model.User{}).Where("username = ?", adminUsername).Count(&count).Error; err != nil {
return err
}
if count > 0 {
slog.Warn("已存在同名用户,跳过初始管理员创建", "username", adminUsername)
return nil
}
password, err := utils.RandomString(adminPasswordLen)
if err != nil {
return err
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
user := model.User{
Username: adminUsername,
Email: adminEmail,
PasswordHash: string(hash),
Nickname: "Administrator",
Status: 1,
}
if err := tx.Create(&user).Error; err != nil {
return err
}
if err := tx.Exec(
"INSERT INTO user_group_members (user_id, user_group_id) VALUES (?, ?)",
user.ID, model.GroupIDAdmin,
).Error; err != nil {
return err
}
passwordFile := adminPasswordPath(tx)
if err := writeAdminPasswordFile(passwordFile, password); err != nil {
slog.Warn("初始管理员密码文件保存失败", "path", passwordFile, "err", err)
}
fmt.Printf(
"\n初始管理员账号已创建,密码仅显示这一次,请立即保存:\n 用户名: %s\n 密码: %s\n 密码文件: %s(登录后请立即删除该文件)\n\n",
adminUsername, password, passwordFile,
)
return nil
}
// adminPasswordPath 密码文件与 SQLite 数据库同目录;其他驱动回落到 ./data。
func adminPasswordPath(db *gorm.DB) string {
if db.Dialector.Name() == "sqlite" {
if file := sqliteDatabaseFile(db); file != "" && file != ":memory:" {
return filepath.Join(filepath.Dir(file), adminPasswordFilename)
}
}
return filepath.Join("data", adminPasswordFilename)
}
func sqliteDatabaseFile(db *gorm.DB) string {
var rows []struct {
File string
}
if err := db.Raw("PRAGMA database_list").Scan(&rows).Error; err != nil || len(rows) == 0 {
return ""
}
return rows[0].File
}
func writeAdminPasswordFile(path, password string) error {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
content := fmt.Sprintf(
"初始管理员账号(密码仅明文保存在此,登录后请立即删除本文件)\n\n用户名: %s\n密码: %s\n",
adminUsername, password,
)
return os.WriteFile(path, []byte(content), 0o600)
}