Changing attributes and making reflinks over sshfs
SSHFS is a very useful tool: it allows mounting a remote server's filesystem locally over an ssh connection, with zero server-side configuration.
However, it comes with a pitfall: "advanced" filesystem operations like changing attributes or creating copy-on-write copies (reflinks) is not supported.
To the extent of my knowledge, there actually isn't a single remote filesystem solution that supports these features well. Even Linux's native NFS lacks these features.
Fortunately, there is a workaround that we can use: if we have SSH access to the server (which is a given if we're already using SSHFS), we can just run the commands directly on the server.
To make this process easier,
I made a script called sshfs-do.sh
.
It's very easy to use:
just execute
sshfs-do.sh <command>
while inside of an SSHFS mount,
and <command>
will be run on the server
hosting that mount.
So, for example, to make a file immutable with the chattr
command,
you would run sshfs-do.sh chattr +i file.txt
(and sshfs-do.sh chattr -i file.txt
to make it mutable again):
Similarly, run sshfs-do.sh cp --reflink=always source dest
to make a copy-on-write copy:
Running sshfs-do.sh
on its own spawns an interactive shell
to the server:
If you want to make use of my sshfs-do.sh
script,
the full code is below:
#!/bin/bash
pwd=$(pwd)
# Find the root of this mountpoint
mountpoint=$(df -P "$pwd" | awk 'NR==2 {print $6}')
# Sanity check: `mountpoint` must report this as a mountpoint
if ! (mountpoint "$mountpoint" > /dev/null)
then
echo "What the hell"
exit 1
fi
mount_entry=$(mount | grep "$mountpoint")
# Check if the mount is sshfs
if [[ ! "$mount_entry" == *"type fuse.sshfs"* ]]
then
echo "$mountpoint -- Not an sshfs mount"
exit 1
fi
remote_info=$(echo "$mount_entry" | awk '{print $1}')
# Check that info about the server could be extracted
if [ -z "$remote_info" ]; then
echo "No mount found for $mountpoint"
exit 1
fi
# Get user and host from the remote info
user_host=${remote_info%%:*}
host=${user_host#*@}
remote_path=${remote_info#*:}
relative_path="${pwd#${mountpoint}}"
# If command is given, run it.
# If no command is given, spawn a shell
if [ -z "$1" ]
then
command=sh
else
command="$@"
fi
# If no mountpoint given to sshfs,
# it will mount the home directory
if [ -z "$remote_path" ]
then
# In single quotes so that $HOME will resolve on the server
remote_path='$HOME'
fi
# Connect to server, cd to the correct directory,
# and run the command
ssh -t "$user_host" "cd \"$remote_path/$relative_path\" && $command"
Reuse ssh connections with controlmaster
If you're using password authentication to access the server, it can be a little annoying to type in the password every time. To fix this, we can enable the Controlmaster feature in the openssh client.
Controlmaster works by checking if a connection to the server already exists before opening a new one, and, if it does, openssh will reuse that connection, meaning that you don't need to authenticate again.
To enable controlmaster, simply add the following to
~/.ssh/config
:
Host *
ControlMaster auto
ControlPath ~/.ssh/master-socket/%r@%h:%p
ControlPersist 3s
Then, make sure that the socket directory exists:
mkdir ~/.ssh/master-socket