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

wasmplugin

package
v0.46.0 Latest Latest
Warning

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

Go to latest
Published: Sep 26, 2026 License: MIT Imports: 55 Imported by: 0

Documentation

Overview

Package wasmplugin runs filex "app plugins": WebAssembly modules executed inside the filex process by wazero (through the Extism host SDK), with no access to the host's filesystem, network, environment or database — only to the host functions filex exposes, each gated by a permission the administrator granted at install.

Why in-process wasm and not a subprocess or a sidecar

The storage-plugin subsystem (internal/plugin) starts a plugin BINARY in filex's own container: same filesystem, same environment, FILEX_SECRET_KEY in reach. That is fine for a driver the operator built, and unacceptable for third-party code a user installs from a URL. A container per plugin needs a Docker socket somewhere; an OS sandbox is strong on Linux and absent on the other two platforms filex ships for. A wasm sandbox is the same on all three, costs no extra process, and cannot reach anything it is not handed.

What the runtime guarantees

  • wazero's compiler backend only (amd64/arm64). The interpreter is refused, not tolerated: measured elsewhere, it takes every core for minutes.
  • A fresh instance per call: no state survives between users or jobs, memory is returned when the call ends.
  • A memory ceiling (Manifest.MemoryPages) and a wall-clock budget per call; the instance is torn down when either is hit.
  • No WASI filesystem preopens, no environment, no arguments.
  • Compiled modules are cached on disk (WithCompilationCache), so a 20–50 MB Go module is compiled once per host, not once per boot.

Index

Constants

View Source
const (
	CodePluginError = "plugin_error" // the guest set an error message
	CodePluginTrap  = "plugin_trap"  // the guest crashed (unreachable, OOB…)
	CodePluginOOM   = "plugin_oom"   // the guest hit its memory ceiling
	CodeTimeout     = "timeout"      // the call outlived its budget
	CodeRefused     = "refused"      // describe disagrees with the manifest
	CodeUnsupported = "unsupported"  // this host cannot run plugins (arch)
)

Outcome codes a guest call can end with. They are what the job row, the admin log and the user-facing message key on — never a wazero stack.

View Source
const (
	KindApp          = "app"
	KindLanguagePack = "language_pack"
)

The two kinds of app, as Status.Kind and DryRunAnswer.Kind say them.

View Source
const (
	DefaultMemoryPages = 1024 // 64 MiB
	MaxMemoryPages     = 4096 // 256 MiB
	DefaultCallTimeout = 15   // seconds, views/pages/describe
	MaxCallTimeout     = 60
	DefaultJobTimeout  = 300 // seconds, action_run
	MaxJobTimeout      = 900
	// MaxTickTimeout caps the hourly wake-up. It is deliberately tighter
	// than a screen's ceiling: a screen has a person waiting and 60 s is
	// defensible, while a wake-up runs unattended and one that hangs holds
	// up every other app's schedule behind it.
	MaxTickTimeout = 30
)

Limits the host imposes on what a manifest may ask for.

View Source
const (
	StateRunning  = "running"
	StateDisabled = "disabled"
	StateRefused  = "refused"
	StateFailed   = "failed"
)

Plugin states.

View Source
const (
	ErrCodeManifestInvalid       = "manifest_invalid"
	ErrCodeSHA256Mismatch        = "sha256_mismatch"
	ErrCodeSHA256Required        = "sha256_required"
	ErrCodeSignatureRequired     = "signature_required"
	ErrCodeSignatureInvalid      = "signature_invalid"
	ErrCodePermissionsIncomplete = "permissions_incomplete"
	ErrCodePermissionsChanged    = "permissions_changed"
	ErrCodeNameTaken             = "name_taken"
	ErrCodeDescribeMismatch      = "describe_mismatch"
	ErrCodeTooLarge              = "too_large"
	ErrCodeDemo                  = "demo_refused"
	ErrCodeFetch                 = "fetch_failed"
	ErrCodeNotFound              = "not_found"
)

Install error codes.

View Source
const (
	// FetchReasonBadRepo: the repository is not written as owner/name.
	FetchReasonBadRepo = "bad_repo"
	// FetchReasonManifestNotFound: no filex-app.json at any ref tried — a
	// repository that does not exist, is private, has no manifest at its
	// root, or a ref that does not exist.
	FetchReasonManifestNotFound = "manifest_not_found"
	// FetchReasonModuleNotFound: the manifest names a module address that
	// answers 404 — usually a release whose asset was never uploaded.
	FetchReasonModuleNotFound = "module_not_found"
	// FetchReasonUnreachable: no answer at all (DNS, network, TLS).
	FetchReasonUnreachable = "unreachable"
	// FetchReasonHTTPStatus: an answer, but not a success or a 404.
	FetchReasonHTTPStatus = "http_status"
	// FetchReasonBadURL: not an https address (plain http only for loopback).
	FetchReasonBadURL = "bad_url"
	// FetchReasonMissingURL: a URL install without its addresses — the
	// manifest always, the module unless the manifest is a language pack.
	FetchReasonMissingURL = "missing_url"
	// FetchReasonTooLarge: the answer was larger than the cap.
	FetchReasonTooLarge = "too_large"
)

Why a fetch failed (InstallError.Reason on fetch_failed).

View Source
const (
	// TickInterval is how often a scheduled app is woken. The wake-up is
	// re-armed to the next boundary of this interval, so an instance that
	// has been up for a week still wakes on the hour.
	TickInterval = time.Hour
	// MaxScheduleItems / MaxSchedulePaths are the wire contract's numbers,
	// not a second copy: the guest is told them in every TickInput and the
	// test kit checks an answer against them, so they are named once in
	// pkg/pluginkit/wire and enforced here.
	MaxScheduleItems = wire.ScheduleMaxItems
	MaxSchedulePaths = wire.ScheduleMaxPaths
)

Bounds. Everything a wake-up can ask for is capped here; a plugin is told the two it has to plan around (MaxScheduleItems, MaxSchedulePaths) in its TickInput, so it can choose what to drop rather than having it chosen.

Variables

View Source
var (
	ErrPageNotFound = errors.New("page not found")
	ErrPageGone     = errors.New("page is no longer available")
)

Page errors the HTTP layer maps to status codes. The PIN-gate errors are internal/share's (ErrBadPIN / ErrLocked): there is one gate now.

View Source
var ErrUnsupportedArch = errors.New("wasmplugin: app plugins need an amd64 or arm64 host (wazero's interpreter is refused — it would take every core)")

ErrUnsupportedArch is returned by New on a CPU wazero has no compiler for.

View Source
var HostVersion = "dev"

HostVersion is what `describe` is told; filled by the server from its build.

View Source
var SurfaceNodeTypes = map[string]bool{
	"text": true, "form": true, "steps": true, "list": true, "progress": true,
	"people-picker": true, "pin-input": true, "file-chooser": true, "pdf-fields": true,
	"signature-pad": true, "preview": true, "divider": true, "row": true,
}

SurfaceNodeTypes is the v1 catalogue.

Functions

func ArchSupported

func ArchSupported() bool

ArchSupported reports whether wazero has a compiler for this CPU.

func CheckOpen

func CheckOpen(p *Installed, s *wire.Surface) error

CheckOpen validates a surface's `open` against the plugin that answered: the screen it names must be that plugin's own, and it may not name both. The PATH is checked by the handler, which is where a caller's permissions are known — a screen must not become a way to look at a file the person asking could not open themselves.

func DecodeInstallBody

func DecodeInstallBody(b []byte) (map[string]json.RawMessage, error)

decodeJSONBody is a small helper for the handlers' JSON install bodies.

func EncodeJobText added in v0.46.0

func EncodeJobText(t wire.Text) string

EncodeJobText is t as a job column: one plain string when every language says the same (or only one was written), else every language, marked.

func ExposedFiles

func ExposedFiles(sh *model.Share) []wire.FileRef

ExposedFiles lists the copies an app link exposed, straight off the row.

⚠ Deliberately a free function and not a Registry method: the no-JS page must be able to hand a visitor the document they were sent even on an instance where the plugin runtime is unavailable (an architecture without wasm, a plugin that failed to load). The copies are already on disk and belong to the share, not to the running app.

func IsCode

func IsCode(err error, code string) bool

IsCode reports whether err is a CallError with the given code.

func JobText added in v0.46.0

func JobText(stored, lang string) string

JobText reads a job column in lang (wire.Text.Get: the language, its base language, English, any). A plain column — every row written before EncodeJobText, a progress line, the host's own words — comes back as it is.

func JobTimeout

func JobTimeout(l wire.Limits) int

JobTimeout is the clamped budget for one action_run of an action.

func LocalizeNote

func LocalizeNote(note, lang string) string

LocalizeNote reads a schedule row's note in `lang`: a note written by wakeNote is composed in that language; a {lang: …} object (a row written before the record existed) is read the same way; anything else (an error) is returned as it is.

func Matches

func Matches(a wire.Applies, items []Item) bool

Matches reports whether an Applies rule accepts a selection described by (kind, ext, mime) per item. It is the one implementation the server uses for every check; the explorer mirrors it client-side for the menu.

func NewJobID

func NewJobID() string

NewJobID mints the random token pending_ops.dest carries.

func NextTickBoundary

func NextTickBoundary(now time.Time) time.Time

NextTickBoundary is when the wake-up after now falls: the next boundary of TickInterval, in UTC. Boundaries rather than "an hour from whenever it last ran" so a restart at 03:37 does not move every later wake-up to :37.

func OutputOverride

func OutputOverride(params map[string]any) *wire.Output

OutputOverride reads the per-job output stored in params, or nil.

func ParseCatalogue

func ParseCatalogue(b []byte) ([]string, error)

ParseCatalogue reads the embedded catalogue file (a flat key → English object — the exact shape of one `ui_locales` language) into its keys.

func PersonalStateKeys

func PersonalStateKeys(keys []string, userID int64) []string

PersonalStateKeys is the view of a file's state keys (`<plugin>:<key>`) that ONE person gets: a personal key `<key>@<id>` (wire.PersonalState) is that person's own and reads `<key>@me`; anybody else's is left out, so a listing never says who else has something to do on the file. Other keys pass as they are. The listing (app badges) and the run check (authorise) both read the state through this, so the menu and the server agree.

func PublicURL

func PublicURL(base, token string) string

PublicURL builds the visitor link; base is the instance's public URL.

func SanitizeSurface

func SanitizeSurface(s *wire.Surface)

SanitizeSurface prunes unknown node types, caps counts and depth.

func SetOutputOverride

func SetOutputOverride(params map[string]any, o *wire.Output)

SetOutputOverride stores o under the override key.

func TickTimeout

func TickTimeout(m *Manifest) int

TickTimeout is the clamped budget for one `tick` call: whatever the manifest asked for as a call timeout, never more than MaxTickTimeout.

func ValidOutputMode

func ValidOutputMode(m string) bool

ValidOutputMode is the closed set an Output.Mode may take. `folder` is a per-job choice only (a manifest declares `sibling` + `elsewhere`).

func ValidScheduleKey

func ValidScheduleKey(key string) bool

ValidScheduleKey reports whether an app may use key for a scheduled item.

func WithActorIP

func WithActorIP(ctx context.Context, ip string) context.Context

WithActorIP carries the address a signed-in person's request came from into a view call, where it becomes `context.actor.ip` (wire.Actor.IP). The handler knows the request (and the trusted-proxy rule that reads it); the registry does not, and should not have to.

func WithScope

func WithScope(ctx context.Context, s *Scope) context.Context

WithScope attaches a call scope to ctx.

Types

type ActionRow

type ActionRow struct {
	Plugin  string       `json:"plugin"`
	ID      string       `json:"id"`
	Key     string       `json:"key"`
	Label   wire.Text    `json:"label"`
	Icon    string       `json:"icon,omitempty"`
	Applies wire.Applies `json:"applies"`
	View    string       `json:"view,omitempty"`
	// ViewPlacement is the declared placement of View (modal | page), so
	// the menu knows whether to open a dialog or a new tab.
	ViewPlacement string    `json:"view_placement,omitempty"`
	Confirm       wire.Text `json:"confirm,omitempty"`
	MinRole       string    `json:"min_role,omitempty"`
	Danger        bool      `json:"danger,omitempty"`
	OutputMode    string    `json:"output_mode"`
	// OutputElsewhere: the result may go into a folder the person chooses
	// (manifest output.elsewhere), so the action is offered on a read-only
	// storage too — its screen asks where the result should go.
	OutputElsewhere bool `json:"output_elsewhere,omitempty"`
	// Gated is what the action WOULD also be offered on once a requirement
	// the server lacks is met — the extensions LibreOffice would add to the
	// signing app's "Sign…", say. Sent to administrators only: the owner's
	// rule for anything that depends on how the server is set up is "greyed
	// with the reason for an administrator, not shown to anybody else", so
	// the explorer draws each as a disabled row that says what is missing.
	Gated []GatedRule `json:"gated,omitempty"`
}

ActionRow is one menu entry as GET /api/files/plugins/actions returns it.

type ActionsAnswer

type ActionsAnswer struct {
	Actions []ActionRow `json:"actions"`
	Views   []ViewRow   `json:"views"`
}

ActionsAnswer is the whole list for one caller.

type CAList

type CAList struct {
	ID          string     `json:"id"`
	Subject     string     `json:"subject"`
	Issuer      string     `json:"issuer"`
	Fingerprint string     `json:"fingerprint"`
	Imported    bool       `json:"imported"`
	NotBefore   time.Time  `json:"not_before"`
	NotAfter    time.Time  `json:"not_after"`
	CreatedAt   time.Time  `json:"created_at"`
	RetiredAt   *time.Time `json:"retired_at,omitempty"`
	Live        bool       `json:"live"`
}

