Если совсем кратко, то:
Если не кратко, то:
В начале было слово… Заядлые геймеры, наверное, помнят, что в бывшей 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, который необходимо открыть в браузере

Открываем этот 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
Теперь можно попробовать запустить стим, первый запуск будет идти долго - сначала должен скачаться стимовский образ, потом он запустится, потом стим будет устанавливаться в профиль. И в итоге:
Естественно можно сделать полноэкранный режим.



