Documentation
¶
Overview ¶
Package kernel defines the Module contract and boots GoRun: it opens the database, runs each module's migrations, lets modules register into shared registries, and assembles the HTTP handler.
Index ¶
- Constants
- Variables
- func AddonEnabled(ctx context.Context, d *sql.DB, name string) (bool, error)
- func AddonEnabledTx(ctx context.Context, tx *sql.Tx, name string) (bool, error)
- func AddonSettingKey(name string) string
- func BaseURL(ctx context.Context, d *Deps) string
- func BaseURLOf(ctx context.Context, st *settings.Store, configured string) string
- func ExtendBootOrder(after string, names ...string)
- func Get[T any](d *Deps, name string) (T, bool)
- func InspectSchema(ctx context.Context, d *sql.DB, mods []Module) (db.SchemaInspection, error)
- func InstalledEpoch(ctx context.Context, d *sql.DB) (int, error)
- func KnownMigrationNames(mods []Module) ([]string, error)
- func MigrateAll(ctx context.Context, d *sql.DB, mods []Module) error
- func MigrateAllWithConfig(ctx context.Context, cfg config.Config, d *sql.DB, mods []Module) error
- type APIContract
- type APIErrorResponse
- type APIOperation
- type APIParam
- type APIRegistry
- type AdminRegistry
- func (a *AdminRegistry) AddCanvasWidget(widget CanvasWidget)
- func (a *AdminRegistry) AddNav(item NavItem)
- func (a *AdminRegistry) AddNavProvider(fn func(ctx context.Context) []NavItem)
- func (a *AdminRegistry) AddWidget(id string, ...)
- func (a *AdminRegistry) CanvasWidgets() []CanvasWidget
- func (a *AdminRegistry) Nav(ctx context.Context) []NavItem
- func (a *AdminRegistry) NavIDs() []string
- func (a *AdminRegistry) SetNavFilter(fn func(ctx context.Context, items []NavItem) []NavItem)
- func (a *AdminRegistry) SetPermissionCheck(can func(ctx context.Context, perm string) bool)
- func (a *AdminRegistry) Widgets() []Widget
- type App
- type BootOptions
- type CanvasMetric
- type CanvasWidget
- type CanvasWidgetData
- type CanvasWidgetRow
- type CatalogProvider
- type DataLock
- type Deps
- type KindRequest
- type MCPRegistry
- type MCPTool
- type Module
- type ModuleRegistration
- type ModuleState
- type NavItem
- type NavigationPins
- type OptionalModule
- type Permission
- type PermissionRegistry
- type Router
- type SchemaInspector
- type ServiceRegistry
- type ThemeRegistry
- type Widget
Constants ¶
const ( InstallSettingKey = "kernel.install_epoch" InstallEpoch = 1 )
Install provenance. A database that this binary creates from nothing (no schema_migrations ledger, or an empty one, before the first migration runs) is stamped once with InstallSettingKey = InstallEpoch. A database that already had a ledger — every upgraded site — is never stamped, so modules can tell a fresh install from an upgrade without inferring it from activity (orders, users, secrets). The epoch is bumped when the meaning of "fresh" changes for some module default.
const ( AddonModeEnabled = "enabled" AddonModeDisabled = "disabled" AddonModeAuto = "auto" )
Addon modes accepted in config.Config.Addons.
const ( ServiceAuth = "auth" // auth.Service ServiceAPIKeys = "auth.keys" // auth.KeyService ServiceRoles = "auth.roles" // auth.RoleService ServiceAdminTemplates = "admin.templates" // *httpx.Templates (admin renderer) ServiceContent = "content" // content.Service ServiceTaxonomy = "taxonomy" // taxonomy.Service ServiceMedia = "media" // media.Service ServiceMenus = "menus" // menus.Service ServiceRedirects = "redirects" // redirects.Service ServiceSEO = "seo" // seo.Service ServiceTheme = "theme" // theme.Service ServicePattern = "pattern" // pattern.Service (layout packages) ServiceCommerce = "commerce" // commerce service (wave 2) ServiceWebhooks = "webhooks" // webhooks.Service ServiceSchema = "kernel.schema" // kernel.SchemaInspector (set by the kernel after every module migrated) )
Names under which modules publish their services in Deps.Services (Services.Set in Register; Get[T](d, name) to consume).
const DataLockFile = "gorun.lock"
DataLockFile is the lock file inside the data directory. The process that runs job workers (gorun serve, gorun mcp --jobs, reindex without --no-jobs) holds an exclusive OS lock on it for its whole lifetime.
const SettingBaseURL = "site.base_url"
SettingBaseURL is the admin-editable public base URL; it wins over Config.BaseURL (GORUN_BASE_URL).
Variables ¶
var BootOrder = []string{"db", "settings", "i18n", "auth", "audit", "jobs", "content", "taxonomy", "media", "menus", "redirects", "seo", "theme", "pattern", "builder", "site", "api", "admin", "webhooks", "commerce", "saas", "membership", "launch", "workagents", "identityai", "identitylogo", "wpmigrate", "mcp", "ext"}
BootOrder is the canonical module order. A module may only import modules earlier in this list (enforced by importorder_test.go); Boot rejects module lists that violate it. Packages not listed (kernel, config, hooks, httpx…) and the kernel infrastructure (db, settings, i18n, audit, jobs) are importable by every module but never import domain modules. A downstream build adds its own modules with ExtendBootOrder.
var ErrDataDirLocked = errors.New("data directory is in use by another GoRun process")
ErrDataDirLocked is returned (wrapped) when another process holds the data-directory lock.
var ErrInstallEpochMalformed = errors.New("kernel: install epoch is not a non-negative integer")
ErrInstallEpochMalformed reports a kernel.install_epoch value that is not a non-negative integer. InstalledEpoch returns it together with 0 so a caller can log and carry on as for an existing database; it is never fatal at boot.
var LogOutput io.Writer = os.Stderr
LogOutput is where Boot's logger writes (tests set io.Discard).
Functions ¶
func AddonEnabled ¶ added in v0.16.0
AddonEnabled is the shared OptionalModule.Enabled implementation for built-in optional modules: it reads AddonSettingKey(name) from the settings table. It runs before any module migration, so on a fresh database (no settings table yet) it reports enabled. A value that is not a JSON boolean is an activation error, which stops the boot with a message instead of guessing.
func AddonEnabledTx ¶ added in v0.17.0
AddonEnabledTx reads activation from the caller's transaction, without a second pool connection. It uses exactly the same defaults and boolean parser.
func AddonSettingKey ¶ added in v0.16.0
AddonSettingKey is the settings key that records whether the optional module name is on: a JSON boolean under "addons.<name>.enabled". A missing key means enabled, so existing installs keep every module they had.
func BaseURL ¶
BaseURL is the one source of the site's public base URL (no trailing slash): the site.base_url setting when set, else Config.BaseURL. Use it for absolute links and the redirects own-host check. Never for cookie Secure flags or HSTS: those follow the request (httpx.SecureCookie, httpx.RequestIsHTTPS), so a wrong setting cannot lock admins out.
func BaseURLOf ¶
BaseURLOf is BaseURL for code that holds the settings store and the configured base URL instead of *Deps.
func ExtendBootOrder ¶
ExtendBootOrder inserts names into BootOrder right after the module named after (at the end when after is empty or unknown), keeping their order. Names already in BootOrder are skipped, so calling it again is harmless. Call it before Boot, typically from a binary's main.
func InspectSchema ¶
InspectSchema reports applied migrations unknown to this binary (the schema is ahead of it) and applied files whose content changed. mods is the module list as given to Boot or MigrateAll. Rows of a BootOrder module missing from mods are not reported: a partial list (CLI commands that boot only settings) cannot judge them.
func InstalledEpoch ¶ added in v0.16.0
InstalledEpoch returns the install epoch stamped when this database was created, or 0 for a database created before the stamp existed (an upgraded site) — the caller must treat 0 as "existing". A malformed value returns 0 and ErrInstallEpochMalformed.
func KnownMigrationNames ¶ added in v0.18.0
KnownMigrationNames lists every migration this binary ships for mods plus the kernel infrastructure, as sorted "module/NNNN_name.sql" keys: the form schema_migrations records and a release index publishes, so an update preflight can refuse a target that does not know the live data.
func MigrateAll ¶
MigrateAll validates the module list like Boot, then applies kernel infrastructure migrations (audit, jobs, diagnostics, observe, update) and every module's migrations, in order. Optional modules are gated like Boot, with no operator config.
Types ¶
type APIContract ¶
type APIContract struct {
SuccessStatuses []int
RequestContentType string
MaxBodyBytes int64
Limits map[string]any
Errors []APIErrorResponse
}
APIContract describes transport details that cannot be inferred from Go types.
type APIErrorResponse ¶
type APIOperation ¶
type APIOperation struct {
Method, Path, Summary string
Tags []string
Scopes []string
Request, Response any
Params []APIParam
Handler http.HandlerFunc
Contract *APIContract
}
APIOperation is one REST operation; Path is relative to the API mount ("/v1/content"). Request/Response are example values for OpenAPI schemas.
type APIRegistry ¶
type APIRegistry struct {
// contains filtered or unexported fields
}
APIRegistry collects REST operations (consumed by the api module for routing and OpenAPI generation).
func (*APIRegistry) Add ¶
func (r *APIRegistry) Add(op APIOperation)
Add registers op; Method+Path must be unique.
func (*APIRegistry) Operations ¶
func (r *APIRegistry) Operations() []APIOperation
Operations returns the operations in registration order.
type AdminRegistry ¶
type AdminRegistry struct {
// contains filtered or unexported fields
}
AdminRegistry collects admin navigation and dashboard widgets.
func NewAdminRegistry ¶
func NewAdminRegistry() *AdminRegistry
NewAdminRegistry returns an empty registry.
func (*AdminRegistry) AddCanvasWidget ¶
func (a *AdminRegistry) AddCanvasWidget(widget CanvasWidget)
func (*AdminRegistry) AddNav ¶
func (a *AdminRegistry) AddNav(item NavItem)
AddNav adds a navigation item; IDs must be unique.
func (*AdminRegistry) AddNavProvider ¶
func (a *AdminRegistry) AddNavProvider(fn func(ctx context.Context) []NavItem)
AddNavProvider adds a function evaluated on every Nav call (each admin request) for entries that change at runtime, e.g. one per content type. Its items go through the same permission filter and sort; an item whose ID is already registered with AddNav is skipped.
func (*AdminRegistry) AddWidget ¶
func (a *AdminRegistry) AddWidget(id string, render func(ctx context.Context, r *http.Request) (template.HTML, error))
AddWidget adds a dashboard widget; IDs must be unique.
func (*AdminRegistry) CanvasWidgets ¶
func (a *AdminRegistry) CanvasWidgets() []CanvasWidget
func (*AdminRegistry) Nav ¶
func (a *AdminRegistry) Nav(ctx context.Context) []NavItem
Nav returns the items visible in ctx (static and provided), sorted by Position then ID.
func (*AdminRegistry) NavIDs ¶ added in v0.16.0
func (a *AdminRegistry) NavIDs() []string
NavIDs returns the IDs of the statically registered navigation items (providers are evaluated per request and not listed).
func (*AdminRegistry) SetNavFilter ¶ added in v0.4.0
func (a *AdminRegistry) SetNavFilter(fn func(ctx context.Context, items []NavItem) []NavItem)
SetNavFilter installs a post-permission filter (e.g. workspace mode). The filter runs after permission checks and before sorting is finalized; it may drop items but should not invent IDs already filtered out.
func (*AdminRegistry) SetPermissionCheck ¶
func (a *AdminRegistry) SetPermissionCheck(can func(ctx context.Context, perm string) bool)
SetPermissionCheck installs the function Nav uses to hide items whose Permission the current principal lacks (set by the auth module).
func (*AdminRegistry) Widgets ¶
func (a *AdminRegistry) Widgets() []Widget
Widgets returns the dashboard widgets in registration order.
type App ¶
App is a booted GoRun instance. Close stops the job workers, waits for them, then closes the database; it is safe to call more than once.
func Boot ¶
Boot opens the database and wires GoRun in this order: migrate infrastructure → settings module migrate+register → load i18n (core catalog plus every CatalogProvider module) → remaining modules migrate+register in the given order → mount routers → start job workers. mods must contain the settings module and follow BootOrder.
func BootWithOptions ¶
func BootWithOptions(ctx context.Context, cfg config.Config, mods []Module, opts BootOptions) (*App, error)
BootWithOptions is Boot with options (see BootOptions).
type BootOptions ¶
type BootOptions struct {
// StartJobs starts the job workers and cron scheduler (Boot does). CLI
// commands that may run beside a live server (reindex --no-jobs) leave
// it false: workers would requeue the server's running jobs as stale
// and duplicate its cron ticks. Jobs enqueued meanwhile wait for the
// server's workers.
//
// A process that starts job workers first takes the exclusive
// data-directory lock (<data>/gorun.lock, see LockDataDir) and refuses
// to boot when another process holds it; App.Close releases it.
StartJobs bool
}
BootOptions tunes BootWithOptions.
type CanvasMetric ¶
type CanvasWidget ¶
type CanvasWidget struct {
Load func(*http.Request) (CanvasWidgetData, error)
}
CanvasWidget is an optional module-owned read-only projection. The admin validates board ownership, permission and visible navigation before Load.
type CanvasWidgetData ¶
type CanvasWidgetData struct {
ID string `json:"id"`
Title string `json:"title"`
Href string `json:"href"`
Metrics []CanvasMetric `json:"metrics"`
Rows []CanvasWidgetRow `json:"rows"`
UpdatedAt string `json:"updated_at"`
}
type CanvasWidgetRow ¶
type CatalogProvider ¶
CatalogProvider is optionally implemented by modules that ship translations. The FS holds "catalog/<module>.<locale>.json" files; they are loaded before any non-settings module registers.
type DataLock ¶
type DataLock struct {
// contains filtered or unexported fields
}
DataLock is a held data-directory lock. The OS releases it when the process exits, even after a crash, so a leftover gorun.lock file never blocks a restart.
func LockDataDir ¶
LockDataDir takes the exclusive, non-blocking lock on <dir>/gorun.lock, creating dir and the file when needed, and records this process (pid, host, start time) in the file for the error message another process sees. It fails with ErrDataDirLocked when the lock is already held.
type Deps ¶
type Deps struct {
DB *sql.DB
Hooks *hooks.Registry
Jobs *jobs.Queue
Settings *settings.Store
I18n *i18n.Bundle // nil only while the settings module registers
Log *slog.Logger
Config config.Config
Router *Router
Admin *AdminRegistry
Theme *ThemeRegistry
API *APIRegistry
MCP *MCPRegistry
Perms *PermissionRegistry
Audit *audit.Writer
Services *ServiceRegistry // Set(name, v) / Get[T](d, name): reach earlier modules' services without import cycles
}
Deps is the set of shared services handed to every module's Register.
type KindRequest ¶
type KindRequest struct{ Kind, Type, Slug, Template, Purpose, Taxonomy, Term string }
KindRequest describes what the theme should render; candidate functions map it to an ordered list of template names (most specific first). KindRequest asks the theme for a template. Purpose is a page's validated purpose (theme.PagePurposes), tried after its template.
type MCPRegistry ¶
type MCPRegistry struct {
// contains filtered or unexported fields
}
MCPRegistry collects MCP tools.
func (*MCPRegistry) AddTool ¶
func (r *MCPRegistry) AddTool(t MCPTool)
AddTool registers t; names must be unique.
func (*MCPRegistry) Tools ¶
func (r *MCPRegistry) Tools() []MCPTool
Tools returns the tools in registration order.
type MCPTool ¶
type MCPTool struct {
Name, Description string
Input any
Scopes []string
Sensitive bool
Handler func(ctx context.Context, args json.RawMessage) (any, error)
}
MCPTool is one MCP tool. Sensitive tools require human approval.
type Module ¶
Module is a pluggable domain. Migrations returns an FS whose files live at "migrations/NNNN_name.sql" (nil when the module has no tables). Register wires the module into Deps; it runs once, in BootOrder.
type ModuleRegistration ¶ added in v0.16.0
type ModuleRegistration struct {
Permissions []string `json:"permissions" doc:"Declared by this module during Register (a name may also be declared by the auth base catalog)"`
APIOperations []string `json:"api_operations" doc:"METHOD /v1/path"`
MCPTools []string `json:"mcp_tools"`
JobKinds []string `json:"job_kinds"`
Crons []string `json:"crons"`
Widgets []string `json:"widgets"`
ThemeFuncs []string `json:"theme_funcs"`
ThemeKinds []string `json:"theme_kinds"`
ServicesPublished []string `json:"services_published"`
ServicesConsumed []string `json:"services_consumed" doc:"Service names looked up during Register"`
HookSubscriptions []string `json:"hook_subscriptions"`
Catalog bool `json:"catalog" doc:"Ships ar/en translations"`
Migrations int `json:"migrations" doc:"Migration files this build knows for the module"`
}
ModuleRegistration is what one module registered while its Register ran, observed by the kernel as the difference of every registry before and after the call. It is evidence of this boot, not a declaration: a module that registers lazily at request time is not seen here.
type ModuleState ¶ added in v0.14.0
type ModuleState struct {
Name string `json:"name"`
Available bool `json:"available"`
Enabled bool `json:"enabled"`
Optional bool `json:"optional"`
Mode string `json:"mode" doc:"Requested addon mode, or empty when not configured"`
// Registered is what the module registered during this boot (nil when it
// did not boot). LastKnownJobKinds are the job kinds it registered at its
// last enabled boot, the basis for parking its queued work while it is off.
Registered *ModuleRegistration `json:"registered,omitempty"`
LastKnownJobKinds []string `json:"last_known_job_kinds,omitempty"`
}
ModuleState records the actual selection made for this process. Availability means the module was supplied to Boot; enabled means it passed the existing OptionalModule gate. Configuration alone never proves activation.
func Modules ¶ added in v0.14.0
func Modules(d *Deps) []ModuleState
Modules returns a copy of the immutable boot inventory, including configured addons absent from this binary. It never re-evaluates activation at request time.
type NavigationPins ¶
type NavigationPins interface {
}
NavigationPins provides personal dashboard placements without coupling auth to the optional admin module's tables. Pins remain hidden after preference reset.
type OptionalModule ¶
OptionalModule is evaluated before any module migrations, catalog loading or route registration. Disabled modules retain their existing data untouched. Enabled must only read the database; an empty database is valid.
The operator's config (config.Config.Addons, from the site config or GORUN_ADDONS) can only restrict: mode "disabled" turns an optional module off without calling Enabled; "enabled", "auto" or no entry leave the decision to the module, so configuration alone never proves activation. Modules that do not implement this interface are always booted; a mode configured for them is reported in the module inventory, warned about and ignored.
type Permission ¶
Permission is a declared permission with labels by locale.
type PermissionRegistry ¶
type PermissionRegistry struct {
// contains filtered or unexported fields
}
PermissionRegistry collects permissions declared by modules. A name may be declared by more than one module (the auth base catalog lists the names default roles grant, and the owning module declares it again), so the registry also records which module declared what for the inventory.
func NewPermissionRegistry ¶
func NewPermissionRegistry() *PermissionRegistry
NewPermissionRegistry returns an empty registry.
func (*PermissionRegistry) All ¶
func (p *PermissionRegistry) All() []Permission
All returns every permission sorted by name.
type Router ¶
type Router struct {
Public, Admin, API, Webhooks chi.Router
// contains filtered or unexported fields
}
Router holds the chi sub-routers modules register on during Register. Use may be called on any of them at any point during Register, even after earlier modules added routes: middlewares are collected and wrap every route of that router, in Use order, from the first request on (Use after serving starts panics). Boot mounts the routers on the root mux after every module has registered.
Mount prefixes: Public "/", Admin "/admin", API "/api" (modules add "/v1/..."; the spec's "/api/docs" sits outside v1), Webhooks "/webhooks". Session/CSRF/permission middleware on Admin and key-or-session on API are installed by the auth module via Admin.Use / API.Use; Webhooks stays raw. Admin and API deny every request (503) until SetAuthenticator is called. The root mux also serves GET /healthz (liveness), GET /readyz (DB ping), and optionally GET /metrics (Prometheus text when GORUN_METRICS is on), outside every sub-router.
func (*Router) Authenticator ¶
Authenticator returns the name passed to SetAuthenticator, or "".
func (*Router) SetAuthenticator ¶
SetAuthenticator records that a module (the auth module, in its Register) has installed authentication middleware on Admin and API. Until it is called, Boot's guard answers every Admin and API request with 503 "authentication module not installed" (deny by default).
type SchemaInspector ¶
type SchemaInspector func(context.Context) (db.SchemaInspection, error)
SchemaInspector compares the live schema_migrations table with the migrations this binary ships. Boot publishes one under ServiceSchema; System Health calls it per request, so it sees a newer binary migrating the database while this one runs.
type ServiceRegistry ¶
type ServiceRegistry struct {
// contains filtered or unexported fields
}
ServiceRegistry lets later modules reach earlier modules' services by name (e.g. "content" → content.Service) without import cycles.
func NewServiceRegistry ¶
func NewServiceRegistry() *ServiceRegistry
NewServiceRegistry returns an empty registry.
func (*ServiceRegistry) Names ¶ added in v0.16.0
func (s *ServiceRegistry) Names() []string
Names returns the registered service names, sorted.
func (*ServiceRegistry) Set ¶
func (s *ServiceRegistry) Set(name string, v any)
Set registers v under name, replacing any previous value.
type ThemeRegistry ¶
type ThemeRegistry struct {
// contains filtered or unexported fields
}
ThemeRegistry collects template funcs and template-hierarchy kinds.
func NewThemeRegistry ¶
func NewThemeRegistry() *ThemeRegistry
NewThemeRegistry returns an empty registry.
func (*ThemeRegistry) AddFunc ¶
func (t *ThemeRegistry) AddFunc(name string, fn any)
AddFunc adds a template func; fn must be a function and name unique.
func (*ThemeRegistry) AddKind ¶
func (t *ThemeRegistry) AddKind(kind string, candidates func(req KindRequest) []string)
AddKind registers the candidate-template function for kind.
func (*ThemeRegistry) Candidates ¶
func (t *ThemeRegistry) Candidates(req KindRequest) []string
Candidates returns the template names for req.Kind, or nil if the kind is not registered.
func (*ThemeRegistry) Funcs ¶
func (t *ThemeRegistry) Funcs() template.FuncMap
Funcs returns a copy of the registered template funcs.
func (*ThemeRegistry) Kinds ¶ added in v0.16.0
func (t *ThemeRegistry) Kinds() []string
Kinds returns the registered template-hierarchy kinds, sorted.