feat(helm): complete chart normalization with ConfigMap, HPA, and OCI migration

- Added debug mode, structured ConfigMap pattern, and README to all charts
- Migrated all flat-structure charts to hierarchical values under chartname key
- Fixed templates to use correct hierarchical paths
- Renamed helm-rustical key to rustical, added HPA support
- Fixed ingress YAML parsing (oxicloud), nested env vars (rustical)
- Pushed all normalized charts to OCI registry as 1.0.0+ and updated ArgoCD apps
This commit is contained in:
cătălin 2026-08-14 09:35:19 +02:00
commit 1fd2bfef3d
No known key found for this signature in database
71 changed files with 2150 additions and 965 deletions

View file

@ -51,6 +51,10 @@ spec:
persistentVolumeClaim: persistentVolumeClaim:
claimName: pvc-authentik-media claimName: pvc-authentik-media
server: server:
resources:
requests:
cpu: 500m
memory: 1Gi
autoscaling: autoscaling:
enabled: true enabled: true
minReplicas: 1 minReplicas: 1
@ -79,6 +83,9 @@ spec:
tls: [] tls: []
https: true https: true
worker: worker:
requests:
cpu: 250m
memory: 512Mi
autoscaling: autoscaling:
enabled: true enabled: true
minReplicas: 2 minReplicas: 2
@ -92,4 +99,6 @@ spec:
targetRevision: main targetRevision: main
project: fuku project: fuku
syncPolicy: syncPolicy:
automated: {} automated:
prune: true
selfHeal: true

View file

@ -12,38 +12,40 @@ spec:
sources: sources:
- repoURL: git.roboces.dev/catalin/fukuops - repoURL: git.roboces.dev/catalin/fukuops
chart: dokuwiki chart: dokuwiki
targetRevision: 0.1.2 targetRevision: 1.0.2
helm: helm:
valuesObject: valuesObject:
replicaCount: 1 dokuwiki:
securityContext: replicaCount: 1
allowPrivilegeEscalation: false securityContext:
readOnlyRootFilesystem: false allowPrivilegeEscalation: false
runAsUser: 33 readOnlyRootFilesystem: false
runAsGroup: 33 runAsUser: 33
capabilities: runAsGroup: 33
drop: capabilities:
- all drop:
service: - all
type: LoadBalancer service:
port: 8004 type: LoadBalancer
ingress: port: 8004
enabled: true ingress:
className: "traefik" enabled: true
hosts: className: "traefik"
- host: wiki.roboces.dev hosts:
paths: - host: wiki.roboces.dev
- path: / paths:
pathType: Prefix - path: /
persistence: pathType: Prefix
enabled: true persistence:
storageClass: "truenas-nfs-csi" enabled: true
accessMode: ReadWriteMany storageClass: "truenas-nfs-csi"
size: 20Gi accessMode: ReadWriteMany
env: size: 20Gi
PHP_TIMEZONE: Europe/Madrid config:
PHP_MEMORYLIMIT: 512M php:
PHP_UPLOADLIMIT: 128M timezone: Europe/Madrid
memoryLimit: 512M
uploadLimit: 128M
project: roboces project: roboces
syncPolicy: syncPolicy:
automated: automated:

View file

@ -101,4 +101,6 @@ spec:
enabled: false enabled: false
project: roboces project: roboces
syncPolicy: syncPolicy:
automated: {} automated:
prune: true
selfHeal: true

View file

@ -12,45 +12,51 @@ spec:
sources: sources:
- repoURL: git.roboces.dev/catalin/fukuops - repoURL: git.roboces.dev/catalin/fukuops
chart: helm-rustical chart: helm-rustical
targetRevision: 0.3.0 targetRevision: 1.1.1
helm: helm:
valuesObject: valuesObject:
debug: rustical:
enabled: false debug:
replicaCount: 1 enabled: false
service: autoscaling:
type: LoadBalancer enabled: true
port: 8001 minReplicas: 1
targetPort: 4000 maxReplicas: 3
ingress: targetCPUUtilizationPercentage: 80
enabled: true targetMemoryUtilizationPercentage: 80
className: "traefik" service:
hosts: type: LoadBalancer
- host: cal.roboces.dev port: 8001
paths: targetPort: 4000
- path: / ingress:
pathType: Prefix enabled: true
env: className: "traefik"
RUSTICAL_OIDC__NAME: Authentik hosts:
RUSTICAL_OIDC__ISSUER: https://auth.fukurokuju.dev/application/o/rustical/ - host: cal.roboces.dev
RUSTICAL_OIDC__CLAIM_USERID: preferred_username paths:
RUSTICAL_OIDC__SCOPES: '["openid", "profile", "groups"]' - path: /
RUSTICAL_OIDC__ALLOW_SIGN_UP: "true" pathType: Prefix
RUSTICAL_FRONTEND__ALLOW_PASSWORD_LOGIN: false config:
persistence: oidc.name: Authentik
enabled: true oidc.issuer: https://auth.fukurokuju.dev/application/o/rustical/
storageClass: "truenas-nfs-csi" oidc.claim_userid: preferred_username
accessMode: ReadWriteMany oidc.scopes: '["openid", "profile", "groups"]'
size: 50Gi oidc.allow_sign_up: "true"
secret: frontend.allow_password_login: false
existingSecretName: rustical persistence:
resources: enabled: true
requests: storageClass: "truenas-nfs-csi"
cpu: 250m accessMode: ReadWriteMany
memory: 256Mi size: 50Gi
limits: secret:
cpu: 500m existingSecretName: rustical
memory: 512Mi resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
project: roboces project: roboces
syncPolicy: syncPolicy:
automated: automated:

View file

@ -12,13 +12,9 @@ spec:
sources: sources:
- repoURL: git.roboces.dev/catalin/fukuops - repoURL: git.roboces.dev/catalin/fukuops
chart: immich chart: immich
targetRevision: 0.2.3 targetRevision: 1.0.4
helm: helm:
valuesObject: valuesObject:
machine-learning:
enabled: true
service:
type: LoadBalancer
immich: immich:
autoscaling: autoscaling:
enabled: true enabled: true
@ -32,16 +28,17 @@ spec:
- all - all
service: service:
type: LoadBalancer type: LoadBalancer
db: config:
hostname: "192.168.1.3" database:
port: 5432 hostname: "192.168.1.3"
database_name: "immich" port: 5432
redis: name: "immich"
hostname: "192.168.1.3" redis:
port: 30036 hostname: "192.168.1.3"
dbindex: "12" port: 30036
general: dbIndex: "12"
tz: "Europe/Madrid" general:
timezone: Europe/Madrid
persistence: persistence:
library: library:
enabled: true enabled: true
@ -96,8 +93,13 @@ spec:
- path: / - path: /
pathType: Prefix pathType: Prefix
tls: [] tls: []
secret: machineLearning:
existingSecretName: "immich-secrets" enabled: true
service:
type: LoadBalancer
secret:
enabled: true
existingSecretName: "immich-secrets"
project: roboces project: roboces
syncPolicy: syncPolicy:
automated: automated:

View file

@ -12,72 +12,79 @@ spec:
sources: sources:
- repoURL: git.roboces.dev/catalin/fukuops - repoURL: git.roboces.dev/catalin/fukuops
chart: miniflux chart: miniflux
targetRevision: 0.2.0 targetRevision: 1.0.3
helm: helm:
valuesObject: valuesObject:
replicaCount: 3 miniflux:
securityContext: autoscaling:
allowPrivilegeEscalation: false enabled: true
readOnlyRootFilesystem: true securityContext:
runAsUser: 10000 allowPrivilegeEscalation: false
runAsGroup: 10000 readOnlyRootFilesystem: true
capabilities: runAsUser: 10000
drop: runAsGroup: 10000
- all capabilities:
service: drop:
type: LoadBalancer - all
ingress: service:
enabled: true type: LoadBalancer
className: "traefik" ingress:
hosts: enabled: true
- host: feeds.roboces.dev className: "traefik"
paths: hosts:
- path: / - host: feeds.roboces.dev
pathType: Prefix paths:
resources: - path: /
requests: pathType: Prefix
cpu: 300m resources:
memory: 300Mi requests:
ephemeral-storage: 2Gi cpu: 500m
limits: memory: 512Mi
cpu: 400m ephemeral-storage: 2Gi
memory: 500Mi limits:
ephemeral-storage: 4Gi cpu: 700m
livenessProbe: memory: 1024Mi
tcpSocket: ephemeral-storage: 4Gi
port: 8080 livenessProbe:
initialDelaySeconds: 5 tcpSocket:
timeoutSeconds: 15 port: 8080
successThreshold: 1 initialDelaySeconds: 5
failureThreshold: 3 timeoutSeconds: 15
periodSeconds: 10 successThreshold: 1
readinessProbe: failureThreshold: 3
httpGet: periodSeconds: 10
path: / readinessProbe:
port: 8080 httpGet:
initialDelaySeconds: 15 path: /
timeoutSeconds: 2 port: 8080
successThreshold: 1 initialDelaySeconds: 15
failureThreshold: 3 timeoutSeconds: 2
periodSeconds: 10 successThreshold: 1
podDisruptionBudget: failureThreshold: 3
enabled: true periodSeconds: 10
maxUnavailable: 1 podDisruptionBudget:
env: enabled: true
RUN_MIGRATIONS: "1" maxUnavailable: 1
CREATE_ADMIN: "1" config:
OAUTH2_PROVIDER: oidc general:
OAUTH2_REDIRECT_URL: https://feeds.roboces.dev/oauth2/oidc/callback runMigrations: true
OAUTH2_OIDC_DISCOVERY_ENDPOINT: https://auth.fukurokuju.dev/application/o/miniflux/ createAdmin: true
OAUTH2_USER_CREATION: "1" workerPoolSize: 1
FETCH_YOUTUBE_WATCH_TIME: "1" pollingFrequency: 120
WORKER_POOL_SIZE: "1" batchSize: 25
POLLING_FREQUENCY: "120" oauth2:
BATCH_SIZE: "25" enabled: true
METRICS_COLLECTOR: "1" provider: oidc
METRICS_ALLOWED_NETWORKS: 10.42.1.0/16 redirectUrl: https://feeds.roboces.dev/oauth2/oidc/callback
secret: oidcDiscoveryEndpoint: https://auth.fukurokuju.dev/application/o/miniflux/
existingSecretName: miniflux userCreation: true
metrics:
enabled: true
allowedNetworks: 10.42.1.0/16
youtube:
fetchWatchTime: true
secret:
existingSecretName: miniflux
project: roboces project: roboces
syncPolicy: syncPolicy:
automated: automated:

View file

