Merge pull request #294641 from devusb/sunshine-module

nixos/sunshine: init

Sandro 5bfe2b69 59dc3a46

Changed files
+235 -1
nixos
doc
manual
release-notes
modules
services
networking
tests
pkgs
servers
sunshine
+2
nixos/doc/manual/release-notes/rl-2405.section.md
···
- [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)
+
- [Sunshine](https://app.lizardbyte.dev/Sunshine), a self-hosted game stream host for Moonlight. Available as [services.sunshine](#opt-services.sunshine.enable).
+
- [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)
- [prometheus-nats-exporter](https://github.com/nats-io/prometheus-nats-exporter), a Prometheus exporter for NATS. Available as [services.prometheus.exporters.nats](#opt-services.prometheus.exporters.nats.enable).
+1
nixos/modules/module-list.nix
···
./services/networking/strongswan.nix
./services/networking/stubby.nix
./services/networking/stunnel.nix
+
./services/networking/sunshine.nix
./services/networking/supplicant.nix
./services/networking/supybot.nix
./services/networking/syncplay.nix
+156
nixos/modules/services/networking/sunshine.nix
···
+
{ 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";
+
};
+
};
+
};
+
}
+1
nixos/tests/all-tests.nix
···
stunnel = handleTest ./stunnel.nix {};
sudo = handleTest ./sudo.nix {};
sudo-rs = handleTest ./sudo-rs.nix {};
+
sunshine = handleTest ./sunshine.nix {};
suwayomi-server = handleTest ./suwayomi-server.nix {};
swap-file-btrfs = handleTest ./swap-file-btrfs.nix {};
swap-partition = handleTest ./swap-partition.nix {};
+70
nixos/tests/sunshine.nix
···
+
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")
+
'';
+
})
+5 -1
pkgs/servers/sunshine/default.nix
···
, autoAddDriverRunpath
, makeWrapper
, buildNpmPackage
+
, nixosTests
, cmake
, avahi
, libevdev
···
install -Dm644 ../packaging/linux/${pname}.desktop $out/share/applications/${pname}.desktop
'';
-
passthru.updateScript = ./updater.sh;
+
passthru = {
+
tests.sunshine = nixosTests.sunshine;
+
updateScript = ./updater.sh;
+
};
meta = with lib; {
description = "Sunshine is a Game stream host for Moonlight";