#!/bin/bash
set -u

#==============================================================================
# Bootstraps a fresh VM to join the SDP Installer Test Suite fleet. Automates
# STEPs 3-11 of InstallSDP-TestServerSetup.md (STEP 1, OS patching + reboot,
# and STEP 2, the sudo-user 'dev' alias, remain small manual steps -- see that
# doc). Run this as root, after the OS-patch-and-reboot step.
#
# This script is self-contained (no sourced libraries) so it can be curl'd
# directly onto a machine that has no P4 client tooling yet, e.g.:
#   curl -O https://workshop.perforce.com/download/test-install_sdp/dev/bootstrap_test_machine.sh
#   chmod +x bootstrap_test_machine.sh
#   ./bootstrap_test_machine.sh -mode basic
#
# Run with -h for a short usage summary, or -man for full documentation.
#==============================================================================

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

declare ThisScript=${0##*/}
declare ThisUser=
declare Args="$*"
declare CmdLine="$0 $Args"
declare ThisHost=${HOSTNAME%%.*}
declare -i ErrorCount=0
declare -i WarningCount=0
declare H1="=============================================================================="

# Mirrors the DEV_HOME logic in dev_env.sh.
declare DevHome=/opt/perforce/dev
[[ "$HOSTNAME" == p4c-bos-01.p4demo.com ]] && DevHome=/opt/perforce/.dev

declare P4Port=public.perforce.com:1666

declare Mode=
declare P4User=ftp
declare P4dVersion=r26.1
declare PlatformTag=
declare NewHostname=
declare SdpStream=p4-sdp/dev_rebrand
declare SuiteStream=test-install_sdp/dev

declare Log=
declare LogLink=

# Maps "<os-release ID>:<os-release VERSION_ID major>" to the short OS tag
# half of a PlatformTag (the other half is 'uname -m', used verbatim -- it
# already matches, e.g. x86_64, aarch64). To support a new platform, add a
# row here. See the 'Platforms' list in InstallSDP-TestServerSetup.md for
# what's actually qualified as of this writing -- this table may include
# entries (e.g. rhel8/9/10) that are expected to work but not yet exercised.
declare -A PlatformTagMap=(
   [rocky:8]=r8
   [rocky:9]=r9
   [rocky:10]=r10
   [rhel:8]=rhel8
   [rhel:9]=rhel9
   [rhel:10]=rhel10
   [sles:15]=sles15
   [ubuntu:20]=u20
   [ubuntu:22]=u22
   [ubuntu:24]=u24
   [ubuntu:26]=u26
   # EDITME: Maybe BSD some day? Add rows like [freebsd:14]=fbsd14 here, and
   # confirm uname -m's output on that platform matches what's expected in
   # ssh-test-install_sdp-<PlatformTag>.sh naming before relying on it.
)

#==============================================================================
# Local Functions
#
# Deliberately defined locally rather than sourced from a shared library
# (SDPCommonLib), since this script must run standalone on a machine with no
# SDP or P4 client tooling installed yet. See the Header comment above.

function msg () { echo -e "$*"; }
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}"; }

# shellcheck disable=SC2317 # Only invoked indirectly via 'trap terminate EXIT ...' below; ShellCheck can't trace trap handlers as call sites. (In the standard's own template, terminate() lives in a sourced library and so is invisible to ShellCheck here at all -- this script inlines it instead, per its self-contained requirement.)
function terminate
{
   local exitCode=$ErrorCount
   [[ -n "$Log" ]] && msg "\nLog is: $Log\n"
   trap - EXIT SIGINT SIGTERM
   exit "$exitCode"
}

#------------------------------------------------------------------------------
# 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). If given, displayed first, followed by the
# standard usage message.
#------------------------------------------------------------------------------
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:

$ThisScript -mode <basic|advanced> [-p4user <user>] [-p4dver <rel>] [-tag <PlatformTag>] [-host <hostname>] [-sdpstream <stream>] [-suitestream <stream>]

or

$ThisScript [-h|-man]
"

   if [[ "$style" == -man ]]; then
      msg "
DESCRIPTION:
Bootstraps a fresh VM to join the SDP Installer Test Suite fleet: downloads
P4 binaries, clones this test suite and the SDP from the Public Depot
(using swap_source.sh, so the source can be changed again later), installs
required OS packages (via setup_test_machine.sh), caches the Sample Depot
tarball for '-local' test runs (via init_root_dirs_for_local_installs.sh),
and sets up the 'dev' alias and shell prompt. Run as root, after patching
the OS and rebooting (STEP 1 in InstallSDP-TestServerSetup.md).

Any required option not given on the command line is prompted for
interactively, except where a sensible default applies (see OPTIONS).

OPTIONS:

 -mode <basic|advanced>
	'basic' pulls read-only from the Public Depot as the 'ftp' user -- no
	Public Depot account needed, but you can't push changes back.
	'advanced' uses your own Public Depot account (read/write). You must
	already be logged in as that user (run e.g.
	'p4 -u <user> -p $P4Port login' first) -- this script does not accept
	or store a password.

 -p4user <user>
	Your Public Depot P4USER. Required (and only meaningful) with
	'-mode advanced'.

 -p4dver <rel>
	The P4 binaries (p4, p4d, p4broker, p4p) release to bootstrap with,
	e.g. r26.1. Default: $P4dVersion. This only affects the binaries used
	to enable the initial 'p4 clone' on this machine -- it has no bearing
	on which P4D version(s) install_sdp.sh itself installs or tests
	against during a test run.

 -tag <PlatformTag>
	Override auto-detection of this machine's PlatformTag (e.g. r9x86_64,
	u26aarch64). Auto-detection reads /etc/os-release (ID + VERSION_ID) and
	'uname -m', and maps them via the table below. Use this option for any
	platform not yet in that table -- and consider adding a row to
	\$PlatformTagMap in this script (and to the 'Platforms' list in
	InstallSDP-TestServerSetup.md, per its 'Qualifying a New Platform'
	section) once it's confirmed working, so future runs auto-detect it.

	Current mapping table (<os-release ID>:<VERSION_ID major> -> tag
	prefix; the architecture suffix comes from 'uname -m' verbatim):
$(
   for key in "${!PlatformTagMap[@]}"; do
      printf '\t  %-12s -> %s<arch>\n' "$key" "${PlatformTagMap[$key]}"
   done | sort
)
	For example, on Rocky Linux 9 x86_64, ID=rocky and VERSION_ID=9.5, so
	the table entry 'rocky:9' -> 'r9' combines with 'uname -m' (x86_64) to
	give the PlatformTag r9x86_64. A hypothetical future BSD platform would
	need its own row here (and BSD's 'uname -m' output confirmed to match
	what's used elsewhere), since BSD has no /etc/os-release file at all.

 -host <hostname>
	Set this machine's hostname (and its timezone to America/Los_Angeles,
	so Public Depot submit timestamps are valid) -- e.g. p4d-test-r9-01.
	Optional; tests don't depend on the hostname, this just makes it easier
	to tell platforms apart at a glance.

 -sdpstream <stream>
	Which //p4-sdp stream to pull the SDP from. Default: $SdpStream.
	Passed straight through to swap_source.sh.

 -suitestream <stream>
	Which //test-install_sdp stream to pull this test suite from.
	Default: $SuiteStream.

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

SEE ALSO:
 swap_source.sh, setup_test_machine.sh, InstallSDP-TestServerSetup.md
"
   fi

   exit 2
}

#------------------------------------------------------------------------------
# Function: detect_platform_tag
#
# Reads /etc/os-release and 'uname -m' and maps them to a PlatformTag via
# $PlatformTagMap. Bails with guidance toward -tag if the OS isn't mapped.
#------------------------------------------------------------------------------
function detect_platform_tag
{
   local id='' versionId='' versionMajor='' arch='' osTag=''

   [[ -r /etc/os-release ]] ||\
      bail "Cannot auto-detect platform; /etc/os-release not found. Use -tag to specify it explicitly."

   id=$(grep -E '^ID=' /etc/os-release | head -1 | cut -d= -f2- | tr -d '"')
   versionId=$(grep -E '^VERSION_ID=' /etc/os-release | head -1 | cut -d= -f2- | tr -d '"')
   versionMajor=${versionId%%.*}
   arch=$(uname -m)

   osTag=${PlatformTagMap[${id}:${versionMajor}]:-}
   [[ -n "$osTag" ]] ||\
      bail "Could not map OS (ID=$id VERSION_ID=$versionId) to a known PlatformTag. Use -tag to specify it explicitly; see '$ThisScript -man' for the mapping table and how to extend it."

   echo "${osTag}${arch}"
}

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

