mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-13 01:54:00 +01:00
feat: translate all pages, UI messages and docs and fix SEO redirects and indexing
This commit is contained in:
1 parent
5e019de5ae
commit
4edb1718cc
3344 files changed
+415643
-4759
No files matched your search
@@ -0,0 +1,275 @@
|
||||
# Implantação com Apache
|
||||
|
||||
Hospede o BentoPDF usando o Apache HTTP Server.
|
||||
|
||||
## Pré-requisitos
|
||||
|
||||
- Apache 2.4+
|
||||
- mod_rewrite habilitado
|
||||
- Certificado SSL (recomendado)
|
||||
|
||||
## Passo 1: Compile o Projeto
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
Para personalizar a marca, defina variáveis de ambiente antes de compilar:
|
||||
|
||||
```bash
|
||||
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
|
||||
```
|
||||
|
||||
## Passo 2: Copie os Arquivos
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/www/bentopdf
|
||||
sudo cp -r dist/* /var/www/bentopdf/
|
||||
sudo chown -R www-data:www-data /var/www/bentopdf
|
||||
```
|
||||
|
||||
## Passo 3: Configuração do Apache
|
||||
|
||||
Crie `/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>
|
||||
```
|
||||
|
||||
## Passo 4: .htaccess para Roteamento
|
||||
|
||||
Crie `/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
|
||||
```
|
||||
|
||||
## Passo 5: Habilite os Módulos Necessários
|
||||
|
||||
```bash
|
||||
sudo a2enmod rewrite
|
||||
sudo a2enmod ssl
|
||||
sudo a2enmod headers
|
||||
sudo a2enmod expires
|
||||
sudo a2enmod deflate
|
||||
```
|
||||
|
||||
## Passo 6: Habilite o Site
|
||||
|
||||
```bash
|
||||
sudo a2ensite bentopdf.conf
|
||||
sudo apache2ctl configtest
|
||||
sudo systemctl reload apache2
|
||||
```
|
||||
|
||||
## Implantação em Subdiretório
|
||||
|
||||
Para hospedar em `/pdf/`:
|
||||
|
||||
1. Compile com a URL base:
|
||||
|
||||
```bash
|
||||
BASE_URL=/pdf/ npm run build
|
||||
```
|
||||
|
||||
2. Atualize o `.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
|
||||
```
|
||||
|
||||
## Solução de Problemas
|
||||
|
||||
### Erros 404 de WASM
|
||||
|
||||
Verifique se o tipo MIME está configurado:
|
||||
|
||||
```apache
|
||||
AddType application/wasm .wasm
|
||||
```
|
||||
|
||||
### Assinar PDF ou Preencher Formulários Mostra um Visualizador em Branco (erro de MIME `.mjs`)
|
||||
|
||||
Se o console do navegador mostrar:
|
||||
|
||||
```
|
||||
Failed to load module script: The server responded with a non-JavaScript
|
||||
MIME type of "application/octet-stream". Strict MIME type checking is
|
||||
enforced for module scripts per HTML spec.
|
||||
```
|
||||
|
||||
…e a requisição com falha termina em `.mjs`, falta na sua configuração do Apache o mapeamento `.mjs → application/javascript`. O visualizador de PDF incluído usa arquivos de módulo ES com extensão `.mjs`, e o `mime.types` padrão do Apache não os inclui por padrão.
|
||||
|
||||
Adicione ao seu VirtualHost ou `.htaccess`:
|
||||
|
||||
```apache
|
||||
AddType application/javascript .mjs
|
||||
```
|
||||
|
||||
Depois, recarregue o Apache:
|
||||
|
||||
```bash
|
||||
sudo apache2ctl configtest && sudo systemctl reload apache2
|
||||
```
|
||||
|
||||
Para confirmar: no DevTools do navegador, na aba Network, encontre a requisição `.mjs` com falha e verifique o cabeçalho de resposta `Content-Type`. Ele deve ser `application/javascript`, e não `application/octet-stream`.
|
||||
|
||||
### Rewrite Não Funciona
|
||||
|
||||
Verifique se o mod_rewrite está habilitado:
|
||||
|
||||
```bash
|
||||
sudo a2enmod rewrite
|
||||
```
|
||||
|
||||
### Word/ODT/Excel para PDF Não Funciona
|
||||
|
||||
O LibreOffice WASM requer `SharedArrayBuffer`, que exige estes cabeçalhos:
|
||||
|
||||
```apache
|
||||
Header always set Cross-Origin-Embedder-Policy "require-corp"
|
||||
Header always set Cross-Origin-Opener-Policy "same-origin"
|
||||
```
|
||||
|
||||
Ele também exige um contexto seguro. `http://localhost` funciona para testes locais, mas `http://192.168.x.x` ou outros IPs de rede local normalmente exigem HTTPS. Se os cabeçalhos estiverem presentes, mas `window.crossOriginIsolated` ainda for `false`, verifique se a página está sendo aberta por HTTP simples em uma origem que não seja loopback.
|
||||
|
||||
Os arquivos pré-comprimidos `.wasm.gz` e `.data.gz` também precisam do `Content-Encoding` correto:
|
||||
|
||||
```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>
|
||||
```
|
||||
|
||||
Verifique se o `mod_headers` está habilitado: `sudo a2enmod headers`
|
||||
|
||||
### Permissão Negada
|
||||
|
||||
```bash
|
||||
sudo chown -R www-data:www-data /var/www/bentopdf
|
||||
sudo chmod -R 755 /var/www/bentopdf
|
||||
```
|
||||
@@ -0,0 +1,200 @@
|
||||
# Implantação na AWS S3 + CloudFront
|
||||
|
||||
Hospede o BentoPDF na AWS para ter o máximo de controle e escalabilidade.
|
||||
|
||||
## Arquitetura
|
||||
|
||||
```
|
||||
User → CloudFront (CDN) → S3 (Static Files)
|
||||
```
|
||||
|
||||
## Passo 1: Crie o 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
|
||||
```
|
||||
|
||||
## Passo 2: Compile e Envie
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Passo 3: Crie a Distribuição do CloudFront
|
||||
|
||||
```bash
|
||||
aws cloudfront create-distribution \
|
||||
--origin-domain-name your-bentopdf-bucket.s3.amazonaws.com \
|
||||
--default-root-object index.html
|
||||
```
|
||||
|
||||
Ou use o Console da AWS:
|
||||
|
||||
1. Vá para CloudFront → Create distribution
|
||||
2. Origin domain: selecione seu bucket S3
|
||||
3. Ative "Origin Access Control"
|
||||
4. Default root object: `index.html`
|
||||
5. Crie a distribuição
|
||||
|
||||
## Passo 3b: Política de Cabeçalhos de Resposta (Necessária para o LibreOffice WASM)
|
||||
|
||||
As conversões baseadas no LibreOffice (Word, Excel e PowerPoint para PDF) exigem `SharedArrayBuffer`, que precisa de cabeçalhos de resposta específicos. Crie uma Response Headers Policy no CloudFront:
|
||||
|
||||
1. Vá para CloudFront → Policies → Response headers
|
||||
2. Crie uma política personalizada com estes cabeçalhos:
|
||||
|
||||
| Cabeçalho | Valor |
|
||||
| ------------------------------ | -------------- |
|
||||
| `Cross-Origin-Embedder-Policy` | `require-corp` |
|
||||
| `Cross-Origin-Opener-Policy` | `same-origin` |
|
||||
| `Cross-Origin-Resource-Policy` | `cross-origin` |
|
||||
|
||||
3. Anexe a política ao comportamento de cache padrão da sua distribuição
|
||||
|
||||
Ou pela 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}
|
||||
]
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## Passo 3c: Metadados do S3 para Arquivos WASM Pré-Comprimidos
|
||||
|
||||
Os arquivos WASM do LibreOffice são pré-comprimidos (`.wasm.gz`, `.data.gz`). Defina o Content-Type e o Content-Encoding corretos para que os navegadores os descomprimam:
|
||||
|
||||
```bash
|
||||
# Set correct headers for soffice.wasm.gz
|
||||
aws s3 cp s3://your-bentopdf-bucket/libreoffice-wasm/soffice.wasm.gz \
|
||||
s3://your-bentopdf-bucket/libreoffice-wasm/soffice.wasm.gz \
|
||||
--content-type "application/wasm" \
|
||||
--content-encoding "gzip" \
|
||||
--metadata-directive REPLACE
|
||||
|
||||
# Set correct headers for soffice.data.gz
|
||||
aws s3 cp s3://your-bentopdf-bucket/libreoffice-wasm/soffice.data.gz \
|
||||
s3://your-bentopdf-bucket/libreoffice-wasm/soffice.data.gz \
|
||||
--content-type "application/octet-stream" \
|
||||
--content-encoding "gzip" \
|
||||
--metadata-directive REPLACE
|
||||
```
|
||||
|
||||
::: warning Importante
|
||||
Sem a política de cabeçalhos de resposta, o `SharedArrayBuffer` fica indisponível e as conversões do LibreOffice WASM travam em ~55%. Sem o Content-Encoding correto nos arquivos `.gz`, o navegador recebe bytes gzip brutos e a compilação do WASM falha.
|
||||
:::
|
||||
|
||||
## Passo 4: Política do Bucket S3
|
||||
|
||||
Permita que o CloudFront acesse o 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Passo 5: Páginas de Erro Personalizadas
|
||||
|
||||
Configure o 404 para retornar `index.html` no roteamento SPA:
|
||||
|
||||
1. CloudFront → Error pages
|
||||
2. Crie uma resposta de erro personalizada:
|
||||
- Código de erro HTTP: 404
|
||||
- Caminho da página de resposta: `/index.html`
|
||||
- Código de resposta HTTP: 200
|
||||
|
||||
## Estimativa de Custos
|
||||
|
||||
| Recurso | Custo Estimado |
|
||||
| ---------------------------------- | -------------- |
|
||||
| Armazenamento S3 (~500MB) | ~$0,01/mês |
|
||||
| CloudFront (1TB de transferência) | ~$85/mês |
|
||||
| CloudFront (10GB de transferência) | ~$0,85/mês |
|
||||
|
||||
::: tip
|
||||
Use o S3 Intelligent Tiering para otimizar custos de arquivos acessados com pouca frequência.
|
||||
:::
|
||||
|
||||
## Automação com Terraform
|
||||
|
||||
```hcl
|
||||
# main.tf
|
||||
resource "aws_s3_bucket" "bentopdf" {
|
||||
bucket = "your-bentopdf-bucket"
|
||||
}
|
||||
|
||||
resource "aws_cloudfront_distribution" "bentopdf" {
|
||||
origin {
|
||||
domain_name = aws_s3_bucket.bentopdf.bucket_regional_domain_name
|
||||
origin_id = "S3Origin"
|
||||
}
|
||||
|
||||
enabled = true
|
||||
default_root_object = "index.html"
|
||||
|
||||
default_cache_behavior {
|
||||
allowed_methods = ["GET", "HEAD"]
|
||||
cached_methods = ["GET", "HEAD"]
|
||||
target_origin_id = "S3Origin"
|
||||
|
||||
viewer_protocol_policy = "redirect-to-https"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,154 @@
|
||||
# Implantação no Cloudflare Pages
|
||||
|
||||
O [Cloudflare Pages](https://pages.cloudflare.com) oferece hospedagem estática rápida e global com largura de banda ilimitada.
|
||||
|
||||
## Implantação Rápida
|
||||
|
||||
1. Acesse o [Cloudflare Pages](https://dash.cloudflare.com/?to=/:account/pages)
|
||||
2. Clique em "Create a project"
|
||||
3. Conecte seu repositório do GitHub
|
||||
|
||||
## Configuração de Build
|
||||
|
||||
| Configuração | Valor |
|
||||
| ---------------------- | --------------- |
|
||||
| Framework preset | Nenhum |
|
||||
| Build command | `npm run build` |
|
||||
| Build output directory | `dist` |
|
||||
| Root directory | `/` |
|
||||
|
||||
## Variáveis de Ambiente
|
||||
|
||||
Adicione estas em Settings → Environment variables:
|
||||
|
||||
| Variável | Valor |
|
||||
| ----------------------- | --------------------------------------------------- |
|
||||
| `NODE_VERSION` | `18` |
|
||||
| `SIMPLE_MODE` | `false` (opcional) |
|
||||
| `VITE_BRAND_NAME` | Nome de marca personalizado (opcional) |
|
||||
| `VITE_BRAND_LOGO` | Caminho do logotipo relativo a `public/` (opcional) |
|
||||
| `VITE_FOOTER_TEXT` | Texto personalizado de rodapé/copyright (opcional) |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Idioma padrão da interface, p. ex. `fr` (opcional) |
|
||||
|
||||
## Arquivo de Configuração
|
||||
|
||||
Crie `_headers` na sua pasta `public`:
|
||||
|
||||
```
|
||||
# Required security headers for SharedArrayBuffer (used by LibreOffice WASM)
|
||||
/*
|
||||
Cross-Origin-Embedder-Policy: require-corp
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
Cross-Origin-Resource-Policy: cross-origin
|
||||
|
||||
# Pre-compressed LibreOffice WASM binary
|
||||
/libreoffice-wasm/soffice.wasm.gz
|
||||
Content-Type: application/wasm
|
||||
Content-Encoding: gzip
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
|
||||
# Pre-compressed LibreOffice WASM data
|
||||
/libreoffice-wasm/soffice.data.gz
|
||||
Content-Type: application/octet-stream
|
||||
Content-Encoding: gzip
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
|
||||
# Cache WASM files aggressively
|
||||
/*.wasm
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
Content-Type: application/wasm
|
||||
|
||||
# Service worker
|
||||
/sw.js
|
||||
Cache-Control: no-cache
|
||||
```
|
||||
|
||||
::: warning Importante
|
||||
Os cabeçalhos `Cross-Origin-Embedder-Policy` e `Cross-Origin-Opener-Policy` são necessários para as conversões de Word/ODT/Excel/PowerPoint para PDF. Sem eles, o `SharedArrayBuffer` fica indisponível e o mecanismo LibreOffice WASM não consegue inicializar.
|
||||
:::
|
||||
|
||||
Crie `_redirects` para o roteamento SPA:
|
||||
|
||||
```
|
||||
/* /index.html 200
|
||||
```
|
||||
|
||||
## Domínio Personalizado
|
||||
|
||||
1. Acesse seu projeto no Pages
|
||||
2. Clique em "Custom domains"
|
||||
3. Adicione seu domínio
|
||||
4. O Cloudflare configura o DNS automaticamente se o domínio estiver no Cloudflare
|
||||
|
||||
## Vantagens
|
||||
|
||||
- **Largura de banda ilimitada e gratuita**
|
||||
- **CDN global** com mais de 300 locais de borda
|
||||
- **HTTPS automático**
|
||||
- **Implantações de pré-visualização** para pull requests
|
||||
- **Builds rápidos**
|
||||
|
||||
## Solução de Problemas
|
||||
|
||||
### Upload de Arquivos Grandes
|
||||
|
||||
O Cloudflare Pages aceita arquivos de até 25 MB. Os módulos WASM devem funcionar bem, mas se você atingir os limites, considere:
|
||||
|
||||
```bash
|
||||
# Split large files during build
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Limites de Tamanho do Worker
|
||||
|
||||
Se você usa o Cloudflare Workers para roteamento avançado, observe o limite de 1 MB nos planos gratuitos.
|
||||
|
||||
## Worker de Proxy CORS (para Assinaturas Digitais)
|
||||
|
||||
A ferramenta de Assinatura Digital exige um proxy CORS para buscar cadeias de certificados. Implante o worker incluído:
|
||||
|
||||
```bash
|
||||
cd cloudflare
|
||||
npx wrangler login
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
### Recursos de Segurança
|
||||
|
||||
| Recurso | Descrição |
|
||||
| -------------------------------- | -------------------------------------- |
|
||||
| **Restrições de URL** | Apenas URLs de certificados permitidas |
|
||||
| **Limite de Tamanho de Arquivo** | Máx. 10MB por requisição |
|
||||
| **Limitação de Taxa** | 60 req/IP/min (requer KV) |
|
||||
| **Bloqueio de IP Privado** | Bloqueia localhost e IPs internos |
|
||||
|
||||
### Ativar Limitação de Taxa
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Compile com a URL do Proxy
|
||||
|
||||
```bash
|
||||
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
|
||||
```
|
||||
|
||||
Ou com 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 .
|
||||
```
|
||||
|
||||
> **Observação:** Veja o [README](https://github.com/alam00000/bentopdf#digital-signature-cors-proxy-required) para configurar a assinatura HMAC.
|
||||
@@ -0,0 +1,180 @@
|
||||
# Proxy CORS para Buscar Certificados
|
||||
|
||||
A ferramenta de assinatura digital usa um proxy CORS para buscar certificados de emissores em Autoridades Certificadoras (CAs) externas. Isso é necessário porque muitos servidores de CA não incluem cabeçalhos CORS nas respostas, o que impede a busca direta pelo navegador.
|
||||
|
||||
Além disso, muitos servidores de CA entregam certificados por HTTP simples. Quando sua instância do BentoPDF é hospedada em HTTPS, os navegadores bloqueiam essas requisições HTTP (política de conteúdo misto). O proxy CORS resolve os dois problemas encaminhando as requisições por um endpoint HTTPS com os cabeçalhos corretos.
|
||||
|
||||
## Como Funciona
|
||||
|
||||
Ao assinar um PDF com um certificado:
|
||||
|
||||
1. A biblioteca `zgapdfsigner` tenta montar uma cadeia de certificados completa
|
||||
2. Ela busca os certificados dos emissores em URLs incorporadas na extensão AIA (Authority Information Access) do seu certificado
|
||||
3. Essas requisições passam por um proxy CORS que adiciona os cabeçalhos `Access-Control-Allow-Origin` necessários
|
||||
4. O proxy devolve os dados do certificado ao navegador
|
||||
|
||||
## Hospedando o Proxy CORS por Conta Própria
|
||||
|
||||
Se você hospeda o BentoPDF por conta própria, precisará implantar seu próprio proxy CORS para que as assinaturas digitais funcionem com certificados que exigem busca da cadeia.
|
||||
|
||||
### Opção 1: Cloudflare Workers (Recomendado)
|
||||
|
||||
1. **Instale a CLI do Wrangler**:
|
||||
|
||||
```bash
|
||||
npm install -g wrangler
|
||||
```
|
||||
|
||||
2. **Faça login no Cloudflare**:
|
||||
|
||||
```bash
|
||||
wrangler login
|
||||
```
|
||||
|
||||
3. **Clone o BentoPDF e atualize as origens permitidas**:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf/cloudflare
|
||||
```
|
||||
|
||||
Abra `cors-proxy-worker.js` e altere o array `ALLOWED_ORIGINS` para o seu domínio:
|
||||
|
||||
```js
|
||||
const ALLOWED_ORIGINS = [
|
||||
'https://your-domain.com',
|
||||
'https://www.your-domain.com',
|
||||
];
|
||||
```
|
||||
|
||||
::: warning Importante
|
||||
Sem essa alteração, o proxy rejeitará todas as requisições do seu site com um erro **403 Forbidden**. O padrão só permite requisições de `bentopdf.com`.
|
||||
:::
|
||||
|
||||
4. **Implante o proxy**:
|
||||
|
||||
```bash
|
||||
wrangler deploy
|
||||
```
|
||||
|
||||
Anote a URL do seu worker (p. ex., `https://bentopdf-cors-proxy.your-subdomain.workers.dev`).
|
||||
|
||||
5. **Recompile o BentoPDF com a URL do proxy**:
|
||||
|
||||
Se estiver usando Docker:
|
||||
|
||||
```bash
|
||||
export VITE_CORS_PROXY_URL="https://your-worker.workers.dev"
|
||||
DOCKER_BUILDKIT=1 docker build \
|
||||
--secret id=VITE_CORS_PROXY_URL,env=VITE_CORS_PROXY_URL \
|
||||
-t your-bentopdf .
|
||||
```
|
||||
|
||||
Se estiver compilando a partir do código-fonte:
|
||||
|
||||
```bash
|
||||
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
|
||||
```
|
||||
|
||||
### Opção 2: Proxy de Backend Personalizado
|
||||
|
||||
Você também pode criar seu próprio endpoint de proxy. Os requisitos são:
|
||||
|
||||
1. Aceitar requisições GET com um parâmetro de consulta `url`
|
||||
2. Buscar a URL a partir do seu servidor (sem restrições de CORS no lado do servidor)
|
||||
3. Devolver a resposta com estes cabeçalhos:
|
||||
- `Access-Control-Allow-Origin: https://your-domain.com`
|
||||
- `Access-Control-Allow-Methods: GET, OPTIONS`
|
||||
- `X-Content-Type-Options: nosniff`
|
||||
|
||||
Exemplo de implementação com 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' });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Considerações de Segurança
|
||||
|
||||
O Cloudflare Worker incluído tem várias medidas de segurança:
|
||||
|
||||
| Recurso | Descrição |
|
||||
| -------------------------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| **Validação de Origem** | Só permite requisições de domínios listados em `ALLOWED_ORIGINS` |
|
||||
| **Restrições de URL** | Só permite URLs de certificados (`.crt`, `.cer`, `.pem`, `/certs/`, `/ocsp`, `/crl`) |
|
||||
| **Bloqueio de IP Privado** | Bloqueia faixas privadas IPv4/IPv6, link-local, loopback, IPs decimais e metadados de nuvem |
|
||||
| **Segurança de Content-Type** | Só devolve tipos MIME de certificado seguros e bloqueia injeção de content-type vinda do upstream |
|
||||
| **Limite de Tamanho de Arquivo** | Transmite a resposta com limite de 10MB e interrompe o download se for excedido |
|
||||
| **Limitação de Taxa** | 60 requisições por minuto por IP ou IPv6 /64 (requer KV, libera o acesso se o KV falhar) |
|
||||
| **Assinaturas HMAC** | Assinatura opcional no cliente (desestimula abuso casual) |
|
||||
|
||||
### DNS rebinding (hospedagem própria fora do Cloudflare)
|
||||
|
||||
O worker rejeita qualquer URL que resolva para um IP privado ou interno. Ele não pode usar uma lista fixa de hosts permitidos, pois as URLs da cadeia de certificados apontam para a CA que emitiu o certificado (FNMT, CAs corporativas e assim por diante), então os destinos não são conhecidos de antemão. Isso deixa uma pequena janela de rebinding: quem controla o DNS do próprio domínio poderia devolver um IP público na verificação de segurança e um IP privado na requisição real, logo em seguida.
|
||||
|
||||
No Cloudflare isso não é explorável: os Workers não alcançam IPs privados nem o endpoint de metadados `169.254.169.254`, então um rebind não chega a lugar nenhum. Apenas não dê ao worker um caminho de entrada: não anexe um Tunnel, Service ou binding de rede privada (ele só precisa de `RATE_LIMIT_KV`).
|
||||
|
||||
Se você executa isso fora do Cloudflare (workerd, Node e similares) dentro de uma VPC, o runtime _consegue_ alcançar IPs internos. Coloque um firewall de saída na frente dele e bloqueie as faixas privadas: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `127.0.0.0/8`, `169.254.0.0/16` e seus equivalentes IPv6. O código da aplicação não consegue impor isso fora do Cloudflare.
|
||||
|
||||
## Desativando o Proxy
|
||||
|
||||
Se você não quer usar um proxy CORS, defina a variável de ambiente como uma string vazia:
|
||||
|
||||
```
|
||||
VITE_CORS_PROXY_URL=
|
||||
```
|
||||
|
||||
**Observação**: Sem o proxy, a assinatura com certificados que exigem busca externa da cadeia (como FNMT ou algumas CAs corporativas) falhará com o erro "Failed to fetch".
|
||||
|
||||
## Solução de Problemas
|
||||
|
||||
### "Signing error: TypeError: Failed to fetch"
|
||||
|
||||
Isso geralmente significa uma destas situações:
|
||||
|
||||
1. **Nenhum proxy CORS configurado**: defina `VITE_CORS_PROXY_URL` e recompile
|
||||
2. **Conteúdo misto bloqueado**: seu site usa HTTPS, mas a URL do emissor do certificado usa HTTP. O proxy CORS resolve isso.
|
||||
3. **Proxy CORS rejeitando sua origem**: verifique se o seu domínio está no array `ALLOWED_ORIGINS` em `cors-proxy-worker.js`
|
||||
|
||||
### "403 Forbidden" vindo do proxy
|
||||
|
||||
Seu domínio não está na lista `ALLOWED_ORIGINS`. Edite `cors-proxy-worker.js`:
|
||||
|
||||
```js
|
||||
const ALLOWED_ORIGINS = ['https://your-domain.com'];
|
||||
```
|
||||
|
||||
Depois, implante novamente: `npx wrangler deploy`
|
||||
|
||||
### Testando o proxy
|
||||
|
||||
```bash
|
||||
curl -H "Origin: https://your-domain.com" \
|
||||
"https://your-proxy.workers.dev?url=http://www.cert.fnmt.es/certs/ACUSU.crt"
|
||||
```
|
||||
|
||||
### Certificados que Funcionam Sem Proxy
|
||||
|
||||
Alguns certificados incluem a cadeia completa no arquivo P12/PFX e não exigem busca externa:
|
||||
|
||||
- Certificados autoassinados
|
||||
- Algumas CAs comerciais que incluem certificados intermediários
|
||||
- Certificados que você montou manualmente com a cadeia completa
|
||||
@@ -0,0 +1,622 @@
|
||||
# Implantação com Docker / Podman
|
||||
|
||||
A maneira mais fácil de hospedar o BentoPDF por conta própria em um ambiente de produção.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Cabeçalhos Necessários para a Conversão de Arquivos do Office**
|
||||
>
|
||||
> As ferramentas baseadas no LibreOffice (conversão de Word, Excel e PowerPoint) exigem estes cabeçalhos HTTP para dar suporte a `SharedArrayBuffer`:
|
||||
>
|
||||
> - `Cross-Origin-Opener-Policy: same-origin`
|
||||
> - `Cross-Origin-Embedder-Policy: require-corp`
|
||||
>
|
||||
> A página também precisa ser servida em um contexto seguro. `http://localhost` funciona para testes locais, mas `http://192.168.x.x` ou outros IPs de rede local normalmente não se qualificam, então a conversão do Office por HTTP simples falhará mesmo com os cabeçalhos presentes.
|
||||
>
|
||||
> As imagens de contêiner oficiais já incluem esses cabeçalhos. Se você usa um proxy reverso (Traefik, Caddy etc.), garanta que esses cabeçalhos sejam preservados ou adicionados, e use HTTPS para acessos que não sejam de loopback.
|
||||
|
||||
> [!TIP]
|
||||
> **Usuários do Podman:** todos os comandos `docker` funcionam com o Podman substituindo `docker` por `podman` e `docker-compose` por `podman-compose`.
|
||||
|
||||
## Início Rápido
|
||||
|
||||
> [!TIP]
|
||||
> O BentoPDF vem em duas versões:
|
||||
>
|
||||
> - **Versão Self-Hosted**: `ghcr.io/alam00000/bentopdf-simple:latest`. Todas as ferramentas de PDF, **sem** a parte de marketing (sem hero, FAQ, depoimentos nem rodapé). Use para implantações internas, de equipes e organizações. **Não** é uma versão "lite" com menos recursos.
|
||||
> - **Versão Comercial**: `ghcr.io/alam00000/bentopdf:latest`. O site de marketing completo, usado pelo próprio bentopdf.com e por titulares de licença comercial que mantêm implantações públicas.
|
||||
>
|
||||
> Em caso de dúvida: baixe a versão Self-Hosted.
|
||||
|
||||
```bash
|
||||
# Docker
|
||||
docker run -d \
|
||||
--name bentopdf \
|
||||
-p 3000:8080 \
|
||||
--restart unless-stopped \
|
||||
ghcr.io/alam00000/bentopdf-simple:latest
|
||||
|
||||
# Podman
|
||||
podman run -d \
|
||||
--name bentopdf \
|
||||
-p 3000:8080 \
|
||||
ghcr.io/alam00000/bentopdf-simple:latest
|
||||
```
|
||||
|
||||
## Docker Compose / Podman Compose
|
||||
|
||||
Crie o `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
|
||||
```
|
||||
|
||||
Execute:
|
||||
|
||||
```bash
|
||||
# Docker Compose
|
||||
docker compose up -d
|
||||
|
||||
# Podman Compose
|
||||
podman-compose up -d
|
||||
```
|
||||
|
||||
## Crie Sua Própria Imagem
|
||||
|
||||
```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;"]
|
||||
```
|
||||
|
||||
Compile e execute:
|
||||
|
||||
```bash
|
||||
docker build -t bentopdf:custom .
|
||||
docker run -d -p 3000:8080 bentopdf:custom
|
||||
```
|
||||
|
||||
## Variáveis de Ambiente
|
||||
|
||||
| Variável | Descrição | Padrão |
|
||||
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
|
||||
| `SIMPLE_MODE` | Compila a variante Self-Hosted (oculta a parte de marketing do bentopdf.com: hero, FAQ, depoimentos e rodapé). Todas as ferramentas de PDF permanecem. | `false` |
|
||||
| `BASE_URL` | Implantar em subdiretório | `/` |
|
||||
| `VITE_WASM_PYMUPDF_URL` | URL do módulo WASM do PyMuPDF | `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/` |
|
||||
| `VITE_WASM_GS_URL` | URL do módulo WASM do Ghostscript | `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm@0.1.1/assets/` |
|
||||
| `VITE_WASM_CPDF_URL` | URL do módulo WASM do CoherentPDF | `https://cdn.jsdelivr.net/npm/coherentpdf@2.5.5/dist/` |
|
||||
| `VITE_TESSERACT_WORKER_URL` | URL do script do worker de OCR | _(vazio; usa a CDN padrão do Tesseract.js)_ |
|
||||
| `VITE_TESSERACT_CORE_URL` | Diretório do runtime principal do OCR | _(vazio; usa a CDN padrão do Tesseract.js)_ |
|
||||
| `VITE_TESSERACT_LANG_URL` | Diretório traineddata do OCR | _(vazio; usa a CDN padrão do Tesseract.js)_ |
|
||||
| `VITE_TESSERACT_AVAILABLE_LANGUAGES` | Idiomas de OCR separados por vírgula exibidos na interface | _(vazio; mostra o catálogo completo)_ |
|
||||
| `VITE_OCR_FONT_BASE_URL` | Diretório de fontes da camada de texto do OCR | _(vazio; usa as URLs remotas de fontes Noto)_ |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Idioma padrão da interface | `en` |
|
||||
| `VITE_BRAND_NAME` | Nome de marca personalizado | `BentoPDF` |
|
||||
| `VITE_BRAND_LOGO` | Caminho do logotipo relativo a `public/` | `images/favicon-no-bg.svg` |
|
||||
| `VITE_FOOTER_TEXT` | Texto personalizado de rodapé/copyright | `© 2026 BentoPDF. All rights reserved.` |
|
||||
| `DISABLE_TOOLS` | IDs de ferramentas separados por vírgula a ocultar | _(vazio; todas as ferramentas ativadas)_ |
|
||||
| `DISABLE_GITHUB_STARS` | Pula a busca da contagem de estrelas do GitHub ao carregar a página e remove `api.github.com` da CSP. As versões Simple Mode sempre a dispensam. | `false` |
|
||||
| `SITE_URL` | URL pública gravada nos links canonical, hreflang e sitemap. Mantenha o padrão, a menos que queira que os mecanismos de busca tratem sua implantação como um site próprio. | `https://www.bentopdf.com` |
|
||||
| `ALLOW_INDEXING` | Com um `SITE_URL` personalizado, todas as páginas são marcadas como `noindex` para que sua cópia não concorra com o bentopdf.com nos resultados de busca. Defina como `true` para permitir a indexação pelos mecanismos de busca. | `false` |
|
||||
|
||||
As URLs dos módulos WASM vêm pré-configuradas com padrões de CDN, então todos os recursos avançados funcionam sem configuração. Substitua-as em implantações isoladas da internet (air-gapped) ou hospedadas por conta própria.
|
||||
|
||||
### Mecanismos de busca
|
||||
|
||||
Por padrão, cada página indica aos mecanismos de busca o bentopdf.com como original, para que sua implantação não apareça como duplicada. Você não precisa mudar nada em uma instância privada ou interna.
|
||||
|
||||
- **Manter sua instância fora dos resultados de busca:** execute o contêiner com `-e ROBOTS_NOINDEX=true`. Isso exige apenas uma reinicialização, não uma recompilação.
|
||||
- **Fazer da sua instância um site próprio nos resultados de busca:** compile com `--build-arg SITE_URL=https://your-domain.example --build-arg ALLOW_INDEXING=true`. Sem `ALLOW_INDEXING=true`, um `SITE_URL` personalizado marca todas as páginas como `noindex`.
|
||||
|
||||
### Content-Security-Policy
|
||||
|
||||
A imagem do nginx vem com um cabeçalho `Content-Security-Policy` em modo de aplicação. As diretivas `script-src`, `connect-src` e `font-src` da CSP são **geradas em tempo de build** a partir das variáveis de URL `VITE_*` acima: os hosts que você passar via `--build-arg` são adicionados automaticamente à política.
|
||||
|
||||
Como resultado:
|
||||
|
||||
- Se você substituir `VITE_CORS_PROXY_URL` ou `VITE_WASM_*_URL` em tempo de build, a CSP permite essas origens automaticamente, sem configuração extra.
|
||||
- Para URLs em `cdn.jsdelivr.net`, o `script-src` permite apenas a pasta exata do pacote com versão, por exemplo `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/`, e não a CDN inteira. O jsDelivr serve qualquer pacote npm público ou repositório do GitHub, então permitir o host inteiro deixaria qualquer um deles rodar na sua instância. URLs em qualquer outro host são permitidas por origem.
|
||||
- Se você configurar URLs WASM personalizadas em _tempo de execução_ pela página de Configurações Avançadas do app, essas origens **não** estarão na CSP e o navegador bloqueará as requisições a elas. Isso vale também para uma versão diferente do mesmo pacote em `cdn.jsdelivr.net`, pois só a versão definida em tempo de build é permitida. A configuração em tempo de execução serve para experimentação; para URLs personalizadas permanentes, defina o build arg `VITE_*` correspondente.
|
||||
- Em implantações air-gapped que substituem os três valores `VITE_WASM_*_URL`, essas pastas de pacote de `cdn.jsdelivr.net` também são removidas da CSP (cada padrão é substituído, não acrescentado). Os padrões do Tesseract OCR em `cdn.jsdelivr.net` permanecem na política até que você defina também os três valores `VITE_TESSERACT_*`. Da mesma forma, definir `VITE_CORS_PROXY_URL` substitui o padrão público `bentopdf-cors-proxy.bentopdf.workers.dev`.
|
||||
|
||||
A CSP inclui `'unsafe-eval'` no `script-src` porque o runtime do LibreOffice WASM (usado pelas ferramentas de conversão de Word/Excel/PowerPoint) e a biblioteca de imagens wasm-vips (usada em PDF para TIFF) compilam código interno de despacho via `new Function()`. Removê-lo quebraria essas ferramentas. Se você compila em `SIMPLE_MODE` (sem LibreOffice) e não precisa de PDF para TIFF, pode editar manualmente o `security-headers.conf` gerado para remover `'unsafe-eval'` e obter uma política mais restrita.
|
||||
|
||||
Para o OCR, deixe as variáveis `VITE_TESSERACT_*` vazias para usar os recursos online padrão, ou defina as três juntas para OCR hospedado por conta própria/offline. Substituições parciais do OCR são rejeitadas porque o worker, o runtime principal e o diretório traineddata precisam corresponder. Para uma saída de PDF pesquisável totalmente offline, defina também `VITE_OCR_FONT_BASE_URL`, de modo que as fontes da camada de texto do OCR sejam carregadas do seu servidor interno e não das URLs públicas de fontes Noto.
|
||||
|
||||
`VITE_DEFAULT_LANGUAGE` define o idioma da interface para quem visita pela primeira vez. Valores aceitos: `en`, `ar`, `be`, `fr`, `de`, `es`, `zh`, `zh-TW`, `vi`, `tr`, `id`, `it`, `pt`, `nl`, `da`. Os usuários ainda podem trocar de idioma; isso só muda o padrão.
|
||||
|
||||
Exemplo:
|
||||
|
||||
```bash
|
||||
# Build with French as the default language
|
||||
docker build --build-arg VITE_DEFAULT_LANGUAGE=fr -t bentopdf .
|
||||
docker run -d -p 3000:8080 bentopdf
|
||||
```
|
||||
|
||||
### Marca Personalizada
|
||||
|
||||
Substitua o logotipo, o nome e o texto de rodapé padrão do BentoPDF pelos seus. Coloque seu arquivo de logotipo na pasta `public/` (ou use uma imagem existente) e passe as variáveis de marca em tempo de build:
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--build-arg VITE_BRAND_NAME="AcmePDF" \
|
||||
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
|
||||
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
|
||||
-t acmepdf .
|
||||
```
|
||||
|
||||
A marca personalizada funciona tanto no modo completo quanto no Simple Mode, e pode ser combinada com todas as outras opções de build.
|
||||
|
||||
### Desativando Ferramentas Específicas
|
||||
|
||||
Oculte ferramentas da interface por requisitos de conformidade ou segurança. As ferramentas desativadas são removidas da página inicial, dos resultados de busca, dos atalhos de teclado e do construtor de fluxos de trabalho. O acesso direto pela URL mostra uma página de "ferramenta indisponível".
|
||||
|
||||
Os IDs das ferramentas são a URL da página sem `.html`. Por exemplo, se a ferramenta está em `bentopdf.com/edit-pdf.html`, o ID é `edit-pdf`.
|
||||
|
||||
#### Encontrando os IDs das Ferramentas
|
||||
|
||||
O jeito mais fácil: abra qualquer ferramenta no BentoPDF e olhe a URL. A última parte do caminho (sem `.html`) é o ID da ferramenta.
|
||||
|
||||
<details>
|
||||
<summary>Lista completa de IDs das ferramentas</summary>
|
||||
|
||||
**Editar e Anotar:** `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`
|
||||
|
||||
**Converter para 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`
|
||||
|
||||
**Converter de PDF:** `pdf-to-jpg`, `pdf-to-png`, `pdf-to-webp`, `pdf-to-bmp`, `pdf-to-tiff`, `pdf-to-cbz`, `pdf-to-svg`, `pdf-to-csv`, `pdf-to-excel`, `pdf-to-greyscale`, `pdf-to-json`, `pdf-to-docx`, `extract-images`, `pdf-to-markdown`, `prepare-pdf-for-ai`, `pdf-to-text`
|
||||
|
||||
**Organizar e Gerenciar:** `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`
|
||||
|
||||
**Otimizar e Reparar:** `compress-pdf`, `pdf-to-pdfa`, `fix-page-size`, `linearize-pdf`, `remove-restrictions`, `repair-pdf`, `rasterize-pdf`, `deskew-pdf`, `font-to-outline`
|
||||
|
||||
**Segurança:** `encrypt-pdf`, `sanitize-pdf`, `decrypt-pdf`, `flatten-pdf`, `remove-metadata`, `change-permissions`, `digital-sign-pdf`, `validate-signature-pdf`, `timestamp-pdf`
|
||||
|
||||
</details>
|
||||
|
||||
#### Opção 1: Em tempo de build (build arg do Docker)
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--build-arg DISABLE_TOOLS="edit-pdf,sign-pdf,encrypt-pdf" \
|
||||
-t bentopdf .
|
||||
```
|
||||
|
||||
Isso embute a lista de desativadas no bundle JavaScript. Mudar exige uma recompilação.
|
||||
|
||||
#### Opção 2: Em tempo de execução (config.json)
|
||||
|
||||
Monte um arquivo `config.json` no diretório servido, sem precisar recompilar:
|
||||
|
||||
```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 com 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
|
||||
```
|
||||
|
||||
Os dois métodos podem ser combinados: as listas são mescladas. Se uma ferramenta aparecer em qualquer uma delas, ela é desativada.
|
||||
|
||||
#### Desativando Recursos do Editor
|
||||
|
||||
Você também pode desativar recursos específicos dentro do Editor de PDF (p. ex., redação, anotações, formulários) sem desativar a ferramenta de edição inteira. Adicione `editorDisabledCategories` ao seu `config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"editorDisabledCategories": ["redaction", "annotation-stamp"]
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Lista completa de categorias do editor</summary>
|
||||
|
||||
**Zoom:** `zoom`, `zoom-in`, `zoom-out`, `zoom-fit-page`, `zoom-fit-width`, `zoom-marquee`, `zoom-level`
|
||||
|
||||
**Anotação:** `annotation`, `annotation-markup`, `annotation-highlight`, `annotation-underline`, `annotation-strikeout`, `annotation-squiggly`, `annotation-ink`, `annotation-text`, `annotation-stamp`
|
||||
|
||||
**Formas:** `annotation-shape`, `annotation-rectangle`, `annotation-circle`, `annotation-line`, `annotation-arrow`, `annotation-polygon`, `annotation-polyline`
|
||||
|
||||
**Formulário:** `form`, `form-textfield`, `form-checkbox`, `form-radio`, `form-select`, `form-listbox`, `form-fill-mode`
|
||||
|
||||
**Redação:** `redaction`, `redaction-area`, `redaction-text`, `redaction-apply`, `redaction-clear`
|
||||
|
||||
**Documento:** `document`, `document-open`, `document-close`, `document-print`, `document-capture`, `document-export`, `document-fullscreen`, `document-protect`
|
||||
|
||||
**Página:** `page`, `spread`, `rotate`, `scroll`, `navigation`
|
||||
|
||||
**Painel:** `panel`, `panel-sidebar`, `panel-search`, `panel-comment`
|
||||
|
||||
**Ferramentas:** `tools`, `pan`, `pointer`, `capture`
|
||||
|
||||
**Seleção:** `selection`, `selection-copy`
|
||||
|
||||
**Histórico:** `history`, `history-undo`, `history-redo`
|
||||
|
||||
</details>
|
||||
|
||||
As categorias são hierárquicas: desativar uma categoria pai (p. ex., `annotation`) desativa todas as suas filhas.
|
||||
|
||||
### Desativando o Contador de Estrelas do GitHub
|
||||
|
||||
A barra de navegação mostra a contagem de estrelas do GitHub em tempo real, buscada em `api.github.com` ao carregar a página. É a única requisição externa que não atende a um recurso de PDF. Desative-a em tempo de build para remover o código de busca do bundle e tirar `api.github.com` da Content-Security-Policy:
|
||||
|
||||
```bash
|
||||
docker build --build-arg DISABLE_GITHUB_STARS=true -t bentopdf .
|
||||
```
|
||||
|
||||
As versões Simple Mode (`bentopdf-simple`) sempre dispensam o contador de estrelas; essa opção só é necessária na versão Comercial ou em builds completos personalizados.
|
||||
|
||||
### URLs WASM Personalizadas (Air-Gapped / Hospedagem Própria)
|
||||
|
||||
> [!IMPORTANT]
|
||||
> As URLs WASM são embutidas no JavaScript em **tempo de build**. Os arquivos WASM são baixados pelo **navegador do usuário** em tempo de execução: o Docker não os baixa durante o build. Em redes air-gapped, você precisa hospedar os arquivos WASM em um servidor interno que os navegadores consigam alcançar.
|
||||
|
||||
**Fluxo de trabalho air-gapped completo:**
|
||||
|
||||
```bash
|
||||
# 1. On a machine WITH internet — download WASM packages
|
||||
bash scripts/prepare-airgap.sh --list-ocr-languages
|
||||
bash scripts/prepare-airgap.sh --search-ocr-language german
|
||||
|
||||
# 2. Download WASM/OCR packages
|
||||
npm pack @bentopdf/pymupdf-wasm@0.11.14
|
||||
npm pack @bentopdf/gs-wasm
|
||||
npm pack coherentpdf
|
||||
npm pack tesseract.js@7.0.0
|
||||
npm pack tesseract.js-core@7.0.0
|
||||
mkdir -p tesseract-langdata
|
||||
curl -fsSL https://cdn.jsdelivr.net/npm/@tesseract.js-data/eng/4.0.0_best_int/eng.traineddata.gz -o tesseract-langdata/eng.traineddata.gz
|
||||
mkdir -p ocr-fonts
|
||||
curl -fsSL https://raw.githack.com/googlefonts/noto-fonts/main/hinted/ttf/NotoSans/NotoSans-Regular.ttf -o ocr-fonts/NotoSans-Regular.ttf
|
||||
|
||||
# 3. Build the image with your internal server URLs
|
||||
docker build \
|
||||
--build-arg VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ \
|
||||
--build-arg VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ \
|
||||
--build-arg VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ \
|
||||
--build-arg VITE_TESSERACT_WORKER_URL=https://internal-server.example.com/wasm/ocr/worker.min.js \
|
||||
--build-arg VITE_TESSERACT_CORE_URL=https://internal-server.example.com/wasm/ocr/core \
|
||||
--build-arg VITE_TESSERACT_LANG_URL=https://internal-server.example.com/wasm/ocr/lang-data \
|
||||
--build-arg VITE_TESSERACT_AVAILABLE_LANGUAGES=eng,deu \
|
||||
--build-arg VITE_OCR_FONT_BASE_URL=https://internal-server.example.com/wasm/ocr/fonts \
|
||||
-t bentopdf .
|
||||
|
||||
# 4. Export the image
|
||||
docker save bentopdf -o bentopdf.tar
|
||||
|
||||
# 5. Transfer bentopdf.tar + the .tgz packages + tesseract-langdata/ + ocr-fonts/ into the air-gapped network
|
||||
|
||||
# 6. Inside the air-gapped network — load and run
|
||||
docker load -i bentopdf.tar
|
||||
|
||||
# Extract WASM packages to your internal web server
|
||||
mkdir -p /var/www/wasm/pymupdf /var/www/wasm/gs /var/www/wasm/cpdf /var/www/wasm/ocr/core /var/www/wasm/ocr/lang-data /var/www/wasm/ocr/fonts
|
||||
tar xzf bentopdf-pymupdf-wasm-0.11.14.tgz -C /var/www/wasm/pymupdf --strip-components=1
|
||||
tar xzf bentopdf-gs-wasm-*.tgz -C /var/www/wasm/gs --strip-components=1
|
||||
tar xzf coherentpdf-*.tgz -C /var/www/wasm/cpdf --strip-components=1
|
||||
TEMP_TESS=$(mktemp -d)
|
||||
tar xzf tesseract.js-7.0.0.tgz -C "$TEMP_TESS"
|
||||
cp "$TEMP_TESS/package/dist/worker.min.js" /var/www/wasm/ocr/worker.min.js
|
||||
rm -rf "$TEMP_TESS"
|
||||
tar xzf tesseract.js-core-7.0.0.tgz -C /var/www/wasm/ocr/core --strip-components=1
|
||||
cp ./tesseract-langdata/*.traineddata.gz /var/www/wasm/ocr/lang-data/
|
||||
cp ./ocr-fonts/* /var/www/wasm/ocr/fonts/
|
||||
|
||||
# Run BentoPDF
|
||||
docker run -d -p 3000:8080 --restart unless-stopped bentopdf
|
||||
```
|
||||
|
||||
Use os códigos exibidos por `bash scripts/prepare-airgap.sh --list-ocr-languages`, ou busque pelo nome com `bash scripts/prepare-airgap.sh --search-ocr-language <term>`, em `--ocr-languages`. Ao compilar com um subconjunto restrito de OCR, passe os mesmos códigos para `VITE_TESSERACT_AVAILABLE_LANGUAGES` para que o app mostre apenas os idiomas incluídos. Para uma saída de OCR totalmente offline, hospede também o diretório `ocr-fonts/` incluído e aponte `VITE_OCR_FONT_BASE_URL` para ele.
|
||||
|
||||
Defina uma variável como string vazia para desativar esse módulo (os usuários precisarão configurar manualmente em Configurações Avançadas).
|
||||
|
||||
## Porta Personalizada
|
||||
|
||||
Por padrão, o BentoPDF escuta na porta `8080` dentro do contêiner. Para mudar isso, defina a variável de ambiente `PORT` em tempo de execução:
|
||||
|
||||
```bash
|
||||
docker run -p 3000:9090 -e PORT=9090 ghcr.io/alam00000/bentopdf-simple:latest
|
||||
```
|
||||
|
||||
| Variável | Descrição | Padrão |
|
||||
| -------- | ------------------------------------- | ------ |
|
||||
| `PORT` | Porta de escuta do Nginx no contêiner | `8080` |
|
||||
|
||||
Isso funciona tanto com o Dockerfile padrão quanto com o nonroot.
|
||||
|
||||
## ID de Usuário Personalizado (PUID/PGID)
|
||||
|
||||
Para ambientes que exigem execução como um usuário específico que não seja root (dispositivos NAS, Kubernetes com contextos de segurança, políticas organizacionais), o BentoPDF oferece um Dockerfile separado com suporte a PUID/PGID no estilo LSIO.
|
||||
|
||||
### Compile e Execute
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Variáveis de Ambiente
|
||||
|
||||
| Variável | Descrição | Padrão |
|
||||
| -------------- | ------------------------- | ------- |
|
||||
| `PUID` | ID do usuário de execução | `1000` |
|
||||
| `PGID` | ID do grupo de execução | `1000` |
|
||||
| `PORT` | Porta de escuta do Nginx | `8080` |
|
||||
| `DISABLE_IPV6` | Desativar o listener IPv6 | `false` |
|
||||
|
||||
### Docker Compose
|
||||
|
||||
```yaml
|
||||
services:
|
||||
bentopdf:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.nonroot
|
||||
container_name: bentopdf
|
||||
ports:
|
||||
- '3000:8080'
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
### Como Funciona
|
||||
|
||||
O contêiner inicia como root, cria um usuário com o PUID/PGID especificado, ajusta a propriedade de todos os diretórios graváveis e depois reduz os privilégios usando `su-exec`. O processo do nginx roda inteiramente como o usuário que você especificou.
|
||||
|
||||
> [!NOTE]
|
||||
> O `Dockerfile` padrão usa `nginx-unprivileged` (UID 101) e é recomendado para a maioria das implantações. Use o `Dockerfile.nonroot` apenas quando precisar de um UID/GID específico.
|
||||
|
||||
> [!WARNING]
|
||||
> PUID/PGID não podem ser `0` (root). O entrypoint valida as entradas e encerra com erro para valores inválidos.
|
||||
|
||||
## Com Traefik (Proxy Reverso)
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Com Caddy (Proxy Reverso)
|
||||
|
||||
```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 Recursos
|
||||
|
||||
```yaml
|
||||
services:
|
||||
bentopdf:
|
||||
image: ghcr.io/alam00000/bentopdf-simple:latest
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '1'
|
||||
memory: 512M
|
||||
reservations:
|
||||
cpus: '0.25'
|
||||
memory: 128M
|
||||
```
|
||||
|
||||
## Podman Quadlet (Integração com Systemd)
|
||||
|
||||
O [Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) permite executar contêineres do Podman como serviços do systemd. É ideal para implantações de produção em sistemas Linux.
|
||||
|
||||
### Configuração Básica do Quadlet
|
||||
|
||||
Crie um arquivo de unidade de contêiner em `~/.config/containers/systemd/bentopdf.container` (usuário) ou `/etc/containers/systemd/bentopdf.container` (sistema):
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=BentoPDF - Privacy-first PDF toolkit
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
Image=ghcr.io/alam00000/bentopdf-simple:latest
|
||||
ContainerName=bentopdf
|
||||
PublishPort=3000:8080
|
||||
AutoUpdate=registry
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=300
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
### Habilite e Inicie
|
||||
|
||||
```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]
|
||||
> Para implantação em todo o sistema, use `systemctl` sem a flag `--user` e coloque o arquivo em `/etc/containers/systemd/`.
|
||||
|
||||
### Quadlet com Health Check
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=BentoPDF with health monitoring
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
Image=ghcr.io/alam00000/bentopdf-simple:latest
|
||||
ContainerName=bentopdf
|
||||
PublishPort=3000:8080
|
||||
AutoUpdate=registry
|
||||
HealthCmd=wget --spider -q http://localhost:8080 || exit 1
|
||||
HealthInterval=30s
|
||||
HealthTimeout=10s
|
||||
HealthRetries=3
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
TimeoutStartSec=300
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
### Atualização Automática com Quadlet
|
||||
|
||||
O Podman pode atualizar contêineres automaticamente quando houver novas imagens disponíveis:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Configuração de Rede do Quadlet
|
||||
|
||||
Para uma configuração de rede personalizada, crie um arquivo de rede `bentopdf.network`:
|
||||
|
||||
```ini
|
||||
[Network]
|
||||
Subnet=10.89.0.0/24
|
||||
Gateway=10.89.0.1
|
||||
```
|
||||
|
||||
Depois, referencie-o no seu arquivo de contêiner:
|
||||
|
||||
```ini
|
||||
[Container]
|
||||
Image=ghcr.io/alam00000/bentopdf-simple:latest
|
||||
ContainerName=bentopdf
|
||||
PublishPort=3000:8080
|
||||
Network=bentopdf.network
|
||||
```
|
||||
|
||||
## Atualizando
|
||||
|
||||
```bash
|
||||
# Pull latest image
|
||||
docker compose pull
|
||||
|
||||
# Recreate container
|
||||
docker compose up -d
|
||||
```
|
||||
@@ -0,0 +1,266 @@
|
||||
# Implantação na Hostinger
|
||||
|
||||
A [Hostinger](https://hostinger.com) é um provedor popular de hospedagem compartilhada. Este guia explica como implantar o BentoPDF na hospedagem compartilhada da Hostinger.
|
||||
|
||||
## Pré-requisitos
|
||||
|
||||
- Plano de hospedagem da Hostinger com acesso ao gerenciador de arquivos
|
||||
- Node.js instalado localmente para compilar
|
||||
|
||||
## Passo 1: Compile o Projeto
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
Os arquivos compilados ficarão na pasta `dist`.
|
||||
|
||||
## Passo 2: Envie para a Hostinger
|
||||
|
||||
### Implantação no Domínio Raiz
|
||||
|
||||
1. Entre na sua conta da Hostinger
|
||||
2. Vá para **File Manager** → **public_html**
|
||||
3. **Exclua** os arquivos existentes (faça backup, se necessário)
|
||||
4. **Envie** todo o conteúdo da sua pasta local `dist` para `public_html`
|
||||
|
||||
### Implantação em Subdiretório
|
||||
|
||||
Se for implantar em um subdiretório (p. ex., `yourdomain.com/pdf-tools/`):
|
||||
|
||||
1. Compile com a URL base correta:
|
||||
|
||||
```bash
|
||||
BASE_URL=/pdf-tools/ npm run build
|
||||
```
|
||||
|
||||
2. Crie a pasta na Hostinger:
|
||||
- Vá para **File Manager** → **public_html**
|
||||
- Crie uma nova pasta: `pdf-tools`
|
||||
3. Envie todo o conteúdo de `dist` para `public_html/pdf-tools/`
|
||||
|
||||
## Passo 3: Crie o Arquivo .htaccess
|
||||
|
||||
Crie um arquivo `.htaccess` na raiz da sua pasta de implantação (`public_html` ou `public_html/pdf-tools/`):
|
||||
|
||||
::: warning Importante
|
||||
Para implantação em subdiretório, altere `RewriteBase /` para `RewriteBase /pdf-tools/` (ou o nome da sua pasta).
|
||||
:::
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Exemplo de .htaccess para Subdiretório
|
||||
|
||||
Para `yourdomain.com/pdf-tools/`, atualize estas linhas:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Solução de Problemas
|
||||
|
||||
### Arquivos WASM Não Carregam
|
||||
|
||||
Verifique se os tipos MIME estão definidos corretamente no `.htaccess`:
|
||||
|
||||
```apache
|
||||
AddType application/wasm .wasm
|
||||
```
|
||||
|
||||
### Ferramentas do LibreOffice Não Funcionam
|
||||
|
||||
Os cabeçalhos de segurança são essenciais para o SharedArrayBuffer:
|
||||
|
||||
```apache
|
||||
Header always set Cross-Origin-Opener-Policy "same-origin"
|
||||
Header always set Cross-Origin-Embedder-Policy "require-corp"
|
||||
```
|
||||
|
||||
Se os cabeçalhos não estiverem sendo aplicados, entre em contato com o suporte da Hostinger para habilitar o `mod_headers`.
|
||||
|
||||
### Erros 404 ao Atualizar a Página
|
||||
|
||||
Verifique se a regra da extensão `.html` e as rotas de idioma estão configuradas corretamente. O BentoPDF usa arquivos HTML estáticos, não roteamento 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 Upload de Arquivos
|
||||
|
||||
A Hostinger pode ter limites de tamanho de arquivo. Envie os arquivos WASM separadamente se o upload em massa falhar.
|
||||
@@ -0,0 +1,538 @@
|
||||
# Guia de Auto-Hospedagem
|
||||
|
||||
O BentoPDF pode ser auto-hospedado na sua própria infraestrutura. Este guia cobre várias opções de implantação.
|
||||
|
||||
## Início Rápido com Docker / Podman
|
||||
|
||||
A forma mais rápida de auto-hospedar o BentoPDF:
|
||||
|
||||
> [!TIP]
|
||||
> O BentoPDF é distribuído em duas versões:
|
||||
>
|
||||
> - **Versão Self-Hosted**: `ghcr.io/alam00000/bentopdf-simple:latest`. Todas as ferramentas de PDF, **sem** os elementos de marketing (sem hero, FAQ, depoimentos nem rodapé). Use esta para implantações internas, de equipe ou de organização. **Não** é uma versão "lite" com menos recursos.
|
||||
> - **Versão Commercial**: `ghcr.io/alam00000/bentopdf:latest`. O site de marketing completo, usado pelo próprio bentopdf.com e por detentores de licença comercial que executam implantações voltadas ao público.
|
||||
>
|
||||
> Na dúvida: use a versão Self-Hosted.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> A conversão de arquivos do Office exige `SharedArrayBuffer`, o que significa que o aplicativo precisa estar isolado de origem cruzada (cross-origin isolated) e ser servido a partir de um contexto seguro. A imagem oficial já envia os cabeçalhos COOP/COEP necessários, mas os navegadores ainda desativam o `SharedArrayBuffer` em origens HTTP simples da rede local, como `http://192.168.x.x`.
|
||||
>
|
||||
> Use `http://localhost` apenas para testes no mesmo dispositivo. Se os usuários acessarem o BentoPDF por um IP ou nome de host da rede local, termine a conexão com 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 com Docker Compose / Podman Compose:
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
services:
|
||||
bentopdf:
|
||||
image: ghcr.io/alam00000/bentopdf-simple:latest
|
||||
ports:
|
||||
- '3000:8080'
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
```bash
|
||||
# Docker Compose
|
||||
docker compose up -d
|
||||
|
||||
# Podman Compose
|
||||
podman-compose up -d
|
||||
```
|
||||
|
||||
## Podman Quadlet (Linux Systemd)
|
||||
|
||||
Execute o BentoPDF como um serviço do systemd. Crie `~/.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
|
||||
```
|
||||
|
||||
Veja o [guia de implantação com Docker](/pt/self-hosting/docker) para a documentação completa do Quadlet.
|
||||
|
||||
## Compilando a Partir do Código-Fonte
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Opções de Configuração
|
||||
|
||||
### Versão Self-Hosted (Simple Mode)
|
||||
|
||||
A versão Self-Hosted (a imagem `bentopdf-simple`, também chamada de Simple Mode) é **funcionalmente idêntica** à versão Commercial: todas as ferramentas de PDF estão presentes e se comportam da mesma forma. Ela apenas oculta os elementos de marketing que só fazem sentido no próprio bentopdf.com ou em uma implantação comercial voltada ao público. **Não é uma versão "lite" nem com menos recursos.**
|
||||
|
||||
**O que a versão Self-Hosted oculta** (apenas visual, nenhum recurso de PDF é removido):
|
||||
|
||||
- Barra de navegação, seção hero, seção de recursos, FAQ, depoimentos, rodapé
|
||||
- Altera o título da página para "Ferramentas PDF"
|
||||
|
||||
**O que a versão Self-Hosted mantém** (tudo o que de fato trabalha com PDF):
|
||||
|
||||
- Todas as ferramentas de PDF (mesclar, dividir, editar, assinar, OCR, conversão do Office etc.)
|
||||
- Suporte a marca personalizada e toda a configuração em tempo de compilação e de execução
|
||||
|
||||
A versão Commercial (`ghcr.io/alam00000/bentopdf:latest`) é a que alimenta o próprio bentopdf.com e é usada por detentores de licença comercial que executam implantações voltadas ao público. Ela acrescenta o hero, o FAQ, os depoimentos e o rodapé, que não fariam sentido em uma ferramenta interna.
|
||||
|
||||
Se você está auto-hospedando o BentoPDF para sua equipe, sua organização ou como ferramenta interna, use a versão Self-Hosted:
|
||||
|
||||
```bash
|
||||
# Use the pre-built image (recommended)
|
||||
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
|
||||
|
||||
# Or build it yourself
|
||||
SIMPLE_MODE=true npm run build
|
||||
```
|
||||
|
||||
Veja [SIMPLE_MODE.md](https://github.com/alam00000/bentopdf/blob/main/SIMPLE_MODE.md) para mais detalhes.
|
||||
|
||||
### Versão Commercial (implantações voltadas ao público com sua própria marca)
|
||||
|
||||
A versão Commercial (a imagem `bentopdf`, sem o sufixo `-simple`) é a que alimenta o próprio bentopdf.com. Ela inclui o site de marketing completo (hero, recursos, FAQ, depoimentos, rodapé) além de todas as ferramentas de PDF. Use esta versão quando quiser **implantar o BentoPDF como um serviço de PDF voltado ao público, sob sua própria marca**, por exemplo:
|
||||
|
||||
- Você executa o BentoPDF como um SaaS hospedado para usuários finais no seu próprio domínio
|
||||
- Você quer a experiência de landing page (seções de marketing + ferramentas), e não apenas a interface básica das ferramentas
|
||||
- Você é detentor de licença comercial e incorpora o BentoPDF em um produto comercial
|
||||
|
||||
**Execute como está** (com a marca BentoPDF, útil para visualizar a versão):
|
||||
|
||||
```bash
|
||||
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
|
||||
```
|
||||
|
||||
**Compile com a sua própria marca** (o fluxo comercial típico):
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Todas as outras opções de tempo de compilação (`BASE_URL`, `VITE_DEFAULT_LANGUAGE`, `DISABLE_TOOLS`, substituições de URL do WASM etc.) funcionam da mesma forma que na versão Self-Hosted.
|
||||
|
||||
::: warning Licenciamento
|
||||
Executar a versão Commercial é permitido sob qualquer uma das duas opções de licença do BentoPDF:
|
||||
|
||||
- **AGPL-3.0** (gratuita): permitida se a sua implantação publicar todo o seu código-fonte sob AGPL, incluindo quaisquer modificações de marca e a lógica de negócio associada.
|
||||
- **Licença comercial** ($79 vitalícia): necessária para implantações proprietárias ou de código fechado, em que você não abre o código do seu fork de marca nem do seu código de negócio.
|
||||
|
||||
Veja a [página de Licenciamento](https://www.bentopdf.com/licensing) para a comparação completa. Os módulos WASM licenciados sob AGPL (PyMuPDF, Ghostscript, CoherentPDF) são carregados de uma CDN em tempo de execução, portanto não entram na sua imagem e não alteram a sua situação de licenciamento.
|
||||
:::
|
||||
|
||||
### URL Base
|
||||
|
||||
Implante em um subdiretório:
|
||||
|
||||
```bash
|
||||
BASE_URL=/pdf-tools/ npm run build
|
||||
```
|
||||
|
||||
### Marca Personalizada
|
||||
|
||||
Substitua o logotipo, o nome e o texto de rodapé padrão do BentoPDF pelos seus em tempo de compilação:
|
||||
|
||||
| Variável | Descrição | Padrão |
|
||||
| ------------------ | ----------------------------------------------- | --------------------------------------- |
|
||||
| `VITE_BRAND_NAME` | Nome da marca exibido no cabeçalho e no rodapé | `BentoPDF` |
|
||||
| `VITE_BRAND_LOGO` | Caminho do logotipo relativo a `public/` | `images/favicon-no-bg.svg` |
|
||||
| `VITE_FOOTER_TEXT` | Texto personalizado de rodapé/direitos autorais | `© 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 .
|
||||
```
|
||||
|
||||
A marca personalizada funciona tanto no modo completo quanto no Simple Mode e pode ser combinada com todas as outras opções de tempo de compilação (`BASE_URL`, `SIMPLE_MODE`, `VITE_DEFAULT_LANGUAGE`).
|
||||
|
||||
### Desativando Ferramentas Específicas
|
||||
|
||||
Oculte ferramentas individuais por conformidade ou segurança. As ferramentas desativadas são removidas da página inicial, da busca, dos atalhos, do construtor de fluxos e do acesso direto por URL.
|
||||
|
||||
Os IDs das ferramentas são a URL da página sem `.html`: abra qualquer ferramenta e veja a URL (ex.: `edit-pdf`, `sign-pdf`, `encrypt-pdf`).
|
||||
|
||||
**Em tempo de compilação** (incorporado ao pacote):
|
||||
|
||||
```bash
|
||||
DISABLE_TOOLS="edit-pdf,sign-pdf" npm run build
|
||||
```
|
||||
|
||||
**Em tempo de execução** (sem recompilar, monte um `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
|
||||
```
|
||||
|
||||
Os dois métodos podem ser combinados: as listas são mescladas.
|
||||
|
||||
Você também pode desativar recursos específicos dentro do Editor de PDF (ex.: redação) sem desativar a ferramenta inteira, adicionando `editorDisabledCategories` ao `config.json`. Veja o [guia do Docker](/pt/self-hosting/docker#desativando-recursos-do-editor) para a lista completa de categorias.
|
||||
|
||||
## Guias de Implantação
|
||||
|
||||
Escolha sua plataforma:
|
||||
|
||||
- [Vercel](/pt/self-hosting/vercel)
|
||||
- [Netlify](/pt/self-hosting/netlify)
|
||||
- [Cloudflare Pages](/pt/self-hosting/cloudflare)
|
||||
- [AWS S3 + CloudFront](/pt/self-hosting/aws)
|
||||
- [Hostinger](/pt/self-hosting/hostinger)
|
||||
- [Nginx](/pt/self-hosting/nginx)
|
||||
- [Apache](/pt/self-hosting/apache)
|
||||
- [Docker](/pt/self-hosting/docker)
|
||||
- [Kubernetes](/pt/self-hosting/kubernetes)
|
||||
- [CORS Proxy](/pt/self-hosting/cors-proxy): necessário para assinaturas digitais
|
||||
|
||||
## Problemas Comuns
|
||||
|
||||
### Word / ODT / Excel / PowerPoint para PDF Trava (SharedArrayBuffer Indisponível)
|
||||
|
||||
**Sintoma**: as conversões de documentos baseadas em LibreOffice (Word, ODT, Excel, PowerPoint para PDF) travam em ~55% ou não iniciam. O console do navegador pode mostrar:
|
||||
|
||||
> `ReferenceError: SharedArrayBuffer is not defined`
|
||||
|
||||
…ou `window.crossOriginIsolated` retorna `false`, ou a compilação do WASM informa `expected magic word 00 61 73 6d, found 1f 8b 08 08`.
|
||||
|
||||
**Causa**: o LibreOffice WASM exige `SharedArrayBuffer`, que o navegador só habilita quando a página está **isolada de origem cruzada (cross-origin isolated)** E é servida a partir de um **contexto seguro**. Isso significa que duas condições precisam ser verdadeiras:
|
||||
|
||||
1. Toda resposta inclui ambos os cabeçalhos:
|
||||
- `Cross-Origin-Embedder-Policy: require-corp`
|
||||
- `Cross-Origin-Opener-Policy: same-origin`
|
||||
2. A página é servida a partir de `https://...` ou `http://localhost`. HTTP simples em um IP da rede local (ex.: `http://192.168.x.x`) NÃO conta como seguro: os navegadores desativam o `SharedArrayBuffer` nesse caso.
|
||||
|
||||
A divergência `00 61 73 6d / 1f 8b 08 08` é um subsintoma separado: os arquivos pré-comprimidos `.wasm.gz` / `.data.gz` estão sem o cabeçalho de resposta `Content-Encoding: gzip`, então o navegador recebe bytes gzip brutos em vez do WASM descompactado.
|
||||
|
||||
**Correção**: veja o guia de implantação da sua plataforma para a configuração exata:
|
||||
|
||||
- [Nginx →](/pt/self-hosting/nginx#conversao-de-word-odt-excel-para-pdf-nao-funciona)
|
||||
- [Apache →](/pt/self-hosting/apache#word-odt-excel-para-pdf-nao-funciona)
|
||||
- [AWS S3 + CloudFront →](/pt/self-hosting/aws#passo-3b-politica-de-cabecalhos-de-resposta-necessaria-para-o-libreoffice-wasm)
|
||||
- [Cloudflare Pages →](/pt/self-hosting/cloudflare#arquivo-de-configuracao) (arquivo `_headers`)
|
||||
- [Netlify →](/pt/self-hosting/netlify#conversao-de-word-odt-excel-para-pdf-travada-em-55) (`netlify.toml`)
|
||||
- [Vercel →](/pt/self-hosting/vercel#conversao-de-word-odt-excel-para-pdf-nao-funciona) (`vercel.json`)
|
||||
- [Hostinger →](/pt/self-hosting/hostinger#ferramentas-do-libreoffice-nao-funcionam) (`.htaccess`)
|
||||
- [Kubernetes →](/pt/self-hosting/kubernetes#garantindo-que-os-cabecalhos-do-sharedarraybuffer-continuem-funcionando-ingress-gateway)
|
||||
- **Docker**: tratado automaticamente pela configuração nginx incluída, sem necessidade de ação.
|
||||
|
||||
**Verificação**: abra o Console do DevTools em qualquer página do BentoPDF e execute:
|
||||
|
||||
```js
|
||||
console.log(window.crossOriginIsolated); // should be true
|
||||
console.log(typeof SharedArrayBuffer); // should be "function"
|
||||
```
|
||||
|
||||
Se a página for HTTPS ou `http://localhost` E ambos os cabeçalhos COEP/COOP estiverem presentes em todas as respostas, as duas verificações passam. Se você estiver em `http://192.168.x.x` ou outra origem HTTP que não seja de loopback, termine a conexão com HTTPS: não existe correção apenas por cabeçalhos.
|
||||
|
||||
### Arquivos `.mjs` Servidos como `application/octet-stream`
|
||||
|
||||
**Sintoma**: Sign PDF / Form Filler / algumas outras ferramentas mostram um visualizador em branco ou não carregam. O console do navegador informa:
|
||||
|
||||
> `Failed to load module script: The server responded with a non-JavaScript MIME type of "application/octet-stream". Strict MIME type checking is enforced for module scripts per HTML spec.`
|
||||
|
||||
**Causa**: seu servidor web ou proxy reverso não tem um mapeamento de tipo MIME para arquivos `.mjs` (o visualizador de PDF incluído traz módulos ES com essa extensão). Muitas configurações padrão de servidor usam `application/octet-stream` para extensões desconhecidas, e os navegadores se recusam a executá-las como módulos ES.
|
||||
|
||||
**Correção**: veja o guia de implantação da sua plataforma para o trecho exato:
|
||||
|
||||
- [Nginx →](/pt/self-hosting/nginx#sign-pdf-ou-form-filler-mostra-um-visualizador-em-branco-erro-de-mime-de-mjs)
|
||||
- [Apache →](/pt/self-hosting/apache#assinar-pdf-ou-preencher-formularios-mostra-um-visualizador-em-branco-erro-de-mime-mjs)
|
||||
- [AWS S3 + CloudFront →](/pt/self-hosting/aws#passo-2-compile-e-envie) (veja `aws s3 cp ... --include "*.mjs"`)
|
||||
- [Kubernetes →](/pt/self-hosting/kubernetes#erros-de-tipo-mime-de-mjs-iframe-em-branco-no-sign-pdf-form-filler)
|
||||
- **Docker, Vercel, Netlify, Cloudflare Pages, Hostinger**: tratados automaticamente, sem necessidade de ação.
|
||||
|
||||
**Verificação**: abra o DevTools → aba Network, encontre a requisição `.mjs` com falha e verifique o cabeçalho de resposta `Content-Type`. Ele deve ser `application/javascript`. Se ainda for `application/octet-stream`, um proxy reverso ou CDN externo pode estar reinterpretando o tipo: verifique cada camada da sua cadeia de entrega.
|
||||
|
||||
## Configuração do WASM (Componentes AGPL)
|
||||
|
||||
O BentoPDF **não inclui** bibliotecas de processamento licenciadas sob AGPL em seu código-fonte, mas **pré-configura URLs de CDN** para que todos os recursos funcionem de imediato, sem configuração manual.
|
||||
|
||||
::: tip Zero Configuração por Padrão
|
||||
A partir da v2.0.0, os módulos WASM vêm pré-configurados para carregar da CDN do jsDelivr por meio de variáveis de ambiente. Todos os recursos avançados funcionam imediatamente, sem nenhuma configuração do usuário.
|
||||
:::
|
||||
|
||||
| Componente | Licença | Recursos |
|
||||
| --------------- | -------- | ------------------------------------------------------------------------ |
|
||||
| **PyMuPDF** | AGPL-3.0 | Conversão de EPUB/MOBI/FB2/XPS, extração de imagens, extração de tabelas |
|
||||
| **Ghostscript** | AGPL-3.0 | Conversão para PDF/A, compressão, correção de inclinação, rasterização |
|
||||
| **CoherentPDF** | AGPL-3.0 | Índice, anexos, mesclagem de PDF com marcadores |
|
||||
|
||||
### Variáveis de Ambiente Padrão
|
||||
|
||||
Estas são definidas em `.env.production` e incorporadas à compilação:
|
||||
|
||||
```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=
|
||||
```
|
||||
|
||||
### Substituindo as URLs do WASM
|
||||
|
||||
Você pode substituir os padrões em tempo de compilação para implantações personalizadas:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Para desativar um módulo por completo (exigindo configuração manual do usuário em Configurações Avançadas), defina sua variável como uma string vazia.
|
||||
|
||||
Para OCR, deixe todas as variáveis `VITE_TESSERACT_*` vazias e mantenha os recursos online padrão, ou defina juntas as URLs de worker/core/lang para OCR auto-hospedado/offline. Se você incluir apenas idiomas de OCR específicos, defina também `VITE_TESSERACT_AVAILABLE_LANGUAGES` com os mesmos códigos separados por vírgula, para que a interface ofereça só os idiomas instalados e seleções não compatíveis falhem com um erro descritivo. Para uma saída de PDF pesquisável totalmente offline, defina também `VITE_OCR_FONT_BASE_URL` com o diretório interno que serve as fontes de OCR incluídas.
|
||||
|
||||
Os usuários também podem substituir esses padrões a qualquer momento em **Configurações Avançadas** na interface: as substituições do usuário armazenadas no navegador têm prioridade sobre os padrões do ambiente.
|
||||
|
||||
### Implantação Air-Gapped / Offline
|
||||
|
||||
Para redes sem acesso à internet (governo, saúde, setor financeiro etc.). As URLs do WASM são incorporadas ao JavaScript em **tempo de compilação**, e os arquivos WASM em si são baixados pelo **navegador do usuário** em tempo de execução. Portanto, você precisa preparar tudo em uma máquina com internet e depois transferir para a rede isolada.
|
||||
|
||||
#### Script Automatizado (Recomendado)
|
||||
|
||||
O script incluído `prepare-airgap.sh` automatiza todo o processo: baixa os pacotes WASM, compila a imagem Docker e produz um pacote autocontido com um script de configuração.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Isso produz um diretório de pacote:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
Transfira o pacote para a rede isolada por USB, repositório interno de artefatos ou outro método aprovado. Em seguida, execute o script de configuração incluído:
|
||||
|
||||
```bash
|
||||
cd bentopdf-airgap-bundle
|
||||
bash setup.sh
|
||||
```
|
||||
|
||||
O script de configuração carrega a imagem Docker, extrai os arquivos WASM e, opcionalmente, inicia o contêiner.
|
||||
|
||||
**Opções do script:**
|
||||
|
||||
| Flag | Descrição | Padrão |
|
||||
| ------------------------------ | ----------------------------------------------------------------- | -------------------------------------- |
|
||||
| `--wasm-base-url <url>` | Onde os WASMs serão hospedados internamente | _(obrigatório, solicitado se ausente)_ |
|
||||
| `--image-name <name>` | Tag da imagem Docker | `bentopdf` |
|
||||
| `--output-dir <path>` | Diretório de saída do pacote | `./bentopdf-airgap-bundle` |
|
||||
| `--simple-mode` | Ativa o Simple Mode | desativado |
|
||||
| `--base-url <path>` | URL base do subdiretório (ex.: `/pdf/`) | `/` |
|
||||
| `--language <code>` | Idioma padrão da interface (ex.: `fr`, `de`) | _(nenhum)_ |
|
||||
| `--brand-name <name>` | Nome de marca personalizado | _(nenhum)_ |
|
||||
| `--brand-logo <path>` | Caminho do logotipo relativo a `public/` | _(nenhum)_ |
|
||||
| `--footer-text <text>` | Texto de rodapé personalizado | _(nenhum)_ |
|
||||
| `--ocr-languages <list>` | Idiomas de OCR a incluir, separados por vírgula | `eng` |
|
||||
| `--list-ocr-languages` | Exibe os códigos e nomes de OCR compatíveis e sai | desativado |
|
||||
| `--search-ocr-language <term>` | Busca códigos de OCR por nome ou abreviação | desativado |
|
||||
| `--dockerfile <path>` | Dockerfile a usar | `Dockerfile` |
|
||||
| `--skip-docker` | Pula a compilação e exportação do Docker | desativado |
|
||||
| `--skip-wasm` | Pula o download do WASM (reutiliza os arquivos `.tgz` existentes) | desativado |
|
||||
|
||||
O prompt interativo também aceita `list` para exibir a lista completa de códigos do Tesseract compatíveis e `search <term>` para encontrar correspondências, como `search german` ou `search chi`.
|
||||
|
||||
::: warning Requisito de Mesma Origem
|
||||
Os arquivos WASM devem ser servidos da **mesma origem** do aplicativo BentoPDF. Os Web Workers usam `importScripts()`, que não consegue carregar scripts de outra origem. Por exemplo, se o BentoPDF roda em `https://internal.example.com`, a URL base do WASM também deve ser `https://internal.example.com/wasm`.
|
||||
:::
|
||||
|
||||
#### Passos Manuais
|
||||
|
||||
<details>
|
||||
<summary>Se você prefere fazer manualmente, sem o script</summary>
|
||||
|
||||
**Passo 1: Baixe os pacotes WASM e de OCR** (em uma máquina com 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
|
||||
```
|
||||
|
||||
**Passo 2: Compile a imagem Docker com as URLs internas**
|
||||
|
||||
```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 .
|
||||
```
|
||||
|
||||
**Passo 3: Exporte a imagem Docker**
|
||||
|
||||
```bash
|
||||
docker save bentopdf -o bentopdf.tar
|
||||
```
|
||||
|
||||
**Passo 4: Transfira para a rede isolada**
|
||||
|
||||
Copie por USB, repositório interno de artefatos ou outro método de transferência aprovado:
|
||||
|
||||
- `bentopdf.tar`: a imagem Docker
|
||||
- Os cinco pacotes `.tgz` de WASM/OCR do Passo 1
|
||||
- O diretório `tesseract-langdata/` do Passo 1
|
||||
- O diretório `ocr-fonts/` do Passo 1
|
||||
|
||||
**Passo 5: Configure dentro da rede isolada**
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Garanta que os arquivos estejam acessíveis nas URLs que você configurou no Passo 2, incluindo `.../ocr/worker.min.js`, `.../ocr/core`, `.../ocr/lang-data` e `.../ocr/fonts`.
|
||||
|
||||
</details>
|
||||
|
||||
::: info Compilando a partir do código-fonte em vez de usar Docker?
|
||||
Defina as variáveis em `.env.production` antes de executar `npm run build`:
|
||||
|
||||
```bash
|
||||
VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/
|
||||
VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/
|
||||
VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/
|
||||
VITE_TESSERACT_WORKER_URL=https://internal-server.example.com/wasm/ocr/worker.min.js
|
||||
VITE_TESSERACT_CORE_URL=https://internal-server.example.com/wasm/ocr/core
|
||||
VITE_TESSERACT_LANG_URL=https://internal-server.example.com/wasm/ocr/lang-data
|
||||
VITE_OCR_FONT_BASE_URL=https://internal-server.example.com/wasm/ocr/fonts
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Hospedando Seu Próprio Proxy WASM
|
||||
|
||||
Se você precisa servir arquivos WASM AGPL com cabeçalhos CORS adequados, pode implantar um proxy simples. Veja o [guia do Cloudflare WASM Proxy](https://github.com/alam00000/bentopdf/blob/main/cloudflare/WASM-PROXY.md) para um exemplo de implementação.
|
||||
|
||||
::: tip Por Que Separar?
|
||||
Essa separação garante:
|
||||
|
||||
- Conformidade legal clara para usuários comerciais
|
||||
- O núcleo do BentoPDF permanece sob sua licença dupla (AGPL-3.0 / Commercial)
|
||||
- Os arquivos WASM são carregados em tempo de execução, e não incluídos no código-fonte
|
||||
:::
|
||||
|
||||
## Requisitos do Sistema
|
||||
|
||||
| Requisito | Mínimo |
|
||||
| ------------- | --------------------------------- |
|
||||
| Armazenamento | ~100 MB (núcleo sem módulos AGPL) |
|
||||
| RAM | 512 MB |
|
||||
| CPU | Qualquer processador moderno |
|
||||
|
||||
::: tip
|
||||
O BentoPDF é um site estático: não é necessário banco de dados nem servidor de back-end!
|
||||
:::
|
||||
@@ -0,0 +1,211 @@
|
||||
# Implantar com Kubernetes
|
||||
|
||||
O Kubernetes pode ser exagero para um site estático, mas pode ser uma ótima opção se você já padroniza em Helm + GitOps.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Cabeçalhos Necessários para a Conversão de Arquivos do Office**
|
||||
>
|
||||
> As ferramentas baseadas em LibreOffice (conversão de Word, Excel, PowerPoint) exigem estes cabeçalhos HTTP para o suporte a `SharedArrayBuffer`:
|
||||
>
|
||||
> - `Cross-Origin-Opener-Policy: same-origin`
|
||||
> - `Cross-Origin-Embedder-Policy: require-corp`
|
||||
>
|
||||
> As imagens nginx oficiais do BentoPDF incluem esses cabeçalhos. No Kubernetes, **os controladores de Ingress/Gateway também são proxies reversos**, então garanta que esses cabeçalhos sejam preservados (ou adicione-os na borda).
|
||||
|
||||
## Pré-requisitos
|
||||
|
||||
- Cluster Kubernetes
|
||||
- Helm v3
|
||||
- Uma imagem nginx do BentoPDF (ex.: `ghcr.io/alam00000/bentopdf:<tag>`) que atenda na **porta 8080**
|
||||
|
||||
## Implantar com Helm
|
||||
|
||||
### Instalar a partir deste repositório (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
|
||||
```
|
||||
|
||||
### Instalar a partir do GHCR (chart OCI)
|
||||
|
||||
Se o chart for publicado no GHCR como um artefato 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
|
||||
```
|
||||
|
||||
## Exponha o Serviço
|
||||
|
||||
### Port-forward (teste rápido)
|
||||
|
||||
```bash
|
||||
kubectl -n bentopdf port-forward deploy/bentopdf 8080:8080
|
||||
```
|
||||
|
||||
### Ingress (opcional)
|
||||
|
||||
Ative o Ingress (exemplo para nginx-ingress):
|
||||
|
||||
```yaml
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
hosts:
|
||||
- host: pdf.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
```
|
||||
|
||||
### Gateway API (opcional)
|
||||
|
||||
Este chart oferece suporte a `Gateway` + `HTTPRoute` da Gateway API.
|
||||
|
||||
Exemplo (operador Cloudflare Gateway API):
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Garantindo que os cabeçalhos do SharedArrayBuffer continuem funcionando (Ingress/Gateway)
|
||||
|
||||
### O que "deveria" acontecer
|
||||
|
||||
A configuração do nginx do BentoPDF define os cabeçalhos de resposta necessários. A maioria dos controladores de Ingress/Gateway **repassa os cabeçalhos de resposta do upstream sem alterá-los**.
|
||||
|
||||
### O que pode quebrar isso
|
||||
|
||||
- Uma política do controlador/borda que **sobrescreve** ou **remove** cabeçalhos de resposta
|
||||
- Um middleware de "cabeçalhos de segurança" que define valores diferentes de COOP/COEP
|
||||
|
||||
### Como verificar
|
||||
|
||||
Execute isto no seu endpoint público:
|
||||
|
||||
```bash
|
||||
curl -I https://pdf.example.com/ | egrep -i 'cross-origin-opener-policy|cross-origin-embedder-policy'
|
||||
```
|
||||
|
||||
Você deve ver:
|
||||
|
||||
- `Cross-Origin-Opener-Policy: same-origin`
|
||||
- `Cross-Origin-Embedder-Policy: require-corp`
|
||||
|
||||
### Se o seu controlador de Ingress não os preservar
|
||||
|
||||
Adicione os cabeçalhos na borda (específico de cada controlador). Exemplo para o **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;
|
||||
```
|
||||
|
||||
## Erros de tipo MIME de `.mjs` (iframe em branco no Sign PDF / Form Filler)
|
||||
|
||||
Se o console do navegador mostrar `Failed to load module script: ... non-JavaScript MIME type "application/octet-stream"` em uma requisição `.mjs`, um controlador de Ingress/Gateway na frente do nginx do BentoPDF está substituindo o cabeçalho `Content-Type` do upstream por `application/octet-stream`.
|
||||
|
||||
O nginx da imagem do BentoPDF já serve `.mjs` como `application/javascript`. A correção é impedir que o controlador o remova ou substitua. Para o nginx-ingress:
|
||||
|
||||
```yaml
|
||||
ingress:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
location ~* \.mjs$ {
|
||||
types {} default_type application/javascript;
|
||||
}
|
||||
```
|
||||
|
||||
Ou use um filtro `ResponseHeaderModifier` na Gateway API para definir `Content-Type: application/javascript` nos caminhos `.mjs`. Verifique no DevTools → aba Network: a requisição `.mjs` que falhou agora deve mostrar `content-type: application/javascript`.
|
||||
|
||||
### Se você usa a Gateway API e quer forçar a adição de cabeçalhos
|
||||
|
||||
A Gateway API oferece suporte a um filtro `ResponseHeaderModifier`. Você pode anexá-lo em `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
|
||||
```
|
||||
|
||||
O suporte a filtros específicos depende do seu controlador Gateway; se um filtro for ignorado, adicione os cabeçalhos na camada de borda/controlador.
|
||||
|
||||
## Desativando Ferramentas Específicas
|
||||
|
||||
Use um ConfigMap para desativar ferramentas em tempo de execução, sem recompilar a imagem:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: bentopdf-config
|
||||
namespace: bentopdf
|
||||
data:
|
||||
config.json: |
|
||||
{
|
||||
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
|
||||
}
|
||||
```
|
||||
|
||||
Monte-o no diretório servido:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Os IDs das ferramentas são a URL da página sem `.html`: abra qualquer ferramenta e veja a URL (ex.: `edit-pdf`, `merge-pdf`, `compress-pdf`). As ferramentas desativadas ficam ocultas na página inicial, na busca, nos atalhos, no construtor de fluxos e no acesso direto por URL. Veja o [guia do Docker](/pt/self-hosting/docker#desativando-ferramentas-especificas) para a lista completa de opções.
|
||||
@@ -0,0 +1,139 @@
|
||||
# Implantar no Netlify
|
||||
|
||||
O [Netlify](https://netlify.com) oferece uma excelente hospedagem de sites estáticos, com um plano gratuito generoso.
|
||||
|
||||
## Implantação com Um Clique
|
||||
|
||||
[](https://app.netlify.com/start/deploy?repository=https://github.com/alam00000/bentopdf)
|
||||
|
||||
## Implantação Manual
|
||||
|
||||
### Passo 1: Conecte o Repositório
|
||||
|
||||
1. Faça login no [Netlify](https://app.netlify.com)
|
||||
2. Clique em "Add new site" → "Import an existing project"
|
||||
3. Conecte sua conta do GitHub
|
||||
4. Selecione seu fork do BentoPDF
|
||||
|
||||
### Passo 2: Configure as Opções de Compilação
|
||||
|
||||
| Configuração | Valor |
|
||||
| ----------------- | --------------- |
|
||||
| Build command | `npm run build` |
|
||||
| Publish directory | `dist` |
|
||||
| Node version | 18+ |
|
||||
|
||||
### Passo 3: Implante
|
||||
|
||||
Clique em "Deploy site" e aguarde a compilação.
|
||||
|
||||
## Arquivo de Configuração
|
||||
|
||||
Crie o `netlify.toml` na raiz do seu projeto:
|
||||
|
||||
```toml
|
||||
[build]
|
||||
command = "npm run build"
|
||||
publish = "dist"
|
||||
|
||||
[build.environment]
|
||||
NODE_VERSION = "18"
|
||||
|
||||
# Required security headers for SharedArrayBuffer (used by LibreOffice WASM)
|
||||
[[headers]]
|
||||
for = "/*"
|
||||
[headers.values]
|
||||
Cross-Origin-Embedder-Policy = "require-corp"
|
||||
Cross-Origin-Opener-Policy = "same-origin"
|
||||
Cross-Origin-Resource-Policy = "cross-origin"
|
||||
|
||||
# Pre-compressed LibreOffice WASM binary - must be served with Content-Encoding
|
||||
[[headers]]
|
||||
for = "/libreoffice-wasm/soffice.wasm.gz"
|
||||
[headers.values]
|
||||
Content-Type = "application/wasm"
|
||||
Content-Encoding = "gzip"
|
||||
Cache-Control = "public, max-age=31536000, immutable"
|
||||
|
||||
# Pre-compressed LibreOffice WASM data - must be served with Content-Encoding
|
||||
[[headers]]
|
||||
for = "/libreoffice-wasm/soffice.data.gz"
|
||||
[headers.values]
|
||||
Content-Type = "application/octet-stream"
|
||||
Content-Encoding = "gzip"
|
||||
Cache-Control = "public, max-age=31536000, immutable"
|
||||
|
||||
# Cache other WASM files
|
||||
[[headers]]
|
||||
for = "*.wasm"
|
||||
[headers.values]
|
||||
Cache-Control = "public, max-age=31536000, immutable"
|
||||
Content-Type = "application/wasm"
|
||||
|
||||
# SPA routing
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
status = 200
|
||||
```
|
||||
|
||||
::: warning Importante
|
||||
Os cabeçalhos `Cross-Origin-Embedder-Policy` e `Cross-Origin-Opener-Policy` são necessários para as conversões de Word/ODT/Excel/PowerPoint para PDF. Sem eles, o `SharedArrayBuffer` fica indisponível e o mecanismo LibreOffice WASM não consegue inicializar.
|
||||
|
||||
Os cabeçalhos `Content-Encoding: gzip` nos arquivos `.wasm.gz` e `.data.gz` instruem o navegador a descompactá-los automaticamente. Sem eles, o navegador recebe bytes gzip brutos e a compilação do WASM falha.
|
||||
:::
|
||||
|
||||
## Variáveis de Ambiente
|
||||
|
||||
Defina estas em Site settings → Environment variables:
|
||||
|
||||
| Variável | Descrição |
|
||||
| ----------------------- | -------------------------------------------------------------------- |
|
||||
| `SIMPLE_MODE` | Defina como `true` para uma compilação mínima |
|
||||
| `VITE_BRAND_NAME` | Nome de marca personalizado (substitui "BentoPDF") |
|
||||
| `VITE_BRAND_LOGO` | Caminho do logotipo relativo a `public/` (ex.: `images/my-logo.svg`) |
|
||||
| `VITE_FOOTER_TEXT` | Texto personalizado de rodapé/direitos autorais |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Idioma padrão da interface (ex.: `fr`, `de`, `es`) |
|
||||
|
||||
## Domínio Personalizado
|
||||
|
||||
1. Acesse Site settings → Domain management
|
||||
2. Clique em "Add custom domain"
|
||||
3. Siga as instruções de configuração de DNS
|
||||
|
||||
## Large Media
|
||||
|
||||
Para arquivos WASM grandes, considere ativar o [Netlify Large Media](https://docs.netlify.com/large-media/overview/):
|
||||
|
||||
```bash
|
||||
netlify lm:setup
|
||||
git lfs track "*.wasm"
|
||||
```
|
||||
|
||||
## Solução de Problemas
|
||||
|
||||
### Conversão de Word/ODT/Excel para PDF Travada em 55%
|
||||
|
||||
Se as conversões de documentos travarem em 55%, abra o Console do DevTools e verifique:
|
||||
|
||||
```js
|
||||
console.log(window.crossOriginIsolated); // should be true
|
||||
console.log(typeof SharedArrayBuffer); // should be "function"
|
||||
```
|
||||
|
||||
Se `crossOriginIsolated` for `false`, os cabeçalhos COEP/COOP do seu `netlify.toml` não estão sendo aplicados. Confirme que o arquivo está na raiz do projeto e implante novamente.
|
||||
|
||||
Se você vir `expected magic word 00 61 73 6d, found 1f 8b 08 08` no console, os arquivos `.wasm.gz` estão sem os cabeçalhos `Content-Encoding: gzip`. Confirme que os blocos `[[headers]]` de `soffice.wasm.gz` e `soffice.data.gz` estão no seu `netlify.toml`.
|
||||
|
||||
### A Compilação Falha
|
||||
|
||||
Verifique a compatibilidade da versão do Node:
|
||||
|
||||
```toml
|
||||
[build.environment]
|
||||
NODE_VERSION = "20"
|
||||
```
|
||||
|
||||
### Carregamento Inicial Lento
|
||||
|
||||
Ative a otimização de ativos em Site settings → Build & deploy → Asset optimization.
|
||||
@@ -0,0 +1,232 @@
|
||||
# Implantar com Nginx
|
||||
|
||||
Hospede o BentoPDF no seu próprio servidor usando o Nginx.
|
||||
|
||||
## Pré-requisitos
|
||||
|
||||
- Servidor Ubuntu/Debian
|
||||
- Nginx instalado
|
||||
- Certificado SSL (recomendado: Let's Encrypt)
|
||||
|
||||
## Passo 1: Compile o Projeto
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
Para personalizar a marca, defina variáveis de ambiente antes de compilar:
|
||||
|
||||
```bash
|
||||
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
|
||||
```
|
||||
|
||||
## Passo 2: Copie os Arquivos
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/www/bentopdf
|
||||
sudo cp -r dist/* /var/www/bentopdf/
|
||||
sudo chown -R www-data:www-data /var/www/bentopdf
|
||||
```
|
||||
|
||||
## Passo 3: Configuração do Nginx
|
||||
|
||||
Crie o arquivo `/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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Passo 4: Ative o Site
|
||||
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
## Passo 5: SSL com Let's Encrypt
|
||||
|
||||
```bash
|
||||
sudo apt install certbot python3-certbot-nginx
|
||||
sudo certbot --nginx -d your-domain.com
|
||||
```
|
||||
|
||||
## Implantação em Subdiretório
|
||||
|
||||
Para hospedar em `/pdf/`:
|
||||
|
||||
```nginx
|
||||
location /pdf/ {
|
||||
alias /var/www/bentopdf/;
|
||||
try_files $uri $uri/ /pdf/index.html;
|
||||
}
|
||||
```
|
||||
|
||||
Compile com:
|
||||
|
||||
```bash
|
||||
BASE_URL=/pdf/ npm run build
|
||||
```
|
||||
|
||||
## Ajuste de Desempenho
|
||||
|
||||
Adicione ao `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;
|
||||
}
|
||||
```
|
||||
|
||||
## Solução de Problemas
|
||||
|
||||
### O WASM Não Carrega
|
||||
|
||||
Confirme que o tipo MIME está definido:
|
||||
|
||||
```nginx
|
||||
types {
|
||||
application/wasm wasm;
|
||||
}
|
||||
```
|
||||
|
||||
### Sign PDF ou Form Filler Mostra um Visualizador em Branco (erro de MIME de `.mjs`)
|
||||
|
||||
Se o console do navegador mostrar:
|
||||
|
||||
```
|
||||
Failed to load module script: The server responded with a non-JavaScript
|
||||
MIME type of "application/octet-stream". Strict MIME type checking is
|
||||
enforced for module scripts per HTML spec.
|
||||
```
|
||||
|
||||
…e a requisição com falha terminar em `.mjs`, falta ao seu nginx o mapeamento `.mjs → application/javascript`. O visualizador de PDF incluído (usado por Sign PDF, Form Filler e algumas outras ferramentas) traz arquivos de módulo ES com extensão `.mjs`, e o `mime.types` padrão do nginx não os inclui por padrão.
|
||||
|
||||
Corrija adicionando `mjs` à linha `application/javascript` existente em `/etc/nginx/mime.types`:
|
||||
|
||||
```nginx
|
||||
application/javascript js mjs;
|
||||
```
|
||||
|
||||
> NÃO acrescente uma _segunda_ linha `application/javascript`. O nginx lê o `mime.types` linha por linha e só um mapeamento de tipo para extensão vale, então uma segunda linha é ignorada silenciosamente em algumas versões. Edite a linha existente para adicionar `mjs` junto com `js`.
|
||||
|
||||
Ou sobrescreva localmente dentro do seu bloco server (sem editar o arquivo global):
|
||||
|
||||
```nginx
|
||||
types {
|
||||
application/javascript js mjs;
|
||||
application/wasm wasm;
|
||||
}
|
||||
```
|
||||
|
||||
Recarregue o nginx após a alteração:
|
||||
|
||||
```bash
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
Para confirmar: no DevTools do navegador → aba Network, encontre a requisição `.mjs` com falha e verifique o cabeçalho de resposta `Content-Type`. Agora ele deve ser `application/javascript`, e não `application/octet-stream`.
|
||||
|
||||
### Conversão de Word/ODT/Excel para PDF Não Funciona
|
||||
|
||||
O LibreOffice WASM exige `SharedArrayBuffer`, que precisa dos cabeçalhos `Cross-Origin-Embedder-Policy` e `Cross-Origin-Opener-Policy`. Ele também precisa de um contexto seguro, então `http://localhost` funciona para testes locais, mas `http://192.168.x.x` ou outros IPs de rede local geralmente exigem HTTPS. Observe que as diretivas `add_header` de um bloco `location` **substituem** as diretivas `add_header` do nível do servidor, e não se combinam. Todo bloco `location` com seu próprio `add_header` deve incluir os cabeçalhos COEP/COOP.
|
||||
|
||||
Verifique com:
|
||||
|
||||
```bash
|
||||
curl -I https://your-domain.com/ | grep -i cross-origin
|
||||
```
|
||||
|
||||
Se você usa um proxy reverso na frente do nginx, garanta que ele preserve esses cabeçalhos.
|
||||
|
||||
### 502 Bad Gateway
|
||||
|
||||
Verifique os logs de erro do Nginx:
|
||||
|
||||
```bash
|
||||
sudo tail -f /var/log/nginx/error.log
|
||||
```
|
||||
@@ -0,0 +1,122 @@
|
||||
# Implantar no Vercel
|
||||
|
||||
O [Vercel](https://vercel.com) oferece a experiência de implantação mais simples para sites estáticos.
|
||||
|
||||
## Implantação com Um Clique
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https://github.com/alam00000/bentopdf)
|
||||
|
||||
## Implantação Manual
|
||||
|
||||
### Passo 1: Faça um Fork do Repositório
|
||||
|
||||
Faça um fork de [bentopdf/bentopdf](https://github.com/alam00000/bentopdf) para sua conta do GitHub.
|
||||
|
||||
### Passo 2: Importe no Vercel
|
||||
|
||||
1. Acesse [vercel.com/new](https://vercel.com/new)
|
||||
2. Selecione seu repositório com o fork
|
||||
3. Configure o projeto:
|
||||
|
||||
| Configuração | Valor |
|
||||
| ---------------- | --------------- |
|
||||
| Framework Preset | Vite |
|
||||
| Build Command | `npm run build` |
|
||||
| Output Directory | `dist` |
|
||||
| Install Command | `npm install` |
|
||||
|
||||
### Passo 3: Variáveis de Ambiente (Opcional)
|
||||
|
||||
Adicione estas se necessário:
|
||||
|
||||
| Variável | Descrição |
|
||||
| ----------------------- | -------------------------------------------------------------------- |
|
||||
| `SIMPLE_MODE` | Defina como `true` para uma interface mínima |
|
||||
| `VITE_BRAND_NAME` | Nome de marca personalizado (substitui "BentoPDF") |
|
||||
| `VITE_BRAND_LOGO` | Caminho do logotipo relativo a `public/` (ex.: `images/my-logo.svg`) |
|
||||
| `VITE_FOOTER_TEXT` | Texto personalizado de rodapé/direitos autorais |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Idioma padrão da interface (ex.: `fr`, `de`, `es`) |
|
||||
|
||||
### Passo 4: Implante
|
||||
|
||||
Clique em "Deploy" e aguarde a conclusão da compilação.
|
||||
|
||||
## Domínio Personalizado
|
||||
|
||||
1. Acesse as configurações do seu projeto
|
||||
2. Navegue até "Domains"
|
||||
3. Adicione seu domínio personalizado
|
||||
4. Configure o DNS conforme as instruções
|
||||
|
||||
## Limitações
|
||||
|
||||
::: warning Arquivos Grandes
|
||||
As funções serverless do Vercel têm um limite de 50MB. Como o BentoPDF é um site estático, isso não deve afetar você, mas os módulos WASM são grandes (~100MB no total). Garanta que eles sejam servidos a partir da pasta `/public`.
|
||||
:::
|
||||
|
||||
## Solução de Problemas
|
||||
|
||||
### Tempo Limite da Compilação
|
||||
|
||||
Se a compilação exceder o tempo limite, tente:
|
||||
|
||||
```json
|
||||
// vercel.json
|
||||
{
|
||||
"buildCommand": "npm run build",
|
||||
"outputDirectory": "dist"
|
||||
}
|
||||
```
|
||||
|
||||
### 404 ao Atualizar a Página
|
||||
|
||||
Adicione um `vercel.json` para o roteamento de SPA:
|
||||
|
||||
```json
|
||||
{
|
||||
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
|
||||
}
|
||||
```
|
||||
|
||||
### Conversão de Word/ODT/Excel para PDF Não Funciona
|
||||
|
||||
As conversões do LibreOffice WASM exigem `SharedArrayBuffer`, que precisa destes cabeçalhos de resposta em todas as páginas:
|
||||
|
||||
- `Cross-Origin-Embedder-Policy: require-corp`
|
||||
- `Cross-Origin-Opener-Policy: same-origin`
|
||||
|
||||
Os arquivos pré-comprimidos `.wasm.gz` e `.data.gz` também precisam de `Content-Encoding: gzip` para que o navegador os descompacte.
|
||||
|
||||
Adicione isto ao seu `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" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Para verificar, abra o Console do DevTools e execute `console.log(window.crossOriginIsolated)`. Deve retornar `true`.
|
||||
Reference in new issue
Block a user