[GUIDE & ADVICE WANTED] Auto-trigger FFS Batch on Drive Mount (macOS)

Discuss new features and functions
Posts: 6
Joined: 21 Feb 2026

carobinario

Hi everyone,

I’m a new user of FreeFileSync (v14.7) and I am currently running it on a Mac Mini M4 with macOS Tahoe 26.3. While setting up my workflow, I realized I was missing a feature that is essential for my needs: automatically starting a backup as soon as an external drive is connected.

Since this "On-Mount" trigger isn't natively available, I collaborated with Google Gemini AI to build a workaround using macOS native tools. It works perfectly on this specific OS version, but being new to FFS and macOS scripting, I would love to get your expert advice on how to improve or harden this setup.

The Current Solution
The challenge with macOS WatchPaths is that it triggers on any file-system change, which can lead to infinite loops during a backup (especially when FFS starts writing to the target). To solve this, we implemented a persistent lock logic: the script only resets when the drive is physically disconnected.

1. The trigger (~/Library/LaunchAgents/com.user.freefilesync.plist)
This agent monitors /Volumes for any mount/unmount events.
<?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.user.freefilesync</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>/Users/yourname/script/auto_ffs.sh</string>
    </array>
    <key>WatchPaths</key>
    <array>
        <string>/Volumes</string>
    </array>
</dict>
</plist>

2. The logic script (auto_ffs.sh)
The script ensures exactly one backup per physical connection.
#!/bin/bash
TARGET_VOLUME="/Volumes/Your_Backup_Drive"
FFS_BATCH="/Path/To/Your/Config.ffs_batch"
LOCKFILE="/tmp/ffs_backup.lock"

# 1. RESET: If drive is NOT connected, remove the lock and exit
if [ ! -d "$TARGET_VOLUME" ]; then
    if [ -f "$LOCKFILE" ]; then
        rm -f "$LOCKFILE"
    fi
    exit 0
fi

# 2. ANTI-LOOP: If lock exists, exit (backup already handled for this session)
if [ -f "$LOCKFILE" ]; then
    exit 0
fi

# 3. EXECUTION
touch "$LOCKFILE"
osascript -e 'display notification "Sync Started..." with title "FreeFileSync"'

# Launch via 'open' to respect macOS GUI environment
open -a "FreeFileSync" --args "$FFS_BATCH"

# Monitor process: Wait until FFS closes before final notification
sleep 5
while pgrep -x "FreeFileSync" > /dev/null; do
    sleep 2
done

osascript -e 'display notification "Backup Completed!" with title "FreeFileSync"'
say "Backup completed"

Seeking advice & feedback
I'm quite happy with how this works on macOS Tahoe, but I have a few concerns I'd like to discuss with the community:

a) Security: to run this, I had to grant Full Disk Access to "/bin/bash". In macOS 26.3, this feels like a very broad permission. Is there a more restricted way to authorize only this specific script?

b) Logic: is "pgrep" the most reliable way to monitor a batch job launched via open -a, or is there a better flag/signal I should use?

c) Stability: for those on recent macOS versions, have you noticed any issues with "WatchPaths" reliability or excessive CPU wake-ups?

d) Native features: Is there a "pro" way to do this within FreeFileSync that I might have overlooked as a beginner?

I look forward to your suggestions and hope this guide helps anyone else looking for an automated "Plug & Play" backup solution on Mac!

Thanks.
Posts: 6
Joined: 21 Feb 2026

carobinario

Thank you for the suggestion! I did look into RTS, but since I'm new to this, I have a few doubts regarding my specific workflow.

My backup drive is connected sporadically (sometimes once a day, other times once every two weeks). This is why I leaned towards the LaunchAgent approach:

Continuous execution & resources: does RTS need to stay active 24/7 as a background process to work? If so, doesn't it consume more system resources (RAM/CPU) compared to a LaunchAgent that stays 100% idle until macOS detects the drive?

