#!/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
#------------------------------------------------------------------------------

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

if [[ ${BASH_VERSINFO[0]} -lt 4 ]]; then
   echo -e "\\n\\nERROR: $0 requires bash version 4.x+; current bash version is $BASH_VERSION."
   exit 1
fi

# Allow override of P4U_HOME, which is set only when testing P4U scripts.
export P4U_HOME=${P4U_HOME:-/p4/common/bin}
export SDP_ENV=${SDP_ENV:-/p4/common/bin/p4_vars}
export P4U_LIB=${P4U_LIB:-/p4/common/lib}
export P4U_ENV=$P4U_LIB/p4u_env.sh
export P4U_LOG=Unset
export VERBOSITY=${VERBOSITY:-3}

# Environment isolation.  For stability and security reasons, prepend
# PATH to include dirs where known-good scripts exist.
# known/tested PATH and, by implication, executables on the PATH.
export PATH=$P4U_HOME:$PATH:~/bin:.
export P4CONFIG=${P4CONFIG:-.p4config}
export P4ENVIRO=/dev/null/.p4enviro

[[ -r "$P4U_ENV" ]] || {
   echo -e "\\nError: Cannot load environment from: $P4U_ENV\\n\\n"
   exit 1
}

declare BASH_LIBS=$P4U_ENV
BASH_LIBS+=" $P4U_LIB/libcore.sh"
BASH_LIBS+=" $P4U_LIB/libp4u.sh"

for bash_lib in $BASH_LIBS; do
   # shellcheck disable=SC1090
   source "$bash_lib" ||\
      { echo -e "\\nFATAL: Failed to load bash lib [$bash_lib]. Aborting.\\n"; exit 1; }
done

declare Version=2.7.2
declare -i StartupCmdNumFirst
declare -i StartupCmdNumLast
declare -i PreflightOK=1
declare RandomPassword=
declare JournalPrefix=
declare -i OverallReturnStatus=0
declare ServerSpec=
declare ServerSpecFile=
declare ShortServerSpec=
declare ServiceUser=
declare ServiceUserSpecFile=
declare ServiceUsersGroup=ServiceUsers
declare TargetPort=
declare TmpDir=
declare TmpFile=
declare ProtectsFile=
declare GroupSpecFile=
declare ServicePasswdFile=
export VERBOSITY=3


#==============================================================================
# Local Functions

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

   vvmsg "$THISSCRIPT: EXITCODE: $OverallReturnStatus"

   # Stop logging.
   [[ "${P4U_LOG}" == off ]] || stoplog

   # Don't litter.
   cleanTrash

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

function bail {
   declare msg="${1:-Unknown Error}"
   declare -i rc

   rc="${2:-1}"
   echo -e "\\n$THISSCRIPT (line: ${BASH_LINENO[0]}): FATAL: $msg\\n\\n" >&2

   exit "$rc"
}

#------------------------------------------------------------------------------
# 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 errorMessage=${2:-Unset}

   if [[ "$errorMessage" != Unset ]]; then
      msg "\\n\\nUsage Error:\\n\\n$errorMessage\\n\\n"
   fi

   echo "USAGE for $THISSCRIPT v$Version:

$THISSCRIPT -i <SDP_Instance> -t <Type> -s <Site_Tag> -r <Replica_Host> [-f <From_ServerID>] [-p] [-L <log>] [-v<n>] [-n] [-D]

or

$THISSCRIPT [-h|-man|-V]
"
   if [[ $style == -man ]]; then
      echo -e "
DESCRIPTION:
	This script simplifies the task of creating Helix Core replicas and
	edge servers, and helps ensure they are setup with best practices.

	This script does all the metadata configuration to be executed on the
	master server that must be baked into a seed checkpoint for creating
	the replica/edge. It also provides enough information to create,
	transfer, and load seed checkpoints into the replica/edge.  This
	essentially captures the planning for a new replica, and can be
	done before the physical infratructure (hardware and storage) is
	ready.

	Before using this script, a set of geograpic site tags must be defined.
	See the FILES: below for details on a site tags.

	This script adheres to the these SDP Standards:
	* Server Spec Naming Standard: https://swarm.workshop.perforce.com/view/guest/perforce_software/sdp/main/doc/ServerSpecNamingStandard.html
	* Journal Prefix Standard: https://swarm.workshop.perforce.com/view/guest/perforce_software/sdp/main/doc/JournalPrefixStandard.html

	This script does the following to help create a replica or edge server:
	* Generates the server spec for the the replica.
	* Generates a server spec for master server (if needed).
	* Sets configurables ('p4 configure' settings) for replication.
	* Selects the correct 'Services' based on replica type.
	* Creates service user for the replica, and sets a password.
	* Creates service user for the master (if needed), and sets a password.
	* Addes newly created service users to the group '$ServiceUsersGroup'.
	* Verifies the group $ServiceUsersGroup is granted super access in the
	protections table (and with the '-p', updations Protections).

	After these steps are completed, detailed instructions are presented
	to the user through the remaining steps needed to complete the
	deployment of the replica.  This starts with creating a new
	checkpoint to capture all the metadata changes made by this
	script.

SERVICE USERS:
	Service users created by this type are always of type 'service',
	and so will not consume a licensed seat.

	Service users also have an 'AuthMethod' of 'perforce' (not
	'ldap') as is required by 'p4d' for 'service' users.  Passwords
	set for service users are long 32 character random strings
	that are not stored, as they are never needed.  Login tickets for
	service users are generated using: p4login -service -v

