Local or self-hosted web app for looking up and downloading Brillux Praxismerkblätter.
  • Python 97.7%
  • Dockerfile 2.3%
Find a file
Keno Teppris ae87270b31
All checks were successful
Deploy / Trigger Portainer redeploy (push) Successful in 10s
Side pannel to 240px
2026-04-11 23:10:55 +02:00
.forgejo/workflows ci: switch to Portainer webhook deploy 2026-04-11 22:32:21 +02:00
.vscode FastAPI startpoint 2024-07-21 00:12:06 +02:00
.dockerignore initial: full rewrite with CI/CD 2026-04-11 21:59:10 +02:00
.env.example initial: full rewrite with CI/CD 2026-04-11 21:59:10 +02:00
.gitignore initial: full rewrite with CI/CD 2026-04-11 21:59:10 +02:00
app.py Side pannel to 240px 2026-04-11 23:10:55 +02:00
cache.py initial: full rewrite with CI/CD 2026-04-11 21:59:10 +02:00
config.py initial: full rewrite with CI/CD 2026-04-11 21:59:10 +02:00
config.yaml initial: full rewrite with CI/CD 2026-04-11 21:59:10 +02:00
docker-compose.yml fix: remove image tag, build-only for local deployment 2026-04-11 22:30:35 +02:00
Dockerfile initial: full rewrite with CI/CD 2026-04-11 21:59:10 +02:00
downloader.py initial: full rewrite with CI/CD 2026-04-11 21:59:10 +02:00
logger.py initial: full rewrite with CI/CD 2026-04-11 21:59:10 +02:00
mailer.py initial: full rewrite with CI/CD 2026-04-11 21:59:10 +02:00
models.py initial: full rewrite with CI/CD 2026-04-11 21:59:10 +02:00
pyproject.toml fix: pin numpy<2 for X86_V2 CPU compatibility on OMV 2026-04-11 22:45:25 +02:00
README.md initial: full rewrite with CI/CD 2026-04-11 21:59:10 +02:00
search_provider.py initial: full rewrite with CI/CD 2026-04-11 21:59:10 +02:00
uv.lock fix: pin numpy<2 for X86_V2 CPU compatibility on OMV 2026-04-11 22:45:25 +02:00

BxPMLoader

Local or self-hosted web app for looking up and downloading Brillux Praxismerkblätter (technical product PDFs) by product code.

Built for a single user — no bulk crawling, no accounts required.


Features

  • Search by product code (e.g. 3100) or paste a name containing the code
  • Direct URL probe for instant exact matches — no scraping for known codes
  • Real product name + first-page thumbnail extracted from the PDF
  • Local SQLite cache with configurable TTL (default 30 days)
  • Download cart: add products, download individually, as ZIP, or send by e-mail
  • SMTP e-mail sending + iOS Web Share (native share sheet / Mail app on iPad)
  • Persistent PDF storage in data/pdfs/ — never auto-deleted

Local development

Requires Python 3.11+ and uv.

uv sync
cp .env.example .env   # fill in SMTP credentials
uv run streamlit run app.py

Opens at http://localhost:8501.


Project structure

bxpmloader/
├── app.py               # Streamlit UI
├── search_provider.py   # SearchProvider interface + BrilluxSearchProvider
├── downloader.py        # PDF download, name extraction, thumbnail rendering
├── mailer.py            # SMTP e-mail sender
├── cache.py             # SQLite cache with TTL
├── models.py            # ProductMatch, DocumentInfo dataclasses
├── config.py            # Config loaded from config.yaml
├── logger.py            # Logging to console + data/logs/
├── config.yaml          # User-editable settings
├── pyproject.toml       # Dependencies (uv)
├── Dockerfile           # Container image
├── docker-compose.yml   # Production stack
├── .env.example         # Secrets template
└── .forgejo/workflows/
    └── deploy.yml       # CI/CD pipeline (Forgejo Actions)

Configuration

config.yaml controls runtime behaviour:

cache_ttl_days: 30       # How long search results stay cached
data_dir: "data"         # PDFs, cache DB, logs
request_delay: 1.0       # Seconds between requests
request_timeout: 15
max_retries: 2

SMTP credentials live in .env (see .env.example). Never commit the real .env.


Self-hosted deployment (Forgejo CI → Docker on OMV)

Overview

Push to main
    │
    ▼
Forgejo Actions (runner LXC 106)
    │  docker build + push
    ▼
Forgejo Container Registry
    │  SSH → docker compose pull + up -d
    ▼
OMV Docker host (LXC 103 / 192.168.178.56)
    │  reverse proxy
    ▼
nginx-proxy-manager → bxpmloader.teppris.duckdns.org

Step 1 — Create the Forgejo repository

  1. Log into Forgejo → New repositorykeno/bxpmloader
  2. Push this code:
    git remote add origin https://git.teppris.duckdns.org/keno/bxpmloader.git
    git push -u origin main
    

Step 2 — Enable the Forgejo container registry

Forgejo has a built-in OCI container registry — no extra setup needed.
Images are stored at git.teppris.duckdns.org/keno/bxpmloader.


Step 3 — Configure repository secrets

Go to the repo → Settings → Secrets and variables → Actions and add:

Secret Value
REGISTRY_TOKEN A Forgejo API token with package:write scope
(Profile → Settings → Applications → Generate token)
REGISTRY_USER Your Forgejo username (keno)
DEPLOY_HOST OMV IP: 192.168.178.56
DEPLOY_USER SSH user on OMV (e.g. root or a dedicated deploy user)
DEPLOY_SSH_KEY Private SSH key whose public key is in ~/.ssh/authorized_keys on OMV

Generating the deploy SSH key (do this once, on your desktop):

ssh-keygen -t ed25519 -C "forgejo-deploy" -f ~/.ssh/forgejo_deploy -N ""
# Add the public key to OMV:
ssh-copy-id -i ~/.ssh/forgejo_deploy.pub root@192.168.178.56
# Paste the private key (cat ~/.ssh/forgejo_deploy) into the DEPLOY_SSH_KEY secret

Step 4 — Prepare the server (OMV)

Run once on OMV:

mkdir -p /opt/bxpmloader
cp .env.example /opt/bxpmloader/.env
nano /opt/bxpmloader/.env        # fill in real SMTP credentials

# docker-compose.yml is pulled from git and used by CI directly
# OR copy it manually once:
scp docker-compose.yml root@192.168.178.56:/opt/bxpmloader/

# Log in to the Forgejo registry so OMV can pull images:
docker login git.teppris.duckdns.org

Step 5 — First deployment

Push to main (or trigger manually in Forgejo Actions).
The workflow will:

  1. Build the Docker image on the runner
  2. Push to the Forgejo container registry
  3. SSH into OMV, pull the new image, restart the container

After ~2 minutes the app is live at http://192.168.178.56:8085.


Step 6 — Reverse proxy with nginx-proxy-manager

In Portainer / nginx-proxy-manager on OMV:

  1. Add Proxy Host
    • Domain: bxpmloader.teppris.duckdns.org
    • Scheme: http
    • Forward hostname: 192.168.178.56 (or the container name bxpmloader-web)
    • Forward port: 8085
  2. Enable SSL via Let's Encrypt (already configured for your duckdns domain)

The app is then reachable at https://bxpmloader.teppris.duckdns.org from anywhere on Tailscale or via your duckdns domain.


Updating the app

Any push to main triggers the full pipeline automatically — build, push, redeploy. No manual steps needed.


Adjusting the web search fallback

If the direct URL probe fails for a code that should exist:

  1. Open search_provider.py and find # ADJUST_ME
  2. Visit https://www.brillux.de, search for the product, inspect the HTML
  3. Update BRILLUX_SEARCH_URL and the CSS selectors in _parse_search_results()

The direct URL pattern (pm{code}.pdf) handles the vast majority of lookups.


TODO

  • Add other document types (Sicherheitsdatenblatt, Technisches Merkblatt) from product detail pages
  • Tune web search selectors once Brillux page structure is confirmed
  • Multi-product batch search (paste a list of codes)
  • k8s manifests for when k3s is ready (Phase 2 of homelab modernisation)

Compliance notice

This tool performs only user-triggered, single-product lookups on publicly accessible pages. It does not bulk-crawl or scrape brillux.de.

Verify that your usage is permitted under Brillux's terms of service before operational use.