Wheelhouse docs

Integration offers#

A hint is an offer an installed app makes on another page of the web UI: "AdGuard Home is running at 10.99.0.10 — point the router's resolver at it", with the exact configuration operations attached. It is the part of the app store that makes an app behave like a plugin rather than like a container that happens to be running.

A hint never applies itself. The card carries a Stage this change button, the operations go into the Commit Bar, and you commit them like anything else.

Where a card appears#

Pages that host hints render <AppHints page="…" /> under their header. The component fetches GET /api/apps/hints/<page> every 30 s and renders nothing at all when the answer is empty, so a router with no relevant app installed sees no extra chrome.

A card shows the hint's title, the app it came from and that app's address, the body text, and — when the hint carries operations — a Stage this change button and a collapsed show the N commands disclosure containing the rendered commands. Staging needs the operator role; a viewer sees the button disabled with the reason.

How the agent builds one#

hintsForPage() in agent/apps.go walks every installed app, skips any that is disabled or not in the catalogue, and takes the hints whose page matches. For each it expands the placeholders in the operation paths and returns the result.

Nothing site-specific may live in the catalogue itself. A hint that needs a value from this router writes a placeholder, and the agent fills it from live configuration:

PlaceholderFilled from
{{address}}the app's own container address
{{dhcp.network}}the first DHCP shared-network name that has at least one subnet
{{dhcp.subnet}}that scope's first subnet
{{dns.domain}}system domain-name, else the first authoritative forwarding zone

When the router cannot fill a placeholder#

The hint keeps its advice and loses its commands, and the body text grows a sentence saying what was missing. The phrasing is in the operator's terms, not the code's:

MissingAppended to the body
address(No commands offered: the container has no address yet.)
dhcp.network(No commands offered: the router has no DHCP scope yet.)
dhcp.subnet(No commands offered: the router has no DHCP subnet yet.)
dns.domain(No commands offered: the router has no domain (set system domain-name, or an authoritative zone).)

Several missing values are joined with semicolons. A hint in this state has no Stage this change button, because there is nothing safe to stage.

The most common cause is host networking: a container in the router's namespace has no address of its own, so {{address}} cannot be filled.

What the shipped catalogue offers#

Twenty hints across ten pages. Six carry operations; the rest are advice, which is a deliberate ratio — a hint that would stage something the operator has not thought about is worse than a sentence.

PageAppTitleCommands
DNSAdGuard HomePoint the resolver at AdGuard Home1
DNSPi-holePoint the resolver at Pi-hole1
DNSdnsmasqHand wildcard zones to dnsmasq1
DNSUnboundResolve recursively instead of forwarding1
DNSBlockyFilter through Blocky1
DHCPnetboot.xyzPoint PXE clients here1
DHCPWatchYourLANTurn discoveries into reservations
FirewallCrowdSecDecisions need a bouncer
FirewallWireGuard (feature module)Remember the firewall for new tunnels
FirewallSuricata IDS (feature module)Suricata observes; these rules filter
NATCloudflaredAn alternative to a port forward
NATNginx Proxy ManagerOne forward instead of many
QoSSpeedtest TrackerSet the shaper from measured throughput
SessionsntopngHistory, not just the live table
Static routesTailscaleSubnet routes are advertised, not configured here
WireGuardWG-EasyThis is a separate tunnel
CertificatesNginx Proxy ManagerACME lives here, not in the router PKI
SystemPrometheusScrape Wheelhouse itself
AppsPortainerChanges made there do not come back here
CompanionsdnsmasqCloses three platform gaps

The AdGuard hint, expanded on a router where the container sits at 10.99.0.10, is one operation:

set service dns forwarding name-server 10.99.0.10

Reading a hint that carries no commands#

The advice-only hints are the ones worth reading carefully, because most of them exist to correct an assumption:

  • Portainer — containers created there are not in the configuration tree, so they do not appear on the Apps page, are not in any diff, and do not survive the way a declared container does.
  • WG-Easy — it manages its own tunnel, separate from the router's native WireGuard. Two tunnels, two places to look.
  • Nginx Proxy Manager on Certificates — its ACME certificates live in the proxy, not in the router's PKI store, so the Certificates page will not show them.
  • Suricata on Firewall — the engine observes; it is the firewall rules that filter.

What hints will not do#

  • They never commit. The Commit Bar is the only thing that commits.
  • They are not per-router configurable. A hint's text and operations come from the catalogue, which is embedded in the agent binary.
  • They are not notifications. A card is only visible while you are on the page it belongs to; nothing is queued, badged or sent anywhere.

See also#

  • Apps — the catalogue, where hints are declared.
  • One app — the Where this app shows up elsewhere panel.
  • QoS — a page that hosts a hint.
  • Companions — another one, and the gap behind it.
  • DNS, Firewall and NAT — the pages that host the rest.

Checked against agent/apps.go, agent/catalog.go, agent/catalog.json, agent/catalog_test.go, ui/src/components/AppHints.tsx, docs/apps.md.

Updated 2026-09-02 manual apps hints integration