Documentation
¶
Overview ¶
Package packages implements the .hmp package format (a zip holding a plugin or a theme), its zip-safety rules, ed25519 signing, the signed registry index and the Postgres store plus manager that installs, activates and removes packages while the server runs. See docs/PACKAGES.md.
Index ¶
- Constants
- Variables
- func BuildIndex(dir, urlPrefix string, key Key, now time.Time, validity time.Duration) ([]byte, error)
- func BuildIndexWith(dir, urlPrefix string, key Key, now time.Time, validity time.Duration, ...) ([]byte, error)
- func CheckFresh(what, issuedAt, expiresAt string, now time.Time, maxLifetime time.Duration) error
- func CompareVersions(a, b string) int
- func FetchURL(ctx context.Context, c *http.Client, u string, max int64) ([]byte, error)
- func IsFirstPartyKey(id string) bool
- func KeyID(pub ed25519.PublicKey) string
- func Newer(a, b string) bool
- func PackDir(dir string, key *Key) ([]byte, error)
- func PackFiles(in map[string][]byte, key *Key) ([]byte, error)
- func ParseMeta(raw []byte) (map[string]Meta, error)
- func ParsePublicKey(s string) (ed25519.PublicKey, error)
- func ReadArchive(data []byte, wantSHA256 string, lim Limits) (map[string][]byte, error)
- func RenderPage(idx *Index, opts PageOptions) ([]byte, error)
- func SafeHTTPS(s string) string
- func ScreenshotExt(rel string) string
- func ServeFile(w http.ResponseWriter, r *http.Request, p *Package, ...)
- func SignIndex(index []byte, k Key) ([]byte, error)
- func ValidSemver(v string) bool
- func ValidateScreenshot(rel string, files map[string][]byte) error
- func WithActor(ctx context.Context, a Actor) context.Context
- type Actor
- type BuildOptions
- type Config
- type ConfirmationRequired
- type DataDropper
- type Entry
- type Index
- type InstallRequest
- type Installed
- type Key
- type Kind
- type Limits
- type Manager
- func (m *Manager) Activate(ctx context.Context, name string, force bool) error
- func (m *Manager) ActiveTheme() string
- func (m *Manager) Audit(ctx context.Context, action, target string, detail map[string]any)
- func (m *Manager) Blocked(kind Kind, name string) string
- func (m *Manager) CheckGrant(plugin string, perms []sdk.Permission) error
- func (m *Manager) CheckPolicy(ctx context.Context, p *Package, src Source) error
- func (m *Manager) CommunityPlugin(name string) bool
- func (m *Manager) CommunityScriptsLive() bool
- func (m *Manager) FetchFromRegistry(ctx context.Context, kind Kind, name string) ([]byte, error)
- func (m *Manager) Install(ctx context.Context, raw []byte, req InstallRequest) (*Installed, error)
- func (m *Manager) InstallFromRegistry(ctx context.Context, kind Kind, name string, req InstallRequest) (*Installed, error)
- func (m *Manager) LoadDevDir(ctx context.Context, dir string)
- func (m *Manager) MarkupStopped(name string) []string
- func (m *Manager) MissingSections(ctx context.Context, theme string) ([]MissingSection, error)
- func (m *Manager) NeedsConfirmation(ctx context.Context, p *Package, src Source) (*ConfirmationRequired, error)
- func (m *Manager) PluginPackage(name string) *Package
- func (m *Manager) PreviewEngine(ctx context.Context, name string) (*render.Engine, error)
- func (m *Manager) Reconcile(ctx context.Context)
- func (m *Manager) Rows(ctx context.Context) ([]Row, error)
- func (m *Manager) Run(ctx context.Context, every time.Duration)
- func (m *Manager) SaveThemeSettings(ctx context.Context, name string, values map[string]any) ([]pluginhost.FieldError, error)
- func (m *Manager) ScriptsBlocked(name string) bool
- func (m *Manager) ScriptsRefused(plugin string) bool
- func (m *Manager) SetAllowUnsigned(ctx context.Context, on bool) error
- func (m *Manager) SetEnabled(ctx context.Context, name string, on bool) error
- func (m *Manager) SetGrants(ctx context.Context, name string, perms []sdk.Permission) error
- func (m *Manager) Store() *Store
- func (m *Manager) ThemeError() string
- func (m *Manager) ThemeSettings(ctx context.Context, name string) (map[string]any, error)
- func (m *Manager) ThemeSettingsSchema(ctx context.Context, name string) (json.RawMessage, error)
- func (m *Manager) Uninstall(ctx context.Context, kind Kind, name string, dropData bool) error
- func (m *Manager) Usable(r Row) error
- func (m *Manager) WidgetCount(name string) int
- type Meta
- type MissingSection
- type MissingSectionsError
- type Options
- type Origin
- type Package
- func (p *Package) Data() []byte
- func (p *Package) FS() fs.FS
- func (p *Package) Has(name string) bool
- func (p *Package) PluginManifest() (sdk.Manifest, error)
- func (p *Package) ReadFile(name string) ([]byte, error)
- func (p *Package) SHA256() [32]byte
- func (p *Package) ThemeMeta() (ThemeMeta, error)
- func (p *Package) Wasm() ([]byte, error)
- func (p *Package) Widgets() (Widgets, error)
- type PageOptions
- type ParseError
- type PluginRuntime
- type Registry
- type Row
- type Signature
- type Source
- type Store
- func (s *Store) Config(ctx context.Context) (Config, error)
- func (s *Store) Delete(ctx context.Context, kind Kind, name string) error
- func (s *Store) Get(ctx context.Context, kind Kind, name string) (Row, error)
- func (s *Store) List(ctx context.Context) ([]Row, error)
- func (s *Store) Load(ctx context.Context, kind Kind, name string) (*Package, Row, error)
- func (s *Store) Put(ctx context.Context, p *Package, src Source, enabled bool) error
- func (s *Store) PutThemeSettings(ctx context.Context, theme string, v json.RawMessage) error
- func (s *Store) SetActiveTheme(ctx context.Context, name string) error
- func (s *Store) SetAllowUnsigned(ctx context.Context, on bool) error
- func (s *Store) SetEnabled(ctx context.Context, kind Kind, name string, on bool) error
- func (s *Store) SetServerEnvironment(ctx context.Context, env string) error
- func (s *Store) SetState(ctx context.Context, kind Kind, name, state string) error
- func (s *Store) ThemeSettings(ctx context.Context, theme string) (json.RawMessage, error)
- func (s *Store) ThemeSettingsStamp(ctx context.Context, theme string) (time.Time, error)
- type ThemeEngine
- type ThemeMeta
- type TrustSet
- type Widget
- type Widgets
Constants ¶
const ( PluginManifest = "plugin.json" ThemeManifest = "theme.json" PluginWasm = "plugin.wasm" SignatureFile = "signature.json" // Extension is the file extension of a package. Extension = ".hmp" )
Well-known entries of a package.
const ( PluginAssetCSS = "assets/plugin.css" PluginAssetJS = "assets/plugin.js" )
Public asset files a plugin may ship; pages link them when present.
const ( TierFirstParty = "first-party" TierCommunity = "community" )
Package tiers an Entry may carry.
const ( MaxScreenshotBytes = 2 << 20 MinShotW, MaxShotW = 640, 2560 MinShotH, MaxShotH = 360, 1600 MaxShotPixels = 4_200_000 )
Screenshot limits for the optional theme.json "screenshot" key.
const ( StateInstalled = "installed" StateUninstalling = "uninstalling" )
Package row states.
const Algorithm = "ed25519"
Algorithm is the only signature algorithm of packages and registry indexes.
const DefaultIndexValidity = 90 * 24 * time.Hour
DefaultIndexValidity is how long a freshly built registry index is believed.
const DefaultTheme = "default"
DefaultTheme names the built-in theme that ships inside the binary. It is always available and is what runs when no theme package is active.
const ReservedPluginName = "webhooks"
ReservedPluginName is the name of the former first-party webhooks plugin. Webhooks are core now and the boot step retires a plugin by this name, so no new plugin may take it.
Variables ¶
var ( ErrUnsignedNotAllowed = errors.New("this package is not signed; installing unsigned packages is switched off (an owner can allow it in Admin > Plugins)") ErrDevDisabled = errors.New("development packages are disabled in production") ErrThemeActive = errors.New("this theme is active; activate another theme first") ErrReservedName = errors.New("that name is reserved (the built-in theme, or the webhooks that moved into core)") ErrPermission = errors.New("permission not requested by the plugin") ErrUnknownSource = errors.New("unknown package source") ErrUninstalling = errors.New("this package is being uninstalled; finish the uninstall first") ErrSignerNotAllowed = errors.New("only first-party packages can be installed here (public demo)") )
Errors the admin and the CLI report to the owner.
var ( ErrIndexExpired = errors.New("registry index has expired") ErrIndexNotFresh = errors.New("registry index has no valid issued_at / expires_at") ErrInsecureURL = errors.New("registry URLs must use https") )
Registry index refusals.
var ( ErrUnsigned = errors.New("package is not signed") ErrUntrustedKey = errors.New("package is signed by a key you do not trust") ErrBadSignature = errors.New("package signature does not match its contents") )
Signature errors Verify answers; the manager shows them as they are.
var DefaultLimits = Limits{
Archive: 64 << 20, Entries: 2048, File: 64 << 20, Total: 128 << 20,
Ratio: 200, RatioFloor: 1 << 20, Path: 200,
}
DefaultLimits are generous for real plugins (a wasm module is the big file) and far below what a bomb needs.
var ErrCommunityScripts = pluginhost.ErrCommunityScripts
ErrCommunityScripts is the one sentinel for errors.Is across layers.
var ErrNotFound = errors.New("package not installed")
ErrNotFound is returned for a package that is not installed.
var ErrPermNotRequested = errors.New("packages: permission not requested by the plugin")
ErrPermNotRequested is what SetGrants answers for a permission the manifest lacks.
var ErrRegistryOff = errors.New("no registry is configured")
ErrRegistryOff means no registry is configured.
var FirstPartyKeyIDs = []string{"a592a1276b3947b1", "adea8e00f5ac74ca"}
FirstPartyKeyIDs are the key ids first-party packages were ever signed under: the key rotated out on 2026-10-16 (docs/PACKAGES.md) and the one that replaced it. config.FirstPartyKey is checked as well. The list is fixed so a later rotation does not strand a package signed under an earlier key.
var FirstPartyPlugins = []string{"searchconsole", "gtm"}
FirstPartyPlugins are the plugins the core team maintains. The list is the rule; the author is not checked.
Functions ¶
func BuildIndex ¶
func BuildIndex(dir, urlPrefix string, key Key, now time.Time, validity time.Duration) ([]byte, error)
BuildIndex lists every .hmp in dir as a registry Entry whose URL is urlPrefix + file name (a relative prefix such as "packages/" keeps the registry movable), and wraps the index in an envelope signed with key. It is what `hatamoto registry index` runs. Packages are listed by name.
validity must be positive and at most maxIndexLifetime (180 days); the window is counted from issued_at (one minute before now, to forgive clock skew), so the longest index still passes the freshness check servers apply.
func BuildIndexWith ¶
func BuildIndexWith(dir, urlPrefix string, key Key, now time.Time, validity time.Duration, opts BuildOptions) ([]byte, error)
BuildIndexWith is BuildIndex plus optional per-package metadata. Package names must be unique per kind (a theme and a plugin may share one). Meta is keyed by bare name: every Meta name must match a package in dir, and a name carried by both a plugin and a theme cannot take Meta.
func CheckFresh ¶
CheckFresh refuses a document whose RFC 3339 window is malformed, longer than maxLifetime, issued in the future (beyond a 10 minute skew) or already expired at now. what names the document in errors. Failures wrap ErrIndexNotFresh or ErrIndexExpired.
func CompareVersions ¶
CompareVersions orders two versions: -1, 0 or 1. Both must be ValidSemver; an unparsable version compares as equal to everything.
func IsFirstPartyKey ¶
IsFirstPartyKey reports whether id is a first-party signing key id.
func PackDir ¶
PackDir builds a .hmp from a plugin or theme directory. Only the files a package may hold are included (dot files, node_modules and the like are skipped, not refused). With a key the archive is signed. The result is checked with Parse before it is returned, so what is built is installable.
func PackFiles ¶
PackFiles builds a .hmp from a file map, deterministically (sorted, fixed timestamps), signing it when key is set.
func ParseMeta ¶
ParseMeta reads a JSON object of package name to Meta. Unknown fields are refused so a typo cannot silently drop a value.
func ParsePublicKey ¶
ParsePublicKey decodes a base64 ed25519 public key.
func ReadArchive ¶
ReadArchive verifies the sha256 of the exact bytes of a zip, then returns its files as a path->bytes map for PackFiles. Everything happens in memory: no entry is extracted and no entry name reaches the file system. It refuses a sha mismatch, unsafe names (checkName), links and special files, duplicate entries (case-insensitive) and anything over lim (entries, file, total, ratio), measured on the bytes actually read. Directories are skipped. When theme.json is not at the root, ONE common wrapper directory is stripped.
func RenderPage ¶
func RenderPage(idx *Index, opts PageOptions) ([]byte, error)
RenderPage renders the plugin entries of idx (sorted as in the index) as one self-contained HTML page: inline style and script, no remote assets. The index is expected to come from ParseIndex.
func SafeHTTPS ¶
SafeHTTPS returns s when it is an https URL with a host and no credentials, and "" otherwise, so a hostile value never becomes a link.
func ScreenshotExt ¶
ScreenshotExt returns the normalised extension (".png", ".webp" or ".jpg") of rel.
func ServeFile ¶
ServeFile writes one file of the package (GET and HEAD). name is a slash path inside the package; anything that is not a regular file is a 404. Responses are marked nosniff and, as untrusted content, sandboxed: an SVG or HTML file opened directly runs without script and without the site's origin, unless the caller passes its own csp.
func ValidSemver ¶
ValidSemver reports whether v parses as a version Newer understands ("v1.2.3", optional pre-release and build metadata).
func ValidateScreenshot ¶
ValidateScreenshot checks that rel names an allowed raster image under assets/ in files, within the size and dimension limits. Only the image header is read.
Types ¶
type BuildOptions ¶
BuildOptions are the optional inputs of BuildIndexWith.
type Config ¶
type Config struct {
// AllowUnsigned lets unsigned packages be installed (the owner's explicit,
// warned opt-in).
AllowUnsigned bool
// ActiveTheme is the installed theme in use; "" is the built-in default.
ActiveTheme string
// ServerEnvironment is the ENVIRONMENT of the server that last booted on
// this database ("" before the first boot). The CLI reads it so that
// "production" is the server's decision, not whatever the shell exports.
ServerEnvironment string
}
Config is the owner's package policy and the active theme.
type ConfirmationRequired ¶
type ConfirmationRequired struct {
Kind Kind
Name string
OldVersion, NewVersion string
// OldSigner and NewSigner are key ids ("" for unsigned).
OldSigner, NewSigner string
Downgrade bool // the new version is not newer than the installed one
SignerChange bool // another signer than the installed package's
// WidenedAccess is set when a plugin update reaches further than the
// installed one: it lists web addresses the installed version did not
// (NewHosts) or newly asks for http.fetch.any (NewFetchAny).
WidenedAccess bool
NewHosts []string
NewFetchAny bool
// NewIsland: the update newly asks for builder.island (JavaScript on the
// site's pages); it counts as widened access too.
NewIsland bool
// NewScripts: the update newly asks for site.scripts.
NewScripts bool
Missing []MissingSection
}
ConfirmationRequired is returned by Install when replacing the installed package needs the owner's explicit say-so: an older (or same) version, a different signer, or an update of the active theme that loses section types the content uses. Install again with InstallRequest.Confirm to go ahead.
func (*ConfirmationRequired) Error ¶
func (e *ConfirmationRequired) Error() string
type DataDropper ¶
DataDropper removes a plugin's own schema, role and stored data.
type Entry ¶
type Entry struct {
Kind Kind `json:"kind"`
Name string `json:"name"`
Version string `json:"version"`
Description string `json:"description,omitempty"`
// URL of the .hmp, absolute or relative to the registry index.
URL string `json:"url"`
// SHA256 is the hex hash of the .hmp (checked after the download).
SHA256 string `json:"sha256"`
Size int64 `json:"size,omitempty"`
Permissions []string `json:"permissions,omitempty"`
Homepage string `json:"homepage,omitempty"`
// Author, Source (repository URL), Commit (the pinned source revision) and
// Tier describe where a package came from. All are optional: servers that
// predate them ignore the keys.
// Title is an optional display name for the directory page.
Title string `json:"title,omitempty"`
Author string `json:"author,omitempty"`
Source string `json:"source,omitempty"`
Commit string `json:"commit,omitempty"`
Tier string `json:"tier,omitempty"`
// Screenshot is a registry-relative path to a theme preview image.
Screenshot string `json:"screenshot,omitempty"`
}
Entry is one package a registry offers.
type Index ¶
type Index struct {
Version int `json:"version"`
// IssuedAt and ExpiresAt (RFC 3339) bound how long a signed index is
// believed: an old signed copy cannot be replayed once it has expired.
IssuedAt string `json:"issued_at"`
ExpiresAt string `json:"expires_at"`
Packages []Entry `json:"packages"`
}
Index is the payload of a registry.json envelope.
func ParseIndex ¶
ParseIndex verifies a signed registry.json document against trust, checks its version and freshness at now and drops entries that are malformed. An unsigned, tampered, expired or untrusted index is refused. An unknown tier is cleared; the entry stays.
type InstallRequest ¶
type InstallRequest struct {
Source Source
// Grant lists the permissions the owner approved at the permission prompt
// (plugins). nil keeps the earlier grant on an update and grants nothing on
// a new install.
Grant []string
// Confirm is the owner's answer to a ConfirmationRequired: replace the
// installed package anyway.
Confirm bool
}
InstallRequest says how to install a package.
type Installed ¶
type Installed struct {
Kind Kind
Name string
Version string
Updated bool // an older version was replaced
Status *pluginhost.Status // plugins, when a runtime is attached
}
Installed is the outcome of an install.
type Key ¶
type Key struct {
ID string `json:"key_id"`
Public string `json:"public"` // base64, 32 bytes
Private string `json:"private,omitempty"` // base64, 64 bytes; absent from a public key file
}
Key is a signing key as `hatamoto keys generate` writes it.
func (Key) PrivateKey ¶
func (k Key) PrivateKey() (ed25519.PrivateKey, error)
PrivateKey decodes the signing half.
type Limits ¶
type Limits struct {
Archive int64 // size of the .hmp file
Entries int // files and directories
File int64 // one file, uncompressed
Total int64 // all files, uncompressed
// Ratio is the largest uncompressed:compressed ratio of a file bigger than
// RatioFloor (a zip bomb compresses far beyond any real asset).
Ratio int64
RatioFloor int64
Path int // longest entry name
}
Limits bound what an archive may expand to. Parse enforces them on the bytes actually read, never on the sizes the archive declares.
type Manager ¶
type Manager struct {
Options
// contains filtered or unexported fields
}
Manager installs, activates and removes packages while the server runs. The database is the source of truth; Reconcile makes the process match it.
func NewManager ¶
NewManager returns a Manager. Call Reconcile once at boot, then Run.
func (*Manager) Activate ¶
Activate makes an installed theme (or the built-in default, for "" or DefaultTheme) the site's theme. It first reports section types the theme lacks (MissingSectionsError) unless force is set, and refuses a theme that does not render.
func (*Manager) ActiveTheme ¶
ActiveTheme is the theme the process is serving with (DefaultTheme for the built-in).
func (*Manager) Audit ¶
Audit writes one row of hatamoto_admin_audit (and a log line) for a change to packages: install, update, uninstall, activate, grants, the unsigned opt-in. The detail never holds a secret.
func (*Manager) Blocked ¶
Blocked says why an installed package is not loaded because of policy ("" when it is not blocked): shown in the admin so a package is not silently dead.
func (*Manager) CheckGrant ¶
func (m *Manager) CheckGrant(plugin string, perms []sdk.Permission) error
CheckGrant is the pluginhost grant policy: a refusal when perms ask a community plugin for site.scripts or builder.island.
func (*Manager) CheckPolicy ¶
CheckPolicy says whether p may be installed from src: signed by a trusted key, or unsigned and allowed (the owner's opt-in, or a dev push outside production). The error is one of the Err* values.
func (*Manager) CommunityPlugin ¶
CommunityPlugin reports whether name is a community-origin plugin, by the same origins map as CommunityScriptsLive. It does not say whether its script is linked; the render path asks it about scripts a page body already links.
func (*Manager) CommunityScriptsLive ¶
CommunityScriptsLive reports whether some community plugin's script is linked on pages right now (policy, grant, loaded and manifest all agree). It reads in-memory state only, so it is cheap enough for every HTML response.
func (*Manager) FetchFromRegistry ¶
FetchFromRegistry downloads kind/name from the registry, checked against the hash the signed index lists and against the name it was listed under (a registry that serves another package under a listed name is lying).
func (*Manager) Install ¶
Install parses, verifies and stores a package and, with a runtime or engine attached, applies it at once. Signed packages must be signed by a trusted key; unsigned ones are installed only when the owner allowed it (or for a dev push outside production).
func (*Manager) InstallFromRegistry ¶
func (m *Manager) InstallFromRegistry(ctx context.Context, kind Kind, name string, req InstallRequest) (*Installed, error)
InstallFromRegistry downloads kind/name from the registry and installs it.
func (*Manager) LoadDevDir ¶
LoadDevDir loads plugins from a directory (HATAMOTO_PLUGIN_DIR, development only) as unsigned in-memory packages. They replace an installed package of the same name and are not stored.
func (*Manager) MarkupStopped ¶
MarkupStopped lists the plugin's sections and partials that are not registered because they carry script constructs and it lacks site.scripts.
func (*Manager) MissingSections ¶
MissingSections lists the section types used by content that theme (an installed theme, or "" / DefaultTheme) cannot render.
func (*Manager) NeedsConfirmation ¶
func (m *Manager) NeedsConfirmation(ctx context.Context, p *Package, src Source) (*ConfirmationRequired, error)
NeedsConfirmation reports, without installing, whether replacing the installed package of the same name with p needs the owner's confirmation (nil when p is new or the replacement is a plain upgrade by the same signer).
func (*Manager) PluginPackage ¶
PluginPackage returns the parsed package of a loaded plugin (its public assets and ui/ files), nil when it is not loaded.
func (*Manager) PreviewEngine ¶
PreviewEngine returns an engine that renders with any installed theme (or the built-in default), whatever is active, cached until the package changes.
func (*Manager) Reconcile ¶
Reconcile makes the running process match the database: plugins that are installed and enabled are loaded (or reloaded when their package changed), removed or disabled ones are unloaded, and the active theme is applied.
func (*Manager) Run ¶
Run reconciles every interval until ctx ends, so a package installed by the CLI (or another replica) goes live without a restart.
func (*Manager) SaveThemeSettings ¶
func (m *Manager) SaveThemeSettings(ctx context.Context, name string, values map[string]any) ([]pluginhost.FieldError, error)
SaveThemeSettings validates values against the theme's settings_schema and stores them. Problems come back as field errors, not as an error. A change to the active theme's settings is live at once.
func (*Manager) ScriptsBlocked ¶
ScriptsBlocked reports whether an installed plugin ships assets/plugin.js that pages do not link because the owner has not granted it site.scripts. A package without plugin.wasm never has its plugin.js linked (only a loaded plugin does), so it is not flagged.
func (*Manager) ScriptsRefused ¶
ScriptsRefused is scriptsPolicyRefuses for other packages (grant policy and the pluginhost script deny hook).
func (*Manager) SetAllowUnsigned ¶
SetAllowUnsigned stores the owner's opt-in for unsigned packages.
func (*Manager) SetEnabled ¶
SetEnabled turns an installed plugin on or off without uninstalling it.
func (*Manager) SetGrants ¶
SetGrants replaces the grants of an installed plugin the runtime does not know (a widget-only package has no wasm to load): perms must be listed in its manifest. The registries are reconciled so an island grant applies at once. ErrNotFound is returned for an unknown plugin.
func (*Manager) ThemeError ¶
ThemeError is why the active theme could not be applied ("" when it was).
func (*Manager) ThemeSettings ¶
ThemeSettings returns a theme's settings with the schema's defaults merged.
func (*Manager) ThemeSettingsSchema ¶
ThemeSettingsSchema is the settings_schema of an installed theme (nil: none).
func (*Manager) Uninstall ¶
Uninstall removes a package. A theme is deleted at once. A plugin goes in a recoverable order: its row is marked uninstalling (so it is unloaded and cannot be installed over), its permissions are revoked, with dropData its schema, role, stored values and settings are dropped, and only then is the row deleted. A failure leaves the row uninstalling; call Uninstall again. Core content is never touched.
func (*Manager) Usable ¶
Usable is usable's exported form: nil when the stored row would be loaded by this instance's trust policy, the reason otherwise.
func (*Manager) WidgetCount ¶
WidgetCount is widgetCount for callers outside the manager's lock: the widgets the last reconcile found in the plugin's package.
type Meta ¶
type Meta struct {
Title string `json:"title,omitempty"`
Author string `json:"author,omitempty"`
Source string `json:"source,omitempty"`
Commit string `json:"commit,omitempty"`
Tier string `json:"tier"`
// Screenshot is screenshots/<name>.(png|webp|jpg); themes only.
Screenshot string `json:"screenshot,omitempty"`
}
Meta says where a package came from. It is keyed by package name in BuildOptions.Meta and never changes the package's identity.
type MissingSection ¶
type MissingSection struct {
Type string
// Uses is how many sections of that type the content holds.
Uses int
}
MissingSection is a section type content uses that a theme does not render.
type MissingSectionsError ¶
type MissingSectionsError struct{ Missing []MissingSection }
MissingSectionsError is returned by Activate when content uses section types the theme lacks and the activation was not forced.
func (*MissingSectionsError) Error ¶
func (e *MissingSectionsError) Error() string
type Options ¶
type Options struct {
Pool *pgxpool.Pool
Trust TrustSet
Registry *Registry
Plugins PluginRuntime
Grants pluginhost.Grants
Dropper DataDropper
Engine ThemeEngine
// NewPreview builds a second engine for previewing an installed theme.
NewPreview func(layers []fs.FS, settings map[string]any) (*render.Engine, error)
// AllowDev lets packages from `hatamoto theme dev` be installed and loaded.
// It is the server's decision (never in production); dev rows are ignored
// at load time when it is off, whatever was written to the database.
AllowDev bool
// OnlySigners, when not empty, limits installs to packages signed by one of
// these key ids (the public demo allows first-party packages only). It
// applies to every source and refuses unsigned packages whatever the
// owner's opt-in says.
OnlySigners []string
// OnChange runs after what the site shows changed (theme, settings,
// plugins): the server clears its page cache.
OnChange func(ctx context.Context)
Logger *slog.Logger
}
Options wires a Manager. Plugins, Engine and Dropper are nil in the CLI, which only writes the database; a running server applies it within a few seconds (see Run).
type Origin ¶
type Origin int
Origin says whose code a plugin is, for the community-script policy.
const ( // OriginSelfBuilt is unsigned, dev, or signed by another owner-trusted key. OriginSelfBuilt Origin = iota // OriginFirstParty is signed by a first-party key and on FirstPartyPlugins. OriginFirstParty // OriginCommunity is signed by a first-party key but not on FirstPartyPlugins // (the registry signs community builds with the same key). OriginCommunity )
type Package ¶
type Package struct {
Kind Kind
Name string
Version string
Manifest json.RawMessage // plugin.json or theme.json, as shipped
// Digest is what the signature covers: a hash over every file name and
// content hash except signature.json, so it does not depend on how the
// zip was compressed.
Digest [32]byte
// Signature is the embedded signature.json, nil when the package is unsigned.
Signature *Signature
// ScreenshotErr is why theme.json's "screenshot" was ignored (nil: valid or
// absent). Parse stays lenient so installed themes keep loading; publishing
// paths (theme check --registry) refuse on it.
ScreenshotErr error
// contains filtered or unexported fields
}
Package is a parsed, validated .hmp. It is immutable.
func Parse ¶
Parse validates raw as a .hmp: zip safety (no traversal, links, odd names, oversized or highly compressed entries), the layout, and the manifest. It does not check the signature; see TrustSet.Verify and Manager.Install.
func (*Package) FS ¶
FS is the package's files (without signature.json). Reads decompress on demand; Parse already read every file once, so they cannot fail on a lying header.
func (*Package) PluginManifest ¶
PluginManifest decodes plugin.json of a plugin package.
type PageOptions ¶
type PageOptions struct {
Title, Intro, WriteURL, SubmitURL, InstallHint string
}
PageOptions are the words and links of a rendered directory page. Empty URLs hide their button; an empty InstallHint hides the hint line.
type ParseError ¶
type ParseError struct{ Msg string }
ParseError is a package that was refused; its message is safe to show.
func (*ParseError) Error ¶
func (e *ParseError) Error() string
type PluginRuntime ¶
type PluginRuntime interface {
LoadWith(ctx context.Context, m sdk.Manifest, wasm []byte, a pluginhost.Assets) pluginhost.Status
Unload(ctx context.Context, name string)
Statuses() []pluginhost.Status
}
PluginRuntime is the part of *pluginhost.Runtime the manager drives.
type Registry ¶
type Registry struct {
URL string
Client *http.Client
Trust TrustSet
Limits Limits
// AllowInsecure accepts http:// URLs (an explicit development or test
// override).
AllowInsecure bool
// Now is the clock (tests); time.Now when nil.
Now func() time.Time
}
Registry reads a signed registry index and downloads the packages it lists.
type Row ¶
type Row struct {
Kind Kind
Name string
Version string
Manifest json.RawMessage
SHA256 string
SignerKeyID string // "" for an unsigned package
Source Source
Enabled bool
// State is StateInstalled, or StateUninstalling for a removal that has
// started and can be retried (the package is not loaded).
State string
Size int
InstalledAt time.Time
UpdatedAt time.Time
}
Row is one installed package as stored, without its archive bytes.
type Signature ¶
type Signature struct {
Signer string `json:"signer"` // KeyID of the signing key
Algorithm string `json:"algorithm"`
Signature string `json:"signature"` // base64
}
Signature is signature.json inside a package: who signed and the ed25519 signature over the package digest (see Package.Digest).
func NewSignature ¶
NewSignature wraps a raw ed25519 signature made by the holder of pub, for signers that never expose their private key (an ssh-agent).
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store keeps packages in hatamoto_packages.
func (*Store) Delete ¶
Delete removes the package row (and a theme's settings). It touches nothing else.
func (*Store) Load ¶
Load reads the archive of an installed package and parses it again (the bytes are trusted: they were verified on the way in, but parsing keeps one code path).
func (*Store) Put ¶
Put installs or replaces (update) the package of the same kind and name. An update keeps the stored enabled flag; a new package is stored with enabled.
func (*Store) PutThemeSettings ¶
PutThemeSettings stores a theme's settings object.
func (*Store) SetActiveTheme ¶
SetActiveTheme stores the active theme ("" selects the built-in default).
func (*Store) SetAllowUnsigned ¶
SetAllowUnsigned stores the unsigned-package opt-in.
func (*Store) SetEnabled ¶
SetEnabled flips a plugin's enabled flag.
func (*Store) SetServerEnvironment ¶
SetServerEnvironment records the booting server's ENVIRONMENT.
func (*Store) ThemeSettings ¶
ThemeSettings returns a theme's stored settings object ({} when none).
type ThemeEngine ¶
type ThemeEngine interface {
SetTheme(layers ...fs.FS) error
SetThemeName(name string)
CheckTheme(layers ...fs.FS) error
SetSettings(map[string]any)
SectionTypes(layers ...fs.FS) ([]string, error)
}
ThemeEngine is the part of *render.Engine the manager drives.
type ThemeMeta ¶
type ThemeMeta struct {
Name string `json:"name"`
Version string `json:"version"`
Description string `json:"description,omitempty"`
SettingsSchema json.RawMessage `json:"settings_schema,omitempty"`
// Screenshot is an optional preview image under assets/, see ValidateScreenshot.
Screenshot string `json:"screenshot,omitempty"`
}
ThemeMeta is the part of theme.json the host reads.
type TrustSet ¶
TrustSet is the signer keys the owner trusts, by key id.
func ParseTrustSet ¶
ParseTrustSet reads a comma- or space-separated list of base64 public keys.
func (TrustSet) Verify ¶
Verify checks the package's embedded signature against the trusted keys. It returns ErrUnsigned for a package without one, ErrUntrustedKey for an unknown signer and ErrBadSignature when the contents do not match.
func (TrustSet) VerifyBytes ¶
VerifyBytes checks a base64 signature over msg made by the named signer. algorithm may be empty (ed25519). Errors are ErrUntrustedKey for a signer outside the set and ErrBadSignature when msg does not match.
func (TrustSet) VerifyIndex ¶
func (t TrustSet) VerifyIndex(doc []byte) (json.RawMessage, error)
VerifyIndex checks a registry.json envelope and returns the index JSON.