diff --git a/apps/discourse/configmap.yaml b/apps/discourse/configmap.yaml new file mode 100644 index 0000000..cd19f49 --- /dev/null +++ b/apps/discourse/configmap.yaml @@ -0,0 +1,43 @@ +--- +# Source: discourse/templates/configmaps.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: discourse + namespace: discourse +data: + DISCOURSE_HOSTNAME: "{{ .apps.discourse.domain }}" + DISCOURSE_SKIP_INSTALL: "no" + DISCOURSE_SITE_NAME: "{{ .apps.discourse.siteName }}" + DISCOURSE_USERNAME: "{{ .apps.discourse.adminUsername }}" + DISCOURSE_EMAIL: "{{ .apps.discourse.adminEmail }}" + DISCOURSE_REDIS_HOST: "{{ .apps.discourse.redisHostname }}" + DISCOURSE_REDIS_PORT_NUMBER: "6379" + DISCOURSE_DATABASE_HOST: "{{ .apps.discourse.dbHostname }}" + DISCOURSE_DATABASE_PORT_NUMBER: "5432" + DISCOURSE_DATABASE_NAME: "{{ .apps.discourse.dbName }}" + DISCOURSE_DATABASE_USER: "{{ .apps.discourse.dbUsername }}" + + # DISCOURSE_SMTP_ADDRESS: "{{ .apps.discourse.smtp.host }}" + # DISCOURSE_SMTP_PORT: "{{ .apps.discourse.smtp.port }}" + # DISCOURSE_SMTP_USER_NAME: "{{ .apps.discourse.smtp.user }}" + # DISCOURSE_SMTP_ENABLE_START_TLS: "{{ .apps.discourse.smtp.startTls }}" + # DISCOURSE_SMTP_AUTHENTICATION: "login" + + # Bitnami specific environment variables (diverges from the original) + # https://techdocs.broadcom.com/us/en/vmware-tanzu/bitnami-secure-images/bitnami-secure-images/services/bsi-app-doc/apps-containers-discourse-index.html + DISCOURSE_SMTP_HOST: "{{ .apps.discourse.smtp.host }}" + DISCOURSE_SMTP_PORT_NUMBER: "{{ .apps.discourse.smtp.port }}" + DISCOURSE_SMTP_USER: "{{ .apps.discourse.smtp.user }}" + DISCOURSE_SMTP_ENABLE_START_TLS: "{{ .apps.discourse.smtp.startTls }}" + DISCOURSE_SMTP_AUTH: "login" + DISCOURSE_SMTP_PROTOCOL: "tls" + + DISCOURSE_PRECOMPILE_ASSETS: "false" + + # SMTP_HOST: "{{ .apps.discourse.smtp.host }}" + # SMTP_PORT: "{{ .apps.discourse.smtp.port }}" + # SMTP_USER_NAME: "{{ .apps.discourse.smtp.user }}" + # SMTP_TLS: "{{ .apps.discourse.smtp.tls }}" + # SMTP_ENABLE_START_TLS: "{{ .apps.discourse.smtp.startTls }}" + # SMTP_AUTHENTICATION: "login" diff --git a/apps/discourse/db-init-job.yaml b/apps/discourse/db-init-job.yaml new file mode 100644 index 0000000..4f2401c --- /dev/null +++ b/apps/discourse/db-init-job.yaml @@ -0,0 +1,77 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: discourse-db-init + namespace: discourse +spec: + template: + metadata: + labels: + component: db-init + spec: + securityContext: + runAsNonRoot: true + runAsUser: 999 + runAsGroup: 999 + seccompProfile: + type: RuntimeDefault + restartPolicy: OnFailure + containers: + - name: db-init + image: postgres:16-alpine + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + env: + - name: PGHOST + value: "{{ .apps.discourse.dbHostname }}" + - name: PGPORT + value: "5432" + - name: PGUSER + value: postgres + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: discourse-secrets + key: apps.postgres.password + - name: DISCOURSE_DB_USER + value: "{{ .apps.discourse.dbUsername }}" + - name: DISCOURSE_DB_NAME + value: "{{ .apps.discourse.dbName }}" + - name: DISCOURSE_DB_PASSWORD + valueFrom: + secretKeyRef: + name: discourse-secrets + key: apps.discourse.dbPassword + command: + - /bin/sh + - -c + - | + echo "Initializing Discourse database..." + + # Create database if it doesn't exist + if ! psql -lqt | cut -d \| -f 1 | grep -qw "$DISCOURSE_DB_NAME"; then + echo "Creating database $DISCOURSE_DB_NAME..." + createdb "$DISCOURSE_DB_NAME" + else + echo "Database $DISCOURSE_DB_NAME already exists." + fi + + # Create user if it doesn't exist and grant permissions + psql -d "$DISCOURSE_DB_NAME" -c " + DO \$\$ + BEGIN + IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '$DISCOURSE_DB_USER') THEN + CREATE USER $DISCOURSE_DB_USER WITH PASSWORD '$DISCOURSE_DB_PASSWORD'; + END IF; + END + \$\$; + GRANT ALL PRIVILEGES ON DATABASE $DISCOURSE_DB_NAME TO $DISCOURSE_DB_USER; + GRANT ALL ON SCHEMA public TO $DISCOURSE_DB_USER; + GRANT USAGE ON SCHEMA public TO $DISCOURSE_DB_USER; + " + + echo "Database initialization completed." \ No newline at end of file diff --git a/apps/discourse/deployment.yaml b/apps/discourse/deployment.yaml new file mode 100644 index 0000000..c06a78a --- /dev/null +++ b/apps/discourse/deployment.yaml @@ -0,0 +1,236 @@ +--- +# Source: discourse/templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: discourse + namespace: discourse +spec: + replicas: 1 + selector: + matchLabels: + component: web + strategy: + type: Recreate + template: + metadata: + labels: + component: web + spec: + automountServiceAccountToken: false + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: + component: web + topologyKey: kubernetes.io/hostname + weight: 1 + + serviceAccountName: discourse + securityContext: + fsGroup: 0 + fsGroupChangePolicy: Always + supplementalGroups: [] + sysctls: [] + initContainers: + containers: + - name: discourse + image: { { .apps.discourse.image } } + imagePullPolicy: "IfNotPresent" + securityContext: + allowPrivilegeEscalation: false + capabilities: + add: + - CHOWN + - SYS_CHROOT + - FOWNER + - SETGID + - SETUID + - DAC_OVERRIDE + drop: + - ALL + privileged: false + readOnlyRootFilesystem: false + runAsGroup: 0 + runAsNonRoot: false + runAsUser: 0 + seLinuxOptions: {} + seccompProfile: + type: RuntimeDefault + env: + - name: BITNAMI_DEBUG + value: "false" + - name: DISCOURSE_PASSWORD + valueFrom: + secretKeyRef: + name: discourse-secrets + key: apps.discourse.adminPassword + - name: DISCOURSE_PORT_NUMBER + value: "8080" + - name: DISCOURSE_EXTERNAL_HTTP_PORT_NUMBER + value: "80" + - name: DISCOURSE_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: discourse-secrets + key: apps.discourse.dbPassword + - name: POSTGRESQL_CLIENT_CREATE_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: discourse-secrets + key: apps.discourse.dbPassword + - name: DISCOURSE_REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: discourse-secrets + key: apps.discourse.redisPassword + - name: DISCOURSE_SECRET_KEY_BASE + valueFrom: + secretKeyRef: + name: discourse-secrets + key: apps.discourse.secretKeyBase + - name: DISCOURSE_SMTP_PASSWORD + valueFrom: + secretKeyRef: + name: discourse-secrets + key: apps.discourse.smtpPassword + - name: SMTP_PASSWORD + valueFrom: + secretKeyRef: + name: discourse-secrets + key: apps.discourse.smtpPassword + envFrom: + - configMapRef: + name: discourse + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + tcpSocket: + port: http + initialDelaySeconds: 500 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 6 + readinessProbe: + httpGet: + path: /srv/status + port: http + initialDelaySeconds: 180 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 6 + resources: + limits: + cpu: 1 + ephemeral-storage: 2Gi + memory: 8Gi # for precompiling assets! + requests: + cpu: 750m + ephemeral-storage: 50Mi + memory: 1Gi + volumeMounts: + - name: discourse-data + mountPath: /bitnami/discourse + subPath: discourse + - name: sidekiq + image: { { .apps.discourse.sidekiqImage } } + imagePullPolicy: "IfNotPresent" + securityContext: + allowPrivilegeEscalation: false + capabilities: + add: + - CHOWN + - SYS_CHROOT + - FOWNER + - SETGID + - SETUID + - DAC_OVERRIDE + drop: + - ALL + privileged: false + readOnlyRootFilesystem: false + runAsGroup: 0 + runAsNonRoot: false + runAsUser: 0 + seLinuxOptions: {} + seccompProfile: + type: RuntimeDefault + command: + - /opt/bitnami/scripts/discourse/entrypoint.sh + args: + - /opt/bitnami/scripts/discourse-sidekiq/run.sh + env: + - name: BITNAMI_DEBUG + value: "false" + - name: DISCOURSE_PASSWORD + valueFrom: + secretKeyRef: + name: discourse-secrets + key: apps.discourse.adminPassword + - name: DISCOURSE_POSTGRESQL_PASSWORD + valueFrom: + secretKeyRef: + name: discourse-secrets + key: apps.discourse.dbPassword + - name: DISCOURSE_REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: discourse-secrets + key: apps.discourse.redisPassword + - name: DISCOURSE_SECRET_KEY_BASE + valueFrom: + secretKeyRef: + name: discourse-secrets + key: apps.discourse.secretKeyBase + - name: DISCOURSE_SMTP_PASSWORD + valueFrom: + secretKeyRef: + name: discourse-secrets + key: apps.discourse.smtpPassword + - name: SMTP_PASSWORD + valueFrom: + secretKeyRef: + name: discourse-secrets + key: apps.discourse.smtpPassword + envFrom: + - configMapRef: + name: discourse + livenessProbe: + exec: + command: ["/bin/sh", "-c", "pgrep -f ^sidekiq"] + initialDelaySeconds: 500 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 6 + readinessProbe: + exec: + command: ["/bin/sh", "-c", "pgrep -f ^sidekiq"] + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 6 + resources: + limits: + cpu: 500m + ephemeral-storage: 2Gi + memory: 768Mi + requests: + cpu: 375m + ephemeral-storage: 50Mi + memory: 512Mi + volumeMounts: + - name: discourse-data + mountPath: /bitnami/discourse + subPath: discourse + volumes: + - name: discourse-data + persistentVolumeClaim: + claimName: discourse diff --git a/apps/discourse/ingress.yaml b/apps/discourse/ingress.yaml new file mode 100644 index 0000000..d11ab64 --- /dev/null +++ b/apps/discourse/ingress.yaml @@ -0,0 +1,26 @@ +--- +# Source: discourse/templates/ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: discourse + namespace: "discourse" + annotations: + external-dns.alpha.kubernetes.io/cloudflare-proxied: "false" + external-dns.alpha.kubernetes.io/target: "{{ .cloud.domain }}" +spec: + rules: + - host: "{{ .apps.discourse.domain }}" + http: + paths: + - path: / + pathType: ImplementationSpecific + backend: + service: + name: discourse + port: + name: http + tls: + - hosts: + - "{{ .apps.discourse.domain }}" + secretName: wildcard-external-wild-cloud-tls diff --git a/apps/discourse/kustomization.yaml b/apps/discourse/kustomization.yaml new file mode 100644 index 0000000..2f407f4 --- /dev/null +++ b/apps/discourse/kustomization.yaml @@ -0,0 +1,18 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: discourse +labels: + - includeSelectors: true + pairs: + app: discourse + managedBy: kustomize + partOf: wild-cloud +resources: + - namespace.yaml + - serviceaccount.yaml + - configmap.yaml + - pvc.yaml + - deployment.yaml + - service.yaml + - ingress.yaml + - db-init-job.yaml diff --git a/apps/discourse/manifest.yaml b/apps/discourse/manifest.yaml new file mode 100644 index 0000000..a7e368d --- /dev/null +++ b/apps/discourse/manifest.yaml @@ -0,0 +1,38 @@ +name: discourse +description: Discourse is a modern, open-source discussion platform designed for online communities and forums. +version: 3.4.7 +icon: https://www.discourse.org/img/icon.png +requires: + - name: postgres + - name: redis +defaultConfig: + image: docker.io/bitnami/discourse:3.4.7-debian-12-r0 + sidekiqImage: docker.io/bitnami/discourse:3.4.7-debian-12-r0 + timezone: UTC + port: 8080 + storage: 10Gi + adminEmail: admin@{{ .cloud.domain }} + adminUsername: admin + siteName: "Community" + domain: discourse.{{ .cloud.domain }} + dbHostname: postgres.postgres.svc.cluster.local + dbUsername: discourse + dbName: discourse + redisHostname: redis.redis.svc.cluster.local + tlsSecretName: wildcard-wild-cloud-tls + smtp: + enabled: false + host: "{{ .cloud.smtp.host }}" + port: "{{ .cloud.smtp.port }}" + user: "{{ .cloud.smtp.user }}" + from: "{{ .cloud.smtp.from }}" + tls: {{ .cloud.smtp.tls }} + startTls: {{ .cloud.smtp.startTls }} +requiredSecrets: + - apps.discourse.adminPassword + - apps.discourse.dbPassword + - apps.discourse.dbUrl + - apps.discourse.redisPassword + - apps.discourse.secretKeyBase + - apps.discourse.smtpPassword + - apps.postgres.password \ No newline at end of file diff --git a/apps/discourse/namespace.yaml b/apps/discourse/namespace.yaml new file mode 100644 index 0000000..6bd89f9 --- /dev/null +++ b/apps/discourse/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: discourse diff --git a/apps/discourse/pvc.yaml b/apps/discourse/pvc.yaml new file mode 100644 index 0000000..f73bd62 --- /dev/null +++ b/apps/discourse/pvc.yaml @@ -0,0 +1,13 @@ +--- +# Source: discourse/templates/pvc.yaml +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: discourse + namespace: discourse +spec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: "{{ .apps.discourse.storage }}" diff --git a/apps/discourse/service.yaml b/apps/discourse/service.yaml new file mode 100644 index 0000000..5b8be2f --- /dev/null +++ b/apps/discourse/service.yaml @@ -0,0 +1,18 @@ +--- +# Source: discourse/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: discourse + namespace: discourse +spec: + type: ClusterIP + sessionAffinity: None + ports: + - name: http + port: 80 + protocol: TCP + targetPort: http + nodePort: null + selector: + component: web diff --git a/apps/discourse/serviceaccount.yaml b/apps/discourse/serviceaccount.yaml new file mode 100644 index 0000000..86b8e63 --- /dev/null +++ b/apps/discourse/serviceaccount.yaml @@ -0,0 +1,8 @@ +--- +# Source: discourse/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: discourse + namespace: discourse +automountServiceAccountToken: false