diff --git a/internal/bootstrap/data/user.go b/internal/bootstrap/data/user.go index b1b1c77f7..44f25440d 100644 --- a/internal/bootstrap/data/user.go +++ b/internal/bootstrap/data/user.go @@ -15,18 +15,28 @@ import ( ) func initUser() { - admin, err := op.GetAdmin() - adminPassword := random.String(8) - envpass := os.Getenv("OPENLIST_ADMIN_PASSWORD") - if flags.Dev { - adminPassword = "admin" - } else if len(envpass) > 0 { - adminPassword = envpass - } + initAdmin() + initGuest() +} + +func initAdmin() { + _, err := op.GetAdmin() if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { + // 系统尚未初始化:仅在开发模式或显式配置 OPENLIST_ADMIN_PASSWORD 时 + // 自动创建管理员;否则交由 Web 安装向导(POST /api/public/init/setup)完成。 + adminPassword := "admin" + envpass := os.Getenv("OPENLIST_ADMIN_PASSWORD") + if flags.Dev { + adminPassword = "admin" + } else if len(envpass) > 0 { + adminPassword = envpass + } else { + // 未初始化:不自动创建管理员,等待 Web 安装向导。 + return + } salt := random.String(16) - admin = &model.User{ + admin := &model.User{ Username: "admin", Salt: salt, PwdHash: model.TwoHashPwd(adminPassword, salt), @@ -47,7 +57,10 @@ func initUser() { utils.Log.Fatalf("[init user] Failed to get admin user: %v", err) } } - _, err = op.GetGuest() +} + +func initGuest() { + _, err := op.GetGuest() if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { salt := random.String(16) diff --git a/server/handles/setup.go b/server/handles/setup.go new file mode 100644 index 000000000..584d3c9d7 --- /dev/null +++ b/server/handles/setup.go @@ -0,0 +1,82 @@ +package handles + +import ( + "github.com/OpenListTeam/OpenList/v4/internal/conf" + "github.com/OpenListTeam/OpenList/v4/internal/model" + "github.com/OpenListTeam/OpenList/v4/internal/op" + "github.com/OpenListTeam/OpenList/v4/server/common" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +// InitStatus 返回系统是否已完成初始化(即是否已存在管理员账号)。 +// 前端据此判断是否跳转到安装向导。 +func InitStatus(c *gin.Context) { + if _, err := op.GetAdmin(); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + common.SuccessResp(c, gin.H{"initialized": false}) + return + } + common.ErrorResp(c, err, 500, true) + return + } + common.SuccessResp(c, gin.H{"initialized": true}) +} + +// InitSetupReq 系统初始化请求体 +type InitSetupReq struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` + SiteTitle string `json:"site_title"` +} + +// InitSetup 执行系统初始化:创建管理员账号并设置站点名称等初始参数。 +// 仅在系统尚未初始化时允许调用;已初始化则返回错误。 +func InitSetup(c *gin.Context) { + var req InitSetupReq + if err := c.ShouldBind(&req); err != nil { + common.ErrorResp(c, err, 400) + return + } + // 已初始化则拒绝 + if _, err := op.GetAdmin(); err == nil { + common.ErrorStrResp(c, "system has already been initialized", 400) + return + } else if !errors.Is(err, gorm.ErrRecordNotFound) { + common.ErrorResp(c, err, 500, true) + return + } + // 密码最小长度校验 + if len(req.Password) < 4 { + common.ErrorStrResp(c, "password must be at least 4 characters", 400) + return + } + admin := &model.User{ + Username: req.Username, + Role: model.ADMIN, + BasePath: "/", + Authn: "[]", + Permission: 0x71FF, + } + admin.SetPassword(req.Password) + if err := op.CreateUser(admin); err != nil { + common.ErrorResp(c, err, 500, true) + return + } + // 设置站点名称(如果提供) + if req.SiteTitle != "" { + item := model.SettingItem{ + Key: conf.SiteTitle, + Value: req.SiteTitle, + Type: conf.TypeString, + Group: model.SITE, + Flag: model.PUBLIC, + } + if err := op.SaveSettingItem(&item); err != nil { + common.ErrorResp(c, err, 500, true) + return + } + } + common.SuccessResp(c) +} diff --git a/server/router.go b/server/router.go index dc0e77efb..4be0dd84a 100644 --- a/server/router.go +++ b/server/router.go @@ -102,6 +102,8 @@ func Init(e *gin.Engine) { public.Any("/settings", handles.PublicSettings) public.Any("/offline_download_tools", handles.OfflineDownloadTools) public.Any("/archive_extensions", handles.ArchiveExtensions) + public.Any("/init_status", handles.InitStatus) + public.POST("/init/setup", handles.InitSetup) _fs(auth.Group("/fs")) fsAndShare(api.Group("/fs", middlewares.Auth(true)))