OPTIONS:
 -i <SDP_Instance>
	Specify the SDP Instance.

 -t <Type>
	Specify the replica type tag. The type corresponds to the 'Type:' and
	'Services:' field of the server spec, which describes the type of services
	offered by a given replica.

	Valid values are:
	* ha:   High Availability standby replica, for 'p4 failover' (P4D 2018.2+)
	* ham:  High Availability metadata-only standby replica, for 'p4 failover' (P4D 2018.2+)
	* ro:   Read-Only standby replica.
	* rom:  Read-Only standby replica, Metadata only.
	* fr:   Forwarding Replica (Unfiltered).
	* fs:   Forwarding Standby (Unfiltered).
	* frm:  Forwarding Replica (Unfiltered, Metadata only).
	* fsm:  Forwarding Standby (Unfiltered, Metadata only).
	* ffr:  Filtered Forwarding Replica.  Not a valid failover target.
	* edge: Edge Server. Filtered by definition.

	Replicas with 'standby' are always unfiltered, and use the 'journalcopy'
	method of replication, which copies a byte-for-byte verbatim journal file
	rather than one that is merely logically equivalent.

	The tag has several purposes:
	1. Short Hand. Each tag represents a combination of 'Type:' and fully
	qualified 'Services:' values used in server specs.

	2. Distillation. Only the most useful Type/Services combinations have a
	shorthand form.

	3. For forwarding replicas, the name includes the critical distinction of
	whether any replication filtering is used; as filtering of any kind disqualifies
	a replica from being a potential failover target. (No such distinction is
	needed for edge servers, which are filtered by definition).

 -s <Site_Tag>
	Specify a geographic site tag indicating the location and/or data center where
	the replica will physically be located. Valid site tags are defined in the site
	tags file:

	$SiteTagsFile

 -r <Replica_Host>
	Specify the target replica host.

 -f <From_ServerID>
	Specify ServerID of the P4TARGET server from which we are replicating.
	This is used to populate the 'ReplicatingFrom' field of the server
	spec. The value must be a valid ServerID.

	By default, this is determined dynamically by checking the ServerID
	of the master server. This option should be used if the target is
	something other than the master. For example, to create an HA replica
	of an edge server, you might specify something like '-f p4d_edge_syd'.

 -p	This script performs a check to ensure that the Protections table grants
	super access to the group $ServiceUsersGroup.

	By default, an error is displayed if the check fails, i.e. if super user
	access for the group $ServiceUsersGroup cannot be verified. This is
	because, by default, we want to avoid making changes to the Protections
	table. Some sites have local policies or custom automation that requires
	site-specific procedures to update the Protections table.

	If '-p' is specified, an attempt is made to append the Protections table
	an entry like:

	super group $ServiceUsersGroup * //...

 -v<n>	Set verbosity 1-5 (-v1 = quiet, -v5 = highest).

 -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 in the logs
	directory referenced by \$LOGS environment variable, in a file named
	mkrep.<timestamp>.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.'

 -n	No-Op. Prints commands instead of running them.

 -D	Set extreme debugging verbosity.

HELP OPTIONS:
 -h	Display short help message
 -man	Display man-style help message
 -V	Dispay version info for this script and its libraries.

FILES:
	This Site Tags file defines the list of valid geographic site tags:
	$SiteTagsFile

	The contains one-line entries of the form:
	
	<tag>: <description>

	where <tag> is a short alphanumeric tag name for a geographic location,
	data center, or other useful distinction. This tag is incorporated into
       	the ServerID of replicas or edge servers created by this script.  Tag
	names should be kept short, ideally no more than about 5 characters in
	length.

	The <description> is a one-line text description of what the tag
	refers to, which may contain spaces and ASCII punctuation.

	Blank lines and lines starting with a '#' are considered comments
	and are ignored.

REPLICA SERVER MACHINE SETUP:
	The replica/edge server machine must be have the SDP structure installed,
	either using the mkdirs.sh script included in the SDP, or the Helix
	Installer for 'green field' installations.

	When setting up an edge server, a replica of an edge server, or filtered
	replica, confirm that the JournaPrefix Standard (see URL above) structure
	has the separate checkpoints folder as identified in the 'Second Form' in
	the standard.  A baseline SDP structure can typically be extended by running
	commands like like these samples (assuming a ServerID of p4d_edge_syd or
	p4d_ha_edge_syd):

	   mkdir /hxdepots/p4/1/checkpoints.edge_syd
	   cd /p4/1
	   ln -s /hxdepots/p4/1/checkpoints.edge_syd

EXAMPLES:
	EXAMPLE 1 - Set up a High Availability (HA) Replica of the master.

	Add an HA replica to instance 1 to run on host bos-helix-02:
	$THISSCRIPT -i 1 -t ha -s bos -r bos-helix-02

	EXAMPLE 2 - Add an Edge Server to the topology.

	Add an Edge server to instance acme to run on host syd-helix-04:

	$THISSCRIPT -i acme -t edge -s syd -r syd-helix-04

	EXAMPLE 3 - Setup an HA replica of an edge server.

	Add a HA replica of the edge server to instance acme to run on host syd-helix-05:

	$THISSCRIPT -i acme -t ha -f p4d_edge_syd -s syd -r syd-helix-05
"
   fi

   exit 1
}

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

declare ReplicaHost=Unset
declare ReplicaTypeTag=Unset
declare ReplicaType=
declare FromServerID=Unset
declare FromServerP4PORT=
declare FromServerHost=
declare -i CreateFromServerID=0
declare -i CreateMasterServiceUser=0
declare SiteTag=Unset
declare SiteTagsFile="${P4CCFG:-/p4/common/config}/SiteTags.cfg"
declare SDPInstance=Unset
#declare -i Interactive=1
declare -i MetadataOnly=0
declare -i shiftArgs=0
declare -i UpdateProtections=0

