#!/bin/bash
set -u
# Version ID Block. Relies on +k filetype modifier.
#------------------------------------------------------------------------------
# shellcheck disable=SC2016
declare VersionID='$Id: //p4-sdp/main/Server/Unix/p4/common/cloud/aws/bin/snapshot.sh#1 $ $Change: 33433 $'
declare VersionStream=${VersionID#*//}; VersionStream=${VersionStream#*/}; VersionStream=${VersionStream%%/*};
declare VersionCL=${VersionID##*: }; VersionCL=${VersionCL%% *}
declare Version=${VersionStream}.${VersionCL}
[[ "$VersionStream" == r* ]] || Version="${Version^^}"
# Get EBS volumes with Name tag of <host>-root, <host>-p4depots, and <host>-p4logs,
# e.g. perforce-01-root, perforce-01-p4depots, and perforce-01-p4logs. Take snapshots,
# and tag them with the current journal counter.
#
# IMPORTANT: These are the exact "Name" tag values this script looks for on
# real AWS EBS volumes. They must be kept in sync with whatever "Name" tags
# are actually applied to the EBS volumes backing live production P4 servers
# -- if a volume's real tag doesn't match one of these, this script will not
# find it and will skip snapshotting it (see the errmsg() below; not
# completely silent, but easy to miss if nobody's watching cron output).
# When the SDP's default mount-point naming changes (as it did for 2026.1,
# /hxdepots -> /mnt/p4depots and /hxlogs -> /mnt/p4logs, see SDP-1379),
# real, already-deployed EBS volumes tagged under the old convention need to
# either be re-tagged to match, or -- as a stopgap -- recognized via
# LegacyVolumeBaseName below so they don't silently stop being backed up.
declare ThisScript="${0##*/}"
declare ThisHost=${HOSTNAME%%.*}
declare VolumeBaseName=
declare VolumeName=
declare LegacyVolumeName=
declare SnapshotName=
declare VolumeId=
declare SnapshotId=
declare Cmd=
declare -i ExitCode=0
declare CurrentJournal=
declare SnapshotAgeToExpire=
declare CurrentDate=
# One release's worth of backward compatibility for EBS volumes not yet
# re-tagged after the 2026.1 mount-name rebrand (see the comment above).
# Maps the current volume-base-name to the legacy name it replaced; "root"
# has no legacy alias, since its name didn't change.
declare -A LegacyVolumeBaseName=( [p4depots]=hxdepots [p4logs]=hxlogs )
function msg () { echo -e "$*"; }
function errmsg () { msg "\\nError: ${1:-Unknown Error}\\n"; ExitCode=1; }
function warnmsg () { msg "\\nWarning: ${1:-Unknown Warning}\\n"; }
function bail () { errmsg "${1:-Unknown Error}"; exit "${2:-1}"; }
# Login to the server so the next command will work properly when testing the script in isolation
/p4/common/bin/p4login
# Get the latest journal version from the db.counters database
CurrentJournal=$("$P4DBIN" -r "$P4ROOT" -k db.counters -jd - 2>&1 | grep @journal@ | cut -d '@' -f 8)
# Set the 'aging off' duration, in days; snapshots older than this will be deleted
SnapshotAgeToExpire=90
msg "Started ${0##*/} version $Version at $(date)."
for VolumeBaseName in root p4depots p4logs; do
VolumeName="${ThisHost}-${VolumeBaseName}"
VolumeId=$(aws ec2 describe-volumes --filters Name=tag:Name,Values="$VolumeName" --query 'Volumes[*].{ID:VolumeId}' --output text)
if [[ -z "$VolumeId" && -n "${LegacyVolumeBaseName[$VolumeBaseName]:-}" ]]; then
LegacyVolumeName="${ThisHost}-${LegacyVolumeBaseName[$VolumeBaseName]}"
VolumeId=$(aws ec2 describe-volumes --filters Name=tag:Name,Values="$LegacyVolumeName" --query 'Volumes[*].{ID:VolumeId}' --output text)
if [[ -n "$VolumeId" ]]; then
warnmsg "Volume tagged '$LegacyVolumeName' (legacy Name tag) instead of '$VolumeName'. Using it for now, but please re-tag this volume to '$VolumeName' -- this fallback is only intended to be temporary."
VolumeName="$LegacyVolumeName"
fi
fi
if [[ -z "$VolumeId" ]]; then
errmsg "Could not determine VolumeId for $VolumeName. Skipping it."
continue
fi
msg "Snapshotting volume $VolumeName [$VolumeId]."
Cmd=$(aws ec2 create-snapshot --description "${VolumeName} snapshot created by ${ThisScript} on main p4d instance" --volume-id "$VolumeId" --query SnapshotId --output text)
msg "Running: ${Cmd[*]}"
SnapshotId=$("${Cmd[@]}")
# The tag "Contents" with value "Perforce Checkpoints and Archives" is used in the following section
# to identify snapshots that are specific to Perforce, so they can be deleted when they expire.
if [[ -n "$SnapshotId" ]]; then
msg "Snapshot created for $VolumeName on $ThisHost with Id: $SnapshotId."
SnapshotName="${VolumeName}-${CurrentJournal}"
Cmd=$(aws ec2 create-tags --resources "$SnapshotId" \
--tags Key=Host,Value="$ThisHost" \
Key=Contents,Value="Perforce Checkpoints and Archives" \
Key=Name,Value="$SnapshotName" \
Key=Backup,Value="true" \
Key=Lifecycle,Value="Managed by /p4/common/cloud/aws/bin/snapshot.sh on main p4d instance" \
--output text)
if "${Cmd[@]}"; then
msg "Verified: Resource tags applied."
else
errmsg "Failed to apply tags to Snapshot $SnapshotId."
fi
else
errmsg "Failed to create snapshot for $VolumeName."
fi
done
#############################################################
# This section automatically deletes snapshots that are #
# older than $SnapshotAgeToExpire days. This replaces #
# the lifecycle management portion of AWS's Snapshot #
# automation, since the snapshots are generated externally. #
#############################################################
# Fetching snapshot IDs and their creation dates where tag Contents is 'Perforce Checkpoints and Archives'
snapshots_to_check=$(aws ec2 describe-snapshots --query "Snapshots[?Tags[?Key=='Contents' && Value=='Perforce Checkpoints and Archives']].[SnapshotId,StartTime]" --output text)
CurrentDate=$(date +%s)
while read -r snapshot_id creation_date; do
# Converting snapshot creation date to seconds
snapshot_date=$(date -d "$creation_date" +%s)
# Calculate age of snapshot
snapshot_age=$(( (CurrentDate - snapshot_date) / 86400 )) # 86400 seconds in a day
# Check if snapshot is older than specified days
if [ "$snapshot_age" -ge "$SnapshotAgeToExpire" ]; then
echo "Deleting snapshot $snapshot_id which is $snapshot_age days old."
aws ec2 delete-snapshot --snapshot-id "$snapshot_id"
fi
done <<< "$snapshots_to_check"
if [[ "$ExitCode" -eq 0 ]]; then
msg "Processing completed OK."
else
msg "Processing completed WITH ERRORS. Review the output above."
fi
exit $ExitCode
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #1 | 33433 | Claude (AI Agent by Anthropic) |
Copy Up from //p4-sdp/dev into //p4-sdp/main. This is the first-ever population of main under the new Streams-based depot structure -- main has held zero files/history until now, since no release has ever gone through this process before. 463 files, covering the entire 2026.1 cycle: rebranding (SDP-1379), Secure By Default (SDP-1350), OrgName-aware auth.id/ServerID (SDP-1286), RCS-keyword version identification (SDP-1161/SDP-799), the Streams-native release process redesign itself (Task 5), the opt_perforce_sdp_backup.sh false-error fix, the P4D 2026.1 test-suite targeting, refreshed P4*.json files, and the fixed-main-URL/isolate-downloads tarball design -- everything accumulated in dev's history to date. Isolated paths (ai_dev_support/, Version, doc/*.html, doc/*.pdf, doc/gen/*.man.txt, doc/gen/sdp_install.cfg, Unsupported/doc/*.html, Unsupported/doc/*.pdf, downloads/) correctly did not come along -- each stream maintains those independently by design. Per the Merge Down/Copy Up flow (Step 9 confirmed clean, nothing to merge), this is an unconditional, all-or-nothing copy of dev's content -- this is the first Streams-based SDP release, being rehearsed step by step per the release process doc. Agent: Claude Code, Model: Claude Sonnet 5 (claude-sonnet-5), operating as bot_Claude_Anthropic. |
||
| //p4-sdp/dev/Server/Unix/p4/common/cloud/aws/bin/snapshot.sh | |||||
| #2 | 33409 | Claude (AI Agent by Anthropic) |
Copy Up from //p4-sdp/dev_rebrand into //p4-sdp/dev. This is the first promotion of dev_rebrand's work into dev since dev_rebrand was created (2025-05-24) -- 303 files, covering the entire 2026.1 rebranding effort (SDP-1379), the Secure By Default adaptation (SDP-1350), OrgName-aware auth.id/ServerID (SDP-1286), RCS-keyword version identification (SDP-1161/SDP-799), and the Streams-native release process redesign (Task 5) done this session, plus everything else accumulated in dev_rebrand's history before this session. Per the Merge Down/Copy Up flow, this is intentionally a full, unconditional blast-replace of dev's content from dev_rebrand -- all selectivity/care happened in the preceding Merge Down (dev -> dev_rebrand, changes 33407-33408), which absorbed Robert Cowham's independent dev-side work first so nothing of his is lost by this Copy Up. Two files are worth calling out since they might look alarming in isolation: - tools/mdcu.sh is deleted -- intentional, retired this session in favor of the two direct Streams commands now documented in doc/ReleaseProcessOverview.md. - tools/ReleaseProcessOverview.md is deleted -- this is a stale relic of a file move dev_rebrand made back in 2025-05-24 (tools/ -> doc/) that was never previously propagated to dev; the current, fully-rewritten doc/ReleaseProcessOverview.md is added/updated correctly by this same changelist. |
||
| #1 | 31397 | C. Thomas Tyler | Populate -b SDP_Classic_to_Streams -s //guest/perforce_software/sdp/...@31368. | ||
| //guest/perforce_software/sdp/dev/Server/Unix/p4/common/cloud/aws/bin/snapshot.sh | |||||
| #6 | 29942 | C. Thomas Tyler |
Customer Contributed changes: 1. I use a slightly different method to get the latest journal version (for appending to the snapshot name). The approach I had been using was running into some issues with the Perforce account not being logged into the Perforce server, so I borrowed the approach out of the existing SDP scripts to login and retrieve that value. 2. I've added a section at the end to automatically delete old snapshots when they age past a value that's configurable in the script. If snapshots were being generated automatically by AWS automation, this aging-off process would be part of that; since we're pushing the snapshot creation from the Perforce server, I needed to add this functionality, so I didn't have to go manually delete old versions from AWS occasionally. |
||
| #5 | 29941 | C. Thomas Tyler |
Contributed changes to snapshot.sh: * Added /hxlogs to list of snapshotted volumes. * Added explicit output formatting (text) to AWS CLI calls. |
||
| #4 | 27722 | C. Thomas Tyler |
Refinements to @27712: * Resolved one out-of-date file (verify_sdp.sh). * Added missing adoc file for which HTML file had a change (WorkflowEnforcementTriggers.adoc). * Updated revdate/revnumber in *.adoc files. * Additional content updates in Server/Unix/p4/common/etc/cron.d/ReadMe.md. * Bumped version numbers on scripts with Version= def'n. * Generated HTML, PDF, and doc/gen files: - Most HTML and all PDF are generated using Makefiles that call an AsciiDoc utility. - HTML for Perl scripts is generated with pod2html. - doc/gen/*.man.txt files are generated with .../tools/gen_script_man_pages.sh. #review-27712 |
||
| #3 | 26843 | C. Thomas Tyler | Updated to adapt to changes in AWS CLI. | ||
| #2 | 25108 | C. Thomas Tyler | Corrected comments; no functional change. | ||
| #1 | 25104 | C. Thomas Tyler |
Added sample script to create EBS snapshot of volumes with a Name tag of <host>-root and <host>-hxdepots, e.g. perforce-01-root and perforce-01-hxdepots. This is intended to be called at the optimal time to reduce risk exposure. The optimal time is immediately after a journal rotation completes near the start of the overall daily checkpoint process, or optionally immediately after the offline checkpoint is created. This script creates 2 EBS snapshots with appropriate resource tagging each time it is run. Note that a full recovery would entail mounting these 2 volumes, creating new hxdepots and hxmetadata volumes, finalizing the SDP structure, etc. This is fairly straightforward, but not trival, and is needed only as a Plan B for recovery. Plan A is to use Perforce replication to a secondary instance for fast and easier recovery. Basic data retention polices can be implemented with EBS Data Lifecycle Policies. Custom automation can copy recovery assets to S3 Glacier for long term storage. In addition to this script, a new high-level SDP structure is created, /p4/common/cloud. Under the new cloud directory is a directory for the cloud provider, e.g. one of aws, azure, gcp, rackspace, etc. Cloud-provider specific files can go in there. |
||