30 Commits

Author SHA1 Message Date
Tuan-Dat Tran
e10e449333 feat(proxmox): per-node CPU type based on hardware capabilities
Add proxmox_node_cpu map — aya01 (Celeron N5105, no AVX2) stays at
x86-64-v2-AES; inko01/lulu/mii01/naruto01 (all AVX2-capable) use x86-64-v3.
Task looks up cpu type by vm.node with x86-64-v2-AES as fallback.
2026-06-04 23:32:18 +02:00
Tuan-Dat Tran
f57ca9ac44 fix(proxmox): correct VM node assignments and upgrade CPU to x86-64-v3
- docker-host11, k3s-server11, k3s-agent21 moved from inko01 → aya01
- CPU type x86-64-v2-AES → x86-64-v3 to enable AVX2 (required by vLLM CPU image)
2026-06-04 23:19:08 +02:00
Tuan-Dat Tran
6325941078 docs: add raspberry-pi ansible management plan and spec 2026-06-04 01:45:16 +02:00
Tuan-Dat Tran
36f944d1c4 feat(edge_vps): add vps playbook 2026-06-04 01:45:16 +02:00
Tuan-Dat Tran
cce6aba4cd fix(edge_vps): fix wireguard route template and update elastic/vps vars 2026-06-04 01:45:16 +02:00
Tuan-Dat Tran
f873256f65 feat(edge_vps): add traefik dynamic config template 2026-06-04 01:45:01 +02:00
Tuan-Dat Tran
a331265bde feat(edge_vps): add pangolin/gerbil/traefik stack with versioned images 2026-06-04 01:44:55 +02:00
Tuan-Dat Tran
a905b25190 fix(raspberry_pi): switch zigbee2mqtt adapter from ezsp to ember 2026-06-03 20:06:21 +02:00
Tuan-Dat Tran
25cc5ac271 fix(inventory): remove undefined k3s_storage group 2026-06-03 19:53:43 +02:00
Tuan-Dat Tran
2b857903a7 fix(raspberry_pi): use /dev/ttyUSB0 and set ezsp adapter for SONOFF MG21 2026-06-03 19:50:30 +02:00
Tuan-Dat Tran
eb4e8445fc fix(raspberry_pi): isolate z2m to own compose dir, fix port conflict 2026-06-03 19:43:35 +02:00
Tuan-Dat Tran
3799dc16d9 fix(raspberry_pi): install docker-compose-plugin before starting stack 2026-06-03 08:31:21 +02:00
Tuan-Dat Tran
585c01ca62 feat(raspberry_pi): wire up role tasks 2026-06-03 08:27:16 +02:00
Tuan-Dat Tran
14b93bf4f5 feat(raspberry_pi): add zigbee2mqtt deploy task 2026-06-03 08:26:04 +02:00
Tuan-Dat Tran
42e790656d feat(raspberry_pi): add zigbee2mqtt and mosquitto templates 2026-06-03 03:12:20 +02:00
Tuan-Dat Tran
da92fb0ccc feat(raspberry_pi): add directory setup task 2026-06-03 03:11:17 +02:00
Tuan-Dat Tran
d655cc54e2 fix(raspberry_pi): remove host condition from handler 2026-06-03 03:03:20 +02:00
Tuan-Dat Tran
9115d30c59 feat(raspberry_pi): add defaults, handlers, and secrets placeholder 2026-06-03 03:01:20 +02:00
Tuan-Dat Tran
8dcb429573 docs: add zigbee2mqtt implementation plan for naruto 2026-06-03 02:57:22 +02:00
Tuan-Dat Tran
29cc38872c docs: add zigbee2mqtt design spec for naruto 2026-06-03 02:54:18 +02:00
Tuan-Dat Tran
f6e2ce8c1a fix(common): replace deprecated apt_repository with deb822_repository 2026-06-03 02:31:33 +02:00
Tuan-Dat Tran
956836dc67 fix(common): replace deprecated ansible_ fact references with ansible_facts[] 2026-06-03 02:17:08 +02:00
Tuan-Dat Tran
aa8b591afd feat(raspberry_pi): add playbook 2026-06-03 01:23:48 +02:00
Tuan-Dat Tran
935389dc6d feat(raspberry_pi): add empty role scaffold 2026-06-03 01:23:48 +02:00
Tuan-Dat Tran
c4327a7596 fix(common): support aarch64 in extra_packages 2026-05-31 23:41:39 +02:00
Tuan-Dat Tran
b190022ff0 feat(raspberry_pi): add inventory and group vars 2026-05-31 23:29:07 +02:00
Tuan-Dat Tran
8da0ab98f8 fix(k3s_server): skip installation if k3s binary already exists
Primary and secondary install tasks now check k3s_status.stat.exists
so re-running the playbook is idempotent on already-provisioned nodes.
2026-04-27 21:43:42 +02:00
Tuan-Dat Tran
b4e093c9b1 fix(k3s_server): use VIP address in kubeconfig instead of k3s_server_name
k3s_server_name resolves to k3s.seyshiro.de which has no DNS entry.
Use k3s_vip (192.168.20.2) so the kubeconfig always works.
2026-04-27 21:41:55 +02:00
Tuan-Dat Tran
e8df950e87 chore(k3s): update vault-encrypted cluster join token 2026-04-27 21:39:37 +02:00
Tuan-Dat Tran
5b44c46e10 docs(arr-cleanup): improve runbook and fix api key paths
Rewrites findings.md with how-to section, cleaner summary tables,
and more detailed per-pass results. Fixes relative path for
sonarr/radarr API key files after runbook moved deeper in repo.
2026-04-27 21:39:28 +02:00
44 changed files with 1233 additions and 146 deletions

View File

@@ -32,7 +32,7 @@ SERIES_ROOT = "/media/series"
script_dir = os.path.dirname(os.path.abspath(__file__)) script_dir = os.path.dirname(os.path.abspath(__file__))
LOG_FILE = os.path.join(script_dir, "cleanup.log") LOG_FILE = os.path.join(script_dir, "cleanup.log")
with open(os.path.join(script_dir, '..', 'sonarr.api.env')) as f: with open(os.path.join(script_dir, '../../../..', 'sonarr.api.env')) as f:
SONARR_KEY = f.read().strip() SONARR_KEY = f.read().strip()

View File

@@ -34,7 +34,9 @@
## Root Cause: No Hardlinks → All Imports Are Copies ## Root Cause: No Hardlinks → All Imports Are Copies
Zero hardlinked files exist anywhere across all three HDDs. Confirmed by inspecting the Kubernetes manifests in `argocd-homelab/services/arr-stack/` and by inode comparison of 1365 download/media file pairs (0 shared inodes found). Zero hardlinked files exist anywhere across all three HDDs. Confirmed by two methods:
1. Inspecting the Kubernetes manifests in `argocd-homelab/services/arr-stack/`
2. Inode comparison of 1365 download/media file pairs — **0 shared inodes found** (every file is a distinct copy)
**All three services mount the mergerfs `/media/` path via NFS:** **All three services mount the mergerfs `/media/` path via NFS:**
@@ -48,63 +50,106 @@ qbit: NFS 192.168.20.12:/media/downloads → /downloads
mergerfs does not support hardlinks across underlying filesystems. When qBit downloads to `/media/downloads/sonarr/` (lands on e.g. hdd1) and Sonarr imports to `/media/series/` (lands on e.g. hdd0), the hardlink attempt crosses a physical disk boundary → falls back to copy. Every import doubles the data. mergerfs does not support hardlinks across underlying filesystems. When qBit downloads to `/media/downloads/sonarr/` (lands on e.g. hdd1) and Sonarr imports to `/media/series/` (lands on e.g. hdd0), the hardlink attempt crosses a physical disk boundary → falls back to copy. Every import doubles the data.
**Estimated wasted space before cleanup: ~21T** (the entire downloads/sonarr + downloads/radarr).
## How to Run
Prerequisites:
```bash
# Port-forward Sonarr and Radarr APIs
kubectl -n arr-stack port-forward svc/sonarr 8989:8989 &
kubectl -n arr-stack port-forward svc/radarr 7878:7878 &
```
API keys are loaded from `../../../../sonarr.api.env` and `../../../../radarr.api.env`
(i.e. `/home/tudattr/workspace/infra/sonarr.api.env` relative to this repo).
Container path mappings used in scripts:
- Sonarr: `/tv/``/media/series/`
- Radarr: `/movies/``/media/movies/`
### Step 1 — Verify (generates `/tmp/arr_verified.json`)
```bash
python3 verify.py
```
Cross-references all downloads against Sonarr/Radarr APIs, verifies reported file paths exist on disk via SSH. Classifies each entry as `safe`, `not_imported`, or `path_missing`.
### Step 2 — Delete confirmed-imported downloads
```bash
python3 cleanup.py --dry-run # preview
python3 cleanup.py --arr sonarr --yes
python3 cleanup.py --arr radarr --yes
```
### Step 3 — Delete orphans (downloads not in Sonarr at all)
```bash
python3 cleanup-orphans.py --dry-run # preview
python3 cleanup-orphans.py --yes
```
All actions are logged to `cleanup.log` with UTC timestamp, size, title, path, and outcome.
## Cleanup Performed (2026-04-23) ## Cleanup Performed (2026-04-23)
Three passes using the scripts in this directory: ### Pass 1 — Orphans (downloads not in Sonarr)
### Pass 1 — Orphans (not in Sonarr at all)
Script: `cleanup-orphans.py` Script: `cleanup-orphans.py`
Deleted 49 entries totalling **461.6G** — downloads with no matching Sonarr series and no series directory on disk. Includes Game of Thrones (all 8 seasons), Sex Education (all 4 seasons), Love Death & Robots (multiple duplicate copies), and various anime episode files. Two-pass logic:
1. Match each download name against Sonarr API (title, slug, sortTitle, alternate titles, partial match)
2. If no API match, check if a series directory with a similar name exists in `/media/series/` — if it does, skip (needs manual review)
3. Delete remaining true orphans
111 entries were SKIPPED (series dir found on disk, needs manual review) — includes Bleach, House, Lucifer, You, Detective Conan episodes, What If, etc. See cleanup.log for full list. Result: **49 deleted, 461.6G freed, 0 failed**
111 entries SKIPPED (series dir found on disk) — includes Bleach, House, Lucifer, You, SpongeBob, Detective Conan episodes, What If, etc. See `cleanup.log` for full list.
Notable orphans deleted:
- Game of Thrones S01S08 (~267G) — removed from Sonarr
- Sex Education S01S04 (~110G) — removed from Sonarr
- Love Death & Robots (multiple duplicate copies, ~45G)
- Senpai is an Otokonoko, Wind Breaker, Wistoria, Hibike! Euphonium S3 episodes, etc.
### Pass 2 — Confirmed-imported Sonarr downloads ### Pass 2 — Confirmed-imported Sonarr downloads
Script: `cleanup.py --arr sonarr` Script: `cleanup.py --arr sonarr --yes`
Deleted **1106 entries**, 0 failed. These were downloads where Sonarr confirmed `episodeFileCount > 0` AND the series directory was verified to exist on disk at the time of `verify.py` run. Deleted downloads where Sonarr confirmed `episodeFileCount > 0` AND the series directory was verified to exist on disk.
Result: **1106 deleted, 0 failed**
### Pass 3 — Confirmed-imported Radarr downloads ### Pass 3 — Confirmed-imported Radarr downloads
Script: `cleanup.py --arr radarr` Script: `cleanup.py --arr radarr --yes`
Deleted **259 entries**, 0 failed. These were downloads where Radarr confirmed `hasFile=True` AND the file/directory path was verified to exist on disk. Deleted downloads where Radarr confirmed `hasFile=True` AND the file/directory path was verified to exist on disk.
### Totals Result: **259 deleted, 0 failed**
| Pass | Entries | Space |
|------|---------|-------|
| Orphans (cleanup-orphans.py) | 49 | ~461G |
| Sonarr imports (cleanup.py) | 1106 | ~12T (estimated) |
| Radarr imports (cleanup.py) | 259 | ~4T (estimated) |
| **Total** | **1414** | **~16T freed** |
All deletions logged to `cleanup.log` with UTC timestamp, size, title, path, outcome. ### Summary
| Pass | Script | Entries | Space freed |
|------|--------|---------|-------------|
| Orphans | `cleanup-orphans.py` | 49 | ~461G |
| Sonarr imports | `cleanup.py --arr sonarr` | 1106 | ~12T (estimated) |
| Radarr imports | `cleanup.py --arr radarr` | 259 | ~4T (estimated) |
| **Total** | | **1414** | **~16T** |
## Verification Results (via API + disk path check) ## Verification Results (from verify.py run before cleanup)
API keys stored in `../sonarr.api.env` and `../radarr.api.env`. | | Safe to delete | Not imported | Path missing | Orphans (no API match) |
Access via `kubectl -n arr-stack port-forward svc/sonarr 8989:8989` and `svc/radarr 7878:7878`. |---|---|---|---|---|
| **Sonarr** (1439 downloads) | 1106 | — | — | 333 |
| **Radarr** (289 downloads) | 265 | — | — | 25 |
Container path mappings: Note: `cleanup-orphans.py` uses more aggressive title matching (alternate titles, partial match) than `verify.py`, so its orphan count (160 not-in-Sonarr out of 1438) is lower than `verify.py`'s 333.
- Sonarr `/tv/``/media/series/`
- Radarr `/movies/``/media/movies/`
| | Safe to delete | Orphans (not in arr) | Keep | ### Radarr Orphans (25) — not matched, not deleted
|---|---|---|---|
| **Radarr** (289 items, ~5.2T) | **265** | 25 | 0 |
| **Sonarr** (1439 items, ~17T) | **1106** | 333 | 0 |
"Safe to delete" = API confirms `hasFile=True` (Radarr) or `episodeFileCount > 0` (Sonarr), AND the reported file/directory path was verified to exist on disk via SSH.
### Radarr Orphans (25) — not matched in Radarr, not deleted
- Constantine (2005) - Constantine (2005)
- Cowboy Bebop: Knockin' on Heaven's Door (2001) - Cowboy Bebop: Knockin' on Heaven's Door (2001)
- Les Misérables (2012) - Les Misérables (2012)
- Pokémon Detective Pikachu (2019) - Pokémon Detective Pikachu (2019)
- Code Geass: Fukkatsu no Lelouch (2019) - Code Geass: Fukkatsu no Lelouch (2019)
- Eiga Go-Toubun no Hanayome (2022) - Eiga Go-Toubun no Hanayome (2022)
- Gisaengchung / Parasite (Korean title matching failure) - Gisaengchung / Parasite Korean title, matching failure
- Dune: Part One (2021) — matching failure, is in Radarr - Dune: Part One (2021) — matching failure, confirmed in Radarr
- Harry Potter (older/duplicate copies — matching failure) - Harry Potter older/duplicate copies — matching failure
- Porco Rosso / Kurenai no buta — matching failure - Porco Rosso / Kurenai no buta — matching failure
- Castle in the Sky / Laputa — matching failure - Castle in the Sky / Laputa — matching failure
- Steins;Gate: The Movie — matching failure - Steins;Gate: The Movie — matching failure
@@ -115,32 +160,41 @@ Container path mappings:
- Fantastic Four (2025) extra copies (3) - Fantastic Four (2025) extra copies (3)
- JJK DCP trailer file - JJK DCP trailer file
### 6 Radarr "path mismatch" entries (all confirmed safe, deleted) ### Path mismatch entries (confirmed safe, deleted anyway)
Flagged due to path comparison artifacts, manually verified on disk: - Star Wars Episode IV/V/VI/IX — all matched to Episode IV record; manually confirmed all 4 dirs exist
- Star Wars Episode IV/V/VI/IX — each is a separate Radarr entry; all directories exist - WALL·E — `·` middle-dot (U+00B7) broke string comparison; file confirmed on disk
- WALL·E — `·` middle-dot character caused comparison failure; file exists
## Pending Decisions ## Pending Decisions
### Bleach USBD Remux TL (1.8T) ### Bleach USBD Remux TL (1.8T)
`/media/downloads/sonarr/Bleach USBD Remux TL` — full lossless Bluray remux S00S16 (-ZR- group). `/media/downloads/sonarr/Bleach USBD Remux TL` — full lossless Bluray remux S00S16 (-ZR- group).
Currently in SKIPPED (series dir `/media/series/Bleach (2004) {imdb-tt0434665}/` exists, 310G imported).
Most seasons were imported from x265 Bluray packs (-iVy group) rather than from this remux. Currently SKIPPED — `/media/series/Bleach (2004) {imdb-tt0434665}/` exists (310G imported).
S11 has no imported content at all. S13, S14 partially imported.
Decision: keep (for quality imports once disk freed) or delete (free 1.8T, accept x265 quality). Most seasons were imported from lighter x265 Bluray packs (`Bleach S0x Bluray EAC3 2.0 1080p x265-iVy`) rather than this remux. S11 has no imported content. S13 and S14 partially imported.
See memory file for full per-season breakdown.
Options:
- **Delete** — free 1.8T, imported x265 content stays, re-download at remux quality later if desired
- **Keep** — retain as source for Sonarr to import remaining episodes at lossless quality now that disk space is freed
Per-season breakdown saved in memory.
### SKIPPED downloads (111 Sonarr entries) ### SKIPPED downloads (111 Sonarr entries)
Downloads where the series directory exists on disk but the series is not currently in Sonarr. Downloads where a matching series directory exists on disk but the series is not in Sonarr.
Likely removed series (House, Lucifer, You, Black Clover, etc.) or ongoing shows with stale episodes. Likely intentionally removed series (House, Lucifer, You, Black Clover, etc.) with leftover download copies.
These need manual review series may have been intentionally removed from Sonarr. Needs manual review per series before deleting.
## Permanent Fix (not applied)
Mount per-HDD NFS paths instead of the mergerfs path, so qBit downloads and arr imports land on the same physical filesystem, enabling hardlinks:
## Fix (not applied — future reference)
Mount per-HDD NFS paths instead of the mergerfs path, so downloads and media share the same physical filesystem and hardlinks work:
```yaml ```yaml
# sonarr/radarr/qtun deployments change NFS path from: # In sonarr/radarr/qtun deployments, change:
path: /media/downloads → path: /mnt/hdd0/downloads path: /media/downloads → path: /mnt/hdd0/downloads
path: /media/series → path: /mnt/hdd0/series path: /media/series → path: /mnt/hdd0/series
path: /media/movies → path: /mnt/hdd0/movies path: /media/movies → path: /mnt/hdd0/movies
``` ```
Jellyfin/Plex continue reading from `/media/` (mergerfs). New imports hardlink within hdd0.
Jellyfin/Plex keep reading from `/media/` (mergerfs union). New imports hardlink within hdd0, wasting no extra space.
Tradeoff: all new content lands on hdd0 only. Load balancing across the three disks stops working for new downloads. Once hdd0 fills up a migration strategy is needed.

View File

@@ -8,7 +8,7 @@ Requirements:
kubectl -n arr-stack port-forward svc/sonarr 8989:8989 kubectl -n arr-stack port-forward svc/sonarr 8989:8989
kubectl -n arr-stack port-forward svc/radarr 7878:7878 kubectl -n arr-stack port-forward svc/radarr 7878:7878
- SSH access to aya01 - SSH access to aya01
- API keys in ../sonarr.api.env and ../radarr.api.env - API keys in ../../../../sonarr.api.env and ../../../../radarr.api.env
Output: Output:
/tmp/arr_verified.json — full structured results for use by cleanup.py /tmp/arr_verified.json — full structured results for use by cleanup.py
@@ -28,7 +28,7 @@ SSH_HOST = "aya01"
script_dir = os.path.dirname(os.path.abspath(__file__)) script_dir = os.path.dirname(os.path.abspath(__file__))
def load_key(filename): def load_key(filename):
path = os.path.join(script_dir, '..', filename) path = os.path.join(script_dir, '../../../..', filename)
return open(path).read().strip() return open(path).read().strip()
SONARR_KEY = load_key('sonarr.api.env') SONARR_KEY = load_key('sonarr.api.env')

View File

@@ -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.

View 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.

View File

@@ -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.

View File

@@ -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.

