Saturday, August 10, 2019

Experiment: Home Assistant in an LXD container without a venv

Update: August 2020 (one year later)

Here's a slightly different way of doing it entirely from the host. Tested with HomeAssistant version 114.

lxc launch -p lanprofile ubuntu:focal ha-test

# Update apt so we can install pip
cat <<EOF > /tmp/container-sources.list
deb http://us.archive.ubuntu.com/ubuntu/ focal main universe
deb http://us.archive.ubuntu.com/ubuntu/ focal-updates main universe
deb http://security.ubuntu.com/ubuntu focal-security main universe
EOF

lxc file push /tmp/container-sources.list ha-test/etc/apt/sources.list
lxc exec ha-test -- apt update
lxc exec ha-test -- apt upgrade

# Here's the meat: Installing pip3, then using pip3 to install HA and dependencies.
lxc exec ha-test -- apt install python3-pip
lxc exec ha-test -- pip3 install aiohttp_cors defusedxml emoji hass_nabucasa home-assistant-frontend homeassistant mutagen netdisco sqlalchemy zeroconf

# Example of fixing a version error message that occurs during pip install:
# ERROR: homeassistant 0.114.2 has requirement cryptography==2.9.2, but you'll have cryptography 2.8 which is incompatible.
lxc exec ha-test -- pip3 install --upgrade cryptography==2.9.2

# Can't start the web browser without knowing the container's IP address. 
lxc list | grep ha-test
   | ha-test       | RUNNING | 192.168.2.248 (eth0) |      | CONTAINER | 0         |

# Run Hass
lxc exec ha-test -- hass
   Unable to find configuration. Creating default one in /root/.homeassistant

# Web browser: http://192.168.2.248:8123....and there it is!


Home Assistant usually runs in a Python 3 virtual environment (venv). The developers wisely chose Python 3 because it has all the libraries they need. The developers wisely chose venv to create an effective single, predictable platform upon which Home Assistant can run. Users like it because just a couple extra shell incantations is the difference between success and cryptic-error hell.

Let's see if I can get HA 0.97 to run on Ubuntu 19.04. In this case, I'm running it in a disposable LXD container so I can just throw it away after the experiment is complete. This experiment turned out to be about 75% successful - Home Assistant installs and runs outside the venv, but logging and sqlalchemy failed to install, so the final product had some limitations.


Setup

First, let's create the LXD container. Step 1. Step 2. I use a networking profile ("lanprofile") that uses DHCP to request an IP address from my router instead of the local server. I'm using an Ubuntu 19.04 ("Disco") image for the container. And I'm calling the container "ha-test2," second in a line of Home Assistant test containers.

    me@host:~$ lxc launch -p lanprofile ubuntu:disco ha-test2

After a minute or two, the container is running and has picked up an IP address from the router.

    me@host:~$ lxc list
        +----------+---------+----------------------+-----
        |   NAME   |  STATE  |        IPV4          |
        +----------+---------+----------------------+-----
        | ha-test2 | RUNNING | 192.168.1.252 (eth0) |
        +----------+---------+----------------------+-----

Let's enter the container. Note that change to a root prompt within the container. This is an unprivileged container (LXD's default), so root within the container is NOT root for the rest of the system. Note also the mysterious "ttyname failed: No such device" error, due to a very minor bug but does not affect our use of the container in any way.

    me@host:~$ lxc shell ha-test2
        mesg: ttyname failed: No such device
        root@ha-test2:~#

OPTIONAL: Limit the Ubuntu sources. We don't need -resrticted or -multiverse or -proposed or -backports, etc. I replaced the entire file with the following three lines. Proper format is important!

    root@ha-test2:~# nano /etc/apt/sources.list

        deb http://archive.ubuntu.com/ubuntu disco main universe
        deb http://archive.ubuntu.com/ubuntu disco-updates main universe
        deb http://security.ubuntu.com/ubuntu disco-security main universe

OPTIONAL: Expand Unattended Upgrades to handle 100% of the limited sources. I replaced the entire file with the following five lines.

    root@ha-test2:~# nano /etc/apt/apt.conf.d/50unattended-upgrades 

        Unattended-Upgrade::Allowed-Origins {
            "${distro_id}:${distro_codename}";
            "${distro_id}:${distro_codename}-security";
            "${distro_id}:${distro_codename}-updates";
        };

Since this is the first run of the package manager...

    root@ha-test2:~# apt update
    root@ha-test2:~# apt upgrade

Home Assistant uses Python 3's pip, not debs. So we install pip.

    root@ha-test2:~# apt install python3-pip


First Try - Learning Curve