declare -i ShiftArgs=0

set +u
while [[ $# -gt 0 ]]; do
   case $1 in
      (-mode) Mode="$2"; ShiftArgs=1;;
      (-p4user) P4User="$2"; ShiftArgs=1;;
      (-p4dver) P4dVersion="$2"; ShiftArgs=1;;
      (-tag) PlatformTag="$2"; ShiftArgs=1;;
      (-host) NewHostname="$2"; ShiftArgs=1;;
      (-sdpstream) SdpStream="$2"; ShiftArgs=1;;
      (-suitestream) SuiteStream="$2"; ShiftArgs=1;;
      (-h) usage -h;;
      (-man|--help) usage -man;;
      (*) usage -h "Unknown arg ($1).";;
   esac

   shift; while [[ $ShiftArgs -gt 0 ]]; do
      [[ $# -eq 0 ]] && usage -h "Incorrect number of arguments."
      ShiftArgs=$ShiftArgs-1
      shift
   done
done
set -u

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

if [[ "$(id -u)" -ne 0 ]]; then
   bail "This script must be run as root (after 'sudo su -', or directly on a machine you log into as root)."
fi

if [[ -z "$Mode" ]]; then
   msg "Choose setup mode:"
   msg "  basic    - read-only, no Public Depot account needed."
   msg "  advanced - read/write, using your own Public Depot account."
   read -r -p "Mode [basic/advanced]: " Mode
fi

case "$Mode" in
   basic) P4User=ftp;;
   advanced)
      if [[ -z "$P4User" || "$P4User" == ftp ]]; then
         read -r -p "Your Public Depot P4USER: " P4User
      fi
      [[ -n "$P4User" ]] || bail "A P4USER is required for -mode advanced."
      if ! p4 -u "$P4User" -p "$P4Port" login -s > /dev/null 2>&1; then
         bail "You're not logged in to $P4Port as $P4User yet. Run:\n\n  p4 -u $P4User -p $P4Port login\n\nthen re-run this script with the same -mode advanced -p4user $P4User."
      fi
      ;;
   *) usage -h "Unknown -mode '$Mode'; expected 'basic' or 'advanced'.";;
esac

if [[ -z "$PlatformTag" ]]; then
   PlatformTag=$(detect_platform_tag) || exit "$ErrorCount"
   msg "Auto-detected PlatformTag: $PlatformTag"
fi

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

trap terminate EXIT SIGINT SIGTERM

Log="$PWD/${ThisScript%.sh}.$(date +'%Y-%m-%d-%H%M%S').log"
LogLink="$PWD/${ThisScript%.sh}.log"
[[ -L "$LogLink" ]] && rm -f "$LogLink"
ln -sf "$Log" "$LogLink"
exec > >(tee "$Log")
exec 2>&1

ThisUser=$(id -n -u)
msg "${H1}\nStarting $ThisScript as $ThisUser@$ThisHost on $(date) with \n$CmdLine\nLog is: $Log\n${H1}"
msg "Mode: $Mode   P4User: $P4User   PlatformTag: $PlatformTag   P4D version: $P4dVersion"

if [[ -n "$NewHostname" ]]; then
   msg "\nSetting hostname to $NewHostname and timezone to America/Los_Angeles..."
   hostnamectl set-hostname "$NewHostname" || warnmsg "Could not set hostname to $NewHostname. Tests don't depend on this; continuing."
   timedatectl set-timezone America/Los_Angeles || warnmsg "Could not set timezone. Continuing."
fi

msg "\nBootstrapping P4 binaries ($P4dVersion) so we can do an initial 'p4 clone'..."
declare BinArch=
case "$(uname -m)" in
   x86_64) BinArch=bin.linux26x86_64;;
   aarch64) BinArch=bin.linux26aarch64;;
   *) bail "Unhandled architecture: $(uname -m)";;
esac

