plugin

package
v0.1.9 Latest Latest
Warning

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

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

Documentation

Overview

Package plugin 提供插件系统:插件包加载、生命周期与设置声明。

v1 的插件是**纯声明式**的:包内只有清单、设置声明与展示图,没有可执行的代码。 这一期的目标是先把插件契约立起来——包的格式、安装与启停的语义、设置如何生效。 缺的从来不是加载器;契约定不下来,加载器做出来也无处可挂(§14.2)。

后端代码的执行(wazero + host function 白名单)排在后面几期, 届时 plugin.yaml 会多一个 runtime 字段,包内会多出 .wasm, 而本包的结构不必推倒重来。

Index

Constants

View Source
const (
	// FileManifest 是插件元信息,必需。
	FileManifest = "plugin.yaml"
	// FileSettings 是插件设置项声明,可缺省;格式与主题设置完全一致。
	FileSettings = "settings.yaml"
	FileLogo = "logo.png"
)

插件包内的固定文件名。

View Source
const (
	APIVersion = "plugin.lumo.run/v1alpha1"
	Kind       = "Plugin"
)

APIVersion 与 Kind 是 plugin.yaml 里的固定写法。

与 Extension 平面同一套形态:插件清单本身就是一份 GVK 资源, 将来要把它放进 extensions 表或经 API 暴露,都不必改格式。

View Source
const Name = "plugin"

Name 是模块名,也是迁移版本表的后缀(goose_db_version_plugin)。

View Source
const PathPlugins = "/plugins"

PathPlugins 是插件管理页在 Console 中的路径。

与接口的 /plugins 恰好同名,但两者是不同的东西:这一条是前端路由。 写成一个常量是为了让「它们恰好一样」这件事是显式的,而不是靠人记得两边一起改。

Variables

View Source
var ErrAlreadyExists = errors.New("插件已安装")

ErrAlreadyExists 表示同名插件已安装。

View Source
var ErrInvalidPackage = errors.New("插件包不合法")

ErrInvalidPackage 表示插件包不合法。

View Source
var ErrNotFound = errors.New("插件不存在")

ErrNotFound 表示插件不存在。

Functions

func AllowedExtensions

func AllowedExtensions() []string

AllowedExtensions 返回允许的扩展名,供接口文档引用。

func ListInstalled

func ListInstalled(root string) ([]string, error)

ListInstalled 列出 root 下已安装的插件目录名。

目录不存在时返回空列表而不是报错:首次启动、或用不到插件的部署里, 这个目录压根不会被创建。

func Remove

func Remove(root, name string) error

Remove 删除一个已安装的插件目录。

Types

type Author

type Author struct {
	Name    string `yaml:"name" json:"name"`
	Website string `yaml:"website" json:"website"`
}

Author 是作者信息。

type Handler

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

Handler 提供插件的 Console 接口。

全部操作都要求 plugins:manage:插件能往后台加页面、改设置,门槛与主题同级。

func NewHandler

func NewHandler(module *Module) *Handler

NewHandler 构造 Handler。

func (*Handler) Register

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

Register 挂载接口。

type Loaded

type Loaded struct {
	Manifest *Manifest
	// Dir 是插件目录的绝对路径。
	Dir string
	// FS 是插件包的根文件系统。
	FS fs.FS
	// Groups 是插件声明的设置分组。
	Groups []SettingsGroup
	// Enabled 是当前的启用状态。
	Enabled bool
}

Loaded 是一个已安装的插件。

func (*Loaded) ID

func (l *Loaded) ID() string

ID 返回插件标识。

func (*Loaded) SettingGroup

func (l *Loaded) SettingGroup(name string) (*SettingsGroup, bool)

SettingGroup 按名字取设置分组。

type Manifest

type Manifest struct {
	APIVersion string `yaml:"apiVersion" json:"apiVersion"`
	Kind       string `yaml:"kind" json:"kind"`
	Metadata   Meta   `yaml:"metadata" json:"metadata"`
	Spec       Spec   `yaml:"spec" json:"spec"`
}

Manifest 是 plugin.yaml 的内容。

func Install

func Install(root string, r io.ReaderAt, size int64, overwrite bool) (*Manifest, error)

Install 把一个 zip 插件包解压安装到 root 下,返回其元信息。

