#!/usr/bin/env bash # SPDX-FileCopyrightText: 2019 Max Mehl # 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."