# 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
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "planka"
        vault.hashicorp.com/agent-inject-secret-planka-env.sh: "kv/data/atlas/planka/planka-db"
        vault.hashicorp.com/agent-inject-template-planka-env.sh: |
          {{ with secret "kv/data/atlas/planka/planka-db" }}
          export DATABASE_URL="{{ .Data.data.DATABASE_URL }}"
          {{ end }}
          {{ with secret "kv/data/atlas/planka/planka-secrets" }}
          export SECRET_KEY="{{ .Data.data.SECRET_KEY }}"
          {{ end }}
          {{ with secret "kv/data/atlas/planka/planka-oidc" }}
          export OIDC_CLIENT_ID="{{ .Data.data.OIDC_CLIENT_ID }}"
          export OIDC_CLIENT_SECRET="{{ .Data.data.OIDC_CLIENT_SECRET }}"
          export OIDC_ENFORCED="{{ .Data.data.OIDC_ENFORCED }}"
          export OIDC_IGNORE_ROLES="{{ .Data.data.OIDC_IGNORE_ROLES }}"
          export OIDC_ISSUER="{{ .Data.data.OIDC_ISSUER }}"
          export OIDC_SCOPES="{{ .Data.data.OIDC_SCOPES }}"
          export OIDC_USE_OAUTH_CALLBACK="{{ .Data.data.OIDC_USE_OAUTH_CALLBACK }}"
          {{ end }}
          {{ with secret "kv/data/atlas/planka/planka-smtp" }}
          export SMTP_HOST="{{ .Data.data.SMTP_HOST }}"
          export SMTP_PORT="{{ .Data.data.SMTP_PORT }}"
          export SMTP_SECURE="{{ .Data.data.SMTP_SECURE }}"
          export SMTP_TLS_REJECT_UNAUTHORIZED="{{ .Data.data.SMTP_TLS_REJECT_UNAUTHORIZED }}"
          {{ end }}
          export SMTP_FROM="no-reply-planka@bstein.dev"
          {{ with secret "kv/data/atlas/shared/postmark-relay" }}
          export SMTP_USER="{{ index .Data.data "apikey" }}"
          export SMTP_PASSWORD="{{ index .Data.data "apikey" }}"
          {{ end }}
    spec:
      serviceAccountName: planka-vault
      nodeSelector:
        node-role.kubernetes.io/worker: "true"
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              preference:
                matchExpressions:
                  - key: atlas.bstein.dev/spillover
                    operator: DoesNotExist
            - weight: 95
              preference:
                matchExpressions:
                  - key: kubernetes.io/hostname
                    operator: NotIn
                    values:
                      - titan-13
                      - titan-15
                      - titan-17
                      - titan-19
            - weight: 90
              preference:
                matchExpressions:
                  - key: hardware
                    operator: In
                    values: ["rpi5"]
            - weight: 50
              preference:
                matchExpressions:
                  - key: hardware
                    operator: In
                    values: ["rpi4"]
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: hardware
                    operator: In
                    values: ["rpi4", "rpi5"]
      securityContext:
        runAsUser: 1000
        runAsGroup: 1000
        fsGroup: 1000
        fsGroupChangePolicy: OnRootMismatch
      initContainers:
        - name: init-user-data
          image: docker.io/alpine:3.20
          securityContext:
            runAsUser: 0
            runAsGroup: 0
          command: ["/bin/sh", "-c"]
          args:
            - |
              set -e
              mkdir -p /data/public/user-avatars \
                /data/public/background-images \
                /data/private/attachments
              chown -R 1000:1000 /data /tmp-data
          volumeMounts:
            - name: user-data
              mountPath: /data
            - name: app-data
              mountPath: /tmp-data
      containers:
        - name: planka
          image: ghcr.io/plankanban/planka:2.0.0-rc.4
          command:
            - /bin/sh
            - -c
          args:
            - . /vault/secrets/planka-env.sh && exec node app.js --prod
          ports:
            - name: http
              containerPort: 1337
          env:
            - name: BASE_URL
              value: https://tasks.bstein.dev
            - name: TRUST_PROXY
              value: "true"
            - name: OIDC_ADMIN_ROLES
              value: admin
            - name: OIDC_PROJECT_OWNER_ROLES
              value: "*"
            - name: OIDC_ROLES_ATTRIBUTE
              value: groups
          volumeMounts:
            - name: user-data
              mountPath: /app/public/user-avatars
              subPath: public/user-avatars
            - name: user-data
              mountPath: /app/public/background-images
              subPath: public/background-images
            - name: user-data
              mountPath: /app/private/attachments
              subPath: private/attachments
            - 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
