Skip to content

fix(ci): fix Docker sha tag prefix for tag pushes (was generating inv… #5

fix(ci): fix Docker sha tag prefix for tag pushes (was generating inv…

fix(ci): fix Docker sha tag prefix for tag pushes (was generating inv… #5

Workflow file for this run

# ==============================================================================

Check failure on line 1 in .github/workflows/release.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/release.yml

Invalid workflow file

You have an error in your yaml syntax
# RELEASE WORKFLOW — Tag + GitHub Release pipeline
#
# Trigger: workflow_dispatch with version input
# Steps:
# 1. Pre-flight: Validate version, check CI status, verify version in files
# 2. Git Tag: vX.Y.Z on HEAD → triggers ci-cd.yml → Docker Multi-Arch Build
# 3. GitHub Release: Auto-generated changelog + Docker instructions
# 4. Trigger: build-raspi-image.yml for arm64 + armhf
#
# NOTE: Version bumps in package.json / pyproject.toml / CHANGELOG.md must be
# done in the PR BEFORE merging to main. This workflow does NOT push
# commits — it only creates a tag + release on the current HEAD.
# This is required because branch protection blocks direct pushes to main.
# ==============================================================================
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: Verify version in package files
run: |
VERSION="${{ inputs.version }}"
echo "🔍 Checking that version $VERSION is present in all package files..."
ERRORS=0
ROOT_VER=$(grep '"version"' package.json | head -1 | grep -o '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[^"]*')
if [ "$ROOT_VER" != "$VERSION" ]; then
echo "::error::package.json version is '$ROOT_VER', expected '$VERSION'"
ERRORS=$((ERRORS + 1))
else
echo " ✅ package.json → $ROOT_VER"
fi
BACKEND_VER=$(grep '^version' apps/backend/pyproject.toml | head -1 | grep -o '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[^"]*')
if [ "$BACKEND_VER" != "$VERSION" ]; then
echo "::error::pyproject.toml version is '$BACKEND_VER', expected '$VERSION'"
ERRORS=$((ERRORS + 1))
else
echo " ✅ apps/backend/pyproject.toml → $BACKEND_VER"
fi
FRONTEND_VER=$(grep '"version"' apps/frontend/package.json | head -1 | grep -o '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[^"]*')
if [ "$FRONTEND_VER" != "$VERSION" ]; then
echo "::error::frontend/package.json version is '$FRONTEND_VER', expected '$VERSION'"
ERRORS=$((ERRORS + 1))
else
echo " ✅ apps/frontend/package.json → $FRONTEND_VER"
fi
if [ "$ERRORS" -gt 0 ]; then
echo ""
echo "::error::Version mismatch! Bump versions in a PR before running the release workflow."
exit 1
fi
echo "✅ All package files match version $VERSION"
- 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"
# ============================================================================
# TAG + RELEASE (no commits — branch protection safe)
# ============================================================================
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
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- 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 "$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 check | ✅ All package files match v${VERSION} |
| 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