--- /dev/null
+BINDIR = $(DESTDIR)/usr/bin
+ETCDIR = $(DESTDIR)/etc
+CRONDAILY = $(DESTDIR)/etc/cron.daily
+
+compile:
+ : # do nothing
+
+$(BINDIR)/% $(ETCDIR)/% $(CRONDAILY)/%: %
+ mkdir -p $$(dirname $@)
+ cp $< $@
+
+INSTALLTARGETS = $(BINDIR)/dupltool
+INSTALLTARGETS += $(ETCDIR)/duplicity-daily.conf
+INSTALLTARGETS += $(CRONDAILY)/duplicity-daily
+
+install: $(INSTALLTARGETS)
+
+deb:
+ PREFIX= INCLUDE_PREFIX=/usr dpkg-buildpackage -us -uc --build=full
--- /dev/null
+buck (0.0.1) unstable; urgency=medium
+
+ * Initial release
+
+ -- Ralph Ronnquist <ralph.ronnquist@gmail.com> Mon, 20 Feb 2023 14:15:35 +1100
--- /dev/null
+Source: buck
+Section: unknown
+Priority: optional
+Maintainer: Ralph Ronnquist <ralph.ronnquist@gmail.com>
+Build-Depends: debhelper-compat (= 13)
+Standards-Version: 4.5.1
+#Homepage: <insert the upstream URL, if relevant>
+#Vcs-Browser: https://salsa.debian.org/debian/buck
+#Vcs-Git: https://salsa.debian.org/debian/buck.git
+Rules-Requires-Root: no
+
+Package: buck
+Architecture: all
+Depends: ${misc:Depends}, bash, duplicity, bsdutils
+Description: Backup control script for duplicity with multi-level history.
+ This script should be set up to run daily by root so as to make a daily
+ backup snapshot run to the ssh host named "backup".
--- /dev/null
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: buck
+Upstream-Contact: <preferred name and address to reach the upstream project>
+Source: <url://example.com>
+
+Files: *
+Copyright: <years> <put author's name and email here>
+ <years> <likewise for another author>
+License: <special license>
+ <Put the license of the package here indented by 1 space>
+ <This follows the format of Description: lines in control file>
+ .
+ <Including paragraphs>
+
+# If you want to use GPL v2 or later for the /debian/* files use
+# the following clauses, or change it to suit. Delete these two lines
+Files: debian/*
+Copyright: 2023 Ralph Ronnquist <ralph@localhost>
+License: GPL-2+
+ This package is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
+
+# Please also look if there are files or directories which have a
+# different copyright/license attached and list them here.
+# Please avoid picking licenses with terms that are more restrictive than the
+# packaged work, as it may make Debian's contributions unacceptable upstream.
+#
+# If you need, there are some extra license texts available in two places:
+# /usr/share/debhelper/dh_make/licenses/
+# /usr/share/common-licenses/
--- /dev/null
+#!/usr/bin/make -f
+# See debhelper(7) (uncomment to enable)
+# output every command that modifies files on the build system.
+#export DH_VERBOSE = 1
+
+
+# see FEATURE AREAS in dpkg-buildflags(1)
+#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
+
+# see ENVIRONMENT in dpkg-buildflags(1)
+# package maintainers to append CFLAGS
+#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
+# package maintainers to append LDFLAGS
+#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
+
+
+%:
+ dh $@
+
+
+# dh_make generated override targets
+# This is example for Cmake (See https://bugs.debian.org/641051 )
+#override_dh_auto_configure:
+# dh_auto_configure -- \
+# -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)
--- /dev/null
+3.0 (native)
--- /dev/null
+#!/bin/bash
+#
+# Backup directories daily. Configuration at /etc/duplicity-daily
+
+# Requires dupltool
+[ -x /usr/bin/dupltool ] || exit 0
+
+CONFIG=/etc/duplicity-daily.conf
+grep -q ^ENABLE=yes $CONFIG || exit 0
+
+# Detail backup options
+OPTIONS=( $(grep ^OPTIONS= $CONFIG | sed 's/^OPTIONS=//') )
+
+# The default backup target
+TARGET="$(grep ^TARGET= -m1 $CONFIG | sed 's/^TARGET=//')"
+
+# Backup lines start with full path, optionally with =target
+# No spaces in pathname or target, e.g.
+# /home
+# /opt/other/x=user@host/backup/x
+
+SRCS=( $(grep ^/ $CONFIG) )
+for SRC in "${SRCS[@]}" ; do
+ if [ "${SRC#*=}" = "$SRC" ] ; then
+ DST=$TARGET$SRC
+ else
+ DST="${SRC##*=}"
+ SRC="${SRC%%=*}"
+ fi
+ echo "** Backing up $SRC to $DST"
+ dupltool ${OPTIONS[*]} $SRC pexpect+scp://$DST
+done |& logger -t backup
--- /dev/null
+# Backup is enabled with ENABLE=yes (exactly)
+ENABLE=no
+
+# TARGET declares the default target which gets the source appended
+# pexpect+scp://$TARGET$SRC
+#TARGET=stuga@backup.lan.hemma
+
+#OPTIONS=--no-encryption
+
+# Each line starting with "/" declares a backup directory $SRC to be
+# backup up with duplicity to "pexpect+scp://$TARGET$SRC"
+#
+# A line may also have target overide, as $SRC=$DST in which case the
+# $SRC directory is backup up to pexpect+scp://$DST
+#
+#/root
+#/etc
+#/home
+#/opt/uml/ceres=stuga@backup.lan.hemma/ceres
--- /dev/null
+#!/bin/bash
+#
+# Duplicity Control Tool -- description at far below (after "exec duplicity")
+
+if [ $# -lt 2 ] ; then
+ cat <<EOF >&2
+** Usage: dupltool [options] source url
+** Source directory and store url are required, and the url
+** MUST be of form "pexpect+scp:/\$HOST/\$REMOTEDIR"
+EOF
+ exit 1
+fi
+
+#-- Grab command line arguments
+DUPL=( $* )
+STORE="${DUPL[-1]}"
+SOURCE="${DUPL[-2]}"
+
+#-- Confirm that the STORE url was of expected format
+
+if [ -n "${STORE##pexpect+scp:/*/*}" ] ; then
+ echo "** Bad store URL: $STORE" >&2
+ echo "** Required format: pexpect+scp:/$HOST/$REMOTEDIR" >&2
+ exit 1
+fi
+
+## Helper function to read duplicity pathnames and prefix them with
+## their from and to months, as in ${FROM}${TO}:${FILENAME}
+## both $FROM and $TO are 6 digits (yyyymm).
+remote_from_to_month_prefix() {
+ sftp $SFTP <<< "ls" | while read F ; do
+ case "$F" in
+ duplicity-full-signatures.*) echo "${F:26:6}${F:26:6}:$F" ; ;;
+ duplicity-full.*) echo "${F:15:6}${F:15:6}:$F" ; ;;
+ duplicity-inc.*) echo "${F:14:6}${F:34:6}:$F" ; ;;
+ duplicity-new-signatures.*) echo "${F:25:6}${F:45:6}:$F" ; ;;
+ esac
+ done | sort -r
+}
+
+THISMONTH="$(date -u "+%Y%m")" # UTC date
+
+SFTP="${STORE##pexpect+scp://}"
+HOST="${SFTP%%/*}"
+REMOTEDIR="${SFTP#*/}"
+SFTP="sftp://$SFTP"
+
+# Obtain remote pathnames with from-to prefix in reverse date order,
+# i.e., newest first.
+FILES=( $(remote_from_to_month_prefix) )
+
+if [ -n "$FILES" ] ; then
+ THATMONTH="${FILES[0]:6:6}" # to-month of newest
+ if [ "$THISMONTH" != "$THATMONTH" ] ; then
+ echo "** New monthly snapshot; stashing daily into .$THATMONTH" >&2
+ { # Generate command stream for sftp
+ echo "mkdir .$THATMONTH"
+ FX=
+ for F in ${FILES[@]} ; do
+ [ "${F:0:6}" != "$THATMONTH" ] && break
+ [ -n "$FX" ] && echo "rename $FX .$THATMONTH/$FX"
+ FX="${F#*:}"
+ [ -z "${FX%duplicity-full*}" ] && break
+ done
+ } | sftp $SFTP
+ echo "** Stashing into .$THATMONTH completed" >&2
+ fi
+fi
+
+exec duplicity ${DUPL[@]}
+exit 1 # jic
+
+# Duplicity Control Tool
+# ======================
+#
+# This Duplicity Control Tool should be run as a daily anacron job (or
+# daily as a cron job) so as to make regular snapshots of a directory
+# tree using duplicity with the pexpect+scp backend.
+#
+# Ideally the backup store should be set up with password-less scp
+# access, preferrably as a non-root remote user, or at the very least
+# be accessed by means of a local ssh-agent.
+#
+# The store is then managed by this Duplicity Control Tool to keep a
+# multi-level history, which ends up as a succession of monthly
+# snapshots with a most recent tail of daily snapshots. Specifically,
+# at the first snapshot event each month, the store is revised by
+# stashing away the snapshots of the most recent previous month, to
+# leave the first one (or the most recent full) as the start point for
+# a new snapshot, which thus becomes a "monthly" snapshot.
+#
+# The chain of daily snapshots from that first (monthly or full)
+# snapshot remain stashed in the ".%Y%m" directory.
+#
+# Note that the Duplicity Control Tool considers "the month of the
+# last snapshot or full" to be "the previous month" regardless of how
+# many months back from today that might be, i.e., it will stash
+# snapshots just as if that month was the prior month. In other words,
+# if you would happen to have a huge temporal hole in the snapshot
+# series you might consider running a plain duplicity snapshot first
+# and then let the subsequent, regular runs of the Duplicity Control
+# Tool build its multi-level snapshot history henceforth.
+#
+# === These are the examples of the various duplicity snapshot files:
+# duplicity-full.20230128T010719Z.vol1.difftar.gz
+# duplicity-full-signatures.20230128T010719Z.sigtar.gz
+# duplicity-new-signatures.20221216T230345Z.to.20221217T230403Z.sigtar.gz
+# duplicity-inc.20230128T010719Z.to.20230130T082241Z.manifest
+# duplicity-inc.20230127T061825Z.to.20230130T081002Z.vol1.difftar.gz