From 132c55e4e166afca12bc8b0c4b2a166fc39b65e2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 28 Jun 2026 01:03:22 +0000 Subject: [PATCH 1/2] Add attach-drives.sh: auto-generate qm set commands for data drives Scans /dev/sd?, skips OS drive (sda) and USB sticks (<100 GiB), checks which drives are already attached to any VM, then prints (or applies with --apply) the qm set commands for the next free scsi slot on the target VM. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01CTRZQmUSrYXFC6WkcypYPv --- scripts/attach-drives.sh | 143 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 scripts/attach-drives.sh diff --git a/scripts/attach-drives.sh b/scripts/attach-drives.sh new file mode 100644 index 0000000..1eee92b --- /dev/null +++ b/scripts/attach-drives.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# Generate qm set commands to attach data drives to a Proxmox VM. +# Run on the Proxmox host. Skips the OS drive (sda) and any drives +# already attached to any VM. +# +# Usage: +# ./attach-drives.sh # preview commands (dry run) +# ./attach-drives.sh --apply # actually run the commands +# ./attach-drives.sh --vm 100 # target a specific VM (default: 100) +# +# Example: +# ./attach-drives.sh --vm 100 --apply + +set -euo pipefail + +VM_ID=100 +APPLY=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --vm) VM_ID="$2"; shift 2 ;; + --apply) APPLY=true; shift ;; + *) echo "Usage: $0 [--vm ] [--apply]"; exit 1 ;; + esac +done + +# ── Find drives already attached to any VM ──────────────────────────────────── + +attached_ids=() +for conf in /etc/pve/qemu-server/*.conf; do + while IFS= read -r line; do + if [[ "$line" =~ /dev/disk/by-id/([^,]+) ]]; then + attached_ids+=("${BASH_REMATCH[1]}") + fi + done < "$conf" +done + +is_attached() { + local id="$1" + for a in "${attached_ids[@]:-}"; do + [[ "$a" == "$id" ]] && return 0 + done + return 1 +} + +# ── Find next free scsi slot for the target VM ──────────────────────────────── + +next_slot() { + local conf="/etc/pve/qemu-server/${VM_ID}.conf" + local slot=1 + while grep -q "^scsi${slot}:" "$conf" 2>/dev/null; do + (( slot++ )) + done + echo "$slot" +} + +# ── Inventory all block devices ─────────────────────────────────────────────── + +echo "=== Available data drives (not yet attached to any VM) ===" +echo "" +printf "%-12s %-10s %-35s %s\n" "DEVICE" "SIZE" "BY-ID PATH" "MODEL" +printf "%-12s %-10s %-35s %s\n" "------" "----" "----------" "-----" + +candidates=() + +for dev in /dev/sd?; do + [[ -b "$dev" ]] || continue + name=$(basename "$dev") + + # Skip OS drive (sda — the PERC RAID array Proxmox is installed on) + [[ "$name" == "sda" ]] && continue + + # Skip USB devices (small drives < 100G are likely USB sticks) + size_bytes=$(lsblk -dn -o SIZE -b "$dev" 2>/dev/null || echo 0) + size_human=$(lsblk -dn -o SIZE "$dev" 2>/dev/null || echo "?") + if [[ "$size_bytes" -lt 107374182400 ]]; then # < 100 GiB + printf "%-12s %-10s %-35s %s\n" "$name" "$size_human" "(skipped — looks like USB stick)" "" + continue + fi + + model=$(cat "/sys/block/${name}/device/model" 2>/dev/null | xargs || echo "?") + + # Find best by-id (prefer ata- over scsi-) + byid="" + for link in /dev/disk/by-id/ata-* /dev/disk/by-id/scsi-3*; do + [[ -L "$link" ]] || continue + [[ "$(readlink -f "$link")" == "$dev" ]] || continue + [[ "$link" =~ -part ]] && continue + byid=$(basename "$link") + [[ "$byid" == ata-* ]] && break # prefer ata- if found + done + + if [[ -z "$byid" ]]; then + printf "%-12s %-10s %-35s %s\n" "$name" "$size_human" "(no by-id found — skip)" "$model" + continue + fi + + if is_attached "$byid"; then + printf "%-12s %-10s %-35s %s\n" "$name" "$size_human" "${byid} [already attached]" "$model" + continue + fi + + printf "%-12s %-10s %-35s %s\n" "$name" "$size_human" "$byid" "$model" + candidates+=("$byid") +done + +echo "" + +if [[ ${#candidates[@]} -eq 0 ]]; then + echo "No unattached data drives found." + exit 0 +fi + +# ── Generate qm set commands ────────────────────────────────────────────────── + +echo "=== Commands to attach to VM ${VM_ID} ===" +echo "" + +slot=$(next_slot) +cmds=() +for byid in "${candidates[@]}"; do + cmd="qm set ${VM_ID} --scsi${slot} /dev/disk/by-id/${byid}" + cmds+=("$cmd") + echo " $cmd" + (( slot++ )) +done + +echo "" + +if $APPLY; then + echo "--- Applying ---" + for cmd in "${cmds[@]}"; do + echo " Running: $cmd" + eval "$cmd" + done + echo "" + echo "Done. Verify with:" + echo " grep scsi /etc/pve/qemu-server/${VM_ID}.conf" +else + echo "Dry run — no changes made. Re-run with --apply to attach drives." + echo "" + echo " ./attach-drives.sh --vm ${VM_ID} --apply" +fi From d01fe4717d9ba3c5d08ecadce84e7863dd2615fa Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 28 Jun 2026 01:05:26 +0000 Subject: [PATCH 2/2] Document NUMA socket pinning for VM 100 (socket 0) and VM 101 (socket 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add cpuset instructions so each VM is actually pinned to its physical CPU rather than relying on Proxmox to guess. VM 100 → CPU 1 (socket 0), VM 101 → CPU 2 (socket 1). Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01CTRZQmUSrYXFC6WkcypYPv --- README.md | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f19e82a..9cb6081 100644 --- a/README.md +++ b/README.md @@ -2236,14 +2236,26 @@ Click **Next**. > **Why 12 cores / 1 socket?** The E5-2660 v4 has 14 cores per socket (28 > total across both CPUs). Keeping Sockets at 1 pins the VM to a single NUMA > node — all memory accesses stay local to that socket's RAM channels, avoiding -> the ~30–40ns penalty of crossing to the other socket. Give VM 100 12 cores -> from socket 0, VM 101 gets 6 cores from socket 1. +> the ~30–40ns penalty of crossing to the other socket. VM 100 gets socket 0 +> (CPU 1), VM 101 gets socket 1 (CPU 2). > > **Enable NUMA** tells Proxmox to allocate RAM from the same socket the vCPUs > are pinned to. Without it the RAM may come from the wrong socket even if > Sockets is set to 1. The checkbox is in **Processors → Edit → Enable NUMA** > (not in the Memory dialog). > +> **Pinning VM 100 to socket 0 (CPU 1) on the Proxmox host:** +> ```bash +> # Find which CPU IDs belong to socket 0 +> lscpu -e | grep "^[0-9]" | awk '$3==0 {print $1}' | tr '\n' ',' +> # Example output: 0,1,2,...,13,28,29,...,41, +> +> # Pin VM 100 to socket 0 cores (replace the CPU list with your actual output above, no trailing comma) +> echo "cpuset: 0-13,28-41" >> /etc/pve/qemu-server/100.conf +> ``` +> After adding `cpuset`, restart the VM. Proxmox will then schedule all vCPUs +> only on those physical cores, keeping memory traffic on socket 0's RAM channels. +> > **nested-virt flag** — you will see this in Extra CPU Flags. Leave it at > Default; it is automatically enabled when CPU type is `host` on Intel, which > is what allows VirtualBox/WinApps to run inside this VM. @@ -2616,8 +2628,19 @@ Click **Create VM** in the web UI. | Type | host | | Enable NUMA | checked | -> 6 cores from socket 1 (VM 100 uses socket 0). NUMA keeps memory local to -> the cores doing the work. +> 6 cores from socket 1 (CPU 2) — VM 100 uses socket 0 (CPU 1). NUMA keeps +> memory local to the cores doing the work. +> +> **Pin VM 101 to socket 1 (CPU 2) on the Proxmox host:** +> ```bash +> # Find which CPU IDs belong to socket 1 +> lscpu -e | grep "^[0-9]" | awk '$3==1 {print $1}' | tr '\n' ',' +> # Example output: 14,15,...,27,42,43,...,55, +> +> # Pin VM 101 to socket 1 cores (replace list with your actual output, no trailing comma) +> echo "cpuset: 14-27,42-55" >> /etc/pve/qemu-server/101.conf +> ``` +> After adding `cpuset`, restart VM 101. **Tab: Memory**