@ -12,39 +12,43 @@ spec:
sources: sources:
- repoURL: git.roboces.dev/catalin/fukuops - repoURL: git.roboces.dev/catalin/fukuops
chart: oxicloud chart: oxicloud
targetRevision: 0.1.0 targetRevision: 1.0.1
helm: helm:
valuesObject: valuesObject:
persistence: oxicloud:
enabled: true persistence:
storageClass: "truenas-nfs-csi" enabled: true
accessMode: ReadWriteMany storageClass: "truenas-nfs-csi"
size: 50Gi accessMode: ReadWriteMany
service: size: 50Gi
type: LoadBalancer service:
config: type: LoadBalancer
server: config:
port: 8086 server:
host: "0.0.0.0" port: 8086
baseUrl: "https://cloud.roboces.dev" host: "0.0.0.0"
features: baseUrl: "https://cloud.roboces.dev"
enableAuth: "true" features:
enableSharing: "true" enableAuth: true
mimalloc: enableSharing: true
purgeDelay: "0" mimalloc:
allowLargeOsPages: "0" purgeDelay: "0"
secrets: allowLargeOsPages: "0"
existingSecret: oxicloud secret:
wopi: existingSecretName: oxicloud
enabled: false wopi:
ingress: enabled: false
className: "traefik" ingress:
hosts: enabled: true
- host: cloud.roboces.dev className: "traefik"
paths: hosts:
- path: / - host: cloud.roboces.dev
pathType: ImplementationSpecific paths:
tls: [] - path: /
pathType: ImplementationSpecific
tls: []
project: roboces project: roboces
syncPolicy: syncPolicy:
automated: {} automated:
prune: true
selfHeal: true

View file

@ -21,6 +21,7 @@ spec:
type: LoadBalancer type: LoadBalancer
persistence: persistence:
storageClass: truenas-nfs-csi storageClass: truenas-nfs-csi
accessMode: ReadWriteMany
dashboard: dashboard:
enabled: true enabled: true
service: service:

View file

@ -12,51 +12,51 @@ spec:
sources: sources:
- repoURL: git.roboces.dev/catalin/fukuops - repoURL: git.roboces.dev/catalin/fukuops
chart: vaultwarden chart: vaultwarden
targetRevision: 0.1.1 targetRevision: 1.0.2
helm: helm:
valuesObject: valuesObject:
conf: vaultwarden:
db: config:
enable_db_wal: true database:
connection_retries: 15 enableWal: true
timeout: 30 connectionRetries: 15
web_vault: 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 enabled: true
folder: "web-vault/" className: "traefik"
hosts:
replicaCount: 1 - host: vault.roboces.dev
securityContext: paths:
allowPrivilegeEscalation: false - path: /
readOnlyRootFilesystem: false pathType: Prefix
runAsUser: 10000 resources:
runAsGroup: 10000 requests:
capabilities: cpu: 250m
drop: memory: 256Mi
- all limits:
service: cpu: 500m
type: LoadBalancer memory: 512Mi
ingress: persistence:
enabled: true enabled: true
className: "traefik" storageClass: "truenas-nfs-csi"
hosts: accessMode: ReadWriteMany
- host: vault.roboces.dev size: 5Gi
paths: secret:
- path: / existingSecretName: vaultwarden-secrets
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 project: roboces
syncPolicy: syncPolicy:
automated: automated:

View file

@ -2,15 +2,9 @@ apiVersion: v2
name: chart name: chart
description: A Helm chart template with standardized structure description: A Helm chart template with standardized structure
type: application type: application
version: 0.1.0 version: 1.0.2
appVersion: "latest" appVersion: latest
annotations: annotations:
artifacthub.io/images: | artifacthub.io/images: "- name: chart\n image: nginx:latest\n"
- name: chart artifacthub.io/changes: "- kind: added\n description: Initial chart template\n"
image: nginx:latest artifacthub.io/maintainers: "- name: catalin\n email: catalin@example.com\n"
artifacthub.io/changes: |
- kind: added
description: Initial chart template
artifacthub.io/maintainers: |
- name: catalin
email: catalin@example.com

View file

@ -5,7 +5,7 @@ A Helm chart template with standardized structure for self-hosted services.
## TL;DR ## TL;DR
```bash ```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 ## Prerequisites
@ -17,64 +17,74 @@ helm install chart oci://git.roboces.dev/catalin/fukuops/chart --version 1.0.0 -
## Configuration ## Configuration
### Basic Configuration All values are nested under the `chart:` key. Example:
```yaml ```yaml
service: chart:
type: LoadBalancer replicaCount: 1
port: 80 service:
ingress: type: LoadBalancer
enabled: true port: 80
className: "traefik" ingress:
hosts: enabled: true
- host: chart.example.com className: "traefik"
paths: hosts:
- path: / - host: chart.example.com
pathType: Prefix paths:
``` - path: /
pathType: Prefix
### With Persistence config:
# Key-value pairs that will be mapped to env vars (uppercased)
```yaml # Example: `FOO: "bar"` becomes env var FOO=bar
persistence: persistence:
enabled: true enabled: true
storageClass: "truenas-nfs-csi" storageClass: "truenas-nfs-csi"
accessMode: ReadWriteMany accessMode: ReadWriteMany
size: 10Gi size: 10Gi
``` secret:
existingSecretName: chart-secrets
### With Secrets
```yaml
secret:
existingSecretName: chart-secrets
``` ```
## Values Reference ## Values Reference
| Key | Type | Default | Description | | Key | Type | Default | Description |
|-----|------|---------|-------------| |-----|------|---------|-------------|
| `replicaCount` | int | `1` | Number of replicas | | `chart.replicaCount` | int | `1` | Number of replicas |
| `image.repository` | string | `nginx` | Image repository | | `chart.image.repository` | string | `nginx` | Image repository |
| `image.pullPolicy` | string | `IfNotPresent` | Image pull policy | | `chart.image.pullPolicy` | string | `IfNotPresent` | Image pull policy |
| `image.tag` | string | `.Chart.AppVersion` | Image tag | | `chart.image.tag` | string | `.Chart.AppVersion` | Image tag |
| `service.type` | string | `LoadBalancer` | Service type | | `chart.service.type` | string | `LoadBalancer` | Service type |
| `service.port` | int | `80` | Service port | | `chart.service.port` | int | `80` | Service port |
| `service.targetPort` | int | `80` | Container port | | `chart.service.targetPort` | int | `80` | Container port |
| `ingress.enabled` | bool | `false` | Enable ingress | | `chart.ingress.enabled` | bool | `false` | Enable ingress |
| `ingress.className` | string | `traefik` | Ingress class | | `chart.ingress.className` | string | `traefik` | Ingress class |
| `persistence.enabled` | bool | `false` | Enable persistence | | `chart.config` | object | `{}` | Config key-value pairs mapped to env vars |
| `persistence.storageClass` | string | `truenas-nfs-csi` | Storage class | | `chart.persistence.enabled` | bool | `false` | Enable persistence |
| `persistence.accessMode` | string | `ReadWriteMany` | Access mode | | `chart.persistence.storageClass` | string | `truenas-nfs-csi` | Storage class |
| `persistence.size` | string | `1Gi` | PVC size | | `chart.persistence.accessMode` | string | `ReadWriteMany` | Access mode |
| `persistence.name` | string | `chart-data` | PVC name | | `chart.persistence.size` | string | `1Gi` | PVC size |
| `secret.existingSecretName` | string | `""` | Existing secret name | | `chart.persistence.name` | string | `chart-data` | PVC name |
| `resources` | object | `{}` | Container resources | | `chart.secret.existingSecretName` | string | `""` | Existing secret name |
| `livenessProbe` | object | (see values.yaml) | Liveness probe | | `chart.debug.enabled` | bool | `false` | Enable debug mode (adds init container for troubleshooting) |
| `readinessProbe` | object | (see values.yaml) | Readiness probe | | `chart.resources` | object | `{}` | Container resources |
| `nodeSelector` | object | `{}` | Node selector | | `chart.livenessProbe` | object | (see values.yaml) | Liveness probe |
| `tolerations` | array | `[]` | Tolerations | | `chart.readinessProbe` | object | (see values.yaml) | Readiness probe |
| `affinity` | object | `{}` | Affinity rules | | `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 ## Persistence
@ -82,10 +92,28 @@ When persistence is enabled, a PVC named `chart-data` is created. The PVC uses t
## Secret Management ## 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 ## 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 <pod-name> -c debug -- sh
```
When debug mode is enabled, liveness and readiness probes are disabled to prevent restarts.
### Pod not starting ### Pod not starting
```bash ```bash

View file

@ -1,4 +1,4 @@
{{- if .Values.config }} {{- if .Values.chart.config }}
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
@ -6,7 +6,8 @@ metadata:
labels: labels:
{{- include "labels" . | nindent 4 }} {{- include "labels" . | nindent 4 }}
data: data:
{{- range $key, $value := .Values.config }} {{- range $key, $value := .Values.chart.config }}
{{ $key }}: {{ $value | quote }} {{- $envKey := $key | upper }}
{{ $envKey }}: {{ $value | quote }}
{{- end }} {{- end }}
{{- end }} {{- end }}

View file

