add bentopdf

This commit is contained in:
ToledoEM
2026-03-16 12:15:36 +00:00
parent 08b3c313d9
commit 5d68d92afa
19 changed files with 482 additions and 0 deletions

8
bentopdf/CHANGELOG.md Normal file
View File

@@ -0,0 +1,8 @@
# Changelog
## 2.5.0
- Initial release of BentoPDF Home Assistant add-on
- Based on upstream BentoPDF v2.5.0
- Serves 50+ client-side PDF tools via nginx on port 8080
- Supports amd64 and aarch64

53
bentopdf/DOCS.md Normal file
View File

@@ -0,0 +1,53 @@
# BentoPDF
Privacy-first PDF toolkit with 50+ tools. All processing happens client-side in the browser — files never leave your device.
## Usage
After starting the add-on, open the web UI via the **Open Web UI** button or navigate to `https://<HA_IP>:8443`.
No configuration is required to get started.
## Browser Security Warning (Expected)
When you first open the add-on, your browser will show a security warning similar to:
> **Warning: Potential Security Risk Ahead** — Firefox detected a potential security threat…
or in Chrome/Edge:
> **Your connection is not private** — NET::ERR_CERT_AUTHORITY_INVALID
**This is expected and safe to proceed.** Here is why it happens and what to do:
### Why does this happen?
The add-on serves content over HTTPS using a **self-signed TLS certificate** generated locally on your Home Assistant instance. This certificate was not issued by a public Certificate Authority (CA) that browsers trust by default — it was created specifically for your installation.
HTTPS is required because the office file conversion feature (Word, Excel, PowerPoint → PDF) uses LibreOffice compiled to WebAssembly, which requires `SharedArrayBuffer`. Browsers only allow `SharedArrayBuffer` on pages served over a secure context. Plain `http://` over a LAN IP does not qualify, but `https://` does — even with a self-signed certificate.
### What to do
Accept the warning once in your browser:
- **Firefox**: Click **Advanced…****Accept the Risk and Continue**
- **Chrome / Edge**: Click **Advanced****Proceed to … (unsafe)**
- **Safari**: Click **Show Details****visit this website**
You only need to do this once per browser. After accepting, the browser remembers the exception for this add-on.
### Is it actually safe?
Yes. The certificate secures the connection between **your browser and your own Home Assistant instance on your local network**. No data leaves your device — all PDF processing is done entirely in the browser. The warning exists only because the certificate was not signed by a global CA, not because anything malicious is happening.
## Configuration
| Option | Default | Description |
|--------|---------|-------------|
| `log_level` | `info` | Log verbosity: `info`, `debug`, `warn`, `error` |
## Support
For issues with the add-on packaging, open an issue at [github.com/ToledoEM/BentoPDF_HA_app](https://github.com/ToledoEM/BentoPDF_HA_app).
For issues with BentoPDF itself, visit [github.com/alam00000/bentopdf](https://github.com/alam00000/bentopdf).

39
bentopdf/Dockerfile Normal file
View File

@@ -0,0 +1,39 @@
# Global build args — must be declared before the first FROM to be usable in FROM instructions
ARG BUILD_FROM=ghcr.io/home-assistant/amd64-base:3.23
ARG BUILD_VERSION=2.5.0
# Stage 1: Download and extract the BentoPDF dist release (includes bundled WASM)
FROM alpine:3.21 AS dist
ARG BUILD_VERSION
# hadolint ignore=DL3018
RUN apk add --no-cache curl unzip \
&& curl -fsSL "https://github.com/alam00000/bentopdf/releases/download/v${BUILD_VERSION}/dist-${BUILD_VERSION}.zip" \
-o /tmp/bentopdf.zip \
&& unzip /tmp/bentopdf.zip -d /tmp/dist
# Stage 2: Final runtime image
FROM ${BUILD_FROM}
ARG BUILD_VERSION
# hadolint ignore=DL3018
RUN apk add --no-cache nginx openssl bash \
&& sed -i 's/nginx:x:100:101/nginx:x:0:0/' /etc/passwd \
&& sed -i 's/^nginx:x:101:/nginx:x:0:/' /etc/group
COPY --from=dist /tmp/dist/ /usr/share/nginx/html/
COPY run.sh /run.sh
COPY rootfs /
RUN chmod +x /run.sh \
&& chmod +x /etc/s6-overlay/s6-rc.d/bentopdf/run \
&& chmod +x /etc/s6-overlay/s6-rc.d/bentopdf/finish \
&& chmod +x /etc/cont-init.d/50-ssl.sh
LABEL \
io.hass.version="${BUILD_VERSION}" \
io.hass.type="addon" \
io.hass.arch="aarch64|amd64"
ENTRYPOINT ["/init"]

111
bentopdf/README.md Normal file
View File

@@ -0,0 +1,111 @@
# BentoPDF for Home Assistant
<div align="left">
<img src="logo.png" alt="BentoPDF" width="100" height="100"/>
</div>
A privacy-first PDF toolkit running entirely in your browser — no uploads, no cloud, no tracking. All processing happens locally via WebAssembly. This add-on serves the BentoPDF web app from your Home Assistant instance so you can access it from anywhere on your network.
---
## Features
### Organize & Edit
| Tool | Tool | Tool |
|------|------|------|
| Merge PDF | Split PDF | Organize PDF |
| Delete Pages | Extract Pages | Reverse Pages |
| Rotate PDF | Rotate Custom | Crop PDF |
| Add Blank Page | Divide Pages | N-Up PDF |
| Alternate Merge | Combine Single Page | PDF Booklet |
| PDF Merge & Split | Fix Page Size | |
### Convert TO PDF
| Tool | Tool | Tool |
|------|------|------|
| Word to PDF | Excel to PDF | PowerPoint to PDF |
| Image to PDF | JPG to PDF | PNG to PDF |
| BMP to PDF | TIFF to PDF | WEBP to PDF |
| HEIC to PDF | SVG to PDF | PSD to PDF |
| Markdown to PDF | HTML / Email to PDF | RTF to PDF |
| TXT to PDF | CSV to PDF | JSON to PDF |
| XML to PDF | ODT to PDF | ODS to PDF |
| ODP to PDF | ODG to PDF | EPUB to PDF |
| MOBI to PDF | FB2 to PDF | CBZ to PDF |
| XPS to PDF | VSD to PDF | PUB to PDF |
| WPS to PDF | WPD to PDF | Pages to PDF |
### Convert FROM PDF
| Tool | Tool | Tool |
|------|------|------|
| PDF to DOCX | PDF to Excel | PDF to JPG |
| PDF to PNG | PDF to BMP | PDF to TIFF |
| PDF to WEBP | PDF to SVG | PDF to Text |
| PDF to Markdown | PDF to JSON | PDF to CSV |
| PDF to PDF/A | PDF to ZIP | PDF to Greyscale |
### Security & Metadata
| Tool | Tool | Tool |
|------|------|------|
| Encrypt PDF | Decrypt PDF | Change Permissions |
| Remove Restrictions | Sign PDF | Digital Sign PDF |
| Validate Signature | Edit Metadata | View Metadata |
| Remove Metadata | Sanitize PDF | Flatten PDF |
| Remove Annotations | Repair PDF | |
### Enhance & Process
| Tool | Tool | Tool |
|------|------|------|
| Compress PDF | OCR PDF | Deskew PDF |
| Rasterize PDF | Linearize PDF | PDF to PDF/A |
| Adjust Colors | Invert Colors | Text Color |
| Background Color | Bates Numbering | Page Numbers |
| Header & Footer | Add Watermark | Add Stamps |
| Scanner Effect | Posterize PDF | Font to Outline |
| PDF Layers | Compare PDFs | Prepare for AI |
### Forms & More
| Tool | Tool | Tool |
|------|------|------|
| Form Creator | Form Filler | Table of Contents |
| Bookmark | PDF Editor | Extract Images |
| Extract Tables | Extract Attachments | Edit Attachments |
| Add Attachments | Page Dimensions | PDF Workflow |
---
## Installation
1. Go to **Settings → Add-ons → Add-on Store** in Home Assistant.
2. Open the menu (`···`) → **Repositories**.
3. Add: `https://github.com/ToledoEM/BentoPDF_HA_app`
4. Find **BentoPDF** in the store and install it.
5. Start the add-on — the web UI is available at `http://<your-HA-IP>:8080`.
---
## Configuration
| Option | Default | Description |
|--------|---------|-------------|
| `log_level` | `info` | Log verbosity: `info`, `debug`, `warn`, `error` |
No other configuration is needed. Drop your files in and go.
---
## Privacy
- All PDF processing runs **in-browser via WebAssembly** (PyMuPDF, Ghostscript, Tesseract, LibreOffice, CPDF)
- Files are **never uploaded** to any server — not even the one running this add-on
- No telemetry, no analytics, no external requests
- Works fully **offline** once loaded
---
## Support
- Add-on issues → [github.com/ToledoEM/BentoPDF_HA_app](https://github.com/ToledoEM/BentoPDF_HA_app/issues)
- BentoPDF upstream → [github.com/alam00000/bentopdf](https://github.com/alam00000/bentopdf)

42
bentopdf/apparmor.txt Normal file
View File

@@ -0,0 +1,42 @@
#include <tunables/global>
profile bentopdf flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
# Capabilities
file,
signal (send) set=(kill,term,int,hup,cont),
# S6-Overlay
/init ix,
/bin/** ix,
/usr/bin/** ix,
/run/{s6,s6-rc*,service}/** ix,
/package/** ix,
/command/** ix,
/etc/services.d/** rwix,
/etc/cont-init.d/** rwix,
/etc/cont-finish.d/** rwix,
/run/{,**} rwk,
/dev/tty rw,
# Bashio
/usr/lib/bashio/** ix,
/tmp/** rwk,
# App data
/data/** rw,
# nginx
/usr/sbin/nginx ix,
/etc/nginx/** r,
/usr/share/nginx/** r,
/var/lib/nginx/** rw,
/var/log/nginx/** rw,
/run/nginx/** rw,
# Deny dangerous kernel interfaces
deny /proc/kcore rwklx,
deny /proc/sysrq-trigger rwklx,
deny /sys/firmware/** rwklx,
}

4
bentopdf/build.yaml Normal file
View File

@@ -0,0 +1,4 @@
---
build_from:
aarch64: ghcr.io/home-assistant/aarch64-base:3.23
amd64: ghcr.io/home-assistant/amd64-base:3.23

31
bentopdf/config.yaml Normal file
View File

@@ -0,0 +1,31 @@
name: "BentoPDF"
slug: bentopdf
image: ghcr.io/alexbelgium/bentopdf-{arch}
description: "Privacy-first PDF toolkit. 50+ tools, all processing client-side in the browser. Files never leave your device."
version: "2.5.0"
url: "https://github.com/alexbelgium/hassio-addons/tree/master/bentopdf"
arch:
- amd64
- aarch64
startup: application
init: false
stage: stable
ports:
8080/tcp: 8080
8443/tcp: 8443
ports_description:
8080/tcp: "BentoPDF HTTP (watchdog / redirect to HTTPS)"
8443/tcp: "BentoPDF HTTPS Web UI"
webui: "https://[HOST]:[PORT:8443]"
watchdog: "http://[HOST]:[PORT:8080]/"
panel_icon: mdi:file-pdf-box
panel_title: BentoPDF
options:
log_level: "info"
schema:
log_level: list(info|debug|warn|error)

BIN
bentopdf/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
bentopdf/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,22 @@
#!/usr/bin/with-contenv bash
# shellcheck shell=bash
# Generate a self-signed TLS certificate for HTTPS access on LAN.
# The cert is stored under /config/bentopdf/ssl/ so it persists across restarts.
SSL_DIR=/config/bentopdf/ssl
CERT="${SSL_DIR}/cert.pem"
KEY="${SSL_DIR}/key.pem"
if [ ! -f "${CERT}" ] || [ ! -f "${KEY}" ]; then
echo "[50-ssl] Generating self-signed TLS certificate..."
mkdir -p "${SSL_DIR}"
openssl req -x509 -nodes -newkey rsa:2048 \
-keyout "${KEY}" \
-out "${CERT}" \
-days 3650 \
-subj "/CN=homeassistant.local" \
-addext "subjectAltName=DNS:homeassistant.local,DNS:localhost,IP:127.0.0.1"
echo "[50-ssl] Certificate generated at ${CERT}"
else
echo "[50-ssl] TLS certificate already exists, skipping generation."
fi

View File

@@ -0,0 +1,63 @@
daemon off;
pid /tmp/nginx.pid;
error_log /proc/1/fd/1 warn;
events {
worker_connections 1024;
}
http {
client_body_temp_path /tmp/client_body;
proxy_temp_path /tmp/proxy;
fastcgi_temp_path /tmp/fastcgi;
uwsgi_temp_path /tmp/uwsgi;
scgi_temp_path /tmp/scgi;
access_log /proc/1/fd/1;
include /etc/nginx/mime.types;
types {
application/javascript mjs;
}
default_type application/octet-stream;
gzip_static on;
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 9;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json application/wasm application/x-javascript text/x-component;
# HTTP on 8080 — redirect to HTTPS; root path returns 200 for HA watchdog
server {
listen 8080;
listen [::]:8080;
server_name localhost;
location = / {
return 200 "ok";
}
location / {
return 301 https://$host:8443$request_uri;
}
}
# HTTPS on 8443 — main browser access with SharedArrayBuffer support
server {
listen 8443 ssl;
listen [::]:8443 ssl;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
absolute_redirect off;
ssl_certificate /config/bentopdf/ssl/cert.pem;
ssl_certificate_key /config/bentopdf/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
include /etc/nginx/snippets/bentopdf-locations.conf;
}
}

View File

@@ -0,0 +1,86 @@
location ~ ^/(en|ar|be|da|de|es|fr|id|it|nl|pt|tr|vi|zh|zh-TW)(/.*)?$ {
try_files $uri $uri/ $uri.html /$1/index.html /index.html;
expires 5m;
add_header Cache-Control "public, must-revalidate";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
location ~ ^/(.+?)/(en|ar|be|da|de|es|fr|id|it|nl|pt|tr|vi|zh|zh-TW)(/.*)?$ {
try_files $uri $uri/ $uri.html /$1/$2/index.html /$1/index.html /index.html;
expires 5m;
add_header Cache-Control "public, must-revalidate";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
location ~* /libreoffice-wasm/soffice\.wasm\.gz$ {
gzip off;
types {} default_type application/wasm;
add_header Content-Encoding gzip;
add_header Vary "Accept-Encoding";
add_header Cache-Control "public, immutable";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
location ~* /libreoffice-wasm/soffice\.data\.gz$ {
gzip off;
types {} default_type application/octet-stream;
add_header Content-Encoding gzip;
add_header Vary "Accept-Encoding";
add_header Cache-Control "public, immutable";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
location ~* \.(wasm|wasm\.gz|data|data\.gz)$ {
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;
}
location ~* \.(js|mjs|css|woff|woff2|ttf|eot|otf)$ {
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;
}
location ~* \.(png|jpg|jpeg|gif|ico|svg|webp|avif|mp4|webm)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location ~* \.json$ {
expires 1w;
add_header Cache-Control "public, must-revalidate";
}
location ~* \.pdf$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
try_files $uri $uri/ $uri.html /index.html;
expires 5m;
add_header Cache-Control "public, must-revalidate";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Resource-Policy "cross-origin" always;

View File

@@ -0,0 +1,3 @@
#!/usr/bin/with-contenv bash
# shellcheck shell=bash
exit 0

View File

@@ -0,0 +1,3 @@
#!/usr/bin/with-contenv bash
# shellcheck shell=bash
exec /run.sh

View File

@@ -0,0 +1 @@
longrun

9
bentopdf/run.sh Normal file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
set -Eeuo pipefail
LOG_LEVEL=$(bashio::config 'log_level' 'info')
bashio::log.info "Starting BentoPDF (log_level=${LOG_LEVEL})"
exec nginx

View File

@@ -0,0 +1,7 @@
configuration:
log_level:
name: Log level
description: Controls the verbosity of add-on logging.
network:
8080/tcp: Web UI port (direct access, not used for Ingress)