How to pass a variable to remove hook?

My python snap has a particular variable, established on first-run of the app in the python code, that I want my remove hook to send to our remote servers so that our servers know this particular instance of the app has been uninstalled.

My first thought was to save the variable to a text file in $SNAP_USER_DATA and read from that file in the remove hook. But that isn’t working for some reason. Is the $SNAP_USER_DATA directory still around when the remove hook is called, or has it already been removed?

But more importantly, how should my app save – and then my remove hook access – a variable? What’s the right way to do this?

Thanks!

hooks run as root … $SNAP_USER_DATA points to /root/snap/<snapname>/current from the POV of a hook …

So $SNAP_USER_DATA for the remove hook is a totally different location than $SNAP_USER_DATA for the python code? Then what would be the best way to store a variable that is available to both sets of code, (with write perms for the python and read perms for the remove hook, if we’re talking file system)?

You can write (from python) the var to SNAP_DATA or SNAP_COMMON and read it from the hook script. (The use case – snap remove – is not related to the user).

1 Like

I think I’m confused here about something, but I’m not sure what.

Snaps are installed & uninstalled on a per-user basis, correct? So if multiple users on one device each want the snap, they each have to install it… right? And then they would each share files in SNAP_COMMON, correct? So if I need the variable for the remove-hook to be user-specific, how would the remove-hook, when it runs, know which user the remove request pertains to?

Or are snaps always installed for all users at once, and always uninstalled for all users at once? If so, SNAP_COMMON or SNAP_DATA will definitely resolve my issue, though I may have other problems I haven’t realized yet.

Seems like the app installs for all users, and also uninstalls for all users.

I think I can make this work with SNAP_COMMON or SNAP_DATA.

Thank you!

note that you can also use snapctl set foo=bar and snapctl get foo from within hooks and apps to exchange globally stored variable data between runtime user session and root owned hooks …

that doesnt need any disk writes or picking of directories at all :wink:

1 Like

Cool. yes, snaps are not installed/removed per user.

1 Like

I am still having some issues here. It doesn’t appear as though my user account, running my python code, can save a file to $SNAP_DATA as @kyleN suggested. The app requires root permissions.

user_data_dir = os.environ['SNAP_DATA']
file_path = user_data_dir + "/my_file.txt"
if not os.path.exists(file_path):
    code_file = open(file_path, "w+")

Is there a reasonable way to elevate permissions for the app in my python code where it is necessary, or else to write to $SNAP_DATA or $SNAP_COMMON without elevated privileges?

Alternatively, I have tried saving data to a snapctl variable as @ogra suggested, but that, too, requires root permission.

subprocess.call('snapctl set myvariable=' + my_variable, shell=True)

I get this error:

error: cannot use "set" with uid 1000, try with sudo

Surely there is some way to pass a variable from my python running as user to my remove hook running as root? I apologize if I seem to need some hand-holding!

If you are running the python from shell as a snapcraft app, then you would need to run it with sudo. If the python is running from a snapcraft daemon, it runs as root and should be able to just write to SNAP_DATA.

1 Like

wow, sorry, i was assuming snapd is a bit cleverer than this , you should file a bug IMHO, this is definitely something i’d expect to work.

1 Like

I finally found a solution to this that doesn’t require manual interfaces thanks to @jdstrand!

In my bash install hook (running as root):

#!/bin/bash
mkdir -m 3777 $SNAP_COMMON/users

In my bash remove hook (running as root):

#!/bin/bash
find $SNAP_COMMON/users -maxdepth 1 -mindepth 1|while read filepath; do
    if [[ $filepath == *.appvar ]];
    then
        while IFS= read -r datastring
        do
            echo "$datastring"
        done < "$filepath"
    fi
done

And in my python code (running as the user):

import subprocess
subprocess.call('umask 027', shell=True)
subprocess.call('echo "' + my_variable.strip() + '" > $SNAP_COMMON/users/$(id -u).appvar', shell=True)
2 Likes