System cleanliness: I prefer a 'clean' setup where automation is handled by the OS kernel rather than having a persistent app sitting in my dock or menu bar for weeks while the drive is disconnected.

Loop management: my script's lock-file logic ensures exactly one sync per physical connection, which seems to handle the 'write-trigger' loop more strictly than RTS's idle-time settings.

For those using RTS on external drives: how does it handle a volume that is missing for 90% of the time? Does it ever throw errors or 'ghost' mount point warnings on newer macOS versions?"
User avatar
Posts: 4866
Joined: 11 Jun 2019

xCSxXenon

RTS does have to be a running process, yes, but so does LaunchAgent. You can have something monitoring for drive presence without it running. It is negligible resource usage, you won't notice it.
RTS is already installed with FFS, so cleanliness also doesn't matter here, aside from an icon showing that it exists.
FFS already has lock files per base location, so multiple sync can't run on the same root locations.
I use RTS on Windows perfectly fine, with a usb drive that goes over a week at times disconnected
Posts: 6
Joined: 21 Feb 2026

carobinario

RTS does have to be a running process, yes, but so does LaunchAgent. You can have something monitoring for drive presence without it running. It is negligible resource usage, you won't notice it.
RTS is already installed with FFS, so cleanliness also doesn't matter here, aside from an icon showing that it exists.
FFS already has lock files per base location, so multiple sync can't run on the same root locations.
I use RTS on Windows perfectly fine, with a usb drive that goes over a week at times disconnected xCSxXenon, 23 Feb 2026, 02:51

Thanks for your feedback. I’d like to clarify a couple of points regarding the macOS architecture, as it differs significantly from Windows in this context.

Launchd vs. persistent processes. On macOS, launchd isn't a separate running process like RTS; it’s the service management framework (PID 1). Using a LaunchAgent is an 'event-driven' approach: the system kernel notifies the script only when an event occurs. RTS, conversely, is a persistent user-space application that must stay open in the Dock/Menu Bar. For a drive that is disconnected 90% of the time, the LaunchAgent, IMHO, is the more 'Mac-native' way to handle automation with zero overhead.

The 'one-shot' logic. While FFS has internal lock files to prevent data corruption, my script's lock-file logic serves a different purpose: it ensures a single execution per physical mount. This is a deliberate design choice to avoid the 'feedback loop' where RTS might re-trigger itself after a successful sync due to file-system noise.

