Firefox Sync Server on a Raspberry Pi

Author: akeil
Date: 2016-09-14
Version: 1.2
updated: 2016-09-15

The Firefox browser can use a Sync Service to synchronize settings, bookmarks and other stuff across multiple Firefox installations. By default, Mozilla's public sync server is used but it is possible to run your own sync server [1].

We will install piCore Linux and the Firefox Sync Server on a new Raspberry Pi.

Install piCore OS

We will use piCore [2], the Raspberry Pi version of Tiny Core Linux [3]. The benefit of using piCore is that it runs entirely from memory which should reduce strain on the SD card.

piCore is installed by downloading the image and putting it on an SD card.

$ wget
$ unzip
$ sudo dd if=piCore-8.0.img of=/dev/sdX bs=1M
$ sudo sync

Where /dev/sdX is the empty SD card. Don't forget to unmount all partitions from the SD card before writing to it.

After that, put the SD card into the Raspberry Pi, plug in power and network and log in with user tc and password piCore.

Extend Partition

TinyCore Linux knows two modes - "Cloud Mode" and "Mounted Mode". Mounted Mode allows to have some persistence, e.g. for installed applications. For this, a second partition is used. The second partition is already available after installation but it should be enlarged.

Use fdisk to delete the existing partition and create a new, larger one:

The second partition will normally be mounted. Unmount it first.

$ sudo umount /dev/mmcblk0p2
$ sudo fdisk -u /dev/mmcblk0

List partitions with p and note down the start and end sectors of the second partition. Delete the second partition with d, then create a new one with n. Use the same start sector as previously but different end sector. The default end sector uses all available space. Save with w.

After a reboot, log in again an expand the filesystem to fill up the second partition:

$ sudo reboot
$ sudo resize2fs /dev/mmcblk0p2

This should leave us with two partitions on the SD card. A small partition 1 (mmcblk0p1) which holds the OS files and will be unmounted after boot and a larger partition 2 (mmcblk0p2) which takes most of the SD cards capacity and holds installed applications and data.

Add Additional Storage

We will use the No. 2 partition to persist applications and settings on the SD card. We will also set up a removable USB drive to hold bulk data. We create a filesystem on it and set it up to be mounted at boot.

Plug the USB storage into the Raspberry Pi. It should show up in /proc/partitions, blkid can be used to get more info.

$ cat /proc/partitions
$ blkid /dev/sdX

We will put an ext2 filesystem on it. ext2 is non-journaling which should generate less writes and extend the life of the flash drive

First, we create a single partition for the whole disk:

$ sudo fdisk /dev/sdX
> d
> n
> p
> 1
> w

Then, create an ext2 filesystem [4]:

$ mke2fs /dev/sdX1

Since the device is a plugged in drive, we mount it by its UUID. Find the UUID with blkid /dev/sdX1. Also we choose a different directory name for the mountpoint.

To mount it automatically on boot, add the following lines to /opt/

mkdir /mnt/storage
chmod 777 /mnt/storage
mount UUID=b0ee2fe8-c54a-4c67-9a40-a9c2c4cb734c /mnt/storage

And the respective unmount command to /opt/

umount /mnt/storage

Tiny Core Persistence

By default, TinyCore Linux has no persistence. That means all changes made in configuration files or newly installed applications are lost when the system reboots. There are two options to keep data between two boots:

  • backup
  • persistent partitions

Software packages for TinyCore Linux are called "extensions" and they are stored in the /tce directory.

The preconfigured image already comes with the /tce directory on the second partition so there is not much to do here.

Verify that /mnt/mmcblk0p2/tce exists; it should contain some preinstalled applications (like openssh). Look at /etc/fstab to see how /dev/mmcblk0p2 is mounted.

The backup utility compresses selected files or directories and stores them as mydata.tgz inside the /tce directory.

The file /opt/.filetool.lst defines which files or directories will be included. /opt/.xfiletool.lst can be used to exclude previously included files (e.g. include a complete directory tree but exclude single large files).

The default list includes the /opt and /home directories, ssh host keys and files related to user management (/etc/passwd for example).


The backup script does not run automatically. One must include a call to -b in /opt/ Also, will only run when the system is shut down with

Install Firefox Sync

The server is installed into a python virtualenv [5] which is located in the syncserver installation directory. The server will later run as a Pyramid [6] app inside a Gunicorn [7] application server.

There is no prebuilt package for Firefox Sync on piCore so we need to download the source and build the software locally.

Install Prerequisites

Start by installing prerequisites:

$ tce-load -wi python
$ tce-load -wo python-dev
$ tce-load -wo make
$ tce-load -wo git
$ tce-load -wo gcc
$ tce-load -wo compiletc
$ tce-load -i python-dev make git gcc compiletc

The -wi switch adds the extension to the OnBoot list which means it is loaded on every boot. The -wo switch creates an OnDemand item which we can load manually with tce-load -i. We install only the base python with the OnBoot option because it is the only package we need for running the app. All other packages are only required for the build process.

The installation additionally requires Python virtualenv for which we do not have a piCore package. But we can install virtualenv from source [8]:

