{...}: { # Unraid USB Creator - writes Unraid OS straight to a USB stick, streaming the # image from unraid.net instead of making you download it first. # # Not in nixpkgs, and upstream ships an AppImage only, so this is a repack. # The AppImage is a Qt6/QML app (a fork of rpi-imager) built with linuxdeploy, # meaning it already vendors the entire Qt6 stack, xcb, icu, gnutls and krb5 in # usr/lib. We keep that bundle verbatim and only autoPatchelf it against the # handful of libraries linuxdeploy deliberately left out (libc, libstdc++, GL, # X11, fontconfig, ...). No FHS wrapper needed, so no 400MB buildFHSEnv closure. flake.nixosModules.unraid-usb-creator = { pkgs, lib, ... }: let pname = "unraid-usb-creator"; version = "1.1.0"; # unraid.net/download's "stable" link is a redirect to this GitHub asset. # Note the v1.1.0 tag is not returned by the releases API (the API only lists # the v2.0.x line), but the download URL is live and matches the checksum. appimage = pkgs.fetchurl { url = "https://github.com/unraid/usb-creator-next/releases/download/v${version}/${pname}-${version}.appimage"; hash = "sha256-NI/SMgOuzifvjhtK4Py96lbuI/uvJO0NiMr2zNtNSUk="; }; unraid-usb-creator = pkgs.stdenvNoCC.mkDerivation { inherit pname version; src = pkgs.appimageTools.extract { inherit pname version; src = appimage; }; nativeBuildInputs = with pkgs; [ autoPatchelfHook makeWrapper ]; # Only the libraries the AppImage does *not* vendor. Everything else # resolves inside $out/share/unraid-usb-creator/lib. buildInputs = with pkgs; [ e2fsprogs # libcom_err.so.2, pulled in by the vendored krb5 fontconfig freetype gmp # pulled in by the vendored nettle/gnutls libgpg-error libglvnd # libGL / libGLX / libEGL / libOpenGL libx11 # also provides libX11-xcb libxcb stdenv.cc.cc # libstdc++ / libgcc_s zlib ]; # Qt's openssl TLS backend resolves libssl/libcrypto with dlopen, so # autoPatchelf never sees the dependency. Without this the app cannot fetch # the release JSON or stream the image over HTTPS. # # This has to be appendRunpaths and NOT runtimeDependencies: in # auto-patchelf, `rpath += runtime_deps` lives inside the set-interpreter # branch, so it only ever reaches executables, while `rpath.extend( # append_rpaths)` runs for every patched file. The dlopen happens from # plugins/tls/libqopensslbackend.so, and glibc resolves dlopen against the # calling object's DT_RUNPATH - the wrapper binary's RUNPATH is not # consulted - so the path has to be on the plugin itself. appendRunpaths = ["${pkgs.openssl.out}/lib"]; installPhase = '' runHook preInstall # qt.conf next to the binary declares Prefix=../ with Plugins=plugins and # Qml2Imports=qml, so the usr/ layout has to be preserved as a unit. install -Dm755 usr/bin/unraid-usb-creator -t $out/share/${pname}/bin install -Dm444 usr/bin/qt.conf -t $out/share/${pname}/bin cp -r usr/lib usr/plugins usr/qml $out/share/${pname}/ cp -r usr/share/icons $out/share/ install -Dm444 usr/share/metainfo/*.xml -t $out/share/metainfo install -Dm444 usr/share/applications/*.desktop -t $out/share/applications # mkfs.fat is shelled out to when formatting the stick; syslinux is NOT # needed as a runtime dep because upstream statically vendors it. # # linuxdeploy only bundled the xcb platform plugin - there is no # libqwayland*.so anywhere in the AppImage - so this always runs through # XWayland under Hyprland. Pinning the platform avoids Qt first trying # wayland and falling back; --set-default rather than --set so it can # still be overridden if upstream ever ships the Wayland plugin. makeWrapper $out/share/${pname}/bin/unraid-usb-creator $out/bin/${pname} \ --prefix PATH : ${lib.makeBinPath (with pkgs; [dosfstools util-linux])} \ --set-default QT_QPA_PLATFORM xcb runHook postInstall ''; postFixup = '' substituteInPlace $out/share/applications/*.desktop \ --replace-fail "Exec=unraid-usb-creator" "Exec=$out/bin/${pname}" ''; meta = { description = "Tool for writing Unraid OS to USB drives"; homepage = "https://github.com/unraid/usb-creator-next"; license = lib.licenses.asl20; sourceProvenance = [lib.sourceTypes.binaryNativeCode]; platforms = ["x86_64-linux"]; mainProgram = pname; }; }; in { environment.systemPackages = [unraid-usb-creator]; # The app enumerates and unmounts removable drives over the UDisks2 D-Bus API # rather than poking /dev directly, which is also how it avoids needing root. # hyprland/default.nix already enables this for udiskie; repeated here so the # module is self-contained on a host that does not use Hyprland. services.udisks2.enable = true; }; }