diff --git a/Readme.MD b/Readme.MD index 84bb513..74a69f9 100644 --- a/Readme.MD +++ b/Readme.MD @@ -30,15 +30,43 @@ sectool exec cntb get instances --oauth2-clientid="$CONTABO_CLIENT_ID" --oauth2- # Windows -## Create a Dedicated “Ansible” User (One-Time Setup) +## Create a Dedicated “Ansible” User (One-Time Setup) — Linux ``` -net user provision /add -net localgroup Administrators provision /add -# Prevent password expiration -wmic UserAccount where "Name='provision'" set PasswordExpires=FALSE +# Create the provision user (Debian/Ubuntu) +sudo useradd -m -s /bin/bash -G sudo provision +sudo passwd provision + +# OR (RHEL/CentOS/Fedora) +sudo useradd -m -s /bin/bash -G wheel provision +sudo passwd provision ``` +``` +# Allow passwordless sudo for the provision user (recommended for Ansible) +echo 'provision ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/provision +sudo chmod 440 /etc/sudoers.d/provision + +# Or edit safely: +sudo visudo -f /etc/sudoers.d/provision +# and add: +# provision ALL=(ALL) NOPASSWD:ALL +``` + +``` +# Install SSH authorized key for the provision user (recommended over passwords) +sudo -u provision mkdir -p /home/provision/.ssh +sudo -u provision chmod 700 /home/provision/.ssh +echo "" | sudo -u provision tee /home/provision/.ssh/authorized_keys +sudo -u provision chmod 600 /home/provision/.ssh/authorized_keys +``` + +Notes: +- Replace with the actual public key. +- Use a strong password if you must set one; prefer SSH keys. +- For tighter security, restrict the sudoers entry to only the commands Ansible requires instead of ALL. +- Validate sudoers syntax with visudo to avoid lockout. + ## Configure windows to allow Ansible to connect via WinRM Copy the script located in scripts "ConfigureRemotingForAnsible.ps1" and run it on the target machine. diff --git a/ansible/files/game_station/srm/userConfigurations.json b/ansible/files/game_station/srm/userConfigurations.json new file mode 100644 index 0000000..6d665ca --- /dev/null +++ b/ansible/files/game_station/srm/userConfigurations.json @@ -0,0 +1,368 @@ +[ + { + "parserType": "Glob", + "configTitle": "Sony PlayStation 3 - RPCS3 (Extracted ISO)", + "steamDirectory": "${steamdirglobal}", + "romDirectory": "/home/gameuser/.config/rpcs3/games", + "steamCategories": [ + "PS3" + ], + "executableArgs": "\"${filePath}\"", + "executableModifier": "\"${exePath}\"", + "startInDirectory": "", + "titleModifier": "${fuzzyTitle}", + "steamInputEnabled": "1", + "imageProviders": [ + "sgdb" + ], + "onlineImageQueries": [ + "${fuzzyTitle}" + ], + "imagePool": "${fuzzyTitle}", + "drmProtect": false, + "userAccounts": { + "specifiedAccounts": [ + "Global" + ] + }, + "parserInputs": { + "glob": "${title}/PS3_GAME/USRDIR/@(eboot.bin|EBOOT.BIN)" + }, + "executable": { + "path": "/home/gameuser/.local/bin/launch_rpcs3.sh", + "shortcutPassthrough": false, + "appendArgsToExecutable": true + }, + "titleFromVariable": { + "limitToGroups": [ + "PS3" + ], + "caseInsensitiveVariables": false, + "skipFileIfVariableWasNotFound": false + }, + "fuzzyMatch": { + "replaceDiacritics": true, + "removeCharacters": true, + "removeBrackets": true + }, + "controllers": { + "ps4": null, + "ps5": null, + "ps5_edge": null, + "xbox360": null, + "xboxone": null, + "xboxelite": null, + "switch_joycon_left": null, + "switch_joycon_right": null, + "switch_pro": null, + "neptune": null, + "steamcontroller_gordon": null + }, + "imageProviderAPIs": { + "sgdb": { + "nsfw": false, + "humor": false, + "styles": [], + "stylesHero": [], + "stylesLogo": [], + "stylesIcon": [], + "imageMotionTypes": [ + "static" + ], + "sizes": [], + "sizesHero": [], + "sizesIcon": [] + } + }, + "defaultImage": { + "tall": "", + "long": "", + "hero": "", + "logo": "", + "icon": "" + }, + "localImages": { + "tall": "", + "long": "", + "hero": "", + "logo": "", + "icon": "" + }, + "parserId": "176098777083347867", + "disabled": false, + "version": 25 + }, + { + "parserType": "Glob", + "configTitle": "Sega Genesis/Mega Drive - Retroarch - Genesis Plus GX", + "steamDirectory": "${steamdirglobal}", + "romDirectory": "/home/gameuser/Emulation/genesis", + "steamCategories": [ + "Genesis/Mega Drive" + ], + "executableArgs": "\"${filePath}\"", + "executableModifier": "\"${exePath}\"", + "startInDirectory": "", + "titleModifier": "${fuzzyTitle}", + "steamInputEnabled": "1", + "imageProviders": [ + "sgdb" + ], + "onlineImageQueries": [ + "${fuzzyTitle}" + ], + "imagePool": "${fuzzyTitle}", + "drmProtect": false, + "userAccounts": { + "specifiedAccounts": [ + "Global" + ] + }, + "parserInputs": { + "glob": "${title}@(.7z|.7Z|.gen|.GEN|.md|.MD|.smd|.SMD|.zip|.ZIP|.bin|.BIN)" + }, + "executable": { + "path": "/home/gameuser/.local/bin/launch_genesis.sh", + "shortcutPassthrough": false, + "appendArgsToExecutable": true + }, + "titleFromVariable": { + "limitToGroups": [], + "caseInsensitiveVariables": false, + "skipFileIfVariableWasNotFound": false + }, + "fuzzyMatch": { + "replaceDiacritics": true, + "removeCharacters": true, + "removeBrackets": true + }, + "controllers": { + "ps4": null, + "ps5": null, + "ps5_edge": null, + "xbox360": null, + "xboxone": null, + "xboxelite": null, + "switch_joycon_left": null, + "switch_joycon_right": null, + "switch_pro": null, + "neptune": null, + "steamcontroller_gordon": null + }, + "imageProviderAPIs": { + "sgdb": { + "nsfw": false, + "humor": false, + "styles": [], + "stylesHero": [], + "stylesLogo": [], + "stylesIcon": [], + "imageMotionTypes": [ + "static" + ], + "sizes": [], + "sizesHero": [], + "sizesIcon": [] + } + }, + "defaultImage": { + "tall": "", + "long": "", + "hero": "", + "logo": "", + "icon": "" + }, + "localImages": { + "tall": "", + "long": "", + "hero": "", + "logo": "", + "icon": "" + }, + "parserId": "176029625316137679", + "disabled": false, + "version": 25 + }, + { + "parserType": "Glob", + "configTitle": "Sony PlayStation Portable - Retroarch - PPSSPP", + "steamDirectory": "${steamdirglobal}", + "romDirectory": "/home/gameuser/Emulation/psp", + "steamCategories": [ + "PSP" + ], + "executableArgs": "\"${filePath}\"", + "executableModifier": "\"${exePath}\"", + "startInDirectory": "", + "titleModifier": "${fuzzyTitle}", + "steamInputEnabled": "1", + "imageProviders": [ + "sgdb" + ], + "onlineImageQueries": [ + "${fuzzyTitle}" + ], + "imagePool": "${fuzzyTitle}", + "drmProtect": false, + "userAccounts": { + "specifiedAccounts": [ + "Global" + ] + }, + "parserInputs": { + "glob": "${title}@(.7z|.7Z|.elf|.ELF|.cso|.CSO|.iso|.ISO|.pbp|.PBP|.prx|.PRX)" + }, + "executable": { + "path": "/home/gameuser/.local/bin/launch_ppspp.sh", + "shortcutPassthrough": false, + "appendArgsToExecutable": true + }, + "titleFromVariable": { + "limitToGroups": [], + "caseInsensitiveVariables": false, + "skipFileIfVariableWasNotFound": false + }, + "fuzzyMatch": { + "replaceDiacritics": true, + "removeCharacters": true, + "removeBrackets": true + }, + "controllers": { + "ps4": null, + "ps5": null, + "ps5_edge": null, + "xbox360": null, + "xboxone": null, + "xboxelite": null, + "switch_joycon_left": null, + "switch_joycon_right": null, + "switch_pro": null, + "neptune": null, + "steamcontroller_gordon": null + }, + "imageProviderAPIs": { + "sgdb": { + "nsfw": false, + "humor": false, + "styles": [], + "stylesHero": [], + "stylesLogo": [], + "stylesIcon": [], + "imageMotionTypes": [ + "static" + ], + "sizes": [], + "sizesHero": [], + "sizesIcon": [] + } + }, + "defaultImage": { + "tall": "", + "long": "", + "hero": "", + "logo": "", + "icon": "" + }, + "localImages": { + "tall": "", + "long": "", + "hero": "", + "logo": "", + "icon": "" + }, + "parserId": "176029685701892152", + "disabled": false, + "version": 25 + }, + { + "parserType": "Glob", + "configTitle": "Nintendo SNES - Retroarch - Mesen-S", + "steamDirectory": "${steamdirglobal}", + "romDirectory": "/home/gameuser/Emulation/snes", + "steamCategories": [ + "SNES" + ], + "executableArgs": "\"${filePath}\"", + "executableModifier": "\"${exePath}\"", + "startInDirectory": "", + "titleModifier": "${fuzzyTitle}", + "steamInputEnabled": "1", + "imageProviders": [ + "sgdb" + ], + "onlineImageQueries": [ + "${fuzzyTitle}" + ], + "imagePool": "${fuzzyTitle}", + "drmProtect": false, + "userAccounts": { + "specifiedAccounts": [ + "Global" + ] + }, + "parserInputs": { + "glob": "${title}@(.7z|.7Z|.bml|.BML|.sfc|.SFC|.smc|.SMC|.zip|.ZIP)" + }, + "executable": { + "path": "/home/gameuser/.local/bin/launch_snes.sh", + "shortcutPassthrough": false, + "appendArgsToExecutable": true + }, + "titleFromVariable": { + "limitToGroups": [], + "caseInsensitiveVariables": false, + "skipFileIfVariableWasNotFound": false + }, + "fuzzyMatch": { + "replaceDiacritics": true, + "removeCharacters": true, + "removeBrackets": true + }, + "controllers": { + "ps4": null, + "ps5": null, + "ps5_edge": null, + "xbox360": null, + "xboxone": null, + "xboxelite": null, + "switch_joycon_left": null, + "switch_joycon_right": null, + "switch_pro": null, + "neptune": null, + "steamcontroller_gordon": null + }, + "imageProviderAPIs": { + "sgdb": { + "nsfw": false, + "humor": false, + "styles": [], + "stylesHero": [], + "stylesLogo": [], + "stylesIcon": [], + "imageMotionTypes": [ + "static" + ], + "sizes": [], + "sizesHero": [], + "sizesIcon": [] + } + }, + "defaultImage": { + "tall": "", + "long": "", + "hero": "", + "logo": "", + "icon": "" + }, + "localImages": { + "tall": "", + "long": "", + "hero": "", + "logo": "", + "icon": "" + }, + "parserId": "176029713768938935", + "disabled": false, + "version": 25 + } +] \ No newline at end of file diff --git a/ansible/files/game_station/system/jupiter-biosupdate b/ansible/files/game_station/system/jupiter-biosupdate new file mode 100644 index 0000000..091cfd2 --- /dev/null +++ b/ansible/files/game_station/system/jupiter-biosupdate @@ -0,0 +1,9 @@ +#!/bin/bash +set -euo pipefail +LOGFILE="$HOME/.local/logs/jupiter-biosupdate.log" + +log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; } + +log "Starting Jupiter BIOS update..." +log "Arguments passed: ${@}" +echo "Not applicable for this OS" diff --git a/ansible/files/game_station/system/launch_gamescope.sh b/ansible/files/game_station/system/launch_gamescope.sh new file mode 100644 index 0000000..942d1d2 --- /dev/null +++ b/ansible/files/game_station/system/launch_gamescope.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -euo pipefail +LOGFILE="$HOME/.local/logs/gamescope.log" + +log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; } + +log "Starting Gamescope session..." +CONF_FILE="$HOME/.config/gamescope/gamescope.conf" +[ -f "$CONF_FILE" ] && source "$CONF_FILE" + +OUTPUT_CONNECTOR=${OUTPUT_CONNECTOR:-HDMI-A-1} +FRAMERATE=${FRAMERATE:-60} +RESOLUTION=${RESOLUTION:-1920x1080} +WIDTH="${RESOLUTION%x*}" +HEIGHT="${RESOLUTION#*x}" + +export __GL_GSYNC_ALLOWED=1 +export __GL_VRR_ALLOWED=1 +# export __GL_THREADED_OPTIMIZATIONS=1 +export __GL_SHADER_DISK_CACHE=1 +export __GL_SYNC_TO_VBLANK=0 + +GAMESCOPE_BIN="$(command -v gamescope || true)" +if [ -z "$GAMESCOPE_BIN" ]; then + log "ERROR: gamescope not found in PATH" + exit 127 +fi + +HDR_FLAG="" +if [[ "$HDR_ENABLED" == "1" || "$HDR_ENABLED" == "true" || "$HDR_ENABLED" == "yes" ]]; then + HDR_FLAG="--hdr-enabled" + log "HDR flag added: --hdr-enabled" +else + log "HDR disabled, no HDR flag added" +fi + +GAMESCOPE_ARGS="--generate-drm-mode fixed --fade-out-duration 200 --cursor-scale-height 720 -f -w $WIDTH -h $HEIGHT --rt --mangoapp -e -O \"$OUTPUT_CONNECTOR\" -r $FRAMERATE $HDR_FLAG" +log "Launching Gamescope: $GAMESCOPE_BIN $GAMESCOPE_ARGS -- /usr/local/bin/launch_steam.sh" +exec "$GAMESCOPE_BIN" ${GAMESCOPE_ARGS} -- /usr/local/bin/launch_steam.sh >>"$LOGFILE" 2>&1 diff --git a/ansible/files/game_station/system/launch_steam.sh b/ansible/files/game_station/system/launch_steam.sh new file mode 100644 index 0000000..8d9c1be --- /dev/null +++ b/ansible/files/game_station/system/launch_steam.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +set -euo pipefail +LOGFILE="$HOME/.local/logs/steam.log" +export PATH="$HOME/.local/bin:$PATH" + +export SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS=0 + +# There is no way to set a color space for an NV12 +# buffer in Wayland. And the color management protocol that is +# meant to let this happen is missing the color range... +# So just workaround this with an ENV var that Remote Play Together +# and Gamescope will use for now. +export GAMESCOPE_NV12_COLORSPACE=k_EStreamColorspace_BT601 +export STEAM_GAMESCOPE_HDR_SUPPORTED=1 + +# Workaround older versions of vkd3d-proton setting this +# too low (desc.BufferCount), resulting in symptoms that are potentially like +# swapchain starvation. +export VKD3D_SWAPCHAIN_LATENCY_FRAMES=3 + +# Let's try this across the board to see if it breaks anything +# Helps performance in HZD, Cyberpunk, at least +# Expose 8 physical cores, instead of 4c/8t +export WINE_CPU_TOPOLOGY=8:0,1,2,3,4,5,6,7 + +# To expose vram info from radv's patch we're including +export WINEDLLOVERRIDES=dxgi=n + +# Disable automatic audio device switching in steam, now handled by wireplumber +export STEAM_DISABLE_AUDIO_DEVICE_SWITCHING=1 + +# Enable support for xwayland isolation per-game in Steam +# export STEAM_MULTIPLE_XWAYLANDS=0 + +# We have NIS support +export STEAM_GAMESCOPE_NIS_SUPPORTED=1 + +# Enable tearing controls in steam +export STEAM_GAMESCOPE_TEARING_SUPPORTED=1 + +# Enable VRR controls in steam +export STEAM_GAMESCOPE_VRR_SUPPORTED=1 + +# When set to 1, a toggle will show up in the steamui to control whether dynamic refresh rate is applied to the steamui +export STEAM_GAMESCOPE_DYNAMIC_REFRESH_IN_STEAM_SUPPORTED=0 + +# Don't wait for buffers to idle on the client side before sending them to gamescope +export vk_xwayland_wait_ready=false + +# Scaling support +export STEAM_GAMESCOPE_FANCY_SCALING_SUPPORT=1 + +# Color management support +export STEAM_GAMESCOPE_COLOR_MANAGED=1 +export STEAM_GAMESCOPE_VIRTUAL_WHITE=1 + +# Temporary crutch until dummy plane interactions / etc are figured out +export GAMESCOPE_DISABLE_ASYNC_FLIPS=1 + +export XCURSOR_THEME=steam +export XCURSOR_SCALE=256 + +log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; } + +if [ -r /proc/sys/user/max_user_namespaces ]; then + max_ns="$(cat /proc/sys/user/max_user_namespaces || echo 0)" + if [ "${max_ns:-0}" -eq 0 ]; then + log "ERROR: user.max_user_namespaces is 0. Enable it with: sudo sysctl -w user.max_user_namespaces=15000" + exit 1 + fi +fi + +if command -v bwrap >/dev/null 2>&1; then + bwrap_bin="$(command -v bwrap)" + if ! [ -u "$bwrap_bin" ]; then + # Not fatal if user namespaces are enabled, but warn for visibility + log "WARN: $bwrap_bin is not setuid root. If Steam still fails, run: sudo dnf reinstall bubblewrap && sudo chmod 4755 /usr/bin/bwrap" + fi +else + log "WARN: bubblewrap (bwrap) not found. Steam may fail to start." +fi + +if findmnt -no OPTIONS / | grep -qw nosuid; then + log "WARN: root filesystem is mounted nosuid. Setuid helpers like bubblewrap will not work." +fi + +STEAM_BIN="$(command -v steam || true)" +if [ -z "$STEAM_BIN" ]; then + log "ERROR: steam not found in PATH" + exit 127 +fi + +log "Updating steam: /opt/steamcmd/steamcmd.sh +quit" +/opt/steamcmd/steamcmd.sh +quit >>"$LOGFILE" 2>&1 || { + log "ERROR: Steam update failed" + exit 1 +} + +log "Updating Steam ROM Manager" +srm add >> "$LOGFILE" 2>&1 || log "ERROR: Steam ROM Manager update failed" + +STEAM_ARGS=("-steamdeck" "-steamos3" "-steampal" "-gamepadui" "-pipewire-dmabuf") +log "Launching Steam: $STEAM_BIN ${STEAM_ARGS[*]}" +exec steam "${STEAM_ARGS[@]}" & + +sleep 5 +systemd-run --user --slice=sunshine.slice --scope sunshine >>"$LOGFILE" 2>&1 || { + log "ERROR: Failed to start Sunshine" + exit 1 +} & + +tail -f /dev/null \ No newline at end of file diff --git a/ansible/files/game_station/system/steamos-select-branch b/ansible/files/game_station/system/steamos-select-branch new file mode 100644 index 0000000..54f1e5d --- /dev/null +++ b/ansible/files/game_station/system/steamos-select-branch @@ -0,0 +1,30 @@ +#!/bin/bash +set -euo pipefail +LOGFILE="$HOME/.local/logs/steamos-select-branch.log" + +log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; } + +if [[ $# -eq 1 ]]; then + case "$1" in + "-c") + log "Current branch requested" + echo "stable" + exit 0 + ;; + "-l") + log "Listing available branches" + exit 0 + ;; + "rel") + log "Legacy branch 'rel' requested" + exit 0 + ;; + "stable" | "rc" | "beta" | "bc" | "preview" | "pc" | "main" | "staging") + log "Branch '$1' requested" + exit 0 + ;; + esac +fi + +echo "Usage: steamos-select-branch <-c|-l|rel|rc|beta|bc|preview|pc|main>" 1>&2 + \ No newline at end of file diff --git a/ansible/files/game_station/system/steamos-update b/ansible/files/game_station/system/steamos-update new file mode 100644 index 0000000..912c590 --- /dev/null +++ b/ansible/files/game_station/system/steamos-update @@ -0,0 +1,11 @@ +#!/bin/bash +set -euo pipefail +LOGFILE="$HOME/.local/logs/steamos-update.log" + +log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; } + +log "Starting SteamOS update..." +log "Arguments passed: ${@}" +echo "Not applicable for this OS" +# TODO: Implement SteamOS update logic for now inform no update available +exit 7 \ No newline at end of file diff --git a/ansible/files/game_station/systemd/system/gamescope-session.service b/ansible/files/game_station/systemd/system/gamescope-session.service new file mode 100644 index 0000000..53bcdbe --- /dev/null +++ b/ansible/files/game_station/systemd/system/gamescope-session.service @@ -0,0 +1,29 @@ +[Unit] +Description=Weston Wayland compositor for user gameuser +Wants=graphical.target +Conflicts=getty@tty1.service +After=getty@tty1.service +After=rc-local.service plymouth-start.service systemd-user-sessions.service + +[Service] +Slice=gamescope.slice +User=gameuser +PAMName=login +TTYPath=/dev/tty1 +TTYReset=yes +TTYVHangup=yes +StandardInput=tty +StandardOutput=journal +StandardError=journal +UtmpIdentifier=tty1 +UtmpMode=user +Environment=XDG_SEAT=seat0 +Environment=XDG_RUNTIME_DIR=/run/user/%U +Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/%U/bus +ExecStartPre=/usr/bin/chvt 1 +ExecStart=/usr/bin/bash /usr/local/bin/launch_gamescope.sh +Restart=always +KeyringMode=shared + +[Install] +WantedBy=graphical.target diff --git a/ansible/files/game_station/user/launch_genesis.sh b/ansible/files/game_station/user/launch_genesis.sh new file mode 100644 index 0000000..9282d65 --- /dev/null +++ b/ansible/files/game_station/user/launch_genesis.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail +LOGFILE="$HOME/.local/logs/retroarch-genesis.log" + +log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; } +RETROARCH_HOME="$HOME/.local/share/Steam/steamapps/common/RetroArch" +if [ ! -d "$RETROARCH_HOME" ]; then + log "ERROR: RetroArch directory not found at $RETROARCH_HOME, install it via Steam" + exit 127 +fi + +RETROARCH_BIN="$RETROARCH_HOME/retroarch" +RETROARCH_CORE=genesis_plus_gx_libretro.so +if [ ! -f "$RETROARCH_BIN" ] || [ ! -f "$RETROARCH_HOME/cores/$RETROARCH_CORE" ]; then + log "ERROR: retroarch or core not found, install it via Steam" + exit 127 +fi + +log "Launching RetroArch: $RETROARCH_BIN -L \"$RETROARCH_HOME/cores/$RETROARCH_CORE\" \"${@}\"" +cd "$RETROARCH_HOME" +$HOME/.steam/steam/ubuntu12_32/steam-runtime/run.sh "$RETROARCH_BIN" -L "$RETROARCH_HOME/cores/$RETROARCH_CORE" "${@}" >>"$LOGFILE" + diff --git a/ansible/files/game_station/user/launch_ppspp.sh b/ansible/files/game_station/user/launch_ppspp.sh new file mode 100644 index 0000000..9dc605d --- /dev/null +++ b/ansible/files/game_station/user/launch_ppspp.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail +LOGFILE="$HOME/.local/logs/retroarch-ppsspp.log" + +log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; } +RETROARCH_HOME="$HOME/.local/share/Steam/steamapps/common/RetroArch" +if [ ! -d "$RETROARCH_HOME" ]; then + log "ERROR: RetroArch directory not found at $RETROARCH_HOME, install it via Steam" + exit 127 +fi + +RETROARCH_BIN="$RETROARCH_HOME/retroarch" +RETROARCH_CORE=ppsspp_libretro.so +if [ ! -f "$RETROARCH_BIN" ] || [ ! -f "$RETROARCH_HOME/cores/$RETROARCH_CORE" ]; then + log "ERROR: retroarch or core not found, install it via Steam" + exit 127 +fi + +log "Launching RetroArch: $RETROARCH_BIN -L \"$RETROARCH_HOME/cores/$RETROARCH_CORE\" \"${@}\"" +"$RETROARCH_BIN" -L "$RETROARCH_HOME/cores/$RETROARCH_CORE" "${@}" >>"$LOGFILE" diff --git a/ansible/files/game_station/user/launch_rpcs3.sh b/ansible/files/game_station/user/launch_rpcs3.sh new file mode 100644 index 0000000..af8594e --- /dev/null +++ b/ansible/files/game_station/user/launch_rpcs3.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail +LOGFILE="$HOME/.local/logs/rpcs3.log" + +log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; } + +RPCS_BIN="$(command -v rpcs3 || true)" +if [ -z "$RPCS_BIN" ]; then + log "ERROR: rpcs3 not found in PATH" + exit 127 +fi + +log "Launching RPCS3: $RPCS_BIN \"${@}\"" +"$RPCS_BIN" --no-gui --fullscreen "${@}" >>"$LOGFILE" + diff --git a/ansible/files/game_station/user/launch_snes.sh b/ansible/files/game_station/user/launch_snes.sh new file mode 100644 index 0000000..2ae190f --- /dev/null +++ b/ansible/files/game_station/user/launch_snes.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail +LOGFILE="$HOME/.local/logs/retroarch-snes.log" + +log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; } +RETROARCH_HOME="$HOME/.local/share/Steam/steamapps/common/RetroArch" +if [ ! -d "$RETROARCH_HOME" ]; then + log "ERROR: RetroArch directory not found at $RETROARCH_HOME, install it via Steam" + exit 127 +fi + +RETROARCH_BIN="$RETROARCH_HOME/retroarch" +RETROARCH_CORE=mesen-s_libretro.so.so +if [ ! -f "$RETROARCH_BIN" ] || [ ! -f "$RETROARCH_HOME/cores/$RETROARCH_CORE" ]; then + log "ERROR: retroarch or core not found, install it via Steam" + exit 127 +fi + +log "Launching RetroArch: $RETROARCH_BIN -L \"$RETROARCH_HOME/cores/$RETROARCH_CORE\" \"${@}\"" +cd "$RETROARCH_HOME" +"$RETROARCH_BIN" -L "$RETROARCH_HOME/cores/$RETROARCH_CORE" "${@}" >>"$LOGFILE" diff --git a/ansible/playbook_game_station.yml b/ansible/playbook_game_station.yml new file mode 100644 index 0000000..596d55b --- /dev/null +++ b/ansible/playbook_game_station.yml @@ -0,0 +1,293 @@ +--- +- name: Configure Fedora 42 for optimized Sunshine + Gamescope streaming + hosts: game_station + vars: + user_name: "gameuser" + gamescope_conf_dir: "/home/{{ user_name }}/.config/gamescope" + system_bin_dir: "/usr/local/bin" + 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" + gamescope_cores: "2-7,10-15" + sunshine_cores: "1,9" + + 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 + 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 + + # --- 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="quiet splash amd_pstate=active mitigations=off threadirqs nohz_full=1-7,9-15 rcu_nocbs=1-7,9-15"' + 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 GDM display manager + become: true + ansible.builtin.systemd: + name: gdm + enabled: false + state: stopped + + # System actions + - name: Create Gamescope slice + become: true + ansible.builtin.copy: + dest: "{{ systemd_system_dir }}/gamescope.slice" + owner: root + group: root + mode: "0644" + content: | + [Slice] + CPUWeight=100 + AllowedCPUs={{ gamescope_cores }} + + - name: Install Systemd service files + become: true + ansible.builtin.copy: + src: "{{ item }}" + dest: "/etc/systemd/system/{{ item | basename }}" + mode: "0644" + owner: root + group: root + loop: "{{ query('fileglob', 'game_station/systemd/system/*.service') }}" + notify: Reload systemd daemon for system services + + - 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/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: Create log file for Jupiter BIOS update (dummy file) + become: true + ansible.builtin.file: + path: /root/.local/logs/jupiter-biosupdate.log + state: touch + 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/bash + 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: + - "{{ gamescope_conf_dir }}" + - "{{ user_bin_dir }}" + - "{{ systemd_user_dir }}" + - "/home/{{ user_name }}/.local/logs" + - "/opt/steamcmd" + - "/home/{{ user_name }}/.config/steam-rom-manager/userData" + - "/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" + + - 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: Write Gamescope configuration + become: true + ansible.builtin.copy: + dest: "{{ gamescope_conf_dir }}/gamescope.conf" + owner: "{{ user_name }}" + mode: "0644" + content: | + OUTPUT_CONNECTOR=HDMI-A-1 + FRAMERATE=120 + RESOLUTION={{ lookup('env', 'GAME_RESOLUTION') | default('1920x1080') }} + HDR_ENABLED={{ lookup('env', 'HDR_ENABLED') | default('false') }} + DRM_DEVICE=/dev/dri/card1 + + - name: Create Sunshine slice + become: true + ansible.builtin.copy: + dest: "{{ systemd_user_dir }}/sunshine.slice" + owner: "{{ user_name }}" + mode: "0644" + content: | + [Slice] + CPUWeight=100 + AllowedCPUs={{ sunshine_cores }} + + - 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/user/*') }}" + + - name: Enable Gamescope session service on boot + become: true + ansible.builtin.systemd: + name: gamescope-session.service + enabled: true + state: started + + + handlers: + - name: Update grub + ansible.builtin.command: grub2-mkconfig -o /boot/grub2/grub.cfg + become: true + changed_when: false + + - name: Reload systemd daemon for system services + become: true + ansible.builtin.systemd: + daemon_reload: true diff --git a/inventory/hosts b/inventory/hosts index 21648ba..a81d2c6 100644 --- a/inventory/hosts +++ b/inventory/hosts @@ -110,3 +110,6 @@ dev-01.lab.alexpires.me [windows_prometheus] game-01.lab.alexpires.me hpz440.lab.alexpires.me + +[game_station] +steambox.local \ No newline at end of file diff --git a/scripts/gamescope-steam-session b/scripts/gamescope-steam-session new file mode 100644 index 0000000..27db420 --- /dev/null +++ b/scripts/gamescope-steam-session @@ -0,0 +1,59 @@ +#!/bin/bash +echo "Starting sunshine service..." + +if [ -f "$HOME/.config/gamescope/gamescope.conf" ]; then + source "$HOME/.config/gamescope/gamescope.conf" +fi +OUTPUT_CONNECTOR=${OUTPUT_CONNECTOR:-HDMI-A-1} +FRAMERATE=${FRAMERATE:-120} +RESOLUTION=${RESOLUTION:-1920x1080} +HDR_ENABLED=${HDR_ENABLED:-${HDR:-1}} + +echo "Building gamescope command..." + +# Choose backend based on environment +BACKEND_ARGS="" +DRM_ARGS="" +IN_WAYLAND=0 +if [ -n "${WAYLAND_DISPLAY:-}" ] || [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then + IN_WAYLAND=1 + BACKEND_ARGS="--backend wayland" +fi + +# HDR only relevant for DRM path generally +HDR_FLAG="" +if [[ "$HDR_ENABLED" == "1" || "$HDR_ENABLED" == "true" || "$HDR_ENABLED" == "yes" ]]; then + HDR_FLAG="--hdr-enabled" + echo " HDR flag added: --hdr-enabled" +else + echo " HDR disabled, no HDR flag added" +fi + +WIDTH="${RESOLUTION%x*}" +HEIGHT="${RESOLUTION#*x}" + +if [ "$IN_WAYLAND" -eq 0 ]; then + DRM_ARGS="-O \"$OUTPUT_CONNECTOR\" -r \"$FRAMERATE\" $HDR_FLAG" +fi + +echo "" +echo "Final Configuration Summary:" +echo " Backend: $([ "$IN_WAYLAND" -eq 1 ] && echo Wayland || echo DRM/KMS)" +echo " Output Connector: ${OUTPUT_CONNECTOR}" +echo " Framerate: ${FRAMERATE}Hz" +echo " Resolution: ${RESOLUTION}" +echo " HDR: $([ -n "$HDR_FLAG" ] && echo "enabled" || echo "disabled")" +echo " MangoHUD: enabled" +echo " Steam Mode: SteamOS3" + +echo "" +echo "Launching gamescope session..." +echo "Full command:" +echo " gamescope $BACKEND_ARGS $DRM_ARGS --mangoapp -w $WIDTH -h $HEIGHT -e -- /usr/bin/steamos" +echo "" +echo "=========================================" + +LOGDIR="${HOME}/.local/logs" +mkdir -p "$LOGDIR" +echo "[$(date --iso-8601=seconds)] Launching (backend: $([ "$IN_WAYLAND" -eq 1 ] && echo wayland || echo drm)): gamescope $BACKEND_ARGS $DRM_ARGS --mangoapp -w $WIDTH -h $HEIGHT -e -- /usr/bin/steamos" >>"$LOGDIR/gamescope-launch.log" +exec bash -lc "gamescope $BACKEND_ARGS $DRM_ARGS --mangoapp -w $WIDTH -h $HEIGHT -e -- /usr/bin/steamos" diff --git a/scripts/steamos b/scripts/steamos new file mode 100644 index 0000000..80e0f23 --- /dev/null +++ b/scripts/steamos @@ -0,0 +1,7 @@ +#!/bin/bash +LOGDIR="${HOME}/.local/logs" +mkdir -p "$LOGDIR" +echo "Starting steamos session..." +systemctl --user start sunshine.service +LOGFILE="${LOGDIR}/steam.log" +steam -steamos3 -pipewire-dmabuf -gamepadui >>"$LOGFILE" 2>&1 diff --git a/terraform/apps/dev-01/exporters.tf b/terraform/apps/dev-01/exporters.tf index 4ec827d..4317b7f 100644 --- a/terraform/apps/dev-01/exporters.tf +++ b/terraform/apps/dev-01/exporters.tf @@ -16,10 +16,10 @@ module "disk_exporter" { standby_mode = false } -module "devolo_exporter" { - for_each = local.devolo_devices - source = "../../modules/utils/devolo-expporter" - devolo_ip = each.value.ip - devolo_model = each.value.model -} +# module "devolo_exporter" { +# for_each = local.devolo_devices +# source = "../../modules/utils/devolo-expporter" +# devolo_ip = each.value.ip +# devolo_model = each.value.model +# } diff --git a/terraform/apps/dev-01/resources/prometheus.yml b/terraform/apps/dev-01/resources/prometheus.yml index 17fbb81..1577156 100644 --- a/terraform/apps/dev-01/resources/prometheus.yml +++ b/terraform/apps/dev-01/resources/prometheus.yml @@ -11,7 +11,7 @@ scrape_configs: scrape_interval: 10s static_configs: - targets: - - "auth-exporter.prometheus.svc.cluster.local:9100" + - " -exporter.prometheus.svc.cluster.local:9100" - labels: cluster: "dev-01" - job_name: disk-exporter @@ -21,9 +21,3 @@ scrape_configs: - "disk-exporter.prometheus.svc.cluster.local:9100" - labels: cluster: "dev-01" - - job_name: devolo-exporter - scrape_interval: 30s - static_configs: - - targets: - - "devolo-exporter-620.prometheus.svc.cluster.local:9100" - # - "devolo-exporter-326.prometheus.svc.cluster.local:9100"