diff --git a/README.md b/README.md
index 920ebf7..08d700d 100644
--- a/README.md
+++ b/README.md
@@ -156,7 +156,7 @@ applies the edge stack.
Set `LAB_PIMOX_PIPELINE=false` to skip Pimox automation. Set
`LAB_PIMOX_WORKER_COUNT=0` to create or refresh only the template. The pipeline
keeps the template on its configured `local` storage, creates new worker VM
-clones on `nvme_thin_pool` by default, checks that the Pimox bridge already
+clones on `nvme_storage` by default, checks that the Pimox bridge already
exists, refuses `local` as worker clone storage, and refuses to edit Orange Pi
host networking.
@@ -171,7 +171,7 @@ attaches to both WAN and LAN bridges. Run `./lab.sh openwrt` after `vmbr1`
already exists on the Orange Pi. The pipeline downloads the OpenWrt ARM
SystemReady EFI image, writes basic WAN/LAN/firewall config into the image,
imports it as VM `9100`, attaches `vmbr0` as WAN and `vmbr1` as LAN, and stores
-the VM disk on `nvme_thin_pool`. It leaves the VM stopped and not enabled for
+the VM disk on `nvme_storage`. It leaves the VM stopped and not enabled for
host boot by default. It does not use the Debian Kubernetes golden-node template
for OpenWrt.
@@ -228,7 +228,7 @@ For Pimox on Orange Pi 5 Plus, `./lab.sh up` can create the Debian 13 arm64
template and worker VM clones automatically. Defaults are intentionally tied to
the observed host: Pimox SSH host `192.168.100.80`, bridge `vmbr0`, template VMID
`9000` on `local` storage, two 4 GiB worker VMs starting at VMID `9010`, worker
-clone storage `nvme_thin_pool`, and no CPU affinity because this Pimox is pinned
+clone storage `nvme_storage`, and no CPU affinity because this Pimox is pinned
to Debian Bullseye. Details and override variables are in
`bootstrap/provisioning/README.md`.
@@ -348,6 +348,13 @@ Traefik's address:
192.168.100.240 demos.lab2025.duckdns.org
192.168.100.240 heimdall.lab2025.duckdns.org
192.168.100.240 n8n.lab2025.duckdns.org
+192.168.100.240 prowlarr.lab2025.duckdns.org
+192.168.100.240 sonarr.lab2025.duckdns.org
+192.168.100.240 radarr.lab2025.duckdns.org
+192.168.100.240 qbittorrent.lab2025.duckdns.org
+192.168.100.240 kapowarr.lab2025.duckdns.org
+192.168.100.240 suwayomi.lab2025.duckdns.org
+192.168.100.240 maintainerr.lab2025.duckdns.org
192.168.100.240 grafana.lab2025.duckdns.org
192.168.100.240 argocd.lab2025.duckdns.org
```
@@ -501,6 +508,12 @@ exposed at `https://n8n.lab2025.duckdns.org`. It receives
`OLLAMA_BASE_URL=http://192.168.100.68:11434` so workflows can call the existing
Debian-host Ollama API for translation prewarming and batch jobs.
+The `arr-stack` app runs Prowlarr, Sonarr, Radarr, qBittorrent, Kapowarr,
+Suwayomi, Maintainerr, and FlareSolverr as native Kubernetes Deployments in the
+`arr-stack` namespace. It uses retained OpenEBS PVCs for shared `/data` media
+paths and per-service config directories, and exposes the user interfaces
+through dedicated Traefik hostnames instead of a Tailscale sidecar.
+
## Storage
OpenEBS provides the platform storage provisioner. Stateful Kubernetes apps use
diff --git a/apps/arr-stack/ingress.yaml b/apps/arr-stack/ingress.yaml
new file mode 100644
index 0000000..75e4d1e
--- /dev/null
+++ b/apps/arr-stack/ingress.yaml
@@ -0,0 +1,132 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: prowlarr
+ namespace: arr-stack
+spec:
+ ingressClassName: traefik
+ rules:
+ - host: prowlarr.lab2025.duckdns.org
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: prowlarr
+ port:
+ number: 9696
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: sonarr
+ namespace: arr-stack
+spec:
+ ingressClassName: traefik
+ rules:
+ - host: sonarr.lab2025.duckdns.org
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: sonarr
+ port:
+ number: 8989
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: radarr
+ namespace: arr-stack
+spec:
+ ingressClassName: traefik
+ rules:
+ - host: radarr.lab2025.duckdns.org
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: radarr
+ port:
+ number: 7878
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: qbittorrent
+ namespace: arr-stack
+spec:
+ ingressClassName: traefik
+ rules:
+ - host: qbittorrent.lab2025.duckdns.org
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: qbittorrent
+ port:
+ number: 8080
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: kapowarr
+ namespace: arr-stack
+spec:
+ ingressClassName: traefik
+ rules:
+ - host: kapowarr.lab2025.duckdns.org
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: kapowarr
+ port:
+ number: 5656
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: suwayomi
+ namespace: arr-stack
+spec:
+ ingressClassName: traefik
+ rules:
+ - host: suwayomi.lab2025.duckdns.org
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: suwayomi
+ port:
+ number: 4567
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: maintainerr
+ namespace: arr-stack
+spec:
+ ingressClassName: traefik
+ rules:
+ - host: maintainerr.lab2025.duckdns.org
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: maintainerr
+ port:
+ number: 6246
diff --git a/apps/arr-stack/kustomization.yaml b/apps/arr-stack/kustomization.yaml
new file mode 100644
index 0000000..7a5c829
--- /dev/null
+++ b/apps/arr-stack/kustomization.yaml
@@ -0,0 +1,7 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+resources:
+ - namespace.yaml
+ - storage.yaml
+ - web-app.yaml
+ - ingress.yaml
diff --git a/apps/arr-stack/namespace.yaml b/apps/arr-stack/namespace.yaml
new file mode 100644
index 0000000..d832602
--- /dev/null
+++ b/apps/arr-stack/namespace.yaml
@@ -0,0 +1,4 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: arr-stack
diff --git a/apps/arr-stack/storage.yaml b/apps/arr-stack/storage.yaml
new file mode 100644
index 0000000..d10c39d
--- /dev/null
+++ b/apps/arr-stack/storage.yaml
@@ -0,0 +1,25 @@
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: arr-config
+ namespace: arr-stack
+spec:
+ accessModes:
+ - ReadWriteOnce
+ storageClassName: openebs-hostpath-retain
+ resources:
+ requests:
+ storage: 20Gi
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: arr-data
+ namespace: arr-stack
+spec:
+ accessModes:
+ - ReadWriteOnce
+ storageClassName: openebs-hostpath-retain
+ resources:
+ requests:
+ storage: 500Gi
diff --git a/apps/arr-stack/web-app.yaml b/apps/arr-stack/web-app.yaml
new file mode 100644
index 0000000..08271a9
--- /dev/null
+++ b/apps/arr-stack/web-app.yaml
@@ -0,0 +1,538 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: flaresolverr
+ namespace: arr-stack
+ labels:
+ app: flaresolverr
+spec:
+ replicas: 1
+ strategy:
+ type: Recreate
+ selector:
+ matchLabels:
+ app: flaresolverr
+ template:
+ metadata:
+ labels:
+ app: flaresolverr
+ spec:
+ nodeSelector:
+ kubernetes.io/os: linux
+ containers:
+ - name: flaresolverr
+ image: ghcr.io/flaresolverr/flaresolverr:latest
+ imagePullPolicy: IfNotPresent
+ env:
+ - name: LOG_LEVEL
+ value: info
+ - name: TZ
+ value: America/Mexico_City
+ ports:
+ - containerPort: 8191
+ name: http
+ resources:
+ requests:
+ cpu: 50m
+ memory: 256Mi
+ limits:
+ memory: 1Gi
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: flaresolverr
+ namespace: arr-stack
+spec:
+ selector:
+ app: flaresolverr
+ ports:
+ - port: 8191
+ targetPort: http
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: prowlarr
+ namespace: arr-stack
+ labels:
+ app: prowlarr
+spec:
+ replicas: 1
+ strategy:
+ type: Recreate
+ selector:
+ matchLabels:
+ app: prowlarr
+ template:
+ metadata:
+ labels:
+ app: prowlarr
+ spec:
+ nodeSelector:
+ kubernetes.io/os: linux
+ securityContext:
+ fsGroup: 1000
+ fsGroupChangePolicy: OnRootMismatch
+ containers:
+ - name: prowlarr
+ image: lscr.io/linuxserver/prowlarr:latest
+ imagePullPolicy: IfNotPresent
+ env:
+ - name: PUID
+ value: "1000"
+ - name: PGID
+ value: "1000"
+ - name: TZ
+ value: America/Mexico_City
+ ports:
+ - containerPort: 9696
+ name: http
+ resources:
+ requests:
+ cpu: 100m
+ memory: 256Mi
+ limits:
+ memory: 1Gi
+ volumeMounts:
+ - name: arr-config
+ mountPath: /config
+ subPath: prowlarr
+ volumes:
+ - name: arr-config
+ persistentVolumeClaim:
+ claimName: arr-config
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: prowlarr
+ namespace: arr-stack
+spec:
+ selector:
+ app: prowlarr
+ ports:
+ - port: 9696
+ targetPort: http
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: radarr
+ namespace: arr-stack
+ labels:
+ app: radarr
+spec:
+ replicas: 1
+ strategy:
+ type: Recreate
+ selector:
+ matchLabels:
+ app: radarr
+ template:
+ metadata:
+ labels:
+ app: radarr
+ spec:
+ nodeSelector:
+ kubernetes.io/os: linux
+ securityContext:
+ fsGroup: 1000
+ fsGroupChangePolicy: OnRootMismatch
+ containers:
+ - name: radarr
+ image: lscr.io/linuxserver/radarr:latest
+ imagePullPolicy: IfNotPresent
+ env:
+ - name: PUID
+ value: "1000"
+ - name: PGID
+ value: "1000"
+ - name: TZ
+ value: America/Mexico_City
+ ports:
+ - containerPort: 7878
+ name: http
+ resources:
+ requests:
+ cpu: 100m
+ memory: 512Mi
+ limits:
+ memory: 2Gi
+ volumeMounts:
+ - name: arr-config
+ mountPath: /config
+ subPath: radarr
+ - name: arr-data
+ mountPath: /data
+ volumes:
+ - name: arr-config
+ persistentVolumeClaim:
+ claimName: arr-config
+ - name: arr-data
+ persistentVolumeClaim:
+ claimName: arr-data
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: radarr
+ namespace: arr-stack
+spec:
+ selector:
+ app: radarr
+ ports:
+ - port: 7878
+ targetPort: http
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: sonarr
+ namespace: arr-stack
+ labels:
+ app: sonarr
+spec:
+ replicas: 1
+ strategy:
+ type: Recreate
+ selector:
+ matchLabels:
+ app: sonarr
+ template:
+ metadata:
+ labels:
+ app: sonarr
+ spec:
+ nodeSelector:
+ kubernetes.io/os: linux
+ securityContext:
+ fsGroup: 1000
+ fsGroupChangePolicy: OnRootMismatch
+ containers:
+ - name: sonarr
+ image: lscr.io/linuxserver/sonarr:latest
+ imagePullPolicy: IfNotPresent
+ env:
+ - name: PUID
+ value: "1000"
+ - name: PGID
+ value: "1000"
+ - name: TZ
+ value: America/Mexico_City
+ ports:
+ - containerPort: 8989
+ name: http
+ resources:
+ requests:
+ cpu: 100m
+ memory: 512Mi
+ limits:
+ memory: 2Gi
+ volumeMounts:
+ - name: arr-config
+ mountPath: /config
+ subPath: sonarr
+ - name: arr-data
+ mountPath: /data
+ volumes:
+ - name: arr-config
+ persistentVolumeClaim:
+ claimName: arr-config
+ - name: arr-data
+ persistentVolumeClaim:
+ claimName: arr-data
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: sonarr
+ namespace: arr-stack
+spec:
+ selector:
+ app: sonarr
+ ports:
+ - port: 8989
+ targetPort: http
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: qbittorrent
+ namespace: arr-stack
+ labels:
+ app: qbittorrent
+spec:
+ replicas: 1
+ strategy:
+ type: Recreate
+ selector:
+ matchLabels:
+ app: qbittorrent
+ template:
+ metadata:
+ labels:
+ app: qbittorrent
+ spec:
+ nodeSelector:
+ kubernetes.io/os: linux
+ securityContext:
+ fsGroup: 1000
+ fsGroupChangePolicy: OnRootMismatch
+ containers:
+ - name: qbittorrent
+ image: lscr.io/linuxserver/qbittorrent:latest
+ imagePullPolicy: IfNotPresent
+ env:
+ - name: PUID
+ value: "1000"
+ - name: PGID
+ value: "1000"
+ - name: TZ
+ value: America/Mexico_City
+ - name: WEBUI_PORT
+ value: "8080"
+ ports:
+ - containerPort: 8080
+ name: http
+ resources:
+ requests:
+ cpu: 100m
+ memory: 512Mi
+ limits:
+ memory: 2Gi
+ volumeMounts:
+ - name: arr-config
+ mountPath: /config
+ subPath: qbittorrent
+ - name: arr-data
+ mountPath: /data
+ volumes:
+ - name: arr-config
+ persistentVolumeClaim:
+ claimName: arr-config
+ - name: arr-data
+ persistentVolumeClaim:
+ claimName: arr-data
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: qbittorrent
+ namespace: arr-stack
+spec:
+ selector:
+ app: qbittorrent
+ ports:
+ - port: 8080
+ targetPort: http
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: kapowarr
+ namespace: arr-stack
+ labels:
+ app: kapowarr
+spec:
+ replicas: 1
+ strategy:
+ type: Recreate
+ selector:
+ matchLabels:
+ app: kapowarr
+ template:
+ metadata:
+ labels:
+ app: kapowarr
+ spec:
+ nodeSelector:
+ kubernetes.io/os: linux
+ securityContext:
+ fsGroup: 1000
+ fsGroupChangePolicy: OnRootMismatch
+ containers:
+ - name: kapowarr
+ image: mrcas/kapowarr:latest
+ imagePullPolicy: IfNotPresent
+ env:
+ - name: PUID
+ value: "1000"
+ - name: PGID
+ value: "1000"
+ - name: TZ
+ value: America/Mexico_City
+ ports:
+ - containerPort: 5656
+ name: http
+ resources:
+ requests:
+ cpu: 100m
+ memory: 256Mi
+ limits:
+ memory: 1Gi
+ volumeMounts:
+ - name: arr-config
+ mountPath: /app/db
+ subPath: kapowarr
+ - name: arr-data
+ mountPath: /app/temp_downloads
+ subPath: torrents
+ - name: arr-data
+ mountPath: /comics
+ subPath: media/comics
+ volumes:
+ - name: arr-config
+ persistentVolumeClaim:
+ claimName: arr-config
+ - name: arr-data
+ persistentVolumeClaim:
+ claimName: arr-data
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: kapowarr
+ namespace: arr-stack
+spec:
+ selector:
+ app: kapowarr
+ ports:
+ - port: 5656
+ targetPort: http
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: suwayomi
+ namespace: arr-stack
+ labels:
+ app: suwayomi
+spec:
+ replicas: 1
+ strategy:
+ type: Recreate
+ selector:
+ matchLabels:
+ app: suwayomi
+ template:
+ metadata:
+ labels:
+ app: suwayomi
+ spec:
+ nodeSelector:
+ kubernetes.io/os: linux
+ securityContext:
+ fsGroup: 1000
+ fsGroupChangePolicy: OnRootMismatch
+ containers:
+ - name: suwayomi
+ image: ghcr.io/suwayomi/suwayomi-server:preview
+ imagePullPolicy: IfNotPresent
+ env:
+ - name: PUID
+ value: "1000"
+ - name: PGID
+ value: "1000"
+ - name: TZ
+ value: America/Mexico_City
+ ports:
+ - containerPort: 4567
+ name: http
+ resources:
+ requests:
+ cpu: 100m
+ memory: 512Mi
+ limits:
+ memory: 2Gi
+ volumeMounts:
+ - name: arr-config
+ mountPath: /home/suwayomi/.local/share/Suwayomi-Server
+ subPath: suwayomi
+ - name: arr-data
+ mountPath: /data/manga
+ subPath: media/manga
+ volumes:
+ - name: arr-config
+ persistentVolumeClaim:
+ claimName: arr-config
+ - name: arr-data
+ persistentVolumeClaim:
+ claimName: arr-data
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: suwayomi
+ namespace: arr-stack
+spec:
+ selector:
+ app: suwayomi
+ ports:
+ - port: 4567
+ targetPort: http
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: maintainerr
+ namespace: arr-stack
+ labels:
+ app: maintainerr
+spec:
+ replicas: 1
+ strategy:
+ type: Recreate
+ selector:
+ matchLabels:
+ app: maintainerr
+ template:
+ metadata:
+ labels:
+ app: maintainerr
+ spec:
+ nodeSelector:
+ kubernetes.io/os: linux
+ securityContext:
+ fsGroup: 1000
+ fsGroupChangePolicy: OnRootMismatch
+ containers:
+ - name: maintainerr
+ image: ghcr.io/jorenn92/maintainerr:latest
+ imagePullPolicy: IfNotPresent
+ env:
+ - name: PUID
+ value: "1000"
+ - name: PGID
+ value: "1000"
+ - name: TZ
+ value: America/Mexico_City
+ ports:
+ - containerPort: 6246
+ name: http
+ resources:
+ requests:
+ cpu: 100m
+ memory: 256Mi
+ limits:
+ memory: 1Gi
+ volumeMounts:
+ - name: arr-config
+ mountPath: /opt/data
+ subPath: maintainerr
+ volumes:
+ - name: arr-config
+ persistentVolumeClaim:
+ claimName: arr-config
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: maintainerr
+ namespace: arr-stack
+spec:
+ selector:
+ app: maintainerr
+ ports:
+ - port: 6246
+ targetPort: http
diff --git a/apps/heimdall/web-app.yaml b/apps/heimdall/web-app.yaml
index 954ea2f..cdafe95 100644
--- a/apps/heimdall/web-app.yaml
+++ b/apps/heimdall/web-app.yaml
@@ -88,6 +88,55 @@ data:
"description": "Workflow automation for translation jobs and homelab tasks",
"colour": "#ea4b71",
"class": "N8N"
+ },
+ {
+ "title": "Prowlarr",
+ "url": "https://prowlarr.lab2025.duckdns.org/",
+ "description": "Indexer management for the media automation stack",
+ "colour": "#2563eb",
+ "class": "Prowlarr"
+ },
+ {
+ "title": "Sonarr",
+ "url": "https://sonarr.lab2025.duckdns.org/",
+ "description": "TV library automation",
+ "colour": "#22c55e",
+ "class": "Sonarr"
+ },
+ {
+ "title": "Radarr",
+ "url": "https://radarr.lab2025.duckdns.org/",
+ "description": "Movie library automation",
+ "colour": "#f59e0b",
+ "class": "Radarr"
+ },
+ {
+ "title": "qBittorrent",
+ "url": "https://qbittorrent.lab2025.duckdns.org/",
+ "description": "Torrent download client",
+ "colour": "#3b82f6",
+ "class": "Qbittorrent"
+ },
+ {
+ "title": "Kapowarr",
+ "url": "https://kapowarr.lab2025.duckdns.org/",
+ "description": "Comics library automation",
+ "colour": "#ef4444",
+ "class": "Kapowarr"
+ },
+ {
+ "title": "Suwayomi",
+ "url": "https://suwayomi.lab2025.duckdns.org/",
+ "description": "Manga server and scraper engine",
+ "colour": "#8b5cf6",
+ "class": "Suwayomi"
+ },
+ {
+ "title": "Maintainerr",
+ "url": "https://maintainerr.lab2025.duckdns.org/",
+ "description": "Media list maintenance automation",
+ "colour": "#14b8a6",
+ "class": "Maintainerr"
}
]
seed.py: |
diff --git a/apps/website/blog.php b/apps/website/blog.php
index c98d7b9..bca35b7 100644
--- a/apps/website/blog.php
+++ b/apps/website/blog.php
@@ -443,7 +443,7 @@ function renderStackSourceLinks(string $stackKey, array $sourceLinks, string $so
Orange Pi 5 Plus Pimox
pimox-worker app nodes
- workers on nvme_thin_pool
+ workers on nvme_storage
Argo CD, Kyverno, website, n8n
Redis cache and app workloads
diff --git a/apps/website/lang/en.php b/apps/website/lang/en.php
index 2082b6f..db7959d 100644
--- a/apps/website/lang/en.php
+++ b/apps/website/lang/en.php
@@ -126,14 +126,14 @@ return [
'blog_q7' => 'Can the current cluster actually handle all that, or are we about to smoke the Pi?',
'blog_a7' => 'The Pi survives because the demos are intentionally local-first and now ship as a separate static artifact. The website pod stays a portfolio shell, the demos-static pod serves static bundles, and the user browser does the expensive work. If I later ship real ONNX object detection, Transformers.js, or full video transcoding models, those must lazy-load in the browser or move to a beefier node. The Raspberry Pi is brave, but it is not a GPU wearing a tiny hat.',
'blog_q8' => 'So the lab can now build its own worker nodes?',
- 'blog_a8' => 'Yes, and now with fewer crossed fingers. Debian runs a provisioning layer with dnsmasq, nginx, PXE boot files, GRUB, and a Debian 13 arm64 preseed. OpenTofu talks to Pimox through qm, creates VM 9000 on local storage, boots it from the network, installs the OS, runs golden-node prep, disables swap, verifies cgroups, installs containerd and kubeadm tooling, then seals it as a template. Worker clones are idempotent by VMID and now land on nvme_thin_pool, so local storage stays reserved for the template.',
+ 'blog_a8' => 'Yes, and now with fewer crossed fingers. Debian runs a provisioning layer with dnsmasq, nginx, PXE boot files, GRUB, and a Debian 13 arm64 preseed. OpenTofu talks to Pimox through qm, creates VM 9000 on local storage, boots it from the network, installs the OS, runs golden-node prep, disables swap, verifies cgroups, installs containerd and kubeadm tooling, then seals it as a template. Worker clones are idempotent by VMID and now land on nvme_storage, so local storage stays reserved for the template.',
'blog_q9' => 'And OpenWrt is joining the story too?',
'blog_a9' => 'Only as a simple firewall, not as a networking science project. The pipeline can create an opt-in OpenWrt ARM SystemReady VM, attach vmbr0 as WAN and vmbr1 as LAN, and configure the LAN side without rewriting Orange Pi host networking. DHCP stays optional, and VLANs wait until there is a managed switch and a local test window.',
'blog_q10' => 'What changed on the observability and scheduling side?',
'blog_a10' => 'Monitoring moved from "someday" to "running," and scheduling moved from "whatever fits" to explicit worker placement. Prometheus Stack, Grafana, Loki, Promtail, node-exporter, and kube-state-metrics give the lab useful signals; the next useful step is choosing the few alerts that would actually wake me up for the right reasons.',
'blog_stack_title' => 'Technologies and why they are here',
'blog_stack_1' => 'Debian Linux is the steady adult in the room: control plane host, deployment workstation, PXE/preseed server, and the place where OpenTofu, Docker, kubeadm, and the scripts do their thing.',
- 'blog_stack_2' => 'Raspberry Pi keeps the external Gitea service close to the lab, while Pimox on the Orange Pi 5 Plus provides the app-worker pool. The Debian template stays on local storage and Kubernetes worker clones go to nvme_thin_pool.',
+ 'blog_stack_2' => 'Raspberry Pi keeps the external Gitea service close to the lab, while Pimox on the Orange Pi 5 Plus provides the app-worker pool. The Debian template stays on local storage and Kubernetes worker clones go to nvme_storage.',
'blog_stack_3' => 'OpenTofu makes the cluster, platform, apps, edge, and provisioning layers repeatable, because "I swear I remember the command" is not a disaster recovery strategy.',
'blog_stack_4' => 'Calico handles pod networking, and OpenEBS hostpath storage keeps the important data around after rebuilds, because deleting everything by accident is only funny once.',
'blog_stack_5' => 'Argo CD is the GitOps referee and now runs on app workers: manifests live in Git, the cluster follows along, and manual drift gets side-eyed back into place.',
@@ -146,7 +146,7 @@ return [
'blog_stack_12' => 'The Pimox worker pipeline uses qm over SSH to create an OVMF/virtio-scsi Debian 13 arm64 VM, wait for qemu-guest-agent, seal it, and convert VM 9000 into a reusable template on local storage.',
'blog_stack_13' => 'The golden image bakes in Kubernetes prerequisites: swap disabled, cgroup boot options checked, kernel modules loaded, containerd configured for systemd cgroups, kubeadm/kubelet/kubectl installed, and qemu-guest-agent enabled.',
'blog_stack_14' => 'Worker clone automation is count-based and idempotent: LAB_PIMOX_WORKER_COUNT=1 means ensure VM 9010 exists, not create a fresh worker on every run. New workers are refused if the target storage is local.',
- 'blog_stack_15' => 'OpenWrt is handled separately from the Debian golden-node template. The lab downloads the upstream ARM SystemReady EFI image, imports it as VM 9050 on nvme_thin_pool, and keeps it disabled unless LAB_OPENWRT_VM=true is set.',
+ 'blog_stack_15' => 'OpenWrt is handled separately from the Debian golden-node template. The lab downloads the upstream ARM SystemReady EFI image, imports it as VM 9050 on nvme_storage, and keeps it disabled unless LAB_OPENWRT_VM=true is set.',
'blog_stack_16' => 'The monitoring layer now includes Prometheus Stack, Grafana, Loki, Promtail, node-exporter, and kube-state-metrics, with placement guardrails so platform add-ons stop defaulting to the control plane.',
'blog_stack_17' => 'The portfolio website keeps Apache at the core, runs PHP through PHP-FPM with OPcache, pre-renders read-heavy pages into static HTML during the image build, and leaves PHP for write and translation endpoints.',
'blog_stack_18' => 'The supply-chain path now uses BuildKit SBOM attestations, Cosign signatures, a cluster-published public key, and an audit-first Kyverno ImageValidatingPolicy that can be promoted to deny unsigned local-registry pods.',
@@ -169,7 +169,7 @@ return [
'blog_activity_8' => 'Changed Gitea backups to dump from the Raspberry Pi Docker container and store archives on the Debian host.',
'blog_activity_9' => 'Validated the full main-branch deployment path: fetch main, apply OpenTofu layers, build and push arm64 images, refresh Argo CD, and confirm the runner completes successfully.',
'blog_activity_10' => 'Built the Debian 13 arm64 Pimox template end to end with PXE, preseed, qemu-guest-agent discovery, cgroup validation, swap disabled, and a final seal step.',
- 'blog_activity_11' => 'Added NVMe-backed Pimox worker clone automation so VM 9000 stays on local storage while worker nodes are created on nvme_thin_pool.',
+ 'blog_activity_11' => 'Added NVMe-backed Pimox worker clone automation so VM 9000 stays on local storage while worker nodes are created on nvme_storage.',
'blog_activity_12' => 'Added an opt-in OpenWrt VM path for a simple firewall between vmbr0 and vmbr1, with guardrails that avoid Orange Pi host networking changes.',
'blog_activity_13' => 'Installed the monitoring stack and moved platform add-ons such as Argo CD, Kyverno, and prometheus-stack work toward app-worker placement instead of treating the control plane as spare capacity.',
'blog_activity_14' => 'Reworked the website image to keep Apache as the web server while moving PHP execution to PHP-FPM, enabling OPcache, and validating the runtime through Gitea Actions instead of local image builds.',
@@ -188,7 +188,7 @@ return [
'blog_todo_8' => 'Turn the installed observability stack into useful operations views: a few high-signal dashboards, alerts for node health, storage pressure, certificate expiry, and failed app syncs.',
'blog_todo_9' => 'Schedule backup restore drills for external Gitea and OpenEBS volumes, then write the exact restore runbook.',
'blog_todo_10' => 'Tighten TLS, SSH, and token rotation around the OCI edge, Gitea, registry, and runner credentials.',
- 'blog_todo_11' => 'Document the new storage split: local for the Pimox template, nvme_thin_pool for VM workers, OpenEBS for Kubernetes app data, and backup targets for anything that must survive a rebuild.',
+ 'blog_todo_11' => 'Document the new storage split: local for the Pimox template, nvme_storage for VM workers, OpenEBS for Kubernetes app data, and backup targets for anything that must survive a rebuild.',
'blog_todo_12' => 'Move sensitive app configuration into Sealed Secrets, External Secrets, or another explicit secret-management path.',
'blog_todo_13' => 'After the new disk has breathing room, clone the first Pimox worker from VM 9000, join it with kubeadm, and verify labels, taints, CNI, storage, and workload scheduling.',
'blog_todo_14' => 'Test the OpenWrt VM in a maintenance window before making it a gateway: confirm WAN, LAN, rollback access, DHCP settings, and that Pimox remains reachable.',
diff --git a/apps/website/lang/nah.php b/apps/website/lang/nah.php
index 3f05656..bd8b5cc 100644
--- a/apps/website/lang/nah.php
+++ b/apps/website/lang/nah.php
@@ -126,14 +126,14 @@ return [
'blog_q7' => 'Current cluster huel quipia nochi inin, o tiquchichinos Pi?',
'blog_a7' => 'Pi survives pampa demos cateh intentionally local-first ihuan now ship as separate static artifact. Website pod mocahua portfolio shell, demos-static pod serves static bundles, ihuan browser quichihua expensive work. Tla later onicship real ONNX object detection, Transformers.js, o full video transcoding models, monequi lazy-load ipan browser o yasque beefier node.',
'blog_q8' => 'Axcan lab huel quichihua own worker nodes?',
- 'blog_a8' => 'Quena, ihuan axcan ika fewer crossed fingers. Debian quipia provisioning layer ika dnsmasq, nginx, PXE boot files, GRUB, ihuan Debian 13 arm64 preseed. OpenTofu notza Pimox through qm, quichihua VM 9000 ipan local storage, quiboota from network, quinstala OS, quichihua golden-node prep, disables swap, verifies cgroups, installs containerd ihuan kubeadm tooling, then seals it as template. Worker clones cateh idempotent by VMID ihuan now land on nvme_thin_pool.',
+ 'blog_a8' => 'Quena, ihuan axcan ika fewer crossed fingers. Debian quipia provisioning layer ika dnsmasq, nginx, PXE boot files, GRUB, ihuan Debian 13 arm64 preseed. OpenTofu notza Pimox through qm, quichihua VM 9000 ipan local storage, quiboota from network, quinstala OS, quichihua golden-node prep, disables swap, verifies cgroups, installs containerd ihuan kubeadm tooling, then seals it as template. Worker clones cateh idempotent by VMID ihuan now land on nvme_storage.',
'blog_q9' => 'Ihuan OpenWrt no quisa nican?',
'blog_a9' => 'Zan simple firewall, ahmo networking science project. Pipeline huel quichihua opt-in OpenWrt ARM SystemReady VM, attach vmbr0 quen WAN ihuan vmbr1 quen LAN, ihuan configure LAN side ahmo rewriting Orange Pi host networking. DHCP stays optional, VLANs wait until onca managed switch ihuan local test window.',
'blog_q10' => 'Tlein omopatla ipan observability ihuan scheduling side?',
'blog_a10' => 'Monitoring moved de "someday" to "running," ihuan scheduling moved de "whatever fits" to explicit worker placement. Prometheus Stack, Grafana, Loki, Promtail, node-exporter, ihuan kube-state-metrics quimaca useful signals; next step quitosnequi elegir few alerts in melahuac nechixitisque para right reasons.',
'blog_stack_title' => 'Tlamantli ihuan tleica nemi nican',
'blog_stack_1' => 'Debian Linux quimaca steady adult in room: control plane host, deployment workstation, PXE/preseed server, ihuan canin OpenTofu, Docker, kubeadm, ihuan scripts quichihua tequitl.',
- 'blog_stack_2' => 'Raspberry Pi keeps external Gitea service close to lab, while Pimox ipan Orange Pi 5 Plus provides app-worker pool. Debian template mocahua ipan local storage ihuan Kubernetes worker clones yahui ipan nvme_thin_pool.',
+ 'blog_stack_2' => 'Raspberry Pi keeps external Gitea service close to lab, while Pimox ipan Orange Pi 5 Plus provides app-worker pool. Debian template mocahua ipan local storage ihuan Kubernetes worker clones yahui ipan nvme_storage.',
'blog_stack_3' => 'OpenTofu quichihua cluster, platform, apps, edge, ihuan provisioning layers repeatable, pampa "I swear I remember the command" amo disaster recovery strategy.',
'blog_stack_4' => 'Calico handles pod networking, ihuan OpenEBS hostpath storage keeps important data after rebuilds, pampa deleting everything by accident zan funny once.',
'blog_stack_5' => 'Argo CD quimaca GitOps referee ihuan now runs on app workers: manifests cateh ipan Git, cluster follows along, ihuan manual drift gets side-eyed back into place.',
@@ -146,7 +146,7 @@ return [
'blog_stack_12' => 'Pimox worker pipeline uses qm over SSH para create OVMF/virtio-scsi Debian 13 arm64 VM, wait for qemu-guest-agent, seal it, ihuan convert VM 9000 into reusable template on local storage.',
'blog_stack_13' => 'Golden image bakes in Kubernetes prerequisites: swap disabled, cgroup boot options checked, kernel modules loaded, containerd configured for systemd cgroups, kubeadm/kubelet/kubectl installed, ihuan qemu-guest-agent enabled.',
'blog_stack_14' => 'Worker clone automation is count-based ihuan idempotent: LAB_PIMOX_WORKER_COUNT=1 quitosnequi ensure VM 9010 exists, amo create fresh worker every run. New workers refused tla target storage is local.',
- 'blog_stack_15' => 'OpenWrt handled separately from Debian golden-node template. Lab downloads upstream ARM SystemReady EFI image, imports it as VM 9050 on nvme_thin_pool, ihuan keeps disabled unless LAB_OPENWRT_VM=true.',
+ 'blog_stack_15' => 'OpenWrt handled separately from Debian golden-node template. Lab downloads upstream ARM SystemReady EFI image, imports it as VM 9050 on nvme_storage, ihuan keeps disabled unless LAB_OPENWRT_VM=true.',
'blog_stack_16' => 'Monitoring layer axcan includes Prometheus Stack, Grafana, Loki, Promtail, node-exporter, ihuan kube-state-metrics, ika placement guardrails para platform add-ons ma amo default to control plane.',
'blog_stack_17' => 'Portfolio website keeps Apache at core, runs PHP through PHP-FPM ika OPcache, pre-renders read-heavy pages into static HTML during image build, ihuan leaves PHP para write ihuan translation endpoints.',
'blog_stack_18' => 'Supply-chain path axcan uses BuildKit SBOM attestations, Cosign signatures, public key ipan cluster, ihuan audit-first Kyverno ImageValidatingPolicy in huel mopromote para deny unsigned local-registry pods.',
@@ -169,7 +169,7 @@ return [
'blog_activity_8' => 'Changed Gitea backups to dump from Raspberry Pi Docker container ihuan store archives on Debian host.',
'blog_activity_9' => 'Validated full main-branch deployment path: fetch main, apply OpenTofu layers, build ihuan push arm64 images, refresh Argo CD, ihuan confirm runner completes successfully.',
'blog_activity_10' => 'Built Debian 13 arm64 Pimox template end to end ika PXE, preseed, qemu-guest-agent discovery, cgroup validation, swap disabled, ihuan final seal step.',
- 'blog_activity_11' => 'Added NVMe-backed Pimox worker clone automation para VM 9000 ma mocahua ipan local storage mientras worker nodes mochihuah ipan nvme_thin_pool.',
+ 'blog_activity_11' => 'Added NVMe-backed Pimox worker clone automation para VM 9000 ma mocahua ipan local storage mientras worker nodes mochihuah ipan nvme_storage.',
'blog_activity_12' => 'Added opt-in OpenWrt VM path para simple firewall between vmbr0 ihuan vmbr1, ika guardrails in avoid Orange Pi host networking changes.',
'blog_activity_13' => 'Installed monitoring stack ihuan moved platform add-ons such as Argo CD, Kyverno, ihuan prometheus-stack toward app-worker placement instead of treating control plane as spare capacity.',
'blog_activity_14' => 'Reworked website image para Apache ma mocahua web server while PHP runs through PHP-FPM, OPcache enabled, ihuan runtime validated through Gitea Actions instead of local image builds.',
@@ -188,7 +188,7 @@ return [
'blog_todo_8' => 'Turn installed observability stack into useful operations views: few high-signal dashboards, alerts para node health, storage pressure, certificate expiry, ihuan failed app syncs.',
'blog_todo_9' => 'Schedule backup restore drills para external Gitea ihuan OpenEBS volumes, then write exact restore runbook.',
'blog_todo_10' => 'Tighten TLS, SSH, ihuan token rotation around OCI edge, Gitea, registry, ihuan runner credentials.',
- 'blog_todo_11' => 'Document new storage split: local para Pimox template, nvme_thin_pool para VM workers, OpenEBS para Kubernetes app data, ihuan backup targets para tlein must survive rebuild.',
+ 'blog_todo_11' => 'Document new storage split: local para Pimox template, nvme_storage para VM workers, OpenEBS para Kubernetes app data, ihuan backup targets para tlein must survive rebuild.',
'blog_todo_12' => 'Move sensitive app configuration into Sealed Secrets, External Secrets, o occe explicit secret-management path.',
'blog_todo_13' => 'After new disk has breathing room, clone first Pimox worker from VM 9000, join it ika kubeadm, ihuan verify labels, taints, CNI, storage, ihuan workload scheduling.',
'blog_todo_14' => 'Test OpenWrt VM ipan maintenance window before making it gateway: confirm WAN, LAN, rollback access, DHCP settings, ihuan Pimox remains reachable.',
diff --git a/bootstrap/apps/variables.tf b/bootstrap/apps/variables.tf
index da60f4a..0d77486 100644
--- a/bootstrap/apps/variables.tf
+++ b/bootstrap/apps/variables.tf
@@ -75,6 +75,15 @@ variable "applications" {
self_heal = true
create_namespace = true
}
+ arr-stack = {
+ project = "default"
+ path = "apps/arr-stack"
+ namespace = "arr-stack"
+ target_revision = "main"
+ prune = true
+ self_heal = true
+ create_namespace = true
+ }
supply-chain-policy = {
project = "default"
path = "apps/supply-chain-policy"
diff --git a/bootstrap/provisioning/README.md b/bootstrap/provisioning/README.md
index 804e0f9..0ecb02c 100644
--- a/bootstrap/provisioning/README.md
+++ b/bootstrap/provisioning/README.md
@@ -112,7 +112,7 @@ networking, virtio-scsi disk, `vmbr0`, `local` template storage, 1 socket with
`./lab.sh up` also creates or reuses worker clones after the template exists. It
defaults to one worker, VMID `9010`, name `pimox-worker-01`, deterministic
locally administered MAC address, 1 socket
-with 2 cores, 4 GiB RAM, `nvme_thin_pool` clone storage, and qemu-guest-agent IP
+with 2 cores, 4 GiB RAM, `nvme_storage` clone storage, and qemu-guest-agent IP
discovery. New workers are full clones created with
`qm clone --storage`, so the template can remain on `local` while worker disks
land on the NVMe thin pool. Set `LAB_PIMOX_WORKER_REPLACE_EXISTING=true` to
@@ -136,7 +136,7 @@ LAB_PIMOX_WORKER_COUNT=0 ./lab.sh up
LAB_PIMOX_WORKER_COUNT=2 ./lab.sh up
LAB_PIMOX_SKIP_WORKER_INDEXES=1 LAB_PIMOX_WORKER_COUNT=2 ./lab.sh up
LAB_PIMOX_WORKER_BASE_VMID=9020 ./lab.sh up
-LAB_PIMOX_WORKER_STORAGE=nvme_thin_pool ./lab.sh up
+LAB_PIMOX_WORKER_STORAGE=nvme_storage ./lab.sh up
LAB_PIMOX_WORKER_REPLACE_EXISTING=true ./lab.sh up
LAB_PIMOX_HOST=192.168.100.80 LAB_PIMOX_BRIDGE=vmbr0 ./lab.sh up
```
@@ -159,7 +159,7 @@ Defaults:
- VMID `9100`
- VM name `openwrt-firewall`
-- disk storage `nvme_thin_pool`
+- disk storage `nvme_storage`
- WAN bridge `vmbr0`
- LAN bridge `vmbr1`
- LAN address `192.168.50.1/24`
@@ -172,7 +172,7 @@ Useful overrides:
```bash
LAB_OPENWRT_VMID=9100
-LAB_OPENWRT_STORAGE=nvme_thin_pool
+LAB_OPENWRT_STORAGE=nvme_storage
LAB_OPENWRT_WAN_BRIDGE=vmbr0
LAB_OPENWRT_LAN_BRIDGE=vmbr1
LAB_OPENWRT_LAN_IP=192.168.50.1
@@ -184,6 +184,6 @@ LAB_OPENWRT_VERSION=24.10.6
LAB_OPENWRT_IMAGE_URL=https://downloads.openwrt.org/releases/24.10.6/targets/armsr/armv8/openwrt-24.10.6-armsr-armv8-generic-ext4-combined-efi.img.gz
```
-The pipeline validates `vmbr0`, `vmbr1`, and `nvme_thin_pool` on the Pimox host.
+The pipeline validates `vmbr0`, `vmbr1`, and `nvme_storage` on the Pimox host.
It refuses `local` as OpenWrt storage and refuses to create or modify host
network bridges.
diff --git a/lab.sh b/lab.sh
index b20ebe5..83c94b9 100755
--- a/lab.sh
+++ b/lab.sh
@@ -894,7 +894,7 @@ run_pimox_pipeline() {
local worker_memory="${LAB_PIMOX_WORKER_MEMORY:-4096}"
local worker_cpu_affinities="${LAB_PIMOX_WORKER_CPU_AFFINITIES:-}"
local worker_replace_existing="${LAB_PIMOX_WORKER_REPLACE_EXISTING:-false}"
- local worker_storage="${LAB_PIMOX_WORKER_STORAGE:-${TF_VAR_pimox_worker_storage:-nvme_thin_pool}}"
+ local worker_storage="${LAB_PIMOX_WORKER_STORAGE:-${TF_VAR_pimox_worker_storage:-nvme_storage}}"
local worker_user="${LAB_PIMOX_WORKER_USER:-jv}"
local worker_key_path="${LAB_PIMOX_WORKER_SSH_KEY_PATH:-/home/jv/.ssh/id_ed25519}"
local ip_prefix="${LAB_PIMOX_GUEST_IP_PREFIX:-192.168.100.}"
@@ -1054,7 +1054,7 @@ run_openwrt_pipeline() {
local qm_bin="${LAB_PIMOX_QM_BIN:-${TF_VAR_pimox_qm_bin:-/usr/sbin/qm}}"
local vmid="${LAB_OPENWRT_VMID:-9100}"
local vm_name="${LAB_OPENWRT_NAME:-openwrt-firewall}"
- local storage="${LAB_OPENWRT_STORAGE:-nvme_thin_pool}"
+ local storage="${LAB_OPENWRT_STORAGE:-nvme_storage}"
local wan_bridge="${LAB_OPENWRT_WAN_BRIDGE:-vmbr0}"
local lan_bridge="${LAB_OPENWRT_LAN_BRIDGE:-vmbr1}"
local cores="${LAB_OPENWRT_CORES:-2}"
@@ -3107,6 +3107,18 @@ apps() {
wait_for_namespaced_resource n8n deployment n8n n8n 300
wait_for_deployment_ready n8n n8n n8n 300
+ refresh_argocd_application arr-stack
+ wait_for_namespace arr-stack arr-stack 300
+ wait_for_namespaced_resource arr-stack deployment prowlarr arr-stack 300
+ wait_for_deployment_ready arr-stack flaresolverr arr-stack 300
+ wait_for_deployment_ready arr-stack prowlarr arr-stack 300
+ wait_for_deployment_ready arr-stack radarr arr-stack 300
+ wait_for_deployment_ready arr-stack sonarr arr-stack 300
+ wait_for_deployment_ready arr-stack qbittorrent arr-stack 300
+ wait_for_deployment_ready arr-stack kapowarr arr-stack 300
+ wait_for_deployment_ready arr-stack suwayomi arr-stack 300
+ wait_for_deployment_ready arr-stack maintainerr arr-stack 300
+
echo "Application deployment successfully completed."
}