Beginner's Questions: file access

Hi all,

I’ve started to experiment with snaps yesterday for the first time, because putting my app to the snap store might be a good way to reach new users.

After a few hours, I managed to create a snap that installs fine and includes all the assets and libraries. (I had to use “environment:” to adjust the $PATH variable so the app can access the pulseaudio library, though).

Note that I just DUMP all app files into the snap, no compiling etc. needed. Usually my app is self contained, i.e. in the main folder, there is the binary and in subfolders are various assets. Also, config and save files (it’s a game) are saved in in the app folder, so currently end users simply download a zip, extract it and run the binary, and everything is fine. If they don’t like it anymore, they just delete the folder and no remains are left.

Obviously, this simple approach is not possible when I want to use snaps, because my app can’t write in the app’s folder, so I can’t save config and save files.

  1. What is the best place for storing app specific config files? I’d say the user’s home directory, but can my app write to it?

Another question is asset file access. When one downloads the (non-snap) zip of my game and runs the binary by double clicking it, all assets are found. But when I install my test snap of my app, assets are only found if I first cd into the app’s folder.

  1. Should I create a shell script that first changes to the snap’s folder and the executes the binary? Or should my binary itself, during runtime, get its own location and then cd into this location before it loads any assets?

Thanks!

2 Likes

Hi. Good to see more games being packaged as snaps!

  1. The ideal place to store config files would be under $SNAP_USER_DATA, which will resolve to something like /home/username/snap/snapname/version. If this worked as intended, it would give you the ability to change the conf file format when the game is updated, but automatically preserve the old conf file should the user wish to roll back to an older version of your game. Unfortunately, there are still some bugs in the implementation so most developers have resorted to using $SNAP_USER_COMMON instead. That doesn’t have the advantage of preserving old versions on upgrade, though. Of note, $HOME should automatically point to $SNAP_USER_DATA on a confined app so if you’re using that path in your game then things should work automatically. You’d want to point your users to that location.

  2. I use a wrapper script for this. It saves having to build a new version of a game to support the different file paths.

2 Likes

Hi and thanks for the answer,

  1. My plan was to get the user’s home dir (using FreePascal’s “GetUserDir” function, which returns the value of the home environment variable), and from my understanding of your answer, this would be $SNAP_USER_DATA. To which actual location would $SNAP_USER_COMMON point?

  2. As command I could then for example write “command: my-game.wrapper”, and in the wrapper script (a sh script, I think) I’d first add my asset folders to the environment variables (like PATH="$SNAP/graphics:$SNAP/music" and so on) and finally execute the binary (like exec “desktop-launch” “$SNAP/my-game”) Is this correct?

Sorry for my stupid questions.

1 Like

These aren’t stupid questions at all, your plan sounds perfectly feasible and I’d be happy to help you get this running.

$SNAP_USER_COMMON should point to /home/username/snap/snapname/common. Your wrapper script sounds as if it should do what you need it to do. I look forward to trying your game :slight_smile:

2 Likes

This may also be useful as a practical example of where the various environment variables point to at runtime. You can use snap run --shell <snapname> to jump into the snap environment, and then use env and friends to discover more about the environment.

alan@KinkPad-K450:~$ snap install hello-world
hello-world 6.3 from Canonical✓ installed
alan@KinkPad-K450:~$ snap run --shell hello-world
alan@KinkPad-K450:/home/alan$ env | grep SNAP
SNAP_USER_COMMON=/home/alan/snap/hello-world/common
SNAP_CONTEXT=LNV44fSJ68hgMJFrBx31Whg9xhrp3pnB71H47RyQG767
SNAP_REEXEC=
SNAP_LIBRARY_PATH=/var/lib/snapd/lib/gl:/var/lib/snapd/lib/gl32:/var/lib/snapd/void
SNAP_COMMON=/var/snap/hello-world/common
SNAP_INSTANCE_NAME=hello-world
SNAP_USER_DATA=/home/alan/snap/hello-world/27
SNAP_DATA=/var/snap/hello-world/27
SNAP_REVISION=27
SNAP_NAME=hello-world
SNAP_COOKIE=LNV44fSJ68hgMJFrBx31Whg9xhrp3pnB71H47RyQG767
SNAP_ARCH=amd64
SNAP_VERSION=6.3
SNAP=/snap/hello-world/27
SNAP_INSTANCE_KEY=
alan@KinkPad-K450:/home/alan$ echo $HOME
/home/alan/snap/hello-world/27
alan@KinkPad-K450:/home/alan$ exit
alan@KinkPad-K450:~$ 
1 Like

Ah, I see… The question now is how I tell my … snap or my binary? to save to this location. With the user data dir it’s clear, I can simply get the current home dir in my binary and it will use the snap’s home dir. But how can I enforce that it does use the common dir instead, without using static paths, of course? Or is this something that I don’t control in the binary, but instead in the .yaml file, so my binary will automatically use the common dir instead of the user dir?

Some additional problems:

I created a wrapper script that looks as follows:

#!/bin/sh
export PATH="$SNAP/graphics:$SNAP/music:$SNAP/data:$SNAP/sound:$PATH"
exec "desktop-launch" "lambdarogue"

where “lambdarogue” is the binary name and the name of my game.

In my yaml file I refer to this wrapper like this:

apps:
  lambdarogue:
    command: command-lambdarogue.wrapper
    desktop: lambdarogue.desktop
    plugs: [x11, pulseaudio]
    environment:
      LD_LIBRARY_PATH: $LD_LIBRARY_PATH:$SNAP/usr/lib/x86_64-linux-gnu/pulseaudio

The wrapper script, the yaml file, and all the other files to dump (binary, data files, assets) are all in the same folder. Before the snap was created successfully, but after I added the wrapper script, creating the snap fails:

Failed to generate snap metadata: The specified command 'command-lambdarogue.wrapper'
defined in the app 'lambdarogue' does not exist or is not executable.
Ensure that 'command-lambdarogue.wrapper' is relative to the prime directory.

As I wrote, “command-lambdarogue.wrapper” IS in the folder and it is also set to be executable.

As far as I can see, I do nothing different than for example Gnome Calculator. I’m really clueless.

command-$appname.wrapper is a name that snapcraft uses internally, you should not create a file named like that … instead, add PATH to your environment line in snapcraft.yaml and change command: like:

apps:
  lambdarogue:
    command: desktop-launch lambdarogue
    desktop: lambdarogue.desktop
    plugs: [x11, pulseaudio]
    environment:
      LD_LIBRARY_PATH: $LD_LIBRARY_PATH:$SNAP/usr/lib/x86_64-linux-gnu/pulseaudio
      PATH: $SNAP/graphics:$SNAP/music:$SNAP/data:$SNAP/sound:$PATH

and remove command-lambdarogue.wrapper from your tree.

1 Like

Thanks, that was helpful regarding the wrapper script’s name, although in the end I had to do it differently. This is now how my “apps” section looks:

apps:
  lambdarogue:
    command: sh $SNAP/run.sh
    desktop: lambdarogue.desktop
    plugs: [x11, pulseaudio]
    environment:
      LD_LIBRARY_PATH: $LD_LIBRARY_PATH:$SNAP/usr/lib/x86_64-linux-gnu/pulseaudio
      PATH: $PATH:$SNAP/graphics:$SNAP/music:$SNAP/data:$SNAP/sound:$SNAP

And this is how the sh file looks:

#!/bin/sh
cd "$SNAP"
exec lambdarogue

This way the game starts and finds all it files. Very simple, of course.

Now I can continue with editing the game itself, to store/read its config and save files from a valid location, i.e. changing all parts of the binary that currently expect config and saves in the game’s own folder, which does not work with the read-only snap.

Which brings me to my other question above:

With the user data dir it’s clear, I can simply get the current home dir in my binary and it will use the snap’s home dir. But how can I enforce that it does use the common dir instead, without using static paths, of course? Or is this something that I don’t control in the binary, but instead in the .yaml file, so my binary will automatically use the common dir instead of the user dir?

Okay, it works – for now! :smile:

Thanks to everyone who helped me so far.

Just one thing: Currently I’m using $SNAP_USER_DATA for my user files. It seems to work just fine on my local installation.

What exactly are the bugs which make most developers use $SNAP_USER_COMMON instead?

EDIT: Is it this one? https://forum.snapcraft.io/t/bug-saves-are-blocked-to-snap-user-data-if-snap-updates-when-it-is-already-running

Yep. It is in the process of being fixed.

So from what I understand the main issue could be that while my app is played by someone, it may get updated in the background, which changes the user folder to the new version’s location.

I’m wondering if I can neglect this. I save or read data from this folder when the game starts, when the list of save games is shown, when a save game is loaded, when the game is saved when quitting, when the save is deleted (perma death) and when a random dungeon is created and saved or loaded.

How can I cause the snap to use the “common” folder’s path instead of the user path?

Can I simply take my string which is holding the path to the user dir and modify it to get the common dir?

You might find the easiest way would be to set $HOME to $SNAP_USER_COMMON, rather than messing around with your binary. You could try adding export HOME=$SNAP_USER_COMMON as the second line of your launcher script, or try adding HOME: $SNAP_USER_COMMON to the environment stanza in your snapcraft.yaml.

2 Likes

it isnt bugs but “packager design decisions”, i guess :wink:

SNAP_USER_DATA is a versioned dir (typically ~/snap/<snapname>/<revision> with ~/snap/<snapdir>/current being a symlink pointing to it)

if your snap gets updated to a new revision the content is copied to the “new” SNAP_USER_DATA … now if you roll forward or backwards (snap revert ...), your snap will always find the data matching that revision even if there were massive API changes, configuration option transitions etc between these two versions …

so while you have the data tied to the revision you also duplicate the content of that dir and use up more disk space …

SNAP_USER_COMMON is not versioned and does not get copied at all, the data is not stored by revision, changes in the API that might lead to issues with i.e. (in your case) game level binary data or changes in config handling (like: upstream decided all options have to have to be capitalized and dropped backwards compatibility, so your settings are not understood anymore after upgrade/rollback) can not easily be managed.

so using SNAP_USER_COMMON saves diskspace, but also adds limitations …

1 Like

Thank you, that worked. So simple…

I’d be happy if you or someone else could test my snap:

https://forum.snapcraft.io/t/call-for-testing-lambdarogue-the-book-of-stars/

If it works for others, I can release it in “strict” mode.

Thanks for the good explanation! The revisions are a good thing, esp. since I tend to break save game compatibility from time to time, when I add new features to my game. For now, I decided for the COMMON alternative, but will come back to the other one once the things discussed in the aforementioned thread are solved.

I’ll have a look tomorrow evening :slight_smile:

1 Like