20 inherit (utils) escapeSystemdExecArgs;
21 cfg = config.services.sunshine;
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;
27 appsFormat = pkgs.formats.json { };
28 settingsFormat = pkgs.formats.keyValue { };
30 appsFile = appsFormat.generate "apps.json" cfg.applications;
31 configFile = settingsFormat.generate "sunshine.conf" cfg.settings;
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 {
41 Whether to automatically open ports in the firewall.
44 capSysAdmin = mkOption {
48 Whether to give the Sunshine binary CAP_SYS_ADMIN, required for DRM/KMS screen capture.
51 autoStart = mkOption {
55 Whether sunshine should be started automatically.
61 Settings to be rendered into the configuration file. If this is set, no configuration is possible from the web UI.
63 See https://docs.lizardbyte.dev/projects/sunshine/en/latest/about/advanced_usage.html#configuration for syntax.
65 example = literalExpression ''
67 sunshine_name = "nixos";
70 type = submodule (settings: {
71 freeformType = settingsFormat.type;
72 options.port = mkOption {
74 default = defaultPort;
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.
81 applications = mkOption {
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.
86 example = literalExpression ''
89 PATH = "$(PATH):$(HOME)/.local/bin";
93 name = "1440p Desktop";
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";
100 exclude-global-prep-cmd = "false";
101 auto-detach = "true";
111 Environment variables to be set for the applications.
118 Applications to be exposed to Moonlight.
127 config = mkIf cfg.enable {
128 services.sunshine.settings.file_apps = mkIf (cfg.applications.apps != [ ]) "${appsFile}";
130 environment.systemPackages = [
134 networking.firewall = mkIf cfg.openFirewall {
135 allowedTCPPorts = generatePorts cfg.settings.port [
141 allowedUDPPorts = generatePorts cfg.settings.port [
150 boot.kernelModules = [ "uinput" ];
152 services.udev.packages = [ cfg.package ];
155 enable = mkDefault true;
157 enable = mkDefault true;
158 userServices = mkDefault true;
162 security.wrappers.sunshine = mkIf cfg.capSysAdmin {
165 capabilities = "cap_sys_admin+p";
166 source = getExe cfg.package;
169 systemd.user.services.sunshine = {
170 description = "Self-hosted game stream host for Moonlight";
172 wantedBy = mkIf cfg.autoStart [ "graphical-session.target" ];
173 partOf = [ "graphical-session.target" ];
174 wants = [ "graphical-session.target" ];
175 after = [ "graphical-session.target" ];
177 startLimitIntervalSec = 500;
180 environment.PATH = lib.mkForce null; # don't use default PATH, needed for tray icon menu links to work
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 (
186 (if cfg.capSysAdmin then "${config.security.wrapperDir}/sunshine" else "${getExe cfg.package}")
189 cfg.applications.apps != [ ]
190 || (builtins.length (builtins.attrNames cfg.settings) > 1 || cfg.settings.port != defaultPort)
191 ) [ "${configFile}" ]
193 Restart = "on-failure";