Monday, October 19, 2026

Thin XFS LVs provisioning and Libvirt hooks for State Isolated KVM Server on openSUSE Tumbleweed

UPDATE as of 10/21/26  

Please, see  update at the bottom of original post, which allows to avoid in recreating of "snap_working_vms" at user's side 

See also Thin XFS LVs provisioning and Libvirt hooks for State Isolated KVM Server on Debian forky  

END UPDATE 

Following below is review/verification approach to Thin XFS LVs provisioning and Libvirt hooks proposed by Google AI. In case of openSUSE Tumbleweed YaST Installer requires minimal manual CLI intervention due to GUI interface enables thin pools and thin LVs . In case Debian 14 network installation you have an option to associate vg_debian group with physical volume. All command stack is supposed to be executed manually upon instance reboot. 

   Yast Installer and Debian Installer behaves the same way in VENV and on bare metal performing relatively complicated BTRFS/XFS partitioning schema pretty stable. In case of Debian forky OverlayFS BTRFS/XFS setup is required ( see https://lxer.com/module/newswire/view/368197/index.html ). On openSUSE Tumbleweed you are protected by Enterprise grade BTRFS implementation (sudo snapper rollback safe-ID) 

Final disk layout



==============================================
Actually, YaST installer in GUI mode executes
==============================================
# Initialize the partition for LVM ingestion
sudo pvcreate --metadatasize 500M /dev/nvme0n1p3
 
# Instantiate your dedicated vg_suse Volume Group container
sudo vgcreate vg_suse /dev/nvme0n1p3
 
# Carve out a 180G (for instance) Thin Pool reservoir tracking array
sudo lvcreate -L 180G --thinpool tp_suse_kvm vg_suse
 
# Slice out the final Thin Volume utilizing the full pool size
sudo lvcreate -V 180G --name vol_suse_thin --thinpool vg_suse/tp_suse_kvm
You may also decouple @qemu subvolume mounted @/etc/libvirt/qemu in YaST installer partitioning phase via embedded GUI interface.
===================================================
Vol_suse_thin shouldn't be formatted and mounted
Upon reboot to Tumbleweed instance to run manually
===================================================
# Format the thin volume enabling fast VFS-level copy-on-write pointer sharing
sudo mkfs.xfs -K -m reflink=1,crc=1 /dev/vg_suse/vol_suse_thin
 
# Purge the old physical device configuration lines from your system map
sudo sed -i '\/opt/d' /etc/fstab
 
# Append your optimized thin block map permanently into the layout tables
echo '/dev/vg_suse/vol_suse_thin   /opt   xfs defaults,noatime,logbufs=8,logbsize=256k 0 0' | sudo tee -a /etc/fstab
 
# Rebuild systemd configuration targets and mount the new thin pool volume
sudo systemctl daemon-reload
sudo mount -a
 
===================================================
Debian Network installer in GUI mode executes only
===================================================
# Instantiate your dedicated vg_debian Volume Group container
sudo vgcreate vg_suse /dev/nvme0n1p3
# Initialize the partition for LVM ingestion
sudo pvcreate --metadatasize 500M /dev/nvme0n1p3
# Instantiate your dedicated vg_deabin Volume Group container
sudo vgcreate vg_debian /dev/nvme0n1p3
 
The rest CLI stack of commands remains your responsibility
upon reboot
=================================================
When KVM Hypervisor is installed on Tumbleweed ( Debain forky) You are supposed to create Libvirt hook
=================================================
# cat /etc/libvirt/hooks/qemu
#!/bin/bash
GUEST_NAME="$1"
ACTION="$2"
PHASE="$3"
STORAGE_DIR="/opt/libvirt/images"
BACKUP_DIR="${STORAGE_DIR}/.snapshots_${GUEST_NAME}"
 
[ ! -d "$STORAGE_DIR" ] && exit 0
 
if [ "$ACTION" = "prepare" ] || [ "$ACTION" = "stopped" ]; then
   (
       /usr/bin/sleep 2
       /usr/bin/mkdir -p "$BACKUP_DIR"
 
       for disk_path in "${STORAGE_DIR}"/*"${GUEST_NAME}"*.qcow2; do
           [ ! -f "$disk_path" ] && continue
           disk_file=$(/usr/bin/basename "$disk_path")
           timestamp=$(/usr/bin/date +%Y%m%d_%H%M%S)
 
           # Atomic VFS copy-on-write pointer creation
           /usr/bin/cp --reflink=always "$disk_path" "${BACKUP_DIR}/${disk_file}.snap_${timesta
mp}"
           /usr/bin/chown qemu:qemu "${BACKUP_DIR}/${disk_file}.snap_${timestamp}" 2>/dev/null
       done
 
       # Prune older operational states, retaining the last 5 configurations
       /usr/bin/ls -tp "${BACKUP_DIR}"/* | /usr/bin/grep -v '/$' | /usr/bin/tail -n +6 | /usr/bin/xargs -I {} /usr/bin/rm -- "{}" 2>/dev/null
   ) & disown
fi
exit 0
===================================
Expected disklayout on both systems
===================================
boris@fedoraServer091026:~$ ssh boris@192.168.0.30
(boris@192.168.0.30) Password:
Last login: Mon Oct 19 18:04:57 MSK 2026 on tty2
Have a lot of fun...
boris@localhost:~> lsblk -f
NAME                            FSTYPE      FSVER    LABEL UUID                                   FSAVAIL FSUSE% MOUNTPOINTS
sr0
vda
├─vda1                          vfat        FAT32          C7FA-229F                               406.3M    20% /boot/efi
├─vda2                          ext4        1.0            ce25d513-c727-41fc-b3d7-d65454842064      1.7G     4% /boot
├─vda3                          LVM2_member LVM2 001       QtA4KW-Basj-r1MV-rGny-gDMX-pFzK-WbtgvK
│ ├─vg_suse-tp_suse_kvm_tmeta
│ │ └─vg_suse-tp_suse_kvm-tpool
│ │   ├─vg_suse-tp_suse_kvm
│ │   └─vg_suse-vol_suse_thin   xfs                        c43d95f3-5e09-46d8-b14a-1790879a27fd     65.4G     9% /opt
│ └─vg_suse-tp_suse_kvm_tdata
│   └─vg_suse-tp_suse_kvm-tpool
│     ├─vg_suse-tp_suse_kvm
│     └─vg_suse-vol_suse_thin   xfs                        c43d95f3-5e09-46d8-b14a-1790879a27fd     65.4G     9% /opt
└─vda4                          btrfs                      59c74873-c215-4123-9212-d7242087e5e9     39.7G    18% /usr/local
                                                                                                                 /srv
                                                                                                                 /var
                                                                                                                 /home
                                                                                                                 /root
                                                                                                                 /etc/libvirt/qemu
                                                                                                                 /.snapshots
                                                                                                                 /
=======================================================
Recovery from emulated qcow2 corruption in libvirt-pool. Default libvirt-pool location is /opt/libvirt/images on thin XFS LV. That is due to limitation of YaST installer.  Debian 14 network installer allows you to mount /var/lib/libvirt/images on thin XFS LV in post installation phase prior of KVM Hypervisor setup.
=======================================================
localhost:~ # cat ArchServerRecovery.sh
#!/bin/bash
set -Eeuo pipefail
 
# Configuration Matrix
VM_NAME="ArchServer"
DISK_PATH="/opt/libvirt/images/ArchServer.qcow2"
VG_NAME="vg_suse"
SNAP_NAME="snap_working_vms"
THIN_VOL="vol_suse_thin"
 
echo "=== PHASE 1: PREPARING CLEAN ENVIRONMENT ==="
echo "Stopping ${VM_NAME}..."
sudo virsh shutdown "$VM_NAME"
 
echo "Waiting for VM to fully power off and release file locks..."
while [ "$(sudo virsh domstate "$VM_NAME" | tr -d '\r\n ')" != "shutoff" ]; do
    sleep 1
done
 
echo "Removing old snapshot if present..."
sudo lvremove -f "${VG_NAME}/${SNAP_NAME}" 2>/dev/null || true
 
echo "Creating pristine baseline snapshot..."
sudo lvcreate -s --name "$SNAP_NAME" "${VG_NAME}/${THIN_VOL}"
sudo lvs "$VG_NAME"
 
echo -e "\n=== PHASE 2: SIMULATING DATA DESTRUCTION DRILL ==="
echo "Injecting 500MB of pure entropy into the qcow2 block architecture..."
sudo dd if=/dev/urandom of="$DISK_PATH" bs=1M count=500 conv=notrunc
sync
 
echo -e "\n=== PHASE 3: OFFLINE BLOCK STORAGE OVERHAUL ==="
echo "Stopping virtualization stack mechanisms..."
sudo systemctl stop virtqemud.service virtqemud.socket
sudo pkill -9 qemu 2>/dev/null || true
 
echo "Unmounting XFS target disk layer..."
sudo umount /opt
 
echo "Executing LVM thin snapshot rollback merge..."
sudo lvconvert --merge "${VG_NAME}/${SNAP_NAME}"
 
echo "Remounting pristine block matrix onto /opt..."
sudo mount /opt
 
echo -e "\n=== PHASE 4: DISK INTEGRITY VERIFICATION ==="
echo "Running validation checks on ${DISK_PATH}..."
 
# Run qemu-img check and capture internal report
if sudo qemu-img check "$DISK_PATH"; then
    echo "вњ… Success: LVM snapshot merge validated. Image is 100% clean."
else
    echo "CRITICAL: Integrity check failed! The disk image remains corrupted." >&2
    exit 1
fi
 
echo -e "\n=== PHASE 5: RUNTIME RESTORATION ==="
echo "Purging leftover runtime lock infrastructure files..."
sudo rm -f /opt/libvirt/images/*.lock 2>/dev/null || true
sudo rm -f /var/run/libvirt/qemu/*.xml 2>/dev/null || true
 
echo "Restarting containment profiling layers..."
sudo systemctl restart systemd-machined
sudo systemctl start virtqemud.socket virtqemud.service
 
echo "Powering on recovered virtual guest domain..."
sudo virsh start "$VM_NAME"
echo "=== DISASTER RECOVERY DRILL COMPLETE ==="

UPDATE as of 10/21/16 

localhost:~ # cat /etc/libvirt/hooks/qemu
#!/bin/bash
GUEST_NAME="$1"
ACTION="$2"
PHASE="$3"
STORAGE_DIR="/opt/libvirt/images"
BACKUP_DIR="${STORAGE_DIR}/.snapshots_${GUEST_NAME}"

[ ! -d "$STORAGE_DIR" ] && exit 0

# --- EXISTING LOGIC: Operational reflink snapshots on state changes ---
if [ "$ACTION" = "prepare" ] || [ "$ACTION" = "stopped" ]; then
    # 🔥 FIX FOR VIRT-MANAGER: Trigger snapshot service for GUI transitions too
    /usr/bin/systemctl start lvm-thin-snapshot.service &

    (
        /usr/bin/sleep 2
        /usr/bin/mkdir -p "$BACKUP_DIR"

        for disk_path in "${STORAGE_DIR}"/*"${GUEST_NAME}"*.qcow2; do
            [ ! -f "$disk_path" ] && continue
            disk_file=$(/usr/bin/basename "$disk_path")
            timestamp=$(/usr/bin/date +%Y%m%d_%H%M%S)

            # Atomic VFS copy-on-write pointer creation
            /usr/bin/cp --reflink=always "$disk_path" "${BACKUP_DIR}/${disk_file}.snap_${timestamp}"
            /usr/bin/chown qemu:qemu "${BACKUP_DIR}/${disk_file}.snap_${timestamp}" 2>/dev/null
        done

        # Prune older operational states, retaining the last 5 configurations
        /usr/bin/ls -tp "${BACKUP_DIR}"/* | /usr/bin/grep -v '/$' | /usr/bin/tail -n +6 | /usr/bin/xargs -I {} /usr/bin/rm -- "{}" 2>/dev/null
    ) & disown

# --- NEW LAYER: Handle LVM Thin Snapshot baseline shifts on VM creation/deletion ---
elif [ "$ACTION" = "define" ] || [ "$ACTION" = "undefine" ]; then
    # Fire off the systemd service asynchronously so libvirt loop is never locked
    /usr/bin/systemctl start lvm-thin-snapshot.service &
fi

exit 0
localhost:~ # cat /etc/systemd/system/lvm-thin-snapshot.service
[Unit]
Description=Rotate Pristine LVM Thin Pool Snapshot on Libvirt Changes
After=local-fs.target

[Service]
Type=oneshot
User=root
Environment=VG_NAME=vg_suse
Environment=SNAP_NAME=snap_working_vms
Environment=THIN_VOL=vol_suse_thin
Environment=IMG_DIR=/opt/libvirt/images

ExecStart=/bin/bash -c '\
    echo "Current allocated qcow2 image count: $(ls -1 ${IMG_DIR}/*.qcow2 2>/dev/null | wc -l)"; \
    echo "Removing old snapshot if present..."; \
    /usr/sbin/lvremove -f "${VG_NAME}/${SNAP_NAME}" 2>/dev/null || true; \
    echo "Creating pristine baseline snapshot..."; \
    /usr/sbin/lvcreate -s --name "${SNAP_NAME}" "${VG_NAME}/${THIN_VOL}"'

[Install]
WantedBy=multi-user.target

 

No comments:

Post a Comment