jean-cloud-services/provisioning/group_vars/borg-client/borg-backup.sh
2023-04-24 12:30:17 +02:00

187 lines
8.3 KiB
Bash
Executable File

#!/bin/bash
# borg-backup.sh
# Script to run regularly to backup a Jean-Cloud machine
#
# This will create a separate borg repo for every item in the BORG_REPOS variable
# And in each location specified in the BORG_HOSTS variable
# Use the file borg-conf.env to set these.
#
# If it finds an item in the BORG_REPOS that isn't yet a borg repository on one
# of the BORG_HOSTS, it will init a new repo there.
#
# Dependencies:
# packages: borg > 1.4
# scripts: /usr[/local]/bin/driglibash-base
# files: /data/borg/config/borg-conf.env
# /data/borg/config/.borgexclude
# Cheatsheet:
# ${#array[@]} number of elements in array
# ${array[@]} each element in array (separate words)
# ${array[i]} i-th element in array
if test -s /usr/local/bin/driglibash-base -a -r /usr/local/bin/driglibash-base ; then
. /usr/local/bin/driglibash-base
elif test -s /usr/bin/driglibash-base -a -r /usr/bin/driglibash-base ; then
. /usr/bin/driglibash-base
else
die "Could'nt source driglibash. See https://github.com/adrian-amaglio/driglibash/"
fi
BORG_ENV="/data/borg/config/borg-conf.env";
test -s "$BORG_ENV" && test -r "$BORG_ENV" || die "Couldn't find \"$BORG_ENV\" configuration file!"
. "$BORG_ENV"
mkdir -p "$BORG_BASE_DIR" "$BORG_CACHE_DIR" "$BORG_CONFIG_DIR" "$BORG_TMPDIR" "$BORG_SECURITY_DIR" "$BORG_SECURITY_DIR/passphrases" "$BORG_SECURITY_DIR/repokeys"
function init_repo() {
# args :
# $1 : host (local path or ssh where the borg repo is stored)
# $2 : path (local dir(s) to be saved in the repo)
# $3 : name of the repo on (remote) host
# $4 : unique alias to identiy the host
test "$verbosity" -gt 0 && echo "init_repo( $1 \\ $2 \\ $3)"
mkdir -p "$BORG_SECURITY_DIR/passphrases/$4/"
mkdir -p "$BORG_SECURITY_DIR/repokeys/$4/"
#create passphrase
LC_ALL=C tr -dc A-Za-z0-9 </dev/urandom | head -c 128 > "$BORG_SECURITY_DIR/passphrases/$4/$3"
export BORG_PASSPHRASE=$(cat "$BORG_SECURITY_DIR/passphrases/$4/$3")
#init repo
test "$verbosity" -gt 1 && echo "borg init ${verbosity:+"--progress"} --make-parent-dirs -e repokey "$1/$3""
test "$verbosity" -gt 3 && read -p " Continue ?"
run borg init ${verbosity:+"--progress"} --make-parent-dirs -e repokey "$1/$3"
#create first entry
test "$verbosity" -gt 1 && echo "borg create ${verbosity:+"--progress"} ${BORG_EXCLUDE_FILE:+"--exclude-from $BORG_EXCLUDE_FILE"} "$1/$3"::"init-$(date +%Y-%m-%d_%H-%M-%S)" "$2""
test "$verbosity" -gt 3 && read -p " Continue ?"
run borg create ${verbosity:+"--progress"} ${BORG_EXCLUDE_FILE:+--exclude-from "$BORG_EXCLUDE_FILE"} "$1/$3"::"init-$(date +%Y-%m-%d_%H-%M-%S)" "$2"
#export repokey in case of repo catastrophic loss
test "$verbosity" -gt 1 && echo "borg key export "$1/$3" "$BORG_SECURITY_DIR/repokeys/$3""
test "$verbosity" -gt 3 && read -p " Continue ?"
run borg key export "$1/$3" "$BORG_SECURITY_DIR/repokeys/$4/$3"
#TODO These keys should be backuped somewhere
}
for alias in "${!host_mode[@]}" ; do
# Begin parameter validation
test -n "${host_repo_dir["$alias"]}" && test -d "${host_repo_dir[$alias]}" || pathchk -p -P "${host_repo_dir["$alias"]}" 2>/dev/null && mkdir -p "${host_repo_dir[$alias]}" || die "Config error! Host $alias : "${host_repo_dir["$alias"]}" isn't a valid repo dir."
if test "${host_mode[$alias]}" = "local" ; then
host="${host_repo_dir[$alias]}"
elif test "${host_mode[$alias]}" = "ssh" ; then
test -n "${host_user["$alias"]}" && echo "${host_user["$alias"]}" | grep -q -E "^[a-z_][a-z0-9_-]*$" || die "Config error! Host $alias : ${host_user["$alias"]} isn't a valid username."
test -z ${host_host["$alias"]} && die "Config error! Host $alias : you must provide a host in ssh mode!"
check_host=false
# IPv4 regexp
echo ${host_host["$alias"]} | grep -q -E "^([0-2]?[0-9]{1,2}\.){3}[0-2]?[0-9]{1,2}$" && check_host=true
# IPv6 regexp
echo ${host_host["$alias"]} | grep -q -E "^(((([a-f]|[0-9]){1,4})|:):){6}([a-f]|[0-9]){1,4}$" && check_host=true
# URL regexp
echo ${host_host["$alias"]} | grep -q -E "^[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*\.[a-z]{2,5}$" && check_host=true
test "$check_host" = true || die "Config error! Host $alias : ${host_host["$alias"]} isn't a valid host (expected IPv4, IPv6 or URL)."
test -n "${host_port["$alias"]}" && test "${host_port["$alias"]}" -gt 2>/dev/null 0 && test "${host_port["$alias"]}" -le 65536 || die "Config error! Host $alias : "${host_port["$alias"]}" isn't a valid port."
# End parameter validation
# Parameter expansion lvl: I was not ready for this.
host="ssh://${host_user["$alias"]:+${host_user["$alias"]}@}\
${host_host["$alias"]:+${host_host["$alias"]}}\
${host_port["$alias"]:+:${host_port["$alias"]}}\
${host_repo_dir["$alias"]:+${host_repo_dir["$alias"]}}"
# super-secret-back-door
elif test "${host_mode[$alias]}" = "iknowwhatimdoing" ; then
host="${host_host["$alias"]}"
else
die "Config error! Host $alias : unrecognized mode ${host_mode[$alias]}"
fi
test "$verbosity" -gt 0 && section "$alias: $host"
for repo in "${local_repos[@]}" ; do
test "$verbosity" -gt 0 && section "$repo"
# we use a python-like name for the repo:
reponame=$(echo "$repo" | tr "/" ".")
#Check that the repo exists (we could be backuping a new service)
check_repo_exists=false;
if test "${host_mode[$alias]}" = "ssh" ; then
export BORG_PASSPHRASE=$(cat "$BORG_SECURITY_DIR/passphrases/$alias/$reponame") && borg list "$host/$reponame" > /dev/null && check_repo_exists=true || "Could'nt open repo $reponame at host $host. Creating it."
fi
test "${host_mode[$alias]}" = "local" && test -d "$host/$reponame" && test -s "$host/$reponame/README" && grep -q "This is a Borg Backup repository." "$host/$reponame/README" && check_repo_exists=true
#TODO: this doesn't check if a distant repo exists
if $check_repo_exists = true ; then
#it's okay, repo exists, start the normal backup
test -s "$BORG_SECURITY_DIR/passphrases/$alias/$reponame" && export BORG_PASSPHRASE=$(cat "$BORG_SECURITY_DIR/passphrases/$alias/$reponame") || die "Couldn't get passphrase for repo $alias/$repo from file: $BORG_SECURITY_DIR/passphrases/$alias/$reponame"
test $verbosity -gt 1 && echo "borg create ${verbosity:+"--progress"} ${BORG_EXCLUDE_FILE:+--exclude-from "$BORG_EXCLUDE_FILE"} --compression obfuscate,115,auto,zstd,20 "$host/$reponame"::"$reponame-$(date +%Y-%m-%d_%H-%M-%S)" "$repo""
test $verbosity -gt 3 && read -p " Continue ?"
run borg create ${verbosity:+"--progress"} ${BORG_EXCLUDE_FILE:+--exclude-from "$BORG_EXCLUDE_FILE"} --compression obfuscate,115,auto,zstd,20 "$host/$reponame"::"$reponame-$(date +%Y-%m-%d_%H-%M-%S)" "$repo"
#TODO Check that zstd lvl 20 compression is not too cpu-intensive, could be reduced (or use lz4) (see borg help benchmark)
# Global retention parameters
hourly=${BORG_KEEP_HOURLY[all]:+"--keep-hourly=${BORG_KEEP_HOURLY[all]} "}
daily=${BORG_KEEP_DAILY[all]:+"--keep-daily=${BORG_KEEP_DAILY[all]} "}
weekly=${BORG_KEEP_WEEKLY[all]:+"--keep-weekly=${BORG_KEEP_WEEKLY[all]} "}
monthly=${BORG_KEEP_MONTHLY[all]:+"--keep-monthly=${BORG_KEEP_MONTHLY[all]} "}
yearly=${BORG_KEEP_YEARLY[all]:+"--keep-yearly=${BORG_KEEP_YEARLY[all]} "}
test $verbosity -gt 2 && echo "Global retention policy : $hourly $daily $weekly $monthly $yearly"
# Per-host retention parameters
test -n "${BORG_KEEP_HOURLY["$alias"]}" && hourly="--keep-hourly=${BORG_KEEP_HOURLY["$alias"]}"
test -n "${BORG_KEEP_DAILY["$alias"]}" && daily="--keep-daily=${BORG_KEEP_DAILY["$alias"]}"
test -n "${BORG_KEEP_WEEKLY["$alias"]}" && weekly="--keep-weekly=${BORG_KEEP_WEEKLY["$alias"]}"
test -n "${BORG_KEEP_MONTHLY["$alias"]}" && monthly="--keep-monthly=${BORG_KEEP_MONTHLY["$alias"]}"
test -n "${BORG_KEEP_YEARLY["$alias"]}" && yearly="--keep-yearly=${BORG_KEEP_YEARLY["$alias"]}"
test $verbosity -gt 2 && echo "$alias retention policy : $hourly $daily $weekly $monthly $yearly"
test $verbosity -gt 1 && echo "borg prune ${verbosity:+"--progress"} --list --glob-archives \"$reponame*\" $hourly $daily $weekly $monthly $yearly \"$host/$reponame\""
test $verbosity -gt 3 && read -p " Continue ?"
run borg prune ${verbosity:+"--progress"} --list --glob-archives \"$reponame*\" $hourly $daily $weekly $monthly $yearly "$host/$reponame"
else
#If repo doesn't exist, create it
init_repo "$host" "$repo" "$reponame" "$alias"
fi
done
done