View 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
View File

@@ -0,0 +1,8 @@
---
- name: Set up VPS
hosts: vps
gather_facts: true
roles:
- role: edge_vps
tags:
- edge_vps

View File

@@ -1,4 +1,9 @@
--- ---
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
become: true
- name: Restart sshd - name: Restart sshd
service: service:
name: sshd name: sshd

View File

@@ -2,9 +2,9 @@
- name: Copy bash-configs - name: Copy bash-configs
ansible.builtin.template: ansible.builtin.template:
src: "files/bash/{{ item }}" src: "files/bash/{{ item }}"
dest: "{{ ansible_env.HOME }}/.{{ item }}" dest: "{{ ansible_facts['env']['HOME'] }}/.{{ item }}"
owner: "{{ ansible_user_id }}" owner: "{{ ansible_facts['user_id'] }}"
group: "{{ ansible_user_id }}" group: "{{ ansible_facts['user_id'] }}"
mode: "644" mode: "644"
loop: loop:
- bashrc - bashrc
@@ -13,25 +13,25 @@
- name: Copy ghostty infocmp - name: Copy ghostty infocmp
ansible.builtin.copy: ansible.builtin.copy:
src: files/ghostty/infocmp src: files/ghostty/infocmp
dest: "{{ ansible_env.HOME }}/ghostty" dest: "{{ ansible_facts['env']['HOME'] }}/ghostty"
owner: "{{ ansible_user_id }}" owner: "{{ ansible_facts['user_id'] }}"
group: "{{ ansible_user_id }}" group: "{{ ansible_facts['user_id'] }}"
mode: "0644" mode: "0644"
register: ghostty_terminfo register: ghostty_terminfo
- name: Compile ghostty terminalinfo - 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 when: ghostty_terminfo.changed
- name: Copy kitty infocmp - name: Copy kitty infocmp
ansible.builtin.copy: ansible.builtin.copy:
src: files/kitty/infocmp src: files/kitty/infocmp
dest: "{{ ansible_env.HOME }}/kitty" dest: "{{ ansible_facts['env']['HOME'] }}/kitty"
owner: "{{ ansible_user_id }}" owner: "{{ ansible_facts['user_id'] }}"
group: "{{ ansible_user_id }}" group: "{{ ansible_facts['user_id'] }}"
mode: "0644" mode: "0644"
register: kitty_terminfo register: kitty_terminfo
- name: Compile kitty terminalinfo - 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 when: kitty_terminfo.changed

