fix: blossom v4 config schema, container-internal blossom URL, e2e suite

- blossom-server 4.4.1 expects rules nested under storage: — the top-level
  rules list was ignored, leaving an empty ruleset that rejected all uploads
- podpuddle now verifies blobs and uploads recordings via BLOSSOM_URL_INTERNAL
  (http://blossom:3000) while feeds keep the browser-facing URL; unreachable
  blossom now returns 502 instead of a 500
- mediamtx: fix deprecated allow-origin params, disable MoQ
- scripts/e2e.mjs: 14 API checks (login, replay/URL rejection, upload,
  episode, feed, stream keys, mediamtx auth) — all green, plus verified
  live: RTMP publish with key → HLS 200, wrong key refused, status
  planned→live→ended via poller, recording remuxed + published as episode,
  feed XML well-formed with 2 items

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 19:21:03 +00:00
co-authored by Claude Fable 5
parent 5216c6451e
commit 6dd541b1a4
9 changed files with 214 additions and 17 deletions
+10 -1
View File
@@ -7,9 +7,18 @@ export interface BlobCheck {
size: number | null;
}
export class BlossomUnreachableError extends Error {}
/** HEAD a blob on a blossom server to confirm it exists and matches the claimed size. */
export async function checkBlob(blossomUrl: string, sha256: string): Promise<BlobCheck> {
const res = await fetch(`${blossomUrl.replace(/\/+$/, '')}/${sha256}`, { method: 'HEAD' });
let res: Response;
try {
res = await fetch(`${blossomUrl.replace(/\/+$/, '')}/${sha256}`, { method: 'HEAD' });
} catch (err) {
throw new BlossomUnreachableError(
`blossom server ${blossomUrl} unreachable: ${(err as Error).message}`,
);
}
if (!res.ok) return { exists: false, size: null };
const len = res.headers.get('content-length');
return { exists: true, size: len ? Number(len) : null };