fix: restore Workshop runtime URLs
This commit is contained in:
1 parent
000c51a131
commit
71c39aa18e
13 files changed
+91
-6
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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,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>
|
||||
);
|
||||
}
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
globalThis.homarrRuntimeConfig ??= Object.freeze({});
|
||||
@@ -18,8 +18,10 @@ RUN --mount=type=cache,id=workshop-pnpm-store,target=/pnpm/store pnpm config set
|
||||
FROM dependencies AS website
|
||||
ARG HOMARR_WEBSITE_URL=https://homarr.dev
|
||||
ARG WORKSHOP_API_URL=https://homarr.dev
|
||||
ARG WORKSHOP_WEB_URL=https://homarr.dev/workshop
|
||||
ENV HOMARR_WEBSITE_URL=$HOMARR_WEBSITE_URL
|
||||
ENV WORKSHOP_API_URL=$WORKSHOP_API_URL
|
||||
ENV WORKSHOP_WEB_URL=$WORKSHOP_WEB_URL
|
||||
RUN CI=true SKIP_ENV_VALIDATION=true pnpm turbo build --filter=@homarr/docs
|
||||
|
||||
FROM ${POCKETBASE_IMAGE} AS pocketbase-runtime
|
||||
@@ -34,6 +36,12 @@ EXPOSE 8090
|
||||
HEALTHCHECK --interval=10s --timeout=5s --start-period=15s --retries=6 CMD wget -q --spider "http://127.0.0.1:${PORT}/api/health" || exit 1
|
||||
|
||||
FROM pocketbase-runtime AS production
|
||||
ARG HOMARR_WEBSITE_URL=https://homarr.dev
|
||||
ARG WORKSHOP_API_URL=https://homarr.dev
|
||||
ARG WORKSHOP_WEB_URL=https://homarr.dev/workshop
|
||||
ENV HOMARR_WEBSITE_URL=$HOMARR_WEBSITE_URL
|
||||
ENV WORKSHOP_API_URL=$WORKSHOP_API_URL
|
||||
ENV WORKSHOP_WEB_URL=$WORKSHOP_WEB_URL
|
||||
COPY --from=website /app/apps/docs/build /pb_public
|
||||
COPY apps/workshop/pb_migrations /pb_migrations
|
||||
COPY apps/workshop/pb_hooks /pb_hooks
|
||||
|
||||
@@ -70,7 +70,8 @@ pnpm dev
|
||||
|
||||
`WORKSHOP_API_URL` controls API calls. `WORKSHOP_WEB_URL` controls links from Homarr. `HOMARR_WEBSITE_URL` is the
|
||||
documentation/site base. All three must be absolute HTTP(S) URLs. The old Docusaurus-only `WORKSHOP_URL` variable
|
||||
remains a deprecated alias for one release.
|
||||
remains a deprecated alias for one release. The production image writes these values into the browser runtime
|
||||
configuration at container startup, so changing them requires a restart but not an image rebuild.
|
||||
|
||||
## Optional GitHub OAuth
|
||||
|
||||
|
||||
@@ -33,9 +33,12 @@ services:
|
||||
args:
|
||||
HOMARR_WEBSITE_URL: ${HOMARR_WEBSITE_URL:-http://localhost:3003}
|
||||
WORKSHOP_API_URL: ${WORKSHOP_API_URL:-http://localhost:3003}
|
||||
WORKSHOP_WEB_URL: ${WORKSHOP_WEB_URL:-http://localhost:3003/workshop}
|
||||
environment:
|
||||
HOMARR_WEBSITE_URL: ${HOMARR_WEBSITE_URL:-http://localhost:3003}
|
||||
PB_ALLOWED_ORIGINS: ${PB_ALLOWED_ORIGINS:-*}
|
||||
PORT: ${DOCS_EXPOSE_PORT:-3003}
|
||||
WORKSHOP_API_URL: ${WORKSHOP_API_URL:-http://localhost:3003}
|
||||
WORKSHOP_WEB_URL: ${WORKSHOP_WEB_URL:-http://localhost:3003/workshop}
|
||||
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID:-}
|
||||
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET:-}
|
||||
|
||||
@@ -6,6 +6,24 @@ if [ "$(id -u)" -eq 0 ]; then
|
||||
exec su -p -s /bin/sh pocketbase -c 'exec /usr/local/bin/workshop-entrypoint'
|
||||
fi
|
||||
|
||||
HOMARR_WEBSITE_URL=${HOMARR_WEBSITE_URL:-https://homarr.dev}
|
||||
WORKSHOP_API_URL=${WORKSHOP_API_URL:-$HOMARR_WEBSITE_URL}
|
||||
WORKSHOP_WEB_URL=${WORKSHOP_WEB_URL:-$HOMARR_WEBSITE_URL/workshop}
|
||||
|
||||
encode_runtime_value() {
|
||||
printf '%s' "$1" | base64 | tr -d '\n'
|
||||
}
|
||||
|
||||
cat >/pb_public/workshop-runtime-config.js <<EOF
|
||||
const decodeRuntimeValue = (value) =>
|
||||
new TextDecoder().decode(Uint8Array.from(atob(value), (character) => character.charCodeAt(0)));
|
||||
globalThis.homarrRuntimeConfig = Object.freeze({
|
||||
homarrWebsiteUrl: decodeRuntimeValue("$(encode_runtime_value "$HOMARR_WEBSITE_URL")"),
|
||||
workshopApiUrl: decodeRuntimeValue("$(encode_runtime_value "$WORKSHOP_API_URL")"),
|
||||
workshopWebUrl: decodeRuntimeValue("$(encode_runtime_value "$WORKSHOP_WEB_URL")")
|
||||
});
|
||||
EOF
|
||||
|
||||
exec pocketbase serve \
|
||||
--http="0.0.0.0:${PORT:-8090}" \
|
||||
--origins="${PB_ALLOWED_ORIGINS:-*}" \
|
||||
|
||||
@@ -54,4 +54,15 @@ for (const required of [
|
||||
const workflow = await read(".github/workflows/workshop.yml");
|
||||
if (/\n\s+paths:/u.test(workflow)) throw new Error("Workshop workflow must not filter out Docker build inputs");
|
||||
|
||||
const entrypoint = await read("apps/workshop/entrypoint.sh");
|
||||
if (!entrypoint.includes("/pb_public/workshop-runtime-config.js") || !entrypoint.includes("WORKSHOP_API_URL")) {
|
||||
throw new Error("Workshop must publish its API URL when the container starts");
|
||||
}
|
||||
|
||||
const docsConfig = await read("apps/docs/docusaurus.config.ts");
|
||||
if (!docsConfig.includes('scripts: [{ src: "/workshop-runtime-config.js" }]')) {
|
||||
throw new Error("Workshop must load the container runtime configuration before the documentation bundle");
|
||||
}
|
||||
await access(resolve("apps/docs/static/workshop-runtime-config.js"));
|
||||
|
||||
console.log("Workshop static contracts passed");
|
||||
Reference in new issue
Block a user