Files
ansible/docs/superpowers/plans/2026-06-03-naruto-zigbee2mqtt.md
2026-06-03 02:57:22 +02:00

9.6 KiB

Zigbee2MQTT + Mosquitto on naruto — Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Deploy Zigbee2MQTT and Mosquitto as Docker containers on naruto, fully managed by the raspberry_pi Ansible role.

Architecture: The raspberry_pi role gains a defaults file, a handlers file, and two task files (directories + zigbee2mqtt). Three Jinja2 templates cover the compose file, Mosquitto config, and Zigbee2MQTT config. All Zigbee2MQTT tasks are guarded with when: inventory_hostname == 'naruto' since the USB dongle only exists there. Secrets live in a new vars/group_vars/raspberry_pi/secrets.yaml.

Tech Stack: Ansible, Docker Compose, Mosquitto, Zigbee2MQTT, Debian 11 (aarch64)


File Map

Action Path Responsibility
Modify roles/raspberry_pi/tasks/main.yaml Include 10_ and 20_ task files
Create roles/raspberry_pi/tasks/10_directories.yaml Create /opt/docker/ tree on naruto
Create roles/raspberry_pi/tasks/20_zigbee2mqtt.yaml Template configs, start compose
Create roles/raspberry_pi/defaults/main.yaml Image versions and path vars
Create roles/raspberry_pi/handlers/main.yaml Restart zigbee2mqtt handler
Create roles/raspberry_pi/templates/zigbee2mqtt/docker-compose.yml.j2 Compose file
Create roles/raspberry_pi/templates/zigbee2mqtt/mosquitto.conf.j2 Mosquitto config
Create roles/raspberry_pi/templates/zigbee2mqtt/z2m-configuration.yaml.j2 Zigbee2MQTT config
Create vars/group_vars/raspberry_pi/secrets.yaml Zigbee network key placeholder

Task 1: Add defaults, handlers, and secrets placeholder

Files:

  • Create: roles/raspberry_pi/defaults/main.yaml

  • Create: roles/raspberry_pi/handlers/main.yaml

  • Create: vars/group_vars/raspberry_pi/secrets.yaml

  • Create defaults file

---
raspberry_pi_docker_base: /opt/docker
raspberry_pi_mosquitto_config_dir: "{{ raspberry_pi_docker_base }}/config/mosquitto"
raspberry_pi_z2m_config_dir: "{{ raspberry_pi_docker_base }}/config/zigbee2mqtt"
raspberry_pi_compose_dir: "{{ raspberry_pi_docker_base }}/compose"
raspberry_pi_mosquitto_version: "2"
raspberry_pi_z2m_version: "2"

Save to roles/raspberry_pi/defaults/main.yaml.

  • Create handlers file
---
- name: Restart zigbee2mqtt
  ansible.builtin.command:
    cmd: docker compose restart zigbee2mqtt
    chdir: "{{ raspberry_pi_compose_dir }}"
  listen: restart zigbee2mqtt
  when: inventory_hostname == 'naruto'

Save to roles/raspberry_pi/handlers/main.yaml.

  • Create secrets placeholder
vault_raspberry_pi:
  zigbee2mqtt:
    network_key: "GENERATE"

Note: GENERATE tells Zigbee2MQTT to auto-generate a network key on first run and persist it to data. Replace with a fixed 16-integer array (e.g. [1, 3, 5, 7, 9, 11, 13, 15, 0, 2, 4, 6, 8, 10, 12, 13]) if you need a stable key across reinstalls.

Save to vars/group_vars/raspberry_pi/secrets.yaml.

  • Commit
git add roles/raspberry_pi/defaults/main.yaml roles/raspberry_pi/handlers/main.yaml vars/group_vars/raspberry_pi/secrets.yaml
git commit -m "feat(raspberry_pi): add defaults, handlers, and secrets placeholder"

Task 2: Create directory task

Files:

  • Create: roles/raspberry_pi/tasks/10_directories.yaml

  • Create directory task file

---
- name: Create docker base directories
  ansible.builtin.file:
    path: "{{ item }}"
    state: directory
    mode: "0755"
  become: true
  loop:
    - "{{ raspberry_pi_docker_base }}"
    - "{{ raspberry_pi_compose_dir }}"
  when: inventory_hostname == 'naruto'

- name: Create Mosquitto directories
  ansible.builtin.file:
    path: "{{ item }}"
    state: directory
    mode: "0755"
  become: true
  loop:
    - "{{ raspberry_pi_mosquitto_config_dir }}"
    - "{{ raspberry_pi_mosquitto_config_dir }}/data"
    - "{{ raspberry_pi_mosquitto_config_dir }}/log"
  when: inventory_hostname == 'naruto'

- name: Create Zigbee2MQTT directories
  ansible.builtin.file:
    path: "{{ item }}"
    state: directory
    mode: "0755"
  become: true
  loop:
    - "{{ raspberry_pi_z2m_config_dir }}"
    - "{{ raspberry_pi_z2m_config_dir }}/data"
  when: inventory_hostname == 'naruto'

Save to roles/raspberry_pi/tasks/10_directories.yaml.

  • Commit
git add roles/raspberry_pi/tasks/10_directories.yaml
git commit -m "feat(raspberry_pi): add directory setup task"

Task 3: Create templates

Files:

  • Create: roles/raspberry_pi/templates/zigbee2mqtt/mosquitto.conf.j2

  • Create: roles/raspberry_pi/templates/zigbee2mqtt/z2m-configuration.yaml.j2

  • Create: roles/raspberry_pi/templates/zigbee2mqtt/docker-compose.yml.j2

  • Create Mosquitto config template

