apiVersion: apps/v1
kind: Deployment
metadata:
  name: prop-backfill
  namespace: prop
spec:
  # Single replica. podAntiAffinity targets `tier: hot` (not `app: prop`)
  # so backfill only repels the web pods, not itself. An `app: prop`
  # selector would also match backfill pods — fine at steady state, but
  # during a rolling hot-pod rollout with HPA at max replicas the old
  # RS's requiredDuringScheduling/IgnoredDuringExecution stays pinned on
  # already-running hot pods, backfill's rule excludes the new hot pod
  # from every physical host backfill hasn't touched, and the rollout
  # deadlocks because maxUnavailable=1 can't bring an old hot pod down
  # until a new one is Ready. Matching only `tier: hot` avoids that:
  # backfill never constrains itself, and hot pods have no anti-affinity
  # of their own so they can co-locate freely if HPA scales past the
  # physical-host count.
  replicas: 1
  minReadySeconds: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 0
      maxUnavailable: 1
  selector:
    matchLabels:
      app: prop
      tier: backfill
  template:
    metadata:
      labels:
        # app=prop matches the libcluster selector in runtime.exs, so these
        # pods join the same Erlang cluster as the hot-path prop pods and
        # share Oban's queue ownership / leader election.
        app: prop
        tier: backfill
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app: prop
                  tier: hot
              topologyKey: topology.kubernetes.io/physical-host
      serviceAccountName: prop
      imagePullSecrets:
        - name: forgejo-registry
      securityContext:
        runAsUser: 65534
        runAsNonRoot: true
        fsGroup: 65534
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: prop-backfill
          image: codeberg.org/gmcintire/prop:main-1777300373-610892e
          imagePullPolicy: IfNotPresent
          env:
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
            # No PHX_SERVER — endpoint stays inert. No PORT either.
            - name: PROP_ROLE
              value: "backfill"
            - name: PHX_HOST
              value: "prop.w5isp.com"
            - name: HRRR_BASE_URL
              value: "http://skippy.w5isp.com:8080"
          envFrom:
            - secretRef:
                name: prop-secrets
          # No HTTP probes (no web server). Use exec probe against the
          # running BEAM to confirm the VM is responsive. `rpc ':ok'`
          # has to spawn a release CLI and round-trip through distributed
          # Erlang; during an IEM 429 retry storm the scheduler is
          # responsive but slow. 15 s still tripped failureThreshold
          # every ~10 min, so the liveness probe now runs every 2 min
          # with a 30 s ceiling — the BEAM itself would have crashed
          # long before that if it were genuinely wedged, and rescue
          # relies on the BEAM's own crash-loop handling anyway.
          startupProbe:
            exec:
              command: ["/bin/sh", "-c", "/app/bin/microwaveprop rpc ':ok'"]
            initialDelaySeconds: 10
            periodSeconds: 10
            failureThreshold: 18
            timeoutSeconds: 30
          livenessProbe:
            exec:
              command: ["/bin/sh", "-c", "/app/bin/microwaveprop rpc ':ok'"]
            periodSeconds: 120
            failureThreshold: 3
            timeoutSeconds: 30
          resources:
            # 8GB T620 box — leave ~3GB for kubelet/containerd/OS.
            requests:
              cpu: 100m
              memory: 512Mi
            limits:
              cpu: "2"
              memory: 4Gi
          securityContext:
            allowPrivilegeEscalation: false
            runAsNonRoot: true
            runAsUser: 65534
            capabilities:
              drop:
                - ALL
            seccompProfile:
              type: RuntimeDefault
          volumeMounts:
            - name: data
              mountPath: /data
      volumes:
        - name: data
          nfs:
            server: 10.0.15.103
            path: /data
