# Dagster B0 — the lifecycle orchestrator (combined-book asset graph) on the cluster.
#
# ONE pod, three containers (webserver + daemon + tailscale userspace sidecar) sharing the RWO
# `fxhnt-surfer-data` PVC (warehouse.duckdb + forward state live there). RWO = single-node, so webserver
# and daemon MUST co-locate; the daemon runs the daily schedule, runs execute in-process (DefaultRunLauncher).
# Storage (runs/events/schedules) is the existing cockpit Postgres, separate `dagster` DB. UI is tailnet-
# private via `tailscale serve` (CGNAT-safe userspace mode, mirrors fxhnt-dashboard) → fxhnt-dagster.<tailnet>.ts.net
#
# Cutover (RWO-forced): bringing this up REPLACES the fxhnt-forward CronJob — suspend that cron at go-live.
# Revert = `kubectl -n foxhunt scale deploy/dagster --replicas=0` (releases the PVC) + un-suspend the cron.
# Data-path revert (independent) = set FXHNT_COMBINED_BOOK_DATA_SOURCE=raw below.
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: dagster-instance
  namespace: foxhunt
data:
  dagster.yaml: |
    storage:
      postgres:
        postgres_db:
          username: foxhunt
          password:
            env: PGPASSWORD
          hostname: postgres.foxhunt.svc.cluster.local
          db_name: dagster
          port: 5432
    telemetry:
      enabled: false
  workspace.yaml: |
    load_from:
      - python_module:
          module_name: fxhnt.adapters.orchestration.definitions
          working_directory: /app
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: fxhnt-dagster-ts-serve
  namespace: foxhunt
data:
  serve.json: |
    {
      "TCP": { "443": { "HTTPS": true } },
      "Web": {
        "${TS_CERT_DOMAIN}:443": {
          "Handlers": { "/": { "Proxy": "http://127.0.0.1:3000" } }
        }
      }
    }
---
apiVersion: v1
kind: ServiceAccount
metadata: { name: tailscale-dagster, namespace: foxhunt }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: tailscale-dagster, namespace: foxhunt }
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["create", "get", "update", "patch", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: tailscale-dagster, namespace: foxhunt }
subjects:
  - { kind: ServiceAccount, name: tailscale-dagster, namespace: foxhunt }
roleRef: { kind: Role, name: tailscale-dagster, apiGroup: rbac.authorization.k8s.io }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dagster
  namespace: foxhunt
  labels: { app.kubernetes.io/name: dagster, app.kubernetes.io/part-of: foxhunt }
