{"id":47768436,"url":"https://github.com/GeiserX/duplicacy-exporter","last_synced_at":"2026-04-18T12:01:00.873Z","repository":{"id":336900524,"uuid":"1151587385","full_name":"GeiserX/duplicacy-exporter","owner":"GeiserX","description":"Real-time Prometheus exporter for Duplicacy backups with live speed, progress, and completion metrics","archived":false,"fork":false,"pushed_at":"2026-04-10T18:32:56.000Z","size":82,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-04-10T19:28:19.937Z","etag":null,"topics":["alerting","backup","dashboard","data-protection","devops","docker","docker-compose","duplicacy","exporter","grafana","homelab","metrics","monitoring","observability","prometheus","prometheus-exporter","python","self-hosted","time-series","webhook"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GeiserX.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"geiserx","patreon":"geiser","buy_me_a_coffee":"geiser","thanks_dev":"u/gh/geiserx"}},"created_at":"2026-02-06T16:47:31.000Z","updated_at":"2026-04-10T18:32:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/GeiserX/duplicacy-exporter","commit_stats":null,"previous_names":["geiserx/duplicacy-exporter"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/GeiserX/duplicacy-exporter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeiserX%2Fduplicacy-exporter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeiserX%2Fduplicacy-exporter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeiserX%2Fduplicacy-exporter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeiserX%2Fduplicacy-exporter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GeiserX","download_url":"https://codeload.github.com/GeiserX/duplicacy-exporter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeiserX%2Fduplicacy-exporter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31967993,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"online","status_checked_at":"2026-04-18T02:00:07.018Z","response_time":103,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alerting","backup","dashboard","data-protection","devops","docker","docker-compose","duplicacy","exporter","grafana","homelab","metrics","monitoring","observability","prometheus","prometheus-exporter","python","self-hosted","time-series","webhook"],"created_at":"2026-04-03T08:00:39.329Z","updated_at":"2026-04-18T12:01:00.866Z","avatar_url":"https://github.com/GeiserX.png","language":"Python","funding_links":["https://github.com/sponsors/geiserx","https://patreon.com/geiser","https://buymeacoffee.com/geiser","https://thanks.dev/u/gh/geiserx"],"categories":["3. Collect"],"sub_categories":["Metrics"],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/GeiserX/duplicacy-exporter/main/docs/images/banner.svg\" alt=\"duplicacy-exporter banner\" width=\"900\"/\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003ePrometheus exporter for \u003ca href=\"https://duplicacy.com\"\u003eDuplicacy\u003c/a\u003e backup metrics -- real-time progress, speed, and post-run summaries for your Grafana dashboards.\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://pypi.org/project/duplicacy-exporter/\"\u003e\u003cimg src=\"https://img.shields.io/pypi/v/duplicacy-exporter?style=flat-square\u0026logo=python\u0026logoColor=white\u0026label=PyPI\" alt=\"PyPI\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/GeiserX/duplicacy-exporter/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/GeiserX/duplicacy-exporter?style=flat-square\u0026color=E6522C\" alt=\"GitHub Release\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://hub.docker.com/r/drumsergio/duplicacy-exporter\"\u003e\u003cimg src=\"https://img.shields.io/docker/v/drumsergio/duplicacy-exporter?sort=semver\u0026style=flat-square\u0026logo=docker\u0026label=Docker%20Hub\" alt=\"Docker Hub\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://grafana.com/grafana/dashboards/25089\"\u003e\u003cimg src=\"https://img.shields.io/badge/Grafana-Dashboard%2025089-F46800?style=flat-square\u0026logo=grafana\u0026logoColor=white\" alt=\"Grafana Dashboard\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/GeiserX/duplicacy-exporter/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/GeiserX/duplicacy-exporter?style=flat-square\" alt=\"License\"\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/python-3.13-3776AB?style=flat-square\u0026logo=python\u0026logoColor=white\" alt=\"Python 3.13\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/image%20size-~30MB-green?style=flat-square\" alt=\"Image Size\"\u003e\n  \u003ca href=\"https://codecov.io/gh/GeiserX/duplicacy-exporter\"\u003e\u003cimg src=\"https://img.shields.io/codecov/c/github/GeiserX/duplicacy-exporter?style=flat-square\" alt=\"Coverage\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Overview\n\n**duplicacy-exporter** bridges [Duplicacy](https://duplicacy.com) backups and [Prometheus](https://prometheus.io), giving you full observability over backup operations. It works with both **Duplicacy CLI** (via log tailing) and **Duplicacy Web UI** (via webhook), exposing metrics that Prometheus scrapes and Grafana visualizes.\n\n### Key capabilities\n\n- **Real-time metrics** -- backup speed (bytes/sec), progress (0-100%), chunks uploaded/skipped, updated per chunk\n- **Post-run summaries** -- duration, file counts, bytes uploaded, exit codes, revision numbers\n- **Prune tracking** -- monitors prune operations with completion timestamps\n- **Two collection modes** -- `log_tail` for CLI users, `webhook` for Web UI users\n- **Smart label resolution** -- automatic snapshot ID, storage target, and machine name detection from logs\n- **Storage host mapping** -- translates IPs and Tailscale FQDNs into human-readable names\n- **Lightweight** -- single Python file, one dependency (`prometheus_client`), Alpine image (~30 MB)\n\n## Architecture\n\n```\n+---------------------+         +----------------------+         +------------+\n|                     |  logs   |                      | scrape  |            |\n|  Duplicacy CLI      +--------\u003e+  duplicacy-exporter  +\u003c--------+ Prometheus |\n|  (Docker / file)    |  tail   |                      |  :9750  |            |\n+---------------------+         +----------+-----------+         +------+-----+\n                                           |                            |\n+---------------------+  POST   |          |                            |\n|  Duplicacy Web UI   +--------\u003e+  /webhook endpoint   |         +------v-----+\n|  (report_url)       |         |                      |         |  Grafana   |\n+---------------------+         +----------------------+         +------------+\n```\n\n**Log tail mode** connects to the Docker Engine API over a Unix socket (or tails a log file) and parses Duplicacy output line-by-line. It extracts chunk-level progress in real time and summary statistics at completion.\n\n**Webhook mode** receives JSON payloads from Duplicacy Web UI's `report_url` setting, extracting the same summary metrics without needing Docker socket access.\n\n## Quick Start\n\n### Docker Compose -- Log Tail Mode (recommended for CLI)\n\nDeploy alongside your Duplicacy CLI container:\n\n```yaml\nservices:\n  duplicacy-exporter:\n    image: drumsergio/duplicacy-exporter:0.3.4\n    container_name: duplicacy-exporter\n    restart: unless-stopped\n    environment:\n      - MODE=log_tail\n      - DOCKER_CONTAINER_NAME=duplicacy-cli-cron\n      - LISTEN_PORT=9750\n    volumes:\n      - /var/run/docker.sock:/var/run/docker.sock:ro\n    ports:\n      - \"9750:9750\"\n```\n\n### Docker Compose -- Webhook Mode (for Web UI)\n\n```yaml\nservices:\n  duplicacy-exporter:\n    image: drumsergio/duplicacy-exporter:0.3.4\n    container_name: duplicacy-exporter\n    restart: unless-stopped\n    environment:\n      - MODE=webhook\n      - LISTEN_PORT=9750\n    ports:\n      - \"9750:9750\"\n```\n\nThen set `report_url` in Duplicacy Web UI to: `http://duplicacy-exporter:9750/webhook`\n\n### Docker Compose -- Log File Mode\n\nIf you write Duplicacy logs to a file instead of using Docker:\n\n```yaml\nservices:\n  duplicacy-exporter:\n    image: drumsergio/duplicacy-exporter:0.3.4\n    container_name: duplicacy-exporter\n    restart: unless-stopped\n    environment:\n      - MODE=log_tail\n      - LOG_FILE=/logs/duplicacy.log\n      - LISTEN_PORT=9750\n    volumes:\n      - /path/to/duplicacy/logs:/logs:ro\n    ports:\n      - \"9750:9750\"\n```\n\n## Configuration\n\nAll configuration is done through environment variables:\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `MODE` | `log_tail` | Collection mode: `log_tail` or `webhook` |\n| `DOCKER_CONTAINER_NAME` | `duplicacy-cli-cron` | Container name to tail logs from (log_tail mode) |\n| `LOG_FILE` | _(empty)_ | Path to log file; alternative to Docker socket (log_tail mode) |\n| `LISTEN_PORT` | `9750` | Port for the metrics and webhook HTTP server |\n| `WEBHOOK_PATH` | `/webhook` | Path for the webhook POST endpoint (webhook mode) |\n| `MACHINE_NAME` | _(empty)_ | Machine name label; auto-detected from logs if not set |\n| `TAILSCALE_DOMAIN` | `mango-alpha.ts.net` | Tailscale domain suffix to strip from storage URLs |\n| `STORAGE_HOST_MAP` | _(empty)_ | JSON object mapping hostname/IP to display name |\n| `REPLAY_HOURS` | `25` | Hours of Docker log history to replay on startup |\n| `LOG_LEVEL` | `INFO` | Logging verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR` |\n\n### Storage host mapping example\n\nMap raw IPs or hostnames to friendly names:\n\n```bash\nSTORAGE_HOST_MAP='{\"192.168.10.100\":\"watchtower\",\"192.168.20.5\":\"geiserct\"}'\n```\n\n## Metrics\n\nAll backup metrics carry labels: `snapshot_id`, `storage_target`, `machine`.\nAll prune metrics carry labels: `storage_target`, `machine`.\n\n### Real-time (updated per chunk during backup)\n\n| Metric | Type | Description |\n|--------|------|-------------|\n| `duplicacy_backup_running` | Gauge | 1 if backup is in progress, 0 otherwise |\n| `duplicacy_backup_speed_bytes_per_second` | Gauge | Current backup speed |\n| `duplicacy_backup_progress_ratio` | Gauge | Progress from 0.0 to 1.0 |\n| `duplicacy_backup_chunks_uploaded` | Gauge | Chunks uploaded in current run |\n| `duplicacy_backup_chunks_skipped` | Gauge | Chunks skipped in current run |\n\n### Post-run summary\n\n| Metric | Type | Description |\n|--------|------|-------------|\n| `duplicacy_backup_last_success_timestamp_seconds` | Gauge | Unix timestamp of last successful backup |\n| `duplicacy_backup_last_duration_seconds` | Gauge | Duration of last backup in seconds |\n| `duplicacy_backup_last_files_total` | Gauge | Total files in last backup |\n| `duplicacy_backup_last_files_new` | Gauge | New files in last backup |\n| `duplicacy_backup_last_bytes_uploaded` | Gauge | Bytes uploaded in last backup |\n| `duplicacy_backup_last_bytes_new` | Gauge | New bytes in last backup |\n| `duplicacy_backup_last_chunks_new` | Gauge | New chunks in last backup |\n| `duplicacy_backup_last_exit_code` | Gauge | Exit code: 0 = success, 1 = failure |\n| `duplicacy_backup_last_revision` | Gauge | Revision number of last backup |\n| `duplicacy_backup_bytes_uploaded_total` | Counter | Cumulative bytes uploaded across all runs |\n\n### Prune\n\n| Metric | Type | Description |\n|--------|------|-------------|\n| `duplicacy_prune_running` | Gauge | 1 if prune is in progress |\n| `duplicacy_prune_last_success_timestamp_seconds` | Gauge | Unix timestamp of last successful prune |\n\n## Endpoints\n\n| Path | Method | Description |\n|------|--------|-------------|\n| `/metrics` | GET | Prometheus metrics endpoint |\n| `/webhook` | POST | Duplicacy Web UI report endpoint |\n| `/health` | GET | Health check (returns `200 OK`) |\n\n## Prometheus Configuration\n\nAdd the exporter as a scrape target:\n\n```yaml\nscrape_configs:\n  - job_name: \"duplicacy\"\n    static_configs:\n      - targets: [\"duplicacy-exporter:9750\"]\n        labels:\n          instance: \"my-server\"\n```\n\n### Example alerting rule\n\n```yaml\ngroups:\n  - name: duplicacy\n    rules:\n      - alert: DuplicacyBackupFailed\n        expr: duplicacy_backup_last_exit_code != 0\n        for: 5m\n        labels:\n          severity: warning\n        annotations:\n          summary: \"Duplicacy backup failed for {{ $labels.snapshot_id }}\"\n\n      - alert: DuplicacyBackupStale\n        expr: time() - duplicacy_backup_last_success_timestamp_seconds \u003e 86400\n        for: 1h\n        labels:\n          severity: critical\n        annotations:\n          summary: \"No successful Duplicacy backup in 24h for {{ $labels.snapshot_id }}\"\n```\n\n## Grafana Dashboard\n\nA ready-to-import dashboard is included in [`dashboard.json`](dashboard.json) and published on [Grafana.com (#25089)](https://grafana.com/grafana/dashboards/25089).\n\nImport it in Grafana via **Dashboards → Import → Upload JSON file** or use the dashboard ID `25089`.\n\n## Troubleshooting\n\n### Exporter starts but no metrics appear\n\n- **Log tail mode**: Verify the Docker socket is mounted (`/var/run/docker.sock:/var/run/docker.sock:ro`) and the `DOCKER_CONTAINER_NAME` matches your Duplicacy container exactly.\n- **Log file mode**: Confirm the log file path is correct and the volume mount provides read access.\n- **Webhook mode**: Ensure `report_url` in Duplicacy Web UI points to `http://\u003cexporter-host\u003e:9750/webhook`. The exporter must be reachable from the Web UI container.\n\n### Metrics show but labels are wrong or missing\n\n- Set `LOG_LEVEL=DEBUG` to see how each log line is parsed and which labels are resolved.\n- If storage targets show as raw IPs, use `STORAGE_HOST_MAP` to map them to friendly names.\n- If machine name is missing, set `MACHINE_NAME` explicitly.\n\n### Docker socket permission denied\n\nThe exporter process runs as root inside the container by default. If you run it as a non-root user, ensure the user has access to the Docker socket (typically group `docker`, GID 999 or similar).\n\n### Webhook returns 404\n\nVerify the `WEBHOOK_PATH` environment variable matches the path you configured in Duplicacy Web UI. The default is `/webhook`.\n\n## Related Projects\n\n- [`duplicacy-container`](https://github.com/GeiserX/duplicacy-container) -- Runtime image and Helm chart for the Kubernetes Duplicacy stack\n- [`duplicacy-cli-cron`](https://github.com/GeiserX/duplicacy-cli-cron) -- Scripts, wrappers, and backup recipes for Duplicacy CLI\n- [Duplicacy](https://duplicacy.com) -- Lock-free deduplication cloud backup tool\n- [Prometheus](https://prometheus.io) -- Monitoring and alerting toolkit\n- [Grafana](https://grafana.com) -- Observability and visualization platform\n- [awesome-prometheus](https://github.com/roaldnefs/awesome-prometheus) -- Curated list of Prometheus resources\n- [prometheus_client (Python)](https://github.com/prometheus/client_python) -- Official Prometheus client library for Python\n\n## License\n\n[GPL-3.0](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGeiserX%2Fduplicacy-exporter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FGeiserX%2Fduplicacy-exporter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGeiserX%2Fduplicacy-exporter/lists"}