Periodic Backup of a Mac Folder on a Synology NAS

A vibrant, nostalgic 1990s GeoCities-style image depicting the concept of backup. The scene features stacked boxes labeled with dates like ‘2024-06-08_16-06-49’, ‘2024-06-08_17-06-49’, and ‘2024-06-08_18-06-49’, symbolizing different backups, arranged in an organized manner. Surrounding the boxes are colorful, retro office supplies like notebooks, pens, and folders. The background includes playful elements and vibrant colors, evoking a sense of retro tech nostalgia. The image is in landscape format, lively and visually engaging. done with Dall-E

On the Ger­man side of my blog, I wrote three arti­cles about using launchd as a sched­uler for short­cuts, cre­at­ing a 1‑click short­cut to sync a fold­er from a Mac to a Syn­ol­o­gy NAS using rsync, and in the third using rsync and launchd to sched­ule a back­up that includes sav­ing delet­ed files from the source to a spe­cial fold­er on the Syn­ol­o­gy NAS. In this arti­cle, I will com­bine these top­ics into one guide.

I will explain how to use rsync to save a copy on anoth­er serv­er, specif­i­cal­ly a Syn­ol­o­gy NAS. I will show you how to turn this shell com­mand into a short­cut, and final­ly, how to extent it into a real back­up solu­tion that runs peri­od­i­cal­ly using the launchd frame­work on macOS.

Let’s start with the basic rsync com­mand cre­ation.

SSH and Rsync Setup

To copy the Obsid­i­an Vault, the rsync com­mand with ssh as the trans­fer pro­to­col is used. There­fore, it is nec­es­sary to enable ssh on both the Mac and the Syn­ol­o­gy NAS.

On macOS, ssh is usu­al­ly installed by default. If not, it can be eas­i­ly installed via Home­brew. There are plen­ty of instruc­tions avail­able online if assis­tance is need­ed.

On the Syn­ol­o­gy side, rsync must be enabled via the Syn­ol­o­gy Con­trol Pan­el:

A screenshot of the Synology Control Panel in the "File Services" section under the "rsync" tab. The "Enable rsync service" checkbox is checked, with the SSH encryption port set to 22. Options to manage rsync accounts and speed limits are also visible.

Once ssh is set up on both the Mac and the Syn­ol­o­gy, test the con­nec­tion. Depend­ing on the router set­up, the Syn­ol­o­gy can be accessed using an inter­nal host­name like synology.lan or a fixed IP address. It is rec­om­mend­ed to use a fixed IP address. In this case, the Syn­ol­o­gy is reach­able at 192.168.1.80. To test the con­nec­tion:

ssh user:password@192.168.1.80

If the user­name on both the Mac and Syn­ol­o­gy is the same, use:

ssh 192.168.1.80

The first time you log on, the fol­low­ing mes­sage will appear:

The authenticity of host '192.168.1.80 (192.168.1.80)' can't be established.
ED25519 key fingerprint is SHA256:yourkeyhere.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
Warning: Permanently added '192.168.1.80' (ED25519) to the list of known hosts.
leif@192.168.1.80's password:

This is a secu­ri­ty fea­ture because the Mac has no pri­or record of the server’s pub­lic key. Answer­ing “yes” stores this pub­lic key in the ~/.ssh/known-hosts file. This helps pre­vent man-in-the-mid­dle attacks by iden­ti­fy­ing the serv­er. If anoth­er machine uses the same IP address and presents a dif­fer­ent key, SSH will warn about the key mis­match and pre­vent the con­nec­tion. After con­firm­ing with “yes,” the Syn­ol­o­gy pass­word is required for login. After enter­ing the pass­word, the fol­low­ing text may appear:

Using terminal commands to modify system configs, execute external binary files, add files, or install unauthorized third-party apps may lead to system damages or unexpected behavior, or cause data loss. Make sure you are aware of the consequences of each command and proceed at your own risk.

Warning: Data should only be stored in shared folders. Data stored elsewhere may be deleted when the system is updated/restarted.

leif@192.168.1.80:~$ 

Type exit to return to the Mac in your ter­mi­nal ses­sion.

This is the basic set­up to access the Syn­ol­o­gy with ssh. How­ev­er, typ­ing the pass­word every time can be incon­ve­nient, espe­cial­ly for script access. Addi­tion­al steps are need­ed to enable pass­word­less access.

Step 1: Creating a Public and Private Key Pair for Server Access

Using keys for serv­er access enhances secu­ri­ty and enables the auto­mat­ic login need­ed for scripts. Set­ting up a key pair is easy:

ssh-keygen -a 420 -f ~/.ssh/synology-tutorial.ed25519 -C "My Synology Tutorial key"

This gen­er­ates the key pair:

  • Pri­vate Key: ~/.ssh/synology-tutorial.ed25519
  • Pub­lic Key: ~/.ssh/synology-tutorial.ed25519.pub
  • The -a 420 option sets the num­ber of key deriva­tion func­tion rounds to 420, enhanc­ing secu­ri­ty by mak­ing it hard­er for an attack­er to brute-force the passphrase.
  • The -f ~/.ssh/synology-tutorial.ed25519 option spec­i­fies the loca­tion and name of the pri­vate key. The pub­lic key will be saved with the same name and loca­tion, but with a .pub exten­sion.
  • The -C "My Synology Tutorial key" option adds a com­ment to the key, mak­ing it eas­i­er to iden­ti­fy the key’s pur­pose or the own­er when view­ing the pub­lic key file.

Dur­ing key gen­er­a­tion, the com­mand will prompt for set­ting a passphrase. It is rec­om­mend­ed to set a passphrase dif­fer­ent from your Mac or serv­er pass­word for added secu­ri­ty. A passphrase can be longer and more com­plex than a typ­i­cal pass­word (e.g. it can also con­tains spaces). It is rec­om­mend­ed to use a strong, unique passphrase to max­i­mize secu­ri­ty.

Although it is pos­si­ble to use a sin­gle key pair for mul­ti­ple servers, cre­at­ing a spe­cif­ic key pair for each serv­er makes man­ag­ing access eas­i­er and allows revo­ca­tion of a key pair for one serv­er with­out inval­i­dat­ing con­nec­tions to oth­ers.

Step 2: Copying the public key to the server

As the next step, the pub­lic key needs to be copied to the serv­er:

ssh-copy-id -i ~/.ssh/synology-tutorial.ed25519.pub 192.168.1.80

The ‑i option spec­i­fies the key that should be copied to the serv­er. Because the user­name on the Mac is the same as on the Syn­ol­o­gy, there is no need to spec­i­fy the user­name in the com­mand. If the user­names dif­fer, use user@192.168.1.80 to spec­i­fy the user­name. You will be asked for the users pass­word on the serv­er, not for the passphrase you defined when gen­er­at­ing the keys.

Now it’s time to check if every­thing is work­ing:

ssh -i ~/.ssh/synology-tutorial.ed25519 192.168.1.80

This com­mand will prompt for the passphrase set dur­ing key gen­er­a­tion and should allow login with­out ask­ing for the server’s pass­word. How­ev­er, this still sub­sti­tutes a pass­word with a pos­si­bly more com­plex passphrase. In the next step, the process of log­ging into the Syn­ol­o­gy using an alias and with­out any cre­den­tials will be explained.

3. Creating a ssh-config file

Cre­at­ing an SSH con­fig file cen­tral­izes the man­age­ment of SSH con­nec­tions. It sim­pli­fies con­nec­tions and elim­i­nates the need to repeat­ed­ly enter pass­words or passphras­es, which is espe­cial­ly use­ful when using SSH in scripts. For exam­ple:

ssh synology

can be used in scripts instead of full con­nec­tion details.

Here is the SSH con­fig used for this arti­cle:

