Fully Automated Offline Backup for under 10 Euros

A quick and dirty hack that enables you to do nightly backups to an offline hard drive, connected only when we’re doing the backup

The Aim of this Project

  1. do nightly backups to a second hard drive
  2. the backup drive should be disconnected and powered off except when performing backups to save electricity
  3. powering up of the hard drive and the whole backup process must not require human intervention

1. This is self-explanatory.

2. While I was trying to work out how to do this, this second issue was a little harder to crack, because although most operating systems support hot swapping of SATA hard drives, this generally doesn’t work if the drive is permanently connected via the SATA connection and we just switch off the power. Making a relay switch which would disconnect both the SATA cable and the power cable would be sometwhat expensive, because of the number of relays needed (if we just switch off the power, one relay is enough).

In the end, I decided to connect the SATA hard drive to the computer through a SATA-to-USB adapter, while a switching circute would cut off the drive’s power. If the drive is permanently connected to the computer through this adapter, when we power up the drive, the system will easily detect the drive. This is the dmesg output of one such event:

kernel: [261005.130086] usb 1-8: new high speed USB device using ehci_hcd and address 13
kernel: [261005.282437] usb 1-8: configuration #1 chosen from 1 choice
kernel: [261005.285433] scsi11 : SCSI emulation for USB Mass Storage devices
kernel: [261010.281628] scsi 11:0:0:0: Direct-Access     ST325082 4AS            L    PQ: 0 ANSI: 2 CCS
kernel: [261010.285138] sd 11:0:0:0: Attached scsi generic sg1 type 0
kernel: [261010.287471] sd 11:0:0:0: [sdb] 488397168 512-byte logical blocks: (250 GB/232 GiB)
kernel: [261010.288412] sd 11:0:0:0: [sdb] Write Protect is off
kernel: [261010.296302]  sdb: sdb1
kernel: [261010.317085] sd 11:0:0:0: [sdb] Attached SCSI disk

When we power down the drive, USB will recognize an ’empty’ adapter, which will refresh once we power the drive back up, and this serves our purpose. USB-to-SATA adapters are sold on eBay for just a few dollars.

3. Solving the third item required some knowledge of electronics. Parallel ports are very suitable if you want to control relays, because the pins can be easily set to 1 or 0, and will remain in that state until you send another byte (if we were to use the serial port, we would have to continuously send 1’s to keep it powered on). Luckily, Linux allows us to easily write directly to parallel port, and we have eight data lines (bits) at our disposal.

You can find a pinout of the parallel port here, and if you need a conversion table between DEC and BIN, check this out (you’ll need this if you decide to control more than one relay through the parallel port) .

I’ve prepared a schematic of the circuit which switches off the power on a SATA power connector and is controlled via the parallel port. You can download a schematic / PCB layout here. Once finished, you should connect the circuit to any of the data pins on the parallel port connector, and we will switch all the bits to 1 or 0.

That’s our hardware part done, and we can start programming.

Parallel port control

Here, we will use a simple programme written in C, which takes a DEC value as an argument and sends the byte directly to the parallel port. Source code is available  here.
If you’re compiling it on newer distros, you’ll have to correct one of the #includes, from #include <asm/io.h> to #include <sys/io.h>
The source with these changes can be found here: lptout.c
Grab the source and compile it.

wget https://sh.com.hr/wp-content/uploads/2010/07/lptout.c
gcc lptout.c

This will give you an executable called  a.out.
We’ll rename it and change permissions.

mv a.out lpt
chmod +x lpt

If your switching circuit is connected to the computer, you can check to see if everything is working as it should by sending the following commands to switch the relay on or off (you should be in the same directory as the executable).

sudo ./lpt 255
sudo ./lpt 0

Shell script

This script will do the following:

  1. Power on the hard drive by switching all the data bits on the parallel port to “1” /home/uprava/backup/lpt 255
  2. Wait 60 seconds for the system to detect the newly connected drive.
  3. Run fsck because it needs to be done every once in a while – the system will refuse to mount an unchecked drive ($dev is the variable with the device name, for instance /dev/sdb1)
    /sbin/e2fsck -p $dev
  4. Mount the hard drive mount /mnt/backup
  5. Prepare additional backup files, a list of installed packages and dump all MySQL
    databases.dpkg --get-selections > /home/uprava/packages-installed.txt
    DBS="$(mysql -u root -h localhost -pPASSWORD -Bse 'show databases')"
    for db in $DBS
    do
    mysqldump -u root -h localhost -pPASSWORD $db | gzip -9 > /home/uprava/mysql-$db.gz
    done

     

  6. Perform the backup, I like to use rsync to make a mirror copy of the folders and delete any deleted files.
    rsync -ahv –delete –ignore-errors –exclude-from ‘exclude’ /home /mnt/backup/
  7. Check disk space usage and include it in the backup log file. There’s a nice script that does that here. In my setup, the main backup script would simply run this one.
  8. Unmount the drive
    umount /mnt/backup
  9. Power off the drive
    /home/uprava/backup/lpt 0

cron job

We must also add a cron job to run this script every day. The script must be run as ‘root’ . Open up the root’s crontab…
sudo crontab -e
Add a line similar to this one, with a full path to your backup script. I run my backup every night at 4:18 a.m.
18 4 * * * /home/uprava/backup/backup.sh

The finished script

In this shell script, we’re assuming that you’ve predefined the mount parameters for your backup hard drive in /etc/fstab. The script is well commented and it shouldn’t be hard to find your way through it. The script logs or stdouts everything it does, so troubleshooting should also be easy.

#!/bin/bash
# Offline Backup Script
# By: Ivan Biuklija
# v. 1.4
# Setup

mnt="/mnt/backup" # mount point for the hard drive
dev="/dev/sdb1" # device to mount
lptout="/home/uprava/pport/lpt" # path to the compiled lptout.c

# Setup ends here

$lptout 255 # Power on the hard drive
echo -e "Drive powered on \n"
sleep 60 # Wait 60 seconds for the system to detect the USB hard drive.

# FSCK, mount

if [ -e $dev ]; then
echo -e "FSCK it up! \n"
/sbin/e2fsck -p $dev
echo -e "Drive found, attempting to mount... \n"
mount $mnt
else
echo -e "Drive not found! Aborting."
exit 1
fi

# Let's check if the drive is mounted

if [ -d $mnt/lost+found ]; then
echo -e "Drive successfully mounted."
else
echo -e "Error: Drive not mounted. Aborting."
exit 1
fi

# dump a list of all installed packages

apt-cache pkgnames > /home/uprava/pkgnames.txt
dpkg --get-selections > /home/uprava/installed-packages.txt

# dump all MySQL databases

DBS="$(mysql -u root -h localhost -pPASSWORD -Bse 'show databases')"
for db in $DBS
do
mysqldump -u root -h localhost -pPASSWORD $db | gzip -9 > /home/uprava/mysql-$db.gz
done

# Backup any directories you want, this is just an example.
rsync -ahv --delete --ignore-errors --exclude-from '/home/uprava/backup/exclude' /home /mnt/backup/system
rsync -avh --delete --ignore-errors --exclude-from '/home/uprava/backup/exclude' /etc /mnt/backup/system
rsync -ahv --delete --ignore-errors --exclude-from '/home/uprava/backup/exclude' /root /mnt/backup/system
rsync -avh --delete --ignore-errors '/mnt/media/music' /mnt/backup/media

# Backup done. Let's check the free disk space.
echo -e "\nBackup done. Free disk space is: \n"
/home/uprava/backup/space.sh

# Unmounting the drive

echo -e "\nUnmounting the drive... \n"
umount $mnt

# We'll wait some time for the process to finish

sleep 2

# Checking if the drive is unmounted, then powering it down

if [ -d $mnt/lost+found ]; then
echo -e "\nDrive is not unmounted! Aborting."
exit 1
else
echo -e "The drive is unmounted. Powering it off.\n"
$lptout 0
fi

# Waiting for the USB to recover and detect an 'empty' adapter

sleep 30

# Checking to see if the drive is powered down

if [ -e $dev ]; then
echo -e "Error: the drive is still powered on! \n"
exit 1
else
echo -e "\nThe drive is powered off. Have a nice day!"
exit 0
fi
exit 0

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.