1
0
Fork 0

update ab-openldap scripts

This commit is contained in:
Asif Bacchus 2020-03-13 02:28:00 -06:00
parent 220f0bdd23
commit ffd336e218
3 changed files with 815 additions and 47 deletions

View File

@ -24,18 +24,23 @@ yellow=$(tput setaf 3)
### parameter defaults
clean=false
restore=false
container_name="ab-openldap"
volume_data="ab-openldap_data"
volume_ldif="ab-openldap_ldif"
backup_dir="$(pwd)/restore"
remove=0
shell=false
tag=latest
scriptHelp () {
printf "\n${magenta}%80s\n" | tr " " "-"
printf "${norm}This is a simple helper script so you can avoid lengthy typing when working\n"
printf "\n%s%80s\n" "$magenta" | tr " " "-"
printf "%sThis is a simple helper script so you can avoid lengthy typing when working\n" "$norm"
printf "with the openLDAP container. The script reads the contents of 'ab-openldap.params'\n"
printf "and constructs various 'docker run' commands based on that file. The biggest\n"
printf "timesaver is working with certificates. If they are specified in the '.params',\n"
printf "the script will automatically bind-mount them so openLDAP starts in 'TLS\n"
printf "file, the script will automatically bind-mount them so openLDAP starts in 'TLS\n"
printf "required' mode.\n\n"
printf "If you run the script with no parameters, it will execute the container\n"
printf "'normally': Run in detached mode with openLDAP automatically launched and\n"
@ -45,29 +50,50 @@ scriptHelp () {
printf "Containers run in SHELL mode are ALWAYS removed upon exit as they are meant for\n"
printf "testing only. By default, containers run without '--rm' will be restarted\n"
printf "automatically unless they are manually stopped via 'docker stop...'\n\n"
printf "${magenta}The script has the following parameters:\n"
printf "${cyan}(parameter in cyan) ${yellow}(default in yellow)${norm}\n\n"
printf "${cyan}-n|--name${norm}\n"
printf "%sThe script has the following parameters:\n" "$magenta"
printf "%s(parameter in cyan) %s(default in yellow)%s\n\n" \
"$cyan" "$yellow" "$norm"
printf "%s-t|--tag%s\n" "$cyan" "$norm"
printf "Change the version of the container downloaded by specifying a particular tag.\n"
printf "This can be useful when testing new versions or if you have to roll back to a\n"
printf "previous container version.\n"
printf "%s(latest)%s\n\n" "$yellow" "$norm"
printf "%s-n|--name%s\n" "$cyan" "$norm"
printf "Change the name of the container. This is cosmetic and does not affect\n"
printf "operation in any way.\n"
printf "${yellow}(ab-openldap)${norm}\n\n"
printf "${cyan}--rm|--remove${norm}\n"
printf "%s(ab-openldap)%s\n\n" "$yellow" "$norm"
printf "%s--data%s\n" "$cyan" "$norm"
printf "Change the name of the docker volume used to persist data.\n"
printf "%s(ab-openldap_data)%s\n\n" "$yellow" "$norm"
printf "%s--ldif%s\n" "$cyan" "$norm"
printf "Change the name of the docker volume used to persist LDIFs.\n"
printf "%s(ab-openldap_ldif)%s\n\n" "$yellow" "$norm"
printf "%s--rm|--remove%s\n" "$cyan" "$norm"
printf "Automatically remove the container and volume (unless data is written) after it\n"
printf "is exited.\n"
printf "${yellow}(off: do not destroy container when stopped)${norm}\n\n"
printf "${cyan}-s|--shell${norm}\n"
printf "%s(off: do not destroy container when stopped)%s\n\n" \
"$yellow" "$norm"
printf "%s-s|--shell%s\n" "$cyan" "$norm"
printf "Enter the container using an interactive POSIX shell. This happens after\n"
printf "startup operations but *before* openLDAP (slapd) is actually started. This is\n"
printf "a great way to see configuration changes possibly stopping openLDAP from\n"
printf "starting. You can combine this with '--rm' for easy configuration checks.\n"
printf "${yellow}(off: run in detached mode)${norm}\n\n"
printf "${cyan}--clean${norm}\n"
printf "%s(off: run in detached mode)%s\n\n" "$yellow" "$norm"
printf "%s--clean%s\n" "$cyan" "$norm"
printf "This option will stop ALL running openLDAP containers *AND DESTROY ALL\n"
printf "VOLUMES*. This is meant to give you a 'clean start' if you've made\n"
printf "configuration changes, etc.\n\n"
printf "${yellow}More information can be found at:\n"
printf "%s--restore%s\n" "$cyan" "$norm"
printf "Restore a 'slapcat' backup to the data and ldif volume in preparation for\n"
printf "mounting them in a normal container.\n"
printf "It is strongly recommended you review your '-t' '--data' and '--ldif' settings\n"
printf "before proceeding with this option.\n\n"
printf "%s--backupdir%s\n" "$cyan" "$norm"
printf "Location of the 'slapcat' backup files which you want to restore.\n"
printf "%s(./restore)%s\n\n" "$yellow" "$norm"
printf "%sMore information can be found at:\n" "$yellow"
printf "https://git.asifbacchus.app/ab-docker/openldap/wiki\n"
printf "${magenta}%80s\n\n" | tr " " "-"
printf "%s%80s\n\n" "$magenta" | tr " " "-"
exit 0
}
@ -76,14 +102,14 @@ scriptHelp () {
# is user root or in the docker group?
if [ ! "$( id -u )" -eq 0 ]; then
if ! id -Gn | grep docker > /dev/null; then
printf "${err}\nYou must either be root or in the 'docker' group to run this script since you must be able to actually start the container! Exiting.\n${norm}"
printf "%s\nYou must either be root or in the 'docker' group to run this script since you must be able to actually start the container! Exiting.\n%s" "$err" "$norm"
exit 2
fi
fi
# does the params file exist?
if [ ! -f "./ab-openldap.params" ]; then
printf "${err}\nCannot find 'ab-openldap.params' file in the same directory as this script. Exiting.\n${norm}"
printf "%s\nCannot find 'ab-openldap.params' file in the same directory as this script. Exiting.\n%s" "$err" "$norm"
exit 3
fi
@ -93,15 +119,18 @@ fi
# check for certs if using TLS
if [ "$TLS_CERT" ]; then
if [ ! -f "$TLS_CERT" ]; then
printf "${err}\nCannot find specified TLS certificate file. Exiting.${norm}\n"
printf "%s\nCannot find specified TLS certificate file. Exiting.%s\n" \
"$err" "$norm"
exit 5
fi
if [ ! -f "$TLS_KEY" ]; then
printf "${err}\nCannot find specified TLS private key file. Exiting.${norm}\n"
printf "%s\nCannot find specified TLS private key file. Exiting.%s\n" \
"$err" "$norm"
exit 5
fi
if [ ! -f "$TLS_CHAIN" ]; then
printf "${err}\nCannot find specified TLS certificate chain file. Exiting.${norm}\n"
printf "%s\nCannot find specified TLS certificate chain file. Exiting.%s\n" \
"$err" "$norm"
exit 5
fi
fi
@ -126,18 +155,63 @@ while [ $# -gt 0 ]; do
# stop if necessary, delete volumes
clean=true
;;
--restore)
# restore backup
restore=true
;;
-n|--name)
# container name
if [ -z "$2" ]; then
printf "${err}\nNo container name specified. Exiting.\n${norm}"
printf "%s\nNo container name specified. Exiting.\n%s" \
"$err" "$norm"
exit 1
fi
container_name="$2"
shift
;;
--data)
# data volume name
if [ -z "$2" ]; then
printf "%s\nNo name specified for data volume. Exiting.\n%s" \
"$err" "$norm"
exit 1
fi
volume_data="$2"
shift
;;
--ldif)
# ldif volume name
if [ -z "$2" ]; then
printf "%s\nNo name specified for LDIF volume. Exiting.\n%s" \
"$err" "$norm"
exit 1
fi
volume_ldif="$2"
shift
;;
--backupdir)
# location of backup files to restore
if [ -z "$2" ]; then
printf "%s\nLocation of your backup files not provided. Exiting.\n%s" \
"$err" "$norm"
exit 1
fi
backup_dir="$2"
shift
;;
-t|--tag)
# specify container tag
if [ -z "$2" ]; then
printf "%s\nNo tag specified. Exiting.\n%s" \
"$err" "$norm"
exit 1
fi
tag="$2"
shift
;;
*)
printf "${err}\nUnknown option: %s\n" "$1"
printf "Use '--help' for valid options.\n\n${norm}"
printf "%s\nUnknown option: %s\n" "$err" "$1"
printf "Use '--help' for valid options.\n\n%s" "$norm"
exit 1
;;
esac
@ -145,13 +219,39 @@ while [ $# -gt 0 ]; do
done
# cleanup any running containers and volumes
# cleanup containers and volumes
if [ $clean = true ]; then
# display warning and confirm user's intentions
printf "\nThis will stop and remove all ab-openldap containers %sAND REMOVE ALL PERSISTENT DATA VOLUMES%s. Please ensure you have a backup and understand how to restore your data.\n" \
"$red" "$norm"
printf "%sThis action CANNOT be undone!%s\n\n" \
"$red" "$norm"
# confirmation loop
while true; do
printf "%sAre you sure you want to continue? (yes/no)%s " \
"$cyan" "$norm"
read -r yn
case "$yn" in
[Yy]*)
break
;;
[Nn]*)
printf "\n"
exit 0
;;
*)
printf "Please answer 'y' or 'n'.\n"
;;
esac
done
# get all ab-openldap containers
containers=$(docker ps -a --no-trunc --filter "label=org.label-schema.name=ab-openldap" --format "{{ .Names }}")
# check for null value -- no containers to remove
if [ -z "$containers" ]; then
printf "${err}No openldap containers to remove. Exiting.${norm}\n\n"
printf "%sNo openldap containers to remove. Exiting.%s\n\n" \
"$err" "$norm"
exit 0
fi
@ -159,66 +259,125 @@ if [ $clean = true ]; then
set -- dummy $containers
shift
for container; do
printf "\n${cyan}Found %s -- processing:${norm}\n" ${container}
volume=$(docker inspect --format '{{ range .Mounts }}{{ if eq .Destination "/var/openldap/data" }}{{ .Name }}{{ end }}{{ end }}' ${container})
printf "\t${red}Stopping container...${norm}\n"
printf "\n%sFound %s -- processing:%s\n" \
"$cyan" "$container" "$norm"
# stop container
printf "\t%sStopping container...%s\n" "$red" "$norm"
docker stop ${container} > /dev/null 2>&1
printf "\t${red}Removing container...${norm}\n"
# find volumes
volumes=$(docker inspect --format '{{ range .Mounts }}{{ println .Name }}{{ end }}' ${container})
# remove container
printf "\t%sRemoving container...%s\n" "$red" "$norm"
docker rm ${container} > /dev/null 2>&1
printf "\t${red}Removing volume...${norm}\n"
docker volume rm ${volume} > /dev/null 2>&1
printf "${cyan}...done${norm}\n"
# pause to allow write flushing
sleep 3
# iterate volumes
set -- dummy2 $volumes
shift
for volume; do
printf "\t%sRemoving volume '%s'...%s\n" "$red" "$volume" "$norm"
docker volume rm ${volume} > /dev/null 2>&1
done
printf "%s...done%s\n" "$cyan" "$norm"
done
elif [ $restore = true ]; then
# restore backup
printf "%s\n*** Restoring Backup ***\n\n%s" "$magenta" "$norm"
printf "To avoid errors due to existing files, this script will delete any volumes that have the following names:\n"
printf "\t%s\n\t%s\n" "$volume_data" "$volume_ldif"
# confirmation loop
while true; do
printf "%sDo you want to continue? (yes/no)%s " \
"$cyan" "$norm"
read -r yn
case "$yn" in
[Yy]*)
break
;;
[Nn]*)
printf "\n"
exit 0
;;
*)
printf "Please answer 'y' or 'n'.\n"
;;
esac
done
# delete any conflicting volumes
docker volume rm ${volume_data} > /dev/null 2>&1
docker volume rm ${volume_ldif} > /dev/null 2>&1
# run temporary container to merge backup data into volumes
docker run --rm \
-v "$volume_data":/var/openldap/data \
-v "$volume_ldif":/etc/openldap/ldif \
-v "$backup_dir":/restore \
docker.asifbacchus.app/ldap/ab-openldap:${tag} \
cat /var/openldap/data/restore.log
printf "\nPlease review the log output on your screen to determine if the restore was successful or what errors need to be corrected. If everything was successful, your data volumes can be used in a new container started normally.\n"
# run without TLS
elif [ -z "$TLS_CERT" ]; then
if [ $shell = true ]; then
# exec shell
printf "${cyan}\nRunning SHELL on %s...${norm}\n" "$container_name"
printf "%s\nRunning SHELL on %s...%s\n" \
"$cyan" "$container_name" "$norm"
if [ -d "$MY_LDIF" ]; then
# bind-mount custom LDIFs if specified
docker run --rm -it --name ${container_name} \
--env-file ab-openldap.params \
-v "$volume_data":/var/openldap/data \
-v "$volume_ldif":/etc/openldap/ldif \
-v "$MY_LDIF":/etc/openldap/customLDIF \
-p 389:389 -p 636:636 \
docker.asifbacchus.app/ldap/ab-openldap:latest /bin/sh
docker.asifbacchus.app/ldap/ab-openldap:${tag} /bin/sh
else
docker run --rm -it --name ${container_name} \
--env-file ab-openldap.params \
-v "$volume_data":/var/openldap/data \
-v "$volume_ldif":/etc/openldap/ldif \
-p 389:389 -p 636:636 \
docker.asifbacchus.app/ldap/ab-openldap:latest /bin/sh
docker.asifbacchus.app/ldap/ab-openldap:${tag} /bin/sh
fi
else
# exec normally
printf "${cyan}\nRunning OPENLDAP on %s...${norm}\n" "$container_name"
printf "%s\nRunning OPENLDAP on %s...%s\n" \
"$cyan" "$container_name" "$norm"
if [ "$remove" -eq 1 ]; then
if [ -d "$MY_LDIF" ]; then
# bind-mount custom LDIFs if specified
docker run --rm -d --name ${container_name} \
--env-file ab-openldap.params \
-v "$volume_data":/var/openldap/data \
-v "$volume_ldif":/etc/openldap/ldif \
-v "$MY_LDIF":/etc/openldap/customLDIF \
-p 389:389 -p 636:636 \
docker.asifbacchus.app/ldap/ab-openldap:latest
docker.asifbacchus.app/ldap/ab-openldap:${tag}
else
docker run --rm -d --name ${container_name} \
--env-file ab-openldap.params \
-v "$volume_data":/var/openldap/data \
-v "$volume_ldif":/etc/openldap/ldif \
-p 389:389 -p 636:636 \
docker.asifbacchus.app/ldap/ab-openldap:latest
docker.asifbacchus.app/ldap/ab-openldap:${tag}
fi
else
if [ -d "$MY_LDIF" ]; then
# bind-mount custom LDIFs if specified
docker run -d --name ${container_name} \
--env-file ab-openldap.params \
-v "$volume_data":/var/openldap/data \
-v "$volume_ldif":/etc/openldap/ldif \
-v "$MY_LDIF":/etc/openldap/customLDIF \
-p 389:389 -p 636:636 \
--restart unless-stopped \
docker.asifbacchus.app/ldap/ab-openldap:latest
docker.asifbacchus.app/ldap/ab-openldap:${tag}
else
docker run -d --name ${container_name} \
--env-file ab-openldap.params \
-v "$volume_data":/var/openldap/data \
-v "$volume_ldif":/etc/openldap/ldif \
-p 389:389 -p 636:636 \
--restart unless-stopped \
docker.asifbacchus.app/ldap/ab-openldap:latest
docker.asifbacchus.app/ldap/ab-openldap:${tag}
fi
fi
fi
@ -226,70 +385,84 @@ elif [ -z "$TLS_CERT" ]; then
elif [ "$TLS_CERT" ] && [ "$TLS_KEY" ] && [ "$TLS_CHAIN" ]; then
if [ $shell = true ]; then
# exec shell
printf "${cyan}\nRunning SHELL on %s (TLS)...${norm}\n" "$container_name"
printf "%s\nRunning SHELL on %s (TLS)...%s\n" \
"$cyan" "$container_name" "$norm"
if [ -d "$MY_LDIF" ]; then
# bind-mount custom LDIFs if specified
docker run --rm -it --name ${container_name} \
--env-file ab-openldap.params \
-v "$volume_data":/var/openldap/data \
-v "$volume_ldif":/etc/openldap/ldif \
-v "$MY_LDIF":/etc/openldap/customLDIF \
-v "$TLS_CERT":/certs/fullchain.pem:ro \
-v "$TLS_KEY":/certs/privkey.pem:ro \
-v "$TLS_CHAIN":/certs/chain.pem:ro \
-p 389:389 -p 636:636 \
docker.asifbacchus.app/ldap/ab-openldap:latest /bin/sh
docker.asifbacchus.app/ldap/ab-openldap:${tag} /bin/sh
else
docker run --rm -it --name ${container_name} \
--env-file ab-openldap.params \
-v "$volume_data":/var/openldap/data \
-v "$volume_ldif":/etc/openldap/ldif \
-v "$TLS_CERT":/certs/fullchain.pem:ro \
-v "$TLS_KEY":/certs/privkey.pem:ro \
-v "$TLS_CHAIN":/certs/chain.pem:ro \
-p 389:389 -p 636:636 \
docker.asifbacchus.app/ldap/ab-openldap:latest /bin/sh
docker.asifbacchus.app/ldap/ab-openldap:${tag} /bin/sh
fi
else
# exec normally
printf "${cyan}\nRunning OPENLDAP on %s (TLS)...${norm}\n" "$container_name"
printf "%s\nRunning OPENLDAP on %s (TLS)...%s\n" \
"$cyan" "$container_name" "$norm"
if [ "$remove" -eq 1 ]; then
if [ -d "$MY_LDIF" ]; then
# bind-mount custom LDIFs if specified
docker run --rm -d --name ${container_name} \
--env-file ab-openldap.params \
-v "$volume_data":/var/openldap/data \
-v "$volume_ldif":/etc/openldap/ldif \
-v "$MY_LDIF":/etc/openldap/customLDIF \
-v "$TLS_CERT":/certs/fullchain.pem:ro \
-v "$TLS_KEY":/certs/privkey.pem:ro \
-v "$TLS_CHAIN":/certs/chain.pem:ro \
-p 389:389 -p 636:636 \
docker.asifbacchus.app/ldap/ab-openldap:latest
docker.asifbacchus.app/ldap/ab-openldap:${tag}
else
docker run --rm -d --name ${container_name} \
--env-file ab-openldap.params \
-v "$volume_data":/var/openldap/data \
-v "$volume_ldif":/etc/openldap/ldif \
-v "$TLS_CERT":/certs/fullchain.pem:ro \
-v "$TLS_KEY":/certs/privkey.pem:ro \
-v "$TLS_CHAIN":/certs/chain.pem:ro \
-p 389:389 -p 636:636 \
docker.asifbacchus.app/ldap/ab-openldap:latest
docker.asifbacchus.app/ldap/ab-openldap:${tag}
fi
else
if [ -d "$MY_LDIF" ]; then
# bind-mount custom LDIFs if specified
docker run -d --name ${container_name} \
--env-file ab-openldap.params \
-v "$volume_data":/var/openldap/data \
-v "$volume_ldif":/etc/openldap/ldif \
-v "$MY_LDIF":/etc/openldap/customLDIF \
-v "$TLS_CERT":/certs/fullchain.pem:ro \
-v "$TLS_KEY":/certs/privkey.pem:ro \
-v "$TLS_CHAIN":/certs/chain.pem:ro \
-p 389:389 -p 636:636 \
--restart unless-stopped \
docker.asifbacchus.app/ldap/ab-openldap:latest
docker.asifbacchus.app/ldap/ab-openldap:${tag}
else
docker run -d --name ${container_name} \
--env-file ab-openldap.params \
-v "$volume_data":/var/openldap/data \
-v "$volume_ldif":/etc/openldap/ldif \
-v "$TLS_CERT":/certs/fullchain.pem:ro \
-v "$TLS_KEY":/certs/privkey.pem:ro \
-v "$TLS_CHAIN":/certs/chain.pem:ro \
-p 389:389 -p 636:636 \
--restart unless-stopped \
docker.asifbacchus.app/ldap/ab-openldap:latest
docker.asifbacchus.app/ldap/ab-openldap:${tag}
fi
fi
fi

