feat(lasuite): add calendars service deployment manifests

Add K8s manifests for calendars backend, frontend (Caddy), CalDAV
server, and Celery worker. Wire Pingora routing for cal.sunbeam.pt
with path-based backend/caldav/static splits. Add OAuth2Client for
OIDC, VaultDynamicSecret for DB credentials, VaultStaticSecret for
Django/CalDAV keys, and TLS cert coverage for the cal subdomain.
Register calendars in the integration service gaufre widget.
This commit is contained in:
2026-03-18 18:36:05 +00:00
parent ccfe8b877a
commit 3c7460f4a6
18 changed files with 659 additions and 0 deletions

View File

@@ -232,6 +232,46 @@ data:
backend = "http://livekit-server.media.svc.cluster.local:80"
websocket = true
[[routes]]
host_prefix = "cal"
backend = "http://calendars-frontend.lasuite.svc.cluster.local:80"
[[routes.paths]]
prefix = "/api/"
backend = "http://calendars-backend.lasuite.svc.cluster.local:80"
[[routes.paths]]
prefix = "/admin/"
backend = "http://calendars-backend.lasuite.svc.cluster.local:80"
[[routes.paths]]
prefix = "/static/"
backend = "http://calendars-backend.lasuite.svc.cluster.local:80"
[[routes.paths]]
prefix = "/caldav"
backend = "http://calendars-backend.lasuite.svc.cluster.local:80"
[[routes.paths]]
prefix = "/.well-known/caldav"
backend = "http://calendars-backend.lasuite.svc.cluster.local:80"
[[routes.paths]]
prefix = "/rsvp/"
backend = "http://calendars-backend.lasuite.svc.cluster.local:80"
[[routes.paths]]
prefix = "/ical/"
backend = "http://calendars-backend.lasuite.svc.cluster.local:80"
[[routes.paths]]
prefix = "/external_api/"
backend = "http://calendars-backend.lasuite.svc.cluster.local:80"
[[routes.paths]]
prefix = "/__"
backend = "http://calendars-backend.lasuite.svc.cluster.local:80"
[[routes]]
host_prefix = "s3"
backend = "http://seaweedfs-filer.storage.svc.cluster.local:8333"

View File

@@ -0,0 +1,163 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: calendars-backend
namespace: lasuite
spec:
replicas: 1
selector:
matchLabels:
app: calendars-backend
template:
metadata:
labels:
app: calendars-backend
spec:
initContainers:
- name: migrate
image: calendars-backend
command: ["python", "manage.py", "migrate", "--no-input"]
envFrom:
- configMapRef:
name: calendars-config
- configMapRef:
name: lasuite-postgres
- configMapRef:
name: lasuite-oidc-provider
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: calendars-db-credentials
key: password
- name: DJANGO_SECRET_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: DJANGO_SECRET_KEY
- name: SALT_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: SALT_KEY
- name: CALDAV_INBOUND_API_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: CALDAV_INBOUND_API_KEY
- name: CALDAV_OUTBOUND_API_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: CALDAV_OUTBOUND_API_KEY
- name: CALDAV_INTERNAL_API_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: CALDAV_INTERNAL_API_KEY
- name: OIDC_RP_CLIENT_ID
valueFrom:
secretKeyRef:
name: oidc-calendars
key: CLIENT_ID
- name: OIDC_RP_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: oidc-calendars
key: CLIENT_SECRET
resources:
limits:
memory: 512Mi
cpu: 500m
requests:
memory: 128Mi
cpu: 100m
containers:
- name: calendars-backend
image: calendars-backend
command:
- gunicorn
- -c
- /app/gunicorn.conf.py
- calendars.wsgi:application
ports:
- containerPort: 8000
envFrom:
- configMapRef:
name: calendars-config
- configMapRef:
name: lasuite-postgres
- configMapRef:
name: lasuite-oidc-provider
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: calendars-db-credentials
key: password
- name: DJANGO_SECRET_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: DJANGO_SECRET_KEY
- name: SALT_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: SALT_KEY
- name: CALDAV_INBOUND_API_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: CALDAV_INBOUND_API_KEY
- name: CALDAV_OUTBOUND_API_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: CALDAV_OUTBOUND_API_KEY
- name: CALDAV_INTERNAL_API_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: CALDAV_INTERNAL_API_KEY
- name: OIDC_RP_CLIENT_ID
valueFrom:
secretKeyRef:
name: oidc-calendars
key: CLIENT_ID
- name: OIDC_RP_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: oidc-calendars
key: CLIENT_SECRET
volumeMounts:
- name: theme
mountPath: /app/theme.json
subPath: theme.json
- name: translations
mountPath: /data/translations.json
subPath: translations.json
livenessProbe:
tcpSocket:
port: 8000
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
tcpSocket:
port: 8000
initialDelaySeconds: 10
periodSeconds: 10
resources:
limits:
memory: 512Mi
cpu: 500m
requests:
memory: 256Mi
cpu: 100m
volumes:
- name: theme
configMap:
name: calendars-theme
- name: translations
configMap:
name: calendars-translations

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: calendars-backend
namespace: lasuite
spec:
selector:
app: calendars-backend
ports:
- port: 80
targetPort: 8000

