From 609d66f2630333de95ab150d02895503e8c47a3b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:02:38 +0000 Subject: [PATCH 1/2] Initial plan From ebcc2319a591d10097c6acad1a1883d3101eb03c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:11:58 +0000 Subject: [PATCH 2/2] feat: add helm chart for kutt with postgresql, mariadb, and redis support Co-authored-by: fredleger <2778741+fredleger@users.noreply.github.com> Agent-Logs-Url: https://github.com/webofmars/kutt/sessions/3ed4719e-31c9-48c5-8d84-f7ef7be8eea1 --- helm/kutt/.helmignore | 23 ++ helm/kutt/Chart.yaml | 30 ++ helm/kutt/README.md | 133 ++++++ helm/kutt/ci/mariadb-values.yaml | 17 + helm/kutt/ci/postgresql-values.yaml | 16 + helm/kutt/ci/sqlite-values.yaml | 10 + helm/kutt/templates/NOTES.txt | 44 ++ helm/kutt/templates/_helpers.tpl | 237 +++++++++++ helm/kutt/templates/configmap.yaml | 75 ++++ helm/kutt/templates/deployment.yaml | 121 ++++++ helm/kutt/templates/hpa.yaml | 32 ++ helm/kutt/templates/ingress.yaml | 43 ++ helm/kutt/templates/pvc.yaml | 17 + helm/kutt/templates/secret.yaml | 24 ++ helm/kutt/templates/service.yaml | 15 + helm/kutt/templates/serviceaccount.yaml | 13 + .../kutt/templates/tests/test-connection.yaml | 15 + helm/kutt/values.yaml | 378 ++++++++++++++++++ 18 files changed, 1243 insertions(+) create mode 100644 helm/kutt/.helmignore create mode 100644 helm/kutt/Chart.yaml create mode 100644 helm/kutt/README.md create mode 100644 helm/kutt/ci/mariadb-values.yaml create mode 100644 helm/kutt/ci/postgresql-values.yaml create mode 100644 helm/kutt/ci/sqlite-values.yaml create mode 100644 helm/kutt/templates/NOTES.txt create mode 100644 helm/kutt/templates/_helpers.tpl create mode 100644 helm/kutt/templates/configmap.yaml create mode 100644 helm/kutt/templates/deployment.yaml create mode 100644 helm/kutt/templates/hpa.yaml create mode 100644 helm/kutt/templates/ingress.yaml create mode 100644 helm/kutt/templates/pvc.yaml create mode 100644 helm/kutt/templates/secret.yaml create mode 100644 helm/kutt/templates/service.yaml create mode 100644 helm/kutt/templates/serviceaccount.yaml create mode 100644 helm/kutt/templates/tests/test-connection.yaml create mode 100644 helm/kutt/values.yaml diff --git a/helm/kutt/.helmignore b/helm/kutt/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/helm/kutt/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/kutt/Chart.yaml b/helm/kutt/Chart.yaml new file mode 100644 index 000000000..5a85be9bf --- /dev/null +++ b/helm/kutt/Chart.yaml @@ -0,0 +1,30 @@ +apiVersion: v2 +name: kutt +description: A Helm chart for Kutt - Free Modern URL Shortener +type: application +version: 0.1.0 +appVersion: "3.2.3" +keywords: + - kutt + - url-shortener + - link-shortener +icon: https://kutt.it/images/logo.svg +home: https://kutt.it +sources: + - https://github.com/webofmars/kutt +maintainers: + - name: webofmars + url: https://github.com/webofmars +dependencies: + - name: postgresql + version: ">=16.0.0 <17.0.0" + repository: https://charts.bitnami.com/bitnami + condition: postgresql.enabled + - name: mariadb + version: ">=20.0.0 <21.0.0" + repository: https://charts.bitnami.com/bitnami + condition: mariadb.enabled + - name: redis + version: ">=20.0.0 <21.0.0" + repository: https://charts.bitnami.com/bitnami + condition: redis.enabled diff --git a/helm/kutt/README.md b/helm/kutt/README.md new file mode 100644 index 000000000..330706f6a --- /dev/null +++ b/helm/kutt/README.md @@ -0,0 +1,133 @@ +# kutt + +A Helm chart for [Kutt](https://kutt.it) – a free, modern URL shortener. + +## Prerequisites + +- Kubernetes 1.23+ +- Helm 3.10+ +- PV provisioner support in the cluster (for SQLite persistence) + +## Installing the Chart + +Add the repository and install the chart: + +```bash +helm install my-kutt ./helm/kutt \ + --set kutt.auth.jwtSecret=$(openssl rand -hex 32) \ + --set kutt.defaultDomain=kutt.example.com +``` + +## Uninstalling the Chart + +```bash +helm uninstall my-kutt +``` + +## Configuration + +The following table lists the most important configurable parameters. See [values.yaml](values.yaml) for the complete list. + +### Application + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `kutt.defaultDomain` | The domain Kutt is hosted on | `""` | +| `kutt.auth.jwtSecret` | **Required.** JWT encryption secret | `""` | +| `kutt.auth.existingSecret` | Existing secret with `JWT_SECRET` key | `""` | +| `kutt.registration.disallowRegistration` | Disable user registration | `true` | +| `kutt.registration.disallowAnonymousLinks` | Disable anonymous link creation | `true` | + +### Database + +By default Kutt uses SQLite. Enable one of the sub-charts for a production database: + +#### PostgreSQL + +```yaml +postgresql: + enabled: true + auth: + database: kutt + username: kutt + password: changeme +``` + +#### MariaDB + +```yaml +mariadb: + enabled: true + auth: + database: kutt + username: kutt + password: changeme + rootPassword: changeme +``` + +#### External database + +```yaml +kutt: + db: + client: pg # or mysql2 + host: my-db-host + port: "5432" + name: kutt + user: kutt + password: changeme +``` + +### Redis + +Redis is optional but recommended for rate-limiting and caching: + +```yaml +redis: + enabled: true +``` + +Or configure an external Redis: + +```yaml +kutt: + redis: + enabled: true + host: my-redis-host + port: 6379 +``` + +### Ingress + +```yaml +ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + hosts: + - host: kutt.example.com + paths: + - path: / + pathType: ImplementationSpecific + tls: + - secretName: kutt-tls + hosts: + - kutt.example.com +``` + +## Dependencies + +This chart has the following optional sub-chart dependencies managed via [Bitnami](https://bitnami.com/): + +| Chart | Version | Condition | +|-------|---------|-----------| +| `bitnami/postgresql` | `>=16.0.0` | `postgresql.enabled` | +| `bitnami/mariadb` | `>=20.0.0` | `mariadb.enabled` | +| `bitnami/redis` | `>=20.0.0` | `redis.enabled` | + +To download dependencies before installing: + +```bash +helm dependency update helm/kutt +``` diff --git a/helm/kutt/ci/mariadb-values.yaml b/helm/kutt/ci/mariadb-values.yaml new file mode 100644 index 000000000..0675b085c --- /dev/null +++ b/helm/kutt/ci/mariadb-values.yaml @@ -0,0 +1,17 @@ +kutt: + auth: + jwtSecret: "test-jwt-secret-for-ci" + defaultDomain: "kutt.example.com" +mariadb: + enabled: true + auth: + database: kutt + username: kutt + password: "testpassword" + rootPassword: "rootpassword" +redis: + enabled: true + auth: + enabled: false +persistence: + enabled: false diff --git a/helm/kutt/ci/postgresql-values.yaml b/helm/kutt/ci/postgresql-values.yaml new file mode 100644 index 000000000..fc4ba0682 --- /dev/null +++ b/helm/kutt/ci/postgresql-values.yaml @@ -0,0 +1,16 @@ +kutt: + auth: + jwtSecret: "test-jwt-secret-for-ci" + defaultDomain: "kutt.example.com" +postgresql: + enabled: true + auth: + database: kutt + username: kutt + password: "testpassword" +redis: + enabled: true + auth: + enabled: false +persistence: + enabled: false diff --git a/helm/kutt/ci/sqlite-values.yaml b/helm/kutt/ci/sqlite-values.yaml new file mode 100644 index 000000000..83ac0b63c --- /dev/null +++ b/helm/kutt/ci/sqlite-values.yaml @@ -0,0 +1,10 @@ +kutt: + auth: + jwtSecret: "test-jwt-secret-for-ci" + defaultDomain: "kutt.example.com" + registration: + disallowRegistration: false + disallowAnonymousLinks: false +persistence: + enabled: true + size: 500Mi diff --git a/helm/kutt/templates/NOTES.txt b/helm/kutt/templates/NOTES.txt new file mode 100644 index 000000000..7faf06c1c --- /dev/null +++ b/helm/kutt/templates/NOTES.txt @@ -0,0 +1,44 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "kutt.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "kutt.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "kutt.fullname" . }} --template "{{"{{range (index .status.loadBalancer.ingress 0)}}{{.}}{{end}}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "kutt.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:3000 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 3000:$CONTAINER_PORT +{{- end }} + +2. Database backend: {{ include "kutt.dbClient" . }} +{{- if .Values.postgresql.enabled }} + PostgreSQL is deployed as a sub-chart ({{ .Release.Name }}-postgresql). +{{- else if .Values.mariadb.enabled }} + MariaDB is deployed as a sub-chart ({{ .Release.Name }}-mariadb). +{{- else }} + SQLite is used as the database. +{{- end }} + +3. Redis caching: {{ include "kutt.redisEnabled" . }} +{{- if .Values.redis.enabled }} + Redis is deployed as a sub-chart ({{ .Release.Name }}-redis-master). +{{- end }} + +{{- if not .Values.kutt.auth.jwtSecret }} +{{- if not .Values.kutt.auth.existingSecret }} + +WARNING: JWT_SECRET is not set! Please set kutt.auth.jwtSecret or kutt.auth.existingSecret + to a long random string before exposing this instance to traffic. +{{- end }} +{{- end }} diff --git a/helm/kutt/templates/_helpers.tpl b/helm/kutt/templates/_helpers.tpl new file mode 100644 index 000000000..3d444342e --- /dev/null +++ b/helm/kutt/templates/_helpers.tpl @@ -0,0 +1,237 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "kutt.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "kutt.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "kutt.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "kutt.labels" -}} +helm.sh/chart: {{ include "kutt.chart" . }} +{{ include "kutt.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "kutt.selectorLabels" -}} +app.kubernetes.io/name: {{ include "kutt.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "kutt.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "kutt.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Determine the database client to use. +Precedence: explicit kutt.db.client > postgresql.enabled > mariadb.enabled > sqlite (default) +*/}} +{{- define "kutt.dbClient" -}} +{{- if .Values.postgresql.enabled }} +{{- "pg" }} +{{- else if .Values.mariadb.enabled }} +{{- "mysql2" }} +{{- else }} +{{- .Values.kutt.db.client | default "better-sqlite3" }} +{{- end }} +{{- end }} + +{{/* +Determine the database host. +*/}} +{{- define "kutt.dbHost" -}} +{{- if .Values.postgresql.enabled }} +{{- printf "%s-postgresql" .Release.Name }} +{{- else if .Values.mariadb.enabled }} +{{- printf "%s-mariadb" .Release.Name }} +{{- else }} +{{- .Values.kutt.db.host | default "" }} +{{- end }} +{{- end }} + +{{/* +Determine the database port. +*/}} +{{- define "kutt.dbPort" -}} +{{- if .Values.postgresql.enabled }} +{{- "5432" }} +{{- else if .Values.mariadb.enabled }} +{{- "3306" }} +{{- else }} +{{- .Values.kutt.db.port | default "" }} +{{- end }} +{{- end }} + +{{/* +Determine the database name. +*/}} +{{- define "kutt.dbName" -}} +{{- if .Values.postgresql.enabled }} +{{- .Values.postgresql.auth.database | default "kutt" }} +{{- else if .Values.mariadb.enabled }} +{{- .Values.mariadb.auth.database | default "kutt" }} +{{- else }} +{{- .Values.kutt.db.name | default "kutt" }} +{{- end }} +{{- end }} + +{{/* +Determine the database user. +*/}} +{{- define "kutt.dbUser" -}} +{{- if .Values.postgresql.enabled }} +{{- .Values.postgresql.auth.username | default "kutt" }} +{{- else if .Values.mariadb.enabled }} +{{- .Values.mariadb.auth.username | default "kutt" }} +{{- else }} +{{- .Values.kutt.db.user | default "" }} +{{- end }} +{{- end }} + +{{/* +Name of the secret holding the DB password. +When using sub-charts, each sub-chart creates its own secret; reference it here. +Otherwise fall back to the kutt managed secret. +*/}} +{{- define "kutt.dbSecretName" -}} +{{- if .Values.kutt.db.existingSecret }} +{{- .Values.kutt.db.existingSecret }} +{{- else if .Values.postgresql.enabled }} +{{- printf "%s-postgresql" .Release.Name }} +{{- else if .Values.mariadb.enabled }} +{{- printf "%s-mariadb" .Release.Name }} +{{- else }} +{{- include "kutt.fullname" . }} +{{- end }} +{{- end }} + +{{/* +Key of the DB password in the secret. +*/}} +{{- define "kutt.dbSecretKey" -}} +{{- if .Values.postgresql.enabled }} +{{- "password" }} +{{- else if .Values.mariadb.enabled }} +{{- "mariadb-password" }} +{{- else }} +{{- "DB_PASSWORD" }} +{{- end }} +{{- end }} + +{{/* +Determine whether Redis is enabled (sub-chart or explicit). +*/}} +{{- define "kutt.redisEnabled" -}} +{{- if or .Values.redis.enabled .Values.kutt.redis.enabled }} +{{- "true" }} +{{- else }} +{{- "false" }} +{{- end }} +{{- end }} + +{{/* +Determine the Redis host. +*/}} +{{- define "kutt.redisHost" -}} +{{- if .Values.redis.enabled }} +{{- printf "%s-redis-master" .Release.Name }} +{{- else }} +{{- .Values.kutt.redis.host | default "127.0.0.1" }} +{{- end }} +{{- end }} + +{{/* +Name of the secret holding the Redis password. +*/}} +{{- define "kutt.redisSecretName" -}} +{{- if .Values.kutt.redis.existingSecret }} +{{- .Values.kutt.redis.existingSecret }} +{{- else if .Values.redis.enabled }} +{{- printf "%s-redis" .Release.Name }} +{{- else }} +{{- include "kutt.fullname" . }} +{{- end }} +{{- end }} + +{{/* +Key of the Redis password in the secret. +*/}} +{{- define "kutt.redisSecretKey" -}} +{{- if .Values.redis.enabled }} +{{- "redis-password" }} +{{- else }} +{{- "REDIS_PASSWORD" }} +{{- end }} +{{- end }} + +{{/* +Name of the secret holding the JWT secret. +*/}} +{{- define "kutt.jwtSecretName" -}} +{{- if .Values.kutt.auth.existingSecret }} +{{- .Values.kutt.auth.existingSecret }} +{{- else }} +{{- include "kutt.fullname" . }} +{{- end }} +{{- end }} + +{{/* +Name of the secret holding the Mail password. +*/}} +{{- define "kutt.mailSecretName" -}} +{{- if .Values.kutt.mail.existingSecret }} +{{- .Values.kutt.mail.existingSecret }} +{{- else }} +{{- include "kutt.fullname" . }} +{{- end }} +{{- end }} + +{{/* +Name of the secret holding the OIDC client secret. +*/}} +{{- define "kutt.oidcSecretName" -}} +{{- if .Values.kutt.oidc.existingSecret }} +{{- .Values.kutt.oidc.existingSecret }} +{{- else }} +{{- include "kutt.fullname" . }} +{{- end }} +{{- end }} diff --git a/helm/kutt/templates/configmap.yaml b/helm/kutt/templates/configmap.yaml new file mode 100644 index 000000000..f939e0236 --- /dev/null +++ b/helm/kutt/templates/configmap.yaml @@ -0,0 +1,75 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "kutt.fullname" . }} + labels: + {{- include "kutt.labels" . | nindent 4 }} +data: + PORT: {{ .Values.kutt.port | quote }} + SITE_NAME: {{ .Values.kutt.siteName | quote }} + DEFAULT_DOMAIN: {{ .Values.kutt.defaultDomain | quote }} + TRUST_PROXY: {{ .Values.kutt.trustProxy | quote }} + LINK_LENGTH: {{ .Values.kutt.linkLength | quote }} + {{- if .Values.kutt.linkCustomAlphabet }} + LINK_CUSTOM_ALPHABET: {{ .Values.kutt.linkCustomAlphabet | quote }} + {{- end }} + {{- if .Values.kutt.serverIpAddress }} + SERVER_IP_ADDRESS: {{ .Values.kutt.serverIpAddress | quote }} + {{- end }} + {{- if .Values.kutt.serverCnameAddress }} + SERVER_CNAME_ADDRESS: {{ .Values.kutt.serverCnameAddress | quote }} + {{- end }} + DISALLOW_REGISTRATION: {{ .Values.kutt.registration.disallowRegistration | quote }} + DISALLOW_LOGIN_FORM: {{ .Values.kutt.registration.disallowLoginForm | quote }} + DISALLOW_ANONYMOUS_LINKS: {{ .Values.kutt.registration.disallowAnonymousLinks | quote }} + ENABLE_RATE_LIMIT: {{ .Values.kutt.features.enableRateLimit | quote }} + CUSTOM_DOMAIN_USE_HTTPS: {{ .Values.kutt.features.customDomainUseHttps | quote }} + {{- if .Values.kutt.features.reportEmail }} + REPORT_EMAIL: {{ .Values.kutt.features.reportEmail | quote }} + {{- end }} + {{- if .Values.kutt.features.contactEmail }} + CONTACT_EMAIL: {{ .Values.kutt.features.contactEmail | quote }} + {{- end }} + # Database + DB_CLIENT: {{ include "kutt.dbClient" . | quote }} + {{- if eq (include "kutt.dbClient" .) "better-sqlite3" }} + DB_FILENAME: {{ .Values.kutt.db.filename | quote }} + {{- else }} + DB_HOST: {{ include "kutt.dbHost" . | quote }} + DB_PORT: {{ include "kutt.dbPort" . | quote }} + DB_NAME: {{ include "kutt.dbName" . | quote }} + DB_USER: {{ include "kutt.dbUser" . | quote }} + DB_SSL: {{ .Values.kutt.db.ssl | quote }} + DB_POOL_MIN: {{ .Values.kutt.db.poolMin | quote }} + DB_POOL_MAX: {{ .Values.kutt.db.poolMax | quote }} + {{- end }} + # Redis + REDIS_ENABLED: {{ include "kutt.redisEnabled" . | quote }} + {{- if eq (include "kutt.redisEnabled" .) "true" }} + REDIS_HOST: {{ include "kutt.redisHost" . | quote }} + REDIS_PORT: {{ .Values.kutt.redis.port | quote }} + REDIS_DB: {{ .Values.kutt.redis.db | quote }} + {{- end }} + # Mail + MAIL_ENABLED: {{ .Values.kutt.mail.enabled | quote }} + {{- if .Values.kutt.mail.enabled }} + MAIL_HOST: {{ .Values.kutt.mail.host | quote }} + MAIL_PORT: {{ .Values.kutt.mail.port | quote }} + MAIL_SECURE: {{ .Values.kutt.mail.secure | quote }} + MAIL_USER: {{ .Values.kutt.mail.user | quote }} + {{- if .Values.kutt.mail.from }} + MAIL_FROM: {{ .Values.kutt.mail.from | quote }} + {{- end }} + {{- end }} + # OIDC + OIDC_ENABLED: {{ .Values.kutt.oidc.enabled | quote }} + {{- if .Values.kutt.oidc.enabled }} + OIDC_ISSUER: {{ .Values.kutt.oidc.issuer | quote }} + OIDC_CLIENT_ID: {{ .Values.kutt.oidc.clientId | quote }} + {{- if .Values.kutt.oidc.scope }} + OIDC_SCOPE: {{ .Values.kutt.oidc.scope | quote }} + {{- end }} + {{- if .Values.kutt.oidc.emailClaim }} + OIDC_EMAIL_CLAIM: {{ .Values.kutt.oidc.emailClaim | quote }} + {{- end }} + {{- end }} diff --git a/helm/kutt/templates/deployment.yaml b/helm/kutt/templates/deployment.yaml new file mode 100644 index 000000000..176a2eb72 --- /dev/null +++ b/helm/kutt/templates/deployment.yaml @@ -0,0 +1,121 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "kutt.fullname" . }} + labels: + {{- include "kutt.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "kutt.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "kutt.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "kutt.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.kutt.port }} + protocol: TCP + envFrom: + - configMapRef: + name: {{ include "kutt.fullname" . }} + env: + # JWT Secret + - name: JWT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "kutt.jwtSecretName" . }} + key: JWT_SECRET + # Database password (only for non-SQLite) + {{- if ne (include "kutt.dbClient" .) "better-sqlite3" }} + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "kutt.dbSecretName" . }} + key: {{ include "kutt.dbSecretKey" . }} + {{- end }} + # Redis password + {{- if eq (include "kutt.redisEnabled" .) "true" }} + {{- if or .Values.redis.enabled (or .Values.kutt.redis.password .Values.kutt.redis.existingSecret) }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "kutt.redisSecretName" . }} + key: {{ include "kutt.redisSecretKey" . }} + {{- end }} + {{- end }} + # Mail password + {{- if .Values.kutt.mail.enabled }} + - name: MAIL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "kutt.mailSecretName" . }} + key: MAIL_PASSWORD + {{- end }} + # OIDC client secret + {{- if .Values.kutt.oidc.enabled }} + - name: OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "kutt.oidcSecretName" . }} + key: OIDC_CLIENT_SECRET + {{- end }} + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + {{- if and (eq (include "kutt.dbClient" .) "better-sqlite3") .Values.persistence.enabled }} + - name: data + mountPath: /var/lib/kutt + {{- end }} + {{- with .Values.volumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + volumes: + {{- if and (eq (include "kutt.dbClient" .) "better-sqlite3") .Values.persistence.enabled }} + - name: data + persistentVolumeClaim: + claimName: {{ .Values.persistence.existingClaim | default (include "kutt.fullname" .) }} + {{- end }} + {{- with .Values.volumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/kutt/templates/hpa.yaml b/helm/kutt/templates/hpa.yaml new file mode 100644 index 000000000..8330acee5 --- /dev/null +++ b/helm/kutt/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "kutt.fullname" . }} + labels: + {{- include "kutt.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "kutt.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/kutt/templates/ingress.yaml b/helm/kutt/templates/ingress.yaml new file mode 100644 index 000000000..77a7c43bc --- /dev/null +++ b/helm/kutt/templates/ingress.yaml @@ -0,0 +1,43 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "kutt.fullname" . }} + labels: + {{- include "kutt.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- with .pathType }} + pathType: {{ . }} + {{- end }} + backend: + service: + name: {{ include "kutt.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/kutt/templates/pvc.yaml b/helm/kutt/templates/pvc.yaml new file mode 100644 index 000000000..02f23b006 --- /dev/null +++ b/helm/kutt/templates/pvc.yaml @@ -0,0 +1,17 @@ +{{- if and (eq (include "kutt.dbClient" .) "better-sqlite3") .Values.persistence.enabled (not .Values.persistence.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "kutt.fullname" . }} + labels: + {{- include "kutt.labels" . | nindent 4 }} +spec: + accessModes: + {{- toYaml .Values.persistence.accessModes | nindent 4 }} + resources: + requests: + storage: {{ .Values.persistence.size }} + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass }} + {{- end }} +{{- end }} diff --git a/helm/kutt/templates/secret.yaml b/helm/kutt/templates/secret.yaml new file mode 100644 index 000000000..773133a4d --- /dev/null +++ b/helm/kutt/templates/secret.yaml @@ -0,0 +1,24 @@ +{{- $secretName := include "kutt.fullname" . }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $secretName }} + labels: + {{- include "kutt.labels" . | nindent 4 }} +type: Opaque +data: + {{- if not .Values.kutt.auth.existingSecret }} + JWT_SECRET: {{ .Values.kutt.auth.jwtSecret | b64enc | quote }} + {{- end }} + {{- if and (not .Values.kutt.db.existingSecret) (not .Values.postgresql.enabled) (not .Values.mariadb.enabled) }} + DB_PASSWORD: {{ .Values.kutt.db.password | b64enc | quote }} + {{- end }} + {{- if and (not .Values.kutt.redis.existingSecret) (not .Values.redis.enabled) }} + REDIS_PASSWORD: {{ .Values.kutt.redis.password | b64enc | quote }} + {{- end }} + {{- if and .Values.kutt.mail.enabled (not .Values.kutt.mail.existingSecret) }} + MAIL_PASSWORD: {{ .Values.kutt.mail.password | b64enc | quote }} + {{- end }} + {{- if and .Values.kutt.oidc.enabled (not .Values.kutt.oidc.existingSecret) }} + OIDC_CLIENT_SECRET: {{ .Values.kutt.oidc.clientSecret | b64enc | quote }} + {{- end }} diff --git a/helm/kutt/templates/service.yaml b/helm/kutt/templates/service.yaml new file mode 100644 index 000000000..a1f962696 --- /dev/null +++ b/helm/kutt/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "kutt.fullname" . }} + labels: + {{- include "kutt.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "kutt.selectorLabels" . | nindent 4 }} diff --git a/helm/kutt/templates/serviceaccount.yaml b/helm/kutt/templates/serviceaccount.yaml new file mode 100644 index 000000000..9818b67cb --- /dev/null +++ b/helm/kutt/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "kutt.serviceAccountName" . }} + labels: + {{- include "kutt.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/helm/kutt/templates/tests/test-connection.yaml b/helm/kutt/templates/tests/test-connection.yaml new file mode 100644 index 000000000..3140283a7 --- /dev/null +++ b/helm/kutt/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "kutt.fullname" . }}-test-connection" + labels: + {{- include "kutt.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['--spider', '{{ include "kutt.fullname" . }}:{{ .Values.service.port }}/api/v2/health'] + restartPolicy: Never diff --git a/helm/kutt/values.yaml b/helm/kutt/values.yaml new file mode 100644 index 000000000..7870d38b1 --- /dev/null +++ b/helm/kutt/values.yaml @@ -0,0 +1,378 @@ +# Default values for kutt. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: kutt/kutt + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Automatically mount a ServiceAccount's API credentials? + automount: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} +podLabels: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 3000 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # cert-manager.io/cluster-issuer: letsencrypt-prod + hosts: + - host: kutt.example.com + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: kutt-tls + # hosts: + # - kutt.example.com + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +livenessProbe: + httpGet: + path: /api/v2/health + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + +readinessProbe: + httpGet: + path: /api/v2/health + port: http + initialDelaySeconds: 15 + periodSeconds: 10 + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +# Additional volumes on the output Deployment definition. +volumes: [] +# - name: foo +# secret: +# secretName: mysecret +# optional: false + +# Additional volumeMounts on the output Deployment definition. +volumeMounts: [] +# - name: foo +# mountPath: "/etc/foo" +# readOnly: true + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# ----------------------------------------------------------------------- +# Kutt application configuration +# ----------------------------------------------------------------------- +kutt: + # -- Application port + port: 3000 + + # -- The name of the site where Kutt is hosted + siteName: "Kutt" + + # -- The domain that this website is on (e.g. "kutt.example.com") + defaultDomain: "" + + # -- Whether Kutt is running behind a proxy server. + # If true, the real client IP will be taken from the X-Forwarded-For header. + trustProxy: true + + # -- Generated link length + linkLength: 6 + + # -- Alphabet used to generate custom addresses. + # Default omits o, O, 0, i, I, l, 1, and j to avoid confusion when reading the URL. + linkCustomAlphabet: "" + + # -- Display-only: Server IP address shown on the settings page + serverIpAddress: "" + + # -- Display-only: Server CNAME address shown on the settings page + serverCnameAddress: "" + + auth: + # -- REQUIRED: A passphrase to encrypt JWT. Use a long random string. + jwtSecret: "" + # -- Name of an existing secret containing the JWT secret. + # The secret must have a key named "JWT_SECRET". + # If set, jwtSecret is ignored. + existingSecret: "" + + registration: + # -- Disable user registration (default: true) + disallowRegistration: true + # -- Disable form-based login. Only makes sense when OIDC is enabled. + disallowLoginForm: false + # -- Disable anonymous link creation (default: true) + disallowAnonymousLinks: true + + # ----------------------------------------------------------------------- + # Database configuration + # ----------------------------------------------------------------------- + # Configure one of: postgresql, mariadb, or sqlite sub-sections. + # If postgresql.enabled or mariadb.enabled (sub-charts), the relevant settings + # are configured automatically. + # ----------------------------------------------------------------------- + db: + # -- Database client driver. + # Options: better-sqlite3 | pg | mysql2 + # Automatically set when postgresql.enabled or mariadb.enabled is true. + client: "better-sqlite3" + + # -- SQLite database file path. Only used when client is "better-sqlite3". + filename: "/var/lib/kutt/data.sqlite" + + # -- External database host. Not needed if postgresql.enabled or mariadb.enabled. + host: "" + + # -- External database port. Not needed if postgresql.enabled or mariadb.enabled. + port: "" + + # -- Database name. Not needed if postgresql.enabled or mariadb.enabled. + name: "kutt" + + # -- Database user. Not needed if postgresql.enabled or mariadb.enabled. + user: "" + + # -- Database password for external databases. + password: "" + + # -- Enable SSL for database connections. + ssl: false + + # -- Minimum database connection pool size. + poolMin: 0 + + # -- Maximum database connection pool size. + poolMax: 10 + + # -- Name of an existing secret with the DB password. + # The secret must have a key named "DB_PASSWORD". + # If set, db.password is ignored. + existingSecret: "" + + # ----------------------------------------------------------------------- + # Redis configuration + # ----------------------------------------------------------------------- + redis: + # -- Enable Redis caching and rate-limiting backend. + # Automatically set to true when redis.enabled (sub-chart) is true. + enabled: false + + # -- Redis host. Not needed if redis.enabled (sub-chart) is true. + host: "" + + # -- Redis port. + port: 6379 + + # -- Redis password. Leave empty if Redis has no authentication. + password: "" + + # -- Redis database number (0-15). + db: 0 + + # -- Name of an existing secret with the Redis password. + # The secret must have a key named "REDIS_PASSWORD". + # If set, redis.password is ignored. + existingSecret: "" + + # ----------------------------------------------------------------------- + # Mail (SMTP) configuration + # ----------------------------------------------------------------------- + mail: + # -- Enable email functionality (verification, password reset, reports). + enabled: false + + # -- SMTP server host. + host: "" + + # -- SMTP server port. + port: 587 + + # -- Use SSL/TLS for SMTP connection. + secure: true + + # -- SMTP username. + user: "" + + # -- SMTP password. + password: "" + + # -- Sender address, e.g. "Kutt ". Leave empty to use mail.user. + from: "" + + # -- Name of an existing secret with the mail password. + # The secret must have a key named "MAIL_PASSWORD". + # If set, mail.password is ignored. + existingSecret: "" + + # ----------------------------------------------------------------------- + # Additional features + # ----------------------------------------------------------------------- + features: + # -- Enable rate limiting for some API routes. + enableRateLimit: false + + # -- Use HTTPS for links with custom domain. + customDomainUseHttps: false + + # -- The email address that receives submitted reports. + reportEmail: "" + + # -- Support email shown in the app. + contactEmail: "" + + # ----------------------------------------------------------------------- + # OpenID Connect (OIDC) configuration + # ----------------------------------------------------------------------- + oidc: + # -- Enable OIDC-based authentication. + enabled: false + + # -- OIDC issuer URL. + issuer: "" + + # -- OIDC client ID. + clientId: "" + + # -- OIDC client secret. + clientSecret: "" + + # -- OIDC scope (e.g. "openid profile email"). + scope: "" + + # -- Name of the claim in the OIDC token that contains the user email. Defaults to "email". + emailClaim: "" + + # -- Name of an existing secret with the OIDC client secret. + # The secret must have a key named "OIDC_CLIENT_SECRET". + # If set, oidc.clientSecret is ignored. + existingSecret: "" + +# ----------------------------------------------------------------------- +# SQLite persistence (only used when postgresql.enabled=false and mariadb.enabled=false) +# ----------------------------------------------------------------------- +persistence: + # -- Enable persistent volume for SQLite database file. + enabled: true + # -- StorageClass for the PVC. Set to "" to use the default. + storageClass: "" + # -- Access modes for the PVC. + accessModes: + - ReadWriteOnce + # -- Size of the persistent volume. + size: 1Gi + # -- Name of an existing PVC to use. If set, a new PVC is not created. + existingClaim: "" + +# ----------------------------------------------------------------------- +# PostgreSQL sub-chart (Bitnami) +# https://github.com/bitnami/charts/tree/main/bitnami/postgresql +# ----------------------------------------------------------------------- +postgresql: + # -- Enable the Bitnami PostgreSQL sub-chart. + # When enabled, the kutt.db settings are automatically configured. + enabled: false + auth: + # -- Database name to create. + database: kutt + # -- Database user. + username: kutt + # -- Database password. Leave empty to auto-generate. + password: "" + primary: + persistence: + enabled: true + size: 8Gi + +# ----------------------------------------------------------------------- +# MariaDB sub-chart (Bitnami) +# https://github.com/bitnami/charts/tree/main/bitnami/mariadb +# ----------------------------------------------------------------------- +mariadb: + # -- Enable the Bitnami MariaDB sub-chart. + # When enabled, the kutt.db settings are automatically configured. + enabled: false + auth: + # -- Database name to create. + database: kutt + # -- Database user. + username: kutt + # -- Database password. Leave empty to auto-generate. + password: "" + # -- MariaDB root password. Leave empty to auto-generate. + rootPassword: "" + primary: + persistence: + enabled: true + size: 8Gi + +# ----------------------------------------------------------------------- +# Redis sub-chart (Bitnami) +# https://github.com/bitnami/charts/tree/main/bitnami/redis +# ----------------------------------------------------------------------- +redis: + # -- Enable the Bitnami Redis sub-chart. + # When enabled, kutt.redis settings are automatically configured. + enabled: false + auth: + # -- Enable Redis authentication. + enabled: false + # -- Redis password. Leave empty to auto-generate when auth.enabled is true. + password: "" + master: + persistence: + enabled: true + size: 8Gi + replica: + replicaCount: 0