at master 8.2 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 ... 6}: 7let 8 inherit (lib) 9 mkIf 10 mkEnableOption 11 mkPackageOption 12 mkOption 13 literalExpression 14 hasAttr 15 toList 16 length 17 head 18 tail 19 concatStringsSep 20 optionalString 21 optionalAttrs 22 isDerivation 23 recursiveUpdate 24 getExe 25 types 26 maintainers 27 ; 28 cfg = config.services.wivrn; 29 configFormat = pkgs.formats.json { }; 30 31 # For the application option to work with systemd PATH, we find the store binary path of 32 # the package, concat all of the following strings, and then update the application attribute. 33 34 # Since the json config attribute type "configFormat.type" doesn't allow specifying types for 35 # individual attributes, we have to type check manually. 36 37 # The application option should be a list with package as the first element, though a single package is also valid. 38 # Note that this module depends on the package containing the meta.mainProgram attribute. 39 40 # Check if an application is provided 41 applicationAttrExists = hasAttr "application" cfg.config.json; 42 applicationList = toList cfg.config.json.application; 43 applicationListNotEmpty = length applicationList != 0; 44 applicationCheck = applicationAttrExists && applicationListNotEmpty; 45 46 # Manage packages and their exe paths 47 applicationAttr = head applicationList; 48 applicationPackage = mkIf applicationCheck applicationAttr; 49 applicationPackageExe = getExe applicationAttr; 50 serverPackageExe = ( 51 if cfg.highPriority then "${config.security.wrapperDir}/wivrn-server" else getExe cfg.package 52 ); 53 54 # Manage strings 55 applicationStrings = tail applicationList; 56 applicationConcat = concatStringsSep " " ([ applicationPackageExe ] ++ applicationStrings); 57 58 # Manage config file 59 applicationUpdate = recursiveUpdate cfg.config.json ( 60 optionalAttrs applicationCheck { application = applicationConcat; } 61 ); 62 configFile = configFormat.generate "config.json" applicationUpdate; 63 enabledConfig = optionalString cfg.config.enable "-f ${configFile}"; 64 65 # Manage server executables and flags 66 serverExec = concatStringsSep " " ( 67 [ 68 serverPackageExe 69 "--systemd" 70 enabledConfig 71 ] 72 ++ cfg.extraServerFlags 73 ); 74in 75{ 76 options = { 77 services.wivrn = { 78 enable = mkEnableOption "WiVRn, an OpenXR streaming application"; 79 80 package = mkPackageOption pkgs "wivrn" { }; 81 82 openFirewall = mkEnableOption "the default ports in the firewall for the WiVRn server"; 83 84 defaultRuntime = mkEnableOption '' 85 WiVRn as the default OpenXR runtime on the system. 86 The config can be found at `/etc/xdg/openxr/1/active_runtime.json`. 87 88 Note that applications can bypass this option by setting an active 89 runtime in a writable XDG_CONFIG_DIRS location like `~/.config` 90 ''; 91 92 autoStart = mkEnableOption "starting the service by default"; 93 94 highPriority = mkEnableOption "high priority capability for asynchronous reprojection"; 95 96 monadoEnvironment = mkOption { 97 type = types.attrs; 98 description = "Environment variables to be passed to the Monado environment."; 99 default = { }; 100 }; 101 102 extraServerFlags = mkOption { 103 type = types.listOf types.str; 104 description = "Flags to add to the wivrn service."; 105 default = [ ]; 106 example = literalExpression ''[ "--no-publish-service" ]''; 107 }; 108 109 steam = { 110 importOXRRuntimes = mkEnableOption '' 111 Sets `PRESSURE_VESSEL_IMPORT_OPENXR_1_RUNTIMES` system-wide to allow Steam to automatically discover the WiVRn server. 112 113 Note that you may have to logout for this variable to be visible 114 ''; 115 116 package = mkPackageOption pkgs "steam" { }; 117 }; 118 119 config = { 120 enable = mkEnableOption "configuration for WiVRn"; 121 json = mkOption { 122 type = configFormat.type; 123 description = '' 124 Configuration for WiVRn. The attributes are serialized to JSON in config.json. The server will fallback to default values for any missing attributes. 125 126 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. 127 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. 128 129 See <https://github.com/WiVRn/WiVRn/blob/master/docs/configuration.md> 130 ''; 131 default = { }; 132 example = literalExpression '' 133 { 134 scale = 0.5; 135 bitrate = 100000000; 136 encoders = [ 137 { 138 encoder = "nvenc"; 139 codec = "h264"; 140 width = 1.0; 141 height = 1.0; 142 offset_x = 0.0; 143 offset_y = 0.0; 144 } 145 ]; 146 application = [ pkgs.wlx-overlay-s ]; 147 } 148 ''; 149 }; 150 }; 151 }; 152 }; 153 154 config = mkIf cfg.enable { 155 assertions = [ 156 { 157 assertion = !applicationCheck || isDerivation applicationAttr; 158 message = "The application in WiVRn configuration is not a package. Please ensure that the application is a package or that a package is the first element in the list."; 159 } 160 ]; 161 162 security.wrappers."wivrn-server" = mkIf cfg.highPriority { 163 setuid = false; 164 owner = "root"; 165 group = "root"; 166 capabilities = "cap_sys_nice+eip"; 167 source = getExe cfg.package; 168 }; 169 170 systemd.user = { 171 services = { 172 wivrn = { 173 description = "WiVRn XR runtime service"; 174 environment = recursiveUpdate { 175 # Default options 176 # https://gitlab.freedesktop.org/monado/monado/-/blob/598080453545c6bf313829e5780ffb7dde9b79dc/src/xrt/targets/service/monado.in.service#L12 177 XRT_COMPOSITOR_LOG = "debug"; 178 XRT_PRINT_OPTIONS = "on"; 179 IPC_EXIT_ON_DISCONNECT = "off"; 180 PRESSURE_VESSEL_IMPORT_OPENXR_1_RUNTIMES = mkIf cfg.steam.importOXRRuntimes "1"; 181 } cfg.monadoEnvironment; 182 serviceConfig = ( 183 if cfg.highPriority then 184 { 185 ExecStart = serverExec; 186 } 187 # Hardening options break high-priority 188 else 189 { 190 ExecStart = serverExec; 191 # Hardening options 192 CapabilityBoundingSet = [ "CAP_SYS_NICE" ]; 193 AmbientCapabilities = [ "CAP_SYS_NICE" ]; 194 LockPersonality = true; 195 NoNewPrivileges = true; 196 PrivateTmp = true; 197 ProtectClock = true; 198 ProtectControlGroups = true; 199 ProtectKernelLogs = true; 200 ProtectKernelModules = true; 201 ProtectKernelTunables = true; 202 ProtectProc = "invisible"; 203 ProtectSystem = "strict"; 204 RemoveIPC = true; 205 RestrictNamespaces = true; 206 RestrictSUIDSGID = true; 207 } 208 ); 209 path = [ cfg.steam.package ]; 210 wantedBy = mkIf cfg.autoStart [ "default.target" ]; 211 restartTriggers = [ 212 cfg.package 213 cfg.steam.package 214 ]; 215 }; 216 }; 217 }; 218 219 services = { 220 udev.packages = with pkgs; [ android-udev-rules ]; 221 avahi = { 222 enable = true; 223 publish = { 224 enable = true; 225 userServices = true; 226 }; 227 }; 228 }; 229 230 networking.firewall = mkIf cfg.openFirewall { 231 allowedTCPPorts = [ 9757 ]; 232 allowedUDPPorts = [ 9757 ]; 233 }; 234 235 environment = { 236 systemPackages = [ 237 cfg.package 238 applicationPackage 239 ]; 240 sessionVariables = mkIf cfg.steam.importOXRRuntimes { 241 PRESSURE_VESSEL_IMPORT_OPENXR_1_RUNTIMES = "1"; 242 }; 243 pathsToLink = [ "/share/openxr" ]; 244 etc."xdg/openxr/1/active_runtime.json" = mkIf cfg.defaultRuntime { 245 source = "${cfg.package}/share/openxr/1/openxr_wivrn.json"; 246 }; 247 }; 248 }; 249 meta.maintainers = with maintainers; [ passivelemon ]; 250}