diff options
| author | Alejandro Soto <alejandro@34project.org> | 2022-04-01 07:23:25 -0600 |
|---|---|---|
| committer | Alejandro Soto <alejandro@34project.org> | 2022-04-01 08:00:55 -0600 |
| commit | f7cc6c83af50e339efd44ef5e4a56770952821ac (patch) | |
| tree | 1e5064f1f72786d4edb7e7486391c3b9d357035e | |
| parent | f14678b49b32df08a8ed2abb608a4ad86e1f5094 (diff) | |
pkgs: add btclone
| -rwxr-xr-x | pkgs/btclone/btclone.nix | 107 | ||||
| -rw-r--r-- | pkgs/btclone/default.nix | 6 | ||||
| -rw-r--r-- | pkgs/default.nix | 1 |
3 files changed, 114 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 +'' diff --git a/pkgs/btclone/default.nix b/pkgs/btclone/default.nix new file mode 100644 index 0000000..bb207ef --- /dev/null +++ b/pkgs/btclone/default.nix @@ -0,0 +1,6 @@ +{ btrfs-progs, pv, writeShellApplication, ... }: +writeShellApplication { + name = "btclone"; + runtimeInputs = [ btrfs-progs pv ]; + text = import ./btclone.nix; +} diff --git a/pkgs/default.nix b/pkgs/default.nix index 5ee0a63..712242e 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -13,6 +13,7 @@ in { exclude = [ "config" ]; }); + btclone = callPackage ./btclone {}; git-aliases = callPackage ./git-aliases.nix {}; scripts = callPackage ./scripts {}; tmux-lift = callPackage ./tmux-lift {}; |
