When you see <driver> that can be replaced with any of these daemon, qemu, lxc, libxl, bhyve and network this is because these are the hooks libvirt exposes. You can find details on them here: https://libvirt.org/hooks.html.
The arguments that libvirt passes to hooks.
- object – specific VM (domain) name
- operation – lifecycle action (e.g.
start,stop,prepare,release) - sub_operation – finer-grained phase (e.g.
begin,end) - shutoff_reason – reason code when a VM shuts off
Note: I don't know how I made such a simple thing this complicated.
hook_router is a hook router for libvirt driver hooks. It replaces the standard hook entry point and dispatches execution to executables stored in a directory tree (<driver>.d/...) that mirrors libvirt’s positional hook arguments. All matching hooks are executed in order, stdin is passed through unchanged, and execution stops immediately if any hook exits with a non-zero status.
The routing model is simple: libvirt’s hook arguments are mapped directly onto a directory hierarchy. Each argument corresponds to one path component, and any executable found along matching paths is run.
Basic principles:
- Directory levels map to:
object → operation → sub_operation → shutoff_reason *acts as a wildcard matching any value at that level, important is that you can't have a trailing*.- executables within a directory are executed in alphabetical order
- Build once:
cargo build --release - Copy helper:
cargo build --release && sudo cp -f target/release/hook_router /etc/libvirt/hooks/<driver>
Goal:
We want to run a script when a specific VM shuts down, at the end of the shutdown process. This means the driver will be qemu. Our VM name is templeOS. documentation at https://libvirt.org/hooks.html says that operation: release, sub-operation: end and we don't care about the shutoff reason so we will ignore it.
Libvirt runs the binary like this:
/etc/libvirt/hooks/qemu templeOS release end shutdownWe will install the hook router at the qemu hook location
cargo build --release && sudo cp -f target/release/hook_router /etc/libvirt/hooks/qemuCreate the following directory. The structure mirrors libvirt hook arguments:
templeOS→ VM name (script runs only for this VM)release→ operation to matchend→ sub-operation to match
/etc/libvirt/hooks/qemu.d/templeOS/release/end/
Place your hook script inside it:
/etc/libvirt/hooks/qemu.d/templeOS/release/end/rebind-driver.sh
Make sure it’s executable:
chmod +x /etc/libvirt/hooks/qemu.d/templeOS/release/end/rebind-driver.sh#!/bin/sh
echo "Rebinding PCI device after VM shutdown" >&2When /etc/libvirt/hooks/<driver> is run by libvirt it scans /etc/libvirt/hooks/<driver>.d/... for hooks to execute. There are a few paths you can put your hook in. Here I have laid them out and what happens if you put your hook there. It will search those directories until it executed every script, the order of which directories it searches is given below. It will execute hooks inside a directory in alphabetical order. If any of the executables fail with a non zero exit code hook_router will propagate that error and the stderr. stdout is discarded. The usage of * means you shouldn't name your VM *. The one you probably want to use is number 8 like in the above example.
<driver>.d/*/executables here are executed for every hook call, for all VMs and all operations.<driver>.d/object/executables here are executed for every hook call for a specific VM.<driver>.d/*/operation/executables here are executed for any VM when the same operation is called.<driver>.d/object/operation/executables here are executed when a specific VM is called with a specific operation.<driver>.d/*/*/sub_operation/executables here are executed for any VM and any operation, but only for the same sub-operation.<driver>.d/object/*/sub_operation/executables here are executed for a specific VM, any operation, for the same sub-operation.<driver>.d/*/operation/sub_operation/executables here are executed for any VM, but only for a specific operation and sub-operation.<driver>.d/object/operation/sub_operation/executables here are executed for a specific VM with a specific operation and sub-operation.<driver>.d/*/*/*/shutoff_reason/executables here are executed for any VM, any operation, any sub-operation, for the same shutoff reason.<driver>.d/object/*/*/shutoff_reason/executables here are executed for a specific VM, any operation, any sub-operation, for the same shutoff reason.<driver>.d/*/operation/*/shutoff_reason/executables here are executed for any VM, for a specific operation, any sub-operation, for the same shutoff reason.<driver>.d/object/operation/*/shutoff_reason/executables here are executed for a specific VM, specific operation, any sub-operation, for the same shutoff reason.<driver>.d/*/*/sub_operation/shutoff_reason/executables here are executed for any VM, any operation, specific sub-operation, for the same shutoff reason.<driver>.d/object/*/sub_operation/shutoff_reason/executables here are executed for a specific VM, any operation, specific sub-operation, for the same shutoff reason.<driver>.d/*/operation/sub_operation/shutoff_reason/executables here are executed for any VM, specific operation and sub-operation, for the same shutoff reason.<driver>.d/object/operation/sub_operation/shutoff_reason/executables here are executed only when the VM, operation, sub-operation, and shutoff reason all match.
You first install it to qemu hook location with for example: cargo build --release && sudo cp -f target/release/hook_router /etc/libvirt/hooks/qemu
We will put our hook in qemu.d/templeOS/release/end/ and name it for example qemu.d/templeOS/release/end/rebind-driver.sh. We shutdown our templeOS VM and Libvirt then calls /etc/libvirt/hooks/qemu templeOS release end shutdown with the xml configuration in stdin. hook_router will look will look in these directories below and execute any executables in those directories. It finds our script in qemu.d/templeOS/release/end/ and then executes it.
qemu.d/*/qemu.d/templeOS/qemu.d/*/release/qemu.d/templeOS/release/qemu.d/*/*/end/qemu.d/templeOS/*/end/qemu.d/*/release/end/qemu.d/templeOS/release/end/qemu.d/*/*/*/shutdown/qemu.d/templeOS/*/*/shutdown/qemu.d/*/release/*/shutdown/qemu.d/templeOS/release/*/shutdown/qemu.d/*/*/end/shutdown/qemu.d/templeOS/*/end/shutdown/qemu.d/*/release/end/shutdown/qemu.d/templeOS/release/end/shutdown/
- Use any language, just make sure the file is executable and starts with a shebang (
#!/bin/bash,#!/usr/bin/env python3, etc.) or just a plain binary. - Hooks inherit stdin, so you can read the XML configuration of the virtual machine.
- Do you have errors you want to propagate? Exit with a non zero code and put your errors into stderr. Everything in stdout is discarded.