# fxhnt-ingress — the bizworx front for *.fxhnt.ai (fund-only; replaces the foxhunt tailscale-gitlab-proxy).
# nginx terminates the *.fxhnt.ai wildcard (cert-manager secret fxhnt-wildcard-tls) and reverse-proxies the 3
# fund vhosts by ClusterIP + a socat sidecar forwards git SSH. A tailscale sidecar joins the tailnet as
# `fxhnt-ingress` so the Scaleway fxhnt.ai A-records can point at its stable tailnet IP (C4).
#
# WHY ClusterIP (not pod tailnet IPs like the foxhunt proxy did): on bizworx a tailscale-sidecar pod CAN reach
# fund ClusterIPs (10.32.x.x) — verified: cockpit ClusterIP returned HTTP 200 from a TS sidecar — but CANNOT
# reach pod IPs in 100.64.0.0/15 (Tailscale CGNAT overlap; verified: pod-IP request timed out). So proxy to
# Services by name, never to pod IPs.
# Non-headless ClusterIP Service for gitea (ns gitea). The Helm chart's gitea-http/gitea-ssh are HEADLESS
# (ClusterIP: None) → DNS returns the gitea pod IP in the 100.64.0.0/15 CGNAT range, which the fxhnt-ingress
# proxy's kernel-mode tailscale sidecar CANNOT reach. This gives a real 10.32.x ClusterIP the proxy can hit.
apiVersion: v1
kind: Service
metadata:
  name: gitea-clusterip
  namespace: gitea
  labels: { app.kubernetes.io/name: gitea-clusterip, app.kubernetes.io/part-of: foxhunt }
spec:
  type: ClusterIP
  selector: { app.kubernetes.io/instance: gitea, app.kubernetes.io/name: gitea }
  ports:
    - { port: 3000, targetPort: 3000, name: http }
    - { port: 22,   targetPort: 22,   name: ssh }
---
# ServiceAccount + Role + RoleBinding: the tailscale sidecar needs get/create/update/patch on its ts-state
# Secret (fxhnt-ingress-ts-state). Same pattern as tailscale-dagster / tailscale-dashboard.
apiVersion: v1
kind: ServiceAccount
metadata: { name: tailscale-fxhnt-ingress, namespace: foxhunt }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: tailscale-fxhnt-ingress, namespace: foxhunt }
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["create", "get", "update", "patch", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: tailscale-fxhnt-ingress, namespace: foxhunt }
subjects:
  - { kind: ServiceAccount, name: tailscale-fxhnt-ingress, namespace: foxhunt }
roleRef: { kind: Role, name: tailscale-fxhnt-ingress, apiGroup: rbac.authorization.k8s.io }
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: fxhnt-ingress-nginx
  namespace: foxhunt
  labels: { app.kubernetes.io/name: fxhnt-ingress, app.kubernetes.io/part-of: foxhunt }
data:
  default.conf: |
    # HTTP -> HTTPS
    server {
        listen 80;
        server_name *.fxhnt.ai;
        return 301 https://$host$request_uri;
    }
    # git.fxhnt.ai -> bizworx gitea (ns gitea)
    server {
        listen 443 ssl;
        server_name git.fxhnt.ai;
        ssl_certificate     /etc/nginx/certs/tls.crt;
        ssl_certificate_key /etc/nginx/certs/tls.key;
        ssl_protocols TLSv1.2 TLSv1.3;
        client_max_body_size 0;
        location / {
            proxy_pass http://gitea-clusterip.gitea.svc.cluster.local:3000;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto https;
            proxy_buffering off;
            proxy_request_buffering off;
            proxy_http_version 1.1;
            proxy_read_timeout 3600s;
            proxy_send_timeout 3600s;
            proxy_connect_timeout 300s;
        }
    }
    # dashboard.fxhnt.ai + cockpit.fxhnt.ai -> bizworx cockpit (ns foxhunt)
    server {
        listen 443 ssl;
        server_name dashboard.fxhnt.ai cockpit.fxhnt.ai;
        ssl_certificate     /etc/nginx/certs/tls.crt;
        ssl_certificate_key /etc/nginx/certs/tls.key;
        ssl_protocols TLSv1.2 TLSv1.3;
        location / {
            proxy_pass http://fxhnt-dashboard.foxhunt.svc.cluster.local:80;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto https;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
    }
    # dagster.fxhnt.ai -> bizworx dagster (ns foxhunt)
    server {
        listen 443 ssl;
        server_name dagster.fxhnt.ai;
        ssl_certificate     /etc/nginx/certs/tls.crt;
        ssl_certificate_key /etc/nginx/certs/tls.key;
        ssl_protocols TLSv1.2 TLSv1.3;
        location / {
            proxy_pass http://dagster.foxhunt.svc.cluster.local:80;
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto https;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_read_timeout 3600s;
        }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: fxhnt-ingress
  namespace: foxhunt
  labels: { app.kubernetes.io/name: fxhnt-ingress, app.kubernetes.io/part-of: foxhunt }
spec:
  replicas: 1
  selector:
    matchLabels: { app.kubernetes.io/name: fxhnt-ingress }
  template:
    metadata:
      labels: { app.kubernetes.io/name: fxhnt-ingress, app.kubernetes.io/part-of: foxhunt }
    spec:
      serviceAccountName: tailscale-fxhnt-ingress
      nodeSelector:
        k8s.scaleway.com/pool-name: large
      containers:
        - name: nginx
          image: nginx:alpine
          ports:
            - { containerPort: 80,  name: http }
            - { containerPort: 443, name: https }
          volumeMounts:
            - { name: nginx-conf, mountPath: /etc/nginx/conf.d, readOnly: true }
            - { name: wildcard-cert, mountPath: /etc/nginx/certs, readOnly: true }
          resources:
            requests: { cpu: 25m, memory: 32Mi }
            limits:   { cpu: 200m, memory: 128Mi }
        # git SSH: git.fxhnt.ai:22 -> bizworx gitea-ssh (ns gitea). socat TCP forward on the same tailnet node.
        - name: ssh-proxy
          image: alpine/socat:latest
          args:
            - TCP-LISTEN:22,fork,reuseaddr
            - TCP:gitea-clusterip.gitea.svc.cluster.local:22
          resources:
            requests: { cpu: 10m, memory: 16Mi }
            limits:   { cpu: 100m, memory: 64Mi }
        - name: tailscale
          image: ghcr.io/tailscale/tailscale:stable
          env:
            - { name: TS_HOSTNAME, value: "fxhnt-ingress" }
            - { name: TS_KUBE_SECRET, value: "fxhnt-ingress-ts-state" }
            - { name: TS_USERSPACE, value: "false" }
            - name: TS_AUTHKEY
              valueFrom: { secretKeyRef: { name: tailscale-auth, key: TS_AUTHKEY } }
            - name: POD_NAME
              valueFrom: { fieldRef: { fieldPath: metadata.name } }
            - name: POD_UID
              valueFrom: { fieldRef: { fieldPath: metadata.uid } }
          securityContext:
            capabilities: { add: ["NET_ADMIN"] }
          resources:
            requests: { cpu: 25m, memory: 64Mi }
            limits:   { cpu: 200m, memory: 128Mi }
      volumes:
        - name: nginx-conf
          configMap: { name: fxhnt-ingress-nginx }
        - name: wildcard-cert
          secret: { secretName: fxhnt-wildcard-tls }
---
# ClusterIP Service (in-cluster reachability; the public entrypoint is the tailscale node, not this Service)
apiVersion: v1
kind: Service
metadata:
  name: fxhnt-ingress
  namespace: foxhunt
  labels: { app.kubernetes.io/name: fxhnt-ingress, app.kubernetes.io/part-of: foxhunt }
spec:
  selector: { app.kubernetes.io/name: fxhnt-ingress }
  ports:
    - { port: 443, targetPort: 443, name: https }
    - { port: 80,  targetPort: 80,  name: http }
    - { port: 22,  targetPort: 22,  name: ssh }
---
# NetworkPolicy: allow this proxy the egress it needs (postgres NOT needed; it only fronts HTTP/SSH to services).
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: fxhnt-ingress
  namespace: foxhunt
  labels: { app.kubernetes.io/name: fxhnt-ingress, app.kubernetes.io/part-of: foxhunt }
spec:
  podSelector: { matchLabels: { app.kubernetes.io/name: fxhnt-ingress } }
  policyTypes: [Egress]
  egress:
    - ports: [{ port: 53, protocol: UDP }, { port: 53, protocol: TCP }]   # DNS
    - to: [{ podSelector: { matchLabels: { app.kubernetes.io/name: fxhnt-dashboard } } }]
      ports: [{ port: 8080, protocol: TCP }, { port: 80, protocol: TCP }]   # cockpit
    - to: [{ podSelector: { matchLabels: { app.kubernetes.io/name: dagster } } }]
      ports: [{ port: 3000, protocol: TCP }]   # dagster webserver
    - to: [{ namespaceSelector: { matchLabels: { kubernetes.io/metadata.name: gitea } } }]
      ports: [{ port: 3000, protocol: TCP }, { port: 22, protocol: TCP }]   # gitea web + ssh (cross-ns)
    - to: [{ ipBlock: { cidr: 172.16.0.11/32 } }]   # kube-apiserver (tailscale sidecar ts-state Secret; post-DNAT dest)
      ports: [{ port: 6443, protocol: TCP }]
    - to: [{ ipBlock: { cidr: 0.0.0.0/0, except: [10.32.0.0/16, 172.16.0.0/16] } }]   # tailscale coordination/DERP
      ports: [{ port: 443, protocol: TCP }, { port: 3478, protocol: UDP }, { port: 41641, protocol: UDP }]
