diff --git a/clusters/atlas/flux-system/applications/kustomization.yaml b/clusters/atlas/flux-system/applications/kustomization.yaml index 5876064..cc98f6b 100644 --- a/clusters/atlas/flux-system/applications/kustomization.yaml +++ b/clusters/atlas/flux-system/applications/kustomization.yaml @@ -25,3 +25,6 @@ resources: - ai-llm/kustomization.yaml - nextcloud/kustomization.yaml - nextcloud-mail-sync/kustomization.yaml + - minio/kustomization.yaml + - outline/kustomization.yaml + - planka/kustomization.yaml diff --git a/clusters/atlas/flux-system/applications/minio/kustomization.yaml b/clusters/atlas/flux-system/applications/minio/kustomization.yaml new file mode 100644 index 0000000..e776a3e --- /dev/null +++ b/clusters/atlas/flux-system/applications/minio/kustomization.yaml @@ -0,0 +1,24 @@ +# clusters/atlas/flux-system/applications/minio/kustomization.yaml +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: minio + namespace: flux-system +spec: + interval: 10m + path: ./services/minio + prune: true + sourceRef: + kind: GitRepository + name: flux-system + targetNamespace: minio + healthChecks: + - apiVersion: apps/v1 + kind: Deployment + name: minio + namespace: minio + - apiVersion: v1 + kind: Service + name: minio + namespace: minio + wait: false diff --git a/clusters/atlas/flux-system/applications/outline/kustomization.yaml b/clusters/atlas/flux-system/applications/outline/kustomization.yaml new file mode 100644 index 0000000..e01449b --- /dev/null +++ b/clusters/atlas/flux-system/applications/outline/kustomization.yaml @@ -0,0 +1,29 @@ +# clusters/atlas/flux-system/applications/outline/kustomization.yaml +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: outline + namespace: flux-system +spec: + interval: 10m + path: ./services/outline + prune: true + sourceRef: + kind: GitRepository + name: flux-system + targetNamespace: outline + dependsOn: + - name: keycloak + - name: mailu + - name: minio + - name: traefik + healthChecks: + - apiVersion: apps/v1 + kind: Deployment + name: outline + namespace: outline + - apiVersion: v1 + kind: Service + name: outline + namespace: outline + wait: false diff --git a/clusters/atlas/flux-system/applications/planka/kustomization.yaml b/clusters/atlas/flux-system/applications/planka/kustomization.yaml new file mode 100644 index 0000000..5219a5d --- /dev/null +++ b/clusters/atlas/flux-system/applications/planka/kustomization.yaml @@ -0,0 +1,28 @@ +# clusters/atlas/flux-system/applications/planka/kustomization.yaml +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: planka + namespace: flux-system +spec: + interval: 10m + path: ./services/planka + prune: true + sourceRef: + kind: GitRepository + name: flux-system + targetNamespace: planka + dependsOn: + - name: keycloak + - name: mailu + - name: traefik + healthChecks: + - apiVersion: apps/v1 + kind: Deployment + name: planka + namespace: planka + - apiVersion: v1 + kind: Service + name: planka + namespace: planka + wait: false diff --git a/services/minio/bucket-job.yaml b/services/minio/bucket-job.yaml new file mode 100644 index 0000000..a17193d --- /dev/null +++ b/services/minio/bucket-job.yaml @@ -0,0 +1,50 @@ +# services/minio/bucket-job.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: minio-bucket-bootstrap-1 + namespace: minio +spec: + backoffLimit: 1 + ttlSecondsAfterFinished: 3600 + template: + spec: + restartPolicy: Never + nodeSelector: + node-role.kubernetes.io/worker: "true" + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: hardware + operator: In + values: ["rpi4", "rpi5"] + containers: + - name: mc + image: minio/mc:RELEASE.2025-08-13T08-35-41Z + command: ["/bin/sh", "-c"] + args: + - | + set -euo pipefail + mc alias set local http://minio.minio.svc.cluster.local:9000 "${MINIO_ROOT_USER}" "${MINIO_ROOT_PASSWORD}" + mc mb -p local/outline || true + mc mb -p local/planka || true + env: + - name: MINIO_ROOT_USER + valueFrom: + secretKeyRef: + name: minio-credentials + key: rootUser + - name: MINIO_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: minio-credentials + key: rootPassword + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi diff --git a/services/minio/deployment.yaml b/services/minio/deployment.yaml new file mode 100644 index 0000000..4b6d72a --- /dev/null +++ b/services/minio/deployment.yaml @@ -0,0 +1,68 @@ +# services/minio/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: minio + namespace: minio + labels: + app: minio +spec: + replicas: 1 + selector: + matchLabels: + app: minio + strategy: + type: Recreate + template: + metadata: + labels: + app: minio + spec: + nodeSelector: + node-role.kubernetes.io/worker: "true" + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: hardware + operator: In + values: ["rpi4", "rpi5"] + containers: + - name: minio + image: minio/minio:RELEASE.2025-09-07T16-13-09Z + args: + - server + - /data + - --console-address + - ":9001" + env: + - name: MINIO_ROOT_USER + valueFrom: + secretKeyRef: + name: minio-credentials + key: rootUser + - name: MINIO_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: minio-credentials + key: rootPassword + ports: + - name: api + containerPort: 9000 + - name: console + containerPort: 9001 + volumeMounts: + - name: data + mountPath: /data + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: "1" + memory: 2Gi + volumes: + - name: data + persistentVolumeClaim: + claimName: minio-data diff --git a/services/minio/kustomization.yaml b/services/minio/kustomization.yaml new file mode 100644 index 0000000..0565e31 --- /dev/null +++ b/services/minio/kustomization.yaml @@ -0,0 +1,10 @@ +# services/minio/kustomization.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: minio +resources: + - namespace.yaml + - pvc.yaml + - deployment.yaml + - bucket-job.yaml + - service.yaml diff --git a/services/minio/namespace.yaml b/services/minio/namespace.yaml new file mode 100644 index 0000000..3465488 --- /dev/null +++ b/services/minio/namespace.yaml @@ -0,0 +1,5 @@ +# services/minio/namespace.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: minio diff --git a/services/minio/pvc.yaml b/services/minio/pvc.yaml new file mode 100644 index 0000000..5c3828b --- /dev/null +++ b/services/minio/pvc.yaml @@ -0,0 +1,12 @@ +# services/minio/pvc.yaml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: minio-data + namespace: minio +spec: + accessModes: ["ReadWriteOnce"] + storageClassName: asteria + resources: + requests: + storage: 100Gi diff --git a/services/minio/service.yaml b/services/minio/service.yaml new file mode 100644 index 0000000..d2edec5 --- /dev/null +++ b/services/minio/service.yaml @@ -0,0 +1,18 @@ +# services/minio/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: minio + namespace: minio + labels: + app: minio +spec: + selector: + app: minio + ports: + - name: api + port: 9000 + targetPort: api + - name: console + port: 9001 + targetPort: console diff --git a/services/outline/app-pvc.yaml b/services/outline/app-pvc.yaml new file mode 100644 index 0000000..8ac702c --- /dev/null +++ b/services/outline/app-pvc.yaml @@ -0,0 +1,12 @@ +# services/outline/app-pvc.yaml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: outline-app + namespace: outline +spec: + accessModes: ["ReadWriteOnce"] + storageClassName: astreae + resources: + requests: + storage: 5Gi diff --git a/services/outline/deployment.yaml b/services/outline/deployment.yaml new file mode 100644 index 0000000..74f3f35 --- /dev/null +++ b/services/outline/deployment.yaml @@ -0,0 +1,111 @@ +# services/outline/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: outline + namespace: outline + labels: + app: outline +spec: + replicas: 1 + selector: + matchLabels: + app: outline + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + template: + metadata: + labels: + app: outline + spec: + nodeSelector: + node-role.kubernetes.io/worker: "true" + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: hardware + operator: In + values: ["rpi4", "rpi5"] + containers: + - name: outline + image: outlinewiki/outline:1.2.0 + ports: + - name: http + containerPort: 3000 + env: + - name: NODE_ENV + value: production + - name: URL + value: https://notes.bstein.dev + - name: PORT + value: "3000" + - name: REDIS_URL + value: redis://outline-redis:6379 + - name: FILE_STORAGE + value: s3 + - name: AWS_REGION + value: us-east-1 + - name: AWS_S3_FORCE_PATH_STYLE + value: "true" + - name: AWS_S3_ACL + value: private + - name: FORCE_HTTPS + value: "true" + - name: OIDC_ENFORCED + value: "true" + - name: OIDC_SCOPES + value: openid profile email + - name: OIDC_USERNAME_CLAIM + value: preferred_username + - name: OIDC_DISPLAY_NAME + value: Atlas SSO + - name: SMTP_SECURE + value: "false" + - name: SMTP_PORT + value: "25" + envFrom: + - secretRef: + name: outline-db + - secretRef: + name: outline-secrets + - secretRef: + name: outline-oidc + - secretRef: + name: outline-s3 + - secretRef: + name: outline-smtp + volumeMounts: + - name: app-data + mountPath: /var/lib/outline + readinessProbe: + httpGet: + path: /_health + port: http + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 6 + livenessProbe: + httpGet: + path: /_health + port: http + initialDelaySeconds: 30 + periodSeconds: 20 + timeoutSeconds: 3 + failureThreshold: 6 + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: "1" + memory: 2Gi + volumes: + - name: app-data + persistentVolumeClaim: + claimName: outline-app diff --git a/services/outline/ingress.yaml b/services/outline/ingress.yaml new file mode 100644 index 0000000..735baae --- /dev/null +++ b/services/outline/ingress.yaml @@ -0,0 +1,26 @@ +# services/outline/ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: outline + namespace: outline + annotations: + kubernetes.io/ingress.class: traefik + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + cert-manager.io/cluster-issuer: letsencrypt +spec: + tls: + - hosts: ["notes.bstein.dev"] + secretName: outline-tls + rules: + - host: notes.bstein.dev + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: outline + port: + number: 80 diff --git a/services/outline/kustomization.yaml b/services/outline/kustomization.yaml new file mode 100644 index 0000000..941cb91 --- /dev/null +++ b/services/outline/kustomization.yaml @@ -0,0 +1,12 @@ +# services/outline/kustomization.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: outline +resources: + - namespace.yaml + - app-pvc.yaml + - redis-deployment.yaml + - redis-service.yaml + - deployment.yaml + - service.yaml + - ingress.yaml diff --git a/services/outline/namespace.yaml b/services/outline/namespace.yaml new file mode 100644 index 0000000..4172c02 --- /dev/null +++ b/services/outline/namespace.yaml @@ -0,0 +1,5 @@ +# services/outline/namespace.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: outline diff --git a/services/outline/redis-deployment.yaml b/services/outline/redis-deployment.yaml new file mode 100644 index 0000000..5e08128 --- /dev/null +++ b/services/outline/redis-deployment.yaml @@ -0,0 +1,47 @@ +# services/outline/redis-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: outline-redis + namespace: outline + labels: + app: outline-redis +spec: + replicas: 1 + selector: + matchLabels: + app: outline-redis + template: + metadata: + labels: + app: outline-redis + spec: + nodeSelector: + node-role.kubernetes.io/worker: "true" + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: hardware + operator: In + values: ["rpi4", "rpi5"] + containers: + - name: redis + image: redis:7.4.1-alpine + ports: + - name: redis + containerPort: 6379 + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 250m + memory: 256Mi + volumeMounts: + - name: data + mountPath: /data + volumes: + - name: data + emptyDir: {} diff --git a/services/outline/redis-service.yaml b/services/outline/redis-service.yaml new file mode 100644 index 0000000..a80def2 --- /dev/null +++ b/services/outline/redis-service.yaml @@ -0,0 +1,15 @@ +# services/outline/redis-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: outline-redis + namespace: outline + labels: + app: outline-redis +spec: + selector: + app: outline-redis + ports: + - name: redis + port: 6379 + targetPort: redis diff --git a/services/outline/service.yaml b/services/outline/service.yaml new file mode 100644 index 0000000..383df0e --- /dev/null +++ b/services/outline/service.yaml @@ -0,0 +1,15 @@ +# services/outline/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: outline + namespace: outline + labels: + app: outline +spec: + selector: + app: outline + ports: + - name: http + port: 80 + targetPort: http diff --git a/services/planka/app-pvc.yaml b/services/planka/app-pvc.yaml new file mode 100644 index 0000000..7ef6a91 --- /dev/null +++ b/services/planka/app-pvc.yaml @@ -0,0 +1,12 @@ +# services/planka/app-pvc.yaml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: planka-app-data + namespace: planka +spec: + accessModes: ["ReadWriteOnce"] + storageClassName: astreae + resources: + requests: + storage: 2Gi diff --git a/services/planka/deployment.yaml b/services/planka/deployment.yaml new file mode 100644 index 0000000..73b6f88 --- /dev/null +++ b/services/planka/deployment.yaml @@ -0,0 +1,95 @@ +# services/planka/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: planka + namespace: planka + labels: + app: planka +spec: + replicas: 1 + selector: + matchLabels: + app: planka + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + template: + metadata: + labels: + app: planka + spec: + nodeSelector: + node-role.kubernetes.io/worker: "true" + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: hardware + operator: In + values: ["rpi4", "rpi5"] + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + fsGroupChangePolicy: OnRootMismatch + containers: + - name: planka + image: ghcr.io/plankanban/planka:2.0.0-rc.4 + ports: + - name: http + containerPort: 1337 + env: + - name: BASE_URL + value: https://tasks.bstein.dev + - name: TRUST_PROXY + value: "true" + envFrom: + - secretRef: + name: planka-db + - secretRef: + name: planka-secrets + - secretRef: + name: planka-oidc + - secretRef: + name: planka-smtp + volumeMounts: + - name: user-data + mountPath: /app/public + - name: user-data + mountPath: /app/private + - name: app-data + mountPath: /app/.tmp + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 6 + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 30 + periodSeconds: 20 + timeoutSeconds: 3 + failureThreshold: 6 + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: "1" + memory: 2Gi + volumes: + - name: user-data + persistentVolumeClaim: + claimName: planka-user-data + - name: app-data + persistentVolumeClaim: + claimName: planka-app-data diff --git a/services/planka/ingress.yaml b/services/planka/ingress.yaml new file mode 100644 index 0000000..7bd2912 --- /dev/null +++ b/services/planka/ingress.yaml @@ -0,0 +1,26 @@ +# services/planka/ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: planka + namespace: planka + annotations: + kubernetes.io/ingress.class: traefik + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + cert-manager.io/cluster-issuer: letsencrypt +spec: + tls: + - hosts: ["tasks.bstein.dev"] + secretName: planka-tls + rules: + - host: tasks.bstein.dev + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: planka + port: + number: 80 diff --git a/services/planka/kustomization.yaml b/services/planka/kustomization.yaml new file mode 100644 index 0000000..ab42954 --- /dev/null +++ b/services/planka/kustomization.yaml @@ -0,0 +1,11 @@ +# services/planka/kustomization.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: planka +resources: + - namespace.yaml + - user-data-pvc.yaml + - app-pvc.yaml + - deployment.yaml + - service.yaml + - ingress.yaml diff --git a/services/planka/namespace.yaml b/services/planka/namespace.yaml new file mode 100644 index 0000000..6a56e21 --- /dev/null +++ b/services/planka/namespace.yaml @@ -0,0 +1,5 @@ +# services/planka/namespace.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: planka diff --git a/services/planka/service.yaml b/services/planka/service.yaml new file mode 100644 index 0000000..6abf6cf --- /dev/null +++ b/services/planka/service.yaml @@ -0,0 +1,15 @@ +# services/planka/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: planka + namespace: planka + labels: + app: planka +spec: + selector: + app: planka + ports: + - name: http + port: 80 + targetPort: http diff --git a/services/planka/user-data-pvc.yaml b/services/planka/user-data-pvc.yaml new file mode 100644 index 0000000..760f33c --- /dev/null +++ b/services/planka/user-data-pvc.yaml @@ -0,0 +1,12 @@ +# services/planka/user-data-pvc.yaml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: planka-user-data + namespace: planka +spec: + accessModes: ["ReadWriteOnce"] + storageClassName: asteria + resources: + requests: + storage: 20Gi