#!/bin/bash
#==============================================================================
# Copyright and license info is available in the LICENSE file included with
# the Server Deployment Package (SDP), and also available online:
# https://swarm.workshop.perforce.com/projects/perforce-software-sdp/view/main/LICENSE
#------------------------------------------------------------------------------
set -u

#==============================================================================
# Declarations and Environment

declare ThisScript="${0##*/}"
declare ThisUser=
declare Version=4.4.1
declare SDPInstance=UnsetSDPInstance
declare SDPInstallRoot="/p4"
declare SDPMountPointBase=""
declare ConfigFile="${0%/*}/mkdirs.cfg"
declare -i NoOp=0
declare -i Debug=0
declare -i PreflightOnly=0
declare -i TestMode=0
declare -i TestCleanup=0
declare -i DoChownCommands=1
declare -i ErrorCount=0
declare -i WarningCount=0
declare SDPVersionString=
declare SDPVersionFile=
declare SDPVersion=
declare OverrideDD=
declare OverrideLG=
declare OverrideDB1=
declare OverrideDB2=
declare TarRoot=
declare TarRootCommon=
declare TarRootConfig=
declare TarRootCron=
declare TarRootBinDir=
declare TarRootInit=
declare TarRootSSL=
declare ServerID=
declare TargetServerID=
declare ServerType=
declare -i ServerIDSet=0
declare -i FastMode=0
declare Log=

#==============================================================================
# Local Functions
function msg () { echo -e "$*"; }
function dbg () { [[ "$Debug" -eq 1 ]] && msg "DEBUG: $*"; }
function errmsg () { msg "\\nError: (line: ${BASH_LINENO[0]}) $*" >&2; ErrorCount+=1; }
function warnmsg () { msg "\\nWarning: (line: ${BASH_LINENO[0]}) $*"; WarningCount+=1; }
function bail() { errmsg "(line: ${BASH_LINENO[0]}) ${1:-Unknown Error}\\n" >&2; exit "${2:-1}"; }

#------------------------------------------------------------------------------
# This function takes as input an SDP version string, and returns a version
# id of the form YYYY.N.CL, where YYYY is the year, N is an incrementing
# release ID with a given year, and CL is a changelist identifier. The
# YYYY.N together comprise the major version, often shortened to YY.N, e.g.
# r20.1 for the 2020.1 release.
#
# The full SDP Version string looks something like this:
# Rev. SDP/MultiArch/2019.3/26494 (2020/04/23).
#
# This function parses that full string and returns a value like: 2019.3.26494
function get_sdp_version_from_string () {
   local versionString="${1:-}"
   local version=
   version="20${versionString##*/20}"
   version="${version%% *}"
   version="${version/\//.}"
   echo "$version"
}

#------------------------------------------------------------------------------
# Function: run ($cmd, $desc, $honorNoOp)
#
# Input:
#
# $cmd  - Command to execute, including arguments.?
#
# $desc - Description of command to run. Optional; default is no description.
#
# $honorNoOp - If set to 1, command is always executed; NoOp setting is ignored.
#    Optional, default is 0.
#------------------------------------------------------------------------------
function run () { 
   declare cmd="${1:-echo}"
   declare desc="${2:-}"
   declare honorNoOp="${3:-1}"

   [[ -n "$desc" ]] && msg "$desc"
   msg "Running: $cmd"

   if [[ "$NoOp" -eq 1 && "$honorNoOp" -eq 1 ]]; then
      msg "NO_OP: Would run $cmd"
      return 0
   else
      if $cmd; then
         return 0
      else
         return 1
      fi
   fi
}

#------------------------------------------------------------------------------
# Function: usage (required function)
#
# Input:
# $1 - style, either -h (for short form) or -man (for man-page like format).
# The default is -h.
#
# $2 - error message (optional).  Specify this if usage() is called due to
# user error, in which case the given message displayed first, followed by the
# standard usage message (short or long depending on $1).  If displaying an
# errror, usually $1 should be -h so that the longer usage message doesn't
# obsure the error message.
#
# Sample Usage:
# usage
# usage -h
# usage -man
# usage -h "Incorrect command line usage."
#------------------------------------------------------------------------------
function usage
{
   declare style=${1:--h}
   declare usageErrorMessage=${2:-Unset}

   if [[ "$usageErrorMessage" != Unset ]]; then
      echo -e "\\n\\nUsage Error:\\n\\n$usageErrorMessage\\n\\n"
   fi

   # tag::includeManual[]
   echo "USAGE for $ThisScript v$Version:

$ThisScript <instance> [-s <ServerID>] [-t <server_type>] [-MDD /bigdisk] [-MLG /jnl] [-MDB1 /db1] [-MDB2 /db2] [-f] [-p] [-test [-clean]] [-n] [-L <log>] [-d|-D]

or

$ThisScript [-h|-man]
"
   if [[ $style == -man ]]; then
      echo -e "
DESCRIPTION:

This script initializes an SDP instance on a single machine.

This script is intended to support two scenarios:

* First time SDP installation on a given machine.
* Adding new SDP instances (separate Helix Core data sets) to an existing
  SDP installation on a given machine.

And SDP instance is a single Helix Core data set, with its own unique
set of one set of users, changelist numbers, jobs, labels, versioned
files, etc. An organization may run a single instance or multiple
instances.

This is intended to be run either as root or as the operating system
user account (OSUSER) that p4d is configured to run as, typically
'perforce'.  It should be run as root for the initial install.
Subsequent additions of new instances do not require root.

If an initial install as done by a user other than root, various
directories must exist and be writable and owned by 'perforce' before starting:

* /p4
* /hxdepots
* /hxlogs
* /hxmetadata

This script creates an init script in the /p4/N/bin directory.

After running this script, set up the crontab based on templates
generated in /p4/common/etc/cron.d.  For convenience, a sample cronat
is generated for the current machine in /p4/common/etc/cron.d named

crontab.<osuser>.<host>

where <osuser> is the user that services run as (typically 'perforce'),
and <host> is the short hostname (as returned by a 'hostname -s' command).


Next, put the license file in place in the P4ROOT dir, and launch the server
with the init script.

Then run /p4/common/bin/p4master_run instance /p4/common/bin/live_checkpoint.sh
and then run both the daily_checkpoint.sh and recreate_db_checkpoint.sh to
make sure everything is working before setting up the crontab.

Also run /p4/common/bin/p4master_run <instance> /p4/common/bin/p4review.py <instance>
to make sure the review script is working properly.  If you intend to use
Swarm, you can skip configuration of the review daemon, and instead configure
Swarm to handle review-style email notifications.

REQUIRED PARAMETERS:
 <instance>
	Specify the SDP instance name to add.  This is a reference to the Perforce
	Helix Core data set.

OPTIONS:
 -s <ServerID>
	Specify the ServerID, overriding the REPLICA_ID setting in the configuration
	file.

 -S <TargetServerID>
	Specify the ServerID of the P4TARGET of the server being installed.
	Use this when setting up an edge server.

 -t <server_type>
	Specify the server type, overriding the SERVER_TYPE setting in the config
	file.  Valid values are:
	* p4d_master - A master/commit server.
	* p4d_replica - A replica with all metadata from the master (not
	  filtered in any way).
	* p4d_filtered_replica - A filtered replica or filtered forwarding
	  replica.
	* p4d_edge - An edge server.
	* p4d_edge_replica - Replica of an edge server. If used,
	  '-S <TargetServerID>' is required.
	* p4broker - An SDP host running only a broker, with no p4d.
	* p4proxy - An SDP host running a proxy (maybe with a broker in front),
	  with no p4d.

 -MDD /bigdisk
 -MLG /jnl
 -MDB1 /db1
 -MDB2 /db2
	Specify the '-M*' to specify mount points, overriding DD/LG/DB1/DB2
	settings in the config file.  Sample:

	-MDD /bigdisk -MLG /jnl -MDB1 /fast

	If -MDB2 is not specified, it is set the the same value as -MDB1 if
	that is set, or else it defaults to the same default value as DB1.

 -f	Specify -f 'fast mode' to skip chown/chmod commands on depot files.
	This should only be used when you are certain the ownership and
	permissions are correct, and if you have large amounts of existing
	data for which the chown/chmod of the directory tree would be
	slow.

 -p	Specify '-p' to halt processing after preflight checks are complete,
	and before actual processing starts. By default, procesing starts
	immediately upon successful completion of preflight checks.

 -L <log>
	Specify the path to a log file, or the special value 'off' to disable
	logging.  By default, all output (stdout and stderr) goes to this file
	in the current directory:

	mkdirs.<instance>.<datestamp>.log

	NOTE: This script is self-logging.  That is, output displayed on the
	screen is simultaneously captured in the log file.  Do not run this
	script with redirection operators like '> log' or '2>&1', and do not
	use 'tee.'

DEBUGGING OPTIONS:
 -test
 	Specify '-test' to execute a simulated install to /tmp/p4 as the install
	root (rather than /p4), and with the mount point directories specifed in
	the configuration file prefixed with /tmp/hxmounts, defaulting to:
	* /tmp/hxmounts/hxdepots
	* /tmp/hxmounts/hxlogs
	* /tmp/hxmounts/hxmetadata
 
 -clean
	Specify '-clean' with '-test' to clean up from prior test installs,
	which will result in removal of files/folders installed under /tmp/hxmounts
	and /tmp/p4.

 	Do not specify '-clean' if you want to test a series of installs.

 -n	No-Op.  In No-Op mode, no actions that affect data or structures are
 	taken.  Instead, commands that would be run are displayed.  This is
	an alternative to -test. Unlike '-p' which stops after the preflight
	checks, with '-n' more processing logic can be exercised, with greater
	detail about what commands that would be executed without '-n'.

 -d     Increase verbosity for debugging.

 -D     Set extreme debugging verbosity, using bash '-x' mode. Also implies -d.

HELP OPTIONS:
 -h	Display short help message
 -man	Display man-style help message

FILES:
	The mkdirs.sh script uses a configuration file for many settings.  A
	sample file, mkdirs.cfg, is included with the SDP.  After determining
	your SDP instance name (e.g. '1' or 'abc'), create a configuration
	file for it named mkdirs.<N>.cfg, replacing 'N' with your instance.

	Running 'mkdirs.sh N' will load configuration settings from mkdirs.N.cfg.

UPGRADING SDP:
	This script can be useful in testing and upgrading to new versions of
	the SDP, when the '-test' flag is used.

EXAMPLES:
	Example 1: Setup of first instance

	Setup of the first instance on a machine using the default instance name,
	'1', executed after using sudo to become root:
	$ sudo su -
	$ cd /hxdepots/sdp/Server/Unix/setup
	$ vi mkdirs.cfg

	# Adjust settings as desired, e.g P4PORT, P4BROKERPORT, etc.

	$ ./mkdirs.sh 1

	A log will be generated, mkdirs.1.<timestamp>.log

	Example 2: Setup of additional instance named 'abc'.

	Setup a second instanced on the machine, which will be a seprate Helix
	Core instantce with its own P4ROOT, its own set of users and
	changelists, and its own license file (copied from the master instance).

	Note that while the first run of mkdirs.sh on a given machine should be
	done as root, but subsequent instane additions should be done as the
	'perforce' user (or whatever operating system user accounts Perforce
	Helix services run as).

	$ sudo su - perforce
	$ cd /hxdepots/sdp/Server/Unix/setup
	$ cp -p mkdirs.cfg mkdirs.abc.cfg
	$ vi mkdirs.abc.cfg

	# Adjust settings in mkdirs.abc.cfg as desired, e.g P4PORT, P4BROKERPORT, etc.

	$ ./mkdirs.sh abc

	A log will be generated, mkdirs.abc.<timestamp>.log
"
   fi

   exit 1
}

