#!/usr/bin/env bash ######################################################################################### #### ebs-snapshot-manager.sh — Create, manage, audit, and prune AWS EBS snapshots #### #### Supports automated creation, cross-region copy, retention, and orphan detection #### #### Requires: bash 4+, aws-cli v2, jq #### #### #### #### Author: Phil Connor #### #### Contact: contact@mylinux.work #### #### License: MIT #### #### Version 1.01 #### #### #### #### Usage: #### #### export AWS_PROFILE="production" #### #### ./ebs-snapshot-manager.sh --snapshot #### #### #### #### See --help for all options. #### ######################################################################################### set -euo pipefail # ── Defaults ────────────────────────────────────────────────────────── AWS_REGION="${AWS_REGION:-}" VOLUME_IDS="${VOLUME_IDS:-}" VOLUME_TAG_KEY="${VOLUME_TAG_KEY:-}" VOLUME_TAG_VALUE="${VOLUME_TAG_VALUE:-}" RETENTION_DAYS="${RETENTION_DAYS:-30}" COPY_TO_REGION="${COPY_TO_REGION:-}" SNAPSHOT_DESCRIPTION="${SNAPSHOT_DESCRIPTION:-Automated snapshot by ebs-snapshot-manager}" NO_WAIT="${NO_WAIT:-false}" DRY_RUN="${DRY_RUN:-true}" RESTORE_AZ="${RESTORE_AZ:-}" RESTORE_VOLUME_TYPE="${RESTORE_VOLUME_TYPE:-gp3}" RESTORE_IOPS="${RESTORE_IOPS:-}" RESTORE_THROUGHPUT="${RESTORE_THROUGHPUT:-}" OUTPUT_FORMAT="${OUTPUT_FORMAT:-text}" VERBOSE="${VERBOSE:-false}" COLOR="${COLOR:-auto}" # ── State ───────────────────────────────────────────────────────────── SCRIPT_NAME="$(basename "$0")" readonly SCRIPT_NAME RUN_MODE="" TARGET_VOLUME="" TARGET_SNAPSHOT="" START_TIME="" WARNINGS=0 # ── Colors ──────────────────────────────────────────────────────────── setup_colors() { if [[ "$COLOR" == "never" ]]; then RED="" GREEN="" YELLOW="" BLUE="" BOLD="" RESET="" return fi if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' BOLD='\033[1m' RESET='\033[0m' else RED="" GREEN="" YELLOW="" BLUE="" BOLD="" RESET="" fi } # ── Logging ─────────────────────────────────────────────────────────── log() { echo -e "${BLUE}[INFO]${RESET} $*"; } warn() { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; ((WARNINGS++)) || true; } err() { echo -e "${RED}[ERROR]${RESET} $*" >&2; } verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${BLUE}[DEBUG]${RESET} $*"; fi; } # ── AWS CLI wrapper ─────────────────────────────────────────────────── aws_cmd() { local args=("$@") [[ -n "$AWS_REGION" ]] && args+=(--region "$AWS_REGION") verbose "aws ${args[*]}" aws "${args[@]}" } # ── Dependency check ────────────────────────────────────────────────── check_deps() { for cmd in aws jq; do if ! command -v "$cmd" &>/dev/null; then err "${cmd} is required but not installed" exit 1 fi done # Verify AWS credentials if ! aws sts get-caller-identity &>/dev/null; then err "AWS credentials not configured or expired" exit 1 fi # Determine region if [[ -z "$AWS_REGION" ]]; then AWS_REGION=$(aws configure get region 2>/dev/null || echo "") if [[ -z "$AWS_REGION" ]]; then err "AWS_REGION is required (set via env var or aws configure)" exit 1 fi fi verbose "Using region: ${AWS_REGION}" verbose "Account: $(aws sts get-caller-identity --query 'Account' --output text 2>/dev/null)" } # ── Get volume list ─────────────────────────────────────────────────── get_volumes() { local filters=() if [[ -n "$VOLUME_IDS" ]]; then # Specific volumes requested local vol_array IFS=',' read -ra vol_array <<< "$VOLUME_IDS" aws_cmd ec2 describe-volumes \ --volume-ids "${vol_array[@]}" \ --query 'Volumes[*].VolumeId' \ --output text | tr '\t' '\n' return fi if [[ -n "$VOLUME_TAG_KEY" ]]; then filters+=(--filters "Name=tag:${VOLUME_TAG_KEY},Values=${VOLUME_TAG_VALUE:-*}") fi aws_cmd ec2 describe-volumes \ "${filters[@]}" \ --query 'Volumes[*].VolumeId' \ --output text | tr '\t' '\n' } # ── Get account ID ─────────────────────────────────────────────────── get_account_id() { aws sts get-caller-identity --query 'Account' --output text } # ══════════════════════════════════════════════════════════════════════ # SNAPSHOT MODE # ══════════════════════════════════════════════════════════════════════ do_snapshot() { log "Creating EBS snapshots..." local volumes volumes=$(get_volumes) if [[ -z "$volumes" ]]; then warn "No volumes found matching criteria" return fi local vol_count vol_count=$(echo "$volumes" | wc -l) log "Found ${vol_count} volume(s) to snapshot" local created=0 local failed=0 local snapshot_ids=() local now now=$(date -u +%Y-%m-%dT%H:%M:%SZ) while IFS= read -r vol_id; do [[ -z "$vol_id" ]] && continue verbose "Snapshotting ${vol_id}..." local vol_name # shellcheck disable=SC2016 vol_name=$(aws_cmd ec2 describe-volumes \ --volume-ids "$vol_id" \ --query 'Volumes[0].Tags[?Key==`Name`].Value | [0]' \ --output text 2>/dev/null) || vol_name="N/A" [[ "$vol_name" == "None" ]] && vol_name="N/A" local snap_id snap_id=$(aws_cmd ec2 create-snapshot \ --volume-id "$vol_id" \ --description "$SNAPSHOT_DESCRIPTION" \ --tag-specifications "ResourceType=snapshot,Tags=[ {Key=Name,Value=snap-${vol_id}-$(date +%Y%m%d)}, {Key=CreatedBy,Value=ebs-snapshot-manager}, {Key=CreatedAt,Value=${now}}, {Key=VolumeId,Value=${vol_id}}, {Key=VolumeName,Value=${vol_name}} ]" \ --query 'SnapshotId' \ --output text 2>/dev/null) || snap_id="" if [[ -n "$snap_id" ]]; then echo -e " ${GREEN}✓${RESET} ${vol_id} → ${snap_id} (${vol_name})" snapshot_ids+=("$snap_id") ((created++)) || true else echo -e " ${RED}✗${RESET} ${vol_id} — snapshot creation failed" ((failed++)) || true fi done <<< "$volumes" # Wait for completion if [[ "$NO_WAIT" != "true" && ${#snapshot_ids[@]} -gt 0 ]]; then log "Waiting for ${#snapshot_ids[@]} snapshot(s) to complete..." for snap_id in "${snapshot_ids[@]}"; do if aws_cmd ec2 wait snapshot-completed --snapshot-ids "$snap_id" 2>/dev/null; then local size size=$(aws_cmd ec2 describe-snapshots \ --snapshot-ids "$snap_id" \ --query 'Snapshots[0].VolumeSize' \ --output text 2>/dev/null) || size="?" verbose "${snap_id} completed (${size} GiB)" else warn "${snap_id} did not complete within timeout" fi done fi echo "" log "Snapshots created: ${created}, failed: ${failed}" } # ══════════════════════════════════════════════════════════════════════ # PRUNE MODE # ══════════════════════════════════════════════════════════════════════ do_prune() { local cutoff_epoch cutoff_epoch=$(date -d "-${RETENTION_DAYS} days" +%s 2>/dev/null) || \ cutoff_epoch=$(date -v-"${RETENTION_DAYS}"d +%s 2>/dev/null) || { err "Could not calculate retention cutoff date" exit 1 } local cutoff_date cutoff_date=$(date -d "@${cutoff_epoch}" +%Y-%m-%dT%H:%M:%S 2>/dev/null) || \ cutoff_date=$(date -r "${cutoff_epoch}" +%Y-%m-%dT%H:%M:%S 2>/dev/null) log "Pruning snapshots older than ${RETENTION_DAYS} days (before ${cutoff_date})" if [[ "$DRY_RUN" == "true" ]]; then log "${YELLOW}DRY RUN${RESET} — no snapshots will be deleted. Use --force to delete." fi local owner_id owner_id=$(get_account_id) local snapshots_json snapshots_json=$(aws_cmd ec2 describe-snapshots \ --owner-ids "$owner_id" \ --filters "Name=tag:CreatedBy,Values=ebs-snapshot-manager" \ --query 'Snapshots[*].{Id:SnapshotId,Start:StartTime,Size:VolumeSize,Vol:VolumeId}' \ --output json) local total total=$(echo "$snapshots_json" | jq 'length') echo "$snapshots_json" | jq -c '.[]' | while IFS= read -r snap; do local snap_id start_time size vol_id snap_id=$(echo "$snap" | jq -r '.Id') start_time=$(echo "$snap" | jq -r '.Start') size=$(echo "$snap" | jq -r '.Size') vol_id=$(echo "$snap" | jq -r '.Vol') local snap_epoch snap_epoch=$(date -d "$start_time" +%s 2>/dev/null) || \ snap_epoch=$(date -jf "%Y-%m-%dT%H:%M:%S" "${start_time%%.*}" +%s 2>/dev/null) || snap_epoch=0 if [[ $snap_epoch -lt $cutoff_epoch ]]; then local age_days=$(( ($(date +%s) - snap_epoch) / 86400 )) if [[ "$DRY_RUN" == "true" ]]; then echo -e " ${YELLOW}⊘${RESET} ${snap_id} — ${age_days}d old, ${size} GiB, vol: ${vol_id} (would delete)" else if aws_cmd ec2 delete-snapshot --snapshot-id "$snap_id" 2>/dev/null; then echo -e " ${GREEN}✓${RESET} ${snap_id} — deleted (${age_days}d old, ${size} GiB)" else echo -e " ${RED}✗${RESET} ${snap_id} — delete failed" fi fi fi done log "Total managed snapshots: ${total}" } # ══════════════════════════════════════════════════════════════════════ # COPY-REGION MODE # ══════════════════════════════════════════════════════════════════════ do_copy_region() { if [[ -z "$COPY_TO_REGION" ]]; then err "Target region required. Use --copy-region REGION or set COPY_TO_REGION" exit 1 fi log "Copying latest snapshots to ${COPY_TO_REGION}..." local owner_id owner_id=$(get_account_id) # Get volumes to copy snapshots for local volumes if [[ -n "$TARGET_VOLUME" ]]; then volumes="$TARGET_VOLUME" else volumes=$(get_volumes) fi if [[ -z "$volumes" ]]; then warn "No volumes found" return fi local copied=0 local failed=0 while IFS= read -r vol_id; do [[ -z "$vol_id" ]] && continue # Find latest snapshot for this volume local latest_snap latest_snap=$(aws_cmd ec2 describe-snapshots \ --owner-ids "$owner_id" \ --filters "Name=volume-id,Values=${vol_id}" "Name=status,Values=completed" \ --query 'sort_by(Snapshots, &StartTime)[-1].SnapshotId' \ --output text 2>/dev/null) || latest_snap="" if [[ -z "$latest_snap" || "$latest_snap" == "None" ]]; then echo -e " ${YELLOW}⊘${RESET} ${vol_id} — no completed snapshots found" continue fi # Copy to target region local copy_id copy_id=$(aws ec2 copy-snapshot \ --region "$COPY_TO_REGION" \ --source-region "$AWS_REGION" \ --source-snapshot-id "$latest_snap" \ --description "DR copy of ${latest_snap} from ${AWS_REGION}" \ --tag-specifications "ResourceType=snapshot,Tags=[ {Key=Name,Value=dr-copy-${latest_snap}}, {Key=CreatedBy,Value=ebs-snapshot-manager}, {Key=SourceRegion,Value=${AWS_REGION}}, {Key=SourceSnapshotId,Value=${latest_snap}}, {Key=VolumeId,Value=${vol_id}} ]" \ --query 'SnapshotId' \ --output text 2>/dev/null) || copy_id="" if [[ -n "$copy_id" ]]; then echo -e " ${GREEN}✓${RESET} ${latest_snap} → ${copy_id} (${AWS_REGION} → ${COPY_TO_REGION})" ((copied++)) || true else echo -e " ${RED}✗${RESET} ${latest_snap} — copy failed" ((failed++)) || true fi done <<< "$volumes" echo "" log "Copied: ${copied}, failed: ${failed}" } # ══════════════════════════════════════════════════════════════════════ # AUDIT MODE # ══════════════════════════════════════════════════════════════════════ do_audit() { log "Auditing EBS snapshots in ${AWS_REGION}..." local owner_id owner_id=$(get_account_id) local snapshots_json snapshots_json=$(aws_cmd ec2 describe-snapshots \ --owner-ids "$owner_id" \ --query 'Snapshots[*].{Id:SnapshotId,Vol:VolumeId,Size:VolumeSize,Status:State,Start:StartTime,Desc:Description,Tags:Tags}' \ --output json) local total total=$(echo "$snapshots_json" | jq 'length') if [[ "$total" -eq 0 ]]; then log "No snapshots found" return fi # Get existing volumes for orphan detection local existing_volumes existing_volumes=$(aws_cmd ec2 describe-volumes \ --query 'Volumes[*].VolumeId' \ --output text | tr '\t' '\n' | sort) local orphan_count=0 local untagged_count=0 local managed_count=0 echo "" echo -e "${BOLD}Snapshot Inventory${RESET}" printf " %-24s %-14s %8s %6s %s\n" "SNAPSHOT" "VOLUME" "SIZE" "AGE" "STATUS" echo " $(printf '%.0s─' {1..70})" echo "$snapshots_json" | jq -c '.[]' | while IFS= read -r snap; do local snap_id vol_id size status start_time snap_id=$(echo "$snap" | jq -r '.Id') vol_id=$(echo "$snap" | jq -r '.Vol') size=$(echo "$snap" | jq -r '.Size') status=$(echo "$snap" | jq -r '.Status') start_time=$(echo "$snap" | jq -r '.Start') local snap_epoch snap_epoch=$(date -d "$start_time" +%s 2>/dev/null) || snap_epoch=0 local age_days=$(( ($(date +%s) - snap_epoch) / 86400 )) # Check if managed local is_managed is_managed=$(echo "$snap" | jq -r '.Tags // [] | map(select(.Key == "CreatedBy" and .Value == "ebs-snapshot-manager")) | length') # Check if orphaned local is_orphan="no" if ! echo "$existing_volumes" | grep -q "^${vol_id}$" 2>/dev/null; then is_orphan="yes" fi # Check if tagged local tag_count tag_count=$(echo "$snap" | jq '.Tags // [] | length') local status_marker="" if [[ "$is_orphan" == "yes" ]]; then status_marker="${RED}orphan${RESET}" elif [[ "$tag_count" -eq 0 ]]; then status_marker="${YELLOW}untagged${RESET}" elif [[ "$is_managed" -gt 0 ]]; then status_marker="${GREEN}managed${RESET}" else status_marker="${status}" fi printf " %-24s %-14s %6s G %4sd %b\n" \ "$snap_id" "$vol_id" "$size" "$age_days" "$status_marker" done # Summary stats local total_size total_size=$(echo "$snapshots_json" | jq '[.[].Size] | add // 0') orphan_count=$(echo "$snapshots_json" | jq --arg vols "$existing_volumes" ' [.[] | select(.Vol as $v | ($vols | split("\n") | map(select(. != "")) | index($v) == null))] | length ') untagged_count=$(echo "$snapshots_json" | jq '[.[] | select((.Tags // []) | length == 0)] | length') managed_count=$(echo "$snapshots_json" | jq '[.[] | select((.Tags // []) | map(select(.Key == "CreatedBy" and .Value == "ebs-snapshot-manager")) | length > 0)] | length') local monthly_cost monthly_cost=$(echo "$total_size * 0.05" | bc 2>/dev/null || echo "?") echo "" echo -e "${BOLD}Summary${RESET}" echo -e " Total snapshots: ${total}" echo -e " Managed snapshots: ${managed_count}" echo -e " Total storage: ${total_size} GiB" echo -e " Est. monthly cost: \$${monthly_cost}" echo -e " Orphaned: ${orphan_count}" echo -e " Untagged: ${untagged_count}" if [[ "$orphan_count" -gt 0 ]]; then echo "" warn "${orphan_count} orphaned snapshot(s) found — source volume no longer exists" fi # Check volumes without recent snapshots echo "" echo -e "${BOLD}Volumes Without Recent Snapshots (>${RETENTION_DAYS}d)${RESET}" local volumes_json volumes_json=$(aws_cmd ec2 describe-volumes \ --query 'Volumes[*].{Id:VolumeId,Size:Size,State:State}' \ --output json) echo "$volumes_json" | jq -c '.[]' | while IFS= read -r vol; do local v_id v_size v_id=$(echo "$vol" | jq -r '.Id') v_size=$(echo "$vol" | jq -r '.Size') local latest_snap_time latest_snap_time=$(aws_cmd ec2 describe-snapshots \ --owner-ids "$owner_id" \ --filters "Name=volume-id,Values=${v_id}" "Name=status,Values=completed" \ --query 'sort_by(Snapshots, &StartTime)[-1].StartTime' \ --output text 2>/dev/null) || latest_snap_time="None" if [[ "$latest_snap_time" == "None" || -z "$latest_snap_time" ]]; then echo -e " ${RED}✗${RESET} ${v_id} (${v_size} GiB) — ${RED}no snapshots${RESET}" else local snap_epoch snap_epoch=$(date -d "$latest_snap_time" +%s 2>/dev/null) || snap_epoch=0 local cutoff_epoch cutoff_epoch=$(date -d "-${RETENTION_DAYS} days" +%s 2>/dev/null) || cutoff_epoch=0 if [[ $snap_epoch -lt $cutoff_epoch ]]; then local age=$(( ($(date +%s) - snap_epoch) / 86400 )) echo -e " ${YELLOW}!${RESET} ${v_id} (${v_size} GiB) — last snapshot ${age}d ago" fi fi done # Prometheus output if [[ "$OUTPUT_FORMAT" == "prometheus" ]]; then echo "" echo "# HELP ebs_snapshots_total Total EBS snapshots" echo "# TYPE ebs_snapshots_total gauge" echo "ebs_snapshots_total{region=\"${AWS_REGION}\"} ${total}" echo "# HELP ebs_snapshots_managed_total Managed EBS snapshots" echo "# TYPE ebs_snapshots_managed_total gauge" echo "ebs_snapshots_managed_total{region=\"${AWS_REGION}\"} ${managed_count}" echo "# HELP ebs_snapshots_orphaned_total Orphaned EBS snapshots" echo "# TYPE ebs_snapshots_orphaned_total gauge" echo "ebs_snapshots_orphaned_total{region=\"${AWS_REGION}\"} ${orphan_count}" echo "# HELP ebs_snapshots_untagged_total Untagged EBS snapshots" echo "# TYPE ebs_snapshots_untagged_total gauge" echo "ebs_snapshots_untagged_total{region=\"${AWS_REGION}\"} ${untagged_count}" echo "# HELP ebs_snapshots_size_gib_total Total snapshot storage in GiB" echo "# TYPE ebs_snapshots_size_gib_total gauge" echo "ebs_snapshots_size_gib_total{region=\"${AWS_REGION}\"} ${total_size}" fi } # ══════════════════════════════════════════════════════════════════════ # RESTORE MODE # ══════════════════════════════════════════════════════════════════════ do_restore() { if [[ -z "$TARGET_SNAPSHOT" ]]; then err "Snapshot ID required. Use --restore SNAP_ID" exit 1 fi log "Restoring volume from snapshot ${TARGET_SNAPSHOT}..." # Verify snapshot exists and is completed local snap_info snap_info=$(aws_cmd ec2 describe-snapshots \ --snapshot-ids "$TARGET_SNAPSHOT" \ --query 'Snapshots[0].{State:State,Size:VolumeSize,Vol:VolumeId}' \ --output json 2>/dev/null) || { err "Snapshot ${TARGET_SNAPSHOT} not found" exit 1 } local snap_state snap_size source_vol snap_state=$(echo "$snap_info" | jq -r '.State') snap_size=$(echo "$snap_info" | jq -r '.Size') source_vol=$(echo "$snap_info" | jq -r '.Vol') if [[ "$snap_state" != "completed" ]]; then err "Snapshot state is '${snap_state}' — must be 'completed'" exit 1 fi # Determine AZ if [[ -z "$RESTORE_AZ" ]]; then RESTORE_AZ=$(aws_cmd ec2 describe-availability-zones \ --query 'AvailabilityZones[0].ZoneName' \ --output text) log "No AZ specified, using ${RESTORE_AZ}" fi # Build create-volume args local create_args=( ec2 create-volume --snapshot-id "$TARGET_SNAPSHOT" --availability-zone "$RESTORE_AZ" --volume-type "$RESTORE_VOLUME_TYPE" --tag-specifications "ResourceType=volume,Tags=[ {Key=Name,Value=restored-from-${TARGET_SNAPSHOT}}, {Key=CreatedBy,Value=ebs-snapshot-manager}, {Key=RestoredFrom,Value=${TARGET_SNAPSHOT}}, {Key=SourceVolumeId,Value=${source_vol}} ]" ) [[ -n "$RESTORE_IOPS" ]] && create_args+=(--iops "$RESTORE_IOPS") [[ -n "$RESTORE_THROUGHPUT" ]] && create_args+=(--throughput "$RESTORE_THROUGHPUT") local vol_id vol_id=$(aws_cmd "${create_args[@]}" \ --query 'VolumeId' \ --output text 2>/dev/null) || { err "Failed to create volume from snapshot" exit 1 } echo -e " ${GREEN}✓${RESET} Created volume ${vol_id}" echo -e " Source snapshot: ${TARGET_SNAPSHOT}" echo -e " Size: ${snap_size} GiB" echo -e " Type: ${RESTORE_VOLUME_TYPE}" echo -e " AZ: ${RESTORE_AZ}" # Wait for volume to become available log "Waiting for volume to become available..." if aws_cmd ec2 wait volume-available --volume-ids "$vol_id" 2>/dev/null; then echo -e " ${GREEN}✓${RESET} Volume ${vol_id} is available" else warn "Volume did not become available within timeout" fi } # ══════════════════════════════════════════════════════════════════════ # LIST MODE # ══════════════════════════════════════════════════════════════════════ do_list() { local owner_id owner_id=$(get_account_id) local filters=("Name=owner-id,Values=${owner_id}") if [[ -n "$TARGET_VOLUME" ]]; then filters+=("Name=volume-id,Values=${TARGET_VOLUME}") fi local snapshots_json snapshots_json=$(aws_cmd ec2 describe-snapshots \ --owner-ids "$owner_id" \ ${TARGET_VOLUME:+--filters "Name=volume-id,Values=${TARGET_VOLUME}"} \ --query 'sort_by(Snapshots, &StartTime) | reverse(@) | [*].{Id:SnapshotId,Vol:VolumeId,Size:VolumeSize,Status:State,Start:StartTime,Desc:Description}' \ --output json) local total total=$(echo "$snapshots_json" | jq 'length') if [[ "$total" -eq 0 ]]; then log "No snapshots found" return fi if [[ "$OUTPUT_FORMAT" == "json" ]]; then echo "$snapshots_json" | jq '.' return fi echo "" printf " %-24s %-14s %8s %-12s %-22s %s\n" "SNAPSHOT" "VOLUME" "SIZE" "STATUS" "CREATED" "DESCRIPTION" echo " $(printf '%.0s─' {1..100})" echo "$snapshots_json" | jq -c '.[]' | while IFS= read -r snap; do local snap_id vol_id size status start_time desc snap_id=$(echo "$snap" | jq -r '.Id') vol_id=$(echo "$snap" | jq -r '.Vol') size=$(echo "$snap" | jq -r '.Size') status=$(echo "$snap" | jq -r '.Status') start_time=$(echo "$snap" | jq -r '.Start' | cut -c1-19) desc=$(echo "$snap" | jq -r '.Desc' | cut -c1-40) printf " %-24s %-14s %6s G %-12s %-22s %s\n" \ "$snap_id" "$vol_id" "$size" "$status" "$start_time" "$desc" done echo "" log "Total: ${total} snapshot(s)" } # ══════════════════════════════════════════════════════════════════════ # MAIN # ══════════════════════════════════════════════════════════════════════ show_help() { cat </dev/null || echo 'default')}" echo -e "Mode: ${RUN_MODE}" echo -e "Time: $(date -u +%Y-%m-%dT%H:%M:%SZ)" echo "" check_deps case "$RUN_MODE" in snapshot) do_snapshot ;; prune) do_prune ;; copy-region) do_copy_region ;; audit) do_audit ;; restore) do_restore ;; list) do_list ;; esac local end_time end_time=$(date +%s) local duration=$(( end_time - START_TIME )) echo "" log "Completed in ${duration}s" if [[ $WARNINGS -gt 0 ]]; then exit 2 fi } main "$@"