From b6f72eab0c803b1dca44b1bc03772bcc3839a7c8 Mon Sep 17 00:00:00 2001 From: Keith Jackson Date: Wed, 26 Aug 2026 00:27:47 -0500 Subject: [PATCH] =?UTF-8?q?feat(apps):=20onboard=20seerr=20(Overseerr=20re?= =?UTF-8?q?quest=20UI)=20=E2=80=94=20completes=20arr=E2=86=94seerr?= =?UTF-8?q?=E2=86=94plex=20graph?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 6: add the seerr ServiceDefinition and its rendered manifests. seerr is the final missing node in the media dependency graph (docs/13 row 5; docs/03 allow rules). - catalog/seerr.yaml: public | egress internet | config on longhorn (5Gi) | auth native (Plex accounts, documented SSO exception, docs/15) | 3 explicit egress deps: sonarr:8989, radarr:7878, plex:32400. No bulk volume (OQ-2 unresolved). - apps/seerr.yaml: rendered via catalog/render.py --no-image-automation (default-deny DNS-only baseline + 3 dep CiliumNetworkPolicies + gateway-ingress + internet world egress + HTTPRoute seerr.home.keithjackson.dev:5055 + ServiceMonitor + alerts). - apps/kustomization.yaml: add seerr.yaml (with the arr/media stack). Gates: render --selftest PASS; ci lint PASS; L0 kyverno 17/17 (schema + default-deny + egress-class + auth-mode + host-ns); L1 render 3/3. Other apps byte-identical. Co-Authored-By: Claude Opus 4.8 --- apps/kustomization.yaml | 1 + apps/seerr.yaml | 269 ++++++++++++++++++++++++++++++++++++++++ catalog/seerr.yaml | 61 +++++++++ 3 files changed, 331 insertions(+) create mode 100644 apps/seerr.yaml create mode 100644 catalog/seerr.yaml diff --git a/apps/kustomization.yaml b/apps/kustomization.yaml index cc042ab..2d995d7 100644 --- a/apps/kustomization.yaml +++ b/apps/kustomization.yaml @@ -16,6 +16,7 @@ resources: - plex.yaml - qbittorrent.yaml - jackett.yaml + - seerr.yaml # Gitea-only supplementary patch: headless-install env + break-glass admin postStart hook. # The renderer does not yet emit env/lifecycle (renderer follow-up); see gitea-bootstrap-patch.yaml. patches: diff --git a/apps/seerr.yaml b/apps/seerr.yaml new file mode 100644 index 0000000..16a27f6 --- /dev/null +++ b/apps/seerr.yaml @@ -0,0 +1,269 @@ +--- +# Namespace (trust zone; default-deny + policies apply) +apiVersion: v1 +kind: Namespace +metadata: + name: seerr + 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: seerr + 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 seerr-config (R-STATE) +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: seerr-config + namespace: seerr + labels: + app.kubernetes.io/name: seerr +spec: + accessModes: + - ReadWriteOnce + storageClassName: longhorn + resources: + requests: + storage: 5Gi +--- +# Deployment (R-PLAT-02) +apiVersion: apps/v1 +kind: Deployment +metadata: + name: seerr + namespace: seerr + labels: + app.kubernetes.io/name: seerr +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app.kubernetes.io/name: seerr + template: + metadata: + labels: + app.kubernetes.io/name: seerr + homelab.keithjackson.dev/egress-class: internet + homelab.keithjackson.dev/auth-mode: native + spec: + containers: + - name: seerr + image: sctx/overseerr:latest + ports: + - containerPort: 5055 + volumeMounts: + - name: config + mountPath: /data/config + volumes: + - name: config + persistentVolumeClaim: + claimName: seerr-config +--- +# Service +apiVersion: v1 +kind: Service +metadata: + name: seerr + namespace: seerr + labels: + app.kubernetes.io/name: seerr +spec: + selector: + app.kubernetes.io/name: seerr + ports: + - name: port-5055 + port: 5055 + targetPort: 5055 +--- +# CiliumNetworkPolicy egress allow (R-NET-02): seerr-allow-sonarr-8989 +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: seerr-allow-sonarr-8989 + namespace: seerr + labels: + app.kubernetes.io/name: seerr +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: seerr + egress: + - toEndpoints: + - matchLabels: + app.kubernetes.io/name: sonarr + toPorts: + - ports: + - port: '8989' + protocol: TCP +--- +# CiliumNetworkPolicy egress allow (R-NET-02): seerr-allow-radarr-7878 +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: seerr-allow-radarr-7878 + namespace: seerr + labels: + app.kubernetes.io/name: seerr +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: seerr + egress: + - toEndpoints: + - matchLabels: + app.kubernetes.io/name: radarr + toPorts: + - ports: + - port: '7878' + protocol: TCP +--- +# CiliumNetworkPolicy egress allow (R-NET-02): seerr-allow-plex-32400 +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: seerr-allow-plex-32400 + namespace: seerr + labels: + app.kubernetes.io/name: seerr +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: seerr + egress: + - toEndpoints: + - matchLabels: + app.kubernetes.io/name: plex + toPorts: + - ports: + - port: '32400' + protocol: TCP +--- +# CiliumNetworkPolicy gateway-ingress allow (R-NET-01/02): seerr-allow-gateway-ingress +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: seerr-allow-gateway-ingress + namespace: seerr + labels: + app.kubernetes.io/name: seerr +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: seerr + ingress: + - fromEntities: + - ingress + toPorts: + - ports: + - port: '5055' + protocol: TCP +--- +# CiliumNetworkPolicy world egress allow (egress class governs the path; R-NET-05): seerr-allow-internet-egress +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: seerr-allow-internet-egress + namespace: seerr + labels: + app.kubernetes.io/name: seerr +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: seerr + egress: + - toEntities: + - world +--- +# HTTPRoute seerr-5055 (R-TLS/R-DNS) +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: seerr-5055 + namespace: seerr + labels: + app.kubernetes.io/name: seerr +spec: + parentRefs: + - name: shared + namespace: gateway + sectionName: https + hostnames: + - seerr.home.keithjackson.dev + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: seerr + port: 5055 +--- +# ServiceMonitor (R-OBS-01) +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: seerr + namespace: seerr + labels: + release: kube-prometheus-stack + app.kubernetes.io/name: seerr +spec: + selector: + matchLabels: + app.kubernetes.io/name: seerr + namespaceSelector: + matchNames: + - seerr + endpoints: + - port: metrics + interval: 30s +--- +# PrometheusRule SLI alerts (R-OBS-01) +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: seerr-slis + namespace: seerr + labels: + release: kube-prometheus-stack + app.kubernetes.io/name: seerr +spec: + groups: + - name: seerr.slis + rules: + - alert: seerr-down + expr: up{job="seerr"} == 0 + for: 5m + labels: + severity: critical + service: seerr + annotations: + summary: seerr is down + description: 'seerr has been unreachable for 5m (SLI: up).' diff --git a/catalog/seerr.yaml b/catalog/seerr.yaml new file mode 100644 index 0000000..0b1104e --- /dev/null +++ b/catalog/seerr.yaml @@ -0,0 +1,61 @@ +# ServiceDefinition: seerr +# Source of truth: spec docs/13-service-catalog.md row 5; docs/15-identity-and-access.md; +# docs/03-networking-isolation-and-egress.md (explicit allow rules). +# public | egress internet | config on Longhorn | auth: native (Plex accounts) | +# seerr -> sonarr:8989, seerr -> radarr:7878, seerr -> plex:32400. +# +# Seerr is the media request UI (Overseerr): users request titles, it hands them to the arr +# stack and reflects library state from Plex. It is publicly exposed (edge port-forward + public +# DNS, docs/09) and authenticates with Plex's own account system — a DOCUMENTED SSO exception +# (docs/13/15; R-IDP-02): NOT Keycloak, so no oidc/forward-auth and no auth.groups. The three +# east-west hops below are EXPLICIT allows on top of default-deny (docs/03; R-NET-01/02); +# everything else (except DNS) is denied. Validates against schemas/servicedefinition.schema.json. +# +# Image: Overseerr (sctx/overseerr) — the Plex-native request UI on port 5055 (docs/13 row 5 +# names "seerr" generically; Overseerr is the Plex-account variant). `:latest` mirrors the +# onboarding posture used for plex (image-automation off during onboarding; update.range is the +# advisory bound the ImagePolicy will enforce once the Flux image controllers are installed). +# +# No `bulk` volume: seerr keeps only its own config PVC (settings + request/cache DB) on the +# longhorn tier. It stores no media and needs no bulk mount, so OQ-2 (unresolved bulk backing) +# does not apply here. +name: seerr +kind: container +source: + image: sctx/overseerr:latest # Overseerr: Plex-native request UI (docs/13 row 5); served on 5055 +exposure: public # edge port-forward + public DNS (docs/09; R-DNS/R-NET) +egress: internet # normal WAN egress (docs/03; R-NET-05) — TMDB/Plex.tv metadata over the internet +storage: + - name: config + size: 5Gi + class: longhorn # settings + request/cache DB: replicated app state on the 3-replica tier (docs/04; R-STATE-01/02) +dependencies: + - to: sonarr # seerr -> sonarr: submit TV requests (docs/03 allow rules; docs/13 graph; R-NET-02) + port: 8989 + protocol: tcp + - to: radarr # seerr -> radarr: submit movie requests (docs/03 allow rules; docs/13 graph; R-NET-02) + port: 7878 + protocol: tcp + - to: plex # seerr -> plex: read library / watch state (docs/03 allow rules; docs/13 graph; R-NET-02) + port: 32400 + protocol: tcp +ingressRoutes: + - host: seerr.home.keithjackson.dev + port: 5055 # Overseerr web UI + API; wildcard TLS terminates at the shared Gateway (docs/08/09) +auth: + mode: native # Plex account auth — documented exception, NOT Keycloak (docs/13/15; R-IDP-02) +update: + channel: stable # R-UPD-01 (never pre-release) + range: ">=1.33.0" # acceptable versions (advisory; image-automation off during onboarding) + cadence: "0 4 * * 0" # weekly stable check/apply + autoRollbackOnFailedHealth: true # R-UPD-03 +observability: + metrics: true + slis: + - up # R-OBS-01: service-down alert +backup: + # seerr's config is on the longhorn (replicated) tier, so the L0 Kyverno backup policy + # (servicedefinition-backup; R-STATE-03) forbids `reacquirable` here — it must reference an + # off-cluster schedule. The request/settings DB is cheap to rebuild but still lives on the + # irreplaceable tier, so it is backed up off-cluster like the other arr-stack config volumes. + policy: daily-offcluster # off-cluster backup of the longhorn config volume (docs/04; R-STATE-03)