Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(homeassistant): add HomeAssistant backup #66

Merged
merged 2 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
COMPOSE_PROFILES=
COMPOSE_PATH_SEPARATOR=:
COMPOSE_FILE=docker-compose.yml:adguardhome/docker-compose.yml:tandoor/docker-compose.yml:joplin/docker-compose.yml
COMPOSE_FILE=docker-compose.yml:adguardhome/docker-compose.yml:tandoor/docker-compose.yml:joplin/docker-compose.yml:homeassistant/docker-compose.yml
USER_ID=1000
GROUP_ID=1000
TIMEZONE="America/New_York"
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,3 @@ docker-compose.override.yml
/adguardhome/conf
/adguardhome/work
/sabnzbd
/homeassistant
17 changes: 1 addition & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,22 +387,7 @@ See [here](./joplin/README.md).

### Home Assistant

Enable Home Assistant by setting `COMPOSE_PROFILES=homeassistant`.

Set the `HOMEASSISTANT_HOSTNAME`, since it does not support
[running in a subfolder](https://github.com/home-assistant/architecture/issues/156).
Add the necessary DNS records in your domain.

You will need to allow Traefik to access Home Assistant by adding the following in `homeassistant/configuration.yaml`:

```yaml
http:
use_x_forwarded_for: true
trusted_proxies:
- 172.0.0.0/8 # You can put a more precise range instead
```

Set the `HOMEASSISTANT_ACCESS_TOKEN` for homepage support.
See [here](./homeassistant/README.md).

## Customization

Expand Down
37 changes: 1 addition & 36 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
traefik:
image: traefik:v2.10
image: traefik:v2.11
container_name: traefik
restart: always
environment:
Expand Down Expand Up @@ -433,41 +433,6 @@ services:
- homepage.widget.type=jellyfin
- homepage.widget.url=http://jellyfin:8096/jellyfin
- homepage.widget.key=${JELLYFIN_API_KEY}
homeassistant:
image: ghcr.io/home-assistant/home-assistant:stable
container_name: homeassistant
network_mode: host
environment:
- PUID=${USER_ID}
- PGID=${GROUP_ID}
- TZ=${TIMEZONE}
volumes:
- ${CONFIG_ROOT:-.}/homeassistant:/config
- /etc/localtime:/etc/localtime:ro
- /run/dbus:/run/dbus:ro
restart: always
healthcheck:
test: [ "CMD", "curl", "--fail", "http://127.0.0.1:8123" ]
interval: 30s
retries: 10
privileged: true
labels:
- traefik.enable=true
- traefik.http.routers.homeassistant.rule=(Host(`${HOMEASSISTANT_HOSTNAME}`))
- traefik.http.routers.homeassistant.tls=true
- traefik.http.routers.homeassistant.tls.certresolver=myresolver
- traefik.http.services.homeassistant.loadbalancer.server.port=8123
- homepage.group=Apps
- homepage.name=Home Assistant
- homepage.icon=home-assistant.png
- homepage.href=https://${HOMEASSISTANT_HOSTNAME}
- homepage.description=Open source home automation that puts local control and privacy first
- homepage.weight=3
- homepage.widget.type=homeassistant
- homepage.widget.url=https://${HOMEASSISTANT_HOSTNAME}
- homepage.widget.key=${HOMEASSISTANT_ACCESS_TOKEN}
profiles:
- homeassistant
homepage:
image: ghcr.io/gethomepage/homepage:latest
container_name: homepage
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*
!README.md
!docker-compose.yml
!backup.env.example
92 changes: 92 additions & 0 deletions homeassistant/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Home Assistant

Open source home automation that puts local control and privacy first. Powered by a worldwide community of tinkerers and DIY enthusiasts

## Installation

Enable Home Assistant by setting `COMPOSE_PROFILES=homeassistant`.

Set the `HOMEASSISTANT_HOSTNAME`, since it does not support
[running in a subfolder](https://github.com/home-assistant/architecture/issues/156).
Add the necessary DNS records in your domain.

You will need to allow Traefik to access Home Assistant by adding the following in `homeassistant/configuration.yaml`:

```yaml
http:
use_x_forwarded_for: true
trusted_proxies:
- 172.0.0.0/8 # You can put a more precise range instead
```

Set the `HOMEASSISTANT_ACCESS_TOKEN` for homepage support.

## Backup

### Enable Backups in HomeAssistant

We will create an automation that will create backups nightly and clear old ones.

Add a `command_line` inclusion in your `configuration.yaml`: `command_line: !include command_lines.yaml`

The `command_lines.yaml` defines a switch that removes backups older than 7 days:

```yaml
- switch:
name: Purge old backups
unique_id: switch.purge_backups
icon: mdi:trash-can
command_on: 'cd /config/backups/ && find . -maxdepth 1 -type f -mtime +7 -print | xargs rm -f'
```

Then, create an automation that will trigger backups nightly and call the purge old backups switch:

```yaml
alias: Backup Home Assistant every night at 3 AM
description: Backup Home Assistant every night at 3 AM
trigger:
- platform: time
at: "03:00:00"
action:
- service: backup.create
data: {}
- service: switch.turn_on
data: {}
target:
entity_id: switch.purge_old_backups
- service: switch.turn_off
data: {}
target:
entity_id: switch.purge_old_backups
mode: single
```

### Save Backups Remotely

Home Assistant can be backed up in the cloud storage product of your choice with [Rclone](https://rclone.org/).

Before a backup can be made, `rclone config` must be run to generate the configuration file:

```shell
docker compose run --rm -it homeassistant-backup rclone config
```

It will generate a `rclone.conf` configuration file in ./homeassistant/rclone/rclone.conf.

Copy the backup environment file to `backup.env` and fill it as needed:
`cp backup.env.exmple backup.env`

| Variable | Description | Default |
|----------------------|---------------------------------------------------------------------|---------------------------|
| `RCLONE_REMOTE_NAME` | Name of the remote you chose during rclone config | |
| `RCLONE_REMOTE_DIR` | Name of the rclone remote dir, eg: S3 bucket name, folder name, etc | |
| `CRON` | How often to run the backup | `@daily` backup every day |
| `TIMEZONE` | Timezone, used for cron times | `America/New_York` |
| `ZIP_PASSWORD` | Password to protect the backup archive with | `123456` |
| `BACKUP_KEEP_DAYS` | How long to keep the backup in the destination | `31` days |

You can test your backup manually with:

```shell
docker compose run --rm -it homeassistant-backup backup
```
6 changes: 6 additions & 0 deletions homeassistant/backup.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
RCLONE_REMOTE_NAME=
RCLONE_REMOTE_DIR=
CRON=@daily
TIMEZONE=America/New_York
ZIP_PASSWORD=123456
BACKUP_KEEP_DAYS=1
50 changes: 50 additions & 0 deletions homeassistant/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
services:
homeassistant:
image: ghcr.io/home-assistant/home-assistant:stable
container_name: homeassistant
network_mode: host
environment:
- PUID=${USER_ID}
- PGID=${GROUP_ID}
- TZ=${TIMEZONE}
volumes:
- ${CONFIG_ROOT:-.}/homeassistant:/config
- /etc/localtime:/etc/localtime:ro
- /run/dbus:/run/dbus:ro
restart: always
healthcheck:
test: [ "CMD", "curl", "--fail", "http://127.0.0.1:8123" ]
interval: 30s
retries: 10
privileged: true
labels:
- traefik.enable=true
- traefik.http.routers.homeassistant.rule=(Host(`${HOMEASSISTANT_HOSTNAME}`))
- traefik.http.routers.homeassistant.tls=true
- traefik.http.routers.homeassistant.tls.certresolver=myresolver
- traefik.http.services.homeassistant.loadbalancer.server.port=8123
- homepage.group=Apps
- homepage.name=Home Assistant
- homepage.icon=home-assistant.png
- homepage.href=https://${HOMEASSISTANT_HOSTNAME}
- homepage.description=Open source home automation that puts local control and privacy first
- homepage.weight=3
- homepage.widget.type=homeassistant
- homepage.widget.url=https://${HOMEASSISTANT_HOSTNAME}
- homepage.widget.key=${HOMEASSISTANT_ACCESS_TOKEN}
profiles:
- homeassistant
homeassistant-backup:
image: adrienpoupa/rclone-backup:latest
container_name: homeassistant-backup
restart: always
env_file:
- ${CONFIG_ROOT:-.}/homeassistant/backup.env
environment:
- BACKUP_FOLDER_NAME=backups
- BACKUP_FOLDER_PATH=/backups
volumes:
- ${CONFIG_ROOT:-.}/homeassistant/backups:/backups
- ${CONFIG_ROOT:-.}/homeassistant/backup:/config
profiles:
- homeassistant
16 changes: 8 additions & 8 deletions joplin/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ services:
container_name: joplin
restart: always
env_file:
- ./joplin/.env
- ${CONFIG_ROOT:-.}/joplin/.env
environment:
- APP_PORT=22300
- APP_BASE_URL=https://${HOSTNAME}/joplin
Expand All @@ -14,9 +14,9 @@ services:
- SQLITE_DATABASE=/database/joplin.db
- STORAGE_DRIVER=Type=Filesystem; Path=/storage
volumes:
- ./joplin/database:/database
- ./joplin/storage:/storage
- ./joplin/healthcheck:/healthcheck
- ${CONFIG_ROOT:-.}/joplin/database:/database
- ${CONFIG_ROOT:-.}/joplin/storage:/storage
- ${CONFIG_ROOT:-.}/joplin/healthcheck:/healthcheck
healthcheck:
test: ["CMD", "node", "/healthcheck/healthcheck.js"]
interval: 30s
Expand All @@ -43,15 +43,15 @@ services:
container_name: joplin-backup
restart: always
env_file:
- ./joplin/backup.env
- ${CONFIG_ROOT:-.}/joplin/backup.env
environment:
- BACKUP_FOLDER_NAME=storage
- BACKUP_FOLDER_PATH=/storage
- DB_TYPE=sqlite
- SQLITE_DATABASE=/database/joplin.db
volumes:
- ./joplin/database:/database
- ./joplin/storage:/storage
- ./joplin/backup:/config
- ${CONFIG_ROOT:-.}/joplin/database:/database
- ${CONFIG_ROOT:-.}/joplin/storage:/storage
- ${CONFIG_ROOT:-.}/joplin/backup:/config
profiles:
- joplin
20 changes: 10 additions & 10 deletions tandoor/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ services:
container_name: tandoor
restart: always
env_file:
- ./tandoor/.env
- ${CONFIG_ROOT:-.}/tandoor/.env
volumes:
- ./tandoor/database:/opt/recipes/database
- ./tandoor/mediafiles:/opt/recipes/mediafiles
- ${CONFIG_ROOT:-.}/tandoor/database:/opt/recipes/database
- ${CONFIG_ROOT:-.}/tandoor/mediafiles:/opt/recipes/mediafiles
- tandoor-staticfiles:/opt/recipes/staticfiles
healthcheck:
test: ["CMD", "wget", "http://127.0.0.1:8080/recipes", "-qO", "/dev/null"]
Expand All @@ -21,10 +21,10 @@ services:
container_name: tandoor-nginx
restart: always
env_file:
- ./tandoor/.env
- ${CONFIG_ROOT:-.}/tandoor/.env
volumes:
- ./tandoor/nginx:/etc/nginx/conf.d:ro
- ./tandoor/mediafiles:/media:ro
- ${CONFIG_ROOT:-.}/tandoor/nginx:/etc/nginx/conf.d:ro
- ${CONFIG_ROOT:-.}/tandoor/mediafiles:/media:ro
- tandoor-staticfiles:/static:ro
healthcheck:
test: ["CMD", "wget", "http://127.0.0.1/recipes", "-qO", "/dev/null"]
Expand Down Expand Up @@ -52,16 +52,16 @@ services:
container_name: tandoor-backup
restart: always
env_file:
- ./tandoor/backup.env
- ${CONFIG_ROOT:-.}/tandoor/backup.env
environment:
- BACKUP_FOLDER_NAME=mediafiles
- BACKUP_FOLDER_PATH=/data/mediafiles
- DB_TYPE=sqlite
- SQLITE_DATABASE=/database/recipes.db
volumes:
- ./tandoor/database:/database
- ./tandoor/mediafiles:/data/mediafiles
- ./tandoor/backup:/config
- ${CONFIG_ROOT:-.}/tandoor/database:/database
- ${CONFIG_ROOT:-.}/tandoor/mediafiles:/data/mediafiles
- ${CONFIG_ROOT:-.}/tandoor/backup:/config
profiles:
- tandoor

Expand Down
32 changes: 16 additions & 16 deletions update-config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,36 @@

function update_arr_config {
echo "Updating ${container^} configuration..."
until [ -f ${CONFIG_ROOT:-.}/"$container"/config.xml ]; do sleep 1; done
sed -i.bak "s/<UrlBase><\/UrlBase>/<UrlBase>\/$1<\/UrlBase>/" ${CONFIG_ROOT:-.}/"$container"/config.xml && rm ${CONFIG_ROOT:-.}/"$container"/config.xml.bak
sed -i.bak 's/^'"${container^^}"'_API_KEY=.*/'"${1^^}"'_API_KEY='"$(sed -n 's/.*<ApiKey>\(.*\)<\/ApiKey>.*/\1/p' ${CONFIG_ROOT:-.}/"$container"/config.xml)"'/' .env && rm .env.bak
until [ -f "${CONFIG_ROOT:-.}"/"$container"/config.xml ]; do sleep 1; done
sed -i.bak "s/<UrlBase><\/UrlBase>/<UrlBase>\/$1<\/UrlBase>/" "${CONFIG_ROOT:-.}"/"$container"/config.xml && rm "${CONFIG_ROOT:-.}"/"$container"/config.xml.bak
sed -i.bak 's/^'"${container^^}"'_API_KEY=.*/'"${1^^}"'_API_KEY='"$(sed -n 's/.*<ApiKey>\(.*\)<\/ApiKey>.*/\1/p' "${CONFIG_ROOT:-.}"/"$container"/config.xml)"'/' .env && rm .env.bak
echo "Update of ${container^} configuration complete."
echo "Restarting ${container^}..."
docker compose restart "$container"
}

function update_jellyfin_config {
echo "Updating ${container^} configuration..."
until [ -f ${CONFIG_ROOT:-.}/"$container"/network.xml ]; do sleep 1; done
sed -i.bak "s/<BaseUrl \/>/<BaseUrl>\/$container<\/BaseUrl>/" ${CONFIG_ROOT:-.}/"$container"/network.xml && rm ${CONFIG_ROOT:-.}/"$container"/network.xml.bak
until [ -f "${CONFIG_ROOT:-.}"/"$container"/network.xml ]; do sleep 1; done
sed -i.bak "s/<BaseUrl \/>/<BaseUrl>\/$container<\/BaseUrl>/" "${CONFIG_ROOT:-.}"/"$container"/network.xml && rm "${CONFIG_ROOT:-.}"/"$container"/network.xml.bak
echo "Update of ${container^} configuration complete."
echo "Restarting ${container^}..."
docker compose restart "$container"
}

function update_bazarr_config {
echo "Updating ${container^} configuration..."
until [ -f ${CONFIG_ROOT:-.}/"$container"/config/config/config.yaml ]; do sleep 1; done
sed -i.bak "s/base_url: ''/base_url: '\/$container'/" ${CONFIG_ROOT:-.}/"$container"/config/config/config.yaml && rm ${CONFIG_ROOT:-.}/"$container"/config/config/config.yaml.bak
sed -i.bak "s/use_radarr: false/use_radarr: true/" ${CONFIG_ROOT:-.}/"$container"/config/config/config.yaml && rm ${CONFIG_ROOT:-.}/"$container"/config/config/config.yaml.bak
sed -i.bak "s/use_sonarr: false/use_sonarr: true/" ${CONFIG_ROOT:-.}/"$container"/config/config/config.yaml && rm ${CONFIG_ROOT:-.}/"$container"/config/config/config.yaml.bak
until [ -f ${CONFIG_ROOT:-.}/sonarr/config.xml ]; do sleep 1; done
SONARR_API_KEY=$(sed -n 's/.*<ApiKey>\(.*\)<\/ApiKey>.*/\1/p' ${CONFIG_ROOT:-.}/sonarr/config.xml)
sed -i.bak "/sonarr:/,/^radarr:/ { s/apikey: .*/apikey: $SONARR_API_KEY/; s/base_url: .*/base_url: \/sonarr/; s/ip: .*/ip: sonarr/ }" ${CONFIG_ROOT:-.}/"$container"/config/config/config.yaml && rm ${CONFIG_ROOT:-.}/"$container"/config/config/config.yaml.bak
until [ -f ${CONFIG_ROOT:-.}/radarr/config.xml ]; do sleep 1; done
RADARR_API_KEY=$(sed -n 's/.*<ApiKey>\(.*\)<\/ApiKey>.*/\1/p' ${CONFIG_ROOT:-.}/radarr/config.xml)
sed -i.bak "/radarr:/,/^sonarr:/ { s/apikey: .*/apikey: $RADARR_API_KEY/; s/base_url: .*/base_url: \/radarr/; s/ip: .*/ip: radarr/ }" ${CONFIG_ROOT:-.}/"$container"/config/config/config.yaml && rm ${CONFIG_ROOT:-.}/"$container"/config/config/config.yaml.bak
sed -i.bak 's/^'"${container^^}"'_API_KEY=.*/'"${container^^}"'_API_KEY='"$(sed -n 's/.*apikey: \(.*\)*/\1/p' ${CONFIG_ROOT:-.}/"$container"/config/config/config.yaml | head -n 1)"'/' .env && rm .env.bak
until [ -f "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml ]; do sleep 1; done
sed -i.bak "s/base_url: ''/base_url: '\/$container'/" "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml && rm "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml.bak
sed -i.bak "s/use_radarr: false/use_radarr: true/" "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml && rm "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml.bak
sed -i.bak "s/use_sonarr: false/use_sonarr: true/" "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml && rm "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml.bak
until [ -f "${CONFIG_ROOT:-.}"/sonarr/config.xml ]; do sleep 1; done
SONARR_API_KEY=$(sed -n 's/.*<ApiKey>\(.*\)<\/ApiKey>.*/\1/p' "${CONFIG_ROOT:-.}"/sonarr/config.xml)
sed -i.bak "/sonarr:/,/^radarr:/ { s/apikey: .*/apikey: $SONARR_API_KEY/; s/base_url: .*/base_url: \/sonarr/; s/ip: .*/ip: sonarr/ }" "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml && rm "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml.bak
until [ -f "${CONFIG_ROOT:-.}"/radarr/config.xml ]; do sleep 1; done
RADARR_API_KEY=$(sed -n 's/.*<ApiKey>\(.*\)<\/ApiKey>.*/\1/p' "${CONFIG_ROOT:-.}"/radarr/config.xml)
sed -i.bak "/radarr:/,/^sonarr:/ { s/apikey: .*/apikey: $RADARR_API_KEY/; s/base_url: .*/base_url: \/radarr/; s/ip: .*/ip: radarr/ }" "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml && rm "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml.bak
sed -i.bak 's/^'"${container^^}"'_API_KEY=.*/'"${container^^}"'_API_KEY='"$(sed -n 's/.*apikey: \(.*\)*/\1/p' "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml | head -n 1)"'/' .env && rm .env.bak
echo "Update of ${container^} configuration complete."
echo "Restarting ${container^}..."
docker compose restart "$container"
Expand Down
Loading