Wheelhouse docs

Catalogue schema#

The app catalogue is one JSON document, agent/catalog.json, embedded in the agent binary at compile time. It is data rather than code so it can be extended without touching the agent's logic — but it ships inside the binary, which means a corrected entry needs an agent release. There is no signed index and nothing fetches a catalogue over the network.

bash
curl -sk "$R/api/apps/catalog" -H "Authorization: Bearer $T"

The document#

FieldTypeMeaning
versionstringThe catalogue's own format version. Currently 1.
networkstringThe container network name every non-host-network app joins. Currently apps.
prefixstringThat network's prefix. Currently 10.99.0.0/24.
categoriesarrayThe category names, in display order. Entries are sorted by this order, then case-insensitively by name within a category.
appsarrayThe entries.

Duplicate id values are a fatal error at startup: the agent exits 1 with app catalog is invalid.

An entry#

FieldTypeMeaning
idstringUnique. What every API call names.
namestringThe display name.
categorystringOne of categories.
blurbstringOne line, for the card.
descriptionstringThe longer text on the detail page.
kindstringcontainer (the default, and what an absent field means) or builtin.
builtinobjectRequired when kind is builtin. See below.
imagestringThe container image reference.
homepagestringThe project's own site.
tagsarraySearch keywords.
host_networkboolRun on the router's own interfaces instead of the container network.
portsarraySee below.
volumesarraySee below.
environmentarraySee below.
capabilityarrayLinux capabilities to grant, each becoming set container name <n> capability <c>.
privilegedboolAdvisory only — it produces a warning in the install plan, not a configuration node.
memorystringBecomes set container name <n> memory <value>.
integrationobjectWhat the UI does with the app once it runs. See below.
notesarrayWarnings to read before committing, in the entry's own words.
closesstringNames a documented platform gap this app fills.

ports#

FieldMeaning
nameLabel.
containerThe port inside the container.
hostThe port on the router, when the app publishes one.
protocoltcp or udp.
descriptionWhat it is for.

volumes#

FieldMeaning
nameThe volume's name in the configuration tree.
sourceThe default path on the router. The operator may override it at install time.
destinationThe path inside the container.
read_onlyAdds mode ro.
fileThis bind mount is a single file, not a directory.
descriptionWhat lives there.

file matters: POST /api/apps/prepare creates what the router will refuse to commit without, and guessing from the extension created a directory named Caddyfile and left another app's configuration directory uncreated. A socket is the daemon's to create, never the agent's.

Each volume becomes three or four operations:

set container name <n> volume <name> source <path>
set container name <n> volume <name> destination <path>
set container name <n> volume <name> mode ro          # only when read_only

environment#

FieldMeaning
nameThe variable.
valueThe catalogue's default.
descriptionWhat it does.
requiredAn empty value produces a warning in the plan.
secretThe UI masks it.

A key present in the install request wins even when empty — that is how an operator clears a catalogue default. An absent key falls back to the catalogue value. An empty final value stages nothing, and warns if the variable was required.

integration#

FieldMeaning
web_ui{port, scheme, path} — where the app's own interface is, so the UI can link to it.
health{port, path} — the probe.
nav_label, nav_iconA navigation entry for the running app.
dashboardShow it on the Dashboard.
hintsIntegration offers surfaced on other pages. See Hint placeholders.

The health probe is a TCP connect, plus one HTTP GET when a path is given. It is deliberately not an app-specific API call: the product does not claim knowledge of an app's internals that it does not have. A 5xx is unhealthy; anything else that answered is healthy; a port that opens and does not speak HTTP reports port open, HTTP did not answer.

Off-router — when the agent is not running on the router it manages — a failed probe says nothing about the app, and the agent knows it: onRouter() checks whether --api-url points at loopback.

builtin#

A builtin entry is not a container at all. It is a native platform feature whose Wheelhouse page is gated by "installing" it — the plugin model, on a configuration-tree substrate.

FieldMeaning
pageThe UI page the entry enables.
config_pathThe configuration path whose presence means the feature is in use.

config_path is what stops a page being hidden while its configuration exists: a feature whose subtree is present shows regardless of the enabled list. Planning an install for a builtin entry stages nothing and says so:

This is a built-in feature, not a container — installing it enables its page instantly
and stages nothing.

Enabled builtins are remembered in the agent's own state.json, under features, not in the router's configuration — it is a UI-surface preference, not router behaviour.

What an install actually stages#

set container network apps prefix 10.99.0.0/24        # only if the network is absent
set container name <n> image <image>
set container name <n> network apps address <chosen address>
set container name <n> volume <v> source <path>
set container name <n> volume <v> destination <path>
set container name <n> environment <VAR> value <value>
set container name <n> capability <cap>
set container name <n> memory <memory>

With host networking, the address line is replaced by set container name <n> allow-host-networks and the plan warns that the app shares the router's interfaces and any port it binds is exposed on the router itself.

Nothing is applied by planning. The operations go to the Commit Bar like every other change.

The three checked fields#

CheckedRuleWhy
The container name^[-a-zA-Z0-9]+$It becomes an element of a configuration path and of an op-mode path on the router.
A volume sourceMust be absolute, and may not contain a space or any of '"`$;&|<>()\On-router the agent creates it as root; off-router it renders into a shell line for the operator to paste.
An image referenceThe OCI reference grammar, at most 255 charactersThe router hands an image name to its container runtime through a shell. An unvalidated one is a command line on the router — alpine; id > /tmp/x pulled nothing and ran.

Refusals happen where the message can name the field. The router would only say Set failed at commit.

Image pinning#

Every container entry currently uses a floating tag. Digest pinning is tooled — check-images.py --pin rewrites each image to host/repo:tag@sha256:… — but it has not been applied. Do not describe the catalogue's images as pinned or reproducible.

Re-deriving the catalogue#

bash
python3 -c '
import json
c = json.load(open("agent/catalog.json"))
print(c["version"], c["network"], c["prefix"])
for a in c["apps"]:
    print(a["id"], a.get("kind","container"), a.get("image",""))
'

See also#

Checked against#

agent/catalog.go (App, Port, Volume, EnvVar, Integration, WebUI, Probe, Hint, HintOp, BuiltinFeature, Catalog, loadCatalog, planInstall, probeApp, onRouter), agent/catalog.json, agent/apps.go (validateInstallRequest, validVolumeSource, validImageRef, handleAppPrepare, handleAppFeature, collectFeatures), agent/store.go (State.Features), agent/catalog_test.go, scripts/check-images.py, docs/apps.md.

Updated 2026-09-02 catalogue apps containers schema