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.target }}) strategy: fail-fast: false matrix: include: - os: macos-latest target: aarch64-apple-darwin # Cross-compiled on the same Mac runner; there is no Intel runner. - os: macos-latest target: x86_64-apple-darwin runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - name: Setup Node uses: actions/setup-node@v6 with: node-version: 24 cache: npm cache-dependency-path: ui/package-lock.json - name: Setup Go uses: actions/setup-go@v6 with: go-version-file: go.mod - name: Setup Rust uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - name: Cache Rust build uses: 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: 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 if [[ "$RUNNER_OS" == "macOS" ]]; then # Build for the matrix target, not the runner's own arch. case "${{ matrix.target }}" in aarch64-*) export GOARCH=arm64 CC="clang -arch arm64" ;; x86_64-*) export GOARCH=amd64 CC="clang -arch x86_64" ;; esac export CGO_ENABLED=1 fi if [[ "$RUNNER_OS" == "Windows" ]]; then export CGO_ENABLED=1 OUTPUT="navidrome.exe" else OUTPUT="navidrome" 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" # Stage the sidecar under the target-triple name tauri expects. mkdir -p desktop/src-tauri/binaries if [[ "$RUNNER_OS" == "Windows" ]]; then cp navidrome.exe "desktop/src-tauri/binaries/navidrome-${{ matrix.target }}.exe" else cp navidrome "desktop/src-tauri/binaries/navidrome-${{ matrix.target }}" chmod +x "desktop/src-tauri/binaries/navidrome-${{ matrix.target }}" fi - name: Build Tauri app shell: bash run: | cd desktop npx --yes @tauri-apps/cli@2 build --target ${{ matrix.target }} - name: Collect artifacts (macOS) if: runner.os == 'macOS' shell: bash run: | mkdir -p dist BUNDLE=desktop/src-tauri/target/${{ matrix.target }}/release/bundle ditto -c -k --keepParent "$BUNDLE/macos/Quintodrome.app" "dist/Quintodrome-${{ matrix.target }}.app.zip" cp "$BUNDLE"/dmg/*.dmg dist/ 2>/dev/null || true ls -la dist - name: Collect artifacts (Windows) if: runner.os == 'Windows' shell: bash run: | mkdir -p dist BUNDLE=desktop/src-tauri/target/${{ matrix.target }}/release/bundle cp "$BUNDLE"/nsis/*.exe dist/ 2>/dev/null || true cp "$BUNDLE"/msi/*.msi dist/ 2>/dev/null || true ls -la dist - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: desktop-${{ matrix.target }} 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: actions/download-artifact@v8 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: git@forge.quinefoundation.com:Quine/homebrew-quintodrome.git steps: - uses: actions/download-artifact@v8 with: path: dist pattern: desktop-*-apple-darwin 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 < 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 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID2WBpBywsGATL7IMkvjlYguBfeYOxPstTcsPuH0Xdp3" > ~/.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