diff --git a/nixos/modules/services/video/wivrn.nix b/nixos/modules/services/video/wivrn.nix index 0623a4ed2cca..9c5e9ebb6486 100644 --- a/nixos/modules/services/video/wivrn.nix +++ b/nixos/modules/services/video/wivrn.nix @@ -10,12 +10,18 @@ let mkEnableOption mkPackageOption mkOption + literalExpression + hasAttr + toList + length + head + tail + concatStringsSep optionalString optionalAttrs isDerivation recursiveUpdate getExe - literalExpression types maintainers ; @@ -28,38 +34,27 @@ let # Since the json config attribute type "configFormat.type" doesn't allow specifying types for # individual attributes, we have to type check manually. - # The application option must be either a package or a list with package as the first element. + # The application option should be a list with package as the first element, though a single package is also valid. + # Note that this module depends on the package containing the meta.mainProgram attribute. - # Checking if an application is provided - applicationAttrExists = builtins.hasAttr "application" cfg.config.json; - applicationListNotEmpty = ( - if builtins.isList cfg.config.json.application then - (builtins.length cfg.config.json.application) != 0 - else - true - ); + # Check if an application is provided + applicationAttrExists = hasAttr "application" cfg.config.json; + applicationList = toList cfg.config.json.application; + applicationListNotEmpty = length applicationList != 0; applicationCheck = applicationAttrExists && applicationListNotEmpty; # Manage packages and their exe paths - applicationAttr = ( - if builtins.isList cfg.config.json.application then - builtins.head cfg.config.json.application - else - cfg.config.json.application - ); + applicationAttr = head applicationList; applicationPackage = mkIf applicationCheck applicationAttr; applicationPackageExe = getExe applicationAttr; - serverPackageExe = getExe cfg.package; - - # Managing strings - applicationStrings = builtins.tail cfg.config.json.application; - applicationConcat = ( - if builtins.isList cfg.config.json.application then - builtins.concatStringsSep " " ([ applicationPackageExe ] ++ applicationStrings) - else - applicationPackageExe + serverPackageExe = ( + if cfg.highPriority then "${config.security.wrapperDir}/wivrn-server" else getExe cfg.package ); + # Manage strings + applicationStrings = tail applicationList; + applicationConcat = concatStringsSep " " ([ applicationPackageExe ] ++ applicationStrings); + # Manage config file applicationUpdate = recursiveUpdate cfg.config.json ( optionalAttrs applicationCheck { application = applicationConcat; } @@ -68,7 +63,7 @@ let enabledConfig = optionalString cfg.config.enable "-f ${configFile}"; # Manage server executables and flags - serverExec = builtins.concatStringsSep " " ( + serverExec = concatStringsSep " " ( [ serverPackageExe "--systemd" @@ -76,14 +71,6 @@ let ] ++ cfg.extraServerFlags ); - applicationExec = builtins.concatStringsSep " " ( - [ - serverPackageExe - "--application" - enabledConfig - ] - ++ cfg.extraApplicationFlags - ); in { options = { @@ -95,7 +82,7 @@ in openFirewall = mkEnableOption "the default ports in the firewall for the WiVRn server"; defaultRuntime = mkEnableOption '' - WiVRn Monado as the default OpenXR runtime on the system. + WiVRn as the default OpenXR runtime on the system. The config can be found at `/etc/xdg/openxr/1/active_runtime.json`. Note that applications can bypass this option by setting an active @@ -104,34 +91,29 @@ in autoStart = mkEnableOption "starting the service by default"; + highPriority = mkEnableOption "high priority capability for asynchronous reprojection"; + monadoEnvironment = mkOption { type = types.attrs; description = "Environment variables to be passed to the Monado environment."; - default = { - XRT_COMPOSITOR_LOG = "debug"; - XRT_PRINT_OPTIONS = "on"; - IPC_EXIT_ON_DISCONNECT = "off"; - }; + default = { }; }; extraServerFlags = mkOption { type = types.listOf types.str; description = "Flags to add to the wivrn service."; default = [ ]; - example = ''[ "--no-publish-service" ]''; + example = literalExpression ''[ "--no-publish-service" ]''; }; - extraApplicationFlags = mkOption { - type = types.listOf types.str; - description = "Flags to add to the wivrn-application service. This is NOT the WiVRn startup application."; - default = [ ]; - }; + steam = { + importOXRRuntimes = mkEnableOption '' + Sets `PRESSURE_VESSEL_IMPORT_OPENXR_1_RUNTIMES` system-wide to allow Steam to automatically discover the WiVRn server. - extraPackages = mkOption { - type = types.listOf types.package; - description = "Packages to add to the wivrn-application service $PATH."; - default = [ ]; - example = literalExpression "[ pkgs.bash pkgs.procps ]"; + Note that you may have to logout for this variable to be visible + ''; + + package = mkPackageOption pkgs "steam" { }; }; config = { @@ -139,12 +121,12 @@ in json = mkOption { type = configFormat.type; description = '' - Configuration for WiVRn. The attributes are serialized to JSON in config.json. If a config or certain attributes are not provided, the server will default to stock values. + Configuration for WiVRn. The attributes are serialized to JSON in config.json. The server will fallback to default values for any missing attributes. - Note that the application option must be either a package or a - list with package as the first element. + Like upstream, the application option is a list including the application and it's flags. In the case of the NixOS module however, the first element of the list must be a package. The module will assert otherwise. + The application can be set to a single package because it gets passed to lib.toList, though this will not allow for flags to be passed. - See + See https://github.com/WiVRn/WiVRn/blob/master/docs/configuration.md ''; default = { }; example = literalExpression '' @@ -177,51 +159,59 @@ in } ]; + security.wrappers."wivrn-server" = mkIf cfg.highPriority { + setuid = false; + owner = "root"; + group = "root"; + capabilities = "cap_sys_nice+eip"; + source = getExe cfg.package; + }; + systemd.user = { services = { - # The WiVRn server runs in a hardened service and starts the application in a different service wivrn = { description = "WiVRn XR runtime service"; - environment = { + environment = recursiveUpdate { # Default options # https://gitlab.freedesktop.org/monado/monado/-/blob/598080453545c6bf313829e5780ffb7dde9b79dc/src/xrt/targets/service/monado.in.service#L12 XRT_COMPOSITOR_LOG = "debug"; XRT_PRINT_OPTIONS = "on"; IPC_EXIT_ON_DISCONNECT = "off"; - } - // cfg.monadoEnvironment; - serviceConfig = { - ExecStart = serverExec; - # Hardening options - CapabilityBoundingSet = [ "CAP_SYS_NICE" ]; - AmbientCapabilities = [ "CAP_SYS_NICE" ]; - LockPersonality = true; - NoNewPrivileges = true; - PrivateTmp = true; - ProtectClock = true; - ProtectControlGroups = true; - ProtectKernelLogs = true; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectProc = "invisible"; - ProtectSystem = "strict"; - RemoveIPC = true; - RestrictNamespaces = true; - RestrictSUIDSGID = true; - }; + PRESSURE_VESSEL_IMPORT_OPENXR_1_RUNTIMES = mkIf cfg.steam.importOXRRuntimes "1"; + } cfg.monadoEnvironment; + serviceConfig = ( + if cfg.highPriority then + { + ExecStart = serverExec; + } + # Hardening options break high-priority + else + { + ExecStart = serverExec; + # Hardening options + CapabilityBoundingSet = [ "CAP_SYS_NICE" ]; + AmbientCapabilities = [ "CAP_SYS_NICE" ]; + LockPersonality = true; + NoNewPrivileges = true; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictNamespaces = true; + RestrictSUIDSGID = true; + } + ); + path = [ cfg.steam.package ]; wantedBy = mkIf cfg.autoStart [ "default.target" ]; - restartTriggers = [ cfg.package ]; - }; - wivrn-application = mkIf applicationCheck { - description = "WiVRn application service"; - requires = [ "wivrn.service" ]; - serviceConfig = { - ExecStart = applicationExec; - Restart = "on-failure"; - RestartSec = 0; - PrivateTmp = true; - }; - path = [ applicationPackage ] ++ cfg.extraPackages; + restartTriggers = [ + cfg.package + cfg.steam.package + ]; }; }; }; @@ -247,6 +237,9 @@ in cfg.package applicationPackage ]; + sessionVariables = mkIf cfg.steam.importOXRRuntimes { + PRESSURE_VESSEL_IMPORT_OPENXR_1_RUNTIMES = "1"; + }; pathsToLink = [ "/share/openxr" ]; etc."xdg/openxr/1/active_runtime.json" = mkIf cfg.defaultRuntime { source = "${cfg.package}/share/openxr/1/openxr_wivrn.json"; diff --git a/pkgs/by-name/wi/wivrn/package.nix b/pkgs/by-name/wi/wivrn/package.nix index ecb6c93686e0..b029233162a3 100644 --- a/pkgs/by-name/wi/wivrn/package.nix +++ b/pkgs/by-name/wi/wivrn/package.nix @@ -4,6 +4,7 @@ stdenv, fetchFromGitHub, fetchFromGitLab, + fetchpatch, applyPatches, autoAddDriverRunpath, avahi, @@ -21,6 +22,7 @@ glslang, harfbuzz, kdePackages, + libarchive, libdrm, libGL, libnotify, @@ -51,13 +53,13 @@ }: stdenv.mkDerivation (finalAttrs: { pname = "wivrn"; - version = "25.6.1"; + version = "25.8"; src = fetchFromGitHub { owner = "wivrn"; repo = "wivrn"; rev = "v${finalAttrs.version}"; - hash = "sha256-DqgayLXI+RPIb8tLzJoHi+Z12px4pdzU50C0UBSa2u4="; + hash = "sha256-x9nZyLk0A9eiZ9V700lc4To1cVJ875ZYR0GeqQ7qNpg="; }; monado = applyPatches { @@ -65,8 +67,8 @@ stdenv.mkDerivation (finalAttrs: { domain = "gitlab.freedesktop.org"; owner = "monado"; repo = "monado"; - rev = "bb9bcee2a3be75592de819d9e3fb2c8ed27bb7dc"; - hash = "sha256-+PiWxnvMXaSFc+67r17GBRXo7kbjikSElawNMJCydrk="; + rev = "5c137fe28b232fe460f9b03defa7749adc32ee48"; + hash = "sha256-4P/ejRAitrYn8hXZPaDOcx27utfm+aVLjtqL6JxZYAg="; }; postPatch = '' @@ -88,6 +90,15 @@ stdenv.mkDerivation (finalAttrs: { fi ''; + patches = [ + # Needed to allow WiVRn in-stream GUI to launch Steam games + (fetchpatch { + name = "wivrn-allow-launching-steam-games.patch"; + url = "https://github.com/WiVRn/WiVRn/commit/30ceab5b3082cbc545acf8bc8ca4a24279e6f738.diff"; + hash = "sha256-BD6MhCET7hdjog8rkl7G2l7/zGfVATpNAhNie0efOlA="; + }) + ]; + nativeBuildInputs = [ cmake git @@ -117,10 +128,12 @@ stdenv.mkDerivation (finalAttrs: { kdePackages.kirigami kdePackages.qcoro kdePackages.qqc2-desktop-style + libarchive libdrm libGL libnotify libpulseaudio + librsvg libva libX11 libXrandr