feat(devtools): deploy Penpot + MCP server, wildcard TLS via DNS-01

Penpot (designer.sunbeam.pt):
- Frontend/backend/exporter deployments with OIDC-only auth via Hydra
- VSO-managed DB, S3, and app secrets from OpenBao
- PostgreSQL user/db in CNPG postInitSQL
- Hydra Maester enabledNamespaces extended to devtools

Penpot MCP server (mcp-designer.sunbeam.pt):
- Pre-built Node.js image pushed to Gitea registry
- Auth-gated via Pingora auth_request → Hydra /userinfo
- WebSocket path for browser plugin connection

Wildcard TLS:
- Switched cert-manager from HTTP-01 (per-SAN) to DNS-01 via Scaleway webhook
- Certificate collapsed to *.sunbeam.pt + sunbeam.pt
- Added scaleway-certmanager-webhook Helm chart
- VSO secret for Scaleway DNS API credentials in cert-manager namespace
- Added cert-manager to OpenBao VSO auth role
This commit is contained in:
2026-04-04 12:53:27 +01:00
parent 97628b0f6f
commit fcb80f1f37
13 changed files with 486 additions and 40 deletions

View File

@@ -9,6 +9,10 @@ resources:
- gitea-theme-cm.yaml
- gitea-servicemonitor.yaml
- gitea-alertrules.yaml
- beam-design.yaml
- penpot.yaml
- penpot-oidc.yaml
- penpot-mcp.yaml
helmCharts:
# helm repo add gitea-charts https://dl.gitea.com/charts/

View File

@@ -0,0 +1,12 @@
FROM node:22-alpine
RUN npm install -g pnpm@latest @penpot/mcp@latest && \
cd /usr/local/lib/node_modules/@penpot/mcp && \
pnpm -r install && \
pnpm run build
ENV PENPOT_MCP_REMOTE_MODE=true \
PENPOT_MCP_SERVER_HOST=0.0.0.0 \
PENPOT_MCP_SERVER_PORT=4401 \
PENPOT_MCP_WEBSOCKET_PORT=4402
EXPOSE 4401 4402
WORKDIR /usr/local/lib/node_modules/@penpot/mcp
CMD ["pnpm", "run", "start"]

View File

@@ -0,0 +1,56 @@
# Penpot MCP server — bridges AI clients to Penpot via the MCP plugin.
# Port 4401: HTTP/SSE for MCP clients (Claude, etc.)
# Port 4402: WebSocket for the Penpot browser plugin
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: penpot-mcp
namespace: devtools
spec:
replicas: 1
selector:
matchLabels:
app: penpot-mcp
template:
metadata:
labels:
app: penpot-mcp
spec:
containers:
- name: penpot-mcp
image: src.sunbeam.pt/studio/penpot-mcp:latest
ports:
- name: http
containerPort: 4401
- name: ws
containerPort: 4402
env:
- name: PENPOT_MCP_REMOTE_MODE
value: "true"
- name: PENPOT_MCP_SERVER_HOST
value: "0.0.0.0"
- name: PENPOT_MCP_SERVER_ADDRESS
value: "mcp-designer.DOMAIN_SUFFIX"
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: penpot-mcp
namespace: devtools
spec:
selector:
app: penpot-mcp
ports:
- name: http
port: 4401
targetPort: http
- name: ws
port: 4402
targetPort: ws

View File

@@ -0,0 +1,20 @@
# Penpot OIDC client — Hydra Maester creates Secret "oidc-penpot" in devtools
# with CLIENT_ID and CLIENT_SECRET keys.
apiVersion: hydra.ory.sh/v1alpha1
kind: OAuth2Client
metadata:
name: penpot
namespace: devtools
spec:
clientName: Penpot
grantTypes:
- authorization_code
- refresh_token
responseTypes:
- code
scope: openid email profile
redirectUris:
- https://designer.DOMAIN_SUFFIX/api/auth/oidc/callback
tokenEndpointAuthMethod: client_secret_post
secretName: oidc-penpot
skipConsent: true

205
base/devtools/penpot.yaml Normal file
View File

@@ -0,0 +1,205 @@
# Penpot — open-source design tool (frontend + backend + exporter).
# OIDC-only auth via Hydra; assets on SeaweedFS; DB on shared CNPG postgres.
---
apiVersion: v1
kind: ConfigMap
metadata:
name: penpot-config
namespace: devtools
data:
PENPOT_PUBLIC_URI: "https://designer.DOMAIN_SUFFIX"
PENPOT_DATABASE_URI: "postgresql://postgres-rw.data.svc.cluster.local:5432/penpot_db"
PENPOT_DATABASE_USERNAME: "penpot"
PENPOT_REDIS_URI: "redis://valkey.data.svc.cluster.local:6379/3"
PENPOT_ASSETS_STORAGE_BACKEND: "assets-s3"
PENPOT_STORAGE_ASSETS_S3_ENDPOINT: "http://seaweedfs-filer.storage.svc.cluster.local:8333"
PENPOT_STORAGE_ASSETS_S3_BUCKET: "penpot"
PENPOT_OIDC_BASE_URI: "https://auth.DOMAIN_SUFFIX/"
PENPOT_TELEMETRY_ENABLED: "false"
PENPOT_FLAGS: "enable-login-with-oidc disable-login-with-password disable-email-verification disable-registration enable-backend-api-doc enable-auto-file-snapshot enable-tiered-file-data-storage enable-webhooks enable-access-tokens enable-cors"
---
# ── Frontend (nginx SPA) ─────────────────────────────────────────────────────
apiVersion: apps/v1
kind: Deployment
metadata:
name: penpot-frontend
namespace: devtools
spec:
replicas: 1
selector:
matchLabels:
app: penpot-frontend
template:
metadata:
labels:
app: penpot-frontend
spec:
containers:
- name: penpot-frontend
image: penpotapp/frontend:latest
ports:
- name: http
containerPort: 8080
env:
- name: PENPOT_FLAGS
valueFrom:
configMapKeyRef:
name: penpot-config
key: PENPOT_FLAGS
- name: PENPOT_BACKEND_URI
value: "http://penpot-backend:6060"
- name: PENPOT_EXPORTER_URI
value: "http://penpot-exporter:6061"
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: penpot-frontend
namespace: devtools
spec:
selector:
app: penpot-frontend
ports:
- name: http
port: 8080
targetPort: http
---
# ── Backend (JVM API + websockets) ───────────────────────────────────────────
apiVersion: apps/v1
kind: Deployment
metadata:
name: penpot-backend
namespace: devtools
spec:
replicas: 1
selector:
matchLabels:
app: penpot-backend
template:
metadata:
labels:
app: penpot-backend
spec:
containers:
- name: penpot-backend
image: penpotapp/backend:latest
ports:
- name: http
containerPort: 6060
envFrom:
- configMapRef:
name: penpot-config
env:
- name: PENPOT_SECRET_KEY
valueFrom:
secretKeyRef:
name: penpot-app-secrets
key: secret-key
- name: PENPOT_DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: penpot-db-credentials
key: password
- name: PENPOT_OIDC_CLIENT_ID
valueFrom:
secretKeyRef:
name: oidc-penpot
key: CLIENT_ID
- name: PENPOT_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: oidc-penpot
key: CLIENT_SECRET
- name: PENPOT_STORAGE_ASSETS_S3_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: penpot-s3-credentials
key: access-key
- name: PENPOT_STORAGE_ASSETS_S3_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: penpot-s3-credentials
key: secret-key
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
memory: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: penpot-backend
namespace: devtools
spec:
selector:
app: penpot-backend
ports:
- name: http
port: 6060
targetPort: http
---
# ── Exporter (headless Chromium for PDF/SVG) ─────────────────────────────────
apiVersion: apps/v1
kind: Deployment
metadata:
name: penpot-exporter
namespace: devtools
spec:
replicas: 1
selector:
matchLabels:
app: penpot-exporter
template:
metadata:
labels:
app: penpot-exporter
spec:
containers:
- name: penpot-exporter
image: penpotapp/exporter:latest
ports:
- name: http
containerPort: 6061
env:
- name: PENPOT_SECRET_KEY
valueFrom:
secretKeyRef:
name: penpot-app-secrets
key: secret-key
- name: PENPOT_PUBLIC_URI
valueFrom:
configMapKeyRef:
name: penpot-config
key: PENPOT_PUBLIC_URI
- name: PENPOT_REDIS_URI
valueFrom:
configMapKeyRef:
name: penpot-config
key: PENPOT_REDIS_URI
resources:
requests:
cpu: 50m
memory: 256Mi
limits:
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: penpot-exporter
namespace: devtools
spec:
selector:
app: penpot-exporter
ports:
- name: http
port: 6061
targetPort: http

View File

@@ -62,6 +62,84 @@ spec:
"secret-key":
text: "{{ index .Secrets \"secret-key\" }}"
---
# Penpot DB credentials from OpenBao database secrets engine (static role, 24h rotation).
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
name: penpot-db-credentials
namespace: devtools
spec:
vaultAuthRef: vso-auth
mount: database
path: static-creds/penpot
allowStaticCreds: true
refreshAfter: 5m
rolloutRestartTargets:
- kind: Deployment
name: penpot-backend
destination:
name: penpot-db-credentials
create: true
overwrite: true
transformation:
excludeRaw: true
templates:
password:
text: "{{ index .Secrets \"password\" }}"
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: penpot-s3-credentials
namespace: devtools
spec:
vaultAuthRef: vso-auth
mount: secret
type: kv-v2
path: seaweedfs
refreshAfter: 30s
rolloutRestartTargets:
- kind: Deployment
name: penpot-backend
destination:
name: penpot-s3-credentials
create: true
overwrite: true
transformation:
excludeRaw: true
templates:
"access-key":
text: "{{ index .Secrets \"access-key\" }}"
"secret-key":
text: "{{ index .Secrets \"secret-key\" }}"
---
# Penpot app secrets (secret-key for internal auth between backend/exporter).
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: penpot-app-secrets
namespace: devtools
spec:
vaultAuthRef: vso-auth
mount: secret
type: kv-v2
path: penpot
refreshAfter: 30s
rolloutRestartTargets:
- kind: Deployment
name: penpot-backend
- kind: Deployment
name: penpot-exporter
destination:
name: penpot-app-secrets
create: true
overwrite: true
transformation:
excludeRaw: true
templates:
"secret-key":
text: "{{ index .Secrets \"secret-key\" }}"
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata: