{{- /*
Step 01 — gitea-mirror.

Mirrors the upstream openova-io public repo into the local Sovereign
Gitea so subsequent Flux GitRepository pivots resolve against in-cluster
DNS. This step is the canonical seam: catalyst-api stamps a Job from
this PodSpec exactly once per cutover.

Consumer contract (catalyst-api cutover endpoint, issue #792):
  - Discovered by labels app.kubernetes.io/part-of=self-sovereign-cutover
    + app.kubernetes.io/component=cutover-step.
  - Order = bp.openova.io/cutover-order label (integer, ascending).
  - Mode = bp.openova.io/cutover-mode label ("job" here).
  - Required data keys:
      stepName  short slug used in status ConfigMap + Job naming
      podSpec   YAML of corev1.PodSpec; catalyst-api wraps it in
                PodTemplateSpec + batchv1.Job.

Image phase: pre — runs BEFORE registries.yaml v2 is active.
*/ -}}
apiVersion: v1
kind: ConfigMap
metadata:
  name: cutover-step-01-gitea-mirror
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "bp-self-sovereign-cutover.labels" . | nindent 4 }}
    app.kubernetes.io/component: cutover-step
    bp.openova.io/cutover-order: "1"
    bp.openova.io/cutover-mode: "job"
data:
  stepName: gitea-mirror
  podSpec: |
    serviceAccountName: {{ include "bp-self-sovereign-cutover.serviceAccountName" . }}
    restartPolicy: Never
    activeDeadlineSeconds: {{ .Values.stepTimeouts.giteaMirrorSeconds }}
    containers:
      - name: gitea-mirror
        image: {{ include "bp-self-sovereign-cutover.image" (dict "repository" .Values.images.git.repository "tag" .Values.images.git.tag "cutoverPhase" "pre" "Values" .Values) }}
        imagePullPolicy: IfNotPresent
        env:
          - name: UPSTREAM_REPO_URL
            value: {{ .Values.upstream.repoURL | quote }}
          - name: UPSTREAM_BRANCH
            value: {{ .Values.upstream.branch | quote }}
          - name: GITEA_INTERNAL_URL
            value: {{ .Values.sovereign.giteaInternalURL | quote }}
          - name: GITEA_ORG
            value: {{ .Values.gitea.org | quote }}
          - name: GITEA_REPO
            value: {{ .Values.gitea.repo | quote }}
          - name: MIRROR_INTERVAL
            value: {{ .Values.upstream.mirrorInterval | quote }}
          - name: GITEA_USERNAME
            valueFrom:
              secretKeyRef:
                name: {{ .Values.gitea.adminSecretRef.name }}
                key: {{ .Values.gitea.adminSecretRef.usernameKey }}
          - name: GITEA_PASSWORD
            valueFrom:
              secretKeyRef:
                name: {{ .Values.gitea.adminSecretRef.name }}
                key: {{ .Values.gitea.adminSecretRef.passwordKey }}
        command: ["/bin/sh", "-c"]
        args:
          - |
            set -eu
            # Credential hygiene: never echo $GITEA_PASSWORD. We feed it
            # to git via URL embedding only inside the redirected http
            # call below; stdout shows just the redacted form. The HTTP
            # API calls use a base64'd Authorization: Basic header (the
            # alpine/git image's BusyBox wget does NOT support --user/
            # --password flags — caught live on otech103 2026-05-04).
            redacted_url="${GITEA_INTERNAL_URL}/${GITEA_ORG}/${GITEA_REPO}.git"
            echo "[gitea-mirror] upstream=${UPSTREAM_REPO_URL} branch=${UPSTREAM_BRANCH}"
            echo "[gitea-mirror] target=${redacted_url}"
            echo "[gitea-mirror] mirror_interval=${MIRROR_INTERVAL}"

            # #968 — DNS-readiness probe for gitea-http.
            #
            # The cutover auto-trigger fires within seconds of bp-self-
            # sovereign-cutover Helm-install completing. On a fresh
            # Sovereign the gitea Pod can still be moving from Running
            # to Ready, in which case the headless service `gitea-http`
            # has no DNS record published yet. Without this probe the
            # very first wget call returns `bad address` and the Job
            # exits non-zero. catalyst-api's cutover engine treats that
            # as a hard failure (per cutover.go #968 backoffLimit was
            # raised to 3, but local resolve here is cheaper and faster
            # than burning Pod-restart budget). On otech115 2026-05-05
            # this race fired Step-01 at +8s after gitea reached Ready
            # and DNS hadn't propagated; one nslookup wait of ~10s would
            # have been sufficient. Loop budget = 30 × 5s = 150s, well
            # under the step's activeDeadlineSeconds.
            gitea_host="$(printf '%s' "${GITEA_INTERNAL_URL}" | sed -E 's|^https?://||' | cut -d: -f1 | cut -d/ -f1)"
            if [ -n "${gitea_host}" ]; then
              echo "[gitea-mirror] waiting for DNS resolution of ${gitea_host}"
              dns_ready="false"
              for i in $(seq 1 30); do
                if nslookup "${gitea_host}" >/dev/null 2>&1; then
                  echo "[gitea-mirror] DNS ready for ${gitea_host} (attempt ${i})"
                  dns_ready="true"
                  break
                fi
                sleep 5
              done
              if [ "${dns_ready}" != "true" ]; then
                echo "[gitea-mirror] FATAL: ${gitea_host} did not resolve within 150s" >&2
                exit 1
              fi
            fi

            # Build BusyBox-wget-compatible Basic auth header. printf -n
            # avoids the trailing newline that would otherwise corrupt
            # the base64 encoding (and thus the credential).
            basic_auth=$(printf '%s' "${GITEA_USERNAME}:${GITEA_PASSWORD}" | base64 -w0 2>/dev/null || printf '%s' "${GITEA_USERNAME}:${GITEA_PASSWORD}" | base64 | tr -d '\n')

            # 1. Ensure the org exists in Gitea (idempotent — POST /orgs
            #    returns 422 if a same-named user exists, 422 if org
            #    exists, 201 on create). We swallow non-201 because the
            #    "already exists" surface is the steady-state path.
            api_url="${GITEA_INTERNAL_URL}/api/v1"
            org_check=$(wget -q -O - \
              --header="Authorization: Basic ${basic_auth}" \
              --header="Content-Type: application/json" \
              "${api_url}/orgs/${GITEA_ORG}" 2>/dev/null || echo "missing")
            if [ "${org_check}" = "missing" ]; then
              echo "[gitea-mirror] creating org ${GITEA_ORG}"
              wget -q -O /dev/null --post-data="{\"username\":\"${GITEA_ORG}\",\"visibility\":\"public\"}" \
                --header="Authorization: Basic ${basic_auth}" \
                --header="Content-Type: application/json" \
                "${api_url}/orgs" 2>/dev/null || true
            else
              echo "[gitea-mirror] org already present"
            fi

            # 2. Ensure the repo exists in Gitea as a REGULAR (non-mirror)
            #    repository, then bare-clone upstream and push so the
            #    local Gitea has the same content. This replaces the
            #    earlier `/repos/migrate {mirror:true}` approach (PR
            #    #970) — Gitea pull-mirrors are read-only and block the
            #    cutover step-06 push that pivots HelmRepository URLs
            #    to local Harbor (see PR #1029, otech125 incident).
            #    Gitea 1.22 does not support converting a pull-mirror
            #    to standalone via API (only `mirror_interval: "0s"`
            #    silently no-ops, leaving `mirror: true` set, which
            #    keeps the repo read-only). The architecturally correct
            #    behaviour at cutover IS to stop tracking upstream — the
            #    Sovereign is going self-hosted from this point on. If
            #    operator wants a chart rollout post-cutover they
            #    re-run this Job (idempotent: rebases local main on
            #    upstream main and force-pushes any new commits).
            repo_check=$(wget -q -O - \
              --header="Authorization: Basic ${basic_auth}" \
              --header="Content-Type: application/json" \
              "${api_url}/repos/${GITEA_ORG}/${GITEA_REPO}" 2>/dev/null || echo "missing")

            if [ "${repo_check}" = "missing" ]; then
              echo "[gitea-mirror] creating empty repo ${GITEA_ORG}/${GITEA_REPO}"
              create_payload=$(printf '{"name":"%s","auto_init":false,"private":false,"description":"Self-hosted clone of %s (post-cutover, standalone)"}' \
                "${GITEA_REPO}" "${UPSTREAM_REPO_URL}")
              wget -q -O /dev/null \
                --post-data="${create_payload}" \
                --header="Authorization: Basic ${basic_auth}" \
                --header="Content-Type: application/json" \
                "${api_url}/orgs/${GITEA_ORG}/repos" 2>&1 || true
              if ! wget -q -O - \
                --header="Authorization: Basic ${basic_auth}" \
                --header="Content-Type: application/json" \
                "${api_url}/repos/${GITEA_ORG}/${GITEA_REPO}" >/dev/null 2>&1; then
                echo "[gitea-mirror] FATAL: repo create failed — not visible after POST" >&2
                exit 1
              fi
              echo "[gitea-mirror] empty repo created"
            else
              echo "[gitea-mirror] repo already present (idempotent re-run); refreshing content"
            fi

            # 3. Bare-clone upstream and push to local Gitea. --mirror
            #    pushes ALL refs (every branch + tag), --force makes
            #    re-runs idempotent (the upstream may have rewritten
            #    history; we want local to match exactly).
            export HOME=/tmp
            git config --global user.name "self-sovereign-cutover"
            git config --global user.email "cutover@${gitea_host}"
            git config --global advice.detachedHead false

            local_url=$(printf '%s' "${GITEA_INTERNAL_URL}" | sed -E "s,^(https?://),\1${GITEA_USERNAME}:${GITEA_PASSWORD}@,")"/${GITEA_ORG}/${GITEA_REPO}.git"
            workdir=$(mktemp -d)
            cd "${workdir}"
            echo "[gitea-mirror] cloning ${UPSTREAM_REPO_URL} (bare)"
            git clone --bare "${UPSTREAM_REPO_URL}" upstream.git >/dev/null 2>&1
            cd upstream.git
            echo "[gitea-mirror] pushing all refs to ${redacted_url}"
            push_err=$(git push --mirror --force "${local_url}" 2>&1) || {
              echo "[gitea-mirror] FATAL: git push to local Gitea failed" >&2
              # Surface git stderr WITHOUT the embedded credential — git
              # redacts URLs in error messages by default but be defensive.
              printf '%s\n' "${push_err}" | sed -E "s,${GITEA_PASSWORD}+,REDACTED,g" >&2
              exit 1
            }
            echo "[gitea-mirror] pushed all refs successfully"

            # 4. Verify by reading back the default branch SHA and
            #    confirming non-empty (auto_init=false means a missing
            #    push leaves the repo "empty: true").
            repo_meta=$(wget -q -O - \
              --header="Authorization: Basic ${basic_auth}" \
              "${api_url}/repos/${GITEA_ORG}/${GITEA_REPO}" 2>/dev/null || echo "{}")
            if printf '%s' "${repo_meta}" | grep -q '"empty":false'; then
              echo "[gitea-mirror] verified: repo has content (empty=false)"
            else
              echo "[gitea-mirror] WARN: repo metadata still reports empty=true — push may not have landed"
            fi
            if printf '%s' "${repo_meta}" | grep -q '"mirror":false'; then
              echo "[gitea-mirror] verified: mirror=false on ${GITEA_ORG}/${GITEA_REPO} (writable for cutover step-06)"
            fi
            echo "[gitea-mirror] step complete"
        resources:
          requests: { cpu: 100m, memory: 256Mi }
          limits:   { memory: 1Gi }
        securityContext:
          runAsNonRoot: true
          runAsUser: 1000
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: false
          capabilities:
            drop: ["ALL"]
