snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 3s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
Thank you for installing Twenty!
To access the application:
- If ingress enabled:
{{- if and .Values.server.ingress.enabled (gt (len .Values.server.ingress.hosts) 0) }}
- Primary host: {{ (index .Values.server.ingress.hosts 0).host | default "<set a host>" }}
{{- else }}
- Primary host: <set a host>
{{- end }}
- URL: {{ include "twenty.serverUrl" . }}
- If ingress disabled: expose the service as needed (e.g., `kubectl port-forward svc/{{ include "twenty.fullname" . }}-server 3000:{{ .Values.server.service.port }}` or create a LoadBalancer/NodePort).
Configuration:
- Using internal DB: {{ ternary "yes" "no" .Values.db.enabled }}
- Using external DB: {{ ternary "yes" "no" (not .Values.db.enabled) }}
- Using internal Redis: {{ ternary "yes" "no" .Values.redisInternal.enabled }}

View File

@@ -0,0 +1,213 @@
{{- define "twenty.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "twenty.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- define "twenty.namespace" -}}
{{ .Release.Namespace }}
{{- end -}}
{{/* Server image fields merged with globals */}}
{{- define "twenty.server.image" -}}
{{- $repo := default $.Values.image.repository (index $.Values.server.image "repository" | default "") -}}
{{- $tag := default (default $.Chart.AppVersion $.Values.image.tag) (index $.Values.server.image "tag" | default "") -}}
{{- $pp := default $.Values.image.pullPolicy (index $.Values.server.image "pullPolicy" | default "") -}}
{{- printf "%s:%s|%s" $repo $tag $pp -}}
{{- end -}}
{{/* Worker image fields merged with globals */}}
{{- define "twenty.worker.image" -}}
{{- $repo := default $.Values.image.repository (index $.Values.worker.image "repository" | default "") -}}
{{- $tag := default (default $.Chart.AppVersion $.Values.image.tag) (index $.Values.worker.image "tag" | default "") -}}
{{- $pp := default $.Values.image.pullPolicy (index $.Values.worker.image "pullPolicy" | default "") -}}
{{- printf "%s:%s|%s" $repo $tag $pp -}}
{{- end -}}
{{/* Extract parts of image helper */}}
{{- define "twenty.image.repository" -}}
{{- regexFind "^([^:|]+)" . -}}
{{- end -}}
{{- define "twenty.image.tag" -}}
{{- regexFind ":([^|]+)" . | trimPrefix ":" -}}
{{- end -}}
{{- define "twenty.image.pullPolicy" -}}
{{- regexFind "\\|(.+)$" . | trimPrefix "|" -}}
{{- end -}}
{{/* Check if using external secret for database password */}}
{{- define "twenty.db.useExternalSecret" -}}
{{- if and (not .Values.db.enabled) .Values.db.external.secretName .Values.db.external.passwordKey -}}
true
{{- else -}}
false
{{- end -}}
{{- end -}}
{{/* Database URL secret name */}}
{{- define "twenty.dbUrl.secretName" -}}
{{- printf "%s-db-url" (include "twenty.fullname" .) -}}
{{- end -}}
{{/* Database password secret name */}}
{{- define "twenty.dbPassword.secretName" -}}
{{- if eq (include "twenty.db.useExternalSecret" .) "true" -}}
{{- .Values.db.external.secretName -}}
{{- else -}}
{{- include "twenty.dbUrl.secretName" . -}}
{{- end -}}
{{- end -}}
{{/* Database password secret key */}}
{{- define "twenty.dbPassword.secretKey" -}}
{{- if eq (include "twenty.db.useExternalSecret" .) "true" -}}
{{- .Values.db.external.passwordKey -}}
{{- else if .Values.db.enabled -}}
appPassword
{{- else -}}
password
{{- end -}}
{{- end -}}
{{/* Database URL template for external secret (will be evaluated at runtime) */}}
{{- define "twenty.dbUrl.template" -}}
{{- if eq (include "twenty.db.useExternalSecret" .) "true" -}}
{{- $scheme := "postgres" -}}
{{- $host := .Values.db.external.host -}}
{{- $port := .Values.db.external.port | default 5432 -}}
{{- $user := .Values.db.external.user | default "postgres" -}}
{{- $db := .Values.db.external.database | default "twenty" -}}
{{- $qs := ternary "?sslmode=require" "" (eq .Values.db.external.ssl true) -}}
{{- printf "%s://%s:$(DB_PASSWORD)@%s:%v/%s%s" $scheme $user $host $port $db $qs -}}
{{- end -}}
{{- end -}}
{{/* Check if using external secret for redis password */}}
{{- define "twenty.redis.useExternalSecret" -}}
{{- if and (not .Values.redisInternal.enabled) .Values.redis.external.secretName .Values.redis.external.passwordKey -}}
true
{{- else -}}
false
{{- end -}}
{{- end -}}
{{/* Compose Redis URL */}}
{{- define "twenty.redisUrl" -}}
{{- if .Values.server.env.REDIS_URL -}}
{{- .Values.server.env.REDIS_URL -}}
{{- else if .Values.redisInternal.enabled -}}
{{- $host := printf "%s-redis" (include "twenty.fullname" .) -}}
{{- printf "redis://%s.%s.svc.cluster.local:6379" $host (include "twenty.namespace" .) -}}
{{- else -}}
{{- $host := .Values.redis.external.host | default "redis" -}}
{{- $port := .Values.redis.external.port | default 6379 -}}
{{- if or (eq (include "twenty.redis.useExternalSecret" .) "true") (.Values.redis.external.password) -}}
{{- $auth := ":$(REDIS_PASSWORD)@" -}}
{{- printf "redis://%s%s:%v" $auth $host $port -}}
{{- else -}}
{{- printf "redis://%s:%v" $host $port -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/* Compose Server URL from override, ingress, or service */}}
{{- define "twenty.serverUrl" -}}
{{- if .Values.server.env.SERVER_URL -}}
{{- .Values.server.env.SERVER_URL -}}
{{- else if and .Values.server.ingress.enabled (gt (len .Values.server.ingress.hosts) 0) -}}
{{- $host := (index .Values.server.ingress.hosts 0).host -}}
{{- $tls := gt (len .Values.server.ingress.tls) 0 -}}
{{- $scheme := ternary "https" "http" $tls -}}
{{- $port := ternary 443 80 $tls -}}
{{- printf "%s://%s:%v" $scheme $host $port -}}
{{- else -}}
{{- $svc := printf "%s-server" (include "twenty.fullname" .) -}}
{{- $ns := include "twenty.namespace" . -}}
{{- $port := .Values.server.service.port | default 3000 -}}
{{- printf "http://%s.%s.svc.cluster.local:%v" $svc $ns $port -}}
{{- end -}}
{{- end -}}
{{/* Tokens secret name */}}
{{- define "twenty.secret.tokens.name" -}}
{{- .Values.secrets.tokens.name | default "tokens" -}}
{{- end -}}
{{/* Access token value: reuse existing secret if present, else provided value, else generated */}}
{{- define "twenty.secret.tokens.access" -}}
{{- $name := include "twenty.secret.tokens.name" . -}}
{{- $ns := include "twenty.namespace" . -}}
{{- $existing := lookup "v1" "Secret" $ns $name -}}
{{- if and $existing $existing.data.accessToken -}}
{{- b64dec $existing.data.accessToken -}}
{{- else if .Values.secrets.tokens.accessToken -}}
{{- .Values.secrets.tokens.accessToken -}}
{{- else -}}
{{- randAlphaNum 32 -}}
{{- end -}}
{{- end -}}
{{/* Server container port */}}
{{- define "twenty.server.containerPort" -}}
{{- .Values.server.service.port | default 3000 -}}
{{- end -}}
{{/* Storage type: prefer top-level storage.type, else legacy server.env.STORAGE_TYPE, else local */}}
{{- define "twenty.storageType" -}}
{{- if .Values.storage.type -}}
{{- .Values.storage.type -}}
{{- else if .Values.server.env.STORAGE_TYPE -}}
{{- .Values.server.env.STORAGE_TYPE -}}
{{- else -}}
local
{{- end -}}
{{- end -}}
{{/* Additional storage env vars (e.g., S3) */}}
{{- define "twenty.storageEnv" -}}
{{- if eq (include "twenty.storageType" .) "s3" -}}
{{- with .Values.storage.s3.bucket }}
- name: STORAGE_S3_NAME
value: {{ . | quote }}
{{- end }}
{{- with .Values.storage.s3.region }}
- name: STORAGE_S3_REGION
value: {{ . | quote }}
{{- end }}
{{- with .Values.storage.s3.endpoint }}
- name: STORAGE_S3_ENDPOINT
value: {{ . | quote }}
{{- end }}
{{- if and .Values.storage.s3.secretName .Values.storage.s3.accessKeyIdKey }}
- name: STORAGE_S3_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: {{ .Values.storage.s3.secretName | quote }}
key: {{ .Values.storage.s3.accessKeyIdKey | quote }}
{{- else }}
{{- with .Values.storage.s3.accessKeyId }}
- name: STORAGE_S3_ACCESS_KEY_ID
value: {{ . | quote }}
{{- end }}
{{- end }}
{{- if and .Values.storage.s3.secretName .Values.storage.s3.secretAccessKeyKey }}
- name: STORAGE_S3_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: {{ .Values.storage.s3.secretName | quote }}
key: {{ .Values.storage.s3.secretAccessKeyKey | quote }}
{{- else }}
{{- with .Values.storage.s3.secretAccessKey }}
- name: STORAGE_S3_SECRET_ACCESS_KEY
value: {{ . | quote }}
{{- end }}
{{- end }}
{{- end -}}
{{- end -}}

