Updating Home Assistant in Docker from Lovelace UI just like in Home Assistant OS

Updating Home Assistant in Docker from Lovelace UI just like in Home Assistant OS

The Docker installation method becomes very popular among Home Assistant users. One of the downsides of the Docker installation method is the absence of update notifications and updates with a single button click. Today we will try to fix this with the power of Named Pipes (Whaaat??).

Disclaimer. This is a little bit of an advanced and not the best solution, but it shows an example of how you could access the host system without providing a full docker socket inside the container or running the Home Assistant container in a privileged mode. You could always use Portainer or Watchtower to do the same things without writing any scripts. Or look into a Supervised install.

So, what the hell is Named Pipes? It is a concept of inter-process communication in Unix-like operating systems. I’m gonna use my Debian home server for this “experiment”, but this method should work on any GNU/Linux and maybe even on macOS (not sure about that).

1. Creating named pipe

First, let’s create a named pipe on our home server. You can do this with the next command:

mkfifo /root/pipes/hapipe

/root/pipes/hapipe – is a path and a name of your future named pipe.

Now, we need to create a bash script that will start listening to commands from our named pipe. I’ve created a /root/scripts/hapipe_listener.sh with the next content:

#!/bin/bash
while true; do eval "$(cat /root/pipes/hapipe)"; done

And made it executable:

chmod +x /root/scripts/hapipe_listener.sh

Also, we need to make sure the listener will work even after the host restart, so we should edit our crontab with:

crontab -e

And add the next line to it:

@reboot /root/scripts/hapipe_listener.sh

2. Testing named pipe

Now we can test if it works. We could temporarily edit our /root/scripts/hapipe_listener.sh to write any commands output to a file. We even could do it permanently to have some log of our pipe commands:

#!/bin/bash
while true; do eval "$(cat /root/pipes/hapipe)" &> /root/logs/hapipe.log; done

You can reboot your server with a simple reboot command and then put some commands into your pipe. For example:

echo "ls -l" > /root/pipes/hapipe

I’ve got an output into a /root/logs/hapipe.log of an ls command, so it works for me:

total 40
-rw-r--r-- 1 root root 1897 Jan 10 13:37 docker-compose.yaml
drwxr-xr-x 3 root root 4096 Dec 30 20:41 esphome
drwxr-xr-x 4 root root 4096 Dec 30 20:42 homeassistant
drwxr-xr-x 4 root root 4096 Jan  6 18:25 homepage
drwxr-xr-x 2 root root 4096 Jan 14 12:52 logs
drwxr-xr-x 5 1883 1883 4096 Dec 30 20:51 mosquitto
drwxr-xr-x 2 root root 4096 Jan 14 11:33 pipes
drwxr-xr-x 2 root root 4096 Jan 14 11:35 scripts
drwxr-xr-x 3 root root 4096 Jan  6 18:42 tmp
drwxr-xr-x 3 root root 4096 Dec 30 20:42 zigbee2mqtt

3. Home Assistant docker update script

You already can see, where are we moving, right? Let’s create another script on our host at /root/scripts/ha_update.sh with the next content:

#!/bin/bash
docker compose -f /root/docker-compose.yaml pull homeassistant &> /root/logs/ha_pull.log

docker compose -f /root/docker-compose.yaml up -d homeassistant &> /root/logs/ha_up.log

/root/docker-compose.yaml is a path to my docker-compose.yaml where homeassistant is defined as service. You can see an example here. The first command will pull the latest image, and the second one will re-create the docker container with the new image.

4. Adding named pipe to Docker

Now we need to make our named pipe available in the Home Assistant Docker container. We can do this by adding a new volume for homeassistant service in our docker-compose.yaml:

    volumes:
      ...
      - /root/pipes/hapipe:/hostpipe

To make things work, we need to re-create a container with

docker compose up -d homeassistant

5. Executing host commands from Home Assistant

Now we can write commands to a named pipe in Home Assistant container and they would be executed on a host. We could do this with Shell command integration. Let’s test our pipe by adding this to a configuration.yaml and restarting our Home Assistant:

