Guest User

Untitled

a guest
Apr 10th, 2025
207
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 8.78 KB | None | 0 0
  1. #!/bin/bash
  2.  
  3. # Define lock file
  4. LOCK_FILE="/tmp/usb_bind.lock"
  5.  
  6. # Function to check if vmid is a number between 100 and 200
  7. is_numeric_vmid() {
  8.     local vmid=$1
  9.     [[ "$vmid" =~ ^[0-9]+$ ]] && (( vmid >= 100 && vmid < 200 ))
  10. }
  11.  
  12. # Validate format xxxx:xxxx
  13. is_valid_pidvid() {
  14.     [[ "$1" =~ ^[0-9a-fA-F]{4}:[0-9a-fA-F]{4}$ ]]
  15. }
  16.  
  17. is_valid_label() {
  18.     local label="$1"
  19.     [[ "$(lsusb | grep -i "$label")" ]]
  20. }
  21.  
  22. # Check USB device speed
  23. check_usb_speed() {
  24.     local vendor_id=$1
  25.     local product_id=$2
  26.     lsusb -d "$vendor_id:$product_id" -v 2>/dev/null | grep -q "Bus" || { echo "Device $vendor_id:$product_id not connected"; return 0; }
  27.     usb_version=$(lsusb -d "$vendor_id:$product_id" -v 2>/dev/null | grep "bcdUSB" | awk '{print $2}')
  28.     speed_supported=$(lsusb -d "$vendor_id:$product_id" -v 2>/dev/null | grep -i "wSpeedsSupported" | awk '{print $2}')
  29.     [[ "$usb_version" == "3.00" && "$speed_supported" =~ "000e" ]] && return 2
  30.     return 1
  31. }
  32.  
  33. # ----------- Show help if no arguments provided -------------
  34. if [[ $# -lt 2 ]]; then
  35.     echo "Usage: $0 <VM ID or VM Name> <PID:VID or USB Label>"
  36.     echo
  37.     echo "Arguments:"
  38.     echo "  VM ID or Name   - The VM ID or Name (numeric ID or '::next' for next available VM)"
  39.     echo "    Example: 101 or 'myVMName' or '::next'"
  40.     echo "  PID:VID         - The USB device identifier in format 'PID:VID' (e.g. 1234:5678)"
  41.     echo "    Example: 1234:5678"
  42.     echo "  USB Label       - A string (case-insensitive) representing the USB device label (e.g. 'razer', 'keyboard')"
  43.     echo "    Example: 'razer mouse' or 'gaming keyboard'"
  44.     echo
  45.     echo "Options:"
  46.     echo "  --skip          - Skip if the device is already binded to a running vm"
  47.     echo "    Example: --skip"
  48.     echo "  --alsobind PID:VID - Also bind the USB device to another VM, can specify another PID:VID or USB Label"
  49.     echo "    Example: --alsobind 1234:5678 or --alsobind 'razer mouse'"
  50.     echo "  --vmlist=vmid1,vmid2,... - Only used with '::next', a comma-separated list of VM IDs (numeric only). Only valid VM IDs that are running will be considered."
  51.     echo "    Example: --vmlist=101,102,103"
  52.     echo "  --nolock        - Skip lock checking for recursive calls (useful for recursive script calls)"
  53.     echo "    Example: --nolock"
  54.     echo
  55.     echo "Examples:"
  56.     echo "  $0 101 1234:5678             # Bind USB device 1234:5678 to VM 101"
  57.     echo "  $0 'myVMName' 'razer mouse'  # Bind the USB device with label 'razer mouse' to 'myVMName'"
  58.     echo "  $0 '::next' 1234:5678        # Bind USB device 1234:5678 to the next available VM"
  59.     echo "  $0 101 'gaming keyboard' --alsobind 1234:5678 --alsobind 6789:1254  # Bind 'gaming keyboard',1234:5678 and 6789:1254  to VM 101"
  60.     echo
  61.     exit 1
  62. fi
  63. # ----------- ARGUMENTS PARSING -------------
  64. vmid_or_name=$1
  65. device="$2"
  66. shift 2
  67.  
  68. if ! is_valid_pidvid "$device" && ! is_valid_label "$device"; then
  69.     echo "Invalid device format or label: $device"
  70.     exit 1
  71. fi
  72.  
  73. if is_valid_pidvid "$device"; then
  74.     pid="${device%%:*}"
  75.     vid="${device##*:}"
  76.     # proceed as before
  77. elif is_valid_label "$device"; then
  78.     device_info=$(lsusb | grep -i "$device" | head -n 1 | awk '{print $6}')
  79.     pid="${device_info%%:*}"
  80.     vid="${device_info##*:}"
  81. else
  82.     echo "Device not found"
  83.     exit 1
  84. fi
  85.  
  86. skip_delete=false
  87. alsobinds=()
  88. custom_vm_list=()
  89. nolock=false
  90.  
  91. while [[ $# -gt 0 ]]; do
  92.     case "$1" in
  93.         --skip) skip_delete=true ;;
  94.         --alsobind)
  95.             shift
  96.             if ! is_valid_pidvid "$1" && ! is_valid_label "$1"; then
  97.                 echo "Invalid alsobind device : $1"
  98.                 exit 1
  99.             fi
  100.             alsobinds+=("$1")
  101.             ;;
  102.         --vmlist=*)
  103.             IFS=',' read -ra raw_vm_list <<< "${1#--vmlist=}"
  104.             for vm in "${raw_vm_list[@]}"; do
  105.                 if [[ "$vm" =~ ^[0-9]+$ ]]; then
  106.                     # Get the VM status and ensure it's running
  107.                     vm_status=$(qm list | awk -v id="$vm" '$1 == id {print $3}')
  108.                     [[ "$vm_status" == "running" ]] && custom_vm_list+=("$vm")
  109.                 else
  110.                     echo "Invalid VMID: $vm. VMIDs must be numeric."
  111.                     exit 1
  112.                 fi
  113.             done
  114.             ;;
  115.         --nolock) nolock=true ;;
  116.     esac
  117.     shift
  118. done
  119.  
  120. if [[ "$nolock" == false ]]; then
  121.  
  122.     # Check if the lock file exists
  123.     if [ -f "$LOCK_FILE" ]; then
  124.         echo "Another instance of this script is already running. Exiting."
  125.         exit 1
  126.     fi
  127.    
  128.     # Create lock file to indicate that the script is running
  129.     touch "$LOCK_FILE"
  130.  
  131.     # Ensure the lock file is removed when the script exits
  132.     trap "rm -f $LOCK_FILE" EXIT
  133.  
  134.  
  135. fi
  136.  
  137.  
  138.  
  139.  
  140. # Resolve VMID
  141. if is_numeric_vmid "$vmid_or_name"; then
  142.     vmid=$vmid_or_name
  143. elif [[ "$vmid_or_name" == "0" || "${vmid_or_name,,}" == "::none" || "${vmid_or_name,,}" == "::next" ]]; then
  144.     vmid=""
  145. else
  146.     vmid=$(qm list | awk -F' ' -v name="$vmid_or_name" 'tolower($2) == tolower(name) {print $1}')
  147.     [[ -z "$vmid" ]] && exit 1
  148. fi
  149.  
  150. # Check if VM is running
  151. vm_status=$(qm list | awk -v vmid="$vmid" '$1 == vmid {print $3}')
  152. [[ -n "$vmid" && "$vm_status" != "running" ]] && exit 1
  153.  
  154. # USB Speed check
  155. check_usb_speed "$pid" "$vid"
  156. usb_status=$?
  157. [[ $usb_status -eq 0 ]] && exit 1
  158. is_usb3=""
  159. [[ $usb_status -eq 2 ]] && is_usb3=",usb3=yes"
  160.  
  161. # Analyse VMs
  162. vms=($(qm list | grep 'running' | awk '{print $1, $2}'))
  163. if [[ ${#vms[@]} -eq 0 ]]; then
  164.     echo "No running VMs."
  165.     exit 1
  166. fi
  167. vm_usb_used=false
  168. fix_problem_slot=""
  169. need_fix_problem=false
  170. free_slot=""
  171.  
  172. if [[ "${vmid_or_name,,}" == "::next" ]]; then
  173.     if [[ ${#custom_vm_list[@]} -gt 0 ]]; then
  174.         # Custom list defined
  175.         found_index=-1
  176.         for ((i=0; i<${#custom_vm_list[@]}; i++)); do
  177.             vmid_candidate="${custom_vm_list[$i]}"
  178.             vm_config=$(qm config "$vmid_candidate" 2>/dev/null)
  179.             if echo "$vm_config" | grep -q "host=$pid:$vid"; then
  180.                 found_index=$i
  181.                 break
  182.             fi
  183.         done
  184.  
  185.         if [[ $found_index -ge 0 ]]; then
  186.             next_index=$(( (found_index + 1) % ${#custom_vm_list[@]} ))
  187.         else
  188.             next_index=0
  189.         fi
  190.         vmid="${custom_vm_list[$next_index]}"
  191.     else
  192.         # Default list: all running VMs
  193.         found_index=-1
  194.         for ((i=0; i<${#vms[@]}; i+=2)); do
  195.             vmid_running="${vms[$i]}"
  196.             vm_config=$(qm config "$vmid_running")
  197.             if echo "$vm_config" | grep -q "host=$pid:$vid"; then
  198.                 found_index=$i
  199.                 break
  200.             fi
  201.         done
  202.  
  203.         if [[ $found_index -ge 0 ]]; then
  204.             next_index=$((found_index + 2))
  205.             if [[ $next_index -ge ${#vms[@]} ]]; then
  206.                 next_index=0
  207.             fi
  208.             vmid="${vms[$next_index]}"
  209.         else
  210.             vmid="${vms[0]}"
  211.         fi
  212.     fi
  213. fi
  214.  
  215. for ((i=0; i<${#vms[@]}; i+=2)); do
  216.     vmid_running="${vms[$i]}"
  217.     vmname="${vms[$i+1]}"
  218.     vm_config=$(qm config "$vmid_running")
  219.  
  220.     if echo "$vm_config" | grep -q "host=$pid:$vid"; then
  221.         slot=$(echo "$vm_config" | grep "host=$pid:$vid" | awk '{print $1}' | sed 's/:$//')
  222.         if [[ "$vmid_running" == "$vmid" ]]; then
  223.             vm_usb_used=true
  224.             fix_problem_slot="$slot"
  225.         else
  226.             [[ "$skip_delete" == false ]] && qm set "$vmid_running" -delete "$slot"
  227.             need_fix_problem=true
  228.         fi
  229.     fi
  230.  
  231.     if [[ "$vmid_running" == "$vmid" ]]; then
  232.         for z in {0..13}; do
  233.             slot="usb$z"
  234.             if ! echo "$vm_config" | grep -q "$slot"; then
  235.                 free_slot="$slot"
  236.                 break
  237.             fi
  238.         done
  239.     fi
  240. done
  241.  
  242. if [[ "$need_fix_problem" == true && "$vm_usb_used" == true && "$skip_delete" == false ]]; then
  243.     qm set "$vmid" -delete "$fix_problem_slot"
  244.     vm_usb_used=false
  245. fi
  246.  
  247. if [[ -n "$free_slot" && "$vm_usb_used" == false && -n "$vmid" ]]; then
  248.     if [[ "$need_fix_problem" == true && "$skip_delete" == true ]]; then
  249.         echo "skip because already attributed"
  250.         exit 1
  251.     else
  252.         qm set "$vmid" --$free_slot host=$pid:$vid$is_usb3
  253.     fi
  254. fi
  255.  
  256. also_bind_target="$vmid"
  257. if [[ -z "$also_bind_target" ]]; then
  258.     also_bind_target="0"
  259. fi
  260.  
  261. # ----------- RECURSIVE CALLS FOR --alsobind ------------
  262. for extra in "${alsobinds[@]}"; do
  263.     if is_valid_pidvid "$extra"; then
  264.         extra_pid="${extra%%:*}"
  265.         extra_vid="${extra##*:}"
  266.     elif is_valid_label "$extra"; then
  267.         extra_info=$(lsusb | grep -i "$extra" | awk '{print $6}')
  268.         extra_pid="${extra_info%%:*}"
  269.         extra_vid="${extra_info##*:}"
  270.     fi
  271.     sleep 3
  272.     echo "Also binding: $also_bind_target $extra -> $extra_pid:$extra_vid"
  273.     "$0" "$also_bind_target" "$extra" --nolock
  274. done
  275.  
Advertisement
Add Comment
Please, Sign In to add comment