与主题同样的两段式:先解到临时目录、校验通过再改名就位。 半个插件留在 plugins/ 下比装不上更糟——它会被当成一个已安装的插件加载, 而缺少的清单或设置声明要到启用时才暴露。

func Validate

func Validate(dir string) (*Manifest, error)

Validate 校验一个**已安装**的插件目录,返回其元信息。

比 validateContents 多一条:目录名必须等于插件标识。 启停与卸载都按名字定位目录,两者不一致时那些操作会作用到别的东西上—— 而这类错只有在这里能拦下,因为它是文件系统与清单之间的关系,不是包自身的性质。

type Meta

type Meta struct {
	// Name 是插件标识(DNS-1123),同时是安装目录名,必须与目录名一致。
	Name string `yaml:"name" json:"name"`
}

Meta 是清单的元信息。

type Module

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

Module 是插件模块。

func From

func From(a *app.App) *Module

From 取回插件模块;未装配时返回 nil。

调用方须能在 nil 时退化——插件是可选能力,核心功能不该因为它而不可用。

func New

func New() *Module

New 构造模块。

func (*Module) Migrations

func (m *Module) Migrations() fs.FS

Migrations 实现 app.Migrator。

func (*Module) Name

func (m *Module) Name() string

Name 实现 app.Module。

func (*Module) Navigation

func (m *Module) Navigation() app.Navigation

Navigation 实现 app.NavigationProvider。

插件能改后台的页面与设置,门槛与主题同级,故整项按 plugins:manage 收起。

func (*Module) Permissions

func (m *Module) Permissions() []app.Permission

Permissions 实现 app.PermissionProvider。

func (*Module) Register

func (m *Module) Register(a *app.App) error

Register 实现 app.Module:只做装配,不访问数据库。

func (*Module) Registry

func (m *Module) Registry() *Registry

Registry 返回插件注册表,供其他模块与测试取用。

func (*Module) Routes

func (m *Module) Routes(r app.Router)

Routes 实现 app.RouteProvider。

func (*Module) Start

func (m *Module) Start(ctx context.Context) error

Start 实现 app.Starter:扫描已安装插件并与库里的状态对齐。

这是模块第一次被允许访问数据库的时机。

type Record

type Record struct {
	bun.BaseModel `bun:"table:plugins,alias:p"`

	Name        string    `bun:"name,pk"`
	Version     string    `bun:"version,notnull"`
	DisplayName string    `bun:"display_name,notnull"`
	Description string    `bun:"description,notnull"`
	Author      string    `bun:"author,notnull"`
	Enabled     bool      `bun:"enabled,notnull"`
	Manifest    []byte    `bun:"manifest,type:jsonb,notnull"`
	InstalledAt time.Time `bun:"installed_at,nullzero,notnull,default:now()"`
	UpdatedAt   time.Time `bun:"updated_at,nullzero,notnull,default:now()"`
}

Record 是 plugins 表的一行。

type Registry

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

Registry 持有全部已安装插件与它们的启用状态。

它是插件系统的唯一运行时状态:设置分组、菜单项与列表接口都经它取数据。 读远多于写,故用 RWMutex。

func NewRegistry

func NewRegistry(opts *RegistryOptions) *Registry

NewRegistry 构造 Registry。此时不读盘也不读库——那是 Load 的事。

func (*Registry) Broken

func (r *Registry) Broken() map[string]string

Broken 返回加载失败或被手工删除的插件及原因。

func (*Registry) Enabled

func (r *Registry) Enabled() []*Loaded

Enabled 返回全部已启用的插件,按标识排序。

每次调用都现算:设置分组与菜单项都靠它,而启用状态随时会变。

func (*Registry) Get

func (r *Registry) Get(name string) (*Loaded, bool)

Get 按名字取插件。

func (*Registry) Install

func (r *Registry) Install(ctx context.Context, reader io.ReaderAt, size int64) (*Manifest, error)

Install 从 zip 包安装或升级一个插件,登记为未启用。

已存在的插件走覆盖安装(升级):升级后仍然是「已安装」状态, 但**保持原来的启用状态**——升级一个正在用的插件不该把它停掉。

func (*Registry) List

func (r *Registry) List() []*Loaded

List 返回全部已安装插件,按标识排序。

func (*Registry) Load

func (r *Registry) Load(ctx context.Context) error

Load 扫描已安装的插件目录并与库里的状态对齐。

这是模块第一次被允许访问数据库的时机。

