The Aim of this Project
- do nightly backups to a second hard drive
- the backup drive should be disconnected and powered off except when performing backups to save electricity
- 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
This script will do the following:
- Power on the hard drive by switching all the data bits on the parallel port to “1” /home/uprava/backup/lpt 255
- Wait 60 seconds for the system to detect the newly connected drive.
- 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
- Mount the hard drive mount /mnt/backup
- 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
- 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/
- 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.
- Unmount the drive
- Power off the drive
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