listener 1883
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log
allow_anonymous true

Save to roles/raspberry_pi/templates/zigbee2mqtt/mosquitto.conf.j2.

  • Create Zigbee2MQTT config template
homeassistant:
  enabled: true

mqtt:
  server: mqtt://mosquitto:1883

serial:
  port: /dev/serial/by-id/usb-SONOFF_SONOFF_Dongle_Lite_MG21_0263f93f46a2ef11b078926661ce3355-if00-port0

advanced:
  network_key: {{ vault_raspberry_pi.zigbee2mqtt.network_key }}
  log_level: info

frontend:
  enabled: true
  port: 8080

Save to roles/raspberry_pi/templates/zigbee2mqtt/z2m-configuration.yaml.j2.

  • Create Docker Compose template
name: zigbee2mqtt
services:
  mosquitto:
    image: eclipse-mosquitto:{{ raspberry_pi_mosquitto_version }}
    container_name: mosquitto
    restart: unless-stopped
    ports:
      - 1883:1883
    volumes:
      - {{ raspberry_pi_mosquitto_config_dir }}/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro
      - {{ raspberry_pi_mosquitto_config_dir }}/data:/mosquitto/data
      - {{ raspberry_pi_mosquitto_config_dir }}/log:/mosquitto/log

  zigbee2mqtt:
    image: koenkk/zigbee2mqtt:{{ raspberry_pi_z2m_version }}
    container_name: zigbee2mqtt
    restart: unless-stopped
    depends_on:
      - mosquitto
    ports:
      - 8080:8080
    volumes:
      - {{ raspberry_pi_z2m_config_dir }}/data:/app/data
      - {{ raspberry_pi_z2m_config_dir }}/configuration.yaml:/app/data/configuration.yaml
      - /run/udev:/run/udev:ro
    devices:
      - /dev/ttyUSB0:/dev/ttyUSB0
    environment:
      - TZ=Europe/Berlin
    group_add:
      - dialout

networks:
  default:
    driver: bridge
    name: zigbee2mqtt

Save to roles/raspberry_pi/templates/zigbee2mqtt/docker-compose.yml.j2.

  • Commit
git add roles/raspberry_pi/templates/
git commit -m "feat(raspberry_pi): add zigbee2mqtt and mosquitto templates"

Task 4: Create Zigbee2MQTT deploy task

Files:

  • Create: roles/raspberry_pi/tasks/20_zigbee2mqtt.yaml

  • Create deploy task file

---
- name: Deploy Mosquitto config
  ansible.builtin.template:
    src: zigbee2mqtt/mosquitto.conf.j2
    dest: "{{ raspberry_pi_mosquitto_config_dir }}/mosquitto.conf"
    mode: "0644"
  become: true
  when: inventory_hostname == 'naruto'

- name: Deploy Zigbee2MQTT config
  ansible.builtin.template:
    src: zigbee2mqtt/z2m-configuration.yaml.j2
    dest: "{{ raspberry_pi_z2m_config_dir }}/configuration.yaml"
    mode: "0644"
  become: true
  notify: restart zigbee2mqtt
  when: inventory_hostname == 'naruto'

- name: Deploy docker-compose
  ansible.builtin.template:
    src: zigbee2mqtt/docker-compose.yml.j2
    dest: "{{ raspberry_pi_compose_dir }}/docker-compose.yml"
    mode: "0644"
  become: true
  when: inventory_hostname == 'naruto'

- name: Start Zigbee2MQTT stack
  ansible.builtin.command:
    cmd: docker compose up -d
    chdir: "{{ raspberry_pi_compose_dir }}"
  become: true
  changed_when: false
  when: inventory_hostname == 'naruto'

Save to roles/raspberry_pi/tasks/20_zigbee2mqtt.yaml.

  • Commit
git add roles/raspberry_pi/tasks/20_zigbee2mqtt.yaml
git commit -m "feat(raspberry_pi): add zigbee2mqtt deploy task"

Task 5: Wire up role main.yaml

Files:

  • Modify: roles/raspberry_pi/tasks/main.yaml

  • Update main.yaml to include task files

Replace the current contents (---) with:

---
- name: Setup directories
  ansible.builtin.include_tasks: 10_directories.yaml

- name: Setup Zigbee2MQTT
  ansible.builtin.include_tasks: 20_zigbee2mqtt.yaml
  • Commit
git add roles/raspberry_pi/tasks/main.yaml
git commit -m "feat(raspberry_pi): wire up role tasks"

Task 6: Run and verify

  • Run the playbook
ansible-playbook playbooks/raspberry-pi.yaml

Expected: all tasks ok or changed on naruto, no failures. On pi, directory and zigbee2mqtt tasks should be skipped.

  • Verify containers are running on naruto
ansible naruto -a "docker ps --format 'table {{.Names}}\t{{.Status}}'" -b

Expected:

NAMES          STATUS
zigbee2mqtt    Up X seconds
mosquitto      Up X seconds
  • Check Zigbee2MQTT logs for successful startup
ssh naruto "sudo docker logs zigbee2mqtt 2>&1 | tail -20"

Expected: lines like Zigbee2MQTT started!, no errors about serial port or MQTT connection.

  • Verify Mosquitto is reachable from the LAN
ssh naruto "docker exec mosquitto mosquitto_pub -h localhost -t test -m hello && echo 'OK'"

Expected: OK

  • Verify pi tasks were skipped

Check playbook output shows skipping: [pi] for all directory and zigbee2mqtt tasks.