Do Something on Plug-in of USB Drive

Linux is able to identify a device when something is plugged into an already-running system and then do something in response. Part of this is done with Udev which supplies a dynamic device directory containing only the nodes for devices which are connected to the system. It creates or removes the device node files in the /dev directory as they are plugged in or taken out.

Using this I am able to backup a Linux system to an external hard drive just by plugging in the USB drive. When the drive is connected Linux will detect it, mount it, and start a backup. Once the backup is done the drive will remain mounted so I can review the files before removing the drive.

The process goes something like this: I plug in the USB drive, the system will detect it and mount the drive. A Udev rule will fire off a small script creating a job using “AT” to start the backup script one minute later. AT will start the backup script which will create a directory on the USB drive and backup files to that directory. I then review the backup, unmount the drive, then unplug it.

Udev rules are stored in these two locations:

joey@HAVEN-E6520:~$ ls /usr/lib/udev/rules.d
total 940K
-rw-r--r-- 1 root root  947 Nov 30  2019 39-usbmuxd.rules
-rw-r--r-- 1 root root  998 Jan 20  2019 40-usb-media-players.rules
-rw-r--r-- 1 root root  42K Feb 24  2020 40-usb_modeswitch.rules
-rw-r--r-- 1 root root  655 Aug 31 11:27 40-vm-hotadd.rules
-rw-r--r-- 1 root root  210 Feb 16  2022 50-firmware.rules
-rw-r--r-- 1 root root 3.9K Aug 31 11:27 50-udev-default.rules
-rw-r--r-- 1 root root 7.2K Feb 13  2020 55-dm.rules
-rw-r--r-- 1 root root 1.5K Apr  6  2020 56-dm-parts.rules
-rw-r--r-- 1 root root  921 Sep 13  2021 56-hpmud.rules
-rw-r--r-- 1 root root 2.4K Feb 13  2020 56-lvm.rules
-rw-r--r-- 1 root root 8.4K Aug 31 11:27 60-autosuspend-chromiumos.rules
-rw-r--r-- 1 root root  703 Apr  1  2020 60-block.rules
-rw-r--r-- 1 root root 1.1K Apr  1  2020 60-cdrom_id.rules
-rw-r--r-- 1 root root   69 Jan 17  2017 60-crda.rules
-rw-r--r-- 1 root root  413 Apr  1  2020 60-drm.rules
-rw-r--r-- 1 root root  990 Apr  1  2020 60-evdev.rules
-rw-r--r-- 1 root root  394 Apr  1  2020 60-fido-id.rules
-rw-r--r-- 1 root root  329 Jan  8  2016 60-inputattach.rules
-rw-r--r-- 1 root root  282 Apr  1  2020 60-input-id.rules
-rw-r--r-- 1 root root 7.2K Jun 15  2020 60-libgphoto2-6.rules
-rw-r--r-- 1 root root 3.5K Sep 17  2020 60-libsane.rules
-rw-r--r-- 1 root root  201 Jul 25  2016 60-openobex.rules
-rw-r--r-- 1 root root  912 Feb 10  2020 60-pcmcia.rules
-rw-r--r-- 1 root root  616 Apr  1  2020 60-persistent-alsa.rules
-rw-r--r-- 1 root root 2.7K Apr  1  2020 60-persistent-input.rules
-rw-r--r-- 1 root root 1.8K Feb 13  2020 60-persistent-storage-dm.rules
-rw-r--r-- 1 root root 7.6K Aug 31 11:27 60-persistent-storage.rules
-rw-r--r-- 1 root root 2.1K Apr  1  2020 60-persistent-storage-tape.rules
-rw-r--r-- 1 root root  769 Apr  1  2020 60-persistent-v4l.rules
-rw-r--r-- 1 root root  736 Apr  1  2020 60-sensor.rules
-rw-r--r-- 1 root root 1.2K Apr  1  2020 60-serial.rules
-rw-r--r-- 1 root root  211 Dec  3  2019 60-tpm-udev.rules
-rw-r--r-- 1 root root  181 Mar  6  2022 60-virtualbox-dkms.rules
-rw-r--r-- 1 root root  454 May 11  2021 60-virtualbox.rules
-rw-r--r-- 1 root root  565 Apr  1  2020 61-autosuspend-manual.rules
-rw-r--r-- 1 root root  292 Apr 29  2020 61-gnome-settings-daemon-rfkill.rules
-rw-r--r-- 1 root root  292 Aug 21  2021 61-mate-settings-daemon-rfkill.rules
-rw-r--r-- 1 root root  456 Aug 31 11:27 61-persistent-storage-android.rules
-rw-r--r-- 1 root root  387 Feb 15  2020 64-btrfs-dm.rules
-rw-r--r-- 1 root root  612 Aug 31 11:27 64-btrfs.rules
-rw-r--r-- 1 root root  257 Jul  6 09:53 64-xorg-xkb.rules
-rw-r--r-- 1 root root  75K Apr 21  2021 65-libwacom.rules
-rw-r--r-- 1 root root  190 Jun  5  2020 66-snapd-autoimport.rules
-rw-r--r-- 1 root root 1.2K Apr  6  2020 68-del-part-nodes.rules
-rw-r--r-- 1 root root 4.9K Mar 20  2020 69-cd-sensors.rules
-rw-r--r-- 1 root root 225K Mar 26  2020 69-libmtp.rules
-rw-r--r-- 1 root root 6.5K Feb 13  2020 69-lvm-metad.rules
-rw-r--r-- 1 root root 1.2K Mar 11  2020 69-wacom.rules
-rw-r--r-- 1 root root  432 Apr  1  2020 70-joystick.rules
-rw-r--r-- 1 root root  734 Apr  1  2020 70-mouse.rules
-rw-r--r-- 1 root root  172 Feb 21  2019 70-pcspkr-beep.rules
-rw-r--r-- 1 root root  568 Apr  1  2020 70-power-switch.rules
-rw-r--r-- 1 root root  429 Nov  3  2020 70-printers.rules
-rw-r--r-- 1 root root  473 Apr  1  2020 70-touchpad.rules
-rw-r--r-- 1 root root 3.9K Jul 13  2019 70-u2f.rules
-rw-r--r-- 1 root root 2.8K Aug 31 11:27 70-uaccess.rules
-rw-r--r-- 1 root root  461 Aug 31 11:27 71-power-switch-proliant.rules
-rw-r--r-- 1 root root 3.7K Aug 31 11:27 71-seat.rules
-rw-r--r-- 1 root root  437 Mar 16  2022 71-u-d-c-gpu-detection.rules
-rw-r--r-- 1 root root  636 Aug 31 11:27 73-seat-late.rules
-rw-r--r-- 1 root root  969 Aug 30 04:40 73-special-net-names.rules
-rw-r--r-- 1 root root  452 Apr  1  2020 75-net-description.rules
-rw-r--r-- 1 root root  174 Apr  1  2020 75-probe_mtd.rules
-rw-r--r-- 1 root root  936 Apr  8  2022 77-mm-broadmobi-port-types.rules
-rw-r--r-- 1 root root 3.7K Apr  8  2022 77-mm-cinterion-port-types.rules
-rw-r--r-- 1 root root 1.8K Apr  8  2022 77-mm-dell-port-types.rules
-rw-r--r-- 1 root root  866 Apr  8  2022 77-mm-dlink-port-types.rules
-rw-r--r-- 1 root root 7.9K Apr  8  2022 77-mm-ericsson-mbm.rules
-rw-r--r-- 1 root root 3.1K Apr  8  2022 77-mm-fibocom-port-types.rules
-rw-r--r-- 1 root root 1.6K Apr  8  2022 77-mm-foxconn-port-types.rules
-rw-r--r-- 1 root root  907 Apr  8  2022 77-mm-gosuncn-port-types.rules
-rw-r--r-- 1 root root  525 Apr  8  2022 77-mm-haier-port-types.rules
-rw-r--r-- 1 root root 2.5K Apr  8  2022 77-mm-huawei-net-port-types.rules
-rw-r--r-- 1 root root  15K Apr  8  2022 77-mm-longcheer-port-types.rules
-rw-r--r-- 1 root root 3.3K Apr  8  2022 77-mm-mtk-port-types.rules
-rw-r--r-- 1 root root 2.2K Apr  8  2022 77-mm-nokia-port-types.rules
-rw-r--r-- 1 root root 1.6K Apr  8  2022 77-mm-qcom-soc.rules
-rw-r--r-- 1 root root 3.1K Oct 12  2021 77-mm-qdl-device-blacklist.rules
-rw-r--r-- 1 root root 4.4K Apr  8  2022 77-mm-quectel-port-types.rules
-rw-r--r-- 1 root root 1.6K Apr  8  2022 77-mm-sierra.rules
-rw-r--r-- 1 root root 3.8K Apr  8  2022 77-mm-simtech-port-types.rules
-rw-r--r-- 1 root root 9.2K Apr  8  2022 77-mm-telit-port-types.rules
-rw-r--r-- 1 root root  739 Apr  8  2022 77-mm-tplink-port-types.rules
-rw-r--r-- 1 root root 4.2K Apr  8  2022 77-mm-ublox-port-types.rules
-rw-r--r-- 1 root root 4.5K Apr  8  2022 77-mm-x22x-port-types.rules
-rw-r--r-- 1 root root  17K Apr  8  2022 77-mm-zte-port-types.rules
-rw-r--r-- 1 root root  965 Aug 31 11:27 78-graphics-card.rules
-rw-r--r-- 1 root root 4.8K Apr  1  2020 78-sound-card.rules
-rw-r--r-- 1 root root 1.4K Aug 31 11:27 80-debian-compat.rules
-rw-r--r-- 1 root root  615 Apr  1  2020 80-drivers.rules
-rw-r--r-- 1 root root  190 Jan 28  2019 80-ifupdown.rules
-rw-r--r-- 1 root root 1.3K Mar 31  2022 80-iio-sensor-proxy.rules
-rw-r--r-- 1 root root  211 Apr 14  2022 80-libinput-device-groups.rules
-rw-r--r-- 1 root root 2.1K Apr  8  2022 80-mm-candidate.rules
-rw-r--r-- 1 root root  295 Aug 31 11:27 80-net-setup-link.rules
-rw-r--r-- 1 root root  11K Sep  5  2021 80-udisks2.rules
-rw-r--r-- 1 root root  528 Aug 31 11:27 81-net-dhcp.rules
-rw-r--r-- 1 root root  528 Nov 26  2021 84-nm-drivers.rules
-rw-r--r-- 1 root root  11K Mar  3  2020 85-brltty.rules
-rw-r--r-- 1 root root   82 Aug 21  2019 85-hdparm.rules
-rw-r--r-- 1 root root 1.9K Jan 30  2020 85-hplj10xx.rules
-rw-r--r-- 1 root root 1.7K Nov 26  2021 85-nm-unmanaged.rules
-rw-r--r-- 1 root root  221 Feb  5  2018 85-regulatory.rules
-rw-r--r-- 1 root root  563 Apr 14  2021 90-alsa-restore.rules
-rw-r--r-- 1 root root  350 Dec  1  2020 90-bolt.rules
-rw-r--r-- 1 root root  265 Apr 23  2019 90-console-setup.rules
-rw-r--r-- 1 root root  281 Jul  2 23:18 90-fwupd-devices.rules
-rw-r--r-- 1 root root 1.9K Sep 25  2019 90-libgpod.rules
-rw-r--r-- 1 root root 1.1K Apr 14  2022 90-libinput-fuzz-override.rules
-rw-r--r-- 1 root root  576 Nov 26  2021 90-nm-thunderbolt.rules
-rw-r--r-- 1 root root 9.2K Nov 19  2021 90-pulseaudio.rules
-rw-r--r-- 1 root root  847 Mar  8  2019 95-cd-devices.rules
-rw-r--r-- 1 root root  479 Feb 13  2020 95-dm-notify.rules
-rw-r--r-- 1 root root 1.4K Apr  6  2020 95-kpartx.rules
-rw-r--r-- 1 root root 1.6K Dec 10  2019 95-upower-csr.rules
-rw-r--r-- 1 root root  570 Dec 10  2019 95-upower-hidpp.rules
-rw-r--r-- 1 root root 8.0K Dec 10  2019 95-upower-hid.rules
-rw-r--r-- 1 root root  354 Dec 10  2019 95-upower-wup.rules
-rw-r--r-- 1 root root  171 Jun  1 20:59 96-e2scrub.rules
-rw-r--r-- 1 root root  372 Jan  5  2020 97-dmraid.rules
-rw-r--r-- 1 root root 1.5K Jun  8 07:09 97-hid2hci.rules
-rw-r--r-- 1 root root 4.7K Aug 31 11:27 99-systemd.rules

joey@HAVEN-E6520:~$ ls /etc/udev/rules.d
total 112K
-rwxr-xr-x 1 root root 1.9K May 12  2020 40-libsane.rules
-rw-r--r-- 1 root root  921 May 12  2020 56-hpmud.rules
-rw-r--r-- 1 root root  62K Jun  7 22:42 70-snap.core.rules
-rw-r--r-- 1 root root 1011 Jun  7 22:42 70-snap.freecell-solitaire.rules
-rw-r--r-- 1 root root 2.6K Jun  7 22:42 70-snap.keepassxc.rules
-rw-r--r-- 1 root root  941 Jun  7 22:42 70-snap.motioneye.rules
-rw-r--r-- 1 root root 1.4K Jun  7 22:42 70-snap.mysql-workbench-community.rules
-rw-r--r-- 1 root root 1.2K Jun  7 22:42 70-snap.notepadqq.rules
-rw-r--r-- 1 root root  935 Jun  7 22:42 70-snap.skype.rules
-rw-r--r-- 1 root root  935 Jun  7 22:42 70-snap.slack.rules
-rw-r--r-- 1 root root  885 Jun  7 22:42 70-snap.spotify.rules
-rw-r--r-- 1 root root  989 Jun  7 22:42 70-snap.vlc.rules
-rwxr-xr-x 1 root root 1.9K May 12  2020 S99-2000S1.rules