CAList describes one signing authority for the admin surface.

type CallError

type CallError struct {
	Code    string
	Export  string
	Message string // safe for a user; the guest's own words when it set any
	Cause   error  // the runtime's error, for the plugin log only
}

CallError is a failed guest call, classified.

func (*CallError) Error

func (e *CallError) Error() string

func (*CallError) Unwrap

func (e *CallError) Unwrap() error

type Cataloguer

type Cataloguer interface {
	Catalogue(ctx context.Context, storageID int64, rel string) (*model.Node, error)
}

Cataloguer records in the catalogue a file the STORAGE has and the catalogue has not seen yet, with the same bookkeeping a write through filex gets (node row, parent rows, index, thumbnail), and answers its node.

The output sink implements it (handlers.AppPlugins, through protocolsync); it is a separate interface so a sink that does not — every test double of OutputSink — leaves share_create exactly as strict as it was.

type Compiled

type Compiled struct {
	Name     string
	Manifest *Manifest
	SHA256   string
	// contains filtered or unexported fields
}

Compiled is one installed plugin's module, compiled once and instantiated per call.

func (*Compiled) Call

func (c *Compiled) Call(ctx context.Context, export string, input []byte, budget time.Duration) ([]byte, error)

Call runs one export on a fresh instance with a wall-clock budget. The returned bytes are the guest's output. Errors are *CallError.

ctx may carry a call scope (WithScope) that host functions read to decide what this particular call is allowed to touch; the guest never sees it.

func (*Compiled) Close

func (c *Compiled) Close(ctx context.Context) error

Close frees the compiled module.

func (*Compiled) Describe

func (c *Compiled) Describe(ctx context.Context, locale string) (*wire.Manifest, error)

Describe calls the guest's describe export and checks that it agrees with the installed manifest on the things a grant depends on. The file on disk is the operator's intent; the guest's answer is the proof it is the same program.

func (*Compiled) HasExport

func (c *Compiled) HasExport(ctx context.Context, export string) (bool, error)

HasExport reports whether the module exports a function by that name.

It costs one instantiation, which is why it is asked at load rather than per call: the alternative is discovering an hour later, in a log, that an app which asked to be woken has nothing to wake.

type DryRunAnswer

type DryRunAnswer struct {
	Manifest    *wire.Manifest  `json:"manifest"`
	Permissions []PermissionRow `json:"permissions"`
	// WasmSHA256 / WasmBytes describe the module; both empty for a language
	// pack, which has none (ManifestSHA256 is what was verified instead).
	WasmSHA256 string `json:"wasm_sha256"`
	WasmBytes  int64  `json:"wasm_bytes"`
	Signed     bool   `json:"signed"`
	// Kind: KindApp | KindLanguagePack. The review says which BEFORE the
	// install, so an administrator is not asked to trust a module that is
	// not there, or surprised by one that is.
	Kind           string `json:"kind"`
	ManifestSHA256 string `json:"manifest_sha256,omitempty"`
	// Languages: what the app would add to filex itself — coverage of the
	// current catalogue, and whether each is written right to left.
	Languages []LanguageRow `json:"languages,omitempty"`
	// Installed is the app of the same name already on this server (an
	// install cannot proceed; an upgrade of it can). Nil on an upgrade's own
	// dry run, and when nothing of that name is installed.
	Installed *DryRunInstalled `json:"installed,omitempty"`
	// EnginesMissing are the engines the manifest asks for that this server
	// does not have: the app installs, and whatever needs them will not work.
	EnginesMissing []DryRunEngine `json:"engines_missing,omitempty"`
}

DryRunAnswer is what a dry run returns for the review step.

⚠ Installed and EnginesMissing are said HERE, at the review, because the dry run already knows them. Before, reinstalling an app that was already there passed the review and only "Install" answered "an app with this name is already installed", and an app needing an engine this server lacks was reviewed as if it would work (release-candidate sweep, 2026-09-21).

type DryRunEngine

type DryRunEngine struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

DryRunEngine is one engine by its id and by the name a person reads.

type DryRunInstalled

type DryRunInstalled struct {
	ID      int64  `json:"id"`
	Version string `json:"version"`
}

DryRunInstalled names the installed app a new install would collide with.

type GatedRule

type GatedRule struct {
	Ext   []string `json:"ext,omitempty"`
	Needs Need     `json:"needs"`
}

GatedRule is one "offered once X is there" part of an action's rule.

type GitHubInput

type GitHubInput struct {
	Repo string `json:"github_repo"`
	Ref  string `json:"ref"`
}

GitHubInput is the JSON body of a GitHub install.

type Grants

type Grants map[Permission]bool

Grants is the set an administrator approved.

func NewGrants

func NewGrants(perms []Permission) Grants

NewGrants builds a set from validated permissions.

func (Grants) AllowedHosts

func (g Grants) AllowedHosts() []string

AllowedHosts lists the http:* grants as Extism allowed_hosts patterns.

func (Grants) Has

func (g Grants) Has(p Permission) bool

Has answers a bare permission.

func (Grants) HasEngine

func (g Grants) HasEngine(name string) bool

HasEngine answers engines:<name>.

func (Grants) HasHost

func (g Grants) HasHost(host string) bool

HasHost answers whether an outbound HTTP request to host is allowed: an exact "http:host" grant, or a "http:*.domain" grant covering a subdomain.

func (Grants) Missing

func (g Grants) Missing(want []Permission) []Permission

Missing returns the permissions in want that are not in g — what an upgrade must ask the administrator to approve again.

type HostFunc

type HostFunc = extism.HostFunction

HostFunc is a host function the runtime links into every instance. The registry builds them (hostfn.go); tests pass a few of their own.

type InstallError

type InstallError struct {
	Code    string
	Message string
	Missing []string
	Reason  string   // a FetchReason* for fetch_failed, else ""
	Where   string   // the repository or the URL that was being fetched
	Refs    []string // the refs a repository install tried
	Status  int      // the HTTP status that came back, when one did
}

InstallError is a failure caused by what the caller supplied. Code is what the HTTP layer answers; Missing carries the permission diff for permissions_incomplete / permissions_changed.

Reason, Where, Refs and Status narrow a fetch_failed, so the install wizard can write a sentence in the reader's language that says WHAT to check. ⚠ The message alone used to be the answer: "filex-app.json not found in BRF-Tech/yok-boyle-bir-depo: http 404 from raw.githubusercontent.com", English, in the Turkish wizard (release-candidate sweep, 2026-09-21).

func (*InstallError) Error

func (e *InstallError) Error() string

type InstallInput

type InstallInput struct {
	Manifest []byte
	// Wasm is the module; Reader is consumed, capped at MaxWasmBytes.
	Wasm io.Reader
	// SHA256 (lower hex) the module must match; "" skips (upload only).
	SHA256    string
	Signature string
	Source    string
	SourceURL string
	// Granted is the permission list the admin approved. It must equal the
	// manifest's set exactly.
	Granted []string
	DryRun  bool
	Lang    string
}

