feat(infra): Meet integration, La Suite theming, Pingora SSH + meet routes

Meet: add backend/frontend/celery deployments and services, meet-config
ConfigMap, nginx SPA config, VSO secrets (meet-db-credentials VDS,
meet-django-secret and meet-livekit VSS). Wire oidc-meet OAuth2Client.

La Suite overlay discipline: move people/docs frontend nginx ConfigMaps
and patches from overlays/local to base so both environments share them.
Remove values-ory.yaml (folded into base). Add docs-frontend nginx config
with sub_filter theming. Add local gitea mkcert CA patch.

Pingora: add [ssh] TCP passthrough block (port 22 → Gitea SSH pod) and
split meet route into frontend default + backend paths for /api/, /admin/,
/oidc/, /static/, /__. Remove now-unused values-pingora.yaml from production
overlay (host ports moved to patch-pingora-hostport.yaml).

Update both overlay kustomizations to reference all new resources and
add meet-backend/meet-frontend image entries.
This commit is contained in:
2026-03-06 12:08:21 +00:00
parent d32d1435f9
commit 424db43ccf
22 changed files with 569 additions and 49 deletions

View File

@@ -9,3 +9,8 @@ resources:
- pingora-deployment.yaml
- pingora-service.yaml
- pingora-config.yaml
images:
- name: sunbeam-proxy
newName: src.DOMAIN_SUFFIX/studio/proxy
newTag: latest

View File

@@ -58,9 +58,29 @@ data:
[[routes]]
host_prefix = "meet"
backend = "http://meet.lasuite.svc.cluster.local:8000"
backend = "http://meet-frontend.lasuite.svc.cluster.local:80"
websocket = true
[[routes.paths]]
prefix = "/api/"
backend = "http://meet-backend.lasuite.svc.cluster.local:80"
[[routes.paths]]
prefix = "/admin/"
backend = "http://meet-backend.lasuite.svc.cluster.local:80"
[[routes.paths]]
prefix = "/oidc/"
backend = "http://meet-backend.lasuite.svc.cluster.local:80"
[[routes.paths]]
prefix = "/static/"
backend = "http://meet-backend.lasuite.svc.cluster.local:80"
[[routes.paths]]
prefix = "/__"
backend = "http://meet-backend.lasuite.svc.cluster.local:80"
[[routes]]
host_prefix = "drive"
backend = "http://drive.lasuite.svc.cluster.local:8000"
@@ -131,6 +151,16 @@ data:
host_prefix = "integration"
backend = "http://integration.lasuite.svc.cluster.local:80"
[[routes]]
host_prefix = "grafana"
backend = "http://kube-prometheus-stack-grafana.monitoring.svc.cluster.local:80"
[[routes]]
host_prefix = "s3"
backend = "http://seaweedfs-filer.storage.svc.cluster.local:8333"
# SSH TCP passthrough: port 22 → Gitea SSH pod (headless service → pod:2222).
[ssh]
listen = "0.0.0.0:22"
backend = "gitea-ssh.devtools.svc.cluster.local:2222"

View File

@@ -23,6 +23,7 @@ spec:
containers:
- name: pingora
image: sunbeam-proxy:latest # overridden per overlay via kustomize images:
imagePullPolicy: IfNotPresent # pre-seeded into containerd; avoids pull deadlock on startup
ports:
- name: http
containerPort: 80
@@ -30,6 +31,9 @@ spec:
- name: https
containerPort: 443
protocol: TCP
- name: ssh
containerPort: 22
protocol: TCP
- name: turn-udp
containerPort: 3478
protocol: UDP

View File

@@ -0,0 +1,38 @@
# nginx config for docs-frontend that injects the brand theme CSS at serve time.
# sub_filter injects the theme.css link before </head> so Cunningham CSS variables
# are overridden at runtime without rebuilding the app.
# gzip must be off for sub_filter to work on HTML responses.
apiVersion: v1
kind: ConfigMap
metadata:
name: docs-frontend-nginx-conf
namespace: lasuite
data:
default.conf: |
server {
listen 8080;
listen 3000;
server_name localhost;
root /app;
gzip off;
sub_filter '</head>' '<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Ysabeau+Variable:ital,wght@0,100..900;1,100..900&display=swap"><link rel="stylesheet" href="https://integration.DOMAIN_SUFFIX/api/v2/theme.css"></head>';
sub_filter_once off;
sub_filter_types text/html;
location / {
try_files $uri index.html $uri/index.html =404;
add_header X-Frame-Options DENY always;
}
location ~ "^/docs/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/?$" {
try_files $uri /docs/[id]/index.html;
add_header X-Frame-Options DENY always;
}
error_page 404 /404.html;
location = /404.html {
internal;
}
}

