mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-13 09:54:19 +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 @@
|
||||
# Distribuzione con Apache
|
||||
|
||||
Ospita BentoPDF usando Apache HTTP Server.
|
||||
|
||||
## Prerequisiti
|
||||
|
||||
- Apache 2.4+
|
||||
- mod_rewrite abilitato
|
||||
- Certificato SSL (consigliato)
|
||||
|
||||
## Passaggio 1: compila il progetto
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
Per personalizzare il marchio, imposta le variabili d'ambiente prima della build:
|
||||
|
||||
```bash
|
||||
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
|
||||
```
|
||||
|
||||
## Passaggio 2: copia i file
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/www/bentopdf
|
||||
sudo cp -r dist/* /var/www/bentopdf/
|
||||
sudo chown -R www-data:www-data /var/www/bentopdf
|
||||
```
|
||||
|
||||
## Passaggio 3: configurazione di 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>
|
||||
```
|
||||
|
||||
## Passaggio 4: .htaccess per il routing
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
## Passaggio 5: abilita i moduli necessari
|
||||
|
||||
```bash
|
||||
sudo a2enmod rewrite
|
||||
sudo a2enmod ssl
|
||||
sudo a2enmod headers
|
||||
sudo a2enmod expires
|
||||
sudo a2enmod deflate
|
||||
```
|
||||
|
||||
## Passaggio 6: abilita il sito
|
||||
|
||||
```bash
|
||||
sudo a2ensite bentopdf.conf
|
||||
sudo apache2ctl configtest
|
||||
sudo systemctl reload apache2
|
||||
```
|
||||
|
||||
## Distribuzione in una sottodirectory
|
||||
|
||||
Per ospitarlo in `/pdf/`:
|
||||
|
||||
1. Compila con l'URL di base:
|
||||
|
||||
```bash
|
||||
BASE_URL=/pdf/ npm run build
|
||||
```
|
||||
|
||||
2. Aggiorna `.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
|
||||
```
|
||||
|
||||
## Risoluzione dei problemi
|
||||
|
||||
### Errori 404 sui file WASM
|
||||
|
||||
Assicurati che il tipo MIME sia configurato:
|
||||
|
||||
```apache
|
||||
AddType application/wasm .wasm
|
||||
```
|
||||
|
||||
### Firma PDF o Compilazione moduli mostra un visualizzatore vuoto (errore MIME `.mjs`)
|
||||
|
||||
Se la console del browser 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.
|
||||
```
|
||||
|
||||
…e la richiesta che fallisce termina in `.mjs`, nella tua configurazione di Apache manca la mappatura `.mjs → application/javascript`. Il visualizzatore PDF incluso usa file ES module con estensione `.mjs`, che il `mime.types` predefinito di Apache non contiene.
|
||||
|
||||
Aggiungi al tuo VirtualHost o a `.htaccess`:
|
||||
|
||||
```apache
|
||||
AddType application/javascript .mjs
|
||||
```
|
||||
|
||||
Poi ricarica Apache:
|
||||
|
||||
```bash
|
||||
sudo apache2ctl configtest && sudo systemctl reload apache2
|
||||
```
|
||||
|
||||
Per verificare: negli DevTools del browser, scheda Network, trova la richiesta `.mjs` che falliva e controlla l'header di risposta `Content-Type`. Ora dovrebbe essere `application/javascript`, non `application/octet-stream`.
|
||||
|
||||
### Le regole di rewrite non funzionano
|
||||
|
||||
Verifica che mod_rewrite sia abilitato:
|
||||
|
||||
```bash
|
||||
sudo a2enmod rewrite
|
||||
```
|
||||
|
||||
### La conversione da Word/ODT/Excel a PDF non funziona
|
||||
|
||||
LibreOffice WASM richiede `SharedArrayBuffer`, che necessita di questi header:
|
||||
|
||||
```apache
|
||||
Header always set Cross-Origin-Embedder-Policy "require-corp"
|
||||
Header always set Cross-Origin-Opener-Policy "same-origin"
|
||||
```
|
||||
|
||||
Serve anche un contesto sicuro. `http://localhost` funziona per i test in locale, ma `http://192.168.x.x` o altri IP di rete locale di solito richiedono HTTPS. Se gli header sono presenti ma `window.crossOriginIsolated` è ancora `false`, controlla se la pagina viene aperta tramite HTTP semplice su un'origine diversa dal loopback.
|
||||
|
||||
Anche i file precompressi `.wasm.gz` e `.data.gz` richiedono un `Content-Encoding` corretto:
|
||||
|
||||
```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>
|
||||
```
|
||||
|
||||
Assicurati che `mod_headers` sia abilitato: `sudo a2enmod headers`
|
||||
|
||||
### Permesso negato
|
||||
|
||||
```bash
|
||||
sudo chown -R www-data:www-data /var/www/bentopdf
|
||||
sudo chmod -R 755 /var/www/bentopdf
|
||||
```
|
||||
@@ -0,0 +1,200 @@
|
||||
# Distribuzione su AWS S3 + CloudFront
|
||||
|
||||
Ospita BentoPDF su AWS per il massimo controllo e la massima scalabilità.
|
||||
|
||||
## Architettura
|
||||
|
||||
```
|
||||
User → CloudFront (CDN) → S3 (Static Files)
|
||||
```
|
||||
|
||||
## Passaggio 1: crea il bucket 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
|
||||
```
|
||||
|
||||
## Passaggio 2: compila e carica
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Passaggio 3: crea la distribuzione CloudFront
|
||||
|
||||
```bash
|
||||
aws cloudfront create-distribution \
|
||||
--origin-domain-name your-bentopdf-bucket.s3.amazonaws.com \
|
||||
--default-root-object index.html
|
||||
```
|
||||
|
||||
Oppure usa la console AWS:
|
||||
|
||||
1. Vai su CloudFront → Create distribution
|
||||
2. Origin domain: seleziona il tuo bucket S3
|
||||
3. Abilita "Origin Access Control"
|
||||
4. Default root object: `index.html`
|
||||
5. Crea la distribuzione
|
||||
|
||||
## Passaggio 3b: policy degli header di risposta (necessaria per LibreOffice WASM)
|
||||
|
||||
Le conversioni basate su LibreOffice (da Word, Excel, PowerPoint a PDF) richiedono `SharedArrayBuffer`, che necessita di specifici header di risposta. Crea una Response Headers Policy di CloudFront:
|
||||
|
||||
1. Vai su CloudFront → Policies → Response headers
|
||||
2. Crea una policy personalizzata con questi header:
|
||||
|
||||
| Header | Valore |
|
||||
| ------------------------------ | -------------- |
|
||||
| `Cross-Origin-Embedder-Policy` | `require-corp` |
|
||||
| `Cross-Origin-Opener-Policy` | `same-origin` |
|
||||
| `Cross-Origin-Resource-Policy` | `cross-origin` |
|
||||
|
||||
3. Associa la policy al default cache behavior della tua distribuzione
|
||||
|
||||
Oppure tramite 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}
|
||||
]
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## Passaggio 3c: metadati S3 per i file WASM precompressi
|
||||
|
||||
I file WASM di LibreOffice sono precompressi (`.wasm.gz`, `.data.gz`). Imposta Content-Type e Content-Encoding corretti in modo che i browser li decomprimano:
|
||||
|
||||
```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 Importante
|
||||
Senza la policy degli header di risposta, `SharedArrayBuffer` non è disponibile e le conversioni LibreOffice WASM si bloccheranno intorno al 55%. Senza il Content-Encoding corretto sui file `.gz`, il browser riceve i byte gzip grezzi e la compilazione WASM fallisce.
|
||||
:::
|
||||
|
||||
## Passaggio 4: policy del bucket S3
|
||||
|
||||
Consenti a CloudFront di accedere 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Passaggio 5: pagine di errore personalizzate
|
||||
|
||||
Configura il 404 in modo che restituisca `index.html` per il routing SPA:
|
||||
|
||||
1. CloudFront → Error pages
|
||||
2. Crea una risposta di errore personalizzata:
|
||||
- HTTP error code: 404
|
||||
- Response page path: `/index.html`
|
||||
- HTTP response code: 200
|
||||
|
||||
## Stima dei costi
|
||||
|
||||
| Risorsa | Costo stimato |
|
||||
| -------------------------- | ------------- |
|
||||
| S3 Storage (~500MB) | ~$0,01/mese |
|
||||
| CloudFront (1TB transfer) | ~$85/mese |
|
||||
| CloudFront (10GB transfer) | ~$0,85/mese |
|
||||
|
||||
::: tip
|
||||
Usa S3 Intelligent Tiering per ottimizzare i costi dei file a cui si accede di rado.
|
||||
:::
|
||||
|
||||
## Automazione con 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 @@
|
||||
# Distribuzione su Cloudflare Pages
|
||||
|
||||
[Cloudflare Pages](https://pages.cloudflare.com) offre hosting di siti statici veloce e globale con banda illimitata.
|
||||
|
||||
## Distribuzione rapida
|
||||
|
||||
1. Vai su [Cloudflare Pages](https://dash.cloudflare.com/?to=/:account/pages)
|
||||
2. Clicca su "Create a project"
|
||||
3. Collega il tuo repository GitHub
|
||||
|
||||
## Configurazione della build
|
||||
|
||||
| Impostazione | Valore |
|
||||
| ---------------------- | --------------- |
|
||||
| Framework preset | None |
|
||||
| Build command | `npm run build` |
|
||||
| Build output directory | `dist` |
|
||||
| Root directory | `/` |
|
||||
|
||||
## Variabili d'ambiente
|
||||
|
||||
Aggiungile in Settings → Environment variables:
|
||||
|
||||
| Variabile | Valore |
|
||||
| ----------------------- | ----------------------------------------------------------- |
|
||||
| `NODE_VERSION` | `18` |
|
||||
| `SIMPLE_MODE` | `false` (facoltativo) |
|
||||
| `VITE_BRAND_NAME` | Nome del marchio personalizzato (facoltativo) |
|
||||
| `VITE_BRAND_LOGO` | Percorso del logo relativo a `public/` (facoltativo) |
|
||||
| `VITE_FOOTER_TEXT` | Testo personalizzato del footer/copyright (facoltativo) |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Lingua predefinita dell'interfaccia, es. `fr` (facoltativo) |
|
||||
|
||||
## File di configurazione
|
||||
|
||||
Crea `_headers` nella tua cartella `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 Importante
|
||||
Gli header `Cross-Origin-Embedder-Policy` e `Cross-Origin-Opener-Policy` sono necessari per le conversioni da Word/ODT/Excel/PowerPoint a PDF. Senza di essi, `SharedArrayBuffer` non è disponibile e il motore LibreOffice WASM non riesce a inizializzarsi.
|
||||
:::
|
||||
|
||||
Crea `_redirects` per il routing SPA:
|
||||
|
||||
```
|
||||
/* /index.html 200
|
||||
```
|
||||
|
||||
## Dominio personalizzato
|
||||
|
||||
1. Vai al tuo progetto Pages
|
||||
2. Clicca su "Custom domains"
|
||||
3. Aggiungi il tuo dominio
|
||||
4. Cloudflare configurerà automaticamente il DNS se il dominio è su Cloudflare
|
||||
|
||||
## Vantaggi
|
||||
|
||||
- **Banda illimitata gratuita**
|
||||
- **CDN globale** con oltre 300 sedi edge
|
||||
- **HTTPS automatico**
|
||||
- **Anteprime di distribuzione** per le pull request
|
||||
- **Build veloci**
|
||||
|
||||
## Risoluzione dei problemi
|
||||
|
||||
### Caricamento di file di grandi dimensioni
|
||||
|
||||
Cloudflare Pages supporta file fino a 25 MB. I moduli WASM dovrebbero andare bene, ma se raggiungi i limiti, valuta:
|
||||
|
||||
```bash
|
||||
# Split large files during build
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Limiti di dimensione dei Worker
|
||||
|
||||
Se usi Cloudflare Workers per un routing avanzato, tieni presente il limite di 1 MB dei piani gratuiti.
|
||||
|
||||
## Worker proxy CORS (per le firme digitali)
|
||||
|
||||
Lo strumento di firma digitale richiede un proxy CORS per recuperare le catene di certificati. Distribuisci il worker incluso:
|
||||
|
||||
```bash
|
||||
cd cloudflare
|
||||
npx wrangler login
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
### Funzionalità di sicurezza
|
||||
|
||||
| Funzionalità | Descrizione |
|
||||
| ---------------------------- | ---------------------------------- |
|
||||
| **Restrizioni sugli URL** | Consentiti solo URL di certificati |
|
||||
| **Limite di dimensione** | Max 10MB per richiesta |
|
||||
| **Limitazione di frequenza** | 60 richieste/IP/min (richiede KV) |
|
||||
| **Blocco degli IP privati** | Blocca localhost e IP interni |
|
||||
|
||||
### Abilitare la limitazione di frequenza
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Compilare con l'URL del proxy
|
||||
|
||||
```bash
|
||||
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
|
||||
```
|
||||
|
||||
Oppure con 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 il [README](https://github.com/alam00000/bentopdf#digital-signature-cors-proxy-required) per la configurazione della firma HMAC.
|
||||
@@ -0,0 +1,180 @@
|
||||
# Proxy CORS per il recupero dei certificati
|
||||
|
||||
Lo strumento di firma digitale usa un proxy CORS per recuperare i certificati degli emittenti da Certificate Authority (CA) esterne. È necessario perché molti server delle CA non includono header CORS nelle risposte, il che impedisce il recupero diretto dal browser.
|
||||
|
||||
Inoltre, molti server delle CA distribuiscono i certificati tramite HTTP semplice. Quando la tua istanza di BentoPDF è ospitata su HTTPS, i browser bloccano queste richieste HTTP (policy sui contenuti misti). Il proxy CORS risolve entrambi i problemi instradando le richieste attraverso un endpoint HTTPS con gli header corretti.
|
||||
|
||||
## Come funziona
|
||||
|
||||
Quando firmi un PDF con un certificato:
|
||||
|
||||
1. La libreria `zgapdfsigner` cerca di costruire una catena di certificati completa
|
||||
2. Recupera i certificati degli emittenti dagli URL incorporati nell'estensione AIA (Authority Information Access) del tuo certificato
|
||||
3. Queste richieste passano attraverso un proxy CORS che aggiunge gli header `Access-Control-Allow-Origin` necessari
|
||||
4. Il proxy restituisce al browser i dati del certificato
|
||||
|
||||
## Self-hosting del proxy CORS
|
||||
|
||||
Se ospiti BentoPDF in autonomia, dovrai distribuire un tuo proxy CORS affinché le firme digitali funzionino con i certificati che richiedono il recupero della catena.
|
||||
|
||||
### Opzione 1: Cloudflare Workers (consigliata)
|
||||
|
||||
1. **Installa la CLI di Wrangler**:
|
||||
|
||||
```bash
|
||||
npm install -g wrangler
|
||||
```
|
||||
|
||||
2. **Accedi a Cloudflare**:
|
||||
|
||||
```bash
|
||||
wrangler login
|
||||
```
|
||||
|
||||
3. **Clona BentoPDF e aggiorna le origini consentite**:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf/cloudflare
|
||||
```
|
||||
|
||||
Apri `cors-proxy-worker.js` e modifica l'array `ALLOWED_ORIGINS` con il tuo dominio:
|
||||
|
||||
```js
|
||||
const ALLOWED_ORIGINS = [
|
||||
'https://your-domain.com',
|
||||
'https://www.your-domain.com',
|
||||
];
|
||||
```
|
||||
|
||||
::: warning Importante
|
||||
Senza questa modifica, il proxy rifiuterà tutte le richieste dal tuo sito con un errore **403 Forbidden**. L'impostazione predefinita consente solo le richieste da `bentopdf.com`.
|
||||
:::
|
||||
|
||||
4. **Distribuisci il proxy**:
|
||||
|
||||
```bash
|
||||
wrangler deploy
|
||||
```
|
||||
|
||||
Annota l'URL del tuo worker (es. `https://bentopdf-cors-proxy.your-subdomain.workers.dev`).
|
||||
|
||||
5. **Ricompila BentoPDF con l'URL del proxy**:
|
||||
|
||||
Se usi 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 .
|
||||
```
|
||||
|
||||
Se compili dai sorgenti:
|
||||
|
||||
```bash
|
||||
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
|
||||
```
|
||||
|
||||
### Opzione 2: proxy backend personalizzato
|
||||
|
||||
Puoi anche creare un tuo endpoint proxy. I requisiti sono:
|
||||
|
||||
1. Accettare richieste GET con un parametro di query `url`
|
||||
2. Recuperare l'URL dal tuo server (nessuna restrizione CORS lato server)
|
||||
3. Restituire la risposta con questi header:
|
||||
- `Access-Control-Allow-Origin: https://your-domain.com`
|
||||
- `Access-Control-Allow-Methods: GET, OPTIONS`
|
||||
- `X-Content-Type-Options: nosniff`
|
||||
|
||||
Esempio di implementazione con 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' });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Considerazioni sulla sicurezza
|
||||
|
||||
Il Cloudflare Worker incluso adotta diverse misure di sicurezza:
|
||||
|
||||
| Funzionalità | Descrizione |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------- |
|
||||
| **Convalida dell'origine** | Consente solo richieste dai domini elencati in `ALLOWED_ORIGINS` |
|
||||
| **Restrizioni sugli URL** | Consente solo URL di certificati (`.crt`, `.cer`, `.pem`, `/certs/`, `/ocsp`, `/crl`) |
|
||||
| **Blocco degli IP privati** | Blocca intervalli privati IPv4/IPv6, link-local, loopback, IP decimali e metadati cloud |
|
||||
| **Sicurezza del Content-Type** | Restituisce solo tipi MIME di certificati sicuri, blocca l'iniezione di content-type dall'upstream |
|
||||
| **Limite di dimensione** | Trasmette la risposta in streaming con limite di 10MB, interrompe il download se superato |
|
||||
| **Limitazione di frequenza** | 60 richieste al minuto per IP o IPv6 /64 (richiede KV, nessun blocco in caso di errori di KV) |
|
||||
| **Firme HMAC** | Firma facoltativa lato client (scoraggia gli abusi occasionali) |
|
||||
|
||||
### DNS rebinding (self-hosting fuori da Cloudflare)
|
||||
|
||||
Il worker rifiuta qualsiasi URL che si risolve in un IP privato o interno. Non può usare un elenco fisso di host consentiti, perché gli URL della catena di certificati puntano a qualunque CA abbia emesso il certificato (FNMT, CA aziendali e così via), quindi le destinazioni non sono note in anticipo. Resta così una piccola finestra di rebinding: chi gestisce il DNS del proprio dominio potrebbe restituire un IP pubblico al controllo di sicurezza e uno privato alla richiesta vera e propria un istante dopo.
|
||||
|
||||
Su Cloudflare non è sfruttabile: i Workers non possono raggiungere IP privati né l'endpoint di metadati `169.254.169.254`, quindi un rebind non porta a nulla. Basta non dare al worker una via d'accesso: non associare un Tunnel, un Service o un binding di rete privata (gli serve solo `RATE_LIMIT_KV`).
|
||||
|
||||
Se lo esegui fuori da Cloudflare (workerd, Node e simili) all'interno di una VPC, il runtime _può_ raggiungere gli IP interni. Metti davanti un firewall in uscita e blocca gli intervalli privati: `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` e i rispettivi equivalenti IPv6. Il codice dell'app non può imporlo una volta fuori da Cloudflare.
|
||||
|
||||
## Disattivare il proxy
|
||||
|
||||
Se non vuoi usare un proxy CORS, imposta la variabile d'ambiente su una stringa vuota:
|
||||
|
||||
```
|
||||
VITE_CORS_PROXY_URL=
|
||||
```
|
||||
|
||||
**Nota**: senza il proxy, la firma con certificati che richiedono il recupero esterno della catena (come FNMT o alcune CA aziendali) fallirà con l'errore "Failed to fetch".
|
||||
|
||||
## Risoluzione dei problemi
|
||||
|
||||
### "Signing error: TypeError: Failed to fetch"
|
||||
|
||||
Di solito significa una di queste cose:
|
||||
|
||||
1. **Nessun proxy CORS configurato**: imposta `VITE_CORS_PROXY_URL` e ricompila
|
||||
2. **Contenuti misti bloccati**: il tuo sito è in HTTPS ma l'URL dell'emittente del certificato è in HTTP. Il proxy CORS risolve il problema.
|
||||
3. **Il proxy CORS rifiuta la tua origine**: verifica che il tuo dominio sia nell'array `ALLOWED_ORIGINS` in `cors-proxy-worker.js`
|
||||
|
||||
### "403 Forbidden" dal proxy
|
||||
|
||||
Il tuo dominio non è nell'elenco `ALLOWED_ORIGINS`. Modifica `cors-proxy-worker.js`:
|
||||
|
||||
```js
|
||||
const ALLOWED_ORIGINS = ['https://your-domain.com'];
|
||||
```
|
||||
|
||||
Poi ridistribuisci: `npx wrangler deploy`
|
||||
|
||||
### Testare il proxy
|
||||
|
||||
```bash
|
||||
curl -H "Origin: https://your-domain.com" \
|
||||
"https://your-proxy.workers.dev?url=http://www.cert.fnmt.es/certs/ACUSU.crt"
|
||||
```
|
||||
|
||||
### Certificati che funzionano senza proxy
|
||||
|
||||
Alcuni certificati includono la catena completa nel file P12/PFX e non richiedono recuperi esterni:
|
||||
|
||||
- Certificati autofirmati
|
||||
- Alcune CA commerciali che includono i certificati intermedi
|
||||
- Certificati che hai assemblato manualmente con la catena completa
|
||||
@@ -0,0 +1,622 @@
|
||||
# Distribuzione con Docker / Podman
|
||||
|
||||
Il modo più semplice per ospitare BentoPDF in autonomia in un ambiente di produzione.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Header richiesti per la conversione dei file Office**
|
||||
>
|
||||
> Gli strumenti basati su LibreOffice (conversione di Word, Excel, PowerPoint) richiedono questi header HTTP per il supporto a `SharedArrayBuffer`:
|
||||
>
|
||||
> - `Cross-Origin-Opener-Policy: same-origin`
|
||||
> - `Cross-Origin-Embedder-Policy: require-corp`
|
||||
>
|
||||
> La pagina deve inoltre essere servita da un contesto sicuro. `http://localhost` funziona per i test in locale, ma `http://192.168.x.x` o altri IP di rete locale di solito non lo qualificano, quindi la conversione Office tramite HTTP semplice fallirà anche se gli header sono presenti.
|
||||
>
|
||||
> Le immagini container ufficiali includono questi header. Se usi un reverse proxy (Traefik, Caddy, ecc.), assicurati che gli header vengano mantenuti o aggiunti e usa HTTPS per gli accessi diversi dal loopback.
|
||||
|
||||
> [!TIP]
|
||||
> **Utenti Podman:** tutti i comandi `docker` funzionano con Podman sostituendo `docker` con `podman` e `docker-compose` con `podman-compose`.
|
||||
|
||||
## Avvio rapido
|
||||
|
||||
> [!TIP]
|
||||
> BentoPDF è disponibile in due build:
|
||||
>
|
||||
> - **Build self-hosted**: `ghcr.io/alam00000/bentopdf-simple:latest`. Tutti gli strumenti PDF, **senza** la parte di marketing (niente hero, FAQ, testimonianze, footer). Usala per installazioni interne, di team o di organizzazioni. **Non** è una versione "lite" con meno funzionalità.
|
||||
> - **Build commerciale**: `ghcr.io/alam00000/bentopdf:latest`. Il sito di marketing completo, usato da bentopdf.com stesso e dai titolari di una licenza commerciale che gestiscono installazioni pubbliche.
|
||||
>
|
||||
> Se hai dubbi: scarica la build self-hosted.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Esegui:
|
||||
|
||||
```bash
|
||||
# Docker Compose
|
||||
docker compose up -d
|
||||
|
||||
# Podman Compose
|
||||
podman-compose up -d
|
||||
```
|
||||
|
||||
## Crea la tua immagine
|
||||
|
||||
```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 ed esegui:
|
||||
|
||||
```bash
|
||||
docker build -t bentopdf:custom .
|
||||
docker run -d -p 3000:8080 bentopdf:custom
|
||||
```
|
||||
|
||||
## Variabili d'ambiente
|
||||
|
||||
| Variabile | Descrizione | Predefinito |
|
||||
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
|
||||
| `SIMPLE_MODE` | Compila la variante self-hosted (nasconde la parte di marketing di bentopdf.com: hero, FAQ, testimonianze, footer). Tutti gli strumenti PDF restano. | `false` |
|
||||
| `BASE_URL` | Distribuzione in una sottodirectory | `/` |
|
||||
| `VITE_WASM_PYMUPDF_URL` | URL del modulo WASM di PyMuPDF | `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/` |
|
||||
| `VITE_WASM_GS_URL` | URL del modulo WASM di Ghostscript | `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm@0.1.1/assets/` |
|
||||
| `VITE_WASM_CPDF_URL` | URL del modulo WASM di CoherentPDF | `https://cdn.jsdelivr.net/npm/coherentpdf@2.5.5/dist/` |
|
||||
| `VITE_TESSERACT_WORKER_URL` | URL dello script worker OCR | _(vuoto; usa la CDN predefinita di Tesseract.js)_ |
|
||||
| `VITE_TESSERACT_CORE_URL` | Directory del runtime core OCR | _(empty; use Tesseract.js default CDN)_ |
|
||||
| `VITE_TESSERACT_LANG_URL` | Directory dei traineddata OCR | _(empty; use Tesseract.js default CDN)_ |
|
||||
| `VITE_TESSERACT_AVAILABLE_LANGUAGES` | Lingue OCR separate da virgole mostrate nell'interfaccia | _(vuoto; mostra il catalogo completo)_ |
|
||||
| `VITE_OCR_FONT_BASE_URL` | Directory dei font del livello di testo OCR | _(vuoto; usa gli URL remoti dei font Noto)_ |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Lingua predefinita dell'interfaccia | `en` |
|
||||
| `VITE_BRAND_NAME` | Nome del marchio personalizzato | `BentoPDF` |
|
||||
| `VITE_BRAND_LOGO` | Percorso del logo relativo a `public/` | `images/favicon-no-bg.svg` |
|
||||
| `VITE_FOOTER_TEXT` | Testo personalizzato del footer/copyright | `© 2026 BentoPDF. All rights reserved.` |
|
||||
| `DISABLE_TOOLS` | ID degli strumenti da nascondere, separati da virgole | _(vuoto; tutti gli strumenti abilitati)_ |
|
||||
| `DISABLE_GITHUB_STARS` | Salta il recupero del numero di stelle GitHub al caricamento della pagina e rimuove `api.github.com` dalla CSP. Le build in Simple Mode lo saltano sempre. | `false` |
|
||||
| `SITE_URL` | URL pubblico scritto nei link canonical, hreflang e sitemap. Lascia il valore predefinito, a meno che tu non voglia che i motori di ricerca trattino la tua installazione come un sito a sé. | `https://www.bentopdf.com` |
|
||||
| `ALLOW_INDEXING` | Con un `SITE_URL` personalizzato, ogni pagina è marcata `noindex` così la tua copia non compete con bentopdf.com nei risultati di ricerca. Imposta `true` per permettere ai motori di ricerca di indicizzarla. | `false` |
|
||||
|
||||
Gli URL dei moduli WASM sono preconfigurati con i valori CDN predefiniti: tutte le funzionalità avanzate funzionano subito. Sostituiscili per installazioni air-gapped o self-hosted.
|
||||
|
||||
### Motori di ricerca
|
||||
|
||||
Per impostazione predefinita, ogni pagina indica ai motori di ricerca bentopdf.com come originale, così la tua installazione non appare come duplicato. Non devi cambiare nulla per un'istanza privata o interna.
|
||||
|
||||
- **Tieni la tua istanza fuori dai risultati di ricerca:** esegui il container con `-e ROBOTS_NOINDEX=true`. Basta un riavvio, non serve ricompilare.
|
||||
- **Rendi la tua istanza un sito a sé nei risultati di ricerca:** compila con `--build-arg SITE_URL=https://your-domain.example --build-arg ALLOW_INDEXING=true`. Senza `ALLOW_INDEXING=true`, un `SITE_URL` personalizzato marca ogni pagina `noindex`.
|
||||
|
||||
### Content-Security-Policy
|
||||
|
||||
L'immagine nginx include un header `Content-Security-Policy` in modalità enforcing. Le direttive `script-src`, `connect-src` e `font-src` della CSP sono **generate in fase di build** a partire dalle variabili URL `VITE_*` qui sopra: tutti gli host che passi con `--build-arg` vengono aggiunti automaticamente alla policy.
|
||||
|
||||
Di conseguenza:
|
||||
|
||||
- Se sovrascrivi `VITE_CORS_PROXY_URL` o `VITE_WASM_*_URL` in fase di build, la CSP consente automaticamente quelle origini, senza configurazioni aggiuntive.
|
||||
- Per gli URL su `cdn.jsdelivr.net`, `script-src` consente solo l'esatta cartella del pacchetto con versione, ad esempio `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/`, non l'intera CDN. jsDelivr serve qualsiasi pacchetto npm pubblico o repository GitHub, quindi consentire l'intero host permetterebbe a ciascuno di essi di essere eseguito sulla tua istanza. Gli URL su qualsiasi altro host sono consentiti per origine.
|
||||
- Se configuri URL WASM personalizzati in _runtime_ dalla pagina Impostazioni avanzate dell'app, quelle origini **non** sono nella CSP e il browser bloccherà le richieste verso di esse. Vale anche per una versione diversa dello stesso pacchetto su `cdn.jsdelivr.net`, perché è consentita solo la versione impostata in fase di build. La configurazione in runtime serve per sperimentare; per URL personalizzati permanenti imposta il corrispondente build arg `VITE_*`.
|
||||
- Nelle installazioni air-gapped che sovrascrivono tutti e tre i valori `VITE_WASM_*_URL`, anche le cartelle dei pacchetti su `cdn.jsdelivr.net` vengono rimosse dalla CSP (ogni valore predefinito viene sostituito, non aggiunto). I valori predefiniti di Tesseract OCR su `cdn.jsdelivr.net` restano nella policy finché non imposti anche i tre valori `VITE_TESSERACT_*`. Allo stesso modo, impostare `VITE_CORS_PROXY_URL` sostituisce il valore pubblico predefinito `bentopdf-cors-proxy.bentopdf.workers.dev`.
|
||||
|
||||
La CSP include `'unsafe-eval'` in `script-src` perché il runtime LibreOffice WASM (usato dagli strumenti di conversione Word/Excel/PowerPoint) e la libreria di immagini wasm-vips (usata da PDF in TIFF) compilano codice di dispatch interno tramite `new Function()`. Rimuoverlo renderebbe inutilizzabili quegli strumenti. Se compili in `SIMPLE_MODE` (senza LibreOffice) e non ti serve PDF in TIFF, puoi modificare a mano il file generato `security-headers.conf` per eliminare `'unsafe-eval'` e ottenere una policy più restrittiva.
|
||||
|
||||
Per l'OCR, lascia vuote le variabili `VITE_TESSERACT_*` per usare le risorse online predefinite, oppure impostale tutte e tre insieme per un OCR self-hosted/offline. Le sostituzioni parziali dell'OCR vengono rifiutate perché worker, runtime core e directory dei traineddata devono corrispondere. Per un output PDF ricercabile completamente offline, imposta anche `VITE_OCR_FONT_BASE_URL` in modo che i font del livello di testo OCR vengano caricati dal tuo server interno invece che dagli URL pubblici dei font Noto.
|
||||
|
||||
`VITE_DEFAULT_LANGUAGE` imposta la lingua dell'interfaccia per i nuovi visitatori. Valori supportati: `en`, `ar`, `be`, `fr`, `de`, `es`, `zh`, `zh-TW`, `vi`, `tr`, `id`, `it`, `pt`, `nl`, `da`. Gli utenti possono comunque cambiare lingua: questo modifica solo il valore predefinito.
|
||||
|
||||
Esempio:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Marchio personalizzato
|
||||
|
||||
Sostituisci il logo, il nome e il testo del footer predefiniti di BentoPDF con i tuoi. Metti il file del logo nella cartella `public/` (o usa un'immagine esistente), poi passa le variabili del marchio in fase di build:
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--build-arg VITE_BRAND_NAME="AcmePDF" \
|
||||
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
|
||||
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
|
||||
-t acmepdf .
|
||||
```
|
||||
|
||||
La personalizzazione del marchio funziona sia in modalità completa sia in Simple Mode e si può combinare con tutte le altre opzioni di build.
|
||||
|
||||
### Disattivare strumenti specifici
|
||||
|
||||
Nascondi strumenti dall'interfaccia per esigenze di conformità o sicurezza. Gli strumenti disattivati vengono rimossi dalla homepage, dai risultati di ricerca, dalle scorciatoie da tastiera e dal workflow builder. L'accesso diretto tramite URL mostra una pagina "strumento non disponibile".
|
||||
|
||||
Gli ID degli strumenti sono l'URL della pagina senza `.html`. Ad esempio, se lo strumento si trova in `bentopdf.com/edit-pdf.html`, l'ID è `edit-pdf`.
|
||||
|
||||
#### Trovare gli ID degli strumenti
|
||||
|
||||
Il modo più semplice: apri uno strumento qualsiasi in BentoPDF e guarda l'URL. L'ultima parte del percorso (senza `.html`) è l'ID dello strumento.
|
||||
|
||||
<details>
|
||||
<summary>Elenco completo degli ID degli strumenti</summary>
|
||||
|
||||
**Modifica e annota:** `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`
|
||||
|
||||
**Converti in 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`
|
||||
|
||||
**Converti da 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`
|
||||
|
||||
**Organizza e gestisci:** `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`
|
||||
|
||||
**Ottimizza e ripara:** `compress-pdf`, `pdf-to-pdfa`, `fix-page-size`, `linearize-pdf`, `remove-restrictions`, `repair-pdf`, `rasterize-pdf`, `deskew-pdf`, `font-to-outline`
|
||||
|
||||
**Sicurezza:** `encrypt-pdf`, `sanitize-pdf`, `decrypt-pdf`, `flatten-pdf`, `remove-metadata`, `change-permissions`, `digital-sign-pdf`, `validate-signature-pdf`, `timestamp-pdf`
|
||||
|
||||
</details>
|
||||
|
||||
#### Opzione 1: in fase di build (build arg di Docker)
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--build-arg DISABLE_TOOLS="edit-pdf,sign-pdf,encrypt-pdf" \
|
||||
-t bentopdf .
|
||||
```
|
||||
|
||||
Questo incorpora l'elenco degli strumenti disattivati nel bundle JavaScript. Per modificarlo serve ricompilare.
|
||||
|
||||
#### Opzione 2: in runtime (config.json)
|
||||
|
||||
Monta un file `config.json` nella directory servita, senza bisogno di ricompilare:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Oppure con 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
|
||||
```
|
||||
|
||||
I due metodi si possono combinare: gli elenchi vengono uniti. Se uno strumento compare in uno dei due, viene disattivato.
|
||||
|
||||
#### Disattivare le funzionalità dell'editor
|
||||
|
||||
Puoi anche disattivare funzionalità specifiche all'interno dell'editor PDF (ad es. oscuramento, annotazioni, moduli) senza disattivare l'intero strumento. Aggiungi `editorDisabledCategories` al tuo `config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"editorDisabledCategories": ["redaction", "annotation-stamp"]
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Elenco completo delle categorie dell'editor</summary>
|
||||
|
||||
**Zoom:** `zoom`, `zoom-in`, `zoom-out`, `zoom-fit-page`, `zoom-fit-width`, `zoom-marquee`, `zoom-level`
|
||||
|
||||
**Annotazioni:** `annotation`, `annotation-markup`, `annotation-highlight`, `annotation-underline`, `annotation-strikeout`, `annotation-squiggly`, `annotation-ink`, `annotation-text`, `annotation-stamp`
|
||||
|
||||
**Forme:** `annotation-shape`, `annotation-rectangle`, `annotation-circle`, `annotation-line`, `annotation-arrow`, `annotation-polygon`, `annotation-polyline`
|
||||
|
||||
**Moduli:** `form`, `form-textfield`, `form-checkbox`, `form-radio`, `form-select`, `form-listbox`, `form-fill-mode`
|
||||
|
||||
**Oscuramento:** `redaction`, `redaction-area`, `redaction-text`, `redaction-apply`, `redaction-clear`
|
||||
|
||||
**Documento:** `document`, `document-open`, `document-close`, `document-print`, `document-capture`, `document-export`, `document-fullscreen`, `document-protect`
|
||||
|
||||
**Pagina:** `page`, `spread`, `rotate`, `scroll`, `navigation`
|
||||
|
||||
**Pannelli:** `panel`, `panel-sidebar`, `panel-search`, `panel-comment`
|
||||
|
||||
**Strumenti:** `tools`, `pan`, `pointer`, `capture`
|
||||
|
||||
**Selezione:** `selection`, `selection-copy`
|
||||
|
||||
**Cronologia:** `history`, `history-undo`, `history-redo`
|
||||
|
||||
</details>
|
||||
|
||||
Le categorie sono gerarchiche: disattivando una categoria padre (ad es. `annotation`) si disattivano tutte le sue figlie.
|
||||
|
||||
### Disattivare il contatore di stelle GitHub
|
||||
|
||||
La barra di navigazione mostra il numero di stelle GitHub in tempo reale, recuperato da `api.github.com` al caricamento della pagina. È l'unica richiesta esterna che non serve a una funzionalità PDF. Disattivalo in fase di build per rimuovere il codice di recupero dal bundle ed eliminare `api.github.com` dalla Content-Security-Policy:
|
||||
|
||||
```bash
|
||||
docker build --build-arg DISABLE_GITHUB_STARS=true -t bentopdf .
|
||||
```
|
||||
|
||||
Le build in Simple Mode (`bentopdf-simple`) saltano sempre il contatore di stelle: questo flag serve solo sulla build commerciale o sulle build complete personalizzate.
|
||||
|
||||
### URL WASM personalizzati (air-gapped / self-hosted)
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Gli URL WASM sono incorporati nel JavaScript in **fase di build**. I file WASM vengono scaricati dal **browser dell'utente** in runtime: Docker non li scarica durante la build. Per le reti air-gapped, devi ospitare i file WASM su un server interno raggiungibile dai browser.
|
||||
|
||||
**Flusso di lavoro air-gapped completo:**
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Usa i codici stampati da `bash scripts/prepare-airgap.sh --list-ocr-languages`, oppure cercali per nome con `bash scripts/prepare-airgap.sh --search-ocr-language <term>`, per `--ocr-languages`. Quando compili con un sottoinsieme limitato di lingue OCR, passa gli stessi codici a `VITE_TESSERACT_AVAILABLE_LANGUAGES` in modo che l'app mostri solo le lingue incluse. Per un output OCR completamente offline, ospita anche la cartella inclusa `ocr-fonts/` e punta `VITE_OCR_FONT_BASE_URL` verso di essa.
|
||||
|
||||
Imposta una variabile su stringa vuota per disattivare quel modulo (gli utenti dovranno configurarlo manualmente da Impostazioni avanzate).
|
||||
|
||||
## Porta personalizzata
|
||||
|
||||
Per impostazione predefinita, BentoPDF è in ascolto sulla porta `8080` all'interno del container. Per cambiarla, imposta la variabile d'ambiente `PORT` in runtime:
|
||||
|
||||
```bash
|
||||
docker run -p 3000:9090 -e PORT=9090 ghcr.io/alam00000/bentopdf-simple:latest
|
||||
```
|
||||
|
||||
| Variabile | Descrizione | Predefinito |
|
||||
| --------- | --------------------------------------- | ----------- |
|
||||
| `PORT` | Porta di ascolto di Nginx nel container | `8080` |
|
||||
|
||||
Funziona sia con il Dockerfile standard sia con quello nonroot.
|
||||
|
||||
## ID utente personalizzato (PUID/PGID)
|
||||
|
||||
Per gli ambienti che richiedono l'esecuzione come utente non root specifico (dispositivi NAS, Kubernetes con security context, policy organizzative), BentoPDF fornisce un Dockerfile separato con supporto PUID/PGID in stile LSIO.
|
||||
|
||||
### Compila ed esegui
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Variabili d'ambiente
|
||||
|
||||
| Variabile | Descrizione | Predefinito |
|
||||
| -------------- | -------------------------- | ----------- |
|
||||
| `PUID` | ID utente con cui eseguire | `1000` |
|
||||
| `PGID` | ID gruppo con cui eseguire | `1000` |
|
||||
| `PORT` | Porta di ascolto di Nginx | `8080` |
|
||||
| `DISABLE_IPV6` | Disattiva il listener 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
|
||||
```
|
||||
|
||||
### Come funziona
|
||||
|
||||
Il container si avvia come root, crea un utente con il PUID/PGID specificato, regola la proprietà di tutte le directory scrivibili e poi abbassa i privilegi con `su-exec`. Il processo nginx viene eseguito interamente come l'utente specificato.
|
||||
|
||||
> [!NOTE]
|
||||
> Il `Dockerfile` standard usa `nginx-unprivileged` (UID 101) ed è consigliato per la maggior parte delle installazioni. Usa `Dockerfile.nonroot` solo quando ti serve un UID/GID specifico.
|
||||
|
||||
> [!WARNING]
|
||||
> PUID/PGID non possono essere `0` (root). L'entrypoint convalida i valori e termina con un errore se non sono validi.
|
||||
|
||||
## Con Traefik (reverse proxy)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v2.10
|
||||
command:
|
||||
- '--providers.docker=true'
|
||||
- '--entrypoints.web.address=:80'
|
||||
- '--entrypoints.websecure.address=:443'
|
||||
- '--certificatesresolvers.letsencrypt.acme.email=you@example.com'
|
||||
- '--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json'
|
||||
- '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web'
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443'
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./letsencrypt:/letsencrypt
|
||||
|
||||
bentopdf:
|
||||
image: ghcr.io/alam00000/bentopdf-simple:latest
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'traefik.http.routers.bentopdf.rule=Host(`pdf.example.com`)'
|
||||
- 'traefik.http.routers.bentopdf.entrypoints=websecure'
|
||||
- 'traefik.http.routers.bentopdf.tls.certresolver=letsencrypt'
|
||||
- 'traefik.http.services.bentopdf.loadbalancer.server.port=8080'
|
||||
# Required headers for SharedArrayBuffer (LibreOffice WASM)
|
||||
- 'traefik.http.routers.bentopdf.middlewares=bentopdf-headers'
|
||||
- 'traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Opener-Policy=same-origin'
|
||||
- 'traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Embedder-Policy=require-corp'
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
## Con Caddy (reverse proxy)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
caddy:
|
||||
image: caddy:2
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443'
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile
|
||||
- caddy_data:/data
|
||||
|
||||
bentopdf:
|
||||
image: ghcr.io/alam00000/bentopdf-simple:latest
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
caddy_data:
|
||||
```
|
||||
|
||||
Caddyfile:
|
||||
|
||||
```
|
||||
pdf.example.com {
|
||||
reverse_proxy bentopdf:8080
|
||||
header Cross-Origin-Opener-Policy "same-origin"
|
||||
header Cross-Origin-Embedder-Policy "require-corp"
|
||||
}
|
||||
```
|
||||
|
||||
## Limiti di risorse
|
||||
|
||||
```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 (integrazione con systemd)
|
||||
|
||||
[Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) permette di eseguire i container Podman come servizi systemd. È ideale per le installazioni di produzione su sistemi Linux.
|
||||
|
||||
### Configurazione base di Quadlet
|
||||
|
||||
Crea un file di unità container in `~/.config/containers/systemd/bentopdf.container` (utente) oppure `/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
|
||||
```
|
||||
|
||||
### Abilita e avvia
|
||||
|
||||
```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 un'installazione a livello di sistema, usa `systemctl` senza il flag `--user` e metti il file in `/etc/containers/systemd/`.
|
||||
|
||||
### Quadlet con health check
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=BentoPDF with health monitoring
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
Image=ghcr.io/alam00000/bentopdf-simple:latest
|
||||
ContainerName=bentopdf
|
||||
PublishPort=3000:8080
|
||||
AutoUpdate=registry
|
||||
HealthCmd=wget --spider -q http://localhost:8080 || exit 1
|
||||
HealthInterval=30s
|
||||
HealthTimeout=10s
|
||||
HealthRetries=3
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=300
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
### Aggiornamento automatico con Quadlet
|
||||
|
||||
Podman può aggiornare automaticamente i container quando sono disponibili nuove immagini:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Configurazione di rete di Quadlet
|
||||
|
||||
Per una configurazione di rete personalizzata, crea un file di rete `bentopdf.network`:
|
||||
|
||||
```ini
|
||||
[Network]
|
||||
Subnet=10.89.0.0/24
|
||||
Gateway=10.89.0.1
|
||||
```
|
||||
|
||||
Poi fai riferimento ad esso nel tuo file container:
|
||||
|
||||
```ini
|
||||
[Container]
|
||||
Image=ghcr.io/alam00000/bentopdf-simple:latest
|
||||
ContainerName=bentopdf
|
||||
PublishPort=3000:8080
|
||||
Network=bentopdf.network
|
||||
```
|
||||
|
||||
## Aggiornamento
|
||||
|
||||
```bash
|
||||
# Pull latest image
|
||||
docker compose pull
|
||||
|
||||
# Recreate container
|
||||
docker compose up -d
|
||||
```
|
||||
@@ -0,0 +1,266 @@
|
||||
# Distribuzione su Hostinger
|
||||
|
||||
[Hostinger](https://hostinger.com) è un diffuso provider di hosting condiviso. Questa guida spiega come distribuire BentoPDF sull'hosting condiviso di Hostinger.
|
||||
|
||||
## Prerequisiti
|
||||
|
||||
- Piano di hosting Hostinger con accesso al file manager
|
||||
- Node.js installato in locale per la build
|
||||
|
||||
## Passaggio 1: compila il progetto
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
I file compilati saranno nella cartella `dist`.
|
||||
|
||||
## Passaggio 2: carica su Hostinger
|
||||
|
||||
### Distribuzione sul dominio principale
|
||||
|
||||
1. Accedi al tuo account Hostinger
|
||||
2. Vai su **File Manager** → **public_html**
|
||||
3. **Elimina** tutti i file esistenti (fai prima un backup se serve)
|
||||
4. **Carica** tutto il contenuto della tua cartella locale `dist` in `public_html`
|
||||
|
||||
### Distribuzione in una sottodirectory
|
||||
|
||||
Se distribuisci in una sottodirectory (es. `yourdomain.com/pdf-tools/`):
|
||||
|
||||
1. Compila con l'URL di base corretto:
|
||||
|
||||
```bash
|
||||
BASE_URL=/pdf-tools/ npm run build
|
||||
```
|
||||
|
||||
2. Crea la cartella in Hostinger:
|
||||
- Vai su **File Manager** → **public_html**
|
||||
- Crea una nuova cartella: `pdf-tools`
|
||||
3. Carica tutto il contenuto di `dist` in `public_html/pdf-tools/`
|
||||
|
||||
## Passaggio 3: crea il file .htaccess
|
||||
|
||||
Crea un file `.htaccess` nella radice della cartella di distribuzione (`public_html` o `public_html/pdf-tools/`):
|
||||
|
||||
::: warning Importante
|
||||
Per la distribuzione in una sottodirectory, cambia `RewriteBase /` in `RewriteBase /pdf-tools/` (o il nome della tua cartella).
|
||||
:::
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Esempio di .htaccess per sottodirectory
|
||||
|
||||
Per `yourdomain.com/pdf-tools/`, aggiorna queste righe:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Risoluzione dei problemi
|
||||
|
||||
### I file WASM non si caricano
|
||||
|
||||
Assicurati che i tipi MIME siano impostati correttamente in `.htaccess`:
|
||||
|
||||
```apache
|
||||
AddType application/wasm .wasm
|
||||
```
|
||||
|
||||
### Gli strumenti LibreOffice non funzionano
|
||||
|
||||
Gli header di sicurezza sono fondamentali per SharedArrayBuffer:
|
||||
|
||||
```apache
|
||||
Header always set Cross-Origin-Opener-Policy "same-origin"
|
||||
Header always set Cross-Origin-Embedder-Policy "require-corp"
|
||||
```
|
||||
|
||||
Se gli header non vengono applicati, contatta il supporto di Hostinger per abilitare `mod_headers`.
|
||||
|
||||
### Errori 404 all'aggiornamento della pagina
|
||||
|
||||
Verifica che la regola per l'estensione `.html` e le route delle lingue siano configurate correttamente. BentoPDF usa file HTML statici, non il routing 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]
|
||||
```
|
||||
|
||||
### Limiti di caricamento dei file
|
||||
|
||||
Hostinger può avere limiti sulla dimensione dei file. Carica i file WASM separatamente se il caricamento in blocco fallisce.
|
||||
@@ -0,0 +1,538 @@
|
||||
# Guida all'self-hosting
|
||||
|
||||
BentoPDF può essere ospitato sulla tua infrastruttura. Questa guida illustra le varie opzioni di deploy.
|
||||
|
||||
## Avvio rapido con Docker / Podman
|
||||
|
||||
Il modo più rapido per ospitare BentoPDF:
|
||||
|
||||
> [!TIP]
|
||||
> BentoPDF è disponibile in due build:
|
||||
>
|
||||
> - **Build Self-Hosted**: `ghcr.io/alam00000/bentopdf-simple:latest`. Tutti gli strumenti PDF, **senza** gli elementi di marketing (niente hero, FAQ, testimonianze, footer). Usala per deploy interni, di team o di organizzazione. **Non** è una versione "lite" con meno funzionalità.
|
||||
> - **Build Commercial**: `ghcr.io/alam00000/bentopdf:latest`. Il sito di marketing completo, usato da bentopdf.com stesso e dai titolari di licenza commerciale che gestiscono deploy pubblici.
|
||||
>
|
||||
> Nel dubbio: scarica la build Self-Hosted.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> La conversione dei file Office richiede `SharedArrayBuffer`, il che significa che l'app deve essere sia isolata cross-origin sia servita da un contesto sicuro. L'immagine ufficiale invia già gli header COOP/COEP necessari, ma i browser disabilitano comunque `SharedArrayBuffer` sulle origini HTTP semplici della rete locale, come `http://192.168.x.x`.
|
||||
>
|
||||
> Usa `http://localhost` solo per test sullo stesso dispositivo. Se gli utenti accedono a BentoPDF tramite un IP o un hostname della LAN, termina la connessione con 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
|
||||
```
|
||||
|
||||
Oppure con 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)
|
||||
|
||||
Esegui BentoPDF come servizio 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 [guida al deploy con Docker](/it/self-hosting/docker) per la documentazione completa di Quadlet.
|
||||
|
||||
## Compilazione dal codice sorgente
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Opzioni di configurazione
|
||||
|
||||
### Build Self-Hosted (Simple Mode)
|
||||
|
||||
La build Self-Hosted (l'immagine `bentopdf-simple`, detta anche Simple Mode) è **funzionalmente identica** alla build Commercial: ogni strumento PDF è presente e si comporta allo stesso modo. Nasconde solo gli elementi di marketing che hanno senso soltanto su bentopdf.com o in un deploy commerciale pubblico. **Non è una versione "lite" con meno funzionalità.**
|
||||
|
||||
**Cosa nasconde la build Self-Hosted** (solo aspetto estetico, nessuna funzionalità PDF viene rimossa):
|
||||
|
||||
- Barra di navigazione, sezione hero, sezione delle funzionalità, FAQ, testimonianze, footer
|
||||
- Aggiorna il titolo della pagina in "Strumenti PDF"
|
||||
|
||||
**Cosa mantiene la build Self-Hosted** (tutto ciò che lavora davvero sui PDF):
|
||||
|
||||
- Ogni strumento PDF (unione, divisione, modifica, firma, OCR, conversione Office, ecc.)
|
||||
- Supporto al branding personalizzato, tutta la configurazione di build e di runtime
|
||||
|
||||
La build Commercial (`ghcr.io/alam00000/bentopdf:latest`) è quella che alimenta bentopdf.com stesso ed è usata dai titolari di licenza commerciale che gestiscono deploy pubblici: aggiunge hero, FAQ, testimonianze e footer che non avrebbero senso in uno strumento interno.
|
||||
|
||||
Se ospiti BentoPDF per il tuo team, la tua organizzazione o come strumento interno, scarica la build Self-Hosted:
|
||||
|
||||
```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 tutti i dettagli.
|
||||
|
||||
### Build Commercial (deploy pubblici con il tuo brand)
|
||||
|
||||
La build Commercial (l'immagine `bentopdf`, senza il suffisso `-simple`) è quella che alimenta bentopdf.com stesso. Include il sito di marketing completo (hero, funzionalità, FAQ, testimonianze, footer) oltre a ogni strumento PDF. Usa questa build quando vuoi **distribuire BentoPDF come servizio PDF pubblico con il tuo brand**, ad esempio:
|
||||
|
||||
- Gestisci BentoPDF come SaaS ospitato per gli utenti finali sul tuo dominio
|
||||
- Vuoi l'esperienza della landing page (sezioni di marketing + strumenti), non solo l'interfaccia essenziale degli strumenti
|
||||
- Sei un titolare di licenza commerciale e integri BentoPDF in un prodotto commerciale
|
||||
|
||||
**Eseguila così com'è** (brand BentoPDF, utile per vedere un'anteprima della build):
|
||||
|
||||
```bash
|
||||
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
|
||||
```
|
||||
|
||||
**Compila con il tuo brand** (il tipico flusso commerciale):
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Ogni altra opzione di build (`BASE_URL`, `VITE_DEFAULT_LANGUAGE`, `DISABLE_TOOLS`, override degli URL WASM, ecc.) funziona come nella build Self-Hosted.
|
||||
|
||||
::: warning Licenze
|
||||
L'esecuzione della build Commercial è consentita con una qualsiasi delle due opzioni di licenza di BentoPDF:
|
||||
|
||||
- **AGPL-3.0** (gratuita): consentita se il tuo deploy pubblica tutto il proprio codice sorgente sotto AGPL, comprese le modifiche al brand e la logica di business circostante.
|
||||
- **Licenza commerciale** ($79 a vita): necessaria per deploy proprietari o a codice chiuso in cui non rendi open source il tuo fork di branding o il tuo codice di business.
|
||||
|
||||
Consulta la [pagina delle licenze](https://www.bentopdf.com/licensing) per il confronto completo. I moduli WASM con licenza AGPL (PyMuPDF, Ghostscript, CoherentPDF) vengono caricati da una CDN a runtime, quindi non entrano nella tua immagine e non cambiano la tua situazione di licenza.
|
||||
:::
|
||||
|
||||
### Base URL
|
||||
|
||||
Deploy in una sottodirectory:
|
||||
|
||||
```bash
|
||||
BASE_URL=/pdf-tools/ npm run build
|
||||
```
|
||||
|
||||
### Branding personalizzato
|
||||
|
||||
Sostituisci il logo, il nome e il testo del footer predefiniti di BentoPDF con i tuoi durante la build:
|
||||
|
||||
| Variabile | Descrizione | Predefinito |
|
||||
| ------------------ | ------------------------------------------ | --------------------------------------- |
|
||||
| `VITE_BRAND_NAME` | Nome del brand mostrato in header e footer | `BentoPDF` |
|
||||
| `VITE_BRAND_LOGO` | Percorso del logo relativo a `public/` | `images/favicon-no-bg.svg` |
|
||||
| `VITE_FOOTER_TEXT` | Testo personalizzato del footer/copyright | `© 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
|
||||
```
|
||||
|
||||
Oppure tramite 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 .
|
||||
```
|
||||
|
||||
Il branding funziona sia in modalità completa sia in Simple Mode e può essere combinato con tutte le altre opzioni di build (`BASE_URL`, `SIMPLE_MODE`, `VITE_DEFAULT_LANGUAGE`).
|
||||
|
||||
### Disabilitare strumenti specifici
|
||||
|
||||
Nascondi singoli strumenti per motivi di conformità o sicurezza. Gli strumenti disabilitati vengono rimossi dalla homepage, dalla ricerca, dalle scorciatoie, dal workflow builder e dall'accesso diretto tramite URL.
|
||||
|
||||
Gli ID degli strumenti sono l'URL della pagina senza `.html`: apri uno strumento qualsiasi e guarda l'URL (ad es. `edit-pdf`, `sign-pdf`, `encrypt-pdf`).
|
||||
|
||||
**In fase di build** (incorporata nel bundle):
|
||||
|
||||
```bash
|
||||
DISABLE_TOOLS="edit-pdf,sign-pdf" npm run build
|
||||
```
|
||||
|
||||
**A runtime** (nessuna ricompilazione necessaria: monta 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
|
||||
```
|
||||
|
||||
I due metodi possono essere combinati: gli elenchi vengono uniti.
|
||||
|
||||
Puoi anche disabilitare funzionalità specifiche dell'Editor PDF (ad es. la redazione) senza disabilitare l'intero strumento, aggiungendo `editorDisabledCategories` a `config.json`. Consulta la [guida Docker](/it/self-hosting/docker#disattivare-le-funzionalita-dell-editor) per l'elenco completo delle categorie.
|
||||
|
||||
## Guide al deploy
|
||||
|
||||
Scegli la tua piattaforma:
|
||||
|
||||
- [Vercel](/it/self-hosting/vercel)
|
||||
- [Netlify](/it/self-hosting/netlify)
|
||||
- [Cloudflare Pages](/it/self-hosting/cloudflare)
|
||||
- [AWS S3 + CloudFront](/it/self-hosting/aws)
|
||||
- [Hostinger](/it/self-hosting/hostinger)
|
||||
- [Nginx](/it/self-hosting/nginx)
|
||||
- [Apache](/it/self-hosting/apache)
|
||||
- [Docker](/it/self-hosting/docker)
|
||||
- [Kubernetes](/it/self-hosting/kubernetes)
|
||||
- [CORS Proxy](/it/self-hosting/cors-proxy) - Necessario per le firme digitali
|
||||
|
||||
## Problemi comuni
|
||||
|
||||
### La conversione da Word / ODT / Excel / PowerPoint a PDF si blocca (SharedArrayBuffer non disponibile)
|
||||
|
||||
**Sintomo**: le conversioni di documenti basate su LibreOffice (da Word, ODT, Excel, PowerPoint a PDF) si bloccano intorno al 55% o non partono. La console del browser può mostrare:
|
||||
|
||||
> `ReferenceError: SharedArrayBuffer is not defined`
|
||||
|
||||
…oppure `window.crossOriginIsolated` restituisce `false`, o la compilazione WASM segnala `expected magic word 00 61 73 6d, found 1f 8b 08 08`.
|
||||
|
||||
**Causa**: LibreOffice WASM richiede `SharedArrayBuffer`, che il browser abilita solo quando la pagina è **isolata cross-origin** E servita da un **contesto sicuro**. Ciò significa che devono valere due condizioni:
|
||||
|
||||
1. Ogni risposta include entrambi gli header:
|
||||
- `Cross-Origin-Embedder-Policy: require-corp`
|
||||
- `Cross-Origin-Opener-Policy: same-origin`
|
||||
2. La pagina è servita da `https://...` o da `http://localhost`. Un semplice HTTP su un IP della LAN (ad es. `http://192.168.x.x`) NON conta come sicuro: i browser disabilitano lì `SharedArrayBuffer`.
|
||||
|
||||
La discrepanza `00 61 73 6d / 1f 8b 08 08` è un sotto-sintomo distinto: ai file precompressi `.wasm.gz` / `.data.gz` manca l'header di risposta `Content-Encoding: gzip`, quindi il browser riceve byte gzip grezzi invece di WASM decompresso.
|
||||
|
||||
**Soluzione**: consulta la guida al deploy specifica della tua piattaforma per la configurazione esatta:
|
||||
|
||||
- [Nginx →](/it/self-hosting/nginx#conversione-da-word-odt-excel-a-pdf-non-funzionante)
|
||||
- [Apache →](/it/self-hosting/apache#la-conversione-da-word-odt-excel-a-pdf-non-funziona)
|
||||
- [AWS S3 + CloudFront →](/it/self-hosting/aws#passaggio-3b-policy-degli-header-di-risposta-necessaria-per-libreoffice-wasm)
|
||||
- [Cloudflare Pages →](/it/self-hosting/cloudflare#file-di-configurazione) (`_headers` file)
|
||||
- [Netlify →](/it/self-hosting/netlify#conversione-da-word-odt-excel-a-pdf-bloccata-al-55) (`netlify.toml`)
|
||||
- [Vercel →](/it/self-hosting/vercel#conversione-da-word-odt-excel-a-pdf-non-funzionante) (`vercel.json`)
|
||||
- [Hostinger →](/it/self-hosting/hostinger#gli-strumenti-libreoffice-non-funzionano) (`.htaccess`)
|
||||
- [Kubernetes →](/it/self-hosting/kubernetes#verificare-che-gli-header-sharedarraybuffer-funzionino-ancora-ingress-gateway)
|
||||
- **Docker**: gestito automaticamente dalla configurazione nginx inclusa, nessuna azione necessaria.
|
||||
|
||||
**Verifica**: apri la Console di DevTools su una qualsiasi pagina di BentoPDF ed esegui:
|
||||
|
||||
```js
|
||||
console.log(window.crossOriginIsolated); // should be true
|
||||
console.log(typeof SharedArrayBuffer); // should be "function"
|
||||
```
|
||||
|
||||
Se la pagina è in HTTPS o su `http://localhost` E entrambi gli header COEP/COOP sono presenti in ogni risposta, entrambi i controlli passano. Se sei su `http://192.168.x.x` o su un'altra origine HTTP non di loopback, termina la connessione con HTTPS: non esiste una soluzione basata solo sugli header.
|
||||
|
||||
### File `.mjs` serviti come `application/octet-stream`
|
||||
|
||||
**Sintomo**: Firma PDF / Compilazione moduli / alcuni altri strumenti mostrano un visualizzatore vuoto o non si caricano. La console del browser segnala:
|
||||
|
||||
> `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**: il tuo web server o reverse proxy non ha una mappatura del tipo MIME per i file `.mjs` (il visualizzatore PDF incluso distribuisce ES module con quell'estensione). Molte configurazioni di server predefinite usano `application/octet-stream` per le estensioni non riconosciute, e i browser rifiutano di eseguirlo come ES module.
|
||||
|
||||
**Soluzione**: consulta la guida al deploy specifica della tua piattaforma per lo snippet esatto:
|
||||
|
||||
- [Nginx →](/it/self-hosting/nginx#firma-pdf-o-compilazione-moduli-mostra-un-visualizzatore-vuoto-errore-mime-mjs)
|
||||
- [Apache →](/it/self-hosting/apache#firma-pdf-o-compilazione-moduli-mostra-un-visualizzatore-vuoto-errore-mime-mjs)
|
||||
- [AWS S3 + CloudFront →](/it/self-hosting/aws#passaggio-2-compila-e-carica) (vedi `aws s3 cp ... --include "*.mjs"`)
|
||||
- [Kubernetes →](/it/self-hosting/kubernetes#errori-di-tipo-mime-per-mjs-iframe-di-firma-pdf-compilazione-moduli-vuoto)
|
||||
- **Docker, Vercel, Netlify, Cloudflare Pages, Hostinger**: gestito automaticamente, nessuna azione necessaria.
|
||||
|
||||
**Verifica**: apri DevTools → scheda Network, trova la richiesta `.mjs` che fallisce e controlla l'header di risposta `Content-Type`. Dovrebbe essere `application/javascript`. Se è ancora `application/octet-stream`, un reverse proxy o una CDN esterna potrebbe riconoscere di nuovo il tipo: controlla ogni livello della catena di servizio.
|
||||
|
||||
## Configurazione WASM (componenti AGPL)
|
||||
|
||||
BentoPDF **non include** nel proprio codice sorgente le librerie di elaborazione con licenza AGPL, ma **preconfigura gli URL della CDN** in modo che tutte le funzionalità funzionino subito, senza alcuna configurazione manuale.
|
||||
|
||||
::: tip Zero-config per impostazione predefinita
|
||||
Dalla v2.0.0, i moduli WASM sono preconfigurati per caricarsi dalla CDN jsDelivr tramite variabili d'ambiente. Tutte le funzionalità avanzate funzionano subito senza alcuna configurazione da parte dell'utente.
|
||||
:::
|
||||
|
||||
| Componente | Licenza | Funzionalità |
|
||||
| --------------- | -------- | ---------------------------------------------------------------------------- |
|
||||
| **PyMuPDF** | AGPL-3.0 | Conversione EPUB/MOBI/FB2/XPS, estrazione di immagini, estrazione di tabelle |
|
||||
| **Ghostscript** | AGPL-3.0 | Conversione PDF/A, compressione, raddrizzamento, rasterizzazione |
|
||||
| **CoherentPDF** | AGPL-3.0 | Indice, allegati, unione di PDF con segnalibri |
|
||||
|
||||
### Variabili d'ambiente predefinite
|
||||
|
||||
Sono impostate in `.env.production` e incorporate nella build:
|
||||
|
||||
```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=
|
||||
```
|
||||
|
||||
### Sovrascrivere gli URL WASM
|
||||
|
||||
Puoi sovrascrivere i valori predefiniti in fase di build per deploy personalizzati:
|
||||
|
||||
```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 disabilitare completamente un modulo (richiedendo la configurazione manuale dell'utente tramite le Impostazioni avanzate), imposta la sua variabile su una stringa vuota.
|
||||
|
||||
Per l'OCR, lascia vuote tutte le variabili `VITE_TESSERACT_*` e mantieni gli asset online predefiniti, oppure imposta insieme gli URL di worker/core/lang per un OCR self-hosted/offline. Se includi solo alcune lingue OCR, imposta anche `VITE_TESSERACT_AVAILABLE_LANGUAGES` con gli stessi codici separati da virgola, così l'interfaccia offre solo le lingue installate e le selezioni non supportate falliscono con un errore descrittivo. Per un output PDF ricercabile completamente offline, imposta anche `VITE_OCR_FONT_BASE_URL` sulla directory interna che serve i font OCR inclusi.
|
||||
|
||||
Gli utenti possono anche sovrascrivere questi valori predefiniti in qualsiasi momento tramite le **Impostazioni avanzate** nell'interfaccia: le sovrascritture dell'utente salvate nel browser hanno la priorità sui valori predefiniti dell'ambiente.
|
||||
|
||||
### Deploy air-gapped / offline
|
||||
|
||||
Per le reti senza accesso a internet (enti pubblici, sanità, finanza, ecc.). Gli URL WASM sono incorporati nel JavaScript **in fase di build**, mentre i file WASM veri e propri vengono scaricati dal **browser dell'utente** a runtime. Quindi devi preparare tutto su una macchina con accesso a internet e poi trasferirlo nella rete isolata.
|
||||
|
||||
#### Script automatico (consigliato)
|
||||
|
||||
Lo script `prepare-airgap.sh` incluso automatizza l'intero processo: scarica i pacchetti WASM, compila l'immagine Docker e produce un bundle autonomo con uno script di setup.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Questo produce una directory bundle:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
Trasferisci il bundle nella rete air-gapped tramite USB, repository di artefatti interno o altro metodo approvato. Poi esegui lo script di setup incluso:
|
||||
|
||||
```bash
|
||||
cd bentopdf-airgap-bundle
|
||||
bash setup.sh
|
||||
```
|
||||
|
||||
Lo script di setup carica l'immagine Docker, estrae i file WASM e, facoltativamente, avvia il container.
|
||||
|
||||
**Opzioni dello script:**
|
||||
|
||||
| Flag | Descrizione | Predefinito |
|
||||
| ------------------------------ | ------------------------------------------------------- | ------------------------------------ |
|
||||
| `--wasm-base-url <url>` | Dove verranno ospitati internamente i WASM | _(obbligatorio, richiesto se manca)_ |
|
||||
| `--image-name <name>` | Tag dell'immagine Docker | `bentopdf` |
|
||||
| `--output-dir <path>` | Directory di output del bundle | `./bentopdf-airgap-bundle` |
|
||||
| `--simple-mode` | Abilita Simple Mode | off |
|
||||
| `--base-url <path>` | Base URL della sottodirectory (ad es. `/pdf/`) | `/` |
|
||||
| `--language <code>` | Lingua predefinita dell'interfaccia (ad es. `fr`, `de`) | _(nessuno)_ |
|
||||
| `--brand-name <name>` | Nome del brand personalizzato | _(nessuno)_ |
|
||||
| `--brand-logo <path>` | Percorso del logo relativo a `public/` | _(nessuno)_ |
|
||||
| `--footer-text <text>` | Testo personalizzato del piè di pagina | _(nessuno)_ |
|
||||
| `--ocr-languages <list>` | Lingue OCR da includere, separate da virgola | `eng` |
|
||||
| `--list-ocr-languages` | Stampa i codici e i nomi OCR supportati, poi esce | off |
|
||||
| `--search-ocr-language <term>` | Cerca i codici OCR per nome o abbreviazione | off |
|
||||
| `--dockerfile <path>` | Dockerfile da usare | `Dockerfile` |
|
||||
| `--skip-docker` | Salta la build e l'esportazione di Docker | off |
|
||||
| `--skip-wasm` | Salta il download WASM (riusa i file `.tgz` esistenti) | off |
|
||||
|
||||
Il prompt interattivo accetta anche `list` per stampare l'elenco completo dei codici Tesseract supportati e `search <term>` per trovare corrispondenze come `search german` o `search chi`.
|
||||
|
||||
::: warning Requisito di stessa origine
|
||||
I file WASM devono essere serviti dalla **stessa origine** dell'app BentoPDF. I Web Worker usano `importScripts()`, che non può caricare script cross-origin. Ad esempio, se BentoPDF gira su `https://internal.example.com`, anche il base URL WASM deve essere `https://internal.example.com/wasm`.
|
||||
:::
|
||||
|
||||
#### Passaggi manuali
|
||||
|
||||
<details>
|
||||
<summary>Se preferisci procedere manualmente senza lo script</summary>
|
||||
|
||||
**Passaggio 1: scarica i pacchetti WASM e OCR** (su una macchina con accesso a 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
|
||||
```
|
||||
|
||||
**Passaggio 2: compila l'immagine Docker con gli URL interni**
|
||||
|
||||
```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 .
|
||||
```
|
||||
|
||||
**Passaggio 3: esporta l'immagine Docker**
|
||||
|
||||
```bash
|
||||
docker save bentopdf -o bentopdf.tar
|
||||
```
|
||||
|
||||
**Passaggio 4: trasferisci nella rete air-gapped**
|
||||
|
||||
Copia tramite USB, repository di artefatti interno o altro metodo di trasferimento approvato:
|
||||
|
||||
- `bentopdf.tar`: l'immagine Docker
|
||||
- I cinque pacchetti `.tgz` WASM/OCR del Passaggio 1
|
||||
- La directory `tesseract-langdata/` del Passaggio 1
|
||||
- La directory `ocr-fonts/` del Passaggio 1
|
||||
|
||||
**Passaggio 5: configura all'interno della rete air-gapped**
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Assicurati che i file siano accessibili agli URL configurati nel Passaggio 2, inclusi `.../ocr/worker.min.js`, `.../ocr/core`, `.../ocr/lang-data`, and `.../ocr/fonts`.
|
||||
|
||||
</details>
|
||||
|
||||
::: info Compili dal sorgente invece di usare Docker?
|
||||
Imposta le variabili in `.env.production` prima di eseguire `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
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Ospitare il tuo proxy WASM
|
||||
|
||||
Se devi servire i file WASM AGPL con gli header CORS corretti, puoi distribuire un semplice proxy. Consulta la [guida al proxy WASM di Cloudflare](https://github.com/alam00000/bentopdf/blob/main/cloudflare/WASM-PROXY.md) per un esempio di implementazione.
|
||||
|
||||
::: tip Perché separati?
|
||||
Questa separazione garantisce:
|
||||
|
||||
- Chiara conformità legale per gli utenti commerciali
|
||||
- Il nucleo di BentoPDF resta sotto la sua doppia licenza (AGPL-3.0 / Commercial)
|
||||
- I file WASM vengono caricati a runtime, non inclusi nel sorgente
|
||||
:::
|
||||
|
||||
## Requisiti di sistema
|
||||
|
||||
| Requisito | Minimo |
|
||||
| --------- | ---------------------------------- |
|
||||
| Spazio | ~100 MB (nucleo senza moduli AGPL) |
|
||||
| RAM | 512 MB |
|
||||
| CPU | Qualsiasi processore moderno |
|
||||
|
||||
::: tip
|
||||
BentoPDF è un sito statico: non servono database né server backend!
|
||||
:::
|
||||
@@ -0,0 +1,211 @@
|
||||
# Deploy con Kubernetes
|
||||
|
||||
Kubernetes può essere eccessivo per un sito statico, ma è un'ottima scelta se usi già Helm + GitOps come standard.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Header necessari per la conversione dei file Office**
|
||||
>
|
||||
> Gli strumenti basati su LibreOffice (conversione di Word, Excel, PowerPoint) richiedono questi header HTTP per il supporto a `SharedArrayBuffer`:
|
||||
>
|
||||
> - `Cross-Origin-Opener-Policy: same-origin`
|
||||
> - `Cross-Origin-Embedder-Policy: require-corp`
|
||||
>
|
||||
> Le immagini nginx ufficiali di BentoPDF includono questi header. In Kubernetes, **anche i controller Ingress/Gateway sono reverse proxy**, quindi assicurati che questi header vengano preservati (oppure aggiungili al livello edge).
|
||||
|
||||
## Prerequisiti
|
||||
|
||||
- Cluster Kubernetes
|
||||
- Helm v3
|
||||
- Un'immagine nginx di BentoPDF (ad es. `ghcr.io/alam00000/bentopdf:<tag>`) che serve sulla **porta 8080**
|
||||
|
||||
## Deploy con Helm
|
||||
|
||||
### Installazione da questo repository (chart locale)
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Installazione da GHCR (chart OCI)
|
||||
|
||||
Se il chart è pubblicato su GHCR come artefatto 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
|
||||
```
|
||||
|
||||
## Esporre il servizio
|
||||
|
||||
### Port-forward (test rapido)
|
||||
|
||||
```bash
|
||||
kubectl -n bentopdf port-forward deploy/bentopdf 8080:8080
|
||||
```
|
||||
|
||||
### Ingress (facoltativo)
|
||||
|
||||
Abilita Ingress (esempio per nginx-ingress):
|
||||
|
||||
```yaml
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
hosts:
|
||||
- host: pdf.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
```
|
||||
|
||||
### Gateway API (facoltativo)
|
||||
|
||||
Questo chart supporta `Gateway` + `HTTPRoute` della Gateway API.
|
||||
|
||||
Esempio (operatore Gateway API di 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
|
||||
```
|
||||
|
||||
## Verificare che gli header SharedArrayBuffer funzionino ancora (Ingress/Gateway)
|
||||
|
||||
### Cosa "dovrebbe" succedere
|
||||
|
||||
La configurazione nginx di BentoPDF imposta gli header di risposta necessari. La maggior parte dei controller Ingress/Gateway **inoltra invariati gli header di risposta dell'upstream**.
|
||||
|
||||
### Cosa può romperlo
|
||||
|
||||
- Una policy del controller o dell'edge che **sovrascrive** o **rimuove** gli header di risposta
|
||||
- Un middleware di "security headers" che imposta valori COOP/COEP diversi
|
||||
|
||||
### Come verificare
|
||||
|
||||
Esegui questo comando sul tuo endpoint pubblico:
|
||||
|
||||
```bash
|
||||
curl -I https://pdf.example.com/ | egrep -i 'cross-origin-opener-policy|cross-origin-embedder-policy'
|
||||
```
|
||||
|
||||
Dovresti vedere:
|
||||
|
||||
- `Cross-Origin-Opener-Policy: same-origin`
|
||||
- `Cross-Origin-Embedder-Policy: require-corp`
|
||||
|
||||
### Se il tuo controller Ingress non li preserva
|
||||
|
||||
Aggiungi gli header al livello edge (dipende dal controller). Esempio per **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;
|
||||
```
|
||||
|
||||
## Errori di tipo MIME per `.mjs` (iframe di Firma PDF / Compilazione moduli vuoto)
|
||||
|
||||
Se la console del browser mostra `Failed to load module script: ... non-JavaScript MIME type "application/octet-stream"` su una richiesta `.mjs`, un controller Ingress/Gateway davanti all'nginx di BentoPDF sta sostituendo l'header `Content-Type` dell'upstream con `application/octet-stream`.
|
||||
|
||||
L'nginx dell'immagine di BentoPDF serve già i file `.mjs` come `application/javascript`. La soluzione è impedire al controller di rimuoverlo o sostituirlo. Per nginx-ingress:
|
||||
|
||||
```yaml
|
||||
ingress:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
location ~* \.mjs$ {
|
||||
types {} default_type application/javascript;
|
||||
}
|
||||
```
|
||||
|
||||
Oppure usa un filtro `ResponseHeaderModifier` nella Gateway API per impostare `Content-Type: application/javascript` sui percorsi `.mjs`. Verifica con DevTools → scheda Network: la richiesta `.mjs` che falliva dovrebbe ora mostrare `content-type: application/javascript`.
|
||||
|
||||
### Se usi la Gateway API e vuoi aggiungere gli header in modo forzato
|
||||
|
||||
La Gateway API supporta un filtro `ResponseHeaderModifier`. Puoi associarlo in `httpRoute.rules[*].filters`:
|
||||
|
||||
```yaml
|
||||
httpRoute:
|
||||
enabled: true
|
||||
hostnames: [pdf.example.com]
|
||||
parentRefs:
|
||||
- name: bento-tunnel
|
||||
namespace: misc
|
||||
sectionName: http
|
||||
rules:
|
||||
- matches:
|
||||
- path: { type: PathPrefix, value: / }
|
||||
filters:
|
||||
- type: ResponseHeaderModifier
|
||||
responseHeaderModifier:
|
||||
set:
|
||||
- name: Cross-Origin-Opener-Policy
|
||||
value: same-origin
|
||||
- name: Cross-Origin-Embedder-Policy
|
||||
value: require-corp
|
||||
```
|
||||
|
||||
Il supporto per filtri specifici dipende dal tuo controller Gateway; se un filtro viene ignorato, aggiungi gli header al livello edge/controller.
|
||||
|
||||
## Disabilitare strumenti specifici
|
||||
|
||||
Usa una ConfigMap per disabilitare gli strumenti a runtime senza ricostruire l'immagine:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: bentopdf-config
|
||||
namespace: bentopdf
|
||||
data:
|
||||
config.json: |
|
||||
{
|
||||
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
|
||||
}
|
||||
```
|
||||
|
||||
Montala nella directory servita:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Gli ID degli strumenti sono l'URL della pagina senza `.html`: apri uno strumento qualsiasi e guarda l'URL (ad es. `edit-pdf`, `merge-pdf`, `compress-pdf`). Gli strumenti disabilitati sono nascosti dalla homepage, dalla ricerca, dalle scorciatoie, dal workflow builder e dall'accesso diretto tramite URL. Consulta la [guida Docker](/it/self-hosting/docker#disattivare-strumenti-specifici) per l'elenco completo delle opzioni.
|
||||
@@ -0,0 +1,139 @@
|
||||
# Deploy su Netlify
|
||||
|
||||
[Netlify](https://netlify.com) offre un ottimo hosting per siti statici con un generoso piano gratuito.
|
||||
|
||||
## Deploy con un clic
|
||||
|
||||
[](https://app.netlify.com/start/deploy?repository=https://github.com/alam00000/bentopdf)
|
||||
|
||||
## Deploy manuale
|
||||
|
||||
### Passaggio 1: collega il repository
|
||||
|
||||
1. Accedi a [Netlify](https://app.netlify.com)
|
||||
2. Fai clic su "Add new site" → "Import an existing project"
|
||||
3. Collega il tuo account GitHub
|
||||
4. Seleziona il tuo fork di BentoPDF
|
||||
|
||||
### Passaggio 2: configura le impostazioni di build
|
||||
|
||||
| Impostazione | Valore |
|
||||
| -------------------------- | --------------- |
|
||||
| Comando di build | `npm run build` |
|
||||
| Directory di pubblicazione | `dist` |
|
||||
| Versione di Node | 18+ |
|
||||
|
||||
### Passaggio 3: esegui il deploy
|
||||
|
||||
Fai clic su "Deploy site" e attendi il completamento della build.
|
||||
|
||||
## File di configurazione
|
||||
|
||||
Crea `netlify.toml` nella radice del tuo progetto:
|
||||
|
||||
```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 Importante
|
||||
Gli header `Cross-Origin-Embedder-Policy` e `Cross-Origin-Opener-Policy` sono necessari per le conversioni da Word/ODT/Excel/PowerPoint a PDF. Senza di essi, `SharedArrayBuffer` non è disponibile e il motore WASM di LibreOffice non riesce a inizializzarsi.
|
||||
|
||||
Gli header `Content-Encoding: gzip` sui file `.wasm.gz` e `.data.gz` indicano al browser di decomprimerli automaticamente. Senza di essi, il browser riceve byte gzip grezzi e la compilazione WASM fallisce.
|
||||
:::
|
||||
|
||||
## Variabili d'ambiente
|
||||
|
||||
Impostale in Site settings → Environment variables:
|
||||
|
||||
| Variabile | Descrizione |
|
||||
| ----------------------- | -------------------------------------------------------------------- |
|
||||
| `SIMPLE_MODE` | Imposta su `true` per una build minimale |
|
||||
| `VITE_BRAND_NAME` | Nome del brand personalizzato (sostituisce "BentoPDF") |
|
||||
| `VITE_BRAND_LOGO` | Percorso del logo relativo a `public/` (ad es. `images/my-logo.svg`) |
|
||||
| `VITE_FOOTER_TEXT` | Testo personalizzato del footer/copyright |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Lingua predefinita dell'interfaccia (ad es. `fr`, `de`, `es`) |
|
||||
|
||||
## Dominio personalizzato
|
||||
|
||||
1. Vai su Site settings → Domain management
|
||||
2. Fai clic su "Add custom domain"
|
||||
3. Segui le istruzioni per la configurazione DNS
|
||||
|
||||
## Large Media
|
||||
|
||||
Per i file WASM di grandi dimensioni, valuta di abilitare [Netlify Large Media](https://docs.netlify.com/large-media/overview/):
|
||||
|
||||
```bash
|
||||
netlify lm:setup
|
||||
git lfs track "*.wasm"
|
||||
```
|
||||
|
||||
## Risoluzione dei problemi
|
||||
|
||||
### Conversione da Word/ODT/Excel a PDF bloccata al 55%
|
||||
|
||||
Se le conversioni dei documenti si bloccano al 55%, apri la Console di DevTools e controlla:
|
||||
|
||||
```js
|
||||
console.log(window.crossOriginIsolated); // should be true
|
||||
console.log(typeof SharedArrayBuffer); // should be "function"
|
||||
```
|
||||
|
||||
Se `crossOriginIsolated` è `false`, gli header COEP/COOP del tuo `netlify.toml` non vengono applicati. Assicurati che il file sia nella radice del progetto e ripeti il deploy.
|
||||
|
||||
Se nella console vedi `expected magic word 00 61 73 6d, found 1f 8b 08 08`, ai file `.wasm.gz` mancano gli header `Content-Encoding: gzip`. Assicurati che i blocchi `[[headers]]` per `soffice.wasm.gz` e `soffice.data.gz` siano presenti nel tuo `netlify.toml`.
|
||||
|
||||
### La build fallisce
|
||||
|
||||
Controlla la compatibilità della versione di Node:
|
||||
|
||||
```toml
|
||||
[build.environment]
|
||||
NODE_VERSION = "20"
|
||||
```
|
||||
|
||||
### Caricamento iniziale lento
|
||||
|
||||
Abilita l'ottimizzazione degli asset in Site settings → Build & deploy → Asset optimization.
|
||||
@@ -0,0 +1,232 @@
|
||||
# Deploy con Nginx
|
||||
|
||||
Ospita BentoPDF sul tuo server usando Nginx.
|
||||
|
||||
## Prerequisiti
|
||||
|
||||
- Server Ubuntu/Debian
|
||||
- Nginx installato
|
||||
- Certificato SSL (consigliato: Let's Encrypt)
|
||||
|
||||
## Passaggio 1: compila il progetto
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
Per personalizzare il brand, imposta le variabili d'ambiente prima della build:
|
||||
|
||||
```bash
|
||||
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
|
||||
```
|
||||
|
||||
## Passaggio 2: copia i file
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/www/bentopdf
|
||||
sudo cp -r dist/* /var/www/bentopdf/
|
||||
sudo chown -R www-data:www-data /var/www/bentopdf
|
||||
```
|
||||
|
||||
## Passaggio 3: configurazione di 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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Passaggio 4: abilita il sito
|
||||
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
## Passaggio 5: SSL con Let's Encrypt
|
||||
|
||||
```bash
|
||||
sudo apt install certbot python3-certbot-nginx
|
||||
sudo certbot --nginx -d your-domain.com
|
||||
```
|
||||
|
||||
## Deploy in una sottodirectory
|
||||
|
||||
Per ospitarlo su `/pdf/`:
|
||||
|
||||
```nginx
|
||||
location /pdf/ {
|
||||
alias /var/www/bentopdf/;
|
||||
try_files $uri $uri/ /pdf/index.html;
|
||||
}
|
||||
```
|
||||
|
||||
Compila con:
|
||||
|
||||
```bash
|
||||
BASE_URL=/pdf/ npm run build
|
||||
```
|
||||
|
||||
## Ottimizzazione delle prestazioni
|
||||
|
||||
Aggiungi 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;
|
||||
}
|
||||
```
|
||||
|
||||
## Risoluzione dei problemi
|
||||
|
||||
### WASM non si carica
|
||||
|
||||
Assicurati che il tipo MIME sia impostato:
|
||||
|
||||
```nginx
|
||||
types {
|
||||
application/wasm wasm;
|
||||
}
|
||||
```
|
||||
|
||||
### Firma PDF o Compilazione moduli mostra un visualizzatore vuoto (errore MIME `.mjs`)
|
||||
|
||||
Se la console del browser 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.
|
||||
```
|
||||
|
||||
…e la richiesta che fallisce termina con `.mjs`, al tuo nginx manca la mappatura `.mjs → application/javascript`. Il visualizzatore PDF incluso (usato da Firma PDF, Compilazione moduli e altri strumenti) distribuisce file ES module con estensione `.mjs`, e il `mime.types` predefinito di nginx non li include.
|
||||
|
||||
Risolvi aggiungendo `mjs` alla riga `application/javascript` esistente in `/etc/nginx/mime.types`:
|
||||
|
||||
```nginx
|
||||
application/javascript js mjs;
|
||||
```
|
||||
|
||||
> NON aggiungere una _seconda_ riga `application/javascript`: nginx legge `mime.types` riga per riga e vince una sola mappatura tipo-estensione, quindi una seconda riga viene ignorata in silenzio su alcune versioni. Modifica la riga esistente aggiungendo `mjs` accanto a `js`.
|
||||
|
||||
Oppure sovrascrivi localmente nel tuo blocco server (senza modificare il file globale):
|
||||
|
||||
```nginx
|
||||
types {
|
||||
application/javascript js mjs;
|
||||
application/wasm wasm;
|
||||
}
|
||||
```
|
||||
|
||||
Ricarica nginx dopo la modifica:
|
||||
|
||||
```bash
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
Per confermare: in DevTools del browser → scheda Network, trova la richiesta `.mjs` che falliva e controlla l'header di risposta `Content-Type`. Ora dovrebbe essere `application/javascript`, non `application/octet-stream`.
|
||||
|
||||
### Conversione da Word/ODT/Excel a PDF non funzionante
|
||||
|
||||
LibreOffice WASM richiede `SharedArrayBuffer`, che necessita degli header `Cross-Origin-Embedder-Policy` e `Cross-Origin-Opener-Policy`. Serve anche un contesto sicuro: `http://localhost` funziona per i test in locale, ma `http://192.168.x.x` o altri IP della LAN di solito richiedono HTTPS. Nota che le direttive `add_header` di nginx in un blocco `location` **sovrascrivono** quelle a livello di server, non si uniscono. Ogni blocco `location` con il proprio `add_header` deve includere gli header COEP/COOP.
|
||||
|
||||
Verifica con:
|
||||
|
||||
```bash
|
||||
curl -I https://your-domain.com/ | grep -i cross-origin
|
||||
```
|
||||
|
||||
Se usi un reverse proxy davanti a nginx, assicurati che preservi questi header.
|
||||
|
||||
### 502 Bad Gateway
|
||||
|
||||
Controlla i log degli errori di Nginx:
|
||||
|
||||
```bash
|
||||
sudo tail -f /var/log/nginx/error.log
|
||||
```
|
||||
@@ -0,0 +1,122 @@
|
||||
# Deploy su Vercel
|
||||
|
||||
[Vercel](https://vercel.com) offre l'esperienza di deploy più semplice per i siti statici.
|
||||
|
||||
## Deploy con un clic
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https://github.com/alam00000/bentopdf)
|
||||
|
||||
## Deploy manuale
|
||||
|
||||
### Passaggio 1: fai il fork del repository
|
||||
|
||||
Fai il fork di [bentopdf/bentopdf](https://github.com/alam00000/bentopdf) sul tuo account GitHub.
|
||||
|
||||
### Passaggio 2: importa su Vercel
|
||||
|
||||
1. Vai su [vercel.com/new](https://vercel.com/new)
|
||||
2. Seleziona il tuo repository forkato
|
||||
3. Configura il progetto:
|
||||
|
||||
| Impostazione | Valore |
|
||||
| ---------------- | --------------- |
|
||||
| Framework Preset | Vite |
|
||||
| Build Command | `npm run build` |
|
||||
| Output Directory | `dist` |
|
||||
| Install Command | `npm install` |
|
||||
|
||||
### Passaggio 3: variabili d'ambiente (facoltativo)
|
||||
|
||||
Aggiungile se necessario:
|
||||
|
||||
| Variabile | Descrizione |
|
||||
| ----------------------- | -------------------------------------------------------------------- |
|
||||
| `SIMPLE_MODE` | Imposta su `true` per un'interfaccia minimale |
|
||||
| `VITE_BRAND_NAME` | Nome del brand personalizzato (sostituisce "BentoPDF") |
|
||||
| `VITE_BRAND_LOGO` | Percorso del logo relativo a `public/` (ad es. `images/my-logo.svg`) |
|
||||
| `VITE_FOOTER_TEXT` | Testo personalizzato del footer/copyright |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Lingua predefinita dell'interfaccia (ad es. `fr`, `de`, `es`) |
|
||||
|
||||
### Passaggio 4: esegui il deploy
|
||||
|
||||
Fai clic su "Deploy" e attendi il completamento della build.
|
||||
|
||||
## Dominio personalizzato
|
||||
|
||||
1. Vai alle impostazioni del progetto
|
||||
2. Apri "Domains"
|
||||
3. Aggiungi il tuo dominio personalizzato
|
||||
4. Configura il DNS come indicato
|
||||
|
||||
## Limitazioni
|
||||
|
||||
::: warning File di grandi dimensioni
|
||||
Le funzioni serverless di Vercel hanno un limite di 50MB. Poiché BentoPDF è un sito statico, questo non dovrebbe riguardarti, ma i moduli WASM sono grandi (~100MB in totale). Assicurati che vengano serviti dalla cartella `/public`.
|
||||
:::
|
||||
|
||||
## Risoluzione dei problemi
|
||||
|
||||
### Timeout della build
|
||||
|
||||
Se le build vanno in timeout, prova:
|
||||
|
||||
```json
|
||||
// vercel.json
|
||||
{
|
||||
"buildCommand": "npm run build",
|
||||
"outputDirectory": "dist"
|
||||
}
|
||||
```
|
||||
|
||||
### 404 al ricaricamento
|
||||
|
||||
Aggiungi un `vercel.json` per il routing SPA:
|
||||
|
||||
```json
|
||||
{
|
||||
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
|
||||
}
|
||||
```
|
||||
|
||||
### Conversione da Word/ODT/Excel a PDF non funzionante
|
||||
|
||||
Le conversioni WASM di LibreOffice richiedono `SharedArrayBuffer`, che necessita di questi header di risposta su tutte le pagine:
|
||||
|
||||
- `Cross-Origin-Embedder-Policy: require-corp`
|
||||
- `Cross-Origin-Opener-Policy: same-origin`
|
||||
|
||||
Anche i file precompressi `.wasm.gz` e `.data.gz` richiedono `Content-Encoding: gzip`, così il browser li decomprime.
|
||||
|
||||
Aggiungili al tuo `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 verificare, apri la Console di DevTools ed esegui `console.log(window.crossOriginIsolated)`: dovrebbe restituire `true`.
|
||||
Reference in new issue
Block a user