Merge pull request #26 from MaxMB15/chore-bump-v0.3.6 #33
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: 0 | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| jobs: | |
| # Create the draft release before platform builds so parallel jobs can upload without racing | |
| # ("release not found" when Linux finished before macOS ran gh release create). | |
| prepare-release: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Create draft GitHub release (if missing) | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(python3 -c "import json; print(json.load(open('apps/desktop/src-tauri/tauri.conf.json'))['version'])") | |
| if gh release view "v${VERSION}" --repo "$GITHUB_REPOSITORY" &>/dev/null; then | |
| echo "Release v${VERSION} already exists; skipping create." | |
| else | |
| gh release create "v${VERSION}" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --title "MaxVideoPlayer v${VERSION}" \ | |
| --notes "See the [CHANGELOG](https://github.com/MaxMB15/MaxVideoPlayer/blob/main/CHANGELOG.md) for details." \ | |
| --draft | |
| fi | |
| release-macos: | |
| needs: [prepare-release] | |
| runs-on: macos-latest | |
| env: | |
| CARGO_HOME: /Users/runner/.cargo | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Rust cache | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: ". -> target" | |
| cache-on-failure: true | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| - name: Install frontend deps | |
| run: npm ci | |
| - name: Install system deps | |
| run: | | |
| # ffmpeg@7 required: mpv 0.40.0 uses FF_PROFILE_* macros removed in ffmpeg 8.x | |
| brew install meson ninja pkg-config ffmpeg@7 libass libplacebo dylibbundler | |
| # Make ffmpeg@7 visible to pkg-config (it is keg-only, not linked by default) | |
| echo "PKG_CONFIG_PATH=$(brew --prefix ffmpeg@7)/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" >> $GITHUB_ENV | |
| - name: Build libmpv | |
| run: ./scripts/build-libmpv.sh macos | |
| - name: Prepare signing key | |
| run: | | |
| # Tauri CLI requires the raw minisign format (starting with "untrusted comment:"). | |
| # The secret may be stored as: | |
| # (a) base64-encoded raw key, or | |
| # (b) raw key with actual newlines, or | |
| # (c) raw key with escaped \n (two chars) instead of real newlines. | |
| # We normalize to raw format and always write to GITHUB_ENV so the key | |
| # is guaranteed to have proper newlines when Tauri reads it. | |
| python3 - << 'EOF' | |
| import base64, os, sys | |
| key = os.environ.get("TAURI_SIGNING_PRIVATE_KEY", "") | |
| print(f"[signing] Key length: {len(key)}, has newlines: {'yes' if chr(10) in key else 'no'}") | |
| if not key.strip(): | |
| print("[signing] ERROR: no signing key set.") | |
| sys.exit(1) | |
| # Normalize literal \n (two chars) to actual newlines | |
| key = key.replace("\\n", "\n") | |
| def try_b64(s): | |
| try: | |
| return base64.b64decode(s.strip(), validate=True).decode("utf-8") | |
| except Exception: | |
| return None | |
| if "untrusted comment:" in key: | |
| final_key = key | |
| print("[signing] Raw minisign key detected.") | |
| else: | |
| # Try up to 2 levels of base64 decoding to handle both | |
| # single-encoded and accidentally double-encoded secrets. | |
| current = key | |
| final_key = None | |
| for depth in range(1, 3): | |
| decoded = try_b64(current) | |
| if decoded is None: | |
| print(f"[signing] ERROR: base64 decode failed at depth {depth}.") | |
| sys.exit(1) | |
| if "untrusted comment:" in decoded: | |
| final_key = decoded | |
| print(f"[signing] Key decoded at depth {depth}.") | |
| break | |
| current = decoded | |
| if final_key is None: | |
| print("[signing] ERROR: key is not a valid minisign private key.") | |
| sys.exit(1) | |
| with open(os.environ["GITHUB_ENV"], "a") as f: | |
| f.write("TAURI_SIGNING_PRIVATE_KEY<<__ENDKEY__\n") | |
| f.write(final_key.rstrip("\n") + "\n") | |
| f.write("__ENDKEY__\n") | |
| print("[signing] Key written to GITHUB_ENV.") | |
| EOF | |
| - name: Build Tauri app | |
| # Build without updater artifacts so bundle_dmg.sh doesn't race with | |
| # hdiutil over the .app bundle. We create the updater artifacts manually below. | |
| run: cd apps/desktop && npx tauri build --config '{"bundle":{"createUpdaterArtifacts":false}}' | |
| - name: Create updater artifacts | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(python3 -c "import json; print(json.load(open('apps/desktop/src-tauri/tauri.conf.json'))['version'])") | |
| echo "VERSION=$VERSION" >> "$GITHUB_ENV" | |
| BUNDLE_DIR="target/release/bundle/macos" | |
| UPDATER="$BUNDLE_DIR/MaxVideoPlayer.app.tar.gz" | |
| # Create the .tar.gz updater archive from the built .app | |
| cd "$BUNDLE_DIR" | |
| tar czf MaxVideoPlayer.app.tar.gz MaxVideoPlayer.app | |
| cd - | |
| # tauri signer sign -k always base64-decodes its argument (no raw-format | |
| # detection). Re-encode the raw key as base64 so Tauri can decode it. | |
| B64_KEY=$(python3 -c "import base64,os; print(base64.b64encode(os.environ['TAURI_SIGNING_PRIVATE_KEY'].encode()).decode())") | |
| if [[ -n "${TAURI_SIGNING_PRIVATE_KEY_PASSWORD:-}" ]]; then | |
| cd apps/desktop && npx tauri signer sign \ | |
| -k "$B64_KEY" \ | |
| -p "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" \ | |
| "../../$UPDATER" | |
| cd - | |
| else | |
| cd apps/desktop && npx tauri signer sign \ | |
| -k "$B64_KEY" \ | |
| --no-password \ | |
| "../../$UPDATER" | |
| cd - | |
| fi | |
| - name: Upload macOS updater signature | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-updater-sig | |
| path: target/release/bundle/macos/MaxVideoPlayer.app.tar.gz.sig | |
| - name: Upload macOS assets to draft release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| VERSION="$VERSION" | |
| DMG="target/release/bundle/dmg/MaxVideoPlayer_${VERSION}_aarch64.dmg" | |
| UPDATER="target/release/bundle/macos/MaxVideoPlayer.app.tar.gz" | |
| gh release upload "v${VERSION}" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --clobber \ | |
| "$DMG" \ | |
| "${UPDATER}#MaxVideoPlayer_${VERSION}_aarch64.app.tar.gz" \ | |
| "${UPDATER}.sig#MaxVideoPlayer_${VERSION}_aarch64.app.tar.gz.sig" | |
| release-linux: | |
| needs: [prepare-release] | |
| runs-on: ubuntu-latest | |
| env: | |
| CARGO_HOME: /home/runner/.cargo | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Rust cache | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: ". -> target" | |
| cache-on-failure: true | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - name: Install frontend deps | |
| run: rm -f package-lock.json && npm install | |
| - name: Install system deps | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libmpv-dev libgtk-3-dev libwebkit2gtk-4.1-dev \ | |
| libjavascriptcoregtk-4.1-dev libsoup-3.0-dev \ | |
| libayatana-appindicator3-dev \ | |
| libegl-dev libssl-dev pkg-config librsvg2-dev \ | |
| patchelf rpm | |
| - name: Prepare signing key | |
| run: | | |
| python3 - << 'EOF' | |
| import base64, os, sys | |
| key = os.environ.get("TAURI_SIGNING_PRIVATE_KEY", "") | |
| if not key.strip(): | |
| print("[signing] ERROR: no signing key set.") | |
| sys.exit(1) | |
| key = key.replace("\\n", "\n") | |
| def try_b64(s): | |
| try: return base64.b64decode(s.strip(), validate=True).decode("utf-8") | |
| except Exception: return None | |
| if "untrusted comment:" in key: | |
| final_key = key | |
| else: | |
| current = key | |
| final_key = None | |
| for depth in range(1, 3): | |
| decoded = try_b64(current) | |
| if decoded is None: | |
| print(f"[signing] ERROR: base64 decode failed at depth {depth}.") | |
| sys.exit(1) | |
| if "untrusted comment:" in decoded: | |
| final_key = decoded | |
| break | |
| current = decoded | |
| if final_key is None: | |
| print("[signing] ERROR: key is not a valid minisign private key.") | |
| sys.exit(1) | |
| with open(os.environ["GITHUB_ENV"], "a") as f: | |
| f.write("TAURI_SIGNING_PRIVATE_KEY<<__ENDKEY__\n") | |
| f.write(final_key.rstrip("\n") + "\n") | |
| f.write("__ENDKEY__\n") | |
| print("[signing] Key written to GITHUB_ENV.") | |
| EOF | |
| - name: Build Tauri app | |
| run: cd apps/desktop && npx tauri build --config '{"bundle":{"createUpdaterArtifacts":false}}' | |
| - name: Create updater artifacts | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(python3 -c "import json; print(json.load(open('apps/desktop/src-tauri/tauri.conf.json'))['version'])") | |
| echo "VERSION=$VERSION" >> "$GITHUB_ENV" | |
| BUNDLE_DIR="target/release/bundle" | |
| APPIMAGE=$(find "$BUNDLE_DIR/appimage" -name "*.AppImage" | head -1) | |
| if [[ -n "$APPIMAGE" ]]; then | |
| UPDATER="$BUNDLE_DIR/MaxVideoPlayer_linux.tar.gz" | |
| tar czf "$UPDATER" -C "$(dirname "$APPIMAGE")" "$(basename "$APPIMAGE")" | |
| B64_KEY=$(python3 -c "import base64,os; print(base64.b64encode(os.environ['TAURI_SIGNING_PRIVATE_KEY'].encode()).decode())") | |
| if [[ -n "${TAURI_SIGNING_PRIVATE_KEY_PASSWORD:-}" ]]; then | |
| cd apps/desktop && npx tauri signer sign -k "$B64_KEY" -p "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" "../../$UPDATER" | |
| cd - | |
| else | |
| cd apps/desktop && npx tauri signer sign -k "$B64_KEY" --no-password "../../$UPDATER" | |
| cd - | |
| fi | |
| fi | |
| - name: Upload Linux updater signature | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-updater-sig | |
| path: target/release/bundle/MaxVideoPlayer_linux.tar.gz.sig | |
| if-no-files-found: warn | |
| - name: Publish to GitHub release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| VERSION="$VERSION" | |
| BUNDLE_DIR="target/release/bundle" | |
| # Collect all Linux artifacts | |
| ARTIFACTS=() | |
| for deb in "$BUNDLE_DIR"/deb/*.deb; do | |
| [[ -f "$deb" ]] && ARTIFACTS+=("$deb") | |
| done | |
| for rpm in "$BUNDLE_DIR"/rpm/*.rpm; do | |
| [[ -f "$rpm" ]] && ARTIFACTS+=("$rpm") | |
| done | |
| for appimage in "$BUNDLE_DIR"/appimage/*.AppImage; do | |
| [[ -f "$appimage" ]] && ARTIFACTS+=("$appimage") | |
| done | |
| # Include updater tar.gz if it exists | |
| UPDATER="$BUNDLE_DIR/MaxVideoPlayer_linux.tar.gz" | |
| if [[ -f "$UPDATER" ]]; then | |
| ARTIFACTS+=("$UPDATER") | |
| [[ -f "${UPDATER}.sig" ]] && ARTIFACTS+=("${UPDATER}.sig") | |
| fi | |
| # Upload to draft release (created by prepare-release job before builds) | |
| if [[ ${#ARTIFACTS[@]} -eq 0 ]]; then | |
| echo "::error::No Linux artifacts found under $BUNDLE_DIR (deb/rpm/appimage)." | |
| exit 1 | |
| fi | |
| for artifact in "${ARTIFACTS[@]}"; do | |
| gh release upload "v${VERSION}" "$artifact" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --clobber | |
| done | |
| # ── Publish latest.json (depends on both platform jobs) ─────────────── | |
| publish-latest-json: | |
| runs-on: ubuntu-latest | |
| needs: [release-macos, release-linux] | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download updater signatures | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: sigs | |
| - name: Build and upload latest.json | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(python3 -c "import json; print(json.load(open('apps/desktop/src-tauri/tauri.conf.json'))['version'])") | |
| MACOS_SIG="" | |
| if [[ -f sigs/macos-updater-sig/MaxVideoPlayer.app.tar.gz.sig ]]; then | |
| MACOS_SIG=$(cat sigs/macos-updater-sig/MaxVideoPlayer.app.tar.gz.sig) | |
| fi | |
| LINUX_SIG="" | |
| if [[ -f sigs/linux-updater-sig/MaxVideoPlayer_linux.tar.gz.sig ]]; then | |
| LINUX_SIG=$(cat sigs/linux-updater-sig/MaxVideoPlayer_linux.tar.gz.sig) | |
| fi | |
| python3 - "$VERSION" "$MACOS_SIG" "$LINUX_SIG" << 'PYEOF' | |
| import json, sys | |
| from datetime import datetime, timezone | |
| version, macos_sig, linux_sig = sys.argv[1], sys.argv[2], sys.argv[3] | |
| platforms = {} | |
| if macos_sig: | |
| platforms["darwin-aarch64"] = { | |
| "signature": macos_sig, | |
| "url": f"https://github.com/MaxMB15/MaxVideoPlayer/releases/download/v{version}/MaxVideoPlayer_{version}_aarch64.app.tar.gz" | |
| } | |
| if linux_sig: | |
| platforms["linux-x86_64"] = { | |
| "signature": linux_sig, | |
| "url": f"https://github.com/MaxMB15/MaxVideoPlayer/releases/download/v{version}/MaxVideoPlayer_linux.tar.gz" | |
| } | |
| data = { | |
| "version": version, | |
| "notes": "See the CHANGELOG for details.", | |
| "pub_date": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), | |
| "platforms": platforms | |
| } | |
| with open("latest.json", "w") as f: | |
| json.dump(data, f, indent=2) | |
| print(f"latest.json written with platforms: {list(platforms.keys())}") | |
| PYEOF | |
| gh release upload "v${VERSION}" latest.json \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --clobber |