shell_command:
  test_host_pipe: echo "ls -l" > /hostpipe

Then go to the Developer Tools > Services and try to call our newly created test_host_pipe service:

We should be able to see the output of ls -l command in our host’s /root/logs/hapipe.log:

total 40
-rw-r--r-- 1 root root 1934 Jan 14 13:07 docker-compose.yaml
drwxr-xr-x 3 root root 4096 Dec 30 20:41 esphome
drwxr-xr-x 4 root root 4096 Dec 30 20:42 homeassistant
drwxr-xr-x 4 root root 4096 Jan  6 18:25 homepage
drwxr-xr-x 2 root root 4096 Jan 14 12:52 logs
drwxr-xr-x 5 1883 1883 4096 Dec 30 20:51 mosquitto
drwxr-xr-x 2 root root 4096 Jan 14 11:33 pipes
drwxr-xr-x 2 root root 4096 Jan 14 12:57 scripts
drwxr-xr-x 3 root root 4096 Jan  6 18:42 tmp
drwxr-xr-x 3 root root 4096 Dec 30 20:42 zigbee2mqtt

It means that now we could create another service that will call our previously created ha_update.sh script on a host. Let’s do this by updating configuration.yaml again:

shell_command:
  test_host_pipe: echo "ls -l" > /hostpipe
  ha_update: echo "/root/scripts/ha_update.sh" > /hostpipe

After Home Assistant restart we could call that service from Developer Tools > Services and if there is a new image available for Home Assistant, this will result in an update and restart of our Home Assistant instance:

We could check the result on our host in /root/logs/ha_up.log. It should look something like this:

Container mariadb  Running
Container homeassistant  Recreate
Container homeassistant  Recreated
Container homeassistant  Starting
Container homeassistant  Started

6. Tracking Home Assistant versions and automating

6.1 Versions tracking

The job is almost done. You could create a button now in your Lovelace UI to call the update service. Just don’t forget to use confirmation for it. Just in case.

But I also want to be notified when there is an update available and start the update process right from the notification. I also want to be able to skip certain versions. So let’s do this.

First, we need to create a helper to store the version we want to skip. Go to Settings > Devices & Services > Helpers tab, click Create Helper and choose “Text”:

Next, to track current and available versions, we will use Version integration. From Settings > Devices & Services > Integrations tab click Add Integration, and search for “Version”:

Select “Home Assistant Versions” as the Version source and then choose to track generic image from stable channel:

The image and board values depend on the platform you are using. I’m running on an x86-64 Intell Nuc, so I want to track the corresponding images.

Then, we need to track our current version as well, so once again from Settings > Devices & Services > Integrations tab click Add Integration, and search for “Version”. Then choose “Local installation”:

At last, we also need to add a Version integration with the “Home Assistant Website” source, because the sensor from this integration will have a release_notes attribute with the link to release notes. We couldn’t use the “Home Assistant Website” source for the new version check, because sometimes the new version on the site could be already announced, but the image for your platform could be not ready yet.

We now have three instances of Version integration set up:

Now let’s create automation.

6.3 Automation UI

6.3.1 Triggers

For triggers, I choose to use the Home Assistant version state change,

Home Assistant start:

And the time trigger for every day at 8:00 in case I dismissed all notifications by accident and forgot about the new version:

I also added triggers to handle both notification actions:

6.3.2 Actions

Then, in the actions, I have a single Choose block with four options.

The first option checks trigger ids and make sure that we have a new version and not the “Unavailable” state of a sensor. It also checks if current version is not the one we want to skip:

Triggers check
Version check

Here is a code of both templates:

{{states('sensor.home_assistant_versions') != states('input_text.home_assistant_version_skip')}}
{{states('sensor.home_assistant_versions') != states('sensor.current_version')}}
Make sure the version is valid

And fires two notifications. One is for my mobile phone, and the second – is a persistent one to display in HA UI. Here is the YAML code, of both actions because it has templates:

