I am trying to set up sunshine to capture using the wlr method and do it headlessly. no dummy plug, no monitor. Supposedly this is possible, but I’m having trouble.
first I create the user in fedora and give them the permissions needed. I set up setd and create the service file for systemd to fire a sway instance. I edit sway config for that user to include the virtual display output by including this line:
output HEADLESS-1 { pos 0,0 mode 3840x2160@60hz }
That should make a virtual monitor that wayland can render to and sunshine can capture the framebuffer from sway with.
Sway launches fine, sunshine see the wayland instance, but it sees no monitor attached so it has nothing in it can capture from.
I cannot use swaymsg from SSH since its not part of that wayland session so it cant access it and pass commands to it. it just complains about socket errors. So I dont know what I’m supposed to do to make this headlessly run.
I have this setup with Plasma, and it is probably easier to do this at the Linux level. I added this to my kernel command line:
drm.edid_firmware=DP-1:edid/lg-ultra.bin video=DP-1:3840x2160@60eWhere that EDID file I dumped from a spare monitor using a method I got here.
Anyway, it can be tricky to pick the right device, but I can confirm Sunshine sees it and works properly, and it can be managed like a normal monitor.
This approach is probably the most reliable and works with any AMD card at the least.
I think I actually figured it out without the EDID injection, I forgot a line in the sway config. I’ll test it after work today and report back what I have if it works as this method is a cleaner way of getting headless zero copy.
EDIT: okay, posted. feel free to suggest corrections as I’m still learning.
Okay here’s what I stumbled through.
This is on a fedora server machine with an intel ARC B580. I intentionally chose fedora because my fedora skills are ass and I wanted to improve them so…
- enable rpmfusion repos - go here (https://docs.fedoraproject.org/en-US/quick-docs/rpmfusion-setup/) make sure to include nonfree. we need the intel-media-driver from there. The one included in the fedora repos is incomplete and generally wont be enough for what we want.
- enable the copr repo for sunshine - at this time, there isnt anything in stable for fedora 43, so we enable the beta repo with “sudo dnf copr enable lizardbyte/beta”
- install the packages - run “sudo dnf install intel-media-driver sway seatd nano pipewire wireplumber Sunshine”
- open ports on the fedora firewall - I’m a hack and used cockpit for this. log into the cockpit UI and got to Networking->edit rules and zones->add services. add TCP 47990, 47984, 47989, 48010 and UDP 47998-48000
- add a sunshine user on the machine - run “sudo adduser sunshine -m”
- set a password for the sunshine user - run “sudo passwd sunshine”
- add sunshine user to the required groups - run “sudo usermod -aG video,render,seat,input sunshine”
- enable seatd at boot so the sunshine user can create a headless wayland session in the background without having to be logged in - run “sudo systemctl enable --now seatd”
- enable linger so things can run by themselves as that user - run “sudo loginctl enable-linger sunshine”
- There is an issue with sunshine rules in fedora not matching because the PS5 controller name attribute isn’t where it is in other distros. So we tell it to look a little deeper. Create a helper script to evaluate and throw controllers into the input group - run “sudo nano /usr/local/bin/sunshines-little-helper.sh” and ensure the following is included in the file:
#!/bin/bash # Called by udev when a hidraw device appears. # $1 = sysfs path of the device (from %p) DEV_SYSFS=$(readlink -f "/sys/$1") # Exit if no device passed if [ -z "$DEV_SYSFS" ] || [ ! -e "$DEV_SYSFS" ]; then echo "sunshine-helper: invalid device path: $DEV_SYSFS" >&2 exit 1 fi # Loop through input child devices to detect Sunshine virtual controller SUNSHINE_FOUND=0 for input_dir in "$DEV_SYSFS"/device/input/input*; do [ -d "$input_dir" ] || continue NAME=$(cat "$input_dir/name" 2>/dev/null) case "$NAME" in "Sunshine PS5 (virtual) pad"*|"Sunshine X-Box One (virtual) pad"*|"Sunshine Nintendo (virtual) pad"*|"Sunshine gamepad (virtual) motion sensors"*) SUNSHINE_FOUND=1 break ;; esac done # If not a Sunshine virtual device, do nothing [ "$SUNSHINE_FOUND" -eq 1 ] || exit 0 # Resolve the /dev/hidrawX node corresponding to the sysfs path DEV_NODE=$(udevadm info -q path -p "$DEV_SYSFS" | xargs -I{} basename {} | sed 's/^/\/dev\//') # Apply group/permissions chgrp input "$DEV_NODE" chmod 0660 "$DEV_NODE"- make this script executable - run “sudo chmod +x /usr/local/bin/sunshines-little-helper.sh”
- tell udev to run our script if it suspects a sunshine controller might have been connected - run “sudo nano /etc/udev/rules.d/99-sunshine.rules” ensure the following is included in the file:
# Fedora doesnt present the PS5 controllers with the name attribute where normal sunshine rules # expect it, so we have to call an external script the run additional checks and act accordingly # This method is gross. If anyone has a better idea, please post it somewhere and euthanize this KERNEL=="hidraw*", KERNELS=="*054C:0CE6*", RUN+="/usr/local/bin/sunshines-little-helper.sh %p"- create config directories - log in as the sunshine user we created. run “mkdir -p ~/.config/sway ~/.config/sunshine ~/.config/systemd/user”
- create sway config - run “nano ~/.config/sway/config” and ensure the following is included in the file:
output HEADLESS-1 { pos 0,0 mode 3840x2160@60hz render_bit_depth 10 } exec swaymsg create_output HEADLESS-1 default_border none default_floating_border none- create the initial sunshine config - run “nano ~/.config/sunshine/sunshine.conf” and ensure the following is included in the file:
capture = wlr encoder = vaapi- config systemd to launch sway at boot - run “nano ~/.config/systemd/user/sway.service” and make the file look like this:
[Unit] Description=Sway Wayland compositor Documentation=man:sway(5) After=pipewire.socket wireplumber.service Wants=pipewire.socket wireplumber.service [Service] ExecStart=/usr/bin/sway Restart=on-failure RestartSec=2 Environment=WLR_RENDERER=vulkan Environment=XDG_SESSION_TYPE=wayland Environment=LIBSEAT_BACKEND=seatd Environment=XDG_CURRENT_DESKTOP=sway [Install] WantedBy=default.target- config systemd to launch sunshine at boot (the way we want it to) - run “EDITOR=nano systemctl --user edit sunshine” and put the following in the area that will apply the override:
[Unit] After=sway.service Requires=sway.service [Service] Environment=WAYLAND_DISPLAY=wayland-1 Environment=XDG_SESSION_TYPE=wayland [Install] WantedBy=default.target- enable the systemd services - run “systemctl --user daemon-reload” then run “systemctl --user enable --now sway” and “systemctl --user enable --now sunshine”
- configure sway to change resolution to whatever the moonlight client asks for - access the sunshine webUI by pointing a web browser to https://(your server IP):47990/ and go to the configuration tab and add the following DO command
sh -c "swaymsg output HEADLESS-1 pos 0 0 res \"${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}@${SUNSHINE_CLIENT_FPS}Hz\""and if you want an undo command set it to
sh -c "swaymsg output HEADLESS-1 pos 0 0 res \"${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}@${SUNSHINE_CLIENT_FPS}Hz\""There, you now have a headless, resolution-matching, zero-copy, lightweight sunshine install and you didn’t need any dummy plugs, connected monitors, EDID injection, etc.
There are likely things I missed but this is what I’ve got so far.
Also, if you are using an intel ARC GPU, you might want to run through fwupd as there are issues with these cards that the windows installer resolves during driver install that we cant run on linux that way, so we can tell linux to do that step for us with fwupd.


