# services/hermes-chat/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hermes-chat
  namespace: hermes-chat
  labels:
    app: hermes-chat
spec:
  replicas: 1
  revisionHistoryLimit: 2
  progressDeadlineSeconds: 1800
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: hermes-chat
  template:
    metadata:
      labels:
        app: hermes-chat
      annotations:
        ai.bstein.dev/model: openai-codex/gpt-5.6-terra with local gpt-oss:20b fallback
        ai.bstein.dev/role: personal-chat-research
        ai.bstein.dev/isolation: observer-only Kubernetes RBAC, private-service egress denied
        ai.bstein.dev/session-reset: "20260802-consumer-pty-recovery"
        ai.bstein.dev/frontend-fix: scope PTY attachment by selected conversation
        ai.bstein.dev/user-boundary: one OIDC subject with an isolated PVC subdirectory
    spec:
      serviceAccountName: hermes-chat
      automountServiceAccountToken: true
      securityContext:
        fsGroup: 10000
        fsGroupChangePolicy: OnRootMismatch
        seccompProfile:
          type: RuntimeDefault
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/arch
                    operator: In
                    values:
                      - arm64
                  - key: node-role.kubernetes.io/worker
                    operator: In
                    values:
                      - "true"
                  - key: kubernetes.io/hostname
                    operator: NotIn
                    values:
                      - titan-13
                      - titan-15
                      - titan-17
                      - titan-18
                      - titan-19
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              preference:
                matchExpressions:
                  - key: atlas.bstein.dev/spillover
                    operator: DoesNotExist
            - weight: 90
              preference:
                matchExpressions:
                  - key: hardware
                    operator: In
                    values:
                      - rpi5
            - weight: 50
              preference:
                matchExpressions:
                  - key: hardware
                    operator: In
                    values:
                      - rpi4
      initContainers:
        - name: migrate-user-sessions
          image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f
          imagePullPolicy: IfNotPresent
          command:
            - /opt/hermes/bin/hermes-session-migrate
          env:
            - name: HERMES_HOME
              value: /storage/users/4794ab8284a14d421eaeea3e
            - name: HERMES_MIGRATE_SOURCE_HOME
              value: /storage
            - name: HERMES_MIGRATE_USER_ID
              value: 26b56113-0ec5-40b0-be7d-c2cd862f9bbc
            - name: HERMES_MIGRATE_SESSION_IDS
              value: 20260802_182723_9f53f1,20260802_173527_9dc51b
          securityContext:
            allowPrivilegeEscalation: false
            runAsUser: 10000
            runAsGroup: 10000
            seccompProfile:
              type: RuntimeDefault
          volumeMounts:
            - name: home
              mountPath: /storage
          resources:
            requests:
              cpu: 25m
              memory: 64Mi
            limits:
              cpu: 250m
              memory: 256Mi
        - name: init-config
          image: busybox:1.37
          imagePullPolicy: IfNotPresent
          command:
            - sh
            - -c
            - |
              set -eu
              user_home=/storage/users/4794ab8284a14d421eaeea3e
              mkdir -p "${user_home}/workspace/skills" "${user_home}/home/.local/bin" "${user_home}/logs"
              cp /config/config.yaml "${user_home}/config.yaml"
              cp /config/SOUL.md "${user_home}/SOUL.md"
              cp /config/AGENTS.md "${user_home}/workspace/AGENTS.md"
              touch "${user_home}/.env"
              if ! grep -q '^API_SERVER_KEY=' "${user_home}/.env"; then
                api_key="$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | od -An -tx1 | tr -d ' \n')"
                printf '\nAPI_SERVER_KEY=%s\n' "${api_key}" >> "${user_home}/.env"
              fi
              chmod 0600 "${user_home}/.env"
              chown -R 10000:10000 "${user_home}"
          securityContext:
            allowPrivilegeEscalation: false
            runAsUser: 0
            runAsGroup: 0
            seccompProfile:
              type: RuntimeDefault
          volumeMounts:
            - name: home
              mountPath: /storage
            - name: config
              mountPath: /config
              readOnly: true
          resources:
            requests:
              cpu: 25m
              memory: 32Mi
            limits:
              cpu: 100m
              memory: 64Mi
        - name: install-kubectl
          image: bitnami/kubectl@sha256:554ab88b1858e8424c55de37ad417b16f2a0e65d1607aa0f3fe3ce9b9f10b131
          imagePullPolicy: IfNotPresent
          command:
            - /bin/sh
            - -c
            - |
              set -eu
              cp "$(command -v kubectl)" /tools/kubectl
              chmod 0755 /tools/kubectl
              chown 10000:10000 /tools/kubectl
          securityContext:
            allowPrivilegeEscalation: false
            runAsUser: 0
            runAsGroup: 0
            seccompProfile:
              type: RuntimeDefault
          volumeMounts:
            - name: tools
              mountPath: /tools
          resources:
            requests:
              cpu: 25m
              memory: 32Mi
            limits:
              cpu: 100m
              memory: 64Mi
      containers:
        - name: hermes-chat
          image: registry.bstein.dev/bstein/hermes-agent@sha256:15c5c538c0b58686af2e54e10bc870b23284789d485a609349df24ed3053622f
          imagePullPolicy: IfNotPresent
          args:
            - gateway
            - run
          ports:
            - name: dashboard
              containerPort: 9119
              protocol: TCP
          env:
            - name: HERMES_HOME
              value: /opt/data
            - name: HOME
              value: /opt/data/home
            - name: PATH
              value: /opt/data/home/.local/bin:/opt/hermes/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
            - name: HERMES_DASHBOARD
              value: "1"
            - name: HERMES_DASHBOARD_HOST
              value: 0.0.0.0
            - name: HERMES_DASHBOARD_PORT
              value: "9119"
            - name: HERMES_DASHBOARD_PUBLIC_URL
              value: https://chat.bstein.dev
            - name: HERMES_DASHBOARD_OIDC_ISSUER
              value: https://sso.bstein.dev/realms/atlas
            - name: HERMES_DASHBOARD_OIDC_CLIENT_ID
              value: hermes-chat-dashboard
            - name: HERMES_DASHBOARD_OIDC_SCOPES
              value: openid profile email groups
            - name: HERMES_DASHBOARD_OIDC_ALLOWED_USER_IDS
              value: 26b56113-0ec5-40b0-be7d-c2cd862f9bbc
            - name: API_SERVER_ENABLED
              value: "true"
            - name: API_SERVER_HOST
              value: 0.0.0.0
            - name: API_SERVER_PORT
              value: "8642"
            - name: API_SERVER_CORS_ORIGINS
              value: https://chat.bstein.dev
          volumeMounts:
            - name: home
              mountPath: /opt/data
              subPath: users/4794ab8284a14d421eaeea3e
            - name: tools
              mountPath: /usr/local/bin/kubectl
              subPath: kubectl
          readinessProbe:
            httpGet:
              path: /api/status
              port: dashboard
            initialDelaySeconds: 30
            periodSeconds: 10
            timeoutSeconds: 5
          livenessProbe:
            httpGet:
              path: /api/status
              port: dashboard
            initialDelaySeconds: 90
            periodSeconds: 30
            timeoutSeconds: 10
          securityContext:
            allowPrivilegeEscalation: false
            seccompProfile:
              type: RuntimeDefault
          resources:
            requests:
              cpu: 100m
              memory: 768Mi
              ephemeral-storage: 256Mi
            limits:
              cpu: "1"
              memory: 2Gi
              ephemeral-storage: 1Gi
      volumes:
        - name: home
          persistentVolumeClaim:
            claimName: hermes-chat-home
        - name: config
          configMap:
            name: hermes-chat-config
        - name: tools
          emptyDir: {}
