#!/bin/sh

#
# start ab-openldap container using params file variables
# version 6.0.0
# Script by Asif Bacchus
#

### 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
myVersion="6.0.0"
scriptName="$(basename "$0")"
scriptPath="$(CDPATH='' \cd -- "$(dirname -- "$0")" && pwd -P)"
clean=false
restore=false
container_name="ab-openldap"
volume_data="ab-openldap_data"
volume_ldif="ab-openldap_ldif"
loglevel="stats"
backup_dir="$(pwd)/restore"
remove=0
shell=false
tag=latest
doKill=0
noPrompt=0

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 time saver 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 "If you want to verify SASL passwords against an IMAP/S server, please refer to the '.params' template file and the wiki for more information."
  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}-l|--log|--loglevel ${yellow}(\"stats\")${norm}"
  textBlock "Set openLDAP logging to this level. Overrides .params file value and container default value. Common options include 'stats', 'stats,acl'. Refer to openLDAP documentation for all options."
  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}--kill|--stop${norm}"
  textBlock "Switch parameter. Stop and remove the running container. If '--name' is specified its value will be used to identify the container."
  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"
  textBlock "${cyan}-y | --no-prompt${norm}"
  textBlock "Switch parameter. Skip confirmation prompts. Useful if running in cron/scripted environment."
  printf "\n"
  textBlock "${cyan}-v | --version${norm}"
  textBlock "Display version information for this script and exit."
  printf "\n"
  textBlock "${cyan}-h | -? | --help${norm}"
  textBlock "Display this help text and exit."
  printf "\n\n"
  textBlock "More information about this script and the ab-openLDAP container can be found at ${magenta}https://git.asifbacchus.dev/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 "${scriptPath}/${scriptName%.*}.params" ]; then
  consoleError '3' "Cannot find '${scriptName%.*}.params' file in the same directory as this script."
  exit 3
fi

# read .params file
# shellcheck source=ab-openldap.params.template
. "${scriptPath}/${scriptName%.*}.params"

# process startup parameters
while [ $# -gt 0 ]; do
  case "$1" in
  -h | -\? | --help)
    # display help
    scriptHelp
    exit 0
    ;;
  -v | --version)
    # display version
    printf "%s\nHelper-script version: %s\n\n%s" "$magenta" "$myVersion" "$norm"
    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
    ;;
  -l | --log | --loglevel)
    # set container logging level
    if [ -z "$2" ]; then
      consoleError '1' 'No loglevel specified.'
    fi
    loglevel="$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
    ;;
  --kill | --stop)
    # stop and remove running container
    doKill=1
    ;;
  -y | --no-prompt)
    # skip prompts
    noPrompt=1
    ;;
  *)
    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 [ "$doKill" = 1 ]; then
  if [ "$noPrompt" = 0 ]; then
    # display warning to confirm
    printf "\nThis will stop and remove the running container (%s). Are you sure?\n" "$container_name"
    prompt_yn
  fi
  # stop and remove container
  if ! docker stop "$container_name"; then
    consoleError '11' 'Unable to stop container, please try stopping it manually.'
  fi
  if ! docker rm "$container_name"; then
    consoleError '12' 'Container stopped but unable to remove it. Please try removing it manually.'
  fi
elif [ "$clean" = "true" ]; then
  # cleanup containers and volumes
  if [ "$noPrompt" = 0 ]; 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"
    prompt_yn
  fi

  # get all ab-openldap containers
  containers=$(docker ps -a --no-trunc --filter "label=dev.asifbacchus.docker.internalName=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 (do NOT quote or loop will fail!)
  # shellcheck disable=SC2086
  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 (do NOT quote or loop will fail)
    # shellcheck disable=SC2086
    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"
  if [ "$noPrompt" = 0 ]; then
    prompt_yn
  fi

  # 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.dev/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 "${scriptPath}/${scriptName%.*}.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.dev/ldap/ab-openldap:"${tag}" /bin/sh
    else
      docker run --rm -it --name "${container_name}" \
        --env-file "${scriptPath}/${scriptName%.*}.params" \
        -v "$volume_data":/var/openldap/data \
        -v "$volume_ldif":/etc/openldap/ldif \
        -p 389:389 -p 636:636 \
        docker.asifbacchus.dev/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 "${scriptPath}/${scriptName%.*}.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 \
          -e LOGLEVEL="$loglevel" \
          docker.asifbacchus.dev/ldap/ab-openldap:"${tag}"
      else
        docker run --rm -d --name "${container_name}" \
          --env-file "${scriptPath}/${scriptName%.*}.params" \
          -v "$volume_data":/var/openldap/data \
          -v "$volume_ldif":/etc/openldap/ldif \
          -p 389:389 -p 636:636 \
          -e LOGLEVEL="$loglevel" \
          docker.asifbacchus.dev/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 "${scriptPath}/${scriptName%.*}.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 \
          -e LOGLEVEL="$loglevel" \
          --restart always \
          docker.asifbacchus.dev/ldap/ab-openldap:"${tag}"
      else
        docker run -d --name "${container_name}" \
          --env-file "${scriptPath}/${scriptName%.*}.params" \
          -v "$volume_data":/var/openldap/data \
          -v "$volume_ldif":/etc/openldap/ldif \
          -p 389:389 -p 636:636 \
          -e LOGLEVEL="$loglevel" \
          --restart always \
          docker.asifbacchus.dev/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 "${scriptPath}/${scriptName%.*}.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.dev/ldap/ab-openldap:"${tag}" /bin/sh
    else
      docker run --rm -it --name "${container_name}" \
        --env-file "${scriptPath}/${scriptName%.*}.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.dev/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 "${scriptPath}/${scriptName%.*}.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 \
          -e LOGLEVEL="$loglevel" \
          docker.asifbacchus.dev/ldap/ab-openldap:"${tag}"
      else
        docker run --rm -d --name "${container_name}" \
          --env-file "${scriptPath}/${scriptName%.*}.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 \
          -e LOGLEVEL="$loglevel" \
          docker.asifbacchus.dev/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 "${scriptPath}/${scriptName%.*}.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 \
          -e LOGLEVEL="$loglevel" \
          --restart always \
          docker.asifbacchus.dev/ldap/ab-openldap:"${tag}"
      else
        docker run -d --name "${container_name}" \
          --env-file "${scriptPath}/${scriptName%.*}.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 \
          -e LOGLEVEL="$loglevel" \
          --restart always \
          docker.asifbacchus.dev/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
# 11: cannot stop container
# 12: cannot remove container

#EOF
