Snapping Nextcloud: Nextcloud itself
In the previous post of this series, we discussed the process of and lessons learned from building Redis for the Nextcloud snap. That means we've covered all the major pieces except for Nextcloud itself, so for this post that's what we'll focus on getting that into the snap.
As I mentioned previously, Nextcloud is a PHP application. As such, it's not difficult to get working with Apache or MySQL. However, it was written in such a way to make its deployment as easy as possible (e.g. untar it <here>), which led to a complication in the way I chose to package it.
Lesson 7: Read-only is both a blessing and a curse
Snaps are squashfs images, which by definition means they're read-only. I thought this was a pretty big plus for Nextcloud: even if it was compromised, the attacker couldn't modify any part of the application. However, there's a downside here, too: Nextcloud has an admin interfaces that allows a user to alter its configuration. However, that configuration isn't stored in the database: it's stored within a config.php file which lives inside the application itself (in the config directory). Well, in a snap, that directory (and config file) is completely read-only. That means you can't even configure email notifications, since those settings are stored in that config file.
This meant I had to choose one of two paths:
- Store the entire application in a readable location (i.e. $SNAP_DATA somewhere).
- Store the config in a writable location, separate from the read-only application.
(1) would mean duplicating the application, because during the installation process I'd have to copy the application from the snap to the writable area, which that means there are two copies of the application. Nextcloud is not small (it's about 90MB all by itself), so that seems wasteful. It also defeats what I consider to be an advantage of the snap: the fact that it is read-only in the first place! So (2) seemed to be the obvious win from my perspective.
However, while Nextcloud supports apps and data being hosted outside the main application, it does not support hosting the config outside of the main application. This meant I had to fork Nextcloud and apply a patch adding that support, and use that fork in the snap. I tried to move that patch upstream back when it was ownCloud, but I was unfortunately unable to gain any traction. Perhaps I should try again with Nextcloud. (Update: This was upstreamed in Nextcloud v10; the snap no longer uses a fork).
Speaking of hosting data outside the main application, that led to:
Lesson 8: Versioned data directories don't cover all use-cases
If you read the available documentation and take a look at the environment in which snap applications and services execute, you'll note that there are two writable areas available to all snaps:
- $SNAP_DATA:system-wide writable area.
$SNAP_USER_DATA: user-specific writable area.
Both of these areas are contained within versioned directories, i.e. the data contained within them are version-specific. What do I mean by this? Let me explain with an example:
Say you install version 1 of snap foo. Say it writes files into both $SNAP_DATA and $SNAP_USER_DATA. Now an update to foo is release: version 2. As part of the upgrade process, snapd copies version 1's data directories into version 2's data directories, and then starts version 2 of foo. Version 2 fires up and is running on the data that was last touched by version 1. Maybe it runs some migrations on it.
What if those migrations fail? Well, then snapd can revert (or rollback) version 2, and start running version 1 again. Since snapd made a copy of version 1's data before the migration occurred, version 1 is fired up running on the data exactly as it left it rather than on the data where the failed migration occurred.
That's lovely, but it's problematic when it comes to applications like Nextcloud. My personal Nextcloud collection of data is several terabytes. Automatically copying that data every time an upgrade occurrs just in case Nextcloud needs to rollback is not a scalable workflow unless you have some serious hard drive space. This led to the development of a new feature in snapd, a data directory that was common across all versions of a given snap. This feature is not yet documented because those directories are not yet represented by an environment variable, but the Nextcloud snap makes use of them anyway for storing its raw data. (Update: They are now: $SNAP_COMMON and $SNAP_USER_COMMON.)
Beyond those issues, Nextcloud was pretty straight-forward to get up and running in the snap. It provides the ability to pre-configure some settings which allowed me to make the initial setup nice and painless (e.g. pre-configure the database, etc.). I wish they exposed more things that way though-- I'd also like to set the background jobs to using cron instead of ajax, but you can only do that after the application is installed (i.e. an admin user is created), so the snap is still using ajax. Maybe I'll try proposing a change (Update: the cron type is now automatically changed once the cron job runs).
But all in all, I'm quite happy with the snap.