InstallInput is one install or upgrade request after the HTTP layer has gathered the pieces.

type Installed

type Installed struct {
	Row      *model.AppPlugin
	Manifest *Manifest
	Grants   Grants
	// Perms is the grant as a sorted list, for answers.
	Perms []Permission
	// contains filtered or unexported fields
}

Installed is one plugin's live entry.

func (*Installed) State

func (p *Installed) State() (string, string)

State returns the live state and its error.

type Item

type Item struct {
	Kind string // file | dir
	Ext  string // lower-case, no dot
	Mime string
	// State is the set of state keys the plugin under consideration keeps
	// on this item (bare keys). Filled per plugin by the caller.
	State []string
}

Item is what Matches sees of a selected entry.

func (Item) HasState

func (it Item) HasState(key string) bool

HasState reports whether the item carries key.

type JobOutput

type JobOutput struct {
	Path string `json:"path"`
}

JobOutput is one committed output, as the ops row reports it.

type JobQueue

type JobQueue interface {
	SubmitTo(ctx context.Context, kind string, storageID, destStorageID int64, sources []string, dest string) (*ops.Op, error)
}

JobQueue is the ops queue, as the scheduler needs it: a way to hand an app_plugin_jobs row to the same worker a person's job goes through. *ops.Service satisfies it; the server injects it with SetJobQueue.

Without it a due item fails loudly (status `failed`, the reason on the row) rather than disappearing.

type LanguageRow

type LanguageRow struct {
	Code string `json:"code"`
	// Keys the pack carries for this language (empty values not counted).
	Keys int `json:"keys"`
	// Translated is how many of those the current catalogue has; Unknown is
	// the rest — keys filex no longer has, or never had (typos). Both are
	// only meaningful when Total > 0.
	Translated int `json:"translated"`
	Unknown    int `json:"unknown"`
	// Total is the size of the current catalogue; 0 means this binary was
	// built without one (a development build), and coverage is unknown.
	Total int `json:"total"`
	// Percent is floor(100 × Translated / Total): 100 only when nothing is
	// missing, so "100 %" is never a rounding of "almost".
	Percent int `json:"percent"`
	// RTL: a right-to-left language (wire.IsRTL) — the interface is laid out
	// right to left in it.
	RTL bool `json:"rtl"`
}

LanguageRow is one language an app adds, as the Apps screens show it.

⚠ Coverage is measured against the CURRENT catalogue — the one this very binary's interface draws from — not against whatever the pack's author had when they wrote it. A pack written for v0.43 and run on v0.45 says how much of v0.45 it covers, and every string it lacks falls back to English.

type LogLine

type LogLine struct {
	Seq   int64     `json:"seq"`
	TS    time.Time `json:"ts"`
	Level string    `json:"level"`
	Msg   string    `json:"msg"`
}

LogLine is one entry of a plugin's ring buffer: what the guest logged through pdk.Log plus the host's own warnings about it (refused calls, failed loads). The admin panel polls it with ?after=<seq>.

type Manifest

type Manifest struct {
	wire.Manifest
	// Perms is Permissions parsed and validated.
	Perms []Permission
}

Manifest is a validated filex-app.json.

func ParseManifest

func ParseManifest(data []byte) (*Manifest, error)

ParseManifest decodes and validates a manifest document.

func (*Manifest) Action

func (m *Manifest) Action(id string) (*wire.Action, bool)

Action finds an action by id.

func (*Manifest) CallTimeout

func (m *Manifest) CallTimeout() int

CallTimeout is the clamped per-call budget for describe/view/page calls.

func (*Manifest) Grants

func (m *Manifest) Grants() Grants

Grants is the full permission set the manifest asks for.

func (*Manifest) MemoryPages

func (m *Manifest) MemoryPages() int

MemoryPages is the clamped wasm memory ceiling.

func (*Manifest) Validate

func (m *Manifest) Validate() error

Validate checks the shape and fills Perms.

func (*Manifest) View

func (m *Manifest) View(id string) (*wire.View, bool)

View finds a view by id.

func (*Manifest) WritesElsewhere

func (m *Manifest) WritesElsewhere() bool

WritesElsewhere reports whether any action may put its result into a folder the person chooses (output.elsewhere) — what makes the host tell the app where that person's results go by default (CallContext.Home).

type Need

type Need struct {
	Kind string `json:"kind"`
	ID   string `json:"id"`
	Name string `json:"name"`
}

Need is a requirement the server does not meet: `kind` says which sort ("engine" today), `id` the machine's word, `name` the one a person reads.

type Options

type Options struct {
	Store db.Store
	// Share mints the public links app plugins open. An app's public page IS
	// a share (migration 00046), so it goes through the same service every
	// other link does and inherits the instance's expiry ceiling, the PIN
	// gate and the administrator's revoke. nil refuses share_create.
	Share *share.Service
	// Dir is where modules live; the compilation cache and the call spool
	// sit next to it.
	Dir string
	// SecretKey seals secret settings (secretbox). Empty → secret fields are
	// refused at PutSettings rather than stored in the clear.
	SecretKey string
	// TrustedKeys (hex/base64 ed25519) make a detached signature mandatory.
	TrustedKeys []string
	// Demo refuses every install/upgrade (the admin account is public).
	Demo bool
	// HTTP fetches manifests and modules for URL/GitHub installs.
	HTTP *http.Client
	Log  *slog.Logger
	// StorageResolver opens the driver a job reads from and writes to.
	StorageResolver func(int64) (storage.Driver, error)
	// Limits (bytes). Zero → defaults below.
	MaxWasmBytes   int64
	MaxInputBytes  int64
	MaxOutputBytes int64
	// PerPluginJobs bounds concurrent action_run calls per plugin (default 2).
	PerPluginJobs int
}

Options configures the registry.

type OutputSink

type OutputSink interface {
	// CommitSibling writes a NEW file next to the first input, taking a
	// unique name when the preferred one is taken. Returns the final path.
	CommitSibling(ctx context.Context, storageID int64, dir, name string, r io.Reader, size int64, actor *int64) (string, error)
	// CommitVersion overwrites rel in place (the previous bytes become a
	// version when versioning is on).
	CommitVersion(ctx context.Context, storageID int64, rel string, r io.Reader, size int64, actor *int64) error
}

OutputSink lands a finished output on a storage with filex's own bookkeeping (kind guard, version snapshot on overwrite, node row, index, thumbnail, write hook, change frame). Implemented by the HTTP layer, which owns those services; the registry only decides WHAT to write WHERE.

type OverrideRow

type OverrideRow struct {
	ID        string        `json:"id"`
	Enabled   bool          `json:"enabled"`
	AdminOnly bool          `json:"admin_only"`
	Applies   *wire.Applies `json:"applies"`
}

OverrideRow is the admin API shape of one action override. Applies is a WHOLE rule both ways — the editor shows a list and sends a list — while the row stores only what changed against the manifest (applies_override.go says why). Nil = as the manifest says.