View File

@@ -0,0 +1,97 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: calendars-caldav
namespace: lasuite
spec:
replicas: 1
selector:
matchLabels:
app: calendars-caldav
template:
metadata:
labels:
app: calendars-caldav
spec:
initContainers:
- name: init-database
image: calendars-caldav
command: ["/usr/local/bin/init-database.sh"]
env:
- name: PGHOST
valueFrom:
configMapKeyRef:
name: lasuite-postgres
key: DB_HOST
- name: PGPORT
valueFrom:
configMapKeyRef:
name: lasuite-postgres
key: DB_PORT
- name: PGDATABASE
value: calendars_db
- name: PGUSER
value: calendars
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: calendars-db-credentials
key: password
resources:
limits:
memory: 128Mi
cpu: 100m
requests:
memory: 64Mi
cpu: 10m
containers:
- name: calendars-caldav
image: calendars-caldav
ports:
- containerPort: 80
env:
- name: PGHOST
valueFrom:
configMapKeyRef:
name: lasuite-postgres
key: DB_HOST
- name: PGPORT
valueFrom:
configMapKeyRef:
name: lasuite-postgres
key: DB_PORT
- name: PGDATABASE
value: calendars_db
- name: PGUSER
value: calendars
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: calendars-db-credentials
key: password
- name: CALDAV_INBOUND_API_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: CALDAV_INBOUND_API_KEY
- name: CALDAV_OUTBOUND_API_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: CALDAV_OUTBOUND_API_KEY
- name: CALDAV_INTERNAL_API_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: CALDAV_INTERNAL_API_KEY
- name: CALDAV_BASE_URI
value: /caldav/
- name: CALLBACK_BASE_URL
value: http://calendars-backend.lasuite.svc.cluster.local:8000
resources:
limits:
memory: 256Mi
cpu: 300m
requests:
memory: 128Mi
cpu: 50m

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: calendars-caldav
namespace: lasuite
spec:
selector:
app: calendars-caldav
ports:
- port: 80
targetPort: 80

View File

@@ -0,0 +1,30 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: calendars-config
namespace: lasuite
data:
DJANGO_CONFIGURATION: Production
DJANGO_SETTINGS_MODULE: calendars.settings
DJANGO_ALLOWED_HOSTS: cal.DOMAIN_SUFFIX,calendars-backend.lasuite.svc.cluster.local,localhost
DJANGO_CSRF_TRUSTED_ORIGINS: https://cal.DOMAIN_SUFFIX
DB_NAME: calendars_db
DB_USER: calendars
CALDAV_URL: http://calendars-caldav.lasuite.svc.cluster.local:80
CALDAV_CALLBACK_BASE_URL: http://calendars-backend.lasuite.svc.cluster.local:8000
REDIS_URL: redis://valkey.data.svc.cluster.local:6379/5
DRAMATIQ_BROKER_URL: redis://valkey.data.svc.cluster.local:6379/5
DRAMATIQ_RESULT_BACKEND_URL: redis://valkey.data.svc.cluster.local:6379/6
APP_URL: https://cal.DOMAIN_SUFFIX
DEFAULT_FROM_EMAIL: noreply@sunbeam.pt
CALENDAR_INVITATION_FROM_EMAIL: calendar@sunbeam.pt
CALENDAR_ITIP_ENABLED: "True"
LOGIN_REDIRECT_URL: /
LOGIN_REDIRECT_URL_FAILURE: /
LOGOUT_REDIRECT_URL: /
FRONTEND_THEME: sunbeam
FRONTEND_HIDE_GAUFRE: "False"
NEXT_PUBLIC_VISIO_BASE_URL: https://meet.DOMAIN_SUFFIX
FRONTEND_CSS_URL: https://integration.DOMAIN_SUFFIX/api/v2/theme.css
OIDC_USERINFO_FULLNAME_FIELDS: given_name,family_name
THEME_CUSTOMIZATION_FILE_PATH: /app/theme.json

