Merge feature/onboard-arr: Sonarr + Radarr (VPN + arr->downloader allows)

This commit is contained in:
jakman85 committed 2026-08-25 08:45:56 -05:00
commit c8c862908b
5 files changed
+570

No files matched your search

+2
View File
@@ -8,6 +8,8 @@ kind: Kustomization
resources:
- pihole.yaml
- gitea.yaml
- sonarr.yaml
- radarr.yaml
- plex.yaml
- qbittorrent.yaml
- jackett.yaml
+236
View File
@@ -0,0 +1,236 @@
---
# Namespace (trust zone; default-deny + policies apply)
apiVersion: v1
kind: Namespace
metadata:
name: radarr
labels:
homelab.keithjackson.dev/app-namespace: 'true'
gateway-access: shared
annotations:
homelab.keithjackson.dev/backup-policy: daily-offcluster
---
# Default-deny baseline (R-NET-01): deny all, allow only DNS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: radarr
labels:
homelab.keithjackson.dev/baseline: default-deny
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress: []
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# PVC radarr-config (R-STATE)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: radarr-config
namespace: radarr
labels:
app.kubernetes.io/name: radarr
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 5Gi
---
# Deployment (R-PLAT-02)
apiVersion: apps/v1
kind: Deployment
metadata:
name: radarr
namespace: radarr
labels:
app.kubernetes.io/name: radarr
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: radarr
template:
metadata:
labels:
app.kubernetes.io/name: radarr
homelab.keithjackson.dev/egress-class: vpn
homelab.keithjackson.dev/auth-mode: forward-auth
annotations:
homelab.keithjackson.dev/auth-mode: forward-auth
homelab.keithjackson.dev/auth-groups: media-users
spec:
containers:
- name: radarr
image: lscr.io/linuxserver/radarr:5.21.1
ports:
- containerPort: 7878
volumeMounts:
- name: config
mountPath: /data/config
volumes:
- name: config
persistentVolumeClaim:
claimName: radarr-config
---
# Service
apiVersion: v1
kind: Service
metadata:
name: radarr
namespace: radarr
labels:
app.kubernetes.io/name: radarr
spec:
selector:
app.kubernetes.io/name: radarr
ports:
- name: port-7878
port: 7878
targetPort: 7878
---
# CiliumNetworkPolicy egress allow (R-NET-02): radarr-allow-qbittorrent-8080
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: radarr-allow-qbittorrent-8080
namespace: radarr
labels:
app.kubernetes.io/name: radarr
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: radarr
egress:
- toEndpoints:
- matchLabels:
app.kubernetes.io/name: qbittorrent
toPorts:
- ports:
- port: '8080'
protocol: TCP
---
# CiliumNetworkPolicy egress allow (R-NET-02): radarr-allow-jackett-9117
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: radarr-allow-jackett-9117
namespace: radarr
labels:
app.kubernetes.io/name: radarr
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: radarr
egress:
- toEndpoints:
- matchLabels:
app.kubernetes.io/name: jackett
toPorts:
- ports:
- port: '9117'
protocol: TCP
---
# CiliumNetworkPolicy gateway-ingress allow (R-NET-01/02): radarr-allow-gateway-ingress
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: radarr-allow-gateway-ingress
namespace: radarr
labels:
app.kubernetes.io/name: radarr
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: radarr
ingress:
- fromEntities:
- ingress
toPorts:
- ports:
- port: '7878'
protocol: TCP
---
# HTTPRoute radarr-7878 (R-TLS/R-DNS)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: radarr-7878
namespace: radarr
labels:
app.kubernetes.io/name: radarr
annotations:
homelab.keithjackson.dev/auth-mode: forward-auth
homelab.keithjackson.dev/auth-groups: media-users
spec:
parentRefs:
- name: shared
namespace: gateway
sectionName: https
hostnames:
- radarr.home.keithjackson.dev
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: radarr
port: 7878
---
# ServiceMonitor (R-OBS-01)
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: radarr
namespace: radarr
labels:
release: kube-prometheus-stack
app.kubernetes.io/name: radarr
spec:
selector:
matchLabels:
app.kubernetes.io/name: radarr
namespaceSelector:
matchNames:
- radarr
endpoints:
- port: metrics
interval: 30s
---
# PrometheusRule SLI alerts (R-OBS-01)
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: radarr-slis
namespace: radarr
labels:
release: kube-prometheus-stack
app.kubernetes.io/name: radarr
spec:
groups:
- name: radarr.slis
rules:
- alert: radarr-down
expr: up{job="radarr"} == 0
for: 5m
labels:
severity: critical
service: radarr
annotations:
summary: radarr is down
description: 'radarr has been unreachable for 5m (SLI: up).'
+236
View File
@@ -0,0 +1,236 @@
---
# Namespace (trust zone; default-deny + policies apply)
apiVersion: v1
kind: Namespace
metadata:
name: sonarr
labels:
homelab.keithjackson.dev/app-namespace: 'true'
gateway-access: shared
annotations:
homelab.keithjackson.dev/backup-policy: daily-offcluster
---
# Default-deny baseline (R-NET-01): deny all, allow only DNS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: sonarr
labels:
homelab.keithjackson.dev/baseline: default-deny
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress: []
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# PVC sonarr-config (R-STATE)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sonarr-config
namespace: sonarr
labels:
app.kubernetes.io/name: sonarr
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 5Gi
---
# Deployment (R-PLAT-02)
apiVersion: apps/v1
kind: Deployment
metadata:
name: sonarr
namespace: sonarr
labels:
app.kubernetes.io/name: sonarr
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: sonarr
template:
metadata:
labels:
app.kubernetes.io/name: sonarr
homelab.keithjackson.dev/egress-class: vpn
homelab.keithjackson.dev/auth-mode: forward-auth
annotations:
homelab.keithjackson.dev/auth-mode: forward-auth
homelab.keithjackson.dev/auth-groups: media-users
spec:
containers:
- name: sonarr
image: lscr.io/linuxserver/sonarr:4.0.15
ports:
- containerPort: 8989
volumeMounts:
- name: config
mountPath: /data/config
volumes:
- name: config
persistentVolumeClaim:
claimName: sonarr-config
---
# Service
apiVersion: v1
kind: Service
metadata:
name: sonarr
namespace: sonarr
labels:
app.kubernetes.io/name: sonarr
spec:
selector:
app.kubernetes.io/name: sonarr
ports:
- name: port-8989
port: 8989
targetPort: 8989
---
# CiliumNetworkPolicy egress allow (R-NET-02): sonarr-allow-qbittorrent-8080
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: sonarr-allow-qbittorrent-8080
namespace: sonarr
labels:
app.kubernetes.io/name: sonarr
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: sonarr
egress:
- toEndpoints:
- matchLabels:
app.kubernetes.io/name: qbittorrent
toPorts:
- ports:
- port: '8080'
protocol: TCP
---
# CiliumNetworkPolicy egress allow (R-NET-02): sonarr-allow-jackett-9117
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: sonarr-allow-jackett-9117
namespace: sonarr
labels:
app.kubernetes.io/name: sonarr
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: sonarr
egress:
- toEndpoints:
- matchLabels:
app.kubernetes.io/name: jackett
toPorts:
- ports:
- port: '9117'
protocol: TCP
---
# CiliumNetworkPolicy gateway-ingress allow (R-NET-01/02): sonarr-allow-gateway-ingress
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: sonarr-allow-gateway-ingress
namespace: sonarr
labels:
app.kubernetes.io/name: sonarr
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: sonarr
ingress:
- fromEntities:
- ingress
toPorts:
- ports:
- port: '8989'
protocol: TCP
---
# HTTPRoute sonarr-8989 (R-TLS/R-DNS)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: sonarr-8989
namespace: sonarr
labels:
app.kubernetes.io/name: sonarr
annotations:
homelab.keithjackson.dev/auth-mode: forward-auth
homelab.keithjackson.dev/auth-groups: media-users
spec:
parentRefs:
- name: shared
namespace: gateway
sectionName: https
hostnames:
- sonarr.home.keithjackson.dev
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: sonarr
port: 8989
---
# ServiceMonitor (R-OBS-01)
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: sonarr
namespace: sonarr
labels:
release: kube-prometheus-stack
app.kubernetes.io/name: sonarr
spec:
selector:
matchLabels:
app.kubernetes.io/name: sonarr
namespaceSelector:
matchNames:
- sonarr
endpoints:
- port: metrics
interval: 30s
---
# PrometheusRule SLI alerts (R-OBS-01)
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: sonarr-slis
namespace: sonarr
labels:
release: kube-prometheus-stack
app.kubernetes.io/name: sonarr
spec:
groups:
- name: sonarr.slis
rules:
- alert: sonarr-down
expr: up{job="sonarr"} == 0
for: 5m
labels:
severity: critical
service: sonarr
annotations:
summary: sonarr is down
description: 'sonarr has been unreachable for 5m (SLI: up).'
+48
View File
@@ -0,0 +1,48 @@
# ServiceDefinition: radarr
# Source of truth: spec docs/13-service-catalog.md row 3; docs/15-identity-and-access.md; docs/03.
# internal | egress VPN (+ kill-switch) | config on Longhorn | auth forward-auth | arr→downloaders.
# Radarr (movies) scrapes indexers and hands releases to the download client; both of those hops leave
# the cluster over the shared PIA/WireGuard egress gateway (egress: vpn, ADR-0015; R-NET-05/06), and
# the intra-cluster hops to qbittorrent/jackett are EXPLICIT east-west allows on top of default-deny
# (R-NET-01/02). Validates against schemas/servicedefinition.schema.json.
name: radarr
kind: container
source:
image: lscr.io/linuxserver/radarr:5.21.1 # pinned stable v5 tag (R-UPD-01); update.range governs advancement
exposure: internal # LAN-only web UI at radarr.home.keithjackson.dev (docs/13 row 3; R-DNS/R-NET)
egress: vpn # indexer scrape + downloader handoff steered via the shared VPN egress gateway,
# kill-switch enforced OUTSIDE the workload (docs/03; ADR-0015; R-NET-05/06)
storage:
- name: config
size: 5Gi
class: longhorn # app DB / config on the 3-replica tier; DR-critical (docs/04; R-STATE-02)
# NOTE (bulk-media PREREQUISITE): docs/13 row 3 places the media library / downloads volume on the
# `bulk` tier (R-STATE-04). The `bulk` StorageClass exists (platform/longhorn/storageclasses.yaml)
# but is a NON-provisioning STUB pending OQ-2 (NFS/SMB backing = REPLACE-ME). Adding a `class: bulk`
# volume now would leave the PVC (and therefore the pod) Pending. The media/downloads volume is
# therefore DEFERRED until OQ-2 is resolved; config-only on longhorn keeps Radarr deployable today.
dependencies:
- to: qbittorrent # arr → download client (docs/13 dependency graph; R-NET-02 explicit allow)
port: 8080
protocol: tcp
- to: jackett # arr → indexer proxy (docs/13 dependency graph; R-NET-02 explicit allow)
port: 9117
protocol: tcp
ingressRoutes:
- host: radarr.home.keithjackson.dev
port: 7878 # Radarr web/API; TLS terminates at the shared Gateway (docs/08, docs/09)
auth:
mode: forward-auth # bare UI, no usable OIDC: oauth2-proxy → Keycloak at the Gateway (docs/15; R-IDP-02)
groups:
- media-users # authenticated AND in the media group (R-IDP-06)
update:
channel: stable # R-UPD-01
range: ">=5.0.0 <6.0.0" # stay on the Radarr v5 stable line
cadence: "0 4 * * *"
autoRollbackOnFailedHealth: true # R-UPD-03
observability:
metrics: true
slis:
- up # arr down => alert (docs/10; R-OBS-01)
backup:
policy: daily-offcluster # Radarr config/DB is irreplaceable, backed up off-cluster (docs/04; R-STATE-03)
+48
View File
@@ -0,0 +1,48 @@
# ServiceDefinition: sonarr
# Source of truth: spec docs/13-service-catalog.md row 2; docs/15-identity-and-access.md; docs/03.
# internal | egress VPN (+ kill-switch) | config on Longhorn | auth forward-auth | arr→downloaders.
# Sonarr (TV) scrapes indexers and hands releases to the download client; both of those hops leave
# the cluster over the shared PIA/WireGuard egress gateway (egress: vpn, ADR-0015; R-NET-05/06), and
# the intra-cluster hops to qbittorrent/jackett are EXPLICIT east-west allows on top of default-deny
# (R-NET-01/02). Validates against schemas/servicedefinition.schema.json.
name: sonarr
kind: container
source:
image: lscr.io/linuxserver/sonarr:4.0.15 # pinned stable v4 tag (R-UPD-01); update.range governs advancement
exposure: internal # LAN-only web UI at sonarr.home.keithjackson.dev (docs/13 row 2; R-DNS/R-NET)
egress: vpn # indexer scrape + downloader handoff steered via the shared VPN egress gateway,
# kill-switch enforced OUTSIDE the workload (docs/03; ADR-0015; R-NET-05/06)
storage:
- name: config
size: 5Gi
class: longhorn # app DB / config on the 3-replica tier; DR-critical (docs/04; R-STATE-02)
# NOTE (bulk-media PREREQUISITE): docs/13 row 2 places the media library / downloads volume on the
# `bulk` tier (R-STATE-04). The `bulk` StorageClass exists (platform/longhorn/storageclasses.yaml)
# but is a NON-provisioning STUB pending OQ-2 (NFS/SMB backing = REPLACE-ME). Adding a `class: bulk`
# volume now would leave the PVC (and therefore the pod) Pending. The media/downloads volume is
# therefore DEFERRED until OQ-2 is resolved; config-only on longhorn keeps Sonarr deployable today.
dependencies:
- to: qbittorrent # arr → download client (docs/13 dependency graph; R-NET-02 explicit allow)
port: 8080
protocol: tcp
- to: jackett # arr → indexer proxy (docs/13 dependency graph; R-NET-02 explicit allow)
port: 9117
protocol: tcp
ingressRoutes:
- host: sonarr.home.keithjackson.dev
port: 8989 # Sonarr web/API; TLS terminates at the shared Gateway (docs/08, docs/09)
auth:
mode: forward-auth # bare UI, no usable OIDC: oauth2-proxy → Keycloak at the Gateway (docs/15; R-IDP-02)
groups:
- media-users # authenticated AND in the media group (R-IDP-06)
update:
channel: stable # R-UPD-01
range: ">=4.0.0 <5.0.0" # stay on the Sonarr v4 stable line
cadence: "0 4 * * *"
autoRollbackOnFailedHealth: true # R-UPD-03
observability:
metrics: true
slis:
- up # arr down => alert (docs/10; R-OBS-01)
backup:
policy: daily-offcluster # Sonarr config/DB is irreplaceable, backed up off-cluster (docs/04; R-STATE-03)