← All posts

Automate SFTP and S3 uploads on a Mac with Shortcuts

The usual way to automate SFTP uploads on a Mac is a shell script around sftp or scp, run by hand or on a schedule. That works, but the script needs credentials it can use unattended, which usually means an SSH key with no passphrase sitting in a file. If your file transfer app exposes actions to Shortcuts, you can build the same automation without a script holding secrets: the app keeps the credentials in the Keychain, and Shortcuts, Finder or launchd tells it what to send. This post covers both approaches, with working examples.

The script approach, and its trade-offs

OpenSSH's sftp has a batch mode that reads commands from a file:

# upload.batch
cd /var/www/example.com/downloads
put build/release.zip
sftp -b upload.batch deploy@example.com

In batch mode, sftp stops at the first failing command and exits non-zero, which makes it easy to check from a script. The catch is authentication: batch mode can't answer a password prompt, so it needs key-based login, and a scheduled job can't type a key passphrase either. You end up with a passphrase-less key, or an ssh-agent that has to be running and unlocked whenever the job fires. Neither is wrong, but it's worth knowing that's the cost.

The sftp manual page documents batch mode and the - prefix that lets a command fail without aborting the batch.

Using Shortcuts to automate SFTP uploads on a Mac

Shortcuts on macOS can run actions provided by apps, take files as input, and be triggered in several ways that don't involve opening the Shortcuts app:

Apple's Shortcuts User Guide for Mac covers building shortcuts and Quick Actions in detail.

Wharf's Shortcuts actions

Wharf adds four actions to Shortcuts, and they work with any saved site, whether SFTP, FTP, FTPS or S3:

ActionTakesReturns
Upload FileFile, Site, Remote FolderThe remote path it uploaded to
Download FileSite, Remote PathThe downloaded file
List FilesSite, FolderThe names in that folder, sorted
Sync FolderSite, Local Folder, Remote FolderA summary such as "3 uploaded, 0 downloaded"

The Site parameter is a picker of the sites already saved in Wharf, not a hostname field. A shortcut can only reach servers you've set up, and no password, key or secret ever appears in the shortcut itself. Leave Upload File's Remote Folder empty and it uses the site's start folder.

Sync Folder copies whatever is missing on either side, and where a file differs on both sides the newer copy wins. Files whose timestamps are too close to call are skipped and counted in the summary. It never deletes anything: deletion propagation needs a human to review the list, and a scheduled job has nobody to show it to. The post on sync deletions explains why that matters.

What a site needs before it can be automated

An automation has nobody to answer questions, so Wharf refuses rather than prompting, with an error saying what's missing:

Transfers made by these actions are recorded in Wharf's activity log alongside everything else, so an unattended upload is still on record.

Example: a Finder Quick Action that uploads

  1. In Shortcuts, create a new shortcut and name it "Upload to staging".
  2. In its details, turn on "Use as Quick Action" and choose Finder, so it receives files.
  3. Add Wharf's Upload File action. Set File to the shortcut's input, pick your staging site, and type the Remote Folder, for example /var/www/staging/uploads.
  4. Optionally, add a Show Notification action using the action's result, so you see the remote path when it's done.

Now right-click a file in Finder, choose Quick Actions, then "Upload to staging".

Example: a nightly upload on a schedule

First, build and test a shortcut, say "Nightly report upload", that uses Upload File or Sync Folder. Then check it runs from the terminal:

shortcuts run "Nightly report upload"

If the shortcut takes a file as input, pass it with --input-path:

shortcuts run "Upload to staging" --input-path ~/Desktop/report.pdf

To run it every night at 02:30, create ~/Library/LaunchAgents/com.example.nightly-upload.plist:

<?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.example.nightly-upload</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/bin/shortcuts</string>
    <string>run</string>
    <string>Nightly report upload</string>
  </array>
  <key>StartCalendarInterval</key>
  <dict>
    <key>Hour</key>
    <integer>2</integer>
    <key>Minute</key>
    <integer>30</integer>
  </dict>
</dict>
</plist>

Load it into your user session:

launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.nightly-upload.plist

A launch agent runs while you're logged in. If the Mac is asleep at 02:30, launchd runs the job when it wakes. To remove it later, use launchctl bootout with the same arguments.

Checking that it worked

Scheduled jobs fail quietly unless you look. Two places to check:

The Shortcuts actions are part of every edition of Wharf; see the features page for the rest, and the support page for credentials and Keychain questions.