set +u
while [[ $# -gt 0 ]]; do
   case $1 in
      (-h) usage -h;;
      (-man) usage -man;;
      (-r) ReplicaHost="$2"; shiftArgs=1;;
      (-t) ReplicaTypeTag="$2"; shiftArgs=1;;
      (-i) SDPInstance="$2"; shiftArgs=1;;
      (-s) SiteTag="$2"; shiftArgs=1;;
      (-f) FromServerID="$2"; shiftArgs=1;;
      (-p) UpdateProtections=1;;
      (-V) show_versions; exit 1;;
      (-v1) export VERBOSITY=1;;
      (-v2) export VERBOSITY=2;;
      (-v3) export VERBOSITY=3;;
      (-v4) export VERBOSITY=4;;
      (-v5) export VERBOSITY=5;;
      (-L) export P4U_LOG="$2"; shiftArgs=1;;
      (-n) export NO_OP=1;;
      (-D) set -x;; # Debug; use bash 'set -x' mode.
      (*) usageError "Unknown arg ($1).";;
   esac

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

#==============================================================================
# Command Line Verification

[[ "$P4U_LOG" == Unset ]] && \
   P4U_LOG=${LOGS:-/tmp}/mkrep.$(date +'%Y%m%d-%H%M').log

[[ $SDPInstance == Unset ]] && usageError "\\nThe '-i <SDP_Instance>' parameter is required."
[[ $ReplicaHost == Unset ]] && usageError "\\nThe '-r <Replica_Host>' parameter is required."
[[ $ReplicaTypeTag == Unset ]] && usageError "\\nThe '-t <Type>' parameter is required."
[[ $SiteTag == Unset ]] && usageError "\\nThe '-s <Site_Tag>' parameter is required."

# Set log file to point at SDP_INSTANCE.
P4U_LOG="/p4/${SDPInstance}/logs/mkrep.$(date +'%Y%m%d-%H%M%S').log"

case "$ReplicaTypeTag" in
   (ha) ReplicaType=standby;;                             # HA Mandatory Standby replica.
   (ham) ReplicaType=standby; MetadataOnly=1;;            # HA Mandatory Standby replica.
   (ro) ReplicaType=standby;;                             # Read-Only Standby replica.
   (rom) ReplicaType=standby; MetadataOnly=1;;            # Read-Only Standby replica, Metadata only.
   (fr) ReplicaType=forwarding-replica;;                  # Forwarding Replica (Unfiltered).
   (fs) ReplicaType=forwarding-standby;;                  # Forwarding Standby (Unfiltered).
   (frm) ReplicaType=forwarding-replica; MetadataOnly=1;; # Forwarding Replica (Unfiltered), Metdata only.
   (fsm) ReplicaType=forwarding-standby; MetadataOnly=1;; # Forwarding Standby (Unfiltered).
   (ffr) ReplicaType=forwarding-replica;;                 # Filtered Forwarding Replica
   (edge) ReplicaType=edge-server;;                       # Edge Server. Filtered by def'n, cannot be Metdata only.
   (*) usageError "The specified replica type tag [$ReplicaTypeTag] is invalid.";;
esac

# We set JournalPrefix value based on the JournalPrefixStandard referenced
# in this script's manual page. There are 3 possibilities:
# - Unfiltered replicas (e.g. ha targeting the master) use the basic form of
# jouranlPrefix, i.e. the same as the master, e.g. /p4/1/checkpoints/p4_1.
# - Edges and filtered replicas use a journaPrefix with the shortend ServerID.
# - Replicas of edge servers use the journalPrefix of their target edge
# server.

if [[ "$ReplicaTypeTag" == "edge" || "$ReplicaTypeTag" == "ffr" || "$ReplicaTypeTag" == "fsm" || "$ReplicaTypeTag" == "frm" || "$ReplicaTypeTag" == "ham" || "$ReplicaTypeTag" == "rom" ]]; then
   ShortServerSpec="${ReplicaTypeTag}_${SiteTag}"
   ServerSpec="p4d_${ShortServerSpec}"
   # shellcheck disable=SC2153
   JournalPrefix="$P4HOME/checkpoints.${ShortServerSpec}/p4_${SDPInstance}.${ShortServerSpec}"
elif [[ "$FromServerID" == "p4d_edge"* && "$ReplicaTypeTag" == "ha"* ]]; then
   FromServerP4PORT=$("$P4BIN" -ztag -F %ExternalAddress% server -o "$FromServerID")
   FromServerHost=${FromServerP4PORT%:*}
   FromServerHost=${FromServerHost#*:}
   ShortServerSpec="${FromServerID#p4d_}"
   ServerSpec="p4d_${ReplicaTypeTag}_edge_${SiteTag}"
   JournalPrefix="$P4HOME/checkpoints.${ShortServerSpec}/p4_${SDPInstance}.${ShortServerSpec}"
else
   ShortServerSpec=
   ServerSpec="p4d_${ReplicaTypeTag}_${SiteTag}"
   JournalPrefix="$P4HOME/checkpoints/p4_${SDPInstance}"
fi

if [[ "$FromServerID" != "Unset" && "$ReplicaTypeTag" == "ha"* ]]; then
   if [[ "$FromServerID" != "p4d_edge"* ]]; then
      usageError "An HA replica can only be created from the master or edge server. So if '-f <FromServerID>' is specified with '-t ha', the '-f' value must refer to an edge server, with a value prefixed with 'p4d_edge_'."
   fi
fi

declare -i tagFound=0
if [[ -r "$SiteTagsFile" ]]; then
   while read -r line; do
      [[ $line == "#*" ]] && continue
      # shellcheck disable=SC2086 disable=SC2116
      [[ -z "$(echo $line)" ]] && continue
      [[ "$line" == *":"* ]] || continue
      tag=${line%%:*}

      if [[ "$tag" == "$SiteTag" ]]; then
         tagFound=1
         break
      fi
   done < "$SiteTagsFile"
else
   bail "Missing site tag configuration file [$SiteTagsFile]. Aborting."
fi

[[ $tagFound -eq 1 ]] ||\
   bail "Failed to find specified site tag [$SiteTag] inite tag configuration file [$SiteTagsFile]. Aborting."

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

trap terminate EXIT SIGINT SIGTERM

TmpDir=$(mktemp -d)
TmpFile="$TmpDir/tmpFile.$THISSCRIPT"
ProtectsFile="$TmpDir/protect.p4s"
GroupSpecFile="$TmpDir/group.$ServiceUsersGroup.p4s"

GARBAGE+=" $TmpDir"

touch "${TmpFile}" || bail "Couldn't touch tmp file [$TmpFile]."

if [[ "${P4U_LOG}" != off ]]; then
   touch "${P4U_LOG}" || bail "Couldn't touch log file [${P4U_LOG}]."

   # Redirect stdout and stderr to a log file.
   exec > >(tee "${P4U_LOG}")
   exec 2>&1

   initlog
fi

msg "Starting $THISSCRIPT v$Version as $OSUSER@${HOSTNAME%%.*} at $(date) as:\\n$CMDLINE\\n"

msg "\\nPhase 1 is automated processing, which this script performs. Phase 2 is manual\\nprocessing, for which this script provides guidance."

msg "${H}\\nPhase 1.0: Environment Setup and Preflight Checks."

msg "Loading SDP environment for instance $SDPInstance."
# shellcheck disable=SC1090
source "$SDP_ENV" "$SDPInstance"

# If the '-f <from_serverid>' was specified, use it.  If not, get the ServerID of the
# master/commit server, one with a Services value of 'commit-server' or simply 'standard'.
# As a last resort, e.g. if the master server does not have a server spec, use 'p4 info'
# on the current server.
msg "Preflight check for master ServerID."
if [[ "$FromServerID" == Unset ]]; then
   # From the list of server specs, find the master/commit server spec.
   FromServerID=$("$P4BIN" -ztag -F "%ServerID% %Services%" servers |\
      grep -E ' (standard|commit-server)')

   if [[ -n "$FromServerID" ]]; then
      FromServerID=${FromServerID%% *}
      msg "Verified: Master ServerID ($FromServerID) exists."
      if [[ -n "$("$P4BIN" -ztag -F %Update% user -o "svc_${FromServerID}")" ]]; then
         msg "Verified: Serivce user for ServerID $FromServerID exists: svc_${FromServerID}"
      else
         msg "Serivce user for ServerID $FromServerID will be created: svc_${FromServerID}"
         CreateMasterServiceUser=1
      fi
   else
      if [[ -n "${P4MASTER_ID:-}" ]]; then
         FromServerID="$P4MASTER_ID"
         CreateFromServerID=1
         CreateMasterServiceUser=1
         msg "Server spec $FromServerID and service user will be created: svc_${FromServerID}"
      else
         errmsg "No server spec with Services value of 'commit-server' or 'standard' found., and P4MASTER_ID is not set.\\n"
         PreflightOK=0
      fi
   fi
fi

msg "Checking if Protections table references group $ServiceUsersGroup."
if [[ "$("$P4BIN" protects -g $ServiceUsersGroup -m)" == "super" ]]; then
   msg "Verified: Protections table grants super access to group $ServiceUsersGroup."
else
   if [[ "$UpdateProtections" -eq 1 ]]; then
      msg "Protections does not grant access to group $ServiceUsersGroup as required, but '-p' was specified to mitigate this."
   else
      errmsg "Protections does not grant access to group $ServiceUsersGroup as required,\\nand '-p' not specified. Specify '-p' or adust the Protections table to add\\nthis line near the bottom:\\n\\n\\tsuper group $ServiceUsersGroup * //...\\n"
      PreflightOK=0
   fi
fi

# Version check: Require P4D 2018.1+ for using 'standby' replica.
# shellcheck disable=SC2072
if [[ "$ReplicaType" == *"standby" && "$P4D_VERSION" > "2018.1" ]]; then
   msg "P4D is 2018.1+, as recommended for standby replicas."
elif [[ "$P4D_VERSION" > "2016.2" ]]; then
   msg "P4D is 2016.2+, as supported for $ReplicaType replicas."
else
   bail "P4D must be 2018.1+ if using journalcopy replicas, P4D_VERSION is $P4D_VERSION.."
   PreflightOK=0
fi

# Version check: Require P4D 2018.2 for using 'ha' replica, i.e. a 2018.2-style
# standby replica with the 'ReplicatingFrom:' field set.
if [[ "$ReplicaTypeTag" == "ha"* ]]; then
   # shellcheck disable=SC2072
   if [[ "$P4D_VERSION" > "2018.2" ]]; then
      msg "P4D is 2018.2+, as required for 'ha' type replicas that use 'p4 failover'."
   else
      errmsg "P4D must be 2018.2+ if using HA replicas, P4D_VERSION is $P4D_VERSION. Aborting."
      PreflightOK=0
   fi
fi

if  [[ "$PreflightOK" -eq 1 ]]; then
   msg "\\nPreflight checks passed. Continuing."
else
   bail "\\nPreflight checks did not pass. Aborting."
fi

#--------------------------------------------------------------
msg "${H}\\nPhase 1.1: Define Server Spec."

if [[ "$CreateFromServerID" -eq 1 ]]; then
   msg "Creating required master server spec [$FromServerID]."

   ServerSpecFile="${TmpDir}/${FromServerID}.server.p4s"

   echo -e "ServerID: $FromServerID\\n
Type: server\\n
Name: $FromServerID\\n
Services: commit-server\\n
Description:\\n\\tMaster server." > "$ServerSpecFile" || bail "Failed to initialize server spec file [$ServerSpecFile]."

   if [[ "$NO_OP" -eq 0 ]]; then
      "$P4BIN" -s server -i < "$ServerSpecFile" ||\
         bail "Failed to load server spec from file: $ServerSpecFile\\n$(cat "$ServerSpecFile")\\n"
   else
      msg "NO_OP: Would run: $P4BIN -s server -i .LT. ${ServerSpecFile##*/}\\nContents of ${ServerSpecFile##*/}:\\n$(grep -v '^#' "$ServerSpecFile")\\n"
   fi
fi

ServerSpecFile="$TmpDir/$ServerSpec.server.p4s"

if [[ "$P4PORT" == "ssl:" ]]; then
   SSLPrefix=ssl:
else
   SSLPrefix=
fi

# Strip off ssl: and host: element from front of value to just leave numeric port.
ReplicaPortNum=${P4MASTERPORT##*:}

if [[ "$ReplicaTypeTag" == "ha"* ]]; then
   echo -e "ServerID: $ServerSpec\\n
Type: server\\n
Name: $ServerSpec\\n
Options: nomandatory\\n
ReplicatingFrom: $FromServerID\\n
Services: $ReplicaType\\n
ExternalAddress: ${SSLPrefix}${ReplicaHost}:${ReplicaPortNum}\\n
Description:" > "$ServerSpecFile" || bail "Failed to initialize server spec file [$ServerSpecFile]."

msg "The server spec $ServerSpec may be configured to use the 'mandatory' setting in the Options field if desired.  However, it must initially configured as 'nomandatory' to prevent undue stalling of a global topology while a fresh new standby replica gets caught up.  As of P4D 2019.1, new standby replicas cannot be made 'mandatory' until they are online.

After this replica is brought online and is seen to be replicating properly (and up-to-date), consider making it a 'mandatory' replica.

A 'mandatory' replica is one that you can trust is at least as current as all other replicas. This helps ensure a smooth failover from the master server.  However, if the 'mandatory' replica fails, it cannot be easily ignored -- if it stalls, the global topology stalls.

Should that ever occur, you can modify the server spec manually on the master server, changing the 'mandatory' value to 'nomandatory'. That will enable global replication to move on.  It should then be a high priority to figure out what went wrong with the standby replica so that it can be brought back online.\\n"

elif [[ "$ReplicaType" == *"standby" ]]; then
   echo -e "ServerID: $ServerSpec\\n
Type: server\\n
Name: $ServerSpec\\n
Options: nomandatory\\n
ReplicatingFrom: $FromServerID\\n
Services: $ReplicaType\\n
ExternalAddress: ${SSLPrefix}${ReplicaHost}:${ReplicaPortNum}\\n
Description:" > "$ServerSpecFile" || bail "Failed to initialize server spec file [$ServerSpecFile]."
else
   echo -e "ServerID: $ServerSpec\\n
Type: server\\n
Name: $ServerSpec\\n
Services: $ReplicaType\\n
ExternalAddress: ${SSLPrefix}${ReplicaHost}:${ReplicaPortNum}\\n
Description:" > "$ServerSpecFile" || bail "Failed to initialize server spec file [$ServerSpecFile]."
fi

case "$ReplicaTypeTag" in
   (ha) Desc="High Availability Standby Replica (Unfiltered) in ${SiteTag^^}.";;
   (ham) Desc="High Availability Metadata-only Standby Replica (Unfiltered) in ${SiteTag^^}.";;
   (ro) Desc="Read-Only Standby Replica (Unfiltered) in ${SiteTag^^}.";;
   (rom) Desc="Read-Only Standby Replica (Unfiltered, Metadata Only) in ${SiteTag^^}.";;
   (fr) Desc="Forwarding Replica (Unfiltered) in ${SiteTag^^}.";;
   (fs) Desc="Forwarding Standby Replica (Unfiltered) in ${SiteTag^^}.";;
   (frm) Desc="Forwarding Replica (Unfiltered, Metadata Only) in ${SiteTag^^}.";;
   (fsm) Desc="Forwarding Standby Replica (Unfiltered, Metadata Only) in ${SiteTag^^}.";;
   (ffr) Desc="Filtered Forwarding Replica in ${SiteTag^^}.";;
   (edge) Desc="Edge server in ${SiteTag^^}.";;
   (*) bail "\\nInternal Error: Unrecognized replica type tag [$ReplicaTypeTag].";;
esac

echo -e "\\t$Desc\\n" >> "$ServerSpecFile" || bail "Failed to complete server spec file [$ServerSpecFile]."

msg "Creating server spec $ServerSpec with these contents:"
msg "${H}"
cat "$ServerSpecFile"
msg "${H}"

if [[ "$NO_OP" -eq 0 ]]; then
   "$P4BIN" -s server -i < "$ServerSpecFile" ||\
      bail "Failed to load server spec from file: $ServerSpecFile\\n$(cat "$ServerSpecFile")\\n"
else
   msg "NO_OP: Would run: $P4BIN -s server -i .LT. ${ServerSpecFile##*/}\\nContents of ${ServerSpecFile##*/}:\\n$(grep -v '^#' "$ServerSpecFile")\\n"
fi

#--------------------------------------------------------------
msg "${H}\\nPhase 1.2: Set configurables."

ServiceUser="svc_${ServerSpec}"
ServiceUserSpecFile="${TmpDir}/${ServiceUser}.user.p4s"
declare -i ConfigureOK=1

if [[ "$FromServerID" == "$P4MASTER_ID" ]]; then
   TargetPort="$P4MASTERPORT"
else
   TargetPort="$FromServerP4PORT"
fi

run "$P4BIN configure set $ServerSpec#P4TARGET=$TargetPort" || ConfigureOK=0
run "$P4BIN configure set $ServerSpec#db.replication=readonly" || ConfigureOK=0
run "$P4BIN configure set $ServerSpec#rpl.forward.all=1" || ConfigureOK=0
run "$P4BIN configure set $ServerSpec#rpl.compress=4" || ConfigureOK=0
run "$P4BIN configure set $ServerSpec#server=4" || ConfigureOK=0
run "$P4BIN configure set $ServerSpec#monitor=2" || ConfigureOK=0
run "$P4BIN configure set $ServerSpec#serviceUser=$ServiceUser" || ConfigureOK=0

if [[ "$ReplicaType" == *"standby" ]] ; then
   run "$P4BIN configure set $ServerSpec#rpl.journalcopy.location=1" || ConfigureOK=0
fi

run "$P4BIN configure set $ServerSpec#journalPrefix=$JournalPrefix" || ConfigureOK=0

# For 'journalcopy' replicas, i.e. those with a Services value of *'standby',
# startup.1 is the 'journalcopy' command to pull the raw P4JOURNAL file from
# the P4TARGET server, and startup.2 is a 'pull' command with the -L' flag
# to replay P4JOURNAL records into the db.

# With the SDP, the pulled journal appears as a file $LOGS/journal.N, where N
# is the journal counter. The rpl.journalcopy.location=1 setting enables this
# desired behavior.

# For non-journalcopy replicas (including any filtered replicas, including
# edge servers that are filtered by nature), startup.1 is a pull
# command that both pulls journal chunks and replays them into the database.

if [[ "$NO_OP" -eq 0 ]]; then
   if [[ "$Desc" == *"Standby"* ]]; then
      vmsg "Executing: $P4BIN configure set $ServerSpec#startup.1='journalcopy -i 0'"
      # shellcheck disable=SC2140
      "$P4BIN" configure set "$ServerSpec#startup.1"="journalcopy -i 0" || ConfigureOK=0
      vmsg "Executing: $P4BIN configure set $ServerSpec#startup.2='pull -i 1 -L'"
      # shellcheck disable=SC2140
      "$P4BIN" configure set "$ServerSpec#startup.2"="pull -i 1 -L" || ConfigureOK=0
      StartupCmdNumFirst=3
      StartupCmdNumLast=7
   else
      vmsg "Executing: $P4BIN configure set $ServerSpec#startup.1='pull -i 1'"
      # shellcheck disable=SC2140
      "$P4BIN" configure set "$ServerSpec#startup.1"="pull -i 1" || ConfigureOK=0
      StartupCmdNumFirst=2
      StartupCmdNumLast=6
   fi
else
   if [[ "$Desc" == *"Standby"* ]]; then
      vmsg "NO_OP: Would execute: $P4BIN configure set $ServerSpec#startup.1=\"journalcopy -i 0\""
      vmsg "NO_OP: Would execute: $P4BIN configure set $ServerSpec#startup.2=\"pull -i 1 -L\""
      StartupCmdNumFirst=3
      StartupCmdNumLast=7
   else
      vmsg "NO_OP: Would execute: $P4BIN configure set $ServerSpec#startup.1=\"pull -i 1\""
      StartupCmdNumFirst=2
      StartupCmdNumLast=6
   fi
fi

if [[ "$MetadataOnly" -eq 0 ]]; then
   run "$P4BIN configure set $ServerSpec#lbr.replication=readonly" || ConfigureOK=0
   for i in $(seq $StartupCmdNumFirst $StartupCmdNumLast); do
      if [[ "$NO_OP" -eq 0 ]]; then
         vmsg "Executing: $P4BIN configure set $ServerSpec#startup.$i='pull -i 1 -u'"
         # shellcheck disable=SC2140
         "$P4BIN" configure set "$ServerSpec#startup.$i"="pull -i 1 -u" || ConfigureOK=0
      else
         vmsg "NO_OP: Would execute: $P4BIN configure set $ServerSpec#startup.1='pull -i 1'"
      fi
   done
else
   run "$P4BIN configure set $ServerSpec#lbr.replication=shared" || ConfigureOK=0
fi

if [[ $ConfigureOK -eq 1 ]]; then
   msg "Verified:  All configurables were set OK."
   run "$P4BIN configure show allservers" "Showing all persistent configurables." 0 1 0
else
   bail "Errors encountered setting configurables.  See the output above. Aborting."
fi

#--------------------------------------------------------------
msg "${H}\\nPhase 1.3: Create replica service user $ServiceUser."

echo -e "User: $ServiceUser\\n
Email: ${MAILFROM#\#}\\n
FullName: Replication Server User for $ServerSpec\\n
Type: service\\n
AuthMethod: perforce\\n" > "$ServiceUserSpecFile" || bail "Failed to initialize user spec file [$ServiceUserSpecFile]."

vmsg "Contents of $ServiceUserSpecFile:"
vmsg "${H}"
[[ $VERBOSITY -gt 2 ]] && cat "$ServiceUserSpecFile"
vmsg "${H}"

if [[ "$NO_OP" -eq 0 ]]; then
   "$P4BIN" user -f -i < "$ServiceUserSpecFile" || \
      bail "Failed to load user spec from file: $ServiceUserSpecFile\\n$(cat "$ServiceUserSpecFile")\\n"
else
   msg "NO_OP: Would run: $P4BIN user -f -i .LT. ${ServiceUserSpecFile##*/}:\\nContents of ${ServiceUserSpecFile##*/}:\\n$(grep -v '^#' "$ServiceUserSpecFile")\\n"
fi

ServicePasswdFile="$TmpDir/.p4passwd.$P4SERVER.$ServiceUser"
touch "$ServicePasswdFile" || bail "Failed to initialize password file $ServicePasswdFile."

if [[ -n "$(command -v sha256sum)" ]]; then
   RandomPassword=$(date +%s | sha256sum | base64 | head -c 32)
elif [[ -n "$(command -v md5sum)" ]]; then
   RandomPassword=$(date +%s | md5sum | base64 | head -c 32)
else
   RandomPassword=$(date +%s | sum | base64 | head -c 32)
fi

chmod 600 "$ServicePasswdFile"
echo "$RandomPassword" > "$ServicePasswdFile"
echo "$RandomPassword" >> "$ServicePasswdFile"

if [[ "$NO_OP" -eq 0 ]]; then
   msg "Setting password for service user $ServiceUser."
   "$P4BIN" passwd "$ServiceUser" < "$ServicePasswdFile"
else
   msg "NO_OP: Would run: $P4BIN passwd $ServiceUser .LT. $ServicePasswdFile"
fi

if [[ "$CreateMasterServiceUser" -eq 1 ]]; then
   MasterServiceUserSpecFile="${TmpDir}/${FromServerID}.user.p4s"
   MasterServiceUser=svc_${FromServerID}
   msg "Creating service $MasterServiceUser for master server spec."

   echo -e "User: $MasterServiceUser\\n
Email: ${MAILFROM#\#}\\n
FullName: Server User for $FromServerID\\n
Type: service\\n
AuthMethod: perforce\\n" > "$MasterServiceUserSpecFile" ||\
      bail "Failed to initialize user spec file [$MasterServiceUserSpecFile]."

   vmsg "Contents of $MasterServiceUserSpecFile:"
   vmsg "${H}"
   [[ $VERBOSITY -gt 2 ]] && cat "$MasterServiceUserSpecFile"
   vmsg "${H}"

   if [[ "$NO_OP" -eq 0 ]]; then
      "$P4BIN" user -f -i < "$MasterServiceUserSpecFile" || \
         bail "Failed to load user spec from file: $MasterServiceUserSpecFile\\n$(cat "$MasterServiceUserSpecFile")\\n"
   else
      msg "NO_OP: Would run: $P4BIN user -f -i .LT. ${MasterServiceUserSpecFile##*/}:\\nContents of ${MasterServiceUserSpecFile##*/}:\\n$(grep -v '^#' "$MasterServiceUserSpecFile")\\n"
   fi

   MasterServicePasswdFile="$TmpDir/.p4passwd.$P4SERVER.$MasterServiceUser"
   touch "$MasterServicePasswdFile" ||\
      bail "Failed to initialize password file $MasterServicePasswdFile."

   chmod 600 "$MasterServicePasswdFile"
   echo "$RandomPassword" > "$MasterServicePasswdFile"
   echo "$RandomPassword" >> "$MasterServicePasswdFile"

   if [[ "$NO_OP" -eq 0 ]]; then
      msg "Setting password for master service user $MasterServiceUser."
      "$P4BIN" passwd "$MasterServiceUser" < "$MasterServicePasswdFile"
   else
      msg "NO_OP: Would run: $P4BIN passwd $MasterServiceUser .LT. $MasterServicePasswdFile"
   fi
fi

#--------------------------------------------------------------
msg "${H}\\nPhase 1.4: Make replica service user a super user with unlimited timeout."

msg "Checking if Protections table references group $ServiceUsersGroup."

if [[ "$UpdateProtections" -eq 1 ]]; then
   msg "Adding protections table entry to reference group $ServiceUsersGroup."

   "$P4BIN" protect -o | grep -v '^#' | grep -v '^Update:' > "$ProtectsFile" ||\
      bail "Failed to dump protections to tmp file: $ProtectsFile"

   echo -e "\\tsuper group $ServiceUsersGroup * //..." >> "$ProtectsFile" ||\
      bail "Failed to update file: $ProtectsFile"

   vmsg "Contents of updated $ProtectsFile:"
   vmsg "${H}"
   [[ $VERBOSITY -gt 3 ]] && cat "$ProtectsFile"
   vmsg "${H}"

   if [[ "$NO_OP" -eq 0 ]]; then
      "$P4BIN" protect -i < "$ProtectsFile" ||\
         bail "Failed to load updated Protections table from file: $ProtectsFile"
   else
      msg "NO_OP: Would run: $P4BIN protect -i .LT. ${ProtectsFile##*/}"
   fi
fi

msg "Checking if serivce user $ServiceUser is in service users group $ServiceUsersGroup."

# shellcheck disable=SC2143
if [[ -n $("$P4BIN" groups "$ServiceUser" | grep "^$ServiceUsersGroup$") ]]; then
   msg "Verified: Serivce user $ServiceUser is in service users group $ServiceUsersGroup."
else
   # This logic will create the group spec for service users if it does not already exist,
   # or add our new service user to the group if it already exists.  The 'p4 group -o'
   # command generates a valid group spec whether the spec actually exists on the server or
   # not.
   if [[ "$CreateMasterServiceUser" -eq 1 ]]; then
      msg "Adding service users $ServiceUser and $MasterServiceUser to group $ServiceUsersGroup."
      "$P4BIN" group -o "$ServiceUsersGroup" | grep -v '^#' |\
         sed "s:43200:unlimited:g;\$ s/.*/\t$ServiceUser\n\t$MasterServiceUser/" > "$GroupSpecFile" ||\
         bail "Failed to update group spec file: $GroupSpecFile"
   else
      msg "Adding service user $ServiceUser to group $ServiceUsersGroup."
      "$P4BIN" group -o "$ServiceUsersGroup" | grep -v '^#' |\
         sed "s:43200:unlimited:g;\$ s/.*/\t$ServiceUser/" > "$GroupSpecFile" ||\
         bail "Failed to update group spec file: $GroupSpecFile"
   fi

   vmsg "Contents of $GroupSpecFile:"
   vmsg "${H}"
   [[ $VERBOSITY -gt 3 ]] && cat "$GroupSpecFile"
   vmsg "${H}"

   if [[ "$NO_OP" -eq 0 ]]; then
      "$P4BIN" -s group -i < "$GroupSpecFile" ||\
         bail "Failed to load group spec from file: $GroupSpecFile\\n$(cat "$GroupSpecFile")\\n"
   else
      msg "NO_OP: Would run: $P4BIN -s group -i .LT. ${GroupSpecFile##*/}\\nContents of ${GroupSpecFile##*/}:\\n$(grep -v '^#' "$GroupSpecFile")\\n"
   fi
fi

#--------------------------------------------------------------
if [[ $OverallReturnStatus -eq 0 ]]; then
   declare -i N=1
   declare SampleCheckpoint=
   msg "${H}\\nAll automated processing in Phase 1 completed successfully.\\n\\n"

   msg "${H}\\nNow Phase 2, manual operations, can begin. This can occur immediately or or long\\nafter Phase 1. Use the most current checkpoints when Phase 2 is executed.\\n"

   msg "First, create a seed checkpoint. Every edge or replica needs an inital seed checkpoint.\\n"

   if [[ "$FromServerID" == "p4d_edge"* && "$ReplicaTypeTag" == "ha"* ]]; then
      msg "STEP $N. Login as ${OSUSER} on the server machine ${FromServerHost} where the edge server (ServerID=$FromServerID) is running."
      N+=1
   else
      msg "STEP $N. Login as ${OSUSER} on the master server machine (${P4MASTERHOST})."
      N+=1
   fi

   msg "STEP $N. Set your shell environment with:\\n\\tcd $P4CBIN\\n\\tsource p4_vars $SDPInstance\\n"
   N+=1

   if [[ "$ReplicaTypeTag" == "ffr" ]]; then
      msg "STEP $N. Define replication filtering.  If you choose to filter by using ArchiveDataFilter and/or ClientDataFilter fields of the server spec, make those changes:\\n\\tp4 server $ServerSpec\\n\\nAlternately, if you choose to filter by database table, use 'p4 configure' commands to modify the $ServerSpec#startup.<n> settings related to the ServerID, adding the '-T' flag to the single 'pull' startup command that pulls metadata.\\n"
      N+=1
   fi

   if [[ "$ReplicaTypeTag" == "edge" || "$ReplicaTypeTag" == "ffr" ]]; then
      msg "STEP $N. Do a journal rotation to update offline_db:\\n\\trotate_journal.sh ${SDPInstance}\\n\\nThis should take only a few minutes, although may take longer for very large data sets (peraps up to 30 minutes). The duration is driven by the time it takes to do a journal rotation and replay the rotated journal to the offline_db. This will typically be a small fraction of the duration of a checkpoint.\\n\\n"
      N+=1
   fi

   if [[ "$ReplicaTypeTag" == "edge" ]]; then
      SampleCheckpoint="/p4/${SDPInstance}/checkpoints/p4_${SDPInstance}.${ServerSpec#p4d_}.seed.NNNN.gz"
      msg "STEP $N. As this is an edge server, create the special edge seed checkpoint:\\n\\tnohup edge_dump.sh ${SDPInstance} ${ServerSpec} < /dev/null > /dev/null 2>&1 &\\n"
      N+=1
      msg "STEP $N. Monitor the log until successful completion:\\n\\ttail -f \$(ls -t \$LOGS/edge_dump.*.log|head -1)\\n"
      N+=1
   elif [[ "$ReplicaTypeTag" == "ffr" ]]; then
      SampleCheckpoint="/p4/${SDPInstance}/checkpoints/p4_${SDPInstance}.${ServerSpec#p4d_}.seed.ckp.gz"
      msg "STEP $N. As this is a filtered replica, create the special filtered seed checkpoint:\\n\\tnohup p4d_${SDPInstance} -r /p4/${SDPInstance}/offline_db -P $ServerSpec -J off -Z -jd $SampleCheckpoint < /dev/null > \$LOGS/seed.$ServerSpec.log 2>&1 &\\n"
      N+=1
      msg "STEP $N. Monitor the log until successful completion:\\n\\ttail -f \$LOGS/seed.${ServerSpec}.log\\n"
      N+=1
   elif [[ "$FromServerID" == "p4d_edge"* && "$ReplicaTypeTag" == "ha"* ]]; then
      msg "STEP $N. As this a replica of an edge server, first take a checkpoint on the edge server by running this script:\\n\\trequest_replica_checkpoint.sh ${SDPInstance}\\n\\nThis will execute instantly, as it only requests a checkpoint on the next journal rotation of the master server, and does not directly start checkpoint processing."
      N+=1
      msg "STEP $N. Login as ${OSUSER} on the master server machine ${P4MASTERHOST}."
      N+=1
      msg "STEP $N. Do a journal rotation on the master to trigger the edge server to start\\nits checkpoint. This should take only a few minutes, although may take longer\\nfor very large data sets (peraps up to 30 minutes). The duration is driven by\\nthe time it takes to do a journal rotation and replay the rotated journal to the\\noffline_db. This will typically be a small fraction of the duration of a checkpoint operation.  Do the journal rotation with this command:\\n\\trotate_journal.sh ${SDPInstance}\\n\\n"
      N+=1
      msg "STEP $N. Back on the server machine on which you are creating the edge\\ncheckpoint, monitor the checkpoint until completion.  Do so by using the 'watch'\\ncommand to monitor an 'ls -lrt' command to observe the new checkpoint being\\ncreated, and looking out for the creation of a *.md5 file. When the new *.md5\\nfile is created, you know the checkpoint get created.  The 'watch' command would look like:\\n\\twatch ls -lrt $JournalPrefix*\\n\\n"
      N+=1
   else
      SampleCheckpoint="/p4/${SDPInstance}/checkpoints/p4_${SDPInstance}.ckp.NNNN.gz"
      msg "STEP $N. Create a new regular checkpoint to seed the replica.  Execute this command:\\n\\tnohup daily_checkpoint.sh $SDPInstance < /dev/null > /dev/null 2>&1 &\\n\\nNote: This step can be skipped if you choose to wait until the next regular daily checkpoint is created before proceeding on to PART 2."
      N+=1
   msg "STEP $N. Monitor the checkpoint.log file until successful completion:\\n\\ttail -f \$LOGS/checkpoint.log\\n"
      N+=1
   fi

   msg "\\n=== PART 2 - Load Checkpoint on Replica ===\\n"
   msg "STEP $N. Login as ${OSUSER}@${ReplicaHost}."
   N+=1

   msg "STEP $N. Set your environment with:\\n\\tcd /p4/common/bin\\n\\tsource p4_vars $SDPInstance\\n"
   N+=1

   if [[ "$ReplicaTypeTag" == "edge" ]]; then
      msg "STEP $N. Copy the edge seed checkpoint file created in the steps above from\\n${P4MASTERHOST}:${CHECKPOINTS}.  Successfully completed checkpoint files have a\\ncorresponding *.md5 file which must also be copied. That might look like:\\n\\tcd $CHECKPOINTS\\n\\tscp -p $P4MASTERHOST:${SampleCheckpoint/gz/gz.md5}\\n\\tscp -p ${P4MASTERHOST}:${SampleCheckpoint}\\n\\nReplace NNNN with the appropriate journal counter number.\\n"
      N+=1
   elif [[ "$ReplicaTypeTag" == "ffr" ]]; then
      msg "STEP $N. Copy the filtered replica seed checkpoint file and created in the steps above from\\n${P4MASTERHOST}:${CHECKPOINTS}.  Successfully completed checkpoint files have a corresponding\\n*.md5 file which must also be copied. That might look like:\\n\\tcd $CHECKPOINTS\\n\\tscp -p ${P4MASTERHOST}:${SampleCheckpoint}.md5\\n\\tscp -p ${P4MASTERHOST}:${SampleCheckpoint}\\n"
      N+=1
   elif [[ "$FromServerID" == "p4d_edge"* && "$ReplicaTypeTag" == "ha"* ]]; then
      SampleCheckpoint="/p4/$SDPInstance/checkpoints.${FromServerID#p4d_}/p4_${SDPInstance}.${FromServerID#p4d_}.ckp.NNN.gz"
      msg "STEP $N: Copy the checkpoint from the edge server machine ($FromServerHost)\\nto the new edge HA machine.  As $OSUSER@{ReplicaHost}, that might look like:\\n\\tcd /p4/$SDPInstance/checkpoints.${FromServerID#p4d_}\\n\\tscp -p ${FromServerHost}:${SampleCheckpoint/gz/md5} .\\n\\tscp -p ${FromServerHost}:${SampleCheckpoint} .\\n\\n"
      N+=1
   else
      msg "STEP $N. Copy the latest regular checkpoint file created in the steps above from\\n${P4MASTERHOST}:${CHECKPOINTS}.  Successfully completed checkpoint files have a corresponding\\n*.md5 file which must also be copied. That might look like this:\\n\\tcd \$CHECKPOINTS\\n\\tscp -p ${P4MASTERHOST}:${SampleCheckpoint/gz/md5}\\n\\tscp -p ${P4MASTERHOST}:${SampleCheckpoint}\\n\\nReplace NNNN with the appropriate journal counter number.\\n"
      N+=1
   fi

   msg "STEP $N. Create $P4ROOT/server.id file like so:\\n\\techo $ServerSpec > $P4ROOT/server.id\\n"
   N+=1

   if [[ "$ReplicaTypeTag" == "ha"* && "$FromServerID" != "p4d_edge"* ]]; then
      msg "STEP $N. As this machine is a potential target for a 'p4 failover' from the master, it will need a license\\nfile. The IP address in the license file should match that returned by running\\nthe command 'hostname -I' on the replica server machine. The license file should\\nbe copied to this file on that machine: $P4ROOT/license\\n"
      N+=1
   fi

   msg "STEP $N. Verify that you have enough disk space, e.g. with:\\n\\tdf -h $P4ROOT\\n\\nAt least 30x (zipped checkpoint size) is recommended.\\n"
   N+=1

   msg "STEP $N: Login super super user and replication service user to P4TARGET server, like so:\\n\\tp4 -p $TargetPort login -a < $SDP_ADMIN_PASSWORD_FILE\\n\\tp4 -p $TargetPort login $ServiceUser\\n\\nNote: Do not try to use the SDP p4login script, as tries to detect the service user from data in P4ROOT which isn't there yet.\\n"
   N+=1

   msg "STEP $N. Load the checkpoint like so:\\n\\tnohup load_checkpoint.sh $SampleCheckpoint -i ${SDPInstance} -y < /dev/null > /dev/null 2>&1 &\\n"
   N+=1

   msg "STEP $N. Monitor the log until successful completion:\\n\\ttail -f \$(ls -t \$LOGS/load_checkpoint.*.log|head -1)\\n"
   N+=1

   if [[ "$MetadataOnly" -eq 0 ]]; then
      msg "STEP $N. OPTIONAL: Kick off a verify to pull over archive files:\\n\\tnohup p4verify.sh $SDPInstance < /dev/null > /dev/null 2>&1 &\\n\\nWait about one minute, then check that it is off to a good start:\\n\\ttail \$LOGS/p4verify.log. That may run for a long while depending on the scale of the versioned file tree."
      N+=1
   fi

else
   msg "${H}\\nProcessing completed, but with errors.  Scan above output carefully.\\n" 
fi

# Illustrate using $SECONDS to display runtime of a script.
msg "That took $((SECONDS/3600)) hours $((SECONDS%3600/60)) minutes $((SECONDS%60)) seconds.\\n"

# See the terminate() function, which is really where this script exits.
exit "$OverallReturnStatus"
