From 34bfd032c47d86f6bea63671623e74c205ca7ffd Mon Sep 17 00:00:00 2001 From: Lakshay Choudhary Date: Tue, 24 Mar 2026 13:51:08 +0530 Subject: [PATCH] feat: add self-hosting bringup scaffolding --- .gitignore | 4 +- Makefile | 121 +++++++ docker-compose.prod.yaml | 9 +- docs/self-hosting-bringup-report.md | 189 +++++++++++ helm/envsync/.helmignore | 8 + helm/envsync/Chart.lock | 9 + helm/envsync/Chart.yaml | 16 + helm/envsync/README.md | 34 ++ helm/envsync/templates/NOTES.txt | 15 + helm/envsync/templates/_helpers.tpl | 308 ++++++++++++++++++ helm/envsync/templates/configmap-api.yaml | 37 +++ .../templates/configmap-bootstrap-lock.yaml | 11 + helm/envsync/templates/deployment-api.yaml | 87 +++++ .../envsync/templates/deployment-minikms.yaml | 79 +++++ .../envsync/templates/deployment-openfga.yaml | 93 ++++++ helm/envsync/templates/deployment-rustfs.yaml | 63 ++++ .../envsync/templates/deployment-zitadel.yaml | 134 ++++++++ helm/envsync/templates/hpa-api.yaml | 22 ++ helm/envsync/templates/ingress.yaml | 44 +++ helm/envsync/templates/job-db-setup.yaml | 125 +++++++ helm/envsync/templates/job-init.yaml | 112 +++++++ helm/envsync/templates/job-migrate.yaml | 40 +++ helm/envsync/templates/pvc-rustfs.yaml | 17 + helm/envsync/templates/pvc-zitadel.yaml | 15 + helm/envsync/templates/role-bootstrap.yaml | 15 + helm/envsync/templates/role-runtime.yaml | 11 + .../templates/rolebinding-bootstrap.yaml | 13 + .../templates/rolebinding-runtime.yaml | 13 + helm/envsync/templates/secret-api.yaml | 17 + helm/envsync/templates/secret-bootstrap.yaml | 18 + helm/envsync/templates/service-api.yaml | 15 + helm/envsync/templates/service-minikms.yaml | 15 + helm/envsync/templates/service-openfga.yaml | 18 + helm/envsync/templates/service-rustfs.yaml | 17 + helm/envsync/templates/service-zitadel.yaml | 15 + .../templates/serviceaccount-bootstrap.yaml | 6 + .../templates/serviceaccount-runtime.yaml | 6 + helm/envsync/values-kind.yaml | 50 +++ helm/envsync/values.yaml | 247 ++++++++++++++ k8s/Makefile | 11 + k8s/README.md | 15 + kind-config.yaml | 6 + 42 files changed, 2096 insertions(+), 4 deletions(-) create mode 100644 Makefile create mode 100644 docs/self-hosting-bringup-report.md create mode 100644 helm/envsync/.helmignore create mode 100644 helm/envsync/Chart.lock create mode 100644 helm/envsync/Chart.yaml create mode 100644 helm/envsync/README.md create mode 100644 helm/envsync/templates/NOTES.txt create mode 100644 helm/envsync/templates/_helpers.tpl create mode 100644 helm/envsync/templates/configmap-api.yaml create mode 100644 helm/envsync/templates/configmap-bootstrap-lock.yaml create mode 100644 helm/envsync/templates/deployment-api.yaml create mode 100644 helm/envsync/templates/deployment-minikms.yaml create mode 100644 helm/envsync/templates/deployment-openfga.yaml create mode 100644 helm/envsync/templates/deployment-rustfs.yaml create mode 100644 helm/envsync/templates/deployment-zitadel.yaml create mode 100644 helm/envsync/templates/hpa-api.yaml create mode 100644 helm/envsync/templates/ingress.yaml create mode 100644 helm/envsync/templates/job-db-setup.yaml create mode 100644 helm/envsync/templates/job-init.yaml create mode 100644 helm/envsync/templates/job-migrate.yaml create mode 100644 helm/envsync/templates/pvc-rustfs.yaml create mode 100644 helm/envsync/templates/pvc-zitadel.yaml create mode 100644 helm/envsync/templates/role-bootstrap.yaml create mode 100644 helm/envsync/templates/role-runtime.yaml create mode 100644 helm/envsync/templates/rolebinding-bootstrap.yaml create mode 100644 helm/envsync/templates/rolebinding-runtime.yaml create mode 100644 helm/envsync/templates/secret-api.yaml create mode 100644 helm/envsync/templates/secret-bootstrap.yaml create mode 100644 helm/envsync/templates/service-api.yaml create mode 100644 helm/envsync/templates/service-minikms.yaml create mode 100644 helm/envsync/templates/service-openfga.yaml create mode 100644 helm/envsync/templates/service-rustfs.yaml create mode 100644 helm/envsync/templates/service-zitadel.yaml create mode 100644 helm/envsync/templates/serviceaccount-bootstrap.yaml create mode 100644 helm/envsync/templates/serviceaccount-runtime.yaml create mode 100644 helm/envsync/values-kind.yaml create mode 100644 helm/envsync/values.yaml create mode 100644 k8s/Makefile create mode 100644 k8s/README.md create mode 100644 kind-config.yaml diff --git a/.gitignore b/.gitignore index 32e74d84..9a9732b6 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json # turbo .turbo -.wrangler \ No newline at end of file +.wrangler +.tmp +helm/envsync/charts diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..b799c7d0 --- /dev/null +++ b/Makefile @@ -0,0 +1,121 @@ +SHELL := /bin/bash + +RELEASE ?= envsync +NAMESPACE ?= envsync +KIND_CLUSTER_NAME ?= envsync +CHART_DIR ?= helm/envsync +VALUES_KIND ?= $(CHART_DIR)/values-kind.yaml +GENERATED_VALUES ?= .tmp/values-kind.generated.yaml +HELM ?= $(if $(wildcard $(CURDIR)/.tmp/bin/helm),$(CURDIR)/.tmp/bin/helm,$(shell command -v helm 2>/dev/null)) + +.DEFAULT_GOAL := help + +.PHONY: help +help: + @printf "EnvSync deployment helpers\n\n" + @printf "Targets:\n" + @printf " %-20s %s\n" "prereqs-check" "Verify required local tooling" + @printf " %-20s %s\n" "kind-create" "Create the local Kind cluster" + @printf " %-20s %s\n" "kind-delete" "Delete the local Kind cluster" + @printf " %-20s %s\n" "helm-deps" "Build Helm chart dependencies" + @printf " %-20s %s\n" "helm-lint" "Lint the Helm chart" + @printf " %-20s %s\n" "helm-template" "Render the Helm chart locally" + @printf " %-20s %s\n" "helm-install-kind" "Install or upgrade the chart into Kind" + @printf " %-20s %s\n" "helm-uninstall" "Uninstall the release" + @printf " %-20s %s\n" "status" "Show workload status in the namespace" + @printf " %-20s %s\n" "port-forward-api" "Port-forward the API service to localhost:4000" + +.PHONY: prereqs-check +prereqs-check: + @missing=0; \ + for tool in kind kubectl openssl bun; do \ + if ! command -v $$tool >/dev/null 2>&1; then \ + echo "Missing required tool: $$tool"; \ + missing=1; \ + fi; \ + done; \ + if [ -z "$(HELM)" ] || [ ! -x "$(HELM)" ]; then \ + echo "Missing required tool: helm"; \ + echo "Install Helm from https://helm.sh/docs/intro/install/"; \ + missing=1; \ + fi; \ + if [ $$missing -ne 0 ]; then \ + exit 1; \ + fi + +.PHONY: kind-create +kind-create: prereqs-check + @if kind get clusters | grep -qx "$(KIND_CLUSTER_NAME)"; then \ + echo "Kind cluster '$(KIND_CLUSTER_NAME)' already exists"; \ + else \ + kind create cluster --name "$(KIND_CLUSTER_NAME)" --config kind-config.yaml; \ + fi + +.PHONY: kind-delete +kind-delete: + @kind delete cluster --name "$(KIND_CLUSTER_NAME)" + +.PHONY: helm-deps +helm-deps: prereqs-check + @"$(HELM)" dependency build "$(CHART_DIR)" + +.PHONY: helm-lint +helm-lint: helm-deps + @"$(HELM)" lint "$(CHART_DIR)" + +.PHONY: generate-kind-values +generate-kind-values: + @mkdir -p .tmp + @cat > "$(GENERATED_VALUES)" <=1.28.0-0" +dependencies: + - name: postgresql + version: 18.5.6 + repository: oci://registry-1.docker.io/bitnamicharts + condition: postgresql.enabled + - name: redis + version: 24.1.2 + repository: oci://registry-1.docker.io/bitnamicharts + condition: redis.enabled diff --git a/helm/envsync/README.md b/helm/envsync/README.md new file mode 100644 index 00000000..4bce57ea --- /dev/null +++ b/helm/envsync/README.md @@ -0,0 +1,34 @@ +# EnvSync Helm Chart + +This chart deploys the core EnvSync control-plane services for self-hosted Kubernetes: + +- `envsync-api` +- `envsync-init` bootstrap job +- `envsync-migrate` upgrade hook +- PostgreSQL +- Redis +- Zitadel +- OpenFGA +- MiniKMS +- RustFS + +## Local Kind Flow + +From the repository root: + +```sh +make kind-create +make helm-deps +make helm-install-kind +kubectl port-forward -n envsync svc/envsync-api 4000:4000 +curl http://127.0.0.1:4000/health +``` + +The Kind flow writes generated bootstrap secrets into `.tmp/values-kind.generated.yaml` +so the chart can come up without editing tracked files. + +## Notes + +- The chart is the primary self-hosting path in this repo. +- The advanced operator-based `k8s/` path is intentionally deferred until the Helm path is stable. +- Ingress is disabled in `values-kind.yaml`; use `port-forward` for local validation. diff --git a/helm/envsync/templates/NOTES.txt b/helm/envsync/templates/NOTES.txt new file mode 100644 index 00000000..773ea365 --- /dev/null +++ b/helm/envsync/templates/NOTES.txt @@ -0,0 +1,15 @@ +EnvSync has been installed into namespace {{ .Release.Namespace }}. + +Core commands: + kubectl get pods -n {{ .Release.Namespace }} + kubectl logs job/{{ include "envsync.fullname" . }}-init -n {{ .Release.Namespace }} + kubectl get configmap {{ include "envsync.bootstrapLockName" . }} -n {{ .Release.Namespace }} -o yaml + +Local access: + kubectl port-forward svc/{{ include "envsync.fullname" . }}-api -n {{ .Release.Namespace }} 4000:{{ .Values.api.service.port }} + curl http://127.0.0.1:4000/health + +Public hosts: + API: {{ include "envsync.publicScheme" . }}://{{ include "envsync.apiHost" . }} + Auth: {{ include "envsync.publicScheme" . }}://{{ include "envsync.authHost" . }} + App: {{ include "envsync.dashboardUrl" . }} diff --git a/helm/envsync/templates/_helpers.tpl b/helm/envsync/templates/_helpers.tpl new file mode 100644 index 00000000..91240523 --- /dev/null +++ b/helm/envsync/templates/_helpers.tpl @@ -0,0 +1,308 @@ +{{- define "envsync.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "envsync.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := include "envsync.name" . -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" -}} +{{- end -}} + +{{- define "envsync.labels" -}} +helm.sh/chart: {{ include "envsync.chart" . }} +app.kubernetes.io/name: {{ include "envsync.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- with .Values.global.commonLabels }} +{{ toYaml . }} +{{- end }} +{{- end -}} + +{{- define "envsync.selectorLabels" -}} +app.kubernetes.io/name: {{ include "envsync.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{- define "envsync.publicScheme" -}} +{{- default "https" .Values.global.publicScheme -}} +{{- end -}} + +{{- define "envsync.domain" -}} +{{- default "envsync.local" .Values.global.domain -}} +{{- end -}} + +{{- define "envsync.apiHost" -}} +{{- default (printf "api.%s" (include "envsync.domain" .)) .Values.ingress.hosts.api -}} +{{- end -}} + +{{- define "envsync.authHost" -}} +{{- default (printf "auth.%s" (include "envsync.domain" .)) .Values.ingress.hosts.auth -}} +{{- end -}} + +{{- define "envsync.dashboardHost" -}} +{{- printf "app.%s" (include "envsync.domain" .) -}} +{{- end -}} + +{{- define "envsync.landingUrl" -}} +{{- default (printf "%s://%s" (include "envsync.publicScheme" .) (include "envsync.domain" .)) .Values.urls.landing -}} +{{- end -}} + +{{- define "envsync.dashboardUrl" -}} +{{- default (printf "%s://%s" (include "envsync.publicScheme" .) (include "envsync.dashboardHost" .)) .Values.urls.dashboard -}} +{{- end -}} + +{{- define "envsync.zitadelPublicUrl" -}} +{{- printf "%s://%s" (include "envsync.publicScheme" .) (default (include "envsync.authHost" .) .Values.zitadel.externalDomain) -}} +{{- end -}} + +{{- define "envsync.zitadelInternalUrl" -}} +{{- printf "http://%s-zitadel:%v" (include "envsync.fullname" .) .Values.zitadel.service.port -}} +{{- end -}} + +{{- define "envsync.openfgaInternalUrl" -}} +{{- printf "http://%s-openfga:%v" (include "envsync.fullname" .) .Values.openfga.service.httpPort -}} +{{- end -}} + +{{- define "envsync.minikmsAddress" -}} +{{- printf "%s-minikms:%v" (include "envsync.fullname" .) .Values.minikms.service.port -}} +{{- end -}} + +{{- define "envsync.redisUrl" -}} +{{- if .Values.external.redis.enabled -}} +{{- required "external.redis.url is required when external.redis.enabled=true" .Values.external.redis.url -}} +{{- else -}} +{{- printf "redis://%s-redis-master:6379" .Release.Name -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.rustfsEndpoint" -}} +{{- if .Values.external.s3.enabled -}} +{{- required "external.s3.endpoint is required when external.s3.enabled=true" .Values.external.s3.endpoint -}} +{{- else -}} +{{- printf "http://%s-rustfs:%v" (include "envsync.fullname" .) .Values.rustfs.service.port -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.rustfsBucketUrl" -}} +{{- if .Values.external.s3.enabled -}} +{{- required "external.s3.bucketUrl is required when external.s3.enabled=true" .Values.external.s3.bucketUrl -}} +{{- else -}} +{{- include "envsync.rustfsEndpoint" . -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.s3Bucket" -}} +{{- if .Values.external.s3.enabled -}} +{{- required "external.s3.bucket is required when external.s3.enabled=true" .Values.external.s3.bucket -}} +{{- else -}} +{{- default "envsync-bucket" .Values.rustfs.bucket -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.s3Region" -}} +{{- if .Values.external.s3.enabled -}} +{{- default "us-east-1" .Values.external.s3.region -}} +{{- else -}} +{{- default "us-east-1" .Values.rustfs.region -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.s3AccessKey" -}} +{{- if .Values.external.s3.enabled -}} +{{- required "external.s3.accessKey is required when external.s3.enabled=true" .Values.external.s3.accessKey -}} +{{- else -}} +{{- required "rustfs.accessKey is required when rustfs is enabled" .Values.rustfs.accessKey -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.s3SecretKey" -}} +{{- if .Values.external.s3.enabled -}} +{{- required "external.s3.secretKey is required when external.s3.enabled=true" .Values.external.s3.secretKey -}} +{{- else -}} +{{- required "rustfs.secretKey is required when rustfs is enabled" .Values.rustfs.secretKey -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.postgresqlHost" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- required "external.postgresql.host is required when external.postgresql.enabled=true" .Values.external.postgresql.host -}} +{{- else -}} +{{- printf "%s-postgresql" .Release.Name -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.postgresqlPort" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- .Values.external.postgresql.port | default 5432 -}} +{{- else -}} +5432 +{{- end -}} +{{- end -}} + +{{- define "envsync.databaseSslMode" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- default "require" .Values.external.postgresql.sslMode -}} +{{- else -}} +{{- default "disable" .Values.database.sslMode -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.databaseSslEnabled" -}} +{{- if eq (include "envsync.databaseSslMode" .) "disable" -}}false{{- else -}}true{{- end -}} +{{- end -}} + +{{- define "envsync.postgresqlAdminUser" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- required "external.postgresql.adminUsername is required when external.postgresql.enabled=true" .Values.external.postgresql.adminUsername -}} +{{- else -}} +postgres +{{- end -}} +{{- end -}} + +{{- define "envsync.postgresqlAdminPassword" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- required "external.postgresql.adminPassword is required when external.postgresql.enabled=true" .Values.external.postgresql.adminPassword -}} +{{- else -}} +{{- required "postgresql.auth.postgresPassword is required" .Values.postgresql.auth.postgresPassword -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.appDatabaseName" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- required "external.postgresql.app.database is required when external.postgresql.enabled=true" .Values.external.postgresql.app.database -}} +{{- else -}} +{{- .Values.postgresql.auth.database -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.appDatabaseUser" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- required "external.postgresql.app.username is required when external.postgresql.enabled=true" .Values.external.postgresql.app.username -}} +{{- else -}} +{{- .Values.postgresql.auth.username -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.appDatabasePassword" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- required "external.postgresql.app.password is required when external.postgresql.enabled=true" .Values.external.postgresql.app.password -}} +{{- else -}} +{{- required "postgresql.auth.password is required" .Values.postgresql.auth.password -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.zitadelDbName" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- required "external.postgresql.zitadel.database is required when external.postgresql.enabled=true" .Values.external.postgresql.zitadel.database -}} +{{- else -}} +{{- .Values.database.roles.zitadel.database -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.zitadelDbUser" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- required "external.postgresql.zitadel.username is required when external.postgresql.enabled=true" .Values.external.postgresql.zitadel.username -}} +{{- else -}} +{{- .Values.database.roles.zitadel.username -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.zitadelDbPassword" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- required "external.postgresql.zitadel.password is required when external.postgresql.enabled=true" .Values.external.postgresql.zitadel.password -}} +{{- else -}} +{{- required "database.roles.zitadel.password is required" .Values.database.roles.zitadel.password -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.openfgaDbName" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- required "external.postgresql.openfga.database is required when external.postgresql.enabled=true" .Values.external.postgresql.openfga.database -}} +{{- else -}} +{{- .Values.database.roles.openfga.database -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.openfgaDbUser" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- required "external.postgresql.openfga.username is required when external.postgresql.enabled=true" .Values.external.postgresql.openfga.username -}} +{{- else -}} +{{- .Values.database.roles.openfga.username -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.openfgaDbPassword" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- required "external.postgresql.openfga.password is required when external.postgresql.enabled=true" .Values.external.postgresql.openfga.password -}} +{{- else -}} +{{- required "database.roles.openfga.password is required" .Values.database.roles.openfga.password -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.minikmsDbName" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- required "external.postgresql.minikms.database is required when external.postgresql.enabled=true" .Values.external.postgresql.minikms.database -}} +{{- else -}} +{{- .Values.database.roles.minikms.database -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.minikmsDbUser" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- required "external.postgresql.minikms.username is required when external.postgresql.enabled=true" .Values.external.postgresql.minikms.username -}} +{{- else -}} +{{- .Values.database.roles.minikms.username -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.minikmsDbPassword" -}} +{{- if .Values.external.postgresql.enabled -}} +{{- required "external.postgresql.minikms.password is required when external.postgresql.enabled=true" .Values.external.postgresql.minikms.password -}} +{{- else -}} +{{- required "database.roles.minikms.password is required" .Values.database.roles.minikms.password -}} +{{- end -}} +{{- end -}} + +{{- define "envsync.openfgaDatabaseUri" -}} +{{- printf "postgres://%s:%s@%s:%v/%s?sslmode=%s" (urlquery (include "envsync.openfgaDbUser" .)) (urlquery (include "envsync.openfgaDbPassword" .)) (include "envsync.postgresqlHost" .) (include "envsync.postgresqlPort" .) (include "envsync.openfgaDbName" .) (include "envsync.databaseSslMode" .) -}} +{{- end -}} + +{{- define "envsync.minikmsDatabaseUrl" -}} +{{- printf "postgres://%s:%s@%s:%v/%s?sslmode=%s" (urlquery (include "envsync.minikmsDbUser" .)) (urlquery (include "envsync.minikmsDbPassword" .)) (include "envsync.postgresqlHost" .) (include "envsync.postgresqlPort" .) (include "envsync.minikmsDbName" .) (include "envsync.databaseSslMode" .) -}} +{{- end -}} + +{{- define "envsync.bootstrapSecretName" -}} +{{- printf "%s-bootstrap" (include "envsync.fullname" .) -}} +{{- end -}} + +{{- define "envsync.bootstrapLockName" -}} +{{- printf "%s-bootstrap-lock" (include "envsync.fullname" .) -}} +{{- end -}} + +{{- define "envsync.apiConfigMapName" -}} +{{- printf "%s-api-config" (include "envsync.fullname" .) -}} +{{- end -}} + +{{- define "envsync.apiSecretName" -}} +{{- printf "%s-api-secret" (include "envsync.fullname" .) -}} +{{- end -}} + +{{- define "envsync.bootstrapServiceAccountName" -}} +{{- printf "%s-bootstrap" (include "envsync.fullname" .) -}} +{{- end -}} + +{{- define "envsync.runtimeServiceAccountName" -}} +{{- printf "%s-runtime" (include "envsync.fullname" .) -}} +{{- end -}} diff --git a/helm/envsync/templates/configmap-api.yaml b/helm/envsync/templates/configmap-api.yaml new file mode 100644 index 00000000..a07d6110 --- /dev/null +++ b/helm/envsync/templates/configmap-api.yaml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "envsync.apiConfigMapName" . }} + labels: + {{- include "envsync.labels" . | nindent 4 }} +data: + NODE_ENV: {{ .Values.global.environment | quote }} + PORT: {{ printf "%v" .Values.api.service.port | quote }} + DB_LOGGING: {{ printf "%v" .Values.api.config.dbLogging | quote }} + DB_AUTO_MIGRATE: {{ printf "%v" .Values.api.config.dbAutoMigrate | quote }} + DATABASE_HOST: {{ include "envsync.postgresqlHost" . | quote }} + DATABASE_PORT: {{ include "envsync.postgresqlPort" . | quote }} + DATABASE_NAME: {{ include "envsync.appDatabaseName" . | quote }} + DATABASE_SSL: {{ include "envsync.databaseSslEnabled" . | quote }} + S3_BUCKET: {{ include "envsync.s3Bucket" . | quote }} + S3_REGION: {{ include "envsync.s3Region" . | quote }} + S3_ENDPOINT: {{ include "envsync.rustfsEndpoint" . | quote }} + S3_BUCKET_URL: {{ include "envsync.rustfsBucketUrl" . | quote }} + CACHE_ENV: {{ .Values.api.config.cacheEnv | quote }} + REDIS_URL: {{ include "envsync.redisUrl" . | quote }} + SMTP_HOST: {{ .Values.smtp.host | quote }} + SMTP_PORT: {{ printf "%v" .Values.smtp.port | quote }} + SMTP_SECURE: {{ printf "%v" .Values.smtp.secure | quote }} + SMTP_FROM: {{ .Values.smtp.from | quote }} + ZITADEL_URL: {{ include "envsync.zitadelInternalUrl" . | quote }} + ZITADEL_WEB_REDIRECT_URI: {{ printf "%s://%s/api/access/web/callback" (include "envsync.publicScheme" .) (include "envsync.apiHost" .) | quote }} + ZITADEL_WEB_CALLBACK_URL: {{ printf "%s/auth/callback" (include "envsync.dashboardUrl" .) | quote }} + ZITADEL_API_REDIRECT_URI: {{ printf "%s://%s/api/access/api/callback" (include "envsync.publicScheme" .) (include "envsync.apiHost" .) | quote }} + LANDING_PAGE_URL: {{ include "envsync.landingUrl" . | quote }} + DASHBOARD_URL: {{ include "envsync.dashboardUrl" . | quote }} + OPENFGA_API_URL: {{ include "envsync.openfgaInternalUrl" . | quote }} + MINIKMS_GRPC_ADDR: {{ include "envsync.minikmsAddress" . | quote }} + MINIKMS_TLS_ENABLED: {{ printf "%v" .Values.minikms.tlsEnabled | quote }} + OTEL_EXPORTER_OTLP_ENDPOINT: {{ .Values.api.config.otelExporterEndpoint | quote }} + OTEL_SERVICE_NAME: {{ .Values.api.config.otelServiceName | quote }} + OTEL_SDK_DISABLED: {{ printf "%v" .Values.api.config.otelSdkDisabled | quote }} diff --git a/helm/envsync/templates/configmap-bootstrap-lock.yaml b/helm/envsync/templates/configmap-bootstrap-lock.yaml new file mode 100644 index 00000000..728cd73e --- /dev/null +++ b/helm/envsync/templates/configmap-bootstrap-lock.yaml @@ -0,0 +1,11 @@ +{{- $existing := lookup "v1" "ConfigMap" .Release.Namespace (include "envsync.bootstrapLockName" .) -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "envsync.bootstrapLockName" . }} + labels: + {{- include "envsync.labels" . | nindent 4 }} +data: + status: {{ index (default dict $existing.data) "status" | default "pending" | quote }} + completedAt: {{ index (default dict $existing.data) "completedAt" | default "" | quote }} + releaseRevision: {{ printf "%v" .Release.Revision | quote }} diff --git a/helm/envsync/templates/deployment-api.yaml b/helm/envsync/templates/deployment-api.yaml new file mode 100644 index 00000000..34f0e9e2 --- /dev/null +++ b/helm/envsync/templates/deployment-api.yaml @@ -0,0 +1,87 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "envsync.fullname" . }}-api + labels: + {{- include "envsync.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.api.replicaCount }} + selector: + matchLabels: + {{- include "envsync.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: api + template: + metadata: + labels: + {{- include "envsync.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: api + {{- with .Values.api.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + annotations: + checksum/api-config: {{ include (print $.Template.BasePath "/configmap-api.yaml") . | sha256sum }} + checksum/api-secret: {{ include (print $.Template.BasePath "/secret-api.yaml") . | sha256sum }} + checksum/bootstrap-secret: {{ include (print $.Template.BasePath "/secret-bootstrap.yaml") . | sha256sum }} + {{- with .Values.global.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.api.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "envsync.runtimeServiceAccountName" . }} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + initContainers: + - name: wait-for-bootstrap + image: "{{ .Values.bootstrap.kubectlImage.repository }}:{{ .Values.bootstrap.kubectlImage.tag }}" + imagePullPolicy: {{ .Values.bootstrap.kubectlImage.pullPolicy }} + command: + - /bin/sh + - -ec + - | + until [ "$(kubectl get configmap {{ include "envsync.bootstrapLockName" . }} -o jsonpath='{.data.status}' 2>/dev/null || true)" = "complete" ]; do + echo "Waiting for bootstrap lock to reach complete..." + sleep 5 + done + containers: + - name: api + image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}" + imagePullPolicy: {{ .Values.api.image.pullPolicy }} + command: ["bun", "run", "src/entrypoint.ts"] + ports: + - name: http + containerPort: {{ .Values.api.service.port }} + envFrom: + - configMapRef: + name: {{ include "envsync.apiConfigMapName" . }} + - secretRef: + name: {{ include "envsync.apiSecretName" . }} + - secretRef: + name: {{ include "envsync.bootstrapSecretName" . }} + {{- with .Values.api.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 20 + periodSeconds: 20 + startupProbe: + httpGet: + path: /health + port: http + failureThreshold: 60 + periodSeconds: 5 + resources: + {{- toYaml .Values.api.resources | nindent 12 }} diff --git a/helm/envsync/templates/deployment-minikms.yaml b/helm/envsync/templates/deployment-minikms.yaml new file mode 100644 index 00000000..dbc8ad7b --- /dev/null +++ b/helm/envsync/templates/deployment-minikms.yaml @@ -0,0 +1,79 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "envsync.fullname" . }}-minikms + labels: + {{- include "envsync.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "envsync.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: minikms + template: + metadata: + labels: + {{- include "envsync.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: minikms + {{- with .Values.global.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + initContainers: + - name: wait-for-database + image: "{{ .Values.bootstrap.dbSetup.image.repository }}:{{ .Values.bootstrap.dbSetup.image.tag }}" + imagePullPolicy: {{ .Values.bootstrap.dbSetup.image.pullPolicy }} + env: + - name: PGPASSWORD + value: {{ include "envsync.postgresqlAdminPassword" . | quote }} + - name: DB_HOST + value: {{ include "envsync.postgresqlHost" . | quote }} + - name: DB_PORT + value: {{ include "envsync.postgresqlPort" . | quote }} + - name: DB_USER + value: {{ include "envsync.postgresqlAdminUser" . | quote }} + - name: DB_NAME + value: {{ include "envsync.minikmsDbName" . | quote }} + command: + - /bin/sh + - -ec + - | + until psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='${DB_NAME}'" | grep -q 1; do + echo "Waiting for database ${DB_NAME}..." + sleep 5 + done + containers: + - name: minikms + image: "{{ .Values.minikms.image.repository }}:{{ .Values.minikms.image.tag }}" + imagePullPolicy: {{ .Values.minikms.image.pullPolicy }} + ports: + - name: grpc + containerPort: {{ .Values.minikms.service.port }} + env: + - name: MINIKMS_ROOT_KEY + value: {{ required "minikms.rootKey is required" .Values.minikms.rootKey | quote }} + - name: MINIKMS_DB_URL + value: {{ include "envsync.minikmsDatabaseUrl" . | quote }} + - name: MINIKMS_REDIS_URL + value: {{ include "envsync.redisUrl" . | quote }} + - name: MINIKMS_GRPC_ADDR + value: {{ printf "0.0.0.0:%v" .Values.minikms.service.port | quote }} + - name: MINIKMS_TLS_ENABLED + value: {{ printf "%v" .Values.minikms.tlsEnabled | quote }} + readinessProbe: + tcpSocket: + port: grpc + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: grpc + initialDelaySeconds: 15 + periodSeconds: 20 + resources: + {{- toYaml .Values.minikms.resources | nindent 12 }} diff --git a/helm/envsync/templates/deployment-openfga.yaml b/helm/envsync/templates/deployment-openfga.yaml new file mode 100644 index 00000000..b6404278 --- /dev/null +++ b/helm/envsync/templates/deployment-openfga.yaml @@ -0,0 +1,93 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "envsync.fullname" . }}-openfga + labels: + {{- include "envsync.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "envsync.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: openfga + template: + metadata: + labels: + {{- include "envsync.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: openfga + {{- with .Values.global.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + initContainers: + - name: wait-for-database + image: "{{ .Values.bootstrap.dbSetup.image.repository }}:{{ .Values.bootstrap.dbSetup.image.tag }}" + imagePullPolicy: {{ .Values.bootstrap.dbSetup.image.pullPolicy }} + env: + - name: PGPASSWORD + value: {{ include "envsync.postgresqlAdminPassword" . | quote }} + - name: DB_HOST + value: {{ include "envsync.postgresqlHost" . | quote }} + - name: DB_PORT + value: {{ include "envsync.postgresqlPort" . | quote }} + - name: DB_USER + value: {{ include "envsync.postgresqlAdminUser" . | quote }} + - name: DB_NAME + value: {{ include "envsync.openfgaDbName" . | quote }} + command: + - /bin/sh + - -ec + - | + until psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='${DB_NAME}'" | grep -q 1; do + echo "Waiting for database ${DB_NAME}..." + sleep 5 + done + - name: migrate + image: "{{ .Values.openfga.image.repository }}:{{ .Values.openfga.image.tag }}" + imagePullPolicy: {{ .Values.openfga.image.pullPolicy }} + args: ["migrate"] + env: + - name: OPENFGA_DATASTORE_ENGINE + value: postgres + - name: OPENFGA_DATASTORE_URI + value: {{ include "envsync.openfgaDatabaseUri" . | quote }} + containers: + - name: openfga + image: "{{ .Values.openfga.image.repository }}:{{ .Values.openfga.image.tag }}" + imagePullPolicy: {{ .Values.openfga.image.pullPolicy }} + args: ["run"] + ports: + - name: http + containerPort: {{ .Values.openfga.service.httpPort }} + - name: grpc + containerPort: {{ .Values.openfga.service.grpcPort }} + env: + - name: OPENFGA_DATASTORE_ENGINE + value: postgres + - name: OPENFGA_DATASTORE_URI + value: {{ include "envsync.openfgaDatabaseUri" . | quote }} + - name: OPENFGA_PLAYGROUND_ENABLED + value: {{ printf "%v" .Values.openfga.playgroundEnabled | quote }} + - name: OPENFGA_HTTP_ADDR + value: {{ printf "0.0.0.0:%v" .Values.openfga.service.httpPort | quote }} + - name: OPENFGA_GRPC_ADDR + value: {{ printf "0.0.0.0:%v" .Values.openfga.service.grpcPort | quote }} + readinessProbe: + exec: + command: ["/usr/local/bin/grpc_health_probe", "-addr=:8091"] + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + livenessProbe: + exec: + command: ["/usr/local/bin/grpc_health_probe", "-addr=:8091"] + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 5 + resources: + {{- toYaml .Values.openfga.resources | nindent 12 }} diff --git a/helm/envsync/templates/deployment-rustfs.yaml b/helm/envsync/templates/deployment-rustfs.yaml new file mode 100644 index 00000000..854dbe7d --- /dev/null +++ b/helm/envsync/templates/deployment-rustfs.yaml @@ -0,0 +1,63 @@ +{{- if not .Values.external.s3.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "envsync.fullname" . }}-rustfs + labels: + {{- include "envsync.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "envsync.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: rustfs + template: + metadata: + labels: + {{- include "envsync.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: rustfs + {{- with .Values.global.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: rustfs + image: "{{ .Values.rustfs.image.repository }}:{{ .Values.rustfs.image.tag }}" + imagePullPolicy: {{ .Values.rustfs.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.rustfs.service.port }} + env: + - name: RUSTFS_DATA_DIR + value: /data + - name: RUSTFS_ACCESS_KEY + value: {{ required "rustfs.accessKey is required" .Values.rustfs.accessKey | quote }} + - name: RUSTFS_SECRET_KEY + value: {{ required "rustfs.secretKey is required" .Values.rustfs.secretKey | quote }} + - name: RUSTFS_CONSOLE_ENABLE + value: {{ printf "%v" .Values.rustfs.consoleEnabled | quote }} + readinessProbe: + tcpSocket: + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: http + initialDelaySeconds: 15 + periodSeconds: 20 + resources: + {{- toYaml .Values.rustfs.resources | nindent 12 }} + volumeMounts: + - name: rustfs-data + mountPath: /data + volumes: + - name: rustfs-data + persistentVolumeClaim: + claimName: {{ include "envsync.fullname" . }}-rustfs-data +{{- end }} diff --git a/helm/envsync/templates/deployment-zitadel.yaml b/helm/envsync/templates/deployment-zitadel.yaml new file mode 100644 index 00000000..bd040bf4 --- /dev/null +++ b/helm/envsync/templates/deployment-zitadel.yaml @@ -0,0 +1,134 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "envsync.fullname" . }}-zitadel + labels: + {{- include "envsync.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "envsync.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: zitadel + template: + metadata: + labels: + {{- include "envsync.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: zitadel + {{- with .Values.global.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + initContainers: + - name: wait-for-database + image: "{{ .Values.bootstrap.dbSetup.image.repository }}:{{ .Values.bootstrap.dbSetup.image.tag }}" + imagePullPolicy: {{ .Values.bootstrap.dbSetup.image.pullPolicy }} + env: + - name: PGPASSWORD + value: {{ include "envsync.postgresqlAdminPassword" . | quote }} + - name: DB_HOST + value: {{ include "envsync.postgresqlHost" . | quote }} + - name: DB_PORT + value: {{ include "envsync.postgresqlPort" . | quote }} + - name: DB_USER + value: {{ include "envsync.postgresqlAdminUser" . | quote }} + - name: DB_NAME + value: {{ include "envsync.zitadelDbName" . | quote }} + command: + - /bin/sh + - -ec + - | + until psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname='${DB_NAME}'" | grep -q 1; do + echo "Waiting for database ${DB_NAME}..." + sleep 5 + done + containers: + - name: zitadel + image: "{{ .Values.zitadel.image.repository }}:{{ .Values.zitadel.image.tag }}" + imagePullPolicy: {{ .Values.zitadel.image.pullPolicy }} + command: + - start-from-init + - --masterkey + - {{ required "zitadel.masterkey is required" .Values.zitadel.masterkey | quote }} + securityContext: + runAsUser: 0 + ports: + - name: http + containerPort: {{ .Values.zitadel.service.port }} + env: + - name: ZITADEL_EXTERNALDOMAIN + value: {{ default (include "envsync.authHost" .) .Values.zitadel.externalDomain | quote }} + - name: ZITADEL_EXTERNALSECURE + value: {{ eq (include "envsync.publicScheme" .) "https" | quote }} + - name: ZITADEL_TLS_ENABLED + value: "false" + - name: ZITADEL_DATABASE_POSTGRES_HOST + value: {{ include "envsync.postgresqlHost" . | quote }} + - name: ZITADEL_DATABASE_POSTGRES_PORT + value: {{ include "envsync.postgresqlPort" . | quote }} + - name: ZITADEL_DATABASE_POSTGRES_DATABASE + value: {{ include "envsync.zitadelDbName" . | quote }} + - name: ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME + value: {{ include "envsync.postgresqlAdminUser" . | quote }} + - name: ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD + value: {{ include "envsync.postgresqlAdminPassword" . | quote }} + - name: ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE + value: {{ include "envsync.databaseSslMode" . | quote }} + - name: ZITADEL_DATABASE_POSTGRES_USER_USERNAME + value: {{ include "envsync.zitadelDbUser" . | quote }} + - name: ZITADEL_DATABASE_POSTGRES_USER_PASSWORD + value: {{ include "envsync.zitadelDbPassword" . | quote }} + - name: ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE + value: {{ include "envsync.databaseSslMode" . | quote }} + - name: ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED + value: "false" + - name: ZITADEL_FIRSTINSTANCE_ORG_HUMAN_USERNAME + value: {{ .Values.zitadel.admin.username | quote }} + - name: ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD + value: {{ required "zitadel.admin.password is required" .Values.zitadel.admin.password | quote }} + - name: ZITADEL_FIRSTINSTANCE_ORG_NAME + value: "EnvSync" + - name: ZITADEL_FIRSTINSTANCE_PATPATH + value: /current-dir/admin.pat + - name: ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME + value: admin + - name: ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME + value: EnvSync IAM Owner + - name: ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE + value: {{ .Values.zitadel.machinePatExpirationDate | quote }} + - name: ZITADEL_DEFAULTINSTANCE_FEATURES_LOGINV2_REQUIRED + value: "false" + - name: ZITADEL_FIRSTINSTANCE_LOGINCLIENTPATPATH + value: /current-dir/login-client.pat + - name: ZITADEL_FIRSTINSTANCE_ORG_LOGINCLIENT_MACHINE_USERNAME + value: login-client + - name: ZITADEL_FIRSTINSTANCE_ORG_LOGINCLIENT_MACHINE_NAME + value: Automatically Initialized IAM_LOGIN_CLIENT + - name: ZITADEL_FIRSTINSTANCE_ORG_LOGINCLIENT_PAT_EXPIRATIONDATE + value: {{ .Values.zitadel.loginClientPatExpirationDate | quote }} + readinessProbe: + exec: + command: ["/app/zitadel", "ready"] + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 60 + startupProbe: + exec: + command: ["/app/zitadel", "ready"] + failureThreshold: 60 + periodSeconds: 5 + timeoutSeconds: 60 + resources: + {{- toYaml .Values.zitadel.resources | nindent 12 }} + volumeMounts: + - name: zitadel-data + mountPath: /current-dir + volumes: + - name: zitadel-data + persistentVolumeClaim: + claimName: {{ include "envsync.fullname" . }}-zitadel-data diff --git a/helm/envsync/templates/hpa-api.yaml b/helm/envsync/templates/hpa-api.yaml new file mode 100644 index 00000000..ae8f025c --- /dev/null +++ b/helm/envsync/templates/hpa-api.yaml @@ -0,0 +1,22 @@ +{{- if .Values.api.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "envsync.fullname" . }}-api + labels: + {{- include "envsync.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "envsync.fullname" . }}-api + minReplicas: {{ .Values.api.autoscaling.minReplicas }} + maxReplicas: {{ .Values.api.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.api.autoscaling.targetCPUUtilizationPercentage }} +{{- end }} diff --git a/helm/envsync/templates/ingress.yaml b/helm/envsync/templates/ingress.yaml new file mode 100644 index 00000000..0f489a8a --- /dev/null +++ b/helm/envsync/templates/ingress.yaml @@ -0,0 +1,44 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "envsync.fullname" . }} + labels: + {{- include "envsync.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . | quote }} + {{- end }} + rules: + - host: {{ include "envsync.apiHost" . | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "envsync.fullname" . }}-api + port: + number: {{ .Values.api.service.port }} + - host: {{ include "envsync.authHost" . | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "envsync.fullname" . }}-zitadel + port: + number: {{ .Values.zitadel.service.port }} + {{- if .Values.ingress.tls.enabled }} + tls: + - hosts: + - {{ include "envsync.apiHost" . | quote }} + - {{ include "envsync.authHost" . | quote }} + secretName: {{ default (printf "%s-tls" (include "envsync.fullname" .)) .Values.ingress.tls.secretName }} + {{- end }} +{{- end }} diff --git a/helm/envsync/templates/job-db-setup.yaml b/helm/envsync/templates/job-db-setup.yaml new file mode 100644 index 00000000..5cab01ce --- /dev/null +++ b/helm/envsync/templates/job-db-setup.yaml @@ -0,0 +1,125 @@ +{{- if .Values.bootstrap.dbSetup.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "envsync.fullname" . }}-db-setup + labels: + {{- include "envsync.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": post-install,pre-upgrade + "helm.sh/hook-weight": "-20" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + backoffLimit: {{ .Values.bootstrap.dbSetup.backoffLimit }} + activeDeadlineSeconds: {{ .Values.bootstrap.dbSetup.activeDeadlineSeconds }} + template: + metadata: + labels: + {{- include "envsync.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: db-setup + {{- with .Values.global.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + restartPolicy: Never + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: db-setup + image: "{{ .Values.bootstrap.dbSetup.image.repository }}:{{ .Values.bootstrap.dbSetup.image.tag }}" + imagePullPolicy: {{ .Values.bootstrap.dbSetup.image.pullPolicy }} + env: + - name: PGHOST + value: {{ include "envsync.postgresqlHost" . | quote }} + - name: PGPORT + value: {{ include "envsync.postgresqlPort" . | quote }} + - name: PGUSER + value: {{ include "envsync.postgresqlAdminUser" . | quote }} + - name: PGPASSWORD + value: {{ include "envsync.postgresqlAdminPassword" . | quote }} + - name: APP_DB + value: {{ include "envsync.appDatabaseName" . | quote }} + - name: APP_USER + value: {{ include "envsync.appDatabaseUser" . | quote }} + - name: APP_PASSWORD + value: {{ include "envsync.appDatabasePassword" . | quote }} + - name: ZITADEL_DB + value: {{ include "envsync.zitadelDbName" . | quote }} + - name: ZITADEL_USER + value: {{ include "envsync.zitadelDbUser" . | quote }} + - name: ZITADEL_PASSWORD + value: {{ include "envsync.zitadelDbPassword" . | quote }} + - name: OPENFGA_DB + value: {{ include "envsync.openfgaDbName" . | quote }} + - name: OPENFGA_USER + value: {{ include "envsync.openfgaDbUser" . | quote }} + - name: OPENFGA_PASSWORD + value: {{ include "envsync.openfgaDbPassword" . | quote }} + - name: MINIKMS_DB + value: {{ include "envsync.minikmsDbName" . | quote }} + - name: MINIKMS_USER + value: {{ include "envsync.minikmsDbUser" . | quote }} + - name: MINIKMS_PASSWORD + value: {{ include "envsync.minikmsDbPassword" . | quote }} + command: + - /bin/sh + - -ec + - | + set -eu + + wait_for_postgres() { + until pg_isready -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" >/dev/null 2>&1; do + echo "Waiting for PostgreSQL at ${PGHOST}:${PGPORT}..." + sleep 5 + done + } + + sql_escape() { + printf "%s" "$1" | sed "s/'/''/g" + } + + exec_psql() { + psql -v ON_ERROR_STOP=1 -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres -c "$1" + } + + ensure_role() { + local role="$1" + local password + password="$(sql_escape "$2")" + + if ! psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${role}'" -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres | grep -q 1; then + exec_psql "CREATE ROLE \"${role}\" LOGIN PASSWORD '${password}'" + fi + + exec_psql "ALTER ROLE \"${role}\" LOGIN PASSWORD '${password}'" + } + + ensure_db() { + local db="$1" + local owner="$2" + + if ! psql -tAc "SELECT 1 FROM pg_database WHERE datname='${db}'" -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres | grep -q 1; then + exec_psql "CREATE DATABASE \"${db}\" OWNER \"${owner}\"" + fi + + exec_psql "GRANT ALL PRIVILEGES ON DATABASE \"${db}\" TO \"${owner}\"" + psql -v ON_ERROR_STOP=1 -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$db" -c "GRANT ALL ON SCHEMA public TO \"${owner}\"" + } + + wait_for_postgres + + ensure_role "$APP_USER" "$APP_PASSWORD" + ensure_role "$ZITADEL_USER" "$ZITADEL_PASSWORD" + ensure_role "$OPENFGA_USER" "$OPENFGA_PASSWORD" + ensure_role "$MINIKMS_USER" "$MINIKMS_PASSWORD" + + ensure_db "$APP_DB" "$APP_USER" + ensure_db "$ZITADEL_DB" "$ZITADEL_USER" + ensure_db "$OPENFGA_DB" "$OPENFGA_USER" + ensure_db "$MINIKMS_DB" "$MINIKMS_USER" + + echo "Database bootstrap complete." +{{- end }} diff --git a/helm/envsync/templates/job-init.yaml b/helm/envsync/templates/job-init.yaml new file mode 100644 index 00000000..00fe8198 --- /dev/null +++ b/helm/envsync/templates/job-init.yaml @@ -0,0 +1,112 @@ +{{- if .Values.bootstrap.init.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "envsync.fullname" . }}-init + labels: + {{- include "envsync.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": post-install + "helm.sh/hook-weight": "20" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + backoffLimit: {{ .Values.bootstrap.init.backoffLimit }} + activeDeadlineSeconds: {{ .Values.bootstrap.init.activeDeadlineSeconds }} + template: + metadata: + labels: + {{- include "envsync.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: bootstrap + {{- with .Values.global.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + restartPolicy: Never + serviceAccountName: {{ include "envsync.bootstrapServiceAccountName" . }} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: shared + emptyDir: {} + - name: zitadel-data + persistentVolumeClaim: + claimName: {{ include "envsync.fullname" . }}-zitadel-data + initContainers: + - name: check-lock + image: "{{ .Values.bootstrap.kubectlImage.repository }}:{{ .Values.bootstrap.kubectlImage.tag }}" + imagePullPolicy: {{ .Values.bootstrap.kubectlImage.pullPolicy }} + volumeMounts: + - name: shared + mountPath: /shared + command: + - /bin/sh + - -ec + - | + status="$(kubectl get configmap {{ include "envsync.bootstrapLockName" . }} -o jsonpath='{.data.status}' 2>/dev/null || true)" + if [ "$status" = "complete" ]; then + echo "complete" > /shared/skip + fi + - name: run-bootstrap + image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}" + imagePullPolicy: {{ .Values.api.image.pullPolicy }} + volumeMounts: + - name: shared + mountPath: /shared + - name: zitadel-data + mountPath: /zitadel-data + readOnly: true + envFrom: + - configMapRef: + name: {{ include "envsync.apiConfigMapName" . }} + - secretRef: + name: {{ include "envsync.apiSecretName" . }} + command: + - /bin/sh + - -ec + - | + if [ -f /shared/skip ]; then + echo "Bootstrap already completed; skipping." + exit 0 + fi + + bun run scripts/prod-init.ts | tee /shared/prod-init.log + grep -E '^[A-Z0-9_]+=' /shared/prod-init.log > /shared/bootstrap.env + if [ ! -s /shared/bootstrap.env ]; then + echo "No bootstrap credentials were produced." + exit 1 + fi + env: + - name: ZITADEL_PAT_FILE + value: /zitadel-data/admin.pat + - name: apply-bootstrap + image: "{{ .Values.bootstrap.kubectlImage.repository }}:{{ .Values.bootstrap.kubectlImage.tag }}" + imagePullPolicy: {{ .Values.bootstrap.kubectlImage.pullPolicy }} + volumeMounts: + - name: shared + mountPath: /shared + command: + - /bin/sh + - -ec + - | + if [ -f /shared/skip ]; then + echo "Bootstrap already completed; lock is already complete." + exit 0 + fi + + kubectl create secret generic {{ include "envsync.bootstrapSecretName" . }} \ + --dry-run=client \ + --from-env-file=/shared/bootstrap.env \ + -o yaml | kubectl apply -f - + + kubectl patch configmap {{ include "envsync.bootstrapLockName" . }} \ + --type merge \ + -p '{"data":{"status":"complete","completedAt":"'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"}}' + containers: + - name: done + image: "{{ .Values.bootstrap.kubectlImage.repository }}:{{ .Values.bootstrap.kubectlImage.tag }}" + imagePullPolicy: {{ .Values.bootstrap.kubectlImage.pullPolicy }} + command: ["/bin/sh", "-ec", "echo Bootstrap finished."] +{{- end }} diff --git a/helm/envsync/templates/job-migrate.yaml b/helm/envsync/templates/job-migrate.yaml new file mode 100644 index 00000000..4a1450ba --- /dev/null +++ b/helm/envsync/templates/job-migrate.yaml @@ -0,0 +1,40 @@ +{{- if .Values.bootstrap.migrate.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "envsync.fullname" . }}-migrate-{{ .Release.Revision }} + labels: + {{- include "envsync.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": pre-upgrade + "helm.sh/hook-weight": "0" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + backoffLimit: {{ .Values.bootstrap.migrate.backoffLimit }} + activeDeadlineSeconds: {{ .Values.bootstrap.migrate.activeDeadlineSeconds }} + template: + metadata: + labels: + {{- include "envsync.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: migrate + {{- with .Values.global.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + restartPolicy: Never + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: migrate + image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}" + imagePullPolicy: {{ .Values.api.image.pullPolicy }} + command: ["bun", "run", "scripts/migrate.ts", "latest"] + envFrom: + - configMapRef: + name: {{ include "envsync.apiConfigMapName" . }} + - secretRef: + name: {{ include "envsync.apiSecretName" . }} +{{- end }} diff --git a/helm/envsync/templates/pvc-rustfs.yaml b/helm/envsync/templates/pvc-rustfs.yaml new file mode 100644 index 00000000..fe7cd0c2 --- /dev/null +++ b/helm/envsync/templates/pvc-rustfs.yaml @@ -0,0 +1,17 @@ +{{- if not .Values.external.s3.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "envsync.fullname" . }}-rustfs-data + labels: + {{- include "envsync.labels" . | nindent 4 }} +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.rustfs.persistence.size }} + {{- with .Values.global.storageClass }} + storageClassName: {{ . | quote }} + {{- end }} +{{- end }} diff --git a/helm/envsync/templates/pvc-zitadel.yaml b/helm/envsync/templates/pvc-zitadel.yaml new file mode 100644 index 00000000..b9584f1f --- /dev/null +++ b/helm/envsync/templates/pvc-zitadel.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "envsync.fullname" . }}-zitadel-data + labels: + {{- include "envsync.labels" . | nindent 4 }} +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.zitadel.persistence.size }} + {{- with .Values.global.storageClass }} + storageClassName: {{ . | quote }} + {{- end }} diff --git a/helm/envsync/templates/role-bootstrap.yaml b/helm/envsync/templates/role-bootstrap.yaml new file mode 100644 index 00000000..cbea8678 --- /dev/null +++ b/helm/envsync/templates/role-bootstrap.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "envsync.bootstrapServiceAccountName" . }} + labels: + {{- include "envsync.labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: ["secrets"] + resourceNames: ["{{ include "envsync.bootstrapSecretName" . }}"] + verbs: ["get", "create", "update", "patch"] + - apiGroups: [""] + resources: ["configmaps"] + resourceNames: ["{{ include "envsync.bootstrapLockName" . }}"] + verbs: ["get", "update", "patch"] diff --git a/helm/envsync/templates/role-runtime.yaml b/helm/envsync/templates/role-runtime.yaml new file mode 100644 index 00000000..cf27971c --- /dev/null +++ b/helm/envsync/templates/role-runtime.yaml @@ -0,0 +1,11 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "envsync.runtimeServiceAccountName" . }} + labels: + {{- include "envsync.labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: ["configmaps"] + resourceNames: ["{{ include "envsync.bootstrapLockName" . }}"] + verbs: ["get"] diff --git a/helm/envsync/templates/rolebinding-bootstrap.yaml b/helm/envsync/templates/rolebinding-bootstrap.yaml new file mode 100644 index 00000000..6aaf3f0c --- /dev/null +++ b/helm/envsync/templates/rolebinding-bootstrap.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "envsync.bootstrapServiceAccountName" . }} + labels: + {{- include "envsync.labels" . | nindent 4 }} +subjects: + - kind: ServiceAccount + name: {{ include "envsync.bootstrapServiceAccountName" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "envsync.bootstrapServiceAccountName" . }} diff --git a/helm/envsync/templates/rolebinding-runtime.yaml b/helm/envsync/templates/rolebinding-runtime.yaml new file mode 100644 index 00000000..62f021ce --- /dev/null +++ b/helm/envsync/templates/rolebinding-runtime.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "envsync.runtimeServiceAccountName" . }} + labels: + {{- include "envsync.labels" . | nindent 4 }} +subjects: + - kind: ServiceAccount + name: {{ include "envsync.runtimeServiceAccountName" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "envsync.runtimeServiceAccountName" . }} diff --git a/helm/envsync/templates/secret-api.yaml b/helm/envsync/templates/secret-api.yaml new file mode 100644 index 00000000..705c9e74 --- /dev/null +++ b/helm/envsync/templates/secret-api.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "envsync.apiSecretName" . }} + labels: + {{- include "envsync.labels" . | nindent 4 }} +type: Opaque +stringData: + DATABASE_USER: {{ include "envsync.appDatabaseUser" . | quote }} + DATABASE_PASSWORD: {{ include "envsync.appDatabasePassword" . | quote }} + S3_ACCESS_KEY: {{ include "envsync.s3AccessKey" . | quote }} + S3_SECRET_KEY: {{ include "envsync.s3SecretKey" . | quote }} + SMTP_USER: {{ .Values.smtp.user | quote }} + SMTP_PASS: {{ .Values.smtp.pass | quote }} + {{- if .Values.minikms.tlsCaCert }} + MINIKMS_TLS_CA_CERT: {{ .Values.minikms.tlsCaCert | quote }} + {{- end }} diff --git a/helm/envsync/templates/secret-bootstrap.yaml b/helm/envsync/templates/secret-bootstrap.yaml new file mode 100644 index 00000000..55f06734 --- /dev/null +++ b/helm/envsync/templates/secret-bootstrap.yaml @@ -0,0 +1,18 @@ +{{- $existing := lookup "v1" "Secret" .Release.Namespace (include "envsync.bootstrapSecretName" .) -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "envsync.bootstrapSecretName" . }} + labels: + {{- include "envsync.labels" . | nindent 4 }} +type: Opaque +stringData: + ZITADEL_PAT: {{ get (default dict $existing.data) "ZITADEL_PAT" | default "" | b64dec | quote }} + ZITADEL_WEB_CLIENT_ID: {{ get (default dict $existing.data) "ZITADEL_WEB_CLIENT_ID" | default "" | b64dec | quote }} + ZITADEL_WEB_CLIENT_SECRET: {{ get (default dict $existing.data) "ZITADEL_WEB_CLIENT_SECRET" | default "" | b64dec | quote }} + ZITADEL_CLI_CLIENT_ID: {{ get (default dict $existing.data) "ZITADEL_CLI_CLIENT_ID" | default "" | b64dec | quote }} + ZITADEL_CLI_CLIENT_SECRET: {{ get (default dict $existing.data) "ZITADEL_CLI_CLIENT_SECRET" | default "" | b64dec | quote }} + ZITADEL_API_CLIENT_ID: {{ get (default dict $existing.data) "ZITADEL_API_CLIENT_ID" | default "" | b64dec | quote }} + ZITADEL_API_CLIENT_SECRET: {{ get (default dict $existing.data) "ZITADEL_API_CLIENT_SECRET" | default "" | b64dec | quote }} + OPENFGA_STORE_ID: {{ get (default dict $existing.data) "OPENFGA_STORE_ID" | default "" | b64dec | quote }} + OPENFGA_MODEL_ID: {{ get (default dict $existing.data) "OPENFGA_MODEL_ID" | default "" | b64dec | quote }} diff --git a/helm/envsync/templates/service-api.yaml b/helm/envsync/templates/service-api.yaml new file mode 100644 index 00000000..31ce98ce --- /dev/null +++ b/helm/envsync/templates/service-api.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "envsync.fullname" . }}-api + labels: + {{- include "envsync.labels" . | nindent 4 }} +spec: + type: {{ .Values.api.service.type }} + selector: + {{- include "envsync.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: api + ports: + - name: http + port: {{ .Values.api.service.port }} + targetPort: http diff --git a/helm/envsync/templates/service-minikms.yaml b/helm/envsync/templates/service-minikms.yaml new file mode 100644 index 00000000..5e42465e --- /dev/null +++ b/helm/envsync/templates/service-minikms.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "envsync.fullname" . }}-minikms + labels: + {{- include "envsync.labels" . | nindent 4 }} +spec: + type: ClusterIP + selector: + {{- include "envsync.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: minikms + ports: + - name: grpc + port: {{ .Values.minikms.service.port }} + targetPort: grpc diff --git a/helm/envsync/templates/service-openfga.yaml b/helm/envsync/templates/service-openfga.yaml new file mode 100644 index 00000000..d712c3b5 --- /dev/null +++ b/helm/envsync/templates/service-openfga.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "envsync.fullname" . }}-openfga + labels: + {{- include "envsync.labels" . | nindent 4 }} +spec: + type: ClusterIP + selector: + {{- include "envsync.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: openfga + ports: + - name: http + port: {{ .Values.openfga.service.httpPort }} + targetPort: http + - name: grpc + port: {{ .Values.openfga.service.grpcPort }} + targetPort: grpc diff --git a/helm/envsync/templates/service-rustfs.yaml b/helm/envsync/templates/service-rustfs.yaml new file mode 100644 index 00000000..990a2c7c --- /dev/null +++ b/helm/envsync/templates/service-rustfs.yaml @@ -0,0 +1,17 @@ +{{- if not .Values.external.s3.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "envsync.fullname" . }}-rustfs + labels: + {{- include "envsync.labels" . | nindent 4 }} +spec: + type: ClusterIP + selector: + {{- include "envsync.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: rustfs + ports: + - name: http + port: {{ .Values.rustfs.service.port }} + targetPort: http +{{- end }} diff --git a/helm/envsync/templates/service-zitadel.yaml b/helm/envsync/templates/service-zitadel.yaml new file mode 100644 index 00000000..8cef4262 --- /dev/null +++ b/helm/envsync/templates/service-zitadel.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "envsync.fullname" . }}-zitadel + labels: + {{- include "envsync.labels" . | nindent 4 }} +spec: + type: ClusterIP + selector: + {{- include "envsync.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: zitadel + ports: + - name: http + port: {{ .Values.zitadel.service.port }} + targetPort: http diff --git a/helm/envsync/templates/serviceaccount-bootstrap.yaml b/helm/envsync/templates/serviceaccount-bootstrap.yaml new file mode 100644 index 00000000..bf6a60ac --- /dev/null +++ b/helm/envsync/templates/serviceaccount-bootstrap.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "envsync.bootstrapServiceAccountName" . }} + labels: + {{- include "envsync.labels" . | nindent 4 }} diff --git a/helm/envsync/templates/serviceaccount-runtime.yaml b/helm/envsync/templates/serviceaccount-runtime.yaml new file mode 100644 index 00000000..446a3e6c --- /dev/null +++ b/helm/envsync/templates/serviceaccount-runtime.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "envsync.runtimeServiceAccountName" . }} + labels: + {{- include "envsync.labels" . | nindent 4 }} diff --git a/helm/envsync/values-kind.yaml b/helm/envsync/values-kind.yaml new file mode 100644 index 00000000..1b90f5bc --- /dev/null +++ b/helm/envsync/values-kind.yaml @@ -0,0 +1,50 @@ +global: + domain: envsync.local + publicScheme: http + +api: + replicaCount: 1 + autoscaling: + enabled: false + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 384Mi + +postgresql: + architecture: standalone + primary: + persistence: + size: 4Gi + readReplicas: + replicaCount: 0 + +redis: + master: + persistence: + enabled: false + +ingress: + enabled: false + +zitadel: + resources: + requests: + cpu: 100m + memory: 192Mi + limits: + cpu: 500m + memory: 768Mi + persistence: + size: 1Gi + +rustfs: + persistence: + size: 4Gi + +bootstrap: + init: + activeDeadlineSeconds: 1500 diff --git a/helm/envsync/values.yaml b/helm/envsync/values.yaml new file mode 100644 index 00000000..e3d36135 --- /dev/null +++ b/helm/envsync/values.yaml @@ -0,0 +1,247 @@ +global: + domain: envsync.local + publicScheme: https + environment: production + imagePullSecrets: [] + commonLabels: {} + podAnnotations: {} + storageClass: "" + +api: + image: + repository: ghcr.io/envsync-cloud/envsync-api + tag: latest + pullPolicy: IfNotPresent + replicaCount: 2 + service: + type: ClusterIP + port: 4000 + config: + dbLogging: false + dbAutoMigrate: false + cacheEnv: production + otelExporterEndpoint: http://otel-collector:4318 + otelServiceName: envsync-api + otelSdkDisabled: true + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: "1" + memory: 512Mi + podLabels: {} + podAnnotations: {} + extraEnv: [] + autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 + +bootstrap: + kubectlImage: + repository: bitnami/kubectl + tag: latest + pullPolicy: IfNotPresent + dbSetup: + enabled: true + image: + repository: postgres + tag: "17" + pullPolicy: IfNotPresent + backoffLimit: 4 + activeDeadlineSeconds: 900 + init: + enabled: true + backoffLimit: 1 + activeDeadlineSeconds: 1200 + migrate: + enabled: true + backoffLimit: 1 + activeDeadlineSeconds: 900 + +database: + sslMode: disable + roles: + zitadel: + username: zitadel + database: zitadel + password: "" + openfga: + username: openfga + database: openfga + password: "" + minikms: + username: minikms + database: minikms + password: "" + +postgresql: + enabled: true + architecture: replication + auth: + username: envsync + password: "" + database: envsync + postgresPassword: "" + replicationPassword: "" + primary: + persistence: + enabled: true + size: 20Gi + readReplicas: + replicaCount: 1 + persistence: + enabled: true + size: 20Gi + +redis: + enabled: true + architecture: standalone + auth: + enabled: false + master: + persistence: + enabled: false + +external: + postgresql: + enabled: false + host: "" + port: 5432 + sslMode: require + adminUsername: postgres + adminPassword: "" + app: + username: envsync + password: "" + database: envsync + zitadel: + username: zitadel + password: "" + database: zitadel + openfga: + username: openfga + password: "" + database: openfga + minikms: + username: minikms + password: "" + database: minikms + redis: + enabled: false + url: "" + s3: + enabled: false + endpoint: "" + bucketUrl: "" + bucket: "" + region: us-east-1 + accessKey: "" + secretKey: "" + +smtp: + host: smtp.example.com + port: 587 + secure: true + user: "" + pass: "" + from: noreply@example.com + +urls: + landing: "" + dashboard: "" + +ingress: + enabled: true + className: "" + annotations: {} + hosts: + api: "" + auth: "" + tls: + enabled: false + secretName: "" + +zitadel: + image: + repository: ghcr.io/zitadel/zitadel + tag: latest + pullPolicy: IfNotPresent + service: + port: 8080 + externalDomain: "" + masterkey: "" + admin: + username: zitadel-admin + password: "" + machinePatExpirationDate: "2029-01-01T00:00:00Z" + loginClientPatExpirationDate: "2029-01-01T00:00:00Z" + persistence: + size: 1Gi + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 1Gi + +openfga: + image: + repository: openfga/openfga + tag: v1.12.0 + pullPolicy: IfNotPresent + service: + httpPort: 8090 + grpcPort: 8091 + playgroundEnabled: false + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + +minikms: + image: + repository: ghcr.io/envsync-cloud/minikms + tag: sha-735dfe8 + pullPolicy: IfNotPresent + service: + port: 50051 + rootKey: "" + tlsEnabled: false + tlsCaCert: "" + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + +rustfs: + enabled: true + image: + repository: rustfs/rustfs + tag: latest + pullPolicy: IfNotPresent + service: + port: 9000 + bucket: envsync-bucket + region: us-east-1 + accessKey: "" + secretKey: "" + consoleEnabled: false + persistence: + size: 20Gi + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi diff --git a/k8s/Makefile b/k8s/Makefile new file mode 100644 index 00000000..54f48ac5 --- /dev/null +++ b/k8s/Makefile @@ -0,0 +1,11 @@ +SHELL := /bin/bash + +.DEFAULT_GOAL := help + +.PHONY: help +help: + @printf "The advanced operator-based Kubernetes path has not been implemented yet.\n" + @printf "Use the primary Helm path from the repo root instead:\n\n" + @printf " make kind-create\n" + @printf " make helm-deps\n" + @printf " make helm-install-kind\n" diff --git a/k8s/README.md b/k8s/README.md new file mode 100644 index 00000000..d001eea5 --- /dev/null +++ b/k8s/README.md @@ -0,0 +1,15 @@ +# Advanced Kubernetes Path + +The primary self-hosting path in this repository is the Helm chart under `helm/envsync/`. + +This `k8s/` tree is reserved for a later operator-based deployment path using dedicated +infrastructure components such as CloudNativePG, Gateway API, cert-manager, and related +cluster-level add-ons. + +Until that path is implemented, use the repo-root Makefile and the Helm chart: + +```sh +make kind-create +make helm-deps +make helm-install-kind +``` diff --git a/kind-config.yaml b/kind-config.yaml new file mode 100644 index 00000000..ae8cd304 --- /dev/null +++ b/kind-config.yaml @@ -0,0 +1,6 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: + - role: control-plane + - role: worker + - role: worker