View File

@@ -0,0 +1,62 @@
{{- if .Values.db.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "twenty.fullname" . }}-db
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: db
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/component: db
strategy:
type: Recreate
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: db
spec:
volumes:
{{- if .Values.db.internal.persistence.enabled }}
- name: db-data
persistentVolumeClaim:
claimName: {{ if .Values.db.internal.persistence.existingClaim }}{{ .Values.db.internal.persistence.existingClaim }}{{ else }}{{ include "twenty.fullname" . }}-db{{ end }}
{{- end }}
containers:
- name: db
image: {{ .Values.db.internal.image.repository }}:{{ .Values.db.internal.image.tag }}
imagePullPolicy: {{ .Values.db.internal.image.pullPolicy | default "IfNotPresent" }}
env:
- name: PGUSER_SUPERUSER
valueFrom:
secretKeyRef:
name: {{ include "twenty.fullname" . }}-db-superuser
key: username
- name: PGPASSWORD_SUPERUSER
valueFrom:
secretKeyRef:
name: {{ include "twenty.fullname" . }}-db-superuser
key: password
- name: SPILO_PROVIDER
value: {{ .Values.db.internal.env.SPILO_PROVIDER | quote }}
- name: ALLOW_NOSSL
value: {{ .Values.db.internal.env.ALLOW_NOSSL | quote }}
ports:
- containerPort: 5432
name: tcp
protocol: TCP
resources:
{{- toYaml .Values.db.internal.resources | nindent 12 }}
{{- if .Values.db.internal.persistence.enabled }}
volumeMounts:
- name: db-data
mountPath: /home/postgres/pgdata
{{- end }}
{{- end }}

