mirror of
https://github.com/readest/readest.git
synced 2026-10-16 13:35:18 +00:00
* fix(web): stop truncating request bodies over 10MB (#6091) `middleware.ts` matches /api/*, so Next buffers a clone of every non-GET request body. Past `experimental.proxyClientMaxBodySize` (10MB by default) `getCloneableBody` pushes null into both the middleware's stream and the one `finalize()` splices back over the IncomingMessage, so the route handler sees a short body ending cleanly: no error, no 413, just a stored file of exactly 10,485,760 bytes. `/api/send/inbox/file` accepts 40MB, so an inbox EPUB over 10MB was already being stored corrupt. The clone is buffered in memory before any handler can reject it, which makes the ceiling an unauthenticated memory budget on every /api/* route. Only the self-hosted image, which may proxy whole book files through the Node server, gets real headroom; `standaloneOutput` is the gate because BUILD_STANDALONE is set by the Dockerfile alone and web.readest.com runs on Cloudflare/Vercel. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(settings): stop gating cloud sync as Premium when self-hosted (#6093) Two causes of the same symptom on a self-hosted Docker deployment. The badge keyed off sign-in, not entitlement: !user || (userProfilePlan !== undefined && !isCloudSyncPremium) `!user` short-circuits, so a signed-out self-hoster — the normal state right after `docker compose up` — got a Premium chip on WebDAV, Google Drive, S3 and OneDrive. The rows opened fine and sync was never paused, but the chip reads as "not available in this deployment". `shouldShowCloudProviderBadge` decides on entitlement instead: self-hosting unlocks with or without a signed-in user. `SELF_HOSTED` also only reached the container through `compose.yaml` (`${SELF_HOSTED:-true}`), which landed in #5996. A compose file copied before that survives every `docker compose pull`, so those deployments came up gated with nothing on screen to explain why. The image now defaults it on: it is the self-hosted artifact, web.readest.com never runs it, and an operator selling plans still overrides with SELF_HOSTED=false. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(settings): clarify that the tier chip still gates signed-out visitors The doc comment described what changed rather than what the predicate does, so "entitlement is the deciding input, not sign-in state" read as if signed-out users stopped being gated. They are not: a signed-out visitor to a hosted deployment is not entitled, so the row is badged and stays behind canToggleCloudProvider and the upgrade route, exactly as before. Only entitlement — which self-hosting confers — removes the chip. Assert the pairing in one test so a future refactor cannot decouple the badge from the gate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
79 lines
4 KiB
Docker
79 lines
4 KiB
Docker
FROM docker.io/library/node:24-slim@sha256:24dc26ef1e3c3690f27ebc4136c9c186c3133b25563ae4d7f0692e4d1fe5db0e AS dependencies
|
|
ENV PNPM_HOME="/pnpm"
|
|
ENV PATH="$PNPM_HOME:$PATH"
|
|
RUN corepack enable
|
|
RUN corepack prepare pnpm@11.1.1 --activate
|
|
WORKDIR /app
|
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
COPY apps/readest-app/package.json ./apps/readest-app/
|
|
COPY patches/ ./patches/
|
|
COPY packages/ ./packages/
|
|
RUN --mount=type=cache,id=pnpm,sharing=locked,target=/pnpm/store pnpm install --frozen-lockfile
|
|
RUN test -f packages/foliate-js/vendor/pdfjs/annotation_layer_builder.css \
|
|
&& test -d packages/simplecc-wasm/dist/web \
|
|
|| { printf '\nERROR: Required git submodules are not initialized in the source directory.\nEnsure submodules are initialized before running docker build.\nRun: git submodule update --init packages/foliate-js packages/simplecc-wasm\n\n'; exit 1; }
|
|
RUN pnpm --filter @readest/readest-app setup-vendors
|
|
|
|
FROM docker.io/library/node:24-slim@sha256:24dc26ef1e3c3690f27ebc4136c9c186c3133b25563ae4d7f0692e4d1fe5db0e AS development-stage
|
|
ENV PNPM_HOME="/pnpm"
|
|
ENV PATH="$PNPM_HOME:$PATH"
|
|
RUN corepack enable
|
|
RUN corepack prepare pnpm@11.1.1 --activate
|
|
WORKDIR /app
|
|
COPY --from=dependencies /app /app
|
|
COPY . .
|
|
WORKDIR /app/apps/readest-app
|
|
EXPOSE 3000
|
|
ENTRYPOINT ["pnpm", "dev-web", "-H", "0.0.0.0"]
|
|
|
|
FROM docker.io/library/node:24-slim@sha256:24dc26ef1e3c3690f27ebc4136c9c186c3133b25563ae4d7f0692e4d1fe5db0e AS build
|
|
ENV PNPM_HOME="/pnpm"
|
|
ENV PATH="$PNPM_HOME:$PATH"
|
|
RUN corepack enable
|
|
RUN corepack prepare pnpm@11.1.1 --activate
|
|
WORKDIR /app
|
|
ARG NEXT_PUBLIC_SUPABASE_URL
|
|
ARG NEXT_PUBLIC_SUPABASE_ANON_KEY
|
|
ARG NEXT_PUBLIC_APP_PLATFORM
|
|
ARG NEXT_PUBLIC_API_BASE_URL
|
|
ARG NEXT_PUBLIC_OBJECT_STORAGE_TYPE
|
|
ARG NEXT_PUBLIC_STORAGE_FIXED_QUOTA
|
|
ARG NEXT_PUBLIC_TRANSLATION_FIXED_QUOTA
|
|
COPY --from=dependencies /app/node_modules /app/node_modules
|
|
COPY --from=dependencies /app/apps/readest-app/node_modules /app/apps/readest-app/node_modules
|
|
COPY --from=dependencies /app/apps/readest-app/public/vendor /app/apps/readest-app/public/vendor
|
|
COPY --from=dependencies /app/packages/foliate-js/node_modules /app/packages/foliate-js/node_modules
|
|
COPY . .
|
|
WORKDIR /app/apps/readest-app
|
|
# Opt into the self-contained `.next/standalone` tree for this image only;
|
|
# next.config.mjs gates `output: 'standalone'` on BUILD_STANDALONE so other
|
|
# web builds keep their default output.
|
|
ENV BUILD_STANDALONE=true
|
|
RUN pnpm build-web
|
|
|
|
# Production runtime ships only the standalone server, its traced node_modules,
|
|
# and the static/public assets — no pnpm, no source tree, no dev dependencies,
|
|
# no build cache. `output: 'standalone'` (next.config.mjs) emits the self-contained
|
|
# tree under .next/standalone, so the entrypoint is a plain `node server.js`.
|
|
FROM docker.io/library/node:24-slim@sha256:24dc26ef1e3c3690f27ebc4136c9c186c3133b25563ae4d7f0692e4d1fe5db0e AS production-stage
|
|
ENV NODE_ENV=production
|
|
ENV PORT=3000
|
|
ENV HOSTNAME=0.0.0.0
|
|
# This image is the self-hosted artifact — web.readest.com runs on
|
|
# Cloudflare/Vercel, never on it — so premium features are unlocked by default
|
|
# rather than left to the operator's compose file. `compose.yaml` passes
|
|
# `SELF_HOSTED: ${SELF_HOSTED:-true}`, but that line only landed in #5996: a
|
|
# compose file copied before it survives every `docker compose pull`, so the
|
|
# container came up gated with nothing on screen to explain why (#6093).
|
|
# An operator running a hosted service that sells plans sets SELF_HOSTED=false.
|
|
ENV SELF_HOSTED=true
|
|
WORKDIR /app
|
|
# Monorepo-rooted standalone tree: server.js + hoisted, traced node_modules.
|
|
COPY --from=build --chown=node:node /app/apps/readest-app/.next/standalone ./
|
|
# Static and public assets are not part of the standalone trace; copy them next
|
|
# to the server so their default relative paths resolve.
|
|
COPY --from=build --chown=node:node /app/apps/readest-app/.next/static ./apps/readest-app/.next/static
|
|
COPY --from=build --chown=node:node /app/apps/readest-app/public ./apps/readest-app/public
|
|
USER node
|
|
EXPOSE 3000
|
|
ENTRYPOINT ["node", "apps/readest-app/server.js"]
|