diff --git a/charts/helm/checkmate/Chart.yaml b/charts/helm/checkmate/Chart.yaml index 97f320e68b..db7c108fd7 100644 --- a/charts/helm/checkmate/Chart.yaml +++ b/charts/helm/checkmate/Chart.yaml @@ -1,6 +1,16 @@ apiVersion: v2 name: checkmate-chart -description: A Helm chart for Checkmate App +description: Helm chart for Checkmate — open-source uptime and infrastructure monitoring type: application -version: 0.1.0 +version: 0.2.0 appVersion: "2.3" +keywords: + - monitoring + - uptime + - infrastructure + - observability + - checkmate +home: https://github.com/bluewave-labs/Checkmate +maintainers: + - name: Bluewave Labs + url: https://github.com/bluewave-labs diff --git a/charts/helm/checkmate/INSTALLATION.md b/charts/helm/checkmate/INSTALLATION.md index 06cf9f7b96..31ea448dab 100644 --- a/charts/helm/checkmate/INSTALLATION.md +++ b/charts/helm/checkmate/INSTALLATION.md @@ -21,24 +21,22 @@ cd checkmate/charts/helm/checkmate Edit `values.yaml` to update: - `client.ingress.host` and `server.ingress.host` with your domain names - `server.protocol` (usually http or https) -- **If upgrading**: Migrate persistence settings from flat structure to nested: - - Old: `persistence.mongodbSize` → New: `persistence.mongo.size` - - Old: `persistence.redisSize` → New: `persistence.redis.size` - - Add: `persistence.mongo.storageClass` and `persistence.redis.storageClass` (leave empty for default) -- Secrets under the `secrets` section (`JWT_SECRET`, email credentials, API keys, etc.) — replace all change_me values +- Secrets under the `secrets` section (`JWT_SECRET`, email credentials, API keys, etc.) — replace all `change_me` values - **For TLS/HTTPS**: Configure ingress TLS settings (see section below) +- **For external databases**: Set `mongodb.enabled: false` and provide `secrets.DB_CONNECTION_STRING`, or set `redis.enabled: true` to use the bundled Redis +- **To use a pre-existing Kubernetes Secret**: Set `secrets.existingSecret: "your-secret-name"` instead of inline values ### 3. Deploy the Helm chart ```bash -helm install checkmate ./charts/helm/checkmate +helm install checkmate ./charts/helm/checkmate --namespace checkmate --create-namespace ``` -This will deploy the client, server, MongoDB, and Redis components. +This will deploy the client, server, and MongoDB. Redis is disabled by default — set `redis.enabled: true` in `values.yaml` to include it. ### 4. Verify the deployment Check pods and services: ```bash -kubectl get pods -kubectl get svc +kubectl get pods -n checkmate +kubectl get svc -n checkmate ``` Once all pods are `Running` and `Ready`, you can access Checkmate via the configured ingress hosts. @@ -72,7 +70,7 @@ server: protocol: https ingress: enabled: true - host: checkmate.example.com + host: api.checkmate.example.com className: nginx annotations: cert-manager.io/cluster-issuer: "letsencrypt-prod" @@ -103,14 +101,27 @@ After deployment, cert-manager will automatically create the TLS secrets. You ca ```bash # Check certificates -kubectl get certificates +kubectl get certificates -n checkmate # Check certificate details -kubectl describe certificate checkmate-client-tls -kubectl describe certificate checkmate-server-tls +kubectl describe certificate checkmate-client-tls -n checkmate +kubectl describe certificate checkmate-server-tls -n checkmate # Verify the secrets were created -kubectl get secrets | grep checkmate-tls +kubectl get secrets -n checkmate | grep tls ``` The ingress will automatically use these secrets to enable HTTPS access to your Checkmate instance. + +## Upgrading from v0.1.x + +Persistence keys moved to component-scoped locations. The old keys are still accepted for backward compatibility, but new installations should use the new paths: + +| Old (v0.1.x) | New (v0.2.0+) | +|---------------------------------|------------------------------------| +| `persistence.mongo.size` | `mongodb.persistence.size` | +| `persistence.redis.size` | `redis.persistence.size` | +| `persistence.mongo.storageClass`| `mongodb.persistence.storageClass` | +| `persistence.redis.storageClass`| `redis.persistence.storageClass` | + +Redis is now disabled by default. If you were using the bundled Redis, add `redis.enabled: true` to your values. diff --git a/charts/helm/checkmate/templates/NOTES.txt b/charts/helm/checkmate/templates/NOTES.txt new file mode 100644 index 0000000000..4fb5d41eba --- /dev/null +++ b/charts/helm/checkmate/templates/NOTES.txt @@ -0,0 +1,68 @@ +Thank you for installing {{ .Chart.Name }} v{{ .Chart.AppVersion }}! + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +ACCESSING CHECKMATE +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +{{- if .Values.client.ingress.enabled }} + + Frontend ➜ {{ .Values.client.protocol }}://{{ .Values.client.ingress.host }} +{{- else if .Values.client.httproute.enabled }} + {{- if .Values.client.httproute.hostnames }} + + Frontend ➜ {{ .Values.client.protocol }}://{{ first .Values.client.httproute.hostnames }} + {{- else }} + + Frontend ➜ (HTTPRoute — check your Gateway for the external address) + {{- end }} +{{- end }} +{{- if .Values.server.ingress.enabled }} + API ➜ {{ .Values.server.protocol }}://{{ .Values.server.ingress.host }}/api/v1 + Docs ➜ {{ .Values.server.protocol }}://{{ .Values.server.ingress.host }}/api-docs +{{- else if .Values.server.httproute.enabled }} + {{- if .Values.server.httproute.hostnames }} + API ➜ {{ .Values.server.protocol }}://{{ first .Values.server.httproute.hostnames }}/api/v1 + Docs ➜ {{ .Values.server.protocol }}://{{ first .Values.server.httproute.hostnames }}/api-docs + {{- else }} + API ➜ (HTTPRoute — check your Gateway for the external address) + {{- end }} +{{- end }} + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +VERIFY PODS +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + kubectl get pods -n {{ include "checkmate.namespace" . }} \ + -l app.kubernetes.io/instance={{ .Release.Name }} + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +DATABASES +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +{{- if .Values.mongodb.enabled }} + + MongoDB ✔ in-cluster StatefulSet + ({{ include "checkmate.fullname" . }}-mongodb.{{ include "checkmate.namespace" . }}.svc) +{{- else }} + + MongoDB ✔ external — ensure secrets.DB_CONNECTION_STRING is correct +{{- end }} +{{- if .Values.redis.enabled }} + + Redis ✔ in-cluster StatefulSet + ({{ include "checkmate.fullname" . }}-redis.{{ include "checkmate.namespace" . }}.svc) +{{- else }} + + Redis — disabled + Set redis.enabled=true to deploy Redis, + or add REDIS_HOST/REDIS_PORT to secrets for an external instance. +{{- end }} + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +SECRETS MODE +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +{{- if .Values.secrets.existingSecret }} + + Using existing Secret: {{ .Values.secrets.existingSecret }} +{{- else }} + + Using chart-managed Secret: {{ include "checkmate.fullname" . }}-secrets +{{- end }} diff --git a/charts/helm/checkmate/templates/_helpers.tpl b/charts/helm/checkmate/templates/_helpers.tpl new file mode 100644 index 0000000000..a6e69f11ff --- /dev/null +++ b/charts/helm/checkmate/templates/_helpers.tpl @@ -0,0 +1,205 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "checkmate.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully-qualified app name, truncated at 63 chars. +If the release name already contains the chart name, the chart name is omitted. +*/}} +{{- define "checkmate.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 }} + +{{/* +Chart name + version as a label value. +*/}} +{{- define "checkmate.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Namespace — honours namespaceOverride when set. +*/}} +{{- define "checkmate.namespace" -}} +{{- default .Release.Namespace .Values.namespaceOverride }} +{{- end }} + +{{/* +Common labels applied to every resource (includes chart version — do not use in selectors). +*/}} +{{- define "checkmate.labels" -}} +helm.sh/chart: {{ include "checkmate.chart" . }} +{{ include "checkmate.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- with .Values.global.labels }} +{{ toYaml . }} +{{- end }} +{{- end }} + +{{/* +Selector labels — immutable after first deploy, must NOT include chart version. +*/}} +{{- define "checkmate.selectorLabels" -}} +app.kubernetes.io/name: {{ include "checkmate.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Component-specific selector labels (used in Deployment.spec.selector and Service.spec.selector). +Usage: {{ include "checkmate.componentSelectorLabels" (dict "root" . "component" "client") }} +*/}} +{{- define "checkmate.componentSelectorLabels" -}} +{{ include "checkmate.selectorLabels" .root }} +app.kubernetes.io/component: {{ .component }} +{{- end }} + +{{/* +Full label set for a specific component (common labels + component selector). +Usage: {{ include "checkmate.componentLabels" (dict "root" . "component" "client") }} +*/}} +{{- define "checkmate.componentLabels" -}} +{{ include "checkmate.labels" .root }} +app.kubernetes.io/component: {{ .component }} +{{- end }} + +{{/* +Name of the Secret resource holding server environment variables. +Returns the existingSecret name when provided, otherwise the chart-generated name. +*/}} +{{- define "checkmate.secretName" -}} +{{- if .Values.secrets.existingSecret }} +{{- .Values.secrets.existingSecret }} +{{- else }} +{{- printf "%s-secrets" (include "checkmate.fullname" .) }} +{{- end }} +{{- end }} + +{{/* +Derive the MongoDB connection string. +Priority: explicit secrets.DB_CONNECTION_STRING > auto-generated (when mongodb.enabled) > fail. +*/}} +{{- define "checkmate.mongodbConnectionString" -}} +{{- if .Values.secrets.DB_CONNECTION_STRING }} +{{- .Values.secrets.DB_CONNECTION_STRING }} +{{- else if .Values.mongodb.enabled }} +{{- printf "mongodb://%s-mongodb.%s.svc:%d/uptime_db" + (include "checkmate.fullname" .) + (include "checkmate.namespace" .) + (int .Values.mongodb.port) }} +{{- else }} +{{- fail "secrets.DB_CONNECTION_STRING must be set when mongodb.enabled is false (external MongoDB)" }} +{{- end }} +{{- end }} + +{{/* +Derive CLIENT_HOST (base URL of the frontend, used by the server for CORS). +Priority: explicit secrets.CLIENT_HOST > Ingress host > first HTTPRoute hostname > fail. +"change_me" is treated as unset so the auto-derivation still applies. +*/}} +{{- define "checkmate.clientHost" -}} +{{- if and .Values.secrets.CLIENT_HOST (ne .Values.secrets.CLIENT_HOST "change_me") (ne .Values.secrets.CLIENT_HOST "") }} +{{- .Values.secrets.CLIENT_HOST }} +{{- else if .Values.client.ingress.enabled }} +{{- printf "%s://%s" .Values.client.protocol .Values.client.ingress.host }} +{{- else if and .Values.client.httproute.enabled .Values.client.httproute.hostnames }} +{{- printf "%s://%s" .Values.client.protocol (first .Values.client.httproute.hostnames) }} +{{- else }} +{{- fail "secrets.CLIENT_HOST must be set when it cannot be auto-derived from client.ingress.host or client.httproute.hostnames" }} +{{- end }} +{{- end }} + +{{/* +Derive the server API base URL injected into the client container at runtime. +Priority: + 1. server.apiBaseURL explicit override (useful for local/port-forward access) + 2. server Ingress host + 3. first server HTTPRoute hostname + 4. empty string — set server.apiBaseURL when no routing is configured +*/}} +{{- define "checkmate.serverAPIBaseURL" -}} +{{- if and .Values.server.apiBaseURL (ne .Values.server.apiBaseURL "") }} +{{- .Values.server.apiBaseURL }} +{{- else if .Values.server.ingress.enabled }} +{{- printf "%s://%s/api/v1" .Values.server.protocol .Values.server.ingress.host }} +{{- else if and .Values.server.httproute.enabled .Values.server.httproute.hostnames }} +{{- printf "%s://%s/api/v1" .Values.server.protocol (first .Values.server.httproute.hostnames) }} +{{- else }} +{{- "" }} +{{- end }} +{{- end }} + +{{/* +TLS Secret name for the client Ingress. +Defaults to "-client-tls" when tls.secretName is not specified. +*/}} +{{- define "checkmate.clientIngressTLSSecretName" -}} +{{- default (printf "%s-client-tls" .Release.Name) .Values.client.ingress.tls.secretName }} +{{- end }} + +{{/* +TLS Secret name for the server Ingress. +Defaults to "-server-tls" when tls.secretName is not specified. +*/}} +{{- define "checkmate.serverIngressTLSSecretName" -}} +{{- default (printf "%s-server-tls" .Release.Name) .Values.server.ingress.tls.secretName }} +{{- end }} + +{{/* +MongoDB PVC size — new mongodb.persistence.size location wins; legacy persistence.mongo.size +overrides it when non-empty (backward compatibility with chart v0.1.x). +*/}} +{{- define "checkmate.mongodbPVCSize" -}} +{{- if .Values.persistence.mongo.size }} +{{- .Values.persistence.mongo.size }} +{{- else }} +{{- .Values.mongodb.persistence.size }} +{{- end }} +{{- end }} + +{{/* +MongoDB PVC storageClass — legacy persistence.mongo.storageClass overrides when non-empty. +*/}} +{{- define "checkmate.mongodbStorageClass" -}} +{{- if .Values.persistence.mongo.storageClass }} +{{- .Values.persistence.mongo.storageClass }} +{{- else }} +{{- .Values.mongodb.persistence.storageClass }} +{{- end }} +{{- end }} + +{{/* +Redis PVC size — legacy persistence.redis.size overrides when non-empty. +*/}} +{{- define "checkmate.redisPVCSize" -}} +{{- if .Values.persistence.redis.size }} +{{- .Values.persistence.redis.size }} +{{- else }} +{{- .Values.redis.persistence.size }} +{{- end }} +{{- end }} + +{{/* +Redis PVC storageClass — legacy persistence.redis.storageClass overrides when non-empty. +*/}} +{{- define "checkmate.redisStorageClass" -}} +{{- if .Values.persistence.redis.storageClass }} +{{- .Values.persistence.redis.storageClass }} +{{- else }} +{{- .Values.redis.persistence.storageClass }} +{{- end }} +{{- end }} diff --git a/charts/helm/checkmate/templates/client-deployment.yaml b/charts/helm/checkmate/templates/client-deployment.yaml index 381631ef5b..ad298afeb8 100644 --- a/charts/helm/checkmate/templates/client-deployment.yaml +++ b/charts/helm/checkmate/templates/client-deployment.yaml @@ -1,44 +1,81 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: checkmate-client + name: {{ include "checkmate.fullname" . }}-client + namespace: {{ include "checkmate.namespace" . }} + labels: + {{- include "checkmate.componentLabels" (dict "root" . "component" "client") | nindent 4 }} + {{- with .Values.global.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: - replicas: 1 + replicas: {{ .Values.client.replicas }} selector: matchLabels: - app: checkmate-client + {{- include "checkmate.componentSelectorLabels" (dict "root" . "component" "client") | nindent 6 }} template: metadata: labels: - app: checkmate-client + {{- include "checkmate.componentSelectorLabels" (dict "root" . "component" "client") | nindent 8 }} + {{- with .Values.client.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.client.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} spec: - {{- with .Values.client.affinity }} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.client.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.client.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.client.affinity }} affinity: - {{- tpl ( . | toYaml) $ | nindent 8 }} - {{- end }} - {{- with .Values.client.tolerations }} + {{- include "checkmate.tplrender" (dict "value" . "context" $) | nindent 8 }} + {{- end }} + {{- with .Values.client.tolerations }} tolerations: - {{- tpl ( . | toYaml) $ | nindent 8 }} - {{- end }} + {{- include "checkmate.tplrender" (dict "value" . "context" $) | nindent 8 }} + {{- end }} containers: - name: client image: {{ .Values.client.image }} + imagePullPolicy: {{ .Values.client.imagePullPolicy }} ports: - - containerPort: {{ .Values.client.port }} + - name: http + containerPort: {{ .Values.client.port }} + protocol: TCP env: + # Runtime variables consumed by the nginx entrypoint script in the container image. - name: UPTIME_APP_API_BASE_URL - value: "{{ .Values.server.protocol }}://{{ .Values.server.ingress.host }}/api/v1" + value: {{ include "checkmate.serverAPIBaseURL" . | quote }} - name: UPTIME_APP_CLIENT_HOST - value: "{{ .Values.client.protocol }}://{{ .Values.client.ingress.host }}" + value: {{ include "checkmate.clientHost" . | quote }} + {{- with .Values.client.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} volumeMounts: - - name: config-volume + - name: nginx-config mountPath: /etc/nginx/conf.d/default.conf subPath: default.conf + {{- with .Values.client.containerSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} {{- with .Values.client.resources }} resources: - {{- tpl ( . | toYaml) $ | nindent 12 }} + {{- toYaml . | nindent 12 }} {{- end }} volumes: - - name: config-volume + - name: nginx-config configMap: - name: checkmate-server-nginx-cm + name: {{ include "checkmate.fullname" . }}-nginx-cm diff --git a/charts/helm/checkmate/templates/client-httproute.yaml b/charts/helm/checkmate/templates/client-httproute.yaml new file mode 100644 index 0000000000..249f6e607c --- /dev/null +++ b/charts/helm/checkmate/templates/client-httproute.yaml @@ -0,0 +1,33 @@ +{{- if .Values.client.httproute.enabled }} +{{- if not (.Capabilities.APIVersions.Has "gateway.networking.k8s.io/v1") }} + {{- fail "Gateway API CRDs (gateway.networking.k8s.io/v1) not found in the cluster. Install them before enabling client.httproute." }} +{{- end }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "checkmate.fullname" . }}-client + namespace: {{ include "checkmate.namespace" . }} + labels: + {{- include "checkmate.componentLabels" (dict "root" . "component" "client") | nindent 4 }} + {{- $ann := merge (default dict .Values.client.httproute.annotations) (default dict .Values.global.annotations) }} + {{- if $ann }} + annotations: + {{- toYaml $ann | nindent 4 }} + {{- end }} +spec: + parentRefs: + - name: {{ .Values.client.httproute.gatewayName }} + namespace: {{ default (include "checkmate.namespace" .) .Values.client.httproute.gatewayNamespace }} + {{- if .Values.client.httproute.hostnames }} + hostnames: + {{- toYaml .Values.client.httproute.hostnames | nindent 4 }} + {{- end }} + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: {{ include "checkmate.fullname" . }}-client + port: {{ .Values.client.port }} +{{- end }} diff --git a/charts/helm/checkmate/templates/client-ingress.yaml b/charts/helm/checkmate/templates/client-ingress.yaml index 4fe126ea68..44b49f2c48 100644 --- a/charts/helm/checkmate/templates/client-ingress.yaml +++ b/charts/helm/checkmate/templates/client-ingress.yaml @@ -2,12 +2,14 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: checkmate-client-ingress - {{- if .Values.client.ingress.annotations }} + name: {{ include "checkmate.fullname" . }}-client + namespace: {{ include "checkmate.namespace" . }} + labels: + {{- include "checkmate.componentLabels" (dict "root" . "component" "client") | nindent 4 }} + {{- $ann := merge (default dict .Values.client.ingress.annotations) (default dict .Values.global.annotations) }} + {{- if $ann }} annotations: - {{- range $key, $value := .Values.client.ingress.annotations }} - {{ $key }}: {{ $value | quote }} - {{- end }} + {{- toYaml $ann | nindent 4 }} {{- end }} spec: ingressClassName: {{ .Values.client.ingress.className }} @@ -15,7 +17,7 @@ spec: tls: - hosts: - {{ .Values.client.ingress.host }} - secretName: {{ default (printf "%s-client-tls" .Release.Name) .Values.client.ingress.tls.secretName }} + secretName: {{ include "checkmate.clientIngressTLSSecretName" . }} {{- end }} rules: - host: {{ .Values.client.ingress.host }} @@ -25,7 +27,7 @@ spec: pathType: Prefix backend: service: - name: checkmate-client + name: {{ include "checkmate.fullname" . }}-client port: number: {{ .Values.client.port }} {{- end }} diff --git a/charts/helm/checkmate/templates/client-service.yaml b/charts/helm/checkmate/templates/client-service.yaml index b56eaeedfc..f8c32c3332 100644 --- a/charts/helm/checkmate/templates/client-service.yaml +++ b/charts/helm/checkmate/templates/client-service.yaml @@ -1,9 +1,20 @@ apiVersion: v1 kind: Service metadata: - name: checkmate-client + name: {{ include "checkmate.fullname" . }}-client + namespace: {{ include "checkmate.namespace" . }} + labels: + {{- include "checkmate.componentLabels" (dict "root" . "component" "client") | nindent 4 }} + {{- with .Values.global.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: + type: ClusterIP selector: - app: checkmate-client + {{- include "checkmate.componentSelectorLabels" (dict "root" . "component" "client") | nindent 4 }} ports: - - port: {{ .Values.client.port }} + - name: http + port: {{ .Values.client.port }} + targetPort: http + protocol: TCP diff --git a/charts/helm/checkmate/templates/mongodb-service.yaml b/charts/helm/checkmate/templates/mongodb-service.yaml index b96fe1a820..322e205104 100644 --- a/charts/helm/checkmate/templates/mongodb-service.yaml +++ b/charts/helm/checkmate/templates/mongodb-service.yaml @@ -1,9 +1,22 @@ +{{- if .Values.mongodb.enabled }} apiVersion: v1 kind: Service metadata: - name: checkmate-mongodb + name: {{ include "checkmate.fullname" . }}-mongodb + namespace: {{ include "checkmate.namespace" . }} + labels: + {{- include "checkmate.componentLabels" (dict "root" . "component" "mongodb") | nindent 4 }} + {{- with .Values.global.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: + type: ClusterIP selector: - app: checkmate-mongodb + {{- include "checkmate.componentSelectorLabels" (dict "root" . "component" "mongodb") | nindent 4 }} ports: - - port: {{ .Values.mongodb.port }} \ No newline at end of file + - name: mongodb + port: {{ .Values.mongodb.port }} + targetPort: mongodb + protocol: TCP +{{- end }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/mongodb-statefulsets.yaml b/charts/helm/checkmate/templates/mongodb-statefulsets.yaml index 8149b77e28..92f065939c 100644 --- a/charts/helm/checkmate/templates/mongodb-statefulsets.yaml +++ b/charts/helm/checkmate/templates/mongodb-statefulsets.yaml @@ -1,44 +1,68 @@ +{{- if .Values.mongodb.enabled }} apiVersion: apps/v1 kind: StatefulSet metadata: - name: checkmate-mongodb + name: {{ include "checkmate.fullname" . }}-mongodb + namespace: {{ include "checkmate.namespace" . }} + labels: + {{- include "checkmate.componentLabels" (dict "root" . "component" "mongodb") | nindent 4 }} + {{- with .Values.global.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: replicas: 1 + serviceName: {{ include "checkmate.fullname" . }}-mongodb selector: matchLabels: - app: checkmate-mongodb + {{- include "checkmate.componentSelectorLabels" (dict "root" . "component" "mongodb") | nindent 6 }} template: metadata: labels: - app: checkmate-mongodb + {{- include "checkmate.componentSelectorLabels" (dict "root" . "component" "mongodb") | nindent 8 }} spec: - {{- with .Values.mongodb.affinity }} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.mongodb.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.mongodb.affinity }} affinity: - {{- tpl ( . | toYaml) $ | nindent 8 }} - {{- end }} - {{- with .Values.mongodb.tolerations }} + {{- include "checkmate.tplrender" (dict "value" . "context" $) | nindent 8 }} + {{- end }} + {{- with .Values.mongodb.tolerations }} tolerations: - {{- tpl ( . | toYaml) $ | nindent 8 }} - {{- end }} + {{- include "checkmate.tplrender" (dict "value" . "context" $) | nindent 8 }} + {{- end }} containers: - name: mongodb image: {{ .Values.mongodb.image }} + imagePullPolicy: {{ .Values.mongodb.imagePullPolicy }} ports: - - containerPort: {{ .Values.mongodb.port }} + - name: mongodb + containerPort: {{ .Values.mongodb.port }} + protocol: TCP command: ["mongod", "--quiet", "--bind_ip_all"] volumeMounts: - - name: checkmate-mongo-persistent-storage + - name: data mountPath: /data/db {{- with .Values.mongodb.resources }} resources: - {{- tpl ( . | toYaml) $ | nindent 12 }} + {{- toYaml . | nindent 12 }} {{- end }} volumeClaimTemplates: - metadata: - name: checkmate-mongo-persistent-storage + name: data spec: - storageClassName: {{ .Values.persistence.mongo.storageClass | quote }} + {{- $sc := include "checkmate.mongodbStorageClass" . }} + {{- if $sc }} + storageClassName: {{ $sc | quote }} + {{- end }} accessModes: ["ReadWriteOnce"] resources: requests: - storage: {{ .Values.persistence.mongo.size | default "5Gi" | quote }} \ No newline at end of file + storage: {{ include "checkmate.mongodbPVCSize" . | quote }} +{{- end }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/prechecks.yaml b/charts/helm/checkmate/templates/prechecks.yaml index e2b9ec5c25..47a0c63ba9 100644 --- a/charts/helm/checkmate/templates/prechecks.yaml +++ b/charts/helm/checkmate/templates/prechecks.yaml @@ -1,48 +1,79 @@ -{{- if eq .Values.client.ingress.host "change_me" }} - {{- fail "client.ingress.host must be overridden and not set to 'change_me'" }} -{{- end }} +{{- /* + prechecks.yaml — pre-flight validation run at helm install / helm upgrade. + Produces no Kubernetes resources; only fail() calls that abort the render early. +*/ -}} -{{- if eq .Values.server.ingress.host "change_me" }} - {{- fail "server.ingress.host must be overridden and not set to 'change_me'" }} +{{- /* ── Secret validation ─────────────────────────────────────────────────── */}} +{{- if not .Values.secrets.existingSecret }} + {{- if or (empty .Values.secrets.JWT_SECRET) (eq .Values.secrets.JWT_SECRET "change_me") }} + {{- fail "secrets.JWT_SECRET must be set to a non-default value before deploying" }} + {{- end }} {{- end }} -{{- $serverProtocol := .Values.server.protocol }} -{{- if not (or (eq $serverProtocol "http") (eq $serverProtocol "https")) }} - {{- fail "server.protocol must be either 'http' or 'https'" }} +{{- /* ── Exactly one routing mode per component ───────────────────────────── */}} +{{- if and .Values.client.ingress.enabled .Values.client.httproute.enabled }} + {{- fail "client: ingress.enabled and httproute.enabled cannot both be true — choose one" }} {{- end }} - -{{- $clientProtocol := .Values.client.protocol }} -{{- if not (or (eq $clientProtocol "http") (eq $clientProtocol "https")) }} - {{- fail "client.protocol must be either 'http' or 'https'" }} +{{- if and .Values.server.ingress.enabled .Values.server.httproute.enabled }} + {{- fail "server: ingress.enabled and httproute.enabled cannot both be true — choose one" }} {{- end }} -{{/* Enforce protocol when TLS is enabled to avoid mixed-content */}} -{{- if and .Values.client.ingress.tls.enabled (ne $clientProtocol "https") }} - {{- fail "client.ingress.tls.enabled is true but client.protocol is not 'https'. Set client.protocol: https to avoid mixed content." }} +{{- /* ── Client Ingress validations ─────────────────────────────────────────── */}} +{{- if .Values.client.ingress.enabled }} + {{- if eq .Values.client.ingress.host "change_me" }} + {{- fail "client.ingress.host must be set to a real hostname before deploying" }} + {{- end }} + {{- $proto := .Values.client.protocol }} + {{- if not (or (eq $proto "http") (eq $proto "https")) }} + {{- fail "client.protocol must be either 'http' or 'https'" }} + {{- end }} + {{- if and .Values.client.ingress.tls.enabled (ne $proto "https") }} + {{- fail "client.ingress.tls.enabled requires client.protocol: https to prevent mixed-content issues" }} + {{- end }} + {{- $ann := .Values.client.ingress.annotations | default dict }} + {{- if and .Values.client.ingress.tls.enabled (not (or (hasKey $ann "cert-manager.io/cluster-issuer") (hasKey $ann "cert-manager.io/issuer"))) }} + {{- fail "client.ingress.tls.enabled requires a cert-manager annotation: cert-manager.io/cluster-issuer or cert-manager.io/issuer" }} + {{- end }} {{- end }} -{{- if and .Values.server.ingress.tls.enabled (ne $serverProtocol "https") }} - {{- fail "server.ingress.tls.enabled is true but server.protocol is not 'https'. Set server.protocol: https to ensure correct API base URL." }} +{{- /* ── Client HTTPRoute validations ────────────────────────────────────────── */}} +{{- if .Values.client.httproute.enabled }} + {{- if not .Values.client.httproute.gatewayName }} + {{- fail "client.httproute.gatewayName is required when client.httproute.enabled is true" }} + {{- end }} {{- end }} -{{/* If client runs on https, API must also be https to avoid mixed content */}} -{{- if and (eq $clientProtocol "https") (ne $serverProtocol "https") }} - {{- fail "client.protocol is 'https' but server.protocol is not. Set server.protocol: https to prevent browser mixed-content issues." }} +{{- /* ── Server Ingress validations ─────────────────────────────────────────── */}} +{{- if .Values.server.ingress.enabled }} + {{- if eq .Values.server.ingress.host "change_me" }} + {{- fail "server.ingress.host must be set to a real hostname before deploying" }} + {{- end }} + {{- $proto := .Values.server.protocol }} + {{- if not (or (eq $proto "http") (eq $proto "https")) }} + {{- fail "server.protocol must be either 'http' or 'https'" }} + {{- end }} + {{- if and .Values.server.ingress.tls.enabled (ne $proto "https") }} + {{- fail "server.ingress.tls.enabled requires server.protocol: https" }} + {{- end }} + {{- $ann := .Values.server.ingress.annotations | default dict }} + {{- if and .Values.server.ingress.tls.enabled (not (or (hasKey $ann "cert-manager.io/cluster-issuer") (hasKey $ann "cert-manager.io/issuer"))) }} + {{- fail "server.ingress.tls.enabled requires a cert-manager annotation: cert-manager.io/cluster-issuer or cert-manager.io/issuer" }} + {{- end }} {{- end }} -{{/* Fail early if TLS enabled without cert-manager annotations (cluster-issuer or issuer) */}} -{{- $cAnn := .Values.client.ingress.annotations | default dict }} -{{- $sAnn := .Values.server.ingress.annotations | default dict }} - -{{- $clientHasIssuer := or (hasKey $cAnn "cert-manager.io/cluster-issuer") (hasKey $cAnn "cert-manager.io/issuer") }} -{{- $serverHasIssuer := or (hasKey $sAnn "cert-manager.io/cluster-issuer") (hasKey $sAnn "cert-manager.io/issuer") }} - -{{- if and .Values.client.ingress.tls.enabled (not $clientHasIssuer) }} - {{- fail "client.ingress.tls.enabled is true but no cert-manager issuer annotation found. Add 'cert-manager.io/cluster-issuer' or 'cert-manager.io/issuer'." }} +{{- /* ── Server HTTPRoute validations ────────────────────────────────────────── */}} +{{- if .Values.server.httproute.enabled }} + {{- if not .Values.server.httproute.gatewayName }} + {{- fail "server.httproute.gatewayName is required when server.httproute.enabled is true" }} + {{- end }} {{- end }} -{{- if and .Values.server.ingress.tls.enabled (not $serverHasIssuer) }} - {{- fail "server.ingress.tls.enabled is true but no cert-manager issuer annotation found. Add 'cert-manager.io/cluster-issuer' or 'cert-manager.io/issuer'." }} +{{- /* ── Cross-component protocol consistency ────────────────────────────────── */}} +{{- if and (eq .Values.client.protocol "https") (eq .Values.server.protocol "http") }} + {{- fail "client.protocol is 'https' but server.protocol is 'http' — browsers will block mixed-content requests. Set server.protocol: https." }} {{- end }} -{{/* Secret name can be omitted; we default to -client|server-tls in templates */}} +{{- /* ── External MongoDB requires an explicit connection string ───────────────── */}} +{{- if and (not .Values.mongodb.enabled) (not .Values.secrets.existingSecret) (empty .Values.secrets.DB_CONNECTION_STRING) }} + {{- fail "mongodb.enabled is false — set secrets.DB_CONNECTION_STRING or use secrets.existingSecret" }} +{{- end }} diff --git a/charts/helm/checkmate/templates/redis-service.yaml b/charts/helm/checkmate/templates/redis-service.yaml index 2720350da5..089523bebd 100644 --- a/charts/helm/checkmate/templates/redis-service.yaml +++ b/charts/helm/checkmate/templates/redis-service.yaml @@ -2,10 +2,21 @@ apiVersion: v1 kind: Service metadata: - name: checkmate-redis + name: {{ include "checkmate.fullname" . }}-redis + namespace: {{ include "checkmate.namespace" . }} + labels: + {{- include "checkmate.componentLabels" (dict "root" . "component" "redis") | nindent 4 }} + {{- with .Values.global.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: + type: ClusterIP selector: - app: checkmate-redis + {{- include "checkmate.componentSelectorLabels" (dict "root" . "component" "redis") | nindent 4 }} ports: - - port: {{ .Values.redis.port }} + - name: redis + port: {{ .Values.redis.port }} + targetPort: redis + protocol: TCP {{- end }} diff --git a/charts/helm/checkmate/templates/redis-statefulsets.yaml b/charts/helm/checkmate/templates/redis-statefulsets.yaml index acf7c85a82..4e745a20b6 100644 --- a/charts/helm/checkmate/templates/redis-statefulsets.yaml +++ b/charts/helm/checkmate/templates/redis-statefulsets.yaml @@ -2,32 +2,66 @@ apiVersion: apps/v1 kind: StatefulSet metadata: - name: checkmate-redis + name: {{ include "checkmate.fullname" . }}-redis + namespace: {{ include "checkmate.namespace" . }} + labels: + {{- include "checkmate.componentLabels" (dict "root" . "component" "redis") | nindent 4 }} + {{- with .Values.global.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: replicas: 1 + serviceName: {{ include "checkmate.fullname" . }}-redis selector: matchLabels: - app: checkmate-redis + {{- include "checkmate.componentSelectorLabels" (dict "root" . "component" "redis") | nindent 6 }} template: metadata: labels: - app: checkmate-redis + {{- include "checkmate.componentSelectorLabels" (dict "root" . "component" "redis") | nindent 8 }} spec: + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.redis.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.redis.affinity }} + affinity: + {{- include "checkmate.tplrender" (dict "value" . "context" $) | nindent 8 }} + {{- end }} + {{- with .Values.redis.tolerations }} + tolerations: + {{- include "checkmate.tplrender" (dict "value" . "context" $) | nindent 8 }} + {{- end }} containers: - name: redis image: {{ .Values.redis.image }} + imagePullPolicy: {{ .Values.redis.imagePullPolicy }} ports: - - containerPort: {{ .Values.redis.port }} + - name: redis + containerPort: {{ .Values.redis.port }} + protocol: TCP volumeMounts: - - name: redis-data + - name: data mountPath: /data + {{- with .Values.redis.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} volumeClaimTemplates: - metadata: - name: checkmate-redis-persistent-storage + name: data spec: - storageClassName: {{ .Values.persistence.redis.storageClass | quote }} + {{- $sc := include "checkmate.redisStorageClass" . }} + {{- if $sc }} + storageClassName: {{ $sc | quote }} + {{- end }} accessModes: ["ReadWriteOnce"] resources: requests: - storage: {{ .Values.persistence.redis.size | default "1Gi" | quote }} + storage: {{ include "checkmate.redisPVCSize" . | quote }} {{- end }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/secrets.yaml b/charts/helm/checkmate/templates/secrets.yaml index af104e7d11..c21d707b9f 100644 --- a/charts/helm/checkmate/templates/secrets.yaml +++ b/charts/helm/checkmate/templates/secrets.yaml @@ -1,15 +1,41 @@ -{{- $secrets := .Values.secrets }} - -{{- if or (not $secrets.JWT_SECRET) (eq $secrets.JWT_SECRET "change_me") }} - {{- fail "secrets.JWT_SECRET must be overridden and cannot be 'change_me'" }} -{{- end }} - +{{- /* + secrets.yaml — creates the Kubernetes Secret consumed by the server via envFrom. + Skipped entirely when secrets.existingSecret is set (Mode A). + In Mode B (inline), smart defaults are applied: + • DB_CONNECTION_STRING — auto-derived when mongodb.enabled is true. + • CLIENT_HOST — auto-derived from the active Ingress/HTTPRoute host. + • REDIS_HOST/PORT — auto-injected when redis.enabled is true. + Any extra keys added under secrets.* are passed through verbatim. +*/ -}} +{{- if not .Values.secrets.existingSecret }} apiVersion: v1 kind: Secret metadata: - name: checkmate-secrets + name: {{ include "checkmate.fullname" . }}-secrets + namespace: {{ include "checkmate.namespace" . }} + labels: + {{- include "checkmate.labels" . | nindent 4 }} + {{- with .Values.global.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} type: Opaque stringData: -{{- range $key, $value := $secrets }} - {{ $key }}: {{ $value | quote }} + {{- /* ── Required keys (with auto-derivation) ──────────────────────────── */}} + JWT_SECRET: {{ .Values.secrets.JWT_SECRET | quote }} + DB_CONNECTION_STRING: {{ include "checkmate.mongodbConnectionString" . | quote }} + CLIENT_HOST: {{ include "checkmate.clientHost" . | quote }} + {{- /* ── Auto-inject Redis connection when deployed in-cluster ─────────── */}} + {{- if .Values.redis.enabled }} + REDIS_HOST: {{ printf "%s-redis" (include "checkmate.fullname" .) | quote }} + REDIS_PORT: {{ .Values.redis.port | quote }} + {{- end }} + {{- /* ── Pass through any remaining optional secrets verbatim ──────────── */}} + {{- /* Keys handled above (plus the meta key) are excluded from iteration. */}} + {{- $managed := list "existingSecret" "JWT_SECRET" "DB_CONNECTION_STRING" "CLIENT_HOST" "REDIS_HOST" "REDIS_PORT" }} + {{- range $key, $val := .Values.secrets }} + {{- if and (not (has $key $managed)) (kindIs "string" $val) (ne $val "") }} + {{ $key }}: {{ $val | quote }} + {{- end }} + {{- end }} {{- end }} \ No newline at end of file diff --git a/charts/helm/checkmate/templates/server-deployment.yaml b/charts/helm/checkmate/templates/server-deployment.yaml index 393964ccdc..2eae4d36ef 100644 --- a/charts/helm/checkmate/templates/server-deployment.yaml +++ b/charts/helm/checkmate/templates/server-deployment.yaml @@ -1,37 +1,73 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: checkmate-server + name: {{ include "checkmate.fullname" . }}-server + namespace: {{ include "checkmate.namespace" . }} + labels: + {{- include "checkmate.componentLabels" (dict "root" . "component" "server") | nindent 4 }} + {{- with .Values.global.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: - replicas: 1 + replicas: {{ .Values.server.replicas }} selector: matchLabels: - app: checkmate-server + {{- include "checkmate.componentSelectorLabels" (dict "root" . "component" "server") | nindent 6 }} template: metadata: labels: - app: checkmate-server + {{- include "checkmate.componentSelectorLabels" (dict "root" . "component" "server") | nindent 8 }} + {{- with .Values.server.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.server.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} spec: - {{- with .Values.server.affinity }} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.server.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.server.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.server.affinity }} affinity: - {{- tpl ( . | toYaml) $ | nindent 8 }} - {{- end }} - {{- with .Values.server.tolerations }} + {{- include "checkmate.tplrender" (dict "value" . "context" $) | nindent 8 }} + {{- end }} + {{- with .Values.server.tolerations }} tolerations: - {{- tpl ( . | toYaml) $ | nindent 8 }} - {{- end }} + {{- include "checkmate.tplrender" (dict "value" . "context" $) | nindent 8 }} + {{- end }} containers: - name: server image: {{ .Values.server.image }} + imagePullPolicy: {{ .Values.server.imagePullPolicy }} ports: - - containerPort: {{ .Values.server.port }} + - name: http + containerPort: {{ .Values.server.port }} + protocol: TCP + # All environment variables are loaded from the Secret (envFrom). + # Use server.extraEnv for additional variables or per-key secretKeyRef overrides. envFrom: - secretRef: - name: checkmate-secrets + name: {{ include "checkmate.secretName" . }} + {{- with .Values.server.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.server.containerSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} {{- with .Values.server.resources }} resources: - {{- tpl ( . | toYaml) $ | nindent 12 }} + {{- toYaml . | nindent 12 }} {{- end }} - env: - - name: CLIENT_HOST - value: "{{ .Values.client.protocol }}://{{ .Values.client.ingress.host }}" diff --git a/charts/helm/checkmate/templates/server-httproute.yaml b/charts/helm/checkmate/templates/server-httproute.yaml new file mode 100644 index 0000000000..e7afa99a95 --- /dev/null +++ b/charts/helm/checkmate/templates/server-httproute.yaml @@ -0,0 +1,40 @@ +{{- if .Values.server.httproute.enabled }} +{{- if not (.Capabilities.APIVersions.Has "gateway.networking.k8s.io/v1") }} + {{- fail "Gateway API CRDs (gateway.networking.k8s.io/v1) not found in the cluster. Install them before enabling server.httproute." }} +{{- end }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "checkmate.fullname" . }}-server + namespace: {{ include "checkmate.namespace" . }} + labels: + {{- include "checkmate.componentLabels" (dict "root" . "component" "server") | nindent 4 }} + {{- $ann := merge (default dict .Values.server.httproute.annotations) (default dict .Values.global.annotations) }} + {{- if $ann }} + annotations: + {{- toYaml $ann | nindent 4 }} + {{- end }} +spec: + parentRefs: + - name: {{ .Values.server.httproute.gatewayName }} + namespace: {{ default (include "checkmate.namespace" .) .Values.server.httproute.gatewayNamespace }} + {{- if .Values.server.httproute.hostnames }} + hostnames: + {{- toYaml .Values.server.httproute.hostnames | nindent 4 }} + {{- end }} + rules: + - matches: + - path: + type: PathPrefix + value: /api/v1 + backendRefs: + - name: {{ include "checkmate.fullname" . }}-server + port: {{ .Values.server.port }} + - matches: + - path: + type: PathPrefix + value: /api-docs + backendRefs: + - name: {{ include "checkmate.fullname" . }}-server + port: {{ .Values.server.port }} +{{- end }} diff --git a/charts/helm/checkmate/templates/server-ingress.yaml b/charts/helm/checkmate/templates/server-ingress.yaml index 34c9ac0dc8..1e779c39d3 100644 --- a/charts/helm/checkmate/templates/server-ingress.yaml +++ b/charts/helm/checkmate/templates/server-ingress.yaml @@ -2,27 +2,22 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: checkmate-server-ingress - {{- if .Values.server.ingress.annotations }} + name: {{ include "checkmate.fullname" . }}-server + namespace: {{ include "checkmate.namespace" . }} + labels: + {{- include "checkmate.componentLabels" (dict "root" . "component" "server") | nindent 4 }} + {{- $ann := merge (default dict .Values.server.ingress.annotations) (default dict .Values.global.annotations) }} + {{- if $ann }} annotations: - {{- range $key, $value := .Values.server.ingress.annotations }} - {{ $key }}: {{ $value | quote }} - {{- end }} + {{- toYaml $ann | nindent 4 }} {{- end }} - {{/*#annotations: - #nginx.ingress.kubernetes.io/rewrite-target: / - #nginx.ingress.kubernetes.io/enable-cors: "true" - #nginx.ingress.kubernetes.io/cors-allow-origin: "http://{{ .Values.client.ingress.host }},https://{{ .Values.client.ingress.host }}" - #nginx.ingress.kubernetes.io/cors-allow-methods: "GET, PUT, POST, DELETE, PATCH, OPTIONS" - #nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" - #nginx.ingress.kubernetes.io/cors-allow-credentials: "true"*/}} spec: ingressClassName: {{ .Values.server.ingress.className }} {{- if .Values.server.ingress.tls.enabled }} tls: - hosts: - {{ .Values.server.ingress.host }} - secretName: {{ default (printf "%s-server-tls" .Release.Name) .Values.server.ingress.tls.secretName }} + secretName: {{ include "checkmate.serverIngressTLSSecretName" . }} {{- end }} rules: - host: {{ .Values.server.ingress.host }} @@ -32,7 +27,7 @@ spec: pathType: Prefix backend: service: - name: checkmate-server + name: {{ include "checkmate.fullname" . }}-server port: number: {{ .Values.server.port }} {{- end }} diff --git a/charts/helm/checkmate/templates/server-nginx-cm.yaml b/charts/helm/checkmate/templates/server-nginx-cm.yaml index 84da552991..39f20e8c82 100644 --- a/charts/helm/checkmate/templates/server-nginx-cm.yaml +++ b/charts/helm/checkmate/templates/server-nginx-cm.yaml @@ -1,16 +1,25 @@ apiVersion: v1 -kind: ConfigMap +kind: ConfigMap metadata: - name: checkmate-server-nginx-cm + name: {{ include "checkmate.fullname" . }}-nginx-cm + namespace: {{ include "checkmate.namespace" . }} + labels: + {{- include "checkmate.componentLabels" (dict "root" . "component" "client") | nindent 4 }} + {{- with .Values.global.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} data: default.conf: | server { listen 80; listen [::]:80; - server_name checkmate-demo.bluewavelabs.ca; + # Catch-all: this container hosts a single site + server_name _; server_tokens off; + # Required for cert-manager HTTP-01 ACME challenge location /.well-known/acme-challenge/ { root /var/www/certbot; } @@ -18,24 +27,17 @@ data: location / { root /usr/share/nginx/html; index index.html index.htm; + # SPA fallback: unknown paths serve index.html so React Router can handle them try_files $uri $uri/ /index.html; } - # location /api/ { - # proxy_pass http://{{ .Values.server.ingress.host }}:5000/api/; - # proxy_http_version 1.1; - # proxy_set_header Host $host; - # proxy_set_header X-Real-IP $remote_addr; - # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - # proxy_set_header X-Forwarded-Proto $scheme; - # } - + # Proxy Swagger UI to the backend ClusterIP Service location /api-docs/ { - proxy_pass http://{{ .Values.server.ingress.host }}:5000/api-docs/; + proxy_pass http://{{ include "checkmate.fullname" . }}-server:{{ .Values.server.port }}/api-docs/; proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; } } \ No newline at end of file diff --git a/charts/helm/checkmate/templates/server-service.yaml b/charts/helm/checkmate/templates/server-service.yaml index d8a7fbe864..f84ddd6545 100644 --- a/charts/helm/checkmate/templates/server-service.yaml +++ b/charts/helm/checkmate/templates/server-service.yaml @@ -1,9 +1,20 @@ apiVersion: v1 kind: Service metadata: - name: checkmate-server + name: {{ include "checkmate.fullname" . }}-server + namespace: {{ include "checkmate.namespace" . }} + labels: + {{- include "checkmate.componentLabels" (dict "root" . "component" "server") | nindent 4 }} + {{- with .Values.global.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: + type: ClusterIP selector: - app: checkmate-server + {{- include "checkmate.componentSelectorLabels" (dict "root" . "component" "server") | nindent 4 }} ports: - - port: {{ .Values.server.port }} + - name: http + port: {{ .Values.server.port }} + targetPort: http + protocol: TCP diff --git a/charts/helm/checkmate/values.yaml b/charts/helm/checkmate/values.yaml index 9a586dd542..3292358ba6 100644 --- a/charts/helm/checkmate/values.yaml +++ b/charts/helm/checkmate/values.yaml @@ -1,70 +1,367 @@ +## ───────────────────────────────────────────────────────────────────────────── +## Checkmate Helm Chart — values.yaml +## +## Defaults reproduce the previous chart's behavior out of the box. +## Change only what you need; everything else falls back to a sane value. +## +## Smart defaults that differ from the old chart: +## • secrets.DB_CONNECTION_STRING — auto-derived from release+namespace when +## mongodb.enabled is true and this key is left empty. +## • secrets.CLIENT_HOST — auto-derived from client.ingress.host (or the first +## httproute hostname) when left empty. +## ───────────────────────────────────────────────────────────────────────────── + +# -- Override the chart name portion of every resource name. +nameOverride: "" + +# -- Override the full release-name prefix for every resource. +fullnameOverride: "" + +# -- Override the target namespace (defaults to .Release.Namespace). +namespaceOverride: "" + +## ── Global settings ─────────────────────────────────────────────────────────── +global: + # -- Labels merged onto every resource created by this chart. + labels: {} + + # -- Annotations merged onto every resource created by this chart. + annotations: {} + + # -- Image pull secrets applied to every Pod spec. + imagePullSecrets: [] + # - name: my-registry-secret + +## ── Client (React frontend served by nginx) ─────────────────────────────────── client: + # -- OCI image for the frontend container. image: ghcr.io/bluewave-labs/checkmate-client:v3.2.0 + + # -- Image pull policy. + imagePullPolicy: IfNotPresent + + # -- Number of Pod replicas. + replicas: 1 + + # -- TCP port nginx listens on inside the Pod. port: 80 + + # -- Protocol the browser uses to reach the frontend: "http" or "https". + # Affects the UPTIME_APP_CLIENT_HOST env var and TLS pre-flight validation. protocol: http + + # -- CPU/memory requests and limits for the client container. + resources: {} + # requests: + # cpu: 100m + # memory: 128Mi + # limits: + # cpu: 500m + # memory: 512Mi + + # -- Additional environment variables injected into the client container. + extraEnv: [] + # - name: MY_VAR + # value: "my-value" + + # ── Ingress (Kubernetes Ingress API) ───────────────────────────────────────── ingress: + # -- Create a Kubernetes Ingress for the client. + # Set to false and enable client.httproute to use the Gateway API instead. + # At most one of ingress.enabled / httproute.enabled may be true; both may be + # false when accessing via kubectl port-forward (no public routing needed). enabled: true + + # -- Public hostname that resolves to the cluster Ingress controller. + # REQUIRED — must be changed from the placeholder before deploying. host: change_me + + # -- IngressClass name (e.g. nginx, traefik, haproxy). className: nginx + + # -- Extra annotations merged onto the Ingress resource. annotations: {} - # Example annotations for cert-manager: - # annotations: - # cert-manager.io/cluster-issuer: "letsencrypt-prod" + # cert-manager.io/cluster-issuer: "letsencrypt-prod" + tls: + # -- Terminate TLS at the Ingress. + # Also requires: client.protocol: https and either a cert-manager issuer + # annotation or a pre-existing TLS Secret referenced by secretName. enabled: false - # secretName: {{ .Release.Name }}-client-tls # Optional; defaults to -client-tls if omitted - # Note: when enabling TLS, also set client.protocol: https and add - # a cert-manager issuer annotation (e.g. cert-manager.io/cluster-issuer: "letsencrypt-prod"). - # The secret will be automatically created by cert-manager when using the cert-manager.io/cluster-issuer annotation + # -- Name of the TLS Secret. Defaults to "-client-tls" when empty. + secretName: "" + + # ── HTTPRoute (Gateway API — gateway.networking.k8s.io/v1) ─────────────────── + httproute: + # -- Create a Gateway API HTTPRoute for the client. + # Mutually exclusive with client.ingress.enabled — enable at most one. + enabled: false + + # -- Name of the Gateway this route attaches to. Required when enabled. + gatewayName: "" + + # -- Namespace of the Gateway. Defaults to the same namespace as the chart. + gatewayNamespace: "" + + # -- Hostnames the route matches. Leave empty to match all hostnames on the Gateway. + hostnames: [] + # - checkmate.example.com + + # -- Extra annotations on the HTTPRoute resource. + annotations: {} + + # ── Pod scheduling ──────────────────────────────────────────────────────────── + nodeSelector: {} + tolerations: [] + affinity: {} + + # -- Annotations added to client Pods (merged with global.annotations). + podAnnotations: {} + + # -- Extra labels added to client Pods. + podLabels: {} + + # -- Pod-level security context (fsGroup, runAsUser, etc.). + podSecurityContext: {} + + # -- Container-level security context (readOnlyRootFilesystem, capabilities, etc.). + containerSecurityContext: {} + +## ── Server (Node.js API backend) ────────────────────────────────────────────── server: + # -- OCI image for the backend container. image: ghcr.io/bluewave-labs/checkmate-backend:v3.2.0 + + # -- Image pull policy. + imagePullPolicy: IfNotPresent + + # -- Number of Pod replicas. + replicas: 1 + + # -- TCP port the server process listens on inside the Pod. port: 52345 + + # -- Protocol the browser uses to reach the API: "http" or "https". + # Must match client.protocol when the client uses HTTPS (browser mixed-content rule). protocol: http + + # -- CPU/memory requests and limits for the server container. + resources: {} + + # -- Explicit server API base URL injected into the client container (UPTIME_APP_API_BASE_URL). + # Auto-derived from server.ingress.host or server.httproute.hostnames when left empty. + # Set this explicitly when both ingress and httproute are disabled (e.g., local/port-forward access). + # Example: "http://localhost:52345/api/v1" + apiBaseURL: "" + + # -- Additional environment variables injected into the server container. + # Variables already loaded via envFrom (from the Secret) take lower precedence. + extraEnv: [] + + # ── Ingress ─────────────────────────────────────────────────────────────────── ingress: + # -- Create a Kubernetes Ingress for the server API. + # Set to false and enable server.httproute for Gateway API. + # At most one of ingress.enabled / httproute.enabled may be true; both may be + # false when accessing via kubectl port-forward (no public routing needed). enabled: true + + # -- Public hostname for the API endpoint. + # REQUIRED — must be changed from the placeholder before deploying. host: change_me + + # -- IngressClass name. className: nginx + + # -- Extra annotations merged onto the Ingress resource. annotations: {} - # Example annotations for cert-manager: - # annotations: - # cert-manager.io/cluster-issuer: "letsencrypt-prod" + # cert-manager.io/cluster-issuer: "letsencrypt-prod" + tls: + # -- Terminate TLS at the Ingress for the API. enabled: false - # secretName: {{ .Release.Name }}-server-tls # Optional; defaults to -server-tls if omitted - # Note: when enabling TLS, also set server.protocol: https and add - # a cert-manager issuer annotation (e.g. cert-manager.io/cluster-issuer: "letsencrypt-prod"). - # The secret will be automatically created by cert-manager when using the cert-manager.io/cluster-issuer annotation + # -- Name of the TLS Secret. Defaults to "-server-tls" when empty. + secretName: "" + + # ── HTTPRoute (Gateway API) ─────────────────────────────────────────────────── + httproute: + # -- Create a Gateway API HTTPRoute for the server API. + # Mutually exclusive with server.ingress.enabled. + enabled: false + + # -- Name of the Gateway this route attaches to. Required when enabled. + gatewayName: "" + + # -- Namespace of the Gateway. Defaults to the chart namespace. + gatewayNamespace: "" + + # -- Hostnames the route matches. + hostnames: [] + # - api.checkmate.example.com + + # -- Extra annotations on the HTTPRoute resource. + annotations: {} + + # ── Pod scheduling ──────────────────────────────────────────────────────────── + nodeSelector: {} + tolerations: [] + affinity: {} + + # -- Annotations added to server Pods (merged with global.annotations). + podAnnotations: {} + + # -- Extra labels added to server Pods. + podLabels: {} + + # -- Pod-level security context. + podSecurityContext: {} + + # -- Container-level security context. + containerSecurityContext: {} + +## ── Secrets ─────────────────────────────────────────────────────────────────── +## +## Two mutually exclusive modes: +## +## Mode A — existingSecret +## Point to a pre-existing Kubernetes Secret. No new Secret resource is +## created by the chart. The Secret must contain every key the server needs +## (JWT_SECRET, DB_CONNECTION_STRING, CLIENT_HOST, plus any optional keys). +## +## Mode B — inline values (default) +## The chart creates a Secret resource from the values below. +## Extra keys beyond the documented ones are passed through verbatim. +## DB_CONNECTION_STRING and CLIENT_HOST are auto-derived when left empty. +## +secrets: + # -- [Mode A] Name of an existing Kubernetes Secret loaded via envFrom. + # When set, every inline value below is ignored and no Secret is created. + existingSecret: "" + + # ── Required keys (Mode B only) ─────────────────────────────────────────────── + + # -- JWT signing secret for authentication tokens. + # Must be changed — the chart aborts if this is still "change_me". + JWT_SECRET: change_me + + # -- MongoDB connection URI. + # Auto-derived when mongodb.enabled is true and this is left empty: + # mongodb://-mongodb..svc:/uptime_db + # Must be set when mongodb.enabled is false (external cluster). + DB_CONNECTION_STRING: "" + + # -- Base URL of the frontend, used by the server for CORS validation. + # Auto-derived from client.protocol + client.ingress.host (or the first + # httproute hostname) when left empty. + # Must be set explicitly when both ingress and httproute are disabled + # (e.g., local access via port-forward: "http://127.0.0.1:8080"). + CLIENT_HOST: "" + + # ── Optional keys ───────────────────────────────────────────────────────────── + # Uncomment and fill in the keys you need; they are included in the Secret as-is. + + # -- Outbound email for alert notifications. + # SYSTEM_EMAIL_ADDRESS: "" + # SYSTEM_EMAIL_PASSWORD: "" + # SYSTEM_EMAIL_HOST: "smtp.example.com" + # SYSTEM_EMAIL_PORT: "587" + + # -- Google PageSpeed Insights API key. + # PAGESPEED_API_KEY: "" + + # -- External Redis (only needed when redis.enabled is false and you still + # want the server to use Redis for caching). + # When redis.enabled is true, these are auto-injected — do not set them here. + # REDIS_HOST: "" + # REDIS_PORT: "6379" + + # -- Miscellaneous server settings. + # ORIGIN: "example.com" # Base domain for CORS (no protocol or port). Required when + # # CLIENT_HOST is set manually (e.g., local port-forward mode). + # DB_TYPE: "MongoDB" + # TOKEN_TTL: "99d" + # REFRESH_TOKEN_TTL: "99d" + +## ── MongoDB ─────────────────────────────────────────────────────────────────── +mongodb: + # -- Deploy a MongoDB StatefulSet inside the cluster. + # Set to false to use an external MongoDB; in that case secrets.DB_CONNECTION_STRING + # must be provided explicitly. + enabled: true + + # -- OCI image for MongoDB. + image: ghcr.io/bluewave-labs/checkmate-mongo:v3.2.0 + + # -- Image pull policy. + imagePullPolicy: IfNotPresent + + # -- MongoDB port. + port: 27017 + + # -- CPU/memory requests and limits. + resources: {} + + persistence: + # -- Size of the MongoDB PersistentVolumeClaim. + size: 5Gi + + # -- StorageClass for the MongoDB PVC. Empty string uses the cluster default. + storageClass: "" + + # ── Pod scheduling ──────────────────────────────────────────────────────────── + nodeSelector: {} + tolerations: [] + affinity: {} + +## ── Redis ───────────────────────────────────────────────────────────────────── redis: + # -- Deploy a Redis StatefulSet inside the cluster. + # When true, REDIS_HOST and REDIS_PORT are auto-injected into the server Secret. + # When false (default), Redis is not deployed; configure an external Redis via + # secrets.REDIS_HOST / secrets.REDIS_PORT if the application needs it. enabled: false + + # -- OCI image for Redis. image: redis:7.2 + + # -- Image pull policy. + imagePullPolicy: IfNotPresent + + # -- Redis port. port: 6379 -mongodb: - image: ghcr.io/bluewave-labs/checkmate-mongo:v3.2.0 - port: 27017 + # -- CPU/memory requests and limits. + resources: {} -secrets: - JWT_SECRET: change_me -# REFRESH_TOKEN_SECRET: change_me -# SYSTEM_EMAIL_ADDRESS: test@example.com -# SYSTEM_EMAIL_PASSWORD: change_me -# SYSTEM_EMAIL_HOST: smtp.example.com -# SYSTEM_EMAIL_PORT: "587" -# PAGESPEED_API_KEY: change_me - DB_CONNECTION_STRING: mongodb://checkmate-mongodb.namespace.svc:27017/uptime_db - CLIENT_HOST: change_me -# REDIS_HOST: redis -# REDIS_PORT: "6379" -# DB_TYPE: MongoDB -# TOKEN_TTL: 99d -# REFRESH_TOKEN_TTL: 99d + persistence: + # -- Size of the Redis PersistentVolumeClaim. + size: 1Gi + # -- StorageClass for the Redis PVC. Empty string uses the cluster default. + storageClass: "" + + # ── Pod scheduling ──────────────────────────────────────────────────────────── + nodeSelector: {} + tolerations: [] + affinity: {} + +## ── Persistence (legacy — retained for backward compatibility) ──────────────── +## +## These keys were the only persistence configuration in chart v0.1.x. +## They are superseded by mongodb.persistence.* and redis.persistence.*, +## but non-empty values here still take precedence (safe upgrade path). +## New installations should use the component-scoped persistence keys above. +## persistence: mongo: - size: 5Gi + # -- Overrides mongodb.persistence.size when non-empty. + size: "" + # -- Overrides mongodb.persistence.storageClass when non-empty. storageClass: "" redis: - size: 1Gi - storageClass: "" \ No newline at end of file + # -- Overrides redis.persistence.size when non-empty. + size: "" + # -- Overrides redis.persistence.storageClass when non-empty. + storageClass: ""