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