It isn’t so much the Image I’m worried about. I can download that no problem and I do keep the second from most recent one. The big thing is to have a hot backup of my conf/userdata so I can just spin up the old container with the old config stuff in a pinch. But to make that work I’ll need two different docker run configs, one that mounts the current one and one that mounts the backup so that I’d be just a couple of commands away from recovery.
And it is the RAM that I’m running right up against the limits of my server right now. I’m pretty much maxed out and need to go ahead and spend the $100 for more RAM. another VM that would take 2G or more would put me over.
No problem, though it might take some time to do a thorough posting. I was lazy in a lot of my roles and have hard coded paths and sensitive information embedded rather than following Ansible best practices. In short, what I have is a little embarrasing at the moment. I do plan to post the full playbook to github and possibly Ansible Galaxy at somepoint.
I do everything using Ansible instead of Docker Compose. I don’t know how much you know about Ansible yet so please ask questions if you have them.
Over all I have my openHAB configs in a locally hosted git server and the playbook checks the config out as part of installation.
openHAB Role
meta/main.yml
Instead of Docker Compose I use meta and dependencies to install the other related apps like mosquitto, influxdb, and grafana.
---
dependencies:
- { role: mosquitto }
- { role: influxdb }
- { role: grafana }
vars/main.yml
---
openhab_conf_repo: <git path to config git repo>
openhab_data: /opt/openhab2
influxdb_ip_address: <domain name or path ti influxdb>
openhab_influxdb_database_name: openhab_db
influx_openhab_user: openhab
influx_openhab_password: <password>
influx_grafana_user: grafana
influx_grafana_password: <password>
Obviously, replace the stuff in < >
with appropriate values.
tasks/main.yml
The playbook:
- create a user and group to match the UID/GID in the container
- add the openhab user to the dialout group
- add my main user {{share_user}} to the openhab group
- fix permissions on the main folder where all the OH files will go and checkout the config there
- creates any missing folders
- creates the InfluxDB user and database for OH
- creates the Grafan user on InfluxDB and gives it permission on the OH database (actions are idempotent so if the user already exists nothing happens)
- finally download and run the official OH Image from DockerHub
---
- name: Change openhab group to 9001
group:
gid: 9001
name: openhab
state: present
system: yes
become: yes
- name: Create openhab user
user:
comment: 'openHAB'
createhome: no
name: openhab
shell: /bin/false
state: present
system: yes
uid: 9001 # uid of openhab user inside the official container
group: openhab
become: yes
- name: Add the openhab user to the dialout group
command: usermod -a -G dialout openhab
become: yes
- name: Add {{ share_user }} to the openhab group
command: usermod -a -G openhab {{ share_user }}
become: yes
- name: Set permissions on openhab data folder so we can check out into it
file:
path: "{{ openhab_data }}"
state: directory
owner: openhab
group: openhab
mode: u=rwx,g=rwx,o=rx
become: yes
- name: Checkout openhab config
git:
repo: "{{ openhab_conf_repo }}"
dest: "{{ openhab_data }}"
accept_hostkey: yes
become: yes
- name: Change ownership of openhab config
file:
path: "{{ openhab_data }}"
owner: openhab
group: openhab
recurse: yes
become: yes
- name: Create expected folders if they don't already exist
file:
path: "{{ item }}"
state: directory
owner: openhab
group: openhab
become: yes
become_user: openhab
with_items:
- "{{ openhab_data }}/conf"
- "{{ openhab_data }}/userdata"
- "{{ openhab_data }}/addons"
- "{{ openhab_data }}/.java"
- name: Create database
influxdb_database:
hostname: "{{ influxdb_ip_address }}"
database_name: "{{ openhab_influxdb_database_name }}"
state: present
username: admin
password: "{{ influxdb_admin_password }}"
# TODO there is currently a bug which prevents us from using influx in the container
- name: Create openhab user
# command: influx -username admin -password {{ influxdb_admin_password }} -database '{{ openhab_influxdb_database_name }}' -execute "CREATE USER {{ influx_openhab_user }} WITH PASSWORD '{{ influx_openhab_password }}'"
command: curl -XPOST http://localhost:8086/query?db={{ openhab_influxdb_database_name }}&u=admin&p={{ influxdb_admin_password }} --data-urlencode "q=CREATE USER {{ influx_openhab_user }} WITH PASSWORD '{{ influx_openhab_password }}'"
- name: Give openhab permissions on openhab db
# command: influx -username admin -password {{ influxdb_admin_password }} -database '{{ openhab_influxdb_database_name }}' -execute "GRANT ALL ON {{ openhab_influxdb_database_name }} TO {{ influx_openhab_user }}"
command: curl -XPOST http://localhost:8086/query?db={{ openhab_influxdb_database_name }}&u=admin&p={{ influxdb_admin_password }} --data-urlencode "q=GRANT ALL ON {{ openhab_influxdb_database_name }} TO {{ influx_openhab_user }}"
- name: Create grafana user
# command: influx -username admin -password {{ influxdb_admin_password }} -database '{{ openhab_influxdb_database_name }}' -execute "CREATE USER {{ influx_grafana_user }} WITH PASSWORD '{{ influx_grafana_password }}'"
command: curl -XPOST http://localhost:8086/query?db={{ openhab_influxdb_database_name }}&u=admin&p={{ influxdb_admin_password }} --data-urlencode "q=CREATE USER {{ influx_grafana_user }} WITH PASSWORD '{{ influx_grafana_password }}'"
- name: Give grafana read permissions on openhab_db
# command: influx -username admin -password {{ influxdb_admin_password }} -database '{{ openhab_influxdb_database_name }}' -execute "GRANT READ ON {{ openhab_influxdb_database_name }} TO {{ influx_grafana_user }}"
command: curl -XPOST http://localhost:8086/query?db={{ openhab_influxdb_database_name }}&u=admin&p={{ influxdb_admin_password }} --data-urlencode "q=GRANT READ ON {{ openhab_influxdb_database_name }} TO {{ influx_grafana_user }}"
# TODO download Jython and add to env in docker call
- name: Start openHAB
docker_container:
detach: True
devices:
- "/dev/ttyUSB0:/dev/ttyUSB0:rwm"
hostname: argus.koshak.net
image: openhab/openhab:2.1.0-snapshot-amd64
log_driver: syslog
name: openhab
network_mode: host
pull: True
recreate: True
restart: True
restart_policy: always
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- "{{ openhab_data }}/conf:/openhab/conf"
- "{{ openhab_data }}/userdata:/openhab/userdata"
- "{{ openhab_data }}/addons:/openhab/addons"
- "{{ openhab_data }}/.java:/openhab/.java"
Obviously there are a lot of areas that need improvements. I welcome comments and critiques.
Mosquitto
NOTE: I’m pretty certain there are better develped Mosquitto roles available on github and Ansible Galaxy. I’ve not yet tested anything to do with certs though do generate them.
I put my mosquitto.conf file in files/mosquitto.conf
which will get uploaded. You could use lineinfile and/or blockinfile to insert config changes to the default conf but I find doing it this way easier.
vars/main.yml
Where possible I try to put the data that gets mounted to the containers on my NAS. I don’t do that for openHAB because I had some permission and file event problems that I haven’t tried to solve yet (probably driven by the fact I’m using CIFS rather than NFS, darned mixed environment).
---
mosquitto_data: /mnt/mosquitto
mosquitto_mount: <cifs path to shared folder>
mosquitto_user: "{{ share_user }}"
mosquitto_passwd: "{{ share_pass }}"
mosquitto_ca_passwd: "{{ share_pass }}"
mosquitto_ca_country: <country>
mosquitto_ca_state: <state>
mosquitto_ca_city: <city>
mosquitto_ca_org: <org>
mosquitto_ca_unit: <unit>
mosquitto_ca_fqdn: "{{ ansible_hostname }}.<domain if you got it>"
mosquitto_ca_email: "{{ email_login }}"
vars/main.yml
- create mosquitto user
- mount the file share where the mosquitto stuff will go
- create the directories that get mounted to the container if they don’t exist
- copy over the conf file
- install mosquitto clients on the host so we can generate the passwd file, generate the passwd file, and remove the clients
- recreate the mosquitto user because it will have been removed when we uninstalled the clients
- create the mosquitto CA if it doesn’t exist
- create the certs
- download and run the eclipse-mosquitto Docker image from DockerHub
---
- name: Create mosquitto user
user:
comment: 'Mosquitto'
createhome: no
name: mosquitto
shell: /bin/false
state: present
system: yes
become: yes
- name: Mount mosquitto from file share
include_role:
name: mount-cifs
vars:
mount_mode: '0660'
cifs_user: "{{ share_user }}"
cifs_pass: "{{ share_pass }}"
cifs_domain: "{{ workgroup }}"
mount_user: "mosquitto"
mount_path: "{{ mosquitto_data }}"
mount_src: "{{ mosquitto_mount }}"
- name: Create mosquitto directories
file:
path: "{{ item }}"
state: directory
mode: u=rwx,g=rwx,o=rx
become: yes
become_user: mosquitto
with_items:
- "{{ mosquitto_data }}/config"
- "{{ mosquitto_data }}/data"
- "{{ mosquitto_data }}/log"
- name: Copy the prepared mosquitto.conf
copy:
src: mosquitto.conf
dest: "{{ mosquitto_data }}/config/mosquitto.conf"
mode: u=rw,g=rw,o=r
become: yes
become_user: mosquitto
- name: Install mosquitto cients, temporarily install mosquitto
apt:
name: "{{ item }}"
update_cache: no
become: yes
with_items:
- mosquitto
- mosquitto-clients
- openssl
- name: Install pexpect
pip:
name: pexpect
become: yes
- name: Generate passwd file
expect:
command: mosquitto_passwd -c {{ mosquitto_data }}/config/passwd {{ mosquitto_user }}
responses:
'Password\:' : "{{ mosquitto_passwd }}"
'Reenter password\:' : "{{mosquitto_passwd }}"
become: yes
become_user: mosquitto
- name: Remove mosquitto
apt:
name: mosquitto
update_cache: no
purge: yes
state: absent
become: yes
- name: Recreate mosquitto user
user:
comment: 'Mosquitto'
createhome: no
name: mosquitto
shell: /bin/false
state: present
system: yes
become: yes
- name: Does the CA already exist?
stat:
path: "{{ mosquitto_data }}/config/ca.crt"
register: mosquitto_ca
- name: Create mosquitto certificate authority
expect:
command: openssl req -new -x509 -days 1461 -extensions v3_ca -keyout {{ mosquitto_data }}/config/ca.key -out {{ mosquitto_data }}/config/ca.crt
responses:
'Enter PEM pass phrase\:' : "{{ mosquitto_ca_passwd }}"
'Verifying \- Enter PEM pass phrase\:' : "{{ mosquitto_ca_passwd }}"
'Country Name \(2 letter code\) \[AU\]\:' : "{{ mosquitto_ca_country }}"
'State or Province Name \(full name\) \[Some\-State\]\:' : "{{ mosquitto_ca_state }}"
'Locality Name \(eg, city\) \[\]\:' : "{{ mosquitto_ca_city }}"
'Organization Name \(eg, company\) \[Internet Widgits Pty Ltd\]\:' : "{{ mosquitto_ca_org }}"
'Organizational Unit Name \(eg, section\) \[\]\:' : "{{ mosquitto_ca_unit }}"
'Common Name \(e\.g\. server FQDN or YOUR name\) \[\]\:' : "{{ mosquitto_ca_fqdn }}"
'Email Address \[\]\:' : "{{ mosquitto_ca_email }}"
when: mosquitto_ca.stat.islnk is not defined
become: yes
become_user: mosquitto
- name: Create server key and cert
include_role:
name: create-cert
vars:
key: "{{ mosquitto_data }}/config/server.key"
csr: "{{ mosquitto_data }}/config/server.csr"
crt: "{{ mosquitto_data }}/config/server.crt"
cert_country: "{{ mosquitto_ca_country }}"
cert_state: "{{ mosquitto_ca_state }}"
cert_city: "{{ mosquitto_ca_city }}"
cert_org: "{{ mosquitto_ca_org }}"
cert_unit: "{{ mosquitto_ca_unit }}"
cert_fqdn: "{{ mosquitto_ca_fqdn }}"
cert_email: "{{ mosquitto_ca_email }}"
ca_crt: "{{ mosquitto_data }}/config/ca.crt"
ca_passwd: "{{ mosquitto_ca_passwd }}"
ca_key: "{{ mosquitto_data }}/config/ca.key"
become: yes
become_user: mosquitto
- name: Get uid
command: id -u mosquitto
register: mosquitto_uid
- name: Get gid
command: id -g mosquitto
register: mosquitto_gid
- name: Run the eclipse-mosquitto docker container
docker_container:
detach: True
exposed_ports:
- 1883
- 9001
- 8883
hostname: mosquitto.koshak.net
image: eclipse-mosquitto
log_driver: syslog
name: mosquitto
published_ports:
- "1883:1883"
- "9001:9001"
- "8883:8883"
recreate: True
restart: True
restart_policy: always
state: started
user: 999 # TODO why can't I use the variable created above?
volumes:
- /etc/passwd:/etc/passwd:ro
- /etc/localtime:/etc/localtime:ro
- /usr/share/zoneinfo:/usr/share/zoneinfo:ro
- "{{ mosquitto_data }}/config:/mosquitto/config"
- "{{ mosquitto_data }}/log:/mosquitto/log"
- "{{ mosquitto_data }}/data:/mosquitto/data"
InfluxDB
As with mosquitto.conf, I put influxdb.conf into files and copy it over to the host rather than editing the file in place.
vars/main.yml
---
influxdb_data: /mnt/influxdb
influxdb_mount: <cisf path to shared folder>
influxdb_admin_password: <password>
tasks/main.yml
- Create influxdb user, add main login user to the influxdb group and mount the share.
- Create the needed directories if they don’t exist and copy over the conf file
- Download and start the official influxdb image from DockerHub
- Give it a chance to come up, then install the influxdb python libraries (needed by Ansible to interat with InfluxDB)
- Create the admin user
---
- name: Create influxdb user
user:
comment: 'InfluxDB Server'
createhome: no
name: influxdb
shell: /usr/sbin/nologin
state: present
system: yes
become: true
- name: Add {{ share_user }} to the influxdb group
command: usermod -a -G influxdb {{ share_user }}
become: yes
- name: Mount influxdb home from file share
include_role:
name: mount-cifs
vars:
mount_mode: '0666'
cifs_user: "{{ share_user }}"
cifs_pass: "{{ share_pass }}"
cifs_domain: "{{ workgroup }}"
mount_user: "influxdb"
mount_path: "{{ influxdb_data }}"
mount_src: "{{ influxdb_mount }}"
- name: Create needed directories
file:
path: "{{ item }}"
state: directory
become: yes
become_user: influxdb
with_items:
- "{{ influxdb_data }}/config"
- "{{ influxdb_data }}/data"
- "{{ influxdb_data }}/logs"
- name: Copy the influxdb.conf file
copy:
src: influxdb.conf
dest: "{{ influxdb_data }}/config/influxdb.conf"
mode: a=r
become: yes
become_user: influxdb
- name: Start InfluxDB
docker_container:
detach: True
exposed_ports:
- 8086
hostname: argus.koshak.net
image: influxdb
log_driver: syslog
name: influxdb
published_ports:
- "8086:8086"
pull: True
restart: True
restart_policy: always
user: 998:998 # TODO get this from tasks or variable
volumes:
- "{{ influxdb_data }}/config:/etc/influxdb"
- "{{ influxdb_data }}/data:/var/lib/influxdb"
- "{{ influxdb_data }}/logs:/var/log/influxdb"
- /etc/localtime:/etc/localtime:ro
- /etc/passwd:/etc/passwd:ro
- name: Sleep for a few to give it a chance to come up
pause:
seconds: 20
prompt: "Waiting for InfluxDB to come up"
- name: Install influxdb python module
pip:
name: influxdb
state: present
become: yes
# Note there is a current bug in Docker preventing one from using exec to run influx so using curl and the rest api here
- name: Create admin user
# command: influx -execute "CREATE USER admin WITH PASSWORD '{{ influxdb_admin_password }}' WITH ALL PRIVILEGES"
command: curl -XPOST http://localhost:8086/query --data-urlencode "q=CREATE USER admin WITH PASSWORD '{{ influxdb_admin_password }}' WITH ALL PRIVILEGES"
Grafana
vars/main.yml
---
grafana_data: /opt/grafana
tasks/main.yml
- create the grafana user and add the main login to the group
- create the data directories for grafan config and logs and such
- download and run the grafana/grafana docker image from DockerHub
---
- name: Create grafana user
user:
comment: 'Grafana Server'
createhome: no
name: grafana
shell: /usr/sbin/nologin
state: present
system: yes
become: true
- name: Add {{ share_user }} to the grafana group
command: usermod -a -G grafana {{ share_user }}
become: yes
- name: Create grafana directories
file:
path: "{{ item }}"
state: directory
owner: grafana
group: grafana
mode: a=rwx
become: yes
with_items:
- "{{ grafana_data }}"
- name: Start Grafana
docker_container:
detach: True
exposed_ports:
- 3000
hostname: grafana.koshak.net
image: grafana/grafana
log_driver: syslog
name: grafana
env:
GF_USERS_ALLOW_SIGN_UP: "false"
GF_AUTH_ANONYMOUS_ENABLED: "true"
published_ports:
- "3000:3000"
pull: True
restart: True
restart_policy: always
volumes:
- "{{ grafana_data }}:/var/lib/grafana"
- /etc/localtime:/etc/localtime:ro
- /etc/passwd:/etc/passwd:ro