All checks were successful
CI / ci (push) Successful in 4m0s
Matches what Homebrew 7's style check wants; Ruby flight blocks are only tolerated in third-party taps for now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
295 lines
11 KiB
YAML
295 lines
11 KiB
YAML
name: Desktop Release
|
|
|
|
# Builds the Quintodrome desktop app (Tauri) for macOS (.app/.dmg), bundling
|
|
# the Navidrome server as a sidecar, attaches the artifacts to the Forgejo
|
|
# release on `v*` tags, and updates the Homebrew tap. Windows isn't built here
|
|
# (no Windows runner); the Windows steps below are kept for when one exists.
|
|
#
|
|
# Note: artifacts are not signed with Apple/Windows certs (macOS builds are only
|
|
# ad-hoc signed), so users will hit Gatekeeper/SmartScreen prompts. The
|
|
# Homebrew cask works around Gatekeeper by stripping the quarantine flag.
|
|
|
|
on:
|
|
push:
|
|
tags: ["v*"]
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: desktop-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
build-desktop:
|
|
name: Build desktop (${{ matrix.name }})
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
# Both Mac targets build in one job, one after the other: there is
|
|
# one Mac runner (no Intel one, so x86_64 is cross-compiled), and
|
|
# concurrent jobs there collide on the shared action cache's git
|
|
# locks. Forgejo ignores max-parallel.
|
|
- os: macos-latest
|
|
name: macos
|
|
targets: aarch64-apple-darwin,x86_64-apple-darwin
|
|
runs-on: ${{ matrix.os }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: ui/package-lock.json
|
|
|
|
# actions/setup-go can't find the go it just installed on the host-mode
|
|
# Mac runner ("/bin/sh: version: command not found"), so fetch the
|
|
# latest patch of go.mod's Go release directly.
|
|
- name: Setup Go
|
|
if: runner.os != 'Windows'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
GO_MINOR=$(awk '/^go /{print $2}' go.mod)
|
|
GO_VERSION=$(curl -fsSL 'https://go.dev/dl/?mode=json&include=all' \
|
|
| jq -r --arg m "go$GO_MINOR" '[.[] | select(.stable) | .version | select(. == $m or startswith($m + "."))][0]')
|
|
case "$(uname -s)/$(uname -m)" in
|
|
Darwin/arm64) PLATFORM=darwin-arm64 ;;
|
|
Darwin/x86_64) PLATFORM=darwin-amd64 ;;
|
|
Linux/x86_64) PLATFORM=linux-amd64 ;;
|
|
Linux/aarch64) PLATFORM=linux-arm64 ;;
|
|
esac
|
|
GOROOT_PARENT="$RUNNER_TEMP/go-$GO_VERSION"
|
|
if [[ ! -x "$GOROOT_PARENT/go/bin/go" ]]; then
|
|
mkdir -p "$GOROOT_PARENT"
|
|
curl -fsSL "https://go.dev/dl/$GO_VERSION.$PLATFORM.tar.gz" | tar -xz -C "$GOROOT_PARENT"
|
|
fi
|
|
echo "$GOROOT_PARENT/go/bin" >> "$GITHUB_PATH"
|
|
"$GOROOT_PARENT/go/bin/go" version
|
|
|
|
- name: Setup Go (Windows)
|
|
if: runner.os == 'Windows'
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version-file: go.mod
|
|
|
|
- name: Setup Rust
|
|
uses: https://github.com/dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: ${{ matrix.targets }}
|
|
|
|
- name: Cache Rust build
|
|
uses: https://github.com/Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: desktop/src-tauri
|
|
|
|
# Navidrome's sqlite_fts5 tag needs CGO, so on Windows we need a gcc.
|
|
- name: Setup Windows CGO (mingw)
|
|
if: runner.os == 'Windows'
|
|
uses: https://github.com/msys2/setup-msys2@v2
|
|
with:
|
|
msystem: MINGW64
|
|
install: mingw-w64-x86_64-gcc
|
|
update: false
|
|
|
|
- name: Add mingw to PATH
|
|
if: runner.os == 'Windows'
|
|
shell: bash
|
|
run: echo "C:/msys64/mingw64/bin" >> $GITHUB_PATH
|
|
|
|
- name: Build Navidrome web UI
|
|
shell: bash
|
|
run: |
|
|
cd ui
|
|
# --ignore-scripts skips the workbox postinstall (bin/update-workbox.sh),
|
|
# a bash script that fails under cmd.exe on Windows. It's only needed
|
|
# for the PWA service worker, which the desktop app doesn't use.
|
|
npm ci --ignore-scripts
|
|
npm run build
|
|
|
|
- name: Build Navidrome server binary
|
|
shell: bash
|
|
run: |
|
|
GIT_SHA=$(git rev-parse --short HEAD)
|
|
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
|
|
GIT_TAG="${GITHUB_REF_NAME#v}"
|
|
else
|
|
GIT_TAG="dev"
|
|
fi
|
|
|
|
export CGO_ENABLED=1
|
|
mkdir -p desktop/src-tauri/binaries
|
|
for TARGET in $(echo "${{ matrix.targets }}" | tr , " "); do
|
|
# Build for each target, not the runner's own arch.
|
|
case "$TARGET" in
|
|
aarch64-apple-*) export GOARCH=arm64 CC="clang -arch arm64" ;;
|
|
x86_64-apple-*) export GOARCH=amd64 CC="clang -arch x86_64" ;;
|
|
esac
|
|
|
|
# Stage the sidecar under the target-triple name tauri expects.
|
|
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
|
OUTPUT="desktop/src-tauri/binaries/navidrome-$TARGET.exe"
|
|
else
|
|
OUTPUT="desktop/src-tauri/binaries/navidrome-$TARGET"
|
|
fi
|
|
|
|
go build \
|
|
-ldflags="-X github.com/navidrome/navidrome/consts.gitSha=$GIT_SHA -X github.com/navidrome/navidrome/consts.gitTag=$GIT_TAG" \
|
|
-tags=netgo,sqlite_fts5 \
|
|
-o "$OUTPUT"
|
|
done
|
|
|
|
- name: Build Tauri app
|
|
shell: bash
|
|
run: |
|
|
cd desktop
|
|
for TARGET in $(echo "${{ matrix.targets }}" | tr , " "); do
|
|
# rust-toolchain.toml pins a toolchain other than the one Setup
|
|
# Rust gave the targets to, so add each to the pinned one here.
|
|
rustup target add "$TARGET"
|
|
npx --yes @tauri-apps/cli@2 build --target "$TARGET"
|
|
done
|
|
|
|
- name: Collect artifacts (macOS)
|
|
if: runner.os == 'macOS'
|
|
shell: bash
|
|
run: |
|
|
mkdir -p dist
|
|
for TARGET in $(echo "${{ matrix.targets }}" | tr , " "); do
|
|
BUNDLE=desktop/src-tauri/target/$TARGET/release/bundle
|
|
ditto -c -k --keepParent "$BUNDLE/macos/Quintodrome.app" "dist/Quintodrome-$TARGET.app.zip"
|
|
cp "$BUNDLE"/dmg/*.dmg dist/ 2>/dev/null || true
|
|
done
|
|
ls -la dist
|
|
|
|
- name: Collect artifacts (Windows)
|
|
if: runner.os == 'Windows'
|
|
shell: bash
|
|
run: |
|
|
mkdir -p dist
|
|
for TARGET in $(echo "${{ matrix.targets }}" | tr , " "); do
|
|
BUNDLE=desktop/src-tauri/target/$TARGET/release/bundle
|
|
cp "$BUNDLE"/nsis/*.exe dist/ 2>/dev/null || true
|
|
cp "$BUNDLE"/msi/*.msi dist/ 2>/dev/null || true
|
|
done
|
|
ls -la dist
|
|
|
|
- name: Upload artifacts
|
|
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
|
|
with:
|
|
name: desktop-${{ matrix.name }}
|
|
path: dist/*
|
|
retention-days: 7
|
|
|
|
release:
|
|
name: Publish Gitea release
|
|
needs: build-desktop
|
|
if: startsWith(github.ref, 'refs/tags/')
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: https://code.forgejo.org/forgejo/download-artifact@v4
|
|
with:
|
|
path: dist
|
|
pattern: desktop-*
|
|
merge-multiple: true
|
|
|
|
- run: ls -R dist
|
|
|
|
- name: Create release and upload assets
|
|
env:
|
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
# tag_name must be the pushed tag itself (with its "v"), or Forgejo
|
|
# creates a second tag without it.
|
|
RELEASE_ID=$(curl -fsS -X POST "$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/releases" \
|
|
-H "Authorization: token $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"tag_name\":\"$GITHUB_REF_NAME\",\"name\":\"${GITHUB_REF_NAME#v}\",\"draft\":false}" \
|
|
| jq -r '.id')
|
|
for f in dist/*; do
|
|
curl -fsS -X POST "$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=$(basename "$f")" \
|
|
-H "Authorization: token $TOKEN" \
|
|
-H "Content-Type: application/octet-stream" \
|
|
--data-binary @"$f"
|
|
done
|
|
|
|
homebrew-tap:
|
|
name: Update Homebrew tap
|
|
needs: release
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
TAP_REPO: ssh://git@forge.quinefoundation.com:2200/quine-global/homebrew-tap.git # Forgejo SSH is on 2200; 22 is the host's sshd
|
|
# Public address for the cask. $GITHUB_SERVER_URL is how the runner
|
|
# reaches Forgejo (http://localhost:3000 on mainframe), not a public URL.
|
|
FORGE_URL: https://forge.quinefoundation.com
|
|
steps:
|
|
- uses: https://code.forgejo.org/forgejo/download-artifact@v4
|
|
with:
|
|
path: dist
|
|
pattern: desktop-macos
|
|
merge-multiple: true
|
|
|
|
# The app is only ad-hoc signed, so the cask strips the quarantine flag
|
|
# that Homebrew adds; otherwise Gatekeeper refuses to open it.
|
|
- name: Write cask
|
|
run: |
|
|
set -euo pipefail
|
|
VERSION="${GITHUB_REF_NAME#v}"
|
|
ARM_SHA=$(sha256sum dist/Quintodrome-aarch64-apple-darwin.app.zip | cut -d' ' -f1)
|
|
INTEL_SHA=$(sha256sum dist/Quintodrome-x86_64-apple-darwin.app.zip | cut -d' ' -f1)
|
|
mkdir -p tap/Casks
|
|
cat > tap/Casks/quintodrome.rb <<CASK
|
|
cask "quintodrome" do
|
|
arch arm: "aarch64", intel: "x86_64"
|
|
|
|
version "$VERSION"
|
|
sha256 arm: "$ARM_SHA",
|
|
intel: "$INTEL_SHA"
|
|
|
|
url "$FORGE_URL/$GITHUB_REPOSITORY/releases/download/v#{version}/Quintodrome-#{arch}-apple-darwin.app.zip"
|
|
name "Quintodrome"
|
|
desc "Desktop app wrapping the Navidrome music server"
|
|
homepage "$FORGE_URL/$GITHUB_REPOSITORY"
|
|
|
|
depends_on :macos
|
|
|
|
app "Quintodrome.app"
|
|
|
|
postflight_steps do
|
|
run "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "{{appdir}}/Quintodrome.app"]
|
|
end
|
|
|
|
zap trash: [
|
|
"~/Library/Application Support/com.quineglobal.drome",
|
|
"~/Library/Application Support/com.quintodrome.desktop",
|
|
]
|
|
end
|
|
CASK
|
|
cat tap/Casks/quintodrome.rb
|
|
|
|
- name: Push to tap
|
|
env:
|
|
SSH_KEY: ${{ secrets.HOMEBREW_TAP_SSH_KEY }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ -z "$SSH_KEY" ]]; then
|
|
echo "::error::HOMEBREW_TAP_SSH_KEY secret is not set (add it under this repo's Settings > Actions > Secrets; its public key needs write access on the tap repo)"
|
|
exit 1
|
|
fi
|
|
mkdir -p ~/.ssh
|
|
echo "$SSH_KEY" > ~/.ssh/tap_key
|
|
chmod 600 ~/.ssh/tap_key
|
|
echo "[forge.quinefoundation.com]:2200 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHECXyh0SwRlXikgu919nC9OTmAU7AEcrlbJ/J/H9DTU" > ~/.ssh/known_hosts
|
|
export GIT_SSH_COMMAND="ssh -i ~/.ssh/tap_key -o IdentitiesOnly=yes"
|
|
git clone "$TAP_REPO" repo
|
|
mkdir -p repo/Casks
|
|
cp tap/Casks/quintodrome.rb repo/Casks/
|
|
cd repo
|
|
git config user.name "Quintodrome release"
|
|
git config user.email "noreply@forge.quinefoundation.com"
|
|
git add Casks/quintodrome.rb
|
|
git diff --cached --quiet && { echo "Cask unchanged"; exit 0; }
|
|
git commit -m "quintodrome ${GITHUB_REF_NAME#v}"
|
|
git push
|