Skip to content

Supports multi-PBS storage and SCSI mounting methods 多pbs储存库支持及scsi挂载方式 #3

Description

@zctmdc

我使用Q35机型进行挂载,会提示挂载失败,pcie插入失败,希望进行更新。

我使用ai进行了代码修改。
1、多pbs储存库支持
2、支持scsi插入方式
如果默认没scsi磁盘,需要使用 qm set {vmid} --scsiN --sataN --virtioN 命令绑定

#!/bin/bash
# Enable strict error handling
set -u            # Treat unset variables as an error
set -o pipefail   # Return the exit status of the last command in the pipe that failed

# ==========================================
# --- Global Variables & State Tracking ---
# ==========================================

# PBS_VARS: Associative array storing PBS connection details extracted from PVE storage config.
# Keys used: server, username, datastore, fingerprint, password
declare -A PBS_VARS

# ATTACHED_DEVICES: Array storing successfully mounted devices to ensure they are cleaned up on exit.
# Format stored: "DEVICE_ID|DRIVE_NAME" (e.g., "dev_123456789|drv_123456789")
ATTACHED_DEVICES=()

# State variables to hold user selections during the interactive menus
VMID=""          # Target VM ID where the disk will be mounted (must be running)
STORAGE=""       # The Proxmox PBS storage name (e.g., "pbs-store")
SOURCE_VMID=""   # The ID of the VM that the backup belongs to
SNAPSHOT=""      # The specific backup snapshot volume ID
DISK=""          # The specific disk file inside the backup (e.g., "drive-scsi0.img.fidx")
BUS_TARGET=""    # The hardware bus in the target VM to attach the disk to (e.g., "virtioscsi0.0", "auto")
DRIVER=""        # The QEMU block driver to use (e.g., "scsi-hd", "virtio-blk-pci")


# ==========================================
# --- Core Functions ---
# ==========================================

# --- Cleanup function: Safely removes attached disks on exit ---
# Triggered automatically when the script exits or when the user quits.
# It communicates with QEMU Monitor (QMP) to gracefully detach the virtual hardware and block nodes.
do_cleanup() {
    if [ ${#ATTACHED_DEVICES[@]} -gt 0 ]; then
        echo ""
        echo "=== Cleaning up attached backup disks ==="
        for item in "${ATTACHED_DEVICES[@]}"; do
            DEV="${item%%|*}"  # Extract Device ID (before the pipe)
            DRV="${item##*|}"  # Extract Drive Node name (after the pipe)
            echo "-> Detaching device [ID: $DEV, Node: $DRV]..."
            
            # 1. Send device_del to detach the virtual device from the guest VM bus using Heredoc
            local del_result
            del_result=$(socat - "UNIX:/run/qemu-server/${VMID}.qmp" <<EOF
{ "execute": "qmp_capabilities" }
{
    "execute": "device_del",
    "arguments": {
        "id": "${DEV}"
    }
}
EOF
)
            echo "   * device_del response: $del_result"
            
            # 2. Wait briefly to ensure QEMU finishes the asynchronous detach process
            sleep 1
            
            # 3. Send blockdev-del to release the underlying PBS block device node from QEMU memory using Heredoc
            local blk_result
            blk_result=$(socat - "UNIX:/run/qemu-server/${VMID}.qmp" <<EOF
{ "execute": "qmp_capabilities" }
{
    "execute": "blockdev-del",
    "arguments": {
        "node-name": "${DRV}"
    }
}
EOF
)
            echo "   * blockdev-del response: $blk_result"
        done
        echo "All attached disks cleaned up successfully."
    else
        echo ""
        echo "No attached devices to clean up."
    fi
    echo "Unlocking VM"
    qm unlock "$VMID"
}

# --- Generic menu selector ---
# Helper function to print a numbered menu from an array and handle user input.
# Returns: The array index (0-based), or special strings "q" (quit) / "back" / "invalid".
select_menu() {
    local prompt="$1"; shift
    local options=("$@")
    echo "" >&2
    echo "=== $prompt ===" >&2
    for i in "${!options[@]}"; do 
        echo "  $((i+1))) ${options[$i]}" >&2
    done
    echo "  b) [Back to previous]" >&2
    echo "  q) [Quit script]" >&2
    read -r -p ">>> Please enter selection: " choice >&2
    case "$choice" in
        q) echo "q" ;;
        b|back) echo "back" ;;
        # Check if input is a valid number within the array bounds
        *) [[ "$choice" =~ ^[0-9]+$ && "$choice" -le "${#options[@]}" && "$choice" -gt 0 ]] && echo "$((choice-1))" || echo "invalid" ;;
    esac
}

