Skip to content

Instantly share code, notes, and snippets.

@AkdM
Last active December 6, 2025 19:07
Show Gist options
  • Select an option

  • Save AkdM/70f3f600356b3b834ae0290bd6f741b3 to your computer and use it in GitHub Desktop.

Select an option

Save AkdM/70f3f600356b3b834ae0290bd6f741b3 to your computer and use it in GitHub Desktop.
Daily backup Home Assistant configuration into a git repository
# You can whitelist files/folders with !, those will not be ignored.
# Everything that starts with a / are for root elements
# ignore
/custom_components/
/zigbee2mqtt/log
/zigbee2mqtt/state.json
/home-assistant_v2.* # Exclude Home Assistant history-related database. Make sure to enable git LFS if you don't exclude that, since those files can go easily over 100MB
/home-assistant.log*
/.ssh/
.storage
# keep
!.gitignore
!.HA_VERSION

Daily backup Home Assistant with git

As always, please CONSTANTLY read and UNDERSTAND what you copy and run on the Internet. Stay safe!

  • mkdir a .ssh dir to the root of the config folder (should be /root/homeassistant)
  • ssh-keygen to that directory. I've used ssh-keygen -t rsa -b 4096 -C "[email protected]". I will not cover the usage of a passphrase here.
  • Copy /root/homeassistant/.ssh/id_rsa.pub content to your Settings ➜ Deploy keys page of your Github repo
  • Because the shell command won't (obviously) have access to all of the HA instance, cd to your ha configuration directory and run that command to target the newly generated id_rsa file:
