{"id":36774933,"url":"https://github.com/chase-roohms/dumpsterr","last_synced_at":"2026-01-15T22:17:26.469Z","repository":{"id":331567094,"uuid":"1130687519","full_name":"chase-roohms/dumpsterr","owner":"chase-roohms","description":"Safely empties your Plex trash, verifying file availability before doing so. Prevents unintentional deletion of movies and shows when drives are disconnected.","archived":false,"fork":false,"pushed_at":"2026-01-12T04:09:03.000Z","size":88,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-12T19:13:36.538Z","etag":null,"topics":["arr-stack","plex","trash"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chase-roohms.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"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}},"created_at":"2026-01-08T21:33:27.000Z","updated_at":"2026-01-12T04:07:42.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/chase-roohms/dumpsterr","commit_stats":null,"previous_names":["chase-roohms/dumpsterr"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/chase-roohms/dumpsterr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chase-roohms%2Fdumpsterr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chase-roohms%2Fdumpsterr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chase-roohms%2Fdumpsterr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chase-roohms%2Fdumpsterr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chase-roohms","download_url":"https://codeload.github.com/chase-roohms/dumpsterr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chase-roohms%2Fdumpsterr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28472625,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-15T22:13:38.078Z","status":"ssl_error","status_checked_at":"2026-01-15T22:12:11.737Z","response_time":62,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["arr-stack","plex","trash"],"created_at":"2026-01-12T13:10:26.813Z","updated_at":"2026-01-15T22:17:26.464Z","avatar_url":"https://github.com/chase-roohms.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dumpsterr\n\nAutomated Plex trash management tool that validates filesystem state before emptying library trash. Prevents accidental deletion when using network-mounted storage.\n\n# Quickstart\n```bash\nmkdir dumpsterr \u0026\u0026 cd dumpsterr\ncurl -fsSL https://raw.githubusercontent.com/chase-roohms/dumpsterr/refs/heads/main/docker-compose/quickstart.sh | bash\n# Edit config.yml and .env\ndocker compose up -d\n```\n## Problem\n\nWhen Plex runs on a different host than your media storage (NFS, SMB, etc.), network interruptions can cause mount failures. If Plex scans while mounts are down, it marks all media as deleted and removes them from your library. Re-mounting triggers a full rescan and metadata rebuild.\n\nPlex's \"fix\" for this is to disable \"Empty trash automatically after every scan\" - which then means you have to periodically empty your trash manually to avoid all the little red trash symbols on **intentionally deleted** media, and the scary red \"unavailable\" buttons on upgraded files.\n\n## Solution\n\ndumpsterr validates filesystem state before allowing Plex to empty trash:\n- Checks directory accessibility\n- Verifies minimum file counts\n- Confirms file count thresholds are within an acceptable range: `if (files on disk / media in library) \u003e minimum threshold in config`\n- Only empties trash when **all** validations pass\n\n## Requirements\n\n- Plex Media Server with API access\n- Docker (or Python 3.12+)\n- Read access to media directories\n\n## Configuration\n\nCreate `data/config.yml`:\n\n```yaml\nlibraries:\n  - name: Movies                    # Plex library name\n    path: /media/movies/            # Container path to media\n    min_files: 100                  # Minimum files required\n    min_threshold: 90               # Minimum percentage of expected files\n\n  - name: TV Shows\n    path: \n      - /media/shows/\n      - /media/more-shows/\n    min_files: 50\n    min_threshold: 90\n\nsettings:\n  log_level: INFO                   # DEBUG, INFO, WARNING, ERROR, CRITICAL\n```\n\nConfiguration validation:\n- Schema: [src/public/config.schema.yml](src/public/config.schema.yml)\n- Validated on startup using jsonschema\n\n## Docker Setup\n\n### Environment Variables\n\nRequired:\n- `PLEX_URL` - Plex server URL (e.g., `http://192.168.1.100:32400`)\n- `PLEX_TOKEN` - [Get your token](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/)\n\nOptional:\n- `TZ` - Timezone (e.g., `America/New_York`)\n\n### Docker Compose\n\n```yaml\nservices:\n  dumpsterr:\n    image: neonvariant/dumpsterr:latest\n    container_name: dumpsterr\n    volumes:\n      - ./config.yml:/app/data/config.yml:ro\n      - /path/to/movies:/media/movies:ro\n      - /path/to/shows:/media/shows:ro\n    environment:\n      - PLEX_URL=${PLEX_URL}\n      - PLEX_TOKEN=${PLEX_TOKEN}\n      - TZ=${TZ}\n    restart: unless-stopped\n```\n\n.env\n```shell\nPLEX_URL=http://\u003cIP\u003e:\u003cPORT\u003e\nPLEX_TOKEN=PLEX-TOKEN-HERE\nTZ=Time/Zone\n```\n\nRun:\n```bash\ndocker compose up -d\n```\n\n### Docker CLI\n\n```bash\ndocker run -d \\\n  --name dumpsterr \\\n  -v ./config.yml:/app/data/config.yml:ro \\\n  -v /path/to/movies:/media/movies:ro \\\n  -v /path/to/shows:/media/shows:ro \\\n  -e PLEX_URL=http://192.168.1.100:32400 \\\n  -e PLEX_TOKEN=your_token_here \\\n  -e TZ=America/New_York \\\n  --restart unless-stopped \\\n  neonvariant/dumpsterr:latest\n```\n\n## Build from Source\n\n```bash\ngit clone https://github.com/chase-roohms/dumpsterr.git\ncd dumpsterr\ndocker build -t dumpsterr .\n```\n\n## Scheduling\n\nRuns hourly via supercronic (see [src/crontab](src/crontab)). Also executes on container startup.\n\nTo modify schedule, edit [src/crontab](src/crontab) and rebuild:\n```bash\n# Current: hourly\n0 * * * * cd /app \u0026\u0026 /usr/local/bin/python src/main.py\n\n# Example: every 6 hours\n0 */6 * * * cd /app \u0026\u0026 /usr/local/bin/python src/main.py\n```\n\n## Validation Process\n\n1. Directory accessibility check\n2. Minimum file count verification\n3. Plex library size comparison\n4. Threshold percentage validation (current files / expected files \u003e minimum threshold)\n5. Trash emptying (only if all checks pass)\n\nValidation failure exits without emptying trash.\n\n## Logs\n\nView logs:\n```bash\ndocker logs dumpsterr\ndocker logs -f dumpsterr  # Follow mode\n```\n\nLog levels: DEBUG, INFO (default), WARNING, ERROR, CRITICAL\n\n## Troubleshooting\n\n### IsADirectoryError: Config file is a directory\n\n**Error**: `IsADirectoryError: [Errno 21] Is a directory: 'data/config.yml'`\n\n**Cause**: The config file doesn't exist on your host system. When Docker tries to mount a non-existent file, it creates a directory instead.\n\n**Solution**:\n1. Ensure the config file exists on your host at the path specified in your `docker-compose.yml`\n2. If using the quickstart, the config file should be in the same directory as your `docker-compose.yml`\n3. Recreate the container to remount the volume correctly:\n   ```bash\n   docker compose down\n   docker compose up -d\n   ```\n\n**Example**: If your docker-compose.yml has:\n```yaml\nvolumes:\n  - ./config.yml:/app/data/config.yml:ro\n```\nThen `config.yml` must exist in the same directory as `docker-compose.yml` before running `docker compose up`.\n\n### Container starts but nothing happens\n\nCheck that:\n- `PLEX_URL` is accessible from the container\n- `PLEX_TOKEN` is valid\n- Media paths in `config.yml` match the container paths (not host paths)\n\n## Plex Configuration\n\nDisable \"Empty trash automatically after every scan\" in:\n- Settings \u003e Library\n\nThis lets dumpsterr control trash emptying on its schedule.\n\n## Project Structure\n\n```\nsrc/\n├── main.py              # Validation and orchestration\n├── config/              # Configuration loading and validation\n├── filesystem/          # Directory and file count checks\n├── plex_client/         # Plex API interaction\n└── public/              # JSON schema for config validation\n```\n\n## Dependencies\n\n- PyYAML\n- jsonschema\n- requests\n\n## License\n\nSee LICENSE file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchase-roohms%2Fdumpsterr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchase-roohms%2Fdumpsterr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchase-roohms%2Fdumpsterr/lists"}