# --- Retrieve PBS storage configuration ---
# Parses /etc/pve/storage.cfg and the private password file to populate the PBS_VARS array.
get_pbs_config() {
    local storage="$1"
    # Extract the block of configuration specific to the selected storage
    local cfg=$(sed -n "/^pbs: ${storage}$/,/^\S/ p" /etc/pve/storage.cfg)
    
    PBS_VARS["server"]=$(echo "$cfg" | grep "server" | awk '{print $2}')
    PBS_VARS["username"]=$(echo "$cfg" | grep "username" | awk '{print $2}')
    PBS_VARS["datastore"]=$(echo "$cfg" | grep "datastore" | awk '{print $2}')
    PBS_VARS["fingerprint"]=$(echo "$cfg" | grep "fingerprint" | awk '{print $2}')
    
    # Passwords for storages are kept in a separate secure directory in PVE
    if [ -f "/etc/pve/priv/storage/${storage}.pw" ]; then
        PBS_VARS["password"]=$(cat "/etc/pve/priv/storage/${storage}.pw")
    else
        PBS_VARS["password"]=""
    fi
}


# ==========================================
# --- State Handlers (Interactive Menus) ---
# ==========================================

# Select the target running VM to mount the backup to
handle_vm() {
    # Get a list of only 'running' VMs
    mapfile -t list < <(qm list | awk '$3 == "running" {print $1, $2}')
    idx=$(select_menu "Select a running VM" "${list[@]}")
    [[ "$idx" == "q" ]] && { do_cleanup; exit 0; }
    [[ "$idx" == "back" || "$idx" == "invalid" ]] && return 1
    
    VMID=$(echo "${list[$idx]}" | awk '{print $1}')
    return 0
}

# Select the PBS storage backend
handle_storage() {
    # Get all storages of type 'pbs'
    mapfile -t list < <(grep -E '^pbs:\s+' /etc/pve/storage.cfg | awk '{print $2}')
    idx=$(select_menu "Select PBS storage" "${list[@]}")
    [[ "$idx" == "q" ]] && { do_cleanup; exit 0; }
    [[ "$idx" == "back" ]] && return 1
    
    STORAGE="${list[$idx]}"
    get_pbs_config "$STORAGE"
    return 0
}

