diff --git a/.env.example b/.env.example index dc77b4a..7a79124 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,7 @@ VITE_CORS_PROXY_SECRET= VITE_WASM_PYMUPDF_URL=https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.14/ VITE_WASM_GS_URL=https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets/ VITE_WASM_CPDF_URL=https://cdn.jsdelivr.net/npm/coherentpdf/dist/ + +# Default UI language (build-time) +# Supported: en, ar, be, fr, de, es, zh, zh-TW, vi, tr, id, it, pt, nl, da +VITE_DEFAULT_LANGUAGE= diff --git a/Dockerfile b/Dockerfile index 5d669d4..6ff58c6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,6 +35,10 @@ ENV VITE_WASM_PYMUPDF_URL=$VITE_WASM_PYMUPDF_URL ENV VITE_WASM_GS_URL=$VITE_WASM_GS_URL ENV VITE_WASM_CPDF_URL=$VITE_WASM_CPDF_URL +# Default UI language (e.g. en, fr, de, es, zh, ar) +ARG VITE_DEFAULT_LANGUAGE +ENV VITE_DEFAULT_LANGUAGE=$VITE_DEFAULT_LANGUAGE + ENV NODE_OPTIONS="--max-old-space-size=3072" RUN npm run build:with-docs diff --git a/Dockerfile.nonroot b/Dockerfile.nonroot new file mode 100644 index 0000000..0cf3b6b --- /dev/null +++ b/Dockerfile.nonroot @@ -0,0 +1,70 @@ +# Non-root Dockerfile — supports PUID/PGID environment variables (LSIO-style) +# Usage: docker build -f Dockerfile.nonroot -t bentopdf . +# docker run -d -p 3000:8080 -e PUID=1000 -e PGID=1000 bentopdf + +ARG BASE_URL= + +# Build stage (identical to main Dockerfile) +FROM public.ecr.aws/docker/library/node:20-alpine AS builder +WORKDIR /app +COPY package*.json ./ +COPY vendor ./vendor +ENV HUSKY=0 +RUN npm config set fetch-retries 5 && \ + npm config set fetch-retry-mintimeout 60000 && \ + npm config set fetch-retry-maxtimeout 300000 && \ + npm config set fetch-timeout 600000 && \ + npm ci +COPY . . + +ARG SIMPLE_MODE=false +ENV SIMPLE_MODE=$SIMPLE_MODE +ARG COMPRESSION_MODE=all +ENV COMPRESSION_MODE=$COMPRESSION_MODE + +ARG BASE_URL +ENV BASE_URL=$BASE_URL + +ARG VITE_WASM_PYMUPDF_URL +ARG VITE_WASM_GS_URL +ARG VITE_WASM_CPDF_URL +ENV VITE_WASM_PYMUPDF_URL=$VITE_WASM_PYMUPDF_URL +ENV VITE_WASM_GS_URL=$VITE_WASM_GS_URL +ENV VITE_WASM_CPDF_URL=$VITE_WASM_CPDF_URL + +# Default UI language (e.g. en, fr, de, es, zh, ar) +ARG VITE_DEFAULT_LANGUAGE +ENV VITE_DEFAULT_LANGUAGE=$VITE_DEFAULT_LANGUAGE + +ENV NODE_OPTIONS="--max-old-space-size=3072" + +RUN npm run build:with-docs + +# Production stage — uses standard nginx (starts as root, drops to PUID/PGID) +FROM nginx:stable-alpine-slim + +LABEL org.opencontainers.image.source="https://github.com/alam00000/bentopdf" +LABEL org.opencontainers.image.url="https://github.com/alam00000/bentopdf" + +ARG BASE_URL + +ENV PUID=1000 +ENV PGID=1000 +ENV DISABLE_IPV6=false + +RUN apk add --no-cache su-exec + +COPY --from=builder /app/dist /usr/share/nginx/html${BASE_URL%/} +COPY nginx.conf /etc/nginx/nginx.conf +COPY --chmod=755 entrypoint.sh /entrypoint.sh + +RUN mkdir -p /etc/nginx/tmp \ + /var/cache/nginx/client_temp \ + /var/cache/nginx/proxy_temp \ + /var/cache/nginx/fastcgi_temp \ + /var/cache/nginx/uwsgi_temp \ + /var/cache/nginx/scgi_temp + +EXPOSE 8080 +ENTRYPOINT ["/entrypoint.sh"] +CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md index af47b99..f280b2e 100644 --- a/README.md +++ b/README.md @@ -584,6 +584,14 @@ docker run -p 3000:8080 bentopdf # The app will be accessible at http://localhost:3000/bentopdf/ ``` +**Default Language:** + +Set the default UI language at build time. Users can still switch languages — this only changes the initial default. Supported: `en`, `ar`, `be`, `fr`, `de`, `es`, `zh`, `zh-TW`, `vi`, `tr`, `id`, `it`, `pt`, `nl`, `da`. + +```bash +docker build --build-arg VITE_DEFAULT_LANGUAGE=fr -t bentopdf . +``` + **Combined with Simple Mode:** ```bash @@ -691,6 +699,26 @@ docker build -t bentopdf . docker run -p 8080:8080 bentopdf ``` +#### Custom User ID (PUID/PGID) + +For environments that require running as a specific non-root user (e.g., NAS devices, Kubernetes with security contexts), use the non-root Dockerfile: + +```bash +# Build the non-root image +docker build -f Dockerfile.nonroot -t bentopdf-nonroot . + +# Run with custom UID/GID +docker run -d -p 3000:8080 -e PUID=1000 -e PGID=1000 bentopdf-nonroot +``` + +| Variable | Description | Default | +| -------- | ------------------ | ------- | +| `PUID` | User ID to run as | `1000` | +| `PGID` | Group ID to run as | `1000` | + +> [!NOTE] +> The standard `Dockerfile` uses `nginx-unprivileged` (UID 101) and is recommended for most deployments. Use `Dockerfile.nonroot` only when you need a specific UID/GID. + For detailed security configuration, see [SECURITY.md](SECURITY.md). ### Digital Signature CORS Proxy (Required) diff --git a/docs/self-hosting/docker.md b/docs/self-hosting/docker.md index f9b41ce..fadd5f9 100644 --- a/docs/self-hosting/docker.md +++ b/docs/self-hosting/docker.md @@ -95,16 +95,18 @@ docker run -d -p 3000:8080 bentopdf:custom | `VITE_WASM_PYMUPDF_URL` | PyMuPDF WASM module URL | `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.14/` | | `VITE_WASM_GS_URL` | Ghostscript WASM module URL | `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets/` | | `VITE_WASM_CPDF_URL` | CoherentPDF WASM module URL | `https://cdn.jsdelivr.net/npm/coherentpdf/dist/` | +| `VITE_DEFAULT_LANGUAGE` | Default UI language | `en` | WASM module URLs are pre-configured with CDN defaults — all advanced features work out of the box. Override these for air-gapped or self-hosted deployments. +`VITE_DEFAULT_LANGUAGE` sets the UI language for first-time visitors. Supported values: `en`, `ar`, `be`, `fr`, `de`, `es`, `zh`, `zh-TW`, `vi`, `tr`, `id`, `it`, `pt`, `nl`, `da`. Users can still switch languages — this only changes the default. + Example: ```bash -docker run -d \ - -e SIMPLE_MODE=true \ - -p 3000:8080 \ - ghcr.io/alam00000/bentopdf:latest +# Build with French as the default language +docker build --build-arg VITE_DEFAULT_LANGUAGE=fr -t bentopdf . +docker run -d -p 3000:8080 bentopdf ``` ### Custom WASM URLs (Air-Gapped / Self-Hosted) @@ -147,6 +149,61 @@ docker run -d -p 3000:8080 --restart unless-stopped bentopdf Set a variable to empty string to disable that module (users must configure manually via Advanced Settings). +## Custom User ID (PUID/PGID) + +For environments that require running as a specific non-root user (NAS devices, Kubernetes with security contexts, organizational policies), BentoPDF provides a separate Dockerfile with LSIO-style PUID/PGID support. + +### Build and Run + +```bash +# Build the non-root image +docker build -f Dockerfile.nonroot -t bentopdf-nonroot . + +# Run with custom UID/GID +docker run -d \ + --name bentopdf \ + -p 3000:8080 \ + -e PUID=1000 \ + -e PGID=1000 \ + --restart unless-stopped \ + bentopdf-nonroot +``` + +### Environment Variables + +| Variable | Description | Default | +| -------------- | --------------------- | ------- | +| `PUID` | User ID to run as | `1000` | +| `PGID` | Group ID to run as | `1000` | +| `DISABLE_IPV6` | Disable IPv6 listener | `false` | + +### Docker Compose + +```yaml +services: + bentopdf: + build: + context: . + dockerfile: Dockerfile.nonroot + container_name: bentopdf + ports: + - '3000:8080' + environment: + - PUID=1000 + - PGID=1000 + restart: unless-stopped +``` + +### How It Works + +The container starts as root, creates a user with the specified PUID/PGID, adjusts ownership on all writable directories, then drops privileges using `su-exec`. The nginx process runs entirely as your specified user. + +> [!NOTE] +> The standard `Dockerfile` uses `nginx-unprivileged` (UID 101) and is recommended for most deployments. Use `Dockerfile.nonroot` only when you need a specific UID/GID. + +> [!WARNING] +> PUID/PGID cannot be `0` (root). The entrypoint validates inputs and will exit with an error for invalid values. + ## With Traefik (Reverse Proxy) ```yaml diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..0a26ae5 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,42 @@ +#!/bin/sh +set -e + +PUID=${PUID:-1000} +PGID=${PGID:-1000} + +# Validate PUID/PGID +case "$PUID" in + ''|*[!0-9]*) echo "ERROR: PUID must be a number, got '$PUID'" >&2; exit 1 ;; +esac +case "$PGID" in + ''|*[!0-9]*) echo "ERROR: PGID must be a number, got '$PGID'" >&2; exit 1 ;; +esac +if [ "$PUID" -eq 0 ] || [ "$PGID" -eq 0 ]; then + echo "ERROR: PUID/PGID cannot be 0 (root). Use the standard Dockerfile instead." >&2 + exit 1 +fi + +echo "Starting BentoPDF with PUID=$PUID PGID=$PGID" + +addgroup -g "$PGID" bentopdf 2>/dev/null || true +adduser -u "$PUID" -G bentopdf -D -H -s /sbin/nologin bentopdf 2>/dev/null || true + +rm -f /var/log/nginx/error.log /var/log/nginx/access.log +touch /var/log/nginx/error.log /var/log/nginx/access.log +chown "$PUID:$PGID" /var/log/nginx /var/log/nginx/error.log /var/log/nginx/access.log + +sed -i '1i error_log stderr warn;' /etc/nginx/nginx.conf +sed -i '/^http {/a\ access_log /var/log/nginx/access.log;' /etc/nginx/nginx.conf + +chown -R "$PUID:$PGID" \ + /etc/nginx/tmp \ + /var/cache/nginx \ + /usr/share/nginx/html \ + /etc/nginx/nginx.conf + +if [ "$DISABLE_IPV6" = "true" ]; then + echo "Disabling Nginx IPv6 listener" + sed -i '/^[[:space:]]*listen[[:space:]]*\[::\]:[0-9]*/s/^/#/' /etc/nginx/nginx.conf +fi + +exec su-exec "$PUID:$PGID" "$@" diff --git a/src/js/i18n/i18n.ts b/src/js/i18n/i18n.ts index 546766b..298f4b6 100644 --- a/src/js/i18n/i18n.ts +++ b/src/js/i18n/i18n.ts @@ -69,6 +69,11 @@ export const getLanguageFromUrl = (): SupportedLanguage => { return storedLang as SupportedLanguage; } + const envLang = import.meta.env.VITE_DEFAULT_LANGUAGE; + if (envLang && supportedLanguages.includes(envLang as SupportedLanguage)) { + return envLang as SupportedLanguage; + } + return 'en'; };