Backup setup
| Server setup | |
| ← Previous | Next → |
| Log stats | PiHole |
I use Borg for taking backups of my two machines every night. It's controlled by a SystemD timer. The deduplication of backups allows many backups to be made in not much space. Borg allows easy recovery by allowing archives to be mounted as a drive.
The desktop and server machine each have two repositories. Each machine creates a backup locally and one on the other machine. I do it this way so there are no clashes with concurrent backups writing to the same repository from different machines.
Create users
Backups are safer if they use non-privileged users to do the backups. I create these users for the OS and databases.
OS
On each machine, create backup users. These will be used as the target of backups to remote machines.
Give these users login shells and home directories.
For each user, create an SSH key and transfer it to the other machine with ssh-copy-id. Once you've set up that both root and backup on each machine can SSH into backup on the other machine, you can disable the passwords for each backup user.
MySQL
For instance, create the backup user in MySQL. This user has read-only access to all databases, and does not require a password. Create the user by entering the following commands:
root@desktop:~# mysql -u 'root' -p mysql> grant select, lock tables, process on *.* to 'backup'@'localhost'; mysql> quit;
This automatically creates the user, with no password, and gives them permissions to read all MySQL databases.
Postgres
For Postgres, it's a bit more complicated. Create the user, connect to the database to back up, and grant permissions.
root@desktop:~# sudo -u postgres psql postgres=# create user backup with login encrypted password 'mypassword'; CREATE ROLE postgres=# \c dmarc psql (9.3.13, server 9.1.18) You are now connected to database "dmarc" as user "postgres". postgres=# grant connect on database dmarc to backup; GRANT postgres=# grant select on all tables in schema public to backup; GRANT postgres=# grant select on all sequences in schema public to backup; GRANT
Then create the file /root/.pgpass with contents
# hostname:port:database:username:password *:*:*:backup:mypassword
- (giving the password you've used) and give it the correct permissions:
root@desktop:~# chmod 0600 ~/.pgpass
Install Borg
I currently use Borg 1.26. It needs a library to support mounting backups via FUSE.
root@desktop:~# apt install borgbackup python3-llfuse
Creating archives
As the backup user, create two repositories on each machine, one for each machine being backed up. Remember to check and write down the repository encryption keys!
root@desktop:~# mkdir -p /backup/from_desktop root@desktop:~# mkdir -p /backup/from_server root@desktop:~# sudo -u backup borg init --encryption=repokey /backup/from_desktop root@desktop:~# sudo -u backup borg init --encryption=repokey /backup/from_server
Do the same on the server, creating the two repositories.
On each machine, create the file /etc/borg/backup_patterns that defines what files are backed up. This allows the same files to be backed up to each destination.
P sh R /home/ - /home/*/.cache - /home/*/TresoritDrive - /home/*/.kde/share/apps/nepomuk - /home/*/.thunderbird - /home/*/.local/share - /home/*/.rvm - /home/*/.vagrant.d - /home/*/anaconda3 - /home/*/.ghcup - /home/**/machine-learning-datasets R /opt/photos R /opt/music R /opt/sources R /etc R /lib/systemd R /var/www
To do the backups from the desktop, create the files /etc/borg/backup_to_desktop and /etc/borg/backup_to_server. Below is the content of /etc/borg/backup_to_desktop. /etc/borg/backup_to_server is identical except for the line at the top which becomes export BORG_REPO=backup@server:/backup/from_desktop and the appropriate passphrase.
#!/bin/sh
# the repo and its passphrase
export BORG_REPO=/backup/from_desktop
export BORG_PASSPHRASE='long complex passphrase'
# some helpers and error handling:
info() { printf "\n%s %s\n\n" "$( date )" "$*" >&2; }
trap 'echo $( date ) Backup interrupted >&2; exit 2' INT TERM
info "Starting backup"
# backup the directories
borg create \
--verbose --filter AME \
--list --stats --show-rc \
--one-file-system \
--compression zstd,22 --exclude-caches \
--patterns-from /etc/borg/backup_patterns \
::'{hostname}-daily-{now}' 2>&1
backup_exit=$?
info "Pruning repository"
# prune the repo
borg prune \
--list \
--prefix '{hostname}-daily-' \
--show-rc \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 24 2>&1
prune_exit=$?
# Compact the repo, for when version 1.2 is available
# borg compact
# compact_exit=$?
# use highest exit code as exit code
global_exit=$(( backup_exit > prune_exit ? backup_exit : prune_exit ))
# global_exit=$(( compact_exit > global_exit ? compact_exit : global_exit ))
if [ ${global_exit} -eq 1 ];
then
info "Backup, Prune, or Compact finished with a warning"
fi
if [ ${global_exit} -gt 1 ];
then
info "Backup, Prune, or Compact finished with an error"
fi
exit ${global_exit}
Backing up the server requires an extra step, as I want to create database dumps that are backed up first. That means there are four Bash scripts in /etc/borg: backup_starter coordinates the backup. It calls create_db_backups to dump the databases, then the two backup_to_server and backup_to_desktop scripts.
backup_starter is simple:
#!/bin/bash /etc/borg/create_db_backups /etc/borg/backup_to_server & /etc/borg/backup_to_desktop & wait
create_db_backups does the database dumps, as well as creating a list of manually-installed packages.
#!/bin/bash
export DB_BACKUP_DIR=/backup/db_backups
rm ${DB_BACKUP_DIR}/* || true
(
nice comm -23 \
<(aptitude search '~i !~M' -F '%p' | sed "s/ *$//" | sort -u) \
<(gzip -dc /var/log/installer/initial-status.gz | sed -n 's/^Package: //p' | sort -u) \
> "${DB_BACKUP_DIR}/manually-installed-packages.txt"
) &
# Create database backups
for db in thisdb thatdb ; do
(
nice mysqldump --user='backup' --opt --databases $db > "${DB_BACKUP_DIR}/${db}-dump.sql"
) &
done
(
pg_dump -h 127.0.0.1 -U backup -Fc dmarc > "${DB_BACKUP_DIR}/dmarc-dump"
) &
for wiki in this_wiki that_wiki ; do
(
php /usr/share/mediawiki/maintenance/dumpBackup.php --full --quiet \
--conf /var/www/${wiki}/mediawiki/LocalSettings.php \
> "${DB_BACKUP_DIR}/${wiki}.dump.xml"
) &
done
wait
backup_to_server and backup_to_desktop are as above, but include the /backup/db_backups directory in the patterns file.
All the Bash scripts need to be executable.
Services and timers
The backups are run by SystemD services and timers. On the server, create /lib/systemd/system/borg_backup.service and /lib/systemd/system/borg_backup.timer.
/lib/systemd/system/borg_backup.service
[Unit] Description=Borg Backup to server and desktop [Service] Type=simple Nice=19 IOSchedulingClass=2 IOSchedulingPriority=7 ExecStart=/etc/borg/backup_starter # User=backup # Group=backup
/lib/systemd/system/borg_backup.timer
[Unit] Description=Borg Backup Timer [Timer] WakeSystem=false OnCalendar=*-*-* 1:00:00 RandomizedDelaySec=10min [Install] WantedBy=timers.target
The desktop machine has two service and timer files, one for each backup script: the backup to the desktop and backup to the server run as separate services.
Once you've created the service and timer files, tell SystemD about them and start the timers.
root@desktop:~# systemctl daemon-reload root@desktop:~# systemctl enable borg_backup_to_desktop.timer root@desktop:~# systemctl restart timers.target
Restoring files
The easiest way is to use borg list to view the backups in each repository, and borg mount to mount the repository as a filesystem. You can then extract the files you want fairly easily.
See also
- Borg documentation
- Andrew Sullivan's guide to Borg
- Andrew Keech's guide to Borg
- Borgmatic may be an interesting tool, if I want something more configurable.