#!/bin/bash
# Build as the SDP account, then select only generated disposable runtimes.
fail() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
[[ $(id -u) == 0 && -f /run/sdp-container-test && -d /inputs/sdp && -d /p4/common ]] || fail 'Requires the provisioned disposable root container.'
ROOT=/opt/sdp-python
INSTALLER=/inputs/sdp/Server/Unix/setup/install_sdp_python.sh
RECEIPT=/results/python-installer-evidence.json
verify() {
[[ -f "$ROOT/.sdp-disposable-runtime" && -x "$ROOT/baseline/bin/python3" && -f "$RECEIPT" ]] || fail 'Missing completed baseline build.'
"$ROOT/baseline/bin/python3" - "$INSTALLER" "$RECEIPT" <<'PY'
import hashlib,json,pathlib,sys
receipt=json.loads(pathlib.Path(sys.argv[2]).read_text())
assert receipt['exit_code']==0 and receipt['build_os_user']=='perforce'
assert receipt['source_sha256']==hashlib.sha256(pathlib.Path(sys.argv[1]).read_bytes()).hexdigest(), 'Installer source differs from completed build'
assert sys.version_info[:3]==(3,14,8),sys.version
import ssl,zlib,P4,ldap,pyrad
print(sys.version)
print(P4.P4.identify())
PY
[[ $? == 0 ]] || fail 'Baseline runtime verification failed.'
}
case "${1:-}" in
install)
if [[ -e "$ROOT" ]]; then
verify || exit 1
"$ROOT/baseline/bin/python3" - "$INSTALLER" "$RECEIPT" <<'PY'
import hashlib,json,pathlib,sys
digest=lambda path: hashlib.sha256(pathlib.Path(path).read_bytes()).hexdigest()
data={'installer_source_sha256':digest(sys.argv[1]),'original_receipt_sha256':digest(sys.argv[2]),
'validation_source_manifest_sha256':digest('/inputs/sdp/.export-manifest.json'),
'verification_exit_code':0,
'reason':'Reused the existing container build only after verifying unchanged installer bytes and runtime imports; no new compilation claimed.'}
pathlib.Path('/results/python-installer-reuse.json').write_text(json.dumps(data,indent=2)+'\n')
PY
[[ $? == 0 ]] || fail 'Cannot record verified Python build reuse.'
exit 0
fi
mkdir -p "$ROOT" /results || exit 1
touch "$ROOT/.sdp-disposable-runtime" || exit 1
chown -R perforce:perforce "$ROOT" || exit 1
# /opt is executable even where the container's /tmp mount is noexec.
runuser -u perforce -- env MAKEFLAGS=-j3 bash "$INSTALLER" -r "$ROOT/baseline" -L "$ROOT/installer.log" -w "$ROOT/build" -w KEEP || fail 'Actual Python baseline build failed.'
runuser -u perforce -- "$ROOT/baseline/bin/python3" -m pip install pyrad python-ldap > /results/python-baseline-dependencies.log 2>&1 || fail 'Baseline trigger dependency installation failed.'
DISTRO=${SDP_DISTRO_PYTHON:-/usr/bin/python3}
"$DISTRO" -m venv "$ROOT/distro" || fail 'Distro Python cannot create a virtual environment.'
"$ROOT/distro/bin/python3" -m pip install 'p4python==2026.1.*' pyrad python-ldap > /results/python-distro-dependencies.log 2>&1 || fail 'Distro Python dependency installation failed.'
chown -R perforce:perforce "$ROOT/distro" || exit 1
cp "$ROOT/installer.log" /results/python-installer.log || exit 1
"$ROOT/baseline/bin/python3" - "$INSTALLER" "$RECEIPT" <<'PY'
import hashlib,json,pathlib,sys
source=pathlib.Path(sys.argv[1])
receipt={'exit_code':0,'source_sha256':hashlib.sha256(source.read_bytes()).hexdigest(),
'build_os_user':'perforce','prefix':'/opt/sdp-python/baseline','python':sys.version,
'note':'Actual source build performed during this clean container bootstrap; suites use this runtime.'}
pathlib.Path(sys.argv[2]).write_text(json.dumps(receipt,indent=2)+'\n')
PY
[[ $? == 0 ]] || exit 1
"$ROOT/baseline/bin/python3" --version > /results/python-version.txt 2>&1 || exit 1
cp /results/python-version.txt /results/installed-python-version.txt || exit 1
"$ROOT/distro/bin/python3" --version > /results/python-distro-version.txt 2>&1 || exit 1
verify > /results/python-installer-runtime.txt || exit 1
"$ROOT/baseline/bin/python3" - <<'PY'
import json,pathlib,subprocess,sys
import P4,ldap,pyrad
root=pathlib.Path('/opt/sdp-python')
distro=json.loads(subprocess.check_output([str(root/'distro/bin/python3'),'-c','import json,sys,P4,ldap,pyrad; print(json.dumps({"version":sys.version,"p4python":P4.P4.identify()}))'],text=True))
data={'primary':'baseline','build_os_user':'perforce','distro_supported':True,
'baseline':{'prefix':str(root/'baseline'),'version':sys.version,'p4python':P4.P4.identify()},
'distro':dict(distro,prefix=str(root/'distro'))}
pathlib.Path('/results/python-runtimes.json').write_text(json.dumps(data,indent=2)+'\n')
PY
[[ $? == 0 ]] || fail 'Runtime dependency/import verification failed.'
;;
verify)
verify || exit 1
cat /results/python-installer.log || exit 1
;;
select)
[[ -f "$ROOT/.sdp-disposable-runtime" ]] || fail 'Runtime ownership marker missing.'
case "${2:-}" in baseline|distro) TARGET="$ROOT/$2";; *) fail 'Select baseline or distro.';; esac
[[ -x "$TARGET/bin/python3" ]] || fail 'Selected runtime is unavailable.'
if [[ -e /p4/common/python || -L /p4/common/python ]]; then
[[ -L /p4/common/python ]] || fail 'Refusing to replace an unmanaged Python directory.'
case "$(readlink /p4/common/python)" in "$ROOT/baseline"|"$ROOT/distro") ;; *) fail 'Refusing to replace an unmanaged Python symlink.';; esac
fi
ln -sfn "$TARGET" /p4/common/python || exit 1
"$TARGET/bin/python3" -c 'import sys, P4; print(sys.version); print(P4.P4.identify())' || exit 1
;;
*) fail 'Usage: python_runtime.sh install|verify|select baseline|distro';;
esac
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #1 | 34004 | Russell C. Jackson (Rusty) |
Improve SDP backup/recovery, Maintenance, logging and cloud operation. Add owned checkpoint locks, recovery-aware retention, preserved database switches, bounded cleanup and coordinated AWS/Azure snapshots. Fix service-user password initialization, edge staging, standby promotion state parsing, proxy cache setup, typed depot verification and historical-server configuration/error handling. Remove unused OTP programs; keep Windows removed, server.locks cleanup and the journal-count condition unchanged. Add no global shell strict modes. Update operational documentation and add reproducible mkdirs.sh/mkdirs.cfg container assurance. Validate Ubuntu 26.04 and UBI 8.10/9.8/10.2 across both Python runtimes: 44 stages; 428 offline tests per runtime/OS, zero offline skips; 19 deployed-archive comparisons per combination. Three six-node topology runs pass all 30 recovery/failure scenarios. P4D 2023.1 through 2026.1 on UBI 8/9 pass all 14 cells and 98 scenarios. Static, documentation and provenance checks pass. Containers do not certify every fleet kernel, patch build or integration. Tested source manifest SHA-256: e1a23e17b8ced8c1148d9a250589977c6f6e98606bf2a81dc3ff5d7c909e3695 Deployed comparison archive SHA-256: 1bd23217356ee3808e040d39aea5be9519bda7c8f2b4924656a70aff8ad7293a |