jackett leaves: it is registered from novox/mesh-media-catalog with sonarr, radarr, lidarr, bazarr, nzbget, qbittorrent, bookshelf, plex, tautulli, kometa and ombi (PRs 145-168 consolidated there). What those branches changed outside the chain stays here: home-assistant's provisions (sonarr-api, radarr-api, mqtt-topic — from #147) and searxng's sidecar dialling the port it was given (#154).
85 lines
3.6 KiB
TypeScript
85 lines
3.6 KiB
TypeScript
// home-assistant's provisions step — run once by the host after Home Assistant starts, and again
|
|
// whenever a binding or pair credential it reads changes (the container's `restart-on`, novox/hq
|
|
// ADR 0099). It points Home Assistant's MQTT integration at the `mqtt-topic` broker and its Sonarr,
|
|
// Radarr and Lidarr integrations at the `sonarr-api`, `radarr-api` and `lidarr-api` apps, through
|
|
// Home Assistant's own config flows (connections.ts). It connects to no mesh broker.
|
|
//
|
|
// Exits non-zero when anything could not be put right, 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 home-assistant's (novox/hq ADR 0136). Never prints a key or password.
|
|
|
|
import { join } from "node:path";
|
|
|
|
import { APPS, MQTT_PROVISION, reconcileApp, reconcileMqtt, type Outcome } from "./connections.js";
|
|
import { HassApi } from "./hass.js";
|
|
import { marksIn, readBinding, readIfThere } from "./mesh.js";
|
|
import { probeBroker } from "./probe.js";
|
|
|
|
const dir = process.env.MESH_PROVISIONS_DIR ?? "/run/provisions";
|
|
const url = process.env.MESH_HOMEASSISTANT_URL ?? "http://127.0.0.1:8123";
|
|
const token = (await readIfThere(process.env.MESH_HOMEASSISTANT_TOKEN_FILE))?.trim() ?? "";
|
|
const marks = marksIn(process.env.MESH_WRITTEN_DIR ?? "/var/lib/home-assistant-provisions");
|
|
const waitSeconds = Number(process.env.MESH_HOMEASSISTANT_WAIT_SECONDS ?? "300");
|
|
|
|
if (!token) {
|
|
console.error("[hass-provisions] no Home Assistant token — home-assistant's own `token` secret has not been accepted");
|
|
process.exit(1);
|
|
}
|
|
|
|
/** Home Assistant answers /api/ with 200 once it is up and the token is good. */
|
|
async function ready(): Promise<boolean> {
|
|
const until = Date.now() + waitSeconds * 1000;
|
|
for (;;) {
|
|
try {
|
|
const res = await fetch(`${url.replace(/\/$/, "")}/api/`, { headers: { Authorization: `Bearer ${token}` } });
|
|
if (res.status === 200) return true;
|
|
if (res.status === 401 || res.status === 403) {
|
|
console.error("[hass-provisions] Home Assistant refuses the token — accept a long-lived access token it issued");
|
|
return false;
|
|
}
|
|
} catch {
|
|
// not listening yet
|
|
}
|
|
if (Date.now() >= until) return false;
|
|
await new Promise((r) => setTimeout(r, 3000));
|
|
}
|
|
}
|
|
|
|
if (!(await ready())) {
|
|
console.error(`[hass-provisions] Home Assistant did not answer at ${url} within ${waitSeconds}s`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const hass = new HassApi(url, token);
|
|
const read = async (p: string) => [await readBinding(join(dir, `${p}.json`)), await readIfThere(join(dir, `${p}.secret`))] as const;
|
|
|
|
const outcomes: Outcome[] = [];
|
|
{
|
|
const [binding, secret] = await read(MQTT_PROVISION);
|
|
outcomes.push(await reconcileMqtt({ hass, probe: probeBroker, marks }, binding, secret));
|
|
}
|
|
for (const spec of APPS) {
|
|
const [binding, secret] = await read(spec.provision);
|
|
outcomes.push(await reconcileApp({ hass, http: { fetch: (u, init) => fetch(u, init) } }, spec, binding, secret));
|
|
}
|
|
|
|
let failed = 0;
|
|
for (const o of outcomes) {
|
|
switch (o.result) {
|
|
case "unchanged":
|
|
console.log(`[hass-provisions] ${o.what}: already as the mesh says${o.note ? ` — ${o.note}` : ""}`);
|
|
break;
|
|
case "written":
|
|
console.log(`[hass-provisions] ${o.what}: wrote ${o.fields.join(", ")}; Home Assistant's own test passed${o.note ? ` — ${o.note}` : ""}`);
|
|
break;
|
|
case "equivalent":
|
|
console.log(`[hass-provisions] ${o.what}: ${o.note}`);
|
|
break;
|
|
case "refused":
|
|
failed++;
|
|
console.error(`[hass-provisions] ${o.what}: ${o.problem}`);
|
|
break;
|
|
}
|
|
}
|
|
process.exitCode = failed > 0 ? 1 : 0;
|