# Select the VMID whose backup we want to restore from
handle_vmid() {
    local fetch_names
    # Prompt whether to fetch names from backup metadata
    while true; do
        echo "" >&2
        echo "=== Fetch VM Names ===" >&2
        read -r -p ">>> Fetch names from backup metadata? (y = Yes, n = No/Pure VMID [Instant], b = Back): " fetch_names >&2
        case "${fetch_names,,}" in
            y|yes) fetch_names="y"; break ;;
            n|no|"") fetch_names="n"; break ;;
            b|back) return 1 ;;
            *) echo "Invalid input. Please enter y, n, or b." >&2 ;;
        esac
    done

    declare -A ADDED_VMIDS
    display_list=()
    
    # Fast extraction bypassing API if requested
    if [[ "$fetch_names" == "n" ]]; then
        mapfile -t list < <(pvesh get "/nodes/localhost/storage/${STORAGE}/content" --content backup --output-format=json | jq -r '.[].vmid' | sort -u -n)
        for vmid in "${list[@]}"; do
            [[ -n "$vmid" ]] && display_list+=("$vmid")
        done
    else
        # Full scan for JSON parsing
        echo "Scanning backup metadata for names..." >&2
        mapfile -t backup_json < <(pvesh get "/nodes/localhost/storage/${STORAGE}/content" --content backup --output-format=json | jq -c '.[]')
        
        declare -A VM_MAP
        for row in "${backup_json[@]}"; do
            vmid=$(echo "$row" | jq -r '.vmid')
            if [[ -z "${VM_MAP[$vmid]:-}" ]]; then
                notes=$(echo "$row" | jq -r '.notes // empty')
                if [[ -n "$notes" ]]; then
                    VM_MAP["$vmid"]="$notes"
                fi
            fi
        done

        for row in "${backup_json[@]}"; do
            vmid=$(echo "$row" | jq -r '.vmid')
            if [[ -z "${ADDED_VMIDS[$vmid]:-}" ]]; then
                if [[ -n "${VM_MAP[$vmid]:-}" ]]; then
                    display_list+=("$vmid (${VM_MAP[$vmid]})")
                else
                    display_list+=("$vmid")
                fi
                ADDED_VMIDS[$vmid]=1
            fi
        done
    fi

    idx=$(select_menu "Select backup source VMID" "${display_list[@]}")
    [[ "$idx" == "q" ]] && exit 0
    [[ "$idx" == "back" || "$idx" == "invalid" ]] && return 1
    
    SOURCE_VMID=$(echo "${display_list[$idx]}" | awk '{print $1}')
    return 0
}

# Select the specific backup snapshot date/time
handle_snapshot() {
    # Fetch snapshots for the selected VMID and strip the storage prefix for cleaner display
    readarray -t list < <(pvesh get "/nodes/localhost/storage/${STORAGE}/content" --content backup --vmid "$SOURCE_VMID" --output-format=json | jq -r ".[].volid | ltrimstr(\"${STORAGE}:backup/\")")
    idx=$(select_menu "Select snapshot" "${list[@]}")
    [[ "$idx" == "q" ]] && { do_cleanup; exit 0; }
    [[ "$idx" == "back" ]] && return 1
    
    SNAPSHOT="${list[$idx]}"
    return 0
}

# Select the specific disk archive (e.g., drive-scsi0) inside the snapshot
handle_disk() {
    # List files available in the selected snapshot
    readarray -t list < <(pvesh get "/nodes/localhost/storage/${STORAGE}/file-restore/list" --volume "$SNAPSHOT" --filepath / --output-format=json | jq -r '.[].text')
    idx=$(select_menu "Select disk" "${list[@]}")
    [[ "$idx" == "q" ]] && { do_cleanup; exit 0; }
    [[ "$idx" == "back" ]] && return 1
    
    DISK="${list[$idx]}"
    return 0
}

# Select the QEMU hardware bus type to attach the disk to
handle_bus() {
    echo ""
    echo "=== Select Bus Type (for hotplug) ==="
    echo "  1) SCSI (virtioscsi0.0 - Recommended/Stable)"
    echo "  2) Test: SCSI Auto (Experimental)"
    echo "  3) VirtIO Block (virtio-bus - May be full)"
    echo "  4) Test: VirtIO Auto (Experimental)"
    echo "  0) Exit/Return"
    read -r -p ">>> Enter selection: " choice

    # Define QEMU driver and target bus based on choice
    # "auto" means we omit the "bus" parameter in QMP to let QEMU decide
    case "$choice" in
        1) DRIVER="scsi-hd"; BUS_TARGET="virtioscsi0.0" ;;
        2) DRIVER="scsi-hd"; BUS_TARGET="auto" ;;
        3) DRIVER="virtio-blk-pci"; BUS_TARGET="virtio-bus" ;;
        4) DRIVER="virtio-blk-pci"; BUS_TARGET="auto" ;;
        *) return 1 ;;
    esac
    return 0
}


# ==========================================
# --- Execution & QMP Communication ---
# ==========================================

# Send QMP commands to QEMU to add the block device and attach it to the VM
mount_device() {
    # 1. Switch to short IDs to avoid QEMU limits
    local SHORT_ID="${RANDOM}"
    local DEVICE_ID="d_${SHORT_ID}"
    local DRIVE_NAME="b_${SHORT_ID}"
    
    echo "Locking VM"
    qm set "$VMID" -lock rollback
    echo "Attaching drive ${DRIVER} from snapshot ${SNAPSHOT} to VM ${VMID}"

    # Step 1: Run blockdev-add independently using Heredoc
    echo " -> Loading block device..."
    local blk_res
    blk_res=$(socat - "UNIX:/run/qemu-server/${VMID}.qmp" <<EOF
{ "execute": "qmp_capabilities" }
{
    "execute": "blockdev-add",
    "arguments": {
        "driver": "pbs",
        "node-name": "${DRIVE_NAME}",
        "read-only": true,
        "repository": "${PBS_VARS["username"]}@${PBS_VARS["server"]}:${PBS_VARS["datastore"]}",
        "snapshot": "${SNAPSHOT}",
        "archive": "${DISK}",
        "password": "${PBS_VARS["password"]}",
        "fingerprint": "${PBS_VARS["fingerprint"]}"
    }
}
EOF
)
    
    if echo "$blk_res" | grep -q '"error"'; then
        echo "" >&2
        echo "[Error]: blockdev-add failed: $blk_res" >&2
        return 1
    fi

    # Step 2: Intelligently poll QEMU until the block node is fully initialized
    echo " -> Waiting for block node to be ready..."
    local retries=15
    local ready=0
    while [ $retries -gt 0 ]; do
        local check_res
        # 修复:去掉了非法的 arguments 参数,直接查询所有 nodes
        check_res=$(socat - "UNIX:/run/qemu-server/${VMID}.qmp" <<EOF
{ "execute": "qmp_capabilities" }
{
    "execute": "query-named-block-nodes"
}
EOF
)
        
        # Verify if the target node exists without errors
        if echo "$check_res" | grep -q "$DRIVE_NAME" && ! echo "$check_res" | grep -q '"error"'; then
            ready=1
            break
        fi
        sleep 1
        retries=$((retries - 1))
    done

    # Abort if initialization times out
    if [ $ready -eq 0 ]; then
        echo "" >&2
        echo "[Error]: Timeout waiting for block device node to initialize." >&2
        socat - "UNIX:/run/qemu-server/${VMID}.qmp" <<EOF >/dev/null 2>&1
{ "execute": "qmp_capabilities" }
{
    "execute": "blockdev-del",
    "arguments": {
        "node-name": "${DRIVE_NAME}"
    }
}
EOF
        return 1
    fi

    # Step 3: Send device_add command dynamically using Heredoc
    echo " -> Attaching hardware device..."
    local dev_res
    
    if [ "$BUS_TARGET" == "auto" ]; then
        dev_res=$(socat - "UNIX:/run/qemu-server/${VMID}.qmp" <<EOF
{ "execute": "qmp_capabilities" }
{
    "execute": "device_add",
    "arguments": {
        "driver": "${DRIVER}",
        "id": "${DEVICE_ID}",
        "drive": "${DRIVE_NAME}"
    }
}
EOF
)
    else
        dev_res=$(socat - "UNIX:/run/qemu-server/${VMID}.qmp" <<EOF
{ "execute": "qmp_capabilities" }
{
    "execute": "device_add",
    "arguments": {
        "driver": "${DRIVER}",
        "id": "${DEVICE_ID}",
        "drive": "${DRIVE_NAME}",
        "bus": "${BUS_TARGET}"
    }
}
EOF
)
    fi

    if echo "$dev_res" | grep -q '"error"'; then
        echo "" >&2
        echo "[Error]: device_add failed: $dev_res" >&2
        socat - "UNIX:/run/qemu-server/${VMID}.qmp" <<EOF >/dev/null 2>&1
{ "execute": "qmp_capabilities" }
{
    "execute": "blockdev-del",
    "arguments": {
        "node-name": "${DRIVE_NAME}"
    }
}
EOF
        return 1
    fi
    
    # Success tracking
    ATTACHED_DEVICES+=("$DEVICE_ID|$DRIVE_NAME")
    echo ""
    echo "=> Success: $DISK mounted (ID: $DEVICE_ID, Mode: $BUS_TARGET)"
    return 0
}

