curseforge¶
Module: pkgs/curseforge.nix
The OFFICIAL Minecraft modpack app (Electron, by Overwolf). It replaced prismlauncher on
14/08/2026: Prism installs a CurseForge modpack by importing a .zip, without the library and
pack updating that are the whole reason the app exists.
It is NOT in nixpkgs (unfree and binary-only), so the repo repackages the official AppImage, the same vendored-binary pattern as claude-desktop.
Do not add Java here¶
It has been tried, and it is DEAD CONFIG (measured on 15/08/2026). The app DOWNLOADS and manages
its own JRE in ~/Documents/curseforge/minecraft/Install/java/, and that is the only one it
consults: with java in the FHS PATH and three JREs in /usr/lib/jvm, the agent log went on
citing ITS java 18 times and ours ZERO times. All three went out in the same commit that brought
them in.
The "Java Runtime Environment is missing" error is NOT fixed from here: the cause is PERMISSIONS,
see curseforge-fix-perms.
Shaders need zink, iris aborts the game¶
Turning on any Oculus shaderpack kills Minecraft with SIGABRT in under two minutes. There is no crash-report and no Java exception, and the log just cuts mid-line, because the abort comes from the video driver and not from the JVM. Measured on 15 and 16/08/2026, six coredumps.
All six carry the same stack: abort() reached from _iris_batch_flush, which in this release
build of Mesa 26.1.5 disassembles to a lone call abort@plt. That is the branch iris takes when
the kernel refuses the batch submission, and it takes the process down with no GL error to catch.
The call sites vary (iris_transfer_map while creating the pipeline, st_TexSubImage during
ordinary gameplay, iris_fence_flush on a context flush), which is what says compiling the
shaderpack is not special: any GPU work with shaders on eventually fails to submit.
The obvious suspects were measured, not guessed, and all cleared:
dmesgis clean, so no GPU hang and no reset on thexedriver.- The signal is SIGABRT, not the SIGKILL an OOM kill sends. earlyoom never fired.
vm.max_map_countis 1048576 against 29259 mappings, counted from thePT_LOADheaders of the coredump.- The card has 12 GiB of VRAM with ReBAR on.
- RAM is not it either, and this is what settles it: the zink run survives on 1274 MB of
MemAvailableat its lowest, tighter than any iris crash had.
MESA_LOADER_DRIVER_OVERRIDE=zink swaps only the broken half of the stack. Same Mesa, same GPU,
same xe kernel driver, but OpenGL is translated to Vulkan and leaves through ANV, where Intel
actually puts its effort, instead of iris, the legacy GL path that is barely exercised on
Battlemage. See gpu.md.
It goes in the derivation's profile and NOT in the .desktop because who needs the variable is
the JAVA the app spawns, not the Electron app. buildFHSEnv appends it to the /etc/profile of
the FHS and its init sources that file before exec, so every child inherits it. The scope stays
on this package on purpose: the rest of the system runs fine on iris and has no reason to carry a
workaround.
Why AppImage and not the .deb¶
Both sources exist and are the SAME release. Everything that matters here is a binary the app
DOWNLOADS at runtime (the JRE, the Forge installer, Minecraft itself), and none of that goes
through autoPatchelfHook, which only reaches what is in the store.
This system's programs.nix-ld does cover the loader (the /lib64/ld-linux here points at it),
but not each one's libraries. The buildFHSEnv from appimageTools solves both at once and also
puts /run/opengl-driver/lib in the ld.so.conf (through container-init.cc), so Minecraft
inherits the FHS and the Arc B580 driver. It is the FHS that holds this package up, not
patchelf.
The pointer URL¶
Overwolf only publishes curseforge-latest-linux.AppImage; there is no versioned URL (the
patterns -1.316.0-, ~37372 and latest.yml were all tested, all 404).
The hash in the module PINS the content, so the build is reproducible, but the day Overwolf
publishes the next version the fetch starts failing with a hash mismatch on a cold store, which is
exactly what VS Code's /latest/ caused in the CI. Who pays that price is
curseforge-bump, which runs on the update alias and rewrites
version and hash there. Do NOT swap it for lib.fakeHash or for a fetch with no hash (rule 13).
The internal auto-updater does not work¶
resources/app-update.yml (gitlab provider) cannot do anything: the store is read-only. Updating
is update/upgrade, like everything else.
Two details in the derivation¶
extract plus wrapAppImage, not wrapType2 (which does both at once):
extraInstallCommands needs to READ the extracted tree to pick up the .desktop and the icons,
and wrapType2 does not expose it.
The .desktop is upstream's, with only AppRun swapped for the FHS wrapper's name.
--no-sandboxis NOT needed here. Measured on 14/08/2026 runningbin/curseforge, which passes no flags at all: the app opens and loads the library normally, without the "SUID sandbox helper" this flag usually works around, because the bwrap frombuildFHSEnvalready gives Chromium the namespace it wants. It stays because it came from upstream and costs nothing; diverging from their.desktopwould need a reason, and there is none.%Uplus thex-scheme-handler/curseforge...MimeType entries are what make the "Install" button on the site open the app (a deep link). Declaring the scheme here is not enough: what says THIS.desktopis the default ishome/apps/curseforge.nix, and without that the LOGIN does not come back.
The version string comes from the X-AppImage-Version of the .desktop inside the AppImage.
The .deb of the same release says 1.316.0~37372-37372; the repeated .37372 is
electron-builder noise.
The user side¶
home/apps/curseforge.nix. The package (the official AppImage, repackaged) lives in
pkgs/curseforge.nix; this module is the home side.
It REPLACED prismlauncher on 14/08/2026: Prism imports a modpack .zip, but what keeps the
library and UPDATES the pack is the CurseForge app, which is the real use here.
And the swap SHRANK the system by 1.5 GiB, the opposite of what "native to Electron" suggests:
27.2 to 25.7 GiB, measured with nix store diff-closures. curseforge +340.2 MiB against
prismlauncher -17.6 MiB and openjdk (8, 17, 21 and 25, which the Prism wrapper bundled) -1.8 GiB.
The four JDKs left because NOBODY declares Java here: the provider is the app itself, which
downloads its own JRE.
Instances, mods and login are STATE (rule 6, so restic), not declaration: ~/.config/CurseForge/
holds config plus session, ~/Documents/curseforge/ holds the instances.
The activation runs curseforge-fix-perms idempotently rather than managing files: the app is the
owner of what it unpacks (rule 14), so Nix only undoes a known bit of damage. writeBoundary
because the package has to be in the profile first. It is ALSO on the PATH, because the download
that breaks can happen IN THE MIDDLE of a session, by which point the activation has already run;
then it is a matter of running it by hand and reopening the app, with no rebuild to wait for.
Why the schemes are declared here, and are not optional¶
The app tries to register itself as the handler for curseforge:// and cfauth:// at RUNTIME
(Electron's setAsDefaultProtocolClient), and that will NEVER work on this system:
~/.config/mimeapps.list is managed by home-manager and points into /nix/store, which is
read-only (rule 14, Nix is the owner). Measured on 14/08/2026, the app's log says exactly that on
startup:
[BackgroundController] Failed subscribing app protocol.
[LoginService] Failed to register login scheme 'cfauth'. This might create issues
with the login process..
And cfauth:// is no detail: it is the LOGIN callback, since the app opens the browser and waits
for the redirect back. With no handler, the login comes back to nothing.
The declarative association is the registration the app cannot make on its own. The package's
.desktop already declares the three schemes in MimeType; the module only says that IT is the
default. The three are cfauth (the login callback, the one that matters most), curseforge (the
"Install" button on the modpack site) and curseforge-checkout (buying a premium add-on).