feat: add support for default UI language configuration and non-root Dockerfile
This commit is contained in:
@@ -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_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_GS_URL=https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm/assets/
|
||||||
VITE_WASM_CPDF_URL=https://cdn.jsdelivr.net/npm/coherentpdf/dist/
|
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=
|
||||||
|
|||||||
@@ -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_GS_URL=$VITE_WASM_GS_URL
|
||||||
ENV VITE_WASM_CPDF_URL=$VITE_WASM_CPDF_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"
|
ENV NODE_OPTIONS="--max-old-space-size=3072"
|
||||||
|
|
||||||
RUN npm run build:with-docs
|
RUN npm run build:with-docs
|
||||||
|
|||||||
70
Dockerfile.nonroot
Normal file
70
Dockerfile.nonroot
Normal file
@@ -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;"]
|
||||||
28
README.md
28
README.md
@@ -584,6 +584,14 @@ docker run -p 3000:8080 bentopdf
|
|||||||
# The app will be accessible at http://localhost:3000/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:**
|
**Combined with Simple Mode:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -691,6 +699,26 @@ docker build -t bentopdf .
|
|||||||
docker run -p 8080:8080 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).
|
For detailed security configuration, see [SECURITY.md](SECURITY.md).
|
||||||
|
|
||||||
### Digital Signature CORS Proxy (Required)
|
### Digital Signature CORS Proxy (Required)
|
||||||
|
|||||||
@@ -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_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_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_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.
|
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:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d \
|
# Build with French as the default language
|
||||||
-e SIMPLE_MODE=true \
|
docker build --build-arg VITE_DEFAULT_LANGUAGE=fr -t bentopdf .
|
||||||
-p 3000:8080 \
|
docker run -d -p 3000:8080 bentopdf
|
||||||
ghcr.io/alam00000/bentopdf:latest
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Custom WASM URLs (Air-Gapped / Self-Hosted)
|
### 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).
|
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)
|
## With Traefik (Reverse Proxy)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
42
entrypoint.sh
Normal file
42
entrypoint.sh
Normal file
@@ -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" "$@"
|
||||||
@@ -69,6 +69,11 @@ export const getLanguageFromUrl = (): SupportedLanguage => {
|
|||||||
return storedLang as 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';
|
return 'en';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user