Now we can use pip to install Home Assistant. This command will run for a few minutes, and will produce a lot of output as it downloads many dependencies. Some of those installs output, at first glance, messages that seem like errors -- read them carefully, they are probably uninstall errors if packages were being upgraded...which they are not, of course.

    root@ha-test2:~# pip3 install homeassistant

The first run of 'hass' (the Home Assistant program name) is where we start to encounter errors that need to be investigated and fixed. When the system ground to a halt for several minutes, I used CTRL+C to end the process and return to a shell prompt.

    root@ha-test2:~# hass

        // Lots of success...but then:

        2019-08-09 22:35:57 INFO (MainThread) [homeassistant.bootstrap] Setting up {'system_log'}
        2019-08-09 22:35:57 INFO (SyncWorker_2) [homeassistant.util.package] Attempting install of aiohttp_cors==0.7.0
        2019-08-09 22:36:01 INFO (MainThread) [homeassistant.setup] Setting up http
        2019-08-09 22:36:01 ERROR (MainThread) [homeassistant.setup] Error during setup of component http
        Traceback (most recent call last):
          File "/usr/local/lib/python3.7/dist-packages/homeassistant/setup.py", line 168, in _async_setup_component
            hass, processed_config
          File "/usr/local/lib/python3.7/dist-packages/homeassistant/components/http/__init__.py", line 178, in async_setup
            ssl_profile=ssl_profile,
          File "/usr/local/lib/python3.7/dist-packages/homeassistant/components/http/__init__.py", line 240, in __init__
            setup_cors(app, cors_origins)
          File "/usr/local/lib/python3.7/dist-packages/homeassistant/components/http/cors.py", line 22, in setup_cors
            import aiohttp_cors
        ModuleNotFoundError: No module named 'aiohttp_cors'
        2019-08-09 22:36:01 ERROR (MainThread) [homeassistant.setup] Unable to set up dependencies of system_log. Setup failed for dependencies: http
        2019-08-09 22:36:01 ERROR (MainThread) [homeassistant.setup] Setup failed for system_log: Could not set up all dependencies.
        2019-08-09 22:36:01 INFO (SyncWorker_4) [homeassistant.util.package] Attempting install of sqlalchemy==1.3.5
        2019-08-09 22:36:11 INFO (MainThread) [homeassistant.setup] Setting up recorder
        Exception in thread Recorder:
        Traceback (most recent call last):
          File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
            self.run()
          File "/usr/local/lib/python3.7/dist-packages/homeassistant/components/recorder/__init__.py", line 211, in run
            from .models import States, Events
          File "/usr/local/lib/python3.7/dist-packages/homeassistant/components/recorder/models.py", line 6, in 
            from sqlalchemy import (
        ModuleNotFoundError: No module named 'sqlalchemy'

        2019-08-09 22:36:21 WARNING (MainThread) [homeassistant.setup] Setup of recorder is taking over 10 seconds.

        // Thread hangs here. Use CTRL+C to abort back to a shell prompt

There are two errors there. Both are simply bugs in Home Assistant's list of dependencies. The developers neglected to include dependencies upon "aiohttp_cors" and "sqlalchemy". Let's uninstall all the pip packages and dependencies and start over. The dependencies are listed in the 'pip3 show' command. Remember to delete pip from the list of removals, and to add homeassistant. The pip3 uninstall command asks a lot of questions about deleting files and directories -- as long as the offered removals are in /usr/local, it won't break anything.

    root@ha-test2:~# pip3 show homeassistant
        Name: homeassistant
        Version: 0.97.1
        Summary: Open-source home automation platform running on Python 3.
        Home-page: https://home-assistant.io/
        Author: The Home Assistant Authors
        Author-email: hello@home-assistant.io
        License: Apache License 2.0
        Location: /usr/local/lib/python3.7/dist-packages
        Requires: pyyaml, async-timeout, bcrypt, voluptuous, voluptuous-serialize, importlib-metadata, ruamel.yaml, jinja2, cryptography, python-slugify, pip, PyJWT, requests, aiohttp, certifi, attrs, astral, pytz
        Required-by: 

    root@ha-test2:~# pip3 uninstall homeassistant pyyaml async-timeout bcrypt voluptuous voluptuous-serialize importlib-metadata ruamel.yaml jinja2 cryptography python-slugify PyJWT requests aiohttp certifi attrs astral pytz


Second Try - Getting closer

For the second try, let's add those two missing dependencies. This time, we successfully started logging and sqalchemy (success), and progressed to the next errors. The web server started, but the Home Assistant front end hosted on the webserver failed. The .homeassistant config directory was created and populated.

    root@ha-test2:~# pip3 install homeassistant aiohttp_cors sqlalchemy

        [lots of installing]

    root@ha-test2:~# hass

        2019-08-09 23:56:16 INFO (MainThread) [homeassistant.setup] Setting up onboarding
        2019-08-09 23:56:16 INFO (MainThread) [homeassistant.setup] Setup of domain config took 0.9 seconds.
        2019-08-09 23:56:16 INFO (MainThread) [homeassistant.setup] Setting up automation
        2019-08-09 23:56:16 INFO (MainThread) [homeassistant.setup] Setup of domain automation took 0.0 seconds.
        2019-08-09 23:56:16 INFO (MainThread) [homeassistant.setup] Setup of domain onboarding took 0.0 seconds.
        2019-08-09 23:56:20 ERROR (MainThread) [homeassistant.config] Unable to import ssdp: No module named 'netdisco'
        2019-08-09 23:56:20 ERROR (MainThread) [homeassistant.setup] Setup failed for ssdp: Invalid config.
        2019-08-09 23:56:20 INFO (SyncWorker_3) [homeassistant.util.package] Attempting install of distro==1.4.0
        2019-08-09 23:56:24 INFO (MainThread) [homeassistant.setup] Setting up updater
        2019-08-09 23:56:24 INFO (MainThread) [homeassistant.setup] Setup of domain updater took 0.0 seconds.
        2019-08-09 23:56:24 INFO (SyncWorker_1) [homeassistant.util.package] Attempting install of mutagen==1.42.0
        2019-08-09 23:56:29 INFO (SyncWorker_2) [homeassistant.loader] Loaded google_translate from homeassistant.components.google_translate
        2019-08-09 23:56:29 INFO (SyncWorker_3) [homeassistant.util.package] Attempting install of hass-nabucasa==0.16
        2019-08-09 23:56:50 INFO (MainThread) [homeassistant.setup] Setting up cloud
        2019-08-09 23:56:50 ERROR (MainThread) [homeassistant.setup] Error during setup of component cloud
        Traceback (most recent call last):
          File "/usr/local/lib/python3.7/dist-packages/homeassistant/setup.py", line 168, in _async_setup_component
            hass, processed_config
          File "/usr/local/lib/python3.7/dist-packages/homeassistant/components/cloud/__init__.py", line 167, in async_setup
            from hass_nabucasa import Cloud
        ModuleNotFoundError: No module named 'hass_nabucasa'
        2019-08-09 23:56:50 INFO (MainThread) [homeassistant.setup] Setting up mobile_app
        2019-08-09 23:56:50 ERROR (MainThread) [homeassistant.config] Unable to import zeroconf: No module named 'zeroconf'
        2019-08-09 23:56:50 ERROR (MainThread) [homeassistant.setup] Setup failed for zeroconf: Invalid config.
        2019-08-09 23:56:50 INFO (SyncWorker_2) [homeassistant.util.package] Attempting install of home-assistant-frontend==20190805.0
        2019-08-09 23:56:50 INFO (MainThread) [homeassistant.setup] Setup of domain mobile_app took 0.0 seconds.
        2019-08-09 23:56:50 INFO (SyncWorker_3) [homeassistant.loader] Loaded notify from homeassistant.components.notify
        2019-08-09 23:56:50 INFO (MainThread) [homeassistant.setup] Setting up notify
        2019-08-09 23:56:50 INFO (MainThread) [homeassistant.setup] Setup of domain notify took 0.0 seconds.
        2019-08-09 23:56:50 INFO (MainThread) [homeassistant.components.notify] Setting up notify.mobile_app
        2019-08-09 23:57:24 INFO (MainThread) [homeassistant.setup] Setting up frontend
        2019-08-09 23:57:24 ERROR (MainThread) [homeassistant.setup] Error during setup of component frontend
        Traceback (most recent call last):
          File "/usr/local/lib/python3.7/dist-packages/homeassistant/setup.py", line 168, in _async_setup_component
            hass, processed_config
          File "/usr/local/lib/python3.7/dist-packages/homeassistant/components/frontend/__init__.py", line 267, in async_setup
            root_path = _frontend_root(repo_path)
          File "/usr/local/lib/python3.7/dist-packages/homeassistant/components/frontend/__init__.py", line 244, in _frontend_root
            import hass_frontend
        ModuleNotFoundError: No module named 'hass_frontend'
        2019-08-09 23:57:24 INFO (SyncWorker_0) [homeassistant.util.package] Attempting install of gTTS-token==1.1.3
        2019-08-09 23:57:24 ERROR (MainThread) [homeassistant.setup] Unable to set up dependencies of logbook. Setup failed for dependencies: frontend
        2019-08-09 23:57:24 ERROR (MainThread) [homeassistant.setup] Setup failed for logbook: Could not set up all dependencies.
        2019-08-09 23:57:24 ERROR (MainThread) [homeassistant.setup] Unable to set up dependencies of map. Setup failed for dependencies: frontend
        2019-08-09 23:57:24 ERROR (MainThread) [homeassistant.setup] Setup failed for map: Could not set up all dependencies.
        2019-08-09 23:57:24 ERROR (MainThread) [homeassistant.setup] Unable to set up dependencies of default_config. Setup failed for dependencies: cloud, frontend, logbook, map, ssdp, zeroconf
        2019-08-09 23:57:24 ERROR (MainThread) [homeassistant.setup] Setup failed for default_config: Could not set up all dependencies.
        2019-08-09 23:57:30 INFO (MainThread) [homeassistant.setup] Setting up tts
        2019-08-09 23:57:30 INFO (SyncWorker_1) [homeassistant.components.tts] Create cache dir /root/.homeassistant/tts.
        2019-08-09 23:57:30 INFO (MainThread) [homeassistant.setup] Setup of domain tts took 0.0 seconds.
        2019-08-09 23:57:30 INFO (MainThread) [homeassistant.bootstrap] Home Assistant initialized in 87.48s
        2019-08-09 23:57:30 INFO (MainThread) [homeassistant.core] Starting Home Assistant
        2019-08-09 23:57:30 INFO (MainThread) [homeassistant.core] Timer:starting

We have two missing dependencies (netdisco and zeroconf), and a bunch of missing internal homeassistant functions. This looks a bit like a race condition - the setup script is expecting functions that aren't-quite-ready yet. This also explains why many of these errors do not appear during a subsequent run of hass.

Let's delete and try again with those two additional dependencies....
    root@ha-test2:~# pip3 uninstall homeassistant pyyaml async-timeout bcrypt voluptuous voluptuous-serialize importlib-metadata ruamel.yaml jinja2 cryptography python-slugify PyJWT requests aiohttp certifi attrs astral pytz aiohttp_cors sqlalchemy
    root@ha-test2:~# rm -r .homeassistant/


Third Try - Close enough to call it success

For the second try, let's add those two missing dependencies. This time, we successfully started logging and sqalchemy (success), and progressed to the next errors. The web server started, but the Home Assistant front end hosted on the webserver failed. The .homeassistant config directory was created and populated.

    root@ha-test2:~# pip3 install homeassistant aiohttp_cors sqlalchemy netdisco zeroconf

        [lots of installing]

    root@ha-test2:~# hass

        // No missing dependencies
        // Same setup errors

On the first run of hass, the dependency errors are gone, but the setup errors remain and the website is still unavailable. One the second run of hass, no errors at all, the website and all features work. The system is ready for systemd integration to bring hass up and down with the system.


Substituting Debs for Pips

Many of those pip dependencies are also available in Debian and Ubuntu. Let's try adding the debs, one by one, and see if we can reduce the number of pip dependencies. This is a separate experiment, obviously.

The process here is to delete homeassistant, it's pip dependencies, and it's config files, then replace Pips with Debs. We want to see if homeassistant pulls in the relevant pip anyway. If so, we can delete that pip, then see if homeassistant installs and initializes properly. That means that this experiment is not persistent - Home Assistant updates (like 0.97 to 0.98) will pull in all the removed pips again.

Several packages are already installed in the default Ubuntu 19.04 image, but are superseded by pips:
  • python3-certifi, python3-cryptography, python3-jinja2, python3-multidict, python3-requests, python3-yarl
Some packages are not available as debs at all. These are all dependencies of homeassistant:
  • attrs, homeassistant, importlib-metadata, PyJWT, pyyaml, zipp
Several packages, once installed, no longer pull in the pip:

    root@ha-test2:~# apt install python3-async-timeout python3-voluptuous-serialize

These packages, after installed, continue to pull in the pip anyway:

    root@ha-test2:~# apt install python3-aiohttp python3-aiohttp-cors python3-astral python3-async-timeout python3-bcrypt python3-python-slugify python3-ruamel.yaml python3-tz python3-voluptuous python3-voluptuous-serialize


After intalling all those debs, the homeassistant install looks something like this:

    root@ha-test2:~# pip3 install homeassistant
    root@ha-test2:~# pip3 uninstall aiohttp aiohttp_cors astral bcrypt certifi cryptography jinja2 multidict python-slugify pytz requests ruamel.yaml voluptuous yarl
    root@ha-test2:~# hass      // first time - no new install errors
    root@ha-test2:~# hass      // frontend works, no startup errors

Of course, this was an experiment - your mileage may vary. You may encounter problems that I did not. But it IS clearly possible to install Home Assistant into a non-venv environment, clearly possible to install Home Assistant into an LXD container, and clearly possible to more closely integrate Home Assistant into a Debian-based system.

No comments: