This repository has been archived on 2025-03-21. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
uberspace-backup/uberspace-backup.sh
Max Mehl 8faea3fef1 Run two-stages backups (#1)
1. Download to a local directory, archive/encrypt there
1. Push encrypted archive to remote folder

This helps when the backup destination is e.g. a NFS drive.

Also, introduce harder checks and fix some flaws.

Reviewed-on: #1
Co-authored-by: Max Mehl <mail@mehl.mx>
Co-committed-by: Max Mehl <mail@mehl.mx>
2023-12-13 12:22:54 +01:00

159 lines
5.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2019 Max Mehl <mail [at] mehl [dot] mx>
# SPDX-License-Identifier: GPL-3.0-or-later
########################################################################
#
# Saves specific files and directories from a remote server via SSH.
# Provides easy shortcuts for Uberspace.de hosts.
# README.md provides more details.
#
########################################################################
# Fail fast on errors
set -Eeuo pipefail
# Set correct UTF-8 encoding (for FreeBSD jail)
export LC_ALL=en_US.UTF-8
CURDIR=$(dirname "$(readlink -f "$0")")
if [ ! -e "$CURDIR"/config.cfg ]; then echo "Missing config.cfg file. Edit and rename config.cfg.sample"; exit 1; fi
source "$CURDIR"/config.cfg
if [ ! -e "${HOSTS}" ]; then echo "Missing hosts file. Please set a correct value of HOSTS= in your config file. Current value: ${HOSTS}"; exit 1; fi
if [ -n "${SSH_KEY}" ]; then
SSH_KEY_ARG="-i ${SSH_KEY}"
else
# defaults
SSH_KEY_ARG=""
SSH_KEY=~/.ssh/id_rsa
fi
# Get current date
DATE=$(date +"%Y-%m-%d_%H-%M")
LOG="$CURDIR"/backup.log
function trim {
sed -r -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g'
}
function pdate {
DATE=$(date +%y-%m-%d_%H:%M:%S)
echo "[$DATE]"
}
function logecho {
# Echo string and copy it to log while attaching the current date
echo "$(pdate) $*"
echo "$(pdate) $*" >> "$LOG"
}
while read -r line; do
# if line is a comment or blank, go to next line
if echo "$line" | grep -qE "^\s*(#|$)"; then continue; fi
RHOST=$(echo "$line" | cut -d";" -f1 | trim)
# Jump to next line if this line's host does not match host of first argument (if given)
if [[ "${1-}" != "" ]] && [[ "${1-}" != "${RHOST}" ]]; then
continue
fi
# Task ssh-checker.sh to check this host
if ! "${CURDIR}"/ssh-checker.sh "${RHOST}"; then
logecho "${RHOST}: ERROR when connecting via SSH. Please run ssh-checker.sh to debug."
logecho "${RHOST}: Aborting backup after an error."
continue
fi
RUSER=$(echo "$RHOST" | cut -d"@" -f1)
ALLRDIR=$(echo "$line" | cut -d";" -f2 | trim)
US_VERSION=$(echo "$line" | cut -d";" -f3 | trim)
# Get SSH port if needed
if echo "$RHOST" | grep -q ":"; then
RPORT=$(echo "$RHOST" | cut -d":" -f2)
RHOST=$(echo "$RHOST" | cut -d":" -f1)
RPORT_ARG="-p ${RPORT}"
else
# defaults
RPORT=""
RPORT_ARG=""
fi
logecho "${RHOST}: Starting backups"
NORDIR=$(echo "$ALLRDIR" | grep -o "|" | wc -l || true)
NORDIR=$(($NORDIR + 1))
# Loop through all backup sources
for ((i = 1; i <= $NORDIR; i++)); do
RDIR=$(echo "$ALLRDIR" | cut -d"|" -f${i} | trim)
# Set a relative destination directory
if [ "${RDIR}" == "%virtual" ]; then
RDIR=/var/www/virtual/${RUSER}
DEST_REL="$RHOST/$DATE/virtual"
elif [ "${RDIR}" == "%mysql" ]; then
RDIR=mysql
DEST_REL="$RHOST/$DATE/$(basename "${RDIR}")"
elif [ "${RDIR}" == "%mails" ]; then
RDIR=/home/${RUSER}/users
DEST_REL="$RHOST/$DATE/mails"
elif [ "${RDIR}" == "%home" ]; then
RDIR=/home/${RUSER}
DEST_REL="$RHOST/$DATE/home"
else
DEST_REL="$RHOST/$DATE/$(basename "${RDIR}")"
fi
# Define absolute temporary and final backup destination paths
# Example:
# DEST=/tmp/uberspace-backup/user@example.com/2019-01-01/virtual
# DEST_FINAL=/media/Uberspace/user@example.com/2019-01-01/
DEST="${TEMPDIR}/${DEST_REL}"
DEST_FINAL="$(dirname "${BACKUPDIR}/${DEST_REL}")"
# Set Source directory, and make exception for %mysql
SOURCE="${RDIR}"
if [ "${RDIR}" == "mysql" ]; then
if [[ $US_VERSION == 6 ]]; then
SOURCE=/mysqlbackup/latest/${RUSER}
else
SOURCE=/mysql_backup/current/${RUSER}
fi
fi
# Create temporary and final backup destination if necessary
if [ ! -e "${DEST}" ]; then mkdir -p "${DEST}"; fi
if [ ! -e "${DEST_FINAL}" ]; then mkdir -p "${DEST_FINAL}"; fi
# RSYNC
logecho "${RHOST}: Downloading ${SOURCE} to ${DEST}"
rsync -a -e "ssh -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o Compression=no -T -x ${RPORT_ARG} ${SSH_KEY_ARG}" "${RHOST}:${SOURCE}"/ "${DEST}"/
# Pack backup directory, and delete uncompressed one
logecho "${RHOST}: Archiving $(basename "${DEST}")"
tar cf "${DEST}".tar -C $(echo ${DEST} | sed "s|$(basename ${DEST})$||") $(basename ${DEST}) # TODO: avoid absolute paths
rm -rf "${DEST}"
# Encrypt archive with GPG (it compresses at the same time)
logecho "${RHOST}: Encrypting and compressing $(basename "${DEST}")"
gpg --output "${DEST}".tar.gpg --encrypt --recipient ${GPG} "${DEST}".tar
rm "${DEST}".tar
# Push encrypted backup to final backup destination
logecho "${RHOST}: Moving $(basename "${DEST}") to ${DEST_FINAL}"
cp "${DEST}".tar.gpg "${DEST_FINAL}/"
rm "${DEST}".tar.gpg
done # End of loop through all backup sources
# Delete all old directories except the $MAXBAK most recent
if [ $(ls -tp "${BACKUPDIR}"/"${RHOST}"/ | grep '/$' | wc -l | tr -d ' ') -gt $MAXBAK ]; then
oldbackups=$(ls -tp "${BACKUPDIR}"/"${RHOST}"/ | grep '/$' | tail -n +$(($MAXBAK + 1)))
logecho "${RHOST}: Removing older backup directories: ${oldbackups}"
ls -tpd "${BACKUPDIR}"/"${RHOST}"/* | grep '/$' | tail -n +$(($MAXBAK + 1)) | xargs -0 | xargs rm -r --
fi
done < "$HOSTS"
logecho "Finished all operations."