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

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

No files matched your search

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