1032 lines
37 KiB
Bash
Executable File
1032 lines
37 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
###############################################################################
|
|
# Copyright (c) 2024 Zion Networks UG #
|
|
# #
|
|
# Permission is hereby granted, free of charge, to any person obtaining a #
|
|
# copy of this software and associated documentation files (the "Software"), #
|
|
# to deal in the Software without restriction, including without limitation #
|
|
# the rights to use, copy, modify, merge, publish, distribute, sublicense, #
|
|
# and/or sell copies of the Software, and to permit persons to whom the #
|
|
# Software is furnished to do so, subject to the following conditions: #
|
|
# #
|
|
# The above copyright notice and this permission notice shall be included in #
|
|
# all copies or substantial portions of the Software. #
|
|
# #
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, #
|
|
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER #
|
|
# DEALINGS IN THE SOFTWARE. #
|
|
###############################################################################
|
|
|
|
# Planned Features:
|
|
# - Add support for more distributions
|
|
# - Add support for databases
|
|
# - Pre-Backup commands
|
|
# - Post-Backup commands
|
|
# - Add support to run, restore, prune, check, delete and list backups
|
|
# - Add support for multiple backup repositories
|
|
# - Add support for multiple backup configurations
|
|
# - Add support for multiple backup servers
|
|
|
|
# SETTINGS - Make sure to adjust these settings to your needs
|
|
DEBUG=0 # Set to 1 to enable debug mode
|
|
|
|
BACKUP_REPO="Backups/$(hostname | tr '[:lower:]' '[:upper:]')" # Repository path on the backup server
|
|
BACKUP_ENCRYPTION="none" # Encryption method for the backup repository (default: none)
|
|
BACKUP_PASSPHRASE='example' # Make sure to change this to a secure passphrase (will be ignored if encryption is set to none)
|
|
BACKUP_DIRS="/etc /var/log /home" # Directories to backup, separated by space
|
|
BACKUP_LABEL_PREFIX="BACKUP-" # Prefix for the backup label
|
|
BACKUP_CRON_SCHEDULE="0 3 * * *" # Cron schedule for backups (default: daily at 3:00 AM)
|
|
|
|
BORGMATIC_CONFIG_FILE="/etc/borgmatic/config.yaml" # Path to the borgmatic configuration file - DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING
|
|
|
|
SSH_HOST="127.0.0.1" # Hostname or IP address of the backup server
|
|
SSH_PORT="22" # SSH port of the backup server (default: 22)
|
|
SSH_USER="root" # SSH user on the backup server
|
|
SSH_PASSWORD='12345678' # Only required for copying the ssh key to the backup server
|
|
SSH_KEY_NAME="borgmatic"
|
|
SSH_KEY_TYPE="ed25519" # SSH key type (e.g. rsa, dsa, ecdsa, ed25519), default: ed25519
|
|
SSH_KEY_FILE="/root/.ssh/${SSH_KEY_NAME}_${SSH_KEY_TYPE}"
|
|
|
|
# SCRIPT - DO NOT EDIT
|
|
|
|
# internal variables
|
|
AUTO=0 # Set to 1 to enable automatic, non-interactive mode
|
|
DO_UPGRADE_ALL=0 # Set to 1 to upgrade all installed packages
|
|
DO_UPGRADE_REQUIRED=0 # Set to 1 to upgrade only required packages
|
|
FIRST_BACKUP=0 # Set to 1 to run the first backup after setup
|
|
IS_REMOTE_SYNOLGY=0 # Set to 1 if the remote end is a Synology NAS
|
|
OVERRIDE_CRONTAB=0 # Set to 1 to override the existing crontab file
|
|
OVERRIDE_CONFIG=0 # Set to 1 to override the existing borgmatic configuration file
|
|
OVERRIDE_REPOSITORY=0 # Set to 1 to override the existing backup repository
|
|
SKIP_CONFIG=0 # Set to 1 to skip the borgmatic configuration
|
|
|
|
# constants
|
|
readonly SCRIPT_NAME="Borgmatic Backup Setup Tool"
|
|
readonly SCRIPT_AUTHOR="Zion Networks at admin@zion-networks.de"
|
|
readonly SCRIPT_SUPPORT="admin@zion-networks.de"
|
|
readonly VERSION="1.2.4"
|
|
|
|
# logging functions
|
|
function inf {
|
|
echo -e "\e[97m[$(date +%H:%M:%S)] \e[1mINF\e[0m $1"
|
|
}
|
|
|
|
function wrn {
|
|
echo -e "\e[93m[$(date +%H:%M:%S)] \e[1mWRN\e[0m $1"
|
|
}
|
|
|
|
function err {
|
|
echo -e "\e[91m[$(date +%H:%M:%S)] \e[1mERR\e[0m $1"
|
|
}
|
|
|
|
function dbg {
|
|
if [ $DEBUG -eq 1 ]; then
|
|
echo -e "\e[94m[$(date +%H:%M:%S)] \e[1mDBG\e[0m \e[3m$1\e[0m"
|
|
fi
|
|
}
|
|
|
|
# logging prompt functions
|
|
function infp {
|
|
echo -e -n "\e[97m[$(date +%H:%M:%S)] \e[1mINF\e[0m $1 "
|
|
read -p "" -r
|
|
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
return 1
|
|
else
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
function wrnp {
|
|
echo -e -n "\e[93m[$(date +%H:%M:%S)] \e[1mWRN\e[0m $1 "
|
|
read -p "" -r
|
|
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
return 1
|
|
else
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
function errp {
|
|
echo -e -n "\e[91m[$(date +%H:%M:%S)] \e[1mERR\e[0m $1 "
|
|
read -p "" -r
|
|
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
return 1
|
|
else
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
# logging functions that wait for user input (single key)
|
|
function infw {
|
|
echo -e -n "\e[97m[$(date +%H:%M:%S)] \e[1mINF\e[0m $1 "
|
|
read -n 1 -r
|
|
}
|
|
|
|
function wrnw {
|
|
echo -e -n "\e[93m[$(date +%H:%M:%S)] \e[1mWRN\e[0m $1 "
|
|
read -n 1 -r
|
|
}
|
|
|
|
function errw {
|
|
echo -e -n "\e[91m[$(date +%H:%M:%S)] \e[1mERR\e[0m $1 "
|
|
read -n 1 -r
|
|
}
|
|
|
|
# logging functions with follow up (like "[HH:MM:SS] INF Installig package ... OK" where OK is a follow up)
|
|
function inf_follow {
|
|
if [ $DEBUG -eq 1 ]; then
|
|
echo -e "\e[97m[$(date +%H:%M:%S)] \e[1mINF\e[0m $1"
|
|
else
|
|
echo -e -n "\e[97m[$(date +%H:%M:%S)] \e[1mINF\e[0m $1"
|
|
fi
|
|
|
|
dbg "Running ${@:4}"
|
|
|
|
${@:4}
|
|
_r=$?
|
|
|
|
dbg "Follow exit status: $_r"
|
|
|
|
if [ $_r -eq 0 ]; then
|
|
if [ $DEBUG -eq 1 ]; then
|
|
echo -e "\e[97m[$(date +%H:%M:%S)] \e[1mINF\e[0m $1: $2"
|
|
else
|
|
echo -e " $2"
|
|
fi
|
|
else
|
|
if [ $DEBUG -eq 1 ]; then
|
|
echo -e "\e[97m[$(date +%H:%M:%S)] \e[1mINF\e[0m $1: $3"
|
|
else
|
|
echo -e " $3"
|
|
fi
|
|
fi
|
|
|
|
return $_r
|
|
}
|
|
|
|
function wrn_follow {
|
|
if [ $DEBUG -eq 1 ]; then
|
|
echo -e "\e[93m[$(date +%H:%M:%S)] \e[1mWRN\e[0m $1"
|
|
else
|
|
echo -e -n "\e[93m[$(date +%H:%M:%S)] \e[1mWRN\e[0m $1"
|
|
fi
|
|
|
|
_r=$(${@:4})
|
|
|
|
dbg "Follow exit status: $_r"
|
|
|
|
if [ $_r -eq 0 ]; then
|
|
if [ $DEBUG -eq 1 ]; then
|
|
echo -e "\e[93m[$(date +%H:%M:%S)] \e[1mWRN\e[0m $1: $2"
|
|
else
|
|
echo -e " $2"
|
|
fi
|
|
else
|
|
if [ $DEBUG -eq 1 ]; then
|
|
echo -e "\e[93m[$(date +%H:%M:%S)] \e[1mWRN\e[0m $1: $3"
|
|
else
|
|
echo -e " $3"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
function err_follow {
|
|
if [ $DEBUG -eq 1 ]; then
|
|
echo -e "\e[91m[$(date +%H:%M:%S)] \e[1mERR\e[0m $1"
|
|
else
|
|
echo -e -n "\e[91m[$(date +%H:%M:%S)] \e[1mERR\e[0m $1"
|
|
fi
|
|
|
|
_r=$(${@:4})
|
|
|
|
dbg "Follow exit status: $_r"
|
|
|
|
if [ $_r -eq 0 ]; then
|
|
if [ $DEBUG -eq 1 ]; then
|
|
echo -e "\e[91m[$(date +%H:%M:%S)] \e[1mERR\e[0m $1: $2"
|
|
else
|
|
echo -e " $2"
|
|
fi
|
|
else
|
|
if [ $DEBUG -eq 1 ]; then
|
|
echo -e "\e[91m[$(date +%H:%M:%S)] \e[1mERR\e[0m $1: $3"
|
|
else
|
|
echo -e " $3"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# setup functions
|
|
function apt_update {
|
|
exec 3>&1
|
|
status=$(apt-get update 2>&1 | while read -r line; do dbg "$line"; done 1>&3)
|
|
exec 3>&-
|
|
return $status
|
|
}
|
|
|
|
function apt_upgrade {
|
|
exec 3>&1
|
|
status=$(apt-get upgrade -y 2>&1 | while read -r line; do dbg "$line"; done 1>&3)
|
|
exec 3>&-
|
|
|
|
return $status
|
|
}
|
|
|
|
function apt_upgrade_selected {
|
|
exec 3>&1
|
|
status=$(apt-get install -y $* 2>&1 | while read -r line; do dbg "$line"; done 1>&3)
|
|
exec 3>&-
|
|
|
|
return $status
|
|
}
|
|
|
|
function apt_install {
|
|
exec 3>&1
|
|
status=$(apt-get install -y $1 2>&1 | while read -r line; do dbg "$line"; done 1>&3)
|
|
exec 3>&-
|
|
|
|
return $status
|
|
}
|
|
|
|
function pipx_install {
|
|
exec 3>&1
|
|
status=$(pipx install $1 2>&1 | while read -r line; do dbg "$line"; done 1>&3)
|
|
exec 3>&-
|
|
|
|
return $status
|
|
}
|
|
|
|
function pipx_upgrade {
|
|
exec 3>&1
|
|
status=$(pipx upgrade $1 2>&1 | while read -r line; do dbg "$line"; done 1>&3)
|
|
exec 3>&-
|
|
|
|
return $status
|
|
}
|
|
|
|
function apt_is_installed {
|
|
status=$(dpkg -l $1 2>&1 | tee >(while read -r line; do dbg "$line"; done))
|
|
|
|
dbg "Status: $status"
|
|
|
|
# Check if package is installed based on the output of dpkg -l
|
|
if echo "$status" | grep -q "^ii"; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function pipx_is_installed {
|
|
if ! check_command pipx; then
|
|
return 1
|
|
fi
|
|
|
|
status=$(pipx list 2>&1 | tee >(while read -r line; do dbg "$line"; done) | grep -q $1)
|
|
|
|
return $?
|
|
}
|
|
|
|
function check_command {
|
|
exec 3>&1
|
|
command -v $1 >/dev/null 2>&1
|
|
status=$?
|
|
if [ $status -eq 0 ]; then
|
|
echo "$1 exists" | while read -r line; do dbg "$line"; done 1>&3
|
|
else
|
|
echo "$1 does not exist" | while read -r line; do dbg "$line"; done 1>&3
|
|
fi
|
|
exec 3>&-
|
|
return $status
|
|
}
|
|
|
|
function run_command {
|
|
dbg "Running command: sudo $*"
|
|
|
|
exec 3>&1
|
|
output=$(sudo "$@" 2>&1)
|
|
status=$?
|
|
echo "$output" | while read -r line; do dbg "$line"; done 1>&3
|
|
exec 3>&-
|
|
|
|
dbg "Command exit status: $status"
|
|
|
|
return $status
|
|
}
|
|
|
|
function run_command_logged {
|
|
# run command and log output using inf function for each line
|
|
|
|
dbg "Running command: sudo $*"
|
|
|
|
exec 3>&1
|
|
sudo "$@" 2>&1 | while read -r line; do inf "$line"; done 1>&3
|
|
status=$?
|
|
exec 3>&-
|
|
|
|
dbg "Command exit status: $status"
|
|
|
|
return $status
|
|
}
|
|
|
|
function file_exists {
|
|
if [ -f $1 ]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function dir_exists {
|
|
if [ -d $1 ]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function add_crontab {
|
|
if check_crontab; then
|
|
if [ $OVERRIDE_CRONTAB -eq 1 ]; then
|
|
wrn "Crontab file already exists. Overriding existing crontab file."
|
|
else
|
|
err "Crontab file already exists. Please remove the existing crontab file or use the --override-crontab option."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
echo "$BACKUP_CRON_SCHEDULE root PATH=\$PATH:/usr/bin:/usr/local/bin /root/.local/bin/borgmatic --verbosity -1 --syslog-verbosity 1" > /etc/cron.d/borgmatic
|
|
|
|
return $?
|
|
}
|
|
|
|
function check_crontab {
|
|
# check for file /etc/cron.d/borgmatic
|
|
if [ -f /etc/cron.d/borgmatic ]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function is_remote_synology {
|
|
run_command ssh -i "$SSH_KEY_FILE" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" test -f /etc/synoinfo.conf
|
|
return $?
|
|
}
|
|
|
|
# main script
|
|
|
|
# Available arguments:
|
|
# -a, --auto: Enable automatic, non-interactive mode
|
|
# -d, --debug: Enable debug mode
|
|
# -f, --first-backup: Run the first backup after setup
|
|
# -o, --override-crontab: Override the existing crontab file
|
|
# -c, --override-config: Override the existing borgmatic configuration file
|
|
# -r, --override-repository: Override the existing backup repository
|
|
# -s, --upgrade-required: Upgrade only required packages
|
|
# -u, --upgrade-all: Upgrade all installed packages
|
|
# -v, --version: Show script version
|
|
# -h, --help: Show help message
|
|
#
|
|
# -R, --repo: Set the backup repository path
|
|
# -E, --encryption: Set the encryption method
|
|
# -P, --passphrase: Set the backup passphrase
|
|
# -D, --dirs: Set the backup directories (separated by space as a string, for example: --dirs "/etc /var/log /home")
|
|
# -L, --label: Set the backup label prefix
|
|
# -S, --schedule: Set the cron schedule
|
|
#
|
|
# -H, --host: Set the backup server hostname or IP address
|
|
# -P, --port: Set the backup server SSH port
|
|
# -U, --user: Set the backup server SSH user
|
|
# -W, --password: Set the backup server SSH password
|
|
# -K, --key-name: Set the SSH key name
|
|
# -T, --key-type: Set the SSH key type
|
|
# -F, --key-file: Set the SSH key file path
|
|
#
|
|
# Example usage:
|
|
# ./borgmatic_setup.sh -R "Backups/MyServer" \
|
|
# -E "none" -P "example" \
|
|
# -D "/etc /var/log /home" \
|
|
# -L "BACKUP-" \
|
|
# -S "0 3 * * *" \
|
|
# -H "192.168.1.200" \
|
|
# -P "22" \
|
|
# -U "root" \
|
|
# -W "12345678" \
|
|
# -K "borgmatic" \
|
|
# -T "ed25519" \
|
|
# -F "/root/.ssh/borgmatic_ed25519"
|
|
#
|
|
# or with long options:
|
|
# ./borgmatic_setup.sh --repo "Backups/MyServer" \
|
|
# --encryption "none" --passphrase "example" \
|
|
# --dirs "/etc /var/log /home" \
|
|
# --label "BACKUP-" \
|
|
# --schedule "0 3 * * *" \
|
|
# --host "192.168.1.200" \
|
|
# --port "22" \
|
|
# --user "root" \
|
|
# --password "12345678"
|
|
# --key-name "borgmatic" \
|
|
# --key-type "ed25519" \
|
|
# --key-file "/root/.ssh/borgmatic_ed25519"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
key="$1"
|
|
|
|
case $key in
|
|
-a|--auto)
|
|
AUTO=1
|
|
shift
|
|
;;
|
|
-d|--debug)
|
|
DEBUG=1
|
|
shift
|
|
;;
|
|
-v|--version)
|
|
echo "$SCRIPT_NAME v$VERSION"
|
|
exit 0
|
|
;;
|
|
-s|--upgrade-required)
|
|
DO_UPGRADE_REQUIRED=1
|
|
shift
|
|
;;
|
|
-u|--upgrade-all)
|
|
DO_UPGRADE_ALL=1
|
|
shift
|
|
;;
|
|
-f|--first-backup)
|
|
FIRST_BACKUP=1
|
|
shift
|
|
;;
|
|
-o|--override-crontab)
|
|
OVERRIDE_CRONTAB=1
|
|
shift
|
|
;;
|
|
-c|--override-config)
|
|
OVERRIDE_CONFIG=1
|
|
shift
|
|
;;
|
|
-k|--skip-config)
|
|
SKIP_CONFIG=1
|
|
shift
|
|
;;
|
|
-r|--override-repository)
|
|
OVERRIDE_REPOSITORY=1
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
inf "Usage: borgmatic_setup.sh [options]"
|
|
inf ""
|
|
inf "Options will override the default settings set in the script."
|
|
inf "If no options are provided, the script will run in interactive mode."
|
|
inf "All options are optional."
|
|
inf ""
|
|
if [ "$EUID" -ne 0 ]; then
|
|
inf "\e[1m\e[31mImportant: The script must be run as root to work properly.\e[0m"
|
|
inf ""
|
|
fi
|
|
inf "Options:"
|
|
inf " -a, --auto: Enable automatic, non-interactive mode"
|
|
inf " -d, --debug: Enable debug mode"
|
|
inf " -f, --first-backup: Run the first backup after setup"
|
|
inf " -o, --override-crontab: Override the existing crontab file"
|
|
inf " -c, --override-config: Override the existing borgmatic configuration file"
|
|
inf " -k, --skip-config: Skip the borgmatic configuration"
|
|
inf " -r, --override-repository: Override the existing backup repository"
|
|
inf " -s, --upgrade-required: Upgrade only required packages"
|
|
inf " -u, --upgrade-all: Upgrade all installed packages"
|
|
inf " -v, --version: Show script version"
|
|
inf " -h, --help: Show help message"
|
|
inf ""
|
|
inf " -R, --repo: Set the backup repository path"
|
|
inf " -E, --encryption: Set the encryption method"
|
|
inf " -P, --passphrase: Set the backup passphrase (if set but empty, the script will ask for the passphrase)"
|
|
inf " -D, --dirs: Set the backup directories (separated by space as a string, for example: --dirs \"/etc /var/log /home\")"
|
|
inf " -L, --label: Set the backup label prefix"
|
|
inf " -S, --schedule: Set the cron schedule"
|
|
inf ""
|
|
inf " -H, --host: Set the backup server hostname or IP address"
|
|
inf " -P, --port: Set the backup server SSH port"
|
|
inf " -U, --user: Set the backup server SSH user"
|
|
inf " -W, --password: Set the backup server SSH password (if set but empty, the script will ask for the password)"
|
|
inf " -K, --key-name: Set the SSH key name"
|
|
inf " -T, --key-type: Set the SSH key type"
|
|
inf " -F, --key-file: Set the SSH key file path"
|
|
inf ""
|
|
inf "Example usage:"
|
|
inf "./borgmatic_setup.sh --dirs \"/etc /var/log /home\" --host \"192.168.1.200\" --encryption \"none\""
|
|
inf ""
|
|
inf "or for an encrypted backup:"
|
|
inf "./borgmatic_setup.sh --dirs \"/etc /var/log /home\" --host \"192.168.1.200\" --encryption \"repokey\" --passphrase \"example\""
|
|
exit 0
|
|
;;
|
|
-R|--repo)
|
|
BACKUP_REPO="$2"
|
|
shift
|
|
shift
|
|
;;
|
|
-E|--encryption)
|
|
BACKUP_ENCRYPTION="$2"
|
|
shift
|
|
shift
|
|
;;
|
|
-P|--passphrase)
|
|
if [ -z $BACKUP_PASSPHRASE ]; then
|
|
BACKUP_PASSPHRASE="$2"
|
|
shift
|
|
shift
|
|
else
|
|
BACKUP_PASSPHRASE=""
|
|
shift
|
|
fi
|
|
;;
|
|
-D|--dirs)
|
|
BACKUP_DIRS="$2"
|
|
shift
|
|
shift
|
|
;;
|
|
-L|--label)
|
|
BACKUP_LABEL_PREFIX="$2"
|
|
shift
|
|
shift
|
|
;;
|
|
-S|--schedule)
|
|
BACKUP_CRON_SCHEDULE="$2"
|
|
shift
|
|
shift
|
|
;;
|
|
-H|--host)
|
|
SSH_HOST="$2"
|
|
shift
|
|
shift
|
|
;;
|
|
-P|--port)
|
|
SSH_PORT="$2"
|
|
shift
|
|
shift
|
|
;;
|
|
-U|--user)
|
|
SSH_USER="$2"
|
|
shift
|
|
shift
|
|
;;
|
|
-W|--password)
|
|
if [ -z $SSH_PASSWORD ]; then
|
|
SSH_PASSWORD="$2"
|
|
shift
|
|
shift
|
|
else
|
|
SSH_PASSWORD=""
|
|
shift
|
|
fi
|
|
;;
|
|
-K|--key-name)
|
|
SSH_KEY_NAME="$2"
|
|
shift
|
|
shift
|
|
;;
|
|
-T|--key-type)
|
|
SSH_KEY_TYPE="$2"
|
|
shift
|
|
shift
|
|
;;
|
|
-F|--key-file)
|
|
SSH_KEY_FILE="$2"
|
|
shift
|
|
shift
|
|
;;
|
|
*)
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
echo -e "\e[97m╭─────────────────────────────────────────────────────────────────────────────╮"
|
|
echo -e "│ │"
|
|
echo -e "│ \e[1m$SCRIPT_NAME\e[0m │"
|
|
echo -e "│ \e[1mv$VERSION\e[0m │"
|
|
echo -e "│ │"
|
|
echo -e "╰─────────────────────────────────────────────────────────────────────────────╯\e[0m"
|
|
|
|
inf "For support please contact $SCRIPT_AUTHOR"
|
|
|
|
if [ $DO_UPGRADE_ALL -eq 1 ]; then
|
|
DO_UPGRADE_REQUIRED=1
|
|
fi
|
|
|
|
if [ $DEBUG -eq 1 ]; then
|
|
dbg "Debug mode is enabled. This will print additional information."
|
|
fi
|
|
|
|
# check if script is running as root
|
|
if [ "$EUID" -ne 0 ]; then
|
|
err "Please run this script as root"
|
|
exit 1
|
|
fi
|
|
|
|
# check if script is running on a supported OS (Ubuntu or Debian)
|
|
if [ ! -f /etc/os-release ]; then
|
|
err "This script currently only supports Ubuntu and Debian"
|
|
exit 1
|
|
fi
|
|
|
|
wrn "This script will install \e[1mborgbackup\e[0m, \e[1mpipx\e[0m, python3-venv"
|
|
wrn "and \e[1msshpass\e[0m from the official repositories and \e[1mborgmatic\e[0m"
|
|
wrn "from the official PyPI repository."
|
|
|
|
inf ""
|
|
inf "The following settings will be used for the backup setup:"
|
|
inf "Backup repository: $BACKUP_REPO"
|
|
inf "Backup encryption: $BACKUP_ENCRYPTION"
|
|
inf "Backup directories: $BACKUP_DIRS"
|
|
inf "Backup cron schedule: $BACKUP_CRON_SCHEDULE"
|
|
inf "Backup server host: $SSH_USER@$SSH_HOST:$SSH_PORT"
|
|
inf ""
|
|
|
|
if [ $DO_UPGRADE_ALL -eq 1 ]; then
|
|
wrn "All installed packages will be upgraded!"
|
|
fi
|
|
|
|
if [ $OVERRIDE_CRONTAB -eq 1 ]; then
|
|
wrn "Existing cron job at /etc/cron.d/borgmatic will be overridden!"
|
|
fi
|
|
|
|
if [ $OVERRIDE_CONFIG -eq 1 ]; then
|
|
wrn "Existing borgmatic configuration file at $BORGMATIC_CONFIG_FILE will be overridden!"
|
|
fi
|
|
|
|
# only ask for confirmation in interactive mode
|
|
if [ $AUTO -eq 0 ] && ! wrnp "Do you want to continue? [y/N]"; then
|
|
inf "Aborted."
|
|
exit 1
|
|
fi
|
|
|
|
if ! check_command apt-get; then
|
|
err "apt-get is not installed. This script currently only supports Ubuntu and Debian."
|
|
exit 1
|
|
fi
|
|
|
|
if ! check_command dpkg; then
|
|
err "dpkg is not installed. This script only currently supports Ubuntu and Debian."
|
|
exit 1
|
|
fi
|
|
|
|
if ! inf_follow "Updating package lists..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" apt_update; then
|
|
exit 1
|
|
fi
|
|
|
|
if ! check_command sshpass; then
|
|
if ! inf_follow "Installing sshpass..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" apt_install sshpass; then
|
|
err "Installation of sshpass failed!"
|
|
exit 1
|
|
fi
|
|
else
|
|
if ! inf_follow "Upgrading sshpass..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" apt_upgrade sshpass; then
|
|
err "Upgrade of sshpass failed!"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if ! inf_follow "Checking if python3-venv is installed..." "\e[1;32mYes\e[0m" "\e[1;31mNo\e[0m" apt_is_installed python3-venv; then
|
|
if ! inf_follow "Installing python3-venv..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" apt_install python3-venv; then
|
|
err "Installation of python3-venv failed!"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ $DO_UPGRADE_ALL -eq 1 ]; then
|
|
if [ $AUTO -eq 1 ] || wrnp "Do you want to upgrade all installed packages? [y/N]"; then
|
|
if ! inf_follow "Upgrading packages..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" apt_upgrade; then
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [ $DO_UPGRADE_REQUIRED -eq 1 ]; then
|
|
if [ $AUTO -eq 1 ] || wrnp "Do you want to upgrade only required packages? [y/N]"; then
|
|
inf "Upgrading required packages..."
|
|
|
|
if ! inf_follow "Upgrading borgbackup..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" apt_upgrade_selected borgbackup; then
|
|
err "Upgrade of borgbackup failed!"
|
|
exit 1
|
|
fi
|
|
|
|
if ! inf_follow "Upgrading pipx..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" apt_upgrade_selected pipx; then
|
|
err "Upgrade of pipx failed!"
|
|
exit 1
|
|
fi
|
|
|
|
if ! inf_follow "Upgrading borgmatic..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" apt_upgrade_selected borgmatic; then
|
|
err "Upgrade of borgmatic failed!"
|
|
exit 1
|
|
fi
|
|
|
|
if ! inf_follow "Upgrading python3-venv..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" apt_upgrade_selected python3-venv; then
|
|
err "Upgrade of python3-venv failed!"
|
|
exit 1
|
|
fi
|
|
|
|
if ! inf_follow "Upgrading sshpass..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" apt_upgrade_selected sshpass; then
|
|
err "Upgrade of sshpass failed!"
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if ! inf_follow "Checking if borgbackup is installed..." "\e[1;32mYes\e[0m" "\e[1;31mNo\e[0m" apt_is_installed borgbackup; then
|
|
if ! inf_follow "Installing borgbackup..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" apt_install borgbackup; then
|
|
err "Installation of borgbackup failed!"
|
|
exit 1
|
|
fi
|
|
|
|
if ! inf_follow "Validating ..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" check_command borg; then
|
|
err "Installation of borgbackup failed!"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if ! inf_follow "Checking if pipx is installed..." "\e[1;32mYes\e[0m" "\e[1;31mNo\e[0m" apt_is_installed pipx; then
|
|
if ! inf_follow "Installing pipx..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" apt_install pipx; then
|
|
err "Installation of pipx failed!"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if ! inf_follow "Checking if borgmatic is installed..." "\e[1;32mYes\e[0m" "\e[1;31mNo\e[0m" pipx_is_installed borgmatic; then
|
|
if ! inf_follow "Installing borgmatic..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" pipx_install borgmatic; then
|
|
err "Installation of borgmatic failed!"
|
|
exit 1
|
|
fi
|
|
else
|
|
if [ $DO_UPGRADE_REQUIRED -eq 1 ]; then
|
|
if [ $AUTO -eq 1 ] || wrnp "Do you want to upgrade borgmatic? [y/N]"; then
|
|
if ! inf_follow "Upgrading borgmatic..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" pipx_upgrade borgmatic; then
|
|
err "Upgrade of borgmatic failed!"
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if ! inf_follow "Checking for ~/.ssh directory..." "\e[1;32mYes\e[0m" "\e[1;31mNo\e[0m" dir_exists ~/.ssh; then
|
|
if ! inf_follow "Creating ~/.ssh directory..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" mkdir -p ~/.ssh; then
|
|
err "Failed to create ~/.ssh directory."
|
|
err "Please make sure you have the required permissions."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if ! inf_follow "Checking for default ssh key at $SSH_KEY_FILE..." "\e[1;32mYes\e[0m" "\e[1;31mNo\e[0m" file_exists $SSH_KEY_FILE; then
|
|
inf "Generating a new ssh key pair..."
|
|
|
|
ssh-keygen -t $SSH_KEY_TYPE -C "borgmatic_backup_$(hostname)" -f $SSH_KEY_FILE -N "" 2>&1 | while read -r line; do dbg "$line"; done
|
|
|
|
if [ $? -eq 0 ]; then
|
|
inf "Successfully generated a new ssh key pair."
|
|
else
|
|
err "Failed to generate a new ssh key pair."
|
|
err "If the error persists, please contact the support at $SCRIPT_SUPPORT."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# check first if ssh key is already present on the remote end
|
|
if ! inf_follow "Checking if ssh key is already present on $SSH_HOST..." "\e[1;32mYes\e[0m" "\e[1;31mNo\e[0m" run_command ssh -oStrictHostKeyChecking=no -oBatchMode=yes -i "$SSH_KEY_FILE" -p "$SSH_PORT" -l "$SSH_USER" "$SSH_HOST" exit; then
|
|
wrn "Please enter the password for the ssh key to copy it to the backup server."
|
|
wrn "This is required to enable passwordless ssh login for backups."
|
|
|
|
if ! inf_follow "Copying ssh key to $SSH_HOST..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" run_command ssh-copy-id -oStrictHostKeyChecking=no -oBatchMode=no -i "$SSH_KEY_FILE.pub" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST"; then
|
|
err "Failed to copy ssh key to $SSH_HOST."
|
|
err "Please validate your ssh password and and host settings and try again."
|
|
err "If the error persists, please contact the support at $SCRIPT_SUPPORT."
|
|
exit 1
|
|
else
|
|
if ! inf_follow "Checking if ssh key is working..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" run_command ssh -i "$SSH_KEY_FILE" -p "$SSH_PORT" -oStrictHostKeyChecking=no -oBatchMode=yes -l "$SSH_USER" "$SSH_HOST" exit; then
|
|
err "Failed to connect to $SSH_HOST using the ssh key."
|
|
err "Please validate you have the correct ssh key and host settings."
|
|
err "If the error persists, please contact the support at $SCRIPT_SUPPORT."
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if ! inf_follow "Checking if remote end has borg installed..." "\e[1;32mYes\e[0m" "\e[1;31mNo\e[0m" run_command ssh -i "$SSH_KEY_FILE" -p "$SSH_PORT" -oStrictHostKeyChecking=no -oBatchMode=yes -l "$SSH_USER" "$SSH_HOST" borg --version; then
|
|
if inf_follow "Checking if remote end is a Synology NAS..." "\e[1;32mYes\e[0m" "\e[1;31mNo\e[0m" is_remote_synology; then
|
|
IS_REMOTE_SYNOLGY=1
|
|
fi
|
|
|
|
if [ $IS_REMOTE_SYNOLGY -eq 1 ]; then
|
|
wrn "The remote end appears to be a Synology NAS."
|
|
wrn "Please make sure you have installed borg on the remote end."
|
|
wrn "To install borg on a Synology NAS you can use the Synology Package Center:"
|
|
wrn "- Open the Package Center"
|
|
wrn "- Go to Settings > Package Sources"
|
|
wrn "- Add a package source named 'Community' with the Location http://packages.synocommunity.com"
|
|
wrn "- Go to the Community section and search for 'Borg'"
|
|
wrn "- Install the 'Borg' package"
|
|
wrn ""
|
|
wrn "You also need to enable user homes in the Synology Control Panel:"
|
|
wrn "- Open the Control Panel"
|
|
wrn "- Go to User & Group > Advanced"
|
|
wrn "- At the bottom, check 'Enable user home service'"
|
|
wrn "- Apply the changes"
|
|
wrn ""
|
|
wrn "PLEASE DO NOT CONTINUE UNTIL YOU HAVE INSTALLED BORG ON THE REMOTE END."
|
|
wrn ""
|
|
wrnw "Press any key to continue..."
|
|
|
|
if ! inf_follow "Checking if remote end has borg installed..." "\e[1;32mYes\e[0m" "\e[1;31mNo\e[0m" run_command ssh -i "$SSH_KEY_FILE" -p "$SSH_PORT" -oStrictHostKeyChecking=no -oBatchMode=yes -l "$SSH_USER" "$SSH_HOST" borg --version; then
|
|
if inf_follow "Checking if borg was installed at /usr/local/bin..." "\e[1;32mYes\e[0m" "\e[1;31mNo\e[0m" run_command ssh -i "$SSH_KEY_FILE" -p "$SSH_PORT" -oStrictHostKeyChecking=no -oBatchMode=yes -l "$SSH_USER" "$SSH_HOST" test -f /usr/local/bin/borg; then
|
|
if inf_follow "Creating symlink to /bin/borg..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" run_command ssh -i "$SSH_KEY_FILE" -p "$SSH_PORT" -oStrictHostKeyChecking=no -oBatchMode=yes -l "$SSH_USER" "$SSH_HOST" ln -s /usr/local/bin/borg /bin/borg; then
|
|
inf "Successfully created symlink to /bin/borg."
|
|
else
|
|
err "Failed to create symlink to /bin/borg."
|
|
err "Please login to the remote end via ssh at $SSH_USER@$SSH_HOST and create the symlink manually."
|
|
err "The required command is: sudo ln -s /usr/local/bin/borg /bin/borg"
|
|
err "Please to not continue until you have created the symlink."
|
|
err ""
|
|
errw "Press any key to continue..."
|
|
fi
|
|
else
|
|
err "Borg could not be found on the remote end at /usr/local/bin (which is the default installation path for Synology NAS)."
|
|
err "Please make sure you have installed borg on the remote end."
|
|
exit 1
|
|
fi
|
|
fi
|
|
else
|
|
if [ $AUTO -eq 1 ] || wrnp "The remote end does not have borg installed. Do you want to install it now? [y/N]"; then
|
|
if ! inf_follow "Updating package repositories on remote end" "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" run_command ssh -i "$SSH_KEY_FILE" -p "$SSH_PORT" -oStrictHostKeyChecking=no -oBatchMode=yes -l "$SSH_USER" "$SSH_HOST" apt-get update; then
|
|
err "Failed to update package repositories on remote end."
|
|
err "Please make sure you have the required access rights."
|
|
exit 1
|
|
fi
|
|
|
|
if ! inf_follow "Installing borg on remote end..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" run_command ssh -i "$SSH_KEY_FILE" -p "$SSH_PORT" -oStrictHostKeyChecking=no -oBatchMode=yes -l "$SSH_USER" "$SSH_HOST" apt-get install -y borgbackup; then
|
|
err "Failed to install borg on remote end."
|
|
err "Please make sure you have the required access rights"
|
|
exit 1
|
|
fi
|
|
else
|
|
err "Please make sure borg is installed on the remote end."
|
|
err "This is required in order to do backups using borgmatic."
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
inf "Setting up borgmatic configuration..."
|
|
|
|
# check if directory path exists
|
|
if ! inf_follow "Checking for borgmatic configuration directory..." "\e[1;32mYes\e[0m" "\e[1;31mNo\e[0m" dir_exists $(dirname $BORGMATIC_CONFIG_FILE); then
|
|
if ! inf_follow "Creating borgmatic configuration directory..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" mkdir -p $(dirname $BORGMATIC_CONFIG_FILE); then
|
|
err "Failed to create borgmatic configuration directory."
|
|
err "If the error persists, please contact the support at $SCRIPT_SUPPORT."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ "${BACKUP_REPO:0:1}" == "/" ]; then
|
|
wrn "Your backup repository path starts with a '/', which is not recommended."
|
|
wrn "It will be removed automatically, but please make sure you have set the correct"
|
|
wrn "path. Leading slashes will cause the config validation to fail."
|
|
BACKUP_REPO=${BACKUP_REPO:1}
|
|
fi
|
|
|
|
# check if borgmatic configuration file already exists and abort if $OVERRIDE_CONFIG is not set
|
|
if [ -f $BORGMATIC_CONFIG_FILE ] && [ $OVERRIDE_CONFIG -ne 1 ]; then
|
|
if [ $SKIP_CONFIG -eq 1 ]; then
|
|
wrn "Skipping borgmatic configuration setup."
|
|
wrn "Please make sure you have set up the configuration file manually."
|
|
else
|
|
err "A borgmatic configuration file already exists at $BORGMATIC_CONFIG_FILE."
|
|
err "Please remove the existing configuration file or use the --override-config option."
|
|
exit 1
|
|
fi
|
|
elif [ -f $BORGMATIC_CONFIG_FILE ] && [ $OVERRIDE_CONFIG -eq 1 ]; then
|
|
wrn "Existing borgmatic configuration file will be overridden!"
|
|
fi
|
|
|
|
if [ $SKIP_CONFIG -eq 0 ]; then
|
|
|
|
cat > $BORGMATIC_CONFIG_FILE <<EOF
|
|
|
|
ssh_command: ssh -i $SSH_KEY_FILE -p $SSH_PORT
|
|
|
|
source_directories:
|
|
$(echo $BACKUP_DIRS | tr ' ' '\n' | sed -e 's/^/- /')
|
|
|
|
repositories:
|
|
- path: ssh://$SSH_USER@$SSH_HOST:$SSH_PORT/$BACKUP_REPO
|
|
label: $BACKUP_LABEL_PREFIX$(hostname | tr '[:lower:]' '[:upper:]')
|
|
|
|
keep_daily: 7
|
|
keep_weekly: 4
|
|
keep_monthly: 12
|
|
keep_yearly: 1
|
|
|
|
checks:
|
|
- name: repository
|
|
- name: archives
|
|
frequency: 4 weeks
|
|
|
|
EOF
|
|
|
|
if [ $? -eq 0 ]; then
|
|
inf "Successfully created borgmatic configuration file."
|
|
else
|
|
err "Failed to create borgmatic configuration file."
|
|
err "If the error persists, please contact the support at $SCRIPT_SUPPORT."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if ! inf_follow "Validating borgmatic configuration..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" run_command /root/.local/pipx/venvs/borgmatic/bin/borgmatic config validate; then
|
|
err "Validation of borgmatic configuration failed."
|
|
err "Please check the configuration file at $BORGMATIC_CONFIG_FILE."
|
|
err "If the error persists, please contact the support at $SCRIPT_SUPPORT."
|
|
exit 1
|
|
fi
|
|
|
|
# check for existing borg repository and abort if $OVERRIDE_REPOSITORY is not set
|
|
if inf_follow "Checking for existing borgmatic repository..." "\e[1;32mYes\e[0m" "\e[1;31mNo\e[0m" run_command /root/.local/pipx/venvs/borgmatic/bin/borgmatic info; then
|
|
if [ $OVERRIDE_REPOSITORY -ne 1 ]; then
|
|
if [ $AUTO -eq 0 ] && ! wrnp "A borgmatic repository already exists. Do you want to override it? [y/N]"; then
|
|
err "A borgmatic repository already exists."
|
|
err "Please remove the existing repository or use the --override-repository option."
|
|
exit 1
|
|
elif [ $AUTO -eq 1 ]; then
|
|
err "A borgmatic repository already exists."
|
|
err "Please remove the existing repository or use the --override-repository option."
|
|
exit 1
|
|
fi
|
|
else
|
|
wrn "Existing borgmatic repository will be overridden!"
|
|
fi
|
|
else
|
|
inf "No existing borgmatic repository found."
|
|
fi
|
|
|
|
# ask for backup passphrase if none is set and encryption is enabled
|
|
if [ -z "$BACKUP_PASSPHRASE" ] && [ "$BACKUP_ENCRYPTION" != "none" ]; then
|
|
wrn "Please enter the passphrase for the backup encryption."
|
|
wrn "This is required to encrypt your backups."
|
|
|
|
read -r -s -p "Passphrase: " BACKUP_PASSPHRASE
|
|
echo ""
|
|
fi
|
|
|
|
if [ "$BACKUP_ENCRYPTION" == "none" ]; then
|
|
if ! inf_follow "Setting up borgmatic repository without encryption..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" run_command /root/.local/pipx/venvs/borgmatic/bin/borgmatic init --make-parent-dirs --encryption=none; then
|
|
err "Failed to set up borgmatic repository without encryption."
|
|
err "If the error persists, please contact the support at $SCRIPT_SUPPORT."
|
|
exit 1
|
|
fi
|
|
else
|
|
if ! inf_follow "Setting up borgmatic repository with encryption..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" run_command BORG_PASSPHRASE="$BACKUP_PASSPHRASE" /root/.local/pipx/venvs/borgmatic/bin/borgmatic init --make-parent-dirs --encryption="$BACKUP_ENCRYPTION"; then
|
|
err "Failed to set up borgmatic repository with encryption."
|
|
err "If the error persists, please contact the support at $SCRIPT_SUPPORT."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if ! inf_follow "Adding borgmatic cron job..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" add_crontab; then
|
|
err "Failed to add borgmatic cron job."
|
|
err "If the error persists, please contact the support at $SCRIPT_SUPPORT."
|
|
exit 1
|
|
else
|
|
if ! inf_follow "Validating borgmatic cron job..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" check_crontab; then
|
|
err "Validation of borgmatic cron job failed."
|
|
err "Please check if the file exists using 'cat /etc/cron.d/borgmatic'."
|
|
err "If the error persists, please contact the support at $SCRIPT_SUPPORT."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ $FIRST_BACKUP -eq 1 ]; then
|
|
if [ $AUTO -eq 1 ] || infp "Do you want to run the first backup now? [Y/n]"; then
|
|
if ! inf_follow "Running first backup (this may take a while!)..." "\e[1;32mOK\e[0m" "\e[1;31mFAILED\e[0m" run_command_logged /root/.local/pipx/venvs/borgmatic/bin/borgmatic create --verbosity 1 --list --stats; then
|
|
err "Failed to run first backup."
|
|
err "If the error persists, please contact the support at $SCRIPT_SUPPORT."
|
|
exit 1
|
|
else
|
|
inf "Your first backup has been created successfully!"
|
|
fi
|
|
else
|
|
inf "Skipping first backup."
|
|
inf "Setup completed."
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
inf "Setup completed."
|
|
|
|
exit 0 |