My repository on GitHub contains the latest version of service files described in this blog post:

In the previous post, I explained how to organize a repository for Emacs Org Mode files. I suggested the use of a Git repository containing symbolic links to directories in Dropbox. You probably have to set up a central Git repository at somewhere in order to synchronize the repository using git push/pull. Git is a distributed system, and you can pull changes from a repository on another computer, but you need a bare repository as the destination of push operations. It is also important to have a backup repository in a geographically remote location, because there is a risk of losing all of your computers.

Of course, you can use a repository hosting service to host your org repository. GitHub offers private repositories for $7 / month, and BitBucket and GitLab.com even provides unlimited private repositories for free. However, you may not like the idea of storing very personal information in such a service, because an administrator of the service may view your contents. I personally don’t want to keep sensitive/personal information in a code hosting service even in a private repository, because it is a code hosting service, and not a database. I actually don’t know what administrators of those services do to clients’ data. If you are paranoid, you wouldn’t want to do that.

This post describes how to add an encryption layer to Dropbox using gocryptfs, so that you can host your org repository in the encrypted space inside Dropbox. You are going to host your Git repository in Dropbox. Don’t share a Git repository on Dropbox! This solution works only for personal private Git repositories that will never be shared with other people. This information is also intended for Linux users, since gocryptfs does not support Windows, and its support for Mac OS X is still experimental.

The problem

You have a Git repository at ~/org, and you want to create its encrypted backup in a remote location. How can you do that?

Ideas

Storing a Git repository in Dropbox

You can store private Git repositories in Dropbox. I haven’t trusted this method until recently, but people report it is possible. However, it is applicable only to your private, unshared repositories. It is unsafe to share a Git repository over Dropbox, because simultaneous operations can corrupt your repository. To share a Git repository with others, use git-remote-dropbox or a code hosting service like BitBucket.

Encrypting a directory using gocryptfs

There are several solutions to encrypt a directory on Linux. Gocryptfs is a relatively newcomer to the world, which aims to solve the security weaknesses of EncFS but still has a good performance. It is considered as a successor to EncFS and reached 1.0. Note that there are security audits for gocryptfs.

Encryption setup

This tutorial assumes the following prerequisites:

  • You are using a Linux computer, preferably Arch Linux. I haven’t tested the solution with any other Linux distributions, but I believe they are mostly the same.
  • You are running the official Dropbox daemon, and ~/Dropbox directory is synchronized with your Dropbox.

Gocryptfs is implemented as a FUSE file system. You will use gocryptfs program to mount a directory inside Dropbox to somewhere on your file system, and fusermount to unmount the directory. You are actually going to create a Git repository inside the mount point, and not in a directory inside Dropbox.

The backup repository can be created in the following steps:

  1. Create a directory for encryption in Dropbox.
  2. Mount the directory on a local file system.
  3. Create a Git bare repository inside the mount point.
  4. Add the repository as a remote of ~/org, and use git push/pull for synchronization.

This tutorial will use the following configuration. You can change the parameters to suit your preferences:

  • Create an encrypted space in ~/Dropbox/encrypt/gocryptfs. This is a deeply nested location, but intended for compatibility.
  • Mount the directory at ~/vaults/dropbox.
  • Create a repository in ~/vaults/dropbox/git. Your backup repository will be ~/vaults/dropbox/git/org.git.

Installation

For Arch Linux, gocryptfs package is available from AUR. For other distributions, see Quickstart.

Setting up an encrypted space on Dropbox

Create a directory in Dropbox:

mkdir -pv ~/Dropbox/encrypt/gocryptfs

Initialize a gocryptfs file system by running the following command:

gocryptfs init ~/Dropbox/encrypt/gocryptfs

After prompting for a master password, gocryptfs initializes an encrypted directory inside your Dropbox.

Mounting the directory

Create a directory for the mount point:

mkdir -pv ~/vaults/dropbox

Mount the file system:

gocryptfs ~/Dropbox/encrypt/gocryptfs ~/vaults/dropbox

