From 1743815703ef464383f84356b8c18a283f424834 Mon Sep 17 00:00:00 2001 From: CTO Date: Sat, 5 Sep 2026 09:38:25 +0000 Subject: [PATCH] feat(vikunja): deploy Vikunja 2.6.0 with Pocket ID OIDC (DEV-560) Namespace-scoped Postgres 16 + unified Vikunja container behind Traefik ingress at vikunja.basicstack.de. Both pods pinned to Hetzner fsn1 due to their encrypted PVCs (10 Gi DB, 10 Gi attachments). Local auth disabled; sign-in only via Pocket ID (client `Vikunja`, groups `vikunja_admins` / `vikunja_members`). SMTP mailer left disabled pending mailbox provisioning. Co-Authored-By: Paperclip --- apps/app-vikunja.yaml | 20 +++ apps/vikunja/README.md | 36 +++++ apps/vikunja/vikunja-app-secrets-sealed.yaml | 15 ++ .../vikunja-db-credentials-sealed.yaml | 15 ++ apps/vikunja/vikunja-db-pvc.yaml | 15 ++ apps/vikunja/vikunja-deployment.yaml | 131 ++++++++++++++++++ apps/vikunja/vikunja-files-pvc.yaml | 15 ++ apps/vikunja/vikunja-ingress.yaml | 26 ++++ apps/vikunja/vikunja-oidc-secret-sealed.yaml | 16 +++ apps/vikunja/vikunja-postgres-deployment.yaml | 79 +++++++++++ apps/vikunja/vikunja-postgres-service.yaml | 17 +++ apps/vikunja/vikunja-service.yaml | 17 +++ 12 files changed, 402 insertions(+) create mode 100644 apps/app-vikunja.yaml create mode 100644 apps/vikunja/README.md create mode 100644 apps/vikunja/vikunja-app-secrets-sealed.yaml create mode 100644 apps/vikunja/vikunja-db-credentials-sealed.yaml create mode 100644 apps/vikunja/vikunja-db-pvc.yaml create mode 100644 apps/vikunja/vikunja-deployment.yaml create mode 100644 apps/vikunja/vikunja-files-pvc.yaml create mode 100644 apps/vikunja/vikunja-ingress.yaml create mode 100644 apps/vikunja/vikunja-oidc-secret-sealed.yaml create mode 100644 apps/vikunja/vikunja-postgres-deployment.yaml create mode 100644 apps/vikunja/vikunja-postgres-service.yaml create mode 100644 apps/vikunja/vikunja-service.yaml diff --git a/apps/app-vikunja.yaml b/apps/app-vikunja.yaml new file mode 100644 index 0000000..558ef1e --- /dev/null +++ b/apps/app-vikunja.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: vikunja + namespace: argocd +spec: + project: default + source: + repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git + targetRevision: main + path: apps/vikunja + destination: + server: https://kubernetes.default.svc + namespace: vikunja + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/apps/vikunja/README.md b/apps/vikunja/README.md new file mode 100644 index 0000000..a32c0ac --- /dev/null +++ b/apps/vikunja/README.md @@ -0,0 +1,36 @@ +# Vikunja (vikunja.basicstack.de) + +Self-hosted task manager. Unified `vikunja/vikunja` container (API + frontend on port 3456). Backed by a namespace-local PostgreSQL. OIDC via Pocket ID (`auth.basicstack.de`); local login disabled. + +## Layout + +| File | Purpose | +|---|---| +| `vikunja-db-pvc.yaml` | 10 Gi encrypted PVC for Postgres data | +| `vikunja-files-pvc.yaml` | 10 Gi encrypted PVC for user-uploaded attachments (`/app/vikunja/files`) | +| `vikunja-postgres-deployment.yaml` | Postgres 16, pinned to `csi.hetzner.cloud/location=fsn1` | +| `vikunja-postgres-service.yaml` | ClusterIP `vikunja-postgres:5432` | +| `vikunja-deployment.yaml` | Vikunja app, pinned to `fsn1`, waits on Postgres | +| `vikunja-service.yaml` | ClusterIP `vikunja:80` → pod `3456` | +| `vikunja-ingress.yaml` | Traefik + `letsencrypt-prod` cert | +| `vikunja-db-credentials-sealed.yaml` | Postgres user password | +| `vikunja-oidc-secret-sealed.yaml` | Pocket ID client id + secret | +| `vikunja-app-secrets-sealed.yaml` | Vikunja JWT secret | + +## Node placement + +Both the app and Postgres pin to Hetzner location `fsn1` because their PVCs live on `hcloud-volumes-encrypted` (datacenter-local). + +## OIDC + +- Pocket ID client `Vikunja` (id `cdfff4ee-28ba-4aab-9f47-7e41b4b6b847`), callback `https://vikunja.basicstack.de/auth/openid/basicstack`. +- Pocket ID groups `vikunja_admins` and `vikunja_members`. +- Vikunja does not map OIDC groups to the admin flag. Promote the first user post-login: `kubectl -n vikunja exec deploy/vikunja -- /app/vikunja/vikunja user chmod --admin `. + +## Optional: SMTP + +`VIKUNJA_MAILER_ENABLED=false` today. To enable reminder emails, create a Stalwart mailbox `vikunja@basicstack.de`, add its credentials as a sealed secret `vikunja-mailer`, and set `VIKUNJA_MAILER_ENABLED=true` plus host/port/from env vars. + +## Change control + +Manifest changes flow through Argo CD (Application: `apps/app-vikunja.yaml`, auto-sync + prune + selfHeal). Sealed secrets are encrypted against the cluster-active sealed-secrets key. diff --git a/apps/vikunja/vikunja-app-secrets-sealed.yaml b/apps/vikunja/vikunja-app-secrets-sealed.yaml new file mode 100644 index 0000000..2fd4a2b --- /dev/null +++ b/apps/vikunja/vikunja-app-secrets-sealed.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: vikunja-app-secrets + namespace: vikunja +spec: + encryptedData: + jwt-secret: AgA2gSMP5tLy+2BeEe/vO3DHi7v4WO2HRZc2kTD/+BRII/CxCd3SIJYOh5O7OPNOq5Vp+F6oCi6GUg7ljyXuHU1IlHSSYWfjdGKS/mIvlKf2XRASOS1CefNSDKjU+x2FB3LPJG10v/0OG7bLKCj7QGq5y+QNc8dQrV7kqwAonG18iZzl5rcL4vN38B2V6kaO+s/tcz0uwmVOlUqblmv6WwrkMm5bVt9RQxcO5UjmDuf2KlnHBgvhRUhcBCbmrnI7J+FkVtq+2hiWwyz3hXAQAyy6ULMltYZvSy4ADseTV3yBdArUvY9e13kwfVZBjLe5EBzn0SKoQyY0XZQUYDmfQU+U5zFxsZLbYeekf7FUN8zHJ+mgvrT35PGrVazoz2rKI+9BrA7sstYJZ0ZUlj+nKnrbHYyaUhrlu3DYFvTHCOz8ylRxIsM5Ux819+RISpBhKkg6+mIhH2L26larI7UEY576c83TMhmCEOeX0es+MoomsAqeQi8vZ+OT5qz9dFZpzshXmwRYy1Ezm9d992iBF9EskB9IuyaozpA+GU9WwKAOj5GF3izj89nel9QPPQVBGHYvxXXfaXi3yQIpqD54nVPKC1oEhPNeCfVp3YcGSd/HOsl3tT/8G7ipD40jGVk5tgWxpymbvNZmcpm1CIb4SPYHOavJtqBcPY1F9k8a5yYl9sMqd64qzDmTlOQhRVWjD0mqCf2W/W3OvHo4dTu6Fy95jHrzoHsmmfNQQDVytCJK/3SmYUvh51nD8/CyiJxRAq3zPViFHKmRS0E5oCR0+zm3GlZTGwUmu1ROSIlsDgik/khmrZvYFLtF + template: + metadata: + creationTimestamp: null + name: vikunja-app-secrets + namespace: vikunja diff --git a/apps/vikunja/vikunja-db-credentials-sealed.yaml b/apps/vikunja/vikunja-db-credentials-sealed.yaml new file mode 100644 index 0000000..6237aa6 --- /dev/null +++ b/apps/vikunja/vikunja-db-credentials-sealed.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: vikunja-db-credentials + namespace: vikunja +spec: + encryptedData: + postgres-password: AgB+P7iBg+tPIDKMGmiPh9HREVb7mKCf1GvQfBLcF61QuWHt0/S1YBYCd4fF3PSA9ok916m6VeOoIQKB0p57XDSEeNewYNsXQgudS0k0JHNdRZER8JhbXpK53E7EY0xw4TD8Tc6WFqyzCFwsL1WpYvvDH3p25W8f3JJfB39D6Up9j5xT5Hufi0G5BJ+tbAujNJooa0SWZbtQPxMWG96fvjmTV5FKxzvwg1JOL4gq0JHBfzo9sKIbQdGpGJjX4JkPLCxA0jU6mcJKEFn80zkDbQBL3g5dXZaYmIAEogZkvytltR4ebI0zyDrSTPqXhJF5bFcRpM43VXz6ZcjgdtG5VPelvAuMqfJ1U7HdYhhJqTt0lvwUi03BjulIJJ52gUjlg2yQioOwfON/y3gRM1TgMHMFCyw32S7vCfR00MKVh7xrb4YceRUTbeHkTd+4TMcdpI11YHeah2scu82tzyD/cGPtpFv2LYyH7JrBcIXXhfIDccBRX4bb2se9VyhpmvEP5Q9SQjazo/BxdnJUYF6kzpJA9oOIjFMXcSDxZh/nwDkA90w4a9iyT9l0+7p3z/RjbXOMKybuQlXpfPrhtCRS6PM3Am5sgnIi/APRaujDa/2a3kCe7K0zZl4Wk3wlAc4mMjnoWs9w//HfMpQByM6GWeHWeoQ/Bj3xCVtACb6SNqTxIs4OP9hAVBnHqouH0O/zjqZNuBa5v7GDwmW6JU1cA+Y/UI4Z/ycu2X6AXBHp5CROheV9PdINTrgS + template: + metadata: + creationTimestamp: null + name: vikunja-db-credentials + namespace: vikunja diff --git a/apps/vikunja/vikunja-db-pvc.yaml b/apps/vikunja/vikunja-db-pvc.yaml new file mode 100644 index 0000000..01664a7 --- /dev/null +++ b/apps/vikunja/vikunja-db-pvc.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: vikunja-db + namespace: vikunja + labels: + app: vikunja + component: database +spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 10Gi diff --git a/apps/vikunja/vikunja-deployment.yaml b/apps/vikunja/vikunja-deployment.yaml new file mode 100644 index 0000000..071a543 --- /dev/null +++ b/apps/vikunja/vikunja-deployment.yaml @@ -0,0 +1,131 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: vikunja + namespace: vikunja + labels: + app: vikunja + component: app +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: vikunja + component: app + template: + metadata: + labels: + app: vikunja + component: app + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: csi.hetzner.cloud/location + operator: In + values: + - fsn1 + initContainers: + - name: wait-for-postgres + image: postgres:16-alpine + command: + - sh + - -c + - | + until pg_isready -h vikunja-postgres -U vikunja; do + echo "Waiting for PostgreSQL..." + sleep 2 + done + securityContext: + fsGroup: 1000 + containers: + - name: vikunja + image: vikunja/vikunja:2.6.0 + ports: + - name: http + containerPort: 3456 + env: + - name: VIKUNJA_SERVICE_PUBLICURL + value: https://vikunja.basicstack.de/ + - name: VIKUNJA_SERVICE_JWTSECRET + valueFrom: + secretKeyRef: + name: vikunja-app-secrets + key: jwt-secret + - name: VIKUNJA_SERVICE_ENABLEREGISTRATION + value: "false" + - name: VIKUNJA_SERVICE_TIMEZONE + value: Europe/Berlin + - name: VIKUNJA_DATABASE_TYPE + value: postgres + - name: VIKUNJA_DATABASE_HOST + value: vikunja-postgres + - name: VIKUNJA_DATABASE_PORT + value: "5432" + - name: VIKUNJA_DATABASE_DATABASE + value: vikunja + - name: VIKUNJA_DATABASE_USER + value: vikunja + - name: VIKUNJA_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: vikunja-db-credentials + key: postgres-password + - name: VIKUNJA_DATABASE_SSLMODE + value: disable + - name: VIKUNJA_FILES_BASEPATH + value: /app/vikunja/files + - name: VIKUNJA_AUTH_LOCAL_ENABLED + value: "false" + - name: VIKUNJA_AUTH_OPENID_ENABLED + value: "true" + - name: VIKUNJA_AUTH_OPENID_PROVIDERS_BASICSTACK_NAME + value: BasicStack SSO + - name: VIKUNJA_AUTH_OPENID_PROVIDERS_BASICSTACK_AUTHURL + value: https://auth.basicstack.de + - name: VIKUNJA_AUTH_OPENID_PROVIDERS_BASICSTACK_SCOPE + value: openid profile email + - name: VIKUNJA_AUTH_OPENID_PROVIDERS_BASICSTACK_CLIENTID + valueFrom: + secretKeyRef: + name: vikunja-oidc-secret + key: client-id + - name: VIKUNJA_AUTH_OPENID_PROVIDERS_BASICSTACK_CLIENTSECRET + valueFrom: + secretKeyRef: + name: vikunja-oidc-secret + key: client-secret + - name: VIKUNJA_MAILER_ENABLED + value: "false" + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: "1" + memory: 512Mi + livenessProbe: + httpGet: + path: /api/v1/info + port: 3456 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /api/v1/info + port: 3456 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + volumeMounts: + - name: files + mountPath: /app/vikunja/files + volumes: + - name: files + persistentVolumeClaim: + claimName: vikunja-files diff --git a/apps/vikunja/vikunja-files-pvc.yaml b/apps/vikunja/vikunja-files-pvc.yaml new file mode 100644 index 0000000..1f830cf --- /dev/null +++ b/apps/vikunja/vikunja-files-pvc.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: vikunja-files + namespace: vikunja + labels: + app: vikunja + component: files +spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 10Gi diff --git a/apps/vikunja/vikunja-ingress.yaml b/apps/vikunja/vikunja-ingress.yaml new file mode 100644 index 0000000..d3db329 --- /dev/null +++ b/apps/vikunja/vikunja-ingress.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: vikunja + namespace: vikunja + labels: + app: vikunja + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod +spec: + ingressClassName: traefik + tls: + - hosts: + - vikunja.basicstack.de + secretName: vikunja-tls + rules: + - host: vikunja.basicstack.de + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: vikunja + port: + number: 80 diff --git a/apps/vikunja/vikunja-oidc-secret-sealed.yaml b/apps/vikunja/vikunja-oidc-secret-sealed.yaml new file mode 100644 index 0000000..b4350d7 --- /dev/null +++ b/apps/vikunja/vikunja-oidc-secret-sealed.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: vikunja-oidc-secret + namespace: vikunja +spec: + encryptedData: + client-id: AgBdzdW0SxhmOyhOpC/jkBJ4UtD0qrRrytVp7fInK3bxiiVBHsIU8r3Hoe2iYZU75IyITogmcGImKH9pivxPMhj5jBqFeCExQadt/pqXHL9TmxmNpIx7FmnlrxaZ+zeqeKRMDhYJnxSeNiuRDVZEEnsBhmUc0gvz8Pq/x82ExY++MQjQQu2xB6XQ7DTyzVDx32yZAvBU6aRFhTtMTqPAvjsMHp6pKmciauS7LmffCVIJvnJUaUKIELHPDyWsMcQvWpPQrPKGkvmGruhXbct6LxPFKdkbsQMUWvj7emSemmbJk0VmGPtVxI7cTmHIJ6l4kr8aJfJ9fv2gMzbgprYVZHJ17qhPvvUv5iTUcYu5FiMiy3QtNHYMP+VyhWvj9aUH8ypFYwf0S3/fRobEtyBxnBLCmnIt4lvS0ABQ49YwdtWnBUXT1jP4Xzk3gWM8fe1LsOuUZXMgFoGMXtxuodhZFhTELjdKPaYTY/DDU6iRMpk/nUsQy1IGls1CySEB+taiQDRNMZmQAFHdoFOxX8sZyPKdYL3wgKDweqcLoI0UKjapkHdpcoWccqg0ky/f5VWWlD7kHWLXYeosH6ucLAKR/mRHoyOfIy1dEpYYevfv5UM4Lewnr8epLse/x3ymsBNaSIhxt8PxnFYe/Gi1rO8MAgTPNcw2pr2z5jiW8CXUPLdXezleoctl4VGg97pkYVibn0BJ+VrZOTgO/p/df5D0v7h7DSsvdbpA7XxkaTnL62JRhC3RDXU= + client-secret: AgA3ZQ5Da1D2tcNBA9RVf7YYNLBvBjKf+OArLLQSKKUDGAULoC0BQUn+T7rctuOANFeKLOYE2OjJQhURoi69xFyQ3d868s4a2Ipo3QwUV2vtoVJnz7l2fGXCzAOTcJhKonHdJooklfeK+gwi80sRXV1IxxDu778yKSWnFUd61vUPbhKgVgtXDM4IItNtctGc4Drz6dvrOHkyM1m4kxGbXUclpN+HztufQrO7W+ba23bZRCejaYz3sQ6IHmM50HXzRPD5Rdb2ALQFm9V7zdSuUQ8njNrXHfgqMSiDWoCzdpTKG50mBPqmait/yyNVnOAd8J906RZk27LnAY9zV1WiMUSImNS6IzuLV0DZTn6I4cWgnemIwNAdNR4/x7TwWNdVS3A3jH8q6S1IP5g65P40SmP27PfFx+L7jx72Om8HXBY8TpMtWwGFcjyEGy61tKuimXd65+o0UkXEmQfJKfnugnSM3yCufEat2PIE2+Arb7+pRh6VtzaBowLdo32OcqS5GMmFLhisv5cR016IIIbSnaLkIGpiZEHSvpcLoGOJm8H+1P6MPsHWfjWtKZy6y8Ri/B9zNHlUx506Kij5YOR+tH3B3ip4PIa2LRlRCAbzBb/WCwzP72WzK8PU/sSujTa+JgxBoNGt3fg2iqBmoaU3UrhVoTTcXhEn7pnvIlk60jZKTscC+thS++VHMnf7LjLAA2ksu9G6qKpWy5DAERYi/MC6YdYz0eoIBSa/NEHLi94zFA== + template: + metadata: + creationTimestamp: null + name: vikunja-oidc-secret + namespace: vikunja diff --git a/apps/vikunja/vikunja-postgres-deployment.yaml b/apps/vikunja/vikunja-postgres-deployment.yaml new file mode 100644 index 0000000..5c80921 --- /dev/null +++ b/apps/vikunja/vikunja-postgres-deployment.yaml @@ -0,0 +1,79 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: vikunja-postgres + namespace: vikunja + labels: + app: vikunja + component: database +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: vikunja + component: database + template: + metadata: + labels: + app: vikunja + component: database + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: csi.hetzner.cloud/location + operator: In + values: + - fsn1 + containers: + - name: postgres + image: postgres:16-alpine + ports: + - name: postgres + containerPort: 5432 + env: + - name: POSTGRES_DB + value: vikunja + - name: POSTGRES_USER + value: vikunja + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: vikunja-db-credentials + key: postgres-password + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: "1" + memory: 1Gi + livenessProbe: + exec: + command: + - pg_isready + - -U + - vikunja + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + exec: + command: + - pg_isready + - -U + - vikunja + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + persistentVolumeClaim: + claimName: vikunja-db diff --git a/apps/vikunja/vikunja-postgres-service.yaml b/apps/vikunja/vikunja-postgres-service.yaml new file mode 100644 index 0000000..b1bc07c --- /dev/null +++ b/apps/vikunja/vikunja-postgres-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: vikunja-postgres + namespace: vikunja + labels: + app: vikunja + component: database +spec: + type: ClusterIP + selector: + app: vikunja + component: database + ports: + - name: postgres + port: 5432 + targetPort: 5432 diff --git a/apps/vikunja/vikunja-service.yaml b/apps/vikunja/vikunja-service.yaml new file mode 100644 index 0000000..dee7bae --- /dev/null +++ b/apps/vikunja/vikunja-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: vikunja + namespace: vikunja + labels: + app: vikunja + component: app +spec: + type: ClusterIP + selector: + app: vikunja + component: app + ports: + - name: http + port: 80 + targetPort: 3456