From ff3680813ca34d8e088d19b7a98b31c0f7be0744 Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Sat, 29 Mar 2025 14:08:23 +0100 Subject: [PATCH 01/11] Create wled-tools.sh Per discussion, add a discover-fueled tool to update/backup wled devices in the local network. --- tools/wled-tools.sh | 226 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 tools/wled-tools.sh diff --git a/tools/wled-tools.sh b/tools/wled-tools.sh new file mode 100644 index 000000000..4ea3282f7 --- /dev/null +++ b/tools/wled-tools.sh @@ -0,0 +1,226 @@ +#!/bin/bash + +# ===================== +# wled-toolbox.sh - A script for managing WLED devices +# ===================== + +# Color Definitions +GREEN="\e[32m" +RED="\e[31m" +BLUE="\e[34m" +YELLOW="\e[33m" +RESET="\e[0m" + +# Path to backup directory +backup_dir="./" + +# Firmware file (if provided) +firmware_file="" + +# Logging function +log() { + local category="$1" + local color="$2" + local text="$3" + + if [ "$quiet" = true ]; then + return + fi + + if [ -t 1 ]; then # Check if output is a terminal + echo -e "${color}[${category}]${RESET} ${text}" + else + echo "[${category}] ${text}" + fi +} + +# Generic curl handler function +curl_handler() { + local command="$1" + local hostname="$2" + + response=$($command -w "%{http_code}" -o /dev/null) + curl_exit_code=$? + + if [ "$response" -ge 200 ] && [ "$response" -lt 300 ]; then + return 0 + elif [ $curl_exit_code -ne 0 ]; then + log "ERROR" "$RED" "Connection error during request to $hostname (curl exit code: $curl_exit_code)." + return 1 + elif [ "$response" -ge 400 ]; then + log "ERROR" "$RED" "Server error during request to $hostname (HTTP status code: $response)." + return 2 + else + log "ERROR" "$RED" "Unexpected response from $hostname (HTTP status code: $response)." + return 3 + fi +} + +# Print help message +show_help() { + cat << EOF +Usage: wled-toolbox.sh [OPTIONS] COMMAND [ARGS...] + +Options: + -h, --help Show this help message and exit. + -v, --verbose Enable verbose output for debugging. + -t, --target Specify a single WLED device by IP address or hostname. + -D, --discover Discover multiple WLED devices using mDNS. + -d, --directory Specify a directory for saving backups (default: working directory). + -f, --firmware Specify the firmware file for updating devices. + -q, --quiet Suppress logging output (also makes discover output hostnames only). + +Commands: + backup Backup the current state of a WLED device or multiple discovered devices. + update Update the firmware of a WLED device or multiple discovered devices. + discover Discover WLED devices using mDNS and list their IP addresses and names. + +EOF +} + +# Discover devices using mDNS +discover_devices() { + if ! command -v avahi-browse &> /dev/null; then + log "ERROR" "$RED" "'avahi-browse' is required but not installed." + exit 1 + fi + + mapfile -t hostnames < <(avahi-browse _wled._tcp --terminate -r -p | awk -F';' '/^=/ {print $7}') + if [ "$quiet" = true ]; then + for hostname in "${hostnames[@]}"; do + echo "$hostname" + done + else + printf "%s\n" "${hostnames[@]}" | sort -u + fi +} + +# Backup one device +backup_one() { + local hostname="$1" + + log "INFO" "$YELLOW" "Backing up device config/presets: $hostname" + + mkdir -p "$backup_dir" + + local cfg_url="http://$hostname/cfg.json" + local presets_url="http://$hostname/presets.json" + local cfg_dest="${backup_dir}/${hostname}.cfg.json" + local presets_dest="${backup_dir}/${hostname}.presets.json" + + # Write to ".tmp" files first, then move when success, to ensure we don't write partial files + local curl_command_cfg="curl -s "$cfg_url" -o "$cfg_dest.tmp"" + local curl_command_presets="curl -s "$presets_url" -o "$presets_dest.tmp"" + + curl_handler "$curl_command_cfg" "$hostname" + curl_handler "$curl_command_presets" "$hostname" + + mv "$cfg_dest.tmp" "$cfg_dest" + mv "$presets_dest.tmp" "$presets_dest" +} + +# Update one device +update_one() { + local hostname="$1" + local firmware="$2" + + log "INFO" "$YELLOW" "Starting firmware update for device: $hostname" + + if [ -z "$firmware" ]; then + log "ERROR" "$RED" "Firmware file not specified." + exit 1 + fi + + local url="http://$hostname/update" + local curl_command="curl -s -X POST -F "file=@$firmware" "$url"" + + curl_handler "$curl_command" "$hostname" +} + +# Command-line arguments processing +command="" +target="" +discover=false +quiet=false + +if [ $# -eq 0 ]; then + show_help + exit 0 +fi + +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) + show_help + exit 0 + ;; + -v|--verbose) + verbose=true + shift + ;; + -t|--target) + target="$2" + shift 2 + ;; + -D|--discover) + discover=true + shift + ;; + -d|--directory) + backup_dir="$2" + shift 2 + ;; + -f|--firmware) + firmware_file="$2" + shift 2 + ;; + -q|--quiet) + quiet=true + shift + ;; + backup|update|discover) + command="$1" + shift + ;; + *) + log "ERROR" "$RED" "Unknown argument: $1" + exit 1 + ;; + esac + +done + +# Execute the appropriate command +case "$command" in + discover) + discover_devices + ;; + backup) + if [ -n "$target" ]; then + backup_one "$target" + elif [ "$discover" = true ]; then + for hostname in $(discover_devices); do + backup_one "$hostname" + done + else + log "ERROR" "$RED" "No target specified. Use --target or --discover." + exit 1 + fi + ;; + update) + if [ -n "$target" ]; then + update_one "$target" "$firmware_file" + elif [ "$discover" = true ]; then + for hostname in $(discover_devices); do + update_one "$hostname" "$firmware_file" + done + else + log "ERROR" "$RED" "No target specified. Use --target or --discover." + exit 1 + fi + ;; + *) + show_help + exit 1 + ;; +esac From 0139c34ec276d2987156daf7837bb1b8f07ec0c3 Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Sat, 29 Mar 2025 14:10:33 +0100 Subject: [PATCH 02/11] Rename script in help text --- tools/wled-tools.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/wled-tools.sh b/tools/wled-tools.sh index 4ea3282f7..731841c70 100644 --- a/tools/wled-tools.sh +++ b/tools/wled-tools.sh @@ -59,7 +59,7 @@ curl_handler() { # Print help message show_help() { cat << EOF -Usage: wled-toolbox.sh [OPTIONS] COMMAND [ARGS...] +Usage: wled-tools.sh [OPTIONS] COMMAND [ARGS...] Options: -h, --help Show this help message and exit. From 8a6e3a9898ff8d11d59523740c61e3f50472d4e7 Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Sat, 29 Mar 2025 18:20:29 +0100 Subject: [PATCH 03/11] Update wled-tools.sh --- tools/wled-tools.sh | 95 ++++++++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/tools/wled-tools.sh b/tools/wled-tools.sh index 731841c70..45f670612 100644 --- a/tools/wled-tools.sh +++ b/tools/wled-tools.sh @@ -1,9 +1,5 @@ #!/bin/bash -# ===================== -# wled-toolbox.sh - A script for managing WLED devices -# ===================== - # Color Definitions GREEN="\e[32m" RED="\e[31m" @@ -11,12 +7,6 @@ BLUE="\e[34m" YELLOW="\e[33m" RESET="\e[0m" -# Path to backup directory -backup_dir="./" - -# Firmware file (if provided) -firmware_file="" - # Logging function log() { local category="$1" @@ -85,26 +75,29 @@ discover_devices() { exit 1 fi - mapfile -t hostnames < <(avahi-browse _wled._tcp --terminate -r -p | awk -F';' '/^=/ {print $7}') - if [ "$quiet" = true ]; then - for hostname in "${hostnames[@]}"; do - echo "$hostname" - done - else - printf "%s\n" "${hostnames[@]}" | sort -u - fi + mapfile -t raw_devices < <(avahi-browse _wled._tcp --terminate -r -p | awk -F';' '/^=/ {print $7, $8, $9}') + + local devices_array=() + for device in "${raw_devices[@]}"; do + read -r hostname address port <<< "$device" + devices_array+=("$hostname" "$address" "$port") + done + + echo "${devices_array[@]}" } # Backup one device backup_one() { local hostname="$1" + local address="$2" + local port="$3" - log "INFO" "$YELLOW" "Backing up device config/presets: $hostname" + log "INFO" "$YELLOW" "Backing up device config/presets: $hostname ($address:$port)" mkdir -p "$backup_dir" - local cfg_url="http://$hostname/cfg.json" - local presets_url="http://$hostname/presets.json" + local cfg_url="http://$address:$port/cfg.json" + local presets_url="http://$address:$port/presets.json" local cfg_dest="${backup_dir}/${hostname}.cfg.json" local presets_dest="${backup_dir}/${hostname}.presets.json" @@ -122,16 +115,13 @@ backup_one() { # Update one device update_one() { local hostname="$1" - local firmware="$2" + local address="$2" + local port="$3" + local firmware="$4" - log "INFO" "$YELLOW" "Starting firmware update for device: $hostname" + log "INFO" "$YELLOW" "Starting firmware update for device: $hostname ($address:$port)" - if [ -z "$firmware" ]; then - log "ERROR" "$RED" "Firmware file not specified." - exit 1 - fi - - local url="http://$hostname/update" + local url="http://$address:$port/update" local curl_command="curl -s -X POST -F "file=@$firmware" "$url"" curl_handler "$curl_command" "$hostname" @@ -142,6 +132,8 @@ command="" target="" discover=false quiet=false +backup_dir="./" +firmware_file="" if [ $# -eq 0 ]; then show_help @@ -154,10 +146,6 @@ while [[ $# -gt 0 ]]; do show_help exit 0 ;; - -v|--verbose) - verbose=true - shift - ;; -t|--target) target="$2" shift 2 @@ -193,14 +181,30 @@ done # Execute the appropriate command case "$command" in discover) - discover_devices + read -ra devices <<< "$(discover_devices)" + for ((i=0; i<${#devices[@]}; i+=3)); do + hostname="${devices[$i]}" + address="${devices[$i+1]}" + port="${devices[$i+2]}" + + if [ "$quiet" = true ]; then + echo "$hostname" + else + log "INFO" "$BLUE" "Discovered device: Hostname=$hostname, Address=$address, Port=$port" + fi + done ;; backup) if [ -n "$target" ]; then - backup_one "$target" + # Assume target is both the hostname and address, with port 80 + backup_one "$target" "$target" "80" elif [ "$discover" = true ]; then - for hostname in $(discover_devices); do - backup_one "$hostname" + read -ra devices <<< "$(discover_devices)" + for ((i=0; i<${#devices[@]}; i+=3)); do + hostname="${devices[$i]}" + address="${devices[$i+1]}" + port="${devices[$i+2]}" + backup_one "$hostname" "$address" "$port" done else log "ERROR" "$RED" "No target specified. Use --target or --discover." @@ -208,11 +212,22 @@ case "$command" in fi ;; update) + # Validate firmware before proceeding + if [ -z "$firmware_file" ] || [ ! -f "$firmware_file" ]; then + log "ERROR" "$RED" "Please provide a file in --firmware that exists" + exit 1 + fi + if [ -n "$target" ]; then - update_one "$target" "$firmware_file" + # Assume target is both the hostname and address, with port 80 + update_one "$target" "$target" "80" "$firmware_file" elif [ "$discover" = true ]; then - for hostname in $(discover_devices); do - update_one "$hostname" "$firmware_file" + read -ra devices <<< "$(discover_devices)" + for ((i=0; i<${#devices[@]}; i+=3)); do + hostname="${devices[$i]}" + address="${devices[$i+1]}" + port="${devices[$i+2]}" + update_one "$hostname" "$address" "$port" "$firmware_file" done else log "ERROR" "$RED" "No target specified. Use --target or --discover." From 6f5482782bb0c747c29a6c6e4cd2e5afcb8b2adc Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Sat, 29 Mar 2025 21:38:31 +0100 Subject: [PATCH 04/11] Update wled-tools.sh --- tools/wled-tools.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/wled-tools.sh b/tools/wled-tools.sh index 45f670612..3ee574b91 100644 --- a/tools/wled-tools.sh +++ b/tools/wled-tools.sh @@ -53,7 +53,6 @@ Usage: wled-tools.sh [OPTIONS] COMMAND [ARGS...] Options: -h, --help Show this help message and exit. - -v, --verbose Enable verbose output for debugging. -t, --target Specify a single WLED device by IP address or hostname. -D, --discover Discover multiple WLED devices using mDNS. -d, --directory Specify a directory for saving backups (default: working directory). From dc5eaf3ae9b3aa4d9cef7914813c2f376df00ba7 Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Thu, 3 Apr 2025 21:46:58 +0200 Subject: [PATCH 05/11] Rename file --- tools/{wled-tools.sh => wled-tools} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tools/{wled-tools.sh => wled-tools} (100%) diff --git a/tools/wled-tools.sh b/tools/wled-tools similarity index 100% rename from tools/wled-tools.sh rename to tools/wled-tools From 646ec30ae5049a3387069d22cb85cbb61ee273c7 Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Sun, 6 Apr 2025 01:07:40 +0200 Subject: [PATCH 06/11] Implement error checking for curl; add checks for required param args --- tools/wled-tools | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/tools/wled-tools b/tools/wled-tools index 3ee574b91..d99e9e51a 100644 --- a/tools/wled-tools +++ b/tools/wled-tools @@ -104,11 +104,22 @@ backup_one() { local curl_command_cfg="curl -s "$cfg_url" -o "$cfg_dest.tmp"" local curl_command_presets="curl -s "$presets_url" -o "$presets_dest.tmp"" - curl_handler "$curl_command_cfg" "$hostname" - curl_handler "$curl_command_presets" "$hostname" + if ! curl_handler "$curl_command_cfg" "$hostname"; then + log "ERROR" "$RED" "Failed to backup configuration for $hostname" + rm -f "$cfg_dest.tmp" + return 1 + fi + + if ! curl_handler "$curl_command_presets" "$hostname"; then + log "ERROR" "$RED" "Failed to backup presets for $hostname" + rm -f "$presets_dest.tmp" + return 1 + fi mv "$cfg_dest.tmp" "$cfg_dest" mv "$presets_dest.tmp" "$presets_dest" + log "INFO" "$GREEN" "Successfully backed up config and presets for $hostname" + return 0 } # Update one device @@ -146,6 +157,10 @@ while [[ $# -gt 0 ]]; do exit 0 ;; -t|--target) + if [ -z "$2" ] || [[ "$2" == -* ]]; then + log "ERROR" "$RED" "The --target option requires an argument." + exit 1 + fi target="$2" shift 2 ;; @@ -154,10 +169,18 @@ while [[ $# -gt 0 ]]; do shift ;; -d|--directory) + if [ -z "$2" ] || [[ "$2" == -* ]]; then + log "ERROR" "$RED" "The --directory option requires an argument." + exit 1 + fi backup_dir="$2" shift 2 ;; -f|--firmware) + if [ -z "$2" ] || [[ "$2" == -* ]]; then + log "ERROR" "$RED" "The --firmware option requires an argument." + exit 1 + fi firmware_file="$2" shift 2 ;; @@ -174,7 +197,6 @@ while [[ $# -gt 0 ]]; do exit 1 ;; esac - done # Execute the appropriate command From bc099baeb11fcb4cc43747c3f58d1df24f6e664a Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Sun, 6 Apr 2025 01:10:27 +0200 Subject: [PATCH 07/11] Guard against hostnames/avahi responses with spaces --- tools/wled-tools | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/tools/wled-tools b/tools/wled-tools index d99e9e51a..345ef1ec1 100644 --- a/tools/wled-tools +++ b/tools/wled-tools @@ -68,22 +68,23 @@ EOF } # Discover devices using mDNS -discover_devices() { - if ! command -v avahi-browse &> /dev/null; then - log "ERROR" "$RED" "'avahi-browse' is required but not installed." - exit 1 - fi +discover_devices() { + if ! command -v avahi-browse &> /dev/null; then + log "ERROR" "$RED" "'avahi-browse' is required but not installed." + exit 1 + fi - mapfile -t raw_devices < <(avahi-browse _wled._tcp --terminate -r -p | awk -F';' '/^=/ {print $7, $8, $9}') + # Map avahi responses to strings seperated by 0x1F (unit separator) + mapfile -t raw_devices < <(avahi-browse _wled._tcp --terminate -r -p | awk -F';' '/^=/ {print $7"\x1F"$8"\x1F"$9}') - local devices_array=() - for device in "${raw_devices[@]}"; do - read -r hostname address port <<< "$device" - devices_array+=("$hostname" "$address" "$port") - done + local devices_array=() + for device in "${raw_devices[@]}"; do + IFS=$'\x1F' read -r hostname address port <<< "$device" + devices_array+=("$hostname" "$address" "$port") + done - echo "${devices_array[@]}" -} + echo "${devices_array[@]}" +} # Backup one device backup_one() { From 5203c3927fbb5e97163704e2435bbeffb81ca0dc Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Sun, 6 Apr 2025 01:11:20 +0200 Subject: [PATCH 08/11] Update tools/wled-tools Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- tools/wled-tools | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/wled-tools b/tools/wled-tools index 345ef1ec1..40d2df91d 100644 --- a/tools/wled-tools +++ b/tools/wled-tools @@ -135,7 +135,13 @@ update_one() { local url="http://$address:$port/update" local curl_command="curl -s -X POST -F "file=@$firmware" "$url"" - curl_handler "$curl_command" "$hostname" + if ! curl_handler "$curl_command" "$hostname"; then + log "ERROR" "$RED" "Failed to update firmware for $hostname" + return 1 + fi + + log "INFO" "$GREEN" "Successfully initiated firmware update for $hostname" + return 0 } # Command-line arguments processing From 9e96bd6705a08a1f70d24f74f141d805616faa1b Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Sun, 6 Apr 2025 01:17:44 +0200 Subject: [PATCH 09/11] Add help examples, header --- tools/wled-tools | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tools/wled-tools b/tools/wled-tools index 40d2df91d..1b27b113f 100644 --- a/tools/wled-tools +++ b/tools/wled-tools @@ -1,5 +1,9 @@ #!/bin/bash +# WLED Tools +# A utility for managing WLED devices in a local network +# https://github.com/wled/WLED + # Color Definitions GREEN="\e[32m" RED="\e[31m" @@ -64,6 +68,19 @@ Commands: update Update the firmware of a WLED device or multiple discovered devices. discover Discover WLED devices using mDNS and list their IP addresses and names. +Examples: + # Discover all WLED devices on the network + ./wled-tools discover + + # Backup a specific WLED device + ./wled-tools -t 192.168.1.100 backup + + # Backup all discovered WLED devices to a specific directory + ./wled-tools -D -d /path/to/backups backup + + # Update firmware on all discovered WLED devices + ./wled-tools -D -f /path/to/firmware.bin update + EOF } From 81b74227fad6b2908757da6280abfe9d09a42969 Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Sun, 6 Apr 2025 01:20:24 +0200 Subject: [PATCH 10/11] Set executable --- tools/wled-tools | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 tools/wled-tools diff --git a/tools/wled-tools b/tools/wled-tools old mode 100644 new mode 100755 From 88aa8e817868e2edd718b26438058d19068deffc Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Fri, 11 Apr 2025 16:15:52 +0200 Subject: [PATCH 11/11] Add note on avahi-utils --- tools/wled-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/wled-tools b/tools/wled-tools index 1b27b113f..9d196526f 100755 --- a/tools/wled-tools +++ b/tools/wled-tools @@ -87,7 +87,7 @@ EOF # Discover devices using mDNS discover_devices() { if ! command -v avahi-browse &> /dev/null; then - log "ERROR" "$RED" "'avahi-browse' is required but not installed." + log "ERROR" "$RED" "'avahi-browse' is required but not installed, please install avahi-utils using your preferred package manager." exit 1 fi