I created my Udev rule in the “/etc/udev/rules.d” directory. The next step was to identify the USB drive when connected. The command “udevadm” is used to determine this. The important parts of the command’s output are highlighted below.

  joey@HAVEN-E6520:~$ udevadm monitor --kernel --property --subsystem-match=usb
  monitor will print the received events for:
  KERNEL - the kernel uevent

  KERNEL[687713.585492] add      /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4 (usb)
  ACTION=add 
  DEVPATH=/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4
  SUBSYSTEM=usb 
  DEVNAME=/dev/bus/usb/002/013
  DEVTYPE=usb_device              NOTE: THIS IS THE DEVICE  
  PRODUCT=152d/562/3 
  TYPE=0/0/0
  BUSNUM=002
  DEVNUM=013
  SEQNUM=75152
  MAJOR=189
  MINOR=140

  KERNEL[687713.586155] add      /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4/2-1.4:1.0 (usb)
  ACTION=add 
  DEVPATH=/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4/2-1.4:1.0
  SUBSYSTEM=usb 
  DEVTYPE=usb_interface           NOTE: THIS IS THE INTERFACE  
  PRODUCT=152d/562/3 
  TYPE=0/0/0
  INTERFACE=8/6/80
  MODALIAS=usb:v152Dp0562d0003dc00dsc00dp00ic08isc06ip50in00
  SEQNUM=75153

  KERNEL[687713.587040] bind     /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4/2-1.4:1.0 (usb)
  ACTION=bind
  DEVPATH=/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4/2-1.4:1.0
  SUBSYSTEM=usb
  DEVTYPE=usb_interface
  DRIVER=uas
  PRODUCT=152d/562/3
  TYPE=0/0/0
  INTERFACE=8/6/98
  MODALIAS=usb:v152Dp0562d0003dc00dsc00dp00ic08isc06ip62in00
  SEQNUM=75156

  KERNEL[687713.587110] bind     /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4 (usb)
  ACTION=bind
  DEVPATH=/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4
  SUBSYSTEM=usb
  DEVNAME=/dev/bus/usb/002/013
  DEVTYPE=usb_device
  DRIVER=usb
  PRODUCT=152d/562/3
  TYPE=0/0/0
  BUSNUM=002
  DEVNUM=013
  SEQNUM=75157
  MAJOR=189
  MINOR=140

The next step was to create a Udev rule. Udev rules are processed in order. Therefore, I named mine starting with “99” to make sure it is one of the last rules to run. I also named the rule something descriptive and specific to the device attached.

sudo nano /etc/udev/rules.d/99-usbdrive-fantom6.rules  

This rule only needs a single line to execute a script.

ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="152d/562/3", RUN+="/home/joey/Scripts/udev.sh 1> /dev/null"  

Conditions for UDEV rule:

  • ACTION==”add” match if device added (not removed)
  • SUBSYSTEM==”usb” match if subsystem is “usb”
  • ENV{DEVTYPE}==”usb_device” match if devtype is “usb_device” (not :usb_interface”) – without this the script ran twice
  • ENV{PRODUCT}==”152d/562/3″ match if product is “152d/562/3”

What to do if conditions match:

  • RUN+=”/home/joey/Scripts/udev.sh” – command needs to be quick else drive will not mount

It appears the action performed by the rule needs to be quick. I initially tried running the backup script from the rule but, the drive would not mount. I determined it would only mount after the rule completed.

The rule runs the following script that essentially does just one thing: schedule the execution of another script that does the backup. However, some extra commands are run to log results and display a notification on the screen.

#!/bin/bash
LOGFILE='/home/joey/script.log'

echo "$(date)|INFO|Starting script: udev.sh" >> "$LOGFILE"

