diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 6f55f20..dd2d492 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -18,7 +18,10 @@ "Bash(grep -l \"molecule_test_mode\" /home/iroddis/dev/sovereign/roles/*/tasks/main.yml)", "Bash(grep -l \"molecule_test_mode\" /home/iroddis/dev/sovereign/roles/*/handlers/main.yml)", "Bash(ls /home/iroddis/dev/sovereign/roles/*/molecule/default/)", - "Bash(grep -v \"^$\")" + "Bash(grep -v \"^$\")", + "Bash(helm template:*)", + "Bash(brew list:*)", + "Bash(export PATH=\"/opt/homebrew/bin:$PATH\")" ] } } diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..7bab8af --- /dev/null +++ b/Justfile @@ -0,0 +1,51 @@ +set dotenv-load + +# Default: list available recipes +default: + @just --list + +# Install Ansible Galaxy collections +deps: + ansible-galaxy collection install -r requirements.yml + +# Set up the target host: install Docker and create the sovereign network +setup-host: + ansible-playbook playbooks/site.yml --tags common + +# Run molecule tests for all roles +test: + #!/usr/bin/env bash + set -euo pipefail + roles=(authentik common forgejo graylog headscale jitsi matrix minio nextcloud roundcube stalwart vaultwarden wazuh website) + failed=() + for role in "${roles[@]}"; do + echo "==> Testing role: $role" + if ! (cd roles/$role && molecule test); then + failed+=("$role") + fi + done + if [ ${#failed[@]} -gt 0 ]; then + echo "FAILED roles: ${failed[*]}" + exit 1 + fi + echo "All molecule tests passed." + +# Run molecule tests for a single role: just test-role common +test-role role: + cd roles/{{ role }} && molecule test + +# Update an existing deployment (pull latest config, re-run all roles) +update: + ansible-playbook playbooks/site.yml + +# Update a single service: just update-service authentik +update-service service: + ansible-playbook playbooks/site.yml --tags {{ service }} + +# Dry-run the full deployment +check: + ansible-playbook playbooks/site.yml --check --diff + +# Lint the project +lint: + ansible-lint diff --git a/helm/sovereign/Chart.yaml b/helm/sovereign/Chart.yaml new file mode 100644 index 0000000..6b828cb --- /dev/null +++ b/helm/sovereign/Chart.yaml @@ -0,0 +1,19 @@ +apiVersion: v2 +name: sovereign +description: > + Self-hosted infrastructure stack for small businesses — Traefik, Graylog, + Authentik, MinIO, Nextcloud, Stalwart Mail, Roundcube, Matrix/Element, + Jitsi, Headscale, Wazuh, Vaultwarden, Forgejo, and a static website. +type: application +version: 0.1.0 +appVersion: "1.0.0" +keywords: + - self-hosted + - infrastructure + - authentik + - nextcloud + - matrix + - forgejo +home: https://github.com/your-org/sovereign +maintainers: + - name: Sovereign Maintainers diff --git a/helm/sovereign/templates/_helpers.tpl b/helm/sovereign/templates/_helpers.tpl new file mode 100644 index 0000000..2b5ca69 --- /dev/null +++ b/helm/sovereign/templates/_helpers.tpl @@ -0,0 +1,176 @@ +{{/* +Expand the chart name. +*/}} +{{- define "sovereign.name" -}} +{{- .Chart.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully-qualified app name using release name. +*/}} +{{- define "sovereign.fullname" -}} +{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels applied to every resource. +*/}} +{{- define "sovereign.labels" -}} +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/part-of: sovereign +{{- end }} + +{{/* +Selector labels — used in matchLabels and Service selectors. +Pass a dict with "root" (.Values context) and "component" (string). +Usage: {{ include "sovereign.selectorLabels" (dict "root" . "component" "graylog") }} +*/}} +{{- define "sovereign.selectorLabels" -}} +app.kubernetes.io/instance: {{ .root.Release.Name }} +app.kubernetes.io/component: {{ .component }} +{{- end }} + +{{/* +Base domain. +*/}} +{{- define "sovereign.baseDomain" -}} +{{- .Values.global.baseDomain -}} +{{- end }} + +{{/* +SMTP host: configured value or the stalwart Service name. +*/}} +{{- define "sovereign.smtpHost" -}} +{{- if .Values.smtp.host -}} +{{- .Values.smtp.host -}} +{{- else -}} +{{- .Release.Name }}-stalwart +{{- end -}} +{{- end }} + +{{/* +SMTP from address. +*/}} +{{- define "sovereign.smtpFrom" -}} +{{- if .Values.smtp.from -}} +{{- .Values.smtp.from -}} +{{- else -}} +{{- printf "noreply@%s" .Values.global.baseDomain -}} +{{- end -}} +{{- end }} + +{{/* +SMTP username. +*/}} +{{- define "sovereign.smtpUser" -}} +{{- if .Values.smtp.user -}} +{{- .Values.smtp.user -}} +{{- else -}} +{{- printf "noreply@%s" .Values.global.baseDomain -}} +{{- end -}} +{{- end }} + +{{/* +Graylog GELF host — release-name-graylog service, unless overridden. +*/}} +{{- define "sovereign.graylogHost" -}} +{{- .Release.Name }}-graylog +{{- end }} + +{{/* +Authentik base URL. +*/}} +{{- define "sovereign.authentikURL" -}} +{{- printf "https://auth.%s" .Values.global.baseDomain -}} +{{- end }} + +{{/* +Traefik ACME email. +*/}} +{{- define "sovereign.acmeEmail" -}} +{{- if .Values.traefik.acmeEmail -}} +{{- .Values.traefik.acmeEmail -}} +{{- else -}} +{{- printf "admin@%s" .Values.global.baseDomain -}} +{{- end -}} +{{- end }} + +{{/* +Authentik admin email. +*/}} +{{- define "sovereign.authentikAdminEmail" -}} +{{- if .Values.authentik.adminEmail -}} +{{- .Values.authentik.adminEmail -}} +{{- else -}} +{{- printf "admin@%s" .Values.global.baseDomain -}} +{{- end -}} +{{- end }} + +{{/* +Forgejo admin email. +*/}} +{{- define "sovereign.forgejoAdminEmail" -}} +{{- if .Values.forgejo.adminEmail -}} +{{- .Values.forgejo.adminEmail -}} +{{- else -}} +{{- printf "admin@%s" .Values.global.baseDomain -}} +{{- end -}} +{{- end }} + +{{/* +Standard Ingress annotations (cert-manager if configured). +*/}} +{{- define "sovereign.ingressAnnotations" -}} +{{- if .Values.global.clusterIssuer }} +cert-manager.io/cluster-issuer: {{ .Values.global.clusterIssuer }} +{{- end }} +{{- end }} + +{{/* +PVC storageClassName snippet — omitted entirely when empty so the cluster default is used. +Usage: {{ include "sovereign.storageClass" .Values.global.storageClass }} +*/}} +{{- define "sovereign.storageClass" -}} +{{- if . }} +storageClassName: {{ . }} +{{- end -}} +{{- end }} + +{{/* +Standard Ingress TLS block for a single host. +Usage: {{ include "sovereign.ingressTLS" (dict "host" "foo.example.com" "secretName" "foo-tls") }} +*/}} +{{/* +Shared environment variables for Authentik server and worker containers. +*/}} +{{- define "authentik.commonEnv" -}} +- name: AUTHENTIK_REDIS__HOST + value: {{ .Release.Name }}-authentik-redis +- name: AUTHENTIK_POSTGRESQL__HOST + value: {{ .Release.Name }}-authentik-postgres +- name: AUTHENTIK_POSTGRESQL__USER + value: authentik +- name: AUTHENTIK_POSTGRESQL__NAME + value: authentik +- name: AUTHENTIK_POSTGRESQL__PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-authentik + key: dbPassword +- name: AUTHENTIK_SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-authentik + key: secretKey +- name: AUTHENTIK_ERROR_REPORTING__ENABLED + value: "false" +{{- end }} + +{{- define "sovereign.ingressTLS" -}} +tls: + - hosts: + - {{ .host }} + secretName: {{ .secretName }} +{{- end }} diff --git a/helm/sovereign/templates/authentik.yaml b/helm/sovereign/templates/authentik.yaml new file mode 100644 index 0000000..8d354a6 --- /dev/null +++ b/helm/sovereign/templates/authentik.yaml @@ -0,0 +1,275 @@ +{{- if .Values.authentik.enabled }} +# ------------------------------------------------------------------------- +# AUTHENTIK — PostgreSQL + Redis + Server + Worker +# ------------------------------------------------------------------------- + +# --- PVCs --- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-authentik-postgres + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.authentik.persistence.postgresSize }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-authentik-media + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.authentik.persistence.mediaSize }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-authentik-certs + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.authentik.persistence.certsSize }} +--- +# --- PostgreSQL --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-authentik-postgres + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: authentik-postgres +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "authentik-postgres") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "authentik-postgres") | nindent 8 }} + spec: + containers: + - name: postgres + image: postgres:16-alpine + env: + - name: POSTGRES_USER + value: authentik + - name: POSTGRES_DB + value: authentik + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-authentik + key: dbPassword + livenessProbe: + exec: + command: [sh, -c, "pg_isready -d authentik -U authentik"] + initialDelaySeconds: 30 + periodSeconds: 30 + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-authentik-postgres +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-authentik-postgres + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "authentik-postgres") | nindent 4 }} + ports: + - port: 5432 + targetPort: 5432 +--- +# --- Redis --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-authentik-redis + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: authentik-redis +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "authentik-redis") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "authentik-redis") | nindent 8 }} + spec: + containers: + - name: redis + image: redis:alpine + livenessProbe: + exec: + command: [redis-cli, ping] + initialDelaySeconds: 10 + periodSeconds: 30 +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-authentik-redis + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "authentik-redis") | nindent 4 }} + ports: + - port: 6379 + targetPort: 6379 +--- +# --- Authentik Server --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-authentik-server + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: authentik-server +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "authentik-server") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "authentik-server") | nindent 8 }} + spec: + containers: + - name: server + image: ghcr.io/goauthentik/server:{{ .Values.authentik.version }} + command: [server] + env: + {{- include "authentik.commonEnv" . | nindent 12 }} + - name: AUTHENTIK_EMAIL__HOST + value: {{ include "sovereign.smtpHost" . | quote }} + - name: AUTHENTIK_EMAIL__PORT + value: {{ .Values.smtp.port | quote }} + - name: AUTHENTIK_EMAIL__USERNAME + value: {{ include "sovereign.smtpUser" . | quote }} + - name: AUTHENTIK_EMAIL__FROM + value: {{ include "sovereign.smtpFrom" . | quote }} + - name: AUTHENTIK_EMAIL__USE_TLS + value: "true" + - name: AUTHENTIK_EMAIL__PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-smtp + key: password + ports: + - name: http + containerPort: 9000 + volumeMounts: + - name: media + mountPath: /media + - name: certs + mountPath: /certs + volumes: + - name: media + persistentVolumeClaim: + claimName: {{ .Release.Name }}-authentik-media + - name: certs + persistentVolumeClaim: + claimName: {{ .Release.Name }}-authentik-certs +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-authentik-server + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "authentik-server") | nindent 4 }} + ports: + - name: http + port: 9000 + targetPort: http +--- +# --- Authentik Worker --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-authentik-worker + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: authentik-worker +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "authentik-worker") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "authentik-worker") | nindent 8 }} + spec: + containers: + - name: worker + image: ghcr.io/goauthentik/server:{{ .Values.authentik.version }} + command: [worker] + env: + {{- include "authentik.commonEnv" . | nindent 12 }} + volumeMounts: + - name: media + mountPath: /media + - name: certs + mountPath: /certs + volumes: + - name: media + persistentVolumeClaim: + claimName: {{ .Release.Name }}-authentik-media + - name: certs + persistentVolumeClaim: + claimName: {{ .Release.Name }}-authentik-certs +--- +# --- Ingress --- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-authentik + labels: + {{- include "sovereign.labels" . | nindent 4 }} + annotations: + {{- include "sovereign.ingressAnnotations" . | nindent 4 }} +spec: + ingressClassName: {{ .Values.global.ingressClassName }} + rules: + - host: auth.{{ .Values.global.baseDomain }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-authentik-server + port: + name: http + tls: + - hosts: + - auth.{{ .Values.global.baseDomain }} + secretName: {{ .Release.Name }}-authentik-tls +{{- end }} diff --git a/helm/sovereign/templates/forgejo.yaml b/helm/sovereign/templates/forgejo.yaml new file mode 100644 index 0000000..d974d08 --- /dev/null +++ b/helm/sovereign/templates/forgejo.yaml @@ -0,0 +1,246 @@ +{{- if .Values.forgejo.enabled }} +# ------------------------------------------------------------------------- +# FORGEJO — PostgreSQL + Forgejo Git +# ------------------------------------------------------------------------- + +# --- PVCs --- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-forgejo-db + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.forgejo.persistence.dbSize }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-forgejo-data + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.forgejo.persistence.dataSize }} +--- +# --- PostgreSQL --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-forgejo-db + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: forgejo-db +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "forgejo-db") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "forgejo-db") | nindent 8 }} + spec: + containers: + - name: postgres + image: postgres:16-alpine + env: + - name: POSTGRES_DB + value: forgejo + - name: POSTGRES_USER + value: forgejo + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-forgejo + key: dbPassword + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-forgejo-db +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-forgejo-db + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "forgejo-db") | nindent 4 }} + ports: + - port: 5432 + targetPort: 5432 +--- +# --- Forgejo --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-forgejo + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: forgejo +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "forgejo") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "forgejo") | nindent 8 }} + spec: + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + containers: + - name: forgejo + image: codeberg.org/forgejo/forgejo:{{ .Values.forgejo.version }} + env: + - name: FORGEJO__database__DB_TYPE + value: postgres + - name: FORGEJO__database__HOST + value: {{ .Release.Name }}-forgejo-db:5432 + - name: FORGEJO__database__NAME + value: forgejo + - name: FORGEJO__database__USER + value: forgejo + - name: FORGEJO__server__DOMAIN + value: git.{{ .Values.global.baseDomain }} + - name: FORGEJO__server__ROOT_URL + value: https://git.{{ .Values.global.baseDomain }} + - name: FORGEJO__server__SSH_DOMAIN + value: git.{{ .Values.global.baseDomain }} + - name: FORGEJO__server__SSH_PORT + value: {{ .Values.forgejo.sshPort | quote }} + - name: FORGEJO__server__SSH_LISTEN_PORT + value: "22" + - name: FORGEJO__mailer__ENABLED + value: "true" + - name: FORGEJO__mailer__SMTP_ADDR + value: {{ include "sovereign.smtpHost" . | quote }} + - name: FORGEJO__mailer__SMTP_PORT + value: {{ .Values.smtp.port | quote }} + - name: FORGEJO__mailer__FROM + value: {{ include "sovereign.smtpFrom" . | quote }} + - name: FORGEJO__mailer__USER + value: {{ include "sovereign.smtpUser" . | quote }} + - name: FORGEJO__openid__ENABLE_OPENID_SIGNIN + value: "true" + - name: FORGEJO__openid__ENABLE_OPENID_SIGNUP + value: "false" + - name: FORGEJO__oauth2_client__REGISTER_EMAIL_CONFIRM + value: "false" + - name: FORGEJO__oauth2_client__ENABLE_AUTO_REGISTRATION + value: "true" + - name: FORGEJO____APP_NAME + value: {{ .Values.global.tenantName | quote }} + - name: FORGEJO__log__LEVEL + value: warn + - name: FORGEJO__database__PASSWD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-forgejo + key: dbPassword + - name: FORGEJO__security__SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-forgejo + key: secretKey + - name: FORGEJO__security__INTERNAL_TOKEN + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-forgejo + key: internalToken + - name: FORGEJO__lfs__JWT_SECRET + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-forgejo + key: lfsJwtSecret + - name: FORGEJO__mailer__PASSWD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-smtp + key: password + ports: + - name: http + containerPort: 3000 + - name: ssh + containerPort: 22 + volumeMounts: + - name: data + mountPath: /data + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-forgejo-data +--- +# HTTP service +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-forgejo + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "forgejo") | nindent 4 }} + ports: + - name: http + port: 3000 + targetPort: http +--- +# SSH service (NodePort on the configured sshPort) +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-forgejo-ssh + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + type: NodePort + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "forgejo") | nindent 4 }} + ports: + - name: ssh + port: {{ .Values.forgejo.sshPort }} + targetPort: ssh + nodePort: {{ .Values.forgejo.sshPort }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-forgejo + labels: + {{- include "sovereign.labels" . | nindent 4 }} + annotations: + {{- include "sovereign.ingressAnnotations" . | nindent 4 }} +spec: + ingressClassName: {{ .Values.global.ingressClassName }} + rules: + - host: git.{{ .Values.global.baseDomain }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-forgejo + port: + name: http + tls: + - hosts: + - git.{{ .Values.global.baseDomain }} + secretName: {{ .Release.Name }}-forgejo-tls +{{- end }} diff --git a/helm/sovereign/templates/graylog.yaml b/helm/sovereign/templates/graylog.yaml new file mode 100644 index 0000000..0e41f32 --- /dev/null +++ b/helm/sovereign/templates/graylog.yaml @@ -0,0 +1,262 @@ +{{- if .Values.graylog.enabled }} +# ------------------------------------------------------------------------- +# GRAYLOG — MongoDB + OpenSearch + Graylog +# ------------------------------------------------------------------------- + +# --- PVCs --- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-graylog-mongodb + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.graylog.persistence.mongodbSize }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-graylog-opensearch + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.graylog.persistence.opensearchSize }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-graylog-data + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.graylog.persistence.graylogSize }} +--- +# --- MongoDB --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-graylog-mongodb + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: graylog-mongodb +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "graylog-mongodb") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "graylog-mongodb") | nindent 8 }} + spec: + containers: + - name: mongodb + image: mongo:{{ .Values.graylog.mongodbVersion }} + volumeMounts: + - name: data + mountPath: /data/db + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-graylog-mongodb +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-graylog-mongodb + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "graylog-mongodb") | nindent 4 }} + ports: + - port: 27017 + targetPort: 27017 +--- +# --- OpenSearch --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-graylog-opensearch + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: graylog-opensearch +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "graylog-opensearch") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "graylog-opensearch") | nindent 8 }} + spec: + initContainers: + - name: sysctl + image: busybox + command: ["sysctl", "-w", "vm.max_map_count=262144"] + securityContext: + privileged: true + containers: + - name: opensearch + image: opensearchproject/opensearch:{{ .Values.graylog.opensearchVersion }} + env: + - name: OPENSEARCH_JAVA_OPTS + value: {{ .Values.graylog.opensearchJavaOpts | quote }} + - name: bootstrap.memory_lock + value: "true" + - name: discovery.type + value: single-node + - name: action.auto_create_index + value: "false" + - name: plugins.security.ssl.http.enabled + value: "false" + - name: plugins.security.disabled + value: "true" + - name: OPENSEARCH_INITIAL_ADMIN_PASSWORD + value: "changeme_os_admin" + securityContext: + capabilities: + add: [IPC_LOCK] + volumeMounts: + - name: data + mountPath: /usr/share/opensearch/data + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-graylog-opensearch +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-graylog-opensearch + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "graylog-opensearch") | nindent 4 }} + ports: + - port: 9200 + targetPort: 9200 +--- +# --- Graylog --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-graylog + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: graylog +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "graylog") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "graylog") | nindent 8 }} + spec: + containers: + - name: graylog + image: graylog/graylog:{{ .Values.graylog.version }} + env: + - name: GRAYLOG_NODE_ID_FILE + value: /usr/share/graylog/data/config/node-id + - name: GRAYLOG_HTTP_BIND_ADDRESS + value: "0.0.0.0:9000" + - name: GRAYLOG_ELASTICSEARCH_HOSTS + value: http://{{ .Release.Name }}-graylog-opensearch:9200 + - name: GRAYLOG_MONGODB_URI + value: mongodb://{{ .Release.Name }}-graylog-mongodb:27017/graylog + - name: GRAYLOG_HTTP_EXTERNAL_URI + value: https://logs.{{ .Values.global.baseDomain }}/ + - name: GRAYLOG_TRANSPORT_EMAIL_ENABLED + value: "true" + - name: GRAYLOG_TRANSPORT_EMAIL_HOSTNAME + value: {{ include "sovereign.smtpHost" . | quote }} + - name: GRAYLOG_TRANSPORT_EMAIL_PORT + value: {{ .Values.smtp.port | quote }} + - name: GRAYLOG_TRANSPORT_EMAIL_FROM_EMAIL + value: {{ include "sovereign.smtpFrom" . | quote }} + - name: GRAYLOG_PASSWORD_SECRET + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-graylog + key: passwordSecret + - name: GRAYLOG_ROOT_PASSWORD_SHA2 + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-graylog + key: rootPasswordSha2 + ports: + - name: http + containerPort: 9000 + - name: gelf-udp + containerPort: 12201 + protocol: UDP + volumeMounts: + - name: data + mountPath: /usr/share/graylog/data + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-graylog-data +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-graylog + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "graylog") | nindent 4 }} + ports: + - name: http + port: 9000 + targetPort: http + - name: gelf-udp + port: {{ .Values.graylog.gelfPort }} + targetPort: gelf-udp + protocol: UDP +--- +# --- Ingress --- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-graylog + labels: + {{- include "sovereign.labels" . | nindent 4 }} + annotations: + {{- include "sovereign.ingressAnnotations" . | nindent 4 }} +spec: + ingressClassName: {{ .Values.global.ingressClassName }} + rules: + - host: logs.{{ .Values.global.baseDomain }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-graylog + port: + name: http + tls: + - hosts: + - logs.{{ .Values.global.baseDomain }} + secretName: {{ .Release.Name }}-graylog-tls +{{- end }} diff --git a/helm/sovereign/templates/headscale.yaml b/helm/sovereign/templates/headscale.yaml new file mode 100644 index 0000000..89dea9a --- /dev/null +++ b/helm/sovereign/templates/headscale.yaml @@ -0,0 +1,189 @@ +{{- if .Values.headscale.enabled }} +# ------------------------------------------------------------------------- +# HEADSCALE — WireGuard mesh VPN coordinator +# ------------------------------------------------------------------------- + +# ConfigMap: base headscale config +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-headscale-config + labels: + {{- include "sovereign.labels" . | nindent 4 }} +data: + config.yaml: | + server_url: "https://headscale.{{ .Values.global.baseDomain }}" + listen_addr: "0.0.0.0:8080" + grpc_listen_addr: "0.0.0.0:50443" + grpc_allow_insecure: true + private_key_path: /var/lib/headscale/private.key + noise: + private_key_path: /var/lib/headscale/noise_private.key + ip_prefixes: + - 100.64.0.0/10 + derp: + server: + enabled: false + urls: + - https://controlplane.tailscale.com/derpmap/default + auto_update_enabled: true + update_frequency: 24h + disable_check_updates: true + ephemeral_node_inactivity_timeout: 30m + db_type: sqlite3 + db_path: /var/lib/headscale/db.sqlite + log: + level: warn + acls_path: /etc/headscale/acls.hujson + oidc: + issuer: "{{ include "sovereign.authentikURL" . }}/application/o/headscale/" + client_id: headscale + client_secret: "changeme_headscale_oidc_secret" + scope: [openid, profile, email] + strip_email_domain: true +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-headscale-config + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.headscale.persistence.configSize }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-headscale-data + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.headscale.persistence.dataSize }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-headscale + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: headscale +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "headscale") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "headscale") | nindent 8 }} + spec: + initContainers: + - name: init-config + image: busybox + command: + - sh + - -c + - | + if [ ! -f /etc/headscale/config.yaml ]; then + cp /configmap/config.yaml /etc/headscale/config.yaml + fi + volumeMounts: + - name: config + mountPath: /etc/headscale + - name: configmap + mountPath: /configmap + containers: + - name: headscale + image: headscale/headscale:{{ .Values.headscale.version }} + command: [serve] + ports: + - name: http + containerPort: 8080 + - name: grpc + containerPort: 50443 + - name: wireguard + containerPort: {{ .Values.headscale.wireguardPort }} + protocol: UDP + volumeMounts: + - name: config + mountPath: /etc/headscale + - name: data + mountPath: /var/lib/headscale + volumes: + - name: config + persistentVolumeClaim: + claimName: {{ .Release.Name }}-headscale-config + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-headscale-data + - name: configmap + configMap: + name: {{ .Release.Name }}-headscale-config +--- +# HTTP service (for Ingress) +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-headscale + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "headscale") | nindent 4 }} + ports: + - name: http + port: 8080 + targetPort: http +--- +# WireGuard UDP service +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-headscale-vpn + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + type: NodePort + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "headscale") | nindent 4 }} + ports: + - name: wireguard + port: {{ .Values.headscale.wireguardPort }} + targetPort: wireguard + nodePort: {{ .Values.headscale.wireguardPort }} + protocol: UDP +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-headscale + labels: + {{- include "sovereign.labels" . | nindent 4 }} + annotations: + {{- include "sovereign.ingressAnnotations" . | nindent 4 }} +spec: + ingressClassName: {{ .Values.global.ingressClassName }} + rules: + - host: headscale.{{ .Values.global.baseDomain }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-headscale + port: + name: http + tls: + - hosts: + - headscale.{{ .Values.global.baseDomain }} + secretName: {{ .Release.Name }}-headscale-tls +{{- end }} diff --git a/helm/sovereign/templates/jitsi.yaml b/helm/sovereign/templates/jitsi.yaml new file mode 100644 index 0000000..bdae33d --- /dev/null +++ b/helm/sovereign/templates/jitsi.yaml @@ -0,0 +1,362 @@ +{{- if .Values.jitsi.enabled }} +# ------------------------------------------------------------------------- +# JITSI MEET — Web + Prosody + Jicofo + JVB +# ------------------------------------------------------------------------- + +# --- PVCs --- +{{- range list "web" "prosody" "jicofo" "jvb" }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ $.Release.Name }}-jitsi-{{ . }} + labels: + {{- include "sovereign.labels" $ | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" $.Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ index $.Values.jitsi.persistence (printf "%sSize" .) }} +--- +{{- end }} + +# --- Prosody (XMPP) --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-jitsi-prosody + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: jitsi-prosody +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "jitsi-prosody") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "jitsi-prosody") | nindent 8 }} + spec: + containers: + - name: prosody + image: jitsi/prosody:{{ .Values.jitsi.version }} + env: + - name: XMPP_DOMAIN + value: meet.jitsi + - name: XMPP_AUTH_DOMAIN + value: auth.meet.jitsi + - name: XMPP_MUC_DOMAIN + value: muc.meet.jitsi + - name: XMPP_INTERNAL_MUC_DOMAIN + value: internal-muc.meet.jitsi + - name: XMPP_RECORDER_DOMAIN + value: recorder.meet.jitsi + - name: JICOFO_AUTH_USER + value: focus + - name: JVB_AUTH_USER + value: jvb + - name: JIBRI_RECORDER_USER + value: recorder + - name: JIBRI_XMPP_USER + value: jibri + - name: JWT_APP_ID + value: jitsi + - name: ENABLE_AUTH + value: "1" + - name: ENABLE_GUESTS + value: "1" + - name: AUTH_TYPE + value: jwt + - name: TZ + value: UTC + - name: JICOFO_AUTH_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-jitsi + key: jicofoAuthPassword + - name: JVB_AUTH_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-jitsi + key: jvbAuthPassword + - name: JIBRI_RECORDER_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-jitsi + key: jibriRecorderPassword + - name: JIBRI_XMPP_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-jitsi + key: jibriXmppPassword + - name: JWT_APP_SECRET + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-jitsi + key: turnSecret + volumeMounts: + - name: config + mountPath: /config + volumes: + - name: config + persistentVolumeClaim: + claimName: {{ .Release.Name }}-jitsi-prosody +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-jitsi-prosody + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "jitsi-prosody") | nindent 4 }} + ports: + - name: xmpp-c2s + port: 5222 + targetPort: 5222 + - name: xmpp-s2s + port: 5269 + targetPort: 5269 + - name: bosh + port: 5280 + targetPort: 5280 + - name: xmpp-component + port: 5347 + targetPort: 5347 +--- +# --- Jicofo --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-jitsi-jicofo + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: jitsi-jicofo +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "jitsi-jicofo") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "jitsi-jicofo") | nindent 8 }} + spec: + containers: + - name: jicofo + image: jitsi/jicofo:{{ .Values.jitsi.version }} + env: + - name: XMPP_SERVER + value: {{ .Release.Name }}-jitsi-prosody + - name: XMPP_DOMAIN + value: meet.jitsi + - name: XMPP_AUTH_DOMAIN + value: auth.meet.jitsi + - name: XMPP_INTERNAL_MUC_DOMAIN + value: internal-muc.meet.jitsi + - name: JICOFO_AUTH_USER + value: focus + - name: JVB_BREWERY_MUC + value: jvbbrewery + - name: TZ + value: UTC + - name: JICOFO_AUTH_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-jitsi + key: jicofoAuthPassword + volumeMounts: + - name: config + mountPath: /config + volumes: + - name: config + persistentVolumeClaim: + claimName: {{ .Release.Name }}-jitsi-jicofo +--- +# --- JVB (video bridge) --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-jitsi-jvb + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: jitsi-jvb +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "jitsi-jvb") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "jitsi-jvb") | nindent 8 }} + spec: + containers: + - name: jvb + image: jitsi/jvb:{{ .Values.jitsi.version }} + env: + - name: XMPP_SERVER + value: {{ .Release.Name }}-jitsi-prosody + - name: XMPP_DOMAIN + value: meet.jitsi + - name: XMPP_AUTH_DOMAIN + value: auth.meet.jitsi + - name: XMPP_INTERNAL_MUC_DOMAIN + value: internal-muc.meet.jitsi + - name: JVB_AUTH_USER + value: jvb + - name: JVB_BREWERY_MUC + value: jvbbrewery + - name: JVB_PORT + value: "10000" + - name: JVB_TCP_HARVESTER_DISABLED + value: "true" + - name: TZ + value: UTC + - name: JVB_AUTH_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-jitsi + key: jvbAuthPassword + ports: + - name: jvb-udp + containerPort: 10000 + protocol: UDP + volumeMounts: + - name: config + mountPath: /config + volumes: + - name: config + persistentVolumeClaim: + claimName: {{ .Release.Name }}-jitsi-jvb +--- +# JVB service — needs to be reachable by clients (UDP) +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-jitsi-jvb + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + type: NodePort + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "jitsi-jvb") | nindent 4 }} + ports: + - name: jvb-udp + port: 10000 + targetPort: jvb-udp + protocol: UDP +--- +# --- Jitsi Web --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-jitsi-web + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: jitsi-web +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "jitsi-web") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "jitsi-web") | nindent 8 }} + spec: + containers: + - name: web + image: jitsi/web:{{ .Values.jitsi.version }} + env: + - name: PUBLIC_URL + value: https://meet.{{ .Values.global.baseDomain }} + - name: JITSI_WATERMARKLINK + value: https://{{ .Values.global.baseDomain }} + - name: XMPP_SERVER + value: {{ .Release.Name }}-jitsi-prosody + - name: XMPP_DOMAIN + value: meet.jitsi + - name: XMPP_AUTH_DOMAIN + value: auth.meet.jitsi + - name: XMPP_INTERNAL_MUC_DOMAIN + value: internal-muc.meet.jitsi + - name: XMPP_MUC_DOMAIN + value: muc.meet.jitsi + - name: XMPP_BOSH_URL_BASE + value: http://{{ .Release.Name }}-jitsi-prosody:5280 + - name: XMPP_RECORDER_DOMAIN + value: recorder.meet.jitsi + - name: ENABLE_AUTH + value: "1" + - name: ENABLE_GUESTS + value: "1" + - name: AUTH_TYPE + value: jwt + - name: JWT_APP_ID + value: jitsi + - name: TZ + value: UTC + - name: JWT_APP_SECRET + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-jitsi + key: turnSecret + - name: TURN_CREDENTIALS + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-jitsi + key: turnSecret + ports: + - name: http + containerPort: 80 + volumeMounts: + - name: config + mountPath: /config + volumes: + - name: config + persistentVolumeClaim: + claimName: {{ .Release.Name }}-jitsi-web +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-jitsi-web + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "jitsi-web") | nindent 4 }} + ports: + - name: http + port: 80 + targetPort: http +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-jitsi + labels: + {{- include "sovereign.labels" . | nindent 4 }} + annotations: + {{- include "sovereign.ingressAnnotations" . | nindent 4 }} +spec: + ingressClassName: {{ .Values.global.ingressClassName }} + rules: + - host: meet.{{ .Values.global.baseDomain }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-jitsi-web + port: + name: http + tls: + - hosts: + - meet.{{ .Values.global.baseDomain }} + secretName: {{ .Release.Name }}-jitsi-tls +{{- end }} diff --git a/helm/sovereign/templates/matrix.yaml b/helm/sovereign/templates/matrix.yaml new file mode 100644 index 0000000..8a7718e --- /dev/null +++ b/helm/sovereign/templates/matrix.yaml @@ -0,0 +1,328 @@ +{{- if .Values.matrix.enabled }} +# ------------------------------------------------------------------------- +# MATRIX / ELEMENT — PostgreSQL + Synapse + Element Web +# +# NOTE: Synapse requires a homeserver.yaml in its data volume. +# On first run, exec into the synapse pod and run: +# python -m synapse.app.homeserver --generate-config \ +# -H matrix. --report-stats=no \ +# -c /data/homeserver.yaml +# Or provide your own via the configMap below. +# ------------------------------------------------------------------------- + +# ConfigMap: basic homeserver.yaml — customize before deploying +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-synapse-config + labels: + {{- include "sovereign.labels" . | nindent 4 }} +data: + homeserver.yaml: | + server_name: "{{ .Values.global.baseDomain }}" + public_baseurl: "https://matrix.{{ .Values.global.baseDomain }}" + pid_file: /data/homeserver.pid + listeners: + - port: 8008 + tls: false + type: http + x_forwarded: true + resources: + - names: [client, federation] + compress: false + database: + name: psycopg2 + args: + user: synapse + password: "" + database: synapse + host: {{ .Release.Name }}-matrix-db + cp_min: 5 + cp_max: 10 + log_config: "/data/log.config" + media_store_path: /data/media_store + registration_shared_secret: "" + report_stats: false + signing_key_path: "/data/{{ .Values.global.baseDomain }}.signing.key" + trusted_key_servers: + - server_name: "matrix.org" + email: + smtp_host: {{ include "sovereign.smtpHost" . | quote }} + smtp_port: {{ .Values.smtp.port }} + notif_from: "{{ include "sovereign.smtpFrom" . }}" + + log.config: | + version: 1 + formatters: + precise: + format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s' + handlers: + console: + class: logging.StreamHandler + formatter: precise + loggers: + synapse.storage.SQL: + level: WARNING + root: + level: WARNING + handlers: [console] + disable_existing_loggers: false + + element-config.json: | + { + "default_server_config": { + "m.homeserver": { + "base_url": "https://matrix.{{ .Values.global.baseDomain }}", + "server_name": "{{ .Values.global.baseDomain }}" + } + }, + "brand": "{{ .Values.global.tenantName }}", + "disable_guests": true, + "room_directory": { "servers": ["{{ .Values.global.baseDomain }}"] } + } +--- +# --- PVCs --- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-matrix-db + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.matrix.persistence.dbSize }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-synapse-data + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.matrix.persistence.synapseSize }} +--- +# --- PostgreSQL --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-matrix-db + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: matrix-db +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "matrix-db") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "matrix-db") | nindent 8 }} + spec: + containers: + - name: postgres + image: postgres:16-alpine + env: + - name: POSTGRES_USER + value: synapse + - name: POSTGRES_DB + value: synapse + - name: POSTGRES_INITDB_ARGS + value: "--encoding=UTF8 --lc-collate=C --lc-ctype=C" + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-matrix + key: dbPassword + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-matrix-db +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-matrix-db + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "matrix-db") | nindent 4 }} + ports: + - port: 5432 + targetPort: 5432 +--- +# --- Synapse --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-synapse + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: synapse +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "synapse") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "synapse") | nindent 8 }} + spec: + initContainers: + # Copy config from ConfigMap into writable data volume on first run + - name: init-config + image: busybox + command: + - sh + - -c + - | + if [ ! -f /data/homeserver.yaml ]; then + cp /config/homeserver.yaml /data/homeserver.yaml + cp /config/log.config /data/log.config + fi + volumeMounts: + - name: data + mountPath: /data + - name: config + mountPath: /config + containers: + - name: synapse + image: ghcr.io/element-hq/synapse:{{ .Values.matrix.synapseVersion }} + env: + - name: SYNAPSE_CONFIG_PATH + value: /data/homeserver.yaml + - name: SYNAPSE_POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-matrix + key: dbPassword + - name: SYNAPSE_REGISTRATION_SHARED_SECRET + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-matrix + key: registrationSecret + ports: + - name: http + containerPort: 8008 + volumeMounts: + - name: data + mountPath: /data + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-synapse-data + - name: config + configMap: + name: {{ .Release.Name }}-synapse-config +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-synapse + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "synapse") | nindent 4 }} + ports: + - name: http + port: 8008 + targetPort: http +--- +# --- Element Web --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-element + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: element +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "element") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "element") | nindent 8 }} + spec: + containers: + - name: element + image: vectorim/element-web:latest + ports: + - name: http + containerPort: 80 + volumeMounts: + - name: config + mountPath: /app/config.json + subPath: element-config.json + volumes: + - name: config + configMap: + name: {{ .Release.Name }}-synapse-config +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-element + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "element") | nindent 4 }} + ports: + - name: http + port: 80 + targetPort: http +--- +# --- Ingress (matrix + chat subdomains) --- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-matrix + labels: + {{- include "sovereign.labels" . | nindent 4 }} + annotations: + {{- include "sovereign.ingressAnnotations" . | nindent 4 }} +spec: + ingressClassName: {{ .Values.global.ingressClassName }} + rules: + - host: matrix.{{ .Values.global.baseDomain }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-synapse + port: + name: http + - host: chat.{{ .Values.global.baseDomain }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-element + port: + name: http + tls: + - hosts: + - matrix.{{ .Values.global.baseDomain }} + - chat.{{ .Values.global.baseDomain }} + secretName: {{ .Release.Name }}-matrix-tls +{{- end }} diff --git a/helm/sovereign/templates/minio.yaml b/helm/sovereign/templates/minio.yaml new file mode 100644 index 0000000..09a3cfd --- /dev/null +++ b/helm/sovereign/templates/minio.yaml @@ -0,0 +1,144 @@ +{{- if .Values.minio.enabled }} +# ------------------------------------------------------------------------- +# MINIO — S3-compatible object storage +# ------------------------------------------------------------------------- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-minio + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.minio.persistence.size }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-minio + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: minio +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "minio") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "minio") | nindent 8 }} + spec: + containers: + - name: minio + image: quay.io/minio/minio:{{ .Values.minio.version }} + command: [server, /data, --console-address, ":9001"] + env: + - name: MINIO_ROOT_USER + value: {{ .Values.minio.rootUser | quote }} + - name: MINIO_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-minio + key: rootPassword + - name: MINIO_BROWSER_REDIRECT_URL + value: https://minio.{{ .Values.global.baseDomain }} + - name: MINIO_IDENTITY_OPENID_CONFIG_URL + value: {{ include "sovereign.authentikURL" . }}/application/o/minio/.well-known/openid-configuration + - name: MINIO_IDENTITY_OPENID_CLIENT_ID + value: minio + - name: MINIO_IDENTITY_OPENID_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-minio + key: oidcSecret + - name: MINIO_IDENTITY_OPENID_CLAIM_NAME + value: policy + - name: MINIO_IDENTITY_OPENID_REDIRECT_URI + value: https://minio.{{ .Values.global.baseDomain }}/oauth_callback + ports: + - name: api + containerPort: 9000 + - name: console + containerPort: 9001 + volumeMounts: + - name: data + mountPath: /data + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-minio +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-minio + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "minio") | nindent 4 }} + ports: + - name: api + port: 9000 + targetPort: api + - name: console + port: 9001 + targetPort: console +--- +# Two Ingresses: API (s3.) and Console (minio.) +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-minio-api + labels: + {{- include "sovereign.labels" . | nindent 4 }} + annotations: + {{- include "sovereign.ingressAnnotations" . | nindent 4 }} +spec: + ingressClassName: {{ .Values.global.ingressClassName }} + rules: + - host: s3.{{ .Values.global.baseDomain }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-minio + port: + name: api + tls: + - hosts: + - s3.{{ .Values.global.baseDomain }} + secretName: {{ .Release.Name }}-minio-api-tls +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-minio-console + labels: + {{- include "sovereign.labels" . | nindent 4 }} + annotations: + {{- include "sovereign.ingressAnnotations" . | nindent 4 }} +spec: + ingressClassName: {{ .Values.global.ingressClassName }} + rules: + - host: minio.{{ .Values.global.baseDomain }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-minio + port: + name: console + tls: + - hosts: + - minio.{{ .Values.global.baseDomain }} + secretName: {{ .Release.Name }}-minio-console-tls +{{- end }} diff --git a/helm/sovereign/templates/nextcloud.yaml b/helm/sovereign/templates/nextcloud.yaml new file mode 100644 index 0000000..a57c1bd --- /dev/null +++ b/helm/sovereign/templates/nextcloud.yaml @@ -0,0 +1,292 @@ +{{- if .Values.nextcloud.enabled }} +# ------------------------------------------------------------------------- +# NEXTCLOUD — MariaDB + Redis + Nextcloud + Cron +# ------------------------------------------------------------------------- + +# --- PVCs --- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-nextcloud-db + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.nextcloud.persistence.dbSize }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-nextcloud-data + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.nextcloud.persistence.dataSize }} +--- +# --- MariaDB --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-nextcloud-db + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: nextcloud-db +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "nextcloud-db") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "nextcloud-db") | nindent 8 }} + spec: + containers: + - name: mariadb + image: mariadb:10.11 + args: + - --transaction-isolation=READ-COMMITTED + - --log-bin=binlog + - --binlog-format=ROW + env: + - name: MYSQL_DATABASE + value: nextcloud + - name: MYSQL_USER + value: nextcloud + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-nextcloud + key: dbRootPassword + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-nextcloud + key: dbPassword + volumeMounts: + - name: data + mountPath: /var/lib/mysql + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-nextcloud-db +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-nextcloud-db + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "nextcloud-db") | nindent 4 }} + ports: + - port: 3306 + targetPort: 3306 +--- +# --- Redis --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-nextcloud-redis + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: nextcloud-redis +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "nextcloud-redis") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "nextcloud-redis") | nindent 8 }} + spec: + containers: + - name: redis + image: redis:alpine +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-nextcloud-redis + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "nextcloud-redis") | nindent 4 }} + ports: + - port: 6379 + targetPort: 6379 +--- +# --- Nextcloud --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-nextcloud + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: nextcloud +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "nextcloud") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "nextcloud") | nindent 8 }} + spec: + containers: + - name: nextcloud + image: nextcloud:{{ .Values.nextcloud.version }} + env: + - name: MYSQL_HOST + value: {{ .Release.Name }}-nextcloud-db + - name: MYSQL_DATABASE + value: nextcloud + - name: MYSQL_USER + value: nextcloud + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-nextcloud + key: dbPassword + - name: REDIS_HOST + value: {{ .Release.Name }}-nextcloud-redis + - name: NEXTCLOUD_ADMIN_USER + value: {{ .Values.nextcloud.adminUser | quote }} + - name: NEXTCLOUD_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-nextcloud + key: adminPassword + - name: NEXTCLOUD_TRUSTED_DOMAINS + value: cloud.{{ .Values.global.baseDomain }} + - name: OVERWRITEPROTOCOL + value: https + - name: OVERWRITECLIURL + value: https://cloud.{{ .Values.global.baseDomain }} + - name: SMTP_HOST + value: {{ include "sovereign.smtpHost" . | quote }} + - name: SMTP_PORT + value: {{ .Values.smtp.port | quote }} + - name: SMTP_NAME + value: {{ include "sovereign.smtpUser" . | quote }} + - name: SMTP_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-smtp + key: password + - name: MAIL_FROM_ADDRESS + value: noreply + - name: MAIL_DOMAIN + value: {{ .Values.global.baseDomain | quote }} + - name: OBJECTSTORE_S3_HOST + value: {{ .Release.Name }}-minio + - name: OBJECTSTORE_S3_PORT + value: "9000" + - name: OBJECTSTORE_S3_SSL + value: "false" + - name: OBJECTSTORE_S3_BUCKET + value: {{ .Values.minio.nextcloudBucket | quote }} + - name: OBJECTSTORE_S3_KEY + value: {{ .Values.minio.nextcloudAccessKey | quote }} + - name: OBJECTSTORE_S3_SECRET + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-minio + key: nextcloudSecretKey + - name: OBJECTSTORE_S3_USEPATH_STYLE + value: "true" + ports: + - name: http + containerPort: 80 + volumeMounts: + - name: data + mountPath: /var/www/html + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-nextcloud-data +--- +# Cron sidecar deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-nextcloud-cron + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: nextcloud-cron +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "nextcloud-cron") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "nextcloud-cron") | nindent 8 }} + spec: + containers: + - name: cron + image: nextcloud:{{ .Values.nextcloud.version }} + command: [/cron.sh] + volumeMounts: + - name: data + mountPath: /var/www/html + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-nextcloud-data +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-nextcloud + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "nextcloud") | nindent 4 }} + ports: + - name: http + port: 80 + targetPort: http +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-nextcloud + labels: + {{- include "sovereign.labels" . | nindent 4 }} + annotations: + {{- include "sovereign.ingressAnnotations" . | nindent 4 }} + # CardDAV / CalDAV redirect + nginx.ingress.kubernetes.io/rewrite-target: /remote.php/dav + nginx.ingress.kubernetes.io/use-regex: "true" +spec: + ingressClassName: {{ .Values.global.ingressClassName }} + rules: + - host: cloud.{{ .Values.global.baseDomain }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-nextcloud + port: + name: http + tls: + - hosts: + - cloud.{{ .Values.global.baseDomain }} + secretName: {{ .Release.Name }}-nextcloud-tls +{{- end }} diff --git a/helm/sovereign/templates/roundcube.yaml b/helm/sovereign/templates/roundcube.yaml new file mode 100644 index 0000000..1e22840 --- /dev/null +++ b/helm/sovereign/templates/roundcube.yaml @@ -0,0 +1,187 @@ +{{- if .Values.roundcube.enabled }} +# ------------------------------------------------------------------------- +# ROUNDCUBE — PostgreSQL + Roundcube webmail +# ------------------------------------------------------------------------- + +# ConfigMap for custom.inc.php overrides +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-roundcube-config + labels: + {{- include "sovereign.labels" . | nindent 4 }} +data: + custom.inc.php: | + -wazuh-certs with these keys: +# +# root-ca.pem root-ca-manager.pem +# wazuh.manager.pem wazuh.manager-key.pem +# wazuh.indexer.pem wazuh.indexer.key +# admin.pem admin-key.pem +# wazuh.dashboard.pem wazuh.dashboard-key.pem +# +# Example: +# kubectl create secret generic -wazuh-certs \ +# --from-file=root-ca.pem=./certs/root-ca.pem \ +# ... (repeat for each cert) +# ------------------------------------------------------------------------- + +# --- PVCs --- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-wazuh-manager + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.wazuh.persistence.managerSize }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-wazuh-indexer + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.wazuh.persistence.indexerSize }} +--- +# --- Wazuh Indexer (OpenSearch-based) --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-wazuh-indexer + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: wazuh-indexer +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "wazuh-indexer") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "wazuh-indexer") | nindent 8 }} + spec: + initContainers: + - name: sysctl + image: busybox + command: ["sysctl", "-w", "vm.max_map_count=262144"] + securityContext: + privileged: true + containers: + - name: wazuh-indexer + image: wazuh/wazuh-indexer:{{ .Values.wazuh.version }} + env: + - name: OPENSEARCH_JAVA_OPTS + value: {{ .Values.wazuh.indexerJavaOpts | quote }} + securityContext: + capabilities: + add: [IPC_LOCK] + volumeMounts: + - name: data + mountPath: /var/lib/wazuh-indexer + - name: certs + mountPath: /usr/share/wazuh-indexer/certs/root-ca.pem + subPath: root-ca.pem + - name: certs + mountPath: /usr/share/wazuh-indexer/certs/wazuh.indexer.key + subPath: wazuh.indexer.key + - name: certs + mountPath: /usr/share/wazuh-indexer/certs/wazuh.indexer.pem + subPath: wazuh.indexer.pem + - name: certs + mountPath: /usr/share/wazuh-indexer/certs/admin.pem + subPath: admin.pem + - name: certs + mountPath: /usr/share/wazuh-indexer/certs/admin-key.pem + subPath: admin-key.pem + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-wazuh-indexer + - name: certs + secret: + secretName: {{ .Release.Name }}-wazuh-certs +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-wazuh-indexer + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "wazuh-indexer") | nindent 4 }} + ports: + - port: 9200 + targetPort: 9200 +--- +# --- Wazuh Manager --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-wazuh-manager + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: wazuh-manager +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "wazuh-manager") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "wazuh-manager") | nindent 8 }} + spec: + containers: + - name: wazuh-manager + image: wazuh/wazuh-manager:{{ .Values.wazuh.version }} + env: + - name: INDEXER_URL + value: https://{{ .Release.Name }}-wazuh-indexer:9200 + - name: INDEXER_USERNAME + value: admin + - name: FILEBEAT_SSL_VERIFICATION_MODE + value: full + - name: SSL_CERTIFICATE_AUTHORITIES + value: /etc/ssl/root-ca.pem + - name: SSL_CERTIFICATE + value: /etc/ssl/filebeat.pem + - name: SSL_KEY + value: /etc/ssl/filebeat.key + - name: API_USERNAME + value: wazuh-wui + - name: INDEXER_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-wazuh + key: adminPassword + - name: API_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-wazuh + key: apiPassword + ports: + - name: agent + containerPort: 1514 + - name: enrollment + containerPort: 1515 + - name: syslog + containerPort: 514 + protocol: UDP + - name: api + containerPort: 55000 + volumeMounts: + - name: data + mountPath: /var/ossec/data + - name: certs + mountPath: /etc/ssl/root-ca.pem + subPath: root-ca-manager.pem + - name: certs + mountPath: /etc/ssl/filebeat.pem + subPath: wazuh.manager.pem + - name: certs + mountPath: /etc/ssl/filebeat.key + subPath: wazuh.manager-key.pem + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ .Release.Name }}-wazuh-manager + - name: certs + secret: + secretName: {{ .Release.Name }}-wazuh-certs +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-wazuh-manager + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "wazuh-manager") | nindent 4 }} + ports: + - name: agent + port: 1514 + targetPort: agent + - name: enrollment + port: 1515 + targetPort: enrollment + - name: syslog + port: 514 + targetPort: syslog + protocol: UDP + - name: api + port: 55000 + targetPort: api +--- +# --- Wazuh Dashboard --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-wazuh-dashboard + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: wazuh-dashboard +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "wazuh-dashboard") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "wazuh-dashboard") | nindent 8 }} + spec: + containers: + - name: wazuh-dashboard + image: wazuh/wazuh-dashboard:{{ .Values.wazuh.version }} + env: + - name: INDEXER_USERNAME + value: admin + - name: WAZUH_API_URL + value: https://{{ .Release.Name }}-wazuh-manager + - name: DASHBOARD_USERNAME + value: kibanaserver + - name: API_USERNAME + value: wazuh-wui + - name: INDEXER_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-wazuh + key: adminPassword + - name: DASHBOARD_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-wazuh + key: adminPassword + - name: API_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-wazuh + key: apiPassword + ports: + - name: https + containerPort: 5601 + volumeMounts: + - name: certs + mountPath: /usr/share/wazuh-dashboard/certs/wazuh-dashboard.pem + subPath: wazuh.dashboard.pem + - name: certs + mountPath: /usr/share/wazuh-dashboard/certs/wazuh-dashboard-key.pem + subPath: wazuh.dashboard-key.pem + - name: certs + mountPath: /usr/share/wazuh-dashboard/certs/root-ca.pem + subPath: root-ca.pem + volumes: + - name: certs + secret: + secretName: {{ .Release.Name }}-wazuh-certs +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-wazuh-dashboard + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "wazuh-dashboard") | nindent 4 }} + ports: + - name: https + port: 5601 + targetPort: https +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-wazuh + labels: + {{- include "sovereign.labels" . | nindent 4 }} + annotations: + {{- include "sovereign.ingressAnnotations" . | nindent 4 }} + # Dashboard speaks HTTPS internally + nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" + traefik.ingress.kubernetes.io/service.serversscheme: "https" +spec: + ingressClassName: {{ .Values.global.ingressClassName }} + rules: + - host: wazuh.{{ .Values.global.baseDomain }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-wazuh-dashboard + port: + name: https + tls: + - hosts: + - wazuh.{{ .Values.global.baseDomain }} + secretName: {{ .Release.Name }}-wazuh-tls +{{- end }} diff --git a/helm/sovereign/templates/website.yaml b/helm/sovereign/templates/website.yaml new file mode 100644 index 0000000..542d87f --- /dev/null +++ b/helm/sovereign/templates/website.yaml @@ -0,0 +1,89 @@ +{{- if .Values.website.enabled }} +# ------------------------------------------------------------------------- +# WEBSITE — static site served by nginx +# ------------------------------------------------------------------------- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-website-html + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + accessModes: [ReadWriteOnce] + {{- include "sovereign.storageClass" .Values.global.storageClass | nindent 2 }} + resources: + requests: + storage: {{ .Values.website.persistence.htmlSize }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-website + labels: + {{- include "sovereign.labels" . | nindent 4 }} + app.kubernetes.io/component: website +spec: + replicas: 1 + selector: + matchLabels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "website") | nindent 6 }} + template: + metadata: + labels: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "website") | nindent 8 }} + spec: + containers: + - name: nginx + image: nginx:{{ .Values.website.nginxVersion }} + ports: + - name: http + containerPort: 80 + volumeMounts: + - name: html + mountPath: /usr/share/nginx/html + readOnly: true + volumes: + - name: html + persistentVolumeClaim: + claimName: {{ .Release.Name }}-website-html +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-website + labels: + {{- include "sovereign.labels" . | nindent 4 }} +spec: + selector: + {{- include "sovereign.selectorLabels" (dict "root" . "component" "website") | nindent 4 }} + ports: + - name: http + port: 80 + targetPort: http +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-website + labels: + {{- include "sovereign.labels" . | nindent 4 }} + annotations: + {{- include "sovereign.ingressAnnotations" . | nindent 4 }} +spec: + ingressClassName: {{ .Values.global.ingressClassName }} + rules: + - host: {{ .Values.global.baseDomain }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-website + port: + name: http + tls: + - hosts: + - {{ .Values.global.baseDomain }} + secretName: {{ .Release.Name }}-website-tls +{{- end }} diff --git a/helm/sovereign/values.yaml b/helm/sovereign/values.yaml new file mode 100644 index 0000000..438c4c7 --- /dev/null +++ b/helm/sovereign/values.yaml @@ -0,0 +1,244 @@ +# ============================================================================= +# SOVEREIGN HELM VALUES +# Mirrors the structure of inventories/production/group_vars/all.yml. +# For a new deployment: copy this file, update baseDomain, and replace all +# changeme_* values with secure secrets. +# ============================================================================= + +# -- Global settings shared by all services +global: + # Base domain — all service subdomains derive from this + baseDomain: "example.com" + # Display name shown in service UIs and email subjects + tenantName: "Example Corp" + # Brand colours (hex) + primaryColor: "#2563eb" + accentColor: "#1e40af" + # Kubernetes IngressClass name used for all Ingress resources + ingressClassName: "traefik" + # cert-manager ClusterIssuer name; leave empty to disable cert-manager integration + clusterIssuer: "" + # Default StorageClass for PVCs; empty string = cluster default + storageClass: "" + +# -- SMTP (shared across services that send email) +smtp: + # Service name or hostname for the SMTP server. + # Defaults to the release-name-stalwart service when empty. + host: "" + port: 587 + # Sender address; defaults to noreply@ when empty + from: "" + # Login username; defaults to noreply@ when empty + user: "" + password: "changeme_smtp" + # starttls | tls | plain + tls: "starttls" + +# ============================================================================= +# TRAEFIK — reverse proxy / ingress controller +# Set enabled: false to use an existing ingress controller (nginx, etc.) +# and still deploy all other services with standard Ingress resources. +# ============================================================================= +traefik: + enabled: true + version: "v3.1" + # ACME / Let's Encrypt email; defaults to admin@ when empty + acmeEmail: "" + # htpasswd-hashed password for the Traefik dashboard + dashboardPassword: "changeme" + service: + # LoadBalancer | NodePort + type: LoadBalancer + +# ============================================================================= +# GRAYLOG — centralised logging (Graylog + OpenSearch + MongoDB) +# ============================================================================= +graylog: + enabled: true + version: "6.0" + opensearchVersion: "2.15.0" + opensearchJavaOpts: "-Xms1g -Xmx1g" + mongodbVersion: "6.0" + # Minimum 16 characters + passwordSecret: "changeme_graylog_secret_min_16_chars" + # sha256 of the root password: echo -n yourpassword | sha256sum + rootPasswordSha2: "changeme_sha256_of_password" + persistence: + mongodbSize: "10Gi" + opensearchSize: "50Gi" + graylogSize: "10Gi" + +# ============================================================================= +# AUTHENTIK — identity provider (OIDC / OAuth2 / SAML) +# ============================================================================= +authentik: + enabled: true + version: "2024.10.5" + # 50-character random string + secretKey: "change-me-to-a-50-char-random-string" + dbPassword: "changeme_authentik_db" + # Defaults to admin@ when empty + adminEmail: "" + adminPassword: "changeme_admin" + persistence: + postgresSize: "10Gi" + mediaSize: "5Gi" + certsSize: "1Gi" + +# ============================================================================= +# MINIO — S3-compatible object storage +# ============================================================================= +minio: + enabled: true + version: "latest" + rootUser: "minioadmin" + rootPassword: "changeme_minio" + # OIDC client secret configured in Authentik + oidcSecret: "changeme_minio_oidc_secret" + # Bucket / credentials used by Nextcloud + nextcloudBucket: "nextcloud" + nextcloudAccessKey: "nextcloud" + nextcloudSecretKey: "changeme_nextcloud_s3" + persistence: + size: "100Gi" + +# ============================================================================= +# NEXTCLOUD — file sync and share +# ============================================================================= +nextcloud: + enabled: true + version: "29" + adminUser: "admin" + adminPassword: "changeme_nextcloud" + dbPassword: "changeme_nextcloud_db" + dbRootPassword: "changeme_nextcloud_db_root" + persistence: + dbSize: "10Gi" + dataSize: "50Gi" + +# ============================================================================= +# STALWART MAIL — SMTP / IMAP mail server +# ============================================================================= +stalwart: + enabled: true + version: "latest" + adminPassword: "changeme_mail_admin" + persistence: + size: "20Gi" + # Service type for external mail ports (25/465/587/993/4190) + # LoadBalancer | NodePort + mailServiceType: LoadBalancer + +# ============================================================================= +# ROUNDCUBE — webmail client +# ============================================================================= +roundcube: + enabled: true + version: "latest" + dbPassword: "changeme_roundcube_db" + # Exactly 24 characters + desKey: "changeme_24_char_des_key____" + skin: "elastic" + persistence: + dbSize: "5Gi" + +# ============================================================================= +# MATRIX / ELEMENT — federated chat (Synapse + Element Web) +# ============================================================================= +matrix: + enabled: true + synapseVersion: "v1.118.0" + # Used for user registration via the admin API + registrationSecret: "changeme_registration_secret" + dbPassword: "changeme_matrix_db" + persistence: + dbSize: "10Gi" + synapseSize: "20Gi" + +# ============================================================================= +# JITSI — video conferencing +# ============================================================================= +jitsi: + enabled: true + version: "stable-9753" + jicofoAuthPassword: "changeme_jicofo" + jvbAuthPassword: "changeme_jvb" + jibriRecorderPassword: "changeme_jibri_recorder" + jibriXmppPassword: "changeme_jibri_xmpp" + turnSecret: "changeme_turn" + persistence: + webSize: "1Gi" + prosodySize: "1Gi" + jicofoSize: "1Gi" + jvbSize: "1Gi" + +# ============================================================================= +# HEADSCALE — WireGuard mesh VPN coordinator +# ============================================================================= +headscale: + enabled: true + version: "0.23.0" + wireguardPort: 51820 + persistence: + configSize: "1Gi" + dataSize: "5Gi" + +# ============================================================================= +# WAZUH — endpoint security (Manager + Indexer + Dashboard) +# NOTE: Wazuh requires pre-generated TLS certificates stored in a Kubernetes +# Secret named -wazuh-certs with the keys documented in +# templates/wazuh.yaml before first deployment. +# ============================================================================= +wazuh: + enabled: true + version: "4.9.0" + adminPassword: "changeme_wazuh_admin" + apiPassword: "changeme_wazuh_api" + indexerJavaOpts: "-Xms512m -Xmx512m" + persistence: + managerSize: "20Gi" + indexerSize: "50Gi" + +# ============================================================================= +# VAULTWARDEN — Bitwarden-compatible password manager +# ============================================================================= +vaultwarden: + enabled: true + version: "latest" + adminToken: "changeme_vaultwarden_admin_token" + dbPassword: "changeme_vaultwarden_db" + # OIDC client secret configured in Authentik + oidcSecret: "changeme_vaultwarden_oidc_secret" + persistence: + dbSize: "5Gi" + dataSize: "10Gi" + +# ============================================================================= +# FORGEJO — self-hosted Git +# ============================================================================= +forgejo: + enabled: true + version: "latest" + dbPassword: "changeme_forgejo_db" + secretKey: "changeme_forgejo_secret" + internalToken: "changeme_forgejo_internal_token" + lfsJwtSecret: "changeme_forgejo_lfs_jwt" + adminUser: "admin" + adminPassword: "changeme_forgejo_admin" + # Defaults to admin@ when empty + adminEmail: "" + # NodePort number for SSH git access + sshPort: 2222 + persistence: + dbSize: "10Gi" + dataSize: "20Gi" + +# ============================================================================= +# WEBSITE — static site (nginx) +# ============================================================================= +website: + enabled: true + nginxVersion: "alpine" + persistence: + htmlSize: "1Gi"