at 25.11-pre 5.7 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 utils, 6 ... 7}: 8let 9 inherit (lib) 10 mkEnableOption 11 mkPackageOption 12 mkOption 13 literalExpression 14 mkIf 15 mkDefault 16 types 17 optionals 18 getExe 19 ; 20 inherit (utils) escapeSystemdExecArgs; 21 cfg = config.services.sunshine; 22 23 # ports used are offset from a single base port, see https://docs.lizardbyte.dev/projects/sunshine/en/latest/about/advanced_usage.html#port 24 generatePorts = port: offsets: map (offset: port + offset) offsets; 25 defaultPort = 47989; 26 27 appsFormat = pkgs.formats.json { }; 28 settingsFormat = pkgs.formats.keyValue { }; 29 30 appsFile = appsFormat.generate "apps.json" cfg.applications; 31 configFile = settingsFormat.generate "sunshine.conf" cfg.settings; 32in 33{ 34 options.services.sunshine = with types; { 35 enable = mkEnableOption "Sunshine, a self-hosted game stream host for Moonlight"; 36 package = mkPackageOption pkgs "sunshine" { }; 37 openFirewall = mkOption { 38 type = bool; 39 default = false; 40 description = '' 41 Whether to automatically open ports in the firewall. 42 ''; 43 }; 44 capSysAdmin = mkOption { 45 type = bool; 46 default = false; 47 description = '' 48 Whether to give the Sunshine binary CAP_SYS_ADMIN, required for DRM/KMS screen capture. 49 ''; 50 }; 51 autoStart = mkOption { 52 type = bool; 53 default = true; 54 description = '' 55 Whether sunshine should be started automatically. 56 ''; 57 }; 58 settings = mkOption { 59 default = { }; 60 description = '' 61 Settings to be rendered into the configuration file. If this is set, no configuration is possible from the web UI. 62 63 See https://docs.lizardbyte.dev/projects/sunshine/en/latest/about/advanced_usage.html#configuration for syntax. 64 ''; 65 example = literalExpression '' 66 { 67 sunshine_name = "nixos"; 68 } 69 ''; 70 type = submodule (settings: { 71 freeformType = settingsFormat.type; 72 options.port = mkOption { 73 type = port; 74 default = defaultPort; 75 description = '' 76 Base port -- others used are offset from this one, see https://docs.lizardbyte.dev/projects/sunshine/en/latest/about/advanced_usage.html#port for details. 77 ''; 78 }; 79 }); 80 }; 81 applications = mkOption { 82 default = { }; 83 description = '' 84 Configuration for applications to be exposed to Moonlight. If this is set, no configuration is possible from the web UI, and must be by the `settings` option. 85 ''; 86 example = literalExpression '' 87 { 88 env = { 89 PATH = "$(PATH):$(HOME)/.local/bin"; 90 }; 91 apps = [ 92 { 93 name = "1440p Desktop"; 94 prep-cmd = [ 95 { 96 do = "''${pkgs.kdePackages.libkscreen}/bin/kscreen-doctor output.DP-4.mode.2560x1440@144"; 97 undo = "''${pkgs.kdePackages.libkscreen}/bin/kscreen-doctor output.DP-4.mode.3440x1440@144"; 98 } 99 ]; 100 exclude-global-prep-cmd = "false"; 101 auto-detach = "true"; 102 } 103 ]; 104 } 105 ''; 106 type = submodule { 107 options = { 108 env = mkOption { 109 default = { }; 110 description = '' 111 Environment variables to be set for the applications. 112 ''; 113 type = attrsOf str; 114 }; 115 apps = mkOption { 116 default = [ ]; 117 description = '' 118 Applications to be exposed to Moonlight. 119 ''; 120 type = listOf attrs; 121 }; 122 }; 123 }; 124 }; 125 }; 126 127 config = mkIf cfg.enable { 128 services.sunshine.settings.file_apps = mkIf (cfg.applications.apps != [ ]) "${appsFile}"; 129 130 environment.systemPackages = [ 131 cfg.package 132 ]; 133 134 networking.firewall = mkIf cfg.openFirewall { 135 allowedTCPPorts = generatePorts cfg.settings.port [ 136 (-5) 137 0 138 1 139 21 140 ]; 141 allowedUDPPorts = generatePorts cfg.settings.port [ 142 9 143 10 144 11 145 13 146 21 147 ]; 148 }; 149 150 boot.kernelModules = [ "uinput" ]; 151 152 services.udev.packages = [ cfg.package ]; 153 154 services.avahi = { 155 enable = mkDefault true; 156 publish = { 157 enable = mkDefault true; 158 userServices = mkDefault true; 159 }; 160 }; 161 162 security.wrappers.sunshine = mkIf cfg.capSysAdmin { 163 owner = "root"; 164 group = "root"; 165 capabilities = "cap_sys_admin+p"; 166 source = getExe cfg.package; 167 }; 168 169 systemd.user.services.sunshine = { 170 description = "Self-hosted game stream host for Moonlight"; 171 172 wantedBy = mkIf cfg.autoStart [ "graphical-session.target" ]; 173 partOf = [ "graphical-session.target" ]; 174 wants = [ "graphical-session.target" ]; 175 after = [ "graphical-session.target" ]; 176 177 startLimitIntervalSec = 500; 178 startLimitBurst = 5; 179 180 environment.PATH = lib.mkForce null; # don't use default PATH, needed for tray icon menu links to work 181 182 serviceConfig = { 183 # only add configFile if an application or a setting other than the default port is set to allow configuration from web UI 184 ExecStart = escapeSystemdExecArgs ( 185 [ 186 (if cfg.capSysAdmin then "${config.security.wrapperDir}/sunshine" else "${getExe cfg.package}") 187 ] 188 ++ optionals ( 189 cfg.applications.apps != [ ] 190 || (builtins.length (builtins.attrNames cfg.settings) > 1 || cfg.settings.port != defaultPort) 191 ) [ "${configFile}" ] 192 ); 193 Restart = "on-failure"; 194 RestartSec = "5s"; 195 }; 196 }; 197 }; 198}