Самохостерный облачный гейминг или запускаем стим в докере

Если совсем кратко, то:

Если не кратко, то:

В начале было слово… Заядлые геймеры, наверное, помнят, что в бывшей Nvidia Geforce Experience была возможность стримить игры на различные устройства. Например, при нахождении в одной сети можно было с NVIDIA SHIELD играть в игры с компьютера. Сообщество разработало аналогичный софт под названием Sunshine

Смысл в следующем: на компьютере запускается Sunshine. На конечные устройства (например телефон или телевизионная приставка) устанавливается клиент Moonlight (клиент есть под разные платформы). Между собой сопрягаются и всё - можно на телефоне играть в игры запущенные на вашем ПК. Но в данной схеме есть недостаток:

  • во время игры компьютером нельзя пользоваться, т.к. Sunshine’у необходимо перехватывать видеовыход, то есть игра должна отображаться на мониторе и если потрогать устройства ввода на ПК во время игры, то естественно это прокинется в игру;
  • видно что происходит на мониторе ПК.

Проблема с отображением решается втыканием в HDMI разъём видеокарточки заглушки, которая эмулирует монитор или установкой специальных драйверов, которые также эмулируют монитор, но проблема с вводом остаётся.

В итоге сообщество придумало решение примерно по такой же схеме как и Sunshine, но всё упаковать в докер. Внутри контейнера эмулируется видеовывод, который перехватывается GStreamer’ом и отдается в Moonlight. Также для контейнера эмулируются устройства ввода - клавиатура, мышка, геймпад. Сам проект назвали Games on whales (Игры на китах), а сам сервер который создаёт необходимое для запуска игры окружение назвали Wolf.

Принцип работы следующий: запускается контейнер с wolf. Он начинает слушать подключения от клиентов (кстати, да - одновременных клиентов может быть несколько) и запускает контейнер с pulse-audio (сервер для проброса звука в/из контейнер “приложения”). Когда клиент подключается и говорит какое “приложение” запустить, то wolf запускает соответствующий контейнер с “приложением”, настраивает его и начинает транслировать клиенту видеопоток из контейнера и в обратную сторону события устройств ввода.

Подробно запуск описывать, наверное, нет смысла, так как в документации все популярно описано. Главное требование - чтобы была видеокарточка для кодирования видеопотока. Если есть nvidia то советую запускать по инструкции Nvidia (Manual), поскольку с Nvidia container toolkit есть проблемы.
Мой текущий portainer stack:

Спойлер
version: "3"
services:
  wolf:
#    image: ghcr.io/games-on-whales/wolf:stable
    image: ghcr.io/games-on-whales/wolf:sha-74976d7
    container_name: wolf
    environment:
      - XDG_RUNTIME_DIR=/tmp/sockets
      - NVIDIA_DRIVER_VOLUME_NAME=nvidia-driver-vol
      - HOST_APPS_STATE_FOLDER=/etc/wolf
      - WOLF_RENDER_NODE=/dev/dri/renderD129
      - WOLF_ENCODER_NODE=/dev/dri/renderD129
      - TZ=Europe/Moscow
    volumes:
      - /etc/wolf/:/etc/wolf:rw
      - /tmp/sockets:/tmp/sockets:rw
      - /var/run/docker.sock:/var/run/docker.sock:rw
      - /dev/:/dev/:rw
      - /run/udev:/run/udev:rw
      - nvidia-driver-vol:/usr/nvidia:rw
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
    devices:
      - /dev/dri
      - /dev/uinput
      - /dev/uhid
      - /dev/nvidia-uvm
      - /dev/nvidia-uvm-tools
      - /dev/nvidia-caps/nvidia-cap1
      - /dev/nvidia-caps/nvidia-cap2
      - /dev/nvidiactl
#      - /dev/nvidia0
      - /dev/nvidia1
      - /dev/nvidia-modeset
    device_cgroup_rules:
      - 'c 13:* rmw'
    network_mode: host
    restart: unless-stopped

volumes:
  nvidia-driver-vol:
    external: true

Пояснения

image: ghcr.io/games-on-whales/wolf:sha-74976d7
не stable потому что у меня на одном из обновлений что-то сломалось и я захардкодил определённую версию. Может сейчас на stable всё нормально.

environment:
  - TZ=Europe/Moscow
volumes:
  - /etc/localtime:/etc/localtime:ro
  - /etc/timezone:/etc/timezone:ro

Время и зону - чтоб везде время отображалось правильно

#      - /dev/nvidia0
      - /dev/nvidia1

У меня две GPU: 3060 и 4060Ti, для игр отдаю 4060 - она в системе определяется как
/dev/nvidia1.

    volumes:
      - /etc/wolf/:/etc/wolf:rw

По умолчанию wolf внутри своего контейнера ищет свои конфиги в /etc/wolf. И директорию по такому же пути пытается замонтировать внутрь контейнера с “приложением”. Меняется переменной окружения. Но!!! В начале своего пути по настройке я поменял эту переменную, заменил точки монтирования и всё сломалось. В итоге пришёл к тому что данную настройку лучше не трогать, а данную директорию на хостовой системе сделать ссылкой на директорию в хранилище:

root@pve:/etc# ls -la /etc/wolf
lrwxrwxrwx 1 root root 19 ноя 13  2024 /etc/wolf -> /ssd_raid/apps/wolf

в итоге все настройки (и игры) лежат на хранилище, а не на системном разделе.

Далее необходимо настроить кофигурационный файл самого wolf (/etc/wolf/cfg/config.toml)

При первом запуске wolf его создаст самостоятельно. Поэтому весь файл прикладывать не буду, а покажу пару “приложений”:

Спойлер
[[apps]]
icon_png_path = '/etc/wolf/icons/steam.png'
start_virtual_compositor = true
title = 'Steam'

    [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"]
  },
  "Labels": {
    "com.docker.compose.project": "games",
    "com.docker.compose.service": "games",
    "traefik.enable": "false",
    "com.centurylinklabs.watchtower.enable": "false"
  }
}
'''
    devices = []
    env = [
        'PROTON_LOG=1',
        'RUN_SWAY=1',
        'GOW_REQUIRED_DEVICES=/dev/input/* /dev/dri/* /dev/nvidia*',
        'ENABLE_VKBASALT=1',
        'TZ=Europe/Moscow'
    ]
    image = 'ghcr.io/games-on-whales/steam:edge'
    mounts = [ '/etc/localtime:/etc/localtime:ro', '/etc/timezone:/etc/timezone:ro' ]
    name = 'WolfSteam'
    ports = []
    type = 'docker'

[[apps]]
icon_png_path = '/etc/wolf/icons/mkx.png'
start_virtual_compositor = true
title = 'Mortal Kombat X'

    [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=1',
        'GOW_REQUIRED_DEVICES=/dev/input/* /dev/dri/* /dev/nvidia*',
        'ENABLE_VKBASALT=1',
        'STEAM_STARTUP_FLAGS=steam://rungameid/307780'
    ]
    image = 'ghcr.io/games-on-whales/steam:edge'
    mounts = []
    name = 'WolfSteam'
    ports = []
    type = 'docker'

icon_png_path = '/etc/wolf/icons/steam.png'

Можно положить картинки приложений по определённому пути здесь их указать и тогда в клиентах приложения будут отображаться этими картинками (подробнее в документации)

base_create_json = ...

Параметры запускаемого контейнера, часть параметров (типа mounts или переменных окружения) имеют свои наименования в wolf, а часть нет, поэтому тут можно задать свои labels или подключить контейнер к определённой сети (подробнее в документации докера).

    env = [
...
        'TZ=Europe/Moscow'
    ]
    image = 'ghcr.io/games-on-whales/steam:edge'
    mounts = [ '/etc/localtime:/etc/localtime:ro', '/etc/timezone:/etc/timezone:ro' ]

аналогично контейнеру wolf прокидываем и задаём правильные временную зону и время. Доступные образа можно посмотреть в репозитории. Их не сильно много, но в принципе кроме стима ничего и не надо.

У “приложения” Mortal Kombat X задана переменная окружения

    env = [
...     
        'STEAM_STARTUP_FLAGS=steam://rungameid/307780'
...
    ]

это позволяет стиму автоматически запускать необходимую игру.

Далее необходимо установить Moonlight на необходимое устройство. Запустить его.

Если устройство и сервер находятся в разных сетях, то надо вручную добавить сервер:


Если в одной сети, то сервер сразу отобразится. Далее необходимо их сопрячь. Открываем логи контейнера Wolf. В Moonlight нажимаем на сервер и ищем в логах контейнера URL, который необходимо открыть в браузере
image
Открываем этот URL в браузере и вводим пин-код который сказал Moonlight

После сопряжения откроется список приложений, которые мы настроили в Wolf.

Далее есть нюанс: для каждого клиента Wolf в /etc/wolf/ создаёт директорию профиля, которую монтирует в запускаемый контейнер с приложением. Получится что на разным устройствах будут разные профили, а мы хотим, например, чтобы для телефона и ТВ приставки был один и тот же профиль. Имя директории по умолчанию - идентификатор клиента. Но имя директории - это параметр в конфиге Wolf:

[[paired_clients]]
app_state_folder = '10031370604393607389'
client_cert = '''-----BEGIN CERTIFICATE-----
MIICvzCCAaegAwIBAgIBADANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBhOVklE
SUEgR2FtZVN0cmVhbSBDbGllbnQwHhcNMjUwOTE5MTE0MzE0WhcNNDUwOTE0MTE0

То есть можно либо в конфиге поменять имя директории для необходимых клиентов, либо снова воспользоваться символьными ссылками. Создать “общую” директорию и на неё сделать ссылки:

root@pve:/etc/wolf# mkdir /etc/wolf/test
root@pve:/etc/wolf# ln -s test 10031370604393607389
root@pve:/etc/wolf# ls -al /etc/wolf/
итого 2640
drwxr-xr-x 1 root root     448 сен 19 15:00 .
drwxr-xr-x 1 root root     494 сен  4 13:46 ..
lrwxrwxrwx 1 root root       4 сен 19 15:00 10031370604393607389 -> test
lrwxrwxrwx 1 root root       4 сен 19 15:00 54656325345634575678 -> test
drwxr-xr-x 1 root root    2194 сен 19 14:42 cfg
-rwxr-xr-x 1 root root 2664992 сен 19 14:42 fake-udev
drwxr-xr-x 1 root root      60 ноя 15  2024 icons
drwxr-xr-x 1 root root       2 сен 19 15:01 test

Теперь можно попробовать запустить стим, первый запуск будет идти долго - сначала должен скачаться стимовский образ, потом он запустится, потом стим будет устанавливаться в профиль. И в итоге:


Естественно можно сделать полноэкранный режим.

6 лайков