silo.arrproxy is a Silo request router that forwards approved media requests to a single Arr Proxy backend which fronts both Radarr (movies) and Sonarr (TV) via path-routing (${BaseURL}/radarr/api/v3/... and ${BaseURL}/sonarr/api/v3/...). It consumes request events from silo.requests, submits them to the appropriate *arr API, polls for download/import progress, and publishes lifecycle status events back to the hub.
Lives under Requests.
Install at most one router per installation — either this plugin or silo.arrouter (the rule-based multi-target alternative), not both.
| Type | ID | Purpose |
|---|---|---|
event_consumer.v1 |
request-router |
Consumes plugin.silo.requests.submitted and …cancelled, forwarding movies to Radarr and TV to Sonarr. |
scheduled_task.v1 |
poll |
Periodic reconciliation against Radarr/Sonarr for queued, downloading, imported, cancelled, and stale-failed states. |
http_routes.v1 |
admin |
Admin status board (SPA + JSON API) under /admin and /api/admin/*, with retry / cancel / force-fail for stuck rows. |
request_router.v1 |
default |
Declares this plugin as the default forwarder for the single-backend Arr Proxy topology. |
silo-plugin-requests— the upstream request intake plugin that publishes thesubmittedandcancelledevents this plugin consumes. The consumer honours the optionaltarget_plugin_id/target_provider_plugin_idfields on incoming payloads so multiple routers can coexist without double-routing.- Silo plugin host — provides the gRPC runtime, event hub, and admin-route proxying that this plugin plugs into via
continuum-plugin-sdk. - Postgres — a dedicated
arrproxyschema persists the request rows, external IDs, status, and the plugin-owned operational config (everything exceptdatabase_url).
Host: ContinuumApp/silo.
- Arr Proxy backend — a single HTTP service that exposes Radarr at
/radarr/...and Sonarr at/sonarr/...behind one base URL and one API key. - Radarr and Sonarr, reached only through the Arr Proxy. This plugin never contacts the underlying
*arrservices directly.
silo.requestspublishesplugin.silo.requests.submittedafter an approval.- The event consumer validates the payload (TMDB ID, media type) and inserts a
queuedrow in the local store. - Based on
mediaType:- movie — resolves Radarr defaults (root folder, quality profile), calls
POST /radarr/api/v3/movievia the Arr Proxy withsearchForMovie: true, then marks the rowsubmittedand emitsplugin.silo.arrproxy.submitted. - tv — looks up the series by TMDB ID through Sonarr, resolves defaults (root folder, quality + language profile), calls
POST /sonarr/api/v3/serieswithmonitor: allandsearchForMissingEpisodes: true, then emitssubmitted. - Conflicts (
409already-exists) are treated as success and reconciled by the next poll tick.
- movie — resolves Radarr defaults (root folder, quality profile), calls
- The poll loop walks rows in
submitted/downloadingeverypoll_interval_seconds(default 30s, clamped 10–600s), callingGET movie/{id},GET series/{id}, and the relevant queue endpoint. It publishesdownloadingonce an item appears in the*arrqueue, andimportedwhen Radarr reportshasFileor Sonarr reportsstatistics.percentOfEpisodes >= 100. - Rows that stay non-terminal past
stale_after_hours(default 72h) are markedfailedwith reason"stuck past stale window"and afailedevent is emitted. - On
plugin.silo.requests.cancelled, the consumer deletes the matching movie/series in Radarr/Sonarr (if it was tracked) and marks the rowcancelled.
Only database_url is declared in the manifest's global_config_schema and set via the Silo host's plugin install form. Everything else is owned by the plugin and edited through the admin UI (persisted in the arrproxy.app_config table).
| Key | Source | Required | Description |
|---|---|---|---|
database_url |
host | yes | Postgres DSN for the dedicated arrproxy schema. Pool capped at min 16 conns; override via ?pool_max_conns=N. |
base_url |
admin UI | yes | Arr Proxy backend URL. Radarr and Sonarr URLs are derived as ${base_url}/radarr and ${base_url}/sonarr. |
api_key |
admin UI | yes | Shared API key accepted by the Arr Proxy backend for both Radarr and Sonarr. |
movies_path |
admin UI | no | Radarr root folder. Blank → first root folder returned by Radarr. |
tv_path |
admin UI | no | Sonarr root folder. Blank → first root folder returned by Sonarr. |
quality_profile_id |
admin UI | no | Quality profile applied to both Radarr and Sonarr. Blank → first profile returned by the service. |
language_profile_id |
admin UI | no | Sonarr language profile. Blank → first profile. |
polling.interval_seconds |
admin UI | no | Poll cadence in seconds. Default 30, clamped to [10, 600]. |
polling.stale_after_hours |
admin UI | no | Hours after which non-terminal rows are marked failed. Default 72. <= 0 disables staleness. |
Database bootstrap:
CREATE ROLE plugin_arrproxy WITH LOGIN PASSWORD '<chosen>';
CREATE SCHEMA arrproxy AUTHORIZATION plugin_arrproxy;
GRANT CONNECT ON DATABASE silo TO plugin_arrproxy;Schema migrations run automatically at startup.
plugin.silo.requests.submittedplugin.silo.requests.cancelled
The consumer ignores any event whose target_plugin_id / target_provider_plugin_id field is set to another router's ID, so this plugin coexists safely with siblings on the same hub.
All events are published under the plugin's own namespace (plugin.silo.arrproxy.<name>):
submitted— request accepted by Radarr/Sonarr (or already existed).downloading— first time the item appears in an*arrdownload queue.imported— RadarrhasFile, or SonarrpercentOfEpisodes >= 100.failed— submission error, stale-window expiry, or operator force-fail. Payload includes anerrorstring with the reason.cancelled— operator cancellation, upstream request cancellation, or the item being removed from Radarr/Sonarr externally.unrouted— reserved for events that did not match any media path (the consumer drops invalid payloads with a warning today).
Every payload carries requestId (and request_id) so downstream consumers can correlate against the originating request.
- Operations & debugging runbook
- Event payload reference
SPEC.md— original capability and data-model specification.
make build # builds the SPA via pnpm, then the Go binary
make test # runs `go test ./...`CI builds linux-amd64 binaries on push to main via the reusable workflow in RXWatcher/silo-plugin-repository and publishes them to the catalog at ./binaries/.