Skip to content

Instantly share code, notes, and snippets.

@troykelly
Last active May 2, 2024 10:51
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save troykelly/5ab2549c0c60bc175d00798cd6609abc to your computer and use it in GitHub Desktop.
Save troykelly/5ab2549c0c60bc175d00798cd6609abc to your computer and use it in GitHub Desktop.
Automatically mount rclone mounts

RClone Mounting Script for macOS

This script is used to automate the process of mounting rclone remote directories to local ones as described by a YAML configuration file. It can also unmount all previously mounted directories and clean up their corresponding local directories.

Dependencies

  • rclone: This script depends on rclone, a command line program to manage files on cloud storage.

Configuration

The script sets up the configuration automatically if it's not previously defined. Specifically, it creates a YAML configuration file at ~/.config/mount/mount.yaml.

Each line in the configuration file should have the format remote: local_dir, where remote is the name of the remote directory as defined in rclone and local_dir is the directory where the remote will be mounted.

For example:

client-crypt: ~/Documents/Clients
render-crypt: ~/Movies/Render

The configuration also supports encrypted rclone configurations that require a password for decrypting.

To set this up, you can run the following command in your Terminal:

security add-generic-password -a "rclone" -s "rclone" -w

This will prompt you to enter the password (the one used for your rclone encryption), and it'll be saved securely in your Keychain, associated with the account and service, both named "rclone". That way, the password will be fetched securely when needed by the script.

If you'd like to choose a custom account or service name, modify the above command accordingly. But also ensure that the script is updated to fetch the password from the correct Keychain entry.

Accessing the macOS keychain from command-line utilities (like security) usually works seamlessly, as long as the user is logged in. The access to keychain items happens without the user having to manually unlock it each time. However, keep in mind that the first time a new application or script attempts to access an item in the keychain, the system may prompt the user to grant this permission. This is a one-time event. For subsequent requests, the permission doesn't have to be given manually.

To ensure that the script functions as expected, it's recommended to run it once manually to go through any potential permission requests. Once these are granted, the script will operate unattended as expected.

Keep in mind that different versions of macOS might have slightly different behaviors. Privacy settings or system preferences might also affect this. If the user faces issues with keychain access, they might need to check their system's keychain access settings under "Security & Privacy" in System Preferences.

Usage

Mounting

Simply run the script and it will mount all remote directories into the respective local directories as mentioned in ~/.config/mount/mount.yaml.

./mount_remotes.sh

Unmounting

Pass -u or -u --auto as an argument when running the script.

./mount_remotes.sh -u

This command will unmount all the directories that are mentioned in ~/.config/mount/mount.yaml. It's a good practice to unmount your directories when they're no longer needed, given that any changes made in the local directories are usually write/synced back to the remote location when the system is idle.

Error Logging

All errors are logged using the logger command which can then be viewed using the Console app in macOS. Filter by the script name to see the log messages generated by this script.

In case of missing rclone configurations or non-empty mount points, the respective directory will be skipped and errors will be logged in the system logs.

If the script is run with the --auto flag, macOS notifications are sent on any error.

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Note: You will need to have rclone correctly setup and installed on your machine where this script will be running. For more details on how to achieve this, you will find the Official rclone documentation really useful.

Automatic Rclone Mounting and Unmounting on macOS

Prerequisites

  • rclone configured with remotes.
  • The mounting script is properly set up.
  • brew package manager for installing sleepwatcher.

Steps

For mounting operations at login, and on wake after sleep.

  1. Create a launch agent .plist file named com.custom.rclonemount.plist in the ~/Library/LaunchAgents directory. Add the following content:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.custom.rclonemount</string>
    <key>ProgramArguments</key>
    <array>
        <string>/path/to/mount_remotes.sh</string>
        <string>--auto</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>

In the ProgramArguments key, replace /path/to/mount_remotes.sh with the actual path to the script.

  1. Load the launch agent:
launchctl load ~/Library/LaunchAgents/com.custom.rclonemount.plist

Now, every time you log in, your specified rclone mounts will be mounted.

For mounting operations on wake from sleep

  1. Install the sleepwatcher utility that reacts to sleep and wake events using brew:
brew install sleepwatcher
  1. Write a script named .wakeup in your home directory (~/.wakeup). Add /path/to/mount_remotes.sh --auto in it and make it executable:
#!/usr/bin/env bash
/path/to/mount_remotes.sh --auto

Make sure to replace /path/to/mount_remotes.sh with a suitable value. Then use chmod +x ~/.wakeup to make it executable.

  1. Create a .plist file named com.custom.sleepwatcher.wakeup.plist in the ~/Library/LaunchAgents directory. Add the following content:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.custom.sleepwatcher.wakeup</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/sbin/sleepwatcher</string>
        <string>-w</string>
        <string>/Users/username/.wakeup</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>

Replace /Users/username/.wakeup with the actual path of .wakeup in your user's home directory.

  1. Load the launch agent:
launchctl load ~/Library/LaunchAgents/com.custom.sleepwatcher.wakeup.plist

