Firefox Sync Server - Backup

Author: akeil
Date: 2016-09-15
Version: 1

This is a follow up on a previous post. where I installed a Firefox Sync Server on a Raspberry Pi running piCore. After having the Sync Service basically running, it is time to set up regular backups.

Filesystem Backup

This is a variant on a backup-scheme described in an older post. Most backups work by creating a backup locally and then pushing it to a remote server. In this case there is no remote server and the backup is to be stored on a desktop computer. The problem is that the desktop computer is not always running.

Therefore the backup will be initiated from the destination machine. It assumes that the source machine(s) will always be reachable.

TinyCore Linux already has a backup facility [1], namely filetool.sh. This creates a backup of selected files under mydata.tgz. Data is restored on every boot.

Use

$ filetool.sh -bs

The -s option tells filetool to create a safe backup. "Safe" means that the previous backup is secured before it is overwritten with a new one.

Still, mydata.tgz is stored in the tce directory on a local drive. And for a Raspberry Pi this means the SD card or an attached USB removable drive. Not exactly reliable storage media.

We will use rsync [2] to periodically download data from the tce directory (and possibly other locations) to another computer. The basic command should look like this:

$ rsync --archive rsync://box/<src> <dst>

Install and Configure rsync

Install rsync on the TinyCore Linux machine with:

$ tce-load -wi rsync

Install with -wi to have it added to the onboot.lst.

We need rsync to run in daemon mode so that the client can connect to it at any time.

First, create a minimal rsyncd configuration [3] at /etc/rsyncd.conf:

uid = nobody
gid = nogroup
pid file = /var/run/rsyncd.pid
log file = /var/log/rsyncd.log
use chroot = no

[tce]
  comment = tce directory backup
  path = /mnt/mmcblk0p2/tce
  read only = yes
  uid = tc

The config defines a module named tce. This means clients will see files under /mnt/mmcblk0p2/tce when they request rsync://box/tce. Since it is only intended for backup it is marked as read only.

Add as many modules for other locations as required.

Note

Remember to add etc/rsyncd.conf to /opt/.filetool.lst.

Start it on boot in bootlocal.sh:

# start rsyncd
rsync --daemon

Stop it on shutdown in shutdown.sh:

# stop rsyncd - location of pidfile from /etc/rsyncd.conf
pid=$(cat /var/run/rsyncd.pid)
if [ -n "$pid" ]; then
    kill "$pid"
fi

The Backup Destination

The actual backup task runs on the computer which is the backup destination. It will be set up so that it connects to a configured list of hosts (sources) via rsync.

The script retrieves a list of rsync modules from each host. If a specific KEYWORD (in this case: "backup") is part of the module name or comment, all files from that module will be included in the backup.

The last KEEP backups are kept, the oldest backup is removed.

Any host that is registered with the backup script can define any number of rsync modules and if they contain the keyword, they will be backed up. Hosts can also use the exclude option in a module definition to control which files are backed up.

On the destination this will result in a directory structure like this:

BASE/
    foo/           # backups from the "foo" host
        files.0/   # the most recent backup of "files"
            [...]
        files.1/   # an older version of "files"
            [...]
        files.2/
            [...]
        data.0/
            [...]
        data.1/
            [...]
        data.2/
            [...]
    bar/
        data.0/
        data.1/

Where we have two hosts "foo" and "bar". The "foo" hosts has two rsync modules included in the backup and we have collected three backups of each (0..2). The "bar" host has only one module of which two backups were collected.

Systemd Timer and Service

To control the periodic backup through systemd a .timer and .service file are needed.

The timer goes into /etc/systemd/system/pi-backup.timer:

[Unit]
Description=Timer for Rasperry Pi backup

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=default.target

And the service in /etc/systemd/system/pi-backup.service:

[Unit]
Description=Backup for Raspberry Pis

[Service]
ExecStart=/usr/local/bin/pi-backup.sh

Assuming that the backup script is located at /usr/local/bin/pi-backup.sh.

The timer is activated with:

$ sudo systemctl enable pi-backup.timer

Backups can be started manually with:

$ sudo systemctl start pi-backup.service

Backup Script

The complete script looks like this. The UPPERCASE variables at the top of the script are meant for configuration:

#!/bin/sh

# basedir for all backups
ROOT=/path/to/backups

# hosts to consider for backup
HOSTS=( foo bar )

# modules with KEYWORD in name or description are backed up
KEYWORD=backup

# the number of old backups to keep (not including the current backup)
KEEP=6


function main {
    for host in "${HOSTS[@]}"; do
        backup_host $host
    done
}

# fetch a list of modules
# filter all modules with "backup" in their description
# args: host
function backup_host {
    local host=$1
    # get a list of all modules for that host,
    # filter the ones containing "KEYWORD"
    # and keep only the module name
    rsync rsync://$host/ | grep "$KEYWORD" | cut -f1 | while read -r module; do
        backup_module $host $module
    done
}


# backup a single module
# args: host module
function backup_module {
    local host=$1
    local module=$2
    local basedst=$ROOT/$host/$module
    local dst=$basedst.0
    local lndst=../$module.1
    rotate $basedst
    echo Backup $host/$module to $dst
    mkdir -p $dst
    rsync --archive --delete --link-dest=$lndst rsync://$host/$module $dst
}


# drop the oldest backup,
# move other backups up one place (e.g. `backup.0` to `backup.1`)
# args: basedst
function rotate {
    local counter=$KEEP
    local dst=$1
    # going backwards: n, n-1, ... 0
    while [ $counter -ge 0 ]; do
        ahead=$(($counter+1))
        local current=$dst.$counter
        local older=$dst.$ahead

        if [ -d $current ]; then
            if [ $counter -eq $KEEP ]; then
                echo delete $current
                rm -r $current
            else
                echo move $current to $older
                mv $current $older
            fi
        fi

        counter=$(($counter-1))
    done
}

# run it
main

SQL Dumps

The Firefox sync server was installed using MariaDB as a database backend. We will perform logical backups of the database. That is, the backup takes the form of SQL statements which, when executed, restore the original data.

mysqldump [4] is used to dump the complete database into SQL statements.

The backup script for the ffsync database looks like this:

#!/bin/sh
dst=/mnt/storage/mysql/dumps/ffsync.dump
cfg=/opt/ffsync.db.cnf

mysqldump --defaults-extra-file=$cfg --lock-tables ffsync > $dst.temp

status=$?
if [ $status -eq 0 ]; then
    mv $dst.temp $dst
fi

exit $status

The --lock-tables option ensures that we retrieve a consistent state of the database. For InnoDB table types, --single-transaction is recommended to achieve that but table locking will work with any storage engine.

To avoid having the database password in the command line, a configuration file is used to keep credentials. It is passed with --defaults-extra-file.

The config file looks like this:

[mysqldump]
user=ffsync
password=<secret>

Set the permissions for the config file so that only the owner can read it and make it owned by the mysql user.

$ sudo chown mysql:nogroup /opt/ffsync.db.cnf
$ sudo chmod 600 /opt/ffsync.db.cnf

Both, script and config file are kept in /opt which means they should already be included in filetool.lst for backup and restore.

Finally, add the script to mysql's crontab:

$ sudo crontab -eu mysql
12 1 * * * /opt/backup-ffsync-db.sh

The backup will be executed with the user we created to run the mysqld service (mysql) so make sure that the backup destination is writable for that user.

To include the SQL dump in the filesystem backup, create an rsync module for it in rsyncd.conf:

...
[sqldumps]
  comment = SQL dumps backup
  path = /mnt/storage/mysql/dumps
  read only = yes
  uid = mysql

Cron

We have made a crontab entry but cron might not be enabled. To enable it, we must add the Tiny Core bootcode [5] "cron".

Bootcodes can be set in a file named cmdline.txt. the file is located in the boot partition, which is normally unmounted after boot. Mount it and edit the file:

$ sudo mount /dev/mmcblk0p1 /mnt/mmcblk0p1
$ sudo vi /mnt/mmcblk0p1/cmdline.txt
(append bootcode "cron")
$ sudo umount /mnt/mmcblk0p1

cmdline.txt contains a space separated list. Simply append " cron" to the end of that list.

Warning

With piCore 8.x and Raspberry Pi 3, the file is cmdline3.txt, not cmdline.txt. See http://forum.tinycorelinux.net/index.php?topic=20107.0

After a reboot, check if it worked. This should return the PID of the running crond daemon:

$ pgrep crond

Now make sure that crontabs are persisted across boots. Add the crontabs directory to /opt/.filetool.lst:

var/spool/cron/crontabs

Timezone

If you did not set it, piCore's time zone is UTC. And your crontab entry would refer to UTC as well.

This is a good opportunity to add another bootcode (tz) for setting the timezone.

The TZ bootcode [6] is a bit more involved it (not as complicated as it looks, though). For Central European Time the timezone is encoded like this:

tz=CET-1CEST,M3.5.0,M10.5.0/3
   |  | |    || | | ||  | | `- 03:00:00
   |  | |    || | | ||  | `--- Sunday (day 0)
   |  | |    || | | ||  `----- Last week (week 5)
   |  | |    || | | |`-------- October (month 10)
   |  | |    || | | `--------- End of DST in format m.w.d[/h]
   |  | |    || | `----------- Sunday (day 0)
   |  | |    || `------------- Last week (week 5)
   |  | |    |`--------------- March (month 3)
   |  | |    `---------------- Begin of DST in format m.w.d[/h]
   |  | `--------------------- TZ name during DST
   |  `----------------------- UTC offset w/o DST
   `-------------------------- TZ name w/o DST

Start and end times for DST are specified in the format:

Mm.w.d[/h]
 ^ ^ ^  ^
m: month, 1=January
w: week of the month (1=first, 5=last)
d: day of the week, 0=Sunday, 6=Monday
h: [optional] hour of the day, 3 for 03:00:00

If no time parameter is given for DST start or end time, 02:00:00 is assumed.

Add the parameter to cmdline[3].txt (for CET):

tz=CET-1CEST,M3.5.0,M10.5.0/3

Whether setting the timezone this way (or setting it at all) is a good idea is debatable. For me - living in a country with daylight savings time - it felt more comfortable having the same time on all computers.


[1] http://wiki.tinycorelinux.net/wiki:backup
[2] https://rsync.samba.org/
[3] https://download.samba.org/pub/rsync/rsyncd.conf.html
[4] https://mariadb.com/kb/en/mariadb/mysqldump/
[5] http://wiki.tinycorelinux.net/wiki:boot_codes_explained
[6] http://wiki.tinycorelinux.net/wiki:time_zone