View File

@@ -0,0 +1,26 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: calendars-frontend-caddyfile
namespace: lasuite
data:
Caddyfile: |
{
auto_https off
admin off
}
:8080 {
root * /srv
header X-Frame-Options DENY
route {
try_files {path} {path}.html /index.html
file_server
}
handle_errors {
rewrite * /404.html
file_server
}
}

View File

@@ -0,0 +1,35 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: calendars-frontend
namespace: lasuite
spec:
replicas: 1
selector:
matchLabels:
app: calendars-frontend
template:
metadata:
labels:
app: calendars-frontend
spec:
containers:
- name: calendars-frontend
image: calendars-frontend
ports:
- containerPort: 8080
volumeMounts:
- name: caddyfile
mountPath: /etc/caddy/Caddyfile
subPath: Caddyfile
resources:
limits:
memory: 128Mi
cpu: 100m
requests:
memory: 64Mi
cpu: 10m
volumes:
- name: caddyfile
configMap:
name: calendars-frontend-caddyfile

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: calendars-frontend
namespace: lasuite
spec:
selector:
app: calendars-frontend
ports:
- port: 80
targetPort: 8080

View File

@@ -0,0 +1,17 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: calendars-theme
namespace: lasuite
data:
theme.json: |
{
"css_url": "https://integration.DOMAIN_SUFFIX/api/v2/theme.css",
"waffle": {
"apiUrl": "https://integration.DOMAIN_SUFFIX/api/v2/services.json",
"widgetPath": "https://integration.DOMAIN_SUFFIX/api/v2/lagaufre.js",
"label": "O Estúdio",
"closeLabel": "Fechar",
"newWindowLabelSuffix": " · nova janela"
}
}

View File

@@ -0,0 +1,88 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: calendars-worker
namespace: lasuite
spec:
replicas: 1
selector:
matchLabels:
app: calendars-worker
template:
metadata:
labels:
app: calendars-worker
spec:
containers:
- name: calendars-worker
image: calendars-backend
command: ["python", "worker.py"]
envFrom:
- configMapRef:
name: calendars-config
- configMapRef:
name: lasuite-postgres
- configMapRef:
name: lasuite-oidc-provider
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: calendars-db-credentials
key: password
- name: DJANGO_SECRET_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: DJANGO_SECRET_KEY
- name: SALT_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: SALT_KEY
- name: CALDAV_INBOUND_API_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: CALDAV_INBOUND_API_KEY
- name: CALDAV_OUTBOUND_API_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: CALDAV_OUTBOUND_API_KEY
- name: CALDAV_INTERNAL_API_KEY
valueFrom:
secretKeyRef:
name: calendars-django-secret
key: CALDAV_INTERNAL_API_KEY
- name: OIDC_RP_CLIENT_ID
valueFrom:
secretKeyRef:
name: oidc-calendars
key: CLIENT_ID
- name: OIDC_RP_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: oidc-calendars
key: CLIENT_SECRET
volumeMounts:
- name: theme
mountPath: /app/theme.json
subPath: theme.json
- name: translations
mountPath: /data/translations.json
subPath: translations.json
resources:
limits:
memory: 256Mi
cpu: 300m
requests:
memory: 128Mi
cpu: 50m
volumes:
- name: theme
configMap:
name: calendars-theme
- name: translations
configMap:
name: calendars-translations

View File

@@ -29,6 +29,16 @@ data:
"name": "Drive",
"url": "https://drive.DOMAIN_SUFFIX",
"logo": "https://integration.DOMAIN_SUFFIX/logos/drive.svg?v=1"
},
{
"name": "Account",
"url": "https://auth.DOMAIN_SUFFIX",
"logo": "https://integration.DOMAIN_SUFFIX/logos/account.svg?v=1"
},
{
"name": "Calendário",
"url": "https://cal.DOMAIN_SUFFIX",
"logo": "https://integration.DOMAIN_SUFFIX/logos/calendar.svg?v=1"
}
]
}

View File

@@ -41,6 +41,16 @@ resources:
- messages-mpa-service.yaml
- messages-socks-proxy-deployment.yaml
- messages-socks-proxy-service.yaml
- calendars-config.yaml
- calendars-theme-configmap.yaml
- calendars-backend-deployment.yaml
- calendars-backend-service.yaml
- calendars-caldav-deployment.yaml
- calendars-caldav-service.yaml
- calendars-worker-deployment.yaml
- calendars-frontend-caddyfile.yaml
- calendars-frontend-deployment.yaml
- calendars-frontend-service.yaml
patches:
# Rewrite hardcoded production integration URL + inject theme CSS in people-frontend

