Change:
Project description <!-- Describe the project a little: --Sunshine has already been packaged here in nixpkgs: But it doesn't come out of the box just by , according to and and , at least it needs a and maybe . It would be better if we could add a NixOS module for it to make it availale just by sth. like or . CC maintainer Add a :+1: [reaction] to [issues you find important]. [reaction]: [issues you find important]:
This has been on my to-do list for ages :) The module itself wouldn't be a huge deal, and I think I have a plan for tests, just need some time to work on it.
I actually started working on this a while ago. My module can create valid and files (i.e. they work when I start sunshine manually) and open the necessary ports, however I couldn't get the systemd service to function properly. If I recall correctly, the issue was related to getting access to the screen. Here is what I have so far, maybe it can help someone:
Thanks I used yours as inspiration and opened for this. It's still a draft -- I need to work on the tests -- but any testing or comments are welcome. I've tested on Plasma 6 + Wayland + AMD and seems to work, have not tested much with Xorg or NVIDIA.
Think it's got all the functionality I had planned -- marked as ready for review.

Discussion:
- [Mealie](https://nightly.mealie.io/), a self-hosted recipe manager and meal planner with a RestAPI backend and a reactive frontend application built in NuxtJS for a pleasant user experience for the whole family. Available as [services.mealie](#opt-services.mealie.enable) <ins> - [Sunshine](https://app.lizardbyte.dev/Sunshine), a self-hosted game stream host for Moonlight. Available as [services.sunshine](#opt-services.sunshine.enable). </ins> - [Uni-Sync](https://github.com/EightB1ts/uni-sync), a synchronization tool for Lian Li Uni Controllers. Available as [hardware.uni-sync](#opt-hardware.uni-sync.enable) ## Backward Incompatibilities {#sec-release-24.05-incompatibilities}

./services/networking/strongswan.nix ./services/networking/stubby.nix ./services/networking/stunnel.nix <ins> ./services/networking/sunshine.nix </ins> ./services/networking/supplicant.nix ./services/networking/supybot.nix ./services/networking/syncplay.nix

<ins> { config, lib, pkgs, utils, ... }: let inherit (lib) mkEnableOption mkPackageOption mkOption mkIf mkDefault types optionals getExe; inherit (utils) escapeSystemdExecArgs; cfg = config.services.sunshine; # ports used are offset from a single base port, see https://docs.lizardbyte.dev/projects/sunshine/en/latest/about/advanced_usage.html#port generatePorts = port: offsets: map (offset: port + offset) offsets; defaultPort = 47989; appsFormat = pkgs.formats.json { }; settingsFormat = pkgs.formats.keyValue { }; appsFile = appsFormat.generate "apps.json" cfg.applications; configFile = settingsFormat.generate "sunshine.conf" cfg.settings; in { options.services.sunshine = with types; { enable = mkEnableOption "Sunshine, a self-hosted game stream host for Moonlight"; package = mkPackageOption pkgs "sunshine" { }; openFirewall = mkOption { type = bool; default = false; description = '' Whether to automatically open ports in the firewall. ''; }; capSysAdmin = mkOption { type = bool; default = false; description = '' Whether to give the Sunshine binary CAP_SYS_ADMIN, required for DRM/KMS screen capture. ''; }; settings = mkOption { default = { }; description = '' Settings to be rendered into the configuration file. If this is set, no configuration is possible from the web UI. See https://docs.lizardbyte.dev/projects/sunshine/en/latest/about/advanced_usage.html#configuration for syntax. ''; example = '' { sunshine_name = "nixos"; } ''; type = submodule (settings: { freeformType = settingsFormat.type; options.port = mkOption { type = port; default = defaultPort; description = '' 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. ''; }; }); }; applications = mkOption { default = { }; description = '' 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. ''; example = '' { env = { PATH = "$(PATH):$(HOME)/.local/bin"; }; apps = [ { name = "1440p Desktop"; prep-cmd = [ { do = "''${pkgs.kdePackages.libkscreen}/bin/kscreen-doctor output.DP-4.mode.2560x1440@144"; undo = "''${pkgs.kdePackages.libkscreen}/bin/kscreen-doctor output.DP-4.mode.3440x1440@144"; } ]; exclude-global-prep-cmd = "false"; auto-detach = "true"; } ]; } ''; type = submodule { options = { env = mkOption { default = { }; description = '' Environment variables to be set for the applications. ''; type = attrsOf str; }; apps = mkOption { default = [ ]; description = '' Applications to be exposed to Moonlight. ''; type = listOf attrs; }; }; }; }; }; config = mkIf cfg.enable { services.sunshine.settings.file_apps = mkIf (cfg.applications.apps != [ ]) "${appsFile}"; environment.systemPackages = [ cfg.package ]; networking.firewall = mkIf cfg.openFirewall { allowedTCPPorts = generatePorts cfg.settings.port [ (-5) 0 1 21 ]; allowedUDPPorts = generatePorts cfg.settings.port [ 9 10 11 13 21 ]; }; boot.kernelModules = [ "uinput" ]; services.udev.packages = [ cfg.package ]; services.avahi = { enable = mkDefault true; publish = { enable = mkDefault true; userServices = mkDefault true; }; }; security.wrappers.sunshine = mkIf cfg.capSysAdmin { owner = "root"; group = "root"; capabilities = "cap_sys_admin+p"; source = getExe cfg.package; }; systemd.user.services.sunshine = { description = "Self-hosted game stream host for Moonlight"; wantedBy = [ "graphical-session.target" ]; partOf = [ "graphical-session.target" ]; wants = [ "graphical-session.target" ]; after = [ "graphical-session.target" ]; startLimitIntervalSec = 500; startLimitBurst = 5; serviceConfig = { # only add configFile if an application or a setting other than the default port is set to allow configuration from web UI ExecStart = escapeSystemdExecArgs ([ (if cfg.capSysAdmin then "${config.security.wrapperDir}/sunshine" else "${getExe cfg.package}") ] ++ optionals (cfg.applications.apps != [ ] || (builtins.length (builtins.attrNames cfg.settings) > 1 || cfg.settings.port != defaultPort)) [ "${configFile}" ]); Restart = "on-failure"; RestartSec = "5s"; }; }; }; } </ins>

stunnel = handleTest ./stunnel.nix {}; sudo = handleTest ./sudo.nix {}; sudo-rs = handleTest ./sudo-rs.nix {}; <ins> sunshine = handleTest ./sunshine.nix {}; </ins> suwayomi-server = handleTest ./suwayomi-server.nix {}; swap-file-btrfs = handleTest ./swap-file-btrfs.nix {}; swap-partition = handleTest ./swap-partition.nix {};

<ins> import ./make-test-python.nix ({ pkgs, lib, ... }: { name = "sunshine"; meta = { # test is flaky on aarch64 broken = pkgs.stdenv.isAarch64; maintainers = [ lib.maintainers.devusb ]; }; nodes.sunshine = { config, pkgs, ... }: { imports = [ ./common/x11.nix ]; services.sunshine = { enable = true; openFirewall = true; settings = { capture = "x11"; encoder = "software"; output_name = 0; }; }; environment.systemPackages = with pkgs; [ gxmessage ]; }; nodes.moonlight = { config, pkgs, ... }: { imports = [ ./common/x11.nix ]; environment.systemPackages = with pkgs; [ moonlight-qt ]; }; enableOCR = true; testScript = '' # start the tests, wait for sunshine to be up start_all() sunshine.wait_for_open_port(48010,"localhost") # set the admin username/password, restart sunshine sunshine.execute("sunshine --creds sunshine sunshine") sunshine.systemctl("restart sunshine","root") sunshine.wait_for_open_port(48010,"localhost") # initiate pairing from moonlight moonlight.execute("moonlight pair sunshine --pin 1234 >&2 & disown") moonlight.wait_for_console_text("Executing request") # respond to pairing request from sunshine sunshine.succeed("curl --insecure -u sunshine:sunshine -d '{"pin": "1234"}' https://localhost:47990/api/pin") # close moonlight once pairing complete moonlight.send_key("kp_enter") # put words on the sunshine screen for moonlight to see sunshine.execute("gxmessage 'hello world' -center -font 'sans 75' >&2 & disown") # connect to sunshine from moonlight and look for the words moonlight.execute("moonlight --video-decoder software stream sunshine 'Desktop' >&2 & disown") moonlight.wait_for_text("hello world") ''; }) </ins>

, autoPatchelfHook , makeWrapper , buildNpmPackage <ins> , nixosTests </ins> , cmake , avahi , libevdev

install -Dm644 ../packaging/linux/${pname}.desktop $out/share/applications/${pname}.desktop ''; <del> passthru.updateScript = ./updater.sh; </del> <ins> passthru = { tests.sunshine = nixosTests.sunshine; updateScript = ./updater.sh; }; </ins> meta = with lib; { description = "Sunshine is a Game stream host for Moonlight";