--- date: 2024-03-28 title: Migrating Nextcloud from Local Storage to S3 tags: - homelab - s3 - Nextcloud --- As I continue to investigate [migration from Unraid to XCP-ng](https://blog.mcknight.tech/2024/01/30/Compute_Setup/) (or maybe Proxmox), I am taking a closer look at what storage resources my containers are still using on my Unraid system. Unsurprisingly, Nextcloud is using a significant amount of persistent storage and I found that moving that data to a remote share is difficult due to Nextcloud's file permission requirements. Looking for a solution, I found that Nextcloud supports S3 storage and, while not officially supported, there are ways to move from local storage to S3 without having to start over from scratch. I will admit that much of this was heacily inspired by other guides which I've link below, but there were a few gotchas related to doing this with containers in Unraid that I'll highlight. ## Why to migrate storage As I mentioned, my primary reason for migrating from local to S3 storage is to simplify running Nextcloud in a container with primary storage residing on a different system. This is also a fun project for me to learn a little more about how S3 works (this is my first application using S3 storage 🎉). I'm using MinIO on TrueNAS which was practically a one-click deployment; I followed [this video from Lawrence Systems](https://youtu.be/uIm41PhGEgQ?si=0X1FZgsNfZisnaKw). ## Preparing for migration Fist things first, configure a bucket for Nextcloud (and only Nextcloud) to use; I've named mine "nextcloud". I only have a few users on my Nextcloud instance and I'm the only one who uses it regularly, so I was able to prevent any changes during the migration by closing any desktop applications that sync and not taking any photos on my phone. If you have more users or activity, I would recommend putting Nextcloud into maintenance mode before migrating things (`occ maintenance:mode --on`). ## Migrating from local to S3 Other guides I found made some assumptions about Nextcloud and SQL running in the same environment, which isn't true in my case where they're in containers. They also assumed the `aws` cli could be used where Nextcloud is running which is also not true in my case since Unraid doesn't have that package available. The code snippets below have been modified from other examples to fit my use case. ### Get files from SQL My Nextcloud setup uses MariaDb, so the first step is to export the list of files, along with some identifiers Nextcloud uses, to be uploaded. My database is called `nextcloud` and the `/config` directory is mounted to the host. `/mnt/user/Nextcloud` on the host system is mounted to `/data` in the Nextcloud container. ```shell mysql -p -B --disable-column-names -D nextcloud << EOF > /config/meta_file_list select concat('urn:oid:', fileid, ' ', substring(id from 8), path) from oc_filecache join oc_storages on storage = numeric_id where id like 'local::%' order by id; EOF mysql -p -B --disable-column-names -D nextcloud << EOF > /config/user_file_list select concat('urn:oid:', fileid, ' ', '/mnt/user/Nextcloud/', substring(id from 7), '/', path) from oc_filecache join oc_storages on storage = numeric_id where id like 'home::%' order by id; EOF sed -i -e "s| /data/| /mnt/user/Nextcloud/|g" meta_file_list ``` Note that `user_file_list` paths are updated in the `mysql` command and the `meta_file_list` is updated by the `sed` command to be accessible on the host. ### Move files to upload directory Since I will be mounting the upload directory to a virtual machine, I opted to copy files rather than create symlinks like the example I used as reference. It doesn't particularly matter where on the host system the files to upload are copied to, but I used `/mnt/user/Nextcloud` which was my existing share for Nextcloud data. I copied `user_file_list` and `meta_file_list` from the previous step to that path. ```shell cd /mnt/user/Nextcloud mkdir s3_files cd s3_files while read target source ; do if [ -f "$source" ] ; then cp "$source" "$target" fi done < ../user_file_list while read target source ; do if [ -f "$source" ] ; then cp "$source" "$target" fi done < ../meta_file_list ``` At this point, all of the files are named and placed in the `s3_files` directory, ready to be uploaded to S3. Note that since the filenames contain `:`, this directory will NOT be accessible via an SMB share. I exported it as an NFS share, but if I could install `awscli` on the host system then it wouldn't have been a concern. On my VM where I actually uploaded everything to a bucket named `nextcloud`: ```shell sudo mount :/mnt/user/Nextcloud /home/$USER/Nextcloud cd ~/Nextcloud aws --endpoint-url s3 sync s3_files s3://nextcloud ``` ### Nextcloud config updates With all of the files uploaded to S3, Nextcloud needs to be updated to point at the new data source. So, time to put Nextcloud into maintenance mode and start breaking things 🙂. From here on out, we are making potentially destructive changes, so make backups, have a recovery plan, and don't continue until reading to the end first. In the Docker container: ```shell occ maintenance:mode --on nano /config/www/nextcloud/config/config.php ``` Add the following to the Nextcloud config (`/config/www/nextcloud/config/config.php`): ```php 'objectstore' => array ( 'class' => 'OC\\Files\\ObjectStore\\S3', 'arguments' => array ( 'bucket' => 'nextcloud', 'autocreate' => true, 'key' => '', 'secret' => '', 'hostname' => '', 'port' => 443, 'use_ssl' => true, 'region' => 'home', 'use_path_style' => true, ), ), ``` > Make sure this is before the closing `);` at the end of the file `use_path_style` was required for MinIO, but I read that this may be false for other providers. ### SQL database updates The SQL database at this point still contains local file references, so those will need to be updated to the uploaded resources. In the SQL container: ```shell mariadb -p -D nextcloud -e "update oc_storages set id = concat('object::user:', substring(id from 7)) where id like 'home::%';" mariadb -p -D nextcloud -e "update oc_storages set id = 'object::store:amazon::nextcloud' where id like 'local::%';" mariadb -p -D nextcloud -e "update oc_mounts set mount_provider_class = 'OC\\\Files\\\Mount\\\ObjectHomeMountProvider' where mount_provider_class like '%LocalHomeMountProvider%';" ``` ### Validating Changes With configuration and database changes completed, its time to take Nextcloud out of maintenance mode and validate changes. In the Nextcloud container: ```shell occ maintenance:mode --off ``` > I also restarted the Nextcloud container here for good measure, but it may not be necessary. Load up Nextcloud in a browser and make sure the page loads, user login works, and most importantly user files are accessible. If this all works, then the migration is complete! ## Cleanup local files With migration completed, I chose to remove the `data` container mount since the directory only needs the `.ocdata` file and should only contain logs. On the host system: ```shell cp /mnt/user/Nextcloud/.oc_data /mnt/user/appdata/nextcloud/data/ chmod 770 /mnt/user/appdata/nextcloud/data/ chown nobody:users /mnt/user/appdata/nextcloud/data/ ``` > Note that the ownership is specific to Unraid, but Nextcloud requires permissions `770`. Then, modify Nextcloud's `config.php`: ```php 'datadirectory' => '/config/data', ``` Then the `/data` mount can be removed from the Docker container and the container restarted. At this point, the old data share can be removed completely (I'll archive mine as a backup). ## Wrap up I have been using my Nextcloud with S3 storage for a few days now and haven't noticed any difference compared to local storage. I like that the storage is accessible via HTTP so I don't have to worry about managing permissions and access from the Nextcloud container to my storage server. ## References - [mrAceT migration scripts](https://github.com/mrAceT/nextcloud-S3-local-S3-migration) - [Nextcloud issue with guide](https://github.com/nextcloud/server/issues/25781) - [Nextcloud forum thread](https://help.nextcloud.com/t/migrate-from-local-storage-to-s3/138042)