Files
sure/.github/workflows/publish.yml
Juan José Mata 2df4406af3 fix: bump pre-release version on source branch instead of main (#875)
The bump-pre_release-version job was hardcoded to push to main, which
caused version bumps to land on main even when tags were created from
release branches (e.g., v0.6.7-rc.2).

This fix:
- Adds a step to detect which branch contains the tagged commit
- Prefers non-main branches (release branches) over main
- Checks out and pushes to the detected source branch

https://claude.ai/code/session_01XsxnhP8ZaGbWUMsQwA5F5V

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-02 19:18:00 +01:00

469 lines
17 KiB
YAML

# Reference: https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
# Conditions for pushing the image to GHCR:
# - Triggered by push to default branch (`main`)
# - Triggered by push to a version tag (`v*`)
# - Triggered by a scheduled run
# - Triggered manually via `workflow_dispatch` with `push: true`
#
# Conditional expression:
# github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || github.event_name == 'schedule' || github.event.inputs.push
name: Publish Docker image
on:
workflow_dispatch:
inputs:
ref:
description: 'Git ref (tag or commit SHA) to build'
required: true
type: string
default: 'main'
push:
description: 'Push the image to container registry'
required: false
type: boolean
default: false
push:
tags:
- 'v*'
branches:
- main
paths-ignore:
- 'charts/**'
schedule:
- cron: '30 1 * * *'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
permissions:
contents: write
packages: write
jobs:
ci:
uses: ./.github/workflows/ci.yml
build:
name: Build Docker image
needs: [ ci ]
strategy:
fail-fast: false
matrix:
platform: [amd64, arm64]
timeout-minutes: 60
runs-on: ${{ matrix.platform == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
outputs:
tags: ${{ steps.meta.outputs.tags }}
permissions:
contents: read
packages: write
steps:
- name: Check out the repo
uses: actions/checkout@v4.2.0
with:
ref: ${{ github.event.inputs.ref || github.ref }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.10.0
- name: Log in to the container registry
uses: docker/login-action@v3.3.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Configure image tags
id: tag_config
shell: bash
run: |
BASE_CONFIG="type=sha,format=long"
if [[ $GITHUB_EVENT_NAME == "schedule" ]]; then
BASE_CONFIG+=$'\n'"type=schedule,pattern=nightly"
BASE_CONFIG+=$'\n'"type=schedule,pattern=nightly-{{date 'ddd'}}"
elif [[ "$GITHUB_REF" == refs/tags/* ]]; then
TAG_NAME="${GITHUB_REF#refs/tags/}"
if [[ "$TAG_NAME" == v* ]]; then
BASE_CONFIG="type=semver,pattern={{version}}"
if [[ "$TAG_NAME" == v*-alpha* ]]; then
BASE_CONFIG+=$'\n'"type=raw,value=latest"
else
BASE_CONFIG+=$'\n'"type=raw,value=stable"
BASE_CONFIG+=$'\n'"type=raw,value=latest"
fi
fi
fi
{
echo 'TAGS_SPEC<<EOF'
echo "$BASE_CONFIG"
echo EOF
} >> $GITHUB_ENV
- name: Get current date (RFC 3339 format)
id: date
run: echo "date=$(date -Iseconds)" >> $GITHUB_OUTPUT
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5.6.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: latest=false
tags: ${{ env.TAGS_SPEC }}
labels: |
org.opencontainers.image.version=${{ startsWith(github.ref, 'refs/tags/v') && github.ref_name || '' }}
org.opencontainers.image.created=${{ steps.date.outputs.date }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.vendor=we-promise
org.opencontainers.image.title=Sure
org.opencontainers.image.description=A multi-arch Docker image for the Sure Rails app
- name: Publish 'linux/${{ matrix.platform }}' image by digest
uses: docker/build-push-action@v6.16.0
id: build
with:
context: .
build-args: BUILD_COMMIT_SHA=${{ github.sha }}
platforms: 'linux/${{ matrix.platform }}'
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:cache-${{ matrix.platform }}
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:cache-${{ matrix.platform }},mode=max
labels: ${{ steps.meta.outputs.labels }}
provenance: false
push: true
# DO NOT REMOVE `oci-mediatypes=true`, fixes annotation not showing up on job.merge.steps[-1]
# ref: https://github.com/docker/build-push-action/discussions/1022
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},name-canonical=true,push-by-digest=true,oci-mediatypes=true
- name: Export the Docker image digest
if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || github.event_name == 'schedule' || github.event.inputs.push }}
run: |
mkdir -p "${RUNNER_TEMP}"/digests
echo "${DIGEST#sha256:}" > "${RUNNER_TEMP}/digests/digest-${PLATFORM}"
env:
DIGEST: ${{ steps.build.outputs.digest }}
PLATFORM: ${{ matrix.platform }}
- name: Upload the Docker image digest
if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || github.event_name == 'schedule' || github.event.inputs.push }}
uses: actions/upload-artifact@v4.6.2
with:
name: digest-${{ matrix.platform }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
merge:
name: Merge multi-arch manifest & push multi-arch tag
if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || github.event_name == 'schedule' || github.event.inputs.push }}
needs: [build]
timeout-minutes: 60
runs-on: 'ubuntu-24.04'
permissions:
packages: write
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.10.0
- name: Download Docker image digests
uses: actions/download-artifact@v4.3.0
with:
path: ${{ runner.temp }}/digests
pattern: digest-*
merge-multiple: true
- name: Log in to the container registry
uses: docker/login-action@v3.3.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Merge and push Docker image
env:
TAGS: ${{ needs.build.outputs.tags }}
DIGESTS_DIR: ${{ runner.temp }}/digests
shell: bash -xeuo pipefail {0}
run: |
tag_args=()
while IFS=$'\n' read -r tag; do
[[ -n "${tag}" ]] || continue
tag_args+=("--tag=${tag}")
done <<< "${TAGS}"
image_args=()
for PLATFORM in amd64 arm64; do
image_args+=("${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:$(<"${DIGESTS_DIR}/digest-${PLATFORM}")")
done
annotations=(
"index:org.opencontainers.image.created=$(date -Iseconds)"
'index:org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}'
'index:org.opencontainers.image.revision=${{ github.sha }}'
'index:org.opencontainers.image.ref.name=${{ github.ref_name }}'
'index:org.opencontainers.image.vendor=we-promise'
'index:org.opencontainers.image.licenses=AGPL-3.0'
'index:org.opencontainers.image.title=Sure'
'index:org.opencontainers.image.description=A multi-arch Docker image for the Sure Rails app'
)
annotation_args=()
for annotation in "${annotations[@]}"; do
annotation_args+=("--annotation=${annotation}")
done
if [[ $GITHUB_REF_TYPE == "tag" ]]; then
annotation_args+=("--annotation=index:org.opencontainers.image.version=$GITHUB_REF_NAME")
fi
attempts=0
until docker buildx imagetools create \
"${annotation_args[@]}" \
"${tag_args[@]}" \
"${image_args[@]}" \
; do
attempts=$((attempts + 1))
if [[ $attempts -ge 3 ]]; then
echo "[$(date -u)] ERROR: Failed after 3 attempts." >&2
exit 1
fi
delay=$((2 ** attempts))
if [[ $delay -gt 15 ]]; then delay=15; fi
echo "Push failed (attempt $attempts). Retrying in ${delay} seconds..."
sleep ${delay}
done
mobile:
name: Build Mobile Apps
if: startsWith(github.ref, 'refs/tags/v')
uses: ./.github/workflows/flutter-build.yml
secrets: inherit
release:
name: Create GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
needs: [merge, mobile]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- name: Download Android APK artifact
uses: actions/download-artifact@v4.3.0
with:
name: app-release-apk
path: ${{ runner.temp }}/mobile-artifacts
- name: Download iOS build artifact
uses: actions/download-artifact@v4.3.0
with:
name: ios-build-unsigned
path: ${{ runner.temp }}/ios-build
- name: Prepare release assets
run: |
mkdir -p ${{ runner.temp }}/release-assets
echo "=== Debugging: List downloaded artifacts ==="
echo "Mobile artifacts:"
ls -laR "${{ runner.temp }}/mobile-artifacts" || echo "No mobile-artifacts directory"
echo "iOS build:"
ls -laR "${{ runner.temp }}/ios-build" || echo "No ios-build directory"
echo "==========================================="
# Copy debug APK if it exists
if [ -f "${{ runner.temp }}/mobile-artifacts/app-debug.apk" ]; then
cp "${{ runner.temp }}/mobile-artifacts/app-debug.apk" "${{ runner.temp }}/release-assets/sure-${{ github.ref_name }}-debug.apk"
echo "✓ Debug APK prepared"
fi
# Copy release APK if it exists
if [ -f "${{ runner.temp }}/mobile-artifacts/app-release.apk" ]; then
cp "${{ runner.temp }}/mobile-artifacts/app-release.apk" "${{ runner.temp }}/release-assets/sure-${{ github.ref_name }}.apk"
echo "✓ Release APK prepared"
fi
# Create iOS app archive (zip the .app bundle)
# Path preserves directory structure from artifact upload
if [ -d "${{ runner.temp }}/ios-build/ios/iphoneos/Runner.app" ]; then
cd "${{ runner.temp }}/ios-build/ios/iphoneos"
zip -r "${{ runner.temp }}/release-assets/sure-${{ github.ref_name }}-ios-unsigned.zip" Runner.app
echo "✓ iOS build archive prepared"
fi
# Copy iOS build info
if [ -f "${{ runner.temp }}/ios-build/ios-build-info.txt" ]; then
cp "${{ runner.temp }}/ios-build/ios-build-info.txt" "${{ runner.temp }}/release-assets/"
fi
echo "Release assets:"
ls -la "${{ runner.temp }}/release-assets/"
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
draft: false
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }}
generate_release_notes: true
files: |
${{ runner.temp }}/release-assets/*
body: |
## Mobile Debug Builds
This release includes debug builds of the mobile applications. Download from the `Assets` area below.
- **Android APK**: Debug build for testing on Android devices
- **iOS Build**: Unsigned iOS build (requires code signing for installation)
> **Note**: These are debug builds intended for testing purposes. For production use, please build from source with proper signing credentials.
bump-pre_release-version:
name: Bump Pre-release Version
if: startsWith(github.ref, 'refs/tags/v') && (contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc'))
needs: [merge]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- name: Determine source branch for tag
id: source_branch
run: |
# Fetch all branches to find which one contains this tag's commit
git init --quiet
git remote add origin "https://github.com/${{ github.repository }}.git"
git fetch origin --quiet
# Find branches containing the tagged commit
BRANCHES=$(git branch -r --contains ${{ github.sha }} | grep -v HEAD | sed 's/origin\///' | xargs)
echo "Branches containing commit: $BRANCHES"
# Prefer non-main branches (release branches) over main
SOURCE_BRANCH="main"
for branch in $BRANCHES; do
if [ "$branch" != "main" ] && [ "$branch" != "master" ]; then
SOURCE_BRANCH="$branch"
break
fi
done
echo "Selected source branch: $SOURCE_BRANCH"
echo "branch=$SOURCE_BRANCH" >> $GITHUB_OUTPUT
- name: Check out source branch
uses: actions/checkout@v4.2.0
with:
ref: ${{ steps.source_branch.outputs.branch }}
token: ${{ secrets.GH_PAT }}
- name: Bump pre-release version
run: |
VERSION_FILE="config/initializers/version.rb"
CHART_FILE="charts/sure/Chart.yaml"
# Ensure version file exists
if [ ! -f "$VERSION_FILE" ]; then
echo "ERROR: Version file not found: $VERSION_FILE"
exit 1
fi
# Ensure chart file exists
if [ ! -f "$CHART_FILE" ]; then
echo "ERROR: Chart file not found: $CHART_FILE"
exit 1
fi
# Extract current version
CURRENT_VERSION=$(grep -oP '"\K[0-9]+\.[0-9]+\.[0-9]+-(alpha|beta|rc)\.[0-9]+' "$VERSION_FILE")
if [ -z "$CURRENT_VERSION" ]; then
echo "ERROR: Could not extract version from $VERSION_FILE"
exit 1
fi
echo "Current version: $CURRENT_VERSION"
# Extract the pre-release tag and number, then increment it
PRE_RELEASE_TAG=$(echo "$CURRENT_VERSION" | grep -oP '(alpha|beta|rc)')
if [ -z "$PRE_RELEASE_TAG" ]; then
echo "ERROR: Could not extract pre-release tag from $CURRENT_VERSION"
exit 1
fi
PRE_RELEASE_NUM=$(echo "$CURRENT_VERSION" | grep -oP '(alpha|beta|rc)\.\K[0-9]+')
if [ -z "$PRE_RELEASE_NUM" ]; then
echo "ERROR: Could not extract pre-release number from $CURRENT_VERSION"
exit 1
fi
NEW_PRE_RELEASE_NUM=$((PRE_RELEASE_NUM + 1))
# Create new version string
BASE_VERSION=$(echo "$CURRENT_VERSION" | grep -oP '^[0-9]+\.[0-9]+\.[0-9]+')
if [ -z "$BASE_VERSION" ]; then
echo "ERROR: Could not extract base version from $CURRENT_VERSION"
exit 1
fi
NEW_VERSION="${BASE_VERSION}-${PRE_RELEASE_TAG}.${NEW_PRE_RELEASE_NUM}"
echo "New version: $NEW_VERSION"
# Update the version file
sed -i "s/\"$CURRENT_VERSION\"/\"$NEW_VERSION\"/" "$VERSION_FILE"
# Verify the change
echo "Updated version.rb:"
grep "semver" "$VERSION_FILE"
# Update Helm chart version and appVersion
sed -i -E "s/^version: .*/version: ${NEW_VERSION}/" "$CHART_FILE"
sed -i -E "s/^appVersion: .*/appVersion: \"${NEW_VERSION}\"/" "$CHART_FILE"
# Verify the change
echo "Updated Chart.yaml:"
grep -E "^(version|appVersion):" "$CHART_FILE"
- name: Commit and push version bump
env:
SOURCE_BRANCH: ${{ steps.source_branch.outputs.branch }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add config/initializers/version.rb
git add charts/sure/Chart.yaml
# Check if there are changes to commit
if git diff --cached --quiet; then
echo "No changes to commit - version may have already been bumped"
exit 0
fi
git commit -m "Bump version to next iteration after ${{ github.ref_name }} release"
echo "Pushing to branch: $SOURCE_BRANCH"
# Push with retry logic
attempts=0
until git push origin HEAD:$SOURCE_BRANCH; do
attempts=$((attempts + 1))
if [[ $attempts -ge 4 ]]; then
echo "ERROR: Failed to push after 4 attempts." >&2
exit 1
fi
delay=$((2 ** attempts))
echo "Push failed (attempt $attempts). Retrying in ${delay} seconds..."
sleep ${delay}
git pull --rebase origin $SOURCE_BRANCH
done