# 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** ```yaml --- 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** ```yaml --- - 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** ```yaml 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** ```bash 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** ```yaml --- - 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** ```bash 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** ```yaml 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** ```yaml 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** ```bash 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** ```yaml --- - 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** ```bash 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: ```yaml --- - name: Setup directories ansible.builtin.include_tasks: 10_directories.yaml - name: Setup Zigbee2MQTT ansible.builtin.include_tasks: 20_zigbee2mqtt.yaml ``` - [ ] **Commit** ```bash 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** ```bash 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** ```bash 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** ```bash 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** ```bash 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.