type PageFile

type PageFile struct {
	Ref  string `json:"ref"`
	Name string `json:"name"`
	File string `json:"file"`
	Size int64  `json:"size"`
	Mime string `json:"mime,omitempty"`
}

PageFile is one exposed copy, as files_json stores it.

⚠ `File` is a BASENAME inside the share's own directory, not an absolute path. v2 stored the absolute path, so moving or restoring the data directory silently broke every exposed copy while the row looked healthy.

type PageInfo

type PageInfo struct {
	Plugin      string         `json:"plugin"`
	Page        string         `json:"page"`
	Title       wire.Text      `json:"title"`
	Subject     string         `json:"subject,omitempty"`
	RequiresPIN bool           `json:"requires_pin"`
	Unlocked    bool           `json:"unlocked"`
	ExpiresAt   *time.Time     `json:"expires_at,omitempty"`
	Files       []wire.FileRef `json:"files"`
}

PageInfo describes an app link without opening it.

type Permission

type Permission string

Permission is one capability a plugin asks for in its manifest and an administrator grants at install. The set is CLOSED: a manifest naming a permission this host does not know is refused at install, because a grant the admin cannot read is not a grant.

Two shapes: bare names ("files:read") and parameterised ones ("http:api.example.com", "engines:ffmpeg"). The parameter is part of the permission — granting "http:a.example.com" says nothing about b.example.com.

const (
	PermFilesRead   Permission = "files:read"
	PermFilesWrite  Permission = "files:write"
	PermFilesLock   Permission = "files:lock"
	PermSign        Permission = "sign"
	PermMailSend    Permission = "mail:send"
	PermNotifySend  Permission = "notify:send"
	PermUsersLookup Permission = "users:lookup"
	PermSettings    Permission = "settings"
	PermState       Permission = "state"
	PermPublicPages Permission = "public_pages"
	// PermSchedule is the one permission that makes an app run when nobody
	// asked it to: it is woken once an hour and runs the work it scheduled
	// at the minute it named. An app without it is never woken, and there
	// is no other way in — see schedule.go.
	PermSchedule Permission = "schedule"
)

Bare permissions.

func ParsePermission

func ParsePermission(s string) (Permission, error)

ParsePermission validates one manifest entry.

func (Permission) Label

func (p Permission) Label(lang string) string

Label is the plain-language meaning shown at install, in lang — any language the server catalogue speaks, a language pack's included (keys `server.perm.*`, internal/srvtext). It serves the API (`GET /api/admin/ app-plugins/{id}` → permission review) and the CLI.

⚠ This is what an administrator reads before trusting an app with their files. It was English or Turkish — a Spanish administrator read the one screen that asks for consent in English, around a Spanish wizard. A key a pack lacks falls back to English, never to the permission id.

type PermissionRow

type PermissionRow struct {
	ID     string    `json:"id"`
	Label  string    `json:"label"`
	Reason wire.Text `json:"reason,omitempty"`
}

PermissionRow is one line of the install-time review.

func PermissionRows

func PermissionRows(m *Manifest, lang string) []PermissionRow

PermissionRows renders the review for a manifest in lang.

type Registry

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

Registry is the installed set plus the runtime.

func New

func New(o Options) (*Registry, error)

New prepares the registry (no plugin is loaded until Load).

func (*Registry) ActionsFor

func (r *Registry) ActionsFor(ctx context.Context, isAdmin bool) (*ActionsAnswer, error)

ActionsFor lists what the caller may see: running plugins, enabled actions, admin-only ones only for admins.

func (*Registry) All

func (r *Registry) All() []*Installed

All returns every entry, by name. (The languages the running apps add to filex itself — UILocaleList / UILocale — live in langpack.go.)

func (*Registry) AppLabel

func (r *Registry) AppLabel(name string) wire.Text

AppLabel is an installed app's own label, in every language its manifest wrote it in ({"en": "e-Signature", "tr": "e-İmza"}), or nil when no app of that name is installed here.

⚠ A lock, an action row and a job all carry the app's manifest NAME (`sign`), because that is what addresses it; a person reading "sign locked this file" is reading an identifier the rest of the product never shows them — every list, the install review and the app's own page say "e-Signature" (v0.43.0). So the words a person reads resolve the name to this, and fall back to the name only where the app is gone.

func (*Registry) ByID

func (r *Registry) ByID(id int64) (*Installed, bool)

ByID returns the entry for a row id.

func (*Registry) ByName

func (r *Registry) ByName(name string) (*Installed, bool)

ByName returns the entry for a plugin name.

func (*Registry) CACertPEM

func (r *Registry) CACertPEM(ctx context.Context, tenantID int64) (string, error)

CACertPEM returns the tenant CA certificate (creating the CA on first use).

func (*Registry) CACertsPEM

func (r *Registry) CACertsPEM(ctx context.Context, tenantID int64) (string, error)

CACertsPEM returns EVERY certificate this tenant has ever signed with — the live CA first, then the retired ones, newest first — as one PEM bundle. This is the root set a verifier needs: a signature made two CAs ago must still check out, so nothing here is ever deleted.

func (*Registry) CAs

func (r *Registry) CAs(ctx context.Context, tenantID int64) ([]CAList, error)

CAs lists the tenant's signing authorities, live and retired, for the admin panel and the verify screen: who signed, with what, until when.

func (*Registry) CheckPIN

func (r *Registry) CheckPIN(ctx context.Context, sh *model.Share, pin string) error

CheckPIN verifies a PIN with the strike counter. A one-line forward: the gate itself is internal/share/pin.go, so /s/, /d/ and an app page cannot drift apart — which is exactly what v2 did (only the app page had a lock).

func (*Registry) Close

func (r *Registry) Close(ctx context.Context)

Close frees every compiled module and the runtime.

func (*Registry) DecorateOps

func (r *Registry) DecorateOps(ctx context.Context, rows []*ops.Op)

DecorateOps fills the plugin fields of ops rows that are plugin jobs. DecorateOps fills the plugin columns of ops rows. A read failure is logged rather than swallowed: the row still renders, but as a nameless "plugin-action" with no message, which is how two different lost-update bugs stayed invisible.

func (*Registry) Dir

func (r *Registry) Dir() string

Dir is where modules live.

func (*Registry) Engines

func (r *Registry) Engines() map[string]bool

Engines is the engine → installed map for this host.

func (*Registry) FetchGitHub

func (r *Registry) FetchGitHub(ctx context.Context, in GitHubInput) (*InstallInput, error)

FetchGitHub resolves a repository + ref into an InstallInput (manifest and module fetched, sha256 taken from the manifest unless overridden).

func (*Registry) FetchURL

func (r *Registry) FetchURL(ctx context.Context, in URLInput) (*InstallInput, error)

FetchURL resolves explicit module + manifest addresses.

func (*Registry) ImportCA

func (r *Registry) ImportCA(ctx context.Context, tenantID int64, certPEM, keyPEM []byte) (*signingKey, error)

