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

kernel

package
v0.18.3 Latest Latest
Warning

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

Go to latest
Published: Oct 28, 2026 License: AGPL-3.0 Imports: 37 Imported by: 0

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

View Source
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.

View Source
const (
	AddonModeEnabled  = "enabled"
	AddonModeDisabled = "disabled"
	AddonModeAuto     = "auto"
)

Addon modes accepted in config.Config.Addons.

View Source
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).

View Source
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.

View Source
const ServiceNavigationPins = "admin.navigation_pins"
View Source
const SettingBaseURL = "site.base_url"

SettingBaseURL is the admin-editable public base URL; it wins over Config.BaseURL (GORUN_BASE_URL).

Variables

View Source
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.

View Source
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.

View Source
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.

View Source
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

func AddonEnabled(ctx context.Context, d *sql.DB, name string) (bool, error)

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

func AddonEnabledTx(ctx context.Context, tx *sql.Tx, name string) (bool, error)

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

func AddonSettingKey(name string) string

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

func BaseURL(ctx context.Context, d *Deps) string

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

func BaseURLOf(ctx context.Context, st *settings.Store, configured string) string

BaseURLOf is BaseURL for code that holds the settings store and the configured base URL instead of *Deps.

func ExtendBootOrder

func ExtendBootOrder(after string, names ...string)

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 Get

func Get[T any](d *Deps, name string) (T, bool)

Get returns the service registered under name if it has type T.

func InspectSchema

func InspectSchema(ctx context.Context, d *sql.DB, mods []Module) (db.SchemaInspection, error)

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

func InstalledEpoch(ctx context.Context, d *sql.DB) (int, error)

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

func KnownMigrationNames(mods []Module) ([]string, error)

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

func MigrateAll(ctx context.Context, d *sql.DB, mods []Module) error

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.

func MigrateAllWithConfig added in v0.16.0

func MigrateAllWithConfig(ctx context.Context, cfg config.Config, d *sql.DB, mods []Module) error

MigrateAllWithConfig is MigrateAll honouring cfg.Addons, so `gorun migrate` and `gorun serve` select the same modules. Used by the CLI.

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 APIErrorResponse struct {
	Status            int
	Code, Description string
}

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 APIParam

type APIParam struct {
	Name, In, Type, Description string
	Required                    bool
}

APIParam documents a path/query/header parameter.

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 NewAPIRegistry

func NewAPIRegistry() *APIRegistry

NewAPIRegistry returns an empty registry.

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

type App struct {
	Deps    *Deps
	Handler http.Handler
	Close   func()
}

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

func Boot(ctx context.Context, cfg config.Config, mods []Module) (*App, error)

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 CanvasMetric struct {
	Label string `json:"label"`
	Value string `json:"value"`
}

type CanvasWidget

type CanvasWidget struct {
	ID, Href, Permission, NavID string
	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 CanvasWidgetRow struct {
	Title  string `json:"title"`
	Detail string `json:"detail"`
	Href   string `json:"href"`
}

type CatalogProvider

type CatalogProvider interface {
	Catalogs() fs.FS
}

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

func LockDataDir(dir string) (*DataLock, error)

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.

func (*DataLock) Path

func (l *DataLock) Path() string

Path is the lock file.

func (*DataLock) Release

func (l *DataLock) Release() error

Release clears the holder record and releases the lock. The file is kept: removing it could let two processes lock different inodes. Safe to call more than once and on a nil lock.

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 NewMCPRegistry

func NewMCPRegistry() *MCPRegistry

NewMCPRegistry returns an empty registry.

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

type Module interface {
	Name() string
	Migrations() fs.FS
	Register(d *Deps) error
}

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"`
	Nav               []string `json:"nav" doc:"Static admin navigation item IDs"`
	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 NavItem struct {
	ID, LabelKey, Icon, Href, Permission, Group string
	Position                                    int
}

NavItem is an admin navigation entry. LabelKey is an i18n key.

type NavigationPins interface {
	PinnedNavigation(context.Context) ([]string, error)
}

NavigationPins provides personal dashboard placements without coupling auth to the optional admin module's tables. Pins remain hidden after preference reset.

type OptionalModule

type OptionalModule interface {
	Enabled(context.Context, *sql.DB) (bool, error)
}

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

type Permission struct {
	Name  string
	Label map[string]string
}

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.

func (*PermissionRegistry) Declare

func (p *PermissionRegistry) Declare(perm string, label map[string]string)

Declare adds perm; declaring it again merges the labels.

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

func (r *Router) Authenticator() string

Authenticator returns the name passed to SetAuthenticator, or "".

func (*Router) SetAuthenticator

func (r *Router) SetAuthenticator(name string)

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.

type Widget

type Widget struct {
	ID     string
	Render func(ctx context.Context, r *http.Request) (template.HTML, error)
}

Widget is an admin dashboard widget.

Jump to

Keyboard shortcuts

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