@ -5,7 +5,7 @@ metadata:
labels: labels:
{{- include "labels" . | nindent 4 }} {{- include "labels" . | nindent 4 }}
spec: spec:
replicas: {{ .Values.replicaCount }} replicas: {{ .Values.chart.replicaCount }}
selector: selector:
matchLabels: matchLabels:
{{- include "selectorLabels" . | nindent 6 }} {{- include "selectorLabels" . | nindent 6 }}
@ -14,55 +14,90 @@ spec:
labels: labels:
{{- include "selectorLabels" . | nindent 8 }} {{- include "selectorLabels" . | nindent 8 }}
spec: spec:
containers: {{- with .Values.chart.podSecurityContext }}
- name: {{ .Chart.Name }} securityContext:
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" {{- toYaml . | nindent 8 }}
imagePullPolicy: {{ .Values.image.pullPolicy }} {{- end }}
ports: {{- if .Values.chart.debug.enabled }}
- name: http initContainers:
containerPort: {{ .Values.service.targetPort }} - name: debug
protocol: TCP image: alpine:3.19
{{- if .Values.config }} command: ["sleep", "infinity"]
envFrom: envFrom:
{{- if .Values.chart.config }}
- configMapRef: - configMapRef:
name: {{ include "fullname" . }}-config name: {{ include "fullname" . }}-config
{{- end }} {{- end }}
{{- if .Values.secret.existingSecretName }} {{- if .Values.chart.secret.existingSecretName }}
envFrom:
- secretRef: - secretRef:
name: {{ .Values.secret.existingSecretName }} name: {{ .Values.chart.secret.existingSecretName }}
{{- end }} {{- end }}
{{- if .Values.persistence.enabled }} {{- if .Values.chart.persistence.enabled }}
volumeMounts: volumeMounts:
- name: {{ include "pvcName" . }} - name: {{ include "pvcName" . }}
mountPath: /data mountPath: /data
{{- end }} {{- 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: resources:
{{- toYaml . | nindent 12 }} {{- toYaml . | nindent 12 }}
{{- end }} {{- end }}
{{- with .Values.livenessProbe }} {{- if not .Values.chart.debug.enabled }}
{{- with .Values.chart.livenessProbe }}
livenessProbe: livenessProbe:
{{- toYaml . | nindent 12 }} {{- toYaml . | nindent 12 }}
{{- end }} {{- end }}
{{- with .Values.readinessProbe }} {{- with .Values.chart.readinessProbe }}
readinessProbe: readinessProbe:
{{- toYaml . | nindent 12 }} {{- toYaml . | nindent 12 }}
{{- end }} {{- end }}
{{- if not .Values.persistence.enabled }} {{- end }}
{{- if not .Values.chart.persistence.enabled }}
volumes: volumes:
- name: {{ include "pvcName" . }} - name: {{ include "pvcName" . }}
emptyDir: {} emptyDir: {}
{{- end }} {{- end }}
{{- with .Values.nodeSelector }} {{- with .Values.chart.nodeSelector }}
nodeSelector: nodeSelector:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
{{- with .Values.tolerations }} {{- with .Values.chart.tolerations }}
tolerations: tolerations:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
{{- with .Values.affinity }} {{- with .Values.chart.affinity }}
affinity: affinity:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}

View file

@ -1,21 +1,21 @@
{{- if .Values.ingress.enabled }} {{- if .Values.chart.ingress.enabled }}
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: {{ include "fullname" . }} name: {{ include "fullname" . }}
labels: labels:
{{- include "labels" . | nindent 4 }} {{- include "labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }} {{- with .Values.chart.ingress.annotations }}
annotations: annotations:
{{- toYaml . | nindent 4 }} {{- toYaml . | nindent 4 }}
{{- end }} {{- end }}
spec: spec:
{{- if .Values.ingress.className }} {{- if .Values.chart.ingress.className }}
ingressClassName: {{ .Values.ingress.className }} ingressClassName: {{ .Values.chart.ingress.className }}
{{- end }} {{- end }}
{{- if .Values.ingress.tls }} {{- if .Values.chart.ingress.tls }}
tls: tls:
{{- range .Values.ingress.tls }} {{- range .Values.chart.ingress.tls }}
- hosts: - hosts:
{{- range .hosts }} {{- range .hosts }}
- {{ . | quote }} - {{ . | quote }}
@ -24,7 +24,7 @@ spec:
{{- end }} {{- end }}
{{- end }} {{- end }}
rules: rules:
{{- range .Values.ingress.hosts }} {{- range .Values.chart.ingress.hosts }}
- host: {{ .host | quote }} - host: {{ .host | quote }}
http: http:
paths: paths:
@ -35,7 +35,7 @@ spec:
service: service:
name: {{ include "fullname" $ }} name: {{ include "fullname" $ }}
port: port:
number: {{ $.Values.service.port }} number: {{ $.Values.chart.service.port }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- end }} {{- end }}

View file

@ -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 }}

View file

@ -1,4 +1,4 @@
{{- if .Values.persistence.enabled }} {{- if .Values.chart.persistence.enabled }}
apiVersion: v1 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
@ -7,11 +7,11 @@ metadata:
{{- include "labels" . | nindent 4 }} {{- include "labels" . | nindent 4 }}
spec: spec:
accessModes: accessModes:
- {{ .Values.persistence.accessMode | default "ReadWriteMany" }} - {{ .Values.chart.persistence.accessMode | default "ReadWriteMany" }}
{{- if .Values.persistence.storageClass }} {{- if .Values.chart.persistence.storageClass }}
storageClassName: {{ .Values.persistence.storageClass }} storageClassName: {{ .Values.chart.persistence.storageClass }}
{{- end }} {{- end }}
resources: resources:
requests: requests:
storage: {{ .Values.persistence.size | default "1Gi" }} storage: {{ .Values.chart.persistence.size | default "1Gi" }}
{{- end }} {{- end }}

View file

@ -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 apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
@ -7,7 +7,7 @@ metadata:
{{- include "labels" . | nindent 4 }} {{- include "labels" . | nindent 4 }}
type: Opaque type: Opaque
data: data:
{{- range $key, $value := .Values.secret.data }} {{- range $key, $value := .Values.chart.secret.data }}
{{ $key }}: {{ $value | b64enc | quote }} {{ $key }}: {{ $value | b64enc | quote }}
{{- end }} {{- end }}
{{- end }} {{- end }}

View file

@ -5,9 +5,9 @@ metadata:
labels: labels:
{{- include "labels" . | nindent 4 }} {{- include "labels" . | nindent 4 }}
spec: spec:
type: {{ .Values.service.type }} type: {{ .Values.chart.service.type }}
ports: ports:
- port: {{ .Values.service.port }} - port: {{ .Values.chart.service.port }}
targetPort: http targetPort: http
protocol: TCP protocol: TCP
name: http name: http

View file

@ -1,60 +1,71 @@
replicaCount: 1 chart:
replicaCount: 1
image: image:
repository: nginx repository: nginx
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
tag: "" tag: ""
service: service:
type: LoadBalancer 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:
port: 80 port: 80
initialDelaySeconds: 30 targetPort: 80
timeoutSeconds: 15
successThreshold: 1
failureThreshold: 3
periodSeconds: 10
readinessProbe: ingress:
tcpSocket: enabled: false
port: 80 className: "traefik"
initialDelaySeconds: 15 annotations: {}
timeoutSeconds: 2 hosts:
successThreshold: 1 - host: chart.example.com
failureThreshold: 3 paths:
periodSeconds: 10 - path: /
pathType: Prefix
tls: []
nodeSelector: {} config: {}
tolerations: []
affinity: {}
secret: persistence:
enabled: true enabled: false
existingSecretName: "" storageClass: "truenas-nfs-csi"
data: {} 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

View file

@ -2,7 +2,7 @@ apiVersion: v2
name: dokuwiki name: dokuwiki
description: A Helm chart for DokuWiki description: A Helm chart for DokuWiki
type: application type: application
version: 0.1.0 version: 1.0.2
appVersion: 2024-02-06b appVersion: 2024-02-06b
annotations: annotations:
artifacthub.io/images: "- name: dokuwiki\n image: dokuwiki/dokuwiki:2024-02-06b\n" artifacthub.io/images: "- name: dokuwiki\n image: dokuwiki/dokuwiki:2024-02-06b\n"

View file