View File

@@ -120,6 +120,7 @@ backend:
name: docs-django-secret
key: DJANGO_SECRET_KEY
DJANGO_CONFIGURATION: Production
FRONTEND_THEME: estudio
ALLOWED_HOSTS: docs.DOMAIN_SUFFIX
DJANGO_ALLOWED_HOSTS: docs.DOMAIN_SUFFIX
DJANGO_CSRF_TRUSTED_ORIGINS: https://docs.DOMAIN_SUFFIX
@@ -148,7 +149,7 @@ backend:
header:
logo: {}
icon:
src: "/assets/icon-docs.svg"
src: "https://integration.DOMAIN_SUFFIX/logos/docs.svg?v=2"
style:
width: "32px"
height: "auto"

View File

@@ -23,17 +23,17 @@ data:
{
"name": "Docs",
"url": "https://docs.DOMAIN_SUFFIX",
"logo": "https://integration.DOMAIN_SUFFIX/logos/docs.svg"
"logo": "https://integration.DOMAIN_SUFFIX/logos/docs.svg?v=2"
},
{
"name": "Reuniões",
"url": "https://meet.DOMAIN_SUFFIX",
"logo": "https://integration.DOMAIN_SUFFIX/logos/visio.svg"
"logo": "https://integration.DOMAIN_SUFFIX/logos/visio.svg?v=2"
},
{
"name": "Humans",
"url": "https://people.DOMAIN_SUFFIX",
"logo": "https://integration.DOMAIN_SUFFIX/logos/people.svg"
"logo": "https://integration.DOMAIN_SUFFIX/logos/people.svg?v=2"
}
]
}

View File

@@ -14,6 +14,21 @@ resources:
- oidc-clients.yaml
- vault-secrets.yaml
- integration-deployment.yaml
- people-frontend-nginx-configmap.yaml
- docs-frontend-nginx-configmap.yaml
- meet-config.yaml
- meet-backend-deployment.yaml
- meet-backend-service.yaml
- meet-celery-worker-deployment.yaml
- meet-frontend-nginx-configmap.yaml
- meet-frontend-deployment.yaml
- meet-frontend-service.yaml
patches:
# Rewrite hardcoded production integration URL + inject theme CSS in people-frontend
- path: patch-people-frontend-nginx.yaml
# Inject theme CSS in docs-frontend
- path: patch-docs-frontend-nginx.yaml
# La Suite Numérique Helm charts.
# Charts with a published Helm repo use helmCharts below.

View File

@@ -0,0 +1,169 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: meet-backend
namespace: lasuite
spec:
replicas: 1
selector:
matchLabels:
app: meet-backend
template:
metadata:
labels:
app: meet-backend
spec:
initContainers:
- name: migrate
image: meet-backend
command: ["python", "manage.py", "migrate", "--no-input"]
envFrom:
- configMapRef:
name: meet-config
- configMapRef:
name: lasuite-postgres
- configMapRef:
name: lasuite-valkey
- configMapRef:
name: lasuite-s3
- configMapRef:
name: lasuite-oidc-provider
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: meet-db-credentials
key: password
- name: DJANGO_SECRET_KEY
valueFrom:
secretKeyRef:
name: meet-django-secret
key: DJANGO_SECRET_KEY
- name: APPLICATION_JWT_SECRET_KEY
valueFrom:
secretKeyRef:
name: meet-django-secret
key: APPLICATION_JWT_SECRET_KEY
- name: LIVEKIT_API_KEY
valueFrom:
secretKeyRef:
name: meet-livekit
key: LIVEKIT_API_KEY
- name: LIVEKIT_API_SECRET
valueFrom:
secretKeyRef:
name: meet-livekit
key: LIVEKIT_API_SECRET
- name: OIDC_RP_CLIENT_ID
valueFrom:
secretKeyRef:
name: oidc-meet
key: CLIENT_ID
- name: OIDC_RP_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: oidc-meet
key: CLIENT_SECRET
- name: AWS_S3_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: seaweedfs-s3-credentials
key: S3_ACCESS_KEY
- name: AWS_S3_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: seaweedfs-s3-credentials
key: S3_SECRET_KEY
resources:
limits:
memory: 512Mi
cpu: 500m
requests:
memory: 128Mi
cpu: 100m
containers:
- name: meet-backend
image: meet-backend
command:
- gunicorn
- -c
- /usr/local/etc/gunicorn/meet.py
- meet.wsgi:application
ports:
- containerPort: 8000
envFrom:
- configMapRef:
name: meet-config
- configMapRef:
name: lasuite-postgres
- configMapRef:
name: lasuite-valkey
- configMapRef:
name: lasuite-s3
- configMapRef:
name: lasuite-oidc-provider
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: meet-db-credentials
key: password
- name: DJANGO_SECRET_KEY
valueFrom:
secretKeyRef:
name: meet-django-secret
key: DJANGO_SECRET_KEY
- name: APPLICATION_JWT_SECRET_KEY
valueFrom:
secretKeyRef:
name: meet-django-secret
key: APPLICATION_JWT_SECRET_KEY
- name: LIVEKIT_API_KEY
valueFrom:
secretKeyRef:
name: meet-livekit
key: LIVEKIT_API_KEY
- name: LIVEKIT_API_SECRET
valueFrom:
secretKeyRef:
name: meet-livekit
key: LIVEKIT_API_SECRET
- name: OIDC_RP_CLIENT_ID
valueFrom:
secretKeyRef:
name: oidc-meet
key: CLIENT_ID
- name: OIDC_RP_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: oidc-meet
key: CLIENT_SECRET
- name: AWS_S3_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: seaweedfs-s3-credentials
key: S3_ACCESS_KEY
- name: AWS_S3_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: seaweedfs-s3-credentials
key: S3_SECRET_KEY
livenessProbe:
httpGet:
path: /__lbheartbeat__
port: 8000
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /__heartbeat__
port: 8000
initialDelaySeconds: 10
periodSeconds: 10
resources:
limits:
memory: 512Mi
cpu: 500m
requests:
memory: 128Mi
cpu: 100m

View File

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

View File

@@ -0,0 +1,83 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: meet-celery-worker
namespace: lasuite
spec:
replicas: 1
selector:
matchLabels:
app: meet-celery-worker
template:
metadata:
labels:
app: meet-celery-worker
spec:
containers:
- name: meet-celery-worker
image: meet-backend
command: ["celery", "-A", "meet.celery_app", "worker", "-l", "info"]
envFrom:
- configMapRef:
name: meet-config
- configMapRef:
name: lasuite-postgres
- configMapRef:
name: lasuite-valkey
- configMapRef:
name: lasuite-s3
- configMapRef:
name: lasuite-oidc-provider
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: meet-db-credentials
key: password
- name: DJANGO_SECRET_KEY
valueFrom:
secretKeyRef:
name: meet-django-secret
key: DJANGO_SECRET_KEY
- name: APPLICATION_JWT_SECRET_KEY
valueFrom:
secretKeyRef:
name: meet-django-secret
key: APPLICATION_JWT_SECRET_KEY
- name: LIVEKIT_API_KEY
valueFrom:
secretKeyRef:
name: meet-livekit
key: LIVEKIT_API_KEY
- name: LIVEKIT_API_SECRET
valueFrom:
secretKeyRef:
name: meet-livekit
key: LIVEKIT_API_SECRET
- name: OIDC_RP_CLIENT_ID
valueFrom:
secretKeyRef:
name: oidc-meet
key: CLIENT_ID
- name: OIDC_RP_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: oidc-meet
key: CLIENT_SECRET
- name: AWS_S3_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: seaweedfs-s3-credentials
key: S3_ACCESS_KEY
- name: AWS_S3_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: seaweedfs-s3-credentials
key: S3_SECRET_KEY
resources:
limits:
memory: 512Mi
cpu: 500m
requests:
memory: 128Mi
cpu: 100m

View File

@@ -0,0 +1,14 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: meet-config
namespace: lasuite
data:
DJANGO_CONFIGURATION: Production
DJANGO_SETTINGS_MODULE: meet.settings
ALLOWED_HOSTS: meet.DOMAIN_SUFFIX
DJANGO_CSRF_TRUSTED_ORIGINS: https://meet.DOMAIN_SUFFIX
DB_NAME: meet_db
DB_USER: meet
AWS_STORAGE_BUCKET_NAME: sunbeam-meet
LIVEKIT_API_URL: http://livekit-server.media.svc.cluster.local:7880

