From 97d99b8c5e4b21b56b797e512cadc59c0545fc85 Mon Sep 17 00:00:00 2001 From: kilyabin <65072190+kilyabin@users.noreply.github.com> Date: Wed, 29 Apr 2026 01:26:32 +0400 Subject: [PATCH] feat(flatpak): add Flatpak packaging and CI release pipeline - GNOME Platform 48 runtime (WebKitGTK included) - GDK_BACKEND=wayland,x11 via finish-args (fixes GTK panic on pure-Wayland) - libappindicator3 from flathub/shared-modules (not in GNOME 48 runtime) - npm run tauri -- build --no-bundle (enables custom-protocol, fixes blank window) - build-flatpak job in reusable-channel-publish.yml - In-app updater disabled via VITE_PSYSONIC_FLATPAK=1 --- .../workflows/reusable-channel-publish.yml | 84 ++++++++++++++ packages/flatpak/.gitignore | 7 ++ .../flatpak/dev.psysonic.Psysonic.desktop | 10 ++ .../dev.psysonic.Psysonic.metainfo.xml | 107 ++++++++++++++++++ packages/flatpak/dev.psysonic.Psysonic.yml | 96 ++++++++++++++++ src/components/AppUpdater.tsx | 3 + 6 files changed, 307 insertions(+) create mode 100644 packages/flatpak/.gitignore create mode 100644 packages/flatpak/dev.psysonic.Psysonic.desktop create mode 100644 packages/flatpak/dev.psysonic.Psysonic.metainfo.xml create mode 100644 packages/flatpak/dev.psysonic.Psysonic.yml diff --git a/.github/workflows/reusable-channel-publish.yml b/.github/workflows/reusable-channel-publish.yml index ab54d42f..afcc2728 100644 --- a/.github/workflows/reusable-channel-publish.yml +++ b/.github/workflows/reusable-channel-publish.yml @@ -270,6 +270,90 @@ jobs: \( -name "*.deb" -o -name "*.rpm" -o -name "*.AppImage" \) \ | xargs gh release upload "$RELEASE_TAG" --clobber + build-flatpak: + needs: create-release + runs-on: ubuntu-24.04 + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-50 + options: --privileged + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ inputs.source_ref }} + + - name: install source-generation tools + run: | + python3 -m ensurepip --upgrade + python3 -m pip install --quiet tomlkit aiohttp + + - name: install flatpak-builder-tools + run: | + git clone --depth=1 \ + https://github.com/flatpak/flatpak-builder-tools.git \ + /tmp/flatpak-builder-tools + python3 -m pip install --quiet \ + /tmp/flatpak-builder-tools/node + + - name: clone shared-modules + run: | + git clone --depth=1 \ + https://github.com/flathub/shared-modules.git \ + packages/flatpak/shared-modules + + - name: generate cargo offline sources + run: | + python3 /tmp/flatpak-builder-tools/cargo/flatpak-cargo-generator.py \ + src-tauri/Cargo.lock -o packages/flatpak/cargo-sources.json + + - name: generate npm offline sources + run: | + flatpak-node-generator npm package-lock.json \ + -o packages/flatpak/npm-sources.json + + - name: patch manifest for this release + env: + RELEASE_TAG: ${{ needs.create-release.outputs.release_tag }} + run: | + git config --global --add safe.directory /__w/psysonic/psysonic + COMMIT=$(git ls-remote origin "refs/tags/${RELEASE_TAG}^{}" | awk '{print $1}') + if [ -z "$COMMIT" ]; then + COMMIT=$(git ls-remote origin "refs/tags/${RELEASE_TAG}" | awk '{print $1}') + fi + sed -i \ + -e "s|tag: app-v.*|tag: ${RELEASE_TAG}|" \ + -e "s|commit: PLACEHOLDER_COMMIT|commit: ${COMMIT}|" \ + -e "s|VITE_LASTFM_API_KEY=__LASTFM_KEY__|VITE_LASTFM_API_KEY=${{ secrets.VITE_LASTFM_API_KEY }}|" \ + -e "s|VITE_LASTFM_API_SECRET=__LASTFM_SECRET__|VITE_LASTFM_API_SECRET=${{ secrets.VITE_LASTFM_API_SECRET }}|" \ + packages/flatpak/dev.psysonic.Psysonic.yml + + - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + bundle: Psysonic_${{ needs.create-release.outputs.package_version }}.flatpak + manifest-path: packages/flatpak/dev.psysonic.Psysonic.yml + cache-key: flatpak-${{ github.sha }} + + upload-flatpak: + needs: [create-release, build-flatpak] + permissions: + contents: write + runs-on: ubuntu-24.04 + steps: + - name: download flatpak artifact + uses: actions/download-artifact@v4 + with: + name: Psysonic_${{ needs.create-release.outputs.package_version }}-x86_64.flatpak + + - name: upload to release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION=${{ needs.create-release.outputs.package_version }} + RELEASE_TAG=${{ needs.create-release.outputs.release_tag }} + gh release upload "$RELEASE_TAG" \ + "Psysonic_${VERSION}.flatpak" \ + --clobber \ + --repo ${{ github.repository }} + verify-nix: if: ${{ inputs.verify_nix }} needs: create-release diff --git a/packages/flatpak/.gitignore b/packages/flatpak/.gitignore new file mode 100644 index 00000000..fd2c1116 --- /dev/null +++ b/packages/flatpak/.gitignore @@ -0,0 +1,7 @@ +# Generated by flatpak-cargo-generator and flatpak-node-generator. +# Committed only in the Flathub mirror repo, not here. +cargo-sources.json +npm-sources.json + +# Cloned at CI build time and manually for local builds (see CLAUDE.md). +shared-modules/ diff --git a/packages/flatpak/dev.psysonic.Psysonic.desktop b/packages/flatpak/dev.psysonic.Psysonic.desktop new file mode 100644 index 00000000..69151064 --- /dev/null +++ b/packages/flatpak/dev.psysonic.Psysonic.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=Psysonic +Comment=Desktop music player for Subsonic API-compatible servers +Exec=psysonic +Icon=dev.psysonic.Psysonic +Terminal=false +Type=Application +Categories=AudioVideo;Audio;Music;Player; +StartupWMClass=psysonic +Keywords=music;player;subsonic;navidrome;streaming; diff --git a/packages/flatpak/dev.psysonic.Psysonic.metainfo.xml b/packages/flatpak/dev.psysonic.Psysonic.metainfo.xml new file mode 100644 index 00000000..89dc1721 --- /dev/null +++ b/packages/flatpak/dev.psysonic.Psysonic.metainfo.xml @@ -0,0 +1,107 @@ + + + dev.psysonic.Psysonic + Psysonic + Music player for Subsonic-compatible servers + CC0-1.0 + GPL-3.0-only + + + Psychotoxical + + + +