View File

@@ -14,11 +14,17 @@
become: true become: true
- name: Add Gierens repository to apt sources - name: Add Gierens repository to apt sources
ansible.builtin.apt_repository: ansible.builtin.deb822_repository:
repo: "deb [signed-by=/etc/apt/keyrings/gierens.asc] http://deb.gierens.de stable main" name: gierens
types: deb
uris: http://deb.gierens.de
suites: stable
components: main
signed_by: /etc/apt/keyrings/gierens.asc
state: present state: present
update_cache: true install_python_debian: true
become: true become: true
notify: Update apt cache
- name: Install eza package - name: Install eza package
ansible.builtin.apt: ansible.builtin.apt:
@@ -28,7 +34,7 @@
- name: Install bottom package - name: Install bottom package
ansible.builtin.apt: 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 state: present
become: true become: true
@@ -37,20 +43,21 @@
register: neovim_installed register: neovim_installed
changed_when: false changed_when: false
ignore_errors: true ignore_errors: true
when: ansible_facts['architecture'] != 'aarch64'
- name: Download Neovim AppImage - name: Download Neovim AppImage
ansible.builtin.get_url: ansible.builtin.get_url:
url: https://github.com/neovim/neovim/releases/download/v0.10.0/nvim.appimage url: https://github.com/neovim/neovim/releases/download/v0.10.0/nvim.appimage
dest: /tmp/nvim.appimage dest: /tmp/nvim.appimage
mode: "0755" mode: "0755"
when: neovim_installed.rc != 0 when: ansible_facts['architecture'] != 'aarch64' and neovim_installed.rc != 0
register: download_result register: download_result
- name: Extract Neovim AppImage - name: Extract Neovim AppImage
ansible.builtin.command: ansible.builtin.command:
cmd: "./nvim.appimage --appimage-extract" cmd: "./nvim.appimage --appimage-extract"
chdir: /tmp chdir: /tmp
when: download_result.changed when: ansible_facts['architecture'] != 'aarch64' and download_result.changed
register: extract_result register: extract_result
- name: Copy extracted Neovim files to /usr - name: Copy extracted Neovim files to /usr
@@ -60,19 +67,19 @@
remote_src: true remote_src: true
mode: "0755" mode: "0755"
become: true become: true
when: extract_result.changed when: ansible_facts['architecture'] != 'aarch64' and extract_result.changed
- name: Clean up extracted Neovim files - name: Clean up extracted Neovim files
ansible.builtin.file: ansible.builtin.file:
path: /tmp/squashfs-root path: /tmp/squashfs-root
state: absent state: absent
when: extract_result.changed when: ansible_facts['architecture'] != 'aarch64' and extract_result.changed
- name: Remove Neovim AppImage - name: Remove Neovim AppImage
ansible.builtin.file: ansible.builtin.file:
path: /tmp/nvim.appimage path: /tmp/nvim.appimage
state: absent state: absent
when: download_result.changed when: ansible_facts['architecture'] != 'aarch64' and download_result.changed
- name: Check if Neovim config directory already exists - name: Check if Neovim config directory already exists
ansible.builtin.stat: ansible.builtin.stat:

