diff --git a/apps/base/arrstack/kustomization.yaml b/apps/base/arrstack/kustomization.yaml new file mode 100644 index 0000000..c68c8c5 --- /dev/null +++ b/apps/base/arrstack/kustomization.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - namespace.yaml + - pv-media-tv.yaml + - pv-media-movies.yaml + - pvc-media-tv.yaml + - pvc-media-movies.yaml + - sonarr + - radarr + - prowlarr + - nzbget diff --git a/apps/base/arrstack/namespace.yaml b/apps/base/arrstack/namespace.yaml new file mode 100644 index 0000000..e57af7f --- /dev/null +++ b/apps/base/arrstack/namespace.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: arrstack diff --git a/apps/base/arrstack/nzbget/deployment.yaml b/apps/base/arrstack/nzbget/deployment.yaml new file mode 100644 index 0000000..acf6381 --- /dev/null +++ b/apps/base/arrstack/nzbget/deployment.yaml @@ -0,0 +1,137 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nzbget + namespace: arrstack +spec: + replicas: 1 + strategy: + # RWO config PVC + single queue state: never run two pods at once. + type: Recreate + selector: + matchLabels: + app: nzbget + template: + metadata: + labels: + app: nzbget + spec: + securityContext: + fsGroup: 1000 + fsGroupChangePolicy: OnRootMismatch + initContainers: + # Seed download layout onto the shared media PVCs (not /config or an + # emptyDir) so completed downloads land beside the arr libraries and + # imports are same-filesystem hardlink moves. Reuses the image's own + # template (inherits correct WebDir/ConfigTemplate) and appends the + # path/category overrides once; nzbget honours the last value for a + # repeated option, and the grep guard keeps re-runs idempotent so admin + # UI edits to the persisted /config/nzbget.conf survive restarts. + - name: seed-config + image: artifactapi.k8s.syd1.au.unkin.net/dockerhub/linuxserver/nzbget:v26.2 + command: + - sh + - -c + - | + set -e + if [ ! -f /config/nzbget.conf ]; then + cp /app/nzbget/share/nzbget/nzbget.conf /config/nzbget.conf + fi + if ! grep -q '# arrstack-managed' /config/nzbget.conf; then + cat >> /config/nzbget.conf << 'CONF' + + # arrstack-managed download layout (appended once; last value wins). + # Downloads land on the shared media PVCs by category so sonarr/radarr + # import with atomic hardlink moves (download dir + library share one + # filesystem per media type). InterDir is empty: nzbget writes each + # download straight into its category DestDir, so BOTH tv and movies + # stay on their own PVC with no cross-filesystem intermediate copy. + MainDir=/media/tv + InterDir= + DestDir=/media/tv/downloads + NzbDir=/config/nzb + QueueDir=/config/queue + TempDir=/config/tmp + ControlIP=0.0.0.0 + ControlPort=6789 + Category1.Name=tv + Category1.DestDir=/media/tv/downloads + Category2.Name=movies + Category2.DestDir=/media/movies/downloads + CONF + fi + mkdir -p /media/tv/series /media/tv/downloads /media/movies/films /media/movies/downloads + chown 1000:1000 /config/nzbget.conf \ + /media/tv /media/tv/series /media/tv/downloads \ + /media/movies /media/movies/films /media/movies/downloads + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 200m + memory: 128Mi + volumeMounts: + - name: config + mountPath: /config + - name: media-tv + mountPath: /media/tv + - name: media-movies + mountPath: /media/movies + containers: + - name: nzbget + image: artifactapi.k8s.syd1.au.unkin.net/dockerhub/linuxserver/nzbget:v26.2 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 6789 + protocol: TCP + env: + - name: PUID + value: "1000" + - name: PGID + value: "1000" + - name: TZ + value: Australia/Sydney + livenessProbe: + # nzbget's root path requires auth (401); a TCP check is the + # dependency-free liveness signal for the web/JSON-RPC server. + tcpSocket: + port: http + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + tcpSocket: + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + cpu: 200m + memory: 256Mi + limits: + # Headroom for par2 repair + unpack of large downloads. + cpu: "2" + memory: 2Gi + volumeMounts: + - name: config + mountPath: /config + - name: media-tv + mountPath: /media/tv + - name: media-movies + mountPath: /media/movies + volumes: + - name: config + persistentVolumeClaim: + claimName: nzbget-config + - name: media-tv + persistentVolumeClaim: + claimName: media-tv + - name: media-movies + persistentVolumeClaim: + claimName: media-movies diff --git a/apps/base/arrstack/nzbget/gateway.yaml b/apps/base/arrstack/nzbget/gateway.yaml new file mode 100644 index 0000000..a0e42e5 --- /dev/null +++ b/apps/base/arrstack/nzbget/gateway.yaml @@ -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: nzbget.k8s.syd1.au.unkin.net + cert-manager.io/private-key-size: "4096" + external-dns.alpha.kubernetes.io/hostname: nzbget.k8s.syd1.au.unkin.net + external-dns.alpha.kubernetes.io/target: 198.18.200.4 + name: nzbget + namespace: arrstack +spec: + gatewayClassName: traefik-internal + listeners: + - allowedRoutes: + namespaces: + from: Same + hostname: nzbget.k8s.syd1.au.unkin.net + name: http + port: 80 + protocol: HTTP + - allowedRoutes: + namespaces: + from: Same + hostname: nzbget.k8s.syd1.au.unkin.net + name: https + port: 443 + protocol: HTTPS + tls: + certificateRefs: + - group: "" + kind: Secret + name: nzbget-tls + mode: Terminate diff --git a/apps/base/arrstack/nzbget/httproute.yaml b/apps/base/arrstack/nzbget/httproute.yaml new file mode 100644 index 0000000..b4aa2fb --- /dev/null +++ b/apps/base/arrstack/nzbget/httproute.yaml @@ -0,0 +1,49 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: nzbget-http-redirect + namespace: arrstack +spec: + hostnames: + - nzbget.k8s.syd1.au.unkin.net + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: nzbget + 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: nzbget-route + namespace: arrstack +spec: + hostnames: + - nzbget.k8s.syd1.au.unkin.net + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: nzbget + sectionName: https + rules: + - backendRefs: + - group: "" + kind: Service + name: nzbget + port: 6789 + weight: 1 + matches: + - path: + type: PathPrefix + value: / diff --git a/apps/base/arrstack/nzbget/kustomization.yaml b/apps/base/arrstack/nzbget/kustomization.yaml new file mode 100644 index 0000000..a7f5633 --- /dev/null +++ b/apps/base/arrstack/nzbget/kustomization.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - pvc-config.yaml + - deployment.yaml + - service.yaml + - gateway.yaml + - httproute.yaml diff --git a/apps/base/arrstack/nzbget/pvc-config.yaml b/apps/base/arrstack/nzbget/pvc-config.yaml new file mode 100644 index 0000000..52c365b --- /dev/null +++ b/apps/base/arrstack/nzbget/pvc-config.yaml @@ -0,0 +1,16 @@ +--- +# NZBGet config + queue/temp state. RWO on cephrbd (block). Retain: this is +# state. The download data itself lives on the shared media PVCs, not here. +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: nzbget-config + namespace: arrstack +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + storageClassName: cephrbd-fast-retain + volumeMode: Filesystem diff --git a/apps/base/arrstack/nzbget/service.yaml b/apps/base/arrstack/nzbget/service.yaml new file mode 100644 index 0000000..43e29f4 --- /dev/null +++ b/apps/base/arrstack/nzbget/service.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: nzbget + namespace: arrstack +spec: + ports: + - name: http + port: 6789 + protocol: TCP + targetPort: http + selector: + app: nzbget + type: ClusterIP diff --git a/apps/base/arrstack/prowlarr/deployment.yaml b/apps/base/arrstack/prowlarr/deployment.yaml new file mode 100644 index 0000000..df9efde --- /dev/null +++ b/apps/base/arrstack/prowlarr/deployment.yaml @@ -0,0 +1,67 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: prowlarr + namespace: arrstack +spec: + replicas: 1 + strategy: + # RWO config PVC + single stateful SQLite DB: never run two pods at once. + type: Recreate + selector: + matchLabels: + app: prowlarr + template: + metadata: + labels: + app: prowlarr + spec: + securityContext: + fsGroup: 1000 + fsGroupChangePolicy: OnRootMismatch + containers: + - name: prowlarr + image: artifactapi.k8s.syd1.au.unkin.net/dockerhub/linuxserver/prowlarr:2.5.2 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 9696 + protocol: TCP + env: + - name: PUID + value: "1000" + - name: PGID + value: "1000" + - name: TZ + value: Australia/Sydney + livenessProbe: + httpGet: + path: /ping + port: http + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /ping + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: "1" + memory: 512Mi + volumeMounts: + - name: config + mountPath: /config + volumes: + - name: config + persistentVolumeClaim: + claimName: prowlarr-config diff --git a/apps/base/arrstack/prowlarr/gateway.yaml b/apps/base/arrstack/prowlarr/gateway.yaml new file mode 100644 index 0000000..4d8a19e --- /dev/null +++ b/apps/base/arrstack/prowlarr/gateway.yaml @@ -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: prowlarr.k8s.syd1.au.unkin.net + cert-manager.io/private-key-size: "4096" + external-dns.alpha.kubernetes.io/hostname: prowlarr.k8s.syd1.au.unkin.net + external-dns.alpha.kubernetes.io/target: 198.18.200.4 + name: prowlarr + namespace: arrstack +spec: + gatewayClassName: traefik-internal + listeners: + - allowedRoutes: + namespaces: + from: Same + hostname: prowlarr.k8s.syd1.au.unkin.net + name: http + port: 80 + protocol: HTTP + - allowedRoutes: + namespaces: + from: Same + hostname: prowlarr.k8s.syd1.au.unkin.net + name: https + port: 443 + protocol: HTTPS + tls: + certificateRefs: + - group: "" + kind: Secret + name: prowlarr-tls + mode: Terminate diff --git a/apps/base/arrstack/prowlarr/httproute.yaml b/apps/base/arrstack/prowlarr/httproute.yaml new file mode 100644 index 0000000..9e43c60 --- /dev/null +++ b/apps/base/arrstack/prowlarr/httproute.yaml @@ -0,0 +1,49 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: prowlarr-http-redirect + namespace: arrstack +spec: + hostnames: + - prowlarr.k8s.syd1.au.unkin.net + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: prowlarr + 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: prowlarr-route + namespace: arrstack +spec: + hostnames: + - prowlarr.k8s.syd1.au.unkin.net + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: prowlarr + sectionName: https + rules: + - backendRefs: + - group: "" + kind: Service + name: prowlarr + port: 9696 + weight: 1 + matches: + - path: + type: PathPrefix + value: / diff --git a/apps/base/arrstack/prowlarr/kustomization.yaml b/apps/base/arrstack/prowlarr/kustomization.yaml new file mode 100644 index 0000000..a7f5633 --- /dev/null +++ b/apps/base/arrstack/prowlarr/kustomization.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - pvc-config.yaml + - deployment.yaml + - service.yaml + - gateway.yaml + - httproute.yaml diff --git a/apps/base/arrstack/prowlarr/pvc-config.yaml b/apps/base/arrstack/prowlarr/pvc-config.yaml new file mode 100644 index 0000000..208bd7c --- /dev/null +++ b/apps/base/arrstack/prowlarr/pvc-config.yaml @@ -0,0 +1,16 @@ +--- +# Prowlarr config + SQLite DB. RWO on cephrbd (block) — the arr apps' SQLite +# does not tolerate CephFS locking. Retain: this is state. +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: prowlarr-config + namespace: arrstack +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + storageClassName: cephrbd-fast-retain + volumeMode: Filesystem diff --git a/apps/base/arrstack/prowlarr/service.yaml b/apps/base/arrstack/prowlarr/service.yaml new file mode 100644 index 0000000..96da114 --- /dev/null +++ b/apps/base/arrstack/prowlarr/service.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: prowlarr + namespace: arrstack +spec: + ports: + - name: http + port: 9696 + protocol: TCP + targetPort: http + selector: + app: prowlarr + type: ClusterIP diff --git a/apps/base/arrstack/pv-media-movies.yaml b/apps/base/arrstack/pv-media-movies.yaml new file mode 100644 index 0000000..a70696c --- /dev/null +++ b/apps/base/arrstack/pv-media-movies.yaml @@ -0,0 +1,31 @@ +--- +# Static PV for the shared MOVIES CephFS subvolume. Same rootPath as jellyfin's +# movies PV so radarr/nzbget write and jellyfin reads the identical library +# tree; each namespace gets its own PV (unique name + volumeHandle) pinned by +# claimRef. +apiVersion: v1 +kind: PersistentVolume +metadata: + name: arrstack-media-movies +spec: + capacity: + storage: 1Ti + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: "" + volumeMode: Filesystem + claimRef: + namespace: arrstack + name: media-movies + csi: + driver: cephfs.csi.ceph.com + volumeHandle: arrstack-media-movies-static + nodeStageSecretRef: + name: csi-cephfs-secret + namespace: csi-cephfs + volumeAttributes: + staticVolume: "true" + clusterID: cephfs_csi_ssd_ec_4_1 + fsName: cephfs + rootPath: /volumes/csi_ssd_ec_4_1/media-movies/e95d8ace-c736-465a-acc3-0c3e46dcede9 diff --git a/apps/base/arrstack/pv-media-tv.yaml b/apps/base/arrstack/pv-media-tv.yaml new file mode 100644 index 0000000..86fd07d --- /dev/null +++ b/apps/base/arrstack/pv-media-tv.yaml @@ -0,0 +1,30 @@ +--- +# Static PV for the shared TV CephFS subvolume. Same rootPath as jellyfin's TV +# PV so sonarr/nzbget write and jellyfin reads the identical library tree; each +# namespace gets its own PV (unique name + volumeHandle) pinned by claimRef. +apiVersion: v1 +kind: PersistentVolume +metadata: + name: arrstack-media-tv +spec: + capacity: + storage: 1Ti + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: "" + volumeMode: Filesystem + claimRef: + namespace: arrstack + name: media-tv + csi: + driver: cephfs.csi.ceph.com + volumeHandle: arrstack-media-tv-static + nodeStageSecretRef: + name: csi-cephfs-secret + namespace: csi-cephfs + volumeAttributes: + staticVolume: "true" + clusterID: cephfs_csi_ssd_ec_4_1 + fsName: cephfs + rootPath: /volumes/csi_ssd_ec_4_1/media-tv/4692957d-f5df-4f72-b9c9-56e4ee6d1333 diff --git a/apps/base/arrstack/pvc-media-movies.yaml b/apps/base/arrstack/pvc-media-movies.yaml new file mode 100644 index 0000000..1fc691a --- /dev/null +++ b/apps/base/arrstack/pvc-media-movies.yaml @@ -0,0 +1,22 @@ +--- +# Movies library + downloads, shared RWX across radarr and nzbget. Statically +# bound to the arrstack-media-movies PV (same CephFS subvolume jellyfin mounts +# read-only). storageClassName "" + volumeName disables dynamic provisioning and +# binds the pre-created static PV. Downloads and library live on one filesystem +# so import is an atomic hardlink move. +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: media-movies + namespace: arrstack + annotations: + k8up.io/backup: "false" +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Ti + storageClassName: "" + volumeName: arrstack-media-movies + volumeMode: Filesystem diff --git a/apps/base/arrstack/pvc-media-tv.yaml b/apps/base/arrstack/pvc-media-tv.yaml new file mode 100644 index 0000000..8680637 --- /dev/null +++ b/apps/base/arrstack/pvc-media-tv.yaml @@ -0,0 +1,22 @@ +--- +# TV library + downloads, shared RWX across sonarr and nzbget. Statically bound +# to the arrstack-media-tv PV (same CephFS subvolume jellyfin mounts read-only). +# storageClassName "" + volumeName disables dynamic provisioning and binds the +# pre-created static PV. Downloads and library live on one filesystem so import +# is an atomic hardlink move. +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: media-tv + namespace: arrstack + annotations: + k8up.io/backup: "false" +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Ti + storageClassName: "" + volumeName: arrstack-media-tv + volumeMode: Filesystem diff --git a/apps/base/arrstack/radarr/deployment.yaml b/apps/base/arrstack/radarr/deployment.yaml new file mode 100644 index 0000000..9de78e5 --- /dev/null +++ b/apps/base/arrstack/radarr/deployment.yaml @@ -0,0 +1,72 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: radarr + namespace: arrstack +spec: + replicas: 1 + strategy: + # RWO config PVC + single stateful SQLite DB: never run two pods at once. + type: Recreate + selector: + matchLabels: + app: radarr + template: + metadata: + labels: + app: radarr + spec: + securityContext: + fsGroup: 1000 + fsGroupChangePolicy: OnRootMismatch + containers: + - name: radarr + image: artifactapi.k8s.syd1.au.unkin.net/dockerhub/linuxserver/radarr:6.3.0 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 7878 + protocol: TCP + env: + - name: PUID + value: "1000" + - name: PGID + value: "1000" + - name: TZ + value: Australia/Sydney + livenessProbe: + httpGet: + path: /ping + port: http + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /ping + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: "1" + memory: 1Gi + volumeMounts: + - name: config + mountPath: /config + - name: media-movies + mountPath: /media/movies + volumes: + - name: config + persistentVolumeClaim: + claimName: radarr-config + - name: media-movies + persistentVolumeClaim: + claimName: media-movies diff --git a/apps/base/arrstack/radarr/gateway.yaml b/apps/base/arrstack/radarr/gateway.yaml new file mode 100644 index 0000000..2fb50ca --- /dev/null +++ b/apps/base/arrstack/radarr/gateway.yaml @@ -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: radarr.k8s.syd1.au.unkin.net + cert-manager.io/private-key-size: "4096" + external-dns.alpha.kubernetes.io/hostname: radarr.k8s.syd1.au.unkin.net + external-dns.alpha.kubernetes.io/target: 198.18.200.4 + name: radarr + namespace: arrstack +spec: + gatewayClassName: traefik-internal + listeners: + - allowedRoutes: + namespaces: + from: Same + hostname: radarr.k8s.syd1.au.unkin.net + name: http + port: 80 + protocol: HTTP + - allowedRoutes: + namespaces: + from: Same + hostname: radarr.k8s.syd1.au.unkin.net + name: https + port: 443 + protocol: HTTPS + tls: + certificateRefs: + - group: "" + kind: Secret + name: radarr-tls + mode: Terminate diff --git a/apps/base/arrstack/radarr/httproute.yaml b/apps/base/arrstack/radarr/httproute.yaml new file mode 100644 index 0000000..002cc5b --- /dev/null +++ b/apps/base/arrstack/radarr/httproute.yaml @@ -0,0 +1,49 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: radarr-http-redirect + namespace: arrstack +spec: + hostnames: + - radarr.k8s.syd1.au.unkin.net + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: radarr + 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: radarr-route + namespace: arrstack +spec: + hostnames: + - radarr.k8s.syd1.au.unkin.net + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: radarr + sectionName: https + rules: + - backendRefs: + - group: "" + kind: Service + name: radarr + port: 7878 + weight: 1 + matches: + - path: + type: PathPrefix + value: / diff --git a/apps/base/arrstack/radarr/kustomization.yaml b/apps/base/arrstack/radarr/kustomization.yaml new file mode 100644 index 0000000..a7f5633 --- /dev/null +++ b/apps/base/arrstack/radarr/kustomization.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - pvc-config.yaml + - deployment.yaml + - service.yaml + - gateway.yaml + - httproute.yaml diff --git a/apps/base/arrstack/radarr/pvc-config.yaml b/apps/base/arrstack/radarr/pvc-config.yaml new file mode 100644 index 0000000..25fadad --- /dev/null +++ b/apps/base/arrstack/radarr/pvc-config.yaml @@ -0,0 +1,16 @@ +--- +# Radarr config + SQLite DB. RWO on cephrbd (block) — the arr apps' SQLite +# does not tolerate CephFS locking. Retain: this is state. +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: radarr-config + namespace: arrstack +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + storageClassName: cephrbd-fast-retain + volumeMode: Filesystem diff --git a/apps/base/arrstack/radarr/service.yaml b/apps/base/arrstack/radarr/service.yaml new file mode 100644 index 0000000..08d154f --- /dev/null +++ b/apps/base/arrstack/radarr/service.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: radarr + namespace: arrstack +spec: + ports: + - name: http + port: 7878 + protocol: TCP + targetPort: http + selector: + app: radarr + type: ClusterIP diff --git a/apps/base/arrstack/sonarr/deployment.yaml b/apps/base/arrstack/sonarr/deployment.yaml new file mode 100644 index 0000000..5352c71 --- /dev/null +++ b/apps/base/arrstack/sonarr/deployment.yaml @@ -0,0 +1,75 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sonarr + namespace: arrstack +spec: + replicas: 1 + strategy: + # RWO config PVC + single stateful SQLite DB: never run two pods at once. + type: Recreate + selector: + matchLabels: + app: sonarr + template: + metadata: + labels: + app: sonarr + spec: + securityContext: + # LinuxServer images init as root via s6 then step down to PUID/PGID. + # fsGroup makes the shared CephFS group-writable for that user; + # OnRootMismatch avoids a recursive chown of the whole media tree. + fsGroup: 1000 + fsGroupChangePolicy: OnRootMismatch + containers: + - name: sonarr + image: artifactapi.k8s.syd1.au.unkin.net/dockerhub/linuxserver/sonarr:4.0.19 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 8989 + protocol: TCP + env: + - name: PUID + value: "1000" + - name: PGID + value: "1000" + - name: TZ + value: Australia/Sydney + livenessProbe: + httpGet: + path: /ping + port: http + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /ping + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: "1" + memory: 1Gi + volumeMounts: + - name: config + mountPath: /config + - name: media-tv + mountPath: /media/tv + volumes: + - name: config + persistentVolumeClaim: + claimName: sonarr-config + - name: media-tv + persistentVolumeClaim: + claimName: media-tv diff --git a/apps/base/arrstack/sonarr/gateway.yaml b/apps/base/arrstack/sonarr/gateway.yaml new file mode 100644 index 0000000..38ff6b3 --- /dev/null +++ b/apps/base/arrstack/sonarr/gateway.yaml @@ -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: sonarr.k8s.syd1.au.unkin.net + cert-manager.io/private-key-size: "4096" + external-dns.alpha.kubernetes.io/hostname: sonarr.k8s.syd1.au.unkin.net + external-dns.alpha.kubernetes.io/target: 198.18.200.4 + name: sonarr + namespace: arrstack +spec: + gatewayClassName: traefik-internal + listeners: + - allowedRoutes: + namespaces: + from: Same + hostname: sonarr.k8s.syd1.au.unkin.net + name: http + port: 80 + protocol: HTTP + - allowedRoutes: + namespaces: + from: Same + hostname: sonarr.k8s.syd1.au.unkin.net + name: https + port: 443 + protocol: HTTPS + tls: + certificateRefs: + - group: "" + kind: Secret + name: sonarr-tls + mode: Terminate diff --git a/apps/base/arrstack/sonarr/httproute.yaml b/apps/base/arrstack/sonarr/httproute.yaml new file mode 100644 index 0000000..5e11c73 --- /dev/null +++ b/apps/base/arrstack/sonarr/httproute.yaml @@ -0,0 +1,49 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: sonarr-http-redirect + namespace: arrstack +spec: + hostnames: + - sonarr.k8s.syd1.au.unkin.net + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: sonarr + 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: sonarr-route + namespace: arrstack +spec: + hostnames: + - sonarr.k8s.syd1.au.unkin.net + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: sonarr + sectionName: https + rules: + - backendRefs: + - group: "" + kind: Service + name: sonarr + port: 8989 + weight: 1 + matches: + - path: + type: PathPrefix + value: / diff --git a/apps/base/arrstack/sonarr/kustomization.yaml b/apps/base/arrstack/sonarr/kustomization.yaml new file mode 100644 index 0000000..a7f5633 --- /dev/null +++ b/apps/base/arrstack/sonarr/kustomization.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - pvc-config.yaml + - deployment.yaml + - service.yaml + - gateway.yaml + - httproute.yaml diff --git a/apps/base/arrstack/sonarr/pvc-config.yaml b/apps/base/arrstack/sonarr/pvc-config.yaml new file mode 100644 index 0000000..8bc132c --- /dev/null +++ b/apps/base/arrstack/sonarr/pvc-config.yaml @@ -0,0 +1,16 @@ +--- +# Sonarr config + SQLite DB. RWO on cephrbd (block) — the arr apps' SQLite +# does not tolerate CephFS locking. Retain: this is state. +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: sonarr-config + namespace: arrstack +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + storageClassName: cephrbd-fast-retain + volumeMode: Filesystem diff --git a/apps/base/arrstack/sonarr/service.yaml b/apps/base/arrstack/sonarr/service.yaml new file mode 100644 index 0000000..3ee669d --- /dev/null +++ b/apps/base/arrstack/sonarr/service.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: sonarr + namespace: arrstack +spec: + ports: + - name: http + port: 8989 + protocol: TCP + targetPort: http + selector: + app: sonarr + type: ClusterIP diff --git a/apps/overlays/au-syd1/arrstack/kustomization.yaml b/apps/overlays/au-syd1/arrstack/kustomization.yaml new file mode 100644 index 0000000..bb227cc --- /dev/null +++ b/apps/overlays/au-syd1/arrstack/kustomization.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../../base/arrstack diff --git a/argocd/applicationsets/media.yaml b/argocd/applicationsets/media.yaml index 9fe99a5..e5c1037 100644 --- a/argocd/applicationsets/media.yaml +++ b/argocd/applicationsets/media.yaml @@ -11,6 +11,7 @@ spec: revision: HEAD directories: - path: apps/overlays/*/jellyfin + - path: apps/overlays/*/arrstack template: metadata: name: 'media-{{path[3]}}'