summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xpkgs/btclone/btclone.nix107
-rw-r--r--pkgs/btclone/default.nix6
-rw-r--r--pkgs/default.nix1
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 {};