- 品牌: Go 模块/数据文件/二进制/构建部署资产全部 opsx→lokistack;前端 localStorage 命名空间迁移(读旧写新,含按用户 id 前缀迁移);修复 nginx upstream 与 Makefile 引用已改名服务的断裂 - i18n: react-i18next 落地,common + 11 个页面域命名空间全站中英(菜单/顶栏/登录/列表页/表单/确认弹窗/状态徽章);设置页语言即时切换;修复词典自注册导致的循环导入白屏 - 用户管理: 批量删除(POST /users/batch-delete,不删自己/最后管理员,部分失败不阻断)+ 行复选/全选/批量工具条 - 主题: 浅色侧栏(中性灰激活态)、四态模式(浅色/深色/跟随系统/定时切换,按时区判定)、顶栏太阳月亮二态、设置页紧凑分段选择器 - UI: 登录记住用户名、CardActionButton 统一五页卡片操作钮、shadcn Badge 替换手写徽章、语义色修正 - 清理: 移除主机 Web 终端(前后端)、k8s-helm/Helm 遗留代码与字段、README 重写精简 - 修复: 侧栏与日志告警 order 重复、路由标题 i18n key 未翻译、词典双逗号等
148 lines
4.6 KiB
Go
148 lines
4.6 KiB
Go
package seed
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"lokistack/models"
|
|
"lokistack/pkg/crypto"
|
|
"lokistack/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 <ns> exec deploy/lokistack -- cat %s", path)
|
|
log.Printf(" ⚠️ 首次登录后请立即修改密码(改密后本文件自动删除)")
|
|
log.Printf("==================================================")
|
|
} else {
|
|
log.Printf("[SEED] 管理员初始密码来自环境变量 ADMIN_INIT_PASSWORD")
|
|
}
|
|
}
|
|
|
|
// 内置默认查询策略:所有人可查询 default 命名空间。
|
|
// 目的:新环境/新账号开箱即有可查范围,快捷 LogQL 的 namespace 下拉不会空。
|
|
seedDefaultPolicy()
|
|
|
|
// 内置告警规则:有 managed 数据源(部署过 Loki)时补齐通用模板(默认禁用)
|
|
SeedAlertRules()
|
|
}
|
|
|
|
// seedDefaultPolicy 创建「默认策略」:所有用户可查询 default 命名空间。
|
|
//
|
|
// 设计要点:
|
|
// - UserIDs 里的 0 是哨兵值,表示「所有人」(anyone / *),见 policyGroupAccess
|
|
// - 只在当前还没有任何策略组时创建,避免覆盖管理员的自定义配置
|
|
// - 命名空间取 default 作为占位;管理员可在「查询授权」里改成实际业务命名空间
|
|
// (如 astralx / prod),改动对该策略组所有用户立即生效
|
|
func seedDefaultPolicy() {
|
|
db := repository.GetDB()
|
|
if db == nil {
|
|
return
|
|
}
|
|
db.RLock()
|
|
hasGroup := len(db.LogPolicyGroups) > 0
|
|
var sourceID uint
|
|
if len(db.LogSources) > 0 {
|
|
sourceID = db.LogSources[0].ID
|
|
}
|
|
db.RUnlock()
|
|
if hasGroup || sourceID == 0 {
|
|
return
|
|
}
|
|
|
|
id := db.NextID("logPolicyGroups")
|
|
now := time.Now()
|
|
group := models.LogPolicyGroup{
|
|
ID: id,
|
|
Name: "默认策略(所有人 · default 命名空间)",
|
|
SourceID: sourceID,
|
|
Labels: map[string]string{"namespace": "default"},
|
|
UserIDs: []uint{0}, // 0 = 所有人
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
db.Lock()
|
|
db.LogPolicyGroups = append(db.LogPolicyGroups, group)
|
|
db.Unlock()
|
|
db.RequestSave()
|
|
log.Printf("[SEED] 已创建默认查询策略:所有人可查 default 命名空间(可在「查询授权」改为实际业务命名空间)")
|
|
}
|
|
|
|
// 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)
|
|
}
|