Release #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================================== | |
| # RELEASE WORKFLOW โ Fully automated release pipeline | |
| # | |
| # Trigger: workflow_dispatch with version input | |
| # Steps: | |
| # 1. Pre-flight: Validate version, check CI status | |
| # 2. Version bump: pyproject.toml, package.json, frontend/package.json | |
| # 3. CHANGELOG.md: Move [Unreleased] โ [X.Y.Z] - YYYY-MM-DD | |
| # 4. Commit: "chore(release): vX.Y.Z" | |
| # 5. Git Tag: vX.Y.Z โ triggers ci-cd.yml โ Docker Multi-Arch Build + Push | |
| # 6. GitHub Release: Auto-generated changelog + Docker instructions | |
| # 7. Trigger: build-raspi-image.yml for arm64 + armhf | |
| # ============================================================================== | |
| name: Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Release version (e.g., 1.0.0 โ without v prefix)' | |
| required: true | |
| type: string | |
| prerelease: | |
| description: 'Mark as pre-release (beta/rc)' | |
| required: false | |
| type: boolean | |
| default: false | |
| skip_raspi: | |
| description: 'Skip Raspberry Pi image builds' | |
| required: false | |
| type: boolean | |
| default: false | |
| jobs: | |
| # ============================================================================ | |
| # PRE-FLIGHT CHECKS | |
| # ============================================================================ | |
| preflight: | |
| name: Pre-flight Checks | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Validate version format | |
| run: | | |
| VERSION="${{ inputs.version }}" | |
| if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then | |
| echo "::error::Invalid version format: $VERSION" | |
| echo "Expected: X.Y.Z or X.Y.Z-beta.1 (e.g., 1.0.0, 2.1.0-rc.1)" | |
| exit 1 | |
| fi | |
| echo "โ Valid version: $VERSION" | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check tag does not exist | |
| run: | | |
| TAG="v${{ inputs.version }}" | |
| if git rev-parse "$TAG" >/dev/null 2>&1; then | |
| echo "::error::Tag $TAG already exists! Delete first: git push --delete origin $TAG && git tag -d $TAG" | |
| exit 1 | |
| fi | |
| echo "โ Tag $TAG is available" | |
| - name: Check latest CI status on main | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| echo "๐ Checking CI/CD status on main..." | |
| LAST_RUN=$(gh run list --workflow=ci-cd.yml --branch=main --limit=1 \ | |
| --json conclusion --jq '.[0].conclusion' 2>/dev/null || echo "unknown") | |
| echo "Last CI run: $LAST_RUN" | |
| if [ "$LAST_RUN" = "failure" ]; then | |
| echo "::warning::Last CI/CD run on main failed. Proceeding anyway (manual trigger)." | |
| fi | |
| echo "โ Pre-flight checks passed" | |
| # ============================================================================ | |
| # VERSION BUMP + TAG + RELEASE | |
| # ============================================================================ | |
| release: | |
| name: Create Release | |
| runs-on: ubuntu-latest | |
| needs: preflight | |
| permissions: | |
| contents: write | |
| outputs: | |
| tag: ${{ steps.tag.outputs.tag }} | |
| version: ${{ steps.tag.outputs.version }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Configure Git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Bump version in all package files | |
| run: | | |
| VERSION="${{ inputs.version }}" | |
| echo "๐ฆ Bumping version to $VERSION..." | |
| # 1. Root package.json | |
| sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" package.json | |
| echo " โ package.json" | |
| # 2. Backend pyproject.toml (project.version AND tool.commitizen.version) | |
| sed -i "s/^version = \"[^\"]*\"/version = \"$VERSION\"/" apps/backend/pyproject.toml | |
| echo " โ apps/backend/pyproject.toml" | |
| # 3. Frontend package.json | |
| sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" apps/frontend/package.json | |
| echo " โ apps/frontend/package.json" | |
| # Verify | |
| echo "" | |
| echo "=== Version verification ===" | |
| grep '"version"' package.json | head -1 | |
| grep '^version' apps/backend/pyproject.toml | head -1 | |
| grep '"version"' apps/frontend/package.json | head -1 | |
| - name: Update CHANGELOG.md | |
| run: | | |
| VERSION="${{ inputs.version }}" | |
| DATE=$(date +%Y-%m-%d) | |
| echo "๐ Updating CHANGELOG.md: [Unreleased] โ [$VERSION] - $DATE" | |
| sed -i "s/## \[Unreleased\]/## [Unreleased]\n\n_No changes yet._\n\n---\n\n## [$VERSION] - $DATE/" CHANGELOG.md | |
| echo " โ CHANGELOG.md updated" | |
| - name: Commit version bump | |
| run: | | |
| VERSION="${{ inputs.version }}" | |
| git add package.json apps/backend/pyproject.toml apps/frontend/package.json CHANGELOG.md | |
| git commit -m "chore(release): v${VERSION} | |
| - Bump version to ${VERSION} in all package files | |
| - Update CHANGELOG.md with release date" | |
| echo "โ Version bump committed" | |
| - name: Create and push tag | |
| id: tag | |
| run: | | |
| VERSION="${{ inputs.version }}" | |
| TAG="v${VERSION}" | |
| git tag -a "$TAG" -m "Release $TAG | |
| OpenCloudTouch $TAG โ Local control for Bose SoundTouch devices. | |
| See CHANGELOG.md for details." | |
| git push origin main | |
| git push origin "$TAG" | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "โ Tag $TAG created and pushed" | |
| echo "๐ CI/CD Pipeline will build Docker images automatically" | |
| - name: Generate release notes | |
| id: notes | |
| run: | | |
| VERSION="${{ inputs.version }}" | |
| TAG="v${VERSION}" | |
| # Get previous tag | |
| PREV_TAG=$(git tag --sort=-v:refname | grep -v "$TAG" | head -1 || echo "") | |
| if [ -z "$PREV_TAG" ]; then | |
| RANGE="" | |
| COMPARE_TEXT="๐ **First release of OpenCloudTouch!**" | |
| else | |
| RANGE="${PREV_TAG}..${TAG}" | |
| COMPARE_TEXT="**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREV_TAG}...${TAG}" | |
| fi | |
| # Build release notes file | |
| cat > release-notes.md << EOF | |
| ## ๐ OpenCloudTouch v${VERSION} | |
| Local control for Boseยฎ SoundTouchยฎ devices โ after the cloud shutdown. | |
| EOF | |
| sed -i 's/^ //' release-notes.md | |
| # Parse conventional commits (only if we have a range) | |
| add_section() { | |
| local title="$1" | |
| local pattern="$2" | |
| local icon="$3" | |
| local content="" | |
| if [ -n "$RANGE" ]; then | |
| content=$(git log $RANGE --pretty=format:"- %s (%h)" --no-merges --grep="$pattern" 2>/dev/null || echo "") | |
| else | |
| content=$(git log --pretty=format:"- %s (%h)" --no-merges --grep="$pattern" 2>/dev/null || echo "") | |
| fi | |
| if [ -n "$content" ]; then | |
| echo "" >> release-notes.md | |
| echo "### ${icon} ${title}" >> release-notes.md | |
| echo "" >> release-notes.md | |
| echo "$content" >> release-notes.md | |
| fi | |
| } | |
| add_section "Features" "^feat" "โจ" | |
| add_section "Bug Fixes" "^fix" "๐" | |
| add_section "Performance" "^perf" "โก" | |
| add_section "Refactoring" "^refactor" "โป๏ธ" | |
| add_section "Tests" "^test" "๐งช" | |
| add_section "Documentation" "^docs" "๐" | |
| add_section "CI/CD" "^ci" "๐ง" | |
| add_section "Maintenance" "^chore\|^build" "๐ ๏ธ" | |
| add_section "Style" "^style" "๐จ" | |
| # Docker instructions | |
| cat >> release-notes.md << EOF | |
| --- | |
| ## ๐ฆ Installation | |
| ### Docker (recommended) | |
| \`\`\`bash | |
| # Pull the latest stable release | |
| docker pull ghcr.io/${{ github.repository }}:stable | |
| # Or pull this specific version | |
| docker pull ghcr.io/${{ github.repository }}:${VERSION} | |
| # Run the container | |
| docker run -d \\ | |
| --name opencloudtouch \\ | |
| --network host \\ | |
| -v opencloudtouch-data:/data \\ | |
| -e OCT_DISCOVERY_ENABLED=true \\ | |
| ghcr.io/${{ github.repository }}:${VERSION} | |
| \`\`\` | |
| ### Docker Compose | |
| \`\`\`yaml | |
| services: | |
| opencloudtouch: | |
| image: ghcr.io/${{ github.repository }}:${VERSION} | |
| container_name: opencloudtouch | |
| restart: unless-stopped | |
| network_mode: host | |
| volumes: | |
| - oct-data:/data | |
| environment: | |
| - OCT_DISCOVERY_ENABLED=true | |
| - OCT_LOG_LEVEL=INFO | |
| volumes: | |
| oct-data: | |
| \`\`\` | |
| ### Raspberry Pi (SD-Card Image) | |
| Pre-built SD card images for Raspberry Pi 3/4/5 will be attached to this release shortly. | |
| 1. Download the \`.img.xz\` file for your architecture (arm64 or armhf) | |
| 2. Flash with [Raspberry Pi Imager](https://www.raspberrypi.com/software/) or: \`xz -d *.img.xz && sudo dd if=*.img of=/dev/sdX bs=4M status=progress\` | |
| 3. Boot โ OpenCloudTouch starts automatically on port 7777 | |
| 4. Default credentials: \`oct\` / \`opencloudtouch\` | |
| ### Available Docker Tags | |
| | Tag | Description | | |
| |-----|-------------| | |
| | \`stable\` | Latest stable release (recommended) | | |
| | \`${VERSION}\` | This specific version | | |
| | \`latest\` | Latest build from main branch | | |
| | \`$(echo $VERSION | cut -d. -f1-2)\` | Latest patch of this minor version | | |
| ### Supported Architectures | |
| | Architecture | Platform | Example Devices | | |
| |-------------|----------|-----------------| | |
| | \`amd64\` | x86_64 | Desktop, Server, NAS | | |
| | \`arm64\` | aarch64 | Raspberry Pi 4/5, Apple Silicon | | |
| | \`arm/v7\` | armhf | Raspberry Pi 2/3 | | |
| --- | |
| ## ๐ Resources | |
| - ๐ [Documentation (Wiki)](https://github.com/scheilch/opencloudtouch/wiki) | |
| - ๐ [Report Issues](https://github.com/scheilch/opencloudtouch/issues) | |
| - ๐ [Upgrade Guide](https://github.com/scheilch/opencloudtouch/blob/main/UPGRADING.md) | |
| - ๐ [Full Changelog](https://github.com/scheilch/opencloudtouch/blob/main/CHANGELOG.md) | |
| ${COMPARE_TEXT} | |
| EOF | |
| # Remove heredoc indentation | |
| sed -i 's/^ //' release-notes.md | |
| echo "=== Release Notes Preview ===" | |
| head -30 release-notes.md | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: v${{ inputs.version }} | |
| name: "๐ OpenCloudTouch v${{ inputs.version }}" | |
| body_path: release-notes.md | |
| prerelease: ${{ inputs.prerelease }} | |
| draft: false | |
| make_latest: ${{ !inputs.prerelease }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Step summary | |
| run: | | |
| VERSION="${{ inputs.version }}" | |
| cat >> $GITHUB_STEP_SUMMARY << EOF | |
| # ๐ Release v${VERSION} Created! | |
| | Step | Status | | |
| |------|--------| | |
| | Version bump | โ package.json, pyproject.toml, frontend/package.json | | |
| | CHANGELOG.md | โ Updated | | |
| | Git commit | โ Pushed to main | | |
| | Git tag | โ v${VERSION} | | |
| | GitHub Release | โ Published | | |
| ## ๐ Automated Downstream Pipelines | |
| 1. **CI/CD** โ Docker multi-arch build + push to GHCR | |
| [Watch](https://github.com/${{ github.repository }}/actions/workflows/ci-cd.yml) | |
| 2. **Raspberry Pi** โ SD card images (arm64 + armhf) | |
| [Watch](https://github.com/${{ github.repository }}/actions/workflows/build-raspi-image.yml) | |
| ## ๐ฆ After CI completes | |
| \`\`\`bash | |
| docker pull ghcr.io/${{ github.repository }}:stable | |
| docker pull ghcr.io/${{ github.repository }}:${VERSION} | |
| \`\`\` | |
| **Release:** https://github.com/${{ github.repository }}/releases/tag/v${VERSION} | |
| EOF | |
| # ============================================================================ | |
| # TRIGGER RASPBERRY PI IMAGE BUILDS | |
| # ============================================================================ | |
| trigger-raspi: | |
| name: Trigger Raspberry Pi Builds | |
| runs-on: ubuntu-latest | |
| needs: release | |
| if: ${{ !inputs.skip_raspi }} | |
| steps: | |
| - name: Trigger build | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.release.outputs.version }}" | |
| echo "๐ Triggering Raspberry Pi image builds for v${VERSION}..." | |
| gh workflow run build-raspi-image.yml \ | |
| --repo "${{ github.repository }}" \ | |
| --field oct_version="${VERSION}" \ | |
| --field architectures=all \ | |
| --field attach_to_release=true | |
| echo "โ Raspberry Pi build triggered" | |
| echo "๐ Images will be automatically attached to the release when ready" | |
| # ============================================================================ | |
| # VERIFY DOWNSTREAM WORKFLOWS | |
| # ============================================================================ | |
| verify: | |
| name: Verify Downstream | |
| runs-on: ubuntu-latest | |
| needs: [release, trigger-raspi] | |
| if: always() && needs.release.result == 'success' | |
| steps: | |
| - name: Wait and check | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| sleep 15 | |
| VERSION="${{ needs.release.outputs.version }}" | |
| TAG="v${VERSION}" | |
| echo "# ๐ Downstream Status" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # CI/CD | |
| CI_STATUS=$(gh run list --workflow=ci-cd.yml --limit=1 \ | |
| --repo "${{ github.repository }}" \ | |
| --json status --jq '.[0].status' 2>/dev/null || echo "unknown") | |
| echo "- **CI/CD Pipeline**: ${CI_STATUS}" >> $GITHUB_STEP_SUMMARY | |
| # RasPi | |
| if [ "${{ needs.trigger-raspi.result }}" = "success" ]; then | |
| RASPI_STATUS=$(gh run list --workflow=build-raspi-image.yml --limit=1 \ | |
| --repo "${{ github.repository }}" \ | |
| --json status --jq '.[0].status' 2>/dev/null || echo "unknown") | |
| echo "- **Raspberry Pi Builds**: ${RASPI_STATUS}" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "- **Raspberry Pi Builds**: โญ๏ธ Skipped" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "๐ [Release Page](https://github.com/${{ github.repository }}/releases/tag/${TAG})" >> $GITHUB_STEP_SUMMARY |