geoffwilliams@home:~$

Systemd overriding libvirt-guests shutdown behavior

libvirt-guets is a simple script that runs at startup and shutdown to either suspend, shutdown or resume all VMs registered with the host.

Usually this blanket approach works fine, but I have one VM that must be shutdown fully instead of suspended since it runs Home Assistant and needs to release the a USB Zigbee adapter otherwise the automatic lights won’t work again until I do this manually.

Modify the main script?

Changing /usr/lib/libvirt/libvirt-guests.sh to special case my one VM would definitely work but this is a brittle fix and needs to be preserved over updates.

Build another service?

The first suggestion out of chatGPT was to just create an additional service file and have it execute before the libvirt-guest.service kicks in - that way the VM would already be shutdown when it gets looked at.

I got pretty far with this but gave up. It’s not a great approach as it introduces complexity and timing issues since action is only needed on shutdown.

Systemd drop-in + script

Using the systemd “drop-in” mechanism, its possible to override service actions without changing the files placed on the system from upstream. I ended up doing this:

Drop-in: /etc/systemd/system/libvirt-guests.service.d/homeassistant.conf

[Service]

# reset because https://github.com/systemd/systemd/issues/4148
ExecStop=
ExecStop=/usr/local/bin/shutdown_homeassistant.sh

# upstream from /lib/systemd/system/libvirt-guests.service
ExecStop=/usr/lib/libvirt/libvirt-guests.sh stop

Script: /usr/local/bin/shutdown_homeassistant.sh

#!/bin/bash
MAX_WAIT=300
WAITED=0
COLS=40
VM_NAME=homeassistant

function vm_stopped() {
  if virsh domstate $VM_NAME | grep -q "shut off" ; then
    return 0
  else 
    return 1
  fi
}

if vm_stopped ; then 
  echo "vm already stopped: $VM_NAME"
else
  virsh shutdown $VM_NAME
  echo "waiting for VM shutdown: $VM_NAME"
  while [ $WAITED -lt $MAX_WAIT ] ; do
    if vm_stopped ; then
      echo 
      echo "shutdown ok: $VM_NAME"
      break
    fi
    WAITED=$((WAITED + 1))
  
    echo -n "."
    if [ $((WAITED % COLS)) == 0 ] ; then
      echo
    fi
    sleep 1
  done
  echo
fi

Notes

  • virsh shutdown ... returns immediately, You need to also poll VM status to watch for completed shutdown before letting libvirt-guests run or else I observed a race condition
  • Follow the output on journalctl -u libvirt-guests
  • ExitStop= are executed sequentially within the context of the drop-in
  • You can have multiple drop-in files, and order can be controlled by prefixing files with numbers if needed

Closing

With the above in place, I can finally reboot the host with confidence and without breaking all Zigbee devices until I login to fix.

Post comment

Markdown is allowed, HTML is not. All comments are moderated.