View File

@@ -178,3 +178,25 @@ spec:
scope: openid
tokenEndpointAuthMethod: client_secret_basic
secretName: oidc-hive
---
# ── Calendars ────────────────────────────────────────────────────────────────
apiVersion: hydra.ory.sh/v1alpha1
kind: OAuth2Client
metadata:
name: calendars
namespace: lasuite
spec:
clientName: Calendars
grantTypes:
- authorization_code
- refresh_token
responseTypes:
- code
scope: openid email profile
redirectUris:
- https://cal.DOMAIN_SUFFIX/api/v1.0/callback/
postLogoutRedirectUris:
- https://cal.DOMAIN_SUFFIX/api/v1.0/logout-callback/
tokenEndpointAuthMethod: client_secret_post
secretName: oidc-calendars
skipConsent: true

View File

@@ -572,3 +572,68 @@ spec:
text: "{{ index .Secrets \"mta-out-smtp-username\" }}"
SMTP_PASSWORD:
text: "{{ index .Secrets \"mta-out-smtp-password\" }}"
---
# Calendars DB credentials from OpenBao database secrets engine (static role, 24h rotation).
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
name: calendars-db-credentials
namespace: lasuite
spec:
vaultAuthRef: vso-auth
mount: database
path: static-creds/calendars
allowStaticCreds: true
refreshAfter: 5m
rolloutRestartTargets:
- kind: Deployment
name: calendars-backend
- kind: Deployment
name: calendars-worker
- kind: Deployment
name: calendars-caldav
destination:
name: calendars-db-credentials
create: true
overwrite: true
transformation:
excludeRaw: true
templates:
password:
text: "{{ index .Secrets \"password\" }}"
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: calendars-django-secret
namespace: lasuite
spec:
vaultAuthRef: vso-auth
mount: secret
type: kv-v2
path: calendars
refreshAfter: 30s
rolloutRestartTargets:
- kind: Deployment
name: calendars-backend
- kind: Deployment
name: calendars-worker
- kind: Deployment
name: calendars-caldav
destination:
name: calendars-django-secret
create: true
overwrite: true
transformation:
excludeRaw: true
templates:
DJANGO_SECRET_KEY:
text: "{{ index .Secrets \"django-secret-key\" }}"
SALT_KEY:
text: "{{ index .Secrets \"salt-key\" }}"
CALDAV_INBOUND_API_KEY:
text: "{{ index .Secrets \"caldav-inbound-api-key\" }}"
CALDAV_OUTBOUND_API_KEY:
text: "{{ index .Secrets \"caldav-outbound-api-key\" }}"
CALDAV_INTERNAL_API_KEY:
text: "{{ index .Secrets \"caldav-internal-api-key\" }}"

View File

@@ -53,6 +53,17 @@ images:
newName: src.DOMAIN_SUFFIX/studio/meet-frontend
newTag: latest
# Calendars — built from source and pushed to Gitea registry.
- name: calendars-backend
newName: src.DOMAIN_SUFFIX/studio/calendars-backend
newTag: latest
- name: calendars-caldav
newName: src.DOMAIN_SUFFIX/studio/calendars-caldav
newTag: latest
- name: calendars-frontend
newName: src.DOMAIN_SUFFIX/studio/calendars-frontend
newTag: latest
patches:
# Disable SSL verification for OIDC server-side calls — mkcert CA not trusted in pods
- path: patch-oidc-verify-ssl.yaml

View File

@@ -70,3 +70,4 @@ spec:
- admin.DOMAIN_SUFFIX
- integration.DOMAIN_SUFFIX
- livekit.DOMAIN_SUFFIX
- cal.DOMAIN_SUFFIX

View File

@@ -64,6 +64,17 @@ images:
newName: src.DOMAIN_SUFFIX/studio/messages-socks-proxy
newTag: latest
# Calendars — built from source and pushed to Gitea registry.
- name: calendars-backend
newName: src.DOMAIN_SUFFIX/studio/calendars-backend
newTag: latest
- name: calendars-caldav
newName: src.DOMAIN_SUFFIX/studio/calendars-caldav
newTag: latest
- name: calendars-frontend
newName: src.DOMAIN_SUFFIX/studio/calendars-frontend
newTag: latest
# Tuwunel Matrix homeserver — built and pushed by `sunbeam build tuwunel`
- name: tuwunel
newName: src.DOMAIN_SUFFIX/studio/tuwunel