For unmounting operations on system sleep:

  1. Write a script named .sleep in your home directory (~/.sleep). Add /path/to/mount_remotes.sh -u in it and make it executable:
#!/usr/bin/env bash
/path/to/mount_remotes.sh -u

Make sure to replace /path/to/mount_remotes.sh with a suitable value. Then use chmod +x ~/.sleep to make it executable.

  1. Create a .plist file named com.custom.sleepwatcher.sleep.plist in the ~/Library/LaunchAgents directory. Add the following content:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.custom.sleepwatcher.sleep</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/sbin/sleepwatcher</string>
        <string>-s</string>
        <string>/Users/username/.sleep</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>

Replace /Users/username/.sleep with the actual path of .sleep in your user's home directory.

  1. Load the launch agent:
launchctl load ~/Library/LaunchAgents/com.custom.sleepwatcher.sleep.plist

Now, every time your Mac goes to sleep, the unmounting will be triggered, and upon waking up, the script will mount it back.

#!/usr/bin/env bash
# Set process name as the script name
process_name=$(basename "$0")
# Checking if rclone configuration is encrypted
if rclone config show 2>&1 | grep -q "pass configuration password:"; then
# Fetching rclone password from Keychain
RCLONE_CONFIG_PASS=$(security find-generic-password -s "rclone" -w)
export RCLONE_CONFIG_PASS
fi
# Reading the yaml configuration file
config_file="$HOME/.config/mount/mount.yaml"
# Creating the directory structure for the configuration file if it doesn't exist
if [ ! -d "$(dirname "$config_file")" ]; then
mkdir -p "$(dirname "$config_file")"
fi
# Check & creation of configuration file if it doesn't exists
if [ ! -f "$config_file" ]; then
echo "# Configuration for rclone auto mount" > "$config_file"
echo "# The key is the remote name in rclone config" >> "$config_file"
echo "# The value is the local directory for mount" >> "$config_file"
echo "# Make sure the directory is empty" >> "$config_file"
echo "# For example:" >> "$config_file"
echo "# tv-crypt: ~/Movies/TV" >> "$config_file"
logger -s -p user.notice -t "$process_name" "Warning: Configuration file not found! A new one has been created at $config_file."
exit 0
fi
# Check if rclone is installed
if ! command -v rclone &> /dev/null
then
logger -s -p user.err -t "$process_name" "Rclone could not be found. Please install rclone before proceeding."
exit 1
fi
# If -u argument is passed, unmount all defined mount points
if [[ "$1" == "-u" ]]; then
while IFS= read -r line
do
if [[ "$line" =~ ^"#".* ]] ; then
continue
fi
mount_point=$(echo "$line" | cut -d ':' -f 2- | xargs)
mount_point=${mount_point/\~/$HOME}
if mount | grep -q "$mount_point"; then
if umount "$mount_point" 2>/dev/null; then
echo -e "\033[33mSuccessfully unmounted $mount_point using macOS umount.\033[0m"
else
fusermount -u "$mount_point" 2>/dev/null
echo -e "\033[33mAttempting to unmount $mount_point using fusermount -u.\033[0m"
fi
if [ "$(ls -A "$mount_point")" ]; then
logger -s -p user.err -t "$process_name" "$mount_point is not empty. Not removing the directory."
else
rmdir "$mount_point"
echo -e "\033[33mUnmounted and removed $mount_point.\033[0m"
fi
fi
done < "$config_file"
exit 0
fi
# Reading and mounting defined remotes
while IFS= read -r line
do
if [[ "$line" =~ ^"#".* ]] ; then
continue
fi
remote=$(echo "$line" | cut -d ':' -f 1 | tr -d ' ')
mount_point=$(echo "$line" | cut -d ':' -f 2- | xargs)
mount_point=${mount_point/\~/$HOME}
if ! rclone listremotes | grep -q "^$remote:"; then
logger -s -p user.err -t "$process_name" "Remote $remote not found in rclone configuration."
continue
fi
mount_point_parent=$(dirname "$mount_point")
if [ ! -w "$mount_point_parent" ]; then
logger -s -p user.err -t "$process_name" "User does not have write permissions for the directory $mount_point_parent."
continue
fi
if [ ! -d "$mount_point" ]; then
mkdir -p "$mount_point"
else
if [ ! -w "$mount_point" ]; then
logger -s -p user.err -t "$process_name" "User does not have write permissions for the directory $mount_point."
continue
fi
if mount | grep -q "$mount_point"; then
logger -s -p user.err -t "$process_name" "$mount_point is already a mount point. Skipping the mount."
continue
fi
fi
if [ "$(ls -A "$mount_point")" ]; then
logger -s -p user.err -t "$process_name" "$mount_point is not empty. Skipping the mount."
continue
fi
rclone mount "$remote:" "$mount_point" &> /dev/null &
echo -e "\033[32mSuccessfully mounted $remote at $mount_point.\033[0m"
done < "$config_file"
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment