From 4a1b788a52f3da681628fe5d56df6bfdf6092f3b Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Sat, 18 Oct 2025 12:31:49 -0700 Subject: [PATCH 01/22] Add baksnapperd-denotebak.sh [#45] A backend for backing up DeNotebak snapshots. --- baksnapperd-denotebak.sh | 164 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 baksnapperd-denotebak.sh diff --git a/baksnapperd-denotebak.sh b/baksnapperd-denotebak.sh new file mode 100644 index 0000000..65244a3 --- /dev/null +++ b/baksnapperd-denotebak.sh @@ -0,0 +1,164 @@ +#! /usr/bin/env bash + +# Baksnapperd-denotbak - Daemon used by baksnapper when backup DeNotebak snapshots + +# SPDX-FileCopyrightText: 2025 Fredrik Salomonsson +# +# SPDX-License-Identifier: GPL-3.0-or-later + +read -rd '' version <. +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. +EOF + +shopt -s extglob + +function error { + echo "[ERROR] $1" 1>&2 + exit 1 +} + +function warning { + echo -e "[Warning] $1" 1>&2 +} + +case "$1" in + version) + # Return what version of the API this is using. + # + # It always a single integer. + # No input. + echo 4 + ;; + snapshot-type) + # Return what type of snapshot this backend supports + # + # No input. + echo denotebak + ;; + list-snapshots) + # List snapshots at location. + # + # Stripping any tags as they are volatile. + # 1 [in]: Path to the directory the snapshots are expected to be in. + shift + find "$1" -mindepth 1 -maxdepth 1 -type d -printf "%f\n" | sed 's/__.*//' | sort -g + ;; + verify-snapshot) + # Verify that a snapshot exist. + # + # 1 [in]: File path to a snapshot — sans tags. + # Return 0 if it exist, non-zero otherwise. + shift + test -d "$1"?(__*) || error "Snapshot $1 doesn't exist." + ;; + incomplete-snapshot) + # Check if a snapshot is incomplete. + # + # 1 [in]: Path to the directory the snapshot is in. + # 2 [in]: Name of the snapshot — sans tags. + # Return 0 if the snapshot is incomplete, non-zero otherwise. + shift + [[ $(btrfs property get "$1/$2"?(__*) ro) != "ro=true" ]] + ;; + create-location) + # Prepare destination to receive a snapshot. + # + # 1 [in]: Path to the directory the snapshots will be in. + shift + mkdir -p "$1" + ;; + receive-snapshot) + # Receive a snapshot. + # + # 1 [in]: Path to the directory the snapshot will be in. + # 2 [in]: Name of the snapshot — sans tags. + shift + btrfs receive "$1" + ;; + send-snapshot) + # Send a snapshot. + # + # 1 [in]: Path to the directory the snapshot is in. + # 2 [in]: Name of the snapshot — sans tags. + shift + btrfs send "$1/$2"?(__*) + ;; + send-incremental-snapshot) + # Only send what is different from the parent snapshot + # + # 1 [in]: File path to the parent snapshot — sans tags. + # 2 [in]: File path to the snapshot — sans tags. + shift + btrfs send -p "$1"?(__*) "$2"?(__*) + ;; + remove-snapshots) + # Remove all snapshots listed in the input. + # + # 1 [in]: Path to the directory the snapshots are in. + # 2+ [in]: Name of the snapshots — sans tags. + shift + dest_root=$1 + shift + for snapshot in "$@" + do + if btrfs subvolume show "$dest_root/$snapshot"?(__*) &> /dev/null + then + echo "Deleting snapshot $snapshot" + btrfs subvolume delete "$dest_root/$snapshot"?(__*) + else + warning "Snapshot $snapshot does not match a DeNotebak snapshot, ignoring it." + fi + done + ;; + remove-broken-snapshot) + # Remove a broken snapshot. + # + # 1 [in]: Path to the directory the snapshot is in. + # 2 [in]: Name of the snapshot — sans tags. + shift + dest_root=$1 + snapshot=$2 + if btrfs subvolume show "$dest_root/$snapshot"?(__*) &> /dev/null + then + btrfs subvolume delete "$dest_root/$snapshot"?(__*) + fi + ;; + link-latest) + # Create symlink `latest` to the latest snapshot. + # + # 1 [in]: Path to the directory the snapshots are in. + set -x + shift + declare -a snapshots + if [[ -e "$1/latest" ]]; then + if [[ -h "$1/latest" ]]; then + rm "$1/latest" + else + error "$1/latest exists and is not a symbolic link. Link is not created." + fi + fi + for dir in $(find "$1" -maxdepth 1 -mindepth 1 -type d -printf "%P\n"|sort) + do + if btrfs subvolume show "$1/$dir" &> /dev/null && [[ ! -h "$1/$dir" ]] + then + snapshots+=("$dir") + fi + done + if [ ${#snapshots[@]} -ne 0 ]; then + ln -sfn "${snapshots[-1]}" "$1/latest" + else + error "link-latest: No suitable snapshots at $1 (${#snapshots[@]})" + fi + ;; + --version) + echo -e "$version" + exit 0 + ;; + *) + error "Unrecognized command '$1', bailing out!" + ;; +esac From 5c3c9b80cb273823a766fe80b4cfed8067324610 Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Sat, 18 Oct 2025 14:59:24 -0700 Subject: [PATCH 02/22] Add baksnapperd command `snapshot-type` [#45] --- baksnapperd.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/baksnapperd.sh b/baksnapperd.sh index 61f3469..9cdbb9c 100755 --- a/baksnapperd.sh +++ b/baksnapperd.sh @@ -35,6 +35,12 @@ case "$1" in # No input. echo 4 ;; + snapshot-type) + # Return what type of snapshot this backend supports + # + # No input. + echo snapper + ;; list-snapshots) # List snapshots at location. # From 976b42894fca66b9f634e6961205e5c99ad45e6c Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Sat, 18 Oct 2025 14:59:47 -0700 Subject: [PATCH 03/22] Add support to backup DeNotebak type snapshots --- baksnapper.sh | 69 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 15 deletions(-) diff --git a/baksnapper.sh b/baksnapper.sh index 2bbd850..a15bf9e 100755 --- a/baksnapper.sh +++ b/baksnapper.sh @@ -466,6 +466,16 @@ then fi fi + if [[ $sender_version -ge 4 && $($sender snapshot-type) != "snapper" ]] + then + error "config interface only supports snapper" + fi + + if [[ $receiver_version -ge 4 && $($receiver snapshot-type) != "snapper" ]] + then + error "config interface only supports snapper" + fi + src_root=${subvolume:?}/.snapshots dest_root="$dest/$p_config" elif [[ -n "$p_src" && -n "$p_dest" ]] @@ -523,6 +533,32 @@ else error "You need to specify the SOURCE and DEST or the config name to backup!" fi +# Get what snapshot type the daemon supports +# 1 [out]: variable where it will save the snapshot type. +# 2 [in]: variable that contains the baksnapperd backend +# 3 [in]: the version of the baksnapperd backend +function get-snapshot-type { + declare -n snapshot_type=$1 + if [[ $3 -ge 4 ]] + then + snapshot_type=$(${!2} snapshot-type) + else + # shellcheck disable=SC2034 + backend=snapper + fi +} +get-snapshot-type sender_snapshot_type sender "$sender_version" +get-snapshot-type receiver_snapshot_type receiver "$receiver_version" + +# shellcheck disable=SC2154 +if [[ "$sender_snapshot_type" != "$receiver_snapshot_type" ]] +then + error "Sender and receiver expects different snapshot types: sender: \ +$sender_snapshot_type, receiver: $receiver_snapshot_type" +fi + +snapshot_type=$sender_snapshot_type + ## Setup signals ############################################################### # EXIT --> if notify-send is installed, inform the user; always cleanup if hash notify-send 2> /dev/null @@ -645,15 +681,17 @@ function single-backup { local start_time start_time=$(date +%s) - $receiver create-snapshot "$dest_root" "$1" || - error "Failed to create snapshot at backup location!" - - if ! $sender send-info "$src_root" "$1" | $receiver receive-info "$dest_root" "$1" + if [[ "$snapshot_type" == "snapper" ]] then - $receiver remove-broken-snapshot "$dest_root" "$1" - error "Failed to send snapshot info!" - fi + $receiver create-snapshot "$dest_root" "$1" || + error "Failed to create snapshot at backup location!" + if ! $sender send-info "$src_root" "$1" | $receiver receive-info "$dest_root" "$1" + then + $receiver remove-broken-snapshot "$dest_root" "$1" + error "Failed to send snapshot info!" + fi + fi printf "%s\t\t%s\t" "${dest_root}" "${1}" >>"${p_summary}" exec 4>"${p_temp_dir}/${1}" if ! $sender send-snapshot "$src_root" "$1" | tee >( wc -c >&4 ) | $receiver receive-snapshot "$dest_root" "$1" @@ -676,16 +714,17 @@ function incremental-backup { start_time=$(date +%s) echo "Incremental backup $1 $2" - - $receiver create-snapshot "$dest_root" "$2" || - error "Failed to create snapshot at backup location!" - - if ! $sender send-info "$src_root" "$2" | $receiver receive-info "$dest_root" "$2" + if [[ "$snapshot_type" == "snapper" ]] then - $receiver remove-broken-snapshot "$dest_root" "$1" - error "Failed to send snapshot info!" - fi + $receiver create-snapshot "$dest_root" "$2" || + error "Failed to create snapshot at backup location!" + if ! $sender send-info "$src_root" "$2" | $receiver receive-info "$dest_root" "$2" + then + $receiver remove-broken-snapshot "$dest_root" "$1" + error "Failed to send snapshot info!" + fi + fi printf "%s\t%s\t%s\t" "${dest_root}" "${1}" "${2}" >>"${p_summary}" exec 4>"${p_temp_dir}/${2}" if ! $sender send-incremental-snapshot "$src_root/"{"$1","$2"} \ From d2c78a3d184ff084f03373ba75f163d9ff8cfbd4 Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Sat, 25 Oct 2025 16:57:03 -0700 Subject: [PATCH 04/22] Install baksnapperd-denotebak backend [#45] --- meson.build | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/meson.build b/meson.build index 49d520c..57879d2 100644 --- a/meson.build +++ b/meson.build @@ -30,6 +30,16 @@ baksnapperd = custom_target( install_tag: 'runtime', ) +baksnapperd_denotebak = custom_target( + command: [install_cmd, '@INPUT@', '@OUTPUT@'], + input: 'baksnapperd-denotebak.sh', + output: 'baksnapperd-denotebak', + install: true, + install_dir: 'bin', + install_mode: 'rwxr-xr-x', + install_tag: 'runtime', +) + bsconf_root = get_option('bsconf-root') if bsconf_root == '' bsconf_root = get_option('sysconfdir') From b11303b20601ce8a9dec41ff7931415e3db78f2c Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Sat, 1 Nov 2025 12:31:39 -0700 Subject: [PATCH 05/22] Add support for DeNotebak in baksnapper [#45] --- baksnapper.sh | 91 +++++++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 47 deletions(-) diff --git a/baksnapper.sh b/baksnapper.sh index a15bf9e..b77ce11 100755 --- a/baksnapper.sh +++ b/baksnapper.sh @@ -559,6 +559,14 @@ fi snapshot_type=$sender_snapshot_type +# Using a bitmap to represent where a snapshot is located: +# 0b01 → exist at the source +# 0b10 → exist at the destination +# 0b11 → exist at both locations, i.e. common +declare -A common +only_in_src=() +only_in_dest=() + ## Setup signals ############################################################### # EXIT --> if notify-send is installed, inform the user; always cleanup if hash notify-send 2> /dev/null @@ -633,28 +641,20 @@ function gather-receiver-snapshots { # only_in_src: only exist at the source # only_in_dest: only exist at the destination function compare-snapshots { - idx_src=0 idx_dest=0 - - # Using a bitmap to represent where a snapshot is located: - # 0b01 → exist at the source - # 0b10 → exist at the destination - # 0b11 → exist at both locations, i.e. common - common=() only_in_src=() only_in_dest=() - # NOTE: the snapshots must be sorted in ascending order hence the # "sort -g" when getting the snapshots. while (( idx_src < num_src_snapshots && idx_dest < num_dest_snapshots )) do - if [ "${src_snapshots[idx_src]}" -eq "${dest_snapshots[idx_dest]}" ] + if [[ "${src_snapshots[idx_src]}" == "${dest_snapshots[idx_dest]}" ]] then common["${src_snapshots[idx_src]}"]=0b11 ((++idx_src)) ((++idx_dest)) - elif [ "${src_snapshots[idx_src]}" -lt "${dest_snapshots[idx_dest]}" ] + elif [[ "${src_snapshots[idx_src]}" < "${dest_snapshots[idx_dest]}" ]] then only_in_src+=("${src_snapshots[idx_src]}") ((++idx_src)) @@ -806,62 +806,59 @@ function backup { return 0 fi + # Find the first common snapshot that is lower than the specified + # snapshot. + # + # 1 [out]: parent snapshot + # 2 [in]: snapshot + function find-parent { + declare -n out=$1 + local snapshot=$2 + local idx=${num_src_snapshots}-1 + for (( ; idx >= 0; --idx )) + do + if [[ "${src_snapshots[idx]}" < "$snapshot" && \ + ${common[${src_snapshots[idx]}]} == 0b11 ]] + then + break + fi + done + out=${src_snapshots[idx]} + } + if [[ $p_all == 0 ]] then - local common_ids=("${!common[@]}") - local common_last=${common_ids[-1]} # Check that it's not already synced - if [[ "$common_last" == "$p_snapshot" ]] + if [[ ${common[$p_snapshot]} == 0b11 ]] then - error "Already synced the last snapshot." + error "Already synced snapshot $p_snapshot." fi - incremental-backup "$common_last" "$p_snapshot" + local parent + find-parent parent "$p_snapshot" + incremental-backup "$parent" "$p_snapshot" return 0 else - local -a src_and_common - for snapshot in "${!common[@]}" - do - # These exist at both locations → 0b01 - src_and_common["$snapshot"]=0b11 - done - for snapshot in "${only_in_src[@]}" - do - # These only exist at the source → 0b01 - src_and_common["$snapshot"]=0b01 - done - local num_snapshots=${#src_and_common[@]} # Find the first common snapshot that is lower than the first # source only snapshot. This will be the start of the incremental # backup. If no one is found it will use the lowest common one. - local first_src_snapshot=${only_in_src[0]} - local snapshot_ids=("${!src_and_common[@]}") - local idx=${num_snapshots}-1 - for (( ; idx >= 0; --idx )) - do - if [[ ${snapshot_ids[idx]} -lt $first_src_snapshot ]] - then - if [[ ${src_and_common[${snapshot_ids[idx]}]} == 0b11 ]] - then - break - fi - fi - done - incremental-backup "${snapshot_ids[idx]}" "$first_src_snapshot" - src_and_common["${snapshot_ids[idx]}"]=0b11 + first_src_snapshot="${only_in_src[0]}" + find-parent parent "$first_src_snapshot" + incremental-backup "$parent" "$first_src_snapshot" + common["${src_snapshots[idx]}"]=0b11 for (( src_idx = 1; src_idx < num_src_only; ++src_idx )) do - # Find the source only snapshot in the snapshot_ids + # Find the next source snapshot to send local src_snapshot=${only_in_src[src_idx]} - for (( ; idx < num_snapshots; ++idx )) + for (( ; idx < num_src_snapshots; ++idx )) do - if [[ ${snapshot_ids[idx]} -eq $src_snapshot ]] + if [[ "${src_snapshots[idx]}" == "$src_snapshot" ]] then break fi done # Use the common snapshot previous to it as the parent - incremental-backup "${snapshot_ids[idx-1]}" "$src_snapshot" - src_and_common["$src_snapshot"]=0b11 + incremental-backup "${src_snapshots[idx-1]}" "$src_snapshot" + common["$src_snapshot"]=0b11 done fi } From 7f1ba5b2aa3279cee4ccca810562c9d37abb9c45 Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Sat, 25 Oct 2025 15:49:47 -0700 Subject: [PATCH 06/22] test: Use make-snapshot-from when reading in input for sender Allows to tweak things on the sender side. --- tests/runner.scm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/runner.scm b/tests/runner.scm index 5ad621f..d446cda 100755 --- a/tests/runner.scm +++ b/tests/runner.scm @@ -352,8 +352,8 @@ Fredrik \"PlaTFooT\" Salomonsson (format #t " expected: ~a~%" expected) (let ((exit-status 0) (sender-snapshots - (map (lambda (id) - (make-snapshot id #f 'valid)) + (map (lambda (input) + (make-snapshot-from input)) sender)) (receiver-snapshots (map (lambda (input) From 084cbad83d80138dd5b450dbef6bf5d3e8ca350d Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Sat, 25 Oct 2025 15:51:09 -0700 Subject: [PATCH 07/22] test: Move `create-snapshot` out from `create-snapper-snapshot` --- tests/runner.scm | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/tests/runner.scm b/tests/runner.scm index d446cda..88d0d76 100755 --- a/tests/runner.scm +++ b/tests/runner.scm @@ -53,6 +53,29 @@ (('state . s) s) (_ 'valid))))) +(define* (create-snapshot path snapshot #:key valid?) + "Create a dummy snapshot at PATH. + +It will have the following structure: + +PATH +└── data + +The file `data` will contain; snapshot=ID, where ID is the id from +SNAPSHOT; parent=PARENT, where PARAENT is the parent from SNAPSHOT. +If parent is #f it will skip it. + +It will also contain `ro=true` if VALID? evaluates to true." +(mkdir path) +(let ((port (open-output-file + (string-append path file-name-separator-string "data")))) + (and-let* ((parent (snapshot-parent snapshot))) + (format port "parent=~a~%" parent)) + (format port "snapshot=~a~%" (snapshot-id snapshot)) + (when valid? + (format port "ro=true~%")) + (close-port port))) + (define (create-snapper-snapshot path snapshot) "Create dummy snapper snapshot at PATH for SNAPSHOT. @@ -93,18 +116,7 @@ incomplete. It will have both `info.xml` and `snapshot`. But the (create-info.xml (lambda () (close-port (open-output-file - (string-append path "/info.xml"))))) - (create-snapshot (lambda* (#:key valid?) - (mkdir snapshot-dir) - (let ((port (open-output-file - (string-append snapshot-dir - file-name-separator-string "data")))) - (and-let* ((parent (snapshot-parent snapshot))) - (format port "parent=~a~%" parent)) - (format port "snapshot=~a~%" (snapshot-id snapshot)) - (when valid? - (format port "ro=true~%")) - (close-port port))))) + (string-append path "/info.xml")))))) (match (snapshot-state snapshot) ('valid (mkdir path) From a2d4ed1fec007e6366ed2689b68109f2fe00ac32 Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Sat, 25 Oct 2025 15:52:03 -0700 Subject: [PATCH 08/22] test: Add tags field to `` This to be able to set tags for denotebak snapshots. --- tests/runner.scm | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/tests/runner.scm b/tests/runner.scm index 88d0d76..7900f89 100755 --- a/tests/runner.scm +++ b/tests/runner.scm @@ -23,11 +23,12 @@ (string-join paths file-name-separator-string)) (define-record-type - (make-snapshot id parent state) + (make-snapshot id parent state tags) snapshot? (id snapshot-id) (parent snapshot-parent) - (state snapshot-state)) + (state snapshot-state) + (tags snapshot-tags)) (define (make-snapshot-from input) "Parse INPUT and create a snapshot from it." @@ -37,12 +38,13 @@ (map (lambda (metadatum) (match (string-split metadatum #\=) - (("p" parent) (cons 'parent parent)) - (("s" state) - (let ((state-sym (string->symbol state))) - (match state-sym + (("p" parent) (cons 'parent parent)) + (("s" state) + (let ((state-sym (string->symbol state))) + (match state-sym ((or 'valid 'empty 'no-info 'no-snapshot 'incomplete) - (cons 'state state-sym))))))) + (cons 'state state-sym))))) + (("t" tags) (cons 'tags tags)))) (cdr info)))) (make-snapshot id @@ -51,7 +53,10 @@ (_ #f)) (match (assoc 'state metadata) (('state . s) s) - (_ 'valid))))) + (_ 'valid)) + (match (assoc 'state metadata) + (('tags . t) t) + (_ #f))))) (define* (create-snapshot path snapshot #:key valid?) "Create a dummy snapshot at PATH. @@ -121,19 +126,19 @@ incomplete. It will have both `info.xml` and `snapshot`. But the ('valid (mkdir path) (create-info.xml) - (create-snapshot #:valid? #t)) + (create-snapshot snapshot-dir snapshot #:valid? #t)) ('empty (mkdir path)) ('no-info (mkdir path) - (create-snapshot #:valid? #t)) + (create-snapshot snapshot-dir snapshot #:valid? #t)) ('no-snapshot (mkdir path) (create-info.xml)) ('incomplete (mkdir path) (create-info.xml) - (create-snapshot #:valid? #f))))) + (create-snapshot snapshot-dir snapshot #:valid? #f))))) (define (check-snapshot-input value) "Verify the input for receiver or expected." @@ -214,7 +219,8 @@ Where DATA is in the format of what you get calling `read-snapshots'." ((#t () _) 'no-snapshot) (_ - 'incomplete))))) + 'incomplete)) + #f))) (define (check-latest expected-latest receiver-dir) "Verify that EXPECTED-LATEST points to the expected snapshot. From 25d50a3455125237de4cfab5270fdf4c729ad7e0 Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Sat, 25 Oct 2025 16:56:42 -0700 Subject: [PATCH 09/22] test: Add support for DeNotebak snapshots in runner --- tests/runner.scm | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/tests/runner.scm b/tests/runner.scm index 7900f89..9e94f23 100755 --- a/tests/runner.scm +++ b/tests/runner.scm @@ -63,15 +63,22 @@ It will have the following structure: -PATH +PATH[__] └── data The file `data` will contain; snapshot=ID, where ID is the id from SNAPSHOT; parent=PARENT, where PARAENT is the parent from SNAPSHOT. If parent is #f it will skip it. +It will append the tags string to the path, separated by `__`, if that +does not evaluate to false. + It will also contain `ro=true` if VALID? evaluates to true." -(mkdir path) +(mkdir + (match (snapshot-tags snapshot) + (#f path) + (tags (string-append path "__" tags)))) + (let ((port (open-output-file (string-append path file-name-separator-string "data")))) (and-let* ((parent (snapshot-parent snapshot))) @@ -154,6 +161,7 @@ incomplete. It will have both `info.xml` and `snapshot`. But the (_ (format (current-error-port) "unsupported state: ~a~%" state) #f))) + (("t" tags) #t) (_ (format (current-error-port) "invalid snapshot syntax: ~a~%" metadata) #f)) @@ -278,7 +286,7 @@ Return the path to the modified CONFIGFILE if it is defined otherwise #f." (define (main args) (let* ((option-spec `((config (single-char #\c) (value #t)) - (sender (single-char #\s) (value #t)) + (sender (single-char #\s) (value #t) (predicate ,check-snapshot-input)) (latest (single-char #\l) (value #t)) (receiver (single-char #\r) (value #t) (predicate ,check-snapshot-input)) (expected (single-char #\e) (value #t) (predicate ,check-snapshot-input)) @@ -350,6 +358,7 @@ Fredrik \"PlaTFooT\" Salomonsson (let* ((parse-comma-option (lambda (input) (remove string-null? (string-split (option-ref options input "") #\,)))) + (type (string->symbol (option-ref options 'type "snapper"))) (config (option-ref options 'config "root")) (sender (parse-comma-option 'sender)) (receiver (parse-comma-option 'receiver)) @@ -361,7 +370,10 @@ Fredrik \"PlaTFooT\" Salomonsson (sender-dir (path-join test-dir config ".snapshots")) (receiver-root-dir (path-join test-dir "receiver")) (receiver-dir (path-join receiver-root-dir config)) - (configfile (option-ref options 'configfile #f))) + (configfile (option-ref options 'configfile #f)) + (create-snapshot-func (match type + ('denotebak create-denotebak-snapshot) + (_ create-snapper-snapshot)))) ;; Needed for nftw to be able to read the directory (chmod test-dir #o744) (format #t "Test ~a~%" test-dir) @@ -389,7 +401,7 @@ Fredrik \"PlaTFooT\" Salomonsson (mkdir sender-dir) (for-each (lambda (snapshot) - (create-snapper-snapshot + (create-snapshot-func (path-join sender-dir (snapshot-id snapshot)) snapshot)) sender-snapshots) @@ -398,7 +410,7 @@ Fredrik \"PlaTFooT\" Salomonsson (mkdir receiver-dir) (for-each (lambda (snapshot) - (create-snapper-snapshot + (create-snapshot-func (path-join receiver-dir (snapshot-id snapshot)) snapshot)) receiver-snapshots) From 46ccda1e18ef9840497d5e01fe945935dd0b8a07 Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Sun, 9 Nov 2025 17:35:45 -0800 Subject: [PATCH 10/22] test: Add `make-snapshot-from-denotebak-read` [#45] To be able to read back DeNotebak snapshots. --- tests/runner.scm | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/runner.scm b/tests/runner.scm index 9e94f23..d1d5aff 100755 --- a/tests/runner.scm +++ b/tests/runner.scm @@ -230,6 +230,36 @@ Where DATA is in the format of what you get calling `read-snapshots'." 'incomplete)) #f))) +(define (make-snapshot-from-denotebak-read data) + "Create a snapshot from DATA. + +Where DATA is in the format of what you get calling `read-snapshots'." + (match data + (('latest . #t) (make-snapshot 'latest #f 'valid #f)) + (_ + (let* ((id (car data)) + (get (lambda (key alist default) + (match (assoc key alist) + ((key . v) v) + (_ default)))) + (metadata (cadr data)) + (snapshot-info (get "snapshot" metadata #f)) + (read-only (match (assoc "ro" metadata) + (("ro" . "true") #t) + (_ #f))) + (parent (get "parent" metadata #f))) + (make-snapshot + id + parent + (match (list snapshot-info read-only) + ((#f _) + 'no-snapshot) + ((snapshot #t) + 'valid) + (_ + 'incomplete)) + #f))))) + (define (check-latest expected-latest receiver-dir) "Verify that EXPECTED-LATEST points to the expected snapshot. From 7af0b60a5c9ef8e02f2ae1dcc3966f3ecf38bc3e Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Sun, 9 Nov 2025 19:09:59 -0800 Subject: [PATCH 11/22] test: Fix mock btrfs command to work with both snapper and DeNotebak [#45] Changed it to take the name of the snapshot it received rather than hard code the name to "snapshot". --- tests/bin/btrfs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/bin/btrfs b/tests/bin/btrfs index 2ddb258..f8e8f5c 100755 --- a/tests/bin/btrfs +++ b/tests/bin/btrfs @@ -60,11 +60,13 @@ do ;; esac done - function subvolume { case "$1" in + show) + test -d "$2" + ;; delete) - rm -rf -- "$1" + rm -rf -- "$2" ;; *) >&2 echo "unsupported subcommand to subvolume: $1" @@ -82,16 +84,21 @@ function receive { snapshot_parent=${data%:*} if [[ -n "$snapshot_parent" ]] then - snapshot_parent=$(basename "$(dirname "$snapshot_parent")") + if [[ "$(basename "$snapshot_parent")" == "snapshot" ]] + then + snapshot_parent=$(basename "$(dirname "$snapshot_parent")") + else + snapshot_parent=$(basename "$snapshot_parent") + fi fi snapshot=${data#*:} - - mkdir -p "$1/snapshot" + path="$1/$(basename "$snapshot")" + mkdir -p "$path" { echo "parent=$snapshot_parent" echo "snapshot=$snapshot" echo "ro=true" - } > "$1/snapshot/data" + } > "$path/data" } function property { From 7d2b2b888cc00f61e4834b08c250a585f9eeea71 Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Sat, 25 Oct 2025 16:57:20 -0700 Subject: [PATCH 12/22] test: Initial test for DeNotebak [#45] --- meson.build | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/meson.build b/meson.build index 57879d2..4b6c2d5 100644 --- a/meson.build +++ b/meson.build @@ -399,3 +399,44 @@ test( ], should_fail: true, ) + +test( + 'denotebak', + runner, + env: test_env, + args: [ + '--sender', ','.join([ + '20251023T130000:t=dailies', + '20251023T180000:t=dailies', + '20251024T090000:t=dailies', + '20251024T130000:t=dailies', + '20251024T180000:t=dailies', + '20251025T120000', + '20251025T130000:t=dailies', + '20251025T140000:t=hourly', + '20251025T150000:t=hourly', + '20251025T160000:t=hourly']), + '--expected', ','.join([ + '20251023T130000', + '20251023T180000:p=20251023T130000', + '20251024T090000:p=20251023T180000', + '20251024T130000:p=20251024T090000', + '20251024T180000:p=20251024T130000', + '20251025T120000:p=20251024T180000', + '20251025T130000:p=20251025T120000', + '20251025T140000:p=20251025T130000', + '20251025T150000:p=20251025T140000', + '20251025T160000:p=20251025T150000']), + '--type', 'denotebak', + '--src-dest', + '--', + baksnapper[0].full_path(), + '--daemon', baksnapperd_denotebak[0].full_path(), + '--all', + ], + depends: [ + baksnapper, + baksnapperd_denotebak, + ], + suite: 'backup', +) From 60e02c580fd6f217a8967baf04064a16af65df64 Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Sun, 9 Nov 2025 17:39:17 -0800 Subject: [PATCH 13/22] test: Add support to switch the backend for reading in snapshots [#45] To be able to test both snapper and DeNotebak --- tests/runner.scm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/runner.scm b/tests/runner.scm index d1d5aff..f1c6593 100755 --- a/tests/runner.scm +++ b/tests/runner.scm @@ -196,7 +196,7 @@ incomplete. It will have both `info.xml` and `snapshot`. But the (read-snapshots (cons (path-join parent name) child))) children))))) -(define (make-snapshot-from-read data) +(define (make-snapshot-from-snapper-read data) "Create a snapshot from DATA. Where DATA is in the format of what you get calling `read-snapshots'." @@ -403,7 +403,10 @@ Fredrik \"PlaTFooT\" Salomonsson (configfile (option-ref options 'configfile #f)) (create-snapshot-func (match type ('denotebak create-denotebak-snapshot) - (_ create-snapper-snapshot)))) + (_ create-snapper-snapshot))) + (make-snapshot-from-read (match type + ('denotebak make-snapshot-from-denotebak-read) + (_ make-snapshot-from-snapper-read)))) ;; Needed for nftw to be able to read the directory (chmod test-dir #o744) (format #t "Test ~a~%" test-dir) From 0b0920fec204ce445928c1309031b98173627038 Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Wed, 12 Nov 2025 21:59:01 -0800 Subject: [PATCH 14/22] test: Add `create-denotebak-snapshot` procedure [#45] To be able to create dummy DeNotebak snapshots. --- tests/runner.scm | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/runner.scm b/tests/runner.scm index f1c6593..d3ed138 100755 --- a/tests/runner.scm +++ b/tests/runner.scm @@ -88,6 +88,30 @@ It will also contain `ro=true` if VALID? evaluates to true." (format port "ro=true~%")) (close-port port))) +(define (create-denotebak-snapshot path snapshot) + "Create dummy DeNotebak snapshot at PATH for SNAPSHOT. + +A DeNotebak snapshot is just a btrfs snapshot. This will mock the +structure with the following: + +PATH[__] +└── data + +The state in SNAPSHOT specify what state it should create the +snapshot, accepted values are: + +- valid: a complete snapshot as described above. With `ro=true` added +to the `data` file. + +- incomplete: a broken snapshot which snapshot directory is +incomplete. Which is indicated by the `ro=true` attribute will be +missing in the `data` file." + (match (snapshot-state snapshot) + ('valid + (create-snapshot path snapshot #:valid? #t)) + ('incomplete + (create-snapshot path snapshot #:valid? #f)))) + (define (create-snapper-snapshot path snapshot) "Create dummy snapper snapshot at PATH for SNAPSHOT. From bb88df4e2f3aaed8a7bcbb82bb0e92be014adc8a Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Wed, 12 Nov 2025 22:05:00 -0800 Subject: [PATCH 15/22] test: Swap the order for `conf` and `test_entry` in the meson tests Since it's mostly the `test_entry` that you'll be changing. Having that first makes more sense. --- meson.build | 346 +++++++++++++++++++++++----------------------------- 1 file changed, 153 insertions(+), 193 deletions(-) diff --git a/meson.build b/meson.build index 4b6c2d5..2af34ce 100644 --- a/meson.build +++ b/meson.build @@ -117,190 +117,190 @@ test_configuration = { 'VERBOSE': 'NO', }, } -foreach conf: [ +foreach test_entry: [ { - 'suffix': '-config', - 'runner-args': [], - 'baksnapper-args': ['--config', 'root'], - 'configfile': false, - 'suite': ['config'], + 'name': 'default', + 'runner-args': [ + '--sender', '1,2,3', + '--expected', '3', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd[0].full_path(), + ], + 'configfile': 'default', + 'configfile-baksnapper-args': [], + 'suite': ['backup'], }, { - 'suffix': '-src-dest', - 'runner-args': ['--src-dest'], - 'baksnapper-args': [], - 'configfile': false, - 'suite': ['src-dest'], + 'name': 'latest', + 'runner-args': [ + '--sender', '1,2,3', + '--expected', '3', + '--expected-latest', '3', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd[0].full_path(), + '--link', + ], + 'configfile': 'latest', + 'configfile-baksnapper-args': [], + 'suite': ['latest'], }, { - 'suffix': '-configfile', - 'runner-args': [], - 'baksnapper-args': [], - 'configfile': true, - 'suite': ['configfile'], + 'name': 'prune', + 'runner-args': [ + '--sender', '1,2,3,6,7,8', + '--receiver', '1,2,3,4,5,6', + '--expected', '1,2,3,6,8:p=6', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd[0].full_path(), + '--prune', + ], + 'configfile': 'prune', + 'configfile-baksnapper-args': [], + 'suite': ['prune'], }, { - 'suffix': '-configfile-path', - 'runner-args': ['--path-config'], - 'baksnapper-args': [], - 'configfile': true, - 'suite': ['configfile', 'config'], + 'name': 'backup-2', + 'runner-args': [ + '--sender', '1,2,3', + '--expected', '2', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd[0].full_path(), + '--snapshot', '2', + ], + 'configfile': 'default', + 'configfile-baksnapper-args': [], + 'suite': ['backup'], }, { - 'suffix': '-configfile-src-dest', - 'runner-args': ['--src-dest'], - 'baksnapper-args': [], - 'configfile': true, - 'suite': ['configfile', 'src-dest'], + 'name': 'delete-1', + 'runner-args': [ + '--sender', '1,2,3', + '--receiver', '1,2,3', + '--expected', '2,3', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd[0].full_path(), + '--delete', '1', + ], + 'configfile': 'default', + 'configfile-baksnapper-args': [], + 'suite': ['delete'], }, { - 'suffix': '-configfile-src', - 'runner-args': ['--src-dest', '--src-config'], - 'baksnapper-args': [], - 'configfile': true, - 'suite': ['configfile', 'src-dest'], + 'name': 'delete-1,3', + 'runner-args': [ + '--sender', '1,2,3', + '--receiver', '1,2,3', + '--expected', '2', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd[0].full_path(), + '--delete', '1,3', + ], + 'configfile': 'default', + 'configfile-baksnapper-args': [], + 'suite': ['delete'], }, { - 'suffix': '-configfile-dest', - 'runner-args': ['--src-dest', '--dest-config'], - 'baksnapper-args': [], - 'configfile': true, - 'suite': ['configfile', 'src-dest'], + 'name': 'backup-with-right-parent', + 'runner-args': [ + '--sender', '64,65,76,83,91,92,93', + '--receiver', '64,66:p=64,76:p=66,83:p=76,91:p=83,92:p=83', + '--expected', '64,66:p=64,76:p=66,83:p=76,91:p=83,92:p=83,93:p=92', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd[0].full_path(), + ], + 'configfile': 'default', + 'configfile-baksnapper-args': [], + 'suite': ['backup'], }, + # https://github.com/plattfot/baksnapper/issues/27 { - 'suffix': '-configfile-both', - 'runner-args': ['--src-dest', '--src-config', '--dest-config'], - 'baksnapper-args': [], - 'configfile': true, - 'suite': ['configfile', 'src-dest'], + 'name': 'backup-all-with-right-parent', + 'runner-args': [ + '--sender', '64,65,76,83,91,92,93', + '--receiver', '64,66:p=64,76:p=66,83:p=76,91:p=83,92:p=83', + '--expected', '64,65:p=64,66:p=64,76:p=66,83:p=76,91:p=83,92:p=83,93:p=92', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd[0].full_path(), + '--all', + ], + 'configfile': 'all', + 'configfile-baksnapper-args': [], + 'suite': ['backup'], }, ] - foreach test_entry: [ + foreach conf: [ { - 'name': 'default', - 'runner-args': [ - '--sender', '1,2,3', - '--expected', '3', - ], - 'baksnapper-args': [ - baksnapper[0].full_path(), - '--daemon', baksnapperd[0].full_path(), - ], - 'configfile': 'default', - 'configfile-baksnapper-args': [], - 'suite': ['backup'], + 'suffix': '-config', + 'runner-args': [], + 'baksnapper-args': ['--config', 'root'], + 'configfile': false, + 'suite': ['config'], }, { - 'name': 'latest', - 'runner-args': [ - '--sender', '1,2,3', - '--expected', '3', - '--expected-latest', '3', - ], - 'baksnapper-args': [ - baksnapper[0].full_path(), - '--daemon', baksnapperd[0].full_path(), - '--link', - ], - 'configfile': 'latest', - 'configfile-baksnapper-args': [], - 'suite': ['latest'], + 'suffix': '-src-dest', + 'runner-args': ['--src-dest'], + 'baksnapper-args': [], + 'configfile': false, + 'suite': ['src-dest'], }, { - 'name': 'prune', - 'runner-args': [ - '--sender', '1,2,3,6,7,8', - '--receiver', '1,2,3,4,5,6', - '--expected', '1,2,3,6,8:p=6', - ], - 'baksnapper-args': [ - baksnapper[0].full_path(), - '--daemon', baksnapperd[0].full_path(), - '--prune', - ], - 'configfile': 'prune', - 'configfile-baksnapper-args': [], - 'suite': ['prune'], + 'suffix': '-configfile', + 'runner-args': [], + 'baksnapper-args': [], + 'configfile': true, + 'suite': ['configfile'], }, { - 'name': 'backup-2', - 'runner-args': [ - '--sender', '1,2,3', - '--expected', '2', - ], - 'baksnapper-args': [ - baksnapper[0].full_path(), - '--daemon', baksnapperd[0].full_path(), - '--snapshot', '2', - ], - 'configfile': 'default', - 'configfile-baksnapper-args': [], - 'suite': ['backup'], + 'suffix': '-configfile-path', + 'runner-args': ['--path-config'], + 'baksnapper-args': [], + 'configfile': true, + 'suite': ['configfile', 'config'], }, { - 'name': 'delete-1', - 'runner-args': [ - '--sender', '1,2,3', - '--receiver', '1,2,3', - '--expected', '2,3', - ], - 'baksnapper-args': [ - baksnapper[0].full_path(), - '--daemon', baksnapperd[0].full_path(), - '--delete', '1', - ], - 'configfile': 'default', - 'configfile-baksnapper-args': [], - 'suite': ['delete'], + 'suffix': '-configfile-src-dest', + 'runner-args': ['--src-dest'], + 'baksnapper-args': [], + 'configfile': true, + 'suite': ['configfile', 'src-dest'], }, { - 'name': 'delete-1,3', - 'runner-args': [ - '--sender', '1,2,3', - '--receiver', '1,2,3', - '--expected', '2', - ], - 'baksnapper-args': [ - baksnapper[0].full_path(), - '--daemon', baksnapperd[0].full_path(), - '--delete', '1,3', - ], - 'configfile': 'default', - 'configfile-baksnapper-args': [], - 'suite': ['delete'], + 'suffix': '-configfile-src', + 'runner-args': ['--src-dest', '--src-config'], + 'baksnapper-args': [], + 'configfile': true, + 'suite': ['configfile', 'src-dest'], }, { - 'name': 'backup-with-right-parent', - 'runner-args': [ - '--sender', '64,65,76,83,91,92,93', - '--receiver', '64,66:p=64,76:p=66,83:p=76,91:p=83,92:p=83', - '--expected', '64,66:p=64,76:p=66,83:p=76,91:p=83,92:p=83,93:p=92', - ], - 'baksnapper-args': [ - baksnapper[0].full_path(), - '--daemon', baksnapperd[0].full_path(), - ], - 'configfile': 'default', - 'configfile-baksnapper-args': [], - 'suite': ['backup'], + 'suffix': '-configfile-dest', + 'runner-args': ['--src-dest', '--dest-config'], + 'baksnapper-args': [], + 'configfile': true, + 'suite': ['configfile', 'src-dest'], }, - # https://github.com/plattfot/baksnapper/issues/27 { - 'name': 'backup-all-with-right-parent', - 'runner-args': [ - '--sender', '64,65,76,83,91,92,93', - '--receiver', '64,66:p=64,76:p=66,83:p=76,91:p=83,92:p=83', - '--expected', '64,65:p=64,66:p=64,76:p=66,83:p=76,91:p=83,92:p=83,93:p=92', - ], - 'baksnapper-args': [ - baksnapper[0].full_path(), - '--daemon', baksnapperd[0].full_path(), - '--all', - ], - 'configfile': 'all', - 'configfile-baksnapper-args': [], - 'suite': ['backup'], - } + 'suffix': '-configfile-both', + 'runner-args': ['--src-dest', '--src-config', '--dest-config'], + 'baksnapper-args': [], + 'configfile': true, + 'suite': ['configfile', 'src-dest'], + }, ] foreach ssh: [ { @@ -360,6 +360,7 @@ foreach conf: [ baksnapper_args += test_entry['configfile-baksnapper-args'] test_depends += [test_config] endif + test( test_entry['name']+conf['suffix']+ssh['suffix']+ssh_key['suffix'], runner, @@ -399,44 +400,3 @@ test( ], should_fail: true, ) - -test( - 'denotebak', - runner, - env: test_env, - args: [ - '--sender', ','.join([ - '20251023T130000:t=dailies', - '20251023T180000:t=dailies', - '20251024T090000:t=dailies', - '20251024T130000:t=dailies', - '20251024T180000:t=dailies', - '20251025T120000', - '20251025T130000:t=dailies', - '20251025T140000:t=hourly', - '20251025T150000:t=hourly', - '20251025T160000:t=hourly']), - '--expected', ','.join([ - '20251023T130000', - '20251023T180000:p=20251023T130000', - '20251024T090000:p=20251023T180000', - '20251024T130000:p=20251024T090000', - '20251024T180000:p=20251024T130000', - '20251025T120000:p=20251024T180000', - '20251025T130000:p=20251025T120000', - '20251025T140000:p=20251025T130000', - '20251025T150000:p=20251025T140000', - '20251025T160000:p=20251025T150000']), - '--type', 'denotebak', - '--src-dest', - '--', - baksnapper[0].full_path(), - '--daemon', baksnapperd_denotebak[0].full_path(), - '--all', - ], - depends: [ - baksnapper, - baksnapperd_denotebak, - ], - suite: 'backup', -) From 9e88ba032332be44a9105e849c6493d787d3f954 Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Wed, 12 Nov 2025 22:07:48 -0800 Subject: [PATCH 16/22] test: Move `baksnapperd` dependency to `test_entry` [#45] As DeNotebak tests will be dependent on the `baksnapperd_denotebak` backend. Moving this up to the `test_entry` to make it possible to switch and get proper dependency tracking. --- meson.build | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 2af34ce..9d7ef6c 100644 --- a/meson.build +++ b/meson.build @@ -131,6 +131,7 @@ foreach test_entry: [ 'configfile': 'default', 'configfile-baksnapper-args': [], 'suite': ['backup'], + 'depends': [baksnapperd], }, { 'name': 'latest', @@ -147,6 +148,7 @@ foreach test_entry: [ 'configfile': 'latest', 'configfile-baksnapper-args': [], 'suite': ['latest'], + 'depends': [baksnapperd], }, { 'name': 'prune', @@ -163,6 +165,7 @@ foreach test_entry: [ 'configfile': 'prune', 'configfile-baksnapper-args': [], 'suite': ['prune'], + 'depends': [baksnapperd], }, { 'name': 'backup-2', @@ -178,6 +181,7 @@ foreach test_entry: [ 'configfile': 'default', 'configfile-baksnapper-args': [], 'suite': ['backup'], + 'depends': [baksnapperd], }, { 'name': 'delete-1', @@ -194,6 +198,7 @@ foreach test_entry: [ 'configfile': 'default', 'configfile-baksnapper-args': [], 'suite': ['delete'], + 'depends': [baksnapperd], }, { 'name': 'delete-1,3', @@ -210,6 +215,7 @@ foreach test_entry: [ 'configfile': 'default', 'configfile-baksnapper-args': [], 'suite': ['delete'], + 'depends': [baksnapperd], }, { 'name': 'backup-with-right-parent', @@ -225,6 +231,7 @@ foreach test_entry: [ 'configfile': 'default', 'configfile-baksnapper-args': [], 'suite': ['backup'], + 'depends': [baksnapperd], }, # https://github.com/plattfot/baksnapper/issues/27 { @@ -242,6 +249,7 @@ foreach test_entry: [ 'configfile': 'all', 'configfile-baksnapper-args': [], 'suite': ['backup'], + 'depends': [baksnapperd], }, ] foreach conf: [ @@ -372,8 +380,7 @@ foreach test_entry: [ ssh_key['args'] + baksnapper_args, depends: [ baksnapper, - baksnapperd, - ] + test_depends, + ] + test_entry['depends'] + test_depends, suite: conf['suite'] + test_entry['suite'], ) endforeach From c64ca307b5d81e0bd4a42962d248bd5e04a0bdc4 Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Wed, 12 Nov 2025 22:10:37 -0800 Subject: [PATCH 17/22] test: Move `env` to `test_entry` [#45] Same reason as `depends`. --- meson.build | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 9d7ef6c..cb6e532 100644 --- a/meson.build +++ b/meson.build @@ -132,6 +132,7 @@ foreach test_entry: [ 'configfile-baksnapper-args': [], 'suite': ['backup'], 'depends': [baksnapperd], + 'env': test_env, }, { 'name': 'latest', @@ -149,6 +150,7 @@ foreach test_entry: [ 'configfile-baksnapper-args': [], 'suite': ['latest'], 'depends': [baksnapperd], + 'env': test_env, }, { 'name': 'prune', @@ -166,6 +168,7 @@ foreach test_entry: [ 'configfile-baksnapper-args': [], 'suite': ['prune'], 'depends': [baksnapperd], + 'env': test_env, }, { 'name': 'backup-2', @@ -182,6 +185,7 @@ foreach test_entry: [ 'configfile-baksnapper-args': [], 'suite': ['backup'], 'depends': [baksnapperd], + 'env': test_env, }, { 'name': 'delete-1', @@ -199,6 +203,7 @@ foreach test_entry: [ 'configfile-baksnapper-args': [], 'suite': ['delete'], 'depends': [baksnapperd], + 'env': test_env, }, { 'name': 'delete-1,3', @@ -216,6 +221,7 @@ foreach test_entry: [ 'configfile-baksnapper-args': [], 'suite': ['delete'], 'depends': [baksnapperd], + 'env': test_env, }, { 'name': 'backup-with-right-parent', @@ -232,6 +238,7 @@ foreach test_entry: [ 'configfile-baksnapper-args': [], 'suite': ['backup'], 'depends': [baksnapperd], + 'env': test_env, }, # https://github.com/plattfot/baksnapper/issues/27 { @@ -250,6 +257,7 @@ foreach test_entry: [ 'configfile-baksnapper-args': [], 'suite': ['backup'], 'depends': [baksnapperd], + 'env': test_env, }, ] foreach conf: [ @@ -372,7 +380,7 @@ foreach test_entry: [ test( test_entry['name']+conf['suffix']+ssh['suffix']+ssh_key['suffix'], runner, - env: test_env, + env: test_entry['env'], args: test_entry['runner-args'] + conf['runner-args'] + ssh['args'] + runner_args + ['--'] + From ae5113f825e507b79f4c4ac187c82636eefe1a42 Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Wed, 12 Nov 2025 22:11:16 -0800 Subject: [PATCH 18/22] test: Add test configurations for DeNotebak [#45] --- meson.build | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/meson.build b/meson.build index cb6e532..8fdc742 100644 --- a/meson.build +++ b/meson.build @@ -116,6 +116,27 @@ test_configuration = { 'ALL': 'YES', 'VERBOSE': 'NO', }, + 'denotebak-default': { + 'DAEMON': baksnapperd_denotebak[0].full_path(), + }, + 'denotebak-latest': { + 'DAEMON': baksnapperd_denotebak[0].full_path(), + 'PRUNE': 'NO', + 'ALL': 'NO', + 'VERBOSE': 'NO', + 'LINK': 'YES', + }, + 'denotebak-prune': { + 'DAEMON': baksnapperd_denotebak[0].full_path(), + 'PRUNE': 'YES', + 'ALL': 'NO', + 'VERBOSE': 'NO', + }, + 'denotebak-all': { + 'DAEMON': baksnapperd_denotebak[0].full_path(), + 'ALL': 'YES', + 'VERBOSE': 'NO', + }, } foreach test_entry: [ { From ea8ab6ad818857ec5f946c99490ed595ee93aff7 Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Wed, 12 Nov 2025 22:11:49 -0800 Subject: [PATCH 19/22] test: Skip `--config` tests for DeNotebak tests [#45] As they are not supported with --config. --- meson.build | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/meson.build b/meson.build index 8fdc742..5b003b8 100644 --- a/meson.build +++ b/meson.build @@ -398,6 +398,11 @@ foreach test_entry: [ test_depends += [test_config] endif + # The DeNotebak backend does not support --config + if 'denotebak' in test_entry['suite'] and 'src-dest' not in conf['suite'] + continue + endif + test( test_entry['name']+conf['suffix']+ssh['suffix']+ssh_key['suffix'], runner, From d99eb835e277f9be54ee2661f69bd857f23612b4 Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Wed, 12 Nov 2025 22:13:36 -0800 Subject: [PATCH 20/22] test: Add `denotebak_test_env` [#45] Which just changes the ssh baksnapperd backend to the denotebak end. --- meson.build | 3 +++ 1 file changed, 3 insertions(+) diff --git a/meson.build b/meson.build index 5b003b8..d474d3e 100644 --- a/meson.build +++ b/meson.build @@ -92,6 +92,9 @@ test_env.set('MOCK_SSH_CONNECTION', '0') test_env.set('MOCK_SSH_KEY', 'foo.key=foo.example.com:bar.key=bar.example.com') test_env.set('MOCK_SSH_BAKSNAPPERD', baksnapperd[0].full_path()) +denotebak_test_env = test_env +denotebak_test_env.set('MOCK_SSH_BAKSNAPPERD', baksnapperd_denotebak[0].full_path()) + runner = find_program('./tests/runner.scm') test_configuration = { From 2841cf3ceb07208f5286466dfd90dc7d6ed88bdc Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Wed, 12 Nov 2025 22:12:54 -0800 Subject: [PATCH 21/22] test: Add DeNotebak tests to `test_entry` [#45] --- meson.build | 245 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) diff --git a/meson.build b/meson.build index d474d3e..1e46ca3 100644 --- a/meson.build +++ b/meson.build @@ -283,6 +283,251 @@ foreach test_entry: [ 'depends': [baksnapperd], 'env': test_env, }, + # DeNotebak + { + 'name': 'denotebak-default', + 'runner-args': [ + '--sender', ','.join([ + '20251023T130000:t=dailies', + '20251025T120000', + '20251025T160000:t=hourly']), + '--expected', ','.join([ + '20251025T160000']), + '--type', 'denotebak', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd_denotebak[0].full_path(), + ], + 'configfile': 'denotebak-default', + 'configfile-baksnapper-args': [], + 'suite': ['denotebak', 'backup'], + 'depends': [baksnapperd_denotebak], + 'env': denotebak_test_env, + }, + { + 'name': 'denotebak-latest', + 'runner-args': [ + '--sender', ','.join([ + '20251023T130000:t=dailies', + '20251025T120000', + '20251025T160000:t=hourly']), + '--expected', ','.join([ + '20251025T160000']), + '--expected-latest', '20251025T160000', + '--type', 'denotebak', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd_denotebak[0].full_path(), + '--link', + ], + 'configfile': 'denotebak-latest', + 'configfile-baksnapper-args': [], + 'suite': ['denotebak', 'latest'], + 'depends': [baksnapperd_denotebak], + 'env': denotebak_test_env, + }, + { + 'name': 'denotebak-prune', + 'runner-args': [ + '--sender', ','.join([ + '20251023T130000:t=dailies', + '20251025T120000', + '20251025T130000:t=dailies', + '20251025T140000:t=hourly', + '20251025T150000:t=hourly', + '20251025T160000:t=hourly']), + '--receiver', ','.join([ + '20251023T130000', + '20251023T180000', + '20251024T090000', + '20251024T130000', + '20251024T180000', + '20251025T120000', + '20251025T130000']), + '--expected', ','.join([ + '20251023T130000', + '20251025T120000', + '20251025T130000', + '20251025T160000:p=20251025T130000']), + '--type', 'denotebak', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd_denotebak[0].full_path(), + '--prune', + ], + 'configfile': 'denotebak-prune', + 'configfile-baksnapper-args': [], + 'suite': ['denotebak', 'prune'], + 'depends': [baksnapperd_denotebak], + 'env': denotebak_test_env, + }, + { + 'name': 'denotebak-backup-20251025T150000', + 'runner-args': [ + '--sender', ','.join([ + '20251025T140000:t=hourly', + '20251025T150000:t=hourly', + '20251025T160000:t=hourly']), + '--expected', ','.join([ + '20251025T150000']), + '--type', 'denotebak', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd_denotebak[0].full_path(), + '--snapshot', '20251025T150000', + ], + 'configfile': 'denotebak-default', + 'configfile-baksnapper-args': [], + 'suite': ['denotebak', 'backup'], + 'depends': [baksnapperd_denotebak], + 'env': denotebak_test_env, + }, + { + 'name': 'denotebak-delete-20251025T140000', + 'runner-args': [ + '--sender', ','.join([ + '20251025T140000:t=hourly', + '20251025T150000:t=hourly', + '20251025T160000:t=hourly']), + '--receiver', ','.join([ + '20251025T140000', + '20251025T150000', + '20251025T160000']), + '--expected', ','.join([ + '20251025T150000', + '20251025T160000']), + '--type', 'denotebak', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd_denotebak[0].full_path(), + '--delete', '20251025T140000', + ], + 'configfile': 'denotebak-default', + 'configfile-baksnapper-args': [], + 'suite': ['denotebak', 'delete'], + 'depends': [baksnapperd_denotebak], + 'env': denotebak_test_env, + }, + { + 'name': 'denotebak-delete-20251025T140000,20251025T160000', + 'runner-args': [ + '--sender', ','.join([ + '20251025T140000:t=hourly', + '20251025T150000:t=hourly', + '20251025T160000:t=hourly']), + '--receiver', ','.join([ + '20251025T140000:t=hourly', + '20251025T150000', + '20251025T160000']), + '--expected', ','.join([ + '20251025T150000']), + '--type', 'denotebak', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd_denotebak[0].full_path(), + '--delete', '20251025T140000,20251025T160000', + ], + 'configfile': 'denotebak-default', + 'configfile-baksnapper-args': [], + 'suite': ['denotebak', 'delete'], + 'depends': [baksnapperd_denotebak], + 'env': denotebak_test_env, + }, + { + 'name': 'denotebak-backup-with-right-parent', + 'runner-args': [ + '--sender', ','.join([ + '20251023T130000:t=dailies', + '20251023T180000:t=dailies', + '20251024T090000:t=dailies', + '20251024T130000:t=dailies', + '20251024T180000:t=dailies', + '20251025T120000', + '20251025T130000:t=dailies', + '20251025T140000:t=hourly', + '20251025T150000:t=hourly', + '20251025T160000:t=hourly']), + '--receiver', ','.join([ + '20251023T130000', + '20251023T180000:p=20251023T130000', + '20251024T090000:p=20251023T180000', + '20251024T130000:p=20251024T090000', + '20251024T180000:p=20251024T130000', + '20251025T120000:p=20251024T180000', + '20251025T130000:p=20251025T120000']), + '--expected', ','.join([ + '20251023T130000', + '20251023T180000:p=20251023T130000', + '20251024T090000:p=20251023T180000', + '20251024T130000:p=20251024T090000', + '20251024T180000:p=20251024T130000', + '20251025T120000:p=20251024T180000', + '20251025T130000:p=20251025T120000', + '20251025T160000:p=20251025T130000']), + '--type', 'denotebak', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd_denotebak[0].full_path(), + ], + 'configfile': 'denotebak-default', + 'configfile-baksnapper-args': [], + 'suite': ['denotebak', 'backup'], + 'depends': [baksnapperd_denotebak], + 'env': denotebak_test_env, + }, + { + 'name': 'denotebak-backup-all-with-right-parent', + 'runner-args': [ + '--sender', ','.join([ + '20251023T130000:t=dailies', + '20251023T180000:t=dailies', + '20251024T090000:t=dailies', + '20251024T130000:t=dailies', + '20251024T180000:t=dailies', + '20251025T120000', + '20251025T130000:t=dailies', + '20251025T140000:t=hourly', + '20251025T150000:t=hourly', + '20251025T160000:t=hourly']), + '--receiver', ','.join([ + '20251023T130000', + '20251023T180000:p=20251023T130000', + '20251024T090000:p=20251023T180000', + '20251024T130000:p=20251024T090000', + '20251024T180000:p=20251024T130000', + '20251025T120000:p=20251024T180000', + '20251025T130000:p=20251025T120000']), + '--expected', ','.join([ + '20251023T130000', + '20251023T180000:p=20251023T130000', + '20251024T090000:p=20251023T180000', + '20251024T130000:p=20251024T090000', + '20251024T180000:p=20251024T130000', + '20251025T120000:p=20251024T180000', + '20251025T130000:p=20251025T120000', + '20251025T140000:p=20251025T130000', + '20251025T150000:p=20251025T140000', + '20251025T160000:p=20251025T150000']), + '--type', 'denotebak', + ], + 'baksnapper-args': [ + baksnapper[0].full_path(), + '--daemon', baksnapperd_denotebak[0].full_path(), + '--all', + ], + 'configfile': 'denotebak-all', + 'configfile-baksnapper-args': [], + 'suite': ['denotebak', 'backup'], + 'depends': [baksnapperd_denotebak], + 'env': denotebak_test_env, + }, ] foreach conf: [ { From b131231b8bb13d6216f195ef19e5bcd730701b20 Mon Sep 17 00:00:00 2001 From: Fredrik Salomonsson Date: Sun, 16 Nov 2025 17:01:36 -0800 Subject: [PATCH 22/22] changelog: add DeNotebak backend --- CHANGELOG.d/20251116T170044--add-denotebak-backend__added.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 CHANGELOG.d/20251116T170044--add-denotebak-backend__added.md diff --git a/CHANGELOG.d/20251116T170044--add-denotebak-backend__added.md b/CHANGELOG.d/20251116T170044--add-denotebak-backend__added.md new file mode 100644 index 0000000..23b768d --- /dev/null +++ b/CHANGELOG.d/20251116T170044--add-denotebak-backend__added.md @@ -0,0 +1,4 @@ + + +### Added +- [DeNotebak](https://sr.ht/~plattfot/DeNotebak/) backend support. [#45](https://github.com/plattfot/baksnapper/issues/45)