Systemd for Simple Backup

Updated: Apr 22, 2023

Hi,

Nice to get back, Recently switched to F15. Wow!! my userland changed heavily. Previously, its very simple things like,

  • SysVinit for booting

  • Udev for devices

  • pppconfig for Network

  • Xfce for GUI

  • ALSA for sound

  • ffmpeg & mplayer for video

Now,

  • Systemd for booting, daemons

  • Udev, DBus, UDisks for devices

  • ModemManager+NetworkManager for Network

  • Gnome3 for GUI

  • PulseAudio+ALSA for sound

  • GStreamer+totem for video

These Technologies are very interesting to learn. I’m not going to explain about each. But, I went through systemd and came up with a solution for my backup problem.

Backup Problem

While in its last phase, my previous laptop teached me the importance of external backup disks. So, I bought an external 512GB Segate, and backed-up /home/${HOME} tree. It helped me to quickly get back my files to F15. However, one problem is, maintaining my backup. I thought If I plug my external drive, someone should automatically copy all the new files resides in my current /home/${HOME} in F15 to that drive. There are lot of ways to do it. I can think of two main ways,

  • Write an udev rule to call a script which will mount that external backup partition and rsync /home/${HOME} in F15 to that external partition. Pros: Fairly straight forward, udevrulefile+rsyncscript will do it. Cons: No control, this will copy every time udev detects that external drive. If there is lot of files to copy, then this will make a mess.

  • Use systemd to call a script whenever that external backup partition gets mounted. Pros: You have full control, create a backup.service for systemd, create backup.bash to rsync /home/${HOME} to backup disk. Enable that service in systemd to automatically do rsync, or just load that service to systemd and only start that service If you want to backup your files. Cons: Need to pass one more layer to run the actual rsync script.

So, I took-up systemd

There are lot of ways you can trigger your service in systemd. Also, you can depend on another systemd unit to trigger your service. In this case, I depend on a mount unit to trigger my backup.service. This service, in-turn, will trigger backup.sh script.

To define a systemd unit, you need to know what type of unit you want to create. Currently there are 10 types of units systemd can understand [read systemd.unit(5)]. For backup job, I used two type units, one is systemd.mount(5) amd systemd.service(5). If you go through systemd.mount(5) you will understand that systemd will automatically load this units whenever systemd saw a block device. So, systemd will automatically provide media-ExternalBackupPartition.mount whenever I insert my external hard disk . I only need to define the next unit, backup.service for systemd.

Note

There is a story behind the name media-ExternalBackupPartition. I’ll tell you at the end of this post. Lets just continue with systemd for now.

To define a unit for systemd, you need to create a file as /etc/systemd/system/name.unit; (for my backup problem, unit file is /etc/systemd/system/backup.service). Here name, may be anything relevent to your job and unit must be one of service, socket, device, mount, automount, swap, target, path, timer and snapshot. Read systemd.unit(5) man pages for more precise information.

Unit Class

Unit definition files contains information in .ini format. One of the class [Unit] must exist in every unit file. Here is the [Unit] class for backup.service

[Unit]
Description='sync my files with external backup drive'
Requires=media-ExternalBackupPartition.mount
After=media-ExternalBackupPartition.mount

Here Description is a general description for your service. We need to put the required unit which will trigger this new unit in Requires field. Systemd will run this unit once all the units in Requires fields satisfied. We can call these Requires units as parent units, and your unit as child. However, systemd will not wait for parent units to complete to run child unit. We need to explicitly ask systemd to wait for parent units to complete, For this purpose we have After field. If you define which parent unit needs to be completed before your child unit could run, you need to mention it in After field.

For my backup.service child unit, media-ExternalBackupPartition.mount unit must be satisfied in systemd. That means, my external HD partition must be mounted inside /media/ExternalBackupPartition path. Also, Using After= field, I instructed systemd, not to start this child unit before mount-ExternalBackupPartition.mount finishes.

Service Class

Now we need to define what to do once the requirements satisfies. For that purpose, we need to define [Service] class, Here is the service class for backup.service,

[Service]
Type=simple
ExecStart=/home/mohan/Development/scripts/backup.sh

This [Service] class is specific to systemd.service units. Here, ExecStart=/home/mohan/Development/scripts/backup.sh asks systemd to run backup.sh whenever backup.service satisfies. You can do lot of customization to setup the execution environment before start running any commands, such as log redirection, demonizing etc., there are lot of fields to use in [Service] class, but I simply used Type=simple to tell systemd, that no need to do any change in execution environment. The script backup.sh will take care of all the redirection within itself.

Install Class

In SysV init system, we use chkconfig (redhat/fedora) or update-rc.d (debian) to enable or disable a service. In systemd, we use this [Install] class to enable or disable our backup.service unit so that it will work even after a restart. Here is install class for backup.service,

[Install]
WantedBy=media-ExternalBackupPartition.mount

In systemd, enabling a service means, adding a symlink to /etc/systemd/system/name.service.wants/ directory. disabling a service means, removing that symlink. systemctl command can do this add/remove symlink automatically when we call it with systemctl enable backup.service or systemctl disable backup.service, but we need to say the parent unit name, thats why we have this [Install] class. Simply, we need to mention that parent unit in WantedBy= field.

Execution

Once the unit files are ready, we need to enable them into systemd. For backup.service, I executed following commands to setup the service

$ sudo cp ~/backup.service /etc/systemd/system/backup.service
$ sudo systemctl daemon-reload

I just copied backup.service unit file to systemd’s location and asked systemd to reload unit definitions. Now we can check if things loaded properly or not using following command,

$ sudo systemctl status backup.service
backup.service - 'sync my files with external backup drive'
          Loaded: loaded (/etc/systemd/system/backup.service)
          Active: inactive (dead)
          CGroup: name=systemd:/system/backup.service
$

systemd will say Loaded : error if it can’t understand any defnintion in backup.service or if it can’t satisfy the definitions. Otherwise we can start this service using following command, we need to make sure the final rsync script backup.sh exists in the location pointed by ExecStart= field.

$ sudo systemctl start backup.service

This will start syncing new files to External backup HD drive, only when It is plugged-in and mounted. Otherwise, the service will fail. you can check the status again using systemctl status. Once you checked that the service is working as intended, we can enable this service (I mean, creating symlinks) using following command,

$ sudo systemctl enable backup.service

We can verify the symlink as below to make sure parent-child linking is done correctly.

$ ls -l /etc/systemd/system/media-ExternalBackupPartition.mount.wants/baskup.service
lrwxrwxrwx 1 root root 34 Nov  9 02:08 /etc/systemd/system/media-ExternalBackupPartition.mount.wants/backup.service -> /etc/systemd/system/backup.service

If we don’t want to copy automatically, we can disable it using below command,

$ sudo systemctl disable backup.service

Even If the service is disabled, you can start/stop the service. Systemd will recognize the backup.service, check it’s dependencies and execute backup.sh correctly.

media-ExternalBackupPartition.mount

As I already said, systemd will automatically create units using udev, so when my external HD plugs-in, udev will tell to udisks that a new partition is available, then udisks will mount that partition inside /media/uuid location, then systemd will create a unit as media-uuid.mount (systemd uses - instead of / for path seperation). But specifying Requires=media-uuid.mount inside backup.service file is not working. Thus, I used a simple udev rule to rename udisk’s mount path, here is the rule file,

$ cat 99-rename-udisk-mountpoint.rules
ENV{ID_FS_UUID}=="251c683d-bce0-489c-aab5-f684a9a1f3b2",ENV{ID_FS_UUID}="ExternalBackupPartition"
$ sudo cp ~/99-rename-udisk-mountpoint.rules /etc/udev/rules.d

This above two commands, will modify udisk’s mount path to /media/ExternalBackupPartition instead of /media/251c683d-bce0-489c-aab5-f684a9a1f3b2, thus systemd will automatically create media-ExternalBackupPartition.mount instead of media-251c683d-bce0-489c-aab5-f684a9a1f3b2.mount

Finally, Thanks for reaching this line. I hope this long boring article will help you to understand something about systemd.