Host synology 
    HostName 192.168.1.80 
    AddKeysToAgent yes 
    UseKeychain yes 
    User leif 
    PreferredAuthentications publickey 
    IdentityFile ~/.ssh/synology-tutorial.ed25519
  • Host syn­ol­o­gy: Defines an alias for this con­fig­u­ra­tion, allow­ing con­nec­tion to the Syn­ol­o­gy using the alias instead of the full host­name or IP address.
  • Host­Name: Spec­i­fies the actu­al host­name or IP address of the Syn­ol­o­gy. This tells the SSH client where to con­nect.
  • Add­KeysToA­gent yes: Adds the key passphrase to the SSH authen­ti­ca­tion agent (ssh-agent) when the con­fig­u­ra­tion is first used.
  • UseK­ey­chain yes: A macOS-spe­cif­ic option to store the passphrase in the key­chain. This is con­ve­nient on a Mac, as the ssh-agent only stores the passphrase dur­ing the ses­sion, while UseK­ey­chain stores it across ses­sions.
  • User leif: Spec­i­fies the user­name to use when con­nect­ing to the Syn­ol­o­gy.
  • Pre­ferredAu­then­ti­ca­tions pub­lick­ey: Spec­i­fies the authen­ti­ca­tion method to use. In this case, it should be pub­lick­ey.
  • Iden­ti­ty­File ~/.ssh/synology-tutorial.ed25519: Spec­i­fies the pri­vate key file to use for authen­ti­ca­tion.

Before test­ing this con­fig­u­ra­tion, there is one more com­mand that might be help­ful because it will explic­it­ly store the passphrase to the key­chain and ssh-agent:

ssh-add --apple-use-keychain ~/.ssh/synology-tutorial.ed25519

Now, its pos­si­ble to test the con­nec­tion with the alias:

ssh synology

Pos­si­ble Issue: Per­mis­sions

Dur­ing a test on a fresh copy of macOS on a vir­tu­al machine, I encounter an issue that was not present on my work­ing Mac set up pre­vi­ous­ly:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/Users/leif/.ssh/synology-tutorial.ed25519.pub' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.

After some research, it was found that the per­mis­sions were not set cor­rect­ly. The fix was to change the per­mis­sions for the fold­er:

chmod 700 ~/.ssh

and for all files inside the fold­er:

chmod 600 ~/.ssh/*

After apply­ing these fix­es, every­thing should work cor­rect­ly.


The Backup Script

For the back­up script, I have the fol­low­ing require­ments:

1. rsync Command

Choose rsync because it:

  • Syn­chro­nize two fold­ers by only copy­ing changes and pre­serves all file and fold­er attrib­ut­es.
  • Syn­chro­nize files and fold­er from a local machine to a serv­er.
  • Delete files and fold­ers in the des­ti­na­tion that were delet­ed in the source, using the –delete option.
  • Archive delet­ed files and fold­ers in back­up fold­ers.

2. Backup Structure

The fold­er tree inside the Syn­ol­o­gy Home-fold­er should look like this. Besides the fold­er with the cur­rent Obsid­i­an Vault, there are back­up fold­ers named using the for­mat yyyy-mm-dd_hh-mm-ss:

myVault/ 
├── 2024-06-08_16-06-49 
├── 2024-06-08_17-06-49 
├── 2024-06-08_18-06-49 
└── current

3. Logging

  • Log each rsync exe­cu­tion with a time­stamp, com­mand out­put, and error mes­sages in a log file (/tmp/myVault.log).

4. Limited Number of backup folders

  • If the num­ber of back­ups exceeds a defined lim­it, delete the old­est back­ups to con­serve stor­age space.

5. 7. Conditional Execution

  • The back­up script should only run if the Syn­ol­o­gy NAS is reach­able.
  • If the NAS is not reach­able (e.g., not con­nect­ed to the local net­work), the script should exit with­out attempt­ing to run the back­up or delete old back­ups.

Complete Backup Script

Here is the com­plete script for back­ing up the Obsid­i­an Vault fold­er to a Syn­ol­o­gy NAS:

#!/bin/bash

# Check if Synology NAS is reachable
if ping -c 1 192.168.1.80 &> /dev/null; then
    # Log the start of the backup
    echo -e "\n\n$(date +"%Y-%m-%d %H:%M:%S")" >> /tmp/myVault.log

    # Define backup variables
    SOURCE="/Users/leif/Documents/10 Obsidian/myVault/"
    DESTINATION="synology:/volume1/homes/leif/myVault/current"
    BACKUP_DIR_BASE="/volume1/homes/leif/myVault"
    MAX_BACKUPS=30
    TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S)
    BACKUP_DIR="${BACKUP_DIR_BASE}/${TIMESTAMP}"

    # Run rsync with backup options
    rsync -avz --delete --backup --backup-dir="${BACKUP_DIR}" --exclude='.DS_Store' --filter 'P @eaDir/' -e ssh "$SOURCE" "$DESTINATION" >> /tmp/myVault.log 2>&1

    # Check number of backup folders and delete oldest if necessary
    BACKUP_COUNT=$(ssh synology "ls -1 ${BACKUP_DIR_BASE} | grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}$' | wc -l")
    
    if [ "$BACKUP_COUNT" -gt "$MAX_BACKUPS" ]; then
        echo "The number of backups ($BACKUP_COUNT) exceeds the maximum ($MAX_BACKUPS). The oldest backups will be deleted." >> /tmp/myVault.log
        ssh synology "ls -1 ${BACKUP_DIR_BASE} | grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}$' | sort | head -n -${MAX_BACKUPS} | xargs -I {} rm -rf ${BACKUP_DIR_BASE}/{}" >> /tmp/myVault.log 2>&1
    fi
else
    # Log if server is not reachable
    echo "SERVER is not reachable. Backup will not be performed." >> /tmp/myVault.log
fi

The Script Line-by-Line Breakdown

if ping -c 1 192.168.1.80 &> /dev/null; then

This line checks if the Syn­ol­o­gy NAS is reach­able by send­ing one ICMP pack­et (ping) to the IP address 192.168.1.80. If the NAS responds, the script con­tin­ues; oth­er­wise, it exits. The if-clause eval­u­ates the return code of the ping com­mand: when the ping gets a response, the return code is 1 (true); if not, the return code is 0 (false).

echo -e "\n\n$(date +"%Y-%m-%d %H:%M:%S")" >> /tmp/myVault.log

This line logs the cur­rent date and time to /tmp/myVault.log, indi­cat­ing when the back­up process start­ed. To struc­ture the log, this com­mand writes two new­lines before the time­stamp. All oth­er log entries will writ­ten direct­ly behind the pre­vi­ous line.

SOURCE="/Users/leif/Documents/10 Obsidian/myVault/"
DESTINATION="synology:/volume1/homes/leif/myVault/current"
BACKUP_DIR_BASE="/volume1/homes/leif/myVault"
MAX_BACKUPS=30
TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S)
BACKUP_DIR="${BACKUP_DIR_BASE}/${TIMESTAMP}"

These lines define the source direc­to­ry (SOURCE), the des­ti­na­tion direc­to­ry on the Syn­ol­o­gy NAS (DESTINATION), the base direc­to­ry for back­ups (BACKUP_DIR_BASE), the max­i­mum num­ber of back­ups to keep (MAX_BACKUPS), and the time­stamp for­mat for back­up direc­to­ries (TIMESTAMP and BACKUP_DIR).

rsync -avz --delete --backup --backup-dir="${BACKUP_DIR}" --exclude='.DS_Store' --filter 'P @eaDir/' -e ssh "$SOURCE" "$DESTINATION" >> /tmp/myVault.log 2>&1

This line runs rsync with the fol­low­ing options:

  • -a: Archive mode to pre­serve file attrib­ut­es.
  • -v: Ver­bose mode to log detailed out­put.
  • -z: Com­press data dur­ing trans­fer.
  • --delete: Delete files from the des­ti­na­tion that have been delet­ed from the source.
  • --backup: Enable back­ups of delet­ed files.
  • --backup-dir="${BACKUP_DIR}": Spec­i­fy the back­up direc­to­ry for delet­ed files.
  • --exclude='.DS_Store': Exclude .DS_Store files from the back­up.
  • --filter 'P @eaDir/': Pro­tect the @eaDir direc­to­ry from being delet­ed. This direc­to­ry is cre­at­ed by the Uni­ver­sal Search on the Syn­ol­o­gy. Since it’s not part of the source direc­to­ry, it would oth­er­wise always be delet­ed, and it con­tains a lot of files, so this would take time dur­ing every run of the script.
  • -e ssh: Use SSH for data trans­fer.
  • "$SOURCE" and "$DESTINATION": Source and des­ti­na­tion direc­to­ries.

The out­put and any errors are append­ed to /tmp/myVault.log.

BACKUP_COUNT=$(ssh synology "ls -1 ${BACKUP_DIR_BASE} | grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}$' | wc -l")

if [ "$BACKUP_COUNT" -gt "$MAX_BACKUPS" ]; then
    echo "The number of backups ($BACKUP_COUNT) exceeds the maximum ($MAX_BACKUPS). The oldest backups will be deleted." >> /tmp/myVault.log
    ssh synology "ls -1 ${BACKUP_DIR_BASE} | grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}$' | sort | head -n -${MAX_BACKUPS} | xargs -I {} rm -rf ${BACKUP_DIR_BASE}/{}" >> /tmp/myVault.log 2>&1
fi

These lines per­form the fol­low­ing actions:

  • Count the num­ber of exist­ing back­up fold­ers in BACKUP_DIR_BASE that match the time­stamp for­mat.
  • If the count exceeds MAX_BACKUPS, log a mes­sage indi­cat­ing that the old­est back­ups will be delet­ed.
  • Delete the old­est back­ups, retain­ing only the lat­est MAX_BACKUPS fold­ers.

There is a lit­tle unix (pipe) mag­ic involved, eg. the script call a func­tion on the Syn­ol­o­gy and store the result in a vari­able inside the script on the Mac. It pipes the result of an ls com­mand to an grep, that fil­ters with a regex expres­sion only the direc­to­ry that fol­low the back­up nam­ing con­ven­tion, and then count the num­ber of lines aka num­ber of back­up fold­er.

in the sec­ond part, if there are more then $MAX_BACKUPS, fil­ters the fold­ers again, sorts them by name and thus by age), deletes all new­er fold­er names from the list, and then pipes the result to a com­mand that deletes the fold­ers by adding each list item to an rm -rf com­mand.

else
    echo "SERVER is not reachable. Backup will not be performed." >> /tmp/myVault.log
fi

This han­dles the else part If the Syn­ol­o­gy NAS is not reach­able. It log a mes­sage indi­cat­ing that the back­up will not be per­formed.

Saving and Running the Script

1. Save the Script

Save the script in your per­son­al Appli­ca­tions fold­er ~/Applications as, for exam­ple, backup.sh, and make it exe­cutable with the fol­low­ing com­mand:

chmod +x ~/Applications/backup.sh

2. Create an Empty Log File

Cre­ate an emp­ty log file to store the script’s out­put:

touch /tmp/myVault.log

3. Run the Script

You can now run the script:

~/Applications/backup.sh

4. Check the Log

Check the log to ensure every­thing runs smooth­ly with­out any errors.

cat /tmp/myVault.log

Dur­ing the first run, all files will be copied to your Syn­ol­o­gy, result­ing in a long list of mes­sages. For sub­se­quent runs, the script will only log changes, so it might be a good idea to delete the log after the first run if every­thing was fine and then cre­ate a new one.

Creating a 1‑Click Backup Solution with Apple Shortcuts

Now we have a script that works very well when called in the ter­mi­nal. To cre­ate a 1‑click back­up solu­tion, we can inte­grate this script into an Apple Short­cut.

So, my first Idea was to copy the script into a “Run Shell Script” action with­in an Apple Short­cut:

A screenshot of the Apple Shortcuts app showing a shortcut named "Make Obsidian Backup." The shortcut uses a "Run Shell Script" action with a script that checks if a server is online, performs an `rsync` backup, and manages backup folder retention. The shortcut is set to run in the zsh shell, and it is pinned in the menu bar.

This works fine, when press­ing the play but­ton in the short­cut app. How­ev­er, if exe­cut­ed via the menu bar, you may encounter error mes­sages in the log file, such as

rsync error: some files could not be transferred (code 23) at /AppleInternal/Library/BuildRoots/91a344b1-f985-11ee-b563-fe8bc7981bff/Library/Caches/com.apple.xbs/Sources/rsync/rsync/main.c(996) [sender=2.6.9]

It took some time to find the cause of this error. The “Short­cuts” appli­ca­tion had been giv­en the nec­es­sary per­mis­sions to edit “Files and fold­ers” or “Full disk access” in the sys­tem set­tings under “Pri­va­cy and secu­ri­ty”. There­fore, the script could run with­in this appli­ca­tion. How­ev­er, a short­cut called from the menu bar no longer runs in the Short­cuts appli­ca­tion and should there­fore be giv­en these per­mis­sions itself. But this isn’t pos­si­ble any­more, because Apple allows only real appli­ca­tions to have these per­mis­sions. The solu­tion is to encap­su­late the script in an appli­ca­tion using Automa­tor, Apple Script Edi­tor, or Platy­pus, which I found in this arti­cle: launchd — How to run a Laun­chA­gent that runs a script which…

I decid­ed to go with Automa­tor to turn the script into a appli­ca­tion:

  • Open Automa­tor: Start Automa­tor and cre­ate a new doc­u­ment.
  • Select Doc­u­ment Type: Choose “Appli­ca­tion” as the doc­u­ment type.
  • Add Action: Search for the action “Run Shell Script”.
  • Copy Script: Copy the script into the “Run Shell Script” action.
  • Save the Appli­ca­tion: Save the result in ~/Applications as, for exam­ple, Obsid­i­an­Back­up.
A screenshot of the Automator app showing an application named "ObsidianBackup." The application includes a "Run Shell Script" action with a script that logs a timestamp, sets backup variables, checks if a server is online, performs an `rsync` backup, and manages backup folder retention. The shell script is set to run using `/bin/zsh`.

After sav­ing the script in Automa­tor as appli­ca­tion, grant it the nec­es­sary per­mis­sions in the “Pri­va­cy & Secu­ri­ty” Sys­tem Set­tings:

  • Go to “Sys­tem Pref­er­ences” > “Secu­ri­ty & Pri­va­cy” > “Pri­va­cy”.
  • Select “Files and Fold­ers” and add the appli­ca­tion again.

You can also sim­ply click the appli­ca­tion icon twice. Nor­mal­ly, you will be asked to allow access to the spe­cif­ic fold­er, in this case, Doc­u­ments.

By fol­low­ing these instruc­tions, the script can be exe­cut­ed with a sin­gle click using Apple Short­cuts, ensur­ing it runs smooth­ly and with the nec­es­sary per­mis­sions.

  • Cre­ate an emp­ty new short­cut an add only one action “Open App”.
  • Choose Obsid­i­an­Back­up as app. Done!

If the app is not in the drop-down menu, start the app once by click­ing the icon. You might also need to restart the Short­cuts app, but then the app should appear in the menu.

A screenshot of the Apple Shortcuts app showing a shortcut named “Obsidian Backup.” The shortcut contains an action to open the “ObsidianBackup” application. The shortcut is pinned in the menu bar, with various options for how it can be used or displayed, such as “Show in Share Sheet” and “Use as Quick Action.”

With that, the back­up can just be start­ed with a click on the Short­cuts menu.

BTW, I was won­der­ing why all my oth­er short­cuts which manip­u­late Obsid­i­an notes run with­out any per­mis­sion prob­lems, as well as some Python scripts I run via the Alfred app. This is because these oper­a­tions run inside apps which I have allowed to manip­u­late files or fold­ers with the nec­es­sary per­mis­sions

Creating a Launchd ‘plist’ File

Now we can back up the Obsid­i­an Vault or any oth­er fold­er with a click in the menu bar. But how can we run the script auto­mat­i­cal­ly, e.g., every hour? The solu­tion for run­ning scripts peri­od­i­cal­ly on macOS is the launchd sys­tem and ser­vice man­ag­er. launchd uses prop­er­ty list (plist) files to define ser­vices, spec­i­fy­ing details like when they should start, their run con­di­tions, and any depen­den­cies. Here’s the plist file to run the ObsidianBackup.app every hour:

<?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.leif.ObsidianBackup</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/open</string>
        <string>-W</string>
        <string>/Users/leif/Applications/ObsidianBackup.app</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>StartInterval</key>
    <integer>3600</integer>
</dict>
</plist>

Save this file as ~/Library/LaunchAgents/com.leif.ObsidianBackup.plist. This ensures that the back­up task will only run when you are logged in with your user account. The name of the plist file and the Label key should be the same. The Pro­gra­mAr­gu­ments key defines the com­mand line with which our Obsid­i­an­Back­up app will be exe­cut­ed. Each part of the com­mand line, sep­a­rat­ed by a space, is an item in the array. Although we save it as Obsid­i­an­Back­up, the name at the com­mand line lev­el is ObsidianBackup.app. The RunAt­Load key instructs launchd to start the pro­gram when you log in, and the Start­In­ter­val defines the time between runs of the back­up in sec­onds.

Now start the ser­vice with:

launchctl load ~/Library/LaunchAgents/com.leif.ObsidianBackup.plist

You can check if the ser­vice is run­ning with:

launchctl list | grep com.leif.ObsidianBackup

and by look­ing into the log file /tmp/myVault.log. If some­thing goes wrong, you can stop the job with:

launchctl unload ~/Library/LaunchAgents/com.leif.ObsidianBackup.plist

If you want to delete the ser­vice, first unload it and then delete the file:

rm ~/Library/LaunchAgents/com.leif.ObsidianBackup.plist

Conclusions and open questions

With this set­up, you should be able to eas­i­ly run an hourly back­up of your Obsid­i­an Vault or any oth­er fold­er on your Mac.

Dur­ing the devel­op­ment of the script, with some help from Chat­G­PT 4o, I encoun­tered two issues that I haven’t resolved yet.

1. Spaces in Fold­er Paths:

I usu­al­ly make my back­ups in the fold­er /volume1/home/leif/98 Backups, but I had issues with the space in the fold­er name when using vari­ables for the DESTINATION and BACKUP_DIR_BASE. To work around this, I cre­at­ed a fold­er with­out spaces on my Syn­ol­o­gy home direc­to­ry.

2. Per­mis­sions with Ded­i­cat­ed Shared Fold­er:

Due to the prob­lem men­tioned above and the issue with @eaDir, I tried cre­at­ing a ded­i­cat­ed shared fold­er that would not be indexed by “Uni­ver­sal Search”. How­ev­er, I encoun­tered some per­mis­sions prob­lems. Despite set­ting all per­mis­sions cor­rect­ly (as far as I under­stood), the per­mis­sions issues per­sist­ed. As result I stay with the solu­tion with a fold­er with no spaces in my Syn­ol­o­gy home direc­to­ry.

If any­one has tips for solv­ing these issues, they would be great­ly appre­ci­at­ed.

Addi­tion­al Con­sid­er­a­tions

Exclud­ing the .obsid­i­an Fold­er:

The cur­rent script copies the .obsidian set­tings fold­er as well. Most changes occur in this fold­er while work­ing in Obsid­i­an, so it might be a good idea to skip this fold­er. The notes are the crit­i­cal data to save, while set­tings and plu­g­ins can be rein­stalled if nec­es­sary.

Adjust­ing MAX_BACKUPS:

The MAX_BACKUPS=30 val­ue is just a guess and should be adjust­ed accord­ing to your spe­cif­ic needs.

Feel free to add any com­ments, cor­rec­tions, or crit­i­cisms.

Leave a Reply

Your email address will not be published. Required fields are marked *