I enjoy gaming and occasionally there’s an application I want to run that is unavailable on Linux. Up until now my primary way of dealing with this was to use multiple wine bottles, one for each game. While this provides a little bit of segmentation for applications, it is not a sandbox.
Something else I dislike is how wine likes to spit out files all over my filesystem. There are ways to stop wine from doing this, such as disabling wine’s winemenubuilder
by setting an environment variable with WINEDLLOVERRIDES="winemenubuilder.exe=d"
.
However I also don’t want my wine bottle having access to my entire system.
I figured this would be a good opportunity to test out flatpak, and figure out how to package and build a wine application with it.
The nice thing about using flatpak is that everything will be isolated in the sandbox. For complex or proprietary applications, flatpak might make packaging possible, when it wasn’t before, or at least was very difficult.
I took a crack at building a flatpak for Path of Exile - which is only available for Windows - that can be installed from my repository. So far everything is working successfully, and it has made installing on multiple Linux systems as simple as installing a regular Linux package.
UPDATE: Since I created this flatpak, the winepak project has appeared. While the information in this post s still useful if you are looking to build a flatpak, if you are looking to install the flatpak, installing Path of Exile from there probably a better option since it supports 64-bit, and Is updated more frequently.
If you’re mostly interested in trying out the flatpak, instructions for installing it are available at the bottom of the post.
Creating the Build Link to heading
The first step to creating a Flatpack is initializing a ‘build’. A gpg key can be used to sign the resulting package. Alternatively the flag --no-gpg-verify
can be given to not require this.
Since Path of Exile is a 32-bit application I will be using the i386
freedesktop platform and sdk.
flatpak build-init pathofexile ca.johnramsden.pathofexile org.freedesktop.Platform/i386/1.6 org.freedesktop.Sdk/i386/1.6
A local repository should also be set up.
flatpak --user remote-add --if-not-exists johnramsden applications
This creates the johnramsden
repository in an applications
directory.
flatpak-builder Link to heading
Creating an flatpak is easy with flatpak-builder
. Instead of doing the individual steps manually to create a build, flatpak-builder
can be used to save the configuration in a json
file and build the Flatpak.
Create a json file in reverse DNS format. Mine will be ca.johnramsden.pathofexile.json
. The entire file can be found on GitHub.
General Options Link to heading
To start, add a few self-explanitory values.
{
"app-id": "ca.johnramsden.pathofexile",
"runtime": "org.freedesktop.Platform/i386/1.6",
"sdk": "org.freedesktop.Sdk/i386/1.6",
"tags": ["proprietary"],
"command": "pathofexile.sh",
...
The command
will be a script that will be run at start.
Next set some global build options.
"build-options": {
"env": {
"WINEPREFIX": "/var/data/.local/share/pathofexile",
"WINEDLLOVERRIDES": "mscoree=d;mshtml=d",
"V": "0"
}
}
Inside the Flatpak, data that is persistent can be saved in /var/data
, the actual location of the data will be ${HOME}/.var/app/ca.johnramsden.pathofexile/
, but inside the Flatpak it can be referenced with /var/data
.
finish-args Link to heading
Next specify some finish-args
. These are very important and will specify the final permissions the application has on a system.
"finish-args": [
"--persist=.local/share/pathofexile",
"--socket=x11",
"--share=network",
"--share=ipc",
"--device=dri",
"--allow=multiarch",
"--socket=pulseaudio",
"--filesystem=/var/log:ro",
"--env=WINEDEBUG=-all",
"--env=WINE_RESOLUTION=1920x1080"
]
Several of them are fairly self-explanatory, but I will explain each of them.
--persist=.local/share/pathofexile
- Bindmount the homedir-relative path
.local/share/pathofexile
to${HOME}/.local/share/pathofexile
during runtime. - The actual directory lives at the corresponding path in the per-application directory, i.e.
${HOME}/.var/app/ca.johnramsden.pathofexile/.local/share/pathofexile
. This allows a persistent directory to be set for data that will be needed at runtime. - Since we set our
WINEPREFIX
to/var/data/.local/share/pathofexile
, this will be the location of our wine bottle.
- Bindmount the homedir-relative path
--socket=x11
- Allow graphical access to the xorg server.
--share=network
- Allow network access.
--share=ipc
- Allow inter-process communication.
--device=dri
- Allow access to graphics direct rendering.
--allow=multiarch
- Allow 64 and 32-bit applications to be built.
--socket=pulseaudio
- Use pulseaudio.
--filesystem=/var/log:ro
- Allow read-only access to
/var/log
. This is used to check the amount of video card memory in the startup script.
- Allow read-only access to
--env=WINEDEBUG=-all
- Do not show wine debugging output, this increases performance.
--env=WINE_RESOLUTION=1920x1080
- Allow users to set the resolution at runtime using an environment variable. Defaults to 1920x1080.
--env=VIDEO_MEMORY=
- Allow users to set the video memory at runtime using an environment variable. If unset, it tries to find it.
modules Link to heading
The Next Step will be to add modules needed to run the application.
I use the following modules in my application:
- wine
- winetricks
- cabextract
- wine-gecko
- wine-mono
- wget
- pathofexile-installer
- icons
- desktop
- metadata
Each of them downloads and if necessary compiles the application, installing it into the Flatpak. Special integration exists for autotools, but simple commands such as make install
can also be used.
The last four are just installing a script, icons, a desktop file, and AppStream metadata. The only one key to the functioning of the flatpak is the pathofexile-installer module.
The following is an example of the module I use to compile and install wine.
"name": "wine",
"buildsystem": "autotools",
"build-options": {
"make-args": [ "--silent" ]
},
"sources": [
{
"type": "archive",
"url": "https://dl.winehq.org/wine/source/3.x/wine-3.4.tar.xz",
"sha256": "a483247ac93f325d623a463438590b7355ec26c670af15d356b0ce6c46398e93"
},
{
"type": "archive",
"url": "https://github.com/wine-staging/wine-staging/archive/v3.4.tar.gz",
"sha256": "c56f5d2706e228fe4f720e9301af30e82b4a7b6c1749d0b6170e60f1cb16fe34"
},
{
"type": "shell",
"commands": [ "./patches/patchinstall.sh DESTDIR=$(pwd) --all" ]
}
]
Tthe “pathofexile-installer” module contains the script required to set up the wine bottle, download Path of Exile, and install it into the wine bottle.
#!/bin/bash
export WINEPREFIX="${HOME}/.local/share/pathofexile"
export WINEDEBUG=-all
POE_INSTALLER_NAME="pathofexile_setup.exe"
POE_SETUP="${WINEPREFIX}/${POE_INSTALLER_NAME}"
POE_DOWNLOAD_URL='https://www.pathofexile.com/downloads/PathOfExileInstaller.exe'
POE_RUN_CMD="${WINEPREFIX}/drive_c/Program Files/Grinding Gear Games/Path of Exile/PathOfExile.exe"
WINE_RESOLUTION="${WINE_RESOLUTION:-1920x1080}"
WINE="/app/bin/wine"
XORG_LOG="/var/log/Xorg.0.log"
VERSION_NUM="0.1.3"
VERSION_FILE="${WINEPREFIX}/ca.johnramsden.pathofexile.version"
declare -ra WINE_PACKAGES=(directx9 usp10 msls31 corefonts tahoma win7)
declare -ra WINE_SETTINGS=('csmt=on' 'glsl=disabled')
echo "#############################################"
echo "## Path of Exile Unofficial Flatpak v${VERSION_NUM} ##"
echo "#############################################"
echo
# Set video memory by checking xorg
set_video_memory(){
if [[ ! -z "${VIDEO_MEMORY}" ]]; then
echo "Using explicitly set VMEM of ${VIDEO_MEMORY}"
elif [ -f "${XORG_LOG}" ]; then
# Get Video Memory from Xorg logs
local xorg_vmem
xorg_vmem="$(sed -rn 's/.*memory: ([0-9]*).*kbytes/\1/gpI' ${XORG_LOG})"
if [[ ! -z "${xorg_vmem}" ]]; then
VIDEO_MEMORY=$((${xorg_vmem} / 1024))
echo "Setting video memory to ${VIDEO_MEMORY}"
else
echo "Unable to find video memory in ${XORG_LOG}."
echo "Leaving video card memory at default settings."
echo "To set value explicitly, set the VIDEO_MEMORY environment variable"
return 1
fi
else
echo "Unable to read Xorg logs from ${XORG_LOG}."
echo "Leaving video card memory at default settings."
return 1
fi
tmpfile=$(mktemp VideoMemory.XXXXX.reg)
cat <<EOF > "${tmpfile}"
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\\Software\\Wine\\Direct3D]
"VideoMemorySize"="${VIDEO_MEMORY}"
EOF
"${WINE}" regedit "${tmpfile}" >/dev/null 2>&1
rm "${tmpfile}"
return 0
}
set_wine_settings(){
local my_documents="${WINEPREFIX}/drive_c/users/${USER}/My Documents"
echo "Installing wine requirements."
winetricks --unattended "${WINE_PACKAGES[@]}"
echo "Setting wine settings."
winetricks --unattended "${WINE_SETTINGS[@]}"
# Symlink points to wrong location, fix it
if [[ "$(readlink "${my_documents}")" != "${XDG_DOCUMENTS_DIR}" ]]; then
echo "Setting game directory to ${XDG_DOCUMENTS_DIR}"
mv "${my_documents}" "${my_documents}.bak.$(date +%F)"
ln -s "${XDG_DOCUMENTS_DIR}" "${my_documents}"
fi
echo
}
# Run only if POE isn't installed
first_run(){
set_wine_settings
echo "${VERSION_NUM}" > "${VERSION_FILE}"
if [ ! -f "${POE_SETUP}" ]; then
echo "Downloading Path of Exile installer."
wget --output-document="${POE_SETUP}" "${POE_DOWNLOAD_URL}"
fi
echo "Running Path of Exile installer."
"${WINE}" "${POE_SETUP}"
}
is_updated(){
if [ -f "${VERSION_FILE}" ]; then
last_version="$(cat ${VERSION_FILE})"
else
last_version="0"
fi
echo "${VERSION_NUM}" > "${VERSION_FILE}"
if [[ "${VERSION_NUM}" == "${last_version}" ]]; then
return 0
else
return 1
fi
}
# Main function
startup(){
set_video_memory
if ! grep -q 'Software\\\\GrindingGearGames\\\\Path of Exile' "${WINEPREFIX}/user.reg" >/dev/null; then
echo "Path of Exile not installed."
first_run
else
if ! is_updated; then
echo "Not up to date, re-run wine settings"
set_wine_settings
fi
fi
echo "Setting resolution to ${WINE_RESOLUTION}"
echo "If resolution was changed from default, game may need restarting"
winetricks --unattended vd="${WINE_RESOLUTION}" >/dev/null
echo ; echo "Starting Path of Exile..."
"${WINE}" "${POE_RUN_CMD}" dbox -no-dwrite -noasync
}
startup
The script gets run at startup, makes sure Path of Exile is Installed, otherwise it must be the first run, therefore install dependencies and download and install path of Exile.
Doing the install this way, I haven’t actually packaged Path of Exile, i’ve essentially packaged everything required to install it in a wine bottle on the first run.
The flatpak-builder
module for it is as follows.
"name": "pathofexile-installer",
"buildsystem": "simple",
"build-commands": [
"install --directory ${WINEPREFIX}",
"install pathofexile.sh /app/bin"
],
"no-make-install": true,
"sources": [
{
"type": "file",
"path": "pathofexile.sh"
}
]
Build the Flatpak Link to heading
Once the package is finished, run flatpak-builder
to create the resulting package (gpg signing is optional).
flatpak-builder --user \
--force-clean \
--repo=applications \
--gpg-sign=${GPG_KEY} pathofexile ca.johnramsden.pathofexile.json
When it’s finished, it will be exported to the applications
repo.
Remainder of flatpak-builder manifest Link to heading
My repo containing the build instructions can be found on github. It contains:
.
├── ca.johnramsden.pathofexile.appdata.xml
├── ca.johnramsden.pathofexile.json
├── pathofexile-256.png
├── pathofexile.desktop
├── pathofexile.sh
├── README.md
└── settings.conf
I tried to highlight the interesting parts above.
Install my Flatpak Link to heading
If you want to try my flatpak without having to build it yourself, add my Flatpak repository.
flatpak --user remote-add \
--if-not-exists johnramsden http://flatpakrepo.johnramsden.ca/johnramsden.flatpakrepo
Now you should be able to install my flatpaks.
flatpak --user install johnramsden ca.johnramsden.pathofexile
If you are missing the freedesktop platform you may need to add the flathub repository and install it. It should install itself, but you can also explicitly install it.
flatpak --user remote-add \
--if-not-exists flathub \
https://flathub.org/repo/flathub.flatpakrepo
flatpak --user install flathub org.freedesktop.Platform//1.6
To change the virtual desktop resolution, set the environment variable WINE_RESOLUTION
in the run command.
It defaults to 1920x1080, to set it to 720x480 for example you would change the run command to:
flatpak run --env=WINE_RESOLUTION=720x480 ca.johnramsden.pathofexile
To change the video memory if it’s not detected properly at run time, use the VIDEO_MEMORY
environment variable
flatpak run --env=VIDEO_MEMORY=1024 ca.johnramsden.pathofexile
To make it permanent edit the ~/.local/share/flatpak/exports/share/applications/ca.johnramsden.pathofexile.desktop
file.
If you think I’ve made a mistake or would like to submit a pull request, please visit the GitHub repository.
Automated Builds Link to heading
I also set up an automated system using Travis CI for building the flatpak upon push to github, and a GitHub pages site for distribution. In my next post I will describe how it is configured.