feat(zoo-exchange): KMS-backed secrets, drop init-container patch script

* Replaced init-container ConfigMap (sed URL rewrites + fetch interceptor +
  CSP injection) with clean SPA wiring — secrets come from KMSSecret CRD
  syncing /zoo path from hanzo-exchange KMS project to zoo-exchange-secrets
  in the cluster.
* Image moves from ghcr.io/luxfi/exchange@sha256:9b97... (digest pin +
  nodeAffinity hack) to ghcr.io/zooai/exchange:latest with normal pull.
* SPA_INSIGHTS_API_KEY arrives via envFrom: secretRef. SPA runtime
  templates /config.json from SPA_* env at pod start; brand singleton
  reads insightsApiKey at boot.
* Removed wildcard *.api.zoo.exchange Ingress + cert (no more fake
  cloudflare-prefix subdomains — events go to insights.hanzo.ai directly).
This commit is contained in:
2026-04-25 20:52:18 -07:00
parent d30ea0b979
commit 7417fbf070
+50 -85
View File
@@ -1,44 +1,40 @@
---
# zoo.exchange — hanzoai/spa runtime, no nginx, in `zoo` namespace.
# Init container reuses the SPA bundle baked into the cached luxfi/exchange
# image (digest pin so we don't need a fresh ghcr-luxfi PAT on disk; the
# image is already on cluster nodes lux-nodes-vvfa0/kzit5/kzitk).
apiVersion: v1
kind: ConfigMap
# zoo.exchange — hanzoai/spa serving the @luxfi/exchange SPA bundle.
#
# Secrets ALL come from Hanzo KMS via KMSSecret CRD.
# DNS: zoo.exchange / www.zoo.exchange / exchange.zoo.network / trade.zoo.exchange →
# lux-k8s LB 134.199.138.27 → ingress → this Service.
# Per-org Insights API key lives at KMS path /zoo in the hanzo-exchange project.
# Public chain RPC + bridge endpoints come from the SPA brand config (/config.json),
# which is templated by the hanzoai/spa runtime from the SPA_* env vars below.
apiVersion: kms.hanzo.ai/v1alpha1
kind: KMSSecret
metadata:
name: zoo-exchange-patch
name: zoo-exchange
namespace: zoo
data:
patch.sh: |
#!/bin/sh
set -eu
SRC="${SRC:-/usr/share/nginx/html}"
cp -a "$SRC"/. /public/
INDEX=/public/index.html
if [ -f "$INDEX" ]; then
# Title — Zoo
sed -i 's|<title>Lux Exchange</title>|<title>Zoo Exchange — Trade ZOO and every token on Zoo Network</title>|g' "$INDEX"
sed -i 's|<title>Uniswap Interface</title>|<title>Zoo Exchange — Trade ZOO and every token on Zoo Network</title>|g' "$INDEX"
# CSP — append Zoo + real gateway origins to connect-src
sed -i 's|wss://nbstream.binance.com|https://*.gateway.uniswap.org wss://*.gateway.uniswap.org https://*.gateway.zoo.exchange wss://*.gateway.zoo.exchange https://api.statsig.com https://*.statsig.com https://api.zoo.network https://api.zoo-test.network https://api.zoo-dev.network https://kms.zoo.network https://iam.zoo.network https://zoolabs.id wss://*.zoo.network wss://*.zoo.exchange wss://nbstream.binance.com|g' "$INDEX"
echo "[patch] index.html updated"
fi
# Rewrite bogus gateway.lux.org (fork placeholder, no matching
# deployment) → real gateway.uniswap.org in every built asset. This
# unblocks Statsig gating (gating.interface.gateway.lux.org) which
# was 404ing and leaving the app in an infinite init loop → blank page.
find /public -type f \( -name "*.js" -o -name "*.html" -o -name "*.css" -o -name "*.json" \) \
-exec sed -i 's|gateway\.lux\.org|gateway.uniswap.org|g' {} + 2>/dev/null || true
echo "[patch] gateway.lux.org → gateway.uniswap.org rewritten in assets"
spec:
authentication:
universalAuth:
credentialsRef:
secretName: kms-zoo-exchange-credentials
secretNamespace: zoo
managedSecretReference:
secretName: zoo-exchange-secrets
secretNamespace: zoo
secretType: Opaque
resyncInterval: 60
secretsScope:
projectSlug: hanzo-exchange
environmentSlug: prod
secretsPath: /zoo
recursive: false
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: zoo-exchange
namespace: zoo
labels:
app: zoo-exchange
brand: zoo
labels: { app: zoo-exchange, brand: zoo }
spec:
replicas: 2
selector:
@@ -48,60 +44,33 @@ spec:
rollingUpdate: { maxSurge: 1, maxUnavailable: 0 }
template:
metadata:
labels:
app: zoo-exchange
brand: zoo
labels: { app: zoo-exchange, brand: zoo }
spec:
# Schedule onto a node that already has the luxfi/exchange image
# cached — avoids the expired ghcr-luxfi PAT pull failure.
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- lux-nodes-vvfa0
- lux-nodes-kzit5
- lux-nodes-kzitk
volumes:
- name: spa
emptyDir: {}
- name: patch
configMap:
name: zoo-exchange-patch
defaultMode: 0755
initContainers:
- name: extract
image: ghcr.io/luxfi/exchange@sha256:9b9765e099d4fe4d377efe0e3e02c5f09d5628f644922eef3c6cc9ef5b57c6c5
imagePullPolicy: IfNotPresent
command: ["/patch/patch.sh"]
volumeMounts:
- { name: spa, mountPath: /public }
- { name: patch, mountPath: /patch }
env:
# The original image keeps the SPA bundle in /usr/share/nginx/html.
- { name: SRC, value: /usr/share/nginx/html }
imagePullSecrets:
- name: ghcr-zoo
containers:
- name: spa
image: ghcr.io/hanzoai/spa:1.2.0
imagePullPolicy: IfNotPresent
image: ghcr.io/zooai/exchange:latest
imagePullPolicy: Always
ports:
- { containerPort: 3000, name: http }
# Secrets sourced from KMS — single source of truth.
envFrom:
- secretRef: { name: zoo-exchange-secrets }
# Public, non-secret runtime config templated into /config.json on pod start.
env:
- { name: PORT, value: "3000" }
- { name: ROOT, value: /public }
# Runtime /config.json — overrides per env via SPA_*
- { name: SPA_BRAND_NAME, value: "Zoo Exchange" }
- { name: SPA_DEFAULT_CHAIN, value: "200200" }
- { name: SPA_RPC_HOST, value: "https://api.zoo.network" }
- { name: SPA_IAM_HOST, value: "https://iam.zoo.network" }
- { name: SPA_ID_HOST, value: "https://zoolabs.id" }
- { name: SPA_KMS_HOST, value: "https://kms.zoo.network" }
- { name: SPA_GATEWAY_HOST, value: "https://dex.lux.network" }
volumeMounts:
- { name: spa, mountPath: /public }
- { name: PORT, value: "3000" }
- { name: ROOT, value: /public }
- { name: SPA_BRAND_NAME, value: "Zoo Exchange" }
- { name: SPA_DEFAULT_CHAIN, value: "200200" }
- { name: SPA_RPC_HOST, value: "https://api.zoo.network" }
- { name: SPA_API_HOST, value: "https://api.zoo.exchange" }
- { name: SPA_GATEWAY_HOST, value: "https://api.zoo.exchange" }
- { name: SPA_IAM_HOST, value: "https://iam.zoo.network" }
- { name: SPA_ID_HOST, value: "https://zoolabs.id" }
- { name: SPA_KMS_HOST, value: "https://kms.zoo.network" }
- { name: SPA_INSIGHTS_HOST, value: "https://insights.hanzo.ai" }
# SPA_INSIGHTS_API_KEY arrives via envFrom (zoo-exchange-secrets).
readinessProbe:
httpGet: { path: /health, port: http }
initialDelaySeconds: 2
@@ -136,11 +105,7 @@ metadata:
spec:
ingressClassName: ingress
tls:
- hosts:
- zoo.exchange
- www.zoo.exchange
- exchange.zoo.network
- trade.zoo.exchange
- hosts: [zoo.exchange, www.zoo.exchange, exchange.zoo.network, trade.zoo.exchange]
secretName: zoo-exchange-tls
rules:
- host: zoo.exchange