diff --git a/apps/ghost/config/example.env b/apps/ghost/config/example.env deleted file mode 100644 index 930add4..0000000 --- a/apps/ghost/config/example.env +++ /dev/null @@ -1,13 +0,0 @@ -GHOST_NAMESPACE=ghost -GHOST_HOST=blog.${DOMAIN} -GHOST_TITLE="My Blog" -GHOST_EMAIL= -GHOST_STORAGE_SIZE=10Gi -GHOST_MARIADB_STORAGE_SIZE=8Gi -GHOST_DATABASE_HOST=mariadb.mariadb.svc.cluster.local -GHOST_DATABASE_USER=ghost -GHOST_DATABASE_NAME=ghost - -# Secrets -GHOST_PASSWORD= -GHOST_DATABASE_PASSWORD= diff --git a/apps/ghost/db-init-job.yaml b/apps/ghost/db-init-job.yaml new file mode 100644 index 0000000..cebe472 --- /dev/null +++ b/apps/ghost/db-init-job.yaml @@ -0,0 +1,44 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: ghost-db-init + labels: + component: db-init +spec: + template: + metadata: + labels: + component: db-init + spec: + containers: + - name: db-init + image: {{ .apps.mysql.image }} + command: ["/bin/bash", "-c"] + args: + - | + mysql -h ${DB_HOSTNAME} -P ${DB_PORT} -u root -p${MYSQL_ROOT_PASSWORD} < - #!/bin/bash - - - . /opt/bitnami/scripts/liblog.sh - - - info "Copying base dir to empty dir" - - # In order to not break the application functionality (such as - upgrades or plugins) we need - - # to make the base directory writable, so we need to copy it to an - empty dir volume - - cp -r --preserve=mode /opt/bitnami/ghost /emptydir/app-base-dir - resources: - limits: - cpu: 375m - ephemeral-storage: 2Gi - memory: 384Mi - requests: - cpu: 250m - ephemeral-storage: 50Mi - memory: 256Mi - volumeMounts: - - name: empty-dir - mountPath: /emptydir - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - imagePullPolicy: IfNotPresent - securityContext: - capabilities: - drop: - - ALL - privileged: false - seLinuxOptions: {} - runAsUser: 1001 - runAsGroup: 1001 - runAsNonRoot: true - readOnlyRootFilesystem: true - allowPrivilegeEscalation: false - seccompProfile: - type: RuntimeDefault containers: - name: ghost - image: docker.io/bitnami/ghost:5.118.1-debian-12-r0 + image: {{ .apps.ghost.image }} ports: - - name: https - containerPort: 2368 + - name: http + containerPort: {{ .apps.ghost.port }} protocol: TCP env: - name: BITNAMI_DEBUG @@ -113,27 +28,33 @@ spec: - name: ALLOW_EMPTY_PASSWORD value: "yes" - name: GHOST_DATABASE_HOST - value: ghost-mysql + value: {{ .apps.ghost.dbHost }} - name: GHOST_DATABASE_PORT_NUMBER - value: "3306" + value: "{{ .apps.ghost.dbPort }}" - name: GHOST_DATABASE_NAME - value: ghost + value: {{ .apps.ghost.dbName }} - name: GHOST_DATABASE_USER - value: ghost - - name: GHOST_DATABASE_PASSWORD_FILE - value: /opt/bitnami/ghost/secrets/mysql-password + value: {{ .apps.ghost.dbUser }} + - name: GHOST_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: ghost-secrets + key: dbPassword - name: GHOST_HOST - value: blog.cloud.payne.io/ + value: {{ .apps.ghost.domain }} - name: GHOST_PORT_NUMBER - value: "2368" + value: "{{ .apps.ghost.port }}" - name: GHOST_USERNAME - value: admin - - name: GHOST_PASSWORD_FILE - value: /opt/bitnami/ghost/secrets/ghost-password + value: {{ .apps.ghost.adminUser }} + - name: GHOST_PASSWORD + valueFrom: + secretKeyRef: + name: ghost-secrets + key: adminPassword - name: GHOST_EMAIL - value: paul@payne.io + value: {{ .apps.ghost.adminEmail }} - name: GHOST_BLOG_TITLE - value: User's Blog + value: {{ .apps.ghost.blogTitle }} - name: GHOST_ENABLE_HTTPS value: "yes" - name: GHOST_EXTERNAL_HTTP_PORT_NUMBER @@ -142,6 +63,21 @@ spec: value: "443" - name: GHOST_SKIP_BOOTSTRAP value: "no" + - name: GHOST_SMTP_SERVICE + value: SMTP + - name: GHOST_SMTP_HOST + value: {{ .apps.ghost.smtp.host }} + - name: GHOST_SMTP_PORT + value: "{{ .apps.ghost.smtp.port }}" + - name: GHOST_SMTP_USER + value: {{ .apps.ghost.smtp.user }} + - name: GHOST_SMTP_PASSWORD + valueFrom: + secretKeyRef: + name: ghost-secrets + key: smtpPassword + - name: GHOST_SMTP_FROM_ADDRESS + value: {{ .apps.ghost.smtp.from }} resources: limits: cpu: 375m @@ -152,22 +88,11 @@ spec: ephemeral-storage: 50Mi memory: 256Mi volumeMounts: - - name: empty-dir - mountPath: /opt/bitnami/ghost - subPath: app-base-dir - - name: empty-dir - mountPath: /.ghost - subPath: app-tmp-dir - - name: empty-dir - mountPath: /tmp - subPath: tmp-dir - name: ghost-data mountPath: /bitnami/ghost - - name: ghost-secrets - mountPath: /opt/bitnami/ghost/secrets livenessProbe: tcpSocket: - port: 2368 + port: {{ .apps.ghost.port }} initialDelaySeconds: 120 timeoutSeconds: 5 periodSeconds: 10 @@ -176,7 +101,7 @@ spec: readinessProbe: httpGet: path: / - port: https + port: http scheme: HTTP httpHeaders: - name: x-forwarded-proto @@ -186,64 +111,22 @@ spec: periodSeconds: 5 successThreshold: 1 failureThreshold: 6 - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - imagePullPolicy: IfNotPresent securityContext: capabilities: drop: - ALL privileged: false - seLinuxOptions: {} runAsUser: 1001 runAsGroup: 1001 runAsNonRoot: true - readOnlyRootFilesystem: true + readOnlyRootFilesystem: false allowPrivilegeEscalation: false seccompProfile: type: RuntimeDefault + volumes: + - name: ghost-data + persistentVolumeClaim: + claimName: ghost-data restartPolicy: Always - terminationGracePeriodSeconds: 30 - dnsPolicy: ClusterFirst - serviceAccountName: ghost - serviceAccount: ghost - automountServiceAccountToken: false securityContext: - fsGroup: 1001 - fsGroupChangePolicy: Always - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 1 - podAffinityTerm: - labelSelector: - matchLabels: - app.kubernetes.io/instance: ghost - app.kubernetes.io/name: ghost - topologyKey: kubernetes.io/hostname - schedulerName: default-scheduler - strategy: - type: RollingUpdate - rollingUpdate: - maxUnavailable: 25% - maxSurge: 25% - revisionHistoryLimit: 10 - progressDeadlineSeconds: 600 -status: - observedGeneration: 1 - replicas: 1 - updatedReplicas: 1 - unavailableReplicas: 1 - conditions: - - type: Available - status: "False" - lastUpdateTime: "2025-04-27T02:01:30Z" - lastTransitionTime: "2025-04-27T02:01:30Z" - reason: MinimumReplicasUnavailable - message: Deployment does not have minimum availability. - - type: Progressing - status: "False" - lastUpdateTime: "2025-04-27T02:11:32Z" - lastTransitionTime: "2025-04-27T02:11:32Z" - reason: ProgressDeadlineExceeded - message: ReplicaSet "ghost-586bbc6ddd" has timed out progressing. + fsGroup: 1001 \ No newline at end of file diff --git a/apps/ghost/ingress.yaml b/apps/ghost/ingress.yaml index 5169dc3..2fc0cac 100644 --- a/apps/ghost/ingress.yaml +++ b/apps/ghost/ingress.yaml @@ -1,19 +1,18 @@ ---- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ghost - namespace: {{ .Values.namespace }} + namespace: ghost annotations: kubernetes.io/ingress.class: "traefik" cert-manager.io/cluster-issuer: "letsencrypt-prod" external-dns.alpha.kubernetes.io/cloudflare-proxied: "false" - external-dns.alpha.kubernetes.io/target: "cloud.payne.io" + external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }} external-dns.alpha.kubernetes.io/ttl: "60" traefik.ingress.kubernetes.io/redirect-entry-point: https spec: rules: - - host: {{ .Values.ghost.host }} + - host: {{ .apps.ghost.domain }} http: paths: - path: / @@ -25,5 +24,5 @@ spec: number: 80 tls: - hosts: - - {{ .Values.ghost.host }} - secretName: ghost-tls \ No newline at end of file + - {{ .apps.ghost.domain }} + secretName: {{ .apps.ghost.tlsSecretName }} \ No newline at end of file diff --git a/apps/ghost/kustomization.yaml b/apps/ghost/kustomization.yaml index e69de29..cf81298 100644 --- a/apps/ghost/kustomization.yaml +++ b/apps/ghost/kustomization.yaml @@ -0,0 +1,16 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: ghost +labels: + - includeSelectors: true + pairs: + app: ghost + managedBy: kustomize + partOf: wild-cloud +resources: + - namespace.yaml + - db-init-job.yaml + - deployment.yaml + - service.yaml + - ingress.yaml + - pvc.yaml \ No newline at end of file diff --git a/apps/ghost/manifest.yaml b/apps/ghost/manifest.yaml new file mode 100644 index 0000000..560ef52 --- /dev/null +++ b/apps/ghost/manifest.yaml @@ -0,0 +1,29 @@ +name: ghost +description: Ghost is a powerful app for new-media creators to publish, share, and grow a business around their content. +version: 5.118.1 +icon: https://ghost.org/images/logos/ghost-logo-orb.png +requires: + - name: mysql +defaultConfig: + image: docker.io/bitnami/ghost:5.118.1-debian-12-r0 + domain: ghost.{{ .cloud.domain }} + tlsSecretName: wildcard-wild-cloud-tls + port: 2368 + storage: 10Gi + dbHost: mysql.mysql.svc.cluster.local + dbPort: 3306 + dbName: ghost + dbUser: ghost + adminUser: admin + adminEmail: "admin@{{ .cloud.domain }}" + blogTitle: "My Blog" + timezone: UTC + smtp: + host: "{{ .cloud.smtp.host }}" + port: "{{ .cloud.smtp.port }}" + from: "{{ .cloud.smtp.from }}" + user: "{{ .cloud.smtp.user }}" +requiredSecrets: + - apps.ghost.adminPassword + - apps.ghost.dbPassword + - apps.ghost.smtpPassword \ No newline at end of file diff --git a/apps/ghost/namespace.yaml b/apps/ghost/namespace.yaml new file mode 100644 index 0000000..9cf06a2 --- /dev/null +++ b/apps/ghost/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: ghost \ No newline at end of file diff --git a/apps/ghost/networkpolicy.yaml b/apps/ghost/networkpolicy.yaml deleted file mode 100644 index c418d77..0000000 --- a/apps/ghost/networkpolicy.yaml +++ /dev/null @@ -1,26 +0,0 @@ ---- -# Source: ghost/templates/networkpolicy.yaml -kind: NetworkPolicy -apiVersion: networking.k8s.io/v1 -metadata: - name: ghost - namespace: "default" - labels: - app.kubernetes.io/instance: ghost - app.kubernetes.io/managed-by: Wild - app.kubernetes.io/name: ghost - app.kubernetes.io/version: 5.118.1 -spec: - podSelector: - matchLabels: - app.kubernetes.io/instance: ghost - app.kubernetes.io/name: ghost - policyTypes: - - Ingress - - Egress - egress: - - {} - ingress: - - ports: - - port: 2368 - - port: 2368 diff --git a/apps/ghost/pdb.yaml b/apps/ghost/pdb.yaml deleted file mode 100644 index 8820e91..0000000 --- a/apps/ghost/pdb.yaml +++ /dev/null @@ -1,20 +0,0 @@ ---- -# Source: ghost/templates/pdb.yaml -apiVersion: policy/v1 -kind: PodDisruptionBudget -metadata: - name: ghost - namespace: "default" - labels: - app.kubernetes.io/instance: ghost - app.kubernetes.io/managed-by: Wild - app.kubernetes.io/name: ghost - app.kubernetes.io/version: 5.118.1 - app.kubernetes.io/component: ghost -spec: - maxUnavailable: 1 - selector: - matchLabels: - app.kubernetes.io/instance: ghost - app.kubernetes.io/name: ghost - app.kubernetes.io/component: ghost diff --git a/apps/ghost/pvc.yaml b/apps/ghost/pvc.yaml new file mode 100644 index 0000000..9417048 --- /dev/null +++ b/apps/ghost/pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: ghost-data + namespace: ghost +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .apps.ghost.storage }} \ No newline at end of file diff --git a/apps/ghost/service-account.yaml b/apps/ghost/service-account.yaml deleted file mode 100644 index 54ad1bd..0000000 --- a/apps/ghost/service-account.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- -# Source: ghost/templates/service-account.yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - name: ghost - namespace: "default" - labels: - app.kubernetes.io/instance: ghost - app.kubernetes.io/managed-by: Wild - app.kubernetes.io/name: ghost - app.kubernetes.io/version: 5.118.1 - app.kubernetes.io/component: ghost -automountServiceAccountToken: false diff --git a/apps/ghost/service.yaml b/apps/ghost/service.yaml new file mode 100644 index 0000000..0d9bc7c --- /dev/null +++ b/apps/ghost/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: ghost + namespace: ghost +spec: + type: ClusterIP + ports: + - name: http + port: 80 + protocol: TCP + targetPort: {{ .apps.ghost.port }} + selector: + component: web \ No newline at end of file diff --git a/apps/ghost/svc.yaml b/apps/ghost/svc.yaml deleted file mode 100644 index 4504ebc..0000000 --- a/apps/ghost/svc.yaml +++ /dev/null @@ -1,26 +0,0 @@ ---- -# Source: ghost/templates/svc.yaml -apiVersion: v1 -kind: Service -metadata: - name: ghost - namespace: "default" - labels: - app.kubernetes.io/instance: ghost - app.kubernetes.io/managed-by: Wild - app.kubernetes.io/name: ghost - app.kubernetes.io/version: 5.118.1 - app.kubernetes.io/component: ghost -spec: - type: LoadBalancer - externalTrafficPolicy: "Cluster" - sessionAffinity: None - ports: - - name: http - port: 80 - protocol: TCP - targetPort: http - selector: - app.kubernetes.io/instance: ghost - app.kubernetes.io/name: ghost - app.kubernetes.io/component: ghost