update/navibeat
1{
2 lib,
3 fetchFromGitHub,
4 fetchurl,
5 stdenv,
6 python3,
7 python3Packages,
8 wineWow64Packages,
9 unzip,
10 makeWrapper,
11 nix-update-script,
12 protontricks,
13}: let
14 version = "7.0.3";
15
16 mo2-lint-src = fetchFromGitHub {
17 owner = "Furglitch";
18 repo = "modorganizer2-linux-installer";
19 tag = version;
20 hash = "sha256-qybjSUmfVjw9NrRFbPClKGOYvCT6llcoLJ6jK/59J34=";
21 };
22
23 python-embed = fetchurl {
24 url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0-embed-amd64.zip";
25 hash = "sha256-AcMtBzdDIkCtzwu8HTIyfwl206HhQnd0vI/ryPHAMRE=";
26 };
27
28 get-pip-script = fetchurl {
29 url = "https://raw.githubusercontent.com/pypa/get-pip/refs/tags/26.1.1/public/get-pip.py";
30 hash = "sha256-ZpBLzLh442PbYjbqkA5pNeUH3LiH6fF49iEu3+f0anY=";
31 };
32
33 windows-python-deps = stdenv.mkDerivation {
34 name = "mo2-win-pip-wheels";
35
36 outputHashMode = "recursive";
37 outputHashAlgo = "sha256";
38 outputHash = "sha256-n9/lI0r3vi0KzkXlkKImOLBOBJu8OT2lYSr/l2gY+dI=";
39
40 nativeBuildInputs = [python3 python3Packages.pip];
41
42 buildCommand = ''
43 mkdir -p $out
44 export PIP_CACHE_DIR=$TMPDIR/pip-cache
45
46 pip download \
47 --platform win_amd64 \
48 --python-version 3.13 \
49 --only-binary=:all: \
50 --dest $out \
51 altgraph==0.17.5 \
52 colorama==0.4.6 \
53 loguru==0.7.3 \
54 packaging==26.2 \
55 pefile==2024.8.26 \
56 pip==26.1.2 \
57 pyinstaller==6.21.0 \
58 pyinstaller_hooks_contrib==2026.6 \
59 pywin32-ctypes==0.2.3 \
60 pyyaml==6.0.3 \
61 setuptools==82.0.1 \
62 win32-setctime==1.2.0
63 '';
64 };
65
66 mo2-redirector = stdenv.mkDerivation {
67 name = "mo2-redirector.exe";
68
69 src = mo2-lint-src;
70
71 nativeBuildInputs = [wineWow64Packages.stable unzip];
72
73 buildPhase = ''
74 runHook preBuild
75
76 export XDG_CACHE_HOME=$TMPDIR/.cache
77 export FONTCONFIG_CACHE=$XDG_CACHE_HOME/fontconfig
78 mkdir -p $FONTCONFIG_CACHE
79
80 export WINEPREFIX=$TMPDIR/wine
81 export WINEARCH=win64
82 export WINEDEBUG=-all
83
84 wineboot --init
85 wineserver -w
86
87 mkdir -p $WINEPREFIX/drive_c/python313
88
89 unzip -q ${python-embed} -d $WINEPREFIX/drive_c/python313
90 sed -i 's/#import site/import site/' $WINEPREFIX/drive_c/python313/python313._pth
91
92 wine "C:\\python313\\python.exe" Z:${get-pip-script} --no-index \
93 --find-links=Z:${windows-python-deps} \
94 --no-warn-script-location
95
96 wine "C:\\python313\\python.exe" -m pip install --no-index \
97 --find-links=Z:${windows-python-deps} \
98 pyinstaller loguru pyyaml
99
100 wine "C:\\python313\\python.exe" -m PyInstaller \
101 --onefile \
102 --noconsole \
103 --name mo2-redirector.exe \
104 --paths src \
105 --hidden-import loguru \
106 --hidden-import yaml \
107 --hidden-import configparser \
108 --add-data "configs;cfg" \
109 --icon Z:$PWD/.github/README/logo.ico \
110 Z:$PWD/src/redirector/__init__.py
111
112 runHook postBuild
113 '';
114
115 installPhase = ''
116 runHook preInstall
117
118 mkdir -p $out/dist
119 cp dist/mo2-redirector.exe $out/dist/
120
121 runHook postInstall
122 '';
123 };
124
125 runtime-python-deps = python3.withPackages (ps:
126 with ps; [
127 certifi
128 click
129 python-dotenv
130 inquirerpy
131 loguru
132 packaging
133 patool
134 psutil
135 pydantic
136 pyyaml
137 requests
138 send2trash
139 websockets
140 (python3Packages.toPythonModule protontricks)
141 ]);
142
143 mo2-lint = stdenv.mkDerivation (finalAttrs: {
144 name = "mo2-lint";
145 pname = "mo2-lint";
146
147 inherit version;
148 src = mo2-lint-src;
149
150 passthru.updateScript = nix-update-script {};
151
152 nativeBuildInputs = [makeWrapper];
153
154 postPatch = ''
155 # Fix fallback path: upstream uses Path(__file__).resolve() which points
156 # to the py file itself, but joinpath needs a directory. Add .parent so the
157 # fallback resolves to src/mo2-lint/ where the cfg/dist symlinks live.
158 # `|| true` for forward-compat if upstream fixes this in a future release.
159 sed -i 's/Path(__file__)/Path(__file__).parent/g' src/mo2-lint/util/internal_file.py || true
160
161 # Nix store files have 0o444 (read-only). When copy2 places config files
162 # from the store into ~/.config/mo2-lint/, they must be made writable.
163 sed -i 's/copy2(src, dest)/copy2(src, dest); import os; os.chmod(dest, 0o644)/g' src/mo2-lint/__init__.py
164
165 # Same read-only Nix store issue for downloaded mod archives etc.
166 sed -i 's/copy2(internal_path, output)/copy2(internal_path, output); import os; os.chmod(output, 0o644)/g' src/mo2-lint/util/nexus/install_handler.py
167
168 # click uses sys.argv[0] for prog_name in help/version output. Set it to
169 # the package name instead of a nix store path or plain "python".
170 sed -i '1s/^/import sys; sys.argv[0] = "${finalAttrs.pname}"\n/' src/mo2-lint/__init__.py
171
172 # pre_init() is called at module import time (before CLI arg parsing) and
173 # hardcodes TRACE, dumping ~40 lines of config parse dumps and trace
174 # messages. Bump to WARNING so only real issues show during startup.
175 sed -i 's/add_loggers(log_level="TRACE", script="mo2-lint", process="pre-check")/add_loggers(log_level="WARNING", script="mo2-lint", process="pre-check")/' src/mo2-lint/__init__.py
176 '';
177
178 buildPhase = ''
179 runHook preBuild
180
181 mkdir -p dist
182 ln -s ${mo2-redirector}/dist/mo2-redirector.exe dist/
183
184 runHook postBuild
185 '';
186
187 installPhase = ''
188 runHook preInstall
189
190 mkdir -p $out/share/mo2-lint
191 cp -r src configs dist $out/share/mo2-lint/
192 ln -s $out/share/mo2-lint/configs $out/share/mo2-lint/src/mo2-lint/util/cfg
193 ln -s $out/share/mo2-lint/dist $out/share/mo2-lint/src/mo2-lint/util/dist
194
195 mkdir -p $out/bin
196 makeWrapper ${runtime-python-deps}/bin/python $out/bin/${finalAttrs.pname} \
197 --add-flags "$out/share/mo2-lint/src/mo2-lint/__init__.py" \
198 --prefix PYTHONPATH : "$out/share/mo2-lint/src" \
199 --set MEIPASS "$out/share/mo2-lint" \
200 --set _MEIPASS2 "$out/share/mo2-lint" \
201 --set-default LOGURU_LEVEL "INFO"
202 makeWrapper ${runtime-python-deps}/bin/python $out/share/mo2-lint/dist/nxm-handler \
203 --add-flags "$out/share/mo2-lint/src/nxm-handler/__init__.py" \
204 --prefix PYTHONPATH : "$out/share/mo2-lint/src" \
205 --set MEIPASS "$out/share/mo2-lint" \
206 --set _MEIPASS2 "$out/share/mo2-lint" \
207 --set-default LOGURU_LEVEL "INFO"
208
209 runHook postInstall
210 '';
211
212 meta = {
213 description = "An easy-to-use Mod Organizer 2 installer for Linux, rewrited in Python.";
214 homepage = "https://github.com/Furglitch/modorganizer2-linux-installer";
215 licence = lib.licenses.gpl3Only;
216 platforms = ["x86_64-linux"];
217 };
218 });
219in
220 mo2-lint