diff --git a/docker/immich/docker-compose.yml b/docker/immich/docker-compose.yml deleted file mode 100644 index d3ca411..0000000 --- a/docker/immich/docker-compose.yml +++ /dev/null @@ -1,34 +0,0 @@ ---- - -name: immich - -services: - immich-server: - container_name: immich_server - image: ghcr.io/immich-app/immich-server:v3.1.0 - restart: unless-stopped - volumes: - - ${IMMICH_UPLOAD_LOCATION:-/mnt/nas1/shared/immich}:/data - environment: - TZ: ${IMMICH_TZ:-Europe/Madrid} - DB_HOSTNAME: ${IMMICH_DB_HOSTNAME} - DB_PORT: ${IMMICH_DB_PORT} - DB_USERNAME: ${IMMICH_DB_USERNAME} - DB_PASSWORD: ${IMMICH_DB_PASSWORD} - DB_DATABASE_NAME: ${IMMICH_DB_DATABASE_NAME} - REDIS_HOSTNAME: ${IMMICH_REDIS_HOSTNAME} - REDIS_PORT: ${IMMICH_REDIS_PORT} - REDIS_USERNAME: ${IMMICH_REDIS_USERNAME} - REDIS_PASSWORD: ${IMMICH_REDIS_PASSWORD} - REDIS_DBINDEX: ${IMMICH_REDIS_DBINDEX} - ports: - - '2283:2283' - - immich-machine-learning: - container_name: immich_machine_learning - image: ghcr.io/immich-app/immich-machine-learning:v3.1.0 - ports: - - "3003:3003" - volumes: - - ${IMMICH_ML_MODEL_CACHE_LOCATION:-/mnt/nas1/shared/immich-model-cache}:/cache - restart: unless-stopped diff --git a/docker/immich/sample.env b/docker/immich/sample.env deleted file mode 100644 index 421baa6..0000000 --- a/docker/immich/sample.env +++ /dev/null @@ -1,16 +0,0 @@ -IMMICH_UPLOAD_LOCATION=/mnt/nas1/shared/immich -IMMICH_TZ=Europe/Madrid - -IMMICH_DB_HOSTNAME=192.168.1.3 -IMMICH_DB_PORT=55432 -IMMICH_DB_USERNAME= -IMMICH_DB_PASSWORD= -IMMICH_DB_DATABASE_NAME= - -IMMICH_REDIS_HOSTNAME=192.168.1.3 -IMMICH_REDIS_PORT=300036 -IMMICH_REDIS_USERNAME= -IMMICH_REDIS_PASSWORD= -IMMICH_REDIS_DBINDEX= - -IMMICH_ML_MODEL_CACHE_LOCATION=/mnt/nas1/shared/immich-model-cache diff --git a/k8s/argo-apps/dcsi.yaml b/k8s/argo-apps/dcsi.yaml index 9c9e48d..9433de9 100644 --- a/k8s/argo-apps/dcsi.yaml +++ b/k8s/argo-apps/dcsi.yaml @@ -19,11 +19,11 @@ spec: node: driver: image: - tag: next + tag: "next@sha256:6b758d6faf96f0e96d4b8ac0e240fc290c7bace23c20d78a3eb93781652cb1f1" controller: driver: image: - tag: next + tag: "next@sha256:6b758d6faf96f0e96d4b8ac0e240fc290c7bace23c20d78a3eb93781652cb1f1" csiDriver: name: org.dcsi.nfs driver: diff --git a/k8s/argo-apps/immich.yaml b/k8s/argo-apps/immich.yaml new file mode 100644 index 0000000..3937981 --- /dev/null +++ b/k8s/argo-apps/immich.yaml @@ -0,0 +1,99 @@ +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: immich + namespace: argocd +spec: + destination: + name: '' + namespace: apps-roboces + server: https://kubernetes.default.svc + sources: + - repoURL: git.roboces.dev/catalin/fukuops + chart: immich + targetRevision: 0.2.1 + helm: + valuesObject: + replicaCount: 1 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsUser: 10000 + runAsGroup: 10000 + capabilities: + drop: + - all + service: + type: LoadBalancer + db: + hostname: "192.168.1.3" + port: 5432 + database_name: "immich" + redis: + hostname: "192.168.1.3" + port: 30036 + dbindex: "12" + general: + tz: "Europe/Madrid" + persistence: + library: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 500Gi + encodedVideo: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 10Gi + thumbs: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 10Gi + upload: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 10Gi + profile: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 1Gi + backups: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 10Gi + modelCache: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 10Gi + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: 1 + memory: 2Gi + ingress: + enabled: true + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + hosts: + - host: photos.roboces.dev + paths: + - path: / + pathType: Prefix + tls: [] + secret: + existingSecretName: "immich-secrets" + project: roboces + syncPolicy: + automated: + prune: true + selfHeal: true diff --git a/k8s/charts/immich/Chart.yaml b/k8s/charts/immich/Chart.yaml new file mode 100644 index 0000000..025a200 --- /dev/null +++ b/k8s/charts/immich/Chart.yaml @@ -0,0 +1,11 @@ +apiVersion: v2 +name: immich +description: A Helm chart for Immich photo and video backup solution +type: application +version: 0.1.0 +appVersion: v3.1.0 +keywords: + - immich + - photo + - video + - backup diff --git a/k8s/charts/immich/templates/_helpers.tpl b/k8s/charts/immich/templates/_helpers.tpl new file mode 100644 index 0000000..47d1f9c --- /dev/null +++ b/k8s/charts/immich/templates/_helpers.tpl @@ -0,0 +1,60 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "immich.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "immich.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "immich.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "immich.labels" -}} +helm.sh/chart: {{ include "immich.chart" . }} +{{ include "immich.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "immich.selectorLabels" -}} +app.kubernetes.io/name: {{ include "immich.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Service account name +*/}} +{{- define "immich.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "immich.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/k8s/charts/immich/templates/configmap.yaml b/k8s/charts/immich/templates/configmap.yaml new file mode 100644 index 0000000..c919f91 --- /dev/null +++ b/k8s/charts/immich/templates/configmap.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "immich.fullname" . }} + labels: + {{- include "immich.labels" . | nindent 4 }} +data: + TZ: {{ .Values.general.tz | quote }} + IMMICH_PORT: {{ .Values.service.port | quote }} + IMMICH_MACHINE_LEARNING_URL: {{ printf "http://%s-machine-learning:3003" (include "immich.fullname" .) | quote }} + {{- range $key, $value := .Values.db }} + {{- if and (ne $key "password") $value }} + {{ $keyName := printf "DB_%s" (upper $key) }} + {{ $keyName }}: {{ $value | quote }} + {{- end }} + {{- end }} + {{- range $key, $value := .Values.redis }} + {{- if and (ne $key "password") (ne $key "username") $value }} + {{ $keyName := printf "REDIS_%s" (upper $key) }} + {{ $keyName }}: {{ $value | quote }} + {{- end }} + {{- end }} diff --git a/k8s/charts/immich/templates/deployment.yaml b/k8s/charts/immich/templates/deployment.yaml new file mode 100644 index 0000000..ffbf537 --- /dev/null +++ b/k8s/charts/immich/templates/deployment.yaml @@ -0,0 +1,167 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "immich.fullname" . }}-server + labels: + {{- include "immich.labels" . | nindent 4 }} + app.kubernetes.io/component: server +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "immich.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: server + strategy: + rollingUpdate: + maxSurge: 50% + maxUnavailable: 50% + type: RollingUpdate + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "immich.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: server + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + initContainers: + - name: init-immich + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: IfNotPresent + command: + - sh + - -c + - | + touch /data/library/.immich + touch /data/encoded-video/.immich + touch /data/thumbs/.immich + touch /data/upload/.immich + touch /data/profile/.immich + touch /data/backups/.immich + chown -R 10000:10000 /data/library /data/encoded-video /data/thumbs /data/upload /data/profile /data/backups + volumeMounts: + - name: library + mountPath: /data/library + - name: encoded-video + mountPath: /data/encoded-video + - name: thumbs + mountPath: /data/thumbs + - name: upload + mountPath: /data/upload + - name: profile + mountPath: /data/profile + - name: backups + mountPath: /data/backups + containers: + - name: {{ .Chart.Name }}-server + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + envFrom: + {{- if .Values.secret.existingSecretName }} + - secretRef: + name: {{ .Values.secret.existingSecretName }} + {{- else if .Values.secret.enabled }} + - secretRef: + name: {{ include "immich.fullname" . }} + {{- end }} + - configMapRef: + name: {{ include "immich.fullname" . }} + env: + - name: IMMICH_PORT + value: {{ .Values.service.port | quote }} + - name: IMMICH_MACHINE_LEARNING_URL + value: {{ printf "http://%s-machine-learning:3003" (include "immich.fullname" .) | quote }} + {{- range $key, $value := .Values.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + ports: + - name: http + containerPort: 2283 + protocol: TCP + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: library + mountPath: /data/library + {{- if .Values.persistence.encodedVideo.enabled }} + - name: encoded-video + mountPath: /data/encoded-video + {{- end }} + {{- if .Values.persistence.thumbs.enabled }} + - name: thumbs + mountPath: /data/thumbs + {{- end }} + {{- if .Values.persistence.upload.enabled }} + - name: upload + mountPath: /data/upload + {{- end }} + {{- if .Values.persistence.profile.enabled }} + - name: profile + mountPath: /data/profile + {{- end }} + {{- if .Values.persistence.backups.enabled }} + - name: backups + mountPath: /data/backups + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + restartPolicy: Always + automountServiceAccountToken: false + volumes: + {{- if .Values.persistence.library.enabled }} + - name: library + persistentVolumeClaim: + claimName: {{ include "immich.fullname" . }}-library + {{- end }} + {{- if .Values.persistence.encodedVideo.enabled }} + - name: encoded-video + persistentVolumeClaim: + claimName: {{ include "immich.fullname" . }}-encoded-video + {{- end }} + {{- if .Values.persistence.thumbs.enabled }} + - name: thumbs + persistentVolumeClaim: + claimName: {{ include "immich.fullname" . }}-thumbs + {{- end }} + {{- if .Values.persistence.upload.enabled }} + - name: upload + persistentVolumeClaim: + claimName: {{ include "immich.fullname" . }}-upload + {{- end }} + {{- if .Values.persistence.profile.enabled }} + - name: profile + persistentVolumeClaim: + claimName: {{ include "immich.fullname" . }}-profile + {{- end }} + {{- if .Values.persistence.backups.enabled }} + - name: backups + persistentVolumeClaim: + claimName: {{ include "immich.fullname" . }}-backups + {{- end }} diff --git a/k8s/charts/immich/templates/hpa.yaml b/k8s/charts/immich/templates/hpa.yaml new file mode 100644 index 0000000..dea6988 --- /dev/null +++ b/k8s/charts/immich/templates/hpa.yaml @@ -0,0 +1,34 @@ +{{- if .Values.autoscaling.enabled }} +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "immich.fullname" . }}-server + labels: + {{- include "immich.labels" . | nindent 4 }} + app.kubernetes.io/component: server +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "immich.fullname" . }}-server + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/k8s/charts/immich/templates/ingress.yaml b/k8s/charts/immich/templates/ingress.yaml new file mode 100644 index 0000000..76cabab --- /dev/null +++ b/k8s/charts/immich/templates/ingress.yaml @@ -0,0 +1,45 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "immich.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "immich.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if .pathType }} + pathType: {{ .pathType }} + {{- end }} + backend: + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- end }} + {{- end }} +{{- end }} diff --git a/k8s/charts/immich/templates/pvc.yaml b/k8s/charts/immich/templates/pvc.yaml new file mode 100644 index 0000000..492c972 --- /dev/null +++ b/k8s/charts/immich/templates/pvc.yaml @@ -0,0 +1,126 @@ +{{- if .Values.persistence.library.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "immich.fullname" . }}-library + labels: + {{- include "immich.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.library.accessMode }} + {{- if .Values.persistence.library.storageClass }} + storageClassName: {{ .Values.persistence.library.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.library.size }} +{{- end }} +{{- if .Values.persistence.encodedVideo.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "immich.fullname" . }}-encoded-video + labels: + {{- include "immich.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.encodedVideo.accessMode }} + {{- if .Values.persistence.encodedVideo.storageClass }} + storageClassName: {{ .Values.persistence.encodedVideo.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.encodedVideo.size }} +{{- end }} +{{- if .Values.persistence.thumbs.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "immich.fullname" . }}-thumbs + labels: + {{- include "immich.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.thumbs.accessMode }} + {{- if .Values.persistence.thumbs.storageClass }} + storageClassName: {{ .Values.persistence.thumbs.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.thumbs.size }} +{{- end }} +{{- if .Values.persistence.upload.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "immich.fullname" . }}-upload + labels: + {{- include "immich.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.upload.accessMode }} + {{- if .Values.persistence.upload.storageClass }} + storageClassName: {{ .Values.persistence.upload.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.upload.size }} +{{- end }} +{{- if .Values.persistence.profile.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "immich.fullname" . }}-profile + labels: + {{- include "immich.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.profile.accessMode }} + {{- if .Values.persistence.profile.storageClass }} + storageClassName: {{ .Values.persistence.profile.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.profile.size }} +{{- end }} +{{- if .Values.persistence.backups.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "immich.fullname" . }}-backups + labels: + {{- include "immich.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.backups.accessMode }} + {{- if .Values.persistence.backups.storageClass }} + storageClassName: {{ .Values.persistence.backups.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.backups.size }} +{{- end }} +{{- if .Values.persistence.modelCache.enabled }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "immich.fullname" . }}-model-cache + labels: + {{- include "immich.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.modelCache.accessMode }} + {{- if .Values.persistence.modelCache.storageClass }} + storageClassName: {{ .Values.persistence.modelCache.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.modelCache.size }} +{{- end }} diff --git a/k8s/charts/immich/templates/secret.yaml b/k8s/charts/immich/templates/secret.yaml new file mode 100644 index 0000000..ffd0bd7 --- /dev/null +++ b/k8s/charts/immich/templates/secret.yaml @@ -0,0 +1,25 @@ +{{- if and .Values.secret.enabled (not .Values.secret.existingSecretName) -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "immich.fullname" . }} + labels: + {{- include "immich.labels" . | nindent 4 }} +type: Opaque +stringData: + {{- if .Values.db.password }} + DB_PASSWORD: {{ .Values.db.password | quote }} + {{- end }} + {{- if .Values.db.username }} + DB_USERNAME: {{ .Values.db.username | quote }} + {{- end }} + {{- if .Values.redis.password }} + REDIS_PASSWORD: {{ .Values.redis.password | quote }} + {{- end }} + {{- if .Values.redis.username }} + REDIS_USERNAME: {{ .Values.redis.username | quote }} + {{- end }} + {{- range $key, $value := .Values.secret.data }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} diff --git a/k8s/charts/immich/templates/service.yaml b/k8s/charts/immich/templates/service.yaml new file mode 100644 index 0000000..69c6657 --- /dev/null +++ b/k8s/charts/immich/templates/service.yaml @@ -0,0 +1,34 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "immich.fullname" . }} + labels: + {{- include "immich.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "immich.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: server +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "immich.fullname" . }}-machine-learning + labels: + {{- include "immich.labels" . | nindent 4 }} + app.kubernetes.io/component: machine-learning +spec: + type: ClusterIP + ports: + - port: 3003 + targetPort: http + protocol: TCP + name: http + selector: + {{- include "immich.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: machine-learning diff --git a/k8s/charts/immich/templates/statefulset.yaml b/k8s/charts/immich/templates/statefulset.yaml new file mode 100644 index 0000000..68087de --- /dev/null +++ b/k8s/charts/immich/templates/statefulset.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "immich.fullname" . }}-machine-learning + labels: + {{- include "immich.labels" . | nindent 4 }} + app.kubernetes.io/component: machine-learning +spec: + serviceName: {{ include "immich.fullname" . }}-machine-learning-headless + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "immich.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: machine-learning + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "immich.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: machine-learning + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }}-machine-learning + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.machineLearningImage.repository }}:{{ .Values.machineLearningImage.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.machineLearningImage.pullPolicy }} + ports: + - name: http + containerPort: 3003 + protocol: TCP + env: + - name: IMMICH_PORT + value: {{ .Values.service.port | quote }} + - name: IMMICH_MACHINE_LEARNING_PORT + value: "3003" + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: model-cache + mountPath: /cache + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + restartPolicy: Always + automountServiceAccountToken: false + volumes: + - name: model-cache + persistentVolumeClaim: + claimName: {{ include "immich.fullname" . }}-model-cache diff --git a/k8s/charts/immich/values.yaml b/k8s/charts/immich/values.yaml new file mode 100644 index 0000000..b0297b2 --- /dev/null +++ b/k8s/charts/immich/values.yaml @@ -0,0 +1,116 @@ +replicaCount: 1 + +image: + repository: ghcr.io/immich-app/immich-server + pullPolicy: Always + tag: "" + +machineLearningImage: + repository: ghcr.io/immich-app/immich-machine-learning + pullPolicy: Always + tag: "" + +imagePullSecrets: [] +podAnnotations: {} +podSecurityContext: {} +securityContext: {} + +service: + type: ClusterIP + port: 2283 + targetPort: 2283 + +ingress: + enabled: false + +db: + hostname: "192.168.1.3" + port: 55432 + username: "" + password: "" + database_name: "" + +redis: + hostname: "192.168.1.3" + port: 300036 + username: "" + password: "" + dbindex: "" + +general: + tz: "Europe/Madrid" + +persistence: + library: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 10Gi + encodedVideo: + enabled: true + storageClass: "" + accessMode: ReadWriteMany + size: 10Gi + thumbs: + enabled: true + storageClass: "" + accessMode: ReadWriteMany + size: 10Gi + upload: + enabled: true + storageClass: "" + accessMode: ReadWriteMany + size: 10Gi + profile: + enabled: true + storageClass: "" + accessMode: ReadWriteMany + size: 1Gi + backups: + enabled: true + storageClass: "" + accessMode: ReadWriteMany + size: 10Gi + modelCache: + enabled: true + storageClass: "" + accessMode: ReadWriteMany + size: 10Gi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + +resources: {} + +livenessProbe: + tcpSocket: + port: 2283 + initialDelaySeconds: 30 + timeoutSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 + +readinessProbe: + tcpSocket: + port: 2283 + initialDelaySeconds: 15 + timeoutSeconds: 2 + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 + +nodeSelector: {} +tolerations: [] +affinity: {} + +env: {} + +secret: + enabled: false + existingSecretName: "" + data: {} diff --git a/k8s/playground/nfstest/pod.yaml b/k8s/playground/nfstest/pod.yaml deleted file mode 100644 index 6cb0319..0000000 --- a/k8s/playground/nfstest/pod.yaml +++ /dev/null @@ -1,19 +0,0 @@ ---- -kind: Pod -apiVersion: v1 -metadata: - name: pod-using-nfs - namespace: apps-fuku -spec: - containers: - - name: app - image: alpine - volumeMounts: - - name: data - mountPath: /var/nfs - command: ["/bin/sh"] - args: ["-c", "sleep 500000"] - volumes: - - name: data - persistentVolumeClaim: - claimName: myapp-nfs diff --git a/k8s/playground/nfstest/pvc.yaml b/k8s/playground/nfstest/pvc.yaml deleted file mode 100644 index 723a948..0000000 --- a/k8s/playground/nfstest/pvc.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: myapp-nfs - namespace: apps-fuku -spec: - accessModes: - - ReadWriteMany - storageClassName: "" - volumeName: nas1 - resources: - requests: - storage: 5Gi diff --git a/k8s/playground/nfstest/pvwithnfs.yaml b/k8s/playground/nfstest/pvwithnfs.yaml deleted file mode 100644 index 994fd5c..0000000 --- a/k8s/playground/nfstest/pvwithnfs.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: v1 -kind: PersistentVolume -metadata: - name: nas1 - namespace: apps-fuku -spec: - capacity: - storage: 5Gi - accessModes: - - ReadWriteMany - persistentVolumeReclaimPolicy: Retain - nfs: - server: zeruel.fuku - path: /mnt/pool1/nas1 diff --git a/k8s/services/argo/project-roboces.yaml b/k8s/services/argo/project-roboces.yaml index b4b1ed0..57f8cfc 100644 --- a/k8s/services/argo/project-roboces.yaml +++ b/k8s/services/argo/project-roboces.yaml @@ -10,6 +10,9 @@ spec: server: https://kubernetes.default.svc - namespace: woodpecker server: https://kubernetes.default.svc + clusterResourceWhitelist: + - group: "" + kind: PersistentVolume sourceRepos: - https://git.roboces.dev/catalin/fukuops.git - code.forgejo.org/forgejo-helm diff --git a/k8s/services/immich/sealedsecrets.yaml b/k8s/services/immich/sealedsecrets.yaml new file mode 100644 index 0000000..2e48dfb --- /dev/null +++ b/k8s/services/immich/sealedsecrets.yaml @@ -0,0 +1,18 @@ +# yamllint disable rule:line-length +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + name: immich-secrets + namespace: apps-roboces +spec: + encryptedData: + DB_PASSWORD: AgAvMTMxo1wZeNfygjwqhoQKV3nkJ87UfAJYAkm5WZrbOk4VOGnyLIP27B2RDm8qVe2ZqABMUxrgGQ7XIgK9qUKXZqP1eK5muap9LIiXTTorPXXATAAVnHGOFRCZG/h+a7rpzJQ/OPrnguXMYx85PU0h5nqhJvk+peXJfT14knpG3gCDik3fvxrOhoGw3zjp/c3+YeQtIQczj6wHKeWV34BHFq4lha+RWVFPh1hc4Ivnya+JE3bzp2Ott5FA/lK6mWzKDjMu9R8Z9p7Ai+vxYZbc8F99MXc4xwe13a42Y9oOrhs4gffFHyIcL2js0lSIa4XqvU6lE/QLfy7XH0eQTi48o56nntH3xrAoWuk8ETydz8Az4E5kHDl2rOSuAUD7XMUTMe4q2/gz7Ney4b6HgqLZx+jU4XjkPDT3jyewKc4KXur5B4/d1PsalbWWtFicDrGRo/BxYkrfSpN81GPWMcNTGD0YgnX9jUlFW56hlSJpD82pL2I2moVSe0ecWJ9MNQionZHrxUtZqq3mtpqD9/IlirDDXW/LIZlT+dzjWzZja+5VxTSgojjpmjVZErMxypKU9WPvXxczoRJqsqh93yVKnJxcV3zsboUN8zX/tj2tGWyLohnlFH59Gfy5aO6jB6kypW9tvszEvx+uk6cUxn2ZAv73Quh5HbNjUgW0ooUSM2ADrE7lTuj00NUSdUkzIgY/immXaLyDHEm8A9qTb1vHlxzZYw== + DB_USERNAME: AgCIpHBEDcHoQC2FeHlR7hE5FPbdVTi43X6X/9tj5WDzkteawdamAfS6Y1kznNzDUEnADi3H3WfGyoR0MlWFvFhLpdKD2ggLBUP1yltejvIpxs/9J9VSVU4lq+3/FtoZlYmmuF5sTKtGwMmVxxwOJuRuet8O70A7BjFM2r6REE1j9jPim3Ckg5srSpI5cz0P/8gPXclWmij5rpem2HQGNHmn/4FeYs89m9LOHLh6cDEG/owEh9pcUpyqVF6BpkBggaGxI/t2I009U6MOKvhtkpPSzK0yI+qzVg4d1Q14r93EFxckPKmt6xcdrNXmxHIl3apx0LuvWTx8QqtDqV7zngXJGbui451OlbKz7LKcuyvv4TYLnFMYsspCN9QT0AR+/pkPWwj+2exYboeQlB4GEcU3U2jqo9qkIMX9UUFp7vG8JVBnLRsbXgGO/dZONLcOuy2eObsKQtyaW74F9gLQJazCbMGgIeMJo3e+DvksydqoA6wTVqgDW0EvBWdQUa/V8pmPFLkeyOpdhjpBC6L0oXmPtMJKkr0X3rNnDqIzfL9pB2gHzIacDKzFWiZktCLNcd1rd80llLeklckOkmj7Gl8RIF3L0DsPbXvFekoaOkqcji5whowdc3yA2nY0lFZ8LQPmGmEqlLo+VysVm3yVU6xxqQi8L7HY5UgFO70Hpe+CPcSOblrki6nzHzAlsRspdpDP294FUMs= + REDIS_PASSWORD: AgCXVS6PinlpI8PY4JTTMXbUOloBaKh62DGze0xO3YSTEDTeEBsjHkYpaD48EVYqGoEp9mXvJvqFEm73LMQZ/kGVqmPNVb1u2e0ZnxB4rgWC2QINVHMaduWPnqbRnkKSMYQohkuXCo/q7A7jQQdbqE4P/v6hLdueZk5MXsNvr/Zak/1/Eyn3Flk50WZjN9pcoGdooGQbZ+Ci0HZCRVHLmZnamw/VI6bGQ3jnvRgOqbJYp8vA5e7kx9yu85damzz7S4i8IDJ+RYJhqfP6jVH30MyKdEpazeIbWlV5MrpE50LchBGo8B1H18dTfgXcE/IKDKrv5oHIs9pOryNV5FL8XTwjFEjfzBhCTWpra+XZw6PNI2IPqhiC3xLlkjzwin94C2D5y7KwGMYBS9ANHi0OxjcZFtMhYD81wsk1kQSalupDnd6XdcEBDNTW8bUGjSJYE8+uB7LdfObVShER1QbySTqt8Ygp1paiIIB1sf7prQZWtAVK6UQhCr7h/PXqJOUoz/aUe/CCr/XgDIrydLeKhcLzXwx0ldWCxkBAIMPLwURDOIAw/S0XneM33EwJuuX8LzThwa/4Evus5QqUpMbzLYa2kwauv8LH/j+xWQ6Td2fAjI89VMWGTBMCUQvY0OuL+wGVYPtJzolkX6ee5C1LYqsZGI5gQ+89EyRozSUXoQyXVyayS0PoSsBYsMezhSTCmQ3R1CJNGiB6tCw1UcgqttZtKoiDGg== + REDIS_USERNAME: AgCzG1j8EUXbATse66Pf0dh91xAkHYMby6LRps/60nzpCQY7ecYBhBsgG+cS7se939MrUP71qcTXI+BfwojAeFTp0ln8TvoAYNoikQIyhOqRUoz/MdnERdxsZH/in7h/TjkyEOtyNo9W8cmUzECz1iQ+EDA5CCB+8+2hiGnUe6PZawbEgd4KVkd8VvAw9tnfKb3W+K/qx/8uDBKyd32ahhzztYNMhGDMbSVFFf6+79ckmROrLDwSLXoo+QuNv1eRronduKUeROiixcHo/ZPJ/Cd28IA/xTZkSjr5RWvz5Ic39Ax0QaDC43dnGSSXLow5f4VQeYDRS9QRZ+jLeyF+b2AuHCZJ6Aa3YIh0tL26/RErI6ujWqgCGZkdQ+dbeSmGH0GI4ugHsnM0iKeD5BxJOoMWstKvGMjNncx2mbwO1U7uDYRnUs5oVHNeYDH5jeWcIgh4ljC7WdsHdK/Ytf0P2DChKf68Isrj5+mggzqPdg6XGZkjM+ineM8LoLNpGb35+eCEUagVzTu0xQ4TCGjKO0Yy+rPjQRlQTVtKfKA+zrya/sNhUH6LCMOrEkkO769sxor5XJb8UpLZ9TnwnqJ63rsvqoQ7DEK83kuAHzF0tT66Q6NpE3xECo8GqAkv0GbLL0UZrTz1qiuUTecdMK5eMSENpfXk2sCG/vW4vl3Eb8rHrzwPXsGUI7vcBtAT1PNVZZU= + template: + metadata: + name: immich-secrets + namespace: apps-roboces + type: Opaque diff --git a/scripts/k8s-ports.sh b/scripts/k8s-ports.sh new file mode 100755 index 0000000..534d980 --- /dev/null +++ b/scripts/k8s-ports.sh @@ -0,0 +1,212 @@ +#!/usr/bin/env bash +set -uo pipefail + +DEFAULT_MIN_PORT=8000 +DEFAULT_MAX_PORT=64000 + +usage() { + cat < 8000 + $(basename "$0") next --from 8200 --to 8300 # Find next available port in range + $(basename "$0") check 8008 # Check if port 8008 is available + $(basename "$0") check 9000 --from 9000 # Check if port 9000 is available in range + $(basename "$0") list # List all ports in use +EOF +} + +VERBOSE=0 +COMMAND="" +MIN_PORT=$DEFAULT_MIN_PORT +MAX_PORT=$DEFAULT_MAX_PORT +CHECK_PORT="" + +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) + usage + exit 0 + ;; + -v) + VERBOSE=1 + shift + ;; + --from) + MIN_PORT="$2" + shift 2 + ;; + --to) + MAX_PORT="$2" + shift 2 + ;; + next|check|list) + COMMAND="$1" + shift + ;; + -*) + echo "Error: Unknown option: $1" >&2 + usage >&2 + exit 1 + ;; + *) + if [[ "$1" =~ ^[0-9]+$ ]]; then + if [[ "$COMMAND" == "check" ]] || [[ -z "$COMMAND" ]]; then + CHECK_PORT="$1" + COMMAND="check" + else + echo "Error: Unexpected argument: $1" >&2 + usage >&2 + exit 1 + fi + else + echo "Error: Unexpected argument: $1" >&2 + usage >&2 + exit 1 + fi + shift + ;; + esac +done + +if [[ -z "$COMMAND" ]]; then + echo "Error: No command specified" >&2 + usage >&2 + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +K8S_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +collect_ports() { + PORTS=() + + while IFS= read -r file || [[ -n "$file" ]]; do + if [[ ! -f "$file" ]]; then + continue + fi + + if ! grep -q "LoadBalancer" "$file" 2>/dev/null; then + continue + fi + + lb_port=$(yq -r '.. | objects | select(.type == "LoadBalancer") | select(.port != null) | .port // empty' "$file" 2>/dev/null || true) + if [[ -n "$lb_port" ]] && [[ "$lb_port" != "null" ]] && [[ "$lb_port" =~ ^[0-9]+$ ]]; then + PORTS+=("$lb_port") + [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found .port $lb_port in $file" + fi + + service_port_http=$(yq -r '.. | objects | select(.type == "LoadBalancer") | .servicePortHttp // empty' "$file" 2>/dev/null || true) + if [[ -n "$service_port_http" ]] && [[ "$service_port_http" != "null" ]] && [[ "$service_port_http" =~ ^[0-9]+$ ]]; then + PORTS+=("$service_port_http") + [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found servicePortHttp $service_port_http in $file" + fi + + service_port_https=$(yq -r '.. | objects | select(.type == "LoadBalancer") | .servicePortHttps // empty' "$file" 2>/dev/null || true) + if [[ -n "$service_port_https" ]] && [[ "$service_port_https" != "null" ]] && [[ "$service_port_https" =~ ^[0-9]+$ ]]; then + PORTS+=("$service_port_https") + [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found servicePortHttps $service_port_https in $file" + fi + + service_dns_port=$(yq -r '.. | objects | select(.type == "LoadBalancer") | .dns.port // empty' "$file" 2>/dev/null || true) + if [[ -n "$service_dns_port" ]] && [[ "$service_dns_port" != "null" ]] && [[ "$service_dns_port" =~ ^[0-9]+$ ]]; then + PORTS+=("$service_dns_port") + [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found dns.port $service_dns_port in $file" + fi + done < <(find "$K8S_DIR/k8s" -name "*.yaml" -type f 2>/dev/null) || true + + if command -v kubectl &>/dev/null && kubectl cluster-info &>/dev/null 2>&1; then + [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Querying kubectl for LoadBalancer services..." + while IFS= read -r line || [[ -n "$line" ]]; do + ports_str=$(echo "$line" | awk '{print $6}') + svc_name=$(echo "$line" | awk '{print $2}') + while IFS=',' read -ra port_entries || [[ -n "${port_entries[*]}" ]]; do + for entry in "${port_entries[@]}"; do + port=$(echo "$entry" | cut -d: -f1 | cut -d/ -f1) + if [[ -n "$port" ]] && [[ "$port" =~ ^[0-9]+$ ]]; then + PORTS+=("$port") + [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Found kubectl port $port for $svc_name" + fi + done + done <<< "$ports_str" + done < <(kubectl get svc -A --no-headers 2>/dev/null | grep LoadBalancer || true) + fi +} + +is_port_in_use() { + local port="$1" + [[ " ${PORTS[*]} " =~ ${port} ]] +} + +run_command() { + collect_ports + + if [[ ${#PORTS[@]} -eq 0 ]]; then + [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] No ports found in use" + else + [[ $VERBOSE -eq 1 ]] && echo "[DEBUG] Ports in use: ${PORTS[*]}" + fi + + case "$COMMAND" in + next) + next_port=$MIN_PORT + while [[ $next_port -le $MAX_PORT ]]; do + if ! is_port_in_use "$next_port"; then + echo "$next_port" + return 0 + fi + next_port=$((next_port + 1)) + done + echo "Error: No available port found between $MIN_PORT and $MAX_PORT" >&2 + return 1 + ;; + check) + if [[ -z "$CHECK_PORT" ]]; then + echo "Error: PORT is required for check command" >&2 + return 1 + fi + if ! [[ "$CHECK_PORT" =~ ^[0-9]+$ ]]; then + echo "Error: PORT must be a number" >&2 + return 1 + fi + if [[ "$CHECK_PORT" -lt 1 ]] || [[ "$CHECK_PORT" -gt 65535 ]]; then + echo "Error: PORT must be between 1 and 65535" >&2 + return 1 + fi + if [[ "$CHECK_PORT" -lt "$MIN_PORT" ]] || [[ "$CHECK_PORT" -gt "$MAX_PORT" ]]; then + echo "Error: PORT $CHECK_PORT is outside the check range ($MIN_PORT - $MAX_PORT)" >&2 + return 1 + fi + if is_port_in_use "$CHECK_PORT"; then + echo "PORT $CHECK_PORT IS IN USE" + return 1 + else + echo "PORT $CHECK_PORT IS AVAILABLE" + return 0 + fi + ;; + list) + if [[ ${#PORTS[@]} -eq 0 ]]; then + echo "No ports in use" + else + printf '%s\n' "${PORTS[@]}" | sort -n -u + fi + ;; + esac +} + +run_command