diff --git a/apps/dashwise/deployment.yaml b/apps/dashwise/deployment.yaml new file mode 100644 index 0000000..0d9388d --- /dev/null +++ b/apps/dashwise/deployment.yaml @@ -0,0 +1,94 @@ +# Dashwise all-in-one: the container runs the web/API server (3000), a bundled +# PocketBase (8090) and valkey, all on localhost inside the pod. +# +# Only 3000 is exposed. The browser never talks to PocketBase directly -- the +# frontend resolves its backend as window.location.origin and every +# PocketBase call is made server-side -- so 8090 stays internal. +# +# NOTE: :latest and :stable are built for amd64 ONLY and will not run on this +# arm64 cluster. Use a tag that publishes an arm64 manifest. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dashwise + namespace: dashwise + labels: + app.kubernetes.io/name: dashwise +spec: + replicas: 1 + revisionHistoryLimit: 3 + strategy: + # the PV is ReadWriteOnce and PocketBase is a single-writer SQLite store, + # so the old pod has to go before the new one starts + type: Recreate + selector: + matchLabels: + app.kubernetes.io/name: dashwise + template: + metadata: + labels: + app.kubernetes.io/name: dashwise + spec: + containers: + - name: dashwise + image: andreasmolnardev/dashwise:v1.0-rc1 + imagePullPolicy: IfNotPresent + env: + - name: ENVIRONMENT + value: production + - name: INSTANCE_NAME + value: Witty One Off + - name: START_POCKETBASE + value: "true" + - name: PB_BINARY_PATH + value: /usr/local/bin/pocketbase + # in-pod addresses: the server talks to PocketBase over localhost + - name: PB_URL + value: http://127.0.0.1:8090 + - name: NEXT_PUBLIC_PB_URL + value: http://127.0.0.1:8090 + - name: APP_BASE_URL + value: https://home.wittyoneoff.com + - name: NEXT_PUBLIC_APP_URL + value: https://home.wittyoneoff.com + - name: PB_ADMIN_EMAIL + valueFrom: + secretKeyRef: + name: dashwise + key: pb-admin-email + - name: PB_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: dashwise + key: pb-admin-password + - name: TZ + value: America/Los_Angeles + ports: + - name: http + containerPort: 3000 + protocol: TCP + - name: pocketbase + containerPort: 8090 + protocol: TCP + startupProbe: + tcpSocket: + port: http + failureThreshold: 60 + periodSeconds: 5 + readinessProbe: + tcpSocket: + port: http + periodSeconds: 10 + failureThreshold: 3 + livenessProbe: + tcpSocket: + port: http + periodSeconds: 20 + failureThreshold: 3 + volumeMounts: + - name: data + mountPath: /app/pocketbase/pb_data + volumes: + - name: data + persistentVolumeClaim: + claimName: dashwise diff --git a/apps/dashwise/ingress.yaml b/apps/dashwise/ingress.yaml new file mode 100644 index 0000000..71fda78 --- /dev/null +++ b/apps/dashwise/ingress.yaml @@ -0,0 +1,23 @@ +# Both hostnames point at the same backend: the public one used by the EC2 +# nginx-proxy-manager, and the origin-* alias traefik matches on when NPM +# forwards to origin.wittyoneoff.com:8080. +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: dashwise + namespace: dashwise +spec: + ingressClassName: traefik + rules: + - host: home.wittyoneoff.com + http: &backend + paths: + - path: / + pathType: Prefix + backend: + service: + name: dashwise + port: + number: 3000 + - host: origin-home.wittyoneoff.com + http: *backend diff --git a/apps/dashwise/kustomization.yaml b/apps/dashwise/kustomization.yaml new file mode 100644 index 0000000..2178980 --- /dev/null +++ b/apps/dashwise/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# secret.example.yaml is intentionally NOT listed -- copy it to secret.yaml +# (gitignored), set real values, and apply it once per cluster. +resources: + - namespace.yaml + - storage.yaml + - deployment.yaml + - service.yaml + - ingress.yaml diff --git a/apps/dashwise/namespace.yaml b/apps/dashwise/namespace.yaml new file mode 100644 index 0000000..48f3ef4 --- /dev/null +++ b/apps/dashwise/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: dashwise diff --git a/apps/dashwise/secret.example.yaml b/apps/dashwise/secret.example.yaml new file mode 100644 index 0000000..6b415a8 --- /dev/null +++ b/apps/dashwise/secret.example.yaml @@ -0,0 +1,13 @@ +# Copy to secret.yaml (gitignored), set real values, and apply once: +# kubectl apply -f secret.yaml +# These are the PocketBase superuser credentials dashwise creates on first +# start; they are also what you use to reach the PocketBase admin UI. +apiVersion: v1 +kind: Secret +metadata: + name: dashwise + namespace: dashwise +type: Opaque +stringData: + pb-admin-email: you@example.com + pb-admin-password: CHANGE-ME diff --git a/apps/dashwise/service.yaml b/apps/dashwise/service.yaml new file mode 100644 index 0000000..d054f99 --- /dev/null +++ b/apps/dashwise/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: dashwise + namespace: dashwise +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: dashwise + ports: + - name: http + port: 3000 + targetPort: http + # PocketBase is deliberately NOT published through the ingress; this port + # exists so its admin UI can be reached with `kubectl port-forward`. + - name: pocketbase + port: 8090 + targetPort: pocketbase diff --git a/apps/dashwise/storage.yaml b/apps/dashwise/storage.yaml new file mode 100644 index 0000000..651eef2 --- /dev/null +++ b/apps/dashwise/storage.yaml @@ -0,0 +1,29 @@ +# PocketBase data lives on GlusterFS (/mnt/gvolume0 is mounted on every node), +# so the pod can be scheduled on any of them. +apiVersion: v1 +kind: PersistentVolume +metadata: + name: dashwise-pv +spec: + capacity: + storage: 5Gi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + volumeMode: Filesystem + hostPath: + path: /mnt/gvolume0/dashwise/pb_data +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: dashwise + namespace: dashwise +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + storageClassName: "" + volumeName: dashwise-pv diff --git a/apps/tududi/deployment.yaml b/apps/tududi/deployment.yaml new file mode 100644 index 0000000..3aeef63 --- /dev/null +++ b/apps/tududi/deployment.yaml @@ -0,0 +1,80 @@ +# tududi keeps everything in a SQLite file under /app/db and user uploads +# under /app/uploads. Both are declared VOLUMEs in the image, so both have to +# be backed by the PVC or they are lost on restart. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tududi + namespace: tududi + labels: + app.kubernetes.io/name: tududi +spec: + replicas: 1 + revisionHistoryLimit: 3 + strategy: + # single-writer SQLite on a ReadWriteOnce volume + type: Recreate + selector: + matchLabels: + app.kubernetes.io/name: tududi + template: + metadata: + labels: + app.kubernetes.io/name: tududi + spec: + containers: + - name: tududi + image: chrisvel/tududi:1.3.1 + imagePullPolicy: IfNotPresent + env: + # tududi sits behind traefik and the EC2 proxy, so it has to trust + # X-Forwarded-* to build correct URLs and mark cookies secure + - name: TUDUDI_TRUST_PROXY + value: "true" + - name: TUDUDI_USER_EMAIL + valueFrom: + secretKeyRef: + name: tududi + key: user-email + - name: TUDUDI_USER_PASSWORD + valueFrom: + secretKeyRef: + name: tududi + key: user-password + - name: TUDUDI_SESSION_SECRET + valueFrom: + secretKeyRef: + name: tududi + key: session-secret + - name: TZ + value: America/Los_Angeles + ports: + - name: http + containerPort: 3002 + protocol: TCP + startupProbe: + tcpSocket: + port: http + failureThreshold: 60 + periodSeconds: 5 + readinessProbe: + tcpSocket: + port: http + periodSeconds: 10 + failureThreshold: 3 + livenessProbe: + tcpSocket: + port: http + periodSeconds: 20 + failureThreshold: 3 + volumeMounts: + - name: data + mountPath: /app/db + subPath: db + - name: data + mountPath: /app/uploads + subPath: uploads + volumes: + - name: data + persistentVolumeClaim: + claimName: tududi diff --git a/apps/tududi/ingress.yaml b/apps/tududi/ingress.yaml new file mode 100644 index 0000000..4aa3c63 --- /dev/null +++ b/apps/tududi/ingress.yaml @@ -0,0 +1,22 @@ +# Public hostname plus the origin-* alias traefik matches on when the EC2 +# nginx-proxy-manager forwards to origin.wittyoneoff.com:8080. +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: tududi + namespace: tududi +spec: + ingressClassName: traefik + rules: + - host: tududi.wittyoneoff.com + http: &backend + paths: + - path: / + pathType: Prefix + backend: + service: + name: tududi + port: + number: 3002 + - host: origin-tududi.wittyoneoff.com + http: *backend diff --git a/apps/tududi/kustomization.yaml b/apps/tududi/kustomization.yaml new file mode 100644 index 0000000..2178980 --- /dev/null +++ b/apps/tududi/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# secret.example.yaml is intentionally NOT listed -- copy it to secret.yaml +# (gitignored), set real values, and apply it once per cluster. +resources: + - namespace.yaml + - storage.yaml + - deployment.yaml + - service.yaml + - ingress.yaml diff --git a/apps/tududi/namespace.yaml b/apps/tududi/namespace.yaml new file mode 100644 index 0000000..b000cfc --- /dev/null +++ b/apps/tududi/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: tududi diff --git a/apps/tududi/secret.example.yaml b/apps/tududi/secret.example.yaml new file mode 100644 index 0000000..731ebb2 --- /dev/null +++ b/apps/tududi/secret.example.yaml @@ -0,0 +1,15 @@ +# Copy to secret.yaml (gitignored), set real values, and apply once: +# kubectl apply -f secret.yaml +# user-email / user-password are the login tududi creates on first start. +# session-secret signs session cookies -- generate with: +# openssl rand -hex 64 +apiVersion: v1 +kind: Secret +metadata: + name: tududi + namespace: tududi +type: Opaque +stringData: + user-email: you@example.com + user-password: CHANGE-ME + session-secret: CHANGE-ME diff --git a/apps/tududi/service.yaml b/apps/tududi/service.yaml new file mode 100644 index 0000000..ce4ea4a --- /dev/null +++ b/apps/tududi/service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: tududi + namespace: tududi +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: tududi + ports: + - name: http + port: 3002 + targetPort: http diff --git a/apps/tududi/storage.yaml b/apps/tududi/storage.yaml new file mode 100644 index 0000000..40d0042 --- /dev/null +++ b/apps/tududi/storage.yaml @@ -0,0 +1,31 @@ +# One volume for both of tududi's data paths, mounted twice with subPath. +# Declaring the same PVC as two separate volume entries in one pod wedges +# kubelet's volume setup (pod stuck in Init with no events) -- that is what +# happened with limesurvey; do not split this into db-pv and uploads-pv. +apiVersion: v1 +kind: PersistentVolume +metadata: + name: tududi-pv +spec: + capacity: + storage: 5Gi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + volumeMode: Filesystem + hostPath: + path: /mnt/gvolume0/tududi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: tududi + namespace: tududi +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + storageClassName: "" + volumeName: tududi-pv diff --git a/kustomization.yaml b/kustomization.yaml index 314b5b6..5be3832 100644 --- a/kustomization.yaml +++ b/kustomization.yaml @@ -8,3 +8,5 @@ resources: - apps/pihole - apps/unbound - apps/unified-streaming + - apps/dashwise + - apps/tududi