spec:
  replicas: 1
  strategy: { type: Recreate }          # RWO PVC: never run two pods holding it at once
  selector: { matchLabels: { app.kubernetes.io/name: dagster } }
  template:
    metadata:
      labels: { app.kubernetes.io/name: dagster, app.kubernetes.io/part-of: foxhunt }   # part-of -> postgres ingress + base egress
    spec:
      serviceAccountName: tailscale-dagster
      nodeSelector: { k8s.scaleway.com/pool-name: platform }
      imagePullSecrets: [{ name: gitlab-registry }]
      initContainers:
        - name: dagster-home-init                       # DAGSTER_HOME must be writable; seed it from the configmap
          image: busybox:1.36
          command: ["sh", "-c", "cp /cfg/dagster.yaml /cfg/workspace.yaml /dagster-home/"]
          volumeMounts:
            - { name: dagster-home, mountPath: /dagster-home }
            - { name: dagster-cfg, mountPath: /cfg, readOnly: true }
      containers:
        - name: webserver
          image: gitlab-registry.foxhunt.svc.cluster.local:5000/root/foxhunt/fxhnt-cockpit:latest
          command: ["dagster-webserver", "-h", "0.0.0.0", "-p", "3000",
                    "-w", "/dagster-home/workspace.yaml"]
          env: &dagster_env
            - { name: DAGSTER_HOME, value: /dagster-home }
            - { name: FXHNT_COMBINED_BOOK_DATA_SOURCE, value: "warehouse" }
            - { name: FXHNT_WAREHOUSE_PATH, value: /data/surfer/warehouse.duckdb }
            - { name: FXHNT_SURFER_DATA_DIR, value: /data/surfer }
            - { name: FXHNT_OPERATIONAL_DSN, value: "postgresql+psycopg://foxhunt@postgres.foxhunt.svc.cluster.local:5432/fxhnt" }
            - { name: FXHNT_DATABENTO_MAX_COST_USD, value: "40.0" }
            - name: PGPASSWORD
              valueFrom: { secretKeyRef: { name: db-credentials, key: password } }
            - name: DATABENTO_API_KEY
              valueFrom: { secretKeyRef: { name: databento-credentials, key: api-key } }
            - name: TIINGO_API_KEY                          # equity sleeves (sixtyforty/multistrat/gd) fetch Tiingo adjClose
              valueFrom: { secretKeyRef: { name: tiingo-api, key: key } }
          readinessProbe: { tcpSocket: { port: 3000 }, initialDelaySeconds: 10, periodSeconds: 15 }
          volumeMounts:
            - { name: dagster-home, mountPath: /dagster-home }
            - { name: surfer-data, mountPath: /data }
          resources: { requests: { cpu: 100m, memory: 512Mi }, limits: { cpu: "1", memory: 1Gi } }
        - name: daemon
          image: gitlab-registry.foxhunt.svc.cluster.local:5000/root/foxhunt/fxhnt-cockpit:latest
          command: ["dagster-daemon", "run", "-w", "/dagster-home/workspace.yaml"]
          env: *dagster_env
          volumeMounts:
            - { name: dagster-home, mountPath: /dagster-home }
            - { name: surfer-data, mountPath: /data }
          resources: { requests: { cpu: 100m, memory: 512Mi }, limits: { cpu: "2", memory: 2Gi } }
        - name: tailscale
          image: ghcr.io/tailscale/tailscale:latest
          env:
            - name: TS_AUTHKEY
              valueFrom: { secretKeyRef: { name: tailscale-auth, key: TS_AUTHKEY } }
            - { name: TS_KUBE_SECRET, value: fxhnt-dagster-ts-state }
            - { name: TS_USERSPACE, value: "true" }
            - { name: TS_HOSTNAME, value: "fxhnt-dagster" }
            - { name: TS_SERVE_CONFIG, value: /etc/ts/serve.json }
          volumeMounts:
            - { name: ts-serve, mountPath: /etc/ts, readOnly: true }
          resources: { requests: { cpu: 25m, memory: 64Mi }, limits: { cpu: 200m, memory: 128Mi } }
      volumes:
        - { name: dagster-home, emptyDir: {} }
        - { name: dagster-cfg, configMap: { name: dagster-instance } }
        - { name: ts-serve, configMap: { name: fxhnt-dagster-ts-serve } }
        - { name: surfer-data, persistentVolumeClaim: { claimName: fxhnt-surfer-data } }
---
apiVersion: v1
kind: Service
metadata: { name: dagster, namespace: foxhunt }
spec:
  selector: { app.kubernetes.io/name: dagster }
  ports: [{ port: 80, targetPort: 3000, name: http }]
---
# Egress: postgres (storage + cockpit ingest) + DNS + public HTTPS (Binance/Databento fetch in the assets)
# + tailscale coordination/DERP. part-of=foxhunt already grants base egress + satisfies postgres ingress.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: dagster, namespace: foxhunt, labels: { app.kubernetes.io/part-of: foxhunt } }
spec:
  podSelector: { matchLabels: { app.kubernetes.io/name: dagster } }
  policyTypes: [Egress]
  egress:
    - to: [{ podSelector: { matchLabels: { app.kubernetes.io/name: postgres } } }]
      ports: [{ port: 5432, protocol: TCP }]
    - ports: [{ port: 53, protocol: UDP }, { port: 53, protocol: TCP }]
    - to: [{ ipBlock: { cidr: 0.0.0.0/0, except: [10.32.0.0/16, 172.16.0.0/16] } }]   # Binance/Databento + tailscale
      ports: [{ port: 443, protocol: TCP }, { port: 3478, protocol: UDP }, { port: 41641, protocol: UDP }]