View File

@@ -5,24 +5,24 @@
upgrade: true upgrade: true
autoremove: true autoremove: true
become: true become: true
when: ansible_user_id != "root" when: ansible_facts['user_id'] != "root"
- name: Install base packages - name: Install base packages
ansible.builtin.apt: ansible.builtin.apt:
name: "{{ common_packages }}" name: "{{ common_packages }}"
state: present state: present
become: true become: true
when: ansible_user_id != "root" when: ansible_facts['user_id'] != "root"
- name: Update and upgrade packages - name: Update and upgrade packages
ansible.builtin.apt: ansible.builtin.apt:
update_cache: true update_cache: true
upgrade: true upgrade: true
autoremove: true autoremove: true
when: ansible_user_id == "root" when: ansible_facts['user_id'] == "root"
- name: Install base packages - name: Install base packages
ansible.builtin.apt: ansible.builtin.apt:
name: "{{ common_packages }}" name: "{{ common_packages }}"
state: present state: present
when: ansible_user_id == "root" when: ansible_facts['user_id'] == "root"

View File

@@ -8,7 +8,7 @@
notify: notify:
- Restart sshd - Restart sshd
become: true become: true
when: ansible_user_id != "root" when: ansible_facts['user_id'] != "root"
- name: Copy root sshd_config - name: Copy root sshd_config
ansible.builtin.template: ansible.builtin.template:
@@ -18,7 +18,7 @@
backup: true backup: true
notify: notify:
- Restart sshd - Restart sshd
when: ansible_user_id == "root" when: ansible_facts['user_id'] == "root"
- name: Copy pubkey - name: Copy pubkey
ansible.builtin.copy: ansible.builtin.copy:

View File

@@ -3,12 +3,12 @@
community.general.timezone: community.general.timezone:
name: "{{ timezone }}" name: "{{ timezone }}"
become: true become: true
when: ansible_user_id != "root" when: ansible_facts['user_id'] != "root"
- name: Set timezone - name: Set timezone
community.general.timezone: community.general.timezone:
name: "{{ timezone }}" name: "{{ timezone }}"
when: ansible_user_id == "root" when: ansible_facts['user_id'] == "root"
- name: Configure NTP servers for systemd-timesyncd - name: Configure NTP servers for systemd-timesyncd
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
@@ -24,11 +24,11 @@
enabled: true enabled: true
state: started state: started
become: true become: true
when: ansible_user_id != "root" when: ansible_facts['user_id'] != "root"
- name: Enable and start systemd-timesyncd - name: Enable and start systemd-timesyncd
ansible.builtin.systemd: ansible.builtin.systemd:
name: systemd-timesyncd name: systemd-timesyncd
enabled: true enabled: true
state: started state: started
when: ansible_user_id == "root" when: ansible_facts['user_id'] == "root"

View File

@@ -6,6 +6,10 @@ edge_vps_wireguard_address: "10.133.7.1/24"
edge_vps_wireguard_port: 61975 edge_vps_wireguard_port: 61975
edge_vps_traefik_config_dir: "{{ edge_vps_config_base }}/traefik" edge_vps_traefik_config_dir: "{{ edge_vps_config_base }}/traefik"
edge_vps_traefik_logs_dir: "{{ edge_vps_traefik_config_dir }}/logs" edge_vps_traefik_logs_dir: "{{ edge_vps_traefik_config_dir }}/logs"
edge_vps_pangolin_config_dir: "{{ edge_vps_config_base }}/pangolin" edge_vps_pangolin_config_dir: "{{ edge_vps_config_base }}"
edge_vps_elastic_config_dir: "{{ edge_vps_config_base }}/elastic-agent" 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 edge_vps_elastic_state_dir: /var/lib/elastic-agent/elastic-system/elastic-agent/state

View File

@@ -7,6 +7,12 @@
- name: Restart traefik - name: Restart traefik
ansible.builtin.command: ansible.builtin.command:
cmd: docker compose restart cmd: podman compose restart traefik
chdir: "{{ edge_vps_traefik_config_dir }}" chdir: "{{ edge_vps_pangolin_compose_dir }}"
listen: restart traefik listen: restart traefik
- name: Restart pangolin
ansible.builtin.command:
cmd: podman compose restart pangolin
chdir: "{{ edge_vps_pangolin_compose_dir }}"
listen: restart pangolin

View File

@@ -14,9 +14,9 @@
- "{{ edge_vps_traefik_config_dir }}" - "{{ edge_vps_traefik_config_dir }}"
- "{{ edge_vps_traefik_logs_dir }}" - "{{ edge_vps_traefik_logs_dir }}"
- name: Create Pangolin config directory - name: Create Pangolin letsencrypt directory
ansible.builtin.file: ansible.builtin.file:
path: "{{ edge_vps_pangolin_config_dir }}" path: "{{ edge_vps_pangolin_config_dir }}/letsencrypt"
state: directory state: directory
mode: "0755" mode: "0755"

