aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Spek <p.spek@tyil.nl>2022-05-26 21:11:20 +0200
committerPatrick Spek <p.spek@tyil.nl>2022-05-26 21:11:20 +0200
commitedfe644496189da09c12009d8c670e5100515dd9 (patch)
tree17ceab6cc80bf79cd60ca3f1c217affe447c690c
parent08520fec143abd63000885f9d8fadb66ef203435 (diff)
Add a first iteration of a backup subcommand
-rw-r--r--CHANGELOG.md3
-rw-r--r--lib/subcommands/backup.bash155
2 files changed, 158 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 523c4c0..dbcc19d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `config_subkeys()` and `config_subkeys_for` have been added, to look up
subkeys defined in config files. These can help when you want to use a list
somewhere in your configuration.
+- A `backup` subcommand has been added. This backup system uses borg, which must
+ be installed, but should be generic enough to be usable by most people out of
+ the box.
### Changed
diff --git a/lib/subcommands/backup.bash b/lib/subcommands/backup.bash
new file mode 100644
index 0000000..2765172
--- /dev/null
+++ b/lib/subcommands/backup.bash
@@ -0,0 +1,155 @@
+#!/usr/bin/env bash
+
+# SPDX-FileCopyrightText: 2022 Patrick Spek <p.spek@tyil.nl>
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+subcommand() {
+ # Retrieve the passphrase used by Borg
+ BORG_PASSPHRASE="$(config "bashtard.backup.key")"
+
+ if [[ -z "$BORG_PASSPHRASE" ]]
+ then
+ emerg "$BASHTARD_NAME/backup" "No backup key configured"
+ return 3
+ fi
+
+ export BORG_PASSPHRASE
+
+ # Run backups for all configured elements, if none were explicitly
+ # specified
+ if (( $# < 1 ))
+ then
+ debug "$BASHTARD_NAME/backup" "Retrieving elements from config"
+
+ while read -r element
+ do
+ set -- "$@" "$(config "$element")"
+ done < <(config_subkeys "bashtard.backup.elements")
+ fi
+
+ # If there are still no configured elements, this is a good time to
+ # return an error instead
+ if (( $# < 1 ))
+ then
+ emerg "$BASHTARD_NAME/backup" "No elements to back up"
+ return 3
+ fi
+
+ # Loop over all repositories, and run backup functions for each of them
+ while read -r key
+ do
+ index="$(awk -F. '{ print $NF }' <<< "$key")"
+
+ for element in "$@"
+ do
+ "backup_$element" "$index"
+ done
+
+ info "$BASHTARD_NAME/backup/$index" "Backups completed"
+ done < <(config_subkeys "bashtard.backup.repositories")
+}
+
+backup_filesystem() {
+ local index=$1 ; shift
+ local borg
+ local cmd_create
+ local cmd_prune
+ local indexes
+
+ borg="$(config "bashtard.backup.borg.command" "borg")"
+ remote="$(config "bashtard.backups.borg.remote_paths.$index" "borg")"
+ repo="$(config "bashtard.backup.repositories.$index")"
+
+ if ! command -v "$borg" > /dev/null 2>&1
+ then
+ emerg "$BASHTARD_NAME/backup/$index" "The backup command depends on '$borg' being installed and available in your \$PATH"
+ return 4
+ fi
+
+ # Prune old backups
+ info "$BASHTARD_NAME/backup/$index" "Pruning filesystem backups in $repo"
+ cmd_prune=(
+ "$borg" "prune"
+ "--keep-daily" "$(config "bashtard.backup.keep.daily" 7)"
+ "--keep-weekly" "$(config "bashtard.backup.keep.weekly" 4)"
+ "--keep-monthly" "$(config "bashtard.backup.keep.monthly" 6)"
+ "--keep-yearly" "$(config "bashtard.backup.keep.yearly" 1)"
+ "--prefix" "{fqdn}-"
+ "--remote-path" "$remote"
+ "$repo/hostfs"
+ )
+
+ notice "$BASHTARD_NAME/backup/$index" "> ${cmd_prune[*]}"
+ ${cmd_prune[@]}
+
+ # Create new backups
+ info "$BASHTARD_NAME/backup/$index" "Writing new filesystem backup to $repo"
+ cmd_create=(
+ "$borg" "create"
+ "--one-file-system"
+ "--remote-path" "$remote"
+ "$repo/hostfs::{fqdn}-$(datetime)"
+ )
+
+ # Add all paths to the command
+ while read -r path
+ do
+ cmd_create+=("$(config "$path")")
+ done < <(config_subkeys "bashtard.backup.fs.paths")
+
+ notice "$BASHTARD_NAME/backup/$index" "> ${cmd_create[*]}"
+ ${cmd_create[@]}
+}
+
+backup_database_postgresql() {
+ local index=$1 ; shift
+ local borg
+ local remote
+ local repo
+
+ if ! command -v "psql" > /dev/null 2>&1
+ then
+ emerg "$BASHTARD_NAME/backup/$index" "The backup command depends on 'psql' being installed and available in your \$PATH"
+ return 4
+ fi
+
+ if ! command -v "pg_dump" > /dev/null 2>&1
+ then
+ emerg "$BASHTARD_NAME/backup/$index" "The backup command depends on 'pg_dump' being installed and available in your \$PATH"
+ return 4
+ fi
+
+ PGPASSWORD="$(config "bashtard.backup.db.postgresql.password" "")"
+ PGUSER="$(config "bashtard.backup.db.postgresql.user" "postgres")"
+ borg="$(config "bashtard.backup.borg.command" "borg")"
+ remote="$(config "bashtard.backups.borg.remote_paths.$index" "borg")"
+ repo="$(config "bashtard.backup.repositories.$index")"
+
+ [[ $PGPASSWORD == "" ]] && export PGPASSWORD
+ export PGUSER
+
+ while read -r database
+ do
+ [[ $database == "postgres" ]] && continue
+ [[ $database =~ template* ]] && continue
+
+ # Prune old backups
+ info "$BASHTARD_NAME/backup/$index" "Pruning PostgreSQL backups of $database in $repo"
+ $borg prune \
+ --keep-daily "$(config "bashtard.backup.keep.daily" 7)" \
+ --keep-weekly "$(config "bashtard.backup.keep.weekly" 4)" \
+ --keep-monthly "$(config "bashtard.backup.keep.monthly" 6)" \
+ --keep-yearly "$(config "bashtard.backup.keep.yearly" 1)" \
+ --prefix "$database-" \
+ --remote-path "$remote" \
+ "$repo/postgresql-$database"
+
+ # Create new backups
+ info "$BASHTARD_NAME/backup/$index" "Writing new PostgreSQL backup of $database to $repo"
+ pg_dump "$database" \
+ | borg create \
+ --remote-path "$remote" \
+ "$repo/postgresql-$database::$database-$(datetime)"
+ done < <(psql -AXt -d template1 -c "SELECT datname FROM pg_database")
+}