# Ask user whether they want to mount another disk from the same snapshot
ask_add_more() {
    local cont
    read -r -p ">>> Add more disks? (y/n): " cont
    [[ "${cont,,}" == "y" ]] # Returns 0 (true) if input is 'y' or 'Y'
}

# Wrapper function to process the mount loop and decide the next state
process_mount() {
    handle_bus || return 0       # If bus selection cancelled, return 0 to go back to DISK state
    mount_device                 # Attempt to mount[cite: 2]
    ask_add_more || return 1     # If user doesn't want more disks, return 1 to break the main loop
    return 0                     # User wants more disks, return 0 to go back to DISK state
}


# ==========================================
# --- Main Logic Loop (State Machine) ---
# ==========================================

# The script flow is controlled by a state machine.
# Each handler function returns 0 on success (proceed to next state) 
# and 1 on failure/back (revert to previous state).
STATE="VM"
while true; do
    case "$STATE" in
        VM)       handle_vm       && STATE="STORAGE"  || { do_cleanup; exit 0; } ;;
        STORAGE)  handle_storage  && STATE="VMID"     || STATE="VM" ;;
        VMID)     handle_vmid     && STATE="SNAPSHOT" || STATE="STORAGE" ;;
        SNAPSHOT) handle_snapshot && STATE="DISK"     || STATE="VMID" ;;
        DISK)     handle_disk     && STATE="BUS"      || STATE="SNAPSHOT" ;;
        BUS)      process_mount   && STATE="DISK"     || break ;;
        *) break ;; # Safety net: exit loop on unknown state
    esac
done

# Perform final cleanup before script exit
do_cleanup

你也可以先挂载到本地,再使用 qm set {vmid} --scsiN --sataN --virtioN 命令绑定

#!/bin/bash
# Enable strict error handling
set -u            # Treat unset variables as an error
set -o pipefail   # Return the exit status of the last command in the pipe that failed

# ==========================================
# --- Global Variables & State Tracking ---
# ==========================================

# PBS_VARS: Associative array storing PBS connection details extracted from PVE storage config.
declare -A PBS_VARS

# State variables to hold user selections during the interactive menus
STORAGE=""       # The Proxmox PBS storage name (e.g., "pbs-store")
SOURCE_VMID=""   # The ID of the VM that the backup belongs to
SNAPSHOT=""      # The specific backup snapshot volume ID
DISK=""          # The specific disk file inside the backup (e.g., "drive-scsi0.img.fidx")


# ==========================================
# --- Core Functions ---
# ==========================================

# --- Generic menu selector ---
# Helper function to print a numbered menu from an array and handle user input.
select_menu() {
    local prompt="$1"; shift
    local options=("$@")
    echo -e "\n=== $prompt ===" >&2
    for i in "${!options[@]}"; do printf "  %d) %s\n" "$((i+1))" "${options[$i]}" >&2; done
    echo "  b) [Back to previous]" >&2; echo "  q) [Quit script]" >&2
    read -r -p ">>> Please enter selection: " choice >&2
    case "$choice" in
        q) echo "q" ;;
        b|back) echo "back" ;;
        *) [[ "$choice" =~ ^[0-9]+$ && "$choice" -le "${#options[@]}" && "$choice" -gt 0 ]] && echo "$((choice-1))" || echo "invalid" ;;
    esac
}

# --- Retrieve PBS storage configuration ---
# Parses /etc/pve/storage.cfg and exports required environment variables for proxmox-backup-client
get_pbs_config() {
    local storage="$1"
    local cfg=$(sed -n "/^pbs: ${storage}$/,/^\S/ p" /etc/pve/storage.cfg)
    
    PBS_VARS["server"]=$(echo "$cfg" | grep "server" | awk '{print $2}')
    PBS_VARS["username"]=$(echo "$cfg" | grep "username" | awk '{print $2}')
    PBS_VARS["datastore"]=$(echo "$cfg" | grep "datastore" | awk '{print $2}')
    PBS_VARS["fingerprint"]=$(echo "$cfg" | grep "fingerprint" | awk '{print $2}')
    
    if [ -f "/etc/pve/priv/storage/${storage}.pw" ]; then
        PBS_VARS["password"]=$(cat "/etc/pve/priv/storage/${storage}.pw")
    else
        PBS_VARS["password"]=""
    fi
    
    # Export variables required by proxmox-backup-client commands
    export PBS_REPOSITORY="${PBS_VARS["username"]}@${PBS_VARS["server"]}:${PBS_VARS["datastore"]}"
    export PBS_PASSWORD="${PBS_VARS["password"]}"
    export PBS_FINGERPRINT="${PBS_VARS["fingerprint"]}"
}


# ==========================================
# --- State Handlers (Interactive Menus) ---
# ==========================================

# Select the PBS storage backend
handle_storage() {
    mapfile -t list < <(grep -E '^pbs:\s+' /etc/pve/storage.cfg | awk '{print $2}')
    idx=$(select_menu "Select PBS storage" "${list[@]}")
    [[ "$idx" == "q" ]] && exit 0
    [[ "$idx" == "back" || "$idx" == "invalid" ]] && return 1
    
    STORAGE="${list[$idx]}"
    get_pbs_config "$STORAGE"
    return 0
}

# Select the VMID whose backup we want to restore from
handle_vmid() {
    local fetch_names
    # Prompt whether to fetch names from backup metadata
    while true; do
        echo -e "\n=== Fetch VM Names ===" >&2
        read -r -p ">>> Fetch names from backup metadata? (y = Yes, n = No/Pure VMID [Instant], b = Back): " fetch_names >&2
        case "${fetch_names,,}" in
            y|yes) fetch_names="y"; break ;;
            n|no|"") fetch_names="n"; break ;;
            b|back) return 1 ;;
            *) echo "Invalid input. Please enter y, n, or b." >&2 ;;
        esac
    done

    declare -A ADDED_VMIDS
    display_list=()
    
    # 优化点:如果用户选择不获取名字(n),直接通过快杰的 awk/grep 从本地配置文件或极简命令提取 VMID,跳过慢速的完整 API 解析
    if [[ "$fetch_names" == "n" ]]; then
        # 仅提取纯 VMID 列表,极速秒开
        mapfile -t list < <(pvesh get "/nodes/localhost/storage/${STORAGE}/content" --content backup --output-format=json | jq -r '.[].vmid' | sort -u -n)
        for vmid in "${list[@]}"; do
            [[ -n "$vmid" ]] && display_list+=("$vmid")
        done
    else
        # 如果用户选择获取名字 (y),才执行完整的 JSON 解析和备注扫描
        echo "Scanning backup metadata for names..." >&2
        mapfile -t backup_json < <(pvesh get "/nodes/localhost/storage/${STORAGE}/content" --content backup --output-format=json | jq -c '.[]')
        
        declare -A VM_MAP
        for row in "${backup_json[@]}"; do
            vmid=$(echo "$row" | jq -r '.vmid')
            if [[ -z "${VM_MAP[$vmid]:-}" ]]; then
                notes=$(echo "$row" | jq -r '.notes // empty')
                if [[ -n "$notes" ]]; then
                    VM_MAP["$vmid"]="$notes"
                fi
            fi
        done

        for row in "${backup_json[@]}"; do
            vmid=$(echo "$row" | jq -r '.vmid')
            if [[ -z "${ADDED_VMIDS[$vmid]:-}" ]]; then
                if [[ -n "${VM_MAP[$vmid]:-}" ]]; then
                    display_list+=("$vmid (${VM_MAP[$vmid]})")
                else
                    display_list+=("$vmid")
                fi
                ADDED_VMIDS[$vmid]=1
            fi
        done
    fi

    idx=$(select_menu "Select backup source VMID" "${display_list[@]}")
    [[ "$idx" == "q" ]] && exit 0
    [[ "$idx" == "back" || "$idx" == "invalid" ]] && return 1
    
    SOURCE_VMID=$(echo "${display_list[$idx]}" | awk '{print $1}')
    return 0
}