data:
  title: Update available
  message: >-
    New Home Assistant version available:
    {{states('sensor.home_assistant_versions')}}. Current version:
    {{states('sensor.current_version')}}
  data:
    tag: ha_updates
    group: server
    actions:
      - action: URI
        title: Release notes
        uri: "{{state_attr('sensor.home_assistant_website', 'release_notes')}}"
      - action: update_ha
        title: Update
      - action: skip_ha_release
        title: Skip
service: notify.mobile_app_sm_g996b

We need a tag here to clear the notification later.

service: persistent_notification.create
data:
  title: Update available
  message: >-
    New Home Assistant version available:    
    {{states('sensor.home_assistant_versions')}}. Current version:    
    {{states('sensor.current_version')}}
  notification_id: ha_updates

notification_id here is also for clearing the notification.

The second option is for removing the notifications. It checks whether the automation was triggered by Home Assistant start and compares current and available versions:

Template condition YAML:

{{states('sensor.home_assistant_versions') == states('sensor.current_version')}}

Removes the persistent notification:

And the notification on my mobile phone:

The third option is simple but most important – the UPDATE:

It also removes the persistent notification. The mobile notification will be removed automatically by clicking “Update” on it.

And the fourth option is for remembering the release we want to skip and removing the persistent notification as well.

6.4 Automation YAML

Here is a full YAML for the automation:

alias: "Update :: HA"
description: ""
trigger:
  - entity_id:
      - sensor.home_assistant_versions
    platform: state
    id: haWebsiteVersionChange
  - platform: homeassistant
    event: start
    id: haStart
  - platform: event
    event_type: mobile_app_notification_action
    event_data:
      action: skip_ha_release
    id: skipRelease
  - platform: time
    at: "08:00:00"
    id: time
  - platform: event
    event_type: mobile_app_notification_action
    event_data:
      action: update_ha
    id: update
condition: []
action:
  - choose:
      - conditions:
          - condition: or
            conditions:
              - condition: trigger
                id: haWebsiteVersionChange
              - condition: trigger
                id: haStart
              - condition: trigger
                id: time
          - condition: template
            value_template: >-
              {{states('sensor.home_assistant_versions') !=
              states('input_text.home_assistant_version_skip')}}
          - condition: template
            value_template: >-
              {{states('sensor.home_assistant_versions') !=
              states('sensor.current_version')}}
          - condition: not
            conditions:
              - condition: state
                entity_id: sensor.home_assistant_versions
                state: unavailable
              - condition: state
                entity_id: sensor.home_assistant_versions
                state: unknown
        sequence:
          - data:
              title: Update available
              message: >-
                New Home Assistant version available:
                {{states('sensor.home_assistant_versions')}}. Current version:
                {{states('sensor.current_version')}}
              data:
                tag: ha_updates
                group: server
                actions:
                  - action: URI
                    title: Release notes
                    uri: >-
                      {{state_attr('sensor.home_assistant_website',
                      'release_notes')}}
                  - action: update_ha
                    title: Оновити
                  - action: skip_ha_release
                    title: Пропустити
            service: notify.mobile_app_sm_g996b
          - service: persistent_notification.create
            data:
              title: Update available
              message: >-
                New Home Assistant version available:    
                {{states('sensor.home_assistant_versions')}}. Current
                version:     {{states('sensor.current_version')}}
              notification_id: ha_updates
      - conditions:
          - condition: trigger
            id: haStart
          - condition: template
            value_template: >-
              {{states('sensor.home_assistant_versions') ==
              states('sensor.current_version')}}
        sequence:
          - service: persistent_notification.dismiss
            data:
              notification_id: ha_updates
          - service: notify.mobile_app_sm_g996b
            data:
              message: clear_notification
              data:
                tag: ha_updates
      - conditions:
          - condition: trigger
            id: update
        sequence:
          - service: shell_command.ha_update
            data: {}
          - service: persistent_notification.dismiss
            data:
              notification_id: ha_updates
      - conditions:
          - condition: trigger
            id: skipRelease
        sequence:
          - service: input_text.set_value
            data:
              value: "{{states('sensor.home_assistant_website')}}"
            target:
              entity_id: input_text.home_assistant_version_skip
          - service: persistent_notification.dismiss
            data:
              notification_id: ha_updates
mode: queued
max: 10

Enjoy!