Compare commits

..

7 Commits

Author SHA1 Message Date
Ben Vincent 54c25be828 Reduce media PR to jellyfin-only in its own namespace
ci/woodpecker/pr/vector-test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
Why:
Jellyfin ships and gets validated first, ahead of the rest of the media stack.
Scoping this PR to jellyfin alone keeps the initial rollout small and lets the
HA fork prove out against the real library before the download and manager apps
follow.

How:
- Drop sonarr, radarr, prowlarr, bazarr, nzbget, and jellyseerr and their shared
  media-apps foundation from this PR; they land in later PRs.
- Move jellyfin into its own jellyfin namespace and fold the namespace and the
  static mediafs PV plus its RWX claim into the jellyfin base.
- Keep the static CephFS PV bound to the in-use mediafs library with
  reclaimPolicy Retain and staticVolume true so nothing can reclaim it, mounted
  into jellyfin by the movies and tvseries subPaths; keep redis, the fresh RWX
  transcode scratch, the intel iGPU nodeSelector and i915 request, gateway, and
  httproute.
- Scope the media AppProject and ApplicationSet to the single jellyfin
  namespace and app, extensible as the remaining apps are added.
2026-08-09 21:08:12 +10:00
Ben Vincent a52a419dfd Point media apps at the real mediafs library via a static CephFS PV
ci/woodpecker/pr/vector-test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
Why:
The media apps must serve and manage the actual media library, not empty
volumes. That library already exists on the puppet-managed CephFS filesystem
mediafs (mounted by the VM/incus instances at /shared/media) and is in active
use, so the k8s apps must mount it in place rather than provision fresh storage.

How:
- Replace the two fresh movies/tvseries PVCs with one static CephFS
  PersistentVolume bound to mediafs and a single RWX media-library claim the
  whole stack shares.
- Set the PV reclaim policy to Retain and mark it staticVolume so ceph-csi only
  mounts the pre-existing storage and can never provision or reclaim it;
  deleting the PVC or PV cannot destroy the underlying library.
- Reuse the live csi-cephfs cluster parameters (clusterID cephfs_csi_ssd_ec_4_1
  for mon discovery, csi-cephfs/csi-cephfs-secret node-stage secret) with
  fsName mediafs and rootPath / (the mediafs root that maps to /shared/media).
- Mount the library into each app by subPath so the tree matches the VM
  layout: sonarr /mnt/tvseries (tvseries), radarr /mnt/movies (movies),
  jellyfin and nzbget both subtrees; prowlarr keeps no library mount. The
  jellyfin transcode PVC stays a fresh scratch volume.
- Whitelist PersistentVolume in the media AppProject so the cluster-scoped PV
  can sync.
2026-08-09 13:39:40 +10:00
Ben Vincent e03aeca101 Add media-apps stack to ArgoCD
ci/woodpecker/pr/vector-test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
Why:
The media stack (jellyfin plus the sonarr/radarr/prowlarr/bazarr/nzbget/
jellyseerr apps) runs in the media-apps namespace but is deployed out-of-band
by terraform-k8s rather than GitOps. Bringing it under ArgoCD makes the stack
declarative, self-healing, and consistent with every other cluster workload,
and prepares terraform-k8s to drop the media-apps config.

How:
- Add a media AppProject scoped to the media-apps namespace and a media-apps
  ApplicationSet that renders one Application per app plus a shared foundation.
- Add a shared media-apps foundation (namespace, media-apps-vault-reader
  ServiceAccount, default VaultAuth on k8s/au/syd1, and the RWX movies/tvseries
  library PVCs) that the whole stack mounts.
- Add per-app kustomize base and au-syd1 overlay for jellyfin and the six *arr
  apps, using plain resource names (jellyfin, sonarr, ...) with fresh PVCs.
- Deploy jellyfin from the jellyfin-ha fork (Redis transcode store, RWX
  transcode scratch) wired to the shared movies/tvseries library PVCs, keeping
  the intel iGPU nodeSelector and gpu.intel.com/i915 request.
- Source API keys and nzbget credentials through VSO VaultStaticSecrets from
  kv/service/media-apps/<app>; expose each app via a traefik-internal Gateway
  and HTTPRoute at <app>.k8s.syd1.au.unkin.net.
- Register the media project and applicationset in the argocd bootstrap
  kustomizations.
