Compare commits
26 Commits
8da0ab98f8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e10e449333 | ||
|
|
f57ca9ac44 | ||
|
|
6325941078 | ||
|
|
36f944d1c4 | ||
|
|
cce6aba4cd | ||
|
|
f873256f65 | ||
|
|
a331265bde | ||
|
|
a905b25190 | ||
|
|
25cc5ac271 | ||
|
|
2b857903a7 | ||
|
|
eb4e8445fc | ||
|
|
3799dc16d9 | ||
|
|
585c01ca62 | ||
|
|
14b93bf4f5 | ||
|
|
42e790656d | ||
|
|
da92fb0ccc | ||
|
|
d655cc54e2 | ||
|
|
9115d30c59 | ||
|
|
8dcb429573 | ||
|
|
29cc38872c | ||
|
|
f6e2ce8c1a | ||
|
|
956836dc67 | ||
|
|
aa8b591afd | ||
|
|
935389dc6d | ||
|
|
c4327a7596 | ||
|
|
b190022ff0 |
@@ -0,0 +1,251 @@
|
||||
# Raspberry Pi Ansible Management 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:** Add `naruto` and `pi` Raspberry Pis to Ansible inventory under a new `raspberry_pi` role, starting with `common` as the base.
|
||||
|
||||
**Architecture:** New inventory group `raspberry_pi` with a dedicated role of the same name. The playbook applies both `common` and `raspberry_pi` roles. Two ARM incompatibilities in `extra_packages.yaml` are fixed in the `common` role itself so all future ARM hosts benefit.
|
||||
|
||||
**Tech Stack:** Ansible, Debian 11 (Bullseye), aarch64
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
| Action | Path | Responsibility |
|
||||
|--------|------|----------------|
|
||||
| Create | `vars/raspberry_pi.ini` | Inventory group with naruto and pi |
|
||||
| Create | `vars/group_vars/raspberry_pi/vars.yaml` | Group-level vars (empty, inherits from `all`) |
|
||||
| Modify | `roles/common/tasks/extra_packages.yaml` | Fix `bottom` arch and Neovim AppImage for ARM |
|
||||
| Create | `roles/raspberry_pi/tasks/main.yaml` | Role entry point, placeholder for future Pi tasks |
|
||||
| Create | `playbooks/raspberry-pi.yaml` | Playbook targeting `raspberry_pi` group |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add inventory and group vars
|
||||
|
||||
**Files:**
|
||||
- Create: `vars/raspberry_pi.ini`
|
||||
- Create: `vars/group_vars/raspberry_pi/vars.yaml`
|
||||
|
||||
- [ ] **Create inventory file**
|
||||
|
||||
```ini
|
||||
[raspberry_pi]
|
||||
naruto
|
||||
pi
|
||||
```
|
||||
|
||||
Save to `vars/raspberry_pi.ini`.
|
||||
|
||||
- [ ] **Create group vars file**
|
||||
|
||||
```yaml
|
||||
---
|
||||
```
|
||||
|
||||
Save to `vars/group_vars/raspberry_pi/vars.yaml`. Empty for now — both hosts inherit all vars from `vars/group_vars/all/`.
|
||||
|
||||
- [ ] **Verify Ansible can see both hosts**
|
||||
|
||||
```bash
|
||||
ansible raspberry_pi --list-hosts
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
hosts (2):
|
||||
naruto
|
||||
pi
|
||||
```
|
||||
|
||||
- [ ] **Commit**
|
||||
|
||||
```bash
|
||||
git add vars/raspberry_pi.ini vars/group_vars/raspberry_pi/vars.yaml
|
||||
git commit -m "feat(raspberry_pi): add inventory and group vars"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Fix ARM incompatibilities in `common/tasks/extra_packages.yaml`
|
||||
|
||||
**Files:**
|
||||
- Modify: `roles/common/tasks/extra_packages.yaml`
|
||||
|
||||
Two issues to fix:
|
||||
|
||||
**Issue 1 — `bottom` deb URL is hardcoded to `amd64`.** The global `arch` variable already resolves to `arm64` on aarch64 hosts.
|
||||
|
||||
**Issue 2 — Neovim AppImage doesn't run on aarch64.** `neovim` is already installed via apt in `common_packages`, so on ARM we skip the AppImage entirely and the apt version is used.
|
||||
|
||||
- [ ] **Fix `bottom` URL to use `arch` variable**
|
||||
|
||||
In `roles/common/tasks/extra_packages.yaml`, replace:
|
||||
|
||||
```yaml
|
||||
- name: Install bottom package
|
||||
ansible.builtin.apt:
|
||||
deb: https://github.com/ClementTsang/bottom/releases/download/0.9.6/bottom_0.9.6_amd64.deb
|
||||
state: present
|
||||
become: true
|
||||
```
|
||||
|
||||
With:
|
||||
|
||||
```yaml
|
||||
- name: Install bottom package
|
||||
ansible.builtin.apt:
|
||||
deb: https://github.com/ClementTsang/bottom/releases/download/0.9.6/bottom_0.9.6_{{ arch }}.deb
|
||||
state: present
|
||||
become: true
|
||||
```
|
||||
|
||||
- [ ] **Add `when: ansible_architecture != 'aarch64'` to all Neovim AppImage tasks**
|
||||
|
||||
Replace the six Neovim AppImage tasks (from "Check if Neovim is already installed" through "Remove Neovim AppImage") with the version below. The neovim config clone tasks at the end are architecture-independent and stay unchanged.
|
||||
|
||||
```yaml
|
||||
- name: Check if Neovim is already installed
|
||||
ansible.builtin.command: "which nvim"
|
||||
register: neovim_installed
|
||||
changed_when: false
|
||||
ignore_errors: true
|
||||
when: ansible_architecture != 'aarch64'
|
||||
|
||||
- name: Download Neovim AppImage
|
||||
ansible.builtin.get_url:
|
||||
url: https://github.com/neovim/neovim/releases/download/v0.10.0/nvim.appimage
|
||||
dest: /tmp/nvim.appimage
|
||||
mode: "0755"
|
||||
when: ansible_architecture != 'aarch64' and neovim_installed.rc != 0
|
||||
register: download_result
|
||||
|
||||
- name: Extract Neovim AppImage
|
||||
ansible.builtin.command:
|
||||
cmd: "./nvim.appimage --appimage-extract"
|
||||
chdir: /tmp
|
||||
when: ansible_architecture != 'aarch64' and download_result.changed
|
||||
register: extract_result
|
||||
|
||||
- name: Copy extracted Neovim files to /usr
|
||||
ansible.builtin.copy:
|
||||
src: /tmp/squashfs-root/usr/
|
||||
dest: /usr/
|
||||
remote_src: true
|
||||
mode: "0755"
|
||||
become: true
|
||||
when: ansible_architecture != 'aarch64' and extract_result.changed
|
||||
|
||||
- name: Clean up extracted Neovim files
|
||||
ansible.builtin.file:
|
||||
path: /tmp/squashfs-root
|
||||
state: absent
|
||||
when: ansible_architecture != 'aarch64' and extract_result.changed
|
||||
|
||||
- name: Remove Neovim AppImage
|
||||
ansible.builtin.file:
|
||||
path: /tmp/nvim.appimage
|
||||
state: absent
|
||||
when: ansible_architecture != 'aarch64' and download_result.changed
|
||||
```
|
||||
|
||||
- [ ] **Commit**
|
||||
|
||||
```bash
|
||||
git add roles/common/tasks/extra_packages.yaml
|
||||
git commit -m "fix(common): support aarch64 in extra_packages"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Create `raspberry_pi` role
|
||||
|
||||
**Files:**
|
||||
- Create: `roles/raspberry_pi/tasks/main.yaml`
|
||||
|
||||
- [ ] **Create role task entry point**
|
||||
|
||||
```yaml
|
||||
---
|
||||
```
|
||||
|
||||
Save to `roles/raspberry_pi/tasks/main.yaml`. Intentionally empty for now — Pi-specific workloads (Newt on naruto, docker stack on pi) are added in future tasks.
|
||||
|
||||
- [ ] **Commit**
|
||||
|
||||
```bash
|
||||
git add roles/raspberry_pi/tasks/main.yaml
|
||||
git commit -m "feat(raspberry_pi): add empty role scaffold"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Create playbook
|
||||
|
||||
**Files:**
|
||||
- Create: `playbooks/raspberry-pi.yaml`
|
||||
|
||||
- [ ] **Create playbook**
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Set up Raspberry Pis
|
||||
hosts: raspberry_pi
|
||||
gather_facts: true
|
||||
roles:
|
||||
- role: common
|
||||
tags:
|
||||
- common
|
||||
- role: raspberry_pi
|
||||
tags:
|
||||
- raspberry_pi
|
||||
```
|
||||
|
||||
Save to `playbooks/raspberry-pi.yaml`.
|
||||
|
||||
- [ ] **Commit**
|
||||
|
||||
```bash
|
||||
git add playbooks/raspberry-pi.yaml
|
||||
git commit -m "feat(raspberry_pi): add playbook"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Run and verify
|
||||
|
||||
- [ ] **Dry-run against both hosts**
|
||||
|
||||
```bash
|
||||
ansible-playbook playbooks/raspberry-pi.yaml --check
|
||||
```
|
||||
|
||||
Note: the `apt upgrade` task will fail in check mode without `python3-apt` on the remote (same issue seen with mii). If it fails there, proceed to the real run.
|
||||
|
||||
- [ ] **Run for real**
|
||||
|
||||
```bash
|
||||
ansible-playbook playbooks/raspberry-pi.yaml
|
||||
```
|
||||
|
||||
Expected: all tasks `ok` or `changed`, no failures. Watch for:
|
||||
- `bottom` task — should download `arm64` deb
|
||||
- Neovim AppImage tasks — should be skipped on both hosts
|
||||
- Hostname task — `pi` will be renamed from `raspberrypi` to `pi`
|
||||
|
||||
- [ ] **Verify hostname on pi was updated**
|
||||
|
||||
```bash
|
||||
ssh pi "hostname"
|
||||
```
|
||||
|
||||
Expected: `pi`
|
||||
|
||||
- [ ] **Verify bottom installed correctly on both**
|
||||
|
||||
```bash
|
||||
ansible raspberry_pi -a "btm --version"
|
||||
```
|
||||
|
||||
Expected: version string printed for both hosts, no errors.
|
||||
356
docs/superpowers/plans/2026-06-03-naruto-zigbee2mqtt.md
Normal file
356
docs/superpowers/plans/2026-06-03-naruto-zigbee2mqtt.md
Normal file
@@ -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.
|
||||
@@ -0,0 +1,36 @@
|
||||
# Raspberry Pi Ansible Management
|
||||
|
||||
**Date:** 2026-05-29
|
||||
|
||||
## Goal
|
||||
|
||||
Bring `naruto` (Pi 4, 8GB) and `pi` (Pi 3, 1GB) under Ansible management using a new `raspberry_pi` role that starts with the `common` role as its base.
|
||||
|
||||
## Inventory
|
||||
|
||||
New file `vars/raspberry_pi.ini` with a `[raspberry_pi]` group containing both hosts. Both connect as user `tudattr` (non-root, sudo available).
|
||||
|
||||
## ARM Fixes in `common` Role
|
||||
|
||||
Two tasks in `extra_packages.yaml` are amd64-only and must be fixed before running on ARM:
|
||||
|
||||
- **bottom:** URL is hardcoded to `amd64.deb`. Fix to use the existing `arch` global variable so it resolves to `arm64` on aarch64 hosts.
|
||||
- **Neovim:** Fetched as an AppImage, which doesn't run on aarch64. Fix to install `neovim` via apt on ARM, skipping the AppImage path.
|
||||
|
||||
These fixes apply to the `common` role itself so any future ARM host benefits.
|
||||
|
||||
## New Role: `raspberry_pi`
|
||||
|
||||
Structure mirrors other roles. `tasks/main.yaml` includes `common` tasks, then Pi-specific tasks (none yet — placeholder for future workloads like Newt on naruto, docker stack on pi).
|
||||
|
||||
## New Playbook
|
||||
|
||||
`playbooks/raspberry-pi.yaml` targets `raspberry_pi` group, applies `raspberry_pi` role with tag `raspberry_pi`.
|
||||
|
||||
## Group Vars
|
||||
|
||||
`vars/group_vars/raspberry_pi/vars.yaml` — empty for now, inherits all from `all`. Can hold Pi-specific overrides later.
|
||||
|
||||
## Hostname
|
||||
|
||||
`pi` is currently named `raspberrypi`. The `common` hostname task will rename it to `pi` to match the inventory name.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Zigbee2MQTT + Mosquitto on naruto — Design Spec
|
||||
|
||||
**Date:** 2026-06-03
|
||||
|
||||
## Goal
|
||||
|
||||
Run Zigbee2MQTT and Mosquitto as Docker containers on naruto, managed by the `raspberry_pi` Ansible role. Home Assistant (running in k3s) connects to Mosquitto over the LAN.
|
||||
|
||||
## Hardware
|
||||
|
||||
- Host: naruto (Pi 4, 192.168.20.13)
|
||||
- Zigbee coordinator: SONOFF Dongle Lite MG21 on `/dev/ttyUSB0`
|
||||
- Stable by-id path: `/dev/serial/by-id/usb-SONOFF_SONOFF_Dongle_Lite_MG21_0263f93f46a2ef11b078926661ce3355-if00-port0`
|
||||
|
||||
## Architecture
|
||||
|
||||
Two containers via Docker Compose on naruto. Ansible templates all configs and manages the stack. Home Assistant adds the MQTT integration pointing at `192.168.20.13:1883`.
|
||||
|
||||
```
|
||||
[SONOFF Dongle /dev/ttyUSB0]
|
||||
|
|
||||
[zigbee2mqtt container]
|
||||
| MQTT (internal docker network)
|
||||
[mosquitto container] :1883
|
||||
|
|
||||
[Home Assistant in k3s] — via LAN 192.168.20.13:1883
|
||||
```
|
||||
|
||||
## Directory Layout on naruto
|
||||
|
||||
```
|
||||
/opt/docker/
|
||||
config/
|
||||
mosquitto/
|
||||
mosquitto.conf
|
||||
data/
|
||||
log/
|
||||
zigbee2mqtt/
|
||||
configuration.yaml
|
||||
data/
|
||||
compose/
|
||||
docker-compose.yml
|
||||
```
|
||||
|
||||
## Mosquitto Config
|
||||
|
||||
- Listens on port 1883
|
||||
- No authentication (internal LAN only)
|
||||
- Persistence enabled, logs to `/opt/docker/config/mosquitto/log/`
|
||||
|
||||
## Zigbee2MQTT Config
|
||||
|
||||
- Serial port: `/dev/serial/by-id/usb-SONOFF_SONOFF_Dongle_Lite_MG21_0263f93f46a2ef11b078926661ce3355-if00-port0`
|
||||
- MQTT broker: `mqtt://mosquitto:1883` (internal docker network)
|
||||
- Network key: stored in `vars/group_vars/raspberry_pi/secrets.yaml` as `vault_raspberry_pi.zigbee2mqtt.network_key`
|
||||
- Frontend enabled on port 8080 for local device management
|
||||
|
||||
## Secrets
|
||||
|
||||
`vars/group_vars/raspberry_pi/secrets.yaml` (vault-encrypted, placeholder for now):
|
||||
|
||||
```yaml
|
||||
vault_raspberry_pi:
|
||||
zigbee2mqtt:
|
||||
network_key: "YOUR_ZIGBEE_NETWORK_KEY"
|
||||
```
|
||||
|
||||
## Ansible Changes
|
||||
|
||||
| Action | Path | Responsibility |
|
||||
|--------|------|----------------|
|
||||
| Modify | `roles/raspberry_pi/tasks/main.yaml` | Include numbered task files |
|
||||
| Create | `roles/raspberry_pi/tasks/10_directories.yaml` | Create `/opt/docker/` tree |
|
||||
| Create | `roles/raspberry_pi/tasks/20_zigbee2mqtt.yaml` | Template configs, start compose |
|
||||
| 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` | Network key placeholder |
|
||||
|
||||
## Host Constraint
|
||||
|
||||
The `raspberry_pi` role applies to both naruto and pi. The Zigbee2MQTT tasks must be guarded with `when: inventory_hostname == 'naruto'` since the USB dongle is only on naruto.
|
||||
11
playbooks/raspberry-pi.yaml
Normal file
11
playbooks/raspberry-pi.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Set up Raspberry Pis
|
||||
hosts: raspberry_pi
|
||||
gather_facts: true
|
||||
roles:
|
||||
- role: common
|
||||
tags:
|
||||
- common
|
||||
- role: raspberry_pi
|
||||
tags:
|
||||
- raspberry_pi
|
||||
8
playbooks/vps.yaml
Normal file
8
playbooks/vps.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
- name: Set up VPS
|
||||
hosts: vps
|
||||
gather_facts: true
|
||||
roles:
|
||||
- role: edge_vps
|
||||
tags:
|
||||
- edge_vps
|
||||
@@ -1,4 +1,9 @@
|
||||
---
|
||||
- name: Update apt cache
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
become: true
|
||||
|
||||
- name: Restart sshd
|
||||
service:
|
||||
name: sshd
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
- name: Copy bash-configs
|
||||
ansible.builtin.template:
|
||||
src: "files/bash/{{ item }}"
|
||||
dest: "{{ ansible_env.HOME }}/.{{ item }}"
|
||||
owner: "{{ ansible_user_id }}"
|
||||
group: "{{ ansible_user_id }}"
|
||||
dest: "{{ ansible_facts['env']['HOME'] }}/.{{ item }}"
|
||||
owner: "{{ ansible_facts['user_id'] }}"
|
||||
group: "{{ ansible_facts['user_id'] }}"
|
||||
mode: "644"
|
||||
loop:
|
||||
- bashrc
|
||||
@@ -13,25 +13,25 @@
|
||||
- name: Copy ghostty infocmp
|
||||
ansible.builtin.copy:
|
||||
src: files/ghostty/infocmp
|
||||
dest: "{{ ansible_env.HOME }}/ghostty"
|
||||
owner: "{{ ansible_user_id }}"
|
||||
group: "{{ ansible_user_id }}"
|
||||
dest: "{{ ansible_facts['env']['HOME'] }}/ghostty"
|
||||
owner: "{{ ansible_facts['user_id'] }}"
|
||||
group: "{{ ansible_facts['user_id'] }}"
|
||||
mode: "0644"
|
||||
register: ghostty_terminfo
|
||||
|
||||
- name: Compile ghostty terminalinfo
|
||||
ansible.builtin.command: "tic -x {{ ansible_env.HOME }}/ghostty"
|
||||
ansible.builtin.command: "tic -x {{ ansible_facts['env']['HOME'] }}/ghostty"
|
||||
when: ghostty_terminfo.changed
|
||||
|
||||
- name: Copy kitty infocmp
|
||||
ansible.builtin.copy:
|
||||
src: files/kitty/infocmp
|
||||
dest: "{{ ansible_env.HOME }}/kitty"
|
||||
owner: "{{ ansible_user_id }}"
|
||||
group: "{{ ansible_user_id }}"
|
||||
dest: "{{ ansible_facts['env']['HOME'] }}/kitty"
|
||||
owner: "{{ ansible_facts['user_id'] }}"
|
||||
group: "{{ ansible_facts['user_id'] }}"
|
||||
mode: "0644"
|
||||
register: kitty_terminfo
|
||||
|
||||
- name: Compile kitty terminalinfo
|
||||
ansible.builtin.command: "tic -x {{ ansible_env.HOME }}/kitty"
|
||||
ansible.builtin.command: "tic -x {{ ansible_facts['env']['HOME'] }}/kitty"
|
||||
when: kitty_terminfo.changed
|
||||
|
||||
@@ -14,11 +14,17 @@
|
||||
become: true
|
||||
|
||||
- name: Add Gierens repository to apt sources
|
||||
ansible.builtin.apt_repository:
|
||||
repo: "deb [signed-by=/etc/apt/keyrings/gierens.asc] http://deb.gierens.de stable main"
|
||||
ansible.builtin.deb822_repository:
|
||||
name: gierens
|
||||
types: deb
|
||||
uris: http://deb.gierens.de
|
||||
suites: stable
|
||||
components: main
|
||||
signed_by: /etc/apt/keyrings/gierens.asc
|
||||
state: present
|
||||
update_cache: true
|
||||
install_python_debian: true
|
||||
become: true
|
||||
notify: Update apt cache
|
||||
|
||||
- name: Install eza package
|
||||
ansible.builtin.apt:
|
||||
@@ -28,7 +34,7 @@
|
||||
|
||||
- name: Install bottom package
|
||||
ansible.builtin.apt:
|
||||
deb: https://github.com/ClementTsang/bottom/releases/download/0.9.6/bottom_0.9.6_amd64.deb
|
||||
deb: https://github.com/ClementTsang/bottom/releases/download/0.9.6/bottom_0.9.6_{{ arch }}.deb
|
||||
state: present
|
||||
become: true
|
||||
|
||||
@@ -37,20 +43,21 @@
|
||||
register: neovim_installed
|
||||
changed_when: false
|
||||
ignore_errors: true
|
||||
when: ansible_facts['architecture'] != 'aarch64'
|
||||
|
||||
- name: Download Neovim AppImage
|
||||
ansible.builtin.get_url:
|
||||
url: https://github.com/neovim/neovim/releases/download/v0.10.0/nvim.appimage
|
||||
dest: /tmp/nvim.appimage
|
||||
mode: "0755"
|
||||
when: neovim_installed.rc != 0
|
||||
when: ansible_facts['architecture'] != 'aarch64' and neovim_installed.rc != 0
|
||||
register: download_result
|
||||
|
||||
- name: Extract Neovim AppImage
|
||||
ansible.builtin.command:
|
||||
cmd: "./nvim.appimage --appimage-extract"
|
||||
chdir: /tmp
|
||||
when: download_result.changed
|
||||
when: ansible_facts['architecture'] != 'aarch64' and download_result.changed
|
||||
register: extract_result
|
||||
|
||||
- name: Copy extracted Neovim files to /usr
|
||||
@@ -60,19 +67,19 @@
|
||||
remote_src: true
|
||||
mode: "0755"
|
||||
become: true
|
||||
when: extract_result.changed
|
||||
when: ansible_facts['architecture'] != 'aarch64' and extract_result.changed
|
||||
|
||||
- name: Clean up extracted Neovim files
|
||||
ansible.builtin.file:
|
||||
path: /tmp/squashfs-root
|
||||
state: absent
|
||||
when: extract_result.changed
|
||||
when: ansible_facts['architecture'] != 'aarch64' and extract_result.changed
|
||||
|
||||
- name: Remove Neovim AppImage
|
||||
ansible.builtin.file:
|
||||
path: /tmp/nvim.appimage
|
||||
state: absent
|
||||
when: download_result.changed
|
||||
when: ansible_facts['architecture'] != 'aarch64' and download_result.changed
|
||||
|
||||
- name: Check if Neovim config directory already exists
|
||||
ansible.builtin.stat:
|
||||
|
||||
@@ -5,24 +5,24 @@
|
||||
upgrade: true
|
||||
autoremove: true
|
||||
become: true
|
||||
when: ansible_user_id != "root"
|
||||
when: ansible_facts['user_id'] != "root"
|
||||
|
||||
- name: Install base packages
|
||||
ansible.builtin.apt:
|
||||
name: "{{ common_packages }}"
|
||||
state: present
|
||||
become: true
|
||||
when: ansible_user_id != "root"
|
||||
when: ansible_facts['user_id'] != "root"
|
||||
|
||||
- name: Update and upgrade packages
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
upgrade: true
|
||||
autoremove: true
|
||||
when: ansible_user_id == "root"
|
||||
when: ansible_facts['user_id'] == "root"
|
||||
|
||||
- name: Install base packages
|
||||
ansible.builtin.apt:
|
||||
name: "{{ common_packages }}"
|
||||
state: present
|
||||
when: ansible_user_id == "root"
|
||||
when: ansible_facts['user_id'] == "root"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
notify:
|
||||
- Restart sshd
|
||||
become: true
|
||||
when: ansible_user_id != "root"
|
||||
when: ansible_facts['user_id'] != "root"
|
||||
|
||||
- name: Copy root sshd_config
|
||||
ansible.builtin.template:
|
||||
@@ -18,7 +18,7 @@
|
||||
backup: true
|
||||
notify:
|
||||
- Restart sshd
|
||||
when: ansible_user_id == "root"
|
||||
when: ansible_facts['user_id'] == "root"
|
||||
|
||||
- name: Copy pubkey
|
||||
ansible.builtin.copy:
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
community.general.timezone:
|
||||
name: "{{ timezone }}"
|
||||
become: true
|
||||
when: ansible_user_id != "root"
|
||||
when: ansible_facts['user_id'] != "root"
|
||||
|
||||
- name: Set timezone
|
||||
community.general.timezone:
|
||||
name: "{{ timezone }}"
|
||||
when: ansible_user_id == "root"
|
||||
when: ansible_facts['user_id'] == "root"
|
||||
|
||||
- name: Configure NTP servers for systemd-timesyncd
|
||||
ansible.builtin.lineinfile:
|
||||
@@ -24,11 +24,11 @@
|
||||
enabled: true
|
||||
state: started
|
||||
become: true
|
||||
when: ansible_user_id != "root"
|
||||
when: ansible_facts['user_id'] != "root"
|
||||
|
||||
- name: Enable and start systemd-timesyncd
|
||||
ansible.builtin.systemd:
|
||||
name: systemd-timesyncd
|
||||
enabled: true
|
||||
state: started
|
||||
when: ansible_user_id == "root"
|
||||
when: ansible_facts['user_id'] == "root"
|
||||
|
||||
@@ -6,6 +6,10 @@ edge_vps_wireguard_address: "10.133.7.1/24"
|
||||
edge_vps_wireguard_port: 61975
|
||||
edge_vps_traefik_config_dir: "{{ edge_vps_config_base }}/traefik"
|
||||
edge_vps_traefik_logs_dir: "{{ edge_vps_traefik_config_dir }}/logs"
|
||||
edge_vps_pangolin_config_dir: "{{ edge_vps_config_base }}/pangolin"
|
||||
edge_vps_elastic_config_dir: "{{ edge_vps_config_base }}/elastic-agent"
|
||||
edge_vps_pangolin_config_dir: "{{ edge_vps_config_base }}"
|
||||
edge_vps_pangolin_compose_dir: /root
|
||||
edge_vps_pangolin_version: "1.12.1"
|
||||
edge_vps_gerbil_version: "1.2.2"
|
||||
edge_vps_traefik_version: "v3.5"
|
||||
edge_vps_elastic_config_dir: /root/agent
|
||||
edge_vps_elastic_state_dir: /var/lib/elastic-agent/elastic-system/elastic-agent/state
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
|
||||
- name: Restart traefik
|
||||
ansible.builtin.command:
|
||||
cmd: docker compose restart
|
||||
chdir: "{{ edge_vps_traefik_config_dir }}"
|
||||
cmd: podman compose restart traefik
|
||||
chdir: "{{ edge_vps_pangolin_compose_dir }}"
|
||||
listen: restart traefik
|
||||
|
||||
- name: Restart pangolin
|
||||
ansible.builtin.command:
|
||||
cmd: podman compose restart pangolin
|
||||
chdir: "{{ edge_vps_pangolin_compose_dir }}"
|
||||
listen: restart pangolin
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
- "{{ edge_vps_traefik_config_dir }}"
|
||||
- "{{ edge_vps_traefik_logs_dir }}"
|
||||
|
||||
- name: Create Pangolin config directory
|
||||
- name: Create Pangolin letsencrypt directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ edge_vps_pangolin_config_dir }}"
|
||||
path: "{{ edge_vps_pangolin_config_dir }}/letsencrypt"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
mode: "0644"
|
||||
notify: restart traefik
|
||||
|
||||
- name: Deploy Cloudflare credentials for ACME
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
CF_DNS_API_TOKEN={{ vault_edge_vps.traefik.cloudflare_api_token }}
|
||||
dest: "{{ edge_vps_traefik_config_dir }}/cloudflare.env"
|
||||
mode: "0600"
|
||||
no_log: true
|
||||
- name: Deploy Traefik dynamic config
|
||||
ansible.builtin.template:
|
||||
src: traefik/dynamic_config.yml.j2
|
||||
dest: "{{ edge_vps_traefik_config_dir }}/dynamic_config.yml"
|
||||
mode: "0644"
|
||||
notify: restart traefik
|
||||
|
||||
@@ -9,16 +9,11 @@
|
||||
- name: Deploy Pangolin docker-compose
|
||||
ansible.builtin.template:
|
||||
src: pangolin/docker-compose.yml.j2
|
||||
dest: "{{ edge_vps_pangolin_config_dir }}/docker-compose.yml"
|
||||
dest: "{{ edge_vps_pangolin_compose_dir }}/docker-compose.yml"
|
||||
mode: "0644"
|
||||
|
||||
- name: Create letsencrypt directory for Pangolin
|
||||
ansible.builtin.file:
|
||||
path: "{{ edge_vps_pangolin_config_dir }}/letsencrypt"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Start Pangolin
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ edge_vps_pangolin_config_dir }}"
|
||||
state: present
|
||||
ansible.builtin.command:
|
||||
cmd: podman compose up -d
|
||||
chdir: "{{ edge_vps_pangolin_compose_dir }}"
|
||||
changed_when: false
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
mode: "0644"
|
||||
|
||||
- name: Start Elastic Agent
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ edge_vps_elastic_config_dir }}"
|
||||
state: present
|
||||
ansible.builtin.command:
|
||||
cmd: podman compose up -d
|
||||
chdir: "{{ edge_vps_elastic_config_dir }}"
|
||||
changed_when: false
|
||||
|
||||
@@ -1,25 +1,58 @@
|
||||
name: pangolin
|
||||
services:
|
||||
pangolin:
|
||||
image: fosrl/pangolin:latest
|
||||
image: docker.io/fosrl/pangolin:{{ edge_vps_pangolin_version }}
|
||||
container_name: pangolin
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3001:3001"
|
||||
- "443:443"
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./config.yml:/app/config/config.yml:ro
|
||||
- ./letsencrypt:/letsencrypt
|
||||
depends_on:
|
||||
- gerbil
|
||||
- ./config:/app/config
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
|
||||
interval: "10s"
|
||||
timeout: "10s"
|
||||
retries: 15
|
||||
|
||||
gerbil:
|
||||
image: fosrl/gerbil:latest
|
||||
image: docker.io/fosrl/gerbil:{{ edge_vps_gerbil_version }}
|
||||
container_name: gerbil
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
depends_on:
|
||||
pangolin:
|
||||
condition: service_healthy
|
||||
command:
|
||||
- --reachableAt=http://gerbil:3004
|
||||
- --generateAndSaveKeyTo=/var/config/key
|
||||
- --remoteConfig=http://pangolin:3001/api/v1/
|
||||
volumes:
|
||||
- ./config/:/var/config
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
ports:
|
||||
- 51820:51820/udp
|
||||
- 21820:21820/udp
|
||||
- 443:443
|
||||
- 80:80
|
||||
- 6443:6443
|
||||
|
||||
traefik:
|
||||
image: docker.io/traefik:{{ edge_vps_traefik_version }}
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
network_mode: service:gerbil
|
||||
depends_on:
|
||||
pangolin:
|
||||
condition: service_healthy
|
||||
command:
|
||||
- --configFile=/etc/traefik/traefik_config.yml
|
||||
environment:
|
||||
CLOUDFLARE_DNS_API_TOKEN: {{ vault_edge_vps.traefik.cloudflare_api_token }}
|
||||
volumes:
|
||||
- /lib/modules:/lib/modules
|
||||
- ./config/traefik:/etc/traefik:ro
|
||||
- ./config/letsencrypt:/letsencrypt
|
||||
- ./config/traefik/logs:/var/log/traefik
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
name: pangolin
|
||||
|
||||
67
roles/edge_vps/templates/traefik/dynamic_config.yml.j2
Normal file
67
roles/edge_vps/templates/traefik/dynamic_config.yml.j2
Normal file
@@ -0,0 +1,67 @@
|
||||
http:
|
||||
middlewares:
|
||||
redirect-to-https:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
|
||||
routers:
|
||||
main-app-router-redirect:
|
||||
rule: "Host(`{{ edge_vps_pangolin_dashboard_url | regex_replace('^https?://', '') }}`)"
|
||||
service: next-service
|
||||
entryPoints:
|
||||
- web
|
||||
middlewares:
|
||||
- redirect-to-https
|
||||
|
||||
next-router:
|
||||
rule: "Host(`{{ edge_vps_pangolin_dashboard_url | regex_replace('^https?://', '') }}`) && !PathPrefix(`/api/v1`)"
|
||||
service: next-service
|
||||
entryPoints:
|
||||
- websecure
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
domains:
|
||||
- main: "{{ edge_vps_pangolin_base_domain }}"
|
||||
sans:
|
||||
- "*.{{ edge_vps_pangolin_base_domain }}"
|
||||
{% for domain in edge_vps_traefik_extra_tls_domains | default([]) %}
|
||||
- main: "{{ domain }}"
|
||||
sans:
|
||||
- "*.{{ domain }}"
|
||||
{% endfor %}
|
||||
|
||||
api-router:
|
||||
rule: "Host(`{{ edge_vps_pangolin_dashboard_url | regex_replace('^https?://', '') }}`) && PathPrefix(`/api/v1`)"
|
||||
service: api-service
|
||||
entryPoints:
|
||||
- websecure
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
|
||||
ws-router:
|
||||
rule: "Host(`{{ edge_vps_pangolin_dashboard_url | regex_replace('^https?://', '') }}`)"
|
||||
service: api-service
|
||||
entryPoints:
|
||||
- websecure
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
|
||||
services:
|
||||
next-service:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://pangolin:3002"
|
||||
|
||||
api-service:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://pangolin:3000"
|
||||
|
||||
tcp:
|
||||
serversTransports:
|
||||
pp-transport-v1:
|
||||
proxyProtocol:
|
||||
version: 1
|
||||
pp-transport-v2:
|
||||
proxyProtocol:
|
||||
version: 2
|
||||
@@ -7,12 +7,12 @@ PostUp = sysctl -w net.ipv4.ip_forward=1
|
||||
PostUp = iptables -A FORWARD -i {{ edge_vps_wireguard_interface }} -j ACCEPT
|
||||
PostUp = iptables -A FORWARD -o {{ edge_vps_wireguard_interface }} -j ACCEPT
|
||||
{% for route in edge_vps_wireguard_routes | default([]) %}
|
||||
PostUp = ip route add {{ route }} via {{ route.gateway }} dev {{ edge_vps_wireguard_interface }}
|
||||
PostUp = ip route add {{ route.network }} via {{ route.gateway }} dev {{ edge_vps_wireguard_interface }}
|
||||
{% endfor %}
|
||||
PostDown = iptables -D FORWARD -i {{ edge_vps_wireguard_interface }} -j ACCEPT
|
||||
PostDown = iptables -D FORWARD -o {{ edge_vps_wireguard_interface }} -j ACCEPT
|
||||
{% for route in edge_vps_wireguard_routes | default([]) %}
|
||||
PostDown = ip route del {{ route }} via {{ route.gateway }} dev {{ edge_vps_wireguard_interface }}
|
||||
PostDown = ip route del {{ route.network }} via {{ route.gateway }} dev {{ edge_vps_wireguard_interface }}
|
||||
{% endfor %}
|
||||
|
||||
{% for peer in vault_edge_vps.wireguard.peers %}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
tags: "{{ proxmox_tags }}"
|
||||
description: "Created via Ansible with cloud-init"
|
||||
boot: "order=scsi0"
|
||||
cpu: "x86-64-v2-AES"
|
||||
cpu: "{{ proxmox_node_cpu[vm.node] | default('x86-64-v2-AES') }}"
|
||||
ciuser: "{{ vm.ciuser }}"
|
||||
cipassword: "{{ vm_secrets[proxmox_secrets_prefix + '_' + vm.name.replace('-', '_')] }}"
|
||||
ipconfig:
|
||||
|
||||
7
roles/raspberry_pi/defaults/main.yaml
Normal file
7
roles/raspberry_pi/defaults/main.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
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/zigbee2mqtt"
|
||||
raspberry_pi_mosquitto_version: "2"
|
||||
raspberry_pi_z2m_version: "2"
|
||||
6
roles/raspberry_pi/handlers/main.yaml
Normal file
6
roles/raspberry_pi/handlers/main.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Restart zigbee2mqtt
|
||||
ansible.builtin.command:
|
||||
cmd: docker compose restart zigbee2mqtt
|
||||
chdir: "{{ raspberry_pi_compose_dir }}"
|
||||
listen: restart zigbee2mqtt
|
||||
34
roles/raspberry_pi/tasks/10_directories.yaml
Normal file
34
roles/raspberry_pi/tasks/10_directories.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
- 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'
|
||||
40
roles/raspberry_pi/tasks/20_zigbee2mqtt.yaml
Normal file
40
roles/raspberry_pi/tasks/20_zigbee2mqtt.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
- name: Install docker-compose-plugin
|
||||
ansible.builtin.apt:
|
||||
name: docker-compose-plugin
|
||||
state: present
|
||||
become: true
|
||||
when: inventory_hostname == 'naruto'
|
||||
|
||||
- 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'
|
||||
6
roles/raspberry_pi/tasks/main.yaml
Normal file
6
roles/raspberry_pi/tasks/main.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Setup directories
|
||||
ansible.builtin.include_tasks: 10_directories.yaml
|
||||
|
||||
- name: Setup Zigbee2MQTT
|
||||
ansible.builtin.include_tasks: 20_zigbee2mqtt.yaml
|
||||
@@ -0,0 +1,36 @@
|
||||
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:
|
||||
- 8081: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
|
||||
@@ -0,0 +1,5 @@
|
||||
listener 1883
|
||||
persistence true
|
||||
persistence_location /mosquitto/data/
|
||||
log_dest file /mosquitto/log/mosquitto.log
|
||||
allow_anonymous true
|
||||
@@ -0,0 +1,17 @@
|
||||
homeassistant:
|
||||
enabled: true
|
||||
|
||||
mqtt:
|
||||
server: mqtt://mosquitto:1883
|
||||
|
||||
serial:
|
||||
port: /dev/ttyUSB0
|
||||
adapter: ember
|
||||
|
||||
advanced:
|
||||
network_key: {{ vault_raspberry_pi.zigbee2mqtt.network_key }}
|
||||
log_level: info
|
||||
|
||||
frontend:
|
||||
enabled: true
|
||||
port: 8080
|
||||
@@ -19,7 +19,7 @@ nfs_server: 192.168.20.12
|
||||
# Packages
|
||||
#
|
||||
|
||||
arch: "{{ 'arm64' if ansible_architecture == 'aarch64' else 'amd64' }}"
|
||||
arch: "{{ 'arm64' if ansible_facts['architecture'] == 'aarch64' else 'amd64' }}"
|
||||
|
||||
netcup_api_key: "{{ vault_netcup.api_key }}"
|
||||
netcup_api_password: "{{ vault_netcup.api_password }}"
|
||||
|
||||
@@ -2,3 +2,12 @@ proxmox_api_host: 192.168.20.12
|
||||
proxmox_api_user: root
|
||||
proxmox_api_token_id: terraform
|
||||
proxmox_api_token_secret: "{{ vault_pve.api.token_secret }}"
|
||||
|
||||
# CPU type per Proxmox node — x86-64-v3 requires AVX2 (Ryzen 5700U, N100, i5-7200U)
|
||||
# aya01 (Celeron N5105) tops out at SSE4.2, must stay at v2
|
||||
proxmox_node_cpu:
|
||||
aya01: "x86-64-v2-AES"
|
||||
inko01: "x86-64-v3"
|
||||
lulu: "x86-64-v3"
|
||||
mii01: "x86-64-v3"
|
||||
naruto01: "x86-64-v3"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
vms:
|
||||
- name: "docker-host11"
|
||||
node: "inko01"
|
||||
node: "aya01"
|
||||
vmid: 411
|
||||
cores: 2
|
||||
memory: 4096 # in MiB
|
||||
@@ -68,7 +68,7 @@ vms:
|
||||
sshkeys: "{{ pubkey }}"
|
||||
disk_size: 32 # in Gb
|
||||
- name: "k3s-server11"
|
||||
node: "inko01"
|
||||
node: "aya01"
|
||||
vmid: 111
|
||||
cores: 2
|
||||
memory: 4096 # in MiB
|
||||
@@ -189,7 +189,7 @@ vms:
|
||||
sshkeys: "{{ pubkey }}"
|
||||
disk_size: 128
|
||||
- name: "k3s-agent21"
|
||||
node: "inko01"
|
||||
node: "aya01"
|
||||
vmid: 221
|
||||
cores: 2
|
||||
memory: 4096
|
||||
|
||||
3
vars/group_vars/raspberry_pi/secrets.yaml
Normal file
3
vars/group_vars/raspberry_pi/secrets.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
vault_raspberry_pi:
|
||||
zigbee2mqtt:
|
||||
network_key: "GENERATE"
|
||||
1
vars/group_vars/raspberry_pi/vars.yaml
Normal file
1
vars/group_vars/raspberry_pi/vars.yaml
Normal file
@@ -0,0 +1 @@
|
||||
---
|
||||
@@ -9,6 +9,8 @@ edge_vps_pangolin_base_endpoint: "pangolin.seyshiro.de"
|
||||
edge_vps_pangolin_base_domain: "seyshiro.de"
|
||||
|
||||
edge_vps_acme_email: "me+acme@tudattr.dev"
|
||||
edge_vps_traefik_extra_tls_domains:
|
||||
- "tudattr.dev"
|
||||
|
||||
edge_vps_elastic_version: "9.2.2"
|
||||
edge_vps_elastic_dns_server: "10.43.0.10"
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
[k3s:children]
|
||||
k3s_server
|
||||
k3s_agent
|
||||
k3s_storage
|
||||
k3s_loadbalancer
|
||||
|
||||
[k3s_server]
|
||||
|
||||
3
vars/raspberry_pi.ini
Normal file
3
vars/raspberry_pi.ini
Normal file
@@ -0,0 +1,3 @@
|
||||
[raspberry_pi]
|
||||
naruto
|
||||
pi
|
||||
Reference in New Issue
Block a user