mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-13 09:54:19 +01:00
feat: translate all pages, UI messages and docs and fix SEO redirects and indexing
This commit is contained in:
1 parent
5e019de5ae
commit
4edb1718cc
3344 files changed
+415643
-4759
No files matched your search
@@ -0,0 +1,275 @@
|
||||
# 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
|
||||
```
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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!
|
||||
:::
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
[](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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -0,0 +1,122 @@
|
||||
# Deploy ke Vercel
|
||||
|
||||
[Vercel](https://vercel.com) menawarkan pengalaman deploy paling mudah untuk situs statis.
|
||||
|
||||
## Deploy Sekali Klik
|
||||
|
||||
[](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`.
|
||||
Reference in new issue
Block a user