Created
January 8, 2026 11:35
-
-
Save xarantolus/9cf096a0b8095d3fe0c09ce30275ed1b to your computer and use it in GitHub Desktop.
Uptime monitor with restart using a Wiz v2 smart plug
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -eoux pipefail | |
| # Note: Ensure that in the Wiz v2 apps' settings under security, local communication is allowed | |
| # Then in the same network, put this script onto another Linux machine, and add a crontab entry like this for every 5 minutes: | |
| # */5 * * * * bash /home/pi/gs/watchdog.sh > /dev/null 2>&1 | |
| # --- CONFIGURATION --- | |
| TARGET_HOST="<host that should be monitored and can be restarted using the Wiz Smart Plug>" | |
| PLUG_HOST="wiz-<some name>" | |
| LOG_FILE="$(dirname "$0")/watchdog.log" | |
| LOCK_FILE="/tmp/gs-watchdog.lock" | |
| UDP_PORT=38899 | |
| # Backoff delays in seconds (30s, 2m, 5m) | |
| RETRY_DELAYS=(30 120 300) | |
| # How long to wait for the host to come online after power on | |
| BOOT_TIMEOUT=60 | |
| # --- FUNCTIONS --- | |
| log_msg() { | |
| local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1" | |
| echo "$msg" >> "$LOG_FILE" | |
| echo "$msg" | |
| } | |
| set_plug_state() { | |
| local state="$1" # true (ON) or false (OFF) | |
| local max_retries=5 | |
| local count=0 | |
| local payload="{\"method\":\"setPilot\",\"params\":{\"state\":$state}}" | |
| until [ $count -ge $max_retries ]; do | |
| # Send command, capture stdout, silence stderr | |
| result=$(echo "$payload" | nc -u -w 2 "$PLUG_HOST" "$UDP_PORT" 2>/dev/null) | |
| # Check specifically for "success":true in the JSON response | |
| if echo "$result" | grep -q '"success":true'; then | |
| return 0 | |
| fi | |
| log_msg "WARN: Retry $((count+1))/$max_retries for state $state. Response was: '$result'" | |
| ((count++)) | |
| sleep 2 | |
| done | |
| log_msg "ERROR: Failed to set plug state to $state after $max_retries attempts." | |
| return 1 | |
| } | |
| cleanup() { | |
| rm -f "$LOCK_FILE" | |
| } | |
| check_connection() { | |
| ping -c 1 -W 2 "$TARGET_HOST" > /dev/null 2>&1 | |
| } | |
| wait_for_recovery() { | |
| local end_time=$((SECONDS + BOOT_TIMEOUT)) | |
| while [ $SECONDS -lt $end_time ]; do | |
| if check_connection; then | |
| return 0 | |
| fi | |
| sleep 2 | |
| done | |
| return 1 | |
| } | |
| # --- EXECUTION --- | |
| # 1. Lock Mechanism | |
| if [ -e "$LOCK_FILE" ]; then | |
| # Optional: Check if PID in lockfile is actually running to handle stale locks | |
| # For now, we assume simple existence check as requested. | |
| echo "Lock file exists. Exiting." | |
| exit 0 | |
| fi | |
| echo $$ > "$LOCK_FILE" | |
| trap cleanup EXIT | |
| # 2. Initial Check | |
| if check_connection; then | |
| # Target is up, nothing to do | |
| exit 0 | |
| fi | |
| log_msg "ALERT: $TARGET_HOST unreachable. Starting recovery sequence." | |
| # 3. Recovery Loop | |
| for delay in "${RETRY_DELAYS[@]}"; do | |
| log_msg "Power cycling $PLUG_HOST. Waiting ${delay}s before turning ON..." | |
| # Turn OFF | |
| set_plug_state false | |
| # Wait the backoff duration | |
| sleep "$delay" | |
| # Turn ON | |
| set_plug_state true | |
| log_msg "Power ON sent. Waiting up to ${BOOT_TIMEOUT}s for boot..." | |
| # Check if back online | |
| if wait_for_recovery; then | |
| log_msg "SUCCESS: $TARGET_HOST is back online." | |
| exit 0 | |
| else | |
| log_msg "FAILURE: Host did not recover within ${BOOT_TIMEOUT}s." | |
| fi | |
| done | |
| log_msg "CRITICAL: All recovery attempts failed. Leaving plug ON." | |
| # Ensure plug is ON before giving up | |
| set_plug_state true | |
| exit 1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment