From ec2062872cc854531d23135d9952fc73f0610d1b Mon Sep 17 00:00:00 2001 From: jessikitty Date: Tue, 23 Dec 2025 12:53:00 +1100 Subject: [PATCH] Fix v2: Handle included files and list-item template format Critical fixes for real-world HA configurations: - Scans ALL .yaml files in /config/ and /config/packages/ - Handles "- platform: template" (list item with dash) - Properly extracts sensors from "sensors:" blocks - Handles multiline templates with > operator - Correct indentation for modern template: format - Counts sensors correctly from included files This version will find all 67+ legacy template entities in typical HA setups. --- migrate_templates.sh | 176 +++++++++++++++++++++++++------------------ 1 file changed, 101 insertions(+), 75 deletions(-) diff --git a/migrate_templates.sh b/migrate_templates.sh index 4ccd351..f18fc5e 100644 --- a/migrate_templates.sh +++ b/migrate_templates.sh @@ -1,7 +1,7 @@ #!/bin/bash ################################################################################ -# Home Assistant Legacy Template Migration (Bash Version) -# Safely migrates legacy template syntax to modern template: syntax +# Home Assistant Legacy Template Migration v2 (Bash) +# Handles templates in included files (sensors.yaml, binary_sensors.yaml) ################################################################################ set -e # Exit on error @@ -25,7 +25,7 @@ TOTAL_BINARY_SENSORS=0 FILES_MODIFIED=0 echo "======================================================================" -echo "Home Assistant Legacy Template Migration (Bash)" +echo "Home Assistant Legacy Template Migration v2 (Bash)" echo "======================================================================" echo "" @@ -53,53 +53,47 @@ extract_legacy_templates() { echo -e "${BLUE}ℹ${NC} Processing $(basename "$file") for ${type}..." - # Use awk to extract legacy template blocks - awk -v type="$type" ' - BEGIN { - in_sensor_section = 0 - in_template_platform = 0 - in_sensors_block = 0 - indent_level = 0 + # Extract all sensor definitions from platform: template blocks + awk ' + BEGIN { + in_platform = 0 + in_sensors = 0 platform_indent = 0 } - # Detect sensor: or binary_sensor: section - /^[[:space:]]*'"$type"':/ { - in_sensor_section = 1 - next - } - - # If in sensor section, look for platform: template - in_sensor_section && /^[[:space:]]*-[[:space:]]*platform:[[:space:]]*template/ { - in_template_platform = 1 + # Detect "- platform: template" or "platform: template" + /^[[:space:]]*-?[[:space:]]*platform:[[:space:]]*template/ { + in_platform = 1 match($0, /^[[:space:]]*/) platform_indent = RLENGTH next } - # Look for sensors: block within template platform - in_template_platform && /^[[:space:]]*sensors:/ { - in_sensors_block = 1 + # Look for "sensors:" within platform block + in_platform && /^[[:space:]]*sensors:/ { + in_sensors = 1 next } # Extract sensor definitions - in_sensors_block { - # Check if we hit next platform or end of section - if (/^[[:space:]]*-[[:space:]]*platform:/ || /^[[:alpha:]]/) { - in_sensor_section = 0 - in_template_platform = 0 - in_sensors_block = 0 + in_sensors { + # Check if we hit next platform or top-level item + if (/^[[:space:]]*-[[:space:]]*platform:/ || /^[[:space:]]*-[[:space:]]*$/ || /^[a-zA-Z]/) { + in_platform = 0 + in_sensors = 0 next } - # Print sensor lines - print + # Print sensor lines (indented under sensors:) + if (/^[[:space:]]+[a-zA-Z_]/) { + print + } } ' "$file" > "$output_file" if [ -s "$output_file" ]; then - local count=$(grep -c "^[[:space:]]*[a-zA-Z_].*:" "$output_file" 2>/dev/null || echo 0) + # Count unique sensor names (lines that start a sensor definition) + local count=$(grep -c "^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*:" "$output_file" 2>/dev/null || echo 0) echo " ${GREEN}✓${NC} Found $count ${type}(s)" if [ "$type" = "sensor" ]; then @@ -108,17 +102,19 @@ extract_legacy_templates() { TOTAL_BINARY_SENSORS=$((TOTAL_BINARY_SENSORS + count)) fi - # Backup original file - cp "$file" "$BACKUP_DIR/$(basename "$file")" - echo " ${GREEN}✓${NC} Backed up: $(basename "$file")" - FILES_MODIFIED=$((FILES_MODIFIED + 1)) + # Backup original file (only once) + if [ ! -f "$BACKUP_DIR/$(basename "$file")" ]; then + cp "$file" "$BACKUP_DIR/$(basename "$file")" + echo " ${GREEN}✓${NC} Backed up: $(basename "$file")" + FILES_MODIFIED=$((FILES_MODIFIED + 1)) + fi else rm -f "$output_file" fi } ################################################################################ -# Remove legacy template definitions from a file +# Remove legacy template platform blocks from a file ################################################################################ remove_legacy_templates() { local file="$1" @@ -127,7 +123,9 @@ remove_legacy_templates() { return fi - # Use awk to remove legacy template platform blocks + echo -e "${BLUE}ℹ${NC} Removing legacy templates from $(basename "$file")..." + + # Use awk to remove entire platform: template blocks awk ' BEGIN { in_template_platform = 0 @@ -135,7 +133,7 @@ remove_legacy_templates() { skip_block = 0 } - # Detect platform: template + # Detect "- platform: template" (list item) /^[[:space:]]*-[[:space:]]*platform:[[:space:]]*template/ { in_template_platform = 1 match($0, /^[[:space:]]*/) @@ -146,20 +144,20 @@ remove_legacy_templates() { # Skip lines in template platform block skip_block { - # Check if we hit next item at same or lower indent - if (/^[[:space:]]*-[[:space:]]/ || /^[[:alpha:]]/) { + # Check if we hit next list item at same or lower indent + if (/^[[:space:]]*-[[:space:]]/ || /^[a-zA-Z]/) { match($0, /^[[:space:]]*/) current_indent = RLENGTH - # If at same or lower indent, end of block - if (current_indent <= platform_indent || /^[[:alpha:]]/) { + # If at same or lower indent level, end of block + if (current_indent <= platform_indent || /^[a-zA-Z]/) { skip_block = 0 in_template_platform = 0 print next } } - # Skip this line + # Skip this line (its part of the template block) next } @@ -168,6 +166,7 @@ remove_legacy_templates() { ' "$file" > "$file.tmp" mv "$file.tmp" "$file" + echo " ${GREEN}✓${NC} Removed legacy template blocks" } ################################################################################ @@ -177,9 +176,9 @@ convert_to_modern_syntax() { local type="$1" # "sensor" or "binary_sensor" local output_file="$TEMP_DIR/modern_${type}.yaml" - echo "- ${type}:" > "$output_file" + echo " - ${type}:" > "$output_file" - # Process all extracted files + # Process all extracted files for this type for extracted in "$TEMP_DIR/${type}_"*.extracted; do [ -f "$extracted" ] || continue @@ -188,11 +187,14 @@ convert_to_modern_syntax() { BEGIN { sensor_name = "" in_sensor = 0 - indent = " " + indent = " " # 4 spaces for items under - binary_sensor:/sensor: + field_indent = " " # 6 spaces for fields + in_multiline = 0 + multiline_field = "" } - # Detect sensor name (e.g., "router_authenticated:") - /^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*:/ { + # Detect sensor name (start of new sensor) + /^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*:[[:space:]]*$/ { if (sensor_name != "") { # End previous sensor print "" @@ -203,38 +205,66 @@ convert_to_modern_syntax() { sensor_name = substr($0, RSTART, RLENGTH) print indent "- unique_id: " sensor_name in_sensor = 1 + in_multiline = 0 next } in_sensor { + # Handle multiline templates (lines starting with more indent after >) + if (in_multiline && /^[[:space:]]+[^a-zA-Z_]/) { + # Continue multiline value + print + next + } else if (in_multiline) { + in_multiline = 0 + } + # Convert field names if (/friendly_name:/) { - sub(/friendly_name:/, "name:") - print indent indent $0 + sub(/^[[:space:]]*friendly_name:/, field_indent "name:") + print } else if (/value_template:/) { - sub(/value_template:/, "state:") - print indent indent $0 + sub(/^[[:space:]]*value_template:/, field_indent "state:") + print + if (/>[[:space:]]*$/) { + in_multiline = 1 + } } else if (/icon_template:/) { - sub(/icon_template:/, "icon:") - print indent indent $0 + sub(/^[[:space:]]*icon_template:/, field_indent "icon:") + print + if (/>[[:space:]]*$/) { + in_multiline = 1 + } } else if (/entity_picture_template:/) { - sub(/entity_picture_template:/, "picture:") - print indent indent $0 + sub(/^[[:space:]]*entity_picture_template:/, field_indent "picture:") + print + if (/>[[:space:]]*$/) { + in_multiline = 1 + } } else if (/availability_template:/) { - sub(/availability_template:/, "availability:") - print indent indent $0 + sub(/^[[:space:]]*availability_template:/, field_indent "availability:") + print + if (/>[[:space:]]*$/) { + in_multiline = 1 + } } else if (/attribute_templates:/) { - sub(/attribute_templates:/, "attributes:") - print indent indent $0 + sub(/^[[:space:]]*attribute_templates:/, field_indent "attributes:") + print } else if (/^[[:space:]]+[a-zA-Z_]/) { # Keep other fields (unit_of_measurement, device_class, etc) - print indent indent $0 + # Adjust indentation to match field_indent + sub(/^[[:space:]]+/, field_indent) + print + } + else if (/^[[:space:]]+/) { + # Indented content (like multiline template continuation) + print } } ' "$extracted" >> "$output_file" @@ -256,8 +286,6 @@ add_to_configuration() { if grep -q "^template:" "$config_file"; then echo -e " ${YELLOW}ℹ${NC} Existing template: section found, appending..." - # Append to existing template section - # This is complex, so we'll add at the end and let user merge manually echo "" >> "$config_file" echo "# Migrated templates from legacy syntax ($BACKUP_TIMESTAMP)" >> "$config_file" echo "# NOTE: Merge these into your existing template: section" >> "$config_file" @@ -318,17 +346,17 @@ main() { echo -e "${BLUE}ℹ${NC} Scanning for legacy template definitions..." echo "" - # Find all YAML files + # Find all YAML files in config directory yaml_files=() - while IFS= read -r -d '' file; do - yaml_files+=("$file") - done < <(find "$CONFIG_DIR" -maxdepth 1 -name "*.yaml" -type f -print0 2>/dev/null) + for file in "$CONFIG_DIR"/*.yaml; do + [ -f "$file" ] && yaml_files+=("$file") + done # Also check packages directory if [ -d "$CONFIG_DIR/packages" ]; then - while IFS= read -r -d '' file; do - yaml_files+=("$file") - done < <(find "$CONFIG_DIR/packages" -name "*.yaml" -type f -print0 2>/dev/null) + for file in "$CONFIG_DIR/packages"/*.yaml; do + [ -f "$file" ] && yaml_files+=("$file") + done fi # Extract templates from all files @@ -361,11 +389,9 @@ main() { echo "" # Remove legacy definitions from original files - echo -e "${BLUE}ℹ${NC} Removing legacy template definitions..." for file in "${yaml_files[@]}"; do if grep -q "platform: template" "$file" 2>/dev/null; then remove_legacy_templates "$file" - echo " ${GREEN}✓${NC} Updated: $(basename "$file")" fi done echo "" @@ -383,11 +409,11 @@ main() { echo "" echo "Backup: $BACKUP_DIR" echo "Migrated: $TOTAL template entities" - echo "Files modified: $FILES_MODIFIED" + echo "Files modified: $FILES_MODIFIED (+ configuration.yaml)" echo "" echo "NEXT STEPS:" - echo "1. Check config: ha core check" - echo "2. Review configuration.yaml template: section" + echo "1. Review configuration.yaml template: section" + echo "2. Check config: ha core check" echo "3. Restart HA: ha core restart" echo "4. If issues: bash $BACKUP_DIR/restore.sh" echo ""