# https://stackoverflow.com/questions/28195805/running-notify-send-as-root
function notify-send() {
  #Detect the name of the display in use
  local display=":$(ls /tmp/.X11-unix/* | sed 's#/tmp/.X11-unix/X##' | head -n 1)"

  #Detect the user using such display
  local user=$(who | grep '('$display')' | awk '{print $1}' | head -n 1)

  #Detect the id of the user
  local uid=$(id -u $user)

  sudo -u $user DISPLAY=$display DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$uid/bus notify-send "$@"
}

# send notification
notify-send "External Harddrive Detected" "Fantom external drive has been detected. A backup will be started soon." -t 0 -u Normal -i /usr/share/icons/gnome/48x48/devices/drive-multidisk.png

# schedule a backup
echo "sudo -u joey /home/joey/Scripts/backup_e6520.sh" | at now + 1 minute


With this script and the Udev rule in-place the execution of a backup script is scheduled and the following notification pops-up when the USB drive is connected.

About one minute after plugging in the external drive the following backup script is executed and another pop-up notification is displayed.

#!/bin/bash
# Backup Dell E6520 laptop to Fantom "F6" external harddrive

# Run from via cron (NOT USED):
# /home/joey/Scripts/backup_e6520.sh
# ---------- copy ----------
# Backup Dropbox at 1AM each day
# * 1 * * * /home/joey/Scripts/backup_e6520.sh 1> /dev/null
# ---------- copy ----------

