- multistreaming (new): RTMP ingest + multi-platform fan-out with pluggable providers (Twitch/YouTube/Kick/custom), zero-knowledge key vaults, Authelia OIDC auth, shared rooms with editor/streamer roles, single-use invites, per-account streaming grants, and scenes & composition (grid/PiP layouts, text/image overlays, per-output audio routing). - installer: support Dockerfile build in metadata (not just image) and RSA key generation for the Authelia OIDC JWKS. - authelia: add OIDC provider with portainer + multistreaming clients (public + PKCE). - services: remove allprox; add nginx-proxy-manager and portainer; update lldap; regenerate catalog.
57 lines
1.9 KiB
TypeScript
57 lines
1.9 KiB
TypeScript
import type { ServiceMetadata } from "./types.ts";
|
|
|
|
export interface ValidationIssue {
|
|
path: string;
|
|
message: string;
|
|
}
|
|
|
|
export function parseAndValidate(raw: string, fallbackId: string): ServiceMetadata {
|
|
let data: unknown;
|
|
try {
|
|
data = JSON.parse(raw);
|
|
} catch (e) {
|
|
throw new Error(`metadata.json is not valid JSON: ${(e as Error).message}`);
|
|
}
|
|
|
|
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
throw new Error("metadata.json must be a JSON object");
|
|
}
|
|
|
|
const meta = data as ServiceMetadata;
|
|
const issues = validateMetadata(meta, fallbackId);
|
|
if (issues.length > 0) {
|
|
const detail = issues.map((i) => `${i.path}: ${i.message}`).join("; ");
|
|
throw new Error(`invalid metadata.json (${detail})`);
|
|
}
|
|
|
|
meta.id = meta.id || fallbackId;
|
|
return meta;
|
|
}
|
|
|
|
export function validateMetadata(meta: ServiceMetadata, fallbackId: string): ValidationIssue[] {
|
|
const issues: ValidationIssue[] = [];
|
|
const id = meta.id || fallbackId;
|
|
|
|
if (!meta.name || typeof meta.name !== "string") {
|
|
issues.push({ path: "name", message: "required string" });
|
|
}
|
|
if (!meta.description || typeof meta.description !== "string") {
|
|
issues.push({ path: "description", message: "required string" });
|
|
}
|
|
if (!meta.version || typeof meta.version !== "string") {
|
|
issues.push({ path: "version", message: "required string" });
|
|
}
|
|
if (!meta.compose || typeof meta.compose !== "object" || Array.isArray(meta.compose)) {
|
|
issues.push({ path: "compose", message: "required object (docker-compose service spec)" });
|
|
} else if (!meta.compose.image && !meta.compose.build) {
|
|
issues.push({
|
|
path: "compose",
|
|
message: "must define `image` (pull an image) or `build` (build from a Dockerfile)",
|
|
});
|
|
}
|
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(id)) {
|
|
issues.push({ path: "id", message: "must be kebab-case and match the folder name" });
|
|
}
|
|
|
|
return issues;
|
|
}
|