#!/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"
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #3 | 33182 | Claude (AI Agent by Anthropic) |
bootstrap_test_machine.sh: also cache the Sample Depot tarball, so a fresh machine is actually test-ready. bootstrap_test_machine.sh's own doc-comment claimed it handles "everything through having a fully working shell environment", but it never called init_root_dirs_for_local_installs.sh -- so /root/downloads (the Sample Depot tarball cache) was never populated on a genuinely fresh machine, and every Sample-Depot-touching test group (and everything cascading after it, given run_cli_tests.sh's -e) would fail immediately with an undocumented rsync error. Caught this rolling the updated bootstrap out to the 6 remaining fleet machines tonight. Verified end-to-end on a fully wiped-clean r9x86_64: a single 'bootstrap_test_machine.sh -mode basic' run now populates both /root/p4_binaries and /root/downloads with no separate manual step. Agent: Claude Code (Sonnet 5, claude-sonnet-5) |
||
| #2 | 33179 | Claude (AI Agent by Anthropic) |
Rename /root/helix_binaries to /root/p4_binaries consistently; fix init_root_dirs_for_local_installs.sh's stale duplicate-download logic. cli_tests.cfg used /root/helix_binaries as its rsync source in all 14 Sample-Depot/local-install test-setup steps, while bootstrap_test_machine.sh (new this session) independently creates and populates that same cache directory for its own initial-clone bootstrap need -- both under the old pre-SDP-1379 Helix-branded name. Renamed to /root/p4_binaries in both. init_root_dirs_for_local_installs.sh (older, predates bootstrap_test_machine.sh) created BOTH /root/helix_binaries and /root/p4_binaries as a redundant duplicate download; its own comment mislabeled helix_binaries as the 'legacy' name even though it was the only one cli_tests.cfg ever actually used. Its preflight check also unconditionally bailed if either already existed, meaning it could no longer run on any machine already bootstrapped via bootstrap_test_machine.sh. Simplified to a single /root/p4_binaries (skipping the download if bootstrap_test_machine.sh already populated it) and /root/downloads (the Sample Depot tarball cache, which nothing else populates). Verified on the r9x86_64 canary: LocalInstallWithSD (Sample Depot group) 15/15, and a full suite run of 287/287 with zero failures. Agent: Claude Code (Sonnet 5, claude-sonnet-5) |
||
| #1 | 33158 | Claude (AI Agent by Anthropic) |
Task 1.2: SDP Installer Test Suite source-swapping capability. Adds swap_source.sh (park/restore or fresh-clone via p4-sdp_*/test-install_sdp_* remote specs) and bootstrap_test_machine.sh (self-contained, curl-bootstrappable machine-join script, SDP Bash Coding Standard style with documented deviations, ShellCheck-clean). Fixes dev_env.sh's hardcoded P4USER/P4PORT, and init_root_dirs_for_local_installs.sh's stale r25.1 P4D default and a real bin.aarch64 URL bug (404; should be bin.linux26aarch64). Restructures InstallSDP-TestServerSetup.md around the new bootstrap script and adds a "Qualifying a New Platform" section. Agent: Claude Code, Model: Claude Sonnet 5 (claude-sonnet-5), operating as bot_Claude_Anthropic. |