@ -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 <pod-name> -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
```

View file

@ -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 }}

View file

@ -1,23 +1,23 @@
{{- if .Values.ingress.enabled -}} {{- if .Values.dokuwiki.ingress.enabled -}}
{{- $fullName := include "dokuwiki.fullname" . -}} {{- $fullName := include "dokuwiki.fullname" . -}}
{{- $svcPort := .Values.service.port -}} {{- $svcPort := .Values.dokuwiki.service.port -}}
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: {{ $fullName }} name: {{ $fullName }}
labels: labels:
{{- include "dokuwiki.labels" . | nindent 4 }} {{- include "dokuwiki.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }} {{- with .Values.dokuwiki.ingress.annotations }}
annotations: annotations:
{{- toYaml . | nindent 4 }} {{- toYaml . | nindent 4 }}
{{- end }} {{- end }}
spec: spec:
{{- if .Values.ingress.className }} {{- if .Values.dokuwiki.ingress.className }}
ingressClassName: {{ .Values.ingress.className }} ingressClassName: {{ .Values.dokuwiki.ingress.className }}
{{- end }} {{- end }}
{{- if .Values.ingress.tls }} {{- if .Values.dokuwiki.ingress.tls }}
tls: tls:
{{- range .Values.ingress.tls }} {{- range .Values.dokuwiki.ingress.tls }}
- hosts: - hosts:
{{- range .hosts }} {{- range .hosts }}
- {{ . | quote }} - {{ . | quote }}
@ -26,7 +26,7 @@ spec:
{{- end }} {{- end }}
{{- end }} {{- end }}
rules: rules:
{{- range .Values.ingress.hosts }} {{- range .Values.dokuwiki.ingress.hosts }}
- host: {{ .host | quote }} - host: {{ .host | quote }}
http: http:
paths: paths:

View file

@ -5,10 +5,10 @@ metadata:
labels: labels:
{{- include "dokuwiki.labels" . | nindent 4 }} {{- include "dokuwiki.labels" . | nindent 4 }}
spec: spec:
type: {{ .Values.service.type }} type: {{ .Values.dokuwiki.service.type }}
ports: ports:
- port: {{ .Values.service.port }} - port: {{ .Values.dokuwiki.service.port }}
targetPort: {{ .Values.service.targetPort }} targetPort: {{ .Values.dokuwiki.service.targetPort }}
protocol: TCP protocol: TCP
name: http name: http
selector: selector:

View file

@ -6,86 +6,84 @@ metadata:
{{- include "dokuwiki.labels" . | nindent 4 }} {{- include "dokuwiki.labels" . | nindent 4 }}
spec: spec:
serviceName: {{ include "dokuwiki.fullname" . }}-headless serviceName: {{ include "dokuwiki.fullname" . }}-headless
{{- if not .Values.autoscaling.enabled }} {{- if not .Values.dokuwiki.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }} replicas: {{ .Values.dokuwiki.replicaCount }}
{{- end }} {{- end }}
selector: selector:
matchLabels: matchLabels:
{{- include "dokuwiki.selectorLabels" . | nindent 6 }} {{- include "dokuwiki.selectorLabels" . | nindent 6 }}
template: template:
metadata: metadata:
{{- with .Values.podAnnotations }} {{- with .Values.dokuwiki.podAnnotations }}
annotations: annotations:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
labels: labels:
{{- include "dokuwiki.selectorLabels" . | nindent 8 }} {{- include "dokuwiki.selectorLabels" . | nindent 8 }}
spec: spec:
{{- with .Values.imagePullSecrets }} {{- with .Values.dokuwiki.imagePullSecrets }}
imagePullSecrets: imagePullSecrets:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
securityContext: securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }} {{- toYaml .Values.dokuwiki.podSecurityContext | nindent 8 }}
containers: containers:
- name: {{ .Chart.Name }} - name: {{ .Chart.Name }}
securityContext: securityContext:
{{- toYaml .Values.securityContext | nindent 12 }} {{- toYaml .Values.dokuwiki.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" image: "{{ .Values.dokuwiki.image.repository }}:{{ .Values.dokuwiki.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }} imagePullPolicy: {{ .Values.dokuwiki.image.pullPolicy }}
env: envFrom:
{{- range $key, $value := .Values.env }} - configMapRef:
- name: {{ $key }} name: {{ include "dokuwiki.fullname" . }}-config
value: {{ $value | quote }}
{{- end }}
ports: ports:
- name: http - name: http
containerPort: 8080 containerPort: 8080
protocol: TCP protocol: TCP
livenessProbe: livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }} {{- toYaml .Values.dokuwiki.livenessProbe | nindent 12 }}
readinessProbe: readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }} {{- toYaml .Values.dokuwiki.readinessProbe | nindent 12 }}
resources: resources:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.dokuwiki.resources | nindent 12 }}
volumeMounts: volumeMounts:
- name: data - name: data
mountPath: /storage mountPath: /storage
{{- if .Values.persistence.existingClaim }} {{- if .Values.dokuwiki.persistence.existingClaim }}
volumes: volumes:
- name: data - name: data
persistentVolumeClaim: persistentVolumeClaim:
claimName: {{ .Values.persistence.existingClaim }} claimName: {{ .Values.dokuwiki.persistence.existingClaim }}
{{- end }} {{- end }}
{{- with .Values.nodeSelector }} {{- with .Values.dokuwiki.nodeSelector }}
nodeSelector: nodeSelector:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
{{- with .Values.affinity }} {{- with .Values.dokuwiki.affinity }}
affinity: affinity:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
{{- with .Values.tolerations }} {{- with .Values.dokuwiki.tolerations }}
tolerations: tolerations:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
restartPolicy: Always restartPolicy: Always
automountServiceAccountToken: false automountServiceAccountToken: false
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} {{- if and .Values.dokuwiki.persistence.enabled (not .Values.dokuwiki.persistence.existingClaim) }}
volumeClaimTemplates: volumeClaimTemplates:
- metadata: - metadata:
name: data name: data
spec: spec:
accessModes: accessModes:
- {{ .Values.persistence.accessMode }} - {{ .Values.dokuwiki.persistence.accessMode }}
{{- if .Values.persistence.storageClass }} {{- if .Values.dokuwiki.persistence.storageClass }}
{{- if eq "-" .Values.persistence.storageClass }} {{- if eq "-" .Values.dokuwiki.persistence.storageClass }}
storageClassName: "" storageClassName: ""
{{- else }} {{- else }}
storageClassName: {{ .Values.persistence.storageClass | quote }} storageClassName: {{ .Values.dokuwiki.persistence.storageClass | quote }}
{{- end }} {{- end }}
{{- end }} {{- end }}
resources: resources:
requests: requests:
storage: {{ .Values.persistence.size }} storage: {{ .Values.dokuwiki.persistence.size }}
{{- end }} {{- end }}

View file

@ -1,66 +1,71 @@
replicaCount: 1 dokuwiki:
replicaCount: 1
image: image:
repository: dokuwiki/dokuwiki repository: dokuwiki/dokuwiki
pullpolicy: IfNotPresent pullpolicy: IfNotPresent
tag: "" tag: ""
imagePullSecrets: [] imagePullSecrets: []
podAnnotations: {} podAnnotations: {}
podSecurityContext: {} podSecurityContext: {}
securityContext: {} securityContext: {}
service: service:
type: LoadBalancer type: LoadBalancer
port: 8004 port: 8004
targetPort: 8080 targetPort: 8080
ingress: ingress:
enabled: true enabled: true
className: "" className: ""
annotations: {} annotations: {}
hosts: hosts:
- host: wiki.roboces.dev - host: wiki.roboces.dev
paths: paths:
- path: / - path: /
pathType: Prefix pathType: Prefix
tls: [] tls: []
persistence: config:
enabled: true php:
storageClass: "truenas-nfs-csi" timezone: Europe/Madrid
accessMode: ReadWriteMany memoryLimit: 512M
size: 2Gi uploadLimit: 128M
existingClaim: ""
autoscaling: persistence:
enabled: false enabled: true
storageClass: "truenas-nfs-csi"
accessMode: ReadWriteMany
size: 2Gi
existingClaim: ""
resources: {} autoscaling:
enabled: false
livenessProbe: resources: {}
tcpSocket:
port: 8080
initialDelaySeconds: 30
timeoutSeconds: 15
successThreshold: 1
failureThreshold: 3
periodSeconds: 10
readinessProbe: livenessProbe:
tcpSocket: tcpSocket:
port: 8080 port: 8080
initialDelaySeconds: 15 initialDelaySeconds: 30
timeoutSeconds: 2 timeoutSeconds: 15
successThreshold: 1 successThreshold: 1
failureThreshold: 3 failureThreshold: 3
periodSeconds: 10 periodSeconds: 10
nodeSelector: {} readinessProbe:
tolerations: [] tcpSocket:
affinity: {} port: 8080
initialDelaySeconds: 15
timeoutSeconds: 2
successThreshold: 1
failureThreshold: 3
periodSeconds: 10
env: nodeSelector: {}
PHP_TIMEZONE: Europe/Madrid tolerations: []
PHP_MEMORYLIMIT: 512M affinity: {}
PHP_UPLOADLIMIT: 128M
debug:
enabled: false

View file

@ -2,7 +2,7 @@ apiVersion: v2
name: helm-rustical name: helm-rustical
description: A Helm chart for Rustical WebDAV server description: A Helm chart for Rustical WebDAV server
type: application type: application
version: 0.3.0 version: 1.1.1
appVersion: 0.14.1 appVersion: 0.14.1
annotations: annotations:
artifacthub.io/images: "- name: rustical\n image: ghcr.io/lennart-k/rustical:0.14.1\n" artifacthub.io/images: "- name: rustical\n image: ghcr.io/lennart-k/rustical:0.14.1\n"

View file

@ -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 <pod-name> -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
```

View file

@ -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 }}

View file

@ -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 }}

View file

@ -1,23 +1,23 @@
{{- if .Values.ingress.enabled -}} {{- if .Values.rustical.ingress.enabled -}}
{{- $fullName := include "rustical.fullname" . -}} {{- $fullName := include "rustical.fullname" . -}}
{{- $svcPort := .Values.service.port -}} {{- $svcPort := .Values.rustical.service.port -}}
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: {{ $fullName }} name: {{ $fullName }}
labels: labels:
{{- include "rustical.labels" . | nindent 4 }} {{- include "rustical.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }} {{- with .Values.rustical.ingress.annotations }}
annotations: annotations:
{{- toYaml . | nindent 4 }} {{- toYaml . | nindent 4 }}
{{- end }} {{- end }}
spec: spec:
{{- if .Values.ingress.className }} {{- if .Values.rustical.ingress.className }}
ingressClassName: {{ .Values.ingress.className }} ingressClassName: {{ .Values.rustical.ingress.className }}
{{- end }} {{- end }}
{{- if .Values.ingress.tls }} {{- if .Values.rustical.ingress.tls }}
tls: tls:
{{- range .Values.ingress.tls }} {{- range .Values.rustical.ingress.tls }}
- hosts: - hosts:
{{- range .hosts }} {{- range .hosts }}
- {{ . | quote }} - {{ . | quote }}
@ -26,7 +26,7 @@ spec:
{{- end }} {{- end }}
{{- end }} {{- end }}
rules: rules:
{{- range .Values.ingress.hosts }} {{- range .Values.rustical.ingress.hosts }}
- host: {{ .host | quote }} - host: {{ .host | quote }}
http: http:
paths: paths:

View file

@ -5,10 +5,10 @@ metadata:
labels: labels:
{{- include "rustical.labels" . | nindent 4 }} {{- include "rustical.labels" . | nindent 4 }}
spec: spec:
type: {{ .Values.service.type }} type: {{ .Values.rustical.service.type }}
ports: ports:
- port: {{ .Values.service.port }} - port: {{ .Values.rustical.service.port }}
targetPort: {{ .Values.service.targetPort }} targetPort: {{ .Values.rustical.service.targetPort }}
protocol: TCP protocol: TCP
name: http name: http
selector: selector:

View file

@ -6,40 +6,35 @@ metadata:
{{- include "rustical.labels" . | nindent 4 }} {{- include "rustical.labels" . | nindent 4 }}
spec: spec:
serviceName: {{ include "rustical.fullname" . }}-headless serviceName: {{ include "rustical.fullname" . }}-headless
{{- if not .Values.autoscaling.enabled }} {{- if not .Values.rustical.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }} replicas: {{ .Values.rustical.replicaCount }}
{{- end }} {{- end }}
selector: selector:
matchLabels: matchLabels:
{{- include "rustical.selectorLabels" . | nindent 6 }} {{- include "rustical.selectorLabels" . | nindent 6 }}
template: template:
metadata: metadata:
{{- with .Values.podAnnotations }} {{- with .Values.rustical.podAnnotations }}
annotations: annotations:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
labels: labels:
{{- include "rustical.selectorLabels" . | nindent 8 }} {{- include "rustical.selectorLabels" . | nindent 8 }}
spec: spec:
{{- with .Values.imagePullSecrets }} {{- with .Values.rustical.imagePullSecrets }}
imagePullSecrets: imagePullSecrets:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
securityContext: securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }} {{- toYaml .Values.rustical.podSecurityContext | nindent 8 }}
{{- if .Values.debug.enabled }} {{- if .Values.rustical.debug.enabled }}
initContainers: initContainers:
- name: debug - name: debug
image: alpine:3.19 image: alpine:3.19
command: ["sleep", "infinity"] command: ["sleep", "infinity"]
envFrom: envFrom:
- secretRef: - secretRef:
name: {{ .Values.secret.existingSecretName }} name: {{ .Values.rustical.secret.existingSecretName }}
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
volumeMounts: volumeMounts:
- name: data - name: data
mountPath: /var/lib/rustical mountPath: /var/lib/rustical
@ -53,67 +48,64 @@ spec:
containers: containers:
- name: {{ .Chart.Name }} - name: {{ .Chart.Name }}
securityContext: securityContext:
{{- toYaml .Values.securityContext | nindent 12 }} {{- toYaml .Values.rustical.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" image: "{{ .Values.rustical.image.repository }}:{{ .Values.rustical.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullpolicy }} imagePullPolicy: {{ .Values.rustical.image.pullpolicy }}
envFrom: envFrom:
- configMapRef:
name: {{ include "rustical.fullname" . }}-config
- secretRef: - secretRef:
name: {{ .Values.secret.existingSecretName | default (include "rustical.fullname" .) }} name: {{ .Values.rustical.secret.existingSecretName | default (include "rustical.fullname" .) }}
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
ports: ports:
- name: http - name: http
containerPort: 4000 containerPort: 4000
protocol: TCP protocol: TCP
{{- if not .Values.debug.enabled }} {{- if not .Values.rustical.debug.enabled }}
livenessProbe: livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }} {{- toYaml .Values.rustical.livenessProbe | nindent 12 }}
readinessProbe: readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }} {{- toYaml .Values.rustical.readinessProbe | nindent 12 }}
{{- end }} {{- end }}
resources: resources:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.rustical.resources | nindent 12 }}
volumeMounts: volumeMounts:
- name: data - name: data
mountPath: /var/lib/rustical mountPath: /var/lib/rustical
{{- if .Values.persistence.existingClaim }} {{- if .Values.rustical.persistence.existingClaim }}
volumes: volumes:
- name: data - name: data
persistentVolumeClaim: persistentVolumeClaim:
claimName: {{ .Values.persistence.existingClaim }} claimName: {{ .Values.rustical.persistence.existingClaim }}
{{- end }} {{- end }}
{{- with .Values.nodeSelector }} {{- with .Values.rustical.nodeSelector }}
nodeSelector: nodeSelector:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
{{- with .Values.affinity }} {{- with .Values.rustical.affinity }}
affinity: affinity:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
{{- with .Values.tolerations }} {{- with .Values.rustical.tolerations }}
tolerations: tolerations:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
restartPolicy: Always restartPolicy: Always
automountServiceAccountToken: false automountServiceAccountToken: false
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} {{- if and .Values.rustical.persistence.enabled (not .Values.rustical.persistence.existingClaim) }}
volumeClaimTemplates: volumeClaimTemplates:
- metadata: - metadata:
name: data name: data
spec: spec:
accessModes: accessModes:
- {{ .Values.persistence.accessMode }} - {{ .Values.rustical.persistence.accessMode }}
{{- if .Values.persistence.storageClass }} {{- if .Values.rustical.persistence.storageClass }}
{{- if eq "-" .Values.persistence.storageClass }} {{- if eq "-" .Values.rustical.persistence.storageClass }}
storageClassName: "" storageClassName: ""
{{- else }} {{- else }}
storageClassName: {{ .Values.persistence.storageClass | quote }} storageClassName: {{ .Values.rustical.persistence.storageClass | quote }}
{{- end }} {{- end }}
{{- end }} {{- end }}
resources: resources:
requests: requests:
storage: {{ .Values.persistence.size }} storage: {{ .Values.rustical.persistence.size }}
{{- end }} {{- end }}

View file

@ -1,63 +1,67 @@
# Configuration is managed in k8s/argo-apps/rustical.yaml rustical:
replicaCount: 1 replicaCount: 1
image: image:
repository: ghcr.io/lennart-k/rustical repository: ghcr.io/lennart-k/rustical
pullpolicy: Always pullpolicy: Always
tag: "" tag: ""
imagePullSecrets: [] imagePullSecrets: []
podAnnotations: {} podAnnotations: {}
podSecurityContext: {} podSecurityContext: {}
securityContext: {} securityContext: {}
service: service:
type: ClusterIP 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:
port: 4000 port: 4000
initialDelaySeconds: 30 targetPort: 4000
timeoutSeconds: 15
successThreshold: 1
failureThreshold: 3
periodSeconds: 10
readinessProbe: ingress:
tcpSocket: enabled: false
port: 4000
initialDelaySeconds: 15
timeoutSeconds: 2
successThreshold: 1
failureThreshold: 3
periodSeconds: 10
nodeSelector: {} persistence:
tolerations: [] enabled: true
affinity: {} storageClass: "truenas-nfs-csi"
accessMode: ReadWriteMany
size: 50Gi
existingClaim: ""
env: {} autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
secret: resources: {}
enabled: true
existingSecretName: "rustical"
debug: livenessProbe:
enabled: false 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

View file

@ -2,7 +2,7 @@ apiVersion: v2
name: immich name: immich
description: A Helm chart for Immich photo and video backup solution description: A Helm chart for Immich photo and video backup solution
type: application type: application
version: 0.2.3 version: 1.0.1
appVersion: v3.1.0 appVersion: v3.1.0
keywords: keywords:
- immich - immich

192
k8s/charts/immich/README.md Normal file
View file