27
backup.params.template Normal file
View File

@ -0,0 +1,27 @@
#######
### openLDAP backup script parameters file
#######
### This file should be protected since it contains the password used to
### encrypt your backup files!
### recommend at least:
### chown root:root backup.parameters
### chmod 600 backup.parameters
# password used to encrypt backup
password='myPassword'
### encryption options
# encryption cipher
# use 'openssl enc --ciphers' to see which ciphers are supported by your
# openSSL installation
encryptionCipher='aes-256-cbc'
# number of iterations used to derive the private key, higher is better but
# more CPU intensive - minimum of 20000 recommended
encryptionIterations=25000
#EOF

568
backup.sh Executable file
View File

@ -0,0 +1,568 @@
#!/bin/sh
#
### backup openLDAP configuration and frontend database(s)
#
# error code reference:
# 0: exited normally, no errors
# 1: parameter or permissions error
# 2: unsupported number of containers (0 or more than 1)
# 3: could not create/remove temporary directory/files
# 4: problem exporting database(s)
# 5: problem compressing/encrypting tar.gz archive
# 6: problem copying encrypted archive to host
# 7: problem creating target subdirectory in outputLocation
# 8: problem extracting gzipped tarball file
# 99: dependencies not installed (tar, gzip)
### text formatting presets
# set colours for various log message types
bold=$(tput bold)
err=$(tput bold)$(tput setaf 1)
info=$(tput sgr0)
norm=$(tput sgr0)
ok=$(tput setaf 2)
# define other colours used
cyan=$(tput bold)$(tput setaf 6)
magenta=$(tput sgr0)$(tput setaf 5)
yellow=$(tput sgr0)$(tput setaf 3)
### parameter defaults
width=$(tput cols)
scriptPath="$( CDPATH='' cd -- "$( dirname -- "$0" )" && pwd -P )"
scriptName="$( basename "$0" )"
# logfile default: same location and name as this script, with '.log' extension
logfile="$scriptPath/${scriptName%.*}.log"
# encryption parameters file default: same location and name as this script,
# with '.params' extension
encParams="$scriptPath/${scriptName%.*}.params"
# backup mode by default
unset backupFile
extract=false
decrypt=false
# encryption enabled by default
noEncryption=0
tempDir="$(date '+%s')"
fileDate="$(date '+%F_%T')"
outputLocation="$scriptPath/"
# frontend starts at '1', so this number should always be the number of
# frontend databases + 1 -- default to only 1 frontend db
numFrontEnd=2
### functions
cleanup () {
logInfo 'task' 'Cleaning up'
if ! docker exec "$container" rm -rf "$tempDir" \
>> "$logfile" 2>&1; then
logInfo 'err'
exitError 3 'Unable to remove temporary files in container.' 'nc'
fi
logInfo 'done'
}
consoleError () {
printf "%s\n%s\n" "$err" "$2"
printf "Exiting.\n\n%s" "$norm"
exit "$1"
}
decryptionNote () {
printf "\n"
textblock "${bold}${magenta}Decrypting your backup archive:${norm}"
printf "\n"
textblock 'To decrypt and extract your backup file, you need the following information:'
textblock '1. The password used to encrypt the file'
textblock '2. The encryption cipher used (default: AES-256-CBC)'
textblock '3. The number of iterations used to create the private key (default: 25000)'
printf "\n"
textblock "Run the following command, replacing the ${cyan}[cyan stuff in square brackets]${norm} with values appropriate to your environment."
printf "\n"
textblock "openssl enc -d -${cyan}[cipher-here]${norm} -iter +${cyan}[iterations]${norm} -k ${cyan}[password]${norm} -in ${cyan}[backup_archive.tar.gz]${norm} | tar -xz -C ${cyan}[destinationDirectory]${norm}"
printf "\n"
textblock 'NOTE: The output directory must already exist or tar will throw an error.'
printf "\n"
textblock "${magenta}Example:${norm}"
textblock "openssl enc -d -${cyan}aes-256-cbc${norm} -iter +${cyan}25000${norm} -k ${cyan}mypassword${norm} -in ${cyan}~/ldap-2020-03-10_17:31:34.tar.gz${norm} | tar -xz -C ${cyan}~/ldapRestore${norm}"
printf "\n"
textblock 'Note: Using this script with the --decrypt option handles this for you and can create target directories automatically.'
printf "\n\n"
exit 0
}
exitError () {
# cleanup temp directory unless 'nc' passed
if [ ! "$3" = "nc" ]; then cleanup; fi
# log error
printf "%s[%s] -- [ERROR] code %s: %s --%s\n" \
"$err" "$(stamp)" "$1" "$2" "$norm" >> "$logfile"
printf "%s[%s] --- %s terminated with errors ---\n%s" \
"$err" "$(stamp)" "$scriptName" "$norm" >> "$logfile"
# exit with proper error code
exit "$1"
}
logInfo () {
if [ "$1" = 'task' ]; then
printf "%s[%s] -- [INFO] %s... " \
"$info" "$(stamp)" "$2" >> "$logfile"
elif [ "$1" = 'done' ]; then
if [ -z "$2" ]; then
printf "%sdone%s --%s\n" \
"$ok" "$info" "$norm" >> "$logfile"
else
printf "%s%s%s --%s\n" \
"$ok" "$2" "$info" "$norm" >> "$logfile"
fi
elif [ "$1" = 'err' ]; then
printf "%serror%s --%s\n" \
"$err" "$info" "$norm" >> "$logfile"
else
printf "%s[%s] -- [INFO] %s --%s\n" \
"$info" "$(stamp)" "$1" "$norm" >> "$logfile"
fi
}
logSuccess () {
printf "%s[%s] -- [SUCCESS] %s --%s\n" \
"$ok" "$(stamp)" "$1" "$norm" >> "$logfile"
}
scriptHelp () {
printf "\n"
textblock "${bold}${magenta}Usage: ${scriptName} [parameters]${norm}"
printf "\n"
textblock "${cyan}Parameters ${yellow}(default value):${norm}"
textblock "There are NO mandatory parameters. By default the script will run in 'backup' mode and save an encrypted backup archive to the current directory. If a parameter is not supplied, it's default value will be used. In the case of a switch parameter, it will remain deactivated if not specified."
printf "\n"
textblock "${bold}*** Common parameters ***${norm}"
printf "\n"
textblock "${cyan}-l, --log ${yellow}(scriptPath/scriptName.log)${norm}"
textblock "FULL path to write log file. If you supply a path ending with a slash ('/') it will be assumed you mean a directory and the log file will be written to that directory using the format 'path/scriptname.log'. If you supply only a filename (no slashes anywhere), it will assumed you want to save the log using that name in the script directory. The script will attempt to create any provided paths/directories if they do not exist."
printf "\n"
textblock "${cyan}-o, --output ${yellow}(scriptPath/)${norm}"
textblock "Location where the output files should be saved on this machine. You should only specify a *directory* here (trailing slash optional). File names are automatic and cannot be changed via this script. All restore operations will create a 'restore' subdirectory in this specified directory."
printf "\n"
textblock "${cyan}-p, --params ${yellow}(scriptPath/scriptName.params)${norm}"
textblock "Location of the encryption parameters file. This file contains the password and encryption options that should be used. By default, the script looks in the script directory for a file named the same as the script but with a '.params' extension."
printf "\n"
textblock "${cyan}-h, -? | -??, --help${norm}"
textblock "Quick-help screen. | This (detailed) help screen."
printf "\n"
textblock "${bold}*** Encryption parameters ***${norm}"
printf "\n"
textblock "${cyan}-n, --num, --frontend ${yellow}(1)${norm}"
textblock "Number of frontend databases to backup. If you specify a number here greater than actually exist, openLDAP will generate an error and your backup will fail."
printf "\n"
textblock "${cyan}--no-encryption${norm}"
textblock "Switch parameter. Specify this if you DO NOT want the backup archive tar.gz file encrypted. Careful!"
printf "\n"
textblock "${bold}*** Decryption/Extraction parameters ***${norm}"
printf "\n"
textblock "${cyan}-b, --backupfile${norm}"
textblock "FULL path of the backup file you want to decrypt/extract. MUST be supplied if using the --decrypt or --extract switches."
printf "\n"
textblock "${cyan}-d, --decrypt ${yellow}(implies --extract)${norm}"
textblock "Switch parameter. Decrypt and extract your backup file (MUST be supplied using the --backupfile parameter) to the 'restore' subdirectory of the current directory or as specified using the '--output' parameter. This switch will log error code 8 if you try to decrypt an unencrypted backup archive."
printf "\n"
textblock "${cyan}-e, --extract${norm}"
textblock "Switch parameter. Extract your backup file (MUST be supplied using the --backupfile parameter) to the 'restore' subdirectory of the current directory or as specified using the '--output' parameter. This switch will log error code 8 if you try to extract an encrypted backup archive. In that case, use --decrypt instead."
printf "\n"
textblock "${cyan}--manualdecryption${norm}"
textblock "Display instructions on how to manually decrypt and extract your backup archive."
printf "\n\n"
textblock "More details and examples of script usage can be found in the repo wiki at ${magenta}https://git.asifbacchus.app/ab-docker/openldap/wiki${norm}"
printf "\n"
}
stamp () {
(date +%F' '%T)
}
textblock () {
printf "%s\n" "$1" | fold -w "$width" -s
}
quickHelp () {
textblock "${bold}${magenta}Usage: ${scriptName} [parameters]${norm}"
printf "\n"
textblock "${cyan}Parameters ${yellow}(default value):${norm}"
printf "\n"
textblock "${bold}*** Common parameters ***${norm}"
textblock "${cyan}-l, --log ${yellow}(scriptPath/scriptName.log)${norm}"
textblock "FULL path to write log file. Path ending with '/' will be treated as a directory. Filename only will be saved in script directory."
textblock "${cyan}-o, --output ${yellow}(scriptPath/)${norm}"
textblock "Directory to save output files on this machine."
textblock "${cyan}-p, --params ${yellow}(scriptPath/scriptName.params)${norm}"
textblock "Location of the encryption parameters file."
textblock "${cyan}-h, -? | -??, --help${norm}"
textblock "This help screen. | Detailed help."
printf "\n"
textblock "${bold}*** Encryption parameters ***${norm}"
textblock "${cyan}-n, --num, --frontend ${yellow}(1)${norm}"
textblock "Number of frontend databases to backup."
textblock "${cyan}--no-encryption${norm}"
textblock "Switch: DO NOT encrypt backup tar.gz."
printf "\n"
textblock "${bold}*** Decryption/Extraction parameters ***${norm}"
textblock "${cyan}-b, --backupfile${norm}"
textblock "FULL path of the backup file to be decrypted/extracted."
textblock "${cyan}-d, --decrypt ${yellow}(implies --extract)${norm}"
textblock "Decrypt and extract specified backup file to 'restore' subdirectory of current path/specified path (--output parameter)."
textblock "${cyan}-e, --extract${norm}"
textblock "Extract specified backup file to 'restore' subdirectory of current path/specified path (--output parameter)."
textblock "${cyan}--manualdecryption${norm}"
textblock "Display instructions on manually decrypting and extracting your backup archive."
printf "\n"
textblock "Run script with '-?? or --help' for detailed help. More details and examples of script usage can be found in the repo wiki at:"
printf "%s\thttps://git.asifbacchus.app/ab-docker/openldap/wiki%s\n\n" \
"$magenta" "$norm"
}
### pre-requisite checks
# is user root or in the docker group?
if [ ! "$( id -u )" -eq 0 ]; then
if ! id -Gn | grep docker > /dev/null; then
consoleError '1' "You must either be root or in the 'docker' group to run this script since you must be able to run 'docker exec' commands against the container!"
fi
fi
# is tar installed?
if ! command -v tar > /dev/null; then
consoleError '99' 'It appears that tar is not installed. This script requires tar in order to package and process backup files.'
fi
# is gzip installed?
if ! command -v gzip > /dev/null ; then
consoleError '99' 'It appears that gzip is not installed. This script requires gzip in order to compress/decompress backup tarball files.'
fi
# process startup parameters
while [ $# -gt 0 ]; do
case "$1" in
-b|--backupfile)
# name of backup file to decrypt/extract
if [ -z "$2" ]; then
consoleError '1' 'Backup file name cannot be null.'
elif [ ! -f "$2" ]; then
consoleError '1' "Specified backup file does not exist ($2)."
fi
backupFile="$2"
shift
;;
-d|--decrypt)
# decrypt and extract backup file (implies '-e')
decrypt=true
extract=true
;;
-e|--extract)
# extract gzipped tarball
extract=true
;;
-h|-\?)
# display brief help
quickHelp
exit 0
;;
-\?\?|--help)
# display full help
scriptHelp
exit 0
;;
-l|--log)
# location of log file
if [ -z "$2" ]; then
consoleError '1' 'Log file path cannot be null. Leave unspecified to save log in the same directory as this script.'
fi
logfile="$2"
shift
;;
--manualdecryption)
decryptionNote
exit 0
;;
-n|--num|--frontend)
# number of frontend databases
if [ -z "$2" ]; then
consoleError '1' 'Number of frontend databases cannot be null.'
fi
numFrontEnd=$(($2+1))
shift
;;
--no-encryption)
# disable encryption, switch parameter
noEncryption=1
# switches do not require an internal 'shift'
;;
-o|--output)
# directory on host to save output files
if [ -z "$2" ]; then
consoleError '1' 'Output destination location cannot be null.'
fi
outputLocation="$2"
shift
;;
-p|--params)
# path to encryption parameters file
if [ -z "$2" ]; then
consoleError '1' 'Path to encryption parameters file cannot be null.'
fi
encParams="$2"
shift
;;
*)
printf "%s\nUnknown option: %s\n" "$err" "$1"
printf "Use '--help' for valid options.\n\n%s" "$norm"
exit 1
;;
esac
shift
done
### start logging
# logfile checks
if ! printf "%s" "$logfile" | grep -o / > /dev/null; then
# filename provided, save in scriptdir
logfile="$scriptPath/$logfile"
elif [ "$( printf "%s" "$logfile" | tail -c 1 )" = '/' ]; then
# directory provided, does it exist?
if [ -d "$logfile" ]; then
logfile="${logfile}${scriptName%.*}.log"
else
if ! mkdir -p "$logfile" > /dev/null 2>&1; then
consoleError '1' 'Unable to make specified log file directory.'
fi
logfile="${logfile}${scriptName%.*}.log"
fi
else
# full path provided, does the parent directory exist?
if [ ! -d "${logfile%/*}" ]; then
# make parent path
if ! mkdir -p "${logfile%/*}" > /dev/null 2>&1; then
consoleError '1' 'Unable to make specified log file path.'
fi
fi
fi
# write initial log entries for script run
if ! printf "%s[%s] --- Start %s execution ---%s\n" \
"$magenta" "$(stamp)" "$scriptName" "$norm" >> "$logfile"; then
consoleError '1' "Unable to write to log file ($logfile)"
fi
logInfo "Log located at $logfile"
### process output directory
if [ -d "$outputLocation" ]; then
# touch test to ensure we can write here
if ! touch "${outputLocation%/}/test.touch" > /dev/null 2>&1; then
exitError '1' "Unable to write to output location: $outputLocation" 'nc'
else
logInfo "Writing output to: $outputLocation"
rm -f "${outputLocation%/}/test.touch" > /dev/null 2>&1
fi
else
# create directory
if ! mkdir -p "$outputLocation" > /dev/null 2>&1; then
exitError '1' "Unable to create output path: $outputLocation" 'nc'
else
logInfo "Writing output to: $outputLocation"
fi
fi
### source encryption parameters file
# check if file exists (unless encryption disabled), otherwise exit
if [ $noEncryption = 1 ]; then
logInfo 'Backup archive will NOT be encrypted!'
elif [ $noEncryption = 0 ] && [ ! -f "$encParams" ]; then
exitError 1 'Encryption parameters file does not exist.' 'nc'
else
# import encryption parameters file
case "${encParams}" in
/*)
# absolute path, no need to rewrite
. "${encParams}"
;;
*)
# relative path, need to rewrite assuming current directory
. "./${encParams}"
;;
esac
logInfo "Imported: '$encParams'"
# verify import
logInfo 'task' 'Verify encryption password'
if [ -z "$password" ]; then
logInfo 'err'
exitError 1 "Imported null value for encryption 'password'." 'nc'
else
logInfo 'done'
fi
logInfo 'task' 'Verify encryption cipher'
if [ -z "$encryptionCipher" ]; then
logInfo 'err'
exitError 1 "Imported null value for 'encryptionCipher'." 'nc'
else
logInfo 'done' "$encryptionCipher"
fi
logInfo 'task' 'Verify encryption key iterations'
if [ -z "$encryptionIterations" ]; then
logInfo 'err'
exitError 1 "Imported null value for 'encryptionIterations'." 'nc'
else
logInfo 'done' "$encryptionIterations"
fi
fi
if [ $extract = 'true' ]; then
# ensure backupFile has been specifed
if [ -z "$backupFile" ]; then
exitError '1' 'backupFile (-b|--backupFile) must be specified when using script in Extract mode.' 'nc'
fi
# extract backupFile to outputLocation
logInfo "Extracting backup file ($backupFile)"
# create extraction target directory
if [ ! -d "${outputLocation%/}/restore" ]; then
# create subdirectory for restored files
if ! mkdir "${outputLocation%/}/restore" > /dev/null 2>&1; then
exitError '7' 'Could not create target subdirectory in output location.' 'nc'
else
logInfo "Extracted files will be written to '${outputLocation%/}/restore/'"
fi
else
# ensure we can write to subdirectory for restored files
if ! touch "${outputLocation%/}/restore/test.touch" > /dev/null 2>&1; then
exitError '1' "Unable to write to output location: ${outputLocation%/}/restore/" 'nc'
else
rm -f "${outputLocation%/}/restore/test.touch" > /dev/null 2>&1
fi
fi
# extract/decrypt backup file
if [ "$decrypt" = 'true' ]; then
logInfo 'Decrypting backup file before extraction'
if ! openssl enc -d -"${encryptionCipher}" -iter +"${encryptionIterations}" -k "${password}" -in "${backupFile}" 2>> "$logfile" | tar --overwrite -xz -C "${outputLocation%/}/restore/" 2>> "$logfile"; then
exitError '8' 'There was a problem extracting the backup file. Perhaps the file is NOT encrypted? If so, please run with the --extract flag instead of the --decrypt flag.' 'nc'
else
logSuccess "Backup extracted to '${outputLocation%/}/restore/'"
fi
else
if ! tar --overwrite -xzf "$backupFile" -C "${outputLocation%/}/restore/" 2>> "$logfile"; then
exitError '8' 'There was a problem extracting the backup file. Perhaps it is encrypted? If so, please run with --decrypt flag.' 'nc'
else
logSuccess "Backup extracted to '${outputLocation%/}/restore/'"
fi
fi
elif [ $extract = 'false' ]; then
### process backup operations
## find ab-openldap container
container=$(docker ps -a --no-trunc --filter "label=org.label-schema.name=ab-openldap" --format "{{ .Names }}")
# check for null value -- cannot find container
if [ -z "$container" ]; then
exitError 2 'Cannot find ab-openldap container. Exiting.' 'nc'
fi
# check for multiple containers, exit if that's the case
# N.B. do NOT quote $container or this loop will NOT work!!!
set -- dummy $container
shift
containerCount=0
for c; do
containerCount=$((containerCount+1))
logInfo "Found container($containerCount): $c"
done
if [ "$containerCount" -gt 1 ]; then
exitError 2 'Multiple containers found. Exiting.' 'nc'
fi
## backup databases to ldif
# create temp working directory
if ! docker exec "$container" mkdir "/$tempDir" >> "$logfile" 2>&1; then
exitError 3 'Could not create temporary working directory.' 'nc'
fi
# backend to ldif
logInfo 'task' 'Backup configuration database'
if ! docker exec "$container" sh -c \
"slapcat -F /etc/openldap/ldif -n 0 -l ${tempDir}/config-${fileDate}.ldif" \
>> "$logfile" 2>&1; then
logInfo 'err'
exitError 4 'Could not backup configuration database.'
fi
logInfo 'done'
# iterate frontend databases and export to ldif
i=1
while [ "$i" -ne "$numFrontEnd" ]; do
logInfo 'task' "Backup frontend database $i"
if ! docker exec "$container" sh -c \
"slapcat -F /etc/openldap/ldif -n ${i} -l ${tempDir}/mdb${i}-${fileDate}.ldif" \
>> "$logfile" 2>&1; then
logInfo 'err'
exitError 4 "Could not backup frontend database $i."
else
logInfo 'done'
fi
i=$((i+1))
done
# compress and encrypt exported ldif files
if [ $noEncryption = 0 ]; then
logInfo 'task' 'Compressing and encrypting backup'
if ! docker exec -w "/$tempDir" "$container" sh -c \
"tar -czf - * | openssl enc -e -${encryptionCipher} -iter +${encryptionIterations} -k ${password} -out ./ldap-${fileDate}.tar.gz" \
>> "$logfile" 2>&1; then
logInfo 'err'
exitError 5 'Could not securely archive backup files.'
fi
logInfo 'done'
elif [ $noEncryption = 1 ]; then
logInfo 'task' 'Compressing backup'
if ! docker exec -w "/$tempDir" "$container" sh -c \
"tar -czf ldap-${fileDate}.tar.gz *" \
>> "$logfile" 2>&1; then
logInfo 'err'
exitError 5 'Could not archive backup files.'
fi
logInfo 'done'
fi
## copy file to output location on host
logInfo 'task' 'Copying archive from docker container to host'
if ! docker cp "$container:/$tempDir/ldap-${fileDate}.tar.gz" "$outputLocation/" \
>> "$logfile" 2>&1; then
logInfo 'err'
exitError 6 'Unable to copy backup archive from container to host.'
fi
logInfo 'done'
logInfo "Backup file: $outputLocation/ldap-${fileDate}.tar.gz"
## cleanup and log success
cleanup
logSuccess 'Backup completed successfully.'
fi
### exit gracefully
logSuccess 'All processes completed'
printf "%s[%s] --- %s execution completed ---\n%s" \
"$magenta" "$(stamp)" "$scriptName" "$norm" >> "$logfile"
exit 0