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 @@
# Desplega amb Apache
Allotja BentoPDF amb Apache HTTP Server.
## Requisits previs
- Apache 2.4+
- mod_rewrite activat
- Certificat SSL (recomanat)
## Pas 1: compila el projecte
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
Per personalitzar la marca, defineix variables d'entorn abans de compilar:
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## Pas 2: copia els fitxers
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## Pas 3: configuració d'Apache
Crea `/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>
```
## Pas 4: .htaccess per a l'encaminament
Crea `/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
```
## Pas 5: activa els mòduls necessaris
```bash
sudo a2enmod rewrite
sudo a2enmod ssl
sudo a2enmod headers
sudo a2enmod expires
sudo a2enmod deflate
```
## Pas 6: activa el lloc
```bash
sudo a2ensite bentopdf.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
```
## Desplegament en un subdirectori
Per allotjar-lo a `/pdf/`:
1. Compila amb la URL base:
```bash
BASE_URL=/pdf/ npm run build
```
2. Actualitza `.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
```
## Resolució de problemes
### Errors 404 amb WASM
Assegura't que el tipus MIME està configurat:
```apache
AddType application/wasm .wasm
```
### Signa PDF o el emplenador de formularis mostra un visor en blanc (error de MIME amb `.mjs`)
Si la consola del navegador mostra:
```
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.
```
…i la petició que falla acaba en `.mjs`, a la configuració d'Apache li falta l'assignació `.mjs → application/javascript`. El visor de PDF inclòs porta fitxers de mòdul ES amb extensió `.mjs`, i el `mime.types` estàndard d'Apache no els inclou per defecte.
Afegeix-ho al VirtualHost o a `.htaccess`:
```apache
AddType application/javascript .mjs
```
Després recarrega Apache:
```bash
sudo apache2ctl configtest && sudo systemctl reload apache2
```
Per comprovar-ho: a les DevTools del navegador → pestanya Network, troba la petició `.mjs` que falla i mira la capçalera de resposta `Content-Type`. Ara hauria de ser `application/javascript`, no `application/octet-stream`.
### La reescriptura no funciona
Comprova que mod_rewrite està activat:
```bash
sudo a2enmod rewrite
```
### La conversió de Word/ODT/Excel a PDF no funciona
LibreOffice WASM requereix `SharedArrayBuffer`, que necessita aquestes capçaleres:
```apache
Header always set Cross-Origin-Embedder-Policy "require-corp"
Header always set Cross-Origin-Opener-Policy "same-origin"
```
També necessita un context segur. `http://localhost` funciona per a proves locals, però `http://192.168.x.x` o altres IP de la xarxa local normalment requereixen HTTPS. Si les capçaleres hi són però `window.crossOriginIsolated` continua sent `false`, comprova si la pàgina s'obre per HTTP sense xifrar des d'un origen que no és de bucle local.
Els fitxers precomprimits `.wasm.gz` i `.data.gz` també necessiten un `Content-Encoding` correcte:
```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>
```
Assegura't que `mod_headers` està activat: `sudo a2enmod headers`
### Permís denegat
```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 @@
# Desplega a AWS S3 + CloudFront
Allotja BentoPDF a AWS per tenir el màxim control i escalabilitat.
## Arquitectura
```
User → CloudFront (CDN) → S3 (Static Files)
```
## Pas 1: crea el bucket d'S3
```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
```
## Pas 2: compila i puja
```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
```
## Pas 3: crea la distribució de CloudFront
```bash
aws cloudfront create-distribution \
--origin-domain-name your-bentopdf-bucket.s3.amazonaws.com \
--default-root-object index.html
```
O utilitza la consola d'AWS:
1. Ves a CloudFront → Create distribution
2. Origin domain: selecciona el teu bucket d'S3
3. Activa "Origin Access Control"
4. Default root object: `index.html`
5. Crea la distribució
## Pas 3b: política de capçaleres de resposta (obligatòria per a LibreOffice WASM)
Les conversions basades en LibreOffice (Word, Excel i PowerPoint a PDF) requereixen `SharedArrayBuffer`, que necessita unes capçaleres de resposta concretes. Crea una Response Headers Policy de CloudFront:
1. Ves a CloudFront → Policies → Response headers
2. Crea una política personalitzada amb aquestes capçaleres:
| Capçalera | Valor |
| ------------------------------ | -------------- |
| `Cross-Origin-Embedder-Policy` | `require-corp` |
| `Cross-Origin-Opener-Policy` | `same-origin` |
| `Cross-Origin-Resource-Policy` | `cross-origin` |
3. Associa la política al comportament de memòria cau per defecte de la distribució
O per CLI:
```bash
aws cloudfront create-response-headers-policy \
--response-headers-policy-config '{
"Name": "BentoPDF-COEP-COOP",
"CustomHeadersConfig": {
"Quantity": 3,
"Items": [
{"Header": "Cross-Origin-Embedder-Policy", "Value": "require-corp", "Override": true},
{"Header": "Cross-Origin-Opener-Policy", "Value": "same-origin", "Override": true},
{"Header": "Cross-Origin-Resource-Policy", "Value": "cross-origin", "Override": true}
]
}
}'
```
## Pas 3c: metadades d'S3 per als fitxers WASM precomprimits
Els fitxers WASM de LibreOffice estan precomprimits (`.wasm.gz`, `.data.gz`). Defineix el Content-Type i el Content-Encoding correctes perquè els navegadors els descomprimeixin:
```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 Important
Sense la política de capçaleres de resposta, `SharedArrayBuffer` no està disponible i les conversions de LibreOffice WASM es quedaran bloquejades al ~55 %. Sense el Content-Encoding correcte als fitxers `.gz`, el navegador rep bytes gzip en brut i la compilació de WASM falla.
:::
## Pas 4: política del bucket d'S3
Permet que CloudFront accedeixi al bucket:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontAccess",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bentopdf-bucket/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
}
}
}
]
}
```
## Pas 5: pàgines d'error personalitzades
Configura el 404 perquè retorni `index.html` per a l'encaminament SPA:
1. CloudFront → Error pages
2. Crea una resposta d'error personalitzada:
- HTTP error code: 404
- Response page path: `/index.html`
- HTTP response code: 200
## Estimació de costos
| Recurs | Cost estimat |
| --------------------------- | ------------ |
| Emmagatzematge S3 (~500MB) | ~0,01 $/mes |
| CloudFront (1TB transfer.) | ~85 $/mes |
| CloudFront (10GB transfer.) | ~0,85 $/mes |
::: tip
Utilitza S3 Intelligent Tiering per optimitzar costos en fitxers d'accés poc freqüent.
:::
## Automatització amb 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 @@
# Desplega a Cloudflare Pages
[Cloudflare Pages](https://pages.cloudflare.com) ofereix allotjament estàtic ràpid i global amb amplada de banda il·limitada.
## Desplegament ràpid
1. Ves a [Cloudflare Pages](https://dash.cloudflare.com/?to=/:account/pages)
2. Fes clic a "Create a project"
3. Connecta el teu repositori de GitHub
## Configuració de la compilació
| Paràmetre | Valor |
| ---------------------- | --------------- |
| Framework preset | None |
| Build command | `npm run build` |
| Build output directory | `dist` |
| Root directory | `/` |
## Variables d'entorn
Afegeix-les a Settings → Environment variables:
| Variable | Valor |
| ----------------------- | ----------------------------------------------------------- |
| `NODE_VERSION` | `18` |
| `SIMPLE_MODE` | `false` (opcional) |
| `VITE_BRAND_NAME` | Nom de marca personalitzat (opcional) |
| `VITE_BRAND_LOGO` | Ruta del logotip relativa a `public/` (opcional) |
| `VITE_FOOTER_TEXT` | Text personalitzat del peu de pàgina/copyright (opcional) |
| `VITE_DEFAULT_LANGUAGE` | Idioma per defecte de la interfície, p. ex. `fr` (opcional) |
## Fitxer de configuració
Crea `_headers` a la carpeta `public`:
```
# Required security headers for SharedArrayBuffer (used by LibreOffice WASM)
/*
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: cross-origin
# Pre-compressed LibreOffice WASM binary
/libreoffice-wasm/soffice.wasm.gz
Content-Type: application/wasm
Content-Encoding: gzip
Cache-Control: public, max-age=31536000, immutable
# Pre-compressed LibreOffice WASM data
/libreoffice-wasm/soffice.data.gz
Content-Type: application/octet-stream
Content-Encoding: gzip
Cache-Control: public, max-age=31536000, immutable
# Cache WASM files aggressively
/*.wasm
Cache-Control: public, max-age=31536000, immutable
Content-Type: application/wasm
# Service worker
/sw.js
Cache-Control: no-cache
```
::: warning Important
Les capçaleres `Cross-Origin-Embedder-Policy` i `Cross-Origin-Opener-Policy` són obligatòries per a les conversions de Word/ODT/Excel/PowerPoint a PDF. Sense elles, `SharedArrayBuffer` no està disponible i el motor LibreOffice WASM no s'inicialitzarà.
:::
Crea `_redirects` per a l'encaminament SPA:
```
/* /index.html 200
```
## Domini personalitzat
1. Ves al teu projecte de Pages
2. Fes clic a "Custom domains"
3. Afegeix el teu domini
4. Cloudflare configurarà el DNS automàticament si el domini és a Cloudflare
## Avantatges
- **Amplada de banda il·limitada i gratuïta**
- **CDN global** amb més de 300 ubicacions de xarxa
- **HTTPS automàtic**
- **Desplegaments de previsualització** per a les pull requests
- **Compilacions ràpides**
## Resolució de problemes
### Càrrega de fitxers grans
Cloudflare Pages admet fitxers de fins a 25 MB. Els mòduls WASM haurien d'anar bé, però si arribes als límits, considera:
```bash
# Split large files during build
npm run build
```
### Límits de mida dels Workers
Si utilitzes Cloudflare Workers per a un encaminament avançat, tingues en compte el límit d'1 MB dels plans gratuïts.
## Worker de proxy CORS (per a signatures digitals)
L'eina de signatura digital requereix un proxy CORS per obtenir les cadenes de certificats. Desplega el worker inclòs:
```bash
cd cloudflare
npx wrangler login
npx wrangler deploy
```
### Funcions de seguretat
| Funció | Descripció |
| -------------------------- | ------------------------------------ |
| **Restriccions d'URL** | Només es permeten URL de certificats |
| **Límit de mida** | Màx. 10MB per petició |
| **Límit de freqüència** | 60 peticions/IP/min (requereix KV) |
| **Bloqueig d'IP privades** | Bloqueja localhost i IP internes |
### Activa el límit de freqüència
```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
```
### Compila amb la URL del proxy
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
O amb 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 .
```
> **Nota:** Consulta el [README](https://github.com/alam00000/bentopdf#digital-signature-cors-proxy-required) per a la configuració de la signatura HMAC.
+180
View File
@@ -0,0 +1,180 @@
# Proxy CORS per obtenir certificats
L'eina de signatura digital utilitza un proxy CORS per obtenir els certificats dels emissors a les autoritats de certificació (CA) externes. Això és necessari perquè molts servidors de CA no inclouen capçaleres CORS a les respostes, cosa que impedeix la consulta directa des del navegador.
A més, molts servidors de CA serveixen els certificats per HTTP sense xifrar. Quan la teva instància de BentoPDF s'allotja per HTTPS, els navegadors bloquegen aquestes peticions HTTP (política de contingut mixt). El proxy CORS resol tots dos problemes encaminant les peticions per un punt final HTTPS amb les capçaleres adequades.
## Com funciona
Quan signes un PDF amb un certificat:
1. La biblioteca `zgapdfsigner` intenta construir una cadena de certificats completa
2. Obté els certificats dels emissors a partir de les URL incrustades a l'extensió AIA (Authority Information Access) del teu certificat
3. Aquestes peticions passen per un proxy CORS que afegeix les capçaleres `Access-Control-Allow-Origin` necessàries
4. El proxy retorna les dades del certificat al navegador
## Autoallotjar el proxy CORS
Si autoallotges BentoPDF, hauràs de desplegar el teu propi proxy CORS perquè les signatures digitals funcionin amb certificats que requereixen obtenir la cadena.
### Opció 1: Cloudflare Workers (recomanada)
1. **Instal·la la CLI de Wrangler**:
```bash
npm install -g wrangler
```
2. **Inicia sessió a Cloudflare**:
```bash
wrangler login
```
3. **Clona BentoPDF i actualitza els orígens permesos**:
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf/cloudflare
```
Obre `cors-proxy-worker.js` i canvia l'array `ALLOWED_ORIGINS` pel teu domini:
```js
const ALLOWED_ORIGINS = [
'https://your-domain.com',
'https://www.your-domain.com',
];
```
::: warning Important
Sense aquest canvi, el proxy rebutjarà totes les peticions del teu lloc amb un error **403 Forbidden**. Per defecte, només permet peticions de `bentopdf.com`.
:::
4. **Desplega el proxy**:
```bash
wrangler deploy
```
Anota la URL del worker (p. ex., `https://bentopdf-cors-proxy.your-subdomain.workers.dev`).
5. **Torna a compilar BentoPDF amb la URL del proxy**:
Si utilitzes 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 .
```
Si compiles des del codi font:
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
### Opció 2: proxy de backend propi
També pots crear el teu propi punt final de proxy. Els requisits són:
1. Acceptar peticions GET amb un paràmetre de consulta `url`
2. Obtenir la URL des del teu servidor (sense restriccions CORS al servidor)
3. Retornar la resposta amb aquestes capçaleres:
- `Access-Control-Allow-Origin: https://your-domain.com`
- `Access-Control-Allow-Methods: GET, OPTIONS`
- `X-Content-Type-Options: nosniff`
Exemple d'implementació amb 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' });
}
});
```
## Consideracions de seguretat
El Cloudflare Worker inclòs té diverses mesures de seguretat:
| Funció | Descripció |
| ------------------------------ | ------------------------------------------------------------------------------------------------------- |
| **Validació d'origen** | Només permet peticions dels dominis llistats a `ALLOWED_ORIGINS` |
| **Restriccions d'URL** | Només permet URL de certificats (`.crt`, `.cer`, `.pem`, `/certs/`, `/ocsp`, `/crl`) |
| **Bloqueig d'IP privades** | Bloqueja intervals privats IPv4/IPv6, d'enllaç local, de bucle local, IP decimals i metadades del núvol |
| **Seguretat del Content-Type** | Només retorna tipus MIME de certificats segurs i bloqueja la injecció de content-type des de l'origen |
| **Límit de mida** | Transmet la resposta amb un límit de 10MB i l'interromp a mitja descàrrega si se supera |
| **Límit de freqüència** | 60 peticions per minut per IP o IPv6 /64 (requereix KV, no bloqueja si KV falla) |
| **Signatures HMAC** | Signatura opcional al client (dissuadeix l'abús casual) |
### DNS rebinding (autoallotjament fora de Cloudflare)
El worker rebutja qualsevol URL que es resolgui a una IP privada o interna. No pot utilitzar una llista fixa de servidors permesos, ja que les URL de la cadena de certificats apunten a qualsevol CA que hagi emès el certificat (FNMT, CA corporatives, etc.), de manera que les destinacions no es coneixen per endavant. Això deixa una petita finestra de rebinding: algú que gestioni el DNS del seu propi domini podria donar una IP pública a la comprovació de seguretat i una de privada a la petició real un moment després.
A Cloudflare no es pot explotar: els Workers no poden accedir a IP privades ni al punt final de metadades `169.254.169.254`, així que un rebinding no arriba enlloc. Només cal no donar al worker cap via d'entrada: no hi associïs cap Tunnel, Service ni enllaç de xarxa privada (només necessita `RATE_LIMIT_KV`).
Si l'executes fora de Cloudflare (workerd, Node i similars) dins d'una VPC, l'entorn _sí_ pot accedir a IP internes. Posa-hi al davant un tallafoc de sortida i bloqueja els intervals privats: `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` i els seus equivalents IPv6. El codi de l'aplicació no ho pot garantir un cop fora de Cloudflare.
## Desactivar el proxy
Si no vols utilitzar un proxy CORS, defineix la variable d'entorn amb una cadena buida:
```
VITE_CORS_PROXY_URL=
```
**Nota**: sense el proxy, la signatura amb certificats que requereixen obtenir la cadena externament (com FNMT o algunes CA corporatives) fallarà amb l'error "Failed to fetch".
## Resolució de problemes
### "Signing error: TypeError: Failed to fetch"
Normalment vol dir una d'aquestes coses:
1. **No hi ha cap proxy CORS configurat**: defineix `VITE_CORS_PROXY_URL` i torna a compilar
2. **Contingut mixt bloquejat**: el teu lloc és HTTPS però la URL de l'emissor del certificat és HTTP. El proxy CORS ho resol.
3. **El proxy CORS rebutja el teu origen**: comprova que el teu domini és a l'array `ALLOWED_ORIGINS` de `cors-proxy-worker.js`
### "403 Forbidden" del proxy
El teu domini no és a la llista `ALLOWED_ORIGINS`. Edita `cors-proxy-worker.js`:
```js
const ALLOWED_ORIGINS = ['https://your-domain.com'];
```
Després torna a desplegar: `npx wrangler deploy`
### Provar el proxy
```bash
curl -H "Origin: https://your-domain.com" \
"https://your-proxy.workers.dev?url=http://www.cert.fnmt.es/certs/ACUSU.crt"
```
### Certificats que funcionen sense proxy
Alguns certificats inclouen la cadena completa al fitxer P12/PFX i no requereixen obtenir res externament:
- Certificats autosignats
- Algunes CA comercials que inclouen els certificats intermedis
- Certificats que has muntat manualment amb la cadena completa
+622
View File
@@ -0,0 +1,622 @@
# Desplega amb Docker / Podman
La manera més fàcil d'autoallotjar BentoPDF en un entorn de producció.
> [!IMPORTANT]
> **Capçaleres necessàries per a la conversió de fitxers d'Office**
>
> Les eines basades en LibreOffice (conversió de Word, Excel i PowerPoint) requereixen aquestes capçaleres HTTP per admetre `SharedArrayBuffer`:
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> La pàgina també s'ha de servir des d'un context segur. `http://localhost` funciona per a proves locals, però `http://192.168.x.x` o altres IP de la xarxa local normalment no ho compleixen, de manera que la conversió d'Office per HTTP sense xifrar fallarà encara que les capçaleres hi siguin.
>
> Les imatges de contenidor oficials inclouen aquestes capçaleres. Si utilitzes un proxy invers (Traefik, Caddy, etc.), assegura't que aquestes capçaleres es conserven o s'afegeixen, i utilitza HTTPS per a l'accés fora del bucle local.
> [!TIP]
> **Usuaris de Podman:** totes les ordres `docker` funcionen amb Podman substituint `docker` per `podman` i `docker-compose` per `podman-compose`.
## Inici ràpid
> [!TIP]
> BentoPDF s'ofereix en dues versions:
>
> - **Versió autoallotjada**: `ghcr.io/alam00000/bentopdf-simple:latest`. Totes les eines PDF, **sense** els elements de màrqueting (sense portada, preguntes freqüents, testimonis ni peu de pàgina). Utilitza-la per a desplegaments interns, d'equip o d'organització. **No** és una versió "lite" amb menys funcions.
> - **Versió comercial**: `ghcr.io/alam00000/bentopdf:latest`. El lloc de màrqueting complet, que utilitza el mateix bentopdf.com i els titulars de llicència comercial amb desplegaments públics.
>
> Si tens dubtes, descarrega la versió autoallotjada.
```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
Crea `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
```
Executa:
```bash
# Docker Compose
docker compose up -d
# Podman Compose
podman-compose up -d
```
## Compila la teva pròpia imatge
```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;"]
```
Compila i executa:
```bash
docker build -t bentopdf:custom .
docker run -d -p 3000:8080 bentopdf:custom
```
## Variables d'entorn
| Variable | Descripció | Per defecte |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `SIMPLE_MODE` | Compila la variant autoallotjada (amaga els elements de màrqueting de bentopdf.com: portada, preguntes freqüents, testimonis i peu de pàgina). Es mantenen totes les eines PDF. | `false` |
| `BASE_URL` | Desplega en un subdirectori | `/` |
| `VITE_WASM_PYMUPDF_URL` | URL del mòdul WASM de PyMuPDF | `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/` |
| `VITE_WASM_GS_URL` | URL del mòdul WASM de Ghostscript | `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm@0.1.1/assets/` |
| `VITE_WASM_CPDF_URL` | URL del mòdul WASM de CoherentPDF | `https://cdn.jsdelivr.net/npm/coherentpdf@2.5.5/dist/` |
| `VITE_TESSERACT_WORKER_URL` | URL del script del worker d'OCR | _(buit; utilitza el CDN per defecte de Tesseract.js)_ |
| `VITE_TESSERACT_CORE_URL` | Directori del runtime principal d'OCR | _(buit; utilitza el CDN per defecte de Tesseract.js)_ |
| `VITE_TESSERACT_LANG_URL` | Directori de traineddata d'OCR | _(buit; utilitza el CDN per defecte de Tesseract.js)_ |
| `VITE_TESSERACT_AVAILABLE_LANGUAGES` | Idiomes d'OCR separats per comes que es mostren a la interfície | _(buit; mostra el catàleg complet)_ |
| `VITE_OCR_FONT_BASE_URL` | Directori de fonts de la capa de text d'OCR | _(buit; utilitza les URL remotes de fonts Noto)_ |
| `VITE_DEFAULT_LANGUAGE` | Idioma per defecte de la interfície | `en` |
| `VITE_BRAND_NAME` | Nom de marca personalitzat | `BentoPDF` |
| `VITE_BRAND_LOGO` | Ruta del logotip relativa a `public/` | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | Text personalitzat del peu de pàgina/copyright | `© 2026 BentoPDF. All rights reserved.` |
| `DISABLE_TOOLS` | ID d'eines separats per comes que s'han d'amagar | _(buit; totes les eines activades)_ |
| `DISABLE_GITHUB_STARS` | Omet la consulta del nombre d'estrelles de GitHub en carregar la pàgina i elimina `api.github.com` de la CSP. Les compilacions en mode simple sempre l'ometen. | `false` |
| `SITE_URL` | URL pública escrita als enllaços canonical, hreflang i del mapa del lloc. Deixa-la per defecte tret que vulguis que els cercadors tractin el teu desplegament com un lloc propi. | `https://www.bentopdf.com` |
| `ALLOW_INDEXING` | Amb un `SITE_URL` personalitzat, totes les pàgines es marquen com a `noindex` perquè la teva còpia no competeixi amb bentopdf.com als resultats de cerca. Defineix-ho a `true` perquè els cercadors l'indexin. | `false` |
Les URL dels mòduls WASM vénen preconfigurades amb CDN per defecte, així que totes les funcions avançades funcionen directament. Substitueix-les en desplegaments aïllats de la xarxa o autoallotjats.
### Cercadors
Per defecte, totes les pàgines indiquen als cercadors que l'original és bentopdf.com, de manera que el teu desplegament no apareix com a duplicat. No cal canviar res per a una instància privada o interna.
- **Mantenir la instància fora dels resultats de cerca:** executa el contenidor amb `-e ROBOTS_NOINDEX=true`. Només cal reiniciar-lo, no tornar a compilar.
- **Fer que la instància sigui un lloc propi als resultats de cerca:** compila amb `--build-arg SITE_URL=https://your-domain.example --build-arg ALLOW_INDEXING=true`. Sense `ALLOW_INDEXING=true`, un `SITE_URL` personalitzat marca totes les pàgines com a `noindex`.
### Content-Security-Policy
La imatge d'nginx inclou una capçalera `Content-Security-Policy` d'aplicació obligatòria. Les directives `script-src`, `connect-src` i `font-src` de la CSP es **generen en el moment de la compilació** a partir de les variables d'URL `VITE_*` anteriors: els servidors que passis amb `--build-arg` s'afegeixen automàticament a la política.
Com a resultat:
- Si substitueixes `VITE_CORS_PROXY_URL` o `VITE_WASM_*_URL` en compilar, la CSP permet aquests orígens automàticament, sense cap configuració addicional.
- Per a les URL de `cdn.jsdelivr.net`, `script-src` només permet la carpeta exacta del paquet amb versió, per exemple `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/`, no tot el CDN. jsDelivr serveix qualsevol paquet npm públic o repositori de GitHub, així que permetre tot el servidor deixaria executar-los a la teva instància. Les URL de qualsevol altre servidor es permeten per origen.
- Si configures URL WASM personalitzades _en execució_ a la pàgina de configuració avançada de l'aplicació, aquests orígens **no** són a la CSP i el navegador en bloquejarà les peticions. Això també val per a una versió diferent del mateix paquet a `cdn.jsdelivr.net`, perquè només es permet la versió definida en compilar. La configuració en execució és per experimentar; per a URL personalitzades permanents, defineix l'argument de compilació `VITE_*` corresponent.
- Als desplegaments aïllats de la xarxa que substitueixen els tres valors `VITE_WASM_*_URL`, també s'eliminen de la CSP aquestes carpetes de paquets de `cdn.jsdelivr.net` (cada valor per defecte se substitueix, no s'afegeix). Els valors per defecte d'OCR de Tesseract a `cdn.jsdelivr.net` es queden a la política fins que també defineixis els tres valors `VITE_TESSERACT_*`. De la mateixa manera, definir `VITE_CORS_PROXY_URL` substitueix el valor públic per defecte `bentopdf-cors-proxy.bentopdf.workers.dev`.
La CSP inclou `'unsafe-eval'` a `script-src` perquè el runtime de LibreOffice WASM (utilitzat per les eines de conversió de Word/Excel/PowerPoint) i la biblioteca d'imatges wasm-vips (utilitzada per PDF a TIFF) compilen codi de distribució intern mitjançant `new Function()`. Eliminar-lo trencaria aquestes eines. Si compiles en `SIMPLE_MODE` (sense LibreOffice) i no necessites PDF a TIFF, pots editar manualment el `security-headers.conf` generat per treure `'unsafe-eval'` i obtenir una política més estricta.
Per a l'OCR, deixa buides les variables `VITE_TESSERACT_*` per utilitzar els recursos en línia per defecte, o defineix-les totes tres alhora per a l'OCR autoallotjat o sense connexió. Les substitucions parcials d'OCR es rebutgen perquè el worker, el runtime principal i el directori de traineddata han de coincidir. Per generar PDF cercables totalment sense connexió, defineix també `VITE_OCR_FONT_BASE_URL` perquè les fonts de la capa de text d'OCR es carreguin des del teu servidor intern i no des de les URL públiques de fonts Noto.
`VITE_DEFAULT_LANGUAGE` defineix l'idioma de la interfície per als visitants que hi entren per primera vegada. Valors admesos: `en`, `ar`, `be`, `fr`, `de`, `es`, `zh`, `zh-TW`, `vi`, `tr`, `id`, `it`, `pt`, `nl`, `da`. Els usuaris poden canviar d'idioma igualment: això només canvia el valor per defecte.
Exemple:
```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
```
### Marca personalitzada
Substitueix el logotip, el nom i el text del peu de pàgina de BentoPDF pels teus. Posa el fitxer del logotip a la carpeta `public/` (o utilitza una imatge existent) i passa les variables de marca en compilar:
```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 .
```
La marca funciona tant en mode complet com en mode simple, i es pot combinar amb totes les altres opcions de compilació.
### Desactivar eines concretes
Amaga eines de la interfície per requisits de compliment o seguretat. Les eines desactivades s'eliminen de la pàgina d'inici, dels resultats de cerca, de les dreceres de teclat i del creador de fluxos de treball. L'accés directe per URL mostra una pàgina d'"eina no disponible".
Els ID d'eina són la URL de la pàgina sense `.html`. Per exemple, si l'eina és a `bentopdf.com/edit-pdf.html`, l'ID és `edit-pdf`.
#### Trobar els ID d'eina
La manera més fàcil: obre qualsevol eina de BentoPDF i mira la URL. L'última part de la ruta (sense `.html`) és l'ID de l'eina.
<details>
<summary>Llista completa d'ID d'eina</summary>
**Edita i anota:** `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`
**Converteix a 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`
**Converteix des de 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`
**Organitza i gestiona:** `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`
**Optimitza i repara:** `compress-pdf`, `pdf-to-pdfa`, `fix-page-size`, `linearize-pdf`, `remove-restrictions`, `repair-pdf`, `rasterize-pdf`, `deskew-pdf`, `font-to-outline`
**Seguretat:** `encrypt-pdf`, `sanitize-pdf`, `decrypt-pdf`, `flatten-pdf`, `remove-metadata`, `change-permissions`, `digital-sign-pdf`, `validate-signature-pdf`, `timestamp-pdf`
</details>
#### Opció 1: en compilar (argument de compilació de Docker)
```bash
docker build \
--build-arg DISABLE_TOOLS="edit-pdf,sign-pdf,encrypt-pdf" \
-t bentopdf .
```
Això incorpora la llista de desactivades al paquet de JavaScript. Per canviar-la cal tornar a compilar.
#### Opció 2: en execució (config.json)
Munta un fitxer `config.json` al directori servit, sense necessitat de tornar a compilar:
```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
```
O amb 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
```
Els dos mètodes es poden combinar: les llistes es fusionen. Si una eina apareix en qualsevol de les dues, es desactiva.
#### Desactivar funcions de l'editor
També pots desactivar funcions concretes de l'editor de PDF (p. ex., censura, anotacions, formularis) sense desactivar tota l'eina d'edició. Afegeix `editorDisabledCategories` al teu `config.json`:
```json
{
"editorDisabledCategories": ["redaction", "annotation-stamp"]
}
```
<details>
<summary>Llista completa de categories de l'editor</summary>
**Zoom:** `zoom`, `zoom-in`, `zoom-out`, `zoom-fit-page`, `zoom-fit-width`, `zoom-marquee`, `zoom-level`
**Anotació:** `annotation`, `annotation-markup`, `annotation-highlight`, `annotation-underline`, `annotation-strikeout`, `annotation-squiggly`, `annotation-ink`, `annotation-text`, `annotation-stamp`
**Formes:** `annotation-shape`, `annotation-rectangle`, `annotation-circle`, `annotation-line`, `annotation-arrow`, `annotation-polygon`, `annotation-polyline`
**Formulari:** `form`, `form-textfield`, `form-checkbox`, `form-radio`, `form-select`, `form-listbox`, `form-fill-mode`
**Censura:** `redaction`, `redaction-area`, `redaction-text`, `redaction-apply`, `redaction-clear`
**Document:** `document`, `document-open`, `document-close`, `document-print`, `document-capture`, `document-export`, `document-fullscreen`, `document-protect`
**Pàgina:** `page`, `spread`, `rotate`, `scroll`, `navigation`
**Tauler:** `panel`, `panel-sidebar`, `panel-search`, `panel-comment`
**Eines:** `tools`, `pan`, `pointer`, `capture`
**Selecció:** `selection`, `selection-copy`
**Historial:** `history`, `history-undo`, `history-redo`
</details>
Les categories són jeràrquiques: desactivar una categoria pare (p. ex., `annotation`) desactiva tots els seus fills.
### Desactivar el comptador d'estrelles de GitHub
La barra de navegació mostra el nombre d'estrelles de GitHub en directe, obtingut de `api.github.com` en carregar la pàgina. És l'única petició externa que no serveix una funció de PDF. Desactiva-ho en compilar per eliminar el codi de consulta del paquet i treure `api.github.com` de la Content-Security-Policy:
```bash
docker build --build-arg DISABLE_GITHUB_STARS=true -t bentopdf .
```
Les compilacions en mode simple (`bentopdf-simple`) sempre ometen el comptador d'estrelles: aquesta opció només cal a la versió comercial o a compilacions completes personalitzades.
### URL WASM personalitzades (aïllat de la xarxa / autoallotjat)
> [!IMPORTANT]
> Les URL WASM s'incorporen al JavaScript **en compilar**. Els fitxers WASM els descarrega el **navegador de l'usuari** en execució: Docker no els descarrega durant la compilació. En xarxes aïllades, has d'allotjar els fitxers WASM en un servidor intern al qual arribin els navegadors.
**Flux de treball complet en xarxa aïllada:**
```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
```
Utilitza per a `--ocr-languages` els codis que mostra `bash scripts/prepare-airgap.sh --list-ocr-languages`, o cerca per nom amb `bash scripts/prepare-airgap.sh --search-ocr-language <term>`. Quan compiles amb un subconjunt restringit d'OCR, passa els mateixos codis a `VITE_TESSERACT_AVAILABLE_LANGUAGES` perquè l'aplicació només mostri els idiomes inclosos. Per a una sortida d'OCR totalment sense connexió, allotja també el directori `ocr-fonts/` inclòs i fes que `VITE_OCR_FONT_BASE_URL` hi apunti.
Defineix una variable amb una cadena buida per desactivar aquest mòdul (els usuaris l'hauran de configurar manualment a la configuració avançada).
## Port personalitzat
Per defecte, BentoPDF escolta al port `8080` dins del contenidor. Per canviar-ho, defineix la variable d'entorn `PORT` en execució:
```bash
docker run -p 3000:9090 -e PORT=9090 ghcr.io/alam00000/bentopdf-simple:latest
```
| Variable | Descripció | Per defecte |
| -------- | ------------------------------------ | ----------- |
| `PORT` | Port d'escolta d'Nginx al contenidor | `8080` |
Això funciona tant amb el Dockerfile estàndard com amb el nonroot.
## ID d'usuari personalitzat (PUID/PGID)
Per a entorns que requereixen executar-se com un usuari concret que no sigui root (dispositius NAS, Kubernetes amb contextos de seguretat, polítiques de l'organització), BentoPDF ofereix un Dockerfile independent amb suport de PUID/PGID a l'estil LSIO.
### Compila i executa
```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
```
### Variables d'entorn
| Variable | Descripció | Per defecte |
| -------------- | ----------------------------- | ----------- |
| `PUID` | ID d'usuari amb què s'executa | `1000` |
| `PGID` | ID de grup amb què s'executa | `1000` |
| `PORT` | Port d'escolta d'Nginx | `8080` |
| `DISABLE_IPV6` | Desactiva l'escoltador IPv6 | `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
```
### Com funciona
El contenidor s'inicia com a root, crea un usuari amb el PUID/PGID especificat, ajusta la propietat de tots els directoris escrivibles i després retira els privilegis mitjançant `su-exec`. El procés d'nginx s'executa íntegrament com l'usuari que has indicat.
> [!NOTE]
> El `Dockerfile` estàndard utilitza `nginx-unprivileged` (UID 101) i es recomana per a la majoria de desplegaments. Utilitza `Dockerfile.nonroot` només quan necessitis un UID/GID concret.
> [!WARNING]
> PUID/PGID no poden ser `0` (root). El punt d'entrada valida els valors i sortirà amb un error si no són vàlids.
## Amb Traefik (proxy invers)
```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
```
## Amb Caddy (proxy invers)
```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"
}
```
## Límits de recursos
```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 (integració amb systemd)
[Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) permet executar contenidors de Podman com a serveis de systemd. És ideal per a desplegaments de producció en sistemes Linux.
### Configuració bàsica de Quadlet
Crea un fitxer d'unitat de contenidor a `~/.config/containers/systemd/bentopdf.container` (usuari) o `/etc/containers/systemd/bentopdf.container` (sistema):
```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
```
### Activa i inicia
```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]
> Per a un desplegament a nivell de sistema, utilitza `systemctl` sense l'opció `--user` i posa el fitxer a `/etc/containers/systemd/`.
### Quadlet amb comprovació d'estat
```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
```
### Actualització automàtica amb Quadlet
Podman pot actualitzar els contenidors automàticament quan hi ha imatges noves disponibles:
```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
```
### Configuració de xarxa de Quadlet
Per a una configuració de xarxa personalitzada, crea un fitxer de xarxa `bentopdf.network`:
```ini
[Network]
Subnet=10.89.0.0/24
Gateway=10.89.0.1
```
Després fes-hi referència al fitxer del contenidor:
```ini
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
Network=bentopdf.network
```
## Actualització
```bash
# Pull latest image
docker compose pull
# Recreate container
docker compose up -d
```
+266
View File
@@ -0,0 +1,266 @@
# Desplega a Hostinger
[Hostinger](https://hostinger.com) és un proveïdor d'allotjament compartit molt popular. Aquesta guia explica com desplegar BentoPDF a l'allotjament compartit de Hostinger.
## Requisits previs
- Pla d'allotjament de Hostinger amb accés al gestor de fitxers
- Node.js instal·lat en local per compilar
## Pas 1: compila el projecte
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
Els fitxers compilats seran a la carpeta `dist`.
## Pas 2: puja'l a Hostinger
### Desplegament al domini arrel
1. Inicia sessió al teu compte de Hostinger
2. Ves a **File Manager** → **public_html**
3. **Suprimeix** els fitxers existents (fes-ne una còpia de seguretat si cal)
4. **Puja** tot el contingut de la teva carpeta `dist` local a `public_html`
### Desplegament en un subdirectori
Si el desplegues en un subdirectori (p. ex., `yourdomain.com/pdf-tools/`):
1. Compila amb la URL base correcta:
```bash
BASE_URL=/pdf-tools/ npm run build
```
2. Crea la carpeta a Hostinger:
- Ves a **File Manager** → **public_html**
- Crea una carpeta nova: `pdf-tools`
3. Puja tot el contingut de `dist` a `public_html/pdf-tools/`
## Pas 3: crea el fitxer .htaccess
Crea un fitxer `.htaccess` a l'arrel de la carpeta de desplegament (`public_html` o `public_html/pdf-tools/`):
::: warning Important
Per al desplegament en un subdirectori, canvia `RewriteBase /` per `RewriteBase /pdf-tools/` (o el nom de la teva carpeta).
:::
```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
```
## Exemple de .htaccess per a un subdirectori
Per a `yourdomain.com/pdf-tools/`, actualitza aquestes línies:
```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
```
## Resolució de problemes
### Els fitxers WASM no es carreguen
Assegura't que els tipus MIME estan ben definits a `.htaccess`:
```apache
AddType application/wasm .wasm
```
### Les eines de LibreOffice no funcionen
Les capçaleres de seguretat són imprescindibles per a SharedArrayBuffer:
```apache
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
```
Si les capçaleres no s'apliquen, contacta amb el suport de Hostinger perquè activin `mod_headers`.
### Errors 404 en actualitzar la pàgina
Assegura't que la regla de l'extensió `.html` i les rutes d'idioma estan ben configurades. BentoPDF utilitza fitxers HTML estàtics, no encaminament SPA:
```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]
```
### Límits de càrrega de fitxers
Hostinger pot tenir límits de mida de fitxer. Puja els fitxers WASM per separat si la càrrega en bloc falla.
+538
View File
@@ -0,0 +1,538 @@
# Guia d'autoallotjament
BentoPDF es pot autoallotjar a la teva pròpia infraestructura. Aquesta guia cobreix diverses opcions de desplegament.
## Inici ràpid amb Docker / Podman
La manera més ràpida d'autoallotjar BentoPDF:
> [!TIP]
> BentoPDF es distribueix en dues compilacions:
>
> - **Compilació autoallotjada**: `ghcr.io/alam00000/bentopdf-simple:latest`. Totes les eines de PDF, **sense** l'embolcall de màrqueting (sense secció principal, preguntes freqüents, testimonis ni peu de pàgina). Fes-la servir per a desplegaments interns, d'equip o d'organització. **No** és una versió "lite" amb menys funcions.
> - **Compilació comercial**: `ghcr.io/alam00000/bentopdf:latest`. El lloc de màrqueting complet, que fan servir el mateix bentopdf.com i els titulars de llicència comercial que executen desplegaments de cara al públic.
>
> Si tens dubtes: descarrega la compilació autoallotjada.
> [!IMPORTANT]
> La conversió de fitxers d'Office requereix `SharedArrayBuffer`, cosa que vol dir que l'aplicació ha d'estar aïllada entre orígens i servida des d'un context segur. La imatge oficial ja envia les capçaleres COOP/COEP necessàries, però els navegadors encara desactiven `SharedArrayBuffer` en orígens HTTP simples de la xarxa local, com `http://192.168.x.x`.
>
> Utilitza `http://localhost` només per a proves al mateix dispositiu. Si els usuaris accedeixen a BentoPDF mitjançant una IP o un nom d'amfitrió de la xarxa local, finalitza la connexió amb 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
```
O amb 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)
Executa BentoPDF com a servei de systemd. Crea `~/.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
```
Consulta la [guia de desplegament amb Docker](/ca/self-hosting/docker) per a la documentació completa de Quadlet.
## Compilació des del codi font
```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
```
## Opcions de configuració
### Compilació autoallotjada (mode simple)
La compilació autoallotjada (la imatge `bentopdf-simple`, també anomenada mode simple) és **funcionalment idèntica** a la compilació comercial: totes les eines de PDF hi són i es comporten igual. Només amaga l'embolcall de màrqueting, que només té sentit al mateix bentopdf.com o en un desplegament comercial de cara al públic. **No és una versió reduïda ni "lite".**
**Què amaga la compilació autoallotjada** (només cosmètic, no s'elimina cap funció de PDF):
- Barra de navegació, secció principal, secció de característiques, preguntes freqüents, testimonis i peu de pàgina
- Actualitza el títol de la pàgina a "Eines PDF"
**Què conserva la compilació autoallotjada** (tot el que realment treballa amb PDF):
- Totes les eines de PDF (fusionar, dividir, editar, signar, OCR, conversió d'Office, etc.)
- Compatibilitat amb marca personalitzada i tota la configuració en temps de compilació i d'execució
La compilació comercial (`ghcr.io/alam00000/bentopdf:latest`) és la que fa funcionar el mateix bentopdf.com i la fan servir els titulars de llicència comercial que executen desplegaments de cara al públic: afegeix la secció principal, les preguntes freqüents, els testimonis i el peu de pàgina, que no tindrien sentit en una eina interna.
Si autoallotges BentoPDF per al teu equip, la teva organització o com a eina interna, descarrega la compilació autoallotjada:
```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
```
Consulta [SIMPLE_MODE.md](https://github.com/alam00000/bentopdf/blob/main/SIMPLE_MODE.md) per a més detalls.
### Compilació comercial (desplegaments de cara al públic amb la teva pròpia marca)
La compilació comercial (la imatge `bentopdf`, sense el sufix `-simple`) és la que fa funcionar el mateix bentopdf.com. Inclou el lloc de màrqueting complet (secció principal, característiques, preguntes freqüents, testimonis i peu de pàgina) a més de totes les eines de PDF. Fes servir aquesta compilació quan vulguis **desplegar BentoPDF com a servei de PDF de cara al públic sota la teva pròpia marca**, per exemple:
- Executes BentoPDF com a SaaS allotjat per a usuaris finals al teu propi domini
- Vols l'experiència de pàgina d'aterratge (seccions de màrqueting + eines), no només la superfície nua de les eines
- Ets titular d'una llicència comercial i incrustes BentoPDF en un producte comercial
**Executa'l tal com és** (amb la marca BentoPDF, útil per previsualitzar la compilació):
```bash
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
```
**Compila amb la teva pròpia marca** (el flux de treball comercial habitual):
```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
```
Totes les altres opcions de compilació (`BASE_URL`, `VITE_DEFAULT_LANGUAGE`, `DISABLE_TOOLS`, substitució d'URL de WASM, etc.) funcionen igual que a la compilació autoallotjada.
::: warning Llicències
Executar la compilació comercial està permès amb qualsevol de les dues opcions de llicència de BentoPDF:
- **AGPL-3.0** (gratuïta): permesa si el teu desplegament publica tot el seu codi font sota AGPL, incloses les modificacions de marca i la lògica de negoci que l'envolta.
- **Llicència comercial** (79 $ de per vida): necessària per a desplegaments de codi tancat o propietaris en què no publiques el codi del teu fork de marca ni del teu codi de negoci.
Consulta la [pàgina de llicències](https://www.bentopdf.com/licensing) per a la comparació completa. Els mòduls WASM amb llicència AGPL (PyMuPDF, Ghostscript, CoherentPDF) es carreguen des d'un CDN en temps d'execució, de manera que no entren a la teva imatge i no canvien la teva situació de llicència.
:::
### URL base
Desplega en un subdirectori:
```bash
BASE_URL=/pdf-tools/ npm run build
```
### Marca personalitzada
Substitueix el logotip, el nom i el text del peu de pàgina per defecte de BentoPDF pels teus en temps de compilació:
| Variable | Descripció | Per defecte |
| ------------------ | --------------------------------------------------------- | --------------------------------------- |
| `VITE_BRAND_NAME` | Nom de marca mostrat a la capçalera i al peu | `BentoPDF` |
| `VITE_BRAND_LOGO` | Ruta del logotip relativa a `public/` | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | Text personalitzat del peu de pàgina o dels drets d'autor | `© 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
```
O amb 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 .
```
La marca funciona tant en mode complet com en mode simple, i es pot combinar amb totes les altres opcions de compilació (`BASE_URL`, `SIMPLE_MODE`, `VITE_DEFAULT_LANGUAGE`).
### Desactiva eines concretes
Amaga eines individuals per motius de compliment normatiu o de seguretat. Les eines desactivades s'eliminen de la pàgina d'inici, la cerca, les dreceres, el constructor de fluxos de treball i l'accés directe per URL.
Els identificadors d'eina són l'URL de la pàgina sense `.html`: obre qualsevol eina i mira l'URL (per exemple, `edit-pdf`, `sign-pdf`, `encrypt-pdf`).
**En temps de compilació** (integrat al paquet):
```bash
DISABLE_TOOLS="edit-pdf,sign-pdf" npm run build
```
**En temps d'execució** (sense recompilar: munta un `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
```
Els dos mètodes es poden combinar: les llistes es fusionen.
També pots desactivar funcions concretes de l'Editor de PDF (per exemple, la censura) sense desactivar tota l'eina, afegint `editorDisabledCategories` a `config.json`. Consulta la [guia de Docker](/ca/self-hosting/docker#desactivar-funcions-de-l-editor) per veure la llista completa de categories.
## Guies de desplegament
Tria la teva plataforma:
- [Vercel](/ca/self-hosting/vercel)
- [Netlify](/ca/self-hosting/netlify)
- [Cloudflare Pages](/ca/self-hosting/cloudflare)
- [AWS S3 + CloudFront](/ca/self-hosting/aws)
- [Hostinger](/ca/self-hosting/hostinger)
- [Nginx](/ca/self-hosting/nginx)
- [Apache](/ca/self-hosting/apache)
- [Docker](/ca/self-hosting/docker)
- [Kubernetes](/ca/self-hosting/kubernetes)
- [Servidor intermediari CORS](/ca/self-hosting/cors-proxy): necessari per a les signatures digitals
## Problemes habituals
### La conversió de Word / ODT / Excel / PowerPoint a PDF es penja (SharedArrayBuffer no disponible)
**Símptoma**: les conversions de documents basades en LibreOffice (Word, ODT, Excel, PowerPoint a PDF) es pengen cap al 55 % o no s'arriben a iniciar. La consola del navegador pot mostrar:
> `ReferenceError: SharedArrayBuffer is not defined`
...o `window.crossOriginIsolated` retorna `false`, o la compilació de WASM informa `expected magic word 00 61 73 6d, found 1f 8b 08 08`.
**Causa**: LibreOffice WASM requereix `SharedArrayBuffer`, que el navegador només activa quan la pàgina està **aïllada entre orígens** I es serveix des d'un **context segur**. Això vol dir que s'han de complir dues coses:
1. Totes les respostes inclouen les dues capçaleres:
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
2. La pàgina es serveix des de `https://...` o `http://localhost`. HTTP simple en una IP de la xarxa local (p. ex. `http://192.168.x.x`) NO es considera segur: els navegadors hi desactiven `SharedArrayBuffer`.
La discrepància `00 61 73 6d / 1f 8b 08 08` és un subsímptoma a part: als fitxers precomprimits `.wasm.gz` / `.data.gz` els falta la capçalera de resposta `Content-Encoding: gzip`, de manera que el navegador rep bytes gzip en brut en comptes de WASM descomprimit.
**Solució**: consulta la guia de desplegament de la teva plataforma per veure la configuració exacta:
- [Nginx →](/ca/self-hosting/nginx#la-conversio-de-word-odt-excel-a-pdf-no-funciona)
- [Apache →](/ca/self-hosting/apache#la-conversio-de-word-odt-excel-a-pdf-no-funciona)
- [AWS S3 + CloudFront →](/ca/self-hosting/aws#pas-3b-politica-de-capcaleres-de-resposta-obligatoria-per-a-libreoffice-wasm)
- [Cloudflare Pages →](/ca/self-hosting/cloudflare#fitxer-de-configuracio) (`_headers` file)
- [Netlify →](/ca/self-hosting/netlify#la-conversio-de-word-odt-excel-a-pdf-es-queda-al-55) (`netlify.toml`)
- [Vercel →](/ca/self-hosting/vercel#la-conversio-de-word-odt-excel-a-pdf-no-funciona) (`vercel.json`)
- [Hostinger →](/ca/self-hosting/hostinger#les-eines-de-libreoffice-no-funcionen) (`.htaccess`)
- [Kubernetes →](/ca/self-hosting/kubernetes#comprova-que-les-capcaleres-de-sharedarraybuffer-continuen-funcionant-ingress-gateway)
- **Docker**: ho gestiona automàticament la configuració de nginx inclosa, no cal fer res.
**Verificació**: obre la consola de DevTools en qualsevol pàgina de BentoPDF i executa:
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
Si la pàgina és HTTPS o `http://localhost` I les dues capçaleres COEP/COOP són presents a totes les respostes, les dues comprovacions passen. Si ets a `http://192.168.x.x` o a un altre origen HTTP que no sigui de loopback, finalitza la connexió amb HTTPS: no hi ha cap solució només amb capçaleres.
### Fitxers `.mjs` servits com a `application/octet-stream`
**Símptoma**: Signa PDF, l'Emplenador de formularis i algunes altres eines mostren un visualitzador en blanc o no es carreguen. La consola del navegador informa:
> `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.`
**Causa**: el teu servidor web o servidor intermediari invers no té cap assignació de tipus MIME per als fitxers `.mjs` (el visualitzador de PDF incorporat inclou mòduls ES amb aquesta extensió). Moltes configuracions de servidor estàndard fan servir `application/octet-stream` per a extensions no reconegudes, i els navegadors es neguen a executar-ho com a mòduls ES.
**Solució**: consulta la guia de desplegament de la teva plataforma per veure el fragment exacte:
- [Nginx →](/ca/self-hosting/nginx#signa-pdf-o-l-emplenador-de-formularis-mostra-un-visualitzador-en-blanc-error-mime-de-mjs)
- [Apache →](/ca/self-hosting/apache#signa-pdf-o-el-emplenador-de-formularis-mostra-un-visor-en-blanc-error-de-mime-amb-mjs)
- [AWS S3 + CloudFront →](/ca/self-hosting/aws#pas-2-compila-i-puja) (consulta `aws s3 cp ... --include "*.mjs"`)
- [Kubernetes →](/ca/self-hosting/kubernetes#errors-de-tipus-mime-de-mjs-iframe-en-blanc-a-signa-pdf-emplenador-de-formularis)
- **Docker, Vercel, Netlify, Cloudflare Pages, Hostinger**: es gestiona automàticament, no cal fer res.
**Verificació**: obre DevTools → pestanya Network, troba la petició `.mjs` que falla i comprova la capçalera de resposta `Content-Type`. Hauria de ser `application/javascript`. Si encara és `application/octet-stream`, és possible que un servidor intermediari invers o un CDN exterior estigui tornant a detectar el tipus: revisa cada capa de la teva cadena de servei.
## Configuració de WASM (components AGPL)
BentoPDF **no inclou** al seu codi font les biblioteques de processament amb llicència AGPL, però **preconfigura URL de CDN** perquè totes les funcions funcionin de sèrie, sense configuració manual.
::: tip Sense configuració per defecte
Des de la v2.0.0, els mòduls WASM estan preconfigurats per carregar-se des del CDN de jsDelivr mitjançant variables d'entorn. Totes les funcions avançades funcionen de seguida sense cap configuració per part de l'usuari.
:::
| Component | Llicència | Funcions |
| --------------- | --------- | -------------------------------------------------------------------- |
| **PyMuPDF** | AGPL-3.0 | Conversió d'EPUB/MOBI/FB2/XPS, extracció d'imatges i de taules |
| **Ghostscript** | AGPL-3.0 | Conversió a PDF/A, compressió, correcció d'inclinació, rasterització |
| **CoherentPDF** | AGPL-3.0 | Taula de continguts, adjunts, fusió de PDF amb marcadors |
### Variables d'entorn per defecte
Estan definides a `.env.production` i integrades a la compilació:
```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=
```
### Substitució de les URL de WASM
Pots substituir els valors per defecte en temps de compilació per a desplegaments personalitzats:
```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
```
Per desactivar un mòdul del tot (cal que l'usuari el configuri manualment a Configuració avançada), posa la seva variable a una cadena buida.
Per a l'OCR, deixa totes les variables `VITE_TESSERACT_*` buides i conserva els recursos en línia per defecte, o defineix alhora les URL de worker, core i lang per a un OCR autoallotjat o sense connexió. Si només incloses idiomes d'OCR concrets, defineix també `VITE_TESSERACT_AVAILABLE_LANGUAGES` amb els mateixos codis separats per comes perquè la interfície només ofereixi els idiomes instal·lats i les seleccions no admeses fallin amb un error descriptiu. Per obtenir PDF cercables totalment sense connexió, defineix també `VITE_OCR_FONT_BASE_URL` amb el directori intern que serveix els tipus de lletra d'OCR inclosos.
Els usuaris també poden substituir aquests valors per defecte en qualsevol moment a **Configuració avançada** de la interfície: les substitucions de l'usuari desades al navegador tenen prioritat sobre els valors per defecte de l'entorn.
### Desplegament aïllat de la xarxa / sense connexió
Per a xarxes sense accés a Internet (govern, sanitat, finances, etc.). Les URL de WASM s'integren al JavaScript en **temps de compilació**; els fitxers WASM reals els baixa el **navegador de l'usuari** en temps d'execució. Per tant, has de preparar-ho tot en una màquina amb Internet i després transferir-ho a la xarxa aïllada.
#### Script automatitzat (recomanat)
L'script `prepare-airgap.sh` inclòs automatitza tot el procés: descarrega els paquets WASM, compila la imatge de Docker i genera un paquet autocontingut amb un script de configuració.
```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
```
Això genera un directori de paquet:
```
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
```
Transfereix el paquet a la xarxa aïllada per USB, un repositori d'artefactes intern o un mètode aprovat. Després executa l'script de configuració inclòs:
```bash
cd bentopdf-airgap-bundle
bash setup.sh
```
L'script de configuració carrega la imatge de Docker, extreu els fitxers WASM i, opcionalment, inicia el contenidor.
**Opcions de l'script:**
| Indicador | Descripció | Per defecte |
| ------------------------------ | --------------------------------------------------------- | ---------------------------------- |
| `--wasm-base-url <url>` | On s'allotjaran els WASM internament | _(obligatori, es demana si falta)_ |
| `--image-name <name>` | Etiqueta de la imatge de Docker | `bentopdf` |
| `--output-dir <path>` | Directori de sortida del paquet | `./bentopdf-airgap-bundle` |
| `--simple-mode` | Activa el mode simple | desactivat |
| `--base-url <path>` | URL base del subdirectori (p. ex. `/pdf/`) | `/` |
| `--language <code>` | Idioma per defecte de la interfície (p. ex. `fr`, `de`) | _(cap)_ |
| `--brand-name <name>` | Nom de marca personalitzat | _(cap)_ |
| `--brand-logo <path>` | Ruta del logotip relativa a `public/` | _(cap)_ |
| `--footer-text <text>` | Text personalitzat del peu de pàgina | _(cap)_ |
| `--ocr-languages <list>` | Idiomes d'OCR a incloure, separats per comes | `eng` |
| `--list-ocr-languages` | Mostra els codis i noms d'OCR admesos i surt | desactivat |
| `--search-ocr-language <term>` | Cerca codis d'OCR per nom o abreviatura | desactivat |
| `--dockerfile <path>` | Dockerfile que s'utilitzarà | `Dockerfile` |
| `--skip-docker` | Omet la compilació i l'exportació de Docker | desactivat |
| `--skip-wasm` | Omet la baixada de WASM (reutilitza els `.tgz` existents) | desactivat |
El mode interactiu també accepta `list` per mostrar la llista completa de codis de Tesseract admesos i `search <term>` per trobar coincidències, com `search german` o `search chi`.
::: warning Requisit del mateix origen
Els fitxers WASM s'han de servir des del **mateix origen** que l'aplicació BentoPDF. Els Web Workers fan servir `importScripts()`, que no pot carregar scripts d'un altre origen. Per exemple, si BentoPDF s'executa a `https://internal.example.com`, l'URL base de WASM també ha de ser `https://internal.example.com/wasm`.
:::
#### Passos manuals
<details>
<summary>Si prefereixes fer-ho manualment sense l'script</summary>
**Pas 1: baixa els paquets WASM i d'OCR** (en una màquina amb 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
```
**Pas 2: compila la imatge de Docker amb les URL internes**
```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 .
```
**Pas 3: exporta la imatge de Docker**
```bash
docker save bentopdf -o bentopdf.tar
```
**Pas 4: transfereix-ho a la xarxa aïllada**
Copia-ho per USB, un repositori d'artefactes intern o un mètode de transferència aprovat:
- `bentopdf.tar`: la imatge de Docker
- Els cinc paquets `.tgz` de WASM/OCR del pas 1
- El directori `tesseract-langdata/` del pas 1
- El directori `ocr-fonts/` del pas 1
**Pas 5: configura-ho dins de la xarxa aïllada**
```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
```
Assegura't que els fitxers són accessibles a les URL que has configurat al pas 2, incloses `.../ocr/worker.min.js`, `.../ocr/core`, `.../ocr/lang-data` i `.../ocr/fonts`.
</details>
::: info Compiles des del codi font en comptes de fer servir Docker?
Defineix les variables a `.env.production` abans d'executar `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
```
:::
### Allotja el teu propi servidor intermediari de WASM
Si necessites servir fitxers WASM AGPL amb les capçaleres CORS adequades, pots desplegar un servidor intermediari senzill. Consulta la [guia del servidor intermediari de WASM de Cloudflare](https://github.com/alam00000/bentopdf/blob/main/cloudflare/WASM-PROXY.md) per veure una implementació d'exemple.
::: tip Per què separat?
Aquesta separació garanteix:
- Un compliment legal clar per als usuaris comercials
- El nucli de BentoPDF es manté sota la seva llicència dual (AGPL-3.0 / comercial)
- Els fitxers WASM es carreguen en temps d'execució, no s'inclouen al codi font
:::
## Requisits del sistema
| Requisit | Mínim |
| -------------- | --------------------------------- |
| Emmagatzematge | ~100 MB (nucli sense mòduls AGPL) |
| RAM | 512 MB |
| CPU | Qualsevol processador modern |
::: tip
BentoPDF és un lloc estàtic: no cal cap base de dades ni servidor de backend!
:::
+211
View File
@@ -0,0 +1,211 @@
# Desplega amb Kubernetes
Kubernetes pot ser excessiu per a un lloc estàtic, però pot encaixar molt bé si ja estandarditzes amb Helm + GitOps.
> [!IMPORTANT]
> **Capçaleres necessàries per a la conversió de fitxers d'Office**
>
> Les eines basades en LibreOffice (conversió de Word, Excel i PowerPoint) requereixen aquestes capçaleres HTTP per a la compatibilitat amb `SharedArrayBuffer`:
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> Les imatges nginx oficials de BentoPDF inclouen aquestes capçaleres. A Kubernetes, **els controladors d'Ingress/Gateway també són servidors intermediaris inversos**, així que assegura't que aquestes capçaleres es conservin (o afegeix-les a l'edge).
## Requisits previs
- Clúster de Kubernetes
- Helm v3
- Una imatge nginx de BentoPDF (per exemple, `ghcr.io/alam00000/bentopdf:<tag>`) que serveixi pel **port 8080**
## Desplega amb Helm
### Instal·la des d'aquest repositori (chart local)
```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
```
### Instal·la des de GHCR (chart OCI)
Si el chart es publica a GHCR com a artefacte OCI:
```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
```
## Exposa'l
### Port-forward (prova ràpida)
```bash
kubectl -n bentopdf port-forward deploy/bentopdf 8080:8080
```
### Ingress (opcional)
Activa l'Ingress (exemple per a nginx-ingress):
```yaml
ingress:
enabled: true
className: nginx
hosts:
- host: pdf.example.com
paths:
- path: /
pathType: Prefix
```
### Gateway API (opcional)
Aquest chart admet `Gateway` + `HTTPRoute` de Gateway API.
Exemple (operador de Gateway API de Cloudflare):
```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
```
## Comprova que les capçaleres de SharedArrayBuffer continuen funcionant (Ingress/Gateway)
### Què hauria de passar
La configuració de nginx de BentoPDF estableix les capçaleres de resposta necessàries. La majoria de controladors d'Ingress/Gateway **deixen passar les capçaleres de resposta de l'origen sense canvis**.
### Què ho pot trencar
- Una política del controlador o de l'edge que **sobreescrigui** o **elimini** capçaleres de resposta
- Un middleware de "capçaleres de seguretat" que estableixi valors de COOP/COEP diferents
### Com verificar-ho
Executa això contra el teu punt d'accés públic:
```bash
curl -I https://pdf.example.com/ | egrep -i 'cross-origin-opener-policy|cross-origin-embedder-policy'
```
Hauries de veure:
- `Cross-Origin-Opener-Policy: same-origin`
- `Cross-Origin-Embedder-Policy: require-corp`
### Si el teu controlador d'Ingress no les conserva
Afegeix les capçaleres a l'edge (depèn del controlador). Exemple per a **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;
```
## Errors de tipus MIME de `.mjs` (iframe en blanc a Signa PDF / Emplenador de formularis)
Si la consola del navegador mostra `Failed to load module script: ... non-JavaScript MIME type "application/octet-stream"` en una petició `.mjs`, un controlador d'Ingress/Gateway situat davant del nginx de BentoPDF està substituint la capçalera `Content-Type` de l'origen per `application/octet-stream`.
El nginx de la imatge de BentoPDF ja serveix `.mjs` com a `application/javascript`. La solució és evitar que el controlador l'elimini o la substitueixi. Per a nginx-ingress:
```yaml
ingress:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
location ~* \.mjs$ {
types {} default_type application/javascript;
}
```
O utilitza un filtre `ResponseHeaderModifier` a Gateway API per establir `Content-Type: application/javascript` per a les rutes `.mjs`. Verifica-ho amb DevTools → pestanya Network: la petició `.mjs` que fallava ara hauria de mostrar `content-type: application/javascript`.
### Si utilitzes Gateway API i vols forçar l'addició de capçaleres
Gateway API admet un filtre `ResponseHeaderModifier`. Pots afegir-lo a `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
```
La compatibilitat amb filtres concrets depèn del teu controlador de Gateway; si un filtre s'ignora, afegeix les capçaleres a la capa d'edge o de controlador.
## Desactiva eines concretes
Utilitza un ConfigMap per desactivar eines en temps d'execució sense reconstruir la imatge:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: bentopdf-config
namespace: bentopdf
data:
config.json: |
{
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
}
```
Munta'l al directori servit:
```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
```
Els identificadors d'eina són l'URL de la pàgina sense `.html`: obre qualsevol eina i mira l'URL (per exemple, `edit-pdf`, `merge-pdf`, `compress-pdf`). Les eines desactivades s'amaguen de la pàgina d'inici, la cerca, les dreceres, el constructor de fluxos de treball i l'accés directe per URL. Consulta la [guia de Docker](/ca/self-hosting/docker#desactivar-eines-concretes) per veure la llista completa d'opcions.
+139
View File
@@ -0,0 +1,139 @@
# Desplega a Netlify
[Netlify](https://netlify.com) ofereix un excel·lent allotjament de llocs estàtics amb un nivell gratuït generós.
## Desplegament amb un clic
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/alam00000/bentopdf)
## Desplegament manual
### Pas 1: connecta el repositori
1. Inicia sessió a [Netlify](https://app.netlify.com)
2. Fes clic a "Add new site" → "Import an existing project"
3. Connecta el teu compte de GitHub
4. Selecciona el teu fork de BentoPDF
### Pas 2: configura la compilació
| Paràmetre | Valor |
| ----------------------- | --------------- |
| Ordre de compilació | `npm run build` |
| Directori de publicació | `dist` |
| Versió de Node | 18+ |
### Pas 3: desplega
Fes clic a "Deploy site" i espera que acabi la compilació.
## Fitxer de configuració
Crea `netlify.toml` a l'arrel del projecte:
```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 Important
Les capçaleres `Cross-Origin-Embedder-Policy` i `Cross-Origin-Opener-Policy` són necessàries per a les conversions de Word/ODT/Excel/PowerPoint a PDF. Sense elles, `SharedArrayBuffer` no està disponible i el motor WASM de LibreOffice no es podrà inicialitzar.
Les capçaleres `Content-Encoding: gzip` als fitxers `.wasm.gz` i `.data.gz` indiquen al navegador que els descomprimeixi automàticament. Sense elles, el navegador rep bytes gzip en brut i la compilació de WASM falla.
:::
## Variables d'entorn
Defineix-les a Site settings → Environment variables:
| Variable | Descripció |
| ----------------------- | ------------------------------------------------------------------- |
| `SIMPLE_MODE` | Posa-ho a `true` per a una compilació mínima |
| `VITE_BRAND_NAME` | Nom de marca personalitzat (substitueix "BentoPDF") |
| `VITE_BRAND_LOGO` | Ruta del logotip relativa a `public/` (p. ex. `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | Text personalitzat del peu de pàgina o dels drets d'autor |
| `VITE_DEFAULT_LANGUAGE` | Idioma per defecte de la interfície (p. ex. `fr`, `de`, `es`) |
## Domini personalitzat
1. Ves a Site settings → Domain management
2. Fes clic a "Add custom domain"
3. Segueix les instruccions de configuració del DNS
## Fitxers multimèdia grans
Per als fitxers WASM grans, considera activar [Netlify Large Media](https://docs.netlify.com/large-media/overview/):
```bash
netlify lm:setup
git lfs track "*.wasm"
```
## Resolució de problemes
### La conversió de Word/ODT/Excel a PDF es queda al 55 %
Si les conversions de documents es queden penjades al 55 %, obre la consola de DevTools i comprova:
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
Si `crossOriginIsolated` és `false`, les capçaleres COEP/COOP del teu `netlify.toml` no s'estan aplicant. Assegura't que el fitxer és a l'arrel del projecte i torna a desplegar.
Si veus `expected magic word 00 61 73 6d, found 1f 8b 08 08` a la consola, als fitxers `.wasm.gz` els falten les capçaleres `Content-Encoding: gzip`. Assegura't que els blocs `[[headers]]` de `soffice.wasm.gz` i `soffice.data.gz` són al teu `netlify.toml`.
### La compilació falla
Comprova la compatibilitat de la versió de Node:
```toml
[build.environment]
NODE_VERSION = "20"
```
### Càrrega inicial lenta
Activa l'optimització d'actius a Site settings → Build & deploy → Asset optimization.
+232
View File
@@ -0,0 +1,232 @@
# Desplega amb Nginx
Allotja BentoPDF al teu propi servidor amb Nginx.
## Requisits previs
- Servidor Ubuntu/Debian
- Nginx instal·lat
- Certificat SSL (recomanat: Let's Encrypt)
## Pas 1: compila el projecte
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
Per personalitzar la marca, defineix variables d'entorn abans de compilar:
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## Pas 2: copia els fitxers
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## Pas 3: configuració de Nginx
Crea `/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;
}
}
```
## Pas 4: activa el lloc
```bash
sudo ln -s /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
## Pas 5: SSL amb Let's Encrypt
```bash
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
```
## Desplegament en un subdirectori
Per allotjar-lo a `/pdf/`:
```nginx
location /pdf/ {
alias /var/www/bentopdf/;
try_files $uri $uri/ /pdf/index.html;
}
```
Compila amb:
```bash
BASE_URL=/pdf/ npm run build
```
## Ajust del rendiment
Afegeix a `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;
}
```
## Resolució de problemes
### WASM no es carrega
Assegura't que el tipus MIME està definit:
```nginx
types {
application/wasm wasm;
}
```
### Signa PDF o l'Emplenador de formularis mostra un visualitzador en blanc (error MIME de `.mjs`)
Si la consola del navegador mostra:
```
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.
```
...i la petició que falla acaba en `.mjs`, al teu nginx li falta l'assignació `.mjs → application/javascript`. El visualitzador de PDF incorporat (que fan servir Signa PDF, l'Emplenador de formularis i algunes altres eines) inclou fitxers de mòdul ES amb extensió `.mjs`, i el `mime.types` estàndard de nginx no els inclou per defecte.
Per solucionar-ho, afegeix `mjs` a la línia existent d'`application/javascript` de `/etc/nginx/mime.types`:
```nginx
application/javascript js mjs;
```
> NO afegeixis una _segona_ línia `application/javascript`: nginx analitza `mime.types` línia per línia i només guanya una assignació de tipus a extensió, així que una segona línia s'ignora en silenci en algunes versions. Edita la línia existent per afegir `mjs` al costat de `js`.
O sobreescriu-ho localment dins del bloc server (sense editar el fitxer global):
```nginx
types {
application/javascript js mjs;
application/wasm wasm;
}
```
Torna a carregar nginx després del canvi:
```bash
sudo nginx -t && sudo systemctl reload nginx
```
Per confirmar-ho: a DevTools del navegador → pestanya Network, troba la petició `.mjs` que fallava i comprova la capçalera de resposta `Content-Type`. Ara hauria de ser `application/javascript`, no `application/octet-stream`.
### La conversió de Word/ODT/Excel a PDF no funciona
LibreOffice WASM requereix `SharedArrayBuffer`, que necessita les capçaleres `Cross-Origin-Embedder-Policy` i `Cross-Origin-Opener-Policy`. També necessita un context segur, així que `http://localhost` funciona per a proves locals, però `http://192.168.x.x` o altres IP de la xarxa local normalment requereixen HTTPS. Tingues en compte que les directives `add_header` de nginx dins d'un bloc `location` **sobreescriuen** les directives `add_header` del nivell de servidor: no es combinen. Cada bloc `location` amb el seu propi `add_header` ha d'incloure les capçaleres COEP/COOP.
Verifica-ho amb:
```bash
curl -I https://your-domain.com/ | grep -i cross-origin
```
Si fas servir un servidor intermediari invers davant de nginx, assegura't que conservi aquestes capçaleres.
### 502 Bad Gateway
Comprova els registres d'errors de Nginx:
```bash
sudo tail -f /var/log/nginx/error.log
```
+122
View File
@@ -0,0 +1,122 @@
# Desplega a Vercel
[Vercel](https://vercel.com) ofereix l'experiència de desplegament més senzilla per a llocs estàtics.
## Desplegament amb un clic
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/alam00000/bentopdf)
## Desplegament manual
### Pas 1: fes un fork del repositori
Fes un fork de [bentopdf/bentopdf](https://github.com/alam00000/bentopdf) al teu compte de GitHub.
### Pas 2: importa'l a Vercel
1. Ves a [vercel.com/new](https://vercel.com/new)
2. Selecciona el repositori del teu fork
3. Configura el projecte:
| Paràmetre | Valor |
| ---------------- | --------------- |
| Framework Preset | Vite |
| Build Command | `npm run build` |
| Output Directory | `dist` |
| Install Command | `npm install` |
### Pas 3: variables d'entorn (opcional)
Afegeix-les si cal:
| Variable | Descripció |
| ----------------------- | ------------------------------------------------------------------- |
| `SIMPLE_MODE` | Posa-ho a `true` per a una interfície mínima |
| `VITE_BRAND_NAME` | Nom de marca personalitzat (substitueix "BentoPDF") |
| `VITE_BRAND_LOGO` | Ruta del logotip relativa a `public/` (p. ex. `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | Text personalitzat del peu de pàgina o dels drets d'autor |
| `VITE_DEFAULT_LANGUAGE` | Idioma per defecte de la interfície (p. ex. `fr`, `de`, `es`) |
### Pas 4: desplega
Fes clic a "Deploy" i espera que s'acabi la compilació.
## Domini personalitzat
1. Ves a la configuració del projecte
2. Navega a "Domains"
3. Afegeix el teu domini personalitzat
4. Configura el DNS segons les instruccions
## Limitacions
::: warning Fitxers grans
Les funcions serverless de Vercel tenen un límit de 50 MB. Com que BentoPDF és un lloc estàtic, això no hauria d'afectar-te, però els mòduls WASM són grans (~100 MB en total). Assegura't que es serveixin des de la carpeta `/public`.
:::
## Resolució de problemes
### Temps d'espera de la compilació esgotat
Si les compilacions s'esgoten, prova això:
```json
// vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": "dist"
}
```
### 404 en actualitzar la pàgina
Afegeix un `vercel.json` per a l'encaminament SPA:
```json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
```
### La conversió de Word/ODT/Excel a PDF no funciona
Les conversions de LibreOffice WASM requereixen `SharedArrayBuffer`, que necessita aquestes capçaleres de resposta a totes les pàgines:
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
Els fitxers precomprimits `.wasm.gz` i `.data.gz` també necessiten `Content-Encoding: gzip` perquè el navegador els descomprimeixi.
Afegeix-ho al teu `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" }
]
}
]
}
```
Per verificar-ho, obre la consola de DevTools i executa `console.log(window.crossOriginIsolated)`: hauria de retornar `true`.