mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-17 03:38:32 +02:00
The release job uses GoReleaser to bump the formula in multica-ai/homebrew-tap. Forks don't have HOMEBREW_TAP_GITHUB_TOKEN and should not publish to that tap, so the job currently fails on every fork tag push (401 Bad credentials against the upstream tap). This makes the workflow red on downstream forks even though the actual artifact pipeline (verify → docker-backend-build → docker-backend-merge) succeeds and produces a usable image. Gate the release job on `github.repository_owner == 'multica-ai'`. Upstream behaviour unchanged. Forks now see a clean green run for docker artifacts only.
381 lines
12 KiB
YAML
381 lines
12 KiB
YAML
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
# GitHub Actions uses glob patterns here, not regex. Match versioned
|
|
# tags broadly at the trigger layer, then enforce strict semver below.
|
|
- "v*.*.*"
|
|
- "!v*-dirty*"
|
|
|
|
permissions:
|
|
contents: write
|
|
packages: write
|
|
|
|
jobs:
|
|
verify:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
tag_name: ${{ steps.release_meta.outputs.tag_name }}
|
|
is_stable: ${{ steps.release_meta.outputs.is_stable }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Validate tag name
|
|
id: release_meta
|
|
shell: bash
|
|
run: |
|
|
tag="${GITHUB_REF_NAME}"
|
|
echo "Triggered by tag: $tag"
|
|
if [[ ! "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
|
|
echo "::error::Release tags must look like vX.Y.Z or vX.Y.Z-suffix; got '$tag'."
|
|
exit 1
|
|
fi
|
|
if [[ "$tag" == *-dirty* ]]; then
|
|
echo "::error::Refusing to release from dirty tag '$tag'."
|
|
exit 1
|
|
fi
|
|
echo "tag_name=$tag" >> "$GITHUB_OUTPUT"
|
|
if [[ "$tag" == *-* ]]; then
|
|
echo "is_stable=false" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "is_stable=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version-file: server/go.mod
|
|
cache-dependency-path: server/go.sum
|
|
|
|
- name: Run tests
|
|
run: cd server && go test ./...
|
|
|
|
release:
|
|
needs: verify
|
|
# Only run on the canonical upstream repo. Forks don't have the
|
|
# HOMEBREW_TAP_GITHUB_TOKEN secret and should not be publishing to
|
|
# `multica-ai/homebrew-tap` anyway. Without this guard, every fork's
|
|
# tag push fails this job (401 against the upstream tap), which makes
|
|
# downstream CI go red without affecting the actual artifact pipeline.
|
|
if: github.repository_owner == 'multica-ai'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version-file: server/go.mod
|
|
cache-dependency-path: server/go.sum
|
|
|
|
- name: Run GoReleaser
|
|
uses: goreleaser/goreleaser-action@v6
|
|
with:
|
|
version: "~> v2"
|
|
args: release --clean
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
|
|
|
|
# Multi-arch images are built natively per platform on dedicated runners
|
|
# (amd64 on ubuntu-latest, arm64 on ubuntu-24.04-arm) and merged into a
|
|
# manifest list. This avoids QEMU emulation, which was making the Next.js
|
|
# arm64 build run for 30+ minutes per release.
|
|
docker-backend-build:
|
|
needs: verify
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- platform: linux/amd64
|
|
runs-on: ubuntu-latest
|
|
- platform: linux/arm64
|
|
runs-on: ubuntu-24.04-arm
|
|
runs-on: ${{ matrix.runs-on }}
|
|
steps:
|
|
- name: Prepare
|
|
run: |
|
|
platform=${{ matrix.platform }}
|
|
echo "PLATFORM_PAIR=${platform//\//-}" >> "$GITHUB_ENV"
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Compute backend image labels
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ghcr.io/${{ github.repository_owner }}/multica-backend
|
|
labels: |
|
|
org.opencontainers.image.title=Multica Backend
|
|
org.opencontainers.image.description=Multica self-hosted backend
|
|
|
|
- name: Setup Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Login to GHCR
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Build and push by digest
|
|
id: build
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
context: .
|
|
file: Dockerfile
|
|
pull: true
|
|
platforms: ${{ matrix.platform }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha,scope=release-backend-${{ env.PLATFORM_PAIR }}
|
|
cache-to: type=gha,mode=max,scope=release-backend-${{ env.PLATFORM_PAIR }}
|
|
build-args: |
|
|
VERSION=${{ needs.verify.outputs.tag_name }}
|
|
COMMIT=${{ github.sha }}
|
|
outputs: type=image,name=ghcr.io/${{ github.repository_owner }}/multica-backend,push-by-digest=true,name-canonical=true,push=true
|
|
|
|
- name: Export digest
|
|
run: |
|
|
mkdir -p /tmp/digests
|
|
digest="${{ steps.build.outputs.digest }}"
|
|
touch "/tmp/digests/${digest#sha256:}"
|
|
|
|
- name: Upload digest
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: digests-backend-${{ env.PLATFORM_PAIR }}
|
|
path: /tmp/digests/*
|
|
if-no-files-found: error
|
|
retention-days: 1
|
|
|
|
docker-backend-merge:
|
|
needs: [verify, docker-backend-build]
|
|
runs-on: ubuntu-latest
|
|
concurrency:
|
|
group: release-docker-backend-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
steps:
|
|
- name: Download digests
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: /tmp/digests
|
|
pattern: digests-backend-*
|
|
merge-multiple: true
|
|
|
|
- name: Setup Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Compute backend image tags
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ghcr.io/${{ github.repository_owner }}/multica-backend
|
|
flavor: |
|
|
latest=false
|
|
tags: |
|
|
type=raw,value=latest,enable=${{ needs.verify.outputs.is_stable == 'true' }}
|
|
type=raw,value=${{ needs.verify.outputs.tag_name }}
|
|
type=sha,prefix=sha-
|
|
|
|
- name: Login to GHCR
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Create manifest list and push
|
|
working-directory: /tmp/digests
|
|
run: |
|
|
docker buildx imagetools create \
|
|
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
|
$(printf 'ghcr.io/${{ github.repository_owner }}/multica-backend@sha256:%s ' *)
|
|
|
|
- name: Inspect image
|
|
run: |
|
|
docker buildx imagetools inspect \
|
|
ghcr.io/${{ github.repository_owner }}/multica-backend:${{ steps.meta.outputs.version }}
|
|
|
|
docker-web-build:
|
|
needs: verify
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- platform: linux/amd64
|
|
runs-on: ubuntu-latest
|
|
- platform: linux/arm64
|
|
runs-on: ubuntu-24.04-arm
|
|
runs-on: ${{ matrix.runs-on }}
|
|
steps:
|
|
- name: Prepare
|
|
run: |
|
|
platform=${{ matrix.platform }}
|
|
echo "PLATFORM_PAIR=${platform//\//-}" >> "$GITHUB_ENV"
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Compute web image labels
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ghcr.io/${{ github.repository_owner }}/multica-web
|
|
labels: |
|
|
org.opencontainers.image.title=Multica Web
|
|
org.opencontainers.image.description=Multica self-hosted web frontend
|
|
|
|
- name: Setup Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Login to GHCR
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Build and push by digest
|
|
id: build
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
context: .
|
|
file: Dockerfile.web
|
|
pull: true
|
|
platforms: ${{ matrix.platform }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha,scope=release-web-${{ env.PLATFORM_PAIR }}
|
|
cache-to: type=gha,mode=max,scope=release-web-${{ env.PLATFORM_PAIR }}
|
|
build-args: |
|
|
REMOTE_API_URL=http://backend:8080
|
|
NEXT_PUBLIC_APP_VERSION=${{ needs.verify.outputs.tag_name }}
|
|
outputs: type=image,name=ghcr.io/${{ github.repository_owner }}/multica-web,push-by-digest=true,name-canonical=true,push=true
|
|
|
|
- name: Export digest
|
|
run: |
|
|
mkdir -p /tmp/digests
|
|
digest="${{ steps.build.outputs.digest }}"
|
|
touch "/tmp/digests/${digest#sha256:}"
|
|
|
|
- name: Upload digest
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: digests-web-${{ env.PLATFORM_PAIR }}
|
|
path: /tmp/digests/*
|
|
if-no-files-found: error
|
|
retention-days: 1
|
|
|
|
docker-web-merge:
|
|
needs: [verify, docker-web-build]
|
|
runs-on: ubuntu-latest
|
|
concurrency:
|
|
group: release-docker-web-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
steps:
|
|
- name: Download digests
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: /tmp/digests
|
|
pattern: digests-web-*
|
|
merge-multiple: true
|
|
|
|
- name: Setup Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Compute web image tags
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ghcr.io/${{ github.repository_owner }}/multica-web
|
|
flavor: |
|
|
latest=false
|
|
tags: |
|
|
type=raw,value=latest,enable=${{ needs.verify.outputs.is_stable == 'true' }}
|
|
type=raw,value=${{ needs.verify.outputs.tag_name }}
|
|
type=sha,prefix=sha-
|
|
|
|
- name: Login to GHCR
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Create manifest list and push
|
|
working-directory: /tmp/digests
|
|
run: |
|
|
docker buildx imagetools create \
|
|
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
|
$(printf 'ghcr.io/${{ github.repository_owner }}/multica-web@sha256:%s ' *)
|
|
|
|
- name: Inspect image
|
|
run: |
|
|
docker buildx imagetools inspect \
|
|
ghcr.io/${{ github.repository_owner }}/multica-web:${{ steps.meta.outputs.version }}
|
|
|
|
# Build the Desktop installers for Linux and Windows and upload them to
|
|
# the GitHub Release that the `release` job above just published. macOS
|
|
# Desktop continues to ship via the manual `release-desktop` skill so it
|
|
# can be signed + notarized with Apple Developer credentials that are
|
|
# not (yet) wired into CI.
|
|
desktop:
|
|
needs: release
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- os: ubuntu-latest
|
|
target: linux
|
|
- os: windows-latest
|
|
target: win
|
|
runs-on: ${{ matrix.os }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Install rpmbuild (Linux)
|
|
if: matrix.target == 'linux'
|
|
run: sudo apt-get update && sudo apt-get install -y rpm
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version-file: server/go.mod
|
|
cache-dependency-path: server/go.sum
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@v4
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Package Desktop installers (${{ matrix.target }})
|
|
working-directory: apps/desktop
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
# electron-builder's GitHub publisher reads this:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
# Disable code signing on Linux/Windows for now — the public
|
|
# release is unsigned for these platforms, the CLI carries the
|
|
# trust boundary. Set CSC_LINK in repo secrets to enable
|
|
# Windows signing later.
|
|
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
|
run: node scripts/package.mjs --${{ matrix.target }} --x64 --arm64 --publish always
|