From 04f6d46082654ea9127e0fcccddfc3757e1f9c41 Mon Sep 17 00:00:00 2001 From: Jason Staack Date: Tue, 17 Mar 2026 18:45:45 -0500 Subject: [PATCH] feat(helm): add API deployment and service templates Includes two init containers (VPN route setup, Alembic migrations), secret refs for JWT/encryption/OpenBao/SMTP, and PVC mounts for git-store, firmware-cache, and wireguard config. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../helm/templates/api-deployment.yaml | 152 ++++++++++++++++++ .../helm/templates/api-service.yaml | 15 ++ 2 files changed, 167 insertions(+) create mode 100644 infrastructure/helm/templates/api-deployment.yaml create mode 100644 infrastructure/helm/templates/api-service.yaml diff --git a/infrastructure/helm/templates/api-deployment.yaml b/infrastructure/helm/templates/api-deployment.yaml new file mode 100644 index 0000000..ebd7256 --- /dev/null +++ b/infrastructure/helm/templates/api-deployment.yaml @@ -0,0 +1,152 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "tod.fullname" . }}-api + labels: + {{- include "tod.componentLabels" (dict "context" . "component" "api") | nindent 4 }} +spec: + replicas: {{ .Values.api.replicaCount }} + selector: + matchLabels: + {{- include "tod.componentSelectorLabels" (dict "context" . "component" "api") | nindent 6 }} + template: + metadata: + labels: + {{- include "tod.componentSelectorLabels" (dict "context" . "component" "api") | nindent 8 }} + spec: + initContainers: + # Init 1: Set up VPN route (only if WireGuard enabled) + {{- if .Values.wireguard.enabled }} + - name: route-setup + image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}" + imagePullPolicy: {{ .Values.api.image.pullPolicy }} + command: + - sh + - -c + - | + apt-get update -qq && apt-get install -y -qq iproute2 >/dev/null 2>&1 || true + GW_IP=$(getent hosts ${WIREGUARD_GATEWAY} 2>/dev/null | awk '{print $1}') + [ -z "$GW_IP" ] && GW_IP=${WIREGUARD_GATEWAY} + ip route add 10.10.0.0/16 via $GW_IP 2>/dev/null || true + echo "VPN route: 10.10.0.0/16 via $GW_IP" + env: + - name: WIREGUARD_GATEWAY + valueFrom: + configMapKeyRef: + name: {{ include "tod.fullname" . }} + key: WIREGUARD_GATEWAY + securityContext: + runAsUser: 0 + capabilities: + add: ["NET_ADMIN"] + {{- end }} + # Init 2: Run database migrations + - name: migrations + image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}" + imagePullPolicy: {{ .Values.api.image.pullPolicy }} + command: ["python", "-m", "alembic", "upgrade", "head"] + env: + - name: DATABASE_URL + valueFrom: + configMapKeyRef: + name: {{ include "tod.fullname" . }} + key: DATABASE_URL + - name: SYNC_DATABASE_URL + valueFrom: + configMapKeyRef: + name: {{ include "tod.fullname" . }} + key: SYNC_DATABASE_URL + securityContext: + runAsUser: 1001 + runAsNonRoot: true + containers: + - name: api + image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}" + imagePullPolicy: {{ .Values.api.image.pullPolicy }} + ports: + - name: http + containerPort: 8000 + protocol: TCP + envFrom: + - configMapRef: + name: {{ include "tod.fullname" . }} + env: + - name: JWT_SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ include "tod.fullname" . }}-secrets + key: JWT_SECRET_KEY + - name: CREDENTIAL_ENCRYPTION_KEY + valueFrom: + secretKeyRef: + name: {{ include "tod.fullname" . }}-secrets + key: CREDENTIAL_ENCRYPTION_KEY + - name: OPENBAO_TOKEN + valueFrom: + secretKeyRef: + name: {{ include "tod.fullname" . }}-secrets + key: OPENBAO_TOKEN + - name: FIRST_ADMIN_EMAIL + valueFrom: + secretKeyRef: + name: {{ include "tod.fullname" . }}-secrets + key: FIRST_ADMIN_EMAIL + - name: FIRST_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "tod.fullname" . }}-secrets + key: FIRST_ADMIN_PASSWORD + - name: SMTP_USER + valueFrom: + secretKeyRef: + name: {{ include "tod.fullname" . }}-secrets + key: SMTP_USER + - name: SMTP_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "tod.fullname" . }}-secrets + key: SMTP_PASSWORD + volumeMounts: + - name: git-store + mountPath: /data/git-store + - name: firmware-cache + mountPath: /data/firmware-cache + {{- if .Values.wireguard.enabled }} + - name: wireguard-config + mountPath: /data/wireguard + {{- end }} + livenessProbe: + httpGet: + path: {{ .Values.api.probes.liveness.path }} + port: http + initialDelaySeconds: {{ .Values.api.probes.liveness.initialDelaySeconds }} + periodSeconds: {{ .Values.api.probes.liveness.periodSeconds }} + failureThreshold: {{ .Values.api.probes.liveness.failureThreshold }} + readinessProbe: + httpGet: + path: {{ .Values.api.probes.readiness.path }} + port: http + initialDelaySeconds: {{ .Values.api.probes.readiness.initialDelaySeconds }} + periodSeconds: {{ .Values.api.probes.readiness.periodSeconds }} + failureThreshold: {{ .Values.api.probes.readiness.failureThreshold }} + resources: + {{- toYaml .Values.api.resources | nindent 12 }} + securityContext: + runAsUser: 1001 + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + capabilities: + drop: ["ALL"] + volumes: + - name: git-store + persistentVolumeClaim: + claimName: {{ include "tod.fullname" . }}-git-store + - name: firmware-cache + persistentVolumeClaim: + claimName: {{ include "tod.fullname" . }}-firmware-cache + {{- if .Values.wireguard.enabled }} + - name: wireguard-config + persistentVolumeClaim: + claimName: {{ include "tod.fullname" . }}-wireguard-config + {{- end }} diff --git a/infrastructure/helm/templates/api-service.yaml b/infrastructure/helm/templates/api-service.yaml new file mode 100644 index 0000000..a9ff356 --- /dev/null +++ b/infrastructure/helm/templates/api-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "tod.fullname" . }}-api + labels: + {{- include "tod.componentLabels" (dict "context" . "component" "api") | nindent 4 }} +spec: + type: ClusterIP + ports: + - name: http + port: {{ .Values.api.service.port }} + targetPort: http + protocol: TCP + selector: + {{- include "tod.componentSelectorLabels" (dict "context" . "component" "api") | nindent 4 }}