install

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 26, 2026 License: AGPL-3.0 Imports: 17 Imported by: 0

Documentation

Overview

Package install 实现首次安装向导:连接测试、安装执行与状态查询。

站点还没有数据库连接信息时 serve 进入安装模式,只挂本包的路由与 Console 静态资源, 其余一律不装配。安装成功后会把连接串写进工作目录的 install.json(见 config 包), 服务随即重新初始化一次,这一次带着数据库,就是正常的站点。

本包不认识模块清单,也不认识设置模块:迁移、初始管理员与站点设置由调用方经 Provision 注入(见 cmd/lumo/install.go),安装包只负责编排顺序与守住安装的前置条件。

Index

Constants

This section is empty.

Variables

View Source
var ErrAlreadyInstalled = errors.New("站点已完成安装")

ErrAlreadyInstalled 表示站点已经装好了,安装入口一律拒绝。

Functions

func WrapBadRequest

func WrapBadRequest(err error) error

WrapBadRequest 把调用方(注入的 Provision)返回的错误标记成站长可自行修正的, 处理器据此原样回显而不是当成内部故障。

安装阶段的失败几乎都属于这一类:数据库权限不足、参数写错、口令不合规。

Types

type AdminInput

type AdminInput struct {
	Username string `` /* 126-byte string literal not displayed */
	Email    string `json:"email" format:"email" maxLength:"254"`
	Password string `json:"password" minLength:"8" maxLength:"128"`
}

AdminInput 是初始管理员账号。

用户名与口令的规则照抄数据库层的约束(migrations/00002_auth.sql 的 CHECK 与 auth/password 的长度限制):向导早一步拦住,比让站长在最后一步收到一条 「违反约束 users_username_format」要好得多。

type ApplyInput

type ApplyInput struct {
	Database DatabaseInput `json:"database"`
	Site     SiteInput     `json:"site"`
	Admin    AdminInput    `json:"admin"`
}

ApplyInput 是执行安装所需的全部输入。

type ApplyResult

type ApplyResult struct {
	Steps []Step `json:"steps"`
	// ExternalURL 是站长填的对外地址,可能为空。
	ExternalURL string `json:"externalUrl,omitempty"`
	// AdminCreated 为假表示库里已有用户,未创建新的管理员。
	AdminCreated bool `json:"adminCreated"`
}

ApplyResult 是安装执行的结果。

type BadRequestError

type BadRequestError struct {
	// contains filtered or unexported fields
}

BadRequestError 标记「按提示改输入就能解决」的错误。

处理器靠它把这些消息原样回给向导(400),而把其余错误当内部故障处理(500, 细节只进日志)——数据库口令写错和磁盘写不进去,对站长是两件完全不同的事。

func (*BadRequestError) Error

func (e *BadRequestError) Error() string

func (*BadRequestError) Unwrap

func (e *BadRequestError) Unwrap() error

type Check

type Check struct {
	Key    string `json:"key"`
	Label  string `json:"label"`
	OK     bool   `json:"ok"`
	Detail string `json:"detail"`
}

Check 是一项安装前置检查的结果。

type DatabaseInfo

type DatabaseInfo struct {
	ServerVersion string `json:"serverVersion"`
	Database      string `json:"database"`
	User          string `json:"user"`
	Encoding      string `json:"encoding"`
	LatencyMS     int64  `json:"latencyMs"`
	// ExistingUsers 是目标库里已有的用户数;-1 表示该库还没有 Lumo 的数据表。
	// 大于 0 时向导要提醒站长:继续下去是接管一个已有站点,不是新建。
	ExistingUsers int64 `json:"existingUsers"`
}

DatabaseInfo 是测试连接的结果。

type DatabaseInput

type DatabaseInput struct {
	DSN      string `json:"dsn,omitempty" doc:"完整连接串;填了就忽略下面各项"`
	Host     string `json:"host,omitempty" doc:"主机地址,例如 127.0.0.1"`
	Port     int    `json:"port,omitempty" doc:"端口,默认 5432" minimum:"1" maximum:"65535"`
	User     string `json:"user,omitempty" doc:"用户名"`
	Password string `json:"password,omitempty" doc:"口令"`
	Name     string `json:"name,omitempty" doc:"数据库名"`
	SSLMode  string `json:"sslMode,omitempty" doc:"TLS 模式" enum:"disable,prefer,require,verify-ca,verify-full"`
}

