summaryrefslogtreecommitdiff
path: root/pkgs/btclone/btclone.nix
diff options
context:
space:
mode:
authorAlejandro Soto <alejandro@34project.org>2022-04-01 07:23:25 -0600
committerAlejandro Soto <alejandro@34project.org>2022-04-01 08:00:55 -0600
commitf7cc6c83af50e339efd44ef5e4a56770952821ac (patch)
tree1e5064f1f72786d4edb7e7486391c3b9d357035e /pkgs/btclone/btclone.nix
parentf14678b49b32df08a8ed2abb608a4ad86e1f5094 (diff)
pkgs: add btclone
Diffstat (limited to 'pkgs/btclone/btclone.nix')
-rwxr-xr-xpkgs/btclone/btclone.nix107
1 files changed, 107 insertions, 0 deletions
diff --git a/pkgs/btclone/btclone.nix b/pkgs/btclone/btclone.nix
new file mode 100755
index 0000000..dce0cde
--- /dev/null
+++ b/pkgs/btclone/btclone.nix
@@ -0,0 +1,107 @@
+''
+ usage() { echo "Usage: $0 <subvolume> <target> [<up-to>]" >&2; exit 1; }
+
+ [[ $# -ge 2 && $# -le 3 && ( $# -ne 3 || $(("$3" + 0)) = "$3" ) ]] || usage
+ [[ $UID -eq 0 ]] || { echo "$0: requires root privileges" >&2; exit 1; }
+
+ SOURCE="$1"
+ TARGET="$2"
+ [ $# -ge 3 ] && UP_TO="$3" || UP_TO=
+
+ get_snapshots() {
+ FILTER="\$1 > 0"
+ [[ -n "$UP_TO" ]] && FILTER="\$1 > 0 && \$1 <= $UP_TO"
+
+ find "$1" -follow -maxdepth 1 -printf '%f\n' \
+ | sed 's@/$@@g' | awk "$FILTER { print }" | sort -n
+ }
+
+ clone() {
+ if [[ -n "$2" ]]; then
+ echo "Cloning $1 (incremental from $2)..." >&2
+ PARENT_ARG="-p$SOURCE/$2/snapshot"
+ else
+ echo "Cloning $1 (non-incremental)..." >&2
+ fi
+
+ FROM="$SOURCE/$1"
+ INTO="$TARGET/$1"
+
+ [[ -d "$INTO" ]] || mkdir "$INTO"
+ btrfs send "$PARENT_ARG" "$FROM/snapshot" | pv | btrfs receive "$INTO"
+ cp -a "$FROM/info.xml" "$INTO/"
+ }
+
+ cleanup() {
+ echo "Removing $1..." >&2
+
+ AT="$TARGET/$1"
+ [[ -d "$AT/snapshot" ]] && btrfs subvolume delete "$AT/snapshot"
+ rm -f "$AT/info.xml"
+ rmdir "$AT"
+ }
+
+ while true; do
+ ls -- "$SOURCE/" "$TARGET/" >/dev/null
+ mapfile -t SNAPSHOTS < <(get_snapshots "$SOURCE")
+ [[ ''${#SNAPSHOTS} -eq 0 ]] && { echo "$0: nothing to do" >&2; exit; }
+ mapfile -t EXISTENT < <(get_snapshots "$TARGET")
+
+ RESET=0
+ PARENT=
+ ALL_ARE_BAD=0
+ for SNAPSHOT in "''${SNAPSHOTS[@]}"; do
+ if [[ ! -e "$SOURCE/$SNAPSHOT" ]]; then
+ echo "Notice: resetting listings due to time lapse" >&2
+
+ RESET=1
+ break
+ elif [[ ! -s "$SOURCE/$SNAPSHOT/info.xml" ]]; then
+ echo "Warning: skipping source $SNAPSHOT due to invalid info.xml" >&2
+ continue
+ fi
+
+ CLONE=0
+ if [[ ! -e "$TARGET/$SNAPSHOT" ]]; then
+ CLONE=1
+ elif [[ $ALL_ARE_BAD -eq 1 || ! -f "$TARGET/$SNAPSHOT/info.xml" ]]; then
+ echo -n "Warning: bad copy $SNAPSHOT (" >&2
+ [[ $ALL_ARE_BAD -eq 1 ]] && echo -n "bad chain" >&2 || echo -n "incomplete" >&2
+ echo "), recreating..." >&2
+
+ cleanup "$SNAPSHOT"
+
+ CLONE=1
+ ALL_ARE_BAD=1
+ fi
+
+ if [[ $CLONE -eq 1 ]]; then
+ clone "$SNAPSHOT" "$PARENT"
+ elif [[ -z "$PARENT" ]]; then
+ echo "Initial snapshot present, all copies will be incremental" >&2
+ fi
+
+ chmod 750 "$TARGET/$SNAPSHOT"
+ PARENT="$SNAPSHOT"
+ done
+
+ [[ $RESET -eq 1 ]] && continue
+
+ echo "Copies updated, cleaning up..." >&2
+ for SNAPSHOT in "''${EXISTENT[@]}"; do
+ FOUND=0
+ for KEEP in "''${SNAPSHOTS[@]}"; do
+ if [ "$KEEP" = "$SNAPSHOT" ]; then
+ FOUND=1
+ break
+ fi
+ done
+
+ [ -n "$FOUND" ] || cleanup "$SNAPSHOT"
+ done
+
+ break
+ done
+
+ echo "Done" >&2
+''