diff --git a/README.md b/README.md index 00373c60b6ba871c350cb2bfa0016822b9e1f7ac..46e328910c484af1c7f73d2fabd67fd0848b6d47 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,6 @@ pressure-vessel is a bit like a simplified version of Flatpak for Steam games. Use cases --------- -### Protecting `$HOME` - -If we change the meaning of `/home` for a game to be a fake -app-specific directory, then the game can't write random files in unknown -subdirectories of $HOME, and we can be reasonably sure that uninstalling -a game removes all of its files. (As a side benefit, this could make it -easier to be sure that all the necessary files have been selected for -Steam Cloud Sync, because we could prevent the game writing anywhere -that won't be synchronised, other than /tmp or similar.) - ### Using an alternative runtime We can get a more predictable library stack than the @@ -23,12 +13,77 @@ We can get a more predictable library stack than the available over `/usr`, `/lib*`, `/bin`, `/sbin`, analogous to Flatpak runtimes, using some or all of the host system's graphics stack. +In particular, this would avoid or mitigate the following problems with +the `LD_LIBRARY_PATH`-based Steam Runtime: + +* The Steam Runtime cannot contain glibc, because the path to + [`ld.so(8)`][ld.so] is hard-coded into all executables (it is the ELF + interpreter, part of the platform ABI), the version of `ld.so` is + coupled to `libdl.so.2`, and the version of `libdl.so.2` is + coupled to the rest of glibc; so everything in the Steam Runtime + must be built for a glibc at least as old as the oldest system that + Steam supports. This makes it difficult to advance beyond + the Ubuntu 12.04-based 'scout' runtime: everything that is updated + effectively has to be backported to Ubuntu 12.04. + +* The Steam Runtime was originally designed to prefer its own version of + each library even if the version in the host system was newer, but + this broke any graphics driver that relied on a newer version of a + host library, notably `libstdc++`. + +* More recent versions of the Steam Runtime prefer the "pinned" host + system version of most libraries that appear to be at least as new as + the Runtime's own version, but this can also break things if + libraries claim to be compatible (by having the same ELF `DT_SONAME`) + but are in fact not compatible, for example: + + - libcurl.so.4 linked to libssl 1.0 is not completely compatible with + libcurl.so.4 linked to libssl 1.1 + + - The history of libcurl.so.4 in Debian/Ubuntu has involved two + incompatible sets of versioned symbols, due to some decisions + made in 2005 and 2007 that, with hindsight, were less wise than + they appeared at the time + + - Various libraries can be compiled with or without particular + features and dependencies; if the version in the Steam Runtime + has a feature, and the host system version is newer but does not + have that feature, then games cannot rely on the feature + + - In the worst-case, compiling a library without a particular + feature or dependency can result in symbols disappearing from + its ABI, resulting in games that reference those symbols crashing + + - There is a fairly subtle interaction between libdbus, + libdbusmenu-gtk, libdbusmenu-glib and Ubuntu's patched GTK 2 + that has resulted in these libraries being forced to be taken + from the Steam Runtime, to avoid breaking the Unity dock + +* Preferring the "pinned" host system libraries makes it very easy for + a game developer to make their game depend on a newer host-system + library without realising that they have done so, resulting in their + game not running correctly on older host systems, or on host systems + that do not have a library with a matching `DT_SONAME`. + A future goal is to use [libcapsule][] to avoid the library dependencies of the host system's graphics stack influencing the libraries loaded by -games, particularly libstdc++. This is not yet implemented. +games at all, and in particular allow game logic to use the Runtime's +libstdc++ while the host system graphics driver uses a different, +newer libstdc++. This is not yet implemented. +[ld.so]: https://linux.die.net/man/8/ld.so [libcapsule]: https://gitlab.collabora.com/vivek/libcapsule/ +### Protecting `$HOME` + +If we change the meaning of `/home` for a game to be a fake +app-specific directory, then the game can't write random files in unknown +subdirectories of $HOME, and we can be reasonably sure that uninstalling +a game removes all of its files. As a side benefit, this could make it +easier to be sure that all the necessary files have been selected for +Steam Cloud Sync, because we could prevent the game writing anywhere +that won't be synchronised, other than /tmp or similar. + Building a relocatable install ------------------------------ @@ -104,79 +159,121 @@ Instructions for testing - Unturned (304930) - UnityPlayer 5.5.3f1 -* Unpack `pressure-vessel-`*VERSION*`-bin.tar.gz` (or `...-bin+src.tar.gz`) - in some convenient place (I used `/opt/pressure-vessel`, but a - production deployment in Steam would more realistically use somewhere - under `~/.steam` or `~/.local/share/Steam`): +* Install bubblewrap or Flatpak (in Debian, Ubuntu or SteamOS: + `sudo apt-get install bubblewrap`). pressure-vessel bundles its own + fallback copy of bubblewrap, but that one will only work on kernels + that are configured to allow unprivileged user namespaces, such as + recent Ubuntu and Fedora (but not, for example, Debian, Arch Linux or + Red Hat Enterprise Linux unless specially configured). - - be a user who can sudo +* Install Python 3, PyGI and GTK 3 introspection data + (in Debian, Ubuntu or SteamOS: + `sudo apt-get install python3-gi gir1.2-gtk-3.0`). + This is not necessary for production use but is very convenient when + testing interactively. - $ cd /opt - $ sudo mkdir pressure-vessel - $ sudo chown $(id -nu) pressure-vessel - $ tar --strip-components=1 -C pressure-vessel -xzvf ~/pressure-vessel-*-bin.tar.gz - $ chmod -R a+rX pressure-vessel +* Unpack `pressure-vessel-`*VERSION*`-bin.tar.gz` (or `...-bin+src.tar.gz`) + in some convenient place, for example + `~/.steam/root/steamapps/common/Steam Linux Runtime/pressure-vessel`: + + $ mkdir -p ~/.steam/root/steamapps/common/"Steam Linux Runtime"/pressure-vessel + $ tar \ + --strip-components=1 \ + -C ~/.steam/root/steamapps/common/"Steam Linux Runtime"/pressure-vessel \ + -xzvf ~/pressure-vessel-*-bin.tar.gz * Launch a game once without pressure-vessel -* Configure the launch options for the chosen game: +* Configure the launch options for the chosen game. Either use the Steam + (non-Big-Picture) UI, for example by logging in as the `desktop` + user on SteamOS, or: - be the Steam user, possibly via sudo -u steam -s - $ nano ~/.steam/steam/userdata/[0-9]*/config/localconfig.vdf + - `$ nano ~/.steam/root/userdata/[0-9]*/config/localconfig.vdf` - navigate to UserLocalConfigStore/Software/Valve/Steam/Apps/*game ID* + - below LastPlayed, add: - "LaunchOptions" "/opt/pressure-vessel/bin/pressure-vessel-unruntime -- %command%" + "LaunchOptions" "~/.steam/root/steamapps/common/'Steam Linux Runtime'/pressure-vessel/bin/pressure-vessel-unruntime-test-ui -- %command%" + + - optionally put some Flatpak-style runtimes alongside pressure-vessel, + for example `~/.steam/root/steamapps/common/Steam Linux Runtime/scout` - restart Steam (on SteamOS use `sudo systemctl restart lightdm`) - TODO: Is there a scriptable way to make Steam edit `localconfig.vdf` itself, or to make it reload an edited `localconfig.vdf`? -* Run the game. If successful, `pstree -pa` will look something like this: +* Run the game. If successful, you will get a GUI launcher that lists possible + runtimes. Choose one, uncheck "Run in an xterm" for now, and click Run. + You should get a game. `pstree -pa` will look something like this: - |-SteamChildMonit,13109 -tenfoot -steamos -enableremotecontrol + |-SteamChildMonit,13109 | `-sh,13110 -c... - | `-pressure-vessel,13111 /opt/pressure-vessel/bin/pressure-vessel-wrap... + | `-pressure-vessel,13111 --runtime /home/desktop/.local... | `-bwrap,13132 --new-session --setenv LD_LIBRARY_PATH... | `-Floating Point.,13133 -* To use a runtime instead of the host system, use: +* The test UI automatically looks for runtimes in the following locations: - /opt/pressure-vessel/bin/pressure-vessel-unruntime --runtime=$HOME/some-runtime -- %command% + - Next to pressure-vessel's `bin` and `lib` directories + - The directory above pressure-vessel's `bin` and `lib` directories + - `~/.steam/root/steamapps/common/Steam Linux Runtime` - The runtime can be either: + Runtimes intended for the test UI must have a `files` subdirectory + and a `metadata` file, like Flatpak runtimes. - - A merged `/usr` containing `bin/bash`, `lib/ld-linux.so.2`, - `bin/env`, `share/locale`, `lib/python2.7` and so on + You can also choose to use the host system `/usr` directly. + +* For a more production-ready version without the test UI, set the launch + options to: + + "LaunchOptions" "~/.steam/root/steamapps/common/'Steam Linux Runtime'/pressure-vessel/bin/pressure-vessel-unruntime -- %command%" + + and then add more options just before the `--` as desired. + This mode does not require Python 3, PyGI, GTK or a normal + window manager, and is probably more suitable for Big Picture mode. + +* When not using the test UI, the default runtime is the host system, + with the `LD_LIBRARY_PATH` Steam Runtime overlaid onto it. + You can specify a runtime with the `--runtime` option. It can be + any of these: + + - The `files` subdirectory of a Flatpak-style runtime such as + `~/.steam/root/steamapps/common/Steam Linux Runtime/scout` or + `~/.local/share/flatpak/runtime/com.valvesoftware.SteamRuntime.Platform/x86_64/scout/active`, + for example produced by [flatdeb][] (this is a special case of a + merged `/usr`). For example, you could use: - - A Flatpak runtime such as - `~/.local/share/flatpak/runtime/com.valvesoftware.SteamRuntime.Platform/x86_64/scout_beta/active/files`, - for example produced by [flatdeb][] (this is a special case of a - merged `/usr`). For example, as the user that will run Steam: + /opt/pressure-vessel/bin/pressure-vessel-unruntime --runtime=$HOME/.steam/root/steamapps/common/'Steam Linux Runtime'/scout -- %command% - $ flatpak --user remote-add --no-gpg-verify smcv-flatdeb https://people.collabora.com/~smcv/flatdeb/repo - $ flatpak --user install smcv-flatdeb com.valvesoftware.SteamRuntime.Platform/x86_64/scout_beta - $ ln -fns ~/.local/share/flatpak/runtime/com.valvesoftware.SteamRuntime.Platform/x86_64/scout_beta/active/files \ - ~/scout_beta-runtime + or: - and then set the launch options to: + /opt/pressure-vessel/bin/pressure-vessel-unruntime --runtime=$HOME/.local/share/flatpak/runtime/com.valvesoftware.SteamRuntime.Platform/x86_64/scout/active -- %command% - /opt/pressure-vessel/bin/pressure-vessel-unruntime --runtime=$HOME/scout_beta-runtime -- %command% + - A merged `/usr` containing `bin/bash`, `lib/ld-linux.so.2`, + `bin/env`, `share/locale`, `lib/python2.7` and so on - A sysroot containing `bin/bash`, `lib/ld-linux.so.2`, - `usr/bin/env`, `usr/share/locale`, `usr/lib/python2.7` and so on, - optionally with `bin`, `lib` etc. being symlinks into `usr` + `usr/bin/env`, `usr/share/locale`, `usr/lib/python2.7` and so on, + optionally with `bin`, `lib` etc. being symlinks into `usr`. + For example, if you have a chroot in + `/var/chroots/steamrt_scout_amd64` generated by [`setup_chroot.sh`][], + you could use: + + /opt/pressure-vessel/bin/pressure-vessel-unruntime --runtime=/var/chroots/steamrt_scout_amd64 -- %command% + +When using a runtime, graphics drivers come from the host system, while +their dependencies (notably glibc and libstdc++) come from either +the host system or the runtime, whichever appears to be newer. This +is currently hard-coded in `pressure-vessel-wrap` and cannot be +configured. - In this mode, graphics drivers come from the host system, while - their dependencies (notably glibc and libstdc++) come from either - the host system or the runtime, whichever appears to be newer. This - is currently hard-coded in `pressure-vessel-wrap` and cannot be - configured. +### Debugging - To see the filesystem in which the game is executing, get the process ID +* To see the filesystem in which the game is executing, get the process ID of some game process from `ps` and use: $ sudo ls -l /proc/$game_pid/root/ @@ -185,12 +282,6 @@ Instructions for testing are available in `/overrides` inside that filesystem, while selected files from the host are visible in `/run/host`. -* To protect `$HOME`, add one of the following options before `--`: - - - `--fake-home=/some/path` (automatically unshares the home directory) - - `--freedesktop-app-id=com.example.Anything --unshare-home` - - `--steam-app-id=70 --unshare-home` (when running from Steam you can use `--steam-app-id=${SteamAppId}`) - * To test something manually: - cd to the directory that you want to be the current working directory @@ -198,32 +289,63 @@ Instructions for testing - Run: + /opt/pressure-vessel/bin/pressure-vessel-test-ui -- ./whatever-game + + or: + /opt/pressure-vessel/bin/pressure-vessel-wrap -- ./whatever-game - Optionally add more options before the `--`. + Optionally add more options before the `--`, such as `--runtime`, + `--xterm` and `--interactive`. -* For interactive testing, if your runtime (if used) or host system (if no - runtime) contains an xterm binary, you can use something like: +* For interactive testing from Steam, if your runtime (if used) or host + system (if no runtime) contains an `xterm` binary, you can use + something like: - /opt/pressure-vessel/bin/pressure-vessel-wrap --xterm -- %command% + /opt/pressure-vessel/bin/pressure-vessel-unruntime --xterm -- %command% to run an xterm containing an interactive shell. The interactive - shell's current working directory matches the game's. Run `"$@"` in - the interactive shell to run the game. + shell's current working directory matches the game's, and you can + explore the container from there. + + When ready, run `"$@"` in the interactive shell (with the double + quotes included!) to run the game. + + Checking "Run in an xterm" in the GUI launcher is the + same as `--xterm` on the command-line. * For interactive testing, if you ran Steam from a shell (not normally valid on SteamOS!), you can use: - /opt/pressure-vessel/bin/pressure-vessel-wrap --interactive -- %command% + /opt/pressure-vessel/bin/pressure-vessel-unruntime --interactive -- %command% + + This is a simpler version of `--xterm`, which uses the terminal from + which you ran Steam instead of an xterm inside the container. The interactive shell's current working directory matches the game's. - Run `"$@"` in the interactive shell to run the game. + As with `--xterm`, run `"$@"` in the interactive shell to run the game. + +### More options -Use `pressure-vessel-unruntime` if you are in a Steam Runtime environment -(the Steam Runtime's `run.sh` or a Steam game), and `pressure-vessel-wrap` +Use `pressure-vessel-unruntime` or `pressure-vessel-unruntime-test-ui` +if you are in a Steam Runtime environment (the Steam Runtime's `run.sh` +or a Steam game), and `pressure-vessel-wrap` or `pressure-vessel-test-ui` if you are not ("Add non-Steam game" in Steam, or a non-Steam-related interactive shell). +* To protect `$HOME`, add one of the following options before `--`: + + - `--fake-home=/some/path` (automatically unshares the home directory) + - `--freedesktop-app-id=com.example.Anything --unshare-home` + - `--steam-app-id=70 --unshare-home` (when running from Steam you can use + `--steam-app-id=${SteamAppId}`, which is the default) + + Unchecking "Share real home directory" in the GUI launcher is the + same as `--unshare-home` on the command-line. It is an error to do + this without telling `pressure-vessel-wrap` what to use for the home + directory, either via `--fake-home`, `--freedesktop-app-id`, + `--steam-app-id`, or having the `SteamAppId` environment variable. + Steam integration ----------------- @@ -306,6 +428,31 @@ TODO Design ------ +### Setting up the container + +With a runtime, the container is basically the same as a Flatpak +sandbox. The root directory is a tmpfs. The runtime is bind-mounted on +`/usr`, with symbolic links at `/bin`, `/sbin` and `/lib*` so that +commonly-hard-coded paths like `/bin/sh`, `/sbin/ldconfig` and +`/lib/ld-linux.so.2` work as expected. + +With a sysroot-style runtime, or when using the host system as the +runtime, the setup is the same, except that `/bin`, `/sbin` and `/lib*` +might be real directories bind-mounted from the runtime or host if it +has not undergone the [/usr merge][]. + +Either way, selected paths from the host system are exposed in `/run/host`, +the same as for `flatpak run --filesystem=host` in Flatpak. + +[/usr merge]: https://fedoraproject.org/wiki/Features/UsrMove + +### Interactive debugging + +`--interactive` and `--xterm` work by wrapping an increasingly long +"adverb" command around the command to be run. + +### Unsharing the home directory + Each game gets a private home directory in `~/.var/app`, the same as Flatpak apps do. The name of the private home directory follows the [naming recommendations from the freedesktop.org Desktop Entry