#!/usr/bin/env bash set -o errexit set -o nounset set -o pipefail DRY_RUN=true TIMESTAMP="$(date +%Y%m%d_%H%M%S)" LOG_FILE="${LOG_FILE:-/tmp/veritas_extend_${TIMESTAMP}.log}" SERVICE_GROUP="" DISKGROUP="" VOLUME="" MOUNTPOINT="" SIZE="" DISKS="" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" log() { local level="${1:-INFO}" shift || true local message="$*" local line line="$(printf '%s [%s] %s' "$(date '+%Y-%m-%d %H:%M:%S')" "$level" "$message")" printf '%s\n' "$line" printf '%s\n' "$line" >> "$LOG_FILE" } ok() { log "OK" "$*" } warning() { log "WARNING" "$*" } critical() { log "CRITICAL" "$*" } require_cmd() { local cmd="$1" if ! command -v "$cmd" >/dev/null 2>&1; then critical "required command not found: $cmd" return 1 fi return 0 } run_cmd() { local description="$1" shift if (( "$#" == 0 )); then critical "run_cmd called without a command" return 2 fi log "INFO" "$description" log "INFO" "command: $*" if [[ "$DRY_RUN" == "true" ]]; then log "INFO" "DRY-RUN: command not executed" return 0 fi "$@" 2>&1 | tee -a "$LOG_FILE" } confirm_execute() { local prompt="${1:-Type EXECUTE to continue with real changes}" local answer="" if [[ "$DRY_RUN" == "true" ]]; then ok "dry-run mode active; confirmation not required" return 0 fi warning "real execution mode requested with --execute" warning "$prompt" printf 'Type EXECUTE to continue: ' read -r answer if [[ "$answer" != "EXECUTE" ]]; then critical "confirmation failed; no changes made" exit 2 fi } usage_common() { cat <<'USAGE' Common options: --sg --dg --vol --mount --size <+SIZE> --disks "disk1 disk2" --execute --help USAGE } parse_common_args() { while (( "$#" > 0 )); do case "$1" in --sg) if [[ -z "${2:-}" ]]; then critical "missing value for --sg" exit 2 fi SERVICE_GROUP="${2:-}" shift 2 ;; --dg) if [[ -z "${2:-}" ]]; then critical "missing value for --dg" exit 2 fi DISKGROUP="${2:-}" shift 2 ;; --vol) if [[ -z "${2:-}" ]]; then critical "missing value for --vol" exit 2 fi VOLUME="${2:-}" shift 2 ;; --mount) if [[ -z "${2:-}" ]]; then critical "missing value for --mount" exit 2 fi MOUNTPOINT="${2:-}" shift 2 ;; --size) if [[ -z "${2:-}" ]]; then critical "missing value for --size" exit 2 fi SIZE="${2:-}" shift 2 ;; --disks) if [[ -z "${2:-}" ]]; then critical "missing value for --disks" exit 2 fi DISKS="${2:-}" shift 2 ;; --execute) DRY_RUN=false shift ;; --help|-h) usage_common exit 0 ;; *) critical "unknown argument: $1" usage_common exit 2 ;; esac done } require_nonempty() { local value="$1" local name="$2" if [[ -z "$value" ]]; then critical "missing required argument: $name" return 1 fi return 0 } require_inputs() { local failed=0 local name for name in "$@"; do case "$name" in sg) require_nonempty "$SERVICE_GROUP" "--sg" || failed=1 ;; dg) require_nonempty "$DISKGROUP" "--dg" || failed=1 ;; vol) require_nonempty "$VOLUME" "--vol" || failed=1 ;; mount) require_nonempty "$MOUNTPOINT" "--mount" || failed=1 ;; size) require_nonempty "$SIZE" "--size" || failed=1 ;; disks) require_nonempty "$DISKS" "--disks" || failed=1 ;; *) critical "internal error: unknown required input '$name'"; failed=1 ;; esac done if (( failed != 0 )); then usage_common exit 2 fi } has_cmd() { command -v "$1" >/dev/null 2>&1 } capture_cmd() { local description="$1" shift log "INFO" "$description" log "INFO" "command: $*" "$@" 2>&1 | tee -a "$LOG_FILE" } disk_status_line() { local disk="$1" vxdisk list "$disk" 2>/dev/null | awk -F': *' ' /device:/ {device=$2} /status:/ {status=$2} END { if (device != "" || status != "") { print device "|" status } }' } vxprint_volume_device() { local dg="$1" local vol="$2" vxprint -g "$dg" -F '%device' "$vol" 2>/dev/null || true }