ImportCA takes over signing with an authority the operator already has — their own PKI, or a certificate they trust everywhere else. The current CA is retired, not deleted, so everything it signed keeps verifying.

certPEM may be a bundle: the signing certificate first, its issuers after it. keyPEM is that certificate's private key, in PKCS#8, PKCS#1 or SEC1 form, NOT encrypted — a .p12/.pfx is converted first (`openssl pkcs12 -in ca.p12 -nodes -out ca.pem`), because the encrypted forms in the wild are more varied than any one library reads.

func (*Registry) Install

func (r *Registry) Install(ctx context.Context, in *InstallInput) (*Status, *DryRunAnswer, error)

Install stages, reviews and installs a plugin.

func (*Registry) InstanceID

func (r *Registry) InstanceID() string

InstanceID is what this process writes into claimed_by.

func (*Registry) LanguageRows

func (r *Registry) LanguageRows(m *Manifest) []LanguageRow

LanguageRows describes every language m adds, with its coverage. Nil-safe on the receiver (no catalogue → coverage unknown).

func (*Registry) LinkOf

func (r *Registry) LinkOf(sh *model.Share) *db.AppLink

LinkOf says what an app's link IS, for a list of links (My shares, the admin's Shares): the purpose the app gave the link when it opened it, else the one its page declares, and the app's home view the row opens. Nil for an ordinary share, for an app that is gone, and for a link with no purpose — the row then lists as a plain share.

⚠ Page-less links too (an app's plain share of a file: PluginID set, no PageID). The finished document's delivery link listed as a share somebody made by hand, beside signing links marked as what they are.

func (*Registry) Load

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

Load reads every row and compiles the enabled ones. A row that fails to load is kept (state failed/refused) so the admin sees why; it never stops the server.

func (*Registry) LoadPage

func (r *Registry) LoadPage(ctx context.Context, token string) (*model.Share, *Installed, error)

LoadPage resolves a token to the live app share behind it (ErrPageNotFound / ErrPageGone).

⚠ A token that names an ordinary share is ErrPageNotFound here, not an error about kinds: this is an unauthenticated lookup and "wrong sort of link" is a fact about somebody else's data.

func (*Registry) LockReason

func (r *Registry) LockReason(l *model.AppPluginLock) (string, wire.Text)

LockReason is a lock's reason as words: the plain text (English, for a consumer with no reader — a log, a protocol server) and, for a reason the app named by message key, the same words in every language the app wrote them in. A plain reason has no Text; a message whose app or key is gone has no words at all (never the raw key).

func (*Registry) Locks

func (r *Registry) Locks(ctx context.Context, storageID int64) ([]*model.AppPluginLock, error)

Locks lists every live app-plugin lock (admin surface). storageID 0 = all.

func (*Registry) Logs

func (r *Registry) Logs(id int64, after int64) ([]LogLine, int64, error)

Logs returns the ring lines after seq.

func (*Registry) LookupUsers

func (r *Registry) LookupUsers(ctx context.Context, q string) ([]UserRow, error)

LookupUsers is the one directory search behind both the host function and the HTTP route. ctx must already carry the caller's tenant scope (the store is the tenant-scoped wrapper, which confines ListUsers by that scope).

func (*Registry) MintUnlock

func (r *Registry) MintUnlock(token string) string

MintUnlock / VerifyUnlock forward to the same one implementation.

func (*Registry) Overrides

func (r *Registry) Overrides(ctx context.Context, id int64) ([]OverrideRow, error)

Overrides returns one row per manifest action, merged with stored changes.

func (*Registry) PageAnchor

func (r *Registry) PageAnchor(ctx context.Context, sh *model.Share) (int64, string)

PageAnchor reports the storage and storage-relative path the link is about — what per-file state and the follow-up job hang on. Zero/"" when the node has gone.

func (*Registry) PageEvent

func (r *Registry) PageEvent(ctx context.Context, sh *model.Share, p *Installed, in wire.ViewEventInput, visitorIP string) (*wire.Surface, error)

PageEvent runs the link's page_event export for an unlocked visitor. "open" spends one visit off the share's own counter. A surface carrying a job is returned as-is; the HTTP layer queues it as the share's creator.

func (*Registry) PageFile

func (r *Registry) PageFile(sh *model.Share, ref string) (*PageFile, *os.File, error)

PageFile opens an exposed copy for the visitor (Range-capable).

func (*Registry) PageInfo

func (r *Registry) PageInfo(sh *model.Share, p *Installed, unlocked bool) *PageInfo

PageInfo describes a link without opening it.

func (*Registry) PutOverrides

func (r *Registry) PutOverrides(ctx context.Context, id int64, rows []OverrideRow) error

PutOverrides stores the rows (unknown and hidden action ids dropped, applies validated).

func (*Registry) PutSettings

func (r *Registry) PutSettings(ctx context.Context, id int64, values map[string]string) error

PutSettings stores values. A secret field sent as "***" keeps its current value; other secret values are sealed. Unknown keys are dropped.

func (*Registry) Remove

func (r *Registry) Remove(ctx context.Context, id int64) error

Remove uninstalls a plugin: module, row and everything keyed on it.

func (*Registry) RequiresSignature

func (r *Registry) RequiresSignature() bool

RequiresSignature reports whether installs must carry a signature.

func (*Registry) ResolveAction

func (r *Registry) ResolveAction(ctx context.Context, plugin, action string, isAdmin bool) (*Installed, *wire.Action, wire.Applies, error)

ResolveAction finds a running plugin's enabled action for a run request. isAdmin gates admin-only actions. The error is a *CallError with CodeUnsupported (not running / disabled) or CodeRefused (admin only).

func (*Registry) RotateCA

func (r *Registry) RotateCA(ctx context.Context, tenantID int64) error

RotateCA retires the tenant CA; the next signature creates a fresh one. Leaves issued by the old CA stay verifiable through its certificate, which is why retiring never deletes.

func (*Registry) RunDueSchedule

func (r *Registry) RunDueSchedule(ctx context.Context, now time.Time) (int, error)

RunDueSchedule performs ONE pass: every row due at or before now, claimed and run. Exported so a test — and, later, an administrator's "run it now" — can drive the schedule without the loop.

A plugin that traps, panics or hangs takes down its own row and nothing else: each row runs behind a recover, and the call itself is bounded by the wasm runtime's own per-call budget.

func (*Registry) RunPluginAction

func (r *Registry) RunPluginAction(ctx context.Context, op *ops.Op, live func(done, total int64)) error

RunPluginAction is what the ops worker calls for an OpPluginAction row. op.Dest carries the job id. Progress is fed to live so the tray can draw a bar; the job row records status, message and outputs.

func (*Registry) ScheduleOf

func (r *Registry) ScheduleOf(ctx context.Context, id int64) ([]*model.AppPluginScheduleItem, error)

ScheduleOf returns one app's schedule rows, soonest first — what an admin screen would draw and what a test reads back.

func (*Registry) SetCatalogue

func (r *Registry) SetCatalogue(keys []string)

SetCatalogue gives the registry the current string catalogue — every key of the explorer's and the admin panel's English tables — so the Apps screens can say how much of it a language covers.

⚠ Called with the keys of `admin/i18n/filex-catalogue-en.json`, which the web build writes (web/vite.config.ts → scripts/lib/i18n-catalogue.mjs) and the binary embeds. A binary built without the web build has none, and coverage then reads "unknown" rather than a made-up percentage.

func (*Registry) SetEnabled

func (r *Registry) SetEnabled(ctx context.Context, id int64, on bool) (*Status, error)

SetEnabled flips a plugin on or off, loading or unloading it.

func (*Registry) SetHTTPTransport

func (r *Registry) SetHTTPTransport(t http.RoundTripper)

SetHTTPTransport replaces the transport http_request uses (tests).

func (*Registry) SetHomeResolver

func (r *Registry) SetHomeResolver(f func(ctx context.Context, u *model.User) string)

SetHomeResolver wires the answer to "where do this person's results go when not beside their source" (CallContext.Home) — the HTTP layer, which knows storages, tenants and the ACL.

func (*Registry) SetJobQueue

func (r *Registry) SetJobQueue(q JobQueue)

SetJobQueue wires the queue scheduled work is handed to.

func (*Registry) SetMailer

func (r *Registry) SetMailer(m mailSink)

SetMailer wires the mailer (nil → mail_send unavailable).

func (*Registry) SetNotify

func (r *Registry) SetNotify(n notifySink)

SetNotify wires the notification service (nil → notify_send unavailable).

func (*Registry) SetOrigins

func (r *Registry) SetOrigins(fn func(ctx context.Context, storageID int64) string)

SetOrigins records how an absolute link's origin is resolved when NO request is in scope — a plugin job runs on the queue, long after the browser left. The tenant then comes from the data (the share's storage), never from a guess: internal/tenanturl.Resolver.ForStorage is what is wired here. Without it a customer's signature request would carry the OPERATOR's hostname.