You are asked to enter the master password. If successful, you will see the following message:

Password:

Decrypting master key

Your master key is:

    YOUR_MASTERKEY_UUID

If the gocryptfs.conf file becomes corrupted or you ever forget your password, there is only one hope for recovery: The master key. Print it to a piece of paper and store it in a drawer. Use "-q" to suppress this message.

Filesystem mounted and ready.

You should backup the master key to somewhere.

After finishing operations, you can unmount the directory by runnning the following command:

fusermount -u ~/vaults/dropbox

Usage

Now that the encrypted space is set up, you can backup your ~/org repository by taking the following steps:

mkdir -p ~/vaults/dropbox/git
git init --bare ~/vaults/dropbox/git/org.git
cd ~/org
git remote add dropbox ~/vaults/dropbox/git/org.git
git push --set-upstream dropbox master

You can use git push/pull to synchronize your local repository with the backup.

Creating a systemd service to automatically mount the encrypted directory

You can mount and unmount the encrypted space using gocryptfs and fusermount on every machine, but it is bothersome to run those commands every time. You can automatically mount the directory by creating a systemd service file and enabling it.

Initial service file

Create a file named ~/.config/systemd/user/gocryptfs@dropbox.service and put the following content:

[Unit]
Description=Mount a gocryptfs-encrypted folder inside Dropbox
ConditionPathIsDirectory=%h/Dropbox/encrypt/gocryptfs
ConditionPathIsDirectory=%h/vaults
ConditionPathExists=%h/.gocryptfs-dropbox-password

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/gocryptfs -passfile %h/.gocryptfs-dropbox-password %h/Dropbox/encrypt/gocryptfs %h/vaults/dropbox
ExecStartPre=/usr/bin/mkdir -p %h/vaults/dropbox
ExecStop=/usr/bin/fusermount -u %h/vaults/dropbox

[Install]
WantedBy=multi-user.target

The password is read from a file at ~/.gocryptfs-dropbox-password so that you don’t have to type it every time. This is a plain text file that stores your password literally. In order to secure the password file, you should encrypt your whole system or home directory. If you haven’t, you recommend you encrypt your storage devices using dm-crypt.

Before running this service, you have to create the password file manually:

cat > ~/.gocryptfs-dropbox-password
# Enter your password and press Ctrl-D twice

# prohibit other users from reading/writing this file
chmod 600 ~/.gocryptfs-dropbox-password

Now you can mount and unmount the directory using the following commands, respectively:

systemctl --user start gocryptfs@dropbox.service

and

systemctl --user stop gocryptfs@dropbox.service

Using systemd to resolve dependencies

Systemd is well-capable of handling dependencies between units. I will use the feature to define the following behaviors of the vault, i.e. the mounted directory:

  • When Dropbox is running, the vault should be unlocked
  • When Dropbox is not running, the vault should be closed

This prevents you from pushing changes to the repository when Dropbox is not working. Note that this is not totally safe. There is a corner case where you push changes to the repository from multiple machines when Dropbox is running but offline on one of the machines. In this case, your Git repository on Dropbox is likely to corrupt:

Machine A ------> Vault <-------> Local copy of Dropbox <---\  not syncing
          git push       gocryptfs               |           X-->
                              inconsistent state |              Central repository of Dropbox
                           b/w Git repositories  |          /--->
Machine B-------> Vault <-------> Local copy of Dropbox <--/  syncing or not syncing
          git push       gocryptfs

I haven’t found a preventative to this risk yet. You will have the same risk whenever you store a Git repository in Dropbox, regardless of the existence of an encryption layer on gocryptfs. It can be prevented/mitigated by the following practices:

  • You must avoid pushing changes to the encrypted Git repository when Dropbox is offline.
  • Backup your repository regularly to somewhere else.

In other cases, unit dependency can make the repository a little safer by making it disappear when Dropbox is not running. For example, if your Dropbox installation is broken, you will have a risk of mistakenly pushing changes to the repository by default, but the systemd dependency will add safety.

Running Dropbox via systemd

