--- - 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: "2-7,10-15" sunshine_cores: "1,9" screen_resolution: "{{ lookup('env', 'GAME_RESOLUTION') | d('1280x720', 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 nvidia_modules: - nvidia - nvidia_modeset - nvidia_uvm - nvidia_drm tasks: # --- Base dependencies --- - name: Run 'dnf copr enable lizardbyte/stable' become: true ansible.builtin.command: dnf copr enable -y lizardbyte/stable failed_when: false changed_when: false - 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 - vulkan - vulkan-loader - nvidia-settings - libepoxy - weston - bubblewrap - waypipe - drm-utils - ethtool 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: 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 - sddm.service - bluetooth.service - ModemManager.service - cups.service - avahi-daemon.service - chronyd.service - NetworkManager-wait-online.service - geoclue.service - smartd.service - upower.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 - "{{ systemd_system_dir }}/getty@tty1.service.d" - name: Create override for getty on tty1 become: true ansible.builtin.copy: dest: "{{ systemd_system_dir }}/getty@tty1.service.d/override.conf" owner: root group: root mode: "0644" content: | [Service] ExecStart= ExecStart=-/usr/bin/agetty --autologin {{ user_name }} --noclear %I $TERM Type=idle - name: Enable user lingering for game user become: true ansible.builtin.command: loginctl enable-linger {{ user_name }} changed_when: false failed_when: false - 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 }}/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" - 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: 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/*') }}" - name: Enable User session service login become: true become_user: "{{ user_name }}" ansible.builtin.systemd: name: steamos.service enabled: true state: started scope: user 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 --force become: true changed_when: false