#------------------------------------------------------------------------------
# Function: terminate
function terminate
{
   # Disable signal trapping.
   trap - EXIT SIGINT SIGTERM

   # Ensure a non-zero exit code if the trap was triggered by anything other
   # than the 'exit 0' at the end of the Main Program.
   [[ "$BASH_COMMAND" == *"exit 0"* ]] || ErrorCount+=1

   msg "\\n$ThisScript: EXITCODE: $ErrorCount\\n"
   [[ "$Log" != "off" ]] && msg "Log is: $Log"

   # With the trap removed, exit.
   exit "$ErrorCount"
}

#==============================================================================
# Command Line Processing

declare -i shiftArgs=0

set +u
while [[ $# -gt 0 ]]; do
   case $1 in
      (-s) ServerID="$2"; ServerIDSet=1; shiftArgs=1;;
      (-S) TargetServerID="$2"; shiftArgs=1;;
      (-t)
         case "$2" in
            (p4d_master|p4d_replica|p4d_filtered_replica|p4d_edge|p4d_edge_replica) ServerType="$2";;
            (p4proxy|p4broker) ServerType="$2";;
            (*) usage -h "Invalid server type specified: $2\\nValid values are:
* p4d_master
* p4d_replica
* p4d_filtered_replica
* p4d_edge
* p4d_edge_replica
* p4proxy
* p4broker\\n";;
         esac
         shiftArgs=1
      ;;
      (-MDD) OverrideDD="$2"; shiftArgs=1;;
      (-MLG) OverrideLG="$2"; shiftArgs=1;;
      (-MDB1) OverrideDB1="$2"; shiftArgs=1;;
      (-MDB2) OverrideDB2="$2"; shiftArgs=1;;
      (-f) FastMode=1;;
      (-p) PreflightOnly=1;;
      (-test) TestMode=1; SDPInstallRoot="/tmp/p4"; SDPMountPointBase="/tmp/hxmounts";;
      (-clean) TestCleanup=1;;
      (-R) # Syntax: -R <alt_root>[:<alt_mount_base>]
	 TestMode=1
         if [[ "$2" == *":"* ]]; then
            # alt_root is to the left of the colon, alt mount base is to the right.
	    SDPInstallRoot="${2%%:*}"
	    SDPMountPointBase="${2:##*:}"
	 else
	    SDPInstallRoot="$2"
	    SDPMountPointBase=
	 fi
	 shiftArgs=1
      ;;
      (-h) usage -h;;
      (-man) usage -man;;
      (-n) NoOp=1;;
      (-d) Debug=1;;
      (-L) Log="$2"; shiftArgs=1;;
      (-D) Debug=1; set -x;; # Debug; use bash 'set -x' high verbosity debugging mode.
      (*)
         if [[ "$SDPInstance" == "UnsetSDPInstance" ]]; then
            SDPInstance="$1"
	 else
            usage -h "Instance $1 specified after already being specified as $SDPInstance. Only one instance can be specified."
	 fi
      ;;
   esac

   # Shift (modify $#) the appropriate number of times.
   shift; while [[ $shiftArgs -gt 0 ]]; do
      [[ $# -eq 0 ]] && usage -h "Incorrect number of arguments."
      shiftArgs=$shiftArgs-1
      shift
   done
done
set -u

if [[ "$SDPInstance" == "UnsetSDPInstance" ]]; then
   usage -h "No instance specified."
fi

#==============================================================================
# Main Program

# Pre-Preflight checks occur before logging starts, to ensure essential utils
# (including those needed for logging) are availble in the PATH.
for util in awk date id sed tee touch; do
   if ! command -v $util > /dev/null; then
      errmsg "Cannot find this essential util in PATH: $util"
   fi
done

if [[ "$ErrorCount" -ne 0 ]]; then
   bail "Aborting due to essential tools not available in directories listed in the PATH."
fi

[[ -n "$Log" ]] ||\
   Log="mkdirs.${SDPInstance}.$(date +'%Y%m%d-%H%M%S').log"
	
if [[ "$Log" != off ]]; then
   touch "$Log" || bail "Couldn't touch log file [$Log]."
fi

trap terminate EXIT SIGINT SIGTERM

# Redirect stdout and stderr to the log file.
if [[ "$Log" != off ]]; then
   exec > >(tee "$Log")
   exec 2>&1
   msg "Log is: $Log"
fi

ThisUser=$(whoami)

msg "Started $ThisScript v$Version as $ThisUser@${HOSTNAME%%.*} on $(date) as:\\n$0 $*"

# Standard Preflight checks after logging has started.

# Check for config file - same dir as this script
if [[ -r "${0%/*}/mkdirs.${SDPInstance}.cfg" ]]; then
   ConfigFile="${0%/*}/mkdirs.${SDPInstance}.cfg"
fi

export SDP_INSTANCE="${SDPInstance}"

#------------------------------------------------------------------------------
# Load settings from the mkdirs config file.
if [[ -r "$ConfigFile" ]]; then
   msg "Loading mkdirs config file: $ConfigFile"
   for c in DB1 DB2 DD LG SHAREDDATA MASTERINSTANCE OSUSER OSGROUP CASE_SENSITIVE ADMINUSER P4ADMINPASS MAILFROM MAILTO MAILHOST SSL_PREFIX P4_PORT P4BROKER_PORT P4WEB_PORT P4FTP_PORT P4P_TARGET_PORT P4MASTERHOST P4SERVICEPASS MASTER_ID SERVER_TYPE REPLICA_ID COMPLAINFROM_DOMAIN COMPLAINFROM; do
      value=$(grep ^$c= "$ConfigFile")
      if [[ -n "$value" ]]; then
         value="${value#*=}"
         eval export $c="$value"

         # Perform value sanity check verifications. For SERVER_TYPE, also do implicit conversion from types
         # specified in mkrep.sh.
         case "$c" in
            (SERVER_TYPE)
               # Convert all unfiltered replica types to p4d_replica.
               [[ "$value" =~ (p4d_ha|p4d_ham|p4d_ro|p4d_rom|p4d_fr|p4d_frm|p4d_fs|p4d_fsm) ]] && \
                  value="p4d_replica"

               # Convert all filtered replica to p4d_filtered_replica.
               [[ "$value" =~ (p4d_ffr) ]] && \
                  value="p4d_filtered_replica"

               [[ "$value" =~ (p4d_master|p4d_replica|p4d_filtered_replica|p4d_edge|p4broker|p4proxy) ]] || \
                  errmsg "SERVER_TYPE value of [$value] is invalid.  Set to one of: p4d_master,p4d_replica,p4d_filtered_replica,p4d_edge,p4broker,p4proxy."
            ;;
         esac
      else
         # Silently ignore if optional settings aren't set; give an error if required settings are missing.
         [[ "$c" =~ (MAILHOST|P4WEB_PORT|P4FTP_PORT|P4P_TARGET_PORT|COMPLAINFROM_DOMAIN|COMPLAINFROM) ]] && \
            continue
	 if [[ "$c" == "REPLICA_ID" && "$SERVER_TYPE" =~ (p4d_replica|p4d_filtered_replica|p4d_edge) ]]; then
		 errmsg "Missing REPLICA_ID (required as SERVER_TYPE is $SERVER_TYPE) in mkdirs config file: $ConfigFile"
         else
            errmsg "Missing required setting [$c] in mkdirs config file: $ConfigFile"
         fi
      fi
   done
else
   errmsg "Missing mkdirs config file: $ConfigFile"
fi

#------------------------------------------------------------------------------
# Handle command line overrides of certain settings in the config file.

# If '-s <ServerID>' was specified on the command line, override REPLICA_ID.
if [[ -n "$ServerID" ]]; then
   msg "\\nUsing ServerID [$ServerID] due to '-s $ServerID'. Ignoring REPLICA_ID value from config file [$REPLICA_ID]."
else
   ServerID="$REPLICA_ID"
   msg "Using ServerID value from REPLICA_ID setting in config file [$ServerID]."
fi

# If '-t <server_type>' was specified on the command line, override SERVER_TYPE.
if [[ -n "$ServerType" ]]; then
   msg "\\nUsing Server Type [$ServerType] due to '-t $ServerType'. Ignoring SERVER_TYPE value from config file [$SERVER_TYPE]."
else
   ServerType="$SERVER_TYPE"
   msg "Using ServerType value from SERVER_TYPE setting in config file [$ServerType]."
fi

[[ "$ServerType" == "p4d_edge_replica" && -z "$TargetServerID" ]] && \
   errmsg "ServerType is p4d_edge_replica but '-S <TargetServerID' is not set."

#------------------------------------------------------------------------------
# Determine SDP "TarRoot" install dir, 3 directory levels above the current dir.
# Usually it will be: /hxdepots/sdp.
#
# This mkdirs.sh script is run from <Base>/Server/Unix/setup, where <Base> is
# usually /hxdepots/sdp. Set TarRoot and several values derived from it.
TarRoot="$PWD"
TarRoot="${TarRoot%/*}"
TarRoot="${TarRoot%/*}"
TarRoot="${TarRoot%/*}"
TarRootCommon="$TarRoot/Server/Unix/p4/common"
TarRootConfig="$TarRoot/Server/Unix/p4/common/config"
TarRootCron="$TarRoot/Server/Unix/p4/common/etc/cron.d"
TarRootBinDir="$TarRoot/helix_binaries"
TarRootInit="$TarRoot/Server/Unix/p4/common/etc/init.d"
TarRootSSL="$TarRoot/Server/Unix/p4/ssl"

#------------------------------------------------------------------------------
# Do preflight checks, starting with the SDP version check.
SDPVersionFile="$TarRoot/Version"
if [[ -r "$SDPVersionFile" ]]; then
   SDPVersionString="$(cat "$SDPVersionFile")"
   SDPVersion="$(get_sdp_version_from_string "$SDPVersionString")"
   msg "SDP Version from $SDPVersionFile is: $SDPVersion"
else
   errmsg "Preflight check failed: Missing SDP Version file: $SDPVersionFile"
fi

[[ -n "$OverrideDD" ]] && DD="$OverrideDD"
[[ -n "$OverrideLG" ]] && LG="$OverrideLG"
[[ -n "$OverrideDB1" ]] && DB1="$OverrideDB1"
[[ -n "$OverrideDB2" ]] && DB2="$OverrideDB2"

# If -MDB1 is specified, and -MDB2 is not, use the value DB1 value for DB2.
[[ -n "$OverrideDB1" && -z "$OverrideDB2" ]] && DB2="$OverrideDB1"

#------------------------------------------------------------------------------
# Normalizations: Trim the leading '/' from DB1, DB2, LG, and DD values. These
# normalizations allow the values as specified in the configuration file to be
# more flexible.  That is, absolute paths with the leading '/' can be
# specified, which tend to be more readable and familiar to humans reading the
# config file, since the values match the actual mount point names. The older
# format, with the leading slash removed, is still accepted for backward
# compatibility.

DD="${DD#/}"
LG="${LG#/}"
DB1="${DB1#/}"
DB2="${DB2#/}"

P4SERVER="p4_$SDPInstance"

export MAIL=mail

if [[ $(id -u) -eq 0 ]]; then
   msg "Verified: Running as root."
elif [[ $(id -u -n) == "$OSUSER" ]]; then
   warnmsg "Not running as root; chown commands will be skipped and basic directories must exist.\\n"
   DoChownCommands=0
else
   errmsg "$0 must be run as as root or $OSUSER.\n"
fi

#------------------------------------------------------------------------------
# Handle -test mode.
if [[ "$TestMode" -eq 1 ]]; then
   DB1="${SDPMountPointBase#/}/$DB1"
   DB2="${SDPMountPointBase#/}/$DB2"
   DD="${SDPMountPointBase#/}/$DD"
   LG="${SDPMountPointBase#/}/$LG"

   msg "\\n********* -test specified *********\\n"
   msg "SDP Install Dir: $TarRoot"
   msg "Override for SDP Install Root: $SDPInstallRoot"
   msg "Override for SDP Mount Point Base: $SDPMountPointBase\\n"
   msg "DD=$DD\\nLG=$LG\\nDB1=$DB1\\nDB2=$DB2\\n"

   if [[ "$TestCleanup" -eq 1 ]]; then
      msg "Test Cleanup specified with -clean."
      DirList="$SDPInstallRoot $SDPMountPointBase"

      for d in $DirList; do
         if [[ -d "$d" ]]; then
            run "rm -rf $d" "Cleanup: Removing test dir from prior test: $d" ||\
               errmsg "Failed to remove test dir from prior test: $d"
         fi
      done
   fi
fi

#------------------------------------------------------------------------------
# Check for required and optional binaries.
[[ -f "$TarRootBinDir/p4" ]] || errmsg "No p4 in $TarRootBinDir/"
[[ -f "$TarRootBinDir/p4d" ]] || errmsg "No p4d in $TarRootBinDir/"
[[ -f "$TarRootBinDir/p4p" ]] || warnmsg "No p4p in $TarRootBinDir/"
[[ -f "$TarRootBinDir/p4broker" ]] || warnmsg "No p4broker in $TarRootBinDir/"

# If we aren't running as root, extend the preflight checks to verify basic dirs
# exist.
if [[ $(id -u) -ne 0 && "$TestMode" -eq 0 ]]; then
   for d in "$SDPInstallRoot" "/$DD" "/$DB1" "/$DB2" "/$LG"; do
      if [[ -d "$d" ]]; then
         # shellcheck disable=SC2012
         dirOwner=$(ls -ld "$d" | awk '{print $3}')
         # shellcheck disable=SC2012
         dirGroup=$(ls -ld "$d" | awk '{print $4}')
         [[ "$dirOwner" == "$OSUSER" ]] ||\
            errmsg "Dir [$d] exists but with wrong owner, $dirOwner instead of $OSUSER."
         [[ "$dirGroup" == "$OSGROUP" ]] ||\
            warnmsg "Dir [$d] exists but with wrong group, $dirGroup instead of $OSGROUP."
      else
         errmsg "Dir must exist if not running as root: $d"
      fi
   done
fi

if [[ "$ErrorCount" -eq 0 ]]; then
   msg "Verified: Preflight checks passed ($WarningCount warnings)."
else
   bail "Aborting due to failed preflight checks."
fi

if [[ "$PreflightOnly" -eq 1 ]]; then
   msg "Exiting early after successful preflight checks due to '-p'."
   exit 0
fi

#------------------------------------------------------------------------------
# Preflight checks completed. Continue on!

run "chmod 755 $TarRootBinDir/p4" || errmsg "Failed to chmod p4 binary."
run "chmod 700 $TarRootBinDir/p4d" || errmsg "Failed to chmod p4d binary."

[[ -f "$TarRootBinDir/p4broker" ]] && run "chmod 700 $TarRootBinDir/p4broker"
[[ -f "$TarRootBinDir/p4p" ]] && run "chmod 700 $TarRootBinDir/p4p"

#------------------------------------------------------------------------------
# Calculate binary versions.
P4RELNUM=$("$TarRootBinDir/p4" -V | grep -i Rev. | awk -F / '{print $3}')
P4BLDNUM=$("$TarRootBinDir/p4" -V | grep -i Rev. | awk -F / '{print $4}' | awk '{print $1}')
P4DRELNUM=$("$TarRootBinDir/p4d" -V | grep -i Rev. | awk -F / '{print $3}')
P4DBLDNUM=$("$TarRootBinDir/p4d" -V | grep -i Rev. | awk -F / '{print $4}' | awk '{print $1}')

#------------------------------------------------------------------------------
# Start installation.
msg "\\nStarting directory structure initialization."
DirList="$SDPInstallRoot /$DD/p4 /$LG/p4 /$DB1/p4 /$DD/p4/common/bin /$DD/p4/common/config"
[[ "$DB1" == "$DB2" ]] || DirList="$DirList /$DB2/p4"

for d in $DirList; do
   if [[ ! -d "$d" ]]; then
      run "mkdir -p $d" "Creating initial install dir: $d" ||\
         errmsg "Failed to create initial install dir: $d"
   fi
done

DirList="$SDPInstallRoot/ssl /$LG/p4/$SDPInstance/tmp /$DD/p4/$SDPInstance/depots /$DD/p4/$SDPInstance/checkpoints"

if [[ "$ServerType" == "p4d_edge" || "$ServerType" == "p4d_filtered_replica" || "$SHAREDDATA" == "TRUE" ]]; then
   DirList="$DirList /$DD/p4/$SDPInstance/checkpoints.${ServerID#p4d_}"
elif [[ "$ServerType" == "p4d_edge_replica" ]]; then
   DirList="$DirList /$DD/p4/$SDPInstance/checkpoints.${TargetServerID#p4d_}"
fi

for d in $DirList; do
   if [[ ! -d "$d" ]]; then
      run "mkdir -p $d" "Creating install subdir: $d" ||\
         errmsg "Failed to create install subdir: $d"
   fi
done

#------------------------------------------------------------------------------
# ServerID handling.
if [[ "$ServerType" == "p4d_edge" || "$ServerType" == "p4d_filtered_replica" ]]; then
   if [[ "$DoChownCommands" -eq 1 ]]; then
      run "chown $OSUSER:$OSGROUP /$DD/p4/$SDPInstance/checkpoints.${ServerID#p4d_}" \
         "Adjusting ownership of checkpoints.${ServerID#p4d_}."
   fi
   run "ln -s /$DD/p4/$SDPInstance/checkpoints.${ServerID#p4d_} $SDPInstallRoot/$SDPInstance/checkpoints.${ServerID#p4d_}" \
      "Creating symlink for $SDPInstallRoot/$SDPInstance/checkpoints.${ServerID#p4d_}" ||\
      errmsg "Failed to create symlink."

   if [[ "$DoChownCommands" -eq 1 ]]; then
      run "chown -h $OSUSER:$OSGROUP /$SDPInstallRoot/$SDPInstance/checkpoints.${ServerID#p4d_}" \
         "Adjusting ownership of symlink to checkpoints.${ServerID#p4d_}."
   fi
elif [[ "$ServerType" == "p4d_edge_replica" ]]; then
   if [[ "$DoChownCommands" -eq 1 ]]; then
      run "chown $OSUSER:$OSGROUP /$DD/p4/$SDPInstance/checkpoints.${TargetServerID#p4d_}" \
         "Adjusting ownership of checkpoints.${TargetServerID#p4d_}."
   fi
   run "ln -s /$DD/p4/$SDPInstance/checkpoints.${TargetServerID#p4d_} $SDPInstallRoot/$SDPInstance/checkpoints.${TargetServerID#p4d_}" \
      "Creating symlink for $SDPInstallRoot/$SDPInstance/checkpoints.${TargetServerID#p4d_}" ||\
      errmsg "Failed to create symlink."

   if [[ "$DoChownCommands" -eq 1 ]]; then
      run "chown -h $OSUSER:$OSGROUP /$SDPInstallRoot/$SDPInstance/checkpoints.${TargetServerID#p4d_}" \
         "Adjusting ownership of symlink to checkpoints.${TargetServerID#p4d_}."
   fi
fi

if [[ -f "$TarRootSSL/config.txt" && ! -f "$SDPInstallRoot/ssl/config.txt" ]]; then
   run "cp $TarRoot/Server/Unix/p4/ssl/config.txt $SDPInstallRoot/ssl/." \
      "Copying SSL cert generation config." ||\
      errmsg "Failed to copy $SDPInstallRoot/ssl/config.txt file."
fi

if [[ ! -L "$SDPInstallRoot/common" ]]; then
   run "ln -s /$DD/p4/common $SDPInstallRoot/common" \
      "Creating symlink for common dir." ||\
      errmsg "Failed to create symlink for common dir."

   if [[ "$DoChownCommands" -eq 1 ]]; then
      run "chown $OSUSER:$OSGROUP $SDPInstallRoot/common" \
         "Adjusting ownership of symlink to $SDPInstallRoot/common" ||\
         errmsg "Failed to adjust ownership of symlink to $SDPInstallRoot/common"
   fi
fi

DirList="/$DB1/p4/$SDPInstance/db1/save /$DB2/p4/$SDPInstance/db2/save /$LG/p4/$SDPInstance/logs /$LG/p4/$SDPInstance/tmp"

for d in $DirList; do
   if [[ ! -d "$d" ]]; then
      run "mkdir -p $d" "Creating dir: $d" ||\
         errmsg "Failed to create dir: $d"
   fi
done

# Test a cd to /$DD/p4/<instance>.
if [[ "$NoOp" -eq 0 ]]; then
   cd "/$DD/p4/$SDPInstance" || errmsg "Could not cd to /$DD/p4/$SDPInstance"
fi

# Test cd to /p4
if [[ "$NoOp" -eq 0 ]]; then
   cd "$SDPInstallRoot" || errmsg "Could not cd to $SDPInstallRoot"
   msg "Operating in: $PWD"
else
   msg "NoOp: Would be operating in: $SDPInstallRoot"
fi

if [[ ! -d "$SDPInstance" ]]; then
   run "mkdir $SDPInstance" "Creating dir for instance: $SDPInstallRoot/$SDPInstance" ||\
      errmsg "Failed to create dir for instance $SDPInstance."
fi

if [[ "$NoOp" -eq 0 ]]; then
   cd "$SDPInstance" || bail "Could not cd to $SDPInstance from: $SDPInstallRoot"
   msg "Operating in: $PWD"
else
   msg "NoOp: Would be operating in: $SDPInstallRoot/$SDPInstance"
fi

if [[ ! -d bin ]]; then
   run "mkdir bin" || errmsg "Failed to mkdir bin"
fi

if [[ ! -L root ]]; then
   run "ln -s /$DB1/p4/$SDPInstance/db1 root" ||\
      errmsg "Failed to make symlink for P4ROOT."
fi

if [[ ! -L offline_db ]]; then
   run "ln -s /$DB2/p4/$SDPInstance/db2 offline_db" ||\
      errmsg "Failed to make symllink for offline_db."
fi

if [[ ! -d logs ]]; then
   if [[ ! -L logs ]]; then
      run "ln -s /$LG/p4/$SDPInstance/logs" ||\
         errmsg "Failed to make symlink for logs."
   fi
fi

if [[ ! -L depots ]]; then
   run "ln -s /$DD/p4/$SDPInstance/depots" ||\
      errmsg "Failed to make symlink for depots."
fi

if [[ ! -L checkpoints ]]; then
   run "ln -s /$DD/p4/$SDPInstance/checkpoints" ||\
      errmsg "Failed to make symlink for checkpoints."
fi

if [[ ! -L tmp ]]; then
   run "ln -s /$LG/p4/$SDPInstance/tmp" ||\
      errmsg "Failed to make symlink for tmp."
fi

if [[ "$ServerType" == "p4d_edge" || "$ServerType" == "p4d_filtered_replica" ]]; then
   if [[ ! -L "checkpoints.${ServerID#p4d_}" ]]; then
      run "ln -s /$DD/p4/$SDPInstance/checkpoints.${ServerID#p4d_}" ||\
         errmsg "Failed to make symlink for checkpoints.${ServerID#p4d_}."
   fi
elif [[ "$ServerType" == "p4d_edge_replica" ]]; then
   if [[ ! -L "checkpoints.${TargetServerID#p4d_}" ]]; then
      run "ln -s /$DD/p4/$SDPInstance/checkpoints.${TargetServerID#p4d_}" ||\
         errmsg "Failed to make symlink for checkpoints.${TargetServerID#p4d_}."
   fi
fi

if [[ -L "$SDPInstallRoot/sdp" ]]; then
   if [[ "$(readlink "$SDPInstallRoot/sdp")" == "$TarRoot" ]]; then
      msg "Verified: $SDPInstallRoot/sdp is a symlink to $TarRoot"
   else
      errmsg "Symlink $SDPInstallRoot/sdp should be to $TarRoot, but instead ponts to: $(readlink "$SDPInstallRoot/sdp")."
   fi
elif [[ -e "$SDPInstallRoot/sdp" ]]; then
   errmsg "This path is expected to be a symlink but is not: $SDPInstallRoot/sdp"
else
   run "ln -s $TarRoot $SDPInstallRoot/sdp" "Creating symlink to SDP package install area." ||\
      errmsg "Failed to make symlink for sdp."
fi

case "$ServerType" in
   (p4d_master)
      # If the ServerID was not set by the user with '-s', then set a
      # default value for a master.
      if [[ "$ServerIDSet" -eq 0 ]]; then
         ServerID="${MASTER_ID}"
      fi
      if [[ "$NoOp" -eq 0 ]]; then
         echo "$ServerID" > "$SDPInstallRoot/$SDPInstance/root/server.id"
      else
         msg "NoOp: Would write $ServerID into $SDPInstallRoot/$SDPInstance/root/server.id"
      fi
   ;;
   (p4d_*)
      if [[ "$NoOp" -eq 0 ]]; then
         echo "$ServerID" > "$SDPInstallRoot/$SDPInstance/root/server.id"
      else
         msg "NoOp: Would write $ServerID into $SDPInstallRoot/$SDPInstance/root/server.id"
      fi
   ;;
   (*)
      # If the ServerID was not set by the user with '-s', then use ServerType
      # as default value for a broker/proxy.
      if [[ "$ServerIDSet" -eq 0 ]]; then
         ServerID="$ServerType"
      fi
   ;;
esac

if [[ ! -f "$SDPInstallRoot/common/bin/p4_$P4RELNUM.$P4BLDNUM" ]]; then
   run "cp $TarRootBinDir/p4 $SDPInstallRoot/common/bin/p4_$P4RELNUM.$P4BLDNUM" ||\
      bail "Failed to copy p4 binary."
fi

if [[ ! -f "$SDPInstallRoot/common/bin/p4d_$P4DRELNUM.$P4DBLDNUM" ]]; then
   run "cp $TarRootBinDir/p4d $SDPInstallRoot/common/bin/p4d_$P4DRELNUM.$P4DBLDNUM" ||\
      bail "Failed to copy p4d binary"
fi

if [[ ! -f "$SDPInstallRoot/common/bin/p4_vars" ]]; then
   run "cp -R $TarRootCommon/bin/* $SDPInstallRoot/common/bin" "Copying $SDPInstallRoot/common/bin" ||\
      errmsg "Failed to copy to $SDPInstallRoot/common/bin"

   # Copy certain subdirs of /p4/common if don't already exist.
   for d in cloud etc lib site; do
      if [[ ! -d "$SDPInstallRoot/common/$d" ]]; then
         run "cp -pr $TarRootCommon/$d $SDPInstallRoot/common/." "Copying $SDPInstallRoot/common/$d." ||\
            errmsg "Failed to copy $d to $SDPInstallRoot/common/"
      fi
   done

   if [[ "$NoOp" -eq 0 ]]; then
      cd "$SDPInstallRoot/common/bin" ||\
        bail "Could not cd to $SDPInstallRoot/common/bin"
      msg "Operating in: $PWD"
   else
      msg "NoOp: Would be operating in: $SDPInstallRoot/common/bin"
   fi

   run "ln -s p4_$P4RELNUM.$P4BLDNUM p4_${P4RELNUM}_bin" "Linking p4_${P4RELNUM}_bin." ||\
      errmsg "Failed to symlink p4_${P4RELNUM}_bin"

   run "ln -s p4d_$P4DRELNUM.$P4DBLDNUM p4d_${P4DRELNUM}_bin" "Linking p4d_${P4DRELNUM}_bin." ||\
      errmsg "Failed to symlink p4_${P4DRELNUM}_bin"

   run "ln -s p4_${P4RELNUM}_bin p4_bin" "Linking p4_bin." ||\
      errmsg "Failed to symlink p4_bin"

   msg "Generating: $PWD/p4_vars"
   if [[ "$NoOp" -eq 0 ]]; then
      sed -e "s:REPL_SDPVERSION:${SDPVersionString}:g" \
         -e "s/REPL_OSUSER/${OSUSER}/g" \
         "$TarRootConfig/p4_vars.template" > p4_vars
   fi
fi

if [[ "$NoOp" -eq 0 ]]; then
   cd "$SDPInstallRoot/common/bin" || bail "Could not cd to $SDPInstallRoot/common/bin"
   msg "Operating in: $PWD"
else
   msg "NoOp: Would be operating in: $SDPInstallRoot/common/bin"
fi

run "ln -s p4d_${P4DRELNUM}_bin p4d_${SDPInstance}_bin" || errmsg "Failed to symlink p4d_${SDPInstance}_bin"

if [[ ! -e "$SDPInstallRoot/common/config/.p4passwd.${P4SERVER}.admin" ]]; then
   if [[ "$NoOp" -eq 0 ]]; then
      echo "$P4ADMINPASS" > "$SDPInstallRoot/common/config/.p4passwd.${P4SERVER}.admin"
   else
      msg "NoOp: Would write admin password into $SDPInstallRoot/common/config/.p4passwd.${P4SERVER}.admin"
   fi
else
   warnmsg "Skipping update of existing admin password file $SDPInstallRoot/common/config/.p4passwd.${P4SERVER}.admin"
fi

if [[ ! -e "$SDPInstallRoot/common/config/.p4passwd.${P4SERVER}.service" ]]; then
   if [[ "$NoOp" -eq 0 ]]; then
      echo "$P4SERVICEPASS" > "$SDPInstallRoot/common/config/.p4passwd.${P4SERVER}.service"
   else
      msg "NoOp: Would write service password into $SDPInstallRoot/common/config/.p4passwd.${P4SERVER}.service"
   fi
else
   warnmsg "Skipping update of existing service password file $SDPInstallRoot/common/config/.p4passwd.${P4SERVER}.service"
fi

#------------------------------------------------------------------------------
# Create broker links if broker binary exists.
if [[ -f "$TarRootBinDir/p4broker" ]]; then
   # shellcheck disable=SC2016
   P4BRELNUM=$("$TarRootBinDir/p4broker" -V | grep -i Rev. | awk -F / '{print $3}')
   # shellcheck disable=SC2016
   P4BBLDNUM=$("$TarRootBinDir/p4broker" -V | grep -i Rev. | awk -F / '{print $4}' | awk '{print $1}')
   if [[ ! -f "$SDPInstallRoot/common/bin/p4broker_$P4BRELNUM.$P4BBLDNUM" ]]; then
      run "cp $TarRootBinDir/p4broker $SDPInstallRoot/common/bin/p4broker_$P4BRELNUM.$P4BBLDNUM" ||\
         errmsg "Could not copy p4broker binary."
   fi

   if [[ -L "p4broker_${P4BRELNUM}_bin" ]]; then
      run "unlink p4broker_${P4BRELNUM}_bin" ||\
         errmsg "Could not unlink: p4broker_${P4BRELNUM}_bin"
   fi

   run "ln -s p4broker_$P4BRELNUM.$P4BBLDNUM p4broker_${P4BRELNUM}_bin" "Creating broker symlink." ||\
      errmsg "Could not create broker symlink."

   if [[ -L "p4broker_${SDPInstance}_bin" ]]; then
      run "unlink p4broker_${SDPInstance}_bin" ||\
         errmsg "Could not unlink: p4broker_${SDPInstance}_bin"
   fi

   run "ln -s p4broker_${P4BRELNUM}_bin p4broker_${SDPInstance}_bin" "Creating broker instance symlink." ||\
      errmsg "Could not create broker instance symlink."

   if [[ "$NoOp" -eq 0 ]]; then
      cd "$SDPInstallRoot/$SDPInstance/bin" || \
         bail "Could not cd to $SDPInstallRoot/$SDPInstance/bin"
      msg "Operating in: $PWD"
   else
      msg "NoOp: Would be operating in: $SDPInstallRoot/$SDPInstance/bin"
   fi

   if [[ ! -L "p4broker_${SDPInstance}" ]]; then
      run "ln -s $SDPInstallRoot/common/bin/p4broker_${SDPInstance}_bin p4broker_${SDPInstance}" ||\
         errmsg "Could not create symlink."
   fi
 
   msg "Generating: p4broker_${SDPInstance}_init"
   sed "s/REPL_SDP_INSTANCE/${SDPInstance}/g" "$TarRootInit/p4broker_instance_init.template" > "p4broker_${SDPInstance}_init"
   chmod +x "p4broker_${SDPInstance}_init"
fi

# Create p4p links if p4p exists
if [[ "$NoOp" -eq 0 ]]; then
   cd "$SDPInstallRoot/common/bin" || bail "Could not cd to $SDPInstallRoot/common/bin"
   msg "Operating in: $PWD"
else
   msg "NoOp: Would be operating in: $SDPInstallRoot/common/bin"
fi

if [[ -x "$TarRootBinDir/p4p" ]]; then
   # shellcheck disable=SC2016
   P4PRELNUM=$("$TarRootBinDir/p4p" -V | grep -i Rev. | awk -F / '{print $3}')
   # shellcheck disable=SC2016
   P4PBLDNUM=$("$TarRootBinDir/p4p" -V | grep -i Rev. | awk -F / '{print $4}' | awk '{print $1}')

   if [[ ! -f "$SDPInstallRoot/common/bin/p4p_$P4PRELNUM.$P4PBLDNUM" ]]; then
      run "cp $TarRootBinDir/p4p $SDPInstallRoot/common/bin/p4p_$P4PRELNUM.$P4PBLDNUM" ||\
         errmsg "Coudl not copy p4p binary."
   fi

   if [[ -L "p4p_${P4PRELNUM}_bin" ]]; then
      run "unlink p4p_${P4PRELNUM}_bin" || errmsg "Could not unlink: p4p_${P4PRELNUM}_bin"
   fi

   run "ln -s p4p_$P4PRELNUM.$P4PBLDNUM p4p_${P4PRELNUM}_bin" ||\
      errmsg "Could not symlink to p4p."

   if [[ -L "p4p_${SDPInstance}_bin" ]]; then
      run "unlink p4p_${SDPInstance}_bin" || errmsg "Cound not unlink: p4p_${SDPInstance}_bin"
   fi

   run "ln -s p4p_${P4PRELNUM}_bin p4p_${SDPInstance}_bin"

   if [[ "$NoOp" -eq 0 ]]; then
      cd "$SDPInstallRoot/$SDPInstance/bin" || \
         bail "Could not cd to $SDPInstallRoot/$SDPInstance/bin"
      msg "Operating in: $PWD"
   else
      msg "NoOp: Would be operating in: $SDPInstallRoot/$SDPInstance/bin"
   fi

   if [[ ! -L "p4p_${SDPInstance}" ]]; then
      run "ln -s $SDPInstallRoot/common/bin/p4p_${SDPInstance}_bin p4p_${SDPInstance}" ||\
         errmsg "Could not symlink for p4p_${SDPInstance}."
   fi

   msg "Generating: p4p_${SDPInstance}_init"
   if [[ "$NoOp" -eq 0 ]]; then
      sed -e "s/REPL_SDP_INSTANCE/${SDPInstance}/g" \
         "$TarRootInit/p4p_instance_init.template" > "p4p_${SDPInstance}_init"
      run "chmod +x p4p_${SDPInstance}_init"
      run "mkdir -p /$DD/p4/$SDPInstance/cache"
   fi
fi

if [[ "$NoOp" -eq 0 ]]; then
   cd "$SDPInstallRoot/$SDPInstance/bin" ||\
      bail "Could not cd to $SDPInstallRoot/$SDPInstance/bin" 
   msg "Operating in: $PWD"
else
   msg "NoOp: Would be operating in: $SDPInstallRoot/$SDPInstance/bin"
fi

run "ln -s $SDPInstallRoot/common/bin/p4_bin p4_${SDPInstance}" ||\
   errmsg "Could not create symlink for: p4_${SDPInstance}"

msg "Generating: p4d_${SDPInstance}_init"
if [[ "$NoOp" -eq 0 ]]; then
   sed "s/REPL_SDP_INSTANCE/${SDPInstance}/g" \
      "$TarRootInit/p4d_instance_init.template" > "p4d_${SDPInstance}_init" ||\
      errmsg "Failed to generate init script."
   run "chmod +x p4d_${SDPInstance}_init" ||\
      errmsg "Failed to chmod init script."
fi

msg "Generating: p4dtg_${SDPInstance}_init"
if [[ "$NoOp" -eq 0 ]]; then
   sed "s/REPL_SDP_INSTANCE/${SDPInstance}/g" \
      "$TarRootInit/p4dtg_instance_init.template" > "p4dtg_${SDPInstance}_init" ||\
      errmsg "Failed to generate init script."
   run "chmod +x p4dtg_${SDPInstance}_init" ||\
      errmsg "Failed to chmod init script."
fi

if [[ "$NoOp" -eq 0 ]]; then
   cd "$SDPInstallRoot/$SDPInstance/bin" || bail "Could not cd to $SDPInstallRoot/$SDPInstance"
   msg "Operating in: $PWD"
else
   msg "NoOp: Would be operating in: $SDPInstallRoot/$SDPInstance/bin"
fi

if [[ $CASE_SENSITIVE -eq 1 ]]; then
   run "ln -s $SDPInstallRoot/common/bin/p4d_${SDPInstance}_bin p4d_$SDPInstance" ||\
      errmsg "Failed to symlink p4d_$SDPInstance"
else
   msg "Generating: p4d_${SDPInstance} for case-sensitive server."
   if [[ "$NoOp" -eq 0 ]]; then
      echo '#!/bin/bash' > "p4d_${SDPInstance}" || errmsg "Failed to generate script."
      echo "P4D=/p4/common/bin/p4d_${SDPInstance}_bin" >> "p4d_$SDPInstance" ||\
         errmsg "Failed to generate script."
      # shellcheck disable=SC2016
      echo 'exec $P4D -C1 "$@"' >> "p4d_$SDPInstance" ||\
         errmsg "Failed to generate script."
      run "chmod +x p4d_$SDPInstance" || errmsg "Failed to chmod wrapper script."
   fi
fi

if [[ "$NoOp" -eq 0 ]]; then
   cd "$SDPInstallRoot/common/config" ||\
      bail "Could not cd to $SDPInstallRoot/common/config"
   msg "Operating in: $PWD"
else
   msg "NoOp: Would be operating in: $SDPInstallRoot/common/config"
fi

msg "Generating: p4_${SDPInstance}.vars"
if [[ "$NoOp" -eq 0 ]]; then
   sed -e "s/REPL_MAILTO/${MAILTO}/g" \
      -e "s/REPL_MAILFROM/${MAILFROM}/g" \
      -e "s/REPL_ADMINUSER/${ADMINUSER}/g" \
      -e "s/REPL_MASTER_ID/${MASTER_ID}/g" \
      -e "s/REPL_SSLPREFIX/${SSL_PREFIX}/g" \
      -e "s/REPL_P4PORT/${P4_PORT}/g" \
      -e "s/REPL_P4BROKERPORT/${P4BROKER_PORT}/g" \
      -e "s/REPL_P4WEBPORT/${P4WEB_PORT}/g" \
      -e "s/REPL_P4FTPPORT/${P4FTP_PORT}/g" \
      -e "s/REPL_P4MASTERHOST/${P4MASTERHOST}/g" \
      -e "s/REPL_P4P_TARGET_PORT/${P4P_TARGET_PORT}/g" \
      "$TarRootCommon/config/instance_vars.template" > "p4_${SDPInstance}.vars" ||\
      errmsg "Failed to generate p4_${SDPInstance}.vars"
fi

msg "Generating: p4_${SDPInstance}.p4review.cfg"
if [[ "$NoOp" -eq 0 ]]; then
   sed -e "s/REPL_ADMINISTRATOR/${MAILTO}/g" \
      -e "s/REPL_COMPLAINFROM/${COMPLAINFROM}/g" \
      -e "s/REPL_MAILHOST/${MAILHOST}/g" \
      -e "s/REPL_P4MASTERHOST/${P4MASTERHOST}/g" \
      "$TarRootCommon/config/p4review.cfg.template" > "p4_${SDPInstance}.p4review.cfg" ||\
      errmsg "Failed to generate p4_${SDPInstance}.p4review.cfg"
fi

if [[ "$NoOp" -eq 0 ]]; then
   cd "$SDPInstallRoot" || bail "Could not cd to $SDPInstallRoot"
   msg "Operating in: $PWD"
else
   msg "NoOp: Would be operating in: $SDPInstallRoot"
fi

if [[ ! -f "${SDPInstallRoot}/p4.crontab.${SDPInstance}" ]]; then
   CRONTAB_NAME=p4.crontab.${SDPInstance}
else
   CRONTAB_NAME=p4.crontab.$SDPInstance.new
   run "rm -f ${SDPInstallRoot}/$CRONTAB_NAME"
   msg "Existing crontab found, writing new crontab to ${SDPInstallRoot}/${CRONTAB_NAME}"
fi

msg "Generating: $CRONTAB_NAME"
if [[ "$NoOp" -eq 0 ]]; then
   sed -e "s/REPL_MAILTO/${MAILTO}/g" \
      -e "s/REPL_MAILFROM/${MAILFROM}/g" \
      -e "s/REPL_INSTANCE/${SDPInstance}/g" \
      "$TarRootCron/template.crontab.combined" > "$CRONTAB_NAME" ||\
      errmsg "Failed to generate crontab file: $CRONTAB:NAME"
fi

if [[ "$DoChownCommands" -eq 1 ]]; then
   run "chown $OSUSER:$OSGROUP $CRONTAB_NAME" || errmsg "Failed to chown $CRONTAB_NAME"
fi

if [[ -r "$TarRoot/Unsupported/Maintenance/template.maintenance.cfg" ]]; then
   msg "Appending configuration section [$SDPInstance] to $TarRoot/Unsupported/Maintenance/maintenance.cfg"

   if [[ -r "$TarRoot/Unsupported/Maintenance/maintenance.cfg" ]]; then
      run "chmod +w $TarRoot/Unsupported/Maintenance/maintenance.cfg"
   fi

   if [[ "$NoOp" -eq 0 ]]; then
      echo -e "[$SDPInstance]" >> "$TarRoot/Unsupported/Maintenance/maintenance.cfg"

      msg "Appending to: $TarRoot/Unsupported/Maintenance/maintenance.cfg"
      sed -e "s/REPL_INSTANCE/$SDPInstance/g" \
         -e "s/REPL_ADMINISTRATOR/${MAILTO}/g" \
         -e "s/REPL_COMPLAINFROM/${COMPLAINFROM}/g" \
         -e "s/REPL_MAILHOST/${MAILHOST}/g" \
         -e "s/REPL_DOMAIN/${COMPLAINFROM_DOMAIN}/g" \
         "$TarRoot/Unsupported/Maintenance/template.maintenance.cfg" >> "$TarRoot/Unsupported/Maintenance/maintenance.cfg" ||\
         errmsg "Could not append to: $TarRoot/Unsupported/Maintenance/maintenance.cfg"
   fi
fi

if [[ "$NoOp" -eq 0 ]]; then
   cd "$SDPInstallRoot/${SDPInstance}/bin" || \
      bail "Could not cd to $SDPInstallRoot/${SDPInstance}/bin"
   msg "Operating in: $PWD"
else
   msg "NoOp: Would be operating in: $SDPInstallRoot/${SDPInstance}/bin"
fi

if [[ "$SHAREDDATA" == "TRUE" ]]; then
   if [[ "$ServerType" == p4d_replica || "$ServerType" == p4d_standby ]]; then
      msg "Configuring Replica sharing depot data with master, skipping chown/chmod of depot files"
      DoChownCommands=0
   fi
fi

if [[ "$DoChownCommands" -eq 1 ]]; then
   if [[ "$TestMode" -eq 0 ]]; then
      run "chown $OSUSER:$OSGROUP /$DD" || errmsg "Failed to chown /$DD"
      run "chown $OSUSER:$OSGROUP /$LG" || errmsg "Failed to chown /$LG"
      run "chown $OSUSER:$OSGROUP /$DB1" || errmsg "Failed to chown /$DB1"
      run "chown $OSUSER:$OSGROUP /$DB2" || errmsg "Failed to chown /$DB2"
   fi

   run "chown $OSUSER:$OSGROUP /$DD/p4" || errmsg "Failed to chown /$DD/p4"
   run "chown $OSUSER:$OSGROUP /$LG/p4" || errmsg "Failed to chown /$LG/p4"
   run "chown $OSUSER:$OSGROUP /$DB1/p4" || errmsg "Failed to chown  /$DB1/p4"

   if [[ "$DB1" != "$DB2" ]]; then
      run "chown $OSUSER:$OSGROUP /$DB2/p4" || errmsg "Failed to chown  /$DB2/p4"
   fi

   run "chown -h $OSUSER:$OSGROUP $SDPInstallRoot" || errmsg "Failed to chown $SDPInstallRoot"
   run "chown -h $OSUSER:$OSGROUP $SDPInstallRoot/$SDPInstance" ||\
      errmsg "Failed to chown $SDPInstance"
   run "chown -h $OSUSER:$OSGROUP $SDPInstallRoot/common" ||\
      errmsg "Failed to chown $SDPInstallRoot/common"

   if [[ "$TestMode" -eq 0 ]]; then
      for path in "$SDPInstallRoot/sdp" "$SDPInstallRoot"/* "$SDPInstallRoot/$SDPInstance"/*; do
         run "chown -h $OSUSER:$OSGROUP $path" || errmsg "Failed to chown -h $OSUSER:$OSGROUP $path"
      done
   else
      msg "NoOp: Would run more chown commands for dirs and symlinks."
   fi

   run "chown -Rh $OSUSER:$OSGROUP $SDPInstallRoot/common/" || errmsg "Failed to chown $SDPInstallRoot/common/"
   if [[ $TestMode -eq 0 ]]; then
      run "chown -Rh $OSUSER:$OSGROUP $SDPInstallRoot/sdp/" || errmsg "Failed to chown $SDPInstallRoot/sdp/"
   fi
   run "chown -Rh $OSUSER:$OSGROUP $SDPInstallRoot/common" || errmsg "Failed to chown $SDPInstallRoot/common"
   run "chown -Rh $OSUSER:$OSGROUP /$DB1/p4/$SDPInstance" || errmsg "Failed to chown /$DB1/p4/$SDPInstance"
   if [[ "$DB1" != "$DB2" ]]; then
      run "chown -Rh $OSUSER:$OSGROUP /$DB2/p4/$SDPInstance" || errmsg "Failed to chown /$DB2/p4/$SDPInstance"
   fi
   run "chown -Rh $OSUSER:$OSGROUP /$LG/p4/$SDPInstance" || errmsg "Failed to chown /$LG/p4/$SDPInstance"

   run "chown -Rh $OSUSER:$OSGROUP $SDPInstallRoot/$SDPInstance/bin" || errmsg "Failed to chown $SDPInstallRoot/$SDPInstance/bin"
else
   msg "Skipped chown/chmod commands for large directory trees."
fi

run "chmod 700 /$DB1/p4" || errmsg "Failed to chmod /$DB1/p4"
if [[ "$DB1" != "$DB2" ]]; then
   run "chmod 700 /$DB2/p4" || errmsg "Failed to chmod /$DB2/p4"
fi
run "chmod 700 /$DD/p4" || errmsg "Failed to chmod /$DD/p4"
run "chmod 700 /$LG/p4" || errmsg "Failed to chmod /$LG/p4"

run "chmod -R 700 /$DB1/p4/$SDPInstance" || errmsg "Failed to chmod /$DB1/p4/$SDPInstance"
if [[ "$DB1" != "$DB2" ]]; then
   run "chmod -R 700 /$DB2/p4/$SDPInstance" || errmsg "Failed to chmod /$DB2/p4/$SDPInstance"
fi

run "chmod -R 700 $SDPInstallRoot/common" || errmsg "Failed to chmod $SDPInstallRoot/common"
run "chmod -R 700 /$LG/p4/$SDPInstance" || errmsg "Failed to chmod /$LG/p4/$SDPInstance"

if [[ "$SDPInstance" != "$MASTERINSTANCE" ]]; then
   if [[ -f "$SDPInstallRoot/$MASTERINSTANCE/root/license" && ! -f "$SDPInstallRoot/$SDPInstance/root/license" ]]; then
      run "ln -s $SDPInstallRoot/$MASTERINSTANCE/root/license $SDPInstallRoot/$SDPInstance/root/license" ||\
         errmsg "Failed to copy license from master instance $MASTERINSTANCE to $SDPInstance."
      run "chown $OSUSER:$OSGROUP $SDPInstallRoot/$SDPInstance/root/license" ||\
         errmsg "Failed to chown $SDPInstallRoot/$SDPInstance/root/license"
   fi
fi

for f in "$SDPInstallRoot/${SDPInstance}"/bin/*_init; do
   run "chmod 755 $f" || errmsg "Failed to chmod $f to 755."
done

run "chmod 700 $SDPInstallRoot/ssl" || errmsg "Failed to chmod 700 $SDPInstallRoot/ssl"

for f in "$SDPInstallRoot/common/config/.p4passwd.${P4SERVER}."* "$SDPInstallRoot"/ssl/*; do
   run "chmod 600 $f" || errmsg "Failed to chmod $f to 600."
done

if [[ "$DoChownCommands" -eq 1 ]]; then
   run "chown -R $OSUSER:$OSGROUP $SDPInstallRoot/ssl" ||\
      errmsg "Failed to do chown for ssl dir."
fi

if [[ "$TestMode" -eq 1 ]]; then
   msg "\\nThis was done in test mode. Production install directories were not affected.\\n"
  
   if [[ -d "/p4/common" ]]; then
      msg "If you are upgrading an existing SDP installation, compare these current folders and files:

diff -qr /p4/$SDPInstance/bin $SDPInstallRoot/$SDPInstance/bin
diff -qr /p4/common $SDPInstallRoot/common

Be careful to ensure generated files are correct, including files in
/p4/common/config and /p4/common/bin/p4_vars is appropriate.\\n"
   fi
fi

#------------------------------------------------------------------------------
# Do the big, long-running things at the end.

msg "\\nAll essential processing is complete. Wrapping up with potentially long-running tasks."

if [[ "$SHAREDDATA" == "FALSE" ]]; then
   if [[ "$FastMode" -eq 0 ]]; then
      msg "Setting permissions on depot files - this may take some time ..."
      run "chmod -R 700 /$DD/p4/$SDPInstance" || errmsg "Failed to chmod: /$DD/p4/$SDPInstance"
   else
      msg "Skipped chmod -R of /$DD/p4/$SDPInstance due to '-f'."
   fi
fi

if [[ "$SHAREDDATA" == "FALSE" ]]; then
   if [[ "$FastMode" -eq 0 ]]; then
      msg "Setting ownership on depot files - this may take some time ..."
      run "chown -Rh $OSUSER:$OSGROUP /$DD/p4/$SDPInstance" || errmsg "Failed to chown: /$DD/p4/$SDPInstance"
   else
      msg "Skipped chown -Rh of /$DD/p4/$SDPInstance due to '-f'."
   fi
fi

exit 0
