#!/bin/bash export BTRFS_BIN=$(which btrfs) module_load confirm module_load helpers # set this for sudo # ALL ALL = (root) NOPASSWD:/bin/btrfs make_subvol() { local usesudo local uid local gid [[ $EUID -ne 0 ]] && usesudo=sudo uid=${2:-$USER} gid=${3:-$uid} mkdir -p $(dirname $1) &> /dev/null echo $usesudo $BTRFS_BIN subvolume create $1 $usesudo $BTRFS_BIN subvolume create $1 echo $usesudo chown $uid:$gid $1 $usesudo chown $uid:$gid $1 } del_subvol() { local usesudo; local delete [[ $1 == "-d" ]] && delete=true && shift [[ $EUID -ne 0 ]] && usesudo=sudo if $usesudo $BTRFS_BIN subvolume show "$1" &> /dev/null; then if [[ -v PS1 && ! $delete ]]; then confirm "DELETE the subvolume $(realpath "$1") (pass -d to avoid confirm)" || return 1 fi $usesudo $BTRFS_BIN subvolume delete $1 else echo "$1 not a btrfs subvolume, nothing to delete" fi } # make_base_subvols() { # svols="shell admin opt data docker images temp" # BTRFSDATAROOT=${BTRFSDATAROOT:-/mnt/data} # # cd $BTRFSDATAROOT || exit # for svol in $svols; do # make_subvol $BTRFSDATAROOT/$svol $@ # done # } #ssudo mount -o subvol=opt /dev/sda4 /test snapshot_restore () { local src=$1 local dest=$2 local name=${3:-$(echo $src| rev | cut -f 2- -d '.' | rev)} local usesudo echo copying $src at $PWD to $2 then renaming to $name # TODO check for snapshot, strip volume from source path, remove extra snapshot when done [[ $EUID -ne 0 ]] && usesudo=sudo $usesudo $BTRFS_BIN send $src | $usesudo $BTRFS_BIN -q receive $dest # cd $2 || return $usesudo $BTRFS_BIN subvolume snapshot $dest/$src $dest/$name # todo check for snapshot then delete transfered one } subvolume_size () { local all; [[ $1 == "-a" ]] && all=true if [[ $all ]]; then sudo $BTRFS_BIN qgroup show "${2:-$PWD}" --kbytes | tail -n +3 str=$(sudo $BTRFS_BIN qgroup show "${2:-$PWD}" --kbytes | tail -n +3); else sudo $BTRFS_BIN qgroup show "${2:-$PWD}" --kbytes | grep $1 str=$(sudo $BTRFS_BIN qgroup show "${2:-$PWD}" --kbytes | grep $1) fi # echo $str subvolumeFolderSize=0; while read line; do FIELDS=( $line ) thisLineKb="${FIELDS[2]/'.00KiB'/''}"; # echo $thisLineKb subvolumeFolderSize=$((subvolumeFolderSize+thisLineKb)); done <<< "$str" [[ $all ]] && echo "size of entire filesystem at ${2:-$PWD}" || echo size of subvolumes/snapshots $1 at "${2:-$PWD}" echo $subvolumeFolderSize KB echo "~ $((subvolumeFolderSize/1024)) MB" echo "~ $((subvolumeFolderSize/1024/1024)) GB" } # https://unix.stackexchange.com/questions/93324/how-does-this-find-command-using-find-exec-sh-c-sh-work find_subvolumes() { dir=${1:-.} dep=${2:-1} sudo /bin/find "$dir" -mindepth 1 -maxdepth $dep -type d -exec bash -c ' for d do subv=$(basename $d) sudo '$BTRFS_BIN' subvolume show "$d" >/dev/null 2>&1 && printf "%s\n" "$subv" done' find-sh {} + } folder_snapshot() { if [[ $1 == "-r" ]];then readonly=-r shift 1 else readonly="" fi for sv in $(find_subvolumes $1 1); do if [[ -d $2/$sv ]]; then echo snapshot $sv already exists in $2. You must manually delete target snapshots! else if sudo mkdir -p "$2" > /dev/null; then sudo $BTRFS_BIN sub snap $readonly "$1/$sv" "$2/$sv" else echo unable to make directory $2 so can not make subvolumes therein fi fi done } snapshot_send() { if [[ $1 == "-e" ]];then dr="" shift 1 else echo "snapshot_send <-e> " echo preface with -e to execute dr="echo" fi temp="$(dirname $1)/tmp_send" name=$(basename $1) name=$(echo $name | cut -f 1 -d '.') ext="$(echo $name | cut -s -f 2 -d '.')" [[ $ext ]] && ext=".$ext" [[ $3 ]] && ext=".$3" sudo mkdir $temp $dr sudo $BTRFS_BIN sub snap -r $1 $temp/$name$ext if [[ $dr ]]; then $dr "sudo $BTRFS_BIN send $temp/$name$ext | btrfs receive $2" else sudo $BTRFS_BIN send $temp/$name$ext | btrfs receive $2 fi $dr sudo $BTRFS_BIN sub del $temp/$name$ext ls -la $2 sudo rm -r $temp } mount_subvolume () { # echo sudo mount $1 -o subvol=$2 $3 if mountpoint $3 &> /dev/null; then echo $3 already a mountpoint, aborting subvolume mount else mkdir -p $3 &> /dev/null if ! sudo mount $1 -o subvol=$2 $3 > /dev/null ; then echo "failed to mount $2"; return 1; fi fi } named_snapshot () { local usage; local subvol; local target; local run; local usesudo [[ $1 == "-r" ]] && run=true && shift [[ $EUID -ne 0 ]] && usesudo=sudo subvol=$(realpath "$1") usage="usage: named_snapshot <-r> " if sudo $BTRFS_BIN subvolume show "$1" &> /dev/null; then # if [[ -v PS1 && ! $delete ]]; then [[ ! $2 ]] && echo no extension name for named snapshot && echo $usage && return 1 if [[ $3 ]]; then [[ ! -d $3 ]] && echo "target directory $3 does not exist, can not make snapshot" && echo $usage && return 2 target=$(realpath "$3")/$(basename "$(rm_ext "$subvol")").$2 else target="$(rm_ext "$subvol").$2" fi [[ -v PS1 && ! $run ]] && confirm "make snapshot of $subvol to $target (pass -r to avoid confirm )" || return 4 $usesudo "$BTRFS_BIN" sub snap "$subvol" "$target" else echo "$subvol not a btrfs subvolume, can't make named snapshot" echo $usage return 3 fi } # finds device and subvolume of a mountpoint (or a mountpoint above the given path) mp_subvol () { module_load filesystem local mp; local subvol mp=$(find_mountpoint $1) path=${1//"$mp"/} dev=$(mount | grep "$mp " | cut -f 1 -d ' ') IFS=, read -r -a input <<< $(findmnt -nt btrfs | grep "$mp ") for x in "${input[@]}"; do eval $(echo $x | grep subvol=) [[ $subvol ]] && echo $dev $subvol$path done } btrfs_clone () { # btrfs_clone # if [[ $# == "3" ]]; then module_load filesystem local src; local dest; local dr; local usesudo [[ $EUID -ne 0 ]] && usesudo=sudo dr="--dry-run" [[ $1 == "-r" ]] && dr="" && shift subvol=$1 src=$2 dest=$3 $usesudo umount /tmp/source &> /dev/null sudo mount $src /tmp/source # $usesudo umount /tmp/dest &> /dev/null # if mount_subvolume $src $subvol /tmp/source; then if ! make_subvol $dest/$(basename $subvol) &> /dev/null; then echo unable to make subvolume $1 at $dest; return 2; fi # $usesudo btrfs subvolume show $dest/$(basename $subvol) # if mount_subvolume $(mp_subvol $dest/$(basename $subvol)) /tmp/dest; then # echo cloning now # # echo $usesudo btrfs-clone --toplevel $dr -v /tmp/source /tmp/dest cmd="$usesudo btrbk $dr archive /tmp/source/$1 $dest/$1" echo "command: $cmd" if eval "$cmd"; then echo "######### $dest/$1 #########" ls -la /tmp/dest echo "#################################" else echo clone failed echo $cmd fi # else # echo unable to mount a destination subvolume $1 at $3 # fi # else # echo unable to mount source subvolume $1 from $2 # return 1 # fi # confirm press any key to unmount temporary mountpoints /tmp/source, /tmp/dest echo unmounting source $usesudo umount /tmp/source &> /dev/null # $usesudo umount /tmp/dest &> /dev/null } # alias alias btvl="sudo $BTRFS_BIN subvolume list" alias btrfs="sudo $BTRFS_BIN" alias btsub="sudo $BTRFS_BIN subvolume" dir2subvol () { # Directory to convert into BTRFS subvolume dirPath="$1" getfacl -R "$dirPath" > /tmp/_perms # Rename original dierctory sudo mv "${dirPath}" "${dirPath}_original" # Create btrfs subvolume sudo $BTRFS_BIN subvolume create "${dirPath}" # Copy as "reflink" for speed and save space sudo cp --archive --one-file-system --reflink=always \ "${dirPath}_original/." "${dirPath}" # Remove old directory sudo rm -rf --one-file-system "${dirPath}_original" setfacl --restore=/tmp/_perms rm -f /tmp/_perms } # sudo find / -type d -exec sh -c ' # for d do # btrfs subvolume show "$d" >/dev/null 2>&1 && printf "%s\n" "$d" # done' find-sh {} + # You may want to exclude paths that are beyond suspicion. The following code excludes /proc, /sys and /dev: # sudo find / -type d \( \ # \( -path /proc -prune \) -o \ # \( -path /sys -prune \) -o \ # \( -path /dev -prune \) -o \ # \( -exec sh -c ' # for d do # btrfs subvolume show "$d" >/dev/null 2>&1 && printf "%s\n" "$d" # done # ' find-sh {} + \) \)