diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000000..6f3c9a7cabf
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,2 @@
+*
+!radarr/
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 336a7cd6f76..ffce4faad6d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -28,7 +28,6 @@ env:
MAJOR_VERSION: '6.1.1'
DOTNET_VERSION: '8.0.405'
NODE_VERSION: '20.19.0'
- INNO_VERSION: '6.4.2'
jobs:
# ---------------------------------------------------------------------------
@@ -282,6 +281,192 @@ jobs:
name: packages
path: _archives/
+ # ---------------------------------------------------------------------------
+ # Release: create GitHub Release with package assets
+ # ---------------------------------------------------------------------------
+ release:
+ runs-on: ubuntu-24.04
+ needs: [packages, installer, setup]
+ if: github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master')
+ permissions:
+ contents: write
+ env:
+ RADARR_VERSION: ${{ needs.setup.outputs.radarr_version }}
+ BRANCH_NAME: ${{ needs.setup.outputs.branch_name }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Download packages
+ uses: actions/download-artifact@v4
+ with:
+ name: packages
+ path: _release/
+
+ - name: Download installer
+ uses: actions/download-artifact@v4
+ with:
+ name: windows-installer
+ path: _release/
+
+ - name: Generate checksums
+ working-directory: _release
+ run: |
+ sha256sum * > sha256sums.txt
+ cat sha256sums.txt
+
+ - name: Determine release type
+ id: release-type
+ run: |
+ if [ "${{ github.ref }}" = "refs/heads/master" ]; then
+ echo "prerelease=false" >> "$GITHUB_OUTPUT"
+ echo "tag=v${RADARR_VERSION}" >> "$GITHUB_OUTPUT"
+ else
+ echo "prerelease=true" >> "$GITHUB_OUTPUT"
+ echo "tag=v${RADARR_VERSION}-nightly" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Create git tag
+ env:
+ TAG: ${{ steps.release-type.outputs.tag }}
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git tag -d "${TAG}" 2>/dev/null || true
+ git push origin ":refs/tags/${TAG}" 2>/dev/null || true
+ git tag -a "${TAG}" -m "Release ${TAG}"
+ git push origin "${TAG}"
+
+ - name: Create GitHub Release
+ env:
+ GH_TOKEN: ${{ github.token }}
+ TAG: ${{ steps.release-type.outputs.tag }}
+ PRERELEASE: ${{ steps.release-type.outputs.prerelease }}
+ run: |
+ RELEASE_ARGS=(
+ "${TAG}"
+ --repo Starosdev/Radarr
+ --title "Radarr v${RADARR_VERSION}"
+ --generate-notes
+ )
+
+ if [ "${PRERELEASE}" = "true" ]; then
+ RELEASE_ARGS+=(--prerelease)
+ else
+ RELEASE_ARGS+=(--latest)
+ fi
+
+ gh release create "${RELEASE_ARGS[@]}" _release/*
+
+ # ---------------------------------------------------------------------------
+ # Docker: build and push per-arch images to GHCR
+ # ---------------------------------------------------------------------------
+ docker:
+ runs-on: ubuntu-24.04
+ needs: [packages, setup]
+ if: github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master')
+ permissions:
+ contents: read
+ packages: write
+ env:
+ RADARR_VERSION: ${{ needs.setup.outputs.radarr_version }}
+ BUILDNAME: ${{ needs.setup.outputs.branch_name }}.${{ needs.setup.outputs.radarr_version }}
+ REGISTRY: ghcr.io
+ IMAGE_NAME: staros-labs/radarr
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - platform: linux/amd64
+ archive_rid: linux-core-x64
+ - platform: linux/arm64
+ archive_rid: linux-core-arm64
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Download packages
+ uses: actions/download-artifact@v4
+ with:
+ name: packages
+ path: _archives/
+
+ - name: Extract package for platform
+ run: |
+ mkdir -p radarr
+ tar xzf _archives/Radarr.${BUILDNAME}.${{ matrix.archive_rid }}.tar.gz -C radarr/
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+ with:
+ platforms: ${{ matrix.platform }}
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to GHCR
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Build and push
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./Dockerfile
+ platforms: ${{ matrix.platform }}
+ push: true
+ tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.RADARR_VERSION }}-${{ matrix.archive_rid }}
+ labels: |
+ org.opencontainers.image.title=Radarr
+ org.opencontainers.image.version=${{ env.RADARR_VERSION }}
+ org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
+ cache-from: type=gha,scope=${{ matrix.archive_rid }}
+ cache-to: type=gha,mode=max,scope=${{ matrix.archive_rid }}
+
+ # ---------------------------------------------------------------------------
+ # Docker Manifest: combine per-arch images into multi-arch manifest
+ # ---------------------------------------------------------------------------
+ docker-manifest:
+ runs-on: ubuntu-24.04
+ needs: [docker, setup]
+ if: github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master')
+ permissions:
+ packages: write
+ env:
+ RADARR_VERSION: ${{ needs.setup.outputs.radarr_version }}
+ REGISTRY: ghcr.io
+ IMAGE_NAME: staros-labs/radarr
+ steps:
+ - name: Login to GHCR
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Create and push manifests
+ run: |
+ VERSION_TAG="${REGISTRY}/${IMAGE_NAME}:${RADARR_VERSION}"
+ BRANCH_TAG="${REGISTRY}/${IMAGE_NAME}:${{ github.ref == 'refs/heads/master' && 'latest' || 'develop' }}"
+
+ docker buildx imagetools create -t "${VERSION_TAG}" \
+ "${REGISTRY}/${IMAGE_NAME}:${RADARR_VERSION}-linux-core-x64" \
+ "${REGISTRY}/${IMAGE_NAME}:${RADARR_VERSION}-linux-core-arm64"
+
+ docker buildx imagetools create -t "${BRANCH_TAG}" \
+ "${REGISTRY}/${IMAGE_NAME}:${RADARR_VERSION}-linux-core-x64" \
+ "${REGISTRY}/${IMAGE_NAME}:${RADARR_VERSION}-linux-core-arm64"
+
# ---------------------------------------------------------------------------
# Lint
# ---------------------------------------------------------------------------
@@ -694,7 +879,6 @@ jobs:
env:
RADARRVERSION: ${{ needs.setup.outputs.radarr_version }}
MAJORVERSION: '6.1.1'
- INNOVERSION: '6.4.2'
BUILD_SOURCEBRANCHNAME: ${{ needs.setup.outputs.branch_name }}
BUILDNAME: ${{ needs.setup.outputs.branch_name }}.${{ needs.setup.outputs.radarr_version }}
steps:
@@ -947,6 +1131,8 @@ jobs:
- integration-docker
- integration-postgres
- installer
+ - release
+ - docker-manifest
- sentry
- sonarqube-frontend
- sonarqube-backend
@@ -972,6 +1158,18 @@ jobs:
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
+ RELEASE_URL="${{ github.server_url }}/${{ github.repository }}/releases/tag/v${RADARR_VERSION}${{ github.ref == 'refs/heads/develop' && '-nightly' || '' }}"
+
+ RELEASE_FIELD=""
+ if [ "${{ needs.release.result }}" = "success" ]; then
+ RELEASE_FIELD=",{\"name\": \"Release\", \"value\": \"[v${RADARR_VERSION}](${RELEASE_URL})\", \"inline\": true}"
+ fi
+
+ DOCKER_FIELD=""
+ if [ "${{ needs.docker-manifest.result }}" = "success" ]; then
+ DOCKER_FIELD=",{\"name\": \"Docker\", \"value\": \"ghcr.io/starosdev/radarr:${RADARR_VERSION}\", \"inline\": true}"
+ fi
+
# Only send if webhook is configured
if [ -n "$DISCORD_WEBHOOK_KEY" ] && [ -n "$DISCORD_WEBHOOK_ID" ]; then
curl -s -H "Content-Type: application/json" \
@@ -984,7 +1182,7 @@ jobs:
\"color\": ${COLOR},
\"fields\": [
{\"name\": \"Branch\", \"value\": \"${BRANCH_NAME}\", \"inline\": true},
- {\"name\": \"Version\", \"value\": \"${RADARR_VERSION}\", \"inline\": true}
+ {\"name\": \"Version\", \"value\": \"${RADARR_VERSION}\", \"inline\": true}${RELEASE_FIELD}${DOCKER_FIELD}
]
}]
}" \
diff --git a/.github/workflows/cleanup-branches.yml b/.github/workflows/cleanup-branches.yml
new file mode 100644
index 00000000000..8d28e2a2ba2
--- /dev/null
+++ b/.github/workflows/cleanup-branches.yml
@@ -0,0 +1,138 @@
+name: Cleanup Stale Branches
+
+on:
+ schedule:
+ - cron: '0 3 * * 0' # Weekly on Sunday at 3 AM UTC
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+jobs:
+ cleanup:
+ name: Delete Stale Merged Branches
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Configure Git
+ run: |
+ git config --global user.name "GitHub Actions Bot"
+ git config --global user.email "actions@github.com"
+
+ - name: Fetch All Branches
+ run: git fetch --all --prune
+
+ - name: Find and Delete Stale Branches
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ echo "=================================================="
+ echo "Stale Branch Cleanup - $(date)"
+ echo "=================================================="
+ echo ""
+
+ PROTECTED_BRANCHES="master develop"
+ DELETED_COUNT=0
+ KEPT_COUNT=0
+
+ echo "Scanning for stale branches (merged >30 days ago)..."
+ echo ""
+
+ for branch in $(git branch -r | grep -v '\->' | grep -v 'HEAD' | sed 's/origin\///'); do
+ skip=false
+ for protected in $PROTECTED_BRANCHES; do
+ if [ "$branch" = "$protected" ]; then
+ skip=true
+ break
+ fi
+ done
+
+ if [ "$skip" = true ]; then
+ continue
+ fi
+
+ merged_to_develop=$(git branch -r --merged origin/develop | grep -cxF " origin/$branch" || echo "0")
+ merged_to_master=$(git branch -r --merged origin/master | grep -cxF " origin/$branch" || echo "0")
+
+ # Check GitHub API for squash-merged PRs
+ if [ "$merged_to_develop" = "0" ] && [ "$merged_to_master" = "0" ]; then
+ pr_merged=$(gh pr list --repo "${{ github.repository }}" --head "$branch" --state merged --json number --jq 'length' 2>/dev/null || echo "0")
+ if [ "$pr_merged" != "0" ] && [ "$pr_merged" != "" ]; then
+ pr_base=$(gh pr list --repo "${{ github.repository }}" --head "$branch" --state merged --json baseRefName --jq '.[0].baseRefName' 2>/dev/null || echo "")
+ if [ "$pr_base" = "develop" ]; then
+ merged_to_develop="1"
+ elif [ "$pr_base" = "master" ]; then
+ merged_to_master="1"
+ else
+ merged_to_develop="1"
+ fi
+ fi
+ fi
+
+ if [ "$merged_to_develop" = "0" ] && [ "$merged_to_master" = "0" ]; then
+ KEPT_COUNT=$((KEPT_COUNT + 1))
+ continue
+ fi
+
+ LAST_COMMIT_DATE=$(git log -1 --format="%ci" "origin/$branch" 2>/dev/null || echo "1970-01-01")
+ LAST_COMMIT_EPOCH=$(date -d "$LAST_COMMIT_DATE" +%s 2>/dev/null || echo "0")
+ CURRENT_EPOCH=$(date +%s)
+ DAYS_OLD=$(( (CURRENT_EPOCH - LAST_COMMIT_EPOCH) / 86400 ))
+
+ if [ "$DAYS_OLD" -gt 30 ]; then
+ merged_to=""
+ [ "$merged_to_develop" != "0" ] && merged_to="develop"
+ if [ "$merged_to_master" != "0" ]; then
+ [ -n "$merged_to" ] && merged_to="$merged_to and master" || merged_to="master"
+ fi
+
+ echo " Deleting: $branch"
+ echo " Last commit: $DAYS_OLD days ago"
+ echo " Merged to: $merged_to"
+
+ git push origin --delete "$branch" 2>&1 | sed 's/^/ /' || {
+ echo " WARNING: Failed to delete $branch"
+ }
+
+ DELETED_COUNT=$((DELETED_COUNT + 1))
+ echo ""
+ else
+ KEPT_COUNT=$((KEPT_COUNT + 1))
+ fi
+ done
+
+ echo ""
+ echo "=================================================="
+ echo "Cleanup Summary"
+ echo "=================================================="
+ echo "Deleted branches: $DELETED_COUNT"
+ echo "Kept branches: $KEPT_COUNT"
+ echo "Protected branches: $PROTECTED_BRANCHES"
+
+ - name: List Remaining Feature Branches
+ run: |
+ echo ""
+ echo "=================================================="
+ echo "Remaining Feature/Development Branches"
+ echo "=================================================="
+
+ PATTERN='feature/|fix/|hotfix/'
+ REMAINING=$(git branch -r | grep -E "$PATTERN" | grep -v 'origin/master' | grep -v 'origin/develop' | sed 's/origin\///' | wc -l)
+
+ if [ "$REMAINING" -gt 0 ]; then
+ echo "Found $REMAINING active feature branches:"
+ echo ""
+ git branch -r | grep -E "$PATTERN" | grep -v 'origin/master' | grep -v 'origin/develop' | sed 's/origin\///' | while read branch; do
+ LAST_COMMIT=$(git log -1 --format="%ci" "origin/$branch" 2>/dev/null | cut -d' ' -f1)
+ AUTHOR=$(git log -1 --format="%an" "origin/$branch" 2>/dev/null)
+ echo " $branch"
+ echo " Last commit: $LAST_COMMIT by $AUTHOR"
+ done
+ else
+ echo "No active feature branches remaining."
+ fi
diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml
new file mode 100644
index 00000000000..02240552f42
--- /dev/null
+++ b/.github/workflows/secret-scan.yml
@@ -0,0 +1,55 @@
+name: Secret Scan
+
+on:
+ push:
+ branches: [develop, master]
+ pull_request:
+ branches: [develop, master]
+ workflow_dispatch:
+
+concurrency:
+ group: "secret-scan-${{ github.ref }}"
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+ security-events: write
+
+jobs:
+ gitleaks:
+ name: Gitleaks Secret Detection
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Run gitleaks scan
+ run: |
+ docker run --rm \
+ -v "$PWD:/repo" \
+ zricethezav/gitleaks:v8.30.0 \
+ detect \
+ --source=/repo \
+ --config=/repo/.gitleaks.toml \
+ --report-format=sarif \
+ --report-path=/repo/gitleaks-results.sarif \
+ --redact
+
+ - name: Upload gitleaks report
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: gitleaks-results
+ path: gitleaks-results.sarif
+ retention-days: 30
+ if-no-files-found: warn
+
+ - name: Upload SARIF to GitHub Security
+ if: always()
+ uses: github/codeql-action/upload-sarif@v3
+ with:
+ sarif_file: gitleaks-results.sarif
+ continue-on-error: true
diff --git a/.gitignore b/.gitignore
index d59be9e35ea..06b8ebbc894 100644
--- a/.gitignore
+++ b/.gitignore
@@ -179,3 +179,4 @@ node_modules.nosync
.claude/
CLAUDE.md
AGENTS.md
+docs/plans/
diff --git a/.gitleaks.toml b/.gitleaks.toml
new file mode 100644
index 00000000000..1004d3ee545
--- /dev/null
+++ b/.gitleaks.toml
@@ -0,0 +1,11 @@
+[global]
+# Radarr-specific gitleaks config
+
+[allowlist]
+description = "Global allowlist"
+paths = [
+ '''node_modules/''',
+ '''_output/''',
+ '''_tests/''',
+ '''_artifacts/''',
+]
diff --git a/AGENTS.md b/AGENTS.md
index 7c17bdc82ef..d8264245d53 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -16,6 +16,7 @@ These instructions apply to automated builders and reviewers working in this rep
- Push back when a request is risky, underspecified, or likely to create avoidable maintenance problems.
- Use role-based language such as `builder` or `reviewer` if you need to describe automation.
- Do not use emojis in commits, pull requests, issues, comments, or docs.
+- Do not mention assistant product names in commits, pull requests, comments, or repo files.
## Branch Workflow
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000000..4e87c9a4a78
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,16 @@
+FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-noble
+
+COPY --chmod=755 radarr /opt/radarr
+
+RUN groupadd -f -g 1000 radarr && \
+ useradd -u 1000 -g 1000 -d /config -s /bin/bash radarr 2>/dev/null; \
+ mkdir -p /config && chown 1000:1000 /config
+
+ENV RADARR_BRANCH="develop" \
+ XDG_CONFIG_HOME="/config/xdg"
+
+VOLUME /config
+EXPOSE 7878
+
+USER radarr
+ENTRYPOINT ["/opt/radarr/Radarr", "-nobrowser", "-data=/config"]
diff --git a/src/NzbDrone.Core/Radarr.Core.csproj b/src/NzbDrone.Core/Radarr.Core.csproj
index da90842bf21..104832c7075 100644
--- a/src/NzbDrone.Core/Radarr.Core.csproj
+++ b/src/NzbDrone.Core/Radarr.Core.csproj
@@ -6,7 +6,7 @@
-
+