#!/bin/sh # # start openldap container using params file variables # version 4.0 # ### functions consoleError () { printf "%s\n%s\n" "$err" "$2" printf "Exiting.\n\n%s" "$norm" exit "$1" } textblock () { printf "%s\n" "$1" | fold -w "$width" -s } prompt_yn () { # confirmation loop while true; do printf "%sAre you sure you want to continue? (y/n)%s " \ "$cyan" "$norm" read -r yn case "$yn" in [Yy]*) break ;; [Nn]*) printf "\n" exit 0 ;; *) printf "%sPlease answer 'y' or 'n'.%s\n" "$err" "$norm" ;; esac done } # text formatting presets bold=$(tput bold) cyan=$(tput setaf 6) err=$(tput bold)$(tput setaf 1) magenta=$(tput setaf 5) norm=$(tput sgr0) red=$(tput setaf 1) yellow=$(tput setaf 3) width=$(tput cols) ### parameter defaults scriptName="$( basename "$0" )" 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" textblock "${bold}Usage: $scriptName [parameters]${norm}" printf "\n" textblock "This is a simple helper script so you can avoid lengthy typing when working with the openLDAP container. The script reads the contents of '${scriptName%.*}.params' and constructs various 'docker run' commands based on that file. The biggest timesaver is working with certificates. If they are specified in the '.params' file, the script will automatically bind-mount them so openLDAP starts in 'TLS required' mode." printf "\n" textblock "If you run the script with no parameters, it will execute the container 'normally'. That is: Run in detached mode with openLDAP automatically launched and logging to stdout. If you specified certificates, openLDAP will require a TLS connection. All modes of operation allow you to enter the container and connect directly using UNIX sockets as root with *unrestricted* access to all DITs and objects." printf "\n" textblock "Specifying the '--mailserver mail.server.tld' option, tells openLDAP to verify SASL passwords in the directory against an IMAP/S remote host. In other words, any user with a password '{SASL}user@server.tld' will have their password checked by the IMAP/S server using the provided email address and a 'NO/OK' reponse is fed back to openLDAP. Please note 2 things: You *must* provide the hostname of a mailserver after the '--mailserver' switch; and the remote server *must* implement IMAP/S. More details can be found in the wiki." printf "\n" textblock "Containers run in SHELL mode are ALWAYS removed upon exit as they are meant for testing only. By default, containers run without '--rm' will be restarted automatically unless they are manually stopped via 'docker stop...'" printf "\n" textblock "${magenta}The script has the following parameters:${norm}" textblock "${cyan}(parameter in cyan) ${yellow}(default in yellow)${norm}" printf "\n" textblock "${cyan}t|--tag ${yellow}(latest)${norm}" textblock "Change the version of the container downloaded by specifying a particular tag. This can be useful when testing new versions or if you have to roll back to a previous container version." printf "\n" textblock "${cyan}-n|--name ${yellow}(ab-openldap)${norm}" textblock "Change the name of the container. This is cosmetic and does not affect operation in any way." printf "\n" textblock "${cyan}--data ${yellow}(ab-openldap_data)${norm}" textblock "Change the name of the docker volume used to persist data." printf "\n" textblock "${cyan}--ldif ${yellow}(ab-openldap_ldif)${norm}" textblock "Change the name of the docker volume used to persist LDIFs." printf "\n" textblock "${cyan}--mailserver ${magenta}mail.server.tld${norm}" textblock "Verify SASL passwords against 'mail.server.tld'. Replace with proper mailserver hostname. Mailserver MUST support IMAP/S." printf "\n" textblock "${cyan}--mailserver-port ${yellow}(imaps)${norm}" textblock "If your IMAP/S server uses a non-standard IMAP/S port (not 993) then specify it here. This parameter is ignored if not using '--mailserver'." printf "\n" textblock "${cyan}--rm|--remove${norm}" textblock "Switch parameter. Automatically remove the container and associated volumes (unless data is written) after it exits." printf "\n" textblock "${cyan}-s|--shell${norm}" textblock "Switch parameter. Enter the container using an interactive POSIX shell. This happens after startup operations but *before* openLDAP (slapd) is started. This is a great way to test out configuration changes or run custom queries. You can combine this with '--rm' for easy configuration checks or LDIF imports." printf "\n" textblock "${cyan}--clean${norm}" textblock "Switch parameter. This option will stop and remove ALL running openLDAP containers *AND DESTROY ALL VOLUMES*. This is meant to give you a 'clean start' if you've made configuration changes, etc." printf "\n" textblock "${cyan}--restore${norm}" textblock "Switch parameter. Restore a 'slapcat' backup to the data and ldif volumes in preparation for mounting them in a normal container. It is strongly recommended you review your '-t' '--data' and '--ldif' settings before proceeding with this option." printf "\n" textblock "${cyan}--backupdir ${yellow}(./restore)${norm}" textblock "Location of the 'slapcat' backup files to restore." printf "\n\n" textblock "More information about this script and the ab-openLDAP container can be found at ${magenta}https://git.asifbacchus.app/ab-docker/openldap/wiki${norm}" printf "\n\n" exit 0 } ### 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 '2' "You must either be root or in the 'docker' group to run this script since you must be able to actually start the container!" fi fi # does the params file exist? if [ ! -f "${scriptName%.*}.params" ]; then consoleError '3' "Cannot find '${scriptName%.*}.params' file in the same directory as this script." exit 3 fi # read .params file . ./${scriptName%.*}.params # process startup parameters while [ $# -gt 0 ]; do case "$1" in -h|-\?|--help) # display help scriptHelp exit 0 ;; --rm|--remove) # remove container on exit remove=1 ;; -s|--shell) # start shell instead of default CMD shell=true ;; --clean) # stop if necessary, delete volumes clean=true ;; --restore) # restore backup restore=true ;; -n|--name) # container name if [ -z "$2" ]; then consoleError '1' 'No container name specified.' fi container_name="$2" shift ;; --data) # data volume name if [ -z "$2" ]; then consoleError '1' 'No name specified for data volume.' fi volume_data="$2" shift ;; --ldif) # ldif volume name if [ -z "$2" ]; then consoleError '1' 'No name specified for LDIF volume.' fi volume_ldif="$2" shift ;; --backupdir) # location of backup files to restore if [ -z "$2" ]; then consoleError '1' 'Location of your backup files not provided.' fi backup_dir="$2" shift ;; -t|--tag) # specify container tag if [ -z "$2" ]; then consoleError '1' 'No tag specified.' fi tag="$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 ### process main operations if [ $clean = true ]; then # cleanup containers and volumes # 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" prompt_yn # 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 consoleError '0' 'No openldap containers to remove.' fi # iterate containers, stop them and remove straggling volumes set -- dummy $containers shift for container; do 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 # find volumes volumes=$(docker inspect --format '{{ range .Mounts }}{{ println .Name }}{{ end }}' ${container}) # remove container printf "\t%sRemoving container...%s\n" "$red" "$norm" docker rm -f ${container} > /dev/null 2>&1 # 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 -f ${volume} > /dev/null 2>&1 done printf "%s...done%s\n" "$cyan" "$norm" done elif [ $restore = true ]; then # automatically restore backups using a temp container to create volumes 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 (based on --data and --ldif):\n" printf "\t%s\n\t%s\n" "$volume_data" "$volume_ldif" prompt_yn # delete any conflicting volumes docker volume rm -f ${volume_data} > /dev/null 2>&1 docker volume rm -f ${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" elif [ -z "$TLS_CERT" ]; then # run container without TLS if [ $shell = true ]; then # exec shell 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:${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:${tag} /bin/sh fi else # exec normally 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:${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:${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:${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:${tag} fi fi fi elif [ "$TLS_CERT" ] && [ "$TLS_KEY" ] && [ "$TLS_CHAIN" ]; then # run container with TLS # verify certificate files exist if [ "$TLS_CERT" ]; then if [ ! -f "$TLS_CERT" ]; then consoleError '5' 'Cannot find specified TLS certificate file.' fi if [ ! -f "$TLS_KEY" ]; then consoleError '5' 'Cannot find specified TLS private key file.' fi if [ ! -f "$TLS_CHAIN" ]; then consoleError '5' 'Cannot find specified TLS certificate chain file.' fi fi if [ $shell = true ]; then # exec shell 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:${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:${tag} /bin/sh fi else # exec normally 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:${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:${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:${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:${tag} fi fi fi fi ### exit gracefully exit 0 # error code reference: # 0: exited normally, no errors # 1: unknown startup option passed to script # 2: current user is unauthorized to operate docker # 3: 'params' file not found in same directory as script # 5: specified TLS-related files (cert, key or chain) not found #EOF