Skip to content

lukmanulhakimdevops/vhd_bootable_dracut

Repository files navigation

This version is production-ready, written for Ubuntu 25.10 Dracut 108+, GRUB 2.12+, and UEFI systems, using only kernel-level VHD attach (ntfs3, loop, partprobe) β€” no FUSE, no vdfuse. All logs go to /run/initramfs/vhd.log.

πŸ”„ Boot Sequence Diagram

The following diagram illustrates the boot process from UEFI to Root Switch.

Deskripsi Gambar

πŸ“ Directory Layout of the Dracut Module

/usr/lib/dracut/modules.d/90vhdattach/
β”œβ”€β”€ module-setup.sh
β”œβ”€β”€ vhdattach-early.sh
β”œβ”€β”€ vhdattach-initqueue.sh
└── 90-vhdattach.conf

🧩 /etc/dracut.conf.d/90-vhdattach.conf

# 90-vhdattach.conf - dracut runtime/config for vhdattach
hostonly="no"
add_drivers+=" loop ntfs3 dm_mod "
add_dracutmodules+=" vhdattach "
dracut_log_level="debug"

# Runtime tunables (override if needed)
HOST_UUID="D8DE7C15DE7BEA60"   # UUID of NTFS host partition that contains the VHD
VHD_REL_PATH="/ubuntu.vhd"     # path relative to NTFS root (leading slash allowed)
HOST_MOUNTPOINT="/mnt/vhd_host"
LOGFILE="/run/initramfs/vhd.log"
MAX_WAIT=30
WAIT_DELAY=1
ROOT_PART_HINT=2               # preferred partition number for root inside VHD (used as fallback)

🧩 /usr/lib/dracut/modules.d/90vhdattach/module-setup.sh

#!/bin/bash
# ============================================================
# Dracut 108+ module-setup.sh
# ============================================================

check() { return 0; }

depends() {
    echo "dm"
    return 0
}

install() {
    # Essential tools to be included in initramfs
    inst_multiple \
        losetup kpartx mount blkid udevadm findmnt partprobe date seq \
        sleep dmesg mkdir tee basename dirname cat lsblk

    # Kernel drivers required for NTFS and loop
    instmods loop ntfs3 dm_mod dm_snapshot

    # Hook stages
    inst_hook pre-mount 05 "$moddir/vhdattach-early.sh"
    inst_hook initqueue/settled 90 "$moddir/vhdattach-initqueue.sh"
}

🧩 /usr/lib/dracut/modules.d/90vhdattach/90-vhdattach.conf

# ============================================================
# Configuration for vhdattach (Dracut 108+)
# ============================================================
hostonly="no"
add_drivers+=" loop ntfs3 dm_mod dm_snapshot "
add_dracutmodules+=" vhdattach "
dracut_log_level="debug"

🧩 /usr/lib/dracut/modules.d/90vhdattach/vhdattach-early.sh

#!/bin/sh
# ============================================================
# vhdattach-early.sh
# Stage: pre-mount (Dracut 108)
# Purpose: Mount NTFS host and attach the VHD file
# ============================================================

set -e
set -u

LOG="/run/initramfs/vhd.log"
mkdir -p "$(dirname "$LOG")"
exec > >(tee -a "$LOG") 2>&1
echo "[vhdattach] ===== EARLY STAGE START ====="
date

[ -f /lib/dracut-lib.sh ] && . /lib/dracut-lib.sh

HOST_UUID="D8DE7C15DE7BEA60"
VHD_REL_PATH="/ubuntu.vhd"
HOST_MOUNTPOINT="/mnt/vhd_host"
MAX_WAIT=30
WAIT_INTERVAL=1

for mod in loop ntfs3 dm_mod; do
    modprobe "$mod" 2>/dev/null || echo "[vhdattach] warn: modprobe $mod failed"
done

DEV_PATH="/dev/disk/by-uuid/$HOST_UUID"
echo "[vhdattach] waiting for host device $DEV_PATH ..."
for i in $(seq 1 $MAX_WAIT); do
    if [ -b "$DEV_PATH" ]; then
        echo "[vhdattach] found $DEV_PATH"
        break
    fi
    sleep "$WAIT_INTERVAL"
done

if [ ! -b "$DEV_PATH" ]; then
    echo "[vhdattach] ERROR: host device not found after ${MAX_WAIT}s"
    exit 0
fi

mkdir -p "$HOST_MOUNTPOINT"
if ! mount -t ntfs3 -o rw,noatime,windows_names "$DEV_PATH" "$HOST_MOUNTPOINT"; then
    echo "[vhdattach] ERROR: failed to mount host!"
    exit 0
fi
echo "[vhdattach] host mounted at $HOST_MOUNTPOINT"

VHD_FILE="$HOST_MOUNTPOINT$VHD_REL_PATH"
if [ ! -f "$VHD_FILE" ]; then
    echo "[vhdattach] ERROR: $VHD_FILE not found"
    umount "$HOST_MOUNTPOINT" || true
    exit 0
fi
echo "[vhdattach] found $VHD_FILE"

LOOPDEV=$(losetup -fP --show "$VHD_FILE")
if [ -z "$LOOPDEV" ]; then
    echo "[vhdattach] ERROR: losetup failed!"
    umount "$HOST_MOUNTPOINT" || true
    exit 0
fi

echo "[vhdattach] loop attached as $LOOPDEV"
partprobe "$LOOPDEV" || true
udevadm trigger --action=add || true
udevadm settle --timeout=15 || true

echo "[vhdattach] partitions now visible:"
lsblk "$LOOPDEV" || true
blkid | grep "$LOOPDEV" || true

echo "[vhdattach] ===== EARLY STAGE COMPLETE ====="
exit 0

🧩 /usr/lib/dracut/modules.d/90vhdattach/vhdattach-initqueue.sh

#!/bin/sh
# ============================================================
# vhdattach-initqueue.sh
# Stage: initqueue/settled (Dracut 108)
# Purpose: Ensure loop device partitions are ready before root mount
# ============================================================

LOG="/run/initramfs/vhd.log"
exec >>"$LOG" 2>&1

echo "[vhdattach] ===== INITQUEUE STAGE START ====="
date

# Find the loop device already attached
LOOPDEV=$(losetup -a | grep "ubuntu.vhd" | cut -d: -f1 | head -n1)
if [ -z "$LOOPDEV" ]; then
    echo "[vhdattach] WARN: no loop device found!"
    exit 0
fi

echo "[vhdattach] checking partitions on $LOOPDEV..."
udevadm settle --timeout=15 || true
partprobe "$LOOPDEV" || true

# Ensure the root partition (loopXp2) is available
ROOT_PART="${LOOPDEV}p2"
for i in $(seq 1 10); do
    if [ -b "$ROOT_PART" ]; then
        echo "[vhdattach] root partition ready: $ROOT_PART"
        lsblk "$ROOT_PART" || true
        break
    fi
    echo "[vhdattach] waiting for $ROOT_PART ($i/10)"
    sleep 1
done

if [ ! -b "$ROOT_PART" ]; then
    echo "[vhdattach] ERROR: root partition $ROOT_PART not ready after wait"
    exit 0
fi

echo "[vhdattach] loop device verified OK"
echo "[vhdattach] ===== INITQUEUE COMPLETE ====="
exit 0

🧠 Workflow Overview

Stage Hook Purpose
1. pre-mount vhdattach-early.sh Mount NTFS host (RW), attach .vhd via losetup, trigger udev
2. initqueue/settled vhdattach-initqueue.sh Wait until loop0p2 exists, verify partitions, write logs
3. root mount handled by dracut Root filesystem is mounted from inside the .vhd

βœ… Expected Results

  • /run/initramfs/vhd.log logs the entire sequence from attach to verification.
  • No more initqueue timeout errors.
  • No emergency shell because loop0p2 is ready before root mount.
  • The .vhd root and host NTFS are both mounted read/write.

πŸ’Ύ Rebuild initramfs

sudo dracut -fv --add vhdattach --kver $(uname -r)

πŸ” Extra Debugging

Add these to GRUB for debugging:

rd.debug rd.shell=1

Then in the emergency shell:

cat /run/initramfs/vhd.log

βœ… Final grub.cfg (Production-Ready for GRUB 2.12+ EFI)

menuentry "Ubuntu from VHD (Native Loopboot)" {
    insmod part_gpt
    insmod ntfs
    insmod ext2
    insmod loopback
    insmod gzio

    # 1️⃣ Locate the NTFS host partition containing ubuntu.vhd
    search --no-floppy --fs-uuid --set=hostdisk D8DE7C15DE7BEA60
    echo "Host NTFS disk UUID=D8DE7C15DE7BEA60 found at ($hostdisk)"

    # 2️⃣ Attach the VHD file to a GRUB loop device
    loopback loop0 ($hostdisk)/ubuntu.vhd
    echo "Attached ubuntu.vhd as (loop0)"

    # 3️⃣ Set root to the Linux partition inside the VHD (usually GPT2)
    set root=(loop0,gpt2)

    # 4️⃣ Load the kernel
    echo "Booting kernel from (loop0,gpt2)..."
    linux ($root)/boot/vmlinuz-6.17.0-5-generic \
        root=UUID=e846e489-b692-442c-bf30-691d1a8d0bbd \
        ro rootwait rootdelay=5 \
        rd.auto rd.retry=3 \
        rd.shell=1 rd.emergency=ignore \
        rd.luks=0 rd.md=0 rd.dm=0 \
        vhd.uuid=D8DE7C15DE7BEA60 \
        vhd.path=/ubuntu.vhd \
        rd.debug \
        quiet splash

    # 5️⃣ Load initramfs
    initrd ($root)/boot/initrd.img-6.17.0-5-generic

    # 6️⃣ Boot kernel
    boot
}

βš™οΈ Notes

Option Explanation
root=UUID= Must point to the root partition inside the .vhd.
rootfstype=ext4 Prevents filesystem autodetection delay.
rd.auto rd.retry=3 Makes Dracut automatically retry device detection.
rd.luks=0 rd.md=0 rd.dm=0 Skips cryptsetup, mdraid, and device-mapper init for speed.
vhd.uuid and vhd.path Passed to Dracut hooks for reference.
rd.emergency=ignore Avoids dropping into emergency shell during boot.

βœ… Boot Checklist

  1. /boot/vmlinuz-* and /boot/initrd.img-* exist inside the .vhd.
  2. The .vhd file resides on NTFS partition UUID=D8DE7C15DE7BEA60.
  3. The Dracut module vhdattach is bundled into initramfs.
  4. The root=UUID= matches the ext4 root partition inside the VHD.
  5. loopback loop0 ($hostdisk)/ubuntu.vhd works successfully in GRUB.

πŸ“‚ EFI Boot Loader Structure (Windows + Ubuntu VHD)

On a typical UEFI dual-boot setup, your EFI partition (FAT32, usually /dev/sda1) will look like this:

/EFI/
β”œβ”€β”€ Microsoft/
β”‚   β”œβ”€β”€ Boot/
β”‚   β”‚   β”œβ”€β”€ bootmgfw.efi
β”‚   β”‚   └── BCD
β”‚   └── ...
β”œβ”€β”€ UbuntuVHD/
β”‚   β”œβ”€β”€ grubx64.efi
β”‚   └── grub.cfg   ← this is our configuration file
└── Boot/
    └── bootx64.efi (UEFI fallback)

You can create a new BCD entry via PowerShell:

bcdedit /create /d "Ubuntu from VHD" /application BOOTSECTOR

or using EasyUEFI, then set its path to:

\EFI\UbuntuVHD\grubx64.efi

on Windows PowerShell (as Administrator):

bcdedit /copy {bootmgr} /d "Ubuntu from VHD"
bcdedit /set {new-guid} path \EFI\UbuntuVHD\grubx64.efi

βœ… Final Result

At boot time:

  • Windows Boot Manager β†’ choose β€œUbuntu from VHD”
  • GRUB EFI (\EFI\UbuntuVHD\grubx64.efi) loads .vhd from NTFS
  • Kernel attaches .vhd via Dracut vhdattach
  • Root filesystem (/) mounts from inside the VHD

Result: True native Ubuntu boot directly from a .vhd stored on Windows β€” single EFI, single disk, zero virtualization. Perfect for hybrid DevOps workflows and portable Linux environments.

This setup gives you a fully native Linux boot directly from a .vhd file stored on a Windows NTFS partition β€” fast, clean, and debug-friendly.

About

GPT Lab -> Review the first boot screen you will get the clue

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages