1
\$\begingroup\$

My bash script mounts two VeraCrypt volumes, rsyncs their contents, and dismounts them. The script takes two parameters, the path for both the source and backup veracrypt volumes. I wrote this script so rsync wouldn't have to copy an entire volume if there was a single change in it.

This is my second time with bash scripting so please let me know what I could do better.

Thanks

#!/bin/bash

# Requirements:
# - Must have both veracrypt and rsync installed
# - Must be ran on Unix based systems
# - Both volumes must have the same password

function prepare_volumes_for_exit() {
    # dismount volumes
    veracrypt -d
    echo "Volumes dismounted"

    # delete mount points
    sudo rm -r "$source_mount_point"
    sudo rm -r "$backup_mount_point"
    echo "Mount points purged"
}

# removes double or single quotes from strings of they exist
function format_string() {
    if [ "${1:0:1}" == "'" ] && [ "${1: -1}" == "'" ]; then
        echo "${1:1:-1}"
    elif [ "${1:0:1}" == '"' ] && [ "${1: -1}" == '"' ]; then
        echo "${1:1:-1}"
    else
        echo "$1"

    fi
}

# make sure all volumes are removed before starting
veracrypt -d

# check that 2 params are passed
if ! [ $# -eq 2 ]; then
    echo "Error: 2 parameters are required [source_volume_path] [backup_volume_path]"
    exit 1
fi

echo "Enter the password for the veracrypt volume: "
read -s veracrypt_password

# path to veracrypt volumes
source_volume_path="$(format_string "$1")"
backup_volume_path="$(format_string "$2")"

# generate random mount point
source_mount_point="$(format_string "/mnt/$(uuidgen)")"
backup_mount_point="$(format_string "/mnt/$(uuidgen)")"

# flag to determine if drives are mounted
source_volume_mounted=0
backup_volume_mounted=0

# create mount points
sudo mkdir -p "$source_mount_point"
sudo mkdir -p "$backup_mount_point"

echo "Beginning to mount volumes"
sudo veracrypt -t --mount "$source_volume_path" "$source_mount_point" -p "$veracrypt_password" --non-interactive

# checks that the source volume mounted
while read -r drive_number volume_location vc_mapper mount_location; do
    formatted_volume_location="$(format_string "$volume_location")"
    formatted_mount_location="$(format_string "$mount_location")"
    if [ "$formatted_volume_location" == "$source_volume_path" ] && [ "$formatted_mount_location" == "$source_mount_point" ]; then
        echo "Source volume mounted"
        source_volume_mounted=1
    else
        echo "Error: source volume not mounted"
        prepare_volumes_for_exit
        exit 1
    fi
done <<< $(veracrypt -t -l)

sudo veracrypt -t --mount "$backup_volume_path" "$backup_mount_point" -p "$veracrypt_password" --non-interactive

# checks both the source and backup volumes are mounted
# http://mywiki.wooledge.org/BashFAQ/001#source
while read -r drive_number volume_location vc_mapper mount_location; do
    formatted_volume_location="$(format_string "$volume_location")"
    formatted_mount_location="$(format_string "$mount_location")"
    if [ "$formatted_volume_location" == "$source_volume_path" ] && [ "$formatted_mount_location" == "$source_mount_point" ]; then
        source_volume_mounted=1
    elif [ "$formatted_volume_location" == "$backup_volume_path" ] && [ "$formatted_mount_location" == "$backup_mount_point" ]; then
        echo "Backup volume mounted"
        backup_volume_mounted=1
    else
        echo "Error: source or backup volume not mounted"
        prepare_volumes_for_exit
        exit 1
    fi
done <<< $(veracrypt -t -l)

if [ "$source_volume_mounted" -eq 1 ] && [ "$backup_volume_mounted" -eq 1 ]
then 
    echo "Volumes mounted, beginning rsync"
#    sudo rsync -hrtq --exclude-from='/media/miller/Primary/exclude.txt' --delete $source_mount_point/ $backup_mount_point
    echo "rsync complete"
else 
    echo 'Error: Drives were not mounted successfully, skipping rsync'
fi

prepare_volumes_for_exit

echo "Complete"
exit 0

```
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Some suggestions:

  • #!/usr/bin/env bash is a more portable shebang line.
  • function prepare_volumes_for_exit() can be simplified to prepare_volumes_for_exit().
  • Using sudo within a script is generally discouraged, since it makes it easy to do really bad things without explicit user consent. Instead, remove the sudos and tell the user to run the whole script as root.
  • format_string is an anti-pattern. First, because quotes are actually valid characters in paths, but more importantly because it is a strong indication that you're expecting the user to wrongly input broken strings.
  • ! [ $# -eq 2 ] can be simplified as [ $# -ne 2 ].
  • while read -r drive_number volume_location vc_mapper mount_location
    …
    done <<< $(veracrypt -t -l)
    
    can be improved as
    while read -r -u9 drive_number volume_location vc_mapper mount_location
    …
    done 9< <(veracrypt -t -l)
    
    This way, no word splitting happens on the input, and commands within the while loop which happen to read from stdin won't break the loop.
  • exit 0 is redundant.
  • By convention, scripts should be silent if there is nothing important to report, and any error messages should go to standard error rather than standard output.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.