#!/bin/bash ################################################################################ # Home Assistant Legacy Template Migration v2 (Bash) # Handles templates in included files (sensors.yaml, binary_sensors.yaml) ################################################################################ set -e # Exit on error # Configuration CONFIG_DIR="/config" BACKUP_TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_DIR="$CONFIG_DIR/backups/template_migration_$BACKUP_TIMESTAMP" TEMP_DIR="/tmp/template_migration_$$" # Colors GREEN='\033[0;32m' BLUE='\033[0;34m' YELLOW='\033[1;33m' RED='\033[0;31m' NC='\033[0m' # Counters TOTAL_SENSORS=0 TOTAL_BINARY_SENSORS=0 FILES_MODIFIED=0 echo "======================================================================" echo "Home Assistant Legacy Template Migration v2 (Bash)" echo "======================================================================" echo "" # Create temp directory mkdir -p "$TEMP_DIR" trap "rm -rf $TEMP_DIR" EXIT # Create backup directory mkdir -p "$BACKUP_DIR" echo -e "${GREEN}✓${NC} Created backup: $BACKUP_DIR" echo "" ################################################################################ # Extract legacy template sensors from a file ################################################################################ extract_legacy_templates() { local file="$1" local type="$2" # "sensor" or "binary_sensor" local output_file="$TEMP_DIR/${type}_$(basename "$file").extracted" # Check if file has legacy templates if ! grep -q "platform: template" "$file" 2>/dev/null; then return fi echo -e "${BLUE}ℹ${NC} Processing $(basename "$file") for ${type}..." # Extract all sensor definitions from platform: template blocks awk ' BEGIN { in_platform = 0 in_sensors = 0 platform_indent = 0 } # Detect "- platform: template" or "platform: template" /^[[:space:]]*-?[[:space:]]*platform:[[:space:]]*template/ { in_platform = 1 match($0, /^[[:space:]]*/) platform_indent = RLENGTH next } # Look for "sensors:" within platform block in_platform && /^[[:space:]]*sensors:/ { in_sensors = 1 next } # Extract sensor definitions 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 (indented under sensors:) if (/^[[:space:]]+[a-zA-Z_]/) { print } } ' "$file" > "$output_file" if [ -s "$output_file" ]; then # 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 TOTAL_SENSORS=$((TOTAL_SENSORS + count)) else TOTAL_BINARY_SENSORS=$((TOTAL_BINARY_SENSORS + count)) fi # 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 platform blocks from a file ################################################################################ remove_legacy_templates() { local file="$1" if [ ! -f "$file" ]; then return fi echo -e "${BLUE}ℹ${NC} Removing legacy templates from $(basename "$file")..." # Use awk to remove entire platform: template blocks awk ' BEGIN { in_template_platform = 0 platform_indent = 0 skip_block = 0 } # Detect "- platform: template" (list item) /^[[:space:]]*-[[:space:]]*platform:[[:space:]]*template/ { in_template_platform = 1 match($0, /^[[:space:]]*/) platform_indent = RLENGTH skip_block = 1 next } # Skip lines in template platform block skip_block { # 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 level, end of block if (current_indent <= platform_indent || /^[a-zA-Z]/) { skip_block = 0 in_template_platform = 0 print next } } # Skip this line (its part of the template block) next } # Print non-skipped lines { print } ' "$file" > "$file.tmp" mv "$file.tmp" "$file" echo " ${GREEN}✓${NC} Removed legacy template blocks" } ################################################################################ # Convert extracted templates to modern syntax ################################################################################ convert_to_modern_syntax() { local type="$1" # "sensor" or "binary_sensor" local output_file="$TEMP_DIR/modern_${type}.yaml" echo " - ${type}:" > "$output_file" # Process all extracted files for this type for extracted in "$TEMP_DIR/${type}_"*.extracted; do [ -f "$extracted" ] || continue # Parse each sensor and convert to modern syntax awk -v type="$type" ' BEGIN { sensor_name = "" in_sensor = 0 indent = " " # 4 spaces for items under - binary_sensor:/sensor: field_indent = " " # 6 spaces for fields in_multiline = 0 multiline_field = "" } # Detect sensor name (start of new sensor) /^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*:[[:space:]]*$/ { if (sensor_name != "") { # End previous sensor print "" } # Start new sensor match($0, /[a-zA-Z_][a-zA-Z0-9_]*/) 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(/^[[:space:]]*friendly_name:/, field_indent "name:") print } else if (/value_template:/) { sub(/^[[:space:]]*value_template:/, field_indent "state:") print if (/>[[:space:]]*$/) { in_multiline = 1 } } else if (/icon_template:/) { sub(/^[[:space:]]*icon_template:/, field_indent "icon:") print if (/>[[:space:]]*$/) { in_multiline = 1 } } else if (/entity_picture_template:/) { sub(/^[[:space:]]*entity_picture_template:/, field_indent "picture:") print if (/>[[:space:]]*$/) { in_multiline = 1 } } else if (/availability_template:/) { sub(/^[[:space:]]*availability_template:/, field_indent "availability:") print if (/>[[:space:]]*$/) { in_multiline = 1 } } else if (/attribute_templates:/) { sub(/^[[:space:]]*attribute_templates:/, field_indent "attributes:") print } else if (/^[[:space:]]+[a-zA-Z_]/) { # Keep other fields (unit_of_measurement, device_class, etc) # Adjust indentation to match field_indent sub(/^[[:space:]]+/, field_indent) print } else if (/^[[:space:]]+/) { # Indented content (like multiline template continuation) print } } ' "$extracted" >> "$output_file" done } ################################################################################ # Add modern templates to configuration.yaml ################################################################################ add_to_configuration() { local config_file="$CONFIG_DIR/configuration.yaml" echo -e "\n${BLUE}ℹ${NC} Adding modern template definitions to configuration.yaml..." # Backup configuration.yaml cp "$config_file" "$BACKUP_DIR/configuration.yaml" # Check if template: section already exists if grep -q "^template:" "$config_file"; then echo -e " ${YELLOW}ℹ${NC} Existing template: section found, appending..." echo "" >> "$config_file" echo "# Migrated templates from legacy syntax ($BACKUP_TIMESTAMP)" >> "$config_file" echo "# NOTE: Merge these into your existing template: section" >> "$config_file" echo "template:" >> "$config_file" [ -f "$TEMP_DIR/modern_sensor.yaml" ] && cat "$TEMP_DIR/modern_sensor.yaml" >> "$config_file" [ -f "$TEMP_DIR/modern_binary_sensor.yaml" ] && cat "$TEMP_DIR/modern_binary_sensor.yaml" >> "$config_file" echo -e " ${YELLOW}⚠${NC} Manual merge required - see end of configuration.yaml" else echo -e " ${GREEN}✓${NC} Adding new template: section..." echo "" >> "$config_file" echo "# Modern template syntax (migrated $BACKUP_TIMESTAMP)" >> "$config_file" echo "template:" >> "$config_file" [ -f "$TEMP_DIR/modern_sensor.yaml" ] && cat "$TEMP_DIR/modern_sensor.yaml" >> "$config_file" [ -f "$TEMP_DIR/modern_binary_sensor.yaml" ] && cat "$TEMP_DIR/modern_binary_sensor.yaml" >> "$config_file" fi echo -e " ${GREEN}✓${NC} Updated configuration.yaml" } ################################################################################ # Create restore script ################################################################################ create_restore_script() { local restore_script="$BACKUP_DIR/restore.sh" cat > "$restore_script" << 'EOF' #!/bin/bash echo "Restoring from template migration backup..." SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONFIG_DIR="/config" # Restore all backed up files for file in "$SCRIPT_DIR"/*.yaml; do [ -f "$file" ] || continue filename=$(basename "$file") cp "$file" "$CONFIG_DIR/$filename" echo "✓ Restored: $filename" done echo "" echo "Files restored!" echo "Restart Home Assistant: ha core restart" EOF chmod +x "$restore_script" echo -e "\n${GREEN}✓${NC} Restore script created: $restore_script" } ################################################################################ # Main migration process ################################################################################ main() { echo -e "${BLUE}ℹ${NC} Scanning for legacy template definitions..." echo "" # Find all YAML files in config directory yaml_files=() for file in "$CONFIG_DIR"/*.yaml; do [ -f "$file" ] && yaml_files+=("$file") done # Also check packages directory if [ -d "$CONFIG_DIR/packages" ]; then for file in "$CONFIG_DIR/packages"/*.yaml; do [ -f "$file" ] && yaml_files+=("$file") done fi # Extract templates from all files for file in "${yaml_files[@]}"; do extract_legacy_templates "$file" "sensor" extract_legacy_templates "$file" "binary_sensor" done # Check if we found anything if [ $TOTAL_SENSORS -eq 0 ] && [ $TOTAL_BINARY_SENSORS -eq 0 ]; then echo -e "\n${GREEN}✓${NC} No legacy template definitions found!" echo "All your templates are already using modern syntax!" rm -rf "$BACKUP_DIR" exit 0 fi TOTAL=$((TOTAL_SENSORS + TOTAL_BINARY_SENSORS)) echo "" echo -e "${BLUE}ℹ${NC} Found $TOTAL legacy template entities:" echo " - $TOTAL_SENSORS sensor(s)" echo " - $TOTAL_BINARY_SENSORS binary_sensor(s)" echo "" # Convert to modern syntax echo -e "${BLUE}ℹ${NC} Converting to modern template: syntax..." [ $TOTAL_SENSORS -gt 0 ] && convert_to_modern_syntax "sensor" [ $TOTAL_BINARY_SENSORS -gt 0 ] && convert_to_modern_syntax "binary_sensor" echo -e " ${GREEN}✓${NC} Conversion complete" echo "" # Remove legacy definitions from original files for file in "${yaml_files[@]}"; do if grep -q "platform: template" "$file" 2>/dev/null; then remove_legacy_templates "$file" fi done echo "" # Add modern templates to configuration.yaml add_to_configuration # Create restore script create_restore_script echo "" echo "======================================================================" echo "MIGRATION COMPLETE" echo "======================================================================" echo "" echo "Backup: $BACKUP_DIR" echo "Migrated: $TOTAL template entities" echo "Files modified: $FILES_MODIFIED (+ configuration.yaml)" echo "" echo "NEXT STEPS:" 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 "" echo "======================================================================" } # Run main function main