diff --git a/k8s/argo-apps/authentik.yaml b/k8s/argo-apps/authentik.yaml index 2b55824..5bfe1bf 100644 --- a/k8s/argo-apps/authentik.yaml +++ b/k8s/argo-apps/authentik.yaml @@ -51,6 +51,10 @@ spec: persistentVolumeClaim: claimName: pvc-authentik-media server: + resources: + requests: + cpu: 500m + memory: 1Gi autoscaling: enabled: true minReplicas: 1 @@ -79,6 +83,9 @@ spec: tls: [] https: true worker: + requests: + cpu: 250m + memory: 512Mi autoscaling: enabled: true minReplicas: 2 @@ -92,4 +99,6 @@ spec: targetRevision: main project: fuku syncPolicy: - automated: {} + automated: + prune: true + selfHeal: true diff --git a/k8s/argo-apps/dokuwiki.yaml b/k8s/argo-apps/dokuwiki.yaml index f8a54cf..6e5216d 100644 --- a/k8s/argo-apps/dokuwiki.yaml +++ b/k8s/argo-apps/dokuwiki.yaml @@ -12,38 +12,40 @@ spec: sources: - repoURL: git.roboces.dev/catalin/fukuops chart: dokuwiki - targetRevision: 0.1.2 + targetRevision: 1.0.2 helm: valuesObject: - replicaCount: 1 - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: false - runAsUser: 33 - runAsGroup: 33 - capabilities: - drop: - - all - service: - type: LoadBalancer - port: 8004 - ingress: - enabled: true - className: "traefik" - hosts: - - host: wiki.roboces.dev - paths: - - path: / - pathType: Prefix - persistence: - enabled: true - storageClass: "truenas-nfs-csi" - accessMode: ReadWriteMany - size: 20Gi - env: - PHP_TIMEZONE: Europe/Madrid - PHP_MEMORYLIMIT: 512M - PHP_UPLOADLIMIT: 128M + dokuwiki: + replicaCount: 1 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsUser: 33 + runAsGroup: 33 + capabilities: + drop: + - all + service: + type: LoadBalancer + port: 8004 + ingress: + enabled: true + className: "traefik" + hosts: + - host: wiki.roboces.dev + paths: + - path: / + pathType: Prefix + persistence: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 20Gi + config: + php: + timezone: Europe/Madrid + memoryLimit: 512M + uploadLimit: 128M project: roboces syncPolicy: automated: diff --git a/k8s/argo-apps/forgejo.yaml b/k8s/argo-apps/forgejo.yaml index 1341051..a6e6599 100644 --- a/k8s/argo-apps/forgejo.yaml +++ b/k8s/argo-apps/forgejo.yaml @@ -101,4 +101,6 @@ spec: enabled: false project: roboces syncPolicy: - automated: {} + automated: + prune: true + selfHeal: true diff --git a/k8s/argo-apps/helm-rustical.yaml b/k8s/argo-apps/helm-rustical.yaml index 8699598..7fcba18 100644 --- a/k8s/argo-apps/helm-rustical.yaml +++ b/k8s/argo-apps/helm-rustical.yaml @@ -12,45 +12,51 @@ spec: sources: - repoURL: git.roboces.dev/catalin/fukuops chart: helm-rustical - targetRevision: 0.3.0 + targetRevision: 1.1.1 helm: valuesObject: - debug: - enabled: false - replicaCount: 1 - service: - type: LoadBalancer - port: 8001 - targetPort: 4000 - ingress: - enabled: true - className: "traefik" - hosts: - - host: cal.roboces.dev - paths: - - path: / - pathType: Prefix - env: - RUSTICAL_OIDC__NAME: Authentik - RUSTICAL_OIDC__ISSUER: https://auth.fukurokuju.dev/application/o/rustical/ - RUSTICAL_OIDC__CLAIM_USERID: preferred_username - RUSTICAL_OIDC__SCOPES: '["openid", "profile", "groups"]' - RUSTICAL_OIDC__ALLOW_SIGN_UP: "true" - RUSTICAL_FRONTEND__ALLOW_PASSWORD_LOGIN: false - persistence: - enabled: true - storageClass: "truenas-nfs-csi" - accessMode: ReadWriteMany - size: 50Gi - secret: - existingSecretName: rustical - resources: - requests: - cpu: 250m - memory: 256Mi - limits: - cpu: 500m - memory: 512Mi + rustical: + debug: + enabled: false + autoscaling: + enabled: true + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + service: + type: LoadBalancer + port: 8001 + targetPort: 4000 + ingress: + enabled: true + className: "traefik" + hosts: + - host: cal.roboces.dev + paths: + - path: / + pathType: Prefix + config: + oidc.name: Authentik + oidc.issuer: https://auth.fukurokuju.dev/application/o/rustical/ + oidc.claim_userid: preferred_username + oidc.scopes: '["openid", "profile", "groups"]' + oidc.allow_sign_up: "true" + frontend.allow_password_login: false + persistence: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 50Gi + secret: + existingSecretName: rustical + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi project: roboces syncPolicy: automated: diff --git a/k8s/argo-apps/immich.yaml b/k8s/argo-apps/immich.yaml index 71dcbd4..31e0c8b 100644 --- a/k8s/argo-apps/immich.yaml +++ b/k8s/argo-apps/immich.yaml @@ -12,13 +12,9 @@ spec: sources: - repoURL: git.roboces.dev/catalin/fukuops chart: immich - targetRevision: 0.2.3 + targetRevision: 1.0.4 helm: valuesObject: - machine-learning: - enabled: true - service: - type: LoadBalancer immich: autoscaling: enabled: true @@ -32,16 +28,17 @@ spec: - 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" + config: + database: + hostname: "192.168.1.3" + port: 5432 + name: "immich" + redis: + hostname: "192.168.1.3" + port: 30036 + dbIndex: "12" + general: + timezone: Europe/Madrid persistence: library: enabled: true @@ -96,8 +93,13 @@ spec: - path: / pathType: Prefix tls: [] - secret: - existingSecretName: "immich-secrets" + machineLearning: + enabled: true + service: + type: LoadBalancer + secret: + enabled: true + existingSecretName: "immich-secrets" project: roboces syncPolicy: automated: diff --git a/k8s/argo-apps/miniflux.yaml b/k8s/argo-apps/miniflux.yaml index f94de08..2339c6f 100644 --- a/k8s/argo-apps/miniflux.yaml +++ b/k8s/argo-apps/miniflux.yaml @@ -12,72 +12,79 @@ spec: sources: - repoURL: git.roboces.dev/catalin/fukuops chart: miniflux - targetRevision: 0.2.0 + targetRevision: 1.0.3 helm: valuesObject: - replicaCount: 3 - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - runAsUser: 10000 - runAsGroup: 10000 - capabilities: - drop: - - all - service: - type: LoadBalancer - ingress: - enabled: true - className: "traefik" - hosts: - - host: feeds.roboces.dev - paths: - - path: / - pathType: Prefix - resources: - requests: - cpu: 300m - memory: 300Mi - ephemeral-storage: 2Gi - limits: - cpu: 400m - memory: 500Mi - ephemeral-storage: 4Gi - livenessProbe: - tcpSocket: - port: 8080 - initialDelaySeconds: 5 - timeoutSeconds: 15 - successThreshold: 1 - failureThreshold: 3 - periodSeconds: 10 - readinessProbe: - httpGet: - path: / - port: 8080 - initialDelaySeconds: 15 - timeoutSeconds: 2 - successThreshold: 1 - failureThreshold: 3 - periodSeconds: 10 - podDisruptionBudget: - enabled: true - maxUnavailable: 1 - env: - RUN_MIGRATIONS: "1" - CREATE_ADMIN: "1" - OAUTH2_PROVIDER: oidc - OAUTH2_REDIRECT_URL: https://feeds.roboces.dev/oauth2/oidc/callback - OAUTH2_OIDC_DISCOVERY_ENDPOINT: https://auth.fukurokuju.dev/application/o/miniflux/ - OAUTH2_USER_CREATION: "1" - FETCH_YOUTUBE_WATCH_TIME: "1" - WORKER_POOL_SIZE: "1" - POLLING_FREQUENCY: "120" - BATCH_SIZE: "25" - METRICS_COLLECTOR: "1" - METRICS_ALLOWED_NETWORKS: 10.42.1.0/16 - secret: - existingSecretName: miniflux + miniflux: + autoscaling: + enabled: true + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsUser: 10000 + runAsGroup: 10000 + capabilities: + drop: + - all + service: + type: LoadBalancer + ingress: + enabled: true + className: "traefik" + hosts: + - host: feeds.roboces.dev + paths: + - path: / + pathType: Prefix + resources: + requests: + cpu: 500m + memory: 512Mi + ephemeral-storage: 2Gi + limits: + cpu: 700m + memory: 1024Mi + ephemeral-storage: 4Gi + livenessProbe: + tcpSocket: + port: 8080 + initialDelaySeconds: 5 + timeoutSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 15 + timeoutSeconds: 2 + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 + podDisruptionBudget: + enabled: true + maxUnavailable: 1 + config: + general: + runMigrations: true + createAdmin: true + workerPoolSize: 1 + pollingFrequency: 120 + batchSize: 25 + oauth2: + enabled: true + provider: oidc + redirectUrl: https://feeds.roboces.dev/oauth2/oidc/callback + oidcDiscoveryEndpoint: https://auth.fukurokuju.dev/application/o/miniflux/ + userCreation: true + metrics: + enabled: true + allowedNetworks: 10.42.1.0/16 + youtube: + fetchWatchTime: true + secret: + existingSecretName: miniflux project: roboces syncPolicy: automated: diff --git a/k8s/argo-apps/oxicloud.yaml b/k8s/argo-apps/oxicloud.yaml index 0f67c31..aa5556e 100644 --- a/k8s/argo-apps/oxicloud.yaml +++ b/k8s/argo-apps/oxicloud.yaml @@ -12,39 +12,43 @@ spec: sources: - repoURL: git.roboces.dev/catalin/fukuops chart: oxicloud - targetRevision: 0.1.0 + targetRevision: 1.0.1 helm: valuesObject: - persistence: - enabled: true - storageClass: "truenas-nfs-csi" - accessMode: ReadWriteMany - size: 50Gi - service: - type: LoadBalancer - config: - server: - port: 8086 - host: "0.0.0.0" - baseUrl: "https://cloud.roboces.dev" - features: - enableAuth: "true" - enableSharing: "true" - mimalloc: - purgeDelay: "0" - allowLargeOsPages: "0" - secrets: - existingSecret: oxicloud - wopi: - enabled: false - ingress: - className: "traefik" - hosts: - - host: cloud.roboces.dev - paths: - - path: / - pathType: ImplementationSpecific - tls: [] + oxicloud: + persistence: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 50Gi + service: + type: LoadBalancer + config: + server: + port: 8086 + host: "0.0.0.0" + baseUrl: "https://cloud.roboces.dev" + features: + enableAuth: true + enableSharing: true + mimalloc: + purgeDelay: "0" + allowLargeOsPages: "0" + secret: + existingSecretName: oxicloud + wopi: + enabled: false + ingress: + enabled: true + className: "traefik" + hosts: + - host: cloud.roboces.dev + paths: + - path: / + pathType: ImplementationSpecific + tls: [] project: roboces syncPolicy: - automated: {} + automated: + prune: true + selfHeal: true diff --git a/k8s/argo-apps/vault-sm.yaml b/k8s/argo-apps/vault-sm.yaml index 7e7ba2c..60f0564 100644 --- a/k8s/argo-apps/vault-sm.yaml +++ b/k8s/argo-apps/vault-sm.yaml @@ -21,6 +21,7 @@ spec: type: LoadBalancer persistence: storageClass: truenas-nfs-csi + accessMode: ReadWriteMany dashboard: enabled: true service: diff --git a/k8s/argo-apps/vaultwarden.yaml b/k8s/argo-apps/vaultwarden.yaml index 55230c4..11573e5 100644 --- a/k8s/argo-apps/vaultwarden.yaml +++ b/k8s/argo-apps/vaultwarden.yaml @@ -12,51 +12,51 @@ spec: sources: - repoURL: git.roboces.dev/catalin/fukuops chart: vaultwarden - targetRevision: 0.1.1 + targetRevision: 1.0.2 helm: valuesObject: - conf: - db: - enable_db_wal: true - connection_retries: 15 - timeout: 30 - web_vault: + vaultwarden: + config: + database: + enableWal: true + connectionRetries: 15 + timeout: 30 + webVault: + enabled: true + folder: "web-vault/" + replicaCount: 1 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsUser: 10000 + runAsGroup: 10000 + capabilities: + drop: + - all + service: + type: LoadBalancer + ingress: enabled: true - folder: "web-vault/" - - replicaCount: 1 - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: false - runAsUser: 10000 - runAsGroup: 10000 - capabilities: - drop: - - all - service: - type: LoadBalancer - ingress: - enabled: true - className: "traefik" - hosts: - - host: vault.roboces.dev - paths: - - path: / - pathType: Prefix - resources: - requests: - cpu: 250m - memory: 256Mi - limits: - cpu: 500m - memory: 512Mi - persistence: - enabled: true - storageClass: "truenas-nfs-csi" - accessMode: ReadWriteMany - size: 5Gi - secret: - existingSecretName: vaultwarden-secrets + className: "traefik" + hosts: + - host: vault.roboces.dev + paths: + - path: / + pathType: Prefix + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + persistence: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 5Gi + secret: + existingSecretName: vaultwarden-secrets project: roboces syncPolicy: automated: diff --git a/k8s/charts/_template/Chart.yaml b/k8s/charts/_template/Chart.yaml index ad3c94c..d41d280 100644 --- a/k8s/charts/_template/Chart.yaml +++ b/k8s/charts/_template/Chart.yaml @@ -2,15 +2,9 @@ apiVersion: v2 name: chart description: A Helm chart template with standardized structure type: application -version: 0.1.0 -appVersion: "latest" +version: 1.0.2 +appVersion: latest annotations: - artifacthub.io/images: | - - name: chart - image: nginx:latest - artifacthub.io/changes: | - - kind: added - description: Initial chart template - artifacthub.io/maintainers: | - - name: catalin - email: catalin@example.com + artifacthub.io/images: "- name: chart\n image: nginx:latest\n" + artifacthub.io/changes: "- kind: added\n description: Initial chart template\n" + artifacthub.io/maintainers: "- name: catalin\n email: catalin@example.com\n" diff --git a/k8s/charts/_template/README.md b/k8s/charts/_template/README.md index 3be5ac7..c82b8e7 100644 --- a/k8s/charts/_template/README.md +++ b/k8s/charts/_template/README.md @@ -5,7 +5,7 @@ A Helm chart template with standardized structure for self-hosted services. ## TL;DR ```bash -helm install chart oci://git.roboces.dev/catalin/fukuops/chart --version 1.0.0 -n apps-roboces +helm install chart oci://git.roboces.dev/catalin/fukuops/chart --version 1.0.1 -n apps-roboces ``` ## Prerequisites @@ -17,64 +17,74 @@ helm install chart oci://git.roboces.dev/catalin/fukuops/chart --version 1.0.0 - ## Configuration -### Basic Configuration +All values are nested under the `chart:` key. Example: ```yaml -service: - type: LoadBalancer - port: 80 -ingress: - enabled: true - className: "traefik" - hosts: - - host: chart.example.com - paths: - - path: / - pathType: Prefix -``` - -### With Persistence - -```yaml -persistence: - enabled: true - storageClass: "truenas-nfs-csi" - accessMode: ReadWriteMany - size: 10Gi -``` - -### With Secrets - -```yaml -secret: - existingSecretName: chart-secrets +chart: + replicaCount: 1 + service: + type: LoadBalancer + port: 80 + ingress: + enabled: true + className: "traefik" + hosts: + - host: chart.example.com + paths: + - path: / + pathType: Prefix + config: + # Key-value pairs that will be mapped to env vars (uppercased) + # Example: `FOO: "bar"` becomes env var FOO=bar + persistence: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 10Gi + secret: + existingSecretName: chart-secrets ``` ## Values Reference | Key | Type | Default | Description | |-----|------|---------|-------------| -| `replicaCount` | int | `1` | Number of replicas | -| `image.repository` | string | `nginx` | Image repository | -| `image.pullPolicy` | string | `IfNotPresent` | Image pull policy | -| `image.tag` | string | `.Chart.AppVersion` | Image tag | -| `service.type` | string | `LoadBalancer` | Service type | -| `service.port` | int | `80` | Service port | -| `service.targetPort` | int | `80` | Container port | -| `ingress.enabled` | bool | `false` | Enable ingress | -| `ingress.className` | string | `traefik` | Ingress class | -| `persistence.enabled` | bool | `false` | Enable persistence | -| `persistence.storageClass` | string | `truenas-nfs-csi` | Storage class | -| `persistence.accessMode` | string | `ReadWriteMany` | Access mode | -| `persistence.size` | string | `1Gi` | PVC size | -| `persistence.name` | string | `chart-data` | PVC name | -| `secret.existingSecretName` | string | `""` | Existing secret name | -| `resources` | object | `{}` | Container resources | -| `livenessProbe` | object | (see values.yaml) | Liveness probe | -| `readinessProbe` | object | (see values.yaml) | Readiness probe | -| `nodeSelector` | object | `{}` | Node selector | -| `tolerations` | array | `[]` | Tolerations | -| `affinity` | object | `{}` | Affinity rules | +| `chart.replicaCount` | int | `1` | Number of replicas | +| `chart.image.repository` | string | `nginx` | Image repository | +| `chart.image.pullPolicy` | string | `IfNotPresent` | Image pull policy | +| `chart.image.tag` | string | `.Chart.AppVersion` | Image tag | +| `chart.service.type` | string | `LoadBalancer` | Service type | +| `chart.service.port` | int | `80` | Service port | +| `chart.service.targetPort` | int | `80` | Container port | +| `chart.ingress.enabled` | bool | `false` | Enable ingress | +| `chart.ingress.className` | string | `traefik` | Ingress class | +| `chart.config` | object | `{}` | Config key-value pairs mapped to env vars | +| `chart.persistence.enabled` | bool | `false` | Enable persistence | +| `chart.persistence.storageClass` | string | `truenas-nfs-csi` | Storage class | +| `chart.persistence.accessMode` | string | `ReadWriteMany` | Access mode | +| `chart.persistence.size` | string | `1Gi` | PVC size | +| `chart.persistence.name` | string | `chart-data` | PVC name | +| `chart.secret.existingSecretName` | string | `""` | Existing secret name | +| `chart.debug.enabled` | bool | `false` | Enable debug mode (adds init container for troubleshooting) | +| `chart.resources` | object | `{}` | Container resources | +| `chart.livenessProbe` | object | (see values.yaml) | Liveness probe | +| `chart.readinessProbe` | object | (see values.yaml) | Readiness probe | +| `chart.nodeSelector` | object | `{}` | Node selector | +| `chart.tolerations` | array | `[]` | Tolerations | +| `chart.affinity` | object | `{}` | Affinity rules | + +## ConfigMap + +The `config:` section creates a ConfigMap that maps keys to environment variables. Keys are uppercased. + +```yaml +chart: + config: + FOO: "bar" + BAZ: "qux" +``` + +This creates env vars `FOO=bar` and `BAZ=qux`. ## Persistence @@ -82,10 +92,28 @@ When persistence is enabled, a PVC named `chart-data` is created. The PVC uses t ## Secret Management -The chart looks for an existing `Secret` with the name specified in `secret.existingSecretName`. If not provided, no secret is mounted. +The chart looks for an existing `Secret` with the name specified in `chart.secret.existingSecretName`. If not provided, no secret is mounted. ## Troubleshooting +### Debug Mode + +Enable debug mode to troubleshoot issues by adding an init container with a shell: + +```yaml +chart: + debug: + enabled: true +``` + +This adds an `alpine:3.19` init container with `sleep infinity` that mounts all volumes and inherits env vars. You can exec into it to inspect the environment: + +```bash +kubectl exec -it -c debug -- sh +``` + +When debug mode is enabled, liveness and readiness probes are disabled to prevent restarts. + ### Pod not starting ```bash diff --git a/k8s/charts/_template/templates/configmap.yaml b/k8s/charts/_template/templates/configmap.yaml index 83ae129..4ef3ad3 100644 --- a/k8s/charts/_template/templates/configmap.yaml +++ b/k8s/charts/_template/templates/configmap.yaml @@ -1,4 +1,4 @@ -{{- if .Values.config }} +{{- if .Values.chart.config }} apiVersion: v1 kind: ConfigMap metadata: @@ -6,7 +6,8 @@ metadata: labels: {{- include "labels" . | nindent 4 }} data: - {{- range $key, $value := .Values.config }} - {{ $key }}: {{ $value | quote }} + {{- range $key, $value := .Values.chart.config }} + {{- $envKey := $key | upper }} + {{ $envKey }}: {{ $value | quote }} {{- end }} {{- end }} diff --git a/k8s/charts/_template/templates/deployment.yaml b/k8s/charts/_template/templates/deployment.yaml index d22f643..fef46d0 100644 --- a/k8s/charts/_template/templates/deployment.yaml +++ b/k8s/charts/_template/templates/deployment.yaml @@ -5,7 +5,7 @@ metadata: labels: {{- include "labels" . | nindent 4 }} spec: - replicas: {{ .Values.replicaCount }} + replicas: {{ .Values.chart.replicaCount }} selector: matchLabels: {{- include "selectorLabels" . | nindent 6 }} @@ -14,55 +14,90 @@ spec: labels: {{- include "selectorLabels" . | nindent 8 }} spec: - containers: - - name: {{ .Chart.Name }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - ports: - - name: http - containerPort: {{ .Values.service.targetPort }} - protocol: TCP - {{- if .Values.config }} + {{- with .Values.chart.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.chart.debug.enabled }} + initContainers: + - name: debug + image: alpine:3.19 + command: ["sleep", "infinity"] envFrom: + {{- if .Values.chart.config }} - configMapRef: name: {{ include "fullname" . }}-config - {{- end }} - {{- if .Values.secret.existingSecretName }} - envFrom: + {{- end }} + {{- if .Values.chart.secret.existingSecretName }} - secretRef: - name: {{ .Values.secret.existingSecretName }} - {{- end }} - {{- if .Values.persistence.enabled }} + name: {{ .Values.chart.secret.existingSecretName }} + {{- end }} + {{- if .Values.chart.persistence.enabled }} volumeMounts: - name: {{ include "pvcName" . }} mountPath: /data {{- end }} - {{- with .Values.resources }} + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + capabilities: + drop: + - all + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.chart.image.repository }}:{{ .Values.chart.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.chart.image.pullPolicy }} + {{- with .Values.chart.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: {{ .Values.chart.service.targetPort }} + protocol: TCP + envFrom: + {{- if .Values.chart.config }} + - configMapRef: + name: {{ include "fullname" . }}-config + {{- end }} + {{- if .Values.chart.secret.existingSecretName }} + - secretRef: + name: {{ .Values.chart.secret.existingSecretName }} + {{- end }} + {{- if .Values.chart.persistence.enabled }} + volumeMounts: + - name: {{ include "pvcName" . }} + mountPath: /data + {{- end }} + {{- with .Values.chart.resources }} resources: {{- toYaml . | nindent 12 }} {{- end }} - {{- with .Values.livenessProbe }} + {{- if not .Values.chart.debug.enabled }} + {{- with .Values.chart.livenessProbe }} livenessProbe: {{- toYaml . | nindent 12 }} {{- end }} - {{- with .Values.readinessProbe }} + {{- with .Values.chart.readinessProbe }} readinessProbe: {{- toYaml . | nindent 12 }} {{- end }} - {{- if not .Values.persistence.enabled }} + {{- end }} + {{- if not .Values.chart.persistence.enabled }} volumes: - name: {{ include "pvcName" . }} emptyDir: {} {{- end }} - {{- with .Values.nodeSelector }} + {{- with .Values.chart.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.tolerations }} + {{- with .Values.chart.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.affinity }} + {{- with .Values.chart.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} diff --git a/k8s/charts/_template/templates/ingress.yaml b/k8s/charts/_template/templates/ingress.yaml index 3917052..fbcf3ea 100644 --- a/k8s/charts/_template/templates/ingress.yaml +++ b/k8s/charts/_template/templates/ingress.yaml @@ -1,21 +1,21 @@ -{{- if .Values.ingress.enabled }} +{{- if .Values.chart.ingress.enabled }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ include "fullname" . }} labels: {{- include "labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} + {{- with .Values.chart.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: - {{- if .Values.ingress.className }} - ingressClassName: {{ .Values.ingress.className }} + {{- if .Values.chart.ingress.className }} + ingressClassName: {{ .Values.chart.ingress.className }} {{- end }} - {{- if .Values.ingress.tls }} + {{- if .Values.chart.ingress.tls }} tls: - {{- range .Values.ingress.tls }} + {{- range .Values.chart.ingress.tls }} - hosts: {{- range .hosts }} - {{ . | quote }} @@ -24,7 +24,7 @@ spec: {{- end }} {{- end }} rules: - {{- range .Values.ingress.hosts }} + {{- range .Values.chart.ingress.hosts }} - host: {{ .host | quote }} http: paths: @@ -35,7 +35,7 @@ spec: service: name: {{ include "fullname" $ }} port: - number: {{ $.Values.service.port }} + number: {{ $.Values.chart.service.port }} {{- end }} {{- end }} {{- end }} diff --git a/k8s/charts/_template/templates/pdb.yaml b/k8s/charts/_template/templates/pdb.yaml new file mode 100644 index 0000000..4275ecd --- /dev/null +++ b/k8s/charts/_template/templates/pdb.yaml @@ -0,0 +1,18 @@ +{{- if .Values.chart.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "fullname" . }} + labels: + {{- include "labels" . | nindent 4 }} +spec: + {{- if .Values.chart.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.chart.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if .Values.chart.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ .Values.chart.podDisruptionBudget.maxUnavailable }} + {{- end }} + selector: + matchLabels: + {{- include "selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/k8s/charts/_template/templates/pvc.yaml b/k8s/charts/_template/templates/pvc.yaml index 44448fd..274699c 100644 --- a/k8s/charts/_template/templates/pvc.yaml +++ b/k8s/charts/_template/templates/pvc.yaml @@ -1,4 +1,4 @@ -{{- if .Values.persistence.enabled }} +{{- if .Values.chart.persistence.enabled }} apiVersion: v1 kind: PersistentVolumeClaim metadata: @@ -7,11 +7,11 @@ metadata: {{- include "labels" . | nindent 4 }} spec: accessModes: - - {{ .Values.persistence.accessMode | default "ReadWriteMany" }} - {{- if .Values.persistence.storageClass }} - storageClassName: {{ .Values.persistence.storageClass }} + - {{ .Values.chart.persistence.accessMode | default "ReadWriteMany" }} + {{- if .Values.chart.persistence.storageClass }} + storageClassName: {{ .Values.chart.persistence.storageClass }} {{- end }} resources: requests: - storage: {{ .Values.persistence.size | default "1Gi" }} + storage: {{ .Values.chart.persistence.size | default "1Gi" }} {{- end }} diff --git a/k8s/charts/_template/templates/secret.yaml b/k8s/charts/_template/templates/secret.yaml index 7305ca0..59e84d8 100644 --- a/k8s/charts/_template/templates/secret.yaml +++ b/k8s/charts/_template/templates/secret.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.secret.enabled (not .Values.secret.existingSecretName) .Values.secret.data }} +{{- if and .Values.chart.secret.enabled (not .Values.chart.secret.existingSecretName) .Values.chart.secret.data }} apiVersion: v1 kind: Secret metadata: @@ -7,7 +7,7 @@ metadata: {{- include "labels" . | nindent 4 }} type: Opaque data: - {{- range $key, $value := .Values.secret.data }} + {{- range $key, $value := .Values.chart.secret.data }} {{ $key }}: {{ $value | b64enc | quote }} {{- end }} {{- end }} diff --git a/k8s/charts/_template/templates/service.yaml b/k8s/charts/_template/templates/service.yaml index c00186b..b3b5ffd 100644 --- a/k8s/charts/_template/templates/service.yaml +++ b/k8s/charts/_template/templates/service.yaml @@ -5,9 +5,9 @@ metadata: labels: {{- include "labels" . | nindent 4 }} spec: - type: {{ .Values.service.type }} + type: {{ .Values.chart.service.type }} ports: - - port: {{ .Values.service.port }} + - port: {{ .Values.chart.service.port }} targetPort: http protocol: TCP name: http diff --git a/k8s/charts/_template/values.yaml b/k8s/charts/_template/values.yaml index acee144..7aa1f5f 100644 --- a/k8s/charts/_template/values.yaml +++ b/k8s/charts/_template/values.yaml @@ -1,60 +1,71 @@ -replicaCount: 1 +chart: + replicaCount: 1 -image: - repository: nginx - pullPolicy: IfNotPresent - tag: "" + image: + repository: nginx + pullPolicy: IfNotPresent + tag: "" -service: - type: LoadBalancer - port: 80 - targetPort: 80 - -ingress: - enabled: false - className: "traefik" - annotations: {} - hosts: - - host: chart.example.com - paths: - - path: / - pathType: Prefix - tls: [] - -config: {} - -persistence: - enabled: false - storageClass: "truenas-nfs-csi" - accessMode: ReadWriteMany - size: 1Gi - name: "chart-data" - -resources: {} - -livenessProbe: - tcpSocket: + service: + type: LoadBalancer port: 80 - initialDelaySeconds: 30 - timeoutSeconds: 15 - successThreshold: 1 - failureThreshold: 3 - periodSeconds: 10 + targetPort: 80 -readinessProbe: - tcpSocket: - port: 80 - initialDelaySeconds: 15 - timeoutSeconds: 2 - successThreshold: 1 - failureThreshold: 3 - periodSeconds: 10 + ingress: + enabled: false + className: "traefik" + annotations: {} + hosts: + - host: chart.example.com + paths: + - path: / + pathType: Prefix + tls: [] -nodeSelector: {} -tolerations: [] -affinity: {} + config: {} -secret: - enabled: true - existingSecretName: "" - data: {} + persistence: + enabled: false + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 1Gi + name: "chart-data" + + resources: {} + + livenessProbe: + tcpSocket: + port: 80 + initialDelaySeconds: 30 + timeoutSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 + + readinessProbe: + tcpSocket: + port: 80 + initialDelaySeconds: 15 + timeoutSeconds: 2 + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 + + nodeSelector: {} + tolerations: [] + affinity: {} + + secret: + enabled: true + existingSecretName: "" + data: {} + + podSecurityContext: {} + securityContext: {} + + podDisruptionBudget: + enabled: false + minAvailable: 1 + + debug: + enabled: false diff --git a/k8s/charts/dokuwiki/Chart.yaml b/k8s/charts/dokuwiki/Chart.yaml index fadd84f..e49d497 100644 --- a/k8s/charts/dokuwiki/Chart.yaml +++ b/k8s/charts/dokuwiki/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: dokuwiki description: A Helm chart for DokuWiki type: application -version: 0.1.0 +version: 1.0.2 appVersion: 2024-02-06b annotations: artifacthub.io/images: "- name: dokuwiki\n image: dokuwiki/dokuwiki:2024-02-06b\n" diff --git a/k8s/charts/dokuwiki/README.md b/k8s/charts/dokuwiki/README.md new file mode 100644 index 0000000..90ff741 --- /dev/null +++ b/k8s/charts/dokuwiki/README.md @@ -0,0 +1,112 @@ +# dokuwiki + +A Helm chart for DokuWiki, a simple to use and highly versatile wiki software. + +## TL;DR + +```bash +helm install dokuwiki oci://git.roboces.dev/catalin/fukuops/dokuwiki --version 1.0.2 -n apps-roboces +``` + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3+ +- A NFS storage class (default: `truenas-nfs-csi`) + +## Configuration + +All values are nested under the `dokuwiki:` key. Example: + +```yaml +dokuwiki: + persistence: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 2Gi + service: + type: LoadBalancer + port: 8004 + ingress: + enabled: true + hosts: + - host: wiki.roboces.dev + paths: + - path: / + pathType: Prefix + config: + php: + timezone: Europe/Madrid + memoryLimit: 512M + uploadLimit: 128M +``` + +## Values Reference + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `dokuwiki.replicaCount` | int | `1` | Number of replicas | +| `dokuwiki.image.repository` | string | `dokuwiki/dokuwiki` | Image repository | +| `dokuwiki.image.pullpolicy` | string | `IfNotPresent` | Image pull policy | +| `dokuwiki.image.tag` | string | `.Chart.AppVersion` | Image tag | +| `dokuwiki.service.type` | string | `LoadBalancer` | Service type | +| `dokuwiki.service.port` | int | `8004` | Service port | +| `dokuwiki.service.targetPort` | int | `8080` | Container port | +| `dokuwiki.ingress.enabled` | bool | `true` | Enable ingress | +| `dokuwiki.ingress.className` | string | `""` | Ingress class | +| `dokuwiki.ingress.hosts[0].host` | string | `wiki.roboces.dev` | Ingress hostname | +| `dokuwiki.persistence.enabled` | bool | `true` | Enable persistence | +| `dokuwiki.persistence.storageClass` | string | `truenas-nfs-csi` | Storage class | +| `dokuwiki.persistence.accessMode` | string | `ReadWriteMany` | Access mode | +| `dokuwiki.persistence.size` | string | `2Gi` | PVC size | +| `dokuwiki.config.php.timezone` | string | `Europe/Madrid` | PHP timezone | +| `dokuwiki.config.php.memoryLimit` | string | `512M` | PHP memory limit | +| `dokuwiki.config.php.uploadLimit` | string | `128M` | PHP upload limit | +| `dokuwiki.debug.enabled` | bool | `false` | Enable debug mode (adds init container for troubleshooting) | +| `dokuwiki.resources` | object | `{}` | Container resources | +| `dokuwiki.livenessProbe` | object | (see values.yaml) | Liveness probe | +| `dokuwiki.readinessProbe` | object | (see values.yaml) | Readiness probe | + +## ConfigMap + +The chart creates a ConfigMap with PHP environment variables: +- `PHP_TIMEZONE` +- `PHP_MEMORYLIMIT` +- `PHP_UPLOADLIMIT` + +## Persistence + +The chart creates a PVC named `dokuwiki-data`. The PVC uses the `truenas-nfs-csi` storage class by default with `ReadWriteMany` access mode. + +## Troubleshooting + +### Debug Mode + +Enable debug mode to troubleshoot issues by adding an init container with a shell: + +```yaml +dokuwiki: + debug: + enabled: true +``` + +This adds an `alpine:3.19` init container with `sleep infinity` that mounts all volumes and inherits env vars. You can exec into it to inspect the environment: + +```bash +kubectl exec -it -c debug -- sh +``` + +When debug mode is enabled, liveness and readiness probes are disabled to prevent restarts. + +### Pod not starting + +```bash +kubectl get pvc -n apps-roboces -l app.kubernetes.io/name=dokuwiki +``` + +### Check logs + +```bash +kubectl logs dokuwiki-0 -n apps-roboces +``` diff --git a/k8s/charts/dokuwiki/templates/configmap.yaml b/k8s/charts/dokuwiki/templates/configmap.yaml new file mode 100644 index 0000000..3320df1 --- /dev/null +++ b/k8s/charts/dokuwiki/templates/configmap.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "dokuwiki.fullname" . }}-config + labels: + {{- include "dokuwiki.labels" . | nindent 4 }} +data: + PHP_TIMEZONE: {{ .Values.dokuwiki.config.php.timezone | quote }} + PHP_MEMORYLIMIT: {{ .Values.dokuwiki.config.php.memoryLimit | quote }} + PHP_UPLOADLIMIT: {{ .Values.dokuwiki.config.php.uploadLimit | quote }} diff --git a/k8s/charts/dokuwiki/templates/ingress.yaml b/k8s/charts/dokuwiki/templates/ingress.yaml index adf8060..6b175ee 100644 --- a/k8s/charts/dokuwiki/templates/ingress.yaml +++ b/k8s/charts/dokuwiki/templates/ingress.yaml @@ -1,23 +1,23 @@ -{{- if .Values.ingress.enabled -}} +{{- if .Values.dokuwiki.ingress.enabled -}} {{- $fullName := include "dokuwiki.fullname" . -}} -{{- $svcPort := .Values.service.port -}} +{{- $svcPort := .Values.dokuwiki.service.port -}} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ $fullName }} labels: {{- include "dokuwiki.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} + {{- with .Values.dokuwiki.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: - {{- if .Values.ingress.className }} - ingressClassName: {{ .Values.ingress.className }} + {{- if .Values.dokuwiki.ingress.className }} + ingressClassName: {{ .Values.dokuwiki.ingress.className }} {{- end }} - {{- if .Values.ingress.tls }} + {{- if .Values.dokuwiki.ingress.tls }} tls: - {{- range .Values.ingress.tls }} + {{- range .Values.dokuwiki.ingress.tls }} - hosts: {{- range .hosts }} - {{ . | quote }} @@ -26,7 +26,7 @@ spec: {{- end }} {{- end }} rules: - {{- range .Values.ingress.hosts }} + {{- range .Values.dokuwiki.ingress.hosts }} - host: {{ .host | quote }} http: paths: diff --git a/k8s/charts/dokuwiki/templates/service.yaml b/k8s/charts/dokuwiki/templates/service.yaml index 146c8bb..add149d 100644 --- a/k8s/charts/dokuwiki/templates/service.yaml +++ b/k8s/charts/dokuwiki/templates/service.yaml @@ -5,10 +5,10 @@ metadata: labels: {{- include "dokuwiki.labels" . | nindent 4 }} spec: - type: {{ .Values.service.type }} + type: {{ .Values.dokuwiki.service.type }} ports: - - port: {{ .Values.service.port }} - targetPort: {{ .Values.service.targetPort }} + - port: {{ .Values.dokuwiki.service.port }} + targetPort: {{ .Values.dokuwiki.service.targetPort }} protocol: TCP name: http selector: diff --git a/k8s/charts/dokuwiki/templates/statefulset.yaml b/k8s/charts/dokuwiki/templates/statefulset.yaml index 5400b2e..476343c 100644 --- a/k8s/charts/dokuwiki/templates/statefulset.yaml +++ b/k8s/charts/dokuwiki/templates/statefulset.yaml @@ -6,86 +6,84 @@ metadata: {{- include "dokuwiki.labels" . | nindent 4 }} spec: serviceName: {{ include "dokuwiki.fullname" . }}-headless - {{- if not .Values.autoscaling.enabled }} - replicas: {{ .Values.replicaCount }} + {{- if not .Values.dokuwiki.autoscaling.enabled }} + replicas: {{ .Values.dokuwiki.replicaCount }} {{- end }} selector: matchLabels: {{- include "dokuwiki.selectorLabels" . | nindent 6 }} template: metadata: - {{- with .Values.podAnnotations }} + {{- with .Values.dokuwiki.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "dokuwiki.selectorLabels" . | nindent 8 }} spec: - {{- with .Values.imagePullSecrets }} + {{- with .Values.dokuwiki.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- toYaml .Values.dokuwiki.podSecurityContext | nindent 8 }} containers: - name: {{ .Chart.Name }} securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - env: - {{- range $key, $value := .Values.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} + {{- toYaml .Values.dokuwiki.securityContext | nindent 12 }} + image: "{{ .Values.dokuwiki.image.repository }}:{{ .Values.dokuwiki.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.dokuwiki.image.pullPolicy }} + envFrom: + - configMapRef: + name: {{ include "dokuwiki.fullname" . }}-config ports: - name: http containerPort: 8080 protocol: TCP livenessProbe: - {{- toYaml .Values.livenessProbe | nindent 12 }} + {{- toYaml .Values.dokuwiki.livenessProbe | nindent 12 }} readinessProbe: - {{- toYaml .Values.readinessProbe | nindent 12 }} + {{- toYaml .Values.dokuwiki.readinessProbe | nindent 12 }} resources: - {{- toYaml .Values.resources | nindent 12 }} + {{- toYaml .Values.dokuwiki.resources | nindent 12 }} volumeMounts: - name: data mountPath: /storage - {{- if .Values.persistence.existingClaim }} + {{- if .Values.dokuwiki.persistence.existingClaim }} volumes: - name: data persistentVolumeClaim: - claimName: {{ .Values.persistence.existingClaim }} + claimName: {{ .Values.dokuwiki.persistence.existingClaim }} {{- end }} - {{- with .Values.nodeSelector }} + {{- with .Values.dokuwiki.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.affinity }} + {{- with .Values.dokuwiki.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.tolerations }} + {{- with .Values.dokuwiki.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} - restartPolicy: Always - automountServiceAccountToken: false - {{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} + restartPolicy: Always + automountServiceAccountToken: false + {{- if and .Values.dokuwiki.persistence.enabled (not .Values.dokuwiki.persistence.existingClaim) }} volumeClaimTemplates: - metadata: name: data spec: accessModes: - - {{ .Values.persistence.accessMode }} - {{- if .Values.persistence.storageClass }} - {{- if eq "-" .Values.persistence.storageClass }} + - {{ .Values.dokuwiki.persistence.accessMode }} + {{- if .Values.dokuwiki.persistence.storageClass }} + {{- if eq "-" .Values.dokuwiki.persistence.storageClass }} storageClassName: "" {{- else }} - storageClassName: {{ .Values.persistence.storageClass | quote }} + storageClassName: {{ .Values.dokuwiki.persistence.storageClass | quote }} {{- end }} {{- end }} resources: requests: - storage: {{ .Values.persistence.size }} + storage: {{ .Values.dokuwiki.persistence.size }} {{- end }} diff --git a/k8s/charts/dokuwiki/values.yaml b/k8s/charts/dokuwiki/values.yaml index dbedf9e..f003331 100644 --- a/k8s/charts/dokuwiki/values.yaml +++ b/k8s/charts/dokuwiki/values.yaml @@ -1,66 +1,71 @@ -replicaCount: 1 +dokuwiki: + replicaCount: 1 -image: - repository: dokuwiki/dokuwiki - pullpolicy: IfNotPresent - tag: "" + image: + repository: dokuwiki/dokuwiki + pullpolicy: IfNotPresent + tag: "" -imagePullSecrets: [] -podAnnotations: {} -podSecurityContext: {} -securityContext: {} + imagePullSecrets: [] + podAnnotations: {} + podSecurityContext: {} + securityContext: {} -service: - type: LoadBalancer - port: 8004 - targetPort: 8080 + service: + type: LoadBalancer + port: 8004 + targetPort: 8080 -ingress: - enabled: true - className: "" - annotations: {} - hosts: - - host: wiki.roboces.dev - paths: - - path: / - pathType: Prefix - tls: [] + ingress: + enabled: true + className: "" + annotations: {} + hosts: + - host: wiki.roboces.dev + paths: + - path: / + pathType: Prefix + tls: [] -persistence: - enabled: true - storageClass: "truenas-nfs-csi" - accessMode: ReadWriteMany - size: 2Gi - existingClaim: "" + config: + php: + timezone: Europe/Madrid + memoryLimit: 512M + uploadLimit: 128M -autoscaling: - enabled: false + persistence: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 2Gi + existingClaim: "" -resources: {} + autoscaling: + enabled: false -livenessProbe: - tcpSocket: - port: 8080 - initialDelaySeconds: 30 - timeoutSeconds: 15 - successThreshold: 1 - failureThreshold: 3 - periodSeconds: 10 + resources: {} -readinessProbe: - tcpSocket: - port: 8080 - initialDelaySeconds: 15 - timeoutSeconds: 2 - successThreshold: 1 - failureThreshold: 3 - periodSeconds: 10 + livenessProbe: + tcpSocket: + port: 8080 + initialDelaySeconds: 30 + timeoutSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 -nodeSelector: {} -tolerations: [] -affinity: {} + readinessProbe: + tcpSocket: + port: 8080 + initialDelaySeconds: 15 + timeoutSeconds: 2 + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 -env: - PHP_TIMEZONE: Europe/Madrid - PHP_MEMORYLIMIT: 512M - PHP_UPLOADLIMIT: 128M + nodeSelector: {} + tolerations: [] + affinity: {} + + debug: + enabled: false diff --git a/k8s/charts/helm-rustical/Chart.yaml b/k8s/charts/helm-rustical/Chart.yaml index f503b9a..57982f1 100644 --- a/k8s/charts/helm-rustical/Chart.yaml +++ b/k8s/charts/helm-rustical/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: helm-rustical description: A Helm chart for Rustical WebDAV server type: application -version: 0.3.0 +version: 1.1.1 appVersion: 0.14.1 annotations: artifacthub.io/images: "- name: rustical\n image: ghcr.io/lennart-k/rustical:0.14.1\n" diff --git a/k8s/charts/helm-rustical/README.md b/k8s/charts/helm-rustical/README.md new file mode 100644 index 0000000..a5843c6 --- /dev/null +++ b/k8s/charts/helm-rustical/README.md @@ -0,0 +1,115 @@ +# helm-rustical + +A Helm chart for Rustical WebDAV server. + +## TL;DR + +```bash +helm install helm-rustical oci://git.roboces.dev/catalin/fukuops/helm-rustical --version 1.1.0 -n apps-roboces +``` + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3+ +- A NFS storage class (default: `truenas-nfs-csi`) +- An existing `Secret` containing: + - `RUSTICAL_PASSWORD` - Password for WebDAV access + +## Configuration + +All values are nested under the `rustical:` key. Example: + +```yaml +rustical: + autoscaling: + enabled: true + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + service: + type: LoadBalancer + port: 8001 + config: + oidc.name: Authentik + oidc.issuer: https://auth.example.com/application/o/rustical/ + persistence: + enabled: true + storageClass: "truenas-nfs-csi" + size: 50Gi + secret: + existingSecretName: rustical +``` + +## Values Reference + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `rustical.replicaCount` | int | `1` | Number of replicas (when autoscaling disabled) | +| `rustical.image.repository` | string | `ghcr.io/lennart-k/rustical` | Image repository | +| `rustical.image.pullpolicy` | string | `Always` | Image pull policy | +| `rustical.image.tag` | string | `.Chart.AppVersion` | Image tag | +| `rustical.service.type` | string | `ClusterIP` | Service type | +| `rustical.service.port` | int | `4000` | Service port | +| `rustical.service.targetPort` | int | `4000` | Container port | +| `rustical.ingress.enabled` | bool | `false` | Enable ingress | +| `rustical.autoscaling.enabled` | bool | `false` | Enable HPA | +| `rustical.autoscaling.minReplicas` | int | `1` | Minimum replicas | +| `rustical.autoscaling.maxReplicas` | int | `3` | Maximum replicas | +| `rustical.autoscaling.targetCPUUtilizationPercentage` | int | `80` | Target CPU utilization | +| `rustical.autoscaling.targetMemoryUtilizationPercentage` | int | `80` | Target memory utilization | +| `rustical.persistence.enabled` | bool | `true` | Enable persistence | +| `rustical.persistence.storageClass` | string | `truenas-nfs-csi` | Storage class | +| `rustical.persistence.accessMode` | string | `ReadWriteMany` | Access mode | +| `rustical.persistence.size` | string | `50Gi` | PVC size | +| `rustical.secret.existingSecretName` | string | `rustical` | Existing secret name | +| `rustical.config` | object | `{}` | Config key-value pairs mapped to env vars | +| `rustical.debug.enabled` | bool | `false` | Enable debug mode (adds init container for troubleshooting) | +| `rustical.resources` | object | `{}` | Container resources | +| `rustical.livenessProbe` | object | (see values.yaml) | Liveness probe | +| `rustical.readinessProbe` | object | (see values.yaml) | Readiness probe | + +## ConfigMap + +The `config:` section creates a ConfigMap that maps keys to environment variables. Keys are uppercased. + +## Persistence + +The chart creates a PVC named `rustical-data`. The PVC uses the `truenas-nfs-csi` storage class by default with `ReadWriteMany` access mode. + +## Secret Management + +The chart looks for an existing `Secret` with the name specified in `rustical.secret.existingSecretName`. Required secret keys: +- `RUSTICAL_PASSWORD` - Password for WebDAV access + +## Troubleshooting + +### Debug Mode + +Enable debug mode to troubleshoot issues by adding an init container with a shell: + +```yaml +rustical: + debug: + enabled: true +``` + +This adds an `alpine:3.19` init container with `sleep infinity` that mounts all volumes and inherits env vars. You can exec into it to inspect the environment: + +```bash +kubectl exec -it -c debug -- sh +``` + +When debug mode is enabled, liveness and readiness probes are disabled to prevent restarts. + +### Pod not starting + +```bash +kubectl get pvc -n apps-roboces -l app.kubernetes.io/name=helm-rustical +``` + +### Check logs + +```bash +kubectl logs helm-rustical-0 -n apps-roboces +``` diff --git a/k8s/charts/helm-rustical/templates/configmap.yaml b/k8s/charts/helm-rustical/templates/configmap.yaml new file mode 100644 index 0000000..05aeb2c --- /dev/null +++ b/k8s/charts/helm-rustical/templates/configmap.yaml @@ -0,0 +1,13 @@ +{{- if .Values.rustical.config }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "rustical.fullname" . }}-config + labels: + {{- include "rustical.labels" . | nindent 4 }} +data: + {{- range $key, $value := .Values.rustical.config }} + {{- $envKey := printf "RUSTICAL_%s" ($key | upper | replace "." "__" | replace "-" "_") }} + {{ $envKey }}: {{ $value | quote }} + {{- end }} +{{- end }} diff --git a/k8s/charts/helm-rustical/templates/hpa.yaml b/k8s/charts/helm-rustical/templates/hpa.yaml new file mode 100644 index 0000000..75c69f8 --- /dev/null +++ b/k8s/charts/helm-rustical/templates/hpa.yaml @@ -0,0 +1,36 @@ +{{- if .Values.rustical.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "rustical.fullname" . }} + labels: + {{- include "rustical.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: StatefulSet + name: {{ include "rustical.fullname" . }} + {{- if .Values.rustical.autoscaling.minReplicas }} + minReplicas: {{ .Values.rustical.autoscaling.minReplicas }} + {{- end }} + {{- if .Values.rustical.autoscaling.maxReplicas }} + maxReplicas: {{ .Values.rustical.autoscaling.maxReplicas }} + {{- end }} + metrics: + {{- if .Values.rustical.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.rustical.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.rustical.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.rustical.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/k8s/charts/helm-rustical/templates/ingress.yaml b/k8s/charts/helm-rustical/templates/ingress.yaml index 5ee460e..e05e93d 100644 --- a/k8s/charts/helm-rustical/templates/ingress.yaml +++ b/k8s/charts/helm-rustical/templates/ingress.yaml @@ -1,23 +1,23 @@ -{{- if .Values.ingress.enabled -}} +{{- if .Values.rustical.ingress.enabled -}} {{- $fullName := include "rustical.fullname" . -}} -{{- $svcPort := .Values.service.port -}} +{{- $svcPort := .Values.rustical.service.port -}} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ $fullName }} labels: {{- include "rustical.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} + {{- with .Values.rustical.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: - {{- if .Values.ingress.className }} - ingressClassName: {{ .Values.ingress.className }} + {{- if .Values.rustical.ingress.className }} + ingressClassName: {{ .Values.rustical.ingress.className }} {{- end }} - {{- if .Values.ingress.tls }} + {{- if .Values.rustical.ingress.tls }} tls: - {{- range .Values.ingress.tls }} + {{- range .Values.rustical.ingress.tls }} - hosts: {{- range .hosts }} - {{ . | quote }} @@ -26,7 +26,7 @@ spec: {{- end }} {{- end }} rules: - {{- range .Values.ingress.hosts }} + {{- range .Values.rustical.ingress.hosts }} - host: {{ .host | quote }} http: paths: diff --git a/k8s/charts/helm-rustical/templates/service.yaml b/k8s/charts/helm-rustical/templates/service.yaml index 12b588c..0fc19bc 100644 --- a/k8s/charts/helm-rustical/templates/service.yaml +++ b/k8s/charts/helm-rustical/templates/service.yaml @@ -5,10 +5,10 @@ metadata: labels: {{- include "rustical.labels" . | nindent 4 }} spec: - type: {{ .Values.service.type }} + type: {{ .Values.rustical.service.type }} ports: - - port: {{ .Values.service.port }} - targetPort: {{ .Values.service.targetPort }} + - port: {{ .Values.rustical.service.port }} + targetPort: {{ .Values.rustical.service.targetPort }} protocol: TCP name: http selector: diff --git a/k8s/charts/helm-rustical/templates/statefulset.yaml b/k8s/charts/helm-rustical/templates/statefulset.yaml index ffd2302..07f9b07 100644 --- a/k8s/charts/helm-rustical/templates/statefulset.yaml +++ b/k8s/charts/helm-rustical/templates/statefulset.yaml @@ -6,40 +6,35 @@ metadata: {{- include "rustical.labels" . | nindent 4 }} spec: serviceName: {{ include "rustical.fullname" . }}-headless - {{- if not .Values.autoscaling.enabled }} - replicas: {{ .Values.replicaCount }} + {{- if not .Values.rustical.autoscaling.enabled }} + replicas: {{ .Values.rustical.replicaCount }} {{- end }} selector: matchLabels: {{- include "rustical.selectorLabels" . | nindent 6 }} template: metadata: - {{- with .Values.podAnnotations }} + {{- with .Values.rustical.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "rustical.selectorLabels" . | nindent 8 }} spec: - {{- with .Values.imagePullSecrets }} + {{- with .Values.rustical.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - {{- if .Values.debug.enabled }} + {{- toYaml .Values.rustical.podSecurityContext | nindent 8 }} + {{- if .Values.rustical.debug.enabled }} initContainers: - name: debug image: alpine:3.19 command: ["sleep", "infinity"] envFrom: - secretRef: - name: {{ .Values.secret.existingSecretName }} - env: - {{- range $key, $value := .Values.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} + name: {{ .Values.rustical.secret.existingSecretName }} volumeMounts: - name: data mountPath: /var/lib/rustical @@ -53,67 +48,64 @@ spec: containers: - name: {{ .Chart.Name }} securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullpolicy }} + {{- toYaml .Values.rustical.securityContext | nindent 12 }} + image: "{{ .Values.rustical.image.repository }}:{{ .Values.rustical.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.rustical.image.pullpolicy }} envFrom: + - configMapRef: + name: {{ include "rustical.fullname" . }}-config - secretRef: - name: {{ .Values.secret.existingSecretName | default (include "rustical.fullname" .) }} - env: - {{- range $key, $value := .Values.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} + name: {{ .Values.rustical.secret.existingSecretName | default (include "rustical.fullname" .) }} ports: - name: http containerPort: 4000 protocol: TCP - {{- if not .Values.debug.enabled }} + {{- if not .Values.rustical.debug.enabled }} livenessProbe: - {{- toYaml .Values.livenessProbe | nindent 12 }} + {{- toYaml .Values.rustical.livenessProbe | nindent 12 }} readinessProbe: - {{- toYaml .Values.readinessProbe | nindent 12 }} + {{- toYaml .Values.rustical.readinessProbe | nindent 12 }} {{- end }} resources: - {{- toYaml .Values.resources | nindent 12 }} + {{- toYaml .Values.rustical.resources | nindent 12 }} volumeMounts: - name: data mountPath: /var/lib/rustical - {{- if .Values.persistence.existingClaim }} + {{- if .Values.rustical.persistence.existingClaim }} volumes: - name: data persistentVolumeClaim: - claimName: {{ .Values.persistence.existingClaim }} + claimName: {{ .Values.rustical.persistence.existingClaim }} {{- end }} - {{- with .Values.nodeSelector }} + {{- with .Values.rustical.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.affinity }} + {{- with .Values.rustical.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.tolerations }} + {{- with .Values.rustical.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} restartPolicy: Always automountServiceAccountToken: false - {{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} + {{- if and .Values.rustical.persistence.enabled (not .Values.rustical.persistence.existingClaim) }} volumeClaimTemplates: - metadata: name: data spec: accessModes: - - {{ .Values.persistence.accessMode }} - {{- if .Values.persistence.storageClass }} - {{- if eq "-" .Values.persistence.storageClass }} + - {{ .Values.rustical.persistence.accessMode }} + {{- if .Values.rustical.persistence.storageClass }} + {{- if eq "-" .Values.rustical.persistence.storageClass }} storageClassName: "" {{- else }} - storageClassName: {{ .Values.persistence.storageClass | quote }} + storageClassName: {{ .Values.rustical.persistence.storageClass | quote }} {{- end }} {{- end }} resources: requests: - storage: {{ .Values.persistence.size }} + storage: {{ .Values.rustical.persistence.size }} {{- end }} diff --git a/k8s/charts/helm-rustical/values.yaml b/k8s/charts/helm-rustical/values.yaml index 05bb46f..08f4a1a 100644 --- a/k8s/charts/helm-rustical/values.yaml +++ b/k8s/charts/helm-rustical/values.yaml @@ -1,63 +1,67 @@ -# Configuration is managed in k8s/argo-apps/rustical.yaml -replicaCount: 1 +rustical: + replicaCount: 1 -image: - repository: ghcr.io/lennart-k/rustical - pullpolicy: Always - tag: "" + image: + repository: ghcr.io/lennart-k/rustical + pullpolicy: Always + tag: "" -imagePullSecrets: [] -podAnnotations: {} -podSecurityContext: {} -securityContext: {} + imagePullSecrets: [] + podAnnotations: {} + podSecurityContext: {} + securityContext: {} -service: - type: ClusterIP - port: 4000 - targetPort: 4000 - -ingress: - enabled: false - -persistence: - enabled: true - storageClass: "truenas-nfs-csi" - accessMode: ReadWriteMany - size: 50Gi - existingClaim: "" - -autoscaling: - enabled: false - -resources: {} - -livenessProbe: - tcpSocket: + service: + type: ClusterIP port: 4000 - initialDelaySeconds: 30 - timeoutSeconds: 15 - successThreshold: 1 - failureThreshold: 3 - periodSeconds: 10 + targetPort: 4000 -readinessProbe: - tcpSocket: - port: 4000 - initialDelaySeconds: 15 - timeoutSeconds: 2 - successThreshold: 1 - failureThreshold: 3 - periodSeconds: 10 + ingress: + enabled: false -nodeSelector: {} -tolerations: [] -affinity: {} + persistence: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 50Gi + existingClaim: "" -env: {} + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 -secret: - enabled: true - existingSecretName: "rustical" + resources: {} -debug: - enabled: false + livenessProbe: + tcpSocket: + port: 4000 + initialDelaySeconds: 30 + timeoutSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 + + readinessProbe: + tcpSocket: + port: 4000 + initialDelaySeconds: 15 + timeoutSeconds: 2 + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 + + nodeSelector: {} + tolerations: [] + affinity: {} + + config: {} + + secret: + enabled: true + existingSecretName: "rustical" + + debug: + enabled: false diff --git a/k8s/charts/immich/Chart.yaml b/k8s/charts/immich/Chart.yaml index 62986a0..6a5cceb 100644 --- a/k8s/charts/immich/Chart.yaml +++ b/k8s/charts/immich/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: immich description: A Helm chart for Immich photo and video backup solution type: application -version: 0.2.3 +version: 1.0.1 appVersion: v3.1.0 keywords: - immich diff --git a/k8s/charts/immich/README.md b/k8s/charts/immich/README.md new file mode 100644 index 0000000..2917057 --- /dev/null +++ b/k8s/charts/immich/README.md @@ -0,0 +1,192 @@ +# immich + +A Helm chart for Immich, a self-hosted photo and video backup solution. + +## TL;DR + +```bash +helm install immich oci://git.roboces.dev/catalin/fukuops/immich --version 1.0.0 -n apps-roboces +``` + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3+ +- A NFS storage class (default: `truenas-nfs-csi`) +- External PostgreSQL database +- External Redis instance + +## Configuration + +All values are nested under the `immich:` key. Example: + +```yaml +immich: + replicaCount: 1 + service: + type: ClusterIP + port: 2283 + config: + database: + hostname: "192.168.1.3" + port: 55432 + username: "" + password: "" + name: "" + redis: + hostname: "192.168.1.3" + port: 6379 + general: + timezone: Europe/Madrid + persistence: + library: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 10Gi + machineLearning: + enabled: false + secret: + enabled: false + existingSecretName: "" +``` + +## Values Reference + +### Main Application + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `immich.replicaCount` | int | `1` | Number of replicas | +| `immich.image.repository` | string | `ghcr.io/immich-app/immich-server` | Image repository | +| `immich.image.pullPolicy` | string | `Always` | Image pull policy | +| `immich.image.tag` | string | `.Chart.AppVersion` | Image tag | +| `immich.service.type` | string | `ClusterIP` | Service type | +| `immich.service.port` | int | `2283` | Service port | +| `immich.service.targetPort` | int | `2283` | Container port | +| `immich.ingress.enabled` | bool | `false` | Enable ingress | +| `immich.debug.enabled` | bool | `false` | Enable debug mode (adds init container for troubleshooting) | +| `immich.resources` | object | `{}` | Container resources | +| `immich.livenessProbe` | object | (see values.yaml) | Liveness probe | +| `immich.readinessProbe` | object | (see values.yaml) | Readiness probe | + +### Database Configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `immich.config.database.hostname` | string | `""` | External database hostname | +| `immich.config.database.port` | int | `55432` | Database port | +| `immich.config.database.username` | string | `""` | Database username | +| `immich.config.database.password` | string | `""` | Database password | +| `immich.config.database.name` | string | `""` | Database name | + +### Redis Configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `immich.config.redis.hostname` | string | `""` | External Redis hostname | +| `immich.config.redis.port` | int | `6379` | Redis port | +| `immich.config.redis.dbIndex` | string | `""` | Redis database index | + +### General Configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `immich.config.general.timezone` | string | `Europe/Madrid` | Timezone | + +### Persistence + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `immich.persistence.library.enabled` | bool | `true` | Enable library persistence | +| `immich.persistence.library.storageClass` | string | `truenas-nfs-csi` | Storage class | +| `immich.persistence.library.size` | string | `10Gi` | PVC size | +| `immich.persistence.encodedVideo.enabled` | bool | `true` | Enable encoded video persistence | +| `immich.persistence.thumbs.enabled` | bool | `true` | Enable thumbnails persistence | +| `immich.persistence.upload.enabled` | bool | `true` | Enable uploads persistence | +| `immich.persistence.profile.enabled` | bool | `true` | Enable profiles persistence | +| `immich.persistence.backups.enabled` | bool | `true` | Enable backups persistence | +| `immich.persistence.modelCache.enabled` | bool | `true` | Enable model cache persistence | + +### Machine Learning + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `immich.machineLearning.enabled` | bool | `false` | Enable machine learning module | +| `immich.machineLearning.image.repository` | string | `ghcr.io/immich-app/immich-machine-learning` | ML image repository | +| `immich.machineLearning.replicaCount` | int | `1` | Number of ML replicas | +| `immich.machineLearning.service.port` | int | `3003` | ML service port | +| `immich.machineLearning.persistence.modelCache.enabled` | bool | `true` | Enable model cache persistence | + +### Secret Management + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `immich.secret.enabled` | bool | `false` | Enable secret management | +| `immich.secret.existingSecretName` | string | `""` | Existing secret name | + +## ConfigMap + +The chart creates a ConfigMap with database and Redis environment variables: +- `TZ` +- `IMMICH_PORT` +- `IMMICH_MACHINE_LEARNING_URL` +- `DB_HOSTNAME`, `DB_PORT`, `DB_USERNAME`, `DB_DATABASE_NAME` +- `REDIS_HOSTNAME`, `REDIS_PORT`, `REDIS_DB` + +## Persistence + +The chart creates multiple PVCs: +- `immich-library` - Original photos and videos +- `immich-encoded-video` - Transcoded videos +- `immich-thumbs` - Generated thumbnails +- `immich-upload` - Upload queue +- `immich-profile` - User profiles +- `immich-backups` - Backup data +- `immich-model-cache` - ML model cache + +The library PVC uses `truenas-nfs-csi` storage class by default with `ReadWriteMany` access mode. + +## Troubleshooting + +### Debug Mode + +Enable debug mode to troubleshoot issues by adding an init container with a shell: + +```yaml +immich: + debug: + enabled: true +``` + +This adds an `alpine:3.19` init container with `sleep infinity` that mounts all volumes and inherits env vars. You can exec into it to inspect the environment: + +```bash +kubectl exec -it -c debug -- sh +``` + +When debug mode is enabled, liveness and readiness probes are disabled to prevent restarts. + +### Pod not starting + +```bash +kubectl get pvc -n apps-roboces -l app.kubernetes.io/name=immich +``` + +### Check logs + +```bash +kubectl logs immich-server-0 -n apps-roboces +``` + +### Check ML logs (if enabled) + +```bash +kubectl logs immich-machine-learning-0 -n apps-roboces +``` + +### Verify configmap + +```bash +kubectl get configmap immich-config -n apps-roboces -o yaml +``` diff --git a/k8s/charts/immich/templates/configmap.yaml b/k8s/charts/immich/templates/configmap.yaml index fc0f6b2..ec569c2 100644 --- a/k8s/charts/immich/templates/configmap.yaml +++ b/k8s/charts/immich/templates/configmap.yaml @@ -1,22 +1,31 @@ apiVersion: v1 kind: ConfigMap metadata: - name: {{ include "immich.fullname" . }} + name: {{ include "immich.fullname" . }}-config labels: {{- include "immich.labels" . | nindent 4 }} data: - TZ: {{ .Values.immich.general.tz | quote }} + TZ: {{ .Values.immich.config.general.timezone | quote }} IMMICH_PORT: {{ .Values.immich.service.port | quote }} IMMICH_MACHINE_LEARNING_URL: {{ printf "http://%s-machine-learning:3003" (include "immich.fullname" .) | quote }} - {{- range $key, $value := .Values.immich.db }} - {{- if and (ne $key "password") $value }} - {{ $keyName := printf "DB_%s" (upper $key) }} - {{ $keyName }}: {{ $value | quote }} + {{- if .Values.immich.config.database.hostname }} + DB_HOSTNAME: {{ .Values.immich.config.database.hostname | quote }} {{- end }} + {{- if .Values.immich.config.database.port }} + DB_PORT: {{ .Values.immich.config.database.port | quote }} {{- end }} - {{- range $key, $value := .Values.immich.redis }} - {{- if and (ne $key "password") (ne $key "username") $value }} - {{ $keyName := printf "REDIS_%s" (upper $key) }} - {{ $keyName }}: {{ $value | quote }} + {{- if .Values.immich.config.database.username }} + DB_USERNAME: {{ .Values.immich.config.database.username | quote }} {{- end }} + {{- if .Values.immich.config.database.name }} + DB_DATABASE_NAME: {{ .Values.immich.config.database.name | quote }} + {{- end }} + {{- if .Values.immich.config.redis.hostname }} + REDIS_HOSTNAME: {{ .Values.immich.config.redis.hostname | quote }} + {{- end }} + {{- if .Values.immich.config.redis.port }} + REDIS_PORT: {{ .Values.immich.config.redis.port | quote }} + {{- end }} + {{- if .Values.immich.config.redis.dbIndex }} + REDIS_DB: {{ .Values.immich.config.redis.dbIndex | quote }} {{- end }} diff --git a/k8s/charts/immich/templates/deployment.yaml b/k8s/charts/immich/templates/deployment.yaml index 789bde1..cec9562 100644 --- a/k8s/charts/immich/templates/deployment.yaml +++ b/k8s/charts/immich/templates/deployment.yaml @@ -62,6 +62,50 @@ spec: mountPath: /data/profile - name: backups mountPath: /data/backups + {{- if .Values.immich.debug.enabled }} + - name: debug + image: alpine:3.19 + command: ["sleep", "infinity"] + envFrom: + {{- if .Values.immich.secret.existingSecretName }} + - secretRef: + name: {{ .Values.immich.secret.existingSecretName }} + {{- else if .Values.immich.secret.enabled }} + - secretRef: + name: {{ include "immich.fullname" . }} + {{- end }} + - configMapRef: + name: {{ include "immich.fullname" . }}-config + volumeMounts: + - name: library + mountPath: /data/library + {{- if .Values.immich.persistence.encodedVideo.enabled }} + - name: encoded-video + mountPath: /data/encoded-video + {{- end }} + {{- if .Values.immich.persistence.thumbs.enabled }} + - name: thumbs + mountPath: /data/thumbs + {{- end }} + {{- if .Values.immich.persistence.upload.enabled }} + - name: upload + mountPath: /data/upload + {{- end }} + {{- if .Values.immich.persistence.profile.enabled }} + - name: profile + mountPath: /data/profile + {{- end }} + {{- if .Values.immich.persistence.backups.enabled }} + - name: backups + mountPath: /data/backups + {{- end }} + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + capabilities: + drop: + - all + {{- end }} containers: - name: {{ .Chart.Name }}-server securityContext: @@ -69,32 +113,30 @@ spec: image: "{{ .Values.immich.image.repository }}:{{ .Values.immich.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.immich.image.pullPolicy }} envFrom: - {{- if .Values.secret.existingSecretName }} + {{- if .Values.immich.secret.existingSecretName }} - secretRef: - name: {{ .Values.secret.existingSecretName }} - {{- else if .Values.secret.enabled }} + name: {{ .Values.immich.secret.existingSecretName }} + {{- else if .Values.immich.secret.enabled }} - secretRef: name: {{ include "immich.fullname" . }} {{- end }} - configMapRef: - name: {{ include "immich.fullname" . }} + name: {{ include "immich.fullname" . }}-config env: - name: IMMICH_PORT value: {{ .Values.immich.service.port | quote }} - name: IMMICH_MACHINE_LEARNING_URL value: {{ printf "http://%s-machine-learning:3003" (include "immich.fullname" .) | quote }} - {{- range $key, $value := .Values.immich.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} - ports: - - name: http - containerPort: 2283 - protocol: TCP + {{- if not .Values.immich.debug.enabled }} livenessProbe: {{- toYaml .Values.immich.livenessProbe | nindent 12 }} readinessProbe: {{- toYaml .Values.immich.readinessProbe | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: 2283 + protocol: TCP resources: {{- toYaml .Values.immich.resources | nindent 12 }} volumeMounts: diff --git a/k8s/charts/immich/templates/pvc.yaml b/k8s/charts/immich/templates/pvc.yaml index 1b8b7dc..5453117 100644 --- a/k8s/charts/immich/templates/pvc.yaml +++ b/k8s/charts/immich/templates/pvc.yaml @@ -106,7 +106,7 @@ spec: requests: storage: {{ .Values.immich.persistence.backups.size }} {{- end }} -{{- if and .Values.machineLearning.enabled .Values.machineLearning.persistence.modelCache.enabled }} +{{- if and .Values.immich.machineLearning.enabled .Values.immich.machineLearning.persistence.modelCache.enabled }} --- apiVersion: v1 kind: PersistentVolumeClaim @@ -116,11 +116,11 @@ metadata: {{- include "immich.labels" . | nindent 4 }} spec: accessModes: - - {{ .Values.machineLearning.persistence.modelCache.accessMode }} - {{- if .Values.machineLearning.persistence.modelCache.storageClass }} - storageClassName: {{ .Values.machineLearning.persistence.modelCache.storageClass | quote }} + - {{ .Values.immich.machineLearning.persistence.modelCache.accessMode }} + {{- if .Values.immich.machineLearning.persistence.modelCache.storageClass }} + storageClassName: {{ .Values.immich.machineLearning.persistence.modelCache.storageClass | quote }} {{- end }} resources: requests: - storage: {{ .Values.machineLearning.persistence.modelCache.size }} + storage: {{ .Values.immich.machineLearning.persistence.modelCache.size }} {{- end }} diff --git a/k8s/charts/immich/templates/secret.yaml b/k8s/charts/immich/templates/secret.yaml index d918746..2efa3fa 100644 --- a/k8s/charts/immich/templates/secret.yaml +++ b/k8s/charts/immich/templates/secret.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.secret.enabled (not .Values.secret.existingSecretName) -}} +{{- if and .Values.immich.secret.enabled (not .Values.immich.secret.existingSecretName) -}} apiVersion: v1 kind: Secret metadata: @@ -7,19 +7,19 @@ metadata: {{- include "immich.labels" . | nindent 4 }} type: Opaque stringData: - {{- if .Values.immich.db.password }} - DB_PASSWORD: {{ .Values.immich.db.password | quote }} + {{- if .Values.immich.config.database.password }} + DB_PASSWORD: {{ .Values.immich.config.database.password | quote }} {{- end }} - {{- if .Values.immich.db.username }} - DB_USERNAME: {{ .Values.immich.db.username | quote }} + {{- if .Values.immich.config.database.username }} + DB_USERNAME: {{ .Values.immich.config.database.username | quote }} {{- end }} - {{- if .Values.immich.redis.password }} - REDIS_PASSWORD: {{ .Values.immich.redis.password | quote }} + {{- if .Values.immich.config.redis.password }} + REDIS_PASSWORD: {{ .Values.immich.config.redis.password | quote }} {{- end }} - {{- if .Values.immich.redis.username }} - REDIS_USERNAME: {{ .Values.immich.redis.username | quote }} + {{- if .Values.immich.config.redis.username }} + REDIS_USERNAME: {{ .Values.immich.config.redis.username | quote }} {{- end }} - {{- range $key, $value := .Values.secret.data }} + {{- range $key, $value := .Values.immich.secret.data }} {{ $key }}: {{ $value | quote }} {{- end }} {{- end }} diff --git a/k8s/charts/immich/templates/service.yaml b/k8s/charts/immich/templates/service.yaml index e006a67..266ea37 100644 --- a/k8s/charts/immich/templates/service.yaml +++ b/k8s/charts/immich/templates/service.yaml @@ -15,7 +15,7 @@ spec: {{- include "immich.selectorLabels" . | nindent 4 }} app.kubernetes.io/component: server --- -{{- if .Values.machineLearning.enabled }} +{{- if .Values.immich.machineLearning.enabled }} apiVersion: v1 kind: Service metadata: @@ -24,9 +24,9 @@ metadata: {{- include "immich.labels" . | nindent 4 }} app.kubernetes.io/component: machine-learning spec: - type: {{ .Values.machineLearning.service.type }} + type: {{ .Values.immich.machineLearning.service.type }} ports: - - port: {{ .Values.machineLearning.service.port }} + - port: {{ .Values.immich.machineLearning.service.port }} targetPort: http protocol: TCP name: http diff --git a/k8s/charts/immich/templates/statefulset.yaml b/k8s/charts/immich/templates/statefulset.yaml index 9d0c4bd..3a59a6f 100644 --- a/k8s/charts/immich/templates/statefulset.yaml +++ b/k8s/charts/immich/templates/statefulset.yaml @@ -1,4 +1,4 @@ -{{- if .Values.machineLearning.enabled -}} +{{- if .Values.immich.machineLearning.enabled -}} apiVersion: apps/v1 kind: StatefulSet metadata: @@ -8,8 +8,8 @@ metadata: app.kubernetes.io/component: machine-learning spec: serviceName: {{ include "immich.fullname" . }}-machine-learning-headless - {{- if not .Values.machineLearning.autoscaling.enabled }} - replicas: {{ .Values.machineLearning.replicaCount }} + {{- if not .Values.immich.machineLearning.autoscaling.enabled }} + replicas: {{ .Values.immich.machineLearning.replicaCount }} {{- end }} selector: matchLabels: @@ -17,7 +17,7 @@ spec: app.kubernetes.io/component: machine-learning template: metadata: - {{- with .Values.machineLearning.podAnnotations }} + {{- with .Values.immich.machineLearning.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} @@ -25,41 +25,41 @@ spec: {{- include "immich.selectorLabels" . | nindent 8 }} app.kubernetes.io/component: machine-learning spec: - {{- with .Values.machineLearning.imagePullSecrets }} + {{- with .Values.immich.machineLearning.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} securityContext: - {{- toYaml .Values.machineLearning.podSecurityContext | nindent 8 }} + {{- toYaml .Values.immich.machineLearning.podSecurityContext | nindent 8 }} containers: - name: {{ .Chart.Name }}-machine-learning securityContext: - {{- toYaml .Values.machineLearning.securityContext | nindent 12 }} - image: "{{ .Values.machineLearning.image.repository }}:{{ .Values.machineLearning.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.machineLearning.image.pullPolicy }} + {{- toYaml .Values.immich.machineLearning.securityContext | nindent 12 }} + image: "{{ .Values.immich.machineLearning.image.repository }}:{{ .Values.immich.machineLearning.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.immich.machineLearning.image.pullPolicy }} ports: - name: http containerPort: 3003 protocol: TCP env: - name: IMMICH_PORT - value: {{ .Values.machineLearning.service.port | quote }} + value: {{ .Values.immich.machineLearning.service.port | quote }} - name: IMMICH_MACHINE_LEARNING_PORT value: "3003" resources: - {{- toYaml .Values.machineLearning.resources | nindent 12 }} + {{- toYaml .Values.immich.machineLearning.resources | nindent 12 }} volumeMounts: - name: model-cache mountPath: /cache - {{- with .Values.machineLearning.nodeSelector }} + {{- with .Values.immich.machineLearning.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.machineLearning.affinity }} + {{- with .Values.immich.machineLearning.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.machineLearning.tolerations }} + {{- with .Values.immich.machineLearning.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} diff --git a/k8s/charts/immich/values.yaml b/k8s/charts/immich/values.yaml index 00667ac..b311baa 100644 --- a/k8s/charts/immich/values.yaml +++ b/k8s/charts/immich/values.yaml @@ -19,22 +19,21 @@ immich: 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" + config: + database: + hostname: "" + port: 55432 + username: "" + password: "" + name: "" + redis: + hostname: "" + port: 6379 + username: "" + password: "" + dbIndex: "" + general: + timezone: Europe/Madrid persistence: library: @@ -104,66 +103,67 @@ immich: tolerations: [] affinity: {} - env: {} - -machineLearning: - enabled: false - - image: - repository: ghcr.io/immich-app/immich-machine-learning - pullPolicy: Always - tag: "" - - imagePullSecrets: [] - podAnnotations: {} - podSecurityContext: {} - securityContext: {} - - service: - type: ClusterIP - port: 3003 - - replicaCount: 1 - - autoscaling: + machineLearning: enabled: false - minReplicas: 1 - maxReplicas: 3 - targetCPUUtilizationPercentage: 80 - targetMemoryUtilizationPercentage: 80 - resources: {} + image: + repository: ghcr.io/immich-app/immich-machine-learning + pullPolicy: Always + tag: "" - livenessProbe: - tcpSocket: + imagePullSecrets: [] + podAnnotations: {} + podSecurityContext: {} + securityContext: {} + + service: + type: ClusterIP port: 3003 - initialDelaySeconds: 30 - timeoutSeconds: 15 - successThreshold: 1 - failureThreshold: 3 - periodSeconds: 10 - readinessProbe: - tcpSocket: - port: 3003 - initialDelaySeconds: 15 - timeoutSeconds: 2 - successThreshold: 1 - failureThreshold: 3 - periodSeconds: 10 + replicaCount: 1 - nodeSelector: {} - tolerations: [] - affinity: {} + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 - persistence: - modelCache: - enabled: true - storageClass: "" - accessMode: ReadWriteMany - size: 10Gi + resources: {} -secret: - enabled: false - existingSecretName: "" - data: {} + livenessProbe: + tcpSocket: + port: 3003 + initialDelaySeconds: 30 + timeoutSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 + + readinessProbe: + tcpSocket: + port: 3003 + initialDelaySeconds: 15 + timeoutSeconds: 2 + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 + + nodeSelector: {} + tolerations: [] + affinity: {} + + persistence: + modelCache: + enabled: true + storageClass: "" + accessMode: ReadWriteMany + size: 10Gi + + secret: + enabled: false + existingSecretName: "" + data: {} + + debug: + enabled: false diff --git a/k8s/charts/miniflux/Chart.yaml b/k8s/charts/miniflux/Chart.yaml index 8fd1a74..47e4070 100644 --- a/k8s/charts/miniflux/Chart.yaml +++ b/k8s/charts/miniflux/Chart.yaml @@ -2,9 +2,7 @@ apiVersion: v2 name: miniflux description: A Helm chart for Miniflux RSS reader type: application -version: 0.1.0 +version: 1.0.2 appVersion: 2.3.3 annotations: - artifacthub.io/images: | - - name: miniflux - image: miniflux/miniflux:2.3.3 + artifacthub.io/images: "- name: miniflux\n image: miniflux/miniflux:2.3.3\n" diff --git a/k8s/charts/miniflux/README.md b/k8s/charts/miniflux/README.md new file mode 100644 index 0000000..757e99c --- /dev/null +++ b/k8s/charts/miniflux/README.md @@ -0,0 +1,153 @@ +# miniflux + +A Helm chart for Miniflux, a minimalist and open source RSS reader. + +## TL;DR + +```bash +helm install miniflux oci://git.roboces.dev/catalin/fukuops/miniflux --version 1.0.1 -n apps-roboces +``` + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3+ +- An existing `Secret` containing: + - `DATABASE_URL` - PostgreSQL connection string + - `ADMIN_USERNAME` - Admin username (optional if oauth2 is configured) + - `ADMIN_PASSWORD` - Admin password (optional if oauth2 is configured) + +## Configuration + +All values are nested under the `miniflux:` key. Example: + +```yaml +miniflux: + service: + type: LoadBalancer + config: + database: + url: "postgres://user:pass@host:5432/miniflux" + general: + createAdmin: true + runMigrations: true + workerPoolSize: 1 + pollingFrequency: 60 + oauth2: + enabled: true + provider: oidc + redirectUrl: https://feeds.example.com/oauth2/oidc/callback + oidcDiscoveryEndpoint: https://auth.example.com/application/o/miniflux/ + userCreation: true + metrics: + enabled: true + allowedNetworks: 10.0.0.0/8 + secret: + existingSecretName: miniflux +``` + +## Values Reference + +### Main Settings + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `miniflux.replicaCount` | int | `1` | Number of replicas | +| `miniflux.image.repository` | string | `miniflux/miniflux` | Image repository | +| `miniflux.image.pullpolicy` | string | `always` | Image pull policy | +| `miniflux.image.tag` | string | `.Chart.AppVersion` | Image tag | +| `miniflux.service.type` | string | `ClusterIP` | Service type | +| `miniflux.service.port` | int | `8888` | Service port | +| `miniflux.service.targetPort` | int | `8080` | Container port | +| `miniflux.ingress.enabled` | bool | `false` | Enable ingress | +| `miniflux.resources` | object | `{}` | Container resources | +| `miniflux.livenessProbe` | object | `{}` | Liveness probe | +| `miniflux.readinessProbe` | object | `{}` | Readiness probe | +| `miniflux.secret.existingSecretName` | string | `""` | Existing secret name | +| `miniflux.debug.enabled` | bool | `false` | Enable debug mode (adds init container for troubleshooting) | +| `miniflux.nodeSelector` | object | `{}` | Node selector | +| `miniflux.tolerations` | array | `[]` | Tolerations | +| `miniflux.affinity` | object | `{}` | Affinity rules | +| `miniflux.autoscaling.enabled` | bool | `false` | Enable autoscaling | +| `miniflux.autoscaling.minReplicas` | int | `1` | Minimum replicas | +| `miniflux.autoscaling.maxReplicas` | int | `3` | Maximum replicas | +| `miniflux.autoscaling.targetCPUUtilizationPercentage` | int | `80` | Target CPU utilization for HPA | +| `miniflux.autoscaling.targetMemoryUtilizationPercentage` | int | `80` | Target memory utilization for HPA | + +### Database Configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `miniflux.config.database.url` | string | `""` | PostgreSQL connection URL | + +### General Configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `miniflux.config.general.createAdmin` | bool | `false` | Create admin user on startup | +| `miniflux.config.general.runMigrations` | bool | `false` | Run database migrations | +| `miniflux.config.general.workerPoolSize` | int | `1` | Number of worker threads | +| `miniflux.config.general.pollingFrequency` | int | `60` | Feed refresh interval (minutes) | +| `miniflux.config.general.batchSize` | int | `10` | Number of feeds to refresh per batch | + +### OAuth2 Configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `miniflux.config.oauth2.enabled` | bool | `false` | Enable OAuth2 authentication | +| `miniflux.config.oauth2.provider` | string | `oidc` | OAuth2 provider | +| `miniflux.config.oauth2.redirectUrl` | string | `""` | OAuth redirect URL | +| `miniflux.config.oauth2.oidcDiscoveryEndpoint` | string | `""` | OIDC discovery endpoint URL | +| `miniflux.config.oauth2.userCreation` | bool | `false` | Allow OAuth users to create accounts | + +### Metrics Configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `miniflux.config.metrics.enabled` | bool | `false` | Enable metrics endpoint | +| `miniflux.config.metrics.allowedNetworks` | string | `""` | Networks allowed to access metrics (CIDR) | + +### YouTube Configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `miniflux.config.youtube.fetchWatchTime` | bool | `false` | Fetch YouTube watch time data | + +## Secret Management + +The chart looks for an existing `Secret` with the name specified in `miniflux.secret.existingSecretName`. Required secret keys: +- `DATABASE_URL` - PostgreSQL connection string +- `ADMIN_USERNAME` - Admin username (if `config.general.createAdmin=true`) +- `ADMIN_PASSWORD` - Admin password (if `config.general.createAdmin=true`) + +## Troubleshooting + +### Debug Mode + +Enable debug mode to troubleshoot issues by adding an init container with a shell: + +```yaml +miniflux: + debug: + enabled: true +``` + +This adds an `alpine:3.19` init container with `sleep infinity` that mounts all volumes and inherits env vars. You can exec into it to inspect the environment: + +```bash +kubectl exec -it -c debug -- sh +``` + +When debug mode is enabled, liveness and readiness probes are disabled to prevent restarts. + +### Check logs + +```bash +kubectl logs miniflux-0 -n apps-roboces +``` + +### Verify configmap + +```bash +kubectl get configmap miniflux-config -n apps-roboces -o yaml +``` diff --git a/k8s/charts/miniflux/templates/configmap.yaml b/k8s/charts/miniflux/templates/configmap.yaml new file mode 100644 index 0000000..f970856 --- /dev/null +++ b/k8s/charts/miniflux/templates/configmap.yaml @@ -0,0 +1,40 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "miniflux.fullname" . }}-config + labels: + {{- include "miniflux.labels" . | nindent 4 }} +data: + {{- if .Values.miniflux.config.database.url }} + DATABASE_URL: {{ .Values.miniflux.config.database.url | quote }} + {{- end }} + {{- if .Values.miniflux.config.general.runMigrations }} + RUN_MIGRATIONS: {{ .Values.miniflux.config.general.runMigrations | quote }} + {{- end }} + {{- if .Values.miniflux.config.general.createAdmin }} + CREATE_ADMIN: {{ .Values.miniflux.config.general.createAdmin | quote }} + {{- end }} + WORKER_POOL_SIZE: {{ .Values.miniflux.config.general.workerPoolSize | quote }} + POLLING_FREQUENCY: {{ .Values.miniflux.config.general.pollingFrequency | quote }} + BATCH_SIZE: {{ .Values.miniflux.config.general.batchSize | quote }} + {{- if .Values.miniflux.config.oauth2.enabled }} + OAUTH2_PROVIDER: {{ .Values.miniflux.config.oauth2.provider | quote }} + {{- if .Values.miniflux.config.oauth2.redirectUrl }} + OAUTH2_REDIRECT_URL: {{ .Values.miniflux.config.oauth2.redirectUrl | quote }} + {{- end }} + {{- if .Values.miniflux.config.oauth2.oidcDiscoveryEndpoint }} + OAUTH2_OIDC_DISCOVERY_ENDPOINT: {{ .Values.miniflux.config.oauth2.oidcDiscoveryEndpoint | quote }} + {{- end }} + {{- if .Values.miniflux.config.oauth2.userCreation }} + OAUTH2_USER_CREATION: {{ .Values.miniflux.config.oauth2.userCreation | quote }} + {{- end }} + {{- end }} + {{- if .Values.miniflux.config.metrics.enabled }} + METRICS_COLLECTOR: {{ .Values.miniflux.config.metrics.enabled | quote }} + {{- if .Values.miniflux.config.metrics.allowedNetworks }} + METRICS_ALLOWED_NETWORKS: {{ .Values.miniflux.config.metrics.allowedNetworks | quote }} + {{- end }} + {{- end }} + {{- if .Values.miniflux.config.youtube.fetchWatchTime }} + FETCH_YOUTUBE_WATCH_TIME: {{ .Values.miniflux.config.youtube.fetchWatchTime | quote }} + {{- end }} diff --git a/k8s/charts/miniflux/templates/deployment.yaml b/k8s/charts/miniflux/templates/deployment.yaml index f7091f8..a6e3858 100644 --- a/k8s/charts/miniflux/templates/deployment.yaml +++ b/k8s/charts/miniflux/templates/deployment.yaml @@ -7,8 +7,8 @@ metadata: annotations: kube-score/ignore: pod-networkpolicy,deployment-has-host-podantiaffinity spec: - {{- if not .Values.autoscaling.enabled }} - replicas: {{ .Values.replicaCount }} + {{- if not .Values.miniflux.autoscaling.enabled }} + replicas: {{ .Values.miniflux.replicaCount }} {{- end }} selector: matchLabels: @@ -20,52 +20,49 @@ spec: type: RollingUpdate template: metadata: - {{- with .Values.podAnnotations }} + {{- with .Values.miniflux.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "miniflux.selectorLabels" . | nindent 8 }} spec: - {{- with .Values.imagePullSecrets }} + {{- with .Values.miniflux.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- toYaml .Values.miniflux.podSecurityContext | nindent 8 }} containers: - name: {{ .Chart.Name }} securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- toYaml .Values.miniflux.securityContext | nindent 12 }} + image: "{{ .Values.miniflux.image.repository }}:{{ .Values.miniflux.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.miniflux.image.pullPolicy }} envFrom: + - configMapRef: + name: {{ include "miniflux.fullname" . }}-config - secretRef: - name: {{ .Values.secret.existingSecretName | default (include "miniflux.fullname" .) }} - env: - {{- range $key, $value := .Values.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} + name: {{ .Values.miniflux.secret.existingSecretName | default (include "miniflux.fullname" .) }} ports: - name: http containerPort: 8080 protocol: TCP livenessProbe: - {{- toYaml .Values.livenessProbe | nindent 12 }} + {{- toYaml .Values.miniflux.livenessProbe | nindent 12 }} readinessProbe: - {{- toYaml .Values.readinessProbe | nindent 12 }} + {{- toYaml .Values.miniflux.readinessProbe | nindent 12 }} resources: - {{- toYaml .Values.resources | nindent 12 }} - {{- with .Values.nodeSelector }} + {{- toYaml .Values.miniflux.resources | nindent 12 }} + {{- with .Values.miniflux.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.affinity }} + {{- with .Values.miniflux.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.tolerations }} + {{- with .Values.miniflux.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} diff --git a/k8s/charts/miniflux/templates/hpa.yaml b/k8s/charts/miniflux/templates/hpa.yaml new file mode 100644 index 0000000..49f2458 --- /dev/null +++ b/k8s/charts/miniflux/templates/hpa.yaml @@ -0,0 +1,33 @@ +{{- if .Values.miniflux.autoscaling.enabled }} +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "miniflux.fullname" . }} + labels: + {{- include "miniflux.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "miniflux.fullname" . }} + minReplicas: {{ .Values.miniflux.autoscaling.minReplicas }} + maxReplicas: {{ .Values.miniflux.autoscaling.maxReplicas }} + metrics: + {{- if .Values.miniflux.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.miniflux.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.miniflux.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.miniflux.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/k8s/charts/miniflux/templates/ingress.yaml b/k8s/charts/miniflux/templates/ingress.yaml index db3f090..9b07318 100644 --- a/k8s/charts/miniflux/templates/ingress.yaml +++ b/k8s/charts/miniflux/templates/ingress.yaml @@ -1,23 +1,23 @@ -{{- if .Values.ingress.enabled -}} +{{- if .Values.miniflux.ingress.enabled -}} {{- $fullName := include "miniflux.fullname" . -}} -{{- $svcPort := .Values.service.port -}} +{{- $svcPort := .Values.miniflux.service.port -}} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ $fullName }} labels: {{- include "miniflux.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} + {{- with .Values.miniflux.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: - {{- if .Values.ingress.className }} - ingressClassName: {{ .Values.ingress.className }} + {{- if .Values.miniflux.ingress.className }} + ingressClassName: {{ .Values.miniflux.ingress.className }} {{- end }} - {{- if .Values.ingress.tls }} + {{- if .Values.miniflux.ingress.tls }} tls: - {{- range .Values.ingress.tls }} + {{- range .Values.miniflux.ingress.tls }} - hosts: {{- range .hosts }} - {{ . | quote }} @@ -26,7 +26,7 @@ spec: {{- end }} {{- end }} rules: - {{- range .Values.ingress.hosts }} + {{- range .Values.miniflux.ingress.hosts }} - host: {{ .host | quote }} http: paths: diff --git a/k8s/charts/miniflux/templates/poddisruptionbudget.yaml b/k8s/charts/miniflux/templates/poddisruptionbudget.yaml index 59e31ee..c1d1401 100644 --- a/k8s/charts/miniflux/templates/poddisruptionbudget.yaml +++ b/k8s/charts/miniflux/templates/poddisruptionbudget.yaml @@ -1,4 +1,4 @@ -{{- if .Values.podDisruptionBudget.enabled -}} +{{- if .Values.miniflux.podDisruptionBudget.enabled -}} apiVersion: policy/v1 kind: PodDisruptionBudget metadata: @@ -6,11 +6,11 @@ metadata: labels: {{- include "miniflux.labels" . | nindent 4 }} spec: - {{- if .Values.podDisruptionBudget.minAvailable }} - minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- if .Values.miniflux.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.miniflux.podDisruptionBudget.minAvailable }} {{- end }} - {{- if .Values.podDisruptionBudget.maxUnavailable }} - maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- if .Values.miniflux.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ .Values.miniflux.podDisruptionBudget.maxUnavailable }} {{- end }} selector: matchLabels: diff --git a/k8s/charts/miniflux/templates/secret.yaml b/k8s/charts/miniflux/templates/secret.yaml index 91c8d4f..c687780 100644 --- a/k8s/charts/miniflux/templates/secret.yaml +++ b/k8s/charts/miniflux/templates/secret.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.secret.enabled (not .Values.secret.existingSecretName) -}} +{{- if and .Values.miniflux.secret.enabled (not .Values.miniflux.secret.existingSecretName) -}} apiVersion: v1 kind: Secret metadata: @@ -7,7 +7,7 @@ metadata: {{- include "miniflux.labels" . | nindent 4 }} type: Opaque stringData: - {{- range $key, $value := .Values.secret.data }} + {{- range $key, $value := .Values.miniflux.secret.data }} {{ $key }}: {{ $value | quote }} {{- end }} {{- end }} diff --git a/k8s/charts/miniflux/templates/service.yaml b/k8s/charts/miniflux/templates/service.yaml index c70a4ab..2dde8d3 100644 --- a/k8s/charts/miniflux/templates/service.yaml +++ b/k8s/charts/miniflux/templates/service.yaml @@ -5,10 +5,10 @@ metadata: labels: {{- include "miniflux.labels" . | nindent 4 }} spec: - type: {{ .Values.service.type }} + type: {{ .Values.miniflux.service.type }} ports: - - port: {{ .Values.service.port }} - targetPort: {{ .Values.service.targetPort }} + - port: {{ .Values.miniflux.service.port }} + targetPort: {{ .Values.miniflux.service.targetPort }} protocol: TCP name: http selector: diff --git a/k8s/charts/miniflux/values.yaml b/k8s/charts/miniflux/values.yaml index 256da47..c01ce81 100644 --- a/k8s/charts/miniflux/values.yaml +++ b/k8s/charts/miniflux/values.yaml @@ -1,42 +1,68 @@ -# Configuration is managed in k8s/argo-apps/miniflux.yaml -replicaCount: 1 +miniflux: + replicaCount: 1 -image: - repository: miniflux/miniflux - pullpolicy: always - tag: "" + image: + repository: miniflux/miniflux + pullpolicy: always + tag: "" -imagePullSecrets: [] -podAnnotations: {} -podSecurityContext: {} -securityContext: {} + imagePullSecrets: [] + podAnnotations: {} + podSecurityContext: {} + securityContext: {} -service: - type: ClusterIP - port: 8888 - targetPort: 8080 + service: + type: ClusterIP + port: 8888 + targetPort: 8080 -ingress: - enabled: false + ingress: + enabled: false -resources: {} + config: + database: + url: "" + general: + createAdmin: false + runMigrations: false + workerPoolSize: 1 + pollingFrequency: 60 + batchSize: 10 + oauth2: + enabled: false + provider: oidc + redirectUrl: "" + oidcDiscoveryEndpoint: "" + userCreation: false + metrics: + enabled: false + allowedNetworks: "" + youtube: + fetchWatchTime: false -livenessProbe: {} -readinessProbe: {} + resources: {} -autoscaling: - enabled: false + livenessProbe: {} + readinessProbe: {} -nodeSelector: {} -tolerations: [] -affinity: {} + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 -podDisruptionBudget: - enabled: false + nodeSelector: {} + tolerations: [] + affinity: {} -env: {} + podDisruptionBudget: + enabled: false -secret: - enabled: false - existingSecretName: "" - data: {} + secret: + enabled: false + existingSecretName: "" + data: {} + + debug: + enabled: false diff --git a/k8s/charts/oxicloud/Chart.yaml b/k8s/charts/oxicloud/Chart.yaml index 738f76d..baaa367 100644 --- a/k8s/charts/oxicloud/Chart.yaml +++ b/k8s/charts/oxicloud/Chart.yaml @@ -5,7 +5,7 @@ description: 'Ultra-fast, secure & lightweight self-hosted cloud storage — you ' type: application -version: 1.0.0 +version: 1.0.1 appVersion: 0.8.6 annotations: artifacthub.io/images: "- name: oxicloud\n image: diocrafts/oxicloud:0.8.6\n" diff --git a/k8s/charts/oxicloud/README.md b/k8s/charts/oxicloud/README.md index bcb9dae..f09fa22 100644 --- a/k8s/charts/oxicloud/README.md +++ b/k8s/charts/oxicloud/README.md @@ -73,6 +73,7 @@ oxicloud: | `oxicloud.persistence.size` | string | `50Gi` | PVC size | | `oxicloud.secret.existingSecretName` | string | `""` | Existing secret name | | `oxicloud.wopi.enabled` | bool | `false` | Enable WOPI/Collabora integration | +| `oxicloud.debug.enabled` | bool | `false` | Enable debug mode (adds init container for troubleshooting) | | `oxicloud.ingress.enabled` | bool | `false` | Enable ingress | | `oxicloud.ingress.className` | string | `""` | Ingress class | | `oxicloud.resources` | object | `{}` | Container resources | @@ -115,6 +116,24 @@ Required secret keys depend on configuration: ## Troubleshooting +### Debug Mode + +Enable debug mode to troubleshoot issues by adding an init container with a shell: + +```yaml +oxicloud: + debug: + enabled: true +``` + +This adds an `alpine:3.19` init container with `sleep infinity` that mounts all volumes and inherits env vars. You can exec into it to inspect the environment: + +```bash +kubectl exec -it -c debug -- sh +``` + +When debug mode is enabled, liveness and readiness probes are disabled to prevent restarts. + ### Pod not starting Check if the PVC is bound: diff --git a/k8s/charts/oxicloud/templates/ingress.yaml b/k8s/charts/oxicloud/templates/ingress.yaml index d2a2ff2..8783f64 100644 --- a/k8s/charts/oxicloud/templates/ingress.yaml +++ b/k8s/charts/oxicloud/templates/ingress.yaml @@ -1,4 +1,3 @@ ---- {{- if .Values.oxicloud.ingress.enabled -}} apiVersion: networking.k8s.io/v1 kind: Ingress @@ -51,7 +50,6 @@ spec: port: number: {{ .Values.oxicloud.wopi.collabora.service.port }} {{- end }} - - path: / pathType: Prefix backend: diff --git a/k8s/charts/oxicloud/values.yaml b/k8s/charts/oxicloud/values.yaml index 0f69bc1..1348f56 100644 --- a/k8s/charts/oxicloud/values.yaml +++ b/k8s/charts/oxicloud/values.yaml @@ -85,3 +85,6 @@ oxicloud: enabled: true existingSecretName: "oxicloud-secret" data: {} + + debug: + enabled: false diff --git a/k8s/charts/vaultwarden/Chart.yaml b/k8s/charts/vaultwarden/Chart.yaml index 00187e6..3d3255d 100644 --- a/k8s/charts/vaultwarden/Chart.yaml +++ b/k8s/charts/vaultwarden/Chart.yaml @@ -2,9 +2,7 @@ apiVersion: v2 name: vaultwarden description: A Helm chart for Vaultwarden password manager type: application -version: 0.1.0 +version: 1.0.2 appVersion: 1.37.1 annotations: - artifacthub.io/images: | - - name: vaultwarden - image: vaultwarden/server:1.37.1-alpine + artifacthub.io/images: "- name: vaultwarden\n image: vaultwarden/server:1.37.1-alpine\n" diff --git a/k8s/charts/vaultwarden/README.md b/k8s/charts/vaultwarden/README.md new file mode 100644 index 0000000..cda5443 --- /dev/null +++ b/k8s/charts/vaultwarden/README.md @@ -0,0 +1,132 @@ +# vaultwarden + +A Helm chart for Vaultwarden (formerly Bitwarden RS), a lightweight self-hosted password manager. + +## TL;DR + +```bash +helm install vaultwarden oci://git.roboces.dev/catalin/fukuops/vaultwarden --version 1.0.2 -n apps-roboces +``` + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3+ +- A NFS storage class (default: `truenas-nfs-csi`) +- An existing `Secret` containing: + - `DATABASE_URL` (if using external database) + - `ADMIN_TOKEN` (optional - for admin panel) + - `SMTP_HOST` and related SMTP settings (optional - for email) + +## Configuration + +All values are nested under the `vaultwarden:` key. Example: + +```yaml +vaultwarden: + persistence: + enabled: true + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 5Gi + service: + type: ClusterIP + port: 80 + secret: + existingSecretName: vaultwarden-kubernetes-secrets + config: + database: + url: "" + enableWal: true + connectionRetries: 15 + webVault: + enabled: true +``` + +## Values Reference + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `vaultwarden.replicaCount` | int | `1` | Number of replicas | +| `vaultwarden.image.repository` | string | `vaultwarden/server` | Image repository | +| `vaultwarden.image.pullpolicy` | string | `Always` | Image pull policy | +| `vaultwarden.image.tag` | string | `.Chart.AppVersion` | Image tag | +| `vaultwarden.service.type` | string | `ClusterIP` | Service type | +| `vaultwarden.service.port` | int | `80` | Service port | +| `vaultwarden.service.targetPort` | int | `80` | Container port | +| `vaultwarden.ingress.enabled` | bool | `false` | Enable ingress | +| `vaultwarden.config.database.url` | string | `""` | Database connection URL | +| `vaultwarden.config.database.enableWal` | bool | `true` | Enable WAL mode | +| `vaultwarden.config.database.connectionRetries` | int | `15` | Connection retries | +| `vaultwarden.config.database.timeout` | int | `30` | Connection timeout | +| `vaultwarden.config.database.idleTimeout` | int | `600` | Idle timeout | +| `vaultwarden.config.webVault.enabled` | bool | `true` | Enable web vault | +| `vaultwarden.persistence.enabled` | bool | `true` | Enable persistence | +| `vaultwarden.persistence.storageClass` | string | `truenas-nfs-csi` | Storage class | +| `vaultwarden.persistence.accessMode` | string | `ReadWriteMany` | Access mode | +| `vaultwarden.persistence.size` | string | `5Gi` | PVC size | +| `vaultwarden.secret.existingSecretName` | string | `vaultwarden-kubernetes-secrets` | Existing secret name | +| `vaultwarden.debug.enabled` | bool | `false` | Enable debug mode (adds init container for troubleshooting) | +| `vaultwarden.resources` | object | `{}` | Container resources | +| `vaultwarden.livenessProbe` | object | (see values.yaml) | Liveness probe | +| `vaultwarden.readinessProbe` | object | (see values.yaml) | Readiness probe | + +## ConfigMap + +The chart creates a ConfigMap with database and web vault environment variables: +- `DATABASE_URL` +- `DB_ENABLE_WAL` +- `DB_CONNECTION_RETRIES` +- `DB_TIMEOUT` +- `DB_IDLE_TIMEOUT` +- `WEB_VAULT_ENABLED` +- `WEB_VAULT_FOLDER` + +## Persistence + +The chart creates a PVC named `vaultwarden-data`. The PVC uses the `truenas-nfs-csi` storage class by default with `ReadWriteMany` access mode. + +## Secret Management + +The chart looks for an existing `Secret` with the name specified in `vaultwarden.secret.existingSecretName`. Required/optional secret keys depend on configuration: +- `DATABASE_URL` - External database connection (if not using built-in SQLite) +- `ADMIN_TOKEN` - Admin panel access token +- `SMTP_HOST`, `SMTP_PORT`, `SMTP_FROM`, `SMTP_USER`, `SMTP_PASS` - Email configuration + +## Troubleshooting + +### Debug Mode + +Enable debug mode to troubleshoot issues by adding an init container with a shell: + +```yaml +vaultwarden: + debug: + enabled: true +``` + +This adds an `alpine:3.19` init container with `sleep infinity` that mounts all volumes and inherits env vars. You can exec into it to inspect the environment: + +```bash +kubectl exec -it -c debug -- sh +``` + +When debug mode is enabled, liveness and readiness probes are disabled to prevent restarts. + +### Pod not starting + +```bash +kubectl get pvc -n apps-roboces -l app.kubernetes.io/name=vaultwarden +``` + +### Check logs + +```bash +kubectl logs vaultwarden-0 -n apps-roboces +``` + +### Verify configmap + +```bash +kubectl get configmap vaultwarden-config -n apps-roboces -o yaml +``` diff --git a/k8s/charts/vaultwarden/templates/configmap.yaml b/k8s/charts/vaultwarden/templates/configmap.yaml new file mode 100644 index 0000000..f765420 --- /dev/null +++ b/k8s/charts/vaultwarden/templates/configmap.yaml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "vaultwarden.fullname" . }}-config + labels: + {{- include "vaultwarden.labels" . | nindent 4 }} +data: + {{- if .Values.vaultwarden.config.database.url }} + DATABASE_URL: {{ .Values.vaultwarden.config.database.url | quote }} + {{- end }} + {{- if .Values.vaultwarden.config.database.enableWal }} + DB_ENABLE_WAL: {{ .Values.vaultwarden.config.database.enableWal | quote }} + {{- end }} + {{- if .Values.vaultwarden.config.database.connectionRetries }} + DB_CONNECTION_RETRIES: {{ .Values.vaultwarden.config.database.connectionRetries | quote }} + {{- end }} + {{- if .Values.vaultwarden.config.database.timeout }} + DB_TIMEOUT: {{ .Values.vaultwarden.config.database.timeout | quote }} + {{- end }} + {{- if .Values.vaultwarden.config.database.idleTimeout }} + DB_IDLE_TIMEOUT: {{ .Values.vaultwarden.config.database.idleTimeout | quote }} + {{- end }} + {{- if .Values.vaultwarden.config.database.minConns }} + DB_MIN_CONNS: {{ .Values.vaultwarden.config.database.minConns | quote }} + {{- end }} + {{- if .Values.vaultwarden.config.database.maxConns }} + DB_MAX_CONNS: {{ .Values.vaultwarden.config.database.maxConns | quote }} + {{- end }} + {{- if .Values.vaultwarden.config.database.connInit }} + DB_CONN_INIT: {{ .Values.vaultwarden.config.database.connInit | quote }} + {{- end }} + {{- if .Values.vaultwarden.config.webVault.enabled }} + WEB_VAULT_ENABLED: {{ .Values.vaultwarden.config.webVault.enabled | quote }} + {{- end }} + {{- if .Values.vaultwarden.config.webVault.folder }} + WEB_VAULT_FOLDER: {{ .Values.vaultwarden.config.webVault.folder | quote }} + {{- end }} diff --git a/k8s/charts/vaultwarden/templates/ingress.yaml b/k8s/charts/vaultwarden/templates/ingress.yaml index 0c3cf36..9b4d26d 100644 --- a/k8s/charts/vaultwarden/templates/ingress.yaml +++ b/k8s/charts/vaultwarden/templates/ingress.yaml @@ -1,23 +1,23 @@ -{{- if .Values.ingress.enabled -}} +{{- if .Values.vaultwarden.ingress.enabled -}} {{- $fullName := include "vaultwarden.fullname" . -}} -{{- $svcPort := .Values.service.port -}} +{{- $svcPort := .Values.vaultwarden.service.port -}} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ $fullName }} labels: {{- include "vaultwarden.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} + {{- with .Values.vaultwarden.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: - {{- if .Values.ingress.className }} - ingressClassName: {{ .Values.ingress.className }} + {{- if .Values.vaultwarden.ingress.className }} + ingressClassName: {{ .Values.vaultwarden.ingress.className }} {{- end }} - {{- if .Values.ingress.tls }} + {{- if .Values.vaultwarden.ingress.tls }} tls: - {{- range .Values.ingress.tls }} + {{- range .Values.vaultwarden.ingress.tls }} - hosts: {{- range .hosts }} - {{ . | quote }} @@ -26,7 +26,7 @@ spec: {{- end }} {{- end }} rules: - {{- range .Values.ingress.hosts }} + {{- range .Values.vaultwarden.ingress.hosts }} - host: {{ .host | quote }} http: paths: diff --git a/k8s/charts/vaultwarden/templates/service.yaml b/k8s/charts/vaultwarden/templates/service.yaml index b73fcea..6316619 100644 --- a/k8s/charts/vaultwarden/templates/service.yaml +++ b/k8s/charts/vaultwarden/templates/service.yaml @@ -5,10 +5,10 @@ metadata: labels: {{- include "vaultwarden.labels" . | nindent 4 }} spec: - type: {{ .Values.service.type }} + type: {{ .Values.vaultwarden.service.type }} ports: - - port: {{ .Values.service.port }} - targetPort: {{ .Values.service.targetPort }} + - port: {{ .Values.vaultwarden.service.port }} + targetPort: {{ .Values.vaultwarden.service.targetPort }} protocol: TCP name: http selector: diff --git a/k8s/charts/vaultwarden/templates/statefulset.yaml b/k8s/charts/vaultwarden/templates/statefulset.yaml index 0c1e7c8..46342c4 100644 --- a/k8s/charts/vaultwarden/templates/statefulset.yaml +++ b/k8s/charts/vaultwarden/templates/statefulset.yaml @@ -6,103 +6,86 @@ metadata: {{- include "vaultwarden.labels" . | nindent 4 }} spec: serviceName: {{ include "vaultwarden.fullname" . }}-headless - {{- if not .Values.autoscaling.enabled }} - replicas: {{ .Values.replicaCount }} + {{- if not .Values.vaultwarden.autoscaling.enabled }} + replicas: {{ .Values.vaultwarden.replicaCount }} {{- end }} selector: matchLabels: {{- include "vaultwarden.selectorLabels" . | nindent 6 }} template: metadata: - {{- with .Values.podAnnotations }} + {{- with .Values.vaultwarden.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "vaultwarden.selectorLabels" . | nindent 8 }} spec: - {{- with .Values.imagePullSecrets }} + {{- with .Values.vaultwarden.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- toYaml .Values.vaultwarden.podSecurityContext | nindent 8 }} containers: - name: {{ .Chart.Name }} securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (printf "%s-alpine" .Chart.AppVersion) }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- toYaml .Values.vaultwarden.securityContext | nindent 12 }} + image: "{{ .Values.vaultwarden.image.repository }}:{{ .Values.vaultwarden.image.tag | default (printf "%s-alpine" .Chart.AppVersion) }}" + imagePullPolicy: {{ .Values.vaultwarden.image.pullPolicy }} envFrom: + - configMapRef: + name: {{ include "vaultwarden.fullname" . }}-config - secretRef: - name: {{ .Values.secret.existingSecretName | default (include "vaultwarden.fullname" .) }} - env: - {{- range $key, $value := .Values.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} - {{- if .Values.conf.db.database_url }} - - name: DATABASE_URL - value: {{ .Values.conf.db.database_url | quote }} - {{- end }} - {{- range $key, $value := .Values.conf.db }} - {{- if and (ne $key "database_url") $value }} - - name: {{ upper (print "DB_" $key) }} - value: {{ $value | quote }} - {{- end }} - {{- end }} - {{- range $key, $value := .Values.conf.web_vault }} - - name: {{ upper (print "WEB_VAULT_" $key) }} - value: {{ $value | quote }} - {{- end }} + name: {{ .Values.vaultwarden.secret.existingSecretName | default (include "vaultwarden.fullname" .) }} ports: - name: http containerPort: 80 protocol: TCP livenessProbe: - {{- toYaml .Values.livenessProbe | nindent 12 }} + {{- toYaml .Values.vaultwarden.livenessProbe | nindent 12 }} readinessProbe: - {{- toYaml .Values.readinessProbe | nindent 12 }} + {{- toYaml .Values.vaultwarden.readinessProbe | nindent 12 }} resources: - {{- toYaml .Values.resources | nindent 12 }} + {{- toYaml .Values.vaultwarden.resources | nindent 12 }} volumeMounts: - name: data mountPath: /data - {{- if .Values.persistence.existingClaim }} + {{- if .Values.vaultwarden.persistence.existingClaim }} volumes: - name: data persistentVolumeClaim: - claimName: {{ .Values.persistence.existingClaim }} + claimName: {{ .Values.vaultwarden.persistence.existingClaim }} {{- end }} - {{- with .Values.nodeSelector }} + {{- with .Values.vaultwarden.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.affinity }} + {{- with .Values.vaultwarden.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.tolerations }} + {{- with .Values.vaultwarden.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} restartPolicy: Always automountServiceAccountToken: false - {{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} + {{- if and .Values.vaultwarden.persistence.enabled (not .Values.vaultwarden.persistence.existingClaim) }} volumeClaimTemplates: - metadata: name: data spec: accessModes: - - {{ .Values.persistence.accessMode }} - {{- if .Values.persistence.storageClass }} - {{- if eq "-" .Values.persistence.storageClass }} + - {{ .Values.vaultwarden.persistence.accessMode }} + {{- if .Values.vaultwarden.persistence.storageClass }} + {{- if eq "-" .Values.vaultwarden.persistence.storageClass }} storageClassName: "" {{- else }} - storageClassName: {{ .Values.persistence.storageClass | quote }} + storageClassName: {{ .Values.vaultwarden.persistence.storageClass | quote }} {{- end }} {{- end }} resources: requests: - storage: {{ .Values.persistence.size }} + storage: {{ .Values.vaultwarden.persistence.size }} {{- end }} diff --git a/k8s/charts/vaultwarden/values.yaml b/k8s/charts/vaultwarden/values.yaml index e8f40b8..ffee53e 100644 --- a/k8s/charts/vaultwarden/values.yaml +++ b/k8s/charts/vaultwarden/values.yaml @@ -1,75 +1,76 @@ -# Configuration is managed in k8s/argo-apps/vaultwarden.yaml -replicaCount: 1 +vaultwarden: + replicaCount: 1 -image: - repository: vaultwarden/server - pullpolicy: Always - tag: "" + image: + repository: vaultwarden/server + pullpolicy: Always + tag: "" -imagePullSecrets: [] -podAnnotations: {} -podSecurityContext: {} -securityContext: {} + imagePullSecrets: [] + podAnnotations: {} + podSecurityContext: {} + securityContext: {} -service: - type: ClusterIP - port: 80 - targetPort: 80 + service: + type: ClusterIP + port: 80 + targetPort: 80 -ingress: - enabled: false + ingress: + enabled: false -conf: - db: - database_url: "" - enable_db_wal: true - connection_retries: 15 - timeout: 30 - idle_timeout: 600 - min_conns: 2 - max_conns: 10 - conn_init: "" - web_vault: + config: + database: + url: "" + enableWal: true + connectionRetries: 15 + timeout: 30 + idleTimeout: 600 + minConns: 2 + maxConns: 10 + connInit: "" + webVault: + enabled: true + folder: "web-vault/" + + persistence: enabled: true - folder: "web-vault/" + storageClass: "truenas-nfs-csi" + accessMode: ReadWriteMany + size: 5Gi + existingClaim: "" -persistence: - enabled: true - storageClass: "truenas-nfs-csi" - accessMode: ReadWriteMany - size: 5Gi - existingClaim: "" + autoscaling: + enabled: false -autoscaling: - enabled: false + resources: {} -resources: {} + livenessProbe: + tcpSocket: + port: 80 + initialDelaySeconds: 30 + timeoutSeconds: 15 + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 -livenessProbe: - tcpSocket: - port: 80 - initialDelaySeconds: 30 - timeoutSeconds: 15 - successThreshold: 1 - failureThreshold: 3 - periodSeconds: 10 + readinessProbe: + tcpSocket: + port: 80 + initialDelaySeconds: 15 + timeoutSeconds: 2 + successThreshold: 1 + failureThreshold: 3 + periodSeconds: 10 -readinessProbe: - tcpSocket: - port: 80 - initialDelaySeconds: 15 - timeoutSeconds: 2 - successThreshold: 1 - failureThreshold: 3 - periodSeconds: 10 + nodeSelector: {} + tolerations: [] + affinity: {} -nodeSelector: {} -tolerations: [] -affinity: {} + secret: + enabled: true + existingSecretName: "vaultwarden-kubernetes-secrets" + data: {} -env: {} - -secret: - enabled: true - existingSecretName: "vaultwarden-kubernetes-secrets" - data: {} + debug: + enabled: false diff --git a/k8s/services/argo/project-fuku.yaml b/k8s/services/argo/project-fuku.yaml index adfc1a7..9e7ba4a 100644 --- a/k8s/services/argo/project-fuku.yaml +++ b/k8s/services/argo/project-fuku.yaml @@ -19,22 +19,10 @@ spec: - https://git.roboces.dev/catalin/fukuops.git - https://charts.goauthentik.io/ - https://kubereboot.github.io/charts - - https://sqljames.github.io/factorio-server-charts/ - - https://portainer.github.io/k8s/ - - https://charts.bitnami.com/bitnami - - https://charts.crystalnet.org - https://portainer.github.io/k8s/ - https://docs.renovatebot.com/helm-charts - https://meilisearch.github.io/meilisearch-kubernetes - - https://kubetail-org.github.io/helm-charts/ - - https://groundhog2k.github.io/helm-charts/ - - registry-1.docker.io/cloudpirates - - https://vmware-tanzu.github.io/helm-charts/ - - https://helm.runix.net - - https://rcourtman.github.io/Pulse - ghcr.io/antoniolago/charts - - https://helm.elastic.co - https://repo.helmforge.dev - https://harish2k01.github.io/helm-charts/ - git.roboces.dev/catalin/fukuops - - git.roboces.dev/catalin diff --git a/k8s/services/argo/project-management.yaml b/k8s/services/argo/project-management.yaml index b150487..4ef7347 100644 --- a/k8s/services/argo/project-management.yaml +++ b/k8s/services/argo/project-management.yaml @@ -25,8 +25,3 @@ spec: - https://git.roboces.dev/catalin/fukuops.git - https://democratic-csi.github.io/charts/ - https://bitnami-labs.github.io/sealed-secrets - - https://grafana.github.io/helm-charts - - https://kubernetes-sigs.github.io/descheduler/ - - https://github.com/rancher/system-upgrade-controller.git - - https://charts.bitnami.com/bitnami - - https://argoproj.github.io/argo-helm diff --git a/k8s/services/argo/project-roboces.yaml b/k8s/services/argo/project-roboces.yaml index e24b9c9..b7a335a 100644 --- a/k8s/services/argo/project-roboces.yaml +++ b/k8s/services/argo/project-roboces.yaml @@ -14,9 +14,6 @@ spec: - group: "" kind: PersistentVolume sourceRepos: - - https://git.roboces.dev/catalin/fukuops.git - code.forgejo.org/forgejo-helm - git.roboces.dev/catalin/fukuops - - git.roboces.dev/catalin - https://git.roboces.dev/catalin/huesoporro.git - - https://gitlab.com/api/v4/projects/64552889/packages/helm/release diff --git a/scripts/source-repos.sh b/scripts/source-repos.sh new file mode 100755 index 0000000..838e825 --- /dev/null +++ b/scripts/source-repos.sh @@ -0,0 +1,174 @@ +#!/usr/bin/env bash +set -euo pipefail + +PROJECTS_DIR="k8s/services/argo" +APPS_DIR="k8s/argo-apps" + +usage() { + cat </dev/null | \ + sed 's/.*repoURL: *//' | \ + sort -u +} + +get_project_name_from_repo() { + local repo="$1" + for project_file in "$PROJECTS_DIR"/project-*.yaml; do + local project_name + project_name=$(basename "$project_file" .yaml | sed 's/^project-//') + if awk '/^[[:space:]]*sourceRepos:/,/^[[:space:]]*[a-z]+:/' "$project_file" | \ + grep -qE "^[[:space:]]+- ${repo//\./\\.}$"; then + echo "$project_name" + return + fi + done +} + +list_all() { + echo "=== All Source Repos in ArgoCD Projects ===" + echo "" + while IFS= read -r repo; do + local project + project=$(get_project_name_from_repo "$repo") + echo "[$project] $repo" + done < <(get_all_project_repos) +} + +list_unused() { + local all_repos + local all_app_repos + all_repos=$(get_all_project_repos) + all_app_repos=$(get_all_app_repos) + + echo "=== Unused Source Repos ===" + echo "" + + local found_unused=false + while IFS= read -r repo; do + if ! echo "$all_app_repos" | grep -qF -- "$repo"; then + found_unused=true + local project + project=$(get_project_name_from_repo "$repo") + echo "[$project] $repo" + fi + done < <(echo "$all_repos") + + if [ "$found_unused" = false ]; then + echo "No unused source repos found." + fi +} + +delete_unused() { + local all_repos + local all_app_repos + all_repos=$(get_all_project_repos) + all_app_repos=$(get_all_app_repos) + + local found_unused=false + local to_delete=() + + while IFS= read -r repo; do + if ! echo "$all_app_repos" | grep -qF -- "$repo"; then + found_unused=true + to_delete+=("$repo") + fi + done < <(echo "$all_repos") + + if [ "$found_unused" = false ]; then + echo "No unused source repos found." + return + fi + + echo "The following source repos will be deleted:" + echo "" + for repo in "${to_delete[@]}"; do + local project + project=$(get_project_name_from_repo "$repo") + echo "[$project] $repo" + done + echo "" + + read -p "Are you sure? [y/N] " -r + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Aborted." + return + fi + + for repo in "${to_delete[@]}"; do + for project_file in "$PROJECTS_DIR"/project-*.yaml; do + if grep -q "sourceRepos:" "$project_file"; then + local escaped_repo + escaped_repo="${repo//\./\\.}" + if grep -qE "^[[:space:]]+- $escaped_repo$" "$project_file"; then + sed -i "s|^[[:space:]]*- $escaped_repo$||" "$project_file" + sed -i '/^[[:space:]]*-$/d' "$project_file" + sed -i '/^[[:space:]]*$/d' "$project_file" + echo "Deleted '$repo' from $(basename "$project_file")" + fi + fi + done + done +} + +COMMAND="list_all" + +while [[ $# -gt 0 ]]; do + case "$1" in + -l|--list) + COMMAND="list_all" + ;; + -u|--unused) + COMMAND="list_unused" + ;; + -d|--delete-unused) + COMMAND="delete_unused" + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" + usage + exit 1 + ;; + esac + shift +done + +case "$COMMAND" in + list_all) + list_all + ;; + list_unused) + list_unused + ;; + delete_unused) + delete_unused + ;; +esac diff --git a/tofu/adguard/main.tf b/tofu/adguard/main.tf index 803522b..afd06bf 100644 --- a/tofu/adguard/main.tf +++ b/tofu/adguard/main.tf @@ -1,9 +1,10 @@ terraform { required_version = ">= 1.6" backend "s3" { - bucket = "fuku-terraform" - key = "adguard/terraform" - region = "us-east-1" + bucket = "fuku-terraform" + key = "adguard/terraform" + region = "us-east-1" + use_lockfile = "true" } required_providers { diff --git a/tofu/authentik/main.tf b/tofu/authentik/main.tf index b5f8061..0822796 100644 --- a/tofu/authentik/main.tf +++ b/tofu/authentik/main.tf @@ -1,9 +1,10 @@ terraform { required_version = ">= 1.6" backend "s3" { - bucket = "fuku-terraform" - key = "authentik/terraform" - region = "us-east-1" + bucket = "fuku-terraform" + key = "authentik/terraform" + region = "us-east-1" + use_lockfile = "true" } required_providers { authentik = { @@ -101,21 +102,6 @@ module "portainer" { sub_mode = "hashed_user_id" } -module "paperless" { - source = "../modules/authentik-oidc" - app_name = "Paperless" - app_slug = "paperless" - client_id = var.paperless_client_id - client_secret = var.paperless_client_secret - app_access_group_id = "" - redirect_uris = [ - { matching_mode = "strict", url = "https://paperless.roboces.dev/accounts/oidc/authentik/login/callback/" } - ] - app_icon = "https://paperless.roboces.dev/favicon.ico" - app_description = "Document manager" - app_publisher = "Paperless" - app_url = "https://paperless.roboces.dev" -} module "sonarr" { source = "../modules/authentik-proxy" @@ -193,28 +179,6 @@ module "jellyfin" { } -module "ganymede" { - source = "../modules/authentik-oidc" - app_name = "Ganymede" - app_slug = "ganymede" - redirect_uris = [{ matching_mode = "strict", url = "https://vods.roboces.dev/api/v1/auth/oauth/callback" }] - client_id = var.ganymede_client_id - client_secret = var.ganymede_client_secret - app_url = "https://vods.roboces.dev" - app_icon = "https://vods.roboces.dev/favicon.ico" - app_access_group_id = authentik_group.vods.id -} - -module "jellyseerr" { - source = "../modules/authentik-app" - app_name = "Solicitudes Jelly" - app_slug = "jellyseer" - app_url = "https://requests.roboces.dev" - app_icon = "https://requests.roboces.dev/os_icon.svg" - app_description = "Solicita series, animes y pelis para ser añadidas automáticamente a Jellyfin" - app_access_group_id = authentik_group.arrs.id -} - module "cloud" { source = "../modules/authentik-oidc" app_name = "Cloud" diff --git a/tofu/authentik/sample.env b/tofu/authentik/sample.env index 224c37a..07c3342 100644 --- a/tofu/authentik/sample.env +++ b/tofu/authentik/sample.env @@ -12,5 +12,5 @@ TF_VAR_rustical_client_id= TF_VAR_rustical_client_secret= TF_VAR_ganymede_client_id= TF_VAR_ganymede_client_secret= -TF_VAR_oxicloud_client_id=aef61f77326b813cf8d8ba71d1ac994b5642685ca37e4710ab0079e91d87702d55fd9775d473b05aff45603bf08e78dba26850af3a815f3c3ac171d163368aa0 -TF_VAR_oxicloud_client_secret=a4038df17c9fd06f86372aeaaae8f3fd1374d8978983af7b398d948ef15d1efe522a1faa2fc7652bc410c516d96cd2e4211dad4e05ba6297bdd8d9090460d5fc +TF_VAR_oxicloud_client_id= +TF_VAR_oxicloud_client_secret=