# services/gitea/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gitea
  namespace: gitea
  labels:
    app: gitea
    atlas.bstein.dev/workload-profile: heavy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gitea
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 0
  template:
    metadata:
      labels:
        app: gitea
        atlas.bstein.dev/workload-profile: heavy
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/agent-init-first: "true"
        vault.hashicorp.com/role: "gitea"
        vault.hashicorp.com/agent-inject-secret-gitea-db-secret__password: "kv/data/atlas/gitea/gitea-db-secret"
        vault.hashicorp.com/agent-inject-template-gitea-db-secret__password: |
          {{ with secret "kv/data/atlas/gitea/gitea-db-secret" }}
          {{ .Data.data.password }}
          {{ end }}
        vault.hashicorp.com/agent-inject-secret-gitea-secret__SECRET_KEY: "kv/data/atlas/gitea/gitea-secret"
        vault.hashicorp.com/agent-inject-template-gitea-secret__SECRET_KEY: |
          {{ with secret "kv/data/atlas/gitea/gitea-secret" }}
          {{ .Data.data.SECRET_KEY }}
          {{ end }}
        vault.hashicorp.com/agent-inject-secret-gitea-secret__INTERNAL_TOKEN: "kv/data/atlas/gitea/gitea-secret"
        vault.hashicorp.com/agent-inject-template-gitea-secret__INTERNAL_TOKEN: |
          {{ with secret "kv/data/atlas/gitea/gitea-secret" }}
          {{ .Data.data.INTERNAL_TOKEN }}
          {{ end }}
        vault.hashicorp.com/agent-inject-secret-gitea-oidc__client_id: "kv/data/atlas/gitea/gitea-oidc"
        vault.hashicorp.com/agent-inject-template-gitea-oidc__client_id: |
          {{ with secret "kv/data/atlas/gitea/gitea-oidc" }}
          {{ .Data.data.client_id }}
          {{ end }}
        vault.hashicorp.com/agent-inject-secret-gitea-oidc__client_secret: "kv/data/atlas/gitea/gitea-oidc"
        vault.hashicorp.com/agent-inject-template-gitea-oidc__client_secret: |
          {{ with secret "kv/data/atlas/gitea/gitea-oidc" }}
          {{ .Data.data.client_secret }}
          {{ end }}
        vault.hashicorp.com/agent-inject-secret-gitea-oidc__openid_auto_discovery_url: "kv/data/atlas/gitea/gitea-oidc"
        vault.hashicorp.com/agent-inject-template-gitea-oidc__openid_auto_discovery_url: |
          {{ with secret "kv/data/atlas/gitea/gitea-oidc" }}
          {{ .Data.data.openid_auto_discovery_url }}
          {{ end }}
        vault.hashicorp.com/agent-inject-secret-gitea-veles__client_id: "kv/data/atlas/gitea/gitea-veles-oidc"
        vault.hashicorp.com/agent-inject-template-gitea-veles__client_id: |
          {{ with secret "kv/data/atlas/gitea/gitea-veles-oidc" }}
          {{ .Data.data.client_id }}
          {{ end }}
        vault.hashicorp.com/agent-inject-secret-gitea-veles__client_secret: "kv/data/atlas/gitea/gitea-veles-oidc"
        vault.hashicorp.com/agent-inject-template-gitea-veles__client_secret: |
          {{ with secret "kv/data/atlas/gitea/gitea-veles-oidc" }}
          {{ .Data.data.client_secret }}
          {{ end }}
        vault.hashicorp.com/agent-inject-secret-gitea-veles__discovery_url: "kv/data/atlas/gitea/gitea-veles-oidc"
        vault.hashicorp.com/agent-inject-template-gitea-veles__discovery_url: |
          {{ with secret "kv/data/atlas/gitea/gitea-veles-oidc" }}
          {{ .Data.data.openid_auto_discovery_url }}
          {{ end }}
        vault.hashicorp.com/agent-inject-secret-gitea-veles__claim_name: "kv/data/atlas/gitea/gitea-veles-oidc"
        vault.hashicorp.com/agent-inject-template-gitea-veles__claim_name: |
          {{ with secret "kv/data/atlas/gitea/gitea-veles-oidc" }}
          {{ .Data.data.required_claim_name }}
          {{ end }}
        vault.hashicorp.com/agent-inject-secret-gitea-veles__claim_value: "kv/data/atlas/gitea/gitea-veles-oidc"
        vault.hashicorp.com/agent-inject-template-gitea-veles__claim_value: |
          {{ with secret "kv/data/atlas/gitea/gitea-veles-oidc" }}
          {{ .Data.data.required_claim_value }}
          {{ end }}
        vault.hashicorp.com/agent-inject-secret-gitea-veles__team_map: "kv/data/atlas/gitea/gitea-veles-oidc"
        vault.hashicorp.com/agent-inject-template-gitea-veles__team_map: |
          {{ with secret "kv/data/atlas/gitea/gitea-veles-oidc" }}
          {{ .Data.data.group_team_map }}
          {{ end }}
    spec:
      serviceAccountName: gitea-vault
      initContainers:
        - name: configure-oidc
          image: gitea/gitea:1.23
          securityContext:
            runAsUser: 1000
            runAsGroup: 1000
          command:
            - /bin/sh
            - -c
            - |
              set -eu
              APPINI=/data/gitea/conf/app.ini
              BIN=/usr/local/bin/gitea

              read_secret() {
                path="$1"
                if [ ! -r "$path" ]; then
                  echo "Missing readable Vault secret file: $path" >&2
                  return 1
                fi
                tr -d '\r\n' <"$path"
              }

              CLIENT_ID="$(read_secret /vault/secrets/gitea-oidc__client_id || true)"
              CLIENT_SECRET="$(read_secret /vault/secrets/gitea-oidc__client_secret || true)"
              DISCOVERY_URL="$(read_secret /vault/secrets/gitea-oidc__openid_auto_discovery_url || true)"
              VELES_CLIENT_ID="$(read_secret /vault/secrets/gitea-veles__client_id || true)"
              VELES_CLIENT_SECRET="$(read_secret /vault/secrets/gitea-veles__client_secret || true)"
              VELES_DISCOVERY_URL="$(read_secret /vault/secrets/gitea-veles__discovery_url || true)"
              VELES_REQUIRED_CLAIM_NAME="$(read_secret /vault/secrets/gitea-veles__claim_name || true)"
              VELES_REQUIRED_CLAIM_VALUE="$(read_secret /vault/secrets/gitea-veles__claim_value || true)"
              VELES_GROUP_TEAM_MAP="$(read_secret /vault/secrets/gitea-veles__team_map || true)"

              if [ ! -r "$APPINI" ]; then
                echo "Gitea app.ini is not readable yet; skipping OIDC source maintenance" >&2
                exit 0
              fi

              if ! list="$($BIN -c "$APPINI" admin auth list)"; then
                echo "Gitea auth source list failed; skipping OIDC source maintenance" >&2
                exit 0
              fi

              ensure_oidc_source() {
                source_name="$1"
                source_scopes="$2"
                source_client_id="$3"
                source_client_secret="$4"
                source_discovery_url="$5"
                source_required_claim_name="$6"
                source_required_claim_value="$7"
                shift 7
                id=$(echo "$list" | awk -v name="$source_name" '$2==name{print $1}')
                if [ -n "$id" ]; then
                  echo "Updating auth source ${source_name} id=${id}"
                  if ! $BIN -c "$APPINI" admin auth update-oauth \
                    --id "$id" \
                    --name "$source_name" \
                    --provider openidConnect \
                    --key "$source_client_id" \
                    --secret "$source_client_secret" \
                    --auto-discover-url "$source_discovery_url" \
                    --scopes "$source_scopes" \
                    --required-claim-name "$source_required_claim_name" \
                    --required-claim-value "$source_required_claim_value" \
                    --group-claim-name groups \
                    --admin-group admin \
                    --skip-local-2fa "$@"; then
                    echo "OIDC update failed for ${source_name}; continuing without blocking startup" >&2
                  fi
                else
                  echo "Creating auth source ${source_name}"
                  if ! $BIN -c "$APPINI" admin auth add-oauth \
                    --name "$source_name" \
                    --provider openidConnect \
                    --key "$source_client_id" \
                    --secret "$source_client_secret" \
                    --auto-discover-url "$source_discovery_url" \
                    --scopes "$source_scopes" \
                    --required-claim-name "$source_required_claim_name" \
                    --required-claim-value "$source_required_claim_value" \
                    --group-claim-name groups \
                    --admin-group admin \
                    --skip-local-2fa "$@"; then
                    echo "OIDC create failed for ${source_name}; continuing without blocking startup" >&2
                  fi
                fi
              }

              if [ -n "$CLIENT_ID" ] && [ -n "$CLIENT_SECRET" ] && [ -n "$DISCOVERY_URL" ]; then
                ensure_oidc_source keycloak "openid profile email groups" "$CLIENT_ID" "$CLIENT_SECRET" "$DISCOVERY_URL" "" ""
              else
                echo "Skipping keycloak auth source maintenance because atlas OIDC secret data is incomplete" >&2
              fi

              if [ -n "$VELES_CLIENT_ID" ] && [ -n "$VELES_CLIENT_SECRET" ] && [ -n "$VELES_DISCOVERY_URL" ] && [ -n "$VELES_REQUIRED_CLAIM_NAME" ] && [ -n "$VELES_REQUIRED_CLAIM_VALUE" ]; then
                if [ -n "$VELES_GROUP_TEAM_MAP" ]; then
                  ensure_oidc_source veles "openid profile email groups" "$VELES_CLIENT_ID" "$VELES_CLIENT_SECRET" "$VELES_DISCOVERY_URL" "$VELES_REQUIRED_CLAIM_NAME" "$VELES_REQUIRED_CLAIM_VALUE" \
                    --restricted-group "$VELES_REQUIRED_CLAIM_VALUE" \
                    --group-team-map "$VELES_GROUP_TEAM_MAP"
                else
                  ensure_oidc_source veles "openid profile email groups" "$VELES_CLIENT_ID" "$VELES_CLIENT_SECRET" "$VELES_DISCOVERY_URL" "$VELES_REQUIRED_CLAIM_NAME" "$VELES_REQUIRED_CLAIM_VALUE" \
                    --restricted-group "$VELES_REQUIRED_CLAIM_VALUE"
                fi
              else
                echo "Skipping veles auth source maintenance because Veles OIDC secret data is incomplete" >&2
              fi
          volumeMounts:
            - name: gitea-data
              mountPath: /data
      nodeSelector:
        node-role.kubernetes.io/worker: "true"
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: hardware
                operator: In
                values: ["rpi5"]
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              preference:
                matchExpressions:
                - key: hardware
                  operator: In
                  values: ["rpi5"]
            - weight: 100
              preference:
                matchExpressions:
                - key: kubernetes.io/hostname
                  operator: In
                  values: ["titan-20","titan-21"]
      containers:
        - name: gitea
          image: gitea/gitea:1.23
          command: ["/bin/sh", "-c"]
          args:
            - |
              set -euo pipefail
              export GITEA__security__SECRET_KEY="$(tr -d '\r\n' </vault/secrets/gitea-secret__SECRET_KEY)"
              export GITEA__security__INTERNAL_TOKEN="$(tr -d '\r\n' </vault/secrets/gitea-secret__INTERNAL_TOKEN)"
              export DB_PASS="$(tr -d '\r\n' </vault/secrets/gitea-db-secret__password)"
              exec /usr/bin/entrypoint /usr/bin/s6-svscan /etc/s6
          ports:
            - containerPort: 3000
              name: http
            - containerPort: 2242
              name: ssh
          env:
            - name: USER_UID
              value: "1000"
            - name: USER_GID
              value: "1000"
            - name: DEFAULT_BRANCH
              value: "master"
            - name: ROOT_URL
              value: "https://scm.bstein.dev"
            - name: GITEA__service__ENABLE_OPENID_SIGNIN
              value: "true"
            - name: GITEA__oauth2_client__ENABLE_AUTO_REGISTRATION
              value: "true"
            - name: GITEA__oauth2_client__ACCOUNT_LINKING
              value: "auto"
            - name: GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION
              value: "true"
            - name: GITEA__service__DISABLE_REGISTRATION
              value: "false"
            - name: GITEA__log__LEVEL
              value: "trace"
            - name: GITEA__service__REQUIRE_SIGNIN_VIEW
              value: "false"
            - name: GITEA__webhook__ALLOWED_HOST_LIST
              value: "ci.bstein.dev"
            - name: GITEA__server__PROXY_HEADERS
              value: "X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host"
            - name: GITEA__session__COOKIE_SECURE
              value: "true"
            - name: GITEA__session__DOMAIN
              value: "scm.bstein.dev"
            - name: GITEA__session__SAME_SITE
              value: "lax"
            - name: DB_TYPE
              value: "postgres"
            - name: DB_HOST
              value: "postgres-service.postgres.svc.cluster.local:5432"
            - name: DB_NAME
              value: "gitea"
            - name: DB_USER
              value: "gitea"
            - name: START_SSH_SERVER
              value: "true"
            - name: SSH_PORT
              value: "2242"
          resources:
            requests:
              cpu: 500m
              memory: 1Gi
            limits:
              cpu: 1500m
              # Raised from 2Gi: the pod sat at 2073Mi and wedged, refusing
              # connections on its own loopback while the API timed out from
              # everywhere. The SSH LoginGraceTime drops in the log are a
              # symptom of that starvation, not a separate problem.
              memory: 3Gi
          volumeMounts:
            - name: gitea-data
              mountPath: /data
      volumes:
        - name: gitea-data
          persistentVolumeClaim:
            claimName: gitea-data
