Skip to content

Instantly share code, notes, and snippets.

@stikkx
Last active June 23, 2026 13:36
Show Gist options
  • Select an option

  • Save stikkx/2a413386a5465d213d36fac8c78d0dfd to your computer and use it in GitHub Desktop.

Select an option

Save stikkx/2a413386a5465d213d36fac8c78d0dfd to your computer and use it in GitHub Desktop.
CachyOSSimRacer
#!/usr/bin/env bash
#
# CachyOS Sim-Racing Setup
# -------------------------------------------------------------------
# Installiert alles Noetige, um Assetto Corsa & RaceRoom mit einem
# Fanatec-Wheel auf CachyOS zu fahren:
# - Steam + Proton-Voraussetzungen (32-bit)
# - AMD-Vulkan-Treiber fuer die Radeon-iGPU des T14 (Proton/DXVK)
# - MangoHud + GameMode
# - Fanatec Force-Feedback-Treiber (hid-fanatecff, DKMS-Kernelmodul)
# - Oversteer (GUI zum Einstellen von Lenkbereich & FFB)
# - gamescope (fuer die Konsolen-/Boot-Session)
# - OPTIONAL: Boot-Auswahlmenue, das nach dem Login direkt erscheint
# und per Default RaceRoom startet (--with-boot-menu)
#
# WICHTIG: NICHT als root starten. Normal als dein User ausfuehren,
# sudo wird bei Bedarf abgefragt.
#
# Aufruf:
# ./cachyos-simracing-setup.sh # nur Installation
# ./cachyos-simracing-setup.sh --with-boot-menu # + Boot-Auswahlmenue
# -------------------------------------------------------------------
set -euo pipefail
# --- Optionen ------------------------------------------------------
WITH_BOOT_MENU=0
for arg in "$@"; do
case "$arg" in
--with-boot-menu) WITH_BOOT_MENU=1 ;;
-h|--help)
grep -E '^#( |$)' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
*) echo "Unbekannte Option: $arg" >&2; exit 1 ;;
esac
done
# --- Logging -------------------------------------------------------
c_info() { printf '\033[1;34m[*]\033[0m %s\n' "$*"; }
c_ok() { printf '\033[1;32m[OK]\033[0m %s\n' "$*"; }
c_warn() { printf '\033[1;33m[!]\033[0m %s\n' "$*"; }
c_err() { printf '\033[1;31m[x]\033[0m %s\n' "$*" >&2; }
# --- Sicherheitschecks --------------------------------------------
if [[ ${EUID} -eq 0 ]]; then
c_err "Bitte NICHT als root starten. Als normaler User ausfuehren."
exit 1
fi
if ! command -v pacman >/dev/null 2>&1; then
c_err "pacman nicht gefunden - das ist kein Arch-/CachyOS-System."
exit 1
fi
c_info "Sudo-Rechte werden einmalig abgefragt..."
sudo -v
# Sudo-Timestamp am Leben halten, solange das Skript laeuft
while true; do sudo -n true; sleep 60; kill -0 "$$" 2>/dev/null || exit; done &
SUDO_KEEPALIVE_PID=$!
trap 'kill "${SUDO_KEEPALIVE_PID}" 2>/dev/null || true' EXIT
# --- 1. Multilib sicherstellen (fuer 32-bit Steam/Proton) ----------
if grep -q '^\[multilib\]' /etc/pacman.conf; then
c_ok "Multilib ist bereits aktiv."
else
c_info "Multilib wird aktiviert..."
sudo sed -i '/^#\[multilib\]/,/^#Include/ s/^#//' /etc/pacman.conf
c_ok "Multilib aktiviert."
fi
# --- 2. System aktualisieren --------------------------------------
c_info "System wird aktualisiert (kann etwas dauern)..."
sudo pacman -Syu --noconfirm
# --- 3. Passende Kernel-Header zum LAUFENDEN Kernel ----------------
c_info "Ermittle passende Kernel-Header (fuer DKMS noetig)..."
KERNEL_PKG="$(pacman -Qqo "/usr/lib/modules/$(uname -r)/vmlinuz" 2>/dev/null || true)"
if [[ -z "${KERNEL_PKG}" ]]; then
c_warn "Kernel-Paket nicht automatisch erkannt - nehme linux-cachyos-headers."
HEADERS_PKG="linux-cachyos-headers"
else
HEADERS_PKG="${KERNEL_PKG}-headers"
c_ok "Kernel: ${KERNEL_PKG} -> Header: ${HEADERS_PKG}"
fi
# --- 4. Basis-Pakete aus den offiziellen Repos ---------------------
c_info "Installiere Steam, Gaming-Tools, AMD-Vulkan und gamescope..."
sudo pacman -S --needed --noconfirm \
base-devel git dkms "${HEADERS_PKG}" \
steam \
mangohud lib32-mangohud \
gamemode lib32-gamemode \
gamescope \
mesa lib32-mesa vulkan-radeon lib32-vulkan-radeon vulkan-icd-loader lib32-vulkan-icd-loader
# --- 5. AUR-Helper sicherstellen (CachyOS hat meist paru) ----------
AUR=""
if command -v paru >/dev/null 2>&1; then
AUR="paru"
elif command -v yay >/dev/null 2>&1; then
AUR="yay"
else
c_info "Kein AUR-Helper gefunden - installiere paru..."
if sudo pacman -S --needed --noconfirm paru 2>/dev/null; then
AUR="paru"
else
tmpdir="$(mktemp -d)"
git clone https://aur.archlinux.org/paru-bin.git "${tmpdir}/paru-bin"
( cd "${tmpdir}/paru-bin" && makepkg -si --noconfirm )
AUR="paru"
fi
fi
c_ok "AUR-Helper: ${AUR}"
# --- 6. Fanatec-FFB-Treiber + Oversteer aus dem AUR ----------------
# Projekt: github.com/gotzl/hid-fanatecff
# Der AUR-Paketname wechselt zwischen 'hid-fanatecff-dkms' und
# '...-dkms-git'. Wir ermitteln, welcher gerade existiert, statt
# einen festen Namen zu erzwingen (sonst bricht paru komplett ab).
c_info "Ermittle verfuegbares Fanatec-AUR-Paket..."
FANATEC_PKG=""
for cand in hid-fanatecff-dkms hid-fanatecff-dkms-git; do
if "${AUR}" -Si "${cand}" >/dev/null 2>&1; then
FANATEC_PKG="${cand}"
break
fi
done
if [[ -n "${FANATEC_PKG}" ]]; then
c_ok "Fanatec-Paket gefunden: ${FANATEC_PKG}"
c_info "Installiere ${FANATEC_PKG}..."
"${AUR}" -S --needed --noconfirm "${FANATEC_PKG}"
else
c_warn "Kein hid-fanatecff-Paket im AUR gefunden - ueberspringe."
c_warn "Spaeter manuell pruefen: ${AUR} -Ss hid-fanatec"
fi
# Oversteer separat installieren, damit ein Fehler beim einen Paket
# das andere nicht mit abbricht.
c_info "Installiere Oversteer (FFB-/Lenkbereich-GUI)..."
"${AUR}" -S --needed --noconfirm oversteer || \
c_warn "Oversteer-Installation fehlgeschlagen - spaeter: ${AUR} -S oversteer"
# Pruefen, dass das Modul wirklich gebaut wurde
if ! modinfo hid_fanatec >/dev/null 2>&1; then
c_warn "Modul hid_fanatec nicht gefunden - DKMS-Build evtl. fehlgeschlagen."
c_warn "Kernel-Header (${HEADERS_PKG}) pruefen, dann: sudo dkms autoinstall"
fi
# --- 7. Modul beim Boot automatisch laden -------------------------
c_info "Konfiguriere automatisches Laden von hid_fanatec beim Boot..."
echo hid_fanatec | sudo tee /etc/modules-load.d/fanatec.conf >/dev/null
c_ok "/etc/modules-load.d/fanatec.conf angelegt."
# --- 8. udev-Regeln fuer Schreibrechte ohne root ------------------
# Sonst braucht FFB-Tuning (Oversteer/fanatecctl) root-Rechte.
c_info "Schreibe udev-Regeln (/etc/udev/rules.d/99-fanatec.rules)..."
sudo tee /etc/udev/rules.d/99-fanatec.rules >/dev/null <<'EOF'
# Fanatec (VID 0eb7) - Schreibzugriff auf hidraw + sysfs-Attribute
KERNEL=="hidraw*", ATTRS{idVendor}=="0eb7", MODE="0666"
ACTION=="add", SUBSYSTEM=="module", KERNEL=="hid_fanatec", \
RUN+="/bin/sh -c 'chmod -R a+rw /sys/module/hid_fanatec/drivers/hid:ftec*/*/leds/* 2>/dev/null; chmod -R a+rw /sys/module/hid_fanatec/drivers/hid:ftec*/*/* 2>/dev/null'"
EOF
c_ok "udev-Regeln geschrieben."
# --- 9. udev-Regeln neu laden -------------------------------------
c_info "udev-Regeln werden neu geladen..."
sudo udevadm control --reload-rules
sudo udevadm trigger
# --- 10. DKMS-Status anzeigen -------------------------------------
c_info "DKMS-Status (hid-fanatecff sollte 'installed' sein):"
dkms status || true
# --- 11. OPTIONAL: Boot-Auswahlmenue ------------------------------
if [[ "${WITH_BOOT_MENU}" -eq 1 ]]; then
c_info "Richte Boot-Auswahlmenue ein (Default: RaceRoom)..."
# Verzeichnis fuer eigene Skripte
mkdir -p "${HOME}/.local/bin"
# --- 11a. Launcher-Skript -------------------------------------
# Zeigt ein TUI-Menue. Auswahl startet das Spiel in einer
# gamescope-Session. Default (Enter / Timeout) = RaceRoom.
cat > "${HOME}/.local/bin/sim-launcher.sh" <<'LAUNCHER'
#!/usr/bin/env bash
#
# Sim-Racing Boot-Launcher
# Zeigt nach dem Login ein Auswahlmenue. Default = RaceRoom.
#
# Ablauf:
# 1. Pruefen, ob Steam schon eingeloggt ist. Wenn nicht ->
# Steam im Big-Picture-Modus starten, dort QR-Code scannen
# (einmalig). Danach bleibt der Login bestehen.
# 2. Auswahlmenue (Default RaceRoom).
# 3. Spiel nicht installiert? -> automatisch installieren.
# 4. Spiel in gamescope starten.
#
set -uo pipefail
AC_APPID=244210 # Assetto Corsa
R3E_APPID=211780 # RaceRoom Racing Experience
TIMEOUT=8 # Sekunden bis automatisch RaceRoom startet
# gamescope-Parameter ggf. an dein Display anpassen
GS_OPTS="-f --adaptive-sync"
# Steam-Pfade (Standard unter Arch/CachyOS)
STEAM_ROOT="${HOME}/.local/share/Steam"
STEAMAPPS="${STEAM_ROOT}/steamapps"
LOGINUSERS="${STEAM_ROOT}/config/loginusers.vdf"
# --- Hilfsfunktionen ----------------------------------------------
# Ist bereits ein Steam-Account hinterlegt / eingeloggt?
steam_logged_in() {
# loginusers.vdf existiert und enthaelt mind. einen Account mit
# "RememberPassword" "1" -> Session-Token vorhanden.
[[ -f "${LOGINUSERS}" ]] && grep -q '"RememberPassword"[[:space:]]*"1"' "${LOGINUSERS}"
}
# Ist ein Spiel (appid) bereits installiert?
game_installed() {
local appid="$1"
[[ -f "${STEAMAPPS}/appmanifest_${appid}.acf" ]] && \
grep -q '"StateFlags"[[:space:]]*"4"' "${STEAMAPPS}/appmanifest_${appid}.acf"
}
# Erst-Login per QR-Code im Big-Picture-UI (in gamescope).
first_login() {
clear
cat <<'LOGINBANNER'
------------------------------------------------------------------
STEAM-LOGIN ERFORDERLICH
Gleich oeffnet sich Steam im Big-Picture-Modus.
Dort wird ein QR-CODE angezeigt.
-> Steam-App auf dem Handy oeffnen
-> Menue -> QR-Code scannen
-> Login mit Steam Guard bestaetigen
Danach bleibst du eingeloggt - dieser Schritt kommt nur
beim ersten Mal.
------------------------------------------------------------------
LOGINBANNER
sleep 4
# Big-Picture/Gamepad-UI zeigt den QR-Login gross an.
# Laeuft in gamescope, blockierend, bis Steam beendet wird.
gamescope ${GS_OPTS} -- steam -gamepadui -steamos || true
# Kurz warten, bis Steam die Session geschrieben hat.
sleep 3
}
# Spiel installieren (blockierend, bis fertig) und dann starten.
install_and_launch() {
local appid="$1" name="$2"
if ! game_installed "${appid}"; then
clear
echo " ${name} ist noch nicht installiert."
echo " Installation wird gestartet - Steam oeffnet sich..."
echo
# Big-Picture zeigt den Download-Fortschritt an.
gamescope ${GS_OPTS} -- steam -gamepadui "steam://install/${appid}" || true
# Warten, bis das appmanifest auf "installiert" steht.
echo " Warte auf Abschluss der Installation..."
local tries=0
while ! game_installed "${appid}"; do
sleep 5
tries=$((tries + 1))
if [[ $tries -ge 720 ]]; then # max ~1h
echo " Timeout bei der Installation. Bitte manuell pruefen."
return 1
fi
done
echo " ${name} ist installiert."
fi
launch "${appid}"
}
launch() {
local appid="$1"
# gamescope umschliesst Steam, das den Titel direkt startet.
# gamemoderun + mangohud wie in den Steam-Startoptionen.
exec gamescope ${GS_OPTS} -- \
env STEAM_FORCE_DESKTOPUI_SCALING=1 \
gamemoderun mangohud \
steam -applaunch "${appid}" -silent
}
# --- Login sicherstellen ------------------------------------------
if ! steam_logged_in; then
first_login
if ! steam_logged_in; then
echo " Login scheint nicht abgeschlossen. Starte trotzdem das Menue -"
echo " Steam fragt ggf. erneut nach dem Login."
sleep 3
fi
fi
# --- Auswahlmenue -------------------------------------------------
clear
cat <<'BANNER'
____ _ ____ _
/ ___|(_)_ __ ___ | _ \ __ _ ___(_)_ __ __ _
\___ \| | '_ ` _ \ | |_) / _` |/ __| | '_ \ / _` |
___) | | | | | | | | _ < (_| | (__| | | | | (_| |
|____/|_|_| |_| |_| |_| \_\__,_|\___|_|_| |_|\__, |
|___/
BANNER
echo
echo " Was moechtest du starten?"
echo
echo " [1] RaceRoom Racing Experience (Default)"
echo " [2] Assetto Corsa"
echo " [3] Desktop / Abbrechen"
echo
printf " Auswahl (Enter = RaceRoom, automatisch in %ss): " "${TIMEOUT}"
# Timeout-Lesung: leere Eingabe oder Timeout -> RaceRoom
if ! read -r -t "${TIMEOUT}" choice; then
choice=""
fi
echo
case "${choice}" in
2) echo " Starte Assetto Corsa..."; install_and_launch "${AC_APPID}" "Assetto Corsa" ;;
3) echo " Abgebrochen - zurueck zum Desktop/Shell."; exit 0 ;;
*) echo " Starte RaceRoom..."; install_and_launch "${R3E_APPID}" "RaceRoom" ;;
esac
LAUNCHER
chmod +x "${HOME}/.local/bin/sim-launcher.sh"
c_ok "Launcher: ${HOME}/.local/bin/sim-launcher.sh"
# --- 11b. Autostart an TTY1 via .bash_profile -----------------
# Startet den Launcher NUR auf TTY1 und nur, wenn keine grafische
# Session laeuft. So bleibt SSH / andere TTYs unberuehrt und du
# kommst per "Desktop" jederzeit normal an deine Shell.
PROFILE="${HOME}/.bash_profile"
MARKER="# >>> sim-racing autostart >>>"
if ! grep -qF "${MARKER}" "${PROFILE}" 2>/dev/null; then
cat >> "${PROFILE}" <<'PROFILEBLOCK'
# >>> sim-racing autostart >>>
# Startet das Sim-Racing-Menue automatisch beim Login auf TTY1.
# Loeschen oder auskommentieren, um den Auto-Start zu deaktivieren.
if [[ "$(tty)" == "/dev/tty1" ]] && [[ -z "${DISPLAY:-}" ]] && [[ -z "${WAYLAND_DISPLAY:-}" ]]; then
"${HOME}/.local/bin/sim-launcher.sh"
fi
# <<< sim-racing autostart <<<
PROFILEBLOCK
c_ok "Autostart in ${PROFILE} eingetragen (nur TTY1)."
else
c_warn "Autostart-Block existiert bereits in ${PROFILE} - uebersprungen."
fi
# --- 11c. Steam beim Herunterfahren sauber beenden ------------
# systemd-User-Service, der beim Logout/Shutdown 'steam -shutdown'
# ausfuehrt. Verhindert "Steam wurde nicht korrekt beendet" und
# Cloud-Sync-Konflikte. Der Login bleibt dabei erhalten.
c_info "Richte sauberes Steam-Beenden beim Herunterfahren ein..."
mkdir -p "${HOME}/.config/systemd/user"
cat > "${HOME}/.config/systemd/user/steam-clean-shutdown.service" <<'UNIT'
[Unit]
Description=Steam sauber beenden beim Logout/Shutdown
DefaultDependencies=no
Conflicts=shutdown.target
Before=shutdown.target
[Service]
Type=oneshot
RemainAfterExit=yes
# ExecStart macht nichts beim Start - die Arbeit passiert beim Stop.
ExecStart=/bin/true
# Beim Stop (Logout/Shutdown): Steam sauber herunterfahren.
ExecStop=/bin/sh -c 'command -v steam >/dev/null 2>&1 && steam -shutdown || true'
TimeoutStopSec=30
[Install]
WantedBy=default.target
UNIT
systemctl --user daemon-reload 2>/dev/null || true
if systemctl --user enable steam-clean-shutdown.service 2>/dev/null; then
c_ok "steam-clean-shutdown.service aktiviert."
else
c_warn "Konnte User-Service nicht aktivieren (kein laufender User-Bus?)."
c_warn "Spaeter manuell: systemctl --user enable --now steam-clean-shutdown.service"
fi
c_warn "WICHTIG fuer den Boot-Menue-Modus:"
c_warn " - Ein Display-Manager (SDDM/GDM) wuerde dir zuvorkommen."
c_warn " Fuer reinen Konsolen-Boot: 'sudo systemctl disable sddm'"
c_warn " (oder den bei dir aktiven DM). Autologin auf TTY1 einrichten:"
c_warn " siehe Hinweis unten in der Zusammenfassung."
fi
c_ok "Installation abgeschlossen!"
cat <<EOF
==================================================================
FAST GESCHAFFT - noch ein paar Schritte, die kein Skript kann:
==================================================================
1) NEUSTART (wichtig!), damit der DKMS-Treiber sauber geladen wird:
reboot
2) Fanatec-Wheel einschalten und im PC-/Compatibility-Mode betreiben
(am Wheel-Base-Display einstellbar). Danach pruefen mit:
oversteer
Dort Lenkbereich (z.B. 900 oder 1080 Grad) und FFB-Gain setzen.
Das Wheel sollte als Geraet auftauchen - sonst Mode/Neustart checken.
3) Steam -> Einstellungen -> Compatibility:
"Steam Play fuer alle anderen Titel aktivieren" -> Proton Experimental
4) Damit Steam das Lenkrad NICHT abfaengt, pro Spiel Steam-Input aus:
Spiel -> Eigenschaften -> Controller -> "Steam-Input deaktivieren"
5) Empfohlene Startoptionen (Spiel -> Eigenschaften -> Startoptionen):
gamemoderun mangohud %command%
6) Direkt per Konsole starten:
steam -applaunch 244210 # Assetto Corsa
steam -applaunch 211780 # RaceRoom Racing Experience
Tipp: Am Netzteil fahren, nicht auf Akku - sonst drosselt die iGPU.
Spaeter mit eGPU/2060: zusaetzlich nvidia-open-dkms + nvidia-utils
+ lib32-nvidia-utils installieren.
EOF
if [[ "${WITH_BOOT_MENU}" -eq 1 ]]; then
cat <<'EOF'
------------------------------------------------------------------
BOOT-AUSWAHLMENUE (aktiviert):
------------------------------------------------------------------
Damit das Menue OHNE Eingabe direkt nach dem Einschalten erscheint,
brauchst du Konsolen-Autologin auf TTY1 (statt grafischem Login):
a) Falls ein Display-Manager laeuft, deaktivieren:
sudo systemctl disable sddm # bzw. gdm/lightdm
sudo systemctl set-default multi-user.target
b) Autologin auf TTY1 einrichten:
sudo systemctl edit getty@tty1
und folgendes eintragen (DEINEN Usernamen einsetzen):
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin DEINUSER --noclear %I $TERM
Ablauf danach: Einschalten -> Autologin TTY1 -> Menue erscheint.
Enter oder 8s Timeout -> RaceRoom startet in gamescope.
"3" waehlen -> du landest in der normalen Shell (z.B. 'startx' / DM).
ERSTER LOGIN (QR-Code):
Beim allerersten Boot ist noch keine Steam-Session da. Der Launcher
oeffnet dann Steam im Big-Picture-Modus mit QR-Code. Mit der
Steam-App auf dem Handy scannen, Steam Guard bestaetigen - danach
bleibst du eingeloggt und der Schritt entfaellt kuenftig.
AUTO-INSTALL:
Ist ein gewaehltes Spiel noch nicht installiert, startet der
Launcher zuerst die Installation (steam://install/APPID) und
danach automatisch das Spiel.
SAUBERES BEENDEN:
Beim Herunterfahren/Logout faehrt 'steam -shutdown' Steam sauber
herunter (User-Service steam-clean-shutdown). Der Login bleibt
erhalten - es wird NICHT ausgeloggt.
Deaktivieren: Block in ~/.bash_profile zwischen den
"sim-racing autostart"-Markern entfernen. Service deaktivieren mit:
systemctl --user disable steam-clean-shutdown.service
------------------------------------------------------------------
EOF
fi
cat <<'EOF'
Viel Spass auf der Strecke!
==================================================================
EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment