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:

[plain]

– 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

[/plain]

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.

[plain]

# 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"]

[/plain]

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:

[plain]

## 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: … }

[/plain]

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

[plain]

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/*

[/plain]

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:

[plain]

# 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 %}

[/plain]

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

[plain]
##### 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=…

[/plain]

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:

[plain language=”tasks/main.yml”]

– 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"

[/plain]

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

[plain]

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…

[/plain]

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

Have fun and do Ansible!