# save and change IFS
OLDIFS=$IFS
IFS=$'\n'

RUNDATE=`date +%Y%m%d`                     # append date to file
LOGFILE='/home/joey/script.log'
LOCKFILE="/home/joey/Temp/backup_e6520.lock"
#SCRIPT=`basename "$0"`                     # get name of this script
SCRIPT='/home/joey/Scripts/backup_e6520.sh'
TARGETDIR="/media/joey/F6/Backups/Laptop E6520/$RUNDATE"

TITLE_BACKUP_START='Backup Started'
MESSAGE_BACKUP_START='Starting backup of the laptop to the Fantom external hard drive.'
TITLE_BACKUP_DONE='Backup Completed'
MESSAGE_BACKUP_DONE='Backup of the laptop to the Fantom external hard drive has completed.'
TITLE_BACKUP_WARN='Backup Failed'
MESSAGE_BACKUP_WARN='Backup of the laptop to the Fantom external hard drive has failed. Check the script log.'

# function to allow notifications to logged-in user
function notify-send() {
    #Detect the name of the display in use
    local display=":$(ls /tmp/.X11-unix/* | sed 's#/tmp/.X11-unix/X##' | head -n 1)"

    #Detect the user using such display
    local user=$(who | grep '('$display')' | awk '{print $1}' | head -n 1)

    #Detect the id of the user
    local uid=$(id -u $user)

    sudo -u $user DISPLAY=$display DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$uid/bus notify-send "$@"
}

echo --------------------------------------------------------------------------  | tee -a "$LOGFILE"
echo "$(date)|INFO|Starting script: $SCRIPT" >> "$LOGFILE"
echo "Starting script: `date`"

# send notification
notify-send "$TITLE_BACKUP_START" "$MESSAGE_BACKUP_START" -t 0 -u Normal -i /usr/share/icons/gnome/48x48/status/network-transmit.png

# if lock file exists something is wrong - abort
if [ -f "$LOCKFILE" ]; then

  # lock file exists
  echo "$(date)|ERROR|Lock file exists. Aborting." >> "$LOGFILE"
  echo "Error: lock file exists."
  echo "Note: this script is run automatically when the USB hard drive is plugged in and may be running."

  # send notification
  notify-send "$TITLE_BACKUP_WARN" "$MESSAGE_BACKUP_WARN" -t 0 -u Normal -i /usr/share/icons/gnome/48x48/status/dialog-warning.png

