Skip to content

v1.2.0: Bazarr+ subtitle providers via the Provider Hub#3

Merged
LavX merged 7 commits into
mainfrom
feat/v1.2.0-bazarr-provider-hub
Jul 6, 2026
Merged

v1.2.0: Bazarr+ subtitle providers via the Provider Hub#3
LavX merged 7 commits into
mainfrom
feat/v1.2.0-bazarr-provider-hub

Conversation

@LavX

@LavX LavX commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Migrates arrstack's default Bazarr+ subtitle providers from the built-in providers to the Provider Hub (Bazarr+ 2.4.0's plugin system; the built-in providers are being retired), and drops the standalone opensubtitles-scraper container. FlareSolverr stays as the Cloudflare fallback.

Stacked on the v1.1.1 branch (PR #2), so this diff is only the v1.2.0 commits.

Changes

  • Remove the standalone opensubtitles-scraper service (catalog + wizard bundle + bazarr dependsOn/env). The OpenSubtitles.org scraper now lives in the catalog's opensubtitles provider (native ai-cloudscraper + FlareSolverr fallback).
  • Bazarr config template: drop podnapisi (dead, not in the catalog) from enabled_providers; remove the dead opensubtitles: { scraper_service_url } block.
  • New wiring/bazarr-providerhub.ts: after Bazarr+ is healthy, install + enable the migrated default set via the Provider Hub REST API (GET /catalog for the manifest, POST /installations, poll, PATCH /providers/<id>). Idempotent + best-effort per provider. opensubtitles gets flaresolverr_url; opensubtitlescom/addic7ed are enabled but left credential-less for the user.
  • Run the wiring on both install and update.
  • Bump to v1.2.0.

Verification

  • bun test: 318 pass / 0 fail
  • bun run typecheck: clean

Design/plan: docs/superpowers/specs/2026-07-06-bazarr-provider-hub-migration-design.md (local).

@LavX

LavX commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

Code review

Strict review (6 independent reviewers + per-issue confidence scoring). Found 4 issues, most-confident first:

  1. The catalog "readiness gate" is a no-op on HTTP 500, and catRes.ok is never checked (high). withRetry only retries when the callback throws, but fetch resolves on HTTP 500, so during Bazarr+'s post-restart DB migration (30-60s, which is exactly the arrstack update path since it gates only on the unauthenticated /api/system/ping) the catalog GET returns 500, is not retried, and catRes.json() then either throws (the best-effort catch abandons the whole step) or yields no entries so every provider logs "not in the catalog; skipping". The comment claims the 500 case is covered, but it cannot be. Use fetchWithRetry (in retry.ts, already handles 500/401) or the waitForBazarrApiReady poll from bazarr.ts, and check .ok.

// withRetry doubles as the readiness gate: the first catalog GET retries while
// Bazarr+ finishes booting (connection refused / 500 during DB migrations).
const catRes = await withRetry(() =>
fetch(`${base}/api/provider-hub/catalog`, { headers }),
);
const catalog = (await catRes.json()) as { entries?: CatalogEntry[] };
const manifestById = new Map<string, unknown>();

  1. No timeout on any Provider Hub fetch, and unbounded serial polling (low). None of the four fetches set AbortSignal.timeout (sibling bazarr.ts uses 5000ms), so a wedged Bazarr hangs the unattended install/update; and the five targets each burn the full 120s pollTimeoutMs serially with no shared cap (~10 min worst case).

const r = await fetch(`${base}/api/provider-hub/providers`, { headers });
if (!r.ok) throw new Error(`list providers returned ${r.status}`);
const j = (await r.json()) as { data?: Array<{ provider_id?: string; id?: string }> };
return new Set(
(j.data ?? []).map((p) => p.provider_id ?? p.id).filter((x): x is string => !!x),
);

  1. waitForInstalled drops a provider on a single transient GET /providers error (low). installedIds throws on any non-OK response and the poll loop has no inner catch, so one 503 while the hub builds the venv escapes the loop and marks that provider failed even though its install finishes moments later.

): Promise<void> {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
if ((await installedIds(base, headers)).has(id)) return;
await new Promise((r) => setTimeout(r, intervalMs));
}

  1. Hardcoded flaresolverr_url gated only on bazarr being enabled (low). Both call sites pass http://flaresolverr:8191/v1 whenever bazarr is enabled, without checking flaresolverr is present, so a legacy install with bazarr but no flaresolverr configures the opensubtitles provider with a dead host.

apiKey: apiKeys["bazarr"] ?? "",
flaresolverrUrl: "http://flaresolverr:8191/v1",
log: (m) => log.info("provider-hub", m),
(and
apiKey: state.api_keys["bazarr"] ?? "",
flaresolverrUrl: "http://flaresolverr:8191/v1",
log: (m) => logAndEcho(`[update] ${m}`),
)

Filtered out below the confidence bar: manifest provider_id not cross-checked before install (marginal; catalog and installations come from the same trusted localhost service); stale user docs still referencing the removed scraper/podnapisi (real, but in files this PR does not touch).

Repository owner deleted a comment from chatgpt-codex-connector Bot Jul 6, 2026
LavX added 2 commits July 6, 2026 18:12
- Readiness gate: call waitForBazarrApiReady before the Provider Hub calls so the
  wiring waits out Bazarr+'s post-restart DB-migration 500s (the update path only
  gates on /api/system/ping) instead of silently no-opping. Switch the catalog
  GET to fetchWithRetry (forgives transient 500/401) and check res.ok.
- Add AbortSignal.timeout to every Provider Hub fetch so a wedged Bazarr can't
  hang an unattended install/update.
- waitForInstalled tolerates a transient GET /providers error mid-build instead
  of dropping the provider on a single blip.
- Only pass flaresolverr_url when flaresolverr is actually enabled.
- Export + parameterize waitForBazarrApiReady's poll interval; add a test for the
  500-during-migration window.

319 tests pass, typecheck clean.
The POST /installations call blocks while the Hub downloads the provider bundle
and builds its venv; heavy providers (opensubtitles) exceed the 15s per-request
timeout, which falsely reported them as failed and skipped their enable+config
PATCH. Give the install POST a 180s cap; keep 15s for the quick catalog/list/
patch calls. Verified end-to-end on a live Bazarr+ 2.4.0: all 5 providers install
+ enable and opensubtitles gets its flaresolverr_url.
@LavX LavX changed the base branch from fix/v1.1.1-reboot-caddy-secrets-root to main July 6, 2026 16:29
@LavX LavX merged commit 701b066 into main Jul 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant