9 Commits

Author SHA1 Message Date
asif 46b29dfce9 fix(script): fix dockerCmd quoting error 2022-07-15 13:49:59 -06:00
asif 47adaceed0 feature(version): add version display to script 2022-07-12 15:07:36 -06:00
asif 475a0d0dd2 refactor(compose): detect proper compose cmd 2022-07-12 14:55:43 -06:00
asif 818483f92e refactor(vars): quote dockerCmd calls 2022-07-12 14:54:17 -06:00
asif c8b328e707 fix(typos): fix various typos 2022-07-12 12:52:37 -06:00
asif fb4dcab29a chore(shellcheck): ignore some warnings 2022-07-12 12:50:42 -06:00
asif 5382dd26d9 fix(cleanup): prevent doDocker error loop
- if doDocker throws an error, cleanup is called which again calls doDocker
- resolve this loop by passing a variable from cleanup
2022-07-12 12:47:52 -06:00
asif bd36035a9f refactor(compose): update container name lookup
- filter by both compose v1 and v2 naming conventions
2022-07-12 12:34:26 -06:00
asif 389f5ef059 feature(compose): detect and use proper compose cmd
- check for compose v2 and use 'docker compose'
- if v2 not detected, fallback to 'docker-compose'
2022-07-12 12:31:24 -06:00
3 changed files with 217 additions and 176 deletions
+8
View File
@@ -9,6 +9,14 @@
</inspection_tool> </inspection_tool>
</profile> </profile>
</component> </component>
<component name="GitSharedSettings">
<option name="FORCE_PUSH_PROHIBITED_PATTERNS">
<list>
<option value="master" />
<option value="main" />
</list>
</option>
</component>
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="$PROJECT_DIR$" vcs="Git" />
</component> </component>
+31 -18
View File
@@ -70,6 +70,7 @@ badParam() {
cleanup() { cleanup() {
# cleanup 503 if copied # cleanup 503 if copied
if [ "$err503Copied" -eq 1 ]; then if [ "$err503Copied" -eq 1 ]; then
# shellcheck disable=SC2129
if ! rm -f "$webroot/$err503File" 2>>"$logFile"; then if ! rm -f "$webroot/$err503File" 2>>"$logFile"; then
printf "%s[%s] -- [WARNING] Could not remove 503 error page." \ printf "%s[%s] -- [WARNING] Could not remove 503 error page." \
"$warn" "$(stamp)" >>"$logFile" "$warn" "$(stamp)" >>"$logFile"
@@ -98,34 +99,34 @@ cleanup() {
printf "%s[%s] -- [INFO] POSTFIX container is running --%s\n" \ printf "%s[%s] -- [INFO] POSTFIX container is running --%s\n" \
"$cyan" "$(stamp)" "$norm" >>"$logFile" "$cyan" "$(stamp)" "$norm" >>"$logFile"
else else
exitError 102 'Could not start POSTFIX container.' exitError 102 'Could not start POSTFIX container.' 'final'
fi fi
doDocker start dovecot doDocker start dovecot
if [ "$dockerResultState" = "true" ]; then if [ "$dockerResultState" = "true" ]; then
printf "%s[%s] -- [INFO] DOVECOT container is running --%s\n" \ printf "%s[%s] -- [INFO] DOVECOT container is running --%s\n" \
"$cyan" "$(stamp)" "$norm" >>"$logFile" "$cyan" "$(stamp)" "$norm" >>"$logFile"
else else
exitError 102 'Could not start DOVECOT container.' exitError 102 'Could not start DOVECOT container.' 'final'
fi fi
} }
doDocker() { doDocker() {
containerName="$(docker ps -a --format '{{ .Names }}' --filter name=${COMPOSE_PROJECT_NAME}_${2}-mailcow_1)" containerName="$(docker ps -a --format '{{ .Names }}' --filter "name=${COMPOSE_PROJECT_NAME}_${2}-mailcow_1" --filter "name=${COMPOSE_PROJECT_NAME}-${2}-mailcow-1")"
# determine action to take # determine action to take
if [ "$1" = "stop" ]; then if [ "$1" = "stop" ]; then
printf "%s[%s] -- [INFO] Stopping %s-mailcow container --%s\n" \ printf "%s[%s] -- [INFO] Stopping %s-mailcow container --%s\n" \
"$cyan" "$(stamp)" "$2" "$norm" >>"$logFile" "$cyan" "$(stamp)" "$2" "$norm" >>"$logFile"
docker-compose -f "$mcDockerCompose" stop --timeout "$dockerStopTimeout" "$2-mailcow" 2>>"$logFile" ${dockerCmd} -f "$mcDockerCompose" stop --timeout "$dockerStopTimeout" "$2-mailcow" 2>>"$logFile"
# set result vars # set result vars
dockerResultState="$(docker inspect -f '{{ .State.Running }}' $containerName)" dockerResultState="$(docker inspect -f '{{ .State.Running }}' "$containerName")"
dockerResultExit="$(docker inspect -f '{{ .State.ExitCode }}' $containerName)" dockerResultExit="$(docker inspect -f '{{ .State.ExitCode }}' "$containerName")"
elif [ "$1" = "start" ]; then elif [ "$1" = "start" ]; then
printf "%s[%s] -- [INFO] Starting %s-mailcow container --%s\n" \ printf "%s[%s] -- [INFO] Starting %s-mailcow container --%s\n" \
"$cyan" "$(stamp)" "$2" "$norm" >>"$logFile" "$cyan" "$(stamp)" "$2" "$norm" >>"$logFile"
docker-compose -f "$mcDockerCompose" start "$2-mailcow" 2>>"$logFile" ${dockerCmd} -f "$mcDockerCompose" start "$2-mailcow" 2>>"$logFile"
# set result vars # set result vars
dockerResultState="$(docker inspect -f '{{ .State.Running }}' $containerName)" dockerResultState="$(docker inspect -f '{{ .State.Running }}' "$containerName")"
fi fi
} }
@@ -133,7 +134,7 @@ doDocker() {
exitError() { exitError() {
printf "%s[%s] -- [ERROR] %s: %s --%s\n" \ printf "%s[%s] -- [ERROR] %s: %s --%s\n" \
"$err" "$(stamp)" "$1" "$2" "$norm" >>"$logFile" "$err" "$(stamp)" "$1" "$2" "$norm" >>"$logFile"
cleanup if [ ! "$3" = "final" ]; then cleanup; fi
# note script completion with error # note script completion with error
printf "%s[%s] --- %s execution completed with error ---%s\n" \ printf "%s[%s] --- %s execution completed with error ---%s\n" \
"$err" "$(stamp)" "$scriptName" "$norm" >>"$logFile" "$err" "$(stamp)" "$scriptName" "$norm" >>"$logFile"
@@ -144,7 +145,8 @@ exitError() {
scriptHelp() { scriptHelp() {
newline newline
printf "%sUsage: %s [parameters]%s\n\n" "$bold" "$scriptName" "$norm" 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." 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 newline
textblock "Parameters 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)." textblock "Parameters 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 newline
@@ -194,7 +196,7 @@ scriptHelp() {
textblock "${magenta}--- mailcow related ---${norm}" textblock "${magenta}--- mailcow related ---${norm}"
newline newline
switchTextblock "-d | --docker-compose" switchTextblock "-d | --docker-compose"
textblock "Path to mailcow's 'docker-compose.yml' file." textblock "Path to the mailcow 'docker-compose.yml' file."
defaultsTextblock "(/opt/mailcow-dockerized/docker-compose.yml)" defaultsTextblock "(/opt/mailcow-dockerized/docker-compose.yml)"
newline newline
switchTextblock "-m | --mailcow-config" switchTextblock "-m | --mailcow-config"
@@ -263,6 +265,7 @@ configDetails="$scriptPath/${scriptName%.*}.details"
err503Copied=0 err503Copied=0
sqlDumpDirCreated=0 sqlDumpDirCreated=0
exclusions=0 exclusions=0
dockerCmd="docker compose"
borgPath="/usr/bin/borg" borgPath="/usr/bin/borg"
# borg output verbosity -- normal # borg output verbosity -- normal
borgCreateParams='--stats' borgCreateParams='--stats'
@@ -430,6 +433,14 @@ if [ "$(id -u)" -ne 0 ]; then
printf "\n%sERROR: script MUST be run as ROOT%s\n\n" "$err" "$norm" printf "\n%sERROR: script MUST be run as ROOT%s\n\n" "$err" "$norm"
exit 2 exit 2
fi fi
# verify compose installed and set proper commands based on version
if ! (docker compose version >/dev/null 2>&1); then
if ! (docker-compose --version >/dev/null 2>&1); then
printf "\n%sERROR: Docker Compose not installed or not functioning%s\n\n" "$err" "$norm"
exit 3
fi
dockerCmd="docker-compose"
fi
# does the details file exist? # does the details file exist?
if [ ! -f "$configDetails" ]; then if [ ! -f "$configDetails" ]; then
badParam dne "(--details default)" "$configDetails" badParam dne "(--details default)" "$configDetails"
@@ -437,7 +448,7 @@ fi
# is borg installed? # is borg installed?
if ! find "$borgPath" -type f -executable >/dev/null 2>&1; then if ! find "$borgPath" -type f -executable >/dev/null 2>&1; then
printf "\n%sERROR: BORG cannot be found in the specified or default location on this system!%s\n\n" "$err" "$norm" printf "\n%sERROR: BORG cannot be found in the specified or default location on this system!%s\n\n" "$err" "$norm"
exit 3 exit 4
fi fi
# if 503 functionality is enabled, do 503 related files exist? # if 503 functionality is enabled, do 503 related files exist?
if [ "$use503" -eq 1 ]; then if [ "$use503" -eq 1 ]; then
@@ -689,9 +700,9 @@ fi
### dump SQL ### dump SQL
printf "%s[%s] -- [INFO] Dumping mailcow SQL database --%s\n" \ printf "%s[%s] -- [INFO] Dumping mailcow SQL database --%s\n" \
"$cyan" "$(stamp)" "$norm" >>"$logFile" "$cyan" "$(stamp)" "$norm" >>"$logFile"
docker-compose exec -T mysql-mailcow mysqldump --default-character-set=utf8mb4 \ ${dockerCmd} exec -T mysql-mailcow mysqldump --default-character-set=utf8mb4 \
-u${DBUSER} -p${DBPASS} ${DBNAME} >"$sqlDumpDir/$sqlDumpFile" 2>>"$logFile" -u${DBUSER} -p${DBPASS} ${DBNAME} >"$sqlDumpDir/$sqlDumpFile" 2>>"$logFile"
dumpResult=$(docker-compose exec -T mysql-mailcow echo "$?") dumpResult=$(${dockerCmd} exec -T mysql-mailcow echo "$?")
if [ "$dumpResult" -eq 0 ]; then if [ "$dumpResult" -eq 0 ]; then
printf "%s[%s] -- [INFO] SQL database dumped successfully --%s\n" \ printf "%s[%s] -- [INFO] SQL database dumped successfully --%s\n" \
"$cyan" "$(stamp)" "$norm" >>"$logFile" "$cyan" "$(stamp)" "$norm" >>"$logFile"
@@ -707,8 +718,8 @@ fi
# dump redis # dump redis
printf "%s[%s] -- [INFO] Dumping mailcow redis database --%s\n" \ printf "%s[%s] -- [INFO] Dumping mailcow redis database --%s\n" \
"$cyan" "$(stamp)" "$norm" >>"$logFile" "$cyan" "$(stamp)" "$norm" >>"$logFile"
docker-compose exec -T redis-mailcow redis-cli save >>"$logFile" 2>&1 ${dockerCmd} exec -T redis-mailcow redis-cli save >>"$logFile" 2>&1
rdumpResult=$(docker-compose exec -T redis-mailcow echo "$?") rdumpResult=$(${dockerCmd} exec -T redis-mailcow echo "$?")
if [ "$rdumpResult" -eq 0 ]; then if [ "$rdumpResult" -eq 0 ]; then
printf "%s[%s] -- [INFO] mailcow redis dumped successfully --%s\n" \ printf "%s[%s] -- [INFO] mailcow redis dumped successfully --%s\n" \
"$cyan" "$(stamp)" "$norm" >>"$logFile" "$cyan" "$(stamp)" "$norm" >>"$logFile"
@@ -772,10 +783,11 @@ else
warnCount=$((warnCount + 1)) warnCount=$((warnCount + 1))
fi fi
### execute borg prune if paramters are provided, otherwise skip with a warning ### execute borg prune if parameters are provided, otherwise skip with a warning
if [ -n "${borgPruneSettings}" ]; then if [ -n "${borgPruneSettings}" ]; then
printf "%s[%s] -- [INFO] Executing borg prune operation --%s\n" \ printf "%s[%s] -- [INFO] Executing borg prune operation --%s\n" \
"$cyan" "$(stamp)" "$norm" >>"$logFile" "$cyan" "$(stamp)" "$norm" >>"$logFile"
# shellcheck disable=SC2086
"${borgPath}" prune --show-rc -v ${borgPruneParams} ${borgPruneSettings} \ "${borgPath}" prune --show-rc -v ${borgPruneParams} ${borgPruneSettings} \
2>>"$logFile" 2>>"$logFile"
borgPruneResult="$?" borgPruneResult="$?"
@@ -834,7 +846,8 @@ exit 0
### error codes ### error codes
# 1: parameter error # 1: parameter error
# 2: not run as root # 2: not run as root
# 3: borg not installed # 3: docker compose not installed or not functioning
# 4: borg not installed
# 99: TERM signal trapped # 99: TERM signal trapped
# 100: could not change to mailcow-dockerized directory # 100: could not change to mailcow-dockerized directory
# 101: could not stop container(s) # 101: could not stop container(s)
+178 -158
View File
@@ -43,7 +43,7 @@ consoleError() {
} }
exitError() { exitError() {
printf "%s[%s] --- %s execution completed with error ---\n%s" "$err" "$(stamp)" "$scriptName" "$norm" >> "$logfile" printf "%s[%s] --- %s execution completed with error ---\n%s" "$err" "$(stamp)" "$scriptName" "$norm" >>"$logfile"
exit "$1" exit "$1"
} }
@@ -51,13 +51,13 @@ doRestore() {
sourceFiles=$(find "${backupLocation}" -iname "${1}" -type d) sourceFiles=$(find "${backupLocation}" -iname "${1}" -type d)
if [ -n "$sourceFiles" ]; then if [ -n "$sourceFiles" ]; then
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
if (! (cd "$sourceFiles/_data" && tar -cf - .) | (cd "${2}" && tar xvf -) >> "$logfile" ); then if ( ! (cd "$sourceFiles/_data" && tar -cf - .) | (cd "${2}" && tar xvf -) >>"$logfile"); then
return 1 return 1
else else
return 0 return 0
fi fi
else else
if (! (cd "$sourceFiles/_data" && tar -cf - .) | (cd "${2}" && tar xvf -) > /dev/null 2>&1 ); then if ( ! (cd "$sourceFiles/_data" && tar -cf - .) | (cd "${2}" && tar xvf -) >/dev/null 2>&1); then
return 1 return 1
else else
return 0 return 0
@@ -146,35 +146,36 @@ textNewline() {
} }
trapExit() { trapExit() {
printf "%s[%s] -- [ERROR] 99: Caught signal --%s\n" "$err" "$(stamp)" "$norm" >> "$logfile" printf "%s[%s] -- [ERROR] 99: Caught signal --%s\n" "$err" "$(stamp)" "$norm" >>"$logfile"
printf "%s[%s] --- %s execution terminated via signal ---\n%s" "$err" "$(stamp)" "$scriptName" "$norm" >> "$logfile" printf "%s[%s] --- %s execution terminated via signal ---\n%s" "$err" "$(stamp)" "$scriptName" "$norm" >>"$logfile"
exit 99 exit 99
} }
writeLog() { writeLog() {
if [ "$1" = "task" ]; then if [ "$1" = "task" ]; then
printf "%s[%s] -- [INFO] %s... " "$info" "$(stamp)" "$2" >> "$logfile" printf "%s[%s] -- [INFO] %s... " "$info" "$(stamp)" "$2" >>"$logfile"
elif [ "$1" = "done" ]; then elif [ "$1" = "done" ]; then
if [ -z "$2" ]; then if [ -z "$2" ]; then
printf "%sdone%s --\n%s" "$ok" "$info" "$norm" >> "$logfile" printf "%sdone%s --\n%s" "$ok" "$info" "$norm" >>"$logfile"
elif [ "$2" = "error" ]; then elif [ "$2" = "error" ]; then
printf "%sERROR%s --\n%s" "$err" "$info" "$norm" >> "$logfile" printf "%sERROR%s --\n%s" "$err" "$info" "$norm" >>"$logfile"
elif [ "$2" = "warn" ]; then elif [ "$2" = "warn" ]; then
printf "%swarning%s --\n%s" "$yellow" "$info" "$norm" >> "$logfile" printf "%swarning%s --\n%s" "$yellow" "$info" "$norm" >>"$logfile"
fi fi
elif [ "$1" = "error" ]; then elif [ "$1" = "error" ]; then
printf "%s[%s] -- [ERROR] %s: %s --\n%s" "$err" "$(stamp)" "$2" "$3" "$norm" >> "$logfile" printf "%s[%s] -- [ERROR] %s: %s --\n%s" "$err" "$(stamp)" "$2" "$3" "$norm" >>"$logfile"
elif [ "$1" = "warn" ]; then elif [ "$1" = "warn" ]; then
printf "%s[%s] -- [WARNING] %s --\n%s" "$yellow" "$(stamp)" "$2" "$norm" >> "$logfile" printf "%s[%s] -- [WARNING] %s --\n%s" "$yellow" "$(stamp)" "$2" "$norm" >>"$logfile"
elif [ "$1" = "info" ]; then elif [ "$1" = "info" ]; then
printf "%s[%s] -- [INFO] %s --\n%s" "$info" "$(stamp)" "$2" "$norm" >> "$logfile" printf "%s[%s] -- [INFO] %s --\n%s" "$info" "$(stamp)" "$2" "$norm" >>"$logfile"
elif [ "$1" = "success" ]; then elif [ "$1" = "success" ]; then
printf "%s[%s] -- [SUCCESS] %s --\n%s" "$ok" "$(stamp)" "$2" "$norm" >> "$logfile" printf "%s[%s] -- [SUCCESS] %s --\n%s" "$ok" "$(stamp)" "$2" "$norm" >>"$logfile"
fi fi
} }
### parameter defaults ### parameter defaults
# script related # script related
scriptVersion="4.0"
scriptPath="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd -P)" scriptPath="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd -P)"
scriptName="$(basename "$0")" scriptName="$(basename "$0")"
errorCount=0 errorCount=0
@@ -195,6 +196,7 @@ mcDockerCompose='/opt/mailcow-dockerized/docker-compose.yml'
sqlRunning=0 sqlRunning=0
dockerStartTimeout=180 dockerStartTimeout=180
dockerStopTimeout=120 dockerStopTimeout=120
dockerCmd="docker compose"
### check if user is root ### check if user is root
if [ "$(id -u)" -ne 0 ]; then if [ "$(id -u)" -ne 0 ]; then
@@ -204,11 +206,16 @@ fi
### process startup parameters ### process startup parameters
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
case "$1" in case "$1" in
-h|-\?|--help) -h | -\? | --help)
# display help # display help
scriptHelp scriptHelp
;; ;;
-l|--log) --version)
# display script version
printf "\nMailcowRestore - Restore Mailcow from a borgbackup repo : Version %s\n\n" ${scriptVersion}
exit 0
;;
-l | --log)
# set logfile location # set logfile location
if [ -z "$2" ]; then if [ -z "$2" ]; then
consoleError '1' "Log file path cannot be null. Leave unspecified to save log in the same directory as this script." consoleError '1' "Log file path cannot be null. Leave unspecified to save log in the same directory as this script."
@@ -216,10 +223,10 @@ while [ $# -gt 0 ]; do
logfile="$2" logfile="$2"
shift shift
;; ;;
-v|--verbose) -v | --verbose)
verbose=1 verbose=1
;; ;;
-d|--docker-compose) -d | --docker-compose)
# FULL path to docker-compose file # FULL path to docker-compose file
if [ -n "$2" ]; then if [ -n "$2" ]; then
if [ -f "$2" ]; then if [ -f "$2" ]; then
@@ -232,8 +239,8 @@ while [ $# -gt 0 ]; do
consoleError '1' "$1: cannot be blank/empty." consoleError '1' "$1: cannot be blank/empty."
fi fi
;; ;;
-m|--mailcow-config) -m | --mailcow-config)
# FULL path to mailcow configuration file file # FULL path to mailcow configuration file file
if [ -n "$2" ]; then if [ -n "$2" ]; then
if [ -f "$2" ]; then if [ -f "$2" ]; then
mcConfig="$2" mcConfig="$2"
@@ -245,7 +252,7 @@ while [ $# -gt 0 ]; do
consoleError '1' "$1: cannot be blank/empty." consoleError '1' "$1: cannot be blank/empty."
fi fi
;; ;;
-t1|--timeout-start) -t1 | --timeout-start)
if [ -z "$2" ]; then if [ -z "$2" ]; then
consoleError '1' "$1: cannot be blank/empty." consoleError '1' "$1: cannot be blank/empty."
else else
@@ -253,7 +260,7 @@ while [ $# -gt 0 ]; do
shift shift
fi fi
;; ;;
-t2|--timeout-stop) -t2 | --timeout-stop)
if [ -z "$2" ]; then if [ -z "$2" ]; then
consoleError '1' "$1: cannot be blank/empty." consoleError '1' "$1: cannot be blank/empty."
else else
@@ -261,9 +268,9 @@ while [ $# -gt 0 ]; do
shift shift
fi fi
;; ;;
-b|--backup-location) -b | --backup-location)
if [ -n "$2" ]; then if [ -n "$2" ]; then
if [ -d "$2" ] && [ -n "$( ls -A "$2" )" ]; then if [ -d "$2" ] && [ -n "$(ls -A "$2")" ]; then
backupLocation="${2%/}" backupLocation="${2%/}"
shift shift
else else
@@ -309,6 +316,14 @@ export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
if ! command -v docker >/dev/null; then if ! command -v docker >/dev/null; then
consoleError '3' 'docker does not seem to be installed!' consoleError '3' 'docker does not seem to be installed!'
fi fi
# verify compose installed and set proper commands based on version
if ! (docker compose version >/dev/null 2>&1); then
if ! (docker-compose --version >/dev/null 2>&1); then
printf "\n%sERROR: Docker Compose not installed or not functioning%s\n\n" "$err" "$norm"
exit 3
fi
dockerCmd="docker-compose"
fi
# mailcow.conf? # mailcow.conf?
if [ ! -f "$mcConfig" ]; then if [ ! -f "$mcConfig" ]; then
consoleError '1' "mailcow configuration file ($mcConfig) cannot be found." consoleError '1' "mailcow configuration file ($mcConfig) cannot be found."
@@ -353,7 +368,7 @@ else
fi fi
fi fi
# write initial log entries # write initial log entries
if ! printf "%s[%s] --- Start %s execution ---\n%s" "$magenta" "$(stamp)" "$scriptName" "$norm" 2>/dev/null >> "$logfile"; then if ! printf "%s[%s] --- Start %s execution ---\n%s" "$magenta" "$(stamp)" "$scriptName" "$norm" 2>/dev/null >>"$logfile"; then
consoleError '1' "Unable to write to log file ($logfile)" consoleError '1' "Unable to write to log file ($logfile)"
fi fi
writeLog 'info' "Log located at $logfile" writeLog 'info' "Log located at $logfile"
@@ -383,14 +398,14 @@ if [ "$restoreSQL" -eq 1 ]; then
sqlBackup=$(find "${backupLocation}/tmp" -iname "*.sql") sqlBackup=$(find "${backupLocation}/tmp" -iname "*.sql")
if [ -n "$sqlBackup" ]; then if [ -n "$sqlBackup" ]; then
# start mysql container if not already running # start mysql container if not already running
if ! docker container inspect -f '{{ .State.Running }}' ${COMPOSE_PROJECT_NAME}_mysql-mailcow_1 > /dev/null 2>&1; then if ! docker container inspect -f '{{ .State.Running }}' ${COMPOSE_PROJECT_NAME}_mysql-mailcow_1 >/dev/null 2>&1; then
docker-compose up -d mysql-mailcow > /dev/null 2>&1 ${dockerCmd} up -d mysql-mailcow >/dev/null 2>&1
if docker container inspect -f '{{ .State.Running }}' ${COMPOSE_PROJECT_NAME}_mysql-mailcow_1 > /dev/null 2>&1; then if docker container inspect -f '{{ .State.Running }}' ${COMPOSE_PROJECT_NAME}_mysql-mailcow_1 >/dev/null 2>&1; then
sqlRunning=1 sqlRunning=1
else else
writeLog 'done' 'error' writeLog 'done' 'error'
writeLog 'error' '12' "Cannot start mysql-mailcow container -- cannot restore mailcow database!" writeLog 'error' '12' "Cannot start mysql-mailcow container -- cannot restore mailcow database!"
errorCount=$((errorCount+1)) errorCount=$((errorCount + 1))
fi fi
else else
sqlRunning=1 sqlRunning=1
@@ -398,29 +413,29 @@ if [ "$restoreSQL" -eq 1 ]; then
else else
writeLog 'done' 'error' writeLog 'done' 'error'
writeLog 'error' '11' "Cannot locate SQL backup -- cannot restore mailcow database!" writeLog 'error' '11' "Cannot locate SQL backup -- cannot restore mailcow database!"
errorCount=$((errorCount+1)) errorCount=$((errorCount + 1))
fi fi
# restore sql # restore sql
if [ "$sqlRunning" -eq 1 ]; then if [ "$sqlRunning" -eq 1 ]; then
if docker exec -i "$(docker-compose ps -q mysql-mailcow)" mysql -u${DBUSER} -p${DBPASS} ${DBNAME} < "${sqlBackup}" > /dev/null 2>&1; then if docker exec -i "$(${dockerCmd} ps -q mysql-mailcow)" mysql -u${DBUSER} -p${DBPASS} ${DBNAME} <"${sqlBackup}" >/dev/null 2>&1; then
writeLog 'done' writeLog 'done'
else else
writeLog 'done' 'error' writeLog 'done' 'error'
writeLog 'error' '13' "Something went wrong while trying to restore SQL database. Perhaps try again?" writeLog 'error' '13' "Something went wrong while trying to restore SQL database. Perhaps try again?"
errorCount=$((errorCount+1)) errorCount=$((errorCount + 1))
fi fi
fi fi
fi fi
### stop containers (necessary for all restore operations except SQL) ### stop containers (necessary for all restore operations except SQL)
writeLog 'task' "Stopping mailcow" writeLog 'task' "Stopping mailcow"
if ! docker-compose down --timeout "${dockerStopTimeout}" > /dev/null 2>&1; then if ! ${dockerCmd} down --timeout "${dockerStopTimeout}" >/dev/null 2>&1; then
writeLog 'done' 'error' writeLog 'done' 'error'
writeLog 'error' '20' "Unable to bring mailcow containers down -- cannot reliably restore. Aborting." writeLog 'error' '20' "Unable to bring mailcow containers down -- cannot reliably restore. Aborting."
exitError 20 exitError 20
fi fi
if [ "$( docker ps --filter "name=${COMPOSE_PROJECT_NAME}" -q | wc -l )" -gt 0 ]; then if [ "$(docker ps --filter "name=${COMPOSE_PROJECT_NAME}" -q | wc -l)" -gt 0 ]; then
writeLog 'done' 'error' writeLog 'done' 'error'
writeLog 'error' '20' "Unable to bring mailcow containers down -- cannot reliably restore. Aborting." writeLog 'error' '20' "Unable to bring mailcow containers down -- cannot reliably restore. Aborting."
exitError 20 exitError 20
@@ -436,59 +451,61 @@ if [ "$restoreMail" -eq 1 ]; then
fi fi
# restore email messages # restore email messages
doRestore "${COMPOSE_PROJECT_NAME}_vmail-vol-1" "$dockerVolumeMail"; ec="$?" doRestore "${COMPOSE_PROJECT_NAME}_vmail-vol-1" "$dockerVolumeMail"
ec="$?"
case "$ec" in case "$ec" in
0) 0)
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
writeLog 'success' "Email messages restored" writeLog 'success' "Email messages restored"
else else
writeLog 'done' writeLog 'done'
fi fi
;; ;;
1) 1)
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
writeLog 'error' '52' "There was an error restoring one or more email messages." writeLog 'error' '52' "There was an error restoring one or more email messages."
else else
writeLog 'done' 'error' writeLog 'done' 'error'
writeLog 'error' '52' "There was an error restoring one or more email messages." writeLog 'error' '52' "There was an error restoring one or more email messages."
fi fi
;; ;;
2) 2)
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
writeLog 'error' '51' "Cannot locate email message backups!" writeLog 'error' '51' "Cannot locate email message backups!"
else else
writeLog 'done' 'error' writeLog 'done' 'error'
writeLog 'error' '51' "Cannot locate email message backups!" writeLog 'error' '51' "Cannot locate email message backups!"
fi fi
;; ;;
esac esac
# restore encryption key # restore encryption key
doRestore "${COMPOSE_PROJECT_NAME}_crypt-vol-1" "$dockerVolumeCrypt"; ec="$?" doRestore "${COMPOSE_PROJECT_NAME}_crypt-vol-1" "$dockerVolumeCrypt"
ec="$?"
case "$ec" in case "$ec" in
0) 0)
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
writeLog 'success' "Encryption key restored" writeLog 'success' "Encryption key restored"
else else
writeLog 'done' writeLog 'done'
fi fi
;; ;;
1) 1)
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
writeLog 'error' '52' "There was an error restoring the encryption key! Any restored messages are likely *not* readable!" writeLog 'error' '52' "There was an error restoring the encryption key! Any restored messages are likely *not* readable!"
else else
writeLog 'done' 'error' writeLog 'done' 'error'
writeLog 'error' '52' "There was an error restoring the encryption key! Any restored messages are likely *not* readable!" writeLog 'error' '52' "There was an error restoring the encryption key! Any restored messages are likely *not* readable!"
fi fi
;; ;;
2) 2)
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
writeLog 'error' '51' "Cannot locate encryption key backup!" writeLog 'error' '51' "Cannot locate encryption key backup!"
else else
writeLog 'done' 'error' writeLog 'done' 'error'
writeLog 'error' '51' "Cannot locate encryption key backup!" writeLog 'error' '51' "Cannot locate encryption key backup!"
fi fi
;; ;;
esac esac
fi fi
@@ -500,31 +517,32 @@ if [ "$restorePostfix" -eq 1 ]; then
writeLog 'task' "Restoring postfix files" writeLog 'task' "Restoring postfix files"
fi fi
doRestore "${COMPOSE_PROJECT_NAME}_postfix-vol-1" "$dockerVolumePostfix"; ec="$?" doRestore "${COMPOSE_PROJECT_NAME}_postfix-vol-1" "$dockerVolumePostfix"
ec="$?"
case "$ec" in case "$ec" in
0) 0)
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
writeLog 'success' "Postfix files restored" writeLog 'success' "Postfix files restored"
else else
writeLog 'done' writeLog 'done'
fi fi
;; ;;
1) 1)
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
writeLog 'error' '52' "There was an error restoring one or more postfix files." writeLog 'error' '52' "There was an error restoring one or more postfix files."
else else
writeLog 'done' 'error' writeLog 'done' 'error'
writeLog 'error' '52' "There was an error restoring one or more postfix files." writeLog 'error' '52' "There was an error restoring one or more postfix files."
fi fi
;; ;;
2) 2)
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
writeLog 'error' '51' "Cannot locate postfix backups!" writeLog 'error' '51' "Cannot locate postfix backups!"
else else
writeLog 'done' 'error' writeLog 'done' 'error'
writeLog 'error' '51' "Cannot locate postfix backups!" writeLog 'error' '51' "Cannot locate postfix backups!"
fi fi
;; ;;
esac esac
fi fi
@@ -536,31 +554,32 @@ if [ "$restoreRspamd" -eq 1 ]; then
writeLog 'task' "Restoring Rspamd files" writeLog 'task' "Restoring Rspamd files"
fi fi
doRestore "${COMPOSE_PROJECT_NAME}_rspamd-vol-1" "$dockerVolumeRspamd"; ec="$?" doRestore "${COMPOSE_PROJECT_NAME}_rspamd-vol-1" "$dockerVolumeRspamd"
ec="$?"
case "$ec" in case "$ec" in
0) 0)
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
writeLog 'success' "Rspamd files restored" writeLog 'success' "Rspamd files restored"
else else
writeLog 'done' writeLog 'done'
fi fi
;; ;;
1) 1)
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
writeLog 'error' '52' "There was an error restoring one or more Rspamd files." writeLog 'error' '52' "There was an error restoring one or more Rspamd files."
else else
writeLog 'done' 'error' writeLog 'done' 'error'
writeLog 'error' '52' "There was an error restoring one or more Rspamd files." writeLog 'error' '52' "There was an error restoring one or more Rspamd files."
fi fi
;; ;;
2) 2)
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
writeLog 'error' '51' "Cannot locate Rspamd backups!" writeLog 'error' '51' "Cannot locate Rspamd backups!"
else else
writeLog 'done' 'error' writeLog 'done' 'error'
writeLog 'error' '51' "Cannot locate Rspamd backups!" writeLog 'error' '51' "Cannot locate Rspamd backups!"
fi fi
;; ;;
esac esac
fi fi
@@ -572,54 +591,55 @@ if [ "$restoreRedis" -eq 1 ]; then
writeLog 'task' "Restoring redis database" writeLog 'task' "Restoring redis database"
fi fi
doRestore "${COMPOSE_PROJECT_NAME}_redis-vol-1" "$dockerVolumeRedis"; ec="$?" doRestore "${COMPOSE_PROJECT_NAME}_redis-vol-1" "$dockerVolumeRedis"
ec="$?"
case "$ec" in case "$ec" in
0) 0)
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
writeLog 'success' "Redis database restored" writeLog 'success' "Redis database restored"
else else
writeLog 'done' writeLog 'done'
fi fi
;; ;;
1) 1)
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
writeLog 'error' '52' "There was an error restoring the redis database. This is usually *not* a serious issue." writeLog 'error' '52' "There was an error restoring the redis database. This is usually *not* a serious issue."
else else
writeLog 'done' 'error' writeLog 'done' 'error'
writeLog 'error' '52' "There was an error restoring the redis database. This is usually *not* a serious issue." writeLog 'error' '52' "There was an error restoring the redis database. This is usually *not* a serious issue."
fi fi
;; ;;
2) 2)
if [ "$verbose" -eq 1 ]; then if [ "$verbose" -eq 1 ]; then
writeLog 'error' '51' "Cannot locate redis database backups!" writeLog 'error' '51' "Cannot locate redis database backups!"
else else
writeLog 'done' 'error' writeLog 'done' 'error'
writeLog 'error' '51' "Cannot locate redis database backups!" writeLog 'error' '51' "Cannot locate redis database backups!"
fi fi
;; ;;
esac esac
fi fi
### restart mailcow ### restart mailcow
writeLog 'task' "Starting mailcow" writeLog 'task' "Starting mailcow"
if ! docker-compose up -d > /dev/null 2>&1; then if ! ${dockerCmd} up -d >/dev/null 2>&1; then
writeLog 'done' 'warn' writeLog 'done' 'warn'
writeLog 'warn' '21' "Unable to automatically start mailcow containers. Please attempt a manual start and note any errors." writeLog 'warn' '21' "Unable to automatically start mailcow containers. Please attempt a manual start and note any errors."
warnCount=$((warnCount+1)) warnCount=$((warnCount + 1))
fi fi
writeLog 'done' writeLog 'done'
### exit gracefully ### exit gracefully
if [ "$errorCount" -gt 0 ]; then if [ "$errorCount" -gt 0 ]; then
# note non-terminating errors # note non-terminating errors
printf "%s[%s] --- %s execution completed with %s error(s) ---\n%s" "$err" "$(stamp)" "$scriptName" "$errorCount" "$norm" >> "$logfile" printf "%s[%s] --- %s execution completed with %s error(s) ---\n%s" "$err" "$(stamp)" "$scriptName" "$errorCount" "$norm" >>"$logfile"
exit 98 exit 98
elif [ "$warnCount" -gt 0 ]; then elif [ "$warnCount" -gt 0 ]; then
printf "%s[%s] --- %s execution completed with %s warning(s) ---\n%s" "$yellow" "$(stamp)" "$scriptName" "$warnCount" "$norm" >> "$logfile" printf "%s[%s] --- %s execution completed with %s warning(s) ---\n%s" "$yellow" "$(stamp)" "$scriptName" "$warnCount" "$norm" >>"$logfile"
exit 97 exit 97
else else
writeLog 'success' "All processes completed" writeLog 'success' "All processes completed"
printf "%s[%s] --- %s execution completed ---\n%s" "$magenta" "$(stamp)" "$scriptName" "$norm" >> "$logfile" printf "%s[%s] --- %s execution completed ---\n%s" "$magenta" "$(stamp)" "$scriptName" "$norm" >>"$logfile"
exit 0 exit 0
fi fi