From 5d68d92afa4a0c14b19d4a4d4ed7f7947b25b8e7 Mon Sep 17 00:00:00 2001 From: ToledoEM <8144940+ToledoEM@users.noreply.github.com> Date: Mon, 16 Mar 2026 12:15:36 +0000 Subject: [PATCH] add bentopdf --- bentopdf/CHANGELOG.md | 8 ++ bentopdf/DOCS.md | 53 +++++++++ bentopdf/Dockerfile | 39 ++++++ bentopdf/README.md | 111 ++++++++++++++++++ bentopdf/apparmor.txt | 42 +++++++ bentopdf/build.yaml | 4 + bentopdf/config.yaml | 31 +++++ bentopdf/icon.png | Bin 0 -> 11611 bytes bentopdf/logo.png | Bin 0 -> 11611 bytes bentopdf/rootfs/etc/cont-init.d/50-ssl.sh | 22 ++++ bentopdf/rootfs/etc/nginx/nginx.conf | 63 ++++++++++ .../nginx/snippets/bentopdf-locations.conf | 86 ++++++++++++++ .../s6-rc.d/bentopdf/dependencies.d/base | 0 .../etc/s6-overlay/s6-rc.d/bentopdf/finish | 3 + .../etc/s6-overlay/s6-rc.d/bentopdf/run | 3 + .../etc/s6-overlay/s6-rc.d/bentopdf/type | 1 + .../s6-rc.d/user/contents.d/bentopdf | 0 bentopdf/run.sh | 9 ++ bentopdf/translations/en.yaml | 7 ++ 19 files changed, 482 insertions(+) create mode 100644 bentopdf/CHANGELOG.md create mode 100644 bentopdf/DOCS.md create mode 100644 bentopdf/Dockerfile create mode 100644 bentopdf/README.md create mode 100644 bentopdf/apparmor.txt create mode 100644 bentopdf/build.yaml create mode 100644 bentopdf/config.yaml create mode 100644 bentopdf/icon.png create mode 100644 bentopdf/logo.png create mode 100644 bentopdf/rootfs/etc/cont-init.d/50-ssl.sh create mode 100644 bentopdf/rootfs/etc/nginx/nginx.conf create mode 100644 bentopdf/rootfs/etc/nginx/snippets/bentopdf-locations.conf create mode 100644 bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/dependencies.d/base create mode 100644 bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/finish create mode 100644 bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/run create mode 100644 bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/type create mode 100644 bentopdf/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/bentopdf create mode 100644 bentopdf/run.sh create mode 100644 bentopdf/translations/en.yaml diff --git a/bentopdf/CHANGELOG.md b/bentopdf/CHANGELOG.md new file mode 100644 index 000000000..3daec7555 --- /dev/null +++ b/bentopdf/CHANGELOG.md @@ -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 diff --git a/bentopdf/DOCS.md b/bentopdf/DOCS.md new file mode 100644 index 000000000..ab2a7a596 --- /dev/null +++ b/bentopdf/DOCS.md @@ -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://: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). diff --git a/bentopdf/Dockerfile b/bentopdf/Dockerfile new file mode 100644 index 000000000..7b62269e5 --- /dev/null +++ b/bentopdf/Dockerfile @@ -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"] diff --git a/bentopdf/README.md b/bentopdf/README.md new file mode 100644 index 000000000..9ab62dc29 --- /dev/null +++ b/bentopdf/README.md @@ -0,0 +1,111 @@ +# BentoPDF for Home Assistant + +
+ BentoPDF +
+ + + +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://: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) diff --git a/bentopdf/apparmor.txt b/bentopdf/apparmor.txt new file mode 100644 index 000000000..9d0b322f6 --- /dev/null +++ b/bentopdf/apparmor.txt @@ -0,0 +1,42 @@ +#include + +profile bentopdf flags=(attach_disconnected,mediate_deleted) { + #include + + # 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, +} diff --git a/bentopdf/build.yaml b/bentopdf/build.yaml new file mode 100644 index 000000000..3f1782577 --- /dev/null +++ b/bentopdf/build.yaml @@ -0,0 +1,4 @@ +--- +build_from: + aarch64: ghcr.io/home-assistant/aarch64-base:3.23 + amd64: ghcr.io/home-assistant/amd64-base:3.23 diff --git a/bentopdf/config.yaml b/bentopdf/config.yaml new file mode 100644 index 000000000..346e10942 --- /dev/null +++ b/bentopdf/config.yaml @@ -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) diff --git a/bentopdf/icon.png b/bentopdf/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5f5c3fb8f455a71d088ebb65204b98cf8bbc34e6 GIT binary patch literal 11611 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 Tmg=vi$G`_^MX3r2qoDr*0PM}- literal 0 HcmV?d00001 diff --git a/bentopdf/logo.png b/bentopdf/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..042c40575939e7f0aae49191343a3f52c17bf6cb GIT binary patch literal 11611 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 Tw$gYlJqF&(C`ngJ8VCOmQbEk{ literal 0 HcmV?d00001 diff --git a/bentopdf/rootfs/etc/cont-init.d/50-ssl.sh b/bentopdf/rootfs/etc/cont-init.d/50-ssl.sh new file mode 100644 index 000000000..b418c6ffb --- /dev/null +++ b/bentopdf/rootfs/etc/cont-init.d/50-ssl.sh @@ -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 diff --git a/bentopdf/rootfs/etc/nginx/nginx.conf b/bentopdf/rootfs/etc/nginx/nginx.conf new file mode 100644 index 000000000..804a9eeba --- /dev/null +++ b/bentopdf/rootfs/etc/nginx/nginx.conf @@ -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; + } +} diff --git a/bentopdf/rootfs/etc/nginx/snippets/bentopdf-locations.conf b/bentopdf/rootfs/etc/nginx/snippets/bentopdf-locations.conf new file mode 100644 index 000000000..e87ac145e --- /dev/null +++ b/bentopdf/rootfs/etc/nginx/snippets/bentopdf-locations.conf @@ -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; diff --git a/bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/dependencies.d/base b/bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/dependencies.d/base new file mode 100644 index 000000000..e69de29bb diff --git a/bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/finish b/bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/finish new file mode 100644 index 000000000..2c31af381 --- /dev/null +++ b/bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/finish @@ -0,0 +1,3 @@ +#!/usr/bin/with-contenv bash +# shellcheck shell=bash +exit 0 diff --git a/bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/run b/bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/run new file mode 100644 index 000000000..781859916 --- /dev/null +++ b/bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/run @@ -0,0 +1,3 @@ +#!/usr/bin/with-contenv bash +# shellcheck shell=bash +exec /run.sh diff --git a/bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/type b/bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/type new file mode 100644 index 000000000..5883cff0c --- /dev/null +++ b/bentopdf/rootfs/etc/s6-overlay/s6-rc.d/bentopdf/type @@ -0,0 +1 @@ +longrun diff --git a/bentopdf/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/bentopdf b/bentopdf/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/bentopdf new file mode 100644 index 000000000..e69de29bb diff --git a/bentopdf/run.sh b/bentopdf/run.sh new file mode 100644 index 000000000..076bda474 --- /dev/null +++ b/bentopdf/run.sh @@ -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 diff --git a/bentopdf/translations/en.yaml b/bentopdf/translations/en.yaml new file mode 100644 index 000000000..31b79a6af --- /dev/null +++ b/bentopdf/translations/en.yaml @@ -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)