apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-ui
  namespace: gsc-my
  labels:
    app: my-ui
    component: frontend
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: my-ui
  template:
    metadata:
      labels:
        app: my-ui
        component: frontend
        # Egress to the in-cluster web-proxy (Squid) for NextAuth's
        # Keycloak issuer-discovery fetch to auth.gosec.cloud.
        egress-internet: "true"
        # Egress to the internal-gateway proxy (Envoy) so the app can
        # reach postgresql.internal-gateway.svc.cluster.local:5432.
        # Selected by the allow-internal-gateway GlobalNetworkPolicy.
        egress-internal: "true"
    spec:
      containers:
        - name: my-ui
          image: registry.gosec.internal/gsc-my/ui:v0.1.5
          imagePullPolicy: Always
          ports:
            - containerPort: 3000
              name: http
          env:
            - name: NODE_ENV
              value: "production"
            # Route Node fetch() through Squid for Keycloak discovery.
            - name: HTTP_PROXY
              value: "http://web-proxy.web-proxy.svc.cluster.local:3128"
            - name: HTTPS_PROXY
              value: "http://web-proxy.web-proxy.svc.cluster.local:3128"
            - name: NO_PROXY
              value: "localhost,127.0.0.1,.cluster.local,.svc,.gosec.internal"
            - name: NEXT_PUBLIC_APP_URL
              value: "https://my.gosec.internal"
            # Postgres (gsc_core — admin/nav/public/shell schemas).
            - name: PGHOST
              valueFrom:
                secretKeyRef:
                  name: gsc-my-db
                  key: host
            - name: PGPORT
              valueFrom:
                secretKeyRef:
                  name: gsc-my-db
                  key: port
            - name: PGUSER
              valueFrom:
                secretKeyRef:
                  name: gsc-my-db
                  key: user
            - name: PGPASSWORD
              valueFrom:
                secretKeyRef:
                  name: gsc-my-db
                  key: password
            - name: PGDATABASE
              valueFrom:
                secretKeyRef:
                  name: gsc-my-db
                  key: database
            - name: DATABASE_URL
              value: "postgresql://$(PGUSER):$(PGPASSWORD)@$(PGHOST):$(PGPORT)/$(PGDATABASE)"
            # NextAuth + Keycloak (gosecCloud realm).
            - name: AUTH_KEYCLOAK_ID
              valueFrom:
                secretKeyRef:
                  name: my-ui
                  key: keycloak-client-id
            - name: AUTH_KEYCLOAK_SECRET
              valueFrom:
                secretKeyRef:
                  name: my-ui
                  key: keycloak-client-secret
            - name: AUTH_KEYCLOAK_ISSUER
              value: "https://auth.gosec.cloud/realms/gosecCloud"
            - name: NEXTAUTH_URL
              value: "https://my.gosec.internal"
            # NextAuth v5 prefers AUTH_URL over NEXTAUTH_URL. Set both
            # so a stale .env baked into a future image build can't
            # shadow the runtime config and emit form actions pointing
            # at the wrong host.
            - name: AUTH_URL
              value: "https://my.gosec.internal"
            - name: AUTH_TRUST_HOST
              value: "true"
            - name: NEXTAUTH_SECRET
              valueFrom:
                secretKeyRef:
                  name: my-ui
                  key: nextauth-secret
            # gsc-ops-api (mTLS) for chat contacts route. Cert files are
            # mounted from a separate secret if/when the route is used;
            # leaving the URL unset disables the contacts provider
            # gracefully — see src/app/api/chat/contacts/route.ts.
            # - name: OPS_API_URL
            # - name: OPS_API_KEY
          resources:
            requests:
              memory: "384Mi"
              cpu: "100m"
            limits:
              memory: "768Mi"
              cpu: "500m"
          readinessProbe:
            httpGet:
              path: /api/health
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 10
            timeoutSeconds: 5
          livenessProbe:
            httpGet:
              path: /api/health
              port: 3000
            initialDelaySeconds: 30
            periodSeconds: 30
            timeoutSeconds: 5
          securityContext:
            runAsNonRoot: true
            runAsUser: 1000
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
          volumeMounts:
            - name: tmp
              mountPath: /tmp
            - name: nextjs-cache
              mountPath: /app/.next/cache
      volumes:
        - name: tmp
          emptyDir: {}
        - name: nextjs-cache
          emptyDir: {}
      imagePullSecrets:
        - name: registry-credentials
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - my-ui
                topologyKey: kubernetes.io/hostname

---
apiVersion: v1
kind: Service
metadata:
  name: my-ui
  namespace: gsc-my
  labels:
    app: my-ui
spec:
  type: ClusterIP
  ports:
    - port: 3000
      targetPort: 3000
      protocol: TCP
      name: http
  selector:
    app: my-ui

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ui
  namespace: gsc-my
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    # ModSecurity + OWASP CRS is enabled cluster-wide; the chunked
    # NextAuth session cookie (>4 KB of base64 JWE) trips CRS rules
    # and returns 403 before the request reaches the pod. Same fix
    # applied to crm-ui / support-ui / chronos / gsc-meet / etc.
    nginx.ingress.kubernetes.io/enable-modsecurity: "false"
    nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
    nginx.ingress.kubernetes.io/proxy-buffers-number: "8"
    # Hardening headers (CSP comes from cluster ConfigMap).
    nginx.ingress.kubernetes.io/configuration-snippet: |
      more_set_headers "X-Frame-Options: SAMEORIGIN";
      more_set_headers "X-Content-Type-Options: nosniff";
      more_set_headers "Referrer-Policy: strict-origin-when-cross-origin";
      more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains";
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - my.gosec.internal
      secretName: my-tls-internal
  rules:
    - host: my.gosec.internal
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-ui
                port:
                  number: 3000
