feat: translate all pages, UI messages and docs and fix SEO redirects and indexing

This commit is contained in:
alam00000 committed 2026-10-25 12:55:21 +05:30
1 parent 5e019de5ae
commit 4edb1718cc
3344 files changed
+415643 -4759

No files matched your search

+275
View File
@@ -0,0 +1,275 @@
# 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
```
+200
View File
@@ -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"
}
}
```
+154
View File
@@ -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.
+180
View File
@@ -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
+622
View File
@@ -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
```
+266
View File
@@ -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.
+538
View File
@@ -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!
:::
+211
View File
@@ -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.
+139
View File
@@ -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
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](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.
+232
View File
@@ -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
```
+122
View File
@@ -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
[![Deploy with Vercel](https://vercel.com/button)](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`.