user@argobox:~/journal/2025-11-18-the-812gb-hiding-in-plain-sight
$ cat entry.md

The 812GB Hiding in Plain Sight

○ NOT REVIEWED

The 812GB Hiding in Plain Sight

Date: November 18, 2025 Issue: VM won’t boot after hypervisor migration Root Cause: Hypervisor compatibility (KVM → Proxmox) Actual Solution: 812GB of free space I didn’t know I had


The Setup

I’ve been building Argo OS — a custom Gentoo distribution — inside a KVM virtual machine on my main workstation. The VM was working perfectly, but running it on my daily driver meant competing for resources.

Simple plan: migrate the VM to my Proxmox server. Free up my workstation. Done.


The Migration Attempt

Step 1: Copy the QCOW2 disk image to Proxmox.

rsync -avP gentoo-btrfs.qcow2 [email protected]:/tank/vms/
qm disk import 200 /tank/vms/gentoo-btrfs.qcow2 tank

Step 2: Boot the VM.

PXE-E53: No boot filename received
Booting from Hard Disk...

Nothing. Just a blinking cursor.


Problem 1: Disk Not Attached

Classic rookie mistake. qm disk import imports the disk but doesn’t attach it.

qm config 200
# boot: order=net0
# unused0: tank:vm-200-disk-0   ← There it is, unused

qm set 200 --scsi0 tank:vm-200-disk-0
qm set 200 --boot order=scsi0

Okay, now boot.


Problem 2: Missing UEFI

Still hung at “Booting from Hard Disk…”

qm config 200 | grep -i "bios\|efi"
# Nothing

The VM was UEFI-based, but Proxmox defaults to BIOS. UEFI boot entries live in firmware NVRAM — they don’t transfer between hypervisors.

qm set 200 --efidisk0 tank:vm-200-efi,efitype=4m,pre-enrolled-keys=1
qm set 200 --bios ovmf

Boot again.


Problem 3: The Graphics Nightmare

GRUB loaded! Progress. Selected the kernel.

Then the screen became modern art. Garbled colors. Corrupted text. Random pixels.

I tried everything:

  • nomodeset kernel parameter
  • vga=off
  • Different display types
  • Serial console fallback

Nothing worked.


The Root Cause

This wasn’t a configuration problem. This was a hardware abstraction layer mismatch.

  • KVM environment: Specific virtual hardware (virtio devices, ACPI tables, CPU flags)
  • Proxmox QEMU: Different virtual hardware emulation

The Gentoo kernel was compiled for KVM’s virtual environment. It had drivers and configurations specific to that hardware abstraction. Proxmox presented different virtual hardware, and the kernel couldn’t initialize it.

I could spend days debugging graphics drivers. Or…


The Pivot

“Is this how distro developers would do it?”

No. Real distro developers don’t migrate VMs. They:

  • Build reproducibly from source
  • Use binary package repositories
  • Version control configurations
  • Deploy fresh to each target

I was treating VM images like pets. I should be treating them like cattle.


The Discovery

While planning a fresh install on my workstation, I ran fdisk -l:

Device           Start        End   Sectors   Size  Type
nvme0n1p1         2048    1050623   1048576   512M  EFI System
nvme0n1p2      1050624    1083391     32768    16M  Linux filesystem
nvme0n1p3      1083392  735160319 734076928   350G  Microsoft basic data
nvme0n1p4    735160320 1258143743 522983424 249.4G  Microsoft basic data
nvme0n1p5   1258143744 1259458559   1314816   642M  Windows recovery
nvme0n1p6   1259458560 1259491327     32768    16M  Microsoft reserved
nvme0n1p8   2963310592 3907029134 943718543   450G  Linux filesystem

# Wait. p6 ends at 1,259,491,327. p8 starts at 2,963,310,592.
# That's a gap of... 1.7 billion sectors?

812 gigabytes. Unallocated. On my main NVMe drive.

I’d been so focused on the 450GB openSUSE partition and the Windows partitions that I never noticed a massive gap between them.


The New Plan

Forget the VM. Fresh install on bare metal.

Proposed layout in the 812GB gap:
├── p7: 200G    Gentoo root (/)
├── p9: 8G      Gentoo swap
└── Remaining: ~604GB (future expansion)

Why this is better:

  • ✅ Native hardware (NVIDIA GPU, actual CPU features)
  • ✅ No hypervisor overhead
  • ✅ No compatibility issues
  • ✅ Binary packages from testbed server
  • ✅ Configurations from Git

The testbed machine compiles everything. My workstation just consumes pre-built packages.


The Architecture Shift

Before:

┌─────────────────────────────────────┐
│ Workstation                         │
│  ├── openSUSE (daily driver)        │
│  └── KVM VM (Gentoo Golden Image)   │
│      └── Compiles @world            │
│      └── Eats CPU/RAM              │
└─────────────────────────────────────┘

After:

┌─────────────────────────────────────┐
│ Testbed (Tau-Ceti-Lab)              │
│  └── Bare metal Gentoo              │
│  └── Compiles @world continuously   │
│  └── Serves binpkgs via HTTP        │
└──────────────┬──────────────────────┘

               │ HTTP: binary packages
               │ Git: configurations

┌─────────────────────────────────────┐
│ Workstation (Galileo-Outpost)       │
│  ├── openSUSE (legacy, shrinking)   │
│  └── Gentoo (fresh install)         │
│      └── Pulls configs from Git     │
│      └── Pulls binpkgs from testbed │
│      └── Never compiles locally     │
└─────────────────────────────────────┘

Lessons Learned

  1. Check for unallocated space before assuming you need to shrink partitions. 812GB was hiding in plain sight.

  2. VM migration between hypervisors is non-trivial. UEFI variables, hardware abstraction, drivers — they’re all tied to the source environment.

  3. Sometimes the right answer is to start fresh. I could have spent days debugging graphics drivers. Instead, I designed a better architecture.

  4. Distro development has best practices for a reason. HTTP repositories, Git for configs, reproducible builds — these exist because VMs don’t scale.

  5. Bare metal can be simpler than VMs. No hypervisor compatibility, faster compilation, real hardware support.


What I Built

# On testbed - set up package repository
emerge -av nginx
# Configure to serve /var/cache/binpkgs

# On workstation - configure to consume
echo 'PORTAGE_BINHOST="http://10.42.0.194"' >> /etc/portage/make.conf
echo 'FEATURES="${FEATURES} getbinpkg"' >> /etc/portage/make.conf

# Install using binary packages
emerge -av --getbinpkg @world

No compilation on the workstation. Everything pre-built on the testbed. Install time: minutes instead of hours.


The Irony

I spent 4 hours trying to fix a VM migration.

The solution was already on my disk, in the form of 812GB of free space I forgot existed.

Sometimes the bug isn’t in the code. It’s in the assumption that you understood the problem.


The discovery of 812GB of hidden free space led to a complete architecture redesign. What started as a troubleshooting session became a lesson in professional distro development practices.