diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index efc228d..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,37 +0,0 @@ -version: 2 -updates: - # Stable 32-bit package.json (Node.js ≤22 only) - - package-ecosystem: npm - directory: "/stable/32bit" - schedule: - interval: daily - time: "10:00" - open-pull-requests-limit: 10 - versioning-strategy: increase - ignore: - - dependency-name: "node" - versions: [">22.x"] - labels: - - "Production" - - "32-bit" - commit-message: - prefix: "Production(32-bit)" - include: "scope" - - # Stable 64-bit package.json (Node.js ≤22 only) - - package-ecosystem: npm - directory: "/stable/64bit" - schedule: - interval: daily - time: "10:00" - open-pull-requests-limit: 10 - versioning-strategy: increase - ignore: - - dependency-name: "node" - versions: [">22.x"] - labels: - - "Production" - - "64-bit" - commit-message: - prefix: "Production(64-bit)" - include: "scope" \ No newline at end of file diff --git a/.github/homebridge-stable-bot.json b/.github/homebridge-stable-bot.json new file mode 100644 index 0000000..3e3b1dc --- /dev/null +++ b/.github/homebridge-stable-bot.json @@ -0,0 +1,43 @@ +{ + "auto_merge": true, + "git_user": { + "name": "Homebridge Stable Bot", + "email": "actions@github.com" + }, + "directories": [ + { + "directory": "stable/32bit", + "packages": [ + { + "name": "homebridge-config-ui-x", + "tag": "latest" + }, + { + "name": "homebridge", + "tag": "latest" + }, + { + "name": "node", + "tag": "^22.x.x" + } + ] + }, + { + "directory": "stable/64bit", + "packages": [ + { + "name": "homebridge-config-ui-x", + "tag": "latest" + }, + { + "name": "homebridge", + "tag": "latest" + }, + { + "name": "node", + "tag": "^24.x.x" + } + ] + } + ] +} \ No newline at end of file diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..d515032 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,69 @@ +# Consolidated GitHub Actions Workflows + +This directory contains the GitHub Actions workflows for the Homebridge APT package repository. As of the consolidation effort (Issue #190), the workflows have been reorganized to reduce duplication and improve maintainability. + +## Workflow Directory Structure + +This directory contains GitHub Actions workflows organized by functionality: + +### Core Release Workflows (2 files) +- `release-stage-1_update_dependencies.yml` - Bot-managed dependency updates for all release types (stable, beta, alpha) +- `release-stage-2_build_and_release.yml` - Unified build, validation and publishing pipeline for all releases + +### Reusable Workflows (1 file) +- `reusable-validate-homebridge.yml` - Package installation validation (used 2+ times) + +### Utility Workflows (7 files) +- `beta-backup_and_clean.yml` - Beta repository cleanup +- `pr-labeler.yml` - PR labeling automation +- `purge.yml` - CloudFlare cache purging +- `release_trigger_logger.yml` - Release event logging +- `stage-3_5_purge_cloudflare_cache.yml` - Post-release cache purging +- `stale.yml` - Stale issue management +- `README.md` - This documentation + +## Release Pipeline Architecture + +### Unified Process (All Release Types) +All release streams (stable, beta, alpha) follow the same 8-step pipeline: + +1. **Bot Updates Dependencies** - `homebridge-beta-bot` updates package.json files daily +2. **Build Packages** - Cross-platform package builds for all architectures +3. **Create GitHub Prerelease** - Upload artifacts and create prerelease +4. **Validate Prerelease** - Download and test .deb packages from GitHub +5. **Promote to Release** - Convert prerelease to full release after validation +6. **Publish to APT** - Upload packages to repository +7. **Validate APT Installation** - Test installation from repository +8. **Publish to NPM** - Publish package with appropriate tags + +### Configuration Files +- `.github/homebridge-stable-bot.json` - Bot configuration for stable releases +- `.github/homebridge-beta-bot.json` - Bot configuration for beta/alpha releases + +### Workflow Triggers +- **Scheduled**: Daily at 8 AM UTC for dependency updates +- **Push**: Triggered by bot commits to package.json files +- **Manual**: workflow_dispatch for testing and manual releases + +## Development and Maintenance + +### Modifying Release Logic +Since all release types use unified workflows, changes only need to be made in 2 places: +1. **Dependency Updates**: Edit `release-stage-1_update_dependencies.yml` +2. **Build/Release Pipeline**: Edit `release-stage-2_build_and_release.yml` + +### Adding Validation Steps +Edit the validation logic in `reusable-validate-homebridge.yml` which is used by both prerelease and APT validation steps. + +### Managing Bot Configuration +- **Stable releases**: Update `.github/homebridge-stable-bot.json` +- **Beta/Alpha releases**: Update `.github/homebridge-beta-bot.json` + +## Benefits of Consolidation + +1. **Maximum Workflow Reduction**: 25 → 10 workflows (60% reduction) +2. **Unified Release Management**: All release streams use identical infrastructure +3. **Enhanced Quality Assurance**: Double validation (GitHub + APT) for every release +4. **Simplified Operations**: Single workflow to maintain instead of separate logic +5. **Automated Process**: Eliminates manual promotion steps with automatic validation gates +6. **Future-Proof Architecture**: Easy to add validation steps or extend to new release streams \ No newline at end of file diff --git a/.github/workflows/Stage-4_post_release_validation.yml b/.github/workflows/Stage-4_post_release_validation.yml deleted file mode 100644 index ae41700..0000000 --- a/.github/workflows/Stage-4_post_release_validation.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: Stage 4 - Post Release Validation - -on: - workflow_dispatch: - inputs: - release_channel: - description: "APT release channel to validate" - required: true - default: "stable" - type: choice - options: - - stable - - beta - - alpha - - workflow_run: - workflows: - - "Stage 3 - Promote Release Package to APT Stores" - - "Beta Stage 2 - Build and Release Beta Package" - - "Alpha Stage 2 - Build and Release Alpha Package" - types: - - completed - -jobs: - determine-channel: - name: Determine Release Channel - runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }} - outputs: - channel: ${{ steps.set.outputs.release_channel }} - steps: - - id: set - run: | - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - echo "release_channel=${{ github.event.inputs.release_channel }}" >> $GITHUB_OUTPUT - elif [[ "${{ github.event.workflow_run.name }}" == "Beta Stage 2 - Build and Release Beta Package" ]]; then - echo "release_channel=beta" >> $GITHUB_OUTPUT - elif [[ "${{ github.event.workflow_run.name }}" == "Alpha Stage 2 - Build and Release Alpha Package" ]]; then - echo "release_channel=alpha" >> $GITHUB_OUTPUT - else - echo "release_channel=stable" >> $GITHUB_OUTPUT - fi - - validate: - name: Validate APT Install ${{ matrix.github-action-runner }} - needs: determine-channel - runs-on: ${{ matrix.github-action-runner }} - strategy: - fail-fast: false - matrix: - github-action-runner: [ubuntu-latest, ubuntu-24.04-arm] - steps: - - name: Install Homebridge from APT - run: | - curl -sSfL https://repo.homebridge.io/KEY.gpg | sudo gpg --dearmor -o /usr/share/keyrings/homebridge.gpg - echo "deb [signed-by=/usr/share/keyrings/homebridge.gpg] https://repo.homebridge.io ${{ needs.determine-channel.outputs.channel }} main" | \ - sudo tee /etc/apt/sources.list.d/homebridge.list > /dev/null - sudo apt-get update - sudo apt-get install -y homebridge - - - name: Display Installed Version - id: installed-version - run: | - dpkg -l homebridge - echo "homebridge-version=\"$(dpkg -l homebridge | tail -1 | awk '{ print $3 }')\"" >> "$GITHUB_OUTPUT" - - - name: Validate Homebridge - run: | - sudo hb-service status - sudo hb-service view - - - name: List Available Versions - run: | - apt-cache madison homebridge - - - name: Publish Notice - run: echo "::notice::APT Package Installed from ${{ needs.determine-channel.outputs.channel }} - Version ${{ steps.installed-version.outputs.homebridge-version }} - ${{ matrix.github-action-runner }}" diff --git a/.github/workflows/alpha-stage-1_update_alpha_dependencies.yml b/.github/workflows/alpha-stage-1_update_alpha_dependencies.yml deleted file mode 100644 index ec24540..0000000 --- a/.github/workflows/alpha-stage-1_update_alpha_dependencies.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Alpha Stage 1 - Update Alpha Dependencies, Create PR, Merge PR and Trigger Alpha Stage 2 -on: - schedule: - - cron: '0 9 * * *' # 5 AM Eastern (11 AM UTC), Docker updates at 6 AM Eastern (12 PM UTC) - workflow_dispatch: -run-name: Alpha Stage 1 - ${{ github.event_name == 'workflow_dispatch' && 'Manual' || 'Scheduled' }} Run - -jobs: - homebridge-alpha-bot: - name: Run Homebridge Alpha Bot - uses: homebridge/.github/.github/workflows/homebridge-beta-bot.yml@latest - with: - config_file: '.github/homebridge-alpha-bot.json' - release_stream: 'alpha' - secrets: inherit - - log-skipped-trigger: - name: Log Skipped Alpha Stage 2 Trigger - needs: homebridge-alpha-bot - if: needs.homebridge-alpha-bot.outputs.changes_detected != 'true' || needs.homebridge-alpha-bot.outputs.auto_merge != 'true' - runs-on: ubuntu-latest - steps: - - name: Log Skipped Trigger - run: | - echo "::warning::Alpha Stage 2 not triggered: Changes Detected=${{ needs.homebridge-alpha-bot.outputs.changes_detected }}, Auto Merge=${{ needs.homebridge-alpha-bot.outputs.auto_merge }}" - - trigger-alpha-stage-2: - name: Trigger Build and Release Alpha Package - needs: homebridge-alpha-bot - if: needs.homebridge-alpha-bot.outputs.changes_detected == 'true' && needs.homebridge-alpha-bot.outputs.auto_merge == 'true' - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - name: Trigger Alpha Stage 2 Workflow - run: | - echo "::notice::Triggering Alpha Stage 2 - Build and Release Alpha Package" - gh workflow run alpha-stage-2_build_alpha_release_and_store.yml --ref latest || { echo "::error::Failed to trigger Alpha Stage 2 workflow"; exit 1; } - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/alpha-stage-2_build_alpha_release_and_store.yml b/.github/workflows/alpha-stage-2_build_alpha_release_and_store.yml deleted file mode 100644 index 79a4ca6..0000000 --- a/.github/workflows/alpha-stage-2_build_alpha_release_and_store.yml +++ /dev/null @@ -1,280 +0,0 @@ -name: Alpha Stage 2 - Build and Release Alpha Package - -permissions: - contents: write - actions: write - id-token: write - -on: - pull_request: - types: [closed] - workflow_dispatch: - -jobs: - check-changes: - if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - outputs: - alpha_only: ${{ steps.verify_changes.outputs.alpha_only }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - - name: Log event context for debugging - if: github.event_name == 'pull_request' - run: | - echo "Event: ${{ github.event_name }}" - echo "Merged: ${{ github.event.pull_request.merged }}" - - - name: Verify alpha-only changes - id: verify_changes - run: | - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - echo "Workflow dispatch detected, setting alpha_only=true" - echo "alpha_only=true" >> $GITHUB_OUTPUT - else - alpha_only=true - changed_files=$(git diff --name-only HEAD^ HEAD) - for file in $changed_files; do - if [[ ! $file == alpha/* ]]; then - echo "Non-alpha changes detected in $file. Setting alpha_only=false" - alpha_only=false - fi - done - echo "alpha_only=$alpha_only" >> $GITHUB_OUTPUT - fi - - generate_alpha_version: - needs: check-changes - if: ${{ needs.check-changes.outputs.alpha_only == 'true' }} - name: Generate Alpha Package Version - runs-on: ubuntu-latest - outputs: - version: ${{ steps.alpha_version.outputs.version }} - npm_version: ${{ steps.alpha_version.outputs.npm_version }} - steps: - - uses: actions/checkout@v4 - - - name: Get base version - uses: reecetech/version-increment@2023.10.1 - id: release_version - with: - scheme: semver - increment: patch - - - name: Generate alpha package version - id: alpha_version - run: | - BASE_VERSION=${{ steps.release_version.outputs.version }} - BASE_VERSION_CLEAN=$(echo "$BASE_VERSION" | sed -E 's/-[a-z0-9.]+//') - DATE=$(date +%Y%m%d) - VERSION="${BASE_VERSION_CLEAN}~alpha.${DATE}" - NPM_VERSION="${BASE_VERSION_CLEAN}-alpha.${DATE}" - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "npm_version=$NPM_VERSION" >> $GITHUB_OUTPUT - echo "::notice::📦 Alpha version: $VERSION" - - build_alpha_and_store: - name: Build Alpha Packages for (${{ matrix.name }}) - needs: [generate_alpha_version] - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - name: [debian-x86_64, debian-arm64v8, debian-arm32v6] - include: - - name: debian-x86_64 - os: ubuntu-latest - BASE_IMAGE: library/debian:bullseye - QEMU_ARCH: x86_64 - - - name: debian-arm64v8 - os: ubuntu-24.04-arm - BASE_IMAGE: arm64v8/debian:bullseye - QEMU_ARCH: aarch64 - - - name: debian-arm32v6 - os: ubuntu-24.04-arm - BASE_IMAGE: balenalib/raspberrypi3-debian:bullseye - QEMU_ARCH: arm - - steps: - - uses: actions/checkout@v4 - - - name: Setup build environment X64 - if: runner.os == 'Linux' && runner.arch == 'X64' - run: | - sudo apt-get update - sudo apt-get --yes --no-install-recommends install binfmt-support qemu-user-static - docker run --rm --privileged multiarch/qemu-user-static:register --reset - continue-on-error: false - - - name: Setup build environment ARM64 - if: runner.os == 'Linux' && runner.arch == 'ARM64' - run: | - sudo apt-get update - sudo apt-get --yes --no-install-recommends install binfmt-support - continue-on-error: false - - - name: Build Docker image - run: | - docker build -f build/Dockerfile \ - --build-arg BASE_IMAGE=${{ matrix.BASE_IMAGE }} \ - --build-arg QEMU_ARCH=${{ matrix.QEMU_ARCH }} \ - -t alpha-package-build \ - --platform=linux/${{ matrix.QEMU_ARCH }} \ - --cache-from=alpha-package-build:latest \ - --build-arg BUILDKIT_INLINE_CACHE=1 . - continue-on-error: false - - - name: Build package - run: | - docker run --rm -v $(pwd):/repo \ - -e PKG_RELEASE_TYPE="alpha" \ - -e PKG_RELEASE_VERSION="${{ needs.generate_alpha_version.outputs.version }}" \ - alpha-package-build - continue-on-error: false - - - name: Rename package to include v - run: | - DEB_FILE=$(ls homebridge*.deb 2>/dev/null || echo "") - if [ -z "$DEB_FILE" ]; then - echo "No .deb file found. Exiting." - exit 1 - fi - UPDATED=$(echo "$DEB_FILE" | sed -e 's/homebridge_/homebridge_v/g') - mv "$DEB_FILE" "$UPDATED" - - - name: Rename manifest to include v - run: | - MANIFEST_FILE=$(ls homebridge*.manifest 2>/dev/null || echo "") - if [ -z "$MANIFEST_FILE" ]; then - echo "No .manifest file found. Exiting." - exit 1 - fi - UPDATED=$(echo "$MANIFEST_FILE" | sed -e 's/homebridge_/homebridge_v/g') - mv "$MANIFEST_FILE" "$UPDATED" - - - uses: actions/upload-artifact@v4 - with: - name: artifacts-${{ matrix.name }} - retention-days: 7 - path: | - homebridge_v*.deb - homebridge_v*.manifest - - publish_apt_alpha: - name: APT Alpha Repo Publish ${{ needs.generate_alpha_version.outputs.version }} - runs-on: ubuntu-latest - needs: [generate_alpha_version, build_alpha_and_store] - steps: - - uses: actions/checkout@v4 - - - name: Download all artifacts - uses: actions/download-artifact@v4 - with: - pattern: artifacts-* - merge-multiple: true - path: repo/ - - - name: Display structure of downloaded files - run: ls -R - - - name: Import GPG Key - uses: crazy-max/ghaction-import-gpg@v6 - with: - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - passphrase: ${{ secrets.GPG_PASSPHRASE }} - - - name: Install deb-s3 - run: | - curl -sLO https://github.com/deb-s3/deb-s3/releases/download/0.11.8/deb-s3-0.11.8.gem - sudo gem install deb-s3-0.11.8.gem - - - name: Upload to Alpha APT Repo - run: | - sudo chown -R $USER: repo/ - deb-s3 upload \ - --codename=alpha \ - --suite=alpha \ - --preserve-versions \ - --s3-region=us-west-2 \ - --bucket repo.homebridge.io \ - --access-key-id=${{ secrets.AWS_ACCESS_KEY_ID }} \ - --secret-access-key=${{ secrets.AWS_SECRET_ACCESS_KEY }} \ - --sign=${{ secrets.GPG_KEY_ID }} \ - repo/homebridge_v*.deb - - publish_github_alpha_release: - name: Publish GitHub Alpha Release v${{ needs.generate_alpha_version.outputs.npm_version }} - needs: [check-changes, publish_apt_alpha, generate_alpha_version] - runs-on: ubuntu-latest - steps: - - name: Download all artifacts - uses: actions/download-artifact@v4 - with: - pattern: artifacts-* - merge-multiple: true - path: artifacts/ - - - name: Read amd64 manifest content - id: read_manifest - run: | - echo "MANIFEST_FILE=$(ls artifacts/*.manifest | head -n 1)" >> $GITHUB_OUTPUT - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: v${{ needs.generate_alpha_version.outputs.npm_version }} - name: "ALPHA: Homebridge APT Pkg Release v${{ needs.generate_alpha_version.outputs.npm_version }}" - prerelease: true - files: | - artifacts/homebridge_v*.deb - artifacts/homebridge_v*.manifest - body_path: ${{ steps.read_manifest.outputs.MANIFEST_FILE }} - body: | - Homebridge Apt Package Manifest - - Release Version: v${{ needs.generate_alpha_version.outputs.npm_version }} - Release Type: alpha - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - purge_cloudflare_cache: - name: Purge Cloudflare Cache - needs: [publish_apt_alpha] - uses: ./.github/workflows/stage-3_5_purge_cloudflare_cache.yml - secrets: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }} - - publish_to_npm: - needs: [generate_alpha_version, build_alpha_and_store] - name: NPM Publish ${{ needs.generate_alpha_version.outputs.npm_version }} - runs-on: ubuntu-latest - steps: - - name: Checkout Repo - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: latest - registry-url: 'https://registry.npmjs.org' - - - name: Set package.json version - run: | - ALPHA_VERSION="${{ needs.generate_alpha_version.outputs.npm_version }}" - echo "Setting version to $ALPHA_VERSION" - jq ".version = \"$ALPHA_VERSION\"" package.json > tmp.$$.json && mv tmp.$$.json package.json - cat package.json - - - name: Publish to npm with alpha tag - run: npm publish --access public --tag alpha - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Output Success Notice - run: echo "::notice::Published @homebridge/homebridge-apt-pkg as version ${{ needs.generate_alpha_version.outputs.npm_version }} with alpha tag" \ No newline at end of file diff --git a/.github/workflows/beta-stage-1_update_beta_dependencies.yml b/.github/workflows/beta-stage-1_update_beta_dependencies.yml deleted file mode 100644 index 90a0339..0000000 --- a/.github/workflows/beta-stage-1_update_beta_dependencies.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Beta Stage 1 - Update Beta Dependencies, Create PR, Merge PR and Trigger Beta Stage 2 -on: - schedule: - - cron: '0 8 * * *' # 4 AM Eastern (10 AM UTC), Docker updates at 6 AM Eastern (12 PM UTC) - workflow_dispatch: -run-name: Beta Stage 1 - ${{ github.event_name == 'workflow_dispatch' && 'Manual' || 'Scheduled' }} Run - -jobs: - homebridge-beta-bot: - name: Run Homebridge Beta Bot - uses: homebridge/.github/.github/workflows/homebridge-beta-bot.yml@latest - with: - config_file: '.github/homebridge-beta-bot.json' - secrets: inherit - - log-skipped-trigger: - name: Log Skipped Beta Stage 2 Trigger - needs: homebridge-beta-bot - if: needs.homebridge-beta-bot.outputs.changes_detected != 'true' || needs.homebridge-beta-bot.outputs.auto_merge != 'true' - runs-on: ubuntu-latest - steps: - - name: Log Skipped Trigger - run: | - echo "::warning::Beta Stage 2 not triggered: Changes Detected=${{ needs.homebridge-beta-bot.outputs.changes_detected }}, Auto Merge=${{ needs.homebridge-beta-bot.outputs.auto_merge }}" - - trigger-beta-stage-2: - name: Trigger Build and Release Beta Package - needs: homebridge-beta-bot - if: needs.homebridge-beta-bot.outputs.changes_detected == 'true' && needs.homebridge-beta-bot.outputs.auto_merge == 'true' - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - name: Trigger Beta Stage 2 Workflow - run: | - echo "::notice::Triggering Beta Stage 2 - Build and Release Beta Package" - gh workflow run beta-stage-2_build_beta_release_and_store.yml --ref latest || { echo "::error::Failed to trigger Beta Stage 2 workflow"; exit 1; } - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/beta-stage-2_build_beta_release_and_store.yml b/.github/workflows/beta-stage-2_build_beta_release_and_store.yml deleted file mode 100644 index ed94dd1..0000000 --- a/.github/workflows/beta-stage-2_build_beta_release_and_store.yml +++ /dev/null @@ -1,280 +0,0 @@ -name: Beta Stage 2 - Build and Release Beta Package - -permissions: - contents: write - actions: write - id-token: write - -on: - pull_request: - types: [closed] - workflow_dispatch: - -jobs: - check-changes: - if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - outputs: - beta_only: ${{ steps.verify_changes.outputs.beta_only }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - - name: Log event context for debugging - if: github.event_name == 'pull_request' - run: | - echo "Event: ${{ github.event_name }}" - echo "Merged: ${{ github.event.pull_request.merged }}" - - - name: Verify beta-only changes - id: verify_changes - run: | - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - echo "Workflow dispatch detected, setting beta_only=true" - echo "beta_only=true" >> $GITHUB_OUTPUT - else - beta_only=true - changed_files=$(git diff --name-only HEAD^ HEAD) - for file in $changed_files; do - if [[ ! $file == beta/* ]]; then - echo "Non-beta changes detected in $file. Setting beta_only=false" - beta_only=false - fi - done - echo "beta_only=$beta_only" >> $GITHUB_OUTPUT - fi - - generate_beta_version: - needs: check-changes - if: ${{ needs.check-changes.outputs.beta_only == 'true' }} - name: Generate Beta Package Version - runs-on: ubuntu-latest - outputs: - version: ${{ steps.beta_version.outputs.version }} - npm_version: ${{ steps.beta_version.outputs.npm_version }} - steps: - - uses: actions/checkout@v4 - - - name: Get base version - uses: reecetech/version-increment@2023.10.1 - id: release_version - with: - scheme: semver - increment: patch - - - name: Generate beta package version - id: beta_version - run: | - BASE_VERSION=${{ steps.release_version.outputs.version }} - BASE_VERSION_CLEAN=$(echo "$BASE_VERSION" | sed -E 's/-[a-z0-9.]+//') - DATE=$(date +%Y%m%d) - VERSION="${BASE_VERSION_CLEAN}~beta.${DATE}" - NPM_VERSION="${BASE_VERSION_CLEAN}-beta.${DATE}" - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "npm_version=$NPM_VERSION" >> $GITHUB_OUTPUT - echo "::notice::📦 Beta version: $VERSION" - - build_beta_and_store: - name: Build Beta Packages for (${{ matrix.name }}) - needs: [generate_beta_version] - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - name: [debian-x86_64, debian-arm64v8, debian-arm32v6] - include: - - name: debian-x86_64 - os: ubuntu-latest - BASE_IMAGE: library/debian:bullseye - QEMU_ARCH: x86_64 - - - name: debian-arm64v8 - os: ubuntu-24.04-arm - BASE_IMAGE: arm64v8/debian:bullseye - QEMU_ARCH: aarch64 - - - name: debian-arm32v6 - os: ubuntu-24.04-arm - BASE_IMAGE: balenalib/raspberrypi3-debian:bullseye - QEMU_ARCH: arm - - steps: - - uses: actions/checkout@v4 - - - name: Setup build environment X64 - if: runner.os == 'Linux' && runner.arch == 'X64' - run: | - sudo apt-get update - sudo apt-get --yes --no-install-recommends install binfmt-support qemu-user-static - docker run --rm --privileged multiarch/qemu-user-static:register --reset - continue-on-error: false - - - name: Setup build environment ARM64 - if: runner.os == 'Linux' && runner.arch == 'ARM64' - run: | - sudo apt-get update - sudo apt-get --yes --no-install-recommends install binfmt-support - continue-on-error: false - - - name: Build Docker image - run: | - docker build -f build/Dockerfile \ - --build-arg BASE_IMAGE=${{ matrix.BASE_IMAGE }} \ - --build-arg QEMU_ARCH=${{ matrix.QEMU_ARCH }} \ - -t beta-package-build \ - --platform=linux/${{ matrix.QEMU_ARCH }} \ - --cache-from=beta-package-build:latest \ - --build-arg BUILDKIT_INLINE_CACHE=1 . - continue-on-error: false - - - name: Build package - run: | - docker run --rm -v $(pwd):/repo \ - -e PKG_RELEASE_TYPE="beta" \ - -e PKG_RELEASE_VERSION="${{ needs.generate_beta_version.outputs.version }}" \ - beta-package-build - continue-on-error: false - - - name: Rename package to include v - run: | - DEB_FILE=$(ls homebridge*.deb 2>/dev/null || echo "") - if [ -z "$DEB_FILE" ]; then - echo "No .deb file found. Exiting." - exit 1 - fi - UPDATED=$(echo "$DEB_FILE" | sed -e 's/homebridge_/homebridge_v/g') - mv "$DEB_FILE" "$UPDATED" - - - name: Rename manifest to include v - run: | - MANIFEST_FILE=$(ls homebridge*.manifest 2>/dev/null || echo "") - if [ -z "$MANIFEST_FILE" ]; then - echo "No .manifest file found. Exiting." - exit 1 - fi - UPDATED=$(echo "$MANIFEST_FILE" | sed -e 's/homebridge_/homebridge_v/g') - mv "$MANIFEST_FILE" "$UPDATED" - - - uses: actions/upload-artifact@v4 - with: - name: artifacts-${{ matrix.name }} - retention-days: 7 - path: | - homebridge_v*.deb - homebridge_v*.manifest - - publish_apt_beta: - name: APT Beta Repo Publish ${{ needs.generate_beta_version.outputs.version }} - runs-on: ubuntu-latest - needs: [generate_beta_version, build_beta_and_store] - steps: - - uses: actions/checkout@v4 - - - name: Download all artifacts - uses: actions/download-artifact@v4 - with: - pattern: artifacts-* - merge-multiple: true - path: repo/ - - - name: Display structure of downloaded files - run: ls -R - - - name: Import GPG Key - uses: crazy-max/ghaction-import-gpg@v6 - with: - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - passphrase: ${{ secrets.GPG_PASSPHRASE }} - - - name: Install deb-s3 - run: | - curl -sLO https://github.com/deb-s3/deb-s3/releases/download/0.11.8/deb-s3-0.11.8.gem - sudo gem install deb-s3-0.11.8.gem - - - name: Upload to Beta APT Repo - run: | - sudo chown -R $USER: repo/ - deb-s3 upload \ - --codename=beta \ - --suite=beta \ - --preserve-versions \ - --s3-region=us-west-2 \ - --bucket repo.homebridge.io \ - --access-key-id=${{ secrets.AWS_ACCESS_KEY_ID }} \ - --secret-access-key=${{ secrets.AWS_SECRET_ACCESS_KEY }} \ - --sign=${{ secrets.GPG_KEY_ID }} \ - repo/homebridge_v*.deb - - publish_github_beta_release: - name: Publish GitHub Beta Release v${{ needs.generate_beta_version.outputs.npm_version }} - needs: [check-changes, publish_apt_beta, generate_beta_version] - runs-on: ubuntu-latest - steps: - - name: Download all artifacts - uses: actions/download-artifact@v4 - with: - pattern: artifacts-* - merge-multiple: true - path: artifacts/ - - - name: Read amd64 manifest content - id: read_manifest - run: | - echo "MANIFEST_FILE=$(ls artifacts/*.manifest | head -n 1)" >> $GITHUB_OUTPUT - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: v${{ needs.generate_beta_version.outputs.npm_version }} - name: "BETA: Homebridge APT Pkg Release v${{ needs.generate_beta_version.outputs.npm_version }}" - prerelease: true - files: | - artifacts/homebridge_v*.deb - artifacts/homebridge_v*.manifest - body_path: ${{ steps.read_manifest.outputs.MANIFEST_FILE }} - body: | - Homebridge Apt Package Manifest - - Release Version: v${{ needs.generate_beta_version.outputs.npm_version }} - Release Type: beta - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - purge_cloudflare_cache: - name: Purge Cloudflare Cache - needs: [publish_apt_beta] - uses: ./.github/workflows/stage-3_5_purge_cloudflare_cache.yml - secrets: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }} - - publish_to_npm: - needs: [generate_beta_version, build_beta_and_store] - name: NPM Publish ${{ needs.generate_beta_version.outputs.npm_version }} - runs-on: ubuntu-latest - steps: - - name: Checkout Repo - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: latest - registry-url: 'https://registry.npmjs.org' - - - name: Set package.json version - run: | - BETA_VERSION="${{ needs.generate_beta_version.outputs.npm_version }}" - echo "Setting version to $BETA_VERSION" - jq ".version = \"$BETA_VERSION\"" package.json > tmp.$$.json && mv tmp.$$.json package.json - cat package.json - - - name: Publish to npm with beta tag - run: npm publish --access public --tag beta - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Output Success Notice - run: echo "::notice::Published @homebridge/homebridge-apt-pkg as version ${{ needs.generate_beta_version.outputs.npm_version }} with beta tag" \ No newline at end of file diff --git a/.github/workflows/old-beta-stage-1_update_beta_dependencies.yml b/.github/workflows/old-beta-stage-1_update_beta_dependencies.yml deleted file mode 100644 index 86d032f..0000000 --- a/.github/workflows/old-beta-stage-1_update_beta_dependencies.yml +++ /dev/null @@ -1,202 +0,0 @@ -name: OLD Beta Stage 1 - Update Beta Dependencies, Create PR, Merge PR and Trigger Beta Stage 2 - -on: -# schedule: -# - cron: '0 8 * * *' # 4 AM Eastern (10 AM UTC), Docker updates at 6 AM Eastern (12 PM UTC) - workflow_dispatch: - -jobs: - update: - runs-on: ubuntu-latest - outputs: - changes_detected: ${{ steps.check-changes.outputs.changes_detected }} - changed_dirs: ${{ steps.check-changes.outputs.changed_dirs }} - branch_name: ${{ steps.create-branch.outputs.branch_name }} - pr_number: ${{ steps.create-pr.outputs.pr_number }} - auto_merge: ${{ steps.load-config.outputs.auto_merge }} - user_name: ${{ steps.load-config.outputs.user_name }} - user_email: ${{ steps.load-config.outputs.user_email }} - dirs_length: ${{ steps.load-config.outputs.dirs_length }} - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - fetch-depth: 0 - - - uses: actions/setup-node@v4 - with: - node-version: 'latest' - - - name: Install jq - run: sudo apt-get install -y jq - - - name: Load config file - id: load-config - run: | - config=".github/beta-bot.json" - config_abs="$(pwd)/$config" - if [ ! -f "$config_abs" ]; then - echo "::error::Config file not found: $config_abs. Please ensure .github/beta-bot.json exists in the repository." - exit 1 - fi - user_name=$(jq -r '.git_user.name // "GitHub Actions"' "$config_abs") - user_email=$(jq -r '.git_user.email // "actions@github.com"' "$config_abs") - auto_merge=$(jq -r '.auto_merge // false' "$config_abs") - dirs_length=$(jq '.directories | length' "$config_abs") - for i in $(seq 0 $(($dirs_length - 1))); do - pkgs_length=$(jq ".directories[$i].packages | length" "$config_abs") - for j in $(seq 0 $(($pkgs_length - 1))); do - pkg=$(jq -r ".directories[$i].packages[$j].name" "$config_abs") - has_tag=$(jq -r ".directories[$i].packages[$j].tag // null" "$config_abs") - has_pattern=$(jq -r ".directories[$i].packages[$j].pattern // null" "$config_abs") - if [ "$has_tag" != "null" ] && [ "$has_pattern" != "null" ]; then - echo "::error::Package $pkg in directory $i has both tag and pattern defined. Specify only one." - exit 1 - fi - if [ "$has_tag" = "null" ] && [ "$has_pattern" = "null" ]; then - echo "::error::Package $pkg in directory $i has neither tag nor pattern defined. Specify one." - exit 1 - fi - done - done - echo "Config file loaded: $config_abs" - echo "config_abs=$config_abs" >> $GITHUB_OUTPUT - echo "user_name=$user_name" >> $GITHUB_OUTPUT - echo "user_email=$user_email" >> $GITHUB_OUTPUT - echo "auto_merge=$auto_merge" >> $GITHUB_OUTPUT - echo "dirs_length=$dirs_length" >> $GITHUB_OUTPUT - - - name: Configure Git identity - run: | - git config user.name "${{ steps.load-config.outputs.user_name }}" - git config user.email "${{ steps.load-config.outputs.user_email }}" - echo "Git identity configured: ${{ steps.load-config.outputs.user_name }} <${{ steps.load-config.outputs.user_email }}>" - - - name: Process directories and packages - id: process-dirs - run: | - config="${{ steps.load-config.outputs.config_abs }}" - dirs_length="${{ steps.load-config.outputs.dirs_length }}" - echo "Found $dirs_length directories in config" - for i in $(seq 0 $(($dirs_length - 1))); do - dir=$(jq -r ".directories[$i].directory" "$config") - if [ ! -d "$dir" ]; then - echo "Directory not found: $dir" - continue - fi - echo "Processing directory: $dir" - cd "$dir" - pkgs_length=$(jq ".directories[$i].packages | length" "$config") - echo "Found $pkgs_length packages in $dir" - for j in $(seq 0 $(($pkgs_length - 1))); do - pkg=$(jq -r ".directories[$i].packages[$j].name" "$config") - tag=$(jq -r ".directories[$i].packages[$j].tag // null" "$config") - pattern=$(jq -r ".directories[$i].packages[$j].pattern // null" "$config") - if [ "$tag" != "null" ]; then - echo "::notice::Installing $pkg@$tag in $dir" - npm install "$pkg@$tag" || { echo "::error::Failed to install $pkg@$tag"; exit 1; } - else - unescaped_pattern=$(echo "$pattern" | sed 's/\\\+/\\/g') - latest_version=$(npm view "$pkg" versions --json | jq -r ".[] | select(test(\"$unescaped_pattern\"))" | sort -V | tail -n 1) - if [ -z "$latest_version" ]; then - echo "::error::No versions found for $pkg matching pattern $unescaped_pattern" - exit 1 - fi - echo "::notice::Installing $pkg@$latest_version in $dir" - npm install "$pkg@$latest_version" || { echo "::error::Failed to install $pkg@$latest_version"; exit 1; } - fi - done - cd - > /dev/null - echo "Completed processing $dir" - done - - - name: Check for changes - id: check-changes - run: | - config="${{ steps.load-config.outputs.config_abs }}" - dirs_length="${{ steps.load-config.outputs.dirs_length }}" - changes_detected=false - changed_dirs="" - for i in $(seq 0 $(($dirs_length - 1))); do - dir=$(jq -r ".directories[$i].directory" "$config") - if [ ! -d "$dir" ]; then - continue - fi - cd "$dir" - if git diff --quiet -- package.json package-lock.json; then - echo "No changes in $dir" - else - echo "Changes detected in $dir" - changes_detected=true - if [ -z "$changed_dirs" ]; then - changed_dirs="$dir" - else - changed_dirs="$changed_dirs,$dir" - fi - git add package.json package-lock.json - fi - cd - > /dev/null - done - if [ "$changes_detected" = "true" ]; then - echo "::notice::Changes detected in directories: $changed_dirs" - else - echo "::notice::No changes detected in any directories" - fi - echo "changes_detected=$changes_detected" >> $GITHUB_OUTPUT - echo "changed_dirs=$changed_dirs" >> $GITHUB_OUTPUT - - - name: Create and push branch - id: create-branch - if: steps.check-changes.outputs.changes_detected == 'true' - run: | - changed_dirs="${{ steps.check-changes.outputs.changed_dirs }}" - branch="update/beta-$(date +%s)" - git checkout -b "$branch" - git commit -m "Update beta dependencies in $changed_dirs" - git push origin "$branch" - echo "::notice::Branch pushed: $branch" - echo "branch_name=$branch" >> $GITHUB_OUTPUT - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Create pull request - id: create-pr - if: steps.check-changes.outputs.changes_detected == 'true' - run: | - branch="${{ steps.create-branch.outputs.branch_name }}" - changed_dirs="${{ steps.check-changes.outputs.changed_dirs }}" - pr_url=$(gh pr create --title "BETA: Update beta dependencies in $changed_dirs" --body "Automated dependency update" --label beta) - pr_number="${pr_url##*/}" - echo "::notice::Pull request created: #$pr_number ($pr_url)" - echo "PR created: $pr_url" - echo "pr_number=$pr_number" >> $GITHUB_OUTPUT - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Approve pull request - id: approve-pr - if: steps.check-changes.outputs.changes_detected == 'true' && steps.load-config.outputs.auto_merge == 'true' - run: | - pr_number="${{ steps.create-pr.outputs.pr_number }}" - echo "::notice::Approving PR #$pr_number" - gh pr review "$pr_number" --approve || { echo "::error::Failed to approve PR #$pr_number"; exit 1; } - env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} - - - name: Auto-merge PR - id: auto-merge - if: steps.check-changes.outputs.changes_detected == 'true' && steps.load-config.outputs.auto_merge == 'true' - run: | - pr_number="${{ steps.create-pr.outputs.pr_number }}" - echo "::notice::Auto-merging PR #$pr_number" - gh pr merge "$pr_number" --squash --delete-branch || { echo "::error::Failed to merge PR #$pr_number"; exit 1; } - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Trigger Beta Stage 2 Workflow - if: steps.check-changes.outputs.changes_detected == 'true' && steps.load-config.outputs.auto_merge == 'true' - run: | - echo "::notice::Triggering Beta Stage 2 - Build and Release Beta Package" - gh workflow run beta-stage-2_build_beta_release_and_store.yml --ref latest || { echo "::error::Failed to trigger Beta Stage 2 workflow"; exit 1; } - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/purge.yml b/.github/workflows/purge.yml index 8771167..d21dfa2 100644 --- a/.github/workflows/purge.yml +++ b/.github/workflows/purge.yml @@ -57,7 +57,7 @@ jobs: purge_cloudflare_cache: name: Clear Cache needs: purge_old_releases - uses: ./.github/workflows/purge-cf-cache.yml + uses: ./.github/workflows/stage-3_5_purge_cloudflare_cache.yml secrets: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }} diff --git a/.github/workflows/release-stage-1_update_dependencies.yml b/.github/workflows/release-stage-1_update_dependencies.yml new file mode 100644 index 0000000..b8e78e6 --- /dev/null +++ b/.github/workflows/release-stage-1_update_dependencies.yml @@ -0,0 +1,74 @@ +name: Release Stage 1 - Update Alpha, Beta, and Stable Dependencies, Create PRs, Merge PRs and Trigger Stage 2 + +on: + schedule: + # Process alpha, beta, and stable streams at 8 AM UTC daily + - cron: '0 8 * * *' + workflow_dispatch: + inputs: + release_type: + description: 'Release type to update' + required: true + type: choice + options: + - alpha + - beta + - stable + - all + default: 'all' + +run-name: Release Stage 1 - ${{ github.event_name == 'workflow_dispatch' && format('Manual - {0}', github.event.inputs.release_type) || 'Scheduled' }} Run + +jobs: + determine-release-types: + name: Determine Release Types + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Set matrix based on trigger + id: set-matrix + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + if [[ "${{ github.event.inputs.release_type }}" == "all" ]]; then + echo 'matrix={"release_type":["alpha","beta","stable"],"include":[{"release_type":"alpha","config_file":".github/homebridge-alpha-bot.json","trigger_workflow":"release-stage-2_build_and_release.yml"},{"release_type":"beta","config_file":".github/homebridge-beta-bot.json","trigger_workflow":"release-stage-2_build_and_release.yml"},{"release_type":"stable","config_file":".github/homebridge-stable-bot.json","trigger_workflow":"release-stage-2_build_and_release.yml"}]}' >> $GITHUB_OUTPUT + else + CONFIG_FILE=".github/homebridge-${{ github.event.inputs.release_type }}-bot.json" + echo "matrix={\"release_type\":[\"${{ github.event.inputs.release_type }}\"],\"include\":[{\"release_type\":\"${{ github.event.inputs.release_type }}\",\"config_file\":\"$CONFIG_FILE\",\"trigger_workflow\":\"release-stage-2_build_and_release.yml\"}]}" >> $GITHUB_OUTPUT + fi + else + # Scheduled run - process alpha, beta, and stable streams + echo 'matrix={"release_type":["alpha","beta","stable"],"include":[{"release_type":"alpha","config_file":".github/homebridge-alpha-bot.json","trigger_workflow":"release-stage-2_build_and_release.yml"},{"release_type":"beta","config_file":".github/homebridge-beta-bot.json","trigger_workflow":"release-stage-2_build_and_release.yml"},{"release_type":"stable","config_file":".github/homebridge-stable-bot.json","trigger_workflow":"release-stage-2_build_and_release.yml"}]}' >> $GITHUB_OUTPUT + fi + + update_dependencies: + name: Update ${{ matrix.release_type }} Dependencies + needs: determine-release-types + strategy: + matrix: ${{ fromJson(needs.determine-release-types.outputs.matrix) }} + runs-on: ubuntu-latest + steps: + - name: Run Homebridge ${{ matrix.release_type }} Bot + id: homebridge-bot + uses: homebridge/.github/sharedWorkflows/homebridge-dependency-bot.yml@latest + with: + config_file: ${{ matrix.config_file }} + release_stream: ${{ matrix.release_type }} + secrets: inherit + + - name: Log Skipped ${{ matrix.release_type }} Stage 2 Trigger + if: steps.homebridge-bot.outputs.changes_detected != 'true' || steps.homebridge-bot.outputs.auto_merge != 'true' + run: | + echo "::warning::${{ matrix.release_type }} Stage 2 not triggered: Changes Detected=${{ steps.homebridge-bot.outputs.changes_detected }}, Auto Merge=${{ steps.homebridge-bot.outputs.auto_merge }}" + + - name: Trigger Build and Release ${{ matrix.release_type }} Package + if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' + run: | + echo "::notice::Triggering ${{ matrix.release_type }} Stage 2 - Build and Release ${{ matrix.release_type }} Package" + gh workflow run ${{ matrix.trigger_workflow }} --ref latest || { echo "::error::Failed to trigger ${{ matrix.release_type }} Stage 2 workflow"; exit 1; } + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Checkout repository (for trigger step) + if: steps.homebridge-bot.outputs.changes_detected == 'true' && steps.homebridge-bot.outputs.auto_merge == 'true' + uses: actions/checkout@v4 diff --git a/.github/workflows/release-stage-2_build_and_release.yml b/.github/workflows/release-stage-2_build_and_release.yml new file mode 100644 index 0000000..bd8ee51 --- /dev/null +++ b/.github/workflows/release-stage-2_build_and_release.yml @@ -0,0 +1,504 @@ +name: Unified Release Pipeline - Build, Prerelease, Validate, and Publish + +on: + pull_request: + types: [closed] + paths: + - 'alpha/**' + - 'beta/**' + - 'stable/**' + workflow_dispatch: + inputs: + release_type: + description: 'Release type to build' + required: true + type: choice + options: + - alpha + - beta + - stable + default: 'beta' + +permissions: + contents: write + actions: write + id-token: write + +jobs: + determine-release-type: + name: Determine Release Type + runs-on: ubuntu-latest + outputs: + release_type: ${{ steps.detect-type.outputs.release_type }} + should_run: ${{ steps.detect-type.outputs.should_run }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect release type + id: detect-type + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "release_type=${{ github.event.inputs.release_type }}" >> $GITHUB_OUTPUT + echo "should_run=true" >> $GITHUB_OUTPUT + echo "::notice::Manual dispatch for ${{ github.event.inputs.release_type }} release" + elif [[ "${{ github.event_name }}" == "pull_request" ]]; then + if [[ "${{ github.event.pull_request.merged }}" != "true" ]]; then + echo "should_run=false" >> $GITHUB_OUTPUT + echo "::notice::PR was not merged, skipping build" + exit 0 + fi + + # Check which directories were changed in the merged PR + CHANGED_FILES=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.number }}/files --jq '.[].filename') + + ALPHA_CHANGES=$(echo "$CHANGED_FILES" | grep -c "^alpha/" || true) + BETA_CHANGES=$(echo "$CHANGED_FILES" | grep -c "^beta/" || true) + STABLE_CHANGES=$(echo "$CHANGED_FILES" | grep -c "^stable/" || true) + + echo "Alpha files changed: $ALPHA_CHANGES" + echo "Beta files changed: $BETA_CHANGES" + echo "Stable files changed: $STABLE_CHANGES" + + TOTAL_CHANGES=$((ALPHA_CHANGES + BETA_CHANGES + STABLE_CHANGES)) + + if [[ $TOTAL_CHANGES -eq 0 ]]; then + echo "should_run=false" >> $GITHUB_OUTPUT + echo "::notice::No relevant changes detected, skipping build" + elif [[ $TOTAL_CHANGES -gt 1 ]] && [[ $ALPHA_CHANGES -gt 0 || $BETA_CHANGES -gt 0 || $STABLE_CHANGES -gt 0 ]]; then + # Check if more than one release type has changes + CHANGE_COUNT=0 + [[ $ALPHA_CHANGES -gt 0 ]] && CHANGE_COUNT=$((CHANGE_COUNT + 1)) + [[ $BETA_CHANGES -gt 0 ]] && CHANGE_COUNT=$((CHANGE_COUNT + 1)) + [[ $STABLE_CHANGES -gt 0 ]] && CHANGE_COUNT=$((CHANGE_COUNT + 1)) + + if [[ $CHANGE_COUNT -gt 1 ]]; then + echo "::error::PR contains changes to multiple release type directories. This should not happen." + echo "should_run=false" >> $GITHUB_OUTPUT + exit 1 + fi + fi + + if [[ $ALPHA_CHANGES -gt 0 ]]; then + echo "release_type=alpha" >> $GITHUB_OUTPUT + echo "should_run=true" >> $GITHUB_OUTPUT + echo "::notice::Detected alpha release based on changed files" + elif [[ $BETA_CHANGES -gt 0 ]]; then + echo "release_type=beta" >> $GITHUB_OUTPUT + echo "should_run=true" >> $GITHUB_OUTPUT + echo "::notice::Detected beta release based on changed files" + elif [[ $STABLE_CHANGES -gt 0 ]]; then + echo "release_type=stable" >> $GITHUB_OUTPUT + echo "should_run=true" >> $GITHUB_OUTPUT + echo "::notice::Detected stable release based on changed files" + fi + else + echo "should_run=false" >> $GITHUB_OUTPUT + echo "::error::Unexpected event type: ${{ github.event_name }}" + fi + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + check-changes: + name: Check Changes + needs: determine-release-type + if: needs.determine-release-type.outputs.should_run == 'true' + runs-on: ubuntu-latest + outputs: + changes_only: ${{ steps.verify_changes.outputs.changes_only }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Log event context for debugging + if: ${{ github.event_name == 'pull_request' }} + run: | + echo "Event: ${{ github.event_name }}" + echo "Merged: ${{ github.event.pull_request.merged }}" + + - name: Verify ${{ needs.determine-release-type.outputs.release_type }}-only changes + id: verify_changes + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + echo "Workflow dispatch detected, setting changes_only=true" + echo "changes_only=true" >> $GITHUB_OUTPUT + else + changes_only=true + changed_files=$(git diff --name-only HEAD^ HEAD) + for file in $changed_files; do + if [[ ! $file == ${{ needs.determine-release-type.outputs.release_type }}/* ]]; then + echo "Non-${{ needs.determine-release-type.outputs.release_type }} changes detected in $file. Setting changes_only=false" + changes_only=false + fi + done + echo "changes_only=$changes_only" >> $GITHUB_OUTPUT + fi + + # For alpha/beta releases, generate version using date-based approach + generate_version: + needs: [determine-release-type, check-changes] + if: ${{ needs.check-changes.outputs.changes_only == 'true' && needs.determine-release-type.outputs.release_type != 'stable' }} + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version_output.outputs.version }} + npm_version: ${{ steps.version_output.outputs.npm_version }} + steps: + - uses: actions/checkout@v4 + + - name: Get base version + uses: reecetech/version-increment@2023.10.1 + id: release_version + with: + scheme: semver + increment: patch + + - name: Generate version + id: version_output + run: | + BASE_VERSION=${{ steps.release_version.outputs.version }} + BASE_VERSION_CLEAN=$(echo "$BASE_VERSION" | sed -E 's/-[a-z0-9.]+//') + DATE=$(date +%Y%m%d) + VERSION="${BASE_VERSION_CLEAN}~${{ needs.determine-release-type.outputs.release_type }}.${DATE}" + NPM_VERSION="${BASE_VERSION_CLEAN}-${{ needs.determine-release-type.outputs.release_type }}.${DATE}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "npm_version=$NPM_VERSION" >> $GITHUB_OUTPUT + echo "::notice::📦 ${{ needs.determine-release-type.outputs.release_type }} version: $VERSION" + + # For stable releases, generate version using semver increment + generate_stable_version: + needs: [determine-release-type, check-changes] + if: ${{ needs.check-changes.outputs.changes_only == 'true' && needs.determine-release-type.outputs.release_type == 'stable' }} + runs-on: ubuntu-latest + outputs: + version: ${{ steps.release_version.outputs.version }} + npm_version: ${{ steps.release_version.outputs.version }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get next stable version + uses: reecetech/version-increment@2023.10.1 + id: release_version + with: + scheme: semver + increment: patch + + build_packages: + name: Build Packages for (${{ matrix.name }}) v${{ needs.generate_version.outputs.version || needs.generate_stable_version.outputs.version }} + needs: [determine-release-type, generate_version, generate_stable_version] + if: always() && (needs.generate_version.result == 'success' || needs.generate_stable_version.result == 'success') + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + name: [ + debian-x86_64, + debian-arm32v6, + debian-arm64v8, + ] + include: + - name: debian-x86_64 + os: ubuntu-latest + BASE_IMAGE: library/debian:bullseye + QEMU_ARCH: x86_64 + + - name: debian-arm32v6 + os: ubuntu-24.04-arm + BASE_IMAGE: balenalib/raspberrypi3-debian:bullseye + QEMU_ARCH: arm + + - name: debian-arm64v8 + os: ubuntu-24.04-arm + BASE_IMAGE: arm64v8/debian:bullseye + QEMU_ARCH: aarch64 + + steps: + - uses: actions/checkout@v4 + + - name: Setup build environment X64 + if: runner.os == 'Linux' && runner.arch == 'X64' + run: | + sudo apt-get update + sudo apt-get --yes --no-install-recommends install binfmt-support qemu-user-static + docker run --rm --privileged multiarch/qemu-user-static:register --reset + continue-on-error: false + + - name: Setup build environment ARM64 + if: runner.os == 'Linux' && runner.arch == 'ARM64' + run: | + sudo apt-get update + sudo apt-get --yes --no-install-recommends install binfmt-support + continue-on-error: false + + - name: Build Docker image + run: | + docker build -f build/Dockerfile \ + --build-arg BASE_IMAGE=${{ matrix.BASE_IMAGE }} \ + --build-arg QEMU_ARCH=${{ matrix.QEMU_ARCH }} \ + -t ${{ needs.determine-release-type.outputs.release_type }}-package-build \ + --platform=linux/${{ matrix.QEMU_ARCH }} \ + --cache-from=${{ needs.determine-release-type.outputs.release_type }}-package-build:latest \ + --build-arg BUILDKIT_INLINE_CACHE=1 . + continue-on-error: false + + - name: Build package + run: | + docker run --rm -v $(pwd):/repo \ + -e PKG_RELEASE_TYPE="${{ needs.determine-release-type.outputs.release_type }}" \ + -e PKG_RELEASE_VERSION="${{ needs.generate_version.outputs.version || needs.generate_stable_version.outputs.version }}" \ + ${{ needs.determine-release-type.outputs.release_type }}-package-build + continue-on-error: false + + - name: Rename package to include v + run: | + DEB_FILE=$(ls homebridge*.deb 2>/dev/null || echo "") + if [ -z "$DEB_FILE" ]; then + echo "No .deb file found. Exiting." + exit 1 + fi + UPDATED=$(echo "$DEB_FILE" | sed -e 's/homebridge_/homebridge_v/g') + mv "$DEB_FILE" "$UPDATED" + + - name: Rename manifest to include v + run: | + MANIFEST_FILE=$(ls homebridge*.manifest 2>/dev/null || echo "") + if [ -z "$MANIFEST_FILE" ]; then + echo "No .manifest file found. Exiting." + exit 1 + fi + UPDATED=$(echo "$MANIFEST_FILE" | sed -e 's/homebridge_/homebridge_v/g') + mv "$MANIFEST_FILE" "$UPDATED" + + - uses: actions/upload-artifact@v4 + with: + name: artifacts-${{ matrix.name }} + retention-days: 7 + path: | + *.deb + *.manifest + + # Step 1: Create prerelease for all release types + create_prerelease: + name: Create GitHub Prerelease v${{ needs.generate_version.outputs.npm_version || needs.generate_stable_version.outputs.npm_version }} + needs: [determine-release-type, generate_version, generate_stable_version, build_packages] + if: always() && (needs.generate_version.result == 'success' || needs.generate_stable_version.result == 'success') && needs.build_packages.result == 'success' + runs-on: ubuntu-latest + outputs: + release_tag: ${{ steps.create_release.outputs.release_tag }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + pattern: artifacts-* + merge-multiple: true + + - name: Display structure of downloaded files + run: ls -R + + - name: Get the date + id: date + run: | + echo "builddate=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT + + - name: Create Release Body + id: release_body + run: | + echo "BODY_FILE=$(ls *.manifest | head -n 1)" >> $GITHUB_OUTPUT + + - name: Determine release details + id: release_details + run: | + RELEASE_TYPE="${{ needs.determine-release-type.outputs.release_type }}" + if [[ "$RELEASE_TYPE" == "stable" ]]; then + VERSION="${{ needs.generate_stable_version.outputs.version }}" + NPM_VERSION="${{ needs.generate_stable_version.outputs.npm_version }}" + TITLE="v$VERSION - ${{ steps.date.outputs.builddate }}" + else + VERSION="${{ needs.generate_version.outputs.version }}" + NPM_VERSION="${{ needs.generate_version.outputs.npm_version }}" + TITLE_PREFIX=$(echo "$RELEASE_TYPE" | tr '[:lower:]' '[:upper:]') + TITLE="$TITLE_PREFIX: Homebridge APT Pkg Release v$NPM_VERSION" + fi + + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "npm_version=$NPM_VERSION" >> $GITHUB_OUTPUT + echo "release_title=$TITLE" >> $GITHUB_OUTPUT + echo "tag_name=v$NPM_VERSION" >> $GITHUB_OUTPUT + + - name: Create GitHub Prerelease + id: create_release + uses: docker://ghcr.io/mini-bomba/create-github-release:v1.2.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ steps.release_details.outputs.tag_name }} + prerelease: true + draft: false + name: ${{ steps.release_details.outputs.release_title }} + files: | + *.deb + *.manifest + body: ${{ steps.release_body.outputs.BODY_FILE }} + clear_attachments: true + fail_on_no_files: true + + - name: Set release tag output + run: | + echo "release_tag=${{ steps.release_details.outputs.tag_name }}" >> $GITHUB_OUTPUT + + - name: Update release body + uses: tubone24/update_release@v1.3.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG_NAME: ${{ steps.release_details.outputs.tag_name }} + with: + body_path: ${{ steps.release_body.outputs.BODY_FILE }} + + # Step 2: Validate prerelease artifacts + validate_prerelease: + name: Validate GitHub Prerelease Artifacts + needs: [create_prerelease] + uses: ./.github/workflows/reusable-validate-homebridge.yml + with: + validation_type: "prerelease" + release_tag: ${{ needs.create_prerelease.outputs.release_tag }} + prerelease: true + latest: false + + # Step 3: Promote prerelease to full release after validation passes + promote_to_release: + name: Promote to Full Release + needs: [validate_prerelease, create_prerelease, determine-release-type] + if: needs.validate_prerelease.result == 'success' + runs-on: ubuntu-latest + steps: + - name: Promote prerelease to full release + uses: actions/github-script@v7 + with: + script: | + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: '${{ needs.create_prerelease.outputs.release_tag }}' + }); + + await github.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: release.id, + prerelease: false + }); + + console.log('Successfully promoted prerelease to full release: ${{ needs.create_prerelease.outputs.release_tag }}'); + + # Step 4: Publish to APT repository + publish_apt: + name: Publish to APT Repository + needs: [promote_to_release, create_prerelease, determine-release-type, generate_version, generate_stable_version] + if: needs.promote_to_release.result == 'success' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Download from GitHub Release + uses: robinraju/release-downloader@v1.11 + with: + tag: ${{ needs.create_prerelease.outputs.release_tag }} + fileName: 'homebridge*.deb' + out-file-path: 'repo/' + + - name: Display structure of downloaded files + run: ls -R + + - name: Import GPG Key + uses: crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + + - name: Install deb-s3 + run: | + curl -sLO https://github.com/deb-s3/deb-s3/releases/download/0.11.8/deb-s3-0.11.8.gem + sudo gem install deb-s3-0.11.8.gem + + - name: Upload to APT Repository + run: | + sudo chown -R $USER: repo/ + deb-s3 upload \ + --codename=${{ needs.determine-release-type.outputs.release_type == 'stable' && 'stable' || needs.determine-release-type.outputs.release_type }} \ + --suite=${{ needs.determine-release-type.outputs.release_type == 'stable' && 'stable' || needs.determine-release-type.outputs.release_type }} \ + --preserve-versions \ + --s3-region=us-west-2 \ + --bucket repo.homebridge.io \ + --access-key-id=${{ secrets.AWS_ACCESS_KEY_ID }} \ + --secret-access-key=${{ secrets.AWS_SECRET_ACCESS_KEY }} \ + --sign=${{ secrets.GPG_KEY_ID }} \ + repo/homebridge_v*.deb repo/*.deb + + # Step 5: Validate APT installation after publishing + validate_apt: + name: Validate APT Installation + needs: [publish_apt, determine-release-type] + if: needs.publish_apt.result == 'success' + uses: ./.github/workflows/reusable-validate-homebridge.yml + with: + validation_type: "apt" + release_channel: ${{ needs.determine-release-type.outputs.release_type == 'stable' && 'stable' || needs.determine-release-type.outputs.release_type }} + + # Step 6: Purge CloudFlare cache + purge_cloudflare_cache: + name: Purge Cloudflare Cache + needs: [publish_apt] + if: needs.publish_apt.result == 'success' + uses: ./.github/workflows/stage-3_5_purge_cloudflare_cache.yml + secrets: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }} + + # Step 7: Publish to NPM + publish_to_npm: + name: Publish to NPM + needs: [validate_apt, create_prerelease, determine-release-type, generate_version, generate_stable_version] + if: needs.validate_apt.result == 'success' + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: latest + registry-url: 'https://registry.npmjs.org' + + - name: Set package.json version + run: | + NPM_VERSION="${{ needs.generate_version.outputs.npm_version || needs.generate_stable_version.outputs.npm_version }}" + echo "Setting version to $NPM_VERSION" + jq ".version = \"$NPM_VERSION\"" package.json > tmp.$$.json && mv tmp.$$.json package.json + cat package.json + + - name: Publish to npm + run: npm publish --access public --tag ${{ needs.determine-release-type.outputs.release_type == 'stable' && 'latest' || needs.determine-release-type.outputs.release_type }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Output Success Notice + run: echo "::notice::Published @homebridge/homebridge-apt-pkg as version ${{ needs.generate_version.outputs.npm_version || needs.generate_stable_version.outputs.npm_version }} with ${{ needs.determine-release-type.outputs.release_type == 'stable' && 'latest' || needs.determine-release-type.outputs.release_type }} tag" + + # Step 8: Notify Discord (for stable releases only) + notify_discord: + name: Notify Discord + needs: [publish_to_npm, create_prerelease, determine-release-type] + if: needs.publish_to_npm.result == 'success' && needs.determine-release-type.outputs.release_type == 'stable' + uses: homebridge/.github/.github/workflows/discord-webhooks.yml@latest + with: + title: "Homebridge APT Package Released" + description: "Version `${{ needs.create_prerelease.outputs.release_tag }}` has been released." + url: "https://github.com/homebridge/homebridge-apt-pkg/releases/tag/${{ needs.create_prerelease.outputs.release_tag }}" + secrets: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL_LATEST }} diff --git a/.github/workflows/reusable-validate-homebridge.yml b/.github/workflows/reusable-validate-homebridge.yml new file mode 100644 index 0000000..2576bf3 --- /dev/null +++ b/.github/workflows/reusable-validate-homebridge.yml @@ -0,0 +1,115 @@ +name: "Reusable - Validate Homebridge Package" + +on: + workflow_call: + inputs: + validation_type: + description: "Type of validation to perform" + required: true + type: string + # Options: "prerelease" (validate GitHub release), "apt" (validate APT installation) + release_tag: + description: "Release tag for GitHub release validation" + required: false + type: string + release_channel: + description: "APT release channel for APT validation" + required: false + type: string + # Options: "stable", "beta", "alpha" + prerelease: + description: "Whether to download from prerelease" + required: false + type: boolean + default: false + latest: + description: "Whether to download latest release" + required: false + type: boolean + default: false + +jobs: + validate_github_release: + name: Validate GitHub Release Package + runs-on: ubuntu-latest + if: ${{ inputs.validation_type == 'prerelease' }} + steps: + - name: Download prerelease assets + id: download_assets + uses: robinraju/release-downloader@v1.11 + with: + tag: ${{ inputs.release_tag }} + preRelease: ${{ inputs.prerelease }} + latest: ${{ inputs.latest }} + fileName: 'homebridge*.deb' + + - name: List the downloaded files + run: ls -l + + - name: Check the number of downloaded files + id: check_file_count + run: | + FILE_COUNT=$(ls -1 | wc -l) + echo "Number of files downloaded: $FILE_COUNT" + if [ "$FILE_COUNT" -ne 3 ]; then + echo "::error::Expected 3 files, but found $FILE_COUNT" + exit 1 + fi + + - name: Download and install AMD64 package + uses: robinraju/release-downloader@v1.11 + with: + tag: ${{ inputs.release_tag }} + preRelease: ${{ inputs.prerelease }} + latest: ${{ inputs.latest }} + fileName: 'homebridge_*_amd64.deb' + + - name: List downloaded files + run: | + echo "Downloaded files:" + ls -l + + - name: Install the downloaded package + run: | + sudo dpkg -i homebridge_*_amd64.deb || sudo apt-get install -f -y + sleep 30 + + - name: Validate Homebridge Service + run: | + sudo hb-service view + sudo hb-service status + + validate_apt_installation: + name: Validate APT Installation on ${{ matrix.github-action-runner }} + runs-on: ${{ matrix.github-action-runner }} + if: ${{ inputs.validation_type == 'apt' }} + strategy: + fail-fast: false + matrix: + github-action-runner: [ubuntu-latest, ubuntu-24.04-arm] + steps: + - name: Install Homebridge from APT + run: | + curl -sSfL https://repo.homebridge.io/KEY.gpg | sudo gpg --dearmor -o /usr/share/keyrings/homebridge.gpg + echo "deb [signed-by=/usr/share/keyrings/homebridge.gpg] https://repo.homebridge.io ${{ inputs.release_channel }} main" | \ + sudo tee /etc/apt/sources.list.d/homebridge.list > /dev/null + sudo apt-get update + sudo apt-get install -y homebridge + + - name: Display Installed Version + id: installed-version + run: | + dpkg -l homebridge + echo "homebridge-version=\"$(dpkg -l homebridge | tail -1 | awk '{ print $3 }')\"" >> "$GITHUB_OUTPUT" + + - name: Validate Homebridge + run: | + sudo hb-service status + sudo hb-service view + + - name: List Available Versions + run: | + apt-cache madison homebridge + + - name: Publish Notice + run: echo "::notice::APT Package Installed from ${{ inputs.release_channel }} - Version ${{ steps.installed-version.outputs.homebridge-version }} - ${{ matrix.github-action-runner }}" \ No newline at end of file diff --git a/.github/workflows/stage-1_create_a_release_and_store.yml b/.github/workflows/stage-1_create_a_release_and_store.yml deleted file mode 100644 index 4a3e21d..0000000 --- a/.github/workflows/stage-1_create_a_release_and_store.yml +++ /dev/null @@ -1,250 +0,0 @@ -name: "Stage 1 - Create a pre-release and build APT package" - -on: - workflow_dispatch: - inputs: - increment: - description: 'Release Level' - required: true - default: 'patch' - type: choice - options: - - major - - minor - - patch - push: - branches: - - latest - paths: - - stable/32bit/package.json - - stable/64bit/package.json - -jobs: - merge_inputs: - # allows for both the workflow_dispatch and push events to trigger this job - runs-on: ubuntu-latest - outputs: - username: ${{ steps.merge_username.outputs.username }} - increment: ${{ steps.merge_increment.outputs.increment }} - steps: - - name: Merge Username - id: merge_username - run: | - if [ "${{ github.event.commits[0].author.username }}" ]; then - USERNAME=${{ github.event.commits[0].author.username }} - else - USERNAME='dependabot[bot]' - fi - echo "Using USERNAME: $USERNAME" - echo "username=$USERNAME" >> "$GITHUB_OUTPUT" - - - name: Merge Username - id: merge_increment - run: | - if [ "${{ inputs.increment }}" ]; then - INCREMENT=${{ inputs.increment }} - else - INCREMENT=patch - fi - echo "Using INCREMENT: $INCREMENT" - echo "increment=$INCREMENT" >> "$GITHUB_OUTPUT" - - create_draft_prerelease: - # Only run this job if the username is dependabot[bot] - needs: merge_inputs - if: ${{ needs.merge_inputs.outputs.username == 'dependabot[bot]' }} - permissions: - # write permission is required to create a github release - contents: write - # write permission is required for autolabeler - # otherwise, read permission is required at least - pull-requests: write - runs-on: ubuntu-latest - outputs: - version: ${{ steps.release_version.outputs.version }} # Without a V - v-version: ${{ steps.release_version.outputs.v-version }} # With a V - release_type: 'stable' - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Get next version - uses: reecetech/version-increment@2023.10.1 - id: release_version - with: - scheme: semver - increment: ${{ needs.merge_inputs.outputs.increment }} - - message_about_not_running: - # Only run this job if the username is dependabot[bot] - needs: merge_inputs - if: ${{ needs.merge_inputs.outputs.username != 'dependabot[bot]' }} - permissions: - # write permission is required to create a github release - contents: write - # write permission is required for autolabeler - # otherwise, read permission is required at least - pull-requests: write - runs-on: ubuntu-latest - steps: - - name: Display Message - run: echo "::error::This job is not running automatically because the invoker is not 'dependabot[bot]'." && exit 1 - - build_package_and_store: - needs: [create_draft_prerelease] - name: Build Packages for (${{ matrix.name }}) v${{ needs.create_draft_prerelease.outputs.version }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - name: [ - debian-x86_64, - debian-arm32v6, - debian-arm64v8, - ] - include: - - name: debian-x86_64 - os: ubuntu-latest - BASE_IMAGE: library/debian:bullseye - QEMU_ARCH: x86_64 - - - name: debian-arm32v6 - os: ubuntu-latest - BASE_IMAGE: balenalib/raspberrypi3-debian:bullseye - QEMU_ARCH: arm - - - name: debian-arm64v8 - os: ubuntu-latest - BASE_IMAGE: arm64v8/debian:bullseye - QEMU_ARCH: aarch64 - - steps: - - uses: actions/checkout@v4 - - - name: Linux - Setup Dependencies - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get --yes --no-install-recommends install binfmt-support qemu-user-static - docker run --rm --privileged multiarch/qemu-user-static:register --reset - - - name: Linux - Build Docker Image - if: runner.os == 'Linux' - run: | - docker build -f build/Dockerfile --build-arg BASE_IMAGE=${{ matrix.BASE_IMAGE }} --build-arg QEMU_ARCH=${{ matrix.QEMU_ARCH }} -t package-build --platform=linux/${{ matrix.QEMU_ARCH }} . - - - name: Linux - Build Package - if: runner.os == 'Linux' - run: | - docker run --rm -v $(pwd):/repo -e PKG_RELEASE_TYPE="${{ needs.create_draft_prerelease.outputs.release_type }}" -e PKG_RELEASE_VERSION="${{ needs.create_draft_prerelease.outputs.version }}" package-build - - - name: Rename package to include v - run: | - UPDATED=$(ls homebridge*.deb | sed -e 's/homebridge_/homebridge_v/g') - mv homebridge_*.deb ${UPDATED} - - - name: Rename manifest to include v - run: | - UPDATED=$(ls homebridge*.manifest | sed -e 's/homebridge_/homebridge_v/g') - mv homebridge_*.manifest ${UPDATED} - - - uses: actions/upload-artifact@v4 - with: - name: artifacts-${{ matrix.name }} - retention-days: 7 - path: | - *.deb - *.manifest - - publish_prerelease: - # Publish the pre-release to the GitHub Releases page - needs: [build_package_and_store, create_draft_prerelease] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/download-artifact@v4 - with: - pattern: artifacts-* - merge-multiple: true - - - name: Display structure of downloaded files - run: ls -R - - - name: Get the date - id: date - run: | - echo "builddate=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT - - - name: Create Release Body - id: release_body - run: | - echo "BODY_FILE=$(ls *.manifest | head -n 1)" >> $GITHUB_OUTPUT - - - name: Dump Steps context - env: - STEPS_CONTEXT: ${{ toJson(steps) }} - run: echo "$STEPS_CONTEXT" - - - name: Create Pre-Release - uses: docker://ghcr.io/mini-bomba/create-github-release:v1.2.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - tag: ${{ needs.create_draft_prerelease.outputs.v-version }} - prerelease: true - draft: false - name: "${{ needs.create_draft_prerelease.outputs.v-version }} - ${{ steps.date.outputs.builddate }}" - files: | - *.deb - *.manifest - body: ${{ steps.release_body.outputs.BODY_FILE }} - clear_attachments: true - fail_on_no_files: true - - - name: update release - uses: tubone24/update_release@v1.3.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG_NAME: ${{ needs.create_draft_prerelease.outputs.v-version }} - with: - body_path: ${{ steps.release_body.outputs.BODY_FILE }} - - print_context: - runs-on: ubuntu-latest - steps: - - name: Dump Inputs context - env: - INPUTS_CONTEXT: ${{ toJson(inputs) }} - run: echo "$INPUTS_CONTEXT" - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Dump job context - env: - JOB_CONTEXT: ${{ toJson(job) }} - run: echo "$JOB_CONTEXT" - - name: Dump steps context - env: - STEPS_CONTEXT: ${{ toJson(steps) }} - run: echo "$STEPS_CONTEXT" - - name: Dump runner context - env: - RUNNER_CONTEXT: ${{ toJson(runner) }} - run: echo "$RUNNER_CONTEXT" - - name: Dump strategy context - env: - STRATEGY_CONTEXT: ${{ toJson(strategy) }} - run: echo "$STRATEGY_CONTEXT" - - name: Dump matrix context - env: - MATRIX_CONTEXT: ${{ toJson(matrix) }} - run: echo "$MATRIX_CONTEXT" - - - name: Show default environment variables - run: | - echo "The job_id is: $GITHUB_JOB" # reference the default environment variables - echo "The id of this action is: $GITHUB_ACTION" # reference the default environment variables - echo "The run id is: $GITHUB_RUN_ID" - echo "The GitHub Actor's username is: $GITHUB_ACTOR" - echo "GitHub SHA: $GITHUB_SHA" diff --git a/.github/workflows/stage-2_pre-release_validation.yml b/.github/workflows/stage-2_pre-release_validation.yml deleted file mode 100644 index 2e17a0f..0000000 --- a/.github/workflows/stage-2_pre-release_validation.yml +++ /dev/null @@ -1,146 +0,0 @@ -name: "Stage 2 - Pre-Release Validation Workflow" - -on: - workflow_dispatch: - inputs: - release_tag: - description: "The tag of the release to validate." - required: true - type: string - release: - types: [prereleased] - # The workflow run trigger is a workaround due to mini-bomba/create-github-release@v1.2.0 in Stage 1 - not triggering the release event - workflow_run: - workflows: ["Stage 1 - Create a pre-release and build APT package", - "Test - Stage 1 - Create a pre-release and build APT package"] - types: - - completed - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - determine_release_tag: - name: Determine Release Tag - runs-on: ubuntu-latest - outputs: - release_tag: ${{ steps.set_release_tag.outputs.release_tag }} - preRelease: ${{ steps.set_release_tag.outputs.preRelease }} - latest: ${{ steps.set_release_tag.outputs.latest }} - steps: - - name: Set Release Tag - id: set_release_tag - run: | - if [ "${{ github.event_name }}" == "release" ]; then - echo "release_tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT" - echo "preRelease=false" >> "$GITHUB_OUTPUT" - echo "latest=false" >> "$GITHUB_OUTPUT" - elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - echo "release_tag=${{ inputs.release_tag }}" >> "$GITHUB_OUTPUT" - echo "preRelease=false" >> "$GITHUB_OUTPUT" - echo "latest=false" >> "$GITHUB_OUTPUT" - elif [ "${{ github.event_name }}" == "workflow_run" ] && [ "${{ github.event.workflow_run.conclusion }}" == "success" ]; then - echo "release_tag=''" >> "$GITHUB_OUTPUT" - echo "preRelease=true" >> "$GITHUB_OUTPUT" - echo "latest=true" >> "$GITHUB_OUTPUT" - else - echo "::error::Unexpected event: ${{ github.event_name }}" - exit 1 - fi - - count_prerelease_assets: - name: Download Latest Pre-Release Assets - runs-on: ubuntu-latest - needs: determine_release_tag - steps: - - name: Download prerelease assets - id: download_assets - uses: robinraju/release-downloader@v1.11 - with: - tag: ${{ needs.determine_release_tag.outputs.release_tag }} - preRelease: ${{ needs.determine_release_tag.outputs.preRelease }} - latest: ${{ needs.determine_release_tag.outputs.latest }} - fileName: 'homebridge*.deb' - - - name: List the downloaded files - run: ls -l - - - name: Check the number of downloaded files - id: check_file_count - run: | - FILE_COUNT=$(ls -1 | wc -l) - echo "Number of files downloaded: $FILE_COUNT" - if [ "$FILE_COUNT" -ne 3 ]; then - echo "::error::Expected 3 files, but found $FILE_COUNT" - exit 1 - fi - - validate_prerelease_x86_package: - needs: [determine_release_tag, count_prerelease_assets] - name: Download, Install, and Validate Homebridge Package - runs-on: ubuntu-latest - steps: - - name: Download release asset - id: download_release - uses: robinraju/release-downloader@v1.11 - with: - tag: ${{ needs.determine_release_tag.outputs.release_tag }} - preRelease: ${{ needs.determine_release_tag.outputs.preRelease }} - latest: ${{ needs.determine_release_tag.outputs.latest }} - fileName: 'homebridge_*_amd64.deb' - - - name: List downloaded files - run: | - echo "Downloaded files:" - ls -l - - - name: Install the downloaded package - run: | - sudo dpkg -i homebridge_*_amd64.deb || sudo apt-get install -f -y - sleep 30 - - - name: Validate Homebridge Service - run: | - sudo hb-service view - sudo hb-service status - - print_context: - runs-on: ubuntu-latest - steps: - - name: Dump Inputs context - env: - INPUTS_CONTEXT: ${{ toJson(inputs) }} - run: echo "$INPUTS_CONTEXT" - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Dump job context - env: - JOB_CONTEXT: ${{ toJson(job) }} - run: echo "$JOB_CONTEXT" - - name: Dump steps context - env: - STEPS_CONTEXT: ${{ toJson(steps) }} - run: echo "$STEPS_CONTEXT" - - name: Dump runner context - env: - RUNNER_CONTEXT: ${{ toJson(runner) }} - run: echo "$RUNNER_CONTEXT" - - name: Dump strategy context - env: - STRATEGY_CONTEXT: ${{ toJson(strategy) }} - run: echo "$STRATEGY_CONTEXT" - - name: Dump matrix context - env: - MATRIX_CONTEXT: ${{ toJson(matrix) }} - run: echo "$MATRIX_CONTEXT" - - - name: Show default environment variables - run: | - echo "The job_id is: $GITHUB_JOB" - echo "The id of this action is: $GITHUB_ACTION" - echo "The run id is: $GITHUB_RUN_ID" - echo "The GitHub Actor's username is: $GITHUB_ACTOR" - echo "GitHub SHA: $GITHUB_SHA" diff --git a/.github/workflows/stage-3_promote_release_to_apt.yml b/.github/workflows/stage-3_promote_release_to_apt.yml deleted file mode 100644 index 057eaae..0000000 --- a/.github/workflows/stage-3_promote_release_to_apt.yml +++ /dev/null @@ -1,142 +0,0 @@ -name: "Stage 3 - Promote Release Package to APT Stores" - -on: - release: - types: [published, released] - workflow_dispatch: - inputs: - release_tag: - description: "The tag of the release to Package to APT Stores." - required: true - type: string - -jobs: - determine_release_tag: - name: Determine Release Tag - runs-on: ubuntu-latest - outputs: - release_tag: ${{ steps.set_release_tag.outputs.release_tag }} - steps: - - name: Set Release Tag - id: set_release_tag - run: | - if [ "${{ github.event_name }}" == "release" ]; then - echo "release_tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT" - else - echo "release_tag=${{ inputs.release_tag }}" >> "$GITHUB_OUTPUT" - fi - - release_type: - name: Determine Release Type - runs-on: ubuntu-latest - needs: determine_release_tag - outputs: - release_type: ${{ steps.release_type.outputs.type }} - steps: - - name: Determine Release Type - id: release_type - run: | - if [ "${{ github.event.release.prerelease }}" == "true" ]; then - echo "type=test" >> "$GITHUB_OUTPUT" - else - echo "type=stable" >> "$GITHUB_OUTPUT" - fi - - update_apt_repo: - name: Update APT Repo - runs-on: ubuntu-latest - needs: [determine_release_tag, release_type] - steps: - - uses: actions/checkout@v4 - - - name: Download Release Assets - uses: robinraju/release-downloader@v1.11 - with: - tag: ${{ needs.determine_release_tag.outputs.release_tag }} - fileName: 'homebridge*.deb' - out-file-path: 'repo/' - - - name: Import GPG Key - id: import_gpg - uses: crazy-max/ghaction-import-gpg@v4 - with: - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - passphrase: ${{ secrets.GPG_PASSPHRASE }} - - - name: Install deb-s3 - run: | - curl -sLO https://github.com/deb-s3/deb-s3/releases/download/0.11.8/deb-s3-0.11.8.gem - sudo gem install deb-s3-0.11.8.gem - - - name: Upload Package to APT Repo - run: | - sudo chown -R $USER: repo/ - deb-s3 upload \ - --codename=${{ needs.release_type.outputs.release_type }} \ - --suite=stable \ - --preserve-versions \ - --s3-region=us-west-2 \ - --bucket repo.homebridge.io \ - --access-key-id=${{ secrets.AWS_ACCESS_KEY_ID }} \ - --secret-access-key=${{ secrets.AWS_SECRET_ACCESS_KEY }} \ - --sign=${{ secrets.GPG_KEY_ID }} \ - repo/*.deb - - purge_cloudflare_cache: - name: Purge Cloudflare Cache - needs: update_apt_repo - uses: ./.github/workflows/stage-3_5_purge_cloudflare_cache.yml - secrets: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }} - - notify_discord: - name: Notify Discord - needs: [update_apt_repo, purge_cloudflare_cache, determine_release_tag] - uses: homebridge/.github/.github/workflows/discord-webhooks.yml@latest - with: - title: "Homebridge APT Package Released" - description: "Version `${{ needs.determine_release_tag.outputs.release_tag }}` has been released." - url: "https://github.com/homebridge/homebridge-apt-pkg/releases/tag/${{ needs.determine_release_tag.outputs.release_tag }}" - secrets: - DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL_LATEST }} - - print_context: - runs-on: ubuntu-latest - steps: - - name: Dump Inputs context - env: - INPUTS_CONTEXT: ${{ toJson(inputs) }} - run: echo "$INPUTS_CONTEXT" - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - name: Dump job context - env: - JOB_CONTEXT: ${{ toJson(job) }} - run: echo "$JOB_CONTEXT" - - name: Dump steps context - env: - STEPS_CONTEXT: ${{ toJson(steps) }} - run: echo "$STEPS_CONTEXT" - - name: Dump runner context - env: - RUNNER_CONTEXT: ${{ toJson(runner) }} - run: echo "$RUNNER_CONTEXT" - - name: Dump strategy context - env: - STRATEGY_CONTEXT: ${{ toJson(strategy) }} - run: echo "$STRATEGY_CONTEXT" - - name: Dump matrix context - env: - MATRIX_CONTEXT: ${{ toJson(matrix) }} - run: echo "$MATRIX_CONTEXT" - - - name: Show default environment variables - run: | - echo "The job_id is: $GITHUB_JOB" # reference the default environment variables - echo "The id of this action is: $GITHUB_ACTION" # reference the default environment variables - echo "The run id is: $GITHUB_RUN_ID" - echo "The GitHub Actor's username is: $GITHUB_ACTOR" - echo "GitHub SHA: $GITHUB_SHA" \ No newline at end of file diff --git a/.github/workflows/stage-5_update_version_on_npm.yml b/.github/workflows/stage-5_update_version_on_npm.yml deleted file mode 100644 index 092f26a..0000000 --- a/.github/workflows/stage-5_update_version_on_npm.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Stage 5 - Update Version on npm -# This workflow updates the version of the stub package on npm when a new tag is pushed. -# It extracts the version from the tag and updates the package.json file accordingly -# before publishing the package to npm. -# It is triggered by the completion of the "Stage 3 - Promote Release Package to APT Stores" workflow. - -on: - workflow_run: - workflows: [Stage 3 - Promote Release Package to APT Stores] - types: - - completed - -jobs: - publish: - name: Publish ${{ github.event.workflow_run.head_branch }} - runs-on: ubuntu-latest - - steps: - - name: Checkout Repo - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: latest - registry-url: 'https://registry.npmjs.org' - - - name: Extract and validate version from tag - id: version - run: | - RAW_TAG="${{ github.event.workflow_run.head_branch }}" - if [[ "$RAW_TAG" =~ ^v([0-9]+\.[0-9]+\.[0-9]+.*)$ ]]; then - CLEAN_VERSION="${BASH_REMATCH[1]}" - echo "CLEAN_VERSION=$CLEAN_VERSION" >> "$GITHUB_ENV" - else - echo "::error::Invalid tag format: '$RAW_TAG'. Expected format 'v1.2.3'" - exit 1 - fi - - - name: Set package.json version - run: | - echo "Setting version to $CLEAN_VERSION" - jq ".version = \"$CLEAN_VERSION\"" package.json > tmp.$$.json && mv tmp.$$.json package.json - cat package.json - - - name: Publish to npm - run: npm publish --access public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Output Success Notice - run: echo "::notice::Published @homebridge/homebridge-apt-pkg as version $CLEAN_VERSION" diff --git a/BUILD.md b/BUILD.md index 259e715..d1d6177 100644 --- a/BUILD.md +++ b/BUILD.md @@ -65,13 +65,27 @@ For detailed usage instructions, see [`scripts/README.md`](scripts/README.md). ## Actions +The repository uses a consolidated set of GitHub Actions workflows that share common functionality through reusable workflows to reduce duplication and improve maintainability. + +### Workflow Architecture + +The workflow system consists of: +- **5 Reusable Workflows**: Common functionality shared across release streams +- **Stable Release Stream**: 4-stage workflow for production releases +- **Beta Release Stream**: 2-stage workflow for beta releases +- **Alpha Release Stream**: 2-stage workflow for alpha releases + +For detailed workflow documentation, see [`.github/workflows/README.md`](.github/workflows/README.md). + +### Stable Release Process + ### Stage 1 - Create a pre-release and build APT package >Average Execution time: Approx 40 minutes This job is triggered when any package.json file (stable/32bit, stable/64bit, or legacy root) is updated on the latest branch, and the author of the change is dependabot. 1. Determine release version based on either manual workflow input or the latest release. -2. Build apt packages for x86_64, Arm ( RPI 32 bit), and aarch64 ( RPI 64 bit ). +2. Build apt packages for x86_64, Arm ( RPI 32 bit), and aarch64 ( RPI 64 bit ) using the reusable build workflow. 3. Apt packages are stored as an artifact against the workflow. 4. Create a Pre-Release, and attach the artifacts. @@ -89,7 +103,7 @@ This job is triggered by the the publishing of a prerelease or the completion of This job is triggered by the changing of the Release status from `pre-release` to `released`. Changing the prerelease to release is a manual step. 1. Release assets are downloaded from the latest release -2. Assets are promoted to `repo.homebridge.io` +2. Assets are promoted to `repo.homebridge.io` using the reusable APT publishing workflow 3. Cloud flare cache is purged ### Stage 4 - Post Release Validation @@ -100,6 +114,15 @@ This job is triggered by the successful completion of step 3 1. Download the current homebridge-apt-pkg for x86 and install. 2. Check that homebridge starts +### Beta and Alpha Release Processes + +Both beta and alpha releases follow a similar 2-stage process: + +1. **Dependency Updates**: Automated bot updates to package.json files in beta/ or alpha/ directories +2. **Build and Release**: Automated build, APT publishing, GitHub release creation, and NPM publishing using consolidated reusable workflows + +These processes automatically generate date-stamped versions and publish to their respective channels (beta/alpha) in the APT repository and NPM registry. + ## Package Manifest Each release includes a Package Manifest file that contains: