{ pkgs, config, lib, ... }: let ms-accept = pkgs.callPackage ../pkgs/moonlight-sunshine-accept { }; sunshineConfiguration = pkgs.writeText "sunshine.conf" '' address_family = both channels = 5 pkey = /var/lib/sunshine/sunshine.key cert = /var/lib/sunshine/sunshine.cert file_state = /var/lib/sunshine/state.json credentials_file = /var/lib/sunshine/credentials.json ''; listenPort = 48011; in { networking.firewall = { allowedTCPPorts = [ 47984 47989 47990 48010 48011 ]; allowedUDPPorts = [ 47998 47999 48000 48002 48010 ]; }; networking.firewall.allowedTCPPortRanges = [ { from = 47984; to = 48010; } ]; networking.firewall.allowedUDPPortRanges = [ { from = 47998; to = 48010; } ]; networking.firewall.interfaces."zt+".allowedTCPPorts = [ 47984 47989 47990 48010 listenPort ]; networking.firewall.interfaces."zt+".allowedUDPPortRanges = [ { from = 47998; to = 48010; } ]; environment.systemPackages = [ ms-accept pkgs.sunshine pkgs.avahi # Convenience script, until we find a better UX (pkgs.writers.writeDashBin "sun" '' ${pkgs.sunshine}/bin/sunshine -0 ${sunshineConfiguration} "$@" '') # Create a dummy account, for easier setup, # don't use this account in actual production yet. (pkgs.writers.writeDashBin "init-sun" '' ${pkgs.sunshine}/bin/sunshine \ --creds "sun" "sun" '') ]; # Required to simulate input boot.kernelModules = [ "uinput" ]; services.udev.extraRules = '' KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", TAG+="uaccess" ''; security = { rtkit.enable = true; wrappers.sunshine = { owner = "root"; group = "root"; capabilities = "cap_sys_admin+p"; source = "${pkgs.sunshine}/bin/sunshine"; }; }; systemd.tmpfiles.rules = [ "d '/var/lib/sunshine' 0770 'user' 'users' - -" "C '/var/lib/sunshine/sunshine.cert' 0644 'user' 'users' - ${config.clanCore.secrets.sunshine.secrets."sunshine.cert".path or ""}" "C '/var/lib/sunshine/sunshine.key' 0644 'user' 'users' - ${config.clanCore.secrets.sunshine.secrets."sunshine.key".path or ""}" ]; hardware.opengl.enable = true; systemd.user.services.sunshine = { enable = true; description = "Sunshine self-hosted game stream host for Moonlight"; startLimitBurst = 5; startLimitIntervalSec = 500; script = "/run/current-system/sw/bin/env /run/wrappers/bin/sunshine ${sunshineConfiguration}"; serviceConfig = { Restart = "on-failure"; RestartSec = "5s"; ReadWritePaths = [ "/var/lib/sunshine" ]; ReadOnlyPaths = [ (config.clanCore.secrets.sunshine.secrets."sunshine.key".path or "") (config.clanCore.secrets.sunshine.secrets."sunshine.cert".path or "") ]; }; wantedBy = [ "graphical-session.target" ]; partOf = [ "graphical-session.target" ]; wants = [ "graphical-session.target" ]; after = [ "sunshine-init-state.service" "sunshine-init-credentials.service" ]; }; systemd.user.services.sunshine-init-state = { enable = true; description = "Sunshine self-hosted game stream host for Moonlight"; startLimitBurst = 5; startLimitIntervalSec = 500; script = '' ${ms-accept}/bin/moonlight-sunshine-accept sunshine init-state --uuid ${config.clanCore.secrets.sunshine.facts.sunshine-uuid.value or null} --state-file /var/lib/sunshine/state.json ''; serviceConfig = { Restart = "on-failure"; RestartSec = "5s"; Type = "oneshot"; ReadWritePaths = [ "/var/lib/sunshine" ]; }; wantedBy = [ "graphical-session.target" ]; }; systemd.user.services.sunshine-init-credentials = { enable = true; description = "Sunshine self-hosted game stream host for Moonlight"; startLimitBurst = 5; startLimitIntervalSec = 500; script = '' ${lib.getExe pkgs.sunshine} --creds sunshine sunshine ''; serviceConfig = { Restart = "on-failure"; RestartSec = "5s"; Type = "oneshot"; ReadWritePaths = [ "/var/lib/sunshine" ]; }; wantedBy = [ "graphical-session.target" ]; }; systemd.user.services.sunshine-listener = { enable = true; description = "Sunshine self-hosted game stream host for Moonlight"; startLimitBurst = 5; startLimitIntervalSec = 500; script = '' ${ms-accept}/bin/moonlight-sunshine-accept sunshine listen --port ${builtins.toString listenPort} --uuid ${config.clanCore.secrets.sunshine.facts.sunshine-uuid.value or null} --state /var/lib/sunshine/state.json --cert '${config.clanCore.secrets.sunshine.facts."sunshine.cert".value or null}' ''; serviceConfig = { # ExecStart = lib.concatStringsSep " " (lib.flatten # [ # (lib.getExe ms-accept) "sunshine" "listen" # "--port" (builtins.toString listenPort) # "--uuid" (config.clanCore.secrets.sunshine.facts."sunshine-uuid".value or "") # "--state" "/var/lib/sunshine/state.json" # "--cert" (config.clanCore.secrets.sunshine.facts."sunshine.cert".value or "") # ] # ); Restart = "on-failure"; RestartSec = 5; ReadWritePaths = [ "/var/lib/sunshine" ]; }; wantedBy = [ "graphical-session.target" ]; }; # xdg.configFile."sunshine/apps.json".text = builtins.toJSON { # env = "/run/current-system/sw/bin"; # apps = [ # { # name = "Steam"; # output = "steam.txt"; # detached = [ # "${pkgs.util-linux}/bin/setsid ${pkgs.steam}/bin/steam steam://open/bigpicture" # ]; # image-path = "steam.png"; # } # ]; # }; clanCore.secrets.sunshine = { secrets."sunshine.key" = { }; secrets."sunshine.cert" = { }; facts."sunshine-uuid" = { }; facts."sunshine.cert" = { }; generator.path = [ pkgs.coreutils ms-accept ]; generator.script = '' moonlight-sunshine-accept sunshine init mv credentials/cakey.pem "$secrets"/sunshine.key cp credentials/cacert.pem "$secrets"/sunshine.cert mv credentials/cacert.pem "$facts"/sunshine.cert mv uuid "$facts"/sunshine-uuid ''; }; }