summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore.d/partsdb-backup2
-rwxr-xr-xbin/bakdb113
2 files changed, 115 insertions, 0 deletions
diff --git a/.gitignore.d/partsdb-backup b/.gitignore.d/partsdb-backup
index c7f32c2..7ab1392 100644
--- a/.gitignore.d/partsdb-backup
+++ b/.gitignore.d/partsdb-backup
@@ -4,6 +4,8 @@
# Include relevant files
!/.gitignore.d/
!/.gitignore.d/partsdb-backup
+!/bin/
+!/bin/bakdb
# Exclude swap and backup files
*.s[a-w]?
diff --git a/bin/bakdb b/bin/bakdb
new file mode 100755
index 0000000..7ab7c8c
--- /dev/null
+++ b/bin/bakdb
@@ -0,0 +1,113 @@
+#!/bin/sh
+
+set -eu
+
+prev_hash=
+prev_bk_file=
+
+# Back up a database and output a SHA-256 hash of its SQL dump.
+backup()
+{
+ local db_file="${1}"
+ local bk_file="${2}"
+ shift 2
+
+ sqlite3 "${db_file}" ".backup ${bk_file}~"
+ sqlite3 "${bk_file}~" '.dump' | sha256sum | cut -d ' ' -f 1
+ return 0
+}
+
+# Read previous backup's hash and file name.
+# Returns 0 if the new backup's hash is equal to the previous backup's hash, or
+# 1 otherwise.
+compare_hashes()
+{
+ local db_file="${1}"
+ local bk_hash="${2}"
+ shift 2
+
+ { read -r prev_hash prev_bk_file 0<"${db_file}.bakhash"; } 2>/dev/null \
+ || :
+
+ case "${prev_hash}" in "${bk_hash}") return 0;; esac
+ return 1
+}
+
+# Make a symbolic link with the new backup file name to the previous backup
+# file.
+# No path resolution is performed, so either absolute paths should be used or
+# all backup files should be in the same directory.
+# The linking of the final backup file and the unlinking of the temporary file
+# are not atomic, but as long as they're done in this order, the only possible
+# consequence is that the linking is done but an extraneous temporary file
+# remains.
+link_to_prev()
+{
+ local bk_file="${1}"
+ shift 1
+
+ ln -s "${prev_bk_file}.xz" "${bk_file}.xz"
+ rm "${bk_file}~"
+
+ return 0
+}
+
+# Compress and save a backup file.
+save_backup()
+{
+ local db_file="${1}"
+ shift 1
+
+ xz "${bk_file}~"
+ mv "${bk_file}~.xz" "${bk_file}.xz"
+
+ return 0
+}
+
+# Save a backup file's name and hash for future comparison and linking.
+save_hash()
+{
+ local db_file="${1}"
+ local bk_file="${2}"
+ local bk_hash="${3}"
+ shift 3
+
+ printf '%s %s\n' "${bk_hash}" "${bk_file}" 1>"${db_file}.bakhash~"
+ mv "${db_file}.bakhash~" "${db_file}.bakhash"
+
+ return 0
+}
+
+usage()
+{
+ printf 'Usage: %s <database-file> <backup-file>\n' "${0}"
+
+ return 0
+}
+
+main()
+{
+ local db_file=
+ local bk_file=
+ local bk_hash=
+
+ if [ ${#} -ne 2 ]; then
+ usage 1>&2
+ return 1
+ fi
+ db_file="${1}"
+ bk_file="${2}"
+ shift 2
+
+ bk_hash="$(backup "${db_file}" "${bk_file}")"
+ if compare_hashes "${db_file}" "${bk_hash}"; then
+ link_to_prev "${bk_file}"
+ else
+ save_backup "${bk_file}"
+ save_hash "${db_file}" "${bk_file}" "${bk_hash}"
+ fi
+
+ return 0
+}
+
+main "${@}"