View File

@@ -0,0 +1,36 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: meet-frontend
namespace: lasuite
spec:
replicas: 1
selector:
matchLabels:
app: meet-frontend
template:
metadata:
labels:
app: meet-frontend
spec:
containers:
- name: meet-frontend
image: meet-frontend
ports:
- containerPort: 80
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
readOnly: true
resources:
limits:
memory: 64Mi
cpu: 100m
requests:
memory: 32Mi
cpu: 25m
volumes:
- name: nginx-conf
configMap:
name: meet-frontend-nginx-conf

View File

@@ -0,0 +1,23 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: meet-frontend-nginx-conf
namespace: lasuite
data:
default.conf: |
server {
listen 80;
server_name localhost;
server_tokens off;
root /usr/share/nginx/html;
location / {
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
internal;
}
}

View File

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

View File

@@ -167,7 +167,7 @@ spec:
- code
scope: openid email profile
redirectUris:
- https://src.DOMAIN_SUFFIX/user/oauth2/hydra/callback
- https://src.DOMAIN_SUFFIX/user/oauth2/Sunbeam/callback
tokenEndpointAuthMethod: client_secret_basic
secretName: oidc-gitea
skipConsent: true

View File

@@ -0,0 +1,19 @@
# Patch: mount custom nginx config into docs-frontend to inject brand theme CSS.
apiVersion: apps/v1
kind: Deployment
metadata:
name: docs-frontend
namespace: lasuite
spec:
template:
spec:
containers:
- name: docs
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
volumes:
- name: nginx-conf
configMap:
name: docs-frontend-nginx-conf

View File

@@ -1,12 +1,9 @@
# nginx config for people-frontend that rewrites the hardcoded production
# integration URL baked into the desk static Next.js build.
#
# The people-frontend image has integration.lasuite.numerique.gouv.fr compiled
# in. sub_filter rewrites it to our local instance so the gaufre.js and
# services.json come from integration.DOMAIN_SUFFIX instead of the official
# government service.
#
# gzip must be off for sub_filter to operate on JS responses.
# sub_filter rewrites integration.lasuite.numerique.gouv.fr → integration.DOMAIN_SUFFIX
# so the gaufre.js widget and services.json come from our own integration service.
# gzip must be off for sub_filter to work on JS responses.
apiVersion: v1
kind: ConfigMap
metadata:
@@ -24,6 +21,7 @@ data:
gzip off;
sub_filter 'integration.lasuite.numerique.gouv.fr' 'integration.DOMAIN_SUFFIX';
sub_filter '</head>' '<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Ysabeau+Variable:ital,wght@0,100..900;1,100..900&display=swap"><link rel="stylesheet" href="https://integration.DOMAIN_SUFFIX/api/v2/theme.css"></head>';
sub_filter_once off;
sub_filter_types text/html application/javascript;

View File

@@ -202,3 +202,76 @@ spec:
templates:
secret:
text: "{{ index .Secrets \"collaboration-secret\" }}"
---
# Meet DB credentials from OpenBao database secrets engine (static role, 24h rotation).
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
name: meet-db-credentials
namespace: lasuite
spec:
vaultAuthRef: vso-auth
mount: database
path: static-creds/meet
allowStaticCreds: true
refreshAfter: 5m
rolloutRestartTargets:
- kind: Deployment
name: meet-backend
- kind: Deployment
name: meet-celery-worker
destination:
name: meet-db-credentials
create: true
overwrite: true
transformation:
excludeRaw: true
templates:
password:
text: "{{ index .Secrets \"password\" }}"
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: meet-django-secret
namespace: lasuite
spec:
vaultAuthRef: vso-auth
mount: secret
type: kv-v2
path: meet
refreshAfter: 30s
destination:
name: meet-django-secret
create: true
overwrite: true
transformation:
excludeRaw: true
templates:
DJANGO_SECRET_KEY:
text: "{{ index .Secrets \"django-secret-key\" }}"
APPLICATION_JWT_SECRET_KEY:
text: "{{ index .Secrets \"application-jwt-secret-key\" }}"
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: meet-livekit
namespace: lasuite
spec:
vaultAuthRef: vso-auth
mount: secret
type: kv-v2
path: livekit
refreshAfter: 30s
destination:
name: meet-livekit
create: true
overwrite: true
transformation:
excludeRaw: true
templates:
LIVEKIT_API_KEY:
text: "{{ index .Secrets \"api-key\" }}"
LIVEKIT_API_SECRET:
text: "{{ index .Secrets \"api-secret\" }}"

View File

@@ -12,7 +12,6 @@ kind: Kustomization
# replace DOMAIN_SUFFIX with <LIMA_IP>.sslip.io before kubectl apply.
resources:
- people-frontend-nginx-configmap.yaml
- ../../base/ingress
- ../../base/ory
- ../../base/data
@@ -23,12 +22,6 @@ resources:
- ../../base/vso
images:
# Pulled from our Gitea registry. Built and pushed by: sunbeam build <target>
# imagePullPolicy: Always in values-pingora.yaml ensures each rollout pulls fresh.
- name: sunbeam-proxy
newName: src.DOMAIN_SUFFIX/studio/sunbeam-proxy
newTag: latest
# La Gaufre v2 integration service — lagaufre.js widget + SVG logos + nginx
- name: integration
newName: src.DOMAIN_SUFFIX/studio/integration
@@ -50,6 +43,14 @@ images:
- name: lasuite/impress-y-provider
newName: src.DOMAIN_SUFFIX/studio/impress-y-provider
# Meet — built from source and pushed to Gitea registry.
- name: meet-backend
newName: src.DOMAIN_SUFFIX/studio/meet-backend
newTag: latest
- name: meet-frontend
newName: src.DOMAIN_SUFFIX/studio/meet-frontend
newTag: latest
patches:
# Disable SSL verification for OIDC server-side calls — mkcert CA not trusted in pods
- path: patch-oidc-verify-ssl.yaml
@@ -57,7 +58,7 @@ patches:
kind: ConfigMap
name: lasuite-oidc-provider
# Add hostPort for TURN relay range on Lima VM
# Add hostPort for TURN relay range + bind :80/:443 on Lima VM
- path: values-pingora.yaml
target:
kind: Deployment
@@ -69,8 +70,11 @@ patches:
kind: Service
name: livekit-server-turn
# Rewrite hardcoded production integration URL in people-frontend static build
- path: patch-people-frontend-nginx.yaml
# Set SSL_CERT_FILE so Gitea's Go TLS trusts the mkcert wildcard CA for OIDC calls
- path: patch-gitea-mkcert-ca.yaml
target:
kind: Deployment
name: gitea
# Apply §10.7 memory limits to all Deployments
- path: values-resources.yaml

View File

@@ -0,0 +1,13 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitea
namespace: devtools
spec:
template:
spec:
containers:
- name: gitea
env:
- name: SSL_CERT_FILE
value: /run/ca/ca.crt

View File

@@ -1,27 +0,0 @@
# Patch: Ory redirect URIs → sslip.io hostnames for local dev.
# Applied as a strategic merge patch over the rendered Kratos/Hydra ConfigMaps.
#
# DOMAIN_SUFFIX is substituted by local-up.sh at deploy time.
# Production overlay uses sunbeam.pt.
# Kratos selfservice URLs
apiVersion: v1
kind: ConfigMap
metadata:
name: kratos-config
namespace: ory
data:
selfservice.default_browser_return_url: "https://auth.DOMAIN_SUFFIX/"
selfservice.flows.login.ui_url: "https://auth.DOMAIN_SUFFIX/login"
selfservice.flows.registration.ui_url: "https://auth.DOMAIN_SUFFIX/registration"
selfservice.flows.recovery.ui_url: "https://auth.DOMAIN_SUFFIX/recovery"
selfservice.flows.settings.ui_url: "https://auth.DOMAIN_SUFFIX/settings"
selfservice.allowed_return_urls: |
- https://auth.DOMAIN_SUFFIX/
- https://docs.DOMAIN_SUFFIX/
- https://meet.DOMAIN_SUFFIX/
- https://drive.DOMAIN_SUFFIX/
- https://mail.DOMAIN_SUFFIX/
- https://chat.DOMAIN_SUFFIX/
- https://people.DOMAIN_SUFFIX/
- https://src.DOMAIN_SUFFIX/