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.
curl -sk "$R/api/apps/catalog" -H "Authorization: Bearer $T"The document#
| Field | Type | Meaning |
|---|---|---|
version | string | The catalogue's own format version. Currently 1. |
network | string | The container network name every non-host-network app joins. Currently apps. |
prefix | string | That network's prefix. Currently 10.99.0.0/24. |
categories | array | The category names, in display order. Entries are sorted by this order, then case-insensitively by name within a category. |
apps | array | The entries. |
Duplicate id values are a fatal error at startup: the agent exits 1 with
app catalog is invalid.
An entry#
| Field | Type | Meaning |
|---|---|---|
id | string | Unique. What every API call names. |
name | string | The display name. |
category | string | One of categories. |
blurb | string | One line, for the card. |
description | string | The longer text on the detail page. |
kind | string | container (the default, and what an absent field means) or builtin. |
builtin | object | Required when kind is builtin. See below. |
image | string | The container image reference. |
homepage | string | The project's own site. |
tags | array | Search keywords. |
host_network | bool | Run on the router's own interfaces instead of the container network. |
ports | array | See below. |
volumes | array | See below. |
environment | array | See below. |
capability | array | Linux capabilities to grant, each becoming set container name <n> capability <c>. |
privileged | bool | Advisory only — it produces a warning in the install plan, not a configuration node. |
memory | string | Becomes set container name <n> memory <value>. |
integration | object | What the UI does with the app once it runs. See below. |
notes | array | Warnings to read before committing, in the entry's own words. |
closes | string | Names a documented platform gap this app fills. |
ports#
| Field | Meaning |
|---|---|
name | Label. |
container | The port inside the container. |
host | The port on the router, when the app publishes one. |
protocol | tcp or udp. |
description | What it is for. |
volumes#
| Field | Meaning |
|---|---|
name | The volume's name in the configuration tree. |
source | The default path on the router. The operator may override it at install time. |
destination | The path inside the container. |
read_only | Adds mode ro. |
file | This bind mount is a single file, not a directory. |
description | What 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_onlyenvironment#
| Field | Meaning |
|---|---|
name | The variable. |
value | The catalogue's default. |
description | What it does. |
required | An empty value produces a warning in the plan. |
secret | The 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#
| Field | Meaning |
|---|---|
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_icon | A navigation entry for the running app. |
dashboard | Show it on the Dashboard. |
hints | Integration 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.
| Field | Meaning |
|---|---|
page | The UI page the entry enables. |
config_path | The 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#
| Checked | Rule | Why |
|---|---|---|
| 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 source | Must 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 reference | The OCI reference grammar, at most 255 characters | The 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#
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#
- The catalogue — all 38 entries.
- Hint placeholders
check-images.py- Endpoint index — the ten
/api/apps/*routes. - Audit entries — what an app action records.
- Apps are configuration
- Install an app
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.