Merge pull request #294641 from devusb/sunshine-module
nixos/sunshine: init
This commit is contained in:
commit
5bfe2b6964
@ -185,6 +185,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
|
||||
|
||||
- [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).
|
||||
|
@ -1161,6 +1161,7 @@
|
||||
./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
Normal file
156
nixos/modules/services/networking/sunshine.nix
Normal file
@ -0,0 +1,156 @@
|
||||
{ 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@ -858,6 +858,7 @@ in {
|
||||
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
Normal file
70
nixos/tests/sunshine.nix
Normal file
@ -0,0 +1,70 @@
|
||||
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,6 +5,7 @@
|
||||
, autoAddDriverRunpath
|
||||
, makeWrapper
|
||||
, buildNpmPackage
|
||||
, nixosTests
|
||||
, cmake
|
||||
, avahi
|
||||
, libevdev
|
||||
@ -185,7 +186,10 @@ stdenv'.mkDerivation rec {
|
||||
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";
|
||||
|
Loading…
Reference in New Issue
Block a user