Files
ha-template-migration/migrate_templates.sh
jessikitty ec2062872c 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.
2025-12-23 12:53:00 +11:00

425 lines
14 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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