@ -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 <pod-name> -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
```

View file

@ -1,22 +1,31 @@
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: {{ include "immich.fullname" . }} name: {{ include "immich.fullname" . }}-config
labels: labels:
{{- include "immich.labels" . | nindent 4 }} {{- include "immich.labels" . | nindent 4 }}
data: data:
TZ: {{ .Values.immich.general.tz | quote }} TZ: {{ .Values.immich.config.general.timezone | quote }}
IMMICH_PORT: {{ .Values.immich.service.port | quote }} IMMICH_PORT: {{ .Values.immich.service.port | quote }}
IMMICH_MACHINE_LEARNING_URL: {{ printf "http://%s-machine-learning:3003" (include "immich.fullname" .) | quote }} IMMICH_MACHINE_LEARNING_URL: {{ printf "http://%s-machine-learning:3003" (include "immich.fullname" .) | quote }}
{{- range $key, $value := .Values.immich.db }} {{- if .Values.immich.config.database.hostname }}
{{- if and (ne $key "password") $value }} DB_HOSTNAME: {{ .Values.immich.config.database.hostname | quote }}
{{ $keyName := printf "DB_%s" (upper $key) }}
{{ $keyName }}: {{ $value | quote }}
{{- end }} {{- end }}
{{- if .Values.immich.config.database.port }}
DB_PORT: {{ .Values.immich.config.database.port | quote }}
{{- end }} {{- end }}
{{- range $key, $value := .Values.immich.redis }} {{- if .Values.immich.config.database.username }}
{{- if and (ne $key "password") (ne $key "username") $value }} DB_USERNAME: {{ .Values.immich.config.database.username | quote }}
{{ $keyName := printf "REDIS_%s" (upper $key) }}
{{ $keyName }}: {{ $value | quote }}
{{- end }} {{- 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 }} {{- end }}

View file

@ -62,6 +62,50 @@ spec:
mountPath: /data/profile mountPath: /data/profile
- name: backups - name: backups
mountPath: /data/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: containers:
- name: {{ .Chart.Name }}-server - name: {{ .Chart.Name }}-server
securityContext: securityContext:
@ -69,32 +113,30 @@ spec:
image: "{{ .Values.immich.image.repository }}:{{ .Values.immich.image.tag | default .Chart.AppVersion }}" image: "{{ .Values.immich.image.repository }}:{{ .Values.immich.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.immich.image.pullPolicy }} imagePullPolicy: {{ .Values.immich.image.pullPolicy }}
envFrom: envFrom:
{{- if .Values.secret.existingSecretName }} {{- if .Values.immich.secret.existingSecretName }}
- secretRef: - secretRef:
name: {{ .Values.secret.existingSecretName }} name: {{ .Values.immich.secret.existingSecretName }}
{{- else if .Values.secret.enabled }} {{- else if .Values.immich.secret.enabled }}
- secretRef: - secretRef:
name: {{ include "immich.fullname" . }} name: {{ include "immich.fullname" . }}
{{- end }} {{- end }}
- configMapRef: - configMapRef:
name: {{ include "immich.fullname" . }} name: {{ include "immich.fullname" . }}-config
env: env:
- name: IMMICH_PORT - name: IMMICH_PORT
value: {{ .Values.immich.service.port | quote }} value: {{ .Values.immich.service.port | quote }}
- name: IMMICH_MACHINE_LEARNING_URL - name: IMMICH_MACHINE_LEARNING_URL
value: {{ printf "http://%s-machine-learning:3003" (include "immich.fullname" .) | quote }} value: {{ printf "http://%s-machine-learning:3003" (include "immich.fullname" .) | quote }}
{{- range $key, $value := .Values.immich.env }} {{- if not .Values.immich.debug.enabled }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
ports:
- name: http
containerPort: 2283
protocol: TCP
livenessProbe: livenessProbe:
{{- toYaml .Values.immich.livenessProbe | nindent 12 }} {{- toYaml .Values.immich.livenessProbe | nindent 12 }}
readinessProbe: readinessProbe:
{{- toYaml .Values.immich.readinessProbe | nindent 12 }} {{- toYaml .Values.immich.readinessProbe | nindent 12 }}
{{- end }}
ports:
- name: http
containerPort: 2283
protocol: TCP
resources: resources:
{{- toYaml .Values.immich.resources | nindent 12 }} {{- toYaml .Values.immich.resources | nindent 12 }}
volumeMounts: volumeMounts:

View file

@ -106,7 +106,7 @@ spec:
requests: requests:
storage: {{ .Values.immich.persistence.backups.size }} storage: {{ .Values.immich.persistence.backups.size }}
{{- end }} {{- 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 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
@ -116,11 +116,11 @@ metadata:
{{- include "immich.labels" . | nindent 4 }} {{- include "immich.labels" . | nindent 4 }}
spec: spec:
accessModes: accessModes:
- {{ .Values.machineLearning.persistence.modelCache.accessMode }} - {{ .Values.immich.machineLearning.persistence.modelCache.accessMode }}
{{- if .Values.machineLearning.persistence.modelCache.storageClass }} {{- if .Values.immich.machineLearning.persistence.modelCache.storageClass }}
storageClassName: {{ .Values.machineLearning.persistence.modelCache.storageClass | quote }} storageClassName: {{ .Values.immich.machineLearning.persistence.modelCache.storageClass | quote }}
{{- end }} {{- end }}
resources: resources:
requests: requests:
storage: {{ .Values.machineLearning.persistence.modelCache.size }} storage: {{ .Values.immich.machineLearning.persistence.modelCache.size }}
{{- end }} {{- end }}

View file

@ -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 apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
@ -7,19 +7,19 @@ metadata:
{{- include "immich.labels" . | nindent 4 }} {{- include "immich.labels" . | nindent 4 }}
type: Opaque type: Opaque
stringData: stringData:
{{- if .Values.immich.db.password }} {{- if .Values.immich.config.database.password }}
DB_PASSWORD: {{ .Values.immich.db.password | quote }} DB_PASSWORD: {{ .Values.immich.config.database.password | quote }}
{{- end }} {{- end }}
{{- if .Values.immich.db.username }} {{- if .Values.immich.config.database.username }}
DB_USERNAME: {{ .Values.immich.db.username | quote }} DB_USERNAME: {{ .Values.immich.config.database.username | quote }}
{{- end }} {{- end }}
{{- if .Values.immich.redis.password }} {{- if .Values.immich.config.redis.password }}
REDIS_PASSWORD: {{ .Values.immich.redis.password | quote }} REDIS_PASSWORD: {{ .Values.immich.config.redis.password | quote }}
{{- end }} {{- end }}
{{- if .Values.immich.redis.username }} {{- if .Values.immich.config.redis.username }}
REDIS_USERNAME: {{ .Values.immich.redis.username | quote }} REDIS_USERNAME: {{ .Values.immich.config.redis.username | quote }}
{{- end }} {{- end }}
{{- range $key, $value := .Values.secret.data }} {{- range $key, $value := .Values.immich.secret.data }}
{{ $key }}: {{ $value | quote }} {{ $key }}: {{ $value | quote }}
{{- end }} {{- end }}
{{- end }} {{- end }}

View file

@ -15,7 +15,7 @@ spec:
{{- include "immich.selectorLabels" . | nindent 4 }} {{- include "immich.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: server app.kubernetes.io/component: server
--- ---
{{- if .Values.machineLearning.enabled }} {{- if .Values.immich.machineLearning.enabled }}
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
@ -24,9 +24,9 @@ metadata:
{{- include "immich.labels" . | nindent 4 }} {{- include "immich.labels" . | nindent 4 }}
app.kubernetes.io/component: machine-learning app.kubernetes.io/component: machine-learning
spec: spec:
type: {{ .Values.machineLearning.service.type }} type: {{ .Values.immich.machineLearning.service.type }}
ports: ports:
- port: {{ .Values.machineLearning.service.port }} - port: {{ .Values.immich.machineLearning.service.port }}
targetPort: http targetPort: http
protocol: TCP protocol: TCP
name: http name: http

View file

@ -1,4 +1,4 @@
{{- if .Values.machineLearning.enabled -}} {{- if .Values.immich.machineLearning.enabled -}}
apiVersion: apps/v1 apiVersion: apps/v1
kind: StatefulSet kind: StatefulSet
metadata: metadata:
@ -8,8 +8,8 @@ metadata:
app.kubernetes.io/component: machine-learning app.kubernetes.io/component: machine-learning
spec: spec:
serviceName: {{ include "immich.fullname" . }}-machine-learning-headless serviceName: {{ include "immich.fullname" . }}-machine-learning-headless
{{- if not .Values.machineLearning.autoscaling.enabled }} {{- if not .Values.immich.machineLearning.autoscaling.enabled }}
replicas: {{ .Values.machineLearning.replicaCount }} replicas: {{ .Values.immich.machineLearning.replicaCount }}
{{- end }} {{- end }}
selector: selector:
matchLabels: matchLabels:
@ -17,7 +17,7 @@ spec:
app.kubernetes.io/component: machine-learning app.kubernetes.io/component: machine-learning
template: template:
metadata: metadata:
{{- with .Values.machineLearning.podAnnotations }} {{- with .Values.immich.machineLearning.podAnnotations }}
annotations: annotations:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
@ -25,41 +25,41 @@ spec:
{{- include "immich.selectorLabels" . | nindent 8 }} {{- include "immich.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: machine-learning app.kubernetes.io/component: machine-learning
spec: spec:
{{- with .Values.machineLearning.imagePullSecrets }} {{- with .Values.immich.machineLearning.imagePullSecrets }}
imagePullSecrets: imagePullSecrets:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
securityContext: securityContext:
{{- toYaml .Values.machineLearning.podSecurityContext | nindent 8 }} {{- toYaml .Values.immich.machineLearning.podSecurityContext | nindent 8 }}
containers: containers:
- name: {{ .Chart.Name }}-machine-learning - name: {{ .Chart.Name }}-machine-learning
securityContext: securityContext:
{{- toYaml .Values.machineLearning.securityContext | nindent 12 }} {{- toYaml .Values.immich.machineLearning.securityContext | nindent 12 }}
image: "{{ .Values.machineLearning.image.repository }}:{{ .Values.machineLearning.image.tag | default .Chart.AppVersion }}" image: "{{ .Values.immich.machineLearning.image.repository }}:{{ .Values.immich.machineLearning.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.machineLearning.image.pullPolicy }} imagePullPolicy: {{ .Values.immich.machineLearning.image.pullPolicy }}
ports: ports:
- name: http - name: http
containerPort: 3003 containerPort: 3003
protocol: TCP protocol: TCP
env: env:
- name: IMMICH_PORT - name: IMMICH_PORT
value: {{ .Values.machineLearning.service.port | quote }} value: {{ .Values.immich.machineLearning.service.port | quote }}
- name: IMMICH_MACHINE_LEARNING_PORT - name: IMMICH_MACHINE_LEARNING_PORT
value: "3003" value: "3003"
resources: resources:
{{- toYaml .Values.machineLearning.resources | nindent 12 }} {{- toYaml .Values.immich.machineLearning.resources | nindent 12 }}
volumeMounts: volumeMounts:
- name: model-cache - name: model-cache
mountPath: /cache mountPath: /cache
{{- with .Values.machineLearning.nodeSelector }} {{- with .Values.immich.machineLearning.nodeSelector }}
nodeSelector: nodeSelector:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
{{- with .Values.machineLearning.affinity }} {{- with .Values.immich.machineLearning.affinity }}
affinity: affinity:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
{{- with .Values.machineLearning.tolerations }} {{- with .Values.immich.machineLearning.tolerations }}
tolerations: tolerations:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}

View file

@ -19,22 +19,21 @@ immich:
ingress: ingress:
enabled: false enabled: false
db: config:
hostname: "192.168.1.3" database:
port: 55432 hostname: ""
username: "" port: 55432
password: "" username: ""
database_name: "" password: ""
name: ""
redis: redis:
hostname: "192.168.1.3" hostname: ""
port: 300036 port: 6379
username: "" username: ""
password: "" password: ""
dbindex: "" dbIndex: ""
general:
general: timezone: Europe/Madrid
tz: "Europe/Madrid"
persistence: persistence:
library: library:
@ -104,66 +103,67 @@ immich:
tolerations: [] tolerations: []
affinity: {} affinity: {}
env: {} machineLearning:
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:
enabled: false enabled: false
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
resources: {} image:
repository: ghcr.io/immich-app/immich-machine-learning
pullPolicy: Always
tag: ""
livenessProbe: imagePullSecrets: []
tcpSocket: podAnnotations: {}
podSecurityContext: {}
securityContext: {}
service:
type: ClusterIP
port: 3003 port: 3003
initialDelaySeconds: 30
timeoutSeconds: 15
successThreshold: 1
failureThreshold: 3
periodSeconds: 10
readinessProbe: replicaCount: 1
tcpSocket:
port: 3003
initialDelaySeconds: 15
timeoutSeconds: 2
successThreshold: 1
failureThreshold: 3
periodSeconds: 10
nodeSelector: {} autoscaling:
tolerations: [] enabled: false
affinity: {} minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
persistence: resources: {}
modelCache:
enabled: true
storageClass: ""
accessMode: ReadWriteMany
size: 10Gi
secret: livenessProbe:
enabled: false tcpSocket:
existingSecretName: "" port: 3003
data: {} 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

View file

@ -2,9 +2,7 @@ apiVersion: v2
name: miniflux name: miniflux
description: A Helm chart for Miniflux RSS reader description: A Helm chart for Miniflux RSS reader
type: application type: application
version: 0.1.0 version: 1.0.2
appVersion: 2.3.3 appVersion: 2.3.3
annotations: annotations:
artifacthub.io/images: | artifacthub.io/images: "- name: miniflux\n image: miniflux/miniflux:2.3.3\n"
- name: miniflux
image: miniflux/miniflux:2.3.3

View file

@ -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 <pod-name> -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
```

View file

@ -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 }}

View file

@ -7,8 +7,8 @@ metadata:
annotations: annotations:
kube-score/ignore: pod-networkpolicy,deployment-has-host-podantiaffinity kube-score/ignore: pod-networkpolicy,deployment-has-host-podantiaffinity
spec: spec:
{{- if not .Values.autoscaling.enabled }} {{- if not .Values.miniflux.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }} replicas: {{ .Values.miniflux.replicaCount }}
{{- end }} {{- end }}
selector: selector:
matchLabels: matchLabels:
@ -20,52 +20,49 @@ spec:
type: RollingUpdate type: RollingUpdate
template: template:
metadata: metadata:
{{- with .Values.podAnnotations }} {{- with .Values.miniflux.podAnnotations }}
annotations: annotations:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
labels: labels:
{{- include "miniflux.selectorLabels" . | nindent 8 }} {{- include "miniflux.selectorLabels" . | nindent 8 }}
spec: spec:
{{- with .Values.imagePullSecrets }} {{- with .Values.miniflux.imagePullSecrets }}
imagePullSecrets: imagePullSecrets:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
securityContext: securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }} {{- toYaml .Values.miniflux.podSecurityContext | nindent 8 }}
containers: containers:
- name: {{ .Chart.Name }} - name: {{ .Chart.Name }}
securityContext: securityContext:
{{- toYaml .Values.securityContext | nindent 12 }} {{- toYaml .Values.miniflux.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" image: "{{ .Values.miniflux.image.repository }}:{{ .Values.miniflux.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }} imagePullPolicy: {{ .Values.miniflux.image.pullPolicy }}
envFrom: envFrom:
- configMapRef:
name: {{ include "miniflux.fullname" . }}-config
- secretRef: - secretRef:
name: {{ .Values.secret.existingSecretName | default (include "miniflux.fullname" .) }} name: {{ .Values.miniflux.secret.existingSecretName | default (include "miniflux.fullname" .) }}
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
ports: ports:
- name: http - name: http
containerPort: 8080 containerPort: 8080
protocol: TCP protocol: TCP
livenessProbe: livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }} {{- toYaml .Values.miniflux.livenessProbe | nindent 12 }}
readinessProbe: readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }} {{- toYaml .Values.miniflux.readinessProbe | nindent 12 }}
resources: resources:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.miniflux.resources | nindent 12 }}
{{- with .Values.nodeSelector }} {{- with .Values.miniflux.nodeSelector }}
nodeSelector: nodeSelector:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
{{- with .Values.affinity }} {{- with .Values.miniflux.affinity }}
affinity: affinity:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
{{- with .Values.tolerations }} {{- with .Values.miniflux.tolerations }}
tolerations: tolerations:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}

View file

@ -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 }}

View file

@ -1,23 +1,23 @@
{{- if .Values.ingress.enabled -}} {{- if .Values.miniflux.ingress.enabled -}}
{{- $fullName := include "miniflux.fullname" . -}} {{- $fullName := include "miniflux.fullname" . -}}
{{- $svcPort := .Values.service.port -}} {{- $svcPort := .Values.miniflux.service.port -}}
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: {{ $fullName }} name: {{ $fullName }}
labels: labels:
{{- include "miniflux.labels" . | nindent 4 }} {{- include "miniflux.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }} {{- with .Values.miniflux.ingress.annotations }}
annotations: annotations:
{{- toYaml . | nindent 4 }} {{- toYaml . | nindent 4 }}
{{- end }} {{- end }}
spec: spec:
{{- if .Values.ingress.className }} {{- if .Values.miniflux.ingress.className }}
ingressClassName: {{ .Values.ingress.className }} ingressClassName: {{ .Values.miniflux.ingress.className }}
{{- end }} {{- end }}
{{- if .Values.ingress.tls }} {{- if .Values.miniflux.ingress.tls }}
tls: tls:
{{- range .Values.ingress.tls }} {{- range .Values.miniflux.ingress.tls }}
- hosts: - hosts:
{{- range .hosts }} {{- range .hosts }}
- {{ . | quote }} - {{ . | quote }}
@ -26,7 +26,7 @@ spec:
{{- end }} {{- end }}
{{- end }} {{- end }}
rules: rules:
{{- range .Values.ingress.hosts }} {{- range .Values.miniflux.ingress.hosts }}
- host: {{ .host | quote }} - host: {{ .host | quote }}
http: http:
paths: paths:

View file

@ -1,4 +1,4 @@
{{- if .Values.podDisruptionBudget.enabled -}} {{- if .Values.miniflux.podDisruptionBudget.enabled -}}
apiVersion: policy/v1 apiVersion: policy/v1
kind: PodDisruptionBudget kind: PodDisruptionBudget
metadata: metadata:
@ -6,11 +6,11 @@ metadata:
labels: labels:
{{- include "miniflux.labels" . | nindent 4 }} {{- include "miniflux.labels" . | nindent 4 }}
spec: spec:
{{- if .Values.podDisruptionBudget.minAvailable }} {{- if .Values.miniflux.podDisruptionBudget.minAvailable }}
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} minAvailable: {{ .Values.miniflux.podDisruptionBudget.minAvailable }}
{{- end }} {{- end }}
{{- if .Values.podDisruptionBudget.maxUnavailable }} {{- if .Values.miniflux.podDisruptionBudget.maxUnavailable }}
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} maxUnavailable: {{ .Values.miniflux.podDisruptionBudget.maxUnavailable }}
{{- end }} {{- end }}
selector: selector:
matchLabels: matchLabels:

View file

@ -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 apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
@ -7,7 +7,7 @@ metadata:
{{- include "miniflux.labels" . | nindent 4 }} {{- include "miniflux.labels" . | nindent 4 }}
type: Opaque type: Opaque
stringData: stringData:
{{- range $key, $value := .Values.secret.data }} {{- range $key, $value := .Values.miniflux.secret.data }}
{{ $key }}: {{ $value | quote }} {{ $key }}: {{ $value | quote }}
{{- end }} {{- end }}
{{- end }} {{- end }}

View file

@ -5,10 +5,10 @@ metadata:
labels: labels:
{{- include "miniflux.labels" . | nindent 4 }} {{- include "miniflux.labels" . | nindent 4 }}
spec: spec:
type: {{ .Values.service.type }} type: {{ .Values.miniflux.service.type }}
ports: ports:
- port: {{ .Values.service.port }} - port: {{ .Values.miniflux.service.port }}
targetPort: {{ .Values.service.targetPort }} targetPort: {{ .Values.miniflux.service.targetPort }}
protocol: TCP protocol: TCP
name: http name: http
selector: selector:

View file

@ -1,42 +1,68 @@
# Configuration is managed in k8s/argo-apps/miniflux.yaml miniflux:
replicaCount: 1 replicaCount: 1
image: image:
repository: miniflux/miniflux repository: miniflux/miniflux
pullpolicy: always pullpolicy: always
tag: "" tag: ""
imagePullSecrets: [] imagePullSecrets: []
podAnnotations: {} podAnnotations: {}
podSecurityContext: {} podSecurityContext: {}
securityContext: {} securityContext: {}
service: service:
type: ClusterIP type: ClusterIP
port: 8888 port: 8888
targetPort: 8080 targetPort: 8080
ingress: ingress:
enabled: false 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: {} resources: {}
readinessProbe: {}
autoscaling: livenessProbe: {}
enabled: false readinessProbe: {}
nodeSelector: {} autoscaling:
tolerations: [] enabled: false
affinity: {} minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
podDisruptionBudget: nodeSelector: {}
enabled: false tolerations: []
affinity: {}
env: {} podDisruptionBudget:
enabled: false
secret: secret:
enabled: false enabled: false
existingSecretName: "" existingSecretName: ""
data: {} data: {}
debug:
enabled: false

View file

@ -5,7 +5,7 @@ description: 'Ultra-fast, secure & lightweight self-hosted cloud storage — you
' '
type: application type: application
version: 1.0.0 version: 1.0.1
appVersion: 0.8.6 appVersion: 0.8.6
annotations: annotations:
artifacthub.io/images: "- name: oxicloud\n image: diocrafts/oxicloud:0.8.6\n" artifacthub.io/images: "- name: oxicloud\n image: diocrafts/oxicloud:0.8.6\n"

View file

@ -73,6 +73,7 @@ oxicloud:
| `oxicloud.persistence.size` | string | `50Gi` | PVC size | | `oxicloud.persistence.size` | string | `50Gi` | PVC size |
| `oxicloud.secret.existingSecretName` | string | `""` | Existing secret name | | `oxicloud.secret.existingSecretName` | string | `""` | Existing secret name |
| `oxicloud.wopi.enabled` | bool | `false` | Enable WOPI/Collabora integration | | `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.enabled` | bool | `false` | Enable ingress |
| `oxicloud.ingress.className` | string | `""` | Ingress class | | `oxicloud.ingress.className` | string | `""` | Ingress class |
| `oxicloud.resources` | object | `{}` | Container resources | | `oxicloud.resources` | object | `{}` | Container resources |
@ -115,6 +116,24 @@ Required secret keys depend on configuration:
## Troubleshooting ## 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 <pod-name> -c debug -- sh
```
When debug mode is enabled, liveness and readiness probes are disabled to prevent restarts.
### Pod not starting ### Pod not starting
Check if the PVC is bound: Check if the PVC is bound:

View file

@ -1,4 +1,3 @@
---
{{- if .Values.oxicloud.ingress.enabled -}} {{- if .Values.oxicloud.ingress.enabled -}}
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
@ -51,7 +50,6 @@ spec:
port: port:
number: {{ .Values.oxicloud.wopi.collabora.service.port }} number: {{ .Values.oxicloud.wopi.collabora.service.port }}
{{- end }} {{- end }}
- path: / - path: /
pathType: Prefix pathType: Prefix
backend: backend:

View file

@ -85,3 +85,6 @@ oxicloud:
enabled: true enabled: true
existingSecretName: "oxicloud-secret" existingSecretName: "oxicloud-secret"
data: {} data: {}
debug:
enabled: false

View file

@ -2,9 +2,7 @@ apiVersion: v2
name: vaultwarden name: vaultwarden
description: A Helm chart for Vaultwarden password manager description: A Helm chart for Vaultwarden password manager
type: application type: application
version: 0.1.0 version: 1.0.2
appVersion: 1.37.1 appVersion: 1.37.1
annotations: annotations:
artifacthub.io/images: | artifacthub.io/images: "- name: vaultwarden\n image: vaultwarden/server:1.37.1-alpine\n"
- name: vaultwarden
image: vaultwarden/server:1.37.1-alpine

View file

@ -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 <pod-name> -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
```

View file

@ -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 }}

View file

@ -1,23 +1,23 @@
{{- if .Values.ingress.enabled -}} {{- if .Values.vaultwarden.ingress.enabled -}}
{{- $fullName := include "vaultwarden.fullname" . -}} {{- $fullName := include "vaultwarden.fullname" . -}}
{{- $svcPort := .Values.service.port -}} {{- $svcPort := .Values.vaultwarden.service.port -}}
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: {{ $fullName }} name: {{ $fullName }}
labels: labels:
{{- include "vaultwarden.labels" . | nindent 4 }} {{- include "vaultwarden.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }} {{- with .Values.vaultwarden.ingress.annotations }}
annotations: annotations:
{{- toYaml . | nindent 4 }} {{- toYaml . | nindent 4 }}
{{- end }} {{- end }}
spec: spec:
{{- if .Values.ingress.className }} {{- if .Values.vaultwarden.ingress.className }}
ingressClassName: {{ .Values.ingress.className }} ingressClassName: {{ .Values.vaultwarden.ingress.className }}
{{- end }} {{- end }}
{{- if .Values.ingress.tls }} {{- if .Values.vaultwarden.ingress.tls }}
tls: tls:
{{- range .Values.ingress.tls }} {{- range .Values.vaultwarden.ingress.tls }}
- hosts: - hosts:
{{- range .hosts }} {{- range .hosts }}
- {{ . | quote }} - {{ . | quote }}
@ -26,7 +26,7 @@ spec:
{{- end }} {{- end }}
{{- end }} {{- end }}
rules: rules:
{{- range .Values.ingress.hosts }} {{- range .Values.vaultwarden.ingress.hosts }}
- host: {{ .host | quote }} - host: {{ .host | quote }}
http: http:
paths: paths:

View file

@ -5,10 +5,10 @@ metadata:
labels: labels:
{{- include "vaultwarden.labels" . | nindent 4 }} {{- include "vaultwarden.labels" . | nindent 4 }}
spec: spec:
type: {{ .Values.service.type }} type: {{ .Values.vaultwarden.service.type }}
ports: ports:
- port: {{ .Values.service.port }} - port: {{ .Values.vaultwarden.service.port }}
targetPort: {{ .Values.service.targetPort }} targetPort: {{ .Values.vaultwarden.service.targetPort }}
protocol: TCP protocol: TCP
name: http name: http
selector: selector:

View file

@ -6,103 +6,86 @@ metadata:
{{- include "vaultwarden.labels" . | nindent 4 }} {{- include "vaultwarden.labels" . | nindent 4 }}
spec: spec:
serviceName: {{ include "vaultwarden.fullname" . }}-headless serviceName: {{ include "vaultwarden.fullname" . }}-headless
{{- if not .Values.autoscaling.enabled }} {{- if not .Values.vaultwarden.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }} replicas: {{ .Values.vaultwarden.replicaCount }}
{{- end }} {{- end }}
selector: selector:
matchLabels: matchLabels:
{{- include "vaultwarden.selectorLabels" . | nindent 6 }} {{- include "vaultwarden.selectorLabels" . | nindent 6 }}
template: template:
metadata: metadata:
{{- with .Values.podAnnotations }} {{- with .Values.vaultwarden.podAnnotations }}
annotations: annotations:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
labels: labels:
{{- include "vaultwarden.selectorLabels" . | nindent 8 }} {{- include "vaultwarden.selectorLabels" . | nindent 8 }}
spec: spec:
{{- with .Values.imagePullSecrets }} {{- with .Values.vaultwarden.imagePullSecrets }}
imagePullSecrets: imagePullSecrets:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
securityContext: securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }} {{- toYaml .Values.vaultwarden.podSecurityContext | nindent 8 }}
containers: containers:
- name: {{ .Chart.Name }} - name: {{ .Chart.Name }}
securityContext: securityContext:
{{- toYaml .Values.securityContext | nindent 12 }} {{- toYaml .Values.vaultwarden.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (printf "%s-alpine" .Chart.AppVersion) }}" image: "{{ .Values.vaultwarden.image.repository }}:{{ .Values.vaultwarden.image.tag | default (printf "%s-alpine" .Chart.AppVersion) }}"
imagePullPolicy: {{ .Values.image.pullPolicy }} imagePullPolicy: {{ .Values.vaultwarden.image.pullPolicy }}
envFrom: envFrom:
- configMapRef:
name: {{ include "vaultwarden.fullname" . }}-config
- secretRef: - secretRef:
name: {{ .Values.secret.existingSecretName | default (include "vaultwarden.fullname" .) }} name: {{ .Values.vaultwarden.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 }}
ports: ports:
- name: http - name: http
containerPort: 80 containerPort: 80
protocol: TCP protocol: TCP
livenessProbe: livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }} {{- toYaml .Values.vaultwarden.livenessProbe | nindent 12 }}
readinessProbe: readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }} {{- toYaml .Values.vaultwarden.readinessProbe | nindent 12 }}
resources: resources:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.vaultwarden.resources | nindent 12 }}
volumeMounts: volumeMounts:
- name: data - name: data
mountPath: /data mountPath: /data
{{- if .Values.persistence.existingClaim }} {{- if .Values.vaultwarden.persistence.existingClaim }}
volumes: volumes:
- name: data - name: data
persistentVolumeClaim: persistentVolumeClaim:
claimName: {{ .Values.persistence.existingClaim }} claimName: {{ .Values.vaultwarden.persistence.existingClaim }}
{{- end }} {{- end }}
{{- with .Values.nodeSelector }} {{- with .Values.vaultwarden.nodeSelector }}
nodeSelector: nodeSelector:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
{{- with .Values.affinity }} {{- with .Values.vaultwarden.affinity }}
affinity: affinity:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
{{- with .Values.tolerations }} {{- with .Values.vaultwarden.tolerations }}
tolerations: tolerations:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
restartPolicy: Always restartPolicy: Always
automountServiceAccountToken: false automountServiceAccountToken: false
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} {{- if and .Values.vaultwarden.persistence.enabled (not .Values.vaultwarden.persistence.existingClaim) }}
volumeClaimTemplates: volumeClaimTemplates:
- metadata: - metadata:
name: data name: data
spec: spec:
accessModes: accessModes:
- {{ .Values.persistence.accessMode }} - {{ .Values.vaultwarden.persistence.accessMode }}
{{- if .Values.persistence.storageClass }} {{- if .Values.vaultwarden.persistence.storageClass }}
{{- if eq "-" .Values.persistence.storageClass }} {{- if eq "-" .Values.vaultwarden.persistence.storageClass }}
storageClassName: "" storageClassName: ""
{{- else }} {{- else }}
storageClassName: {{ .Values.persistence.storageClass | quote }} storageClassName: {{ .Values.vaultwarden.persistence.storageClass | quote }}
{{- end }} {{- end }}
{{- end }} {{- end }}
resources: resources:
requests: requests:
storage: {{ .Values.persistence.size }} storage: {{ .Values.vaultwarden.persistence.size }}
{{- end }} {{- end }}