$ wget
$ tar -xzf virtualenv-15.0.3.tar.gz
$ cd virtualenv-15.0.3
$ sudo python install
$ cd ..
$ rm virtualenv-15.0.3.tar.gz
$ rm -r virtualenv-15.0.3

The download URL can be obtained from PyPi.


Installing virtualenv in this way places installation files in their default locations. They will be lost on shutdown. This does not matter (much) as we only need virtualenv for the installation, not for running the application.

Create an ffsync User

We want to run the sync service under a dedicated ffsync user. Create it like so:

$ sudo adduser ffsync -SH

Installation Location

Decide upon an installation location.

The default installation procedure is to clone the syncserver git repository [9] and build the application inside the repo directory. Building the application entails creation of a virtual Python environment. The virtualenv is basically a copy of the system-wide Python install plus additional packages installed in a local directory, in this case the syncserver installation directory.

This results in a large number of files and directories. If we keep the installation inside the /home directory, we have the benefit of running it from memory but the disadvantage that the backup and restore for the home directory takes quite long.

If we install to a persistent partition, we lose the "running from memory" advantage.

The proper(?) solution is to create a TinyCore Linux extension for it. This is described in detail in chapter 14 and 15 of the Tiny Core book [10] [PDF]. The basic idea is to create the desired directory structure and files in some temporary directory and then "pack" it with squashfs. TinyCore Linux will later mount/symlink it into the desired location.

That is, if we want to have a file /usr/share/myfile, create /tmp/myextension/usr/share/myfile and then use squashfs to create myextension.tcz from /tmp/myextension.

The syncserver build process (more precisely: virtualenv) will generate some files with hardcoded paths so it is tricky to move the installation to some place different than where it was created. There is an option to make a relocatable virtualenv but it does not seem fully supported and we are not going to use it.

The good thing is that the virtualenv installation keeps all required files under a single base directory. This makes it possible to install the application at the desired location then pack it up and remove the original install.

We will install under /usr/local/syncserver.

First create the required directory and clone the syncserver repo into it:

$ cd /usr/local
$ sudo mkdir syncserver
$ sudo chown syncserver tc:staff
$ git clone --depth=1 syncserver
$ cd syncserver
$ make build


Root permissions are only required to create a new directory in /usr/local. The installation itself works with normal permissions.

Use git clone with --depth=1 to retrieve as little history as possible, but still keep it as a git repo.

Delete the syncserver/.git directory to save additional space.

Next, we need to install the Python database driver. This is done using the Python package manager (pip) that is part of the virtualenv. So, from the syncserver directory:

$ ./local/bin/pip install pysqlite

Install pysqlite for Usage with SQLite, PyMySQL for MySQL/MariaDB.


The make build and the installation of pysqlite/PyMySQL with pip will leave several files in ~/.cache/pip. You may want to delete those so they will not be included in the backup.

We should now have a complete installation at /usr/share/syncserver. Running sudo make serve from that directory should start the server with default settings on port 5000.

Look at ./local/bin/gunicorn. On the top it should say:



The default setup assumes that the syncserver is started from inside the installation directory with make serve and that the configuration file is kept there (it is referenced with ./syncserver.ini). If we want to change config separately from the installation, we need to copy it to another location:

$ cp syncserver.ini /etc

And also include it in /opt/.filetool.lst:


We will later use a custom command like this to run the service:

$ ./local/bin/gunicorn --paste /etc/syncserver.ini

Create the Extension

First off, install squashfs-tools:

$ tce-load -wo squashfs-tools

To produce a TinyCore Linux extension, we need to transfer everything to a temporary directory structure, pack it and move the resulting .tcz file to the tce directory:

$ cd /tmp
$ mkdir -p syncserver/usr/local
$ sudo mv /usr/local/syncserver syncserver/usr/local
$ sudo chown root:root -R syncserver/
$ mksquashfs syncserver syncserver.tcz
$ mv syncserver.tcz /etc/sysconfig/tcedir/optional
$ sudo rm -r syncserver/


It is a good idea to place a backup of the syncserver.tcz file somewhere other then the tce/optional directory.

We can now start the application with:

$ tce-load -i syncserver

You should see the syncserver installation (as a collection of symlinks) under /usr/local/syncserver.

Note that tce-run will not actually start the service. It will only make the installed application available. Running sudo make serve from the installation directory should now work as before.

To have the application available on boot, add it to the onboot.lst (inside the tce directory):

$ echo syncserver.tcz >> /etc/sysconfig/tcedir/onboot.lst

Start and Stop Script

We need a custom script to start the service. The start script is included in to start the service on boot and performs three tasks:

  1. change into the correct working directory (/usr/local/syncserver)
  2. switch to the ffsync user
  3. start the syncserver with our configuration file at /etc/syncserver.ini
COMMAND="$GUNICORN --paste $CONFIG --log-file /tmp/syncserver.log > /dev/null &"
(cd $BASEDIR && su ffsync -c "$COMMAND" -s /bin/sh)

We still need to change the working directory before we execute the sync server. If we do this inside the script we might affect subsequent actions in that script. One way to avoid this, invoke everything in a subshell.

Also, since the gunicorn command will not exit, "disown" it afterwards with "&".

To stop the service, use:

pkill syncserver

(not pretty)


Edit the config file to reflect your settings:

public_url = http://box:5000/
sqluri = sqlite:////mnt/storage/syncserver/syncserver.db
secret = xxx
allow_new_users = false  # set to true to create initial user

public_url is set to what the client sees as the URL of the service.

If you do not configure a sqluri, the default applies - which is an in-memory database (which is lost when the service is shut down).

The secret is used to generate authentication tokens. It can be any random string. Generate one like this:

$ head -c20 /dev/urandom | sha1sum

allow_new_users should be set to true initially. When the service is fully installed and all required Firefox accounts are registered, it can be set to false.

Install a Database

The syncserver works with SQLite, MySQL/MariaDB or PostgreSQL. There are piCore packages for SQLite and MariaDB.


For SQLite, make sure the sqlite3 package is installed. Then decide where the database file should live and update syncserver.ini accordingly:

sqluri = sqlite:////mnt/storage/syncserver/syncserver.db

Remember to make the database directory accessible for the ffsync user.

$ sudo mkdir /mnt/storage/syncserver
$ sudo chown ffsync:staff /mnt/storage/syncserver


There is a (somewhat outdated?) MariaDB installation guide [11] in the TinyCore Linux wiki.

Start by installing MariaDB

$ tce-load -wi mariadb
$ tce-load -wi mariadb-client

Next, we make sure that the DB service starts on boot and stops on shutdown. The package comes only with a partially complete configuration and some effort is required to make it work.

Start by creating a system account to run mariadb:

$ sudo adduser -SH mysql

Next, add commands to start and stop MariaDB [12] to and

/usr/local/share/mysql/mysql.server start

/usr/local/share/mysql/mysql.server stop


Make sure that the database service is started before the syncserver, and shut down after the syncserver.

Then create a minimal configuration file for MariaDB [13] file at /etc/my.cnf:

socket = /tmp/mysql.sock

socket = /tmp/mysql.sock
basedir = /usr/local
user = mysql
datadir = /usr/local/mysql/data
tmpdir = /tmp
log_error = /tmp/mysql.error.log
pid_file = /tmp/

It is important to set the socket option in the [server] and [client] sections. Otherwise tools like mysqladmin will not see the socket option and will fail to connect.

Make sure that the datadir is accessible for the mysql user:

$ sudo chown -R mysql:nogroup /usr/local/mysql/data

Manually starting and stopping mysqld should now work:

$ sudo /usr/local/share/mysql/mysql.server start
Starting MySQL. SUCCESS!
$ sudo /usr/local/share/mysql/mysql.server stop
Shutting down MySQL.. SUCCESS!


For now, the config file and data directory only exist in memory. To persist the config file between reboots, add it to the backup list in /opt/.filetool.lst:


The data directory is where the database files are stored. By default, this is /usr/local/mysql/data.

If this would be kept in memory and backed up/restored when the system restarts, we have a risk of losing data when the system shuts down unexpectedly and the backup script does not run (e.g. when power is unplugged).

To avoid this, keep the database on a separate partition and create a symlink in the original location which points to the new location. Stop the mysqld service before moving the data directory.

$ sudo /usr/local/share/mysql/mysql.server stop
$ sudo mkdir /mnt/storage/mysql
$ sudo chown mysql:nogroup /mnt/storage/mysql
$ sudo mv /usr/local/mysql/data /mnt/storage/mysql
$ sudo ln -s /mnt/storage/mysql/data /usr/local/mysql
$ sudo /usr/local/share/mysql/mysql.server start

We need to repeat parts of this on every reboot. So add the following to (before starting mysqld):

# replace the standard datadir with persisted data
ln -s /mnt/storage/mysql/data $MYSQL_DATADIR

Setup a Database User

First, set a password for mysql admin

$ mysqladmin -u root password <secret>

Login to mysql console to create the user and database for ffsync:

$ mysql -u root -p
(enter password when prompted)
mysql> CREATE USER 'ffsync'@'localhost' IDENTIFIED BY '<secret>';
mysql> CREATE DATABASE ffsync;
mysql> GRANT ALL ON ffsync.* TO 'ffsync'@'localhost';

Finally, configure Firefox Sync Server to use the MariaDB database:

sqluri = pymysql://ffsync:<secret>@localhost/ffsync

Configure Firefox

Go to about:config and set:

identity.sync.tokenserver.uri = http://box:5000/token/1.0/sync/1.5

(the default is

You will still need a firefox account to use the sync feature.


syncserver complains that public_url does not match application url. Public URL is set to "http://hostname:5000/", error says that app url is "http://hostname/". Change public_url in config file to hostname w/o port, get the same error but the other way round. Set force_wsgi_environ = true fixed this.

With SQLite - errors when clients try to sync:

(OperationalError) cannot start a transaction within a transaction

Tried to limit gunicorn to a single worker thread - no help. Installed MariaDB to work around this.


Type about:sync-log into Firefox address bar to see ...sync logs.

Some additional links: - - -