From 288b050a339ce6b31885c7ad50978c30aa1e7ecc Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Mon, 12 Jul 2021 16:14:57 -0500 Subject: [PATCH] roles/homeassistant: Deploy container with Podman Installing Home Assistant in a Python virtualenv is rather tedious, especially on non-x86 machines. The main issue is Python packages that include native extensions, as many of these do not have binary wheels available for aarch64, etc. on PyPI. Thus, to install these, they have to be built from source, which then requires the appropriate development packages to be installed. Additionally, compiling native code on a Raspberry Pi is excruciatingly slow. I have considered various ways of mitigating this, but all would require a substantial time investment, both up front and ongoing, making them rather pointless. Eventually, I settled on just deploying the official Home Assistant container image with Podman. Although Podman includes a tool for generating systemd service unit files for running containers, I ended up creating my own for several reasons. First and foremost, the generated unit files configure the containers to run as *root*, but I wanted to run Home Assistant as an unprivileged user. Unfortunately, I could not seem to get the container to work when dropping privileges using the `User` directive of the unit. Fortunately, `podman` has `--uidmap` and `--gidmap` arguments, which I was able to use to map UID/GID 0 in the container to the *homeassistant* user on the host. Another drawback of the generated unit files is that they specify a "forking" type service, which is not really necessary. Podman/conmon supports the systemd notify protocol, but the generator has not been updated to make use of that yet. Recent versions of Home Assistant are more strict with respect to how reverse proxies are handled. In order to use one, it must be explicitly listed in the configuration file. Therefore, the *homeassistant* Ansible role will now create a stub `configuration.yaml`, based on the one generated by Home Assistant itslf when it starts for the first time on a new machine, that includes the appropriate configuration for a reverse proxy running on the same machine. The stub configuration will not overwrite an existing configuration file, so it is only useful when deploying Home Assistant for the first time on a new machine. Overall, although I think a 300+ MB container image is ridiculous, deploying Home Assistant this way should make it a lot easier to manage, especially when updating. --- homeassistant.yml | 3 +- roles/homeassistant/defaults/main.yml | 1 + roles/homeassistant/files/hass.sh | 3 - .../homeassistant/files/homeassistant.service | 13 --- roles/homeassistant/files/http.yaml | 4 + .../files/stubs/automations.yaml | 1 + .../files/stubs/configuration.yaml | 14 +++ roles/homeassistant/files/stubs/groups.yaml | 0 roles/homeassistant/files/stubs/scenes.yaml | 0 roles/homeassistant/files/stubs/scripts.yaml | 0 roles/homeassistant/files/stubs/secrets.yaml | 1 + roles/homeassistant/tasks/main.yml | 95 +++++++++++-------- .../templates/configuration.yaml.j2 | 37 -------- .../templates/homeassistant.service.j2 | 24 +++++ roles/homeassistant/vars/aarch64.yml | 1 + roles/homeassistant/vars/armv7l.yml | 13 +-- roles/homeassistant/vars/defaults.yml | 2 +- roles/homeassistant/vars/main.yml | 5 +- 18 files changed, 109 insertions(+), 108 deletions(-) create mode 100644 roles/homeassistant/defaults/main.yml delete mode 100644 roles/homeassistant/files/hass.sh delete mode 100644 roles/homeassistant/files/homeassistant.service create mode 100644 roles/homeassistant/files/http.yaml create mode 100644 roles/homeassistant/files/stubs/automations.yaml create mode 100644 roles/homeassistant/files/stubs/configuration.yaml create mode 100644 roles/homeassistant/files/stubs/groups.yaml create mode 100644 roles/homeassistant/files/stubs/scenes.yaml create mode 100644 roles/homeassistant/files/stubs/scripts.yaml create mode 100644 roles/homeassistant/files/stubs/secrets.yaml delete mode 100644 roles/homeassistant/templates/configuration.yaml.j2 create mode 100644 roles/homeassistant/templates/homeassistant.service.j2 create mode 100644 roles/homeassistant/vars/aarch64.yml diff --git a/homeassistant.yml b/homeassistant.yml index 03ac68f..4e103dd 100644 --- a/homeassistant.yml +++ b/homeassistant.yml @@ -1,7 +1,8 @@ - hosts: home-assistant roles: - apache - - homeassistant + - role: homeassistant + tags: homeassistant - role: mosquitto tags: mosquitto tasks: diff --git a/roles/homeassistant/defaults/main.yml b/roles/homeassistant/defaults/main.yml new file mode 100644 index 0000000..901d523 --- /dev/null +++ b/roles/homeassistant/defaults/main.yml @@ -0,0 +1 @@ +homeassistant_image_name: '{{ homeassistant_default_image_name }}' diff --git a/roles/homeassistant/files/hass.sh b/roles/homeassistant/files/hass.sh deleted file mode 100644 index 7969908..0000000 --- a/roles/homeassistant/files/hass.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -exec /usr/local/homeassistant/bin/hass diff --git a/roles/homeassistant/files/homeassistant.service b/roles/homeassistant/files/homeassistant.service deleted file mode 100644 index 3248908..0000000 --- a/roles/homeassistant/files/homeassistant.service +++ /dev/null @@ -1,13 +0,0 @@ -# vim: set ft=systemd : -[Unit] -Description=Home Assistant - -[Service] -Type=simple -Environment=TMPDIR=/var/lib/homeassistant/tmp -ExecStart=/usr/local/bin/hass -User=homeassistant -UMask=0077 - -[Install] -WantedBy=multi-user.target diff --git a/roles/homeassistant/files/http.yaml b/roles/homeassistant/files/http.yaml new file mode 100644 index 0000000..5311045 --- /dev/null +++ b/roles/homeassistant/files/http.yaml @@ -0,0 +1,4 @@ +server_host: '::1' +trusted_proxies: +- '::1' +use_x_forwarded_for: true diff --git a/roles/homeassistant/files/stubs/automations.yaml b/roles/homeassistant/files/stubs/automations.yaml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/roles/homeassistant/files/stubs/automations.yaml @@ -0,0 +1 @@ +[] diff --git a/roles/homeassistant/files/stubs/configuration.yaml b/roles/homeassistant/files/stubs/configuration.yaml new file mode 100644 index 0000000..69a414d --- /dev/null +++ b/roles/homeassistant/files/stubs/configuration.yaml @@ -0,0 +1,14 @@ + +# Configure a default setup of Home Assistant (frontend, api, etc) +default_config: + +http: !include http.yaml + +# Text to speech +tts: + - platform: google_translate + +group: !include groups.yaml +automation: !include automations.yaml +script: !include scripts.yaml +scene: !include scenes.yaml diff --git a/roles/homeassistant/files/stubs/groups.yaml b/roles/homeassistant/files/stubs/groups.yaml new file mode 100644 index 0000000..e69de29 diff --git a/roles/homeassistant/files/stubs/scenes.yaml b/roles/homeassistant/files/stubs/scenes.yaml new file mode 100644 index 0000000..e69de29 diff --git a/roles/homeassistant/files/stubs/scripts.yaml b/roles/homeassistant/files/stubs/scripts.yaml new file mode 100644 index 0000000..e69de29 diff --git a/roles/homeassistant/files/stubs/secrets.yaml b/roles/homeassistant/files/stubs/secrets.yaml new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/roles/homeassistant/files/stubs/secrets.yaml @@ -0,0 +1 @@ +{} diff --git a/roles/homeassistant/tasks/main.yml b/roles/homeassistant/tasks/main.yml index 522775c..18c50eb 100644 --- a/roles/homeassistant/tasks/main.yml +++ b/roles/homeassistant/tasks/main.yml @@ -3,11 +3,12 @@ with_first_found: - '{{ ansible_architecture }}.yml' - defaults.yml + tags: + - always -- name: ensure system dependencies are installed +- name: ensure podman is installed package: - name: >- - {{ homeassistant_common_system_deps + homeassistant_arch_system_deps }} + name: '{{ homeassistant_podman_packages }}' state: present tags: - install @@ -17,62 +18,73 @@ name: homeassistant system: true home: /var/lib/homeassistant + createhome: false + register: homeassistant_user + tags: + - user -- name: ensure homeassistant tmp dir exists +- name: ensure homeassistant home directory exists file: - path: /var/lib/homeassistant/tmp - mode: '0700' + path: /var/lib/homeassistant owner: homeassistant group: homeassistant - state: directory - -- name: ensure homeassistant install dir exists - file: - path: /usr/local/homeassistant mode: '0755' - owner: homeassistant - group: homeassistant state: directory -- name: ensure homeassistant is installed - environment: - TMPDIR: /var/lib/homeassistant/tmp - become: true - become_user: homeassistant - pip: - name: homeassistant - extra_args: >- - --prefer-binary - virtualenv: /usr/local/homeassistant - virtualenv_command: '/usr/bin/python3 -m venv' + tags: + - datadir -- name: ensure selinux file context map is correct for home assistant dir - sefcontext: - ftype: a - setype: bin_t - target: /usr/local/homeassistant/bin(/.*)? - state: present - notify: relabel home assistant dir - -- name: ensure homeassistant entry point is installed +- name: ensure stub home assistant configuration is set copy: - src: hass.sh - dest: /usr/local/bin/hass - setype: bin_t - mode: '0755' + src: '{{ item }}' + dest: /var/lib/homeassistant/{{ item|basename }} + owner: homeassistant + group: homeassistant + mode: '0644' + force: false + with_fileglob: + - 'stubs/*.yaml' + tags: + - config +- name: ensure home assistant proxy settings are configured + copy: + src: http.yaml + dest: /var/lib/homeassistant/http.yaml + owner: homeassistant + group: homeassistant + mode: '0644' notify: - restart homeassistant + tags: + - config + +- name: ensure homeassistant container image is available + podman_image: + name: ghcr.io/home-assistant/{{ homeassistant_image_name }} + tag: stable + state: present + notify: + - restart homeassistant + tags: + - container-image + - container + - name: ensure homeassistant systemd unit is installed - copy: - src: homeassistant.service + template: + src: homeassistant.service.j2 dest: /etc/systemd/system/homeassistant.service mode: '0644' notify: - reload systemd - restart homeassistant + tags: + - service + - systemd - name: ensure homeassistant starts at boot service: name: homeassistant enabled: true + tags: + - service - name: ensure apache is configured to proxy for homeassistant template: @@ -81,8 +93,13 @@ mode: '0644' notify: - restart httpd + tags: + - apache - name: ensure selinux allows apache to proxy seboolean: name: httpd_can_network_connect state: true persistent: true + tags: + - selinux + - apache diff --git a/roles/homeassistant/templates/configuration.yaml.j2 b/roles/homeassistant/templates/configuration.yaml.j2 deleted file mode 100644 index 8ffc9ae..0000000 --- a/roles/homeassistant/templates/configuration.yaml.j2 +++ /dev/null @@ -1,37 +0,0 @@ -homeassistant: - # Name of the location where Home Assistant is running - name: Home - # Location required to calculate the time the sun rises and sets - latitude: 38.9568 - longitude: -94.6832 - # Impacts weather/sunrise data (altitude above sea level in meters) - elevation: 0 - # metric for Metric, imperial for Imperial - unit_system: imperial - # Pick yours from here: http://en.wikipedia.org/wiki/List_of_tz_database_time_zones - time_zone: America/Chicago - # Customization file - customize: !include customize.yaml - -# Configure a default setup of Home Assistant (frontend, api, etc) -default_config: - -# Show the introduction message on startup. -introduction: - -# Uncomment this if you are using SSL/TLS, running in Docker container, etc. -# http: -# base_url: example.duckdns.org:8123 -http: - server_host: '::1' - -# Sensors -sensor: !include sensors.yaml - -# Text to speech -tts: - - platform: google - -group: !include groups.yaml -automation: !include automations.yaml -script: !include scripts.yaml diff --git a/roles/homeassistant/templates/homeassistant.service.j2 b/roles/homeassistant/templates/homeassistant.service.j2 new file mode 100644 index 0000000..a9a27ac --- /dev/null +++ b/roles/homeassistant/templates/homeassistant.service.j2 @@ -0,0 +1,24 @@ +# vim: set ft=systemd : +[Unit] +Description=Home Assistant + +[Service] +Type=notify +NotifyAccess=all +ExecStartPre=-/usr/bin/podman container rm --ignore -f homeassistant +ExecStart=/usr/bin/podman run \ + --sdnotify=conmon --cgroups=no-conmon \ + --rm \ + --network=host \ + --name homeassistant \ + -v /var/lib/homeassistant:/config:Z \ + --uidmap 0:{{ homeassistant_user.uid }}:1 \ + --gidmap 0:{{ homeassistant_user.group }}:1 \ + --uidmap 1:4000000:65536 \ + --gidmap 1:4000000:65536 \ + ghcr.io/home-assistant/{{ homeassistant_image_name }}:stable +ProtectSystem=full +UMask=0077 + +[Install] +WantedBy=multi-user.target diff --git a/roles/homeassistant/vars/aarch64.yml b/roles/homeassistant/vars/aarch64.yml new file mode 100644 index 0000000..d74f6ee --- /dev/null +++ b/roles/homeassistant/vars/aarch64.yml @@ -0,0 +1 @@ +homeassistant_default_image_name: aarch64-homeassistant diff --git a/roles/homeassistant/vars/armv7l.yml b/roles/homeassistant/vars/armv7l.yml index c8b9726..6f45c85 100644 --- a/roles/homeassistant/vars/armv7l.yml +++ b/roles/homeassistant/vars/armv7l.yml @@ -1,12 +1 @@ -# These are required to build Python packages that do not have wheels -# on pypi.org for armv7hl -homeassistant_arch_system_deps: -- gcc -- gcc-c++ -- libffi-devel -- libopenzwave-devel -- libudev-devel -- make -- openssl-devel -- python3-devel -- which +homeassistant_default_image_name: armhf-homeassistant diff --git a/roles/homeassistant/vars/defaults.yml b/roles/homeassistant/vars/defaults.yml index 222f814..a159839 100644 --- a/roles/homeassistant/vars/defaults.yml +++ b/roles/homeassistant/vars/defaults.yml @@ -1 +1 @@ -homeassistant_arch_system_deps: [] +homeassistant_default_image_name: home-assistant diff --git a/roles/homeassistant/vars/main.yml b/roles/homeassistant/vars/main.yml index 447fcd7..a3a1a5d 100644 --- a/roles/homeassistant/vars/main.yml +++ b/roles/homeassistant/vars/main.yml @@ -1,2 +1,3 @@ -homeassistant_common_system_deps: -- python3-pip +homeassistant_podman_packages: +- podman +- catatonit