Outdated documentation. I noticed that the FFS manual still mentions cron for scheduling (https://freefilesync.org/manual.php?topic=schedule-batch-jobs) and don't say a word about RealTimeSync: Run as Service. On macOS, cron has been deprecated for years in favor of launchd. This discrepancy is exactly why I wanted to share a modern, tested solution for macOS Tahoe 26.3.

My goal was to provide a contemporary contribution for Mac users, bridging the gap where the official documentation might be a bit outdated or superficial regarding macOS-specific behaviors. I’m still very open to suggestions on how to further refine this script!

Thanks.
Posts: 6
Joined: 21 Feb 2026

carobinario

UPDATE: Final security & maintenance hardening (macOS Tahoe 26.3)

I’ve successfully optimized the setup and confirmed the behavior on the new M4 architecture. Here are the final improvements:

Security hardening. You do not need to grant Full Disk Access to /bin/bash (which is a broad permission). I verified that dragging the auto_ffs.sh script file directly into the Full Disk Access list (System Settings > Privacy & Security) works perfectly. This limits permissions strictly to our specific script.

Self-cleaning logs. To prevent the log file from growing indefinitely over months of use, I added a simple rotation logic to the script:
echo "$(tail -n 500 "$LOGFILE")" > "$LOGFILE"
This keeps only the last 500 lines, ensuring the system stays lean while preserving enough history for troubleshooting.

Reliability & documentation. Tested with various sync sizes. The pgrep logic correctly monitors the process, and my custom LOCKFILE ensures exactly one sync per physical mount, avoiding the feedback loops sometimes seen with RTS. I also noticed the official FFS manual still mentions cron; for macOS users, using launchd as shown here is the modern, Apple-recommended way to handle these events.

Resource. Confirmed 0% CPU/RAM impact while the drive is disconnected. Unlike persistent background apps, launchd handles the event trigger natively at the kernel level, only waking the script when the volume is actually present.

I am still very much open to further discussion and suggestions from the community to refine this workflow!
Posts: 6
Joined: 21 Feb 2026

carobinario

UPDATE: Final version 1.0 (macOS Tahoe & legacy compatibility)

I would like to share my final script with the community, hoping it might be useful for other macOS users. While I developed and tested this on macOS Tahoe 26.3 on an M4 Mac Mini, the logic relies on standard launchd events and bash commands, so it should be compatible with older versions of the OS as well.

Security. You can grant Full Disk Access directly to the .sh script file in System Settings > Privacy & Security. This is more secure than granting it to the entire /bin/bash binary.

Lockfile Logic. The script uses a lockfile to ensure the sync runs exactly once per mount, preventing loops even if FFS writes data back to the drive.

Maintenance. I added a log rotation line to keep the log file size under control (last 500 lines).

Resources. 0% CPU/RAM impact while the drive is disconnected, as launchd handles the mount trigger natively.

Setup Instructions:

Save the script. Save the code below
as auto_ffs.sh
(e.g., in a folder like /Users/yourname/scripts/).

Set Permissions. Open Terminal and run
chmod +x /path/to/your/auto_ffs.sh
to make it executable.

Full Disk Access. Go to System Settings > Privacy & Security > Full Disk Access. Click the "+" button and select your auto_ffs.sh file.

Automation. To make it run automatically on mount, you need to create a .plist file in
~/Library/LaunchAgents/
using the StartOnMount key pointing to this script.

Here is the code:
#!/bin/bash

################################################################################
# Script Name: auto_ffs.sh
# Author: carobinario
# Email: riccardo@carobinario.it
# Website: www.carobinario.it
# Created: 24/02/2026
#
# Changelog:
# 24/02/2026 - v1.0:
#   - launchd automation with lockfile logic (one-shot per mount).
#   - Automatic log rotation (keeps last 500 lines).
#   - Security optimized for macOS (Direct FDA on script).
################################################################################

# --- Configuration --- #
TARGET_VOLUME="/Volumes/YOUR_EXTERNAL_DRIVE_NAME"
FFS_BATCH="/PATH/TO/YOUR/settings.ffs_batch"
LOGFILE="$HOME/script/backup_log.txt"
LOCKFILE="/tmp/ffs_backup.lock"

# --- Logic --- #

# 1. If the drive is not mounted, remove the lock and exit
if [ ! -d "$TARGET_VOLUME" ]; then
    rm -f "$LOCKFILE"
    exit 0
fi

# 2. If the lock exists, the backup already ran for this mount
if [ -f "$LOCKFILE" ]; then
    exit 0
fi

# 3. Start backup process
touch "$LOCKFILE"

# --- Log Rotation (keeps last 500 lines) --- #
if [ -f "$LOGFILE" ]; then
    echo "$(tail -n 500 "$LOGFILE")" > "$LOGFILE"
fi

echo "$(date): [START] Drive connected. Starting backup..." >> "$LOGFILE"
osascript -e 'display notification "Sync started..." with title "FreeFileSync"'

# Run FreeFileSync
open -a "FreeFileSync" --args "$FFS_BATCH"

# Wait for process to end
sleep 5
while pgrep -x "FreeFileSync" > /dev/null; do
    sleep 2
done

echo "$(date): [INFO] Backup finished. Script idling until drive is removed." >> "$LOGFILE"
osascript -e 'display notification "Backup completed!" with title "FreeFileSync"'
say "Backup completed."

# Note: the lockfile is removed only at Step 1 (physical disconnection)
If anyone is interested in this setup, I will consider studying and implementing further improvements or features. Let me know!