> ## Content Index
> Fetch the complete content index at: https://dees.co/llms.txt
> Use this file to discover other available public pages before exploring further.

# Hi-jack an Unraid server power button to initiate S3 sleep
- URL: https://dees.co/hi-jack-an-unraid-server-power-button-to-initiate-s3-sleep/
- Published: 2026-09-07T21:22:40.000Z
- Updated: 2026-09-10T13:00:23.000Z
- Description: Repurpose the Unraid power button to gracefully stop GPU passthrough VMs and put the server into S3 sleep, with clean GPU re-initialization after wake.
- Author: Garrett Dees
- Tags: Unraid

I run an Unraid server with a CachyOS virtual machine that has my GPU, SSD, and USB controller passed through. I wanted a simple way to put the whole system to sleep just by pressing the power button. The catch is that I can't just sleep the host directly. If I do, the VM comes back with a black screen on wake, because the host can't properly send the PCI reset signal to the passed-through GPU. So instead, I had to override the power button's behavior to gracefully shut down any running VMs first, then put the system to sleep. This lets the GPU be cleanly reinitialized after resume.

## Prerequisites

- Community Applications
- User Scripts (Enhanced)
- Dynamix S3 Sleep
- [QEMU Guest Agent](https://dees.co/qemu-guest-agent/)

## Initialization

1. Log into the **Unraid GUI**.
2. Navigate to **Settings > User Scripts**.

### Create the "Update ACPI Handler" script

1. Click **Add new script**.
2. Type **update\_acpi\_handler**, click **OK**.
3. Click the **Cog** next to **update\_acpi\_handler**, click **Edit Name**.
4. Type **Update ACPI Handler**, click the **check mark**.
5. Click the **Cog** next to **Update ACPI Handler**, click **Edit Description**.
6. Type **Repurpose the power button to stop running VMs and put the system to sleep.** **The power button can be used to wake the system and start VMs set to autostart.**, click the **check mark**.
7. Click the **Cog** next to **Update ACPI Handler**, click **Update Script**, type:

```bash
#!/bin/bash

sed -i -e \
    '/power) \/sbin\/init 0/c\
      power)\
        case "$3" in\
          PBTN) bash /boot/config/plugins/user.scripts/scripts/acpi_handler_override/script ;;\
          LNXPWRBN:00) logger -t "acpi_handler" "Ignoring Linux Power Button virtual event" ;;\
          *) logger -t "acpi_handler" "ACPI group $1 / action $2 / event $3 is not defined" ;;\
        esac' \
/etc/acpi/acpi_handler.sh
/etc/rc.d/rc.acpid restart

mkdir -p /etc/elogind/logind.conf.d

    # sed -i -e '/#HandlePowerKey=poweroff/aHandlePowerKey=ignore' /etc/elogind/logind.conf
    echo -e '[Login]\nHandlePowerKey=ignore' > /etc/elogind/logind.conf.d/90-powerkey.conf

/etc/rc.d/rc.elogind restart

```

1. Click **Save Changes**.
2. Click the drop-down next to **Update ACPI Handler**, select **At First Array Start Only**.
3. Click **Run Script**, then **Done**.

### Create the "ACPI Handler Override" script

1. Click **Add new script**.
2. Type **acpi\_handler\_override**, click **OK**.
3. Click the **Cog** next to **acpi\_handler\_override**, click **Edit Name**.
4. Type **ACPI Handler Override**, click the **check mark**.
5. Click the **Cog** next to **ACPI Handler Override**, click **Edit Description**.
6. Type **Invoked by /etc/acpi/acpi\_handler.sh**, click the **check mark**.
7. Click the **Cog** next to **ACPI Handler Override**, click **Update Script**, type:

```bash
#!/bin/bash

script="s3_sleep"
plugin="dynamix.s3.sleep"

config="/boot/config/plugins/$plugin/$plugin.cfg"

if [ ! -e $config ]; then
    config="/usr/local/emhttp/plugins/$plugin/default.cfg"
fi

execute="/usr/local/emhttp/plugins/$plugin/scripts/$script"
options=$(grep '^options=' $config | cut -d '"' -f2 | tr -d '\n')

$execute -S $options  # use WoL, Pre-Run, Post-Run, and Debug from config

```

1. Click **Save Changes**.

### Create the "Stop Running Virtual Machines" script

1. Click **Add new script**.
2. Type **stop\_running\_vms**, click **OK**.
3. Click the **Cog** next to **stop\_running\_vms**, click **Edit Name**.
4. Type **Stop Running Virtual Machines**, click the **check mark**.
5. Click the **Cog** next to **Stop Running Virtual Machines**, click **Edit Description**.
6. Type **Invoked by /boot/config/plugins/user.scripts/scripts/acpi\_handler\_override**, click the **check mark**.
7. Click the **Cog** next to **Stop Running Virtual Machines**, click **Update Script**, type:

```bash
#!/bin/bash

# Configuration
TIMEOUT=120  # Seconds to wait for graceful shutdown before forcing kill

logger -t "stop_running_vms" "=== Starting VM Shutdown Sequence ==="
logger -t "stop_running_vms" "Timestamp: $(date)"

# Get list of running VMs
RUNNING_VMS=$(virsh list --name --state-running)

if [ -z "$RUNNING_VMS" ]; then
    logger -t "stop_running_vms" "No VMs are currently running."
    exit 0
fi

logger -t "stop_running_vms" "Found running VMs:"
logger -t "stop_running_vms" "$RUNNING_VMS"
logger -t "stop_running_vms" "========================================"

# Loop through each running VM
while IFS= read -r vm_name; do
    if [ -z "$vm_name" ]; then continue; fi

    logger -t "stop_running_vms" "Attempting graceful shutdown for: $vm_name"

    # Send ACPI shutdown signal (graceful)
    virsh shutdown "$vm_name"

    # Wait for the VM to stop
    START_TIME=$(date +%s)
    while true; do
        CURRENT_STATE=$(virsh domstate "$vm_name" 2>/dev/null)

        # Check if VM is dead/shutdown
        if [[ "$CURRENT_STATE" == "shut off" ]] || [[ "$CURRENT_STATE" == "crashed" ]]; then
            logger -t "stop_running_vms" "  -> $vm_name stopped successfully."
            break
        fi

        # Check timeout
        CURRENT_TIME=$(date +%s)
        ELAPSED=$((CURRENT_TIME - START_TIME))

        if [ $ELAPSED -ge $TIMEOUT ]; then
            logger -t "stop_running_vms" "  -> Timeout reached ($TIMEOUT seconds). Force killing $vm_name..."
            virsh destroy "$vm_name"
            logger -t "stop_running_vms" "  -> $vm_name force killed."
            break
        fi

        sleep 5
    done
done <<< "$RUNNING_VMS"

logger -t "stop_running_vms" "========================================"
logger -t "stop_running_vms" "Shutdown sequence completed."

```

1. Click **Save Changes**.

### Create the "**Stop Running GPU Passthrough VMs** " script

1. Click **Add new script**.
2. Type **stop\_gpu\_vms**, click **OK**.
3. Click the **Cog** next to **stop\_gpu\_vms**, click **Edit Name**.
4. Type **Stop Running GPU Passthrough VMs**, click the **check mark**.
5. Click the **Cog** next to **Stop Running GPU Passthrough VMs**, click **Edit Description**.
6. Type **Alternative to /boot/config/plugins/user.scripts/scripts/stop\_running\_vms**, click the **check mark**.
7. Click the **Cog** next to **Stop Running GPU Passthrough VMs**, click **Update Script**, type:

```bash
#!/bin/bash

# Configuration
SHUTDOWN_TIMEOUT=120 

echo "Starting GPU VM Shutdown Process..."
echo "-----------------------------------"

# Get list of all active (running) VMs
ACTIVE_VMS=$(virsh list --name --state-running)

if [ -z "$ACTIVE_VMS" ]; then
    echo "No VMs are currently running."
    exit 0
fi

for VM_NAME in $ACTIVE_VMS; do
    [ -z "$VM_NAME" ] && continue

    echo "Checking VM: $VM_NAME"

    # Fetch the XML definition of the VM
    VM_XML=$(virsh dumpxml "$VM_NAME")

    # Check for GPU Passthrough
    # Look for hostdev with vfio driver AND function='0x0' (main GPU) or function='0x1'/'0x6' (GPU audio)
    # Using grep -E for extended regex to match the actual XML structure
    if echo "$VM_XML" | grep -q "<hostdev mode='subsystem' type='pci'.*managed='yes'" && \
       echo "$VM_XML" | grep -q "<driver name='vfio'/>" && \
       echo "$VM_XML" | grep -E "function='0x0'" > /dev/null; then  # (0x0|0x1|0x6)

        echo "  -> GPU Passthrough detected."

        echo "  -> Initiating graceful shutdown for $VM_NAME..."
        virsh shutdown "$VM_NAME"

        # Wait for the VM to stop gracefully
        START_TIME=$(date +%s)
        while true; do
            CURRENT_STATUS=$(virsh domstate "$VM_NAME" 2>/dev/null)

            if [[ "$CURRENT_STATUS" != *"running"* ]]; then
                echo "  -> $VM_NAME has stopped."
                break
            fi

            CURRENT_TIME=$(date +%s)
            ELAPSED=$((CURRENT_TIME - START_TIME))

            if [ $ELAPSED -ge $SHUTDOWN_TIMEOUT ]; then
                echo "  -> Timeout reached ($SHUTDOWN_TIMEOUT s). Force stopping $VM_NAME..."
                virsh destroy "$VM_NAME"
                break
            fi

            sleep 5
        done
    else
        echo "  -> No GPU passthrough detected. Skipping."
    fi
done

echo "-----------------------------------"
echo "Process complete."

```

1. Click **Save**.

### Create the "Start Autostart Virtual Machines" script

1. Click **Add new script**.
2. Type **start\_autostart\_vms**, click **OK**.
3. Click the **Cog** next to **start\_autostart\_vms**, click **Edit Name**.
4. Type **Start Autostart Virtual Machines**, click the **check mark**.
5. Click the **Cog** next to S**tart Autostart Virtual Machines**, click **Edit Description**.
6. Type **Invoked by /boot/config/plugins/user.scripts/scripts/acpi\_handler\_override**, click the **check mark**.
7. Click the **Cog** next to S**tart Running Virtual Machines**, click **Update Script**, type:

```bash
#!/bin/bash

# Configuration
STARTUP_DELAY=10
AUTOSTART_DIR="/etc/libvirt/qemu/autostart"

logger -t "start_autostart_vms" "=== Starting Auto-Start VM Sequence (Symlink Scan) ==="
logger -t "start_autostart_vms" "Timestamp: $(date)"

# Check if the autostart directory exists
if [ ! -d "$AUTOSTART_DIR" ]; then
    logger -t "start_autostart_vms" "Error: Autostart directory not found at $AUTOSTART_DIR"
    logger -t "start_autostart_vms" "Ensure 'Start on boot' is enabled for at least one VM in the Unraid GUI."
    exit 1
fi

logger -t "start_autostart_vms" "Scanning $AUTOSTART_DIR for autostart symlinks..."
logger -t "start_autostart_vms" "========================================"

# Check if there are any files in the directory
if [ -z "$(ls -A "$AUTOSTART_DIR")" ]; then
    logger -t "start_autostart_vms" "No VMs are configured for Auto Start (directory is empty)."
    exit 0
fi

# Loop through every file in the autostart directory
for link in "$AUTOSTART_DIR"/*; do
    # Skip if no files found (safety check)
    [ -e "$link" ] || continue

    # Get the filename (e.g., "MyVM.xml")
    filename=$(basename "$link")

    # Strip the .xml extension to get the VM name (e.g., "MyVM")
    # This handles cases where the name might have dots, but ends in .xml
    vm_name="${filename%.xml}"

    # Check current state
    STATE=$(virsh domstate "$vm_name" 2>/dev/null)

    # If already running, skip
    if [[ "$STATE" == "running" ]]; then
        logger -t "start_autostart_vms" "Skipping '$vm_name': Already running."
        continue
    fi

    # If the VM is not running, start it
    logger -t "start_autostart_vms" "Found Autostart VM (not running): $vm_name"
    logger -t "start_autostart_vms" "  -> Starting: $vm_name"

    if virsh start "$vm_name" > /dev/null 2>&1; then
        logger -t "start_autostart_vms" "  -> Command sent. Waiting for state change..."
        sleep 5

        NEW_STATE=$(virsh domstate "$vm_name" 2>/dev/null)
        if [[ "$NEW_STATE" == "running" ]]; then
            logger -t "start_autostart_vms" "  -> $vm_name is now running."
        else
            logger -t "start_autostart_vms" "  -> Warning: $vm_name failed to start (Current state: $NEW_STATE)"
        fi
    else
        logger -t "start_autostart_vms" "  -> Error: Failed to send start command for $vm_name."
    fi

    sleep $STARTUP_DELAY
done

logger -t "start_autostart_vms" "========================================"
logger -t "start_autostart_vms" "Auto-start sequence completed."

```

1. Click **Save Changes**.

## Configuration

### **Start Autostart Virtual Machines**

1. Log into the **Unraid GUI**.
2. Navigate to **Settings > Sleep Settings**.
3. Set **Set WOL options before sleep** to **g** to allow **Wake-on-LAN (WoL)**.
4. Set **Custom commands before sleep** to **bash /boot/config/plugins/user.scripts/scripts/stop\_running\_vms/script**.
5. Set **Custom commands after wake-up** to **bash /boot/config/plugins/user.scripts/scripts/start\_autostart\_vms/script**.
6. Set **Enable DEBUG mode** to **Syslog**.
7. Click **Apply**.
8. Click **Done**.