Rich CLI + web dashboard for grabbing albums and files from Bunkr with resilient retries, live progress, and container-ready deployment.
If you find this project useful, please consider giving it a star on GitHub! β
- BunkrDownloader
- Dual experience: Python CLI (
downloader.py,main.py) or a polished web dashboard powered by FastAPI. - Liquid-glass UI π: macOS Sonoma / iOS 17 aesthetic with
backdrop-filtervibrancy, OKLCH-based color tokens, an Auto / Light / Dark appearance menu that follows your OS, and WCAG-AA contrast in both modes. - Realtime feedback: Rich terminal UI and a websocket + polling hybrid on the web keep progress/logs alive, even after restarts.
- Concurrent + reliable π: Per-job network context isolates parallel downloads; monotonic event IDs with a WS β polling mutex prevent duplicate or missed progress updates across reconnects; progress bars converge to 100% (no more 99% stalls); sandboxed download root, bounded in-memory event log, optional bearer-token auth, and TTL-scoped job reaper keep long-running containers safe.
- Maintenance detection π: Real-time status page checks detect server maintenance and apply intelligent retry strategies with longer delays or skip affected files. Item pages that say "Download unavailable β¦ maintenance" are recognised too, so affected files are logged to
session.logfor retry even when the status page itself is down. - Smart filtering: Include/ignore rules, disk-space guard, filename sanitisation, and album pagination handled automatically.
- Configurable storage: Point downloads to any folder (CLI
--custom-pathor web directory picker) with existing files skipped safely. - Container friendly: Multi-stage Docker image, docker-compose stack, and CI pipeline for publishing multi-arch images to GHCR.
Note/Advice: Use a VPN. Bunkr sometimes blocks your IP when you bulk download. While it gets unblocked after a while, its best to use this behind a VPN
v0.13.2 β September 2026
- Maintenance pages recognised: Bunkr answers HTTP 200 with a "Download unavailable β¦ server under maintenance" page for files whose host is down. Those items are now logged to
session.logas[MAINTENANCE]entries for later retry, raise the structured maintenance event in the web UI, and follow thebackoff/skipstrategy (re-fetching the item page after each delay) instead of vanishing as a generic "could not resolve" failure. Works even whenstatus.bunkr.ruitself is down. MAINTENANCE_BACKOFF_DELAYS_SECONDS: the 2/5/10-minute maintenance retry waits are now an env knob (default120,300,600, each clamped 1β3600), shared by the CDN-failure and item-page paths.
v0.13.1 β September 2026
- Quieter status-page failures: a slow or down
status.bunkr.runow logs one warning line instead of a full traceback per attempt; the fetch timeout is the newSTATUS_PAGE_TIMEOUT_SECONDSknob (default10, clamped 1β60). API_PORTis the real listen port: the container now starts uvicorn onAPI_PORT(default8000) instead of a hard-coded 8000, and both compose files mapAPI_PORT:API_PORT. Needed for running the app inside a VPN sidecar's network namespace, where the port must not collide with the sidecar's own services.
v0.13.0 β September 2026
- Run behind a VPN: new
docker-compose.vpn.ymlruns the app through a gluetun sidecar on Surfshark WireGuard with a kill switch, keeping the dashboard reachable on the LAN. Includes Synology DSM 7 Container Manager notes. See Running behind a VPN.
v0.12.0 β June 2026
- Update notifications in the web UI: the dashboard now checks GitHub for newer releases and shows an "β update available: vX.Y.Z" badge directly under the version when your running build is out of date, linking straight to the Releases page.
- Light on GitHub: the check runs server-side and is cached (~6h), so all browser sessions share a single upstream request instead of each polling GitHub β and a GitHub hiccup simply shows no badge rather than erroring.
v0.11.5 β June 2026
- Security maintenance: cleared the open frontend security advisories β axios bumped to 1.16.1 (
GHSA-654m-c8p4-x5fp, Proxy-Authorization header injection via prototype pollution) and the build-time postcss to 8.5.15 (GHSA-qx2v-qp2m-jg93, CSS-stringify XSS).npm auditnow reports zero vulnerabilities.
v0.11.4 β June 2026
- Downloads work again: Bunkr retired the
/api/vsendpoint (it now returns 404 for every file) and switched to signed CDN URLs β item pages embed the raw CDN link plus a signing endpoint that issues a short-lived access token. The crawler was rewritten to follow this new flow, restoring album and single-file downloads. - SSRF hardening: the signing endpoint host is read from page content, so it is now validated (HTTPS + host allowlist) before any server-side request is made. Override the allowlist with
BUNKR_SIGN_ALLOWED_HOSTS(defaultcdn.cr) if Bunkr rotates its signing infrastructure.
See the Releases page for the full changelog.
This is the easiest way to run BunkrDownloader. You'll need Docker Desktop installed on your machine (or Docker Engine + Compose on Linux).
-
Prepare configuration: Create a
.envfile to customize your settings.cp .env.sample .env
Windows users: You can copy and rename the file in File Explorer.
-
Customize (Optional): Open
.envin a text editor to change where files are saved (DOWNLOADS_DIR) or which port to use (API_PORT). -
Start the application: Run the following command to download the image and start the service in the background:
docker compose up -d
Once running, open http://localhost:8000 in your browser.
- Stop:
docker compose down - Updates: Run
docker compose pullfollowed bydocker compose up -dto switch to the latest version. - Logs:
docker compose logs -f(Ctrl+C to exit).
You can configure the deployment by setting the following environment variables in your .env file.
| Variable | Description | Default Value |
|---|---|---|
API_PORT |
Port the app listens on inside the container and the host port compose publishes (they are kept equal). | 8000 |
API_HOST |
Address uvicorn binds to inside the container. | 0.0.0.0 |
DOWNLOADS_DIR |
The local directory where files will be saved. | ./Downloads |
LOGS_DIR |
The local directory where logs will be stored. | ./logs |
SESSION_LOG_PATH |
The internal path for the session log. | /app/logs/session.log |
IMAGE_TAG |
The Docker image version tag to use (e.g., latest or 1.2.3). |
latest |
UID |
The user ID to run the container as. | 1000 |
GID |
The group ID to run the container as. | 1000 |
STATUS_CHECK_ON_FAILURE π |
Enable real-time status page checks on download failures. | true |
STATUS_CACHE_TTL_SECONDS π |
Cache duration for status page results in seconds. | 60 |
STATUS_PAGE_TIMEOUT_SECONDS π |
Seconds to wait for the Bunkr status page on job start and failure re-checks (clamped 1β60). A failed fetch is logged as a single warning and the job continues without host data. | 10 |
MAINTENANCE_RETRY_STRATEGY π |
Strategy for maintenance: backoff (retry with delays) or skip (log and skip). |
backoff |
MAINTENANCE_BACKOFF_DELAYS_SECONDS π |
Comma-separated waits between maintenance retries, one per retry, each clamped 1β3600. | 120,300,600 |
ALLOWED_DOWNLOAD_ROOT π |
Filesystem root that incoming custom_path and /api/directories?basePath values must resolve under. Rejects any path that escapes this root with HTTP 422. Set to / to disable sandboxing (not recommended for public-facing deployments). |
<cwd>/Downloads |
API_ACCESS_TOKEN π |
Shared bearer token. When set, every /api/* request must carry Authorization: Bearer <token> and every /ws/* connection must include ?token=<token>. When unset, the API is unauthenticated and a warning is logged on startup β safe only on a trusted LAN. |
(unset) |
ALLOWED_ORIGINS π |
Comma-separated list of CORS-allowed origins (e.g. https://dash.example.com,https://admin.example.com). Takes precedence over ALLOWED_ORIGIN_REGEX when set. |
(unset) |
ALLOWED_ORIGIN_REGEX π |
Regex fallback for CORS when ALLOWED_ORIGINS is unset. The default covers Vite dev-server and uvicorn on localhost. |
https?://(localhost|127\.0\.0\.1)(:\d+)? |
JOB_EVENT_RETENTION π |
Maximum number of events retained per job in the in-memory ring buffer. Clients whose cursor falls below the retained floor get a 410 Gone so they can reset rather than silently missing history. |
2000 |
JOB_TTL_HOURS π |
Terminal jobs (completed / failed / cancelled) older than this are evicted by the background reaper so long-running containers don't grow unbounded. | 24 |
JOB_REAPER_INTERVAL_SECONDS π |
How often the reaper scans for stale terminal jobs. | 900 |
BUNKR_SIGN_ALLOWED_HOSTS π |
Comma-separated allowlist of hosts the page-declared media signing endpoint may use (SSRF guard). A candidate matches when it equals an entry or is a subdomain of one, and only HTTPS is accepted. Override when Bunkr rotates its signing infrastructure off cdn.cr. |
cdn.cr |
Set IMAGE_TAG to a published semantic version (for example 1.2.3) if you want to pin a specific release; otherwise latest is used.
# Backend
pip install -r requirements.txt
uvicorn src.web.app:app --reload
# Frontend (optional live dev server)
cd frontend
npm install
npm run devThe Vite dev server proxies API/WebSocket traffic to http://localhost:8000 by default. Run npm run build once to bake a production bundle served by FastAPI.
The README's advice to use a VPN can be baked into the stack: docker-compose.vpn.yml runs the app behind a gluetun sidecar connected to Surfshark over WireGuard. Every byte the downloader sends leaves through the tunnel, gluetun's firewall acts as a kill switch if the VPN drops, and the web UI stays reachable on your LAN.
Use the file on its own (it is not an override of docker-compose.yml):
docker compose -f docker-compose.vpn.yml up -d- Get WireGuard credentials from Surfshark: in your account go to VPN β Manual setup β Desktop or mobile β WireGuard, choose I don't have a keypair, generate one and copy the private key. Download a config file for any location and copy its
Addressline (e.g.10.64.222.21/16); it is the same for every server. - Put them in
.envasWIREGUARD_PRIVATE_KEYandWIREGUARD_ADDRESSES. Optionally setVPN_SERVER_COUNTRIES(defaultNetherlands) andTZ. - Start with the command above, then confirm the tunnel:
docker exec bunkr wget -qO- https://api.ipify.orgshould print a Surfshark address, andhttp://<host>:${API_PORT}should serve the dashboard.
| Variable | Description | Default |
|---|---|---|
WIREGUARD_PRIVATE_KEY |
Surfshark WireGuard private key (required). | (unset) |
WIREGUARD_ADDRESSES |
The Address from a Surfshark WireGuard config (required). |
(unset) |
VPN_SERVER_COUNTRIES |
Exit country, passed to gluetun SERVER_COUNTRIES. |
Netherlands |
WIREGUARD_IMPLEMENTATION |
auto, kernelspace or userspace. auto falls back to userspace where the kernel has no WireGuard module. |
auto |
WIREGUARD_GSO |
Set to off if gluetun logs write /dev/net/tun: invalid argument (some NAS kernels). |
on |
Synology DSM 7 (Container Manager): create a Project, paste docker-compose.vpn.yml as the compose file and add the .env next to it. Set UID/GID to your DSM user (see id <user> over SSH) and point DOWNLOADS_DIR/LOGS_DIR at shared-folder paths such as /volume1/Bunkr-Downloads. The DSM kernel has no WireGuard module, so gluetun uses its userspace implementation automatically. If gluetun fails with /dev/net/tun missing, load the module once over SSH with sudo insmod /lib/modules/tun.ko and start the project again.
The bunkr service in docker-compose.vpn.yml mirrors the one in docker-compose.yml; when the base file changes, update both. Because both containers share one network namespace, gluetun's control server is moved from its default :8000 to 127.0.0.1:8001 so it does not collide with the app; if you edit the file by hand, keep that line.
# Single URL
python3 downloader.py <bunkr_url> [--include term ...] [--ignore term ...] [--custom-path /path] [--disable-ui] [--disable-disk-check] [--skip-status-check] [--maintenance-strategy backoff|skip] [--status-cache-ttl SECONDS]
# Batch mode (URLs.txt)
python3 main.py [shared flags]--includedownloads files containing any supplied substring.--ignoreskips files containing any supplied substring.--custom-pathpoints downloads to<path>/Downloads.--disable-uiswaps the Rich interface for plain logging (useful in notebooks/CI).--skip-status-checkπ disables real-time status page checks on download failures (defaults to enabled).--status-cache-ttl SECONDSπ controls cache duration for status page results (default: 60s).--maintenance-strategy backoff|skipπ chooses retry behavior:backoffretries with longer delays (2min, 5min, 10min),skiplogs and skips maintenance files.
- Job launcher β paste URLs, set filters, toggle the disk check, or choose a custom destination via the directory browser.
- Progress panes β overall progress + per-file stripes; tooltips explain each control and metric.
- Live log β chronological events, retries, and skips with full timestamps.
- Resilient updates β when a WebSocket drops (e.g. container restart) the UI polls
/api/downloads/{job}/eventsuntil the socket reconnects. - Source shortcut β in-app link to the GitHub repository for quick reference.
- Version badge β header shows the semantic version embedded in the running container image.
.env(tracked example) controls container defaults:API_HOST,API_PORT,DOWNLOADS_DIR, plus Vite proxy hints (VITE_*).- Web UI tooltips describe every form element; hover to see accepted formats and side effects.
- Downloads default to
Downloads/in the working directory unlesscustom_path(CLI) or the directory picker overrides it. session.logpersists problematic URLs so you can retry them later. π Maintenance events are logged with format:[MAINTENANCE] timestamp | subdomain | status | url.
downloader.py/main.pycallvalidate_and_download, which builds aSessionInfoand streams progress viaLiveManager.src/web/app.pywraps the same flow:JobEventBrokerbuffers events,WebLiveManagermirrors CLI progress/log calls, and FastAPI exposes REST + WebSocket endpoints.src/crawlers/*resolve album pagination, decrypt media URLs, and normalise filenames.src/downloaders/*handle concurrency, retries, and chunked writes throughdownload_utils.save_file_with_progress; subdomain outages are tracked withbunkr_utils.- π Maintenance detection:
bunkr_utils.refresh_server_statuschecks Bunkr's status page on download failures with TTL-based caching. Failed downloads are grouped by subdomain and rechecked before final retries. frontend/src/App.jsxconsumes/api/downloads,/api/directories,/ws/jobs/{id}, and the/api/downloads/{id}/eventspolling fallback for seamless updates. π Maintenance events trigger toast notifications.
- Python β₯ 3.10, Node β₯ 18 recommended.
- Activate the virtualenv and lint with
python -m pylint $(git ls-files '*.py'); resolve any findings before committing. - Run
python -m compileall srcandnpm run buildbefore opening a PR to catch syntax/bundle issues. docker compose up --buildexercises the full stack locally using the tracked.env.- Use
session.logand the web log pane to inspect failed URLs or storage issues.
- Start with the guidelines in
CONTRIBUTING.mdfor branching, linting, and smoke-test expectations. - Prefer Conventional Commits (
feat:,fix:,docs:, etc.) so semantic-release can generate changelogs correctly. - Include the manual checks you ran (lint,
compileall,npm run build, CLI/Web smoke tests) in your pull request description. - Keep your branch rebased on
mainand respond quickly to review feedback to keep the release pipeline flowing.
- Open a new issue from the repositoryβs Issues β New issue page; choose the Bug report template for defects or the Feature request template for enhancements.
- The templates collect environment details (OS, Python version, tool version) and walk you through logs, reproduction steps, and desired outcomes so maintainers can triage quickly.
- Always confirm youβre running the latest release from GitHub Releases and search for duplicates before filing.
- Blank issues are disabled, so pick the template thatβs closest to your situation and use the βAdditional contextβ field to capture anything extra.
This project is a fork of Lysagxra/BunkrDownloader. However, it has been heavily refactored and modified for a web dashboard interface and other enhancements including dockerizing the application.
MIT License Β© tekgnosis-net