git config core.sshCommand "ssh -i /config/.ssh/id_rsa -o 'StrictHostKeyChecking=no' -F /dev/null"
  • Copy backup.sh to the HA configuration folder
  • Create a .gitignore file into the HA configuration folder and add the needed exceptions (I've provided mine)
  • Copy the automation content (automation.yaml), create a new automation and paste the content. You can also go to the automations.yaml and write it here if you're more comfortable. I've set the automation to automatically run at 2:00 am everday. Feel free to change it as you like. Also I like to be notified on the app, using the native notify service.
# I've included the notify service to let you know when the backup is successful or not.
# In the long term I felt like having the successful notification wasn't needed so I removed it.
# Feel free to tweak that, and why not share what you did :)
alias: Daily Backup
description: "Backup Home Assistant configuration folder"
trigger:
- platform: time
at: "02:00:00"
condition: []
action:
- service: shell_command.backup
data: {}
response_variable: backup_response
- if:
- condition: template
value_template: "{{ backup_response['returncode'] == 0 }}"
then:
- service: notify.notify
data:
title: βœ… Backup successful
message: "{{ backup_response['stdout'] }}"
else:
- service: notify.notify
data:
title: ❌ Backup failed
message: "{{ backup_response['stderr'] }}"
mode: single
#!/bin/sh
# To run before
# git config core.sshCommand "ssh -i /config/.ssh/id_rsa -o 'StrictHostKeyChecking=no' -F /dev/null"
HA_VERSION=`cat .HA_VERSION`
COMMIT_CURRENT_DATE=$(date +'%d-%m-%Y %H:%M:%S')
COMMIT_MESSAGE="[$HA_VERSION]: $COMMIT_CURRENT_DATE"
echo "$COMMIT_MESSAGE"
git add .
git commit -m "$COMMIT_MESSAGE"
git push
# add the following into your configuration.yaml file
# …
shell_command:
backup: bash /config/backup.sh
# …
@wjcarpenter
Copy link

@AkdM Thanks so much for doing this write-up. It saved me a lot of poking around to figure out the pieces myself.

@bcjmk
Copy link

bcjmk commented Oct 11, 2025

I had a number of challenges getting this to work under HAOS. Apparently either ROOT or the ssh_config is a lot more restrictive. Anyway, had to make the following updates:

  1. Added id_rsa_git and known_hosts_git to /config/.ssh
  2. updated the GIT SSH command line to only use these 2 files

BTW - ssh -T [email protected] will work from the CLI, but not always in a script

# Add the GIT public keys to the known_hosts file from your directory
ssh-keyscan github.com >> /config/.ssh/known_hosts_git

# Test the config
ssh -i /config/.ssh/id_rsa_git -o UserKnownHostsFile=/config/.ssh/known_hosts_git -T [email protected]

# Update the GIT SSH command
# Use "-F /dev/null" to eliminate any global configuration settings that will screw things up
git config core.sshCommand "ssh -i /config/.ssh/id_rsa_git -o UserKnownHostsFile=/config/.ssh/known_hosts_git -F /dev/null"

@bcjmk
Copy link

bcjmk commented Oct 11, 2025

Also, had to tweak the .gitignore to include Lovelace. here is my current incarnation:

# WARNING: Make your GitHub repo Private if you are using this as it is
# Example .gitignore file for your config dir.

# An * ensures that everything will be ignored.
*

# You can whitelist files/folders with !, these will not be ignored.
!*.yaml
!.gitignore
!*.md
!*.sh
!*.js*
!*.py

# Comment these 2 lines if you don't want to include your SSH keys
!.ssh/
!id_rsa*

# Capture various custom script/flow directories
!node-red/
!python_scripts/
!shell_scripts/

# Capture lovelace dashboards
# Have to enable the folder first, then the individual files
# Lovelace files are NOT .yaml, so include anything that matches
!.storage/
!.storage/lovelace*

# Uncomment this if you don' want to include secrets.yaml 
# secrets.yaml

@mawmawmawm
Copy link

I changed the commit message a bit so it's more informative and give a list of entities and things going on.

backup.sh:

#!/bin/bash
set -e

# --------------------------
# Configuration
# --------------------------
HA_VERSION=$(cat .HA_VERSION)
COMMIT_DATE=$(date +'%d-%m-%Y %H:%M:%S')
REGISTRY="/config/.storage/core.entity_registry"

# --------------------------
# Entity counts by domain
# --------------------------
TOTAL_ENTITIES=$(jq '.data.entities | length' "$REGISTRY")
LIGHTS=$(jq '[.data.entities[] | select(.entity_id | startswith("light."))] | length' "$REGISTRY")
SWITCHES=$(jq '[.data.entities[] | select(.entity_id | startswith("switch."))] | length' "$REGISTRY")
SENSORS=$(jq '[.data.entities[] | select(.entity_id | startswith("sensor."))] | length' "$REGISTRY")
AUTOMATIONS=$(jq '[.data.entities[] | select(.entity_id | startswith("automation."))] | length' "$REGISTRY")
SCRIPTS=$(jq '[.data.entities[] | select(.entity_id | startswith("script."))] | length' "$REGISTRY")
SCENES=$(jq '[.data.entities[] | select(.entity_id | startswith("scene."))] | length' "$REGISTRY")

# --------------------------
# Compose commit message
# --------------------------
COMMIT_MESSAGE="[$HA_VERSION] πŸ“… $COMMIT_DATE

πŸ“¦ Entities: $TOTAL_ENTITIES
πŸ’‘ Lights: $LIGHTS
πŸ”€ Switches: $SWITCHES
🌑️ Sensors: $SENSORS
βš™οΈ Automations: $AUTOMATIONS
πŸ“œ Scripts: $SCRIPTS
🎭 Scenes: $SCENES"

# --------------------------
# Commit and push
# --------------------------
echo -e "$COMMIT_MESSAGE"

git add .
git commit -m "$(echo -e "$COMMIT_MESSAGE")"
git push

@bcjmk thanks for your ignore file, however /blueprints is missing and should be added:

# Capture various custom script/flow directories
!blueprints/
!blueprints/*
!blueprints/*/*

@AkdM
Copy link
Author

AkdM commented Oct 31, 2025

Nice improvement @mawmawmawm, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment