diff --git a/modules/targets/generic-linux.nix b/modules/targets/generic-linux.nix index 45be10acb..31529fc24 100644 --- a/modules/targets/generic-linux.nix +++ b/modules/targets/generic-linux.nix @@ -26,6 +26,7 @@ in ) (lib.mkRenamedOptionModule [ "nixGL" ] [ "targets" "genericLinux" "nixGL" ]) ./generic-linux/nixgl.nix + ./generic-linux/gpu ]; options.targets.genericLinux = { diff --git a/modules/targets/generic-linux/gpu/default.nix b/modules/targets/generic-linux/gpu/default.nix new file mode 100644 index 000000000..09665f99b --- /dev/null +++ b/modules/targets/generic-linux/gpu/default.nix @@ -0,0 +1,155 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + options.targets.genericLinux.gpu = + let + inherit (lib) + mkOption + mkEnableOption + types + literalExpression + ; + in + { + enable = mkOption { + type = types.bool; + default = config.targets.genericLinux.enable && config.targets.genericLinux.nixGL.packages == null; + defaultText = literalExpression "config.targets.genericLinux.enable && config.targets.genericLinux.nixGL.packages == null"; + example = true; + description = "Whether to enable GPU driver integration for non-NixOS systems."; + }; + + packages = mkOption { + type = types.attrs; + default = pkgs; + defaultText = literalExpression "pkgs"; + description = "The Nixpkgs package set where drivers are taken from."; + }; + + nvidia = { + enable = mkEnableOption "proprietary Nvidia drivers"; + + version = mkOption { + type = types.nullOr (types.strMatching "[0-9]{3}\\.[0-9]{2,3}\\.[0-9]{2}"); + default = null; + example = literalExpression "550.163.01"; + description = '' + The exact version of Nvidia drivers to use. This version **must** + match the version of the driver used by the host OS. + ''; + }; + + sha256 = mkOption { + type = types.nullOr (types.strMatching "sha256-.*="); + default = null; + example = literalExpression "sha256-hfK1D5EiYcGRegss9+H5dDr/0Aj9wPIJ9NVWP3dNUC0="; + description = '' + The hash of the downloaded driver file. It can be obtained by + running, for example, + + ```sh + nix store prefetch-file https://download.nvidia.com/XFree86/Linux-x86_64/@VERSION@/NVIDIA-Linux-x86_64-@VERSION@.run + ``` + + where `@VERSION@` is replaced with the exact driver version. + If you are on ARM, replace Linux-x86_64 with Linux-aarch64. + ''; + }; + }; + + nixStateDirectory = mkOption { + type = types.path; + default = "/nix/var/nix"; + example = "/var/lib/nix"; + description = '' + The path to the Nix state directory. This only needs to be changed + from default if the path was overridden, e.g., by setting the + `NIX_STATE_DIR` environment variable. + ''; + }; + }; + + config = + let + cfg = config.targets.genericLinux.gpu; + + # This builds the driver archive downloaded from download.nvidia.com + nvidia = + (cfg.packages.linuxPackages.nvidiaPackages.mkDriver { + version = cfg.nvidia.version; + sha256_64bit = cfg.nvidia.sha256; + sha256_aarch64 = cfg.nvidia.sha256; + useSettings = false; + usePersistenced = false; + }).override + { + libsOnly = true; + kernel = null; + }; + + drivers = cfg.packages.callPackage ./gpu-libs-env.nix { + addNvidia = cfg.nvidia.enable; + nvidia_x11 = nvidia; # Only used if addNvidia is enabled + }; + + setupPackage = cfg.packages.callPackage ./setup { + inherit (cfg) nixStateDirectory; + nonNixosGpuEnv = drivers; + }; + + in + lib.mkIf cfg.enable { + assertions = lib.optionals cfg.nvidia.enable [ + { + assertion = !isNull cfg.nvidia.version; + message = '' + Nvidia proprietary driver is enabled, version must be given. + Please set targets.genericLinux.gpu.nvidia.version. + ''; + } + { + assertion = !isNull cfg.nvidia.sha256; + message = '' + Nvidia proprietary driver is enabled, driver hash must be given. + Please set targets.genericLinux.gpu.nvidia.sha256. + ''; + } + ]; + + warnings = lib.optional (config.targets.genericLinux.nixGL.packages != null) '' + Both targets.genericLinux.gpu and targets.genericLinux.nixGL are enabled. + This is an unsupported configuration. Only mix the two if you know what + you are doing! + ''; + + home.packages = [ setupPackage ]; + + home.activation.checkExistingGpuDrivers = + let + # Absolute path is needed for use with sudo which doesn't have the user's + # home environment. + setupPath = "${lib.getExe setupPackage}"; + in + lib.hm.dag.entryAnywhere '' + existing=$(readlink /run/opengl-driver || true) + new=${drivers} + verboseEcho Existing drivers: ''${existing} + verboseEcho New drivers: ''${new} + if [[ -z "''${existing}" ]] ; then + warnEcho "This non-NixOS system is not yet set up to use the GPU" + warnEcho "with Nix packages. To set up GPU drivers, run" + warnEcho " sudo ${setupPath}" + elif [[ "''${existing}" != "''${new}" ]] ; then + warnEcho "GPU drivers require an update, run" + warnEcho " sudo ${setupPath}" + fi + ''; + }; + + meta.maintainers = with lib.hm.maintainers; [ exzombie ]; +} diff --git a/modules/targets/generic-linux/gpu/gpu-libs-env.nix b/modules/targets/generic-linux/gpu/gpu-libs-env.nix new file mode 100644 index 000000000..37d0d5e50 --- /dev/null +++ b/modules/targets/generic-linux/gpu/gpu-libs-env.nix @@ -0,0 +1,24 @@ +{ + lib, + buildEnv, + mesa, + libvdpau-va-gl, + intel-media-driver, + nvidia-vaapi-driver, + linuxPackages, + nvidia_x11 ? linuxPackages.nvidia_x11, + addNvidia ? false, +}: + +buildEnv { + name = "non-nixos-gpu"; + paths = [ + mesa + libvdpau-va-gl + intel-media-driver + ] + ++ lib.optionals addNvidia [ + nvidia_x11 + nvidia-vaapi-driver + ]; +} diff --git a/modules/targets/generic-linux/gpu/setup/default.nix b/modules/targets/generic-linux/gpu/setup/default.nix new file mode 100644 index 000000000..6fa79af32 --- /dev/null +++ b/modules/targets/generic-linux/gpu/setup/default.nix @@ -0,0 +1,30 @@ +{ + lib, + stdenv, + nixStateDirectory, + nonNixosGpuEnv, +}: + +stdenv.mkDerivation { + name = "non-nixos-gpu"; + + meta = { + description = "GPU driver setup for Nix on non-NixOS Linux systems"; + homepage = "https://github.com/exzombie/non-nixos-gpu"; + license = lib.licenses.mit; + mainProgram = "non-nixos-gpu-setup"; + }; + + src = ./.; + patchPhase = '' + substituteInPlace non-nixos-gpu* \ + --replace '@@resources@@' "$out/resources" \ + --replace '@@statedir@@' '${nixStateDirectory}' \ + --replace '@@env@@' "${nonNixosGpuEnv}" + ''; + installPhase = '' + mkdir -p $out/{bin,resources} + cp non-nixos-gpu-setup $out/bin + cp non-nixos-gpu.service $out/resources + ''; +} diff --git a/modules/targets/generic-linux/gpu/setup/non-nixos-gpu-setup b/modules/targets/generic-linux/gpu/setup/non-nixos-gpu-setup new file mode 100755 index 000000000..6da38ba6b --- /dev/null +++ b/modules/targets/generic-linux/gpu/setup/non-nixos-gpu-setup @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +# Install the systemd service file and ensure that the store path won't be +# garbage-collected as long as it's installed. +unit_path=/etc/systemd/system/non-nixos-gpu.service +ln -sf @@resources@@/non-nixos-gpu.service "$unit_path" +ln -sf "$unit_path" "@@statedir@@"/gcroots/non-nixos-gpu.service + +systemctl daemon-reload +systemctl enable non-nixos-gpu.service +systemctl restart non-nixos-gpu.service diff --git a/modules/targets/generic-linux/gpu/setup/non-nixos-gpu.service b/modules/targets/generic-linux/gpu/setup/non-nixos-gpu.service new file mode 100644 index 000000000..9ce6054a8 --- /dev/null +++ b/modules/targets/generic-linux/gpu/setup/non-nixos-gpu.service @@ -0,0 +1,10 @@ +[Unit] +Description=GPU driver setup for Nix on non-NixOS Linux systems + +[Install] +WantedBy=multi-user.target + +[Service] +Type=oneshot +ExecStart=ln -nsf @@env@@ /run/opengl-driver +RemainAfterExit=yes diff --git a/tests/modules/targets-linux/default.nix b/tests/modules/targets-linux/default.nix index e13617ccb..24786f8de 100644 --- a/tests/modules/targets-linux/default.nix +++ b/tests/modules/targets-linux/default.nix @@ -1 +1,6 @@ -{ targets-generic-linux = ./generic-linux.nix; } +{ + targets-generic-linux = ./generic-linux.nix; + targets-generic-linux-gpu-basic = ./generic-linux-gpu/basic-enable.nix; + targets-generic-linux-gpu-setup-contents = ./generic-linux-gpu/setup-package-contents.nix; + targets-generic-linux-gpu-nvidia = ./generic-linux-gpu/nvidia-enabled.nix; +} diff --git a/tests/modules/targets-linux/generic-linux-gpu/basic-enable.nix b/tests/modules/targets-linux/generic-linux-gpu/basic-enable.nix new file mode 100644 index 000000000..23c187ba0 --- /dev/null +++ b/tests/modules/targets-linux/generic-linux-gpu/basic-enable.nix @@ -0,0 +1,8 @@ +{ + targets.genericLinux.gpu.enable = true; + + nmt.script = '' + assertFileExists home-path/bin/non-nixos-gpu-setup + assertFileContains activate "checkExistingGpuDrivers" + ''; +} diff --git a/tests/modules/targets-linux/generic-linux-gpu/nvidia-enabled.nix b/tests/modules/targets-linux/generic-linux-gpu/nvidia-enabled.nix new file mode 100644 index 000000000..3b9f4dbd2 --- /dev/null +++ b/tests/modules/targets-linux/generic-linux-gpu/nvidia-enabled.nix @@ -0,0 +1,56 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + mockNvidiaDriver = config.lib.test.mkStubPackage { + buildScript = '' + mkdir -p $out/lib + echo "MOCK_NVIDIA_DRIVER_FOR_TESTING" > $out/lib/nvidia-test-marker + ''; + }; + + # Override the package set to provide our mock driver. This also tests that a + # custom package set can be used with this module. The mock driver needs to + # support .override because the module calls it. + customPkgs = pkgs // { + linuxPackages = pkgs.linuxPackages // { + nvidiaPackages = pkgs.linuxPackages.nvidiaPackages // { + mkDriver = args: lib.makeOverridable (_: mockNvidiaDriver) { }; + }; + }; + }; +in +{ + targets.genericLinux.gpu = { + enable = true; + packages = customPkgs; + nvidia = { + enable = true; + version = "550.163.01"; + sha256 = "sha256-hfK1D5EiYcGRegss9+H5dDr/0Aj9wPIJ9NVWP3dNUC0="; + }; + }; + + nmt.script = '' + setupScript="$TESTED/home-path/bin/non-nixos-gpu-setup" + assertFileExists "$setupScript" + + # Find the resources directory + resourcesPath=$(grep -oP '/nix/store/[^/]+-non-nixos-gpu/resources' "$setupScript" | head -1) + + # Extract the GPU environment path + envPath=$(grep -oP '/nix/store/[^/]+-non-nixos-gpu' "$resourcesPath/non-nixos-gpu.service" | head -1) + + if [[ -z "$envPath" ]]; then + fail "Could not find GPU environment path in service file" + fi + + markerFile="$envPath/lib/nvidia-test-marker" + assertFileExists "$markerFile" + assertFileContains "$markerFile" "MOCK_NVIDIA_DRIVER_FOR_TESTING" + ''; +} diff --git a/tests/modules/targets-linux/generic-linux-gpu/setup-package-contents.nix b/tests/modules/targets-linux/generic-linux-gpu/setup-package-contents.nix new file mode 100644 index 000000000..e36ee9315 --- /dev/null +++ b/tests/modules/targets-linux/generic-linux-gpu/setup-package-contents.nix @@ -0,0 +1,29 @@ +{ config, lib, ... }: + +{ + targets.genericLinux.gpu = { + enable = true; + nixStateDirectory = "/custom/state/directory"; + }; + + nmt.script = '' + setupScript="$TESTED/home-path/bin/non-nixos-gpu-setup" + assertFileExists "$setupScript" + assertFileIsExecutable "$setupScript" + + # Find and check the resources directory + resourcesPath=$(grep -oP '/nix/store/[^/]+-non-nixos-gpu/resources' "$setupScript" | head -1) + assertDirectoryExists "$resourcesPath" + + # Check that gcroots dir was set + cat "$setupScript" + assertFileRegex "$setupScript" ' "/custom/state/directory"/gcroots' + + serviceFile="$resourcesPath/non-nixos-gpu.service" + assertFileExists "$serviceFile" + + # Check that no placeholders remain + assertFileNotRegex "$serviceFile" '@@[^@]\+@@' + assertFileNotRegex "$setupScript" '@@[^@]\+@@' + ''; +}