feat: translate all pages, UI messages and docs and fix SEO redirects and indexing

This commit is contained in:
alam00000 committed 2026-10-25 12:55:21 +05:30
1 parent 5e019de5ae
commit 4edb1718cc
3344 files changed
+415643 -4759

No files matched your search

+275
View File
@@ -0,0 +1,275 @@
# Mit Apache bereitstellen
Hosten Sie BentoPDF mit dem Apache HTTP Server.
## Voraussetzungen
- Apache 2.4+
- mod_rewrite aktiviert
- SSL-Zertifikat (empfohlen)
## Schritt 1: Projekt bauen
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
Um das Branding anzupassen, setzen Sie vor dem Build Umgebungsvariablen:
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## Schritt 2: Dateien kopieren
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## Schritt 3: Apache-Konfiguration
Erstellen Sie `/etc/apache2/sites-available/bentopdf.conf`:
```apache
<VirtualHost *:80>
ServerName your-domain.com
Redirect permanent / https://your-domain.com/
</VirtualHost>
<VirtualHost *:443>
ServerName your-domain.com
DocumentRoot /var/www/bentopdf
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/your-domain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/your-domain.com/privkey.pem
<Directory /var/www/bentopdf>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
# WASM MIME type
AddType application/wasm .wasm
# Prevent double-compression of pre-compressed files
SetEnvIfNoCase Request_URI "\.gz$" no-gzip
# Compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript application/json application/wasm
</IfModule>
# Cache headers
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/wasm "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
</IfModule>
# Required headers for SharedArrayBuffer (LibreOffice WASM)
Header always set Cross-Origin-Embedder-Policy "require-corp"
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Resource-Policy "cross-origin"
# Security headers
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
# Pre-compressed LibreOffice WASM files
<FilesMatch "soffice\.wasm\.gz$">
ForceType application/wasm
Header set Content-Encoding "gzip"
</FilesMatch>
<FilesMatch "soffice\.data\.gz$">
ForceType application/octet-stream
Header set Content-Encoding "gzip"
</FilesMatch>
</VirtualHost>
```
## Schritt 4: .htaccess für das Routing
Erstellen Sie `/var/www/bentopdf/.htaccess`:
```apache
RewriteEngine On
RewriteBase /
# Existing files/dirs - serve directly
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# ============================================
# LANGUAGE ROUTES
# ============================================
# Supported languages: de, es, zh, zh-TW, vi, it, id, tr, fr, pt
# English has no prefix - served from root
# English prefix redirects to root
RewriteRule ^en/?$ / [R=301,L]
RewriteRule ^en/(.+)$ /$1 [R=301,L]
# Language prefix root (e.g., /de/ -> /de/index.html)
RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /$1/index.html [L]
# Language prefix with path (e.g., /de/merge-pdf -> /de/merge-pdf.html)
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# ============================================
# ADD .HTML EXTENSION (ROOT LEVEL)
# ============================================
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
ErrorDocument 404 /404.html
```
## Schritt 5: Erforderliche Module aktivieren
```bash
sudo a2enmod rewrite
sudo a2enmod ssl
sudo a2enmod headers
sudo a2enmod expires
sudo a2enmod deflate
```
## Schritt 6: Die Site aktivieren
```bash
sudo a2ensite bentopdf.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
```
## Bereitstellung in einem Unterverzeichnis
So hosten Sie unter `/pdf/`:
1. Mit Basis-URL bauen:
```bash
BASE_URL=/pdf/ npm run build
```
2. `.htaccess` anpassen:
```apache
RewriteEngine On
RewriteBase /pdf/
# Existing files/dirs - serve directly
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# Language routes
RewriteRule ^en/?$ /pdf/ [R=301,L]
RewriteRule ^en/(.+)$ /pdf/$1 [R=301,L]
RewriteCond %{DOCUMENT_ROOT}/pdf/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /pdf/$1/index.html [L]
RewriteCond %{DOCUMENT_ROOT}/pdf/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /pdf/$1/$2.html [L]
# Root level .html extension
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
ErrorDocument 404 /pdf/404.html
```
## Fehlerbehebung
### WASM-404-Fehler
Stellen Sie sicher, dass der MIME-Typ konfiguriert ist:
```apache
AddType application/wasm .wasm
```
### Signieren oder Formularausfüllung zeigt einen leeren Viewer (`.mjs`-MIME-Fehler)
Wenn die Browserkonsole Folgendes anzeigt:
```
Failed to load module script: The server responded with a non-JavaScript
MIME type of "application/octet-stream". Strict MIME type checking is
enforced for module scripts per HTML spec.
```
...und die fehlgeschlagene Anfrage auf `.mjs` endet, fehlt in Ihrer Apache-Konfiguration die Zuordnung `.mjs → application/javascript`. Der mitgelieferte PDF-Viewer enthält ES-Moduldateien mit der Endung `.mjs`, die in der Standard-`mime.types` von Apache nicht enthalten sind.
Fügen Sie Folgendes zu Ihrem VirtualHost oder Ihrer `.htaccess` hinzu:
```apache
AddType application/javascript .mjs
```
Laden Sie Apache anschließend neu:
```bash
sudo apache2ctl configtest && sudo systemctl reload apache2
```
Zur Kontrolle: Suchen Sie in den Browser-DevTools im Tab Netzwerk die fehlgeschlagene `.mjs`-Anfrage und prüfen Sie den Antwort-Header `Content-Type`. Er sollte jetzt `application/javascript` lauten, nicht `application/octet-stream`.
### Rewrite funktioniert nicht
Prüfen Sie, ob mod_rewrite aktiviert ist:
```bash
sudo a2enmod rewrite
```
### Word/ODT/Excel zu PDF funktioniert nicht
LibreOffice WASM benötigt `SharedArrayBuffer`, wofür diese Header erforderlich sind:
```apache
Header always set Cross-Origin-Embedder-Policy "require-corp"
Header always set Cross-Origin-Opener-Policy "same-origin"
```
Außerdem ist ein sicherer Kontext nötig. `http://localhost` funktioniert für lokale Tests, aber `http://192.168.x.x` oder andere LAN-IPs erfordern meist HTTPS. Sind die Header vorhanden und `window.crossOriginIsolated` ist trotzdem `false`, prüfen Sie, ob die Seite über einfaches HTTP von einem Nicht-Loopback-Origin aus geöffnet wird.
Die vorkomprimierten Dateien `.wasm.gz` und `.data.gz` brauchen außerdem das richtige `Content-Encoding`:
```apache
<FilesMatch "soffice\.wasm\.gz$">
ForceType application/wasm
Header set Content-Encoding "gzip"
</FilesMatch>
<FilesMatch "soffice\.data\.gz$">
ForceType application/octet-stream
Header set Content-Encoding "gzip"
</FilesMatch>
```
Stellen Sie sicher, dass `mod_headers` aktiviert ist: `sudo a2enmod headers`
### Zugriff verweigert
```bash
sudo chown -R www-data:www-data /var/www/bentopdf
sudo chmod -R 755 /var/www/bentopdf
```
+200
View File
@@ -0,0 +1,200 @@
# Auf AWS S3 + CloudFront bereitstellen
Hosten Sie BentoPDF auf AWS für maximale Kontrolle und Skalierbarkeit.
## Architektur
```
User → CloudFront (CDN) → S3 (Static Files)
```
## Schritt 1: S3-Bucket erstellen
```bash
# Create bucket
aws s3 mb s3://your-bentopdf-bucket --region us-east-1
# Enable static website hosting
aws s3 website s3://your-bentopdf-bucket \
--index-document index.html \
--error-document index.html
```
## Schritt 2: Bauen und hochladen
```bash
# Build the project (optionally with custom branding)
# VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
npm run build
# Sync to S3
aws s3 sync dist/ s3://your-bentopdf-bucket \
--delete \
--cache-control "max-age=31536000"
# Set correct MIME types for WASM
aws s3 cp s3://your-bentopdf-bucket/ s3://your-bentopdf-bucket/ \
--recursive \
--exclude "*" \
--include "*.wasm" \
--content-type "application/wasm" \
--metadata-directive REPLACE
# Set correct MIME type for ES module files (.mjs)
# S3 defaults .mjs to application/octet-stream, which browsers refuse to
# execute as ES modules. The bundled PDF viewer (Sign PDF, Form Filler)
# ships ES modules with .mjs extensions and will fail without this step.
aws s3 cp s3://your-bentopdf-bucket/ s3://your-bentopdf-bucket/ \
--recursive \
--exclude "*" \
--include "*.mjs" \
--content-type "application/javascript" \
--metadata-directive REPLACE
```
## Schritt 3: CloudFront-Distribution erstellen
```bash
aws cloudfront create-distribution \
--origin-domain-name your-bentopdf-bucket.s3.amazonaws.com \
--default-root-object index.html
```
Oder nutzen Sie die AWS-Konsole:
1. Gehen Sie zu CloudFront → Create distribution
2. Origin domain: Wählen Sie Ihren S3-Bucket aus
3. Aktivieren Sie "Origin Access Control"
4. Default root object: `index.html`
5. Erstellen Sie die Distribution
## Schritt 3b: Response Headers Policy (erforderlich für LibreOffice WASM)
LibreOffice-basierte Konvertierungen (Word, Excel, PowerPoint zu PDF) benötigen `SharedArrayBuffer`, wofür bestimmte Response-Header nötig sind. Erstellen Sie eine CloudFront Response Headers Policy:
1. Gehen Sie zu CloudFront → Policies → Response headers
2. Erstellen Sie eine eigene Policy mit diesen Headern:
| Header | Wert |
| ------------------------------ | -------------- |
| `Cross-Origin-Embedder-Policy` | `require-corp` |
| `Cross-Origin-Opener-Policy` | `same-origin` |
| `Cross-Origin-Resource-Policy` | `cross-origin` |
3. Weisen Sie die Policy dem Default Cache Behavior Ihrer Distribution zu
Oder per CLI:
```bash
aws cloudfront create-response-headers-policy \
--response-headers-policy-config '{
"Name": "BentoPDF-COEP-COOP",
"CustomHeadersConfig": {
"Quantity": 3,
"Items": [
{"Header": "Cross-Origin-Embedder-Policy", "Value": "require-corp", "Override": true},
{"Header": "Cross-Origin-Opener-Policy", "Value": "same-origin", "Override": true},
{"Header": "Cross-Origin-Resource-Policy", "Value": "cross-origin", "Override": true}
]
}
}'
```
## Schritt 3c: S3-Metadaten für vorkomprimierte WASM-Dateien
Die LibreOffice-WASM-Dateien sind vorkomprimiert (`.wasm.gz`, `.data.gz`). Setzen Sie den richtigen Content-Type und das richtige Content-Encoding, damit Browser sie entpacken:
```bash
# Set correct headers for soffice.wasm.gz
aws s3 cp s3://your-bentopdf-bucket/libreoffice-wasm/soffice.wasm.gz \
s3://your-bentopdf-bucket/libreoffice-wasm/soffice.wasm.gz \
--content-type "application/wasm" \
--content-encoding "gzip" \
--metadata-directive REPLACE
# Set correct headers for soffice.data.gz
aws s3 cp s3://your-bentopdf-bucket/libreoffice-wasm/soffice.data.gz \
s3://your-bentopdf-bucket/libreoffice-wasm/soffice.data.gz \
--content-type "application/octet-stream" \
--content-encoding "gzip" \
--metadata-directive REPLACE
```
::: warning Wichtig
Ohne die Response Headers Policy ist `SharedArrayBuffer` nicht verfügbar, und LibreOffice-WASM-Konvertierungen bleiben bei ca. 55% hängen. Ohne das richtige Content-Encoding für die `.gz`-Dateien erhält der Browser rohe gzip-Bytes, und die WASM-Kompilierung schlägt fehl.
:::
## Schritt 4: S3-Bucket-Policy
Erlauben Sie CloudFront den Zugriff auf den Bucket:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontAccess",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bentopdf-bucket/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
}
}
}
]
}
```
## Schritt 5: Eigene Fehlerseiten
Konfigurieren Sie 404 so, dass für das SPA-Routing `index.html` zurückgegeben wird:
1. CloudFront → Error pages
2. Erstellen Sie eine eigene Fehlerantwort:
- HTTP error code: 404
- Response page path: `/index.html`
- HTTP response code: 200
## Kostenschätzung
| Ressource | Geschätzte Kosten |
| -------------------------- | ----------------- |
| S3 Storage (~500MB) | ~$0.01/Monat |
| CloudFront (1TB transfer) | ~$85/Monat |
| CloudFront (10GB transfer) | ~$0.85/Monat |
::: tip
Nutzen Sie S3 Intelligent Tiering zur Kostenoptimierung bei selten abgerufenen Dateien.
:::
## Automatisierung mit Terraform
```hcl
# main.tf
resource "aws_s3_bucket" "bentopdf" {
bucket = "your-bentopdf-bucket"
}
resource "aws_cloudfront_distribution" "bentopdf" {
origin {
domain_name = aws_s3_bucket.bentopdf.bucket_regional_domain_name
origin_id = "S3Origin"
}
enabled = true
default_root_object = "index.html"
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3Origin"
viewer_protocol_policy = "redirect-to-https"
}
}
```
+154
View File
@@ -0,0 +1,154 @@
# Auf Cloudflare Pages bereitstellen
[Cloudflare Pages](https://pages.cloudflare.com) bietet schnelles, globales Hosting für statische Websites mit unbegrenzter Bandbreite.
## Schnelle Bereitstellung
1. Gehen Sie zu [Cloudflare Pages](https://dash.cloudflare.com/?to=/:account/pages)
2. Klicken Sie auf "Create a project"
3. Verbinden Sie Ihr GitHub-Repository
## Build-Konfiguration
| Einstellung | Wert |
| ---------------------- | --------------- |
| Framework preset | None |
| Build command | `npm run build` |
| Build output directory | `dist` |
| Root directory | `/` |
## Umgebungsvariablen
Fügen Sie diese unter Settings → Environment variables hinzu:
| Variable | Wert |
| ----------------------- | -------------------------------------------------- |
| `NODE_VERSION` | `18` |
| `SIMPLE_MODE` | `false` (optional) |
| `VITE_BRAND_NAME` | Eigener Markenname (optional) |
| `VITE_BRAND_LOGO` | Logo-Pfad relativ zu `public/` (optional) |
| `VITE_FOOTER_TEXT` | Eigener Footer-/Copyright-Text (optional) |
| `VITE_DEFAULT_LANGUAGE` | Standard-Oberflächensprache, z. B. `fr` (optional) |
## Konfigurationsdatei
Erstellen Sie `_headers` in Ihrem Ordner `public`:
```
# Required security headers for SharedArrayBuffer (used by LibreOffice WASM)
/*
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: cross-origin
# Pre-compressed LibreOffice WASM binary
/libreoffice-wasm/soffice.wasm.gz
Content-Type: application/wasm
Content-Encoding: gzip
Cache-Control: public, max-age=31536000, immutable
# Pre-compressed LibreOffice WASM data
/libreoffice-wasm/soffice.data.gz
Content-Type: application/octet-stream
Content-Encoding: gzip
Cache-Control: public, max-age=31536000, immutable
# Cache WASM files aggressively
/*.wasm
Cache-Control: public, max-age=31536000, immutable
Content-Type: application/wasm
# Service worker
/sw.js
Cache-Control: no-cache
```
::: warning Wichtig
Die Header `Cross-Origin-Embedder-Policy` und `Cross-Origin-Opener-Policy` sind für Konvertierungen von Word/ODT/Excel/PowerPoint zu PDF erforderlich. Ohne sie ist `SharedArrayBuffer` nicht verfügbar, und die LibreOffice-WASM-Engine kann nicht initialisiert werden.
:::
Erstellen Sie `_redirects` für das SPA-Routing:
```
/* /index.html 200
```
## Eigene Domain
1. Öffnen Sie Ihr Pages-Projekt
2. Klicken Sie auf "Custom domains"
3. Fügen Sie Ihre Domain hinzu
4. Cloudflare konfiguriert das DNS automatisch, wenn die Domain bei Cloudflare liegt
## Vorteile
- **Kostenlose, unbegrenzte Bandbreite**
- **Globales CDN** mit über 300 Edge-Standorten
- **Automatisches HTTPS**
- **Vorschau-Bereitstellungen** für Pull Requests
- **Schnelle Builds**
## Fehlerbehebung
### Upload großer Dateien
Cloudflare Pages unterstützt Dateien bis 25 MB. WASM-Module sollten kein Problem sein, aber wenn Sie an Grenzen stoßen, sollten Sie Folgendes überlegen:
```bash
# Split large files during build
npm run build
```
### Größenlimits für Worker
Wenn Sie Cloudflare Workers für erweitertes Routing nutzen, beachten Sie das Limit von 1 MB in kostenlosen Tarifen.
## CORS-Proxy-Worker (für digitale Signaturen)
Das Werkzeug für digitale Signaturen benötigt einen CORS-Proxy, um Zertifikatsketten abzurufen. Stellen Sie den mitgelieferten Worker bereit:
```bash
cd cloudflare
npx wrangler login
npx wrangler deploy
```
### Sicherheitsfunktionen
| Funktion | Beschreibung |
| ---------------------------- | ----------------------------------- |
| **URL-Beschränkungen** | Nur Zertifikat-URLs erlaubt |
| **Dateigrößenlimit** | Max. 10MB pro Anfrage |
| **Ratenbegrenzung** | 60 Anfr./IP/Min. (erfordert KV) |
| **Blockierung privater IPs** | Blockiert localhost und interne IPs |
### Ratenbegrenzung aktivieren
```bash
# Create KV namespace
npx wrangler kv namespace create "RATE_LIMIT_KV"
# Add to wrangler.toml with returned ID:
# [[kv_namespaces]]
# binding = "RATE_LIMIT_KV"
# id = "YOUR_ID"
npx wrangler deploy
```
### Mit Proxy-URL bauen
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
Oder mit Docker:
```bash
export VITE_CORS_PROXY_URL="https://your-worker.workers.dev"
DOCKER_BUILDKIT=1 docker build \
--secret id=VITE_CORS_PROXY_URL,env=VITE_CORS_PROXY_URL \
-t your-bentopdf .
```
> **Hinweis:** Die Einrichtung der HMAC-Signatur finden Sie in der [README](https://github.com/alam00000/bentopdf#digital-signature-cors-proxy-required).
+180
View File
@@ -0,0 +1,180 @@
# CORS-Proxy zum Abrufen von Zertifikaten
Das Werkzeug für digitale Signaturen nutzt einen CORS-Proxy, um Aussteller-Zertifikate von externen Zertifizierungsstellen (CAs) abzurufen. Das ist nötig, weil viele CA-Server keine CORS-Header in ihren Antworten mitsenden, wodurch ein direkter Abruf aus dem Browser verhindert wird.
Außerdem liefern viele CA-Server Zertifikate über einfaches HTTP aus. Wird Ihre BentoPDF-Instanz über HTTPS gehostet, blockieren Browser diese HTTP-Anfragen (Mixed-Content-Richtlinie). Der CORS-Proxy löst beide Probleme, indem er Anfragen über einen HTTPS-Endpunkt mit den passenden Headern leitet.
## So funktioniert es
Beim Signieren einer PDF mit einem Zertifikat:
1. Die Bibliothek `zgapdfsigner` versucht, eine vollständige Zertifikatskette aufzubauen
2. Sie ruft Aussteller-Zertifikate von URLs ab, die in der AIA-Erweiterung (Authority Information Access) Ihres Zertifikats eingebettet sind
3. Diese Anfragen laufen über einen CORS-Proxy, der die nötigen `Access-Control-Allow-Origin`-Header ergänzt
4. Der Proxy liefert die Zertifikatsdaten an den Browser zurück
## Den CORS-Proxy selbst hosten
Wenn Sie BentoPDF selbst hosten, müssen Sie einen eigenen CORS-Proxy bereitstellen, damit digitale Signaturen mit Zertifikaten funktionieren, die einen Abruf der Kette erfordern.
### Option 1: Cloudflare Workers (empfohlen)
1. **Wrangler CLI installieren**:
```bash
npm install -g wrangler
```
2. **Bei Cloudflare anmelden**:
```bash
wrangler login
```
3. **BentoPDF klonen und erlaubte Origins anpassen**:
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf/cloudflare
```
Öffnen Sie `cors-proxy-worker.js` und änderen Sie das Array `ALLOWED_ORIGINS` auf Ihre Domain:
```js
const ALLOWED_ORIGINS = [
'https://your-domain.com',
'https://www.your-domain.com',
];
```
::: warning Wichtig
Ohne diese Änderung lehnt der Proxy alle Anfragen von Ihrer Website mit dem Fehler **403 Forbidden** ab. Standardmäßig sind nur Anfragen von `bentopdf.com` erlaubt.
:::
4. **Den Proxy bereitstellen**:
```bash
wrangler deploy
```
Notieren Sie sich die URL Ihres Workers (z. B. `https://bentopdf-cors-proxy.your-subdomain.workers.dev`).
5. **BentoPDF mit der Proxy-URL neu bauen**:
Bei Verwendung von Docker:
```bash
export VITE_CORS_PROXY_URL="https://your-worker.workers.dev"
DOCKER_BUILDKIT=1 docker build \
--secret id=VITE_CORS_PROXY_URL,env=VITE_CORS_PROXY_URL \
-t your-bentopdf .
```
Beim Bauen aus dem Quellcode:
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
### Option 2: Eigener Backend-Proxy
Sie können auch einen eigenen Proxy-Endpunkt erstellen. Die Anforderungen sind:
1. GET-Anfragen mit einem Query-Parameter `url` annehmen
2. Die URL von Ihrem Server aus abrufen (serverseitig gibt es keine CORS-Beschränkungen)
3. Die Antwort mit diesen Headern zurückgeben:
- `Access-Control-Allow-Origin: https://your-domain.com`
- `Access-Control-Allow-Methods: GET, OPTIONS`
- `X-Content-Type-Options: nosniff`
Beispielimplementierung mit Express.js:
```javascript
app.get('/api/cert-proxy', async (req, res) => {
const targetUrl = req.query.url;
// Validate it's a certificate URL
if (!isValidCertUrl(targetUrl)) {
return res.status(400).json({ error: 'Invalid URL' });
}
try {
const response = await fetch(targetUrl);
const data = await response.arrayBuffer();
res.set('Access-Control-Allow-Origin', 'https://your-domain.com');
res.set('Content-Type', 'application/octet-stream');
res.set('X-Content-Type-Options', 'nosniff');
res.send(Buffer.from(data));
} catch (error) {
res.status(500).json({ error: 'Proxy error' });
}
});
```
## Sicherheitshinweise
Der mitgelieferte Cloudflare Worker hat mehrere Sicherheitsmaßnahmen:
| Funktion | Beschreibung |
| ---------------------------- | ----------------------------------------------------------------------------------------------- |
| **Origin-Prüfung** | Erlaubt nur Anfragen von Domains aus `ALLOWED_ORIGINS` |
| **URL-Beschränkungen** | Erlaubt nur Zertifikat-URLs (`.crt`, `.cer`, `.pem`, `/certs/`, `/ocsp`, `/crl`) |
| **Blockierung privater IPs** | Blockiert private IPv4-/IPv6-Bereiche, Link-Local, Loopback, dezimale IPs und Cloud-Metadaten |
| **Content-Type-Sicherheit** | Liefert nur sicheren Sie Zertifikat-MIME-Typen, blockiert Content-Type-Injection vom Upstream |
| **Dateigrößenlimit** | Streamt die Antwort mit 10MB-Limit und bricht den Download bei Überschreitung ab |
| **Ratenbegrenzung** | 60 Anfragen pro Minute pro IP oder IPv6 /64 (erfordert KV, bei KV-Fehlern wird nicht blockiert) |
| **HMAC-Signaturen** | Optionale clientseitige Signierung (schreckt gelegentlichen Missbrauch ab) |
### DNS-Rebinding (Selbst-Hosting außerhalb von Cloudflare)
Der Worker lehnt jede URL ab, die auf eine private oder interne IP aufgelöst wird. Eine feste Allowlist von Hosts ist nicht möglich, da Zertifikatsketten-URLs auf die jeweilige CA verweisen, die das Zertifikat ausgestellt hat (FNMT, Unternehmens-CAs usw.), die Ziele also nicht im Voraus bekannt sind. Dadurch bleibt ein kleines Rebinding-Fenster: Wer das DNS für seine eigene Domain betreibt, könnte der Sicherheitsprüfung eine öffentliche IP liefern und der eigentlichen Anfrage einen Moment später eine private.
Auf Cloudflare ist das nicht ausnutzbar: Workers können weder private IPs noch den Metadaten-Endpunkt `169.254.169.254` erreichen, ein Rebind läuft also ins Leere. Geben Sie dem Worker nur keinen Zugang: Hängen Sie keinen Tunnel, Service oder kein Private-Network-Binding an (er braucht nur `RATE_LIMIT_KV`).
Betreiben Sie ihn außerhalb von Cloudflare (workerd, Node und Ähnliches) in einer VPC, _kann_ die Runtime interne IPs erreichen. Setzen Sie eine Egress-Firewall davor und blockieren Sie die privaten Bereiche: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `127.0.0.0/8`, `169.254.0.0/16` sowie die IPv6-Entsprechungen. Anwendungscode kann das außerhalb von Cloudflare nicht durchsetzen.
## Den Proxy deaktivieren
Wenn Sie keinen CORS-Proxy nutzen möchten, setzen Sie die Umgebungsvariable auf einen leeren String:
```
VITE_CORS_PROXY_URL=
```
**Hinweis**: Ohne den Proxy schlägt das Signieren mit Zertifikaten, die einen externen Abruf der Kette erfordern (wie FNMT oder einige Unternehmens-CAs), mit dem Fehler "Failed to fetch" fehl.
## Fehlerbehebung
### "Signing error: TypeError: Failed to fetch"
Das bedeutet meist eines von beidem:
1. **Kein CORS-Proxy konfiguriert**: Setzen Sie `VITE_CORS_PROXY_URL` und bauen Sie neu
2. **Mixed Content blockiert**: Ihre Website nutzt HTTPS, aber die Aussteller-URL des Zertifikats ist HTTP. Der CORS-Proxy löst das.
3. **CORS-Proxy lehnt Ihren Origin ab**: Prüfen Sie, ob Ihre Domain im Array `ALLOWED_ORIGINS` in `cors-proxy-worker.js` steht
### "403 Forbidden" vom Proxy
Ihre Domain steht nicht in der Liste `ALLOWED_ORIGINS`. Bearbeiten Sie `cors-proxy-worker.js`:
```js
const ALLOWED_ORIGINS = ['https://your-domain.com'];
```
Stellen Sie danach erneut bereit: `npx wrangler deploy`
### Den Proxy testen
```bash
curl -H "Origin: https://your-domain.com" \
"https://your-proxy.workers.dev?url=http://www.cert.fnmt.es/certs/ACUSU.crt"
```
### Zertifikate, die ohne Proxy funktionieren
Manche Zertifikate enthalten die vollständige Kette in der P12/PFX-Datei und benötigen keinen externen Abruf:
- Selbstsignierte Zertifikate
- Einige kommerzielle CAs, die Zwischenzertifikate mitliefern
- Zertifikate, die Sie manuell mit der vollständigen Kette zusammengestellt haben
+622
View File
@@ -0,0 +1,622 @@
# Mit Docker / Podman bereitstellen
Der einfachste Weg, BentoPDF in einer Produktionsumgebung selbst zu hosten.
> [!IMPORTANT]
> **Erforderliche Header für die Konvertierung von Office-Dateien**
>
> LibreOffice-basierte Werkzeuge (Word-, Excel-, PowerPoint-Konvertierung) benötigen diese HTTP-Header für die `SharedArrayBuffer`-Unterstützung:
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> Die Seite muss außerdem aus einem sicheren Kontext ausgeliefert werden. `http://localhost` funktioniert für lokale Tests, aber `http://192.168.x.x` oder andere LAN-IPs gelten meist nicht als sicher, daher schlägt die Office-Konvertierung über einfaches HTTP fehl, auch wenn die Header vorhanden sind.
>
> Die offiziellen Container-Images enthalten diese Header. Wenn Sie einen Reverse-Proxy (Traefik, Caddy usw.) verwenden, stellen Sie sicher, dass diese Header erhalten bleiben oder hinzugefügt werden, und nutzen Sie HTTPS für Zugriffe außerhalb von Loopback.
> [!TIP]
> **Podman-Nutzer:** Alle `docker`-Befehle funktionieren mit Podman, wenn Sie `docker` durch `podman` und `docker-compose` durch `podman-compose` ersetzen.
## Schnellstart
> [!TIP]
> BentoPDF gibt es in zwei Builds:
>
> - **Self-Hosted-Build**: `ghcr.io/alam00000/bentopdf-simple:latest`. Jedes PDF-Werkzeug, **ohne** Marketing-Elemente (kein Hero, keine FAQ, keine Testimonials, kein Footer). Nutzen Sie ihn für interne Bereitstellungen in Teams und Organisationen. Er ist **keine** funktionsreduzierte "Lite"-Version.
> - **Commercial-Build**: `ghcr.io/alam00000/bentopdf:latest`. Die vollständige Marketing-Website, die bentopdf.com selbst und Inhaber kommerzieller Lizenzen für öffentlich zugängliche Bereitstellungen nutzen.
>
> Im Zweifel: Nehmen Sie den Self-Hosted-Build.
```bash
# Docker
docker run -d \
--name bentopdf \
-p 3000:8080 \
--restart unless-stopped \
ghcr.io/alam00000/bentopdf-simple:latest
# Podman
podman run -d \
--name bentopdf \
-p 3000:8080 \
ghcr.io/alam00000/bentopdf-simple:latest
```
## Docker Compose / Podman Compose
Erstellen Sie `docker-compose.yml`:
```yaml
services:
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
container_name: bentopdf
ports:
- '3000:8080'
restart: unless-stopped
healthcheck:
test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:8080']
interval: 30s
timeout: 10s
retries: 3
```
Ausführen:
```bash
# Docker Compose
docker compose up -d
# Podman Compose
podman-compose up -d
```
## Eigenes Image bauen
```dockerfile
# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginxinc/nginx-unprivileged:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]
```
Bauen und ausführen:
```bash
docker build -t bentopdf:custom .
docker run -d -p 3000:8080 bentopdf:custom
```
## Umgebungsvariablen
| Variable | Beschreibung | Standard |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- |
| `SIMPLE_MODE` | Baut die Self-Hosted-Variante (blendet die Marketing-Elemente von bentopdf.com aus: Hero, FAQ, Testimonials, Footer). Alle PDF-Werkzeuge bleiben erhalten. | `false` |
| `BASE_URL` | In Unterverzeichnis bereitstellen | `/` |
| `VITE_WASM_PYMUPDF_URL` | URL des PyMuPDF-WASM-Moduls | `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/` |
| `VITE_WASM_GS_URL` | URL des Ghostscript-WASM-Moduls | `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm@0.1.1/assets/` |
| `VITE_WASM_CPDF_URL` | URL des CoherentPDF-WASM-Moduls | `https://cdn.jsdelivr.net/npm/coherentpdf@2.5.5/dist/` |
| `VITE_TESSERACT_WORKER_URL` | URL des OCR-Worker-Skripts | _(leer; Standard-CDN von Tesseract.js)_ |
| `VITE_TESSERACT_CORE_URL` | Verzeichnis der OCR-Core-Runtime | _(leer; Standard-CDN von Tesseract.js)_ |
| `VITE_TESSERACT_LANG_URL` | Verzeichnis der OCR-traineddata | _(leer; Standard-CDN von Tesseract.js)_ |
| `VITE_TESSERACT_AVAILABLE_LANGUAGES` | Kommagetrennte OCR-Sprachen, die in der Oberfläche angeboten werden | _(leer; vollständigen Katalog anzeigen)_ |
| `VITE_OCR_FONT_BASE_URL` | Verzeichnis der OCR-Textebenen-Schriften | _(leer; externe Noto-Schrift-URLs)_ |
| `VITE_DEFAULT_LANGUAGE` | Standard UI language | `en` |
| `VITE_BRAND_NAME` | Eigener Markenname | `BentoPDF` |
| `VITE_BRAND_LOGO` | Logo-Pfad relativ zu `public/` | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | Eigener Footer-/Copyright-Text | `© 2026 BentoPDF. All rights reserved.` |
| `DISABLE_TOOLS` | Kommagetrennte Werkzeug-IDs zum Ausblenden | _(leer; alle Werkzeuge aktiviert)_ |
| `DISABLE_GITHUB_STARS` | Überspringt den Abruf der GitHub-Sterne beim Laden der Seite und entfernt `api.github.com` aus der CSP. Simple-Mode-Builds überspringen ihn immer. | `false` |
| `SITE_URL` | Öffentliche URL, die in Canonical-, hreflang- und Sitemap-Links geschrieben wird. Lassen Sie den Standard, sofern Suchmaschinen Ihre Bereitstellung nicht als eigene Website behandeln sollen. | `https://www.bentopdf.com` |
| `ALLOW_INDEXING` | Bei einer eigenen `SITE_URL` wird jede Seite mit `noindex` markiert, damit Ihre Kopie in den Suchergebnissen nicht mit bentopdf.com konkurriert. Mit `true` dürfen Suchmaschinen sie indexieren. | `false` |
Die WASM-Modul-URLs sind mit CDN-Standardwerten vorkonfiguriert, sodass alle erweiterten Funktionen sofort funktionieren. Überschreiben Sie sie für abgeschottete oder selbst gehostete Bereitstellungen.
### Suchmaschinen
Standardmäßig verweist jede Seite Suchmaschinen auf bentopdf.com als Original, sodass Ihre Bereitstellung nicht als Duplikat erscheint. Für eine private oder interne Instanz müssen Sie nichts ändern.
- **Ihre Instanz komplett aus den Suchergebnissen heraushalten:** Starten Sie den Container mit `-e ROBOTS_NOINDEX=true`. Das erfordert nur einen Neustart, keinen Rebuild.
- **Ihre Instanz als eigene Website in den Suchergebnissen erscheinen lassen:** Bauen Sie mit `--build-arg SITE_URL=https://your-domain.example --build-arg ALLOW_INDEXING=true`. Ohne `ALLOW_INDEXING=true` markiert eine eigene `SITE_URL` jede Seite mit `noindex`.
### Content-Security-Policy
Das nginx-Image wird mit einem erzwingenden `Content-Security-Policy`-Header ausgeliefert. Die Direktiven `script-src`, `connect-src` und `font-src` der CSP werden **beim Build** aus den oben genannten `VITE_*`-URL-Variablen **generiert**: Alle Hosts, die Sie per `--build-arg` übergeben, werden automatisch in die Richtlinie aufgenommen.
Daraus folgt:
- Wenn Sie `VITE_CORS_PROXY_URL` oder `VITE_WASM_*_URL` beim Build überschreiben, erlaubt die CSP diese Origins automatisch, ohne zusätzliche Konfiguration.
- Für URLs auf `cdn.jsdelivr.net` erlaubt `script-src` nur den exakten versionierten Paketordner, zum Beispiel `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/`, nicht das gesamte CDN. jsDelivr liefert jedes öffentliche npm-Paket und GitHub-Repository aus, sodass die Freigabe des gesamten Hosts es jedem davon erlauben würde, auf Ihrer Instanz zu laufen. URLs auf allen anderen Hosts werden per Origin erlaubt.
- Wenn Sie eigene WASM-URLs zur _Laufzeit_ über die Seite Erweiterte Einstellungen in der App konfigurieren, stehen diese Origins **nicht** in der CSP, und der Browser blockiert Abrufe dorthin. Das gilt auch für eine andere Version desselben Pakets auf `cdn.jsdelivr.net`, da nur die beim Build festgelegte Version erlaubt ist. Die Konfiguration zur Laufzeit ist für Experimente gedacht; für dauerhafte eigene URLs setzen Sie das passende `VITE_*`-Build-Argument.
- Bei abgeschotteten Bereitstellungen, die alle drei `VITE_WASM_*_URL`-Werte überschreiben, werden auch diese `cdn.jsdelivr.net`-Paketordner aus der CSP entfernt (jeder Standardwert wird ersetzt, nicht ergänzt). Die Tesseract-OCR-Standardwerte auf `cdn.jsdelivr.net` bleiben in der Richtlinie, bis Sie auch die drei `VITE_TESSERACT_*`-Werte setzen. Ebenso ersetzt das Setzen von `VITE_CORS_PROXY_URL` den öffentlichen Standard `bentopdf-cors-proxy.bentopdf.workers.dev`.
Die CSP enthält `'unsafe-eval'` in `script-src`, weil die LibreOffice-WASM-Runtime (für die Word-/Excel-/PowerPoint-Konvertierung) und die Bildbibliothek wasm-vips (für PDF zu TIFF) internen Dispatch-Code über `new Function()` kompilieren. Ohne es würden diese Werkzeuge nicht mehr funktionieren. Wenn Sie im `SIMPLE_MODE` (ohne LibreOffice) bauen und PDF zu TIFF nicht brauchen, können Sie die generierte `security-headers.conf` manuell bearbeiten und `'unsafe-eval'` für eine strengere Richtlinie entfernen.
Lassen Sie für OCR die `VITE_TESSERACT_*`-Variablen leer, um die Standard-Online-Ressourcen zu nutzen, oder setzen Sie alle drei zusammen für selbst gehostetes/Offline-OCR. Teilweise Überschreibungen werden abgelehnt, weil Worker, Core-Runtime und traineddata-Verzeichnis zusammenpassen müssen. Für eine vollständig offline erzeugte durchsuchbare PDF setzen Sie zusätzlich `VITE_OCR_FONT_BASE_URL`, damit die OCR-Textebenen-Schriften von Ihrem internen Server statt von den öffentlichen Noto-Schrift-URLs geladen werden.
`VITE_DEFAULT_LANGUAGE` legt die Oberflächensprache für Erstbesucher fest. Unterstützte Werte: `en`, `ar`, `be`, `fr`, `de`, `es`, `zh`, `zh-TW`, `vi`, `tr`, `id`, `it`, `pt`, `nl`, `da`. Nutzer können die Sprache weiterhin wechseln, das ändert nur den Standard.
Beispiel:
```bash
# Build with French as the default language
docker build --build-arg VITE_DEFAULT_LANGUAGE=fr -t bentopdf .
docker run -d -p 3000:8080 bentopdf
```
### Eigenes Branding
Ersetzen Sie das Standard-Logo, den Namen und den Footer-Text von BentoPDF durch Ihre eigenen. Legen Sie Ihre Logodatei im Ordner `public/` ab (oder nutzen Sie ein vorhandenes Bild) und übergib die Branding-Variablen beim Build:
```bash
docker build \
--build-arg VITE_BRAND_NAME="AcmePDF" \
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
-t acmepdf .
```
Das Branding funktioniert im Vollmodus und im Simple Mode und lässt sich mit allen anderen Build-Optionen kombinieren.
### Bestimmte Werkzeuge deaktivieren
Blenden Sie Werkzeuge aus der Oberfläche aus, etwa aus Compliance- oder Sicherheitsgründen. Deaktivierte Werkzeuge werden von der Startseite, aus den Suchergebnissen, den Tastenkürzeln und dem Workflow-Builder entfernt. Beim direkten Aufruf der URL erscheint eine Seite "Werkzeug nicht verfügbar".
Werkzeug-IDs entsprechen der Seiten-URL ohne `.html`. Liegt das Werkzeug zum Beispiel unter `bentopdf.com/edit-pdf.html`, lautet die ID `edit-pdf`.
#### Werkzeug-IDs finden
Am einfachsten: Öffnen Sie ein beliebiges Werkzeug in BentoPDF und sehen Sie sich die URL an. Der letzte Teil des Pfads (ohne `.html`) ist die Werkzeug-ID.
<details>
<summary>Vollständige Liste der Werkzeug-IDs</summary>
**Bearbeiten & Annotieren:** `edit-pdf`, `bookmark`, `table-of-contents`, `page-numbers`, `add-page-labels`, `bates-numbering`, `add-watermark`, `header-footer`, `invert-colors`, `scanner-effect`, `adjust-colors`, `background-color`, `text-color`, `sign-pdf`, `add-stamps`, `remove-annotations`, `crop-pdf`, `form-filler`, `form-creator`, `remove-blank-pages`
**In PDF konvertieren:** `image-to-pdf`, `jpg-to-pdf`, `png-to-pdf`, `webp-to-pdf`, `svg-to-pdf`, `bmp-to-pdf`, `heic-to-pdf`, `tiff-to-pdf`, `txt-to-pdf`, `markdown-to-pdf`, `json-to-pdf`, `csv-to-pdf`, `rtf-to-pdf`, `odt-to-pdf`, `word-to-pdf`, `excel-to-pdf`, `powerpoint-to-pdf`, `xps-to-pdf`, `mobi-to-pdf`, `epub-to-pdf`, `fb2-to-pdf`, `cbz-to-pdf`, `wpd-to-pdf`, `wps-to-pdf`, `xml-to-pdf`, `pages-to-pdf`, `odg-to-pdf`, `ods-to-pdf`, `odp-to-pdf`, `pub-to-pdf`, `vsd-to-pdf`, `psd-to-pdf`, `email-to-pdf`
**Aus PDF konvertieren:** `pdf-to-jpg`, `pdf-to-png`, `pdf-to-webp`, `pdf-to-bmp`, `pdf-to-tiff`, `pdf-to-cbz`, `pdf-to-svg`, `pdf-to-csv`, `pdf-to-excel`, `pdf-to-greyscale`, `pdf-to-json`, `pdf-to-docx`, `extract-images`, `pdf-to-markdown`, `prepare-pdf-for-ai`, `pdf-to-text`
**Organisieren & Verwalten:** `ocr-pdf`, `merge-pdf`, `alternate-merge`, `organize-pdf`, `add-attachments`, `extract-attachments`, `edit-attachments`, `pdf-multi-tool`, `pdf-layers`, `extract-tables`, `split-pdf`, `divide-pages`, `extract-pages`, `delete-pages`, `add-blank-page`, `reverse-pages`, `rotate-pdf`, `rotate-custom`, `n-up-pdf`, `pdf-booklet`, `combine-single-page`, `view-metadata`, `edit-metadata`, `pdf-to-zip`, `compare-pdfs`, `posterize-pdf`, `page-dimensions`
**Optimieren & Reparieren:** `compress-pdf`, `pdf-to-pdfa`, `fix-page-size`, `linearize-pdf`, `remove-restrictions`, `repair-pdf`, `rasterize-pdf`, `deskew-pdf`, `font-to-outline`
**Sicherheit:** `encrypt-pdf`, `sanitize-pdf`, `decrypt-pdf`, `flatten-pdf`, `remove-metadata`, `change-permissions`, `digital-sign-pdf`, `validate-signature-pdf`, `timestamp-pdf`
</details>
#### Option 1: Zur Build-Zeit (Docker-Build-Argument)
```bash
docker build \
--build-arg DISABLE_TOOLS="edit-pdf,sign-pdf,encrypt-pdf" \
-t bentopdf .
```
Dadurch wird die Liste der deaktivierten Werkzeuge fest in das JavaScript-Bundle geschrieben. Zum Ändern ist ein Rebuild nötig.
#### Option 2: Zur Laufzeit (config.json)
Binden Sie eine `config.json`-Datei in das ausgelieferte Verzeichnis ein, ganz ohne Rebuild:
```json
{
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
}
```
```bash
docker run -d \
-p 3000:8080 \
-v ./config.json:/usr/share/nginx/html/config.json:ro \
ghcr.io/alam00000/bentopdf-simple:latest
```
Oder mit Docker Compose:
```yaml
services:
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
ports:
- '3000:8080'
volumes:
- ./config.json:/usr/share/nginx/html/config.json:ro
```
Beide Methoden lassen sich kombinieren, die Listen werden zusammengeführt. Taucht ein Werkzeug in einer von beiden auf, ist es deaktiviert.
#### Editor-Funktionen deaktivieren
Sie können auch einzelne Funktionen im PDF-Editor (z. B. Schwärzen, Annotationen, Formulare) deaktivieren, ohne das gesamte Editor-Werkzeug abzuschalten. Fügen Sie `editorDisabledCategories` zu Ihrer `config.json` hinzu:
```json
{
"editorDisabledCategories": ["redaction", "annotation-stamp"]
}
```
<details>
<summary>Vollständige Liste der Editor-Kategorien</summary>
**Zoom:** `zoom`, `zoom-in`, `zoom-out`, `zoom-fit-page`, `zoom-fit-width`, `zoom-marquee`, `zoom-level`
**Annotation:** `annotation`, `annotation-markup`, `annotation-highlight`, `annotation-underline`, `annotation-strikeout`, `annotation-squiggly`, `annotation-ink`, `annotation-text`, `annotation-stamp`
**Formen:** `annotation-shape`, `annotation-rectangle`, `annotation-circle`, `annotation-line`, `annotation-arrow`, `annotation-polygon`, `annotation-polyline`
**Formular:** `form`, `form-textfield`, `form-checkbox`, `form-radio`, `form-select`, `form-listbox`, `form-fill-mode`
**Schwärzung:** `redaction`, `redaction-area`, `redaction-text`, `redaction-apply`, `redaction-clear`
**Dokument:** `document`, `document-open`, `document-close`, `document-print`, `document-capture`, `document-export`, `document-fullscreen`, `document-protect`
**Seite:** `page`, `spread`, `rotate`, `scroll`, `navigation`
**Panel:** `panel`, `panel-sidebar`, `panel-search`, `panel-comment`
**Werkzeuge:** `tools`, `pan`, `pointer`, `capture`
**Auswahl:** `selection`, `selection-copy`
**Verlauf:** `history`, `history-undo`, `history-redo`
</details>
Kategorien sind hierarchisch: Das Deaktivieren einer übergeordneten Kategorie (z. B. `annotation`) deaktiviert alle ihre untergeordneten.
### Den GitHub-Sternezähler deaktivieren
Die Navigationsleiste zeigt einen Live-Zähler der GitHub-Sterne, der beim Laden der Seite von `api.github.com` abgerufen wird. Es ist die einzige externe Anfrage, die keiner PDF-Funktion dient. Deaktivieren Sie ihn beim Build, um den Abrufcode aus dem Bundle zu entfernen und `api.github.com` aus der Content-Security-Policy zu streichen:
```bash
docker build --build-arg DISABLE_GITHUB_STARS=true -t bentopdf .
```
Simple-Mode-Builds (`bentopdf-simple`) überspringen den Sternezähler immer. Dieses Flag wird nur für den Commercial-Build oder eigene Voll-Builds benötigt.
### Eigene WASM-URLs (abgeschottet / selbst gehostet)
> [!IMPORTANT]
> WASM-URLs werden **beim Build** fest in das JavaScript geschrieben. Die WASM-Dateien lädt der **Browser des Nutzers** zur Laufzeit herunter, Docker lädt sie beim Build nicht herunter. In abgeschotteten Netzwerken müssen Sie die WASM-Dateien auf einem internen Server hosten, den Browser erreichen können.
**Vollständiger Workflow für abgeschottete Umgebungen:**
```bash
# 1. On a machine WITH internet — download WASM packages
bash scripts/prepare-airgap.sh --list-ocr-languages
bash scripts/prepare-airgap.sh --search-ocr-language german
# 2. Download WASM/OCR packages
npm pack @bentopdf/pymupdf-wasm@0.11.14
npm pack @bentopdf/gs-wasm
npm pack coherentpdf
npm pack tesseract.js@7.0.0
npm pack tesseract.js-core@7.0.0
mkdir -p tesseract-langdata
curl -fsSL https://cdn.jsdelivr.net/npm/@tesseract.js-data/eng/4.0.0_best_int/eng.traineddata.gz -o tesseract-langdata/eng.traineddata.gz
mkdir -p ocr-fonts
curl -fsSL https://raw.githack.com/googlefonts/noto-fonts/main/hinted/ttf/NotoSans/NotoSans-Regular.ttf -o ocr-fonts/NotoSans-Regular.ttf
# 3. Build the image with your internal server URLs
docker build \
--build-arg VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ \
--build-arg VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ \
--build-arg VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ \
--build-arg VITE_TESSERACT_WORKER_URL=https://internal-server.example.com/wasm/ocr/worker.min.js \
--build-arg VITE_TESSERACT_CORE_URL=https://internal-server.example.com/wasm/ocr/core \
--build-arg VITE_TESSERACT_LANG_URL=https://internal-server.example.com/wasm/ocr/lang-data \
--build-arg VITE_TESSERACT_AVAILABLE_LANGUAGES=eng,deu \
--build-arg VITE_OCR_FONT_BASE_URL=https://internal-server.example.com/wasm/ocr/fonts \
-t bentopdf .
# 4. Export the image
docker save bentopdf -o bentopdf.tar
# 5. Transfer bentopdf.tar + the .tgz packages + tesseract-langdata/ + ocr-fonts/ into the air-gapped network
# 6. Inside the air-gapped network — load and run
docker load -i bentopdf.tar
# Extract WASM packages to your internal web server
mkdir -p /var/www/wasm/pymupdf /var/www/wasm/gs /var/www/wasm/cpdf /var/www/wasm/ocr/core /var/www/wasm/ocr/lang-data /var/www/wasm/ocr/fonts
tar xzf bentopdf-pymupdf-wasm-0.11.14.tgz -C /var/www/wasm/pymupdf --strip-components=1
tar xzf bentopdf-gs-wasm-*.tgz -C /var/www/wasm/gs --strip-components=1
tar xzf coherentpdf-*.tgz -C /var/www/wasm/cpdf --strip-components=1
TEMP_TESS=$(mktemp -d)
tar xzf tesseract.js-7.0.0.tgz -C "$TEMP_TESS"
cp "$TEMP_TESS/package/dist/worker.min.js" /var/www/wasm/ocr/worker.min.js
rm -rf "$TEMP_TESS"
tar xzf tesseract.js-core-7.0.0.tgz -C /var/www/wasm/ocr/core --strip-components=1
cp ./tesseract-langdata/*.traineddata.gz /var/www/wasm/ocr/lang-data/
cp ./ocr-fonts/* /var/www/wasm/ocr/fonts/
# Run BentoPDF
docker run -d -p 3000:8080 --restart unless-stopped bentopdf
```
Verwenden Sie für `--ocr-languages` die Codes, die `bash scripts/prepare-airgap.sh --list-ocr-languages` ausgibt, oder suchen Sie per Name mit `bash scripts/prepare-airgap.sh --search-ocr-language <term>`. Wenn Sie mit einer eingeschränkten OCR-Teilmenge bauen, übergib dieselben Codes an `VITE_TESSERACT_AVAILABLE_LANGUAGES`, damit die App nur gebündelte Sprachen anzeigt. Für eine vollständige Offline-OCR-Ausgabe hosten Sie zusätzlich das mitgelieferte Verzeichnis `ocr-fonts/` und lassen Sie `VITE_OCR_FONT_BASE_URL` darauf zeigen.
Setzen Sie eine Variable auf einen leeren String, um das jeweilige Modul zu deaktivieren (Nutzer müssen es dann manuell über die Erweiterten Einstellungen konfigurieren).
## Eigener Port
Standardmäßig lauscht BentoPDF im Container auf Port `8080`. Um das zu ändern, setzen Sie zur Laufzeit die Umgebungsvariable `PORT`:
```bash
docker run -p 3000:9090 -e PORT=9090 ghcr.io/alam00000/bentopdf-simple:latest
```
| Variable | Beschreibung | Standard |
| -------- | ------------------------------ | -------- |
| `PORT` | Nginx-Listen-Port im Container | `8080` |
Das funktioniert sowohl mit dem Standard- als auch mit dem Nonroot-Dockerfile.
## Eigene Benutzer-ID (PUID/PGID)
Für Umgebungen, die einen bestimmten Nicht-Root-Benutzer erfordern (NAS-Geräte, Kubernetes mit Security-Contexts, organisatorische Vorgaben), bietet BentoPDF ein separates Dockerfile mit PUID/PGID-Unterstützung im LSIO-Stil.
### Bauen und ausführen
```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
```
### Umgebungsvariablen
| Variable | Beschreibung | Standard |
| -------------- | -------------------------------------- | -------- |
| `PUID` | Benutzer-ID, unter der ausgeführt wird | `1000` |
| `PGID` | Gruppen-ID, unter der ausgeführt wird | `1000` |
| `PORT` | Nginx-Listen-Port | `8080` |
| `DISABLE_IPV6` | IPv6-Listener deaktivieren | `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
```
### So funktioniert es
Der Container startet als root, legt einen Benutzer mit der angegebenen PUID/PGID an, passt die Besitzrechte aller beschreibbaren Verzeichnisse an und gibt dann mit `su-exec` die Rechte ab. Der nginx-Prozess läuft vollständig unter dem angegebenen Benutzer.
> [!NOTE]
> Das Standard-`Dockerfile` verwendet `nginx-unprivileged` (UID 101) und wird für die meisten Bereitstellungen empfohlen. Nutzen Sie `Dockerfile.nonroot` nur, wenn Sie eine bestimmte UID/GID brauchen.
> [!WARNING]
> PUID/PGID dürfen nicht `0` (root) sein. Der Entrypoint prüft die Eingaben und beendet sich bei ungültigen Werten mit einem Fehler.
## Mit Traefik (Reverse-Proxy)
```yaml
services:
traefik:
image: traefik:v2.10
command:
- '--providers.docker=true'
- '--entrypoints.web.address=:80'
- '--entrypoints.websecure.address=:443'
- '--certificatesresolvers.letsencrypt.acme.email=you@example.com'
- '--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json'
- '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web'
ports:
- '80:80'
- '443:443'
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.bentopdf.rule=Host(`pdf.example.com`)'
- 'traefik.http.routers.bentopdf.entrypoints=websecure'
- 'traefik.http.routers.bentopdf.tls.certresolver=letsencrypt'
- 'traefik.http.services.bentopdf.loadbalancer.server.port=8080'
# Required headers for SharedArrayBuffer (LibreOffice WASM)
- 'traefik.http.routers.bentopdf.middlewares=bentopdf-headers'
- 'traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Opener-Policy=same-origin'
- 'traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Embedder-Policy=require-corp'
restart: unless-stopped
```
## Mit Caddy (Reverse-Proxy)
```yaml
services:
caddy:
image: caddy:2
ports:
- '80:80'
- '443:443'
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
restart: unless-stopped
volumes:
caddy_data:
```
Caddyfile:
```
pdf.example.com {
reverse_proxy bentopdf:8080
header Cross-Origin-Opener-Policy "same-origin"
header Cross-Origin-Embedder-Policy "require-corp"
}
```
## Ressourcenlimits
```yaml
services:
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
```
## Podman Quadlet (systemd-Integration)
[Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) ermöglicht es, Podman-Container als systemd-Dienste auszuführen. Das ist ideal für Produktionsbereitstellungen auf Linux-Systemen.
### Grundlegende Quadlet-Einrichtung
Erstellen Sie eine Container-Unit-Datei unter `~/.config/containers/systemd/bentopdf.container` (Benutzer) oder `/etc/containers/systemd/bentopdf.container` (System):
```ini
[Unit]
Description=BentoPDF - Privacy-first PDF toolkit
After=network-online.target
Wants=network-online.target
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
AutoUpdate=registry
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=default.target
```
### Aktivieren und starten
```bash
# Reload systemd to detect new unit
systemctl --user daemon-reload
# Start the service
systemctl --user start bentopdf
# Enable on boot
systemctl --user enable bentopdf
# Check status
systemctl --user status bentopdf
# View logs
journalctl --user -u bentopdf -f
```
> [!TIP]
> Für eine systemweite Bereitstellung nutzen Sie `systemctl` ohne das Flag `--user` und legen Sie die Datei in `/etc/containers/systemd/` ab.
### Quadlet mit Health-Check
```ini
[Unit]
Description=BentoPDF with health monitoring
After=network-online.target
Wants=network-online.target
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
AutoUpdate=registry
HealthCmd=wget --spider -q http://localhost:8080 || exit 1
HealthInterval=30s
HealthTimeout=10s
HealthRetries=3
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=default.target
```
### Automatisches Update mit Quadlet
Podman kann Container automatisch aktualisieren, wenn neue Images verfügbar sind:
```bash
# Enable auto-update timer
systemctl --user enable --now podman-auto-update.timer
# Check for updates manually
podman auto-update
# Dry run (check without updating)
podman auto-update --dry-run
```
### Quadlet-Netzwerkkonfiguration
Für eine eigene Netzwerkkonfiguration erstellen Sie eine Netzwerkdatei `bentopdf.network`:
```ini
[Network]
Subnet=10.89.0.0/24
Gateway=10.89.0.1
```
Verweisen Sie dann in Ihrer Container-Datei darauf:
```ini
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
Network=bentopdf.network
```
## Aktualisieren
```bash
# Pull latest image
docker compose pull
# Recreate container
docker compose up -d
```
+266
View File
@@ -0,0 +1,266 @@
# Auf Hostinger bereitstellen
[Hostinger](https://hostinger.com) ist ein beliebter Shared-Hosting-Anbieter. Diese Anleitung zeigt, wie Sie BentoPDF auf dem Shared Hosting von Hostinger bereitstellen.
## Voraussetzungen
- Hostinger-Hosting-Tarif mit Zugriff auf den Dateimanager
- Lokal installiertes Node.js zum Bauen
## Schritt 1: Projekt bauen
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
Die gebauten Dateien liegen im Ordner `dist`.
## Schritt 2: Auf Hostinger hochladen
### Bereitstellung auf der Hauptdomain
1. Melden Sie sich bei Ihrem Hostinger-Konto an
2. Gehen Sie zu **File Manager** → **public_html**
3. **Löschen Sie** vorhandene Dateien (bei Bedarf vorher sichern)
4. **Laden Sie** den gesamten Inhalt Ihres lokalen Ordners `dist` nach `public_html` **hoch**
### Bereitstellung in einem Unterverzeichnis
Wenn Sie in einem Unterverzeichnis bereitstellen (z. B. `yourdomain.com/pdf-tools/`):
1. Mit der richtigen Basis-URL bauen:
```bash
BASE_URL=/pdf-tools/ npm run build
```
2. Den Ordner in Hostinger erstellen:
- Gehen Sie zu **File Manager** → **public_html**
- Erstellen Sie einen neuen Ordner: `pdf-tools`
3. Laden Sie den gesamten Inhalt von `dist` nach `public_html/pdf-tools/` hoch
## Schritt 3: .htaccess-Datei erstellen
Erstellen Sie eine `.htaccess`-Datei im Stammverzeichnis Ihres Bereitstellungsordners (`public_html` oder `public_html/pdf-tools/`):
::: warning Wichtig
Bei der Bereitstellung in einem Unterverzeichnis änderen Sie `RewriteBase /` in `RewriteBase /pdf-tools/` (oder in den Namen Ihres Ordners).
:::
```apache
RewriteEngine On
RewriteBase /
# ============================================
# 1. SECURITY HEADERS (CRITICAL FOR WASM)
# ============================================
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# REQUIRED for soffice.js (SharedArrayBuffer)
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
</IfModule>
# ============================================
# 2. BROWSER CACHING
# ============================================
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType image/x-icon "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/ttf "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType application/wasm "access plus 1 year"
ExpiresByType application/gzip "access plus 1 year"
ExpiresByType text/html "access plus 0 seconds"
</IfModule>
# ============================================
# 3. COMPRESSION (STANDARD)
# ============================================
# Prevent server from double-compressing files
SetEnvIfNoCase Request_URI "\.gz$" no-gzip
SetEnvIfNoCase Request_URI "\.br$" no-gzip
SetEnvIfNoCase Request_URI "\.wasm$" no-gzip
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE font/woff
AddOutputFilterByType DEFLATE font/ttf
</IfModule>
# ============================================
# 4. MIME TYPES & SPECIAL FILE HANDLING
# ============================================
AddType application/javascript .js .mjs
AddType application/wasm .wasm
AddType font/woff2 .woff2
AddType font/woff .woff
AddType image/webp .webp
# Handle soffice.wasm.gz correctly
<FilesMatch "soffice\.wasm\.gz$">
ForceType application/wasm
Header set Content-Encoding "gzip"
Header set Cross-Origin-Resource-Policy "cross-origin"
Header append Vary Accept-Encoding
</FilesMatch>
# Handle data.gz
<FilesMatch "soffice\.data\.gz$">
ForceType application/octet-stream
Header set Content-Encoding "gzip"
Header append Vary Accept-Encoding
</FilesMatch>
# ============================================
# 5. REDIRECTS & ROUTING
# ============================================
# Canonical WWW (update domain as needed)
RewriteCond %{HTTP_HOST} ^bentopdf\.com [NC]
RewriteRule ^(.*)$ https://www.bentopdf.com/$1 [L,R=301]
# Force HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Remove trailing slash (except for language root directories)
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/$
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [R=301,L]
# Existing files/dirs - serve directly
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# ============================================
# 5.1. LANGUAGE ROUTES
# ============================================
# Supported languages: de, es, zh, zh-TW, vi, it, id, tr, fr, pt
# English has no prefix - served from root
# English prefix redirects to root (for SEO consistency)
RewriteRule ^en/?$ / [R=301,L]
RewriteRule ^en/(.+)$ /$1 [R=301,L]
# Language prefix root (e.g., /de/ -> /de/index.html)
RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /$1/index.html [L]
# Language prefix with path (e.g., /de/merge-pdf -> /de/merge-pdf.html)
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# ============================================
# 5.5. DOCS ROUTING (VitePress)
# ============================================
RewriteCond %{REQUEST_URI} ^/docs
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html [L]
# ============================================
# 6. ADD .HTML EXTENSION IF FILE EXISTS (ROOT LEVEL ONLY)
# ============================================
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
# ============================================
# 7. ERROR PAGES
# ============================================
ErrorDocument 404 /404.html
```
## Beispiel für .htaccess im Unterverzeichnis
Für `yourdomain.com/pdf-tools/` passen Sie diese Zeilen an:
```apache
RewriteBase /pdf-tools/
# ... (same content as above, but update paths) ...
# Language prefix root
RewriteCond %{DOCUMENT_ROOT}/pdf-tools/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /pdf-tools/$1/index.html [L]
# Language prefix with path
RewriteCond %{DOCUMENT_ROOT}/pdf-tools/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /pdf-tools/$1/$2.html [L]
# Root level .html extension
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
ErrorDocument 404 /pdf-tools/404.html
```
## Fehlerbehebung
### WASM-Dateien werden nicht geladen
Stellen Sie sicher, dass die MIME-Typen in der `.htaccess` korrekt gesetzt sind:
```apache
AddType application/wasm .wasm
```
### LibreOffice-Werkzeuge funktionieren nicht
Die Sicherheits-Header sind für SharedArrayBuffer entscheidend:
```apache
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
```
Werden die Header nicht angewendet, bitte den Hostinger-Support, `mod_headers` zu aktivieren.
### 404-Fehler beim Neuladen der Seite
Stellen Sie sicher, dass die Regel für die Endung `.html` und die Sprachrouten korrekt konfiguriert sind. BentoPDF nutzt statische HTML-Dateien, kein SPA-Routing:
```apache
# Language routes serve actual files from language directories
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# Root level pages
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
```
### Limits für Datei-Uploads
Hostinger kann Dateigrößenlimits haben. Laden Sie WASM-Dateien separat hoch, wenn der Massen-Upload fehlschlägt.
+538
View File
@@ -0,0 +1,538 @@
# Self-Hosting-Leitfaden
BentoPDF kann auf Ihrer eigenen Infrastruktur selbst gehostet werden. Dieser Leitfaden behandelt verschiedene Bereitstellungsoptionen.
## Schnellstart mit Docker / Podman
Der schnellste Weg, BentoPDF selbst zu hosten:
> [!TIP]
> BentoPDF gibt es in zwei Builds:
>
> - **Self-Hosted-Build** (`ghcr.io/alam00000/bentopdf-simple:latest`). Jedes PDF-Werkzeug, **ohne** das Marketing-Drumherum (kein Hero, keine FAQ, keine Testimonials, kein Footer). Nutzen Sie ihn für interne Bereitstellungen in Teams und Organisationen. Es ist **keine** funktionsreduzierte "Lite"-Version.
> - **Commercial-Build** (`ghcr.io/alam00000/bentopdf:latest`). Die vollständige Marketing-Website, die von bentopdf.com selbst und von Inhabern einer kommerziellen Lizenz mit öffentlich zugänglichen Bereitstellungen verwendet wird.
>
> Im Zweifel: Ziehen Sie den Self-Hosted-Build.
> [!IMPORTANT]
> Die Konvertierung von Office-Dateien erfordert `SharedArrayBuffer`. Die App muss dafür sowohl Cross-Origin-isoliert sein als auch aus einem sicheren Kontext ausgeliefert werden. Das offizielle Image sendet bereits die erforderlichen COOP/COEP-Header, aber Browser deaktivieren `SharedArrayBuffer` bei reinen HTTP-Ursprüngen im lokalen Netzwerk wie `http://192.168.x.x` trotzdem.
>
> Verwenden Sie `http://localhost` nur für Tests auf demselben Gerät. Wenn Nutzende BentoPDF über eine LAN-IP oder einen Hostnamen aufrufen, beenden Sie die Verbindung mit HTTPS.
```bash
# Docker
docker run -d -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
# Podman
podman run -d -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
```
Oder mit Docker Compose / Podman Compose:
```yaml
# docker-compose.yml
services:
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
ports:
- '3000:8080'
restart: unless-stopped
```
```bash
# Docker Compose
docker compose up -d
# Podman Compose
podman-compose up -d
```
## Podman Quadlet (Linux Systemd)
Betreiben Sie BentoPDF als systemd-Dienst. Erstellen Sie `~/.config/containers/systemd/bentopdf.container`:
```ini
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
AutoUpdate=registry
[Service]
Restart=always
[Install]
WantedBy=default.target
```
```bash
systemctl --user daemon-reload
systemctl --user enable --now bentopdf
```
Die vollständige Quadlet-Dokumentation finden Sie im [Docker-Bereitstellungsleitfaden](/de/self-hosting/docker).
## Aus dem Quellcode bauen
```bash
# Clone and build
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
# The built files are in the `dist` folder
```
## Konfigurationsoptionen
### Self-Hosted-Build (Simple Mode)
Der Self-Hosted-Build (das Image `bentopdf-simple`, auch Simple Mode genannt) ist **funktional identisch** mit dem Commercial-Build: Jedes PDF-Werkzeug ist vorhanden und verhält sich gleich. Er blendet lediglich das Marketing-Drumherum aus, das nur auf bentopdf.com selbst oder in einer kommerziellen, öffentlich zugänglichen Bereitstellung sinnvoll ist. **Es ist keine funktionsreduzierte oder "Lite"-Version.**
**Was der Self-Hosted-Build ausblendet** (nur kosmetisch, keine PDF-Funktionen werden entfernt):
- Navigationsleiste, Hero-Bereich, Funktionsbereich, FAQ, Testimonials, Footer
- Setzt den Seitentitel auf "PDF-Werkzeuge"
**Was der Self-Hosted-Build behält** (alles, was tatsächlich PDF-Arbeit leistet):
- Jedes PDF-Werkzeug (Zusammenführen, Teilen, Bearbeiten, Signieren, OCR, Office-Konvertierung usw.)
- Unterstützung für eigenes Branding, alle Build-Time- und Runtime-Konfigurationen
Der Commercial-Build (`ghcr.io/alam00000/bentopdf:latest`) betreibt bentopdf.com selbst und wird von Inhabern einer kommerziellen Lizenz für öffentlich zugängliche Bereitstellungen verwendet. Er ergänzt Hero, FAQ, Testimonials und Footer, die bei einem internen Werkzeug keinen Sinn ergeben würden.
Wenn Sie BentoPDF für Ihr Team, Ihre Organisation oder als internes Werkzeug selbst hosten, ziehen Sie den Self-Hosted-Build:
```bash
# Use the pre-built image (recommended)
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
# Or build it yourself
SIMPLE_MODE=true npm run build
```
Alle Details finden Sie in [SIMPLE_MODE.md](https://github.com/alam00000/bentopdf/blob/main/SIMPLE_MODE.md).
### Commercial-Build (öffentlich zugängliche Bereitstellungen mit eigener Marke)
Der Commercial-Build (das Image `bentopdf`, ohne Suffix `-simple`) betreibt bentopdf.com selbst. Er enthält die vollständige Marketing-Website (Hero, Funktionen, FAQ, Testimonials, Footer) zusätzlich zu jedem PDF-Werkzeug. Verwenden Sie diesen Build, wenn Sie **BentoPDF als öffentlich zugänglichen PDF-Dienst unter Ihrer eigenen Marke bereitstellen** möchten, zum Beispiel:
- Sie betreiben BentoPDF als gehostetes SaaS für Endnutzende auf Ihrer eigenen Domain
- Sie möchten das Landingpage-Erlebnis (Marketing-Bereiche + Werkzeuge), nicht nur die reine Werkzeugoberfläche
- Sie sind Inhaber einer kommerziellen Lizenz und betten BentoPDF in ein kommerzielles Produkt ein
**Unverändert ausführen** (mit BentoPDF-Branding, nützlich, um den Build auszuprobieren):
```bash
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
```
**Mit eigener Marke bauen** (der typische kommerzielle Workflow):
```bash
docker build \
--build-arg VITE_BRAND_NAME="AcmePDF" \
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. All rights reserved." \
-t acmepdf .
docker run -p 3000:8080 acmepdf
```
Alle anderen Build-Time-Optionen (`BASE_URL`, `VITE_DEFAULT_LANGUAGE`, `DISABLE_TOOLS`, WASM-URL-Überschreibungen usw.) funktionieren genauso wie beim Self-Hosted-Build.
::: warning Lizenzierung
Der Commercial-Build darf unter einer der beiden Lizenzoptionen von BentoPDF betrieben werden:
- **AGPL-3.0** (kostenlos): erlaubt, wenn Ihre Bereitstellung ihren vollständigen Quellcode unter der AGPL veröffentlicht, einschließlich aller Branding-Änderungen und der umgebenden Geschäftslogik.
- **Kommerzielle Lizenz** (79 $ lebenslang): erforderlich für Closed-Source-/proprietäre Bereitstellungen, bei denen Sie Ihren Branding-Fork oder Ihren Geschäftscode nicht als Open Source veröffentlichen.
Den vollständigen Vergleich finden Sie auf der [Lizenzierungsseite](https://www.bentopdf.com/licensing). AGPL-lizenzierte WASM-Module (PyMuPDF, Ghostscript, CoherentPDF) werden zur Laufzeit von einem CDN geladen, gelangen also nicht in Ihr Image und ändern nichts an Ihrer Lizenzlage.
:::
### Base-URL
In einem Unterverzeichnis bereitstellen:
```bash
BASE_URL=/pdf-tools/ npm run build
```
### Eigenes Branding
Ersetzen Sie das Standard-Logo, den Namen und den Footer-Text von BentoPDF zur Build-Zeit durch Ihre eigenen:
| Variable | Beschreibung | Standard |
| ------------------ | ------------------------------- | --------------------------------------- |
| `VITE_BRAND_NAME` | Markenname in Header und Footer | `BentoPDF` |
| `VITE_BRAND_LOGO` | Logopfad relativ zu `public/` | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | Eigener Footer-/Copyright-Text | `© 2026 BentoPDF. All rights reserved.` |
```bash
# Place your logo in public/, then build
VITE_BRAND_NAME="AcmePDF" \
VITE_BRAND_LOGO="images/acme-logo.svg" \
VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
npm run build
```
Oder über Docker:
```bash
docker build \
--build-arg VITE_BRAND_NAME="AcmePDF" \
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
-t acmepdf .
```
Das Branding funktioniert sowohl im vollständigen Modus als auch im Simple Mode und lässt sich mit allen anderen Build-Time-Optionen kombinieren (`BASE_URL`, `SIMPLE_MODE`, `VITE_DEFAULT_LANGUAGE`).
### Bestimmte Werkzeuge deaktivieren
Blenden Sie einzelne Werkzeuge aus Compliance- oder Sicherheitsgründen aus. Deaktivierte Werkzeuge werden auf der Startseite, in der Suche, in den Tastenkürzeln, im Workflow-Builder und beim direkten URL-Zugriff entfernt.
Werkzeug-IDs entsprechen der Seiten-URL ohne `.html`: Öffnen Sie ein beliebiges Werkzeug und sehen Sie sich die URL an (z. B. `edit-pdf`, `sign-pdf`, `encrypt-pdf`).
**Build-Time** (in das Bundle eingebaut):
```bash
DISABLE_TOOLS="edit-pdf,sign-pdf" npm run build
```
**Runtime** (ohne Neubau: eine `config.json` einbinden):
```json
{
"disabledTools": ["edit-pdf", "sign-pdf"]
}
```
```bash
docker run -d -p 3000:8080 \
-v ./config.json:/usr/share/nginx/html/config.json:ro \
ghcr.io/alam00000/bentopdf-simple:latest
```
Beide Methoden lassen sich kombinieren, die Listen werden zusammengeführt.
Sie können außerdem einzelne Funktionen im PDF-Editor (z. B. Schwärzen) deaktivieren, ohne das gesamte Werkzeug zu deaktivieren, indem Sie `editorDisabledCategories` zur `config.json` hinzufügen. Die vollständige Liste der Kategorien finden Sie im [Docker-Leitfaden](/de/self-hosting/docker#editor-funktionen-deaktivieren).
## Bereitstellungsleitfäden
Wählen Sie Ihre Plattform:
- [Vercel](/de/self-hosting/vercel)
- [Netlify](/de/self-hosting/netlify)
- [Cloudflare Pages](/de/self-hosting/cloudflare)
- [AWS S3 + CloudFront](/de/self-hosting/aws)
- [Hostinger](/de/self-hosting/hostinger)
- [Nginx](/de/self-hosting/nginx)
- [Apache](/de/self-hosting/apache)
- [Docker](/de/self-hosting/docker)
- [Kubernetes](/de/self-hosting/kubernetes)
- [CORS-Proxy](/de/self-hosting/cors-proxy) - Erforderlich für digitale Signaturen
## Häufige Probleme
### Word / ODT / Excel / PowerPoint zu PDF hängt (SharedArrayBuffer nicht verfügbar)
**Symptom**: LibreOffice-basierte Dokumentkonvertierungen (Word, ODT, Excel, PowerPoint zu PDF) hängen bei ca. 55 % oder starten nicht. Die Browserkonsole zeigt möglicherweise:
> `ReferenceError: SharedArrayBuffer is not defined`
…oder `window.crossOriginIsolated` meldet `false`, oder die WASM-Kompilierung meldet `expected magic word 00 61 73 6d, found 1f 8b 08 08`.
**Ursache**: LibreOffice-WASM benötigt `SharedArrayBuffer`, das der Browser nur aktiviert, wenn die Seite **Cross-Origin-isoliert** ist UND aus einem **sicheren Kontext** ausgeliefert wird. Dafür müssen zwei Dinge zutreffen:
1. Jede Antwort enthält beide Header:
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
2. Die Seite wird über `https://...` oder `http://localhost` ausgeliefert. Reines HTTP auf einer LAN-IP (z. B. `http://192.168.x.x`) zählt NICHT als sicher, dort deaktivieren Browser `SharedArrayBuffer`.
Die Abweichung `00 61 73 6d / 1f 8b 08 08` ist ein separates Teilsymptom: Den vorkomprimierten Dateien `.wasm.gz` / `.data.gz` fehlt der Response-Header `Content-Encoding: gzip`, sodass der Browser rohe gzip-Bytes statt dekomprimiertem WASM erhält.
**Lösung**: Die genaue Konfiguration finden Sie im Bereitstellungsleitfaden Ihrer Plattform:
- [Nginx →](/de/self-hosting/nginx#signieren-oder-formularausfullung-zeigt-einen-leeren-viewer-mjs-mime-fehler)
- [Apache →](/de/self-hosting/apache#signieren-oder-formularausfullung-zeigt-einen-leeren-viewer-mjs-mime-fehler)
- [AWS S3 + CloudFront →](/de/self-hosting/aws#schritt-3b-response-headers-policy-erforderlich-fur-libreoffice-wasm)
- [Cloudflare Pages →](/de/self-hosting/cloudflare#konfigurationsdatei) (`_headers`-Datei)
- [Netlify →](/de/self-hosting/netlify#word-odt-excel-zu-pdf-bleibt-bei-55-hangen) (`netlify.toml`)
- [Vercel →](/de/self-hosting/vercel#word-odt-excel-zu-pdf-funktioniert-nicht) (`vercel.json`)
- [Hostinger →](/de/self-hosting/hostinger#libreoffice-werkzeuge-funktionieren-nicht) (`.htaccess`)
- [Kubernetes →](/de/self-hosting/kubernetes#mjs-mime-type-fehler-pdf-unterschreiben-pdf-formular-ausfullen-leerer-iframe)
- **Docker**: wird von der mitgelieferten Nginx-Konfiguration automatisch erledigt, keine Aktion nötig.
**Überprüfen**: Öffnen Sie auf einer beliebigen BentoPDF-Seite die DevTools-Konsole und führen Sie aus:
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
Wenn die Seite über HTTPS oder `http://localhost` läuft UND beide COEP/COOP-Header in jeder Antwort vorhanden sind, bestehen beide Prüfungen. Wenn Sie auf `http://192.168.x.x` oder einem anderen nicht-Loopback-HTTP-Ursprung sind, beenden Sie die Verbindung mit HTTPS. Es gibt keine Lösung allein über Header.
### `.mjs`-Dateien werden als `application/octet-stream` ausgeliefert
**Symptom**: PDF unterschreiben / PDF-Formular ausfüllen / bestimmte andere Werkzeuge zeigen einen leeren Viewer oder laden nicht. Die Browserkonsole meldet:
> `Failed to load module script: The server responded with a non-JavaScript MIME type of "application/octet-stream". Strict MIME type checking is enforced for module scripts per HTML spec.`
**Ursache**: Ihr Webserver oder Reverse-Proxy hat keine MIME-Type-Zuordnung für `.mjs`-Dateien (der mitgelieferte PDF-Viewer enthält ES-Module mit dieser Endung). Viele Standard-Serverkonfigurationen verwenden für unbekannte Endungen `application/octet-stream`, was Browser als ES-Module nicht ausführen.
**Lösung**: Den genauen Ausschnitt finden Sie im Bereitstellungsleitfaden Ihrer Plattform:
- [Nginx →](/de/self-hosting/nginx#signieren-oder-formularausfullung-zeigt-einen-leeren-viewer-mjs-mime-fehler)
- [Apache →](/de/self-hosting/apache#signieren-oder-formularausfullung-zeigt-einen-leeren-viewer-mjs-mime-fehler)
- [AWS S3 + CloudFront →](/de/self-hosting/aws#schritt-2-bauen-und-hochladen) (siehe `aws s3 cp ... --include "*.mjs"`)
- [Kubernetes →](/de/self-hosting/kubernetes#mjs-mime-type-fehler-pdf-unterschreiben-pdf-formular-ausfullen-leerer-iframe)
- **Docker, Vercel, Netlify, Cloudflare Pages, Hostinger**: wird automatisch erledigt, keine Aktion nötig.
**Überprüfen**: Öffnen Sie die DevTools → Tab Network, suchen Sie die fehlgeschlagene `.mjs`-Anfrage und prüfen Sie den Response-Header `Content-Type`. Er sollte `application/javascript` lauten. Wenn er immer noch `application/octet-stream` lautet, bestimmt möglicherweise ein vorgelagerter Reverse-Proxy oder CDN den Typ neu. Prüfen Sie jede Schicht in Ihrer Auslieferungskette.
## WASM-Konfiguration (AGPL-Komponenten)
BentoPDF **bündelt keine** AGPL-lizenzierten Verarbeitungsbibliotheken in seinem Quellcode, **konfiguriert aber CDN-URLs vor**, sodass alle Funktionen sofort funktionieren, ohne manuelle Einrichtung.
::: tip Standardmäßig ohne Konfiguration
Seit v2.0.0 sind WASM-Module so vorkonfiguriert, dass sie über Umgebungsvariablen vom jsDelivr-CDN geladen werden. Alle erweiterten Funktionen funktionieren sofort, ohne Konfiguration durch Nutzende.
:::
| Komponente | Lizenz | Funktionen |
| --------------- | -------- | ---------------------------------------------------------------------- |
| **PyMuPDF** | AGPL-3.0 | EPUB-/MOBI-/FB2-/XPS-Konvertierung, Bildextraktion, Tabellenextraktion |
| **Ghostscript** | AGPL-3.0 | PDF/A-Konvertierung, Komprimierung, Begradigung, Rasterung |
| **CoherentPDF** | AGPL-3.0 | Inhaltsverzeichnis, Anhänge, PDF-Zusammenführung mit Lesezeichen |
### Standard-Umgebungsvariablen
Diese sind in `.env.production` gesetzt und in den Build eingebaut:
```bash
VITE_WASM_PYMUPDF_URL=https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/
VITE_WASM_GS_URL=https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm@0.1.1/assets/
VITE_WASM_CPDF_URL=https://cdn.jsdelivr.net/npm/coherentpdf@2.5.5/dist/
VITE_TESSERACT_WORKER_URL=
VITE_TESSERACT_CORE_URL=
VITE_TESSERACT_LANG_URL=
VITE_TESSERACT_AVAILABLE_LANGUAGES=
VITE_OCR_FONT_BASE_URL=
```
### WASM-URLs überschreiben
Sie können die Standardwerte zur Build-Zeit für eigene Bereitstellungen überschreiben:
```bash
# Via Docker build args
docker build \
--build-arg VITE_WASM_PYMUPDF_URL=https://your-server.com/pymupdf/ \
--build-arg VITE_WASM_GS_URL=https://your-server.com/gs/ \
--build-arg VITE_WASM_CPDF_URL=https://your-server.com/cpdf/ \
--build-arg VITE_TESSERACT_WORKER_URL=https://your-server.com/ocr/worker.min.js \
--build-arg VITE_TESSERACT_CORE_URL=https://your-server.com/ocr/core \
--build-arg VITE_TESSERACT_LANG_URL=https://your-server.com/ocr/lang-data \
--build-arg VITE_TESSERACT_AVAILABLE_LANGUAGES=eng,deu \
--build-arg VITE_OCR_FONT_BASE_URL=https://your-server.com/ocr/fonts \
-t bentopdf .
# Or via .env.production before building from source
VITE_WASM_PYMUPDF_URL=https://your-server.com/pymupdf/ npm run build
```
Um ein Modul vollständig zu deaktivieren (manuelle Konfiguration durch Nutzende über die erweiterten Einstellungen erforderlich), setzen Sie seine Variable auf eine leere Zeichenfolge.
Für OCR lassen Sie entweder alle `VITE_TESSERACT_*`-Variablen leer und behalten die standardmäßigen Online-Assets, oder Sie setzen die Worker-/Core-/Sprach-URLs gemeinsam für selbst gehostetes/Offline-OCR. Wenn Sie nur bestimmte OCR-Sprachen bündeln, setzen Sie `VITE_TESSERACT_AVAILABLE_LANGUAGES` ebenfalls auf dieselben kommagetrennten Codes, damit die Oberfläche nur installierte Sprachen anbietet und nicht unterstützte Auswahlen mit einer aussagekräftigen Fehlermeldung fehlschlagen. Für eine vollständig offline erzeugte durchsuchbare PDF-Ausgabe setzen Sie außerdem `VITE_OCR_FONT_BASE_URL` auf das interne Verzeichnis, das die mitgelieferten OCR-Schriftarten bereitstellt.
Nutzende können diese Standardwerte jederzeit über die **erweiterten Einstellungen** in der Oberfläche überschreiben. Im Browser gespeicherte Überschreibungen haben Vorrang vor den Standardwerten der Umgebung.
### Air-Gapped- / Offline-Bereitstellung
Für Netzwerke ohne Internetzugang (Behörden, Gesundheitswesen, Finanzwesen usw.). Die WASM-URLs sind zur **Build-Zeit** in das JavaScript eingebaut, die eigentlichen WASM-Dateien werden zur Laufzeit vom **Browser der Nutzenden** heruntergeladen. Sie müssen also alles auf einem Rechner mit Internetzugang vorbereiten und dann in das isolierte Netzwerk übertragen.
#### Automatisiertes Skript (empfohlen)
Das mitgelieferte Skript `prepare-airgap.sh` automatisiert den gesamten Ablauf: Es lädt die WASM-Pakete herunter, baut das Docker-Image und erzeugt ein in sich geschlossenes Bundle mit einem Setup-Skript.
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
# Show supported OCR language codes (for --ocr-languages)
bash scripts/prepare-airgap.sh --list-ocr-languages
# Search OCR language codes by name or abbreviation
bash scripts/prepare-airgap.sh --search-ocr-language german
# Interactive mode — prompts for all options
bash scripts/prepare-airgap.sh
# Or fully automated
bash scripts/prepare-airgap.sh --wasm-base-url https://internal.example.com/wasm
```
Dies erzeugt ein Bundle-Verzeichnis:
```
bentopdf-airgap-bundle/
bentopdf.tar # Docker image
*.tgz # WASM packages (PyMuPDF, Ghostscript, CoherentPDF, Tesseract)
tesseract-langdata/ # OCR traineddata files
ocr-fonts/ # OCR text-layer font files
setup.sh # Setup script for the air-gapped side
README.md # Instructions
```
Übertragen Sie das Bundle per USB, internem Artefakt-Repository oder auf einem anderen genehmigten Weg in das Air-Gapped-Netzwerk. Führen Sie dann das mitgelieferte Setup-Skript aus:
```bash
cd bentopdf-airgap-bundle
bash setup.sh
```
Das Setup-Skript lädt das Docker-Image, entpackt die WASM-Dateien und startet optional den Container.
**Skriptoptionen:**
| Flag | Beschreibung | Standard |
| ------------------------------ | ------------------------------------------------------------- | ------------------------------------------- |
| `--wasm-base-url <url>` | Wo die WASMs intern gehostet werden | _(erforderlich, wird bei Fehlen abgefragt)_ |
| `--image-name <name>` | Docker-Image-Tag | `bentopdf` |
| `--output-dir <path>` | Ausgabeverzeichnis des Bundles | `./bentopdf-airgap-bundle` |
| `--simple-mode` | Simple Mode aktivieren | aus |
| `--base-url <path>` | Base-URL des Unterverzeichnisses (z. B. `/pdf/`) | `/` |
| `--language <code>` | Standardsprache der Oberfläche (z. B. `fr`, `de`) | _(keine)_ |
| `--brand-name <name>` | Eigener Markenname | _(keine)_ |
| `--brand-logo <path>` | Logopfad relativ zu `public/` | _(keine)_ |
| `--footer-text <text>` | Eigener Footer-Text | _(keine)_ |
| `--ocr-languages <list>` | Kommagetrennte OCR-Sprachen zum Bündeln | `eng` |
| `--list-ocr-languages` | Unterstützte OCR-Codes und Namen ausgeben, dann beenden | aus |
| `--search-ocr-language <term>` | OCR-Codes nach Name oder Abkürzung suchen | aus |
| `--dockerfile <path>` | Zu verwendendes Dockerfile | `Dockerfile` |
| `--skip-docker` | Docker-Build und -Export überspringen | aus |
| `--skip-wasm` | WASM-Download überspringen (vorhandene `.tgz`-Dateien nutzen) | aus |
Die interaktive Abfrage akzeptiert außerdem `list`, um die vollständige Liste der unterstützten Tesseract-Codes auszugeben, und `search <term>`, um Treffer wie `search german` oder `search chi` zu finden.
::: warning Same-Origin-Anforderung
WASM-Dateien müssen vom **selben Ursprung** wie die BentoPDF-App ausgeliefert werden. Web Worker verwenden `importScripts()`, das keine Skripte von anderen Ursprüngen laden kann. Wenn BentoPDF beispielsweise unter `https://internal.example.com` läuft, sollte auch die WASM-Basis-URL `https://internal.example.com/wasm` lauten.
:::
#### Manuelle Schritte
<details>
<summary>Wenn Sie es lieber manuell ohne das Skript machen</summary>
**Schritt 1: WASM- und OCR-Pakete herunterladen** (auf einem Rechner mit Internetzugang)
```bash
npm pack @bentopdf/pymupdf-wasm@0.11.14
npm pack @bentopdf/gs-wasm
npm pack coherentpdf
npm pack tesseract.js@7.0.0
npm pack tesseract.js-core@7.0.0
mkdir -p tesseract-langdata
curl -fsSL https://cdn.jsdelivr.net/npm/@tesseract.js-data/eng/4.0.0_best_int/eng.traineddata.gz -o tesseract-langdata/eng.traineddata.gz
mkdir -p ocr-fonts
curl -fsSL https://raw.githack.com/googlefonts/noto-fonts/main/hinted/ttf/NotoSans/NotoSans-Regular.ttf -o ocr-fonts/NotoSans-Regular.ttf
```
**Schritt 2: Das Docker-Image mit internen URLs bauen**
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
docker build \
--build-arg VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ \
--build-arg VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ \
--build-arg VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ \
--build-arg VITE_TESSERACT_WORKER_URL=https://internal-server.example.com/wasm/ocr/worker.min.js \
--build-arg VITE_TESSERACT_CORE_URL=https://internal-server.example.com/wasm/ocr/core \
--build-arg VITE_TESSERACT_LANG_URL=https://internal-server.example.com/wasm/ocr/lang-data \
--build-arg VITE_OCR_FONT_BASE_URL=https://internal-server.example.com/wasm/ocr/fonts \
-t bentopdf .
```
**Schritt 3: Das Docker-Image exportieren**
```bash
docker save bentopdf -o bentopdf.tar
```
**Schritt 4: In das Air-Gapped-Netzwerk übertragen**
Kopieren Sie per USB, internem Artefakt-Repository oder einem genehmigten Übertragungsweg:
- `bentopdf.tar`: das Docker-Image
- Die fünf `.tgz`-WASM-/OCR-Pakete aus Schritt 1
- Das Verzeichnis `tesseract-langdata/` aus Schritt 1
- Das Verzeichnis `ocr-fonts/` aus Schritt 1
**Schritt 5: Im Air-Gapped-Netzwerk einrichten**
```bash
# Load the Docker image
docker load -i bentopdf.tar
# Extract WASM packages
mkdir -p ./wasm/pymupdf ./wasm/gs ./wasm/cpdf ./wasm/ocr/core ./wasm/ocr/lang-data ./wasm/ocr/fonts
tar xzf bentopdf-pymupdf-wasm-0.11.14.tgz -C ./wasm/pymupdf --strip-components=1
tar xzf bentopdf-gs-wasm-*.tgz -C ./wasm/gs --strip-components=1
tar xzf coherentpdf-*.tgz -C ./wasm/cpdf --strip-components=1
TEMP_TESS=$(mktemp -d)
tar xzf tesseract.js-7.0.0.tgz -C "$TEMP_TESS"
cp "$TEMP_TESS/package/dist/worker.min.js" ./wasm/ocr/worker.min.js
rm -rf "$TEMP_TESS"
tar xzf tesseract.js-core-7.0.0.tgz -C ./wasm/ocr/core --strip-components=1
cp ./tesseract-langdata/*.traineddata.gz ./wasm/ocr/lang-data/
cp ./ocr-fonts/* ./wasm/ocr/fonts/
# Run BentoPDF
docker run -d -p 3000:8080 --restart unless-stopped bentopdf
```
Stellen Sie sicher, dass die Dateien unter den in Schritt 2 konfigurierten URLs erreichbar sind, einschließlich `.../ocr/worker.min.js`, `.../ocr/core`, `.../ocr/lang-data` und `.../ocr/fonts`.
</details>
::: info Aus dem Quellcode statt mit Docker bauen?
Setzen Sie die Variablen in `.env.production`, bevor Sie `npm run build` ausführen:
```bash
VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/
VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/
VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/
VITE_TESSERACT_WORKER_URL=https://internal-server.example.com/wasm/ocr/worker.min.js
VITE_TESSERACT_CORE_URL=https://internal-server.example.com/wasm/ocr/core
VITE_TESSERACT_LANG_URL=https://internal-server.example.com/wasm/ocr/lang-data
VITE_OCR_FONT_BASE_URL=https://internal-server.example.com/wasm/ocr/fonts
```
:::
### Einen eigenen WASM-Proxy hosten
Wenn Sie AGPL-WASM-Dateien mit passenden CORS-Headern ausliefern müssen, können Sie einen einfachen Proxy bereitstellen. Eine Beispielimplementierung finden Sie im [Cloudflare-WASM-Proxy-Leitfaden](https://github.com/alam00000/bentopdf/blob/main/cloudflare/WASM-PROXY.md).
::: tip Warum getrennt?
Diese Trennung sorgt für:
- Klare rechtliche Konformität für kommerzielle Nutzende
- Den Kern von BentoPDF unter seiner Dual-Lizenz (AGPL-3.0 / Commercial)
- WASM-Dateien, die zur Laufzeit geladen und nicht im Quellcode gebündelt werden
:::
## Systemanforderungen
| Anforderung | Minimum |
| ----------- | ---------------------------------- |
| Speicher | ca. 100 MB (Kern ohne AGPL-Module) |
| RAM | 512 MB |
| CPU | Jeder moderne Prozessor |
::: tip
BentoPDF ist eine statische Website, es werden keine Datenbank und kein Backend-Server benötigt!
:::
+211
View File
@@ -0,0 +1,211 @@
# Mit Kubernetes bereitstellen
Kubernetes ist für eine statische Website vielleicht überdimensioniert, kann aber gut passen, wenn Sie ohnehin auf Helm + GitOps setzen.
> [!IMPORTANT]
> **Erforderliche Header für die Office-Dateikonvertierung**
>
> LibreOffice-basierte Werkzeuge (Word-, Excel-, PowerPoint-Konvertierung) benötigen diese HTTP-Header für die `SharedArrayBuffer`-Unterstützung:
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> Die offiziellen BentoPDF-Nginx-Images enthalten diese Header. In Kubernetes sind **Ingress-/Gateway-Controller ebenfalls Reverse-Proxys**. Stellen Sie daher sicher, dass diese Header erhalten bleiben (oder fügen Sie sie am Edge hinzu).
## Voraussetzungen
- Kubernetes-Cluster
- Helm v3
- Ein BentoPDF-Nginx-Image (z. B. `ghcr.io/alam00000/bentopdf:<tag>`), das auf **Port 8080** läuft
## Mit Helm bereitstellen
### Aus diesem Repo installieren (lokales Chart)
```bash
kubectl create namespace bentopdf
helm upgrade --install bentopdf /path/to/bentopdf/chart \
--namespace bentopdf \
--set image.repository=ghcr.io/alam00000/bentopdf \
--set image.tag=latest
```
### Aus GHCR installieren (OCI-Chart)
Wenn das Chart als OCI-Artefakt in GHCR veröffentlicht ist:
```bash
export GHCR_USERNAME="<github-org-or-user>"
helm upgrade --install bentopdf oci://ghcr.io/$GHCR_USERNAME/charts/bentopdf \
--namespace bentopdf \
--create-namespace \
--version 0.1.0 \
--set image.repository=ghcr.io/alam00000/bentopdf \
--set image.tag=latest
```
## Verfügbar machen
### Port-Forward (schneller Test)
```bash
kubectl -n bentopdf port-forward deploy/bentopdf 8080:8080
```
### Ingress (optional)
Aktivieren Sie Ingress (Beispiel für nginx-ingress):
```yaml
ingress:
enabled: true
className: nginx
hosts:
- host: pdf.example.com
paths:
- path: /
pathType: Prefix
```
### Gateway API (optional)
Dieses Chart unterstützt Gateway API `Gateway` + `HTTPRoute`.
Beispiel (Cloudflare-Gateway-API-Operator):
```yaml
gateway:
enabled: true
name: bento-tunnel
namespace: bentopdf
gatewayClassName: cloudflare
httpRoute:
enabled: true
parentRefs:
- name: bento-tunnel
namespace: bentopdf
sectionName: http
hostnames:
- pdfs.example.com
```
## Sicherstellen, dass die SharedArrayBuffer-Header weiterhin funktionieren (Ingress/Gateway)
### Was passieren "sollte"
Die Nginx-Konfiguration von BentoPDF setzt die erforderlichen Response-Header. Die meisten Ingress-/Gateway-Controller **leiten Response-Header des Upstreams unverändert weiter**.
### Was das verhindern kann
- Eine Controller-/Edge-Richtlinie, die Response-Header **überschreibt** oder **entfernt**
- Eine "Security-Header"-Middleware, die andere COOP/COEP-Werte setzt
### So überprüfen Sie es
Führen Sie dies gegen Ihren öffentlichen Endpunkt aus:
```bash
curl -I https://pdf.example.com/ | egrep -i 'cross-origin-opener-policy|cross-origin-embedder-policy'
```
Sie sollten Folgendes sehen:
- `Cross-Origin-Opener-Policy: same-origin`
- `Cross-Origin-Embedder-Policy: require-corp`
### Wenn Ihr Ingress-Controller sie nicht beibehält
Fügen Sie die Header am Edge hinzu (controllerspezifisch). Beispiel für **nginx-ingress**:
```yaml
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
```
## `.mjs`-MIME-Type-Fehler (PDF unterschreiben / PDF-Formular ausfüllen, leerer iframe)
Wenn die Browserkonsole bei einer `.mjs`-Anfrage `Failed to load module script: ... non-JavaScript MIME type "application/octet-stream"` anzeigt, ersetzt ein Ingress-/Gateway-Controller vor dem BentoPDF-Nginx den Upstream-Header `Content-Type` durch `application/octet-stream`.
Der Nginx im BentoPDF-Image liefert `.mjs` bereits als `application/javascript` aus. Die Lösung besteht darin, den Controller daran zu hindern, den Header zu entfernen oder zu ersetzen. Für nginx-ingress:
```yaml
ingress:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
location ~* \.mjs$ {
types {} default_type application/javascript;
}
```
Oder verwenden Sie in der Gateway API einen `ResponseHeaderModifier`-Filter, um `Content-Type: application/javascript` für `.mjs`-Pfade zu setzen. Überprüfen Sie es im DevTools-Tab Network: Die fehlgeschlagene `.mjs`-Anfrage sollte nun `content-type: application/javascript` zeigen.
### Wenn Sie die Gateway API nutzen und Header erzwingen möchten
Die Gateway API unterstützt einen `ResponseHeaderModifier`-Filter. Sie können ihn in `httpRoute.rules[*].filters` einbinden:
```yaml
httpRoute:
enabled: true
hostnames: [pdf.example.com]
parentRefs:
- name: bento-tunnel
namespace: misc
sectionName: http
rules:
- matches:
- path: { type: PathPrefix, value: / }
filters:
- type: ResponseHeaderModifier
responseHeaderModifier:
set:
- name: Cross-Origin-Opener-Policy
value: same-origin
- name: Cross-Origin-Embedder-Policy
value: require-corp
```
Die Unterstützung bestimmter Filter hängt von Ihrem Gateway-Controller ab. Wenn ein Filter ignoriert wird, fügen Sie die Header stattdessen am Edge bzw. im Controller hinzu.
## Bestimmte Werkzeuge deaktivieren
Verwenden Sie eine ConfigMap, um Werkzeuge zur Laufzeit zu deaktivieren, ohne das Image neu zu bauen:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: bentopdf-config
namespace: bentopdf
data:
config.json: |
{
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
}
```
Binden Sie sie in das ausgelieferte Verzeichnis ein:
```yaml
spec:
containers:
- name: bentopdf
volumeMounts:
- name: config
mountPath: /usr/share/nginx/html/config.json
subPath: config.json
readOnly: true
volumes:
- name: config
configMap:
name: bentopdf-config
```
Werkzeug-IDs entsprechen der Seiten-URL ohne `.html`: Öffnen Sie ein beliebiges Werkzeug und sehen Sie sich die URL an (z. B. `edit-pdf`, `merge-pdf`, `compress-pdf`). Deaktivierte Werkzeuge werden auf der Startseite, in der Suche, in den Tastenkürzeln, im Workflow-Builder und beim direkten URL-Zugriff ausgeblendet. Die vollständige Liste der Optionen finden Sie im [Docker-Leitfaden](/de/self-hosting/docker#bestimmte-werkzeuge-deaktivieren).
+139
View File
@@ -0,0 +1,139 @@
# Auf Netlify bereitstellen
[Netlify](https://netlify.com) bietet hervorragendes Hosting für statische Websites mit einem großzügigen kostenlosen Kontingent.
## Bereitstellung mit einem Klick
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/alam00000/bentopdf)
## Manuelle Bereitstellung
### Schritt 1: Repository verbinden
1. Melden Sie sich bei [Netlify](https://app.netlify.com) an
2. Klicken Sie auf "Add new site" → "Import an existing project"
3. Verbinden Sie Ihr GitHub-Konto
4. Wählen Sie Ihren BentoPDF-Fork aus
### Schritt 2: Build-Einstellungen konfigurieren
| Einstellung | Wert |
| ----------------- | --------------- |
| Build command | `npm run build` |
| Publish directory | `dist` |
| Node version | 18+ |
### Schritt 3: Bereitstellen
Klicken Sie auf "Deploy site" und warten Sie auf den Build.
## Konfigurationsdatei
Erstellen Sie `netlify.toml` im Stammverzeichnis Ihres Projekts:
```toml
[build]
command = "npm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "18"
# Required security headers for SharedArrayBuffer (used by LibreOffice WASM)
[[headers]]
for = "/*"
[headers.values]
Cross-Origin-Embedder-Policy = "require-corp"
Cross-Origin-Opener-Policy = "same-origin"
Cross-Origin-Resource-Policy = "cross-origin"
# Pre-compressed LibreOffice WASM binary - must be served with Content-Encoding
[[headers]]
for = "/libreoffice-wasm/soffice.wasm.gz"
[headers.values]
Content-Type = "application/wasm"
Content-Encoding = "gzip"
Cache-Control = "public, max-age=31536000, immutable"
# Pre-compressed LibreOffice WASM data - must be served with Content-Encoding
[[headers]]
for = "/libreoffice-wasm/soffice.data.gz"
[headers.values]
Content-Type = "application/octet-stream"
Content-Encoding = "gzip"
Cache-Control = "public, max-age=31536000, immutable"
# Cache other WASM files
[[headers]]
for = "*.wasm"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
Content-Type = "application/wasm"
# SPA routing
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
```
::: warning Wichtig
Die Header `Cross-Origin-Embedder-Policy` und `Cross-Origin-Opener-Policy` sind für die Konvertierung von Word/ODT/Excel/PowerPoint in PDF erforderlich. Ohne sie ist `SharedArrayBuffer` nicht verfügbar, und die LibreOffice-WASM-Engine kann nicht initialisiert werden.
Die `Content-Encoding: gzip`-Header für die Dateien `.wasm.gz` und `.data.gz` weisen den Browser an, sie automatisch zu dekomprimieren. Ohne sie erhält der Browser rohe gzip-Bytes, und die WASM-Kompilierung schlägt fehl.
:::
## Umgebungsvariablen
Legen Sie diese unter Site settings → Environment variables fest:
| Variable | Beschreibung |
| ----------------------- | ---------------------------------------------------------- |
| `SIMPLE_MODE` | Auf `true` setzen für einen minimalen Build |
| `VITE_BRAND_NAME` | Eigener Markenname (ersetzt "BentoPDF") |
| `VITE_BRAND_LOGO` | Logopfad relativ zu `public/` (z. B. `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | Eigener Footer-/Copyright-Text |
| `VITE_DEFAULT_LANGUAGE` | Standardsprache der Oberfläche (z. B. `fr`, `de`, `es`) |
## Eigene Domain
1. Gehen Sie zu Site settings → Domain management
2. Klicken Sie auf "Add custom domain"
3. Folgen Sie den Anweisungen zur DNS-Konfiguration
## Große Mediendateien
Für große WASM-Dateien können Sie [Netlify Large Media](https://docs.netlify.com/large-media/overview/) aktivieren:
```bash
netlify lm:setup
git lfs track "*.wasm"
```
## Fehlerbehebung
### Word/ODT/Excel zu PDF bleibt bei 55 % hängen
Wenn Dokumentkonvertierungen bei 55 % hängen bleiben, öffnen Sie die DevTools-Konsole und prüfen Sie:
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
Wenn `crossOriginIsolated` den Wert `false` hat, werden die COEP/COOP-Header aus Ihrer `netlify.toml` nicht angewendet. Stellen Sie sicher, dass sich die Datei im Stammverzeichnis Ihres Projekts befindet, und stellen Sie erneut bereit.
Wenn Sie in der Konsole `expected magic word 00 61 73 6d, found 1f 8b 08 08` sehen, fehlen den `.wasm.gz`-Dateien die `Content-Encoding: gzip`-Header. Stellen Sie sicher, dass die `[[headers]]`-Blöcke für `soffice.wasm.gz` und `soffice.data.gz` in Ihrer `netlify.toml` enthalten sind.
### Build schlägt fehl
Prüfen Sie die Kompatibilität der Node-Version:
```toml
[build.environment]
NODE_VERSION = "20"
```
### Langsames erstes Laden
Aktivieren Sie die Asset-Optimierung unter Site settings → Build & deploy → Asset optimization.
+232
View File
@@ -0,0 +1,232 @@
# Mit Nginx bereitstellen
Hosten Sie BentoPDF mit Nginx auf Ihrem eigenen Server.
## Voraussetzungen
- Ubuntu-/Debian-Server
- Installierter Nginx
- SSL-Zertifikat (empfohlen: Let's Encrypt)
## Schritt 1: Das Projekt bauen
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
Um das Branding anzupassen, setzen Sie vor dem Build Umgebungsvariablen:
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## Schritt 2: Dateien kopieren
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## Schritt 3: Nginx-Konfiguration
Erstellen Sie `/etc/nginx/sites-available/bentopdf`:
```nginx
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
root /var/www/bentopdf;
index index.html;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/wasm;
gzip_min_length 1000;
# WASM MIME type
types {
application/wasm wasm;
}
# Required headers for SharedArrayBuffer (LibreOffice WASM)
# These must be on every response - especially HTML pages
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "cross-origin" always;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Pre-compressed LibreOffice WASM binary
location ~* /libreoffice-wasm/soffice\.wasm\.gz$ {
gzip off;
types {} default_type application/wasm;
add_header Content-Encoding gzip;
add_header Cache-Control "public, immutable";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
# Pre-compressed LibreOffice WASM data
location ~* /libreoffice-wasm/soffice\.data\.gz$ {
gzip off;
types {} default_type application/octet-stream;
add_header Content-Encoding gzip;
add_header Cache-Control "public, immutable";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|wasm)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
# SPA routing - serve index.html for all routes
location / {
try_files $uri $uri/ /index.html;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
}
```
## Schritt 4: Die Site aktivieren
```bash
sudo ln -s /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
## Schritt 5: SSL mit Let's Encrypt
```bash
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
```
## Bereitstellung in einem Unterverzeichnis
Um unter `/pdf/` zu hosten:
```nginx
location /pdf/ {
alias /var/www/bentopdf/;
try_files $uri $uri/ /pdf/index.html;
}
```
Bauen Sie mit:
```bash
BASE_URL=/pdf/ npm run build
```
## Performance-Tuning
Fügen Sie in `nginx.conf` hinzu:
```nginx
http {
# Enable sendfile
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Increase buffer sizes
client_max_body_size 100M;
# Worker connections
worker_connections 2048;
}
```
## Fehlerbehebung
### WASM wird nicht geladen
Stellen Sie sicher, dass der MIME-Type gesetzt ist:
```nginx
types {
application/wasm wasm;
}
```
### Signieren oder Formularausfüllung zeigt einen leeren Viewer (`.mjs`-MIME-Fehler)
Wenn die Browserkonsole Folgendes anzeigt:
```
Failed to load module script: The server responded with a non-JavaScript
MIME type of "application/octet-stream". Strict MIME type checking is
enforced for module scripts per HTML spec.
```
…und die fehlgeschlagene Anfrage auf `.mjs` endet, fehlt in Ihrem Nginx die Zuordnung `.mjs → application/javascript`. Der mitgelieferte PDF-Viewer (genutzt von PDF unterschreiben, PDF-Formular ausfüllen und einigen weiteren Werkzeugen) enthält ES-Moduldateien mit der Endung `.mjs`, und die Standard-`mime.types` von Nginx enthält diese nicht.
Beheben Sie es, indem Sie `mjs` in der bestehenden `application/javascript`-Zeile in `/etc/nginx/mime.types` ergänzen:
```nginx
application/javascript js mjs;
```
> Fügen Sie KEINE _zweite_ `application/javascript`-Zeile hinzu: Nginx liest `mime.types` Zeile für Zeile und nur eine Zuordnung von Typ zu Endung gilt, sodass eine zweite Zeile in manchen Versionen stillschweigend ignoriert wird. Bearbeiten Sie die bestehende Zeile und ergänzen Sie `mjs` neben `js`.
Oder überschreiben Sie es lokal in Ihrem Server-Block (ohne Änderung der globalen Datei):
```nginx
types {
application/javascript js mjs;
application/wasm wasm;
}
```
Laden Sie Nginx nach der Änderung neu:
```bash
sudo nginx -t && sudo systemctl reload nginx
```
Zur Bestätigung: Suchen Sie in den Browser-DevTools im Tab Network die fehlgeschlagene `.mjs`-Anfrage und prüfen Sie den Response-Header `Content-Type`. Er sollte jetzt `application/javascript` lauten, nicht `application/octet-stream`.
### Word/ODT/Excel zu PDF funktioniert nicht
LibreOffice-WASM benötigt `SharedArrayBuffer`, wofür die Header `Cross-Origin-Embedder-Policy` und `Cross-Origin-Opener-Policy` nötig sind. Außerdem wird ein sicherer Kontext benötigt: `http://localhost` funktioniert für lokale Tests, aber `http://192.168.x.x` oder andere LAN-IPs erfordern in der Regel HTTPS. Beachten Sie, dass `add_header`-Direktiven in einem `location`-Block die `add_header`-Direktiven auf Server-Ebene **überschreiben**, statt sich mit ihnen zu vereinen. Jeder `location`-Block mit eigenem `add_header` muss die COEP/COOP-Header enthalten.
Überprüfen Sie mit:
```bash
curl -I https://your-domain.com/ | grep -i cross-origin
```
Wenn ein Reverse-Proxy vor Nginx liegt, stellen Sie sicher, dass er diese Header beibehält.
### 502 Bad Gateway
Prüfen Sie die Nginx-Fehlerprotokolle:
```bash
sudo tail -f /var/log/nginx/error.log
```
+122
View File
@@ -0,0 +1,122 @@
# Auf Vercel bereitstellen
[Vercel](https://vercel.com) bietet die einfachste Bereitstellung für statische Websites.
## Bereitstellung mit einem Klick
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/alam00000/bentopdf)
## Manuelle Bereitstellung
### Schritt 1: Repository forken
Forken Sie [bentopdf/bentopdf](https://github.com/alam00000/bentopdf) in Ihr GitHub-Konto.
### Schritt 2: In Vercel importieren
1. Gehen Sie zu [vercel.com/new](https://vercel.com/new)
2. Wählen Sie Ihr geforktes Repository aus
3. Konfigurieren Sie das Projekt:
| Einstellung | Wert |
| ---------------- | --------------- |
| Framework Preset | Vite |
| Build Command | `npm run build` |
| Output Directory | `dist` |
| Install Command | `npm install` |
### Schritt 3: Umgebungsvariablen (optional)
Fügen Sie diese bei Bedarf hinzu:
| Variable | Beschreibung |
| ----------------------- | ---------------------------------------------------------- |
| `SIMPLE_MODE` | Auf `true` setzen für eine minimale Oberfläche |
| `VITE_BRAND_NAME` | Eigener Markenname (ersetzt "BentoPDF") |
| `VITE_BRAND_LOGO` | Logopfad relativ zu `public/` (z. B. `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | Eigener Footer-/Copyright-Text |
| `VITE_DEFAULT_LANGUAGE` | Standardsprache der Oberfläche (z. B. `fr`, `de`, `es`) |
### Schritt 4: Bereitstellen
Klicken Sie auf "Deploy" und warten Sie, bis der Build abgeschlossen ist.
## Eigene Domain
1. Öffnen Sie die Einstellungen Ihres Projekts
2. Navigieren Sie zu "Domains"
3. Fügen Sie Ihre eigene Domain hinzu
4. Konfigurieren Sie das DNS wie angegeben
## Einschränkungen
::: warning Große Dateien
Die Serverless Functions von Vercel haben ein Limit von 50 MB. Da BentoPDF eine statische Website ist, sollte Sie das nicht betreffen, aber die WASM-Module sind groß (insgesamt ca. 100 MB). Stellen Sie sicher, dass sie aus dem Ordner `/public` ausgeliefert werden.
:::
## Fehlerbehebung
### Build-Timeout
Wenn Builds in ein Timeout laufen, versuchen Sie Folgendes:
```json
// vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": "dist"
}
```
### 404 beim Neuladen
Fügen Sie eine `vercel.json` für das SPA-Routing hinzu:
```json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
```
### Word/ODT/Excel zu PDF funktioniert nicht
Die LibreOffice-WASM-Konvertierungen erfordern `SharedArrayBuffer`, wofür diese Response-Header auf allen Seiten nötig sind:
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
Die vorkomprimierten Dateien `.wasm.gz` und `.data.gz` benötigen außerdem `Content-Encoding: gzip`, damit der Browser sie dekomprimiert.
Fügen Sie diese zu Ihrer `vercel.json` hinzu:
```json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }],
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" },
{ "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
{ "key": "Cross-Origin-Resource-Policy", "value": "cross-origin" }
]
},
{
"source": "/libreoffice-wasm/soffice.wasm.gz",
"headers": [
{ "key": "Content-Type", "value": "application/wasm" },
{ "key": "Content-Encoding", "value": "gzip" }
]
},
{
"source": "/libreoffice-wasm/soffice.data.gz",
"headers": [
{ "key": "Content-Type", "value": "application/octet-stream" },
{ "key": "Content-Encoding", "value": "gzip" }
]
}
]
}
```
Zur Überprüfung öffnen Sie die DevTools-Konsole und führen Sie `console.log(window.crossOriginIsolated)` aus. Es sollte `true` zurückgeben.