Skip to content

Create Release

Create Release #360

Workflow file for this run

name: Create Release
on:
workflow_dispatch:
inputs:
version:
description: "Version number (e.g., 1.2.3, 1.2.3b1, or 1.2.3.dev1)"
required: true
type: string
channel:
description: "Release channel"
required: true
type: choice
options:
- stable
- beta
- nightly
workflow_call:
inputs:
version:
description: "Version number (e.g., 1.2.3, 1.2.3b1, or 1.2.3.dev1)"
required: true
type: string
channel:
description: "Release channel"
required: true
type: string
secrets:
PYPI_TOKEN:
required: true
PRIVILEGED_GITHUB_TOKEN:
required: true
env:
PYTHON_VERSION: "3.12"
BASE_IMAGE_VERSION_STABLE: "1.3.1"
BASE_IMAGE_VERSION_BETA: "1.4.4"
BASE_IMAGE_VERSION_NIGHTLY: "1.4.4"
jobs:
preflight-checks:
name: Run tests and linting before release
runs-on: ubuntu-latest
outputs:
branch: ${{ steps.branch.outputs.branch }}
steps:
- name: Determine branch to use
id: branch
run: |
CHANNEL="${{ inputs.channel }}"
if [ "$CHANNEL" = "stable" ]; then
echo "branch=stable" >> $GITHUB_OUTPUT
echo "Using stable branch for stable release"
else
echo "branch=dev" >> $GITHUB_OUTPUT
echo "Using dev branch for $CHANNEL release"
fi
- name: Trigger test workflow
uses: convictional/[email protected]
with:
owner: ${{ github.repository_owner }}
repo: server
github_token: ${{ secrets.PRIVILEGED_GITHUB_TOKEN }}
workflow_file_name: test.yml
ref: ${{ steps.branch.outputs.branch }}
wait_interval: 10
propagate_failure: true
trigger_workflow: true
wait_workflow: true
validate-and-build:
name: Validate version and build Python artifact
runs-on: ubuntu-latest
needs: preflight-checks
outputs:
version: ${{ inputs.version }}
is_prerelease: ${{ steps.validate.outputs.is_prerelease }}
base_image_version: ${{ steps.validate.outputs.base_image_version }}
branch: ${{ needs.preflight-checks.outputs.branch }}
steps:
- uses: actions/checkout@v5
with:
ref: ${{ needs.preflight-checks.outputs.branch }}
fetch-depth: 0
- name: Validate version number format
id: validate
run: |
VERSION="${{ inputs.version }}"
CHANNEL="${{ inputs.channel }}"
# Regex patterns for each channel
STABLE_PATTERN='^[0-9]+\.[0-9]+\.[0-9]+$'
BETA_PATTERN='^[0-9]+\.[0-9]+\.[0-9]+b[0-9]+$'
NIGHTLY_PATTERN='^[0-9]+\.[0-9]+\.[0-9]+\.dev[0-9]+$'
# Validate version format matches channel
case "$CHANNEL" in
stable)
if ! [[ "$VERSION" =~ $STABLE_PATTERN ]]; then
echo "Error: Stable channel requires version format: X.Y.Z (e.g., 1.2.3)"
exit 1
fi
echo "is_prerelease=false" >> $GITHUB_OUTPUT
echo "base_image_version=${{ env.BASE_IMAGE_VERSION_STABLE }}" >> $GITHUB_OUTPUT
;;
beta)
if ! [[ "$VERSION" =~ $BETA_PATTERN ]]; then
echo "Error: Beta channel requires version format: X.Y.ZbN (e.g., 1.2.3b1)"
exit 1
fi
echo "is_prerelease=true" >> $GITHUB_OUTPUT
echo "base_image_version=${{ env.BASE_IMAGE_VERSION_BETA }}" >> $GITHUB_OUTPUT
;;
nightly)
if ! [[ "$VERSION" =~ $NIGHTLY_PATTERN ]]; then
echo "Error: Nightly channel requires version format: X.Y.Z.devN (e.g., 1.2.3.dev1)"
exit 1
fi
echo "is_prerelease=true" >> $GITHUB_OUTPUT
echo "base_image_version=${{ env.BASE_IMAGE_VERSION_NIGHTLY }}" >> $GITHUB_OUTPUT
;;
*)
echo "Error: Invalid channel: $CHANNEL"
exit 1
;;
esac
echo "✅ Version $VERSION is valid for $CHANNEL channel"
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/[email protected]
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install build dependencies
run: >-
pip install build tomli tomli-w
- name: Update version in pyproject.toml
shell: python
run: |-
import tomli
import tomli_w
with open("pyproject.toml", "rb") as f:
pyproject = tomli.load(f)
pyproject["project"]["version"] = "${{ inputs.version }}"
with open("pyproject.toml", "wb") as f:
tomli_w.dump(pyproject, f)
print(f"✅ Updated pyproject.toml version to ${{ inputs.version }}")
- name: Build python package
run: >-
python3 -m build
- name: Upload distributions
uses: actions/upload-artifact@v5
with:
name: release-dists
path: dist/
create-release:
name: Create GitHub Release with Release Drafter
runs-on: ubuntu-latest
needs: validate-and-build
permissions:
contents: write
pull-requests: read
outputs:
release_id: ${{ steps.create_release.outputs.id }}
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- uses: actions/checkout@v5
with:
ref: ${{ needs.validate-and-build.outputs.branch }}
fetch-depth: 0
- name: Download distributions
uses: actions/download-artifact@v6
with:
name: release-dists
path: dist/
- name: Determine previous version tag
id: prev_version
run: |
CHANNEL="${{ inputs.channel }}"
# Find the previous tag of this channel
case "$CHANNEL" in
stable)
PREV_TAG=$(git tag --sort=-version:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)
;;
beta)
PREV_TAG=$(git tag --sort=-version:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+b[0-9]+$' | head -n 1)
;;
nightly)
PREV_TAG=$(git tag --sort=-version:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.dev[0-9]+$' | head -n 1)
;;
esac
if [ -z "$PREV_TAG" ]; then
echo "⚠️ No previous $CHANNEL release found"
echo "prev_tag=" >> $GITHUB_OUTPUT
echo "has_prev_tag=false" >> $GITHUB_OUTPUT
else
echo "✅ Previous $CHANNEL release: $PREV_TAG"
echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT
echo "has_prev_tag=true" >> $GITHUB_OUTPUT
fi
- name: Generate complete release notes
id: generate_notes
uses: ./.github/actions/generate-release-notes
with:
version: ${{ inputs.version }}
previous-tag: ${{ steps.prev_version.outputs.prev_tag }}
branch: ${{ needs.validate-and-build.outputs.branch }}
channel: ${{ inputs.channel }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Format release title
id: format_title
run: |
VERSION="${{ inputs.version }}"
CHANNEL="${{ inputs.channel }}"
if [[ "$CHANNEL" == "nightly" ]]; then
# Extract base version and date from X.Y.Z.devYYYYMMDD
if [[ "$VERSION" =~ ^([0-9]+\.[0-9]+\.[0-9]+)\.dev([0-9]+)$ ]]; then
BASE="${BASH_REMATCH[1]}"
DATE="${BASH_REMATCH[2]}"
TITLE="$BASE NIGHTLY $DATE"
else
TITLE="$VERSION NIGHTLY"
fi
elif [[ "$CHANNEL" == "beta" ]]; then
# Extract base version and beta number from X.Y.ZbN
if [[ "$VERSION" =~ ^([0-9]+\.[0-9]+\.[0-9]+)b([0-9]+)$ ]]; then
BASE="${BASH_REMATCH[1]}"
BETA_NUM="${BASH_REMATCH[2]}"
TITLE="$BASE BETA $BETA_NUM"
else
TITLE="$VERSION BETA"
fi
else
# Stable release - just use the version
TITLE="$VERSION"
fi
echo "title=$TITLE" >> $GITHUB_OUTPUT
echo "Release title: $TITLE"
- name: Create GitHub Release
id: create_release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ inputs.version }}
name: ${{ steps.format_title.outputs.title }}
body: ${{ steps.generate_notes.outputs.release-notes }}
prerelease: ${{ needs.validate-and-build.outputs.is_prerelease }}
target_commitish: ${{ needs.validate-and-build.outputs.branch }}
files: dist/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Output release info
run: |
echo "✅ Created release ${{ inputs.version }}"
echo "Release URL: ${{ steps.create_release.outputs.url }}"
pypi-publish:
name: Publish release to PyPI (stable releases only)
runs-on: ubuntu-latest
needs: create-release
if: inputs.channel == 'stable'
permissions:
id-token: write
steps:
- name: Download distributions
uses: actions/download-artifact@v6
with:
name: release-dists
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
build-and-push-container-image:
name: Build and push Music Assistant Server container to ghcr.io
runs-on: ubuntu-latest
permissions:
packages: write
needs:
- validate-and-build
- create-release
steps:
- uses: actions/checkout@v5
with:
ref: ${{ needs.validate-and-build.outputs.branch }}
- name: Download distributions
uses: actions/download-artifact@v6
with:
name: release-dists
path: dist/
- name: Log in to the GitHub container registry
uses: docker/[email protected]
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/[email protected]
- name: Generate Docker tags
id: tags
run: |
VERSION="${{ inputs.version }}"
CHANNEL="${{ inputs.channel }}"
# Extract version components
if [[ "$VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
MAJOR="${BASH_REMATCH[1]}"
MINOR="${BASH_REMATCH[2]}"
PATCH="${BASH_REMATCH[3]}"
fi
TAGS="ghcr.io/${{ github.repository_owner }}/server:$VERSION"
case "$CHANNEL" in
stable)
# For stable: add major, minor, major.minor, stable, and latest tags
TAGS="$TAGS,ghcr.io/${{ github.repository_owner }}/server:$MAJOR.$MINOR.$PATCH"
TAGS="$TAGS,ghcr.io/${{ github.repository_owner }}/server:$MAJOR.$MINOR"
TAGS="$TAGS,ghcr.io/${{ github.repository_owner }}/server:$MAJOR"
TAGS="$TAGS,ghcr.io/${{ github.repository_owner }}/server:stable"
TAGS="$TAGS,ghcr.io/${{ github.repository_owner }}/server:latest"
;;
beta)
# For beta: add beta tag
TAGS="$TAGS,ghcr.io/${{ github.repository_owner }}/server:beta"
;;
nightly)
# For nightly: add nightly tag
TAGS="$TAGS,ghcr.io/${{ github.repository_owner }}/server:nightly"
;;
esac
echo "tags=$TAGS" >> $GITHUB_OUTPUT
echo "Docker tags: $TAGS"
- name: Build and push Docker image
uses: docker/[email protected]
with:
context: .
platforms: linux/amd64,linux/arm64
file: Dockerfile
tags: ${{ steps.tags.outputs.tags }}
push: true
build-args: |
MASS_VERSION=${{ inputs.version }}
BASE_IMAGE_VERSION=${{ needs.validate-and-build.outputs.base_image_version }}
update-addon-repository:
name: Update Home Assistant Add-on Repository
runs-on: ubuntu-latest
needs:
- validate-and-build
- create-release
- build-and-push-container-image
steps:
- name: Determine addon folder
id: addon_folder
run: |
CHANNEL="${{ inputs.channel }}"
case "$CHANNEL" in
stable)
echo "folder=music_assistant" >> $GITHUB_OUTPUT
echo "Updating stable add-on"
;;
beta)
echo "folder=music_assistant_beta" >> $GITHUB_OUTPUT
echo "Updating beta add-on"
;;
nightly)
echo "folder=music_assistant_nightly" >> $GITHUB_OUTPUT
echo "Updating nightly add-on"
;;
esac
- name: Checkout add-on repository
uses: actions/checkout@v5
with:
repository: music-assistant/home-assistant-addon
token: ${{ secrets.PRIVILEGED_GITHUB_TOKEN }}
path: addon-repo
- name: Get release notes
id: get_notes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cd addon-repo
# Get the release body from the server repository
RELEASE_NOTES=$(gh release view "${{ inputs.version }}" --repo music-assistant/server --json body --jq .body)
# Save to file for processing
echo "$RELEASE_NOTES" > /tmp/release_notes.md
- name: Update config.yaml version
run: |
ADDON_FOLDER="${{ steps.addon_folder.outputs.folder }}"
VERSION="${{ inputs.version }}"
cd addon-repo/$ADDON_FOLDER
# Update version in config.yaml using sed
sed -i "s/^version: .*/version: $VERSION/" config.yaml
echo "✅ Updated config.yaml to version $VERSION"
- name: Update CHANGELOG.md
run: |
ADDON_FOLDER="${{ steps.addon_folder.outputs.folder }}"
VERSION="${{ inputs.version }}"
cd addon-repo/$ADDON_FOLDER
# Get current date
RELEASE_DATE=$(date +"%d.%m.%Y")
# Read the new release notes
NEW_NOTES=$(cat /tmp/release_notes.md)
# Create new changelog entry
{
echo "# [$VERSION] - $RELEASE_DATE"
echo ""
echo "$NEW_NOTES"
echo ""
echo ""
} > /tmp/new_changelog.md
# If CHANGELOG.md exists, keep only the last 2 versions
if [ -f CHANGELOG.md ]; then
# Extract headers to count versions
VERSION_COUNT=$(grep -c "^# \[" CHANGELOG.md || echo "0")
if [ "$VERSION_COUNT" -ge 2 ]; then
# Keep only first 2 versions (extract everything before the 3rd version header)
awk '/^# \[/{i++}i==3{exit}1' CHANGELOG.md > /tmp/old_changelog.md
# Combine new entry with trimmed old changelog
cat /tmp/new_changelog.md /tmp/old_changelog.md > CHANGELOG.md
else
# Less than 2 versions, just prepend
cat /tmp/new_changelog.md CHANGELOG.md > /tmp/combined_changelog.md
mv /tmp/combined_changelog.md CHANGELOG.md
fi
else
# No existing changelog, create new
mv /tmp/new_changelog.md CHANGELOG.md
fi
echo "✅ Updated CHANGELOG.md with new release"
- name: Commit and push changes
run: |
ADDON_FOLDER="${{ steps.addon_folder.outputs.folder }}"
VERSION="${{ inputs.version }}"
CHANNEL="${{ inputs.channel }}"
cd addon-repo
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add "$ADDON_FOLDER/config.yaml" "$ADDON_FOLDER/CHANGELOG.md"
git commit -m "🤖 Bump $CHANNEL add-on to version $VERSION" || {
echo "No changes to commit"
exit 0
}
git push
echo "✅ Successfully updated add-on repository"