{ config, pkgs, lib, ... }: let inherit (lib) mkIf mkEnableOption mkPackageOption mkOption literalExpression hasAttr toList length head tail concatStringsSep optionalString optionalAttrs isDerivation recursiveUpdate getExe types maintainers ; cfg = config.services.wivrn; configFormat = pkgs.formats.json { }; # For the application option to work with systemd PATH, we find the store binary path of # the package, concat all of the following strings, and then update the application attribute. # 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 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. # 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 = head applicationList; applicationPackage = mkIf applicationCheck applicationAttr; applicationPackageExe = getExe applicationAttr; 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; } ); configFile = configFormat.generate "config.json" applicationUpdate; enabledConfig = optionalString cfg.config.enable "-f ${configFile}"; # Manage server executables and flags serverExec = concatStringsSep " " ( [ serverPackageExe "--systemd" enabledConfig ] ++ cfg.extraServerFlags ); in { options = { services.wivrn = { enable = mkEnableOption "WiVRn, an OpenXR streaming application"; package = mkPackageOption pkgs "wivrn" { }; openFirewall = mkEnableOption "the default ports in the firewall for the WiVRn server"; defaultRuntime = mkEnableOption '' 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 runtime in a writable XDG_CONFIG_DIRS location like `~/.config` ''; 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 = { }; }; extraServerFlags = mkOption { type = types.listOf types.str; description = "Flags to add to the wivrn service."; default = [ ]; example = literalExpression ''[ "--no-publish-service" ]''; }; steam = { importOXRRuntimes = mkEnableOption '' Sets `PRESSURE_VESSEL_IMPORT_OPENXR_1_RUNTIMES` system-wide to allow Steam to automatically discover the WiVRn server. Note that you may have to logout for this variable to be visible ''; package = mkPackageOption pkgs "steam" { }; }; config = { enable = mkEnableOption "configuration for WiVRn"; json = mkOption { type = configFormat.type; description = '' Configuration for WiVRn. The attributes are serialized to JSON in config.json. The server will fallback to default values for any missing attributes. 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 https://github.com/WiVRn/WiVRn/blob/master/docs/configuration.md ''; default = { }; example = literalExpression '' { scale = 0.5; bitrate = 100000000; encoders = [ { encoder = "nvenc"; codec = "h264"; width = 1.0; height = 1.0; offset_x = 0.0; offset_y = 0.0; } ]; application = [ pkgs.wlx-overlay-s ]; } ''; }; }; }; }; config = mkIf cfg.enable { assertions = [ { assertion = !applicationCheck || isDerivation applicationAttr; 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."; } ]; 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 = { wivrn = { description = "WiVRn XR runtime service"; 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"; 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 cfg.steam.package ]; }; }; }; services = { udev.packages = with pkgs; [ android-udev-rules ]; avahi = { enable = true; publish = { enable = true; userServices = true; }; }; }; networking.firewall = mkIf cfg.openFirewall { allowedTCPPorts = [ 9757 ]; allowedUDPPorts = [ 9757 ]; }; environment = { systemPackages = [ 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"; }; }; }; meta.maintainers = with maintainers; [ passivelemon ]; }