Skip to content

Instantly share code, notes, and snippets.

@lbussy
Created July 7, 2026 11:11
Show Gist options
  • Select an option

  • Save lbussy/4e556402959ff6204144041c1ecb24cb to your computer and use it in GitHub Desktop.

Select an option

Save lbussy/4e556402959ff6204144041c1ecb24cb to your computer and use it in GitHub Desktop.
Mount Raspberry Pi on Mac via macFUSE

mount-pi

mount-pi is a small macOS helper script for mounting Raspberry Pi project directories over SSHFS using macFUSE. It is intended for workflows where local tools such as VS Code or Codex need file-level access to a remote Pi checkout, while builds and tests still run on the Pi.

The script uses SSH aliases from ~/.ssh/config, so user names, hostnames, ports, keys, and host-key behavior stay in your normal SSH configuration.

What it does

mount-pi can:

  • Mount a remote Pi directory under $HOME/mnt/<name>
  • Open the mounted directory in VS Code
  • Unmount one configured mount by name
  • Unmount a specific mount path
  • Force-unmount all SSHFS-style mounts under $HOME/mnt
  • List current mounts under $HOME/mnt

Requirements

  • macOS
  • Homebrew
  • macFUSE
  • SSHFS for macOS
  • VS Code command-line launcher, code
  • SSH access to the remote Pi

Installing macFUSE and SSHFS

macFUSE provides the filesystem layer used by SSHFS on macOS. Installing the macFUSE package lets third-party FUSE filesystems run on macOS, but the filesystem implementation, such as SSHFS, must be installed separately. macFUSE also notes that newer macFUSE releases are moving toward FSKit support on macOS 26, but existing SSHFS workflows may still require the traditional macFUSE setup path. (macFUSE)

Install macFUSE:

brew install --cask macfuse

Install SSHFS for macOS:

brew install gromgit/fuse/sshfs-mac

Homebrew’s core sshfs formula currently lists bottle support for Linux, not macOS, so the gromgit/fuse/sshfs-mac tap is commonly used for macOS SSHFS installs. (Homebrew Formulae)

Important Apple Silicon security step

On Apple Silicon Macs, macFUSE may require enabling user-managed kernel extensions before the macFUSE extension can load. Apple documents this under Startup Security Utility as Reduced Security with Allow user management of kernel extensions from identified developers. (Apple Support)

The macFUSE getting-started instructions describe the same flow: select Reduced Security, enable Allow user management of kernel extensions from identified developers, approve the change, and restart the Mac. (GitHub)

Typical sequence:

  1. Install macFUSE.
  2. If macOS blocks the system extension, shut down or reboot as prompted.
  3. Boot into Recovery:
    • Shut down the Mac.
    • Press and hold the power button until startup options appear.
    • Choose Options.
  4. Open Startup Security Utility.
  5. Select the startup disk.
  6. Choose Security Policy.
  7. Select Reduced Security.
  8. Enable Allow user management of kernel extensions from identified developers.
  9. Restart macOS.
  10. After logging in, open System Settings → Privacy & Security.
  11. Approve the macFUSE system extension if macOS presents an Allow button.
  12. Restart again if prompted.

Do not skip the reboot steps. macFUSE may install successfully but fail to mount filesystems until the security policy and extension approval have fully taken effect.

Install the script

Save the script as:

$HOME/bin/mount-pi

Make it executable:

chmod +x "$HOME/bin/mount-pi"

Make sure $HOME/bin is on your PATH. For zsh:

echo 'export PATH="$HOME/bin:$PATH"' >> "$HOME/.zshrc"
source "$HOME/.zshrc"

Configure SSH aliases

Add an alias to ~/.ssh/config:

Host pi
  HostName pi.local
  User pi
  IdentityFile ~/.ssh/id_ed25519
  StrictHostKeyChecking accept-new
  ServerAliveInterval 15
  ServerAliveCountMax 2

Test normal SSH first:

ssh pi

The script expects the alias to work before it attempts an SSHFS mount.

Usage

Mount the default remote path:

mount-pi pi

This mounts:

pi:/home/pi

at:

$HOME/mnt/pi

and then opens that path in VS Code.

Mount a different remote path:

mount-pi pi /home/pi

Mount a remote path using a custom local mount name:

mount-pi pi /home/pi pi-dev

This mounts at:

$HOME/mnt/pi-dev

List current mounts under $HOME/mnt:

mount-pi --list

Unmount by mount name:

mount-pi --unmount pi

Unmount by explicit path:

mount-pi --unmount-path ~/mnt/pi

Unmount all mounts under $HOME/mnt:

mount-pi --unmount-all

Show help:

mount-pi --help

Recommended development workflow

Use the mounted path for editing:

code "$HOME/mnt/pi"

Why these SSHFS options are used

The script uses a conservative SSHFS option set intended for editor and Codex-style access:

-oServerAliveInterval=15
-oServerAliveCountMax=2
-oTCPKeepAlive=yes
-oConnectTimeout=10
-oreconnect
-oauto_cache
-ocache_timeout=2
-ocache_stat_timeout=2
-ocache_dir_timeout=2
-ocache_link_timeout=2
-oworkaround=rename
-odefer_permissions

The short cache timeouts reduce stale directory and file metadata without turning caching off completely. reconnect helps SSHFS recover from transient network interruptions. workaround=rename helps with editor save patterns that write temporary files and then rename them over the original.

Troubleshooting

sshfs: command not found

Install SSHFS:

brew install gromgit/fuse/sshfs-mac

Then check:

which sshfs
sshfs --version

macFUSE installed, but mounts fail

Revisit the Apple Silicon security steps. You may need to approve the macFUSE extension in System Settings → Privacy & Security and reboot. On Apple Silicon, kernel-extension policy changes require Recovery-mode security changes and a restart. (Apple Support)

Mount appears stale or VS Code hangs

Force-unmount and re-mount:

mount-pi --unmount pi
mount-pi pi

Or unmount by path:

mount-pi --unmount-path ~/mnt/pi

If necessary, use the all-mount cleanup:

mount-pi --unmount-all

Host key verification failed

Connect once with normal SSH and resolve the host-key prompt:

ssh pi

Then re-run the mount.

Permission denied

Verify your SSH alias:

ssh pi

Check the User, IdentityFile, and HostName entries in ~/.ssh/config.

VS Code does not open

Install the VS Code shell command from VS Code:

  1. Open VS Code.
  2. Press Command+Shift+P.
  3. Run Shell Command: Install 'code' command in PATH.
  4. Try again:
code "$HOME/mnt/pi"
#!/usr/bin/env bash
set -euo pipefail
MOUNT_ROOT="$HOME/mnt"
DEFAULT_REMOTE_PATH="/home/pi/"
usage() {
cat <<'EOF'
Usage:
mount-pi HOST_ALIAS [REMOTE_PATH] [MOUNT_NAME]
Unmount:
mount-pi --unmount HOST_ALIAS_OR_MOUNT_NAME
mount-pi --unmount-path PATH
mount-pi --unmount-all
Other:
mount-pi --list
mount-pi --help
Examples:
mount-pi wspr4
mount-pi wspr4 /home/pi
mount-pi wspr4 /home/pi pi4
mount-pi --unmount pi4
mount-pi --unmount-path ~/mnt/pi4
mount-pi --unmount-all
mount-pi --list
Notes:
HOST_ALIAS should be an entry from ~/.ssh/config.
SSH user, hostname, identity file, port, and host-key
policy are read from ~/.ssh/config.
EOF
}
expand_path() {
local path="$1"
case "$path" in
\~)
printf '%s\n' "$HOME"
;;
\~/*)
printf '%s\n' "$HOME/${path#\~/}"
;;
*)
printf '%s\n' "$path"
;;
esac
}
is_mounted() {
local path="$1"
mount | grep -q " on $path "
}
list_mounts() {
echo "SSHFS mounts under $MOUNT_ROOT:"
mount | awk -v root="$MOUNT_ROOT" '
$0 ~ " on " root "/" {
print " " $0
}
'
}
force_unmount_path() {
local path
path="$(expand_path "$1")"
if [[ ! -d "$path" ]]; then
echo "Mount path does not exist: $path"
return 1
fi
if ! is_mounted "$path"; then
echo "Not mounted: $path"
return 0
fi
echo "Unmounting $path"
diskutil unmount force "$path" >/dev/null 2>&1 ||
umount -f "$path" >/dev/null 2>&1 ||
/sbin/umount -f "$path" >/dev/null 2>&1 || {
echo "Failed to unmount: $path" >&2
return 1
}
echo "Unmounted $path"
}
unmount_by_name() {
local name="$1"
local path="$MOUNT_ROOT/$name"
force_unmount_path "$path"
}
unmount_all() {
local mounted_paths=()
while IFS= read -r line; do
mounted_paths+=("$line")
done < <(
mount | awk -v root="$MOUNT_ROOT" '
$0 ~ " on " root "/" {
split($0, parts, " on ")
split(parts[2], rest, " ")
print rest[1]
}
'
)
if [[ ${#mounted_paths[@]} -eq 0 ]]; then
echo "No SSHFS-style mounts found under $MOUNT_ROOT"
return 0
fi
for path in "${mounted_paths[@]}"; do
force_unmount_path "$path"
done
}
mount_host() {
local host_alias="$1"
local remote_path="${2:-$DEFAULT_REMOTE_PATH}"
local mount_name="${3:-$host_alias}"
local mount_path="$MOUNT_ROOT/$mount_name"
local remote="$host_alias:$remote_path"
mkdir -p "$mount_path"
if is_mounted "$mount_path"; then
echo "Existing mount found at $mount_path"
force_unmount_path "$mount_path"
fi
echo "Mounting $remote at $mount_path"
sshfs "$remote" "$mount_path" \
-ovolname="$mount_name" \
-oServerAliveInterval=15 \
-oServerAliveCountMax=2 \
-oTCPKeepAlive=yes \
-oConnectTimeout=10 \
-oreconnect \
-oauto_cache \
-ocache_timeout=2 \
-ocache_stat_timeout=2 \
-ocache_dir_timeout=2 \
-ocache_link_timeout=2 \
-oworkaround=rename \
-odefer_permissions
echo "Mounted $remote at $mount_path"
cd "$HOME"
code "$mount_path"
}
main() {
if [[ $# -eq 0 ]]; then
usage
exit 2
fi
case "$1" in
-h | --help)
usage
exit 0
;;
--list)
list_mounts
exit 0
;;
--unmount)
if [[ $# -ne 2 ]]; then
echo "Error: --unmount requires HOST_ALIAS_OR_MOUNT_NAME" >&2
usage
exit 2
fi
unmount_by_name "$2"
exit 0
;;
--unmount-path)
if [[ $# -ne 2 ]]; then
echo "Error: --unmount-path requires PATH" >&2
usage
exit 2
fi
force_unmount_path "$2"
exit 0
;;
--unmount-all)
if [[ $# -ne 1 ]]; then
echo "Error: --unmount-all takes no extra arguments" >&2
usage
exit 2
fi
unmount_all
exit 0
;;
--*)
echo "Unknown option: $1" >&2
usage
exit 2
;;
*)
if [[ $# -gt 3 ]]; then
echo "Error: too many arguments" >&2
usage
exit 2
fi
mount_host "$@"
;;
esac
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment