Turn the HDD on and off (physically) for borg backups (or any other task)
Wow, another project where I got so occupied with the question if I can do it that I forgot about the question if a simple 100€ purchase could not solve it in a fraction of the time. But that's how you learn new things.
There is really no upper end when people talk about their personal backup strategy. RAIDed, rendundant on- and off-site backups, encrypted, in the cloud... While a lot of people have no backup whatsover, others set up an infrastructure like some national archive.
While it is easy to copy your most important folders to some USB stick every once in a while, it is a complex thing to have an automated, reliable and resilient backup plan for everything.
This post is about the worst of both worlds: A pretty complicated way to have a mediocre backup. It still might be of interest for anyone who wants to programmatically turn electrical devices on and off in their home network, so I will explain in details what I did.

What I wanted
Periodically, my laptop and a server somewhere in the internet should be backed up to a USB hard drive that is connected to a SBC (a ten year old Orange Pi Plus 2) running Armbian, sitting in a closet in my home.
How I did it
I installed borgbackup on my backup server, installed Pika Backup on my laptop and borgmatic on my internet server. Borgmatic's configuration file is yaml (what I hate), but is so heavily commented that it was easy even for me to configure a nightly backup. Pika is even easier to set up. Done!
Why I was not done
While the server itself only consumes 4-5 watts, the USB hard drive consumes 10-12 watts on top of that - continuously. It does not go idle, not automatically and not with tools like hdparm or hd-idle.
Having a server running on 14-17 watts 24/7 was not acceptable for me. But the hard drive has a manual power switch. Would it be possible to control that switch via software?
How I connect my USB HDD to power with shell commands
The Orange Pi Plus 2 has a Raspberry Pi-compatible set of GPIO pins. I got myself a 3.3 Volt-compatible relay and hooked it up to the GPIO pins.
I opened the USB drive enclosure and soldered two wires to the pins where the hardware switch is connected. When I touched the cables, the hard drive would turn on. Looks good! I connected the cables to the other side of the relay and was done on the hardware side of things.

On to the software part! To use the GPIO pin 18 for the relay trigger signal. Pin 18 translates to GPIO 110 somehow, so this is how you would turn on the relay and the connected USB drive on a Linux shell:
sudo echo 110 > /sys/class/gpio/export
sudo echo out > /sys/class/gpio/gpio110/direction
echo 1 > /sys/class/gpio/gpio110/value # turn on
echo 1 > /sys/class/gpio/gpio110/value # turn offSo far so good, but A) I want to have the GPIO pin ready without doing the "echo 11" and "echo out" stuff after every reboot and B) I want my "borg" user that I use for the backup to be able to turn the drive on and off, so the permissions to write to /sys/class/gpio/gpio110/value must be set accordingly:
To enable pin 17 as OUT pin on boot, create a small script in /usr/local/bin/gpio-setup.sh:
#!/bin/bash
echo 110 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio110/direction
chown borg:gpio /sys/class/gpio/gpio110/value # not sure if this really does somethingNow this script must be executed during boot. Create a new file /etc/systemd/system/gpio-setup.service:
[Unit]
Description=GPIO Pin Setup
After=systemd-udevd.service
Wants=systemd-udevd.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/gpio-setup.sh
RemainAfterExit=yes
User=root
[Install]
WantedBy=multi-user.targetNow the GPIO pin is always ready! But only root can write to it :/ To change that, create a new user group "gpio" and add the "borg" user to it:
sudo groupadd -r gpio
sudo usermod -a -G gpio borgAdd the "gpio" group to the gpio device files. Create a file /etc/udev/rules.d/99-gpio.rules:
KERNEL=="gpiochip*", GROUP="gpio"Done!
The only thing left to do is to run a small bash script that turns on and mounts the drive before the backup and turns it off afterwards. I did this in /home/borg/drive-control.sh:
#!/bin/bash
# Lock file for backup synchronization
LOCK_FILE="/tmp/nas-backup.lock"
# GPIO path for the USB drive power control
GPIO_PATH="/sys/class/gpio/gpio110/value"
# Logging function
# Syslog logging function
log() {
    logger -t nas-backup -p local0.info "$*"
    echo "$*"
}
# Error logging function
log_error() {
    logger -t nas-backup -p local0.err "$*"
    echo "$*"
}
# Main script with flock protection
(
    # Try to acquire an exclusive, non-blocking lock
    flock -n 9 || {
        log "Backup operation already in progress. Exiting."
        exit 1
    }
    # Log the start of the operation
    log "Backup operation started with mode: $1"
    if [ "$1" == "pre" ]; then
        # Pre-backup script: Power on the drive
        log "Powering on USB drive and wait 30 seconds"
        echo 1 > "$GPIO_PATH"
        # Wait for drive to spin up and become ready
        sleep 30
        # Mount the backup drive
        log "Mounting backup drive"
        mount /mnt/backup || {
            log_error "Failed to mount backup drive. Powering off drive."
            # Power off the drive in case of mount failure
            echo 0 > "$GPIO_PATH"
            exit 1
        }
        log "Pre-backup setup complete"
    elif [ "$1" == "post" ]; then
        log "Unmounting backup drive"
        if umount /mnt/backup; then
                # Umount was successful, now power off the drive
                log "Powering off USB drive"
                echo 0 > "$GPIO_PATH"
                log "Post-backup cleanup complete"
                exit 0
        else
                log_error "Warning: Failed to unmount backup drive cleanly"
                exit 1
        fi
    else
        echo "Invalid operation mode. Use 'pre' or 'post'."
        exit 1
    fi
) 9>"$LOCK_FILE"I included a simple locking mechanism in case my laptop and my cloud server would try to backup at the same time (unlikely but not impossible, since the cloud server is set to backup at 4am).
Borgmatic makes it easy to run this kind of pre- and post-backup script. In Borgmatic's config.yml (version 1.5, version 2.0+ does it differently):
hooks:
    before_everything:
      - ssh borg@backup "/home/borg/drive-control.sh pre"
    after_everything:
      - ssh borg@backup "/home/borg/drive-control.sh post"With Pika Backup the solution is not so easy, unfortunately. Pika can be configured to run a pre- and post-backup command - but after the backup, Pika can run additional commands to remove old backups etc. So there is no after_everything hook, mor like a after_backup hook.
I tried to solve the issue by adding a "post everything" command to Pika. You can download a Flatpak bundle of my fork of Pika with the added command from my Github.

This was my first contact with the Gnome developer infrastructure (most importantly GNOME Builder) and I am very impressed by the streamlined workflow they have. Adding the additional command was really easy and building the Flatpak bundle was doable for me. Still, it did not work super reliably for me. Instead of using my fork, I would recommend to stick to the standard "post backup" command and modify the drive-control.sh script by maybe waiting a couple of minutes, checking if no process accesses the USB drive and umount it then.
And that's how it's done!
And that's how you frankenstein together an outdated SBC, a USB drive with a stupid always-on controller and Borg backup :)
There is something really satisfying in hearing the click sound of the relay and the up-spinning of the drive when a backup starts. Even more when you connect the backup server to a power socket measure and seeing the consumption going up and down again.
A tip: Now that your drive is off most of the time, you might want to not put it on a sponge or another kind of foam to silence it. TIL putting a HDD on soft material is not a good idea. And indeed, I had random I/O errors and now they are gone, although I still have to verify that on the long term.

- ← Previous
 A webapp without frameworks