+ Psysonic is a desktop music player for Subsonic-compatible servers such as + Navidrome and Gonic. It connects to your self-hosted music library and lets + you stream your collection from anywhere. +

+

Features:

+ +
+ + dev.psysonic.Psysonic.desktop + + https://github.com/Psychotoxical/psysonic + https://github.com/Psychotoxical/psysonic/issues + https://ko-fi.com/psychotoxic + https://github.com/Psychotoxical/psysonic + + + AudioVideo + Audio + Music + Player + + + + music + player + subsonic + navidrome + gonic + streaming + self-hosted + + + + + mild + + + + 768 + keyboard + pointing + + + + always + + + + + + Main player view with album art and play queue + https://raw.githubusercontent.com/Psychotoxical/psysonic/main/assets/screenshots/main.png + + + Fullscreen ambient player + https://raw.githubusercontent.com/Psychotoxical/psysonic/main/assets/screenshots/fullscreen.png + + + Settings — server profiles and appearance + https://raw.githubusercontent.com/Psychotoxical/psysonic/main/assets/screenshots/settings.png + + + + + + https://github.com/Psychotoxical/psysonic/releases/tag/app-v1.43.0 + +

Added user management for Navidrome admins, seekable HTTP streaming, + mini player improvements, floating player bar, NVIDIA DMA-BUF auto-disable, + and smoother lyrics scroll animation.

+
+
+ + https://github.com/Psychotoxical/psysonic/releases/tag/app-v1.42.1 + +

Critical fix: mini player no longer hangs the app on Windows. Added + optional "Preload mini player" setting on Linux and macOS.

+
+
+
+
diff --git a/packages/flatpak/dev.psysonic.Psysonic.yml b/packages/flatpak/dev.psysonic.Psysonic.yml new file mode 100644 index 00000000..d13a411d --- /dev/null +++ b/packages/flatpak/dev.psysonic.Psysonic.yml @@ -0,0 +1,96 @@ +app-id: dev.psysonic.Psysonic +runtime: org.gnome.Platform +runtime-version: '50' +sdk: org.gnome.Sdk +sdk-extensions: + - org.freedesktop.Sdk.Extension.rust-stable + - org.freedesktop.Sdk.Extension.node20 +command: psysonic +separate-locales: false + +finish-args: + # Network: Subsonic server, Last.fm, Discord RPC, LRCLIB, Bandsintown + - --share=network + - --share=ipc + # Display: prefer Wayland, fall back to X11 + - --socket=wayland + - --socket=fallback-x11 + # On pure-Wayland sessions the X11 socket is absent; tell GDK to try + # Wayland first so GTK doesn't panic when XDG_SESSION_TYPE=wayland. + - --env=GDK_BACKEND=wayland,x11 + # Audio: PulseAudio + ALSA, USB DACs (UMC202HD etc.) + - --socket=pulseaudio + - --device=all + # D-Bus: MPRIS, Discord RPC, tray icon + - --socket=session-bus + - --talk-name=org.mpris.MediaPlayer2.* + - --own-name=org.mpris.MediaPlayer2.psysonic + - --talk-name=com.discordapp.* + # Discord RPC unix socket lives in $XDG_RUNTIME_DIR + - --filesystem=xdg-run/discord-ipc-0:ro + - --filesystem=xdg-run/discord-ipc-1:ro + - --filesystem=xdg-run/discord-ipc-2:ro + - --filesystem=xdg-run/discord-ipc-3:ro + - --filesystem=xdg-run/discord-ipc-4:ro + # Tray icon (GNOME needs AppIndicator extension installed) + - --talk-name=org.kde.StatusNotifierWatcher + - --talk-name=org.freedesktop.Notifications + # Local music library (read-only, for future local-file playback) + - --filesystem=xdg-music:ro + +build-options: + append-path: /usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/node20/bin + env: + - CARGO_HOME=/run/build/psysonic/cargo + - CARGO_NET_OFFLINE=true + - RUSTUP_TOOLCHAIN=stable + +modules: + # libappindicator3 is not in the GNOME 48 runtime; build the Canonical + # version from shared-modules (cloned by the CI job before flatpak-builder + # runs, and manually for local builds per CLAUDE.md). + - shared-modules/libappindicator/libappindicator-gtk3-12.10.json + + - name: psysonic + buildsystem: simple + build-options: + env: + # Disables the in-app updater UI — Flathub owns updates for Flatpak installs + - VITE_PSYSONIC_FLATPAK=1 + # Prevents tauri-build from checking whether the Vite dev server is running + - TAURI_SKIP_DEVSERVER_CHECK=true + # Last.fm API keys — replaced with real values by the CI job at build time + - VITE_LASTFM_API_KEY=__LASTFM_KEY__ + - VITE_LASTFM_API_SECRET=__LASTFM_SECRET__ + # npm offline cache — flatpak-node-generator places sources in flatpak-node/npm-cache + - npm_config_cache=/run/build/psysonic/flatpak-node/npm-cache + build-commands: + # Install npm dependencies from pre-generated offline sources + - npm ci --offline + # `tauri build` runs beforeBuildCommand (vite) then compiles the Rust + # binary with the custom-protocol feature enabled (required for the + # embedded asset server). --no-bundle skips deb/rpm/AppImage generation. + - npm run tauri -- build --no-bundle + # Install binary and assets + - install -Dm755 src-tauri/target/release/psysonic "${FLATPAK_DEST}/bin/psysonic" + - install -Dm644 dev.psysonic.Psysonic.desktop "${FLATPAK_DEST}/share/applications/dev.psysonic.Psysonic.desktop" + - install -Dm644 dev.psysonic.Psysonic.metainfo.xml "${FLATPAK_DEST}/share/metainfo/dev.psysonic.Psysonic.metainfo.xml" + - install -Dm644 src-tauri/icons/32x32.png "${FLATPAK_DEST}/share/icons/hicolor/32x32/apps/dev.psysonic.Psysonic.png" + - install -Dm644 src-tauri/icons/128x128.png "${FLATPAK_DEST}/share/icons/hicolor/128x128/apps/dev.psysonic.Psysonic.png" + - install -Dm644 src-tauri/icons/128x128@2x.png "${FLATPAK_DEST}/share/icons/hicolor/256x256/apps/dev.psysonic.Psysonic.png" + - install -Dm644 src-tauri/icons/icon.png "${FLATPAK_DEST}/share/icons/hicolor/512x512/apps/dev.psysonic.Psysonic.png" + sources: + - type: git + url: https://github.com/Psychotoxical/psysonic.git + # Updated automatically by the CI release job; bump manually for local builds + tag: app-v1.43.0 + commit: PLACEHOLDER_COMMIT + # Generated by the CI job via flatpak-cargo-generator and flatpak-node-generator. + # Committed into git only for the Flathub mirror repo (separate from this source + # repo); locally, run the commands in CLAUDE.md to create them. + - cargo-sources.json + - npm-sources.json + - type: file + path: dev.psysonic.Psysonic.desktop + - type: file + path: dev.psysonic.Psysonic.metainfo.xml diff --git a/src/components/AppUpdater.tsx b/src/components/AppUpdater.tsx index 5db23032..03e065e4 100644 --- a/src/components/AppUpdater.tsx +++ b/src/components/AppUpdater.tsx @@ -94,6 +94,9 @@ function pickAsset(assets: GithubAsset[]): GithubAsset | undefined { type DlState = 'idle' | 'downloading' | 'done' | 'error'; export default function AppUpdater() { + // Flathub manages updates for Flatpak installs — updater UI must not appear + if (import.meta.env.VITE_PSYSONIC_FLATPAK === '1') return null; + const { t } = useTranslation(); const [release, setRelease] = useState(null); const [dismissed, setDismissed] = useState(false);