2023-02-07 09:31:25 -08:00
|
|
|
#!/bin/bash
|
2024-03-02 10:20:19 -08:00
|
|
|
export BTRFS_BIN=$(which btrfs)
|
|
|
|
module_load confirm
|
|
|
|
module_load helpers
|
|
|
|
|
2024-11-04 07:01:55 -08:00
|
|
|
# NOTE: set this for sudo
|
2024-03-02 10:20:19 -08:00
|
|
|
# ALL ALL = (root) NOPASSWD:/bin/btrfs
|
|
|
|
|
2024-11-04 07:01:55 -08:00
|
|
|
is_subvol () {
|
|
|
|
if [[ $1 == "-r" ]]; then
|
|
|
|
if ! btrfs subvolume show "$2" 2> /dev/null | grep readonly &> /dev/null; then return 1; fi
|
|
|
|
else
|
|
|
|
btrfs subvolume show "$1" &> /dev/null
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2023-02-07 09:31:25 -08:00
|
|
|
make_subvol() {
|
|
|
|
local usesudo
|
|
|
|
local uid
|
|
|
|
local gid
|
|
|
|
[[ $EUID -ne 0 ]] && usesudo=sudo
|
|
|
|
uid=${2:-$USER}
|
|
|
|
gid=${3:-$uid}
|
2024-03-05 13:50:09 -08:00
|
|
|
mkdir -p $(dirname $1) &> /dev/null
|
2024-03-02 10:20:19 -08:00
|
|
|
echo $usesudo $BTRFS_BIN subvolume create $1
|
|
|
|
$usesudo $BTRFS_BIN subvolume create $1
|
2023-02-07 09:31:25 -08:00
|
|
|
echo $usesudo chown $uid:$gid $1
|
|
|
|
$usesudo chown $uid:$gid $1
|
|
|
|
}
|
|
|
|
|
2024-03-02 10:20:19 -08:00
|
|
|
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
|
2023-02-07 09:31:25 -08:00
|
|
|
|
2024-03-02 10:20:19 -08:00
|
|
|
}
|
2023-02-07 09:31:25 -08:00
|
|
|
|
2024-03-02 10:20:19 -08:00
|
|
|
# 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
|
|
|
|
# }
|
2023-02-07 09:31:25 -08:00
|
|
|
|
2024-03-02 10:20:19 -08:00
|
|
|
#ssudo mount -o subvol=opt /dev/sda4 /test
|
2023-02-22 11:39:34 -08:00
|
|
|
|
|
|
|
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
|
2024-03-02 10:20:19 -08:00
|
|
|
$usesudo $BTRFS_BIN send $src | $usesudo $BTRFS_BIN -q receive $dest
|
2023-02-22 11:39:34 -08:00
|
|
|
# cd $2 || return
|
2024-03-02 10:20:19 -08:00
|
|
|
$usesudo $BTRFS_BIN subvolume snapshot $dest/$src $dest/$name
|
2023-02-22 11:39:34 -08:00
|
|
|
# todo check for snapshot then delete transfered one
|
|
|
|
}
|
|
|
|
|
2024-03-02 10:20:19 -08:00
|
|
|
|
2023-02-07 09:31:25 -08:00
|
|
|
|
2023-02-22 11:39:34 -08:00
|
|
|
subvolume_size () {
|
|
|
|
local all;
|
|
|
|
[[ $1 == "-a" ]] && all=true
|
|
|
|
if [[ $all ]]; then
|
2024-03-02 10:20:19 -08:00
|
|
|
sudo $BTRFS_BIN qgroup show "${2:-$PWD}" --kbytes | tail -n +3
|
|
|
|
str=$(sudo $BTRFS_BIN qgroup show "${2:-$PWD}" --kbytes | tail -n +3);
|
2023-02-22 11:39:34 -08:00
|
|
|
else
|
2024-03-02 10:20:19 -08:00
|
|
|
sudo $BTRFS_BIN qgroup show "${2:-$PWD}" --kbytes | grep $1
|
|
|
|
str=$(sudo $BTRFS_BIN qgroup show "${2:-$PWD}" --kbytes | grep $1)
|
2023-02-22 11:39:34 -08:00
|
|
|
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"
|
|
|
|
|
2023-12-14 13:59:24 -08:00
|
|
|
}
|
|
|
|
|
2024-08-21 12:40:04 -07:00
|
|
|
# https://unix.stackexchange.com/questions/93324/how-does-this-find-command-using-find-exec-sh-c-sh-work
|
2023-12-16 08:27:37 -08:00
|
|
|
find_subvolumes() {
|
|
|
|
dir=${1:-.}
|
|
|
|
dep=${2:-1}
|
2024-08-21 12:40:04 -07:00
|
|
|
sudo /bin/find "$dir" -mindepth 1 -maxdepth $dep -type d -exec bash -c '
|
2023-12-14 13:59:24 -08:00
|
|
|
for d do
|
2023-12-16 08:27:37 -08:00
|
|
|
subv=$(basename $d)
|
2024-08-21 12:47:44 -07:00
|
|
|
sudo '$BTRFS_BIN' subvolume show "$d" >/dev/null 2>&1 && printf "%s\n" "$subv"
|
2023-12-14 13:59:24 -08:00
|
|
|
done' find-sh {} +
|
|
|
|
}
|
|
|
|
|
2023-12-16 08:27:37 -08:00
|
|
|
folder_snapshot() {
|
2024-04-20 16:43:25 -07:00
|
|
|
if [[ $1 == "-r" ]];then
|
|
|
|
readonly=-r
|
|
|
|
shift 1
|
|
|
|
else
|
|
|
|
readonly=""
|
|
|
|
fi
|
2023-12-16 08:27:37 -08:00
|
|
|
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
|
2024-08-23 14:15:40 -07:00
|
|
|
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
|
2023-12-16 08:27:37 -08:00
|
|
|
fi
|
|
|
|
done
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-04-20 16:43:25 -07:00
|
|
|
|
|
|
|
snapshot_send() {
|
2024-11-04 07:01:55 -08:00
|
|
|
|
|
|
|
# Show usage and exit with status
|
|
|
|
help () {
|
|
|
|
echo 'usage: snapshot_send -h,-r <source_path, full or relative> <destination_parent_dir> <optional, alternate directory name>'
|
|
|
|
}
|
|
|
|
|
|
|
|
if [[ $1 == "-h" ]]; then help; return; fi
|
|
|
|
|
|
|
|
local dr="echo "
|
|
|
|
if [[ $1 == "-r" ]]; then
|
|
|
|
unset dr; shift;
|
|
|
|
else
|
|
|
|
echo this will be a dry run, use -r to actually run the command
|
|
|
|
fi
|
|
|
|
|
|
|
|
if [[ $# -lt 2 ]]; then help; return 1; fi
|
|
|
|
|
|
|
|
# Check for directories
|
|
|
|
if ! is_subvol "$1"; then
|
|
|
|
echo source $1 not a subvolume
|
|
|
|
help
|
|
|
|
return 2
|
|
|
|
fi
|
|
|
|
|
|
|
|
if [[ ! -d "$2" ]]; then
|
|
|
|
echo destination $2 not a valid directory
|
|
|
|
help; return 3
|
|
|
|
fi
|
|
|
|
|
|
|
|
# Get paths
|
|
|
|
local src=$(abs_path "${1%/}")
|
|
|
|
local psrc="$(dirname $src)"
|
|
|
|
local dest=$(abs_path "${2%/}")
|
|
|
|
local snap=$(basename $src)
|
|
|
|
local ext="$(echo $snap | cut -s -f 2 -d '.')"
|
|
|
|
local name=$(echo $snap | cut -f 1 -d '.')
|
2024-04-20 16:43:25 -07:00
|
|
|
[[ $ext ]] && ext=".$ext"
|
|
|
|
[[ $3 ]] && ext=".$3"
|
2024-11-04 07:01:55 -08:00
|
|
|
|
|
|
|
local tsnap
|
|
|
|
if ! is_subvol -r $src; then
|
|
|
|
tsnap=$psrc/$name.tmp
|
|
|
|
$dr sudo $BTRFS_BIN sub snap -r $src $tsnap
|
|
|
|
fi
|
|
|
|
|
2024-04-20 16:43:25 -07:00
|
|
|
if [[ $dr ]]; then
|
2024-11-04 07:01:55 -08:00
|
|
|
echo "sudo $BTRFS_BIN send $([[ $tsnap ]] && echo $tsnap || echo $src) | btrfs receive $dest/$name$ext"
|
|
|
|
echo "[[ "$tsnap" ]] && sudo $BTRFS_BIN sub del $tsnap"
|
2024-04-20 16:43:25 -07:00
|
|
|
else
|
2024-11-04 07:01:55 -08:00
|
|
|
sudo $BTRFS_BIN send $([[ $tsnap ]] && echo $tsnap || echo $src) | btrfs receive $dest
|
|
|
|
mv $dest/$snap $dest/$name$ext
|
|
|
|
[[ "$tsnap" ]] && sudo $BTRFS_BIN sub del $tsnap
|
|
|
|
echo transfer is complete
|
|
|
|
ls -la $dest
|
|
|
|
ls -la $dest/$name$ext
|
|
|
|
fi
|
|
|
|
|
2024-04-20 16:43:25 -07:00
|
|
|
}
|
|
|
|
|
2024-03-02 10:20:19 -08:00
|
|
|
mount_subvolume () {
|
2024-03-05 13:50:09 -08:00
|
|
|
# echo sudo mount $1 -o subvol=$2 $3
|
|
|
|
if mountpoint $3 &> /dev/null; then
|
2024-03-02 10:20:19 -08:00
|
|
|
echo $3 already a mountpoint, aborting subvolume mount
|
|
|
|
else
|
2024-03-05 13:50:09 -08:00
|
|
|
mkdir -p $3 &> /dev/null
|
|
|
|
if ! sudo mount $1 -o subvol=$2 $3 > /dev/null ; then echo "failed to mount $2"; return 1; fi
|
2024-03-02 10:20:19 -08:00
|
|
|
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> <subvolume/snapshot> <new extension name> <optional target directory>"
|
|
|
|
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
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-03-05 13:50:09 -08:00
|
|
|
|
|
|
|
# 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 <subvol> <source> <dest>
|
|
|
|
# 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
|
|
|
|
}
|
|
|
|
|
2024-03-02 10:20:19 -08:00
|
|
|
# alias
|
|
|
|
alias btvl="sudo $BTRFS_BIN subvolume list"
|
|
|
|
alias btrfs="sudo $BTRFS_BIN"
|
|
|
|
alias btsub="sudo $BTRFS_BIN subvolume"
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-10-17 11:24:08 -07:00
|
|
|
dir2subvol () {
|
|
|
|
# Directory to convert into BTRFS subvolume
|
2024-03-02 10:20:19 -08:00
|
|
|
|
2024-10-17 11:24:08 -07:00
|
|
|
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
|
|
|
|
|
|
|
|
}
|
2023-12-16 08:27:37 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
2023-12-14 13:59:24 -08:00
|
|
|
# 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
|
2023-12-15 10:23:44 -08:00
|
|
|
# ' find-sh {} + \) \)
|