Skip to content

Commit 8cde6e8

Browse files
authored
radarr: build from source (#384974)
2 parents a03f275 + 01b8100 commit 8cde6e8

File tree

9 files changed

+2211
-171
lines changed

9 files changed

+2211
-171
lines changed

pkgs/by-name/ra/radarr/deps.json

Lines changed: 1687 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkgs/by-name/ra/radarr/package.nix

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
{
2+
lib,
3+
stdenvNoCC,
4+
fetchFromGitHub,
5+
buildDotnetModule,
6+
dotnetCorePackages,
7+
sqlite,
8+
withFFmpeg ? true, # replace bundled ffprobe binary with symlink to ffmpeg package.
9+
servarr-ffmpeg,
10+
fetchYarnDeps,
11+
yarn,
12+
fixup-yarn-lock,
13+
nodejs,
14+
nixosTests,
15+
# update script
16+
writers,
17+
python3Packages,
18+
nix,
19+
prefetch-yarn-deps,
20+
fetchpatch,
21+
applyPatches,
22+
}:
23+
let
24+
version = "5.26.2.10099";
25+
# The dotnet8 compatibility patches also change `yarn.lock`, so we must pass
26+
# the already patched lockfile to `fetchYarnDeps`.
27+
src = applyPatches {
28+
src = fetchFromGitHub {
29+
owner = "Radarr";
30+
repo = "Radarr";
31+
tag = "v${version}";
32+
hash = "sha256-7tU9oxE1F/dcR5bwb/qHyux3WA6lEwdozLloDgOMVbU=";
33+
};
34+
postPatch = ''
35+
mv src/NuGet.config NuGet.Config
36+
'';
37+
patches = lib.optionals (lib.versionOlder version "6.0") [
38+
# See https://github.com/Radarr/Radarr/pull/11064
39+
# Unfortunately, the .NET 8 upgrade will be merged into the v6 branch,
40+
# and it may take some time for that to become stable.
41+
# However, the patches cleanly apply to v5 as well.
42+
(fetchpatch {
43+
name = "dotnet8-compatibility";
44+
url = "https://github.com/Radarr/Radarr/commit/490891c63de589604bdc3373cfc85068c3826648.patch";
45+
hash = "sha256-SCP7MPUkEZLSrls8ouekSXpXdgAJTwNFPirHjaMkQ6s=";
46+
})
47+
(fetchpatch {
48+
name = "dotnet8-darwin-compatibility";
49+
url = "https://github.com/Radarr/Radarr/commit/f38a129289c49a242d8901dc2f041f9dc8bfc303.patch";
50+
hash = "sha256-SAMUHqlSj8FPq20wY8NWbRytVZXTPtMXMfM3CoM8kSA=";
51+
})
52+
];
53+
};
54+
rid = dotnetCorePackages.systemToDotnetRid stdenvNoCC.hostPlatform.system;
55+
in
56+
buildDotnetModule {
57+
pname = "radarr";
58+
inherit version src;
59+
60+
strictDeps = true;
61+
nativeBuildInputs = [
62+
nodejs
63+
yarn
64+
prefetch-yarn-deps
65+
fixup-yarn-lock
66+
];
67+
68+
yarnOfflineCache = fetchYarnDeps {
69+
yarnLock = "${src}/yarn.lock";
70+
hash = "sha256-WFILG6nLEc8WO0j0CKH7RcX1++ucVOCge/UKcpHj87A=";
71+
};
72+
73+
ffprobe = lib.optionalDrvAttr withFFmpeg (lib.getExe' servarr-ffmpeg "ffprobe");
74+
75+
postConfigure = ''
76+
yarn config --offline set yarn-offline-mirror "$yarnOfflineCache"
77+
fixup-yarn-lock yarn.lock
78+
yarn install --offline --frozen-lockfile --ignore-platform --ignore-scripts --no-progress --non-interactive
79+
patchShebangs --build node_modules
80+
'';
81+
postBuild = ''
82+
yarn --offline run build --env production
83+
'';
84+
postInstall =
85+
lib.optionalString withFFmpeg ''
86+
rm -- "$out/lib/radarr/ffprobe"
87+
ln -s -- "$ffprobe" "$out/lib/radarr/ffprobe"
88+
''
89+
+ ''
90+
cp -a -- _output/UI "$out/lib/radarr/UI"
91+
'';
92+
93+
nugetDeps = ./deps.json;
94+
95+
runtimeDeps = [ sqlite ];
96+
97+
dotnet-sdk = dotnetCorePackages.sdk_8_0;
98+
dotnet-runtime = dotnetCorePackages.aspnetcore_8_0;
99+
100+
doCheck = true;
101+
102+
__darwinAllowLocalNetworking = true; # for tests
103+
104+
__structuredAttrs = true; # for Copyright property that contains spaces
105+
106+
executables = [ "Radarr" ];
107+
108+
projectFile = [
109+
"src/NzbDrone.Console/Radarr.Console.csproj"
110+
"src/NzbDrone.Mono/Radarr.Mono.csproj"
111+
];
112+
113+
testProjectFile = [
114+
"src/NzbDrone.Api.Test/Radarr.Api.Test.csproj"
115+
"src/NzbDrone.Common.Test/Radarr.Common.Test.csproj"
116+
"src/NzbDrone.Core.Test/Radarr.Core.Test.csproj"
117+
"src/NzbDrone.Host.Test/Radarr.Host.Test.csproj"
118+
"src/NzbDrone.Libraries.Test/Radarr.Libraries.Test.csproj"
119+
"src/NzbDrone.Mono.Test/Radarr.Mono.Test.csproj"
120+
"src/NzbDrone.Test.Common/Radarr.Test.Common.csproj"
121+
];
122+
123+
dotnetFlags = [
124+
"--property:TargetFramework=net8.0"
125+
"--property:EnableAnalyzers=false"
126+
"--property:SentryUploadSymbols=false" # Fix Sentry upload failed warnings
127+
# Override defaults in src/Directory.Build.props that use current time.
128+
"--property:Copyright=Copyright 2014-2025 radarr.video (GNU General Public v3)"
129+
"--property:AssemblyVersion=${version}"
130+
"--property:AssemblyConfiguration=master"
131+
"--property:RuntimeIdentifier=${rid}"
132+
];
133+
134+
# Skip manual, integration, automation and platform-dependent tests.
135+
testFilters =
136+
[
137+
"TestCategory!=ManualTest"
138+
"TestCategory!=IntegrationTest"
139+
"TestCategory!=AutomationTest"
140+
141+
# makes real HTTP requests
142+
"FullyQualifiedName!~NzbDrone.Core.Test.UpdateTests.UpdatePackageProviderFixture"
143+
]
144+
++ lib.optionals stdenvNoCC.buildPlatform.isDarwin [
145+
# fails on macOS
146+
"FullyQualifiedName!~NzbDrone.Core.Test.Http.HttpProxySettingsProviderFixture"
147+
];
148+
149+
disabledTests =
150+
[
151+
# setgid tests
152+
"NzbDrone.Mono.Test.DiskProviderTests.DiskProviderFixture.should_preserve_setgid_on_set_folder_permissions"
153+
"NzbDrone.Mono.Test.DiskProviderTests.DiskProviderFixture.should_clear_setgid_on_set_folder_permissions"
154+
155+
# we do not set application data directory during tests (i.e. XDG data directory)
156+
"NzbDrone.Mono.Test.DiskProviderTests.FreeSpaceFixture.should_return_free_disk_space"
157+
"NzbDrone.Common.Test.ServiceFactoryFixture.event_handlers_should_be_unique"
158+
159+
# attempts to read /etc/*release and fails since it does not exist
160+
"NzbDrone.Mono.Test.EnvironmentInfo.ReleaseFileVersionAdapterFixture.should_get_version_info"
161+
162+
# fails to start test dummy because it cannot locate .NET runtime for some reason
163+
"NzbDrone.Common.Test.ProcessProviderFixture.should_be_able_to_start_process"
164+
"NzbDrone.Common.Test.ProcessProviderFixture.exists_should_find_running_process"
165+
"NzbDrone.Common.Test.ProcessProviderFixture.kill_all_should_kill_all_process_with_name"
166+
]
167+
++ lib.optionals stdenvNoCC.buildPlatform.isDarwin [
168+
# flaky on darwin
169+
"NzbDrone.Core.Test.NotificationTests.TraktServiceFixture.should_add_collection_movie_if_valid_mediainfo"
170+
"NzbDrone.Core.Test.NotificationTests.TraktServiceFixture.should_format_audio_channels_to_one_decimal_when_adding_collection_movie"
171+
];
172+
173+
passthru = {
174+
tests = {
175+
inherit (nixosTests) radarr;
176+
};
177+
178+
updateScript = writers.writePython3 "radarr-updater" {
179+
libraries = with python3Packages; [ requests ];
180+
flakeIgnore = [ "E501" ];
181+
makeWrapperArgs = [
182+
"--prefix"
183+
"PATH"
184+
":"
185+
(lib.makeBinPath [
186+
nix
187+
prefetch-yarn-deps
188+
])
189+
];
190+
} ./update.py;
191+
};
192+
193+
meta = {
194+
description = "Usenet/BitTorrent movie downloader";
195+
homepage = "https://radarr.video";
196+
changelog = "https://github.com/Radarr/Radarr/releases/tag/v${version}";
197+
license = lib.licenses.gpl3Only;
198+
maintainers = with lib.maintainers; [
199+
edwtjo
200+
purcell
201+
nyanloutre
202+
];
203+
mainProgram = "Radarr";
204+
# platforms inherited from dotnet-sdk.
205+
};
206+
}

pkgs/by-name/ra/radarr/update.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import json
2+
import os
3+
import pathlib
4+
import requests
5+
import shutil
6+
import subprocess
7+
import sys
8+
import tempfile
9+
10+
11+
def replace_in_file(file_path, replacements):
12+
file_contents = pathlib.Path(file_path).read_text()
13+
for old, new in replacements.items():
14+
if old == new:
15+
continue
16+
updated_file_contents = file_contents.replace(old, new)
17+
# A dumb way to check that we’ve actually replaced the string.
18+
if file_contents == updated_file_contents:
19+
print(f"no string to replace: {old}{new}", file=sys.stderr)
20+
sys.exit(1)
21+
file_contents = updated_file_contents
22+
with tempfile.NamedTemporaryFile(mode="w") as t:
23+
t.write(file_contents)
24+
t.flush()
25+
shutil.copyfile(t.name, file_path)
26+
27+
28+
def nix_hash_to_sri(hash):
29+
return subprocess.run(
30+
[
31+
"nix",
32+
"--extra-experimental-features", "nix-command",
33+
"hash",
34+
"to-sri",
35+
"--type", "sha256",
36+
"--",
37+
hash,
38+
],
39+
stdout=subprocess.PIPE,
40+
text=True,
41+
check=True,
42+
).stdout.rstrip()
43+
44+
45+
nixpkgs_path = "."
46+
attr_path = os.getenv("UPDATE_NIX_ATTR_PATH", "radarr")
47+
48+
package_attrs = json.loads(subprocess.run(
49+
[
50+
"nix",
51+
"--extra-experimental-features", "nix-command",
52+
"eval",
53+
"--json",
54+
"--file", nixpkgs_path,
55+
"--apply", """p: {
56+
dir = builtins.dirOf p.meta.position;
57+
version = p.version;
58+
sourceHash = p.src.src.outputHash;
59+
yarnHash = p.yarnOfflineCache.outputHash;
60+
}""",
61+
"--",
62+
attr_path,
63+
],
64+
stdout=subprocess.PIPE,
65+
text=True,
66+
check=True,
67+
).stdout)
68+
69+
old_version = package_attrs["version"]
70+
new_version = old_version
71+
72+
# Note that we use Radarr API instead of GitHub to fetch latest stable release.
73+
# This corresponds to the Updates tab in the web UI. See also
74+
# https://github.com/Radarr/Radarr/blob/edec432244933a2143c5d13c71de7eb210434e7b/src/NzbDrone.Core/Update/UpdatePackageProvider.cs
75+
# https://github.com/Radarr/Radarr/blob/edec432244933a2143c5d13c71de7eb210434e7b/src/NzbDrone.Common/Cloud/RadarrCloudRequestBuilder.cs
76+
version_update = requests.get(
77+
f"https://radarr.servarr.com/v1/update/master?version={old_version}&includeMajorVersion=true",
78+
).json()
79+
if version_update["available"]:
80+
new_version = version_update["updatePackage"]["version"]
81+
82+
if new_version == old_version:
83+
sys.exit()
84+
85+
source_nix_hash, source_store_path = subprocess.run(
86+
[
87+
"nix-prefetch-url",
88+
"--name", "source",
89+
"--unpack",
90+
"--print-path",
91+
f"https://github.com/Radarr/Radarr/archive/v{new_version}.tar.gz",
92+
],
93+
stdout=subprocess.PIPE,
94+
text=True,
95+
check=True,
96+
).stdout.rstrip().split("\n")
97+
98+
old_source_hash = package_attrs["sourceHash"]
99+
new_source_hash = nix_hash_to_sri(source_nix_hash)
100+
101+
package_dir = package_attrs["dir"]
102+
package_file_name = "package.nix"
103+
deps_file_name = "deps.json"
104+
105+
# To update deps.nix, we copy the package to a temporary directory and run
106+
# passthru.fetch-deps script there.
107+
with tempfile.TemporaryDirectory() as work_dir:
108+
package_file = os.path.join(work_dir, package_file_name)
109+
deps_file = os.path.join(work_dir, deps_file_name)
110+
111+
shutil.copytree(package_dir, work_dir, dirs_exist_ok=True)
112+
113+
replace_in_file(package_file, {
114+
# NB unlike hashes, versions are likely to be used in code or comments.
115+
# Try to be more specific to avoid false positive matches.
116+
f"version = \"{old_version}\"": f"version = \"{new_version}\"",
117+
old_source_hash: new_source_hash,
118+
})
119+
120+
# We need access to the patched and updated src to get the patched
121+
# `yarn.lock`.
122+
patched_src = os.path.join(work_dir, "patched-src")
123+
subprocess.run(
124+
[
125+
"nix",
126+
"--extra-experimental-features", "nix-command",
127+
"build",
128+
"--impure",
129+
"--nix-path", "",
130+
"--include", f"nixpkgs={nixpkgs_path}",
131+
"--include", f"package={package_file}",
132+
"--expr", "(import <nixpkgs> { }).callPackage <package> { }",
133+
"--out-link", patched_src,
134+
"src",
135+
],
136+
check=True,
137+
)
138+
old_yarn_hash = package_attrs["yarnHash"]
139+
new_yarn_hash = nix_hash_to_sri(subprocess.run(
140+
[
141+
"prefetch-yarn-deps",
142+
# does not support "--" separator :(
143+
# Also --verbose writes to stdout, yikes.
144+
os.path.join(patched_src, "yarn.lock"),
145+
],
146+
stdout=subprocess.PIPE,
147+
text=True,
148+
check=True,
149+
).stdout.rstrip())
150+
151+
replace_in_file(package_file, {
152+
old_yarn_hash: new_yarn_hash,
153+
})
154+
155+
# Generate nuget-to-json dependency lock file.
156+
fetch_deps = os.path.join(work_dir, "fetch-deps")
157+
subprocess.run(
158+
[
159+
"nix",
160+
"--extra-experimental-features", "nix-command",
161+
"build",
162+
"--impure",
163+
"--nix-path", "",
164+
"--include", f"nixpkgs={nixpkgs_path}",
165+
"--include", f"package={package_file}",
166+
"--expr", "(import <nixpkgs> { }).callPackage <package> { }",
167+
"--out-link", fetch_deps,
168+
"passthru.fetch-deps",
169+
],
170+
check=True,
171+
)
172+
subprocess.run(
173+
[
174+
fetch_deps,
175+
deps_file,
176+
],
177+
stdout=subprocess.DEVNULL,
178+
check=True,
179+
)
180+
181+
shutil.copy(deps_file, os.path.join(package_dir, deps_file_name))
182+
shutil.copy(package_file, os.path.join(package_dir, package_file_name))

0 commit comments

Comments
 (0)