func (*Registry) SetOutputSink

func (r *Registry) SetOutputSink(s OutputSink)

SetOutputSink wires the committer that lands job outputs on a storage.

func (*Registry) SetPublicURL

func (r *Registry) SetPublicURL(base string)

SetPublicURL records the base the visitor link is built on. It is the fallback; SetOrigins is what a multi-tenant install answers with.

func (*Registry) SetStorageResolver

func (r *Registry) SetStorageResolver(fn func(int64) (storage.Driver, error))

SetStorageResolver wires (or replaces) the driver resolver jobs read through; the server builds it after the registry.

func (*Registry) SetUserScope

func (r *Registry) SetUserScope(fn func(ctx context.Context, u *model.User) context.Context)

SetUserScope wires the function that puts the acting user's tenant scope on a job context, so users_lookup from a queued job sees the same directory the user's own requests see. Nil = single-tenant, unscoped.

func (*Registry) SetVisibility

func (r *Registry) SetVisibility(fn func(ctx context.Context, u *model.User, storageID int64, rel string) bool)

SetVisibility wires the check state_list runs on every row: a plugin may keep state on a file the person asking has no right to see, and a listing is not a way around the ACL. Nil = everything passes.

func (*Registry) Settings

func (r *Registry) Settings(ctx context.Context, id int64) (map[string]string, []wire.Field, error)

Settings returns the stored values with secret ones masked as "***".

func (*Registry) StartScheduler

func (r *Registry) StartScheduler(ctx context.Context)

StartScheduler runs the schedule until ctx ends. Call it once at boot, after the job queue is wired.

It does not poll on a fixed beat: after each pass it sleeps until the next row is actually due, capped at schedulePollCeiling so a row another process wrote is picked up too. That is what makes "to the minute" true — an item due at 03:00:00 runs at 03:00:00, not at the next tick of a one-minute timer.

func (*Registry) StartSweeper

func (r *Registry) StartSweeper(ctx context.Context)

StartSweeper runs SweepPages every hour until ctx ends.

func (*Registry) StatusOf

func (r *Registry) StatusOf(p *Installed) *Status

StatusOf builds the list row. ⚠ Nil-safe receiver: the wire-fixture test calls it on a nil registry, which then reports languages without coverage.

func (*Registry) StorageName

func (r *Registry) StorageName(ctx context.Context, id int64) string

StorageName is a storage's name, or "" when it is gone (or unreadable) — what the panel prints beside a lock instead of the storage's id.

func (*Registry) SweepPages

func (r *Registry) SweepPages(ctx context.Context) int

SweepPages removes the exposed copies of links that are gone. Called hourly by the server; safe to call any time.