# Select the specific backup snapshot date/time
handle_snapshot() {
    readarray -t list < <(pvesh get "/nodes/localhost/storage/${STORAGE}/content" --content backup --vmid "$SOURCE_VMID" --output-format=json | jq -r ".[].volid | ltrimstr(\"${STORAGE}:backup/\")")
    idx=$(select_menu "Select snapshot" "${list[@]}")
    [[ "$idx" == "q" ]] && exit 0
    [[ "$idx" == "back" || "$idx" == "invalid" ]] && return 1
    
    SNAPSHOT="${list[$idx]}"
    return 0
}

# Select the specific disk archive to map
handle_disk() {
    readarray -t list < <(pvesh get "/nodes/localhost/storage/${STORAGE}/file-restore/list" --volume "$SNAPSHOT" --filepath / --output-format=json | jq -r '.[].text')
    idx=$(select_menu "Select disk" "${list[@]}")
    [[ "$idx" == "q" ]] && exit 0
    [[ "$idx" == "back" || "$idx" == "invalid" ]] && return 1
    
    DISK="${list[$idx]}"
    return 0
}


# ==========================================
# --- Execution & Mapping Logic ---
# ==========================================

# Execute proxmox-backup-client map
map_device() {
    echo -e "\n=> Mapping $DISK to local device..."
    
    # Execute command and capture output (both stdout and stderr)
    local MAP_RESULT
    MAP_RESULT=$(proxmox-backup-client map "$SNAPSHOT" "$DISK" 2>&1)
    
    # --- Debug: Raw output ---
    echo "--- Raw Output (Debug) ---"
    echo "$MAP_RESULT"
    echo "--------------------------"
    
    if [ $? -eq 0 ]; then
        # Try to parse the /dev/loopX path from the output
        local DEVICE_PATH
        DEVICE_PATH=$(echo "$MAP_RESULT" | grep -o '/dev/loop[0-9]*' | head -n 1)
        
        if [ -n "$DEVICE_PATH" ]; then
            echo -e "\n[Success]: Device mapped at: $DEVICE_PATH"
            echo "Run 'proxmox-backup-client unmap $DEVICE_PATH' to unmap."
        else
            echo -e "\n[Warning]: Command executed successfully, but device path could not be parsed."
        fi
        return 0
    else
        echo -e "\n[Error]: Mapping failed!"
        return 1
    fi
}

# Ask user whether they want to map another disk
ask_add_more() {
    local cont
    read -r -p ">>> Map more disks? (y/n): " cont
    [[ "${cont,,}" == "y" ]]
}

# Wrapper function to process the map loop and decide the next state
process_map() {
    handle_disk || return 0      # If disk selection cancelled, return 0 to go back to SNAPSHOT state
    map_device                   # Attempt to map
    ask_add_more || return 1     # If user doesn't want more disks, return 1 to break the main loop
    return 0                     # User wants more disks, return 0 to stay in DISK state
}


# ==========================================
# --- Main Logic Loop (State Machine) ---
# ==========================================

STATE="STORAGE"
while true; do
    case "$STATE" in
        STORAGE)  handle_storage  && STATE="VMID"     || exit 0 ;;
        VMID)     handle_vmid     && STATE="SNAPSHOT" || STATE="STORAGE" ;;
        SNAPSHOT) handle_snapshot && STATE="DISK"     || STATE="VMID" ;;
        DISK)     process_map     && STATE="DISK"     || break ;;
        *) break ;;
    esac
done

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions