Let me explain the how the script works:
-
Script Structure and Error Handling:
- Added
set -euo pipefail
for strict error handling - Organized code into functions for better modularity and maintenance
- Added proper logging functionality
- Added ZFS availability check
- Added
-
Key Functions:
log_message
: Records script activity with timestampscheck_zfs_available
: Verifies ZFS commands are availableget_pool_status
: Safely gets pool status with error handlingcheck_pool_health
: Determines severity based on pool statesend_notification
: Handles notification logic with duplicate checkingmain
: Orchestrates the overall script flow
-
Core Functionality:
- Checks all ZFS pools for errors
- Monitors for DEGRADED or FAULTED states
- Sends notifications only for mounted pools with issues
- Prevents duplicate notifications using checksums
- Supports both warning and alert severity levels
-
Enhanced Features:
- Better error handling and logging
- More robust pool status checking
- Clearer variable names and documentation
- Array handling improvements using mapfile
- Configuration variables at the top for easy modification
-
How It Works:
1. Script starts and checks for ZFS availability 2. Gets list of all ZFS pools 3. Checks overall status of all pools 4. For each pool: - Verifies if it's mounted - Checks individual pool status - Records errors and determines severity 5. If errors are found: - Generates checksum to prevent duplicates - Sends notification with appropriate severity - Logs the action
The script can be used in two ways:
-
Manual Execution:
chmod +x zfs_notify.sh ./zfs_notify.sh
-
Automated via Cron:
# Add to crontab (run every hour) 0 * * * * /path/to/zfs_notify.sh
To monitor the script’s activity, you can check the log file:
tail -f /var/log/zfs_errors.log
[zfs_notify.sh]
#!/bin/bash
# Set strict error handling
set -euo pipefail
# Configuration
NOTIFY_SCRIPT="/usr/local/emhttp/webGui/scripts/notify"
LOG_FILE="/var/log/zfs_errors.log"
# Function to log messages
log_message() {
local message="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $message" >> "$LOG_FILE"
}
# Function to check if ZFS is available
check_zfs_available() {
if ! command -v zfs >/dev/null 2>&1; then
log_message "ERROR: ZFS commands not found. Is ZFS installed?"
exit 1
}
}
# Function to get pool status
get_pool_status() {
local pool="$1"
zpool status -x "$pool" 2>/dev/null || echo "Error getting status for pool $pool"
}
# Function to check pool health
check_pool_health() {
local status="$1"
local severity="warning"
if echo "$status" | grep -q "DEGRADED"; then
severity="alert"
elif echo "$status" | grep -q "FAULTED"; then
severity="alert"
fi
echo "$severity"
}
# Function to send notification
send_notification() {
local severity="$1"
local pools="$2"
local status="$3"
local cksum="$4"
# Check for existing notifications
if /usr/local/emhttp/webGui/scripts/notify get | grep -q "$cksum"; then
log_message "Notification already exists for checksum: $cksum"
return 0
}
# Send notification
"$NOTIFY_SCRIPT" \
-i "$severity" \
-e "ZFS" \
-s "ZFS Errors Detected" \
-d "Errors detected in the following pools:<br>${pools}<br>($cksum)" \
-m "$status"
log_message "Sent notification for pools: $pools"
}
main() {
# Check if ZFS is available
check_zfs_available
# Get all ZFS pools
mapfile -t pools < <(zfs list -d 0 -o name -H)
# Get overall status
zStatus=$(zpool status -x -v)
# Initialize arrays and variables
declare -a poolErr=()
severity="warning"
# Check if there are any errors
if [ "$(echo "$zStatus" | wc -l)" -gt 1 ]; then
# Loop through each pool
for pool in "${pools[@]}"; do
# Check if pool is mounted
if mountpoint -q "/mnt/$pool"; then
pool_status=$(get_pool_status "$pool")
# Check for errors in this pool
if [ "$(echo "$pool_status" | wc -l)" -gt 1 ]; then
poolErr+=("$pool")
# Update severity if needed
current_severity=$(check_pool_health "$pool_status")
if [ "$current_severity" = "alert" ]; then
severity="alert"
fi
fi
fi
done
fi
# Send notification if there are errors
if [ "${#poolErr[@]}" -gt 0 ]; then
cksum=$(echo "$zStatus" | cksum | cut -f1 -d' ')
send_notification "$severity" "${poolErr[*]}" "$zStatus" "$cksum"
else
log_message "No ZFS errors detected"
fi
}
# Run main function
main "$@"
Enhanced Unraid ZFS Error Notification Script with Discord Integration
I have modify the script to include Discord webhook notifications alongside the existing Unraid notifications. This will allow for monitoring ZFS errors through Discord as well.
I’ve enhanced the script to include Discord notifications. Here are the key additions and how to set it up:
- New Discord Configuration Section:
# Discord Configuration
DISCORD_WEBHOOK_URL="YOUR_DISCORD_WEBHOOK_URL"
DISCORD_USERNAME="Unraid ZFS Monitor"
DISCORD_AVATAR_URL="https://i.imgur.com/your-avatar.png"
- New Discord Notification Function:
- Creates formatted messages with embeds
- Color-coded based on severity (red for alerts, yellow for warnings)
- Includes timestamp and pool details
- Formats ZFS status output in a code block
- Handles message length limits
- Includes error handling for failed notifications
- Setting Up Discord Integration:
a. Create a Discord Webhook:
- Open Discord server settings
- Go to “Integrations” → “Webhooks”
- Click “New Webhook”
- Name it (e.g., “Unraid ZFS Monitor”)
- Choose the channel for notifications
- Copy the webhook URL
b. Configure the Script:
# Edit the script and replace the webhook URL
DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/your-webhook-url"
- Sample Discord Notification Format:
Title: ZFS Errors Detected
Description: Issues found in the following pools:
pool1, pool2
Severity: alert
Details:
[ZFS status output in code block]
Footer: Unraid ZFS Monitor - [Timestamp]
- Using the Script:
a. Make it executable:
chmod +x zfs_notify.sh
b. Test the Discord integration:
./zfs_notify.sh
c. Set up automated monitoring via cron:
# Edit crontab
crontab -e
# Add this line to run every hour
0 * * * * /path/to/zfs_notify.sh
- Features of Discord Integration:
- Color-coded notifications based on severity
- Formatted message embeds for better readability
- Code blocks for status output
- Timestamp information
- Error handling and logging
- Configurable bot name and avatar
- Troubleshooting:
- Check the log file for notification status:
tail -f /var/log/zfs_errors.log
- Test the Discord webhook separately:
curl -H "Content-Type: application/json" \
-d '{"content": "Test message"}' \
YOUR_DISCORD_WEBHOOK_URL
#!/bin/bash
# Set strict error handling
set -euo pipefail
# Configuration
NOTIFY_SCRIPT="/usr/local/emhttp/webGui/scripts/notify"
LOG_FILE="/var/log/zfs_errors.log"
# Discord Configuration - Replace with your webhook URL
DISCORD_WEBHOOK_URL="YOUR_DISCORD_WEBHOOK_URL"
DISCORD_USERNAME="Unraid ZFS Monitor"
DISCORD_AVATAR_URL="https://i.imgur.com/your-avatar.png" # Optional: Replace with your desired avatar URL
# Function to log messages
log_message() {
local message="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $message" >> "$LOG_FILE"
}
# Function to check if ZFS is available
check_zfs_available() {
if ! command -v zfs >/dev/null 2>&1; then
log_message "ERROR: ZFS commands not found. Is ZFS installed?"
exit 1
}
}
# Function to send Discord notification
send_discord_notification() {
local severity="$1"
local pools="$2"
local status="$3"
# Set color based on severity (Discord uses decimal color codes)
local color
if [ "$severity" = "alert" ]; then
color="16711680" # Red
else
color="16776960" # Yellow
fi
# Format status message for Discord (limit to 2000 characters)
local formatted_status
formatted_status=$(echo "$status" | sed 's/```/\\`\\`\\`/g' | cut -c 1-1500)
# Create JSON payload
local json_payload=$(cat <<EOF
{
"username": "${DISCORD_USERNAME}",
"avatar_url": "${DISCORD_AVATAR_URL}",
"embeds": [
{
"title": "ZFS Errors Detected",
"description": "Issues found in the following pools:\n${pools}",
"color": ${color},
"fields": [
{
"name": "Severity",
"value": "${severity}",
"inline": true
},
{
"name": "Details",
"value": "\`\`\`${formatted_status}\`\`\`"
}
],
"footer": {
"text": "Unraid ZFS Monitor - $(date '+%Y-%m-%d %H:%M:%S')"
}
}
]
}
EOF
)
# Send to Discord
curl -H "Content-Type: application/json" \
-d "$json_payload" \
"$DISCORD_WEBHOOK_URL" 2>/dev/null || {
log_message "ERROR: Failed to send Discord notification"
return 1
}
log_message "Sent Discord notification for pools: $pools"
}
# Function to get pool status
get_pool_status() {
local pool="$1"
zpool status -x "$pool" 2>/dev/null || echo "Error getting status for pool $pool"
}
# Function to check pool health
check_pool_health() {
local status="$1"
local severity="warning"
if echo "$status" | grep -q "DEGRADED"; then
severity="alert"
elif echo "$status" | grep -q "FAULTED"; then
severity="alert"
fi
echo "$severity"
}
# Function to send Unraid notification
send_unraid_notification() {
local severity="$1"
local pools="$2"
local status="$3"
local cksum="$4"
# Check for existing notifications
if /usr/local/emhttp/webGui/scripts/notify get | grep -q "$cksum"; then
log_message "Notification already exists for checksum: $cksum"
return 0
}
# Send notification
"$NOTIFY_SCRIPT" \
-i "$severity" \
-e "ZFS" \
-s "ZFS Errors Detected" \
-d "Errors detected in the following pools:<br>${pools}<br>($cksum)" \
-m "$status"
log_message "Sent Unraid notification for pools: $pools"
}
main() {
# Check if ZFS is available
check_zfs_available
# Get all ZFS pools
mapfile -t pools < <(zfs list -d 0 -o name -H)
# Get overall status
zStatus=$(zpool status -x -v)
# Initialize arrays and variables
declare -a poolErr=()
severity="warning"
# Check if there are any errors
if [ "$(echo "$zStatus" | wc -l)" -gt 1 ]; then
# Loop through each pool
for pool in "${pools[@]}"; do
# Check if pool is mounted
if mountpoint -q "/mnt/$pool"; then
pool_status=$(get_pool_status "$pool")
# Check for errors in this pool
if [ "$(echo "$pool_status" | wc -l)" -gt 1 ]; then
poolErr+=("$pool")
# Update severity if needed
current_severity=$(check_pool_health "$pool_status")
if [ "$current_severity" = "alert" ]; then
severity="alert"
fi
fi
fi
done
fi
# Send notifications if there are errors
if [ "${#poolErr[@]}" -gt 0 ]; then
cksum=$(echo "$zStatus" | cksum | cut -f1 -d' ')
# Send both Unraid and Discord notifications
send_unraid_notification "$severity" "${poolErr[*]}" "$zStatus" "$cksum"
send_discord_notification "$severity" "${poolErr[*]}" "$zStatus"
else
log_message "No ZFS errors detected"
fi
}
# Run main function
main "$@"