Quickstart

Wolf runs as a single container, it’ll spin up and down additional containers on-demand.

Docker

Docker CLI:

docker run \
    --name wolf \
    --network=host \
    -e XDG_RUNTIME_DIR=/tmp/sockets \
    -v /tmp/sockets:/tmp/sockets:rw \
    -e HOST_APPS_STATE_FOLDER=/etc/wolf \
    -v /etc/wolf:/etc/wolf:rw \
    -v /var/run/docker.sock:/var/run/docker.sock:rw \
    --device /dev/dri/ \
    --device /dev/uinput \
    --device /dev/uhid \
    -v /dev/:/dev/:rw \
    -v /run/udev:/run/udev:rw \
    --device-cgroup-rule "c 13:* rmw" \
    ghcr.io/games-on-whales/wolf:stable
bash

Docker compose:

version: "3"
services:
  wolf:
    image: ghcr.io/games-on-whales/wolf:stable
    environment:
      - XDG_RUNTIME_DIR=/tmp/sockets
      - HOST_APPS_STATE_FOLDER=/etc/wolf
    volumes:
      - /etc/wolf/:/etc/wolf
      - /tmp/sockets:/tmp/sockets:rw
      - /var/run/docker.sock:/var/run/docker.sock:rw
      - /dev/:/dev/:rw
      - /run/udev:/run/udev:rw
    device_cgroup_rules:
      - 'c 13:* rmw'
    devices:
      - /dev/dri
      - /dev/uinput
      - /dev/uhid
    network_mode: host
    restart: unless-stopped
yaml
Which ports are used by Wolf?

To keep things simple the scripts above defaulted to network:host; that’s not really required, the minimum set of ports that needs to be exposed are:

# HTTPS
EXPOSE 47984/tcp
# HTTP
EXPOSE 47989/tcp
# Control
EXPOSE 47999/udp
# RTSP
EXPOSE 48010/tcp
# Video
EXPOSE 48100/udp
# Audio
EXPOSE 48200/udp
dockerfile

These are the default values, if you want to assign custom ports you can set the env variables:

  • WOLF_HTTP_PORT

  • WOLF_HTTPS_PORT

  • WOLF_CONTROL_PORT

  • WOLF_RTSP_SETUP_PORT

  • WOLF_VIDEO_PING_PORT

  • WOLF_AUDIO_PING_PORT

Moonlight pairing

You should now be able to point Moonlight to the IP address of the server and start the pairing process:

  • In Moonlight, you’ll get a prompt for a PIN A screenshot of Moonlight asking for a PIN

  • Wolf will log a line with a link to a page where you can input that PIN (ex: http://localhost:47989/pin/#337327E8A6FC0C66 make sure to replace localhost with your server IP) A screenshot of the Wolf page where you can insert the PIN

  • In Moonlight, you should now be able to see a list of the applications that are supported by Wolf A screenshot of Moonlight showing the apps

If you can only see a black screen with a cursor in Moonlight it’s because the first time that you start an app Wolf will download the corresponding docker image + first time updates.
Keep an eye on the logs from Wolf to get more details.

Virtual devices support

We use uinput to create virtual devices (Mouse, Keyboard and Joypad), make sure that /dev/uinput is present in the host:

ls -la /dev/uinput
crw------- 1 root root 10, 223 Jan 17 09:08 /dev/uinput
bash
Add your user to group input
sudo usermod -a -G input $USER
bash
Create udev rules under /etc/udev/rules.d/85-wolf-virtual-inputs.rules
# Allows Wolf to acces /dev/uinput
KERNEL=="uinput", SUBSYSTEM=="misc", MODE="0660", GROUP="input", OPTIONS+="static_node=uinput"

# Allows Wolf to access /dev/uhid
KERNEL=="uhid", TAG+="uaccess"

# Move virtual keyboard and mouse into a different seat
SUBSYSTEMS=="input", ATTRS{id/vendor}=="ab00", MODE="0660", GROUP="input", ENV{ID_SEAT}="seat9"

# Joypads
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"
bash
What does that mean?
KERNEL=="uinput", SUBSYSTEM=="misc", MODE="0660", GROUP="input", OPTIONS+="static_node=uinput"

Allows Wolf to access /dev/uinput on your system. It needs that node to create the virtual devices. This is usually not the default on servers, but if that is already working for you on your desktop system, you can skip this line.

SUBSYSTEMS=="input", ATTRS{id/vendor}=="ab00", MODE="0660", GROUP="input", ENV{ID_SEAT}="seat9"

This line checks for the custom vendor-id that Wolf gives to newly created virtual devices and assigns them to seat9, which will cause any session with a lower seat (usually you only have seat1 for your main session) to ignore the devices.

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"

Now the virtual controllers are different, because we need to emulate an existing brand for them to be picked up correctly, so our virtual controllers have a vendor/product id resembling a real controller. The assigned name instead is specific to Wolf.

You can’t assign controllers a seat however (well - you can - but it won’t have the same effect), so we just give it permissions where only user+group can pick it up.

Reload the udev rules either by rebooting or run:

udevadm control --reload-rules && udevadm trigger
bash