#!/bin/bash
set -u
#==============================================================================
# This script serves as a guide defining best-practice configurables for a
# production environment.  See documentation regarding configurables here:
# https://help.perforce.com/helix-core/server-apps/cmdref/current/Content/CmdRef/configurables.alphabetical.html
#
# Copyright and license info is available in the LICENSE file included with
# the Server Deployment Package (SDP), and also available online:
# https://workshop.perforce.com/view/p4-sdp/main/LICENSE
#------------------------------------------------------------------------------
# Set P4PORT and P4USER and run p4 login before running this script.

declare ThisScript=${0##*/}
declare ThisUser=
declare Args="$*"
declare CmdLine="$0 $Args"
declare ThisHost=${HOSTNAME%%.*}

#------------------------------------------------------------------------------
# Version ID Block. Relies on +k filetype modifier.
# shellcheck disable=SC2016
declare VersionID='$Id: //p4-sdp/r26.1.0/Server/setup/configure_new_server.sh#1 $ $Change: 33565 $'
declare VersionStream=${VersionID#*//}; VersionStream=${VersionStream#*/}; VersionStream=${VersionStream%%/*};
declare VersionCL=${VersionID##*: }; VersionCL=${VersionCL%% *}
declare Version=${VersionStream}.${VersionCL}
[[ "$VersionStream" == r* ]] || Version="${Version^^}"

declare SpecFile=
declare ProtectsFile=
declare ProtectsTemplate=
declare ChangeCounter=
declare ChangeCounterLine=
declare AccessLevel=
declare DiskSpaceAvail=
declare MinKBFor5GLimits=7340032
declare CleartextPasswordFile=
declare EncryptedPasswordFile=
declare TmpFile=
declare UserCount=
declare SDPRoot=${SDP_ROOT:-/p4}
declare SDPCommon="$SDPRoot/common"
declare SDPCommonBin="$SDPCommon/bin"
declare SDPCommonLib="$SDPCommon/lib"
declare SDPEnv="$SDPCommonBin/p4_vars"
declare SDPCommonCfg="$SDPCommon/config"
declare InstanceCfg=
declare SDPInstance=
declare Log=
declare -i ErrorCount=0
declare -i WarningCount=0
declare -i DoCheckpoint=0
declare -i Force=0
declare -i NoOp=0
declare -i Debug=0
declare -i NewServer=0
declare H1="=============================================================================="

declare P4DInitScript=
declare P4DSystemdServiceFile=

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

#==============================================================================
# Load SDP Library Functions.

if [[ -d "$SDPCommonLib" ]]; then
   # shellcheck disable=SC1090 disable=SC1091
   source "$SDPCommonLib/logging.lib" ||\
      bail "Failed to load bash lib [$SDPCommonLib/logging.lib]. Aborting."
   # shellcheck disable=SC1090 disable=SC1091
   source "$SDPCommonLib/run.lib" ||\
      bail "Failed to load bash lib [$SDPCommonLib/run.lib]. Aborting."
   # shellcheck disable=SC1090 disable=SC1091
   source "$SDPCommonLib/service_management.lib" ||\
      bail "Failed to load bash lib [$SDPCommonLib/service_management.lib]. Aborting."
   # shellcheck disable=SC1090 disable=SC1091
   source "$SDPCommonLib/utils.lib" ||\
      bail "Failed to load bash lib [$SDPCommonLib/utils.lib]. Aborting."
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 a
# 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
# error, usually $1 should be -h so that the longer usage message doesn't
# obscure the error message.
#
# Sample Usage:
# usage
# usage -h
# usage -man
# usage -h "Missing required parameter <RequiredParameter>."
#------------------------------------------------------------------------------
function usage
{
   local style=${1:--h}
   local usageErrorMessage=${2:-Unset}

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

   msg "USAGE for $ThisScript v$Version:

$ThisScript <instance> [-checkpoint] [-f] [-n] [-d|-D]

or

$ThisScript [-h|-man|-V]
"
   if [[ $style == -man ]]; then
      msg "
DESCRIPTION:
	This script configures a new commit server by setting best practices
	configurables, as well as setting SDP-required configurables such as
	server.depot.root and journalPrefix.

	This script is intended to be run on a new, empty P4 data set. If it
	is run on a server with existing configurables, it will abort by
	default to avoid overwriting existing configurables.  Before running
	on a server with existing data, review the configurables it sets to
	ensure they will not cause a problem. (This could be done by
	operating the script on a copy-of-production sandbox or perhaps by
	reviewing the script code).  Use the '-f' (force) option to allow this
	script to be run on a server that already has configuration items set.

REQUIRED PARAMETERS:
	<instance> - Specify the SDP Instance.

OPTIONS:
 -checkpoint

	Specify '-checkpoint' to do a live checkpoint after setting the configurables.

 -f
	Specify '-f' (force) to allow running this script on data set that is not a brand
	new/empty data set.  This script deems a server to be new and empty if the
	change counter is '0'.

 -n
	Enable DRY RUN/Preview mode, displaying commands that would affect data
	rather than executing them.

 -D
	Set extreme debugging verbosity.

LOGGING:
 -L <Log>

	Specify the path to desired log file, or the special value 'off' to disable logging.

	The default log file name is:

	${LOGS:-/tmp}/${ThisScript%.sh}.<timestamp>.log

	This script is self-logging.  That is, output displayed on the screen
	is simultaneously captured in the log file.  Using redirection operators like
	like '> log' or '2>&1' or using 'tee' are unnecessary (but harmless).

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

FILES:

EXAMPLES:
	Example 1: Do a preview:
	$ThisScript 1 -n

	Example 2: Typical operation to configure new instance 1.
	$ThisScript 1

	Example 3: Same as Example 2, and then do a live checkpoint after.
	$ThisScript 1 -checkpoint

	Example 4: Apply configurables to an existing server using '-f'.
	$ThisScript 1 -f

SEE ALSO:
	See 'ccheck.sh -man' to for info on how to check configurables after
	this script has been run.
"
   fi

   exit 2
}

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

declare -i ShiftArgs=0

set +u
while [[ $# -gt 0 ]]; do
   case $1 in
      (-h) usage -h;;
      (-man|--help) usage -man;;
      (-checkpoint) DoCheckpoint=1;;
      (-f) Force=1;;
      (-V|--version) show_versions; exit 0;;
      (-L) Log="$2"; ShiftArgs=1;;
      (-n) NoOp=1;;
      (-d) Debug=1;;
      (-D) Debug=1; set -x;; # Use bash 'set -x' extreme debug mode.
      (-*) usage -h "Unknown option ($1).";;
      (*)
         if [[ -z "$SDPInstance" ]]; then
            SDPInstance="$1"
         else
            usage -h "SDP Instance parameter already provided as '$SDPInstance'; ignoring parameter '$1'."
         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 [[ -n "$SDPInstance" ]]; then
   InstanceCfg="$SDPCommonCfg/p4_${SDPInstance}.vars"
   [[ -r "$InstanceCfg" ]] ||\
      bail "Invalid instance parameter '$SDPInstance' specfied; instance config is issing: $InstanceCfg. Aborting."
else
    bail "The <instance> parameter is required. Aborting."
fi

# shellcheck disable=SC1090
source "$SDPEnv" "$SDPInstance" ||\
   bail "Could not do: source \"$SDPEnv\" \"$SDPInstance\""

# These are defined for use in /p4/common/lib/service_management.lib.
# shellcheck disable=SC2034
P4DInitScript="$P4HOME/bin/p4d_${SDPInstance}_init"
# shellcheck disable=SC2034
P4DSystemdServiceFile="/etc/systemd/system/p4d_${SDPInstance}.service"

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

ThisUser=$(id -n -u)
[[ "$ThisUser" != "$OSUSER" ]] &&\
   usage -h "Run $ThisScript as user '$OSUSER', not '$ThisUser'."

[[ -n "$Log" ]] || Log="${LOGS:-/tmp}/${ThisScript%.sh}.$(date +'%Y%m%d-%H%M%S').log"

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

trap terminate EXIT SIGINT SIGTERM

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

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

   msg "${H1}\nLog is: $Log\n"
fi

msg "Starting $ThisScript v$Version as $ThisUser@$ThisHost on $(date) with \n$CmdLine"

msg "See documentation regarding configurables here:\n
https://help.perforce.com/helix-core/server-apps/cmdref/current/Content/CmdRef/configurables.alphabetical.html\n"

# Capture whether p4d is already running before we (maybe) start it below.
# Used further down to detect a genuinely new server: one whose first-ever
# start at P4D 2026.1 is about to happen as part of this script run.
NewServerBeforeStart=1
svc_is_up p4d && NewServerBeforeStart=0

msg "Starting p4d service (if needed)."
if [[ "$NoOp" -eq 0 ]]; then
   svc_start_p4d
else
   msg "Would have started p4d."
fi

# As of P4D 2026.1, the first-ever start of a data set at 2026.1
# permanently bumps the "security" configurable up to its new default of
# 4 (and similarly hardens dm.user.noautocreate to 2), even if a lower
# value was set beforehand offline via 'p4d -cset'. This is a one-time
# bump tied to that first start (analogous to a version upgrade), not
# something re-enforced on every subsequent start. It blocks the classic
# empty-Protections-table bootstrap this script relies on to create the
# very first super user: even the unauthenticated-friendly 'p4 user -o'
# template read now requires authentication that cannot exist yet. Work
# around this by relaxing both configurables offline (the same
# 'p4d -cset' mechanism already used elsewhere in the SDP -- see
# configure_sample_depot_for_sdp.sh) right after that one-time bump has
# already happened, then starting the server again: a second start does
# not re-trigger the bump, so the relaxed values hold. Only do this for a
# genuinely new server (p4d wasn't already running before the start
# above), so an already-configured/live server is never touched. The live
# 'configure set' calls further below (once $P4USER exists and is logged
# in) restore both configurables to their secure defaults; since the
# server is already past its one-time bump by then, those live changes
# hold across any future restart.
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2026.1" ]] && [[ "$NewServerBeforeStart" -eq 1 ]] && [[ "$NoOp" -eq 0 ]]; then
   msg "Temporarily relaxing P4D 2026.1 Secure By Default configurables to bootstrap the first super user."
   svc_stop_p4d
   "$P4DBIN" -r "$P4ROOT" "-cset security=0" ||\
      errmsg "Failed to set configurable security=0 for initial bootstrap."
   "$P4DBIN" -r "$P4ROOT" "-cset dm.user.noautocreate=0" ||\
      errmsg "Failed to set configurable dm.user.noautocreate=0 for initial bootstrap."
   svc_start_p4d
fi

# Ensure trust if SSL (which doubles as a connectivity check), or if not SSL just
# check connectivity.
dbg "Doing trust and/or connectivity check."
if [[ "$P4PORT" =~ ^ssl[46]*: ]]; then
   msg "Trusting P4PORT [$P4PORT]."
   if [[ "$NoOp" -eq 0 ]]; then
      timeout 10s p4 trust -f -y > /dev/null 2>&1 || bail "Could not trust P4PORT [$P4PORT]. Aborting."
   else
      msg "NO_OP: Would have done: timeout 10s p4 trust -f -y"
   fi
else
   msg "Checking connecition to P4PORT [$P4PORT]."
   if [[ "$NoOp" -eq 0 ]]; then
      timeout 10s p4 -s info -s > /dev/null 2>&1 || bail "Could not connect to P4PORT [$P4PORT]. Aborting."
   else
      msg "NO_OP: Would have done: timeout 10s p4 info -s"
   fi
fi

dbg "Checking access level with 'p4 protects -m'."
AccessLevel=$(p4 protects -m 2>&1)

if [[ -n "$AccessLevel" ]]; then
   dbg "AccessLevel=[$AccessLevel]."
   if [[ "$AccessLevel" == super ]]; then
      dbg "Verified: Access Level for P4USER '$P4USER' is 'super'."
   elif [[ "$AccessLevel" = "Protections table is empty." ]]; then
      dbg "Verified: Protections table is not initialized."
   elif [[ -z "$("$P4DBIN" -r "$P4ROOT" -k db.protect -jd - | grep ^@pv@ | head -1)" ]]; then
      # As of P4D 2026.1, an unauthenticated 'p4 protects -m' against a
      # genuinely empty Protections table no longer returns the informative
      # "Protections table is empty." string above -- it returns a generic
      # authentication error instead (e.g. "Perforce password (P4PASSWD)
      # invalid or unset."), which would otherwise incorrectly bail here.
      # Confirm directly against db.protect instead of relying on that
      # message (same technique already used further below in this script).
      dbg "Verified: Protections table is not initialized (confirmed directly via db.protect)."
   else
      bail "Access level of current P4USER '$P4USER' is '$AccessLevel', but it must be 'super'."
   fi
else
   if [[ "$NoOp" -eq 0 ]]; then
      bail "Could not determine access level granted in Protections to user '$P4USER'."
   else
      msg "NO_OP: Assuming protections granted to user '$P4USER' is super."
   fi
fi

# Determine if this is a fresh new server, and thus if the '-f' option is required to run this
# script.
ChangeCounter=$(p4 counter change 2>/dev/null)

# As of P4D 2026.1, 'p4 counter' also requires authentication -- even for a
# fresh/empty server, where an unset counter traditionally just reads back as
# "0" without needing auth. Confirm directly against db.counters instead when
# the live read comes back empty (same technique as the Protections check
# above): no entry for the 'change' counter at all means it's unset, i.e. 0.
if [[ -z "$ChangeCounter" ]]; then
   ChangeCounterLine=$("$P4DBIN" -r "$P4ROOT" -k db.counters -jd - 2>/dev/null | grep '@change@')
   if [[ -z "$ChangeCounterLine" ]]; then
      ChangeCounter=0
   else
      ChangeCounter=$(echo "$ChangeCounterLine" | sed -E 's/.*@change@ @([^@]*)@.*/\1/')
   fi
fi

# Spoof ChangeCounter=0 in NoOp mode.
[[ "$NoOp" -eq 1 && -z "$ChangeCounter" ]] && ChangeCounter=0

dbg "ChangeCounter is: $ChangeCounter"

[[ "$ChangeCounter" == 0 ]] && NewServer=1

if [[ "$NewServer" -eq 1 ]]; then
   msg "Server instance $SDPInstance is a new data set."
else
   if [[ "$Force" -eq 1 ]]; then
      warnmsg "Server instance '$SDPInstance' is not new. Proceeding anyway due to '-f'."
   else
      bail "Aborting because Server instance '$SDPInstance' is not new. Running $ThisScript against an existing data set. The data set is assumed to be new/empty if the 'change' counter is '0'; it is $ChangeCounter. To proceed anyway, use '-f'."
   fi
fi

# Generate the super user account, but only if there is only a single account
# on the server.
UserCount=$(p4 users 2>/dev/null | head -n 2 | wc -l)

# As of P4D 2026.1, 'p4 users' also requires authentication, even on a
# fresh/empty server where it traditionally just returned no output (and
# thus UserCount=0) without needing auth. That would otherwise miss the
# UserCount==1 condition below and incorrectly skip creating $P4USER. Since
# $NewServer (above) already reliably establishes whether this is a
# genuinely fresh server (via a direct db.counters check, not just a live
# read), trust it here too: a fresh server always has 0 real users at this
# point, which is the case UserCount==1 needs to match to proceed.
[[ "$NewServer" -eq 1 && "$UserCount" -eq 0 ]] && UserCount=1

if (( UserCount == 1 )); then
   msg "Creating user '$P4USER'."
   SpecFile="$(mktemp)"
   if p4 --field User="$P4USER" --field FullName="Perforce P4 Admin" --field Email="$P4USER@${MAILFROM##*@}" user -o "$P4USER" > "$SpecFile"; then
      msg "Creating user '$P4USER'."
      if [[ "$NoOp" -eq 0 ]]; then
         if p4 -s user -f -i < "$SpecFile"; then
            msg "Setting password for user '$P4USER'."
            CleartextPasswordFile="$SDP_ADMIN_PASSWORD_FILE"
            EncryptedPasswordFile="${CleartextPasswordFile}.enc"
            if [[ -r "$EncryptedPasswordFile" ]]; then
               TmpFile=$(mktemp)
               touch "$TmpFile"
               chmod 600 "$TmpFile"
               base64 -d - < "$EncryptedPasswordFile" > "$TmpFile" ||\
                  errmsg "Failed to decrypt password in: $EncryptedPasswordFile"
               yes "$(cat "$TmpFile")" | p4 passwd
               rm -f "$TmpFile"
            elif [[ -r "$CleartextPasswordFile" ]]; then
               yes "$(cat "$CleartextPasswordFile")" | p4 passwd
            else
               errmsg "Could not find encrypted or cleartext password files, neither $EncryptedPasswordFile nor $CleartextPasswordFile exist."
            fi

            "$P4CBIN"/p4login

            # Verify the Protections table is not initialized so we don't overwrite an existing table.
            # Check for any entries in the db.protect table.
            if [[ -z "$("$P4DBIN" -r "$P4ROOT" -k db.protect -jd - | grep ^@pv@ | head -1)" ]]; then
               msg "Initializing Protections table."
               ProtectsFile=$(mktemp)
               ProtectsTemplate="${0%/*}/protect.p4t"
               if [[ -r "$ProtectsTemplate" ]]; then
                  if sed -e "s@__P4USER__@$P4USER@g" "$ProtectsTemplate" > "$ProtectsFile"; then
                     if p4 -s protect -i < "$ProtectsFile"; then
                        msg "Protections table initialized to:\n$(p4 protect -o | grep -v '^#')\n"
                    else
                        errmsg "Failed to load generated Protections file:\n$(cat "$ProtectsFile")"
                    fi
                  else
                     errmsg "Failed to generate Protections file from template. Not initializing protections."
                  fi
               else
                  warnmsg "Skipping Protections table initialization due to missing template: $ProtectsTemplate"
               fi
            else
               warnmsg "Skipping Protections table initialization because Protections table is already initialized."
            fi
         else
            errmsg "Failed to create $P4USER user; tried to load this generated spec file:\n$(cat "$SpecFile")"
         fi
      else
         msg "NO_OP: Would have created user $P4USER with password and initialized Protections. User specs is:\n$(cat "$SpecFile")"
      fi
      rm -f "$SpecFile"
   else
      errmsg "Failed to generate spec file for $P4USER user."
   fi
else
   warnmsg "Skipping $P4USER user creation; more than one user account exists."
fi

# Generate the Automation group with P4USER as member and owner.
if [[ "$(p4 group --exists -o Automation 2>&1)" =~ ^Group\ \' ]]; then
   SpecFile="$(mktemp)"
   if p4 --field Timeout=unlimited --field PasswordTimeout=unlimited --field Owners="$P4USER" --field Users="$P4USER" group -o Automation > "$SpecFile"; then
      msg "Creating group 'Automation'."
      if [[ "$NoOp" -eq 0 ]]; then
         p4 -s group -i < "$SpecFile" ||\
            errmsg "Failed to create Automation group; tried to load this generated spec file:\n$(grep -v ^# "$SpecFile")"
      else
         msg "NO_OP: Would have created Automation group with this spec:\n$(grep -v ^# "$SpecFile")"
      fi
      rm -f "$SpecFile"
   else
      errmsg "Failed to generate spec file for Automation group."
   fi
else
   warnmsg "Skipping Automation group creation; group already exists."
fi

# The server.depot.root configurable was introduced in 2014.1.
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2014.1" ]]; then
   run "p4 -s configure set server.depot.root=$DEPOTS" 1 1 || errmsg "Failed to set configurable server.depot.root."
fi

# The server.rolechecks configurable was introduced in 2011.1.
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2011.1" ]]; then
   run "p4 -s configure set server.rolechecks=1" '' 1 1 || errmsg "Failed to set configurable server.rolechecks."
fi

# SDP-1320: server.startup.autorestart=1 is a new best practice for P4D
# 2026.1+ (the p4d default is 0). Gated to 2026.1+, not just "introduced in",
# since it depends on the P4-7318 fix (gaps in startup.N numbering breaking
# replication on restart) which landed in 2026.1.
# shellcheck disable=SC2072
if [[ ! "$P4D_VERSION" < "2026.1" ]]; then
   run "p4 -s configure set server.startup.autorestart=1" '' 1 1 || errmsg "Failed to set configurable server.startup.autorestart."
fi

run "p4 -s configure set journalPrefix=$CHECKPOINTS/p4_${SDPInstance}" '' 1 1 || errmsg "Failed to set configurable journalPrefix."
run "p4 -s configure set dm.user.noautocreate=2" '' 1 1 || errmsg "Failed to set configurable dm.user.noautocreate."
run "p4 -s configure set dm.info.hide=1" '' 1 1 || errmsg "Failed to set configurable dm.info.hide."
run "p4 -s configure set dm.protects.hide=1" '' 1 1 || errmsg "Failed to set configurable dm.protects.hide."
run "p4 -s configure set dm.user.setinitialpasswd=0" '' 1 1 || errmsg "Failed to set configurable dm.user.setinitialpasswd."

### For now, setting dm.user.resetpassword=1 until P4-27901 is implemented.
### The default for dm.user.resetpassword changes from 1 to 0 in 2026.1; the 0 value is deemed more secure.
### However, that causes breakage with service accounts, per P4-27901. Fix in progress.
run "p4 -s configure set dm.user.resetpassword=1" '' 1 1 || errmsg "Failed to set configurable dm.user.resetpassword."

# For filesys.*.min configurables, use 5G defaults if we have 7G+ of space
# available, otherwise assume this is a demo-scale environment and use 20M.
DiskSpaceAvail=$(df -k "$P4ROOT/" 2>/dev/null | grep / | awk '{print $4}')
if [[ "$DiskSpaceAvail" -ge "$MinKBFor5GLimits" ]]; then
   run "p4 -s configure set filesys.P4ROOT.min=5G" '' 1 1 || errmsg "Failed to set configurable filesys.P4ROOT.min."
else
   run "p4 -s configure set filesys.P4ROOT.min=20M" '' 1 1 || errmsg "Failed to set configurable filesys.P4ROOT.min."
fi

DiskSpaceAvail=$(df -k "$LOGS/" 2>/dev/null | grep / | awk '{print $4}')
if [[ "$DiskSpaceAvail" -ge "$MinKBFor5GLimits" ]]; then
   run "p4 -s configure set filesys.P4JOURNAL.min=5G" '' 1 1 || errmsg "Failed to set configurable filesys.P4JOURNAL.min."
else
   run "p4 -s configure set filesys.P4JOURNAL.min=20M" '' 1 1 || errmsg "Failed to set configurable filesys.P4JOURNAL.min."
fi

if [[ "$DiskSpaceAvail" -ge "$MinKBFor5GLimits" ]]; then
   run "p4 -s configure set filesys.P4LOG.min=5G" '' 1 1 || errmsg "Failed to set configurable filesys.P4LOG.min."
else
   run "p4 -s configure set filesys.P4LOG.min=20M" '' 1 1 || errmsg "Failed to set configurable filesys.P4LOG.min."
fi

DiskSpaceAvail=$(df -k "$DEPOTS/" 2>/dev/null | grep / | awk '{print $4}')
if [[ "$DiskSpaceAvail" -ge "$MinKBFor5GLimits" ]]; then
   run "p4 -s configure set filesys.depot.min=5G" '' 1 1 || errmsg "Failed to set configurable filesys.depot.min."
else
   run "p4 -s configure set filesys.depot.min=20M" '' 1 1 || errmsg "Failed to set configurable filesys.depot.min."
fi

DiskSpaceAvail=$(df -k /tmp/ 2>/dev/null | grep / | awk '{print $4}')
if [[ "$DiskSpaceAvail" -ge "$MinKBFor5GLimits" ]]; then
   run "p4 -s configure set filesys.TEMP.min=5G" '' 1 1 || errmsg "Failed to set configurable filesys.TEMP.min."
else
   run "p4 -s configure set filesys.TEMP.min=20M" '' 1 1 || errmsg "Failed to set configurable filesys.TEMP.min."
fi

run "p4 -s configure set server=4" '' 1 1 || errmsg "Failed to set configurable server."
run "p4 -s configure set monitor=2" '' 1 1 || errmsg "Failed to set configurable monitor."

# For UNIX/Linux servers, set monitor.lsof
run "p4 -s configure set monitor.lsof=\"/usr/bin/lsof -F pln\"" '' 1 1 || errmsg "Failed to set configurable monitor.lsof."

# For P4D 2013.2+, setting db.reorg.disable=1, which turns off
# dynamic database reorg, has been shown to significantly improve
# performance when Perforce databases (db.* files) are stored on
# some solid state storage devices, while not making a difference
# on others.
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2013.1" ]]; then
	run "p4 -s configure set db.reorg.disable=1" '' 1 1 || errmsg "Failed to set configurable db.reorg.disable."
fi

# Performance Tracking as required by P4Promtheus.
run "p4 -s configure set track=1" '' 1 1 || errmsg "Failed to set configurable track."

# For P4D 2017.2.1594901 or greater, enable net.autotune.  For net.autotune
# to take effect, it must be enabled on both sides of a connection.  So, to
# get the full benefit, net.autotune must be enabled on all brokers, proxies,
# and clients.  See this KB article for details on fully enabling net.autotune:
# https://portal.perforce.com/s/article/15368
#
# For connections in which net.autotune is not enabled, the p4d default value
# of net.tcpsize takes effect.
#
# When P4D is older than 2014.2 but less than 2017.2.1594901, set net.tcpsize
# to 512k.  In 2014.2, the default value for net.tcpsize became 512k, a
# reasonable default, so it should not be set explicitly. Also, there are
# indications it can reduce performance if set when not needed.
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" < "2014.2" ]]; then
   run "p4 -s configure set net.tcpsize=524288" '' 1 1 || errmsg "Failed to set configurable net.tcpsize."
elif [[ "$P4D_VERSION" > "2017.2.1594900" ]]; then
   msg "Unsetting configurable net.tcpsize, deferring to p4d default value."
	# Suppress expected error and ignore exit code when unsetting net.tcpsize.
   run "p4 -s configure unset net.tcpsize 2>/dev/null" ||:
else
   msg "Unsetting configurable net.autotune and net.tcpsize, deferring to p4d default values."
	# Suppress expected errors and ignore exit codes when unsetting net.autotune & net.tcpsize.
   run "p4 -s configure unset net.autotune 2>/dev.null" ||:
   run "p4 -s configure unset net.tcpsize 2>/dev/null" ||:
fi

# For P4D 2016.2.1468155+, set db.monitor.shared = max value.
if [[ "$P4D_VERSION" > "2016.2.1468154" ]]; then
   # This is the number of 8k pages to set aside for monitoring,
   # which requires pre-allocation of sufficient RAM.  The default
   # is 256, or 2MB, enough for about 128 active/concurrent processes.
   # The max as of 2016.2 is 4096.  Setting db.monitor.shared=0
   # causes the db.monitor on disk to be used instead, which can
   # potentially be a bottleneck.
   run "p4 -s configure set db.monitor.shared=4096" '' 1 1 || errmsg "Failed to set configurable db.monitor.shared."
fi

run "p4 -s configure set net.backlog=2048" '' 1 1 || errmsg "Failed to set configurable net.backlog."

run "p4 -s configure set lbr.autocompress=1" '' 1 1 || errmsg "Failed to set configurable lbr.autocompress."

# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2025.2" ]]; then
   run "p4 -s configure set lbr.unloaddepot.compress=1" '' 1 1 || errmsg "Failed to set configurable lbr.unloaddepot.compress."
fi

run "p4 -s configure set lbr.bufsize=1M" '' 1 1 || errmsg "Failed to set configurable lbr.bufsize."
run "p4 -s configure set filesys.bufsize=1M" '' 1 1 || errmsg "Failed to set configurable filesys.bufsize."

run "p4 -s configure set serverlog.file.1=$LOGS/auth.csv" '' 1 1 || errmsg "Failed to set configurable serverlog.file.1."
run "p4 -s configure set serverlog.retain.1=$KEEPLOGS" '' 1 1 || errmsg "Failed to set configurable serverlog.retain.1."

run "p4 -s configure set serverlog.file.3=$LOGS/errors.csv" '' 1 1 || errmsg "Failed to set configurable serverlog.file.3."
run "p4 -s configure set serverlog.retain.3=$KEEPLOGS" '' 1 1 || errmsg "Failed to set configurable serverlog.retain.3."

# The following are useful if using threat detection based on P4AUDIT
# logs or if those logs are otherwise desired. These are not enabled
# by default as they have special considerations for performance,
# storage, retention, and possibly external processing.
### p4 -s configure set serverlog.file.4="$LOGS/audit.csv"

run "p4 -s configure set serverlog.file.7=$LOGS/events.csv" '' 1 1 || errmsg "Failed to set configurable serverlog.file.7."
run "p4 -s configure set serverlog.retain.7=$KEEPLOGS" '' 1 1 || errmsg "Failed to set configurable serverlog.retain.7."

run "p4 -s configure set serverlog.file.8=$LOGS/integrity.csv" '' 1 1 || errmsg "Failed to set configurable serverlog.file.8."
run "p4 -s configure set serverlog.retain.8=$KEEPLOGS" '' 1 1 || errmsg "Failed to set configurable serverlog.retain.8."

# Add a custom trigger for tracking trigger events:
run "p4 -s configure set serverlog.file.11=$LOGS/triggers.csv" '' 1 1 || errmsg "Failed to set configurable serverlog.file.11."
run "p4 -s configure set serverlog.retain.11=$KEEPLOGS" '' 1 1 || errmsg "Failed to set configurable serverlog.retain.11."

# Temporary Change: Disable certain resource pressure features.
run "p4 -s configure set sys.pressure.mem.medium=0" '' 1 1 || errmsg "Failed to set configurable sys.pressure.mem.medium."
run "p4 -s configure set sys.pressure.mem.high=0" '' 1 1 || errmsg "Failed to set configurable sys.pressure.mem.high."

# Net Keepalives
run "p4 -s configure set net.keepalive.count=9" '' 1 1 || errmsg "Failed to set configurable net.keepalive.count."
run "p4 -s configure set net.keepalive.disable=0" '' 1 1 || errmsg "Failed to set configurable net.keepalive.disable."
run "p4 -s configure set net.keepalive.idle=180" '' 1 1 || errmsg "Failed to set configurable net.keepalive.idle."
run "p4 -s configure set net.keepalive.interval=15" '' 1 1 || errmsg "Failed to set configurable net.keepalive.interval."

SpecFile="${0%/*}/spec.depot.p4s"
if [[ -r "$SpecFile" ]]; then
   msg "Creating a depot named 'spec' of type 'spec'."
   if [[ "$NoOp" -eq 0 ]]; then
      p4 -s depot -i < "$SpecFile" ||\
         errmsg "Failed to create spec depot."
   else
      msg "NO_OP: Would have created spec depot with this spec:\n$(cat "$SpecFile")"
   fi
else
   warnmsg "Skipping spec depot creation due to missing depot spec file: $SpecFile"
fi

SpecFile="${0%/*}/unload.depot.p4s"
if [[ -r "$SpecFile" ]]; then
   msg "Creating a depot named 'unload' of unload 'unload'."
   if [[ "$NoOp" -eq 0 ]]; then
      p4 -s depot -i < "$SpecFile" ||\
         errmsg "Failed to create unload depot."
   else
      msg "NO_OP: Would have created unload depot with this spec:\n$(cat "$SpecFile")"
   fi
else
   warnmsg "Skipping unload depot creation due to missing depot spec file: $SpecFile"
fi

# Load shedding and other performance-preserving configurable.
# For p4d 2013.1+
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2013.1" ]]; then
   run "p4 -s configure set server.maxcommands=2500" '' 1 1 || errmsg "Failed to set configurable server.maxcommands."
fi

# For p4d 2013.2+ -Turn off max* command line overrides.
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2013.2" ]]; then
   run "p4 -s configure set server.commandlimits=2" '' 1 1 || errmsg "Failed to set configurable server.commandlimits."
fi

msg "See: https://portal.perforce.com/s/article/3867"
run "p4 -s configure set rpl.checksum.auto=1" '' 1 1 || errmsg "Failed to set configurable rpl.checksum.auto."
run "p4 -s configure set rpl.checksum.change=2" '' 1 1 || errmsg "Failed to set configurable rpl.checksum.change."
run "p4 -s configure set rpl.checksum.table=1" '' 1 1 || errmsg "Failed to set configurable rpl.checksum.table."

# Define number of login attempts before there is a delay, to thwart
# automated password crackers.  Default is 3; set to a higher value to
# be more friendly to humans without compromising the protection.
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2013.1" ]]; then
   run "p4 -s configure set dm.user.loginattempts=7" '' 1 1 || errmsg "Failed to set configurable dm.user.loginattempts."
fi

# For p4d 2016.1 Patch 5+
# Enable a server with an expired temp license to start, albeit with limited
# functionality, so that license expiry doesn't make it impossible to perform
# license management via the front-door.  This configurable allows the server
# to be started regardless of a bad license, though users will still be blocked
# by license invalid messages.  Perpetual commercial licenses never expire;
# this configurable will not affect those.
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2016.1.1408676" ]]; then
   run "p4 -s configure set server.start.unlicensed=1" '' 1 1 || errmsg "Failed to set configurable server.start.unlicensed."
fi

# Starting with p4d 2015.1 Patch 5, disallow P4EXP v2014.2 (a client
# version known to misbehave) from connecting to the server.
# See:  http://portal.perforce.com/articles/KB/15014
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2015.1.1126924" ]]; then
   run "p4 -s configure set rejectList=\"P4EXP,version=2014.2\"" '' 1 1 || errmsg "Failed to set configurable rejectList."
fi

# For p4d 2011.1 thru 2015.1, set rpl.compress=3.  For p4d 2015.2+, set
# rpl.compress=4.  This setting compresses journal data only, which is
# almost always advantageous as it compresses well, while avoiding
# compression of archive data, which is a mixed bag in terms of performance
# benefits, and potentially a net negative.
# server.global.client.views - makes client views global in a commit/edge environment.
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2015.2" ]]; then
   run "p4 -s configure set rpl.compress=4" '' 1 1 || errmsg "Failed to set configurable rpl.compress."
   run "p4 -s configure set server.global.client.views=1" '' 1 1 || errmsg "Failed to set configurable server.global.client.views."
elif [[ "$P4D_VERSION" > "2011.1" ]]; then
   run "p4 -s configure set rpl.compress=3" '' 1 1 || errmsg "Failed to set configurable rpl.compress."
fi

# Starting with p4d 2016.2, enable these features.
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2016.2" ]]; then
   run "p4 -s configure set server.locks.global=1" '' 1 1 || errmsg "Failed to set configurable server.locks.global."
   run "p4 -s configure set proxy.monitor.level=3" '' 1 1 || errmsg "Failed to set configurable proxy.monitor.level."
fi

# Enable faster resubmit after failed submit.
run "p4 -s configure set submit.noretransfer=1" '' 1 1 || errmsg "Failed to set configurable submit.noretransfer."

# Recommended for Swarm
run "p4 -s configure set dm.shelve.promote=1" '' 1 1 || errmsg "Failed to set configurable dm.shelve.promote."
run "p4 -s configure set dm.keys.hide=2" '' 1 1 || errmsg "Failed to set configurable dm.keys.hide."
run "p4 -s configure set filetype.bypasslock=1" '' 1 1 || errmsg "Failed to set configurable filetype.bypasslock."

# Starting with p4d 2018.2 (as tech-preview, 2019.2 for GA), add best
# practices for Extensions.
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2018.2" ]]; then
   run "p4 -s configure set server.extensions.dir=$LOGS/p4-extensions" '' 1 1 || errmsg "Failed to set configurable server.extensions.dir."
fi

# Set configurables to optimize for P4 Authentication Service (P4AS)
# deployment. These will also affect behavior of older `auth-check-sso`
# triggers.
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2018.2" ]]; then
   run "p4 -s configure set auth.sso.allow.passwd=1" '' 1 1 || errmsg "Failed to set configurable auth.sso.allow.passwd."
   run "p4 -s configure set auth.sso.nonldap=1" '' 1 1 || errmsg "Failed to set configurable auth.sso.nonldap."
fi

# Enable parallelization.
run "p4 -s configure set net.parallel.max=10" '' 1 1 || errmsg "Failed to set configurable net.parallel.max."
run "p4 -s configure set net.parallel.threads=4" '' 1 1 || errmsg "Failed to set configurable net.parallel.threads."

# Limit max parallel syncs.
run "p4 -s configure set net.parallel.sync.svrthreads=150" '' 1 1 || errmsg "Failed to set configurable net.parallel.sync.svrthreads."

# Enable partitioned clients.
run "p4 -s configure set client.readonly.dir=client.readonly.dir" '' 1 1 || errmsg "Failed to set configurable client.readonly.dir."
run "p4 -s configure set client.sendq.dir=client.readonly.dir" '' 1 1 || errmsg "Failed to set configurable client.sendq.dir."
run "p4 -s configure set db.partition.dropondelete=1" '' 1 1 || errmsg "Failed to set configurable db.partition.dropondelete."

# Starting with p4d 2016.1, use auth.id to simplify ticket handling.
# After setting auth.id, login again.
#
# The default value incorporates ORGNAME (from p4_vars), if set, to make
# auth.id more unique across an enterprise. This is deliberately NOT based
# on $P4SERVER (which is a fixed p4_<SDPInstance> shorthand used elsewhere
# for filenames/log tags) so that P4SERVER's other uses are unaffected. If
# ORGNAME is blank, the resulting value is unchanged from prior releases.
#
# A meaningfully unique auth.id also matters for cross-organization P4
# push/fetch: the auth.id of the server you're pushing to/fetching from
# must differ from your own, or one side has to change its auth.id to
# avoid a collision. The old p4_<SDPInstance>-only default (e.g. "p4_1")
# collided often since most SDP instances are numbered similarly; adding
# ORGNAME makes such collisions far less likely for the increasingly
# common case of cross-organization push/fetch between P4 customers.
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2016.1" ]]; then
   run "p4 -s configure set rpl.forward.login=1" '' 1 1 || errmsg "Failed to set configurable rpl.forward.login."
   run "p4 -s configure set auth.id=p4_${ORGNAME:+${ORGNAME}.}${SDP_INSTANCE}" '' 1 1 || errmsg "Failed to set configurable auth.id."
   "$P4CBIN"/p4login
fi

# Set SDP version identifying info.
run "p4 -s counter SDP_DATE \"$(date +'%Y-%m-%d')\"" '' 1 1 || errmsg "Failed to set counter SDP_DATE."
run "p4 -s counter SDP_VERSION \"$SDP_VERSION\"" '' 1 1 || errmsg "Failed to set counter SDP_VERSION."

# Enable real time monitoring with 'p4 monitor rt'.
# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2023.1" ]]; then
   run "p4 -s configure set rt.monitorfile=monfile.mem" '' 1 1 || errmsg "Failed to set configurable rt.monitorfile."
fi

# Basic security features.
run "p4 -s configure set run.users.authorize=1" '' 1 1 || errmsg "Failed to set configurable run.users.authorize."
run "p4 -s configure set dm.user.hideinvalid=1" '' 1 1 || errmsg "Failed to set configurable dm.user.hideinvalid."
run "p4 -s configure set security=4" '' 1 1 || errmsg "Failed to set configurable security."

msg "Restarting server to ensure all configurable changes take effect."
if [[ "$NoOp" -eq 0 ]]; then
   svc_stop_p4d
   svc_start_p4d
else
   msg "NO_OP: Would have stopped and then started p4d."
fi

msg "Logging in."
if [[ "$NoOp" -eq 0 ]]; then
   "$P4CBIN"/p4login -v
else
   msg "NO_OP: Would have run: $P4CBIN/p4login -v"
fi

if [[ "$DoCheckpoint" -eq 1 ]]; then
   if [[ ! -r "$OFFLINE_DB/db.domain" ]]; then
      msg "Creating initial checkpoint with: live_checkpoint.sh $SDPInstance"
      if [[ "$NoOp" -eq 0 ]]; then
         "$P4CBIN/live_checkpoint.sh" "$SDPInstance"
      else
         msg "NO_OP: Would have done: $P4CBIN/live_checkpoint.sh $SDPInstance"
      fi
   else
      msg "Skipping live checkpoint because db.* files exist in $OFFLINE_DB."
   fi
fi

# shellcheck disable=SC2072
if [[ "$P4D_VERSION" > "2017.2.1594900" ]]; then
   msg "\nThe net.autotune value has been set on the server.  To get the full benefit, it must also be\nenabled on proxies, brokers, and clients as well."
fi

msg "\nSummary:"
if [[ "$ErrorCount" -eq 0 && "$WarningCount" -eq 0 ]]; then
   msg "\nAll processing completed successfully."
elif [[ "$ErrorCount" -eq 0 ]]; then
   warnmsg "Processing completed with no errors but $WarningCount warnings. Review the output carefully searching for 'Warning:', for example:\n\tgrep ^Warning: $Log"
else
   errmsg "Processing completed, but with $ErrorCount errors and $WarningCount warnings. Review the output carefully searching for '^Error:' and 'Warning:', for example:\n\tgrep -E '^(Error|Warning):' $Log"
fi

# See the terminate() function in logging.lib.
exit "$ErrorCount"
