openhab_ansible_docker

Since some times I’m building a smart home based on OpenHAB. After playing around with local installation and an installation on the Raspberry PI, I decided to change the hosting platform. I’m running a full-fledged HP Proliant G6 server at home (for some other reasons) which has Debian 8 installed on it. For all systems provisioned, I’m using Ansible and Docker. This article gives some insights in the installation.

My Debian host is configured to run as a Docker host – just a straight forward installation using Ansible. To manage that I found a very nice role on Ansible Galaxy called  angstwad.docker_ubuntu which I used without any modifications.

My current main playbook looks like this:


- name: Z-Zone Provision
  hosts: ...
  become: yes
  become_user: root
  vars:
  - timezone: Europe/Berlin
  - hostname: ...
  vars_files:
  - secrets.yml
  roles:
  - role: init
  - role: ntp
  - role: angstwad.docker_ubuntu
  - role: apache2
  - role: docker_openhab
  - role: docker_icinga2
  - role: docker_mysql
  - role: docker_firebird_small
  - role: docker_confluence
  - role: docker_gitlab

As you can see, I run Apache as a reverse proxy on the host system and anything else packaged in containers. In order to store data from the Docker container, I decided to provide a directory /data containing volumes which are mounted in every container. This approaches simplifies the backup.

Now, after you got my general setup, let me tell you how I install OpenHAB. My experiments started with OpenHAB2, but decided to switch back to OpenHAB1. In doing so, I adopted some ideas from the OpenHAB original Docker file and some ideas from the Ansible OpenHAB role of Prof Falken.

The general idea is:
– Prepare the directories
– Copy Dockerfile and entrypoint.sh to the docker host
– Build an image on the docker host
– Copy OpenHAB configuration
– Start the container

Let start with the Dockerfile, which is provided as a j2 template and copied using the template module to the Docker host.


# openhab image
FROM java:openjdk-8-jre

MAINTAINER Simon Zambrovski, simon@zambrovski.org

ENV DEBIAN_FRONTEND=noninteractive \
    EXTRA_JAVA_OPTS="" \
    JAVA_HOME='/usr/lib/java-8' \
    OPENHAB_HTTP_PORT="8080" \
    OPENHAB_HTTPS_PORT="8443" \
    GOSU_VERSION="1.10" \
    LC_ALL="{{ openhab_lc_all }}" \
    LANG="{{ openhab_lang }}" \
    LANGUAGE="{{ openhab_language }}"

# Install base packages
RUN apt-get update \
    && apt-get install --no-install-recommends -y \
      ca-certificates \
      fontconfig \
      locales \
      locales-all \
      libpcap-dev \
      netbase \
      unzip \
      wget \
      python-software-properties \
      software-properties-common \
    && rm -rf /var/lib/apt/lists/*

RUN set -x \
    && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture)" \
    && wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture).asc" \
    && export GNUPGHOME="$(mktemp -d)" \
    && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 \
    && gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu \
    && rm -r "$GNUPGHOME" /usr/local/bin/gosu.asc \
    && chmod +x /usr/local/bin/gosu \
    && gosu nobody true \
    && echo "export TERM=dumb" | tee -a ~/.bashrc \
    && wget -O - https://bintray.com/user/downloadSubjectPublicKey?username=openhab | apt-key add - \
    && add-apt-repository 'deb http://dl.bintray.com/openhab/apt-repo stable main'

RUN apt-get update \
  && apt-get install --no-install-recommends -y \
    openhab-runtime \
{% for action in openhab_actions %}
    openhab-addon-action-{{ action }} \
{% endfor %}
{% for binding in openhab_bindings %}
    openhab-addon-binding-{{ binding }} \
{% endfor %}
{% for persistence in openhab_persistence %}
    openhab-addon-persistence-{{ persistence }} \
{% endfor %}
  && rm -rf /var/lib/apt/lists/*

COPY entrypoint.sh "/entrypoint.sh"

# Expose volume with configuration and userdata dir
VOLUME ["/etc/openhab/configurations", "/var/log/openhab", "/var/lib/openhab"]

# Execute command
WORKDIR /var/lib/openhab
EXPOSE 8080 8443 5555
ENTRYPOINT ["/entrypoint.sh"]
CMD ["gosu", "openhab", "/usr/share/openhab/bin/openhab.sh"]


Most intersting parts here are the three RUN commands. The first one is installing required packages on the base image. The second one installs GOSU and prepares the OpenHAB installation. The third one is installing OpenHAB and extensions.

This is where the magic happens. The idea (of proffalken) is that we can define the actions, bindings and persistence of OpenHAB in the Ansible file and then, iterate over those and generate the openhab.cfg and install the add-ons.

The corresponding section looks as following in Ansible:


## Configure your Actions here
openhab_actions:
  mail:
    - { key: hostname, value: ... }

openhab_persistence:
  rrd4j:
    - { key: der5min.def, value: 'DERIVE,900,0,U,300' }
    - { key: der5min.archives, value: 'AVERAGE,0.5,1,365:AVERAGE,0.5,7,300' }
  logging:
    - { key: pattern, value: '%date{ISO8601} - %-25logger: %msg%n' }
  mysql:
    - { key: ..., value: 'jdbc:mysql://...' }
    - { key: user, value: '...' }
    - { key: password, value: '...' }
    - { key: reconnectCnt, value: 1 }

openhab_bindings:
  tinkerforge:
  - { key: ..., value: ... }
  pioneeravr:
  - { key: ..., value: ... }
  fritzbox:
  - { key: ..., value: ... }
  homematic:
  - { key: ..., value: ... }
  astro:
  - { key: ..., value: ... }
  weather:
  - { key: ..., value: ... }

The intersting section of the resulting Dockerfile looks then as following:


RUN apt-get update \
  && apt-get install --no-install-recommends -y \
    openhab-runtime \
    openhab-addon-action-mail \
    openhab-addon-binding-pioneeravr \
    openhab-addon-binding-homematic \
    openhab-addon-binding-astro \
    openhab-addon-binding-weather \
    openhab-addon-binding-tinkerforge \
    openhab-addon-binding-fritzbox \
    openhab-addon-persistence-rrd4j \
    openhab-addon-persistence-logging \
    openhab-addon-persistence-mysql \
  && rm -rf /var/lib/apt/lists/*

In the same time, the configuration is used to generate the openhab.cfg which is provided as a j2 template, too. The relevant piece of magic (from proffalken) is:


# Some general configuration
...

##### Action configurations
{% for action in openhab_actions %}
########### {{ action }} ##################
{% for acfg_item in openhab_actions[action] %}
{{ action }}:{{ acfg_item['key'] }}={{ acfg_item['value'] }}
{% endfor %}
{% endfor %}

##### Persistence configurations
{% for persist in openhab_persistence %}
########### {{ persist }} ##################
{% for pcfg_item in openhab_persistence[persist] %}
{{ persist }}:{{ pcfg_item['key'] }}={{ pcfg_item['value'] }}
{% endfor %}
{% endfor %}

##### Binding configurations
{% for binding in openhab_bindings %}
########### {{ binding }} ##################
{% for bcfg_item in openhab_bindings[binding] %}
{{ binding }}:{{ bcfg_item['key'] }}={{ bcfg_item['value'] }}
{% endfor %}
{% endfor %}

This results in a openhab.cfg with the following section generated:

##### Action configurations
########### mail ##################
mail:hostname=...
mail:port=...

##### Persistence configurations
########### rrd4j ##################
rrd4j:der5min.def=DERIVE,900,0,U,300
rrd4j:der5min.archives=AVERAGE,0.5,1,365:AVERAGE,0.5,7,300
rrd4j:der5min.items=...
########### logging ##################
logging:pattern=%date{ISO8601} - %-25logger: %msg%n
########### mysql ##################
mysql:url=...
mysql:user=...
mysql:password=...
mysql:reconnectCnt=...

##### Binding configurations
########### pioneeravr ##################
pioneeravr:livingroom.host=...
########### homematic ##################
homematic:host=...
########### astro ##################
astro:latitude=...
astro:longitude=...
########### weather ##################
weather:apikey.OpenWeatherMap=...
weather:location.home-OWM.latitude=...
weather:location.home-OWM.longitude=...
weather:location.home-OWM.provider=...
weather:location.home-OWM.language=...
weather:location.home-OWM.updateInterval=...
########### tinkerforge ##################
tinkerforge:hosts=...
########### fritzbox ##################
fritzbox:url=...

I use rsync (in form of a synchronize Ansible role) for managing my OpenHAB files like items, rules etc. The nice story about the docker image is that its creation is executed only once, as long I’m not changing the actions, bindings and persistence.

The main Ansible role file is the following:


- name: Prepare OpenHab directories
  file:
    path: "{{ item }}"
    mode: 0777
    state: directory
  with_items:
  - "{{ openhab_conf }}"
  - "{{ openhab_workspace }}"
  - "{{ openhab_logs }}"

- name: Copy Openhab docker file
  template:
    src: Dockerfile
    dest: "{{ openhab_data }}"

- name: Copy Openhab entrypoint
  template:
    src: entrypoint.sh
    dest: "{{ openhab_data }}"
    mode: 0755

- name: Build Openhab image
  docker_image:
    name: zzone/openhab
    path: "{{ openhab_data }}"
    state: present

- name: Use the variables to build out the configuration file
  template:
    src: openhab.cfg
    dest: "{{ openhab_conf }}/openhab.cfg"
- name: Copy users.cfg
  template:
    src: users.cfg
    dest: "{{ openhab_conf }}/users.cfg"

- name: Copy in the configuration files
  synchronize:
    src: "{{ item }}"
    dest: "{{ openhab_conf }}"
    delete: yes
  become: true
  become_user: root
  with_items: "{{ openhab_configuration_files }}"

- name: Change configuration permissions
  file:
    path: "{{ openhab_conf }}"
    recurse: yes
    state: directory
    mode: 0755

- name: Create and run container for OpenHab
  docker_container:
    image: zzone/openhab
    name: openhab
    state: started
    ports:
    - "10080:8080"
    - "10443:8443"
    - "15555:5555"
    volumes:
    - "/etc/localtime:/etc/localtime:ro"
    - "/etc/timezone:/etc/timezone:ro"
    - "{{ openhab_conf }}:/etc/openhab/configurations"
    - "{{ openhab_logs }}:/var/log/openhab"
    - "{{ openhab_workspace }}:/var/lib/openhab"


With some basic configuration defined in defaults/main.yml:


openhab_data: /data/openhab
openhab_conf: "{{ openhab_data }}/configurations"
openhab_workspace: "{{ openhab_data }}/workspace"
openhab_logs: "{{ openhab_data }}/logs"

openhab_configuration_files:
- items
- persistence
- rules
- scripts
- sitemaps
- transform

# More specific settings follow...

If you have any questions, don’t hesitate to ask me in comments.

Have fun and do Ansible!

Tags: , ,