Lutris

The gameonwhales/lutris (or ghcr.io/games-on-whales/lutris) image can be used to run a variety of applications.
Lutris refers to its self as a "preservation platform", and provides scripts for installing many pieces of software on Linux, from games to productivity software.

The software you want to run may already have an entry on the Lutris website, in which case, you can either install the software from the Lutris UI directly or extend the image to create a bespoke image.

Internal Layout

On first run, the startup scripts for this image will configure Lutris to store game installation files and installation meta-data in the /var/lutris directory. By mounting a volume at this directory, multiple instances of the image will share videogame installations. Most applications will save user-specific data in the home directory, which remains exclusively visible to the individual user.

Lutris is a desktop application that launches other windows, and as such, it requires a window manager to run; we recommend enabling Sway (set the env variable USE_SWAY=1).

Folder Format

The following documents the file-format for images in GOW in general, as well as a couple of peculiar specific to the Lutris Image.

The Lutris image consists of the following files;

images
 +- lutris
     +- Dockerfile
     +- scripts
     |   +- startup.sh
     |   +- startup-10-create-dirs.sh
     +- configs
         +- lutris-system.yml
         +- lutris-lutris.yml

At a basic level, all images based on the base image will feature a Dockerfile and startup.sh script.
The Dockerfile will stage the startup.sh script at /opt/gow/startup.sh, overwriting the version provided by the base-app script. The Dockerfile will also copy all the assets for the container (usually to /opt/gow/), and install any additional dependencies.

The startup.sh script will launch at the start of every container.
It must first prepare the home directory, which will be empty on first-run, and then launch the application. The Lutris image also features a /opt/gow/startup.d directory. Scripts placed in this directory will be sourced by the startup.sh script prior to launching Lutris.

startup-10-create-dirs.sh is copied by the Dockerfile to /opt/gow/startup.d. It configures Lutris to install games in /var/lutris/Games by default, and allow multiple instances of the Lutris container to share installation files.

Building the Lutris Image

These instructions are applicable to any image based on the GoW base image, and are useful if you want to make changes to the image. When making changes to images upon which other images are based, it is naturally required to build all dependent images to propergate the changes.

Step 1. Build the base image

docker build -t gow/base images/base

Step 2. Build the base-app image

docker build -t gow/base-app --build-arg BASE_IMAGE=gow/base images/base-app

Step 3. Build the lutris image

docker build -t gow/lutris --build-arg BASE_APP_IMAGE=gow/base-app images/lutris

Step 4. Configure Wolf to use the container

Place the following in /etc/wolf/cfg/config.toml

[[apps]]
title = "Lutris"
start_virtual_compositor = true

[apps.runner]
type = "docker"
name = "WolfLutris"
image = "gow/lutris"
mounts = ["lutris-games:/var/lutris/:rw"]
env = ["RUN_SWAY=1","GOW_REQUIRED_DEVICES=/dev/input/event* /dev/dri/* /dev/nvidia*"]
devices = []
ports = []
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"]
  }
}
\
"""
This configuration creates a docker named-volume called lutris-games and mounts it at /var/lutris. Since this is where the games will be installed, this may need a lot of storage space. By default, docker will store volumes in /var/lib/docker/volumes. In the above example, you can change lutris-games to any arbitrary path on the host you like, and the game data will be saved there instead.

Extending the Lutris Image

Lutris supports launching games directly, skipping its UI, by passing the correct command line arguments to the lutris binary. Lutris uses this feature to add launchers to menus and desktop shortcuts. We can use it to create a custom container image which uses Lutris' installation scripts, but features a single program.

Consider the following structure:

images
 +- lutris-app
     +- Dockerfile
     +- scripts
         +- startup-20-configure-lutris.sh

The Dockerfile would copy startup-20-configure-lutris.sh to /opt/gow/startup.d/20-configure-lutris.sh, which in turn would be picked up by the Lutris image’s startup.sh script, and would adjust Lutris commandline args to run the application.

A Practical Example

Super Tux is a Linux native game which is distributed via AppImage. It also happens to have a Lutris install script. To create a Super Tux image based on the Lutris image, replicate the above structure with the following file contents;

Dockerfile

ARG BASE_APP_IMAGE

FROM ${BASE_APP_IMAGE}

COPY --chmod=777 scripts/startup-20-launch-supertux.sh /opt/gow/startup.d/20-launch-supertux.sh

ARG IMAGE_SOURCE
LABEL org.opencontainers.image.source $IMAGE_SOURCE

scripts/startup-20-launch-supertux.sh

#!/bin/bash -e

source /opt/gow/bash-lib/utils.sh

gow_log "[start-launch-supertux] Begin"

if $LUTRIS -lo 2>/dev/null | grep "supertux"
then
    gow_log "[start-launch-supertux] Super Tux is already installed! Launching."
    LUTRIS_ARGS=("lutris:rungame/supertux")
else
    gow_log "[start-launch-supertux] Super Tux is not installed! Installing."
    LUTRIS_ARGS=("lutris:supertux")
fi

gow_log "[start-launch-supertux] End"

Build the image

Build the image based on the Lutris image with the following command;

docker build -t lutris-supertux --build-arg BASE_APP_IMAGE=gow/lutris images/lutris-supertux

config.toml

Finally, add the appropreate entry to /etc/wolf/cfg/config.toml to add it to wolf.

[[apps]]
title = "Super Tux"
start_virtual_compositor = true

[apps.runner]
type = "docker"
name = "WolfSupertux"
image = "lutris-supertux"
mounts = ["lutris-games:/var/lutris/:rw"]
env = ["APPIMAGE_EXTRACT_AND_RUN=1","RUN_SWAY=1","GOW_REQUIRED_DEVICES=/dev/input/event* /dev/dri/* /dev/nvidia*"]
devices = []
ports = []
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"]
  }
}
\
"""

This will work. But when you run the image in wolf, you will find the game open by default in windowed mode. Also, because Super Tux runs from an Appimage in docker, it requires the APPIMAGE_EXTRACT_AND_RUN environment variable to be set.

Use a custom install script.

These things can be configured in Lutris, and we can achieve the changes we desire by providing a customised version of a Lutris installation script.

All we need to do is add the customised script to the scripts directory, have the Dockerfile copy it into the image, and change the startup script to install from the provided script.

Structure

images
 +- lutris-app
     +- Dockerfile
     +- scripts
         +- startup-20-configure-lutris.sh
         +- supertux-appimage.yaml

supertux-appimage.yaml

This customised installation script sets APPIMAGE_EXTRACT_AND_RUN as an environment variable, and passes --fullscreen as a commandline argument.

description: ''
game_slug: supertux
gogslug: ''
humblestoreid: ''
installer_slug: supertux-appimage
name: SuperTux
notes: 'Arch-based systems might need to install the following dependencies: "physfs
  glew1.10 libcurl-gnutls"'
runner: linux
script:
  files:
  - appimg: https://github.com/SuperTux/supertux/releases/download/v0.6.3/SuperTux-v0.6.3.glibc2.29-x86_64.AppImage
  game:
    exe: SuperTux-v0.6.3.glibc2.29-x86_64.AppImage
    args: --fullscreen
  installer:
  - chmodx: appimg
  - move:
      dst: $GAMEDIR
      src: appimg
  system:
    env:
      APPIMAGELAUNCHER_DISABLE: true
      APPIMAGE_EXTRACT_AND_RUN: true
slug: supertux-appimage
steamid: null
version: AppImage
year: 2003

startup-20-configure-lutris.sh

The startup script is mostly the same as the previous version, except the installation command now points to the custom install script.

#!/bin/bash -e

source /opt/gow/bash-lib/utils.sh

gow_log "[start-launch-supertux] Begin"

if $LUTRIS -lo 2>/dev/null | grep "supertux"
then
    gow_log "[start-launch-supertux] Super Tux is already installed! Launching."
    LUTRIS_ARGS=("lutris:rungame/supertux")
else
    gow_log "[start-launch-supertux] Super Tux is not installed! Installing."
    LUTRIS_ARGS=("-i" "/opt/gow/supertux-appimage.yaml")
fi

gow_log "[start-launch-supertux] End"

Dockerfile

The Dockerfile needs to be modified to copy the installation script to the right place.

ARG BASE_APP_IMAGE

# hadolint ignore=DL3006
FROM ${BASE_APP_IMAGE}

COPY --chmod=777 scripts/startup-20-launch-supertux.sh /opt/gow/startup.d/20-launch-supertux.sh
COPY scripts/supertux-appimage.yaml /opt/gow/supertux-appimage.yaml

ARG IMAGE_SOURCE
LABEL org.opencontainers.image.source $IMAGE_SOURCE

Build the image

The build command is exactly the same as it was in the previous example.

docker build -t lutris-supertux --build-arg BASE_APP_IMAGE=gow/lutris images/lutris-supertux

config.toml

Finally, the appropriate entry in /etc/wolf/cfg/config.toml can be changed to remove the now superfluous environment variable.

[[apps]]
title = "Super Tux"
start_virtual_compositor = true

[apps.runner]
type = "docker"
name = "WolfSupertux"
image = "lutris-supertux"
mounts = ["lutris-games:/var/lutris/:rw"]
env = ["RUN_SWAY=1","GOW_REQUIRED_DEVICES=/dev/input/event* /dev/dri/* /dev/nvidia*"]
devices = []
ports = []
base_create_json = """
{
  "HostConfig": {
    "IpcMode": "host",
    "CapAdd": ["NET_RAW", "MKNOD", "NET_ADMIN", "SYS_ADMIN", "SYS_NICE"],
    "Privileged": false,
    "DeviceCgroupRules": ["c 13:* rmw"]
  }
}
\
"""

config.toml

Because the installation script is now correctly setting the environment, we no longer have to set APPIMAGE_EXTRACT_AND_RUN in config.toml.

[[apps]]
title = "Super Tux"
start_virtual_compositor = true

[apps.runner]
type = "docker"
name = "WolfSupertux"
image = "lutris-supertux"
mounts = ["lutris-games:/var/lutris/:rw"]
env = ["RUN_SWAY=1","GOW_REQUIRED_DEVICES=/dev/input/event* /dev/dri/* /dev/nvidia*"]
devices = []
ports = []
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"]
  }
}
\
"""

Now, when you select the "Super Tux" entry in Moonlight, Super Tux will install from the yaml script we’ve defined, and the game will run in fullscreen mode by default.