View File

@@ -6,10 +6,9 @@
mode: "0644" mode: "0644"
notify: restart traefik notify: restart traefik
- name: Deploy Cloudflare credentials for ACME - name: Deploy Traefik dynamic config
ansible.builtin.copy: ansible.builtin.template:
content: | src: traefik/dynamic_config.yml.j2
CF_DNS_API_TOKEN={{ vault_edge_vps.traefik.cloudflare_api_token }} dest: "{{ edge_vps_traefik_config_dir }}/dynamic_config.yml"
dest: "{{ edge_vps_traefik_config_dir }}/cloudflare.env" mode: "0644"
mode: "0600" notify: restart traefik
no_log: true

View File

@@ -9,16 +9,11 @@
- name: Deploy Pangolin docker-compose - name: Deploy Pangolin docker-compose
ansible.builtin.template: ansible.builtin.template:
src: pangolin/docker-compose.yml.j2 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" 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 - name: Start Pangolin
community.docker.docker_compose_v2: ansible.builtin.command:
project_src: "{{ edge_vps_pangolin_config_dir }}" cmd: podman compose up -d
state: present chdir: "{{ edge_vps_pangolin_compose_dir }}"
changed_when: false

View File

@@ -24,6 +24,7 @@
mode: "0644" mode: "0644"
- name: Start Elastic Agent - name: Start Elastic Agent
community.docker.docker_compose_v2: ansible.builtin.command:
project_src: "{{ edge_vps_elastic_config_dir }}" cmd: podman compose up -d
state: present chdir: "{{ edge_vps_elastic_config_dir }}"
changed_when: false

View File

@@ -1,25 +1,58 @@
name: pangolin
services: services:
pangolin: pangolin:
image: fosrl/pangolin:latest image: docker.io/fosrl/pangolin:{{ edge_vps_pangolin_version }}
container_name: pangolin container_name: pangolin
restart: unless-stopped restart: unless-stopped
ports:
- "3001:3001"
- "443:443"
- "80:80"
volumes: volumes:
- ./config.yml:/app/config/config.yml:ro - ./config:/app/config
- ./letsencrypt:/letsencrypt healthcheck:
depends_on: test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
- gerbil interval: "10s"
timeout: "10s"
retries: 15
gerbil: gerbil:
image: fosrl/gerbil:latest image: docker.io/fosrl/gerbil:{{ edge_vps_gerbil_version }}
container_name: gerbil container_name: gerbil
restart: unless-stopped 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: cap_add:
- NET_ADMIN - NET_ADMIN
- SYS_MODULE - 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: 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

View 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

View File

@@ -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 -i {{ edge_vps_wireguard_interface }} -j ACCEPT
PostUp = iptables -A FORWARD -o {{ 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([]) %} {% 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 %} {% endfor %}
PostDown = iptables -D FORWARD -i {{ edge_vps_wireguard_interface }} -j ACCEPT PostDown = iptables -D FORWARD -i {{ edge_vps_wireguard_interface }} -j ACCEPT
PostDown = iptables -D FORWARD -o {{ 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([]) %} {% 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 %} {% endfor %}
{% for peer in vault_edge_vps.wireguard.peers %} {% for peer in vault_edge_vps.wireguard.peers %}

View File

@@ -46,7 +46,7 @@
- name: Add K3s cluster to kubeconfig - name: Add K3s cluster to kubeconfig
ansible.builtin.command: > ansible.builtin.command: >
kubectl config set-cluster "{{ k3s_cluster_name }}" kubectl config set-cluster "{{ k3s_cluster_name }}"
--server="https://{{ k3s_server_name }}:6443" --server="https://{{ k3s_vip }}:6443"
--certificate-authority=/tmp/k3s-ca.crt --certificate-authority=/tmp/k3s-ca.crt
--embed-certs=true --embed-certs=true
environment: environment:

View File

@@ -16,14 +16,18 @@
- name: Install primary k3s server - name: Install primary k3s server
include_tasks: primary_installation.yaml include_tasks: primary_installation.yaml
when: inventory_hostname == groups['k3s_server'] | first when:
- inventory_hostname == groups['k3s_server'] | first
- not k3s_status.stat.exists
- name: Get token from primary k3s server - name: Get token from primary k3s server
include_tasks: pull_token.yaml include_tasks: pull_token.yaml
- name: Install seconary k3s servers - name: Install seconary k3s servers
include_tasks: secondary_installation.yaml include_tasks: secondary_installation.yaml
when: inventory_hostname != groups['k3s_server'] | first when:
- inventory_hostname != groups['k3s_server'] | first
- not k3s_status.stat.exists
- name: Set kubeconfig on localhost - name: Set kubeconfig on localhost
include_tasks: create_kubeconfig.yaml include_tasks: create_kubeconfig.yaml

View File

@@ -18,7 +18,7 @@
tags: "{{ proxmox_tags }}" tags: "{{ proxmox_tags }}"
description: "Created via Ansible with cloud-init" description: "Created via Ansible with cloud-init"
boot: "order=scsi0" boot: "order=scsi0"
cpu: "x86-64-v2-AES" cpu: "{{ proxmox_node_cpu[vm.node] | default('x86-64-v2-AES') }}"
ciuser: "{{ vm.ciuser }}" ciuser: "{{ vm.ciuser }}"
cipassword: "{{ vm_secrets[proxmox_secrets_prefix + '_' + vm.name.replace('-', '_')] }}" cipassword: "{{ vm_secrets[proxmox_secrets_prefix + '_' + vm.name.replace('-', '_')] }}"
ipconfig: ipconfig:

View 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"

View File

@@ -0,0 +1,6 @@
---
- name: Restart zigbee2mqtt
ansible.builtin.command:
cmd: docker compose restart zigbee2mqtt
chdir: "{{ raspberry_pi_compose_dir }}"
listen: restart zigbee2mqtt

View 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'

View 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'

View 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

View File

@@ -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

View File

@@ -0,0 +1,5 @@
listener 1883
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log
allow_anonymous true

View File

@@ -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

View File

@@ -19,7 +19,7 @@ nfs_server: 192.168.20.12
# Packages # 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_key: "{{ vault_netcup.api_key }}"
netcup_api_password: "{{ vault_netcup.api_password }}" netcup_api_password: "{{ vault_netcup.api_password }}"

View File

@@ -1,11 +1,11 @@
$ANSIBLE_VAULT;1.1;AES256 $ANSIBLE_VAULT;1.1;AES256
38623839366365343437313030343332643334386664393434376366613533643964663466666566 64356331353036663336626237373732393636366236326430343435313362333332656639356661
6530613234373464633437313563633532313362663033370a643636636565303131366363663738 3861323465653764303733366430306335303737323863370a393737656163623432363432366430
30346339613135323663383736346435653736656537313664613832363931313232383262373862 32353030303630323438643839363730326365303062653335303130623264613939303037376239
6263646537356337630a363833636339393763336365316139336530353765623861666662393237 3062613036333661300a363633306333373239633233653064343066343162356636373862656136
33383464633438663264646465393534353338373330363132313236343161666633643632346430 62333933353566643166643831313035643034376166316166623835326263376166626235306131
35303632366335336431393635663032363262373138343331626636386562356662656162386533 36393461633962333637636163333532626663316363653131333561653635373037353864353763
36653561393832333339353539353932306565313532306636613865313336636265613533343639 65666665653161383835663631656166346431613435396331356539353231623034623938393836
66386137646334636436396338386233613932373866393266386438346436393532356436316435 33643761303234376162383465383130633335356366393839636665373365623462363239636364
33386136306535313236323030666537643036643865666563396262353065633463643131373265 65343938653062623963666531653861646134633732313764356566633533666232373663633661
6562653834646565663162343562343530366237343764373230 6563396563643334666437353962383535306339663834623666

View File

@@ -2,3 +2,12 @@ proxmox_api_host: 192.168.20.12
proxmox_api_user: root proxmox_api_user: root
proxmox_api_token_id: terraform proxmox_api_token_id: terraform
proxmox_api_token_secret: "{{ vault_pve.api.token_secret }}" 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"

View File

@@ -1,6 +1,6 @@
vms: vms:
- name: "docker-host11" - name: "docker-host11"
node: "inko01" node: "aya01"
vmid: 411 vmid: 411
cores: 2 cores: 2
memory: 4096 # in MiB memory: 4096 # in MiB
@@ -68,7 +68,7 @@ vms:
sshkeys: "{{ pubkey }}" sshkeys: "{{ pubkey }}"
disk_size: 32 # in Gb disk_size: 32 # in Gb
- name: "k3s-server11" - name: "k3s-server11"
node: "inko01" node: "aya01"
vmid: 111 vmid: 111
cores: 2 cores: 2
memory: 4096 # in MiB memory: 4096 # in MiB
@@ -189,7 +189,7 @@ vms:
sshkeys: "{{ pubkey }}" sshkeys: "{{ pubkey }}"
disk_size: 128 disk_size: 128
- name: "k3s-agent21" - name: "k3s-agent21"
node: "inko01" node: "aya01"
vmid: 221 vmid: 221
cores: 2 cores: 2
memory: 4096 memory: 4096

View File

@@ -0,0 +1,3 @@
vault_raspberry_pi:
zigbee2mqtt:
network_key: "GENERATE"

View File

@@ -0,0 +1 @@
---

View File

@@ -9,6 +9,8 @@ edge_vps_pangolin_base_endpoint: "pangolin.seyshiro.de"
edge_vps_pangolin_base_domain: "seyshiro.de" edge_vps_pangolin_base_domain: "seyshiro.de"
edge_vps_acme_email: "me+acme@tudattr.dev" 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_version: "9.2.2"
edge_vps_elastic_dns_server: "10.43.0.10" edge_vps_elastic_dns_server: "10.43.0.10"

View File

@@ -3,7 +3,6 @@
[k3s:children] [k3s:children]
k3s_server k3s_server
k3s_agent k3s_agent
k3s_storage
k3s_loadbalancer k3s_loadbalancer
[k3s_server] [k3s_server]

3
vars/raspberry_pi.ini Normal file
View File

@@ -0,0 +1,3 @@
[raspberry_pi]
naruto
pi