Skip to content

Instantly share code, notes, and snippets.

@antznin
Last active January 4, 2024 01:10
Show Gist options
  • Save antznin/dbe071305f308a80e69b623b47763de2 to your computer and use it in GitHub Desktop.
Save antznin/dbe071305f308a80e69b623b47763de2 to your computer and use it in GitHub Desktop.
Automatically copy files when a new USB drive is plugged with udev
#!/usr/bin/env bash
set -eu -o pipefail
NOCOLOR='\033[0m'
COLOR_ERROR='\033[1;31m' # bold red
COLOR_INFO='\033[1;36m' # bold cyan
COLOR_NOTE='\033[0;36m' # cyan
DESTINATION="/path/to/destination"
declare -a MOUNT_POINTS
_log ()
{
local level="$1"
local message="$2"
local begin_color
shift
case "$level" in
note)
begin_color="$COLOR_NOTE"
;;
info)
begin_color="$COLOR_INFO"
;;
error)
begin_color="$COLOR_ERROR"
;;
*)
echo "Invalid level ${level}"
;;
esac
echo -e "${begin_color}[${level^^}] $message${NOCOLOR}"
}
_populate_mount_points ()
{
local device="$1"
local real_device mount_points
real_device="$(systemd-escape -u -p "$device")"
# Check that the device is a block device
if [ ! -b "$real_device" ]; then
_log error "Device $real_device does not exist!"
exit 1
fi
mount_points="$(mount | grep -F "$real_device" \
| sed -r -e 's/^.+ on (.+) type .*$/\1/g' || true)"
if [ -z "$mount_points" ]; then
_log error "No mount points found for $real_device."
exit 1
fi
while IFS= read mount_point; do
MOUNT_POINTS+=("$mount_point")
done <<< "$mount_points"
}
_sanity_checks ()
{
local destination="$1"
if [ ${#MOUNT_POINTS[@]} -eq 0 ]; then
_log error "No mount points given!"
exit 1
fi
if [ ! -d "$destination" ]; then
_log error "Directory $destination does not exist!"
exit 1
fi
}
_perform_copy ()
{
local destination="$1"
local base_source_basename source_basename new_dir i="1"
for source in "${MOUNT_POINTS[@]}"; do
base_source_basename="$(basename "$source")"
source_basename="$(basename "$source")"
while find "$destination" -mindepth 1 -maxdepth 1 \
-name "$source_basename" | grep .; do
source_basename="${base_source_basename}_$i"
(( i += 1 ))
done
[ "$base_source_basename" != "$source_basename" ] \
&& _log note "Suffix added to ${base_source_basename} as it already existed."
new_dir="${destination}/${source_basename}"
_log info "Creating directory ${new_dir}"
mkdir "${new_dir}"
_log info "Copying from ${source} to ${new_dir}..."
rsync -avP "${source}/" "${new_dir}"
_log info "Finished copying from ${source} to ${new_dir}."
done
}
main ()
{
local device="$1"
local destination="${2:-$DESTINATION}"
_log info "Waiting 5 seconds for device to be mounted…"
sleep 5
_populate_mount_points "$device"
_sanity_checks "$destination"
_perform_copy "$destination"
}
main "$@"
@antznin
Copy link
Author

antznin commented Nov 7, 2022

auto-copy.sh

This script can be used in conjunction with a udev rule and a systemd user service to copy all the content from block device's partitions to a specified location. Following step can be followed to use it like so:

  1. Copy the following udev rule in /etc/udev/rules.d/90-auto-copy.rules:

    ACTION=="add", KERNEL=="sd?", SUBSYSTEM=="block", ENV{ID_BUS}=="usb", \
      TAG+="systemd", PROGRAM="/bin/systemd-escape -p %N", \
      ENV{SYSTEMD_USER_WANTS}+="auto-copy@%c.service"
    
  2. Create a systemd user service in $HOME/.config/systemd/user/auto-copy@.service. Get the name for the destination partition by running systemd-escape /path/to/mountpoint. Replace /path/to/auto-copy.sh with the proper path. Finally, either edit auto-copy.sh's _DESTINATION variable or pass it as the second argument in ExecStart= below:

    [Unit]
    Description=Copy files from new device to KINGSTON device
    Requires=run-media-user-KINGSTON.mount
    After=run-media-user-KINGSTON.mount
    
    [Service]
    ExecStart=/path/to/auto-copy.sh "%I"
    
    [Install]
    WantedBy=run-media-user-KINGSTON.mount
    
  3. Activate the rule:

    udevadm control --reload && udevadm trigger
  4. Follow logs for when new block device appear (a USB key, for example):

    journalctl --user -fu "auto-copy@*.service"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment