Skip to content

curseforge-fix-perms

Module: pkgs/curseforge-fix-perms.nix

Gives back the exec bit that the CurseForge extractor drops on everything it unpacks under minecraft/Install. It fixes an APP BUG, not a NixOS quirk: on no distro would those binaries run.

What happens

Measured on 15/08/2026, agent 1.316.0-37372. The app unpacks what it downloads with a .NET extractor that does NOT preserve permissions, so every file lands rw-r--r--. Whatever the app later tries to exec dies with Permission denied, and the app never says so: it fails to map its own internal error (Invalid enum value: General, in background.js) and falls back to the generic red banner, "An unexpected error occurred. Operation failed.". That banner points at nothing, which is the expensive part.

It does not heal itself either. Reinstalling the JRE to fix it fails at The file '.../Jre_21/NOTICE' already exists., because the extractor does not overwrite, so the "Retry" button loops forever without moving.

It was born as curseforge-fix-java, and the name described a symptom

On 14/08/2026 the visible damage was the JRE the app downloads (OpenJDK21U-jre_x64_linux_hotspot_21.0.4_7.tar.gz, into Install/java/): its 6 bin/ binaries and 37 .so came out 644, the first java -version died with System.ComponentModel.Win32Exception, and the app concluded, to my face, "Java Runtime Environment is missing or out of date". So the script covered Install/java and nothing else.

On 15/08/2026 Minecraft still refused to open, and the agent log named a different victim:

[Radiuminator] Failed to launch Minecraft instance: d967e030-...
  An error occurred trying to start process
  '.../Documents/curseforge/minecraft/Install/minecraft-launcher'
  with working directory '.../Install'. Permission denied.

A sweep of Install/ by ELF magic byte found 115 ELF files still at 644:

Tree Files What it is
runtime/ 68 the JRE of the VANILLA launcher, the one that actually runs the game
natives/ 31 lwjgl/openal per modloader
launcher/ 8 the launcher CEF
bin/ 6 natives per version
webcache2/ 1 widevine
minecraft-launcher 1 itself

And java/ was the ONLY correct tree, 133 of 133, precisely because that was what the old script covered. The extractor loses the bit on EVERYTHING, so the fix follows it there.

Why ELF magic byte and not a list of names

The java-only version matched -path '*/bin/*' -o -name '*.so'. The tree grows on its own: each new MC version brings a bin/<hash>/, each modloader brings a natives/<name>-<version>/, and a name list has to guess the next binary's name. Reading 4 bytes answers it without guessing, and bash reads them itself, so there is no file and no fork per candidate.

Why assets/ is pruned

It is the only subtree that grows without bound (8879 files with a single modpack installed, and it multiplies per MC version), and it holds Mojang's content-addressed blobs, objects/<2 hex>/<sha1>, which are textures, sounds and lang files. Nothing there is ever executed. Pruning it takes the sweep from ~1.0s to ~0.2s on EVERY activation, which is what pays for the assumption.

Why Instances/ is out of scope

There are two 644 .so in there today (libEffekseerNativeForJava.so and epicfight/.../ServerCommunicationHelper.so). Those are unpacked from their own jars by the MODS, at runtime, 644 on every distro, and dlopen does not look at the exec bit. That is not a lost bit, it is how those mods ship. Touching them would be fighting the mod on every launch.

The .so under Install/ do get +x for the opposite reason: dlopen does not need it there either, but the original tarballs ship them 755, so restoring what the extractor lost is more defensible than judging one by one which ones would load anyway.

Do not try to fix it with declarative Java

That is the obvious attempt and the wrong one. Putting java in the FHS PATH does nothing, because the app only consults the JRE it manages itself. With three JREs installed the agent log went on citing ITS java 18 times and ours ZERO times. That is why pkgs/curseforge.nix has no Java in it, and why this script exists.

Where it runs

In the home-manager activation (home/apps/curseforge.nix), on every rebuild, and by hand when the app downloads something new in the middle of a session. It is idempotent: with nothing to fix it writes nothing and says nothing.