Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hyperv-to-kvm

Cold-migrate Hyper-V VMs onto a Linux KVM/libvirt host — one small script per VM — preserving each guest's MAC so it keeps its DHCP-reserved IP and nothing downstream has to change.

I used this to retire two Windows Hyper-V servers onto a single Ubuntu box (~20 single-service VMs). It is deliberately small and readable: no agent, no control plane, just ssh, qemu-img, guestfish and virt-install. Read it before you run it.

The one trap worth knowing about

Under Hyper-V the guest's NIC driver (hv_netvsc) presents the interface as eth0. Move the disk to KVM and the virtio-net NIC comes up as enp1s0 instead. If the guest's netplan matches on interface name, it now matches nothing — the VM boots with no network and you're on the console wondering why.

The fix, applied offline to the qcow2 before first boot: match the NIC by MAC address, force set-name: eth0, and disable cloud-init's networking so it can't clobber the override on boot. That's the heart of migrate-vm.sh.

network:
  version: 2
  ethernets:
    primary:
      match:
        macaddress: "00:15:5d:..."   # the MAC, carried over from Hyper-V
      set-name: eth0
      dhcp4: true

How it works (per VM)

  1. Ask the Hyper-V host over SSH+PowerShell where the VHDX lives, then Stop-VM.
  2. qemu-img convert -f vhdx -O qcow2 into the libvirt pool on the KVM host.
  3. guestfish rewrites the guest's netplan offline (the trap above) and disables cloud-init networking.
  4. virt-install --import onto a bridge with the original MAC, disk and NIC on virtio, and virsh autostart.
  5. Ping the expected IP until it answers.

Preserving the MAC means a MAC-keyed DHCP reservation hands the VM its old address, so DNS, firewall rules and every service that talked to it keep working untouched.

Requirements

KVM host: libvirt, qemu-utils, libguestfs-tools (guestfish), virtinst, a bridge (e.g. br0), and the Hyper-V VHDX stores mounted somewhere readable (CIFS is fine). Hyper-V hosts: OpenSSH server + PowerShell, your public key in the administrator's authorized_keys. Runner (your workstation): SSH access to both, and one key (SSH_KEY below).

Usage

Edit the config block at the top of migrate-vm.sh (hosts, user, bridge, SSH key, SOURCES map), then:

# one VM
KVM_HOST=kvmhost.lan KVM_USER=ubuntu \
  ./migrate-vm.sh MyServer hosta 00:15:5d:01:02:03 4096 4 10.0.0.42

# a whole fleet from an inventory file
cp examples/vms.tsv vms.tsv    # fill in from lib/hyperv-inventory.ps1 output
./examples/run-migration.sh vms.tsv

Build the inventory (MAC, MemGB, ProcessorCount, VHDX paths) by running lib/hyperv-inventory.ps1 on each Hyper-V host.

If sudo on the KVM host needs a password, export ZPW=...; otherwise passwordless (NOPASSWD) sudo is assumed.

Afterwards: backups

vm-backup-to-nas.sh is the companion for once the VMs live on KVM — a nightly live backup (snapshot + blockcommit, no downtime) that writes zstd-compressed qcow2 images to a CIFS share and prunes by age. Point a systemd timer at it.

Honest trade-offs

  • Cold migration. Each VM is down while its disk converts (minutes for a small disk, longer for large ones). This is not live migration — it's the simple, reliable path for a one-shot move you do once.
  • Ubuntu/netplan guests. The network fix targets netplan. Other distros need the equivalent (udev .link, NetworkManager, /etc/network/interfaces).
  • Crash-consistent backups. Install qemu-guest-agent in the guests for filesystem-consistent (fsfreeze) snapshots — recommended for databases.
  • Gen-2 / UEFI guests map to the q35 machine type used here; adjust --os-variant (osinfo-query os) to match your guests.

License

MIT — see LICENSE.

About

Cold-migrate Hyper-V VMs to Linux KVM/libvirt, preserving each guest's MAC and DHCP-reserved IP. Handles the eth0->enp1s0 rename trap.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages