11 Commits

Author SHA1 Message Date
Asif Bacchus a5bc4549e9 basic import from generic backup script 2020-05-06 21:29:48 -06:00
Asif Bacchus 04bc748816 add 'all' switch to docker ps 2020-03-06 00:43:04 -07:00
Asif Bacchus a26bb36f17 define containerName once 2019-09-10 17:22:43 -06:00
Asif Bacchus bbbdf38dcc change mailcowConfigFilePath to explicit param 2019-09-10 16:58:30 -06:00
Asif Bacchus 43f1bb0021 update gitignore, add gitattributes 2019-09-10 16:34:53 -06:00
Asif Bacchus b371e18ab0 'containerName' instead of 'rname' for consistency 2019-09-10 15:23:45 -06:00
Asif Bacchus fb8091ecce Merge pull request #1 from hockeymikey/master
Ensure non-exactly named services are found
2019-09-09 16:45:55 -06:00
hockeymikey 25c6b407a6 Ensure non-exactly named services are found
My naming scheme for my mailcown was like -mailcow_1_k8bdj9op so the script never found them. This ensures they do.
2019-09-07 12:33:54 -05:00
Asif Bacchus 3ddc2c5d4a quoted path-related vars to prevent issues with spaces in path 2019-01-30 12:27:54 -07:00
Asif Bacchus 82f80d0f83 made explanations clearer regarding remote server and keys 2019-01-30 12:22:02 -07:00
Asif Bacchus 0597127cbf clear up confusion between ssh and borg key passwords 2019-01-30 12:19:24 -07:00
11 changed files with 957 additions and 96 deletions
+75
View File
@@ -0,0 +1,75 @@
# Common settings that generally should always be used with your language specific settings
# Auto detect text files and perform LF normalization
# https://www.davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
* text=auto
#
# The above will handle all files NOT found below
#
# Documents
*.bibtex text diff=bibtex
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.md text
*.tex text diff=tex
*.adoc text
*.textile text
*.mustache text
*.csv text
*.tab text
*.tsv text
*.txt text
*.sql text
# Graphics
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.tif binary
*.tiff binary
*.ico binary
# SVG treated as an asset (binary) by default.
*.svg text
# If you want to treat it as binary,
# use the following line instead.
# *.svg binary
*.eps binary
# Scripts
*.bash text eol=lf
*.sh text eol=lf
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
# Serialisation
*.json text
*.toml text
*.xml text
*.yaml text
*.yml text
# Archives
*.7z binary
*.gz binary
*.tar binary
*.zip binary
#
# Exclude files from exporting
#
.gitattributes export-ignore
.gitignore export-ignore
+6 -1
View File
@@ -1,5 +1,10 @@
# ignore all vscode config files
.vscode/
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/numbered-bookmarks.json
# ignore all generated logfiles
*.log
+3
View File
@@ -0,0 +1,3 @@
{
"bookmarks": []
}
+67 -14
View File
@@ -26,18 +26,22 @@ This script automates the following tasks:
- [Why this script must be run as root](#why-this-script-must-be-run-as-root)
- [Script parameters](#script-parameters)
- [Optional parameters](#optional-parameters)
- [Docker container STOP timeout before error: -1 _number_](#docker-container-stop-timeout-before-error--1-_number_)
- [Docker container START timeout before error: -2 _number_](#docker-container-start-timeout-before-error--2-_number_)
- [Path to 503 error page: -5 _/path/to/filename.html_](#path-to-503-error-page--5-_pathtofilenamehtml_)
- [Path to borg details file: -b _/path/to/filename.file_](#path-to-borg-details-file--b-_pathtofilenamefile_)
- [File name of docker-compose configuration file: -d _filename.file_](#file-name-of-docker-compose-configuration-file--d-_filenamefile_)
- [Log file location: -l _/path/to/filename.file_](#log-file-location--l-_pathtofilenamefile_)
- [File name of Mailcow master configuration file: -m _filename.file_](#file-name-of-mailcow-master-configuration-file--m-_filenamefile_)
- [Docker container STOP timeout before error: -1 _number_](#docker-container-stop-timeout-before-error--1-number)
- [Docker container START timeout before error: -2 _number_](#docker-container-start-timeout-before-error--2-number)
- [Path to 503 error page: -5 _/path/to/filename.html_](#path-to-503-error-page--5-pathtofilenamehtml)
- [Path to borg details file: -b _/path/to/filename.file_](#path-to-borg-details-file--b-pathtofilenamefile)
- [File name of docker-compose configuration file: -d _filename.file_](#file-name-of-docker-compose-configuration-file--d-filenamefile)
- [Log file location: -l _/path/to/filename.file_](#log-file-location--l-pathtofilenamefile)
- [File name of Mailcow master configuration file: -m _filename.file_](#file-name-of-mailcow-master-configuration-file--m-filenamefile)
- [Verbose output from borg: -v (no arguments)](#verbose-output-from-borg--v-no-arguments)
- [Path to webroot: -w _/path/to/webroot/_](#path-to-webroot--w-_pathtowebroot_)
- [Path to webroot: -w _/path/to/webroot/_](#path-to-webroot--w-pathtowebroot)
- [Borg details file](#borg-details-file)
- [Protect your borg details file](#protect-your-borg-details-file)
- [borg specific entries (lines 1-4)](#borg-specific-entries-lines-1-4)
- [Line 1: Path to borg base directory](#line-1-path-to-borg-base-directory)
- [Line 2: Path to SSH key for remote server](#line-2-path-to-ssh-key-for-remote-server)
- [Line 3: Connection string to remote repo](#line-3-connection-string-to-remote-repo)
- [Line 4: Password for borg repo/repo key](#line-4-password-for-borg-reporepo-key)
- [additional files/directories to backup](#additional-filesdirectories-to-backup)
- [exclusion patterns](#exclusion-patterns)
- [prune timeframe options](#prune-timeframe-options)
@@ -209,9 +213,9 @@ example entries. The file must have the following information in the following
order:
1. path to borg base directory **(required)**
2. path to ssh private key for repo **(required)**
2. path to ssh private key for remote server **(required)**
3. connection string to remote repo **(required)**
4. password for ssh key/repo **(required)**
4. password for borg repo/repo key **(required)**
5. path to file listing additional files/directories to backup
6. path to file containing borg-specific exclusion patterns
7. prune timeframe options
@@ -234,10 +238,59 @@ chmod 600 mc_borg.details # grant access to root user only (read/write)
If you need help with these options, then you should consult the borg
documentation or search my blog at
[https://mytechiethoughts.com](https://mytechiethoughts.com) for borg. This is
especially true if you want to understand why an SSH key and passphrase are
preferred and why just a passphrase on it's own presents problems automating
borg backups.
[https://mytechiethoughts.com](https://mytechiethoughts.com) for borg. Here's a
very brief overview:
#### Line 1: Path to borg base directory
This is primary directory on your local system where your borg configuration is
located, **NOT* the path to your borg binary. The base directory contains the
borg configuration, cache, security files and keys.
#### Line 2: Path to SSH key for remote server
This is the SSH key used to connect to your remote (backup) server where your
borg repo is located. **This is NOT your borg repo key!**
> Please note: If you are planning on executing this script via cron or some
> other form of automation, it is *highly recommended* that you use an SSH key
> **without** a password! SSH is designed such that passwords cannot simply be
> passed to it via environment variables, etc. so this is something not easily
> automated by a script such as this for security reasons. As such, your
> computer will sit and wait for you to enter the password and will NOT execute
> the actual backup portion of the script until the SSH key password is provided.
>
> If you really want/need to use an SSH key password, you will have to look into
> somethign like GNOME keyring or SSH-agent to provide a secure automated way to
> provide that password to SSH and allow this script to continue.
>
> In practice, SSH keys without passwords are still quite safe since the key
> must still be known in order to connect and most keys are quite long. In
> addition, they key only connects to the remote server, your actual information
> within the borg repository is still encrypted and secured with both a key and
> password.
#### Line 3: Connection string to remote repo
This is the full server and path required to connect to your borg repo on the
remote server. Very often it is the in the form of:
```
user@servername.tld:repo-name/
```
for rsync.net it is in the following form:
```
username@server-number.rsync.net:repo-name/
```
#### Line 4: Password for borg repo/repo key
This is the password needed to access and decrypt your *borg repo*. Assuming
you set up your borg repo using recommended practices, this will actually be the
password for your *borg repo private key*. **This is NOT your SSH key
password!**
### additional files/directories to backup
+26
View File
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>503 - Unavailable: Backup in progress</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
margin: 0 auto;
padding: 0;
width: 85%;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif
}
</style>
</head>
<body>
<h1>Bad timing!</h1>
<p>Seems you're trying to access me during my daily backup window. Don't worry though, I should be up and running again very soon.</p>
<p>My average backup window duration is pretty short and I'm quite busy during that time copying your super-important stuff to my secure hiding place so they stay safe in case anything ever happens to me!</p>
<h3><em>I'm really sorry for the delay. Please try me again soon!</em></h3>
</body>
</html>
+61
View File
@@ -0,0 +1,61 @@
#######
### backup script configuration details
###
### This file contains sensitive information, make sure you have protected
### it by restricting permissions!
### Run the following in the directory where this file is located:
### chown root:root ./backup.details
### chmod 600 ./backup.details
###
### Do NOT include any commands in this file as they WILL be executed!!!
#######
### borg details
# if you're unsure what to enter here, please consult the repo wiki and/or
# the borg documentation
# base configuration directory for borg, all borg parameters use this directory
# as their 'root'. I recommend setups with this being "/var/borgbackup", the
# default is "$HOME" or "~$USER" in that order. If you're unsure, try "$HOME"
borgBaseDir="/var/borgbackup"
# full path to the SSH key used to connect to your remote backup server
borgSSHKey="/var/borgbackup/private.key"
# connection string to access the borg repo on your remote backup server
# this is usually in the form user@servername.tld:repoName/
borgConnectRepo="jdoe123@borg.server.net:mailcow/"
# password to access repo
# this was set when the repo was initialized and, while optional, is HIGHLY
# recommended for security
borgRepoPassphrase="p@ssW0rd"
# keyfile to access repo
# FULL PATH where the associated keyfile for your repo is located -- relevant
# only if your repo requires a keyfile (i.e. 'keyfile' vs 'repokey') and if you
# are not using the default keyfile location
borgKeyfileLocation="/var/borgbackup/.config/borg/keys/server_address__repo_name"
# REQUIRED: path to text file containing a list (one per line) of files/
# directories to include in your backup. Since this is a generic backup script,
# nothing is defined by default. Therefore, ONLY files specified in this file
# will be backed up!
# see repo wiki for more details
borgXtraListPath="/root/scripts/backup/xtraLocations.borg"
# OPTIONAL: path to file containing files/directories or 'patterns' to be
# excluded in a BORG RECOGNIZED format
# see repo wiki for more details or consult borg documentation
# leave blank for no exclusions.
borgExcludeListPath="/root/scripts/backup/excludeLocations.borg"
# parameters to determine how borg deletes aged backups
# more details in the repo wiki and/or borg documentation
# leave blank to skip pruning altogether -- NOT recommended!
borgPruneSettings="--keep-within=14d --keep-daily=30 --keep-weekly=12 --keep-monthly=12"
# location of borg instance on your remote backup server
# this is very often just "borg1"
borgRemote="borg1"
+572
View File
@@ -0,0 +1,572 @@
#!/bin/sh
#######
### mailcow backup using borgbackup
### this assumes three things:
### 1. standard mailcow-dockerized setup as per the docs
### 2. using borg to perform backups to ssh-capable remote server
### 3. remote repo already set-up and configured
#######
### text formatting presents
if command -v tput > /dev/null; then
bold=$(tput bold)
cyan=$(tput setaf 6)
err=$(tput bold)$(tput setaf 1)
magenta=$(tput setaf 5)
norm=$(tput sgr0)
ok=$(tput setaf 2)
warn=$(tput bold)$(tput setaf 3)
width=$(tput cols)
yellow=$(tput setaf 3)
else
bold=""
cyan=""
err=""
magenta=""
norm=""
ok=""
warn=""
width=80
yellow=""
fi
### trap
trap trapExit 1 2 3 6
### functions
# bad configuration value passed in details file
badDetails () {
if [ "$1" = "empty" ]; then
exitError 130 "details:${2} cannot be NULL (undefined)"
elif [ "$1" = "dne" ]; then
exitError 131 "details:${2} file or directory does not exist."
fi
}
# bad parameter passed to script
badParam () {
if [ "$1" = "dne" ]; then
printf "\n%sError: '%s %s'\n" "$err" "$2" "$3"
printf "file or directory does not exist.%s\n\n" "$norm"
exit 1
elif [ "$1" = "empty" ]; then
printf "\n%sError: '%s' cannot have a NULL (empty) value.\n" "$err" "$2"
printf "%sPlease use '--help' for assistance%s\n\n" "$cyan" "$norm"
exit 1
elif [ "$1" = "svc" ]; then
printf "\n%sError: '%s %s': Service does not exist!%s\n\n" \
"$err" "$2" "$3" "$norm"
exit 1
elif [ "$1" = "user" ]; then
printf "\n%sError: '%s %s': User does not exist!%s\n\n" \
"$err" "$2" "$3" "$norm"
exit 1
fi
}
# cleanup
cleanup () {
# cleanup 503 if copied
if [ "$err503Copied" -eq 1 ]; then
if ! rm -f "$webroot/$err503File" 2>>"$logFile"; then
printf "%s[%s] -- [WARNING] Could not remove 503 error page." \
"$warn" "$(stamp)" >> "$logFile"
printf " Web interface will not function until this file is " \
>> "$logFile"
printf "removed --%s\n" "$norm" >> "$logFile"
warnCount=$((warnCount+1))
else
printf "%s[%s] -- [INFO] 503 error page removed --%s\n" \
"$cyan" "$(stamp)" "$norm" >> "$logFile"
fi
fi
}
# call cleanup and then exit with error report
exitError () {
printf "%s[%s] -- [ERROR] %s: %s --%s\n" \
"$err" "$(stamp)" "$1" "$2" "$norm" >> "$logFile"
cleanup
# note script completion with error
printf "%s[%s] --- %s execution completed with error ---%s\n" \
"$err" "$(stamp)" "$scriptName" "$norm" >> "$logFile"
exit "$1"
}
# display script help information
scriptHelp () {
newline
printf "%sUsage: %s [parameters]%s\n\n" "$bold" "$scriptName" "$norm"
textblock "There are NO mandatory parameters. If a parameter is not supplied, its default value will be used. In the case of a switch parameter, it will remain DEactivated if NOT specified."
newline
textblock "Switches are listed then followed by a description of their effect on the following line. Finally, if a default value exists, it will be listed on the next line in (parentheses)."
newline
textblock "${magenta}--- script related parameters ---${norm}"
newline
switchTextblock "-c | --config | --details"
textblock "Path to the configuration key/value-pair file for this script."
defaultsTextblock "(scriptPath/scriptName.details)"
newline
switchTextblock "-h | -? | --help"
textblock "This help screen"
newline
switchTextblock "-l | --log"
textblock "Path to write log file"
defaultsTextblock "(scriptPath/scriptName.log)"
newline
switchTextblock "[SWITCH] -v | --verbose"
textblock "Log borg output with increased verbosity (list all files). Careful! Your log file can get very large very quickly!"
defaultsTextblock "(normal output, option is OFF)"
newline
textblock "${magenta}--- 503 functionality ---${norm}"
newline
switchTextblock "[SWITCH] -5 | --use-503"
textblock "Copy an 'error 503' page/indicator file to your webroot for your webserver to find. Specifying this option will enable other 503 options."
defaultsTextblock "(do NOT copy, option is OFF)"
newline
switchTextblock "--503-path"
textblock "Path to the file you want copied to your webroot as the 'error 503' page."
defaultsTextblock "(scriptPath/503_backup.html)"
newline
switchTextblock "-w | --webroot"
textblock "Path to where the 'error 503' file should be copied."
defaultsTextblock "(/usr/share/nginx/html/)"
newline
textblock "More details and examples of script usage can be found in the repo wiki at ${yellow}https://git.asifbacchus.app/asif/myGitea/wiki${norm}"
newline
}
# generate dynamic timestamps
stamp () {
(date +%F" "%T)
}
textblock() {
printf "%s\n" "$1" | fold -w "$width" -s
}
defaultsTextblock() {
printf "%s%s%s\n" "$yellow" "$1" "$norm"
}
switchTextblock() {
printf "%s%s%s\n" "$cyan" "$1" "$norm"
}
# print a blank line
newline () {
printf "\n"
}
# same as exitError but for signal captures
trapExit () {
printf "%s[%s] -- [ERROR] 99: Caught signal --%s\n" \
"$err" "$(stamp)" "$norm" >> "$logFile"
cleanup
# note script completion with error
printf "%s[%s] --- %s execution was terminated via signal ---%s\n" \
"$err" "$(stamp)" "$scriptName" "$norm" >> "$logFile"
exit 99
}
### end of functions
### default variable values
## script related
# store logfile in the same directory as this script file using the same file
# name as the script but with the extension '.log'
scriptPath="$( CDPATH='' cd -- "$( dirname -- "$0" )" && pwd -P )"
scriptName="$( basename "$0" )"
logFile="$scriptPath/${scriptName%.*}.log"
warnCount=0
configDetails="$scriptPath/${scriptName%.*}.details"
err503Copied=0
exclusions=0
# borg output verbosity -- normal
borgCreateParams='--stats'
borgPruneParams='--list'
# 503 related
use503=0
err503Path="$scriptPath/503_backup.html"
err503File="${err503Path##*/}"
webroot="/usr/share/nginx/html"
### process startup parameters
while [ $# -gt 0 ]; do
case "$1" in
-h|-\?|--help)
# display help
scriptHelp
exit 0
;;
-l|--log)
# set log file location
if [ -n "$2" ]; then
logFile="${2%/}"
shift
else
badParam empty "$@"
fi
;;
-v|--verbose)
# set verbose logging from borg
borgCreateParams='--list --stats'
borgPruneParams='--list'
;;
-c|--config|--details)
# location of config details file
if [ -n "$2" ]; then
if [ -f "$2" ]; then
configDetails="${2%/}"
shift
else
badParam dne "$@"
fi
else
badParam empty "$@"
fi
;;
-5|--use-503)
# enable copying 503 error page to webroot
use503=1
;;
--503-path)
# FULL path to 503 file
if [ -n "$2" ]; then
if [ -f "$2" ]; then
err503Path="${2%/}"
err503File="${2##*/}"
shift
else
badParam dne "$@"
fi
else
badParam empty "$@"
fi
;;
-w|--webroot)
# path to webroot (copy 503)
if [ -n "$2" ]; then
if [ -d "$2" ]; then
webroot="${2%/}"
shift
else
badParam dne "$@"
fi
else
badParam empty "$@"
fi
;;
*)
printf "\n%sUnknown option: %s\n" "$err" "$1"
printf "%sUse '--help' for valid options.%s\n\n" "$cyan" "$norm"
exit 1
;;
esac
shift
done
### check pre-requisites and default values
# check if running as root, otherwise exit
if [ "$( id -u )" -ne 0 ]; then
printf "\n%sERROR: script MUST be run as ROOT%s\n\n" "$err" "$norm"
exit 2
fi
# does the details file exist?
if [ ! -f "$configDetails" ]; then
badParam dne "(--details default)" "$configDetails"
fi
# is borg installed?
if ! command -v borg > /dev/null; then
printf "\n%sERROR: BORG is not installed on this system!%s\n\n" "$err" "$norm"
exit 3
fi
# if 503 functionality is enabled, do 503 related files exist?
if [ "$use503" -eq 1 ]; then
if [ ! -f "$err503Path" ]; then
badParam dne "(--503-path default)" "$err503Path"
elif [ ! -d "$webroot" ]; then
badParam dne "(--webroot default)" "$webroot"
fi
fi
### start logging
printf "%s[%s] --- Start %s execution ---%s\n" \
"$magenta" "$(stamp)" "$scriptName" "$norm" >> "$logFile"
printf "%s[%s] -- [INFO] Log located at %s%s%s --%s\n" \
"$cyan" "$(stamp)" "$yellow" "$logFile" "$cyan" "$norm" >> "$logFile"
### 503 functionality
if [ "$use503" -eq 1 ]; then
printf "%s[%s] -- [INFO] Copying 503 error page to " \
"$cyan" "$(stamp)" >> "$logFile"
printf "webroot -- %s\n" "$norm">> "$logFile"
if ! cp --force "${err503Path}" "${webroot}/${err503File}" 2>> "$logFile"
then
printf "%s[%s] -- [WARNING] Failed to copy 503 error page. " \
"$warn" "$(stamp)" >> "$logFile"
printf "Web users will NOT be notified --%s\n" "$norm" >> "$logFile"
warnCount=$((warnCount+1))
else
printf "%s[%s] -- [SUCCESS] 503 error page copied --%s\n" \
"$ok" "$(stamp)" "$norm" >> "$logFile"
# set cleanup flag
err503Copied=1
fi
fi
### read details file to get variables needed run borg
# check if config details file was provided as a relative or absolute path
case "${configDetails}" in
/*)
# absolute path, no need to rewrite variable
. "${configDetails}"
;;
*)
# relative path, prepend './' to create absolute path
. "./${configDetails}"
;;
esac
printf "%s[%s] -- [INFO] %s%s%s imported --%s\n" \
"$cyan" "$(stamp)" "$yellow" "$configDetails" "$cyan" "$norm" >> "$logFile"
### Run borg variable checks
printf "%s[%s] -- [INFO] Verifying supplied borg details --%s\n" \
"$cyan" "$(stamp)" "$norm" >> "$logFile"
## read additional files -- this is required otherwise nothing to backup!
if [ -z "${borgXtraListPath}" ]; then
badDetails empty 'xtraLocations'
else
# check if file actually exists
if [ ! -f "${borgXtraListPath}" ]; then
badDetails dne 'borgXtraListPath'
fi
# read file contents into concatenated list for echo to cmdline
while read -r xtraItem; do
if [ -z "${xtraList}" ]; then
xtraList="${xtraItem}"
else
xtraList="${xtraList} ${xtraItem}"
fi
done <<EOF
$( sed -e '/^\s*#.*$/d' -e '/^\s*$/d' "${borgXtraListPath}" )
EOF
printf "%sdetails:borgXtraListPath %s-- %s[OK]%s\n" \
"$magenta" "$norm" "$ok" "$norm" >> "$logFile"
fi
## verify borg base directory
if [ -z "${borgBaseDir}" ]; then
badDetails empty 'borgBaseDir'
elif [ ! -d "${borgBaseDir}" ]; then
badDetails dne 'borgBaseDir'
fi
printf "%sdetails:borgBaseDir %s-- %s[OK]%s\n" \
"$magenta" "$norm" "$ok" "$norm" >> "$logFile"
export BORG_BASE_DIR="${borgBaseDir%/}"
## check path to SSH keyfile
if [ -z "${borgSSHKey}" ]; then
badDetails empty 'borgSSHKey'
elif [ ! -f "${borgSSHKey}" ]; then
badDetails dne 'borgSSHKey'
fi
printf "%sdetails:borgSSHKey %s-- %s[OK]%s\n" \
"$magenta" "$norm" "$ok" "$norm" >> "$logFile"
export BORG_RSH="ssh -i ${borgSSHKey}"
## check borg repo connect string
if [ -z "${borgConnectRepo}" ]; then
badDetails empty 'borgConnectRepo'
fi
printf "%sdetails:borgConnectRepo %s-- %s[OK]%s\n" \
"$magenta" "$norm" "$ok" "$norm" >> "$logFile"
export BORG_REPO="${borgConnectRepo}"
## check borg repo password
if [ -n "${borgRepoPassphrase}" ]; then
printf "%sdetails:borgRepoPassphrase %s-- %s[OK]%s\n" \
"$magenta" "$norm" "$ok" "$norm" >> "$logFile"
export BORG_PASSPHRASE="${borgRepoPassphrase}"
else
# if passwd is blank intentionally, this is insecure
printf "%s-- [WARNING] Using a borg repo without a password is an " \
"$warn" >> "$logFile"
printf "insecure configuration --%s\n" "$norm">> "$logFile"
warnCount=$((warnCount+1))
# if this was an accident, we need to provide a bogus passwd so borg fails
# otherwise it will sit forever just waiting for input
export BORG_PASSPHRASE="DummyPasswordSoBorgFails"
fi
## check borg repository keyfile location
if [ -z "${borgKeyfileLocation}" ]; then
printf "%sdetails:borgKeyfileLocation %s-- %s[DEFAULT]%s\n" "$magenta" "$norm" "$ok" "$norm" >> "$logFile"
else
# check if keyfile location exists
if [ ! -f "${borgKeyfileLocation}" ]; then
badDetails dne 'borgKeyfileLocation'
fi
printf "%sdetails:borgKeyfileLocation %s-- %s[OK]%s\n" "$magenta" "$norm" "$ok" "$norm" >> "$logFile"
export BORG_KEY_FILE="${borgKeyfileLocation}"
fi
## export borg remote path, if specified
if [ -n "${borgRemote}" ]; then export BORG_REMOTE_PATH="${borgRemote}"; fi
## check if exlusion list file is specified
if [ -n "${borgExcludeListPath}" ]; then
# check if the file actually exists
if [ ! -f "${borgExcludeListPath}" ]; then
badDetails dne 'borgExcludeListPath'
fi
exclusions=1
fi
### create borg temp dir:
## python requires a writable temporary directory when unpacking borg and
## executing commands. This defaults to /tmp but many systems mount /tmp with
## the 'noexec' option for security. Thus, we will use/create a 'tmp' folder
## within the BORG_BASE_DIR and instruct python to use that instead of /tmp
# check if BORG_BASE_DIR/tmp exists, if not, create it
if [ ! -d "${borgBaseDir}/tmp" ]; then
if ! mkdir "${borgBaseDir}/tmp"; then
exitError 132 "Unable to create borg ${borgBaseDir}/tmp directory"
else
printf "%s[%s] -- [INFO] Created %s%s/tmp " \
"$cyan" "$(stamp)" "$yellow" "${borgBaseDir}" >> "$logFile"
printf "%s--%s\n" "$cyan" "$norm">> "$logFile"
fi
fi
export TMPDIR="${borgBaseDir}/tmp"
### execute borg depending on whether exclusions are defined
## construct the proper borg commandline
# base command
if [ "$exclusions" -eq 0 ]; then
borgCMD="borg --show-rc create ${borgCreateParams} \
::$(date +%Y-%m-%d_%H%M%S) \
${xtraList}"
elif [ "$exclusions" -eq 1 ]; then
borgCMD="borg --show-rc create ${borgCreateParams} \
--exclude-from ${borgExcludeListPath} \
::$(date +%Y-%m-%d_%H%M%S) \
${xtraList}"
fi
# execute borg
printf "%s[%s] -- [INFO] Executing borg backup operation --%s\n" \
"$cyan" "$(stamp)" "$norm" >> "$logFile"
${borgCMD} 2>> "$logFile"
borgResult="$?"
## check borg exit status
if [ "$borgResult" -eq 0 ]; then
printf "%s[%s] -- [SUCCESS] Borg backup completed --%s\n" \
"$ok" "$(stamp)" "$norm" >> "$logFile"
elif [ "$borgResult" -eq 1 ]; then
printf "%s[%s] -- [WARNING] Borg completed with warnings. " \
"$warn" "$(stamp)" >> "$logFile"
printf "Review this logfile for details --%s\n" "$norm">> "$logFile"
warnCount=$((warnCount+1))
elif [ "$borgResult" -ge 2 ]; then
err_1="Borg exited with a critical error. Please review this log file"
err_2="for details."
exitError 138 "$err_1 $err_2"
else
printf "%s[%s] -- [WARNING] Borg exited with unknown return code. " \
"$warn" "$(stamp)" >> "$logFile"
printf "Review this logfile for details --%s\n" "$norm">> "$logFile"
warnCount=$((warnCount+1))
fi
### execute borg prune if paramters are provided, otherwise skip with a warning
if [ -n "${borgPruneSettings}" ]; then
printf "%s[%s] -- [INFO] Executing borg prune operation --%s\n" \
"$cyan" "$(stamp)" "$norm" >> "$logFile"
borg prune --show-rc -v ${borgPruneParams} ${borgPruneSettings} \
2>> "$logFile"
borgPruneResult="$?"
else
printf "%s[%s] -- [WARNING] No prune parameters provided. " \
"$warn" "$(stamp)" >> "$logFile"
printf "Your archive will continue growing with each backup --%s\n" \
"$norm" >> "$logFile"
warnCount=$((warnCount+1))
fi
## report on prune operation if executed
if [ -n "${borgPruneResult}" ]; then
if [ "${borgPruneResult}" -eq 0 ]; then
printf "%s[%s] -- [SUCCESS] Borg prune completed --%s\n" \
"$ok" "$(stamp)" "$norm" >> "$logFile"
elif [ "$borgPruneResult" -eq 1 ]; then
printf "%s[%s] -- [WARNING] Borg prune completed with warnings. " \
"$warn" "$(stamp)" >> "$logFile"
printf "Review this logfile for details --%s\n" "$norm" >> "$logFile"
warnCount=$((warnCount+1))
elif [ "$borgPruneResult" -ge 2 ]; then
err_1="Borg prune exited with a critical error. Please review this"
err_2="log file for details."
exitError 139 "$err_1 $err_2"
else
printf "%s[%s] -- [WARNING] Borg prune exited with an unknown " \
"$warn" "$(stamp)" >> "$logFile"
printf "return code. Review this logfile for details --%s\n" \
"$norm" >> "$logFile"
warnCount=$((warnCount+1))
fi
fi
### all processes successfully completed, cleanup and exit gracefully
# note successful completion of borg commands
printf "%s[%s] -- [SUCCESS] Backup operations completed --%s\n" \
"$ok" "$(stamp)" "$norm" >> "$logFile"
# cleanup
cleanup
# note complete success, tally warnings and exit
printf "%s[%s] -- [SUCCESS] All processes completed --%s\n" \
"$ok" "$(stamp)" "$norm" >> "$logFile"
printf "%s[%s] --- %s execution completed ---%s\n" \
"$magenta" "$(stamp)" "$scriptName" "$norm" >> "$logFile"
if [ "$warnCount" -gt 0 ]; then
printf "%s%s warnings issued!%s\n" "$warn" "${warnCount}" "$norm" >> "$logFile"
else
printf "%s0 warnings issued.%s\n" "$ok" "$norm" >> "$logFile"
fi
exit 0
### error codes
# 1: parameter error
# 2: not run as root
# 3: borg not installed
# 99: TERM signal trapped
# 130: null configuration variable in details file
# 131: invalid configuration variable in details file
# 138: borg exited with a critical error
# 139: borg prune exited with a critical error
+2
View File
@@ -0,0 +1,2 @@
EOF
+57
View File
@@ -0,0 +1,57 @@
# Files and directories listed here will be included in your borg backup
#
# Good candidates for inclusion would be things like your mailcow configuration
# files, customized docker-compose overrides, your SSL certificates, etc.
#
# List the path to files/directories one per line.
# Any blank lines will be ignored.
# Any lines starting with '#' will be ignored as a comment.
# For consistency, you should include the trailing slash for directories.
# these examples are for a very basic Debian machine hosting mailcow
### important system configuration files
# basic configuration
/etc/fstab
/etc/network/interfaces
/etc/network/interfaces.d/
/etc/systemd/timesyncd.conf
# ssh configuration and host keys
/etc/ssh/
# apt configuration
/etc/apt/sources.list
/etc/apt/sources.list.d/
/etc/apt/listchanges.conf
/etc/apt/apt.conf.d/50unattended-upgrades
/etc/apt/apt.conf.d/20auto-upgrades
# user profile defaults and configurations
/etc/profile
/etc/bash.bashrc
/etc/skel/
/etc/nanorc
# selected root user files
/root/.bashrc
/root/.ssh/
# scripts
/scripts/
### important programs and configurations
# name of program for reference
# include the paths to important configuration files/directories and/or
# data directories
# NGINX (example)
/etc/nginx/
/usr/share/nginx/html/
# LetsEncrypt (example)
/etc/letsencrypt/
Regular → Executable
+51 -44
View File
@@ -33,10 +33,15 @@ function scriptHelp {
echo -e "\tMailcow to operational status."
echo -e "\nThe readme file included in this script's git contains detailed"
echo -e "usage information. The following is a brief summary:\n"
echo -e "${bold}***This script scans for your mailcow configuration file"
echo -e "either with the default name or with your provided filename. If"
echo -e "multiple files with this name are on your system, the script"
echo -e "WILL get confused and exit with errors***${normal}${default}"
echo -e "${bold}***You MUST provide the full path to your mailcow"
echo -e "configuration file using the '-m' parameter***${normal}${default}"
echo -e "${bold}${note}\nMandatory parameters:${normal}${default}"
echo -e "${lit}\n-m, File name of the Mailcow build configuration file${default}"
echo -e "FULL PATH to your Mailcow master build configuration file containing"
echo -e "all variables and configuration info unique to your Mailcow setup."
echo -e "The path specified here is also used for all docker-related"
echo -e "operations in this script."
echo -e "${info}Default: <none>${default}"
echo -e "${bold}${note}\nOptional parameters:${normal}${default}"
echo -e "${lit}\n-1, Timeout for containers to STOP before error${default}"
echo -e "The number of seconds to wait for a docker container to STOP"
@@ -63,20 +68,12 @@ function scriptHelp {
echo -e "${lit}\n-d, File name of the docker-compose configuration file${default}"
echo -e "Name of the docker-compose configuration file that Mailcow uses"
echo -e "to build/start/stop all containers. This will only be searched"
echo -e "for in the path found to contain your mailcow configuration file."
echo -e "for in the path provided for your mailcow configuration file."
echo -e "${info}Default: docker-compose.yml${default}"
echo -e "${lit}\n-l, Location to save log file${default}"
echo -e "This script writes a detailed log file of all activities. It is"
echo -e "structured in an way easy for log parsers (like Logwatch) to read."
echo -e "${info}Default: ScriptPath/ScriptName.log${default}"
echo -e "${lit}\n-m, File name of the Mailcow build configuration file${default}"
echo -e "Name of the Mailcow master build configuration file that has all"
echo -e "variables and configuration info unique to your Mailcow setup."
echo -e "This script will search for any file matching what you specify"
echo -e "so please ensure you don't have multiple files laying around with"
echo -e "the same name! The path where this file is found is used for all"
echo -e "docker-based operations in this script."
echo -e "${info}Default: mailcow.conf${default}"
echo -e "${lit}\n-v, Verbose output from borgbackup${default}"
echo -e "By default, this script will only log summary data from borg."
echo -e "If you need/want more detailed information, the verbose setting"
@@ -228,6 +225,8 @@ function cleanup {
### operate docker containers
function operateDocker {
containerName="$(docker ps -a --format '{{ .Names }}' --filter name=${COMPOSE_PROJECT_NAME}_${2}-mailcow_1)"
# determine action to take
if [ "$1" = "stop" ]; then
echo -e "${op}[$(stamp)] Stopping ${2}-mailcow container...${normal}" \
@@ -235,18 +234,15 @@ if [ "$1" = "stop" ]; then
docker-compose stop --timeout ${dockerStopTimeout} ${2}-mailcow \
2>> "$logFile"
# verify container stopped (should return true)
dockerResultState=$(docker inspect -f '{{ .State.Running }}' \
${COMPOSE_PROJECT_NAME}_${2}-mailcow_1)
dockerResultState="$(docker inspect -f '{{ .State.Running }}' $containerName)"
# verify clean stop (exit code 0)
dockerResultExit=$(docker inspect -f '{{ .State.ExitCode }}' \
${COMPOSE_PROJECT_NAME}_${2}-mailcow_1)
dockerResultExit="$(docker inspect -f '{{ .State.ExitCode }}' $containerName)"
elif [ "$1" = "start" ]; then
echo -e "${op}[$(stamp)] Starting ${2}-mailcow container...${normal}" \
>> "$logFile"
docker-compose start ${2}-mailcow 2>> "$logFile"
# verify
dockerResultState=$(docker inspect -f '{{ .State.Running }}' \
${COMPOSE_PROJECT_NAME}_${2}-mailcow_1)
dockerResultState="$(docker inspect -f '{{ .State.Running }}' $containerName)"
fi
}
@@ -261,9 +257,6 @@ scriptPath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
scriptName="$( basename ${0} )"
logFile="$scriptPath/${scriptName%.*}.log"
# Set default mailcow configuration filename
mailcowConfigFile=mailcow.conf
# Set default docker-compose filename
dockerComposeFile=docker-compose.yml
@@ -384,8 +377,8 @@ while getopts ':l:v5:w:b:m:d:1:2:' PARAMS; do
borgDetails="${OPTARG%/}"
;;
m)
# name of mailcow configuration file
mailcowConfigFile="${OPTARG}"
# full path to mailcow.conf configuration file
mailcowConfigFilePath="${OPTARG%/}"
;;
d)
# name of docker-compose configuration file
@@ -415,20 +408,33 @@ if [ $(id -u) -ne 0 ]; then
exit 3
fi
## Find mailcow configuration file so additional variables can be read
mailcowConfigFilePath=$( find / -mount -name "$mailcowConfigFile" -print )
## verify mailcow.conf location provided
if [ -z "$mailcowConfigFilePath" ]; then
echo -e "\n${err}Could not locate the specified mailcow configuration" \
"file: ${lit}${mailcowConfigFile}${normal}"
echo -e "\n${err}You MUST provide the full path to your mailcow.conf" \
"configuration file. Exiting.${normal}"
exit 1
fi
## Find docker-compose file using mailcow configuration file path as a reference
mailcowPath="${mailcowConfigFilePath%/$mailcowConfigFile*}"
dockerComposeFilePath="$mailcowPath/$dockerComposeFile"
checkExist ff "$dockerComposeFilePath"
checkResult="$?"
if [ "$checkResult" = 1 ]; then
## verify mailcow.conf and extract path
if checkExist ff "$mailcowConfigFilePath"; then
# extract directory name
case $mailcowConfigFilePath in
*/*)
mailcowPath=${mailcowConfigFilePath%/*}
;;
*)
mailcowPath="."
;;
esac
else
echo -e "\n${err}Could not locate the specified mailcow configuration" \
"file: ${lit}${mailcowConfigFilePath}${normal}"
exit 1
fi
# verify docker-compose.yml exists at location of mailcow.conf (standard setup)
dockerComposeFilePath="${mailcowPath}/${dockerComposeFile}"
if ! checkExist ff "$dockerComposeFilePath"; then
echo -e "\n${err}Could not locate docker-compose configuration file:" \
"${lit}${dockerComposeFilePath}${normal}"
exit 1
@@ -453,8 +459,9 @@ echo -e "${info}[$(stamp)] -- [INFO] using ${lit}${mailcowConfigFilePath}" \
echo -e "${info}[$(stamp)] -- [INFO] using ${lit}${dockerComposeFilePath}" \
>> "$logFile"
### Import additional variables from mailcow configuration file
source "${mailcowConfigFilePath}"
source "$mailcowConfigFilePath"
### Export PATH so this script can access all docker and docker-compose commands
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
@@ -797,21 +804,21 @@ if [ -z "$borgExclude" ]; then
echo -e "${bold}${op}[$(stamp)] Executing borg without exclusions${normal}" \
>> "$logFile"
borg --show-rc create ${borgCreateParams} ::`date +%Y-%m-%d_%H%M%S` \
${xtraFiles[@]} \
${sqlDumpDir} \
${dockerVolumeMail} ${dockerVolumeRspamd} ${dockerVolumePostfix} \
${dockerVolumeRedis} ${dockerVolumeCrypt} \
"${xtraFiles[@]}" \
"${sqlDumpDir}" \
"${dockerVolumeMail}" "${dockerVolumeRspamd}" "${dockerVolumePostfix}" \
"${dockerVolumeRedis}" "${dockerVolumeCrypt}" \
2>> "$logFile"
else
# borgExclude is not empty
echo -e "${bold}${op}[$(stamp)] Executing borg with exclusions${normal}" \
>> "$logFile"
borg --show-rc create ${borgCreateParams} --exclude-from ${borgExclude} \
borg --show-rc create ${borgCreateParams} --exclude-from "${borgExclude}" \
::`date +%Y-%m-%d_%H%M%S` \
${xtraFiles[@]} \
${sqlDumpDir} \
${dockerVolumeMail} ${dockerVolumeRspamd} ${dockerVolumePostfix} \
${dockerVolumeRedis} ${dockerVolumeCrypt} \
"${xtraFiles[@]}" \
"${sqlDumpDir}" \
"${dockerVolumeMail}" "${dockerVolumeRspamd}" "${dockerVolumePostfix}" \
"${dockerVolumeRedis}" "${dockerVolumeCrypt}" \
2>> "$logFile"
fi
+3 -3
View File
@@ -1,7 +1,7 @@
<path to borgbackup base directory> /var/borgbackup
<path to SSH private key for repo> /var/borgbackup/sshPrivate.key
<connection string to remote repo> user@server-number.rsync.net:repoName/
<password for SSH key/repo> pAsSwOrd
<path to SSH private key for remote server> /var/borgbackup/sshPrivate.key
<connection string to remote repo> user@servername.tld:repoName/
<password for repo> pAsSwOrd
<path to file listing extra files> /root/scripts/xtraLocations.borg
<path to file with exclusions> /root/scripts/excludeLocations.borg
<purge timeframe options> --keep-within=7d --keep-daily=30 --keep-weekly=12 --keep-monthly=-1