From fdbcde49f621ff745d6b5c6f53a5459b5cf56152 Mon Sep 17 00:00:00 2001 From: Laur IVAN Date: Thu, 26 Feb 2026 13:00:52 +0100 Subject: [PATCH] feat: Add Navidrome application and VolSync components, migrating media apps from beta. --- kubernetes/{beta => apps}/media/README.md | 0 kubernetes/apps/media/kustomization.yaml | 13 +++ kubernetes/apps/media/namespace.yaml | 0 kubernetes/apps/media/navidrome/app.ks.yaml | 31 ++++++ .../media/navidrome/app/helm-release.yaml | 94 +++++++++++++++++++ .../media/navidrome/app/kustomization.yaml | 7 ++ .../media/navidrome/app/oci-repository.yaml | 15 +++ .../apps/media/navidrome/kustomization.yaml | 0 .../components/volsync/external-secret.yaml | 22 +++++ .../components/volsync/kustomization.yaml | 10 ++ kubernetes/components/volsync/pvc.yaml | 20 ++++ .../volsync/replica-destination.yaml | 28 ++++++ .../components/volsync/replica-source.yaml | 31 ++++++ .../components/volsync/secrets.sops.yaml | 25 +++++ 14 files changed, 296 insertions(+) rename kubernetes/{beta => apps}/media/README.md (100%) create mode 100644 kubernetes/apps/media/kustomization.yaml create mode 100644 kubernetes/apps/media/namespace.yaml create mode 100644 kubernetes/apps/media/navidrome/app.ks.yaml create mode 100644 kubernetes/apps/media/navidrome/app/helm-release.yaml create mode 100644 kubernetes/apps/media/navidrome/app/kustomization.yaml create mode 100644 kubernetes/apps/media/navidrome/app/oci-repository.yaml create mode 100644 kubernetes/apps/media/navidrome/kustomization.yaml create mode 100644 kubernetes/components/volsync/external-secret.yaml create mode 100644 kubernetes/components/volsync/kustomization.yaml create mode 100644 kubernetes/components/volsync/pvc.yaml create mode 100644 kubernetes/components/volsync/replica-destination.yaml create mode 100644 kubernetes/components/volsync/replica-source.yaml create mode 100644 kubernetes/components/volsync/secrets.sops.yaml diff --git a/kubernetes/beta/media/README.md b/kubernetes/apps/media/README.md similarity index 100% rename from kubernetes/beta/media/README.md rename to kubernetes/apps/media/README.md diff --git a/kubernetes/apps/media/kustomization.yaml b/kubernetes/apps/media/kustomization.yaml new file mode 100644 index 0000000..900688e --- /dev/null +++ b/kubernetes/apps/media/kustomization.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: media + +resources: + - ./namespace.yaml + #- ./booklore + #- ./feishin + #- ./immich + #- ./plex + #- ./karakeep + - ./navidrome diff --git a/kubernetes/apps/media/namespace.yaml b/kubernetes/apps/media/namespace.yaml new file mode 100644 index 0000000..e69de29 diff --git a/kubernetes/apps/media/navidrome/app.ks.yaml b/kubernetes/apps/media/navidrome/app.ks.yaml new file mode 100644 index 0000000..9c02611 --- /dev/null +++ b/kubernetes/apps/media/navidrome/app.ks.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app navidrome +spec: + interval: 10m + prune: true + wait: true + timeout: 15m + targetNamespace: media + + path: ./apps/media/navidrome/app + components: + - ../../../../components/volsync/ + sourceRef: + kind: GitRepository + name: flux-system + namespace: flux-system + + postBuild: + substitute: + APP: *app + VOLSYNC_CAPACITY: 1Gi + VOLSYNC_PUID: "568" + VOLSYNC_PGID: "568" + + decryption: { provider: sops } + dependsOn: + - name: volsync + namespace: storage-system diff --git a/kubernetes/apps/media/navidrome/app/helm-release.yaml b/kubernetes/apps/media/navidrome/app/helm-release.yaml new file mode 100644 index 0000000..807ec65 --- /dev/null +++ b/kubernetes/apps/media/navidrome/app/helm-release.yaml @@ -0,0 +1,94 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/bjw-s-labs/helm-charts/main/charts/other/app-template/schemas/helmrelease-helm-v2.schema.json +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: navidrome +spec: + interval: 10m + chartRef: + kind: OCIRepository + name: navidrome + + values: + controllers: + navidrome: + containers: + navidrome: + image: + repository: ghcr.io/navidrome/navidrome + tag: 0.60.3 + env: + ND_DATAFOLDER: /data + ND_ENABLEDOWNLOADS: "true" + ND_ENABLEEXTERNALSERVICES: "false" + ND_ENABLESHARING: "false" + ND_ENABLESTARRATING: "false" + ND_LOGLEVEL: info + ND_MUSICFOLDER: /music + ND_PORT: &port 4533 + ND_SCANSCHEDULE: 30m + # ND_BASEURL: https://navidrome.home.mirceanton.com + + resources: + requests: + cpu: 20m + memory: 512Mi + limits: + memory: 2Gi + + probes: + liveness: + enabled: true + readiness: + enabled: true + startup: + enabled: true + spec: + failureThreshold: 30 + periodSeconds: 5 + + securityContext: + allowPrivilegeEscalation: false + # readOnlyRootFilesystem: true + capabilities: { drop: ["ALL"] } + + defaultPodOptions: + enableServiceLinks: false + securityContext: + runAsNonRoot: true + runAsUser: 568 + runAsGroup: 568 + fsGroup: 568 + fsGroupChangePolicy: "OnRootMismatch" + + service: + app: + ports: + http: + port: *port + + route: + home: + hostnames: ["music.laurivan.com"] + parentRefs: + - name: envoy-internal + namespace: network-system + # tailscale: {} + + persistence: + config: + existingClaim: navidrome + globalMounts: + - path: /data + music: + type: nfs + server: 10.0.0.14 + path: /mnt/Main/shares/audio + globalMounts: + - path: /music + readOnly: true + cache: + type: emptyDir + globalMounts: + - path: /config/cache diff --git a/kubernetes/apps/media/navidrome/app/kustomization.yaml b/kubernetes/apps/media/navidrome/app/kustomization.yaml new file mode 100644 index 0000000..f1d57c6 --- /dev/null +++ b/kubernetes/apps/media/navidrome/app/kustomization.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ./helm-release.yaml + - ./oci-repository.yaml diff --git a/kubernetes/apps/media/navidrome/app/oci-repository.yaml b/kubernetes/apps/media/navidrome/app/oci-repository.yaml new file mode 100644 index 0000000..24e8804 --- /dev/null +++ b/kubernetes/apps/media/navidrome/app/oci-repository.yaml @@ -0,0 +1,15 @@ +--- +# yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/source.toolkit.fluxcd.io/ocirepository_v1.json +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: navidrome +spec: + interval: 15m + url: oci://ghcr.io/bjw-s-labs/helm/app-template + ref: + tag: 4.6.2 + + layerSelector: + mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip + operation: copy diff --git a/kubernetes/apps/media/navidrome/kustomization.yaml b/kubernetes/apps/media/navidrome/kustomization.yaml new file mode 100644 index 0000000..e69de29 diff --git a/kubernetes/components/volsync/external-secret.yaml b/kubernetes/components/volsync/external-secret.yaml new file mode 100644 index 0000000..79b2854 --- /dev/null +++ b/kubernetes/components/volsync/external-secret.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: "${APP}-volsync" +spec: + secretStoreRef: + kind: SecretStore + name: volsync-local + + target: + name: "${APP}-volsync-secret" + template: + engineVersion: v2 + data: + RESTIC_REPOSITORY: "{{ .RESTIC_REPOSITORY }}" + RESTIC_PASSWORD: "{{ .RESTIC_PASSWORD }}" + AWS_ACCESS_KEY_ID: "{{ .AWS_ACCESS_KEY_ID }}" + AWS_SECRET_ACCESS_KEY: "{{ .AWS_SECRET_ACCESS_KEY }}" + dataFrom: + - extract: + key: volsync-sops-secret diff --git a/kubernetes/components/volsync/kustomization.yaml b/kubernetes/components/volsync/kustomization.yaml new file mode 100644 index 0000000..9b74534 --- /dev/null +++ b/kubernetes/components/volsync/kustomization.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +resources: + - external-secret.yaml + - replication-source.yaml + - replication-destination.yaml + - pvc.yaml + - secret.sops.yaml diff --git a/kubernetes/components/volsync/pvc.yaml b/kubernetes/components/volsync/pvc.yaml new file mode 100644 index 0000000..3fa6b50 --- /dev/null +++ b/kubernetes/components/volsync/pvc.yaml @@ -0,0 +1,20 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.30.0/persistentvolumeclaim-v1.json +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: "${VOLSYNC_PVC:=${APP}}" + annotations: + kustomize.toolkit.fluxcd.io/prune: disabled +spec: + storageClassName: "${VOLSYNC_STORAGECLASS:=openebs-zfs}" + accessModes: ["${VOLSYNC_ACCESSMODES:=ReadWriteOnce}"] + + dataSourceRef: + kind: ReplicationDestination + apiGroup: volsync.backube + name: "${APP}-dst" + + resources: + requests: + storage: "${VOLSYNC_CAPACITY:=5Gi}" diff --git a/kubernetes/components/volsync/replica-destination.yaml b/kubernetes/components/volsync/replica-destination.yaml new file mode 100644 index 0000000..3d6bcec --- /dev/null +++ b/kubernetes/components/volsync/replica-destination.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: volsync.backube/v1alpha1 +kind: ReplicationDestination +metadata: + name: "${APP}-dst" + labels: + kustomize.toolkit.fluxcd.io/ssa: IfNotPresent +spec: + trigger: + manual: restore-once + restic: + repository: "${APP}-volsync-secret" + copyMethod: Snapshot + volumeSnapshotClassName: "${VOLSYNC_SNAPSHOTCLASS:=openebs-snapshots}" + cacheStorageClassName: "${VOLSYNC_CACHE_SNAPSHOTCLASS:=openebs-zfs}" + cacheAccessModes: ["${VOLSYNC_CACHE_ACCESSMODES:=ReadWriteOnce}"] + cacheCapacity: "${VOLSYNC_CACHE_CAPACITY:=5Gi}" + storageClassName: "${VOLSYNC_STORAGECLASS:=openebs-zfs}" + accessModes: ["${VOLSYNC_ACCESSMODES:=ReadWriteOnce}"] + capacity: "${VOLSYNC_CAPACITY:=5Gi}" + moverSecurityContext: + runAsUser: ${VOLSYNC_PUID:=1000} + runAsGroup: ${VOLSYNC_PGID:=1000} + fsGroup: ${VOLSYNC_PGID:=1000} + fsGroupChangePolicy: "OnRootMismatch" + enableFileDeletion: true + cleanupCachePVC: true + cleanupTempPVC: true diff --git a/kubernetes/components/volsync/replica-source.yaml b/kubernetes/components/volsync/replica-source.yaml new file mode 100644 index 0000000..5506996 --- /dev/null +++ b/kubernetes/components/volsync/replica-source.yaml @@ -0,0 +1,31 @@ +--- +# yaml-language-server: $schema=https://k8s-schemas.m00nlit.dev/volsync.backube/replicationsource_v1alpha1.json +apiVersion: volsync.backube/v1alpha1 +kind: ReplicationSource +metadata: + name: "${APP}" +spec: + sourcePVC: "${VOLSYNC_PVC:=${APP}}" + trigger: + schedule: "0 */2 * * *" + + restic: + copyMethod: "${VOLSYNC_COPYMETHOD:=Snapshot}" + pruneIntervalDays: 14 + repository: "${APP}-volsync-secret" + volumeSnapshotClassName: "${VOLSYNC_SNAPSHOTCLASS:=openebs-snapshots}" + cacheCapacity: "${VOLSYNC_CACHE_CAPACITY:=5Gi}" + cacheStorageClassName: "${VOLSYNC_CACHE_SNAPSHOTCLASS:=openebs-zfs}" + cacheAccessModes: ["${VOLSYNC_CACHE_ACCESSMODES:=ReadWriteOnce}"] + storageClassName: "${VOLSYNC_STORAGECLASS:=openebs-zfs}" + accessModes: ["${VOLSYNC_SNAP_ACCESSMODES:=ReadWriteOnce}"] + + moverSecurityContext: + runAsUser: ${VOLSYNC_PUID:=1000} + runAsGroup: ${VOLSYNC_PGID:=1000} + fsGroup: ${VOLSYNC_PGID:=1000} + fsGroupChangePolicy: "OnRootMismatch" + + retain: + hourly: 24 + daily: 7 diff --git a/kubernetes/components/volsync/secrets.sops.yaml b/kubernetes/components/volsync/secrets.sops.yaml new file mode 100644 index 0000000..e50cf30 --- /dev/null +++ b/kubernetes/components/volsync/secrets.sops.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: Secret +metadata: + name: volsync-sops-secret +stringData: + RESTIC_REPOSITORY: ENC[AES256_GCM,data:IKeoMJMhqvBW9M0Et8st1DrcrkQuw9VH/Mdmz9OGorm7ECPIxKQseQ6J6IkW9LqOt9kXjNFbiA==,iv:DnSDCC82nlmoH5SliGbdbAZRcUyYpgWKfS2BhTXIy/0=,tag:jAd08ZsqUDFt/cd2H79QsA==,type:str] + RESTIC_PASSWORD: ENC[AES256_GCM,data:DYOgxKL/isykzUPQeroucni999HArY8kp/2l6Fq2RLuI8LJRqTd0q/6qCeEe1G0=,iv:VPc1BW8q8yMnjOpL9ys0TloxeE12YL4IK0QdUhXyP8w=,tag:MlYI3TIDlSIOtgaP6k0myg==,type:str] + AWS_ACCESS_KEY_ID: ENC[AES256_GCM,data:DQ4XP510iouZxHk1quzldRyxQnxerhSBj2M=,iv:HQIIDDLhgMUd/FVk9GLE+Mr7NzPVlLbng1uV8zc/ZHE=,tag:1bNVkRplUbQ0cu+tA7GakA==,type:str] + AWS_SECRET_ACCESS_KEY: ENC[AES256_GCM,data:RB2Du0GVSyoBXIrLbc5Lqa5XJPRdgyLDp0LaOR1KiX8Fsu6Y18mZVrHuGJSMyUkvtkiw1r6WM7i6krnwJHmZmA==,iv:5SoDONSzqq3er07sJUNH0xvZ07jdvl9zxj+BIoZD2D4=,tag:OqOe20odWaS7CFcFWmjxyw==,type:str] +sops: + age: + - recipient: age1yzrqhl9dk8ljswpmzsqme3enad5kxxhsptdvecy3lwlq0ms80gaqxrctst + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqRDRDT2JzZmF2RlcyREg5 + aEgyZ0QwNTJQK2JYbDBrNjRhT3BNSzdFZGlzCndQVloyK1RUU281S1Q2YnI4eXQv + RVoxa0UxOFNEVkZwQzB3ZUhTNHBMTWcKLS0tIGZLMTZ3YUs3d2FHWVBtczJzdzhp + dUtWdGJ0cjhjREI5YnVzVDk5VGJJS0kKpa+N5XC8a5/V/eUgqZoosxrio9CJMTYS + TzhILOHxY59zNtl4Jw7QtIy27jWki4+318WnQ2XGHO5yPUitc1yPuA== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-02-26T11:50:59Z" + mac: ENC[AES256_GCM,data:Mc8yc/04WdQZIDUXIosrA0s6fFw42OF+q+FUxkrWQhT37w2NfdMq8PIWUT4TwlbWthoHZRfTP/vLW6/p6fvKYrc+bjFGdwa4CHSXq5CdhqTZEt0VBA1XyjYh06k01Sf7JFK0X4YlolR6qrmyloibh6reW25Sq7xjU+HI/x1mmWA=,iv:FbUCwU8lfpPebBXFngVhqOO+cc/u8/CT+cC2qBN+h6I=,tag:O1uAYkFi+TmrWO/EwJtUbg==,type:str] + encrypted_regex: ^(data|stringData)$ + mac_only_encrypted: true + version: 3.11.0