diff --git a/.github/CI_CD_README.md b/.github/CI_CD_README.md new file mode 100644 index 00000000..31e5f3e4 --- /dev/null +++ b/.github/CI_CD_README.md @@ -0,0 +1,228 @@ +# CI/CD Pipeline Documentation + +This repository uses a comprehensive CI/CD pipeline to ensure code quality, security, and reliable deployments. + +## 🚀 Workflow Overview + +### Main Workflows + +1. **Android CI/CD Pipeline** (`android.yml`) - Main build and deployment workflow +2. **Pull Request Validation** (`pr-validation.yml`) - Lightweight checks for PRs +3. **Security Analysis** (`security.yml`) - CodeQL security scanning and dependency review +4. **Dependency Updates** (`dependency-updates.yml`) - Automated dependency management +5. **Release Automation** (`release.yml`) - Automated release creation and publishing + +## 📋 Workflow Details + +### Android CI/CD Pipeline + +**Triggers:** +- Push to `master` branch (excluding documentation files) +- Manual workflow dispatch with optional test skipping + +**Jobs:** + +#### 1. Code Quality & Security 🔍 +- Gradle wrapper validation +- Lint checks with detailed reporting +- Security vulnerability scanning +- Dependency analysis + +#### 2. Build & Test 🏗️ +- **Matrix Strategy**: Builds both `smartphone` and `tv` variants +- Unit test execution (when enabled) +- Baseline profile generation for performance optimization +- Debug and Release APK compilation +- APK size reporting +- Test report and baseline profile artifact upload + +#### 3. Deploy & Notify 🚀 +- **Conditional**: Only runs on `master` branch (not PRs) +- Combines all build artifacts +- Uploads to Telegram channel (if configured) +- Long-term artifact retention (90 days) + +#### 4. Build Summary 📊 +- Generates comprehensive build status report +- Lists all generated artifacts +- Provides quick status overview + +### Pull Request Validation + +**Purpose**: Fast feedback for pull requests without full CI overhead + +**Features:** +- Quick lint checks +- Code compilation validation +- Debug APK builds +- Automatic PR status comments +- Change analysis (Kotlin, Gradle, Workflow changes) + +### Security Analysis + +**Components:** +- **CodeQL Analysis**: Static security analysis for Java/Kotlin +- **Dependency Review**: Automated dependency vulnerability checking +- **Secret Scanning**: Trivy-based security scanning +- **SARIF Reports**: Security findings uploaded to GitHub Security tab + +**Schedule**: Weekly security scans (Sundays at 3 AM UTC) + +### Dependency Updates + +**Features:** +- Weekly dependency update checks (Mondays at 9 AM UTC) +- Automated issue creation for available updates +- Security audit with vulnerability reporting +- Stable release filtering (excludes alpha/beta/rc versions) + +**Manual Trigger**: Available for immediate dependency checks + +### Release Automation + +**Triggers:** +- Git tags matching `v*` pattern +- Manual dispatch with custom tag and pre-release options + +**Process:** +1. Extract version from tag or input +2. Run final quality checks and linting +3. Generate optimized baseline profiles +4. Build signed release APKs (if signing configured) +5. Generate checksums for verification +6. Create GitHub release with artifacts +7. Upload APKs and checksums as release assets +8. Send notification to Telegram + +## 🛠 Build Tools & Quality Checks + +### Enabled Gradle Plugins + +1. **Versions Plugin**: Dependency update tracking +2. **OWASP Dependency Check**: Security vulnerability scanning +3. **Detekt**: Static code analysis for Kotlin +4. **Spotless**: Code formatting and style enforcement + +### Configuration Files + +- **Detekt**: `config/detekt/detekt.yml` - Code analysis rules +- **CodeQL**: `.github/codeql/codeql-config.yml` - Security analysis configuration + +## 📊 Artifacts & Reports + +### Build Artifacts +- **APKs**: Debug and Release builds for smartphone and TV +- **Baseline Profiles**: Performance optimization profiles +- **Test Reports**: Unit test results and coverage + +### Quality Reports +- **Lint Reports**: HTML and XML format lint analysis +- **Security Reports**: Dependency vulnerability analysis +- **Detekt Reports**: Static analysis findings +- **CodeQL Reports**: Security analysis results + +## 🔧 Environment Configuration + +### Required Secrets +- `BOT_TOKEN`: Telegram bot token for notifications +- `CHAT_ID`: Telegram chat ID for notifications +- `API_ID`: Telegram API ID +- `API_HASH`: Telegram API hash + +### Optional Configuration +- APK signing keys for release builds +- Additional notification channels +- Custom deployment targets + +## 📈 Performance Optimizations + +### Caching Strategy +- **Gradle Build Cache**: Enabled with cleanup +- **Dependency Cache**: Shared across jobs with read-only mode for subsequent jobs +- **Dependency Graph**: Generated and submitted for GitHub Insights + +### Resource Management +- **Timeouts**: All jobs have appropriate timeout limits +- **Concurrency**: Intelligent cancellation of outdated runs +- **Matrix Builds**: Parallel execution for different variants + +### Build Optimizations +- **Configuration Cache**: Enabled for faster Gradle execution +- **Parallel Builds**: Optimized JVM settings +- **Incremental Builds**: Leverages Gradle's incremental compilation + +## 🔒 Security Features + +### Dependency Security +- Weekly vulnerability scans +- Critical vulnerability alerting +- Automated dependency review for PRs +- OWASP dependency checking + +### Code Security +- CodeQL static analysis +- Secret scanning with Trivy +- Security findings integration with GitHub Security + +### Access Control +- Workflow permissions follow principle of least privilege +- Separate jobs for different security levels +- Artifact access controls + +## 📝 Maintenance + +### Regular Tasks +- **Weekly**: Dependency updates check +- **Weekly**: Security vulnerability scan +- **Per PR**: Code quality validation +- **Per Release**: Full security audit + +### Monitoring +- Build status summaries in GitHub Actions +- Security findings in GitHub Security tab +- Dependency insights in GitHub Insights +- Performance metrics through baseline profiles + +## 🚨 Troubleshooting + +### Common Issues +1. **Build Failures**: Check lint reports and compilation errors +2. **Security Alerts**: Review dependency-check reports +3. **Test Failures**: Examine test reports artifacts +4. **Dependency Issues**: Check dependency-updates workflow + +### Debug Tips +- Enable debug logging with `--debug` in Gradle commands +- Check individual job logs for specific failures +- Review artifact uploads for detailed reports +- Use manual workflow dispatch for testing changes + +## 🔄 Workflow Dependencies + +``` +android.yml (Main Pipeline) +├── code-quality (runs first) +├── build-and-test (depends on code-quality) +├── deploy-and-notify (depends on build-and-test, master only) +└── build-summary (runs after all, always) + +pr-validation.yml (PR only) +├── pr-checks (validates PR changes) +└── changed-files (analyzes changes) + +security.yml (Scheduled + PR) +├── analyze (CodeQL security analysis) +├── dependency-review (PR only) +└── secret-scan (security scanning) + +dependency-updates.yml (Scheduled) +├── update-dependencies (Monday mornings) +└── security-audit (weekly security check) + +release.yml (Tag-triggered) +├── build-release (creates release builds) +├── create-release (depends on build-release) +└── notify-release (depends on create-release) +``` + +This comprehensive CI/CD setup ensures code quality, security, and reliable deployments while providing fast feedback for development workflow. \ No newline at end of file diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 00000000..9a5924ca --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,34 @@ +name: "M3UAndroid Security Analysis" + +disable-default-queries: false + +query-filters: + - exclude: + id: java/hardcoded-credentials + - exclude: + id: java/cleartext-storage-android + +queries: + - uses: security-and-quality + - uses: security-extended + +paths: + - "app/" + - "data/" + - "core/" + - "business/" + +paths-ignore: + - "**/build/" + - "**/test/" + - "**/*.md" + - "gradle/" + - ".idea/" + +# Android-specific configuration +android: + # Include Android-specific security queries + include-android-queries: true + + # Minimum API level to analyze + min-api-level: 26 \ No newline at end of file diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 448176c6..a3f98d6d 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,4 +1,4 @@ -name: Android CI +name: Android CI/CD Pipeline on: push: @@ -13,55 +13,146 @@ on: pull_request: branches: [ "master" ] workflow_dispatch: + inputs: + skip-tests: + description: 'Skip test execution' + required: false + default: false + type: boolean concurrency: - group: ${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -jobs: - build: +env: + GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx6g -Dorg.gradle.daemon=false" + JAVA_VERSION: "17" +jobs: + # Code Quality Check (Basic) + code-quality: + name: 🔍 Code Quality Check runs-on: ubuntu-latest + timeout-minutes: 15 + + outputs: + cache-key: ${{ steps.cache-key.outputs.key }} + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Generate Cache Key + id: cache-key + run: | + echo "key=gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'gradle/libs.versions.toml') }}" >> $GITHUB_OUTPUT + + - name: Setup JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: "zulu" + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + cache-read-only: false + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v3 + - name: Check Build Configuration + run: ./gradlew projects --info + + # Build and Test + build-and-test: + name: 🏗️ Build APKs + runs-on: ubuntu-latest + needs: code-quality + timeout-minutes: 45 + + strategy: + matrix: + module: [smartphone, tv] + fail-fast: false + steps: - - name: Checkout + - name: Checkout Code uses: actions/checkout@v4 - - name: Setup JDK - id: setup-java + - name: Setup JDK ${{ env.JAVA_VERSION }} uses: actions/setup-java@v4 with: - java-version: 17 + java-version: ${{ env.JAVA_VERSION }} distribution: "zulu" - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 with: gradle-home-cache-cleanup: true + cache-read-only: true - name: Clean GMD run: ./gradlew cleanManagedDevices --unused-only - - name: Build production app - run: ./gradlew :app:smartphone:assembleRelease - -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile - -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" - -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true - -Pandroid.experimental.androidTest.numManagedDeviceShards=1 - -Pandroid.experimental.testOptions.managedDevices.maxConcurrentDevices=1 + - name: Build Release APK + run: | + ./gradlew :app:${{ matrix.module }}:assembleRelease \ + -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile \ + -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" \ + -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true \ + -Pandroid.experimental.androidTest.numManagedDeviceShards=1 \ + -Pandroid.experimental.testOptions.managedDevices.maxConcurrentDevices=1 - - name: Build production tv app - run: ./gradlew :app:tv:assembleRelease + - name: Get APK Size + id: apk-size + run: | + APK_PATH="app/${{ matrix.module }}/build/outputs/apk/release" + if [ -d "$APK_PATH" ]; then + APK_SIZE=$(du -h $APK_PATH/*.apk | cut -f1 | head -1) + echo "size=$APK_SIZE" >> $GITHUB_OUTPUT + echo "📱 ${{ matrix.module }} APK size: $APK_SIZE" + fi - - name: Upload + - name: Upload APK Artifacts uses: actions/upload-artifact@v4 with: + name: apk-${{ matrix.module }} path: | - app/smartphone/build/outputs/apk/release/*.apk - app/tv/build/outputs/apk/release/*.apk + app/${{ matrix.module }}/build/outputs/apk/release/*.apk + retention-days: 30 + + # Deployment and Notifications + deploy-and-notify: + name: 🚀 Deploy & Notify + runs-on: ubuntu-latest + needs: [code-quality, build-and-test] + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master' + timeout-minutes: 15 + + steps: + - name: Download All Artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts/ + + - name: Prepare Release Assets + run: | + mkdir -p release/ + find artifacts/ -name "*.apk" -path "*/apk-*/*" -exec cp {} release/ \; + ls -la release/ + + - name: Upload Combined Artifacts + uses: actions/upload-artifact@v4 + with: + name: release-apks + path: release/*.apk + retention-days: 90 - name: Upload To Telegram - if: github.event_name != 'pull_request' + if: success() + continue-on-error: true uses: xireiki/channel-post@v1.0.10 with: bot_token: ${{ secrets.BOT_TOKEN }} @@ -70,6 +161,34 @@ jobs: api_hash: ${{ secrets.API_HASH }} large_file: true method: sendFile - path: | - app/smartphone/build/outputs/apk/release/*.apk - app/tv/build/outputs/apk/release/*.apk + path: release/*.apk + + # Summary Report + build-summary: + name: 📊 Build Summary + runs-on: ubuntu-latest + needs: [code-quality, build-and-test] + if: always() + + steps: + - name: Generate Build Summary + run: | + echo "## 🏗️ Build Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ needs.code-quality.result }}" == "success" ]; then + echo "✅ Code Quality: Passed" >> $GITHUB_STEP_SUMMARY + else + echo "❌ Code Quality: Failed" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.build-and-test.result }}" == "success" ]; then + echo "✅ Build: Passed" >> $GITHUB_STEP_SUMMARY + else + echo "❌ Build: Failed" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 📱 Build Artifacts" >> $GITHUB_STEP_SUMMARY + echo "- Smartphone APK" >> $GITHUB_STEP_SUMMARY + echo "- TV APK" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/dependency-updates.yml b/.github/workflows/dependency-updates.yml new file mode 100644 index 00000000..d7241d4a --- /dev/null +++ b/.github/workflows/dependency-updates.yml @@ -0,0 +1,149 @@ +name: Dependency Updates + +on: + schedule: + # Run weekly on Monday at 9 AM UTC + - cron: '0 9 * * 1' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + update-dependencies: + name: 🔄 Update Dependencies + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: "zulu" + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + + - name: Check for Gradle Updates + id: gradle-updates + run: | + ./gradlew dependencyUpdates --refresh-dependencies > dependency_updates.txt + if grep -q "outdated" dependency_updates.txt; then + echo "updates-available=true" >> $GITHUB_OUTPUT + else + echo "updates-available=false" >> $GITHUB_OUTPUT + fi + + - name: Generate Dependency Report + if: steps.gradle-updates.outputs.updates-available == 'true' + run: | + echo "## 🔄 Available Dependency Updates" > dependency_report.md + echo "" >> dependency_report.md + echo "The following dependencies have updates available:" >> dependency_report.md + echo "" >> dependency_report.md + echo '```' >> dependency_report.md + grep -A 20 "outdated" dependency_updates.txt >> dependency_report.md || true + echo '```' >> dependency_report.md + + - name: Create Issue for Updates + if: steps.gradle-updates.outputs.updates-available == 'true' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const reportPath = 'dependency_report.md'; + + if (fs.existsSync(reportPath)) { + const body = fs.readFileSync(reportPath, 'utf8'); + + // Check if an issue already exists + const issues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + labels: 'dependencies', + state: 'open' + }); + + const existingIssue = issues.data.find(issue => + issue.title.includes('Dependency Updates Available') + ); + + if (!existingIssue) { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: '🔄 Dependency Updates Available', + body: body, + labels: ['dependencies', 'maintenance'] + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: existingIssue.number, + body: 'New dependency updates detected!\n\n' + body + }); + } + } + + - name: Upload Dependency Report + if: steps.gradle-updates.outputs.updates-available == 'true' + uses: actions/upload-artifact@v4 + with: + name: dependency-updates + path: | + dependency_updates.txt + dependency_report.md + retention-days: 30 + + security-audit: + name: 🔒 Security Audit + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: "zulu" + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + + - name: Run Security Audit + continue-on-error: true + run: | + ./gradlew dependencyCheckAnalyze + + - name: Upload Security Report + if: always() + uses: actions/upload-artifact@v4 + with: + name: security-audit + path: | + **/build/reports/dependency-check-report.html + **/build/reports/dependency-check-report.xml + retention-days: 30 + + - name: Check for Critical Vulnerabilities + run: | + if find . -name "dependency-check-report.xml" -exec grep -l "severity=\"HIGH\|severity=\"CRITICAL\"" {} \; | grep -q .; then + echo "⚠️ Critical vulnerabilities found! Check the security report." >> $GITHUB_STEP_SUMMARY + else + echo "✅ No critical vulnerabilities detected." >> $GITHUB_STEP_SUMMARY + fi \ No newline at end of file diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml new file mode 100644 index 00000000..9eb9b8c7 --- /dev/null +++ b/.github/workflows/pr-validation.yml @@ -0,0 +1,129 @@ +name: Pull Request Validation + +on: + pull_request: + branches: [ "master" ] + types: [opened, synchronize, reopened, ready_for_review] + +concurrency: + group: pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + +env: + GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false" + +jobs: + # Quick validation for PRs + pr-checks: + name: 🔍 PR Quality Checks + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + timeout-minutes: 20 + + steps: + - name: Checkout PR + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: "zulu" + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + cache-read-only: true + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v3 + + - name: Run Quick Lint + run: ./gradlew lint -x lintVitalRelease --continue + + - name: Check Code Formatting + run: ./gradlew spotlessCheck || echo "Code formatting issues found" + + - name: Compile All Modules + run: ./gradlew compileDebugKotlin --continue + + - name: Build Debug APKs + run: | + ./gradlew :app:smartphone:assembleDebug + ./gradlew :app:tv:assembleDebug + + - name: Comment PR + if: always() + uses: actions/github-script@v7 + with: + script: | + const conclusion = '${{ job.status }}' === 'success' ? '✅ All checks passed!' : '❌ Some checks failed'; + const body = `## PR Validation Results + + ${conclusion} + + ### Completed Checks: + - ✅ Gradle wrapper validation + - ✅ Code compilation + - ✅ Basic lint checks + - ✅ Debug APK build + + For full validation, this PR will be tested when merged to master. + `; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: body + }); + + # Analyze changed files + changed-files: + name: 📝 Analyze Changes + runs-on: ubuntu-latest + + outputs: + has-kotlin-changes: ${{ steps.changes.outputs.kotlin }} + has-gradle-changes: ${{ steps.changes.outputs.gradle }} + has-workflow-changes: ${{ steps.changes.outputs.workflows }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect Changes + id: changes + run: | + # Check for Kotlin changes + if git diff --name-only origin/master...HEAD | grep -E '\.(kt|kts)$'; then + echo "kotlin=true" >> $GITHUB_OUTPUT + else + echo "kotlin=false" >> $GITHUB_OUTPUT + fi + + # Check for Gradle changes + if git diff --name-only origin/master...HEAD | grep -E '\.(gradle|toml)$|gradle'; then + echo "gradle=true" >> $GITHUB_OUTPUT + else + echo "gradle=false" >> $GITHUB_OUTPUT + fi + + # Check for workflow changes + if git diff --name-only origin/master...HEAD | grep -E '\.github/workflows/'; then + echo "workflows=true" >> $GITHUB_OUTPUT + else + echo "workflows=false" >> $GITHUB_OUTPUT + fi + + - name: PR Summary + run: | + echo "## 📊 Change Analysis" >> $GITHUB_STEP_SUMMARY + echo "- Kotlin Changes: ${{ steps.changes.outputs.kotlin }}" >> $GITHUB_STEP_SUMMARY + echo "- Gradle Changes: ${{ steps.changes.outputs.gradle }}" >> $GITHUB_STEP_SUMMARY + echo "- Workflow Changes: ${{ steps.changes.outputs.workflows }}" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..963024bd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,194 @@ +name: Release Automation + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + tag: + description: 'Release tag (e.g., v1.0.0)' + required: true + type: string + prerelease: + description: 'Mark as pre-release' + required: false + default: false + type: boolean + +permissions: + contents: write + +env: + GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx6g -Dorg.gradle.daemon=false" + +jobs: + build-release: + name: 🏗️ Build Release + runs-on: ubuntu-latest + timeout-minutes: 60 + + outputs: + version: ${{ steps.version.outputs.version }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract Version + id: version + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + VERSION="${{ inputs.tag }}" + else + VERSION="${GITHUB_REF#refs/tags/}" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Building release $VERSION" + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: "zulu" + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + dependency-graph: generate-and-submit + + - name: Clean GMD + run: ./gradlew cleanManagedDevices --unused-only + + - name: Run Final Quality Checks + run: | + ./gradlew lint --continue + ./gradlew compileReleaseKotlin + + - name: Generate Baseline Profiles + run: | + ./gradlew generateBaselineProfile \ + -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile \ + -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" \ + -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true + + - name: Build Release APKs + run: | + ./gradlew :app:smartphone:assembleRelease \ + -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile + ./gradlew :app:tv:assembleRelease + + - name: Sign APKs + id: sign + continue-on-error: true + run: | + # This step would require signing keys to be configured + echo "APK signing would happen here if keys are configured" + echo "signed=false" >> $GITHUB_OUTPUT + + - name: Generate Checksums + run: | + cd app/smartphone/build/outputs/apk/release + sha256sum *.apk > checksums.txt + cd ../../../tv/build/outputs/apk/release + sha256sum *.apk >> checksums.txt + + - name: Prepare Release Assets + run: | + mkdir -p release-assets + cp app/smartphone/build/outputs/apk/release/*.apk release-assets/ + cp app/tv/build/outputs/apk/release/*.apk release-assets/ + find . -name "checksums.txt" -exec cp {} release-assets/ \; + + # Generate release info + echo "# M3UAndroid ${{ steps.version.outputs.version }}" > release-assets/RELEASE_NOTES.md + echo "" >> release-assets/RELEASE_NOTES.md + echo "## 📱 Downloads" >> release-assets/RELEASE_NOTES.md + echo "- **Smartphone APK**: $(basename app/smartphone/build/outputs/apk/release/*.apk)" >> release-assets/RELEASE_NOTES.md + echo "- **TV APK**: $(basename app/tv/build/outputs/apk/release/*.apk)" >> release-assets/RELEASE_NOTES.md + echo "" >> release-assets/RELEASE_NOTES.md + echo "## 🔍 Checksums" >> release-assets/RELEASE_NOTES.md + echo "\`\`\`" >> release-assets/RELEASE_NOTES.md + cat release-assets/checksums.txt >> release-assets/RELEASE_NOTES.md + echo "\`\`\`" >> release-assets/RELEASE_NOTES.md + + - name: Upload Release Assets + uses: actions/upload-artifact@v4 + with: + name: release-${{ steps.version.outputs.version }} + path: release-assets/ + retention-days: 90 + + create-release: + name: 🚀 Create GitHub Release + runs-on: ubuntu-latest + needs: build-release + timeout-minutes: 10 + + steps: + - name: Download Release Assets + uses: actions/download-artifact@v4 + with: + name: release-${{ needs.build-release.outputs.version }} + path: release-assets/ + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ needs.build-release.outputs.version }} + release_name: M3UAndroid ${{ needs.build-release.outputs.version }} + body_path: release-assets/RELEASE_NOTES.md + draft: false + prerelease: ${{ inputs.prerelease || false }} + + - name: Upload APK Assets + run: | + for apk in release-assets/*.apk; do + if [ -f "$apk" ]; then + echo "Uploading $(basename "$apk")" + curl -X POST \ + -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Content-Type: application/vnd.android.package-archive" \ + --data-binary @"$apk" \ + "${{ steps.create_release.outputs.upload_url }}?name=$(basename "$apk")" + fi + done + + - name: Upload Checksums + if: always() + run: | + if [ -f "release-assets/checksums.txt" ]; then + curl -X POST \ + -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Content-Type: text/plain" \ + --data-binary @"release-assets/checksums.txt" \ + "${{ steps.create_release.outputs.upload_url }}?name=checksums.txt" + fi + + notify-release: + name: 📢 Notify Release + runs-on: ubuntu-latest + needs: [build-release, create-release] + if: always() && needs.create-release.result == 'success' + + steps: + - name: Notify Telegram + if: success() + uses: xireiki/channel-post@v1.0.10 + with: + bot_token: ${{ secrets.BOT_TOKEN }} + chat_id: ${{ secrets.CHAT_ID }} + api_id: ${{ secrets.API_ID }} + api_hash: ${{ secrets.API_HASH }} + text: | + 🎉 **M3UAndroid ${{ needs.build-release.outputs.version }}** Released! + + 📱 Download from: https://github.com/${{ github.repository }}/releases/tag/${{ needs.build-release.outputs.version }} + + #M3UAndroid #Release #Android \ No newline at end of file diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 00000000..28c48c1c --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,106 @@ +name: "CodeQL Security Analysis" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + # Weekly security scans on Sunday at 3 AM UTC + - cron: '0 3 * * 0' + +permissions: + actions: read + contents: read + security-events: write + +jobs: + analyze: + name: 🔒 Security Analysis + runs-on: ubuntu-latest + timeout-minutes: 60 + + strategy: + fail-fast: false + matrix: + language: [ 'java', 'kotlin' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + config-file: ./.github/codeql/codeql-config.yml + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: "zulu" + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + cache-read-only: true + + - name: Build for Analysis + run: | + ./gradlew compileDebugKotlin compileDebugJavaWithJavac --continue + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" + + dependency-review: + name: 📦 Dependency Review + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - name: 'Checkout Repository' + uses: actions/checkout@v4 + + - name: 'Dependency Review' + uses: actions/dependency-review-action@v4 + with: + fail-on-severity: moderate + warn-only: false + comment-summary-in-pr: on-failure + + secret-scan: + name: 🔐 Secret Scanning + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + scan-ref: '.' + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH,MEDIUM' + + - name: Upload Trivy scan results + if: always() + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: 'trivy-results.sarif' + + - name: Upload Trivy Report + if: always() + uses: actions/upload-artifact@v4 + with: + name: trivy-security-scan + path: trivy-results.sarif + retention-days: 30 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1475c0ab..f67cbd8c 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,13 @@ jks.txt *.jks /sample/ /.kotlin + +# CI/CD Build artifacts +**/build/reports/ +**/build/dependencyUpdates/ +**/build/outputs/lint-results-*.html +**/build/outputs/lint-results-*.xml +**/build/reports/dependency-check-report.html +**/build/reports/dependency-check-report.xml +**/build/compose_metrics/ +/config/detekt/baseline.xml diff --git a/build.gradle.kts b/build.gradle.kts index 49bae1c3..1b067681 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,6 +16,16 @@ plugins { alias(libs.plugins.androidx.baselineprofile) apply false alias(libs.plugins.com.squareup.wire) apply false } + +// Extension function for version stability check +fun String.isNonStable(): Boolean { + val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { + uppercase().contains(it) + } + val regex = "^[0-9,.v-]+(-r)?$".toRegex() + val isStable = stableKeyword || regex.matches(this) + return isStable.not() +} subprojects { tasks.withType().configureEach { compilerOptions { diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml new file mode 100644 index 00000000..000ef16e --- /dev/null +++ b/config/detekt/detekt.yml @@ -0,0 +1,96 @@ +build: + maxIssues: 0 + excludeCorrectable: false + +config: + validation: true + checkExhaustiveness: false + +processors: + active: true + +console-reports: + active: true + +output-reports: + active: true + +comments: + active: true + UndocumentedPublicClass: + active: false + UndocumentedPublicFunction: + active: false + +complexity: + active: true + LongMethod: + active: true + threshold: 60 + LongParameterList: + active: true + functionThreshold: 6 + constructorThreshold: 7 + ignoreDataClasses: true + +coroutines: + active: true + GlobalCoroutineUsage: + active: false + +exceptions: + active: true + SwallowedException: + active: true + ignoredExceptionTypes: + - InterruptedException + - NumberFormatException + - ParseException + +naming: + active: true + FunctionNaming: + active: true + functionPattern: '[a-z][a-zA-Z0-9]*' + ignoreAnnotated: ['Composable'] + +performance: + active: true + +potential-bugs: + active: true + LateinitUsage: + active: false + +style: + active: true + MagicNumber: + active: true + ignoreNumbers: + - '-1' + - '0' + - '1' + - '2' + - '24' + - '60' + - '100' + - '1000' + ignoreHashCodeFunction: true + ignorePropertyDeclaration: false + ignoreConstantDeclaration: true + ignoreCompanionObjectPropertyDeclaration: true + ignoreAnnotation: false + ignoreNamedArgument: true + ignoreEnums: false + ignoreExtensionFunctions: true + MaxLineLength: + active: true + maxLineLength: 120 + excludePackageStatements: true + excludeImportStatements: true + WildcardImport: + active: true + excludeImports: + - 'java.util.*' + - 'kotlinx.android.synthetic.*' + - 'androidx.compose.ui.tooling.preview.*' \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a1c89bb7..70e45deb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,7 +41,7 @@ squareup-leakcanary = "2.14" squareup-wire = "5.3.5" kotlin = "2.2.0" -android-gradle-plugin = "8.9.1" +android-gradle-plugin = "8.5.2" kotlin-symbol-processor = "2.2.0-2.0.2" androidx-test-ext-junit = "1.2.1"