Dockers
Games on Whales
Installation Wolf (Multi-User Cloud Gaming)
Objectif : Faire tourner plusieurs instances de Steam indépendantes sur un ou plusieurs GPU, accessibles via Moonlight avec support manettes, en utilisant Wolf et la méthode Nvidia (Manual) sur Unraid.
1. Prérequis Système (Unraid)
A. Plugin Nvidia
Installer le plugin "Nvidia Driver" (par ich777) depuis les Community Applications.
B. Correctifs NVENC et NvFBC pour les pilotes Nvidia
Nvidia bloque le nombre de flux NVENC au-delà de 3. Pour lever cette limite, on applique un patch au chargement du driver.
Installer le plugin "User Scripts" depuis les Community Applications.
Créer ensuite un script, par exemple nommé "Nvidia NVENC Patch" avec comme contenu :
#!/bin/bash
wget https://raw.githubusercontent.com/keylase/nvidia-patch/master/patch.sh -O /tmp/nvidia-patch.sh
chmod +x /tmp/nvidia-patch.sh
/tmp/nvidia-patch.sh
À lancer une première fois manuellement.
Dans User Scripts, régler le script pour qu’il s’exécute à chaque démarrage de l’Array.
C. Activation du "Kernel Mode Setting" (KMS)
Wolf a besoin de contrôler l’affichage au niveau du noyau pour créer les sessions graphiques. Sans ça, on risque l’écran noir.
Aller dans Main > Flash > Syslinux Configuration.
Dans l’entrée
Unraid OS, ajouter à la fin de la ligne :
nvidia-drm.modeset=1
Redémarrer Unraid.
Vérifier que le KMS est bien activé :
cat /sys/module/nvidia_drm/parameters/modeset
Il doit renvoyer :
Y
D. Persistance des Manettes (uinput / uhid)
Pour que Unraid recrée correctement les règles de manettes virtuelles à chaque démarrage (sinon elles disparaissent après reboot).
Ouvrir le terminal Unraid.
Éditer le fichier de boot :
nano /boot/config/go
Ajouter ce bloc à la fin du fichier :
# --- Wolf : Support Manettes Virtuelles ---
# 1. Permissions uinput
chmod 666 /dev/uinput || true
# 2. Règles UDEV
cat << 'EOF' > /etc/udev/rules.d/85-wolf-virtual-inputs.rules
KERNEL=="uinput", SUBSYSTEM=="misc", MODE="0660", GROUP="input", OPTIONS+="static_node=uinput"
KERNEL=="uhid", TAG+="uaccess"
SUBSYSTEMS=="input", ATTRS{id/vendor}=="ab00", MODE="0660", GROUP="input", ENV{ID_SEAT}="seat9"
SUBSYSTEMS=="input", ATTRS{name}=="Wolf X-Box One (virtual) pad", MODE="0660", GROUP="input"
SUBSYSTEMS=="input", ATTRS{name}=="Wolf PS5 (virtual) pad", MODE="0660", GROUP="input"
SUBSYSTEMS=="input", ATTRS{name}=="Wolf gamepad (virtual) motion sensors", MODE="0660", GROUP="input"
SUBSYSTEMS=="input", ATTRS{name}=="Wolf Nintendo (virtual) pad", MODE="0660", GROUP="input"
EOF
# 3. Recharger les règles
udevadm control --reload-rules && udevadm trigger
2. Installation de Wolf (Méthode Nvidia Manual sur Unraid)
Avec la méthode Nvidia Manual, on va :
Construire une image Docker qui contient les fichiers du driver Nvidia.
Pré-remplir un volume Docker
nvidia-driver-vol avec ces fichiers.
Monter ce volume dans le conteneur Wolf via un template Unraid.
A. Construire l’image
gow/nvidia-driver:latest
Dans le terminal Unraid :
cd /root
curl https://raw.githubusercontent.com/games-on-whales/gow/master/images/nvidia-driver/Dockerfile \
| docker build -t gow/nvidia-driver:latest -f - --build-arg NV_VERSION=$(cat /sys/module/nvidia/version) .
Cela crée l’image locale
gow/nvidia-driver:latest avec la version de driver actuellement chargée.
B. Créer / mettre à jour le volume Docker
nvidia-driver-vol
On pré-popule un volume Docker avec les libs Nvidia :
docker create --rm \
--mount source=nvidia-driver-vol,destination=/usr/nvidia \
gow/nvidia-driver:latest sh
Vérifier qu’il existe :
docker volume ls | grep nvidia-driver
Il doit afficher :
nvidia-driver-vol
C. Script User Scripts : reconstruire le volume après chaque reboot
Important : à chaque mise à jour du driver Nvidia, il faut reconstruire l’image et repopuler le volume, sinon Wolf utilise des libs qui ne correspondent plus au driver chargé.
Le plus simple sur Unraid : utiliser le plugin User Scripts avec un script lancé au démarrage de l’Array.
Dans User Scripts, créer un script nommé par exemple "Wolf - Rebuild Nvidia driver volume".
Contenu du script :
#!/bin/bash
# Wolf - (Re)construction de l'image et du volume driver Nvidia + restart Wolf
WOLF_CONTAINER_NAME="wolf"
DRIVER_IMAGE="gow/nvidia-driver:latest"
DRIVER_VOLUME="nvidia-driver-vol"
NOTIFY="/usr/local/emhttp/webGui/scripts/notify"
notify_msg() {
# $1 = niveau (normal|warning|alert)
# $2 = sujet
# $3 = description
if [ -x "${NOTIFY}" ]; then
"${NOTIFY}" -e "Wolf Nvidia" -s "$2" -d "$3" -i "$1"
fi
}
echo ">>> [Wolf] $(date) - Début de la reconstruction Nvidia (image + volume)"
notify_msg "normal" "Début mise à jour Wolf Nvidia" "Reconstruction de l'image Nvidia et du volume ${DRIVER_VOLUME} en cours..."
# Vérif rapide de la version de driver en cours
if [ -r /sys/module/nvidia/version ]; then
DRV_VER=$(cat /sys/module/nvidia/version)
echo ">>> [Wolf] Driver Nvidia détecté : ${DRV_VER}"
else
echo "!!! [Wolf] Impossible de lire /sys/module/nvidia/version (driver Nvidia non chargé ?)"
notify_msg "warning" "Driver Nvidia introuvable" "Impossible de lire /sys/module/nvidia/version. Vérifiez que le driver est bien chargé avant de relancer le script."
fi
echo ">>> [Wolf] Reconstruction de l'image ${DRIVER_IMAGE}..."
curl -s https://raw.githubusercontent.com/games-on-whales/gow/master/images/nvidia-driver/Dockerfile \
| docker build -t "${DRIVER_IMAGE}" -f - \
--build-arg NV_VERSION=$(cat /sys/module/nvidia/version) \
.
if [ $? -ne 0 ]; then
echo "!!! [Wolf] Échec du build de l'image ${DRIVER_IMAGE}"
notify_msg "alert" "Échec build image Nvidia" "Le build de l'image ${DRIVER_IMAGE} a échoué. Consultez les logs du script / de Docker pour plus de détails."
exit 1
fi
echo ">>> [Wolf] (Re)population du volume ${DRIVER_VOLUME}..."
# On crée un conteneur temporaire pour initialiser le volume,
# comme dans la doc officielle Wolf, sans jamais l'exécuter.
CID=$(docker create \
--mount source=${DRIVER_VOLUME},destination=/usr/nvidia \
"${DRIVER_IMAGE}" sh 2>/dev/null)
if [ -z "${CID}" ]; then
echo "!!! [Wolf] Échec de la création du conteneur temporaire pour le volume ${DRIVER_VOLUME}"
notify_msg "alert" "Échec initialisation volume Nvidia" "Impossible de créer un conteneur temporaire pour le volume ${DRIVER_VOLUME}. Vérifiez Docker et le volume."
exit 1
fi
echo ">>> [Wolf] Conteneur temporaire ${CID} créé pour initialiser le volume ${DRIVER_VOLUME}"
# On supprime tout de suite le conteneur (le volume, lui, reste)
docker rm "${CID}" >/dev/null 2>&1 || true
echo ">>> [Wolf] Conteneur temporaire ${CID} supprimé"
echo ">>> [Wolf] Redémarrage du conteneur ${WOLF_CONTAINER_NAME}..."
# On essaye de le stopper (au cas où il tourne déjà)
if docker stop "${WOLF_CONTAINER_NAME}" 2>/dev/null; then
echo ">>> [Wolf] Conteneur ${WOLF_CONTAINER_NAME} stoppé."
else
echo ">>> [Wolf] Conteneur ${WOLF_CONTAINER_NAME} n'était pas en cours d'exécution."
fi
# Puis on le démarre
if docker start "${WOLF_CONTAINER_NAME}" 2>/dev/null; then
echo ">>> [Wolf] Conteneur ${WOLF_CONTAINER_NAME} démarré."
notify_msg "normal" "Wolf Nvidia en place" "La mise en place des bibliothèques Nvidia et le redémarrage de Wolf se sont terminés avec succès."
else
echo "!!! [Wolf] Impossible de démarrer le conteneur ${WOLF_CONTAINER_NAME} (à vérifier dans l'UI Docker)."
notify_msg "alert" "Échec redémarrage Wolf" "Impossible de démarrer le conteneur ${WOLF_CONTAINER_NAME}. Vérifiez la configuration dans l'UI Docker."
exit 1
fi
echo ">>> [Wolf] $(date) - Opération terminée."
Configurer le script pour s’exécuter à chaque démarrage de l’Array.
À chaque reboot (après une maj de driver par exemple), le volume sera automatiquement synchronisé avec la bonne version.
Ce que fait le script :
- Vérifie la version du driver Nvidia actuellement chargée sur Unraid.
- Rebuild l’image Docker gow/nvidia-driver:latest avec cette version de driver.
- (Re)inititialise le volume Docker nvidia-driver-vol avec les librairies Nvidia de l’image.
- Redémarre automatiquement le conteneur wolf pour qu’il utilise les librairies à jour.
- Logue chaque étape pour faciliter le debug en cas de problème.
D. Création automatique du template Unraid
my-wolf.xml
On va créer directement le template Unraid via une commande dans le terminal, pour qu’il apparaisse dans Docker > Add Container.
Dans le terminal Unraid, lancer :
mkdir -p /boot/config/plugins/dockerMan/templates-user
cat << 'EOF' > /boot/config/plugins/dockerMan/templates-user/my-wolf.xml
wolf
ghcr.io/games-on-whales/wolf:stable
https://ghcr.io/
host
bash
true
https://games-on-whales.github.io/wolf/stable/
https://github.com/games-on-whales/wolf
Wolf est un serveur de streaming Moonlight permettant de partager un hôte pour plusieurs sessions de jeu et bureaux virtuels.
GameServers:
http://[IP]:8080/
https://games-on-whales.github.io/assets/favicon.png
-v nvidia-driver-vol:/usr/nvidia:rw --device-cgroup-rule="c 13:* rmw"
0
Wolf est un serveur de streaming Moonlight permettant de partager un hôte pour plusieurs sessions de jeu et bureaux virtuels.
host
/mnt/user/appdata/wolf
/etc/wolf
rw
/var/run/docker.sock
/var/run/docker.sock
rw
/dev
/dev
rw
/run/udev
/run/udev
rw
nvidia-driver-vol
NVIDIA_DRIVER_VOLUME_NAME
/mnt/user/appdata/wolf
/var/run/docker.sock
/dev
/run/udev
nvidia-driver-vol
/dev/dri
/dev/nvidiactl
/dev/nvidia0
/dev/nvidia-uvm
/dev/nvidia-uvm-tools
/dev/nvidia-caps/nvidia-cap1
/dev/nvidia-caps/nvidia-cap2
/dev/uinput
/dev/uhid
EOF
Aller dans Docker > Add Container et choisir le template wolf dans la liste déroulante. Normalement, tout est déjà correctement renseigné.
Cliquer sur Apply pour créer et lancer le conteneur Wolf.
Note GPU : ce template est prévu pour une configuration avec un seul GPU Nvidia : il expose explicitement le device
/dev/nvidia0.
Si tu ajoutes un second GPU, il suffira d’ajouter aussi le device
/dev/nvidia1
Via l’interface Unraid : Docker → Éditer le conteneur → “Add another device” pour le rendre visible dans le conteneur Wolf.
J'explique plus loin le multi GPU. (en cours de rédaction)
3. Configuration de Wolf (config.toml & utilisateurs)
A. Préparation des permissions aux répertoires (méthode recommandée : ACL)
Par défaut, Unraid utilise l’utilisateur
nobody:users (99:100) pour les partages.
Plutôt que de faire des
chown/chmod -R à chaque nouvel utilisateur ou nouveau jeu, on peut utiliser les ACL pour donner en plus tous les droits au groupe
1000 (celui utilisé par les containers Wolf/Steam).
Donner les droits rwx au groupe 1000 sur l’appdata Wolf
setfacl -R -m g:1000:rwx /mnt/user/appdata/wolf
setfacl -R -d -m g:1000:rwx /mnt/user/appdata/wolf
Donner les droits rwx au groupe 1000 sur le dossier de jeux
setfacl -R -m g:1000:rwx /mnt/user/Games
setfacl -R -d -m g:1000:rwx /mnt/user/Games
Certains outils Unraid comme “New Permissions” ou des scripts maison peuvent écraser les ACL si tu les lances sur ces dossiers → dans ce cas, il faudra relancer les
setfacl.
Si tu déplaces des données entre disques via certains outils externes (rsync sans
-A, cp, etc.), les ACL peuvent ne pas être copiées.
Modifier le
config.toml
Fichier cible (côté host) :
nano /mnt/user/appdata/wolf/cfg/config.toml
À titre perso, pour le moment, j’ai choisi d’épurer totalement l’affichage dans Moonlight. Mais il est possible de laisser l’app Wolf UI présente, qui permet de classer les apps par utilisateur.
Dans ce fichier, vous supprimez tout ce qu’il y a entre :
[[profiles]]
id = 'moonlight-profile-id'
et
[[profiles]]
id = 'user'
name = 'User'
Et vous collez à la place la configuration de votre premier utilisateur suivante :
Remplacez bien les quatre .
Remplacez par une image PNG. Dimension idéale width=112px height=150px (facilement reconnaissable dans Moonlight).
# --- Utilisateur 1 : ---
[[profiles.apps]]
icon_png_path = ''
start_virtual_compositor = true
title = ''
[profiles.apps.runner]
base_create_json = '''{
"HostConfig": {
"IpcMode": "host",
"CapAdd": ["SYS_ADMIN", "SYS_NICE", "SYS_PTRACE", "NET_RAW", "MKNOD", "NET_ADMIN"],
"SecurityOpt": ["seccomp=unconfined", "apparmor=unconfined"],
"Ulimits": [{"Name":"nofile", "Hard":10240, "Soft":10240}],
"Privileged": false,
"DeviceCgroupRules": ["c 13:* rmw", "c 244:* rmw"]
}
}
'''
devices = []
env = [
'PROTON_LOG=1',
'RUN_SWAY=true',
'GOW_REQUIRED_DEVICES=/dev/input/* /dev/dri/* /dev/nvidia*',
'XKB_DEFAULT_LAYOUT=fr',
'TZ=Europe/Paris'
]
image = 'ghcr.io/games-on-whales/steam:edge'
mounts = [
'/mnt/user/Games:/home/retro/games-unraid:rw',
'/mnt/user/appdata/wolf/:/home/retro/.steam:rw'
]
name = ''
ports = []
type = 'docker'
'XKB_DEFAULT_LAYOUT=fr' → clavier AZERTY français.
'TZ=Europe/Paris' → heure correcte dans l’app.
'/mnt/user/Games:/home/retro/games-unraid:rw' → point de montage du répertoire d’installation des jeux.
'/mnt/user/appdata/wolf/:/home/retro/.steam:rw' → rend l’installation de Steam persistante par utilisateur.
3b. Variante des gestions d’utilisateurs
Wolf UI
Wolf UI permet d’avoir un répertoire par utilisateur.
Ranger toutes les apps par utilisateur.
Utiliser le mode
co-op (deux utilisateurs sur la même session en coop locale).
Activation de Wolf UI
Faire apparaître le menu Wolf UI dans Moonlight
Pour activer Wolf UI, il faut modifier
config.toml, repérer la partie :
[[profiles]]
id = 'moonlight-profile-id'
Directement dessous, collez :
[[profiles.apps]]
icon_png_path = 'https://raw.githubusercontent.com/games-on-whales/wolf-ui/refs/heads/main/src/Icons/wolf_ui_icon.png'
start_virtual_compositor = true
title = 'Wolf UI'
[profiles.apps.runner]
base_create_json = '''{
"HostConfig": {
"IpcMode": "host",
"CapAdd": ["NET_RAW", "MKNOD", "NET_ADMIN", "SYS_ADMIN", "SYS_NICE"],
"Privileged": false,
"DeviceCgroupRules": ["c 13:* rmw", "c 244:* rmw"]
}
}'''
devices = []
env = [
'GOW_REQUIRED_DEVICES=/dev/input/event* /dev/dri/* /dev/nvidia*',
'WOLF_SOCKET_PATH=/var/run/wolf/wolf.sock',
'WOLF_UI_AUTOUPDATE=False',
'LOGLEVEL=INFO',
'XKB_DEFAULT_LAYOUT=fr'
]
image = 'ghcr.io/games-on-whales/wolf-ui:main'
mounts = [ '/var/run/wolf/wolf.sock:/var/run/wolf/wolf.sock' ]
name = 'Wolf-UI'
ports = []
type = 'docker'
Création des espaces utilisateur
De base, dans
config.toml, un utilisateur
user est créé avec toutes les apps dedans.
Vous pouvez créer un nouvel espace en ajoutant :
[[profiles]]
id = 'user'
name = ''
icon_png_path = 'url_de_votre_image.png'
pin = [ 3, 2, 1, 4 ]
L'
id doit être unique, exemple :
user1,
user2, etc...
Le
pin est facultatif, il permet de mettre un password de 4 chiffres pour accéder à l'espace de l'utilisateur.
Toutes les apps de cet utilisateur doivent être présentes sous ces lignes.
4. Configuration Post-Installation
A. Appairage Moonlight
Lancer Moonlight sur le client (PC, TV, Tablette).
Noter le code PIN affiché.
Consulter les logs Docker :
docker logs -f wolf
Cliquer sur le lien
http://IP_UNRAID:47989/pin/ID visible dans les logs et entrer le code PIN à 4 chiffres affiché dans Moonlight.
La page web vous demande de renseigner le PIN pour déverrouiller Moonlight.
À la première ouverture de Steam, le dépôt s’installe, puis il faut vous connecter à votre compte avec Steam Guard.
B. Déclaration des jeux dans Steam
Par défaut, Steam ne connaît pas le disque d’installation des jeux configuré dans
config.toml. Si vous laissez tel quel, il va installer les jeux dans le répertoire
appdata (non partagé entre utilisateurs).
Fermer Big Picture.
Dans Steam, aller dans Paramètres > Stockage.
Ajouter un lecteur > remonter à
/Home > sélectionner le dossier
/games-unraid.
Le définir comme lecteur par défaut (étoile).
À faire pour chaque compte Steam.
C. MangoHud
L’overlay Steam ne fonctionne pas dans le conteneur Wolf. Pour voir les FPS et autres stats, on utilise MangoHud.
Il est déjà installé et activé par défaut pour tous les jeux Vulkan (y compris les jeux Proton).
Jeux OpenGL (Linux natif)
Pour les jeux OpenGL, vous pouvez l’activer jeu par jeu en ajoutant :
mangohud %command%
Activation et configuration
En jeu, vous pouvez appuyer sur
Maj droite + F12 pour afficher/masquer MangoHud.
Et sur
Maj droite + F11 pour changer sa position à l’écran.
5. Accès à Distance (Ouverture de Ports)
Wolf ne gère pas l’UPnP. Pour jouer depuis l’extérieur, deux options :
Option A : VPN (Recommandé)
Utiliser Tailscale ou Wireguard (plugins Unraid). C’est plus sécurisé et ne nécessite aucune ouverture de port. (Plus chiant côté FireTV ou autre, mais c’est la vie 😄)
Option B : Ouverture de Ports (NAT)
Sans VPN, rediriger les ports suivants sur votre routeur vers l’IP du serveur Unraid :
TCP 47984 : HTTPS (sécurité)
TCP 47989 : HTTP (appairage Web)
TCP 48010 : RTSP (initialisation du stream)
UDP 47999 : Contrôle (manettes)
UDP 48100 : Flux vidéo (RTP - spécifique Wolf)
UDP 48200 : Flux audio (RTP - spécifique Wolf)
Applications
Voici la liste des applications utilisables.
Heroic Games Launcher
Un lanceur open source pour gérer et jouer aux jeux Epic Games Store, GOG et Amazon Prime Games sous Linux.
Configuration par défaut
Voici le bloc de configuration par défaut qui sera utilisé dans Wolf.
Vous pouvez le remplacer en éditant le fichier
config.toml.
[[profiles.apps]]
icon_png_path = 'https://games-on-whales.github.io/wildlife/apps/heroic-games-launcher/assets/icon.png'
start_virtual_compositor = true
title = 'Heroic'
[profiles.apps.runner]
base_create_json = '''{
"HostConfig": {
"IpcMode": "host",
"CapAdd": ["SYS_ADMIN", "SYS_NICE", "SYS_PTRACE", "NET_RAW", "MKNOD", "NET_ADMIN"],
"SecurityOpt": ["seccomp=unconfined", "apparmor=unconfined"],
"Ulimits": [{"Name":"nofile", "Hard":10240, "Soft":10240}],
"Privileged": false,
"DeviceCgroupRules": ["c 13:* rmw", "c 244:* rmw"]
}
}'''
devices = []
env = [ 'RUN_SWAY=true', 'GOW_REQUIRED_DEVICES=/dev/input/* /dev/dri/* /dev/nvidia*' ]
image = 'ghcr.io/games-on-whales/heroic-games-launcher:edge'
mounts = []
name = 'WolfHeroic'
ports = []
type = 'docker'
Pensez à ajouter dans les variables d'environnement : 'XKB_DEFAULT_LAYOUT=fr' pour le clavier AZERTY
Si vous avez suivi mon tutoriel pour Wolf. Il faudra ajouter les points de montage suivant
mounts = [
'/mnt/cache/Games:/home/retro/games-unraid:rw',
'/mnt/user/appdata/wolf/:/home/retro/.heroic:rw'
]
Premier lancement
Voici l'interface au démarrage de Heroic.
Association des comptes
Connecter vos différents comptes EPIC / GOG / Amazon, afin de récupérer vos bibliothèques de jeux.
Ajout du répertoire d'installation des jeux
Nous allons maintenant faire en sorte que les jeux installés depuis Heroic se fassent dans le même répertoire commun.
Allez dans le menu
Options puis
Général.
Dans la section
Chemin d'installation par défaut, mettez
/home/retro/games-unraid
C'est le chemin du répertoire que nous avons utilisé dans le tuto d'installation de Wolf. Il faudra l'adapter si vous avez choisi un autre répertoire.
Utilisation de GE-PROTON
Pour faire fonctionner les jeux Windows (exécutable en .exe). Nous allons utiliser PROTON.
J'ai noté une meilleure compatibilité qu'avec
Wine qui est utilisé par défaut dans Heroic.
Allez dans le menu
Gestion de Wine
Choisir l'onglet
GE-Proton
Cliquez sur la flèche de téléchargement pour la version
GE-Proton-Latest
Utilisation de Proton par dédaut :
Allez dans le menu
Options puis
Paramètre de jeu par défaut.
Choisissez
Proton - GE - Proton - Latest dans la version de Wine
Ajout des jeux Heroic dans Steam
Dans le but de simplifier l'accès à notre bibliothèque de jeux. Nous allons ajouter les jeux téléchargés depuis les différentes plateformes, à Steam. Ce qui permettra de centraliser sur Steam l'ensemble de nos jeux.
Au lancement de Steam, quittez le mode Big Picture.
Allez dans le menu
Steam puis
Paramètres.
Dans les paramètres, allez dans le menu
Bibliothèque, ensuite cliquez
Ajouter un jeu non Steam
Cliquez sur
Parcourir... Et naviguez dans
/home/retro/games-unraid (le répertoire de notre tuto)
Sélectionner les
.exe des jeux que vous voulez importer dans Steam.
Ne pas oublier de cliquer
Ajouter la sélection une fois fait.
Les jeux seront désormais dans la section
NON STEAM de votre bibliothèque.
Prérequis pour lancer les jeux
Si vos jeux sont des installations Windows (executable en .exe). Il faudra les forcer en compatibilité PROTON
Dans Steam, allez dans les
Propriétés... de votre jeu.
Dans le menu
Compatibilité, cochez
forcer l'utilisation d'un outil... Et choisissez
Proton Expérimental
Dolphin pour RomM
RomM — Emulator Streaming avec Dolphin sur Unraid (GPU NVIDIA)
Ce tutoriel explique comment ajouter un conteneur Dolphin (GameCube/Wii) à une instance RomM 5.1.0+ existante pour utiliser la fonctionnalité Emulator Streaming : lancer un jeu depuis l'interface RomM et y jouer dans le navigateur, avec rendu et encodage sur le GPU NVIDIA.
Architecture : RomM ne pilote pas Dolphin directement. Un Docker Mod (maintenu par LoneAngelFayt) injecte dans le conteneur Dolphin un broker HTTP (port 8000) que RomM contacte pour lancer/piloter les jeux. L'image et le son sont diffusés par Selkies (le bureau web intégré aux images LinuxServer), en H.264 NVENC.
⚠️ Testé avec : Unraid 7, RTX 3060, driver NVIDIA 610.x,
lscr.io/linuxserver/dolphin:latest (base Selkies/Wayland), RomM 5.1.0.
1. Prérequis Unraid
1.1 Plugin Nvidia Driver
Installer le plugin Nvidia Driver (Community Applications).
Driver propriétaire 580 ou supérieur requis. LinuxServer recommande la branche Production pour Unraid.
Le GPU ne doit pas être réservé au VFIO (il doit être disponible pour Docker).
Sur un serveur headless (sans écran), la doc LinuxServer indique qu'un dummy plug HDMI/DP branché sur la carte est requis pour que DRM s'initialise correctement.
1.2 Paramètres noyau (Syslinux)
Menu Main → Flash → Syslinux Config (vue Raw), ajouter à la fin de la ligne
append de l'entrée « Unraid OS » (et « Unraid OS GUI Mode » si utilisée) :
nvidia-drm.modeset=1 nvidia_drm.fbdev=1
Redémarrer le serveur, puis vérifier :
cat /proc/cmdline # les 2 paramètres doivent apparaître
cat /sys/module/nvidia_drm/parameters/modeset # doit renvoyer Y
nvidia-smi -L # liste le(s) GPU + UUID
Notez l'UUID du GPU (
GPU-xxxxxxxx-...), il servira dans le template.
1.3 Limite inotify (optionnel mais recommandé)
Les images Selkies consomment beaucoup d'instances inotify. Pour éviter l'avertissement « Too many open files » :
sysctl -w fs.inotify.max_user_instances=1024
echo 'sysctl -w fs.inotify.max_user_instances=1024' >> /boot/config/go
2. ⚠️ Correctifs NVIDIA indispensables (Unraid)
Le runtime NVIDIA d'Unraid présente deux trous d'injection vers les conteneurs. Sans eux, l'émulateur tourne en rendu logiciel (très lent) ou affiche un écran noir. Les deux correctifs sont simples et persistants.
2.1 Composant egl-x11 (rendu OpenGL/EGL)
C'est le point qui fait échouer la plupart des installations (symptôme : interface Dolphin visible, mais zone de jeu noire avec le son, ou jeu qui rame énormément en rendu logiciel).
Le runtime NVIDIA d'Unraid injecte dans les conteneurs les plateformes EGL
wayland,
wayland2 et
gbm, mais pas les deux plateformes X11 (
xlib/
xcb) — pourtant livrées par le driver et indispensables à Dolphin, qui crée son contexte OpenGL via EGL sur X11. Sans elles, l'EGL NVIDIA ne peut pas créer de surface et Dolphin retombe en rendu logiciel (ou écran noir via Zink :
MESA: error: zink: could not create swapchain).
Vérifier que le driver de l'hôte fournit bien les fichiers :
ls /var/local/overlay/usr/lib64/ | grep egl-x
ls /var/local/overlay/usr/share/egl/egl_external_platform.d/ | grep -E "xlib|xcb"
Vous devez voir
libnvidia-egl-xlib.so.1.X.X,
libnvidia-egl-xcb.so.1.X.X,
20_nvidia_xlib.json et
20_nvidia_xcb.json. Notez le numéro de version exact des
.so (ex.
1.0.5) : il est utilisé dans les montages ci-dessous, et il change à chaque mise à jour du driver (voir §7).
Le correctif consiste en 4 montages en lecture seule + 1 variable, intégrés au template Dolphin de la section suivante.
2.2 Nœud
/dev/nvidia-modeset (présentation Vulkan)
Le toolkit injecte
nvidia0,
nvidiactl,
nvidia-uvm,
nvidia-uvm-tools et
nvidia-caps… mais pas
/dev/nvidia-modeset, qui est nécessaire à la présentation Vulkan (l'énumération des GPU, elle, fonctionne sans lui — d'où des diagnostics trompeurs).
Symptômes sans ce nœud :
vulkaninfo liste bien la carte mais
vkGetPhysicalDeviceSurfacePresentModesKHR échoue en
ERROR_UNKNOWN ;
vkcube sélectionne le GPU puis plante à la création de la swapchain ; Dolphin en backend Vulkan ou via Zink affiche un écran noir avec le son.
Vérification :
ls /dev/nvidia-modeset # sur l'hôte : présent
docker exec dolphin ls /dev/nvidia-modeset 2>&1 # dans le conteneur : absent
Correctif : ajouter
--device /dev/nvidia-modeset aux Extra Parameters (voir §3.4).
3. Conteneur Dolphin
Ajouter un conteneur avec l'image
lscr.io/linuxserver/dolphin et la configuration suivante.
3.1 Générer un secret partagé
Ce secret authentifie les échanges RomM ↔ broker. Générez-le une fois :
openssl rand -hex 32
3.2 Ports (host:container)
Port hôte
Port conteneur
Rôle
3000
3000
Selkies HTTP (peu utile)
3001
3001
Selkies HTTPS (le flux de jeu)
8000
8000
Broker RomM
Adaptez les ports hôte s'ils sont déjà pris par un autre conteneur (dans ce cas, reportez le port choisi dans le bloc
streaming du §4.2).
3.3 Variables d'environnement
Variable
Valeur
Remarque
DOCKER_MODS
ghcr.io/loneangelfayt/dolphin-romm-integration-mod:latest
installe le broker
ROM_ROOT
/romm/library
racine de la bibliothèque vue par le broker
BROKER_SECRET
(votre secret)
même valeur que côté RomM
NVIDIA_VISIBLE_DEVICES
(UUID du GPU)
ou
all si un seul GPU
NVIDIA_DRIVER_CAPABILITIES
all
DRINODE
/dev/dri/renderD128
GPU de rendu (vérifier avec
ls -l /dev/dri/by-path/ que c'est bien la carte NVIDIA)
DRI_NODE
/dev/dri/renderD128
GPU d'encodage — identique = mode Zero-Copy
SELKIES_MANUAL_WIDTH
1920
bride la résolution du flux
SELKIES_MANUAL_HEIGHT
1080
sinon elle suit la fenêtre du navigateur (coûteux)
__EGL_VENDOR_LIBRARY_FILENAMES
/usr/share/glvnd/egl_vendor.d/10_nvidia.json
force l'EGL NVIDIA (fait partie du correctif §2)
PUID /
PGID
99 /
100
standard Unraid
3.4 Extra Parameters
--gpus all --runtime nvidia --shm-size=1gb --device /dev/nvidia-modeset --cpuset-cpus=0-5,12-17
--device /dev/nvidia-modeset : correctif §2.2.
--cpuset-cpus : épinglage CPU, obligatoire sur tout Ryzen multi-CCD — sans lui les performances sont fortement dégradées (voir §7). Adaptez impérativement la liste à votre processeur : la valeur ci-dessus correspond au premier CCD d'un 3900X et sera contre-productive sur une autre topologie. Sur un CPU mono-CCD ou Intel, retirez l'option.
3.5 Volumes
Host Path
Container Path
Mode
(votre dossier de ROMs — le même que celui monté dans RomM)
/romm/library
ro
(votre appdata, ex.
/mnt/user/appdata/dolphin)
/config
rw
/var/local/overlay/usr/lib64/libnvidia-egl-xlib.so.1.X.X
/usr/lib64/libnvidia-egl-xlib.so.1
ro
/var/local/overlay/usr/lib64/libnvidia-egl-xcb.so.1.X.X
/usr/lib64/libnvidia-egl-xcb.so.1
ro
/var/local/overlay/usr/share/egl/egl_external_platform.d/20_nvidia_xlib.json
/usr/share/egl/egl_external_platform.d/20_nvidia_xlib.json
ro
/var/local/overlay/usr/share/egl/egl_external_platform.d/20_nvidia_xcb.json
/usr/share/egl/egl_external_platform.d/20_nvidia_xcb.json
ro
Remplacez
1.X.X par la version relevée au §2. Le Container Path des libs est volontairement le nom court
.so.1 : c'est celui que le loader EGL recherche (déclaré dans les JSON), ce qui évite d'avoir à créer des symlinks.
💡 Important : le chemin de la bibliothèque doit être identique dans RomM et dans Dolphin (
/romm/library des deux côtés), sinon le broker ne retrouvera pas les fichiers que RomM lui demande de lancer.
3.6 Vérifications après démarrage
docker logs dolphin 2>&1 | grep -iE "broker|NVENC|Zero-Copy"
Attendu :
ROM broker listening on port 8000,
Shared secret auth enabled,
NVENC ... Initialized successfully,
Decision: Zero-Copy path active.
curl -s http://localhost:8000/health # → {"status":"ok"}
docker exec dolphin sh -c 'ldconfig -p | grep egl-x' # → les 2 libs xlib/xcb
4. Côté RomM
4.1 Variable d'environnement
Ajouter au conteneur RomM :
Variable
Valeur
STREAMING_BROKER_SECRET
(le même secret qu'au §3.1)
4.2 config.yml
Dans le
config.yml de RomM (dossier config), ajouter le bloc
streaming (adapter l'IP du serveur et les ports choisis) :
streaming:
enabled: true
containers:
- platform: ngc
host: https://IP_DU_SERVEUR:3001
broker_host: http://IP_DU_SERVEUR:8000
label: Dolphin
memory_card_sync: true
- platform: wii
host: https://IP_DU_SERVEUR:3001
broker_host: http://IP_DU_SERVEUR:8000
label: Dolphin
Redémarrer RomM après modification.
4.3 Structure de la bibliothèque
Le dossier de plateforme doit correspondre au fs_slug de RomM :
ngc pour la GameCube,
wii pour la Wii (pas
gc, pas
gamecube) — sinon prévoir un mappage dans
config.yml.
Les ROMs doivent être des fichiers directs (
.rvz,
.iso) — pas d'archives
.zip/
.7z, Dolphin ne peut pas les lancer via le broker.
⚠️ RomM supporte deux structures (
library/roms// ou
library//roms/) mais la simple présence d'un dossier
library/roms/ fait basculer toute la bibliothèque sur la première : ne mélangez pas les deux, ou toutes vos autres plateformes deviendront invisibles au scan.
Rescanner la plateforme dans RomM après tout changement.
5. Premier lancement
Ouvrir une première fois
https://IP_DU_SERVEUR:3001 et accepter le certificat auto-signé (sinon le flux ne se chargera pas dans RomM).
Dans RomM, ouvrir la fiche d'un jeu GameCube → bouton Play on Dolphin.
Le jeu doit s'afficher, fluide, avec le son.
Vérifier que le GPU est bien utilisé (pendant qu'un jeu tourne) :
docker exec -u abc dolphin sh -c 'for p in /proc/[0-9]*; do ls -l $p/fd 2>/dev/null | grep -q nvidia && echo "$(basename $p) $(tr "\0" " " < $p/cmdline | cut -c1-60)"; done'
dolphin-emu doit apparaître dans la liste. S'il n'y a que
selkies,
labwc et
Xwayland, Dolphin est en rendu logiciel → revoir le §2.
Note : lancez bien ce test avec
-u abc — en root, la lecture de
/proc//fd des processus de l'utilisateur
abc échoue silencieusement.
6. Réglages Dolphin utiles
Le fichier de configuration est
/.config/dolphin-emu/Dolphin.ini. Toujours éditer conteneur arrêté (Dolphin réécrit ses fichiers en se fermant), et savoir que le mod réimpose
GFXBackend = OpenGL à chaque recréation du conteneur — c'est le bon backend ici (c'est le chemin EGL corrigé au §2), ne pas chercher à le changer.
⚠️ Après toute édition depuis l'hôte (
sed -i,
cat >, redirection…), rétablissez le propriétaire :
chown -R 99:100 . Ces commandes peuvent recréer le fichier en
root:root, et l'émulateur ne peut alors plus enregistrer sa configuration — silencieusement, à part une ligne d'erreur dans son log.
6.1 Performances
Réglage recommandé (section
[Core]) :
CPUThread = True
C'est le mode « Dual Core » de Dolphin ; selon les versions il peut être désactivé par défaut, et son absence coûte très cher en performances.
6.2 Jeux en français
La langue de la console émulée (celle que les jeux GameCube utilisent) se règle dans
[Core] :
docker stop dolphin
F=/.config/dolphin-emu/Dolphin.ini
sed -i '/^SelectedLanguage *=/d' "$F"
sed -i '/^\[Core\]/a SelectedLanguage = 2' "$F"
chown -R 99:100
docker start dolphin
grep -n "^SelectedLanguage" "$F"
Valeurs :
0 anglais,
1 allemand,
2 français,
3 espagnol,
4 italien,
5 néerlandais.
Cette clé survit aux redémarrages et aux recréations du conteneur : le mod ne la touche pas.
⚠️ N'ajoutez pas
[Interface] LanguageCode pour traduire l'interface de Dolphin : dans cette image, la clé provoque une boîte de dialogue d'erreur de langue à chaque démarrage. Les jeux sont de toute façon en français grâce à
SelectedLanguage, ce qui est l'essentiel — l'interface de l'émulateur n'est presque jamais visible pour les joueurs (le broker lance directement le jeu en plein écran).
Jeux Wii : la langue ne vient pas de
Dolphin.ini mais de la NAND Wii émulée. Elle se règle dans l'interface (Options → Configuration → Wii → Langue du système) et persiste dans l'appdata.
7. ⚠️ Épinglage CPU — obligatoire sur Ryzen multi-CCD
Sur les Ryzen à plusieurs CCD (3900X, 3950X, 5900X, 5950X, 7900X, 7950X…), les cœurs sont répartis en groupes ayant chacun son propre cache L3. Un émulateur dont les threads dialoguent en permanence perd beaucoup quand l'ordonnanceur les fait migrer d'un groupe à l'autre : les échanges passent alors par l'Infinity Fabric, bien plus lent.
Ce n'est pas une optimisation facultative : sans épinglage, comptez une perte de l'ordre d'un tiers des performances, suffisante pour rendre injouables les titres exigeants. Le symptôme est déroutant — performances médiocres alors que rien ne sature (CPU global bas, GPU à 10 %, aucun thread à 100 %) — parce que les threads n'attendent pas du calcul, mais la mémoire.
Identifiez vos groupes de cache :
lscpu -e=CPU,CORE,L3
Les CPU partageant le même numéro de L3 (ou deux L3 voisins d'un même CCD) forment un groupe. Exemple sur un 3900X : le premier CCD regroupe les CPU
0-5 et leurs hyperthreads
12-17.
Épinglez ensuite le conteneur sur un seul CCD via
--cpuset-cpus=0-5,12-17 (Extra Parameters), ou l'onglet CPU Pinning du template Unraid.
Le gain observé sur ce type de configuration va de quelques pourcents à +50 %. C'est aussi ce qui explique qu'une VM avec vCPU épinglés paraisse bien plus rapide que le même émulateur en conteneur non épinglé, sur la même machine.
Multi-conteneurs : si Dolphin et Eden tournent simultanément, répartissez-les sur des groupes différents plutôt que de les faire cohabiter sur le même CCD.
8. Maintenance et limites connues
À chaque mise à jour du driver NVIDIA de l'hôte, le nom des libs (
libnvidia-egl-*.so.1.X.X) change : mettre à jour les 2 Host Path correspondants dans le template Dolphin, sinon écran noir / rendu logiciel au retour.
Une seule session à la fois par conteneur : un conteneur = une instance Dolphin = un flux. Pour du multi-joueurs simultané, dupliquer le conteneur (autres ports, autre secret possible) et ajouter une entrée dans
streaming.containers.
Sessions fantômes : fermer l'onglet du navigateur ne tue pas Dolphin ; la session peut rester occupée. En attendant mieux :
docker exec dolphin pkill dolphin-emu.
Les paquets installés à la main dans le conteneur (
apt-get) ne survivent pas à une recréation. Pour des outils de diagnostic permanents, chaîner le mod
universal-package-install :
DOCKER_MODS=ghcr.io/loneangelfayt/dolphin-romm-integration-mod:latest|linuxserver/mods:universal-package-install +
INSTALL_PACKAGES=vulkan-tools|mesa-utils|x11-utils.
9. Dépannage rapide
Symptôme
Cause probable
Solution
« ROMs not found » au scan
Nom de dossier ≠ fs_slug, ou structures mélangées
§4.3
Pas de bouton « Play on Dolphin »
Bloc
streaming absent/invalide, ou secret différent
§4.1–4.2
Flux ne se charge pas dans RomM
Certificat auto-signé jamais accepté
§5.1
Jeu très lent,
dolphin-emu absent du test des fd
Rendu logiciel (egl-x11 manquant)
§2.1
Zone de jeu noire, interface et son OK
Présentation Vulkan impossible (
/dev/nvidia-modeset manquant)
§2.2
Jeu lent alors que rien ne sature (CPU bas, GPU ~10 %)
Threads migrant entre CCD
§7
Écran noir après une mise à jour du driver
Version des libs egl-x11 changée
§8
RomM renvoie 502 au lancement
L'émulateur crashe au démarrage : le broker abandonne après 5 essais. Voir
/dolphin.log
ci-dessous
qt.qpa.xcb: could not connect to display :0 dans
dolphin.log
Sockets X périmées accumulées dans
/tmp/.X11-unix (le broker détecte un affichage mort)
Recréer le conteneur — un simple redémarrage conserve
/tmp
Boîte d'erreur de langue au démarrage
Clé
[Interface] LanguageCode présente
La supprimer (§6.2)
« Failed to initialize video backend »
Variables graphiques parasites ou backend forcé à Vulkan
Revenir à la config §3,
GFXBackend = OpenGL
Eden pour RomM
RomM — Emulator Streaming avec Eden (Nintendo Switch) sur Unraid (GPU NVIDIA)
Ce tutoriel explique comment ajouter un conteneur Eden (émulateur Nintendo Switch, fork de Yuzu) à une instance RomM 5.1.0+ pour lancer des jeux Switch depuis RomM et y jouer dans le navigateur, avec rendu GPU NVIDIA.
Le principe est identique à l'intégration Dolphin : un Docker Mod injecte un broker HTTP (port 8000 du conteneur) que RomM contacte, et le flux est diffusé par Selkies en H.264 NVENC.
✅ Testé et fonctionnel avec RomM 5.1.0 stable (la branche de développement n'est pas nécessaire), Unraid 7, Ryzen 9 3900X + RTX 3060,
lscr.io/linuxserver/eden. Résultat : Mario Kart 8 Deluxe à 60 FPS stables en 1080p dans le navigateur.
📖 Ce guide suppose que la page « Emulator Streaming avec Dolphin » a déjà été suivie : les prérequis hôte sont les mêmes et ne sont pas re-détaillés ici — plugin Nvidia Driver (branche Production, ≥ 580), paramètres noyau
nvidia-drm.modeset=1 nvidia_drm.fbdev=1, et surtout les deux correctifs NVIDIA (§2 de la page Dolphin) : composant egl-x11 et nœud
/dev/nvidia-modeset, indispensables ici aussi.
🌐 Ce guide décrit une installation en réseau local. Pour jouer depuis l'extérieur, pour l'isolation réseau des conteneurs, et pour le portail nginx du mod Eden qui bloque le streaming avec RomM 5.1.0, voir la page « Accès distant, publication HTTPS et isolation réseau ». Elle contient aussi l'avertissement de sécurité à lire avant toute publication.
1. Particularités d'Eden à connaître avant de commencer
Contenu requis de votre propre console. Eden ne lance aucun jeu commercial sans les
prod.keys (clés de déchiffrement) et le firmware de la Switch — à extraire de votre propre console.
Eden est Vulkan-first, et son backend Vulkan a besoin du nœud
/dev/nvidia-modeset (§2.2 de la page Dolphin). Sans lui : liste de GPU vide dans les menus, et au lancement d'un jeu l'erreur « Eden has encountered an error while running the video core » (log :
Invalid device index -1,
VK_ERROR_INITIALIZATION_FAILED).
Les réglages faits dans l'interface ne sont jamais sauvegardés quand Eden est lancé par le broker : celui-ci tue le processus avant qu'Eden n'écrive sa configuration. Tout réglage durable se fait dans le fichier, conteneur arrêté (§5).
Deux réglages par défaut coûtent très cher en performances : la précision GPU sur High et l'absence d'épinglage CPU sur les Ryzen multi-CCD (§5 et §6). Sans eux, comptez ~30 FPS au lieu de 60.
2. Conteneur Eden
Image :
lscr.io/linuxserver/eden. Configuration identique à Dolphin, à trois différences près : le mod, les ports hôte (les deux conteneurs coexistent), et l'appdata dédié.
2.1 Ports (host:container)
Les services écoutent toujours sur 3000/3001/8000 côté conteneur — seuls les ports hôte changent :
Port hôte (exemple)
Port conteneur
Rôle
3010
3000
Selkies HTTP
3011
3001
Selkies HTTPS (flux de jeu)
8001
8000
Broker RomM
2.2 Variables d'environnement
Variable
Valeur
DOCKER_MODS
ghcr.io/loneangelfayt/eden-romm-integration-mod:latest
ROM_ROOT
/romm/library
BROKER_SECRET
le même secret que vos autres conteneurs émulateurs — RomM n'a qu'un
STREAMING_BROKER_SECRET global
NVIDIA_VISIBLE_DEVICES
UUID du GPU (ou
all)
NVIDIA_DRIVER_CAPABILITIES
all
DRINODE /
DRI_NODE
/dev/dri/renderD128 (adapter)
SELKIES_MANUAL_WIDTH /
HEIGHT
1920 /
1080
__EGL_VENDOR_LIBRARY_FILENAMES
/usr/share/glvnd/egl_vendor.d/10_nvidia.json
PUID /
PGID
99 /
100
2.3 Extra Parameters
--gpus all --runtime nvidia --shm-size=1gb --device /dev/nvidia-modeset --cpuset-cpus=0-5,12-17
L'épinglage
--cpuset-cpus est obligatoire sur tout Ryzen multi-CCD : sans lui, comptez environ un tiers de performances en moins (§6). Adaptez impérativement la liste à votre processeur — la valeur ci-dessus correspond au premier CCD d'un 3900X et serait contre-productive sur une autre topologie. Sur un CPU mono-CCD ou Intel, retirez l'option.
2.4 Volumes
Host Path
Container Path
Mode
(le même dossier de ROMs que RomM)
/romm/library
ro
(appdata dédié, ex.
/mnt/user/appdata/eden)
/config
rw
+ les 4 montages du correctif egl-x11 (identiques à la page Dolphin : 2 libs + 2 JSON)
ro
Rappel : le dossier de bibliothèque doit être monté au même chemin conteneur (
/romm/library) dans RomM et dans Eden.
2.5 Vérification du broker
docker logs eden 2>&1 | grep -iE "broker|mod" | head -10
curl -s http://localhost:8001/health # → {"status": "ok"}
3. Premier démarrage d'Eden
Ouvrir
https://IP_DU_SERVEUR:3011, accepter le certificat auto-signé. Au premier lancement, Eden propose de forcer X11 au lieu de Wayland : accepter (c'est le chemin réparé par le correctif egl-x11, et celui qu'utilise le broker).
4. Clés, firmware, jeux et mises à jour
Ordre important : les clés d'abord, le firmware ensuite (Eden a besoin des clés pour installer le firmware).
prod.keys : fichier décompressé, à poser dans l'appdata :
/.local/share/eden/keys/prod.keys. Redémarrer Eden ensuite.
Firmware : garder le ZIP tel quel, le poser dans l'appdata (visible sous
/config/ dans le conteneur), puis dans Eden : Tools → Install Firmware en pointant le ZIP.
Jeux : fichiers directs
.nsp ou
.xci (pas d'archives), dans le dossier de plateforme
switch de la bibliothèque (fs_slug RomM :
switch).
Mises à jour et DLC : Eden n'a pas de « dossier de patchs » — ils s'installent une fois pour toutes dans la NAND virtuelle : File → Install Files to NAND, sélectionner les
.nsp de mise à jour. Vérification : clic droit sur le jeu → Properties → la version doit être celle de la mise à jour. Tout persiste dans l'appdata (
.local/share/eden/nand/) et profite à tous les joueurs. Ne placez pas les mises à jour dans la bibliothèque RomM (elles seraient scannées comme une fausse plateforme).
5. ⚠️ Réglages Eden (à faire dans le fichier, conteneur arrêté)
L'interface ne sauvegarde rien quand Eden est lancé par le broker (§1.3). Éditez donc directement
/.config/eden/qt-config.ini, conteneur arrêté.
Chez Eden, chaque clé est accompagnée d'une ligne
clé\default= : tant qu'elle vaut
true, la valeur est ignorée au profit du défaut compilé. Il faut donc toujours passer
\default à
false.
⚠️ Après toute édition depuis l'hôte (
sed -i,
cat >, redirection…), rétablissez le propriétaire :
chown -R 99:100 . Ces commandes recréent le fichier et peuvent le laisser en
root:root ; Eden ne peut alors plus enregistrer sa configuration, ce qui ne se voit que par un
Config file could not be saved! dans son log.
5.1 Les deux réglages qui comptent
docker stop eden
F=/.config/eden/qt-config.ini
# Backend graphique : Vulkan
sed -i 's/^backend\\default=.*/backend\\default=false/; s/^backend=.*/backend=1/' "$F"
# Précision GPU : Normal (le défaut compilé est High = beaucoup plus lent)
sed -i 's/^gpu_accuracy\\default=.*/gpu_accuracy\\default=false/; s/^gpu_accuracy=.*/gpu_accuracy=0/' "$F"
docker start eden
grep -nE "^backend|^gpu_accuracy" "$F"
gpu_accuracy est le réglage le plus rentable de tout ce guide : le défaut High force des chemins d'émulation beaucoup plus lents, pour une précision dont la quasi-totalité des jeux n'a pas besoin. Mesuré ici : +33 % de FPS en passant sur Normal.
5.2 Énumérations utiles
Depuis janvier 2026, Eden a fusionné le backend de rendu et le backend de shaders dans une seule clé
backend :
backend
Valeur
0
OpenGL (GLSL)
1
Vulkan (recommandé)
2
Null
3
OpenGL GLASM
4
OpenGL SPIR-V
La clé
shader_backend est obsolète : si elle traîne dans votre fichier, elle est ignorée en silence. Supprimez-la.
gpu_accuracy
Valeur
0
Normal (recommandé)
1
High (défaut compilé)
2
Extreme
5.3 Réglages vérifiés, à laisser tels quels
use_asynchronous_gpu_emulation=true (indispensable),
use_multi_core=true,
use_disk_shader_cache=true,
speed_limit=100.
use_asynchronous_shaders : laisser à false — testé ici, l'activer dégrade légèrement les performances.
5.4 Langue française et plein écran
Là encore, deux notions distinctes : la langue de la console émulée (celle que les jeux utilisent) et celle de l'interface d'Eden.
docker stop eden
F=/.config/eden/qt-config.ini
# Langue de la console émulée (jeux) : 2 = français
sed -i 's/^language_index\\default=.*/language_index\\default=false/; s/^language_index=.*/language_index=2/' "$F"
# Plein écran persistant
sed -i 's/^fullscreen\\default=.*/fullscreen\\default=false/; s/^fullscreen=.*/fullscreen=true/' "$F"
docker start eden
grep -nE "^language_index|^fullscreen" "$F"
Valeurs de
language_index :
0 japonais,
1 anglais US,
2 français,
3 allemand,
4 italien,
5 espagnol,
12 anglais UK,
13 français canadien.
Interface d'Eden en français : la clé
language de la section
[UI] stocke un code de locale dont le format varie selon les builds. Le plus fiable est de la faire écrire par Eden lui-même :
Lancer Eden manuellement (pas via RomM) depuis le bureau Selkies
Emulation → Configure → General → Interface language → Français
Fermer Eden proprement (File → Exit) — c'est ce qui déclenche l'écriture de la configuration
Vérifier :
grep -n "^language" /.config/eden/qt-config.ini
Si la ligne
language\default=true subsiste, la passer à
false conteneur arrêté.
Rappel : le plein écran est de toute façon déclenché par le broker (il envoie F11 trois secondes après le lancement). Le réglage ci-dessus le rend cohérent aussi pour les lancements manuels. Pour en sortir ponctuellement — par exemple pour lire le compteur de FPS — appuyer sur Échap.
6. ⚠️ Épinglage CPU — obligatoire sur Ryzen multi-CCD
Voir la section correspondante de la page Dolphin pour l'explication détaillée. En résumé : sur un Ryzen à plusieurs CCD, les threads d'Eden migrant d'un groupe de cache à l'autre coûtent très cher, sans que rien ne paraisse saturer.
Ce réglage n'est pas optionnel : il représente ici la moitié du chemin entre un jeu injouable et 60 FPS stables.
lscpu -e=CPU,CORE,L3 # identifier les groupes de cache
Puis épingler le conteneur sur un seul CCD via
--cpuset-cpus (Extra Parameters) ou l'onglet CPU Pinning du template.
Mesuré sur un 3900X avec Mario Kart 8 Deluxe : 40 → 60 FPS grâce à ce seul réglage. C'est aussi ce qui explique qu'une VM avec vCPU épinglés paraisse bien plus rapide que le même émulateur en conteneur non épinglé.
Récapitulatif des gains mesurés (Mario Kart 8 Deluxe, 1080p) :
Étape
FPS
Configuration initiale
~30 (voire écran noir sans les correctifs §2 de la page Dolphin)
+
gpu_accuracy=0
~40
+ épinglage CCD
60 stables
7. Côté RomM
Ajouter l'entrée dans le bloc
streaming du
config.yml (adapter IP et ports) :
- platform: switch
host: https://IP_DU_SERVEUR:3011
broker_host: http://IP_DU_SERVEUR:8001
label: Eden
Redémarrer RomM, rescanner la plateforme Switch.
8. Test final
Fermer proprement toute instance d'Eden ouverte dans le bureau Selkies (le broker lance la sienne).
Fiche d'un jeu Switch dans RomM → bouton Play on Eden.
Le jeu doit s'afficher, avec le son.
Vérifier l'accélération GPU pendant qu'un jeu tourne :
docker exec -u abc eden sh -c 'for p in /proc/[0-9]*; do ls -l $p/fd 2>/dev/null | grep -q nvidia && echo "$(basename $p) $(tr "\0" " " < $p/cmdline | cut -c1-60)"; done'
Le processus
eden doit apparaître dans la liste.
Astuce mesure : le broker met Eden en plein écran (il envoie F11 après 3 s), ce qui masque la barre d'état. Appuyez sur Échap pour en sortir et lire le FPS et le pourcentage de vitesse.
9. Dépannage rapide
Symptôme
Cause
Solution
« error while running the video core », log :
Invalid device index -1
Nœud
/dev/nvidia-modeset absent → Vulkan sans présentation
§2.2 de la page Dolphin
Liste de GPU vide dans les menus Graphics
Idem
§2.2 de la page Dolphin
Réglages qui « ne tiennent pas »
Config jamais écrite (processus tué par le broker)
Éditer
qt-config.ini conteneur arrêté (§5)
Réglage écrit mais sans effet
Ligne
clé\default=true restée en place
Passer
\default à
false (§5)
~30 FPS au lieu de 60, sans que rien ne sature
gpu_accuracy sur High et/ou pas d'épinglage CPU
§5.1 et §6
Erreur de clés à l'installation du firmware ou d'un jeu
prod.keys absent ou mal placé
§4, puis redémarrer Eden
Jeu à la version 1.0.0 malgré la mise à jour
Mise à jour non installée en NAND
§4 — File → Install Files to NAND
Jeu très lent,
eden absent du test des fd
Correctif egl-x11 manquant
§2.1 de la page Dolphin
Écran noir après une mise à jour du driver NVIDIA
Version des libs egl-x11 changée sur l'hôte
Mettre à jour les Host Path des montages
403 Forbidden nginx en ouvrant l'URL du flux
Portail du mod : normal sans session RomM ouverte
Page « Accès distant », §5
Écran noir depuis RomM, alors que le lancement réussit côté serveur
Portail du mod : RomM 5.1.0 n'envoie pas le
stream_token
Page « Accès distant », §5
Le problème revient après chaque modification de template
Le conteneur est recréé, le portail est réinstallé
Script
custom-cont-init.d — page « Accès distant », §5
RomM — Accès distant, publication HTTPS et isolation réseau
Cette page complète les guides Dolphin et Eden. Elle explique comment rendre le streaming utilisable en dehors du réseau local, et comment limiter les risques que cela crée.
⚠️ À lire en entier avant de publier quoi que ce soit. Dans l'état actuel de RomM (5.1.0), rendre le flux accessible depuis Internet revient à exposer un bureau interactif sans authentification. La section 2 détaille exactement ce que cela implique.
1. Pourquoi ça ne fonctionne qu'en LAN par défaut
Le bloc
streaming du
config.yml contient deux adresses par conteneur, et elles ne sont pas utilisées par le même acteur :
- platform: switch
host: https://192.168.10.100:3713 # ← chargé par le NAVIGATEUR du joueur
broker_host: http://192.168.10.100:8011 # ← appelé par le BACKEND de RomM
broker_host est un appel de serveur à serveur. RomM le contacte lui-même, en interne. Une adresse privée convient parfaitement.
host est chargé par le navigateur du joueur, dans une iframe. RomM ne relaie rien : c'est le poste du joueur qui établit la connexion vidéo directement avec Selkies.
Conséquence : avec une IP privée dans
host, un joueur hors du réseau obtient un délai d'attente. La page RomM se charge, la session est bien réclamée côté serveur, mais l'image n'arrive jamais.
host doit donc être une adresse joignable depuis le poste du joueur. Il n'existe que deux façons d'y arriver : amener le joueur dans le réseau (VPN), ou amener le flux jusqu'à lui (publication). Il n'y a pas de troisième voie.
2. ⚠️ Le trou de sécurité qu'il faut assumer
Le point d'accès du flux n'a aucune authentification. RomM n'attache aucun identifiant à l'URL de l'iframe, et
STREAMING_BROKER_SECRET — qui existe pourtant — ne protège que le côté
broker_host, jamais le côté
host. Il est facile de croire qu'on est couvert alors qu'on ne l'est pas.
Ce que cela signifie concrètement une fois le sous-domaine publié :
N'importe qui connaissant l'URL obtient un bureau interactif dans le conteneur, sans jamais toucher à RomM.
L'obscurité ne protège pas. Chaque certificat Let's Encrypt est publié dans les journaux de Certificate Transparency, consultables publiquement. Un sous-domaine « non devinable » y apparaît en quelques minutes. Constaté ici : des robots scannaient le sous-domaine moins de 24 h après sa création.
La bibliothèque est montée dans le conteneur. Même en lecture seule, elle reste intégralement copiable.
/config est en écriture et contient les clés de console, le firmware et les sauvegardes.
Le GPU et la bande passante sont utilisables par qui obtient le bureau.
Le streaming contourne aussi le modèle de permissions de RomM : un compte en lecture seule peut réclamer une session et se retrouver sur ce bureau. Cela vaut même sur une instance purement locale.
Ce que l'isolation réseau (section 4) apporte — et n'apporte pas
Le cloisonnement décrit plus bas ferme le pire scénario : le rebond depuis le conteneur vers le reste du réseau local et vers l'hôte. C'est la mesure la plus rentable, et elle ne coûte rien aux joueurs.
En revanche, elle ne protège ni la bibliothèque, ni les clés, ni le GPU. Elle réduit le rayon d'impact, elle ne referme pas la porte.
Vos options, en toute honnêteté
Option
Protection
Friction pour les joueurs
LAN uniquement
Totale
Personne ne joue à distance
VPN (WireGuard, Tailscale)
Totale
Chaque joueur installe un client
Publication (cette page)
Aucune sur le flux
Aucune
Publication + Access List NPM
Réelle
Identifiants à ressaisir à chaque session de navigateur
L'Access List de NPM mérite d'être mentionnée mais déçoit à l'usage : Chrome bloque les invites d'authentification dans une iframe cross-origin, il faut donc visiter le sous-domaine une première fois pour saisir ses identifiants, et la mémorisation ne survit pas à la fermeture du navigateur. Le SSO (Authelia, Authentik…) ne fonctionne pas davantage dans ce contexte, pour les mêmes raisons de cookies
SameSite et d'en-têtes
X-Frame-Options.
Une atténuation simple et gratuite
Désactivez les Proxy Hosts entre les sessions. Deux clics dans NPM, et le sous-domaine ne répond plus du tout le reste du temps. La fenêtre d'exposition passe de « permanente » à « quand quelqu'un joue », sans aucune contrainte pour les joueurs.
C'est temporaire
Une PR est en cours côté RomM pour corriger exactement ce point : le broker émettra un jeton de session que RomM ajoutera à l'URL du flux, et un portail le validera avant d'autoriser la connexion. Suivre rommapp/romm#4173 et l'issue #3968. Cette page devra être révisée à ce moment-là.
3. Publier le flux derrière Nginx Proxy Manager
Un sous-domaine par émulateur. Exemple avec
eden.thymon.fr :
DNS — un enregistrement CNAME
eden vers votre domaine, exactement comme pour RomM. Vérifier la résolution avant de demander le certificat.
Proxy Host, onglet Details :
Champ
Valeur
Domain Names
eden.thymon.fr
Scheme
https
Forward Hostname / IP
eden (nom du conteneur, voir §4) ou l'IP du serveur
Forward Port
3001
Block Common Exploits
activé
Websockets Support
ACTIVÉ
🚨 Visez impérativement le port HTTPS du conteneur (3001), jamais le port HTTP (3000). Le portail de sécurité installé par le mod n'existe que dans le bloc serveur HTTPS. Proxyfier vers le port 3000 contourne toute protection — l'erreur a été commise ici, et l'interface s'est retrouvée servie sans aucun filtre. NPM ne vérifie pas le certificat en amont, l'auto-signé du conteneur ne pose donc aucun problème.
Onglet SSL : certificat Let's Encrypt, Force SSL, HTTP/2.
Onglet Advanced — le flux est une connexion WebSocket de longue durée, les délais par défaut la couperaient au bout d'une minute d'inactivité :
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
client_max_body_size 0;
Puis dans
config.yml, remplacer uniquement
host: :
host: https://eden.thymon.fr
broker_host ne change pas : c'est RomM qui l'appelle, en interne.
Tant qu'un seul émulateur est migré, la page RomM restera signalée « HTTPS brisé » par Chrome : elle continue de charger du contenu actif depuis les autres origines en certificat auto-signé. L'avertissement ne disparaît qu'une fois tous les flux publiés. Si le message persiste ensuite, réinitialisez les autorisations du site dans Chrome — l'option « autoriser le contenu non sécurisé » reste mémorisée.
4. Réseau Docker dédié et isolation
C'est la partie qui réduit réellement les risques, et elle apporte aussi de la simplicité.
4.1 Créer le réseau
docker network create --subnet 172.30.0.0/24 romm-net
Placez-y RomM et tous les conteneurs d'émulation, avec une IP fixe pour chacun (le champ apparaît dans le template Unraid dès qu'un réseau personnalisé est sélectionné) :
romm → 172.30.0.5 eden → 172.30.0.10
dolphin → 172.30.0.11 rpcs3 → 172.30.0.12
4.2 Supprimer tous les ports publiés
Une fois les conteneurs sur le même réseau, ils se joignent par leur nom. Plus aucun port n'a besoin d'être publié sur l'hôte — ni les ports Selkies, ni surtout les ports des brokers, qui commandent le lancement des jeux et n'ont rien à faire sur le LAN.
Dans
config.yml, utilisez les noms et les ports internes :
broker_host: http://eden:8000 # et non http://IP:8011
Et dans NPM,
Forward Hostname =
eden, port
3001.
4.3 NPM doit rester sur les deux réseaux
NPM doit rejoindre
romm-net pour résoudre les noms, tout en gardant son accès au LAN pour proxyfier vos autres services. On l'attache donc aux deux :
docker network connect romm-net NginxProxyManager
⚠️ Cette commande ne survit pas à une recréation du conteneur (mise à jour, modification de template). Pour la rendre permanente, ajoutez dans le champ Post Arguments du template NPM :
&& docker network connect romm-net NginxProxyManager
Unraid l'ajoute à la fin du
docker run, donc le rattachement est rejoué automatiquement à chaque recréation — exactement le seul cas où il se perdait.
4.4 Couper les conteneurs du reste du réseau
Deux règles suffisent. Attention : la première ne couvre pas l'accès à l'hôte lui-même, car ce trafic passe par la chaîne
INPUT et non
FORWARD — d'où la seconde.
Créez un User Script planifié sur At First Array Start (les règles iptables ne survivent pas à un redémarrage) :
#!/bin/bash
SUBNET=172.30.0.0/24
LAN=192.168.10.0/24 # adapter
iptables -D DOCKER-USER -s "$SUBNET" -d "$LAN" -j DROP 2>/dev/null
iptables -I DOCKER-USER -s "$SUBNET" -d "$LAN" -j DROP
iptables -D INPUT -s "$SUBNET" -j DROP 2>/dev/null
iptables -I INPUT -s "$SUBNET" -j DROP
Les
-D en amont évitent d'empiler des doublons si le script est relancé.
Vérification :
docker exec eden sh -c 'timeout 3 curl -sI http://IP_UNRAID >/dev/null && echo "PASSE (raté)" || echo "BLOQUE (bon)"'
docker exec eden sh -c 'timeout 3 curl -sI http://AUTRE_MACHINE_LAN >/dev/null && echo "PASSE (raté)" || echo "BLOQUE (bon)"'
Les deux doivent afficher BLOQUE, et le streaming doit continuer de fonctionner (RomM appelle le broker, jamais l'inverse).
NPM garde son IP sur le réseau bridge pour joindre le LAN, il n'est donc pas concerné par ces règles. Si vous choisissez de le placer uniquement sur
romm-net, il faudra ajouter des règles
ACCEPT pour son IP avant les DROP.
5. Le portail nginx du mod Eden
Les versions récentes du mod
eden-romm-integration installent un portail de sécurité : nginx interroge le broker (
auth_request →
/verify) et exige un
stream_token avant de servir quoi que ce soit.
C'est une bonne chose sur le principe — mais RomM 5.1.0 n'envoie pas encore ce jeton. Le résultat est un 403 systématique, qui se manifeste par un écran noir dans le lecteur. Le journal d'accès du conteneur est sans ambiguïté :
GET /?stream_token= → 200, puis GET /websockets → 101 (ouverture manuelle)
GET / → 403 (iframe de RomM)
Le mod Dolphin, plus ancien, n'a pas ce portail — d'où une Switch qui ne fonctionne plus alors que la GameCube va bien.
Neutraliser le portail automatiquement
Le portail est réinstallé à chaque démarrage du conteneur, donc un
sed manuel est à refaire sans arrêt. Le point d'accroche
custom-cont-init.d s'exécute malheureusement avant que le mod ne pose le portail : il faut donc y lancer une boucle de surveillance en arrière-plan.
mkdir -p /mnt/user/appdata/eden-init
cat > /mnt/user/appdata/eden-init/10-gate-off.sh << 'EOF'
#!/bin/bash
(
while true; do
if grep -qE '^[[:space:]]*auth_request /_stream_auth;' /etc/nginx/sites-available/default 2>/dev/null; then
sed -i '/auth_request off/!s|^\( *auth_request .*\)|#\1|' /etc/nginx/sites-available/default
nginx -s reload 2>/dev/null && echo "[gate-off] portail neutralise"
fi
sleep 30
done
) &
EOF
chmod +x /mnt/user/appdata/eden-init/10-gate-off.sh
chown -R root:root /mnt/user/appdata/eden-init
Puis, dans le template Eden, ajouter un chemin :
Champ
Valeur
Container Path
/custom-cont-init.d
Host Path
/mnt/user/appdata/eden-init
Access Mode
Read Only
Ce dossier est volontairement placé hors de
/config : ces scripts s'exécutent en root, et
/config est inscriptible par l'application. Sur un conteneur exposé, un répertoire d'exécution root modifiable de l'intérieur serait une porte ouverte. C'est précisément pour cette raison que LinuxServer a sorti ces dossiers de
/config.
Vérification (compter 40 secondes après le démarrage) :
docker logs eden 2>&1 | grep gate-off
docker exec eden grep -n "auth_request /_stream_auth" /etc/nginx/sites-available/default
Vous devez voir « portail neutralise » et la ligne commentée.
🔻 À supprimer le jour où la PR v2 arrive. Une fois que RomM enverra le jeton, ce portail deviendra une vraie protection — et ce script la désactiverait silencieusement.
6. Pièges à connaître
nginx met en cache la résolution des noms. NPM résout
eden au chargement de sa configuration et garde l'IP. Après une recréation de conteneur, il tape sur l'ancienne adresse → 502 Bad Gateway, alors qu'un
curl depuis NPM fonctionne (résolution fraîche). Remède : redémarrer NPM. Les IP fixes évitent le problème.
Toute modification d'un template recrée le conteneur : le portail revient, la couche inscriptible est effacée (paquets installés à la main et journaux nginx perdus), et les patchs du mod sont réappliqués. Un simple redémarrage, lui, conserve tout.
Le journal d'accès nginx est dans le conteneur, à
/var/log/nginx/access.log — pas sous
/config. C'est l'outil de diagnostic le plus fiable : il montre l'URI exacte reçue et son code de retour.
Vérifier une exposition depuis l'extérieur : utilisez le journal d'accès ou un téléphone en 4G, en navigation privée. Un navigateur qui possède déjà un cookie de session vous laissera passer alors que tout le monde est bloqué.
7. Récapitulatif de l'architecture cible
Un sous-domaine par émulateur, en HTTPS Let's Encrypt, WebSockets activés, pointant vers le port 3001 du conteneur
Tous les conteneurs sur un réseau Docker dédié avec IP fixes, aucun port publié
Brokers joignables uniquement depuis ce réseau, par nom de conteneur
NPM attaché aux deux réseaux, rattachement rendu permanent par Post Arguments
Conteneurs coupés du LAN et de l'hôte par deux règles iptables persistées
Portail Eden neutralisé automatiquement, en attendant la v2
Et l'acceptation assumée que le flux reste accessible sans authentification tant que la v2 n'est pas là