两边不一致时的处置:

  • 目录在、库里没有 —— 手工拷进来的插件。照收,登记为「已安装但未启用」, 与主题的做法一致;不这样,用户把目录拷进去后会找不到它。
  • 库里有、目录不在 —— 文件被手工删了。标记为损坏并在列表里标出来, 而不是静默丢掉那一行:用户需要知道发生了什么。

func (*Registry) Root

func (r *Registry) Root() string

Root 返回插件安装目录。

func (*Registry) SetEnabled

func (r *Registry) SetEnabled(ctx context.Context, name string, enabled bool) error

SetEnabled 启用或停用一个插件。

func (*Registry) Uninstall

func (r *Registry) Uninstall(ctx context.Context, name string) error

Uninstall 卸载一个插件:删目录、删状态、级联删设置值。

顺序是刻意的:**先删库再删文件**。反过来的话,删文件成功而删库失败时, 库里会留下一条指向不存在目录的记录,而列表里会一直显示一个打不开的插件。 先删库则最坏情况只是留下一个未登记目录,重启时会被当成手工放入的插件重新登记。

type RegistryOptions

type RegistryOptions struct {
	// Root 是插件安装目录,通常是 data/plugins。
	Root   string
	Store  *Store
	Logger *slog.Logger
}

RegistryOptions 是构造 Registry 的参数。

type SettingsGroup

type SettingsGroup struct {
	Name        string
	Label       string
	Description string
	Order       int
	Icon        string
	Form        *form.Form
	// contains filtered or unexported fields
}

SettingsGroup 是一个插件的设置分组。

形态与 app.SettingGroup 同构,但**不注册进全局设置服务**: 插件的设置随插件走,停用时要能连同它一起收起,而全局设置分组的生命周期 钉在进程启动那一刻。主题设置也是同样的处置(见 internal/theme/settings.go)。

func (*SettingsGroup) Validator

func (g *SettingsGroup) Validator() *settings.Validator

Validator 返回分组的校验器。

type Spec

type Spec struct {
	// DisplayName 是显示名。
	DisplayName string `yaml:"displayName" json:"displayName"`
	// Version 是插件版本。
	Version string `yaml:"version" json:"version"`
	// Description 是一句话说明。
	Description string `yaml:"description" json:"description"`
	// Author 是作者信息。
	Author Author `yaml:"author" json:"author"`
	// Homepage 是插件主页。
	Homepage string `yaml:"homepage" json:"homepage"`
	// Repo 是源码仓库地址。
	Repo string `yaml:"repo" json:"repo"`
	// License 是许可证标识。
	License string `yaml:"license" json:"license"`
	// Requires 声明所需的 Lumo 版本范围;留空表示不限制。
	//
	// v1 只做语法校验,不做范围求解与拒绝安装——「不满足就装不上」需要一套
	// 完整的版本比较,而那要等插件间依赖一起做(§14.2 期 3)。届时这里会被真正用上。
	Requires string `yaml:"requires" json:"requires"`
}

Spec 是清单的规格。

type Store

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

Store 持久化插件状态与设置值。

func NewStore

func NewStore(db *bun.DB) *Store

NewStore 构造 Store。

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, name string) error

Delete 删除一个插件的状态;它的设置值由外键级联一并清除。

func (*Store) Get

func (s *Store) Get(ctx context.Context, name string) (*Record, error)

Get 读取一个插件;不存在时返回 ErrNotFound。

func (*Store) List

func (s *Store) List(ctx context.Context) ([]Record, error)

List 返回全部已安装插件,按名称排序。

func (*Store) LoadSettings

func (s *Store) LoadSettings(ctx context.Context, name string) (map[string]map[string]any, error)

LoadSettings 读取某个插件全部分组的已保存值。

func (*Store) Put

func (s *Store) Put(ctx context.Context, manifest *Manifest, raw []byte) error

Put 写入或整体覆盖一个插件的元信息。

安装与升级共用这一条:两者的差别只在 overwrite,而那是文件系统层面的事。

func (*Store) SaveSettings

func (s *Store) SaveSettings(ctx context.Context, name, group string, values map[string]any) error

SaveSettings 覆盖式写入某个插件某个分组的值。

func (*Store) SetEnabled

func (s *Store) SetEnabled(ctx context.Context, name string, enabled bool) error

SetEnabled 启用或停用一个插件。

Jump to

Keyboard shortcuts

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