我使用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
我使用Q35机型进行挂载,会提示挂载失败,pcie插入失败,希望进行更新。
我使用ai进行了代码修改。
1、多pbs储存库支持
2、支持scsi插入方式
如果默认没scsi磁盘,需要使用
qm set {vmid} --scsiN --sataN --virtioN命令绑定你也可以先挂载到本地,再使用 qm set {vmid} --scsiN --sataN --virtioN 命令绑定