diff --git a/.gitignore b/.gitignore index cbe6031ed..df6b2d86e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ build/ dist/ src/target/ +# Nix +result + # Python __pycache__/ *.pyc diff --git a/dev/nix/flake/flake-parts.nix b/dev/nix/flake/flake-parts.nix new file mode 100644 index 000000000..9469fe7d6 --- /dev/null +++ b/dev/nix/flake/flake-parts.nix @@ -0,0 +1,5 @@ +{ inputs, ... }: { + imports = [ + inputs.flake-parts.flakeModules.modules + ]; +} diff --git a/dev/nix/flake/systems.nix b/dev/nix/flake/systems.nix new file mode 100644 index 000000000..804b32fcf --- /dev/null +++ b/dev/nix/flake/systems.nix @@ -0,0 +1,3 @@ +{ inputs, ... }: { + systems = inputs.nixpkgs.lib.systems.flakeExposed; +} diff --git a/dev/nix/package/_packages/cef-binary.nix b/dev/nix/package/_packages/cef-binary.nix new file mode 100644 index 000000000..7667f5020 --- /dev/null +++ b/dev/nix/package/_packages/cef-binary.nix @@ -0,0 +1,26 @@ +{ cef-binary }: +let + version = "149.0.4"; +in +( + if (cef-binary.version == version) then + cef-binary + else + (cef-binary.override { + inherit version; + gitRevision = "2f1bfd8"; + chromiumVersion = "149.0.7827.156"; + srcHashes = { + aarch64-linux = "sha256-iQmnlonux7I+2ACEtpdmlS1E4A+aNFgylRsykD+KgKA="; + x86_64-linux = "sha256-bUNgdnXkfta/pA0c/OE20E53IFKfjxxENdS6Hc0YObI="; + }; + }) +).overrideAttrs + (old: { + # make nix understand that src and version are defined in this file + inherit (old) src version; + + passthru = old.passthru // { + updateScript = ./update-cef.sh; + }; + }) diff --git a/dev/nix/package/_packages/cef-lib.nix b/dev/nix/package/_packages/cef-lib.nix new file mode 100644 index 000000000..ff7737eb7 --- /dev/null +++ b/dev/nix/package/_packages/cef-lib.nix @@ -0,0 +1,29 @@ +{ + stdenv, + cef-binary, + writers, + lib, +}: +let + archive = writers.writeJSON "archive.json" { + type = "minimal"; + name = baseNameOf cef-binary.src.url; + sha1 = lib.fakeHash; + }; +in +# Jellyfin expects CEF in a certain layout. +# Cf the Stremio package for the same issue. +# Can't symlinkJoin here though because CEF uses the realpaths to determine icudtl.dat path +# Trivial compilation and should stay correctly linked. +# There's likely a Rust issue that is the reason why for the fixup. +stdenv.mkDerivation (finalAttrs: { + pname = "cef-lib"; + inherit (cef-binary) version; + dontUnpack = true; + installPhase = '' + mkdir -p $out + cp -r ${cef-binary}/Release/* $out/ + cp -r ${cef-binary}/Resources/* $out/ + cp ${archive} $out/archive.json + ''; +}) diff --git a/dev/nix/package/_packages/jellyfin-desktop_crane-deps.nix b/dev/nix/package/_packages/jellyfin-desktop_crane-deps.nix new file mode 100644 index 000000000..631d90428 --- /dev/null +++ b/dev/nix/package/_packages/jellyfin-desktop_crane-deps.nix @@ -0,0 +1,13 @@ +{ + craneLib, + craneCommonArgs, + metaSkeleton, +}: +craneLib.buildDepsOnly ( + craneCommonArgs + // { + meta = metaSkeleton // { + description = "${metaSkeleton.description} - dependencies"; + }; + } +) diff --git a/dev/nix/package/_packages/jellyfin-desktop_crane.nix b/dev/nix/package/_packages/jellyfin-desktop_crane.nix new file mode 100644 index 000000000..7a4a5ebfc --- /dev/null +++ b/dev/nix/package/_packages/jellyfin-desktop_crane.nix @@ -0,0 +1,46 @@ +{ + craneLib, + craneCommonArgs, + rustPlatform, + pkg-config, + + # Dependencies + jellyfin-desktop_crane-deps, + jellyfin-desktop_resources, + + jellyfin-desktop_nixpkgs, +}: +craneLib.buildPackage ( + craneCommonArgs + // { + + nativeBuildInputs = [ + pkg-config + rustPlatform.bindgenHook + ]; + + cargoArtifacts = jellyfin-desktop_crane-deps; + + installPhase = '' + runHook preInstall + + install -Dm755 \ + target/release/jellyfin-desktop \ + $out/bin/jellyfin-desktop + + install -Dm644 \ + ${jellyfin-desktop_resources}/linux/org.jellyfin.JellyfinDesktop.desktop \ + $out/share/applications/org.jellyfin.JellyfinDesktop.desktop + install -Dm644 \ + ${jellyfin-desktop_resources}/linux/org.jellyfin.JellyfinDesktop.metainfo.xml \ + $out/share/metainfo/org.jellyfin.JellyfinDesktop.metainfo.xml + install -Dm644 \ + ${jellyfin-desktop_resources}/linux/org.jellyfin.JellyfinDesktop.svg \ + $out/share/icons/hicolor/scalable/apps/org.jellyfin.JellyfinDesktop.svg + + runHook postInstall + ''; + + inherit (jellyfin-desktop_nixpkgs) postFixup; + } +) diff --git a/dev/nix/package/_packages/jellyfin-desktop_nixpkgs.nix b/dev/nix/package/_packages/jellyfin-desktop_nixpkgs.nix new file mode 100644 index 000000000..965a338fb --- /dev/null +++ b/dev/nix/package/_packages/jellyfin-desktop_nixpkgs.nix @@ -0,0 +1,99 @@ +{ + lib, + rustPlatform, + pkg-config, + + # Needed at runtime by CEF + libGL, + + # Dependencies + ffmpeg, + libxkbcommon, + libxcb, + cef-lib, + mpv-external-prefix, + + lastModifiedDate, +}: +rustPlatform.buildRustPackage (finalAttrs: { + src = ../../../..; + pname = "jellyfin-desktop"; + version = + let + majorVersion = + (lib.importTOML "${finalAttrs.src}/${finalAttrs.cargoRoot}/Cargo.toml").workspace.package.version; + + s = b: l: builtins.substring b l lastModifiedDate; + date = "${s 0 4}-${s 4 2}-${s 6 2}"; + in + "${majorVersion}-${date}"; + + # Fixes some Cargo.lock issues + cargoRoot = "src"; + cargoLock = { + # Fixes some other Cargo.lock issues + lockFile = "${finalAttrs.src}/${finalAttrs.cargoRoot}/Cargo.lock"; + outputHashes = { + "wl-proxy-0.1.2" = "sha256-8NMNPhBSW2gLXc9bwyg2kmHb12XIaV6b4PjM62xLldQ="; + }; + }; + + strictDeps = true; + + nativeBuildInputs = [ + rustPlatform.bindgenHook # fixes clang issues + pkg-config + ]; + + buildInputs = [ + libxcb + libxkbcommon + ffmpeg + ]; + + buildPhase = '' + runHook preBuild + cargo xtask build \ + --cef-path ${cef-lib} \ + --external-mpv ${mpv-external-prefix} \ + --out build/ + ''; + + installPhase = '' + runHook preInstall + + install -Dm755 \ + build/jellyfin-desktop \ + $out/bin/jellyfin-desktop + + install -Dm644 \ + resources/linux/org.jellyfin.JellyfinDesktop.desktop \ + $out/share/applications/org.jellyfin.JellyfinDesktop.desktop + install -Dm644 \ + resources/linux/org.jellyfin.JellyfinDesktop.metainfo.xml \ + $out/share/metainfo/org.jellyfin.JellyfinDesktop.metainfo.xml + install -Dm644 \ + resources/linux/org.jellyfin.JellyfinDesktop.svg \ + $out/share/icons/hicolor/scalable/apps/org.jellyfin.JellyfinDesktop.svg + + runHook postInstall + ''; + + postFixup = '' + old_rpath="$(patchelf --print-rpath $out/bin/jellyfin-desktop)" + patchelf \ + --set-rpath "$old_rpath:${lib.makeLibraryPath [ libGL ]}" \ + --add-needed libGL.so \ + $out/bin/jellyfin-desktop + ''; + + doCheck = false; + + meta = { + description = "Jellyfin desktop client"; + homepage = "https://github.com/jellyfin/jellyfin-desktop"; + license = lib.licenses.gpl2Only; + mainProgram = "jellyfin-desktop"; + maintainers = with lib.maintainers; [ xaltsc ]; + }; +}) diff --git a/dev/nix/package/_packages/jellyfin-desktop_resources.nix b/dev/nix/package/_packages/jellyfin-desktop_resources.nix new file mode 100644 index 000000000..a1ac2df5e --- /dev/null +++ b/dev/nix/package/_packages/jellyfin-desktop_resources.nix @@ -0,0 +1,15 @@ +{ + stdenv, + jellyfin-desktop_nixpkgs, + metaSkeleton, +}: +stdenv.mkDerivation (finalAttrs: { + src = ../../../../resources; + pname = "${jellyfin-desktop_nixpkgs.pname}-resources"; + inherit (jellyfin-desktop_nixpkgs) version; + dontUnpack = true; + installPhase = '' + cp -r $src $out + ''; + meta = metaSkeleton; +}) diff --git a/dev/nix/package/_packages/mpv-external-prefix.nix b/dev/nix/package/_packages/mpv-external-prefix.nix new file mode 100644 index 000000000..a7e6d69c4 --- /dev/null +++ b/dev/nix/package/_packages/mpv-external-prefix.nix @@ -0,0 +1,13 @@ +{ + symlinkJoin, + lib, + mpv-unwrapped, +}: +symlinkJoin { + pname = "mpv-external-prefix"; + inherit (mpv-unwrapped) version; + paths = [ + (lib.getDev mpv-unwrapped) + (lib.getLib mpv-unwrapped) + ]; +} diff --git a/dev/nix/package/_packages/update-cef.sh b/dev/nix/package/_packages/update-cef.sh new file mode 100755 index 000000000..2ac604467 --- /dev/null +++ b/dev/nix/package/_packages/update-cef.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env nix-shell +#!nix-shell -i bash -p curl gnused jq nix-update cargo rustc git + +set -euo pipefail + +export NIX_CONFIG="experimental-features = nix-command flakes" + +if [ -f "${PWD}/flake.nix" ]; then + ROOT="${PWD}" +else + ROOT="$(git rev-parse --show-toplevel)" +fi + +PACKAGE_PATH="${ROOT}/dev/nix/package/_packages" +PACKAGE="cef-binary" + +target_version=$(cargo metadata --format-version 1 --locked --manifest-path "${ROOT}/src/Cargo.toml" \ + | jq -r 'first(.packages[] | select(.name=="cef") | .version) + // error("Cannot determine version")' \ + | cut -d+ -f2) + +get_attr() { + local attr="$1" + local default_system='${builtins.currentSystem}' + local system="${2:-$default_system}" + nix-instantiate --eval -E \ + "(builtins.getFlake \"${ROOT}\") + .packages.${system} + .${PACKAGE}.${attr}" \ + | tr -d '"' +} + +current_version="$(get_attr 'version')" + +echo "Target version : $target_version" +echo "Current version: $current_version" + +if [[ "$target_version" == "$current_version" ]]; then + echo "Package is up-to-date" + exit 0 +fi + + +version_json=$(curl --silent https://cef-builds.spotifycdn.com/index.json \ + | jq '[ .linux64.versions[] + | select (.channel == "stable") + | select (.cef_version | startswith("'"${target_version}"'")) + ][0]') + +cef_version=$(echo "$version_json" | jq -r '.cef_version' | cut -d'+' -f1) +git_revision=$(echo "$version_json" | jq -r '.cef_version' | cut -d'+' -f2 | cut -c 2-) +chromium_version=$(echo "$version_json" | jq -r '.chromium_version') + +echo "Latest version: $cef_version" + +if [[ "$cef_version" == "$current_version" ]]; then + echo "Package is up-to-date, rust crate cef has an unsupported version" + exit 1 +fi + + + +update_nix_value() { + local key="$1" + local value="${2:-}" + sed -i "s|$key = \".*\"|$key = \"$value\"|" "${PACKAGE_PATH}/${PACKAGE}.nix" +} + +update_nix_value version "$cef_version" +update_nix_value gitRevision "$git_revision" +update_nix_value chromiumVersion "$chromium_version" + +update_hashes () { + local system="$1" + local url="$(get_attr 'src.url' "${system}")" + local hash=$(nix store prefetch-file --json "${url}" | jq -r .hash) + update_nix_value "${system}" "${hash}" +} + +update_hashes x86_64-linux +update_hashes aarch64-linux diff --git a/dev/nix/package/default.nix b/dev/nix/package/default.nix new file mode 100644 index 000000000..1b2c0776a --- /dev/null +++ b/dev/nix/package/default.nix @@ -0,0 +1,77 @@ +{ inputs, self, ... }: { + imports = [ + inputs.flake-parts.flakeModules.easyOverlay + ]; + perSystem = + { + pkgs, + lib, + config, + ... + }: + { + overlayAttrs = { + inherit (config.packages) jellyfin-desktop; + }; + packages = + let + craneLib = inputs.crane.mkLib pkgs; + in + (lib.fix ( + p: + let + metaSkeleton = { + inherit (p.jellyfin-desktop_nixpkgs.meta) + homepage + license + maintainers + description + ; + }; + craneCommonArgs = { + src = ../../../src; + strictDeps = true; + + inherit (p.jellyfin-desktop_nixpkgs) + version + pname + passthru + buildInputs + ; + + CEF_PATH = p.cef-lib; + EXTERNAL_MPV_DIR = p.mpv-external-prefix; + + cargoExtraArgs = "--bin jellyfin-desktop"; + + meta = metaSkeleton; + }; + + in + (lib.mapAttrs (n: pkgs.callPackage ./_packages/${n}.nix) { + cef-binary = { }; + cef-lib = { inherit (p) cef-binary; }; + jellyfin-desktop_resources = { + inherit (p) jellyfin-desktop_nixpkgs; + inherit metaSkeleton; + }; + jellyfin-desktop_nixpkgs = { + inherit (p) cef-lib mpv-external-prefix; + inherit (self) lastModifiedDate; + }; + jellyfin-desktop_crane-deps = { + inherit craneLib craneCommonArgs metaSkeleton; + }; + jellyfin-desktop_crane = { + inherit craneLib craneCommonArgs; + inherit (p) jellyfin-desktop_crane-deps jellyfin-desktop_resources jellyfin-desktop_nixpkgs; + }; + mpv-external-prefix = { }; + }) + // { + jellyfin-desktop = p.jellyfin-desktop_crane; + default = p.jellyfin-desktop; + } + )); + }; +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..1abaf6b0f --- /dev/null +++ b/flake.lock @@ -0,0 +1,110 @@ +{ + "nodes": { + "crane": { + "locked": { + "lastModified": 1781825982, + "narHash": "sha256-SlXKwIRIhrOSAcTjCB3ftPLzJWZStQIPS7J1FlZPnKk=", + "owner": "ipetkov", + "repo": "crane", + "rev": "469fd08d0bcf6926321fa973c6777fbc87785dd7", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1778716662, + "narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "import-tree": { + "locked": { + "lastModified": 1778781969, + "narHash": "sha256-Jjuz5CmSkur8KvLDoGa+vylEp+RkQtv4mt/qcMznpH0=", + "owner": "denful", + "repo": "import-tree", + "rev": "d321337efd0f23a9eb14a42adb7b2c29313ab274", + "type": "github" + }, + "original": { + "owner": "denful", + "repo": "import-tree", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1781577229, + "narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "567a49d1913ce81ac6e9582e3553dd90a955875f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1777168982, + "narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1781808408, + "narHash": "sha256-0sOb6OIaD/K1zHxBhpmlKvkADfe0n8+l4Jj2e1Q0r3w=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e8210c649915deed7080033cdbabcc19e40bb899", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-26.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "flake-parts": "flake-parts", + "import-tree": "import-tree", + "nixpkgs": "nixpkgs", + "nixpkgs-stable": "nixpkgs-stable" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..9d838d476 --- /dev/null +++ b/flake.nix @@ -0,0 +1,17 @@ +{ + description = "Jellyfin Desktop Client"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-26.05"; + + flake-parts.url = "github:hercules-ci/flake-parts"; + import-tree.url = "github:denful/import-tree"; + + crane.url = "github:ipetkov/crane"; + }; + + outputs = + inputs@{ flake-parts, import-tree, ... }: + flake-parts.lib.mkFlake { inherit inputs; } (import-tree ./dev/nix); +}