Help us shape the future of Go! Take the 2026 Go Developer Survey

bootstrap

package
v0.32.41 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2026 License: Apache-2.0 Imports: 101 Imported by: 0

README

bootstrap/ - 服务初始化(库入口)

此目录存放服务启动时的初始化代码。

文件说明

  • bootstrap.go - 初始化入口,协调各组件的初始化顺序

初始化顺序

  1. 配置 - 加载配置文件
  2. 日志 - 初始化日志系统
  3. 数据库 - 建立数据库连接
  4. 缓存 - 建立 Redis 连接
  5. 服务器 - 创建 HTTP 服务器并注册路由

使用方式

// main.go
func main() {
    h, err := bootstrap.Bootstrap("/path/to/config.yaml")
    if err != nil {
        log.Fatal(err)
    }
    defer bootstrap.Cleanup()

    h.Spin()
}

添加新组件初始化

// bootstrap.go
func Bootstrap(configPath string) (*server.Hertz, error) {
    // ... 现有初始化

    // 添加新组件
    if err := initNewComponent(); err != nil {
        return nil, err
    }

    // ...
}

清理资源

func Cleanup() {
    logger.Sync()
    _ = db.Close()
    _ = redis.Close()
    // 添加新组件的清理
}

Documentation

Overview

API Key 临期站内通知(波次3):定期扫描 7 天内到期的有效 Key, 给属主发站内信(带 Webhook 外推),并打去重标记。 通知失败不阻塞扫描(下轮重试);标记成功才视为已通知,绝不重复打扰。

datamigrate.go — 启动期数据迁移(2026-10-26 兼容层清理)。

为什么要 Go 侧迁移而不是 migrations/*.sql: 版本化迁移只在 database.migrate=true 时执行;默认路径走 GORM AutoMigrate, 而 AutoMigrate **不会**做数据回填(也不会删列)。本轮退役的兼容层里有 「存量数据必须先补齐,代码才能删掉兜底」的部分(子表回填),必须两条路径 都生效 ⇒ 实现为幂等的 Go 侧迁移。

全部步骤都幂等:可重复执行,失败只记日志不阻断启动(数据类迁移失败不应 让站点起不来;但读取侧会因缺数据而报错,故日志必须响亮)。

setup_gate.go —— 首启向导(/setup)的暴露面加固(2026-10-22)。

问题

/setup 是**无鉴权**的建号端点,唯一的护栏是「库里还没有 admin」。 ⇒ 一个尚未初始化的实例,**任何能访问到它的人都可以抢先完成首启**并拿到 管理员(setup-race)。此前四个打包面把「没配密码 → 走 /setup」设为默认 (hub/ugreen/terramaster 的 compose entrypoint、qnap 的 prepare_env), 这条默认把暴露面从「弱口令 admin/admin123」换成了「先到先得的管理员」—— 在**内网**是体验升级,在**公网**是新的暴露面。

三道加固(都不改变「不配密码也能用」的默认体验)

  1. **可选令牌**:设 PB_SETUP_TOKEN 后,建号端点需带匹配的 X-Setup-Token 头 (或 ?token= 查询参数 —— 便于把「服务启动日志里的那条链接」直接粘进 浏览器完成首启)。未设 = 不要求(保持默认体验不变)。
  2. **按 IP 失败锁定**:复用 middleware.Lockout(Redis 共享计数,多副本 生效;无 Redis 退进程内)。**只把「令牌不匹配」计为失败**——校验 参数不合法(弱口令等)是用户自己的输入问题,不该把他锁在门外。
  3. **启动告警**:未初始化且未设令牌时,响亮提示暴露面与两种处置办法。

为什么不用「必须令牌」

强制令牌会把「打开浏览器就走完首启」这条最顺的路变成「先翻日志抄令牌」, 与「多部署方式下的第一天体验」这个目标相反。故做成**可选加固**: 内网自托管(NAS)默认零摩擦;公网部署按需一行 env 打开。

Index

Constants

This section is empty.

Variables

View Source
var ErrInsecureDefaultAdmin = errors.New("refusing to create default admin with known default password in production mode")

ErrInsecureDefaultAdmin 生产模式下拒绝以已知默认口令创建默认管理员时返回 (errors.Is 可判)。逃生门:PB_ADMIN_PASSWORD 注入强密码,或 PB_DISABLE_DEFAULT_ADMIN=true 走 /setup 首启向导。

Functions

func Bootstrap

func Bootstrap(configPath string) (*server.Hertz, error)

Bootstrap 应用程序启动入口(兼容入口,等价于无选项的 BootstrapWithOptions)。 configPath 为空时依次回退到 CONFIG_PATH 环境变量、默认 configs/config.yaml。

func BootstrapWithOptions

func BootstrapWithOptions(configPath string, opts ...Option) (*server.Hertz, error)

BootstrapWithOptions 带函数选项的启动入口。 可用选项见 options.go(如 WithStaticDir 覆盖前端静态资源目录)。

func CORS

func CORS() app.HandlerFunc

CORS 跨域中间件(配置化)。

安全策略:

  • 配置了 allow_origins 白名单时,仅放行白名单内的 Origin(生产推荐)
  • 未配置白名单时,退化为反射 Origin(便于本地开发,等同于宽松模式)
  • allow_credentials=true 时,绝不返回 "*",而是精确匹配的 Origin

同时允许 X-Trace-Id / X-API-Key 等自定义请求头跨域。 allow_origins 来源:yaml 的 security.cors.allow_origins(数组)或 环境变量 PB_CORS_ALLOW_ORIGINS(逗号分隔,如 "https://a.com,https://b.com")。

func Cleanup

func Cleanup()

Cleanup 清理资源(优雅停机编排,oekit core/lifecycle——Manager 原语自 logx 迁来,logx 依赖随之移除):teardown 先取消后台任务根 ctx(拦下一轮),随后 各资源按注册序、各自独立超时预算排水——panic 隔离,单个资源卡死不拖垮整体 停机;在飞轮次由 bg-tasks 预算内等其跑完。停机失败经 Errors() 落回 zap (Manager 内部日志走自身门面,宿主观测以 Errors 为准)。

func CreateDefaultAdmin

func CreateDefaultAdmin(database *gorm.DB, cfg *conf.AppConfiguration) error

CreateDefaultAdmin 创建默认管理员。

密码来源(优先级):PB_ADMIN_PASSWORD 环境变量 > 默认 admin123。 密码用 bcrypt 现场哈希(此前硬编码的哈希与 admin123 不匹配,导致管理员无法登录)。 生产模式(app.production 或 server.mode=release)下若未注入 PB_ADMIN_PASSWORD 且库中无管理员,返回 ErrInsecureDefaultAdmin 由调用方终止启动——已知弱口令 不允许上线(2026-10-08 审计:此前仅 Warn,与 JWT secret 的全环境 fail-fast 不对称)。 仅在"即将创建"时校验:库中已有 admin 的存量部署升级不受影响(每次启动都查会 打断未配 env 的老部署)。cfg 允许 nil(视为非生产,保持旧行为)。

func InitDatabase

func InitDatabase(config *conf.DatabaseConfig) (*gorm.DB, error)

InitDatabase 初始化数据库。

迁移策略(按配置):

  • database.migrate=true:先执行版本化迁移(migrations/*.sql),适合生产/需要版本控制
  • database.auto_migrate(默认 true):GORM AutoMigrate,开发友好、自动补表/列
  • 两者可共存:版本化迁移建表后,AutoMigrate 兜底补充新字段

func NewOfflineBackupService added in v0.32.38

func NewOfflineBackupService(cfg *conf.AppConfiguration, database *gorm.DB) (*backup.Service, error)

NewOfflineBackupService 构造**离线**备份服务(导出 / 恢复用;不启动 HTTP 服务)。

为什么是「离线」而不是管理面端点

导出与恢复天然是**同机**操作(要读本地 DB 与本地存储):

· 做成 HTTP 端点 ⇒ 多一层「服务端正在运行 ⇒ 恢复会砸自己的脚」的风险;
· `pb` 是**纯 HTTP 客户端**(刻意不链接 core)⇒ 让 `pb backup` 落地就得把
  core 拖进这个轻量 CLI(体积 + 数十 MB)。

⇒ 形态定为**同机离线子命令**(server 二进制:`pigeonbox backup export` / `pigeonbox restore`),与「恢复必须停服」的事实一致。

前置

调用方必须先完成:InitConfig → conf.SetGlobalConfig → InitDatabase (否则 DB 与存储后端都没就绪)。本函数只做**装配**,不负责初始化。

func ReapplyEnvOverrides added in v0.22.0

func ReapplyEnvOverrides()

ReapplyEnvOverrides 把非空 env 绑定值重新施加到全局 conf(幂等)。

由 config service 的 OnOverlayApplied 钩子在每次 DB overlay 后调用; 启动期因钩子注册晚于首次 RestoreAdminSettings,wiring 在注册后显式补调一次。

⚠️ **copy-on-write**(2026-10-31 更正)

此前本函数**原地改写**调用方传入的 conf 指针(bootstrap 包级 config)。 自全局配置改为 copy-on-write(原子换指针)后:

  • 传入的 bootstrap.config 是**被换掉的旧对象** ⇒ env 覆盖**根本没作用到 当前快照** ⇒ 「env 恒覆盖」这条配置优先级规则**静默失效**;
  • 更糟:旧对象被改后再被 COW 的浅拷贝带走 ⇒ env 覆盖值会**污染后续所有 快照**(看起来像生效了,实际是脏值扩散)。

⇒ 现在走 conf.UpdateGlobalConfig:在**当前快照的副本**上反射改字段, 再原子换指针 ⇒ env 覆盖既生效、又不会造成撕裂读。

func RunDataMigrations added in v0.32.14

func RunDataMigrations(gormDB *gorm.DB)

RunDataMigrations 启动期数据迁移入口(幂等)。

调用时机:InitDatabase 之后(AutoMigrate 已补齐表结构)、对外服务之前 (读取侧必须先于迁移完成——子表是文件清单的唯一真相源)。

Types

type CapabilitySnapshot added in v0.32.30

type CapabilitySnapshot struct {
	// DeploymentMode 部署模式:standalone | public | admin。
	// 多副本拓扑下前端据此知道「本副本是公开面/管理面」,不再各面各猜。
	DeploymentMode string `json:"deployment_mode"`
	// PublicURLSet 站点公网地址是否已配置(base.public_url 或 server.base_url)。
	// 未配置时分享链接按**请求来源**拼 ⇒ 内网部署会把内网 IP 发给外部用户。
	PublicURLSet bool `json:"public_url_set"`
	// SetupTokenRequired 首启建号是否需要令牌(自动生成的一次性令牌或 PB_SETUP_TOKEN)。
	// true = 未初始化实例的 /setup 会被门禁拦下(前端据此提示「令牌在启动日志里」)。
	SetupTokenRequired bool `json:"setup_token_required"`
	// Federation 联邦是否启用(**生效值**:多副本下自动降级关闭)。
	Federation bool `json:"federation"`
	// RedisMode redis | memory(redis.host 空 = 单机内存模式)。
	RedisMode string `json:"redis_mode"`
	// StorageBackend 当前存储后端(**生效值**,含远端构造失败后的降级结果)。
	StorageBackend string `json:"storage_backend"`
	// StorageDegraded 是否发生降级(配置的后端 ≠ 生效后端 ⇒ 远端构造失败回落 local)。
	// ⚠️ 这是**真实故障的信号**:用户以为在用 S3,实际全落在本地盘上。
	StorageDegraded bool `json:"storage_degraded"`
}

CapabilitySnapshot 描述「本实例实际支持什么」。

⚠️ 字段语义一律是**生效值**(runtime 真实状态),不是配置回显—— 后者会与实际漂移(如后端启动后才起 Redis、远端存储构造失败降级 local)。

type Config

type Config = conf.AppConfiguration

Config 与 internal/conf 包中的统一配置类型互为别名。

func GetConfig

func GetConfig() *Config

GetConfig 获取全局配置

func InitConfig

func InitConfig(configPath string) (*Config, error)

InitConfig 初始化配置。

配置来源优先级(高 → 低):

  1. 环境变量(PB_ 前缀完整名 / 文档化的短名,见 bindEnvironment)
  2. 配置文件(yaml,路径由 configPath 或 CONFIG_PATH 决定)
  3. 代码内默认值(SetDefault)

这样容器化部署(K8s/Docker)可通过 env 注入敏感配置(jwt_secret、db 密码等), 而无需修改镜像内的配置文件,符合 12-factor。

type DiagnosticsDatabase added in v0.32.30

type DiagnosticsDatabase struct {
	// Driver 驱动名(sqlite / mysql / postgres)。
	Driver string `json:"driver"`
	// Reachable 是否可连通(Ping 成功)。
	Reachable bool `json:"reachable"`
	// Error 连通失败原因(reachable=true 时为空)。
	Error string `json:"error,omitempty"`
}

DiagnosticsDatabase 数据库连通性。

type DiagnosticsRedis added in v0.32.30

type DiagnosticsRedis struct {
	// Mode redis | memory。
	Mode string `json:"mode"`
	// Address Redis 地址(mode=memory 时为空)。
	Address string `json:"address,omitempty"`
}

DiagnosticsRedis 缓存模式。

type DiagnosticsReport added in v0.32.30

type DiagnosticsReport struct {
	// Version **构建版本**(oekit/core/version.Version,由 ldflags 注入)。
	// ⚠️ 不是 config.App.Version —— 那是 yaml 里的 app.version(缺省 1.0.0,
	// 常年不更新)⇒ 真机上 doctor 会把 4.2.0 的实例报成「1.0.0」。
	// 版本披露用于 CVE 匹配,必须与 /version 端点**同源**。
	Version string `json:"version"`
	// BuildCommit 构建 commit(版本对齐用)。
	BuildCommit string `json:"build_commit,omitempty"`
	// BuildTime 构建时间。
	BuildTime string `json:"build_time,omitempty"`
	// DeploymentMode 部署模式:standalone | public | admin。
	DeploymentMode string `json:"deployment_mode"`
	// ServerTime 服务端当前时间(CLI 用它与本机时钟比对 ⇒ 时钟偏差)。
	ServerTime time.Time `json:"server_time"`
	// Database 数据库连通性。
	Database DiagnosticsDatabase `json:"database"`
	// Storage 存储后端状态。
	Storage DiagnosticsStorage `json:"storage"`
	// Redis 缓存模式。
	Redis DiagnosticsRedis `json:"redis"`
	// Site 站点对外地址状态。
	Site DiagnosticsSite `json:"site"`
	// Setup 首启状态。
	Setup DiagnosticsSetup `json:"setup"`
	// Capabilities 能力快照(生效值)。
	Capabilities CapabilitySnapshot `json:"capabilities"`
}

DiagnosticsReport 部署自检报告(一份快照)。

type DiagnosticsSetup added in v0.32.30

type DiagnosticsSetup struct {
	// Initialized 是否已初始化(已有 admin)。
	Initialized bool `json:"initialized"`
	// TokenRequired 建号是否需要令牌。
	TokenRequired bool `json:"token_required"`
	// TokenSource 令牌来源:env | auto | none。
	TokenSource string `json:"token_source"`
}

DiagnosticsSetup 首启状态。

type DiagnosticsSite added in v0.32.30

type DiagnosticsSite struct {
	// PublicURL 生效的站点公网地址(未配置时为空)。
	PublicURL string `json:"public_url,omitempty"`
	// PublicURLSet 是否已配置。
	PublicURLSet bool `json:"public_url_set"`
}

DiagnosticsSite 站点对外地址状态。

type DiagnosticsStorage added in v0.32.30

type DiagnosticsStorage struct {
	// Backend **生效**后端(远端构造失败会降级 local ⇒ 这里与 Configured 可能不同)。
	Backend string `json:"backend"`
	// Configured 配置里指定的后端。
	Configured string `json:"configured"`
	// Degraded 是否降级(Backend ≠ Configured ⇒ 远端构造失败回落 local)。
	Degraded bool `json:"degraded"`
	// DataPath 本地存储根目录(远端模式下仍承载分片合并副本)。
	DataPath string `json:"data_path,omitempty"`
	// InitError 降级原因(无降级时为空)。
	InitError string `json:"init_error,omitempty"`
}

DiagnosticsStorage 存储后端状态。

type Option

type Option func(*Options)

Option 是 Bootstrap 的可选配置项(函数选项模式)。

用法:

h, err := bootstrap.BootstrapWithOptions(configPath,
    bootstrap.WithStaticDir("/opt/pb/ui"),
)

兼容性:Bootstrap(configPath) 等价于无选项的 BootstrapWithOptions, 既有调用方(server、fnos)不受影响。

func WithStaticDir

func WithStaticDir(dir string) Option

WithStaticDir 覆盖前端静态资源目录(空串忽略,保持默认)。

type Options

type Options struct {
	// StaticDir 是前端构建产物的根目录。
	// 该目录需包含 index.html 与 assets/(Vite 构建产物布局)。
	// 默认 "./static"(相对进程工作目录)。
	// 典型消费者:fnos 单容器部署时把前端内嵌到自定义路径。
	StaticDir string
}

Options 控制 Bootstrap 的行为。零值字段取默认。

type PublicCapabilities added in v0.32.31

type PublicCapabilities struct {
	// DeploymentMode 部署模式:standalone | public | admin。
	DeploymentMode string `json:"deployment_mode"`
	// PublicURLSet 站点公网地址是否已配置。
	PublicURLSet bool `json:"public_url_set"`
	// SetupTokenRequired 首启建号是否需要令牌。
	SetupTokenRequired bool `json:"setup_token_required"`
}

PublicCapabilities 公开面(/api/config)下发的能力子集。

⚠️ **独立结构体**,不复用 CapabilitySnapshot:复用会把管理面字段 (redis_mode / storage_backend / …)以**空串**形态泄到公开 JSON 里 ⇒ 客户端读到的 `redis_mode: ""` 既不是事实、也不是"未知"的可辨识形态。

Jump to

Keyboard shortcuts

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