mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-16 19:29:26 +02:00
Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai>
422 lines
14 KiB
YAML
422 lines
14 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 }}
|
|
|
|
helm-chart:
|
|
needs: [verify, docker-backend-merge, docker-web-merge]
|
|
if: github.repository_owner == 'multica-ai'
|
|
runs-on: ubuntu-latest
|
|
concurrency:
|
|
group: release-helm-chart-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
env:
|
|
CHART_DIR: deploy/helm/multica
|
|
OCI_REGISTRY: oci://ghcr.io/${{ github.repository_owner }}/charts
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Helm
|
|
uses: azure/setup-helm@v4
|
|
|
|
- name: Sync chart metadata with release tag
|
|
env:
|
|
TAG_NAME: ${{ needs.verify.outputs.tag_name }}
|
|
run: |
|
|
chart_version="${TAG_NAME#v}"
|
|
sed -i -E "s/^version:.*/version: ${chart_version}/" "$CHART_DIR/Chart.yaml"
|
|
sed -i -E "s/^appVersion:.*/appVersion: \"${TAG_NAME}\"/" "$CHART_DIR/Chart.yaml"
|
|
echo "CHART_VERSION=${chart_version}" >> "$GITHUB_ENV"
|
|
|
|
- name: Lint chart
|
|
run: helm lint "$CHART_DIR"
|
|
|
|
- name: Package chart
|
|
run: helm package "$CHART_DIR" --destination .chart-packages
|
|
|
|
- name: Login to GHCR
|
|
run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io --username "${{ github.actor }}" --password-stdin
|
|
|
|
- name: Push chart to GHCR
|
|
run: helm push ".chart-packages/multica-${CHART_VERSION}.tgz" "$OCI_REGISTRY"
|
|
|
|
- name: Verify published chart
|
|
run: helm show chart "$OCI_REGISTRY/multica" --version "$CHART_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
|