bazarr: reach sonarr and radarr through the mesh; placed dirs; the image ace runs #165

Closed
mesh-admin wants to merge 6 commits from feat/bazarr-for-ace into main
Contributor

Prepares bazarr for ace. Conversion only — nothing assigned, deployed or moved.

Depends on #156 (feat/servarr-api-provision), which this branch contains: bazarr consumes the sonarr-api/radarr-api provisions it adds. Merge #156 first. Does not need controller #149.

What changed

  • bazarr reaches sonarr and radarr through the mesh. It reached them as sonarr:8989 / radarr:7878 — container names on HAL's network, which the mesh does not have. It now requires sonarr-api and radarr-api, binds them and takes their pair credentials into a placed state dir, and a run-once step (servarr/, same shape as ombi's in #156) writes host, port, TLS, base path and key into bazarr through bazarr's own POST /api/system/settings — only the fields that differ, then reads them back. use_sonarr, sync intervals, excluded tags, path mappings: untouched. The mesh does not write bazarr's config.yaml (bazarr holds it in memory and rewrites it). A key the app refuses — a minted credential before the operator accepts the app's own — is never written; the step fails naming the secret accept. Declared last (ADR 0136), restart-on its four inputs (ADR 0099).
  • The api-key own-secret is removed. bazarr makes its own key and nothing lets the mesh set it, so a minted value could never work. Tools and the step read auth.apikey from bazarr's own config/config.yaml (config dir mounted read-only) — nothing to accept, and it follows a regenerated key.
  • config is a pathless ${dir:config}; config.json, route and bindings live in ${dir:state}; /var/lib/mesh/bazarr keeps only the broker.
  • Image pinned to what ace runs: v1.6.1-ls364 sha256:d24bd004…. The old pin 3a820372… is v1.6.0-ls361, older than ace's database.

Verified

  • MESH_CATALOGUE=<this> go test ./internal/catalogue/ passes (not skipped).
  • Scratch resolve: sonarr+radarr+bazarr on a fake ace renders both bindings + sealed credentials into the state dir, every ${} filled, restart-on ids exist; bazarr alone is refused naming sonarr and radarr.
  • Strict tsc -p tsconfig.json passes; the Dockerfile's non-strict compile builds; npm test 8/8.
  • The compiled step against the pinned image (throwaway container, fresh config) and fake sonarr/radarr: wrote sonarr ip, port, apikey; refused radarr's minted key and wrote nothing for it; wrote radarr once the key was right; changed nothing on a third run; values persisted in config.yaml.

Gaps

  • hq 153: ace's paths are /storage/media/{movies,series,anime} + /storage/downloads; bazarr must run as 1001:2000 to write subtitles next to media. Not expressible yet.
  • bazarr can only be assigned where sonarr-api and radarr-api are provided (by design) — it moves after sonarr and radarr.
  • TZ stays Etc/UTC (ace: Europe/Brussels): bazarr's backup/full-update hours shift by 1–2 h.
Prepares bazarr for ace. **Conversion only — nothing assigned, deployed or moved.** **Depends on #156** (feat/servarr-api-provision), which this branch contains: bazarr consumes the `sonarr-api`/`radarr-api` provisions it adds. Merge #156 first. Does not need controller #149. ## What changed - **bazarr reaches sonarr and radarr through the mesh.** It reached them as `sonarr:8989` / `radarr:7878` — container names on HAL's network, which the mesh does not have. It now `requires` `sonarr-api` and `radarr-api`, binds them and takes their pair credentials into a placed `state` dir, and a run-once step (`servarr/`, same shape as ombi's in #156) writes host, port, TLS, base path and key into bazarr through bazarr's own `POST /api/system/settings` — only the fields that differ, then reads them back. `use_sonarr`, sync intervals, excluded tags, path mappings: untouched. The mesh does not write bazarr's `config.yaml` (bazarr holds it in memory and rewrites it). A key the app refuses — a minted credential before the operator accepts the app's own — is never written; the step fails naming the `secret accept`. Declared last (ADR 0136), `restart-on` its four inputs (ADR 0099). - **The `api-key` own-secret is removed.** bazarr makes its own key and nothing lets the mesh set it, so a minted value could never work. Tools and the step read `auth.apikey` from bazarr's own `config/config.yaml` (config dir mounted read-only) — nothing to accept, and it follows a regenerated key. - `config` is a pathless `${dir:config}`; `config.json`, route and bindings live in `${dir:state}`; `/var/lib/mesh/bazarr` keeps only the broker. - Image pinned to what ace runs: `v1.6.1-ls364` `sha256:d24bd004…`. The old pin `3a820372…` is `v1.6.0-ls361`, older than ace's database. ## Verified - `MESH_CATALOGUE=<this> go test ./internal/catalogue/` passes (not skipped). - Scratch resolve: sonarr+radarr+bazarr on a fake ace renders both bindings + sealed credentials into the state dir, every `${}` filled, restart-on ids exist; bazarr alone is refused naming sonarr and radarr. - Strict `tsc -p tsconfig.json` passes; the Dockerfile's non-strict compile builds; `npm test` 8/8. - The compiled step against the pinned image (throwaway container, fresh config) and fake sonarr/radarr: wrote sonarr `ip, port, apikey`; refused radarr's minted key and wrote nothing for it; wrote radarr once the key was right; changed nothing on a third run; values persisted in `config.yaml`. ## Gaps - **hq 153**: ace's paths are `/storage/media/{movies,series,anime}` + `/storage/downloads`; bazarr must run as `1001:2000` to write subtitles next to media. Not expressible yet. - bazarr can only be assigned where sonarr-api and radarr-api are provided (by design) — it moves after sonarr and radarr. - TZ stays `Etc/UTC` (ace: Europe/Brussels): bazarr's backup/full-update hours shift by 1–2 h.
mesh-admin added 6 commits 2026-09-30 09:59:32 +00:00
ombi's definition named /services/ombi/config (a HAL machine path) in three
places and pinned an image older than the one ace runs. Ombi migrates its
own SQLite schema, so a take onto the older pin (v4.53.10-ls267) would start
it on a database the newer build (ls269) already touched.

- config is a pathless placed directory, mounted as ${dir:config}
- a state directory placed at the assignment root carries route.json
- image pinned to the digest ace runs today (v4.53.10-ls269)
- the sidecar reaches ombi on the machine port the mesh assigns
  (${port:3579}) rather than assuming 3579 is free
- the sidecar no longer mounts ombi's data directory: MESH_OMBI_CONFIG_DIR
  is read by no code, and the mount exposed the databases for nothing

Verified: catalogue tests (MESH_CATALOGUE set, 6 pass, none skipped); the
pinned image starts as PUID 1000 in a 0700 dir and answers /api/v1/Status
200; data owned 1001:2000 (ace's media ids) under a 1000:1000 dir is
re-owned by the image's init and serves 200; a minted ApiKey is refused
(401) - the api-key secret must be accepted from ombi's own settings.
A host-network sidecar reaches its service over the machine's loopback, and
the mesh publishes that service on a machine port it assigns (ADR 0038) —
so dialling the software's port reaches whatever else holds it. On ace,
searxng's sidecar dialled 127.0.0.1:8080 and got unifi's inform port. The
same shape in bazarr, bookshelf, lidarr, nzbget, qbittorrent, radarr and
sonarr; each now asks with ${port:N} (hq 088). Found in review of ace's
module preparation.
A consumer on another machine (ombi first; jackett, bazarr and home-assistant
later) reached these by container name on HAL's shared network, which the mesh
does not have. Each app now provides <app>-api at mesh scope and serves the
software port, so the mesh tells a consumer where it is and redirects the
port to where the machine published it.

Named per app, not one servarr-api: a requirement is matched by name and
answered by exactly one provider per node, so a consumer cannot require one
name from three providers - and ombi's code is written against each app's
own API version (ADR 0027).

No grants and no provisioner: a Servarr instance has one API key, which the
mesh cannot mint. The operator accepts it as the pair credential for each
consumer (ADR 0092).
ombi keeps its Servarr connections in its own database, so the mesh has no
file to write them into. A run-once step reads the three bindings and pair
credentials and writes host, port, TLS, base path and key into ombi through
ombi's own API - only when they differ, and nothing else ombi keeps.

Until the operator accepts an app's key for this pair the mesh delivers a
value it minted, which no Servarr app accepts. The step tries the key against
the app first and, refused, writes nothing and fails naming the secret accept
that fixes it, so the old working key in ombi is never replaced by a dead one.

Declared last so its failing gates nothing else of ombi (ADR 0136), and
restart-on its six inputs so it runs again when a provider moves (ADR 0099).
bazarr reached sonarr and radarr as `sonarr:8989` and `radarr:7878`,
container names on HAL's shared network, which the mesh does not have.
It now requires sonarr-api and radarr-api (provided since #156) and a
run-once step writes host, port, TLS, base path and key into bazarr
through bazarr's own POST /api/system/settings - the call its settings
screen makes - only for the fields that differ, and reads them back.
bazarr's config.yaml is not written by the mesh: bazarr holds it in
memory and rewrites it, so the two would overwrite each other. The key is
tried against the app first; a refused key (a minted pair credential
before the operator accepts the app's own) is never written, and the step
fails naming the `secret accept` that fixes it. Declared last so its
failing gates nothing else (ADR 0136); restart-on its four inputs.

The api-key own-secret is gone. bazarr makes its own key and nothing lets
the mesh set it, so a minted one could never work; the tools and the step
read auth.apikey from bazarr's own config/config.yaml (mounted read-only),
which also stays right if the key is regenerated. Nothing to accept.

The config dir is a pathless ${dir:config}; config.json and the bindings
live in a placed state dir; /var/lib/mesh/bazarr keeps only the broker.
The image is pinned to the digest ace runs (v1.6.1-ls364); the old pin was
v1.6.0-ls361, older than ace's database.

Based on feat/servarr-api-provision (#156); this branch contains it.

Verified: catalogue tests with MESH_CATALOGUE pass (not skipped); a
resolve of sonarr+radarr+bazarr on a fake ace renders both bindings and
sealed credentials into the state dir with every ${} filled, and bazarr
alone is refused naming sonarr and radarr; strict tsc passes and the
Dockerfile's non-strict compile builds; 8 node tests pass; the compiled
step against the pinned image in a throwaway container with fake
sonarr/radarr wrote sonarr (ip, port, apikey), refused radarr's minted
key and wrote nothing for it, wrote radarr once the key was right, and
changed nothing on a third run - the values landed in config.yaml.
Author
Contributor

Superseded: this module now lives in novox/mesh-media-catalog (the media chain, consolidated from #145–#168 in stack order; its non-media parts merged via #195). Closing.

Superseded: this module now lives in novox/mesh-media-catalog (the media chain, consolidated from #145–#168 in stack order; its non-media parts merged via #195). Closing.
mesh-admin closed this pull request 2026-09-30 19:45:26 +00:00

Pull request closed

Please reopen this pull request to perform a merge.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: novox/mesh-catalog#165