Commit 9a9fe1ab authored by Paul TREHIOU's avatar Paul TREHIOU Committed by Niklas Korz
Browse files

radarr: init source build

parent cc80c888
Loading
Loading
Loading
Loading
+1687 −0

File added.

Preview size limit exceeded, changes collapsed.

+206 −0
Original line number Diff line number Diff line
{
  lib,
  stdenvNoCC,
  fetchFromGitHub,
  buildDotnetModule,
  dotnetCorePackages,
  sqlite,
  withFFmpeg ? true, # replace bundled ffprobe binary with symlink to ffmpeg package.
  servarr-ffmpeg,
  fetchYarnDeps,
  yarn,
  fixup-yarn-lock,
  nodejs,
  nixosTests,
  # update script
  writers,
  python3Packages,
  nix,
  prefetch-yarn-deps,
  fetchpatch,
  applyPatches,
}:
let
  version = "5.25.0.10024";
  # The dotnet8 compatibility patches also change `yarn.lock`, so we must pass
  # the already patched lockfile to `fetchYarnDeps`.
  src = applyPatches {
    src = fetchFromGitHub {
      owner = "Radarr";
      repo = "Radarr";
      tag = "v${version}";
      hash = "sha256-Pzdkm9oxPJRdzdCjRC22XgLBS5ffBY5u9m2QwkoZzOM=";
    };
    postPatch = ''
      mv src/NuGet.config NuGet.Config
    '';
    patches = lib.optionals (lib.versionOlder version "6.0") [
      # See https://github.com/Radarr/Radarr/pull/11064
      # Unfortunately, the .NET 8 upgrade will be merged into the v6 branch,
      # and it may take some time for that to become stable.
      # However, the patches cleanly apply to v5 as well.
      (fetchpatch {
        name = "dotnet8-compatibility";
        url = "https://github.com/Radarr/Radarr/commit/490891c63de589604bdc3373cfc85068c3826648.patch";
        hash = "sha256-SCP7MPUkEZLSrls8ouekSXpXdgAJTwNFPirHjaMkQ6s=";
      })
      (fetchpatch {
        name = "dotnet8-darwin-compatibility";
        url = "https://github.com/Radarr/Radarr/commit/f38a129289c49a242d8901dc2f041f9dc8bfc303.patch";
        hash = "sha256-SAMUHqlSj8FPq20wY8NWbRytVZXTPtMXMfM3CoM8kSA=";
      })
    ];
  };
  rid = dotnetCorePackages.systemToDotnetRid stdenvNoCC.hostPlatform.system;
in
buildDotnetModule {
  pname = "radarr";
  inherit version src;

  strictDeps = true;
  nativeBuildInputs = [
    nodejs
    yarn
    prefetch-yarn-deps
    fixup-yarn-lock
  ];

  yarnOfflineCache = fetchYarnDeps {
    yarnLock = "${src}/yarn.lock";
    hash = "sha256-WFILG6nLEc8WO0j0CKH7RcX1++ucVOCge/UKcpHj87A=";
  };

  ffprobe = lib.optionalDrvAttr withFFmpeg (lib.getExe' servarr-ffmpeg "ffprobe");

  postConfigure = ''
    yarn config --offline set yarn-offline-mirror "$yarnOfflineCache"
    fixup-yarn-lock yarn.lock
    yarn install --offline --frozen-lockfile --ignore-platform --ignore-scripts --no-progress --non-interactive
    patchShebangs --build node_modules
  '';
  postBuild = ''
    yarn --offline run build --env production
  '';
  postInstall =
    lib.optionalString withFFmpeg ''
      rm -- "$out/lib/radarr/ffprobe"
      ln -s -- "$ffprobe" "$out/lib/radarr/ffprobe"
    ''
    + ''
      cp -a -- _output/UI "$out/lib/radarr/UI"
    '';

  nugetDeps = ./deps.json;

  runtimeDeps = [ sqlite ];

  dotnet-sdk = dotnetCorePackages.sdk_8_0;
  dotnet-runtime = dotnetCorePackages.aspnetcore_8_0;

  doCheck = true;

  __darwinAllowLocalNetworking = true; # for tests

  __structuredAttrs = true; # for Copyright property that contains spaces

  executables = [ "Radarr" ];

  projectFile = [
    "src/NzbDrone.Console/Radarr.Console.csproj"
    "src/NzbDrone.Mono/Radarr.Mono.csproj"
  ];

  testProjectFile = [
    "src/NzbDrone.Api.Test/Radarr.Api.Test.csproj"
    "src/NzbDrone.Common.Test/Radarr.Common.Test.csproj"
    "src/NzbDrone.Core.Test/Radarr.Core.Test.csproj"
    "src/NzbDrone.Host.Test/Radarr.Host.Test.csproj"
    "src/NzbDrone.Libraries.Test/Radarr.Libraries.Test.csproj"
    "src/NzbDrone.Mono.Test/Radarr.Mono.Test.csproj"
    "src/NzbDrone.Test.Common/Radarr.Test.Common.csproj"
  ];

  dotnetFlags = [
    "--property:TargetFramework=net8.0"
    "--property:EnableAnalyzers=false"
    "--property:SentryUploadSymbols=false" # Fix Sentry upload failed warnings
    # Override defaults in src/Directory.Build.props that use current time.
    "--property:Copyright=Copyright 2014-2025 radarr.video (GNU General Public v3)"
    "--property:AssemblyVersion=${version}"
    "--property:AssemblyConfiguration=master"
    "--property:RuntimeIdentifier=${rid}"
  ];

  # Skip manual, integration, automation and platform-dependent tests.
  testFilters =
    [
      "TestCategory!=ManualTest"
      "TestCategory!=IntegrationTest"
      "TestCategory!=AutomationTest"

      # makes real HTTP requests
      "FullyQualifiedName!~NzbDrone.Core.Test.UpdateTests.UpdatePackageProviderFixture"
    ]
    ++ lib.optionals stdenvNoCC.buildPlatform.isDarwin [
      # fails on macOS
      "FullyQualifiedName!~NzbDrone.Core.Test.Http.HttpProxySettingsProviderFixture"
    ];

  disabledTests =
    [
      # setgid tests
      "NzbDrone.Mono.Test.DiskProviderTests.DiskProviderFixture.should_preserve_setgid_on_set_folder_permissions"
      "NzbDrone.Mono.Test.DiskProviderTests.DiskProviderFixture.should_clear_setgid_on_set_folder_permissions"

      # we do not set application data directory during tests (i.e. XDG data directory)
      "NzbDrone.Mono.Test.DiskProviderTests.FreeSpaceFixture.should_return_free_disk_space"
      "NzbDrone.Common.Test.ServiceFactoryFixture.event_handlers_should_be_unique"

      # attempts to read /etc/*release and fails since it does not exist
      "NzbDrone.Mono.Test.EnvironmentInfo.ReleaseFileVersionAdapterFixture.should_get_version_info"

      # fails to start test dummy because it cannot locate .NET runtime for some reason
      "NzbDrone.Common.Test.ProcessProviderFixture.should_be_able_to_start_process"
      "NzbDrone.Common.Test.ProcessProviderFixture.exists_should_find_running_process"
      "NzbDrone.Common.Test.ProcessProviderFixture.kill_all_should_kill_all_process_with_name"
    ]
    ++ lib.optionals stdenvNoCC.buildPlatform.isDarwin [
      # flaky on darwin
      "NzbDrone.Core.Test.NotificationTests.TraktServiceFixture.should_add_collection_movie_if_valid_mediainfo"
      "NzbDrone.Core.Test.NotificationTests.TraktServiceFixture.should_format_audio_channels_to_one_decimal_when_adding_collection_movie"
    ];

  passthru = {
    tests = {
      inherit (nixosTests) radarr;
    };

    updateScript = writers.writePython3 "radarr-updater" {
      libraries = with python3Packages; [ requests ];
      flakeIgnore = [ "E501" ];
      makeWrapperArgs = [
        "--prefix"
        "PATH"
        ":"
        (lib.makeBinPath [
          nix
          prefetch-yarn-deps
        ])
      ];
    } ./update.py;
  };

  meta = {
    description = "Usenet/BitTorrent movie downloader";
    homepage = "https://radarr.video";
    changelog = "https://github.com/Radarr/Radarr/releases/tag/v${version}";
    license = lib.licenses.gpl3Only;
    maintainers = with lib.maintainers; [
      edwtjo
      purcell
      nyanloutre
    ];
    mainProgram = "Radarr";
    # platforms inherited from dotnet-sdk.
  };
}
+182 −0
Original line number Diff line number Diff line
import json
import os
import pathlib
import requests
import shutil
import subprocess
import sys
import tempfile


def replace_in_file(file_path, replacements):
    file_contents = pathlib.Path(file_path).read_text()
    for old, new in replacements.items():
        if old == new:
            continue
        updated_file_contents = file_contents.replace(old, new)
        # A dumb way to check that we’ve actually replaced the string.
        if file_contents == updated_file_contents:
            print(f"no string to replace: {old}{new}", file=sys.stderr)
            sys.exit(1)
        file_contents = updated_file_contents
    with tempfile.NamedTemporaryFile(mode="w") as t:
        t.write(file_contents)
        t.flush()
        shutil.copyfile(t.name, file_path)


def nix_hash_to_sri(hash):
    return subprocess.run(
        [
            "nix",
            "--extra-experimental-features", "nix-command",
            "hash",
            "to-sri",
            "--type", "sha256",
            "--",
            hash,
        ],
        stdout=subprocess.PIPE,
        text=True,
        check=True,
    ).stdout.rstrip()


nixpkgs_path = "."
attr_path = os.getenv("UPDATE_NIX_ATTR_PATH", "radarr")

package_attrs = json.loads(subprocess.run(
    [
        "nix",
        "--extra-experimental-features", "nix-command",
        "eval",
        "--json",
        "--file", nixpkgs_path,
        "--apply", """p: {
          dir = builtins.dirOf p.meta.position;
          version = p.version;
          sourceHash = p.src.src.outputHash;
          yarnHash = p.yarnOfflineCache.outputHash;
        }""",
        "--",
        attr_path,
    ],
    stdout=subprocess.PIPE,
    text=True,
    check=True,
).stdout)

old_version = package_attrs["version"]
new_version = old_version

# Note that we use Radarr API instead of GitHub to fetch latest stable release.
# This corresponds to the Updates tab in the web UI. See also
# https://github.com/Radarr/Radarr/blob/edec432244933a2143c5d13c71de7eb210434e7b/src/NzbDrone.Core/Update/UpdatePackageProvider.cs
# https://github.com/Radarr/Radarr/blob/edec432244933a2143c5d13c71de7eb210434e7b/src/NzbDrone.Common/Cloud/RadarrCloudRequestBuilder.cs
version_update = requests.get(
    f"https://radarr.servarr.com/v1/update/master?version={old_version}&includeMajorVersion=true",
).json()
if version_update["available"]:
    new_version = version_update["updatePackage"]["version"]

if new_version == old_version:
    sys.exit()

source_nix_hash, source_store_path = subprocess.run(
    [
        "nix-prefetch-url",
        "--name", "source",
        "--unpack",
        "--print-path",
        f"https://github.com/Radarr/Radarr/archive/v{new_version}.tar.gz",
    ],
    stdout=subprocess.PIPE,
    text=True,
    check=True,
).stdout.rstrip().split("\n")

old_source_hash = package_attrs["sourceHash"]
new_source_hash = nix_hash_to_sri(source_nix_hash)

package_dir = package_attrs["dir"]
package_file_name = "package.nix"
deps_file_name = "deps.json"

# To update deps.nix, we copy the package to a temporary directory and run
# passthru.fetch-deps script there.
with tempfile.TemporaryDirectory() as work_dir:
    package_file = os.path.join(work_dir, package_file_name)
    deps_file = os.path.join(work_dir, deps_file_name)

    shutil.copytree(package_dir, work_dir, dirs_exist_ok=True)

    replace_in_file(package_file, {
        # NB unlike hashes, versions are likely to be used in code or comments.
        # Try to be more specific to avoid false positive matches.
        f"version = \"{old_version}\"": f"version = \"{new_version}\"",
        old_source_hash: new_source_hash,
    })

    # We need access to the patched and updated src to get the patched
    # `yarn.lock`.
    patched_src = os.path.join(work_dir, "patched-src")
    subprocess.run(
        [
            "nix",
            "--extra-experimental-features", "nix-command",
            "build",
            "--impure",
            "--nix-path", "",
            "--include", f"nixpkgs={nixpkgs_path}",
            "--include", f"package={package_file}",
            "--expr", "(import <nixpkgs> { }).callPackage <package> { }",
            "--out-link", patched_src,
            "src",
        ],
        check=True,
    )
    old_yarn_hash = package_attrs["yarnHash"]
    new_yarn_hash = nix_hash_to_sri(subprocess.run(
        [
            "prefetch-yarn-deps",
            # does not support "--" separator :(
            # Also --verbose writes to stdout, yikes.
            os.path.join(patched_src, "yarn.lock"),
        ],
        stdout=subprocess.PIPE,
        text=True,
        check=True,
    ).stdout.rstrip())

    replace_in_file(package_file, {
        old_yarn_hash: new_yarn_hash,
    })

    # Generate nuget-to-json dependency lock file.
    fetch_deps = os.path.join(work_dir, "fetch-deps")
    subprocess.run(
        [
            "nix",
            "--extra-experimental-features", "nix-command",
            "build",
            "--impure",
            "--nix-path", "",
            "--include", f"nixpkgs={nixpkgs_path}",
            "--include", f"package={package_file}",
            "--expr", "(import <nixpkgs> { }).callPackage <package> { }",
            "--out-link", fetch_deps,
            "passthru.fetch-deps",
        ],
        check=True,
    )
    subprocess.run(
        [
            fetch_deps,
            deps_file,
        ],
        stdout=subprocess.DEVNULL,
        check=True,
    )

    shutil.copy(deps_file, os.path.join(package_dir, deps_file_name))
    shutil.copy(package_file, os.path.join(package_dir, package_file_name))

pkgs/servers/radarr/default.nix

deleted100644 → 0
+0 −94
Original line number Diff line number Diff line
{
  lib,
  stdenv,
  fetchurl,
  mono,
  libmediainfo,
  sqlite,
  curl,
  makeWrapper,
  icu,
  dotnet-runtime,
  openssl,
  nixosTests,
  zlib,
}:

let
  os = if stdenv.hostPlatform.isDarwin then "osx" else "linux";
  arch =
    {
      x86_64-linux = "x64";
      aarch64-linux = "arm64";
      x86_64-darwin = "x64";
      aarch64-darwin = "arm64";
    }
    ."${stdenv.hostPlatform.system}" or (throw "Unsupported system: ${stdenv.hostPlatform.system}");

  hash =
    {
      x64-linux_hash = "sha256-rHm2qDBDBPioAyN3SYw1CbCTDBA5PhF72Yd/LcpXGbI=";
      arm64-linux_hash = "sha256-ukwLekQ5kI7eXdydHXDev1WkISHR2vUQGtNd0njWyy0=";
      x64-osx_hash = "sha256-0ZzGcfMl3Q3vLSdN0j8B8NL1dQLvJn/lqKyprguexQI=";
      arm64-osx_hash = "sha256-aM9bmPW6Vv2D6lIKfT5+uuUXfq/xqxNuHAysEYUFzt4=";
    }
    ."${arch}-${os}_hash";

in
stdenv.mkDerivation rec {
  pname = "radarr";
  version = "5.25.0.10024";

  src = fetchurl {
    url = "https://github.com/Radarr/Radarr/releases/download/v${version}/Radarr.master.${version}.${os}-core-${arch}.tar.gz";
    sha256 = hash;
  };

  nativeBuildInputs = [ makeWrapper ];

  installPhase = ''
    runHook preInstall

    mkdir -p $out/{bin,share/${pname}-${version}}
    cp -r * $out/share/${pname}-${version}/.

    makeWrapper "${dotnet-runtime}/bin/dotnet" $out/bin/Radarr \
      --add-flags "$out/share/${pname}-${version}/Radarr.dll" \
      --prefix LD_LIBRARY_PATH : ${
        lib.makeLibraryPath [
          curl
          sqlite
          libmediainfo
          mono
          openssl
          icu
          zlib
        ]
      }

    runHook postInstall
  '';

  passthru = {
    updateScript = ./update.sh;
    tests.smoke-test = nixosTests.radarr;
  };

  meta = with lib; {
    description = "Usenet/BitTorrent movie downloader";
    homepage = "https://radarr.video/";
    changelog = "https://github.com/Radarr/Radarr/releases/tag/v${version}";
    license = licenses.gpl3Only;
    maintainers = with maintainers; [
      edwtjo
      purcell
    ];
    mainProgram = "Radarr";
    platforms = [
      "x86_64-linux"
      "aarch64-linux"
      "x86_64-darwin"
      "aarch64-darwin"
    ];
  };
}

pkgs/servers/radarr/update.sh

deleted100755 → 0
+0 −43
Original line number Diff line number Diff line
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p curl gnused nix-prefetch jq

set -e

dirname="$(dirname "$0")"

updateHash()
{
    version=$1
    arch=$2
    os=$3

    hashKey="${arch}-${os}_hash"

    url="https://github.com/Radarr/Radarr/releases/download/v$version/Radarr.master.$version.$os-core-$arch.tar.gz"
    hash=$(nix-prefetch-url --type sha256 $url)
    sriHash="$(nix hash to-sri --type sha256 $hash)"

    sed -i "s|$hashKey = \"[a-zA-Z0-9\/+-=]*\";|$hashKey = \"$sriHash\";|g" "$dirname/default.nix"
}

updateVersion()
{
    sed -i "s/version = \"[0-9.]*\";/version = \"$1\";/g" "$dirname/default.nix"
}

currentVersion=$(cd $dirname && nix eval --raw -f ../../.. radarr.version)

latestTag=$(curl https://api.github.com/repos/Radarr/Radarr/releases/latest | jq -r ".tag_name")
latestVersion="$(expr $latestTag : 'v\(.*\)')"

if [[ "$currentVersion" == "$latestVersion" ]]; then
    echo "Radarr is up-to-date: ${currentVersion}"
    exit 0
fi

updateVersion $latestVersion

updateHash $latestVersion x64 linux
updateHash $latestVersion arm64 linux
updateHash $latestVersion x64 osx
updateHash $latestVersion arm64 osx
Loading