2026-08-09 13:25:25 +10:00
unkinben 4d58f37ea5 Fix cert-manager recursive-nameserver ControllerConfiguration field (#347)
## Why
- The cert-manager v1.20.2 controller crashloops: strict decoding of its ControllerConfiguration rejects the unknown field `acmeDNS01` (`failed to load config file ... strict decoding error: unknown field "acmeDNS01"`), so `/var/cert-manager/config/config.yaml` fails to load and the controller never starts. The rollout is stuck with only the old pod running.
- PR #337 placed the DNS-01 recursive-nameserver settings under `acmeDNS01`, but the field in the `controller.config.cert-manager.io/v1alpha1` schema is `acmeDNS01Config` (`ACMEDNS01Config`, with `recursiveNameservers` / `recursiveNameserversOnly`). The recursive-ns settings belong in the config file, not `extraArgs`; the CLI flags feed the same struct but the chart already renders a `--config` ControllerConfiguration, so the correct fix is the correct field name.

## How
- Rename the `config:` block `acmeDNS01` to `acmeDNS01Config`, keeping `recursiveNameservers` (`8.8.8.8:53`, `1.1.1.1:53`) and `recursiveNameserversOnly: true` so DNS-01 resolution and self-checks still use the public DNS view for the split-horizon delegation.

Rendered `kustomize build --enable-helm` confirms the ConfigMap `config.yaml` now carries a valid `acmeDNS01Config` block and no longer contains the invalid `acmeDNS01`; the cert-manager overlay is kubeconform-clean (55 valid, 0 invalid).

---------

Co-authored-by: Ben Vincent <neotheo@gmail.com>
Reviewed-on: #347
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-08-09 12:35:38 +10:00
unkinben 07bd94c55a Wire API_TOKEN_PEPPERS into NetBox config (#346)
## Why

NetBox 4.6.5 refuses to save v2 API tokens without `API_TOKEN_PEPPERS` ("Unable to save v2 tokens: API_TOKEN_PEPPERS is not defined"), which blocks creating the superuser token the NetBox Vault engine needs (it defaults to v2 tokens). The chart only auto-generates a pepper when it creates the config secret itself; it does not do that while `existingSecret` (`netbox-secret-key`) is set, so the config secret carries no `api_token_peppers` key.

## Changes

- Document `api_token_peppers` on the `netbox-secret-key` VaultStaticSecret: a JSON pepper map `{"1": "<random>"}` seeded once into Vault alongside `secret_key`. VSO syncs every key at the path into the config secret, which the chart already mounts as an optional file into `API_TOKEN_PEPPERS`.
- Add a reloader annotation via `commonAnnotations` so the `netbox` and `netbox-worker` Deployments roll when `netbox-secret-key` changes, picking up the seeded pepper (and any rotated `secret_key`) without a manual restart.

## Follow-up (out of band)

seed the pepper once (rotating it invalidates existing v2 tokens):

```
PEP=$(openssl rand -base64 48 | tr -d '\n')
vault kv patch kv/kubernetes/namespace/netbox/default/netbox-secret-key \
  api_token_peppers="{\"1\": \"$PEP\"}"
```

---------

Co-authored-by: Ben Vincent <neotheo@gmail.com>
Reviewed-on: #346
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-08-09 12:33:30 +10:00
unkinben 2739a29778 Bump kea images to v0.1.3 (HA peer DNS startup wait) (#344)
## Why
kea-dhcp4 crash-loops on a cold container start: the HA hook resolves the StatefulSet peer URL hostnames once at config load, but the peer DNS records aren't resolvable in the first instant of a fresh container, and kea exits hard instead of retrying. Verified in-cluster that the rendered config validates once DNS is warm, so it's a startup race. kea-operator v0.1.3 gates dhcp4 startup on a bounded `kea-dhcp4 -t` retry (~120s, then proceeds/fails loud).

## How
- bump kea-operator, kea, and kea-api images to v0.1.3

---------

Co-authored-by: Ben Vincent <neotheo@gmail.com>
Reviewed-on: #344
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
2026-08-08 23:36:25 +10:00
unkinben 5e409f5f06 traefik-internal: add ldaps :636 entrypoint for authentik LDAPS (#345) 2026-08-08 23:19:18 +10:00
25 changed files with 509 additions and 6 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ metadata:
argocd.argoproj.io/sync-wave: "1"
spec:
replicas: 1
image: git.unkin.net/unkin/kea-api:v0.1.2
image: git.unkin.net/unkin/kea-api:v0.1.3
tokenSecretName: kea-api-token
service:
type: ClusterIP
+1 -1
View File
@@ -11,7 +11,7 @@ metadata:
argocd.argoproj.io/sync-wave: "1"
spec:
replicas: 2
image: git.unkin.net/unkin/kea:v0.1.2
image: git.unkin.net/unkin/kea:v0.1.3
domainName: main.unkin.net
defaultLeaseTime: 1200
maxLeaseTime: 86400
+1 -1
View File
@@ -21,7 +21,7 @@ spec:
runAsNonRoot: true
containers:
- name: operator
image: git.unkin.net/unkin/kea-operator:v0.1.2
image: git.unkin.net/unkin/kea-operator:v0.1.3
args:
- --metrics-bind-address=:8080
- --health-probe-bind-address=:8081
+97
View File
@@ -0,0 +1,97 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jellyfin
namespace: jellyfin
spec:
# Single-replica for now. The jellyfin-ha fork adds the Redis transcode store
# and RWX transcode scratch that make scaling to true HA a follow-up.
replicas: 1
strategy:
# Config PVC is RWO; Recreate avoids two pods contending for it.
type: Recreate
selector:
matchLabels:
app: jellyfin
template:
metadata:
labels:
app: jellyfin
spec:
securityContext:
fsGroup: 1000
nodeSelector:
feature.node.kubernetes.io/pci-0300_8086.present: "true"
containers:
- name: jellyfin
image: git.unkin.net/unkin/jellyfin-ha:v0.1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8096
protocol: TCP
env:
- name: TZ
value: Australia/Sydney
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: JELLYFIN_PublishedServerUrl
value: https://jellyfin.k8s.syd1.au.unkin.net
# Distributed transcode session store (jellyfin-ha additions).
- name: Jellyfin__TranscodeStore__RedisConnectionString
value: "jellyfin-redis:6379,abortConnect=false"
- name: Jellyfin__TranscodeStore__LeaseDurationSeconds
value: "30"
resources:
requests:
cpu: 100m
memory: 1Gi
limits:
cpu: "4"
memory: 8Gi
gpu.intel.com/i915: "1"
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
volumeMounts:
- name: config
mountPath: /config
- name: cache
mountPath: /cache
- name: transcode
mountPath: /transcode
- name: media-library
mountPath: /mnt/movies
subPath: movies
- name: media-library
mountPath: /mnt/tvseries
subPath: tvseries
volumes:
- name: config
persistentVolumeClaim:
claimName: jellyfin-config
- name: cache
persistentVolumeClaim:
claimName: jellyfin-cache
- name: transcode
persistentVolumeClaim:
claimName: jellyfin-transcode
- name: media-library
persistentVolumeClaim:
claimName: media-library
+37
View File
@@ -0,0 +1,37 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
labels:
traefik.io/instance: internal
annotations:
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: jellyfin.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
external-dns.alpha.kubernetes.io/hostname: jellyfin.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.4
name: jellyfin
namespace: jellyfin
spec:
gatewayClassName: traefik-internal
listeners:
- allowedRoutes:
namespaces:
from: Same
hostname: jellyfin.k8s.syd1.au.unkin.net
name: http
port: 80
protocol: HTTP
- allowedRoutes:
namespaces:
from: Same
hostname: jellyfin.k8s.syd1.au.unkin.net
name: https
port: 443
protocol: HTTPS
tls:
certificateRefs:
- group: ""
kind: Secret
name: jellyfin-tls
mode: Terminate
+49
View File
@@ -0,0 +1,49 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http-redirect
namespace: jellyfin
spec:
hostnames:
- jellyfin.k8s.syd1.au.unkin.net
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: jellyfin
sectionName: http
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
matches:
- path:
type: PathPrefix
value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: jellyfin
namespace: jellyfin
spec:
hostnames:
- jellyfin.k8s.syd1.au.unkin.net
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: jellyfin
sectionName: https
rules:
- backendRefs:
- group: ""
kind: Service
name: jellyfin
port: 8096
weight: 1
matches:
- path:
type: PathPrefix
value: /
+18
View File
@@ -0,0 +1,18 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- pv_media-library.yaml
- pvc_media-library.yaml
- pvc_config.yaml
- pvc_cache.yaml
- pvc_transcode.yaml
- deployment.yaml
- service.yaml
- redis-deployment.yaml
- redis-service.yaml
- redis-pvc.yaml
- gateway.yaml
- httproute.yaml
+5
View File
@@ -0,0 +1,5 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: jellyfin
+41
View File
@@ -0,0 +1,41 @@
---
# Static CephFS PersistentVolume bound to the pre-existing, ACTIVELY-USED
# puppet media library (ceph filesystem "mediafs", mounted by the VM/incus
# instances at /shared/media). ceph-csi only mounts this volume; staticVolume
# tells it the storage pre-exists and it must never provision or delete it.
#
# reclaimPolicy MUST stay Retain: deleting this PV or its PVC must NEVER be able
# to reclaim or destroy the underlying CephFS data that the VM instances use.
apiVersion: v1
kind: PersistentVolume
metadata:
name: jellyfin-media-library
spec:
accessModes:
- ReadWriteMany
capacity:
storage: 10Ti
# Load-bearing safety control. Do not change to Delete.
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
volumeMode: Filesystem
# Pre-bind to the media-library claim so nothing else can grab this PV.
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: media-library
namespace: jellyfin
csi:
driver: cephfs.csi.ceph.com
volumeHandle: jellyfin-media-library-static
nodeStageSecretRef:
name: csi-cephfs-secret
namespace: csi-cephfs
volumeAttributes:
# clusterID maps (in the csi-cephfs ceph-csi-config) to the mon set that
# also serves mediafs; for a static volume only the mon lookup is used.
clusterID: cephfs_csi_ssd_ec_4_1
fsName: mediafs
staticVolume: "true"
# Filesystem-internal root of the library (mediafs root == /shared/media).
rootPath: /
+15
View File
@@ -0,0 +1,15 @@
---
# Local transcode/image cache. Scratch, delete reclaim policy.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jellyfin-cache
namespace: jellyfin
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 200Gi
storageClassName: cephrbd-fast-delete
volumeMode: Filesystem
+15
View File
@@ -0,0 +1,15 @@
---
# Jellyfin config + SQLite library database. Single-writer, block storage.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jellyfin-config
namespace: jellyfin
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: cephrbd-fast-retain
volumeMode: Filesystem
+19
View File
@@ -0,0 +1,19 @@
---
# Claim bound to the static mediafs PV. RWX so every app in the stack shares the
# one library. storageClassName "" + volumeName pin it to the static PV (no
# dynamic provisioning). Deleting this claim cannot reclaim the data (PV is
# Retain).
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: media-library
namespace: jellyfin
spec:
accessModes:
- ReadWriteMany
storageClassName: ""
volumeName: jellyfin-media-library
resources:
requests:
storage: 10Ti
volumeMode: Filesystem
+17
View File
@@ -0,0 +1,17 @@
---
# Shared transcode scratch. ReadWriteMany is the hard requirement for the HA
# fork: a taking-over pod must read the in-flight HLS segments written by the
# pod it replaces. Scratch data, so delete reclaim policy.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jellyfin-transcode
namespace: jellyfin
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Gi
storageClassName: cephfs-raid5-delete
volumeMode: Filesystem
+64
View File
@@ -0,0 +1,64 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jellyfin-redis
namespace: jellyfin
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: jellyfin-redis
template:
metadata:
labels:
app: jellyfin-redis
spec:
restartPolicy: Always
containers:
- name: redis
image: redis:7-alpine
imagePullPolicy: IfNotPresent
command:
- redis-server
- --save
- "20"
- "1"
ports:
- name: redis
containerPort: 6379
protocol: TCP
livenessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: jellyfin-redis-data
+14
View File
@@ -0,0 +1,14 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jellyfin-redis-data
namespace: jellyfin
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: cephrbd-fast-delete
volumeMode: Filesystem
+17
View File
@@ -0,0 +1,17 @@
---
apiVersion: v1
kind: Service
metadata:
name: jellyfin-redis
namespace: jellyfin
spec:
type: ClusterIP
internalTrafficPolicy: Cluster
sessionAffinity: None
selector:
app: jellyfin-redis
ports:
- name: redis
port: 6379
targetPort: redis
protocol: TCP
+17
View File
@@ -0,0 +1,17 @@
---
apiVersion: v1
kind: Service
metadata:
name: jellyfin
namespace: jellyfin
spec:
type: ClusterIP
internalTrafficPolicy: Cluster
sessionAffinity: None
selector:
app: jellyfin
ports:
- name: http
port: 8096
targetPort: http
protocol: TCP
+7 -1
View File
@@ -19,7 +19,13 @@ spec:
type: kv-v2
vaultAuthRef: default
---
# Django SECRET_KEY (key: secret_key). One-time Vault seed.
# Config secret. Keys:
# secret_key : Django SECRET_KEY (50+ random chars). One-time Vault seed.
# api_token_peppers : JSON object {"1": "<50+ char random>"} used to HMAC-hash
# v2 API tokens. One-time Vault seed — rotating a pepper
# invalidates existing v2 tokens, so set it once.
# VSO syncs every key at the path into the destination Secret, and the NetBox
# chart mounts both keys from it (existingSecret) — no explicit key mapping needed.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
@@ -5,7 +5,7 @@ config:
apiVersion: controller.config.cert-manager.io/v1alpha1
kind: ControllerConfiguration
enableGatewayAPI: true
acmeDNS01:
acmeDNS01Config:
recursiveNameservers:
- "8.8.8.8:53"
- "1.1.1.1:53"
@@ -0,0 +1,6 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../../base/jellyfin
+12 -1
View File
@@ -13,13 +13,24 @@ image:
# Two web replicas for zero-downtime rollouts (media is RWX, see below).
replicaCount: 2
# Django SECRET_KEY — existingSecret must contain key: secret_key.
# Config secret. Must contain key: secret_key (Django SECRET_KEY). Also carries
# api_token_peppers — a JSON object {"1": "<50+ char random>"} the chart mounts
# (optional file) into API_TOKEN_PEPPERS; NetBox 4.6 refuses to save v2 tokens
# without it. The chart only auto-generates a pepper when it creates this secret
# itself, which it does NOT do while existingSecret is set — so the pepper is
# seeded into Vault alongside secret_key (see base/vaultstaticsecret.yaml).
existingSecret: netbox-secret-key
# Bootstrap superuser — existingSecret keys: username, password, email, api_token.
superuser:
existingSecret: netbox-superuser
# Roll NetBox (web + worker) when the config secret changes, so a Vault-seeded
# api_token_peppers (or a rotated secret_key) is picked up without a manual
# restart. commonAnnotations lands on Deployment metadata, where reloader reads it.
commonAnnotations:
secret.reloader.stakater.com/reload: netbox-secret-key
# Disable the bundled Bitnami subcharts; we bring our own Postgres and Valkey.
postgresql:
enabled: false
@@ -5,6 +5,7 @@ kind: Kustomization
resources:
- aitooling.yaml
- logging.yaml
- media.yaml
- observability.yaml
- platform.yaml
- storage.yaml
+32
View File
@@ -0,0 +1,32 @@
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: media-apps
namespace: argocd
spec:
generators:
- git:
repoURL: https://git.unkin.net/unkin/argocd-apps
revision: HEAD
directories:
# jellyfin only for now; downloads/managers apps join in later PRs.
- path: apps/overlays/*/jellyfin
template:
metadata:
name: 'media-{{path[3]}}'
spec:
project: media
source:
repoURL: https://git.unkin.net/unkin/argocd-apps
targetRevision: HEAD
path: '{{path}}'
destination:
server: https://kubernetes.default.svc
namespace: jellyfin
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- ServerSideApply=true
+1
View File
@@ -5,6 +5,7 @@ kind: Kustomization
resources:
- aitooling.yaml
- logging.yaml
- media.yaml
- observability.yaml
- platform.yaml
- storage.yaml
+21
View File
@@ -0,0 +1,21 @@
---
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: media
namespace: argocd
spec:
description: Media services (jellyfin; downloads/managers namespaces to follow)
sourceRepos:
- https://git.unkin.net/unkin/argocd-apps
destinations:
- namespace: jellyfin
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: ''
kind: Namespace
- group: ''
kind: PersistentVolume
namespaceResourceWhitelist:
- group: '*'
kind: '*'