In order to let systemd resolve dependencies between Dropbox and gocryptfs, you have to run Dropbox via systemd.

On Arch Linux, a systemd service file is shipped with dropbox package in the AUR, which is not activated by default. Unfortunately, the default service doesn’t seem to work properly. There is a workaround described on the thread, but I somehow failed to make it work.

Instead, I have found an alternative systemd service file written by a user, and it worked pretty well. I will describe how to adapt it to my use case.

Save the service file as a user unit so that it will replace the default service file:

mkdir -pv ~/.config/systemd/user
curl https://raw.githubusercontent.com/joeroback/dropbox/master/dropbox%40.service > ~/.config/systemd/user/dropbox.service

Note the service file downloaded in the above now needs modification. Use a service file in my GitHub repository instead.

In order to display an icon of the Dropbox daemon in your desktop tray, you have to tweak the unit. For this purpose, systemd provides a convenient feature named drop-in units to override part of an installed systemd unit.

Use systemctl edit command to create/edit a drop-in unit:

systemctl --user edit dropbox.service

Put the following content in the file:

[Service]
Environment=DISPLAY=:0

Start Dropbox via systemd using the following command:

systemctl --user start dropbox.service

If it works properly, you have to start the service as a startup application of your desktop. As Dropbox automatically adds itself to the list of startup applications, the service should be started subsequently to kill the already started Dropbox. The command should be systemctl --user start dropbox.service, and the startup delay should be five seconds, for example:

Adding dropbox service to startup applications on Cinnamon Desktop

Also add a startup program to kill an existing Dropbox daemon (killall dropbox) with 2 seconds of delay.

Defining dependencies between the gocryptfs service and Dropbox

Now that Dropbox is running as a systemd service, you can define dependencies on the service. gocryptfs@dropbox.service should have the following content:

[Unit]
Description=Mount a gocryptfs-encrypted folder inside Dropbox
ConditionPathIsDirectory=%h/Dropbox/encrypt/gocryptfs
ConditionPathIsDirectory=%h/vaults
ConditionPathExists=%h/.gocryptfs-dropbox-password
After=dropbox.service
BindsTo=dropbox.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/gocryptfs -passfile %h/.gocryptfs-dropbox-password %h/Dropbox/encrypt/gocryptfs %h/vaults/dropbox
ExecStartPre=/usr/bin/mkdir -p %h/vaults/dropbox
ExecStop=/usr/bin/fusermount -u %h/vaults/dropbox

[Install]
WantedBy=dropbox.service

Here I will describe its details. The following line designates that the service should start after Dropbox is started:

After=dropbox.service

There is another additional line in [Unit] section:

BindsTo=dropbox.service

This ensures the following two things:

  • When this service is started, Dropbox is started. (This is the same behavior as Requires=. Note that Dropbox is started before this service because of the After= property)
  • When Dropbox is stopped, this service is stopped.

Finally, the following [Install] section allows you to automatically trigger this service when Dropbox starts:

[Install]
WantedBy=dropbox.service

In summary, this unit defines the following dependencies:

  • If this service is enabled, the service automatically starts when Dropbox starts. (WantedBy=)
  • This service is started after Dropbox starts. (After=)
  • If Dropbox is not running when this service starts, Dropbox is automatically started too. (Bind=)
  • When Dropbox stops, this service stops too. (Bind=)

Enable this service using the following command:

systemctl --user daemon-reload
systemctl --user enable gocryptfs@dropbox.service

Start both Dropbox and the vault by restarting Dropbox service:

systemctl --user restart dropbox.service

Wrapping up

This post has defined how to provide an encrypted space inside Dropbox. It is available alongside the Dropbox service, and you can use it to store a central copy of private Git repositories. The whole backup of ~/org repository now exists on the central server of Dropbox to prepare for a computer loss. I still recommend that you should keep another offsite backup set of your Git repositories for true resilience, but the encrypted space on Dropbox can help with your Git workflow while keeping an up-to-date copy of your data in a convenient place. The encrypted space can provide more use cases, which are left undiscussed in this post.