else    

  echo "$(date)|INFO|Lock file not found. Continuing." >> "$LOGFILE"

  # wait for USB drive to be mounted
  #sleep 30
  
  # check if backup folder already exists (if so abort)
  if [ -d "$TARGETDIR" ]; then

    # backup folder exists, abort
    echo "$(date)|ERROR|Backup folder exists. Aborting." >> "$LOGFILE"
    echo "Error: backup folder already exists. A backup has already been run."

    # send notification
    notify-send "$TITLE_BACKUP_WARN" "$MESSAGE_BACKUP_WARN" -t 0 -u Normal -i /usr/share/icons/gnome/48x48/status/dialog-warning.png

  else 

    #echo "Backup started: `date`" | tee -a "$LOGFILE"
    echo "$(date)|INFO|Backup started: `date`" >> "$LOGFILE"
    echo "Starting backup."

    # create lock file
    echo 'This file should not exist for more than a few hours.' > "$LOCKFILE"

    # Create an array to hold directories to backup
    declare -a DIRARRAY
    DIRARRAY+=('/home/joey/Desktop')
    DIRARRAY+=('/home/joey/Documents')
    DIRARRAY+=('/home/joey/Development')
    DIRARRAY+=('/home/joey/Pictures')
    DIRARRAY+=('/home/joey/Scripts')
    DIRARRAY+=('/home/joey/mame')
    DIRARRAY+=('/home/joey/Streams')
    DIRARRAY+=('/home/joey/websites')
    DIRARRAY+=('/home/joey/Temp')
    DIRARRAY+=('/home/joey/Videos')
    DIRARRAY+=('/home/joey/Downloads')
    DIRARRAY+=("/media/joey/SD Card/Dropbox/Dropbox")

    # Test: dump array
    #echo ${DIRARRAY[@]}

    # Create new backup destination folder
    mkdir "$TARGETDIR"

    #echo Backing-up folders to: "$TARGETDIR" | tee -a "$LOGFILE"
    echo "$(date)|INFO|Backing-up files to: $TARGETDIR" >> "$LOGFILE"
    echo "Backing-up files to: $TARGETDIR"

    # Backup each folder to a separate archive
    for i in ${DIRARRAY[@]}
    do
      #echo Processing folder: $i | tee -a "$LOGFILE"
      echo "$(date)|INFO|Processing folder: $i" >> "$LOGFILE"
      echo "Processing folder: $i"      
      
      SOURCEDIR=$i              # full path of folder to archive
      THISDIR=${i##*/}          # last name of folder in path
      FILENAME=$THISDIR.tar.gz  # name of backup file

      # strip leading slash off source directory path
      SOURCEDIR=${SOURCEDIR#/}

      # run archive command
      tar -C / -cpzf $TARGETDIR/$FILENAME $SOURCEDIR

    done

    # Delete array
    unset DIRARRAY

    # delete lockfile
    echo "$(date)|INFO|Deleting lock file" >> "$LOGFILE"
    rm $LOCKFILE

    # send notification
    notify-send "$TITLE_BACKUP_DONE" "$MESSAGE_BACKUP_DONE" -t 0 -u Normal -i /usr/share/icons/gnome/48x48/status/network-transmit.png

  fi  # check if backup folder exists
  
  # END PROCESSING
  
fi  # check if lock file exists

echo "$(date)|INFO|Processing complete. Restoring file system separator" >> "$LOGFILE"

# restore IFS
IFS=$OLDIFS

echo "$(date)|INFO|Exiting script" >> "$LOGFILE"
echo "Exiting script: `date`"
echo --------------------------------------------------------------------------  | tee -a "$LOGFILE"

exit


When the backup script is executed at the scheduled time the following notification is displayed and files start writing to the external hard drive.

When the backup is complete a final notification is displayed letting me know I can disconnect the hard drive. In this example, the backup script does not un-mount the USB drive. I leave it mounted so I can review the files backed-up.

If, when the backup script is executed, it detects an error (like a lock file indicating a backup is already taking place) it will not backup any files and display a notification of the error.

Troubleshooting

  • USB drive not mounting when plugged in.

    Check for an error in the Udev rule or the script that the rule fires off that is preventing the rule from finishing. It appears that no other rules that would’ve executed for this device will not run until yours completes – even if it is the last one.

  • Notifications sent in backup script not displayed.

    This is because udev.sh is run as root and submits the backup job also as root. So, when the backup script is running it is not running as you (or another user). This is discussed in this thread o Stack Overflow: https://stackoverflow.com/questions/28195805/running-notify-send-as-root

  • Monitor syslog for events.

    In another terminal window run the following to monitor the events taking place and scripts being executed: sudo tail -f /var/log/syslog

I had to install the “AT” command utility and could not find a way to script a job with “cron”. It may be possible to schedule the backup script using Systemd timers or some other mechanism. TBH Systemd is still new to me and I tend to fallback on old tried-and-true ways even if they are more expensive in time or resources.

Resources

https://www.thegeekdiary.com/how-to-run-a-script-when-usb-devices-is-attached-or-removed-using-udev/

http://migueleonardortiz.com.ar/udev/running-bash-scripts-using-udev-rules/1505

https://linuxconfig.org/tutorial-on-how-to-write-basic-udev-rules-in-linux

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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