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 @@
# Installer med Apache
Host BentoPDF med Apache HTTP Server.
## Forudsætninger
- Apache 2.4+
- mod_rewrite aktiveret
- SSL-certifikat (anbefales)
## Trin 1: Byg projektet
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
Hvis du vil tilpasse branding, skal du sætte miljøvariabler før build:
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## Trin 2: Kopiér filer
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## Trin 3: Apache-konfiguration
Opret `/etc/apache2/sites-available/bentopdf.conf`:
```apache
<VirtualHost *:80>
ServerName your-domain.com
Redirect permanent / https://your-domain.com/
</VirtualHost>
<VirtualHost *:443>
ServerName your-domain.com
DocumentRoot /var/www/bentopdf
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/your-domain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/your-domain.com/privkey.pem
<Directory /var/www/bentopdf>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
# WASM MIME type
AddType application/wasm .wasm
# Prevent double-compression of pre-compressed files
SetEnvIfNoCase Request_URI "\.gz$" no-gzip
# Compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript application/json application/wasm
</IfModule>
# Cache headers
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/wasm "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
</IfModule>
# Required headers for SharedArrayBuffer (LibreOffice WASM)
Header always set Cross-Origin-Embedder-Policy "require-corp"
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Resource-Policy "cross-origin"
# Security headers
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
# Pre-compressed LibreOffice WASM files
<FilesMatch "soffice\.wasm\.gz$">
ForceType application/wasm
Header set Content-Encoding "gzip"
</FilesMatch>
<FilesMatch "soffice\.data\.gz$">
ForceType application/octet-stream
Header set Content-Encoding "gzip"
</FilesMatch>
</VirtualHost>
```
## Trin 4: .htaccess til routing
Opret `/var/www/bentopdf/.htaccess`:
```apache
RewriteEngine On
RewriteBase /
# Existing files/dirs - serve directly
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# ============================================
# LANGUAGE ROUTES
# ============================================
# Supported languages: de, es, zh, zh-TW, vi, it, id, tr, fr, pt
# English has no prefix - served from root
# English prefix redirects to root
RewriteRule ^en/?$ / [R=301,L]
RewriteRule ^en/(.+)$ /$1 [R=301,L]
# Language prefix root (e.g., /de/ -> /de/index.html)
RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /$1/index.html [L]
# Language prefix with path (e.g., /de/merge-pdf -> /de/merge-pdf.html)
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# ============================================
# ADD .HTML EXTENSION (ROOT LEVEL)
# ============================================
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
ErrorDocument 404 /404.html
```
## Trin 5: Aktivér de nødvendige moduler
```bash
sudo a2enmod rewrite
sudo a2enmod ssl
sudo a2enmod headers
sudo a2enmod expires
sudo a2enmod deflate
```
## Trin 6: Aktivér sitet
```bash
sudo a2ensite bentopdf.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
```
## Installation i en undermappe
For at hoste på `/pdf/`:
1. Byg med base-URL:
```bash
BASE_URL=/pdf/ npm run build
```
2. Opdater `.htaccess`:
```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
```
## Fejlfinding
### WASM 404-fejl
Sørg for, at MIME-typen er konfigureret:
```apache
AddType application/wasm .wasm
```
### Underskriv PDF eller Formularudfylder viser en tom viewer (`.mjs` MIME-fejl)
Hvis browserkonsollen viser:
```
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.
```
…og den mislykkede forespørgsel ender på `.mjs`, mangler din Apache-konfiguration tilknytningen `.mjs → application/javascript`. Den medfølgende PDF-viewer leveres med ES-modulfiler med filtypen `.mjs`, og Apaches standard-`mime.types` indeholder dem ikke som standard.
Tilføj til din VirtualHost eller `.htaccess`:
```apache
AddType application/javascript .mjs
```
Genindlæs derefter Apache:
```bash
sudo apache2ctl configtest && sudo systemctl reload apache2
```
Sådan bekræfter du: find den mislykkede `.mjs`-forespørgsel under fanen Network i browserens DevTools, og tjek svarheaderen `Content-Type`. Den skal nu være `application/javascript`, ikke `application/octet-stream`.
### Rewrite virker ikke
Tjek, at mod_rewrite er aktiveret:
```bash
sudo a2enmod rewrite
```
### Word/ODT/Excel til PDF virker ikke
LibreOffice WASM kræver `SharedArrayBuffer`, som kræver disse headers:
```apache
Header always set Cross-Origin-Embedder-Policy "require-corp"
Header always set Cross-Origin-Opener-Policy "same-origin"
```
Det kræver også en sikker kontekst. `http://localhost` virker til lokal test, men `http://192.168.x.x` eller andre LAN-IP'er kræver normalt HTTPS. Hvis headerne er til stede, men `window.crossOriginIsolated` stadig er `false`, så tjek, om siden åbnes over almindelig HTTP på en origin, der ikke er loopback.
De forkomprimerede `.wasm.gz`- og `.data.gz`-filer kræver også korrekt `Content-Encoding`:
```apache
<FilesMatch "soffice\.wasm\.gz$">
ForceType application/wasm
Header set Content-Encoding "gzip"
</FilesMatch>
<FilesMatch "soffice\.data\.gz$">
ForceType application/octet-stream
Header set Content-Encoding "gzip"
</FilesMatch>
```
Sørg for, at `mod_headers` er aktiveret: `sudo a2enmod headers`
### Tilladelse nægtet
```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 @@
# Installer på AWS S3 + CloudFront
Host BentoPDF på AWS for maksimal kontrol og skalerbarhed.
## Arkitektur
```
User → CloudFront (CDN) → S3 (Static Files)
```
## Trin 1: Opret 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
```
## Trin 2: Byg og upload
```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
```
## Trin 3: Opret CloudFront-distribution
```bash
aws cloudfront create-distribution \
--origin-domain-name your-bentopdf-bucket.s3.amazonaws.com \
--default-root-object index.html
```
Eller brug AWS Console:
1. Gå til CloudFront → Create distribution
2. Origin domain: Vælg din S3-bucket
3. Aktivér "Origin Access Control"
4. Default root object: `index.html`
5. Opret distributionen
## Trin 3b: Response Headers Policy (påkrævet til LibreOffice WASM)
LibreOffice-baserede konverteringer (Word, Excel, PowerPoint til PDF) kræver `SharedArrayBuffer`, som kræver bestemte svarheadere. Opret en CloudFront Response Headers Policy:
1. Gå til CloudFront → Policies → Response headers
2. Opret en brugerdefineret policy med disse headere:
| Header | Værdi |
| ------------------------------ | -------------- |
| `Cross-Origin-Embedder-Policy` | `require-corp` |
| `Cross-Origin-Opener-Policy` | `same-origin` |
| `Cross-Origin-Resource-Policy` | `cross-origin` |
3. Tilknyt policyen til din distributions standard-cache behavior
Eller via 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}
]
}
}'
```
## Trin 3c: S3-metadata til forkomprimerede WASM-filer
LibreOffice WASM-filerne er forkomprimerede (`.wasm.gz`, `.data.gz`). Sæt den korrekte Content-Type og Content-Encoding, så browsere dekomprimerer dem:
```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 Vigtigt
Uden response headers policy er `SharedArrayBuffer` ikke tilgængelig, og LibreOffice WASM-konverteringer hænger ved ca. 55 %. Uden den korrekte Content-Encoding på `.gz`-filerne modtager browseren rå gzip-bytes, og WASM-kompilering mislykkes.
:::
## Trin 4: S3 bucket policy
Giv CloudFront adgang til bucketten:
```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"
}
}
}
]
}
```
## Trin 5: Brugerdefinerede fejlsider
Konfigurer 404 til at returnere `index.html` til SPA-routing:
1. CloudFront → Error pages
2. Opret et brugerdefineret fejlsvar:
- HTTP error code: 404
- Response page path: `/index.html`
- HTTP response code: 200
## Omkostningsestimat
| Ressource | Estimeret pris |
| -------------------------- | -------------- |
| S3 Storage (~500MB) | ~$0.01/måned |
| CloudFront (1TB transfer) | ~$85/måned |
| CloudFront (10GB transfer) | ~$0.85/måned |
::: tip
Brug S3 Intelligent Tiering til at spare penge på filer, der sjældent tilgås.
:::
## Automatisering med 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 @@
# Installer på Cloudflare Pages
[Cloudflare Pages](https://pages.cloudflare.com) tilbyder hurtig, global hosting af statiske sites med ubegrænset båndbredde.
## Hurtig installation
1. Gå til [Cloudflare Pages](https://dash.cloudflare.com/?to=/:account/pages)
2. Klik på "Create a project"
3. Forbind dit GitHub-repository
## Build-konfiguration
| Indstilling | Værdi |
| ---------------------- | --------------- |
| Framework preset | None |
| Build command | `npm run build` |
| Build output directory | `dist` |
| Root directory | `/` |
## Miljøvariabler
Tilføj disse under Settings → Environment variables:
| Variabel | Værdi |
| ----------------------- | ---------------------------------------------------- |
| `NODE_VERSION` | `18` |
| `SIMPLE_MODE` | `false` (valgfrit) |
| `VITE_BRAND_NAME` | Brugerdefineret brandnavn (valgfrit) |
| `VITE_BRAND_LOGO` | Logosti relativt til `public/` (valgfrit) |
| `VITE_FOOTER_TEXT` | Brugerdefineret footer-/copyrighttekst (valgfrit) |
| `VITE_DEFAULT_LANGUAGE` | Standardsprog i brugerfladen, f.eks. `fr` (valgfrit) |
## Konfigurationsfil
Opret `_headers` i din `public`-mappe:
```
# 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 Vigtigt
Headerne `Cross-Origin-Embedder-Policy` og `Cross-Origin-Opener-Policy` er påkrævet til konvertering af Word/ODT/Excel/PowerPoint til PDF. Uden dem er `SharedArrayBuffer` ikke tilgængelig, og LibreOffice WASM-motoren kan ikke starte.
:::
Opret `_redirects` til SPA-routing:
```
/* /index.html 200
```
## Eget domæne
1. Gå til dit Pages-projekt
2. Klik på "Custom domains"
3. Tilføj dit domæne
4. Cloudflare konfigurerer automatisk DNS, hvis domænet ligger hos Cloudflare
## Fordele
- **Gratis, ubegrænset båndbredde**
- **Globalt CDN** med 300+ edge-lokationer
- **Automatisk HTTPS**
- **Preview-installationer** til pull requests
- **Hurtige builds**
## Fejlfinding
### Upload af store filer
Cloudflare Pages understøtter filer op til 25 MB. WASM-moduler bør være fine, men hvis du rammer grænser, kan du overveje:
```bash
# Split large files during build
npm run build
```
### Størrelsesgrænser for Workers
Hvis du bruger Cloudflare Workers til avanceret routing, skal du være opmærksom på grænsen på 1 MB for gratis planer.
## CORS-proxy-worker (til digitale signaturer)
Værktøjet til digital signatur kræver en CORS-proxy for at hente certifikatkæder. Installer den medfølgende worker:
```bash
cd cloudflare
npx wrangler login
npx wrangler deploy
```
### Sikkerhedsfunktioner
| Funktion | Beskrivelse |
| ------------------------------ | ----------------------------------- |
| **URL-begrænsninger** | Kun certifikat-URL'er tilladt |
| **Filstørrelsesgrænse** | Maks. 10MB pr. forespørgsel |
| **Rate limiting** | 60 forespørgsler/IP/min (kræver KV) |
| **Blokering af private IP'er** | Blokerer localhost og interne IP'er |
### Aktivér rate limiting
```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
```
### Byg med proxy-URL
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
Eller med 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 .
```
> **Bemærk:** Se [README](https://github.com/alam00000/bentopdf#digital-signature-cors-proxy-required) for opsætning af HMAC-signatur.
+180
View File
@@ -0,0 +1,180 @@
# CORS-proxy til hentning af certifikater
Værktøjet til digital signatur bruger en CORS-proxy til at hente udstedercertifikater fra eksterne certifikatudstedere (CA'er). Det er nødvendigt, fordi mange CA-servere ikke inkluderer CORS-headere i deres svar, hvilket forhindrer direkte hentning fra browseren.
Derudover udleverer mange CA-servere certifikater over almindelig HTTP. Når din BentoPDF-instans hostes over HTTPS, blokerer browsere disse HTTP-forespørgsler (mixed content-politik). CORS-proxyen løser begge problemer ved at route forespørgsler gennem et HTTPS-endpoint med de rette headere.
## Sådan fungerer det
Når du signerer en PDF med et certifikat:
1. Biblioteket `zgapdfsigner` forsøger at bygge en komplet certifikatkæde
2. Det henter udstedercertifikater fra URL'er indlejret i dit certifikats AIA-udvidelse (Authority Information Access)
3. Disse forespørgsler routes gennem en CORS-proxy, der tilføjer de nødvendige `Access-Control-Allow-Origin`-headere
4. Proxyen returnerer certifikatdata til browseren
## Selvhosting af CORS-proxyen
Hvis du selvhoster BentoPDF, skal du installere din egen CORS-proxy, for at digitale signaturer virker med certifikater, der kræver hentning af kæden.
### Mulighed 1: Cloudflare Workers (anbefales)
1. **Installer Wrangler CLI**:
```bash
npm install -g wrangler
```
2. **Log ind på Cloudflare**:
```bash
wrangler login
```
3. **Klon BentoPDF og opdater tilladte origins**:
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf/cloudflare
```
Åbn `cors-proxy-worker.js` og ret arrayet `ALLOWED_ORIGINS` til dit domæne:
```js
const ALLOWED_ORIGINS = [
'https://your-domain.com',
'https://www.your-domain.com',
];
```
::: warning Vigtigt
Uden denne ændring afviser proxyen alle forespørgsler fra dit site med fejlen **403 Forbidden**. Standarden tillader kun forespørgsler fra `bentopdf.com`.
:::
4. **Installer proxyen**:
```bash
wrangler deploy
```
Notér din worker-URL (f.eks. `https://bentopdf-cors-proxy.your-subdomain.workers.dev`).
5. **Genbyg BentoPDF med proxy-URL'en**:
Hvis du bruger 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 .
```
Hvis du bygger fra kildekode:
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
### Mulighed 2: Egen backend-proxy
Du kan også oprette dit eget proxy-endpoint. Kravene er:
1. Acceptér GET-forespørgsler med en `url`-forespørgselsparameter
2. Hent URL'en fra din server (ingen CORS-begrænsninger på serversiden)
3. Returnér svaret med disse headere:
- `Access-Control-Allow-Origin: https://your-domain.com`
- `Access-Control-Allow-Methods: GET, OPTIONS`
- `X-Content-Type-Options: nosniff`
Eksempel på en implementering i 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' });
}
});
```
## Sikkerhedsovervejelser
Den medfølgende Cloudflare Worker har flere sikkerhedsforanstaltninger:
| Funktion | Beskrivelse |
| ------------------------------ | ------------------------------------------------------------------------------------------------ |
| **Origin-validering** | Tillader kun forespørgsler fra domæner på listen `ALLOWED_ORIGINS` |
| **URL-begrænsninger** | Tillader kun certifikat-URL'er (`.crt`, `.cer`, `.pem`, `/certs/`, `/ocsp`, `/crl`) |
| **Blokering af private IP'er** | Blokerer private IPv4/IPv6-områder, link-local, loopback, decimale IP'er og cloud-metadata |
| **Content-Type-sikkerhed** | Returnerer kun sikre MIME-typer for certifikater og blokerer content-type-injektion fra upstream |
| **Filstørrelsesgrænse** | Streamer svaret med en grænse på 10MB og afbryder midt i overførslen, hvis den overskrides |
| **Rate limiting** | 60 forespørgsler pr. minut pr. IP eller IPv6 /64 (kræver KV, fejler åbent ved KV-fejl) |
| **HMAC-signaturer** | Valgfri signering på klientsiden (afskrækker tilfældigt misbrug) |
### DNS rebinding (selvhosting uden for Cloudflare)
Workeren afviser enhver URL, der resolver til en privat eller intern IP. Den kan ikke bruge en fast tilladelsesliste over værter, fordi URL'er i certifikatkæder peger på den CA, der har udstedt certifikatet (FNMT, virksomheders CA'er og så videre), så destinationerne kendes ikke på forhånd. Det efterlader et lille rebinding-vindue: en person, der driver DNS for sit eget domæne, kan give sikkerhedstjekket en offentlig IP og den egentlige forespørgsel en privat lidt senere.
På Cloudflare kan det ikke udnyttes: Workers kan ikke nå private IP'er eller metadata-endpointet `169.254.169.254`, så en rebind rammer ingenting. Giv bare ikke workeren en vej ind: tilknyt ikke en Tunnel, Service eller binding til et privat netværk (den behøver kun `RATE_LIMIT_KV`).
Hvis du kører den uden for Cloudflare (workerd, Node og lignende) i en VPC, _kan_ runtime'en nå interne IP'er. Sæt en egress-firewall foran, og bloker de private områder: `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` og deres IPv6-ækvivalenter. Det kan applikationskode ikke håndhæve, når du er uden for Cloudflare.
## Deaktivering af proxyen
Hvis du ikke vil bruge en CORS-proxy, så sæt miljøvariablen til en tom streng:
```
VITE_CORS_PROXY_URL=
```
**Bemærk**: Uden proxyen mislykkes signering med certifikater, der kræver ekstern hentning af kæden (som FNMT eller nogle virksomheders CA'er), med fejlen "Failed to fetch".
## Fejlfinding
### "Signing error: TypeError: Failed to fetch"
Det betyder normalt enten:
1. **Ingen CORS-proxy konfigureret**: Sæt `VITE_CORS_PROXY_URL` og genbyg
2. **Mixed content blokeret**: Dit site bruger HTTPS, men certifikatets udsteder-URL er HTTP. CORS-proxyen løser dette.
3. **CORS-proxyen afviser din origin**: Tjek, at dit domæne står i arrayet `ALLOWED_ORIGINS` i `cors-proxy-worker.js`
### "403 Forbidden" fra proxyen
Dit domæne står ikke på listen `ALLOWED_ORIGINS`. Rediger `cors-proxy-worker.js`:
```js
const ALLOWED_ORIGINS = ['https://your-domain.com'];
```
Installer derefter igen: `npx wrangler deploy`
### Test af proxyen
```bash
curl -H "Origin: https://your-domain.com" \
"https://your-proxy.workers.dev?url=http://www.cert.fnmt.es/certs/ACUSU.crt"
```
### Certifikater, der virker uden proxy
Nogle certifikater indeholder hele kæden i P12/PFX-filen og kræver ikke ekstern hentning:
- Selvsignerede certifikater
- Nogle kommercielle CA'er, der medsender mellemliggende certifikater
- Certifikater, du selv har samlet med hele kæden
+622
View File
@@ -0,0 +1,622 @@
# Installer med Docker / Podman
Den nemmeste måde at selvhoste BentoPDF i et produktionsmiljø.
> [!IMPORTANT]
> **Påkrævede headere til konvertering af Office-filer**
>
> LibreOffice-baserede værktøjer (konvertering af Word, Excel og PowerPoint) kræver disse HTTP-headere for at understøtte `SharedArrayBuffer`:
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> Siden skal også serveres fra en sikker kontekst. `http://localhost` virker til lokal test, men `http://192.168.x.x` eller andre LAN-IP'er opfylder normalt ikke kravet, så Office-konvertering over almindelig HTTP mislykkes, selv om headerne er til stede.
>
> De officielle containerbilleder indeholder disse headere. Hvis du bruger en reverse proxy (Traefik, Caddy osv.), så sørg for, at headerne bevares eller tilføjes, og brug HTTPS ved adgang uden for loopback.
> [!TIP]
> **Podman-brugere:** Alle `docker`-kommandoer virker med Podman, hvis du erstatter `docker` med `podman` og `docker-compose` med `podman-compose`.
## Hurtig start
> [!TIP]
> BentoPDF leveres i to builds:
>
> - **Self-Hosted-build**: `ghcr.io/alam00000/bentopdf-simple:latest`. Alle PDF-værktøjer, **uden** markedsføringsindhold (ingen hero, FAQ, udtalelser, footer). Brug den til interne installationer i teams og organisationer. Det er **ikke** en "lite"-version med færre funktioner.
> - **Commercial-build**: `ghcr.io/alam00000/bentopdf:latest`. Det fulde markedsføringssite, som bruges af bentopdf.com selv og af indehavere af kommercielle licenser med offentligt tilgængelige installationer.
>
> Er du i tvivl: hent Self-Hosted-builden.
```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
Opret `docker-compose.yml`:
```yaml
services:
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
container_name: bentopdf
ports:
- '3000:8080'
restart: unless-stopped
healthcheck:
test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:8080']
interval: 30s
timeout: 10s
retries: 3
```
Kør:
```bash
# Docker Compose
docker compose up -d
# Podman Compose
podman-compose up -d
```
## Byg dit eget 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;"]
```
Byg og kør:
```bash
docker build -t bentopdf:custom .
docker run -d -p 3000:8080 bentopdf:custom
```
## Miljøvariabler
| Variabel | Beskrivelse | Standard |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- |
| `SIMPLE_MODE` | Byg Self-Hosted-varianten (skjuler bentopdf.com's markedsføringsindhold: hero, FAQ, udtalelser, footer). Alle PDF-værktøjer bevares. | `false` |
| `BASE_URL` | Installer i en undermappe | `/` |
| `VITE_WASM_PYMUPDF_URL` | URL til PyMuPDF WASM-modul | `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/` |
| `VITE_WASM_GS_URL` | URL til Ghostscript WASM-modul | `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm@0.1.1/assets/` |
| `VITE_WASM_CPDF_URL` | URL til CoherentPDF WASM-modul | `https://cdn.jsdelivr.net/npm/coherentpdf@2.5.5/dist/` |
| `VITE_TESSERACT_WORKER_URL` | URL til OCR-workerscript | _(tom; brug Tesseract.js' standard-CDN)_ |
| `VITE_TESSERACT_CORE_URL` | Mappe til OCR-kerne-runtime | _(tom; brug Tesseract.js' standard-CDN)_ |
| `VITE_TESSERACT_LANG_URL` | Mappe til OCR-traineddata | _(tom; brug Tesseract.js' standard-CDN)_ |
| `VITE_TESSERACT_AVAILABLE_LANGUAGES` | Kommaseparerede OCR-sprog, der vises i brugerfladen | _(tom; vis hele kataloget)_ |
| `VITE_OCR_FONT_BASE_URL` | Mappe til OCR-tekstlagets skrifttyper | _(tom; brug eksterne Noto-skrift-URL'er)_ |
| `VITE_DEFAULT_LANGUAGE` | Standard UI language | `en` |
| `VITE_BRAND_NAME` | Brugerdefineret brandnavn | `BentoPDF` |
| `VITE_BRAND_LOGO` | Logosti relativt til `public/` | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | Brugerdefineret footer-/copyrighttekst | `© 2026 BentoPDF. All rights reserved.` |
| `DISABLE_TOOLS` | Kommaseparerede værktøjs-ID'er, der skal skjules | _(tom; alle værktøjer aktiveret)_ |
| `DISABLE_GITHUB_STARS` | Spring hentningen af GitHub-stjerneantal over ved sideindlæsning, og fjern `api.github.com` fra CSP. Simple Mode-builds springer den altid over. | `false` |
| `SITE_URL` | Offentlig URL, der skrives i canonical-, hreflang- og sitemap-links. Lad den stå på standardværdien, medmindre du vil have søgemaskiner til at behandle din installation som et eget site. | `https://www.bentopdf.com` |
| `ALLOW_INDEXING` | Med en brugerdefineret `SITE_URL` markeres hver side `noindex`, så din kopi ikke konkurrerer med bentopdf.com i søgeresultaterne. Sæt til `true` for at lade søgemaskiner indeksere den. | `false` |
WASM-modul-URL'erne er forudkonfigureret med CDN-standarder, så alle avancerede funktioner virker fra start. Overskriv dem til air-gapped eller selvhostede installationer.
### Søgemaskiner
Som standard peger hver side søgemaskiner hen på bentopdf.com som originalen, så din installation ikke vises som dublet. Du behøver ikke ændre noget for en privat eller intern instans.
- **Hold din instans helt ude af søgeresultaterne:** kør containeren med `-e ROBOTS_NOINDEX=true`. Det kræver kun en genstart, ikke en ny build.
- **Gør din instans til sit eget site i søgeresultaterne:** byg med `--build-arg SITE_URL=https://your-domain.example --build-arg ALLOW_INDEXING=true`. Uden `ALLOW_INDEXING=true` markerer en brugerdefineret `SITE_URL` hver side `noindex`.
### Content-Security-Policy
Nginx-imaget leveres med en håndhævende `Content-Security-Policy`-header. CSP'ens direktiver `script-src`, `connect-src` og `font-src` **genereres ved build** ud fra `VITE_*`-URL-variablerne ovenfor, og alle værter, du angiver via `--build-arg`, føjes automatisk til politikken.
Det betyder:
- Hvis du overskriver `VITE_CORS_PROXY_URL` eller `VITE_WASM_*_URL` ved build, tillader CSP automatisk disse origins, så der kræves ingen ekstra konfiguration.
- For URL'er på `cdn.jsdelivr.net` tillader `script-src` kun den præcise versionerede pakkemappe, for eksempel `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/`, ikke hele CDN'et. jsDelivr leverer enhver offentlig npm-pakke eller GitHub-repository, så hvis hele værten blev tilladt, kunne hvilken som helst af dem køre på din instans. URL'er på alle andre værter tillades pr. origin.
- Hvis du konfigurerer brugerdefinerede WASM-URL'er _ved kørsel_ via siden Avancerede indstillinger i appen, står disse origins **ikke** i CSP, og browseren blokerer hentning fra dem. Det gælder også en anden version af samme pakke på `cdn.jsdelivr.net`, fordi kun den version, der blev sat ved build, er tilladt. Konfiguration ved kørsel er beregnet til eksperimenter. Til permanente brugerdefinerede URL'er skal du sætte det tilsvarende `VITE_*`-build-arg.
- Air-gapped installationer, der overskriver alle tre `VITE_WASM_*_URL`-værdier, får også disse `cdn.jsdelivr.net`-pakkemapper fjernet fra CSP (hver standardværdi erstattes, ikke tilføjes). Tesseract OCR-standarderne på `cdn.jsdelivr.net` forbliver i politikken, indtil du også sætter de tre `VITE_TESSERACT_*`-værdier. Tilsvarende erstatter `VITE_CORS_PROXY_URL` den offentlige standard `bentopdf-cors-proxy.bentopdf.workers.dev`.
CSP indeholder `'unsafe-eval'` i `script-src`, fordi LibreOffice WASM-runtime (brugt af værktøjerne til konvertering af Word/Excel/PowerPoint) og billedbiblioteket wasm-vips (brugt til PDF til TIFF) kompilerer intern dispatch-kode via `new Function()`. Hvis den fjernes, holder de værktøjer op med at virke. Hvis du bygger i `SIMPLE_MODE` (uden LibreOffice) og ikke har brug for PDF til TIFF, kan du manuelt redigere den genererede `security-headers.conf` og fjerne `'unsafe-eval'` for en strengere politik.
For OCR skal du lade `VITE_TESSERACT_*`-variablerne stå tomme for at bruge standardressourcerne online, eller sætte alle tre sammen til selvhostet/offline OCR. Delvise OCR-overskrivninger afvises, fordi worker, kerne-runtime og traineddata-mappe skal passe sammen. For fuldt offline søgbar PDF-output skal du også sætte `VITE_OCR_FONT_BASE_URL`, så OCR-tekstlagets skrifttyper indlæses fra din interne server i stedet for de offentlige Noto-skrift-URL'er.
`VITE_DEFAULT_LANGUAGE` angiver brugerfladens sprog for førstegangsbesøgende. Understøttede værdier: `en`, `ar`, `be`, `fr`, `de`, `es`, `zh`, `zh-TW`, `vi`, `tr`, `id`, `it`, `pt`, `nl`, `da`. Brugere kan stadig skifte sprog, det ændrer kun standarden.
Eksempel:
```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
```
### Brugerdefineret branding
Erstat BentoPDF's standardlogo, navn og footertekst med dine egne. Læg din logofil i mappen `public/` (eller brug et eksisterende billede), og angiv derefter branding-variablerne ved build:
```bash
docker build \
--build-arg VITE_BRAND_NAME="AcmePDF" \
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
-t acmepdf .
```
Branding virker i både fuld tilstand og Simple Mode og kan kombineres med alle andre build-indstillinger.
### Deaktivering af bestemte værktøjer
Skjul værktøjer i brugerfladen af hensyn til compliance eller sikkerhed. Deaktiverede værktøjer fjernes fra forsiden, søgeresultater, tastaturgenveje og workflow-builderen. Direkte adgang via URL viser en side om, at værktøjet ikke er tilgængeligt.
Værktøjs-ID'er er sidens URL uden `.html`. Hvis værktøjet for eksempel ligger på `bentopdf.com/edit-pdf.html`, er ID'et `edit-pdf`.
#### Find værktøjs-ID'er
Den nemmeste måde: åbn et værktøj i BentoPDF og se på URL'en. Den sidste del af stien (uden `.html`) er værktøjs-ID'et.
<details>
<summary>Komplet liste over værktøjs-ID'er</summary>
**Rediger og annotér:** `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`
**Konverter til PDF:** `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`
**Konverter fra PDF:** `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`
**Organiser og administrer:** `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`
**Optimer og reparer:** `compress-pdf`, `pdf-to-pdfa`, `fix-page-size`, `linearize-pdf`, `remove-restrictions`, `repair-pdf`, `rasterize-pdf`, `deskew-pdf`, `font-to-outline`
**Sikkerhed:** `encrypt-pdf`, `sanitize-pdf`, `decrypt-pdf`, `flatten-pdf`, `remove-metadata`, `change-permissions`, `digital-sign-pdf`, `validate-signature-pdf`, `timestamp-pdf`
</details>
#### Mulighed 1: Ved build (Docker build-arg)
```bash
docker build \
--build-arg DISABLE_TOOLS="edit-pdf,sign-pdf,encrypt-pdf" \
-t bentopdf .
```
Det bager listen over deaktiverede værktøjer ind i JavaScript-bundlen. Kræver en ny build at ændre.
#### Mulighed 2: Ved kørsel (config.json)
Mount en `config.json`-fil i den serverede mappe, uden behov for ny 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
```
Eller med 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
```
Begge metoder kan kombineres, og listerne flettes. Hvis et værktøj står i en af dem, er det deaktiveret.
#### Deaktivering af editorfunktioner
Du kan også deaktivere bestemte funktioner i PDF-editoren (f.eks. sortlægning, annotationer, formularer) uden at deaktivere hele editorværktøjet. Tilføj `editorDisabledCategories` til din `config.json`:
```json
{
"editorDisabledCategories": ["redaction", "annotation-stamp"]
}
```
<details>
<summary>Komplet liste over editorkategorier</summary>
**Zoom:** `zoom`, `zoom-in`, `zoom-out`, `zoom-fit-page`, `zoom-fit-width`, `zoom-marquee`, `zoom-level`
**Annotation:** `annotation`, `annotation-markup`, `annotation-highlight`, `annotation-underline`, `annotation-strikeout`, `annotation-squiggly`, `annotation-ink`, `annotation-text`, `annotation-stamp`
**Former:** `annotation-shape`, `annotation-rectangle`, `annotation-circle`, `annotation-line`, `annotation-arrow`, `annotation-polygon`, `annotation-polyline`
**Formular:** `form`, `form-textfield`, `form-checkbox`, `form-radio`, `form-select`, `form-listbox`, `form-fill-mode`
**Sortlægning:** `redaction`, `redaction-area`, `redaction-text`, `redaction-apply`, `redaction-clear`
**Dokument:** `document`, `document-open`, `document-close`, `document-print`, `document-capture`, `document-export`, `document-fullscreen`, `document-protect`
**Side:** `page`, `spread`, `rotate`, `scroll`, `navigation`
**Panel:** `panel`, `panel-sidebar`, `panel-search`, `panel-comment`
**Værktøjer:** `tools`, `pan`, `pointer`, `capture`
**Markering:** `selection`, `selection-copy`
**Historik:** `history`, `history-undo`, `history-redo`
</details>
Kategorierne er hierarkiske: Deaktiverer du en overordnet kategori (f.eks. `annotation`), deaktiveres alle dens underkategorier.
### Deaktivering af GitHub-stjernetælleren
Navigationslinjen viser et live antal GitHub-stjerner, som hentes fra `api.github.com` ved sideindlæsning. Det er den eneste eksterne forespørgsel, der ikke tjener en PDF-funktion. Deaktivér den ved build for at fjerne hentekoden fra bundlen og fjerne `api.github.com` fra Content-Security-Policy:
```bash
docker build --build-arg DISABLE_GITHUB_STARS=true -t bentopdf .
```
Simple Mode-builds (`bentopdf-simple`) springer altid stjernetælleren over. Flaget er kun nødvendigt på Commercial-builden eller egne fulde builds.
### Brugerdefinerede WASM-URL'er (air-gapped / selvhostet)
> [!IMPORTANT]
> WASM-URL'er bages ind i JavaScript **ved build**. WASM-filerne downloades af **brugerens browser** ved kørsel, og Docker downloader dem ikke under build. I air-gapped netværk skal du hoste WASM-filerne på en intern server, som browsere kan nå.
**Fuld air-gapped arbejdsgang:**
```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
```
Brug koderne udskrevet af `bash scripts/prepare-airgap.sh --list-ocr-languages`, eller søg på navn med `bash scripts/prepare-airgap.sh --search-ocr-language <term>`, til `--ocr-languages`. Når du bygger med en begrænset OCR-delmængde, skal du give de samme koder til `VITE_TESSERACT_AVAILABLE_LANGUAGES`, så appen kun viser medfølgende sprog. For fuld offline OCR-output skal du også hoste den medfølgende mappe `ocr-fonts/` og pege `VITE_OCR_FONT_BASE_URL` på den.
Sæt en variabel til en tom streng for at deaktivere det modul (brugere skal så konfigurere manuelt via Avancerede indstillinger).
## Brugerdefineret port
Som standard lytter BentoPDF på port `8080` inde i containeren. For at ændre det skal du sætte miljøvariablen `PORT` ved kørsel:
```bash
docker run -p 3000:9090 -e PORT=9090 ghcr.io/alam00000/bentopdf-simple:latest
```
| Variable | Beskrivelse | Standard |
| -------- | ------------------------------ | -------- |
| `PORT` | Nginx' lytteport i containeren | `8080` |
Det virker med både standard- og nonroot-Dockerfilerne.
## Brugerdefineret bruger-ID (PUID/PGID)
Til miljøer, der kræver kørsel som en bestemt bruger uden root (NAS-enheder, Kubernetes med sikkerhedskontekster, organisationens politikker), tilbyder BentoPDF en separat Dockerfile med PUID/PGID-understøttelse i LSIO-stil.
### Byg og kør
```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
```
### Miljøvariabler
| Variabel | Beskrivelse | Standard |
| -------------- | --------------------- | -------- |
| `PUID` | Bruger-ID at køre som | `1000` |
| `PGID` | Gruppe-ID at køre som | `1000` |
| `PORT` | Nginx' lytteport | `8080` |
| `DISABLE_IPV6` | Deaktivér IPv6-lytter | `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
```
### Sådan fungerer det
Containeren starter som root, opretter en bruger med den angivne PUID/PGID, justerer ejerskab på alle skrivbare mapper og dropper derefter rettigheder med `su-exec`. Nginx-processen kører udelukkende som den bruger, du har angivet.
> [!NOTE]
> Standard-`Dockerfile` bruger `nginx-unprivileged` (UID 101) og anbefales til de fleste installationer. Brug kun `Dockerfile.nonroot`, når du har brug for et bestemt UID/GID.
> [!WARNING]
> PUID/PGID kan ikke være `0` (root). Entrypointet validerer input og afslutter med en fejl ved ugyldige værdier.
## Med 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
```
## Med 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"
}
```
## Ressourcegrænser
```yaml
services:
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
```
## Podman Quadlet (systemd-integration)
[Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) giver dig mulighed for at køre Podman-containere som systemd-services. Det er ideelt til produktionsinstallationer på Linux-systemer.
### Grundlæggende Quadlet-opsætning
Opret en container-unitfil på `~/.config/containers/systemd/bentopdf.container` (bruger) eller `/etc/containers/systemd/bentopdf.container` (system):
```ini
[Unit]
Description=BentoPDF - Privacy-first PDF toolkit
After=network-online.target
Wants=network-online.target
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
AutoUpdate=registry
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=default.target
```
### Aktivér og start
```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]
> Til installation for hele systemet skal du bruge `systemctl` uden flaget `--user` og placere filen i `/etc/containers/systemd/`.
### Quadlet med 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
```
### Automatisk opdatering med Quadlet
Podman kan automatisk opdatere containere, når nye images er tilgængelige:
```bash
# Enable auto-update timer
systemctl --user enable --now podman-auto-update.timer
# Check for updates manually
podman auto-update
# Dry run (check without updating)
podman auto-update --dry-run
```
### Quadlet-netværkskonfiguration
Til brugerdefineret netværkskonfiguration skal du oprette en netværksfil `bentopdf.network`:
```ini
[Network]
Subnet=10.89.0.0/24
Gateway=10.89.0.1
```
Henvis derefter til den i din containerfil:
```ini
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
Network=bentopdf.network
```
## Opdatering
```bash
# Pull latest image
docker compose pull
# Recreate container
docker compose up -d
```
+266
View File
@@ -0,0 +1,266 @@
# Installer på Hostinger
[Hostinger](https://hostinger.com) er en populær udbyder af shared hosting. Denne vejledning beskriver, hvordan du installerer BentoPDF på Hostingers shared hosting.
## Forudsætninger
- Hostinger-hostingplan med adgang til filhåndtering
- Node.js installeret lokalt til build
## Trin 1: Byg projektet
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
De byggede filer ligger i mappen `dist`.
## Trin 2: Upload til Hostinger
### Installation på rodomænet
1. Log ind på din Hostinger-konto
2. Gå til **File Manager** → **public_html**
3. **Slet** eventuelle eksisterende filer (tag backup, hvis nødvendigt)
4. **Upload** hele indholdet af din lokale `dist`-mappe til `public_html`
### Installation i en undermappe
Hvis du installerer i en undermappe (f.eks. `yourdomain.com/pdf-tools/`):
1. Byg med den korrekte base-URL:
```bash
BASE_URL=/pdf-tools/ npm run build
```
2. Opret mappen i Hostinger:
- Gå til **File Manager** → **public_html**
- Opret en ny mappe: `pdf-tools`
3. Upload hele indholdet af `dist` til `public_html/pdf-tools/`
## Trin 3: Opret .htaccess-filen
Opret en `.htaccess`-fil i roden af din installationsmappe (`public_html` eller `public_html/pdf-tools/`):
::: warning Vigtigt
Ved installation i en undermappe skal du ændre `RewriteBase /` til `RewriteBase /pdf-tools/` (eller navnet på din mappe).
:::
```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
```
## Eksempel på .htaccess i en undermappe
For `yourdomain.com/pdf-tools/` skal du opdatere disse linjer:
```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
```
## Fejlfinding
### WASM-filer indlæses ikke
Sørg for, at MIME-typerne er sat korrekt i `.htaccess`:
```apache
AddType application/wasm .wasm
```
### LibreOffice-værktøjer virker ikke
Sikkerhedsheaderne er afgørende for SharedArrayBuffer:
```apache
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
```
Hvis headerne ikke bliver anvendt, så kontakt Hostingers support for at aktivere `mod_headers`.
### 404-fejl ved genindlæsning af siden
Sørg for, at reglen for filtypen `.html` og sprogrutene er konfigureret korrekt. BentoPDF bruger statiske HTML-filer, ikke 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]
```
### Grænser for filupload
Hostinger kan have grænser for filstørrelse. Upload WASM-filer separat, hvis masseupload mislykkes.
+538
View File
@@ -0,0 +1,538 @@
# Selvhostingsguide
BentoPDF kan selvhostes på din egen infrastruktur. Denne guide gennemgår forskellige deployment-muligheder.
## Hurtig start med Docker / Podman
Den hurtigste måde at selvhoste BentoPDF på:
> [!TIP]
> BentoPDF leveres i to builds:
>
> - **Selvhostet build**: `ghcr.io/alam00000/bentopdf-simple:latest`. Alle PDF-værktøjer, **uden** marketingelementerne (ingen hero, FAQ, udtalelser eller sidefod). Brug denne til interne deployments i teams og organisationer. Den er **ikke** en "lite"-version med færre funktioner.
> - **Kommerciel build**: `ghcr.io/alam00000/bentopdf:latest`. Det fulde marketingsite, brugt af bentopdf.com selv og af kommercielle licensindehavere med offentligt tilgængelige deployments.
>
> Er du i tvivl: Hent det selvhostede build.
> [!IMPORTANT]
> Konvertering af Office-filer kræver `SharedArrayBuffer`, hvilket betyder, at appen både skal være cross-origin-isoleret og serveres fra en sikker kontekst. Det officielle image sender allerede de nødvendige COOP/COEP-headere, men browsere deaktiverer stadig `SharedArrayBuffer` på almindelige HTTP-origins i lokale netværk som `http://192.168.x.x`.
>
> Brug kun `http://localhost` til test på samme enhed. Hvis brugerne tilgår BentoPDF via en LAN-IP eller et værtsnavn, skal du afslutte forbindelsen med 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
```
Eller med 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)
Kør BentoPDF som en systemd-tjeneste. Opret `~/.config/containers/systemd/bentopdf.container`:
```ini
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
AutoUpdate=registry
[Service]
Restart=always
[Install]
WantedBy=default.target
```
```bash
systemctl --user daemon-reload
systemctl --user enable --now bentopdf
```
Se [Docker-deploymentguiden](/da/self-hosting/docker) for den fulde Quadlet-dokumentation.
## Bygning fra kildekode
```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
```
## Konfigurationsindstillinger
### Selvhostet build (Simple Mode)
Det selvhostede build (imaget `bentopdf-simple`, også kaldet Simple Mode) er **funktionelt identisk** med det kommercielle build. Alle PDF-værktøjer er til stede og opfører sig på samme måde. Det skjuler blot de marketingelementer, der kun giver mening på bentopdf.com selv eller i en kommerciel, offentligt tilgængelig deployment. **Det er ikke en version med færre funktioner eller en "lite"-version.**
**Hvad det selvhostede build skjuler** (kun kosmetisk, ingen PDF-funktioner fjernes):
- Navigationslinje, hero-sektion, funktionssektion, FAQ, udtalelser, sidefod
- Opdaterer sidens titel til "PDF-værktøjer"
**Hvad det selvhostede build beholder** (alt det, der faktisk arbejder med PDF):
- Alle PDF-værktøjer (flet, opdel, rediger, underskriv, OCR, Office-konvertering osv.)
- Understøttelse af eget brand, al konfiguration ved build og under kørsel
Det kommercielle build (`ghcr.io/alam00000/bentopdf:latest`) driver bentopdf.com selv og bruges af kommercielle licensindehavere med offentligt tilgængelige deployments. Det tilføjer hero, FAQ, udtalelser og sidefod, som ikke ville give mening i et internt værktøj.
Hvis du selvhoster BentoPDF til dit team, din organisation eller som internt værktøj, skal du hente det selvhostede build:
```bash
# Use the pre-built image (recommended)
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
# Or build it yourself
SIMPLE_MODE=true npm run build
```
Se [SIMPLE_MODE.md](https://github.com/alam00000/bentopdf/blob/main/SIMPLE_MODE.md) for alle detaljer.
### Kommerciel build (offentligt tilgængelige deployments med dit eget brand)
Det kommercielle build (imaget `bentopdf`, uden suffikset `-simple`) driver bentopdf.com selv. Det indeholder det fulde marketingsite (hero, funktioner, FAQ, udtalelser, sidefod) oven på alle PDF-værktøjer. Brug dette build, når du vil **deploye BentoPDF som en offentligt tilgængelig PDF-tjeneste under dit eget brand**, for eksempel:
- Du kører BentoPDF som hostet SaaS for slutbrugere på dit eget domæne
- Du vil have landingsside-oplevelsen (marketingsektioner + værktøjer), ikke kun selve værktøjsfladen
- Du er kommerciel licensindehaver og indlejrer BentoPDF i et kommercielt produkt
**Kør det som det er** (BentoPDF-branding, nyttigt til at få vist buildet):
```bash
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
```
**Byg med dit eget brand** (den typiske kommercielle arbejdsgang):
```bash
docker build \
--build-arg VITE_BRAND_NAME="AcmePDF" \
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. All rights reserved." \
-t acmepdf .
docker run -p 3000:8080 acmepdf
```
Alle andre build-indstillinger (`BASE_URL`, `VITE_DEFAULT_LANGUAGE`, `DISABLE_TOOLS`, WASM-URL-overstyringer osv.) virker på samme måde som i det selvhostede build.
::: warning Licensering
Det er tilladt at køre det kommercielle build under begge af BentoPDF's to licensmuligheder:
- **AGPL-3.0** (gratis): tilladt, hvis din deployment udgiver hele sin kildekode under AGPL, inklusive eventuelle branding-ændringer og omkringliggende forretningslogik.
- **Kommerciel licens** ($79 livstid): påkrævet til lukkede/proprietære deployments, hvor du ikke gør din branding-fork eller forretningskode open source.
Se [licenssiden](https://www.bentopdf.com/licensing) for den fulde sammenligning. AGPL-licenserede WASM-moduler (PyMuPDF, Ghostscript, CoherentPDF) indlæses fra et CDN under kørsel, så de indgår ikke i dit image og ændrer ikke din licensmæssige stilling.
:::
### Base-URL
Deploy til en undermappe:
```bash
BASE_URL=/pdf-tools/ npm run build
```
### Eget brand
Erstat BentoPDF's standardlogo, -navn og sidefodstekst med dine egne ved build:
| Variabel | Beskrivelse | Standard |
| ------------------ | ----------------------------------------- | --------------------------------------- |
| `VITE_BRAND_NAME` | Brandnavn vist i sidehoved og sidefod | `BentoPDF` |
| `VITE_BRAND_LOGO` | Logosti relativt til `public/` | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | Brugerdefineret sidefods-/ophavsretstekst | `© 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
```
Eller 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 .
```
Branding virker i både fuld tilstand og Simple Mode og kan kombineres med alle andre build-indstillinger (`BASE_URL`, `SIMPLE_MODE`, `VITE_DEFAULT_LANGUAGE`).
### Deaktivering af bestemte værktøjer
Skjul enkelte værktøjer af hensyn til compliance eller sikkerhed. Deaktiverede værktøjer fjernes fra startsiden, søgning, genveje, workflow-builderen og direkte URL-adgang.
Værktøjs-id'er er sidens URL uden `.html`. Åbn et værktøj, og se på URL'en (f.eks. `edit-pdf`, `sign-pdf`, `encrypt-pdf`).
**Ved build** (indbygget i bundlen):
```bash
DISABLE_TOOLS="edit-pdf,sign-pdf" npm run build
```
**Under kørsel** (ingen genopbygning nødvendig, montér en `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
```
Begge metoder kan kombineres, og listerne bliver slået sammen.
Du kan også deaktivere bestemte funktioner i PDF-editoren (f.eks. redigering af indhold) uden at deaktivere hele værktøjet ved at tilføje `editorDisabledCategories` til `config.json`. Se [Docker-guiden](/da/self-hosting/docker#deaktivering-af-editorfunktioner) for den fulde liste over kategorier.
## Deployment-guider
Vælg din platform:
- [Vercel](/da/self-hosting/vercel)
- [Netlify](/da/self-hosting/netlify)
- [Cloudflare Pages](/da/self-hosting/cloudflare)
- [AWS S3 + CloudFront](/da/self-hosting/aws)
- [Hostinger](/da/self-hosting/hostinger)
- [Nginx](/da/self-hosting/nginx)
- [Apache](/da/self-hosting/apache)
- [Docker](/da/self-hosting/docker)
- [Kubernetes](/da/self-hosting/kubernetes)
- [CORS-proxy](/da/self-hosting/cors-proxy) - Påkrævet til digitale signaturer
## Almindelige problemer
### Word / ODT / Excel / PowerPoint til PDF hænger (SharedArrayBuffer utilgængelig)
**Symptom**: LibreOffice-baserede dokumentkonverteringer (Word, ODT, Excel, PowerPoint til PDF) hænger ved ca. 55 % eller starter ikke. Browserens konsol kan vise:
> `ReferenceError: SharedArrayBuffer is not defined`
…eller `window.crossOriginIsolated` rapporterer `false`, eller WASM-kompileringen rapporterer `expected magic word 00 61 73 6d, found 1f 8b 08 08`.
**Årsag**: LibreOffice WASM kræver `SharedArrayBuffer`, som browseren kun aktiverer, når siden er **cross-origin-isoleret** OG serveres fra en **sikker kontekst**. Det betyder, at to ting skal være opfyldt:
1. Hvert svar indeholder begge headere:
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
2. Siden serveres fra `https://...` eller `http://localhost`. Almindelig HTTP på en LAN-IP (f.eks. `http://192.168.x.x`) tæller IKKE som sikker, og browsere deaktiverer `SharedArrayBuffer` dér.
Uoverensstemmelsen `00 61 73 6d / 1f 8b 08 08` er et separat undersymptom: De forkomprimerede `.wasm.gz`- / `.data.gz`-filer mangler svarheaderen `Content-Encoding: gzip`, så browseren modtager rå gzip-bytes i stedet for dekomprimeret WASM.
**Løsning**: Se din platformspecifikke deployment-guide for den præcise konfiguration:
- [Nginx →](/da/self-hosting/nginx#underskriv-pdf-eller-formularudfylder-viser-en-tom-læser-mjs-mime-fejl)
- [Apache →](/da/self-hosting/apache#underskriv-pdf-eller-formularudfylder-viser-en-tom-viewer-mjs-mime-fejl)
- [AWS S3 + CloudFront →](/da/self-hosting/aws#trin-3b-response-headers-policy-pakrævet-til-libreoffice-wasm)
- [Cloudflare Pages →](/da/self-hosting/cloudflare#konfigurationsfil) (`_headers`-fil)
- [Netlify →](/da/self-hosting/netlify#word-odt-excel-til-pdf-hænger-ved-55) (`netlify.toml`)
- [Vercel →](/da/self-hosting/vercel#word-odt-excel-til-pdf-virker-ikke) (`vercel.json`)
- [Hostinger →](/da/self-hosting/hostinger#libreoffice-værktøjer-virker-ikke) (`.htaccess`)
- [Kubernetes →](/da/self-hosting/kubernetes#mjs-mime-typefejl-underskriv-pdf-formularudfylder-tom-iframe)
- **Docker**: håndteres automatisk af den medfølgende nginx-konfiguration, ingen handling nødvendig.
**Kontrollér**: Åbn DevTools Console på en vilkårlig BentoPDF-side, og kør:
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
Hvis siden er HTTPS eller `http://localhost` OG begge COEP/COOP-headere findes på hvert svar, består begge kontroller. Hvis du er på `http://192.168.x.x` eller en anden HTTP-origin, der ikke er loopback, skal du afslutte den med HTTPS. Der findes ingen løsning, der kun bruger headere.
### `.mjs`-filer serveres som `application/octet-stream`
**Symptom**: Underskriv PDF / Formularudfylder / visse andre værktøjer viser en tom læser eller kan ikke indlæses. Browserens konsol rapporterer:
> `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.`
**Årsag**: Din webserver eller reverse proxy har ingen MIME-type-tilknytning til `.mjs`-filer (den indbyggede PDF-læser leverer ES-moduler med den filendelse). Mange standardkonfigurationer bruger som standard `application/octet-stream` til ukendte filendelser, og browsere nægter at køre dem som ES-moduler.
**Løsning**: Se din platformspecifikke deployment-guide for det præcise udsnit:
- [Nginx →](/da/self-hosting/nginx#underskriv-pdf-eller-formularudfylder-viser-en-tom-læser-mjs-mime-fejl)
- [Apache →](/da/self-hosting/apache#underskriv-pdf-eller-formularudfylder-viser-en-tom-viewer-mjs-mime-fejl)
- [AWS S3 + CloudFront →](/da/self-hosting/aws#trin-2-byg-og-upload) (se `aws s3 cp ... --include "*.mjs"`)
- [Kubernetes →](/da/self-hosting/kubernetes#mjs-mime-typefejl-underskriv-pdf-formularudfylder-tom-iframe)
- **Docker, Vercel, Netlify, Cloudflare Pages, Hostinger**: håndteres automatisk, ingen handling nødvendig.
**Kontrollér**: Åbn DevTools → fanen Network, find den mislykkede `.mjs`-anmodning, og kontrollér svarheaderen `Content-Type`. Den skal være `application/javascript`. Hvis den stadig er `application/octet-stream`, er det muligt, at en ydre reverse proxy eller et CDN gætter typen igen. Tjek hvert led i din serveringskæde.
## WASM-konfiguration (AGPL-komponenter)
BentoPDF **bundler ikke** AGPL-licenserede behandlingsbiblioteker i sin kildekode, men **forhåndskonfigurerer CDN-URL'er**, så alle funktioner virker ud af boksen, uden manuel opsætning.
::: tip Nul konfiguration som standard
Fra og med v2.0.0 er WASM-moduler forhåndskonfigureret til at blive indlæst fra jsDelivr CDN via miljøvariabler. Alle avancerede funktioner virker med det samme uden brugerkonfiguration.
:::
| Komponent | Licens | Funktioner |
| --------------- | -------- | ------------------------------------------------------------------------------ |
| **PyMuPDF** | AGPL-3.0 | EPUB/MOBI/FB2/XPS-konvertering, udtrækning af billeder, udtrækning af tabeller |
| **Ghostscript** | AGPL-3.0 | PDF/A-konvertering, komprimering, udretning af skæve sider, rasterisering |
| **CoherentPDF** | AGPL-3.0 | Indholdsfortegnelse, vedhæftninger, PDF-sammenfletning med bogmærker |
### Standardmiljøvariabler
Disse er sat i `.env.production` og indbygget i buildet:
```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=
```
### Overstyring af WASM-URL'er
Du kan overstyre standardværdierne ved build til brugerdefinerede deployments:
```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
```
For at deaktivere et modul helt (kræver manuel brugerkonfiguration via Avancerede indstillinger) skal du sætte dets variabel til en tom streng.
Til OCR skal du enten lade alle `VITE_TESSERACT_*`-variabler være tomme og beholde de online standardressourcer, eller angive worker-, core- og lang-URL'erne sammen til selvhostet/offline OCR. Hvis du kun bundler bestemte OCR-sprog, skal du også sætte `VITE_TESSERACT_AVAILABLE_LANGUAGES` til de samme kommaseparerede koder, så brugerfladen kun tilbyder installerede sprog, og ikke-understøttede valg fejler med en beskrivende fejl. Til fuldt offline PDF-output med søgbar tekst skal du desuden sætte `VITE_OCR_FONT_BASE_URL` til den interne mappe, der serverer de bundlede OCR-skrifttyper.
Brugere kan også til enhver tid overstyre disse standardværdier via **Avancerede indstillinger** i brugerfladen. Brugeroverstyringer gemt i browseren har forrang over miljøstandarderne.
### Air-gapped / offline deployment
Til netværk uden internetadgang (offentlig sektor, sundhed, finans osv.). WASM-URL'erne er indbygget i JavaScript ved **build**. De egentlige WASM-filer downloades af **brugerens browser** under kørsel. Du skal derfor forberede alt på en maskine med internet og derefter overføre det til det isolerede netværk.
#### Automatiseret script (anbefalet)
Det medfølgende script `prepare-airgap.sh` automatiserer hele processen: det downloader WASM-pakker, bygger Docker-imaget og producerer en selvstændig bundle med et opsætningsscript.
```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
```
Dette producerer en bundle-mappe:
```
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
```
Overfør bundlen til det air-gappede netværk via USB, internt artefaktlager eller en godkendt metode. Kør derefter det medfølgende opsætningsscript:
```bash
cd bentopdf-airgap-bundle
bash setup.sh
```
Opsætningsscriptet indlæser Docker-imaget, udpakker WASM-filer og starter eventuelt containeren.
**Script-indstillinger:**
| Flag | Beskrivelse | Standard |
| ------------------------------ | ------------------------------------------------------------- | -------------------------------------- |
| `--wasm-base-url <url>` | Hvor WASM-filerne hostes internt | _(påkrævet, spørges om, hvis mangler)_ |
| `--image-name <name>` | Docker image-tag | `bentopdf` |
| `--output-dir <path>` | Output-mappe til bundlen | `./bentopdf-airgap-bundle` |
| `--simple-mode` | Aktivér Simple Mode | fra |
| `--base-url <path>` | Base-URL til undermappe (f.eks. `/pdf/`) | `/` |
| `--language <code>` | Standardsprog i brugerfladen (f.eks. `fr`, `de`) | _(ingen)_ |
| `--brand-name <name>` | Brugerdefineret brandnavn | _(ingen)_ |
| `--brand-logo <path>` | Logosti relativt til `public/` | _(ingen)_ |
| `--footer-text <text>` | Brugerdefineret sidefodstekst | _(ingen)_ |
| `--ocr-languages <list>` | Kommasepareret liste over OCR-sprog, der skal bundles | `eng` |
| `--list-ocr-languages` | Udskriv understøttede OCR-koder og navne, og afslut | fra |
| `--search-ocr-language <term>` | Søg efter OCR-koder via navn eller forkortelse | fra |
| `--dockerfile <path>` | Dockerfile, der skal bruges | `Dockerfile` |
| `--skip-docker` | Spring Docker-build og eksport over | fra |
| `--skip-wasm` | Spring WASM-download over (genbrug eksisterende `.tgz`-filer) | fra |
Den interaktive prompt accepterer også `list` for at udskrive den fulde liste over understøttede Tesseract-koder og `search <term>` for at finde match som `search german` eller `search chi`.
::: warning Krav om samme origin
WASM-filer skal serveres fra **samme origin** som BentoPDF-appen. Web Workers bruger `importScripts()`, som ikke kan indlæse scripts på tværs af origins. Hvis BentoPDF for eksempel kører på `https://internal.example.com`, skal WASM-base-URL'en også være `https://internal.example.com/wasm`.
:::
#### Manuelle trin
<details>
<summary>Hvis du foretrækker at gøre det manuelt uden scriptet</summary>
**Trin 1: Download WASM- og OCR-pakkerne** (på en maskine med 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
```
**Trin 2: Byg Docker-imaget med interne URL'er**
```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 .
```
**Trin 3: Eksportér Docker-imaget**
```bash
docker save bentopdf -o bentopdf.tar
```
**Trin 4: Overfør til det air-gappede netværk**
Kopiér via USB, internt artefaktlager eller en godkendt overførselsmetode:
- `bentopdf.tar`: Docker-imaget
- De fem `.tgz` WASM/OCR-pakker fra trin 1
- Mappen `tesseract-langdata/` fra trin 1
- Mappen `ocr-fonts/` fra trin 1
**Trin 5: Opsætning inde i det air-gappede netværk**
```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
```
Sørg for, at filerne er tilgængelige på de URL'er, du konfigurerede i trin 2, herunder `.../ocr/worker.min.js`, `.../ocr/core`, `.../ocr/lang-data` og `.../ocr/fonts`.
</details>
::: info Bygger du fra kildekode i stedet for Docker?
Sæt variablerne i `.env.production`, før du kører `npm run build`:
```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
```
:::
### Hosting af din egen WASM-proxy
Hvis du skal servere AGPL WASM-filer med korrekte CORS-headere, kan du deploye en simpel proxy. Se [Cloudflare WASM Proxy-guiden](https://github.com/alam00000/bentopdf/blob/main/cloudflare/WASM-PROXY.md) for et eksempel på en implementering.
::: tip Hvorfor adskilt?
Denne adskillelse sikrer:
- Klar juridisk compliance for kommercielle brugere
- BentoPDF's kerne forbliver under sin dobbeltlicens (AGPL-3.0 / kommerciel)
- WASM-filer indlæses under kørsel og bundles ikke i kildekoden
:::
## Systemkrav
| Krav | Minimum |
| ---------- | --------------------------------- |
| Lagerplads | ~100 MB (kerne uden AGPL-moduler) |
| RAM | 512 MB |
| CPU | Enhver moderne processor |
::: tip
BentoPDF er et statisk site, så der kræves ingen database eller backend-server!
:::
+211
View File
@@ -0,0 +1,211 @@
# Deploy med Kubernetes
Kubernetes er måske overkill til et statisk site, men det kan passe godt, hvis du allerede standardiserer på Helm + GitOps.
> [!IMPORTANT]
> **Påkrævede headere til konvertering af Office-filer**
>
> LibreOffice-baserede værktøjer (konvertering af Word, Excel, PowerPoint) kræver disse HTTP-headere for at understøtte `SharedArrayBuffer`:
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> De officielle BentoPDF nginx-images indeholder disse headere. I Kubernetes er **Ingress-/Gateway-controllere også reverse proxies**, så sørg for, at disse headere bevares (eller tilføj dem i kanten).
## Forudsætninger
- Kubernetes-klynge
- Helm v3
- Et BentoPDF nginx-image (f.eks. `ghcr.io/alam00000/bentopdf:<tag>`), der serverer på **port 8080**
## Deploy med Helm
### Installér fra dette repo (lokalt 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
```
### Installér fra GHCR (OCI-chart)
Hvis chartet er udgivet til GHCR som et OCI-artefakt:
```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
```
## Gør det tilgængeligt
### Port-forward (hurtig test)
```bash
kubectl -n bentopdf port-forward deploy/bentopdf 8080:8080
```
### Ingress (valgfrit)
Aktivér Ingress (eksempel til nginx-ingress):
```yaml
ingress:
enabled: true
className: nginx
hosts:
- host: pdf.example.com
paths:
- path: /
pathType: Prefix
```
### Gateway API (valgfrit)
Dette chart understøtter Gateway API `Gateway` + `HTTPRoute`.
Eksempel (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
```
## Sørg for, at SharedArrayBuffer-headerne stadig virker (Ingress/Gateway)
### Hvad der "burde" ske
BentoPDF's nginx-konfiguration sætter de nødvendige svarheadere. De fleste Ingress-/Gateway-controllere **sender upstream-svarheadere videre uændret**.
### Hvad der kan gå galt
- En controller- eller edge-politik, der **overskriver** eller **fjerner** svarheadere
- En "security headers"-middleware, der sætter andre COOP/COEP-værdier
### Sådan kontrollerer du det
Kør dette mod dit offentlige endpoint:
```bash
curl -I https://pdf.example.com/ | egrep -i 'cross-origin-opener-policy|cross-origin-embedder-policy'
```
Du skal se:
- `Cross-Origin-Opener-Policy: same-origin`
- `Cross-Origin-Embedder-Policy: require-corp`
### Hvis din Ingress-controller ikke bevarer dem
Tilføj headerne i kanten (afhænger af controlleren). Eksempel til **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-typefejl (Underskriv PDF / Formularudfylder, tom iframe)
Hvis browserens konsol viser `Failed to load module script: ... non-JavaScript MIME type "application/octet-stream"` på en `.mjs`-anmodning, erstatter en Ingress-/Gateway-controller foran BentoPDF's nginx den upstream `Content-Type`-header med `application/octet-stream`.
BentoPDF-imagets nginx serverer allerede `.mjs` som `application/javascript`. Løsningen er at stoppe controlleren i at fjerne eller erstatte den. For nginx-ingress:
```yaml
ingress:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
location ~* \.mjs$ {
types {} default_type application/javascript;
}
```
Eller brug et `ResponseHeaderModifier`-filter i Gateway API til at sætte `Content-Type: application/javascript` for `.mjs`-stier. Kontrollér i DevTools → fanen Network: Den mislykkede `.mjs`-anmodning skulle nu vise `content-type: application/javascript`.
### Hvis du bruger Gateway API og vil tvinge headere på
Gateway API understøtter et `ResponseHeaderModifier`-filter. Du kan knytte det til i `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
```
Understøttelse af bestemte filtre afhænger af din Gateway-controller. Hvis et filter ignoreres, så tilføj headerne i kanten eller i controller-laget i stedet.
## Deaktivering af bestemte værktøjer
Brug en ConfigMap til at deaktivere værktøjer under kørsel uden at genopbygge imaget:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: bentopdf-config
namespace: bentopdf
data:
config.json: |
{
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
}
```
Montér den i den serverede mappe:
```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
```
Værktøjs-id'er er sidens URL uden `.html`. Åbn et værktøj, og se på URL'en (f.eks. `edit-pdf`, `merge-pdf`, `compress-pdf`). Deaktiverede værktøjer skjules fra startsiden, søgning, genveje, workflow-builderen og direkte URL-adgang. Se [Docker-guiden](/da/self-hosting/docker#deaktivering-af-bestemte-værktøjer) for den fulde liste over indstillinger.
+139
View File
@@ -0,0 +1,139 @@
# Deploy til Netlify
[Netlify](https://netlify.com) tilbyder fremragende hosting af statiske sites med en generøs gratis plan.
## Deploy med ét klik
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/alam00000/bentopdf)
## Manuel deployment
### Trin 1: Forbind repository
1. Log ind på [Netlify](https://app.netlify.com)
2. Klik på "Add new site" → "Import an existing project"
3. Forbind din GitHub-konto
4. Vælg din BentoPDF-fork
### Trin 2: Konfigurér build-indstillinger
| Indstilling | Værdi |
| ----------------- | --------------- |
| Build command | `npm run build` |
| Publish directory | `dist` |
| Node version | 18+ |
### Trin 3: Deploy
Klik på "Deploy site", og vent på buildet.
## Konfigurationsfil
Opret `netlify.toml` i dit projekts rodmappe:
```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 Vigtigt
Headerne `Cross-Origin-Embedder-Policy` og `Cross-Origin-Opener-Policy` er påkrævet til konvertering af Word/ODT/Excel/PowerPoint til PDF. Uden dem er `SharedArrayBuffer` utilgængelig, og LibreOffice WASM-motoren kan ikke starte.
Headerne `Content-Encoding: gzip` på filerne `.wasm.gz` og `.data.gz` får browseren til at dekomprimere dem automatisk. Uden dem modtager browseren rå gzip-bytes, og WASM-kompileringen mislykkes.
:::
## Miljøvariabler
Angiv disse under Site settings → Environment variables:
| Variabel | Beskrivelse |
| ----------------------- | ------------------------------------------------------------ |
| `SIMPLE_MODE` | Sæt til `true` for et minimalt build |
| `VITE_BRAND_NAME` | Brugerdefineret brandnavn (erstatter "BentoPDF") |
| `VITE_BRAND_LOGO` | Logosti relativt til `public/` (f.eks. `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | Brugerdefineret sidefods-/ophavsretstekst |
| `VITE_DEFAULT_LANGUAGE` | Standardsprog i brugerfladen (f.eks. `fr`, `de`, `es`) |
## Eget domæne
1. Gå til Site settings → Domain management
2. Klik på "Add custom domain"
3. Følg anvisningerne til DNS-konfiguration
## Store mediefiler
Til store WASM-filer kan du overveje at aktivere [Netlify Large Media](https://docs.netlify.com/large-media/overview/):
```bash
netlify lm:setup
git lfs track "*.wasm"
```
## Fejlfinding
### Word/ODT/Excel til PDF hænger ved 55 %
Hvis dokumentkonverteringer hænger ved 55 %, så åbn DevTools Console og kontrollér:
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
Hvis `crossOriginIsolated` er `false`, bliver COEP/COOP-headerne fra din `netlify.toml` ikke anvendt. Sørg for, at filen ligger i dit projekts rodmappe, og deploy igen.
Hvis du ser `expected magic word 00 61 73 6d, found 1f 8b 08 08` i konsollen, mangler `.wasm.gz`-filerne headerne `Content-Encoding: gzip`. Sørg for, at blokkene `[[headers]]` til `soffice.wasm.gz` og `soffice.data.gz` findes i din `netlify.toml`.
### Build mislykkes
Kontrollér kompatibiliteten med Node-versionen:
```toml
[build.environment]
NODE_VERSION = "20"
```
### Langsom første indlæsning
Aktivér asset-optimering under Site settings → Build & deploy → Asset optimization.
+232
View File
@@ -0,0 +1,232 @@
# Deploy med Nginx
Host BentoPDF på din egen server med Nginx.
## Forudsætninger
- Ubuntu/Debian-server
- Nginx installeret
- SSL-certifikat (anbefalet: Let's Encrypt)
## Trin 1: Byg projektet
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
For at tilpasse branding skal du sætte miljøvariabler, før du bygger:
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## Trin 2: Kopiér filer
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## Trin 3: Nginx-konfiguration
Opret `/etc/nginx/sites-available/bentopdf`:
```nginx
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
root /var/www/bentopdf;
index index.html;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/wasm;
gzip_min_length 1000;
# WASM MIME type
types {
application/wasm wasm;
}
# Required headers for SharedArrayBuffer (LibreOffice WASM)
# These must be on every response - especially HTML pages
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "cross-origin" always;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Pre-compressed LibreOffice WASM binary
location ~* /libreoffice-wasm/soffice\.wasm\.gz$ {
gzip off;
types {} default_type application/wasm;
add_header Content-Encoding gzip;
add_header Cache-Control "public, immutable";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
# Pre-compressed LibreOffice WASM data
location ~* /libreoffice-wasm/soffice\.data\.gz$ {
gzip off;
types {} default_type application/octet-stream;
add_header Content-Encoding gzip;
add_header Cache-Control "public, immutable";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|wasm)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
# SPA routing - serve index.html for all routes
location / {
try_files $uri $uri/ /index.html;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
}
```
## Trin 4: Aktivér sitet
```bash
sudo ln -s /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
## Trin 5: SSL med Let's Encrypt
```bash
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
```
## Deployment i undermappe
For at hoste på `/pdf/`:
```nginx
location /pdf/ {
alias /var/www/bentopdf/;
try_files $uri $uri/ /pdf/index.html;
}
```
Byg med:
```bash
BASE_URL=/pdf/ npm run build
```
## Ydelsesjustering
Tilføj til `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;
}
```
## Fejlfinding
### WASM indlæses ikke
Sørg for, at MIME-typen er sat:
```nginx
types {
application/wasm wasm;
}
```
### Underskriv PDF eller Formularudfylder viser en tom læser (`.mjs` MIME-fejl)
Hvis browserens konsol viser:
```
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.
```
…og den mislykkede anmodning ender på `.mjs`, mangler din nginx tilknytningen `.mjs → application/javascript`. Den indbyggede PDF-læser (brugt af Underskriv PDF, Formularudfylder og et par andre værktøjer) leverer ES-modulfiler med filendelsen `.mjs`, og nginx' standardfil `mime.types` indeholder dem ikke som standard.
Ret det ved at tilføje `mjs` til den eksisterende `application/javascript`-linje i `/etc/nginx/mime.types`:
```nginx
application/javascript js mjs;
```
> Tilføj IKKE en _anden_ `application/javascript`-linje. Nginx læser `mime.types` linje for linje, og kun én tilknytning mellem type og filendelse vinder, så en anden linje ignoreres i stilhed på nogle versioner. Redigér den eksisterende linje, så `mjs` står ved siden af `js`.
Eller overstyr lokalt i din server-blok (uden at redigere den globale fil):
```nginx
types {
application/javascript js mjs;
application/wasm wasm;
}
```
Genindlæs nginx efter ændringen:
```bash
sudo nginx -t && sudo systemctl reload nginx
```
For at bekræfte det: Find den mislykkede `.mjs`-anmodning i browserens DevTools → fanen Network, og kontrollér svarheaderen `Content-Type`. Den skulle nu være `application/javascript`, ikke `application/octet-stream`.
### Word/ODT/Excel til PDF virker ikke
LibreOffice WASM kræver `SharedArrayBuffer`, som kræver headerne `Cross-Origin-Embedder-Policy` og `Cross-Origin-Opener-Policy`. Det kræver også en sikker kontekst, så `http://localhost` virker til lokal test, men `http://192.168.x.x` eller andre LAN-IP'er kræver normalt HTTPS. Bemærk, at nginx' `add_header`-direktiver i en `location`-blok **overskriver** `add_header`-direktiver på serverniveau. De bliver ikke slået sammen. Hver `location`-blok med sin egen `add_header` skal indeholde COEP/COOP-headerne.
Kontrollér med:
```bash
curl -I https://your-domain.com/ | grep -i cross-origin
```
Hvis du bruger en reverse proxy foran nginx, så sørg for, at den bevarer disse headere.
### 502 Bad Gateway
Tjek Nginx' fejllogge:
```bash
sudo tail -f /var/log/nginx/error.log
```
+122
View File
@@ -0,0 +1,122 @@
# Deploy til Vercel
[Vercel](https://vercel.com) tilbyder den letteste deployment-oplevelse for statiske sites.
## Deploy med ét klik
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/alam00000/bentopdf)
## Manuel deployment
### Trin 1: Fork repositoriet
Fork [bentopdf/bentopdf](https://github.com/alam00000/bentopdf) til din GitHub-konto.
### Trin 2: Importér til Vercel
1. Gå til [vercel.com/new](https://vercel.com/new)
2. Vælg dit forkede repository
3. Konfigurér projektet:
| Indstilling | Værdi |
| ---------------- | --------------- |
| Framework Preset | Vite |
| Build Command | `npm run build` |
| Output Directory | `dist` |
| Install Command | `npm install` |
### Trin 3: Miljøvariabler (valgfrit)
Tilføj disse, hvis det er nødvendigt:
| Variabel | Beskrivelse |
| ----------------------- | ------------------------------------------------------------ |
| `SIMPLE_MODE` | Sæt til `true` for minimal brugerflade |
| `VITE_BRAND_NAME` | Brugerdefineret brandnavn (erstatter "BentoPDF") |
| `VITE_BRAND_LOGO` | Logosti relativt til `public/` (f.eks. `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | Brugerdefineret sidefods-/ophavsretstekst |
| `VITE_DEFAULT_LANGUAGE` | Standardsprog i brugerfladen (f.eks. `fr`, `de`, `es`) |
### Trin 4: Deploy
Klik på "Deploy", og vent på, at buildet er færdigt.
## Eget domæne
1. Gå til dit projekts indstillinger
2. Naviger til "Domains"
3. Tilføj dit eget domæne
4. Konfigurér DNS som anvist
## Begrænsninger
::: warning Store filer
Vercels serverless-funktioner har en grænse på 50 MB. Da BentoPDF er et statisk site, bør det ikke påvirke dig, men WASM-modulerne er store (ca. 100 MB i alt). Sørg for, at de leveres fra mappen `/public`.
:::
## Fejlfinding
### Build-timeout
Hvis buildet timer ud, så prøv:
```json
// vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": "dist"
}
```
### 404 ved opdatering af siden
Tilføj en `vercel.json` til SPA-routing:
```json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
```
### Word/ODT/Excel til PDF virker ikke
LibreOffice WASM-konverteringer kræver `SharedArrayBuffer`, som kræver disse svarheadere på alle sider:
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
De forkomprimerede `.wasm.gz`- og `.data.gz`-filer skal også have `Content-Encoding: gzip`, så browseren dekomprimerer dem.
Tilføj disse til din `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" }
]
}
]
}
```
For at kontrollere det skal du åbne DevTools Console og køre `console.log(window.crossOriginIsolated)`. Den skal returnere `true`.