diff --git a/docs/superpowers/plans/2026-06-03-naruto-zigbee2mqtt.md b/docs/superpowers/plans/2026-06-03-naruto-zigbee2mqtt.md new file mode 100644 index 0000000..540a930 --- /dev/null +++ b/docs/superpowers/plans/2026-06-03-naruto-zigbee2mqtt.md @@ -0,0 +1,356 @@ +# 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.