#!/bin/bash # Exploring remote encryption using sshfs and gocryptfs # F-Hauri.ch - 2023 # Using gocryptfs in reverse mode for rsync # Don't touch this line: rsyncRoot='This line will be replaced by encrypted root dir path' # read -r pass < <(gpg -d /path/to/passphrase) # read -r pass < <(systemd-ask-password) pass='whatafuckingstrongpassphrase' declare -r pass # Don't export this! target='user@remotebackup:/path/to/backups/root' source="$HOME" excludes=( Downloads Music Videos .mozilla .cache ) exclude_wildcards=( '*~' '*.bak' ) mrcrypt="/run/user/$UID/mnts/rcrypt" # Reverse crypt mtarget="/run/user/$UID/mnts/target" # SSHFS mount point mucrypt="/run/user/$UID/mnts/ucrypt" # Remotely encrypted mkdir -p $mtarget $mucrypt $mrcrypt mountRCrypt() { # gocryptfs in reverse mode for sending local datas grep -q " $mrcrypt " /proc/mounts && return local string cmdexcl=() for string in "${excludes[@]}"; do cmdexcl+=(-exclude "$string") done for string in "${exclude_wildcards[@]}"; do cmdexcl+=(-exclude-wildcard "$string") done gocryptfs -q -ro -reverse "${cmdexcl[@]}" \ "$source" "$mrcrypt" <<<"$pass" } mountUCrypt() { # mount remote by sshfs, then gocryptfs for unencryption grep -q " $mucrypt " /proc/mounts && return grep -q " $mtarget " /proc/mounts || sshfs "$target" "$mtarget" gocryptfs -q "$mtarget" "$mucrypt" <<<"$pass" } initBackups() { # Initialize master key, build '/backup/root' tree, copy # .diriv file and retrieve encrypted '/backup/root' to store # into $rsyncRoot variable (at begin of script file). # # shellcheck disable=SC2087 # Local variable in here-document ssh ${target%:*} bash <<-eomktarget [[ -d ${target#*:} ]] || mkdir -p "${target#*:}" eomktarget [[ -f $source/.gocryptfs.reverse.conf ]] || gocryptfs -q -reverse -init "$source/" <<<"$pass" mountRCrypt # Copy gocrypt conf file scp -q "$source/.gocryptfs.reverse.conf" \ "$target/gocryptfs.conf" # Copy IV file from /backup/root to /. Same IV at both level. # Required for using same conf file at both level. scp -q "$mrcrypt/gocryptfs.diriv" "$target/" mountUCrypt # Create root target mkdir -p "$mucrypt/backup/root" # Browse sshfs mounted for encrypted name of 2 level subdir rsyncRoot=("$mtarget/"*/*/gocryptfs.diriv) rsyncRoot[0]=${rsyncRoot[0]#$mtarget/} rsyncRoot[0]=${rsyncRoot[0]%/gocryptfs.diriv} # Self edit for changing 1st occurence of rsyncRoot=... sed "x;/./ba;x; s|^rsyncRoot=.*$|rsyncRoot=('${rsyncRoot[0]}')|; tc;bb; :c; h; bb; :a; x; :b; " -i.bak "$0" } doRsync() { # Run rsync on encrypted datas (no chance to use exclude) local backdate local -i inum mountRCrypt rsync -axzS --zc=zstd --delete \ "$mrcrypt/." "$target/${rsyncRoot[0]}/." # Add dated dir on backup host mountUCrypt printf -v backdate 'root-%(%F-%H%M%S)T' -1 mkdir "$mucrypt/backup/$backdate" # Retrieve encrypted name of dated dir (by his inode number) inum=$(stat -c %i "$mucrypt/backup/$backdate") CrCpy=$( find "$mtarget/${rsyncRoot[0]%/*}" \ -maxdepth 1 -inum $inum ) CrCpy=${CrCpy##*/} # shellcheck disable=SC2087 # Local variable in here-document ssh ${target%:*} bash <<-eoTargetCpAl # Remove read-only IV file in order to copy them from rsync rm -f \ "${target#*:}/${rsyncRoot%/*}/$CrCpy/gocryptfs.diriv" cp -al "${target#*:}/${rsyncRoot[0]}/". \ "${target#*:}/${rsyncRoot[0]%/*}/$CrCpy" eoTargetCpAl } [[ -e $source/.gocryptfs.reverse.conf ]] || initBackups doRsync sync for mpnt in /run/user/1000/mnts/{{r,u}crypt,target};do fusermount -u "$mpnt" sleep .02 done