mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2025-12-24 06:28:55 -05:00
Compare commits
311 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9341ff441 | ||
|
|
555be79a7a | ||
|
|
66be8aaa32 | ||
|
|
737b683285 | ||
|
|
c0e94733cd | ||
|
|
5843ec1d15 | ||
|
|
51c968fb2d | ||
|
|
ea50a7a535 | ||
|
|
b1477704b6 | ||
|
|
04d287e1e0 | ||
|
|
59a5eea684 | ||
|
|
2078eab054 | ||
|
|
3278a24931 | ||
|
|
f9b83cf3b2 | ||
|
|
54997b184c | ||
|
|
2e7d89e1c5 | ||
|
|
68c9d41f5f | ||
|
|
a9503bbca3 | ||
|
|
07553131a0 | ||
|
|
b21bab2a8a | ||
|
|
c9e033c9d1 | ||
|
|
a26fd1da5d | ||
|
|
d904240aa8 | ||
|
|
4f066c3f2a | ||
|
|
84bee5f63a | ||
|
|
8d7b8e5848 | ||
|
|
8115205158 | ||
|
|
ada2b9fa4a | ||
|
|
222c21a85e | ||
|
|
d6cea762eb | ||
|
|
f9d096d76a | ||
|
|
ee89570cad | ||
|
|
42145e58eb | ||
|
|
0cda75157e | ||
|
|
4ef223b0cc | ||
|
|
0df1fd0d19 | ||
|
|
7409ddeb33 | ||
|
|
f46702f073 | ||
|
|
9f22db9cad | ||
|
|
bbd2052086 | ||
|
|
8436f93727 | ||
|
|
8c020d5aa5 | ||
|
|
cd36a32b91 | ||
|
|
9e43ce76f6 | ||
|
|
a068b93458 | ||
|
|
96dc8bec51 | ||
|
|
910633a413 | ||
|
|
823b9fec3f | ||
|
|
b99cbd0d6e | ||
|
|
534a8a9c66 | ||
|
|
0473d14462 | ||
|
|
febb9c4432 | ||
|
|
0933b99cea | ||
|
|
20483fab9a | ||
|
|
60f96589cd | ||
|
|
eec0cb9605 | ||
|
|
20fe4c5b3f | ||
|
|
8d7b207181 | ||
|
|
8f34bdc780 | ||
|
|
a61a10af8f | ||
|
|
17c37e8eef | ||
|
|
bfbd2c16dd | ||
|
|
eef21ac734 | ||
|
|
978b1c9ade | ||
|
|
5ea1361832 | ||
|
|
861c74e452 | ||
|
|
6f437de698 | ||
|
|
2cdbc938fd | ||
|
|
26d98f815f | ||
|
|
1a7e86aca4 | ||
|
|
4b2aa6c4f6 | ||
|
|
2dcd495da7 | ||
|
|
38b7d1d4bb | ||
|
|
c109b15ec1 | ||
|
|
1cc749243c | ||
|
|
a7c8f37bed | ||
|
|
65d0d5188d | ||
|
|
15b0e4218d | ||
|
|
de7e7d244b | ||
|
|
7a8cdbb354 | ||
|
|
4c5e4e95d9 | ||
|
|
5881c24ff3 | ||
|
|
b323235227 | ||
|
|
ee0f915aaf | ||
|
|
8883a2ca3f | ||
|
|
bbfde4bb17 | ||
|
|
4d8d3ea732 | ||
|
|
e9718c3a66 | ||
|
|
3e8fb01f44 | ||
|
|
e5d7d8ed12 | ||
|
|
d76216665b | ||
|
|
1255d0a50a | ||
|
|
cb53ee09da | ||
|
|
f2622b129d | ||
|
|
7ac72cbece | ||
|
|
b7902ca7ba | ||
|
|
84dd43a85a | ||
|
|
fe54813abf | ||
|
|
7b2af6dd5d | ||
|
|
cacd62058f | ||
|
|
0a8d1450dd | ||
|
|
f651663fd3 | ||
|
|
b4548573ee | ||
|
|
f39e8eca46 | ||
|
|
ba1ced3b84 | ||
|
|
ef8aa4c002 | ||
|
|
cd2b66a525 | ||
|
|
b4dad7ebc9 | ||
|
|
9411ab815e | ||
|
|
9ebf49f0b8 | ||
|
|
369a75b4c5 | ||
|
|
1f55929ab1 | ||
|
|
4558fa462f | ||
|
|
f23c0e0186 | ||
|
|
d5d661543f | ||
|
|
53163774a5 | ||
|
|
00dfd63797 | ||
|
|
d5e3e9954d | ||
|
|
bf37668dcb | ||
|
|
62eee94497 | ||
|
|
91274dac4b | ||
|
|
689adb13d8 | ||
|
|
1194db6c1e | ||
|
|
5f412c2e6a | ||
|
|
bd9f24eb1a | ||
|
|
458688770a | ||
|
|
cf208754a3 | ||
|
|
4b5f4dc447 | ||
|
|
3c2e36eb9e | ||
|
|
95d39c40ed | ||
|
|
9e393eebcc | ||
|
|
033b50519b | ||
|
|
0be5f48717 | ||
|
|
9353a55cff | ||
|
|
0cb737a7e1 | ||
|
|
6a0641ef63 | ||
|
|
26bfa5adb2 | ||
|
|
96130501c3 | ||
|
|
fef7efb7dc | ||
|
|
857a6a88b4 | ||
|
|
b85842d3f0 | ||
|
|
4800ec66d5 | ||
|
|
225e80cdbe | ||
|
|
64a24051d7 | ||
|
|
d73cb46006 | ||
|
|
0ab5c01ebc | ||
|
|
f2303023b4 | ||
|
|
7b67ab6967 | ||
|
|
3ae1f9c81d | ||
|
|
3b676a8c60 | ||
|
|
0f45b298b8 | ||
|
|
0260e43d79 | ||
|
|
a85687f687 | ||
|
|
ee24856504 | ||
|
|
9b337134c9 | ||
|
|
64d4abf25b | ||
|
|
a4423a28e9 | ||
|
|
fc26f40fb3 | ||
|
|
e2e775c073 | ||
|
|
ca13171b82 | ||
|
|
ee71eab6fa | ||
|
|
565b6b3fde | ||
|
|
91e7e92cc6 | ||
|
|
c8626c220e | ||
|
|
c9dc917401 | ||
|
|
20e1df722e | ||
|
|
06a2b82881 | ||
|
|
1afbf5c573 | ||
|
|
e5586fc774 | ||
|
|
565d6a78a7 | ||
|
|
9cac4fdf8c | ||
|
|
d28389641a | ||
|
|
46c48d7c00 | ||
|
|
d2bcf8ac89 | ||
|
|
8463b8b786 | ||
|
|
fd74455951 | ||
|
|
34314de284 | ||
|
|
195d361364 | ||
|
|
cc07466c2b | ||
|
|
30aaac5e67 | ||
|
|
44a6c37530 | ||
|
|
9526f133a1 | ||
|
|
ddbfee33d1 | ||
|
|
12ab97825b | ||
|
|
bcf093148c | ||
|
|
d2cc8517c1 | ||
|
|
cae4e323a5 | ||
|
|
f6b0014ec6 | ||
|
|
5d0a48e7cd | ||
|
|
d177790d6f | ||
|
|
d7e28fc6e7 | ||
|
|
14f16cbe70 | ||
|
|
8b8dc672d1 | ||
|
|
d80d897acb | ||
|
|
d9d1f86897 | ||
|
|
2de3cccac7 | ||
|
|
b2bb48a260 | ||
|
|
d078ea288c | ||
|
|
84d984082c | ||
|
|
b289b2ee39 | ||
|
|
97473b47fd | ||
|
|
a38d370925 | ||
|
|
1e3a4cb220 | ||
|
|
62e31a2497 | ||
|
|
c58f6080f0 | ||
|
|
7cfd69b1f7 | ||
|
|
86c356c3a9 | ||
|
|
36cfd3b4e5 | ||
|
|
c6f34432b7 | ||
|
|
97fe7138c2 | ||
|
|
2b83e1a334 | ||
|
|
599f8959a9 | ||
|
|
f6bcd29ea0 | ||
|
|
62fc39251b | ||
|
|
f5fe9405cd | ||
|
|
e4bb0ac04c | ||
|
|
fdab02a937 | ||
|
|
b7546a7015 | ||
|
|
7482f963e3 | ||
|
|
90b0ea8306 | ||
|
|
25ae542e87 | ||
|
|
e96d76091b | ||
|
|
28b6c10917 | ||
|
|
60884198ff | ||
|
|
367a499155 | ||
|
|
414ad299b6 | ||
|
|
b3bc071943 | ||
|
|
7bd69046ea | ||
|
|
7cfe1333d2 | ||
|
|
4bb54517c1 | ||
|
|
607bebaf0f | ||
|
|
0da1ef518a | ||
|
|
a660480a7c | ||
|
|
e8a7373b0d | ||
|
|
0785fe9a12 | ||
|
|
153c490198 | ||
|
|
4f8d2c57d7 | ||
|
|
0ba1979016 | ||
|
|
7ec9dfe68d | ||
|
|
8b7e39fa86 | ||
|
|
2143c74767 | ||
|
|
9e596cfe17 | ||
|
|
dd95c40bec | ||
|
|
df37aee2a2 | ||
|
|
cb57b06abb | ||
|
|
ac924d1294 | ||
|
|
ba4379417e | ||
|
|
ff565895ce | ||
|
|
1f5e8e5711 | ||
|
|
e284ea2c13 | ||
|
|
d5dab0a726 | ||
|
|
8b36e363ac | ||
|
|
d3c5f7a210 | ||
|
|
a97b898d33 | ||
|
|
aa3ec6c49c | ||
|
|
2a55ad456c | ||
|
|
856bd9a93d | ||
|
|
64518ad071 | ||
|
|
100528ab06 | ||
|
|
a8e188aa01 | ||
|
|
91bd85708c | ||
|
|
0bd4e77e9d | ||
|
|
9bd46d7255 | ||
|
|
1604e56a89 | ||
|
|
6c9b60dff5 | ||
|
|
ee02666dc1 | ||
|
|
21e1fde1c8 | ||
|
|
8721bc411e | ||
|
|
3d0ab5f4a6 | ||
|
|
0e4535d7a6 | ||
|
|
7a1e019c76 | ||
|
|
701a7dc417 | ||
|
|
eb0f782f53 | ||
|
|
a1bd278652 | ||
|
|
9409346732 | ||
|
|
3d9b286206 | ||
|
|
c675924be7 | ||
|
|
2bd8f69aff | ||
|
|
ef9868be4d | ||
|
|
4a394928bb | ||
|
|
60d3ddb3d2 | ||
|
|
0173598519 | ||
|
|
96b9a54b64 | ||
|
|
212aeccaf3 | ||
|
|
d2eb9e50e0 | ||
|
|
1b47921ac5 | ||
|
|
d8d6e31395 | ||
|
|
10be7d4a73 | ||
|
|
e98ecfcb2a | ||
|
|
bc1da2113c | ||
|
|
46ef6123cc | ||
|
|
f2027f77a9 | ||
|
|
3c2bb7a289 | ||
|
|
f9fd118f88 | ||
|
|
4d79307d24 | ||
|
|
a52fd9198d | ||
|
|
4cb36e6727 | ||
|
|
a7c2b698d6 | ||
|
|
57326b2f8e | ||
|
|
374f0f72a7 | ||
|
|
06f514c3ea | ||
|
|
f386bf700c | ||
|
|
b6950f545f | ||
|
|
6eb457ed8f | ||
|
|
0e99a510a8 | ||
|
|
b4316a4f0d | ||
|
|
fa3aebde9a | ||
|
|
a68a4c733f | ||
|
|
0fc7352db6 | ||
|
|
69788d55d2 | ||
|
|
461e935128 |
2
.github/ISSUE_TEMPLATE/1-bug.yml
vendored
2
.github/ISSUE_TEMPLATE/1-bug.yml
vendored
@@ -14,7 +14,7 @@ body:
|
||||
options:
|
||||
- label: Reviewed the documentation.
|
||||
required: true
|
||||
- label: Ensured I am using ghcr.io/cleanuparr/cleanuparr docker repository.
|
||||
- label: Ensured I am using ghcr.io/Cleanuparr/Cleanuparr docker repository.
|
||||
required: true
|
||||
- label: Ensured I am using the latest version.
|
||||
required: true
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/3-help.yml
vendored
2
.github/ISSUE_TEMPLATE/3-help.yml
vendored
@@ -14,7 +14,7 @@ body:
|
||||
options:
|
||||
- label: Reviewed the documentation.
|
||||
required: true
|
||||
- label: Ensured I am using ghcr.io/cleanuparr/cleanuparr docker repository.
|
||||
- label: Ensured I am using ghcr.io/Cleanuparr/Cleanuparr docker repository.
|
||||
required: true
|
||||
- label: Ensured I am using the latest version.
|
||||
required: true
|
||||
|
||||
7
.github/workflows/build-docker.yml
vendored
7
.github/workflows/build-docker.yml
vendored
@@ -53,10 +53,11 @@ jobs:
|
||||
githubTags=""
|
||||
|
||||
if [ -n "$latestDockerTag" ]; then
|
||||
githubTags="$githubTags,ghcr.io/cleanuparr/cleanuparr:$latestDockerTag"
|
||||
githubTags="$githubTags,ghcr.io/cleanuparr:$latestDockerTag"
|
||||
fi
|
||||
|
||||
if [ -n "$versionDockerTag" ]; then
|
||||
githubTags="$githubTags,ghcr.io/cleanuparr/cleanuparr:$versionDockerTag"
|
||||
githubTags="$githubTags,ghcr.io/cleanuparr:$versionDockerTag"
|
||||
fi
|
||||
|
||||
# set env vars
|
||||
@@ -112,7 +113,7 @@ jobs:
|
||||
version=${{ env.versionDockerTag }}
|
||||
build-args: |
|
||||
VERSION=${{ env.version }}
|
||||
PACKAGES_USERNAME=${{ secrets.PACKAGES_USERNAME }}
|
||||
PACKAGES_USERNAME=${{ env.PACKAGES_USERNAME }}
|
||||
PACKAGES_PAT=${{ env.PACKAGES_PAT }}
|
||||
outputs: |
|
||||
type=image
|
||||
|
||||
22
.github/workflows/build-executable.yml
vendored
22
.github/workflows/build-executable.yml
vendored
@@ -101,6 +101,28 @@ jobs:
|
||||
- name: Build osx-arm64
|
||||
run: dotnet publish code/backend/${{ env.executableName }}/${{ env.executableName }}.csproj -c Release --runtime osx-arm64 --self-contained -o artifacts/${{ env.githubRepositoryName }}-${{ env.appVersion }}-osx-arm64 /p:PublishSingleFile=true /p:Version=${{ env.appVersion }} /p:DebugSymbols=false
|
||||
|
||||
- name: Create sample configuration files
|
||||
run: |
|
||||
# Create a sample appsettings.json for each platform
|
||||
cat > sample-config.json << 'EOF'
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Copy to each build directory
|
||||
cp sample-config.json artifacts/${{ env.githubRepositoryName }}-${{ env.appVersion }}-win-amd64/appsettings.json
|
||||
cp sample-config.json artifacts/${{ env.githubRepositoryName }}-${{ env.appVersion }}-linux-amd64/appsettings.json
|
||||
cp sample-config.json artifacts/${{ env.githubRepositoryName }}-${{ env.appVersion }}-linux-arm64/appsettings.json
|
||||
cp sample-config.json artifacts/${{ env.githubRepositoryName }}-${{ env.appVersion }}-osx-amd64/appsettings.json
|
||||
cp sample-config.json artifacts/${{ env.githubRepositoryName }}-${{ env.appVersion }}-osx-arm64/appsettings.json
|
||||
|
||||
- name: Zip win-x64
|
||||
run: |
|
||||
cd ./artifacts
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
# Build portable executables
|
||||
build-executables:
|
||||
needs: validate
|
||||
uses: ./.github/workflows/build-executable.yml
|
||||
uses: ./.github/workflows/build_executable.yml
|
||||
secrets: inherit
|
||||
|
||||
# Build Windows installer
|
||||
@@ -110,8 +110,8 @@ jobs:
|
||||
tag_name: ${{ needs.validate.outputs.release_version }}
|
||||
token: ${{ env.REPO_READONLY_PAT }}
|
||||
make_latest: true
|
||||
target_commitish: main
|
||||
generate_release_notes: true
|
||||
prerelease: ${{ contains(needs.validate.outputs.app_version, '-') }}
|
||||
files: |
|
||||
./artifacts/**/*.zip
|
||||
./artifacts/**/*.pkg
|
||||
|
||||
75
README.md
75
README.md
@@ -12,67 +12,34 @@ Cleanuparr was created primarily to address malicious files, such as `*.lnk` or
|
||||
> **Features:**
|
||||
> - Strike system to mark bad downloads.
|
||||
> - Remove and block downloads that reached a maximum number of strikes.
|
||||
> - Remove and block downloads that are **failing to be imported** by the arrs.
|
||||
> - Remove and block downloads that are **stalled** or in **metadata downloading** state.
|
||||
> - Remove and block downloads that have a **low download speed** or **high estimated completion time**.
|
||||
> - Remove and block downloads blocked by qBittorrent or by Cleanuparr's **Content Blocker**.
|
||||
> - Remove and block downloads that are **failing to be imported** by the arrs. [configuration](https://cleanuparr.github.io/cleanuparr/docs/configuration/queue-cleaner/import-failed)
|
||||
> - Remove and block downloads that are **stalled** or in **metadata downloading** state. [configuration](https://cleanuparr.github.io/cleanuparr/docs/configuration/queue-cleaner/stalled)
|
||||
> - Remove and block downloads that have a **low download speed** or **high estimated completion time**. [configuration](https://cleanuparr.github.io/cleanuparr/docs/configuration/queue-cleaner/slow)
|
||||
> - Remove and block downloads blocked by qBittorrent or by Cleanuparr's **Content Blocker**. [configuration](https://cleanuparr.github.io/cleanuparr/docs/configuration/content-blocker/general)
|
||||
> - Automatically trigger a search for downloads removed from the arrs.
|
||||
> - Clean up downloads that have been **seeding** for a certain amount of time.
|
||||
> - Remove downloads that are **orphaned**/have no **hardlinks**/are not referenced by the arrs anymore (with [cross-seed](https://www.cross-seed.org/) support).
|
||||
> - Notify on strike or download removal.
|
||||
> - Clean up downloads that have been **seeding** for a certain amount of time. [configuration](https://cleanuparr.github.io/cleanuparr/docs/configuration/download-cleaner/seeding)
|
||||
> - Remove downloads that are **orphaned**/have no **hardlinks**/are not referenced by the arrs anymore (with [cross-seed](https://www.cross-seed.org/) support). [configuration](https://cleanuparr.github.io/cleanuparr/docs/configuration/download-cleaner/hardlinks)
|
||||
> - Notify on strike or download removal. [configuration](https://cleanuparr.github.io/cleanuparr/docs/category/notifications)
|
||||
> - Ignore certain torrent hashes, categories, tags or trackers from being processed by Cleanuparr.
|
||||
|
||||
## 🎯 Supported Applications
|
||||
Cleanuparr supports both qBittorrent's built-in exclusion features and its own blocklist-based system. Binaries for all platforms are provided, along with Docker images for easy deployment.
|
||||
|
||||
### *Arr Applications
|
||||
- **Sonarr** (TV Shows)
|
||||
- **Radarr** (Movies)
|
||||
- **Lidarr** (Music)
|
||||
## Quick Start
|
||||
|
||||
### Download Clients
|
||||
- **qBittorrent**
|
||||
- **Transmission**
|
||||
- **Deluge**
|
||||
> [!NOTE]
|
||||
>
|
||||
> 1. **Docker (Recommended)**
|
||||
> Pull the Docker image from `ghcr.io/Cleanuparr/Cleanuparr:latest`.
|
||||
>
|
||||
> 2. **Unraid (for Unraid users)**
|
||||
> Use the Unraid Community App.
|
||||
>
|
||||
> 3. **Manual Installation (if you're not using Docker)**
|
||||
> Go to [Windows](#windows), [Linux](#linux) or [MacOS](#macos).
|
||||
|
||||
### Platforms
|
||||
- **Docker** (Linux, Windows, macOS)
|
||||
- **Windows** (Native installer)
|
||||
- **macOS** (Intel & Apple Silicon)
|
||||
- **Linux** (Portable executable)
|
||||
- **Unraid** (Community Apps)
|
||||
# Docs
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
```bash
|
||||
docker run -d --name cleanuparr \
|
||||
--restart unless-stopped \
|
||||
-p 11011:11011 \
|
||||
-v /path/to/config:/config \
|
||||
-e PORT=11011 \
|
||||
-e PUID=1000 \
|
||||
-e PGID=1000 \
|
||||
-e TZ=Etc/UTC \
|
||||
ghcr.io/cleanuparr/cleanuparr:latest
|
||||
```
|
||||
|
||||
For Docker Compose, health checks, and other installation methods, see our [Complete Installation Guide](https://cleanuparr.github.io/Cleanuparr/docs/installation/detailed).
|
||||
|
||||
### 🌐 Access the Web Interface
|
||||
|
||||
After installation, open your browser and navigate to:
|
||||
```
|
||||
http://localhost:11011
|
||||
```
|
||||
|
||||
**Next Steps:** Check out the [📖 Complete Documentation](https://cleanuparr.github.io/Cleanuparr/) for detailed configuration guides and setup instructions.
|
||||
|
||||
## 📖 Documentation & Support
|
||||
|
||||
- **📚 [Complete Documentation](https://cleanuparr.github.io/Cleanuparr/)** - Installation guides, configuration, and troubleshooting
|
||||
- **⚙️ [Configuration Guide](https://cleanuparr.github.io/Cleanuparr/docs/category/configuration)** - Set up download clients, *arr apps, and features
|
||||
- **🔧 [Setup Scenarios](https://cleanuparr.github.io/Cleanuparr/docs/category/setup-scenarios)** - Common use cases and examples
|
||||
- **💬 [Discord Community](https://discord.gg/SCtMCgtsc4)** - Get help and discuss with other users
|
||||
- **🔗 [GitHub Releases](https://github.com/Cleanuparr/Cleanuparr/releases)** - Download binaries and view changelog
|
||||
Docs can be found [here](https://Cleanuparr.github.io/Cleanuparr/).
|
||||
|
||||
# <img style="vertical-align: middle;" width="24px" src="./Logo/256.png" alt="Cleanuparr"> <span style="vertical-align: middle;">Cleanuparr</span> <img src="https://raw.githubusercontent.com/FortAwesome/Font-Awesome/6.x/svgs/solid/x.svg" height="24px" width="30px" style="vertical-align: middle;"> <span style="vertical-align: middle;">Huntarr</span> <img style="vertical-align: middle;" width="24px" src="https://github.com/plexguide/Huntarr.io/blob/main/frontend/static/logo/512.png?raw=true" alt Huntarr></img>
|
||||
|
||||
|
||||
347
blacklist
347
blacklist
@@ -1,27 +1,12 @@
|
||||
*(sample).*
|
||||
*sample.avchd
|
||||
*sample.avi
|
||||
*sample.mkv
|
||||
*sample.mov
|
||||
*sample.mp4
|
||||
*sample.webm
|
||||
*sample.wmv
|
||||
*.000
|
||||
*.001
|
||||
*.002
|
||||
*.004
|
||||
*.0xe
|
||||
*.001
|
||||
*.73k
|
||||
*.73p
|
||||
*.7z
|
||||
*.7z.001
|
||||
*.7z.002
|
||||
*.89k
|
||||
*.89z
|
||||
*.8ck
|
||||
*.a00
|
||||
*.a01
|
||||
*.a02
|
||||
*.a7r
|
||||
*.ac
|
||||
*.acc
|
||||
@@ -37,11 +22,8 @@
|
||||
*.ahk
|
||||
*.ai
|
||||
*.aif
|
||||
*.ain
|
||||
*.air
|
||||
*.alz
|
||||
*.ana
|
||||
*.apex
|
||||
*.api
|
||||
*.apk
|
||||
*.app
|
||||
@@ -49,28 +31,15 @@
|
||||
*.applescript
|
||||
*.application
|
||||
*.appx
|
||||
*.apz
|
||||
*.ar
|
||||
*.arc
|
||||
*.archiver
|
||||
*.arduboy
|
||||
*.arh
|
||||
*.ari
|
||||
*.arj
|
||||
*.ark
|
||||
*.arscript
|
||||
*.asb
|
||||
*.asice
|
||||
*.asp
|
||||
*.aspx
|
||||
*.aspx-exe
|
||||
*.atmx
|
||||
*.ayt
|
||||
*.azw2
|
||||
*.b1
|
||||
*.b6z
|
||||
*.b64
|
||||
*.ba
|
||||
*.ba_
|
||||
*.bak
|
||||
*.bas
|
||||
@@ -78,48 +47,26 @@
|
||||
*.bat
|
||||
*.bdjo
|
||||
*.bdmv
|
||||
*.bdoc
|
||||
*.beam
|
||||
*.bh
|
||||
*.bin
|
||||
*.bmp
|
||||
*.bms
|
||||
*.bndl
|
||||
*.bns
|
||||
*.boo
|
||||
*.bsa
|
||||
*.btm
|
||||
*.bundle
|
||||
*.bz
|
||||
*.bz2
|
||||
*.bza
|
||||
*.bzabw
|
||||
*.bzip
|
||||
*.bzip2
|
||||
*.c
|
||||
*.c00
|
||||
*.c01
|
||||
*.c02
|
||||
*.c10
|
||||
*.cab
|
||||
*.caction
|
||||
*.car
|
||||
*.cb7
|
||||
*.cba
|
||||
*.cbr
|
||||
*.cbt
|
||||
*.cbz
|
||||
*.cci
|
||||
*.cda
|
||||
*.cdb
|
||||
*.cdz
|
||||
*.cel
|
||||
*.celx
|
||||
*.cfs
|
||||
*.cgi
|
||||
*.cheat
|
||||
*.chm
|
||||
*.cit
|
||||
*.ckpt
|
||||
*.cla
|
||||
*.class
|
||||
@@ -129,15 +76,9 @@
|
||||
*.coffee
|
||||
*.com
|
||||
*.command
|
||||
*.comppkg.hauptwerk.rar
|
||||
*.comppkg_hauptwerk_rar
|
||||
*.conda
|
||||
*.conf
|
||||
*.config
|
||||
*.cp9
|
||||
*.cpgz
|
||||
*.cpl
|
||||
*.cpt
|
||||
*.crt
|
||||
*.cs
|
||||
*.csh
|
||||
@@ -145,27 +86,17 @@
|
||||
*.csproj
|
||||
*.css
|
||||
*.csv
|
||||
*.ctx
|
||||
*.ctz
|
||||
*.cue
|
||||
*.cur
|
||||
*.cxarchive
|
||||
*.cyw
|
||||
*.czip
|
||||
*.daemon
|
||||
*.daf
|
||||
*.dar
|
||||
*.dat
|
||||
*.data-00000-of-00001
|
||||
*.db
|
||||
*.dd
|
||||
*.deamon
|
||||
*.deb
|
||||
*.dek
|
||||
*.dgc
|
||||
*.dist
|
||||
*.diz
|
||||
*.dl_
|
||||
*.dld
|
||||
*.dll
|
||||
*.dmc
|
||||
@@ -182,27 +113,19 @@
|
||||
*.dw
|
||||
*.dword
|
||||
*.dxl
|
||||
*.dz
|
||||
*.e_e
|
||||
*.ear
|
||||
*.ebacmd
|
||||
*.ebm
|
||||
*.ebs
|
||||
*.ebs2
|
||||
*.ecar
|
||||
*.ecf
|
||||
*.ecs
|
||||
*.ecsbx
|
||||
*.edz
|
||||
*.efw
|
||||
*.egg
|
||||
*.eham
|
||||
*.elf
|
||||
*.elf-so
|
||||
*.email
|
||||
*.emu
|
||||
*.epk
|
||||
*.epi
|
||||
*.es
|
||||
*.esh
|
||||
*.etc
|
||||
@@ -218,62 +141,36 @@
|
||||
*.exz
|
||||
*.ezs
|
||||
*.ezt
|
||||
*.f
|
||||
*.f3z
|
||||
*.fas
|
||||
*.fba
|
||||
*.fcx
|
||||
*.fky
|
||||
*.flac
|
||||
*.flatpak
|
||||
*.flv
|
||||
*.fp8
|
||||
*.fpi
|
||||
*.frs
|
||||
*.fxp
|
||||
*.fzpz
|
||||
*.gadget
|
||||
*.gar
|
||||
*.gat
|
||||
*.gca
|
||||
*.gif
|
||||
*.gifv
|
||||
*.gm9
|
||||
*.gmz
|
||||
*.gpe
|
||||
*.gpu
|
||||
*.gs
|
||||
*.gz
|
||||
*.gz2
|
||||
*.gza
|
||||
*.gzi
|
||||
*.gzip
|
||||
*.h5
|
||||
*.ha
|
||||
*.ham
|
||||
*.hbc
|
||||
*.hbc2
|
||||
*.hbe
|
||||
*.hex
|
||||
*.hki
|
||||
*.hki1
|
||||
*.hki2
|
||||
*.hki3
|
||||
*.hlp
|
||||
*.hms
|
||||
*.hpf
|
||||
*.hpk
|
||||
*.hpkg
|
||||
*.hta
|
||||
*.hta-psh
|
||||
*.htaccess
|
||||
*.htm
|
||||
*.html
|
||||
*.htmi
|
||||
*.hyp
|
||||
*.iadproj
|
||||
*.icd
|
||||
*.ice
|
||||
*.icns
|
||||
*.ico
|
||||
*.idx
|
||||
@@ -286,27 +183,17 @@
|
||||
*.ins
|
||||
*.ipa
|
||||
*.ipf
|
||||
*.ipg
|
||||
*.ipk
|
||||
*.ipsw
|
||||
*.iqylink
|
||||
*.ish
|
||||
*.iso
|
||||
*.isp
|
||||
*.isu
|
||||
*.isx
|
||||
*.ita
|
||||
*.ize
|
||||
*.izh
|
||||
*.izma ace
|
||||
*.j
|
||||
*.jar
|
||||
*.jar.pack
|
||||
*.java
|
||||
*.jex
|
||||
*.jgz
|
||||
*.jhh
|
||||
*.jic
|
||||
*.jpeg
|
||||
*.jpg
|
||||
*.js
|
||||
@@ -315,51 +202,27 @@
|
||||
*.jse
|
||||
*.jsf
|
||||
*.json
|
||||
*.jsonlz4
|
||||
*.jsp
|
||||
*.jsx
|
||||
*.kextraction
|
||||
*.kgb
|
||||
*.kix
|
||||
*.ksh
|
||||
*.ksp
|
||||
*.kwgt
|
||||
*.kx
|
||||
*.kz
|
||||
*.layout
|
||||
*.lbr
|
||||
*.lck
|
||||
*.ldb
|
||||
*.lemon
|
||||
*.lha
|
||||
*.lhzd
|
||||
*.lib
|
||||
*.libzip
|
||||
*.link
|
||||
*.lnk
|
||||
*.lo
|
||||
*.lock
|
||||
*.log
|
||||
*.loop-vbs
|
||||
*.lpkg
|
||||
*.lqr
|
||||
*.ls
|
||||
*.lz
|
||||
*.lz4
|
||||
*.lzh
|
||||
*.lzm
|
||||
*.lzma
|
||||
*.lzo
|
||||
*.lzr
|
||||
*.lzx
|
||||
*.m3u
|
||||
*.m4a
|
||||
*.mac
|
||||
*.macho
|
||||
*.mamc
|
||||
*.manifest
|
||||
*.mar
|
||||
*.mbz
|
||||
*.mcr
|
||||
*.md
|
||||
*.mda
|
||||
@@ -370,29 +233,22 @@
|
||||
*.mdt
|
||||
*.mel
|
||||
*.mem
|
||||
*.memo
|
||||
*.meta
|
||||
*.mgm
|
||||
*.mhm
|
||||
*.mht
|
||||
*.mhtml
|
||||
*.mid
|
||||
*.mint
|
||||
*.mio
|
||||
*.mlappinstall
|
||||
*.mlproj
|
||||
*.mlx
|
||||
*.mm
|
||||
*.mobileconfig
|
||||
*.model
|
||||
*.moo
|
||||
*.mou
|
||||
*.movpkg
|
||||
*.mozlz4
|
||||
*.mp3
|
||||
*.mpa
|
||||
*.mpk
|
||||
*.mpkg
|
||||
*.mpls
|
||||
*.mrc
|
||||
*.mrp
|
||||
@@ -411,79 +267,41 @@
|
||||
*.msp
|
||||
*.mst
|
||||
*.msu
|
||||
*.mxc
|
||||
*.mxe
|
||||
*.mzp
|
||||
*.n
|
||||
*.nar
|
||||
*.ncl
|
||||
*.net
|
||||
*.nex
|
||||
*.nexe
|
||||
*.nfo
|
||||
*.npk
|
||||
*.nrg
|
||||
*.num
|
||||
*.nz
|
||||
*.nzb.bz2
|
||||
*.nzb.gz
|
||||
*.nzbs
|
||||
*.oar
|
||||
*.ocx
|
||||
*.odlgz
|
||||
*.odt
|
||||
*.opk
|
||||
*.ore
|
||||
*.osf
|
||||
*.ost
|
||||
*.osx
|
||||
*.osx-app
|
||||
*.otm
|
||||
*.out
|
||||
*.ova
|
||||
*.oz
|
||||
*.p
|
||||
*.p01
|
||||
*.p19
|
||||
*.p7z
|
||||
*.pa
|
||||
*.pack.gz
|
||||
*.package
|
||||
*.pae
|
||||
*.paf
|
||||
*.pak
|
||||
*.paq6
|
||||
*.paq7
|
||||
*.paq8
|
||||
*.paq8f
|
||||
*.paq8l
|
||||
*.paq8p
|
||||
*.par
|
||||
*.par2
|
||||
*.pax
|
||||
*.pb
|
||||
*.pbi
|
||||
*.pcd
|
||||
*.pcv
|
||||
*.pdb
|
||||
*.pdf
|
||||
*.pea
|
||||
*.perl
|
||||
*.pet
|
||||
*.pex
|
||||
*.pf
|
||||
*.phar
|
||||
*.php
|
||||
*.php5
|
||||
*.pif
|
||||
*.pim
|
||||
*.pima
|
||||
*.pit
|
||||
*.piz
|
||||
*.pkg
|
||||
*.pkg.tar.xz
|
||||
*.pkg.tar.zst
|
||||
*.pkz
|
||||
*.pl
|
||||
*.plsc
|
||||
*.plx
|
||||
@@ -501,7 +319,6 @@
|
||||
*.pptx
|
||||
*.prc
|
||||
*.prg
|
||||
*.prs
|
||||
*.ps
|
||||
*.ps1
|
||||
*.ps1xml
|
||||
@@ -517,16 +334,9 @@
|
||||
*.psh-reflection
|
||||
*.psm1
|
||||
*.pst
|
||||
*.psz
|
||||
*.pt
|
||||
*.pup
|
||||
*.puz
|
||||
*.pvd
|
||||
*.pvmp
|
||||
*.pvmz
|
||||
*.pwa
|
||||
*.pwc
|
||||
*.pxl
|
||||
*.pxo
|
||||
*.py
|
||||
*.pyc
|
||||
@@ -534,20 +344,8 @@
|
||||
*.pyo
|
||||
*.python
|
||||
*.pyz
|
||||
*.q
|
||||
*.qda
|
||||
*.qit
|
||||
*.qpx
|
||||
*.r0
|
||||
*.r00
|
||||
*.r01
|
||||
*.r02
|
||||
*.r03
|
||||
*.r04
|
||||
*.r1
|
||||
*.r2
|
||||
*.r21
|
||||
*.r30
|
||||
*.ram
|
||||
*.rar
|
||||
*.raw
|
||||
@@ -558,35 +356,22 @@
|
||||
*.reg
|
||||
*.resources
|
||||
*.resx
|
||||
*.rev
|
||||
*.rfs
|
||||
*.rfu
|
||||
*.rgs
|
||||
*.rk
|
||||
*.rm
|
||||
*.rnc
|
||||
*.rox
|
||||
*.rp9
|
||||
*.rpg
|
||||
*.rpj
|
||||
*.rpm
|
||||
*.rss
|
||||
*.ruby
|
||||
*.run
|
||||
*.rxe
|
||||
*.rz
|
||||
*.s00
|
||||
*.s01
|
||||
*.s02
|
||||
*.s09
|
||||
*.s2a
|
||||
*.s7z
|
||||
*.sample
|
||||
*.sapk
|
||||
*.sar
|
||||
*.savedmodel
|
||||
*.sbs
|
||||
*.sbx
|
||||
*.sca
|
||||
*.scar
|
||||
*.scb
|
||||
@@ -596,85 +381,42 @@
|
||||
*.scr
|
||||
*.script
|
||||
*.sct
|
||||
*.sdc
|
||||
*.sdn
|
||||
*.sdoc
|
||||
*.sdocx
|
||||
*.sea
|
||||
*.seed
|
||||
*.sen
|
||||
*.server
|
||||
*.service
|
||||
*.sfg
|
||||
*.sfm
|
||||
*.sfs
|
||||
*.sfv
|
||||
*.sfx
|
||||
*.sh
|
||||
*.shar
|
||||
*.shb
|
||||
*.shell
|
||||
*.shk
|
||||
*.shortcut
|
||||
*.shr
|
||||
*.shs
|
||||
*.shtml
|
||||
*.sifz
|
||||
*.sipa
|
||||
*.sit
|
||||
*.sitx
|
||||
*.sk
|
||||
*.sldm
|
||||
*.sln
|
||||
*.smm
|
||||
*.smpf
|
||||
*.snap
|
||||
*.snagitstamps
|
||||
*.snappy
|
||||
*.snb
|
||||
*.snd
|
||||
*.snz
|
||||
*.spa
|
||||
*.spd
|
||||
*.spl
|
||||
*.spm
|
||||
*.spr
|
||||
*.spt
|
||||
*.sqf
|
||||
*.sql
|
||||
*.sqx
|
||||
*.sqz
|
||||
*.srec
|
||||
*.srep
|
||||
*.srt
|
||||
*.ssm
|
||||
*.stg
|
||||
*.stkdoodlz
|
||||
*.stproj
|
||||
*.sts
|
||||
*.sub
|
||||
*.svg
|
||||
*.swf
|
||||
*.sy_
|
||||
*.sys
|
||||
*.tar
|
||||
*.tar.bz2
|
||||
*.tar.gz
|
||||
*.tar.gz2
|
||||
*.tar.lz
|
||||
*.tar.lzma
|
||||
*.tar.xz
|
||||
*.tar.z
|
||||
*.tar.zip
|
||||
*.taz
|
||||
*.tbl
|
||||
*.tbz
|
||||
*.tbz2
|
||||
*.tcp
|
||||
*.tcx
|
||||
*.text
|
||||
*.tf
|
||||
*.tg
|
||||
*.tgs
|
||||
*.tgz
|
||||
*.thm
|
||||
*.thmx
|
||||
@@ -683,35 +425,19 @@
|
||||
*.tif
|
||||
*.tiff
|
||||
*.tipa
|
||||
*.tlz
|
||||
*.tlzma
|
||||
*.tmp
|
||||
*.tms
|
||||
*.toast
|
||||
*.torrent
|
||||
*.tpk
|
||||
*.tpsr
|
||||
*.trs
|
||||
*.txt
|
||||
*.tx_
|
||||
*.txz
|
||||
*.tz
|
||||
*.tzst
|
||||
*.u3p
|
||||
*.ubz
|
||||
*.uc2
|
||||
*.udf
|
||||
*.ufdr
|
||||
*.ufs.uzip
|
||||
*.uha
|
||||
*.upk
|
||||
*.upx
|
||||
*.url
|
||||
*.uue
|
||||
*.uvm
|
||||
*.uw8
|
||||
*.uzed
|
||||
*.uzip
|
||||
*.vb
|
||||
*.vba
|
||||
*.vba-exe
|
||||
@@ -723,46 +449,26 @@
|
||||
*.vbscript
|
||||
*.vcd
|
||||
*.vdo
|
||||
*.vem
|
||||
*.vexe
|
||||
*.vfs
|
||||
*.vhd
|
||||
*.vhdx
|
||||
*.vib
|
||||
*.vip
|
||||
*.vlx
|
||||
*.vm
|
||||
*.vmcz
|
||||
*.vmdk
|
||||
*.vms
|
||||
*.vob
|
||||
*.vocab
|
||||
*.voca
|
||||
*.vpk
|
||||
*.vpm
|
||||
*.vrpackage
|
||||
*.vsi
|
||||
*.vwi
|
||||
*.vxp
|
||||
*.wa
|
||||
*.wacz
|
||||
*.waff
|
||||
*.war
|
||||
*.wastickers
|
||||
*.wav
|
||||
*.wbk
|
||||
*.wcm
|
||||
*.wdz
|
||||
*.webm
|
||||
*.whl
|
||||
*.wick
|
||||
*.widget
|
||||
*.wim
|
||||
*.wiz
|
||||
*.wlb
|
||||
*.wma
|
||||
*.workflow
|
||||
*.wot
|
||||
*.wpk
|
||||
*.wpl
|
||||
*.wpm
|
||||
@@ -771,26 +477,14 @@
|
||||
*.wsc
|
||||
*.wsf
|
||||
*.wsh
|
||||
*.wux
|
||||
*.x86
|
||||
*.x86_64
|
||||
*.xaml
|
||||
*.xap
|
||||
*.xapk
|
||||
*.xar
|
||||
*.xbap
|
||||
*.xbe
|
||||
*.xcf.bz2
|
||||
*.xcf.gz
|
||||
*.xcf.xz
|
||||
*.xcfbz2
|
||||
*.xcfgz
|
||||
*.xcfxz
|
||||
*.xex
|
||||
*.xez
|
||||
*.xfp
|
||||
*.xig
|
||||
*.xip
|
||||
*.xla
|
||||
*.xlam
|
||||
*.xll
|
||||
@@ -803,47 +497,24 @@
|
||||
*.xltb
|
||||
*.xltm
|
||||
*.xlw
|
||||
*.xmcdz
|
||||
*.xml
|
||||
*.xoj
|
||||
*.xopp
|
||||
*.xqt
|
||||
*.xrt
|
||||
*.xx
|
||||
*.xys
|
||||
*.xz
|
||||
*.xzm
|
||||
*.y
|
||||
*.yc
|
||||
*.ygh
|
||||
*.yz1
|
||||
*.z
|
||||
*.z00
|
||||
*.z01
|
||||
*.z02
|
||||
*.z03
|
||||
*.z04
|
||||
*.zabw
|
||||
*.zap
|
||||
*.zed
|
||||
*.zfsendtotarget
|
||||
*.zhelp
|
||||
*.zi
|
||||
*.zi_
|
||||
*.zim
|
||||
*.zip
|
||||
*.zipx
|
||||
*.zix
|
||||
*.zl
|
||||
*.zl9
|
||||
*.zoo
|
||||
*.zpaq
|
||||
*.zpi
|
||||
*.zsplit
|
||||
*.zst
|
||||
*.zw
|
||||
*.zwi
|
||||
*.zz
|
||||
*sample.avchd
|
||||
*sample.avi
|
||||
*sample.mkv
|
||||
*sample.mov
|
||||
*sample.mp4
|
||||
*sample.webm
|
||||
*sample.wmv
|
||||
Trailer.*
|
||||
VOSTFR
|
||||
api
|
||||
@@ -1,410 +1,53 @@
|
||||
*.000
|
||||
*.001
|
||||
*.002
|
||||
*.004
|
||||
*.7z
|
||||
*.7z.001
|
||||
*.7z.002
|
||||
*.a00
|
||||
*.a01
|
||||
*.a02
|
||||
*.ace
|
||||
*.ain
|
||||
*.alz
|
||||
*.ana
|
||||
*.apex
|
||||
*.apk
|
||||
*.apz
|
||||
*.ar
|
||||
*.arc
|
||||
*.archiver
|
||||
*.arduboy
|
||||
*.arh
|
||||
*.ari
|
||||
*.arj
|
||||
*.ark
|
||||
*.asice
|
||||
*.ayt
|
||||
*.b1
|
||||
*.b6z
|
||||
*.b64
|
||||
*.ba
|
||||
*.bat
|
||||
*.bdoc
|
||||
*.bh
|
||||
*.bin
|
||||
*.bmp
|
||||
*.bndl
|
||||
*.boo
|
||||
*.bundle
|
||||
*.bz
|
||||
*.bz2
|
||||
*.bza
|
||||
*.bzabw
|
||||
*.bzip
|
||||
*.bzip2
|
||||
*.c00
|
||||
*.c01
|
||||
*.c02
|
||||
*.c10
|
||||
*.car
|
||||
*.cb7
|
||||
*.cba
|
||||
*.cbr
|
||||
*.cbt
|
||||
*.cbz
|
||||
*.cdz
|
||||
*.cit
|
||||
*.cmd
|
||||
*.com
|
||||
*.comppkg.hauptwerk.rar
|
||||
*.comppkg_hauptwerk_rar
|
||||
*.conda
|
||||
*.cp9
|
||||
*.cpgz
|
||||
*.cpt
|
||||
*.ctx
|
||||
*.ctz
|
||||
*.cxarchive
|
||||
*.czip
|
||||
*.daf
|
||||
*.dar
|
||||
*.db
|
||||
*.dd
|
||||
*.deb
|
||||
*.dgc
|
||||
*.dist
|
||||
*.diz
|
||||
*.dl_
|
||||
*.dll
|
||||
*.dmg
|
||||
*.dz
|
||||
*.ecar
|
||||
*.ecs
|
||||
*.ecsbx
|
||||
*.edz
|
||||
*.efw
|
||||
*.egg
|
||||
*.epi
|
||||
*.etc
|
||||
*.exe
|
||||
*.f
|
||||
*.f3z
|
||||
*.fcx
|
||||
*.fp8
|
||||
*.fzpz
|
||||
*.gar
|
||||
*.gca
|
||||
*.gif
|
||||
*.gmz
|
||||
*.gz
|
||||
*.gz2
|
||||
*.gza
|
||||
*.gzi
|
||||
*.gzip
|
||||
*.ha
|
||||
*.hbc
|
||||
*.hbc2
|
||||
*.hbe
|
||||
*.hki
|
||||
*.hki1
|
||||
*.hki2
|
||||
*.hki3
|
||||
*.hpk
|
||||
*.hpkg
|
||||
*.htm
|
||||
*.html
|
||||
*.htmi
|
||||
*.hyp
|
||||
*.iadproj
|
||||
*.ice
|
||||
*.ico
|
||||
*.ini
|
||||
*.ipg
|
||||
*.ipk
|
||||
*.ish
|
||||
*.iso
|
||||
*.isx
|
||||
*.ita
|
||||
*.ize
|
||||
*.j
|
||||
*.jar
|
||||
*.jar.pack
|
||||
*.jex
|
||||
*.jgz
|
||||
*.jhh
|
||||
*.jic
|
||||
*.jpg
|
||||
*.js
|
||||
*.jsonlz4
|
||||
*.kextraction
|
||||
*.kgb
|
||||
*.ksp
|
||||
*.kwgt
|
||||
*.kz
|
||||
*.layout
|
||||
*.lbr
|
||||
*.lemon
|
||||
*.lha
|
||||
*.lhzd
|
||||
*.libzip
|
||||
*.link
|
||||
*.lnk
|
||||
*.lpkg
|
||||
*.lqr
|
||||
*.lz
|
||||
*.lz4
|
||||
*.lzh
|
||||
*.lzm
|
||||
*.lzma
|
||||
*.lzo
|
||||
*.lzr
|
||||
*.lzx
|
||||
*.mar
|
||||
*.mbz
|
||||
*.md
|
||||
*.memo
|
||||
*.mint
|
||||
*.mlproj
|
||||
*.mou
|
||||
*.movpkg
|
||||
*.mozlz4
|
||||
*.mpkg
|
||||
*.msi
|
||||
*.mxc
|
||||
*.mzp
|
||||
*.nar
|
||||
*.nex
|
||||
*.nfo
|
||||
*.npk
|
||||
*.nz
|
||||
*.oar
|
||||
*.odlgz
|
||||
*.opk
|
||||
*.osf
|
||||
*.oz
|
||||
*.p01
|
||||
*.p19
|
||||
*.p7z
|
||||
*.pa
|
||||
*.pack.gz
|
||||
*.package
|
||||
*.pae
|
||||
*.pak
|
||||
*.paq6
|
||||
*.paq7
|
||||
*.paq8
|
||||
*.paq8f
|
||||
*.paq8l
|
||||
*.paq8p
|
||||
*.par
|
||||
*.par2
|
||||
*.pax
|
||||
*.pbi
|
||||
*.pcv
|
||||
*.pea
|
||||
*.perl
|
||||
*.pet
|
||||
*.pf
|
||||
*.php
|
||||
*.pim
|
||||
*.pima
|
||||
*.pit
|
||||
*.piz
|
||||
*.pkg
|
||||
*.pkg.tar.xz
|
||||
*.pkg.tar.zst
|
||||
*.pkz
|
||||
*.pl
|
||||
*.png
|
||||
*.prs
|
||||
*.ps1
|
||||
*.psc1
|
||||
*.psd1
|
||||
*.psm1
|
||||
*.psz
|
||||
*.pup
|
||||
*.puz
|
||||
*.pvmp
|
||||
*.pvmz
|
||||
*.pwa
|
||||
*.pxl
|
||||
*.py
|
||||
*.pyd
|
||||
*.q
|
||||
*.qda
|
||||
*.r0
|
||||
*.r00
|
||||
*.r01
|
||||
*.r02
|
||||
*.r03
|
||||
*.r04
|
||||
*.r1
|
||||
*.r2
|
||||
*.r21
|
||||
*.r30
|
||||
*.rar
|
||||
*.rb
|
||||
*.readme
|
||||
*.reg
|
||||
*.rev
|
||||
*.rk
|
||||
*.rnc
|
||||
*.rp9
|
||||
*.rpm
|
||||
*.rss
|
||||
*.run
|
||||
*.rz
|
||||
*.s00
|
||||
*.s01
|
||||
*.s02
|
||||
*.s09
|
||||
*.s7z
|
||||
*.sar
|
||||
*.sbx
|
||||
*.scr
|
||||
*.sdc
|
||||
*.sdn
|
||||
*.sdoc
|
||||
*.sdocx
|
||||
*.sea
|
||||
*.sen
|
||||
*.sfg
|
||||
*.sfm
|
||||
*.sfs
|
||||
*.sfx
|
||||
*.sh
|
||||
*.shar
|
||||
*.shk
|
||||
*.shr
|
||||
*.sifz
|
||||
*.sipa
|
||||
*.sit
|
||||
*.sitx
|
||||
*.smpf
|
||||
*.snagitstamps
|
||||
*.snappy
|
||||
*.snb
|
||||
*.snz
|
||||
*.spa
|
||||
*.spd
|
||||
*.spl
|
||||
*.spm
|
||||
*.spt
|
||||
*.sql
|
||||
*.sqf
|
||||
*.sqx
|
||||
*.sqz
|
||||
*.srep
|
||||
*.stg
|
||||
*.stkdoodlz
|
||||
*.stproj
|
||||
*.sy_
|
||||
*.tar.bz2
|
||||
*.tar.gz
|
||||
*.tar.gz2
|
||||
*.tar.lz
|
||||
*.tar.lzma
|
||||
*.tar.xz
|
||||
*.tar.z
|
||||
*.tar.zip
|
||||
*.taz
|
||||
*.tbz
|
||||
*.tbz2
|
||||
*.tcx
|
||||
*.text
|
||||
*.tg
|
||||
*.tgs
|
||||
*.tgz
|
||||
*.thumb
|
||||
*.tlz
|
||||
*.tlzma
|
||||
*.torrent
|
||||
*.tpsr
|
||||
*.trs
|
||||
*.txt
|
||||
*.tx_
|
||||
*.txz
|
||||
*.tz
|
||||
*.tzst
|
||||
*.ubz
|
||||
*.uc2
|
||||
*.ufdr
|
||||
*.ufs.uzip
|
||||
*.uha
|
||||
*.url
|
||||
*.uue
|
||||
*.uvm
|
||||
*.uzed
|
||||
*.uzip
|
||||
*.vbs
|
||||
*.vem
|
||||
*.vfs
|
||||
*.vib
|
||||
*.vip
|
||||
*.vmcz
|
||||
*.vms
|
||||
*.voca
|
||||
*.vpk
|
||||
*.vrpackage
|
||||
*.vsi
|
||||
*.vwi
|
||||
*.wa
|
||||
*.wacz
|
||||
*.waff
|
||||
*.war
|
||||
*.wastickers
|
||||
*.wdz
|
||||
*.whl
|
||||
*.wick
|
||||
*.wlb
|
||||
*.wot
|
||||
*.wsf
|
||||
*.wux
|
||||
*.xapk
|
||||
*.xar
|
||||
*.xcf.bz2
|
||||
*.xcf.gz
|
||||
*.xcf.xz
|
||||
*.xcfbz2
|
||||
*.xcfgz
|
||||
*.xcfxz
|
||||
*.xez
|
||||
*.xfp
|
||||
*.xip
|
||||
*.xml
|
||||
*.xmcdz
|
||||
*.xoj
|
||||
*.xopp
|
||||
*.xx
|
||||
*.xz
|
||||
*.xzm
|
||||
*.y
|
||||
*.yc
|
||||
*.yz1
|
||||
*.z
|
||||
*.z00
|
||||
*.z01
|
||||
*.z02
|
||||
*.z03
|
||||
*.z04
|
||||
*.zabw
|
||||
*.zap
|
||||
*.zed
|
||||
*.zfsendtotarget
|
||||
*.zhelp
|
||||
*.zi
|
||||
*.zi_
|
||||
*.zim
|
||||
*.zip
|
||||
*.zipx
|
||||
*.zix
|
||||
*.zl
|
||||
*.zoo
|
||||
*.zpaq
|
||||
*.zpi
|
||||
*.zsplit
|
||||
*.zst
|
||||
*.zw
|
||||
*.zwi
|
||||
*.zz
|
||||
*.zipx
|
||||
@@ -7,7 +7,6 @@ using Cleanuparr.Infrastructure.Helpers;
|
||||
using Cleanuparr.Infrastructure.Http.DynamicHttpClientSystem;
|
||||
using Cleanuparr.Infrastructure.Logging;
|
||||
using Cleanuparr.Infrastructure.Models;
|
||||
using Cleanuparr.Infrastructure.Services.Interfaces;
|
||||
using Cleanuparr.Infrastructure.Utilities;
|
||||
using Cleanuparr.Persistence;
|
||||
using Cleanuparr.Persistence.Models.Configuration;
|
||||
@@ -17,6 +16,7 @@ using Cleanuparr.Persistence.Models.Configuration.DownloadCleaner;
|
||||
using Cleanuparr.Persistence.Models.Configuration.General;
|
||||
using Cleanuparr.Persistence.Models.Configuration.Notification;
|
||||
using Cleanuparr.Persistence.Models.Configuration.QueueCleaner;
|
||||
using Infrastructure.Services.Interfaces;
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace Cleanuparr.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Health check endpoints for Docker and Kubernetes
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class HealthController : ControllerBase
|
||||
{
|
||||
private readonly HealthCheckService _healthCheckService;
|
||||
private readonly ILogger<HealthController> _logger;
|
||||
|
||||
public HealthController(HealthCheckService healthCheckService, ILogger<HealthController> logger)
|
||||
{
|
||||
_healthCheckService = healthCheckService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Basic liveness probe - checks if the application is running
|
||||
/// Used by Docker HEALTHCHECK and Kubernetes liveness probes
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[Route("/health")]
|
||||
public async Task<IActionResult> GetHealth()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _healthCheckService.CheckHealthAsync(
|
||||
registration => registration.Tags.Contains("liveness"));
|
||||
|
||||
return result.Status == HealthStatus.Healthy
|
||||
? Ok(new { status = "healthy", timestamp = DateTime.UtcNow })
|
||||
: StatusCode(503, new { status = "unhealthy", timestamp = DateTime.UtcNow });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Health check failed");
|
||||
return StatusCode(503, new { status = "unhealthy", error = "Health check failed", timestamp = DateTime.UtcNow });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Readiness probe - checks if the application is ready to serve traffic
|
||||
/// Used by Kubernetes readiness probes
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[Route("/health/ready")]
|
||||
public async Task<IActionResult> GetReadiness()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _healthCheckService.CheckHealthAsync(
|
||||
registration => registration.Tags.Contains("readiness"));
|
||||
|
||||
if (result.Status == HealthStatus.Healthy)
|
||||
{
|
||||
return Ok(new { status = "ready", timestamp = DateTime.UtcNow });
|
||||
}
|
||||
|
||||
// For readiness, we consider degraded as not ready
|
||||
return StatusCode(503, new {
|
||||
status = "not_ready",
|
||||
timestamp = DateTime.UtcNow,
|
||||
details = result.Entries.Where(e => e.Value.Status != HealthStatus.Healthy)
|
||||
.ToDictionary(e => e.Key, e => new {
|
||||
status = e.Value.Status.ToString().ToLowerInvariant(),
|
||||
description = e.Value.Description
|
||||
})
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Readiness check failed");
|
||||
return StatusCode(503, new { status = "not_ready", error = "Readiness check failed", timestamp = DateTime.UtcNow });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detailed health status - for monitoring and debugging
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[Route("/health/detailed")]
|
||||
public async Task<IActionResult> GetDetailedHealth()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _healthCheckService.CheckHealthAsync();
|
||||
|
||||
var response = new
|
||||
{
|
||||
status = result.Status.ToString().ToLowerInvariant(),
|
||||
timestamp = DateTime.UtcNow,
|
||||
totalDuration = result.TotalDuration.TotalMilliseconds,
|
||||
entries = result.Entries.ToDictionary(
|
||||
e => e.Key,
|
||||
e => new
|
||||
{
|
||||
status = e.Value.Status.ToString().ToLowerInvariant(),
|
||||
description = e.Value.Description,
|
||||
duration = e.Value.Duration.TotalMilliseconds,
|
||||
tags = e.Value.Tags,
|
||||
data = e.Value.Data,
|
||||
exception = e.Value.Exception?.Message
|
||||
})
|
||||
};
|
||||
|
||||
return result.Status == HealthStatus.Healthy
|
||||
? Ok(response)
|
||||
: StatusCode(503, response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Detailed health check failed");
|
||||
return StatusCode(503, new {
|
||||
status = "unhealthy",
|
||||
error = "Detailed health check failed",
|
||||
timestamp = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using Cleanuparr.Api.Models;
|
||||
using Cleanuparr.Infrastructure.Models;
|
||||
using Cleanuparr.Infrastructure.Services.Interfaces;
|
||||
using Infrastructure.Services.Interfaces;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Cleanuparr.Api.Controllers;
|
||||
|
||||
@@ -83,17 +83,9 @@ public static class MainDI
|
||||
/// </summary>
|
||||
private static IServiceCollection AddHealthServices(this IServiceCollection services) =>
|
||||
services
|
||||
// Register the existing health check service for download clients
|
||||
// Register the health check service
|
||||
.AddSingleton<IHealthCheckService, HealthCheckService>()
|
||||
|
||||
// Register the background service for periodic health checks
|
||||
.AddHostedService<HealthCheckBackgroundService>()
|
||||
|
||||
// Add ASP.NET Core health checks
|
||||
.AddHealthChecks()
|
||||
.AddCheck<ApplicationHealthCheck>("application", tags: ["liveness"])
|
||||
.AddCheck<DatabaseHealthCheck>("database", tags: ["readiness"])
|
||||
.AddCheck<FileSystemHealthCheck>("filesystem", tags: ["readiness"])
|
||||
.AddCheck<DownloadClientsHealthCheck>("download_clients", tags: ["readiness"])
|
||||
.Services;
|
||||
.AddHostedService<HealthCheckBackgroundService>();
|
||||
}
|
||||
@@ -12,9 +12,9 @@ using Cleanuparr.Infrastructure.Features.ItemStriker;
|
||||
using Cleanuparr.Infrastructure.Features.Security;
|
||||
using Cleanuparr.Infrastructure.Interceptors;
|
||||
using Cleanuparr.Infrastructure.Services;
|
||||
using Cleanuparr.Infrastructure.Services.Interfaces;
|
||||
using Cleanuparr.Persistence;
|
||||
using Infrastructure.Interceptors;
|
||||
using Infrastructure.Services.Interfaces;
|
||||
using Infrastructure.Verticals.Files;
|
||||
|
||||
namespace Cleanuparr.Api.DependencyInjection;
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using System.Text;
|
||||
|
||||
namespace Cleanuparr.Api;
|
||||
|
||||
/// <summary>
|
||||
/// Custom health check response writers for different formats
|
||||
/// </summary>
|
||||
public static class HealthCheckResponseWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes a minimal plain text response suitable for Docker health checks
|
||||
/// </summary>
|
||||
public static async Task WriteMinimalPlaintext(HttpContext context, HealthReport report)
|
||||
{
|
||||
context.Response.ContentType = "text/plain";
|
||||
|
||||
var status = report.Status switch
|
||||
{
|
||||
HealthStatus.Healthy => "healthy",
|
||||
HealthStatus.Degraded => "degraded",
|
||||
HealthStatus.Unhealthy => "unhealthy",
|
||||
_ => "unknown"
|
||||
};
|
||||
|
||||
await context.Response.WriteAsync(status, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
@@ -175,7 +175,7 @@ public class BackgroundJobManager : IHostedService
|
||||
IOperableTrigger triggerObj = (IOperableTrigger)TriggerBuilder.Create()
|
||||
.WithIdentity("ValidationTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule(cronExpression, x => x.WithMisfireHandlingInstructionDoNothing())
|
||||
.WithCronSchedule(cronExpression)
|
||||
.Build();
|
||||
|
||||
IReadOnlyList<DateTimeOffset> nextFireTimes = TriggerUtils.ComputeFireTimes(triggerObj, null, 2);
|
||||
@@ -197,26 +197,26 @@ public class BackgroundJobManager : IHostedService
|
||||
}
|
||||
}
|
||||
|
||||
// Create main cron trigger with consistent naming (matches JobManagementService)
|
||||
// Create cron trigger
|
||||
var trigger = TriggerBuilder.Create()
|
||||
.WithIdentity($"{typeName}-trigger")
|
||||
.ForJob(jobKey)
|
||||
.WithCronSchedule(cronExpression, x => x.WithMisfireHandlingInstructionDoNothing())
|
||||
.StartNow()
|
||||
.Build();
|
||||
|
||||
// Schedule the main trigger
|
||||
await _scheduler.ScheduleJob(trigger, cancellationToken);
|
||||
|
||||
// Trigger immediate execution for startup using a one-time trigger
|
||||
// Create startup trigger to run immediately
|
||||
var startupTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity($"{typeName}-startup-{DateTimeOffset.UtcNow.Ticks}")
|
||||
.WithIdentity($"{typeName}-startup-trigger")
|
||||
.ForJob(jobKey)
|
||||
.StartNow()
|
||||
.Build();
|
||||
|
||||
// Schedule job with both triggers
|
||||
await _scheduler.ScheduleJob(trigger, cancellationToken);
|
||||
await _scheduler.ScheduleJob(startupTrigger, cancellationToken);
|
||||
|
||||
_logger.LogInformation("Added trigger for job {name} with cron expression {CronExpression} and immediate startup execution",
|
||||
_logger.LogInformation("Added triggers for job {name} with cron expression {CronExpression}",
|
||||
typeName, cronExpression);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ using Cleanuparr.Api;
|
||||
using Cleanuparr.Api.DependencyInjection;
|
||||
using Cleanuparr.Infrastructure.Logging;
|
||||
using Cleanuparr.Shared.Helpers;
|
||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Serilog;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
@@ -149,19 +147,6 @@ logConfig.WriteTo.Sink(signalRSink);
|
||||
|
||||
Log.Logger = logConfig.CreateLogger();
|
||||
|
||||
// Configure health check endpoints before the API configuration
|
||||
app.MapHealthChecks("/health", new HealthCheckOptions
|
||||
{
|
||||
Predicate = registration => registration.Tags.Contains("liveness"),
|
||||
ResponseWriter = HealthCheckResponseWriter.WriteMinimalPlaintext
|
||||
});
|
||||
|
||||
app.MapHealthChecks("/health/ready", new HealthCheckOptions
|
||||
{
|
||||
Predicate = registration => registration.Tags.Contains("readiness"),
|
||||
ResponseWriter = HealthCheckResponseWriter.WriteMinimalPlaintext
|
||||
});
|
||||
|
||||
app.ConfigureApi();
|
||||
|
||||
await app.RunAsync();
|
||||
@@ -16,7 +16,6 @@
|
||||
<PackageReference Include="Mapster" Version="7.4.0" />
|
||||
<PackageReference Include="MassTransit.Abstractions" Version="8.4.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.6" />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Cleanuparr.Domain.Entities.Deluge.Response;
|
||||
using Cleanuparr.Infrastructure.Services;
|
||||
using Infrastructure.Services;
|
||||
|
||||
namespace Cleanuparr.Infrastructure.Extensions;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Cleanuparr.Infrastructure.Services;
|
||||
using Infrastructure.Services;
|
||||
using QBittorrent.Client;
|
||||
|
||||
namespace Cleanuparr.Infrastructure.Extensions;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Cleanuparr.Infrastructure.Services;
|
||||
using Infrastructure.Services;
|
||||
using Transmission.API.RPC.Entity;
|
||||
|
||||
namespace Cleanuparr.Infrastructure.Extensions;
|
||||
|
||||
@@ -168,7 +168,9 @@ public sealed class DelugeClient
|
||||
content.Headers.ContentType = new MediaTypeWithQualityHeaderValue("application/json");
|
||||
|
||||
UriBuilder uriBuilder = new(_config.Url);
|
||||
uriBuilder.Path = $"{uriBuilder.Path.TrimEnd('/')}/json";
|
||||
uriBuilder.Path = string.IsNullOrEmpty(_config.UrlBase)
|
||||
? $"{uriBuilder.Path.TrimEnd('/')}/json"
|
||||
: $"{uriBuilder.Path.TrimEnd('/')}/{_config.UrlBase.TrimStart('/').TrimEnd('/')}/json";
|
||||
var responseMessage = await _httpClient.PostAsync(uriBuilder.Uri, content);
|
||||
responseMessage.EnsureSuccessStatusCode();
|
||||
|
||||
|
||||
@@ -238,8 +238,6 @@ public partial class QBitService
|
||||
}
|
||||
|
||||
await _dryRunInterceptor.InterceptAsync(ChangeCategory, download.Hash, downloadCleanerConfig.UnlinkedTargetCategory);
|
||||
|
||||
await _eventPublisher.PublishCategoryChanged(download.Category, downloadCleanerConfig.UnlinkedTargetCategory, downloadCleanerConfig.UnlinkedUseTag);
|
||||
|
||||
if (downloadCleanerConfig.UnlinkedUseTag)
|
||||
{
|
||||
@@ -250,6 +248,8 @@ public partial class QBitService
|
||||
_logger.LogInformation("category changed for {name}", download.Name);
|
||||
download.Category = downloadCleanerConfig.UnlinkedTargetCategory;
|
||||
}
|
||||
|
||||
await _eventPublisher.PublishCategoryChanged(download.Category, downloadCleanerConfig.UnlinkedTargetCategory, downloadCleanerConfig.UnlinkedUseTag);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,9 @@ public partial class TransmissionService : DownloadService, ITransmissionService
|
||||
)
|
||||
{
|
||||
UriBuilder uriBuilder = new(_downloadClientConfig.Url);
|
||||
uriBuilder.Path = $"{uriBuilder.Path.TrimEnd('/')}/rpc";
|
||||
uriBuilder.Path = string.IsNullOrEmpty(_downloadClientConfig.UrlBase)
|
||||
? $"{uriBuilder.Path.TrimEnd('/')}/rpc"
|
||||
: $"{uriBuilder.Path.TrimEnd('/')}/{_downloadClientConfig.UrlBase.TrimStart('/').TrimEnd('/')}/rpc";
|
||||
_client = new Client(
|
||||
_httpClient,
|
||||
uriBuilder.Uri.ToString(),
|
||||
|
||||
@@ -132,7 +132,7 @@ public abstract class GenericHandler : IHandler
|
||||
.FirstAsync(x => x.Type == InstanceType.Lidarr));
|
||||
ContextProvider.Set(nameof(QueueCleanerConfig), await _dataContext.QueueCleanerConfigs.AsNoTracking().FirstAsync());
|
||||
ContextProvider.Set(nameof(ContentBlockerConfig), await _dataContext.ContentBlockerConfigs.AsNoTracking().FirstAsync());
|
||||
ContextProvider.Set(nameof(DownloadCleanerConfig), await _dataContext.DownloadCleanerConfigs.Include(x => x.Categories).AsNoTracking().FirstAsync());
|
||||
ContextProvider.Set(nameof(DownloadCleanerConfig), await _dataContext.DownloadCleanerConfigs.AsNoTracking().FirstAsync());
|
||||
ContextProvider.Set(nameof(DownloadClientConfig), await _dataContext.DownloadClients.AsNoTracking()
|
||||
.Where(x => x.Enabled)
|
||||
.ToListAsync());
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace Cleanuparr.Infrastructure.Health;
|
||||
|
||||
/// <summary>
|
||||
/// Basic application health check that verifies the application is running
|
||||
/// </summary>
|
||||
public class ApplicationHealthCheck : IHealthCheck
|
||||
{
|
||||
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Basic liveness check - if we can execute this, the app is running
|
||||
return Task.FromResult(HealthCheckResult.Healthy("Application is running"));
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using Cleanuparr.Persistence;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Cleanuparr.Infrastructure.Health;
|
||||
|
||||
/// <summary>
|
||||
/// Health check that verifies database connectivity
|
||||
/// </summary>
|
||||
public class DatabaseHealthCheck : IHealthCheck
|
||||
{
|
||||
private readonly DataContext _dataContext;
|
||||
private readonly ILogger<DatabaseHealthCheck> _logger;
|
||||
|
||||
public DatabaseHealthCheck(DataContext dataContext, ILogger<DatabaseHealthCheck> logger)
|
||||
{
|
||||
_dataContext = dataContext;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try to execute a simple query to verify database connectivity
|
||||
var canConnect = await _dataContext.Database.CanConnectAsync(cancellationToken);
|
||||
|
||||
if (!canConnect)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy("Cannot connect to database");
|
||||
}
|
||||
|
||||
// Optionally check if database schema is up to date
|
||||
var pendingMigrations = await _dataContext.Database.GetPendingMigrationsAsync(cancellationToken);
|
||||
if (pendingMigrations.Any())
|
||||
{
|
||||
_logger.LogWarning("Database has pending migrations: {migrations}", string.Join(", ", pendingMigrations));
|
||||
return HealthCheckResult.Degraded($"Database has {pendingMigrations.Count()} pending migrations");
|
||||
}
|
||||
|
||||
return HealthCheckResult.Healthy("Database connection successful");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Database health check failed");
|
||||
return HealthCheckResult.Unhealthy("Database health check failed", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Cleanuparr.Infrastructure.Health;
|
||||
|
||||
/// <summary>
|
||||
/// Health check that verifies download clients are healthy
|
||||
/// </summary>
|
||||
public class DownloadClientsHealthCheck : IHealthCheck
|
||||
{
|
||||
private readonly IHealthCheckService _healthCheckService;
|
||||
private readonly ILogger<DownloadClientsHealthCheck> _logger;
|
||||
|
||||
public DownloadClientsHealthCheck(IHealthCheckService healthCheckService, ILogger<DownloadClientsHealthCheck> logger)
|
||||
{
|
||||
_healthCheckService = healthCheckService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get current health status of all clients without triggering new checks
|
||||
var allClientHealth = _healthCheckService.GetAllClientHealth();
|
||||
|
||||
if (!allClientHealth.Any())
|
||||
{
|
||||
// No clients configured - this might be ok depending on the deployment
|
||||
return HealthCheckResult.Healthy("No download clients configured");
|
||||
}
|
||||
|
||||
var healthyClients = allClientHealth.Values.Where(h => h.IsHealthy).ToList();
|
||||
var unhealthyClients = allClientHealth.Values.Where(h => !h.IsHealthy).ToList();
|
||||
var totalClients = allClientHealth.Count;
|
||||
|
||||
if (unhealthyClients.Any())
|
||||
{
|
||||
var unhealthyNames = string.Join(", ", unhealthyClients.Select(c => c.ClientName));
|
||||
var message = $"{unhealthyClients.Count}/{totalClients} download clients unhealthy: {unhealthyNames}";
|
||||
|
||||
// If more than half are unhealthy, consider it unhealthy
|
||||
if (unhealthyClients.Count > totalClients / 2)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy(message);
|
||||
}
|
||||
|
||||
// Otherwise, it's degraded
|
||||
return HealthCheckResult.Degraded(message);
|
||||
}
|
||||
|
||||
return HealthCheckResult.Healthy($"All {totalClients} download clients are healthy");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Download clients health check failed");
|
||||
return HealthCheckResult.Unhealthy("Download clients health check failed", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
using Cleanuparr.Shared.Helpers;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Cleanuparr.Infrastructure.Health;
|
||||
|
||||
/// <summary>
|
||||
/// Health check that verifies file system access to critical directories
|
||||
/// </summary>
|
||||
public class FileSystemHealthCheck : IHealthCheck
|
||||
{
|
||||
private readonly ILogger<FileSystemHealthCheck> _logger;
|
||||
|
||||
public FileSystemHealthCheck(ILogger<FileSystemHealthCheck> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var issues = new List<string>();
|
||||
|
||||
// Check config directory access
|
||||
var configPath = ConfigurationPathProvider.GetConfigPath();
|
||||
if (!CheckDirectoryAccess(configPath, "config"))
|
||||
{
|
||||
issues.Add($"Cannot access config directory: {configPath}");
|
||||
}
|
||||
|
||||
// Check current working directory
|
||||
var currentDir = Directory.GetCurrentDirectory();
|
||||
if (!CheckDirectoryAccess(currentDir, "working"))
|
||||
{
|
||||
issues.Add($"Cannot access working directory: {currentDir}");
|
||||
}
|
||||
|
||||
if (issues.Any())
|
||||
{
|
||||
var message = $"File system issues detected: {string.Join(", ", issues)}";
|
||||
return Task.FromResult(HealthCheckResult.Unhealthy(message));
|
||||
}
|
||||
|
||||
return Task.FromResult(HealthCheckResult.Healthy("File system access verified"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "File system health check failed");
|
||||
return Task.FromResult(HealthCheckResult.Unhealthy("File system health check failed", ex));
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckDirectoryAccess(string path, string description)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
_logger.LogWarning("Directory does not exist: {path} ({description})", path, description);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to enumerate directory contents
|
||||
_ = Directory.GetFiles(path).Take(1).ToList();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Cannot access {description} directory: {path}", description, path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Net;
|
||||
using Cleanuparr.Infrastructure.Services;
|
||||
using Infrastructure.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@@ -9,3 +9,29 @@ public enum JobType
|
||||
ContentBlocker,
|
||||
DownloadCleaner
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for JobType enum
|
||||
/// </summary>
|
||||
public static class JobTypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a JobType enum to its string representation
|
||||
/// </summary>
|
||||
/// <param name="jobType">The job type to convert</param>
|
||||
/// <returns>String representation of the job type</returns>
|
||||
public static string ToJobName(this JobType jobType) => jobType.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Parses a string to JobType enum
|
||||
/// </summary>
|
||||
/// <param name="jobName">The job name to parse</param>
|
||||
/// <returns>JobType if successful, null if parsing failed</returns>
|
||||
public static JobType? TryParseJobType(string jobName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(jobName))
|
||||
return null;
|
||||
|
||||
return Enum.TryParse<JobType>(jobName, true, out var result) ? result : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Cleanuparr.Infrastructure.Models;
|
||||
using Quartz;
|
||||
|
||||
namespace Cleanuparr.Infrastructure.Services.Interfaces;
|
||||
namespace Infrastructure.Services.Interfaces;
|
||||
|
||||
public interface IJobManagementService
|
||||
{
|
||||
@@ -9,9 +8,7 @@ public interface IJobManagementService
|
||||
Task<bool> StopJob(JobType jobType);
|
||||
Task<bool> PauseJob(JobType jobType);
|
||||
Task<bool> ResumeJob(JobType jobType);
|
||||
Task<bool> TriggerJobOnce(JobType jobType);
|
||||
Task<IReadOnlyList<JobInfo>> GetAllJobs(IScheduler? scheduler = null);
|
||||
Task<IReadOnlyList<JobInfo>> GetAllJobs();
|
||||
Task<JobInfo> GetJob(JobType jobType);
|
||||
Task<bool> UpdateJobSchedule(JobType jobType, JobSchedule schedule);
|
||||
Task<ITrigger?> GetMainTrigger(JobType jobType);
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Cleanuparr.Infrastructure.Models;
|
||||
using Cleanuparr.Infrastructure.Services.Interfaces;
|
||||
using Cleanuparr.Infrastructure.Utilities;
|
||||
using Infrastructure.Services.Interfaces;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Quartz;
|
||||
using Quartz.Impl.Matchers;
|
||||
using Quartz.Spi;
|
||||
|
||||
namespace Cleanuparr.Infrastructure.Services;
|
||||
|
||||
@@ -23,7 +22,7 @@ public class JobManagementService : IJobManagementService
|
||||
|
||||
public async Task<bool> StartJob(JobType jobType, JobSchedule? schedule = null, string? directCronExpression = null)
|
||||
{
|
||||
string jobName = jobType.ToString();
|
||||
string jobName = jobType.ToJobName();
|
||||
string? cronExpression = null;
|
||||
|
||||
// Validate and set the cron expression
|
||||
@@ -60,48 +59,60 @@ public class JobManagementService : IJobManagementService
|
||||
// Check if job exists, create it if it doesn't
|
||||
if (!await scheduler.CheckExists(jobKey))
|
||||
{
|
||||
_logger.LogError("Job {name} does not exist in scheduler. " +
|
||||
"Jobs should be created at startup by BackgroundJobManager.", jobName);
|
||||
return false;
|
||||
_logger.LogInformation("Job {name} does not exist, creating it", jobName);
|
||||
|
||||
// Create the job based on its type
|
||||
if (!await CreateJobIfNotExists(scheduler, jobType, jobKey))
|
||||
{
|
||||
_logger.LogError("Failed to create job {name}", jobName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Store the job key for later use
|
||||
_jobKeys.TryAdd(jobName, jobKey);
|
||||
|
||||
// Clean up all existing triggers for this job first
|
||||
await CleanupAllTriggersForJob(scheduler, jobKey);
|
||||
|
||||
// If cron expression is provided, create and schedule the main trigger
|
||||
// If cron expression is provided, update the trigger
|
||||
if (!string.IsNullOrEmpty(cronExpression))
|
||||
{
|
||||
var triggerKey = new TriggerKey($"{jobName}-trigger");
|
||||
var newTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity(triggerKey)
|
||||
.ForJob(jobKey)
|
||||
.WithCronSchedule(cronExpression, x => x.WithMisfireHandlingInstructionDoNothing())
|
||||
.Build();
|
||||
var triggerKey = new TriggerKey($"{jobName}Trigger");
|
||||
var existingTrigger = await scheduler.GetTrigger(triggerKey);
|
||||
|
||||
await scheduler.ScheduleJob(newTrigger);
|
||||
|
||||
// Compute next fire time for logging
|
||||
IReadOnlyList<DateTimeOffset> nextFireTimes = TriggerUtils.ComputeFireTimes((IOperableTrigger)newTrigger, null, 1);
|
||||
_logger.LogInformation("Job {name} scheduled with cron expression '{cronExpression}', next run at {nextRunTime}",
|
||||
jobName, cronExpression, nextFireTimes.FirstOrDefault().LocalDateTime);
|
||||
|
||||
// Optionally trigger immediate execution for startup
|
||||
// await TriggerJobImmediately(scheduler, jobKey, "startup");
|
||||
if (existingTrigger != null)
|
||||
{
|
||||
var newTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity(triggerKey)
|
||||
.ForJob(jobKey)
|
||||
.WithCronSchedule(cronExpression)
|
||||
.Build();
|
||||
|
||||
await scheduler.RescheduleJob(triggerKey, newTrigger);
|
||||
}
|
||||
else
|
||||
{
|
||||
var trigger = TriggerBuilder.Create()
|
||||
.WithIdentity(triggerKey)
|
||||
.ForJob(jobKey)
|
||||
.WithCronSchedule(cronExpression)
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(trigger);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If no cron expression, create a one-time trigger to run now
|
||||
var oneTimeTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity($"{jobName}-onetime-trigger")
|
||||
.ForJob(jobKey)
|
||||
.StartNow()
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(oneTimeTrigger);
|
||||
_logger.LogInformation("Job {name} scheduled for immediate one-time execution", jobName);
|
||||
// If no trigger exists, create a simple one-time trigger
|
||||
var triggers = await scheduler.GetTriggersOfJob(jobKey);
|
||||
if (!triggers.Any())
|
||||
{
|
||||
var trigger = TriggerBuilder.Create()
|
||||
.WithIdentity($"{jobName}Trigger")
|
||||
.ForJob(jobKey)
|
||||
.StartNow()
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(trigger);
|
||||
}
|
||||
}
|
||||
|
||||
// Resume the job if it's paused
|
||||
@@ -117,86 +128,21 @@ public class JobManagementService : IJobManagementService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up all existing triggers for a job to ensure a clean state
|
||||
/// Creates a job in the scheduler if it doesn't exist based on the job type.
|
||||
/// Note: Since this is in the Infrastructure layer, we cannot directly reference Application layer job types.
|
||||
/// Job creation is now handled at startup by BackgroundJobManager.
|
||||
/// </summary>
|
||||
private async Task CleanupAllTriggersForJob(IScheduler scheduler, JobKey jobKey)
|
||||
private Task<bool> CreateJobIfNotExists(IScheduler scheduler, JobType jobType, JobKey jobKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
var existingTriggers = await scheduler.GetTriggersOfJob(jobKey);
|
||||
foreach (var trigger in existingTriggers)
|
||||
{
|
||||
await scheduler.UnscheduleJob(trigger.Key);
|
||||
_logger.LogDebug("Removed existing trigger {triggerKey} for job {jobKey}",
|
||||
trigger.Key.Name, jobKey.Name);
|
||||
}
|
||||
|
||||
if (existingTriggers.Any())
|
||||
{
|
||||
_logger.LogDebug("Cleaned up {count} existing triggers for job {jobName}",
|
||||
existingTriggers.Count, jobKey.Name);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Error cleaning up triggers for job {jobName}", jobKey.Name);
|
||||
}
|
||||
_logger.LogError("Job {jobName} of type {jobType} does not exist in scheduler. " +
|
||||
"Jobs should be created at startup by BackgroundJobManager, regardless of enabled status.",
|
||||
jobKey.Name, jobType);
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers a job immediately with a one-time trigger
|
||||
/// </summary>
|
||||
private async Task TriggerJobImmediately(IScheduler scheduler, JobKey jobKey, string reason)
|
||||
{
|
||||
try
|
||||
{
|
||||
var immediateTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity($"{jobKey.Name}-immediate-{reason}-{DateTimeOffset.UtcNow.Ticks}")
|
||||
.ForJob(jobKey)
|
||||
.StartNow()
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(immediateTrigger);
|
||||
_logger.LogDebug("Triggered job {jobName} immediately for reason: {reason}", jobKey.Name, reason);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to trigger job {jobName} immediately", jobKey.Name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the main scheduled trigger for a job (excludes one-time triggers)
|
||||
/// </summary>
|
||||
public async Task<ITrigger?> GetMainTrigger(JobType jobType)
|
||||
{
|
||||
string jobName = jobType.ToString();
|
||||
try
|
||||
{
|
||||
var scheduler = await _schedulerFactory.GetScheduler();
|
||||
var jobKey = new JobKey(jobName);
|
||||
|
||||
if (!await scheduler.CheckExists(jobKey))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Look for the main trigger (follows our naming convention)
|
||||
var mainTriggerKey = new TriggerKey($"{jobName}-trigger");
|
||||
return await scheduler.GetTrigger(mainTriggerKey);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting main trigger for job {jobName}", jobName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task<bool> StopJob(JobType jobType)
|
||||
{
|
||||
string jobName = jobType.ToString();
|
||||
string jobName = jobType.ToJobName();
|
||||
try
|
||||
{
|
||||
var scheduler = await _schedulerFactory.GetScheduler();
|
||||
@@ -208,8 +154,12 @@ public class JobManagementService : IJobManagementService
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clean up all triggers for this job (reuse our centralized method)
|
||||
await CleanupAllTriggersForJob(scheduler, jobKey);
|
||||
// Unschedule all triggers for this job
|
||||
var triggers = await scheduler.GetTriggersOfJob(jobKey);
|
||||
foreach (var trigger in triggers)
|
||||
{
|
||||
await scheduler.UnscheduleJob(trigger.Key);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Job {name} stopped successfully", jobName);
|
||||
return true;
|
||||
@@ -223,7 +173,7 @@ public class JobManagementService : IJobManagementService
|
||||
|
||||
public async Task<bool> PauseJob(JobType jobType)
|
||||
{
|
||||
string jobName = jobType.ToString();
|
||||
string jobName = jobType.ToJobName();
|
||||
try
|
||||
{
|
||||
var scheduler = await _schedulerFactory.GetScheduler();
|
||||
@@ -248,7 +198,7 @@ public class JobManagementService : IJobManagementService
|
||||
|
||||
public async Task<bool> ResumeJob(JobType jobType)
|
||||
{
|
||||
string jobName = jobType.ToString();
|
||||
string jobName = jobType.ToJobName();
|
||||
try
|
||||
{
|
||||
var scheduler = await _schedulerFactory.GetScheduler();
|
||||
@@ -271,11 +221,11 @@ public class JobManagementService : IJobManagementService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<JobInfo>> GetAllJobs(IScheduler? scheduler = null)
|
||||
public async Task<IReadOnlyList<JobInfo>> GetAllJobs()
|
||||
{
|
||||
try
|
||||
{
|
||||
scheduler ??= await _schedulerFactory.GetScheduler();
|
||||
var scheduler = await _schedulerFactory.GetScheduler();
|
||||
var result = new List<JobInfo>();
|
||||
|
||||
var jobGroups = await scheduler.GetJobGroupNames();
|
||||
@@ -333,7 +283,7 @@ public class JobManagementService : IJobManagementService
|
||||
|
||||
public async Task<JobInfo> GetJob(JobType jobType)
|
||||
{
|
||||
string jobName = jobType.ToString();
|
||||
string jobName = jobType.ToJobName();
|
||||
try
|
||||
{
|
||||
var scheduler = await _schedulerFactory.GetScheduler();
|
||||
@@ -389,37 +339,12 @@ public class JobManagementService : IJobManagementService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> TriggerJobOnce(JobType jobType)
|
||||
{
|
||||
string jobName = jobType.ToString();
|
||||
try
|
||||
{
|
||||
var scheduler = await _schedulerFactory.GetScheduler();
|
||||
var jobKey = new JobKey(jobName);
|
||||
|
||||
if (!await scheduler.CheckExists(jobKey))
|
||||
{
|
||||
_logger.LogError("Job {name} does not exist", jobName);
|
||||
return false;
|
||||
}
|
||||
|
||||
await TriggerJobImmediately(scheduler, jobKey, "manual");
|
||||
_logger.LogInformation("Job {name} triggered for one-time execution", jobName);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error triggering job {jobName}", jobName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateJobSchedule(JobType jobType, JobSchedule schedule)
|
||||
{
|
||||
if (schedule == null)
|
||||
throw new ArgumentNullException(nameof(schedule));
|
||||
|
||||
string jobName = jobType.ToString();
|
||||
string jobName = jobType.ToJobName();
|
||||
string cronExpression = schedule.ToCronExpression();
|
||||
|
||||
try
|
||||
@@ -433,18 +358,24 @@ public class JobManagementService : IJobManagementService
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clean up all existing triggers for this job
|
||||
await CleanupAllTriggersForJob(scheduler, jobKey);
|
||||
|
||||
// Create new trigger with consistent naming
|
||||
var triggerKey = new TriggerKey($"{jobName}-trigger");
|
||||
var triggerKey = new TriggerKey($"{jobName}Trigger");
|
||||
var existingTrigger = await scheduler.GetTrigger(triggerKey);
|
||||
|
||||
var newTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity(triggerKey)
|
||||
.ForJob(jobKey)
|
||||
.WithCronSchedule(cronExpression, x => x.WithMisfireHandlingInstructionDoNothing())
|
||||
.WithSchedule(SimpleScheduleBuilder.RepeatSecondlyForever(10))
|
||||
.WithCronSchedule(cronExpression)
|
||||
.Build();
|
||||
|
||||
await scheduler.ScheduleJob(newTrigger);
|
||||
if (existingTrigger != null)
|
||||
{
|
||||
await scheduler.RescheduleJob(triggerKey, newTrigger);
|
||||
}
|
||||
else
|
||||
{
|
||||
await scheduler.ScheduleJob(newTrigger);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Job {name} schedule updated successfully to {cronExpression}", jobName, cronExpression);
|
||||
return true;
|
||||
|
||||
@@ -35,7 +35,7 @@ public static class CronValidationHelper
|
||||
IOperableTrigger triggerObj = (IOperableTrigger)TriggerBuilder.Create()
|
||||
.WithIdentity("ValidationTrigger")
|
||||
.StartNow()
|
||||
.WithCronSchedule(cronExpression, x => x.WithMisfireHandlingInstructionDoNothing())
|
||||
.WithCronSchedule(cronExpression)
|
||||
.Build();
|
||||
|
||||
IReadOnlyList<DateTimeOffset> nextFireTimes = TriggerUtils.ComputeFireTimes(triggerObj, null, 2);
|
||||
|
||||
@@ -23,10 +23,10 @@ export class ApplicationPathService {
|
||||
*/
|
||||
getDocumentationBaseUrl(): string {
|
||||
if (isDevMode()) {
|
||||
return 'http://localhost:3000/Cleanuparr';
|
||||
return 'http://localhost:3000';
|
||||
}
|
||||
|
||||
return 'https://cleanuparr.github.io/Cleanuparr';
|
||||
return 'https://cleanuparr.github.io/cleanuparr';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +59,7 @@ export class ApplicationPathService {
|
||||
*/
|
||||
buildDocumentationUrl(section: string, fieldAnchor?: string): string {
|
||||
const baseUrl = this.getDocumentationBaseUrl();
|
||||
let url = `${baseUrl}/docs/configuration/${section}`;
|
||||
let url = `${baseUrl}/cleanuparr/docs/configuration/${section}`;
|
||||
|
||||
if (fieldAnchor) {
|
||||
url += `#${fieldAnchor}`;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<!-- Sidebar Navigation -->
|
||||
<nav class="nav-menu">
|
||||
<!-- Project Sponsors Link -->
|
||||
<a href="https://cleanuparr.github.io/Cleanuparr/support" class="nav-item sponsor-link" target="_blank" rel="noopener noreferrer">
|
||||
<a href="https://cleanuparr.github.io/cleanuparr/support" class="nav-item sponsor-link" target="_blank" rel="noopener noreferrer">
|
||||
<div class="nav-icon-wrapper heart-icon">
|
||||
<i class="pi pi-heart"></i>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
<h2 class="card-title m-0">Content Blocker Configuration</h2>
|
||||
<span class="card-subtitle">Configure automatic content filtering and blocking</span>
|
||||
</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-ban text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -137,16 +140,7 @@
|
||||
<p-accordion [multiple]="false" [value]="activeAccordionIndices" styleClass="mt-3">
|
||||
<!-- Sonarr Settings -->
|
||||
<p-accordion-panel [disabled]="!contentBlockerForm.get('enabled')?.value" [value]="0">
|
||||
<p-accordion-header>
|
||||
<ng-template #toggleicon let-active="active">
|
||||
@if (active) {
|
||||
<i class="pi pi-chevron-up"></i>
|
||||
} @else {
|
||||
<i class="pi pi-chevron-down"></i>
|
||||
}
|
||||
</ng-template>
|
||||
Sonarr Settings
|
||||
</p-accordion-header>
|
||||
<p-accordion-header>Sonarr Settings</p-accordion-header>
|
||||
<p-accordion-content>
|
||||
<div formGroupName="sonarr">
|
||||
<div class="field-row">
|
||||
@@ -207,16 +201,7 @@
|
||||
|
||||
<!-- Radarr Settings -->
|
||||
<p-accordion-panel [disabled]="!contentBlockerForm.get('enabled')?.value" [value]="1">
|
||||
<p-accordion-header>
|
||||
<ng-template #toggleicon let-active="active">
|
||||
@if (active) {
|
||||
<i class="pi pi-chevron-up"></i>
|
||||
} @else {
|
||||
<i class="pi pi-chevron-down"></i>
|
||||
}
|
||||
</ng-template>
|
||||
Radarr Settings
|
||||
</p-accordion-header>
|
||||
<p-accordion-header>Radarr Settings</p-accordion-header>
|
||||
<p-accordion-content>
|
||||
<div formGroupName="radarr">
|
||||
<div class="field-row">
|
||||
@@ -277,16 +262,7 @@
|
||||
|
||||
<!-- Lidarr Settings -->
|
||||
<p-accordion-panel [disabled]="!contentBlockerForm.get('enabled')?.value" [value]="2">
|
||||
<p-accordion-header>
|
||||
<ng-template #toggleicon let-active="active">
|
||||
@if (active) {
|
||||
<i class="pi pi-chevron-up"></i>
|
||||
} @else {
|
||||
<i class="pi pi-chevron-down"></i>
|
||||
}
|
||||
</ng-template>
|
||||
Lidarr Settings
|
||||
</p-accordion-header>
|
||||
<p-accordion-header>Lidarr Settings</p-accordion-header>
|
||||
<p-accordion-content>
|
||||
<div formGroupName="lidarr">
|
||||
<div class="field-row">
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
<h2 class="card-title m-0">Download Cleaner Configuration</h2>
|
||||
<span class="card-subtitle">Configure automatic download cleanup</span>
|
||||
</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-cog text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -110,16 +113,7 @@
|
||||
<p-accordion [multiple]="false" [value]="activeAccordionIndices" styleClass="mt-3">
|
||||
<!-- Seeding Settings -->
|
||||
<p-accordion-panel [disabled]="!downloadCleanerForm.get('enabled')?.value" [value]="0">
|
||||
<p-accordion-header>
|
||||
<ng-template #toggleicon let-active="active">
|
||||
@if (active) {
|
||||
<i class="pi pi-chevron-up"></i>
|
||||
} @else {
|
||||
<i class="pi pi-chevron-down"></i>
|
||||
}
|
||||
</ng-template>
|
||||
Seeding Settings
|
||||
</p-accordion-header>
|
||||
<p-accordion-header>Seeding Settings</p-accordion-header>
|
||||
<p-accordion-content>
|
||||
<!-- Delete Private Option -->
|
||||
<div class="field-row">
|
||||
@@ -236,16 +230,7 @@
|
||||
|
||||
<!-- Unlinked Download Settings -->
|
||||
<p-accordion-panel [disabled]="!downloadCleanerForm.get('enabled')?.value" [value]="1">
|
||||
<p-accordion-header>
|
||||
<ng-template #toggleicon let-active="active">
|
||||
@if (active) {
|
||||
<i class="pi pi-chevron-up"></i>
|
||||
} @else {
|
||||
<i class="pi pi-chevron-down"></i>
|
||||
}
|
||||
</ng-template>
|
||||
Unlinked Download Settings
|
||||
</p-accordion-header>
|
||||
<p-accordion-header>Unlinked Download Settings</p-accordion-header>
|
||||
<p-accordion-content>
|
||||
<div class="field-row">
|
||||
<label class="field-label">
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
<h2 class="card-title m-0">Download Clients</h2>
|
||||
<span class="card-subtitle">Manage download client instances</span>
|
||||
</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-download text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
||||
@@ -363,9 +363,8 @@ export class DownloadClientSettingsComponent implements OnDestroy, CanComponentD
|
||||
const clientType = this.clientForm.get('type')?.value;
|
||||
const hostControl = this.clientForm.get('host');
|
||||
const usernameControl = this.clientForm.get('username');
|
||||
const urlBaseControl = this.clientForm.get('urlBase');
|
||||
|
||||
if (!hostControl || !usernameControl || !urlBaseControl) return;
|
||||
if (!hostControl || !usernameControl) return;
|
||||
|
||||
hostControl.setValidators([
|
||||
Validators.required,
|
||||
@@ -378,11 +377,6 @@ export class DownloadClientSettingsComponent implements OnDestroy, CanComponentD
|
||||
usernameControl.clearValidators();
|
||||
}
|
||||
|
||||
// Set default URL base for Transmission
|
||||
if (clientType === DownloadClientType.Transmission) {
|
||||
urlBaseControl.setValue('transmission');
|
||||
}
|
||||
|
||||
// Update validation state
|
||||
hostControl.updateValueAndValidity();
|
||||
usernameControl.updateValueAndValidity();
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
<h2 class="card-title m-0">General Configuration</h2>
|
||||
<span class="card-subtitle">Configure general application settings</span>
|
||||
</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-cog text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
||||
@@ -22,9 +22,12 @@
|
||||
<ng-template pTemplate="header">
|
||||
<div class="flex align-items-center justify-content-between p-3 border-bottom-1 surface-border">
|
||||
<div class="header-title-container">
|
||||
<h2 class="card-title m-0">Lidarr Settings</h2>
|
||||
<h2 class="card-title m-0">Global Settings</h2>
|
||||
<span class="card-subtitle">Configure general Lidarr integration settings</span>
|
||||
</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-cog text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -70,6 +73,9 @@
|
||||
<h2 class="card-title m-0">Instances</h2>
|
||||
<span class="card-subtitle">Manage Lidarr server instances</span>
|
||||
</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-cog text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
<h2 class="card-title m-0">Notification Configuration</h2>
|
||||
<span class="card-subtitle">Configure notification settings for Notifiarr and Apprise</span>
|
||||
</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-bell text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -33,11 +36,11 @@
|
||||
<!-- API Key -->
|
||||
<div class="field-row">
|
||||
<label class="field-label">
|
||||
API Key
|
||||
<i class="pi pi-info-circle field-info-icon"
|
||||
(click)="openFieldDocs('notifiarr.apiKey')"
|
||||
title="View documentation for Notifiarr API key setup">
|
||||
</i>
|
||||
API Key
|
||||
</label>
|
||||
<div class="field-input">
|
||||
<input type="text" pInputText formControlName="apiKey" inputId="notifiarrApiKey" placeholder="Enter Notifiarr API key" />
|
||||
@@ -48,10 +51,6 @@
|
||||
<!-- Channel ID -->
|
||||
<div class="field-row">
|
||||
<label class="field-label">
|
||||
<i class="pi pi-info-circle field-info-icon"
|
||||
(click)="openFieldDocs('notifiarr.channelId')"
|
||||
title="View documentation for Discord channel ID setup">
|
||||
</i>
|
||||
Channel ID
|
||||
<i class="pi pi-info-circle field-info-icon"
|
||||
(click)="openFieldDocs('notifiarr.channelId')"
|
||||
@@ -67,11 +66,11 @@
|
||||
<!-- Event Triggers -->
|
||||
<div class="field-row">
|
||||
<label class="field-label">
|
||||
Event Triggers
|
||||
<i class="pi pi-info-circle field-info-icon"
|
||||
(click)="openFieldDocs('eventTriggers')"
|
||||
title="View documentation for notification event types">
|
||||
</i>
|
||||
Event Triggers
|
||||
</label>
|
||||
<div class="field-input">
|
||||
<div class="flex flex-column gap-2">
|
||||
@@ -113,11 +112,11 @@
|
||||
<!-- URL -->
|
||||
<div class="field-row">
|
||||
<label class="field-label">
|
||||
URL
|
||||
<i class="pi pi-info-circle field-info-icon"
|
||||
(click)="openFieldDocs('apprise.url')"
|
||||
title="View documentation for Apprise server URL setup">
|
||||
</i>
|
||||
URL
|
||||
</label>
|
||||
<div class="field-input">
|
||||
<input type="text" pInputText formControlName="url" inputId="appriseUrl" placeholder="Enter Apprise URL" />
|
||||
@@ -128,11 +127,11 @@
|
||||
<!-- Key -->
|
||||
<div class="field-row">
|
||||
<label class="field-label">
|
||||
Key
|
||||
<i class="pi pi-info-circle field-info-icon"
|
||||
(click)="openFieldDocs('apprise.key')"
|
||||
title="View documentation for Apprise configuration key">
|
||||
</i>
|
||||
Key
|
||||
</label>
|
||||
<div class="field-input">
|
||||
<input type="text" pInputText formControlName="key" inputId="appriseKey" placeholder="Enter key" />
|
||||
@@ -143,11 +142,11 @@
|
||||
<!-- Event Triggers -->
|
||||
<div class="field-row">
|
||||
<label class="field-label">
|
||||
Event Triggers
|
||||
<i class="pi pi-info-circle field-info-icon"
|
||||
(click)="openFieldDocs('eventTriggers')"
|
||||
title="View documentation for notification event types">
|
||||
</i>
|
||||
Event Triggers
|
||||
</label>
|
||||
<div class="field-input">
|
||||
<div class="flex flex-column gap-2">
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
<h2 class="card-title m-0">Queue Cleaner Configuration</h2>
|
||||
<span class="card-subtitle">Configure automatic arr queue cleanup</span>
|
||||
</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-cog text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -113,16 +116,7 @@
|
||||
<p-accordion [multiple]="false" [value]="activeAccordionIndices" styleClass="mt-3">
|
||||
<!-- Failed Import Settings -->
|
||||
<p-accordion-panel [disabled]="!queueCleanerForm.get('enabled')?.value" [value]="0">
|
||||
<p-accordion-header>
|
||||
<ng-template #toggleicon let-active="active">
|
||||
@if (active) {
|
||||
<i class="pi pi-chevron-up"></i>
|
||||
} @else {
|
||||
<i class="pi pi-chevron-down"></i>
|
||||
}
|
||||
</ng-template>
|
||||
Failed Import Settings
|
||||
</p-accordion-header>
|
||||
<p-accordion-header>Failed Import Settings</p-accordion-header>
|
||||
<p-accordion-content>
|
||||
<div class="field-row" formGroupName="failedImport">
|
||||
<label class="field-label">
|
||||
@@ -202,16 +196,7 @@
|
||||
|
||||
<!-- Stalled Settings -->
|
||||
<p-accordion-panel [disabled]="!queueCleanerForm.get('enabled')?.value" [value]="1">
|
||||
<p-accordion-header>
|
||||
<ng-template #toggleicon let-active="active">
|
||||
@if (active) {
|
||||
<i class="pi pi-chevron-up"></i>
|
||||
} @else {
|
||||
<i class="pi pi-chevron-down"></i>
|
||||
}
|
||||
</ng-template>
|
||||
Stalled Download Settings
|
||||
</p-accordion-header>
|
||||
<p-accordion-header>Stalled Download Settings</p-accordion-header>
|
||||
<p-accordion-content>
|
||||
<div class="field-row" formGroupName="stalled">
|
||||
<label class="field-label">
|
||||
@@ -285,16 +270,7 @@
|
||||
|
||||
<!-- Downloading Metadata Settings -->
|
||||
<p-accordion-panel [disabled]="!queueCleanerForm.get('enabled')?.value" [value]="2">
|
||||
<p-accordion-header>
|
||||
<ng-template #toggleicon let-active="active">
|
||||
@if (active) {
|
||||
<i class="pi pi-chevron-up"></i>
|
||||
} @else {
|
||||
<i class="pi pi-chevron-down"></i>
|
||||
}
|
||||
</ng-template>
|
||||
Downloading Metadata Settings (qBittorrent only)
|
||||
</p-accordion-header>
|
||||
<p-accordion-header>Downloading Metadata Settings (qBittorrent only)</p-accordion-header>
|
||||
<p-accordion-content>
|
||||
<div class="field-row" formGroupName="stalled">
|
||||
<label class="field-label">
|
||||
@@ -326,16 +302,7 @@
|
||||
|
||||
<!-- Slow Download Settings -->
|
||||
<p-accordion-panel [disabled]="!queueCleanerForm.get('enabled')?.value" [value]="3">
|
||||
<p-accordion-header>
|
||||
<ng-template #toggleicon let-active="active">
|
||||
@if (active) {
|
||||
<i class="pi pi-chevron-up"></i>
|
||||
} @else {
|
||||
<i class="pi pi-chevron-down"></i>
|
||||
}
|
||||
</ng-template>
|
||||
Slow Download Settings
|
||||
</p-accordion-header>
|
||||
<p-accordion-header>Slow Download Settings</p-accordion-header>
|
||||
<p-accordion-content>
|
||||
<div class="field-row" formGroupName="slow">
|
||||
<label class="field-label">
|
||||
|
||||
@@ -22,9 +22,12 @@
|
||||
<ng-template pTemplate="header">
|
||||
<div class="flex align-items-center justify-content-between p-3 border-bottom-1 surface-border">
|
||||
<div class="header-title-container">
|
||||
<h2 class="card-title m-0">Radarr Settings</h2>
|
||||
<h2 class="card-title m-0">Global Settings</h2>
|
||||
<span class="card-subtitle">Configure general Radarr integration settings</span>
|
||||
</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-cog text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -70,6 +73,9 @@
|
||||
<h2 class="card-title m-0">Instances</h2>
|
||||
<span class="card-subtitle">Manage Radarr server instances</span>
|
||||
</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-cog text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
||||
@@ -22,9 +22,12 @@
|
||||
<ng-template pTemplate="header">
|
||||
<div class="flex align-items-center justify-content-between p-3 border-bottom-1 surface-border">
|
||||
<div class="header-title-container">
|
||||
<h2 class="card-title m-0">Sonarr Settings</h2>
|
||||
<h2 class="card-title m-0">Global Settings</h2>
|
||||
<span class="card-subtitle">Configure general Sonarr integration settings</span>
|
||||
</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-cog text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -70,6 +73,9 @@
|
||||
<h2 class="card-title m-0">Instances</h2>
|
||||
<span class="card-subtitle">Manage Sonarr server instances</span>
|
||||
</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-cog text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Donation Link Placeholder -->
|
||||
<a href="https://cleanuparr.github.io/Cleanuparr/support" class="support-link donate-link" target="_blank" rel="noopener noreferrer">
|
||||
<a href="#" class="support-link donate-link" (click)="onDonateClick($event)">
|
||||
<div class="support-icon donate-icon">
|
||||
<i class="pi pi-heart-fill"></i>
|
||||
</div>
|
||||
|
||||
@@ -17,4 +17,10 @@ import { TagModule } from 'primeng/tag';
|
||||
styleUrl: './support-section.component.scss'
|
||||
})
|
||||
export class SupportSectionComponent {
|
||||
|
||||
onDonateClick(event: Event): void {
|
||||
event.preventDefault();
|
||||
// TODO: Navigate to donation page when implemented
|
||||
console.log('Donation functionality coming soon!');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,4 +39,3 @@ $ GIT_USER=<Your GitHub username> yarn deploy
|
||||
```
|
||||
|
||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ These settings need a download client to be configured.
|
||||
<ConfigSection
|
||||
id="enable-content-blocker"
|
||||
title="Enable Content Blocker"
|
||||
description="When enabled, the Content Blocker will run according to the configured schedule to automatically block or remove downloads based on the configured blocklists."
|
||||
icon="🔄"
|
||||
>
|
||||
|
||||
@@ -35,6 +36,7 @@ When enabled, the Content Blocker will run according to the configured schedule
|
||||
<ConfigSection
|
||||
id="scheduling-mode"
|
||||
title="Scheduling Mode"
|
||||
description="Choose how to configure the Content Blocker schedule"
|
||||
icon="📅"
|
||||
>
|
||||
|
||||
@@ -47,6 +49,7 @@ Choose how to configure the Content Blocker schedule:
|
||||
<ConfigSection
|
||||
id="cron-expression"
|
||||
title="Cron Expression"
|
||||
description="Enter a valid Quartz.NET cron expression to control when the Content Blocker runs"
|
||||
icon="⏲️"
|
||||
>
|
||||
|
||||
@@ -62,6 +65,7 @@ Enter a valid Quartz.NET cron expression to control when the Content Blocker run
|
||||
<ConfigSection
|
||||
id="ignore-private"
|
||||
title="Ignore Private"
|
||||
description="When enabled, private torrents will be skipped from being processed during content blocking"
|
||||
icon="🔒"
|
||||
>
|
||||
|
||||
@@ -72,6 +76,7 @@ When enabled, private torrents will be skipped from being processed during conte
|
||||
<ConfigSection
|
||||
id="delete-private"
|
||||
title="Delete Private"
|
||||
description="When enabled, private torrents that match blocklist criteria will be deleted from the download client"
|
||||
icon="🗑️"
|
||||
>
|
||||
|
||||
@@ -99,6 +104,7 @@ Setting this to true means private torrents will be permanently deleted, potenti
|
||||
<ConfigSection
|
||||
id="enable-blocklist"
|
||||
title="Enable Blocklist"
|
||||
description="When enabled, the Content Blocker will use the configured blocklist to filter content"
|
||||
icon="✅"
|
||||
>
|
||||
|
||||
@@ -109,6 +115,7 @@ When enabled, the Content Blocker will use the configured blocklist to filter co
|
||||
<ConfigSection
|
||||
id="blocklist-path"
|
||||
title="Blocklist Path"
|
||||
description="Path to the blocklist file or URL. This can be a local file path or a remote URL that will be fetched automatically"
|
||||
icon="📂"
|
||||
>
|
||||
|
||||
@@ -132,6 +139,7 @@ regex:<ANY_REGEX> // regex that needs to be marked at the start of the line wi
|
||||
<ConfigSection
|
||||
id="blocklist-type"
|
||||
title="Blocklist Type"
|
||||
description="Controls how the blocklist is interpreted"
|
||||
icon="🎭"
|
||||
>
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ These settings need a download client to be configured.
|
||||
<ConfigSection
|
||||
id="enable-download-cleaner"
|
||||
title="Enable Download Cleaner"
|
||||
description="When enabled, the Download Cleaner will run according to the configured schedule to automatically clean completed downloads from your download client."
|
||||
icon="🔄"
|
||||
>
|
||||
|
||||
@@ -36,6 +37,7 @@ When enabled, the Download Cleaner will run according to the configured schedule
|
||||
<ConfigSection
|
||||
id="scheduling-mode"
|
||||
title="Scheduling Mode"
|
||||
description="Choose how to configure the Download Cleaner schedule"
|
||||
icon="📅"
|
||||
>
|
||||
|
||||
@@ -48,6 +50,7 @@ Choose how to configure the Download Cleaner schedule:
|
||||
<ConfigSection
|
||||
id="cron-expression"
|
||||
title="Cron Expression"
|
||||
description="Enter a valid Quartz.NET cron expression to control when the Download Cleaner runs"
|
||||
icon="⏲️"
|
||||
>
|
||||
|
||||
@@ -76,6 +79,7 @@ Enter a valid Quartz.NET cron expression to control when the Download Cleaner ru
|
||||
<ConfigSection
|
||||
id="delete-private-torrents"
|
||||
title="Delete Private Torrents"
|
||||
description="When enabled, private torrents will be deleted from the download client when they meet the cleanup criteria"
|
||||
icon="🗑️"
|
||||
>
|
||||
|
||||
@@ -111,6 +115,7 @@ Both Max Ratio and Max Seed Time cannot be disabled (-1) at the same time. At le
|
||||
<ConfigSection
|
||||
id="category-name"
|
||||
title="Category Name"
|
||||
description="The name of the download client category to apply these rules to"
|
||||
icon="🏷️"
|
||||
>
|
||||
|
||||
@@ -126,6 +131,7 @@ The name of the download client category to apply these rules to. Must match the
|
||||
<ConfigSection
|
||||
id="max-ratio"
|
||||
title="Max Ratio"
|
||||
description="Maximum ratio to seed before considering the download for removal"
|
||||
icon="📊"
|
||||
>
|
||||
|
||||
@@ -136,6 +142,7 @@ Maximum ratio to seed before considering the download for removal. Set to `-1` t
|
||||
<ConfigSection
|
||||
id="min-seed-time"
|
||||
title="Min Seed Time (hours)"
|
||||
description="Minimum time in hours to seed before removing a download that has reached the max ratio"
|
||||
icon="⏰"
|
||||
>
|
||||
|
||||
@@ -146,6 +153,7 @@ Minimum time in hours to seed before removing a download that has reached the ma
|
||||
<ConfigSection
|
||||
id="max-seed-time"
|
||||
title="Max Seed Time (hours)"
|
||||
description="Maximum time in hours to seed before removing a download regardless of ratio"
|
||||
icon="⏳"
|
||||
>
|
||||
|
||||
@@ -169,6 +177,7 @@ Maximum time in hours to seed before removing a download regardless of ratio. Se
|
||||
<ConfigSection
|
||||
id="enable-unlinked-download-handling"
|
||||
title="Enable Unlinked Download Handling"
|
||||
description="Enable management of downloads that have no hardlinks remaining"
|
||||
icon="🔍"
|
||||
>
|
||||
|
||||
@@ -183,6 +192,7 @@ If you are using Docker, make sure to mount the downloads directory the same way
|
||||
<ConfigSection
|
||||
id="target-category"
|
||||
title="Target Category"
|
||||
description="Category to move unlinked downloads to"
|
||||
icon="🎯"
|
||||
>
|
||||
|
||||
@@ -193,6 +203,7 @@ Category to move unlinked downloads to.
|
||||
<ConfigSection
|
||||
id="use-tag"
|
||||
title="Use Tag"
|
||||
description="When enabled, uses a tag instead of category for marking unlinked downloads (qBittorrent only)"
|
||||
icon="🏷️"
|
||||
>
|
||||
|
||||
@@ -203,6 +214,7 @@ When enabled, uses a tag instead of category for marking unlinked downloads (qBi
|
||||
<ConfigSection
|
||||
id="ignored-root-directory"
|
||||
title="Ignored Root Directory"
|
||||
description="Root directory to ignore when checking for unlinked downloads"
|
||||
icon="📁"
|
||||
>
|
||||
|
||||
@@ -224,6 +236,7 @@ For the example above, the ignored root directory should be set to `/data/downlo
|
||||
<ConfigSection
|
||||
id="unlinked-categories"
|
||||
title="Unlinked Categories"
|
||||
description="Categories to check for unlinked downloads"
|
||||
icon="📋"
|
||||
>
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ Configure download client connections for torrents and usenet. Cleanuparr suppor
|
||||
<ConfigSection
|
||||
id="enable-download-client"
|
||||
title="Enable Download Client"
|
||||
description="Controls whether this download client instance is active and will be used by Cleanuparr for operations."
|
||||
icon="🔄"
|
||||
>
|
||||
|
||||
@@ -36,6 +37,7 @@ Controls whether this download client instance is active and will be used by Cle
|
||||
<ConfigSection
|
||||
id="client-name"
|
||||
title="Client Name"
|
||||
description="A descriptive name to identify this download client instance in the Cleanuparr interface."
|
||||
icon="🏷️"
|
||||
>
|
||||
|
||||
@@ -46,6 +48,7 @@ A descriptive name to identify this download client instance in the Cleanuparr i
|
||||
<ConfigSection
|
||||
id="client-type"
|
||||
title="Client Type"
|
||||
description="Specifies which download client software this configuration connects to."
|
||||
icon="📱"
|
||||
>
|
||||
|
||||
@@ -65,6 +68,7 @@ Specifies which download client software this configuration connects to.
|
||||
<ConfigSection
|
||||
id="client-host"
|
||||
title="Client Host"
|
||||
description="The complete URL to access your download client's web interface."
|
||||
icon="🖥️"
|
||||
>
|
||||
|
||||
@@ -83,6 +87,7 @@ The complete URL to access your download client's web interface.
|
||||
<ConfigSection
|
||||
id="url-base-path"
|
||||
title="URL Base Path"
|
||||
description="URL path prefix if your download client runs behind a reverse proxy with a subpath."
|
||||
icon="📂"
|
||||
>
|
||||
|
||||
@@ -112,6 +117,7 @@ URL path prefix if your download client runs behind a reverse proxy with a subpa
|
||||
<ConfigSection
|
||||
id="username"
|
||||
title="Username"
|
||||
description="Username for download client authentication if required."
|
||||
icon="👤"
|
||||
>
|
||||
|
||||
@@ -122,6 +128,7 @@ Username for download client authentication if required.
|
||||
<ConfigSection
|
||||
id="password"
|
||||
title="Password"
|
||||
description="Password for download client authentication."
|
||||
icon="🔑"
|
||||
>
|
||||
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
---
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
import { Important, Warning } from '@site/src/components/Admonition';
|
||||
import {
|
||||
ConfigSection,
|
||||
EnhancedImportant,
|
||||
EnhancedWarning,
|
||||
styles
|
||||
} from '@site/src/components/documentation';
|
||||
|
||||
# Health Checks
|
||||
|
||||
Cleanuparr provides comprehensive health check endpoints that are compatible with Docker health checks and Kubernetes probes. This allows for better monitoring, automated failover, and container orchestration.
|
||||
|
||||
<div className={styles.documentationPage}>
|
||||
|
||||
<div className={styles.section}>
|
||||
|
||||
<h2 className={styles.sectionTitle}>
|
||||
<span className={styles.sectionIcon}>🏥</span>
|
||||
Health Check Endpoints
|
||||
</h2>
|
||||
|
||||
<ConfigSection
|
||||
id="health-liveness"
|
||||
title="/health - Liveness Probe"
|
||||
icon="💓"
|
||||
>
|
||||
|
||||
**Purpose**: Basic liveness check to verify the application is running
|
||||
**Use Case**: Docker HEALTHCHECK and Kubernetes liveness probes
|
||||
**Response**: HTTP 200 (healthy) or 503 (unhealthy)
|
||||
**Checks**: Application startup and basic functionality
|
||||
|
||||
```bash
|
||||
curl http://localhost:11011/health
|
||||
```
|
||||
|
||||
</ConfigSection>
|
||||
|
||||
<ConfigSection
|
||||
id="health-readiness"
|
||||
title="/health/ready - Readiness Probe"
|
||||
icon="✅"
|
||||
>
|
||||
|
||||
**Purpose**: Verify the application is ready to serve traffic
|
||||
**Use Case**: Kubernetes readiness probes and load balancer health checks
|
||||
**Response**: HTTP 200 (ready) or 503 (not ready)
|
||||
**Checks**: Database connectivity, file system access, download client health
|
||||
|
||||
```bash
|
||||
curl http://localhost:11011/health/ready
|
||||
```
|
||||
|
||||
</ConfigSection>
|
||||
|
||||
<ConfigSection
|
||||
id="health-detailed"
|
||||
title="/health/detailed - Detailed Status"
|
||||
icon="📊"
|
||||
>
|
||||
|
||||
**Purpose**: Comprehensive health status for monitoring and debugging
|
||||
**Use Case**: Monitoring systems and troubleshooting
|
||||
**Response**: Detailed JSON with status of all components
|
||||
**Checks**: All health checks with timing and detailed status
|
||||
|
||||
```bash
|
||||
curl http://localhost:11011/health/detailed
|
||||
```
|
||||
|
||||
</ConfigSection>
|
||||
|
||||
</div>
|
||||
|
||||
<div className={styles.section}>
|
||||
|
||||
<h2 className={styles.sectionTitle}>
|
||||
<span className={styles.sectionIcon}>🔍</span>
|
||||
Health Check Components
|
||||
</h2>
|
||||
|
||||
<ConfigSection
|
||||
id="application-health"
|
||||
title="Application Health"
|
||||
icon="🚀"
|
||||
>
|
||||
|
||||
- Verifies the application is running and responsive
|
||||
- Basic functionality test
|
||||
- Used for liveness probes
|
||||
|
||||
</ConfigSection>
|
||||
|
||||
<ConfigSection
|
||||
id="database-health"
|
||||
title="Database Health"
|
||||
icon="🗄️"
|
||||
>
|
||||
|
||||
- Tests database connectivity
|
||||
- Checks for pending migrations
|
||||
- Validates schema integrity
|
||||
- Used for readiness probes
|
||||
|
||||
</ConfigSection>
|
||||
|
||||
<ConfigSection
|
||||
id="filesystem-health"
|
||||
title="File System Health"
|
||||
icon="📁"
|
||||
>
|
||||
|
||||
- Verifies access to configuration directories
|
||||
- Validates working directory access
|
||||
- Used for readiness probes
|
||||
|
||||
<EnhancedImportant>
|
||||
File write tests are not performed during health checks to avoid creating temporary files on every check.
|
||||
</EnhancedImportant>
|
||||
|
||||
</ConfigSection>
|
||||
|
||||
<ConfigSection
|
||||
id="download-client-health"
|
||||
title="Download Client Health"
|
||||
icon="⬇️"
|
||||
>
|
||||
|
||||
- Integrates with existing download client monitoring
|
||||
- Reports status of all configured download clients
|
||||
- Considers overall health based on client availability
|
||||
- Used for readiness probes
|
||||
|
||||
</ConfigSection>
|
||||
|
||||
</div>
|
||||
|
||||
<div className={styles.section}>
|
||||
|
||||
<h2 className={styles.sectionTitle}>
|
||||
<span className={styles.sectionIcon}>🐳</span>
|
||||
Environment Variables
|
||||
</h2>
|
||||
|
||||
<ConfigSection
|
||||
id="health-environment-variables"
|
||||
title="Environment Variables"
|
||||
icon="🌍"
|
||||
>
|
||||
|
||||
Health check endpoints are affected by these environment variables:
|
||||
|
||||
- **`PORT`**: Application port (default: 11011)
|
||||
- **`BASE_PATH`**: Base path for the application (affects health check URLs)
|
||||
|
||||
If you set `BASE_PATH=/cleanuparr`, health checks will be available at:
|
||||
- `localhost:[PORT]/cleanuparr/health`
|
||||
- `localhost:[PORT]/cleanuparr/health/ready`
|
||||
- `localhost:[PORT]/cleanuparr/health/detailed`
|
||||
|
||||
</ConfigSection>
|
||||
|
||||
</div>
|
||||
|
||||
<div className={styles.section}>
|
||||
|
||||
<h2 className={styles.sectionTitle}>
|
||||
<span className={styles.sectionIcon}>🔧</span>
|
||||
Troubleshooting
|
||||
</h2>
|
||||
|
||||
<ConfigSection
|
||||
id="health-common-issues"
|
||||
title="Common Issues"
|
||||
icon="⚠️"
|
||||
>
|
||||
|
||||
**Health check timeout**
|
||||
Increase timeout values if the application is slow to respond
|
||||
|
||||
**File system permission errors**
|
||||
Check container user permissions and mount points
|
||||
|
||||
**Download client unavailability**
|
||||
Review download client configurations and network connectivity
|
||||
|
||||
</ConfigSection>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -29,6 +29,7 @@ Configure notification services to receive alerts about Cleanuparr operations.
|
||||
<ConfigSection
|
||||
id="notifiarr-api-key"
|
||||
title="Notifiarr API Key"
|
||||
description="Your Notifiarr API key for authentication. This key is obtained from your Notifiarr dashboard."
|
||||
icon="🔑"
|
||||
>
|
||||
|
||||
@@ -43,6 +44,7 @@ Requires Notifiarr's [Passthrough](https://notifiarr.wiki/pages/integrations/pas
|
||||
<ConfigSection
|
||||
id="notifiarr-channel-id"
|
||||
title="Notifiarr Channel ID"
|
||||
description="The Discord channel ID where notifications will be sent. This determines the destination for your alerts."
|
||||
icon="💬"
|
||||
>
|
||||
|
||||
@@ -66,6 +68,7 @@ The Discord channel ID where notifications will be sent. This determines the des
|
||||
<ConfigSection
|
||||
id="apprise-url"
|
||||
title="Apprise URL"
|
||||
description="The Apprise server URL where notification requests will be sent."
|
||||
icon="🌐"
|
||||
>
|
||||
|
||||
@@ -76,6 +79,7 @@ The Apprise server URL where notification requests will be sent.
|
||||
<ConfigSection
|
||||
id="apprise-key"
|
||||
title="Apprise Key"
|
||||
description="The key that identifies your Apprise configuration. This corresponds to a configuration defined in your Apprise server."
|
||||
icon="🔐"
|
||||
>
|
||||
|
||||
@@ -95,6 +99,7 @@ The key that identifies your Apprise configuration. This corresponds to a config
|
||||
<ConfigSection
|
||||
id="event-failed-import-strike"
|
||||
title="Failed Import Strike"
|
||||
description="Triggered When: A download receives a strike for failed import."
|
||||
icon="❌"
|
||||
>
|
||||
|
||||
@@ -105,6 +110,7 @@ The key that identifies your Apprise configuration. This corresponds to a config
|
||||
<ConfigSection
|
||||
id="event-stalled-strike"
|
||||
title="Stalled Strike"
|
||||
description="Triggered When: A download receives a strike for being stalled."
|
||||
icon="⏸️"
|
||||
>
|
||||
|
||||
@@ -115,6 +121,7 @@ The key that identifies your Apprise configuration. This corresponds to a config
|
||||
<ConfigSection
|
||||
id="event-slow-strike"
|
||||
title="Slow Strike"
|
||||
description="Triggered When: A download receives a strike for slow speed."
|
||||
icon="🐌"
|
||||
>
|
||||
|
||||
@@ -125,6 +132,7 @@ The key that identifies your Apprise configuration. This corresponds to a config
|
||||
<ConfigSection
|
||||
id="event-queue-item-deleted"
|
||||
title="Queue Item Deleted"
|
||||
description="Triggered When: A download is removed from the queue."
|
||||
icon="🗑️"
|
||||
>
|
||||
|
||||
@@ -135,6 +143,7 @@ The key that identifies your Apprise configuration. This corresponds to a config
|
||||
<ConfigSection
|
||||
id="event-download-cleaned"
|
||||
title="Download Cleaned"
|
||||
description="Triggered When: Download Cleaner removes completed downloads."
|
||||
icon="🧽"
|
||||
>
|
||||
|
||||
@@ -145,6 +154,7 @@ The key that identifies your Apprise configuration. This corresponds to a config
|
||||
<ConfigSection
|
||||
id="event-category-changed"
|
||||
title="Category Changed"
|
||||
description="Triggered When: Download Cleaner changes a download's category."
|
||||
icon="🏷️"
|
||||
>
|
||||
|
||||
|
||||
@@ -37,10 +37,11 @@ Docker is the preferred installation method as it ensures all dependencies are c
|
||||
- Docker (version 20.10 or newer)
|
||||
- Docker Compose (optional, for compose method)
|
||||
|
||||
### Docker Run
|
||||
### Docker Run Method
|
||||
|
||||
The simplest way to run Cleanuparr is with a single Docker command:
|
||||
|
||||
#### Option 1: GitHub Container Registry (Recommended)
|
||||
```bash
|
||||
docker run -d --name cleanuparr \
|
||||
--restart unless-stopped \
|
||||
@@ -55,7 +56,22 @@ docker run -d --name cleanuparr \
|
||||
ghcr.io/cleanuparr:latest
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
#### Option 2: DockerHub
|
||||
```bash
|
||||
docker run -d --name cleanuparr \
|
||||
--restart unless-stopped \
|
||||
-p 11011:11011 \
|
||||
-v /path/to/config:/config \
|
||||
-e PORT=11011 \
|
||||
-e BASE_PATH= \
|
||||
-e PUID=1000 \
|
||||
-e PGID=1000 \
|
||||
-e UMASK=022 \
|
||||
-e TZ=Etc/UTC \
|
||||
cleanuparr/cleanuparr:latest
|
||||
```
|
||||
|
||||
### Docker Compose Method
|
||||
|
||||
For easier management, create a `docker-compose.yml` file:
|
||||
|
||||
@@ -77,13 +93,6 @@ services:
|
||||
- PGID=1000
|
||||
- UMASK=022
|
||||
- TZ=Etc/UTC
|
||||
# Health check configuration
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:11011/health"]
|
||||
interval: 30s # Check every 30 seconds
|
||||
timeout: 10s # Allow up to 10 seconds for response
|
||||
start_period: 30s # Wait 30 seconds before first check
|
||||
retries: 3 # Mark unhealthy after 3 consecutive failures
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
@@ -264,6 +273,3 @@ To run Cleanuparr as a systemd service:
|
||||
- **Web Interface**: `http://localhost:11011`
|
||||
- **Base Path**: *(empty)* (for reverse proxy setups, change `BASE_PATH` environment variable)
|
||||
- **Configuration Location**: Varies by platform and installation method
|
||||
|
||||
#### Health Checks
|
||||
Cleanuparr provides comprehensive health check endpoints for monitoring and container orchestration. For detailed information about available endpoints and configuration, see the [Health Checks documentation](../configuration/health-checks/).
|
||||
|
||||
@@ -52,7 +52,7 @@ services:
|
||||
- **Linux**: Download and extract the portable executable
|
||||
|
||||
<Note>
|
||||
For detailed installation instructions, security notes, troubleshooting, and all platform-specific options, see the [Complete Installation Guide](/docs/installation/detailed).
|
||||
For detailed installation instructions, security notes, troubleshooting, and all platform-specific options, see the [Complete Installation Guide](/docs/installation/installation).
|
||||
</Note>
|
||||
|
||||
## 🚀 After Installation
|
||||
|
||||
@@ -8,7 +8,7 @@ const config: Config = {
|
||||
favicon: 'img/favicon.ico',
|
||||
|
||||
url: 'https://cleanuparr.github.io',
|
||||
baseUrl: '/Cleanuparr/',
|
||||
baseUrl: '/cleanuparr/',
|
||||
|
||||
organizationName: 'Cleanuparr',
|
||||
projectName: 'Cleanuparr',
|
||||
|
||||
Reference in New Issue
Block a user