#!/bin/bash # fix-proxmox-smbios.sh — Apply per-VM SMBIOS (Type 1) to a Proxmox VM # via `qm set -smbios1 ...,base64=1`, so the guest produces a valid # MukenVault auth_dna (Tier 1). # # Proxmox stores SMBIOS Type 1 fields as a comma-separated list. With # `base64=1` set, every string value (manufacturer / product / serial / # sku / family / version) is base64-encoded. We do the encoding here so # partners don't have to. # # What this does: # 1. Verify vmid exists, read current smbios1 to /var/backups/... # 2. Stop the VM (acpi shutdown → 60s → fallback stop) # 3. qm set -smbios1 'uuid=...,...,base64=1' # 4. qm start # # Usage (Proxmox host, as root): # sudo curl -fsSL https://install.mukenvault.com/fix-proxmox-smbios.sh | \ # sudo bash -s -- # # Caveats: # - VM downtime ~10-30s (graceful shutdown). # - Only Type 1 (system) is set via qm; baseBoard / chassis are not # reachable through `qm set`. Tier 2 (board_serial / chassis_serial) # will stay empty on Proxmox VMs — that's normal and matches the # entropy budget in DESIGN_AUTH_DNA_separation.md §6.5 (Tier 1 alone # provides the per-instance identification). set -euo pipefail IFS=$'\n\t' red() { printf '\033[1;31m%s\033[0m' "$1"; } green() { printf '\033[1;32m%s\033[0m' "$1"; } yellow() { printf '\033[1;33m%s\033[0m' "$1"; } log() { printf '\033[1;36m[fix-proxmox]\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[fix-proxmox WARN]\033[0m %s\n' "$*" >&2; } fail() { printf '\033[1;31m[fix-proxmox FAIL]\033[0m %s\n' "$*" >&2; exit 1; } if [[ "$(id -u)" -ne 0 ]]; then fail "must run as root on the Proxmox host (re-run with sudo)" fi VMID="${1:-}" if [[ -z "$VMID" ]]; then cat >&2 <<'EOF' [fix-proxmox FAIL] missing argument: Usage: sudo curl -fsSL https://install.mukenvault.com/fix-proxmox-smbios.sh | \ sudo bash -s -- List VMs to find the vmid: qm list EOF exit 2 fi # `qm` accepts only positive integers — sanity-check before we shell it. if ! [[ "$VMID" =~ ^[0-9]+$ ]]; then fail "vmid must be a positive integer (got: '$VMID')" fi command -v qm >/dev/null 2>&1 || fail "qm not found — is this a Proxmox VE host?" command -v uuidgen >/dev/null 2>&1 || fail "uuidgen not found (apt install uuid-runtime)" command -v base64 >/dev/null 2>&1 || fail "base64 not found (apt install coreutils)" if ! qm config "$VMID" >/dev/null 2>&1; then fail "VM not found: vmid=$VMID. Run 'qm list' to see available VMs." fi BACKUP_DIR="/var/backups/mukenvault-proxmox-smbios" mkdir -p "$BACKUP_DIR" TS=$(date +%Y%m%d-%H%M%S) BACKUP_FILE="${BACKUP_DIR}/${VMID}-${TS}.conf" # ── 1. Backup current smbios1 line (+ enough context for rollback) ── log "backing up current smbios1 line → $BACKUP_FILE" { printf '# vmid=%s captured=%s\n' "$VMID" "$TS" qm config "$VMID" | grep -E '^(smbios1|name):' || true } > "$BACKUP_FILE" chmod 600 "$BACKUP_FILE" # ── 2. Stop VM if running. `qm status` prints "status: running" or # "status: stopped". ───────────────────────────────────────── STATUS=$(qm status "$VMID" | awk '{print $NF}') if [[ "$STATUS" == "running" ]]; then log "VM state: running → acpi shutdown (up to 60s)" qm shutdown "$VMID" --timeout 60 >/dev/null 2>&1 || true for _ in $(seq 1 30); do STATUS=$(qm status "$VMID" 2>/dev/null | awk '{print $NF}') [[ "$STATUS" == "stopped" ]] && break sleep 2 done if [[ "$STATUS" != "stopped" ]]; then warn "graceful shutdown timed out — forcing stop" qm stop "$VMID" >/dev/null 2>&1 || true fi fi # ── 3. Build new smbios1 line ────────────────────────────────────── NEW_UUID=$(uuidgen) RAND=$(openssl rand -hex 3 2>/dev/null || head -c 3 /dev/urandom | xxd -p) SERIAL="MUKENVAULT-${TS}-${RAND}" # `base64 -w0` keeps the line on a single field; qm set rejects newlines # inside the value list. coreutils base64 supports -w0; the BSD variant # (not present on Proxmox) does not, but Proxmox ships GNU coreutils. b64() { printf '%s' "$1" | base64 -w0; } SMBIOS1="uuid=${NEW_UUID}" SMBIOS1+=",manufacturer=$(b64 'MukenVaultPartner')" SMBIOS1+=",product=$(b64 'PartnerVM')" SMBIOS1+=",version=$(b64 '1.0')" SMBIOS1+=",serial=$(b64 "$SERIAL")" SMBIOS1+=",sku=$(b64 'mukenvault')" SMBIOS1+=",family=$(b64 'MukenVault')" SMBIOS1+=",base64=1" log "applying new smbios1 (qm set $VMID -smbios1 ...)" qm set "$VMID" -smbios1 "$SMBIOS1" >/dev/null # ── 4. Start ─────────────────────────────────────────────────────── log "starting VM" qm start "$VMID" >/dev/null for _ in $(seq 1 30); do STATUS=$(qm status "$VMID" 2>/dev/null | awk '{print $NF}') [[ "$STATUS" == "running" ]] && break sleep 1 done cat <' qm start $VMID # If no previous smbios1 line existed, remove the field entirely: qm shutdown $VMID qm set $VMID -delete smbios1 qm start $VMID EOF