DatabaseInput 是数据库连接信息。填了 DSN 就忽略逐项字段。

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler 提供安装向导的三个端点。

func NewHandler

func NewHandler(svc *Service, logger *slog.Logger) *Handler

NewHandler 构造 Handler。

func (*Handler) Register

func (h *Handler) Register(api huma.API)

Register 把安装端点挂到根 huma API(planes.API())上。

三平面都不合适:它们的解析凭据中间件要查库,而此刻库还不存在 —— 浏览器只要带着任何旧 Cookie 过来就会被挡成 401,向导第一步都走不到。

三个端点一律无条件注册,包括已经装好的站点:OpenAPI 规范要稳定。若按运行时状态 条件注册,`lumo openapi` 在不同库上会导出不同的规范,进而污染 Console 的生成类型。 「能不能装」放在处理器内部判断。

这些端点没有 CSRF 中间件。跨站表单发不出 application/json,而 huma 只解析 JSON 请求体,所以浏览器侧的跨站提交在这里自然失效;至于能直接访问服务器的攻击者, 他本来就能自己跑一遍安装向导,CSRF 拦不住也不增加风险。

type Options

type Options struct {
	// Config 是本次启动的配置(此时必然不含可用 DSN)。
	Config config.Config
	// DataDir 是已初始化的工作目录绝对路径。
	DataDir string
	Logger  *slog.Logger
	Version string
	// Provision 由调用方注入。
	Provision ProvisionFunc
}

Options 是构造 Service 的依赖。

type Params

type Params struct {
	DSN   string
	Site  SiteInput
	Admin AdminInput
}

Params 是交给调用方执行安装所需的全部输入。

type ProvisionFunc

type ProvisionFunc func(ctx context.Context, db *database.DB, cfg config.Config, in Params) (ProvisionResult, error)

ProvisionFunc 在目标库上完成安装的实质工作:迁移、内置角色、初始管理员、站点设置。

由 cmd/lumo 注入:它拿得到模块清单与设置模块,而本包刻意不认识它们。

type ProvisionResult

type ProvisionResult struct {
	// AdminCreated 为假表示目标库里已有用户,故未创建新的管理员。
	AdminCreated bool
}

ProvisionResult 是安装执行的结果。

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service 是安装向导的服务端逻辑。

func New

func New(opts Options) *Service

New 构造 Service。

func (*Service) Apply

func (s *Service) Apply(ctx context.Context, in ApplyInput) (*ApplyResult, error)

Apply 执行安装:连库、迁移、建管理员、写站点设置,最后落盘安装产物。

顺序是有意的:安装产物写在最后一步。中途任何一步失败都不会留下这个文件, 于是下次启动仍然进入安装模式,站长可以改完参数重来。

func (*Service) Done

func (s *Service) Done() <-chan struct{}

Done 在安装成功时关闭,供 serve 据此重启一轮。

func (*Service) Installed

func (s *Service) Installed() bool

Installed 报告本次进程内安装是否已经成功。

func (*Service) Status

func (s *Service) Status() StatusView

Status 汇总当前是否可安装与前置检查结果。

func (*Service) TestDatabase

func (s *Service) TestDatabase(ctx context.Context, in DatabaseInput) (*DatabaseInfo, error)

TestDatabase 试连目标库并回报它的身份。

type SiteInput

type SiteInput struct {
	Title string `json:"title,omitempty" maxLength:"100" doc:"站点名称"`
	URL   string `json:"url,omitempty" maxLength:"255" doc:"对外访问地址,例如 https://example.com"`
}

SiteInput 是站点信息。

type StatusView

type StatusView struct {
	// Installed 为真时向导应当自行退出:要么跳后台,要么按提示手动配置。
	Installed bool    `json:"installed"`
	Version   string  `json:"version"`
	DataDir   string  `json:"dataDir"`
	Checks    []Check `json:"checks"`
}

StatusView 是安装状态。

type Step

type Step struct {
	Key        string `json:"key"`
	Label      string `json:"label"`
	DurationMS int64  `json:"durationMs"`
	Detail     string `json:"detail,omitempty"`
}

Step 是安装过程中的一个阶段及其耗时。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL