# 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 :

```bash
#!/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.

1. Aller dans **Main &gt; Flash &gt; Syslinux Configuration**.
2. Dans l’entrée `Unraid OS`, ajouter à la fin de la ligne : ```
    nvidia-drm.modeset=1
    ```
3. **Redémarrer Unraid**.
4. Vérifier que le KMS est bien activé : ```bash
    cat /sys/module/nvidia_drm/parameters/modeset
    ```
    
    Il doit renvoyer : `<strong>Y</strong>`

#### 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).

1. Ouvrir le terminal Unraid.
2. Éditer le fichier de boot : `nano /boot/config/go`
3. Ajouter ce bloc à la fin du fichier :

```bash
# --- 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 :

```bash
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 :

```bash
docker create --rm \
  --mount source=nvidia-driver-vol,destination=/usr/nvidia \
  gow/nvidia-driver:latest sh

```

Vérifier qu’il existe :

```bash
docker volume ls | grep nvidia-driver
```

Il doit afficher :`<strong>nvidia-driver-vol</strong>`

#### 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.

1. Dans User Scripts, créer un script nommé par exemple **"Wolf - Rebuild Nvidia driver volume"**.
2. Contenu du script :

```bash
#!/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."

```

3. 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.

<p class="callout info">**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.</p>

#### 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 &gt; Add Container**.

1. Dans le terminal Unraid, lancer :

```bash
mkdir -p /boot/config/plugins/dockerMan/templates-user

cat << 'EOF' > /boot/config/plugins/dockerMan/templates-user/my-wolf.xml
<?xml version="1.0"?>
<Container version="2">
    <Name>wolf</Name>
    <Repository>ghcr.io/games-on-whales/wolf:stable</Repository>
    <Registry>https://ghcr.io/</Registry>
    <Network>host</Network>
    <MyIP/>
    <Shell>bash</Shell>
    <Privileged>true</Privileged>
    <Support>https://games-on-whales.github.io/wolf/stable/</Support>
    <Project>https://github.com/games-on-whales/wolf</Project>
    <Overview>Wolf est un serveur de streaming Moonlight permettant de partager un hôte pour plusieurs sessions de jeu et bureaux virtuels.</Overview>
    <Category>GameServers:</Category>
    <WebUI>http://[IP]:8080/</WebUI>
    <TemplateURL/>
    <Icon>https://games-on-whales.github.io/assets/favicon.png</Icon>
    <!-- Volume Docker Nvidia + règle cgroup -->
    <ExtraParams>-v nvidia-driver-vol:/usr/nvidia:rw --device-cgroup-rule="c 13:* rmw"</ExtraParams>
    <PostArgs/>
    <CPUset/>
    <DateInstalled>0</DateInstalled>
    <DonateText/>
    <DonateLink/>
    <Description>Wolf est un serveur de streaming Moonlight permettant de partager un hôte pour plusieurs sessions de jeu et bureaux virtuels.</Description>

    <Networking>
        <Mode>host</Mode>
        <Publish/>
    </Networking>

    <!-- Volumes "bruts" utilisés par le conteneur -->
    <Data>
        <Volume>
            <HostDir>/mnt/user/appdata/wolf</HostDir>
            <ContainerDir>/etc/wolf</ContainerDir>
            <Mode>rw</Mode>
        </Volume>
        <Volume>
            <HostDir>/var/run/docker.sock</HostDir>
            <ContainerDir>/var/run/docker.sock</ContainerDir>
            <Mode>rw</Mode>
        </Volume>
        <Volume>
            <HostDir>/dev</HostDir>
            <ContainerDir>/dev</ContainerDir>
            <Mode>rw</Mode>
        </Volume>
        <Volume>
            <HostDir>/run/udev</HostDir>
            <ContainerDir>/run/udev</ContainerDir>
            <Mode>rw</Mode>
        </Volume>
    </Data>

    <!-- Variables d'environnement réelles -->
    <Environment>
        <Variable>
            <Value>nvidia-driver-vol</Value>
            <Name>NVIDIA_DRIVER_VOLUME_NAME</Name>
            <Mode/>
        </Variable>
    </Environment>

    <Labels/>

    <!-- Config = ce qui apparaît dans l'UI Unraid -->

    <!-- Appdata Wolf -->
    <Config Name="Wolf Config" Target="/etc/wolf" Default="/mnt/user/appdata/wolf" Mode="rw"
            Description="Dossier de configuration de Wolf"
            Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/wolf</Config>

    <!-- Docker socket -->
    <Config Name="Docker socket" Target="/var/run/docker.sock" Default="/var/run/docker.sock" Mode="rw"
            Description="Socket Docker (nécessaire pour lancer les apps GOW)"
            Type="Path" Display="advanced" Required="true" Mask="false">/var/run/docker.sock</Config>

    <!-- /dev host -->
    <Config Name="Dev" Target="/dev" Default="/dev" Mode="rw"
            Description="Montage de /dev du host"
            Type="Path" Display="advanced" Required="true" Mask="false">/dev</Config>

    <!-- /run/udev host -->
    <Config Name="Udev" Target="/run/udev" Default="/run/udev" Mode="rw"
            Description="Montage de /run/udev du host"
            Type="Path" Display="advanced" Required="true" Mask="false">/run/udev</Config>

    <!-- Variable : nom du volume Nvidia -->
    <Config Name="NVIDIA_DRIVER_VOLUME_NAME" Target="NVIDIA_DRIVER_VOLUME_NAME" Default="nvidia-driver-vol" Mode=""
            Description="Nom du volume Docker contenant les bibliothèques Nvidia (méthode Manual)"
            Type="Variable" Display="advanced" Required="false" Mask="false">nvidia-driver-vol</Config>

    <!-- Devices GPU / input -->

    <Config Name="Device /dev/dri" Target="/dev/dri" Default="/dev/dri" Mode=""
            Description="DRM / GPU devices"
            Type="Device" Display="advanced" Required="true" Mask="false">/dev/dri</Config>

    <Config Name="Device /dev/nvidiactl" Target="/dev/nvidiactl" Default="/dev/nvidiactl" Mode=""
            Description="Nvidia control device"
            Type="Device" Display="advanced" Required="true" Mask="false">/dev/nvidiactl</Config>

    <Config Name="Device /dev/nvidia0" Target="/dev/nvidia0" Default="/dev/nvidia0" Mode=""
            Description="GPU Nvidia principal"
            Type="Device" Display="advanced" Required="true" Mask="false">/dev/nvidia0</Config>

    <Config Name="Device /dev/nvidia-uvm" Target="/dev/nvidia-uvm" Default="/dev/nvidia-uvm" Mode=""
            Description="Nvidia UVM"
            Type="Device" Display="advanced" Required="true" Mask="false">/dev/nvidia-uvm</Config>

    <Config Name="Device /dev/nvidia-uvm-tools" Target="/dev/nvidia-uvm-tools" Default="/dev/nvidia-uvm-tools" Mode=""
            Description="Nvidia UVM tools"
            Type="Device" Display="advanced" Required="true" Mask="false">/dev/nvidia-uvm-tools</Config>

    <Config Name="Device /dev/nvidia-cap1" Target="/dev/nvidia-caps/nvidia-cap1" Default="/dev/nvidia-caps/nvidia-cap1" Mode=""
            Description="Nvidia capability 1"
            Type="Device" Display="advanced" Required="true" Mask="false">/dev/nvidia-caps/nvidia-cap1</Config>

    <Config Name="Device /dev/nvidia-cap2" Target="/dev/nvidia-caps/nvidia-cap2" Default="/dev/nvidia-caps/nvidia-cap2" Mode=""
            Description="Nvidia capability 2"
            Type="Device" Display="advanced" Required="true" Mask="false">/dev/nvidia-caps/nvidia-cap2</Config>

    <Config Name="Device /dev/uinput" Target="/dev/uinput" Default="/dev/uinput" Mode=""
            Description="uinput (manettes virtuelles, clavier, etc.)"
            Type="Device" Display="advanced" Required="true" Mask="false">/dev/uinput</Config>

    <Config Name="Device /dev/uhid" Target="/dev/uhid" Default="/dev/uhid" Mode=""
            Description="UHID (entrée HID user space)"
            Type="Device" Display="advanced" Required="true" Mask="false">/dev/uhid</Config>

</Container>
EOF

```

2. Aller dans **Docker &gt; Add Container** et choisir le template **wolf** dans la liste déroulante. Normalement, tout est déjà correctement renseigné.
3. Cliquer sur **Apply** pour créer et lancer le conteneur Wolf.

<p class="callout info">**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)</p>

---

### 3. Configuration de Wolf (config.toml &amp; 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

```bash
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

```bash
setfacl -R -m g:1000:rwx /mnt/user/Games
setfacl -R -d -m g:1000:rwx /mnt/user/Games

```

<p class="callout warning">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.</p>

#### Modifier le `config.toml`

**Fichier cible (côté host) :**

```bash
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 :

```bash
[[profiles]]
id = 'moonlight-profile-id'
```

et

```bash
[[profiles]]
id = 'user'
name = 'User'
```

Et vous collez à la place la configuration de votre premier utilisateur suivante :

<p class="callout info">Remplacez bien les quatre **&lt;NOM\_DU\_COMPTE&gt;**.  
Remplacez **&lt;url-image-png&gt;** par une image PNG. Dimension idéale width=112px height=150px (facilement reconnaissable dans Moonlight).</p>

```toml
# --- Utilisateur 1 : <NOM_DU_COMPTE> ---
    [[profiles.apps]]
    icon_png_path = '<url-image-png>'
    start_virtual_compositor = true
    title = '<NOM_DU_COMPTE>'

        [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/<NOM_DU_COMPTE>:/home/retro/.steam:rw'
        ]
        name = '<NOM_DU_COMPTE>'
        ports = []
        type = 'docker'

```

<p class="callout info">**'XKB\_DEFAULT\_LAYOUT=fr'** → clavier AZERTY français.  
**'TZ=Europe/Paris'** → heure correcte dans l’app.</p>

<p class="callout info">**'/mnt/user/Games:/home/retro/games-unraid:rw'** → point de montage du répertoire d’installation des jeux.  
**'/mnt/user/appdata/wolf/&lt;NOM\_DU\_COMPTE&gt;:/home/retro/.steam:rw'** → rend l’installation de Steam persistante par utilisateur.</p>

### 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).

<iframe allowfullscreen="allowfullscreen" height="314" src="https://www.youtube.com/embed/8an-SvnD4pk?si=IFsfXUJj87F0c-a3" width="560"></iframe>

#### 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 = '<NOM_DU_COMPTE>'
icon_png_path = 'url_de_votre_image.png'
pin = [ 3, 2, 1, 4 ]

```

<p class="callout info">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.</p>

<p class="callout info">Toutes les apps de cet utilisateur doivent être présentes sous ces lignes.</p>

---

### 4. Configuration Post-Installation

#### A. Appairage Moonlight

1. Lancer Moonlight sur le client (PC, TV, Tablette).
2. Noter le code PIN affiché.
3. Consulter les logs Docker : `docker logs -f wolf`
4. Cliquer sur le lien `http://IP_UNRAID:47989/pin/ID` visible dans les logs et entrer le code PIN à 4 chiffres affiché dans Moonlight.  
    ![image.png](https://bookstack.thymon.fr/uploads/images/gallery/2025-11/scaled-1680-/image.png)
5. La page web vous demande de renseigner le PIN pour déverrouiller Moonlight.  
    ![image.png](https://bookstack.thymon.fr/uploads/images/gallery/2025-11/scaled-1680-/bmTimage.png)

À 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).

1. Fermer Big Picture.
2. Dans Steam, aller dans **Paramètres &gt; Stockage**.
3. Ajouter un lecteur &gt; remonter à `/Home` &gt; sélectionner le dossier `/games-unraid`.
4. Le définir comme lecteur **par défaut** (étoile).

<p class="callout info">À faire pour chaque compte Steam.</p>

#### 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).

![MangoHud](https://games-on-whales.github.io/wolf/stable/_images/mangohud_example.gif)

##### Jeux OpenGL (Linux natif)

Pour les jeux OpenGL, vous pouvez l’activer jeu par jeu en ajoutant :

```bash
mangohud %command%

```

![image.png](https://bookstack.thymon.fr/uploads/images/gallery/2025-11/scaled-1680-/8sUimage.png)

##### 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.

<p class="callout success"><span style="color: rgb(224, 62, 45);">**&lt;en cours de rédaction pour l'ajout des autres app&gt;**</span></p>

## 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'
```

<p class="callout info">Pensez à ajouter dans les variables d'environnement : **'XKB\_DEFAULT\_LAYOUT=fr'** pour le clavier AZERTY</p>

<p class="callout info">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/&lt;NOM\_DU\_COMPTE&gt;:/home/retro/.heroic:rw'**  
 **\]**</p>

### Premier lancement

Voici l'interface au démarrage de **Heroic.**

[![image.png](https://bookstack.thymon.fr/uploads/images/gallery/2025-11/scaled-1680-/NqOimage.png)](https://bookstack.thymon.fr/uploads/images/gallery/2025-11/NqOimage.png)

#### 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.

1. Allez dans le menu **`Options`** puis **`Général`**.
2. Dans la section `<strong>Chemin d'installation par défaut</strong>`, mettez ```
    /home/retro/games-unraid
    ```
    
    <p class="callout info">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.</p>

#### 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.

1. Allez dans le menu `<strong>Gestion de Wine</strong>`
2. Choisir l'onglet **`GE-Proton`**
3. Cliquez sur la flèche de téléchargement pour la version `<strong>GE-Proton-Latest</strong>`

Utilisation de Proton par dédaut :

1. Allez dans le menu **`Options`** puis **`Paramètre de jeu par défaut`**.
2. 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.

1. Au lancement de Steam, quittez le mode Big Picture.
2. Allez dans le menu `<strong>Steam</strong>` puis **`Paramètres`.**
3. Dans les paramètres, allez dans le menu **`Bibliothèque`**, ensuite cliquez **`Ajouter un jeu non Steam`**
4. Cliquez sur `<strong>Parcourir...</strong>` Et naviguez dans `/home/retro/games-unraid` (le répertoire de notre tuto)
5. Sélectionner les `<strong>.exe</strong>` des jeux que vous voulez importer dans Steam.
6. Ne pas oublier de cliquer `<strong>Ajouter la sélection</strong>` 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

1. Dans Steam, allez dans les `<strong>Propriétés...</strong>` de votre jeu.
2. Dans le menu `<strong>Compatibilité</strong>`, cochez `<strong>forcer l'utilisation d'un outil...</strong>` Et choisissez `<strong>Proton Expérimental</strong>`

[![image.png](https://bookstack.thymon.fr/uploads/images/gallery/2025-11/scaled-1680-/60Oimage.png)](https://bookstack.thymon.fr/uploads/images/gallery/2025-11/60Oimage.png)

# 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 :

```bash
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 » :

```bash
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 :

```bash
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 :

```bash
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 :

```bash
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

```bash
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`.

```bash
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) :

```yaml
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/<plateforme>/` ou `library/<plateforme>/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

1. 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).
2. Dans RomM, ouvrir la fiche d'un jeu GameCube → bouton **Play on Dolphin**.
3. Le jeu doit s'afficher, fluide, avec le son.

**Vérifier que le GPU est bien utilisé** (pendant qu'un jeu tourne) :

```bash
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/<pid>/fd` des processus de l'utilisateur `abc` échoue silencieusement.

---

## 6. Réglages Dolphin utiles

Le fichier de configuration est `<appdata>/.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 <appdata>`. 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]`) :

```ini
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]` :

```bash
docker stop dolphin
F=<appdata>/.config/dolphin-emu/Dolphin.ini

sed -i '/^SelectedLanguage *=/d' "$F"
sed -i '/^\[Core\]/a SelectedLanguage = 2' "$F"

chown -R 99:100 <appdata>
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 :

```bash
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 `<appdata>/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

1. **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.
2. **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`).
3. **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).
4. **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

```bash
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).

1. **`prod.keys`** : fichier décompressé, à poser dans l'appdata : `<appdata>/.local/share/eden/keys/prod.keys`. Redémarrer Eden ensuite.
2. **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.
3. **Jeux** : fichiers directs `.nsp` ou `.xci` (pas d'archives), dans le dossier de plateforme **`switch`** de la bibliothèque (fs_slug RomM : `switch`).
4. **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 `<appdata>/.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 <appdata>`. 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

```bash
docker stop eden
F=<appdata>/.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.

```bash
docker stop eden
F=<appdata>/.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 :

1. Lancer Eden **manuellement** (pas via RomM) depuis le bureau Selkies
2. Emulation → Configure → General → Interface language → *Français*
3. **Fermer Eden proprement** (File → Exit) — c'est ce qui déclenche l'écriture de la configuration
4. Vérifier : `grep -n "^language" <appdata>/.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.

```bash
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) :

```yaml
    - 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

1. Fermer proprement toute instance d'Eden ouverte dans le bureau Selkies (le broker lance la sienne).
2. Fiche d'un jeu Switch dans RomM → bouton **Play on Eden**.
3. Le jeu doit s'afficher, avec le son.

Vérifier l'accélération GPU pendant qu'un jeu tourne :

```bash
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 :

```yaml
    - 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](https://github.com/rommapp/romm/pull/4173) et [l'issue #3968](https://github.com/rommapp/romm/issues/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:` :

```yaml
      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

```bash
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** :

```yaml
      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 :

```bash
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) :

```bash
#!/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 :**

```bash
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=<jeton>   → 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.

```bash
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) :

```bash
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à