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 @@
# Implementeren met Apache
Host BentoPDF met Apache HTTP Server.
## Vereisten
- Apache 2.4+
- mod_rewrite ingeschakeld
- SSL-certificaat (aanbevolen)
## Stap 1: Bouw het project
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
Wil je de huisstijl aanpassen, stel dan omgevingsvariabelen in voordat je bouwt:
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## Stap 2: Kopieer de bestanden
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## Stap 3: Apache-configuratie
Maak `/etc/apache2/sites-available/bentopdf.conf` aan:
```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>
```
## Stap 4: .htaccess voor routing
Maak `/var/www/bentopdf/.htaccess` aan:
```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
```
## Stap 5: Schakel de vereiste modules in
```bash
sudo a2enmod rewrite
sudo a2enmod ssl
sudo a2enmod headers
sudo a2enmod expires
sudo a2enmod deflate
```
## Stap 6: Schakel de site in
```bash
sudo a2ensite bentopdf.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
```
## Implementatie in een submap
Om te hosten op `/pdf/`:
1. Bouw met de basis-URL:
```bash
BASE_URL=/pdf/ npm run build
```
2. Werk `.htaccess` bij:
```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
```
## Probleemoplossing
### WASM 404-fouten
Controleer of het MIME-type is geconfigureerd:
```apache
AddType application/wasm .wasm
```
### PDF ondertekenen of formulierinvuller toont een leeg weergavevenster (`.mjs` MIME-fout)
Als de browserconsole dit toont:
```
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.
```
...en het mislukte verzoek eindigt op `.mjs`, ontbreekt in je Apache-configuratie de koppeling `.mjs → application/javascript`. De meegeleverde PDF-viewer bevat ES-modulebestanden met de extensie `.mjs`, en de standaard `mime.types` van Apache bevat die niet.
Voeg toe aan je VirtualHost of `.htaccess`:
```apache
AddType application/javascript .mjs
```
Laad Apache daarna opnieuw:
```bash
sudo apache2ctl configtest && sudo systemctl reload apache2
```
Controleren: zoek in de browser-DevTools op het tabblad Network het mislukte `.mjs`-verzoek en bekijk de responsheader `Content-Type`. Die moet nu `application/javascript` zijn, niet `application/octet-stream`.
### Rewrite werkt niet
Controleer of mod_rewrite is ingeschakeld:
```bash
sudo a2enmod rewrite
```
### Word/ODT/Excel naar PDF werkt niet
LibreOffice WASM vereist `SharedArrayBuffer`, waarvoor deze headers nodig zijn:
```apache
Header always set Cross-Origin-Embedder-Policy "require-corp"
Header always set Cross-Origin-Opener-Policy "same-origin"
```
Daarnaast is een beveiligde context nodig. `http://localhost` werkt voor lokaal testen, maar `http://192.168.x.x` of andere LAN-IP's vereisen meestal HTTPS. Als de headers aanwezig zijn maar `window.crossOriginIsolated` nog steeds `false` is, controleer dan of de pagina via gewoon HTTP wordt geopend op een origin die geen loopback is.
De vooraf gecomprimeerde bestanden `.wasm.gz` en `.data.gz` hebben ook de juiste `Content-Encoding` nodig:
```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>
```
Zorg dat `mod_headers` is ingeschakeld: `sudo a2enmod headers`
### Toegang geweigerd
```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 @@
# Implementeren op AWS S3 + CloudFront
Host BentoPDF op AWS voor maximale controle en schaalbaarheid.
## Architectuur
```
User → CloudFront (CDN) → S3 (Static Files)
```
## Stap 1: Maak een S3-bucket
```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
```
## Stap 2: Bouwen en uploaden
```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
```
## Stap 3: Maak een CloudFront-distributie
```bash
aws cloudfront create-distribution \
--origin-domain-name your-bentopdf-bucket.s3.amazonaws.com \
--default-root-object index.html
```
Of gebruik de AWS Console:
1. Ga naar CloudFront → Create distribution
2. Origin domain: selecteer je S3-bucket
3. Schakel "Origin Access Control" in
4. Default root object: `index.html`
5. Maak de distributie aan
## Stap 3b: Response Headers Policy (vereist voor LibreOffice WASM)
Conversies met LibreOffice (Word, Excel, PowerPoint naar PDF) vereisen `SharedArrayBuffer`, waarvoor specifieke responsheaders nodig zijn. Maak een CloudFront Response Headers Policy aan:
1. Ga naar CloudFront → Policies → Response headers
2. Maak een aangepaste policy met deze headers:
| Header | Waarde |
| ------------------------------ | -------------- |
| `Cross-Origin-Embedder-Policy` | `require-corp` |
| `Cross-Origin-Opener-Policy` | `same-origin` |
| `Cross-Origin-Resource-Policy` | `cross-origin` |
3. Koppel de policy aan het standaard cachegedrag van je distributie
Of via de 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}
]
}
}'
```
## Stap 3c: S3-metadata voor vooraf gecomprimeerde WASM-bestanden
De LibreOffice WASM-bestanden zijn vooraf gecomprimeerd (`.wasm.gz`, `.data.gz`). Stel de juiste Content-Type en Content-Encoding in, zodat browsers ze decomprimeren:
```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 Belangrijk
Zonder de response headers policy is `SharedArrayBuffer` niet beschikbaar en blijven LibreOffice WASM-conversies hangen op ~55%. Zonder de juiste Content-Encoding op de `.gz`-bestanden ontvangt de browser onbewerkte gzip-bytes en mislukt de WASM-compilatie.
:::
## Stap 4: S3-bucketbeleid
Geef CloudFront toegang tot de 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"
}
}
}
]
}
```
## Stap 5: Aangepaste foutpagina's
Configureer 404 zo dat `index.html` wordt geretourneerd voor SPA-routing:
1. CloudFront → Error pages
2. Maak een aangepaste foutrespons:
- HTTP error code: 404
- Response page path: `/index.html`
- HTTP response code: 200
## Kostenraming
| Resource | Geschatte kosten |
| -------------------------- | ---------------- |
| S3-opslag (~500MB) | ~$0,01/maand |
| CloudFront (1TB transfer) | ~$85/maand |
| CloudFront (10GB transfer) | ~$0,85/maand |
::: tip
Gebruik S3 Intelligent Tiering voor kostenoptimalisatie bij bestanden die zelden worden geopend.
:::
## Automatisering met 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 @@
# Implementeren op Cloudflare Pages
[Cloudflare Pages](https://pages.cloudflare.com) biedt snelle, wereldwijde hosting van statische sites met onbeperkte bandbreedte.
## Snel implementeren
1. Ga naar [Cloudflare Pages](https://dash.cloudflare.com/?to=/:account/pages)
2. Klik op "Create a project"
3. Koppel je GitHub-repository
## Buildconfiguratie
| Instelling | Waarde |
| ---------------------- | --------------- |
| Framework preset | None |
| Build command | `npm run build` |
| Build output directory | `dist` |
| Root directory | `/` |
## Omgevingsvariabelen
Voeg deze toe onder Settings → Environment variables:
| Variabele | Waarde |
| ----------------------- | ------------------------------------------------------ |
| `NODE_VERSION` | `18` |
| `SIMPLE_MODE` | `false` (optioneel) |
| `VITE_BRAND_NAME` | Aangepaste merknaam (optioneel) |
| `VITE_BRAND_LOGO` | Logopad relatief aan `public/` (optioneel) |
| `VITE_FOOTER_TEXT` | Aangepaste footer-/copyrighttekst (optioneel) |
| `VITE_DEFAULT_LANGUAGE` | Standaardtaal van de interface, bijv. `fr` (optioneel) |
## Configuratiebestand
Maak `_headers` aan in je map `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 Belangrijk
De headers `Cross-Origin-Embedder-Policy` en `Cross-Origin-Opener-Policy` zijn vereist voor conversies van Word/ODT/Excel/PowerPoint naar PDF. Zonder deze headers is `SharedArrayBuffer` niet beschikbaar en kan de LibreOffice WASM-engine niet starten.
:::
Maak `_redirects` aan voor SPA-routing:
```
/* /index.html 200
```
## Eigen domein
1. Ga naar je Pages-project
2. Klik op "Custom domains"
3. Voeg je domein toe
4. Cloudflare configureert DNS automatisch als het domein bij Cloudflare staat
## Voordelen
- **Gratis onbeperkte bandbreedte**
- **Wereldwijd CDN** met 300+ edge-locaties
- **Automatische HTTPS**
- **Previewimplementaties** voor pull requests
- **Snelle builds**
## Probleemoplossing
### Uploads van grote bestanden
Cloudflare Pages ondersteunt bestanden tot 25 MB. WASM-modules zouden geen probleem moeten zijn, maar als je tegen limieten aanloopt, overweeg dan:
```bash
# Split large files during build
npm run build
```
### Groottelimieten van Workers
Gebruik je Cloudflare Workers voor geavanceerde routing, let dan op de limiet van 1 MB voor gratis abonnementen.
## CORS-proxyworker (voor digitale handtekeningen)
De tool voor digitale handtekeningen vereist een CORS-proxy om certificaatketens op te halen. Implementeer de meegeleverde worker:
```bash
cd cloudflare
npx wrangler login
npx wrangler deploy
```
### Beveiligingsfuncties
| Functie | Beschrijving |
| ------------------------- | ----------------------------------- |
| **URL-beperkingen** | Alleen certificaat-URL's toegestaan |
| **Bestandsgroottelimiet** | Max 10MB per verzoek |
| **Rate limiting** | 60 verzoeken/IP/min (vereist KV) |
| **Blokkering privé-IP's** | Blokkeert localhost en interne IP's |
### Rate limiting inschakelen
```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
```
### Bouwen met proxy-URL
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
Of met 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 .
```
> **Opmerking:** Zie de [README](https://github.com/alam00000/bentopdf#digital-signature-cors-proxy-required) voor de installatie van de HMAC-handtekening.
+180
View File
@@ -0,0 +1,180 @@
# CORS-proxy voor het ophalen van certificaten
De tool voor digitale handtekeningen gebruikt een CORS-proxy om uitgevers-certificaten op te halen bij externe certificeringsinstanties (CA's). Dat is nodig omdat veel CA-servers geen CORS-headers in hun antwoorden opnemen, waardoor rechtstreeks ophalen vanuit de browser niet lukt.
Bovendien leveren veel CA-servers certificaten via gewoon HTTP. Wanneer je BentoPDF-instantie via HTTPS wordt gehost, blokkeren browsers deze HTTP-verzoeken (mixed content-beleid). De CORS-proxy lost beide problemen op door verzoeken via een HTTPS-eindpunt met de juiste headers te routeren.
## Hoe het werkt
Bij het ondertekenen van een PDF met een certificaat:
1. De bibliotheek `zgapdfsigner` probeert een volledige certificaatketen op te bouwen
2. Ze haalt uitgevers-certificaten op via URL's in de AIA-extensie (Authority Information Access) van je certificaat
3. Deze verzoeken lopen via een CORS-proxy die de benodigde `Access-Control-Allow-Origin`-headers toevoegt
4. De proxy stuurt de certificaatgegevens terug naar de browser
## De CORS-proxy zelf hosten
Host je BentoPDF zelf, dan moet je een eigen CORS-proxy implementeren, zodat digitale handtekeningen werken met certificaten waarvoor de keten moet worden opgehaald.
### Optie 1: Cloudflare Workers (aanbevolen)
1. **Installeer de Wrangler CLI**:
```bash
npm install -g wrangler
```
2. **Log in bij Cloudflare**:
```bash
wrangler login
```
3. **Clone BentoPDF en pas de toegestane origins aan**:
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf/cloudflare
```
Open `cors-proxy-worker.js` en verander de array `ALLOWED_ORIGINS` naar je eigen domein:
```js
const ALLOWED_ORIGINS = [
'https://your-domain.com',
'https://www.your-domain.com',
];
```
::: warning Belangrijk
Zonder deze wijziging weigert de proxy alle verzoeken van je site met een **403 Forbidden**-fout. Standaard worden alleen verzoeken van `bentopdf.com` toegestaan.
:::
4. **Implementeer de proxy**:
```bash
wrangler deploy
```
Noteer de URL van je worker (bijv. `https://bentopdf-cors-proxy.your-subdomain.workers.dev`).
5. **Bouw BentoPDF opnieuw met de proxy-URL**:
Met 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 .
```
Bouwen vanuit de broncode:
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
### Optie 2: Eigen backendproxy
Je kunt ook je eigen proxy-eindpunt maken. De vereisten zijn:
1. Accepteer GET-verzoeken met een queryparameter `url`
2. Haal de URL op vanaf je server (aan de serverkant gelden geen CORS-beperkingen)
3. Retourneer het antwoord met deze headers:
- `Access-Control-Allow-Origin: https://your-domain.com`
- `Access-Control-Allow-Methods: GET, OPTIONS`
- `X-Content-Type-Options: nosniff`
Voorbeeldimplementatie met 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' });
}
});
```
## Beveiligingsoverwegingen
De meegeleverde Cloudflare Worker heeft diverse beveiligingsmaatregelen:
| Functie | Beschrijving |
| --------------------------- | ------------------------------------------------------------------------------------------------------- |
| **Origin-validatie** | Staat alleen verzoeken toe van domeinen in `ALLOWED_ORIGINS` |
| **URL-beperkingen** | Staat alleen certificaat-URL's toe (`.crt`, `.cer`, `.pem`, `/certs/`, `/ocsp`, `/crl`) |
| **Blokkering privé-IP's** | Blokkeert privébereiken van IPv4/IPv6, link-local, loopback, decimale IP's en cloud-metadata |
| **Content-Type-veiligheid** | Retourneert alleen veilige MIME-typen voor certificaten en blokkeert injectie van upstream content-type |
| **Bestandsgroottelimiet** | Streamt het antwoord met een limiet van 10MB en breekt halverwege de download af bij overschrijding |
| **Rate limiting** | 60 verzoeken per minuut per IP of IPv6 /64 (vereist KV, laat verkeer door bij KV-fouten) |
| **HMAC-handtekeningen** | Optionele ondertekening aan de clientzijde (ontmoedigt gelegenheidsmisbruik) |
### DNS-rebinding (zelf hosten buiten Cloudflare)
De worker weigert elke URL die naar een privé- of intern IP verwijst. Een vaste allowlist van hosts is niet mogelijk, omdat URL's in certificaatketens verwijzen naar de CA die het certificaat heeft uitgegeven (FNMT, bedrijfs-CA's enzovoort), dus de bestemmingen zijn vooraf niet bekend. Daardoor blijft een klein rebinding-venster over: wie de DNS van een eigen domein beheert, kan de veiligheidscontrole een publiek IP geven en het echte verzoek even later een privé-IP.
Op Cloudflare is dit niet te misbruiken: Workers kunnen geen privé-IP's of het metadata-eindpunt `169.254.169.254` bereiken, dus een rebind komt nergens uit. Geef de worker alleen geen toegang naar binnen: koppel geen Tunnel, Service of privénetwerkbinding (hij heeft alleen `RATE_LIMIT_KV` nodig).
Draai je dit buiten Cloudflare (workerd, Node en dergelijke) in een VPC, dan _kan_ de runtime interne IP's bereiken. Zet er een egress-firewall voor en blokkeer de privébereiken: `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` en hun IPv6-equivalenten. Applicatiecode kan dat niet afdwingen zodra je buiten Cloudflare draait.
## De proxy uitschakelen
Wil je geen CORS-proxy gebruiken, stel de omgevingsvariabele dan in op een lege string:
```
VITE_CORS_PROXY_URL=
```
**Opmerking**: Zonder de proxy mislukt het ondertekenen met certificaten waarvoor de keten extern moet worden opgehaald (zoals FNMT of sommige bedrijfs-CA's) met de foutmelding "Failed to fetch".
## Probleemoplossing
### "Signing error: TypeError: Failed to fetch"
Dit betekent meestal een van deze dingen:
1. **Geen CORS-proxy geconfigureerd**: stel `VITE_CORS_PROXY_URL` in en bouw opnieuw
2. **Mixed content geblokkeerd**: je site is HTTPS, maar de uitgevers-URL van het certificaat is HTTP. De CORS-proxy lost dit op.
3. **CORS-proxy weigert je origin**: controleer of je domein in de array `ALLOWED_ORIGINS` in `cors-proxy-worker.js` staat
### "403 Forbidden" van de proxy
Je domein staat niet in de lijst `ALLOWED_ORIGINS`. Bewerk `cors-proxy-worker.js`:
```js
const ALLOWED_ORIGINS = ['https://your-domain.com'];
```
Implementeer daarna opnieuw: `npx wrangler deploy`
### De proxy testen
```bash
curl -H "Origin: https://your-domain.com" \
"https://your-proxy.workers.dev?url=http://www.cert.fnmt.es/certs/ACUSU.crt"
```
### Certificaten die zonder proxy werken
Sommige certificaten bevatten de volledige keten in het P12/PFX-bestand en hoeven niets extern op te halen:
- Zelfondertekende certificaten
- Sommige commerciële CA's die tussencertificaten meeleveren
- Certificaten die je handmatig met de volledige keten hebt samengesteld
+622
View File
@@ -0,0 +1,622 @@
# Implementeren met Docker / Podman
De eenvoudigste manier om BentoPDF zelf te hosten in een productieomgeving.
> [!IMPORTANT]
> **Vereiste headers voor conversie van Office-bestanden**
>
> Op LibreOffice gebaseerde tools (conversie van Word, Excel en PowerPoint) hebben deze HTTP-headers nodig voor `SharedArrayBuffer`-ondersteuning:
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> De pagina moet ook vanuit een beveiligde context worden aangeboden. `http://localhost` werkt voor lokaal testen, maar `http://192.168.x.x` of andere LAN-IP's komen meestal niet in aanmerking, dus Office-conversie via gewoon HTTP mislukt ook als de headers aanwezig zijn.
>
> De officiële containerimages bevatten deze headers. Gebruik je een reverse proxy (Traefik, Caddy enzovoort), zorg dan dat deze headers behouden blijven of worden toegevoegd, en gebruik HTTPS voor toegang buiten loopback.
> [!TIP]
> **Podman-gebruikers:** Alle `docker`-opdrachten werken met Podman door `docker` te vervangen door `podman` en `docker-compose` door `podman-compose`.
## Snelstart
> [!TIP]
> BentoPDF wordt in twee builds geleverd:
>
> - **Zelfgehoste build**: `ghcr.io/alam00000/bentopdf-simple:latest`. Alle PDF-tools, **zonder** de marketingelementen (geen hero, FAQ, testimonials, footer). Gebruik deze voor interne implementaties van teams of organisaties. Het is **geen** "lite"-versie met minder functies.
> - **Commerciële build**: `ghcr.io/alam00000/bentopdf:latest`. De volledige marketingsite, gebruikt door bentopdf.com zelf en door houders van een commerciële licentie met openbare implementaties.
>
> Twijfel je, haal dan de zelfgehoste build op.
```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
Maak `docker-compose.yml` aan:
```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
```
Uitvoeren:
```bash
# Docker Compose
docker compose up -d
# Podman Compose
podman-compose up -d
```
## Bouw je eigen image
```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;"]
```
Bouwen en uitvoeren:
```bash
docker build -t bentopdf:custom .
docker run -d -p 3000:8080 bentopdf:custom
```
## Omgevingsvariabelen
| Variable | Description | Default |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `SIMPLE_MODE` | Bouw de zelfgehoste variant (verbergt de marketingelementen van bentopdf.com: hero, FAQ, testimonials, footer). Alle PDF-tools blijven beschikbaar. | `false` |
| `BASE_URL` | Implementeren in een submap | `/` |
| `VITE_WASM_PYMUPDF_URL` | URL van de PyMuPDF WASM-module | `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/` |
| `VITE_WASM_GS_URL` | URL van de Ghostscript WASM-module | `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm@0.1.1/assets/` |
| `VITE_WASM_CPDF_URL` | URL van de CoherentPDF WASM-module | `https://cdn.jsdelivr.net/npm/coherentpdf@2.5.5/dist/` |
| `VITE_TESSERACT_WORKER_URL` | URL van het OCR-workerscript | _(leeg; gebruik de standaard-CDN van Tesseract.js)_ |
| `VITE_TESSERACT_CORE_URL` | Map met de OCR-core-runtime | _(leeg; gebruik de standaard-CDN van Tesseract.js)_ |
| `VITE_TESSERACT_LANG_URL` | Map met OCR-traineddata | _(leeg; gebruik de standaard-CDN van Tesseract.js)_ |
| `VITE_TESSERACT_AVAILABLE_LANGUAGES` | OCR-talen in de interface, gescheiden door komma's | _(leeg; toon de volledige catalogus)_ |
| `VITE_OCR_FONT_BASE_URL` | Map met OCR-tekstlaaglettertypen | _(leeg; gebruik externe Noto-lettertype-URL's)_ |
| `VITE_DEFAULT_LANGUAGE` | Standaardtaal van de interface | `en` |
| `VITE_BRAND_NAME` | Aangepaste merknaam | `BentoPDF` |
| `VITE_BRAND_LOGO` | Logopad relatief aan `public/` | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | Aangepaste footer-/copyrighttekst | `© 2026 BentoPDF. All rights reserved.` |
| `DISABLE_TOOLS` | Tool-ID's om te verbergen, gescheiden door komma's | _(leeg; alle tools ingeschakeld)_ |
| `DISABLE_GITHUB_STARS` | Sla het ophalen van het aantal GitHub-sterren bij het laden van de pagina over en haal `api.github.com` uit de CSP. Builds in Simple Mode slaan het altijd over. | `false` |
| `SITE_URL` | Openbare URL die in canonical-, hreflang- en sitemaplinks wordt geschreven. Laat de standaardwaarde staan, tenzij zoekmachines je implementatie als eigen site moeten zien. | `https://www.bentopdf.com` |
| `ALLOW_INDEXING` | Bij een aangepaste `SITE_URL` krijgt elke pagina `noindex`, zodat je kopie niet concurreert met bentopdf.com in zoekresultaten. Zet op `true` om indexering toe te staan. | `false` |
WASM-module-URL's zijn vooraf geconfigureerd met CDN-standaardwaarden, dus alle geavanceerde functies werken direct. Overschrijf ze voor air-gapped of zelfgehoste implementaties.
### Zoekmachines
Standaard verwijst elke pagina zoekmachines naar bentopdf.com als origineel, zodat je implementatie niet als duplicaat verschijnt. Voor een privé- of interne instantie hoef je niets te wijzigen.
- **Houd je instantie helemaal uit zoekresultaten:** start de container met `-e ROBOTS_NOINDEX=true`. Hiervoor is alleen een herstart nodig, geen nieuwe build.
- **Maak van je instantie een eigen site in zoekresultaten:** bouw met `--build-arg SITE_URL=https://your-domain.example --build-arg ALLOW_INDEXING=true`. Zonder `ALLOW_INDEXING=true` markeert een aangepaste `SITE_URL` elke pagina als `noindex`.
### Content-Security-Policy
De nginx-image wordt geleverd met een afdwingende `Content-Security-Policy`-header. De richtlijnen `script-src`, `connect-src` en `font-src` van de CSP worden **tijdens de build gegenereerd** uit de bovenstaande `VITE_*`-URL-variabelen: alle hosts die je via `--build-arg` meegeeft, worden automatisch aan het beleid toegevoegd.
Daardoor geldt:
- Overschrijf je `VITE_CORS_PROXY_URL` of `VITE_WASM_*_URL` tijdens de build, dan staat de CSP die origins automatisch toe, zonder extra configuratie.
- Voor URL's op `cdn.jsdelivr.net` staat `script-src` alleen de exacte map van het versiegebonden pakket toe, bijvoorbeeld `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/`, niet de hele CDN. jsDelivr serveert elk openbaar npm-pakket of GitHub-repository, dus het toestaan van de hele host zou elk daarvan op je instantie laten draaien. URL's op andere hosts worden op origin toegestaan.
- Configureer je aangepaste WASM-URL's tijdens _runtime_ via de pagina Geavanceerde instellingen in de app, dan staan die origins **niet** in de CSP en blokkeert de browser het ophalen ervan. Dit geldt ook voor een andere versie van hetzelfde pakket op `cdn.jsdelivr.net`, omdat alleen de tijdens de build ingestelde versie is toegestaan. Runtimeconfiguratie is bedoeld om mee te experimenteren; stel voor permanente aangepaste URL's het bijbehorende `VITE_*`-buildargument in.
- Air-gapped implementaties die alle drie de `VITE_WASM_*_URL`-waarden overschrijven, verwijderen ook die `cdn.jsdelivr.net`-pakketmappen uit de CSP (elke standaardwaarde wordt vervangen, niet aangevuld). De Tesseract OCR-standaardwaarden op `cdn.jsdelivr.net` blijven in het beleid staan totdat je ook de drie `VITE_TESSERACT_*`-waarden instelt. Zo vervangt het instellen van `VITE_CORS_PROXY_URL` ook de openbare standaardwaarde `bentopdf-cors-proxy.bentopdf.workers.dev`.
De CSP bevat `'unsafe-eval'` in `script-src`, omdat de LibreOffice WASM-runtime (gebruikt door de conversietools voor Word/Excel/PowerPoint) en de afbeeldingsbibliotheek wasm-vips (gebruikt door PDF naar TIFF) interne dispatch-code compileren via `new Function()`. Zonder dit werken die tools niet meer. Bouw je in `SIMPLE_MODE` (zonder LibreOffice) en heb je PDF naar TIFF niet nodig, dan kun je het gegenereerde `security-headers.conf` handmatig bewerken om `'unsafe-eval'` te verwijderen voor een strenger beleid.
Laat voor OCR de `VITE_TESSERACT_*`-variabelen leeg om de standaard online assets te gebruiken, of stel alle drie samen in voor zelfgehoste/offline OCR. Gedeeltelijke OCR-overschrijvingen worden geweigerd, omdat de worker, de core-runtime en de traineddata-map bij elkaar moeten passen. Stel voor volledig offline doorzoekbare PDF-uitvoer ook `VITE_OCR_FONT_BASE_URL` in, zodat de OCR-tekstlaaglettertypen van je interne server worden geladen in plaats van van de openbare Noto-lettertype-URL's.
`VITE_DEFAULT_LANGUAGE` stelt de interfacetaal in voor nieuwe bezoekers. Ondersteunde waarden: `en`, `ar`, `be`, `fr`, `de`, `es`, `zh`, `zh-TW`, `vi`, `tr`, `id`, `it`, `pt`, `nl`, `da`. Gebruikers kunnen nog steeds van taal wisselen: dit verandert alleen de standaardtaal.
Voorbeeld:
```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
```
### Eigen huisstijl
Vervang het standaardlogo, de naam en de footertekst van BentoPDF door je eigen. Plaats je logobestand in de map `public/` (of gebruik een bestaande afbeelding) en geef de huisstijlvariabelen tijdens de build mee:
```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 .
```
Huisstijl werkt in zowel de volledige modus als Simple Mode en kan worden gecombineerd met alle andere buildopties.
### Specifieke tools uitschakelen
Verberg tools in de interface voor compliance- of beveiligingseisen. Uitgeschakelde tools verdwijnen van de startpagina, uit zoekresultaten, sneltoetsen en de workflowbouwer. Rechtstreekse toegang via de URL toont een pagina "tool niet beschikbaar".
Tool-ID's zijn de pagina-URL zonder `.html`. Staat een tool bijvoorbeeld op `bentopdf.com/edit-pdf.html`, dan is de ID `edit-pdf`.
#### Tool-ID's vinden
Het makkelijkst: open een tool in BentoPDF en kijk naar de URL. Het laatste deel van het pad (zonder `.html`) is de tool-ID.
<details>
<summary>Volledige lijst met tool-ID's</summary>
**Bewerken en annoteren:** `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`
**Naar PDF converteren:** `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`
**Van PDF converteren:** `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`
**Organiseren en beheren:** `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`
**Optimaliseren en repareren:** `compress-pdf`, `pdf-to-pdfa`, `fix-page-size`, `linearize-pdf`, `remove-restrictions`, `repair-pdf`, `rasterize-pdf`, `deskew-pdf`, `font-to-outline`
**Beveiliging:** `encrypt-pdf`, `sanitize-pdf`, `decrypt-pdf`, `flatten-pdf`, `remove-metadata`, `change-permissions`, `digital-sign-pdf`, `validate-signature-pdf`, `timestamp-pdf`
</details>
#### Optie 1: Tijdens de build (Docker-buildargument)
```bash
docker build \
--build-arg DISABLE_TOOLS="edit-pdf,sign-pdf,encrypt-pdf" \
-t bentopdf .
```
Hiermee wordt de lijst met uitgeschakelde tools in de JavaScript-bundel ingebakken. Wijzigen vereist een nieuwe build.
#### Optie 2: Tijdens runtime (config.json)
Koppel een bestand `config.json` in de aangeboden map, zonder nieuwe build:
```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
```
Of met 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 kunnen worden gecombineerd: de lijsten worden samengevoegd. Staat een tool in een van beide, dan is hij uitgeschakeld.
#### Editorfuncties uitschakelen
Je kunt ook specifieke functies in de PDF-editor (bijv. redactie, annotaties, formulieren) uitschakelen zonder de hele editortool uit te schakelen. Voeg `editorDisabledCategories` toe aan je `config.json`:
```json
{
"editorDisabledCategories": ["redaction", "annotation-stamp"]
}
```
<details>
<summary>Volledige lijst met editorcategorieën</summary>
**Zoom:** `zoom`, `zoom-in`, `zoom-out`, `zoom-fit-page`, `zoom-fit-width`, `zoom-marquee`, `zoom-level`
**Annotatie:** `annotation`, `annotation-markup`, `annotation-highlight`, `annotation-underline`, `annotation-strikeout`, `annotation-squiggly`, `annotation-ink`, `annotation-text`, `annotation-stamp`
**Vormen:** `annotation-shape`, `annotation-rectangle`, `annotation-circle`, `annotation-line`, `annotation-arrow`, `annotation-polygon`, `annotation-polyline`
**Formulier:** `form`, `form-textfield`, `form-checkbox`, `form-radio`, `form-select`, `form-listbox`, `form-fill-mode`
**Redactie:** `redaction`, `redaction-area`, `redaction-text`, `redaction-apply`, `redaction-clear`
**Document:** `document`, `document-open`, `document-close`, `document-print`, `document-capture`, `document-export`, `document-fullscreen`, `document-protect`
**Pagina:** `page`, `spread`, `rotate`, `scroll`, `navigation`
**Paneel:** `panel`, `panel-sidebar`, `panel-search`, `panel-comment`
**Tools:** `tools`, `pan`, `pointer`, `capture`
**Selectie:** `selection`, `selection-copy`
**Geschiedenis:** `history`, `history-undo`, `history-redo`
</details>
Categorieën zijn hiërarchisch: het uitschakelen van een bovenliggende categorie (bijv. `annotation`) schakelt alle onderliggende uit.
### De GitHub-sterrenteller uitschakelen
De navigatiebalk toont een live aantal GitHub-sterren, bij het laden van de pagina opgehaald van `api.github.com`. Het is het enige externe verzoek dat geen PDF-functie dient. Schakel het uit tijdens de build om de ophaalcode uit de bundel te verwijderen en `api.github.com` uit de Content-Security-Policy te halen:
```bash
docker build --build-arg DISABLE_GITHUB_STARS=true -t bentopdf .
```
Builds in Simple Mode (`bentopdf-simple`) slaan de sterrenteller altijd over: deze vlag is alleen nodig voor de commerciële build of aangepaste volledige builds.
### Aangepaste WASM-URL's (air-gapped / zelfgehost)
> [!IMPORTANT]
> WASM-URL's worden tijdens de **build** in de JavaScript ingebakken. De WASM-bestanden worden tijdens runtime door de **browser van de gebruiker** gedownload: Docker downloadt ze niet tijdens de build. Voor air-gapped netwerken moet je de WASM-bestanden hosten op een interne server die browsers kunnen bereiken.
**Volledige air-gapped werkwijze:**
```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
```
Gebruik de codes die `bash scripts/prepare-airgap.sh --list-ocr-languages` toont, of zoek op naam met `bash scripts/prepare-airgap.sh --search-ocr-language <term>`, voor `--ocr-languages`. Bouw je met een beperkte OCR-subset, geef dan dezelfde codes door aan `VITE_TESSERACT_AVAILABLE_LANGUAGES`, zodat de app alleen meegeleverde talen toont. Host voor volledig offline OCR-uitvoer ook de meegeleverde map `ocr-fonts/` en laat `VITE_OCR_FONT_BASE_URL` ernaar verwijzen.
Stel een variabele in op een lege string om die module uit te schakelen (gebruikers moeten dan handmatig configureren via Geavanceerde instellingen).
## Aangepaste poort
Standaard luistert BentoPDF op poort `8080` in de container. Om dit te wijzigen, stel je tijdens runtime de omgevingsvariabele `PORT` in:
```bash
docker run -p 3000:9090 -e PORT=9090 ghcr.io/alam00000/bentopdf-simple:latest
```
| Variable | Description | Default |
| -------- | ---------------------------------- | ------- |
| `PORT` | Nginx-luisterpoort in de container | `8080` |
Dit werkt met zowel de standaard als de nonroot Dockerfile.
## Aangepaste gebruikers-ID (PUID/PGID)
Voor omgevingen die als een specifieke niet-rootgebruiker moeten draaien (NAS-apparaten, Kubernetes met security contexts, organisatiebeleid) biedt BentoPDF een aparte Dockerfile met PUID/PGID-ondersteuning in LSIO-stijl.
### Bouwen en uitvoeren
```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
```
### Omgevingsvariabelen
| Variable | Description | Default |
| -------------- | ------------------------------- | ------- |
| `PUID` | Gebruikers-ID om mee te draaien | `1000` |
| `PGID` | Groeps-ID om mee te draaien | `1000` |
| `PORT` | Nginx-luisterpoort | `8080` |
| `DISABLE_IPV6` | IPv6-listener uitschakelen | `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
```
### Hoe het werkt
De container start als root, maakt een gebruiker aan met de opgegeven PUID/PGID, past het eigendom van alle beschrijfbare mappen aan en laat daarna rechten vallen met `su-exec`. Het nginx-proces draait volledig als de door jou opgegeven gebruiker.
> [!NOTE]
> De standaard `Dockerfile` gebruikt `nginx-unprivileged` (UID 101) en wordt aanbevolen voor de meeste implementaties. Gebruik `Dockerfile.nonroot` alleen als je een specifieke UID/GID nodig hebt.
> [!WARNING]
> PUID/PGID mag niet `0` (root) zijn. De entrypoint valideert de invoer en stopt met een foutmelding bij ongeldige waarden.
## Met 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
```
## Met 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"
}
```
## Resourcelimieten
```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-integratie)
[Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) laat je Podman-containers als systemd-services draaien. Dit is ideaal voor productie-implementaties op Linux-systemen.
### Basisinstallatie van Quadlet
Maak een containerunitbestand aan op `~/.config/containers/systemd/bentopdf.container` (gebruiker) of `/etc/containers/systemd/bentopdf.container` (systeem):
```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
```
### Inschakelen en 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]
> Gebruik voor een systeembrede implementatie `systemctl` zonder de vlag `--user` en plaats het bestand in `/etc/containers/systemd/`.
### Quadlet met 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
```
### Automatisch bijwerken met Quadlet
Podman kan containers automatisch bijwerken wanneer nieuwe images beschikbaar zijn:
```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
```
### Netwerkconfiguratie van Quadlet
Maak voor een aangepaste netwerkconfiguratie een netwerkbestand `bentopdf.network` aan:
```ini
[Network]
Subnet=10.89.0.0/24
Gateway=10.89.0.1
```
Verwijs er daarna naar in je containerbestand:
```ini
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
Network=bentopdf.network
```
## Bijwerken
```bash
# Pull latest image
docker compose pull
# Recreate container
docker compose up -d
```
+266
View File
@@ -0,0 +1,266 @@
# Implementeren op Hostinger
[Hostinger](https://hostinger.com) is een populaire aanbieder van shared hosting. Deze gids behandelt het implementeren van BentoPDF op de shared hosting van Hostinger.
## Vereisten
- Hostinger-hostingpakket met toegang tot de bestandsbeheerder
- Lokaal geïnstalleerde Node.js om te bouwen
## Stap 1: Bouw het project
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
De gebouwde bestanden staan in de map `dist`.
## Stap 2: Uploaden naar Hostinger
### Implementatie op het hoofddomein
1. Log in op je Hostinger-account
2. Ga naar **File Manager** → **public_html**
3. **Verwijder** bestaande bestanden (maak zo nodig een back-up)
4. **Upload** de volledige inhoud van je lokale map `dist` naar `public_html`
### Implementatie in een submap
Bij implementatie in een submap (bijv. `yourdomain.com/pdf-tools/`):
1. Bouw met de juiste basis-URL:
```bash
BASE_URL=/pdf-tools/ npm run build
```
2. Maak de map aan in Hostinger:
- Ga naar **File Manager** → **public_html**
- Maak een nieuwe map: `pdf-tools`
3. Upload de volledige inhoud van `dist` naar `public_html/pdf-tools/`
## Stap 3: Maak het .htaccess-bestand aan
Maak een bestand `.htaccess` aan in de hoofdmap van je implementatiemap (`public_html` of `public_html/pdf-tools/`):
::: warning Belangrijk
Wijzig bij implementatie in een submap `RewriteBase /` naar `RewriteBase /pdf-tools/` (of de naam van je map).
:::
```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
```
## Voorbeeld van .htaccess voor een submap
Pas voor `yourdomain.com/pdf-tools/` deze regels aan:
```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
```
## Probleemoplossing
### WASM-bestanden worden niet geladen
Controleer of de MIME-typen correct zijn ingesteld in `.htaccess`:
```apache
AddType application/wasm .wasm
```
### LibreOffice-tools werken niet
De beveiligingsheaders zijn cruciaal voor SharedArrayBuffer:
```apache
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
```
Als de headers niet worden toegepast, neem dan contact op met de support van Hostinger om `mod_headers` in te schakelen.
### 404-fouten bij het vernieuwen van de pagina
Controleer of de regel voor de extensie `.html` en de taalroutes correct zijn geconfigureerd. BentoPDF gebruikt statische HTML-bestanden, geen 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]
```
### Limieten voor bestandsuploads
Hostinger kan limieten voor bestandsgrootte hebben. Upload WASM-bestanden afzonderlijk als de bulkupload mislukt.
+538
View File
@@ -0,0 +1,538 @@
# Zelf hosten
BentoPDF kun je op je eigen infrastructuur zelf hosten. Deze gids behandelt verschillende implementatiemogelijkheden.
## Snel aan de slag met Docker / Podman
De snelste manier om BentoPDF zelf te hosten:
> [!TIP]
> BentoPDF wordt in twee builds geleverd:
>
> - **Self-Hosted build**: `ghcr.io/alam00000/bentopdf-simple:latest`. Alle PDF-tools, **zonder** de marketingelementen (geen hero, FAQ, testimonials, footer). Gebruik dit voor interne implementaties voor teams of organisaties. Het is **geen** versie met minder functies of een "lite"-versie.
> - **Commercial build**: `ghcr.io/alam00000/bentopdf:latest`. De volledige marketingsite, gebruikt door bentopdf.com zelf en door commerciële licentiehouders met openbare implementaties.
>
> Twijfel je? Haal dan de Self-Hosted build op.
> [!IMPORTANT]
> Voor het converteren van Office-bestanden is `SharedArrayBuffer` nodig. De app moet daarvoor zowel cross-origin geïsoleerd zijn als vanuit een veilige context worden aangeboden. De officiële image stuurt de vereiste COOP/COEP-headers al mee, maar browsers schakelen `SharedArrayBuffer` nog steeds uit op gewone HTTP-origins in een lokaal netwerk, zoals `http://192.168.x.x`.
>
> Gebruik `http://localhost` alleen voor tests op hetzelfde apparaat. Als gebruikers BentoPDF via een LAN-IP of hostnaam openen, zorg dan voor 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
```
Of met 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)
Draai BentoPDF als systemd-service. Maak `~/.config/containers/systemd/bentopdf.container` aan:
```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
```
Zie de [Docker-implementatiegids](/nl/self-hosting/docker) voor de volledige Quadlet-documentatie.
## Bouwen vanuit de broncode
```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
```
## Configuratieopties
### Self-Hosted build (Simple Mode)
De Self-Hosted build (de `bentopdf-simple` image, ook wel Simple Mode genoemd) is **functioneel identiek** aan de Commercial build: alle PDF-tools zijn aanwezig en werken hetzelfde. Hij verbergt alleen de marketingelementen die uitsluitend zinvol zijn op bentopdf.com zelf of bij een commerciële, openbare implementatie. **Het is geen versie met minder functies of een "lite"-versie.**
**Wat de Self-Hosted build verbergt** (alleen cosmetisch, er worden geen PDF-functies verwijderd):
- Navigatiebalk, hero-sectie, functiesectie, FAQ, testimonials, footer
- Wijzigt de paginatitel in "PDF-tools"
**Wat de Self-Hosted build behoudt** (alles wat daadwerkelijk PDF-werk doet):
- Alle PDF-tools (samenvoegen, splitsen, bewerken, ondertekenen, OCR, Office-conversie, enz.)
- Ondersteuning voor eigen huisstijl, alle configuratie tijdens het bouwen en tijdens runtime
De Commercial build (`ghcr.io/alam00000/bentopdf:latest`) is wat bentopdf.com zelf draait en wordt gebruikt door commerciële licentiehouders met openbare implementaties. Hij voegt de hero, FAQ, testimonials en footer toe die op een interne tool niet zinvol zouden zijn.
Host je BentoPDF zelf voor je team, organisatie of als interne tool, haal dan de Self-Hosted build op:
```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
```
Zie [SIMPLE_MODE.md](https://github.com/alam00000/bentopdf/blob/main/SIMPLE_MODE.md) voor alle details.
### Commercial build (openbare implementaties met je eigen merk)
De Commercial build (de `bentopdf` image, zonder het achtervoegsel `-simple`) is wat bentopdf.com zelf draait. Hij bevat de volledige marketingsite (hero, functies, FAQ, testimonials, footer) bovenop alle PDF-tools. Gebruik deze build als je **BentoPDF wilt implementeren als openbare PDF-dienst onder je eigen merk**, bijvoorbeeld:
- Je draait BentoPDF als gehoste SaaS voor eindgebruikers op je eigen domein
- Je wilt de ervaring van de landingspagina (marketingsecties plus tools), niet alleen de kale toolomgeving
- Je bent commercieel licentiehouder en verwerkt BentoPDF in een commercieel product
**Draai hem ongewijzigd** (met BentoPDF-huisstijl, handig om de build te bekijken):
```bash
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
```
**Bouw met je eigen merk** (de gebruikelijke commerciële werkwijze):
```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
```
Elke andere buildoptie (`BASE_URL`, `VITE_DEFAULT_LANGUAGE`, `DISABLE_TOOLS`, WASM-URL-overschrijvingen, enz.) werkt op dezelfde manier als bij de Self-Hosted build.
::: warning Licenties
De Commercial build draaien is toegestaan onder een van de twee licentieopties van BentoPDF:
- **AGPL-3.0** (gratis): toegestaan als je implementatie de volledige broncode onder AGPL publiceert, inclusief aanpassingen aan de huisstijl en de omringende bedrijfslogica.
- **Commerciële licentie** ($79 eenmalig): vereist voor closed-source / propriëtaire implementaties waarbij je je huisstijlfork of bedrijfscode niet open source maakt.
Zie de [Licentiepagina](https://www.bentopdf.com/licensing) voor de volledige vergelijking. AGPL-gelicentieerde WASM-modules (PyMuPDF, Ghostscript, CoherentPDF) worden tijdens runtime vanaf een CDN geladen, dus ze komen niet in je image terecht en veranderen je licentiepositie niet.
:::
### Basis-URL
Implementeren in een submap:
```bash
BASE_URL=/pdf-tools/ npm run build
```
### Eigen huisstijl
Vervang tijdens het bouwen het standaard BentoPDF-logo, de naam en de voettekst door je eigen:
| Variabele | Beschrijving | Standaard |
| ------------------ | ------------------------------------------- | --------------------------------------- |
| `VITE_BRAND_NAME` | Merknaam in de header en footer | `BentoPDF` |
| `VITE_BRAND_LOGO` | Logopad relatief ten opzichte van `public/` | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | Eigen footer-/copyrighttekst | `© 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
```
Of via 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 .
```
Eigen huisstijl werkt in zowel de volledige modus als Simple Mode en kan worden gecombineerd met alle andere buildopties (`BASE_URL`, `SIMPLE_MODE`, `VITE_DEFAULT_LANGUAGE`).
### Specifieke tools uitschakelen
Verberg afzonderlijke tools voor compliance of beveiliging. Uitgeschakelde tools worden verwijderd van de startpagina, uit zoeken, sneltoetsen, de workflowbouwer en directe URL-toegang.
Tool-ID's zijn de pagina-URL zonder `.html`: open een tool en kijk naar de URL (bijv. `edit-pdf`, `sign-pdf`, `encrypt-pdf`).
**Tijdens het bouwen** (ingebakken in de bundel):
```bash
DISABLE_TOOLS="edit-pdf,sign-pdf" npm run build
```
**Tijdens runtime** (geen nieuwe build nodig: koppel een `config.json`):
```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 kunnen worden gecombineerd: de lijsten worden samengevoegd.
Je kunt ook specifieke functies in de PDF-editor (bijv. redactie) uitschakelen zonder de hele tool uit te schakelen door `editorDisabledCategories` toe te voegen aan `config.json`. Zie de [Docker-gids](/nl/self-hosting/docker#editorfuncties-uitschakelen) voor de volledige lijst met categorieën.
## Implementatiegidsen
Kies je platform:
- [Vercel](/nl/self-hosting/vercel)
- [Netlify](/nl/self-hosting/netlify)
- [Cloudflare Pages](/nl/self-hosting/cloudflare)
- [AWS S3 + CloudFront](/nl/self-hosting/aws)
- [Hostinger](/nl/self-hosting/hostinger)
- [Nginx](/nl/self-hosting/nginx)
- [Apache](/nl/self-hosting/apache)
- [Docker](/nl/self-hosting/docker)
- [Kubernetes](/nl/self-hosting/kubernetes)
- [CORS Proxy](/nl/self-hosting/cors-proxy) - Vereist voor digitale handtekeningen
## Veelvoorkomende problemen
### Word / ODT / Excel / PowerPoint naar PDF blijft hangen (SharedArrayBuffer niet beschikbaar)
**Symptoom**: Documentconversies op basis van LibreOffice (Word, ODT, Excel, PowerPoint naar PDF) blijven hangen bij ~55% of starten niet. De browserconsole toont mogelijk:
> `ReferenceError: SharedArrayBuffer is not defined`
...of `window.crossOriginIsolated` meldt `false`, of de WASM-compilatie meldt `expected magic word 00 61 73 6d, found 1f 8b 08 08`.
**Oorzaak**: LibreOffice WASM vereist `SharedArrayBuffer`, dat de browser alleen inschakelt wanneer de pagina **cross-origin geïsoleerd** is EN vanuit een **veilige context** wordt aangeboden. Dat betekent dat twee dingen moeten kloppen:
1. Elke respons bevat beide headers:
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
2. De pagina wordt aangeboden vanaf `https://...` of `http://localhost`. Gewone HTTP op een LAN-IP (bijv. `http://192.168.x.x`) telt NIET als veilig: browsers schakelen `SharedArrayBuffer` daar uit.
Het verschil tussen `00 61 73 6d / 1f 8b 08 08` is een apart subsymptoom: bij de vooraf gecomprimeerde `.wasm.gz` / `.data.gz`-bestanden ontbreekt de responsheader `Content-Encoding: gzip`, waardoor de browser ruwe gzip-bytes ontvangt in plaats van gedecomprimeerde WASM.
**Oplossing**: zie de platformspecifieke implementatiegids voor de exacte configuratie:
- [Nginx →](/nl/self-hosting/nginx#pdf-ondertekenen-of-formulierinvuller-toont-een-lege-viewer-mjs-mime-fout)
- [Apache →](/nl/self-hosting/apache#pdf-ondertekenen-of-formulierinvuller-toont-een-leeg-weergavevenster-mjs-mime-fout)
- [AWS S3 + CloudFront →](/nl/self-hosting/aws#stap-3b-response-headers-policy-vereist-voor-libreoffice-wasm)
- [Cloudflare Pages →](/nl/self-hosting/cloudflare#configuratiebestand) (`_headers`-bestand)
- [Netlify →](/nl/self-hosting/netlify#word-odt-excel-naar-pdf-blijft-hangen-op-55) (`netlify.toml`)
- [Vercel →](/nl/self-hosting/vercel#word-odt-excel-naar-pdf-werkt-niet) (`vercel.json`)
- [Hostinger →](/nl/self-hosting/hostinger#libreoffice-tools-werken-niet) (`.htaccess`)
- [Kubernetes →](/nl/self-hosting/kubernetes#mjs-mime-typefouten-iframe-van-pdf-ondertekenen-pdf-formulier-invullen-leeg)
- **Docker**: wordt automatisch afgehandeld door de meegeleverde nginx-configuratie, geen actie nodig.
**Controleren**: open de DevTools-console op een willekeurige BentoPDF-pagina en voer uit:
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
Als de pagina HTTPS of `http://localhost` gebruikt EN beide COEP/COOP-headers op elke respons aanwezig zijn, slagen beide controles. Zit je op `http://192.168.x.x` of een andere niet-loopback HTTP-origin, zorg dan voor HTTPS: een oplossing met alleen headers bestaat niet.
### `.mjs`-bestanden geleverd als `application/octet-stream`
**Symptoom**: PDF ondertekenen / PDF-formulier invullen / enkele andere tools tonen een lege viewer of laden niet. De browserconsole meldt:
> `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.`
**Oorzaak**: Je webserver of reverse proxy heeft geen MIME-type-koppeling voor `.mjs`-bestanden (de meegeleverde PDF-viewer gebruikt ES-modules met die extensie). Veel standaard serverconfiguraties gebruiken voor onbekende extensies `application/octet-stream`, wat browsers weigeren uit te voeren als ES-modules.
**Oplossing**: zie de platformspecifieke implementatiegids voor het exacte fragment:
- [Nginx →](/nl/self-hosting/nginx#pdf-ondertekenen-of-formulierinvuller-toont-een-lege-viewer-mjs-mime-fout)
- [Apache →](/nl/self-hosting/apache#pdf-ondertekenen-of-formulierinvuller-toont-een-leeg-weergavevenster-mjs-mime-fout)
- [AWS S3 + CloudFront →](/nl/self-hosting/aws#stap-2-bouwen-en-uploaden) (zie `aws s3 cp ... --include "*.mjs"`)
- [Kubernetes →](/nl/self-hosting/kubernetes#mjs-mime-typefouten-iframe-van-pdf-ondertekenen-pdf-formulier-invullen-leeg)
- **Docker, Vercel, Netlify, Cloudflare Pages, Hostinger**: worden automatisch afgehandeld, geen actie nodig.
**Controleren**: open DevTools → tabblad Netwerk, zoek het mislukte `.mjs`-verzoek en bekijk de responsheader `Content-Type`. Die hoort `application/javascript` te zijn. Is het nog steeds `application/octet-stream`, dan bepaalt een externe reverse proxy of CDN het type mogelijk opnieuw: controleer elke laag in je serveringsketen.
## WASM-configuratie (AGPL-componenten)
BentoPDF **bundelt geen** AGPL-gelicentieerde verwerkingsbibliotheken in de broncode, maar **stelt CDN-URL's vooraf in** zodat alle functies direct werken, zonder handmatige configuratie.
::: tip Standaard zonder configuratie
Sinds v2.0.0 zijn WASM-modules via omgevingsvariabelen vooraf ingesteld om vanaf de jsDelivr CDN te laden. Alle geavanceerde functies werken direct zonder configuratie door de gebruiker.
:::
| Component | Licentie | Functies |
| --------------- | -------- | ----------------------------------------------------------------- |
| **PyMuPDF** | AGPL-3.0 | EPUB/MOBI/FB2/XPS-conversie, afbeeldingsextractie, tabelextractie |
| **Ghostscript** | AGPL-3.0 | PDF/A-conversie, compressie, rechtzetten, rasteren |
| **CoherentPDF** | AGPL-3.0 | Inhoudsopgave, bijlagen, PDF samenvoegen met bladwijzers |
### Standaard omgevingsvariabelen
Deze staan in `.env.production` en worden in de build ingebakken:
```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-URL's overschrijven
Je kunt de standaardwaarden tijdens het bouwen overschrijven voor aangepaste implementaties:
```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
```
Om een module helemaal uit te schakelen (handmatige configuratie door de gebruiker via Geavanceerde instellingen is dan vereist), stel je de variabele in op een lege tekenreeks.
Laat voor OCR alle `VITE_TESSERACT_*`-variabelen leeg en gebruik de standaard online bronnen, of stel de worker-, core- en lang-URL's samen in voor zelf gehoste/offline OCR. Bundel je alleen specifieke OCR-talen, stel dan ook `VITE_TESSERACT_AVAILABLE_LANGUAGES` in op dezelfde kommagescheiden codes, zodat de interface alleen geïnstalleerde talen aanbiedt en niet-ondersteunde selecties mislukken met een duidelijke foutmelding. Stel voor volledig offline doorzoekbare PDF-uitvoer ook `VITE_OCR_FONT_BASE_URL` in op de interne map waarin de meegeleverde OCR-lettertypen worden aangeboden.
Gebruikers kunnen deze standaardwaarden ook op elk moment overschrijven via **Geavanceerde instellingen** in de interface. Overschrijvingen van gebruikers die in de browser zijn opgeslagen, hebben voorrang op de standaardwaarden uit de omgeving.
### Air-gapped / offline implementatie
Voor netwerken zonder internettoegang (overheid, zorg, financiële sector, enz.). De WASM-URL's worden tijdens het **bouwen** in de JavaScript ingebakken: de eigenlijke WASM-bestanden worden tijdens runtime door de **browser van de gebruiker** gedownload. Je moet dus alles voorbereiden op een machine met internet en het daarna overbrengen naar het geïsoleerde netwerk.
#### Geautomatiseerd script (aanbevolen)
Het meegeleverde script `prepare-airgap.sh` automatiseert het hele proces: het downloadt WASM-pakketten, bouwt de Docker-image en maakt een zelfstandige bundel met een installatiescript.
```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
```
Dit levert een bundelmap op:
```
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
```
Breng de bundel via USB, een interne artefactrepository of een goedgekeurde methode over naar het air-gapped netwerk. Voer daarna het meegeleverde installatiescript uit:
```bash
cd bentopdf-airgap-bundle
bash setup.sh
```
Het installatiescript laadt de Docker-image, pakt de WASM-bestanden uit en start optioneel de container.
**Scriptopties:**
| Vlag | Beschrijving | Standaard |
| ------------------------------ | ----------------------------------------------------------------- | ------------------------------------------------- |
| `--wasm-base-url <url>` | Waar de WASM's intern worden gehost | _(vereist, wordt gevraagd indien niet opgegeven)_ |
| `--image-name <name>` | Docker-imagetag | `bentopdf` |
| `--output-dir <path>` | Uitvoermap voor de bundel | `./bentopdf-airgap-bundle` |
| `--simple-mode` | Simple Mode inschakelen | uit |
| `--base-url <path>` | Basis-URL van submap (bijv. `/pdf/`) | `/` |
| `--language <code>` | Standaardtaal van de interface (bijv. `fr`, `de`) | _(geen)_ |
| `--brand-name <name>` | Eigen merknaam | _(geen)_ |
| `--brand-logo <path>` | Logopad relatief ten opzichte van `public/` | _(geen)_ |
| `--footer-text <text>` | Eigen voettekst | _(geen)_ |
| `--ocr-languages <list>` | Kommagescheiden OCR-talen om te bundelen | `eng` |
| `--list-ocr-languages` | Toon ondersteunde OCR-codes en namen en stop | uit |
| `--search-ocr-language <term>` | Zoek OCR-codes op naam of afkorting | uit |
| `--dockerfile <path>` | Te gebruiken Dockerfile | `Dockerfile` |
| `--skip-docker` | Docker-build en export overslaan | uit |
| `--skip-wasm` | WASM-download overslaan (bestaande `.tgz`-bestanden hergebruiken) | uit |
De interactieve prompt accepteert ook `list` om de volledige lijst met ondersteunde Tesseract-codes te tonen en `search <term>` om resultaten te vinden, zoals `search german` of `search chi`.
::: warning Vereiste van dezelfde origin
WASM-bestanden moeten worden aangeboden vanaf dezelfde **origin** als de BentoPDF-app. Web Workers gebruiken `importScripts()`, dat geen scripts cross-origin kan laden. Draait BentoPDF bijvoorbeeld op `https://internal.example.com`, dan moet de WASM-basis-URL ook `https://internal.example.com/wasm` zijn.
:::
#### Handmatige stappen
<details>
<summary>Als je het liever handmatig doet, zonder het script</summary>
**Stap 1: Download de WASM- en OCR-pakketten** (op een machine met internet)
```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
```
**Stap 2: Bouw de Docker-image met interne URL's**
```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 .
```
**Stap 3: Exporteer de Docker-image**
```bash
docker save bentopdf -o bentopdf.tar
```
**Stap 4: Breng alles over naar het air-gapped netwerk**
Kopieer via USB, een interne artefactrepository of een goedgekeurde overdrachtsmethode:
- `bentopdf.tar`: de Docker-image
- De vijf `.tgz` WASM-/OCR-pakketten uit stap 1
- De map `tesseract-langdata/` uit stap 1
- De map `ocr-fonts/` uit stap 1
**Stap 5: Installeer binnen het air-gapped netwerk**
```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
```
Zorg dat de bestanden bereikbaar zijn op de URL's die je in stap 2 hebt ingesteld, waaronder `.../ocr/worker.min.js`, `.../ocr/core`, `.../ocr/lang-data` en `.../ocr/fonts`.
</details>
::: info Bouwen vanuit de broncode in plaats van Docker?
Stel de variabelen in `.env.production` in voordat je `npm run build` uitvoert:
```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
```
:::
### Je eigen WASM-proxy hosten
Als je AGPL-WASM-bestanden met de juiste CORS-headers wilt aanbieden, kun je een eenvoudige proxy implementeren. Zie de [Cloudflare WASM Proxy-gids](https://github.com/alam00000/bentopdf/blob/main/cloudflare/WASM-PROXY.md) voor een voorbeeldimplementatie.
::: tip Waarom gescheiden?
Deze scheiding zorgt voor:
- Duidelijke juridische naleving voor commerciële gebruikers
- De kern van BentoPDF blijft onder de dubbele licentie (AGPL-3.0 / Commercieel)
- WASM-bestanden worden tijdens runtime geladen en niet in de broncode gebundeld
:::
## Systeemvereisten
| Vereiste | Minimum |
| -------- | ---------------------------------- |
| Opslag | ~100 MB (kern zonder AGPL-modules) |
| RAM | 512 MB |
| CPU | Elke moderne processor |
::: tip
BentoPDF is een statische site: er is geen database of backendserver nodig!
:::
+211
View File
@@ -0,0 +1,211 @@
# Implementeren met Kubernetes
Kubernetes is misschien overdreven voor een statische site, maar het kan een goede keuze zijn als je al standaardiseert op Helm + GitOps.
> [!IMPORTANT]
> **Vereiste headers voor Office-bestandsconversie**
>
> Tools op basis van LibreOffice (conversie van Word, Excel, PowerPoint) vereisen deze HTTP-headers voor `SharedArrayBuffer`-ondersteuning:
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> De officiële BentoPDF nginx-images bevatten deze headers. In Kubernetes zijn **Ingress-/Gateway-controllers ook reverse proxies**, dus zorg dat deze headers behouden blijven (of voeg ze toe aan de edge).
## Vereisten
- Kubernetes-cluster
- Helm v3
- Een BentoPDF nginx-image (bijv. `ghcr.io/alam00000/bentopdf:<tag>`) die op **poort 8080** draait
## Implementeren met Helm
### Installeren vanuit deze repo (lokale 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
```
### Installeren vanuit GHCR (OCI-chart)
Als de chart als OCI-artefact op GHCR is gepubliceerd:
```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
```
## Beschikbaar maken
### Port-forward (snelle test)
```bash
kubectl -n bentopdf port-forward deploy/bentopdf 8080:8080
```
### Ingress (optioneel)
Schakel Ingress in (voorbeeld voor nginx-ingress):
```yaml
ingress:
enabled: true
className: nginx
hosts:
- host: pdf.example.com
paths:
- path: /
pathType: Prefix
```
### Gateway API (optioneel)
Deze chart ondersteunt Gateway API `Gateway` + `HTTPRoute`.
Voorbeeld (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
```
## Zorgen dat de SharedArrayBuffer-headers blijven werken (Ingress/Gateway)
### Wat er hoort te gebeuren
De nginx-configuratie van BentoPDF stelt de vereiste responsheaders in. De meeste Ingress-/Gateway-controllers **laten responsheaders van de upstream ongewijzigd door**.
### Wat het kan breken
- Een controller- of edge-beleid dat responsheaders **overschrijft** of **verwijdert**
- Een "security headers"-middleware die andere COOP/COEP-waarden instelt
### Hoe te controleren
Voer dit uit tegen je openbare endpoint:
```bash
curl -I https://pdf.example.com/ | egrep -i 'cross-origin-opener-policy|cross-origin-embedder-policy'
```
Je zou moeten zien:
- `Cross-Origin-Opener-Policy: same-origin`
- `Cross-Origin-Embedder-Policy: require-corp`
### Als je Ingress-controller ze niet behoudt
Voeg de headers toe aan de edge (afhankelijk van de controller). Voorbeeld voor **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-typefouten (iframe van PDF ondertekenen / PDF-formulier invullen leeg)
Als de browserconsole `Failed to load module script: ... non-JavaScript MIME type "application/octet-stream"` toont bij een `.mjs`-verzoek, vervangt een Ingress-/Gateway-controller vóór de BentoPDF nginx de upstream `Content-Type`-header door `application/octet-stream`.
De nginx van de BentoPDF-image levert `.mjs` al als `application/javascript`. De oplossing is te voorkomen dat de controller de header verwijdert of vervangt. Voor nginx-ingress:
```yaml
ingress:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
location ~* \.mjs$ {
types {} default_type application/javascript;
}
```
Of gebruik een `ResponseHeaderModifier`-filter in Gateway API om `Content-Type: application/javascript` in te stellen voor `.mjs`-paden. Controleer met DevTools → tabblad Netwerk: het mislukte `.mjs`-verzoek zou nu `content-type: application/javascript` moeten tonen.
### Gebruik je Gateway API en wil je headers afdwingen
Gateway API ondersteunt een `ResponseHeaderModifier`-filter. Je kunt dit koppelen in `httpRoute.rules[*].filters`:
```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
```
Ondersteuning voor specifieke filters hangt af van je Gateway-controller. Wordt een filter genegeerd, voeg de headers dan toe aan de edge of controllerlaag.
## Specifieke tools uitschakelen
Gebruik een ConfigMap om tools tijdens runtime uit te schakelen zonder de image opnieuw te bouwen:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: bentopdf-config
namespace: bentopdf
data:
config.json: |
{
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
}
```
Koppel het in de aangeboden map:
```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
```
Tool-ID's zijn de pagina-URL zonder `.html`: open een tool en kijk naar de URL (bijv. `edit-pdf`, `merge-pdf`, `compress-pdf`). Uitgeschakelde tools worden verborgen op de startpagina, in zoeken, sneltoetsen, de workflowbouwer en bij directe URL-toegang. Zie de [Docker-gids](/nl/self-hosting/docker#specifieke-tools-uitschakelen) voor de volledige lijst met opties.
+139
View File
@@ -0,0 +1,139 @@
# Implementeren op Netlify
[Netlify](https://netlify.com) biedt uitstekende hosting voor statische sites met een royale gratis laag.
## Implementeren met één klik
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/alam00000/bentopdf)
## Handmatig implementeren
### Stap 1: Repository koppelen
1. Log in bij [Netlify](https://app.netlify.com)
2. Klik op "Add new site" → "Import an existing project"
3. Koppel je GitHub-account
4. Selecteer je BentoPDF-fork
### Stap 2: Buildinstellingen configureren
| Instelling | Waarde |
| ----------------- | --------------- |
| Build command | `npm run build` |
| Publish directory | `dist` |
| Node version | 18+ |
### Stap 3: Implementeren
Klik op "Deploy site" en wacht tot de build klaar is.
## Configuratiebestand
Maak `netlify.toml` aan in de hoofdmap van je project:
```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 Belangrijk
De headers `Cross-Origin-Embedder-Policy` en `Cross-Origin-Opener-Policy` zijn vereist voor conversies van Word/ODT/Excel/PowerPoint naar PDF. Zonder deze headers is `SharedArrayBuffer` niet beschikbaar en kan de LibreOffice WASM-engine niet worden geïnitialiseerd.
De `Content-Encoding: gzip`-headers op de bestanden `.wasm.gz` en `.data.gz` zorgen ervoor dat de browser ze automatisch decomprimeert. Zonder deze headers ontvangt de browser ruwe gzip-bytes en mislukt de WASM-compilatie.
:::
## Omgevingsvariabelen
Stel deze in via Site settings → Environment variables:
| Variabele | Beschrijving |
| ----------------------- | ------------------------------------------------------------------------ |
| `SIMPLE_MODE` | Stel in op `true` voor een minimale build |
| `VITE_BRAND_NAME` | Eigen merknaam (vervangt "BentoPDF") |
| `VITE_BRAND_LOGO` | Logopad relatief ten opzichte van `public/` (bijv. `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | Eigen footer-/copyrighttekst |
| `VITE_DEFAULT_LANGUAGE` | Standaardtaal van de interface (bijv. `fr`, `de`, `es`) |
## Eigen domein
1. Ga naar Site settings → Domain management
2. Klik op "Add custom domain"
3. Volg de DNS-configuratie-instructies
## Grote media
Overweeg voor grote WASM-bestanden [Netlify Large Media](https://docs.netlify.com/large-media/overview/) in te schakelen:
```bash
netlify lm:setup
git lfs track "*.wasm"
```
## Probleemoplossing
### Word/ODT/Excel naar PDF blijft hangen op 55%
Als documentconversies blijven hangen op 55%, open dan de DevTools-console en controleer:
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
Als `crossOriginIsolated` `false` is, worden de COEP/COOP-headers uit je `netlify.toml` niet toegepast. Zorg dat het bestand in de hoofdmap van je project staat en implementeer opnieuw.
Als je `expected magic word 00 61 73 6d, found 1f 8b 08 08` in de console ziet, ontbreken bij de `.wasm.gz`-bestanden de `Content-Encoding: gzip`-headers. Zorg dat de `[[headers]]`-blokken voor `soffice.wasm.gz` en `soffice.data.gz` in je `netlify.toml` staan.
### Build mislukt
Controleer de compatibiliteit van de Node-versie:
```toml
[build.environment]
NODE_VERSION = "20"
```
### Trage eerste lading
Schakel assetoptimalisatie in via Site settings → Build & deploy → Asset optimization.
+232
View File
@@ -0,0 +1,232 @@
# Implementeren met Nginx
Host BentoPDF op je eigen server met Nginx.
## Vereisten
- Ubuntu/Debian-server
- Nginx geïnstalleerd
- SSL-certificaat (aanbevolen: Let's Encrypt)
## Stap 1: Bouw het project
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
Om de huisstijl aan te passen, stel je omgevingsvariabelen in vóór het bouwen:
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## Stap 2: Bestanden kopiëren
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## Stap 3: Nginx-configuratie
Maak `/etc/nginx/sites-available/bentopdf` aan:
```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;
}
}
```
## Stap 4: De site inschakelen
```bash
sudo ln -s /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
## Stap 5: SSL met Let's Encrypt
```bash
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
```
## Implementatie in een submap
Om te hosten op `/pdf/`:
```nginx
location /pdf/ {
alias /var/www/bentopdf/;
try_files $uri $uri/ /pdf/index.html;
}
```
Bouw met:
```bash
BASE_URL=/pdf/ npm run build
```
## Prestaties afstellen
Voeg toe aan `nginx.conf`:
```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;
}
```
## Probleemoplossing
### WASM wordt niet geladen
Zorg dat het MIME-type is ingesteld:
```nginx
types {
application/wasm wasm;
}
```
### PDF ondertekenen of formulierinvuller toont een lege viewer (`.mjs` MIME-fout)
Als de browserconsole het volgende toont:
```
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.
```
...en het mislukte verzoek eindigt op `.mjs`, dan mist in je nginx de koppeling `.mjs → application/javascript`. De meegeleverde PDF-viewer (gebruikt door PDF ondertekenen, PDF-formulier invullen en een paar andere tools) levert ES-modulebestanden met de extensie `.mjs`, en de standaard `mime.types` van nginx bevat die niet.
Los dit op door `mjs` toe te voegen aan de bestaande regel `application/javascript` in `/etc/nginx/mime.types`:
```nginx
application/javascript js mjs;
```
> Voeg GEEN _tweede_ regel `application/javascript` toe: nginx leest `mime.types` regel voor regel en slechts één koppeling van type naar extensie wint, dus een tweede regel wordt op sommige versies stilzwijgend genegeerd. Pas de bestaande regel aan door `mjs` naast `js` toe te voegen.
Of overschrijf het lokaal in je serverblok (geen globale bestandswijziging):
```nginx
types {
application/javascript js mjs;
application/wasm wasm;
}
```
Laad nginx opnieuw na de wijziging:
```bash
sudo nginx -t && sudo systemctl reload nginx
```
Om te bevestigen: zoek in browser DevTools → tabblad Netwerk het mislukte `.mjs`-verzoek en controleer de responsheader `Content-Type`. Die moet nu `application/javascript` zijn, niet `application/octet-stream`.
### Word/ODT/Excel naar PDF werkt niet
LibreOffice WASM vereist `SharedArrayBuffer`, waarvoor de headers `Cross-Origin-Embedder-Policy` en `Cross-Origin-Opener-Policy` nodig zijn. Ook is een veilige context nodig, dus `http://localhost` werkt voor lokaal testen, maar `http://192.168.x.x` of andere LAN-IP's vereisen meestal HTTPS. Let op: nginx `add_header`-directives in een `location`-blok **overschrijven** `add_header`-directives op serverniveau, ze worden niet samengevoegd. Elk `location`-blok met eigen `add_header` moet de COEP/COOP-headers bevatten.
Controleren met:
```bash
curl -I https://your-domain.com/ | grep -i cross-origin
```
Gebruik je een reverse proxy vóór nginx, zorg dan dat die deze headers behoudt.
### 502 Bad Gateway
Controleer de Nginx-foutlogboeken:
```bash
sudo tail -f /var/log/nginx/error.log
```
+122
View File
@@ -0,0 +1,122 @@
# Implementeren op Vercel
[Vercel](https://vercel.com) biedt de eenvoudigste implementatie-ervaring voor statische sites.
## Implementeren met één klik
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/alam00000/bentopdf)
## Handmatig implementeren
### Stap 1: Fork de repository
Fork [bentopdf/bentopdf](https://github.com/alam00000/bentopdf) naar je GitHub-account.
### Stap 2: Importeren in Vercel
1. Ga naar [vercel.com/new](https://vercel.com/new)
2. Selecteer je geforkte repository
3. Configureer het project:
| Instelling | Waarde |
| ---------------- | --------------- |
| Framework Preset | Vite |
| Build Command | `npm run build` |
| Output Directory | `dist` |
| Install Command | `npm install` |
### Stap 3: Omgevingsvariabelen (optioneel)
Voeg deze toe indien nodig:
| Variabele | Beschrijving |
| ----------------------- | ------------------------------------------------------------------------ |
| `SIMPLE_MODE` | Stel in op `true` voor een minimale interface |
| `VITE_BRAND_NAME` | Eigen merknaam (vervangt "BentoPDF") |
| `VITE_BRAND_LOGO` | Logopad relatief ten opzichte van `public/` (bijv. `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | Eigen footer-/copyrighttekst |
| `VITE_DEFAULT_LANGUAGE` | Standaardtaal van de interface (bijv. `fr`, `de`, `es`) |
### Stap 4: Implementeren
Klik op "Deploy" en wacht tot de build klaar is.
## Eigen domein
1. Ga naar de projectinstellingen
2. Ga naar "Domains"
3. Voeg je eigen domein toe
4. Configureer DNS zoals aangegeven
## Beperkingen
::: warning Grote bestanden
De serverless functions van Vercel hebben een limiet van 50 MB. Omdat BentoPDF een statische site is, zou dit je niet moeten raken, maar WASM-modules zijn groot (~100 MB in totaal). Zorg dat ze worden aangeboden vanuit de map `/public`.
:::
## Probleemoplossing
### Build-timeout
Als builds een timeout geven, probeer dan:
```json
// vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": "dist"
}
```
### 404 bij vernieuwen
Voeg een `vercel.json` toe voor SPA-routing:
```json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
```
### Word/ODT/Excel naar PDF werkt niet
LibreOffice WASM-conversies vereisen `SharedArrayBuffer`, waarvoor deze responsheaders op alle pagina's nodig zijn:
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
De vooraf gecomprimeerde bestanden `.wasm.gz` en `.data.gz` hebben ook `Content-Encoding: gzip` nodig, zodat de browser ze decomprimeert.
Voeg deze toe aan je `vercel.json`:
```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" }
]
}
]
}
```
Controleer dit door de DevTools-console te openen en `console.log(window.crossOriginIsolated)` uit te voeren: het moet `true` teruggeven.