diff --git a/apps/templates/sogo-helm.yaml b/apps/templates/sogo-helm.yaml new file mode 100644 index 0000000..2ecbfd4 --- /dev/null +++ b/apps/templates/sogo-helm.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: sogo + namespace: argocd +spec: + project: default + source: + repoURL: https://github.com/rubenhensen/k8scd.git + targetRevision: HEAD + path: sogo + destination: + server: https://kubernetes.default.svc + namespace: sogo + syncPolicy: + automated: + selfHeal: true + prune: true + syncOptions: + - CreateNamespace=true diff --git a/sogo/Chart.yaml b/sogo/Chart.yaml new file mode 100644 index 0000000..491a681 --- /dev/null +++ b/sogo/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +name: sogo +description: Helm chart for SOGo groupware server +version: 0.1.0 +appVersion: "latest" diff --git a/sogo/templates/_helpers.tpl b/sogo/templates/_helpers.tpl new file mode 100644 index 0000000..db25d09 --- /dev/null +++ b/sogo/templates/_helpers.tpl @@ -0,0 +1,60 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "sogo.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "sogo.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "sogo.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "sogo.labels" -}} +helm.sh/chart: {{ include "sogo.chart" . }} +{{ include "sogo.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "sogo.selectorLabels" -}} +app.kubernetes.io/name: {{ include "sogo.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +PostgreSQL connection URL +*/}} +{{- define "sogo.postgresqlURL" -}} +{{- $user := .Values.postgresql.username -}} +{{- $host := printf "%s-postgres-rw.%s.svc.cluster.local" (include "sogo.fullname" .) .Release.Namespace -}} +{{- $port := "5432" -}} +{{- $db := .Values.postgresql.database -}} +{{- printf "postgresql://%s:${POSTGRES_PASSWORD}@%s:%s/%s?sslmode=require" $user $host $port $db -}} +{{- end }} diff --git a/sogo/templates/configmap.yaml b/sogo/templates/configmap.yaml new file mode 100644 index 0000000..8993865 --- /dev/null +++ b/sogo/templates/configmap.yaml @@ -0,0 +1,88 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "sogo.fullname" . }}-config + labels: + {{- include "sogo.labels" . | nindent 4 }} +data: + 01-database.yaml: | + OCSFolderInfoURL: {{ include "sogo.postgresqlURL" . }}/sogo_folder_info + OCSSessionsFolderURL: {{ include "sogo.postgresqlURL" . }}/sogo_sessions_folder + OCSEMailAlarmsFolderURL: {{ include "sogo.postgresqlURL" . }}/sogo_alarms_folder + OCSStoreURL: {{ include "sogo.postgresqlURL" . }}/sogo_store + OCSAclURL: {{ include "sogo.postgresqlURL" . }}/sogo_acl + OCSCacheFolderURL: {{ include "sogo.postgresqlURL" . }}/sogo_cache_folder + + 02-mail.yaml: | + SOGoIMAPServer: {{ .Values.config.mail.imapServer }} + SOGoIMAPAclConformsToIMAPExt: YES + SOGoMailDomain: {{ .Values.config.domain }} + SOGoMailingMechanism: smtp + SOGoSMTPServer: {{ .Values.config.mail.smtpServer }}:{{ .Values.config.mail.smtpPort }} + SOGoMailSpoolPath: /var/spool/sogo + SOGoSentFolderName: Sent + SOGoTrashFolderName: Trash + SOGoDraftsFolderName: Drafts + SOGoJunkFolderName: Junk + SOGoForceExternalLoginWithEmail: YES + + 03-general.yaml: | + SOGoLanguage: {{ .Values.config.language }} + SOGoTimeZone: {{ .Values.config.timezone }} + SOGoSuperUsername: {{ .Values.config.adminUser }} + SOGoProfileURL: {{ include "sogo.postgresqlURL" . }}/sogo_user_profile + SOGoUserSources: + - type: sql + id: users + viewURL: {{ include "sogo.postgresqlURL" . }}/sogo_view + canAuthenticate: YES + isAddressBook: YES + userPasswordAlgorithm: none + {{- if .Values.config.modules.mail }} + SOGoMailAuxiliaryUserAccountsEnabled: YES + {{- end }} + {{- if .Values.config.modules.calendar }} + SOGoCalendarDefaultRoles: + - PublicViewer + - ConfidentialDAndTViewer + {{- end }} + + 04-calendar.yaml: | + {{- if .Values.config.modules.calendar }} + SOGoFirstDayOfWeek: {{ .Values.config.calendar.firstDayOfWeek }} + SOGoDayStartTime: {{ .Values.config.calendar.dayStartTime }} + SOGoDayEndTime: {{ .Values.config.calendar.dayEndTime }} + SOGoTimeFormat: {{ .Values.config.calendar.timeFormat | quote }} + SOGoCalendarEventsDefaultClassification: PUBLIC + SOGoFirstWeekOfYear: January1 + {{- end }} + + 05-authentication.yaml: | + SOGoAuthenticationType: {{ .Values.config.mail.authenticationType }} + {{- if eq .Values.config.mail.authenticationType "IMAP" }} + SOGoIMAPServer: {{ .Values.config.mail.imapServer }}:{{ .Values.config.mail.imapPort }} + {{- end }} + + 06-webui.yaml: | + SOGoPageTitle: {{ .Values.config.webui.pageTitle | quote }} + SOGoLoginModule: Mail + SOGoRefreshViewCheck: every_minute + SOGoMailReloadOnArrival: YES + SOGoMailMessageCheck: every_5_minutes + SOGoMailPollingIntervals: (1, 5, 10, 20, 30, 60) + + 07-security.yaml: | + SOGoXSRFValidation: YES + SOGoPasswordChangeEnabled: NO + SOGoForceSSL: NO + WOUseRelativeURLs: NO + WOApplicationRedirectURL: /SOGo + + 08-performance.yaml: | + WOWorkersCount: {{ .Values.config.workersCount }} + WOListenQueueSize: {{ .Values.config.listenQueueSize }} + SOGoMaximumPingInterval: 3540 + SOGoMaximumSyncInterval: 3540 + SOGoInternalSyncInterval: 30 + NGImap4DisableIMAP4Pooling: NO + SOGoMaximumMessageSizeLimit: 52428800 diff --git a/sogo/templates/deployment.yaml b/sogo/templates/deployment.yaml new file mode 100644 index 0000000..362e0c2 --- /dev/null +++ b/sogo/templates/deployment.yaml @@ -0,0 +1,64 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "sogo.fullname" . }} + labels: + {{- include "sogo.labels" . | nindent 4 }} +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + {{- include "sogo.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + labels: + {{- include "sogo.selectorLabels" . | nindent 8 }} + spec: + containers: + - name: sogo + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 80 + protocol: TCP + env: + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "sogo.fullname" . }}-postgres-credentials + key: password + volumeMounts: + - name: config + mountPath: /etc/sogo/sogo.conf.d + - name: data + mountPath: /var/spool/sogo + livenessProbe: + httpGet: + path: /SOGo + port: http + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 10 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /SOGo + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + {{- toYaml .Values.resources | nindent 10 }} + volumes: + - name: config + configMap: + name: {{ include "sogo.fullname" . }}-config + - name: data + persistentVolumeClaim: + claimName: {{ include "sogo.fullname" . }}-data diff --git a/sogo/templates/external-secret.yaml b/sogo/templates/external-secret.yaml new file mode 100644 index 0000000..85bab71 --- /dev/null +++ b/sogo/templates/external-secret.yaml @@ -0,0 +1,24 @@ +{{- if .Values.externalSecret.enabled }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: {{ include "sogo.fullname" . }}-postgres-credentials + labels: + {{- include "sogo.labels" . | nindent 4 }} +spec: + secretStoreRef: + name: {{ .Values.externalSecret.secretStore }} + kind: ClusterSecretStore + refreshInterval: {{ .Values.externalSecret.refreshInterval }} + target: + name: {{ include "sogo.fullname" . }}-postgres-credentials + creationPolicy: Owner + template: + engineVersion: v2 + data: + username: "{{ `{{ .postgres_user }}` }}" + password: "{{ `{{ .postgres_pw }}` }}" + dataFrom: + - extract: + key: {{ .Values.externalSecret.vaultPath }} +{{- end }} diff --git a/sogo/templates/ingress.yaml b/sogo/templates/ingress.yaml new file mode 100644 index 0000000..e0be83d --- /dev/null +++ b/sogo/templates/ingress.yaml @@ -0,0 +1,29 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "sogo.fullname" . }} + labels: + {{- include "sogo.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ .Values.ingress.className }} + tls: + - hosts: + - {{ .Values.ingress.host }} + secretName: {{ .Values.ingress.tls.secretName }} + rules: + - host: {{ .Values.ingress.host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "sogo.fullname" . }} + port: + number: {{ .Values.service.port }} +{{- end }} diff --git a/sogo/templates/postgresql-cluster.yaml b/sogo/templates/postgresql-cluster.yaml new file mode 100644 index 0000000..88a9dae --- /dev/null +++ b/sogo/templates/postgresql-cluster.yaml @@ -0,0 +1,42 @@ +{{- if .Values.postgresql.enabled }} +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: {{ include "sogo.fullname" . }}-postgres + labels: + {{- include "sogo.labels" . | nindent 4 }} +spec: + instances: {{ .Values.postgresql.instances }} + + primaryUpdateStrategy: unsupervised + + bootstrap: + initdb: + database: {{ .Values.postgresql.database }} + owner: {{ .Values.postgresql.username }} + secret: + name: {{ include "sogo.fullname" . }}-postgres-credentials + postInitSQL: + - CREATE EXTENSION IF NOT EXISTS pg_trgm; + + storage: + storageClass: {{ .Values.postgresql.storage.storageClass }} + size: {{ .Values.postgresql.storage.size }} + + postgresql: + parameters: + shared_buffers: {{ .Values.postgresql.sharedBuffers }} + max_connections: {{ .Values.postgresql.maxConnections | quote }} + log_destination: 'csvlog' + logging_collector: 'on' + log_directory: '/var/log/postgresql' + log_filename: 'postgres' + log_rotation_age: '0' + log_rotation_size: '0' + log_truncate_on_rotation: 'off' + pg_hba: + - host all all all scram-sha-256 + + monitoring: + enablePodMonitor: false +{{- end }} diff --git a/sogo/templates/pvc.yaml b/sogo/templates/pvc.yaml new file mode 100644 index 0000000..8f648ca --- /dev/null +++ b/sogo/templates/pvc.yaml @@ -0,0 +1,15 @@ +{{- if .Values.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "sogo.fullname" . }}-data + labels: + {{- include "sogo.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.accessMode }} + storageClassName: {{ .Values.persistence.storageClass }} + resources: + requests: + storage: {{ .Values.persistence.size }} +{{- end }} diff --git a/sogo/templates/service.yaml b/sogo/templates/service.yaml new file mode 100644 index 0000000..03b6df9 --- /dev/null +++ b/sogo/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "sogo.fullname" . }} + labels: + {{- include "sogo.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "sogo.selectorLabels" . | nindent 4 }} diff --git a/sogo/values.yaml b/sogo/values.yaml new file mode 100644 index 0000000..094866a --- /dev/null +++ b/sogo/values.yaml @@ -0,0 +1,84 @@ +image: + repository: ghcr.io/sonroyaalmerol/docker-sogo + tag: latest + pullPolicy: Always + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: prod-cluster-issuer + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + nginx.ingress.kubernetes.io/proxy-body-size: "50m" + host: post.hensen.io + tls: + secretName: sogo-tls + +persistence: + enabled: true + storageClass: longhorn + size: 5Gi + accessMode: ReadWriteOnce + +resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1000m" + +config: + timezone: Europe/Amsterdam + language: en + domain: hensen.io + adminUser: admin@hensen.io + workersCount: 3 + listenQueueSize: 5 + + modules: + mail: true + calendar: true + contacts: true + tasks: true + + mail: + imapServer: stalwart.stalwart.svc.cluster.local + imapPort: 993 + imapEncryption: tls + smtpServer: stalwart.stalwart.svc.cluster.local + smtpPort: 587 + smtpEncryption: tls + authenticationType: IMAP + + calendar: + firstDayOfWeek: 1 # Monday + dayStartTime: 8 + dayEndTime: 18 + timeFormat: "%H:%M" + + webui: + pageTitle: "Hensen Webmail" + mailReloadInterval: 300 + calendarReloadInterval: 300 + +postgresql: + enabled: true + instances: 1 + storage: + storageClass: longhorn + size: 10Gi + database: sogo + username: sogo + sharedBuffers: 256MB + maxConnections: 100 + +externalSecret: + enabled: true + secretStore: vault-backend + vaultPath: kv/sogo + refreshInterval: 15m