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.
64 lines
2.7 KiB
TypeScript
64 lines
2.7 KiB
TypeScript
// bazarr's Servarr step — run once by the host after bazarr's server starts, and run again whenever a
|
|
// binding or pair credential it reads changes (the container's `restart-on`, novox/hq ADR 0099).
|
|
//
|
|
// **A step, not a loop**: everything it does is a function of files the mesh writes, and the host
|
|
// already knows when they change. It connects to no broker.
|
|
//
|
|
// Exits non-zero when any app could not be put right — a refused credential, an unreachable app, a
|
|
// bazarr that would not keep the settings — so the node reports the step failed and the host runs it
|
|
// again on the next apply. Declared last in the manifest, so its failing gates nothing else of
|
|
// bazarr's (novox/hq ADR 0136).
|
|
//
|
|
// Reads, per app, `<dir>/<provision>.json` (the binding) and `<dir>/<provision>.secret` (the pair
|
|
// credential), where <dir> is MESH_SERVARR_DIR; and bazarr's own key from bazarr's own config.yaml
|
|
// under MESH_BAZARR_CONFIG_DIR. Never prints a key.
|
|
|
|
import { join } from "node:path";
|
|
|
|
import { apiKeyFromConfigDir } from "../apikey.js";
|
|
import { APPS, bazarrReady, readBinding, readIfThere, reconcileApp, type Http } from "./settings.js";
|
|
|
|
const dir = process.env.MESH_SERVARR_DIR ?? "/run/servarr";
|
|
const url = process.env.MESH_BAZARR_URL ?? "http://127.0.0.1:6767";
|
|
const waitSeconds = Number(process.env.MESH_BAZARR_WAIT_SECONDS ?? "180");
|
|
|
|
const http: Http = { fetch: (u, init) => fetch(u, init) };
|
|
|
|
const bazarrUp = await bazarrReady(http, { url, apiKey: "" }, waitSeconds * 1000);
|
|
if (!bazarrUp) {
|
|
console.error(`[bazarr-servarr] bazarr did not answer at ${url} within ${waitSeconds}s`);
|
|
process.exit(1);
|
|
}
|
|
|
|
// Read after bazarr answers: on a first start bazarr writes its config.yaml, key included, as it boots.
|
|
const apiKey = apiKeyFromConfigDir(process.env.MESH_BAZARR_CONFIG_DIR);
|
|
if (!apiKey) {
|
|
console.error("[bazarr-servarr] no bazarr API key in bazarr's config/config.yaml under MESH_BAZARR_CONFIG_DIR");
|
|
process.exit(1);
|
|
}
|
|
const bazarr = { url, apiKey };
|
|
|
|
let failed = 0;
|
|
for (const spec of APPS) {
|
|
const outcome = await reconcileApp(
|
|
http,
|
|
bazarr,
|
|
spec,
|
|
await readBinding(join(dir, `${spec.provision}.json`)),
|
|
await readIfThere(join(dir, `${spec.provision}.secret`)),
|
|
);
|
|
switch (outcome.result) {
|
|
case "unchanged":
|
|
console.log(`[bazarr-servarr] ${outcome.app}: already as the mesh says; key taken by ${outcome.app}`);
|
|
break;
|
|
case "written":
|
|
console.log(`[bazarr-servarr] ${outcome.app}: wrote ${outcome.fields.join(", ")}; key taken by ${outcome.app}`);
|
|
break;
|
|
case "refused":
|
|
failed++;
|
|
console.error(`[bazarr-servarr] ${outcome.app}: ${outcome.problem}`);
|
|
break;
|
|
}
|
|
}
|
|
process.exitCode = failed > 0 ? 1 : 0;
|