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 @@
# Déployer avec Apache
Hébergez BentoPDF avec Apache HTTP Server.
## Prérequis
- Apache 2.4+
- mod_rewrite activé
- Certificat SSL (recommandé)
## Étape 1 : compiler le projet
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
Pour personnaliser l'image de marque, définissez des variables d'environnement avant la compilation :
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## Étape 2 : copier les fichiers
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## Étape 3 : configuration d'Apache
Créez `/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>
```
## Étape 4 : .htaccess pour le routage
Créez `/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
```
## Étape 5 : activer les modules requis
```bash
sudo a2enmod rewrite
sudo a2enmod ssl
sudo a2enmod headers
sudo a2enmod expires
sudo a2enmod deflate
```
## Étape 6 : activer le site
```bash
sudo a2ensite bentopdf.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
```
## Déploiement dans un sous-répertoire
Pour héberger sous `/pdf/` :
1. Compilez avec l'URL de base :
```bash
BASE_URL=/pdf/ npm run build
```
2. Mettez à jour `.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
```
## Dépannage
### Erreurs 404 sur les fichiers WASM
Vérifiez que le type MIME est configuré :
```apache
AddType application/wasm .wasm
```
### Signer un PDF ou le remplissage de formulaires affiche un visualiseur vide (erreur MIME `.mjs`)
Si la console du navigateur affiche :
```
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.
```
…et que la requête en échec se termine par `.mjs`, il manque à votre configuration Apache l'association `.mjs → application/javascript`. Le visualiseur PDF fourni livre des modules ES avec l'extension `.mjs`, et le fichier `mime.types` standard d'Apache ne les inclut pas par défaut.
Ajoutez à votre VirtualHost ou à `.htaccess` :
```apache
AddType application/javascript .mjs
```
Rechargez ensuite Apache :
```bash
sudo apache2ctl configtest && sudo systemctl reload apache2
```
Pour vérifier : dans les DevTools du navigateur → onglet Réseau, trouvez la requête `.mjs` en échec et consultez l'en-tête de réponse `Content-Type`. Il doit maintenant valoir `application/javascript`, et non `application/octet-stream`.
### La réécriture ne fonctionne pas
Vérifiez que mod_rewrite est activé :
```bash
sudo a2enmod rewrite
```
### La conversion Word/ODT/Excel vers PDF ne fonctionne pas
LibreOffice WASM nécessite `SharedArrayBuffer`, qui exige ces en-têtes :
```apache
Header always set Cross-Origin-Embedder-Policy "require-corp"
Header always set Cross-Origin-Opener-Policy "same-origin"
```
Un contexte sécurisé est également nécessaire. `http://localhost` fonctionne pour les tests locaux, mais `http://192.168.x.x` ou d'autres adresses IP du réseau local nécessitent généralement HTTPS. Si les en-têtes sont présents mais que `window.crossOriginIsolated` vaut toujours `false`, vérifiez si la page est ouverte en HTTP simple sur une origine autre que le bouclage local.
Les fichiers précompressés `.wasm.gz` et `.data.gz` ont aussi besoin du bon `Content-Encoding` :
```apache
<FilesMatch "soffice\.wasm\.gz$">
ForceType application/wasm
Header set Content-Encoding "gzip"
</FilesMatch>
<FilesMatch "soffice\.data\.gz$">
ForceType application/octet-stream
Header set Content-Encoding "gzip"
</FilesMatch>
```
Vérifiez que `mod_headers` est activé : `sudo a2enmod headers`
### Permission refusée
```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 @@
# Déployer sur AWS S3 + CloudFront
Hébergez BentoPDF sur AWS pour un contrôle et une évolutivité maximaux.
## Architecture
```
User → CloudFront (CDN) → S3 (Static Files)
```
## Étape 1 : créer le 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
```
## Étape 2 : compiler et téléverser
```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
```
## Étape 3 : créer la distribution CloudFront
```bash
aws cloudfront create-distribution \
--origin-domain-name your-bentopdf-bucket.s3.amazonaws.com \
--default-root-object index.html
```
Ou utilisez la console AWS :
1. Allez dans CloudFront → Create distribution
2. Origin domain : sélectionnez votre bucket S3
3. Activez « Origin Access Control »
4. Default root object : `index.html`
5. Créez la distribution
## Étape 3b : politique d'en-têtes de réponse (requise pour LibreOffice WASM)
Les conversions basées sur LibreOffice (Word, Excel, PowerPoint vers PDF) nécessitent `SharedArrayBuffer`, qui exige des en-têtes de réponse spécifiques. Créez une politique d'en-têtes de réponse CloudFront :
1. Allez dans CloudFront → Policies → Response headers
2. Créez une politique personnalisée avec ces en-têtes :
| Header | Value |
| ------------------------------ | -------------- |
| `Cross-Origin-Embedder-Policy` | `require-corp` |
| `Cross-Origin-Opener-Policy` | `same-origin` |
| `Cross-Origin-Resource-Policy` | `cross-origin` |
3. Associez la politique au comportement de cache par défaut de votre distribution
Ou via la 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}
]
}
}'
```
## Étape 3c : métadonnées S3 pour les fichiers WASM précompressés
Les fichiers WASM de LibreOffice sont précompressés (`.wasm.gz`, `.data.gz`). Définissez le bon Content-Type et le bon Content-Encoding pour que les navigateurs les décompressent :
```bash
# Set correct headers for soffice.wasm.gz
aws s3 cp s3://your-bentopdf-bucket/libreoffice-wasm/soffice.wasm.gz \
s3://your-bentopdf-bucket/libreoffice-wasm/soffice.wasm.gz \
--content-type "application/wasm" \
--content-encoding "gzip" \
--metadata-directive REPLACE
# Set correct headers for soffice.data.gz
aws s3 cp s3://your-bentopdf-bucket/libreoffice-wasm/soffice.data.gz \
s3://your-bentopdf-bucket/libreoffice-wasm/soffice.data.gz \
--content-type "application/octet-stream" \
--content-encoding "gzip" \
--metadata-directive REPLACE
```
::: warning Important
Sans la politique d'en-têtes de réponse, `SharedArrayBuffer` n'est pas disponible et les conversions LibreOffice WASM resteront bloquées à environ 55 %. Sans le bon Content-Encoding sur les fichiers `.gz`, le navigateur reçoit des octets gzip bruts et la compilation WASM échoue.
:::
## Étape 4 : politique du bucket S3
Autorisez CloudFront à accéder au 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"
}
}
}
]
}
```
## Étape 5 : pages d'erreur personnalisées
Configurez la 404 pour renvoyer `index.html` afin de gérer le routage SPA :
1. CloudFront → Error pages
2. Créez une réponse d'erreur personnalisée :
- HTTP error code : 404
- Response page path : `/index.html`
- HTTP response code : 200
## Estimation des coûts
| Ressource | Coût estimé |
| ----------------------------- | ------------ |
| Stockage S3 (~500 Mo) | ~0,01 $/mois |
| CloudFront (1 To transféré) | ~85 $/mois |
| CloudFront (10 Go transférés) | ~0,85 $/mois |
::: tip
Utilisez S3 Intelligent Tiering pour optimiser les coûts des fichiers rarement consultés.
:::
## Automatisation avec 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 @@
# Déployer sur Cloudflare Pages
[Cloudflare Pages](https://pages.cloudflare.com) propose un hébergement de sites statiques rapide et mondial, avec une bande passante illimitée.
## Déploiement rapide
1. Allez sur [Cloudflare Pages](https://dash.cloudflare.com/?to=/:account/pages)
2. Cliquez sur « Create a project »
3. Connectez votre dépôt GitHub
## Configuration du build
| Paramètre | Valeur |
| ---------------------- | --------------- |
| Framework preset | None |
| Build command | `npm run build` |
| Build output directory | `dist` |
| Root directory | `/` |
## Variables d'environnement
Ajoutez-les dans Settings → Environment variables :
| Variable | Valeur |
| ----------------------- | ---------------------------------------------------------- |
| `NODE_VERSION` | `18` |
| `SIMPLE_MODE` | `false` (facultatif) |
| `VITE_BRAND_NAME` | Nom de marque personnalisé (facultatif) |
| `VITE_BRAND_LOGO` | Chemin du logo relatif à `public/` (facultatif) |
| `VITE_FOOTER_TEXT` | Texte de pied de page/copyright personnalisé (facultatif) |
| `VITE_DEFAULT_LANGUAGE` | Langue par défaut de l'interface, p. ex. `fr` (facultatif) |
## Fichier de configuration
Créez `_headers` dans votre dossier `public` :
```
# Required security headers for SharedArrayBuffer (used by LibreOffice WASM)
/*
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: cross-origin
# Pre-compressed LibreOffice WASM binary
/libreoffice-wasm/soffice.wasm.gz
Content-Type: application/wasm
Content-Encoding: gzip
Cache-Control: public, max-age=31536000, immutable
# Pre-compressed LibreOffice WASM data
/libreoffice-wasm/soffice.data.gz
Content-Type: application/octet-stream
Content-Encoding: gzip
Cache-Control: public, max-age=31536000, immutable
# Cache WASM files aggressively
/*.wasm
Cache-Control: public, max-age=31536000, immutable
Content-Type: application/wasm
# Service worker
/sw.js
Cache-Control: no-cache
```
::: warning Important
Les en-têtes `Cross-Origin-Embedder-Policy` et `Cross-Origin-Opener-Policy` sont requis pour les conversions Word/ODT/Excel/PowerPoint vers PDF. Sans eux, `SharedArrayBuffer` n'est pas disponible et le moteur LibreOffice WASM ne parviendra pas à s'initialiser.
:::
Créez `_redirects` pour le routage SPA :
```
/* /index.html 200
```
## Domaine personnalisé
1. Ouvrez votre projet Pages
2. Cliquez sur « Custom domains »
3. Ajoutez votre domaine
4. Cloudflare configurera automatiquement le DNS si le domaine est chez Cloudflare
## Avantages
- **Bande passante illimitée et gratuite**
- **CDN mondial** avec plus de 300 points de présence
- **HTTPS automatique**
- **Déploiements de prévisualisation** pour les pull requests
- **Builds rapides**
## Dépannage
### Téléversement de gros fichiers
Cloudflare Pages prend en charge les fichiers jusqu'à 25 Mo. Les modules WASM ne devraient pas poser de problème, mais si vous atteignez les limites, envisagez :
```bash
# Split large files during build
npm run build
```
### Limites de taille des Workers
Si vous utilisez Cloudflare Workers pour un routage avancé, notez la limite de 1 Mo des offres gratuites.
## Worker proxy CORS (pour les signatures numériques)
L'outil de signature numérique nécessite un proxy CORS pour récupérer les chaînes de certificats. Déployez le worker fourni :
```bash
cd cloudflare
npx wrangler login
npx wrangler deploy
```
### Fonctionnalités de sécurité
| Fonctionnalité | Description |
| -------------------------- | --------------------------------------------- |
| **Restrictions d'URL** | Seules les URL de certificats sont autorisées |
| **Limite de taille** | 10 Mo max par requête |
| **Limitation de débit** | 60 req/IP/min (nécessite KV) |
| **Blocage des IP privées** | Bloque localhost et les IP internes |
### Activer la limitation de débit
```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
```
### Compiler avec l'URL du proxy
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
Ou avec 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 .
```
> **Remarque :** Consultez le [README](https://github.com/alam00000/bentopdf#digital-signature-cors-proxy-required) pour la configuration de la signature HMAC.
+180
View File
@@ -0,0 +1,180 @@
# Proxy CORS pour la récupération des certificats
L'outil de signature numérique utilise un proxy CORS pour récupérer les certificats des émetteurs auprès d'autorités de certification (AC) externes. C'est nécessaire, car de nombreux serveurs d'AC n'incluent pas d'en-têtes CORS dans leurs réponses, ce qui empêche la récupération directe depuis le navigateur.
De plus, de nombreux serveurs d'AC diffusent leurs certificats en HTTP simple. Lorsque votre instance BentoPDF est servie en HTTPS, les navigateurs bloquent ces requêtes HTTP (politique de contenu mixte). Le proxy CORS résout ces deux problèmes en faisant transiter les requêtes par un point de terminaison HTTPS avec les bons en-têtes.
## Fonctionnement
Lors de la signature d'un PDF avec un certificat :
1. La bibliothèque `zgapdfsigner` tente de construire une chaîne de certificats complète
2. Elle récupère les certificats des émetteurs à partir des URL intégrées à l'extension AIA (Authority Information Access) de votre certificat
3. Ces requêtes passent par un proxy CORS qui ajoute les en-têtes `Access-Control-Allow-Origin` nécessaires
4. Le proxy renvoie les données du certificat au navigateur
## Auto-héberger le proxy CORS
Si vous auto-hébergez BentoPDF, vous devrez déployer votre propre proxy CORS pour que les signatures numériques fonctionnent avec les certificats qui nécessitent la récupération de la chaîne.
### Option 1 : Cloudflare Workers (recommandé)
1. **Installez la CLI Wrangler** :
```bash
npm install -g wrangler
```
2. **Connectez-vous à Cloudflare** :
```bash
wrangler login
```
3. **Clonez BentoPDF et mettez à jour les origines autorisées** :
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf/cloudflare
```
Ouvrez `cors-proxy-worker.js` et remplacez le tableau `ALLOWED_ORIGINS` par votre domaine :
```js
const ALLOWED_ORIGINS = [
'https://your-domain.com',
'https://www.your-domain.com',
];
```
::: warning Important
Sans cette modification, le proxy rejettera toutes les requêtes de votre site avec une erreur **403 Forbidden**. Par défaut, seules les requêtes provenant de `bentopdf.com` sont autorisées.
:::
4. **Déployez le proxy** :
```bash
wrangler deploy
```
Notez l'URL de votre worker (p. ex. `https://bentopdf-cors-proxy.your-subdomain.workers.dev`).
5. **Recompilez BentoPDF avec l'URL du proxy** :
Avec 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 .
```
Avec une compilation depuis les sources :
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
### Option 2 : proxy backend personnalisé
Vous pouvez aussi créer votre propre point de terminaison proxy. Les exigences sont les suivantes :
1. Accepter les requêtes GET avec un paramètre de requête `url`
2. Récupérer l'URL depuis votre serveur (aucune restriction CORS côté serveur)
3. Renvoyer la réponse avec ces en-têtes :
- `Access-Control-Allow-Origin: https://your-domain.com`
- `Access-Control-Allow-Methods: GET, OPTIONS`
- `X-Content-Type-Options: nosniff`
Exemple d'implémentation avec 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' });
}
});
```
## Considérations de sécurité
Le Cloudflare Worker fourni intègre plusieurs mesures de sécurité :
| Fonctionnalité | Description |
| --------------------------- | ----------------------------------------------------------------------------------------------- |
| **Validation de l'origine** | Autorise uniquement les requêtes des domaines listés dans `ALLOWED_ORIGINS` |
| **Restrictions d'URL** | Autorise uniquement les URL de certificats (`.crt`, `.cer`, `.pem`, `/certs/`, `/ocsp`, `/crl`) |
| **Blocage des IP privées** | Bloque les plages privées IPv4/IPv6, link-local, bouclage, IP décimales et métadonnées cloud |
| **Sûreté du Content-Type** | Ne renvoie que des types MIME de certificat sûrs, bloque l'injection de content-type en amont |
| **Limite de taille** | Réponse en flux limitée à 10 Mo, interrompue en cours de téléchargement en cas de dépassement |
| **Limitation de débit** | 60 requêtes par minute par IP ou IPv6 /64 (nécessite KV, laisse passer en cas d'erreur KV) |
| **Signatures HMAC** | Signature côté client facultative (dissuade les abus occasionnels) |
### DNS rebinding (auto-hébergement hors Cloudflare)
Le worker rejette toute URL qui se résout vers une IP privée ou interne. Il ne peut pas utiliser une liste fixe d'hôtes autorisés, car les URL des chaînes de certificats pointent vers l'AC qui a émis le certificat (FNMT, AC d'entreprise, etc.), donc les destinations ne sont pas connues à l'avance. Il reste une petite fenêtre de rebinding : une personne qui gère le DNS de son propre domaine pourrait renvoyer une IP publique lors du contrôle de sécurité, puis une IP privée pour la vraie requête un instant plus tard.
Sur Cloudflare, ce n'est pas exploitable : les Workers ne peuvent pas atteindre les IP privées ni le point de terminaison de métadonnées `169.254.169.254`, donc un rebinding n'aboutit à rien. Ne donnez simplement pas au worker de voie d'accès : n'associez ni Tunnel, ni Service, ni binding de réseau privé (il n'a besoin que de `RATE_LIMIT_KV`).
Si vous l'exécutez hors de Cloudflare (workerd, Node, etc.) dans un VPC, le runtime _peut_ atteindre les IP internes. Placez un pare-feu de sortie devant lui et bloquez les plages privées : `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` et leurs équivalents IPv6. Le code de l'application ne peut pas l'imposer une fois hors de Cloudflare.
## Désactiver le proxy
Si vous ne souhaitez pas utiliser de proxy CORS, définissez la variable d'environnement sur une chaîne vide :
```
VITE_CORS_PROXY_URL=
```
**Remarque** : sans le proxy, la signature avec des certificats qui nécessitent la récupération externe de la chaîne (comme FNMT ou certaines AC d'entreprise) échouera avec une erreur « Failed to fetch ».
## Dépannage
### « Signing error: TypeError: Failed to fetch »
Cela signifie généralement :
1. **Aucun proxy CORS configuré** : définissez `VITE_CORS_PROXY_URL` et recompilez
2. **Contenu mixte bloqué** : votre site est en HTTPS mais l'URL de l'émetteur du certificat est en HTTP. Le proxy CORS résout ce problème.
3. **Le proxy CORS rejette votre origine** : vérifiez que votre domaine figure dans le tableau `ALLOWED_ORIGINS` de `cors-proxy-worker.js`
### « 403 Forbidden » renvoyé par le proxy
Votre domaine ne figure pas dans la liste `ALLOWED_ORIGINS`. Modifiez `cors-proxy-worker.js` :
```js
const ALLOWED_ORIGINS = ['https://your-domain.com'];
```
Redéployez ensuite : `npx wrangler deploy`
### Tester le proxy
```bash
curl -H "Origin: https://your-domain.com" \
"https://your-proxy.workers.dev?url=http://www.cert.fnmt.es/certs/ACUSU.crt"
```
### Certificats qui fonctionnent sans proxy
Certains certificats incluent la chaîne complète dans le fichier P12/PFX et ne nécessitent pas de récupération externe :
- Certificats auto-signés
- Certaines AC commerciales qui regroupent les certificats intermédiaires
- Certificats que vous avez assemblés manuellement avec la chaîne complète
+622
View File
@@ -0,0 +1,622 @@
# Déployer avec Docker / Podman
Le moyen le plus simple d'auto-héberger BentoPDF en production.
> [!IMPORTANT]
> **En-têtes requis pour la conversion de fichiers Office**
>
> Les outils basés sur LibreOffice (conversion Word, Excel, PowerPoint) nécessitent ces en-têtes HTTP pour la prise en charge de `SharedArrayBuffer` :
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> La page doit aussi être servie depuis un contexte sécurisé. `http://localhost` fonctionne pour les tests locaux, mais `http://192.168.x.x` ou d'autres adresses IP du réseau local n'en font généralement pas partie : la conversion Office en HTTP simple échouera donc, même si les en-têtes sont présents.
>
> Les images de conteneur officielles incluent ces en-têtes. Si vous utilisez un reverse proxy (Traefik, Caddy, etc.), assurez-vous que ces en-têtes sont conservés ou ajoutés, et utilisez HTTPS pour tout accès hors bouclage local.
> [!TIP]
> **Utilisateurs de Podman :** toutes les commandes `docker` fonctionnent avec Podman en remplaçant `docker` par `podman` et `docker-compose` par `podman-compose`.
## Démarrage rapide
> [!TIP]
> BentoPDF est proposé en deux versions :
>
> - **Version auto-hébergée** : `ghcr.io/alam00000/bentopdf-simple:latest`. Tous les outils PDF, **sans** l'habillage marketing (ni section d'en-tête, ni FAQ, ni témoignages, ni pied de page). À utiliser pour les déploiements internes, d'équipe ou d'organisation. Ce n'est **pas** une version « allégée » aux fonctionnalités réduites.
> - **Version commerciale** : `ghcr.io/alam00000/bentopdf:latest`. Le site marketing complet, utilisé par bentopdf.com lui-même et par les détenteurs d'une licence commerciale qui exploitent des déploiements publics.
>
> En cas de doute : récupérez la version auto-hébergée.
```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
Créez `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
```
Exécutez :
```bash
# Docker Compose
docker compose up -d
# Podman Compose
podman-compose up -d
```
## Construire votre propre image
```dockerfile
# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginxinc/nginx-unprivileged:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]
```
Construisez et exécutez :
```bash
docker build -t bentopdf:custom .
docker run -d -p 3000:8080 bentopdf:custom
```
## Variables d'environnement
| Variable | Description | Par défaut |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- |
| `SIMPLE_MODE` | Compile la variante auto-hébergée (masque l'habillage marketing de bentopdf.com : section d'en-tête, FAQ, témoignages, pied de page). Tous les outils PDF restent disponibles. | `false` |
| `BASE_URL` | Déployer dans un sous-répertoire | `/` |
| `VITE_WASM_PYMUPDF_URL` | URL du module WASM PyMuPDF | `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/` |
| `VITE_WASM_GS_URL` | URL du module WASM Ghostscript | `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm@0.1.1/assets/` |
| `VITE_WASM_CPDF_URL` | URL du module WASM CoherentPDF | `https://cdn.jsdelivr.net/npm/coherentpdf@2.5.5/dist/` |
| `VITE_TESSERACT_WORKER_URL` | URL du script worker OCR | _(vide ; CDN par défaut de Tesseract.js)_ |
| `VITE_TESSERACT_CORE_URL` | Dossier du runtime principal OCR | _(vide ; CDN par défaut de Tesseract.js)_ |
| `VITE_TESSERACT_LANG_URL` | Dossier des fichiers traineddata OCR | _(vide ; CDN par défaut de Tesseract.js)_ |
| `VITE_TESSERACT_AVAILABLE_LANGUAGES` | Langues OCR séparées par des virgules affichées dans l'interface | _(vide ; catalogue complet affiché)_ |
| `VITE_OCR_FONT_BASE_URL` | Dossier des polices de la couche de texte OCR | _(vide ; URL de polices Noto distantes)_ |
| `VITE_DEFAULT_LANGUAGE` | Langue par défaut de l'interface | `en` |
| `VITE_BRAND_NAME` | Nom de marque personnalisé | `BentoPDF` |
| `VITE_BRAND_LOGO` | Chemin du logo relatif à `public/` | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | Texte de pied de page/copyright personnalisé | `© 2026 BentoPDF. All rights reserved.` |
| `DISABLE_TOOLS` | ID d'outils séparés par des virgules à masquer | _(vide ; tous les outils activés)_ |
| `DISABLE_GITHUB_STARS` | Ignore la récupération du nombre d'étoiles GitHub au chargement de la page et retire `api.github.com` de la CSP. Les builds en Simple Mode l'ignorent toujours. | `false` |
| `SITE_URL` | URL publique écrite dans les liens canonical, hreflang et sitemap. Laissez la valeur par défaut, sauf si vous voulez que les moteurs de recherche traitent votre déploiement comme un site à part entière. | `https://www.bentopdf.com` |
| `ALLOW_INDEXING` | Avec un `SITE_URL` personnalisé, chaque page est marquée `noindex` pour que votre copie ne concurrence pas bentopdf.com dans les résultats de recherche. Définissez `true` pour autoriser l'indexation par les moteurs de recherche. | `false` |
Les URL des modules WASM sont préconfigurées avec des valeurs CDN par défaut : toutes les fonctionnalités avancées fonctionnent sans configuration. Remplacez-les pour les déploiements isolés du réseau ou auto-hébergés.
### Moteurs de recherche
Par défaut, chaque page indique aux moteurs de recherche que bentopdf.com est l'original, afin que votre déploiement n'apparaisse pas comme un doublon. Vous n'avez rien à changer pour une instance privée ou interne.
- **Exclure totalement votre instance des résultats de recherche :** lancez le conteneur avec `-e ROBOTS_NOINDEX=true`. Un simple redémarrage suffit, sans recompilation.
- **Faire de votre instance un site à part entière dans les résultats de recherche :** compilez avec `--build-arg SITE_URL=https://your-domain.example --build-arg ALLOW_INDEXING=true`. Sans `ALLOW_INDEXING=true`, un `SITE_URL` personnalisé marque chaque page `noindex`.
### Content-Security-Policy
L'image nginx est livrée avec un en-tête `Content-Security-Policy` appliqué. Les directives `script-src`, `connect-src` et `font-src` de la CSP sont **générées à la compilation** à partir des variables d'URL `VITE_*` ci-dessus : tous les hôtes que vous passez via `--build-arg` sont automatiquement ajoutés à la politique.
En conséquence :
- Si vous remplacez `VITE_CORS_PROXY_URL` ou `VITE_WASM_*_URL` à la compilation, la CSP autorise automatiquement ces origines, sans configuration supplémentaire.
- Pour les URL sur `cdn.jsdelivr.net`, `script-src` n'autorise que le dossier exact du paquet versionné, par exemple `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/`, et non tout le CDN. jsDelivr sert n'importe quel paquet npm public ou dépôt GitHub : autoriser tout l'hôte permettrait à chacun de s'exécuter sur votre instance. Les URL sur tout autre hôte sont autorisées par origine.
- Si vous configurez des URL WASM personnalisées _à l'exécution_ via la page Paramètres avancés de l'application, ces origines ne figurent **pas** dans la CSP et le navigateur bloquera les requêtes vers elles. Cela s'applique aussi à une autre version du même paquet sur `cdn.jsdelivr.net`, car seule la version définie à la compilation est autorisée. La configuration à l'exécution est prévue pour l'expérimentation ; pour des URL personnalisées permanentes, définissez l'argument de build `VITE_*` correspondant.
- Dans les déploiements isolés du réseau qui remplacent les trois valeurs `VITE_WASM_*_URL`, ces dossiers de paquets `cdn.jsdelivr.net` sont aussi retirés de la CSP (chaque valeur par défaut est remplacée, pas complétée). Les valeurs par défaut de Tesseract OCR sur `cdn.jsdelivr.net` restent dans la politique tant que vous ne définissez pas aussi les trois valeurs `VITE_TESSERACT_*`. De même, définir `VITE_CORS_PROXY_URL` remplace la valeur publique par défaut `bentopdf-cors-proxy.bentopdf.workers.dev`.
La CSP inclut `'unsafe-eval'` dans `script-src`, car le runtime LibreOffice WASM (utilisé par les outils de conversion Word/Excel/PowerPoint) et la bibliothèque d'images wasm-vips (utilisée par PDF vers TIFF) compilent du code de dispatch interne via `new Function()`. Le supprimer casserait ces outils. Si vous compilez en `SIMPLE_MODE` (sans LibreOffice) et n'avez pas besoin de PDF vers TIFF, vous pouvez modifier manuellement le fichier `security-headers.conf` généré pour retirer `'unsafe-eval'` et obtenir une politique plus stricte.
Pour l'OCR, laissez les variables `VITE_TESSERACT_*` vides afin d'utiliser les ressources en ligne par défaut, ou définissez les trois ensemble pour un OCR auto-hébergé/hors ligne. Les remplacements OCR partiels sont refusés, car le worker, le runtime principal et le dossier traineddata doivent correspondre. Pour une production de PDF avec texte recherchable entièrement hors ligne, définissez aussi `VITE_OCR_FONT_BASE_URL` afin que les polices de la couche de texte OCR soient chargées depuis votre serveur interne plutôt que depuis les URL publiques des polices Noto.
`VITE_DEFAULT_LANGUAGE` définit la langue de l'interface pour les nouveaux visiteurs. Valeurs prises en charge : `en`, `ar`, `be`, `fr`, `de`, `es`, `zh`, `zh-TW`, `vi`, `tr`, `id`, `it`, `pt`, `nl`, `da`. Les utilisateurs peuvent toujours changer de langue : cela ne modifie que la valeur par défaut.
Exemple :
```bash
# Build with French as the default language
docker build --build-arg VITE_DEFAULT_LANGUAGE=fr -t bentopdf .
docker run -d -p 3000:8080 bentopdf
```
### Image de marque personnalisée
Remplacez le logo, le nom et le texte de pied de page par défaut de BentoPDF par les vôtres. Placez votre fichier de logo dans le dossier `public/` (ou utilisez une image existante), puis passez les variables de marque à la compilation :
```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 personnalisation fonctionne en mode complet comme en Simple Mode, et peut se combiner avec toutes les autres options de compilation.
### Désactiver des outils spécifiques
Masquez des outils de l'interface pour des exigences de conformité ou de sécurité. Les outils désactivés sont retirés de la page d'accueil, des résultats de recherche, des raccourcis clavier et du créateur de workflows. L'accès direct par URL affiche une page « outil indisponible ».
Les ID d'outils correspondent à l'URL de la page sans `.html`. Par exemple, si l'outil se trouve à `bentopdf.com/edit-pdf.html`, l'ID est `edit-pdf`.
#### Trouver les ID d'outils
Le plus simple : ouvrez n'importe quel outil dans BentoPDF et regardez l'URL. La dernière partie du chemin (sans `.html`) est l'ID de l'outil.
<details>
<summary>Liste complète des ID d'outils</summary>
**Éditer et annoter :** `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`
**Convertir en 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`
**Convertir depuis PDF :** `pdf-to-jpg`, `pdf-to-png`, `pdf-to-webp`, `pdf-to-bmp`, `pdf-to-tiff`, `pdf-to-cbz`, `pdf-to-svg`, `pdf-to-csv`, `pdf-to-excel`, `pdf-to-greyscale`, `pdf-to-json`, `pdf-to-docx`, `extract-images`, `pdf-to-markdown`, `prepare-pdf-for-ai`, `pdf-to-text`
**Organiser et gérer :** `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`
**Optimiser et réparer :** `compress-pdf`, `pdf-to-pdfa`, `fix-page-size`, `linearize-pdf`, `remove-restrictions`, `repair-pdf`, `rasterize-pdf`, `deskew-pdf`, `font-to-outline`
**Sécurité :** `encrypt-pdf`, `sanitize-pdf`, `decrypt-pdf`, `flatten-pdf`, `remove-metadata`, `change-permissions`, `digital-sign-pdf`, `validate-signature-pdf`, `timestamp-pdf`
</details>
#### Option 1 : à la compilation (argument de build Docker)
```bash
docker build \
--build-arg DISABLE_TOOLS="edit-pdf,sign-pdf,encrypt-pdf" \
-t bentopdf .
```
Cela intègre la liste des outils désactivés dans le bundle JavaScript. Une recompilation est nécessaire pour la modifier.
#### Option 2 : à l'exécution (config.json)
Montez un fichier `config.json` dans le répertoire servi, sans recompilation :
```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
```
Ou avec 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
```
Les deux méthodes peuvent être combinées : les listes sont fusionnées. Si un outil figure dans l'une ou l'autre, il est désactivé.
#### Désactiver des fonctionnalités de l'éditeur
Vous pouvez aussi désactiver des fonctionnalités précises de l'éditeur PDF (p. ex. caviardage, annotations, formulaires) sans désactiver l'outil d'édition entier. Ajoutez `editorDisabledCategories` à votre `config.json` :
```json
{
"editorDisabledCategories": ["redaction", "annotation-stamp"]
}
```
<details>
<summary>Liste complète des catégories de l'éditeur</summary>
**Zoom :** `zoom`, `zoom-in`, `zoom-out`, `zoom-fit-page`, `zoom-fit-width`, `zoom-marquee`, `zoom-level`
**Annotation :** `annotation`, `annotation-markup`, `annotation-highlight`, `annotation-underline`, `annotation-strikeout`, `annotation-squiggly`, `annotation-ink`, `annotation-text`, `annotation-stamp`
**Formes :** `annotation-shape`, `annotation-rectangle`, `annotation-circle`, `annotation-line`, `annotation-arrow`, `annotation-polygon`, `annotation-polyline`
**Formulaire :** `form`, `form-textfield`, `form-checkbox`, `form-radio`, `form-select`, `form-listbox`, `form-fill-mode`
**Caviardage :** `redaction`, `redaction-area`, `redaction-text`, `redaction-apply`, `redaction-clear`
**Document :** `document`, `document-open`, `document-close`, `document-print`, `document-capture`, `document-export`, `document-fullscreen`, `document-protect`
**Page :** `page`, `spread`, `rotate`, `scroll`, `navigation`
**Panneau :** `panel`, `panel-sidebar`, `panel-search`, `panel-comment`
**Outils :** `tools`, `pan`, `pointer`, `capture`
**Sélection :** `selection`, `selection-copy`
**Historique :** `history`, `history-undo`, `history-redo`
</details>
Les catégories sont hiérarchiques : désactiver un parent (p. ex. `annotation`) désactive tous ses enfants.
### Désactiver le compteur d'étoiles GitHub
La barre de navigation affiche le nombre d'étoiles GitHub en direct, récupéré depuis `api.github.com` au chargement de la page. C'est la seule requête externe qui ne sert pas une fonctionnalité PDF. Désactivez-la à la compilation pour retirer le code de récupération du bundle et supprimer `api.github.com` de la Content-Security-Policy :
```bash
docker build --build-arg DISABLE_GITHUB_STARS=true -t bentopdf .
```
Les builds en Simple Mode (`bentopdf-simple`) ignorent toujours le compteur d'étoiles : cette option n'est utile que pour la version commerciale ou les builds complets personnalisés.
### URL WASM personnalisées (isolé du réseau / auto-hébergé)
> [!IMPORTANT]
> Les URL WASM sont intégrées au JavaScript **à la compilation**. Les fichiers WASM sont téléchargés par le **navigateur de l'utilisateur** à l'exécution : Docker ne les télécharge pas pendant le build. Pour les réseaux isolés, vous devez héberger les fichiers WASM sur un serveur interne accessible aux navigateurs.
**Workflow complet pour un réseau isolé :**
```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
```
Utilisez pour `--ocr-languages` les codes affichés par `bash scripts/prepare-airgap.sh --list-ocr-languages`, ou cherchez par nom avec `bash scripts/prepare-airgap.sh --search-ocr-language <term>`. Lorsque vous compilez avec un sous-ensemble OCR restreint, passez les mêmes codes à `VITE_TESSERACT_AVAILABLE_LANGUAGES` afin que l'application n'affiche que les langues incluses. Pour une sortie OCR entièrement hors ligne, hébergez aussi le dossier `ocr-fonts/` fourni et pointez `VITE_OCR_FONT_BASE_URL` vers lui.
Définissez une variable sur une chaîne vide pour désactiver ce module (les utilisateurs devront le configurer manuellement via les Paramètres avancés).
## Port personnalisé
Par défaut, BentoPDF écoute sur le port `8080` à l'intérieur du conteneur. Pour le modifier, définissez la variable d'environnement `PORT` à l'exécution :
```bash
docker run -p 3000:9090 -e PORT=9090 ghcr.io/alam00000/bentopdf-simple:latest
```
| Variable | Description | Par défaut |
| -------- | ------------------------------------- | ---------- |
| `PORT` | Port d'écoute Nginx dans le conteneur | `8080` |
Cela fonctionne avec le Dockerfile standard comme avec le Dockerfile nonroot.
## ID utilisateur personnalisé (PUID/PGID)
Pour les environnements qui exigent une exécution sous un utilisateur non root précis (NAS, Kubernetes avec contextes de sécurité, politiques d'organisation), BentoPDF fournit un Dockerfile distinct avec prise en charge PUID/PGID de style LSIO.
### Construire et exécuter
```bash
# Build the non-root image
docker build -f Dockerfile.nonroot -t bentopdf-nonroot .
# Run with custom UID/GID
docker run -d \
--name bentopdf \
-p 3000:8080 \
-e PUID=1000 \
-e PGID=1000 \
--restart unless-stopped \
bentopdf-nonroot
```
### Variables d'environnement
| Variable | Description | Par défaut |
| -------------- | -------------------------- | ---------- |
| `PUID` | ID utilisateur d'exécution | `1000` |
| `PGID` | ID de groupe d'exécution | `1000` |
| `PORT` | Port d'écoute Nginx | `8080` |
| `DISABLE_IPV6` | Désactiver l'écoute 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
```
### Fonctionnement
Le conteneur démarre en root, crée un utilisateur avec le PUID/PGID indiqué, ajuste la propriété de tous les répertoires inscriptibles, puis abandonne ses privilèges avec `su-exec`. Le processus nginx s'exécute entièrement sous l'utilisateur que vous avez indiqué.
> [!NOTE]
> Le `Dockerfile` standard utilise `nginx-unprivileged` (UID 101) et est recommandé pour la plupart des déploiements. N'utilisez `Dockerfile.nonroot` que si vous avez besoin d'un UID/GID précis.
> [!WARNING]
> PUID/PGID ne peuvent pas valoir `0` (root). Le point d'entrée valide les valeurs et s'arrête avec une erreur si elles sont invalides.
## Avec 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
```
## Avec 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"
}
```
## Limites de ressources
```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 (intégration systemd)
[Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) permet d'exécuter des conteneurs Podman comme services systemd. C'est idéal pour les déploiements en production sur Linux.
### Configuration Quadlet de base
Créez un fichier d'unité de conteneur à `~/.config/containers/systemd/bentopdf.container` (utilisateur) ou `/etc/containers/systemd/bentopdf.container` (système) :
```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
```
### Activer et démarrer
```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]
> Pour un déploiement à l'échelle du système, utilisez `systemctl` sans l'option `--user` et placez le fichier dans `/etc/containers/systemd/`.
### Quadlet avec contrôle de santé
```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
```
### Mise à jour automatique avec Quadlet
Podman peut mettre à jour automatiquement les conteneurs lorsque de nouvelles images sont disponibles :
```bash
# Enable auto-update timer
systemctl --user enable --now podman-auto-update.timer
# Check for updates manually
podman auto-update
# Dry run (check without updating)
podman auto-update --dry-run
```
### Configuration réseau Quadlet
Pour une configuration réseau personnalisée, créez un fichier réseau `bentopdf.network` :
```ini
[Network]
Subnet=10.89.0.0/24
Gateway=10.89.0.1
```
Référencez-le ensuite dans votre fichier de conteneur :
```ini
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
Network=bentopdf.network
```
## Mise à jour
```bash
# Pull latest image
docker compose pull
# Recreate container
docker compose up -d
```
+266
View File
@@ -0,0 +1,266 @@
# Déployer sur Hostinger
[Hostinger](https://hostinger.com) est un hébergeur mutualisé très répandu. Ce guide explique comment déployer BentoPDF sur l'hébergement mutualisé de Hostinger.
## Prérequis
- Offre d'hébergement Hostinger avec accès au gestionnaire de fichiers
- Node.js installé en local pour la compilation
## Étape 1 : compiler le projet
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
Les fichiers compilés se trouvent dans le dossier `dist`.
## Étape 2 : téléverser sur Hostinger
### Déploiement à la racine du domaine
1. Connectez-vous à votre compte Hostinger
2. Allez dans **File Manager** → **public_html**
3. **Supprimez** tous les fichiers existants (faites une sauvegarde si nécessaire)
4. **Téléversez** tout le contenu de votre dossier local `dist` dans `public_html`
### Déploiement dans un sous-répertoire
Si vous déployez dans un sous-répertoire (p. ex. `yourdomain.com/pdf-tools/`) :
1. Compilez avec la bonne URL de base :
```bash
BASE_URL=/pdf-tools/ npm run build
```
2. Créez le dossier dans Hostinger :
- Allez dans **File Manager** → **public_html**
- Créez un nouveau dossier : `pdf-tools`
3. Téléversez tout le contenu de `dist` dans `public_html/pdf-tools/`
## Étape 3 : créer le fichier .htaccess
Créez un fichier `.htaccess` à la racine de votre dossier de déploiement (`public_html` ou `public_html/pdf-tools/`) :
::: warning Important
Pour un déploiement dans un sous-répertoire, remplacez `RewriteBase /` par `RewriteBase /pdf-tools/` (ou le nom de votre dossier).
:::
```apache
RewriteEngine On
RewriteBase /
# ============================================
# 1. SECURITY HEADERS (CRITICAL FOR WASM)
# ============================================
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# REQUIRED for soffice.js (SharedArrayBuffer)
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
</IfModule>
# ============================================
# 2. BROWSER CACHING
# ============================================
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType image/x-icon "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/ttf "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType application/wasm "access plus 1 year"
ExpiresByType application/gzip "access plus 1 year"
ExpiresByType text/html "access plus 0 seconds"
</IfModule>
# ============================================
# 3. COMPRESSION (STANDARD)
# ============================================
# Prevent server from double-compressing files
SetEnvIfNoCase Request_URI "\.gz$" no-gzip
SetEnvIfNoCase Request_URI "\.br$" no-gzip
SetEnvIfNoCase Request_URI "\.wasm$" no-gzip
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE font/woff
AddOutputFilterByType DEFLATE font/ttf
</IfModule>
# ============================================
# 4. MIME TYPES & SPECIAL FILE HANDLING
# ============================================
AddType application/javascript .js .mjs
AddType application/wasm .wasm
AddType font/woff2 .woff2
AddType font/woff .woff
AddType image/webp .webp
# Handle soffice.wasm.gz correctly
<FilesMatch "soffice\.wasm\.gz$">
ForceType application/wasm
Header set Content-Encoding "gzip"
Header set Cross-Origin-Resource-Policy "cross-origin"
Header append Vary Accept-Encoding
</FilesMatch>
# Handle data.gz
<FilesMatch "soffice\.data\.gz$">
ForceType application/octet-stream
Header set Content-Encoding "gzip"
Header append Vary Accept-Encoding
</FilesMatch>
# ============================================
# 5. REDIRECTS & ROUTING
# ============================================
# Canonical WWW (update domain as needed)
RewriteCond %{HTTP_HOST} ^bentopdf\.com [NC]
RewriteRule ^(.*)$ https://www.bentopdf.com/$1 [L,R=301]
# Force HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Remove trailing slash (except for language root directories)
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/$
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [R=301,L]
# Existing files/dirs - serve directly
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# ============================================
# 5.1. LANGUAGE ROUTES
# ============================================
# Supported languages: de, es, zh, zh-TW, vi, it, id, tr, fr, pt
# English has no prefix - served from root
# English prefix redirects to root (for SEO consistency)
RewriteRule ^en/?$ / [R=301,L]
RewriteRule ^en/(.+)$ /$1 [R=301,L]
# Language prefix root (e.g., /de/ -> /de/index.html)
RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /$1/index.html [L]
# Language prefix with path (e.g., /de/merge-pdf -> /de/merge-pdf.html)
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# ============================================
# 5.5. DOCS ROUTING (VitePress)
# ============================================
RewriteCond %{REQUEST_URI} ^/docs
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html [L]
# ============================================
# 6. ADD .HTML EXTENSION IF FILE EXISTS (ROOT LEVEL ONLY)
# ============================================
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
# ============================================
# 7. ERROR PAGES
# ============================================
ErrorDocument 404 /404.html
```
## Exemple de .htaccess pour un sous-répertoire
Pour `yourdomain.com/pdf-tools/`, modifiez ces lignes :
```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
```
## Dépannage
### Les fichiers WASM ne se chargent pas
Vérifiez que les types MIME sont correctement définis dans `.htaccess` :
```apache
AddType application/wasm .wasm
```
### Les outils LibreOffice ne fonctionnent pas
Les en-têtes de sécurité sont essentiels pour SharedArrayBuffer :
```apache
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
```
Si les en-têtes ne sont pas appliqués, contactez le support Hostinger pour activer `mod_headers`.
### Erreurs 404 lors de l'actualisation de la page
Assurez-vous que la règle d'extension `.html` et les routes de langue sont correctement configurées. BentoPDF utilise des fichiers HTML statiques, pas un routage 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]
```
### Limites de téléversement
Hostinger peut imposer des limites de taille de fichier. Téléversez les fichiers WASM séparément si le téléversement groupé échoue.
+538
View File
@@ -0,0 +1,538 @@
# Guide d'auto-hébergement
BentoPDF peut être auto-hébergé sur votre propre infrastructure. Ce guide présente différentes options de déploiement.
## Démarrage rapide avec Docker / Podman
Le moyen le plus rapide d'auto-héberger BentoPDF :
> [!TIP]
> BentoPDF est disponible en deux versions :
>
> - **Version auto-hébergée** : `ghcr.io/alam00000/bentopdf-simple:latest`. Tous les outils PDF, **sans** l'habillage marketing (pas de section d'accueil, de FAQ, de témoignages ni de pied de page). À utiliser pour les déploiements internes, d'équipe ou d'organisation. Ce **n'est pas** une version « allégée » avec moins de fonctionnalités.
> - **Version commerciale** : `ghcr.io/alam00000/bentopdf:latest`. Le site marketing complet, utilisé par bentopdf.com lui-même et par les titulaires d'une licence commerciale qui exploitent des déploiements publics.
>
> Dans le doute : utilisez la version auto-hébergée.
> [!IMPORTANT]
> La conversion de fichiers Office nécessite `SharedArrayBuffer`, ce qui impose que l'application soit à la fois isolée cross-origin et servie depuis un contexte sécurisé. L'image officielle envoie déjà les en-têtes COOP/COEP requis, mais les navigateurs désactivent quand même `SharedArrayBuffer` sur les origines HTTP simples du réseau local comme `http://192.168.x.x`.
>
> N'utilisez `http://localhost` que pour des tests sur le même appareil. Si les utilisateurs accèdent à BentoPDF via une IP ou un nom d'hôte du réseau local, terminez la connexion en 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
```
Ou avec 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 (systemd sous Linux)
Exécutez BentoPDF comme service systemd. Créez `~/.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
```
Consultez le [guide de déploiement Docker](/fr/self-hosting/docker) pour la documentation complète de Quadlet.
## Compiler depuis les sources
```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
```
## Options de configuration
### Version auto-hébergée (mode simple)
La version auto-hébergée (l'image `bentopdf-simple`, aussi appelée mode simple) est **fonctionnellement identique** à la version commerciale : tous les outils PDF sont présents et se comportent de la même façon. Elle masque simplement l'habillage marketing qui n'a de sens que sur bentopdf.com ou sur un déploiement commercial public. **Ce n'est pas une version allégée avec moins de fonctionnalités.**
**Ce que la version auto-hébergée masque** (purement cosmétique, aucune fonctionnalité PDF n'est retirée) :
- Barre de navigation, section d'accueil, section des fonctionnalités, FAQ, témoignages, pied de page
- Remplace le titre de la page par « Outils PDF »
**Ce que la version auto-hébergée conserve** (tout ce qui traite réellement les PDF) :
- Tous les outils PDF (fusion, scission, édition, signature, OCR, conversion Office, etc.)
- La personnalisation de la marque et toute la configuration au build et à l'exécution
La version commerciale (`ghcr.io/alam00000/bentopdf:latest`) est celle qui alimente bentopdf.com et qu'utilisent les titulaires d'une licence commerciale pour des déploiements publics : elle ajoute la section d'accueil, la FAQ, les témoignages et le pied de page qui n'auraient pas de sens sur un outil interne.
Si vous auto-hébergez BentoPDF pour votre équipe, votre organisation ou comme outil interne, utilisez la version auto-hébergée :
```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
```
Consultez [SIMPLE_MODE.md](https://github.com/alam00000/bentopdf/blob/main/SIMPLE_MODE.md) pour tous les détails.
### Version commerciale (déploiements publics sous votre propre marque)
La version commerciale (l'image `bentopdf`, sans suffixe `-simple`) est celle qui alimente bentopdf.com. Elle inclut le site marketing complet (section d'accueil, fonctionnalités, FAQ, témoignages, pied de page) en plus de tous les outils PDF. Utilisez cette version si vous souhaitez **déployer BentoPDF comme service PDF public sous votre propre marque**, par exemple :
- Vous exploitez BentoPDF comme service SaaS hébergé pour des utilisateurs finaux sur votre propre domaine
- Vous voulez l'expérience de la page d'accueil (sections marketing + outils), et pas seulement la surface brute des outils
- Vous êtes titulaire d'une licence commerciale et intégrez BentoPDF dans un produit commercial
**Exécutez-la telle quelle** (marque BentoPDF, utile pour prévisualiser la version) :
```bash
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
```
**Compilez-la avec votre propre marque** (le flux commercial typique) :
```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
```
Toutes les autres options de build (`BASE_URL`, `VITE_DEFAULT_LANGUAGE`, `DISABLE_TOOLS`, remplacements d'URL WASM, etc.) fonctionnent comme sur la version auto-hébergée.
::: warning Licence
L'exécution de la version commerciale est autorisée selon l'une des deux options de licence de BentoPDF :
- **AGPL-3.0** (gratuite) : autorisée si votre déploiement publie l'intégralité de son code source sous AGPL, y compris les modifications de marque et la logique métier associée.
- **Licence commerciale** (79 $, à vie) : requise pour les déploiements propriétaires ou à code fermé où vous ne publiez pas votre fork de marque ni votre code métier.
Consultez la [page Licence](https://www.bentopdf.com/licensing) pour la comparaison complète. Les modules WASM sous licence AGPL (PyMuPDF, Ghostscript, CoherentPDF) sont chargés depuis un CDN à l'exécution : ils n'entrent donc pas dans votre image et ne changent pas votre situation en matière de licence.
:::
### URL de base
Déployer dans un sous-répertoire :
```bash
BASE_URL=/pdf-tools/ npm run build
```
### Marque personnalisée
Remplacez le logo, le nom et le texte de pied de page par défaut de BentoPDF par les vôtres au moment du build :
| Variable | Description | Valeur par défaut |
| ------------------ | ------------------------------------------------------- | --------------------------------------- |
| `VITE_BRAND_NAME` | Nom de marque affiché dans l'en-tête et le pied de page | `BentoPDF` |
| `VITE_BRAND_LOGO` | Chemin du logo relatif à `public/` | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | Texte personnalisé du pied de page/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
```
Ou via Docker :
```bash
docker build \
--build-arg VITE_BRAND_NAME="AcmePDF" \
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
-t acmepdf .
```
La personnalisation de la marque fonctionne en mode complet comme en mode simple, et peut être combinée avec toutes les autres options de build (`BASE_URL`, `SIMPLE_MODE`, `VITE_DEFAULT_LANGUAGE`).
### Désactiver des outils précis
Masquez des outils individuels pour des raisons de conformité ou de sécurité. Les outils désactivés sont retirés de la page d'accueil, de la recherche, des raccourcis, du constructeur de workflow et de l'accès direct par URL.
Les identifiants d'outils correspondent à l'URL de la page sans `.html` : ouvrez un outil et regardez l'URL (par ex. `edit-pdf`, `sign-pdf`, `encrypt-pdf`).
**Au build** (intégré au bundle) :
```bash
DISABLE_TOOLS="edit-pdf,sign-pdf" npm run build
```
**À l'exécution** (sans reconstruire : montez 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
```
Les deux méthodes peuvent être combinées : les listes sont fusionnées.
Vous pouvez aussi désactiver des fonctionnalités précises de l'éditeur PDF (par ex. la rédaction) sans désactiver tout l'outil, en ajoutant `editorDisabledCategories` à `config.json`. Consultez le [guide Docker](/fr/self-hosting/docker#desactiver-des-fonctionnalites-de-l-editeur) pour la liste complète des catégories.
## Guides de déploiement
Choisissez votre plateforme :
- [Vercel](/fr/self-hosting/vercel)
- [Netlify](/fr/self-hosting/netlify)
- [Cloudflare Pages](/fr/self-hosting/cloudflare)
- [AWS S3 + CloudFront](/fr/self-hosting/aws)
- [Hostinger](/fr/self-hosting/hostinger)
- [Nginx](/fr/self-hosting/nginx)
- [Apache](/fr/self-hosting/apache)
- [Docker](/fr/self-hosting/docker)
- [Kubernetes](/fr/self-hosting/kubernetes)
- [Proxy CORS](/fr/self-hosting/cors-proxy) : requis pour les signatures numériques
## Problèmes courants
### La conversion Word / ODT / Excel / PowerPoint vers PDF se bloque (SharedArrayBuffer indisponible)
**Symptôme** : les conversions de documents basées sur LibreOffice (Word, ODT, Excel, PowerPoint vers PDF) se bloquent à environ 55 % ou ne démarrent pas. La console du navigateur peut afficher :
> `ReferenceError: SharedArrayBuffer is not defined`
…ou `window.crossOriginIsolated` renvoie `false`, ou la compilation WASM signale `expected magic word 00 61 73 6d, found 1f 8b 08 08`.
**Cause** : LibreOffice WASM nécessite `SharedArrayBuffer`, que le navigateur n'active que si la page est **isolée cross-origin** ET servie depuis un **contexte sécurisé**. Deux conditions doivent donc être remplies :
1. Chaque réponse inclut les deux en-têtes :
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
2. La page est servie depuis `https://...` ou `http://localhost`. Le HTTP simple sur une IP du réseau local (par ex. `http://192.168.x.x`) n'est PAS considéré comme sécurisé : les navigateurs y désactivent `SharedArrayBuffer`.
L'écart `00 61 73 6d / 1f 8b 08 08` est un sous-symptôme distinct : il manque l'en-tête de réponse `Content-Encoding: gzip` sur les fichiers précompressés `.wasm.gz` / `.data.gz`, si bien que le navigateur reçoit des octets gzip bruts au lieu du WASM décompressé.
**Solution** : consultez le guide de déploiement de votre plateforme pour la configuration exacte :
- [Nginx →](/fr/self-hosting/nginx#la-conversion-word-odt-excel-vers-pdf-ne-fonctionne-pas)
- [Apache →](/fr/self-hosting/apache#la-conversion-word-odt-excel-vers-pdf-ne-fonctionne-pas)
- [AWS S3 + CloudFront →](/fr/self-hosting/aws#etape-3b-politique-d-en-tetes-de-reponse-requise-pour-libreoffice-wasm)
- [Cloudflare Pages →](/fr/self-hosting/cloudflare#fichier-de-configuration) (`_headers` file)
- [Netlify →](/fr/self-hosting/netlify#la-conversion-word-odt-excel-vers-pdf-reste-bloquee-a-55) (`netlify.toml`)
- [Vercel →](/fr/self-hosting/vercel#la-conversion-word-odt-excel-vers-pdf-ne-fonctionne-pas) (`vercel.json`)
- [Hostinger →](/fr/self-hosting/hostinger#les-outils-libreoffice-ne-fonctionnent-pas) (`.htaccess`)
- [Kubernetes →](/fr/self-hosting/kubernetes#verifier-que-les-en-tetes-sharedarraybuffer-fonctionnent-toujours-ingress-gateway)
- **Docker** : géré automatiquement par la configuration nginx fournie, aucune action nécessaire.
**Vérification** : ouvrez la console des DevTools sur n'importe quelle page BentoPDF et exécutez :
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
Si la page est en HTTPS ou en `http://localhost` ET que les deux en-têtes COEP/COOP sont présents sur chaque réponse, les deux vérifications passent. Si vous êtes sur `http://192.168.x.x` ou une autre origine HTTP hors boucle locale, terminez la connexion en HTTPS : aucune correction par simples en-têtes n'existe.
### Fichiers `.mjs` servis en `application/octet-stream`
**Symptôme** : Signer un PDF, le remplissage de formulaire et certains autres outils affichent un lecteur vide ou ne se chargent pas. La console du navigateur signale :
> `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.`
**Cause** : votre serveur web ou reverse proxy n'a pas de correspondance de type MIME pour les fichiers `.mjs` (le lecteur PDF intégré fournit des modules ES avec cette extension). De nombreuses configurations de serveur par défaut utilisent `application/octet-stream` pour les extensions inconnues, ce que les navigateurs refusent d'exécuter comme modules ES.
**Solution** : consultez le guide de déploiement de votre plateforme pour l'extrait exact :
- [Nginx →](/fr/self-hosting/nginx#signer-un-pdf-ou-le-remplissage-de-formulaire-affiche-un-lecteur-vide-erreur-mime-mjs)
- [Apache →](/fr/self-hosting/apache#signer-un-pdf-ou-le-remplissage-de-formulaires-affiche-un-visualiseur-vide-erreur-mime-mjs)
- [AWS S3 + CloudFront →](/fr/self-hosting/aws#etape-2-compiler-et-televerser) (voir `aws s3 cp ... --include "*.mjs"`)
- [Kubernetes →](/fr/self-hosting/kubernetes#erreurs-de-type-mime-mjs-iframe-vide-dans-signer-un-pdf-remplissage-de-formulaire)
- **Docker, Vercel, Netlify, Cloudflare Pages, Hostinger** : géré automatiquement, aucune action nécessaire.
**Vérification** : ouvrez les DevTools, onglet Network, repérez la requête `.mjs` en échec et vérifiez l'en-tête de réponse `Content-Type`. Il doit valoir `application/javascript`. S'il vaut toujours `application/octet-stream`, un reverse proxy ou un CDN en amont détecte peut-être à nouveau le type : vérifiez chaque couche de votre chaîne de diffusion.
## Configuration WASM (composants AGPL)
BentoPDF **n'intègre pas** de bibliothèques de traitement sous licence AGPL dans son code source, mais **préconfigure des URL de CDN** pour que toutes les fonctionnalités marchent immédiatement, sans configuration manuelle.
::: tip Zéro configuration par défaut
Depuis la v2.0.0, les modules WASM sont préconfigurés pour se charger depuis le CDN jsDelivr via des variables d'environnement. Toutes les fonctionnalités avancées fonctionnent immédiatement, sans aucune configuration de l'utilisateur.
:::
| Composant | Licence | Fonctionnalités |
| --------------- | -------- | ------------------------------------------------------------------------- |
| **PyMuPDF** | AGPL-3.0 | Conversion EPUB/MOBI/FB2/XPS, extraction d'images, extraction de tableaux |
| **Ghostscript** | AGPL-3.0 | Conversion PDF/A, compression, redressement, rastérisation |
| **CoherentPDF** | AGPL-3.0 | Table des matières, pièces jointes, fusion de PDF avec signets |
### Variables d'environnement par défaut
Elles sont définies dans `.env.production` et intégrées au 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=
```
### Remplacer les URL WASM
Vous pouvez remplacer les valeurs par défaut au moment du build pour des déploiements personnalisés :
```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
```
Pour désactiver complètement un module (la configuration manuelle par l'utilisateur via les paramètres avancés devient alors nécessaire), définissez sa variable sur une chaîne vide.
Pour l'OCR, laissez toutes les variables `VITE_TESSERACT_*` vides et conservez les ressources en ligne par défaut, ou définissez ensemble les URL worker/core/lang pour un OCR auto-hébergé ou hors ligne. Si vous n'intégrez que certaines langues d'OCR, définissez aussi `VITE_TESSERACT_AVAILABLE_LANGUAGES` avec les mêmes codes séparés par des virgules afin que l'interface ne propose que les langues installées et que les sélections non prises en charge échouent avec une erreur explicite. Pour une sortie PDF consultable entièrement hors ligne, définissez aussi `VITE_OCR_FONT_BASE_URL` sur le répertoire interne qui sert les polices OCR fournies.
Les utilisateurs peuvent aussi remplacer ces valeurs à tout moment via les **paramètres avancés** de l'interface : les remplacements enregistrés dans le navigateur ont priorité sur les valeurs d'environnement par défaut.
### Déploiement isolé du réseau / hors ligne
Pour les réseaux sans accès à Internet (administrations, santé, finance, etc.). Les URL WASM sont intégrées au JavaScript **au moment du build** : les fichiers WASM eux-mêmes sont téléchargés par le **navigateur de l'utilisateur** à l'exécution. Vous devez donc tout préparer sur une machine connectée à Internet, puis transférer le résultat dans le réseau isolé.
#### Script automatisé (recommandé)
Le script `prepare-airgap.sh` fourni automatise tout le processus : téléchargement des paquets WASM, construction de l'image Docker et production d'un bundle autonome avec un script d'installation.
```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
```
Cela produit un répertoire de 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
```
Transférez le bundle dans le réseau isolé par clé USB, dépôt d'artefacts interne ou toute méthode approuvée. Exécutez ensuite le script d'installation fourni :
```bash
cd bentopdf-airgap-bundle
bash setup.sh
```
Le script d'installation charge l'image Docker, extrait les fichiers WASM et démarre éventuellement le conteneur.
**Options du script :**
| Option | Description | Valeur par défaut |
| ------------------------------ | ------------------------------------------------------------- | ------------------------------------ |
| `--wasm-base-url <url>` | Emplacement où les WASM seront hébergés en interne | _(obligatoire, demandée si absente)_ |
| `--image-name <name>` | Tag de l'image Docker | `bentopdf` |
| `--output-dir <path>` | Répertoire de sortie du bundle | `./bentopdf-airgap-bundle` |
| `--simple-mode` | Active le mode simple | désactivé |
| `--base-url <path>` | URL de base du sous-répertoire (par ex. `/pdf/`) | `/` |
| `--language <code>` | Langue par défaut de l'interface (par ex. `fr`, `de`) | _(aucune)_ |
| `--brand-name <name>` | Nom de marque personnalisé | _(aucun)_ |
| `--brand-logo <path>` | Chemin du logo relatif à `public/` | _(aucun)_ |
| `--footer-text <text>` | Texte de pied de page personnalisé | _(aucun)_ |
| `--ocr-languages <list>` | Langues d'OCR à intégrer, séparées par des virgules | `eng` |
| `--list-ocr-languages` | Affiche les codes et noms d'OCR pris en charge, puis quitte | désactivé |
| `--search-ocr-language <term>` | Recherche des codes d'OCR par nom ou abréviation | désactivé |
| `--dockerfile <path>` | Dockerfile à utiliser | `Dockerfile` |
| `--skip-docker` | Ignore la construction et l'export Docker | désactivé |
| `--skip-wasm` | Ignore le téléchargement WASM (réutilise les fichiers `.tgz`) | désactivé |
L'invite interactive accepte aussi `list` pour afficher la liste complète des codes Tesseract pris en charge et `search <term>` pour trouver des correspondances, par exemple `search german` ou `search chi`.
::: warning Exigence de même origine
Les fichiers WASM doivent être servis depuis la **même origine** que l'application BentoPDF. Les Web Workers utilisent `importScripts()`, qui ne peut pas charger de scripts cross-origin. Par exemple, si BentoPDF tourne sur `https://internal.example.com`, l'URL de base WASM doit aussi être `https://internal.example.com/wasm`.
:::
#### Étapes manuelles
<details>
<summary>Si vous préférez procéder manuellement, sans le script</summary>
**Étape 1 : télécharger les paquets WASM et OCR** (sur une machine connectée à 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
```
**Étape 2 : construire l'image Docker avec les URL internes**
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
docker build \
--build-arg VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ \
--build-arg VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ \
--build-arg VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ \
--build-arg VITE_TESSERACT_WORKER_URL=https://internal-server.example.com/wasm/ocr/worker.min.js \
--build-arg VITE_TESSERACT_CORE_URL=https://internal-server.example.com/wasm/ocr/core \
--build-arg VITE_TESSERACT_LANG_URL=https://internal-server.example.com/wasm/ocr/lang-data \
--build-arg VITE_OCR_FONT_BASE_URL=https://internal-server.example.com/wasm/ocr/fonts \
-t bentopdf .
```
**Étape 3 : exporter l'image Docker**
```bash
docker save bentopdf -o bentopdf.tar
```
**Étape 4 : transférer dans le réseau isolé**
Copiez par clé USB, dépôt d'artefacts interne ou méthode de transfert approuvée :
- `bentopdf.tar` : l'image Docker
- Les cinq paquets `.tgz` WASM/OCR de l'étape 1
- Le répertoire `tesseract-langdata/` de l'étape 1
- Le répertoire `ocr-fonts/` de l'étape 1
**Étape 5 : installer dans le réseau isolé**
```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
```
Vérifiez que les fichiers sont accessibles aux URL configurées à l'étape 2, notamment `.../ocr/worker.min.js`, `.../ocr/core`, `.../ocr/lang-data` et `.../ocr/fonts`.
</details>
::: info Compilation depuis les sources plutôt que Docker ?
Définissez les variables dans `.env.production` avant d'exécuter `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
```
:::
### Héberger votre propre proxy WASM
Si vous devez servir des fichiers WASM AGPL avec les en-têtes CORS appropriés, vous pouvez déployer un proxy simple. Consultez le [guide du proxy WASM Cloudflare](https://github.com/alam00000/bentopdf/blob/main/cloudflare/WASM-PROXY.md) pour un exemple d'implémentation.
::: tip Pourquoi cette séparation ?
Cette séparation garantit :
- Une conformité juridique claire pour les utilisateurs commerciaux
- Le cœur de BentoPDF reste sous sa double licence (AGPL-3.0 / commerciale)
- Les fichiers WASM sont chargés à l'exécution et non intégrés au code source
:::
## Configuration requise
| Exigence | Minimum |
| -------- | ------------------------------------ |
| Stockage | ~100 Mo (cœur sans les modules AGPL) |
| RAM | 512 Mo |
| CPU | Tout processeur moderne |
::: tip
BentoPDF est un site statique : aucune base de données ni serveur backend n'est nécessaire !
:::
+211
View File
@@ -0,0 +1,211 @@
# Déployer avec Kubernetes
Kubernetes est peut-être excessif pour un site statique, mais il peut très bien convenir si vous standardisez déjà sur Helm + GitOps.
> [!IMPORTANT]
> **En-têtes requis pour la conversion de fichiers Office**
>
> Les outils basés sur LibreOffice (conversion Word, Excel, PowerPoint) nécessitent ces en-têtes HTTP pour la prise en charge de `SharedArrayBuffer` :
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> Les images nginx officielles de BentoPDF incluent ces en-têtes. Dans Kubernetes, **les contrôleurs Ingress/Gateway sont aussi des reverse proxies** : assurez-vous donc que ces en-têtes sont conservés (ou ajoutez-les en périphérie).
## Prérequis
- Un cluster Kubernetes
- Helm v3
- Une image nginx BentoPDF (par ex. `ghcr.io/alam00000/bentopdf:<tag>`) qui sert sur le **port 8080**
## Déployer avec Helm
### Installer depuis ce dépôt (chart local)
```bash
kubectl create namespace bentopdf
helm upgrade --install bentopdf /path/to/bentopdf/chart \
--namespace bentopdf \
--set image.repository=ghcr.io/alam00000/bentopdf \
--set image.tag=latest
```
### Installer depuis GHCR (chart OCI)
Si le chart est publié sur GHCR en tant qu'artefact 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
```
## L'exposer
### Port-forward (test rapide)
```bash
kubectl -n bentopdf port-forward deploy/bentopdf 8080:8080
```
### Ingress (facultatif)
Activez Ingress (exemple pour nginx-ingress) :
```yaml
ingress:
enabled: true
className: nginx
hosts:
- host: pdf.example.com
paths:
- path: /
pathType: Prefix
```
### Gateway API (facultatif)
Ce chart prend en charge `Gateway` + `HTTPRoute` de Gateway API.
Exemple (opérateur Gateway API de Cloudflare) :
```yaml
gateway:
enabled: true
name: bento-tunnel
namespace: bentopdf
gatewayClassName: cloudflare
httpRoute:
enabled: true
parentRefs:
- name: bento-tunnel
namespace: bentopdf
sectionName: http
hostnames:
- pdfs.example.com
```
## Vérifier que les en-têtes SharedArrayBuffer fonctionnent toujours (Ingress/Gateway)
### Ce qui « devrait » se passer
La configuration nginx de BentoPDF définit les en-têtes de réponse requis. La plupart des contrôleurs Ingress/Gateway **transmettent les en-têtes de réponse en amont sans les modifier**.
### Ce qui peut tout casser
- Une politique de contrôleur ou de périphérie qui **remplace** ou **supprime** les en-têtes de réponse
- Un middleware d'« en-têtes de sécurité » qui définit des valeurs COOP/COEP différentes
### Comment vérifier
Exécutez ceci sur votre point d'accès public :
```bash
curl -I https://pdf.example.com/ | egrep -i 'cross-origin-opener-policy|cross-origin-embedder-policy'
```
Vous devriez voir :
- `Cross-Origin-Opener-Policy: same-origin`
- `Cross-Origin-Embedder-Policy: require-corp`
### Si votre contrôleur Ingress ne les conserve pas
Ajoutez les en-têtes en périphérie (selon le contrôleur). Exemple pour **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;
```
## Erreurs de type MIME `.mjs` (iframe vide dans Signer un PDF / remplissage de formulaire)
Si la console du navigateur affiche `Failed to load module script: ... non-JavaScript MIME type "application/octet-stream"` sur une requête `.mjs`, un contrôleur Ingress/Gateway placé devant le nginx de BentoPDF remplace l'en-tête `Content-Type` en amont par `application/octet-stream`.
Le nginx de l'image BentoPDF sert déjà `.mjs` en `application/javascript`. La solution consiste à empêcher le contrôleur de le supprimer ou de le remplacer. Pour nginx-ingress :
```yaml
ingress:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
location ~* \.mjs$ {
types {} default_type application/javascript;
}
```
Ou utilisez un filtre `ResponseHeaderModifier` dans Gateway API pour définir `Content-Type: application/javascript` sur les chemins `.mjs`. Vérifiez avec les DevTools, onglet Network : la requête `.mjs` en échec doit maintenant afficher `content-type: application/javascript`.
### Si vous utilisez Gateway API et voulez forcer l'ajout d'en-têtes
Gateway API prend en charge un filtre `ResponseHeaderModifier`. Vous pouvez l'attacher dans `httpRoute.rules[*].filters` :
```yaml
httpRoute:
enabled: true
hostnames: [pdf.example.com]
parentRefs:
- name: bento-tunnel
namespace: misc
sectionName: http
rules:
- matches:
- path: { type: PathPrefix, value: / }
filters:
- type: ResponseHeaderModifier
responseHeaderModifier:
set:
- name: Cross-Origin-Opener-Policy
value: same-origin
- name: Cross-Origin-Embedder-Policy
value: require-corp
```
La prise en charge de filtres précis dépend de votre contrôleur Gateway ; si un filtre est ignoré, ajoutez plutôt les en-têtes au niveau de la périphérie ou du contrôleur.
## Désactiver des outils précis
Utilisez un ConfigMap pour désactiver des outils à l'exécution sans reconstruire l'image :
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: bentopdf-config
namespace: bentopdf
data:
config.json: |
{
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
}
```
Montez-le dans le répertoire servi :
```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
```
Les identifiants d'outils correspondent à l'URL de la page sans `.html` : ouvrez un outil et regardez l'URL (par ex. `edit-pdf`, `merge-pdf`, `compress-pdf`). Les outils désactivés sont masqués de la page d'accueil, de la recherche, des raccourcis, du constructeur de workflow et de l'accès direct par URL. Consultez le [guide Docker](/fr/self-hosting/docker#desactiver-des-outils-specifiques) pour la liste complète des options.
+139
View File
@@ -0,0 +1,139 @@
# Déployer sur Netlify
[Netlify](https://netlify.com) propose un excellent hébergement de sites statiques avec une offre gratuite généreuse.
## Déploiement en 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)
## Déploiement manuel
### Étape 1 : connecter le dépôt
1. Connectez-vous à [Netlify](https://app.netlify.com)
2. Cliquez sur « Add new site » → « Import an existing project »
3. Connectez votre compte GitHub
4. Sélectionnez votre fork de BentoPDF
### Étape 2 : configurer les paramètres de build
| Paramètre | Valeur |
| ------------------------- | --------------- |
| Commande de build | `npm run build` |
| Répertoire de publication | `dist` |
| Version de Node | 18+ |
### Étape 3 : déployer
Cliquez sur « Deploy site » et attendez la fin du build.
## Fichier de configuration
Créez `netlify.toml` à la racine de votre projet :
```toml
[build]
command = "npm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "18"
# Required security headers for SharedArrayBuffer (used by LibreOffice WASM)
[[headers]]
for = "/*"
[headers.values]
Cross-Origin-Embedder-Policy = "require-corp"
Cross-Origin-Opener-Policy = "same-origin"
Cross-Origin-Resource-Policy = "cross-origin"
# Pre-compressed LibreOffice WASM binary - must be served with Content-Encoding
[[headers]]
for = "/libreoffice-wasm/soffice.wasm.gz"
[headers.values]
Content-Type = "application/wasm"
Content-Encoding = "gzip"
Cache-Control = "public, max-age=31536000, immutable"
# Pre-compressed LibreOffice WASM data - must be served with Content-Encoding
[[headers]]
for = "/libreoffice-wasm/soffice.data.gz"
[headers.values]
Content-Type = "application/octet-stream"
Content-Encoding = "gzip"
Cache-Control = "public, max-age=31536000, immutable"
# Cache other WASM files
[[headers]]
for = "*.wasm"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
Content-Type = "application/wasm"
# SPA routing
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
```
::: warning Important
Les en-têtes `Cross-Origin-Embedder-Policy` et `Cross-Origin-Opener-Policy` sont nécessaires pour les conversions Word/ODT/Excel/PowerPoint vers PDF. Sans eux, `SharedArrayBuffer` n'est pas disponible et le moteur LibreOffice WASM ne parviendra pas à s'initialiser.
Les en-têtes `Content-Encoding: gzip` sur les fichiers `.wasm.gz` et `.data.gz` indiquent au navigateur de les décompresser automatiquement. Sans eux, le navigateur reçoit des octets gzip bruts et la compilation WASM échoue.
:::
## Variables d'environnement
Définissez-les dans Site settings → Environment variables :
| Variable | Description |
| ----------------------- | ----------------------------------------------------------------- |
| `SIMPLE_MODE` | Mettre à `true` pour un build minimal |
| `VITE_BRAND_NAME` | Nom de marque personnalisé (remplace « BentoPDF ») |
| `VITE_BRAND_LOGO` | Chemin du logo relatif à `public/` (par ex. `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | Texte personnalisé du pied de page/copyright |
| `VITE_DEFAULT_LANGUAGE` | Langue par défaut de l'interface (par ex. `fr`, `de`, `es`) |
## Domaine personnalisé
1. Allez dans Site settings → Domain management
2. Cliquez sur « Add custom domain »
3. Suivez les instructions de configuration DNS
## Large Media
Pour les gros fichiers WASM, envisagez d'activer [Netlify Large Media](https://docs.netlify.com/large-media/overview/):
```bash
netlify lm:setup
git lfs track "*.wasm"
```
## Dépannage
### La conversion Word/ODT/Excel vers PDF reste bloquée à 55 %
Si les conversions de documents restent bloquées à 55 %, ouvrez la console des DevTools et vérifiez :
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
Si `crossOriginIsolated` vaut `false`, les en-têtes COEP/COOP de votre `netlify.toml` ne sont pas appliqués. Vérifiez que le fichier se trouve à la racine de votre projet et redéployez.
Si vous voyez `expected magic word 00 61 73 6d, found 1f 8b 08 08` dans la console, il manque les en-têtes `Content-Encoding: gzip` sur les fichiers `.wasm.gz`. Assurez-vous que les blocs `[[headers]]` pour `soffice.wasm.gz` et `soffice.data.gz` figurent dans votre `netlify.toml`.
### Échec du build
Vérifiez la compatibilité de la version de Node :
```toml
[build.environment]
NODE_VERSION = "20"
```
### Chargement initial lent
Activez l'optimisation des ressources dans Site settings → Build & deploy → Asset optimization.
+232
View File
@@ -0,0 +1,232 @@
# Déployer avec Nginx
Hébergez BentoPDF sur votre propre serveur avec Nginx.
## Prérequis
- Serveur Ubuntu/Debian
- Nginx installé
- Certificat SSL (recommandé : Let's Encrypt)
## Étape 1 : construire le projet
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
Pour personnaliser l'image de marque, définissez des variables d'environnement avant le build :
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## Étape 2 : copier les fichiers
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## Étape 3 : configuration de Nginx
Créez `/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;
}
}
```
## Étape 4 : activer le site
```bash
sudo ln -s /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
## Étape 5 : SSL avec Let's Encrypt
```bash
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
```
## Déploiement dans un sous-répertoire
Pour héberger sous `/pdf/` :
```nginx
location /pdf/ {
alias /var/www/bentopdf/;
try_files $uri $uri/ /pdf/index.html;
}
```
Construisez avec :
```bash
BASE_URL=/pdf/ npm run build
```
## Optimisation des performances
Ajoutez à `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;
}
```
## Dépannage
### Le WASM ne se charge pas
Vérifiez que le type MIME est défini :
```nginx
types {
application/wasm wasm;
}
```
### Signer un PDF ou le remplissage de formulaire affiche un lecteur vide (erreur MIME `.mjs`)
Si la console du navigateur affiche :
```
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.
```
…et que la requête en échec se termine par `.mjs`, il manque à votre nginx la correspondance `.mjs → application/javascript`. Le lecteur PDF intégré (utilisé par Signer un PDF, le remplissage de formulaire et quelques autres outils) fournit des modules ES avec l'extension `.mjs`, et le fichier `mime.types` par défaut de nginx ne les inclut pas.
Corrigez-le en ajoutant `mjs` à la ligne `application/javascript` existante dans `/etc/nginx/mime.types` :
```nginx
application/javascript js mjs;
```
> N'ajoutez PAS une _seconde_ ligne `application/javascript` : nginx analyse `mime.types` ligne par ligne et une seule correspondance type-extension l'emporte, donc une seconde ligne est ignorée silencieusement sur certaines versions. Modifiez la ligne existante pour ajouter `mjs` à côté de `js`.
Ou redéfinissez-le localement dans votre bloc server (sans modifier le fichier global) :
```nginx
types {
application/javascript js mjs;
application/wasm wasm;
}
```
Rechargez nginx après la modification :
```bash
sudo nginx -t && sudo systemctl reload nginx
```
Pour confirmer : dans les DevTools du navigateur, onglet Network, repérez la requête `.mjs` en échec et vérifiez l'en-tête de réponse `Content-Type`. Il doit maintenant valoir `application/javascript` et non `application/octet-stream`.
### La conversion Word/ODT/Excel vers PDF ne fonctionne pas
LibreOffice WASM nécessite `SharedArrayBuffer`, qui exige les en-têtes `Cross-Origin-Embedder-Policy` et `Cross-Origin-Opener-Policy`. Il faut aussi un contexte sécurisé : `http://localhost` fonctionne pour les tests locaux, mais `http://192.168.x.x` ou d'autres IP du réseau local nécessitent généralement HTTPS. Notez que les directives `add_header` d'un bloc `location` **remplacent** celles du niveau server, elles ne fusionnent pas. Chaque bloc `location` ayant son propre `add_header` doit inclure les en-têtes COEP/COOP.
Vérifiez avec :
```bash
curl -I https://your-domain.com/ | grep -i cross-origin
```
Si vous utilisez un reverse proxy devant nginx, assurez-vous qu'il conserve ces en-têtes.
### 502 Bad Gateway
Consultez les journaux d'erreurs de Nginx :
```bash
sudo tail -f /var/log/nginx/error.log
```
+122
View File
@@ -0,0 +1,122 @@
# Déployer sur Vercel
[Vercel](https://vercel.com) offre l'expérience de déploiement la plus simple pour les sites statiques.
## Déploiement en un clic
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/alam00000/bentopdf)
## Déploiement manuel
### Étape 1 : forker le dépôt
Forkez [bentopdf/bentopdf](https://github.com/alam00000/bentopdf) vers votre compte GitHub.
### Étape 2 : importer dans Vercel
1. Rendez-vous sur [vercel.com/new](https://vercel.com/new)
2. Sélectionnez votre dépôt forké
3. Configurez le projet :
| Paramètre | Valeur |
| ----------------------- | --------------- |
| Préréglage du framework | Vite |
| Commande de build | `npm run build` |
| Répertoire de sortie | `dist` |
| Commande d'installation | `npm install` |
### Étape 3 : variables d'environnement (facultatif)
Ajoutez-les si nécessaire :
| Variable | Description |
| ----------------------- | ----------------------------------------------------------------- |
| `SIMPLE_MODE` | Mettre à `true` pour une interface minimale |
| `VITE_BRAND_NAME` | Nom de marque personnalisé (remplace « BentoPDF ») |
| `VITE_BRAND_LOGO` | Chemin du logo relatif à `public/` (par ex. `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | Texte personnalisé du pied de page/copyright |
| `VITE_DEFAULT_LANGUAGE` | Langue par défaut de l'interface (par ex. `fr`, `de`, `es`) |
### Étape 4 : déployer
Cliquez sur « Deploy » et attendez la fin du build.
## Domaine personnalisé
1. Ouvrez les paramètres de votre projet
2. Allez dans « Domains »
3. Ajoutez votre domaine personnalisé
4. Configurez le DNS comme indiqué
## Limitations
::: warning Fichiers volumineux
Les fonctions serverless de Vercel ont une limite de 50 Mo. BentoPDF étant un site statique, cela ne devrait pas vous concerner, mais les modules WASM sont volumineux (~100 Mo au total). Assurez-vous qu'ils sont servis depuis le dossier `/public`.
:::
## Dépannage
### Délai d'attente du build dépassé
Si les builds dépassent le délai, essayez :
```json
// vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": "dist"
}
```
### Erreur 404 à l'actualisation
Ajoutez un fichier `vercel.json` pour le routage SPA :
```json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
```
### La conversion Word/ODT/Excel vers PDF ne fonctionne pas
Les conversions LibreOffice WASM nécessitent `SharedArrayBuffer`, qui exige ces en-têtes de réponse sur toutes les pages :
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
Les fichiers précompressés `.wasm.gz` et `.data.gz` ont aussi besoin de `Content-Encoding: gzip` pour que le navigateur les décompresse.
Ajoutez-les à votre `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" }
]
}
]
}
```
Pour vérifier, ouvrez la console des DevTools et exécutez `console.log(window.crossOriginIsolated)` : le résultat doit être `true`.