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 @@ - +