fix: restore Workshop runtime URLs

This commit is contained in:
Thomas Camlong authored and ajnart committed 2026-08-13 19:51:25 +02:00
1 parent 000c51a131
commit 71c39aa18e
13 files changed
+91 -6

No files matched your search

+3 -2
View File
@@ -108,8 +108,9 @@ The public URL contract is:
| `WORKSHOP_PUBLIC_ORIGIN` | PocketBase-generated links and email | Required in production |
| `PB_ALLOWED_ORIGINS` | PocketBase browser origins | `*` for the central community |
The deprecated docs-only `WORKSHOP_URL` alias remains for one release; `WORKSHOP_API_URL` takes precedence. Restart
services after changing these values.
The deprecated docs-only `WORKSHOP_URL` alias remains for one release; `WORKSHOP_API_URL` takes precedence. The
combined Workshop image writes these public URLs into its browser runtime configuration whenever the container starts;
the Docker build arguments remain fallbacks. Restart the container after changing these values, without rebuilding it.
Back up application and PocketBase data before deployment. Publish the combined image by immutable digest, test the
exact digest in staging, rehearse application rollback and PocketBase restore, then promote that same digest.
+3
View File
@@ -12,6 +12,7 @@ if (deprecatedWorkshopUrl && !process.env.WORKSHOP_API_URL) {
const publicUrls = resolveHomarrUrlConfig({
homarrWebsiteUrl: process.env.HOMARR_WEBSITE_URL,
workshopApiUrl: process.env.WORKSHOP_API_URL ?? deprecatedWorkshopUrl,
workshopWebUrl: process.env.WORKSHOP_WEB_URL,
});
const config: Config = {
title: "Homarr documentation",
@@ -24,7 +25,9 @@ const config: Config = {
projectName: "homarr",
customFields: {
workshopUrl: publicUrls.workshopApiUrl,
workshopWebUrl: publicUrls.workshopWebUrl,
},
scripts: [{ src: "/workshop-runtime-config.js" }],
i18n: {
defaultLocale: "en",
locales: ["en"],
@@ -29,6 +29,7 @@ import { toast } from "sonner";
import type { WorkshopSubmission, WorkshopVote } from "@site/src/lib/pocketbase";
import { getWorkshopBackend } from "@site/src/lib/pocketbase";
import { getRuntimeWorkshopApiUrl } from "@site/src/lib/runtime-config";
import type { SubmissionType } from "@site/src/lib/workshop-schema";
import type { HomarrCustomWidgetV2 } from "@homarr/custom-widgets/core";
import {
@@ -1028,7 +1029,7 @@ export default function MarketplaceDetailPage() {
<BrowserOnly fallback={<DetailSkeleton />}>
{() => (
<WorkshopErrorBoundary>
<MarketplaceDetail workshopUrl={configuredWorkshopUrl || window.location.origin} />
<MarketplaceDetail workshopUrl={getRuntimeWorkshopApiUrl(configuredWorkshopUrl)} />
</WorkshopErrorBoundary>
)}
</BrowserOnly>
+19
View File
@@ -0,0 +1,19 @@
import { afterEach, describe, expect, test } from "vitest";
import { getRuntimeWorkshopApiUrl } from "./runtime-config";
describe("Workshop runtime configuration", () => {
afterEach(() => {
delete window.homarrRuntimeConfig;
});
test("prefers the URL supplied when the container starts", () => {
window.homarrRuntimeConfig = { workshopApiUrl: "https://v2.preview.homarr.dev" };
expect(getRuntimeWorkshopApiUrl("https://homarr.dev")).toBe("https://v2.preview.homarr.dev");
});
test("keeps the build-time URL as a fallback", () => {
expect(getRuntimeWorkshopApiUrl("https://homarr.dev")).toBe("https://homarr.dev");
});
});
+16
View File
@@ -0,0 +1,16 @@
interface HomarrRuntimeConfig {
homarrWebsiteUrl?: string;
workshopApiUrl?: string;
workshopWebUrl?: string;
}
declare global {
interface Window {
homarrRuntimeConfig?: Readonly<HomarrRuntimeConfig>;
}
}
export const getRuntimeWorkshopApiUrl = (configuredUrl: string): string => {
if (typeof window === "undefined") return configuredUrl;
return window.homarrRuntimeConfig?.workshopApiUrl || configuredUrl || window.location.origin;
};
+3 -1
View File
@@ -3,12 +3,14 @@ import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import Layout from "@theme/Layout";
import { WorkshopAdmin } from "@site/src/components/workshop/WorkshopAdmin";
import { getRuntimeWorkshopApiUrl } from "@site/src/lib/runtime-config";
export default function WorkshopAdminRoute() {
const { siteConfig } = useDocusaurusContext();
const workshopUrl = (siteConfig.customFields?.workshopUrl as string | undefined) ?? "";
return (
<Layout title="Workshop moderation" noFooter>
<WorkshopAdmin workshopUrl={workshopUrl} />
<WorkshopAdmin workshopUrl={getRuntimeWorkshopApiUrl(workshopUrl)} />
</Layout>
);
}
+2 -1
View File
@@ -5,6 +5,7 @@ import Layout from "@theme/Layout";
import { WorkshopApp, WorkshopListingFallback } from "@site/src/components/workshop/WorkshopApp";
import { WorkshopErrorBoundary } from "@site/src/components/workshop/WorkshopErrorBoundary";
import { getRuntimeWorkshopApiUrl } from "@site/src/lib/runtime-config";
export default function WorkshopPage() {
const { siteConfig } = useDocusaurusContext();
@@ -16,7 +17,7 @@ export default function WorkshopPage() {
<BrowserOnly fallback={<WorkshopListingFallback />}>
{() => (
<WorkshopErrorBoundary>
<WorkshopApp workshopUrl={configuredWorkshopUrl || window.location.origin} />
<WorkshopApp workshopUrl={getRuntimeWorkshopApiUrl(configuredWorkshopUrl)} />
</WorkshopErrorBoundary>
)}
</BrowserOnly>
+1
View File
@@ -0,0 +1 @@
globalThis.homarrRuntimeConfig ??= Object.freeze({});