View File

@@ -0,0 +1,57 @@
{{- if .Values.redisInternal.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "twenty.fullname" . }}-redis
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: redis
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/component: redis
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: redis
spec:
containers:
- name: redis
image: {{ .Values.redisInternal.image.repository }}:{{ .Values.redisInternal.image.tag }}
imagePullPolicy: {{ default "IfNotPresent" .Values.redisInternal.image.pullPolicy }}
command:
- redis-stack-server
args:
- "--port"
- {{ .Values.redisInternal.service.port | quote }}
- "--maxmemory-policy"
- "noeviction"
ports:
- containerPort: {{ .Values.redisInternal.service.port }}
name: redis
protocol: TCP
resources:
{{- toYaml .Values.redisInternal.resources | nindent 12 }}
volumeMounts:
- name: redis-data
mountPath: /data
volumes:
- name: redis-data
{{- if .Values.redisInternal.persistence.enabled }}
persistentVolumeClaim:
claimName: {{ .Values.redisInternal.persistence.existingClaim | default (printf "%s-redis" (include "twenty.fullname" .)) }}
{{- else }}
emptyDir: {}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,219 @@
{{- if .Values.server.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "twenty.fullname" . }}-server
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: server
spec:
replicas: {{ .Values.server.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/component: server
strategy:
{{- if or .Values.server.dockerDataPersistence.enabled .Values.server.persistence.enabled }}
type: Recreate
{{- else }}
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
{{- end }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: server
spec:
securityContext:
{{- toYaml .Values.securityContext | nindent 8 }}
{{- with .Values.server.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.server.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.server.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.server.dnsPolicy }}
dnsPolicy: {{ . }}
{{- end }}
{{- with .Values.server.dnsConfig }}
dnsConfig:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if .Values.server.dockerDataPersistence.enabled }}
- name: docker-data
persistentVolumeClaim:
claimName: {{ if .Values.server.dockerDataPersistence.existingClaim }}{{ .Values.server.dockerDataPersistence.existingClaim }}{{ else }}{{ include "twenty.fullname" . }}-docker-data{{ end }}
{{- end }}
{{- if .Values.server.persistence.enabled }}
- name: server-data
persistentVolumeClaim:
claimName: {{ if .Values.server.persistence.existingClaim }}{{ .Values.server.persistence.existingClaim }}{{ else }}{{ include "twenty.fullname" . }}-server{{ end }}
{{- end }}
initContainers:
- name: wait-for-db
image: postgres:16-alpine
command:
- sh
- -c
- |
until pg_isready -h {{ if .Values.db.enabled }}{{ include "twenty.fullname" . }}-db{{ else }}{{ .Values.db.external.host }}{{ end }} \
-p {{ if .Values.db.enabled }}5432{{ else }}{{ .Values.db.external.port | default 5432 }}{{ end }} \
-U postgres; do
echo "Waiting for database socket..."
sleep 2
done
echo "Database socket is ready!"
{{- if .Values.db.enabled }}
- name: ensure-database-exists
image: postgres:16-alpine
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: {{ include "twenty.fullname" . }}-db-superuser
key: password
- name: APP_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "twenty.fullname" . }}-db-url
key: appPassword
command:
- sh
- -c
- |
DBNAME={{ .Values.db.internal.database | default "twenty" }}
APP_USER={{ .Values.db.internal.appUser | default "twenty_app_user" }}
export PGPASSWORD
export APP_PASSWORD
echo "Creating database ${DBNAME} if it doesn't exist..."
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d postgres -v db="${DBNAME}" -Atc "SELECT 1 FROM pg_database WHERE datname = :'db'" | grep -q 1 || \
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d postgres -v db="${DBNAME}" -c 'CREATE DATABASE :"db";'
echo "Creating app user ${APP_USER} if it doesn't exist..."
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d postgres -v app_user="${APP_USER}" -v app_password="${APP_PASSWORD}" <<'EOSQL'
DO
$do$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = :'app_user') THEN
EXECUTE format('CREATE USER %I WITH PASSWORD %L', :'app_user', :'app_password');
END IF;
END
$do$;
EOSQL
echo "Creating core schema and granting permissions..."
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v app_user="${APP_USER}" -c 'CREATE SCHEMA IF NOT EXISTS core'
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v db="${DBNAME}" -v app_user="${APP_USER}" -c 'GRANT ALL PRIVILEGES ON DATABASE :"db" TO :"app_user";'
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v app_user="${APP_USER}" -c 'GRANT ALL PRIVILEGES ON SCHEMA core TO :"app_user";'
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v app_user="${APP_USER}" -c 'GRANT ALL PRIVILEGES ON SCHEMA public TO :"app_user";'
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v app_user="${APP_USER}" -c 'GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA core TO :"app_user";'
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v app_user="${APP_USER}" -c 'GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO :"app_user";'
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v app_user="${APP_USER}" -c 'ALTER DEFAULT PRIVILEGES IN SCHEMA core GRANT ALL ON TABLES TO :"app_user";'
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v app_user="${APP_USER}" -c 'ALTER DEFAULT PRIVILEGES IN SCHEMA core GRANT ALL ON SEQUENCES TO :"app_user";'
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v app_user="${APP_USER}" -c 'ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO :"app_user";'
psql -h {{ include "twenty.fullname" . }}-db -p 5432 -U postgres -d "${DBNAME}" -v app_user="${APP_USER}" -c 'ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO :"app_user";'
echo "Database ${DBNAME} is ready."
{{- end }}
containers:
- name: server
{{- $img := include "twenty.server.image" . }}
image: {{ include "twenty.image.repository" $img }}:{{ include "twenty.image.tag" $img }}
imagePullPolicy: {{ include "twenty.image.pullPolicy" $img }}
env:
- name: SERVER_URL
value: {{ include "twenty.serverUrl" . | quote }}
{{- if eq (include "twenty.db.useExternalSecret" .) "true" }}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "twenty.dbPassword.secretName" . }}
key: {{ include "twenty.dbPassword.secretKey" . }}
- name: PG_DATABASE_URL
value: {{ include "twenty.dbUrl.template" . | quote }}
{{- else }}
- name: PG_DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "twenty.dbUrl.secretName" . }}
key: url
{{- end }}
{{- if eq (include "twenty.redis.useExternalSecret" .) "true" }}
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.redis.external.secretName }}
key: {{ .Values.redis.external.passwordKey }}
{{- else if .Values.redis.external.password }}
- name: REDIS_PASSWORD
value: {{ .Values.redis.external.password | quote }}
{{- end }}
- name: REDIS_URL
value: {{ include "twenty.redisUrl" . | quote }}
- name: SIGN_IN_PREFILLED
value: {{ .Values.server.env.SIGN_IN_PREFILLED | quote }}
- name: STORAGE_TYPE
value: {{ include "twenty.storageType" . | quote }}
- name: ACCESS_TOKEN_EXPIRES_IN
value: {{ .Values.server.env.ACCESS_TOKEN_EXPIRES_IN | quote }}
- name: LOGIN_TOKEN_EXPIRES_IN
value: {{ .Values.server.env.LOGIN_TOKEN_EXPIRES_IN | quote }}
- name: APP_SECRET
valueFrom:
secretKeyRef:
name: {{ include "twenty.secret.tokens.name" . }}
key: accessToken
{{- $storageEnv := (include "twenty.storageEnv" .) }}
{{- if $storageEnv }}
{{- $storageEnv | nindent 12 }}
{{- end }}
{{- with .Values.server.extraEnv }}
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- name: http-tcp
containerPort: {{ include "twenty.server.containerPort" . }}
protocol: TCP
{{- with .Values.server.startupProbe }}
startupProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.server.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.server.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.server.resources | nindent 12 }}
volumeMounts:
{{- if .Values.server.dockerDataPersistence.enabled }}
- name: docker-data
mountPath: /app/docker-data
{{- end }}
{{- if .Values.server.persistence.enabled }}
- name: server-data
mountPath: /app/packages/twenty-server/.local-storage
{{- end }}
{{- with .Values.server.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if .Values.server.stdin }}
stdin: {{ .Values.server.stdin }}
{{- end }}
{{- if .Values.server.tty }}
tty: {{ .Values.server.tty }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,119 @@
{{- if .Values.worker.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "twenty.fullname" . }}-worker
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: worker
spec:
replicas: {{ .Values.worker.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/component: worker
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: worker
spec:
securityContext:
{{- toYaml .Values.securityContext | nindent 8 }}
{{- with .Values.worker.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.worker.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.worker.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.worker.dnsPolicy }}
dnsPolicy: {{ . }}
{{- end }}
{{- with .Values.worker.dnsConfig }}
dnsConfig:
{{- toYaml . | nindent 8 }}
{{- end }}
initContainers:
- name: wait-for-db
image: postgres:16-alpine
command:
- sh
- -c
- |
until pg_isready -h {{ if .Values.db.enabled }}{{ include "twenty.fullname" . }}-db{{ else }}{{ .Values.db.external.host }}{{ end }} \
-p {{ if .Values.db.enabled }}5432{{ else }}{{ .Values.db.external.port | default 5432 }}{{ end }} \
-U postgres; do
echo "Waiting for database socket..."
sleep 2
done
echo "Database socket is ready!"
containers:
- name: worker
{{- $img := include "twenty.worker.image" . }}
image: {{ include "twenty.image.repository" $img }}:{{ include "twenty.image.tag" $img }}
imagePullPolicy: {{ include "twenty.image.pullPolicy" $img }}
command:
{{- toYaml .Values.worker.command | nindent 12 }}
env:
- name: SERVER_URL
value: {{ include "twenty.serverUrl" . | quote }}
{{- if eq (include "twenty.db.useExternalSecret" .) "true" }}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "twenty.dbPassword.secretName" . }}
key: {{ include "twenty.dbPassword.secretKey" . }}
- name: PG_DATABASE_URL
value: {{ include "twenty.dbUrl.template" . | quote }}
{{- else }}
- name: PG_DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "twenty.dbUrl.secretName" . }}
key: url
{{- end }}
{{- if eq (include "twenty.redis.useExternalSecret" .) "true" }}
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.redis.external.secretName }}
key: {{ .Values.redis.external.passwordKey }}
{{- else if .Values.redis.external.password }}
- name: REDIS_PASSWORD
value: {{ .Values.redis.external.password | quote }}
{{- end }}
- name: REDIS_URL
value: {{ include "twenty.redisUrl" . | quote }}
- name: STORAGE_TYPE
value: {{ include "twenty.storageType" . | quote }}
- name: APP_SECRET
valueFrom:
secretKeyRef:
name: {{ include "twenty.secret.tokens.name" . }}
key: accessToken
{{- with .Values.worker.extraEnv }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- $storageEnv := (include "twenty.storageEnv" .) }}
{{- if $storageEnv }}
{{- $storageEnv | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.worker.resources | nindent 12 }}
stdin: {{ default true .Values.worker.stdin }}
tty: {{ default true .Values.worker.tty }}
{{- end }}

View File

@@ -0,0 +1,47 @@
{{- if and .Values.server.enabled .Values.server.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "twenty.fullname" . }}
namespace: {{ include "twenty.namespace" . }}
annotations:
{{- if .Values.server.ingress.acme }}
cert-manager.io/cluster-issuer: "letsencrypt-prod"
{{- end }}
{{- with .Values.server.ingress.annotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.server.ingress.className }}
ingressClassName: {{ .Values.server.ingress.className }}
{{- end }}
rules:
{{- range .Values.server.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- if .paths }}
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "twenty.fullname" $ }}-server
port:
name: http-tcp
{{- end }}
{{- else }}
- path: /
pathType: Prefix
backend:
service:
name: {{ include "twenty.fullname" $ }}-server
port:
name: http-tcp
{{- end }}
{{- end }}
{{- if .Values.server.ingress.tls }}
tls:
{{- toYaml .Values.server.ingress.tls | nindent 4 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,20 @@
{{- if and .Values.db.enabled .Values.db.internal.persistence.enabled (not .Values.db.internal.persistence.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "twenty.fullname" . }}-db
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: db
spec:
accessModes:
{{ toYaml .Values.db.internal.persistence.accessModes | nindent 4 }}
resources:
requests:
storage: {{ .Values.db.internal.persistence.size }}
{{- if .Values.db.internal.persistence.storageClass }}
storageClassName: {{ .Values.db.internal.persistence.storageClass }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,20 @@
{{- if and .Values.server.enabled .Values.server.dockerDataPersistence.enabled (not .Values.server.dockerDataPersistence.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "twenty.fullname" . }}-docker-data
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: server
spec:
accessModes:
{{ toYaml .Values.server.dockerDataPersistence.accessModes | nindent 4 }}
resources:
requests:
storage: {{ .Values.server.dockerDataPersistence.size }}
{{- if .Values.server.dockerDataPersistence.storageClass }}
storageClassName: {{ .Values.server.dockerDataPersistence.storageClass }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,20 @@
{{- if and .Values.redisInternal.enabled .Values.redisInternal.persistence.enabled (not .Values.redisInternal.persistence.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "twenty.fullname" . }}-redis
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: redis
spec:
accessModes:
{{ toYaml .Values.redisInternal.persistence.accessModes | nindent 4 }}
resources:
requests:
storage: {{ .Values.redisInternal.persistence.size }}
{{- if .Values.redisInternal.persistence.storageClass }}
storageClassName: {{ .Values.redisInternal.persistence.storageClass }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,20 @@
{{- if and .Values.server.enabled .Values.server.persistence.enabled (not .Values.server.persistence.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "twenty.fullname" . }}-server
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: server
spec:
accessModes:
{{ toYaml .Values.server.persistence.accessModes | nindent 4 }}
resources:
requests:
storage: {{ .Values.server.persistence.size }}
{{- if .Values.server.persistence.storageClass }}
storageClassName: {{ .Values.server.persistence.storageClass }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,24 @@
{{- if .Values.db.enabled }}
{{- $ns := include "twenty.namespace" . -}}
{{- $existing := lookup "v1" "Secret" $ns (printf "%s-db-superuser" (include "twenty.fullname" .)) -}}
{{- $user := .Values.db.internal.env.PGUSER_SUPERUSER | default "postgres" -}}
{{- $pass := .Values.db.internal.env.PGPASSWORD_SUPERUSER | default "" -}}
{{- if and $existing $existing.data.password -}}
{{- $pass = (b64dec $existing.data.password) -}}
{{- else if eq $pass "" -}}
{{- $pass = randAlphaNum 32 -}}
{{- end }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "twenty.fullname" . }}-db-superuser
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: db
type: Opaque
stringData:
username: {{ $user | quote }}
password: {{ $pass | quote }}
{{- end }}

View File

@@ -0,0 +1,45 @@
{{- $secretName := printf "%s-db-url" (include "twenty.fullname" .) -}}
{{- if .Values.db.enabled -}}
{{- $existingSecret := lookup "v1" "Secret" (include "twenty.namespace" .) $secretName -}}
{{- $appPassword := "" -}}
{{- if $existingSecret -}}
{{- $appPassword = index $existingSecret.data "appPassword" | b64dec -}}
{{- else -}}
{{- $appPassword = .Values.db.internal.appPassword | default (randAlphaNum 32) -}}
{{- end -}}
{{- $appUser := .Values.db.internal.appUser | default "twenty_app_user" -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ $secretName }}
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: server
type: Opaque
stringData:
url: {{ printf "postgres://%s:%s@%s-db.%s.svc.cluster.local/%s" (urlquery $appUser) (urlquery $appPassword) (include "twenty.fullname" .) (include "twenty.namespace" .) (.Values.db.internal.database | default "twenty") | quote }}
appPassword: {{ $appPassword | quote }}
{{- else if not .Values.db.external.secretName -}}
{{- $scheme := "postgres" -}}
{{- $host := .Values.db.external.host -}}
{{- $port := .Values.db.external.port | default 5432 -}}
{{- $user := .Values.db.external.user | default "postgres" -}}
{{- $pass := .Values.db.external.password | default "" -}}
{{- $db := .Values.db.external.database | default "twenty" -}}
{{- $qs := ternary "?sslmode=require" "" (eq .Values.db.external.ssl true) -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ $secretName }}
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: server
type: Opaque
stringData:
url: {{ printf "%s://%s:%s@%s:%v/%s%s" $scheme (urlquery $user) (urlquery $pass) $host $port $db $qs | quote }}
password: {{ $pass | quote }}
{{- end -}}

View File

@@ -0,0 +1,13 @@
{{- if .Values.secrets.tokens.create }}
apiVersion: v1
kind: Secret
metadata:
name: {{ .Values.secrets.tokens.name | default "tokens" }}
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: tokens
stringData:
accessToken: {{ include "twenty.secret.tokens.access" . | quote }}
{{- end }}

View File

@@ -0,0 +1,21 @@
{{- if .Values.db.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "twenty.fullname" . }}-db
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: db
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/component: db
ports:
- port: 5432
targetPort: 5432
protocol: TCP
name: postgres
{{- end }}

View File

@@ -0,0 +1,21 @@
{{- if .Values.redisInternal.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "twenty.fullname" . }}-redis
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: redis
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/component: redis
ports:
- port: {{ .Values.redisInternal.service.port }}
targetPort: {{ .Values.redisInternal.service.port }}
protocol: TCP
name: redis
{{- end }}

View File

@@ -0,0 +1,21 @@
{{- if .Values.server.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "twenty.fullname" . }}-server
namespace: {{ include "twenty.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: server
spec:
type: {{ .Values.server.service.type | default "ClusterIP" }}
selector:
app.kubernetes.io/name: {{ include "twenty.name" . }}
app.kubernetes.io/component: server
ports:
- name: http-tcp
port: {{ .Values.server.service.port }}
targetPort: {{ include "twenty.server.containerPort" . }}
protocol: TCP
{{- end }}