--- - name: Configure Fedora 42 for optimized Sunshine + Gamescope streaming hosts: game_station vars: user_name: "gameuser" system_bin_dir: "/usr/local/bin" user_config_dir: "/home/{{ user_name }}/.config" user_bin_dir: "/home/{{ user_name }}/.local/bin" systemd_user_dir: "/home/{{ user_name }}/.config/systemd/user" systemd_system_dir: "/etc/systemd/system" grub_cfg: "/etc/default/grub" system_cores: "0,8" general_cores: "1-7,9-15" sunshine_cores: "1-7,9-15" screen_resolution: "{{ lookup('env', 'GAME_RESOLUTION') | d('1920x1080', true) }}" screen_width: "{{ screen_resolution.split('x')[0] }}" screen_height: "{{ screen_resolution.split('x')[1] }}" hdr_enabled: "{{ lookup('env', 'HDR_ENABLED') | d('false', true) }}" output_connector: "{{ lookup('env', 'OUTPUT_CONNECTOR') | d('HDMI-A-1', true) }}" framerate: "{{ lookup('env', 'FRAMERATE') | d('60', true) }}" drm_device: "{{ lookup('env', 'DRM_DEVICE') | d('/dev/dri/card1', true) }}" grub_options: - quiet - splash - amd_pstate=active - nvidia_drm.modeset=1 - fbdev=1 - mitigations=off - threadirqs # - nohz_full=1-7,9-15 # - rcu_nocbs=1-7,9-15 - drm.edid_firmware=DP-1:edid/aoc28e850.bin - video=DP-1:e - acpi_enforce_resources=lax nvidia_modules: - nvidia - nvidia_modeset - nvidia_uvm - nvidia_drm appimages: - name: Steam ROM Manager url: "https://github.com/SteamGridDB/steam-rom-manager/releases/download/v2.5.30/Steam-ROM-Manager-2.5.30.AppImage" executable: "SteamRomManager.AppImage" link: "srm" - name: RPCS3 url: "https://github.com/RPCS3/rpcs3-binaries-linux/releases/download/build-cc8929ef4a6142fa5b240d2f5ec19ead54cc6efa/rpcs3-v0.0.38-18287-cc8929ef_linux64.AppImage" executable: "rpcs3.AppImage" link: "rpcs3" - name: Eden url: "https://github.com/eden-emulator/Releases/releases/download/v0.0.4-rc1/Eden-Linux-v0.0.4-rc1-amd64-clang-standard.AppImage" executable: "Eden.AppImage" link: "eden" alvr_version: "20.14.1" tasks: # --- Base dependencies --- - name: Enable COPR repositories become: true community.general.copr: name: "{{ item }}" state: enabled loop: - lizardbyte/stable - atim/heroic-games-launcher # # --- CachyOS Kernel (conditional) --- # - name: Detect supported x86_64 ISA levels # ansible.builtin.shell: # cmd: | # set -euo pipefail; # /lib64/ld-linux-x86-64.so.2 --help | grep -F "(supported, searched)" # register: glibc_loader_help # changed_when: false # failed_when: false # - name: Determine CPU support for CachyOS kernel # ansible.builtin.set_fact: # has_x86_64_v3: "{{ ('x86-64-v3' in glibc_loader_help.stdout) or ('x86_64_v3' in glibc_loader_help.stdout) }}" # has_x86_64_v2: "{{ ('x86-64-v2' in glibc_loader_help.stdout) or ('x86_64_v2' in glibc_loader_help.stdout) }}" # - name: Debug CPU ISA detection # ansible.builtin.debug: # msg: "CPU supports x86_64_v3: {{ has_x86_64_v3 }}, x86_64_v2: {{ has_x86_64_v2 }}" # - name: Enable CachyOS COPR repository (kernel) # become: true # community.general.copr: # name: bieszczaders/kernel-cachyos # state: enabled # when: has_x86_64_v3 | bool or has_x86_64_v2 | bool # - name: Enable CachyOS COPR repository (addons) # become: true # community.general.copr: # name: bieszczaders/kernel-cachyos-addons # state: enabled # when: has_x86_64_v3 | bool or has_x86_64_v2 | bool # - name: Install CachyOS kernel for x86_64_v3 CPUs # become: true # ansible.builtin.dnf: # name: # - pesign # - openssl # - kernel-devel # - mokutil # - keyutils # - kernel-cachyos-lts # - kernel-cachyos-lts-devel # - uksmd # state: present # when: has_x86_64_v3 | bool # - name: Ensures user is on /etc/pesign/users # become: true # ansible.builtin.lineinfile: # path: /etc/pesign/users # line: "{{ ansible_user }}" # state: present # when: has_x86_64_v3 | bool # notify: Run pesign # - name: Run /usr/libexec/pesign/pesign-authorize # become: true # ansible.builtin.command: /usr/libexec/pesign/pesign-authorize # when: has_x86_64_v3 | bool # changed_when: false # - name: Generate certificate for CachyOS kernel signing # become: true # ansible.builtin.shell: | # openssl req -new -x509 -newkey rsa:2048 -keyout "key.pem" \ # -outform DER -out "cert.der" -nodes -days 36500 \ # -subj "/CN=CachyOS Secure Boot/" && \ # openssl pkcs12 -export -out key.p12 -inkey key.pem -in cert.der # when: has_x86_64_v3 | bool # changed_when: false # args: # executable: /bin/bash # creates: # - key.pem # - cert.der # - key.p12 # - name: Inform user to enroll MOK key # ansible.builtin.debug: # msg: | # Please enroll the MOK key for kernel module signing on next reboot. # Run: # sudo certutil -A -i cert.der -n "CachyOS Secure Boot" -d /etc/pki/pesign/ -t "Pu,Pu,Pu" && \ # sudo pk12util -i key.p12 -d /etc/pki/pesign # sudo mokutil --import cert.der' if not prompted automatically. # when: has_x86_64_v3 | bool # - name: Create /etc/kernel/postinst.d directory # become: true # ansible.builtin.file: # path: /etc/kernel/postinst.d # state: directory # owner: root # group: root # mode: "0755" # - name: Enable Automatically signing kernel updates # become: true # ansible.builtin.copy: # dest: /etc/kernel/postinst.d/00-signing # owner: root # group: root # mode: "0755" # content: | # #!/bin/sh # set -e # KERNEL_IMAGE="$2" # MOK_KEY_NICKNAME="CachyOS Secure Boot" # if [ "$#" -ne "2" ] ; then # echo "Wrong count of command line arguments. This is not meant to be called directly." >&2 # exit 1 # fi # if [ ! -x "$(command -v pesign)" ] ; then # echo "pesign not executable. Bailing." >&2 # exit 1 # fi # if [ ! -w "$KERNEL_IMAGE" ] ; then # echo "Kernel image $KERNEL_IMAGE is not writable." >&2 # exit 1 # fi # echo "Signing $KERNEL_IMAGE..." # pesign --certificate "$MOK_KEY_NICKNAME" --in "$KERNEL_IMAGE" --sign --out "$KERNEL_IMAGE.signed" # mv "$KERNEL_IMAGE.signed" "$KERNEL_IMAGE" # - name: Install CachyOS LTS kernel for x86_64_v2-only CPUs # become: true # ansible.builtin.dnf: # name: # - kernel-cachyos-lts # - kernel-cachyos-lts-devel-matched # state: present # when: (not has_x86_64_v3 | bool) and (has_x86_64_v2 | bool) # --- Core system configuration --- - name: Ensure core gaming and streaming packages are installed become: true ansible.builtin.dnf: name: - gamescope - Sunshine - steam - mangohud - kernel-tools - util-linux - mesa-dri-drivers - vulkan-tools - jq - xorg-x11-drv-nvidia - xorg-x11-drv-nvidia-cuda - xorg-x11-drv-nvidia-libs - vulkan - vulkan-loader - nvidia-settings - libepoxy - weston - bubblewrap - waypipe - drm-utils - ethtool - heroic-games-launcher-bin - libva-nvidia-driver state: present - name: Ensure user namespaces are enabled become: true ansible.posix.sysctl: name: user.max_user_namespaces value: "15000" state: present sysctl_set: true reload: true - name: Ensure bubblewrap is setuid root (Fedora default) become: true ansible.builtin.file: path: /usr/bin/bwrap owner: root group: root mode: "04755" - name: Enable cpupower at boot become: true ansible.builtin.systemd: name: cpupower enabled: true state: started - name: Create firmware directory for EDID files become: true ansible.builtin.file: path: /lib/firmware/edid state: directory owner: root group: root mode: "0755" - name: Copy custom EDID file become: true ansible.builtin.copy: dest: "/lib/firmware/edid/aoc28e850.bin" owner: root group: root mode: "0644" content: "{{ lookup('file', 'game_station/firmware/aoc28e850.bin') | b64decode }}" - name: Ensure EDID file is available in initramfs become: true ansible.builtin.copy: dest: /etc/dracut.conf.d/90-edid.conf owner: root group: root mode: "0644" content: | install_items+=" /lib/firmware/edid/aoc28e850.bin " notify: Run dracut - name: Ensure NVIDIA modules are loaded at boot become: true ansible.builtin.copy: dest: /etc/modules-load.d/nvidia.conf owner: root group: root mode: "0644" content: | {% for module in nvidia_modules %} {{ module }} {% endfor %} notify: Run dracut - name: Determine default network interface ansible.builtin.set_fact: primary_iface: "{{ ansible_default_ipv4.interface }}" - name: Get NetworkManager connection name for primary interface become: true ansible.builtin.command: > bash -lc "nmcli -t -f NAME,DEVICE,TYPE c show --active | awk -F: -v IF='{{ primary_iface }}' '$2==IF && $3==\"802-3-ethernet\"{print $1; exit}'" register: nm_conn_name changed_when: false failed_when: nm_conn_name.stdout == "" - name: Ensure WOL is persistent (magic) on the NM connection become: true ansible.builtin.shell: > set -euo pipefail; cur=$(nmcli -g 802-3-ethernet.wake-on-lan connection show "{{ nm_conn_name.stdout }}" | tr -d '[:space:]'); if [ "$cur" != "magic" ]; then nmcli connection modify "{{ nm_conn_name.stdout }}" 802-3-ethernet.wake-on-lan magic; echo changed; fi register: nm_wol_set changed_when: "'changed' in nm_wol_set.stdout" - name: Apply WOL for current boot become: true ansible.builtin.command: "ethtool -s {{ primary_iface }} wol g" changed_when: false failed_when: false # --- Performance kernel options --- - name: Tune kernel command line for isolation become: true ansible.builtin.lineinfile: path: "{{ grub_cfg }}" regexp: "^GRUB_CMDLINE_LINUX_DEFAULT=" line: 'GRUB_CMDLINE_LINUX_DEFAULT="{{ grub_options | join(" ") }}"' notify: Update grub - name: Ensure cpupower governor is performance become: true ansible.builtin.command: cpupower frequency-set -g performance changed_when: false failed_when: false - name: Set default target to graphical become: true ansible.builtin.command: systemctl set-default graphical.target changed_when: false - name: Disable Unwanted services become: true ansible.builtin.systemd: name: "{{ item }}" enabled: false state: stopped loop: - gdm.service - ModemManager.service - cups.service - chronyd.service - NetworkManager-wait-online.service - smartd.service # System actions - name: Install Systemd system files become: true ansible.builtin.copy: src: "{{ item }}" dest: "{{ systemd_system_dir }}/{{ item | basename }}" mode: "0644" owner: root group: root loop: "{{ query('fileglob', 'game_station/systemd/system/*') }}" - name: Reload systemd daemon for system services become: true ansible.builtin.systemd: daemon_reload: true - name: Install System scripts become: true ansible.builtin.copy: src: "{{ item }}" dest: "{{ system_bin_dir }}/{{ item | basename }}" owner: root group: root mode: "0755" loop: "{{ query('fileglob', 'game_station/bin/system/*') }}" - name: Create system directories become: true ansible.builtin.file: path: "{{ item }}" state: directory owner: root group: root mode: "0755" loop: - /root/.local/logs - name: Copy wayland session files for SDDM become: true ansible.builtin.copy: src: "{{ item }}" dest: "/usr/share/wayland-sessions/{{ item | basename }}" owner: root group: root mode: "0644" loop: "{{ query('fileglob', 'game_station/wayland-sessions/*') }}" - name: Install SDDM configuration for auto-login become: true ansible.builtin.template: src: "{{ item }}" dest: "/etc/sddm.conf.d/{{ item | basename }}" owner: root group: root mode: "0644" loop: "{{ query('fileglob', 'game_station/sddm.conf/*') }}" - name: Create log file for Jupiter BIOS update (dummy file) become: true ansible.builtin.file: path: /root/.local/logs/jupiter-biosupdate.log state: file owner: root group: root mode: "0644" # User actions - name: Create game group become: true ansible.builtin.group: name: "{{ user_name }}" state: present - name: Create dedicated game user become: true ansible.builtin.user: name: "{{ user_name }}" groups: - "{{ user_name }}" - wheel - video - audio - input shell: /bin/zsh create_home: true state: present - name: Add user to sudoers with no password become: true ansible.builtin.copy: dest: "/etc/sudoers.d/{{ user_name }}" owner: root group: root mode: "0440" content: "{{ user_name }} ALL=(ALL) NOPASSWD: ALL" - name: Create directories become: true ansible.builtin.file: path: "{{ item }}" state: directory owner: "{{ user_name }}" group: "{{ user_name }}" mode: "0755" loop: - "{{ user_bin_dir }}" - "{{ systemd_user_dir }}" - "{{ user_config_dir }}" - "{{ user_config_dir }}/gamescope" - "{{ user_config_dir }}/sunshine" - "{{ user_config_dir }}/steam-rom-manager/userData" - "/home/{{ user_name }}/.local/logs" - "/home/{{ user_name }}/.local/run" - "/home/{{ user_name }}/.local/share/applications" - "/home/{{ user_name }}/.local/share/icons" - "/home/{{ user_name }}/Emulation" - "/home/{{ user_name }}/Emulation/snes" - "/home/{{ user_name }}/Emulation/psp" - "/home/{{ user_name }}/Emulation/genesis" - "/home/{{ user_name }}/RetroArch/shaders" - "/home/{{ user_name }}/RetroArch/config" - "/opt/steamcmd" - "/opt/alvr" - name: Install Systemd user files become: true ansible.builtin.template: src: "{{ item }}" dest: "{{ systemd_user_dir }}/{{ item | basename }}" owner: "{{ user_name }}" group: "{{ user_name }}" mode: "0644" loop: "{{ query('fileglob', 'game_station/systemd/user/*') }}" - name: Reload systemd daemon for user services become: true become_user: "{{ user_name }}" ansible.builtin.systemd: daemon_reload: true scope: user - name: Install AppImages become: true ansible.builtin.get_url: url: "{{ item.url }}" dest: "{{ user_bin_dir }}/{{ item.executable }}" mode: "0755" owner: "{{ user_name }}" group: "{{ user_name }}" loop: "{{ appimages }}" - name: Create symlink for AppImages become: true ansible.builtin.file: src: "{{ user_bin_dir }}/{{ item.executable }}" dest: "{{ user_bin_dir }}/{{ item.link }}" state: link owner: "{{ user_name }}" group: "{{ user_name }}" loop: "{{ appimages }}" - name: Download steamtools become: true ansible.builtin.get_url: url: "https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz" dest: "/tmp/steamcmd_linux.tar.gz" mode: "0644" owner: "{{ user_name }}" group: "{{ user_name }}" register: steamcmd_download - name: Extract steamtools become: true ansible.builtin.unarchive: src: "/tmp/steamcmd_linux.tar.gz" dest: "/opt/steamcmd" remote_src: true owner: "{{ user_name }}" group: "{{ user_name }}" mode: "0755" - name: Create symlink for Steam logs become: true ansible.builtin.file: src: "/home/{{ user_name }}/.steam/steam/logs" dest: "/home/{{ user_name }}/.local/logs/steam" state: link owner: "{{ user_name }}" group: "{{ user_name }}" - name: Copy steam-rom-manager configurations become: true ansible.builtin.copy: src: "game_station/srm/userConfigurations.json" dest: "/home/{{ user_name }}/.config/steam-rom-manager/userData/userConfigurations.json" owner: "{{ user_name }}" group: "{{ user_name }}" mode: "0644" - name: Install User scripts become: true ansible.builtin.copy: src: "{{ item }}" dest: "{{ user_bin_dir }}/{{ item | basename }}" owner: "{{ user_name }}" group: "{{ user_name }}" mode: "0755" loop: "{{ query('fileglob', 'game_station/bin/user/*') }}" - name: Write Gamescope configuration become: true ansible.builtin.template: src: "{{ item }}" dest: "{{ user_config_dir }}/gamescope/{{ item | basename }}" owner: "{{ user_name }}" group: "{{ user_name }}" mode: "0644" loop: "{{ query('fileglob', 'game_station/gamescope/*') }}" - name: Install Sunshine files become: true ansible.builtin.template: src: "{{ item }}" dest: "{{ user_config_dir }}/sunshine/{{ item | basename }}" owner: "{{ user_name }}" group: "{{ user_name }}" mode: "0644" loop: "{{ query('fileglob', 'game_station/sunshine/*') }}" # ALVR installation - name: Install ALVR ansible.builtin.get_url: url: "https://github.com/alvr-org/ALVR/releases/download/v{{ alvr_version }}/alvr_streamer_linux.tar.gz" dest: "/tmp/alvr_streamer_linux.tar.gz" mode: "0644" - name: Extract ALVR become: true ansible.builtin.unarchive: src: "/tmp/alvr_streamer_linux.tar.gz" dest: "/opt/alvr" remote_src: true owner: "{{ user_name }}" group: "{{ user_name }}" mode: "0755" extra_opts: - --strip-components=1 - name: Create symlink for ALVR become: true ansible.builtin.file: src: "/opt/alvr/bin/alvr_dashboard" dest: "{{ user_bin_dir }}/alvr_dashboard" state: link owner: "{{ user_name }}" group: "{{ user_name }}" - name: Copy shortcuts to remote host become: true ansible.builtin.copy: src: "{{ item }}" dest: "/home/{{ user_name }}/.local/share/applications/{{ item | basename }}" owner: "{{ user_name }}" group: "{{ user_name }}" mode: "0644" loop: "{{ query('fileglob', 'game_station/shortcuts/*.desktop') }}" - name: Copy icons to remote host become: true ansible.builtin.copy: src: "{{ item }}" dest: "/home/{{ user_name }}/.local/share/icons/{{ item | basename }}" owner: "{{ user_name }}" group: "{{ user_name }}" mode: "0644" loop: "{{ query('fileglob', 'game_station/shortcuts/icons/*.png') }}" - name: Enable Required services become: true ansible.builtin.systemd: name: "{{ item }}" enabled: true state: started loop: - sddm.service handlers: - name: Update grub ansible.builtin.command: grub2-mkconfig -o /boot/grub2/grub.cfg become: true changed_when: false - name: Run dracut ansible.builtin.command: dracut --regenerate-all --force become: true changed_when: false