feat(nix): inline flake + psysonic derivation, wire verify into release

Supersedes PR #203's out-of-tree upstream-src layout now that the Nix
build lives in the canonical repo. The flake treats `self` as the
source of truth — nothing in flake.nix or psysonic.nix needs a manual
version bump per release.

flake.nix
  - `inputs.upstream-src` dropped; `src = self` throughout
  - Dev shell retains the full WebKitGTK / GStreamer / AppIndicator
    closure needed for tauri:dev on NixOS (Linux x86_64 + aarch64)

nix/psysonic.nix
  - Version read from package.json (upstreamMeta.version removed)
  - `srcClean` filter excludes node_modules / dist / target / .git /
    result / .flatpak-builder
  - `cargoDeps = rustPlatform.importCargoLock` with empty
    `outputHashes` — sufficient for our local `[patch.crates-io]`
    path overrides (cpal-0.15.3, symphonia-format-isomp4)
  - Wraps the installed binary with LD_LIBRARY_PATH + GST_PLUGIN_PATH
    + GIO_EXTRA_MODULES + GDK_BACKEND=x11 + WebKit disable flags

nix/upstream-sources.json
  - Reduced to `{ npmDepsHash }`. Placeholder for the initial commit;
    the verify-nix job rewrites it on every release from the actual
    `prefetch-npm-deps` output.

.github/workflows/release.yml
  - New `verify-nix` job gated on `create-release` (same as the
    Tauri builds). Checks out `main`, installs Nix, recomputes
    `npmDepsHash`, refreshes `flake.lock`, runs
    `nix build .#psysonic --no-link` as the sanity check, then
    commits lock + hash updates back to `main` when they changed.
  - The old standalone `upstream-release-nix.yml` is intentionally
    not ported; its scheduled re-poll of the upstream release is
    redundant now that the source lives here.

Tarball publishing and Cachix upload remain explicitly out of scope —
those ship in a follow-up workflow tied to the cache setup.
This commit is contained in:
cucadmuh
2026-04-17 22:25:19 +02:00
committed by Psychotoxical
parent 02c7a3fe22
commit bf93ee17da
5 changed files with 363 additions and 0 deletions
+59
View File
@@ -159,3 +159,62 @@ jobs:
find src-tauri/target/release/bundle \
\( -name "*.deb" -o -name "*.rpm" -o -name "*.AppImage" \) \
| xargs gh release upload "app-v${VERSION}" --clobber
# Verifies that `nix build .#psysonic` still works against the current source
# and refreshes `nix/upstream-sources.json` (npmDepsHash) + `flake.lock`
# (nixpkgs pin). Commits the refreshed files back to `main` when they change.
#
# Tarball publishing / Cachix upload are intentionally out of scope here —
# those will live in a dedicated workflow tied to a binary cache setup.
verify-nix:
needs: create-release
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@v5
with:
# Full history so we can push the auto-commit back to the default branch.
fetch-depth: 0
# Checkout main, not the tag — we want to push lock/hash refreshes to
# the moving branch, not the immutable tag ref.
ref: main
- name: install Nix
uses: DeterminateSystems/nix-installer-action@v15
- name: compute npmDepsHash from package-lock.json
id: npm-hash
run: |
set -euo pipefail
HASH="$(nix run nixpkgs/nixos-unstable#prefetch-npm-deps -- package-lock.json)"
echo "hash=$HASH" >> "$GITHUB_OUTPUT"
echo "Computed npmDepsHash: $HASH"
- name: write npmDepsHash into nix/upstream-sources.json
run: |
set -euo pipefail
HASH='${{ steps.npm-hash.outputs.hash }}'
jq --arg h "$HASH" '.npmDepsHash = $h' nix/upstream-sources.json > nix/upstream-sources.json.new
mv nix/upstream-sources.json.new nix/upstream-sources.json
cat nix/upstream-sources.json
- name: refresh flake.lock (nixpkgs pin)
run: nix flake update --accept-flake-config
- name: verify nix build
run: nix build .#psysonic --accept-flake-config --no-link --print-build-logs
- name: commit + push refreshed lock and hash (if changed)
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add flake.lock nix/upstream-sources.json
if git diff --cached --quiet; then
echo "flake.lock / nix/upstream-sources.json unchanged — nothing to commit."
exit 0
fi
VERSION="${{ needs.create-release.outputs.package_version }}"
git commit -m "chore(nix): refresh lock + npmDepsHash for v${VERSION}"
git push origin HEAD:main
+4
View File
@@ -44,3 +44,7 @@ tmp/
# Third-party clones for local research (not committed)
research/
# Nix build output symlink
result
result-*
+121
View File
@@ -0,0 +1,121 @@
{
description = ''
Psysonic for NixOS / nixpkgs: installable app + dev shell.
Packages:
nix build .#psysonic # or .#default desktop app (.desktop + icon)
nix profile install .#psysonic
Run (after build, or from any clone with flake):
nix run .#psysonic
nix run github:Psychotoxical/psysonic
Development:
nix develop # mkShell (Rust/Node/WebKit deps + hooks)
nix shell .#devShells.default # same environment without entering subshell semantics
Release pipeline updates `flake.lock` (nixpkgs pin refresh) and
`nix/upstream-sources.json` (npmDepsHash) on every `v*` tag push
see `.github/workflows/release.yml` (verify-nix job). Package version
is read from `package.json`; nothing in this file needs manual bumping
per release.
'';
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs =
{ self, nixpkgs }:
let
inherit (nixpkgs) lib;
systems = [
"x86_64-linux"
"aarch64-linux"
];
forSystem = f: lib.genAttrs systems f;
mkShellFor =
system:
let
pkgs = nixpkgs.legacyPackages.${system};
gstPlugins = with pkgs.gst_all_1; [
gstreamer
gst-plugins-base
gst-plugins-good
gst-plugins-bad
];
gstPluginPath = pkgs.lib.makeSearchPath "lib/gstreamer-1.0" gstPlugins;
in
pkgs.mkShell {
packages = with pkgs; [
nodejs_22
rustc
cargo
cmake
pkg-config
openssl
gtk3
webkitgtk_4_1
libsoup_3
glib-networking
atk
cairo
gdk-pixbuf
glib
pango
librsvg
alsa-lib
libayatana-appindicator
]
++ gstPlugins;
shellHook = ''
export LD_LIBRARY_PATH="${pkgs.libayatana-appindicator}/lib''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export GST_PLUGIN_PATH="${gstPluginPath}''${GST_PLUGIN_PATH:+:$GST_PLUGIN_PATH}"
export GIO_EXTRA_MODULES="${pkgs.glib-networking}/lib/gio/modules''${GIO_EXTRA_MODULES:+:$GIO_EXTRA_MODULES}"
export GDK_BACKEND=x11
export WEBKIT_DISABLE_COMPOSITING_MODE=1
export WEBKIT_DISABLE_DMABUF_RENDERER=1
unset CI
'';
OPENSSL_LIB_DIR = "${pkgs.openssl.out}/lib";
OPENSSL_INCLUDE_DIR = "${pkgs.openssl.dev}/include";
};
upstreamMeta = lib.importJSON ./nix/upstream-sources.json;
psysonicFor =
system:
nixpkgs.legacyPackages.${system}.callPackage ./nix/psysonic.nix {
src = self;
inherit upstreamMeta;
};
in
{
devShells = forSystem (system: { default = mkShellFor system; });
packages = forSystem (system: {
psysonic = psysonicFor system;
default = psysonicFor system;
});
apps = forSystem (
system:
let
p = psysonicFor system;
in
{
default = {
type = "app";
program = lib.getExe p;
meta = {
inherit (p.meta) description homepage license;
mainProgram = "psysonic";
};
};
}
);
};
}
+176
View File
@@ -0,0 +1,176 @@
# Installable Psysonic (Tauri): npm build → cargo tauri build --no-bundle.
# Source: `self` (this repo). Package version is read from package.json.
# `npmDepsHash` in nix/upstream-sources.json is refreshed by the release
# workflow (see .github/workflows/release.yml, verify-nix job).
{
lib,
stdenv,
fetchNpmDeps,
npmHooks,
rustPlatform,
cargo,
rustc,
pkg-config,
cmake,
openssl,
gtk3,
webkitgtk_4_1,
libsoup_3,
glib-networking,
alsa-lib,
libayatana-appindicator,
atk,
cairo,
gdk-pixbuf,
glib,
pango,
librsvg,
cargo-tauri,
nodejs,
makeWrapper,
wrapGAppsHook4,
copyDesktopItems,
makeDesktopItem,
gst_all_1,
src,
upstreamMeta,
}:
let
version = (lib.importJSON (src + "/package.json")).version;
# WebKit media stack needs discoverable GStreamer plugins (e.g. appsink in gst-plugins-base).
gstPlugins = with gst_all_1; [
gstreamer
gst-plugins-base
gst-plugins-good
gst-plugins-bad
];
gstPluginPath = lib.makeSearchPath "lib/gstreamer-1.0" gstPlugins;
srcClean = lib.cleanSourceWith {
inherit src;
filter =
path: _:
let
f = toString path;
in
!(lib.hasInfix "/node_modules/" f)
&& !(lib.hasInfix "/dist/" f)
&& !(lib.hasInfix "/target/" f)
&& !(lib.hasInfix "/.git/" f)
&& !(lib.hasInfix "/result/" f)
&& !(lib.hasInfix "/.flatpak-builder/" f);
};
npmDeps = fetchNpmDeps {
src = srcClean;
hash = upstreamMeta.npmDepsHash;
};
cargoLockFile = src + "/src-tauri/Cargo.lock";
in
stdenv.mkDerivation (finalAttrs: {
pname = "psysonic";
inherit version;
src = srcClean;
inherit npmDeps;
strictDeps = true;
# cmake is only for Rust deps (e.g. libopus); no top-level CMakeLists.txt in repo root
dontUseCmakeConfigure = true;
nativeBuildInputs = [
npmHooks.npmConfigHook
cargo
rustc
rustPlatform.cargoSetupHook
pkg-config
cmake
makeWrapper
wrapGAppsHook4
copyDesktopItems
cargo-tauri
nodejs
];
buildInputs = [
gtk3
webkitgtk_4_1
libsoup_3
glib-networking
openssl
alsa-lib
libayatana-appindicator
atk
cairo
gdk-pixbuf
glib
pango
librsvg
]
++ gstPlugins;
cargoRoot = "src-tauri";
cargoDeps = rustPlatform.importCargoLock {
lockFile = cargoLockFile;
# Local path overrides for `[patch.crates-io]` entries in src-tauri/Cargo.toml.
# Keep in sync with that block — importCargoLock needs the source to match
# the lockfile entries for patched crates (otherwise it tries to fetch from
# crates.io and the hash mismatches).
outputHashes = { };
};
dontUseCargoParallelJobs = true;
env = {
OPENSSL_DIR = "${openssl.dev}";
OPENSSL_LIB_DIR = "${openssl.out}/lib";
OPENSSL_INCLUDE_DIR = "${openssl.dev}/include";
VITE_LASTFM_API_KEY = "";
VITE_LASTFM_API_SECRET = "";
};
# beforeBuildCommand runs npm run build; npmConfigHook supplies offline node_modules
buildPhase = ''
runHook preBuild
export HOME=$(mktemp -d)
(cd src-tauri && cargo tauri build --no-bundle -v)
runHook postBuild
'';
installPhase = ''
runHook preInstall
install -Dm755 src-tauri/target/release/psysonic -t $out/bin
install -Dm644 src-tauri/icons/128x128.png $out/share/icons/hicolor/128x128/apps/psysonic.png
runHook postInstall
'';
desktopItems = [
(makeDesktopItem {
name = "psysonic";
desktopName = "Psysonic";
comment = "Subsonic-compatible music player";
icon = "psysonic";
exec = "psysonic";
categories = [ "AudioVideo" "Audio" "Player" ];
})
];
postFixup = ''
wrapProgram $out/bin/psysonic \
--prefix LD_LIBRARY_PATH : "${lib.makeLibraryPath [ libayatana-appindicator ]}" \
--prefix GST_PLUGIN_PATH : "${gstPluginPath}" \
--prefix GIO_EXTRA_MODULES : "${glib-networking}/lib/gio/modules" \
--set GDK_BACKEND x11 \
--set WEBKIT_DISABLE_COMPOSITING_MODE 1 \
--set WEBKIT_DISABLE_DMABUF_RENDERER 1
'';
meta = {
description = "Desktop music player for Subsonic-compatible servers";
homepage = "https://github.com/Psychotoxical/psysonic";
license = lib.licenses.gpl3Only;
mainProgram = "psysonic";
platforms = lib.platforms.linux;
};
})
+3
View File
@@ -0,0 +1,3 @@
{
"npmDepsHash": "sha256-0000000000000000000000000000000000000000000="
}