Created
April 27, 2023 07:55
-
-
Save 1x24/9ca8e44c20a5351203058815c4be6a05 to your computer and use it in GitHub Desktop.
Easily mount a remote Linux server to MacOS using Fuse-T and SSHFS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
################################################################################################################################################################## | |
################################################################################################################################################################## | |
# https://github.com/1x24 | |
# 1x24 AT tuta.io | |
# April 27, 2023 | |
################################################################################################################################################################## | |
# This BASH script automatically mounts remote directories from a Linux server to your MacOS computer, using SSHFS. | |
# It is assumed that you have already set up passwordless login using SSH keys. If not, you can use https://gist.github.com/1x24/c96a93d3c749e996319ac6001ef4e3ab | |
# The script will terminate its process if you didn't set up SSH key-based login to the remote server that you're attempting to mount. | |
# | |
# The technology: | |
# FUSE is commonly used for such user-space purposes, but it requires you to accept Kernel Extensions (kext) in your OS, which could be a rather alarming idea. | |
# Enter the recent FUSE-T project https://www.fuse-t.org/ - a kext-less user-space FUSE for MacOS that uses an NFS v4 local server rather than a kernel extension. | |
# Note that FUSE-T is for non-commercial use, and under some conditions. See https://github.com/macos-fuse-t/fuse-t/blob/main/License.txt for more details. | |
# This script also uses SSHFS (https://github.com/macos-fuse-t/sshfs) which is GNU GPL v2.0 | |
# Finally, to install both SSHFS and FUSE-T, this script uses Homebrew, the "missing package manager for MacOS" https://brew.sh/ | |
# | |
# Requirements: | |
# Ensure you have Apple XCode installed. If you're not sure, run "xcode-select --install" in Terminal. This will start an XCode installation if necessary. | |
# | |
# Caveat: | |
# I have a Base-64 encoded string inside this script, but I explained my reason below, as well as included the decoded string for your inspection. | |
# | |
# USAGE: | |
# Simply download the script, and let's say you save it as `mount.sh`. Then run `chmod +x mount.sh` to make it executable and finally run `bash mount.sh` | |
# Enjoy! | |
################################################################################################################################################################### | |
################################################################################################################################################################### | |
# | |
# name of this script | |
script_name="$0"; | |
# the user and their home (regardless of whether or not this is a sudo command) | |
orig_user="$(stat -f "%Su" /dev/console)"; | |
orig_home="$(eval echo ~$orig_user)"; | |
# important paths | |
private_key_path="" | |
mount_base_path="/Volumes/" | |
base_path="${orig_home}/.ssh" | |
ssh_config_path="${base_path}/config" | |
sshfs_config_path="${base_path}/.sshfs" | |
sshfs_install_ready_path="${base_path}/.sshfs_install_ready" | |
script_log_path="/tmp/$script_name.log" | |
# Clear out script install log file, or otherwise create it | |
echo -n "" >$script_log_path | |
# is homebrew ready? | |
is_brew_ready() { command -v "brew" >/dev/null 2>&1; } | |
# is the FUSE-T file system ready? | |
is_fuse_ready() { brew list "fuse-t" >/dev/null 2>&1; } | |
# is the SSHFS driver ready? | |
is_sshfs_ready() { brew list "fuse-t-sshfs" >/dev/null 2>&1; } | |
# add a line for remote_hostname into the file at $sshfs_config_path; create it if it doesn't exist. | |
add_sshfs_config(){ | |
local remote_hostname="$1"; local remote_port="$2"; local remote_user="$3"; local private_key_path="$4" | |
[ ! -f "$sshfs_config_path" ] && touch "$sshfs_config_path" | |
echo "${remote_hostname}|${remote_user}|${remote_port}|${private_key_path}" >> "$sshfs_config_path" | |
} | |
# remove the line that starts with remote_hostname from the file at $sshfs_config_path, if the file exists. | |
remove_sshfs_config(){ | |
local remote_hostname="$1" | |
[ -f "$sshfs_config_path" ] && sed -i.bak "/^${remote_hostname}|/d" "$sshfs_config_path" && rm "$sshfs_config_path.bak" | |
} | |
# fetch the remote_port, username and private key path from the host definition for $remote_hostname inside $ssh_config_path | |
fetch_ssh_config() { | |
local remote_hostname="$1" | |
[ -f "$ssh_config_path" ] && awk -v host="$remote_hostname" ' | |
$1 == "Host" { current_host = $2 } | |
current_host == host && $1 == "User" { user = $2 } | |
current_host == host && $1 == "Port" { port = $2 } | |
current_host == host && $1 == "IdentityFile" { idfile = $2 } | |
END { if (user) print user "|" (port ? port : 22) "|" idfile } | |
' "$ssh_config_path" | |
} | |
# test this SSH key $key_path to confirm if it can log $remote_user into $remote_hostname at port $remote_port | |
test_ssh_key(){ | |
local remote_hostname=$1; local remote_port=$2; local remote_user=$3; local key_path=$4; | |
sshOptions=(-o PasswordAuthentication=no -o PubkeyAuthentication=yes -o PreferredAuthentications=publickey -o StrictHostKeyChecking=no -o BatchMode=yes -o ConnectTimeout=5) | |
ssh "${sshOptions[@]}" -p "$remote_port" -i "$key_path" -q "$remote_user@$remote_hostname" "exit" >/dev/null 2>&1 | |
} | |
# find SSH keys in the default location (inside .ssh within the user's home) | |
find_default_ssh_keys(){ | |
local key_paths="" | |
local possible_key_paths=("${base_path}/id_ed25519" "${base_path}/id_rsa") | |
for key_path in "${possible_key_paths[@]}"; do | |
[ -f "$key_path" ] && key_paths="${key_paths},${key_path}" | |
done | |
echo "${key_paths#,}" | |
} | |
# connect to the remote server and fetch all eligible remote directories as a comma separated list of items which are each directory path piped to its group name | |
########################################################################################################################################## | |
###### This part retrieves the directories that this remote user is eligible to mount. The directories are sourced from three ###### | |
###### locations, and then automatically mounted. ###### | |
###### (1) /etc/ssh/sshfs_config owned by root with permissions 644. Comments begin with #. Each line has form <group_name>:<path> ###### | |
###### meaning all users in <group_name> are eligible to mount the directory <path>. If remote user belongs to the group, that ###### | |
###### path will be included as an eligible path. This specific config file is, of course, only managed by a server admin. ###### | |
###### (2) ~/.sshfs is a file inside the remote user's home directory, owned by the remote user. Each non-comment line contains a ###### | |
###### <path> and as long as the user has read-write access to that path, it will be included as an eligible path. ###### | |
###### (3) Regardless of the absence or presence of the first two config files, the script will always mount the remote user's ###### | |
###### home directory. And if the user's home directory was already included by earlier configs, only one home will mount. ###### | |
###### P.S. The script retrieves the groups as well, though they are not currently in use for this version of the script. ###### | |
########################################################################################################################################## | |
get_remote_folders_list(){ | |
### Base64 of the remote BASH script | |
local bash_script_base64='IyBzZXQgdGhlIGdyb3VwX2RpcnMgdmFyaWFibGUgYnkgbG9va2luZyBmb3IgZGlyZWN0b3JpZXMgaW4gL2V0Yy9zc2gvc3NoZnNfY29uZmlnIHRoYXQgdGhlIHVzZXIgaGFzIHBlcm1pc3Npb24gdG8gcmVhZAojIGlnbm9yZXMgYWxsIGNvbW1lbnQgbGluZXMgKHRob3NlIGJlZ2luIHdpdGggYSAjKSBhbmQgZW1wdHkgbGluZXMKZ3JvdXBfZGlycz0kKCAKICBbIC1mIC9ldGMvc3NoL3NzaGZzX2NvbmZpZyBdICYmIGF3ayAtRjogXAogICAgLXYgZ3JvdXBzPSIkKGlkIC1HbikiIFwKICAgIC12IHVzZXI9IiRVU0VSIiBcCiAgICAnQkVHSU4geyBPUlM9IiwiIH0gXAogICAgIC9eW14jXG5dLyB7IGlmIChpbmRleChncm91cHMsICQxKSAmJiBzeXN0ZW0oInRlc3QgLXIgIiAkMikgPT0gMCkgcHJpbnQgJDIgInwiICQxIH0nIFwKICAgIC9ldGMvc3NoL3NzaGZzX2NvbmZpZyB8fCBlY2hvICcnCikKCiMgc2V0IHRoZSBjdXN0b21fZGlycyB2YXJpYWJsZSBieSBsb29raW5nIGZvciBkaXJlY3RvcmllcyBpbiB+Ly5zc2hmcyB0aGF0IHRoZSB1c2VyIGhhcyBwZXJtaXNzaW9uIHRvIHJlYWQKIyBpZ25vcmVzIGFsbCBjb21tZW50IGxpbmVzICh0aG9zZSBiZWdpbiB3aXRoIGEgIykgYW5kIGVtcHR5IGxpbmVzCmN1c3RvbV9kaXJzPSQoIAogIFsgLWYgIiRIT01FLy5zc2hmcyIgXSAmJiBhd2sgXAogICAgLXYgdXNlcj0iJFVTRVIiIFwKICAgIC12IGdyb3Vwcz0iJChpZCAtRykiIFwKICAgICdCRUdJTiB7IE9SUz0iLCIgfSBcCiAgICAgL15bXiNcbl0vIHsgcGF0aD0kMDsgZ3JvdXA9Z2Vuc3ViKC8uKlwvKFteXC9dKylcL1teXC9dKyQvLCAiXFwxIiwgImciLCBwYXRoKTsgXAogICAgICAgaWYgKHN5c3RlbSgidGVzdCAtciAiIHBhdGgpID09IDApIHByaW50IHBhdGggInwiIGdyb3VwIH0nIFwKICAgICIkSE9NRS8uc3NoZnMiIHx8IGVjaG8gJycKKQoKIyBpZiBjdXN0b21fZGlycyBpcyBub3QgZW1wdHksIHJlcGxhY2UgdGhlIGdyb3VwIG5hbWVzIHdpdGggdGhlIGFjdHVhbCBncm91cCBuYW1lcyB1c2luZyBzdGF0IC1jICIlRyIKaWYgWyAtbiAiJGN1c3RvbV9kaXJzIiBdOyB0aGVuCiAgY3VzdG9tX2RpcnNfd2l0aF9ncm91cD0iIgogIElGUz0iLCIKICBmb3IgZGlyX2luZm8gaW4gJGN1c3RvbV9kaXJzOyBkbwogICAgZGlyPSQoZWNobyAiJGRpcl9pbmZvIiB8IGN1dCAtZCJ8IiAtZjEpCiAgICBncm91cD0kKHN0YXQgLWMgIiVHIiAiJGRpciIpCiAgICBjdXN0b21fZGlyc193aXRoX2dyb3VwPSIkY3VzdG9tX2RpcnNfd2l0aF9ncm91cCwkZGlyfCRncm91cCIKICBkb25lCiAgY3VzdG9tX2RpcnM9JChlY2hvICIkY3VzdG9tX2RpcnNfd2l0aF9ncm91cCIgfCBzZWQgInMvXiwvLyIpCmZpCgojIGNvbmNhdGVuYXRlIGdyb3VwX2RpcnMgYW5kIGN1c3RvbV9kaXJzCmFsbF9kaXJzPSIkZ3JvdXBfZGlycywkY3VzdG9tX2RpcnMiCgojIHJlbW92ZSBkaXJlY3RvcmllcyB0aGF0IHN0YXJ0IHdpdGggcGF0aHNfdG9fZXhjbHVkZQpwYXRoc190b19leGNsdWRlPSIvcm9vdCwvc3lzLC9ydW4sL2RldiwvYm9vdCwvcHJvYywvc2JpbiwvdXNyL3NiaW4sL3Vzci9iaW4sL3Vzci9sb2NhbCwvdG1wLC9ldGMsL3ZhciIKSUZTPSIsIgpmb3IgZXhjbHVkZV9wYXRoIGluICRwYXRoc190b19leGNsdWRlOyBkbwogIGFsbF9kaXJzPSQoZWNobyAiJGFsbF9kaXJzIiB8IHNlZCAicyxcKCRleGNsdWRlX3BhdGhbXix8XSp8W14sXSpcKSwsZyIpCmRvbmUKCiMgaW5jbHVkZSB0aGUgaG9tZV9kaXIgdmFyaWFibGUgdG8gdGhlIHVzZXIncyBob21lIGRpcmVjdG9yeSBhbmQgcHJpbWFyeSBncm91cCBuYW1lCmhvbWVfZGlyPSIvaG9tZS8kVVNFUnwkKGlkIC1nbiAkVVNFUikiCmFsbF9kaXJzPSIkYWxsX2RpcnMsJGhvbWVfZGlyIgoKIyBpbmNsdWRlIHBhdGhzIGZyb20gcGF0aHNfdG9faW5jbHVkZSBpZiB0aGV5IGFyZSByZWFkYWJsZSBhbmQgcHJlc2VudCBpbiBncm91cF9kaXJzIG9yIGN1c3RvbV9kaXJzCnBhdGhzX3RvX2luY2x1ZGU9Ii92YXIvd3d3IgpJRlM9IiwiCmZvciBpbmNsdWRlX3BhdGggaW4gJHBhdGhzX3RvX2luY2x1ZGU7IGRvCiAgaW5jbHVkZWRfcGF0aD0kKGVjaG8gIiRncm91cF9kaXJzLCRjdXN0b21fZGlycyIgfCBncmVwIC1vICIkaW5jbHVkZV9wYXRoW14sfF0qfFteLF0qIikKICBpZiBbIC1uICIkaW5jbHVkZWRfcGF0aCIgXTsgdGhlbgogICAgZGlyX3RvX2NoZWNrPSIkKGVjaG8gIiRpbmNsdWRlZF9wYXRoIiB8IGN1dCAtZCJ8IiAtZjEpIgogICAgaWYgdGVzdCAtciAiJGRpcl90b19jaGVjayI7IHRoZW4KICAgICAgYWxsX2RpcnM9IiRhbGxfZGlycywkaW5jbHVkZWRfcGF0aCIKICAgIGZpCiAgZmkKZG9uZQoKIyBjb25jYXRlbmF0ZSB0aGUgZmlsdGVyZWQgbGlzdCB3aXRoIGhvbWVfZGlyIGFuZCBmb3JtYXQgdGhlIG91dHB1dAplY2hvICIkYWxsX2RpcnMiIHwgXAogIHNlZCAicy8sXFwrLywvZzsgcy9eLC8vOyBzLyxcJC8vIiB8IFwKICB0ciAiLCIgIlxuIiB8IFwKICBzb3J0IC11IHwgXAogIHRyICJcbiIgIiwiIHwgXAogIHNlZCAicy9eW1x8LF0qLy87cy9bXHwsXSokLy8i' | |
local remoteCommand="echo $bash_script_base64 | base64 --decode | bash 2>/dev/null"; | |
######################################################################################################################################## | |
###### Okay, so what's all this gibberish above? It's simply a base64 representation of a BASH script to be sent server-side. ###### | |
###### I decided to keep things uniform between MacOS/Linux client-side MacOS (BASH) and client-side Windows Powershell scripts, ###### | |
###### but sadly, Powershell can't handle all the various forms of character escapes that BASH can, and so this was the best way. ###### | |
###### See the bottom of this file for the decoded BASH script. ###### | |
###### Don't take my word for it, though. You can confirm with a base64 de-converter e.g. https://www.base64decode.org/ ###### | |
######################################################################################################################################## | |
local remote_hostname="$1"; local remote_port="$2"; local remote_user="$3"; local private_key_path="$4"; | |
local sshOptions=(-o PasswordAuthentication=no -o PubkeyAuthentication=yes -o PreferredAuthentications=publickey -o StrictHostKeyChecking=no) | |
echo $(ssh "${sshOptions[@]}" -p "$remote_port" -i "$private_key_path" "$remote_user@$remote_hostname" "$remoteCommand") | |
} | |
# mount a specific remote folder using SSHFS | |
mount_remote_folder(){ | |
local remote_hostname="$1"; local remote_port="$2"; local remote_user="$3"; local private_key_path="$4"; local remote_data="$5" | |
local remote_dir_path=$(echo "$remote_data" | cut -d'|' -f1) | |
local remote_group=$(echo "$remote_data" | cut -d'|' -f2) | |
local dir_name=$(basename "$remote_dir_path") | |
local nfs_mountpoint="${mount_base_path}${remote_hostname}/${dir_name}" | |
# Check if the local mount point exists and is already mounted | |
if mount | grep -q "$nfs_mountpoint"; then | |
echo "Skipping directory $remote_dir_path since it already exists at $nfs_mountpoint..."; return | |
fi | |
# Check if the local mount point exists. Create it if not, and set ownership with 755 permissions | |
if [ ! -d "$nfs_mountpoint" ]; then | |
echo "Creating local mount point directory..."; | |
fi | |
sudo mkdir -m 775 -p "$nfs_mountpoint" >>$script_log_path 2>&1 | |
sudo chown "$orig_user" "$nfs_mountpoint" >>$script_log_path 2>&1 | |
# Mount the remote directory using SSHFS as the original user | |
sshfs -p "$remote_port" "$remote_user@$remote_hostname:$remote_dir_path" "$nfs_mountpoint" -o rw,reconnect,default_permissions,volname="${dir_name}",IdentityFile="$private_key_path",follow_symlinks,ServerAliveInterval=15,ServerAliveCountMax=3 >>$script_log_path 2>&1 | |
if [ $? -eq 0 ]; then | |
# Reset permissions | |
sudo chmod 775 "$nfs_mountpoint" >>$script_log_path 2>&1 | |
sudo chown "$orig_user" "$nfs_mountpoint" >>$script_log_path 2>&1 | |
# Mount was successful. Append command to automounter script | |
echo "Remote directory $remote_dir_path mounted at $nfs_mountpoint" | |
else | |
# Mount failed. | |
echo "Mounting directory $remote_dir_path failed. Skipping this mount..." | |
fi | |
} | |
# mount all remote folders that this remote user is eligible to read and write, AND have also been assigned to them | |
mount_eligible_folders() { | |
local remote_hostname="$1"; local remote_port="$2"; local remote_user="$3"; local private_key_path="$4"; | |
local remote_data_string=$(get_remote_folders_list "$remote_hostname" "$remote_port" "$remote_user" "$private_key_path"); | |
IFS=',' read -ra remote_data_array <<< "$remote_data_string"; | |
# Mount all remote data | |
local remote_data="" | |
for remote_data in "${remote_data_array[@]}"; do | |
mount_remote_folder "$remote_hostname" "$remote_port" "$remote_user" "$private_key_path" "$remote_data" | |
sleep 1 | |
done | |
} | |
# check for SSH keys in the default directory, and see if any of them work for this remote server | |
try_default_keys() { | |
local remote_hostname="$1" | |
local possible_keys=$(find_default_ssh_keys) | |
local remote_port="" | |
local remote_user="" | |
if [ -z "$possible_keys" ]; then | |
echo "Error: No valid SSH keys were found." | |
exit 1 | |
else | |
IFS=',' read -ra possible_keys_array <<< "$possible_keys" | |
read -rp "Enter the remote SSH port [22]: " -e remote_port | |
remote_port="${remote_port:-22}" | |
read -rp "Enter your username on the $remote_hostname server: " remote_user | |
local possible_key="" | |
for possible_key in "${possible_keys_array[@]}"; do | |
if test_ssh_key "$remote_hostname" "$remote_port" "$remote_user" "$possible_key"; then | |
private_key_path="$possible_key" | |
add_sshfs_config "$remote_hostname" "$remote_port" "$remote_user" "$private_key_path" | |
mount_eligible_folders "$remote_hostname" "$remote_port" "$remote_user" "$private_key_path" | |
break | |
fi | |
done | |
if [ -z "$private_key_path" ]; then | |
echo "Error: No valid SSH keys were found." | |
exit 1 | |
fi | |
fi | |
} | |
# attempt to use information from the ssh config file, as much as possible | |
use_ssh_config(){ | |
local remote_hostname="$1" | |
local result=$(fetch_ssh_config "$remote_hostname") | |
if [ -z "$result" ]; then | |
try_default_keys $remote_hostname | |
else | |
IFS='|' read -ra config <<< "$result" | |
local remote_user="${config[0]}" | |
local remote_port="${config[1]}" | |
local private_key_path="${config[2]}" | |
if test_ssh_key "$remote_hostname" "$remote_port" "$remote_user" "$private_key_path"; then | |
add_sshfs_config "$remote_hostname" "$remote_port" "$remote_user" "$private_key_path" | |
mount_eligible_folders "$remote_hostname" "$remote_port" "$remote_user" "$private_key_path" | |
else | |
echo "Error: Invalid SSH key" | |
exit 1 | |
fi | |
fi | |
} | |
# attempt to connect automatically using previously saved configuration details, if those exist | |
try_sshfs_config() { | |
if [ -s "$sshfs_config_path" ]; then | |
while read -r config_line; do | |
IFS='|' read -ra config <<< "$config_line" | |
local remote_hostname="${config[0]}" | |
local remote_user="${config[1]}" | |
local remote_port="${config[2]}" | |
local private_key_path="${config[3]}" | |
echo "Server: $remote_hostname" | |
if test_ssh_key "$remote_hostname" "$remote_port" "$remote_user" "$private_key_path"; then | |
mount_eligible_folders "$remote_hostname" "$remote_port" "$remote_user" "$private_key_path" | |
else | |
echo "Error: Invalid SSH key" | |
exit 1 | |
fi | |
done < "$sshfs_config_path" | |
else | |
local remote_hostname="" | |
read -rp "Enter remote host: " remote_hostname | |
use_ssh_config "$remote_hostname" | |
fi | |
} | |
# are all installation requirements ready? | |
is_install_ready(){ | |
if [ -f "$sshfs_install_ready_path" ]; then | |
return | |
else | |
if is_brew_ready; then | |
echo "Homebrew package manager is already installed." | |
else | |
echo "Homebrew package manager not found. Installing Homebrew." | |
while true; do echo -n '.'; sleep 1; done & bg_pid=$!; disown $bg_pid | |
NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" >>$script_log_path 2>&1 | |
kill $bg_pid; (echo; echo 'eval "$(/opt/homebrew/bin/brew shellenv)"') >> "$orig_home/.bash_profile" | |
eval "$(/opt/homebrew/bin/brew shellenv)"; echo "Done" | |
fi | |
if is_fuse_ready; then | |
echo "MacOS-FUSE-T for Homebrew is already installed." | |
else | |
echo "MacOS-FUSE-T for Homebrew not found. Installing MacOS-FUSE-T." | |
while true; do echo -n '.'; sleep 1; done & bg_pid=$!; disown $bg_pid | |
brew tap macos-fuse-t/homebrew-cask >>$script_log_path 2>&1 | |
brew install fuse-t >>$script_log_path 2>&1 | |
kill $bg_pid; echo "Done" | |
fi | |
if is_sshfs_ready; then | |
echo "SSHFS for FUSE-T is already installed." | |
else | |
echo "SSHFS not found. Installing SSHFS for FUSE-T." | |
while true; do echo -n '.'; sleep 1; done & bg_pid=$!; disown $bg_pid | |
brew install fuse-t-sshfs >>$script_log_path 2>&1 | |
kill $bg_pid; echo "Done" | |
fi | |
echo $(is_brew_ready && is_fuse_ready && is_sshfs_ready) | |
fi | |
} | |
# main driver function | |
main() { | |
local cmd="" | |
local required_commands=("awk" "sed" "curl" "stat" "eval" "sleep" "disown" "kill" "ssh" "cut" "basename" "cut" "mount" "grep" "read") | |
for cmd in "${required_commands[@]}"; do | |
command -v "$cmd" > /dev/null || { echo "Error: $cmd is not available in your shell"; exit 1; } | |
done | |
echo "###################################################################################################" | |
echo "To setup things seamlessly, this script may ask you to provide the MacOS password for $orig_user." | |
export SUDO_PROMPT="MacOS account password for $orig_user: " # apply custom sudo prompt (for clarity) | |
sudo -v # ask for sudo upfront and avoid hiccups | |
export SUDO_PROMPT="Password: " # reset default sudo prompt | |
echo "###################################################################################################" | |
if (! is_install_ready); then | |
echo "Error: some installation requirements are missing. Please contact the administrator." | |
exit 1 | |
else | |
touch "${sshfs_install_ready_path}" | |
try_sshfs_config | |
fi | |
} | |
main | |
################# The decoded remote-side BASH script ################# | |
################# | |
### This code finds directories that the user has read and write permissions to, and prints them along with their corresponding group names. | |
### The output is a comma-separated list of directories and group names. The sed and tr commands at the end of the script clean up the output | |
### by removing any extra commas and formatting the output as a comma-separated list. | |
### P.S. The script retrieves the groups as well, though they are not currently in use for this version of the script. | |
################# | |
# decoded string begins below, between the `remote_script_decoded` heredocs | |
:<<'remote_script_decoded' | |
# set the group_dirs variable by looking for directories in /etc/ssh/sshfs_config that the user has permission to read | |
# ignores all comment lines (those begin with a #) and empty lines | |
group_dirs=$( | |
[ -f /etc/ssh/sshfs_config ] && awk -F: \ | |
-v groups="$(id -Gn)" \ | |
-v user="$USER" \ | |
'BEGIN { ORS="," } \ | |
/^[^#\n]/ { if (index(groups, $1) && system("test -r " $2) == 0) print $2 "|" $1 }' \ | |
/etc/ssh/sshfs_config || echo '' | |
) | |
# set the custom_dirs variable by looking for directories in ~/.sshfs that the user has permission to read | |
# ignores all comment lines (those begin with a #) and empty lines | |
custom_dirs=$( | |
[ -f "$HOME/.sshfs" ] && awk \ | |
-v user="$USER" \ | |
-v groups="$(id -G)" \ | |
'BEGIN { ORS="," } \ | |
/^[^#\n]/ { path=$0; group=gensub(/.*\/([^\/]+)\/[^\/]+$/, "\\1", "g", path); \ | |
if (system("test -r " path) == 0) print path "|" group }' \ | |
"$HOME/.sshfs" || echo '' | |
) | |
# if custom_dirs is not empty, replace the group names with the actual group names using stat -c "%G" | |
if [ -n "$custom_dirs" ]; then | |
custom_dirs_with_group="" | |
IFS="," | |
for dir_info in $custom_dirs; do | |
dir=$(echo "$dir_info" | cut -d"|" -f1) | |
group=$(stat -c "%G" "$dir") | |
custom_dirs_with_group="$custom_dirs_with_group,$dir|$group" | |
done | |
custom_dirs=$(echo "$custom_dirs_with_group" | sed "s/^,//") | |
fi | |
# concatenate group_dirs and custom_dirs | |
all_dirs="$group_dirs,$custom_dirs" | |
# remove directories that start with paths_to_exclude | |
paths_to_exclude="/root,/sys,/run,/dev,/boot,/proc,/sbin,/usr/sbin,/usr/bin,/usr/local,/tmp,/etc,/var" | |
IFS="," | |
for exclude_path in $paths_to_exclude; do | |
all_dirs=$(echo "$all_dirs" | sed "s,\($exclude_path[^,|]*|[^,]*\),,g") | |
done | |
# include the home_dir variable to the user's home directory and primary group name | |
home_dir="/home/$USER|$(id -gn $USER)" | |
all_dirs="$all_dirs,$home_dir" | |
# include paths from paths_to_include if they are readable and present in group_dirs or custom_dirs | |
paths_to_include="/var/www" | |
IFS="," | |
for include_path in $paths_to_include; do | |
included_path=$(echo "$group_dirs,$custom_dirs" | grep -o "$include_path[^,|]*|[^,]*") | |
if [ -n "$included_path" ]; then | |
dir_to_check="$(echo "$included_path" | cut -d"|" -f1)" | |
if test -r "$dir_to_check"; then | |
all_dirs="$all_dirs,$included_path" | |
fi | |
fi | |
done | |
# concatenate the filtered list with home_dir and format the output | |
echo "$all_dirs" | \ | |
sed "s/,\\+/,/g; s/^,//; s/,\$//" | \ | |
tr "," "\n" | \ | |
sort -u | \ | |
tr "\n" "," | \ | |
sed "s/^[\|,]*//;s/[\|,]*$//" | |
remote_script_decoded | |
####################### End of BASH script ########################### |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment