Event System

Validated against Unraid 7.2.3 - Event names and execution order verified against /usr/local/sbin/emhttp_event.

Unraid plugins can respond to system events by placing executable scripts in their event/ directory. This allows plugins to perform actions when the array starts, Docker launches, or the system is shutting down.

How Events Work

The emhttp process calls /usr/local/sbin/emhttp_event when system events occur. This script then looks for and executes event handlers in each plugin’s event/ directory.

⚠️ Caution: The emhttp process blocks until all event scripts complete. Long-running scripts will delay system operations. Use background processes for lengthy tasks.

Event Execution Order

For each event, scripts are executed in this order:

  1. any_event handlers - Scripts in event/any_event/ or event/any_event receive ALL events
  2. Specific event handlers - Scripts matching the event name

Plugins are processed alphabetically by plugin folder name (ASCII sort order).

If your plugin depends on another plugin’s event handler running first, you can prefix your plugin name with a character that sorts after the dependency. For example, z-myplugin would run after compose.manager. However, relying on execution order is fragile—design your event handlers to be independent when possible.

Available Events

Startup Events

Event Description When to Use
driver_loaded Early initialization, INI files are valid Load kernel modules
starting Array start begins Pre-start preparation
array_started Array devices (/dev/md*) are valid Access array devices
disks_mounted Disks and user shares are mounted Access /mnt/user/
svcs_restarted Network services started/restarted Configure network services
docker_started Docker service is running Start Docker containers
libvirt_started VM service is running Start VMs
started Array start complete Most common - general startup

Shutdown Events

Event Description When to Use
stopping Array stop begins Pre-shutdown tasks
stopping_libvirt About to stop VMs Gracefully stop VMs
stopping_docker About to stop Docker Stop Docker containers
stopping_svcs About to stop network services Cleanup network resources
unmounting_disks Disks about to unmount Final disk access
stopping_array Disks unmounted, array stopping Last chance before stopped
stopped Array fully stopped Post-stop cleanup
flowchart LR
    subgraph Startup["Array Start Sequence"]
        direction TB
        S1[driver_loaded] --> S2[starting]
        S2 --> S3[array_started]
        S3 --> S4[disks_mounted]
        S4 --> S5[svcs_restarted]
        S5 --> S6[docker_started]
        S6 --> S7[libvirt_started]
        S7 --> S8[started]
    end
    
    subgraph Shutdown["Array Stop Sequence"]
        direction TB
        P1[stopping] --> P2[stopping_libvirt]
        P2 --> P3[stopping_docker]
        P3 --> P4[stopping_svcs]
        P4 --> P5[unmounting_disks]
        P5 --> P6[stopping_array]
        P6 --> P7[stopped]
    end
    
    Startup -.->|🔄 Reboot/Stop| Shutdown
    
    style S8 fill:#4caf50,color:#fff
    style P7 fill:#f44336,color:#fff

📷 Screenshot needed: Syslog output showing event/plugin messages

Event syslog

Other Events

Event Description When to Use
poll_attributes SMART data has been polled Monitor disk health

Creating Event Handlers

See the DocTest validation plugin event handlers for working examples of all 16 documented events.

Directory Structure

Place event handler scripts in your plugin’s event/ directory. You can use either a single executable file named after the event, or a directory containing multiple numbered scripts that run in alphabetical order.

/usr/local/emhttp/plugins/myplugin/
└── event/
    ├── started           # Single script for 'started' event
    ├── stopping_docker   # Single script for 'stopping_docker' event
    └── any_event/        # Directory for scripts that handle ALL events
        └── logger.sh

You can use either:

  • A single executable file named after the event
  • A directory containing multiple scripts

Basic Event Script

Create an executable script at event/started:

#!/bin/bash
# event/started - Runs when the array has fully started

# Source configuration
source /usr/local/emhttp/plugins/myplugin/default.cfg
source /boot/config/plugins/myplugin/myplugin.cfg 2>/dev/null

# Log that we're starting
logger "myplugin: Array started, initializing..."

# Do your startup tasks here
# ...

# For long-running tasks, background them
/usr/local/emhttp/plugins/myplugin/scripts/background_task.sh &

logger "myplugin: Initialization complete"

Event Script Arguments

All event scripts receive the event name as the first argument:

#!/bin/bash
# event/any_event - Handles all events

EVENT_NAME="$1"

case "$EVENT_NAME" in
    started)
        logger "myplugin: Array started"
        ;;
    stopping)
        logger "myplugin: Array stopping"
        ;;
    *)
        # Ignore other events
        ;;
esac

Common Patterns

Starting Services on Array Start

#!/bin/bash
# event/started

source /boot/config/plugins/myplugin/myplugin.cfg 2>/dev/null

# Only start if enabled in settings
if [ "${SERVICE_ENABLED}" = "true" ]; then
    logger "myplugin: Starting service..."
    /usr/local/emhttp/plugins/myplugin/scripts/start_service.sh &
fi

Autostarting Docker Compose Stacks

Real-world example from Compose Manager:

#!/bin/bash
# event/started

source /usr/local/emhttp/plugins/compose.manager/default.cfg
source /boot/config/plugins/compose.manager/compose.manager.cfg

COMPOSE_ROOT=$PROJECTS_FOLDER
COMPOSE_WRAPPER=/usr/local/emhttp/plugins/compose.manager/scripts/compose.sh

# Process each project directory
for dir in $COMPOSE_ROOT/*; do
    if [ -d "$dir" ]; then
        if [ -f "$dir/docker-compose.yml" ]; then
            if [ -f "$dir/autostart" ]; then
                name=$(< "${dir}/name")
                logger "Starting compose stack: ${name}"
                $COMPOSE_WRAPPER -c up -d "${dir}" &
            fi
        fi
    fi
done

Graceful Shutdown

#!/bin/bash
# event/stopping_docker

source /usr/local/emhttp/plugins/myplugin/default.cfg
source /boot/config/plugins/myplugin/myplugin.cfg

# Stop all managed containers gracefully
for container in $MANAGED_CONTAINERS; do
    logger "myplugin: Stopping container ${container}"
    docker stop "$container" 2>/dev/null
done

Handling Multiple Scripts

If you need multiple scripts for one event, use a directory. Prefix script names with numbers to control execution order—scripts run in alphabetical order, so 01-load-config.sh runs before 02-start-services.sh.

event/
└── started/
    ├── 01-load-config.sh
    ├── 02-start-services.sh
    └── 03-notify.sh

Scripts are executed in alphabetical order.

Best Practices

1. Keep Scripts Fast

The emhttp process waits for event scripts to complete. For long tasks:

#!/bin/bash
# Bad - blocks emhttp
sleep 60
do_long_task

# Good - runs in background using &
do_long_task &

Using the at Command for Background Processes

For more reliable background execution (especially when the parent process may exit), use the at command which schedules a job to run independently:

#!/bin/bash
# Using 'at' command - most reliable for background tasks

# Schedule task to run immediately but independently
echo "/usr/local/emhttp/plugins/myplugin/scripts/long_task.sh" | at now

# With logging
echo "/usr/local/emhttp/plugins/myplugin/scripts/long_task.sh >> /var/log/myplugin.log 2>&1" | at now

Using nohup for Background Processes

The nohup command prevents the process from being killed when the parent exits:

#!/bin/bash
# Using nohup - process continues even if parent exits

nohup /usr/local/emhttp/plugins/myplugin/scripts/long_task.sh > /var/log/myplugin.log 2>&1 &

# Disown to fully detach from shell
nohup /usr/local/emhttp/plugins/myplugin/scripts/long_task.sh > /var/log/myplugin.log 2>&1 &
disown

The at command is generally preferred over nohup & because it creates a completely independent process that won’t be affected by signal propagation when emhttp continues execution.

2. Use Logger for Debugging

Log messages appear in /var/log/syslog:

logger "myplugin: Event $1 received"
logger -t myplugin "More specific tag"

View with: tail -f /var/log/syslog | grep myplugin

3. Handle Missing Config Gracefully

#!/bin/bash
# Source config files safely
source /usr/local/emhttp/plugins/myplugin/default.cfg

# User config might not exist yet
if [ -f /boot/config/plugins/myplugin/myplugin.cfg ]; then
    source /boot/config/plugins/myplugin/myplugin.cfg
fi

4. Check Dependencies

#!/bin/bash
# event/started

# Only run if Docker is actually running
if ! docker info &>/dev/null; then
    logger "myplugin: Docker not running, skipping startup"
    exit 0
fi

# Proceed with Docker-dependent tasks

5. Make Scripts Executable

In your package build script:

chmod +x /path/to/plugin/event/*
chmod +x /path/to/plugin/event/started

6. Be Idempotent

Scripts may run multiple times (reboots, manual array start/stop):

#!/bin/bash
# event/started

PIDFILE=/var/run/myplugin.pid

# Check if already running
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then
    logger "myplugin: Already running"
    exit 0
fi

# Start the service
start_my_service &
echo $! > "$PIDFILE"

Testing Events

Manually Trigger Events

You can simulate events for testing:

# Simulate the 'started' event
/usr/local/sbin/emhttp_event started

# Watch syslog for output
tail -f /var/log/syslog | grep myplugin

Test Without Rebooting

During development, you can directly execute your script:

# Run your event script directly
/usr/local/emhttp/plugins/myplugin/event/started

Check for Errors

# Run with bash debugging
bash -x /usr/local/emhttp/plugins/myplugin/event/started

Reference: emhttp_event Script

Here’s the core logic from /usr/local/sbin/emhttp_event:

#!/bin/bash
# Invoke all 'any_event' scripts that might exist
for Dir in /usr/local/emhttp/plugins/* ; do
  if [ -d $Dir/event/any_event ]; then
    for File in $Dir/event/any_event/* ; do
      if [ -x $File ]; then
        $File "$@"
      fi
    done
  elif [ -x $Dir/event/any_event ]; then
    $Dir/event/any_event "$@"
  fi
done

# Invoke specific event scripts that might exist for this event
for Dir in /usr/local/emhttp/plugins/* ; do
  if [ -d $Dir/event/$1 ]; then
    for File in $Dir/event/$1/* ; do
      if [ -x $File ]; then
        $File "$@"
      fi
    done
  elif [ -x $Dir/event/$1 ]; then
    $Dir/event/$1 "$@"
  fi
done

Next Steps