View file

@ -1,75 +1,76 @@
# Configuration is managed in k8s/argo-apps/vaultwarden.yaml vaultwarden:
replicaCount: 1 replicaCount: 1
image: image:
repository: vaultwarden/server repository: vaultwarden/server
pullpolicy: Always pullpolicy: Always
tag: "" tag: ""
imagePullSecrets: [] imagePullSecrets: []
podAnnotations: {} podAnnotations: {}
podSecurityContext: {} podSecurityContext: {}
securityContext: {} securityContext: {}
service: service:
type: ClusterIP type: ClusterIP
port: 80 port: 80
targetPort: 80 targetPort: 80
ingress: ingress:
enabled: false enabled: false
conf: config:
db: database:
database_url: "" url: ""
enable_db_wal: true enableWal: true
connection_retries: 15 connectionRetries: 15
timeout: 30 timeout: 30
idle_timeout: 600 idleTimeout: 600
min_conns: 2 minConns: 2
max_conns: 10 maxConns: 10
conn_init: "" connInit: ""
web_vault: webVault:
enabled: true
folder: "web-vault/"
persistence:
enabled: true enabled: true
folder: "web-vault/" storageClass: "truenas-nfs-csi"
accessMode: ReadWriteMany
size: 5Gi
existingClaim: ""
persistence: autoscaling:
enabled: true enabled: false
storageClass: "truenas-nfs-csi"
accessMode: ReadWriteMany
size: 5Gi
existingClaim: ""
autoscaling: resources: {}
enabled: false
resources: {} livenessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 30
timeoutSeconds: 15
successThreshold: 1
failureThreshold: 3
periodSeconds: 10
livenessProbe: readinessProbe:
tcpSocket: tcpSocket:
port: 80 port: 80
initialDelaySeconds: 30 initialDelaySeconds: 15
timeoutSeconds: 15 timeoutSeconds: 2
successThreshold: 1 successThreshold: 1
failureThreshold: 3 failureThreshold: 3
periodSeconds: 10 periodSeconds: 10
readinessProbe: nodeSelector: {}
tcpSocket: tolerations: []
port: 80 affinity: {}
initialDelaySeconds: 15
timeoutSeconds: 2
successThreshold: 1
failureThreshold: 3
periodSeconds: 10
nodeSelector: {} secret:
tolerations: [] enabled: true
affinity: {} existingSecretName: "vaultwarden-kubernetes-secrets"
data: {}
env: {} debug:
enabled: false
secret:
enabled: true
existingSecretName: "vaultwarden-kubernetes-secrets"
data: {}

View file

@ -19,22 +19,10 @@ spec:
- https://git.roboces.dev/catalin/fukuops.git - https://git.roboces.dev/catalin/fukuops.git
- https://charts.goauthentik.io/ - https://charts.goauthentik.io/
- https://kubereboot.github.io/charts - 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://portainer.github.io/k8s/
- https://docs.renovatebot.com/helm-charts - https://docs.renovatebot.com/helm-charts
- https://meilisearch.github.io/meilisearch-kubernetes - 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 - ghcr.io/antoniolago/charts
- https://helm.elastic.co
- https://repo.helmforge.dev - https://repo.helmforge.dev
- https://harish2k01.github.io/helm-charts/ - https://harish2k01.github.io/helm-charts/
- git.roboces.dev/catalin/fukuops - git.roboces.dev/catalin/fukuops
- git.roboces.dev/catalin

View file

@ -25,8 +25,3 @@ spec:
- https://git.roboces.dev/catalin/fukuops.git - https://git.roboces.dev/catalin/fukuops.git
- https://democratic-csi.github.io/charts/ - https://democratic-csi.github.io/charts/
- https://bitnami-labs.github.io/sealed-secrets - 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

View file

@ -14,9 +14,6 @@ spec:
- group: "" - group: ""
kind: PersistentVolume kind: PersistentVolume
sourceRepos: sourceRepos:
- https://git.roboces.dev/catalin/fukuops.git
- code.forgejo.org/forgejo-helm - code.forgejo.org/forgejo-helm
- git.roboces.dev/catalin/fukuops - git.roboces.dev/catalin/fukuops
- git.roboces.dev/catalin
- https://git.roboces.dev/catalin/huesoporro.git - https://git.roboces.dev/catalin/huesoporro.git
- https://gitlab.com/api/v4/projects/64552889/packages/helm/release

174
scripts/source-repos.sh Executable file
View file

@ -0,0 +1,174 @@
#!/usr/bin/env bash
set -euo pipefail
PROJECTS_DIR="k8s/services/argo"
APPS_DIR="k8s/argo-apps"
usage() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
List and manage source repos in ArgoCD projects.
OPTIONS:
-l, --list List all source repos (default)
-u, --unused List unused source repos
-d, --delete-unused Delete unused source repos from projects
-h, --help Show this help message
Examples:
$(basename "$0") # List all source repos
$(basename "$0") -u # List unused source repos
$(basename "$0") -d # Delete unused source repos
EOF
}
get_all_project_repos() {
awk '/^[[:space:]]*sourceRepos:/,/^[[:space:]]*[a-z]+:/' "$PROJECTS_DIR"/project-*.yaml | \
grep -E '^[[:space:]]+-[[:space:]]+' | \
sed 's/^[[:space:]]*- //' | \
sort -u
}
get_all_app_repos() {
grep -E '(^\s+-\s+repoURL:|^\s+repoURL:)' "$APPS_DIR"/*.yaml 2>/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

View file

@ -1,9 +1,10 @@
terraform { terraform {
required_version = ">= 1.6" required_version = ">= 1.6"
backend "s3" { backend "s3" {
bucket = "fuku-terraform" bucket = "fuku-terraform"
key = "adguard/terraform" key = "adguard/terraform"
region = "us-east-1" region = "us-east-1"
use_lockfile = "true"
} }
required_providers { required_providers {

View file

@ -1,9 +1,10 @@
terraform { terraform {
required_version = ">= 1.6" required_version = ">= 1.6"
backend "s3" { backend "s3" {
bucket = "fuku-terraform" bucket = "fuku-terraform"
key = "authentik/terraform" key = "authentik/terraform"
region = "us-east-1" region = "us-east-1"
use_lockfile = "true"
} }
required_providers { required_providers {
authentik = { authentik = {
@ -101,21 +102,6 @@ module "portainer" {
sub_mode = "hashed_user_id" 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" { module "sonarr" {
source = "../modules/authentik-proxy" 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" { module "cloud" {
source = "../modules/authentik-oidc" source = "../modules/authentik-oidc"
app_name = "Cloud" app_name = "Cloud"

View file

@ -12,5 +12,5 @@ TF_VAR_rustical_client_id=
TF_VAR_rustical_client_secret= TF_VAR_rustical_client_secret=
TF_VAR_ganymede_client_id= TF_VAR_ganymede_client_id=
TF_VAR_ganymede_client_secret= TF_VAR_ganymede_client_secret=
TF_VAR_oxicloud_client_id=aef61f77326b813cf8d8ba71d1ac994b5642685ca37e4710ab0079e91d87702d55fd9775d473b05aff45603bf08e78dba26850af3a815f3c3ac171d163368aa0 TF_VAR_oxicloud_client_id=
TF_VAR_oxicloud_client_secret=a4038df17c9fd06f86372aeaaae8f3fd1374d8978983af7b398d948ef15d1efe522a1faa2fc7652bc410c516d96cd2e4211dad4e05ba6297bdd8d9090460d5fc TF_VAR_oxicloud_client_secret=