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 @@
# Deploy dengan Apache
Hosting BentoPDF menggunakan Apache HTTP Server.
## Prasyarat
- Apache 2.4+
- mod_rewrite diaktifkan
- Sertifikat SSL (disarankan)
## Langkah 1: Build Proyek
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
Untuk menyesuaikan branding, atur environment variable sebelum build:
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## Langkah 2: Salin File
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## Langkah 3: Konfigurasi Apache
Buat `/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>
```
## Langkah 4: .htaccess untuk Routing
Buat `/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
```
## Langkah 5: Aktifkan Modul yang Diperlukan
```bash
sudo a2enmod rewrite
sudo a2enmod ssl
sudo a2enmod headers
sudo a2enmod expires
sudo a2enmod deflate
```
## Langkah 6: Aktifkan Situs
```bash
sudo a2ensite bentopdf.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
```
## Deploy di Subdirektori
Untuk hosting di `/pdf/`:
1. Build dengan base URL:
```bash
BASE_URL=/pdf/ npm run build
```
2. Perbarui `.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
```
## Pemecahan Masalah
### Error WASM 404
Pastikan MIME type sudah dikonfigurasi:
```apache
AddType application/wasm .wasm
```
### Tanda Tangani PDF atau Form Filler Menampilkan Penampil Kosong (error MIME `.mjs`)
Jika konsol browser menampilkan:
```
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.
```
dan permintaan yang gagal berakhir dengan `.mjs`, konfigurasi Apache Anda tidak memiliki pemetaan `.mjs → application/javascript`. Viewer PDF bawaan menyertakan file ES module dengan ekstensi `.mjs`, dan `mime.types` bawaan Apache tidak memuatnya secara default.
Tambahkan ke VirtualHost atau `.htaccess` Anda:
```apache
AddType application/javascript .mjs
```
Lalu muat ulang Apache:
```bash
sudo apache2ctl configtest && sudo systemctl reload apache2
```
Untuk memastikan: di DevTools browser → tab Network, temukan permintaan `.mjs` yang gagal dan periksa header respons `Content-Type`. Nilainya sekarang harus `application/javascript`, bukan `application/octet-stream`.
### Rewrite Tidak Berfungsi
Periksa bahwa mod_rewrite sudah diaktifkan:
```bash
sudo a2enmod rewrite
```
### Word/ODT/Excel ke PDF Tidak Berfungsi
LibreOffice WASM memerlukan `SharedArrayBuffer`, yang membutuhkan header berikut:
```apache
Header always set Cross-Origin-Embedder-Policy "require-corp"
Header always set Cross-Origin-Opener-Policy "same-origin"
```
Ini juga memerlukan konteks aman (secure context). `http://localhost` berfungsi untuk pengujian lokal, tetapi `http://192.168.x.x` atau IP LAN lainnya biasanya memerlukan HTTPS. Jika header sudah ada tetapi `window.crossOriginIsolated` masih `false`, periksa apakah halaman dibuka melalui HTTP biasa pada origin non-loopback.
File `.wasm.gz` dan `.data.gz` yang sudah terkompresi juga memerlukan `Content-Encoding` yang benar:
```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>
```
Pastikan `mod_headers` sudah diaktifkan: `sudo a2enmod headers`
### Izin Ditolak
```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 @@
# Deploy ke AWS S3 + CloudFront
Hosting BentoPDF di AWS untuk kontrol dan skalabilitas maksimum.
## Arsitektur
```
User → CloudFront (CDN) → S3 (Static Files)
```
## Langkah 1: Buat 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
```
## Langkah 2: Build dan Unggah
```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
```
## Langkah 3: Buat Distribusi CloudFront
```bash
aws cloudfront create-distribution \
--origin-domain-name your-bentopdf-bucket.s3.amazonaws.com \
--default-root-object index.html
```
Atau gunakan AWS Console:
1. Buka CloudFront → Create distribution
2. Origin domain: pilih bucket S3 Anda
3. Aktifkan "Origin Access Control"
4. Default root object: `index.html`
5. Buat distribusi
## Langkah 3b: Response Headers Policy (Diperlukan untuk LibreOffice WASM)
Konversi berbasis LibreOffice (Word, Excel, PowerPoint ke PDF) memerlukan `SharedArrayBuffer`, yang membutuhkan header respons tertentu. Buat CloudFront Response Headers Policy:
1. Buka CloudFront → Policies → Response headers
2. Buat kebijakan kustom dengan header berikut:
| Header | Nilai |
| ------------------------------ | -------------- |
| `Cross-Origin-Embedder-Policy` | `require-corp` |
| `Cross-Origin-Opener-Policy` | `same-origin` |
| `Cross-Origin-Resource-Policy` | `cross-origin` |
3. Lampirkan kebijakan ke default cache behavior distribusi Anda
Atau melalui 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}
]
}
}'
```
## Langkah 3c: Metadata S3 untuk File WASM yang Sudah Terkompresi
File LibreOffice WASM sudah terkompresi (`.wasm.gz`, `.data.gz`). Atur Content-Type dan Content-Encoding yang benar agar browser dapat mendekompresinya:
```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 Penting
Tanpa response headers policy, `SharedArrayBuffer` tidak tersedia dan konversi LibreOffice WASM akan macet di sekitar 55%. Tanpa Content-Encoding yang benar pada file `.gz`, browser menerima byte gzip mentah dan kompilasi WASM gagal.
:::
## Langkah 4: Bucket Policy S3
Izinkan CloudFront mengakses 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"
}
}
}
]
}
```
## Langkah 5: Halaman Error Kustom
Konfigurasikan 404 agar mengembalikan `index.html` untuk routing SPA:
1. CloudFront → Error pages
2. Buat custom error response:
- HTTP error code: 404
- Response page path: `/index.html`
- HTTP response code: 200
## Perkiraan Biaya
| Sumber Daya | Perkiraan Biaya |
| -------------------------- | --------------- |
| S3 Storage (~500MB) | ~$0.01/bulan |
| CloudFront (1TB transfer) | ~$85/bulan |
| CloudFront (10GB transfer) | ~$0.85/bulan |
::: tip
Gunakan S3 Intelligent Tiering untuk mengoptimalkan biaya pada file yang jarang diakses.
:::
## Otomatisasi dengan 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 @@
# Deploy ke Cloudflare Pages
[Cloudflare Pages](https://pages.cloudflare.com) menawarkan hosting situs statis global yang cepat dengan bandwidth tak terbatas.
## Deploy Cepat
1. Buka [Cloudflare Pages](https://dash.cloudflare.com/?to=/:account/pages)
2. Klik "Create a project"
3. Hubungkan repositori GitHub Anda
## Konfigurasi Build
| Pengaturan | Nilai |
| ---------------------- | --------------- |
| Framework preset | Tidak ada |
| Build command | `npm run build` |
| Build output directory | `dist` |
| Root directory | `/` |
## Environment Variable
Tambahkan ini di Settings → Environment variables:
| Variabel | Nilai |
| ----------------------- | ----------------------------------------------- |
| `NODE_VERSION` | `18` |
| `SIMPLE_MODE` | `false` (opsional) |
| `VITE_BRAND_NAME` | Nama merek kustom (opsional) |
| `VITE_BRAND_LOGO` | Path logo relatif terhadap `public/` (opsional) |
| `VITE_FOOTER_TEXT` | Teks footer/hak cipta kustom (opsional) |
| `VITE_DEFAULT_LANGUAGE` | Bahasa UI default, mis. `fr` (opsional) |
## File Konfigurasi
Buat `_headers` di folder `public` Anda:
```
# 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 Penting
Header `Cross-Origin-Embedder-Policy` dan `Cross-Origin-Opener-Policy` diperlukan untuk konversi Word/ODT/Excel/PowerPoint ke PDF. Tanpa keduanya, `SharedArrayBuffer` tidak tersedia dan mesin LibreOffice WASM gagal diinisialisasi.
:::
Buat `_redirects` untuk routing SPA:
```
/* /index.html 200
```
## Domain Kustom
1. Buka proyek Pages Anda
2. Klik "Custom domains"
3. Tambahkan domain Anda
4. Cloudflare akan mengonfigurasi DNS secara otomatis jika domain berada di Cloudflare
## Keunggulan
- **Bandwidth tak terbatas gratis**
- **CDN global** dengan 300+ lokasi edge
- **HTTPS otomatis**
- **Deployment pratinjau** untuk pull request
- **Build cepat**
## Pemecahan Masalah
### Unggahan File Besar
Cloudflare Pages mendukung file hingga 25 MB. Modul WASM seharusnya aman, tetapi jika Anda mencapai batas, pertimbangkan:
```bash
# Split large files during build
npm run build
```
### Batas Ukuran Worker
Jika menggunakan Cloudflare Workers untuk routing lanjutan, perhatikan batas 1 MB untuk paket gratis.
## Worker CORS Proxy (Untuk Tanda Tangan Digital)
Alat Tanda Tangan Digital memerlukan CORS proxy untuk mengambil rantai sertifikat. Deploy worker yang disertakan:
```bash
cd cloudflare
npx wrangler login
npx wrangler deploy
```
### Fitur Keamanan
| Fitur | Deskripsi |
| ------------------------- | ----------------------------------- |
| **Pembatasan URL** | Hanya URL sertifikat yang diizinkan |
| **Batas Ukuran File** | Maks 10MB per permintaan |
| **Pembatasan Laju** | 60 req/IP/menit (memerlukan KV) |
| **Pemblokiran IP Privat** | Memblokir localhost, IP internal |
### Aktifkan Pembatasan Laju
```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
```
### Build dengan URL Proxy
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
Atau dengan 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 .
```
> **Catatan:** Lihat [README](https://github.com/alam00000/bentopdf#digital-signature-cors-proxy-required) untuk penyiapan tanda tangan HMAC.
+180
View File
@@ -0,0 +1,180 @@
# CORS Proxy untuk Pengambilan Sertifikat
Alat tanda tangan digital menggunakan CORS proxy untuk mengambil sertifikat penerbit dari Certificate Authority (CA) eksternal. Ini diperlukan karena banyak server CA tidak menyertakan header CORS dalam responsnya, sehingga pengambilan langsung dari browser tidak dapat dilakukan.
Selain itu, banyak server CA menyajikan sertifikat melalui HTTP biasa. Saat instans BentoPDF Anda dihosting melalui HTTPS, browser memblokir permintaan HTTP ini (kebijakan mixed content). CORS proxy mengatasi kedua masalah tersebut dengan mengarahkan permintaan melalui endpoint HTTPS dengan header yang tepat.
## Cara Kerjanya
Saat menandatangani PDF dengan sertifikat:
1. Pustaka `zgapdfsigner` mencoba membangun rantai sertifikat yang lengkap
2. Pustaka ini mengambil sertifikat penerbit dari URL yang tertanam di ekstensi AIA (Authority Information Access) sertifikat Anda
3. Permintaan ini diarahkan melalui CORS proxy yang menambahkan header `Access-Control-Allow-Origin` yang diperlukan
4. Proxy mengembalikan data sertifikat ke browser
## Hosting Mandiri CORS Proxy
Jika Anda menghosting BentoPDF sendiri, Anda perlu men-deploy CORS proxy sendiri agar tanda tangan digital dapat bekerja dengan sertifikat yang memerlukan pengambilan rantai.
### Opsi 1: Cloudflare Workers (Disarankan)
1. **Instal Wrangler CLI**:
```bash
npm install -g wrangler
```
2. **Login ke Cloudflare**:
```bash
wrangler login
```
3. **Clone BentoPDF dan perbarui origin yang diizinkan**:
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf/cloudflare
```
Buka `cors-proxy-worker.js` dan ubah array `ALLOWED_ORIGINS` ke domain Anda:
```js
const ALLOWED_ORIGINS = [
'https://your-domain.com',
'https://www.your-domain.com',
];
```
::: warning Penting
Tanpa perubahan ini, proxy akan menolak semua permintaan dari situs Anda dengan error **403 Forbidden**. Secara default hanya permintaan dari `bentopdf.com` yang diizinkan.
:::
4. **Deploy proxy**:
```bash
wrangler deploy
```
Catat URL worker Anda (mis., `https://bentopdf-cors-proxy.your-subdomain.workers.dev`).
5. **Build ulang BentoPDF dengan URL proxy**:
Jika menggunakan 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 .
```
Jika build dari sumber:
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
### Opsi 2: Proxy Backend Kustom
Anda juga dapat membuat endpoint proxy sendiri. Persyaratannya:
1. Menerima permintaan GET dengan parameter query `url`
2. Mengambil URL tersebut dari server Anda (tanpa pembatasan CORS di sisi server)
3. Mengembalikan respons dengan header berikut:
- `Access-Control-Allow-Origin: https://your-domain.com`
- `Access-Control-Allow-Methods: GET, OPTIONS`
- `X-Content-Type-Options: nosniff`
Contoh implementasi 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' });
}
});
```
## Pertimbangan Keamanan
Cloudflare Worker yang disertakan memiliki beberapa langkah keamanan:
| Fitur | Deskripsi |
| ------------------------- | ------------------------------------------------------------------------------------------------ |
| **Validasi Origin** | Hanya mengizinkan permintaan dari domain yang tercantum di `ALLOWED_ORIGINS` |
| **Pembatasan URL** | Hanya mengizinkan URL sertifikat (`.crt`, `.cer`, `.pem`, `/certs/`, `/ocsp`, `/crl`) |
| **Pemblokiran IP Privat** | Memblokir rentang privat IPv4/IPv6, link-local, loopback, IP desimal, dan metadata cloud |
| **Keamanan Content-Type** | Hanya mengembalikan MIME type sertifikat yang aman, memblokir injeksi content-type dari upstream |
| **Batas Ukuran File** | Men-stream respons dengan batas 10MB, menghentikan unduhan jika terlampaui |
| **Pembatasan Laju** | 60 permintaan per menit per IP atau IPv6 /64 (memerlukan KV, tetap lolos saat KV error) |
| **Tanda Tangan HMAC** | Penandatanganan sisi klien opsional (mencegah penyalahgunaan biasa) |
### DNS rebinding (hosting mandiri di luar Cloudflare)
Worker menolak setiap URL yang me-resolve ke IP privat atau internal. Worker tidak dapat memakai allowlist host tetap, karena URL rantai sertifikat menunjuk ke CA mana pun yang menerbitkan sertifikat (FNMT, CA korporat, dan seterusnya), sehingga tujuannya tidak diketahui sebelumnya. Hal ini menyisakan celah rebinding kecil: seseorang yang menjalankan DNS untuk domainnya sendiri dapat memberikan IP publik pada pemeriksaan keamanan dan IP privat pada permintaan sebenarnya beberapa saat kemudian.
Di Cloudflare hal ini tidak dapat dieksploitasi: Workers tidak dapat menjangkau IP privat atau endpoint metadata `169.254.169.254`, sehingga rebind tidak mengenai apa pun. Hanya saja, jangan beri worker jalan masuk: jangan pasang Tunnel, Service, atau binding jaringan privat (worker hanya memerlukan `RATE_LIMIT_KV`).
Jika Anda menjalankannya di luar Cloudflare (workerd, Node, dan sejenisnya) di dalam VPC, runtime _dapat_ menjangkau IP internal. Pasang firewall egress di depannya dan blokir rentang privat: `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`, beserta padanan IPv6-nya. Kode aplikasi tidak dapat menegakkannya setelah Anda berada di luar Cloudflare.
## Menonaktifkan Proxy
Jika Anda tidak ingin menggunakan CORS proxy, atur environment variable ke string kosong:
```
VITE_CORS_PROXY_URL=
```
**Catatan**: Tanpa proxy, penandatanganan dengan sertifikat yang memerlukan pengambilan rantai eksternal (seperti FNMT atau beberapa CA korporat) akan gagal dengan error "Failed to fetch".
## Pemecahan Masalah
### "Signing error: TypeError: Failed to fetch"
Ini biasanya berarti salah satu dari:
1. **CORS proxy belum dikonfigurasi**: Atur `VITE_CORS_PROXY_URL` dan build ulang
2. **Mixed content diblokir**: Situs Anda HTTPS tetapi URL penerbit sertifikat adalah HTTP. CORS proxy mengatasi hal ini.
3. **CORS proxy menolak origin Anda**: Periksa apakah domain Anda ada di array `ALLOWED_ORIGINS` pada `cors-proxy-worker.js`
### "403 Forbidden" dari proxy
Domain Anda tidak ada di daftar `ALLOWED_ORIGINS`. Edit `cors-proxy-worker.js`:
```js
const ALLOWED_ORIGINS = ['https://your-domain.com'];
```
Lalu deploy ulang: `npx wrangler deploy`
### Menguji proxy
```bash
curl -H "Origin: https://your-domain.com" \
"https://your-proxy.workers.dev?url=http://www.cert.fnmt.es/certs/ACUSU.crt"
```
### Sertifikat yang Berfungsi Tanpa Proxy
Beberapa sertifikat menyertakan rantai lengkap di file P12/PFX dan tidak memerlukan pengambilan eksternal:
- Sertifikat self-signed
- Beberapa CA komersial yang menyertakan sertifikat perantara
- Sertifikat yang Anda rakit sendiri dengan rantai lengkap
+622
View File
@@ -0,0 +1,622 @@
# Deploy dengan Docker / Podman
Cara termudah untuk menghosting BentoPDF sendiri di lingkungan produksi.
> [!IMPORTANT]
> **Header yang Diperlukan untuk Konversi File Office**
>
> Alat berbasis LibreOffice (konversi Word, Excel, PowerPoint) memerlukan header HTTP berikut untuk dukungan `SharedArrayBuffer`:
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> Halaman juga harus disajikan dari konteks aman (secure context). `http://localhost` berfungsi untuk pengujian lokal, tetapi `http://192.168.x.x` atau IP LAN lainnya biasanya tidak memenuhi syarat, sehingga konversi Office melalui HTTP biasa akan gagal meskipun header sudah ada.
>
> Image container resmi sudah menyertakan header ini. Jika menggunakan reverse proxy (Traefik, Caddy, dll.), pastikan header ini dipertahankan atau ditambahkan, dan gunakan HTTPS untuk akses non-loopback.
> [!TIP]
> **Pengguna Podman:** Semua perintah `docker` dapat dipakai dengan Podman dengan mengganti `docker` menjadi `podman` dan `docker-compose` menjadi `podman-compose`.
## Mulai Cepat
> [!TIP]
> BentoPDF tersedia dalam dua build:
>
> - **Self-Hosted build**: `ghcr.io/alam00000/bentopdf-simple:latest`. Semua alat PDF, **tanpa** elemen pemasaran (tanpa hero, FAQ, testimoni, footer). Gunakan ini untuk penerapan internal/tim/organisasi. Ini **bukan** versi "lite" dengan fitur yang dikurangi.
> - **Commercial build**: `ghcr.io/alam00000/bentopdf:latest`. Situs pemasaran lengkap, digunakan oleh bentopdf.com sendiri dan oleh pemegang lisensi komersial yang menjalankan penerapan publik.
>
> Jika ragu: gunakan Self-Hosted build.
```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
Buat `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
```
Jalankan:
```bash
# Docker Compose
docker compose up -d
# Podman Compose
podman-compose up -d
```
## Build Image Anda Sendiri
```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;"]
```
Build dan jalankan:
```bash
docker build -t bentopdf:custom .
docker run -d -p 3000:8080 bentopdf:custom
```
## Environment Variable
| Variabel | Deskripsi | Default |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `SIMPLE_MODE` | Build varian Self-Hosted (menyembunyikan elemen pemasaran bentopdf.com: hero, FAQ, testimoni, footer). Semua alat PDF tetap ada. | `false` |
| `BASE_URL` | Deploy ke subdirektori | `/` |
| `VITE_WASM_PYMUPDF_URL` | URL modul WASM PyMuPDF | `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/` |
| `VITE_WASM_GS_URL` | URL modul WASM Ghostscript | `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm@0.1.1/assets/` |
| `VITE_WASM_CPDF_URL` | URL modul WASM CoherentPDF | `https://cdn.jsdelivr.net/npm/coherentpdf@2.5.5/dist/` |
| `VITE_TESSERACT_WORKER_URL` | URL skrip worker OCR | _(kosong; gunakan CDN default Tesseract.js)_ |
| `VITE_TESSERACT_CORE_URL` | Direktori runtime inti OCR | _(empty; use Tesseract.js default CDN)_ |
| `VITE_TESSERACT_LANG_URL` | Direktori traineddata OCR | _(empty; use Tesseract.js default CDN)_ |
| `VITE_TESSERACT_AVAILABLE_LANGUAGES` | Bahasa OCR yang ditampilkan di UI, dipisahkan koma | _(kosong; tampilkan katalog lengkap)_ |
| `VITE_OCR_FONT_BASE_URL` | Direktori font lapisan teks OCR | _(kosong; gunakan URL font Noto jarak jauh)_ |
| `VITE_DEFAULT_LANGUAGE` | Bahasa UI default | `en` |
| `VITE_BRAND_NAME` | Nama merek kustom | `BentoPDF` |
| `VITE_BRAND_LOGO` | Path logo relatif terhadap `public/` | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | Teks footer/hak cipta kustom | `© 2026 BentoPDF. All rights reserved.` |
| `DISABLE_TOOLS` | ID alat yang disembunyikan, dipisahkan koma | _(kosong; semua alat aktif)_ |
| `DISABLE_GITHUB_STARS` | Lewati pengambilan jumlah bintang GitHub saat halaman dimuat dan hapus `api.github.com` dari CSP. Build Simple Mode selalu melewatinya. | `false` |
| `SITE_URL` | URL publik yang ditulis ke tautan canonical, hreflang, dan sitemap. Biarkan default kecuali Anda ingin mesin pencari memperlakukan deployment Anda sebagai situs tersendiri. | `https://www.bentopdf.com` |
| `ALLOW_INDEXING` | Dengan `SITE_URL` kustom, setiap halaman ditandai `noindex` agar salinan Anda tidak bersaing dengan bentopdf.com di hasil pencarian. Atur ke `true` agar mesin pencari mengindeksnya. | `false` |
URL modul WASM sudah dikonfigurasi dengan default CDN, sehingga semua fitur lanjutan langsung berfungsi. Timpa nilai ini untuk deployment air-gapped atau hosting mandiri.
### Mesin pencari
Secara default, setiap halaman mengarahkan mesin pencari ke bentopdf.com sebagai sumber aslinya, sehingga deployment Anda tidak muncul sebagai duplikat. Anda tidak perlu mengubah apa pun untuk instans privat atau internal.
- **Sembunyikan instans Anda sepenuhnya dari hasil pencarian:** jalankan container dengan `-e ROBOTS_NOINDEX=true`. Ini hanya perlu restart, tidak perlu build ulang.
- **Jadikan instans Anda situs tersendiri di hasil pencarian:** build dengan `--build-arg SITE_URL=https://your-domain.example --build-arg ALLOW_INDEXING=true`. Tanpa `ALLOW_INDEXING=true`, `SITE_URL` kustom menandai setiap halaman `noindex`.
### Content-Security-Policy
Image nginx dilengkapi header `Content-Security-Policy` yang ditegakkan. Direktif `script-src`, `connect-src`, dan `font-src` pada CSP **dibuat saat build** dari variabel URL `VITE_*` di atas, sehingga host apa pun yang Anda berikan melalui `--build-arg` otomatis ditambahkan ke kebijakan.
Dengan demikian:
- Jika Anda menimpa `VITE_CORS_PROXY_URL` atau `VITE_WASM_*_URL` saat build, CSP otomatis mengizinkan origin tersebut, tanpa konfigurasi tambahan.
- Untuk URL di `cdn.jsdelivr.net`, `script-src` hanya mengizinkan folder paket dengan versi yang tepat, misalnya `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/`, bukan seluruh CDN. jsDelivr menyajikan paket npm publik atau repositori GitHub mana pun, sehingga mengizinkan seluruh host berarti semuanya dapat berjalan di instans Anda. URL di host lain diizinkan berdasarkan origin.
- Jika Anda mengonfigurasi URL WASM kustom saat _runtime_ melalui halaman Pengaturan Lanjutan di aplikasi, origin tersebut **tidak** ada di CSP dan browser akan memblokir pengambilannya. Hal ini juga berlaku untuk versi lain dari paket yang sama di `cdn.jsdelivr.net`, karena hanya versi yang diatur saat build yang diizinkan. Konfigurasi runtime ditujukan untuk eksperimen; untuk URL kustom permanen, atur build arg `VITE_*` yang sesuai.
- Deployment air-gapped yang menimpa ketiga nilai `VITE_WASM_*_URL` juga menghapus folder paket `cdn.jsdelivr.net` tersebut dari CSP (setiap default diganti, bukan ditambahkan). Default Tesseract OCR di `cdn.jsdelivr.net` tetap ada di kebijakan sampai Anda juga mengatur ketiga nilai `VITE_TESSERACT_*`. Demikian pula, mengatur `VITE_CORS_PROXY_URL` menggantikan default publik `bentopdf-cors-proxy.bentopdf.workers.dev`.
CSP menyertakan `'unsafe-eval'` di `script-src` karena runtime LibreOffice WASM (digunakan alat konversi Word/Excel/PowerPoint) dan pustaka gambar wasm-vips (digunakan PDF ke TIFF) mengompilasi kode dispatch internal melalui `new Function()`. Menghapusnya akan merusak alat-alat tersebut. Jika Anda build dengan `SIMPLE_MODE` (tanpa LibreOffice) dan tidak memerlukan PDF ke TIFF, Anda dapat mengedit `security-headers.conf` yang dihasilkan secara manual untuk membuang `'unsafe-eval'` demi kebijakan yang lebih ketat.
Untuk OCR, biarkan variabel `VITE_TESSERACT_*` kosong untuk memakai aset online default, atau atur ketiganya sekaligus untuk OCR hosting mandiri/offline. Penimpaan OCR sebagian ditolak karena worker, runtime inti, dan direktori traineddata harus cocok. Untuk keluaran PDF yang dapat dicari secara offline penuh, atur juga `VITE_OCR_FONT_BASE_URL` agar font lapisan teks OCR dimuat dari server internal Anda, bukan dari URL font Noto publik.
`VITE_DEFAULT_LANGUAGE` menentukan bahasa UI untuk pengunjung pertama kali. Nilai yang didukung: `en`, `ar`, `be`, `fr`, `de`, `es`, `zh`, `zh-TW`, `vi`, `tr`, `id`, `it`, `pt`, `nl`, `da`. Pengguna tetap dapat berganti bahasa, ini hanya mengubah default.
Contoh:
```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
```
### Branding Kustom
Ganti logo, nama, dan teks footer BentoPDF default dengan milik Anda. Letakkan file logo di folder `public/` (atau gunakan gambar yang sudah ada), lalu berikan variabel branding saat 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 .
```
Branding berfungsi di mode penuh maupun Simple Mode, dan dapat dikombinasikan dengan semua opsi build lainnya.
### Menonaktifkan Alat Tertentu
Sembunyikan alat dari UI untuk kebutuhan kepatuhan atau keamanan. Alat yang dinonaktifkan dihapus dari beranda, hasil pencarian, pintasan keyboard, dan workflow builder. Akses URL langsung menampilkan halaman "alat tidak tersedia".
ID alat adalah URL halaman tanpa `.html`. Misalnya, jika alat berada di `bentopdf.com/edit-pdf.html`, ID-nya adalah `edit-pdf`.
#### Menemukan ID Alat
Cara termudah: buka alat apa pun di BentoPDF dan lihat URL-nya. Bagian terakhir dari path (tanpa `.html`) adalah ID alat.
<details>
<summary>Daftar lengkap ID alat</summary>
**Edit & Anotasi:** `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`
**Konversi ke 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`
**Konversi dari 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`
**Atur & Kelola:** `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`
**Optimalkan & Perbaiki:** `compress-pdf`, `pdf-to-pdfa`, `fix-page-size`, `linearize-pdf`, `remove-restrictions`, `repair-pdf`, `rasterize-pdf`, `deskew-pdf`, `font-to-outline`
**Keamanan:** `encrypt-pdf`, `sanitize-pdf`, `decrypt-pdf`, `flatten-pdf`, `remove-metadata`, `change-permissions`, `digital-sign-pdf`, `validate-signature-pdf`, `timestamp-pdf`
</details>
#### Opsi 1: Saat build (Docker build arg)
```bash
docker build \
--build-arg DISABLE_TOOLS="edit-pdf,sign-pdf,encrypt-pdf" \
-t bentopdf .
```
Ini menanamkan daftar yang dinonaktifkan ke dalam bundel JavaScript. Perlu build ulang untuk mengubahnya.
#### Opsi 2: Saat runtime (config.json)
Mount file `config.json` ke direktori yang disajikan, tanpa perlu build ulang:
```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
```
Atau dengan 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
```
Kedua metode dapat digabungkan: daftarnya digabung. Jika sebuah alat muncul di salah satunya, alat itu dinonaktifkan.
#### Menonaktifkan Fitur Editor
Anda juga dapat menonaktifkan fitur tertentu di dalam Editor PDF (mis., redaksi, anotasi, formulir) tanpa menonaktifkan seluruh alat editor. Tambahkan `editorDisabledCategories` ke `config.json` Anda:
```json
{
"editorDisabledCategories": ["redaction", "annotation-stamp"]
}
```
<details>
<summary>Daftar lengkap kategori editor</summary>
**Zoom:** `zoom`, `zoom-in`, `zoom-out`, `zoom-fit-page`, `zoom-fit-width`, `zoom-marquee`, `zoom-level`
**Anotasi:** `annotation`, `annotation-markup`, `annotation-highlight`, `annotation-underline`, `annotation-strikeout`, `annotation-squiggly`, `annotation-ink`, `annotation-text`, `annotation-stamp`
**Bentuk:** `annotation-shape`, `annotation-rectangle`, `annotation-circle`, `annotation-line`, `annotation-arrow`, `annotation-polygon`, `annotation-polyline`
**Formulir:** `form`, `form-textfield`, `form-checkbox`, `form-radio`, `form-select`, `form-listbox`, `form-fill-mode`
**Redaksi:** `redaction`, `redaction-area`, `redaction-text`, `redaction-apply`, `redaction-clear`
**Dokumen:** `document`, `document-open`, `document-close`, `document-print`, `document-capture`, `document-export`, `document-fullscreen`, `document-protect`
**Halaman:** `page`, `spread`, `rotate`, `scroll`, `navigation`
**Panel:** `panel`, `panel-sidebar`, `panel-search`, `panel-comment`
**Alat:** `tools`, `pan`, `pointer`, `capture`
**Pilihan:** `selection`, `selection-copy`
**Riwayat:** `history`, `history-undo`, `history-redo`
</details>
Kategori bersifat hierarkis: menonaktifkan induk (mis., `annotation`) menonaktifkan semua turunannya.
### Menonaktifkan Penghitung Bintang GitHub
Navbar menampilkan jumlah bintang GitHub secara langsung, diambil dari `api.github.com` saat halaman dimuat. Ini satu-satunya permintaan eksternal yang tidak melayani fitur PDF. Nonaktifkan saat build untuk menghapus kode pengambilan dari bundel dan membuang `api.github.com` dari Content-Security-Policy:
```bash
docker build --build-arg DISABLE_GITHUB_STARS=true -t bentopdf .
```
Build Simple Mode (`bentopdf-simple`) selalu melewati penghitung bintang, flag ini hanya diperlukan pada Commercial build atau build penuh kustom.
### URL WASM Kustom (Air-Gapped / Hosting Mandiri)
> [!IMPORTANT]
> URL WASM ditanamkan ke JavaScript saat **build**. File WASM diunduh oleh **browser pengguna** saat runtime, Docker tidak mengunduhnya selama build. Untuk jaringan air-gapped, Anda harus menghosting file WASM di server internal yang dapat dijangkau browser.
**Alur kerja air-gapped lengkap:**
```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
```
Gunakan kode yang dicetak oleh `bash scripts/prepare-airgap.sh --list-ocr-languages`, atau cari berdasarkan nama dengan `bash scripts/prepare-airgap.sh --search-ocr-language <term>`, untuk `--ocr-languages`. Saat build dengan subset OCR terbatas, berikan kode yang sama ke `VITE_TESSERACT_AVAILABLE_LANGUAGES` agar aplikasi hanya menampilkan bahasa yang disertakan. Untuk keluaran OCR offline penuh, hosting juga direktori `ocr-fonts/` bawaan dan arahkan `VITE_OCR_FONT_BASE_URL` ke sana.
Atur variabel ke string kosong untuk menonaktifkan modul tersebut (pengguna harus mengonfigurasinya secara manual melalui Pengaturan Lanjutan).
## Port Kustom
Secara default, BentoPDF mendengarkan di port `8080` di dalam container. Untuk mengubahnya, atur environment variable `PORT` saat runtime:
```bash
docker run -p 3000:9090 -e PORT=9090 ghcr.io/alam00000/bentopdf-simple:latest
```
| Variabel | Deskripsi | Default |
| -------- | ------------------------------ | ------- |
| `PORT` | Port listen Nginx di container | `8080` |
Ini berfungsi dengan Dockerfile standar maupun nonroot.
## User ID Kustom (PUID/PGID)
Untuk lingkungan yang mengharuskan berjalan sebagai user non-root tertentu (perangkat NAS, Kubernetes dengan security context, kebijakan organisasi), BentoPDF menyediakan Dockerfile terpisah dengan dukungan PUID/PGID ala LSIO.
### Build dan Jalankan
```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
```
### Environment Variable
| Variabel | Deskripsi | Default |
| -------------- | -------------------------- | ------- |
| `PUID` | User ID untuk menjalankan | `1000` |
| `PGID` | Group ID untuk menjalankan | `1000` |
| `PORT` | Port listen Nginx | `8080` |
| `DISABLE_IPV6` | Nonaktifkan 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
```
### Cara Kerjanya
Container dimulai sebagai root, membuat user dengan PUID/PGID yang ditentukan, menyesuaikan kepemilikan pada semua direktori yang dapat ditulis, lalu menurunkan hak akses menggunakan `su-exec`. Proses nginx berjalan sepenuhnya sebagai user yang Anda tentukan.
> [!NOTE]
> `Dockerfile` standar menggunakan `nginx-unprivileged` (UID 101) dan direkomendasikan untuk sebagian besar deployment. Gunakan `Dockerfile.nonroot` hanya jika Anda memerlukan UID/GID tertentu.
> [!WARNING]
> PUID/PGID tidak boleh `0` (root). Entrypoint memvalidasi input dan akan keluar dengan error untuk nilai yang tidak valid.
## Dengan Traefik (Reverse Proxy)
```yaml
services:
traefik:
image: traefik:v2.10
command:
- '--providers.docker=true'
- '--entrypoints.web.address=:80'
- '--entrypoints.websecure.address=:443'
- '--certificatesresolvers.letsencrypt.acme.email=you@example.com'
- '--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json'
- '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web'
ports:
- '80:80'
- '443:443'
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.bentopdf.rule=Host(`pdf.example.com`)'
- 'traefik.http.routers.bentopdf.entrypoints=websecure'
- 'traefik.http.routers.bentopdf.tls.certresolver=letsencrypt'
- 'traefik.http.services.bentopdf.loadbalancer.server.port=8080'
# Required headers for SharedArrayBuffer (LibreOffice WASM)
- 'traefik.http.routers.bentopdf.middlewares=bentopdf-headers'
- 'traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Opener-Policy=same-origin'
- 'traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Embedder-Policy=require-corp'
restart: unless-stopped
```
## Dengan Caddy (Reverse Proxy)
```yaml
services:
caddy:
image: caddy:2
ports:
- '80:80'
- '443:443'
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
restart: unless-stopped
volumes:
caddy_data:
```
Caddyfile:
```
pdf.example.com {
reverse_proxy bentopdf:8080
header Cross-Origin-Opener-Policy "same-origin"
header Cross-Origin-Embedder-Policy "require-corp"
}
```
## Batas Sumber Daya
```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 (Systemd Integration)
[Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) memungkinkan Anda menjalankan container Podman sebagai layanan systemd. Ini ideal untuk deployment produksi di sistem Linux.
### Penyiapan Quadlet Dasar
Buat file unit container di `~/.config/containers/systemd/bentopdf.container` (user) atau `/etc/containers/systemd/bentopdf.container` (sistem):
```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
```
### Aktifkan dan Mulai
```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]
> Untuk deployment tingkat sistem, gunakan `systemctl` tanpa flag `--user` dan letakkan file di `/etc/containers/systemd/`.
### Quadlet dengan 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
```
### Auto-Update dengan Quadlet
Podman dapat memperbarui container secara otomatis saat image baru tersedia:
```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
```
### Konfigurasi Jaringan Quadlet
Untuk konfigurasi jaringan kustom, buat file jaringan `bentopdf.network`:
```ini
[Network]
Subnet=10.89.0.0/24
Gateway=10.89.0.1
```
Lalu referensikan di file container Anda:
```ini
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
Network=bentopdf.network
```
## Memperbarui
```bash
# Pull latest image
docker compose pull
# Recreate container
docker compose up -d
```
+266
View File
@@ -0,0 +1,266 @@
# Deploy ke Hostinger
[Hostinger](https://hostinger.com) adalah penyedia shared hosting yang populer. Panduan ini membahas cara men-deploy BentoPDF ke shared hosting Hostinger.
## Prasyarat
- Paket hosting Hostinger dengan akses file manager
- Node.js terinstal secara lokal untuk build
## Langkah 1: Build Proyek
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
File hasil build akan berada di folder `dist`.
## Langkah 2: Unggah ke Hostinger
### Deploy di Domain Root
1. Masuk ke akun Hostinger Anda
2. Buka **File Manager** → **public_html**
3. **Hapus** semua file yang ada (buat cadangan jika perlu)
4. **Unggah** seluruh isi folder `dist` lokal Anda ke `public_html`
### Deploy di Subdirektori
Jika men-deploy ke subdirektori (mis., `yourdomain.com/pdf-tools/`):
1. Build dengan base URL yang benar:
```bash
BASE_URL=/pdf-tools/ npm run build
```
2. Buat folder di Hostinger:
- Buka **File Manager** → **public_html**
- Buat folder baru: `pdf-tools`
3. Unggah seluruh isi `dist` ke `public_html/pdf-tools/`
## Langkah 3: Buat File .htaccess
Buat file `.htaccess` di root folder deployment Anda (`public_html` atau `public_html/pdf-tools/`):
::: warning Penting
Untuk deploy di subdirektori, ubah `RewriteBase /` menjadi `RewriteBase /pdf-tools/` (atau nama folder Anda).
:::
```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
```
## Contoh .htaccess untuk Subdirektori
Untuk `yourdomain.com/pdf-tools/`, perbarui baris berikut:
```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
```
## Pemecahan Masalah
### File WASM Tidak Dimuat
Pastikan MIME type sudah diatur dengan benar di `.htaccess`:
```apache
AddType application/wasm .wasm
```
### Alat LibreOffice Tidak Berfungsi
Header keamanan sangat penting untuk SharedArrayBuffer:
```apache
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
```
Jika header tidak diterapkan, hubungi dukungan Hostinger untuk mengaktifkan `mod_headers`.
### Error 404 Saat Halaman Dimuat Ulang
Pastikan aturan ekstensi `.html` dan rute bahasa dikonfigurasi dengan benar. BentoPDF menggunakan file HTML statis, bukan routing SPA:
```apache
# Language routes serve actual files from language directories
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# Root level pages
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
```
### Batas Unggahan File
Hostinger mungkin memiliki batas ukuran file. Unggah file WASM secara terpisah jika unggahan massal gagal.
+538
View File
@@ -0,0 +1,538 @@
# Panduan Self-Hosting
BentoPDF dapat di-host sendiri pada infrastruktur Anda. Panduan ini mencakup berbagai opsi deployment.
## Mulai Cepat dengan Docker / Podman
Cara tercepat untuk self-host BentoPDF:
> [!TIP]
> BentoPDF tersedia dalam dua build:
>
> - **Build Self-Hosted**: `ghcr.io/alam00000/bentopdf-simple:latest`. Semua alat PDF, **tanpa** elemen pemasaran (tanpa hero, FAQ, testimoni, footer). Gunakan ini untuk deployment internal/tim/organisasi. Ini **bukan** versi "lite" dengan fitur yang dikurangi.
> - **Build Commercial**: `ghcr.io/alam00000/bentopdf:latest`. Situs pemasaran lengkap, dipakai oleh bentopdf.com sendiri dan oleh pemegang lisensi komersial yang menjalankan deployment publik.
>
> Jika ragu: gunakan build Self-Hosted.
> [!IMPORTANT]
> Konversi file Office memerlukan `SharedArrayBuffer`, yang berarti aplikasi harus terisolasi lintas origin sekaligus dilayani dari konteks aman (secure context). Image resmi sudah mengirim header COOP/COEP yang diperlukan, tetapi browser tetap menonaktifkan `SharedArrayBuffer` pada origin jaringan lokal HTTP biasa seperti `http://192.168.x.x`.
>
> Gunakan `http://localhost` hanya untuk pengujian di perangkat yang sama. Jika pengguna mengakses BentoPDF melalui IP LAN atau hostname, akhiri koneksi dengan 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
```
Atau dengan 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)
Jalankan BentoPDF sebagai layanan systemd. Buat `~/.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
```
Lihat [panduan deployment Docker](/id/self-hosting/docker) untuk dokumentasi Quadlet lengkap.
## Build dari Source
```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
```
## Opsi Konfigurasi
### Build Self-Hosted (Simple Mode)
Build Self-Hosted (image `bentopdf-simple`, juga disebut Simple Mode) **secara fungsional identik** dengan build Commercial: semua alat PDF tersedia dan berperilaku sama. Build ini hanya menyembunyikan elemen pemasaran yang hanya masuk akal di bentopdf.com sendiri atau pada deployment komersial yang terbuka untuk publik. **Ini bukan versi dengan fitur yang dikurangi atau "lite".**
**Yang disembunyikan build Self-Hosted** (hanya kosmetik, tidak ada fitur PDF yang dihapus):
- Bilah navigasi, bagian hero, bagian fitur, FAQ, testimoni, footer
- Mengubah judul halaman menjadi "Alat PDF"
**Yang dipertahankan build Self-Hosted** (semua yang benar-benar mengerjakan PDF):
- Semua alat PDF (gabung, pisah, edit, tanda tangan, OCR, konversi Office, dll.)
- Dukungan branding kustom, semua konfigurasi build-time dan runtime
Build Commercial (`ghcr.io/alam00000/bentopdf:latest`) adalah yang menjalankan bentopdf.com sendiri dan dipakai oleh pemegang lisensi komersial yang menjalankan deployment publik. Build ini menambahkan hero, FAQ, testimoni, dan footer yang tidak masuk akal pada alat internal.
Jika Anda self-host BentoPDF untuk tim, organisasi, atau sebagai alat internal, gunakan build Self-Hosted:
```bash
# Use the pre-built image (recommended)
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
# Or build it yourself
SIMPLE_MODE=true npm run build
```
Lihat [SIMPLE_MODE.md](https://github.com/alam00000/bentopdf/blob/main/SIMPLE_MODE.md) untuk detail lengkap.
### Build Commercial (deployment publik dengan merek Anda sendiri)
Build Commercial (image `bentopdf`, tanpa akhiran `-simple`) adalah yang menjalankan bentopdf.com sendiri. Build ini menyertakan situs pemasaran lengkap (hero, fitur, FAQ, testimoni, footer) di atas semua alat PDF. Gunakan build ini saat Anda ingin **men-deploy BentoPDF sebagai layanan PDF publik dengan merek Anda sendiri**, misalnya:
- Anda menjalankan BentoPDF sebagai SaaS terkelola untuk pengguna akhir di domain Anda sendiri
- Anda menginginkan pengalaman halaman landing (bagian pemasaran + alat), bukan hanya tampilan alat saja
- Anda pemegang lisensi komersial yang menyematkan BentoPDF ke dalam produk komersial
**Jalankan apa adanya** (branding BentoPDF, berguna untuk melihat pratinjau build):
```bash
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
```
**Build dengan merek Anda sendiri** (alur kerja komersial yang umum):
```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
```
Setiap opsi build-time lainnya (`BASE_URL`, `VITE_DEFAULT_LANGUAGE`, `DISABLE_TOOLS`, override URL WASM, dll.) bekerja dengan cara yang sama seperti pada build Self-Hosted.
::: warning Lisensi
Menjalankan build Commercial diizinkan di bawah salah satu dari dua opsi lisensi BentoPDF:
- **AGPL-3.0** (gratis): diizinkan jika deployment Anda mempublikasikan seluruh source code di bawah AGPL, termasuk modifikasi branding dan logika bisnis di sekitarnya.
- **Lisensi komersial** ($79 seumur hidup): diperlukan untuk deployment sumber tertutup/berpemilik (closed-source/proprietary) di mana Anda tidak membuka source code fork branding atau kode bisnis Anda.
Lihat [halaman Lisensi](https://www.bentopdf.com/licensing) untuk perbandingan lengkap. Modul WASM berlisensi AGPL (PyMuPDF, Ghostscript, CoherentPDF) dimuat dari CDN saat runtime, sehingga tidak masuk ke image Anda dan tidak mengubah posisi lisensi Anda.
:::
### Base URL
Deploy ke subdirektori:
```bash
BASE_URL=/pdf-tools/ npm run build
```
### Branding Kustom
Ganti logo, nama, dan teks footer BentoPDF default dengan milik Anda saat build:
| Variabel | Deskripsi | Default |
| ------------------ | ------------------------------------ | --------------------------------------- |
| `VITE_BRAND_NAME` | Nama merek di header dan footer | `BentoPDF` |
| `VITE_BRAND_LOGO` | Path logo relatif terhadap `public/` | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | Teks footer/hak cipta kustom | `© 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
```
Atau melalui 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 .
```
Branding berfungsi pada mode penuh maupun Simple Mode, dan dapat dikombinasikan dengan semua opsi build-time lainnya (`BASE_URL`, `SIMPLE_MODE`, `VITE_DEFAULT_LANGUAGE`).
### Menonaktifkan Alat Tertentu
Sembunyikan alat tertentu demi kepatuhan atau keamanan. Alat yang dinonaktifkan dihapus dari beranda, pencarian, pintasan, pembuat alur kerja, dan akses URL langsung.
ID alat adalah URL halaman tanpa `.html`: buka alat apa pun dan lihat URL-nya (misalnya `edit-pdf`, `sign-pdf`, `encrypt-pdf`).
**Build-time** (tertanam dalam bundle):
```bash
DISABLE_TOOLS="edit-pdf,sign-pdf" npm run build
```
**Runtime** (tanpa build ulang, mount `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
```
Kedua metode dapat dikombinasikan, daftarnya digabungkan.
Anda juga dapat menonaktifkan fitur tertentu di dalam Editor PDF (misalnya redaksi) tanpa menonaktifkan seluruh alat dengan menambahkan `editorDisabledCategories` ke `config.json`. Lihat [panduan Docker](/id/self-hosting/docker#menonaktifkan-fitur-editor) untuk daftar lengkap kategori.
## Panduan Deployment
Pilih platform Anda:
- [Vercel](/id/self-hosting/vercel)
- [Netlify](/id/self-hosting/netlify)
- [Cloudflare Pages](/id/self-hosting/cloudflare)
- [AWS S3 + CloudFront](/id/self-hosting/aws)
- [Hostinger](/id/self-hosting/hostinger)
- [Nginx](/id/self-hosting/nginx)
- [Apache](/id/self-hosting/apache)
- [Docker](/id/self-hosting/docker)
- [Kubernetes](/id/self-hosting/kubernetes)
- [CORS Proxy](/id/self-hosting/cors-proxy) - Diperlukan untuk tanda tangan digital
## Masalah Umum
### Word / ODT / Excel / PowerPoint ke PDF Macet (SharedArrayBuffer Tidak Tersedia)
**Gejala**: Konversi dokumen berbasis LibreOffice (Word, ODT, Excel, PowerPoint ke PDF) macet di ~55% atau gagal dimulai. Konsol browser mungkin menampilkan:
> `ReferenceError: SharedArrayBuffer is not defined`
...atau `window.crossOriginIsolated` melaporkan `false`, atau kompilasi WASM melaporkan `expected magic word 00 61 73 6d, found 1f 8b 08 08`.
**Penyebab**: LibreOffice WASM memerlukan `SharedArrayBuffer`, yang hanya diaktifkan browser ketika halaman **terisolasi lintas origin** DAN dilayani dari **konteks aman (secure context)**. Artinya dua hal harus terpenuhi:
1. Setiap respons menyertakan kedua header berikut:
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
2. Halaman dilayani dari `https://...` atau `http://localhost`. HTTP biasa pada IP LAN (mis. `http://192.168.x.x`) TIDAK dianggap aman: browser menonaktifkan `SharedArrayBuffer` di sana.
Ketidakcocokan `00 61 73 6d / 1f 8b 08 08` adalah sub-gejala tersendiri: file `.wasm.gz` / `.data.gz` yang sudah dikompresi tidak memiliki header respons `Content-Encoding: gzip`, sehingga browser menerima byte gzip mentah, bukan WASM yang sudah didekompresi.
**Solusi**: lihat panduan deployment khusus platform Anda untuk konfigurasi yang tepat:
- [Nginx →](/id/self-hosting/nginx#word-odt-excel-ke-pdf-tidak-berfungsi)
- [Apache →](/id/self-hosting/apache#word-odt-excel-ke-pdf-tidak-berfungsi)
- [AWS S3 + CloudFront →](/id/self-hosting/aws#langkah-3b-response-headers-policy-diperlukan-untuk-libreoffice-wasm)
- [Cloudflare Pages →](/id/self-hosting/cloudflare#file-konfigurasi) (`_headers` file)
- [Netlify →](/id/self-hosting/netlify#word-odt-excel-ke-pdf-macet-di-55) (`netlify.toml`)
- [Vercel →](/id/self-hosting/vercel#word-odt-excel-ke-pdf-tidak-berfungsi) (`vercel.json`)
- [Hostinger →](/id/self-hosting/hostinger#alat-libreoffice-tidak-berfungsi) (`.htaccess`)
- [Kubernetes →](/id/self-hosting/kubernetes#memastikan-header-sharedarraybuffer-tetap-berfungsi-ingress-gateway)
- **Docker**: ditangani otomatis oleh konfigurasi nginx bawaan, tidak perlu tindakan.
**Verifikasi**: buka Konsol DevTools di halaman BentoPDF mana pun dan jalankan:
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
Jika halaman menggunakan HTTPS atau `http://localhost` DAN kedua header COEP/COOP ada pada setiap respons, kedua pemeriksaan lulus. Jika Anda berada di `http://192.168.x.x` atau origin HTTP non-loopback lainnya, akhiri koneksi dengan HTTPS: tidak ada solusi yang hanya berupa header.
### File `.mjs` Dilayani sebagai `application/octet-stream`
**Gejala**: Tanda Tangani PDF / Form Filler / beberapa alat lain menampilkan penampil kosong atau gagal dimuat. Konsol browser melaporkan:
> `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.`
**Penyebab**: Web server atau reverse proxy Anda tidak memiliki pemetaan MIME-type untuk file `.mjs` (penampil PDF bawaan menyertakan modul ES dengan ekstensi itu). Banyak konfigurasi server bawaan menggunakan `application/octet-stream` untuk ekstensi yang tidak dikenali, yang menolak dieksekusi browser sebagai modul ES.
**Solusi**: lihat panduan deployment khusus platform Anda untuk potongan konfigurasi yang tepat:
- [Nginx →](/id/self-hosting/nginx#tanda-tangani-pdf-atau-form-filler-menampilkan-penampil-kosong-error-mime-mjs)
- [Apache →](/id/self-hosting/apache#tanda-tangani-pdf-atau-form-filler-menampilkan-penampil-kosong-error-mime-mjs)
- [AWS S3 + CloudFront →](/id/self-hosting/aws#langkah-2-build-dan-unggah) (lihat `aws s3 cp ... --include "*.mjs"`)
- [Kubernetes →](/id/self-hosting/kubernetes#error-mime-type-mjs-iframe-tanda-tangani-pdf-form-filler-kosong)
- **Docker, Vercel, Netlify, Cloudflare Pages, Hostinger**: ditangani otomatis, tidak perlu tindakan.
**Verifikasi**: buka DevTools → tab Network, cari permintaan `.mjs` yang gagal, periksa header respons `Content-Type`. Seharusnya `application/javascript`. Jika masih `application/octet-stream`, reverse proxy atau CDN di luar mungkin mendeteksi ulang tipenya: periksa setiap lapisan dalam rantai layanan Anda.
## Konfigurasi WASM (Komponen AGPL)
BentoPDF **tidak menyertakan** pustaka pemrosesan berlisensi AGPL dalam source code-nya, tetapi **sudah mengonfigurasi URL CDN** sehingga semua fitur langsung berfungsi tanpa pengaturan manual.
::: tip Tanpa Konfigurasi secara Default
Sejak v2.0.0, modul WASM sudah dikonfigurasi untuk dimuat dari CDN jsDelivr melalui variabel lingkungan. Semua fitur lanjutan langsung berfungsi tanpa konfigurasi pengguna.
:::
| Komponen | Lisensi | Fitur |
| --------------- | -------- | ------------------------------------------------------------- |
| **PyMuPDF** | AGPL-3.0 | Konversi EPUB/MOBI/FB2/XPS, ekstraksi gambar, ekstraksi tabel |
| **Ghostscript** | AGPL-3.0 | Konversi PDF/A, kompresi, deskewing, rasterisasi |
| **CoherentPDF** | AGPL-3.0 | Daftar isi, lampiran, penggabungan PDF dengan bookmark |
### Variabel Lingkungan Default
Variabel ini diatur di `.env.production` dan tertanam dalam build:
```bash
VITE_WASM_PYMUPDF_URL=https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/
VITE_WASM_GS_URL=https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm@0.1.1/assets/
VITE_WASM_CPDF_URL=https://cdn.jsdelivr.net/npm/coherentpdf@2.5.5/dist/
VITE_TESSERACT_WORKER_URL=
VITE_TESSERACT_CORE_URL=
VITE_TESSERACT_LANG_URL=
VITE_TESSERACT_AVAILABLE_LANGUAGES=
VITE_OCR_FONT_BASE_URL=
```
### Menimpa URL WASM
Anda dapat menimpa nilai default saat build untuk deployment kustom:
```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
```
Untuk menonaktifkan sebuah modul sepenuhnya (memerlukan konfigurasi manual pengguna melalui Pengaturan Lanjutan), atur variabelnya ke string kosong.
Untuk OCR, biarkan semua variabel `VITE_TESSERACT_*` kosong dan gunakan aset online default, atau atur URL worker/core/lang bersamaan untuk OCR self-hosted/offline. Jika Anda hanya menyertakan bahasa OCR tertentu, atur juga `VITE_TESSERACT_AVAILABLE_LANGUAGES` dengan kode yang sama (dipisahkan koma) agar UI hanya menawarkan bahasa yang terpasang dan pilihan yang tidak didukung gagal dengan pesan error yang jelas. Untuk keluaran PDF yang dapat dicari sepenuhnya offline, atur juga `VITE_OCR_FONT_BASE_URL` ke direktori internal yang melayani font OCR bawaan.
Pengguna juga dapat menimpa nilai default ini kapan saja melalui **Pengaturan Lanjutan** di UI. Penimpaan pengguna yang tersimpan di browser lebih diprioritaskan daripada default lingkungan.
### Deployment Air-Gapped / Offline
Untuk jaringan tanpa akses internet (pemerintah, kesehatan, keuangan, dll.). URL WASM tertanam ke dalam JavaScript pada **waktu build**, sedangkan file WASM sebenarnya diunduh oleh **browser pengguna** saat runtime. Jadi Anda perlu menyiapkan semuanya di mesin yang terhubung internet, lalu memindahkannya ke jaringan terisolasi.
#### Skrip Otomatis (Disarankan)
Skrip `prepare-airgap.sh` yang disertakan mengotomatiskan seluruh proses: mengunduh paket WASM, membangun image Docker, dan menghasilkan bundel mandiri beserta skrip setup.
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
# Show supported OCR language codes (for --ocr-languages)
bash scripts/prepare-airgap.sh --list-ocr-languages
# Search OCR language codes by name or abbreviation
bash scripts/prepare-airgap.sh --search-ocr-language german
# Interactive mode — prompts for all options
bash scripts/prepare-airgap.sh
# Or fully automated
bash scripts/prepare-airgap.sh --wasm-base-url https://internal.example.com/wasm
```
Ini menghasilkan direktori bundel:
```
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
```
Pindahkan bundel ke jaringan air-gapped melalui USB, repositori artefak internal, atau metode yang disetujui. Lalu jalankan skrip setup yang disertakan:
```bash
cd bentopdf-airgap-bundle
bash setup.sh
```
Skrip setup memuat image Docker, mengekstrak file WASM, dan secara opsional memulai kontainer.
**Opsi skrip:**
| Flag | Deskripsi | Default |
| ------------------------------ | -------------------------------------------------- | --------------------------------- |
| `--wasm-base-url <url>` | Tempat WASM akan di-host secara internal | _(wajib, ditanyakan jika kosong)_ |
| `--image-name <name>` | Tag image Docker | `bentopdf` |
| `--output-dir <path>` | Direktori bundel keluaran | `./bentopdf-airgap-bundle` |
| `--simple-mode` | Aktifkan Simple Mode | mati |
| `--base-url <path>` | Base URL subdirektori (mis. `/pdf/`) | `/` |
| `--language <code>` | Bahasa UI default (mis. `fr`, `de`) | _(tidak ada)_ |
| `--brand-name <name>` | Nama merek kustom | _(tidak ada)_ |
| `--brand-logo <path>` | Path logo relatif terhadap `public/` | _(tidak ada)_ |
| `--footer-text <text>` | Teks footer kustom | _(tidak ada)_ |
| `--ocr-languages <list>` | Bahasa OCR yang disertakan, dipisahkan koma | `eng` |
| `--list-ocr-languages` | Cetak kode dan nama OCR yang didukung, lalu keluar | mati |
| `--search-ocr-language <term>` | Cari kode OCR berdasarkan nama atau singkatan | mati |
| `--dockerfile <path>` | Dockerfile yang digunakan | `Dockerfile` |
| `--skip-docker` | Lewati build dan ekspor Docker | mati |
| `--skip-wasm` | Lewati unduhan WASM (pakai ulang file `.tgz`) | mati |
Prompt interaktif juga menerima `list` untuk mencetak daftar lengkap kode Tesseract yang didukung dan `search <term>` untuk mencari kecocokan seperti `search german` atau `search chi`.
::: warning Persyaratan Same-Origin
File WASM harus dilayani dari **origin yang sama** dengan aplikasi BentoPDF. Web Worker menggunakan `importScripts()` yang tidak dapat memuat skrip lintas origin. Misalnya, jika BentoPDF berjalan di `https://internal.example.com`, base URL WASM juga harus `https://internal.example.com/wasm`.
:::
#### Langkah Manual
<details>
<summary>Jika Anda lebih suka melakukannya secara manual tanpa skrip</summary>
**Langkah 1: Unduh paket WASM dan OCR** (di mesin yang terhubung 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
```
**Langkah 2: Build image Docker dengan URL internal**
```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 .
```
**Langkah 3: Ekspor image Docker**
```bash
docker save bentopdf -o bentopdf.tar
```
**Langkah 4: Pindahkan ke jaringan air-gapped**
Salin melalui USB, repositori artefak internal, atau metode transfer yang disetujui:
- `bentopdf.tar`: image Docker
- Kelima paket WASM/OCR `.tgz` dari Langkah 1
- Direktori `tesseract-langdata/` dari Langkah 1
- Direktori `ocr-fonts/` dari Langkah 1
**Langkah 5: Siapkan di dalam jaringan air-gapped**
```bash
# Load the Docker image
docker load -i bentopdf.tar
# Extract WASM packages
mkdir -p ./wasm/pymupdf ./wasm/gs ./wasm/cpdf ./wasm/ocr/core ./wasm/ocr/lang-data ./wasm/ocr/fonts
tar xzf bentopdf-pymupdf-wasm-0.11.14.tgz -C ./wasm/pymupdf --strip-components=1
tar xzf bentopdf-gs-wasm-*.tgz -C ./wasm/gs --strip-components=1
tar xzf coherentpdf-*.tgz -C ./wasm/cpdf --strip-components=1
TEMP_TESS=$(mktemp -d)
tar xzf tesseract.js-7.0.0.tgz -C "$TEMP_TESS"
cp "$TEMP_TESS/package/dist/worker.min.js" ./wasm/ocr/worker.min.js
rm -rf "$TEMP_TESS"
tar xzf tesseract.js-core-7.0.0.tgz -C ./wasm/ocr/core --strip-components=1
cp ./tesseract-langdata/*.traineddata.gz ./wasm/ocr/lang-data/
cp ./ocr-fonts/* ./wasm/ocr/fonts/
# Run BentoPDF
docker run -d -p 3000:8080 --restart unless-stopped bentopdf
```
Pastikan file dapat diakses pada URL yang Anda konfigurasikan di Langkah 2, termasuk `.../ocr/worker.min.js`, `.../ocr/core`, `.../ocr/lang-data`, dan `.../ocr/fonts`.
</details>
::: info Build dari source, bukan Docker?
Atur variabel di `.env.production` sebelum menjalankan `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
```
:::
### Menjalankan Proxy WASM Sendiri
Jika Anda perlu melayani file WASM AGPL dengan header CORS yang tepat, Anda dapat men-deploy proxy sederhana. Lihat [panduan Cloudflare WASM Proxy](https://github.com/alam00000/bentopdf/blob/main/cloudflare/WASM-PROXY.md) untuk contoh implementasi.
::: tip Mengapa Dipisah?
Pemisahan ini memastikan:
- Kepatuhan hukum yang jelas bagi pengguna komersial
- Inti BentoPDF tetap berada di bawah lisensi ganda (AGPL-3.0 / Commercial)
- File WASM dimuat saat runtime, tidak disertakan dalam source
:::
## Persyaratan Sistem
| Persyaratan | Minimum |
| ----------- | ------------------------------- |
| Penyimpanan | ~100 MB (inti tanpa modul AGPL) |
| RAM | 512 MB |
| CPU | Prosesor modern apa pun |
::: tip
BentoPDF adalah situs statis, tidak diperlukan database atau server backend!
:::
+211
View File
@@ -0,0 +1,211 @@
# Deploy dengan Kubernetes
Kubernetes mungkin berlebihan untuk situs statis, tetapi bisa sangat cocok jika Anda sudah menstandarkan Helm + GitOps.
> [!IMPORTANT]
> **Header yang Diperlukan untuk Konversi File Office**
>
> Alat berbasis LibreOffice (konversi Word, Excel, PowerPoint) memerlukan header HTTP berikut untuk dukungan `SharedArrayBuffer`:
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> Image nginx resmi BentoPDF sudah menyertakan header ini. Di Kubernetes, **controller Ingress/Gateway juga merupakan reverse proxy**, jadi pastikan header ini tetap dipertahankan (atau tambahkan di edge).
## Prasyarat
- Klaster Kubernetes
- Helm v3
- Image nginx BentoPDF (misalnya `ghcr.io/alam00000/bentopdf:<tag>`) yang melayani di **port 8080**
## Deploy dengan Helm
### Instal dari repo ini (chart lokal)
```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
```
### Instal dari GHCR (chart OCI)
Jika chart dipublikasikan ke GHCR sebagai artefak 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
```
## Mengeksposnya
### Port-forward (uji cepat)
```bash
kubectl -n bentopdf port-forward deploy/bentopdf 8080:8080
```
### Ingress (opsional)
Aktifkan Ingress (contoh untuk nginx-ingress):
```yaml
ingress:
enabled: true
className: nginx
hosts:
- host: pdf.example.com
paths:
- path: /
pathType: Prefix
```
### Gateway API (opsional)
Chart ini mendukung Gateway API `Gateway` + `HTTPRoute`.
Contoh (operator 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
```
## Memastikan header SharedArrayBuffer tetap berfungsi (Ingress/Gateway)
### Apa yang "seharusnya" terjadi
Konfigurasi nginx BentoPDF menetapkan header respons yang diperlukan. Sebagian besar controller Ingress/Gateway **meneruskan header respons upstream tanpa perubahan**.
### Apa yang bisa merusaknya
- Kebijakan controller/edge yang **menimpa** atau **menghapus** header respons
- Middleware "security headers" yang menetapkan nilai COOP/COEP yang berbeda
### Cara memverifikasi
Jalankan perintah ini pada endpoint publik Anda:
```bash
curl -I https://pdf.example.com/ | egrep -i 'cross-origin-opener-policy|cross-origin-embedder-policy'
```
Anda seharusnya melihat:
- `Cross-Origin-Opener-Policy: same-origin`
- `Cross-Origin-Embedder-Policy: require-corp`
### Jika controller Ingress Anda tidak mempertahankannya
Tambahkan header di edge (bergantung pada controller). Contoh untuk **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;
```
## Error MIME-type `.mjs` (iframe Tanda Tangani PDF / Form Filler kosong)
Jika konsol browser menampilkan `Failed to load module script: ... non-JavaScript MIME type "application/octet-stream"` pada permintaan `.mjs`, berarti controller Ingress/Gateway di depan nginx BentoPDF mengganti header `Content-Type` upstream dengan `application/octet-stream`.
Nginx pada image BentoPDF sudah melayani `.mjs` sebagai `application/javascript`. Solusinya adalah menghentikan controller dari menghapus/menggantinya. Untuk nginx-ingress:
```yaml
ingress:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
location ~* \.mjs$ {
types {} default_type application/javascript;
}
```
Atau gunakan filter `ResponseHeaderModifier` di Gateway API untuk menetapkan `Content-Type: application/javascript` pada path `.mjs`. Verifikasi dengan DevTools → tab Network: permintaan `.mjs` yang gagal seharusnya kini menampilkan `content-type: application/javascript`.
### Jika Anda memakai Gateway API dan ingin menambahkan header secara paksa
Gateway API mendukung filter `ResponseHeaderModifier`. Anda dapat memasangnya di `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
```
Dukungan untuk filter tertentu bergantung pada controller Gateway Anda; jika sebuah filter diabaikan, tambahkan header di lapisan edge/controller.
## Menonaktifkan Alat Tertentu
Gunakan ConfigMap untuk menonaktifkan alat saat runtime tanpa membangun ulang image:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: bentopdf-config
namespace: bentopdf
data:
config.json: |
{
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
}
```
Mount ke direktori yang dilayani:
```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
```
ID alat adalah URL halaman tanpa `.html`: buka alat apa pun dan lihat URL-nya (misalnya `edit-pdf`, `merge-pdf`, `compress-pdf`). Alat yang dinonaktifkan disembunyikan dari beranda, pencarian, pintasan, pembuat alur kerja, dan akses URL langsung. Lihat [panduan Docker](/id/self-hosting/docker#menonaktifkan-alat-tertentu) untuk daftar lengkap opsi.
+139
View File
@@ -0,0 +1,139 @@
# Deploy ke Netlify
[Netlify](https://netlify.com) menyediakan hosting situs statis yang sangat baik dengan paket gratis yang lapang.
## Deploy Sekali Klik
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/alam00000/bentopdf)
## Deploy Manual
### Langkah 1: Hubungkan Repositori
1. Masuk ke [Netlify](https://app.netlify.com)
2. Klik "Add new site" → "Import an existing project"
3. Hubungkan akun GitHub Anda
4. Pilih fork BentoPDF Anda
### Langkah 2: Atur Pengaturan Build
| Pengaturan | Nilai |
| ----------------- | --------------- |
| Perintah build | `npm run build` |
| Direktori publish | `dist` |
| Versi Node | 18+ |
### Langkah 3: Deploy
Klik "Deploy site" dan tunggu build selesai.
## File Konfigurasi
Buat `netlify.toml` di root proyek Anda:
```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 Penting
Header `Cross-Origin-Embedder-Policy` dan `Cross-Origin-Opener-Policy` diperlukan untuk konversi Word/ODT/Excel/PowerPoint ke PDF. Tanpa keduanya, `SharedArrayBuffer` tidak tersedia dan mesin LibreOffice WASM gagal diinisialisasi.
Header `Content-Encoding: gzip` pada file `.wasm.gz` dan `.data.gz` memberi tahu browser untuk mendekompresinya secara otomatis. Tanpa header ini, browser menerima byte gzip mentah dan kompilasi WASM gagal.
:::
## Variabel Lingkungan
Atur variabel ini di Site settings → Environment variables:
| Variabel | Deskripsi |
| ----------------------- | ---------------------------------------------------------------- |
| `SIMPLE_MODE` | Atur ke `true` untuk build minimal |
| `VITE_BRAND_NAME` | Nama merek kustom (menggantikan "BentoPDF") |
| `VITE_BRAND_LOGO` | Path logo relatif terhadap `public/` (mis. `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | Teks footer/hak cipta kustom |
| `VITE_DEFAULT_LANGUAGE` | Bahasa UI default (mis. `fr`, `de`, `es`) |
## Domain Kustom
1. Buka Site settings → Domain management
2. Klik "Add custom domain"
3. Ikuti petunjuk konfigurasi DNS
## Large Media
Untuk file WASM berukuran besar, pertimbangkan mengaktifkan [Netlify Large Media](https://docs.netlify.com/large-media/overview/):
```bash
netlify lm:setup
git lfs track "*.wasm"
```
## Pemecahan Masalah
### Word/ODT/Excel ke PDF Macet di 55%
Jika konversi dokumen macet di 55%, buka Konsol DevTools dan periksa:
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
Jika `crossOriginIsolated` bernilai `false`, header COEP/COOP dari `netlify.toml` Anda tidak diterapkan. Pastikan file berada di root proyek Anda lalu deploy ulang.
Jika Anda melihat `expected magic word 00 61 73 6d, found 1f 8b 08 08` di konsol, berarti file `.wasm.gz` tidak memiliki header `Content-Encoding: gzip`. Pastikan blok `[[headers]]` untuk `soffice.wasm.gz` dan `soffice.data.gz` ada di `netlify.toml` Anda.
### Build Gagal
Periksa kompatibilitas versi Node:
```toml
[build.environment]
NODE_VERSION = "20"
```
### Pemuatan Awal Lambat
Aktifkan optimasi aset di Site settings → Build & deploy → Asset optimization.
+232
View File
@@ -0,0 +1,232 @@
# Deploy dengan Nginx
Host BentoPDF di server Anda sendiri menggunakan Nginx.
## Prasyarat
- Server Ubuntu/Debian
- Nginx terinstal
- Sertifikat SSL (disarankan: Let's Encrypt)
## Langkah 1: Build Proyek
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
Untuk menyesuaikan branding, atur variabel lingkungan sebelum build:
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## Langkah 2: Salin File
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## Langkah 3: Konfigurasi Nginx
Buat `/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;
}
}
```
## Langkah 4: Aktifkan Situs
```bash
sudo ln -s /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
## Langkah 5: SSL dengan Let's Encrypt
```bash
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
```
## Deploy di Subdirektori
Untuk hosting di `/pdf/`:
```nginx
location /pdf/ {
alias /var/www/bentopdf/;
try_files $uri $uri/ /pdf/index.html;
}
```
Build dengan:
```bash
BASE_URL=/pdf/ npm run build
```
## Penyetelan Performa
Tambahkan ke `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;
}
```
## Pemecahan Masalah
### WASM Tidak Dimuat
Pastikan MIME type sudah diatur:
```nginx
types {
application/wasm wasm;
}
```
### Tanda Tangani PDF atau Form Filler Menampilkan Penampil Kosong (error MIME `.mjs`)
Jika konsol browser menampilkan:
```
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.
```
...dan permintaan yang gagal berakhir dengan `.mjs`, berarti nginx Anda tidak memiliki pemetaan `.mjs → application/javascript`. Penampil PDF bawaan (dipakai oleh Tanda Tangani PDF, Form Filler, dan beberapa alat lain) menyertakan file modul ES berekstensi `.mjs`, dan `mime.types` bawaan nginx tidak memuatnya secara default.
Perbaiki dengan menambahkan `mjs` ke baris `application/javascript` yang sudah ada di `/etc/nginx/mime.types`:
```nginx
application/javascript js mjs;
```
> JANGAN menambahkan baris `application/javascript` _kedua_: nginx mem-parse `mime.types` baris demi baris dan hanya satu pemetaan tipe ke ekstensi yang berlaku, sehingga baris kedua diabaikan begitu saja pada beberapa versi. Edit baris yang sudah ada untuk menambahkan `mjs` di samping `js`.
Atau timpa secara lokal di dalam blok server Anda (tanpa mengedit file global):
```nginx
types {
application/javascript js mjs;
application/wasm wasm;
}
```
Muat ulang nginx setelah perubahan:
```bash
sudo nginx -t && sudo systemctl reload nginx
```
Untuk memastikan: di DevTools browser → tab Network, cari permintaan `.mjs` yang gagal dan periksa header respons `Content-Type`. Seharusnya kini `application/javascript`, bukan `application/octet-stream`.
### Word/ODT/Excel ke PDF Tidak Berfungsi
LibreOffice WASM memerlukan `SharedArrayBuffer`, yang membutuhkan header `Cross-Origin-Embedder-Policy` dan `Cross-Origin-Opener-Policy`. Ini juga membutuhkan secure context, sehingga `http://localhost` berfungsi untuk pengujian lokal, tetapi `http://192.168.x.x` atau IP LAN lain biasanya memerlukan HTTPS. Perhatikan bahwa direktif `add_header` nginx dalam blok `location` **menimpa** direktif `add_header` tingkat server, bukan menggabungkannya. Setiap blok `location` yang memiliki `add_header` sendiri harus menyertakan header COEP/COOP.
Verifikasi dengan:
```bash
curl -I https://your-domain.com/ | grep -i cross-origin
```
Jika memakai reverse proxy di depan nginx, pastikan header ini tetap dipertahankan.
### 502 Bad Gateway
Periksa log error Nginx:
```bash
sudo tail -f /var/log/nginx/error.log
```
+122
View File
@@ -0,0 +1,122 @@
# Deploy ke Vercel
[Vercel](https://vercel.com) menawarkan pengalaman deploy paling mudah untuk situs statis.
## Deploy Sekali Klik
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/alam00000/bentopdf)
## Deploy Manual
### Langkah 1: Fork Repositori
Fork [bentopdf/bentopdf](https://github.com/alam00000/bentopdf) ke akun GitHub Anda.
### Langkah 2: Impor ke Vercel
1. Buka [vercel.com/new](https://vercel.com/new)
2. Pilih repositori hasil fork Anda
3. Konfigurasikan proyek:
| Pengaturan | Nilai |
| ---------------- | --------------- |
| Framework Preset | Vite |
| Build Command | `npm run build` |
| Output Directory | `dist` |
| Install Command | `npm install` |
### Langkah 3: Variabel Lingkungan (Opsional)
Tambahkan jika diperlukan:
| Variabel | Deskripsi |
| ----------------------- | ---------------------------------------------------------------- |
| `SIMPLE_MODE` | Atur ke `true` untuk UI minimal |
| `VITE_BRAND_NAME` | Nama merek kustom (menggantikan "BentoPDF") |
| `VITE_BRAND_LOGO` | Path logo relatif terhadap `public/` (mis. `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | Teks footer/hak cipta kustom |
| `VITE_DEFAULT_LANGUAGE` | Bahasa UI default (mis. `fr`, `de`, `es`) |
### Langkah 4: Deploy
Klik "Deploy" dan tunggu build selesai.
## Domain Kustom
1. Buka pengaturan proyek Anda
2. Buka "Domains"
3. Tambahkan domain kustom Anda
4. Konfigurasikan DNS sesuai petunjuk
## Batasan
::: warning File Besar
Fungsi serverless Vercel memiliki batas 50MB. Karena BentoPDF adalah situs statis, ini seharusnya tidak memengaruhi Anda, tetapi modul WASM berukuran besar (total ~100MB). Pastikan modul tersebut dilayani dari folder `/public`.
:::
## Pemecahan Masalah
### Batas Waktu Build
Jika build mengalami timeout, coba:
```json
// vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": "dist"
}
```
### 404 Saat Refresh
Tambahkan `vercel.json` untuk routing SPA:
```json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
```
### Word/ODT/Excel ke PDF Tidak Berfungsi
Konversi LibreOffice WASM memerlukan `SharedArrayBuffer`, yang membutuhkan header respons berikut pada semua halaman:
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
File `.wasm.gz` dan `.data.gz` yang sudah dikompresi juga memerlukan `Content-Encoding: gzip` agar browser mendekompresinya.
Tambahkan ini ke `vercel.json` Anda:
```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" }
]
}
]
}
```
Untuk memverifikasi, buka Konsol DevTools dan jalankan `console.log(window.crossOriginIsolated)`, hasilnya seharusnya `true`.