shell-base/modules/filesystem/btrfs/btrfs.mod

339 lines
9.6 KiB
Bash

#!/bin/bash
export BTRFS_BIN=$(which btrfs)
module_load confirm
module_load helpers
module_load path
# NOTE: set this for sudo
# ALL ALL = (root) NOPASSWD:/bin/btrfs
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
}
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() {
# 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 '.')
[[ $ext ]] && ext=".$ext"
[[ $3 ]] && ext=".$3"
local tsnap
if ! is_subvol -r "$src"; then
tsnap=$psrc/$name.tmp
$dr sudo "$BTRFS_BIN" sub snap -r "$src" "$tsnap"
fi
if [[ $dr ]]; then
echo "sudo $BTRFS_BIN send $([[ $tsnap ]] && echo "$tsnap" || echo "$src") | btrfs receive $dest"
echo "sudo mv $dest/$name.tmp $dest/$name$ext"
echo "[[ ""$tsnap"" ]] && sudo $BTRFS_BIN sub del $tsnap"
else
sudo "$BTRFS_BIN" send $([[ $tsnap ]] && echo "$tsnap" || echo "$src") | btrfs receive "$dest"
sudo mv "$dest"/"$name".tmp "$dest"/"$name"$ext
[[ "$tsnap" ]] && sudo "$BTRFS_BIN" sub del "$tsnap"
echo transfer is complete
ls -la "$dest"
ls -la "$dest"/"$name"$ext
fi
}
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> <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
}
# 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
}
# 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 {} + \) \)