mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-10 17:14:34 +01:00
feat: translate all pages, UI messages and docs and fix SEO redirects and indexing
This commit is contained in:
1 parent
5e019de5ae
commit
4edb1718cc
3344 files changed
+415643
-4759
No files matched your search
@@ -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
|
||||
```
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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!
|
||||
:::
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
[](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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
|
||||
[](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`.
|
||||
Reference in new issue
Block a user