⚠ It walks the DIRECTORIES rather than querying for dead rows, because a directory can outlive its row: DeleteShare (the administrator's hard delete) leaves nothing to join against, and those copies would then sit on disk until somebody noticed. A directory is removed when its share is gone, or when the share has been dead for longer than the grace period.

func (*Registry) SweepSchedule

func (r *Registry) SweepSchedule(ctx context.Context) int64

SweepSchedule drops finished rows whose due time is past the retention window. Called once an hour from the schedule loop.

It measures on due_at rather than updated_at on purpose: due_at is always written from Go and updated_at from the engine's CURRENT_TIMESTAMP, and comparing a Go time against an engine-formatted column is how a sweep silently stops matching anything.

func (*Registry) Tick

func (r *Registry) Tick(ctx context.Context, p *Installed, now, windowEnd time.Time) (*wire.TickOutput, error)

Tick wakes one plugin and returns what it wants done. Exported so a test (and a future "run it now" button) can drive one wake-up on its own.

The call gets a READ-ONLY scope with no storage and no actor: the same shape a screen gets. It may read settings and its own state, look people up, notify, mail and make granted HTTP calls; it may not write files, write state, take locks or sign. Those belong in the actions it schedules, which run as real jobs with a real scope.

func (*Registry) UILocale

func (r *Registry) UILocale(code string) map[string]string

UILocale is one language's strings, merged over every running app that ships it; nil when none does.

⚠ First app (by name) wins a contested key — arbitrary, but stable across restarts, which is what matters to somebody reading the screen. ⚠ An empty value is left out rather than served: an app that has not translated a string yet must fall back to English, not blank it.

func (*Registry) UILocaleCodes

func (r *Registry) UILocaleCodes() []string

UILocaleCodes lists the languages the running apps add, sorted — the tags the server's catalogue may answer in besides English and Turkish.

func (*Registry) UILocaleList

func (r *Registry) UILocaleList() []UILocaleInfo

UILocaleList is every language the running apps add, by tag.

func (*Registry) UIString

func (r *Registry) UIString(code, key string) (string, bool)

UIString is ONE key of one language, under the same rules as UILocale (running apps only, first app by name wins, an empty value is no value) — without building the whole ~3 000-key map. It is how the server's own catalogue (internal/srvtext) reads a pack: a mail or a public page asks for a few dozen keys, and UILocale would merge every string of every pack for each of them.

func (*Registry) Unlock

func (r *Registry) Unlock(ctx context.Context, storageID int64, rel string) (bool, error)

Unlock lifts a lock by force — the administrator's override for a plugin that will never come back to lift it. Returns false when nothing was locked there.

func (*Registry) Upgrade

func (r *Registry) Upgrade(ctx context.Context, id int64, in *InstallInput) (*Status, *DryRunAnswer, error)

Upgrade replaces the module + manifest of an installed plugin. A manifest that asks for a permission not yet granted answers permissions_changed until the request grants it; a narrower manifest simply narrows the grant.

func (*Registry) VerifyUnlock

func (r *Registry) VerifyUnlock(token, value string) bool

VerifyUnlock reports whether an unlock cookie belongs to this link.

func (*Registry) ViewEvent

func (r *Registry) ViewEvent(ctx context.Context, plugin, view string, storageID int64, rels []string, actor *model.User, locale string, in wire.ViewEventInput) (*wire.Surface, error)

ViewEvent runs one view event and returns the sanitised surface.

func (*Registry) WakeSoon

func (r *Registry) WakeSoon(ctx context.Context, pluginID int64, why string)

WakeSoon brings an app's hourly wake-up forward to a few seconds from now.

It is how the host tells an app that something it cannot see happened to one of its own things — today, a PERSON ended one of its links from the Shares screen (handlers: appLinkEnded). ⚠⚠ It carries no event and no payload, on purpose: the app is not told WHAT happened, it is woken, and its `tick` asks the host about its own links (`share_state` → the link's facts) exactly as it does every hour. One mechanism — the wake-up — and one way of learning — asking — rather than a revoke callback living beside them with its own delivery, retry and ordering rules. Without the nudge the same wake-up still notices within the hour; the nudge only makes it prompt.

A no-op for an app without the `schedule` grant, a stopped app and a demo instance — each of those is simply not woken, which is what they already were.

type Runtime

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

Runtime owns the compilation cache and the per-host settings.

func NewRuntime

func NewRuntime(cacheDir string) (*Runtime, error)

New prepares a runtime. cacheDir holds compiled modules; "" disables the on-disk cache (tests).

func (*Runtime) Close

func (r *Runtime) Close(ctx context.Context) error

Close releases the compilation cache.

func (*Runtime) Compile

func (r *Runtime) Compile(ctx context.Context, wasmPath, sha256Hex string, m *Manifest, fns []HostFunc, logger func(level, msg string)) (*Compiled, error)

Compile validates the module against sha256 (lower-hex; "" skips the check) and compiles it with the given host functions.

type Scope

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

Scope is one call's ticket. Build with newScope; close with Close.

func (*Scope) AddInput

func (s *Scope) AddInput(rel string, size int64, mime string) wire.FileRef

AddInput registers a storage file the call may read.

func (*Scope) Close

func (s *Scope) Close()

Close shuts every open handle and removes the spool.

⚠ It also abandons any share this call PROMISED and did not keep (a job that failed, an output the plugin never named). Nothing has to be undone in the database — a promise is the absence of a row — but the copies staged for it are removed here, on every exit path, rather than left for the sweeper.

func (*Scope) Inputs

func (s *Scope) Inputs() []wire.FileRef

Inputs lists the registered inputs in order.

func (*Scope) Output

func (s *Scope) Output(ref string) (*scopeFile, bool)

Output returns a plugin-created file by ref, for committing.

type Status

type Status struct {
	ID          int64     `json:"id"`
	Name        string    `json:"name"`
	Version     string    `json:"version"`
	Label       wire.Text `json:"label"`
	Description wire.Text `json:"description,omitempty"`
	Icon        string    `json:"icon,omitempty"`
	Homepage    string    `json:"homepage,omitempty"`
	Enabled     bool      `json:"enabled"`
	State       string    `json:"state"`
	StateError  string    `json:"state_error,omitempty"`
	Source      string    `json:"source"`
	SourceURL   string    `json:"source_url,omitempty"`
	SHA256      string    `json:"sha256"`
	Signed      bool      `json:"signed"`
	Permissions []string  `json:"permissions"`
	// Scheduled says this app is woken once an hour and runs work of its
	// own choosing — the one thing on this row that happens with nobody
	// present, so the list can mark it.
	Scheduled   bool `json:"scheduled"`
	Actions     int  `json:"actions"`
	Views       int  `json:"views"`
	PublicPages int  `json:"public_pages"`
	// Kind is KindApp or KindLanguagePack (langpack.go). A language pack has
	// no module, so `sha256` is the hash of its MANIFEST — the thing that was
	// verified and, on an instance that requires signatures, signed.
	Kind string `json:"kind"`
	// Languages are the languages this app adds to filex itself, each with
	// how much of the CURRENT catalogue it covers. Absent when it adds none.
	Languages []LanguageRow `json:"languages,omitempty"`
	CreatedAt time.Time     `json:"created_at"`
	UpdatedAt time.Time     `json:"updated_at"`
}

Status is one plugin as the admin list shows it.

type UILocaleInfo

type UILocaleInfo struct {
	Code string `json:"code"`
	// Source is always "plugin" here; the field is the client's
	// PublicLocaleOption, which also describes the built-in languages.
	Source string `json:"source"`
	// Plugin names the app that brought it (the first, by name, when several
	// do) — shown beside the language so nobody wonders where it came from.
	Plugin string `json:"plugin"`
	RTL    bool   `json:"rtl,omitempty"`
}

UILocaleInfo is one language the running apps add, as GET /api/public/branding lists it. ⚠ No strings: those are fetched for the one language a visitor actually picks (UILocale, GET /api/public/ui-locales/ {code}). A complete language is ~150 KB of JSON, and the branding answer is read by every public page and every panel start.

type URLInput

type URLInput struct {
	URL         string `json:"url"`
	ManifestURL string `json:"manifest_url"`
	SHA256      string `json:"sha256"`
	Signature   string `json:"signature"`
}

URLInput is the JSON body of a URL install.

type UserRow

type UserRow struct {
	// UserID, not `id`: the row doubles as a people-picker VALUE, whose
	// shape the surface contract fixes as {user_id, email, name}.
	UserID int64  `json:"user_id"`
	Email  string `json:"email"`
	Name   string `json:"name,omitempty"`
}

UserRow is what users_lookup and GET /api/files/plugins/users return: the directory entry a picker needs and nothing else (no role, no flags).

type ViewRow

type ViewRow struct {
	Plugin    string       `json:"plugin"`
	ID        string       `json:"id"`
	Placement string       `json:"placement"`
	Label     wire.Text    `json:"label"`
	Icon      string       `json:"icon,omitempty"`
	Applies   wire.Applies `json:"applies"`
}

ViewRow is one non-modal view (inspector/home) the explorer may offer.

Jump to

Keyboard shortcuts

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