mkdir -p /root/p4_binaries || bail "Could not do: mkdir -p /root/p4_binaries"
(
   cd /root/p4_binaries || exit 1
   for Binary in p4 p4d p4broker p4p; do
      msg "Acquiring: https://ftp.perforce.com/perforce/$P4dVersion/$BinArch/$Binary"
      curl -sS -O "https://ftp.perforce.com/perforce/$P4dVersion/$BinArch/$Binary" || exit 1
      chmod +x "$Binary"
      ./"$Binary" -V | grep Rev || exit 1
   done
) || bail "Could not acquire P4 binaries for $P4dVersion/$BinArch."

export PATH=/root/p4_binaries:$PATH

msg "\nCloning the SDP Installer Test Suite from //$SuiteStream ..."
mkdir -p "$DevHome/bin" || bail "Could not do: mkdir -p \"$DevHome/bin\""
(
   cd "$DevHome/bin" || exit 1
   export P4CONFIG=.p4config.local
   p4 -u "$P4User" clone -p "$P4Port" -r "${SuiteStream//\//_}" || exit 1
   sed -i -E -e 's/^P4IGNORE=.*/P4IGNORE=.p4ignore.local/' .p4config.local || exit 1
   echo "${SuiteStream//\//_}" > .current-source
) || bail "Could not clone the test suite from //$SuiteStream."

cp /root/p4_binaries/p4 "$DevHome/bin/." || errmsg "Could not copy p4 to $DevHome/bin."
cp /root/p4_binaries/p4d "$DevHome/bin/." || errmsg "Could not copy p4d to $DevHome/bin."

msg "\nCloning the SDP from //$SdpStream, via swap_source.sh ..."
(
   cd "$DevHome/bin" || exit 1
   if [[ "$Mode" == advanced ]]; then
      P4_SWAP_USER="$P4User" ./swap_source.sh "$SdpStream"
   else
      ./swap_source.sh "$SdpStream"
   fi
) || bail "swap_source.sh $SdpStream failed."

msg "\nInstalling packages needed by the SDP Test Suite (setup_test_machine.sh) ..."
( cd "$DevHome/bin" && ./setup_test_machine.sh ) || errmsg "setup_test_machine.sh reported errors; review its own log."

msg "\nCaching the Sample Depot tarball for '-local' test runs (init_root_dirs_for_local_installs.sh) ..."
( cd "$DevHome/bin" && ./init_root_dirs_for_local_installs.sh ) || errmsg "init_root_dirs_for_local_installs.sh reported errors; review its own log."

msg "\nAdding 'dev' alias and prompt customization to ~root/.bashrc ..."
if ! grep -q "alias dev=" /root/.bashrc 2>/dev/null; then
   {
      echo ""
      echo "alias dev='cd $DevHome/bin; source ./dev_env.sh; cd ../sdp/Server/Unix/setup; clear'"
   } >> /root/.bashrc || errmsg "Could not append 'dev' alias to /root/.bashrc."
fi

cat <<EOL > /etc/profile.d/nice_prompt.sh
export PS1='\$USER@\${HOSTNAME%%.*}:\$PWD\$ '
EOL

if [[ -r /etc/os-release ]] && grep -qi ubuntu /etc/os-release; then
   for BashRc in /root/.bashrc "/home/${SUDO_USER:-}/.bashrc"; do
      [[ -w "$BashRc" ]] || continue
      grep -q 'nice_prompt.sh' "$BashRc" 2>/dev/null ||\
         echo '[[ -r /etc/profile.d/nice_prompt.sh ]] && source /etc/profile.d/nice_prompt.sh' >> "$BashRc"
   done
fi

msg "\n${H1}"
if [[ "$ErrorCount" -eq 0 && "$WarningCount" -eq 0 ]]; then
   msg "Success: $ThisScript completed with no errors or warnings."
elif [[ "$ErrorCount" -eq 0 ]]; then
   msg "$ThisScript completed with $WarningCount warning(s); review the log above."
else
   msg "$ThisScript completed with $ErrorCount error(s) and $WarningCount warning(s); review the log above."
fi
msg "\nLog out and back in (or 'dev' twice if you used the sudo-user alias from STEP 2)"
msg "to pick up the new shell environment, then 'cdd; r Usage' to get started."
msg "${H1}"

exit "$ErrorCount"
