Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
This commit is contained in:
@@ -0,0 +1,726 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#########################################################################################
|
||||
#### azure-snapshot-manager.sh — Create, rotate, list, audit, and restore Azure ####
|
||||
#### managed disk snapshots via az CLI. Automated retention and fleet-wide ops ####
|
||||
#### Requires: bash 4+, az CLI, jq ####
|
||||
#### ####
|
||||
#### Author: Phil Connor ####
|
||||
#### Contact: contact@mylinux.work ####
|
||||
#### License: MIT ####
|
||||
#### Version 1.01 ####
|
||||
#### ####
|
||||
#### Usage: ####
|
||||
#### ./azure-snapshot-manager.sh --snapshot --all ####
|
||||
#### ####
|
||||
#### See --help for all options. ####
|
||||
#########################################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Colors (pre-initialized) ─────────────────────────────────────────
|
||||
RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET=""
|
||||
|
||||
setup_colors() {
|
||||
if [[ "${COLOR:-auto}" == "never" ]]; then
|
||||
return
|
||||
fi
|
||||
if [[ "${COLOR:-auto}" == "always" ]] || [[ -t 1 ]]; then
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
DIM='\033[2m'
|
||||
RESET='\033[0m'
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Logging ───────────────────────────────────────────────────────────
|
||||
log() { echo -e "${BLUE}[INFO]${RESET} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
|
||||
err() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
|
||||
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${DIM}[DEBUG]${RESET} $*"; fi; }
|
||||
die() { err "$*"; exit 1; }
|
||||
|
||||
section_header() {
|
||||
echo ""
|
||||
echo -e " ${BOLD}${CYAN}── $1 ──${RESET}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
field() {
|
||||
printf " ${BOLD}%-22s${RESET} %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
field_color() {
|
||||
printf " ${BOLD}%-22s${RESET} %b\n" "$1" "$2"
|
||||
}
|
||||
|
||||
elapsed() {
|
||||
local end_time
|
||||
end_time=$(date +%s)
|
||||
echo "$(( end_time - START_TIME ))s"
|
||||
}
|
||||
|
||||
# ── Defaults ──────────────────────────────────────────────────────────
|
||||
RUN_MODE=""
|
||||
ALSO_ROTATE="false"
|
||||
VM_NAME=""
|
||||
RESOURCE_GROUP=""
|
||||
TARGET_ALL="false"
|
||||
SNAPSHOT_ID=""
|
||||
KEEP="${ASM_KEEP:-3}"
|
||||
PREFIX="${ASM_PREFIX:-auto}"
|
||||
MAX_AGE="${ASM_MAX_AGE:-7}"
|
||||
OUTPUT_FORMAT="${ASM_FORMAT:-text}"
|
||||
DRY_RUN="true"
|
||||
FORCE="false"
|
||||
VERBOSE="${VERBOSE:-false}"
|
||||
COLOR="${COLOR:-auto}"
|
||||
SUBSCRIPTION=""
|
||||
|
||||
# ── State ─────────────────────────────────────────────────────────────
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
readonly SCRIPT_NAME
|
||||
START_TIME=""
|
||||
SNAP_CREATED=0
|
||||
SNAP_DELETED=0
|
||||
SNAP_ERRORS=0
|
||||
|
||||
# ── Dependency and credential checks ────────────────────────────────
|
||||
check_deps() {
|
||||
command -v az &>/dev/null || die "az CLI is required (install: https://aka.ms/InstallAzureCLIDeb)"
|
||||
command -v jq &>/dev/null || die "jq is required"
|
||||
}
|
||||
|
||||
check_credentials() {
|
||||
local acct
|
||||
acct=$(az account show --output json 2>&1) || die "Azure credentials not configured — run 'az login'"
|
||||
|
||||
local sub_name sub_id
|
||||
sub_name=$(echo "$acct" | jq -r '.name')
|
||||
sub_id=$(echo "$acct" | jq -r '.id')
|
||||
verbose "Subscription: ${sub_name} (${sub_id})"
|
||||
log "Subscription: ${sub_name}"
|
||||
|
||||
if [[ -n "$SUBSCRIPTION" ]]; then
|
||||
az account set --subscription "$SUBSCRIPTION" 2>/dev/null \
|
||||
|| die "Cannot switch to subscription: ${SUBSCRIPTION}"
|
||||
log "Switched to subscription: ${SUBSCRIPTION}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Azure CLI wrapper ────────────────────────────────────────────────
|
||||
az_cmd() {
|
||||
local args=("$@")
|
||||
[[ -n "$SUBSCRIPTION" ]] && args+=(--subscription "$SUBSCRIPTION")
|
||||
verbose "az ${args[*]}"
|
||||
az "${args[@]}"
|
||||
}
|
||||
|
||||
# ── VM helpers ───────────────────────────────────────────────────────
|
||||
get_all_vms() {
|
||||
local args=(vm list --output json)
|
||||
[[ -n "$RESOURCE_GROUP" ]] && args+=(--resource-group "$RESOURCE_GROUP")
|
||||
az_cmd "${args[@]}" 2>/dev/null
|
||||
}
|
||||
|
||||
get_vm_os_disk_id() {
|
||||
local vm_name="$1" rg="$2"
|
||||
az_cmd vm show --name "$vm_name" --resource-group "$rg" \
|
||||
--query 'storageProfile.osDisk.managedDisk.id' --output tsv 2>/dev/null
|
||||
}
|
||||
|
||||
get_vm_rg() {
|
||||
local vm_json="$1"
|
||||
echo "$vm_json" | jq -r '.resourceGroup'
|
||||
}
|
||||
|
||||
# ── Snapshot helpers ─────────────────────────────────────────────────
|
||||
list_snapshots() {
|
||||
local args=(snapshot list --output json)
|
||||
[[ -n "$RESOURCE_GROUP" ]] && args+=(--resource-group "$RESOURCE_GROUP")
|
||||
az_cmd "${args[@]}" 2>/dev/null
|
||||
}
|
||||
|
||||
managed_snapshots() {
|
||||
list_snapshots | jq --arg pfx "$PREFIX" \
|
||||
'[.[] | select(.name | startswith($pfx))]'
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# SNAPSHOT
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
do_snapshot() {
|
||||
local vm_json
|
||||
vm_json=$(get_all_vms)
|
||||
|
||||
local vms
|
||||
if [[ "$TARGET_ALL" == "true" ]]; then
|
||||
vms="$vm_json"
|
||||
elif [[ -n "$VM_NAME" ]]; then
|
||||
vms=$(echo "$vm_json" | jq --arg n "$VM_NAME" '[.[] | select(.name == $n)]')
|
||||
else
|
||||
die "Specify --vm NAME or --all"
|
||||
fi
|
||||
|
||||
local count
|
||||
count=$(echo "$vms" | jq 'length')
|
||||
[[ "$count" -eq 0 ]] && die "No VMs found"
|
||||
|
||||
local target_label="$VM_NAME"
|
||||
[[ "$TARGET_ALL" == "true" ]] && target_label="all (${count} VMs)"
|
||||
|
||||
section_header "Creating Snapshots"
|
||||
field "Target:" "$target_label"
|
||||
field "Prefix:" "$PREFIX"
|
||||
echo ""
|
||||
|
||||
echo "$vms" | jq -c '.[]' | while IFS= read -r vm; do
|
||||
local name rg disk_id snap_name
|
||||
name=$(echo "$vm" | jq -r '.name')
|
||||
rg=$(echo "$vm" | jq -r '.resourceGroup')
|
||||
disk_id=$(get_vm_os_disk_id "$name" "$rg")
|
||||
snap_name="${PREFIX}-${name}-$(date +%Y%m%d-%H%M%S)"
|
||||
|
||||
if [[ -z "$disk_id" ]]; then
|
||||
echo -e " ${RED}✗${RESET} ${name} (${rg}) no OS disk found"
|
||||
((SNAP_ERRORS++)) || true
|
||||
continue
|
||||
fi
|
||||
|
||||
verbose "Snapshotting ${name} disk ${disk_id}"
|
||||
|
||||
if az_cmd snapshot create \
|
||||
--resource-group "$rg" \
|
||||
--name "$snap_name" \
|
||||
--source "$disk_id" \
|
||||
--tags "managed-by=${SCRIPT_NAME}" "source-vm=${name}" \
|
||||
--output none 2>/dev/null; then
|
||||
echo -e " ${GREEN}✓${RESET} ${name} (${rg}) ${snap_name}"
|
||||
((SNAP_CREATED++)) || true
|
||||
else
|
||||
echo -e " ${RED}✗${RESET} ${name} (${rg}) failed"
|
||||
((SNAP_ERRORS++)) || true
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo ""
|
||||
field_color "Created:" "${GREEN}${SNAP_CREATED}${RESET}"
|
||||
if [[ "$SNAP_ERRORS" -gt 0 ]]; then
|
||||
field_color "Errors:" "${RED}${SNAP_ERRORS}${RESET}"
|
||||
fi
|
||||
|
||||
if [[ "$ALSO_ROTATE" == "true" ]]; then
|
||||
do_rotate
|
||||
fi
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# ROTATE
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
do_rotate() {
|
||||
section_header "Rotating Snapshots"
|
||||
field "Keep:" "$KEEP per VM"
|
||||
field "Prefix:" "$PREFIX"
|
||||
if [[ "$DRY_RUN" == "true" && "$FORCE" != "true" ]]; then
|
||||
field "Mode:" "DRY RUN (use --force to delete)"
|
||||
else
|
||||
field "Mode:" "LIVE — deletions are permanent"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
local snaps
|
||||
snaps=$(managed_snapshots)
|
||||
|
||||
local vm_names
|
||||
vm_names=$(echo "$snaps" | jq -r '.[].tags["source-vm"] // empty' | sort -u)
|
||||
|
||||
if [[ -z "$vm_names" ]]; then
|
||||
log "No managed snapshots found matching prefix '${PREFIX}'"
|
||||
return
|
||||
fi
|
||||
|
||||
while IFS= read -r vm; do
|
||||
[[ -z "$vm" ]] && continue
|
||||
local vm_snaps
|
||||
vm_snaps=$(echo "$snaps" | jq --arg vm "$vm" \
|
||||
'[.[] | select(.tags["source-vm"] == $vm)] | sort_by(.timeCreated) | reverse')
|
||||
local total
|
||||
total=$(echo "$vm_snaps" | jq 'length')
|
||||
|
||||
if (( total <= KEEP )); then
|
||||
verbose "${vm}: ${total} snapshots, keeping all"
|
||||
continue
|
||||
fi
|
||||
|
||||
local to_delete
|
||||
to_delete=$(echo "$vm_snaps" | jq --argjson k "$KEEP" '.[$k:]')
|
||||
local del_count
|
||||
del_count=$(echo "$to_delete" | jq 'length')
|
||||
|
||||
echo "$to_delete" | jq -c '.[]' | while IFS= read -r snap; do
|
||||
local sname srg
|
||||
sname=$(echo "$snap" | jq -r '.name')
|
||||
srg=$(echo "$snap" | jq -r '.resourceGroup')
|
||||
|
||||
if [[ "$DRY_RUN" == "true" && "$FORCE" != "true" ]]; then
|
||||
echo -e " ${DIM}[DRY RUN]${RESET} would delete ${sname} (${srg})"
|
||||
else
|
||||
if az_cmd snapshot delete --name "$sname" --resource-group "$srg" \
|
||||
--output none 2>/dev/null; then
|
||||
echo -e " ${YELLOW}✓${RESET} deleted ${sname}"
|
||||
((SNAP_DELETED++)) || true
|
||||
else
|
||||
echo -e " ${RED}✗${RESET} failed to delete ${sname}"
|
||||
((SNAP_ERRORS++)) || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
log "${vm}: ${total} total, keeping ${KEEP}, removing ${del_count}"
|
||||
done <<< "$vm_names"
|
||||
|
||||
echo ""
|
||||
field_color "Deleted:" "${YELLOW}${SNAP_DELETED}${RESET}"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# LIST
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
do_list() {
|
||||
section_header "All Snapshots"
|
||||
|
||||
local snaps
|
||||
snaps=$(list_snapshots)
|
||||
local count
|
||||
count=$(echo "$snaps" | jq 'length')
|
||||
|
||||
if [[ "$count" -eq 0 ]]; then
|
||||
log "No snapshots found"
|
||||
return
|
||||
fi
|
||||
|
||||
printf " %-36s %-16s %-8s %-12s %s\n" \
|
||||
"NAME" "RESOURCE_GROUP" "SIZE_GB" "AGE" "SOURCE_VM"
|
||||
printf " %s\n" "$(printf '%.0s─' {1..90})"
|
||||
|
||||
local now
|
||||
now=$(date +%s)
|
||||
|
||||
echo "$snaps" | jq -c '.[]' | while IFS= read -r snap; do
|
||||
local name rg size_gb created source_vm age_str
|
||||
name=$(echo "$snap" | jq -r '.name')
|
||||
rg=$(echo "$snap" | jq -r '.resourceGroup')
|
||||
size_gb=$(echo "$snap" | jq -r '.diskSizeGb // 0')
|
||||
created=$(echo "$snap" | jq -r '.timeCreated // ""')
|
||||
source_vm=$(echo "$snap" | jq -r '.tags["source-vm"] // "manual"')
|
||||
|
||||
if [[ -n "$created" ]]; then
|
||||
local snap_epoch
|
||||
snap_epoch=$(date -d "$created" +%s 2>/dev/null || echo 0)
|
||||
if [[ "$snap_epoch" -gt 0 ]]; then
|
||||
local age_days=$(( (now - snap_epoch) / 86400 ))
|
||||
age_str="${age_days}d"
|
||||
else
|
||||
age_str="unknown"
|
||||
fi
|
||||
else
|
||||
age_str="unknown"
|
||||
fi
|
||||
|
||||
printf " %-36s %-16s %-8s %-12s %s\n" \
|
||||
"${name:0:35}" "${rg:0:15}" "$size_gb" "$age_str" "${source_vm:0:20}"
|
||||
done
|
||||
|
||||
echo ""
|
||||
field "Total snapshots:" "$count"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# AUDIT
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
do_audit() {
|
||||
section_header "Snapshot Audit"
|
||||
|
||||
local vm_json
|
||||
vm_json=$(get_all_vms)
|
||||
local snaps
|
||||
snaps=$(list_snapshots)
|
||||
local now
|
||||
now=$(date +%s)
|
||||
|
||||
printf " %-24s %-16s %-20s %-8s %-8s %s\n" \
|
||||
"VM_NAME" "RESOURCE_GROUP" "LATEST_SNAPSHOT" "AGE" "COUNT" "STATUS"
|
||||
printf " %s\n" "$(printf '%.0s─' {1..95})"
|
||||
|
||||
echo "$vm_json" | jq -c '.[]' | while IFS= read -r vm; do
|
||||
local name rg
|
||||
name=$(echo "$vm" | jq -r '.name')
|
||||
rg=$(echo "$vm" | jq -r '.resourceGroup')
|
||||
|
||||
local vm_snaps snap_count
|
||||
vm_snaps=$(echo "$snaps" | jq --arg vm "$name" \
|
||||
'[.[] | select(.tags["source-vm"] == $vm)]')
|
||||
snap_count=$(echo "$vm_snaps" | jq 'length')
|
||||
|
||||
if [[ "$snap_count" -eq 0 ]]; then
|
||||
printf " %-24s %-16s %-20s %-8s %-8s %b%s%b\n" \
|
||||
"${name:0:23}" "${rg:0:15}" "(none)" "—" "0" \
|
||||
"$RED" "✗ Unprotected" "$RESET"
|
||||
continue
|
||||
fi
|
||||
|
||||
local latest_name latest_date age_str status color
|
||||
latest_name=$(echo "$vm_snaps" | jq -r 'sort_by(.timeCreated) | last | .name // ""')
|
||||
latest_date=$(echo "$vm_snaps" | jq -r 'sort_by(.timeCreated) | last | .timeCreated // ""')
|
||||
|
||||
if [[ -n "$latest_date" ]]; then
|
||||
local snap_epoch
|
||||
snap_epoch=$(date -d "$latest_date" +%s 2>/dev/null || echo 0)
|
||||
if [[ "$snap_epoch" -gt 0 ]]; then
|
||||
local age_days=$(( (now - snap_epoch) / 86400 ))
|
||||
age_str="${age_days}d"
|
||||
if (( age_days > MAX_AGE )); then
|
||||
status="⚠ Stale"
|
||||
color="$YELLOW"
|
||||
else
|
||||
status="✓ OK"
|
||||
color="$GREEN"
|
||||
fi
|
||||
else
|
||||
age_str="unknown"
|
||||
status="✓ OK"
|
||||
color="$GREEN"
|
||||
fi
|
||||
else
|
||||
age_str="unknown"
|
||||
status="✓ OK"
|
||||
color="$GREEN"
|
||||
fi
|
||||
|
||||
printf " %-24s %-16s %-20s %-8s %-8s %b%s%b\n" \
|
||||
"${name:0:23}" "${rg:0:15}" "${latest_name:0:19}" \
|
||||
"$age_str" "$snap_count" "$color" "$status" "$RESET"
|
||||
done
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# RESTORE
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
do_restore() {
|
||||
[[ -z "$VM_NAME" ]] && die "--restore requires --vm NAME"
|
||||
[[ -z "$SNAPSHOT_ID" ]] && die "--restore requires --snapshot-id NAME"
|
||||
[[ -z "$RESOURCE_GROUP" ]] && die "--restore requires --resource-group RG"
|
||||
|
||||
section_header "Restore from Snapshot"
|
||||
field "VM:" "$VM_NAME"
|
||||
field "Snapshot:" "$SNAPSHOT_ID"
|
||||
field "Resource Group:" "$RESOURCE_GROUP"
|
||||
echo ""
|
||||
|
||||
if [[ "$FORCE" != "true" ]]; then
|
||||
warn "This will replace the VM's OS disk. Use --force to confirm."
|
||||
return
|
||||
fi
|
||||
|
||||
log "Creating disk from snapshot..."
|
||||
local disk_name="restored-${VM_NAME}-$(date +%Y%m%d-%H%M%S)"
|
||||
local snap_id
|
||||
snap_id=$(az_cmd snapshot show --name "$SNAPSHOT_ID" --resource-group "$RESOURCE_GROUP" \
|
||||
--query 'id' --output tsv 2>/dev/null) || die "Snapshot not found: ${SNAPSHOT_ID}"
|
||||
|
||||
if az_cmd disk create \
|
||||
--resource-group "$RESOURCE_GROUP" \
|
||||
--name "$disk_name" \
|
||||
--source "$snap_id" \
|
||||
--output none 2>/dev/null; then
|
||||
echo -e " ${GREEN}✓${RESET} Disk created: ${disk_name}"
|
||||
else
|
||||
die "Failed to create disk from snapshot"
|
||||
fi
|
||||
|
||||
log "Deallocating VM..."
|
||||
az_cmd vm deallocate --name "$VM_NAME" --resource-group "$RESOURCE_GROUP" \
|
||||
--output none 2>/dev/null || die "Failed to deallocate VM"
|
||||
|
||||
local new_disk_id
|
||||
new_disk_id=$(az_cmd disk show --name "$disk_name" --resource-group "$RESOURCE_GROUP" \
|
||||
--query 'id' --output tsv 2>/dev/null)
|
||||
|
||||
log "Swapping OS disk..."
|
||||
if az_cmd vm update --name "$VM_NAME" --resource-group "$RESOURCE_GROUP" \
|
||||
--os-disk "$new_disk_id" --output none 2>/dev/null; then
|
||||
echo -e " ${GREEN}✓${RESET} OS disk swapped"
|
||||
else
|
||||
die "Failed to swap OS disk"
|
||||
fi
|
||||
|
||||
log "Starting VM..."
|
||||
az_cmd vm start --name "$VM_NAME" --resource-group "$RESOURCE_GROUP" \
|
||||
--output none 2>/dev/null
|
||||
echo -e " ${GREEN}✓${RESET} VM started"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# STATUS
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
do_status() {
|
||||
local vm_json
|
||||
vm_json=$(get_all_vms)
|
||||
local snaps
|
||||
snaps=$(list_snapshots)
|
||||
local now
|
||||
now=$(date +%s)
|
||||
|
||||
local total_vms=0 total_snaps=0 total_gb=0
|
||||
local protected=0 stale=0 unprotected=0
|
||||
|
||||
while IFS= read -r vm; do
|
||||
[[ -z "$vm" ]] && continue
|
||||
((total_vms++)) || true
|
||||
|
||||
local name
|
||||
name=$(echo "$vm" | jq -r '.name')
|
||||
|
||||
local vm_snaps snap_count
|
||||
vm_snaps=$(echo "$snaps" | jq --arg vm "$name" \
|
||||
'[.[] | select(.tags["source-vm"] == $vm)]')
|
||||
snap_count=$(echo "$vm_snaps" | jq 'length')
|
||||
total_snaps=$(( total_snaps + snap_count ))
|
||||
|
||||
local gb
|
||||
gb=$(echo "$vm_snaps" | jq '[.[].diskSizeGb // 0] | add // 0')
|
||||
total_gb=$(( total_gb + gb ))
|
||||
|
||||
if [[ "$snap_count" -eq 0 ]]; then
|
||||
((unprotected++)) || true
|
||||
continue
|
||||
fi
|
||||
|
||||
local latest_date
|
||||
latest_date=$(echo "$vm_snaps" | jq -r \
|
||||
'sort_by(.timeCreated) | last | .timeCreated // ""')
|
||||
|
||||
if [[ -n "$latest_date" ]]; then
|
||||
local snap_epoch
|
||||
snap_epoch=$(date -d "$latest_date" +%s 2>/dev/null || echo 0)
|
||||
if [[ "$snap_epoch" -gt 0 ]]; then
|
||||
local age_days=$(( (now - snap_epoch) / 86400 ))
|
||||
if (( age_days > MAX_AGE )); then
|
||||
((stale++)) || true
|
||||
else
|
||||
((protected++)) || true
|
||||
fi
|
||||
else
|
||||
((protected++)) || true
|
||||
fi
|
||||
else
|
||||
((protected++)) || true
|
||||
fi
|
||||
done < <(echo "$vm_json" | jq -c '.[]')
|
||||
|
||||
if [[ "$OUTPUT_FORMAT" == "prometheus" ]]; then
|
||||
cat <<EOF
|
||||
# HELP azure_snapshot_vms_total Total Azure VMs
|
||||
# TYPE azure_snapshot_vms_total gauge
|
||||
azure_snapshot_vms_total ${total_vms}
|
||||
# HELP azure_snapshot_total Total snapshots across all VMs
|
||||
# TYPE azure_snapshot_total gauge
|
||||
azure_snapshot_total ${total_snaps}
|
||||
# HELP azure_snapshot_storage_gb Total snapshot storage in GB
|
||||
# TYPE azure_snapshot_storage_gb gauge
|
||||
azure_snapshot_storage_gb ${total_gb}
|
||||
# HELP azure_snapshot_protected_vms VMs with recent snapshots
|
||||
# TYPE azure_snapshot_protected_vms gauge
|
||||
azure_snapshot_protected_vms ${protected}
|
||||
# HELP azure_snapshot_stale_vms VMs with snapshots older than threshold
|
||||
# TYPE azure_snapshot_stale_vms gauge
|
||||
azure_snapshot_stale_vms ${stale}
|
||||
# HELP azure_snapshot_unprotected_vms VMs with no snapshots
|
||||
# TYPE azure_snapshot_unprotected_vms gauge
|
||||
azure_snapshot_unprotected_vms ${unprotected}
|
||||
# HELP azure_snapshot_max_age_days Stale threshold in days
|
||||
# TYPE azure_snapshot_max_age_days gauge
|
||||
azure_snapshot_max_age_days ${MAX_AGE}
|
||||
# HELP azure_snapshot_retention_count Retention count setting
|
||||
# TYPE azure_snapshot_retention_count gauge
|
||||
azure_snapshot_retention_count ${KEEP}
|
||||
EOF
|
||||
return
|
||||
fi
|
||||
|
||||
section_header "Fleet Snapshot Status"
|
||||
field "VMs:" "$total_vms"
|
||||
field "Total snapshots:" "$total_snaps"
|
||||
field "Total storage:" "${total_gb} GB"
|
||||
field_color "Protected:" "${GREEN}${protected}${RESET}"
|
||||
if [[ "$stale" -gt 0 ]]; then
|
||||
field_color "Stale (>${MAX_AGE}d):" "${YELLOW}${stale}${RESET}"
|
||||
else
|
||||
field_color "Stale (>${MAX_AGE}d):" "${GREEN}0${RESET}"
|
||||
fi
|
||||
if [[ "$unprotected" -gt 0 ]]; then
|
||||
field_color "Unprotected:" "${RED}${unprotected}${RESET}"
|
||||
else
|
||||
field_color "Unprotected:" "${GREEN}0${RESET}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# HELP
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
show_help() {
|
||||
cat <<EOF
|
||||
${BOLD}${SCRIPT_NAME}${RESET} — Azure Snapshot Manager
|
||||
|
||||
Create, rotate, list, audit, and restore Azure managed disk snapshots
|
||||
via az CLI.
|
||||
|
||||
${BOLD}MODES${RESET}
|
||||
--snapshot Create snapshots of VM OS disks
|
||||
--rotate Prune old managed snapshots
|
||||
--list List all snapshots
|
||||
--audit Check snapshot coverage and staleness
|
||||
--restore Restore a VM from a snapshot
|
||||
--status Fleet-wide snapshot summary
|
||||
|
||||
${BOLD}TARGETING${RESET}
|
||||
--vm NAME Target a specific VM
|
||||
--all Target all VMs
|
||||
--resource-group RG Limit to a resource group
|
||||
|
||||
${BOLD}OPTIONS${RESET}
|
||||
--keep N Snapshots to retain per VM (default: 3)
|
||||
--prefix STR Managed snapshot name prefix (default: auto)
|
||||
--max-age DAYS Stale threshold for audit (default: 7)
|
||||
--snapshot-id NAME Snapshot name for restore
|
||||
--format FMT Output: text, prometheus (default: text)
|
||||
--subscription SUB Azure subscription name or ID
|
||||
--dry-run Preview deletions without executing (default)
|
||||
--force Execute deletions / skip confirmations
|
||||
--verbose Debug output
|
||||
--no-color Disable colored output
|
||||
--help Show this help message
|
||||
|
||||
${BOLD}ENVIRONMENT VARIABLES${RESET}
|
||||
ASM_KEEP Default retention count
|
||||
ASM_PREFIX Default snapshot name prefix
|
||||
ASM_MAX_AGE Default stale threshold (days)
|
||||
ASM_FORMAT Default output format
|
||||
VERBOSE Enable verbose output (true/false)
|
||||
COLOR Color mode: auto, always, never
|
||||
|
||||
${BOLD}EXAMPLES${RESET}
|
||||
# Snapshot a single VM
|
||||
${SCRIPT_NAME} --snapshot --vm web01 --resource-group prod-rg
|
||||
|
||||
# Snapshot all VMs
|
||||
${SCRIPT_NAME} --snapshot --all
|
||||
|
||||
# Snapshot and rotate in one run
|
||||
${SCRIPT_NAME} --snapshot --rotate --all --keep 3 --force
|
||||
|
||||
# Dry-run rotation
|
||||
${SCRIPT_NAME} --rotate --all --keep 3
|
||||
|
||||
# Execute rotation
|
||||
${SCRIPT_NAME} --rotate --all --keep 3 --force
|
||||
|
||||
# List all snapshots
|
||||
${SCRIPT_NAME} --list
|
||||
|
||||
# Audit fleet snapshot health
|
||||
${SCRIPT_NAME} --audit
|
||||
|
||||
# Restore a VM
|
||||
${SCRIPT_NAME} --restore --vm web01 --resource-group prod-rg --snapshot-id auto-web01-20260411
|
||||
|
||||
# Prometheus metrics output
|
||||
${SCRIPT_NAME} --status --format prometheus
|
||||
|
||||
${BOLD}EXIT CODES${RESET}
|
||||
0 Success
|
||||
1 Runtime error
|
||||
EOF
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# PARSE ARGS
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--snapshot) RUN_MODE="snapshot"; shift ;;
|
||||
--rotate)
|
||||
if [[ "$RUN_MODE" == "snapshot" ]]; then
|
||||
ALSO_ROTATE="true"
|
||||
else
|
||||
RUN_MODE="rotate"
|
||||
fi
|
||||
shift ;;
|
||||
--list) RUN_MODE="list"; shift ;;
|
||||
--audit) RUN_MODE="audit"; shift ;;
|
||||
--restore) RUN_MODE="restore"; shift ;;
|
||||
--status) RUN_MODE="status"; shift ;;
|
||||
--vm) VM_NAME="${2:?--vm requires a name}"; shift 2 ;;
|
||||
--resource-group) RESOURCE_GROUP="${2:?--resource-group requires a value}"; shift 2 ;;
|
||||
--all) TARGET_ALL="true"; shift ;;
|
||||
--keep) KEEP="${2:?--keep requires a number}"; shift 2 ;;
|
||||
--prefix) PREFIX="${2:?--prefix requires a string}"; shift 2 ;;
|
||||
--max-age) MAX_AGE="${2:?--max-age requires days}"; shift 2 ;;
|
||||
--snapshot-id) SNAPSHOT_ID="${2:?--snapshot-id requires a name}"; shift 2 ;;
|
||||
--format) OUTPUT_FORMAT="${2:?--format requires a value}"; shift 2 ;;
|
||||
--subscription) SUBSCRIPTION="${2:?--subscription requires a value}"; shift 2 ;;
|
||||
--dry-run) DRY_RUN="true"; shift ;;
|
||||
--force) FORCE="true"; shift ;;
|
||||
--verbose) VERBOSE="true"; shift ;;
|
||||
--no-color) COLOR="never"; shift ;;
|
||||
--help|-h) setup_colors; show_help; exit 0 ;;
|
||||
*) die "Unknown option: $1 (see --help)" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# MAIN
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
main() {
|
||||
parse_args "$@"
|
||||
setup_colors
|
||||
|
||||
if [[ -z "$RUN_MODE" ]]; then
|
||||
err "No mode specified"
|
||||
echo ""
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check_deps
|
||||
check_credentials
|
||||
|
||||
START_TIME=$(date +%s)
|
||||
|
||||
case "$RUN_MODE" in
|
||||
snapshot) do_snapshot ;;
|
||||
rotate) do_rotate ;;
|
||||
list) do_list ;;
|
||||
audit) do_audit ;;
|
||||
restore) do_restore ;;
|
||||
status) do_status ;;
|
||||
*) die "Unknown mode: ${RUN_MODE}" ;;
|
||||
esac
|
||||
|
||||
if [[ "$OUTPUT_FORMAT" != "prometheus" ]]; then
|
||||
echo ""
|
||||
field "Duration:" "$(elapsed)"
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user